Back to Community
Risk Managed Momentum, 12 Minus 1

Risk Managed Momentum, 12 Minus 1 from 8/2003 to 3/2019

Clone Algorithm
2
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.
"""
from quantopian.algorithm import attach_pipeline, pipeline_output
from quantopian.pipeline import Pipeline
from quantopian.pipeline.data.builtin import USEquityPricing
import numpy as np
import pandas as pd
import scipy as sp
from scipy.stats.mstats import winsorize
from scipy.stats import norm
import math
from quantopian.pipeline.data import morningstar, Fundamentals
from quantopian.pipeline.factors import CustomFactor, Returns
from datetime import datetime, timedelta
from quantopian.pipeline.filters import QTradableStocksUS, StaticAssets
from quantopian.pipeline.filters.morningstar import Q500US, Q1500US
from quantopian.pipeline.classifiers.morningstar import Sector
from sklearn.linear_model import LinearRegression
import cvxopt as opt
from cvxopt import blas, solvers
solvers.options['show_progress'] = False
 
 
def initialize(context):
    """
    Called once at the start of the algorithm.
    """
    context.factor_percentile_short = 0.5 #Should be between 0 and 0.5
    context.factor_percentile_long = 0.5
    context.long_weight = 1.0
    context.short_weight = 1.0
    context.long_short = 'long_short'
    context.payoff_rolling_window = 6 #rolling window size for smoothing payoff coefficients
    context.base_universe = Q1500US()
    context.winsorize = 0.05
    context.groupby_cols = []
    context.annvol_target = 0.12
    set_benchmark(symbol('SHY'))
       
    # Rebalance every month end at open.
    schedule_function(my_rebalance, date_rule=date_rules.month_end(),\
                      time_rule=time_rules.market_open(minutes=30))
    
    attach_pipeline(PriceMomentumPipe(context), 'price_momentum_pipeline')
 
    context.strategies = {'risk_managed_momentum':FactorStrategy('risk_managed_momentum')}
    
              
def my_rebalance(context,data):
    """
    Execute orders according to our schedule_function() timing. 
    """
    risk_managed_momentum_results = pipeline_output('price_momentum_pipeline')
    risk_managed_momentum_results.loc[:,'date'] = get_datetime()
    risk_managed_momentum_results.index.names = ['ticker']
    risk_managed_momentum_results = risk_managed_momentum_results.reset_index().sort_values(['date','ticker']).set_index(['date','ticker'])
    risk_managed_momentum_orders = factor_port_norm(context, risk_managed_momentum_results, groupby_cols=context.groupby_cols)
    context.strategies['risk_managed_momentum'].RecordPortVar(data, context, risk_managed_momentum_orders)
    
    if len(context.strategies['risk_managed_momentum'].port_vars.keys()) >= 6:
        hist_vars = pd.Series(context.strategies['risk_managed_momentum'].port_vars, index=context.strategies['risk_managed_momentum'].port_vars.keys()).sort_index()
        hist_vars = hist_vars[-6:]
        hist_vars = hist_vars.sum()
        vol_estimate = np.sqrt(21*hist_vars/126)
        scalar = (np.sqrt(1.0/12)*context.annvol_target)/vol_estimate
        risk_managed_momentum_orders *= scalar
        context.strategies['risk_managed_momentum'].RecordPortRet(data, context, risk_managed_momentum_orders)
        context.strategies['risk_managed_momentum'].Trade(data, context, 1.0, risk_managed_momentum_orders)
     
        
        
class MomentumFactorRR(CustomFactor):
    inputs = [USEquityPricing.close] 
    def compute(self, today, assets, out, close_price):
        out[:] = close_price[-22]/close_price[-len(close_price)]
 
 
 
 
def PriceMomentumPipe(context):
    # create the piepline for the data pull
    Data_Pipe = Pipeline()    
    spy = StaticAssets([symbol('SPY')])
    
    mom = MomentumFactorRR(window_length=252)
    
    Data_Pipe.add(mom,'mom')
    
    Data_Pipe.add(USEquityPricing.close.latest,'close_px')
    
    universe = context.base_universe & mom.notnull()
    
    Data_Pipe.set_screen(universe)
        
    return Data_Pipe
 
 
 
def sdnorm_quantile(p):
    return np.sqrt(2)*sp.special.erfinv(2*p-1)
 
 
def blom_transform(data):
    ranks = data.rank()
    c = 3/8
    temp = (ranks - c)/(len(data.dropna())-2*c+1)
    return sdnorm_quantile(temp)
 
 
def zscore(data):
    mu = np.mean(data)
    std = np.std(data)
    return (data-mu)/std
 
 
def last_day_of_month(any_day):
    next_month = any_day.replace(day=28) + timedelta(days=4)  # this will never fail
    return next_month - timedelta(days=next_month.day)
 
        
standardize = lambda x: (x-x.mean())/x.std()   
 
def factor_port_norm(context, pipeline_data, groupby_cols):
    factor_port = pipeline_data
    if len(groupby_cols) > 0:
        temp = factor_port.groupby(groupby_cols).apply(lambda x: pd.DataFrame(winsorize(x,limits=(context.winsorize,context.winsorize),\
                                             inclusive=(True,True),axis=0),index=x.index,columns=x.columns))
        temp = temp.groupby(groupby_cols).apply(blom_transform)
        temp.index = temp.index.droplevel(level=0)
    
        order_weights = temp.drop(groupby_cols+['close_px'],axis=1)
        order_weights = blom_transform(order_weights.sum(axis=1))
    else:
        temp = factor_port.apply(lambda x: winsorize(x,limits=(context.winsorize,context.winsorize),inclusive=(True,True),axis=0))
        temp = blom_transform(temp)
        temp.index = temp.index.droplevel(level=0)
    
        order_weights = temp.drop(['close_px'],axis=1)
        order_weights = blom_transform(order_weights.sum(axis=1))        
               
    lower = (order_weights - norm.ppf(context.factor_percentile_short)).abs().sort_values().index[0]
    upper = (order_weights - norm.ppf(1-context.factor_percentile_long)).abs().sort_values().index[0]
    
    if context.long_short == 'long':
        order_weights = order_weights.sort_values().loc[upper:]
        order_weights = pd.DataFrame(order_weights/order_weights.abs().sum(),columns=['weight'])
        order_weights *= context.long_weight
    elif context.long_short == 'short':
        order_weights = order_weights.sort_values().loc[:lower]
        order_weights = pd.DataFrame(order_weights/order_weights.abs().sum(),columns=['weight'])
        order_weights *= context.short_weight
    else:
        lower_weights = order_weights.sort_values().loc[:lower]
        lower_weights = lower_weights/lower_weights.abs().sum()
        lower_weights *= context.short_weight
 
        upper_weights = order_weights.sort_values().loc[upper:]
        upper_weights = upper_weights/upper_weights.abs().sum()
        upper_weights *= context.long_weight
 
        order_weights = pd.DataFrame(pd.concat([lower_weights, upper_weights],axis=0),columns=['weight'])
    
    return order_weights
 
 
# Strategy class to keep track of basic strategy parameters
class Strategy:
    def __init__(self, name):
        self.name = name
        #Keep track if we are invested or Flat
        self.raw_orders = {}
        self.weighted_orders = {}
        #Keep track of the last shares bought from this strategy so we can exit only those shares
        self.port_rets = {}
        #init these dicts
        self.port_vars = {}
        
# Sub-Strategy class to implement specific rules
class FactorStrategy(Strategy):
    def RecordPortRet(self, data, context, order_weights):
        self.weighted_orders[get_datetime().date()] = order_weights
                
        keys = set(self.weighted_orders.keys())
        unwanted = set([i for i in keys if i <= (get_datetime() + timedelta(days=-10*365.25)).date()])
        for unwanted_key in unwanted: del self.weighted_orders[unwanted_key]
 
        if len(self.weighted_orders.keys()) > 1:
            last_date = sorted(self.weighted_orders.keys())[-2]
            last_orders = self.weighted_orders[last_date]
            port_ret = 0
            stock_ret_sum = 0
            weight_sum = 0
            count = 0
            for stock, weight in zip(last_orders.index, last_orders['weight']):
                price_history = data.history(stock, fields="price", bar_count=40, frequency="1d")
                ret = price_history.iloc[-2]/price_history.loc[last_date] - 1
                ret = round(ret,5)
                if math.isnan(ret) == False:
                    port_ret += (1+weight)*(1+ret)
                    stock_ret_sum += ret
                    weight_sum += weight
                    count += 1
                else:
                    pass
            self.port_rets[last_day_of_month(last_date)] = port_ret - count - stock_ret_sum - weight_sum
 
            
    def RecordPortVar(self,data,context, order_weights):
        self.raw_orders[get_datetime().date()] = order_weights
                
        keys = set(self.raw_orders.keys())
        unwanted = set([i for i in keys if i <= (get_datetime() + timedelta(days=-10*365.25)).date()])
        for unwanted_key in unwanted: del self.raw_orders[unwanted_key]
 
        if len(self.raw_orders.keys()) > 1:
            last_date = sorted(self.raw_orders.keys())[-2]
            last_orders = self.raw_orders[last_date]
            var = []
            for stock, weight in zip(last_orders.index, last_orders['weight']):
                price_history = data.history(stock, fields="price", bar_count=40, frequency="1d")
                price_history = price_history.loc[last_date:]
                rets = np.log(price_history/price_history.shift())
                rets = (rets.dropna()*weight)
                var.append(rets)
            var = pd.concat(var, axis=1)
            var = var.sum(axis=1)
            var = var**2
            var = var.sum()
            self.port_vars[last_day_of_month(last_date)] = var
                       
    
    def Trade(self, data, context, allocation, order_weights):
        if allocation > 0:
            # Close all current positions
            for stock in context.portfolio.positions:
                if stock not in order_weights.index:
                   order_target(stock, 0)
            #Order
            for stock, weight in zip(order_weights.index, order_weights['weight']):
                if stock in context.portfolio.positions:
                    order_target_percent(stock, allocation*weight)
                else:
                    order_percent(stock, allocation*weight)
 
                
 
 
#
There was a runtime error.