Indicator · Pine Script v6 · On the chart

TehThomas - Aligned Timeframe Fair Value Gaps

by MeridianAlgo· code credited to TehThomas

This indicator automatically maps the current chart timeframe to an aligned higher timeframe to detect and draw ICT-style Fair Value Gaps (FVGs).

View on GitHub

GitHub stars9on the repository
Pine versionv6current
Lines of code292
Settings14inputs
LicenceMIT
Last update4 months ago29 Jun 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 indicator, select all of the template text and paste. Press Save and give it a name.
  4. 4Add it to the chartPress Add to chart, then adjust the settings with the gear icon next to its name.

Source Code

292 lines of Pine Script v6 from examples/TehThomas_-_Aligned_Timeframe_Fair_Value_Gaps.pine, published under MIT. Keep the author's credit and licence lines when you reuse it.

TehThomas_-_Aligned_Timeframe_Fair_Value_Gaps.pine
// This Pine Script® code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © TehThomas


// ==========================================
// Indicator: Aligned Timeframe Fair Value Gaps
// Author: @TehThomas
// Description: Shows Fair Value Gaps on aligned timeframes, ICT style.
// ==========================================


//@version=6
indicator("[TehThomas] - Aligned Timeframe Fair Value Gaps", overlay=true, max_boxes_count=500, max_lines_count=500, max_bars_back = 2000)


// === Timeframe Alignment Function ===
getAlignedTimeframe() =>
    string result = ""
    if timeframe.in_seconds() <= 60  // <= 1 min
        result := "15"  // 15 min
    else if timeframe.in_seconds() <= 180  // <= 3 min
        result := "30"  // 30 min
    else if timeframe.in_seconds() <= 300  // <= 5 min
        result := "60"  // 1 hour
    else if timeframe.in_seconds() <= 900  // <= 15 min
        result := "240"  // 4 hour
    else if timeframe.in_seconds() <= 3600  // <= 1 hour
        result := "D"  // Daily
    else if timeframe.in_seconds() <= 14400  // <= 4 hour
        result := "W"  // Weekly
    else
        result := "M"  // Monthly
    result


// === Get Display Name for Timeframe ===
getTimeframeDisplay(tf) =>
    string result = ""
    if tf == "15"
        result := "15M"
    else if tf == "30"
        result := "30M"
    else if tf == "60"
        result := "1H"
    else if tf == "240"
        result := "4H"
    else if tf == "D"
        result := "Daily"
    else if tf == "W"
        result := "Weekly"
    else if tf == "M"
        result := "Monthly"
    else
        result := tf
    result


alignedTF = getAlignedTimeframe()
tfDisplay = getTimeframeDisplay(alignedTF)


// === User Inputs ===
extendBars   = input.int(20, "Extend boxes", minval=0, group = "Boxes")
Gapsize      = input.float(0, "Min Gap Size (%)", minval=0, step=0.1, group = "Boxes", tooltip = "Set the percentage size of how big the fvg should be")
dynamicGaps  = input.bool(false, "Dynamic (shrinking) gaps", group="Boxes")
maxGaps      = input.int(10, "Max Unfilled Gaps to Display", minval=1, group="Boxes")
maxHistory   = input.int(50, "Max FVGs in History", minval=50, maxval=200, group="Boxes", tooltip="Higher = more unfilled gaps available, but slower performance")
bullColor    = input.color(color.new(#47f7414d, 85), "Bullish Gap Color", group = "Colors")
bearColor    = input.color(#fa7b7b26, "Bearish Gap Color", group = "Colors")
gapBorderColor = input.color(color.black, "Gap Border Color", group = "Colors")
showMidline  = input.bool(true, "Show Midline", group = "Midline")
midlineColor = input.color(color.new(color.black, 0), "Midline Color", group = "Midline")
midlineStyleOpt = input.string("Dotted", "Midline Style", options=["Solid", "Dashed", "Dotted"], group = "Midline")


// === Label Inputs ===
showLabel    = input.bool(true, "Show FVG Label", group = "Label")
labelColor   = input.color(color.new(color.black, 0), "Label Text Color", group = "Label")
labelSize    = input.string("Small", "Label Size", options=["Tiny", "Small", "Normal", "Large"], group = "Label")


midlineStyle = midlineStyleOpt == "Solid"  ? line.style_solid :
               midlineStyleOpt == "Dashed" ? line.style_dashed :
               line.style_dotted


textSize = labelSize == "Tiny"   ? size.tiny   :
           labelSize == "Small"  ? size.small  :
           labelSize == "Normal" ? size.normal :
           size.large


// === FVG Type ===
type FVG
    float top
    float bot
    bool Bullish
    int created_bar
    int start_bar
    bool triggered
    box fvgBox
    line midLine
    bool isFilled      // Filled by price
    bool isVisible     // Currently displayed


// === Arrays for tracking ===
var FVG[] fvg_records = array.new<FVG>()


// === Get HTF OHLC Data - Uses [1] offset to only use confirmed/closed HTF bars ===
[htfLow, htfHigh, htfLow2, htfHigh2, htfTime] = request.security(syminfo.tickerid, alignedTF, [low[1], high[1], low[3], high[3], time[1]], lookahead=barmerge.lookahead_on)


// Detect new HTF bar
htfChanged = ta.change(htfTime) != 0


// === Track HTF bar starts ===
var int htfBarStart = na
var int htfBarStart1 = na
var int htfBarStart2 = na
var int htfBarStart3 = na


if htfChanged
    htfBarStart3 := htfBarStart2
    htfBarStart2 := htfBarStart1
    htfBarStart1 := htfBarStart
    htfBarStart := bar_index


// === FVG Detection ===
threshold = Gapsize / 100
bullFVG = htfChanged and htfLow > htfHigh2 and ((htfLow - htfHigh2) / htfHigh2 > threshold)
bearFVG = htfChanged and htfHigh < htfLow2 and ((htfLow2 - htfHigh) / htfHigh > threshold)


// === Variables for alert conditions ===
bullFVG_entry = false
bearFVG_entry = false


// === UPDATE: Update existing FVGs ===
i = 0
while i < array.size(fvg_records)
    get = array.get(fvg_records, i)
    
    // Skip if already filled by price
    if get.isFilled
        i += 1
        continue
    
    right_edge = bar_index + extendBars
    top = get.top
    bot = get.bot
    changed = false
    shouldMarkFilled = false
    
    // Alert only if price enters gap after creation bar and not triggered yet
    if not get.triggered and bar_index > get.created_bar
        inGap = (low <= top and high >= bot)
        if inGap
            if get.Bullish
                bullFVG_entry := true
            else
                bearFVG_entry := true
            get := FVG.new(top, bot, get.Bullish, get.created_bar, get.start_bar, true, get.fvgBox, get.midLine, get.isFilled, get.isVisible)
            array.set(fvg_records, i, get)
    
    // Shrinking / removal logic
    if dynamicGaps
        if get.Bullish
            if low < top
                top := math.max(low, bot)
                changed := true
            if top <= bot
                shouldMarkFilled := true
        else
            if high > bot
                bot := math.min(high, top)
                changed := true
            if bot >= top
                shouldMarkFilled := true
    else
        if get.Bullish and low <= bot
            shouldMarkFilled := true
        if not get.Bullish and high >= top
            shouldMarkFilled := true
    
    if shouldMarkFilled
        // Mark as filled
        if get.isVisible
            box.delete(get.fvgBox)
            if showMidline and not na(get.midLine)
                line.delete(get.midLine)
        get := FVG.new(top, bot, get.Bullish, get.created_bar, get.start_bar, get.triggered, na, na, true, false)
        array.set(fvg_records, i, get)
        i += 1
        continue
    
    if changed
        if get.isVisible
            box.set_top(get.fvgBox, top)
            box.set_bottom(get.fvgBox, bot)
        get := FVG.new(top, bot, get.Bullish, get.created_bar, get.start_bar, get.triggered, get.fvgBox, get.midLine, get.isFilled, get.isVisible)
        array.set(fvg_records, i, get)
    
    // Update box/line only if visible
    if get.isVisible
        box.set_right(get.fvgBox, right_edge)
        if showMidline and not na(get.midLine)
            mid = (top + bot) / 2
            line.set_x2(get.midLine, right_edge)
            line.set_y1(get.midLine, mid)
            line.set_y2(get.midLine, mid)
            line.set_style(get.midLine, midlineStyle)
    i += 1


// === Add new FVG ===
addFVG(isBull, top, bot, color, startBar) =>
    fvgBox = box.new(left=startBar, top=top, right=bar_index+extendBars, bottom=bot, bgcolor=color, border_color=gapBorderColor)
    
    if showLabel
        box.set_text(fvgBox, tfDisplay + " FVG")
        box.set_text_color(fvgBox, labelColor)
        box.set_text_size(fvgBox, textSize)
        box.set_text_halign(fvgBox, text.align_center)
        box.set_text_valign(fvgBox, text.align_center)
    
    mid = (top + bot) / 2
    midLine = showMidline ? line.new(startBar, mid, bar_index+extendBars, mid, xloc.bar_index, color=midlineColor, style=midlineStyle) : na
    
    newFvg = FVG.new(top, bot, isBull, bar_index, startBar, false, fvgBox, midLine, false, true)
    fvg_records.unshift(newFvg)
    
    // Keep max history
    if array.size(fvg_records) > maxHistory
        oldFvg = array.pop(fvg_records)
        if not oldFvg.isFilled and oldFvg.isVisible
            box.delete(oldFvg.fvgBox)
            if showMidline and not na(oldFvg.midLine)
                line.delete(oldFvg.midLine)


// === Detect and Add New FVGs - Box starts at htfBarStart2 (where gap actually formed) ===
if bullFVG and not na(htfBarStart2)
    addFVG(true, htfLow, htfHigh2, bullColor, htfBarStart2)
if bearFVG and not na(htfBarStart2)
    addFVG(false, htfLow2, htfHigh, bearColor, htfBarStart2)


// === Count unfilled and manage visibility ===
unfilledCount = 0
i2 = 0
while i2 < array.size(fvg_records)
    get2 = array.get(fvg_records, i2)
    
    // Only count unfilled gaps
    if not get2.isFilled
        unfilledCount += 1
        
        // Within top maxGaps: ensure visible
        if unfilledCount <= maxGaps
            if not get2.isVisible
                // Recreate box and line
                color = get2.Bullish ? bullColor : bearColor
                newBox = box.new(left=get2.start_bar, top=get2.top, right=bar_index+extendBars, bottom=get2.bot, bgcolor=color, border_color=gapBorderColor)
                
                if showLabel
                    box.set_text(newBox, tfDisplay + " FVG")
                    box.set_text_color(newBox, labelColor)
                    box.set_text_size(newBox, textSize)
                    box.set_text_halign(newBox, text.align_center)
                    box.set_text_valign(newBox, text.align_center)
                
                mid = (get2.top + get2.bot) / 2
                newLine = showMidline ? line.new(get2.start_bar, mid, bar_index+extendBars, mid, xloc.bar_index, color=midlineColor, style=midlineStyle) : na
                
                get2 := FVG.new(get2.top, get2.bot, get2.Bullish, get2.created_bar, get2.start_bar, get2.triggered, newBox, newLine, get2.isFilled, true)
                array.set(fvg_records, i2, get2)
        else
            // Outside top maxGaps: hide
            if get2.isVisible
                box.delete(get2.fvgBox)
                if showMidline and not na(get2.midLine)
                    line.delete(get2.midLine)
                get2 := FVG.new(get2.top, get2.bot, get2.Bullish, get2.created_bar, get2.start_bar, get2.triggered, na, na, get2.isFilled, false)
                array.set(fvg_records, i2, get2)
    
    i2 += 1

Overview

TehThomas - Aligned Timeframe Fair Value Gaps identifies bullish and bearish price imbalance zones and displays them as colored overlay boxes directly on the price chart. Users can customize minimum gap size percentages, maximum active gaps, historical gap limits, box extensions, dynamic gap-shrinking behavior, and optional midlines with labels.

Details

Published by

MeridianAlgo on GitHub

Code credit

TehThomas, as written in the script's header

Type

Indicator, Pine Script v6

Draws

On the price chart

Category

SMC / ICT / price action, Support & resistance

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

code-converterconverterjavascriptpinescripttranspilercode-reviewmeridianalgocode-conversionpinescript-algorithms

Settings

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

SettingTypeDefaultVariable
Extend boxesint20extendBars
Min Gap Size (%)float0Gapsize
Dynamic (shrinking) gapsboolfalsedynamicGaps
Max Unfilled Gaps to Displayint10maxGaps
Max FVGs in Historyint50maxHistory
Bullish Gap Colorcolorcolor.new(#47f7414d, 85)bullColor
Bearish Gap Colorcolor#fa7b7b26bearColor
Gap Border Colorcolorcolor.blackgapBorderColor
Show MidlinebooltrueshowMidline
Midline Colorcolorcolor.new(color.black, 0)midlineColor
Midline StylestringDottedmidlineStyleOpt
Show FVG LabelbooltrueshowLabel
Show all 14 settings
SettingTypeDefaultVariable
Label Text Colorcolorcolor.new(color.black, 0)labelColor
Label SizestringSmalllabelSize
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

ICT Equal Highs and Lows

by ArunKBhaskar
575 stars

This overlay indicator finds pivot highs and lows on the price chart and compares the last two highs and the last two lows. When the percentage difference between a pair is below the Equal HL threshold, it treats them as equal levels and draws a box. Left and

Indicator v5 SMC / ICT / price actionSupport & resistance
Updated 1 year ago · MPL-2.0 GitHub repository of ICT Equal Highs and Lows

DE-tracker

by Allen1212
303 stars

This indicator plots labels on the TradingView price chart to mark ARK Invest's buy and sell trades for the DE stock. Buy transactions appear in green with X1 or X2 multipliers showing the number of ARK ETFs involved on the same day; sells appear in red. The m

Indicator v4 Support & resistanceSMC / ICT / price action
Updated 2 years ago · None stated GitHub repository of DE-tracker

This Pine Script indicator plots a ZigZag on the price chart. It identifies reversal points when price moves more than a set percentage from the last pivot. The script calculates forecasts using zigzag data and labels detected geometric patterns including harm

Indicator v4 TrendSupport & resistance

Golden Bullet Strategy

by TradersPost
218 stars

The Golden Bullet Strategy is an overlay signal indicator based on Smart Money Concepts and time-based liquidity models. It identifies liquidity sweeps of a designated reference session range during a specified hunt window. It then confirms Market Structure Sh

Indicator v6 SMC / ICT / price actionSessions & time
Updated 3 months ago · None stated GitHub repository of Golden Bullet Strategy

Open Source Fractal

by cantolab
183 stars

This indicator implements the Fractal Model concept to plot market structure shifts, liquidity sweeps, and Change in State of Delivery (CISD) levels. It overlays custom higher-timeframe candle previews, open prices, and time markers directly on the price chart

Indicator v6 SMC / ICT / price actionSupport & resistance
Updated 2 months ago · MIT GitHub repository of Open Source Fractal

Swing Failure Signals

by Ganador1
154 stars

This indicator calculates pivot highs and pivot lows to detect liquidity sweeps and Swing Failure Patterns. It identifies when price breaks past recent key swing highs or lows within a configurable historical window, then looks for a quick reversal or change i

Indicator v6 SMC / ICT / price actionSupport & resistance
Updated 2 months ago · Apache-2.0 GitHub repository of Swing Failure Signals

More indicators

Trade Your Charts With a Regulated Broker

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

Compare brokers