Back To Top

August 14, 2025

Visualizing Reversal Probability Zones

Implement Statistical Reversal Bands By Detecting Regimes, Quantifying Swing Outliers, and Price Zones

Most traders only react after the move. They never see the true reversal zones until it’s too late.

The methodology discussed here aims at addressing this issue. We aim at seeing probability zones in advanced, mapped from price action.

Each reversal zone is an arc and grounded in statistics, which updates with every new swing and shift in volatility.

We combine pivot detection, volatility regime labeling, and probability bands to see reversal areas.

The complete Python notebook for the analysis is provided below.

Visualizing Reversal Probability Zones

1. How the Probability Zones Work

We use 3 core steps: (i) pivot detection, (ii) regime labeling, and (iii) probability mapping.

1.1 Pivot Detection

We define pivots as local highs and lows within a moving window.

For each price bar, check if it is the maximum or minimum in a window of size 2k+1:

Visualizing Reversal Probability Zones

where L is the low price, H is the high price, and k is the window size.

Pivots mark significant swing points. These are the anchors for reversal arcs.

1.2 Regime Labeling

Volatility regimes are labeled using a hidden Markov model. We use the log returns of closing prices:

Visualizing Reversal Probability Zones

The HMM fits these returns and assigns a regime label to each bar. Each regime reflects a different volatility state.

For example:

  • Regime 0: low volatility
  • Regime 1: medium volatility
  • Regime 2: high volatility

This step adapts the reversal zones to current market conditions.

1.3. Probability Mapping

For each swing (pivot-to-pivot move), calculate:

  • Amplitude: relative price change between pivots
Visualizing Reversal Probability Zones
  • Duration: number of bars between pivots
Visualizing Reversal Probability Zones

Collect amplitudes and durations for all swings in each regime.

Fit a kernel density estimate to the pairs (A,D). This gives a continuous probability surface for swing size and duration.

To find probability bands, sort the KDE values, integrate to get the cumulative probability, and find thresholds for desired bands (e.g. 50%, 75%).

These bands define the arcs.

1.4. Interpretation

Each reversal arc is a statistical envelope. If a move lands within the 50% arc, it is typical for this regime.

Moves outside the arc are outliers and signal either strong trends or market stress.

All calculations update with new price data. The zones shift as the market shifts.

2. Visualizing Reversal Zones in Python

2.1 Set Parameters

Set all model parameters up front.

These control how pivots sensitivity, how many volatility regimes, how wide the probability bands run, and how detailed the probability grid is.

Adjust these to match your timeframe, instrument, and noise tolerance.

				
					import numpy as np
import pandas as pd
import yfinance as yf
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
import matplotlib.patches as mpatches
from hmmlearn.hmm import GaussianHMM
from scipy.stats import gaussian_kde

# ── PARAMETERS
TICKER   = 'TSLA'
START    = '2022-01-01'
END      = '2024-12-31'
INTERVAL = '1d'

PIVOT_K  = 5    # Pivot window size: higher = stronger, fewer pivots; lower = more, noisier pivots.
N_STATE  = 3    # Number of HMM regimes: more states = finer volatility splits, but risk of overfitting.
BAND_P   = [0.50]  # Probability bands: lower = common moves, higher = rare outliers (add more for extra bands).
GRID_NX  = 60   # KDE X grid (amplitude): higher = smoother arcs, slower plot; lower = faster, rougher arcs.
GRID_NY  = 60   # KDE Y grid (duration): same as above, for swing duration.
				
			

2.2 Download and Prepare Price Data

Create a helper function to pull OHLCV data for the given symbol and timeframe. Clean the columns and drop rows with missing price values.

				
					def download_ohlc(ticker, start, end, interval):
    df = yf.download(ticker, start=start, end=end,
                     interval=interval, auto_adjust=True, progress=False)
    # flatten MultiIndex: keep level-0 (“Open,High,…”)
    if isinstance(df.columns, pd.MultiIndex):
        df.columns = df.columns.get_level_values(0)
    df.columns = df.columns.map(str.title)
    df.dropna(subset=['Open','High','Low','Close'], inplace=True)
    return df
				
			

3.3 Detect Fractal Pivots

Scan for local highs and lows using a rolling window. Each pivot marks a key swing point. This is where the arcs start.

Increasing the window size makes pivots rarer but more significant. Decreasing it catches more minor moves, but increases noise.

				
					def detect_pivots(df, k):
    pivots, last = [], 0
    H, L = df['High'].values, df['Low'].values
    for i in range(k, len(df)-k):
        win_h = H[i-k:i+k+1]
        win_l = L[i-k:i+k+1]
        if L[i]==win_l.min() and last!= -1:
            pivots.append((i, L[i], -1)); last=-1
        elif H[i]==win_h.max() and last!=  1:
            pivots.append((i, H[i],  1)); last=1
    return pivots
				
			

3.4. Label Market Regimes

Use the Hidden Markov Model to assign a volatility regime to each bar. The model runs on log returns cluster periods by volatility.

Each bar now has a regime label to adapts the probability arcs to current conditions.

Prev Post

Finding Big Money Options

Next Post

Detecting VIX Term Structure Regimes

post-bars
Mail Icon

Newsletter

Get Every Weekly Update & Insights

[mc4wp_form id=]

Leave a Comment