Back to Community
Chipy Hackathon: Long Short Portfolio

Multi Factor Model - Pipeline
Build Long/Short based on Z-score ranks

Clone Algorithm
26
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 import CustomFactor
from quantopian.pipeline.data import morningstar
from quantopian.pipeline.data.builtin import USEquityPricing
from quantopian.pipeline.factors import Latest, Returns, RSI, EWMA as ewma, SimpleMovingAverage

import numpy as np
import pandas as pd
from sklearn.svm import SVR
#Fundamental Factors
#Growth
class growth(CustomFactor):
    window_length = 1
    inputs = [morningstar.operation_ratios.revenue_growth]
    
    def compute(self, today, assets, out, growth):
        out[:] = growth
        
class sus_growth(CustomFactor):
    window_length = 1
    inputs = [morningstar.valuation_ratios.sustainable_growth_rate]
    
    def compute(self, today, assets, out, sustainable_growth_rate):
        out[:] = sustainable_growth_rate

#Quality
class turns(CustomFactor):
    window_length = 1
    inputs = [morningstar.operation_ratios.assets_turnover]
    
    def compute(self, today, assets, out, assets_turnover):
        out[:] = assets_turnover

class roic(CustomFactor):
    window_length = 1
    inputs = [morningstar.operation_ratios.roic]
    def compute(self, today, assets, out, roic):
        out[:] = roic

class margins(CustomFactor):
    window_length = 1
    inputs = [morningstar.operation_ratios.ebitda_margin]
    def compute(self, today, assets, out, ebitda_margin):
        out[:] = ebitda_margin

#Valuation
class peg_ratio(CustomFactor):
    window_length = 1
    inputs = [morningstar.valuation_ratios.peg_ratio]
    def compute(self, today, assets, out, peg_ratio):
        out[:] = np.where(peg_ratio <= 0, 0, 1/peg_ratio)
        
class pe_ratio(CustomFactor):
    window_length = 1
    inputs = [morningstar.valuation_ratios.pe_ratio]
    def compute(self, today, assets, out, pe_ratio):
        out[:] = np.where(pe_ratio <= 0, 0, 1/pe_ratio)

class pb_ratio(CustomFactor):
    window_length = 1
    inputs = [morningstar.valuation_ratios.pb_ratio]
    def compute(self, today, assets, out, pb_ratio):
        out[:] = np.where(pb_ratio <= 0, 0, 1/pb_ratio)

class total_yield(CustomFactor):
    window_length = 1
    inputs = [morningstar.valuation_ratios.total_yield]
    def compute(self, today, assets, out, total_yield):
        out[:] = total_yield
        
class ev_ebitda(CustomFactor):
    window_length = 1
    inputs = [morningstar.valuation_ratios.ev_to_ebitda]
    def compute(self, today, assets, out, ev_to_ebitda):
        out[:] = np.where(ev_to_ebitda <= 0, 0, 1/ev_to_ebitda)

class liquidity(CustomFactor):   
    inputs = [USEquityPricing.volume, USEquityPricing.close] 
    window_length = 21

    def compute(self, today, assets, out, volume, close): 
        out[:] = (volume * close).mean(axis=0)
        
class sector(CustomFactor):
    inputs = [morningstar.asset_classification.morningstar_sector_code]
    window_length = 1
    def compute(self, today, assets, out, morningstar_sector_code):
        out[:] = morningstar_sector_code[-1]
        
class AvgDailyDollarVolumeTraded(CustomFactor):
    
    inputs = [USEquityPricing.close, USEquityPricing.volume]
    window_length = 20
    
    def compute(self, today, assets, out, close_price, volume):
        out[:] = np.mean(close_price * volume, axis=0)

def the_pipe(context):
    pipe = Pipeline()
    #Add Last Price
    last_price = Latest([USEquityPricing.close])
    pipe.add(last_price,'last')
    #Add Trailing Returns
    daily_return = Returns(inputs=[USEquityPricing.close],window_length=2)
    pipe.add(daily_return,'daily_return')
    monthly_return = Returns(inputs=[USEquityPricing.close],window_length=20)/100+1
    pipe.add(monthly_return,'monthly_return')
    #Add RSI
    pipe.add(RSI(),'rsi')
    #Add MACD
    ewma26 = ewma.from_span([USEquityPricing.close], window_length=26, span=13)
    ewma12 = ewma.from_span([USEquityPricing.close], window_length=12, span=6)
    ewma9 = ewma.from_span([USEquityPricing.close], window_length=9, span=4)
    macd = (ewma12-ewma26)/ewma9
    pipe.add(macd,'macd')
    #Add Growth
    pipe.add(growth(),'rev_growth')
    pipe.add(sus_growth(),'sus_growth')
    #Add Quality
    pipe.add(turns(),'turns')
    pipe.add(margins(),'margins')
    pipe.add(roic(),'roic')
    #Add Valuation
    pipe.add(peg_ratio(),'peg')
    pipe.add(pe_ratio(),'pe')
    pipe.add(pb_ratio(),'pb')
    pipe.add(total_yield(),'total_yield')
    pipe.add(ev_ebitda(),'ev_ebitda')
    pipe.add(sector(), 'sector_code')
    pipe.add(liquidity(),'liquidity')
    
    sma_200 = SimpleMovingAverage(inputs=[USEquityPricing.close], window_length=200)
    dollar_volume = AvgDailyDollarVolumeTraded()
    
    # Screen out penny stocks and low liquidity securities.
    pipe.set_screen((sma_200 > 5) & (dollar_volume > 10**7))
    return pipe
        
def initialize(context):
    #Set the benchmark to S&P500 ETF
    context.spy = sid(8554)
    
    context.days_to_hold = 20
    #Longs/Shorts
    context.stocks_held = {}
    context.shorts = None
    context.longs = None
    
    attach_pipeline(the_pipe(context),'factors')
    
    schedule_function(before_trading_start,
                      date_rules.month_start(days_offset=2),
                      time_rules.market_open(minutes=15)
                     )
    schedule_function(rebalance,
                      date_rules.month_start(days_offset=2)
                     )
# Compute final rank and assign long and short baskets.
def before_trading_start(context, data):
    data = pipeline_output('factors').dropna()
    data_factor = data
    data_factor = data_factor.reset_index().rename(columns={'index':'asset'})
    
    factor_list = [u'ev_ebitda',u'margins',u'monthly_return',u'pb',u'pe',u'peg',u'rev_growth',u'roic',u'sus_growth',u'total_yield',u'turns',u'macd',u'rsi']
    
    data_factor = data_factor.dropna()
    for factor in factor_list:
        data_factor = data_factor.merge(data_factor.groupby(['sector_code'])[factor].agg({'factor_mean':np.mean,'factor_var':np.std}).reset_index(),on=['sector_code'])
        data_factor[factor+'_z'] = (data_factor[factor]-data_factor['factor_mean'])/data_factor['factor_var']  
        data_factor[factor] = np.where(data_factor[factor+'_z'].abs()>3,data_factor['factor_mean'],data_factor[factor])
        data_factor = data_factor.drop(['factor_mean','factor_var'],1)
        data_factor = data_factor.merge(data_factor.groupby(['sector_code'])[factor].agg({'factor_mean':np.mean,'factor_var':np.std}).reset_index(),on=['sector_code'])
        data_factor[factor+'_z'] = (data_factor[factor]-data_factor['factor_mean'])/data_factor['factor_var']  
        data_factor = data_factor.drop(['factor_mean','factor_var'],1)
    
    data_factor = data_factor.reset_index(drop=True)
    data_factor['raw_score'] = data_factor['ev_ebitda_z']+data_factor['margins_z']+data_factor['monthly_return_z']+data_factor['pb_z']+data_factor['pe_z']+data_factor['peg_z']+data_factor['rev_growth_z']+data_factor['roic_z']+data_factor['sus_growth_z']+data_factor['total_yield_z']+data_factor['turns_z']+data_factor['macd_z']+data_factor['rsi_z']
    data_factor['factor_mean'] = data_factor['raw_score'].mean()
    data_factor['factor_var'] = data_factor['raw_score'].std()
    data_factor['score'] = (data_factor['raw_score']-data_factor['factor_mean'])/data_factor['factor_var']
    data_factor = data_factor.drop(['factor_mean','factor_var'],1)
    
    results = data_factor[['asset','score']].set_index('asset')
    ranks = results.rank().mean(axis=1).order()
    
    context.shorts = 1/ranks.head(5)
    context.shorts /= context.shorts.sum()
    
    context.longs = ranks.tail(50)
    context.longs /= context.longs.sum()
    
    
    print('Data:'+str(len(data))+' Ranks:'+str(len(ranks))+' Shorts:'+str(len(context.shorts.index))+' Longs:'+str(len(context.longs.index)))
    # Will be called on every trade event for the securities you specify. 
def handle_data(context, data):
    record(lever=context.account.leverage)
    record(exposure=context.account.net_leverage)
    record(num_pos=len(context.portfolio.positions))
    record(oo=len(get_open_orders()))
    record(AccountValue=context.portfolio.positions_value + context.portfolio.cash)

def rebalance(context, data):
    port = context.portfolio.positions
    # Check our current positions
    
    for security in port:  
        if data.can_trade(security):  
            if context.stocks_held.get(security) is not None:  
                context.stocks_held[security] += 1  
                if context.stocks_held[security] >= context.days_to_hold:  
                    order_target_percent(security, 0)  
                    del context.stocks_held[security]  
            # If we've deleted it but it still hasn't been exited. Try exiting again  
            else:  
                log.info("Haven't yet exited %s, ordering again" % security.symbol)  
                order_target_percent(security, 0)  
    
    current_longs = [pos for pos in port if (port[pos].amount > 0 and pos in context.stocks_held)]
    current_shorts = [pos for pos in port if (port[pos].amount < 0 and pos in context.stocks_held)]
    long_stocks = context.longs.index.tolist() + current_shorts
    short_stocks = context.shorts.index.tolist() + current_longs
   
    # Rebalance our negative surprise securities (existing + new)
    for security in short_stocks:
        can_trade = context.stocks_held.get(security) <= context.days_to_hold or \
                    context.stocks_held.get(security) is None
        if data.can_trade(security) and can_trade:
            order_target_percent(security, -1.0 / len(short_stocks))
            if context.stocks_held.get(security) is None:
                context.stocks_held[security] = 0

    # Rebalance our positive surprise securities (existing + new)                
    for security in long_stocks:
        can_trade = context.stocks_held.get(security) <= context.days_to_hold or \
                    context.stocks_held.get(security) is None
        if data.can_trade(security) and can_trade:
            order_target_percent(security, 1.0 / len(long_stocks))
            if context.stocks_held.get(security) is None:
                context.stocks_held[security] = 0
    
        
## RETURNS TRUE IF THERE ARE PENDING OPEN ORDERS, OTHERWISE RETURNS FALSE
def has_open_orders(data,context):               
# Only rebalance when we have zero pending orders.
    has_orders = False
    for stk in data:
        orders = get_open_orders(stk)
        if orders:
            for oo in orders:                  
                message = 'Open order for {amount} shares in {stock}'  
                message = message.format(amount=oo.amount, stock=stk)  
            has_orders = True
    return has_orders           
    if has_orders:
        return  
        
        
        
            
There was a runtime error.