Algorithm To Isolate Common/Specific Returns

I recently coded an algorithm which (I think) can be used to isolate either a specific or common return. To isolate a common return you simply put your algorithm logic into the pipeline and let it run. To isolate a specific return you add in your entire algorithm, reverse the weights (negatives go to positive and vice versa) essentially shorting this algorithm and the outcome should be the specific returns.

It's not perfect because I:
a) Don't know exactly how Quantopian's risk model works
b) Haven't run the sector exposures of the stocks I added to mimic the factor returns, leaving some gapping.

It works in backtesting with a very simple fundamental long-short algorithm

The specific returns are caused (I believe) by the errors I outlined above.

18
Backtest from to with initial capital
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
from quantopian.algorithm import attach_pipeline, pipeline_output
from quantopian.pipeline import Pipeline
from quantopian.pipeline.data import morningstar
from quantopian.pipeline.data import builtin
from quantopian.pipeline import CustomFactor
from quantopian.pipeline.data.builtin import USEquityPricing
import numpy as np
from quantopian.pipeline.experimental import Volatility, Momentum, ShortTermReversal, Size, Value, BasicMaterials, ConsumerCyclical, FinancialServices, RealEstate, ConsumerDefensive, HealthCare, Utilities, CommunicationServices, Energy, Industrials, Technology
import pandas as pd
from quantopian.pipeline.factors import SimpleMovingAverage, AnnualizedVolatility

def initialize(context):

context.leverage = 1.0

schedule_function(record_vars,
date_rules.every_day(),
time_rules.market_close(hours=1))

schedule_function(rebalance,
date_rules.every_day(),
time_rules.market_open(hours=1))

attach_pipeline(factor_pipeline_long(), 'factor_pipeline_long')
attach_pipeline(factor_pipeline_shorts(), 'factor_pipeline_shorts')
attach_pipeline(stock_pipeline(), 'stock_pipeline')

def stock_pipeline():

#Momentum

sma_high = SimpleMovingAverage(inputs=[USEquityPricing.close], window_length=30)

Momentum_Ratio = USEquityPricing.close.latest/sma_high

#Value

pb_ratio = morningstar.valuation_ratios.pb_ratio.latest

#Short Term Reversal

sma_low = SimpleMovingAverage(inputs=[USEquityPricing.close], window_length=3)

Reversal_Ratio = USEquityPricing.close.latest/sma_low

#Volatility

volatility = AnnualizedVolatility(inputs=[USEquityPricing.close], window_length=30)

#Size

market_cap = morningstar.valuation.market_cap.latest

return Pipeline(
columns = {
'momentum_longs': momentum_longs,
'momentum_shorts': momentum_shorts,
'value_longs': value_longs,
'value_shorts': value_shorts,
'reversal_longs': reversal_longs,
'reversal_shorts': reversal_shorts,
'volatility_longs': volatility_longs,
'volatility_shorts': volatility_shorts,
'size_longs': size_longs,
'size_shorts': size_shorts,
},
)

def factor_pipeline_long():

filter_requirement = morningstar.valuation_ratios.fcf_yield.latest
working_capital = morningstar.balance_sheet.working_capital.latest
market_cap = morningstar.balance_sheet.working_capital.latest
pe_ratio = morningstar.valuation_ratios.pb_ratio.latest

filter_ = filter_requirement*(working_capital/market_cap)

filter_1 = (pe_ratio > 0) & (pe_ratio < 5) & (market_cap > 2e7)

longs = filter_.percentile_between(80, 100, mask = filter_1)

return Pipeline(
columns = {
'VOL': Volatility(),
'MOM': Momentum(),
'STR': ShortTermReversal(),
'SZE': Size(),
'VLE': Value(),
'BM': BasicMaterials(),
'CC': ConsumerCyclical(),
'FS':  FinancialServices(),
'RE': RealEstate(),
'CD': ConsumerDefensive(),
'HC': HealthCare(),
'U': Utilities(),
'CS': CommunicationServices(),
'E': Energy(),
'I': Industrials(),
'T': Technology()
},
)

return Pipeline()

def factor_pipeline_shorts():

filter_requirement = morningstar.valuation_ratios.fcf_yield.latest
working_capital = morningstar.balance_sheet.working_capital.latest
market_cap = morningstar.balance_sheet.working_capital.latest
pe_ratio = morningstar.valuation_ratios.pb_ratio.latest

filter_ = filter_requirement*(working_capital/market_cap)

filter_1 = (pe_ratio > 0) & (pe_ratio < 5) & (market_cap > 2e7)

shorts = filter_.percentile_between(1, 20, mask = filter_1)

return Pipeline(
columns = {
'VOL': Volatility(),
'MOM': Momentum(),
'STR': ShortTermReversal(),
'SZE': Size(),
'VLE': Value(),
'BM': BasicMaterials(),
'CC': ConsumerCyclical(),
'FS':  FinancialServices(),
'RE': RealEstate(),
'CD': ConsumerDefensive(),
'HC': HealthCare(),
'U': Utilities(),
'CS': CommunicationServices(),
'E': Energy(),
'I': Industrials(),
'T': Technology()
},
)

return Pipeline()

context.output = pipeline_output('stock_pipeline')
context.momentum_longs = context.output[context.output['momentum_longs']].index
context.momentum_shorts = context.output[context.output['momentum_shorts']].index
context.value_longs = context.output[context.output['value_longs']].index
context.value_shorts = context.output[context.output['value_shorts']].index
context.reversal_longs = context.output[context.output['reversal_longs']].index
context.reversal_shorts = context.output[context.output['reversal_shorts']].index
context.volatility_longs = context.output[context.output['volatility_longs']].index
context.volatility_shorts = context.output[context.output['volatility_shorts']].index
context.size_longs = context.output[context.output['size_longs']].index
context.size_shorts = context.output[context.output['size_shorts']].index

context.outputs = pipeline_output('factor_pipeline_long')
context.VOL_Long = context.outputs['VOL']
context.MOM_Long = context.outputs['MOM']
context.STR_Long = context.outputs['STR']
context.SZE_Long = context.outputs['SZE']
context.VLE_Long = context.outputs['VLE']
context.BM_Long = context.outputs['BM']
context.CC_Long = context.outputs['CC']
context.FS_Long = context.outputs['FS']
context.RE_Long = context.outputs['RE']
context.CD_Long = context.outputs['CD']
context.HC_Long = context.outputs['HC']
context.U_Long = context.outputs['U']
context.CS_Long = context.outputs['CS']
context.E_Long = context.outputs['E']
context.I_Long = context.outputs['I']
context.T_Long = context.outputs['T']

context.outputz = pipeline_output('factor_pipeline_shorts')
context.VOL_Short = context.outputz['VOL']
context.MOM_Short = context.outputz['MOM']
context.STR_Short = context.outputz['STR']
context.SZE_Short = context.outputz['SZE']
context.VLE_Short = context.outputz['VLE']
context.BM_Short = context.outputz['BM']
context.CC_Short = context.outputz['CC']
context.FS_Short = context.outputz['FS']
context.RE_Short = context.outputz['RE']
context.CD_Short = context.outputz['CD']
context.HC_Short = context.outputz['HC']
context.U_Short = context.outputz['U']
context.CS_Short = context.outputz['CS']
context.E_Short = context.outputz['E']
context.I_Short = context.outputz['I']
context.T_Short = context.outputz['T']

context.VOL_Short = context.VOL_Short.dropna()
context.MOM_Short = context.MOM_Short.dropna()
context.STR_Short = context.STR_Short.dropna()
context.SZE_Short = context.SZE_Short.dropna()
context.VLE_Short = context.VLE_Short.dropna()
context.BM_Short = context.BM_Short.dropna()
context.CC_Short = context.CC_Short.dropna()
context.FS_Short = context.FS_Short.dropna()
context.RE_Short = context.RE_Short.dropna()
context.CD_Short = context.CD_Short.dropna()
context.HC_Short = context.HC_Short.dropna()
context.U_Short = context.U_Short.dropna()
context.CS_Short = context.CS_Short.dropna()
context.E_Short = context.E_Short.dropna()
context.I_Short = context.I_Short.dropna()
context.T_Short = context.T_Short.dropna()

context.VOL_Long = context.VOL_Long.dropna()
context.MOM_Long = context.MOM_Long.dropna()
context.STR_Long = context.STR_Long.dropna()
context.SZE_Long = context.SZE_Long.dropna()
context.VLE_Long = context.VLE_Long.dropna()
context.BM_Long = context.BM_Long.dropna()
context.CC_Long = context.CC_Long.dropna()
context.FS_Long = context.FS_Long.dropna()
context.RE_Long = context.RE_Long.dropna()
context.CD_Long = context.CD_Long.dropna()
context.HC_Long = context.HC_Long.dropna()
context.U_Long = context.U_Long.dropna()
context.CS_Long = context.CS_Long.dropna()
context.E_Long = context.E_Long.dropna()
context.I_Long = context.I_Long.dropna()
context.T_Long = context.T_Long.dropna()

context.VOL = sum(context.VOL_Long) - sum(context.VOL_Short)
context.VOL_LEN = len(context.VOL_Long) + len(context.VOL_Short)
context.MOM = sum(context.MOM_Long) - sum(context.MOM_Short)
context.MOM_LEN = len(context.MOM_Long) + len(context.MOM_Short)
context.STR = sum(context.STR_Long) - sum(context.STR_Short)
context.STR_LEN = len(context.STR_Long) + len(context.STR_Short)
context.SZE = sum(context.SZE_Long) - sum(context.SZE_Short)
context.SZE_LEN = len(context.SZE_Long) + len(context.SZE_Short)
context.VLE = sum(context.VLE_Long) - sum(context.VLE_Short)
context.VLE_LEN = len(context.VLE_Long) + len(context.VLE_Short)

context.BM = sum(context.BM_Long) - sum(context.BM_Short)
context.BM_LEN = len(context.BM_Long) + len(context.BM_Short)
context.CC = sum(context.CC_Long) - sum(context.CC_Short)
context.CC_LEN = len(context.CC_Long) + len(context.CC_Short)
context.FS = sum(context.FS_Long) - sum(context.FS_Short)
context.FS_LEN = len(context.FS_Long) + len(context.FS_Short)
context.RE = sum(context.RE_Long) - sum(context.RE_Short)
context.RE_LEN = len(context.RE_Long) + len(context.RE_Short)
context.CD = sum(context.CD_Long) - sum(context.CD_Short)
context.CD_LEN = len(context.CD_Long) + len(context.CD_Short)
context.HC = sum(context.HC_Long) - sum(context.HC_Short)
context.HC_LEN = len(context.HC_Long) + len(context.HC_Short)
context.U = sum(context.U_Long) - sum(context.U_Short)
context.U_LEN = len(context.U_Long) + len(context.U_Short)
context.CS = sum(context.CS_Long) - sum(context.CS_Short)
context.CS_LEN = len(context.CS_Long) + len(context.CS_Short)
context.E = sum(context.E_Long) - sum(context.E_Short)
context.E_LEN = len(context.E_Long) + len(context.E_Short)
context.I = sum(context.I_Long) - sum(context.I_Short)
context.I_LEN = len(context.I_Long) + len(context.I_Short)
context.T = sum(context.T_Long) - sum(context.T_Short)
context.T_LEN = len(context.T_Long) + len(context.T_Short)

context.vol_exposure = (context.VOL)/(context.VOL_LEN)
context.mom_exposure = (context.MOM)/(context.MOM_LEN)
context.str_exposure = (context.STR)/(context.STR_LEN)
context.sze_exposure = (context.SZE)/(context.SZE_LEN)
context.vle_exposure = (context.VLE)/(context.VLE_LEN)

context.bm_exposure = (context.BM)/(context.BM_LEN)
context.cc_exposure = (context.CC)/(context.CC_LEN)
context.fs_exposure = (context.FS)/(context.FS_LEN)
context.re_exposure = (context.RE)/(context.RE_LEN)
context.cd_exposure = (context.CD)/(context.CD_LEN)
context.hc_exposure = (context.HC)/(context.HC_LEN)
context.u_exposure = (context.U)/(context.U_LEN)
context.cs_exposure = (context.CS)/(context.CS_LEN)
context.e_exposure = (context.E)/(context.E_LEN)
context.i_exposure = (context.I)/(context.I_LEN)
context.t_exposure = (context.T)/(context.T_LEN)

total_sector = abs(context.bm_exposure)+abs(context.cc_exposure)+abs(context.fs_exposure)+abs(context.re_exposure)+abs(context.cd_exposure)+abs(context.hc_exposure)+abs(context.u_exposure)+abs(context.cs_exposure)+abs(context.e_exposure)+abs(context.i_exposure)+abs(context.t_exposure)

total_factor = abs(context.vol_exposure)+abs(context.mom_exposure)+abs(context.str_exposure)+abs(context.sze_exposure)+abs(context.vle_exposure)

context.vol_weight = context.vol_exposure/total_factor
context.mom_weight = context.mom_exposure/total_factor
context.str_weight = context.str_exposure/total_factor
context.sze_weight = context.sze_exposure/total_factor
context.vle_weight = context.vle_exposure/total_factor

context.bm_weight = context.bm_exposure/total_sector
context.cc_weight = context.cc_exposure/total_sector
context.fs_weight = context.fs_exposure/total_sector
context.re_weight = context.re_exposure/total_sector
context.cd_weight = context.cd_exposure/total_sector
context.hc_weight = context.hc_exposure/total_sector
context.u_weight = context.u_exposure/total_sector
context.cs_exposure = context.cs_exposure/total_sector
context.e_exposure = context.e_exposure/total_sector
context.i_exposure = context.i_exposure/total_sector
context.t_exposure = context.t_exposure/total_sector

def record_vars(context, data):

record(leverage = context.account.leverage)

def rebalance(context, data):

etf_stocks = [sid(19654), sid(19662), sid(19656), sid(26669), sid(45719), sid(19661), sid(19660), sid(26670), sid(19655), sid(19657), sid(19658)]

order_target_percent(sid(19654), -0.5*context.bm_weight)
order_target_percent(sid(19662), -0.5*context.cc_weight)
order_target_percent(sid(19656), -0.5*context.fs_weight)
order_target_percent(sid(26669), -0.5*context.re_weight)
order_target_percent(sid(45719), -0.5*context.cd_weight)
order_target_percent(sid(19661), -0.5*context.hc_weight)
order_target_percent(sid(19660), -0.5*context.u_weight)
order_target_percent(sid(26670), -0.5*context.cs_exposure)
order_target_percent(sid(19655), -0.5*context.e_exposure)
order_target_percent(sid(19657), -0.5*context.i_exposure)
order_target_percent(sid(19658), -0.5*context.t_exposure)

momentum_longs = context.momentum_longs
momentum_shorts = context.momentum_shorts
value_longs = context.value_longs
value_shorts = context.value_shorts
reversal_longs = context.reversal_longs
reversal_shorts = context.reversal_shorts
volatility_longs = context.volatility_longs
volatility_shorts = context.volatility_shorts
size_longs = context.size_longs
size_shorts = context.size_shorts

vol_weight = context.vol_weight
mom_weight = context.mom_weight
str_weight = context.str_weight
sze_weight = context.sze_weight
vle_weight = context.vle_weight

total_weight = 0.0

for stock in context.output.index:

weight = 0.0

if stock in momentum_longs:
weight = weight + mom_weight/(2*(len(momentum_longs)+len(momentum_shorts)))
if stock in momentum_shorts:
weight = weight - mom_weight/(2*(len(momentum_longs)+len(momentum_shorts)))

if stock in value_longs:
weight = weight + vle_weight/(2*(len(value_longs)+len(value_shorts)))
if stock in value_shorts:
weight = weight - vle_weight/(2*(len(value_longs)+len(value_shorts)))
if stock in reversal_longs:
weight = weight + str_weight/(2*(len(reversal_longs)+len(reversal_shorts)))
if stock in reversal_shorts:
weight = weight - str_weight/(2*(len(reversal_longs)+len(reversal_shorts)))

if stock in volatility_longs:
weight = weight + vol_weight/(2*(len(volatility_longs)+len(volatility_shorts)))
if stock in volatility_shorts:
weight = weight - vol_weight/(2*(len(volatility_longs)+len(volatility_shorts)))

if stock in size_longs:
weight = weight + sze_weight/(2*(len(size_longs)+len(size_shorts)))
if stock in size_shorts:
weight = weight - sze_weight/(2*(len(size_longs)+len(size_shorts)))

total_weight = total_weight + abs(weight)

for stock in context.portfolio.positions:
if stock not in context.output.index and stock not in etf_stocks:
order_target_percent(stock, 0)

multiplier = 0.5/total_weight

for stock in context.output.index:

weight = 0.0

if stock in momentum_longs:
weight = weight + mom_weight/(2*(len(momentum_longs)+len(momentum_shorts)))
if stock in momentum_shorts:
weight = weight - mom_weight/(2*(len(momentum_longs)+len(momentum_shorts)))

if stock in value_longs:
weight = weight + vle_weight/(2*(len(value_longs)+len(value_shorts)))
if stock in value_shorts:
weight = weight - vle_weight/(2*(len(value_longs)+len(value_shorts)))
if stock in reversal_longs:
weight = weight + str_weight/(2*(len(reversal_longs)+len(reversal_shorts)))
if stock in reversal_shorts:
weight = weight - str_weight/(2*(len(reversal_longs)+len(reversal_shorts)))

if stock in volatility_longs:
weight = weight + vol_weight/(2*(len(volatility_longs)+len(volatility_shorts)))
if stock in volatility_shorts:
weight = weight - vol_weight/(2*(len(volatility_longs)+len(volatility_shorts)))

if stock in size_longs:
weight = weight + sze_weight/(2*(len(size_longs)+len(size_shorts)))
if stock in size_shorts:
weight = weight - sze_weight/(2*(len(size_longs)+len(size_shorts)))

weight = -weight*multiplier

order_target_percent(stock, weight)
There was a runtime error.
4 responses

For example,

Common Returns Unconstrained

Common Returns Constrained

It would be better if the algorithm was good, but the constraint (when applied) does significantly impact common returns. Would be more effective if I had more knowledge regarding what Quantopian used to model the factors.

1) Can anyone say why this goes to -10 with use_norm??
That switch independently normalizes positive values 0 to 1 and negative values -1 to 0.

2) Does weight-flipping in any way offer a quick hint about specific and/or common returns?
When using order_optimal_portfolio, one can add a minus sign in front of the input weights (and notice the minus sign here)
like objective = opt.MaximizeAlpha( -pipeline_data.alpha ) thus flipping/reversing long and short. Sometimes a negative return will become positive, sometimes more negative. A positive return could go either up or down also presumably.
Here, that would look like order_target_percent(s, -weight). Related?

Intriguing code by Quant Trader to be aiming for zero returns for understanding.

With this version I'm primarily hoping it might help in testing changes and someone might share their findings.

- context.use_norm = 1 # use 0 for off
- Log preview to see pipe contents, look to bottom of this code now for example. Some fundamentals floor are zero (affects norm()).
- Vertical alignment using white space (the start of the code will take an extra 1/10,000th of a second or so, otherwise no difference).
- Short variable names for readability, less typing, fast editing.
- Custom chart values somewhat atypical.
- dropna() from pipeline_output once
- Easily adjust percentile_between hi (high) and lo (low)
- Fundamentals instead of morningstar

14
Backtest from to with initial capital
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
import numpy  as np
import pandas as pd
from quantopian.algorithm             import attach_pipeline, pipeline_output
from quantopian.pipeline              import CustomFactor, Pipeline
from quantopian.pipeline.data         import builtin, Fundamentals
from quantopian.pipeline.factors      import SimpleMovingAverage, AnnualizedVolatility
from quantopian.pipeline.data.builtin import USEquityPricing
from quantopian.pipeline.experimental import Volatility, Momentum, ShortTermReversal, Size, Value, BasicMaterials, ConsumerCyclical, FinancialServices, RealEstate, ConsumerDefensive, HealthCare, Utilities, CommunicationServices, Energy, Industrials, Technology

def initialize(context):
context.use_norm = 1
context.leverage = 1.0

attach_pipeline(factor_pipe_longs(), 'factor_pipe_longs')
attach_pipeline(factor_pipe_shrts(), 'factor_pipe_shrts')
attach_pipeline(stock_pipe(),        'stock_pipe')

def stock_pipe():
lo = 20 ; hi = 100 - lo

# Momentum
sma_high       = SimpleMovingAverage(inputs=[USEquityPricing.close], window_length=30)
Momentum_Ratio = USEquityPricing.close.latest / sma_high
momentum_shrts = Momentum_Ratio.percentile_between( 1, lo, mask=m)

# Value
pb_ratio       = Fundamentals.pb_ratio.latest
value_longs    = pb_ratio      .percentile_between( 1, lo, mask=m)
value_shrts    = pb_ratio      .percentile_between(hi, 99, mask=m)

# Short Term Reversal
sma_low        = SimpleMovingAverage(inputs=[USEquityPricing.close], window_length=3)
Reversal_Ratio = USEquityPricing.close.latest / sma_low
reversal_longs = Reversal_Ratio.percentile_between( 1, lo, mask=m)

# Volatility
vltlty         = AnnualizedVolatility(inputs=[USEquityPricing.close], window_length=30)
vltlty_longs   = vltlty        .percentile_between(hi, 99, mask=m)
vltlty_shrts   = vltlty        .percentile_between( 1, lo, mask=m)

# Size
market_cap     = Fundamentals.market_cap.latest
size_longs     = market_cap    .percentile_between(hi, 99, mask=m)
size_shrts     = market_cap    .percentile_between( 1, lo, mask=m)

return Pipeline( screen = m,
columns = {
'momentum_longs': momentum_longs,
'momentum_shrts': momentum_shrts,
'value_longs'   : value_longs,
'value_shrts'   : value_shrts,
'reversal_longs': reversal_longs,
'reversal_shrts': reversal_shrts,
'size_longs'    : size_longs,
'size_shrts'    : size_shrts,
'vltlty_longs'  : vltlty_longs,
'vltlty_shrts'  : vltlty_shrts,
}
)

def factor_pipe_longs():    # Insert your own Long Logic
fcf_yield   = Fundamentals.fcf_yield      .latest
working_cap = Fundamentals.working_capital.latest
market_cap  = Fundamentals.market_cap     .latest      # was bug here
pe_ratio    = Fundamentals.pb_ratio       .latest
filter_1    = fcf_yield * (working_cap / market_cap)
filter_2    = (pe_ratio > 0) & (pe_ratio < 5) & (market_cap > 2e7)
longs       = filter_1.percentile_between(80, 100, mask = filter_2)
return Pipeline( screen = longs,
columns = {
'VOL': Volatility(),
'MOM': Momentum(),
'STR': ShortTermReversal(),
'SZE': Size(),
'VLE': Value(),
'BM' : BasicMaterials(),
'CC' : ConsumerCyclical(),
'FS' : FinancialServices(),
'RE' : RealEstate(),
'CD' : ConsumerDefensive(),
'HC' : HealthCare(),
'UT' : Utilities(),
'CS' : CommunicationServices(),
'EN' : Energy(),
'IN' : Industrials(),
'TY' : Technology()
}
)
return Pipeline()

def factor_pipe_shrts():    # Insert your own Short Logic
fcf_yield   = Fundamentals.fcf_yield      .latest
working_cap = Fundamentals.working_capital.latest
market_cap  = Fundamentals.market_cap     .latest      # was bug here
pe_ratio    = Fundamentals.pb_ratio       .latest
filter_1    = fcf_yield * (working_cap / market_cap)
filter_2    = (pe_ratio > 0) & (pe_ratio < 5) & (market_cap > 2e7)
shrts       = filter_1.percentile_between(1, 20, mask = filter_2)
return Pipeline( screen = shrts,
columns = {
'VOL': Volatility(),
'MOM': Momentum(),
'STR': ShortTermReversal(),
'SZE': Size(),
'VLE': Value(),
'BM' : BasicMaterials(),
'CC' : ConsumerCyclical(),
'FS' : FinancialServices(),
'RE' : RealEstate(),
'CD' : ConsumerDefensive(),
'HC' : HealthCare(),
'UT' : Utilities(),
'CS' : CommunicationServices(),
'EN' : Energy(),
'IN' : Industrials(),
'TY' : Technology()
}
)
return Pipeline()

records(context, data)

c = context

fpl = pipeline_output('factor_pipe_longs').dropna()
fps = pipeline_output('factor_pipe_shrts').dropna()
o   = pipeline_output('stock_pipe'       ).dropna()

c.momentum_longs = o[o['momentum_longs']].index
c.momentum_shrts = o[o['momentum_shrts']].index
c.value_longs    = o[o['value_longs'   ]].index
c.value_shrts    = o[o['value_shrts'   ]].index
c.reversal_longs = o[o['reversal_longs']].index
c.reversal_shrts = o[o['reversal_shrts']].index
c.vltlty_longs   = o[o['vltlty_longs'  ]].index
c.vltlty_shrts   = o[o['vltlty_shrts'  ]].index
c.size_longs     = o[o['size_longs'    ]].index
c.size_shrts     = o[o['size_shrts'    ]].index

if c.use_norm:
VOL_Long = norm(c, fpl['VOL'])
MOM_Long = norm(c, fpl['MOM'])
STR_Long = norm(c, fpl['STR'])
SZE_Long = norm(c, fpl['SZE'])
VLE_Long = norm(c, fpl['VLE'])
BM_Long  = norm(c, fpl['BM'] )
CC_Long  = norm(c, fpl['CC'] )
FS_Long  = norm(c, fpl['FS'] )
RE_Long  = norm(c, fpl['RE'] )
CD_Long  = norm(c, fpl['CD'] )
HC_Long  = norm(c, fpl['HC'] )
UT_Long  = norm(c, fpl['UT'] )
CS_Long  = norm(c, fpl['CS'] )
EN_Long  = norm(c, fpl['EN']  )
IN_Long  = norm(c, fpl['IN']  )
TY_Long  = norm(c, fpl['TY']  )

VOL_Shrt = norm(c, fps['VOL'])
MOM_Shrt = norm(c, fps['MOM'])
STR_Shrt = norm(c, fps['STR'])
SZE_Shrt = norm(c, fps['SZE'])
VLE_Shrt = norm(c, fps['VLE'])
BM_Shrt  = norm(c, fps['BM'] )
CC_Shrt  = norm(c, fps['CC'] )
FS_Shrt  = norm(c, fps['FS'] )
RE_Shrt  = norm(c, fps['RE'] )
CD_Shrt  = norm(c, fps['CD'] )
HC_Shrt  = norm(c, fps['HC'] )
CS_Shrt  = norm(c, fps['CS'] )
UT_Shrt  = norm(c, fps['UT'] )
EN_Shrt  = norm(c, fps['EN']  )
IN_Shrt  = norm(c, fps['IN']  )
TY_Shrt  = norm(c, fps['TY']  )
else:
VOL_Long = fpl['VOL']
MOM_Long = fpl['MOM']
STR_Long = fpl['STR']
SZE_Long = fpl['SZE']
VLE_Long = fpl['VLE']
BM_Long  = fpl['BM']
CC_Long  = fpl['CC']
FS_Long  = fpl['FS']
RE_Long  = fpl['RE']
CD_Long  = fpl['CD']
HC_Long  = fpl['HC']
UT_Long  = fpl['UT']
CS_Long  = fpl['CS']
EN_Long  = fpl['EN']
IN_Long  = fpl['IN']
TY_Long  = fpl['TY']

VOL_Shrt = fps['VOL']
MOM_Shrt = fps['MOM']
STR_Shrt = fps['STR']
SZE_Shrt = fps['SZE']
VLE_Shrt = fps['VLE']
BM_Shrt  = fps['BM']
CC_Shrt  = fps['CC']
FS_Shrt  = fps['FS']
RE_Shrt  = fps['RE']
CD_Shrt  = fps['CD']
HC_Shrt  = fps['HC']
CS_Shrt  = fps['CS']
UT_Shrt  = fps['UT']
EN_Shrt  = fps['EN']
IN_Shrt  = fps['IN']
TY_Shrt  = fps['TY']

VOL = sum(VOL_Long) - sum(VOL_Shrt) ; VOL_LEN = len(VOL_Long) + len(VOL_Shrt)
MOM = sum(MOM_Long) - sum(MOM_Shrt) ; MOM_LEN = len(MOM_Long) + len(MOM_Shrt)
STR = sum(STR_Long) - sum(STR_Shrt) ; STR_LEN = len(STR_Long) + len(STR_Shrt)
SZE = sum(SZE_Long) - sum(SZE_Shrt) ; SZE_LEN = len(SZE_Long) + len(SZE_Shrt)
VLE = sum(VLE_Long) - sum(VLE_Shrt) ; VLE_LEN = len(VLE_Long) + len(VLE_Shrt)
BM  = sum(BM_Long)  - sum(BM_Shrt)  ; BM_LEN  = len(BM_Long)  + len(BM_Shrt)
CC  = sum(CC_Long)  - sum(CC_Shrt)  ; CC_LEN  = len(CC_Long)  + len(CC_Shrt)
FS  = sum(FS_Long)  - sum(FS_Shrt)  ; FS_LEN  = len(FS_Long)  + len(FS_Shrt)
RE  = sum(RE_Long)  - sum(RE_Shrt)  ; RE_LEN  = len(RE_Long)  + len(RE_Shrt)
CD  = sum(CD_Long)  - sum(CD_Shrt)  ; CD_LEN  = len(CD_Long)  + len(CD_Shrt)
HC  = sum(HC_Long)  - sum(HC_Shrt)  ; HC_LEN  = len(HC_Long)  + len(HC_Shrt)
CS  = sum(CS_Long)  - sum(CS_Shrt)  ; CS_LEN  = len(CS_Long)  + len(CS_Shrt)
UT  = sum(UT_Long)  - sum(UT_Shrt)  ; UT_LEN  = len(UT_Long)  + len(UT_Shrt)
EN  = sum(EN_Long)  - sum(EN_Shrt)  ; EN_LEN  = len(EN_Long)  + len(EN_Shrt)
IN  = sum(IN_Long)  - sum(IN_Shrt)  ; IN_LEN  = len(IN_Long)  + len(IN_Shrt)
TY  = sum(TY_Long)  - sum(TY_Shrt)  ; TY_LEN  = len(TY_Long)  + len(TY_Shrt)

vol_exposure = (VOL) / (VOL_LEN)
mom_exposure = (MOM) / (MOM_LEN)
str_exposure = (STR) / (STR_LEN)
sze_exposure = (SZE) / (SZE_LEN)
vle_exposure = (VLE) / (VLE_LEN)
bm_exposure  = (BM)  / (BM_LEN)
cc_exposure  = (CC)  / (CC_LEN)
fs_exposure  = (FS)  / (FS_LEN)
re_exposure  = (RE)  / (RE_LEN)
cd_exposure  = (CD)  / (CD_LEN)
hc_exposure  = (HC)  / (HC_LEN)
cs_exposure  = (CS)  / (CS_LEN)
ut_exposure  = (UT)  / (UT_LEN)
en_exposure  = (EN)  / (EN_LEN)
in_exposure  = (IN)  / (IN_LEN)
ty_exposure  = (TY)  / (TY_LEN)

total_factor = abs(vol_exposure) + abs(mom_exposure) + abs(str_exposure) + abs(sze_exposure) + abs(vle_exposure)
total_sector = abs(bm_exposure)  + abs(cc_exposure)  + abs(fs_exposure)  + abs(re_exposure)  + abs(cd_exposure) + abs(hc_exposure) + abs(ut_exposure) + abs(cs_exposure) + abs(en_exposure) + abs(in_exposure) + abs(ty_exposure)

c.vol_weight  = vol_exposure / total_factor
c.mom_weight  = mom_exposure / total_factor
c.str_weight  = str_exposure / total_factor
c.sze_weight  = sze_exposure / total_factor
c.vle_weight  = vle_exposure / total_factor
c.bm_weight   = bm_exposure  / total_sector
c.cc_weight   = cc_exposure  / total_sector
c.fs_weight   = fs_exposure  / total_sector
c.re_weight   = re_exposure  / total_sector
c.cd_weight   = cd_exposure  / total_sector
c.hc_weight   = hc_exposure  / total_sector
c.ut_weight   = ut_exposure  / total_sector
c.cs_exposure = cs_exposure  / total_sector
c.en_exposure = en_exposure  / total_sector
c.in_exposure = in_exposure  / total_sector
c.ty_exposure = ty_exposure  / total_sector

c.output = o

if 'log_pipe_done' not in c:    # show pipe info once
log_pipe(c, data, fpl, 4)
log_pipe(c, data, fps, 4)
log_pipe(c, data,   o, 4)
#log_pipe(c, data,   o, 4, details=['alpha', 'beta', ... etc])

c = context
mult = -0.5

etfs = [sid(19654), sid(19662), sid(19656), sid(26669), sid(45719), sid(19661), sid(19660), sid(26670), sid(19655), sid(19657), sid(19658)]
order_target_percent(sid(19654), mult * c.bm_weight)
order_target_percent(sid(19662), mult * c.cc_weight)
order_target_percent(sid(19656), mult * c.fs_weight)
order_target_percent(sid(26669), mult * c.re_weight)
order_target_percent(sid(45719), mult * c.cd_weight)
order_target_percent(sid(19661), mult * c.hc_weight)
order_target_percent(sid(19660), mult * c.ut_weight)
order_target_percent(sid(26670), mult * c.cs_exposure)
order_target_percent(sid(19655), mult * c.en_exposure)
order_target_percent(sid(19657), mult * c.in_exposure)
order_target_percent(sid(19658), mult * c.ty_exposure)

total_weight = 0.0

for s in c.output.index:
weight = 0.0

if s in c.momentum_longs:
weight += c.mom_weight / (2 * (len(c.momentum_longs) + len(c.momentum_shrts)))
if s in c.momentum_shrts:
weight -= c.mom_weight / (2 * (len(c.momentum_longs) + len(c.momentum_shrts)))
if s in c.value_longs:
weight += c.vle_weight / (2 * (len(c.value_longs)    + len(c.value_shrts)))
if s in c.value_shrts:
weight -= c.vle_weight / (2 * (len(c.value_longs)    + len(c.value_shrts)))
if s in c.reversal_longs:
weight += c.str_weight / (2 * (len(c.reversal_longs) + len(c.reversal_shrts)))
if s in c.reversal_shrts:
weight -= c.str_weight / (2 * (len(c.reversal_longs) + len(c.reversal_shrts)))
if s in c.vltlty_longs:
weight += c.vol_weight / (2 * (len(c.vltlty_longs)   + len(c.vltlty_shrts)))
if s in c.vltlty_shrts:
weight -= c.vol_weight / (2 * (len(c.vltlty_longs)   + len(c.vltlty_shrts)))
if s in c.size_longs:
weight += c.sze_weight / (2 * (len(c.size_longs)     + len(c.size_shrts)))
if s in c.size_shrts:
weight -= c.sze_weight / (2 * (len(c.size_longs)     + len(c.size_shrts)))

total_weight += + abs(weight)

for s in c.portfolio.positions:
if s not in c.output.index and s not in etfs:
order_target(s, 0)

multiplier = 0.5 / total_weight

for s in c.output.index:
weight = 0.0

if s in c.momentum_longs:
weight += c.mom_weight / (2 * (len(c.momentum_longs) + len(c.momentum_shrts)))
if s in c.momentum_shrts:
weight -= c.mom_weight / (2 * (len(c.momentum_longs) + len(c.momentum_shrts)))
if s in c.value_longs:
weight += c.vle_weight / (2 * (len(c.value_longs)    + len(c.value_shrts)))
if s in c.value_shrts:
weight -= c.vle_weight / (2 * (len(c.value_longs)    + len(c.value_shrts)))
if s in c.reversal_longs:
weight += c.str_weight / (2 * (len(c.reversal_longs) + len(c.reversal_shrts)))
if s in c.reversal_shrts:
weight -= c.str_weight / (2 * (len(c.reversal_longs) + len(c.reversal_shrts)))
if s in c.vltlty_longs:
weight += c.vol_weight / (2 * (len(c.vltlty_longs)   + len(c.vltlty_shrts)))
if s in c.vltlty_shrts:
weight -= c.vol_weight / (2 * (len(c.vltlty_longs)   + len(c.vltlty_shrts)))
if s in c.size_longs:
weight += c.sze_weight / (2 * (len(c.size_longs)     + len(c.size_shrts)))
if s in c.size_shrts:
weight -= c.sze_weight / (2 * (len(c.size_longs)     + len(c.size_shrts)))

weight = -weight * multiplier

if np.isnan(weight):
print s.symbol

order_target_percent(s, weight)

def norm(c, d):    # d data, it's a series, normalize it pos, neg separately
# don't return to same object or may create nans? use .copy()
#if     0 and    d.min() >= 0 or d.max() <= 0:  # breaks algo if not active, if all pos or neg
if d.min() >= 0 or d.max() <= 0:
d -= d.mean()
pos  = d[ d > 0 ]
neg  = d[ d < 0 ]
num  = min(len(pos), len(neg))
neg  = neg.sort_values(ascending=False).tail(num)
pos /=   pos.sum()
neg  = -(neg / neg.sum())
return pos.append(neg)

def records(context, data):
pos = context.portfolio.positions
long_list = [z.amount * z.last_sale_price for s, z in pos.items() if z.amount > 0]
shrt_list = [z.amount * z.last_sale_price for s, z in pos.items() if z.amount < 0]
long_val  =  sum(long_list) ; long_len = len(long_list)
shrt_val  = -sum(shrt_list) ; shrt_len = len(shrt_list)
long_to_shrt_val = long_val / shrt_val if shrt_val else 0
record(
pos   = len(pos),
lv    = context.account.leverage,
longs = long_len,
shrts = shrt_len,
long_to_shrt_val = long_to_shrt_val,
)

def log_pipe(context, data, z, num, details=None):
'''
# Options
log_nan_only = 0          # Only log if nans are present
show_sectors = 0          # If sectors, do you want to see them or not
show_sorted_details = 1   # [num] high & low securities sorted, each column

if 'log_init_done' not in context:
log.info('${} {} to {}'.format('%.0e' % (context.portfolio.starting_cash), get_environment('start').date(), get_environment('end').date())) context.log_init_done = 1 if not len(z): log.info('Empty') return # Series ...... context.log_pipe_done = 1 ; padmax = 6 if 'Series' in str(type(z)): # is Series, not DataFrame nan_count = len(z[z != z]) nan_count = 'NaNs {}/{}'.format(nan_count, len(z)) if nan_count else '' if (log_nan_only and nan_count) or not log_nan_only: pad = max(6, len(str(z.max()))) log.info('{}{}{} Series {} len {}'.format('min' .rjust(pad+5), 'mean'.rjust(pad+5), 'max' .rjust(pad+5), z.name, len(z))) log.info('{}{}{} {}'.format(str(z.min()) .rjust(pad+5), str(z.mean()).rjust(pad+5), str(z.max()) .rjust(pad+5), nan_count )) return # DataFrame ...... content_min_max = [ ['','min','mean','max',''] ] ; content = '' for col in z.columns: if col == 'sector' and not show_sectors: continue nan_count = len(z[col][z[col] != z[col]]) nan_count = 'NaNs {}/{}'.format(nan_count, len(z)) if nan_count else '' padmax = max( padmax, 6, len(str(z[col].max())) ) content_min_max.append([col, str(z[col] .min()), str(z[col].mean()), str(z[col] .max()), nan_count]) if log_nan_only and nan_count or not log_nan_only: content = 'Rows: {} Columns: {}'.format(z.shape[0], z.shape[1]) if len(z.columns) == 1: content = 'Rows: {}'.format(z.shape[0]) paddings = [6 for i in range(4)] for lst in content_min_max: # set max lengths i = 0 for val in lst[:4]: # value in each sub-list paddings[i] = max(paddings[i], len(str(val))) i += 1 headr = content_min_max[0] content += ('\n{}{}{}{}{}'.format( headr[0] .rjust(paddings[0]), (headr[1]).rjust(paddings[1]+5), (headr[2]).rjust(paddings[2]+5), (headr[3]).rjust(paddings[3]+5), '' )) for lst in content_min_max[1:]: # populate content using max lengths content += ('\n{}{}{}{} {}'.format( lst[0].rjust(paddings[0]), lst[1].rjust(paddings[1]+5), lst[2].rjust(paddings[2]+5), lst[3].rjust(paddings[3]+5), lst[4], )) log.info(content) if not show_sorted_details: return if len(z.columns) == 1: return # skip detail if only 1 column if details == None: details = z.columns for detail in details: if detail == 'sector': continue hi = z[details].sort_values(by=detail, ascending=False).head(num) lo = z[details].sort_values(by=detail, ascending=False).tail(num) content = '' content += ('_ _ _ {} _ _ _' .format(detail)) content += ('\n\t... {} highs\n{}'.format(detail, str(hi))) content += ('\n\t... {} lows \n{}'.format(detail, str(lo))) if log_nan_only and not len(lo[lo[detail] != lo[detail]]): continue # skip if no nans log.info(content) ''' 2017-08-09 05:45 log_pipe:404 INFO$1e+07    2017-08-09 to 2018-05-11
2017-08-09 05:45 log_pipe:459 INFO Rows: 468  Columns: 16
min                   mean                max
BM      -0.0958819800687     -8.24364318498e-18      2.33677333258
CC       -0.216766907861      2.62847673351e-16      1.64353850506
CD       -0.035058553393     -2.20621242073e-17       1.0046880354
CS      -0.0100214587073      2.63618661563e-17      1.55152460302
EN      -0.0924246338046     -1.46487760194e-17      2.10121337835
FS      -0.0412084365718        0.0332660639116      1.16539405485
HC      -0.0647112622143        0.0908974086954      2.24884667767
IN       -0.405718849911         0.217438375245      1.98046109567
MOM        -5.22537579763        -0.394073537443                5.0
RE       -0.156493619003        0.0208514390651      1.37927790887
STR        -6.27767670709         0.616939019775      5.83828457965
SZE        -6.02828633756         -1.28555882406      2.73286452836
TY       -0.12...
2017-08-09 05:45 log_pipe:474 INFO _ _ _   BM   _ _ _
... BM highs
BM        CC        CD        CS        EN   FS  \
Equity(13197 [FCX])   2.336773 -0.216767 -0.035059 -0.010021 -0.092425  0.0
Equity(40530 [TROX])  2.272126 -0.216767 -0.035059 -0.010021 -0.092425  0.0
Equity(8329 [X])      2.018658 -0.216767 -0.035059 -0.010021 -0.092425  0.0
Equity(1595 [CLF])    1.935253 -0.216767 -0.035059 -0.010021 -0.092425  0.0

HC   IN       MOM   RE       STR       SZE        TY  \
Equity(13197 [FCX])   0.0  0.0  0.130250  0.0 -1.303728  1.268838 -0.129813
Equity(40530 [TROX])  0.0  0.0  2.307034  0.0 -1.434932 -0.335039 -0.129813
Equity(8329 [X])      0.0  0.0 -0.005854  0.0 -0.216663  0.093986 -0.129813
Equity(1595 [CLF])    0.0  0.0  0.133846  0.0 -0.209154 -0.349139 -0.129813

UT       VLE       VOL
Equity(13197 [FCX])  -0.004838 -0.280191  0.895722
Equity(40530 [TROX]) -0.004838 -0.045931  2.525600
Equity(8329 [X])     -0.004838  0.344237...
2017-08-09 05:45 log_pipe:474 INFO _ _ _   CC   _ _ _
... CC highs
BM        CC        CD        CS        EN   FS  \
Equity(10728 [BZH]) -0.095882  1.643539 -0.035059 -0.010021 -0.092425  0.0
Equity(3645 [HOV])  -0.095882  1.375185 -0.035059 -0.010021 -0.092425  0.0
'''

There was a runtime error.

Can also be used to convert an algorithm from long (or short) only to a long-short portfolio with limited side effects. See below a S&P500 long-short mimic.

I set the leverage to 2 because I found out that when the leverage was set to 1, the returns and volatility were both reduced, however, the volatility was reduced by more than the returns. That meant I could leverage up to the original volatility levels but with extra returns, essentially giving me an extra 1-2% of returns for 'free'.

14
Backtest from to with initial capital
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
import numpy  as np
import pandas as pd
from quantopian.algorithm             import attach_pipeline, pipeline_output
from quantopian.pipeline              import CustomFactor, Pipeline
from quantopian.pipeline.data         import builtin, Fundamentals, morningstar
from quantopian.pipeline.factors      import SimpleMovingAverage, AnnualizedVolatility
from quantopian.pipeline.data.builtin import USEquityPricing
from quantopian.pipeline.experimental import Volatility, Momentum, ShortTermReversal, Size, Value, BasicMaterials, ConsumerCyclical, FinancialServices, RealEstate, ConsumerDefensive, HealthCare, Utilities, CommunicationServices, Energy, Industrials, Technology

def initialize(context):

context.leverage = 2.0

attach_pipeline(factor_pipeline_longs(), 'factor_pipeline_longs')
attach_pipeline(factor_pipeline_shrts(), 'factor_pipeline_shrts')
attach_pipeline(stock_pipeline(), 'stock_pipeline')

def stock_pipeline():
lo = 2 ; hi = 100 - lo

# Momentum
sma_high         = SimpleMovingAverage(inputs=[USEquityPricing.close], window_length=330)
Momentum_Ratio   = USEquityPricing.close.latest / sma_high
momentum_shrts   = Momentum_Ratio.percentile_between( 1, lo, mask=m)

# Value
pb_ratio         = Fundamentals.pb_ratio.latest
value_longs      = pb_ratio      .percentile_between( 1, lo, mask=m)
value_shrts      = pb_ratio      .percentile_between(hi, 99, mask=m)

# Short Term Reversal
sma_low          = SimpleMovingAverage(inputs=[USEquityPricing.close], window_length=2)
Reversal_Ratio   = USEquityPricing.close.latest / sma_low
reversal_longs   = Reversal_Ratio.percentile_between( 1, lo, mask=m)

# Volatility
volatility       = AnnualizedVolatility(inputs=[USEquityPricing.close], window_length=30)
volatility_longs = volatility    .percentile_between(hi, 99, mask=m)
volatility_shrts = volatility    .percentile_between( 1, lo, mask=m)

# Size
market_cap       = Fundamentals.market_cap.latest
size_longs       = market_cap    .percentile_between(hi, 99, mask=m)
size_shrts       = market_cap    .percentile_between( 1, lo, mask=m)

sector = morningstar.asset_classification.morningstar_sector_code.latest

BasicMaterials = market_cap.top(20, mask = (sector.eq(101)))

ConsumerCyclical = market_cap.top(20, mask = (sector.eq(102)))

FinancialServices = market_cap.top(20, mask = (sector.eq(103)))

RealEstate = market_cap.top(20, mask = (sector.eq(104)))

ConsumerDefensive = market_cap.top(20, mask = (sector.eq(205)))

HealthCare = market_cap.top(20, mask = (sector.eq(206)))

Utilities = market_cap.top(20, mask = (sector.eq(207)))

CommunicationServices = market_cap.top(20, mask = (sector.eq(308)))

Energy = market_cap.top(20, mask = (sector.eq(309)))

Industrials = market_cap.top(20, mask = (sector.eq(310)))

Technology = market_cap.top(20, mask = (sector.eq(311)))

return Pipeline( screen = m,
columns = {
'momentum_longs'  : momentum_longs,
'momentum_shrts'  : momentum_shrts,
'value_longs'     : value_longs,
'value_shrts'     : value_shrts,
'reversal_longs'  : reversal_longs,
'reversal_shrts'  : reversal_shrts,
'size_longs'      : size_longs,
'size_shrts'      : size_shrts,
'volatility_longs': volatility_longs,
'volatility_shrts': volatility_shrts,
'BM' : BasicMaterials,
'CC' : ConsumerCyclical,
'FS' : FinancialServices,
'RE' : RealEstate,
'CD' : ConsumerDefensive,
'HC' : HealthCare,
'U'  : Utilities,
'CS' : CommunicationServices,
'E'  : Energy,
'I'  : Industrials,
'T'  : Technology,
}
)

def factor_pipeline_longs():    # Insert your own Long Logic
market_cap = Fundamentals.market_cap.latest

return Pipeline( screen = longs,
columns = {
'VOL': Volatility(),
'MOM': Momentum(),
'STR': ShortTermReversal(),
'SZE': Size(),
'VLE': Value(),
'BM' : BasicMaterials(),
'CC' : ConsumerCyclical(),
'FS' : FinancialServices(),
'RE' : RealEstate(),
'CD' : ConsumerDefensive(),
'HC' : HealthCare(),
'U'  : Utilities(),
'CS' : CommunicationServices(),
'E'  : Energy(),
'I'  : Industrials(),
'T'  : Technology(),
'longs': longs,
}
)
return Pipeline()

def factor_pipeline_shrts():    # Insert your own Short Logic

market_cap = Fundamentals.market_cap.latest

return Pipeline( screen = shrts,
columns = {
'VOL': Volatility(),
'MOM': Momentum(),
'STR': ShortTermReversal(),
'SZE': Size(),
'VLE': Value(),
'BM' : BasicMaterials(),
'CC' : ConsumerCyclical(),
'FS' : FinancialServices(),
'RE' : RealEstate(),
'CD' : ConsumerDefensive(),
'HC' : HealthCare(),
'U'  : Utilities(),
'CS' : CommunicationServices(),
'E'  : Energy(),
'I'  : Industrials(),
'T'  : Technology(),
'shorts': shrts,
}
)
return Pipeline()

records(context, data)
c = context

o = pipeline_output('stock_pipeline').dropna()
c.momentum_longs    = o[o['momentum_longs']]  .index
c.momentum_shrts    = o[o['momentum_shrts']]  .index
c.value_longs       = o[o['value_longs']]     .index
c.value_shrts       = o[o['value_shrts']]     .index
c.reversal_longs    = o[o['reversal_longs']]  .index
c.reversal_shrts    = o[o['reversal_shrts']]  .index
c.volatility_longs  = o[o['volatility_longs']].index
c.volatility_shrts  = o[o['volatility_shrts']].index
c.size_longs        = o[o['size_longs']]      .index
c.size_shrts        = o[o['size_shrts']]      .index
c.BM_MIMIC          = o[o['BM']]              .index
c.CC_MIMIC          = o[o['CC']]              .index
c.FS_MIMIC          = o[o['FS']]              .index
c.RE_MIMIC          = o[o['RE']]              .index
c.CD_MIMIC          = o[o['CD']]              .index
c.HC_MIMIC          = o[o['HC']]              .index
c.U_MIMIC           = o[o['volatility_longs']].index
c.CS_MIMIC          = o[o['volatility_shrts']].index
c.E_MIMIC           = o[o['E']]               .index
c.I_MIMIC           = o[o['I']]               .index
c.T_MIMIC           = o[o['T']]               .index

os  = pipeline_output('factor_pipeline_longs').dropna()
VOL_Long = norm(c, os['VOL'].copy())    # os['VOL']
MOM_Long = norm(c, os['MOM'].copy())    # os['MOM']
STR_Long = norm(c, os['STR'].copy())    # os['STR']
SZE_Long = norm(c, os['SZE'].copy())    # os['SZE']
VLE_Long = norm(c, os['VLE'].copy())    # os['VLE']
BM_Long  = norm(c, os['BM'] .copy())    # os['BM']
CC_Long  = norm(c, os['CC'] .copy())    # os['CC']
FS_Long  = norm(c, os['FS'] .copy())    # os['FS']
RE_Long  = norm(c, os['RE'] .copy())    # os['RE']
CD_Long  = norm(c, os['CD'] .copy())    # os['CD']
HC_Long  = norm(c, os['HC'] .copy())    # os['HC']
U_Long   = norm(c, os['U']  .copy())    # os['U']
CS_Long  = norm(c, os['CS'] .copy())    # os['CS']
E_Long   = norm(c, os['E']  .copy())    # os['E']
I_Long   = norm(c, os['I']  .copy())    # os['I']
T_Long   = norm(c, os['T']  .copy())    # os['T']
c.longs = os[os['longs']].index

oz = pipeline_output('factor_pipeline_shrts').dropna()
VOL_Shrt = norm(c, oz['VOL'].copy())    # oz['VOL']
MOM_Shrt = norm(c, oz['MOM'].copy())    # oz['MOM']
STR_Shrt = norm(c, oz['STR'].copy())    # oz['STR']
SZE_Shrt = norm(c, oz['SZE'].copy())    # oz['SZE']
VLE_Shrt = norm(c, oz['VLE'].copy())    # oz['VLE']
BM_Shrt  = norm(c, oz['BM'] .copy())    # oz['BM']
CC_Shrt  = norm(c, oz['CC'] .copy())    # oz['CC']
FS_Shrt  = norm(c, oz['FS'] .copy())    # oz['FS']
RE_Shrt  = norm(c, oz['RE'] .copy())    # oz['RE']
CD_Shrt  = norm(c, oz['CD'] .copy())    # oz['CD']
HC_Shrt  = norm(c, oz['HC'] .copy())    # oz['HC']
CS_Shrt  = norm(c, oz['CS'] .copy())    # oz['CS']
U_Shrt   = norm(c, oz['U']  .copy())    # oz['U']
E_Shrt   = norm(c, oz['E']  .copy())    # oz['E']
I_Shrt   = norm(c, oz['I']  .copy())    # oz['I']
T_Shrt   = norm(c, oz['T']  .copy())    # oz['T']
c.shorts = oz[oz['shorts']].index

VOL = sum(VOL_Long) - sum(VOL_Shrt) ; VOL_LEN = len(VOL_Long) + len(VOL_Shrt)
MOM = sum(MOM_Long) - sum(MOM_Shrt) ; MOM_LEN = len(MOM_Long) + len(MOM_Shrt)
STR = sum(STR_Long) - sum(STR_Shrt) ; STR_LEN = len(STR_Long) + len(STR_Shrt)
SZE = sum(SZE_Long) - sum(SZE_Shrt) ; SZE_LEN = len(SZE_Long) + len(SZE_Shrt)
VLE = sum(VLE_Long) - sum(VLE_Shrt) ; VLE_LEN = len(VLE_Long) + len(VLE_Shrt)
BM  = sum(BM_Long)  - sum(BM_Shrt)  ; BM_LEN  = len(BM_Long)  + len(BM_Shrt)
CC  = sum(CC_Long)  - sum(CC_Shrt)  ; CC_LEN  = len(CC_Long)  + len(CC_Shrt)
FS  = sum(FS_Long)  - sum(FS_Shrt)  ; FS_LEN  = len(FS_Long)  + len(FS_Shrt)
RE  = sum(RE_Long)  - sum(RE_Shrt)  ; RE_LEN  = len(RE_Long)  + len(RE_Shrt)
CD  = sum(CD_Long)  - sum(CD_Shrt)  ; CD_LEN  = len(CD_Long)  + len(CD_Shrt)
HC  = sum(HC_Long)  - sum(HC_Shrt)  ; HC_LEN  = len(HC_Long)  + len(HC_Shrt)
CS  = sum(CS_Long)  - sum(CS_Shrt)  ; CS_LEN  = len(CS_Long)  + len(CS_Shrt)
U   = sum(U_Long)   - sum(U_Shrt)   ; U_LEN   = len(U_Long)   + len(U_Shrt)
E   = sum(E_Long)   - sum(E_Shrt)   ; E_LEN   = len(E_Long)   + len(E_Shrt)
I   = sum(I_Long)   - sum(I_Shrt)   ; I_LEN   = len(I_Long)   + len(I_Shrt)
T   = sum(T_Long)   - sum(T_Shrt)   ; T_LEN   = len(T_Long)   + len(T_Shrt)

vol_exposure = (VOL) / (VOL_LEN)
mom_exposure = (MOM) / (MOM_LEN)
str_exposure = (STR) / (STR_LEN)
sze_exposure = (SZE) / (SZE_LEN)
vle_exposure = (VLE) / (VLE_LEN)
bm_exposure  = (BM)  / (BM_LEN)
cc_exposure  = (CC)  / (CC_LEN)
fs_exposure  = (FS)  / (FS_LEN)
re_exposure  = (RE)  / (RE_LEN)
cd_exposure  = (CD)  / (CD_LEN)
hc_exposure  = (HC)  / (HC_LEN)
cs_exposure  = (CS)  / (CS_LEN)
u_exposure   = (U)   / (U_LEN)
e_exposure   = (E)   / (E_LEN)
i_exposure   = (I)   / (I_LEN)
t_exposure   = (T)   / (T_LEN)

total_factor = abs(vol_exposure) + abs(mom_exposure) + abs(str_exposure) + abs(sze_exposure) + abs(vle_exposure)
total_sector = abs(bm_exposure) + abs(cc_exposure) + abs(fs_exposure) + abs(re_exposure) + abs(cd_exposure) + abs(hc_exposure) + abs(u_exposure) + abs(cs_exposure) + abs(e_exposure)  + abs(i_exposure)  + abs(t_exposure)

c.vol_weight  = vol_exposure / total_factor
c.mom_weight  = mom_exposure / total_factor
c.str_weight  = str_exposure / total_factor
c.sze_weight  = sze_exposure / total_factor
c.vle_weight  = vle_exposure / total_factor
c.bm_weight   = bm_exposure  / total_sector
c.cc_weight   = cc_exposure  / total_sector
c.fs_weight   = fs_exposure  / total_sector
c.re_weight   = re_exposure  / total_sector
c.cd_weight   = cd_exposure  / total_sector
c.hc_weight   = hc_exposure  / total_sector
c.u_weight    = u_exposure   / total_sector
c.cs_weight = cs_exposure  / total_sector
c.e_weight  = e_exposure   / total_sector
c.i_weight  = i_exposure   / total_sector
c.t_weight  = t_exposure   / total_sector

c.output = o

c = context

total_weight = 0.0

for s in c.output.index:
weight = 0.0

if s in c.momentum_longs:
weight += c.mom_weight / len(c.momentum_longs)
if s in c.momentum_shrts:
weight -= c.mom_weight / len(c.momentum_shrts)

if s in c.value_longs:
weight += c.vle_weight / len(c.value_longs)
if s in c.value_shrts:
weight -= c.vle_weight / len(c.value_shrts)
if s in c.reversal_longs:
weight += c.str_weight / len(c.reversal_longs)
if s in c.reversal_shrts:
weight -= c.str_weight / len(c.reversal_shrts)

if s in c.volatility_longs:
weight += c.vol_weight / len(c.volatility_longs)
if s in c.volatility_shrts:
weight -= c.vol_weight / len(c.volatility_shrts)

if s in c.size_longs:
weight += c.sze_weight / len(c.size_longs)
if s in c.size_shrts:
weight -= c.sze_weight / len(c.size_shrts)

if s in c.longs:
weight -= context.leverage/len(c.longs) #sign reversed lower down

#if s in c.shorts:
#    weight += context.leverage/len(c.shorts)

if s in c.BM_MIMIC:
weight += c.bm_weight/len(c.BM_MIMIC)

if s in c.CC_MIMIC:
weight += c.cc_weight/len(c.CC_MIMIC)

if s in c.FS_MIMIC:
weight += c.fs_weight/len(c.FS_MIMIC)

if s in c.RE_MIMIC:
weight += c.re_weight/len(c.RE_MIMIC)

if s in c.CD_MIMIC:
weight += c.cd_weight/len(c.CD_MIMIC)

if s in c.HC_MIMIC:
weight += c.hc_weight/len(c.HC_MIMIC)

if s in c.U_MIMIC:
weight += c.u_weight/len(c.U_MIMIC)

if s in c.CS_MIMIC:
weight += c.cs_weight/len(c.CS_MIMIC)

if s in c.E_MIMIC:
weight += c.e_weight/len(c.E_MIMIC)

if s in c.I_MIMIC:
weight += c.i_weight/len(c.I_MIMIC)

if s in c.T_MIMIC:
weight += c.t_weight/len(c.T_MIMIC)

total_weight = total_weight + abs(weight)

for s in c.portfolio.positions:
if s not in c.output.index and s not in c.longs and s != sid(8554):
order_target(s, 0)

multiplier = context.leverage / total_weight

for s in c.output.index:
weight = 0.0

if s in c.momentum_longs:
weight += c.mom_weight / len(c.momentum_longs)
if s in c.momentum_shrts:
weight -= c.mom_weight / len(c.momentum_shrts)

if s in c.value_longs:
weight += c.vle_weight / len(c.value_longs)
if s in c.value_shrts:
weight -= c.vle_weight / len(c.value_shrts)
if s in c.reversal_longs:
weight += c.str_weight / len(c.reversal_longs)
if s in c.reversal_shrts:
weight -= c.str_weight / len(c.reversal_shrts)

if s in c.volatility_longs:
weight += c.vol_weight / len(c.volatility_longs)
if s in c.volatility_shrts:
weight -= c.vol_weight / len(c.volatility_shrts)

if s in c.size_longs:
weight += c.sze_weight / len(c.size_longs)
if s in c.size_shrts:
weight -= c.sze_weight / len(c.size_shrts)

if s in c.longs:
weight -= context.leverage/len(c.longs)

# if s in c.shorts:
#  weight += context.leverage/len(c.shorts)

if s in c.BM_MIMIC:
weight += c.bm_weight/len(c.BM_MIMIC)

if s in c.CC_MIMIC:
weight += c.cc_weight/len(c.CC_MIMIC)

if s in c.FS_MIMIC:
weight += c.fs_weight/len(c.FS_MIMIC)

if s in c.RE_MIMIC:
weight += c.re_weight/len(c.RE_MIMIC)

if s in c.CD_MIMIC:
weight += c.cd_weight/len(c.CD_MIMIC)

if s in c.HC_MIMIC:
weight += c.hc_weight/len(c.HC_MIMIC)

if s in c.U_MIMIC:
weight += c.u_weight/len(c.U_MIMIC)

if s in c.CS_MIMIC:
weight += c.cs_weight/len(c.CS_MIMIC)

if s in c.E_MIMIC:
weight += c.e_weight/len(c.E_MIMIC)

if s in c.I_MIMIC:
weight += c.i_weight/len(c.I_MIMIC)

if s in c.T_MIMIC:
weight += c.t_weight/len(c.T_MIMIC)

weight =  -weight * multiplier

order_target_percent(s, weight)

def norm(c, d):
if d.min() >= 0 or d.max() <= 0:
d -= d.mean()
pos  = d[ d > 0 ]
neg  = d[ d < 0 ]
num  = min(len(pos), len(neg))
neg  = neg.sort_values(ascending=False).tail(num)
pos /=   pos.sum()
neg  = -(neg / neg.sum())
return pos.append(neg)

def records(context, data):
pos = context.portfolio.positions
long_list = [z.amount * z.last_sale_price for s, z in pos.items() if z.amount > 0]
shrt_list = [z.amount * z.last_sale_price for s, z in pos.items() if z.amount < 0]
long_len = len(long_list)
shrt_len = len(shrt_list)

record(
pos   = len(pos),
lv    = context.account.leverage,
longs = long_len,
shrts = shrt_len,
exposure = context.account.net_leverage,
)
There was a runtime error.

Does this work better than constraining the style risks via the optimizer? Or does it do something fundamentally different? (I guess you can also update the code now that the whitepaper is released which gives exact specifications of the different styles)