Back to Community
ETF Rotation Strategy from LazyTrader

Hi Everyone,

I have tried to implement a strategy I found on (

It consists of rotating between 5 ETFs:
1. EFA: iShares MSCI EAFE (International Equity)
2. GLD: SPDR Gold Trust
3. ICF: iShares Cohen & Steers Realty REIT (Real Estate)
4. IEF: iShares Barclays 7 -10 Yr. Treasury (Medium Term Bonds)
5. SPY: SPDR S & P 500 Index (US Equity)

The ranking happens as follows:

  1. Make three rankings with.
    3-month average returns (40%)
    20-day average return (30%)
    20-day average volatility (30%)

  2. Build final rank with weights indicated in brackets above

  3. Buy top two ETFs

The ranking and corresponding rebalancing happens at the end of each month.

As results I get lower sharpe ratio, higher max drawdown and higher volatility.

Did I implement the strategy correctly?

Clone Algorithm
Total Returns
Max Drawdown
Benchmark Returns
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
import numpy as np

def initialize(context):
    Called once at the start of the algorithm.
    context.universe = [sid(8554), sid(22972), sid(22446), sid(23870), sid(26807)]
    schedule_function(my_rebalance, date_rules.month_end(), time_rules.market_close(hours=1))

def my_rebalance(context,data):
    closings = data.history(context.universe, fields = "price", bar_count = 61, frequency = "1d")
    return_20days = closings.ix[-2] / closings.ix[-21] - 1
    return_60days = closings.ix[-2] / closings.ix[-61] - 1
    return_daily = closings.pct_change(1)
    roll_vol_20days = return_daily.ix[-21:-1].std(axis = 0)
    rank_20days_ret = return_20days.rank(ascending=False)
    rank_60days_ret = return_60days.rank(ascending=False)
    rank_20days_roll_vol = roll_vol_20days.rank(ascending=True)
    # 20 days x 0.3, 60 days x 0.4, and 20 days vol x 0.3
    weighted_rank = rank_20days_ret * 0.3 + rank_60days_ret * 0.4 + rank_20days_roll_vol * 0.3
    context.to_buy = weighted_rank.sort_values(ascending = True).index[:2].tolist()
    for security in context.universe:
        if (security not in context.to_buy) & (data.can_trade(security)):
        elif (security in context.to_buy) & (data.can_trade(security)):
            order_target_percent(security, 0.5)
    #record("Leverage", context.account.leverage)
    #record("SPY", data.current(context.universe[0],"price"))
    #record("EFA", data.current(context.universe[1],"price"))
    #record("ICF", data.current(context.universe[2],"price"))
    #record("IEF", data.current(context.universe[3],"price"))
    #record("GLD", data.current(context.universe[4],"price"))
There was a runtime error.
5 responses

Whatever happens after 2014 is of absolutely no interest to anyone right :-)?

This might or might not be a shameless plug for the lazy website, i don't know, i haven't clicked the link, i am lazy too;-)

I stand corrected the user is legit. 507 backtests...

Hi Lionel,
I have a "standard test interval" from 1 Sept 2009 to 1 Sept 2017 and just ran it on that. Over that interval, it looks only marginally different from SPY most of the time, and then inferior to SPY since Jan 2016. I guess that means a modern VERY LAZY trader should just B&H the SPY. ;-))
Cheers, best wishes, Tony

Sebastian, there is a Pandas tutorial in your Notebooks that will teach you about .ix. It's a way of indexing into your dataframe.


The material on this website is provided for informational purposes only and does not constitute an offer to sell, a solicitation to buy, or a recommendation or endorsement for any security or strategy, nor does it constitute an offer to provide investment advisory services by Quantopian. In addition, the material offers no opinion with respect to the suitability of any security or specific investment. No information contained herein should be regarded as a suggestion to engage in or refrain from any investment-related course of action as none of Quantopian nor any of its affiliates is undertaking to provide investment advice, act as an adviser to any plan or entity subject to the Employee Retirement Income Security Act of 1974, as amended, individual retirement account or individual retirement annuity, or give advice in a fiduciary capacity with respect to the materials presented herein. If you are an individual retirement or other investor, contact your financial advisor or other fiduciary unrelated to Quantopian about whether any given investment idea, strategy, product or service described herein may be appropriate for your circumstances. All investments involve risk, including loss of principal. Quantopian makes no guarantees as to the accuracy or completeness of the views expressed in the website. The views are subject to change, and may have become unreliable for various reasons, including changes in market conditions or economic circumstances.

Thanks to everyone for your comments. I actually thought .ix comprises iloc and loc? With the first one you can access the index and columns by their names while with the later you can access them by their (index) number? With ix you can do both.

And this strategy is not from me (neither the website), I just wanted to practice implementing some strategies in the quantopian framework.