Back to Community
Risk Budgeting to improve performance.

Attached is a backtest of a modified momentum strategy from the base by Johnny Wu. Attached in the reply to this post is a base benchmark of the strategy using equal weighting. The strategy is as follows:

Long 3 stocks that have performed the best over the last year for one month. Each month, positions are adjusted or liquidated based on the new set of stocks screened.

The universe I have chosen is a proxy for a potential global portfolio of US sectors, europe, asia, commodities, bonds and real estate.

The portfolio construction method is done through traditional risk parity. I assume that all stocks have equal pairwise correlation and the model simplifies to where the % weight in one stock is equal to its inverse variance divided by the sum of all stock's inverse variance.

Comparing with this method to the equal sizing method, I see two important traits:
1) The 08' crisis was well hedged through non-us positions
2) Although drawdowns and volatility are lower than equal sizing, after 2010, the Risk budgeting method underperforms the SPY. This has led me to conclude that the momentum strategy may not be as robust in large bull markets.
3) Correlations seem to be much higher after 2010 as well, therefore, our portfolio is just generating returns through beta.

Anyone else have success with this method of construction? Furthermore, has anyone implemented the numerical solution to risk budgeting with unique correlations?

Clone Algorithm
57
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
# For this example, we're going to write a simple momentum script.  
# When the stock goes up quickly, we're going to buy; 
# when it goes down we're going to sell.  
# Hopefully we'll ride the waves.

# To run an algorithm in Quantopian, you need two functions: 
# initialize and handle_data
from operator import itemgetter
import numpy as np
import pandas
import scipy
import math

def initialize(context):
    context.topMom = 3
    context.rebal_int = 3
    context.sort = False #Sort Optimize?
    context.rp = True #Risk Parity Optimize?
    context.lookback = 250
    set_symbol_lookup_date('2015-01-01')
    context.stocks = symbols('XLF', 'XLE', 'XLU', 'XLK', 'XLB', 'XLP', 'XLY', 'XLI', 'XLV', 'BIL', 'FEZ', 'SMEZ', 'DGT', 'EMFT', 'GXC', 'JPP', 'GLD', 'LAG', 'RWO')
    #set_universe(universe.DollarVolumeUniverse(floor_percentile=98, ceiling_percentile=100))
  
    schedule_function(rebalance,
                      date_rule=date_rules.month_start(),
                      time_rule=time_rules.market_open())

def rebalance(context, data):
    #Create stock dictionary of momentum
    MomList = GenerateMomentumList(context, data)
    
    #sell all previous unique positions
    for stock in context.portfolio.positions:
        if stock not in MomList.index:
           order_target(stock, 0)    
    #create % weight
    #spy = symbol('SPY')
    #if data[spy].price < data[spy].mavg(200):
    #    order_target_percent(symbol('IEF'), 1)
    #    return
    
    
    #Order
    for i,n in enumerate(MomList):
        if MomList.index[i] in data:
            if MomList.index[i] in context.portfolio.positions: #If position already exists
                order_target_percent(MomList.index[i], n)
            else:
                order_percent(MomList.index[i], n)
    
    pass

    
def GenerateMomentumList(context, data):
    
    MomList = []
    price_history = history(bar_count=context.lookback, frequency="1d", field='price')
    
    for stock in price_history:
        now = price_history[stock].ix[-1]
        old = price_history[stock].ix[0]
        pct_change = (now - old) / old
        #if now > data[stock].mavg(200):
        if pct_change == pct_change: #Check if nan
            MomList.append([stock, pct_change, price_history[stock].ix[0]])

    #sort in descending order, the price change (%)
        
    MomList = sorted(MomList, key=itemgetter(1), reverse=True)
    #return only the top "topMom" number of securities
    MomLongList = MomList[0:context.topMom]
    MomShortList = MomList[-context.topMom-1:-1]

    # Sort optimization
    if context.sort == True:
        #Covar Component
        rets_sort = pandas.DataFrame()
        rets_calc = (price_history/price_history.shift(1)-1)[1:].dropna(axis=1)
        for sec in MomLongList:
            rets_sort = rets_sort.append(rets_calc[sec[0]], ignore_index=False)
        for sec in MomShortList:
            rets_sort = rets_sort.append(rets_calc[sec[0]], ignore_index=False)
        covar = rets_sort.T.cov()
        
        #Centroid Component
        alpha = 0.4424-0.1185*pow(context.topMom*2,0.21)
        c = pandas.Series(range(1,context.topMom*2+1),index=covar.index)
        c = scipy.stats.norm.ppf((context.topMom*2+1-c-alpha)/(context.topMom*2-2*alpha+1))
        w = np.linalg.solve(covar,c)
        w = pandas.Series(w/sum(w), index=covar.index)
        return w
    
    # Risk Parity Optimization
    if context.rp == True:
        #Covar Component
        rets_sort = pandas.DataFrame()
        rets_calc = (price_history/price_history.shift(1)-1)[1:].dropna(axis=1)
        for sec in MomLongList:
            rets_sort = rets_sort.append(rets_calc[sec[0]], ignore_index=False)
        std = rets_sort.T.std()
        w = pandas.Series(np.power(std,-2)/sum(np.power(std,-2)), index=std.index)
        return w 


def change(one, two):
    return(( two - one)/one)
    
def handle_data(context, data):
    pass
There was a runtime error.
2 responses

Attached is the equal sizing backtest

Clone Algorithm
14
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
# For this example, we're going to write a simple momentum script.  
# When the stock goes up quickly, we're going to buy; 
# when it goes down we're going to sell.  
# Hopefully we'll ride the waves.

# To run an algorithm in Quantopian, you need two functions: 
# initialize and handle_data
from operator import itemgetter
import numpy as np
import pandas
import scipy
import math

def initialize(context):
    context.topMom = 3
    context.lookback = 250
    set_symbol_lookup_date('2015-01-01')
    context.stocks = symbols('XLF', 'XLE', 'XLU', 'XLK', 'XLB', 'XLP', 'XLY', 'XLI', 'XLV', 'BIL', 'FEZ', 'SMEZ', 'DGT', 'EMFT', 'GXC', 'JPP', 'GLD', 'LAG', 'RWO')
    #set_universe(universe.DollarVolumeUniverse(floor_percentile=98, ceiling_percentile=100))
  
    schedule_function(rebalance,
                      date_rule=date_rules.month_start(),
                      time_rule=time_rules.market_open())

def rebalance(context, data):
    #Create stock dictionary of momentum
    MomList = GenerateMomentumList(context, data)
    
    #sell all previous unique positions
    for stock in context.portfolio.positions:
        if stock not in MomList:
           order_target(stock, 0)    
    #create % weight
    #spy = symbol('SPY')
    #if data[spy].price < data[spy].mavg(200):
    #    order_target_percent(symbol('IEF'), 1)
    #    return
    
    num = len(MomList) # Keep it long only for now
    for i in MomList:
        if i in data:
            if i in context.portfolio.positions:
                order_target_percent(i, 0.95/num)
            else:
                order_percent(i, 0.95/num)
    
    pass

    
def GenerateMomentumList(context, data):
    
    MomList = []
    price_history = history(bar_count=context.lookback, frequency="1d", field='price')
    
    for stock in price_history:
        now = price_history[stock].ix[-1]
        old = price_history[stock].ix[0]
        pct_change = (now - old) / old
        #if now > data[stock].mavg(200):
        if pct_change == pct_change: #Check if nan
            MomList.append([stock, pct_change, price_history[stock].ix[0]])
       

    #sort in descending order, the price change (%)
        
    MomList = sorted(MomList, key=itemgetter(1), reverse=True)
    MomListRet = []
    for i in range(0,context.topMom): #+range(-context.topMom,0) <- add for shorts
        MomListRet.append(MomList[i][0])
    
    return MomListRet


def change(one, two):
    return(( two - one)/one)
    
def handle_data(context, data):
    pass
There was a runtime error.

@good trader... I just clone your algo nothing more.... how come it didnt work...?

------>21 Error Symbol 'LAG' was not found. Did you check set_symbol_lookup_date() to confirm the asset traded at this time?