Comment on page
Factor exposures
The
FactorExposures
class enables you to create an exposure object to which different factor time series data can be attached.
To give some examples of how this object can then be used:- 1.View a detailed report on a strategy's exposures to given factors on a given date and through time.
- 2.Create a replicating basket for a given strategy with a defined replicating universe, by mimicking factor exposure.
- 3.Enable factor exposure to be constrained in optimisation problems.
- 4.Create Equity Factor Basket building blocks: a strategy driven by factor data.
This page details case 1 above. Cases 2 & 3 are detailed in the
FactorOptimisation
page, whilst 4 is explained in the EquityFactorBasket
show.Setting up your environment takes three steps:
- Import the relevant internal and external libraries
- Configure the environment parameters
- Initialise the environment
import sigtech.framework as sig
# import method to build equity ReinvestmentStrategy objects
from sigtech.framework.strategies.equity_factor_basket import get_single_stock_strategy
# import AnalystIndex class to get an overview of built-in factor time series data
from sigtech.framework.instruments.indices import AnalystIndex
# import SchedulePeriodic to build custom schedule below
from sigtech.framework.schedules import SchedulePeriodic
import pandas as pd
import seaborn as sns
import datetime as dtm
from uuid import uuid4
sns.set(rc={'figure.figsize':(18, 6)})
if not sig.config.is_initialised():
env = sig.init()
The
FactorExposures
class can be used with all asset classes. Equities are probably the most common use case. The FactorExposures
class implements estimation methods for exposures to factors for a selection of instruments, such as performing a series of multivariate time series regressions based on a configuration.The configuration is created by adding steps to the object:
- A
fit()
method executes the steps on the data provided, calculating the factor loadings across the instruments. - A
report()
method is available to generate a report summarising the results.
This short example will showcase how to apply the
FactorExposure
to a number of ReinvetmentStrategy
objects. tickers = ['AAPL', 'AMZN', 'MSFT', 'NFLX']
Input
Output
tickers = ['AAPL', 'AMZN', 'MSFT', 'NFLX']
sm = env.sig_master().filter_primary_fungible().filter_primary_tradable().filter_to_last_pit_record()
ticker_mapping = sm.filter('US', 'EXCHANGE_COUNTRY_ISO2').filter_exchange_tickers(tickers).to_single_stock()
ticker_mapping = {ticker.split('.')[0]: single_stock.name
for ticker, single_stock in ticker_mapping.items()}
ticker_mapping
{'MSFT': 1001489.SINGLE_STOCK.TRADABLE <class 'sigtech.framework.instruments.equities.SingleStock'>[140604326065744],
'AMZN': 1001962.SINGLE_STOCK.TRADABLE <class 'sigtech.framework.instruments.equities.SingleStock'>[140604323354960],
'NFLX': 1025677.SINGLE_STOCK.TRADABLE <class 'sigtech.framework.instruments.equities.SingleStock'>[140615196136848],
'AAPL': 1000045.SINGLE_STOCK.TRADABLE <class 'sigtech.framework.instruments.equities.SingleStock'>[140604323394064]
Input
Output
all_rs = [get_single_stock_strategy(sig.obj.get(single_stock).name) for single_stock in ticker_mapping.values()]
(pid=16032) # Getting reinvestment strategy for 1001489.SINGLE_STOCK.TRADABLE
(pid=16036) # Getting reinvestment strategy for 1000045.SINGLE_STOCK.TRADABLE
(pid=16062) # Getting reinvestment strategy for 1025677.SINGLE_STOCK.TRADABLE
(pid=16037) # Getting reinvestment strategy for 1001962.SINGLE_STOCK.TRADABLE
Input
Output
rs_mapping = {rs.underlyer_object.exchange_ticker: rs for rs in all_rs}
rs_mapping
{'MSFT': 1001489.SINGLE_STOCK.TRADABLE REINVSTRAT STRATEGY <class 'sigtech.framework.strategies.reinvestment_strategy.ReinvestmentStrategy'>[140617254363984],
'AMZN': 1001962.SINGLE_STOCK.TRADABLE REINVSTRAT STRATEGY <class 'sigtech.framework.strategies.reinvestment_strategy.ReinvestmentStrategy'>[140604325949520],
'NFLX': 1025677.SINGLE_STOCK.TRADABLE REINVSTRAT STRATEGY <class 'sigtech.framework.strategies.reinvestment_strategy.ReinvestmentStrategy'>[140604323550160],
'AAPL': 1000045.SINGLE_STOCK.TRADABLE REINVSTRAT STRATEGY <class 'sigtech.framework.strategies.reinvestment_strategy.ReinvestmentStrategy'>[140604325952400]}
A subset of what is available:
Input
Output
AnalystIndex.get_names()[:5]
['US 10_INDUSTRY_PORTFOLIOS_EQ FAMA-FRENCH INDEX',
'US 10_INDUSTRY_PORTFOLIOS_VAL FAMA-FRENCH INDEX',
'US 12_INDUSTRY_PORTFOLIOS_EQ FAMA-FRENCH INDEX',
'US 12_INDUSTRY_PORTFOLIOS_VAL FAMA-FRENCH INDEX',
'US 3F_DAILY FAMA-FRENCH INDEX']
factor_data = sig.obj.get('US 5F_2X3_DAILY FAMA-FRENCH INDEX')
factor_ts = pd.concat({
factor: factor_data.history(field=factor)
for factor in factor_data.history_fields if factor not in ['rf']
}, axis=1) / 100
factor_ts.plot();

Create exposures object and fit regression factors:
factor_exposures = sig.FactorExposures()
factor_exposures.add_regression_factors(factor_ts)
Compute security returns and fit regression:
Where the exposure to factor is, by default, the OLS estimate
and the residual at time
is
. Here
denotes the return of the strategy and
denotes the return of factor
, both at time
.
rs_returns = {rs: rs.history().pct_change().dropna() for rs in all_rs}
exposures, residuals = factor_exposures.fit(rs_returns)
exposures

residuals.head()

residuals.plot();

Recompute residuals manually for one strategy and compare to output:
rs = exposures.columns[0]
rs_exposures = exposures[rs]
r = rs.history().pct_change().dropna()
model_returns = (rs_exposures * factor_ts).sum(axis=1)
residuals_estimate = r - model_returns[r.index]
# numpy array
data = residuals_estimate.values - residuals[rs].values
# creating series
s = pd.Series(data, index = residuals_estimate.index)
s.plot()

There are a range of methods that can be used to add factor data to a
FactorExposures
object:Note: it is not necessary to be familiar with each method before the
FactorExposures
object is utilised.add_regression_factors
add_cross_score
For use with
EquityFactorBasket
and detailed in the EquityFactorBasket
page:add_factor_exposures
add_factor_exposure_method
add_raw_factor_timeseries
Add existing regression factor returns data as seen in the above example using the Fama-French 5-factor model:
# use 3-factor model here
factor_data = sig.obj.get('US 3F_DAILY FAMA-FRENCH INDEX')
factor_ts = pd.concat({
factor: factor_data.history(field=factor)
for factor in factor_data.history_fields if factor not in ['rf']
}, axis=1) / 100
factor_exposures = sig.FactorExposures()
factor_exposures.add_regression_factors(factor_ts)
Input
Output
factor_exposures.factor_list
['mkt_rf', 'smb', 'hml']
pd.DataFrame(factor_exposures.timeseries_factors).head()

e1, _ = factor_exposures.fit(rs_returns)
e1

View exposure to factor on ranked percentile level:
factor_exposures = sig.FactorExposures()
factor_exposures.add_regression_factors(factor_ts)
# add a cross_score for the `smb` factor called `smb_rank`
factor_exposures.add_cross_score('smb_rank', 'smb')
The updated exposures now contain the row
smb_rank
, indicating the percentile score for each assets exposure to the smb
factor. For example, the reinvestment strategy 1001489.SINGLE_STOCK.TRADABLE REINVSTRAT STRATEGY
has an smb_rank
score of 0.25 since it had the lowest exposure to the smb
factor out of the four assets in the universe.e2, _ = factor_exposures.fit(rs_returns)
e2

As seen above, after regression factors have been added to the
FactorExposure
object, it is possible to fit a regression from a dictionary of input returns onto those factors.More granular control over the fitting process can be gained by adding individual steps to the fitting configuration.
Input
Output
factor_exposures.fit_configuration
[(Index(['mkt_rf', 'smb', 'hml'], dtype='object'), {})]
factor_exposures = sig.FactorExposures()
params_dict = {
'l1': 0.5, # default: 0
'l2': 0.25, # default: 0
'include_constant': False, # default: True
'normalize_if_regularized': True # default: True
}
factor_exposures.add_step(factor_ts.columns, params_dict=params_dict)
factor_exposures.add_regression_factors(factor_ts)
Input
Output
factor_exposures.fit_configuration
[(Index(['mkt_rf', 'smb', 'hml'], dtype='object'),
{'l1': 0.5,
'l2': 0.25,
'include_constant': False,
'normalize_if_regularized': True}),
(Index(['mkt_rf', 'smb', 'hml'], dtype='object'), {})]
e, res = factor_exposures.fit(rs_returns)
e

Possible to clear configuration:
Input
Output
factor_exposures.clear_steps()
factor_exposures.fit_configuration
[]
Fit exposures again:
factor_exposures = sig.FactorExposures()
factor_exposures.add_regression_factors(factor_ts)
e, r = factor_exposures.fit(rs_returns)
Create example portfolios:
rs_histories = pd.concat({rs: rs.history() for rs in all_rs}, axis=1).dropna()
ew_portfolio = 1 / len(all_rs) + 0 * rs_histories
ew_portfolio.tail()

perf_weighted_portfolio = rs_histories.divide(rs_histories.sum(axis=1), axis=0)
perf_weighted_portfolio.tail()

Compute exposure of portfolios to factors through time:
ew_exp = factor_exposures.rolling_exposures(ew_portfolio)
perf_exp = factor_exposures.rolling_exposures(perf_weighted_portfolio)
ew_exp.columns = [f'ew_{c}' for c in ew_exp]
perf_exp.columns = [f'perf_{c}' for c in perf_exp]
df = pd.concat([ew_exp, perf_exp], axis=1)
(len(all_rs) * df).tail()

View the report for an individual portfolio, including an equally weights combination of securities within the portfolio for comparison. In addition to factor exposure, there is also variance decomposition and density plots of the weighted exposures of securities within the portfolio.
factor_exposures.report(ew_portfolio.iloc[-1])




factor_exposures.report(perf_weighted_portfolio.iloc[-1])




Last modified 10mo ago