21
21
import numpy as np
22
22
import scipy as sp
23
23
import scipy .stats as stats
24
+ from sklearn import linear_model
24
25
25
26
from . import utils
26
27
from .utils import APPROX_BDAYS_PER_MONTH , APPROX_BDAYS_PER_YEAR
@@ -551,27 +552,27 @@ def rolling_beta(returns, factor_returns,
551
552
def rolling_fama_french (returns , factor_returns = None ,
552
553
rolling_window = APPROX_BDAYS_PER_MONTH * 6 ):
553
554
"""
554
- Computes rolling Fama-French single factor betas.
555
+ Computes rolling Fama-French single factor betas using a multivariate
556
+ linear regression (separate linear regressions is problematic because
557
+ the Fama-French factors are confounded).
555
558
556
- Specifically, returns SMB, HML, and UMD.
559
+ Specifically, returns rolling betas to SMB, HML, and UMD.
557
560
558
561
Parameters
559
562
----------
560
563
returns : pd.Series
561
564
Daily returns of the strategy, noncumulative.
562
565
- See full explanation in tears.create_full_tear_sheet.
563
566
factor_returns : pd.DataFrame, optional
564
- data set containing the Fama-French risk factors. See
567
+ Data set containing the Fama-French risk factors. See
565
568
utils.load_portfolio_risk_factors.
566
569
rolling_window : int, optional
567
- The days window over which to compute the beta.
568
- Default is 6 months.
570
+ The days window over which to compute the beta. Defaults to 6 months.
569
571
570
572
Returns
571
573
-------
572
574
pandas.DataFrame
573
- DataFrame containing rolling beta coefficients for SMB, HML
574
- and UMD
575
+ DataFrame containing rolling beta coefficients to SMB, HML and UMD
575
576
"""
576
577
577
578
if factor_returns is None :
@@ -580,8 +581,26 @@ def rolling_fama_french(returns, factor_returns=None,
580
581
factor_returns = factor_returns .drop (['Mkt-RF' , 'RF' ],
581
582
axis = 'columns' )
582
583
583
- return rolling_beta (returns , factor_returns ,
584
- rolling_window = rolling_window )
584
+ # add constant to regression
585
+ factor_returns ['const' ] = 1
586
+
587
+ # have NaNs when there is insufficient data to do a regression
588
+ regression_coeffs = np .empty ((rolling_window ,
589
+ len (factor_returns .columns )))
590
+ regression_coeffs .fill (np .nan )
591
+
592
+ for beg , end in zip (factor_returns .index [:- rolling_window ],
593
+ factor_returns .index [rolling_window :]):
594
+ coeffs = linear_model .LinearRegression ().fit (factor_returns [beg :end ],
595
+ returns [beg :end ]).coef_
596
+ regression_coeffs = np .append (regression_coeffs , [coeffs ], axis = 0 )
597
+
598
+ rolling_fama_french = pd .DataFrame (data = regression_coeffs [:, :3 ],
599
+ columns = ['SMB' , 'HML' , 'UMD' ],
600
+ index = factor_returns .index )
601
+ rolling_fama_french .index .name = None
602
+
603
+ return rolling_fama_french
585
604
586
605
587
606
def gross_lev (positions ):
0 commit comments