Back to Community
ETF Rotation Strategy

Sources:

The Lazy Trader ETF Rotation Strategy
http://www.the-lazy-trader.com/2015/01/etf-rotation-systems-to-beat-market-American-Equities.html

Developing A Rotation Strategy Using Highly Diversified ETFs - Part III
https://seekingalpha.com/article/2107713-developing-a-rotation-strategy-using-highly-diversified-etfs-part-iii

I buy 3 etfs based on a ranking of how it has performed in the past and the volatility.
Let me know what you think! Still work for lots of improvement!

Clone Algorithm
302
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
# Backtest ID: 58d57d8b94b5c81ccecb168f
There was a runtime error.
17 responses

Why do you rank them before? I mean, you do the returns for each ETF (20d, 3m, 6m, 1yr), but you don't need to rank them yet. First, do the score, and then rank them.
I would delete this:
day20_rank=day20_ret.rank(ascending=False)
day3mo_rank=day3mo_ret.rank(ascending=False)
day6mo_rank=day6mo_ret.rank(ascending=False)
day1yr_rank=day1yr_ret.rank(ascending=False)
vol_rank=volatility.rank(ascending=True)

Thank you,

So just to clarify. I want to rank by the average of their returns, not the average of their ranks.
i.e. I was doing
average_ranks=(1+3+7+4+3)/5
but you say
average_returns = (0.01 +0.09 +0.15 +0.10+0.19)/5
then:
rank = average_returns rank

That makes sense, here is the backtest.

Clone Algorithm
302
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
# Backtest ID: 597111b48f44b151fdd31762
There was a runtime error.

I still don't know why the alpha is so low compared with other similar strategies like this. Any one? https://www.quantopian.com/posts/global-market-rotation-strategy-buggy-implementation

I think it has to do with the performance of emerging ETFs, I used sector ETFs. I took out everything but the 3 month returns and the volatility. It performed a lot better. There's a leverage problem though, so it's not perfectly accurate right now.

Clone Algorithm
302
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
# Backtest ID: 59779e669e448e4e06de92c5
There was a runtime error.

Here is the performance with the sector etfs instead of emerging market. Interesting!

Clone Algorithm
302
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
# Backtest ID: 59779f2739718f4e1d752ccc
There was a runtime error.

I had to replace EDV with TLT. The partial fills from the stocks ordering were messing up the leverage. I'm sure there is a better way, to fix the problem and still trade EDV.

Clone Algorithm
302
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
# Backtest ID: 5977af183cbfe14e1efd5521
There was a runtime error.

When you introduce the volatility of the ETF, I think (might be wrong) that you are rewarding those with more vol. because you are adding it.

-ETF A has a 4% return, and 2% volatility = 0.7*4%+0.3*2%=2.8+0.6=3.4 Score
-ETF B has a 4% return , and 3% volatility = 0.7*4%+0.3*3%=2.8+0.9=3.7 Score

I did this based on your approach, but still not as profitable as yours

Clone Algorithm
7
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
# Backtest ID: 5978711dbcf2d651d7a146b1
There was a runtime error.

Also picking SHY instead of TLT may be a more accurate option

Start date of the EB code showing 800% is being changed to 2009. Tried this, same:

def initialize(context):  
    set_symbol_lookup_date('2004-01-31')  

@Blue So are you saying it is not trading until 2009?
@Ro FG You are exiting every time it is below the 200 day SPY, and putting it in EDV. It doesn't work as well as a safety because after the recession you get those signals, and it still goes up also.

I normalized the returns by dividing them by their volatility . I am not sure if this is correct. I also combined all the etfs from both strategies. I am trying to lower the drawdown, because the returns seem way to high. I wouldn't want to just invest in emerging etfs. What comes up must come down!

Clone Algorithm
302
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
# Backtest ID: 597b9621e3a3894f3b55fb86
There was a runtime error.

Relatively a flat strategy the last 3 years or so. Did really well up until 2010.

Clone Algorithm
302
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
# Backtest ID: 597b9782ae317c4f36b7bad4
There was a runtime error.

@EB I was saying the start date field changed to 2009 automatically. Clicking on that calendar after the test, there was a message to the effect of first available date for securities being traded, surely you've seen that a few times eh. If there's a log message about it as well, I missed it.

Switching to the second-most recent backtest above with apparent returns of 197, its profit per dollar was just 60 due to margin. A common problem. This version has apparent returns of 233 and profit per dollar is 232, so almost no margin. So not only is 233 higher than 197 but also 232 is higher than 60. Using profit per dollar activated|invested is the only way I know for comparing two algorithms apples-to-apples and seeing more clearly what they are really doing by neutralizing, or adjusting for, their use of capital, i.e. amount of risk. Maybe people don't go that route because of the reduced speed.

There were evidently so many partial fills (the cause of all that margin along with the inherent unpredictability that goes with it hand-in-hand, unintended margin with order_target_percent when there are partial fills) that I took an unusually extreme approach in reactivating close() by running an entire day before balance and again the morning of that monthly balance, as this, the first try. @Ro FG, you might want to use similar and see what happens in your version.

Clone Algorithm
8
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
# Backtest ID: 597ba96a3f6f2b53365603de
There was a runtime error.

interesting point. Dividing by the volatility lowers the drawdown, but the ratio returns/drawdown is worse.

Clone Algorithm
121
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
# Backtest ID: 59805238ea66af532117ef8c
There was a runtime error.

This version is the one that gives best results

Clone Algorithm
121
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
# Backtest ID: 59804d00ea66af532117ef44
There was a runtime error.

Hi there @Ro FG!
This is great work ! However, i am still not clear about " Choosing the best ETF from the ranking and take long positions.
best=(score_rank<=1) . Since its rebalancing every month on the first, it only takes into account the last ranking on the last day of the month( ETF is the first rank on 30th or 31th and buys that) from context.stock. Is there is a way to add how many number of times a particular ETF has rank 1 over the past month and the alto buys the ETF which appeared on Rank 1 max. number of times.

Cheers
Abhi

This makes pipeline values, orders and intraday leverage visible.
Addresses the previous max intraday leverage of 2 (from buying when sell was unfilled).
More output at the bottom in Source Code tab

You'll want to address the EEM Flyr NaN.

2004-01-02 05:45 before_trading_start:96 INFO .  
                         F1yr      F20d       F3m       F6m  Rank     Score  
Equity(12915 [MDY])  0.312246  0.001519  0.098958  0.183990   4.0  0.149178  
Equity(21769 [IEV])  0.324552  0.045654  0.159640  0.214569   2.0  0.186104  
Equity(23118 [EPP])  0.405521  0.034187  0.078139  0.206206   3.0  0.181013  
Equity(23134 [ILF])  0.573648  0.056455  0.156921  0.295129   1.0  0.270538  
Equity(24705 [EEM])       NaN  0.059416  0.159293  0.318650   NaN       NaN  
2004-01-02 05:45 log_pipe:231 INFO len 5  
                        min                mean                 max  
  F1yr       0.312245808241      0.403991685886      0.573647562935     NaNs 1/5  
  F20d     0.00151889120942     0.0394460654015     0.0594161715319  
   F3m      0.0781389800179      0.130590178576       0.15963963964  
   F6m       0.183989809724      0.243708652001      0.318649517685  
  Rank                  1.0                 2.5                 4.0     NaNs 1/5  
 Score       0.149178210627        0.1967082655        0.2705379173     NaNs 1/5  
2004-01-02 05:45 log_pipe:245 INFO _ _ _   F1yr   _ _ _  
    ... F1yr highs  
                         F1yr      F20d       F3m       F6m  Rank     Score  
Equity(23134 [ILF])  0.573648  0.056455  0.156921  0.295129   1.0  0.270538  
Equity(23118 [EPP])  0.405521  0.034187  0.078139  0.206206   3.0  0.181013  
Equity(21769 [IEV])  0.324552  0.045654  0.159640  0.214569   2.0  0.186104  
    ... F1yr lows  

[snip]

There are some issues filling orders to be aware of.

2004-01-05 06:31 picks:131 INFO ETF  
2004-01-06 06:31 buys:158 INFO  
     Buy: ILF  
2004-01-06 06:31 _trac:280 INFO    1   Buy 159 ILF _ at 62.86                           d2b9  
2004-01-06 06:32 _trac:280 INFO    2         ILF 159 unfilled  d2b9  
2004-01-06 06:33 _trac:280 INFO    3         ILF 159 unfilled  d2b9  
2004-01-06 06:34 _trac:280 INFO    4         ILF 159 unfilled  d2b9  
2004-01-06 06:35 _trac:280 INFO    5         ILF 159 unfilled  d2b9  
2004-01-06 06:36 _trac:280 INFO    6         ILF 159 unfilled  d2b9  
2004-01-06 06:37 _trac:280 INFO    7         ILF 159 unfilled  d2b9  
2004-01-06 06:38 _trac:280 INFO    8         ILF 159 unfilled  d2b9  
2004-01-06 06:39 _trac:280 INFO    9         ILF 159 unfilled  d2b9  
2004-01-06 06:40 _trac:280 INFO   10         ILF 159 unfilled  d2b9  
2004-01-06 06:41 _trac:280 INFO   11      Bot 159 ILF (159) at 62.86                    d2b9  
2004-02-03 06:31 picks:131 INFO ETF  
2004-02-04 06:31 buys:158 INFO  
     Buy: ILF  
2004-03-02 06:31 picks:131 INFO ETF  
2004-03-03 06:31 buys:158 INFO  
     Buy: ILF  
2004-04-02 06:31 picks:131 INFO Safety  
2004-04-02 06:31 sells:145 INFO  
    Sell: ILF  
2004-04-02 06:31 _trac:280 INFO    1   Sell -159 ILF (159) at 63.50                     c63a  
2004-04-02 06:32 _trac:280 INFO    2         ILF -159 unfilled  c63a  
2004-04-02 06:33 _trac:280 INFO    3         ILF -159 unfilled  c63a  
2004-04-02 06:34 _trac:280 INFO    4         ILF -159 unfilled  c63a  
2004-04-02 06:35 _trac:280 INFO    5         ILF -159 unfilled  c63a  
2004-04-02 06:36 _trac:280 INFO    6         ILF -159 unfilled  c63a  
2004-04-02 06:37 _trac:280 INFO    7         ILF -159 unfilled  c63a  
2004-04-02 06:38 _trac:280 INFO    8         ILF -159 unfilled  c63a  
2004-04-02 06:39 _trac:280 INFO    9         ILF -159 unfilled  c63a  
2004-04-02 06:40 _trac:280 INFO   10         ILF -159 unfilled  c63a  
2004-04-02 06:41 _trac:280 INFO   11         ILF -159 unfilled  c63a  
2004-04-02 06:42 _trac:280 INFO   12         ILF -159 unfilled  c63a  
2004-04-02 06:43 _trac:280 INFO   13         ILF -159 unfilled  c63a  
2004-04-02 06:44 _trac:280 INFO   14         ILF -159 unfilled  c63a  
2004-04-02 06:45 _trac:280 INFO   15         ILF -159 unfilled  c63a  
2004-04-02 06:46 _trac:280 INFO   16         ILF -159 unfilled  c63a  
2004-04-02 06:47 _trac:280 INFO   17      Sold -159 ILF _ at 64.20                      c63a  
2004-04-05 06:31 buys:158 INFO  
     Buy: SHY  
2004-04-05 06:31 _trac:280 INFO    1   Buy 123 SHY _ at 82.42                           9f91  
2004-04-05 06:32 _trac:280 INFO    2      Bot 123 SHY (123) at 82.42                    9f91  
2004-05-04 06:31 picks:131 INFO Safety  
2004-05-05 06:31 buys:158 INFO  
     Buy: SHY  
2004-06-02 06:31 picks:131 INFO Safety  
2004-06-03 06:31 buys:158 INFO  
     Buy: SHY  
2004-07-02 06:31 picks:131 INFO ETF  
2004-07-02 06:31 sells:145 INFO  
    Sell: SHY  
2004-07-02 06:31 _trac:280 INFO    1   Sell -123 SHY (123) at 81.78                     46d6  
2004-07-02 06:32 _trac:280 INFO    2         SHY -123 unfilled  46d6  
2004-07-02 06:33 _trac:280 INFO    3         SHY -123 unfilled  46d6  
2004-07-02 06:34 _trac:280 INFO    4         SHY -123 unfilled  46d6  
2004-07-02 06:35 _trac:280 INFO    5         SHY -123 unfilled  46d6  
2004-07-02 06:36 _trac:280 INFO    6         SHY -123 unfilled  46d6  
2004-07-02 06:37 _trac:280 INFO    7         SHY -123 unfilled  46d6  
2004-07-02 06:38 _trac:280 INFO    8         SHY -123 unfilled  46d6  
2004-07-02 06:39 _trac:280 INFO    9         SHY -123 unfilled  46d6  
2004-07-02 06:40 _trac:280 INFO   10         SHY -123 unfilled  46d6  
2004-07-02 06:41 _trac:280 INFO   11      Sold -30/-123 SHY (93) at 81.79  (-20)        46d6  
2004-07-02 06:44 _trac:280 INFO   14      Sold -90/-123 SHY (3) at 81.83  (-60)         46d6  
2004-07-02 06:47 _trac:280 INFO   17      Sold -123 SHY _ at 81.83                      46d6  
2004-07-06 6:31 buys:159 INFO  
     Buy: MDY  
2004-07-06 6:31 _trac:281 INFO    1   Buy 92 MDY _ at 109.32                           d7d0  
2004-07-06 6:32 _trac:281 INFO    2      Bot 92 MDY (92) at 109.27                     d7d0  

[snip]

Orders become more interesting if you want to turn on try_all_longs.
Then you could try replacing .Rank with .F3m or something (and each one) to see how those compare.
Or adding two or more while leaving one or more out or use a threshold or whatever else you can think of to try.
All a bit easier with this visibility and style of code.

2007-12-04 06:31 _trac:281 INFO    1   Sell -14 EEM (14) at 152.75                      5f8a  
2007-12-04 06:31 _trac:281 INFO    1   Sell -35 MDY (35) at 154.81                      975e  
2007-12-04 06:31 _trac:281 INFO    1   Sell -4 ILF (4) at 247.59                        2973  
2007-12-04 06:31 _trac:281 INFO    1   Sell -19 EPP (19) at 166.00                      134a  
2007-12-04 06:31 _trac:281 INFO    1   Sell -37 IEV (37) at 120.27                      fb58  
2007-12-04 06:32 _trac:281 INFO    2      Sold -19 EPP _ at 165.72                      134a  
2007-12-04 06:32 _trac:281 INFO    2         IEV -37 unfilled  fb58  
2007-12-04 06:32 _trac:281 INFO    2         ILF -4 unfilled  2973  
2007-12-04 06:32 _trac:281 INFO    2      Sold -14 EEM _ at 152.84                      5f8a  
2007-12-04 06:32 _trac:281 INFO    2      Sold -35 MDY _ at 154.78                      975e  
2007-12-04 06:33 _trac:281 INFO    3         IEV -37 unfilled  fb58  
2007-12-04 06:33 _trac:281 INFO    3         ILF -4 unfilled  2973  
2007-12-04 06:34 _trac:281 INFO    4      Sold -37 IEV _ at 119.24                      fb58  
2007-12-04 06:34 _trac:281 INFO    4         ILF -4 unfilled  2973  
2007-12-04 06:35 _trac:281 INFO    5      Sold -4 ILF _ at 248.52                       2973  

(Just thought I'd try to help because someone kindly contacted me about this but otherwise my interest is only in dynamically selected 50+ stocks).

Clone Algorithm
6
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
# Backtest ID: 5c0854ae6d36e84a44094b8a
There was a runtime error.