Back to Community
Different long short strategy algo. Additive and filter factors.

Hi,

I'm building an algo that accomplishes:
- Follows a different strategy for longs and shorts.
- Some of the factors are applied as filters, reducing the universe.
- Some of the factors are processed (ranked) and added altogether.
- Is market neutral.
- Uses order_optimal_portfolio and TargetWeights.

I've built the skeleton and implemented some factors just as a first draft (not focused on returns for now, just on coding the strategy):

Short:
- First, filters low profitability stocks.
- Second, filters expensive companies (using the profitability result as a mask).
- Once we have the stocks to trade, weights are defined by adding high volatility and low momentum.

Long:
- This part of the portfolio (50%), in the example just hedges longing the S&P.

Some doubts or things that came to my mind while building it:
- Being profitability the first filter factor, should it be the most emphasized?
- Regarding factor addition, in other discussions I've read that something similar to what I do here does not provide real additive quality but just average between two factors. Which are typical techniques for proper factor addition?
- Any other feedback on the approach is really appreciated.

Thanks in advance.

Clone Algorithm
1
Loading...
Total Returns
--
Alpha
--
Beta
--
Sharpe
--
Sortino
--
Max Drawdown
--
Benchmark Returns
--
Volatility
--
Returns 1 Month 3 Month 6 Month 12 Month
Alpha 1 Month 3 Month 6 Month 12 Month
Beta 1 Month 3 Month 6 Month 12 Month
Sharpe 1 Month 3 Month 6 Month 12 Month
Sortino 1 Month 3 Month 6 Month 12 Month
Volatility 1 Month 3 Month 6 Month 12 Month
Max Drawdown 1 Month 3 Month 6 Month 12 Month
import quantopian.algorithm as algo
from quantopian.pipeline.filters import QTradableStocksUS
from quantopian.pipeline import Pipeline
from quantopian.pipeline.factors import CustomFactor
from quantopian.pipeline.data.builtin import USEquityPricing
from quantopian.pipeline.data.morningstar import Fundamentals as ms
import quantopian.optimize as opt
import numpy as np 
import pandas as pd
from quantopian.pipeline.filters import  StaticAssets

def initialize(context):
    context.FILTER_BASE = 80 
    context.FILTER_LONG = 1 
    context.FILTER_SHORT = 30
    context.long_weights = pd.Series() 
    context.short_weights = pd.Series()

    set_commission(commission.PerTrade(cost=0.00))
    set_slippage(slippage.FixedSlippage(spread=0.00))
    algo.attach_pipeline(make_pipeline(context), 'pipeline')   
    schedule_function(select_stocks_and_set_weights, date_rules.every_day(), 
        time_rules.market_open(minutes = 60))
    schedule_function(trade, date_rules.every_day(), 
        time_rules.market_open(minutes = 60))
 
def make_pipeline(context):
    universe = QTradableStocksUS()
    
    longs = build_long_filter(universe, context)
    shorts = build_short_filter(universe, context)
    
    alpha_short = build_short_sort(shorts)
    #for this case it doesn't really matter as we just have SPY    
    alpha_long = build_long_sort(longs)
    
    universe = longs | shorts
    
    pipe = Pipeline(columns={
            'long': longs, 
            'short': shorts,
            'alpha_long':alpha_long,
            'alpha_short':alpha_short,
            },screen=universe)
    return pipe

def select_stocks_and_set_weights(context, data):
    df = algo.pipeline_output('pipeline')
    long_stocks = df.query('long').index
    short_stocks = df.query('short').index
    
    alpha_long = df.alpha_long
    stock_weight_long = alpha_long/alpha_long.abs().sum()*0.5
    
    alpha_short = df.alpha_short
    stock_weight_short = alpha_short/alpha_short.abs().sum()*0.5
    
    context.long_weights = pd.Series(index=long_stocks, data=stock_weight_long)
    context.short_weights = pd.Series(index=short_stocks, data=-stock_weight_short)
    
def trade(context, data):
    total_weights = pd.concat([context.long_weights, context.short_weights])
    target_weights = opt.TargetWeights(total_weights) 
    order_optimal_portfolio(
        objective = target_weights,
        constraints = []
        )
    record(num_positions=len(context.portfolio.positions))
    record(leverage=context.account.leverage)
    
def build_long_filter(universe, context):
    return StaticAssets(symbols('SPY'))  
    
def build_short_filter(universe, context):  
    factor_1 = ms.roic.latest.rank() #profitability
    factor_2 = ms.fcf_yield.latest.rank() #value
    
    filter_1 = factor_1.bottom(context.FILTER_BASE, mask=universe)
    filter_2 = factor_2.bottom(context.FILTER_SHORT, mask=filter_1)
    return filter_2

def build_short_sort(universe):
    momentum = Momentum(mask=universe).rank(mask=universe, ascending=False)
    volatility = Volatility(mask=universe).rank(mask=universe)
    return momentum + volatility 

def build_long_sort(universe):
    return Momentum(mask=universe).rank(mask=universe) 
    
class Volatility(CustomFactor): 
    inputs = [USEquityPricing.close]  
    window_length = 20  
    def compute(self, today, assets, out, close):   
        daily_returns = np.diff(close, axis = 0) / close[0:-1] 
        out[:] = -np.nanstd(daily_returns, axis=0) * np.sqrt(250)
        
class Momentum(CustomFactor):
    inputs = [USEquityPricing.close]
    window_length = 128
    def compute(self, today, assets, out, prices):
        out[:] = np.nan_to_num((prices[-2] - prices[-128])/prices[-128])
There was a runtime error.