Quantopian's community platform is shutting down. Please read this post for more information and download your code.
Back to Community
Enhanced Dual Momentum strategy using quarterly rotation

Securities basket used: US Equities (SPY), Int'l Equities (EFA), Emerging Markets (EEM), Gold (GLD), Corporate Bonds (AGG) and Long-Term Treasury (TLT). This current model holds up to 4 equal-weight positions.

This algorithm essentially monitors dual momentum during the last month of each quarter. These periods coincide with heavy institutional re-balancing and reporting activities, which in theory could provide information on what securities institutions are getting out of (underperformers) and what securities will be chosen by them for the coming quarter (outperformers).

More on this anomaly here:
http://blog.alphaarchitect.com/2015/11/30/momentum-seasonality/#gs.=t85MZQ

https://seekingalpha.com/instablog/709762-varan/251242-a-low-drawdown-strategy-for-sector-rotation-for-fidelity-select-funds

https://www.r-bloggers.com/the-quarterly-tactical-strategy-aka-qts/

Here's a backtest using PortfolioVisualizer that uses mutual funds and goes back to the 90s:
http://bit.ly/2vUKlGM

Have fun! Would love to see how this can be further improved. For collaboration requests, please contact: [email protected] and [email protected]

Shout out to the homie Mohammed Khalfan for helping me code this algo!

16 responses

Here's a "crazy" version of this algo using leveraged ETFs.

This algo is also quite robust! Here's a version of it trading a basket of commodity ETFs which if bought-and-held would have all gotten hammered since 2008.

Hi there, would you mind to explain how your algorithm works? I can't quite get the logic after reading the code since I am new to quantopian. Thanks.

I believe it's based on Dual Momentum / Global Equity Momentum: http://www.optimalmomentum.com/momentum.html

Hi Viridian,

Yes I understand that, but I can't get the purpose of a few bits in the code:

    hist = data.history(assets, 'price', mom_period + 1, '1d')  
    mom = (hist.iloc[-1]/hist.iloc[0]) - 1.0  
    mom = mom.dropna()  
    top_assets = mom.sort_values().index[-4:]  
    top_assets_values = mom.sort_values()[-4:]

It seems sorting the performance of the 4 assets 'SPY','EFA','EEM','GLD', then I am not sure what this means "mom = (hist[-1]/hist[0]) - 1.0", and why it is trying to do the following.

       else:  
            hist = data.history(safe, 'price', mom_period + 1, '1d')  
            mom = (hist[-1]/hist[0]) - 1.0  
            if mom > 0:  
                safe_percent += 0.25  
                if safe not in context.currently_holding: context.currently_holding.append(safe)  
            else:  
                tlt_percent += 0.25  
                if tlt not in context.currently_holding: context.currently_holding.append(tlt)  

Hi there,

So is this algorithm trying to do the following:

1) If the current month is not Mar / Jun / Sep / Dec, do nothing

2) Otherwise,

a) Retrieve the price of last 23 days. Divide the price of yesterday by the price of today then minus 1 to get the momentum.
b) Sort the momentum of SPY/EFA/EEM/GLD and get the best performing asset.
c) Do the same to decide the safe asset for AGG vs TLT
d) Calculate the weighting
e) Buy the best performing asset, the rest allocate to safe asset.

Repeat the same steps everyday in Mar/Jun/Sep/Dec.

I still can't understanding what this line really means:

    top_assets = assets_return.sort_values().index[-n:]  

Is it trying to select the best performing asset or I am wrong?

Appreciated for your explanation Vladimir. Will try to study the API documentation more to make sure I get that right.

XIV created a problem or two. Easy with hindsight, innit

Yikes. Even without the XIV implosion, it has performed quite poorly since 2018.

I'm not convinced that dual momentum strategies aren't significantly the result of data mining/curve fitting.

Even the non leveraged version has had a tough time. But.... Who knows?

So, the algo was published mid 2017 with a good back tested track record and then, bingo, 2018 was down. And yet the concept seems valid. To me at least.

The square brackets are PnL, added for each sell in this hacked version of track_orders.
Underscore means 0 shares, just easier to spot.
Current shares in parens.
First column is minute of the trading day.
Last column is last four of order id.
At times, there are partial fills, they look like this:

2008-03-31 6:31 clear:31 INFO held: ['TLT', 'SPY', 'GLD']   safe 0   tlt 0.5  
2008-03-31 6:31 _trac:147 INFO    1   Sell -92 TLT (92) at 96.09                            116  92a8  
2008-03-31 6:31 _trac:147 INFO    1   Sell -29 SPY (29) at 131.23                           116  9b92  
2008-03-31 6:31 _trac:147 INFO    1   Sell -52 GLD (52) at 92.58                            116  c267  
2008-03-31 6:32 _trac:147 INFO    2      Sold -30/-92 TLT (62) at 96.10   [+90]             11617  92a8  < Sold 30  
2008-03-31 6:32 _trac:147 INFO    2      Sold -29 SPY _ at 131.34   [-448]                  11617  9b92  
2008-03-31 6:32 _trac:147 INFO    2      Sold -52 GLD _ at 92.59   [+530]                   11617  c267  
2008-03-31 6:33 _trac:147 INFO    3      Sold -92 TLT _ at 96.09   [+276]                   17572  92a8  < Complete  

In one time period at least, 22 for mom_period (which then winds up as 23 day history) was far better than 21. Wonder why.

One factor which has certainly adversely affected the algo is that interest rates have no further to fall. Rising interest rates may mean the safe asset ought to be money markets or cash. But that will certainly badly affect the historic CAGR. Endless fiddling to "improve" the algo will undoubtedly destroy it.

Unfortunately that way lies curve fitting and madness.

Interesting to look back at "Wild About Harry".
The back test I ran ended in August 2016. Here is an update using TLT as the bond element. As perhaps I expected, you would have cursed its under-performance in the last couple of years. But One might assume it will prove its worth in a Black Swan event.

Oops

Would be interesting to determine the optimum rebalance schedule i.e., weekly, monthly, or biannually vs.quarterly.