Notebook

Common Risk Factor Performance

by Max Margenot

This is a quick notebook meant to provide a snapshot of the performance of common risk factors over the past year, using whatever today is as a reference point.

This notebook is more useful for getting a sense of the behavior of the market over the past year than it is for algorithm development, but it's always important to keep in mind that algorithms run within the context of the market. As market regimes shift, the way your strategy behaves may change, so keeping an eye on the market should prove useful, even if you are designing a strategy with neutrality to common factor risk in mind.

For more information on the common risk factors used in this notebook, check out the Quantopian Risk Model.

In [1]:
from quantopian.research.experimental import get_factor_returns
import matplotlib.pyplot as plt
import datetime as dt
import qgrid
In [2]:
today = dt.datetime.today() - dt.timedelta(days=1)
#last_year = dt.datetime.today() - dt.timedelta(weeks=52)
last_year = dt.datetime.today() - dt.timedelta(weeks=52*10)
In [3]:
factor_returns = get_factor_returns(start=last_year, end=today)

Here is a quick use-case of qgrid. Try examining the performance of style risk factors when you filter the sector risk factor returns to within $r_{factor} < |0.2|$, or try examining how momentum performs on days where size is small.

In [4]:
qgrid_widget = qgrid.show_grid(factor_returns, show_toolbar=False)
In [5]:
qgrid_widget

Failed to display Jupyter Widget of type QgridWidget.

If you're reading this message in the Jupyter Notebook or JupyterLab Notebook, it may mean that the widgets JavaScript is still loading. If this message persists, it likely means that the widgets JavaScript library is either not installed or not enabled. See the Jupyter Widgets Documentation for setup instructions.

If you're reading this message in another frontend (for example, a static rendering on GitHub or NBViewer), it may mean that your frontend doesn't currently support widgets.

Summary Statistics of Common Risk Factor Returns

Here we calculate the summary statistics of sector and style risk factors over the past year.

In [6]:
summary_stats = factor_returns.describe()
In [7]:
summary_stats[[
    'basic_materials',
    'consumer_cyclical',
    'financial_services',
    'real_estate',
    'consumer_defensive',
    'health_care',
    'utilities',
    'communication_services',
    'energy',
    'industrials',
    'technology'
]]
Out[7]:
basic_materials consumer_cyclical financial_services real_estate consumer_defensive health_care utilities communication_services energy industrials technology
count 2509.000000 2509.000000 2509.000000 2509.000000 2509.000000 2509.000000 2509.000000 2509.000000 2509.000000 2509.000000 2509.000000
mean 0.000403 0.000686 0.000522 0.000490 0.000397 0.000555 0.000376 0.000325 0.000309 0.000517 0.000659
std 0.015447 0.013256 0.020618 0.020739 0.008943 0.010880 0.011281 0.013890 0.018100 0.013633 0.012691
min -0.115069 -0.093750 -0.164697 -0.205874 -0.065084 -0.072157 -0.080584 -0.084548 -0.170049 -0.089182 -0.082613
25% -0.006122 -0.004563 -0.006452 -0.005619 -0.003709 -0.004124 -0.004936 -0.006109 -0.007341 -0.004914 -0.004453
50% 0.000867 0.001197 0.000687 0.000783 0.000662 0.000954 0.000742 0.000854 0.000410 0.000831 0.000989
75% 0.007616 0.006630 0.007874 0.006875 0.004777 0.006066 0.005937 0.007088 0.008267 0.006573 0.006270
max 0.117324 0.081794 0.171779 0.176318 0.091073 0.111789 0.125871 0.137725 0.187807 0.092884 0.118323
In [8]:
summary_stats[[
    'momentum',
    'size',
    'value',
    'short_term_reversal',
    'volatility'
]]
Out[8]:
momentum size value short_term_reversal volatility
count 2509.000000 2509.000000 2509.000000 2509.000000 2509.000000
mean 0.000008 -0.000038 0.000350 0.000132 -0.000286
std 0.001937 0.001901 0.017158 0.001259 0.001889
min -0.012030 -0.013557 -0.011089 -0.009015 -0.012397
25% -0.000978 -0.001095 -0.000634 -0.000464 -0.001424
50% 0.000095 0.000012 -0.000005 0.000090 -0.000280
75% 0.001143 0.001051 0.000660 0.000672 0.000838
max 0.009336 0.010450 0.856911 0.016160 0.010302

Performance Statistics

Here we plot the mean return of each common risk factor and the volatility of the returns of each risk factor. It seems like the volatility of each style factor is generally lower than the volatility of each sector factor, though more observations should be accumulated before stating that with confidence.

In [9]:
summary_stats.loc['mean'].plot(kind='bar',
                               title='Mean of Common Risk Factor Returns');
In [10]:
summary_stats.loc['std'].plot(kind='bar',
                              title='Volatility of Common Risk Factor Returns');

Daily Common Factor Returns

Choose which risk factor you'd like to examine in more detail. The following plot will show it alongside bars for one, two, and three standard deviations away from the mean return. The idea here is to get a sense of the outliers in the returns of risk factors over the past year.

In [11]:
selected_risk_factor = 'momentum'
In [12]:
factor_returns[selected_risk_factor].plot(title='{0} Risk Factor Returns'.format(selected_risk_factor))
# First standard deviation
plt.axhline(summary_stats[selected_risk_factor].loc['mean'] + \
            summary_stats[selected_risk_factor].loc['std'],
            linestyle='--',
            color='purple',
            alpha=1.0, 
            label='1 Standard Deviation of {0} Returns'.format(selected_risk_factor))
plt.axhline(summary_stats[selected_risk_factor].loc['mean'] - \
            summary_stats[selected_risk_factor].loc['std'], linestyle='--', color='purple', alpha=1.0)
# Second standard deviation
plt.axhline(summary_stats[selected_risk_factor].loc['mean'] + \
            2 * summary_stats[selected_risk_factor].loc['std'],
            linestyle='--',
            color='orange',
            alpha=1.0, 
            label='2 Standard Deviations of {0} Returns'.format(selected_risk_factor))
plt.axhline(summary_stats[selected_risk_factor].loc['mean'] - \
            2 * summary_stats[selected_risk_factor].loc['std'], linestyle='--', color='orange', alpha=1.0)
# Third standard deviation
plt.axhline(summary_stats[selected_risk_factor].loc['mean'] + \
            3 * summary_stats[selected_risk_factor].loc['std'],
            linestyle='--',
            color='r',
            alpha=1.0, 
            label='3 Standard Deviations of {0} Returns'.format(selected_risk_factor))
plt.axhline(summary_stats[selected_risk_factor].loc['mean'] - \
            3 * summary_stats[selected_risk_factor].loc['std'], linestyle='--', color='r', alpha=1.0)
plt.legend();

Cumulative Returns of Common Risk Factors

And, finally, here we examine the cumulative returns of each common risk factor. Sector factors seem to be collectively impacted by significant market shifts, while style factors each react differently.

In [13]:
def graph_legend_lastrow_basis(df, plot_name=None):
    lastrow = df.iloc[-1,:].values      # Latest values 
    order   = np.argsort(lastrow)[::-1][:]  # Descending sort producing indices of sectors
    df      = df.reindex_axis(df.columns[order], axis = 1)  #Reorder columns, based on latest(last) values of cum returns
    df.plot(title=plot_name);
In [14]:
((1 + factor_returns[[
    'basic_materials',
    'consumer_cyclical',
    'financial_services',
    'real_estate',
    'consumer_defensive',
    'health_care',
    'utilities',
    'communication_services',
    'energy',
    'industrials',
    'technology'
]]).cumprod() - 1).plot(title='Cumulative Sector Factor Returns');
In [15]:
import numpy as np
grph = ((1 + factor_returns[[
    'basic_materials',
    'consumer_cyclical',
    'financial_services',
    'real_estate',
    'consumer_defensive',
    'health_care',
    'utilities',
    'communication_services',
    'energy',
    'industrials',
    'technology'
]]).cumprod() - 1).rolling(window = 66).mean()

graph_legend_lastrow_basis(grph, plot_name='Cumulative Sector Factor Returns')
In [16]:
grph_style = ((1 + factor_returns[[
    'momentum',
    'size',
    'value',
    'short_term_reversal',
    'volatility'
]]).cumprod() - 1).rolling(window = 66).mean()

graph_legend_lastrow_basis(grph_style, plot_name='Cumulative Style Factor Returns')