Back to Community
Fundamental analysis with dynamic exposure

Hi all,
I run some analysis on fundamental factors. For every factor there are 2 class: one for long side and one for short since I am doig 2 different ranking for the long list and short list.
The equity exposure is dynamic from +1 to -1 and it is based on momentum (simple moving average of 60 day cross 90 days). After ranking factors, there is a filter on stocks and only stocks with positive momentum will be in long list and only stocks with negative momentum will be in short list.

I am wondering how I can improve the algo and avoid drawdown in 2016. I think it can be a good idea to have dynamic weights for the ranked factors based on previous month result using multiple regression. Any idea how to do that?

Thanks,
Michele

Clone Algorithm
5
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
from quantopian.algorithm import attach_pipeline, pipeline_output
from quantopian.pipeline import Pipeline
from quantopian.pipeline.data import Fundamentals
from quantopian.pipeline.filters import QTradableStocksUS
from quantopian.pipeline.factors import BusinessDaysSincePreviousEvent
from quantopian.pipeline.factors import Latest, SimpleMovingAverage, Returns
from quantopian.pipeline.data.builtin import USEquityPricing
from quantopian.pipeline.data import morningstar
from quantopian.pipeline import CustomFactor
from quantopian.pipeline.filters import Q1500US

import pandas as pd
import numpy as np


############## long side factors ############################################

class Pricetoearnings(CustomFactor):   
    
       # Pre-declare inputs and window_length
    inputs = [morningstar.valuation_ratios.pe_ratio] 
    window_length = 1
    
    # Compute factor1 value
    def compute(self, today, assets, out, pe):
        table = pd.DataFrame(index=assets)
        table ["pe"] = pe[-1] 
        #nan are filled with max value to penalize securities without data but not exclude
        out[:] = table.fillna(table.max()).mean(axis=1)
 
 
class Evebitda(CustomFactor):   
    
    inputs = [morningstar.valuation_ratios.ev_to_ebitda] 
    window_length = 1
    
    
    def compute(self, today, assets, out, ee):
        table = pd.DataFrame(index=assets)
        table ["ee"] = ee[-1]
        out[:] = table.fillna(table.max()).mean(axis=1)
         
class Fcf(CustomFactor):   
    
    
    inputs = [morningstar.valuation_ratios.fcf_yield] 
    window_length = 1
    
    #nan are filled with min value to penalize securities without data but not exclude
    def compute(self, today, assets, out, fcf):
        table = pd.DataFrame(index=assets)
        table ["fcf"] = fcf[-1]
        out[:] = table.fillna(table.min()).mean(axis=1)
        
class Roic(CustomFactor):   
    
    inputs = [morningstar.operation_ratios.roic] 
    window_length = 1
    
    def compute(self, today, assets, out, roic):
        table = pd.DataFrame(index=assets)
        table ["roic"] = roic[-1]
        out[:] = table.fillna(table.min()).mean(axis=1)
  

######################### Short side factors #######################################
#do the opposite than long factor. Short factors are marked with 1
        
class Pricetoearnings1(CustomFactor):   
    
    inputs = [morningstar.valuation_ratios.pe_ratio] 
    window_length = 1
    
    #nan are filled with min value to penalize securities without data but not exclude
    def compute(self, today, assets, out, pe):
        table = pd.DataFrame(index=assets)
        table ["pe"] = pe[-1] 
        out[:] = table.fillna(table.min()).mean(axis=1)  
  
class Evebitda1(CustomFactor):   
    inputs = [morningstar.valuation_ratios.ev_to_ebitda] 
    window_length = 1
    
   
    def compute(self, today, assets, out, ee):
        table = pd.DataFrame(index=assets)
        table ["ee"] = ee[-1]
        out[:] = table.fillna(table.min()).mean(axis=1)
    
        
class Fcf1(CustomFactor):   
    
    
    inputs = [morningstar.valuation_ratios.fcf_yield] 
    window_length = 1
    
    def compute(self, today, assets, out, fcf):
        table = pd.DataFrame(index=assets)
        table ["fcf"] = fcf[-1]
        out[:] = table.fillna(table.max()).mean(axis=1)
     
class Roic1(CustomFactor):   
    
    inputs = [morningstar.operation_ratios.roic] 
    window_length = 1
      
    def compute(self, today, assets, out, roic):
        table = pd.DataFrame(index=assets)
        table ["roic"] = roic[-1]
        out[:] = table.fillna(table.max()).mean(axis=1)
        

########################### common factors#########################################        
     
class Month_return(CustomFactor):
    inputs = [USEquityPricing.close]
    window_length=25
    def compute(self, today, assets, out, price):
        out[:]=(price[0]/price[-22]) -1   
        
class Quarter_return(CustomFactor):
    inputs = [USEquityPricing.close]
    window_length=95
    def compute(self, today, assets, out, price):
        out[:]=(price[0]/price[-90]) -1           
        
######################################################################################
def initialize(context):
    
    # Rebalance every 2 weeks
    schedule_function(rebalance,
                      date_rule=date_rules.month_start(days_offset=5),
                      time_rule=time_rules.market_open(minutes=30))
    schedule_function(rebalance,
                      date_rule=date_rules.month_start(days_offset=18),
                      time_rule=time_rules.market_open(minutes=30))
    
    schedule_function(before_trading_start,
                      date_rule=date_rules.month_start(days_offset=5),
                      time_rule=time_rules.market_open(minutes=10))
    
    schedule_function(before_trading_start,
                      date_rule=date_rules.month_start(days_offset=18),
                      time_rule=time_rules.market_open(minutes=10))
    

    attach_pipeline(make_pipeline(), 'fundamentals_pipeline')

def make_pipeline():
    
    pipe = Pipeline()
    
    sector = morningstar.asset_classification.morningstar_sector_code.latest

    
    #exclude financial sector 
    universe = Q1500US() & (sector.element_of([101,102,205,206,207,308,309,310,311]))
    
    #add factors to pipeline

    month_ret = Month_return()
    pipe.add(month_ret, 'month_ret')
    q_return = Quarter_return()
    pipe.add(q_return,'q_return')
    
############long side#######################################################################        
    roic = Roic()
    pipe.add(roic, 'roic')
       
    ee = Evebitda()
    pipe.add(ee, 'ee')

    pe = Pricetoearnings()
    pipe.add(pe, 'pe')
    
    fcf = Fcf()
    pipe.add(fcf, 'fcf')
    
    #ranking the factors
    #lower is better
    pe_rank = pe.rank(mask=universe, ascending=True)
    pipe.add(pe_rank, 'pe_rank')
    
    ee_rank = ee.rank(mask=universe, ascending=True)
    pipe.add(ee_rank, 'ee_rank')
     
    #higher is better
    roic_rank = roic.rank(mask=universe, ascending=False)
    pipe.add(roic_rank, 'roic_rank')
    
    fcf_rank = fcf.rank(mask=universe, ascending=False)
    pipe.add(fcf_rank, 'fcf_rank')
    
    #assigned weights to each rank. Might it worth to make the weight dynamic using multiple regression? 

    combo_raw = (30*fcf_rank+10*pe_rank+20*ee_rank+40*roic_rank)/100
    pipe.add(combo_raw, 'combo_raw')
    
    # Rank the combo_raw and add that to the pipeline (lower rank is better)
    combo_rank = combo_raw.rank(ascending=True)
    pipe.add(combo_rank, 'combo_rank')
    
 ########short side######################################################################   
 #short factors marked with 1  
    
    roic1 = Roic1()
    pipe.add(roic1, 'roic1')
       
    ee1 = Evebitda1()
    pipe.add(ee1, 'ee1')

    pe1 = Pricetoearnings1()
    pipe.add(pe1, 'pe1')
       
    fcf1 = Fcf1()
    pipe.add(fcf1, 'fcf1')

    pe_rank1 = pe1.rank(mask=universe, ascending=False)
    pipe.add(pe_rank1, 'pe_rank1')
    
    ee_rank1 = ee1.rank(mask=universe, ascending=False)
    pipe.add(ee_rank1, 'ee_rank1')
    
        
    roic_rank1 = roic1.rank(mask=universe, ascending=True)
    pipe.add(roic_rank1, 'roic_rank1')
    
    fcf_rank1 = fcf1.rank(mask=universe, ascending=True)
    pipe.add(fcf_rank1, 'fcf_rank1')
    
    #assigned weights to each rank. Might it worth to make the weight dynamic using multiple regression?
    combo_raw1 = (30*fcf_rank1+10*pe_rank1+20*ee_rank1+40*roic_rank1)/100
    pipe.add(combo_raw1, 'combo_raw1')
    
     # Rank the combo_raw and add that to the pipeline (lower rank is better)
    combo_rank1 = combo_raw1.rank(ascending=True)
    pipe.add(combo_rank1, 'combo_rank1')
    
 ##################################################################################
  #movoing average for momentum 
    
    sma_60 = SimpleMovingAverage(inputs=[USEquityPricing.close], window_length=60, mask=universe)
    
    sma_90 = SimpleMovingAverage(inputs=[USEquityPricing.close], window_length=90, mask=universe)
    
    mom = sma_60 - sma_90
    pipe.add(mom, 'mom')
    
    #first decile for long stocks
    combo_rank_decile=combo_rank.deciles()
    top_decile = (combo_rank_decile.eq(0))
    long_list = (top_decile) 
   
    #first decile for short stock
    combo_rank_decile1=combo_rank1.deciles()
    top_decile1 = (combo_rank_decile1.eq(0))
    short_list = (top_decile1)
    
    #filtering for momentum: positive momentum for long stocks and negative momentum for short stocks
    longs = long_list & (mom>0) 
    shorts = short_list & (mom<0)
    
    securities_to_trade = (longs | shorts)
    
    pipe = Pipeline(
              columns={
                'longs': longs,
                'shorts': shorts,
                'pe1' : pe1,
                'pe_rank1':pe_rank1,
                'pe' : pe1,
                'pe_rank':pe_rank1,
                'combo_rank1':combo_rank1,
                'combo_rank1':combo_rank1,
               },
              screen = securities_to_trade
          )

    return pipe
    
"""
Runs our fundamentals pipeline before the marke opens every day.
"""
def before_trading_start(context, data): 

    context.pipe_output = pipeline_output('fundamentals_pipeline')
    
      
    context.longs = context.pipe_output[context.pipe_output['longs']].index


    context.shorts = context.pipe_output[context.pipe_output['shorts']].index

def rebalance(context, data):
    
    my_positions = context.portfolio.positions
    
    #assigned weights according to the number of total number of securities we have to trade. It sum up to 1    
    a=float(len(context.longs))
    b=float(len(context.shorts))

    try:          
   
        long_weight = float((a/(a+b))/a)
        
           
    except ZeroDivisionError:
            long_weight = 0        
       
    try:
        
        short_weight = -float((b/(a+b))/b)
        
      
    except ZeroDivisionError:
            short_weight = 0
    
    
                 
  
    for security in context.longs:
        if data.can_trade(security):
            order_target_percent(security, long_weight)
           

    for security in context.shorts:
         if data.can_trade(security):
            order_target_percent(security, short_weight)
            

    closed_positions = []
    
    # Close our previous positions that are no longer in our pipeline.
    for security in my_positions:
        if security not in context.longs and security not in context.shorts:
            
            order_target_percent(security, 0)
            closed_positions.append(security)
        
    
     # Check how many long and short positions we have.
    longs = shorts = 0
    for position in context.portfolio.positions.values():
        if position.amount > 0:
            longs += 1
        elif position.amount < 0:
            shorts += 1
   
    
    # Record our variables.
    record(long_count=longs, short_count=shorts)
    #record(long_W=longs*long_weight, short_w=shorts*short_weight)
    record(long_exposure=longs*long_weight)
    record(short_exposure=shorts*short_weight)
    record(leverage = context.account.leverage)
There was a runtime error.