The strategy allocates 50% to a trend-following strategy based on NASDAQ EMA and 50% to a counter-trend strategy using the S&P 500 ETF, with specific buy/sell conditions for each.

I. 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.

II. 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

III. SOURCE PAPER

Opposites Attract: Improvements to Trend Following for Absolute Returns [Click to Open PDF]

Leake, Anchor Capital Management Group, Inc.

<Abstract>

Recent market events have reminded market participants of the long-term profitability of long/short trend following strategies. While trend following can be profitable over the long term, choppy or trendless markets can make trend following challenging. Large short-term, countertrend moves are typical during strongly trending markets, and when unaccounted for can often produce a large drawdown in an otherwise successful trend following system. The purpose of this paper is to demonstrate a simple quantitative blend of Momentum investing and Counter Trend methodology that offers the benefits of long/short trend following strategies with reduced drawdown. The result is a simple-to-apply investment method that has delivered a significant increase in annual returns and reduced risk over the benchmark index over a 35-year period.

IV. BACKTEST PERFORMANCE

Annualised Return12.2%
VolatilityN/A
Beta-0.313
Sharpe RatioN/A
Sortino Ratio0.004
Maximum Drawdown-11.5%
Win Rate47%

V. 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)

VI. Backtest Performance

Leave a Reply

Discover more from Quant Buffet

Subscribe now to keep reading and get access to the full archive.

Continue reading