1Copy the scriptPress Copy script above, or the copy button on the code below.
2Open the Pine EditorOpen a TradingView chart and choose Pine Editor at the bottom of the screen.
3Paste over the templateCreate a new strategy, select all of the template text and paste. Press Save and give it a name.
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
228 lines of Pine Script v5 from FVG_Volatility_Regime_Strategy.pine, published under MIT. Keep the author's credit and licence lines when you reuse it.
FVG_Volatility_Regime_Strategy.pine
//@version=5
// =============================================================================
// FAIR VALUE GAP (FVG) + VOLATILITY REGIME FILTER — Strategy (Pine v5)
// -----------------------------------------------------------------------------
// Idea:
// * Detect 3-candle Fair Value Gaps (price imbalances left by aggressive
// institutional order flow).
// * Only act on them when the market's VOLATILITY REGIME agrees. The core
// thesis is that an FVG formed during EXPANSION (high vol) is more likely
// to act as a continuation/momentum magnet, whereas in quiet markets the
// same gap is noise. A toggle lets you flip the logic and test the
// mean-reversion hypothesis (trade gaps only in LOW vol).
// * Entries are RETRACEMENT entries: we wait for price to trade back INTO an
// unmitigated gap, then position in the gap's direction.
// =============================================================================
strategy("FVG + Volatility Regime",
overlay = true,
initial_capital = 10000,
default_qty_type = strategy.percent_of_equity,
default_qty_value = 10,
commission_type = strategy.commission.percent,
commission_value = 0.02,
max_boxes_count = 500,
max_labels_count = 100,
pyramiding = 0,
calc_on_every_tick = false)
// =============================================================================
// INPUTS
// =============================================================================
// ---- Volatility Regime ------------------------------------------------------
grpVol = "Volatility Regime"
atrLen = input.int(14, "ATR Length", minval = 1, group = grpVol, tooltip = "Period for the True Range average that measures current volatility.")
regimeMethod = input.string("ATR vs MA", "Regime Method", options = ["ATR vs MA", "ATR Percentile"], group = grpVol, tooltip = "How to classify High vs Low volatility.")
regimeLookback= input.int(20, "Regime Lookback", minval = 2, group = grpVol, tooltip = "Lookback for the ATR moving average / percentile window.")
maMultiplier = input.float(1.0, "High-Vol Multiplier (MA)", minval = 0.1, step = 0.05, group = grpVol, tooltip = "ATR vs MA mode: High vol when ATR > MA * this multiplier.")
pctThreshold = input.float(70, "High-Vol Percentile (%)", minval = 1, maxval = 99, step = 1, group = grpVol, tooltip = "Percentile mode: High vol when current ATR sits above this percentile of the lookback window.")
// ---- Strategy behaviour -----------------------------------------------------
grpStrat = "Strategy Logic"
tradeInHighVol= input.bool(true, "Trade FVGs in HIGH volatility", group = grpStrat, tooltip = "ON = momentum mode: only enter when regime is HIGH (default thesis).\nOFF = mean-reversion mode: only enter when regime is LOW.")
allowLongs = input.bool(true, "Allow Long entries", group = grpStrat)
allowShorts = input.bool(true, "Allow Short entries", group = grpStrat)
maxTrackFvg = input.int(20, "Max FVGs tracked", minval = 1, maxval = 50, group = grpStrat, tooltip = "How many recent unmitigated gaps to keep alive.")
mitigateMode = input.string("Full fill", "Mitigation rule", options = ["Full fill", "Touch"], group = grpStrat, tooltip = "Full fill = gap dies only when price closes fully through it.\nTouch = gap dies the moment price tags it (so each gap is good for one entry).")
// ---- Risk Management --------------------------------------------------------
grpRisk = "Risk Management"
slBufferAtr = input.float(0.10, "SL buffer (xATR)", minval = 0, step = 0.05, group = grpRisk, tooltip = "Extra distance beyond Candle-1 high/low for the stop, in ATR units.")
useTrailing = input.bool(false, "Use ATR Trailing Stop (instead of fixed RR TP)", group = grpRisk)
rrRatio = input.float(2.0, "Risk : Reward (fixed TP)", minval = 0.1, step = 0.1, group = grpRisk, tooltip = "Take-profit distance = RR * stop distance. Used when trailing is OFF.")
trailAtrMult = input.float(2.0, "Trail distance (xATR)", minval = 0.1, step = 0.1, group = grpRisk, tooltip = "ATR trailing-stop distance. Used when trailing is ON.")
// ---- Visuals ----------------------------------------------------------------
grpViz = "Visuals"
showZones = input.bool(true, "Draw FVG zones", group = grpViz)
showDashboard = input.bool(true, "Show regime dashboard",group = grpViz)
tintBg = input.bool(true, "Tint background by regime", group = grpViz)
bullCol = input.color(color.new(color.teal, 80), "Bullish FVG fill", group = grpViz)
bearCol = input.color(color.new(color.red, 80), "Bearish FVG fill", group = grpViz)
// =============================================================================
// 1) VOLATILITY REGIME
// =============================================================================
atr = ta.atr(atrLen)
atrMa = ta.sma(atr, regimeLookback)
atrPctile = ta.percentrank(atr, regimeLookback) // % of lookback bars with ATR <= current
// Classify the current bar's regime.
isHighVol = regimeMethod == "ATR vs MA" ? atr > atrMa * maMultiplier : atrPctile >= pctThreshold
isLowVol = not isHighVol
// The single boolean that gates entries, respecting the momentum/mean-reversion toggle.
regimeOK = tradeInHighVol ? isHighVol : isLowVol
// =============================================================================
// 2) FAIR VALUE GAP DETECTION (3-candle imbalance)
// -----------------------------------------------------------------------------
// Candle index on the CURRENT (just-closed) bar:
// Candle 1 = bar [2] (the origin candle — used for the stop)
// Candle 2 = bar [1] (the displacement candle — the body that creates the gap)
// Candle 3 = bar [0] (the current bar)
// Bullish FVG: Candle 1 HIGH < Candle 3 LOW -> unfilled gap above C1 high.
// Bearish FVG: Candle 1 LOW > Candle 3 HIGH -> unfilled gap below C1 low.
// =============================================================================
bullFvg = low > high[2] // gap zone = [ high[2] .. low ]
bearFvg = high < low[2] // gap zone = [ high .. low[2] ]
// ---- Persistent storage for active gaps (parallel arrays) -------------------
var float[] zTop = array.new_float() // upper bound of the gap
var float[] zBot = array.new_float() // lower bound of the gap
var float[] zStop = array.new_float() // protective level = Candle-1 high/low
var bool[] zBull = array.new_bool() // true = bullish gap
var bool[] zTraded = array.new_bool() // already produced an entry?
var box[] zBox = array.new_box() // drawing handle (na if not drawn)
// Helper: push a freshly-formed gap onto the stack and (optionally) draw it.
addFvg(_top, _bot, _stop, _isBull) =>
array.push(zTop, _top)
array.push(zBot, _bot)
array.push(zStop, _stop)
array.push(zBull, _isBull)
array.push(zTraded, false)
bx = na
if showZones
bx := box.new(bar_index - 2, _top, bar_index, _bot,
border_color = _isBull ? color.teal : color.red,
bgcolor = _isBull ? bullCol : bearCol,
border_width = 1)
array.push(zBox, bx)
// Cap memory: drop the oldest gap if we exceed the tracking limit.
if array.size(zTop) > maxTrackFvg
oldBx = array.shift(zBox)
if not na(oldBx)
box.delete(oldBx)
array.shift(zTop)
array.shift(zBot)
array.shift(zStop)
array.shift(zBull)
array.shift(zTraded)
// Register new gaps as they confirm on bar close.
if barstate.isconfirmed
if bullFvg
addFvg(low, high[2], low[2], true) // stop sits under Candle-1 low
if bearFvg
addFvg(low[2], high, high[2], false) // stop sits above Candle-1 high
// =============================================================================
// 3) MITIGATION + ENTRY SCANNING
// -----------------------------------------------------------------------------
// We sweep the active gaps once per bar. For each gap we:
// * extend its box to the current bar (live visual),
// * detect a retracement touch (entry trigger),
// * detect full mitigation (retire the gap).
// Entry signals are aggregated into booleans and fired once after the loop so
// that strategy.entry is called from the script's top level.
// =============================================================================
longSignal = false
shortSignal = false
longStopPx = na
shortStopPx = na
if array.size(zTop) > 0
// iterate newest -> oldest so the freshest gap wins if several trigger
for i = array.size(zTop) - 1 to 0
gTop = array.get(zTop, i)
gBot = array.get(zBot, i)
gStop = array.get(zStop, i)
gBull = array.get(zBull, i)
gDone = array.get(zTraded, i)
// keep the visual anchored to the latest bar
if showZones
gbx = array.get(zBox, i)
if not na(gbx)
box.set_right(gbx, bar_index)
// --- price interaction this bar ---
touched = low <= gTop and high >= gBot // price tagged the zone
fullFill = gBull ? (low <= gBot) : (high >= gTop) // price closed the gap entirely
// --- entry trigger: first retracement into an untraded gap, regime aligned ---
if touched and not gDone and regimeOK and strategy.position_size == 0
if gBull and allowLongs
longSignal := true
longStopPx := gStop
array.set(zTraded, i, true)
else if (not gBull) and allowShorts
shortSignal := true
shortStopPx := gStop
array.set(zTraded, i, true)
// --- mitigation: retire the gap ---
// "Touch" mode kills the gap on first tag; "Full fill" waits for a clean sweep.
mitigated = mitigateMode == "Touch" ? touched : fullFill
if mitigated
gbx2 = array.get(zBox, i)
if not na(gbx2)
box.delete(gbx2)
array.remove(zBox, i)
array.remove(zTop, i)
array.remove(zBot, i)
array.remove(zStop, i)
array.remove(zBull, i)
array.remove(zTraded, i)
// =============================================================================
// 4) ORDER EXECUTION & RISK
// =============================================================================
// LONG
if longSignal
strategy.entry("Long", strategy.long)
stopPx = longStopPx - atr * slBufferAtr // just below Candle-1 low
risk = close - stopPx
if useTrailing
strategy.exit("L-exit", from_entry = "Long", stop = stopPx, trail_points = atr * trailAtrMult / syminfo.mintick, trail_offset = atr * trailAtrMult / syminfo.mintick)
else
strategy.exit("L-exit", from_entry = "Long", stop = stopPx, limit = close + risk * rrRatio)
// SHORT
if shortSignal
strategy.entry("Short", strategy.short)
stopPx = shortStopPx + atr * slBufferAtr // just above Candle-1 high
risk = stopPx - close
if useTrailing
strategy.exit("S-exit", from_entry = "Short", stop = stopPx, trail_points = atr * trailAtrMult / syminfo.mintick, trail_offset = atr * trailAtrMult / syminfo.mintick)
else
strategy.exit("S-exit", from_entry = "Short", stop = stopPx, limit = close - risk * rrRatio)
// =============================================================================
// 5) VISUALS
// =============================================================================
// Background tint by regime
bgcolor(tintBg ? (isHighVol ? color.new(color.orange, 92) : color.new(color.blue, 94)) : na, title = "Regime BG")
// Dashboard label (top-right) showing live regime + active-gap count
var table dash = table.new(position.top_right, 1, 3, border_width = 1)
if showDashboard and barstate.islast
regimeTxt = isHighVol ? "HIGH VOL" : "LOW / NORMAL VOL"
regimeBg = isHighVol ? color.new(color.orange, 0) : color.new(color.blue, 0)
modeTxt = tradeInHighVol ? "Mode: Momentum (trade HIGH)" : "Mode: Mean-Rev (trade LOW)"
table.cell(dash, 0, 0, regimeTxt, text_color = color.white, bgcolor = regimeBg, text_size = size.normal)
table.cell(dash, 0, 1, modeTxt, text_color = color.white, bgcolor = color.new(color.gray, 20), text_size = size.small)
table.cell(dash, 0, 2, "Active FVGs: " + str.tostring(array.size(zTop)) + " | ATR: " + str.tostring(atr, format.mintick), text_color = color.white, bgcolor = color.new(color.gray, 40), text_size = size.small)
// Optional ATR/percentile plots in a hidden pane reference (kept off-chart by overlay=true)
plotchar(isHighVol, "HighVol marker", "", location = location.bottom, color = color.new(color.orange, 60), size = size.tiny)
Overview
The regime is high or low by comparing ATR to its average or to a percentile, and a toggle flips between high-volatility momentum and low-volatility mean-reversion testing. The stop sits beyond the gap with an ATR buffer. The target is a fixed reward-to-risk multiple or an ATR trailing stop. Zones and a dashboard are drawn.
The inputs you can change from the gear icon on the chart. Defaults are the author's.
Setting
Type
Default
Variable
ATR Length
int
14
atrLen
Regime Method
string
ATR vs MA
regimeMethod
Regime Lookback
int
20
regimeLookback
High-Vol Multiplier (MA)
float
1.0
maMultiplier
High-Vol Percentile (%)
float
70
pctThreshold
Trade FVGs in HIGH volatility
bool
true
tradeInHighVol
Allow Long entries
bool
true
allowLongs
Allow Short entries
bool
true
allowShorts
Max FVGs tracked
int
20
maxTrackFvg
Mitigation rule
string
Full fill
mitigateMode
SL buffer (xATR)
float
0.10
slBufferAtr
Use ATR Trailing Stop (instead of fixed RR TP)
bool
false
useTrailing
Show all 19 settings
Setting
Type
Default
Variable
Risk : Reward (fixed TP)
float
2.0
rrRatio
Trail distance (xATR)
float
2.0
trailAtrMult
Draw FVG zones
bool
true
showZones
Show regime dashboard
bool
true
showDashboard
Tint background by regime
bool
true
tintBg
Bullish FVG fill
color
color.new(color.teal, 80)
bullCol
Bearish FVG fill
color
color.new(color.red, 80)
bearCol
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.
This overlay strategy plots previous-day high and low, the daily open, and fair value gap zones on the price chart. It takes entries on a selectable timeframe when the FVG requirement and an optional session window allow them, then uses an ATR or fixed pip sto
This overlay strategy looks for gold scalps from break of structure and change of character. It enters long when a bullish break, a bullish character change, and an optional volume filter align, and short on the opposite set. Optional RSI levels can block trad
This strategy identifies trend reversals through market structure changes (CHoCH) filtered by MACD momentum. It initiates long positions on bullish structure breaks when MACD is positive and above its signal line, and short positions on bearish breaks when MAC
This strategy is titled for Bitcoin on the four-hour chart and is set to draw on the price chart. It detects three-candle fair value gaps and inverted gaps, and it defines a check for price near a gap midpoint within a percentage tolerance. An optional filter
This overlay strategy combines EMA trend following with simple Smart Money Concepts filters. It reads H4 and H1 bias, then looks for a liquidity sweep plus a displacement candle in that direction, gated by ADX, ATR, and volume. Entries are allowed only in Lond
This overlay strategy estimates trend with a selectable TEMA, Hull, or ALMA basis and optional higher-timeframe or Heikin Ashi sources. It plots a regression estimator with ATR near and far boundaries, can color bars by trend, and can show order blocks. Entrie