Adaptive Moving Averages used for Market Timing
Log in to collectAcademic paper
Technical Analysis with a Long Term Perspective: Trading Strategies and Market Timing Ability
Dušan Isakov; Didier Marti
- CHUniversity of Fribourg
- ?University of Fribourg (Switzerland) - Faculty of Management, Economics and Social Sciences
- ?University of Fribourg - Faculty of Economics and Social Science
Strategy in a nutshell
The strategy trades the S&P 500 using adaptive short- and long-term moving averages (1–100 days for SMA, 5–990 for LMA). An upward trend triggers long positions, a downward trend triggers exits or shorts. Optimal MAs are selected over a rolling 4-year window and updated daily for adaptive trend-following. Trades are executed via futures or ETFs.
Economic rationale
Trend-following exploits volatility clustering: avoiding risky assets in high-volatility, low-return periods and capturing gains in low-volatility, high-return periods. Adaptive MA selection enhances responsiveness to changing market conditions, improving risk management and return potential.
Backtest performance
Full Python code
import numpy as np
from AlgorithmImports import *
class AdaptiveMovingAveragesMarketTiming(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2010, 1, 1)
self.SetCash(100000)
data = self.AddEquity('SPY', Resolution.Daily)
data.SetLeverage(5)
self.symbol = data.Symbol
self.period = 4 * 12 * 21
self.SetWarmUp(self.period, Resolution.Daily)
self.data = RollingWindow[float](self.period)
self.ma = {}
self.ma_signal = {}
sma_periods = [x for x in range(1, 101, 4)]
lma_periods = [x for x in range(5, 991, 20)]
ma_combinations = [[i, j] for i in sma_periods for j in lma_periods if i<j]
for sma, lma in ma_combinations:
self.ma[sma,lma] = [self.SMA(self.symbol, sma, Resolution.Daily), self.SMA(self.symbol, lma, Resolution.Daily)]
self.ma_signal[sma,lma] = RollingWindow[float](self.period)
def OnData(self, data):
# Update SPY price every day.
symbol_obj = self.Symbol(self.symbol)
if symbol_obj in data and data[symbol_obj]:
self.data.Add(data[symbol_obj].Value)
# Store vector value for current day and optimize from previous values.
avg_return = {}
for sma_lma in self.ma:
sma = self.ma[sma_lma][0]
lma = self.ma[sma_lma][1]
if sma.IsReady and lma.IsReady:
sma_value = sma.Current.Value
lma_value = lma.Current.Value
# MA SIGNAL = if short term MA is OVER long term MA == 1 else -1
self.ma_signal[sma_lma].Add(1 if sma_value > lma_value else -1)
# 4 years of SPY data is ready.
if self.data.IsReady:
values = np.array([x for x in self.data])
daily_changes = values[:-1] / values[1:] - 1
# Find optimal sma_lma pair.
if self.ma_signal[sma_lma].IsReady:
# Multiply both vectors to get daily performance for sma_lma pair.
# Ignore last value from ma_signal since it's today's value. It will be used to trade decision for next day.
ma_signal_vector = [x for x in self.ma_signal[sma_lma]][1:]
return_vector = ma_signal_vector * daily_changes
# Store avg daily performance for sma_lma pair.
avg_return[sma_lma] = np.average([x for x in return_vector])
else:
# MA is not ready yet.
self.ma_signal[sma_lma].Add(0)
if self.IsWarmingUp: return
if len(avg_return) == 0: return
# Optimalization
optimal_sma_lma = max(avg_return, key=avg_return.get)
# Trading
last_signal = self.ma_signal[optimal_sma_lma][0]
if last_signal == 1:
self.SetHoldings(self.symbol, 1)
elif last_signal == -1:
self.SetHoldings(self.symbol, -1)