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.

Learn more: to fully understand the content of this page, see Reinvestment Strategies.

Environment

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()

Class overview

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.

Example

This short example will showcase how to apply the FactorExposure to a number of ReinvetmentStrategy objects.

Build stock reinvestment strategies

tickers = ['AAPL', 'AMZN', 'MSFT', 'NFLX']
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
all_rs = [get_single_stock_strategy(sig.obj.get(single_stock).name) for single_stock in ticker_mapping.values()]
rs_mapping = {rs.underlyer_object.exchange_ticker: rs for rs in all_rs}
rs_mapping

Fetch factor data within the platform

A subset of what is available:

AnalystIndex.get_names()[:5]
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();

Compute exposures & residuals

Create exposures object and fit regression factors:

factor_exposures = sig.FactorExposures()
factor_exposures.add_regression_factors(factor_ts)

Compute security returns and fit regression:

R(t)=iβiRi(t)+ε(t)R(t) = \sum_{i} \beta_i R_i(t) + \varepsilon(t)

Where the exposure to factor is, by default, the OLS estimate βi\beta_i and the residual at time tt is ε(t)\varepsilon(t). Here R(t)R(t) denotes the return of the strategy and Ri(t)R_i(t) denotes the return of factor ii , both at time tt .

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()

Add factor data

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_regression_factors

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)
factor_exposures.factor_list
pd.DataFrame(factor_exposures.timeseries_factors).head()
e1, _ = factor_exposures.fit(rs_returns)
e1

add_cross_score

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

Fit regressions

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.

factor_exposures.fit_configuration
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)
factor_exposures.fit_configuration
e, res = factor_exposures.fit(rs_returns)
e

Possible to clear configuration:

factor_exposures.clear_steps()
factor_exposures.fit_configuration

Reporting

Fit exposures again:

factor_exposures = sig.FactorExposures()
factor_exposures.add_regression_factors(factor_ts)
e, r = factor_exposures.fit(rs_returns)

Rolling exposures

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()

Fixed portfolio report

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 updated

© 2023 SIG Technologies Limited