Analysis 11 min read

Mean Reversion vs Trend Following: Which Strategy to Code

A data-driven comparison of mean reversion and trend following strategies. Learn when each approach works, how to detect the current regime, and which to implement first.

7Sections
4Code samples
11Min read
AnalysisCategory

Two Philosophies of Price

Every trading strategy is fundamentally based on one of two beliefs about price behavior: either prices tend to continue in the current direction (trend following), or prices tend to return to a central value (mean reversion). The market alternates between regimes where each approach works.

Trend Following

Buy when price is going up, sell when it is going down. Ride the trend until it reverses.

Indicators: MACD, EMA crossovers, ADX, Parabolic SAR

Win rate: 30-40% with large winners

Mean Reversion

Buy when price is "too low," sell when it is "too high." Bet on return to average.

Indicators: RSI, CCI, Bollinger Bands, Stochastic

Win rate: 60-70% with smaller winners

Detecting the Current Regime

ADX is the most reliable regime detector. High ADX (above 25) indicates a trending market; low ADX (below 20) indicates a ranging market.

Python Detecting the Current Regime
import requests

API_KEY = "YOUR_API_KEY"
BASE_URL = "https://tickatlas.com/v1"

def detect_regime(symbol: str, timeframe: str = "D1") -> dict:
    """Detect whether market is trending or ranging."""
    # Fetch ADX
    adx_resp = requests.get(
        f"{BASE_URL}/indicator",
        headers={"X-API-Key": API_KEY},
        params={"symbol": symbol, "indicator": "ADX", "timeframe": timeframe},
    )
    adx = adx_resp.json()["data"]["value"]

    # Fetch the Bollinger Bands for confirmation (one call, three keys)
    bb_resp = requests.get(
        f"{BASE_URL}/indicators",
        headers={"X-API-Key": API_KEY},
        params={"symbol": symbol, "timeframe": timeframe},
    )
    bb = bb_resp.json()["data"]["indicators"]
    bb_width = (bb["BB_upper"] - bb["BB_lower"]) / bb["BB_middle"] * 100

    if adx > 30:
        regime = "STRONG_TREND"
        strategy = "trend_following"
    elif adx > 25:
        regime = "TRENDING"
        strategy = "trend_following"
    elif adx < 15:
        regime = "RANGE_BOUND"
        strategy = "mean_reversion"
    else:
        regime = "TRANSITIONING"
        strategy = "wait"

    return {
        "symbol": symbol,
        "adx": round(adx, 1),
        "bb_width_pct": round(bb_width, 2),
        "regime": regime,
        "recommended_strategy": strategy,
    }

# Check multiple symbols
for sym in ["EURUSD", "GBPUSD", "XAUUSD", "BTCUSD"]:
    result = detect_regime(sym)
    print(f"{sym}: {result['regime']} (ADX={result['adx']}) "
          f"-> {result['recommended_strategy']}")

Trend Following Implementation

Python Trend Following Implementation
def trend_following_signal(symbol: str, timeframe: str) -> dict:
    """EMA crossover trend following strategy."""
    ema_fast = requests.get(
        f"{BASE_URL}/indicator", headers={"X-API-Key": API_KEY},
        params={"symbol": symbol, "indicator": "EMA_20", "timeframe": timeframe},
    ).json()["data"]["value"]

    ema_slow = requests.get(
        f"{BASE_URL}/indicator", headers={"X-API-Key": API_KEY},
        params={"symbol": symbol, "indicator": "EMA_50", "timeframe": timeframe},
    ).json()["data"]["value"]

    macd_hist = requests.get(
        f"{BASE_URL}/indicator", headers={"X-API-Key": API_KEY},
        params={"symbol": symbol, "indicator": "MACD_hist", "timeframe": timeframe},
    ).json()["data"]["value"]

    signal = None
    if ema_fast > ema_slow and macd_hist > 0:
        signal = "LONG"
    elif ema_fast < ema_slow and macd_hist < 0:
        signal = "SHORT"

    return {"symbol": symbol, "signal": signal, "type": "trend_following"}

Mean Reversion Implementation

Python Mean Reversion Implementation
def mean_reversion_signal(symbol: str, timeframe: str) -> dict:
    """RSI + Bollinger Band mean reversion strategy."""
    rsi = requests.get(
        f"{BASE_URL}/indicator", headers={"X-API-Key": API_KEY},
        params={"symbol": symbol, "indicator": "RSI_14", "timeframe": timeframe},
    ).json()["data"]["value"]

    bb_resp = requests.get(
        f"{BASE_URL}/indicators", headers={"X-API-Key": API_KEY},
        params={"symbol": symbol, "timeframe": timeframe},
    ).json()["data"]

    close = bb_resp["ohlcv"]["close"]
    lower = bb_resp["indicators"]["BB_lower"]
    upper = bb_resp["indicators"]["BB_upper"]

    signal = None
    if rsi < 30 and close <= lower:
        signal = "LONG"  # Oversold + at lower band
    elif rsi > 70 and close >= upper:
        signal = "SHORT"  # Overbought + at upper band

    return {"symbol": symbol, "signal": signal, "type": "mean_reversion"}

The Adaptive Approach

Python The Adaptive Approach
def adaptive_strategy(symbol: str, timeframe: str) -> dict:
    """Switch strategy based on current market regime."""
    regime = detect_regime(symbol)

    if regime["recommended_strategy"] == "trend_following":
        return trend_following_signal(symbol, timeframe)
    elif regime["recommended_strategy"] == "mean_reversion":
        return mean_reversion_signal(symbol, timeframe)
    else:
        return {"symbol": symbol, "signal": None, "type": "waiting"}

# Scan and adapt
for sym in ["EURUSD", "GBPUSD", "XAUUSD"]:
    result = adaptive_strategy(sym, "H4")
    if result["signal"]:
        print(f"{sym}: {result['signal']} ({result['type']})")
    else:
        print(f"{sym}: No signal")

Which to Code First?

Start with mean reversion if you prefer higher win rates

Mean reversion strategies win more often (60-70%) but each win is smaller. This is psychologically easier for most traders and simpler to code.

Start with trend following if you can tolerate frequent losses

Trend following wins less often (30-40%) but the winners are large enough to compensate. You need the discipline to take many small losses before the big win.

Build both and let the regime decide

The best approach is to implement both and use ADX or Bollinger Band width to automatically switch between them. This is more complex but far more robust.

Related Reading

Run this against live data.

Every account starts pay-as-you-go with $2.50 of credit and no card. Paste the key into the samples above and the requests work unchanged.

Prefer the API to the article

Build it instead of reading about it.

Create a key, run the first request, then extend one layer at a time. Every account starts pay-as-you-go with $2.50 of credit — no card required.