Back to Community
Sector Rotation ETF Strategy

I want to backtest sector rotation ETF Strategy as described here http://myetfhedgefund.com/strategies/sector-switch

UNIVERSE:
IDU (U.S. Utilities)
IYC (U.S. Consumer Services)
IYE (U.S. Energy Sector)
IYF (U.S. Financials)
IYH (U.S. Healthcare Sector)
IYJ (U.S. Industrial Sector)
IYK (U.S. Consumer Goods)
IYM (U.S. Basic Materials Sector)
IYW (U.S. Technology)
IYZ (U.S. Telecommunications)
TLT

ALGO:
- At the end of each month, RANK the 10 ETFs based on their 6-month returns.
- At the close of the 1st trading day of each month, BUY the Top 2 ETFs, except if they closed the previous month below their 6-month moving average. In such case, buy TLT (Long-Term Treasury Bonds) instead.

I donot know how to test sector rotation strategy. Do you have any idea?

8 responses

That looks pretty simple, a good project for starting out. Are you asking for someone to write that for you? I could do it when I get home tonight, unless someone beats me to it.

I am new to quantoption. It would be nice if you would be able to write that. I donot know where to start.

I don't mind writing it, but bear in mind you are unlikely to learn anything from my doing it!

Hi KORAY, thank you for sharing the link. Attached will help you kick start this strategy. Clone the algorithm and go through the code line by line and let us know if the logic is implemented appropriately. This will get you familiarized with Quantopian. Finally, backtest the algorithm in minute-resolution, and confirm the backtest performance is comparable to that presented in the referenced blog. Happy backtesting.
update1: removed unused code.
update2: updated logic of code.

Clone Algorithm
332
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
# Sector Rotation ETF Strategy, 
# http://myetfhedgefund.com/strategies/sector-switch
# modified from Keller/Butler - Elastic Asset Allocation (EAA)
# https://www.quantopian.com/posts/keller-slash-butler-a-century-of-generalized-momentum-elastic-asset-allocation-eaa

import numpy as np
import pandas as pd
import datetime as dt
import math

def initialize(context):

    # Assets (N=10) from http://myetfhedgefund.com/strategies/sector-switch
    # IDU (U.S. Utilities)
    # IYC (U.S. Consumer Services)
    # IYE (U.S. Energy Sector)
    # IYF (U.S. Financials)
    # IYH (U.S. Healthcare Sector)
    # IYJ (U.S. Industrial Sector)
    # IYK (U.S. Consumer Goods)
    # IYM (U.S. Basic Materials Sector)
    # IYW (U.S. Technology)
    # IYZ (U.S. Telecommunications)
    # TLT  sid(23921)
    context.use_adjusted = False
    context.active = [sid(23921),sid(21638),sid(21644),sid(21646),sid(21522),sid(21648),sid(21649),sid(21650),sid(21651),sid(21524),sid(21525)]
    context.cash = sid(23921)
    context.bill = sid(23921)
    
    context.leverage = 1.0
    

    context.assets = set(context.active + [context.cash, context.bill])
    context.alloc = pd.Series([0.0] * len(context.assets), index=context.assets)

    context.isFirstTimeRun = True
    
    # logic 4. At the close of the 1st trading day of each month,    
    schedule_function(
        reallocate,
        date_rules.month_start(days_offset=0),
        time_rules.market_close(hours=0,minutes=1)
    )
    
    schedule_function(
        rebalance,
        date_rules.month_start(days_offset=0),
        time_rules.market_close(hours=0,minutes=1)
    )

def handle_data(context, data):
    if context.isFirstTimeRun is True:
        context.isFirstTimeRun = False
        reallocate(context, data)
        rebalance(context, data)
        
    record(leverage = context.portfolio.positions_value / context.portfolio.portfolio_value)

def rebalance(context, data):
    for s in context.alloc.index:
        if s in data:
            order_target_percent(s, context.alloc[s] * context.leverage)
            
def reallocate(context, data):
    h = history(300, '1d', 'price')
    
    hm = h.resample('M', how='last')[context.active]
    hb = h.resample('M', how='last')[context.bill]
    
    non_cash_assets = list(context.active)
    non_cash_assets.remove(context.cash)
    
    print "***************************************************************"
    
    # core logic (logics 1,2,3,4)
    # 1. rank the 10 ETFs based on their 6-month returns.
    excess_six_mnth_return = hm.ix[-1] / hm.ix[-7] - hb.ix[-1] / hb.ix[-7]    
    z = excess_six_mnth_return 
    
    # 2. BUY the Top 2 ETFs, 
    top_n = 2 #     
    top_z = z.order().index[-top_n:]
    print "top_z = %s" % [(i.symbol,z[i]) for i in top_z]
    z[top_z] = 1
        
    # 3.1 if they closed the previous month below their 6-month moving average. In such case,     
    below_six_mnth_avg = hm.ix[-1] < hm.ix[-7:].mean()    
    z[below_six_mnth_avg == True] = 0.0
    
    w_z = (z[top_z] / z[top_z].sum()).dropna()
    w = pd.Series([0.0] * len(context.assets), index=context.assets)
    for s in w_z.index:
        w[s] = w_z[s]
        
    # 3.2 buy TLT (Long-Term Treasury Bonds) instead.
    w[context.cash] = np.abs(1.0-w_z.sum())
    print "Allocation:\n%s" % w
    
    context.alloc = w
    

There was a runtime error.

I have modified the strategy to better match the performance from the referenced blog.
Leverage is now set to 2, slippage and commission are disabled. Note the horrible max draw down.

Clone Algorithm
332
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
# Sector Rotation ETF Strategy, 
# http://myetfhedgefund.com/strategies/sector-switch
# modified from Keller/Butler - Elastic Asset Allocation (EAA)
# https://www.quantopian.com/posts/keller-slash-butler-a-century-of-generalized-momentum-elastic-asset-allocation-eaahttps://www.quantopian.com/algorithms/55a6f65ce964f84ac5000234#collaboration-modal

import numpy as np
import pandas as pd
import datetime as dt
import math

def initialize(context):

    # Assets (N=10) from http://myetfhedgefund.com/strategies/sector-switch
    # IDU (U.S. Utilities)
    # IYC (U.S. Consumer Services)
    # IYE (U.S. Energy Sector)
    # IYF (U.S. Financials)
    # IYH (U.S. Healthcare Sector)
    # IYJ (U.S. Industrial Sector)
    # IYK (U.S. Consumer Goods)
    # IYM (U.S. Basic Materials Sector)
    # IYW (U.S. Technology)
    # IYZ (U.S. Telecommunications)
    # TLT  sid(23921)
    context.active = [sid(23921),sid(21638),sid(21644),sid(21646),sid(21522),sid(21648),sid(21649),sid(21650),sid(21651),sid(21524),sid(21525)]
    context.cash = sid(23921)
    context.bill = sid(23921)
    
    context.leverage = 1.0
    show_off_to_non_quantopian_friends = True
    if show_off_to_non_quantopian_friends:
        context.leverage = 2.0
        set_commission(commission.PerShare(cost=0.00))
        set_slippage(slippage.FixedSlippage(spread=0.00))        
        print('leverage is changed; slippage, commision not set!') 
    
    context.assets = set(context.active + [context.cash, context.bill])
    context.alloc = pd.Series([0.0] * len(context.assets), index=context.assets)

    # logic 4. At the close of the 1st trading day of each month,    
    schedule_function(
        reallocate,
        date_rules.month_start(days_offset=0),
        time_rules.market_close(hours=0,minutes=1)
    )    
    schedule_function(
        rebalance,
        date_rules.month_start(days_offset=0),
        time_rules.market_close(hours=0,minutes=1)
    )

def handle_data(context, data):        
    record(leverage = context.portfolio.positions_value / context.portfolio.portfolio_value)

def rebalance(context, data):
    for s in context.alloc.index:
        if s in data:
            order_target_percent(s, context.alloc[s] * context.leverage)
            
def reallocate(context, data):
    h = history(300, '1d', 'price')
    hm = h.resample('M', how='mean')[context.active]
    hb = h.resample('M', how='mean')[context.bill]
    
    print "***************************************************************"
    
    # core logic (logics 1,2,3,4)
    # 1. rank the 10 ETFs based on their 6-month returns.
    excess_six_mnth_return = hm.ix[-1] / hm.ix[-7] - hb.ix[-1] / hb.ix[-7]    
    z = excess_six_mnth_return 
    
    # 2. BUY the Top 2 ETFs, 
    top_n = 2 #     
    top_z = z.order().index[-top_n:]
    print "top_z = %s" % [(i.symbol,z[i]) for i in top_z]
    z[top_z] = 1
        
    # 3.1 if they closed the previous month below their 6-month moving average. In such case,     
    below_six_mnth_avg = hm.ix[-1] < hm.ix[-7:].mean()    
    z[below_six_mnth_avg == True] = 0.0
    
    w_z = (z[top_z] / z[top_z].sum()).dropna()
    w = pd.Series([0.0] * len(context.assets), index=context.assets)
    for s in w_z.index:
        w[s] = w_z[s]
        
    # 3.2 buy TLT (Long-Term Treasury Bonds) instead.
    w[context.cash] = 1.0-w_z.sum()
    print "Allocation:\n%s" % w
    
    context.alloc = w
    

There was a runtime error.

thanks a lot that is great.

Is someone able to run this with the newer algorithms if not time consuming? It has information which I wish to study for my first strategy to back test and modify...

What updates are needed to bring this algo up to speed with 2018 Q upgrades?