Search…
Equity factor basket
The EquityFactorBasket building block provides a convenient way to build equity factor strategies. Factor scores can use historical and fundamental data at the instrument level, and incorporate custom signals and methods.

Prerequisites

The examples provided on this page utilise the EquityUniverseFilter, ReinvestmentStrategy, and FactorExposures methods.
Before working through these examples, you should familiarise yourself with the following pages:

Environment

Import the relevant Python libraries and initialise your environment:
import numpy as np
import pandas as pd
import datetime as dtm
from uuid import uuid4
import sigtech.framework as sig
from sigtech.framework.infra import cal
if not sig.config.is_initialised():
env = sig.config.init(env_date='2022-02-28')
sig.config.set('ALLOWED_MISSING_CA_DATA', True)

Build equity universe

The following code block describes the creation of the equity universe:
CURRENCY='USD'
START_DATE = pd.Timestamp(2021, 1, 4)
You can define a custom rebalance schedule and apply it to the SPX using the EquityUniverseFilter:
rebal_schedule = sig.SchedulePeriodic(START_DATE, env.asofdate, 'NYSE(T) CALENDAR', frequency='EOM', bdc=cal.BDC_FOLLOWING).all_data_dates()
equity_filter = sig.EquityUniverseFilter('SPX INDEX')
UNIVERSE = equity_filter.apply_filter(rebal_schedule)
A reinvestment strategy can be created for every stock in your universe and mapped to the stock names:
unique_stocks = UNIVERSE.explode().unique()
rs = sig.get_single_stock_strategy(unique_stocks, build=True)
UNIVERSE_MAPPING = {s: f'{s} REINV STRATEGY' for s in unique_stocks}

Equity factor basket class

The EquityFactorBasket inherits from SignalStrategy and requires these additional inputs:
  • A universe_filter input, either the EquityUniverseFilter or its output.
  • A FactorExposures object.
  • A factors_to_weight_function to transform the input factor data into allocations.
During construction, the strategy:
  • Evaluates the universe on each rebalance day.
  • Calculates each defined factor for each unique stock as a time series.
  • Proceeds through the rebalance dates, combining the factors as defined in the factors_to_weight_function, and allocating through the allocation_function.

Factor exposures

You can use the sig.FactorExposures object to define the factors for your strategy.
Different factors can be added to this object via the add_raw_factor_timeseries method:
factor_definitions = sig.FactorExposures()

Adding a raw history field

Total market cap is defined as a size factor:
factor_definitions.add_raw_factor_timeseries(
'size',
history_field='MARKETCAP'
)

Adding a raw fundamental field

Quarterly dividend yield is defined as a value factor:
factor_definitions.add_raw_factor_timeseries(
'value',
fundamental_field='Dividend Yield',
fundamental_freq='Quarterly'
)

Add a custom method

This method can be run for each unique stock in your universe. The trailing 12 month returns is computed with a one month lag as a momentum factor. The method input rs is the name of the reinvestment strategy that the method runs:
def get_momentum_signal(rs, mtm_period=256, lag_period=21):
price = sig.obj.get(rs).history()
momentum = price.pct_change(mtm_period - 1).shift(lag_period).dropna()
return momentum
factor_definitions.add_raw_factor_timeseries(
'momentum',
method=get_momentum_signal
)

Factors to weights

This method is called on each rebalance date with the calculated individual factor values and defines how the factors are combined to select the stocks for your portfolio.
The method accepts a pd.DataFrame with factor values for each reinvestment strategy indexed by factor name:
Index
STOCK 1 REINV STRATEGY
STOCK 2 REINV STRATEGY
leverage
0.5
0.5
liquidity
100
100
And returns a pd.Series indexed by stocks with signal values:
Index
Values
STOCK 1 REINV STRATEGY
1
STOCK 2 REINV STRATEGY
1

Default factors to weights

If no argument for the factors_to_weight_function is supplied, the default_factors_to_weight_function is used:
Input
Output
from sigtech.framework.strategies.equity_factor_basket import default_factors_to_weight_function
default_factors_to_weight_function?
Signature:
default_factors_to_weight_function(
factors,
factor_weights=None,
proportion=None,
)
Docstring:
Default factor to weight function - This creates a linear combination of factors.
:param factors: factor dataframe.
:param factor_weights: Optional dictionary of weights.
:param proportion: Optional, proportion for long/short allocations.
:return: Series of weights.
File: ~/sig-env/lib/python3.7/site-packages/sigtech/framework/strategies/equity_factor_basket.py
Type: function
To retrieve all the API documentation and source code associated with an object, append ?? to the object name:
Input
Output
default_factors_to_weight_function??
Signature:
default_factors_to_weight_function(
factors,
factor_weights=None,
proportion=None,
)
Source:
def default_factors_to_weight_function(factors, factor_weights=None, proportion=None):
"""
Default factor to weight function - This creates a linear combination of factors.
:param factors: factor dataframe.
:param factor_weights: Optional dictionary of weights.
:param proportion: Optional, proportion for long/short allocations.
:return: Series of weights.
"""
if factor_weights is None:
combined_factors = factors.sum(axis=0)
else:
combined_factors = (factors.multiply(pd.Series(factor_weights), axis=0)).sum(axis=0)
if proportion is None:
return combined_factors
ts_row = combined_factors.dropna()
n = int(len(ts_row) * proportion)
row_output = pd.concat([pd.Series(1, index=ts_row.nlargest(n).index),
pd.Series(-1, index=ts_row.nsmallest(n).index)])
return row_output.loc[row_output.index.drop_duplicates(keep=False)]
File: ~/sig-env/lib/python3.7/site-packages/sigtech/framework/strategies/equity_factor_basket.py
Type: function
This method takes a weighted sum of the factor data across the universe of securities, ranks each constituent based on its factor score and assigns +/-1 signals for the top/bottom proportion of the universe.
This logic is visible in the following implementation. The process begins by providing the necessary factor data:
df = pd.DataFrame({
'Stock.1': [1, 3], 'Stock.2': [-2, 4.7], 'Stock.3': [9, -2], 'Stock.4': [4, 4],
'Stock.5': [0.3, 2], 'Stock.6': [-2.2, 6], 'Stock.7': [1, 0], 'Stock.8': [0, 4],
'Stock.9': [3.5, 4], 'Stock.10': [2, 1]
}, index=['Factor.1', 'Factor.2'])
df
The factor scores are then summed and sorted:
Input
Output
df.sum().sort_values()
Stock.7 1.0
Stock.5 2.3
Stock.2 2.7
Stock.10 3.0
Stock.6 3.8
Stock.1 4.0
Stock.8 4.0
Stock.3 7.0
Stock.9 7.5
Stock.4 8.0
dtype: float64
This is followed by assigning +1 to the top 20% and -1 to the bottom 20%.
Input
Output
default_factors_to_weight_function(df, proportion=0.2)
Stock.4 1
Stock.9 1
Stock.7 -1
Stock.5 -1
dtype: int64
The following code performs the same function as above but on a weighted sum of factor scores:
Input
Output
factor_weights = {
'Factor.1': 0.1,
'Factor.2': 0.9
}
default_factors_to_weight_function(df, factor_weights, proportion=0.2)
Stock.6 1
Stock.2 1
Stock.3 -1
Stock.7 -1
dtype: int64

Custom factors to weights

The sigtech.framework.strategies.equity_factor_basket class contains a suite of available factors to weights functions. If additional methods are required you can define your own. Arguments can be passed through the factors_to_weight_kwargs parameter in EquityFactorBasket.
A simple sum combination and long only allocation is defined:
def long_only_selection(factors, **kwargs):
factors = factors.sum(axis=0)
n = int(len(factors) * 0.2)
long = pd.Series(1, index=factors.nlargest(n).index)
return long
Input
Output
long_only_selection(df)
Stock.4 1
Stock.9 1
dtype: int64

Building the strategy

The allocation_function parameter, inherited from SignalStrategy, defines how you allocate to the selected stocks from the factors_to_weight_function.
In the following example, NORMALIZE_ZERO_FILLED is used to equally weight your long and short stocks:
efbs = sig.EquityFactorBasket(
currency=CURRENCY,
start_date=START_DATE,
universe_filter=UNIVERSE,
universe_mapping=UNIVERSE_MAPPING,
factor_exposure_generator=factor_definitions,
factors_to_weight_function=default_factors_to_weight_function,
rebalance_frequency='EOM',
allocation_function=sig.EquityFactorBasket.AVAILABLE_ALLOCATION_FUNCTIONS.NORMALIZE_ZERO_FILLED
)
efbs.history().plot()

Querying factor data

Factor data can be queried for your strategy:
efbs.factor_exposures_on_date(dtm.date(2022, 1, 4))