Regime Mean Reversion Strategy

The idea in one sentence

Only buy dips (or sell rips) when the market is actually trending and the historical odds of a reversal are in your favour.

Financial background

Plain mean reversion has a well-known weakness: it buys falling knives in bear markets and sells into runaway rallies. The reason is that it treats every market day the same — it does not ask “what kind of market are we in?” before placing the trade.

This strategy solves that problem by adding two layers of intelligence on top of basic mean reversion:

  1. Regime classification — it uses three technical indicators (ADX, Bollinger Band Width, Volume) to determine whether the market is trending, transitioning, ranging, or compressed. Signals are only generated in trending regimes, where mean-reversion dips and pops are most likely to be temporary.

  2. Probabilistic confirmation — before acting on a dip or pop, the strategy asks: “Historically, when this stock dipped by this much in this specific regime, how often did it bounce the next day?” A signal is only generated if the historical probability and win/loss ratio both exceed configurable thresholds.

The strategy is ported from the QIMPOD5 research framework, where it was originally developed with an additional VIX momentum filter. VIX filtering will be added in Phase 2 when a VIX data feed is available.


How it works — overview

Price data (OHLCV)
    │
    ├─ Compute indicators (ADX, Bollinger Bands, Volume, SMAs, Returns)
    │
    ├─ Classify today's market regime (one of 10 categories)
    │
    ├─ Is the regime a trending one (UP or DOWN)?
    │   └─ No → no signal
    │
    ├─ Did today have a significant dip (for BUY) or pop (for SELL)?
    │   └─ No → no signal
    │
    ├─ Compute expanding-window probabilities:
    │   "In the entire history, when a similar dip happened in this same regime,
    │    how often did the stock recover the next day?"
    │
    ├─ Do the probabilities and win/loss ratio exceed thresholds?
    │   └─ No → no signal
    │
    └─ Emit BUY or SELL with confidence = regime-specific probability

Step 1 — Technical indicators

The strategy computes several indicators from daily OHLCV bars. Each indicator captures a different dimension of market behaviour.

Average Directional Index (ADX)

What it measures: the strength of a trend, regardless of direction.

ADX was developed by J. Welles Wilder in the 1970s and is one of the most widely used trend-strength indicators. It is derived from the Directional Movement system:

  1. +DM (Plus Directional Movement) — how much today’s high exceeds yesterday’s high (upward pressure).

  2. -DM (Minus Directional Movement) — how much today’s low is below yesterday’s low (downward pressure).

  3. These are smoothed using Wilder’s smoothing (a form of exponential moving average with factor 1/N) over 14 periods and normalized by the Average True Range (ATR) to produce +DI and -DI (directional indicators, 0–100 each).

  4. The DX (Directional Index) measures the spread between +DI and -DI: DX = |+DI - -DI| / (+DI + -DI) * 100.

  5. ADX is the smoothed average of DX over another 14 periods.

Interpretation:

ADX value

Market state

< 20

Weak or no trend — range-bound, choppy

20–25

Borderline — trend may be emerging or fading

25–50

Strong trend

50–75

Very strong trend

> 75

Extreme trend (rare, often near exhaustion)

ADX alone does not tell you the direction — only the strength. Direction is determined separately by comparing the close price to the fast and slow SMAs.

Wilder’s Smoothing

A specific exponential smoothing method where the decay factor is (N-1)/N instead of the usual 2/(N+1). This makes it respond more slowly than a standard EMA of the same period, which is appropriate for trend-strength indicators because it filters out short-term noise.

wilder_smooth[0..N-1] = sum of first N values
wilder_smooth[t] = wilder_smooth[t-1] * (N-1)/N + value[t]

True Range (TR)

The True Range accounts for overnight gaps, unlike a simple high-minus-low calculation:

TR = max(High - Low, |High - Previous Close|, |Low - Previous Close|)

For example, if a stock closes at 100, gaps up to open at 105, and trades between 104 and 107, the simple range is 3 (107-104) but the True Range is 7 (107-100), which better reflects the actual volatility experienced by a trader holding overnight.

Bollinger Bands and Bollinger Band Width (BBW)

Bollinger Bands are volatility envelopes placed 2 standard deviations above and below a 20-period SMA:

Middle band = SMA(20)
Upper band  = SMA(20) + 2 * StdDev(20)
Lower band  = SMA(20) - 2 * StdDev(20)

Bollinger Band Width normalises the distance between the bands:

BBW = (Upper - Lower) / Middle

BBW is a pure volatility measure. When volatility expands, the bands widen and BBW increases. When volatility contracts, the bands squeeze and BBW decreases.

Why BBW matters for regime detection:

  • Expanding BBW (BBW > its 5-period SMA and BBW > BBW from 3 bars ago) signals that volatility is actively increasing — the market is “breaking out” of a range and a trend may be strengthening.

  • Low BBW (BBW in the bottom 20th percentile of its 250-day history) signals a volatility squeeze — the market is compressed and may be building energy for a move, but is not trending yet.

Relative Volume

Relative Volume = Today's Volume / SMA(Volume, 20)

A value above 1.0 means today is busier than average. Volume confirms conviction — a breakout on heavy volume is more meaningful than one on thin volume.

Relative volume

Interpretation

< 0.8

Unusually quiet — market is disengaged

0.8 – 1.2

Normal activity

1.2 – 2.0

Above-average interest — confirms moves

> 2.0

Very high activity — earnings, news, institutional activity

Simple Moving Averages (SMA 20 / SMA 50)

Two standard SMAs determine the direction of a trend:

  • Uptrend: Close > SMA(20) > SMA(50) — the price is above both averages, and the short-term average is above the long-term average. All three are “stacked” upward.

  • Downtrend: Close < SMA(20) < SMA(50) — the mirror image. All three are stacked downward.

  • Undefined: any other arrangement — the trend direction is ambiguous.

Daily Returns

return[t] = (close[t] - close[t-1]) / close[t-1]

The daily percentage change. A return of -0.03 means the stock dropped 3% in one day. Returns are used both to detect dip/pop events and to compute historical probabilities.


Step 2 — Regime classification

The strategy combines ADX, BBW, and Relative Volume to classify every bar into one of 10 market regimes. Think of regimes as the “weather” of the market — you do not want to apply the same trading logic in a hurricane as on a calm day.

The 10 regimes

Transition regimes (ADX between 20 and 21)

The market is on the boundary between trending and ranging. The strategy does not generate signals here — the direction is too uncertain.

Regime

Conditions

Meaning

TRANSITION_CONFIRMED

BBW expanding, vol >= 1.0x

A trend may be starting. Watch, but do not act yet.

TRANSITION_WEAK

BBW not expanding or vol < 1.0x

Ambiguous. Could go either way.

Ranging regimes (ADX < 20)

The market is not trending. Mean reversion dips could become deeper drawdowns with no clear trend to anchor a recovery.

Regime

Conditions

Meaning

COMPRESSION

BBW in bottom 20th percentile, vol >= 1.0x

A “Bollinger Squeeze” — volatility is extremely low and may precede a sharp move. No signal.

RANGE

Otherwise

Choppy, directionless market. No signal.

Example: reading a regime

Date:       2025-03-15
Close:      $185.40
SMA(20):    $182.10
SMA(50):    $178.50
ADX:        28.5
BBW:        0.065 (expanding — above its 5-day SMA and above 3-bar-ago level)
Rel Volume: 1.35

→ ADX 28.5 >= 21    → trending
→ BBW expanding + vol 1.35 >= 1.2  → STRONG
→ Close $185.40 > SMA20 $182.10 > SMA50 $178.50  → UP

Regime: TREND_STRONG_UP

This stock is in a strong uptrend with expanding volatility and above-average volume. If it dips 2%+ tomorrow, the strategy will evaluate whether to buy.


Step 3 — Expanding-window probability

Before generating a signal, the strategy checks if history supports the trade. It does this by scanning every bar in the available history (typically ~500 days) and computing two conditional probabilities:

Base probability (all regimes)

“Across all historical days, when this stock dropped >= 2% in a single day, what fraction of the time did it bounce at least 0.5% the next day?”

This gives a baseline. If the stock almost never bounces after dips (e.g. it is a chronic decliner), then even a favourable regime is not enough.

Regime-specific probability

“Restrict to only those days when the regime was TREND_STRONG_UP. Same question: after a >= 2% dip, how often did it bounce >= 0.5% the next day?”

This is the refined probability. If dips in this specific regime recover more reliably than dips in general, we have a regime edge.

Win/loss ratio

In addition to the probability, the strategy computes:

Win/Loss Ratio = Average winning return / Average losing return

A probability of 60% with a 1.5 win/loss ratio means: 6 out of 10 times you win, and when you win your average gain is 50% larger than your average loss. This is a positive expected value bet.

Expanding window

The computation uses an expanding window — it uses all available history up to (but not including) the current bar. This avoids lookahead bias (the model does not peek at future data), while using as much history as possible.

The outcome for each historical event is the return on the next day (t+1), not the same day. This represents the realistic scenario: you observe a dip today, you buy at the close, and you evaluate the result tomorrow.

Minimum events

The strategy requires at least 12 qualifying events (by default) in both the base and regime-specific histories before it trusts the probabilities. With fewer events, the estimated probability is too noisy to be useful.

Example

Historical scan of 480 days for AAPL in TREND_STRONG_UP:

Base (all regimes):
  Days with return <= -2%:  45 events
  Next-day recovery >= 0.5%: 29 times
  → Base probability: 29/45 = 64.4%
  → Base W/L ratio: 1.42

Regime-specific (TREND_STRONG_UP only):
  Days with return <= -2%:  18 events
  Next-day recovery >= 0.5%: 14 times
  → Regime probability: 14/18 = 77.8%
  → Regime W/L ratio: 1.65

Today: AAPL is in TREND_STRONG_UP and just dropped -2.3%.
All probability thresholds pass.
→ Signal: BUY, confidence = 0.778

Step 4 — Signal generation

All of the following conditions must pass simultaneously for a signal to be emitted:

BUY (uptrend regimes: TREND_WEAK_UP or TREND_STRONG_UP)

  1. Regime is an uptrend. Dips in uptrends are pullbacks, not collapses.

  2. Today’s return is a dip. return <= -return_threshold * (1 + safemargin) (default: -2%).

  3. Base probability exceeds threshold. The stock has a general tendency to bounce after dips.

  4. Regime probability exceeds threshold. The bounce tendency is even stronger in the current regime.

  5. Win/loss ratio exceeds threshold. The average win is sufficiently larger than the average loss.

SELL (downtrend regimes: TREND_WEAK_DOWN or TREND_STRONG_DOWN)

Mirror of BUY: a pop (positive return) in a downtrend is interpreted as a temporary rally that will reverse. All the same probability checks apply in the opposite direction.

No signal cases

  • Non-trending regimes (RANGE, COMPRESSION, TRANSITION) — the strategy abstains.

  • Trending but undefined direction — the strategy abstains.

  • Dip/pop not large enough — below the return threshold.

  • Insufficient historical events — not enough data to trust the probabilities.

  • Probabilities or W/L ratio below thresholds — the historical edge is not strong enough.

Confidence

The signal’s confidence value is the regime-specific probability (0.0 to 1.0). A confidence of 0.78 means “in this regime, 78% of similar dips were followed by a bounce.”


Worked example — full pipeline

Stock: MSFT, daily bars, 500-day history.

Today’s data:

Close:       $415.20 (yesterday: $425.80)
High:        $420.50
Low:         $413.90
Volume:      45,200,000 (20-day avg: 33,000,000)

Step 1 — Indicators:

Daily return:    (415.20 - 425.80) / 425.80 = -2.49%
ADX(14):         31.2
SMA(20):         $420.50
SMA(50):         $408.30
Bollinger BBW:   0.072 (5-day SMA of BBW: 0.058, BBW 3 bars ago: 0.055)
Relative volume: 45.2M / 33M = 1.37

Step 2 — Regime classification:

ADX 31.2 >= 21                        → trending
BBW 0.072 > SMA 0.058 and > 0.055    → BBW expanding
Rel volume 1.37 >= 1.2               → volume confirmed
→ TREND_STRONG

Close $415.20 < SMA20 $420.50        → wait, close is BELOW SMA20...
                                        but SMA20 $420.50 > SMA50 $408.30
→ Close < SMA20 but SMA20 > SMA50    → direction = UNDEFINED

Regime: TREND_STRONG_UNDEFINED → no signal generated

The strategy does not buy here because even though there is a dip, the trend direction is ambiguous. The price just crossed below the 20-day SMA, which could signal the start of a deeper correction rather than a buyable pullback.

Alternative scenario — if close were $421.50 (return -1.01%):

Close $421.50 > SMA20 $420.50 > SMA50 $408.30 → TREND_STRONG_UP
But return -1.01% > -2% threshold → dip not large enough → no signal

Alternative scenario — if close were $416.50 with SMA20 at $415.00:

Close $416.50 > SMA20 $415.00 > SMA50 $408.30 → TREND_STRONG_UP
Return -2.18% <= -2% threshold → dip qualifies

Historical scan: base prob 61%, regime prob 73%, regime W/L 1.52
All thresholds pass.

→ BUY, confidence = 0.73

The safety margin parameter

The safemargin parameter (default 0.0) adds a buffer to all thresholds. With safemargin = 0.10 (10%):

  • Return threshold becomes -2% * 1.10 = -2.2% — requires a deeper dip.

  • Probability threshold becomes 50% * 1.10 = 55% — requires higher conviction.

  • W/L threshold becomes 1.3 * 1.10 = 1.43 — requires a better risk/reward profile.

Use this to make the strategy more conservative without changing each threshold individually.


VIX filtering (Phase 2 — not yet implemented)

In the original QIMPOD5 research, the strategy included a VIX momentum filter as an additional gate:

What is the VIX?

The CBOE Volatility Index (VIX) measures the market’s expectation of 30-day volatility, derived from S&P 500 option prices. It is often called the “fear gauge”:

VIX level

Market mood

< 15

Complacent — low expected volatility

15–20

Normal

20–30

Elevated fear — uncertainty rising

30–40

High fear — significant market stress

> 40

Panic (e.g. COVID crash, 2008 crisis)

How VIX filtering works

The idea is to avoid buying dips during periods of rising systemic fear. Even if a stock is in a strong uptrend with high recovery probability, a spike in VIX suggests the broader market environment is deteriorating and individual stock pullbacks may deepen.

The filter checks:

  1. VIX level — is VIX below a maximum threshold (e.g. 30)?

  2. VIX momentum — is VIX declining or stable (not spiking)?

If VIX is spiking above the threshold, all BUY signals are suppressed regardless of individual stock conditions. SELL signals in downtrends may be enhanced (falling markets + rising VIX = strong sell).

This filter will be added in Phase 2 once a VIX data feed is integrated into the platform.


Parameters

Indicator parameters

Parameter

Default

What it controls

adx_period

14

Lookback for ADX calculation. Standard is 14. Shorter = more responsive, noisier.

bb_period

20

Bollinger Bands SMA period. Standard is 20.

bb_std

2.0

Standard deviations for the bands. 2.0 captures ~95% of price action.

bbw_percentile_window

250

Rolling window (~1 year) for determining “low” BBW via 20th percentile.

bbw_sma_period

5

SMA period for detecting BBW expansion (is BBW rising short-term?).

bbw_lookback

3

Bars to look back for BBW expansion confirmation.

sma_fast

20

Fast SMA for trend direction (~1 month).

sma_slow

50

Slow SMA for trend direction (~2.5 months).

volume_sma_period

20

Lookback for average volume (relative volume denominator).

Regime thresholds

Parameter

Default

What it controls

adx_trend_threshold

21.0

ADX above this = market is trending.

adx_transition_low

20.0

ADX in the [20, 21) zone = transition.

volume_strong_threshold

1.2

Relative volume for TREND_STRONG classification.

volume_normal_threshold

1.0

Relative volume for TRANSITION/COMPRESSION classification.

Signal thresholds

Parameter

Default

What it controls

return_threshold

0.02

Minimum daily drop (2%) to qualify as a “dip” for BUY, or daily rise for SELL.

success_threshold

0.005

Minimum next-day recovery (0.5%) to count as a “success” in probability computation.

prob_threshold_base

0.50

Minimum base probability (across all regimes) to proceed.

prob_threshold_weak

0.50

Minimum regime probability in TREND_WEAK regimes.

prob_threshold_strong

0.50

Minimum regime probability in TREND_STRONG regimes.

winloss_threshold_weak

1.3

Minimum win/loss ratio in TREND_WEAK regimes.

winloss_threshold_strong

1.3

Minimum win/loss ratio in TREND_STRONG regimes.

safemargin

0.0

Safety buffer applied multiplicatively to all thresholds.

min_events

12

Minimum historical qualifying events to trust the probability estimate.

Data parameters

Parameter

Default

What it controls

candle_count

500

Number of daily bars to request (~2 years). More data = more events for probability, but older data may be less relevant.

Tuning guidelines

  • More aggressive (more signals): lower return_threshold to 0.015, lower probability thresholds to 0.45, lower min_events to 8.

  • More conservative (fewer, higher-quality signals): raise return_threshold to 0.03, raise probability thresholds to 0.60, set safemargin to 0.10.

  • For volatile stocks: increase return_threshold (a 2% dip in a volatile stock is routine, not a signal-worthy event).

  • For stable blue-chips: decrease return_threshold (a 1.5% dip in a stable stock may already be significant).