Integrate rolling ATR deviation bands, short-term trend scoring, and contextual price histograms to reveal trend strength
Most indicators track direction or volatility, but not both. Certainly, they almost never show you where price is clustering inside swings.
The methodology presented here fills that gap and aim to visualize trend direction in real-time.
We visualize adaptive ATR bands, real-time momentum shifts, and live price clustering.
The aim is to spot transitions as they form, and understand where price truly spends its time.
The approach combines multi-band ATR envelopes, percentile-based momentum scoring, and contextual price histograms.
The complete Python notebook for the analysis is provided below.

1. Layering Trend, Volatility, and Momentum
The visualization is built from three layers: trend, volatility, and momentum. Each show a different structural property of the market.
The sequence of the process: (i) extracting market structure by combining rolling averages, (ii) ATR-based volatility bands, (iii) percentile-normalized momentum scoring, (iv) regime segmentation, and (v) price clustering.
Step 1: Compute the Rolling Mean (Trend Baseline)
The rolling mean provides the directional reference.
N is the lookback window (e.g. 50 bars).
The rolling mean defines the baseline trend and achor all subsequent calculations to the prevailing market direction.
Step 2: Calculate Rolling ATR and Multi-Band Deviation Channels
Volatility is quantified by the rolling Average True Range:
- True Range at time t:
- ATR over window M:
- Multi-band channels:
For each multiplier k (e.g. k=1,2,3):
These bands define the volatility context for each price level at every time step.
Step 3: Calculate Momentum (Directional Change in Trend)
Momentum is measured as the change in the rolling mean over a short interval:
where nnn is a small lookback (e.g., 5 bars).
This difference captures the speed and direction of the trend baseline.
Step 4: Normalize Momentum Using Rolling Percentile
To standardize momentum, divide by the rolling 100th percentile (max value) of Δavg over a window W:

Clip Scoret to [−1,1] for stability.
This produces a momentum oscillator sensitive to recent history, robust to changes in volatility.
Step 5: Trend Regime Determination (Trend State Flagging)
A trend regime is identified using percentile-based thresholds:
- Uptrend: If Scoret>UP_THRESHOLD and trend flag is off, the regime flips to “on” (uptrend).
- Down/Exit: If Scoret<DOWN_THRESHOLD and trend flag is on, the regime flips to “off” (trend exit).
- Trend state: At each time step, track the binary regime state (on/off) and its current start index.
This structure segments the price series into adaptive trend regimes based on recent normalized momentum.
Step 6: Price Clustering (Histogram Within Regime)
For the current regime window (from regime start index to present), bin all closing prices:
- Split the full price range in the regime into B bins.
- Count the frequency of closes in each bin:
where 1binj is the indicator function for bin j.
This histogram highlights zones of high price concentration (support, resistance) within each detected trend.
Step 7: Visual Assembly
- Upper Panel: Price with rolling mean and multi-band ATR envelopes; trend state marked by color changes and regime-flip markers.
- Middle Panel: Percentile-normalized momentum oscillator, with regime thresholds.
- Lower Panel: Histogram of price clustering for the active regime, overlaid with ATR bands for contextual reference.
The usefulness of this Combination
- Rolling mean anchors the trend, providing structure and smoothing noise.
- ATR bands contextualize volatility and dynamically show when price exceeds normal ranges, key for breakout or reversion trades.
- Percentile momentum detects regime shifts early and adapt to changing volatility and market conditions.
- Regime segmentation allows for analysis within market context, not arbitrary windowing.
- Price clustering shows zones of market activity, support, resistance, and congestion, within the structural boundaries of each trend.
2. Python Implementation
2.1 Imports and Parameter Setup
Import all core libraries. Set parameters for symbol, time range, and analytical windows.
Each parameter is annotated so you know how to tune responsiveness and sensitivity.
import yfinance as yf
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
from matplotlib.collections import LineCollection
# ── PARAMETERS ───
TICKER = "BTC-USD"
PERIOD = "180d"
INTERVAL = "1h"
SMA_LEN = 50 # Rolling mean window length. Higher = smoother trend, slower to react. Lower = more responsive, but noisier.
ATR_LEN = 200 # ATR calculation window. Higher = more stable bands, slower to adapt. Lower = bands respond quicker, can be more volatile.
MULTS = [1, 2, 3] # ATR multipliers for deviation bands. Increase values for wider bands (fewer extremes flagged), decrease for tighter bands (more sensitivity).
BINS = 50 # Number of price histogram bins. Higher = more detailed clustering, can look noisy. Lower = smoother, less granular distribution.
PERCENTILE_WINDOW = 500 # Window for percentile normalization. Higher = smoother momentum normalization, less sensitive to recent spikes. Lower = reacts faster to new momentum extremes.
UP_THRESHOLD = 0.5 # Momentum score threshold for uptrend regime. Increase = stricter trend confirmation. Decrease = easier to trigger an uptrend.
DOWN_THRESHOLD = -0.5 # Momentum score threshold for ending uptrend. Increase (closer to 0) = earlier exits. Decrease (more negative) = trends persist longer.
COLOR_PRICE = "gray" # Plot color for raw price.
COLOR_SMA_UP = np.array([0, 1, 0]) # SMA line color during uptrend (green).
COLOR_SMA_DOWN = np.array([1, 0, 0]) # SMA line color during downtrend (red).
COLOR_FLIP_UP = "green" # Marker color for uptrend regime flip.
COLOR_FLIP_DOWN = "red" # Marker color for downtrend regime flip.
2.2 Data Download and Preprocessing
Download OHLCV data using yfinance. Columns are standardized and rows with missing data are dropped.
def download_ohlc(ticker, period, interval):
df = yf.download(
ticker,
period=period,
interval=interval,
auto_adjust=True,
progress=False
)
# flatten MultiIndex if present
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
# 1) Fetch data
df = download_ohlc(TICKER, PERIOD, INTERVAL)
2.3 Trend and Volatility Calculation
Calculate the rolling mean as the trend baseline and compute ATR for volatility.
Newsletter