Back to Community
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  
    momentum_constraint = opt.FactorExposure(loadings = context.output,  
                                               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.

Clone Algorithm
4
Loading...
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
    schedule_function(enter_trades, date_rules.every_day(), time_rules.market_open())
    
    # 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),
            )


def before_trading_start(context, data):
    # Get the data
    context.output = pipeline_output('my_pipeline')

     
def enter_trades(context, data):

    # 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
    momentum_constraint = opt.FactorExposure(loadings = context.output,
                                               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.

Loading notebook preview...

Here's the 'constrained' version with the constraint set at .20. The tear sheet now shows (unsurprisingly) a momentum exposure of .20.

Loading notebook preview...

Hey Dan,

Thanks for the help! I'll make incorporate those functions. It's very interesting that you can specify the constraints when making orders, appears very useful.