FinanceRoutines.jl

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

beta.md (3983B)


      1 # Estimating Stock Betas
      2 
      3 This describes the step by step procedure to estimate betas stock by stock first unconditionally and then using rolling windows.
      4 We first download the monthly stock file.
      5 
      6 
      7 Since we are about to download data from CRSP we set up a connection with our WRDS credentials
      8 ```julia
      9 using FinanceRoutines
     10 using DataFrames, DataPipes, Dates
     11 using FixedEffectModels # for regressions
     12 
     13 const wrds_conn = FinanceRoutines.open_wrds_pg()
     14 const date_init = Date("1990-01-01")
     15 ```
     16 
     17 We are ready to import the monthly stock file:
     18 ```julia
     19 @time df_msf_raw = import_MSF_v2(wrds_conn; date_range = (date_init, Dates.today())); 
     20 ```
     21 
     22 And the Fama-French three pricing factors from Ken French's website.
     23 This downloads directly data from Ken French's website and formats the data
     24 ```julia
     25 df_FF3 = import_FF3()
     26 # make sure the returns are expressed in the same unit as in the MSF
     27 transform!(df_FF3, [:mktrf, :smb, :hml, :rf] .=> ByRow((x->x/100)), renamecols=false )
     28 ```
     29 
     30 
     31 ## Unconditional Stock Betas
     32 
     33 
     34 ### Format the monthly stock file from CRSP
     35 
     36 ```julia
     37 # keep only what we need from the MSF
     38 df_msf = select(df_msf_raw, :permno, :mthcaldt => :date, :datem, 
     39     [:mthret, :mthcap] .=> ByRow(passmissing(Float64)) .=> [:ret, :mthcap]) # convert from decimals
     40 ```
     41 
     42 
     43 ### Merge the data and estimate beta
     44 
     45 ```julia
     46 # Merge the data
     47 df_msf = leftjoin(df_msf, df_FF3, on = [:datem] )
     48 # Create excess return
     49 transform!(df_msf, [:ret, :rf] => ( (r1, r0) -> r1 .- r0 ) => :ret_rf)
     50 
     51 # Estimate CAPM beta over the whole sample
     52 sort!(df_msf, [:permno, :date])
     53 for subdf in groupby(df_msf, :permno)
     54     if size(dropmissing(subdf, [:ret_rf, :mktrf]))[1] > 2
     55         β_CAPM = coef(reg(subdf, @formula(ret_rf ~ mktrf)))[2]
     56         subdf[:, :β_CAPM ] .= β_CAPM
     57     else
     58         subdf[:, :β_CAPM ] .= missing
     59     end
     60 end
     61 select(unique(df_msf, [:permno, :β_CAPM]), :permno, :β_CAPM)
     62 
     63 # Estimate 3 Factor betas
     64 for subdf in groupby(df_msf, :permno)
     65     if size(dropmissing(subdf, [:ret_rf, :mktrf, :smb, :hml]))[1] > 2
     66         β_MKT, β_SMB, β_HML = coef(reg(subdf, @formula(ret_rf ~ mktrf + smb + hml)))[2:4]
     67         subdf[:, :β_MKT ] .= β_MKT
     68         subdf[:, :β_SMB ] .= β_SMB
     69         subdf[:, :β_HML ] .= β_HML
     70     else
     71         subdf[:, :β_MKT ] .= missing; 
     72         subdf[:, :β_SMB ] .= missing; 
     73         subdf[:, :β_HML ] .= missing
     74     end
     75 end
     76 unique(df_msf, r"β")
     77 select(unique(df_msf, r"β"), :permno, :β_MKT, :β_SMB, :β_HML)
     78 ```
     79 
     80 
     81 
     82 ## Rolling Betas for Stocks
     83 
     84 I export a very simple function for rolling betas (see the test for examples). 
     85 
     86 First we prepare the basic dataset from the monthly stock file and the Fama-French risk factors for example
     87 ```julia
     88 # Get individual stock returns
     89 df_msf = select(df_msf_raw, :permno, :mthcaldt => :date, :datem, 
     90     [:mthret, :mthcap] .=> ByRow(passmissing(Float64)) .=> [:ret, :mthcap]) # convert from decimals
     91 # merge and create excess returns
     92 df_msf = leftjoin(df_msf, df_FF3, on = [:datem] )
     93 transform!(df_msf, [:ret, :rf] => ( (r1, r0) -> r1 .- r0 ) => :ret_rf)
     94 sort!(df_msf, [:permno, :date])
     95 ```
     96 
     97 Now we are ready to run the regression using the function `calculate_rolling_betas` that the package exports
     98 ```julia
     99 insertcols!(df_msf, :a=>missing, :bMKT=>missing, :bSMB=>missing, :bHML=>missing)
    100 @time for subdf in groupby(df_msf, :permno)
    101     β = calculate_rolling_betas(
    102         [ones(nrow(subdf)) subdf.mktrf subdf.smb subdf.hml],
    103         subdf.ret_rf; 
    104         window=60,         # 60 months
    105         min_data=nothing,   # what is the minimum number of nonmissing data to return a proper number
    106         method=:linalg
    107     )
    108     subdf[!, [:a, :bMKT, :bSMB, :bHML]] = β
    109 end
    110 
    111 import Statistics: median, mean
    112 @p df_msf |> groupby(__, :datem) |>
    113     combine(__, :bMKT .=>
    114         [(x-> mean(skipmissing(x))) (x-> median(skipmissing(x)))] .=>
    115         [:bMKT_mean :bMKT_median])
    116 ```
    117 Go make some coffee ... this takes a little while (~ 15mn on M2max macbook pro). 
    118 I don't think my method is super efficient 
    119 
    120 
    121