FinanceRoutines.jl

Financial data routines for Julia
Log | Files | Refs | README | LICENSE

EventStudy.jl (3232B)


      1 @testset "Event Study" begin
      2 
      3     import Dates: Date, Day
      4     import Statistics: mean
      5     import Random: seed!
      6 
      7     seed!(42)  # deterministic random draws
      8 
      9     # Build synthetic daily returns panel: 2 firms, 300 trading days
     10     dates = Date("2010-01-04"):Day(1):Date("2011-04-01")
     11     # Remove weekends for realistic trading calendar
     12     trading_days = filter(d -> Dates.dayofweek(d) <= 5, dates)
     13     trading_days = trading_days[1:300]  # exactly 300 days
     14 
     15     n = length(trading_days)
     16     df_ret = DataFrame(
     17         permno = vcat(fill(1, n), fill(2, n)),
     18         date = vcat(trading_days, trading_days),
     19         ret = vcat(0.001 .+ 0.01 .* randn(n), 0.0005 .+ 0.015 .* randn(n)),
     20         mktrf = vcat(repeat([0.0005 + 0.008 * randn()], n), repeat([0.0005 + 0.008 * randn()], n))
     21     )
     22     # Regenerate mktrf properly (same market for both firms on same date)
     23     mkt_returns = 0.0005 .+ 0.008 .* randn(n)
     24     df_ret.mktrf = vcat(mkt_returns, mkt_returns)
     25 
     26     # Inject a large positive event: +20% abnormal return on event day for firm 1
     27     # Must be large enough to dominate cumulative noise over the 21-day event window
     28     event_idx_firm1 = 270  # well within bounds for estimation window
     29     df_ret.ret[event_idx_firm1] += 0.20
     30 
     31     events = DataFrame(
     32         permno = [1, 2],
     33         event_date = [trading_days[event_idx_firm1], trading_days[280]]
     34     )
     35 
     36     # ---- Market-adjusted model ----
     37     @testset "Market-adjusted" begin
     38         result = event_study(events, df_ret; model=:market_adjusted)
     39         @test nrow(result) == 2
     40         @test "car" in names(result)
     41         @test "bhar" in names(result)
     42         @test "n_obs" in names(result)
     43         @test !ismissing(result.car[1])
     44         @test !ismissing(result.car[2])
     45         @test result.n_obs[1] == 21  # -10 to +10 inclusive
     46         # Firm 1 should have positive CAR (we injected +20%)
     47         @test result.car[1] > 0.10
     48     end
     49 
     50     # ---- Market model ----
     51     @testset "Market model" begin
     52         result = event_study(events, df_ret;
     53             model=:market_model,
     54             event_window=(-5, 5),
     55             estimation_window=(-250, -11))
     56         @test nrow(result) == 2
     57         @test !ismissing(result.car[1])
     58         @test result.n_obs[1] == 11  # -5 to +5
     59     end
     60 
     61     # ---- Mean-adjusted model ----
     62     @testset "Mean-adjusted" begin
     63         result = event_study(events, df_ret;
     64             model=:mean_adjusted,
     65             event_window=(-3, 3),
     66             estimation_window=(-200, -11))
     67         @test nrow(result) == 2
     68         @test !ismissing(result.car[1])
     69         @test result.n_obs[1] == 7  # -3 to +3
     70     end
     71 
     72     # ---- Edge cases ----
     73     @testset "Edge cases" begin
     74         # Entity not in returns
     75         events_missing = DataFrame(permno=[9999], event_date=[Date("2010-06-01")])
     76         result = event_study(events_missing, df_ret)
     77         @test ismissing(result.car[1])
     78         @test result.n_obs[1] == 0
     79 
     80         # Event too early (no estimation window)
     81         events_early = DataFrame(permno=[1], event_date=[trading_days[5]])
     82         result = event_study(events_early, df_ret)
     83         @test ismissing(result.car[1])
     84 
     85         # Invalid model
     86         @test_throws ArgumentError event_study(events, df_ret; model=:foo)
     87     end
     88 
     89 end