Hedging against factors for algos

Hey everyone,

I just ran a performance attribution tear sheet on an algo of mine and I noticed a high negative exposure to momentum. I want to improve by reducing my exposure to momentum, without introducing a significant amount of other exposures; is this possible and is it the right thing to do? I was thinking of constructing a portfolio with a high exposure to momentum, and not much other exposure, and introducing that portfolio as a hedge against my original algo. Any ideas on better ways to hedge or tips on this?

4 responses

"I want to improve by reducing my exposure to momentum, without introducing a significant amount of other exposures; is this possible and is it the right thing to do?"

If you're planning on entering the Q contest then you need to keep the abs of momentum exposure below .40. So yes, it's the right thing to do for the contest. If you're developing an algo for some other purpose than maybe it's not so important. There's some interesting discussion on this here (https://www.quantopian.com/posts/a-new-contest-is-coming-more-winners-and-a-new-scoring-system).

However, let's assume you're looking for ways to reduce momentum exposure. "Any ideas on better ways to hedge or tips on this?"

Take a look at the 'FactorExposure' constraint (https://www.quantopian.com/help#module-quantopian_optimize_constraints). Assuming you are using the 'order_optimal_portfolio' method for ordering, one could do something like this.

   # Create a pipeline factor for momentum. A good start is simply 120 day return
momentum = Factors.Returns(window_length = MOMENTUM_WINDOW, mask = universe)

# Simply use that factor as an input to the FactorExposure constraint
min_exposures = {'momentum': -MAX_ABS_EXPOSURE},
max_exposures = {'momentum': MAX_ABS_EXPOSURE}
)

# Finally, order using this factor exposure constraint
order_optimal_portfolio(objective = maximize_momentum_objective,
constraints = [leverage_constraint,
concentration_constraint,
momentum_constraint])



Attached is a simple algorithm which orders the highest and lowest momentum securities. There's a constant called CONSTRAIN_EXPOSURE which one can use to turn on and off the exposure constraint. Unconstrained, the algorithm has a momentum exposure of 1.77 (according to the tear sheet). Adding this constraint with an exposure limit of .20 and then running, results in an exposure of .20 (again according to the tear sheet).

If you just get hung up on a few factor 'over dependancies', this is a simple straightforward way to limit those exposures.

I'll attach the tear sheets of the 'unconstrained' version and then the 'constrained' version for comparison.

Good luck.

4
Total Returns
--
Alpha
--
Beta
--
Sharpe
--
Sortino
--
Max Drawdown
--
Benchmark Returns
--
Volatility
--
 Returns 1 Month 3 Month 6 Month 12 Month
 Alpha 1 Month 3 Month 6 Month 12 Month
 Beta 1 Month 3 Month 6 Month 12 Month
 Sharpe 1 Month 3 Month 6 Month 12 Month
 Sortino 1 Month 3 Month 6 Month 12 Month
 Volatility 1 Month 3 Month 6 Month 12 Month
 Max Drawdown 1 Month 3 Month 6 Month 12 Month
'''
Simple algorithm to test factor exposure constraint.
Select Q3000US marketcap securities with highest and lowest momentum.
Define a 'momentum' factor as 200 day return
Define objective to maximize momentum (call that our alpha factor)
Define a constraint to keep this factor exposure within certain bounds
'''

# import pipeline methods
from quantopian.algorithm import attach_pipeline, pipeline_output
from quantopian.pipeline import Pipeline
import quantopian.optimize as opt

# import built in factors and filters
import quantopian.pipeline.factors as Factors
import quantopian.pipeline.filters as Filters

# import any datasets we need
from quantopian.pipeline.data.builtin import USEquityPricing
from quantopian.pipeline.data import Fundamentals

# import numpy and pandas just in case
import numpy as np
import pandas as pd

# define any constants
MAX_SECURITIES = 200
MAX_ABS_POSITION = .05
MAX_LEVERAGE = 1.0

MOMENTUM_WINDOW = 120

MAX_ABS_EXPOSURE = .2

CONSTRAIN_EXPOSURE = False

def initialize(context):
"""
Called once at the start of the algorithm.
"""

# Create and attach pipeline to get data
attach_pipeline(my_pipeline(context), name='my_pipeline')

# Place orders once a day

# Record tracking variables at the end of each day.
schedule_function(my_record_vars, date_rules.every_day(), time_rules.market_close())

def my_pipeline(context):
'''
Define the pipline data columns
'''

# Create filter for just the securities we want to trade
universe = Filters.Q3000US()

# Create the fundamental factors we want to use in our trade decisions
momentum = Factors.Returns(window_length = MOMENTUM_WINDOW, mask = universe)

# Define our pipeline
return Pipeline(
columns = {
'momentum' : momentum,
},
screen = momentum.top(MAX_SECURITIES/2) | momentum.bottom(MAX_SECURITIES/2),
)

# Get the data
context.output = pipeline_output('my_pipeline')

# First determine which securities we want to be holding
# Place selection logic in the query method
securities_to_hold = context.output.index

# Next create a series with an alpha factor for each stock
# Here we assume alpha is proportional to momentum so series is just momentums
momentum = context.output.momentum
momentum_factors = pd.Series(index = securities_to_hold, data = momentum)

# Next create a MaximizeAlpha object using our momentum_factors
# This will become our ordering objective
maximize_momentum_objective = opt.MaximizeAlpha(momentum_factors)

# Need to set a couple of constraints
# First set a gross leverage constraint to keep optimize from going crazy with leverage
leverage_constraint = opt.MaxGrossExposure(MAX_LEVERAGE)

# Next set a gross leverage constraint to keep optimize from going crazy with leverage
concentration_constraint = opt.PositionConcentration.with_equal_bounds(-MAX_ABS_POSITION, MAX_ABS_POSITION)

# Next set a constraint on our min/max exposure to our momentum factor
min_exposures = {'momentum': -MAX_ABS_EXPOSURE},
max_exposures = {'momentum': MAX_ABS_EXPOSURE}
)

# Finally, execute the order_optimal_portfolio method
# No need to loop through the stocks.
# The order_optimal_portfolio does all the ordering at one time
# Also closes any positions not in 'securities_to_hold'

# Check with and withouut the exposure constraint
if CONSTRAIN_EXPOSURE:
order_optimal_portfolio(objective = maximize_momentum_objective,
constraints = [leverage_constraint,
concentration_constraint,
momentum_constraint])
else:
order_optimal_portfolio(objective = maximize_momentum_objective,
constraints = [leverage_constraint,
concentration_constraint])

def my_record_vars(context, data):
"""
Plot variables at the end of each day.
"""

record(securities = len(context.portfolio.positions))


There was a runtime error.

Here's the 'unconstrained' tear sheet showing a momentum exposure of 1.77.

1