Strategy · Pine Script v5 · On the chart

FVG + Volatility Regime

by ankitpatel117

A strategy that detects three-candle fair value gaps and trades retracements into unfilled gaps, but only when an ATR-based volatility regime agrees.

View on GitHub

GitHub stars1on the repository
Pine versionv5runs as is
Lines of code228
Settings19inputs
LicenceMIT
Last update3 months ago28 Jul 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

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.

Details

Published by

ankitpatel117 on GitHub

Draws

On the price chart

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.

Topics

algorithmic-tradingbacktestingmetatrader5options-tradingpythonquantitative-finance

Settings

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

SettingTypeDefaultVariable
ATR Lengthint14atrLen
Regime MethodstringATR vs MAregimeMethod
Regime Lookbackint20regimeLookback
High-Vol Multiplier (MA)float1.0maMultiplier
High-Vol Percentile (%)float70pctThreshold
Trade FVGs in HIGH volatilitybooltruetradeInHighVol
Allow Long entriesbooltrueallowLongs
Allow Short entriesbooltrueallowShorts
Max FVGs trackedint20maxTrackFvg
Mitigation rulestringFull fillmitigateMode
SL buffer (xATR)float0.10slBufferAtr
Use ATR Trailing Stop (instead of fixed RR TP)boolfalseuseTrailing
Show all 19 settings
SettingTypeDefaultVariable
Risk : Reward (fixed TP)float2.0rrRatio
Trail distance (xATR)float2.0trailAtrMult
Draw FVG zonesbooltrueshowZones
Show regime dashboardbooltrueshowDashboard
Tint background by regimebooltruetintBg
Bullish FVG fillcolorcolor.new(color.teal, 80)bullCol
Bearish FVG fillcolorcolor.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.

Similar Pine Scripts

Daily Bias + FVG Strategy

by TradersPost
228 stars

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

Strategy v6 SMC / ICT / price actionSupport & resistance Repaints
Updated 3 months ago · None stated GitHub repository of Daily Bias + FVG Strategy
105 stars

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

Strategy v6 SMC / ICT / price actionOscillators & momentum Repaints
57 stars

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

Strategy v6 SMC / ICT / price actionOscillators & momentum Repaints
Updated 1 year ago · Apache-2.0 GitHub repository of Market Structure MACD 4H + Trailing
12 stars

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

Strategy v6 SMC / ICT / price actionTrend
Updated 4 months ago · None stated GitHub repository of BTC 4h FVG/iFVG Automated Strategy

Hedge Fund SMC Strategy - QuantAlgo

by thesenegalesehitch
4 stars

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

Strategy v6 SMC / ICT / price actionTrend Repaints
Updated 4 months ago · Custom licence GitHub repository of Hedge Fund SMC Strategy - QuantAlgo

Estimations-10min

by IAMATOMIC001
3 stars

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

Strategy v5 TrendMoving averages Repaints
Updated 12 months ago · None stated GitHub repository of Estimations-10min

More SMC / ICT scripts

Related on ForexR

Trade Your Charts With a Regulated Broker

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

Compare brokers