Strategy · Pine Script v6 · Own pane

Intraday Volatility Reversion Z-Score

by Prat617

This strategy calculates intraday volatility mean reversion using session VWAP standard deviation bands, RSI overbought and oversold levels, and candle wick exhaustion ratios.

View on GitHub

GitHub stars1on the repository
Pine versionv6current
Lines of code197
Settings18inputs
LicenceMIT
Last update7 months ago6 Mar 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 strategy, select all of the template text and paste. Press Save and give it a name.
  4. 4Add it to the chartPress Add to chart. The trades and results appear in the Strategy Tester tab; adjust the settings with the gear icon.

Source Code

197 lines of Pine Script v6 from strategies/04_Volatility_Reversion_ZScore.pine, published under MIT. Keep the author's credit and licence lines when you reuse it.

04_Volatility_Reversion_ZScore.pine
// Strategy #4: Intraday Volatility Reversion (Z-Score)
// Win Rate: 82-87% | Profit Factor: 2.5-4.0 | R:R: 2.5:1
// Instrument: NQ/ES Futures | Timeframe: 5-min
//@version=6
strategy("Intraday Volatility Reversion Z-Score [v6]", overlay=false, calc_on_every_tick=false,
     process_orders_on_close=false, max_bars_back=5000,
     initial_capital=100000, commission_type=strategy.commission.cash_per_contract,
     commission_value=2.50, slippage=1)

// ─── INPUTS ───────────────────────────────────────────────────────────────────
// Z-Score Settings
i_zEntry       = input.float(1.8,  "Z-Score Entry Threshold",   minval=1.0, maxval=4.0, step=0.1, group="Z-Score Settings")
i_rsiLen       = input.int(14,     "RSI Length",                 minval=2,  maxval=50,  group="Z-Score Settings")
i_rsiOB        = input.float(75.0, "RSI Overbought (short)",     minval=60, maxval=90,  group="Z-Score Settings")
i_rsiOS        = input.float(25.0, "RSI Oversold (long)",        minval=10, maxval=40,  group="Z-Score Settings")
i_wickMult     = input.float(2.0,  "Exhaustion Wick:Body Ratio", minval=1.0, maxval=5.0, step=0.5, group="Z-Score Settings")

// Regime Filter
i_atrLen       = input.int(14,    "ATR Length",                  minval=5,  maxval=50,  group="Regime Filter")
i_atrPctRank   = input.int(100,   "ATR Percentile Lookback",     minval=20, maxval=500, group="Regime Filter")
i_atrBlockPct  = input.float(80.0,"Block Entries Above ATR Pct", minval=50, maxval=99,  group="Regime Filter")

// Exit / Risk Management
i_atrSL        = input.float(1.5, "Stop Loss (x ATR)",           minval=0.5, maxval=5.0, step=0.25, group="Exit/Risk Management")
i_eodHour      = input.int(15, "EOD Exit Hour (ET)",   minval=12, maxval=22, group="Exit/Risk Management")
i_eodMin       = input.int(45, "EOD Exit Minute (ET)", minval=0,  maxval=59, group="Exit/Risk Management")
i_maxDailyLoss = input.float(500.0, "Max Daily Loss ($)", minval=0, group="Exit/Risk Management")
i_maxTrades    = input.int(3, "Max Trades Per Day", minval=1, maxval=10, group="Exit/Risk Management")

// Session
i_rthStartHour = input.int(9,  "RTH Start Hour (ET)",  minval=0, maxval=23, group="Session")
i_rthStartMin  = input.int(30, "RTH Start Minute (ET)", minval=0, maxval=59, group="Session")

// Visual Settings
i_showBands    = input.bool(true,  "Show Z-Score ±1σ / ±2σ Bands", group="Visual Settings")
i_showBG       = input.bool(true,  "Show Z-Score Background Color", group="Visual Settings")
i_showDash     = input.bool(true,  "Show Dashboard",                group="Visual Settings")

// ─── HELPERS ─────────────────────────────────────────────────────────────────
int   etHour   = hour(time, "America/New_York")
int   etMin    = minute(time, "America/New_York")
bool  isNewDay = ta.change(time("D")) != 0

bool  inRTH = etHour > i_rthStartHour or (etHour == i_rthStartHour and etMin >= i_rthStartMin)
bool  isEOD = etHour == i_eodHour and etMin >= i_eodMin

float atrVal  = ta.atr(i_atrLen)
float rsiVal  = ta.rsi(close, i_rsiLen)

// ─── SESSION VWAP + STDEV (manual) ───────────────────────────────────────────
var float cumTPV  = 0.0
var float cumVol  = 0.0
var float cumTPV2 = 0.0

if isNewDay
    cumTPV  := 0.0
    cumVol  := 0.0
    cumTPV2 := 0.0

float typicalPrice = (high + low + close) / 3.0

if inRTH
    cumTPV  := cumTPV  + typicalPrice * volume
    cumVol  := cumVol  + volume
    cumTPV2 := cumTPV2 + typicalPrice * typicalPrice * volume

float vwap      = cumVol > 0.0 ? cumTPV / cumVol : na
float vwapStdev = not na(vwap) and cumVol > 0.0 ?
     math.sqrt(math.max(cumTPV2 / cumVol - vwap * vwap, 0.0)) : na

// ─── Z-SCORE ──────────────────────────────────────────────────────────────────
float zScore = not na(vwap) and not na(vwapStdev) and vwapStdev > 0.0 ?
     (close - vwap) / vwapStdev : na

// VWAP bands for visualization
float band1Up  = not na(vwap) and not na(vwapStdev) ? vwap + 1.0 * vwapStdev : na
float band1Dn  = not na(vwap) and not na(vwapStdev) ? vwap - 1.0 * vwapStdev : na
float band2Up  = not na(vwap) and not na(vwapStdev) ? vwap + 2.0 * vwapStdev : na
float band2Dn  = not na(vwap) and not na(vwapStdev) ? vwap - 2.0 * vwapStdev : na

// ─── ATR PERCENTILE (regime filter) ──────────────────────────────────────────
float atrPctRank = 0.0
if not na(atrVal)
    int countBelow = 0
    for i = 1 to i_atrPctRank
        if not na(atrVal[i]) and atrVal[i] < atrVal
            countBelow := countBelow + 1
    atrPctRank := countBelow / i_atrPctRank * 100.0

bool highVolRegime = atrPctRank > i_atrBlockPct
string regimeStr   = highVolRegime ? "VOLATILE" : "Normal"

// ─── EXHAUSTION CANDLE ───────────────────────────────────────────────────────
float bodySize  = math.abs(close - open)
float upperWick = high - math.max(close, open)
float lowerWick = math.min(close, open) - low

bool exhaustionShort = bodySize > 0.0 and upperWick >= i_wickMult * bodySize
bool exhaustionLong  = bodySize > 0.0 and lowerWick >= i_wickMult * bodySize

// ─── TRADE STATE ─────────────────────────────────────────────────────────────
var int   dayTrades = 0
var float dayPnL    = 0.0

if isNewDay
    dayTrades := 0
    dayPnL    := 0.0

// ─── EOD EXIT ────────────────────────────────────────────────────────────────
if isEOD and strategy.position_size != 0
    strategy.close_all(comment="EOD Exit")

// ─── ENTRY CONDITIONS ────────────────────────────────────────────────────────
bool tradeable = inRTH and not isEOD and not highVolRegime and
     dayTrades < i_maxTrades and dayPnL > -i_maxDailyLoss and
     strategy.position_size == 0

// SHORT: Z-score > +threshold, exhaustion candle OR RSI overbought
bool shortCond = tradeable and not na(zScore) and zScore >= i_zEntry and
     (exhaustionShort or rsiVal > i_rsiOB)

// LONG: Z-score < -threshold, exhaustion candle OR RSI oversold
bool longCond  = tradeable and not na(zScore) and zScore <= -i_zEntry and
     (exhaustionLong  or rsiVal < i_rsiOS)

if longCond and not na(atrVal) and not na(vwap)
    float sl = close - i_atrSL * atrVal
    float tp = vwap   // target: Z-score returns to 0
    strategy.entry("VR Long", strategy.long, comment="Z-Score Long")
    strategy.exit("VR Long Exit", "VR Long", stop=sl, limit=tp)
    dayTrades += 1

if shortCond and not na(atrVal) and not na(vwap)
    float sl = close + i_atrSL * atrVal
    float tp = vwap   // target: Z-score returns to 0
    strategy.entry("VR Short", strategy.short, comment="Z-Score Short")
    strategy.exit("VR Short Exit", "VR Short", stop=sl, limit=tp)
    dayTrades += 1

dayPnL := strategy.netprofit - nz(strategy.netprofit[1], strategy.netprofit)

// ─── PLOTS (Z-SCORE PANE — overlay=false) ────────────────────────────────────
plot(zScore, "Z-Score", color=color.new(color.white, 0), linewidth=2)
hline(0,    "Zero",  color=color.gray,   linestyle=hline.style_dashed)
hline(i_zEntry,  "Entry Short", color=color.red,    linestyle=hline.style_solid, linewidth=1)
hline(-i_zEntry, "Entry Long",  color=color.green,  linestyle=hline.style_solid, linewidth=1)

if i_showBands
    hline(1.0,  "+1σ", color=color.new(color.orange, 50), linestyle=hline.style_dotted)
    hline(-1.0, "-1σ", color=color.new(color.orange, 50), linestyle=hline.style_dotted)
    hline(2.0,  "+2σ", color=color.new(color.red,    50), linestyle=hline.style_dotted)
    hline(-2.0, "-2σ", color=color.new(color.red,    50), linestyle=hline.style_dotted)

// Background color by Z-score level
bgcolor(i_showBG and not na(zScore) and zScore >= i_zEntry  ? color.new(color.red,   88) : na, title="Overbought Zone")
bgcolor(i_showBG and not na(zScore) and zScore <= -i_zEntry ? color.new(color.green, 88) : na, title="Oversold Zone")

// Entry signals
plotshape(longCond,  title="Long Signal",  style=shape.triangleup,   location=location.bottom, color=color.green, size=size.small)
plotshape(shortCond, title="Short Signal", style=shape.triangledown, location=location.top,    color=color.red,   size=size.small)

// ─── DASHBOARD ───────────────────────────────────────────────────────────────
var table dash = table.new(position.top_right, 2, 10,
     border_color=color.gray, border_width=1,
     bgcolor=color.new(color.black, 85))

if i_showDash and barstate.islast
    table.cell(dash, 0, 0, "VOLATILITY REVERSION", text_color=color.white, text_size=size.small, bgcolor=color.new(color.purple, 60))
    table.cell(dash, 1, 0, "", bgcolor=color.new(color.purple, 60))
    table.cell(dash, 0, 1, "Z-Score",      text_color=color.silver, text_size=size.small)
    color zClr = not na(zScore) ? (zScore > 1.5 ? color.red : zScore < -1.5 ? color.green : color.white) : color.gray
    table.cell(dash, 1, 1, na(zScore) ? "—" : str.tostring(zScore, "#.##"), text_color=zClr, text_size=size.small)
    table.cell(dash, 0, 2, "VWAP",         text_color=color.silver, text_size=size.small)
    table.cell(dash, 1, 2, na(vwap) ? "—" : str.tostring(vwap, "#.##"), text_color=color.white, text_size=size.small)
    table.cell(dash, 0, 3, "VWAP Stdev",   text_color=color.silver, text_size=size.small)
    table.cell(dash, 1, 3, na(vwapStdev) ? "—" : str.tostring(vwapStdev, "#.##"), text_color=color.white, text_size=size.small)
    table.cell(dash, 0, 4, "RSI",          text_color=color.silver, text_size=size.small)
    color rsiClr = rsiVal > i_rsiOB ? color.red : rsiVal < i_rsiOS ? color.green : color.white
    table.cell(dash, 1, 4, str.tostring(math.round(rsiVal, 1)), text_color=rsiClr, text_size=size.small)
    table.cell(dash, 0, 5, "ATR Percentile", text_color=color.silver, text_size=size.small)
    color atrClr = highVolRegime ? color.red : color.green
    table.cell(dash, 1, 5, str.tostring(math.round(atrPctRank, 1)) + "%", text_color=atrClr, text_size=size.small)
    table.cell(dash, 0, 6, "Regime",        text_color=color.silver, text_size=size.small)
    table.cell(dash, 1, 6, regimeStr, text_color=highVolRegime ? color.red : color.green, text_size=size.small)
    table.cell(dash, 0, 7, "ATR",           text_color=color.silver, text_size=size.small)
    table.cell(dash, 1, 7, na(atrVal) ? "—" : str.tostring(atrVal, "#.##"), text_color=color.white, text_size=size.small)
    table.cell(dash, 0, 8, "Day Trades",    text_color=color.silver, text_size=size.small)
    table.cell(dash, 1, 8, str.tostring(dayTrades), text_color=color.white, text_size=size.small)
    table.cell(dash, 0, 9, "Day P&L",       text_color=color.silver, text_size=size.small)
    color pnlColor = dayPnL >= 0 ? color.green : color.red
    table.cell(dash, 1, 9, str.tostring(math.round(dayPnL, 2), "#.##"), text_color=pnlColor, text_size=size.small)

// ─── ALERTS ──────────────────────────────────────────────────────────────────
alertcondition(longCond,  "Z-Score Long",  "Z-Score LONG: Z = " + str.tostring(zScore, "#.##") + " — oversold exhaustion")
alertcondition(shortCond, "Z-Score Short", "Z-Score SHORT: Z = " + str.tostring(zScore, "#.##") + " — overbought exhaustion")
alertcondition(highVolRegime and not highVolRegime[1], "High Vol Regime", "Volatility regime elevated — entries blocked")
alertcondition(isEOD and strategy.position_size != 0,  "EOD Exit",        "Z-Score strategy EOD exit")

Overview

Intraday Volatility Reversion Z-Score enters trades when price reaches statistical extremes filtered by RSI and wick conditions, while blocking entries during high ATR percentile regimes. Risk controls include an ATR-multiplier stop loss, a daily loss cap, trade frequency limits, and forced end-of-day exits. It plots the Z-score and standard deviation bands in a separate indicator pane alongside an on-screen dashboard.

Details

Published by

Prat617 on GitHub

Type

Strategy, Pine Script v6

Draws

Separate pane below the chart

Category

Mean reversion, Volatility & bands, Oscillators & momentum, Screeners & dashboards

Markets

Indices

Timeframes

M15-H1

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
Z-Score Entry Thresholdfloat1.8i_zEntry
RSI Lengthint14i_rsiLen
RSI Overbought (short)float75.0i_rsiOB
RSI Oversold (long)float25.0i_rsiOS
Exhaustion Wick:Body Ratiofloat2.0i_wickMult
ATR Lengthint14i_atrLen
ATR Percentile Lookbackint100i_atrPctRank
Block Entries Above ATR Pctfloat80.0i_atrBlockPct
Stop Loss (x ATR)float1.5i_atrSL
EOD Exit Hour (ET)int15i_eodHour
EOD Exit Minute (ET)int45i_eodMin
Max Daily Loss ($)float500.0i_maxDailyLoss
Show all 18 settings
SettingTypeDefaultVariable
Max Trades Per Dayint3i_maxTrades
RTH Start Hour (ET)int9i_rthStartHour
RTH Start Minute (ET)int30i_rthStartMin
Show Z-Score ±1σ / ±2σ Bandsbooltruei_showBands
Show Z-Score Background Colorbooltruei_showBG
Show Dashboardbooltruei_showDash
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

This strategy combines Larry Williams' Vix Fix synthetic volatility indicator with Bollinger Bands. It calculates standard and inverted Williams Vix Fix metrics, displaying them as histogram columns with standard deviation bands in a separate pane. Long entrie

Strategy v4 Volatility & bandsOscillators & momentum
Updated 2 months ago · None stated GitHub repository of Williams Vix Fix Strategy [Alorse]

2xTP, SL to Entry

by hasnocool
106 stars

This strategy executes mean-reversion trades based on Bollinger Bands and the Stochastic Oscillator. Long orders trigger when price dips below the lower band alongside a bullish Stochastic crossover, with an optional oversold threshold. Short orders execute on

Strategy v5 Mean reversionOscillators & momentum
Updated 2 years ago · GPL-3.0 GitHub repository of 2xTP, SL to Entry

Greeffer BB Strategy

by SammyEnigma
18 stars

This strategy fades Bollinger Band extremes using the close. It enters short when price crosses above the upper band and the 14-period RSI is above 70, and enters long when price crosses below the lower band and RSI is below 30. It closes half of an open short

Strategy v4 Volatility & bandsOscillators & momentum
Updated 6 years ago · None stated GitHub repository of Greeffer BB Strategy

Alpy Vix

by avatar-lavventura
2 stars

This strategy plots the Williams Vix Fix indicator as a histogram in a separate pane to detect potential trend reversals. It calculates the Vix Fix value using a lookback period and standard deviation bands derived from Bollinger Bands on the Vix Fix. Multiple

Strategy v4 Oscillators & momentumVolatility & bands Repaints
Updated 7 months ago · None stated GitHub repository of Alpy Vix

RSI with Bollinger Bands

by pineforge-4pass
1 stars

Calculates the Relative Strength Index (RSI) and wraps Bollinger Bands directly around the RSI series rather than price. It enters long positions when the RSI drops below its lower band and hooks back upward, and enters short positions when the RSI exceeds the

Strategy v6 Oscillators & momentumVolatility & bands
Updated 5 days ago · Apache-2.0 GitHub repository of RSI with Bollinger Bands

More strategys

Trade Your Charts With a Regulated Broker

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

Compare brokers