Rules based sector rotation strategy based on Mebane Faber research

Copyright reserved by Jessica Stauth - posted Dec 13, 2013 in Quantopian

I came across this nice post on Mebane Faber’s research page outlining a simple rules-based sector momentum model for long term investing.

The model makes binary (yes/no) decisions on whether to invest, or not, in each of the nine major US stock market sectors based on whether they are trading above or below their 10 month trailing average price. The portfolio is equal weighted across all sectors that are trading above their trailing moving average, and the sector exposures are capped at a maximum level of 25% exposure to any one sector.

This algo is designed to work in minute mode and to be compatible with paper trading and live trading, as implemented rebalance is triggered once per week. It’s also a nice example for both the history API and one of the new order methods, the order_target_percent() method, which is an awesome time saver method if you find yourself rewriting or reusing your code for basic portfolio management. Also take a look at Dan’s post on these new methods for some more context.

There are lots of easy knobs to turn with this one, from the selection of the sector ETFs to the rebalance frequency, the momentum horizon and even the rules themselves, so clone it and see if you can find a better version than this one!

This is a template algorithm on Quantopian for you to adapt and fill in.

from dateutil.relativedelta import relativedelta
import datetime
import pandas as pd
import numpy as np
def initialize(context):
    Called once at the start of the algorithm.
    #set_commission(commission.PerShare(cost=0, min_trade_cost=0))
    context.TRADING_DAYS_IN_MONTH = 20
    context.spy = sid(8554)
    context.spy_sma_month_count = 10
    context.spy_sma_day_count = context.TRADING_DAYS_IN_MONTH * context.spy_sma_month_count
    context.max_active = 3

    context.sector_etfs = symbols("XLI", "XLB", "XLE", "XLV", "XLP", "XLU", "XLF", "XLY", "XLK")
    context.sector_perf = pd.Series()
    context.sector_perf_month_count = 3
    context.sector_perf_day_count = context.TRADING_DAYS_IN_MONTH * context.sector_perf_month_count
    # Rebalance every day, 1 hour after market open.
    schedule_function(my_rebalance, date_rules.month_start(), time_rules.market_open())
    # Record tracking variables at the end of each day.
    schedule_function(my_record_vars, date_rules.every_day(), time_rules.market_close())
def before_trading_start(context, data):
    Called every day before market open.
def my_assign_weights(etfs):
    Assign weights to securities that we want to order.
    if len(etfs) == 0:
        return 0 
        weight = 1.0 / len(etfs)
        return weight
def my_rebalance(context, data):
    Execute orders according to our schedule_function() timing. 
    spy_history = data.history(context.spy, 'price', context.spy_sma_day_count + 2, '1d')
    spy_sma = spy_history[:-2].mean()
    spy_price = spy_history[-2]
    #log.info("%.2f, %.2f, %r" % (spy_sma, spy_price, spy_price > spy_sma))
    if spy_price > spy_sma:
        hist = data.history(context.sector_etfs, 'price', context.sector_perf_day_count+1, '1d')
        context.sector_perf = pd.Series(index=context.sector_etfs)

        #price_history10_weekly = hist.resample('W', how='last')
        for etf in context.sector_etfs:
            etfPrices = hist.loc[:,etf]
            context.sector_perf[etf] = ((etfPrices[-2] - etfPrices[0])/etfPrices[0])

        context.sector_perf.sort_values(inplace=True, ascending=False)
        context.sector_perf = context.sector_perf[0:context.max_active]  

        # sum = np.sum(context.sector_perf);

        weight = my_assign_weights(context.sector_perf)

        for etf in context.portfolio.positions:
            if etf not in context.sector_perf and data.can_trade(etf):
                order_target_percent(etf, 0)

        for etf, value in context.sector_perf.iteritems():
            if data.can_trade(etf):
              #if weight != 0:
              #    log.info("Ordering %0.0f%% percent of %s" % (weight * 100, etf.symbol))
              order_target_percent(etf, weight)
        for etf in context.portfolio.positions:
            order_target_percent(etf, 0)
def my_record_vars(context, data):
    Plot variables at the end of each day.
    record(leverage=context.account.leverage, num_positions = len(context.portfolio.positions))
def handle_data(context,data):
    Called every minute.