Back to Community
Help needed for factor analysis and backtesting

I am studying this CGO factor and find it has some merit (ic positive and increasing cumulative returns) .

Loading notebook preview...
Notebook previews are currently unavailable.
3 responses

Here it is

Loading notebook preview...
Notebook previews are currently unavailable.

Thanks @ Vladimir !

I am a beginner of quantopian so I am not really sure how to interpret the factor analysis result other than reading IC and the cumulative curve.

For some reason, it does not work as expected when I run a backtest (I remove commission and slippage parts). Can you and other spot any wrong-doing in my algo?

Clone Algorithm
1
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
"""
This is a template algorithm on Quantopian for you to adapt and fill in.
"""
import quantopian.algorithm as algo
from quantopian.pipeline import Pipeline,CustomFactor
from quantopian.pipeline.data.builtin import USEquityPricing
from quantopian.pipeline.filters import QTradableStocksUS,StaticAssets
from quantopian.pipeline.factors import AverageDollarVolume, SimpleBeta
from quantopian.pipeline.data import morningstar 
from quantopian.pipeline.data import Fundamentals
import quantopian.optimize as opt
import quantopian.pipeline.factors as Factors
from quantopian.pipeline.experimental import risk_loading_pipeline
from quantopian.pipeline.classifiers.morningstar import Sector
from sklearn import preprocessing
import numpy as np
from scipy import stats

def preprocess(a):
    a = np.nan_to_num(a - np.nanmean(a)) 
    return preprocessing.scale(a)
    

def make_factors():
    
    class CGO(CustomFactor):    
        inputs = [USEquityPricing.close, USEquityPricing.volume, morningstar.valuation.shares_outstanding]
        window_length=1260
        
        def compute(self, today, assets, out, close, volume, share):
            mon = 60
            unit = 21
            df_price = close   
            df_hsl = volume/share 
            copy = 1 - df_hsl
            copy[-1,:] = 1.0
            copy = copy.prod(axis=0)/copy.cumprod(axis=0)*copy
            copy = copy * df_hsl
            k = copy.sum(axis=0)
            ref_pr = ((df_price*(copy)).sum(axis=0)/k)
            if unit > 1:
                df_price = df_price[::unit,]
            CGO = ((df_price[-1,:] - ref_pr)/df_price[-1,:])
            
            out[:] = preprocess(CGO)
    
    return {

        'CGO': CGO,
        
    }    

def initialize(context):
    #set_commission(commission.PerShare(cost=0.001, min_trade_cost=0))
    #set_slippage(slippage.VolumeShareSlippage(volume_limit=0.1, price_impact=0.1))
    context.spy = sid(8554)
    #context.exclusion_list = StaticRestrictions([sid(8340), sid(34648), sid(32430)])
    context.std = []
    
    algo.schedule_function(
            allocate,
            date_rule=algo.date_rules.every_day(),
            time_rule=algo.time_rules.market_open(hours=0,minutes=5),
        )
    
    # Create our dynamic stock selector.
    pipe = make_pipeline(context)
    algo.attach_pipeline(pipe, 'pipeline')
    algo.attach_pipeline(risk_loading_pipeline(), 'risk_loading_pipeline')

def make_pipeline(context):
    pipe = Pipeline()
    beta = SimpleBeta(target=symbol('SPY'),regression_length=120,
                      allowed_missing_percentage=1.0
                     )
    universe = QTradableStocksUS() & Sector().notnull() & beta.notnull() 
    dollar_volume = AverageDollarVolume(window_length=5, mask = universe)
    high_dollar_volume = dollar_volume.top(500)
    universe = universe & high_dollar_volume 
    mktcap = Factors.MarketCap(mask =universe)
    universe = universe & mktcap.bottom(200)
        
    factors = make_factors()    
    combined_alpha = None
    for name, f in factors.iteritems():
        fac = f(mask=universe)
        if combined_alpha == None:
            combined_alpha = fac.zscore()
        else:
            combined_alpha += fac.zscore()
            
    book_value = Fundamentals.tangible_book_value.latest
    pipe.add(mktcap, 'mkt_cap')
    pipe.add(book_value, 'value')
    pipe.add(beta, 'beta')
    pipe.add(combined_alpha,'combined_alpha')
    pipe.add(Sector(), 'sector')
    pipe.set_screen(universe & combined_alpha.notnull())

    
    return pipe


def before_trading_start(context, data):

    context.risk_loading_pipeline = algo.pipeline_output('risk_loading_pipeline').dropna() 
    context.output = algo.pipeline_output('pipeline')
    context.sector = context.output.sector


def allocate(context, data):

    df = context.output
    stocks = df.index
    st = []
    for stock in stocks:
        if data.can_trade(stock):
            st.append(stock)
    alpha = df.loc[st,:].combined_alpha    
    how_to = {'1': 'alpha', '2':'weights'}
    
    how = how_to['1']
     
       
    # Constraint Parameters
    MAX_GROSS_LEVERAGE = 1.0
    MAX_SHORT_POSITION_SIZE = 0.025  # 1.5%
    MAX_LONG_POSITION_SIZE = 0.025   # 1.5%
    MIN_BETA_EXPOSURE = -0.03
    MAX_BETA_EXPOSURE = 0.03
    SECTOR_EXPOSURE = 0.10
    MAX_TURNOVER = 0.95
    
    constraints = []
    constrain_gross_leverage = opt.MaxGrossExposure(MAX_GROSS_LEVERAGE)    
    constrain_pos_size = opt.PositionConcentration.with_equal_bounds(
        -MAX_SHORT_POSITION_SIZE,
        MAX_LONG_POSITION_SIZE,
    )
    beta_neutral = opt.FactorExposure(
        context.output[['beta']],#, 'mkt_cap','value']],
        min_exposures={'beta': MIN_BETA_EXPOSURE}, #'value': -0.1},
        max_exposures={'beta': MAX_BETA_EXPOSURE}, #'value': 0.1},
    )
   
    constrain_turnover = opt.MaxTurnover(MAX_TURNOVER)
    dollar_neutral = opt.DollarNeutral()#tolerance=0.001)
    sector_neutral = opt.NetGroupExposure.with_equal_bounds(
        labels=context.sector,
        min=-SECTOR_EXPOSURE,
        max=SECTOR_EXPOSURE,
    )   
    constrain_sector_style_risk = opt.experimental.RiskModelExposure(
        context.risk_loading_pipeline,
        version=0#opt.Newest,
        
    )
    
    constraints = [
        constrain_sector_style_risk,
        #sector_neutral,
        dollar_neutral,
        #constrain_turnover,
        #beta_neutral,
        constrain_pos_size,
        constrain_gross_leverage,
        ]
    
    
    # Run the optimization. This will calculate new portfolio weights and
    # manage moving our portfolio toward the target.
    if how == 'alpha':
        try:
            algo.order_optimal_portfolio(
                objective=opt.MaximizeAlpha(alpha),
                constraints=constraints
            ) 
        except Exception as e:  
            log.info('error {}'.format(e) ) 
            return
    else:
        
        weights = opt.calculate_optimal_portfolio(
            objective=opt.TargetWeights(alpha),
            constraints=constraints
        )
      
        objective = opt.TargetWeights(weights)
        
        algo.order_optimal_portfolio(  
            objective=objective,  
            constraints=[],  
            )  


def handle_data(context, data):
    """
    Called every minute.
    """
    pass
There was a runtime error.

I run the algo on a weekly schedule, it looks better now

Clone Algorithm
1
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
"""
This is a template algorithm on Quantopian for you to adapt and fill in.
"""
import quantopian.algorithm as algo
from quantopian.pipeline import Pipeline,CustomFactor
from quantopian.pipeline.data.builtin import USEquityPricing
from quantopian.pipeline.filters import QTradableStocksUS,StaticAssets
from quantopian.pipeline.factors import AverageDollarVolume, SimpleBeta
from quantopian.pipeline.data import morningstar 
from quantopian.pipeline.data import Fundamentals
import quantopian.optimize as opt
import quantopian.pipeline.factors as Factors
from quantopian.pipeline.experimental import risk_loading_pipeline
from quantopian.pipeline.classifiers.morningstar import Sector
from sklearn import preprocessing
import numpy as np
from scipy import stats

def preprocess(a):
    a = np.nan_to_num(a - np.nanmean(a)) 
    return preprocessing.scale(a)
    

def make_factors():
    
    class CGO(CustomFactor):    
        inputs = [USEquityPricing.close, USEquityPricing.volume, morningstar.valuation.shares_outstanding]
        window_length=1260
        
        def compute(self, today, assets, out, close, volume, share):
            mon = 60
            unit = 21
            df_price = close   
            df_hsl = volume/share 
            copy = 1 - df_hsl
            copy[-1,:] = 1.0
            copy = copy.prod(axis=0)/copy.cumprod(axis=0)*copy
            copy = copy * df_hsl
            k = copy.sum(axis=0)
            ref_pr = ((df_price*(copy)).sum(axis=0)/k)
            if unit > 1:
                df_price = df_price[::unit,]
            CGO = ((df_price[-1,:] - ref_pr)/df_price[-1,:])
            
            out[:] = preprocess(CGO)
    
    return {

        'CGO': CGO,
        
    }    

def initialize(context):
    #set_commission(commission.PerShare(cost=0.001, min_trade_cost=0))
    #set_slippage(slippage.VolumeShareSlippage(volume_limit=0.1, price_impact=0.1))
    context.spy = sid(8554)
    #context.exclusion_list = StaticRestrictions([sid(8340), sid(34648), sid(32430)])
    context.std = []
    
    algo.schedule_function(
            allocate,
            date_rule=algo.date_rules.week_start(),
            time_rule=algo.time_rules.market_open(hours=0,minutes=5),
        )
    
    # Create our dynamic stock selector.
    pipe = make_pipeline(context)
    algo.attach_pipeline(pipe, 'pipeline')
    algo.attach_pipeline(risk_loading_pipeline(), 'risk_loading_pipeline')

def make_pipeline(context):
    pipe = Pipeline()
    beta = SimpleBeta(target=symbol('SPY'),regression_length=120,
                      allowed_missing_percentage=1.0
                     )
    universe = QTradableStocksUS() & Sector().notnull() & beta.notnull() 
    dollar_volume = AverageDollarVolume(window_length=5, mask = universe)
    high_dollar_volume = dollar_volume.top(500)
    universe = universe & high_dollar_volume 
    mktcap = Factors.MarketCap(mask =universe)
    universe = universe & mktcap.bottom(200)
        
    factors = make_factors()    
    combined_alpha = None
    for name, f in factors.iteritems():
        fac = f(mask=universe)
        if combined_alpha == None:
            combined_alpha = fac.zscore()
        else:
            combined_alpha += fac.zscore()
            
    book_value = Fundamentals.tangible_book_value.latest
    pipe.add(mktcap, 'mkt_cap')
    pipe.add(book_value, 'value')
    pipe.add(beta, 'beta')
    pipe.add(combined_alpha,'combined_alpha')
    pipe.add(Sector(), 'sector')
    pipe.set_screen(universe & combined_alpha.notnull())

    
    return pipe


def before_trading_start(context, data):

    context.risk_loading_pipeline = algo.pipeline_output('risk_loading_pipeline').dropna() 
    context.output = algo.pipeline_output('pipeline')
    context.sector = context.output.sector


def allocate(context, data):

    df = context.output
    stocks = df.index
    st = []
    for stock in stocks:
        if data.can_trade(stock):
            st.append(stock)
    alpha = df.loc[st,:].combined_alpha    
    how_to = {'1': 'alpha', '2':'weights'}
    
    how = how_to['1']
     
       
    # Constraint Parameters
    MAX_GROSS_LEVERAGE = 1.0
    MAX_SHORT_POSITION_SIZE = 0.025  # 1.5%
    MAX_LONG_POSITION_SIZE = 0.025   # 1.5%
    MIN_BETA_EXPOSURE = -0.03
    MAX_BETA_EXPOSURE = 0.03
    SECTOR_EXPOSURE = 0.10
    MAX_TURNOVER = 0.95
    
    constraints = []
    constrain_gross_leverage = opt.MaxGrossExposure(MAX_GROSS_LEVERAGE)    
    constrain_pos_size = opt.PositionConcentration.with_equal_bounds(
        -MAX_SHORT_POSITION_SIZE,
        MAX_LONG_POSITION_SIZE,
    )
    beta_neutral = opt.FactorExposure(
        context.output[['beta']],#, 'mkt_cap','value']],
        min_exposures={'beta': MIN_BETA_EXPOSURE}, #'value': -0.1},
        max_exposures={'beta': MAX_BETA_EXPOSURE}, #'value': 0.1},
    )
   
    constrain_turnover = opt.MaxTurnover(MAX_TURNOVER)
    dollar_neutral = opt.DollarNeutral()#tolerance=0.001)
    sector_neutral = opt.NetGroupExposure.with_equal_bounds(
        labels=context.sector,
        min=-SECTOR_EXPOSURE,
        max=SECTOR_EXPOSURE,
    )   
    constrain_sector_style_risk = opt.experimental.RiskModelExposure(
        context.risk_loading_pipeline,
        version=0#opt.Newest,
        
    )
    
    constraints = [
        constrain_sector_style_risk,
        #sector_neutral,
        dollar_neutral,
        #constrain_turnover,
        #beta_neutral,
        constrain_pos_size,
        constrain_gross_leverage,
        ]
    
    
    # Run the optimization. This will calculate new portfolio weights and
    # manage moving our portfolio toward the target.
    if how == 'alpha':
        try:
            algo.order_optimal_portfolio(
                objective=opt.MaximizeAlpha(alpha),
                constraints=constraints
            ) 
        except Exception as e:  
            log.info('error {}'.format(e) ) 
            return
    else:
        
        weights = opt.calculate_optimal_portfolio(
            objective=opt.TargetWeights(alpha),
            constraints=constraints
        )
      
        objective = opt.TargetWeights(weights)
        
        algo.order_optimal_portfolio(  
            objective=objective,  
            constraints=[],  
            )  


def handle_data(context, data):
    """
    Called every minute.
    """
    pass
There was a runtime error.