Combined Momentum and Counter Trend Strategy on US Equity Indexes
Log in to collectAcademic paper
Strategy in a nutshell
The strategy combines trend-following on NASDAQ ETFs/futures with a counter-trend approach on the S&P 500. Trend signals are based on EMAs, while the counter-trend rule buys after 20-day lows. Each strategy receives 50% allocation.
Economic rationale
The counter-trend component cushions short-term reversals by entering after sharp declines, while the trend-following rule captures momentum. Together, they improve return-to-risk ratios compared to using either approach alone
Backtest performance
Annualised return12.2%
Beta-0.313
Sortino ratio0.004
Maximum drawdown-11.5%
Win rate47%
Full Python code
from AlgorithmImports import *
class MomentumandCountertrend(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2000, 1, 1)
self.SetCash(100000)
self.SetWarmUp(150)
self.spy = self.AddEquity("SPY", Resolution.Daily).Symbol
self.qqq = self.AddEquity("QQQ", Resolution.Daily).Symbol
self.qqq_short_ema = self.EMA("QQQ", 50, Resolution.Daily)
self.qqq_long_ema = self.EMA("QQQ", 150, Resolution.Daily)
self.low_history_period = 20
self.spy_low_history = RollingWindow[float](self.low_history_period)
def OnData(self, data):
if self.IsWarmingUp: return
# QQQ trend-following strategy
if self.qqq_short_ema.IsReady and self.qqq_long_ema.IsReady:
if self.qqq in data.Bars:
qqq_close = data.Bars[self.qqq].Close
short_ema = self.qqq_short_ema.Current.Value
long_ema = self.qqq_long_ema.Current.Value
if (short_ema > long_ema) and (qqq_close > short_ema) and (qqq_close > long_ema):
self.SetHoldings(self.qqq, 1/2)
elif (short_ema < long_ema) and (qqq_close < short_ema) and (qqq_close < long_ema):
self.SetHoldings(self.qqq, -1/2)
# SPY counter-trend strategy
if self.spy in data.Bars:
spy_low = data.Bars[self.spy].Low
self.spy_low_history.Add(spy_low)
if self.spy_low_history.IsReady:
history_low = min([x for x in self.spy_low_history]) # low of the 20 most recent days
if history_low == spy_low:
self.SetHoldings(self.spy, 1/2)
else:
self.SetHoldings(self.spy, -1/2)