Trend Following and Momentum Strategies › Time-Series Momentum and Trend Following
Quantitative FinanceAll chapters Trend Following and Momentum Strategies › Time-Series Momentum and Trend FollowingOne section · Sections in this chapter2 / 9 ↓
Reading tools Definitions & inline links
Choose your expertise level to adjust how many terms are explained. Beginners see more tooltips, experts see fewer to maintain reading flow. Hover over underlined terms for instant definitions.
Article links
Make inline references clickable
Time-series momentum, also called absolute momentum or trend following, generates signals based on an asset's own historical performance rather than comparing it to other assets. The core premise is straightforward: if an asset has been rising, go long; if it has been falling, go short or stay out. This approach has been implemented by trend-following hedge funds and CTAs for decades, with documented profitability extending back over a century.
The Mathematical Foundation
The foundation of time-series momentum rests on a deceptively simple observation: recent price direction tends to persist. Before we formalize this intuition, consider why such persistence might exist. When new information enters the market, perhaps an unexpected earnings surprise or a shift in monetary policy, prices do not instantly jump to their new equilibrium value. Instead, the adjustment unfolds gradually as different market participants process and trade on the news. This gradual adjustment creates a window during which prices exhibit directional persistence, and momentum strategies seek to profit from this window.
The simplest time-series momentum signal compares an asset's current price to its price periods ago. This comparison answers a fundamental question: has the asset been trending upward or downward over the recent past? If we denote the price at time as , the momentum signal is:
where:
- : price of the asset at time
- : price of the asset periods ago
- : cumulative return over the lookback period
- : function returning if positive, if negative, and if zero
The sign function turns the continuous return into a discrete trading signal. A positive signal indicates a long position, while a negative signal indicates a short position. This binary rule reflects the core philosophy of trend following: we care primarily about the direction of the trend rather than its magnitude. A stock that rose 5% over the past year receives the same long signal as one that rose 50%, because both have demonstrated positive momentum.

In practice, however, raw price comparisons can generate noisy signals that whipsaw between long and short positions during sideways markets. You often use moving averages to smooth noise and generate more stable signals. The smoothing process filters out short-term fluctuations while preserving the underlying trend direction. The dual moving average crossover represents one of the most popular implementations, comparing a fast moving average with a short lookback period to a slow moving average with a longer lookback period:
where:
- : moving average value with a short lookback period
- : moving average value with a long lookback period
- : function returning if positive, if negative, and otherwise
The intuition behind this signal is elegant: the fast moving average represents recent price momentum, while the slow moving average represents the longer-term price equilibrium. When the fast average crosses above the slow average, it suggests that short-term momentum has turned positive relative to the longer-term baseline. This crossover often signals a shift in market sentiment or a fundamental change in the asset's supply and demand dynamics. Conversely, when the fast average crosses below the slow average, it indicates deteriorating short-term momentum and suggests the trend may be reversing.
The simple moving average (SMA) calculates the arithmetic mean of prices over a lookback window , treating each observation within the window equally:
where:
- : simple moving average at time with lookback
- : asset price at lag
- : number of periods in the moving average window
By weighting all prices in the window equally with coefficient , this formula filters out high-frequency noise but introduces a deliberate lag. The average reacts more slowly to new price changes compared to the raw price series because each new observation only contributes of the total average value. This lag represents a tradeoff inherent in all smoothing techniques: greater noise reduction comes at the cost of slower signal response. For a 50-day moving average, today's price contributes only 2% of the average value, meaning the average responds gradually to even substantial price movements.
While simple moving averages provide effective smoothing, you might prefer alternatives that give more weight to recent observations. Exponential moving averages (EMAs) provide such an alternative, assigning exponentially decaying weights to historical prices:
where:
- : smoothing factor, typically calculated as
- : effective span or lookback period
- : current asset price
- : value of the exponential moving average in the previous period
This recursive formulation reveals the elegant structure of the EMA. The formula updates the average by combining two components: the new information representing today's price contribution, and the accumulated history carrying forward the weighted sum of all previous prices. The smoothing factor determines the relative importance of new versus old information. A larger value creates a more responsive average that tracks price changes closely, while a smaller produces a smoother average that emphasizes historical prices. The exponential weighting scheme assigns geometrically declining weights to older observations, so the average maintains a fading memory of the entire price series while remaining more responsive to recent trend changes than a simple moving average of equivalent span.

Implementing Moving Average Crossovers
Let's implement a dual moving average crossover strategy on equity index data. We'll use a 50-day fast moving average and a 200-day slow moving average, a popular combination known as the golden cross (when fast crosses above slow) and death cross (when fast crosses below slow).
# import yfinance as yf
from datetime import datetime
import numpy as np
import pandas as pd
# Download S&P 500 ETF data
# end_date = datetime.now()
# start_date = end_date - timedelta(days=365*10)
# spy = yf.download('SPY', start=start_date, end=end_date, progress=False)
# prices = spy['Close'].squeeze()
# Generate synthetic data for demonstration
np.random.seed(42)
dates = pd.date_range(end=datetime.now(), periods=252 * 10, freq="B")
returns = np.random.normal(0.0004, 0.012, len(dates)) # Drift + Noise
prices = pd.Series(100 * (1 + returns).cumprod(), index=dates)
# Calculate moving averages
ma_fast = prices.rolling(window=50).mean()
ma_slow = prices.rolling(window=200).mean()
# Generate signals: 1 for long, -1 for short/flat
signal = np.where(ma_fast > ma_slow, 1, -1)
signal = pd.Series(signal, index=prices.index)
The shaded regions reveal how the strategy positions itself during different market regimes. During sustained uptrends, the fast moving average stays above the slow, keeping the strategy long. During corrections, the crossover generates a short or flat signal.
Now let's calculate the strategy's returns and compare them to buy-and-hold:
# Calculate daily returns
daily_returns = prices.pct_change()
# Strategy returns: signal is lagged by 1 day to avoid look-ahead bias
strategy_returns = signal.shift(1) * daily_returns
# Calculate cumulative returns
cumulative_market = (1 + daily_returns).cumprod()
cumulative_strategy = (1 + strategy_returns).cumprod()
# Performance metrics
def calculate_metrics(returns, periods_per_year=252):
"""Calculate key performance metrics for a return series."""
returns = returns.dropna()
total_return = (1 + returns).prod() - 1
years = len(returns) / periods_per_year
ann_return = (1 + total_return) ** (1 / years) - 1
ann_vol = returns.std() * np.sqrt(periods_per_year)
sharpe = ann_return / ann_vol if ann_vol > 0 else 0
# Maximum drawdown
cumulative = (1 + returns).cumprod()
rolling_max = cumulative.expanding().max()
drawdowns = cumulative / rolling_max - 1
max_dd = drawdowns.min()
return {
"Total Return": total_return,
"Annualized Return": ann_return,
"Annualized Volatility": ann_vol,
"Sharpe Ratio": sharpe,
"Max Drawdown": max_dd,
}
market_metrics = calculate_metrics(daily_returns)
strategy_metrics = calculate_metrics(strategy_returns)Performance Comparison: Moving Average Crossover vs Buy-and-Hold ================================================================= Metric Buy-and-Hold MA Crossover ----------------------------------------------------------------- Total Return 523.7% 128.5% Annualized Return 20.1% 8.6% Annualized Volatility 0.19 0.19 Sharpe Ratio 1.07 0.46 Max Drawdown -24.6% -34.5%
The metrics comparison highlights the trade-offs of the strategy. While the Buy-and-Hold approach tracks the market's total return, the Moving Average Crossover strategy aims to improve the Sharpe Ratio by sidestepping major drawdowns, albeit often at the cost of lower total returns during strong bull markets.

Key Parameters
The key parameters for the Moving Average Crossover strategy are:
- Fast Window: The lookback period for the short-term moving average (e.g., 50 days). Shorter windows make the strategy more responsive but prone to false signals.
- Slow Window: The lookback period for the long-term baseline (e.g., 200 days). Longer windows filter more noise but increase lag.
- Regime: The market state (trending vs. mean-reverting) heavily influences performance. Crossovers perform best in sustained trends.
Breakout Strategies
Breakout strategies represent another powerful implementation of time-series momentum, grounded in a different but related intuition. Rather than using moving average crossovers, breakout systems enter positions when prices exceed recent highs or lows. The underlying premise is that price extremes carry informational content: when an asset reaches a new high, it signals that buyers have overwhelmed sellers at all previously established price levels, suggesting strong demand that may persist. Conversely, a new low indicates that selling pressure has broken through all previous support levels.
The classic Donchian channel strategy, popularized by the famous "Turtle Traders" in the 1980s, operationalizes this intuition by buying when price breaks above the highest high of the past days and selling when price breaks below the lowest low. The Turtle Traders demonstrated that this systematic approach, when combined with proper position sizing and risk management, could generate substantial profits across diverse futures markets.
The Donchian channel defines dynamic support and resistance levels based on recent price extremes:
where:
- : highest price over the past periods
- : lowest price over the past periods
- : asset price periods ago
- : lookback window size
These formulas create a price channel that contains all price action over the lookback window. The upper band represents the resistance level that buyers have been unable to breach, while the lower band represents the support level that sellers have been unable to break. When price finally moves outside this channel, it suggests a potential shift in the market's supply and demand equilibrium.
A long entry occurs when , indicating that today's price has exceeded all prices in the lookback window. This breakout above resistance suggests that new buyers have entered the market or that existing buyers have become more aggressive, potentially initiating an upward trend. A short entry occurs when , indicating that today's price has fallen below all recent support levels. This breakout logic assumes that prices reaching new extremes signal a fundamental shift in market dynamics that is likely to persist as other market participants recognize and react to the new price regime.
# Donchian Channel breakout strategy
lookback = 20 # 20-day breakout
upper_band = prices.rolling(window=lookback).max().shift(1)
lower_band = prices.rolling(window=lookback).min().shift(1)
# Generate signals
breakout_signal = pd.Series(0.0, index=prices.index)
position = 0
for i in range(lookback, len(prices)):
if prices.iloc[i] > upper_band.iloc[i]:
position = 1 # Long breakout
elif prices.iloc[i] < lower_band.iloc[i]:
position = -1 # Short breakout
breakout_signal.iloc[i] = position
# Calculate returns
breakout_returns = breakout_signal.shift(1) * daily_returns
cumulative_breakout = (1 + breakout_returns).cumprod()
Key Parameters
The key parameters for Breakout strategies are:
- Lookback Period (): The window for determining the breakout levels (e.g., 20 days).
- Upper/Lower Bands: The highest high and lowest low over the lookback period, which serve as dynamic support and resistance levels.
CTA Strategies and Multi-Asset Trend Following
Commodity Trading Advisors (CTAs) are managed futures funds that primarily employ trend-following strategies across a diversified portfolio of futures contracts. The power of CTA strategies comes from combining trend signals across multiple uncorrelated asset classes: equity indices, government bonds, currencies, commodities, and interest rates.
The main point is that while any single trend signal may fail, combining signals across 50 to 100 futures markets with low correlation to each other produces a more stable return stream. The strategy also benefits from the ability to profit in both rising and falling markets, and gives useful diversification during equity bear markets.
A typical CTA strategy involves several components:
- Signal generation: Each market receives a trend signal (e.g., moving average crossover, momentum score, or breakout)
- Volatility scaling: Position sizes are inversely proportional to recent volatility, so more volatile markets receive smaller positions
- Risk allocation: Capital is allocated across asset classes and individual markets based on target risk contribution
- Portfolio construction: Final positions combine signals, volatility scaling, and risk budgets
Let's implement a simplified multi-asset trend following system:
from datetime import datetime, timedelta
# Download futures proxy data for multiple asset classes
tickers = {
"SPY": "Equities", # S&P 500
"TLT": "Bonds", # Long-term Treasuries
"GLD": "Commodities", # Gold
"UUP": "Currencies", # US Dollar Index
}
end_date = datetime.now()
start_date = end_date - timedelta(days=365 * 8)
# data = {}
# for ticker, asset_class in tickers.items():
# df = yf.download(ticker, start=start_date, end=end_date, progress=False)
# data[ticker] = df['Close'].squeeze()
# prices_df = pd.DataFrame(data)
# Generate synthetic data for demonstration
np.random.seed(42)
dates = pd.date_range(end=datetime.now(), periods=252 * 8, freq="B")
data = {
ticker: 100 * (1 + np.random.normal(0.0002, 0.01, len(dates))).cumprod()
for ticker in tickers
}
prices_df = pd.DataFrame(data, index=dates)
returns_df = prices_df.pct_change()
# Calculate momentum signals for each asset
lookback_signal = 252 # 12-month momentum
lookback_vol = 63 # 3-month volatility for scaling
signals = pd.DataFrame(index=prices_df.index, columns=prices_df.columns)
vol_scaled_signals = pd.DataFrame(
index=prices_df.index, columns=prices_df.columns
)
for ticker in tickers.keys():
# 12-month momentum signal
momentum = prices_df[ticker] / prices_df[ticker].shift(lookback_signal) - 1
signals[ticker] = np.sign(momentum)
# Volatility scaling: target 10% annual vol per position
realized_vol = returns_df[ticker].rolling(lookback_vol).std() * np.sqrt(252)
target_vol = 0.10
vol_scalar = target_vol / realized_vol
vol_scalar = vol_scalar.clip(upper=2.0) # Cap leverage at 2x
vol_scaled_signals[ticker] = signals[ticker] * vol_scalar
# Equal-weight portfolio of vol-scaled signals
n_assets = len(tickers)
portfolio_returns = (vol_scaled_signals.shift(1) * returns_df).sum(
axis=1
) / n_assets
cumulative_cta = (1 + portfolio_returns).cumprod()

The rolling correlation chart illustrates why institutional investors value trend-following strategies, but it also shows that diversification is not constant. Correlation is positive through most of this synthetic sample and negative only for a limited interval. In real markets, a trend strategy's ability to go short during sustained declines can produce the "crisis alpha" investors seek, but the outcome depends on the signals, markets, and speed of the reversal.
Key Parameters
The key parameters for the Multi-Asset CTA strategy are:
- Signal Lookback: The period for measuring momentum (e.g., 12 months).
- Volatility Lookback: The window for estimating asset volatility to scale positions (e.g., 3 months).
- Target Volatility: The desired annualized volatility level for each position or the portfolio.
- Leverage Cap: Maximum leverage allowed per position to prevent excessive risk.