A Basic Logical Invest Global Market Rotation Strategy

This may be one of the simplest strategies I’ve ever presented on this blog, but nevertheless, it works, for some definition of “works”.

Here’s the strategy: take five global market ETFs (MDY, ILF, FEZ, EEM, and EPP), along with a treasury ETF (TLT), and every month, fully invest in the security that had the best momentum. While I’ve tried various other tweaks, none have given the intended high return performance that the original variant has.

Here’s the link to the original strategy.

While I’m not quite certain of how to best go about programming the variable lookback period, this is the code for the three month lookback.


symbols <- c("MDY", "TLT", "EEM", "ILF", "EPP", "FEZ")
getSymbols(symbols, from="1990-01-01")
prices <- list()
for(i in 1:length(symbols)) {
  prices[[i]] <- Ad(get(symbols[i]))
prices <- do.call(cbind, prices)
colnames(prices) <- gsub("\\.[A-z]*", "", colnames(prices))
returns <- Return.calculate(prices)
returns <- na.omit(returns)

logicInvestGMR <- function(returns, lookback = 3) {
  ep <- endpoints(returns, on = "months") 
  weights <- list()
  for(i in 2:(length(ep) - lookback)) {
    retSubset <- returns[ep[i]:ep[i+lookback],]
    cumRets <- Return.cumulative(retSubset)
    rankCum <- rank(cumRets)
    weight <- rep(0, ncol(retSubset))
    weight[which.max(cumRets)] <- 1
    weight <- xts(t(weight), order.by=index(last(retSubset)))
    weights[[i]] <- weight
  weights <- do.call(rbind, weights)
  stratRets <- Return.portfolio(R = returns, weights = weights)

gmr <- logicInvestGMR(returns)

And here’s the performance:

> rbind(table.AnnualizedReturns(gmr), maxDrawdown(gmr), CalmarRatio(gmr))
Annualized Return                  0.287700
Annualized Std Dev                 0.220700
Annualized Sharpe (Rf=0%)          1.303500
Worst Drawdown                     0.222537
Calmar Ratio                       1.292991

With the resultant equity curve:

While I don’t get the 34% advertised, nevertheless, the risk to reward ratio over the duration of the backtest is fairly solid for something so simple, and I just wanted to put this out there.

Thanks for reading.

19 thoughts on “A Basic Logical Invest Global Market Rotation Strategy

  1. Is it possible to test the following: pick one etf based on 3 mo return (“MDY”, “EEM”, “ILF”, “EPP”, “FEZ”)
    and use the Logical Invest walk forward sharpe strategy to find the optimal allocation between that etf and TLT? Or maybe even test 2 separate screens?

    Screen A (Stocks): “MDY”, “EEM”, “ILF”, “EPP”, “FEZ”
    Screen B (Hedge): “TLT”, “JNK”, “PCY”, “CWB” maybe even add EFF, TIP

    take one stock from each and then use the walk forward strategy to find the optimal allocation

    Seems like it might be a good idea to screen between a couple of bond etfs to find the best hedge rather than just using TLT all the time.

  2. My first reaction to this is that there is probably some level of overfitting. As the saying goes in data science: if you torture your data long enough, it will confess… :)

    It may be interesting to test this strategy by offsetting the month end by a day or two, and see if you get similar results. If you get drastically different results, I’d say that’s a clear hint that overfitting is at play.

    • To be fair, the first day of the month is pretty sacred. I’ve found that in many of my backtests that I’ve done with monthly rotation strategies. So I’m sure there’ll be some loss of performance.

      • Point taken.
        Just for kicks, I ran your code and used a 0 to 10 days offset to see what we’d get. It’s still pretty good, with a CAGR between 22% to 28% and all Sharpe ratios above 1.0.

      • Hello Ilya,
        I was thinking of two strategies that may run in parallel with the same universe and rotate monthly with an offset of 2 weeks. Don’t you think it could smooth drawdown and increase returns ?
        Oh, and please, are you going to introduce the CAA ? :-)

    • I’m always suspicious of results based on first of the month trading. Shouldn’t there be a way to test an all days start resulting in resulting in min, max & avg? That way there can be more confidence that the strategy is robust and not reliant on a beginning of the month start date.

      • My belief is that a rolling date is the best way to avoid curve fitting. Over very long periods the end results are much the same regardless of which day of the month your first roll date occurs. As to Jamie’ first comment above any talk of “optimising” makes my hair stand on end. However in my view he is quite right not to rely on tests of small numbers of instruments, be they the stocks or the hedges.

  3. Pingback: Quantocracy's Daily Wrap for 05/18/2015 | Quantocracy

  4. I think you should lag weights for 1 period (i.e. weights <- lag(weights, 1)) so that previous 3-month cumulative returns determine the next month weight. Let me know if I am wrong.

  5. Pingback: Проверка стратегии GMR с применением языка R | QuantAlgos

  6. Isn’t there still a lag issue?

    Assume the decision to invest in an asset based on momentum over a lookback period happens on, e.g., a Friday (the end of the lookback period).
    The weights will start on that Friday.
    Return.portfolio() will give a return for the portfolio, using those weights, and it does so starting the following Monday.
    So Return.portfolio() does have a lag.
    But the close-to-close return on Monday requires the closing price on the preceding Friday.
    So you have to decide what asset to invest in using the close on Friday and also invest in that asset before the close.

    One can almost avoid this crystal ball effect by both deciding and investing say one minute before the close on Friday.
    But perhaps a more faithful backtest would start the portfolio returns on the following Tuesday.
    Or do I misunderstand?

    • That’s essentially the one slightly optimistic assumption — that you can get the data, say, five minutes before close, and get in at that time. Since you’re holding for a month, it doesn’t have an enormous effect.

  7. In retrospect, this looks like a curve-fit. Frank published this towards the end of 2013. I just ran the code since we have a few years of out-of-sample data. The returns are as follows: 2014 had +1.8%, 2015 had -18%, 2016 (so far) has -1.2%. The new maximum draw-down, which has occurred since publication, is a whopping 35%. Anyone actually using this is getting killed.

  8. Pingback: Create Amazing Looking Backtests With This One Wrong–I Mean Weird–Trick! (And Some Troubling Logical Invest Results) | QuantStrat TradeR

Leave a Reply to Ilya Kipnis Cancel reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s