← Back to Algorithmic Glossary

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

Signalt=sign(k=1Kwk(EWMAfast,k(t)EWMAslow,k(t)))Signal_t = \text{sign}\left(\sum_{k=1}^{K} w_k \cdot (EWMA_{fast,k}(t) - EWMA_{slow,k}(t))\right)

Where EWMAfastEWMA_{fast} and EWMAslowEWMA_{slow} are exponentially weighted moving averages with fast and slow decay parameters, wkw_k are weights across KK different lookback combinations, and sign()\text{sign}(\cdot) 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

Ready to find your edge ?

Start for Free