How to Code Order Blocks and Fair Value Gaps in Pine Script (Properly)
How to code order blocks and fair value gaps in Pine Script: detection rules, zone storage, mitigation logic, and the backtest showing what survives.

To code order blocks and fair value gaps in Pine Script you need three pieces: a detection rule that fires on a confirmed bar (never the live one), a store of zones with their top, bottom, direction and birth bar so they can be drawn and tracked, and a mitigation rule that retires or marks a zone once price has traded into it. A fair value gap is the simplest case: a three-bar pattern where the first bar's high is below the third bar's low (bullish) or the first bar's low is above the third bar's high (bearish), with the gap between them as the zone. An order block is the last opposite-coloured candle before an impulsive move that breaks structure, with the candle's body or full range as the zone.
Everything else people argue about, displacement thresholds, whether to use bodies or wicks, how many zones to keep, when a zone is "used up", is a parameter on top of those three pieces. In this post I walk through each rule in words and numbered steps, then show what happened when I put an order block and FVG retest strategy through 1.75 million regime-filter combinations on Indian index futures. Spoiler: the detection code was the easy part; the honest testing was where the real lessons were. Backtest research only, not financial advice.
How do you detect a fair value gap in Pine Script?
Work on closed bars. On every bar, look back at the three most recently completed candles and ask:
- Is the high of the oldest of the three strictly below the low of the newest? If yes, a bullish FVG exists, with the zone spanning from that older high up to that newer low.
- Is the low of the oldest strictly above the high of the newest? If yes, a bearish FVG exists, spanning from the newer high up to the older low.
- Optionally, require the middle candle's range to exceed some multiple of average true range, so only displacement gaps count and the chart is not covered in micro-gaps.
- Optionally, require the gap size itself to exceed a minimum in ticks or ATR fraction.
Detection happens one bar after the pattern completes, so the box you draw starts at the middle candle and extends right. Drawing it on the live bar is the classic repaint: the gap can close before the bar finishes.
How do you detect an order block?
An order block needs a definition of "impulsive move", and that is where implementations diverge. The rule I use:
- Track swing highs and lows with a pivot of a chosen length.
- When a closed bar breaks above the most recent swing high, scan back from that break to find the last bearish candle before the move started. That candle is the bullish order block; its zone is either its body (open to close) or its full range (low to high), selectable.
- Mirror the logic for a break below the most recent swing low to find the last bullish candle as a bearish order block.
- Require that the move from the order block to the break covers at least a minimum distance in ATR, otherwise it is noise rather than displacement.
The subtle part is "last opposite candle before the move started". If you simply take the candle immediately before the break bar, you will often grab a continuation candle in the middle of the move rather than the origin. Walk back until the candle colour flips and the prior bars were not already trending.
How do you store and draw zones without hitting object limits?
Pine lets you keep arrays of boxes and user-defined types, and a chart can display at most a few hundred drawing objects per script. A workable structure:
- One array per zone type and direction, each element holding top, bottom, the bar index it was born on, and a state flag (fresh, touched, mitigated).
- On each new zone, push to the array and create a box; if the array exceeds the maximum you allow, delete the oldest box and remove it from the array.
- On each bar, loop through live zones and update the box's right edge so it extends to the current bar, which is what makes the zone look like it "projects forward".
- Keep the loop cheap: only zones in the fresh or touched state need checking, and only against the current bar's high and low.
A readable default is 20 to 40 zones per type. Beyond that the chart is unreadable and the script risks the object cap.
When is an order block or FVG "mitigated"?
Mitigation is the rule that stops a zone being treated as live. Common choices, in order of strictness:
- Touch: price has traded into any part of the zone. Good for marking "retest happened" but too loose to retire a zone.
- Fifty percent fill: price has reached the midpoint. The usual compromise.
- Full fill: price has traded through the far edge, which for an FVG means the gap is closed.
- Close through: a bar has closed beyond the far edge, which invalidates the zone entirely.
For a strategy that enters on the retest, the entry is the touch and the invalidation is the close-through. The state flag in the zone store carries the zone from fresh to touched to mitigated, and the box colour can change at each step so the chart tells the story.
Can a Pine Script order block strategy actually be backtested fairly?
This is the question most order block indicators avoid, and it is where my project spent most of its time. A zone-retest strategy has a nasty property: a single giant zone (born from a huge bar) can stay live for months, hold a position open through anything, and dominate the backtest statistics. Two engineering rules fix that: cap the risk per trade in ATR terms rather than using the zone edge as the stop when the zone is huge, and add a time stop so no position outlives its thesis.
There is also a Pine-specific trap I found the hard way. If your strategy calls cancel-all after close-all at an end-of-day or time exit, the cancel kills the close order itself and the exit silently never fires. The tester shows positions carried that your logic claims it closed. Order the calls the other way, or close and cancel on separate bars. For a broader list of silent-exit and repaint problems, my Pine Script to production workflow covers the ones that have cost me money.
How I built this: FVG + Order-Block Retest Strategy with Honest Regime Filtering
The FVG and order-block retest strategy started as a simple question: does a retest strategy on Nifty and BankNifty futures have any measurable edge? The workflow was:
- Data extraction. Nifty and BankNifty bars were pulled from the live chart over the Chrome DevTools Protocol so the research used the same data the Strategy Tester sees.
- Pine strategy. Detection, zone store and mitigation exactly as described above, with entries on first touch, stops beyond the zone capped in ATR terms, targets at a fixed reward multiple and a time stop.
- Python replication. The same logic in a NumPy engine, validated against the TradingView Strategy Tester on 318 positions. It matched within 0.2 percent, but only once fills mirrored the tester's path rules for which of stop or target is hit first inside a bar.
- Parameter sweep. Honest in-sample selection on the core parameters failed the holdout on all four symbol-timeframe datasets, and rolling walk-forward was mostly negative. The plain strategy was curve-fit.
- Regime filters. A second round added session VWAP, EMA, ADX, ATR, time-window and direction filters, 1.75 million combinations in total. One family survived: long-only afternoon entries above session VWAP on Nifty 15-minute, with training and holdout profit factors that agree and 89 percent of neighbouring variants profitable out of sample.
- Null baseline. A matched random strategy (random afternoon longs above VWAP) captured roughly half of that edge, which is reported next to the result rather than hidden. BankNifty never reached the bar on any setting.
- Ship the defaults. The Pine defaults were set to the surviving setting and attached to the chart.
The design choice I care most about is step six. An order block strategy that reports a profit factor without a null baseline is not telling you how much of the result is drift. The outcome is a strategy whose claims are modest, specific and reproducible, and two documented traps (giant zones and cancel-after-close) that every zone-based Pine strategy should check for.
FAQ
Should order blocks use the candle body or the full wick range?
The body is tighter and gives cleaner retests on indices; the full range catches more touches but produces wider stops. Make it an input and test both on your market. In my sweep, neither choice alone separated winners from losers; the regime filter did.
Why does my FVG indicator show gaps that disappear?
You are detecting on the live bar. Shift detection to confirmed bars so a gap is only drawn once the third candle has closed. If you also use a higher timeframe through a security request, fetch the previous completed candle's values with lookahead off.
How many zones should an indicator keep?
Twenty to forty per type is readable and safe under object limits. More than that and the chart becomes noise; fewer and recent zones get deleted while still relevant on higher timeframes.
Do order blocks and FVGs work on Indian indices?
As a plain retest strategy, my data says no on four datasets. With a strict regime filter, one narrow setting on Nifty 15-minute held up out of sample, and half of its edge is explained by drift. That is the honest range of the answer.
If you want zone detection or a properly tested retest strategy built for your market, tell me about it on the contact page.
Building something in this space? I ship trading automation, Pine Script, MQL5 EAs, and full-stack tools with source and docs.
Start a project →