Range Sweeps and Fair Value Gaps: A Beginner’s Trading Framework

Video: How To Day Trade: The Ultimate Beginner Guide
Creator: TradingLab
Original Video: Watch the original video
Difficulty: Beginner

The Big Idea

TradingLab presents a repeating sequence: a sideways range, a break beyond one edge, an opposing impulse that forms a fair value gap, and a return to that gap. The proposed trade joins the opposing move after the initial range break fails.

The lifestyle and portfolio claims in the introduction are the creator’s account, not evidence that a beginner should expect similar results. The useful starting point is a chart sequence you can define and investigate.

If You Remember Only One Thing

A sweep followed by a gap is a candidate sequence; the gap must be confirmed and the return must still offer acceptable risk.

60-Second Summary

  • Mark the sideways range before the break.
  • A break outside the range can continue instead of reverse.
  • Confirm the opposing three-candle gap before treating it as available.
  • Specify the entry location and order expiry rather than assuming every touch fills.
  • Separate the source’s institutional explanation from observable price relationships.

Understand the Four Parts

PartVisible observationQuestion to answer
RangePrice moves sideways between identifiable boundariesWere the boundaries marked before the break?
SweepPrice trades beyond an edgeDoes the break fail or continue?
Opposing impulsePrice moves back the other way and leaves a three-candle gapIs the formation complete?
ReturnPrice revisits the gapIs an entry still justified by the stop and target?

The source calls the phases accumulation, manipulation, and distribution. Use those labels carefully. Candles show prices and timing; they do not establish that a particular institution engineered the move or reveal its inventory.

Define a Fair Value Gap Precisely

In a bullish three-candle formation, the third candle’s low is above the first candle’s high. The intervening price interval is the gap being discussed. In the bearish version, the third candle’s high is below the first candle’s low.

The middle candle usually supplies the strong directional move in this explanation. The gap becomes confirmed only when the third candle closes. An apparent gap can disappear while that candle is still forming. Its existence also does not prove that no transactions occurred in the interval during the middle candle.

The Proposed Long and Short

For the long example, first identify the sideways range. Price breaks below it, then moves upward and forms a bullish gap. The creator waits for a return to that gap, uses protection below the sweep, and looks toward a previous high.

The short reverses the sequence: a range-high break, a bearish impulse with a confirmed gap, then a return upward into the gap. Protection goes beyond the sweep high, with a prior low as a possible target.

A break alone is insufficient. A market can leave a range and continue. Even after an opposing gap forms, a revisit can fail, overshoot the zone, or never occur.

Turn the Idea Into Rules Before Testing

The transcript does not fix how many bars define a range, its maximum width, the entry price within the gap, or how long an unfilled entry remains valid. Those choices materially change results. Decide whether a touch is sufficient or whether a completed rejection candle is required.

A fair test also needs a minimum reward-to-risk ratio, a rule for gaps that fill before an order could be placed, and a consistent definition of the prior high or low used as a target. Do not select whichever historical reference makes each completed trade look best.

Check the Actual Reward

Suppose a hypothetical long entry is 50, the sweep-based stop is 49, and a previously known high is 52. Planned reward is 2R before costs. If waiting for confirmation moves entry to 51 while those other levels stay fixed, the trade offers only 0.5R.

Confirmation may improve selectivity while worsening price. It is a tradeoff to measure. Size the position using the stop distance and instrument value, with allowance for execution costs. A stop does not guarantee its requested execution price.

Review Beyond the Winning Examples

The video illustrates winning trades and suggests using larger timeframes such as four-hour and daily charts. Results from those charts do not establish an intraday edge. Collect every qualifying sequence on one market and timeframe, including failed returns and unfilled orders.

Track net expectancy, average win and loss, drawdown, costs, and how often the setup appears. Practice execution in simulation and distinguish chart recognition from realistic fills. The indicator below labels its added mechanical definitions explicitly rather than silently presenting them as the creator’s exact rules.

A Three-Candle Example You Can Audit

In this original example, an earlier range runs from 100 to 104. Price briefly trades below 100 to 99. A later bullish sequence has a first-candle high of 100.40 and a third-candle low of 101.10. After that third candle closes, the interval from 100.40 to 101.10 is the confirmed bullish gap for this example.

Entering at the midpoint would mean 100.75, but midpoint entry is an added research choice, not a universal instruction in the source. With protection at 98.90 and a target at the pre-existing range high of 104, planned reward is 3.25 against risk of 1.85, or about 1.76R before costs.

If price never returns to the chosen entry, the result is an unfilled opportunity. If the gap was touched before its third candle closed, that earlier touch is not a valid fill for an order created after confirmation. This distinction separates a visible chart pattern from an executable historical trade.

Research Exercise: Which Entry Rule Survives?

Choose one market, one timeframe, and a contiguous historical interval. Define a range in writing before collecting cases—for example, a fixed 12-bar interval with width no greater than twice an ATR value known before the break. This is an original approximation, not the creator’s range definition.

Use the same qualifying ranges, sweep definition, stop beyond the sweep, target reference, and transaction-cost assumptions for every version. Compare three entries: the nearest gap boundary, its midpoint, and a confirmed rejection close after a return. Give unfilled orders the same predeclared expiry, such as five bars after gap confirmation.

Record eligible setups, filled trades, expired orders, net R per filled trade, and net R per eligible setup. Suppose version A earns 8R over ten filled trades from 20 setups, while version B earns 9R over 18 fills from the same 20 setups. A looks stronger per trade at 0.8R versus 0.5R, but B contributes more per original opportunity at 0.45R versus 0.4R. These fictional totals show why both denominators matter.

Use a later untouched interval to evaluate the chosen definitions. Do not select the best entry retrospectively for each individual example. A consistent rule that misses some moves is more informative than a perfect collection assembled after the outcome.

Five Questions Before Calling It a Setup

Was the range known before the sweep? Was the gap complete before order submission? Is the chosen entry still ahead of the stop and target in the intended order? Was a plausible fill available after submission? Have losing and unfilled sequences been retained? If any answer is unclear, record the uncertainty rather than counting a convenient winner.

Pine Script Indicator: Range Sweep, Gap, and Retest

Show Pine Script v6 — Range Sweep FVG Indicator

//@version=6
// https://candlechasers.com/range-sweep-fair-value-gap-framework/
// Original mechanical approximation; chart events only, no trading orders.
indicator("CandleChasers - Range Sweep FVG", overlay=true, max_boxes_count=100)
rangeBars = input.int(12, "Pre-sweep range bars", minval=3, maxval=200)
atrLen = input.int(14, "ATR length", minval=2, maxval=100)
filterWidth = input.bool(false, "Require range width / ATR limit")
maxWidth = input.float(4.0, "Maximum range width in prior ATR", minval=0.5, step=0.5)
gapWait = input.int(8, "Maximum bars from sweep to gap confirmation", minval=2, maxval=50)
gapLife = input.int(30, "Gap lifetime after confirmation", minval=1, maxval=300)
gapTicks = input.int(1, "Minimum gap width in ticks", minval=1, maxval=100)
bodyATR = input.float(0.0, "Middle-candle minimum body / prior ATR", minval=0, step=0.1)
buffer = input.int(1, "Sweep invalidation buffer in ticks", minval=1, maxval=100)
keep = input.int(20, "Maximum historical gap boxes", minval=1, maxval=80)
if not chart.is_standard
    runtime.error("Use standard price candles.")
float rollingHigh = ta.highest(high, rangeBars)[1]
float rollingLow = ta.lowest(low, rangeBars)[1]
float atr = ta.atr(atrLen)
bool ready = bar_index >= rangeBars and not na(rollingHigh) and not na(rollingLow)
bool widthOK = not filterWidth or (not na(atr[1]) and
 rollingHigh - rollingLow <= maxWidth * atr[1])
// 0 scanning; 1 waiting for gap; 2 gap ready; 3 retested;
// 4 rejection signalled; 5 invalidated/ambiguous; 6 expired.
var int stage = 0
var int side = 0
var int sweepBar = na
var int gapBar = na
var float frozenHigh = na
var float frozenLow = na
var float extreme = na
var float stopRef = na
var float gapTop = na
var float gapBottom = na
var box currentBox = na
var array<box> boxes = array.new<box>()
var int sweeps = 0
var int gaps = 0
var int retests = 0
var int rejections = 0
var int ambiguousBars = 0
bool sweepEvent = false
bool gapEvent = false
bool retestEvent = false
bool rejectEvent = false
bool ambiguous = false
if barstate.isconfirmed
    // Process an existing setup before looking for another one.
    if stage == 1 and bar_index > sweepBar
        if bar_index - sweepBar > gapWait
            stage := 6
        else
            extreme := side == 1 ? math.min(extreme, low) : math.max(extreme, high)
            bool enoughBars = bar_index - sweepBar >= 2
            bool middleDirection = side == 1 ? close[1] > open[1] : close[1] < open[1]
            bool bodyOK = bodyATR == 0 or (not na(atr[2]) and
             math.abs(close[1] - open[1]) >= bodyATR * atr[2])
            bool formation = side == 1 ?
             low - high[2] >= gapTicks * syminfo.mintick :
             low[2] - high >= gapTicks * syminfo.mintick
            bool reclaimed = side == 1 ? close > frozenLow : close < frozenHigh
            if enoughBars and middleDirection and bodyOK and formation and reclaimed
                gapTop := side == 1 ? low : low[2]
                gapBottom := side == 1 ? high[2] : high
                stopRef := extreme - side * buffer * syminfo.mintick
                gapBar := bar_index
                stage := 2
                gapEvent := true
                gaps += 1
                color col = side == 1 ? color.lime : color.red
                // Draw from confirmation time, never back on the first gap candle.
                currentBox := box.new(left=bar_index, top=gapTop,
                 right=bar_index + 1, bottom=gapBottom,
                 border_color=col, bgcolor=color.new(col, 85))
                array.push(boxes, currentBox)
                while array.size(boxes) > keep
                    box.delete(array.shift(boxes))
    // Retests must occur strictly AFTER gap confirmation.
    if (stage == 2 or stage == 3 or stage == 4) and bar_index > gapBar
        bool invalid = side == 1 ? low <= stopRef : high >= stopRef
        if invalid
            stage := 5
        else if bar_index - gapBar > gapLife
            stage := 6
        else
            bool overlap = low <= gapTop and high >= gapBottom
            if stage == 2 and overlap
                stage := 3
                retestEvent := true
                retests += 1
            bool rejection = side == 1 ? close > open and close > gapTop :
             close < open and close < gapBottom
            if stage == 3 and rejection
                stage := 4
                rejectEvent := true
                rejections += 1
    if not na(currentBox) and stage >= 2
        box.set_right(currentBox, bar_index + 1)
        if stage == 5 or stage == 6
            box.set_bgcolor(currentBox, color.new(color.gray, 93))
            box.set_border_color(currentBox, color.new(color.gray, 60))
            // Leave the historical box fixed after this bar.
            currentBox := na
    bool available = stage == 0 or stage == 5 or stage == 6
    if available and ready and widthOK
        bool lowSweep = low < rollingLow and close[1] >= rollingLow
        bool highSweep = high > rollingHigh and close[1] <= rollingHigh
        ambiguous := lowSweep and highSweep
        if ambiguous
            ambiguousBars += 1
        else if lowSweep or highSweep
            side := lowSweep ? 1 : -1
            frozenHigh := rollingHigh
            frozenLow := rollingLow
            extreme := lowSweep ? low : high
            sweepBar := bar_index
            gapBar := na
            gapTop := na
            gapBottom := na
            stopRef := na
            stage := 1
            sweepEvent := true
            sweeps += 1
plot(rollingHigh, "Pre-bar rolling range high", color=color.new(color.blue, 60))
plot(rollingLow, "Pre-bar rolling range low", color=color.new(color.blue, 60))
plot(stage != 0 ? frozenHigh : na, "Frozen setup range high",
 color=color.orange, style=plot.style_stepline)
plot(stage != 0 ? frozenLow : na, "Frozen setup range low",
 color=color.orange, style=plot.style_stepline)
plot(stopRef, "Frozen sweep invalidation reference",
 color=color.new(color.red, 30), style=plot.style_stepline)
plotshape(sweepEvent and side == 1, title="Low sweep", text="SWEEP",
 style=shape.diamond, location=location.belowbar, color=color.orange, size=size.tiny)
plotshape(sweepEvent and side == -1, title="High sweep", text="SWEEP",
 style=shape.diamond, location=location.abovebar, color=color.orange, size=size.tiny)
plotshape(gapEvent, title="Opposing gap confirmed", text="FVG",
 style=shape.square, location=location.abovebar, color=color.aqua, size=size.tiny)
plotshape(retestEvent, title="First later gap retest", text="TEST",
 style=shape.circle, location=location.belowbar, color=color.yellow, size=size.tiny)
plotshape(rejectEvent and side == 1, title="Bullish gap rejection", text="LONG?",
 style=shape.triangleup, location=location.belowbar, color=color.lime,
 textcolor=color.lime, size=size.small)
plotshape(rejectEvent and side == -1, title="Bearish gap rejection", text="SHORT?",
 style=shape.triangledown, location=location.abovebar, color=color.red,
 textcolor=color.red, size=size.small)
bgcolor(ambiguous ? color.new(color.orange, 80) : na,
 title="Both range edges swept; no direction selected")
alertcondition(gapEvent, "Opposing FVG confirmed", "A range sweep has an opposing confirmed gap.")
alertcondition(retestEvent, "Gap retested", "Price returned to a previously confirmed setup gap.")
alertcondition(rejectEvent and side == 1, "Bullish gap rejection",
 "After a low sweep and bullish FVG, a later retest rejected upward.")
alertcondition(rejectEvent and side == -1, "Bearish gap rejection",
 "After a high sweep and bearish FVG, a later retest rejected downward.")
var table info = table.new(position.top_right, 1, 3, border_width=1)
if barstate.islast
    string status = switch stage
        0 => "Scanning range edges"
        1 => "Sweep recorded; waiting for opposing FVG"
        2 => "Confirmed FVG; waiting for a later retest"
        3 => "Gap retested; waiting for directional rejection"
        4 => "Rejection signalled; setup remains until invalidation/expiry"
        5 => "Invalidated; scanning for a fresh sweep"
        => "Expired; scanning for a fresh sweep"
    if not ready
        status := "Load more history for range calculation"
    table.cell(info, 0, 0, "Range Sweep FVG | " + status,
     text_color=color.white, bgcolor=color.rgb(25, 35, 50))
    table.cell(info, 0, 1, "Sweeps: " + str.tostring(sweeps) +
     " | gaps: " + str.tostring(gaps) + " | retests: " + str.tostring(retests) +
     " | rejections: " + str.tostring(rejections),
     text_color=color.white, bgcolor=color.rgb(25, 35, 50))
    table.cell(info, 0, 2, "Range width filter: " + (filterWidth ? "ON" : "OFF") +
     " | both-edge bars skipped: " + str.tostring(ambiguousBars),
     text_color=color.white, bgcolor=color.rgb(25, 35, 50))

This original Pine Script v6 indicator makes the source’s sequence visible: a range-edge excursion, an opposing confirmed fair value gap, and a later return to the gap. It also marks an optional-style rejection event after that retest. The script’s numerical definitions are ours, not a claim that TradingLab uses these exact parameters.

How the Setup Is Defined

The range is the high and low of the preceding 12 completed bars by default, excluding the sweep candle. Blue lines show that rolling reference. A low sweep requires price below the prior low after a preceding close at or above it; a high sweep mirrors this above the range. The range freezes in orange when the sweep is recorded.

The range-width filter is off by default. Consequently, a rolling range is a reference window, not proof of sideways accumulation. Enable the filter to require width no greater than the selected multiple of prior ATR; four ATR is the starting limit. A candle sweeping both edges when a new setup is being selected is treated as ambiguous and skipped.

After a low sweep, the script waits for a bullish three-candle gap: the third candle’s low must exceed the first candle’s high by the minimum tick width, and the middle candle must be bullish. After a high sweep, it requires the bearish mirror. The first gap candle must occur on or after the sweep bar, so confirmation cannot happen until at least two bars later. The default search expires after eight bars.

At gap confirmation, price must also close back above the swept range low for a bullish setup, or below the swept range high for a bearish setup. This is a reclaim of the selected edge; it does not require the close to remain inside both boundaries. The optional middle-body ATR threshold can require stronger displacement. Its zero default applies no minimum body-size threshold beyond the required candle direction.

What You Should See

  • SWEEP diamond: a fresh range-edge excursion has been recorded.
  • FVG square and colored box: the opposing three-candle gap is confirmed. Green boxes are bullish; red boxes are bearish.
  • TEST circle: a later completed candle’s range overlaps the confirmed gap.
  • LONG? or SHORT? triangle: after the retest, a directional candle closes beyond the gap in the proposed direction. A bullish version requires a green close above the gap top; a bearish version requires a red close below its bottom.
  • Red reference: the sweep episode’s extreme, frozen when the gap confirms, with the selected tick buffer beyond it.
  • Status table: current stage and separate counts for sweeps, gaps, retests, and rejections.

A retest and directional rejection may occur on the same candle, but that candle must be later than gap confirmation. The script never counts the third formation candle as its own retest. Once retested, the setup can wait for a later rejection; no second arrow is issued for the same setup.

Install and Create Alerts

Use standard candles, paste the full code into Pine Editor, save, and add it to the chart. Load enough history for the range and ATR calculations. The indicator can be studied on intraday or higher timeframes, but those are separate experiments with different holding periods and execution costs.

Start with the width filter off to inspect the event sequence. If the table is scanning, no eligible fresh sweep has been selected. If it is waiting for a gap, the opposing formation has not qualified before expiry. If a box exists without a retest marker, price has not returned after confirmation. A box or marker is not a guaranteed fill.

Create alerts for gap confirmation, first retest, or the selected rejection direction using Once Per Bar Close. Recreate alerts after changing code or inputs. Historical markers do not trigger retrospective notifications.

State, Invalidation, and Expiry

The indicator tracks one setup at a time. While it waits for a gap, the sweep extreme can extend as further completed bars arrive. Once the gap confirms, that extreme and the invalidation reference freeze. A later touch of invalidation takes priority over a retest or rejection on the same candle.

The default gap lifetime is 30 bars after confirmation. A signalled setup remains active until invalidated or expired, so intervening sweeps do not overwrite it. This deliberately limits the opportunity sample. Ended boxes turn gray and stop extending. The display retains up to 20 historical boxes by default; removing an old box does not remove its historical event count.

What This Code Actually Tests

It recognizes one explicit chart sequence inspired by the source. Our added definitions cover rolling-range selection, sweep timing, gap width, the middle candle, the reclaim requirement, retest timing, rejection, expiry, and one-active-setup handling. The source idea and these implementation choices should be evaluated separately.

The gap box begins at its confirmation bar, not two candles earlier. All setup transitions and alerts wait for completed bars, and the script requests no future or higher-timeframe data. Prior plots and markers are not moved backward when later confirmation arrives.

What This Code Does Not Test

This is an indicator, so it places no orders and produces no Strategy Tester trades. It does not establish institutional manipulation, choose a tradable profit target, calculate position size, or model costs. The opposite frozen range edge is a chart reference, not a guaranteed destination; if it is behind the eventual entry, it cannot serve as a forward target for that trade.

The gap can be penetrated without invalidating the setup if the frozen sweep reference remains intact. Opening gaps and different session settings can also create different formations. Review those cases rather than assuming every displayed gap represents the same market behavior.

Offline checks covered gap direction and equality, confirmation timing, later retests, rejection rules, invalidation priority, expiry, bullish/bearish mirrors, and prefix consistency. The script has not been compiled or run inside TradingView here. Use replay to inspect the stage sequence before relying on alerts.

Useful Follow-Up Comparisons

Compare range windows of 8, 12, and 20 bars; middle-body thresholds of zero, 0.5, and 1 prior ATR; or gap lifetimes of 10, 30, and 60 bars. Change one input at a time on the same market and date interval. Record original sweeps, qualified gaps, retests, expired setups, and invalidations, not just attractive rejection arrows. More markers do not by themselves mean a stronger method.

Final Takeaway

Learn the sequence before attempting to trade it: range, failed break, confirmed opposing gap, and a return with acceptable risk. A recognizable pattern is the beginning of a test, not proof of profitability.

Risk Disclosure

This article is for educational purposes only and is not financial advice or a recommendation to buy or sell any security. Trading involves substantial risk, including loss of capital. Leverage can magnify losses, and stop orders do not guarantee an execution price. Examples and historical results do not predict future performance. Account for fees, spread, slippage, and your own financial circumstances before trading.

CandleChasers is independent of the video creator. This article provides educational analysis of the source material; it does not verify the creator’s earnings or endorse paid products.

As an Amazon Associate I earn from qualifying purchases.