Indicator Β· Pine Script v5 Β· Own pane

Half-Life Estimator (O-U)

by Teerth1

Estimates the mean-reversion half-life of price in bars by fitting an Ornstein-Uhlenbeck model with a rolling regression on log prices.

View on GitHub

GitHub stars1on the repository
Pine versionv5runs as is
Lines of code145
Settings6inputs
LicenceNone statedfile: MPL-2.0
Last update6 months ago10 Apr 2026

Add It to Your TradingView Chart

  1. 1Copy the scriptPress Copy script above, or the copy button on the code below.
  2. 2Open the Pine EditorOpen a TradingView chart and choose Pine Editor at the bottom of the screen.
  3. 3Paste over the templateCreate a new indicator, select all of the template text and paste. Press Save and give it a name.
  4. 4Add it to the chartPress Add to chart, then adjust the settings with the gear icon next to its name.

Source Code

145 lines of Pine Script v5 from deal-api/src/main/resources/PineScript/Half-Life Estimator (Ornstein-Uhlenbeck).pine, published under MPL-2.0 (file header). Keep the author's credit and licence lines when you reuse it.

Half-Life Estimator (Ornstein-Uhlenbeck).pine
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// Half-Life Estimator for Mean Reversion (Ornstein-Uhlenbeck Process)
// Estimates how many bars until price reverts halfway to the mean

//@version=5
indicator('Half-Life Estimator (O-U)', 'HLπŸ”„', precision=2, max_bars_back=1000)

// ══════════════════════════════════════════════════════════════════════════════
// INPUTS
// ══════════════════════════════════════════════════════════════════════════════
group1 = 'Calculation Settings'
group2 = 'Display Settings'

src = input.source(close, 'Source', group=group1)
lookback = input.int(100, 'Lookback Period', minval=20, maxval=500, group=group1, tooltip='Number of bars for regression. Larger = more stable, smaller = more responsive')
showTable = input.bool(true, 'Show Info Table', group=group2)
showBands = input.bool(true, 'Show Mean Β± Std Bands', group=group2)
bandMult = input.float(2.0, 'Band Multiplier', minval=0.5, maxval=4.0, step=0.5, group=group2)
bgColor = input.bool(true, 'Dark Background', group=group2)

bgcolor(bgColor ? color.new(#000000, 80) : na)

// ══════════════════════════════════════════════════════════════════════════════
// HALF-LIFE CALCULATION (Ornstein-Uhlenbeck via AR(1) Regression)
// ══════════════════════════════════════════════════════════════════════════════

// Log prices for stationarity
logPrice = math.log(src)

// Lagged difference: y(t) - y(t-1) regressed on y(t-1)
// Model: Ξ”y = Ξ» * (y - ΞΌ) + Ξ΅  β†’  Half-life = -ln(2) / ln(1 + Ξ»)
deltaY = logPrice - logPrice[1]
lagY = logPrice[1]

// Calculate means
meanDelta = ta.sma(deltaY, lookback)
meanLag = ta.sma(lagY, lookback)

// OLS Regression: Ξ”y = Ξ± + Ξ² * y(t-1) + Ξ΅
// Ξ² (lambda) tells us speed of mean reversion
// Covariance and Variance
float sumCov = 0.0
float sumVar = 0.0

for i = 0 to lookback - 1
    devDelta = nz(deltaY[i]) - meanDelta
    devLag = nz(lagY[i]) - meanLag
    sumCov := sumCov + devDelta * devLag
    sumVar := sumVar + devLag * devLag

// Beta (mean reversion speed parameter)
beta = sumVar != 0 ? sumCov / sumVar : 0

// Half-life in bars: -ln(2) / ln(1 + Ξ²)
// Note: Ξ² should be negative for mean reversion
float halfLife = na
if beta < 0 and beta > -1
    halfLife := -math.log(2) / math.log(1 + beta)

// Cap half-life for display purposes
halfLifeCapped = halfLife > 500 ? 500 : halfLife < 0 ? na : halfLife

// ══════════════════════════════════════════════════════════════════════════════
// EQUILIBRIUM MEAN & BANDS
// ══════════════════════════════════════════════════════════════════════════════

// Implied equilibrium (long-term mean)
alpha = meanDelta - beta * meanLag
equilibrium = beta != 0 ? math.exp(-alpha / beta) : ta.sma(src, lookback)

// Standard deviation for bands
stdev = ta.stdev(src, lookback)
upperBand = equilibrium + bandMult * stdev
lowerBand = equilibrium - bandMult * stdev

// Z-Score from equilibrium
zScore = stdev != 0 ? (src - equilibrium) / stdev : 0

// ══════════════════════════════════════════════════════════════════════════════
// REGIME CLASSIFICATION
// ══════════════════════════════════════════════════════════════════════════════

isMeanReverting = beta < 0 and halfLifeCapped < 50
isRandom = math.abs(beta) < 0.01 or na(halfLife)
isTrending = beta >= 0 or halfLifeCapped >= 50

regimeColor = isMeanReverting ? color.lime : isTrending ? color.red : color.gray
regimeText = isMeanReverting ? 'Mean Reverting' : isTrending ? 'Trending/Random' : 'Inconclusive'

// ══════════════════════════════════════════════════════════════════════════════
// SIGNALS
// ══════════════════════════════════════════════════════════════════════════════

// Entry signals when mean reverting AND price at extreme
buySignal = isMeanReverting and zScore < -bandMult
sellSignal = isMeanReverting and zScore > bandMult

// ══════════════════════════════════════════════════════════════════════════════
// PLOTS
// ══════════════════════════════════════════════════════════════════════════════

// Half-life plot
plot(halfLifeCapped, 'Half-Life (bars)', color=regimeColor, linewidth=2, style=plot.style_stepline)

// Reference lines
hline(10, 'Fast Reversion (10 bars)', color=color.new(color.green, 50), linestyle=hline.style_dashed)
hline(30, 'Slow Reversion (30 bars)', color=color.new(color.yellow, 50), linestyle=hline.style_dashed)
hline(50, 'Very Slow (50 bars)', color=color.new(color.red, 50), linestyle=hline.style_dashed)

// Background coloring for regime
bgcolor(isMeanReverting ? color.new(color.green, 90) : na)

// Signals
plotshape(buySignal, 'Buy Signal', shape.triangleup, location.bottom, color.lime, size=size.small)
plotshape(sellSignal, 'Sell Signal', shape.triangledown, location.top, color.red, size=size.small)

// ══════════════════════════════════════════════════════════════════════════════
// INFO TABLE
// ══════════════════════════════════════════════════════════════════════════════

var table infoTable = table.new(position.top_right, 2, 5, bgcolor=color.new(#1a1a2e, 10), frame_color=color.gray, frame_width=1, border_width=1)

if barstate.islast and showTable
    table.cell(infoTable, 0, 0, 'Half-Life', text_color=color.white, bgcolor=color.new(color.gray, 50))
    table.cell(infoTable, 1, 0, str.tostring(math.round(halfLifeCapped, 1)) + ' bars', text_color=regimeColor, bgcolor=color.new(regimeColor, 85))
    
    table.cell(infoTable, 0, 1, 'Beta (Ξ»)', text_color=color.white, bgcolor=color.new(color.gray, 50))
    table.cell(infoTable, 1, 1, str.tostring(math.round(beta, 4)), text_color=beta < 0 ? color.lime : color.red, bgcolor=color.new(color.gray, 70))
    
    table.cell(infoTable, 0, 2, 'Regime', text_color=color.white, bgcolor=color.new(color.gray, 50))
    table.cell(infoTable, 1, 2, regimeText, text_color=regimeColor, bgcolor=color.new(regimeColor, 85))
    
    table.cell(infoTable, 0, 3, 'Z-Score', text_color=color.white, bgcolor=color.new(color.gray, 50))
    table.cell(infoTable, 1, 3, str.tostring(math.round(zScore, 2)), text_color=zScore > 0 ? color.red : color.lime, bgcolor=color.new(color.gray, 70))
    
    table.cell(infoTable, 0, 4, 'Equilibrium', text_color=color.white, bgcolor=color.new(color.gray, 50))
    table.cell(infoTable, 1, 4, str.tostring(math.round(equilibrium, 2)), text_color=color.aqua, bgcolor=color.new(color.gray, 70))

// ══════════════════════════════════════════════════════════════════════════════
// ALERTS
// ══════════════════════════════════════════════════════════════════════════════

alertcondition(buySignal, 'Buy Signal', 'Half-Life: Mean reversion BUY (oversold)')
alertcondition(sellSignal, 'Sell Signal', 'Half-Life: Mean reversion SELL (overbought)')
alertcondition(ta.cross(halfLifeCapped, 30), 'Regime Change', 'Half-Life crossed 30 bars threshold')

Overview

A lookback input sets how many bars feed the regression, and the half-life is capped for display. Optional bands show the mean plus or minus a standard deviation multiple, with an information table and dark background toggle. It plots in a separate pane, gives no entry or exit signals, and only returns a value when the fitted relationship implies reversion.

Details

Published by

Teerth1 on GitHub

Type

Indicator, Pine Script v5

Draws

Separate pane below the chart

Category

Mean reversion, Volatility & bands

Markets

Any symbol

Timeframes

Any timeframe

Risk Notes

No repainting, lookahead or stacked entries were found in its code or README. That says nothing about how well it works: test it first.

Settings

The inputs you can change from the gear icon on the chart. Defaults are the author's.

SettingTypeDefaultVariable
Sourcesourceclosesrc
Lookback Periodint100lookback
Show Info TablebooltrueshowTable
Show Mean Β± Std BandsbooltrueshowBands
Band Multiplierfloat2.0bandMult
Dark BackgroundbooltruebgColor
Third-party code. ForexR did not write, test or endorse this script, and the summary above was generated automatically from its code and README. Indicators describe the past, and a strategy's backtest can look better than live trading, especially when a script repaints. Check it in the Strategy Tester and on a demo account before you rely on it.

Similar Pine Scripts

261 stars

This indicator calculates standardized z-scores of price data across four different lookback periods (6, 14, 26, and 100). Standardizing data subtracts the moving average from the price and divides by the standard deviation. It applies a Double Exponential Mov

Indicator v5 Mean reversionVolatility & bands
Updated 4 years ago Β· None stated GitHub repository of Function - Standardization (zScore)
114 stars

This indicator implements a dual-direction variation of the Williams Vix Fix oscillator in a separate pane. It normalizes price data using min-max scaling to compute both upward and downward synthetic volatility metrics across two selectable calculation modes

Indicator v4 Volatility & bandsMean reversion
Updated 1 year ago Β· None stated GitHub repository of Combo Williams Vix Fix (Twin)
99 stars

The Mean Squared Error indicator calculates the mean of squared differences between a source series and its EMA, averaged via a rolling buffer acting as SMA. This error metric plots as a filled red area on a separate pane, while the EMA overlays as a yellow li

Indicator v6 Volatility & bandsMean reversion
Updated 4 weeks ago Β· Apache-2.0 GitHub repository of Mean Squared Error (MSE)

This indicator classifies market behavior into momentum, mean-reversion, noise, or mixed regimes. It fuses four statistical predictability sensorsβ€”Variance Ratio across multiple horizons, return autocorrelation, Hurst exponent approximation, and fractal effici

Indicator v6 TrendMean reversion
Updated 2 days ago Β· MIT GitHub repository of Predictability Regime Index [WavesUnchained]

Variance Ratio Test

by Anderson-ALGO
9 stars

This indicator calculates the Variance Ratio Test statistic to evaluate random walk behavior across multiple sampling periods. It computes log returns, base period variance over a specified lookback window, and scaled multi-period variances to determine test Z

Indicator v4 Volatility & bandsMean reversion
Updated 5 years ago Β· None stated GitHub repository of Variance Ratio Test

This indicator builds a channel from a Hull moving average of the source and an ATR scaled by a band multiplier, then normalizes price inside that channel into a 0 to 100 oscillator. It smooths the result with a weighted moving average and plots the line in a

Indicator v6 Mean reversionVolatility & bands
Updated 3 months ago Β· None stated GitHub repository of Radiant Mean Reversion Channels [Pineify]

More indicators

Trade Your Charts With a Regulated Broker

Compare brokers that connect to TradingView, with spreads, minimum deposits and leverage side by side.

Compare brokers