Trend Following in Python
A systematic strategy paradigm that identifies and rides sustained directional price movements across asset classes.
Definition
Trend Following is the oldest and most empirically validated paradigm in systematic trading, with documented live track records spanning decades from Commodity Trading Advisors (CTAs). It is predicated on the behavioral finance observation that markets exhibit serial autocorrelation in returns over medium-term horizons (weeks to months) — driven by investor herding, slow information diffusion, and feedback loops between price and investor sentiment. Unlike Mean Reversion, Trend Following has a low win rate (typically 35–45%) compensated by a large average win-to-loss ratio. It performs best during sustained macro dislocations and worst during choppy, range-bound regimes.
Quantitative Formula
Where and are exponentially weighted moving averages with fast and slow decay parameters, are weights across different lookback combinations, and determines the direction of the position. The magnitude of the difference between fast and slow EWMAs measures trend strength and can be used to scale position size proportionally to signal conviction.
Why It Matters in Backtesting
Trend following strategies have a naturally positive skew — many small losses and occasional large wins — which makes their equity curves psychologically brutal to live through during drawdown periods. A rigorous backtest must evaluate performance across multiple asset classes simultaneously (equities, commodities, FX, rates) because the strategy's true edge is diversification across uncorrelated trends. A trend following backtest on a single asset is meaningless; the strategy's documented Sharpe ratios of 0.5–0.8 emerge only from a properly diversified multi-asset implementation.
Python Implementation
import numpy as np
import pandas as pd
def trend_following_strategy(prices_df: pd.DataFrame,
fast_spans: list = [8, 16, 32],
slow_spans: list = [24, 48, 96],
vol_target: float = 0.15,
trading_days: int = 252) -> dict:
"""
Multi-asset, multi-speed trend following with volatility-targeted position sizing.
prices_df: DataFrame where each column is a different asset's price series.
"""
all_signals, all_returns = {}, {}
for asset in prices_df.columns:
prices = prices_df[asset].dropna()
daily_returns = prices.pct_change()
# Combine signals across multiple speed pairs
combined_signal = pd.Series(0.0, index=prices.index)
for fast, slow in zip(fast_spans, slow_spans):
ewma_fast = prices.ewm(span=fast).mean()
ewma_slow = prices.ewm(span=slow).mean()
raw_signal = ewma_fast - ewma_slow
# Normalize by rolling volatility of the signal itself
signal_vol = raw_signal.ewm(span=slow * 4).std()
combined_signal += raw_signal / (signal_vol + 1e-9)
combined_signal = np.tanh(combined_signal / len(fast_spans))
# Volatility-targeted position sizing
realized_vol = daily_returns.ewm(span=20).std() * np.sqrt(trading_days)
vol_scalar = (vol_target / len(prices_df.columns)) / (realized_vol + 1e-9)
position = combined_signal * vol_scalar
all_signals[asset] = position
all_returns[asset] = position.shift(1) * daily_returns
portfolio_returns = pd.DataFrame(all_returns).sum(axis=1)
return {
"portfolio_returns": portfolio_returns,
"asset_signals": pd.DataFrame(all_signals),
"sharpe": portfolio_returns.mean() / portfolio_returns.std() * np.sqrt(trading_days)
}Test this in a live environment
Stop running Jupyter notebooks locally. Paste this Trend Following code directly into Valetha's Strategy Lab and run a full historical backtest in seconds.
Open the Python Strategy Lab