Back to Community
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.

Clone Algorithm
18
Loading...
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.filters import QTradableStocksUS
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
    
    momentum_longs = Momentum_Ratio.percentile_between(80, 99, mask = QTradableStocksUS())
    momentum_shorts = Momentum_Ratio.percentile_between(1, 20, mask = QTradableStocksUS())
    
    #Value
    
    pb_ratio = morningstar.valuation_ratios.pb_ratio.latest
    
    value_longs = pb_ratio.percentile_between(1, 20, mask = QTradableStocksUS())
    value_shorts = pb_ratio.percentile_between(80, 99, mask = QTradableStocksUS())
    
    #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, 20, mask = QTradableStocksUS())
    reversal_shorts = Reversal_Ratio.percentile_between(80, 99, mask = QTradableStocksUS())
    
    #Volatility
    
    volatility = AnnualizedVolatility(inputs=[USEquityPricing.close], window_length=30)
        
    volatility_longs = volatility.percentile_between(80, 99, mask = QTradableStocksUS())
    volatility_shorts = volatility.percentile_between(1, 20, mask = QTradableStocksUS())
    
    #Size
    
    market_cap = morningstar.valuation.market_cap.latest
    
    size_longs = market_cap.percentile_between(80, 99, mask = QTradableStocksUS())
    size_shorts = market_cap.percentile_between(1, 20, mask = QTradableStocksUS())
    
    
    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,            
        },
        screen = QTradableStocksUS()
    )    


def factor_pipeline_long():
    
    #Insert your own Long Logic
    
    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)
    
    mask =  (longs)
    
    
    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()
        },
        screen = mask
    )

    return Pipeline()

def factor_pipeline_shorts():
    
    #Insert your own Short Logic
    
    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)
    
    mask =  (shorts)
    
    
    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()
        },
        screen = mask
    )

    return Pipeline()


def before_trading_start(context, data):
   

    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.

Several things about this:
- 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

See also: Isolating Specific Returns

Clone Algorithm
14
Loading...
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.filters      import QTradableStocksUS
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

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

    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
    m = QTradableStocksUS()  # mask

    # Momentum
    sma_high       = SimpleMovingAverage(inputs=[USEquityPricing.close], window_length=30)
    Momentum_Ratio = USEquityPricing.close.latest / sma_high
    momentum_longs = Momentum_Ratio.percentile_between(hi, 99, mask=m)
    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)
    reversal_shrts = Reversal_Ratio.percentile_between(hi, 99, 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()

def before_trading_start(context, data):
    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])

def trade(context, data):
    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))
    pos  = pos.sort_values(ascending=False).head(num)
    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):
    ''' Log info about pipeline output or, z can be any DataFrame or Series
    https://www.quantopian.com/posts/overview-of-pipeline-content-easy-to-add-to-your-backtest
    '''
    # 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'.

Clone Algorithm
14
Loading...
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.filters      import QTradableStocksUS
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
        
    schedule_function(trade, date_rules.month_start(), time_rules.market_open(hours=1))
        
    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
    m = QTradableStocksUS()  # mask

    # Momentum
    sma_high         = SimpleMovingAverage(inputs=[USEquityPricing.close], window_length=330)
    Momentum_Ratio   = USEquityPricing.close.latest / sma_high
    momentum_longs   = Momentum_Ratio.percentile_between(hi, 99, mask=m)
    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)
    reversal_shrts   = Reversal_Ratio.percentile_between(hi, 99, 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
    
    longs = market_cap.top(500, mask = QTradableStocksUS())
    
    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
    
    shrts = market_cap.bottom(2, mask = QTradableStocksUS())

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

def before_trading_start(context, data):
    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

def trade(context, data):
    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))
    pos  = pos.sort_values(ascending=False).head(num)
    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)