Skip to content

Commit 17bbfe3

Browse files
authored
Merge pull request #406 from quantopian/ff_multivar
Fama-French multivariate regression
2 parents 8f0a969 + cfe4600 commit 17bbfe3

File tree

3 files changed

+30
-10
lines changed

3 files changed

+30
-10
lines changed

.travis.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ before_install:
2525
- cp pyfolio/tests/matplotlibrc .
2626

2727
install:
28-
- conda create -q -n testenv --yes python=$TRAVIS_PYTHON_VERSION ipython pyzmq numpy scipy nose matplotlib pandas Cython patsy flake8 seaborn runipy pytables networkx pandas-datareader matplotlib-tests joblib
28+
- conda create -q -n testenv --yes python=$TRAVIS_PYTHON_VERSION ipython pyzmq numpy scipy nose matplotlib pandas Cython patsy flake8 seaborn scikit-learn runipy pytables networkx pandas-datareader matplotlib-tests joblib
2929
- source activate testenv
3030
- pip install nose_parameterized
3131
#- pip install --no-deps git+https://github.com/quantopian/zipline

pyfolio/timeseries.py

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import numpy as np
2222
import scipy as sp
2323
import scipy.stats as stats
24+
from sklearn import linear_model
2425

2526
from . import utils
2627
from .utils import APPROX_BDAYS_PER_MONTH, APPROX_BDAYS_PER_YEAR
@@ -551,27 +552,27 @@ def rolling_beta(returns, factor_returns,
551552
def rolling_fama_french(returns, factor_returns=None,
552553
rolling_window=APPROX_BDAYS_PER_MONTH * 6):
553554
"""
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).
555558
556-
Specifically, returns SMB, HML, and UMD.
559+
Specifically, returns rolling betas to SMB, HML, and UMD.
557560
558561
Parameters
559562
----------
560563
returns : pd.Series
561564
Daily returns of the strategy, noncumulative.
562565
- See full explanation in tears.create_full_tear_sheet.
563566
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
565568
utils.load_portfolio_risk_factors.
566569
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.
569571
570572
Returns
571573
-------
572574
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
575576
"""
576577

577578
if factor_returns is None:
@@ -580,8 +581,26 @@ def rolling_fama_french(returns, factor_returns=None,
580581
factor_returns = factor_returns.drop(['Mkt-RF', 'RF'],
581582
axis='columns')
582583

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
585604

586605

587606
def gross_lev(positions):

setup.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
'pandas>=0.19.0',
5252
'pytz>=2014.10',
5353
'scipy>=0.14.0',
54+
'scikit-learn>=0.18.2'
5455
'seaborn>=0.7.1',
5556
'pandas-datareader>=0.2',
5657
'empyrical>=0.3.0'

0 commit comments

Comments
 (0)