Indicator · Pine Script v6 · On the chart

NXOG + FVG

by TavaresBugs

The indicator detects and plots Fair Value Gaps across multiple timeframes on the price chart, including 15 minutes, 1 hour, 4 hours, daily, weekly, and monthly.

View on GitHub

GitHub stars1on the repository
Pine versionv6current
Lines of code1,339
Settings40inputs
LicenceMIT
Last update9 months ago20 Jan 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

1,339 lines of Pine Script v6 from indicator.pine, published under MIT. Keep the author's credit and licence lines when you reuse it.

indicator.pine
// ========================================
// NXOG - New X Opening Gaps + FVG Multi-TF
// NWOG: Sex (close) → Seg (open) - Semanal
// NDOG: Dia Ant (close) → Dia Atual (open) - Diário
// NMOG: Mês Ant (close) → Mês Atual (open) - Mensal
// FVG: Fair Value Gap com filtro de sobreposição NXOG
// ========================================

//@version=6
indicator('NXOG + FVG', overlay = true, max_boxes_count = 500, max_labels_count = 200)

// ========== CONFIGURAÇÕES GERAIS ==========
var g_general = 'Configurações Gerais'
showLabels      = input.bool(true, 'Texto dentro da BOX', group = g_general)
boxTransparency = input.int(85, 'Transparência dos Boxes', minval = 0, maxval = 100, group = g_general)

var g_mitig = 'Mitigação'
mitig_pct_threshold    = input.int(50, 'Mitigando em X %', minval = 1, maxval = 100, group = g_mitig)
mitig_use_close        = input.bool(false, 'Usar o corpo ao invés do pavio', group = g_mitig)
deleteAllMitigatedNXOG = input.bool(false, 'Remover TODOS Gaps (NXOG) mitigados', group = g_mitig)
deleteAllMitigatedFVG  = input.bool(true, 'Remover TODOS FVGs mitigados', group = g_mitig)

// Configuração Hardcoded
nxog_min_gap = 0.01  // % mínimo do gap

// ========== NDOG ==========
var g_ndog = 'NDOG (Diário)'
enable_ndog      = input.bool(true, 'Mostrar NDOG', tooltip = 'New Day Opening Gap (Ontem → Hoje)', group = g_ndog)
deleteFilledNDOG = input.bool(false, 'Remover NDOGs mitigados', group = g_ndog)
max_ndog         = input.int(5, 'Máx NDOG', minval = 1, maxval = 10, group = g_ndog)
color_ndog_up    = input.color(color.new(color.lime, 75), 'Cor Alta', group = g_ndog)
color_ndog_dn    = input.color(color.new(color.lime, 75), 'Cor Baixa', group = g_ndog)

// ========== NWOG ==========
var g_nwog = 'NWOG (Semanal)'
enable_nwog      = input.bool(true, 'Mostrar NWOG', tooltip = 'New Week Opening Gap (Sex → Seg)', group = g_nwog)
deleteFilledNWOG = input.bool(false, 'Remover NWOGs mitigados', group = g_nwog)
max_nwog         = input.int(3, 'Máx NWOG', minval = 1, maxval = 10, group = g_nwog)
color_nwog_up    = input.color(color.new(color.teal, 75), 'Cor Alta', group = g_nwog)
color_nwog_dn    = input.color(color.new(color.teal, 75), 'Cor Baixa', group = g_nwog)

// ========== NMOG ==========
var g_nmog = 'NMOG (Mensal)'
enable_nmog      = input.bool(true, 'Mostrar NMOG', tooltip = 'New Month Opening Gap (Mês Ant. → Novo)', group = g_nmog)
deleteFilledNMOG = input.bool(false, 'Remover NMOGs mitigados', group = g_nmog)
max_nmog         = input.int(3, 'Máx NMOG', minval = 1, maxval = 10, group = g_nmog)
color_nmog_up    = input.color(color.new(color.maroon, 75), 'Cor Alta', group = g_nmog)
color_nmog_dn    = input.color(color.new(color.maroon, 75), 'Cor Baixa', group = g_nmog)

// ========== FVG 15M ==========
var g_tf15m = 'FVG 15M'
enable_15m      = input.bool(true, 'FVG 15M', group = g_tf15m)
deleteFilled15m = input.bool(true, 'Remover FVGs mitigados', group = g_tf15m)
max_fvg_15m     = input.int(8, 'Máx FVGs', minval = 1, maxval = 20, group = g_tf15m)
color_up_15m    = input.color(color.new(color.blue, 85), 'Cor FVG Alta', group = g_tf15m)
color_dn_15m    = input.color(color.new(color.blue, 85), 'Cor FVG Baixa', group = g_tf15m)

// ========== FVG 1H ==========
var g_tf1h = 'FVG 1H'
enable_1h      = input.bool(true, 'FVG 1H', group = g_tf1h)
deleteFilled1h = input.bool(true, 'Remover FVGs mitigados', group = g_tf1h)
max_fvg_1h     = input.int(6, 'Máx FVGs', minval = 1, maxval = 15, group = g_tf1h)
color_up_1h    = input.color(color.new(color.orange, 85), 'Cor FVG Alta', group = g_tf1h)
color_dn_1h    = input.color(color.new(color.orange, 85), 'Cor FVG Baixa', group = g_tf1h)

// ========== FVG 4H ==========
var g_tf4h = 'FVG 4H'
enable_4h      = input.bool(true, 'FVG 4H', group = g_tf4h)
deleteFilled4h = input.bool(true, 'Remover FVGs mitigados', group = g_tf4h)
max_fvg_4h     = input.int(5, 'Máx FVGs', minval = 1, maxval = 12, group = g_tf4h)
color_up_4h    = input.color(color.new(color.purple, 85), 'Cor FVG Alta', group = g_tf4h)
color_dn_4h    = input.color(color.new(color.purple, 85), 'Cor FVG Baixa', group = g_tf4h)

// ========== FVG 1D ==========
var g_tf1d = 'FVG 1D'
enable_1d      = input.bool(true, 'FVG 1D', group = g_tf1d)
deleteFilled1d = input.bool(true, 'Remover FVGs mitigados', group = g_tf1d)
max_fvg_1d     = input.int(4, 'Máx FVGs', minval = 1, maxval = 10, group = g_tf1d)
color_up_1d    = input.color(color.new(color.yellow, 85), 'Cor FVG Alta', group = g_tf1d)
color_dn_1d    = input.color(color.new(color.yellow, 85), 'Cor FVG Baixa', group = g_tf1d)

// ========== FVG 1W ==========
var g_tf1w = 'FVG 1W'
enable_1w      = input.bool(true, 'FVG 1W', group = g_tf1w)
deleteFilled1w = input.bool(true, 'Remover FVGs mitigados', group = g_tf1w)
max_fvg_1w     = input.int(3, 'Máx FVGs', minval = 1, maxval = 8, group = g_tf1w)
color_up_1w    = input.color(color.new(color.fuchsia, 85), 'Cor FVG Alta', group = g_tf1w)
color_dn_1w    = input.color(color.new(color.fuchsia, 85), 'Cor FVG Baixa', group = g_tf1w)

// ========== FVG 1M ==========
var g_tf1mo = 'FVG 1M'
enable_1mo      = input.bool(true, 'FVG 1M', group = g_tf1mo)
deleteFilled1mo = input.bool(true, 'Remover FVGs mitigados', group = g_tf1mo)
max_fvg_1mo     = input.int(2, 'Máx FVGs', minval = 1, maxval = 5, group = g_tf1mo)
color_up_1mo    = input.color(color.new(color.navy, 85), 'Cor FVG Alta', group = g_tf1mo)
color_dn_1mo    = input.color(color.new(color.navy, 85), 'Cor FVG Baixa', group = g_tf1mo)

// ========== ESTRUTURA DE DADOS ==========
type GapData
    float top
    float bottom
    int   start_time
    int   bar_index_start
    box   gap_box
    bool  hasRealBodyGap

// ========== ARRAYS NXOG ==========
var array<GapData> nwog_up_arr = array.new<GapData>(0)
var array<GapData> nwog_dn_arr = array.new<GapData>(0)
var array<GapData> ndog_up_arr = array.new<GapData>(0)
var array<GapData> ndog_dn_arr = array.new<GapData>(0)
var array<GapData> nmog_up_arr = array.new<GapData>(0)
var array<GapData> nmog_dn_arr = array.new<GapData>(0)

// ========== ARRAYS TIMESTAMPS JÁ PLOTADOS (evita duplicação após remoção) ==========
var array<int> plotted_nwog_times = array.new<int>(0)
var array<int> plotted_ndog_times = array.new<int>(0)
var array<int> plotted_nmog_times = array.new<int>(0)

// ========== ARRAYS FVG ==========
var array<GapData> fvgs_up_15m = array.new<GapData>(0)
var array<GapData> fvgs_dn_15m = array.new<GapData>(0)
var array<GapData> fvgs_up_1h  = array.new<GapData>(0)
var array<GapData> fvgs_dn_1h  = array.new<GapData>(0)
var array<GapData> fvgs_up_4h  = array.new<GapData>(0)
var array<GapData> fvgs_dn_4h  = array.new<GapData>(0)
var array<GapData> fvgs_up_1d  = array.new<GapData>(0)
var array<GapData> fvgs_dn_1d  = array.new<GapData>(0)
var array<GapData> fvgs_up_1w  = array.new<GapData>(0)
var array<GapData> fvgs_dn_1w  = array.new<GapData>(0)
var array<GapData> fvgs_up_1mo = array.new<GapData>(0)
var array<GapData> fvgs_dn_1mo = array.new<GapData>(0)

// ========== FUNÇÃO NWOG ==========
detectNWOG() =>
    isDayChange = ta.change(dayofmonth) != 0
    prevDayOfWeek = dayofweek[1]
    wasFriday = prevDayOfWeek == dayofweek.friday
    isFirstBarAfterWeekend = isDayChange and wasFriday

    var float persistedNwogTop = na
    var float persistedNwogBottom = na
    var int persistedNwogTime = na
    var bool persistedNwogBullish = false
    var bool persistedNwogHasRealBodyGap = false
    var int lastFridayTime = na

    fridayOpen = open[1]
    fridayClose = close[1]
    fridayTime = time[1]

    if isFirstBarAfterWeekend and not na(fridayClose) and not na(fridayOpen)
        mondayOpen = open
        mondayClose = close
        mondayHigh = high
        mondayLow = low
        gapSize = math.abs(mondayOpen - fridayClose)
        minGap = close * nxog_min_gap / 100
        // Vácuo: o candle de segunda (incluindo wicks) está completamente fora do corpo de sexta
        bodyGapUp = mondayLow > math.max(fridayOpen, fridayClose)
        bodyGapDn = mondayHigh < math.min(fridayOpen, fridayClose)
        hasRealBodyGap = bodyGapUp or bodyGapDn
        isNewFriday = na(lastFridayTime) or fridayTime > lastFridayTime

        if gapSize >= minGap and isNewFriday
            persistedNwogTop := math.max(fridayClose, mondayOpen)
            persistedNwogBottom := math.min(fridayClose, mondayOpen)
            persistedNwogTime := fridayTime
            persistedNwogBullish := mondayOpen > fridayClose
            persistedNwogHasRealBodyGap := hasRealBodyGap
            lastFridayTime := fridayTime

    hasNWOG = not na(persistedNwogTop) and not na(persistedNwogBottom)
    [persistedNwogTop, persistedNwogBottom, persistedNwogTime, persistedNwogBullish, hasNWOG, persistedNwogHasRealBodyGap]

// ========== FUNÇÃO NDOG ==========
detectNDOG() =>
    isWeekChange = ta.change(weekofyear) != 0
    isDayChange = ta.change(dayofmonth) != 0
    currentDayOfWeek = dayofweek
    prevDayOfWeek = dayofweek[1]

    isMonday = currentDayOfWeek == dayofweek.monday
    isSunday = currentDayOfWeek == dayofweek.sunday
    wasFriday = prevDayOfWeek == dayofweek.friday
    wasSaturday = prevDayOfWeek == dayofweek.saturday
    wasSunday = prevDayOfWeek == dayofweek.sunday
    isWeekendGap = wasFriday or isWeekChange or (isSunday and not wasSaturday) or (isMonday and wasSunday)

    var float persistedNdogTop = na
    var float persistedNdogBottom = na
    var int persistedNdogTime = na
    var bool persistedNdogBullish = false
    var bool persistedNdogHasRealBodyGap = false
    var bool persistedHasNDOG = false
    var bool persistedHasNWOG_masked = false
    var int persistedNdogDay = na
    
    prevDayOpen = open[1]
    prevDayClose = close[1]
    currentDay = dayofmonth
    gapAlreadyDetectedToday = not na(persistedNdogDay) and persistedNdogDay == currentDay

    if timeframe.isdaily and isWeekendGap and not na(prevDayClose) and not na(prevDayOpen) and not gapAlreadyDetectedToday
        currDayOpen = open
        currDayClose = close
        currDayHigh = high
        currDayLow = low
        persistedNdogTop := math.max(prevDayClose, currDayOpen)
        persistedNdogBottom := math.min(prevDayClose, currDayOpen)
        persistedNdogTime := time[1]
        persistedNdogBullish := currDayOpen > prevDayClose
        // Vácuo: o candle atual (incluindo wicks) está completamente fora do corpo do dia anterior
        bodyGapUp = currDayLow > math.max(prevDayOpen, prevDayClose)
        bodyGapDn = currDayHigh < math.min(prevDayOpen, prevDayClose)
        persistedNdogHasRealBodyGap := bodyGapUp or bodyGapDn
        persistedHasNWOG_masked := true
        persistedHasNDOG := false
        persistedNdogDay := currentDay

    else if not isWeekendGap and not na(prevDayClose) and not na(prevDayOpen) and not gapAlreadyDetectedToday
        currDayOpen = open
        currDayClose = close
        currDayHigh = high
        currDayLow = low
        gapSize = math.abs(currDayOpen - prevDayClose)
        minGap = close * nxog_min_gap / 100

        if gapSize >= minGap
            persistedNdogTop := math.max(prevDayClose, currDayOpen)
            persistedNdogBottom := math.min(prevDayClose, currDayOpen)
            persistedNdogTime := time
            persistedNdogBullish := currDayOpen > prevDayClose
            // Vácuo: o candle atual (incluindo wicks) está completamente fora do corpo do dia anterior
            bodyGapUp = currDayLow > math.max(prevDayOpen, prevDayClose)
            bodyGapDn = currDayHigh < math.min(prevDayOpen, prevDayClose)
            persistedNdogHasRealBodyGap := bodyGapUp or bodyGapDn
            persistedHasNDOG := true
            persistedNdogDay := currentDay

    [persistedNdogTop, persistedNdogBottom, persistedNdogTime, persistedNdogBullish, persistedHasNDOG, persistedHasNWOG_masked, persistedNdogHasRealBodyGap]

// ========== FUNÇÃO NMOG ==========
detectNMOG() =>
    isMonthChange = ta.change(month) != 0

    var float persistedNmogTop = na
    var float persistedNmogBottom = na
    var int persistedNmogTime = na
    var bool persistedNmogBullish = false
    var bool persistedNmogHasRealBodyGap = false
    var int lastMonthTime = na
    
    lastMonthOpen = open[1]
    lastMonthClose = close[1]
    lastMonthTimeVal = time[1]

    if isMonthChange and not na(lastMonthClose) and not na(lastMonthOpen)
        newMonthOpen = open
        newMonthClose = close
        newMonthHigh = high
        newMonthLow = low
        gapSize = math.abs(newMonthOpen - lastMonthClose)
        minGap = close * nxog_min_gap / 100
        // Vácuo: o candle do novo mês (incluindo wicks) está completamente fora do corpo do mês anterior
        bodyGapUp = newMonthLow > math.max(lastMonthOpen, lastMonthClose)
        bodyGapDn = newMonthHigh < math.min(lastMonthOpen, lastMonthClose)
        hasRealBodyGap = bodyGapUp or bodyGapDn
        isNewMonth = na(lastMonthTime) or lastMonthTimeVal > lastMonthTime

        if gapSize >= minGap and isNewMonth
            persistedNmogTop := math.max(lastMonthClose, newMonthOpen)
            persistedNmogBottom := math.min(lastMonthClose, newMonthOpen)
            persistedNmogTime := lastMonthTimeVal
            persistedNmogBullish := newMonthOpen > lastMonthClose
            persistedNmogHasRealBodyGap := hasRealBodyGap
            lastMonthTime := lastMonthTimeVal

    hasNMOG = not na(persistedNmogTop) and not na(persistedNmogBottom)
    [persistedNmogTop, persistedNmogBottom, persistedNmogTime, persistedNmogBullish, hasNMOG, persistedNmogHasRealBodyGap]

// ========== FUNÇÃO FVG ==========
// Nota: O filtro de session break foi removido aqui.
// A função fvgOverlapsNxog() controla se bloqueia FVGs baseado em:
// - Sobreposição de preço
// - Sobreposição de tempo
// - hasRealBodyGap == true (vácuo real)
// FVGs em cenário de sobreposição (sem vácuo) são permitidos.
detectFVG() =>
    // Verificar se há gap de tempo entre os candles (indica gap de sessão sem candle intermediário)
    // Tempo esperado entre candle[2] e candle atual = 2 períodos do timeframe
    tfSeconds = timeframe.in_seconds(timeframe.period)
    expectedTimeGap = tfSeconds * 2 * 1000  // em milissegundos
    actualTimeGap = time - time[2]
    
    // Se o gap de tempo é maior que 3x o esperado, há gap de sessão (sem candle intermediário real)
    hasSessionGap = actualTimeGap > (expectedTimeGap * 1.5)
    
    // Também verificar se o candle do meio [1] realmente tocou na região do gap
    // FVG UP: gap entre high[2] e low - verificar se candle[1] tocou essa região
    // FVG DN: gap entre low[2] e high - verificar se candle[1] tocou essa região
    
    fvg_up_raw = high[2] < low and barstate.isconfirmed and not hasSessionGap
    fvg_dn_raw = low[2] > high and barstate.isconfirmed and not hasSessionGap

    up_top       = fvg_up_raw ? low     : na
    up_bottom    = fvg_up_raw ? high[2] : na
    up_time      = fvg_up_raw ? time[1] : na
    dn_top       = fvg_dn_raw ? low[2]  : na
    dn_bottom    = fvg_dn_raw ? high    : na
    dn_time      = fvg_dn_raw ? time[1] : na
    [up_top, up_bottom, up_time, dn_top, dn_bottom, dn_time, fvg_up_raw, fvg_dn_raw]

// ========== HELPERS ==========
getChartRank() =>
    r = 99999
    if timeframe.isminutes
        r := timeframe.multiplier
    else if timeframe.isdaily
        r := 10000
    else if timeframe.isweekly
        r := 20000
    else if timeframe.ismonthly
        r := 30000
    r

tfLabelToRank(tfLabel) =>
    r = 0
    if tfLabel == '15m'
        r := 15
    else if tfLabel == '1H'
        r := 60
    else if tfLabel == '4H'
        r := 240
    else if tfLabel == '1D'
        r := 10000
    else if tfLabel == '1W'
        r := 20000
    else
        r := 30000
    r

tfLabelToSeconds(tfLabel) =>
    seconds = timeframe.in_seconds(timeframe.period)
    if tfLabel == '15m'
        seconds := timeframe.in_seconds('15')
    else if tfLabel == '1H'
        seconds := timeframe.in_seconds('60')
    else if tfLabel == '4H'
        seconds := timeframe.in_seconds('240')
    else if tfLabel == '1D'
        seconds := timeframe.in_seconds('1D')
    else if tfLabel == '1W'
        seconds := timeframe.in_seconds('1W')
    else if tfLabel == '1MÊS'
        seconds := timeframe.in_seconds('1M')
    seconds

canShowGapInCurrentTF(gapType) =>
    bool show = false
    currentTfRank = getChartRank()
    if gapType == "NDOG"
        show := currentTfRank <= 10000 and not timeframe.isweekly and not timeframe.ismonthly
    else if gapType == "NWOG"
        show := currentTfRank <= 20000 and not timeframe.ismonthly
    else if gapType == "NMOG"
        show := true
    show

getBarIndexFromTime(targetTime, _currentTimeframe) =>
    idx = bar_index
    if not na(targetTime) and targetTime > 0
        timeDiffMs = time - targetTime
        tfSec      = timeframe.in_seconds(timeframe.period)
        barsBack   = tfSec > 0 ? int(math.round(timeDiffMs / (tfSec * 1000))) : 0
        barsBack   := math.min(math.max(barsBack, 0), 500)
        idx        := bar_index - barsBack
        idx        := idx < 0 ? 0 : idx
    idx

gap_fill_pct(isUp, top, bottom, srcHigh, srcLow, srcClose, useClose) =>
    gap   = math.max(top - bottom, syminfo.mintick)
    pLow  = useClose ? srcClose : srcLow
    pHigh = useClose ? srcClose : srcHigh
    float pct = na
    if isUp
        if pLow >= top
            pct := 0.0
        else if pLow <= bottom
            pct := 100.0
        else
            pct := (top - pLow) / gap * 100.0
    else
        if pHigh <= bottom
            pct := 0.0
        else if pHigh >= top
            pct := 100.0
        else
            pct := (pHigh - bottom) / gap * 100.0
    pct

gapExistsInAny(gapTime, top, bottom) =>
    exists = false
    
    if array.size(nwog_up_arr) > 0
        for i = 0 to array.size(nwog_up_arr) - 1 by 1
            existing = array.get(nwog_up_arr, i)
            if existing.start_time == gapTime or (math.abs(existing.top - top) < syminfo.mintick and math.abs(existing.bottom - bottom) < syminfo.mintick)
                exists := true
                break
    if not exists and array.size(nwog_dn_arr) > 0
        for i = 0 to array.size(nwog_dn_arr) - 1 by 1
            existing = array.get(nwog_dn_arr, i)
            if existing.start_time == gapTime or (math.abs(existing.top - top) < syminfo.mintick and math.abs(existing.bottom - bottom) < syminfo.mintick)
                exists := true
                break
    if not exists and array.size(ndog_up_arr) > 0
        for i = 0 to array.size(ndog_up_arr) - 1 by 1
            existing = array.get(ndog_up_arr, i)
            if existing.start_time == gapTime or (math.abs(existing.top - top) < syminfo.mintick and math.abs(existing.bottom - bottom) < syminfo.mintick)
                exists := true
                break
    if not exists and array.size(ndog_dn_arr) > 0
        for i = 0 to array.size(ndog_dn_arr) - 1 by 1
            existing = array.get(ndog_dn_arr, i)
            if existing.start_time == gapTime or (math.abs(existing.top - top) < syminfo.mintick and math.abs(existing.bottom - bottom) < syminfo.mintick)
                exists := true
                break
    if not exists and array.size(nmog_up_arr) > 0
        for i = 0 to array.size(nmog_up_arr) - 1 by 1
            existing = array.get(nmog_up_arr, i)
            if existing.start_time == gapTime or (math.abs(existing.top - top) < syminfo.mintick and math.abs(existing.bottom - bottom) < syminfo.mintick)
                exists := true
                break
    if not exists and array.size(nmog_dn_arr) > 0
        for i = 0 to array.size(nmog_dn_arr) - 1 by 1
            existing = array.get(nmog_dn_arr, i)
            if existing.start_time == gapTime or (math.abs(existing.top - top) < syminfo.mintick and math.abs(existing.bottom - bottom) < syminfo.mintick)
                exists := true
                break
    exists

fvgExists(fvgArray, fvgTime) =>
    exists = false
    if array.size(fvgArray) > 0
        for i = 0 to array.size(fvgArray) - 1 by 1
            existingFVG = array.get(fvgArray, i)
            if existingFVG.start_time == fvgTime
                exists := true
                break
    exists

// Verifica se FVG sobrepõe NXOG com vácuo real
// Retorna true APENAS quando há: sobreposição de preço + janela temporal + hasRealBodyGap
fvgOverlapsNxog(fvgTop, fvgBottom, fvgTime, tfLabel) =>
    overlaps = false
    tfSeconds = tfLabelToSeconds(tfLabel)
    float timeToleranceMs = tfSeconds * 1000.0
    
    // NWOG UP
    if not overlaps and array.size(nwog_up_arr) > 0
        for i = 0 to array.size(nwog_up_arr) - 1 by 1
            gap = array.get(nwog_up_arr, i)
            priceOverlap = math.min(fvgTop, gap.top) > math.max(fvgBottom, gap.bottom)
            timeOverlap = not na(fvgTime) and not na(gap.start_time) and fvgTime >= gap.start_time and (fvgTime - gap.start_time) <= timeToleranceMs
            hasGapVacuum = gap.hasRealBodyGap == true
            if priceOverlap and timeOverlap and hasGapVacuum
                overlaps := true
                break
    
    // NWOG DN
    if not overlaps and array.size(nwog_dn_arr) > 0
        for i = 0 to array.size(nwog_dn_arr) - 1 by 1
            gap = array.get(nwog_dn_arr, i)
            priceOverlap = math.min(fvgTop, gap.top) > math.max(fvgBottom, gap.bottom)
            timeOverlap = not na(fvgTime) and not na(gap.start_time) and fvgTime >= gap.start_time and (fvgTime - gap.start_time) <= timeToleranceMs
            hasGapVacuum = gap.hasRealBodyGap == true
            if priceOverlap and timeOverlap and hasGapVacuum
                overlaps := true
                break
    
    // NDOG UP
    if not overlaps and array.size(ndog_up_arr) > 0
        for i = 0 to array.size(ndog_up_arr) - 1 by 1
            gap = array.get(ndog_up_arr, i)
            priceOverlap = math.min(fvgTop, gap.top) > math.max(fvgBottom, gap.bottom)
            timeOverlap = not na(fvgTime) and not na(gap.start_time) and fvgTime >= gap.start_time and (fvgTime - gap.start_time) <= timeToleranceMs
            hasGapVacuum = gap.hasRealBodyGap == true
            if priceOverlap and timeOverlap and hasGapVacuum
                overlaps := true
                break
    
    // NDOG DN
    if not overlaps and array.size(ndog_dn_arr) > 0
        for i = 0 to array.size(ndog_dn_arr) - 1 by 1
            gap = array.get(ndog_dn_arr, i)
            priceOverlap = math.min(fvgTop, gap.top) > math.max(fvgBottom, gap.bottom)
            timeOverlap = not na(fvgTime) and not na(gap.start_time) and fvgTime >= gap.start_time and (fvgTime - gap.start_time) <= timeToleranceMs
            hasGapVacuum = gap.hasRealBodyGap == true
            if priceOverlap and timeOverlap and hasGapVacuum
                overlaps := true
                break
    
    // NMOG UP
    if not overlaps and array.size(nmog_up_arr) > 0
        for i = 0 to array.size(nmog_up_arr) - 1 by 1
            gap = array.get(nmog_up_arr, i)
            priceOverlap = math.min(fvgTop, gap.top) > math.max(fvgBottom, gap.bottom)
            timeOverlap = not na(fvgTime) and not na(gap.start_time) and fvgTime >= gap.start_time and (fvgTime - gap.start_time) <= timeToleranceMs
            hasGapVacuum = gap.hasRealBodyGap == true
            if priceOverlap and timeOverlap and hasGapVacuum
                overlaps := true
                break
    
    // NMOG DN
    if not overlaps and array.size(nmog_dn_arr) > 0
        for i = 0 to array.size(nmog_dn_arr) - 1 by 1
            gap = array.get(nmog_dn_arr, i)
            priceOverlap = math.min(fvgTop, gap.top) > math.max(fvgBottom, gap.bottom)
            timeOverlap = not na(fvgTime) and not na(gap.start_time) and fvgTime >= gap.start_time and (fvgTime - gap.start_time) <= timeToleranceMs
            hasGapVacuum = gap.hasRealBodyGap == true
            if priceOverlap and timeOverlap and hasGapVacuum
                overlaps := true
                break
    
    overlaps

// Verifica se FVG sobrepõe o NXOG atual (ainda não adicionado aos arrays)
// Recebe os dados final* diretamente para verificar antes de serem adicionados aos arrays
fvgOverlapsCurrentNxog(fvgTop, fvgBottom, fvgTime, tfLabel, 
                       nwogTop, nwogBottom, nwogTime, hasNwog, nwogHasRealBodyGap,
                       ndogTop, ndogBottom, ndogTime, hasNdog, ndogHasRealBodyGap,
                       nmogTop, nmogBottom, nmogTime, hasNmog, nmogHasRealBodyGap) =>
    overlaps = false
    tfSeconds = tfLabelToSeconds(tfLabel)
    float timeToleranceMs = tfSeconds * 1000.0
    
    // Verificar contra NWOG atual
    if hasNwog and not na(nwogTop) and not na(nwogBottom) and nwogHasRealBodyGap
        priceOverlap = math.min(fvgTop, nwogTop) > math.max(fvgBottom, nwogBottom)
        timeOverlap = not na(fvgTime) and not na(nwogTime) and fvgTime >= nwogTime and (fvgTime - nwogTime) <= timeToleranceMs
        if priceOverlap and timeOverlap
            overlaps := true
    
    // Verificar contra NDOG atual
    if not overlaps and hasNdog and not na(ndogTop) and not na(ndogBottom) and ndogHasRealBodyGap
        priceOverlap = math.min(fvgTop, ndogTop) > math.max(fvgBottom, ndogBottom)
        timeOverlap = not na(fvgTime) and not na(ndogTime) and fvgTime >= ndogTime and (fvgTime - ndogTime) <= timeToleranceMs
        if priceOverlap and timeOverlap
            overlaps := true
    
    // Verificar contra NMOG atual
    if not overlaps and hasNmog and not na(nmogTop) and not na(nmogBottom) and nmogHasRealBodyGap
        priceOverlap = math.min(fvgTop, nmogTop) > math.max(fvgBottom, nmogBottom)
        timeOverlap = not na(fvgTime) and not na(nmogTime) and fvgTime >= nmogTime and (fvgTime - nmogTime) <= timeToleranceMs
        if priceOverlap and timeOverlap
            overlaps := true
    
    overlaps


// ========== REQUEST.SECURITY - NXOG ==========
[nwog_top_4h, nwog_bottom_4h, nwog_time_4h, nwog_bullish_4h, has_nwog_4h, nwog_has_real_body_gap_4h] = request.security(syminfo.tickerid, '240', detectNWOG(), lookahead = barmerge.lookahead_off)
[ndog_top_4h, ndog_bottom_4h, ndog_time_4h, ndog_bullish_4h, has_ndog_4h, has_nwog_masked_4h, ndog_has_real_body_gap_4h] = request.security(syminfo.tickerid, '240', detectNDOG(), lookahead = barmerge.lookahead_off)
[nmog_top_4h, nmog_bottom_4h, nmog_time_4h, nmog_bullish_4h, has_nmog_4h, nmog_has_real_body_gap_4h] = request.security(syminfo.tickerid, '240', detectNMOG(), lookahead = barmerge.lookahead_off)
[nmog_top_1h, nmog_bottom_1h, nmog_time_1h, nmog_bullish_1h, has_nmog_1h, nmog_has_real_body_gap_1h] = request.security(syminfo.tickerid, '60', detectNMOG(), lookahead = barmerge.lookahead_off)

// ========== REQUEST.SECURITY - FVG ==========
[up_top_15m, up_bottom_15m, up_time_15m, dn_top_15m, dn_bottom_15m, dn_time_15m, signal_up_15m, signal_dn_15m] = request.security(syminfo.tickerid, '15', detectFVG(), lookahead = barmerge.lookahead_off)
[up_top_1h, up_bottom_1h, up_time_1h, dn_top_1h, dn_bottom_1h, dn_time_1h, signal_up_1h, signal_dn_1h] = request.security(syminfo.tickerid, '60', detectFVG(), lookahead = barmerge.lookahead_off)
[up_top_4h, up_bottom_4h, up_time_4h, dn_top_4h, dn_bottom_4h, dn_time_4h, signal_up_4h, signal_dn_4h] = request.security(syminfo.tickerid, '240', detectFVG(), lookahead = barmerge.lookahead_off)
[up_top_1d, up_bottom_1d, up_time_1d, dn_top_1d, dn_bottom_1d, dn_time_1d, signal_up_1d, signal_dn_1d] = request.security(syminfo.tickerid, '1D', detectFVG(), lookahead = barmerge.lookahead_off)
[up_top_1w, up_bottom_1w, up_time_1w, dn_top_1w, dn_bottom_1w, dn_time_1w, signal_up_1w, signal_dn_1w] = request.security(syminfo.tickerid, '1W', detectFVG(), lookahead = barmerge.lookahead_off)
[up_top_1mo, up_bottom_1mo, up_time_1mo, dn_top_1mo, dn_bottom_1mo, dn_time_1mo, signal_up_1mo, signal_dn_1mo] = request.security(syminfo.tickerid, '1M', detectFVG(), lookahead = barmerge.lookahead_off)

// ========== PROCESSAMENTO NXOG ==========
is4HorHigher = (timeframe.isminutes and timeframe.multiplier >= 240) or timeframe.isdaily or timeframe.isweekly or timeframe.ismonthly
isHigherTF = timeframe.isdaily or timeframe.isweekly or timeframe.ismonthly

var float finalNwogTop = na
var float finalNwogBottom = na
var int finalNwogTime = na
var bool finalNwogBullish = false
var bool finalHasNWOG = false
var bool finalNwogHasRealBodyGap = false

var float finalNdogTop = na
var float finalNdogBottom = na
var int finalNdogTime = na
var bool finalNdogBullish = false
var bool finalHasNDOG = false
var bool finalNdogHasRealBodyGap = false

var float finalNmogTop = na
var float finalNmogBottom = na
var int finalNmogTime = na
var bool finalNmogBullish = false
var bool finalHasNMOG = false
var bool finalNmogHasRealBodyGap = false

var bool hasNWOG_masked = false

if isHigherTF
    hasNWOG_masked := has_nwog_masked_4h
    finalHasNWOG := has_nwog_4h or has_nwog_masked_4h
    if has_nwog_masked_4h
        finalNwogTop := ndog_top_4h
        finalNwogBottom := ndog_bottom_4h
        finalNwogTime := ndog_time_4h
        finalNwogBullish := ndog_bullish_4h
        finalNwogHasRealBodyGap := ndog_has_real_body_gap_4h
    else if has_nwog_4h
        finalNwogTop := nwog_top_4h
        finalNwogBottom := nwog_bottom_4h
        finalNwogTime := nwog_time_4h
        finalNwogBullish := nwog_bullish_4h
        finalNwogHasRealBodyGap := nwog_has_real_body_gap_4h
else
    [nwogTopLocal, nwogBottomLocal, nwogTimeLocal, nwogBullishLocal, hasNWOGLocal, nwogHasRealBodyGapLocal] = detectNWOG()
    [ndogTopLocal, ndogBottomLocal, ndogTimeLocal, ndogBullishLocal, hasNDOGLocal, hasNWOG_maskedLocal, ndogHasRealBodyGapLocal] = detectNDOG()
    
    hasNWOG_masked := hasNWOG_maskedLocal
    finalHasNWOG := hasNWOGLocal or hasNWOG_maskedLocal
    finalNwogTop := hasNWOG_maskedLocal ? ndogTopLocal : nwogTopLocal
    finalNwogBottom := hasNWOG_maskedLocal ? ndogBottomLocal : nwogBottomLocal
    finalNwogTime := hasNWOG_maskedLocal ? ndogTimeLocal : nwogTimeLocal
    finalNwogBullish := hasNWOG_maskedLocal ? ndogBullishLocal : nwogBullishLocal
    finalNwogHasRealBodyGap := hasNWOG_maskedLocal ? ndogHasRealBodyGapLocal : nwogHasRealBodyGapLocal

// NDOG: Sempre usa 4H para detecção consistente
finalHasNDOG := has_ndog_4h and not has_nwog_masked_4h
if finalHasNDOG
    finalNdogTop := ndog_top_4h
    finalNdogBottom := ndog_bottom_4h
    finalNdogTime := ndog_time_4h
    finalNdogBullish := ndog_bullish_4h
    finalNdogHasRealBodyGap := ndog_has_real_body_gap_4h

[nmog_top, nmog_bottom, nmog_time, nmog_bullish, has_nmog, nmog_has_real_body_gap] = detectNMOG()

// ========== PROCESSAR NMOG ==========
if enable_nmog and canShowGapInCurrentTF("NMOG")
    bool use1HData = is4HorHigher or (timeframe.isminutes and timeframe.multiplier == 60)
    
    finalNmogTop     := use1HData ? nmog_top_1h : nmog_top
    finalNmogBottom  := use1HData ? nmog_bottom_1h : nmog_bottom
    finalNmogTime    := use1HData ? nmog_time_1h : nmog_time
    finalNmogBullish := use1HData ? nmog_bullish_1h : nmog_bullish
    finalHasNMOG     := use1HData ? has_nmog_1h : has_nmog
    finalNmogHasRealBodyGap := use1HData ? nmog_has_real_body_gap_1h : nmog_has_real_body_gap
    
    if finalHasNMOG and not na(finalNmogTop) and not na(finalNmogBottom)
        gapArr = finalNmogBullish ? nmog_up_arr : nmog_dn_arr
        gapColor = finalNmogBullish ? color_nmog_up : color_nmog_dn
        if not gapExistsInAny(finalNmogTime, finalNmogTop, finalNmogBottom) and not array.includes(plotted_nmog_times, finalNmogTime)
            startBarIndex = bar_index - 1
            isYearChange = month(finalNmogTime) == 12
            
            if timeframe.isdaily
                if isYearChange
                    startBarIndex := getBarIndexFromTime(finalNmogTime, timeframe.period)
                else
                    startBarIndex := getBarIndexFromTime(finalNmogTime, timeframe.period) + 1
            else if timeframe.ismonthly
                startBarIndex := getBarIndexFromTime(finalNmogTime, timeframe.period) - 1
            else if timeframe.isweekly and isYearChange
                startBarIndex := bar_index
            
            newBox = box.new(startBarIndex, finalNmogTop, bar_index + 20, finalNmogBottom,
                             bgcolor = color.new(gapColor, boxTransparency),
                             border_color = color.new(gapColor, 50),
                             border_width = 1,
                             border_style = line.style_dashed,
                             text = showLabels ? 'NMOG ' + (finalNmogBullish ? '↑' : '↓') : na,
                             text_color = color.new(gapColor, 30),
                             text_size = size.small,
                             text_halign = text.align_center,
                             text_valign = text.align_center)
            newGap = GapData.new(top = finalNmogTop, bottom = finalNmogBottom, start_time = finalNmogTime, bar_index_start = startBarIndex, gap_box = newBox, hasRealBodyGap = finalNmogHasRealBodyGap)
            array.push(gapArr, newGap)
            array.push(plotted_nmog_times, finalNmogTime)

// ========== PROCESSAR NWOG ==========
if enable_nwog and finalHasNWOG and not na(finalNwogTop) and not na(finalNwogBottom) and canShowGapInCurrentTF("NWOG")
    gapArr = finalNwogBullish ? nwog_up_arr : nwog_dn_arr
    gapColor = finalNwogBullish ? color_nwog_up : color_nwog_dn
    if not gapExistsInAny(finalNwogTime, finalNwogTop, finalNwogBottom) and not array.includes(plotted_nwog_times, finalNwogTime)
        startBarIndex = bar_index - 1
        if timeframe.isweekly
            startBarIndex := getBarIndexFromTime(finalNwogTime, timeframe.period) - 1
        else if timeframe.isdaily
            startBarIndex := getBarIndexFromTime(finalNwogTime, timeframe.period) + 1
        newBox = box.new(startBarIndex, finalNwogTop, bar_index + 20, finalNwogBottom,
                         bgcolor = color.new(gapColor, boxTransparency),
                         border_color = color.new(gapColor, 50),
                         border_width = 1,
                         border_style = line.style_dashed,
                         text = showLabels ? 'NWOG ' + (finalNwogBullish ? '↑' : '↓') : na,
                         text_color = color.new(gapColor, 30),
                         text_size = size.small,
                         text_halign = text.align_center,
                         text_valign = text.align_center)
        newGap = GapData.new(top = finalNwogTop, bottom = finalNwogBottom, start_time = finalNwogTime, bar_index_start = startBarIndex, gap_box = newBox, hasRealBodyGap = finalNwogHasRealBodyGap)
        array.push(gapArr, newGap)
        array.push(plotted_nwog_times, finalNwogTime)

// ========== PROCESSAR NDOG ==========
if enable_ndog and finalHasNDOG and not na(finalNdogTop) and not na(finalNdogBottom) and canShowGapInCurrentTF("NDOG")
    gapArr = finalNdogBullish ? ndog_up_arr : ndog_dn_arr
    gapColor = finalNdogBullish ? color_ndog_up : color_ndog_dn
    if not gapExistsInAny(finalNdogTime, finalNdogTop, finalNdogBottom) and not array.includes(plotted_ndog_times, finalNdogTime)
        // Sempre usar timestamp para calcular posição correta
        startBarIndex = getBarIndexFromTime(finalNdogTime, timeframe.period) - 1
        newBox = box.new(startBarIndex, finalNdogTop, bar_index + 20, finalNdogBottom,
                         bgcolor = color.new(gapColor, boxTransparency),
                         border_color = color.new(gapColor, 50),
                         border_width = 1,
                         border_style = line.style_dashed,
                         text = showLabels ? 'NDOG ' + (finalNdogBullish ? '↑' : '↓') : na,
                         text_color = color.new(gapColor, 30),
                         text_size = size.small,
                         text_halign = text.align_center,
                         text_valign = text.align_center)
        newGap = GapData.new(top = finalNdogTop, bottom = finalNdogBottom, start_time = finalNdogTime, bar_index_start = startBarIndex, gap_box = newBox, hasRealBodyGap = finalNdogHasRealBodyGap)
        array.push(gapArr, newGap)
        array.push(plotted_ndog_times, finalNdogTime)

// ========== PROCESSAR FVG ==========
processFVGTimeframe(signalUp, upTop, upBottom, upTime, signalDn, dnTop, dnBottom, dnTime, fvgArrayUp, fvgArrayDn, maxFvgs, colorUp, colorDn, tfLabel, enabled, deleteFilled) =>
    targetRank = tfLabelToRank(tfLabel)
    chartRank = getChartRank()
    showThisTF = targetRank >= chartRank

    if enabled and barstate.isconfirmed and showThisTF
        if signalUp and not na(upTop) and not na(upBottom) and not na(upTime)
            overlapsArrays = fvgOverlapsNxog(upTop, upBottom, upTime, tfLabel)
            overlapsCurrent = fvgOverlapsCurrentNxog(upTop, upBottom, upTime, tfLabel,
                                                      finalNwogTop, finalNwogBottom, finalNwogTime, finalHasNWOG, finalNwogHasRealBodyGap,
                                                      finalNdogTop, finalNdogBottom, finalNdogTime, finalHasNDOG, finalNdogHasRealBodyGap,
                                                      finalNmogTop, finalNmogBottom, finalNmogTime, finalHasNMOG, finalNmogHasRealBodyGap)
            if not fvgExists(fvgArrayUp, upTime) and not overlapsArrays and not overlapsCurrent
                startBarIndex = getBarIndexFromTime(upTime, tfLabel) - 1
                newBox = box.new(startBarIndex, upTop, bar_index + 20, upBottom, bgcolor = color.new(colorUp, boxTransparency), border_color = color.new(colorUp, 50), border_width = 1, text = showLabels ? tfLabel + ' ↑' : na, text_color = color.new(colorUp, 30), text_size = size.small)
                newFVG = GapData.new(top = upTop, bottom = upBottom, start_time = upTime, bar_index_start = startBarIndex, gap_box = newBox, hasRealBodyGap = false)
                array.push(fvgArrayUp, newFVG)

        if signalDn and not na(dnTop) and not na(dnBottom) and not na(dnTime)
            overlapsArrays = fvgOverlapsNxog(dnTop, dnBottom, dnTime, tfLabel)
            overlapsCurrent = fvgOverlapsCurrentNxog(dnTop, dnBottom, dnTime, tfLabel,
                                                      finalNwogTop, finalNwogBottom, finalNwogTime, finalHasNWOG, finalNwogHasRealBodyGap,
                                                      finalNdogTop, finalNdogBottom, finalNdogTime, finalHasNDOG, finalNdogHasRealBodyGap,
                                                      finalNmogTop, finalNmogBottom, finalNmogTime, finalHasNMOG, finalNmogHasRealBodyGap)
            if not fvgExists(fvgArrayDn, dnTime) and not overlapsArrays and not overlapsCurrent
                startBarIndex = getBarIndexFromTime(dnTime, tfLabel) - 1
                newBox = box.new(startBarIndex, dnTop, bar_index + 20, dnBottom, bgcolor = color.new(colorDn, boxTransparency), border_color = color.new(colorDn, 50), border_width = 1, text = showLabels ? tfLabel + ' ↓' : na, text_color = color.new(colorDn, 30), text_size = size.small)
                newFVG = GapData.new(top = dnTop, bottom = dnBottom, start_time = dnTime, bar_index_start = startBarIndex, gap_box = newBox, hasRealBodyGap = false)
                array.push(fvgArrayDn, newFVG)

if barstate.isconfirmed
    processFVGTimeframe(signal_up_15m, up_top_15m, up_bottom_15m, up_time_15m, signal_dn_15m, dn_top_15m, dn_bottom_15m, dn_time_15m, fvgs_up_15m, fvgs_dn_15m, max_fvg_15m, color_up_15m, color_dn_15m, '15m', enable_15m, deleteFilled15m or deleteAllMitigatedFVG)
    processFVGTimeframe(signal_up_1h, up_top_1h, up_bottom_1h, up_time_1h, signal_dn_1h, dn_top_1h, dn_bottom_1h, dn_time_1h, fvgs_up_1h, fvgs_dn_1h, max_fvg_1h, color_up_1h, color_dn_1h, '1H', enable_1h, deleteFilled1h or deleteAllMitigatedFVG)
    processFVGTimeframe(signal_up_4h, up_top_4h, up_bottom_4h, up_time_4h, signal_dn_4h, dn_top_4h, dn_bottom_4h, dn_time_4h, fvgs_up_4h, fvgs_dn_4h, max_fvg_4h, color_up_4h, color_dn_4h, '4H', enable_4h, deleteFilled4h or deleteAllMitigatedFVG)
    processFVGTimeframe(signal_up_1d, up_top_1d, up_bottom_1d, up_time_1d, signal_dn_1d, dn_top_1d, dn_bottom_1d, dn_time_1d, fvgs_up_1d, fvgs_dn_1d, max_fvg_1d, color_up_1d, color_dn_1d, '1D', enable_1d, deleteFilled1d or deleteAllMitigatedFVG)
    processFVGTimeframe(signal_up_1w, up_top_1w, up_bottom_1w, up_time_1w, signal_dn_1w, dn_top_1w, dn_bottom_1w, dn_time_1w, fvgs_up_1w, fvgs_dn_1w, max_fvg_1w, color_up_1w, color_dn_1w, '1W', enable_1w, deleteFilled1w or deleteAllMitigatedFVG)
    processFVGTimeframe(signal_up_1mo, up_top_1mo, up_bottom_1mo, up_time_1mo, signal_dn_1mo, dn_top_1mo, dn_bottom_1mo, dn_time_1mo, fvgs_up_1mo, fvgs_dn_1mo, max_fvg_1mo, color_up_1mo, color_dn_1mo, '1MÊS', enable_1mo, deleteFilled1mo or deleteAllMitigatedFVG)

// ========== ESTENDER E GERENCIAR GAPS ==========
extendGap(gapArray, maxSize, deleteIfMitigated, isUp) =>
    if array.size(gapArray) > 0
        for i = array.size(gapArray) - 1 to 0 by 1
            gapData = array.get(gapArray, i)
            currentBox = gapData.gap_box
            box.set_right(currentBox, bar_index + 5)
            
            isMitigated = false
            if deleteIfMitigated
                pct = gap_fill_pct(isUp, gapData.top, gapData.bottom, high, low, close, mitig_use_close)
                if pct >= mitig_pct_threshold
                    isMitigated := true
            
            if isMitigated
                box.delete(currentBox)
                array.remove(gapArray, i)

    while array.size(gapArray) > maxSize
        oldGap = array.shift(gapArray)
        box.delete(oldGap.gap_box)

// NXOG
extendGap(nwog_up_arr, max_nwog, deleteFilledNWOG or deleteAllMitigatedNXOG, true)
extendGap(nwog_dn_arr, max_nwog, deleteFilledNWOG or deleteAllMitigatedNXOG, false)
extendGap(ndog_up_arr, max_ndog, deleteFilledNDOG or deleteAllMitigatedNXOG, true)
extendGap(ndog_dn_arr, max_ndog, deleteFilledNDOG or deleteAllMitigatedNXOG, false)
extendGap(nmog_up_arr, max_nmog, deleteFilledNMOG or deleteAllMitigatedNXOG, true)
extendGap(nmog_dn_arr, max_nmog, deleteFilledNMOG or deleteAllMitigatedNXOG, false)

// FVG
extendGap(fvgs_up_15m, max_fvg_15m, deleteFilled15m or deleteAllMitigatedFVG, true)
extendGap(fvgs_dn_15m, max_fvg_15m, deleteFilled15m or deleteAllMitigatedFVG, false)
extendGap(fvgs_up_1h, max_fvg_1h, deleteFilled1h or deleteAllMitigatedFVG, true)
extendGap(fvgs_dn_1h, max_fvg_1h, deleteFilled1h or deleteAllMitigatedFVG, false)
extendGap(fvgs_up_4h, max_fvg_4h, deleteFilled4h or deleteAllMitigatedFVG, true)
extendGap(fvgs_dn_4h, max_fvg_4h, deleteFilled4h or deleteAllMitigatedFVG, false)
extendGap(fvgs_up_1d, max_fvg_1d, deleteFilled1d or deleteAllMitigatedFVG, true)
extendGap(fvgs_dn_1d, max_fvg_1d, deleteFilled1d or deleteAllMitigatedFVG, false)
extendGap(fvgs_up_1w, max_fvg_1w, deleteFilled1w or deleteAllMitigatedFVG, true)
extendGap(fvgs_dn_1w, max_fvg_1w, deleteFilled1w or deleteAllMitigatedFVG, false)
extendGap(fvgs_up_1mo, max_fvg_1mo, deleteFilled1mo or deleteAllMitigatedFVG, true)
extendGap(fvgs_dn_1mo, max_fvg_1mo, deleteFilled1mo or deleteAllMitigatedFVG, false)

// ========== ALERTAS ==========
alertcondition(finalHasNWOG, 'NWOG', 'New Week Opening Gap detectado')
alertcondition(finalHasNDOG, 'NDOG', 'New Day Opening Gap detectado')
alertcondition(has_nmog or has_nmog_1h, 'NMOG', 'New Month Opening Gap detectado')
alertcondition(signal_up_15m or signal_dn_15m, 'FVG 15m', 'Novo FVG detectado no 15m')
alertcondition(signal_up_1h or signal_dn_1h, 'FVG 1H', 'Novo FVG detectado no 1H')
alertcondition(signal_up_4h or signal_dn_4h, 'FVG 4H', 'Novo FVG detectado no 4H')
alertcondition(signal_up_1d or signal_dn_1d, 'FVG 1D', 'Novo FVG detectado no 1D')
alertcondition(signal_up_1w or signal_dn_1w, 'FVG 1W', 'Novo FVG detectado no 1W')
alertcondition(signal_up_1mo or signal_dn_1mo, 'FVG 1MÊS', 'Novo FVG detectado no 1MÊS')

// ========================================
// SESSÕES / KILLZONES
// ========================================

timeZone = 'America/New_York'

var g_KZ_General = 'Sessões — Geral'
show_kz      = input.bool(true, 'Mostrar Caixas', inline = 'VIS', group = g_KZ_General)
show_kz_text = input.bool(true, 'Mostrar Texto', inline = 'VIS', group = g_KZ_General)
max_sessions_per_type    = input.int(2, 'Histórico (caixas passadas)', minval = 0, maxval = 50, group = g_KZ_General)
boxTransparencySessions  = input.int(85, 'Transparência Box', minval = 0, maxval = 100, group = g_KZ_General)
textTransparencySessions = input.int(20, 'Transparência Texto', minval = 0, maxval = 100, group = g_KZ_General)

var g_KZ_Asia = 'Sessões — Asia'
use_asia  = input.bool(true, '', inline = 'ASIA', group = g_KZ_Asia)
as_txt    = input.string('Asia', '', inline = 'ASIA', group = g_KZ_Asia)
asia      = input.session('2000-0000', '', inline = 'ASIA', group = g_KZ_Asia)
as_color  = input.color(color.blue, '', inline = 'ASIA', group = g_KZ_Asia)

var g_KZ_London = 'Sessões — London'
use_london = input.bool(true, '', inline = 'LONDON', group = g_KZ_London)
lo_txt     = input.string('London', '', inline = 'LONDON', group = g_KZ_London)
london     = input.session('0200-0500', '', inline = 'LONDON', group = g_KZ_London)
lo_color   = input.color(color.red, '', inline = 'LONDON', group = g_KZ_London)

var g_KZ_NY = 'Sessões — New York'
use_nyam = input.bool(true, '', inline = 'NYAM', group = g_KZ_NY)
na_txt   = input.string('NY AM', '', inline = 'NYAM', group = g_KZ_NY)
nyam     = input.session('0930-1100', '', inline = 'NYAM', group = g_KZ_NY)
na_color = input.color(#089981, '', inline = 'NYAM', group = g_KZ_NY)

use_nylu = input.bool(true, '', inline = 'NYLU', group = g_KZ_NY)
nl_txt   = input.string('NY Lunch', '', inline = 'NYLU', group = g_KZ_NY)
nylu     = input.session('1200-1300', '', inline = 'NYLU', group = g_KZ_NY)
nl_color = input.color(color.yellow, '', inline = 'NYLU', group = g_KZ_NY)

use_nypm = input.bool(true, '', inline = 'NYPM', group = g_KZ_NY)
np_txt   = input.string('NY PM', '', inline = 'NYPM', group = g_KZ_NY)
nypm     = input.session('1330-1600', '', inline = 'NYPM', group = g_KZ_NY)
np_color = input.color(color.purple, '', inline = 'NYPM', group = g_KZ_NY)

var array<box>  asia_boxes = array.new<box>(0)
var array<line> asia_hi    = array.new<line>(0)
var array<line> asia_lo    = array.new<line>(0)

var array<box>  lon_boxes  = array.new<box>(0)
var array<line> lon_hi     = array.new<line>(0)
var array<line> lon_lo     = array.new<line>(0)

var array<box>  nyam_boxes = array.new<box>(0)
var array<line> nyam_hi    = array.new<line>(0)
var array<line> nyam_lo    = array.new<line>(0)

var array<box>  nylu_boxes = array.new<box>(0)
var array<line> nylu_hi    = array.new<line>(0)
var array<line> nylu_lo    = array.new<line>(0)

var array<box>  nypm_boxes = array.new<box>(0)
var array<line> nypm_hi    = array.new<line>(0)
var array<line> nypm_lo    = array.new<line>(0)

get_box_color(color c) =>
    color.new(c, boxTransparencySessions)

get_text_color(color c) =>
    color.new(c, textTransparencySessions)

prune(boxes, his, los, limit) =>
    while array.size(boxes) > limit
        b = array.shift(boxes)
        if not na(b)
            box.delete(b)
    keep = array.size(boxes)
    while array.size(his) > keep
        lh = array.shift(his)
        if not na(lh)
            line.delete(lh)
    while array.size(los) > keep
        ll = array.shift(los)
        if not na(ll)
            line.delete(ll)

processSession(sessionRange, sessionColor, sessionText, enabled) =>
    box  retBox = na
    line retHi  = na
    line retLo  = na

    if enabled
        inSession  = not na(time(timeframe.period, sessionRange, timeZone))
        isFirstBar = inSession and not inSession[1]
        isLastBar  = not inSession and inSession[1]

        var float sessionHigh = na
        var float sessionLow  = na
        var int   sessionStartTime = na
        var int   sessionHighTime  = na
        var int   sessionLowTime   = na
        var box   sessionBox       = na

        if isFirstBar
            sessionStartTime := time
            sessionHigh      := high
            sessionLow       := low
            sessionHighTime  := time
            sessionLowTime   := time

            _boxColor  = get_box_color(sessionColor)
            _textColor = get_text_color(sessionColor)

            sessionBox := box.new(left = sessionStartTime, top = sessionHigh, right = sessionStartTime, bottom = sessionLow,
                                  xloc = xloc.bar_time,
                                  bgcolor = show_kz ? _boxColor : na, border_color = show_kz ? _boxColor : na,
                                  border_width = 0,
                                  text = show_kz and show_kz_text ? sessionText : na,
                                  text_color = _textColor, text_size = size.large)

        else if inSession
            if high > sessionHigh
                sessionHigh     := high
                sessionHighTime := time
            if low < sessionLow
                sessionLow     := low
                sessionLowTime := time

            if not na(sessionBox)
                box.set_top(sessionBox, sessionHigh)
                box.set_bottom(sessionBox, sessionLow)
                box.set_right(sessionBox, time)

        if isLastBar and show_kz
            if sessionHighTime != time[1]
                retHi := line.new(sessionHighTime, sessionHigh, time[1], sessionHigh,
                                  xloc = xloc.bar_time, color = color.new(sessionColor, 0), width = 2, style = line.style_solid)
            if sessionLowTime != time[1]
                retLo := line.new(sessionLowTime, sessionLow, time[1], sessionLow,
                                  xloc = xloc.bar_time, color = color.new(sessionColor, 0), width = 2, style = line.style_solid)
            retBox := sessionBox

    [retBox, retHi, retLo]

showSessions = timeframe.in_seconds() <= 3600

[asia_box, asia_ln_hi, asia_ln_lo] = processSession(asia, as_color, as_txt, use_asia and showSessions)
if not na(asia_box)
    array.push(asia_boxes, asia_box)
    array.push(asia_hi, asia_ln_hi)
    array.push(asia_lo, asia_ln_lo)
    prune(asia_boxes, asia_hi, asia_lo, max_sessions_per_type)

[lon_box, lon_ln_hi, lon_ln_lo] = processSession(london, lo_color, lo_txt, use_london and showSessions)
if not na(lon_box)
    array.push(lon_boxes, lon_box)
    array.push(lon_hi, lon_ln_hi)
    array.push(lon_lo, lon_ln_lo)
    prune(lon_boxes, lon_hi, lon_lo, max_sessions_per_type)

[nyam_box, nyam_ln_hi, nyam_ln_lo] = processSession(nyam, na_color, na_txt, use_nyam and showSessions)
if not na(nyam_box)
    array.push(nyam_boxes, nyam_box)
    array.push(nyam_hi, nyam_ln_hi)
    array.push(nyam_lo, nyam_ln_lo)
    prune(nyam_boxes, nyam_hi, nyam_lo, max_sessions_per_type)

[nylu_box, nylu_ln_hi, nylu_ln_lo] = processSession(nylu, nl_color, nl_txt, use_nylu and showSessions)
if not na(nylu_box)
    array.push(nylu_boxes, nylu_box)
    array.push(nylu_hi, nylu_ln_hi)
    array.push(nylu_lo, nylu_ln_lo)
    prune(nylu_boxes, nylu_hi, nylu_lo, max_sessions_per_type)

[nypm_box, nypm_ln_hi, nypm_ln_lo] = processSession(nypm, np_color, np_txt, use_nypm and showSessions)
if not na(nypm_box)
    array.push(nypm_boxes, nypm_box)
    array.push(nypm_hi, nypm_ln_hi)
    array.push(nypm_lo, nypm_ln_lo)
    prune(nypm_boxes, nypm_hi, nypm_lo, max_sessions_per_type)

// ========================================
// TF COUNTDOWN
// ========================================

var tfd_grpLayout = 'TF Countdown — Layout'
tfd_posChoice  = input.string('Top Right', 'Posição', options = ['Top Left', 'Top Right', 'Bottom Left', 'Bottom Right'], group = tfd_grpLayout)

tfd_showFooter = true
tfd_footerText = 'Tavares'
tfd_panelBg      = color.black
tfd_upColor      = color.rgb(46, 204, 113)
tfd_dnColor      = color.rgb(239, 83, 80)
tfd_accentCol    = color.white
tfd_useTrendCols = true
tfd_labelsBlack  = true

var tfd_const_hdr_size = size.small
var tfd_const_row_size = size.normal
var tfd_const_ftr_size = size.normal

tfd_posTbl = tfd_posChoice == 'Top Left' ? position.top_left :
             tfd_posChoice == 'Top Right' ? position.top_right :
             tfd_posChoice == 'Bottom Left' ? position.bottom_left : position.bottom_right

tfd_pad_label(txt) => ' '  + txt + ' '
tfd_pad_timer(txt) => '  ' + txt + '  '

tfd_zz(n) =>
    n < 10 ? '0' + str.tostring(n) : str.tostring(n)

tfd_fmt_hhmmss(hh, mm, ss) =>
    tfd_zz(hh) + ':' + tfd_zz(mm) + ':' + tfd_zz(ss)

tfd_fmt_mmss(mm, ss) =>
    tfd_zz(mm) + ':' + tfd_zz(ss)

tfd_fmt_by_tf(tf, ms) =>
    s  = math.max(0, int(ms / 1000))
    hh = int(math.floor(s / 3600))
    mm = int(math.floor(s % 3600 / 60))
    ss = s % 60
    forceHH = tf == '240' or tf == 'D'
    (forceHH or hh > 0) ? tfd_fmt_hhmmss(hh, mm, ss) : tfd_fmt_mmss(mm, ss)

tfd_pretty_tf(tf) =>
    tf == '3'   ? '3M'  :
     tf == '5'   ? '5M'  :
     tf == '15'  ? '15M' :
     tf == '60'  ? '1H'  :
     tf == '240' ? '4H'  :
     tf == 'D'   ? '1D'  : tf

tfd_tf_dir_closed(tf) =>
    request.security(syminfo.tickerid, tf, close >= open, barmerge.gaps_off, barmerge.lookahead_off)

tfd_up_3   = tfd_tf_dir_closed('3')
tfd_up_5   = tfd_tf_dir_closed('5')
tfd_up_15  = tfd_tf_dir_closed('15')
tfd_up_60  = tfd_tf_dir_closed('60')
tfd_up_240 = tfd_tf_dir_closed('240')
tfd_up_D   = tfd_tf_dir_closed('D')

tfd_getUp(tf) =>
    tf == '3'   ? tfd_up_3   :
     tf == '5'   ? tfd_up_5   :
     tf == '15'  ? tfd_up_15  :
     tf == '60'  ? tfd_up_60  :
     tf == '240' ? tfd_up_240 :
     tf == 'D'   ? tfd_up_D   : false

tfd_tf_ms(tf) =>
    tf == '3'   ? 3   * 60 * 1000 :
     tf == '5'   ? 5   * 60 * 1000 :
     tf == '15'  ? 15  * 60 * 1000 :
     tf == '60'  ? 60  * 60 * 1000 :
     tf == '240' ? 240 * 60 * 1000 :
     tf == 'D'   ? 24  * 60 * 60 * 1000 : 60 * 1000

tfd_calc_next_close_math(tfMinutes) =>
    currentTime = timenow
    currentHour = hour(currentTime)
    currentMinute = minute(currentTime)
    currentSecond = second(currentTime)

    totalMinutesFromMidnight = currentHour * 60 + currentMinute
    periodsFromMidnight = math.floor(totalMinutesFromMidnight / tfMinutes)
    nextCloseMinutes = (periodsFromMidnight + 1) * tfMinutes

    nextCloseHour = math.floor(nextCloseMinutes / 60)
    nextCloseMinute = nextCloseMinutes % 60

    if nextCloseHour >= 24
        nextCloseHour := nextCloseHour - 24

    nextCloseTime = timestamp(year(currentTime), month(currentTime), dayofmonth(currentTime), nextCloseHour, nextCloseMinute, 0)

    if nextCloseTime <= currentTime
        nextCloseTime := nextCloseTime + 24 * 60 * 60 * 1000

    math.max(0, nextCloseTime - currentTime)

tfd_tc3_on   = request.security(syminfo.tickerid, '3',   time_close, barmerge.gaps_off, barmerge.lookahead_on)
tfd_tc5_on   = request.security(syminfo.tickerid, '5',   time_close, barmerge.gaps_off, barmerge.lookahead_on)
tfd_tc15_on  = request.security(syminfo.tickerid, '15',  time_close, barmerge.gaps_off, barmerge.lookahead_on)
tfd_tc60_on  = request.security(syminfo.tickerid, '60',  time_close, barmerge.gaps_off, barmerge.lookahead_on)
tfd_tc240_on = request.security(syminfo.tickerid, '240', time_close, barmerge.gaps_off, barmerge.lookahead_on)
tfd_tcD_on   = request.security(syminfo.tickerid, 'D',   time_close, barmerge.gaps_off, barmerge.lookahead_on)

tfd_tc3_off   = request.security(syminfo.tickerid, '3',   time_close, barmerge.gaps_off, barmerge.lookahead_off)
tfd_tc5_off   = request.security(syminfo.tickerid, '5',   time_close, barmerge.gaps_off, barmerge.lookahead_off)
tfd_tc15_off  = request.security(syminfo.tickerid, '15',  time_close, barmerge.gaps_off, barmerge.lookahead_off)
tfd_tc60_off  = request.security(syminfo.tickerid, '60',  time_close, barmerge.gaps_off, barmerge.lookahead_off)
tfd_tc240_off = request.security(syminfo.tickerid, '240', time_close, barmerge.gaps_off, barmerge.lookahead_off)
tfd_tcD_off   = request.security(syminfo.tickerid, 'D',   time_close, barmerge.gaps_off, barmerge.lookahead_off)

tfd_get_tc_on(tf) =>
     tf == '3'   ? tfd_tc3_on   :
     tf == '5'   ? tfd_tc5_on   :
     tf == '15'  ? tfd_tc15_on  :
     tf == '60'  ? tfd_tc60_on  :
     tf == '240' ? tfd_tc240_on :
     tf == 'D'   ? tfd_tcD_on   : na

tfd_get_tc_off(tf) =>
     tf == '3'   ? tfd_tc3_off   :
     tf == '5'   ? tfd_tc5_off   :
     tf == '15'  ? tfd_tc15_off  :
     tf == '60'  ? tfd_tc60_off  :
     tf == '240' ? tfd_tc240_off :
     tf == 'D'   ? tfd_tcD_off   : na

tfd_remain_security(tf) =>
    msPer = tfd_tf_ms(tf)
    tc_on  = tfd_get_tc_on(tf)
    tc_off = tfd_get_tc_off(tf)
    tc = na(tc_on) ? (na(tc_off) ? na : tc_off + msPer) : tc_on
    na(tc) ? 0 : math.max(0, tc - timenow)

tfd_remain(tf) =>
    currentTfMinutes = timeframe.in_seconds(timeframe.period) / 60

    tfMinutes = tf == '3' ? 3 : tf == '5' ? 5 : tf == '15' ? 15 : tf == '60' ? 60 : tf == '240' ? 240 : tf == 'D' ? 1440 : 1

    if tfMinutes < currentTfMinutes
        tfd_calc_next_close_math(tfMinutes)
    else
        tfd_remain_security(tf)

var array<string> tfd_labels = array.new_string()
var array<string> tfd_tfs    = array.new_string()
array.clear(tfd_labels)
array.clear(tfd_tfs)

array.push(tfd_labels, 'M3')
array.push(tfd_tfs, '3')

array.push(tfd_labels, 'M5')
array.push(tfd_tfs, '5')

array.push(tfd_labels, 'M15')
array.push(tfd_tfs, '15')

array.push(tfd_labels, 'H1')
array.push(tfd_tfs, '60')

array.push(tfd_labels, 'H4')
array.push(tfd_tfs, '240')

array.push(tfd_labels, 'D')
array.push(tfd_tfs, 'D')

tfd_tfCount    = math.min(array.size(tfd_labels), array.size(tfd_tfs))
tfd_activeRows = 1 + tfd_tfCount + (tfd_showFooter ? 1 : 0)

var table tfd_tbl = na
var int   tfd_prevRows = 0
if na(tfd_tbl) or tfd_activeRows != tfd_prevRows or barstate.isfirst
    if not na(tfd_tbl)
        table.delete(tfd_tbl)
    tfd_tbl := table.new(tfd_posTbl, 2, tfd_activeRows, border_width = 1, frame_color = color.black, border_color = color.black)
    tfd_prevRows := tfd_activeRows
    table.merge_cells(tfd_tbl, 0, 0, 1, 0)

tfd_headerTxt = syminfo.ticker + ' • ' + tfd_pretty_tf(timeframe.period)
table.cell(tfd_tbl, 0, 0, tfd_pad_label(tfd_headerTxt),
           text_color = tfd_accentCol, text_halign = text.align_center, text_valign = text.align_center,
           bgcolor = tfd_panelBg, text_size = tfd_const_hdr_size)

tfd_draw_row(row, labelTxt, tfStr) =>
    remainMs = tfd_remain(tfStr)
    dirUp    = tfd_getUp(tfStr)
    boxCol   = tfd_useTrendCols ? (dirUp ? tfd_upColor : tfd_dnColor) : color.new(color.white, 85)

    lblCol = tfd_labelsBlack ? color.black : (labelTxt == 'H4' or labelTxt == 'D' ? color.black : color.white)
    table.cell(tfd_tbl, 0, row, tfd_pad_label(labelTxt),
               text_color = lblCol, bgcolor = boxCol,
               text_halign = text.align_center, text_valign = text.align_center, text_size = tfd_const_row_size)

    timerTxt = tfd_fmt_by_tf(tfStr, remainMs)
    table.cell(tfd_tbl, 1, row, tfd_pad_timer(timerTxt),
               text_color = color.black, bgcolor = na,
               text_halign = text.align_center, text_valign = text.align_center, text_size = tfd_const_row_size)

for i = 0 to tfd_tfCount - 1
    tfd_draw_row(i + 1, array.get(tfd_labels, i), array.get(tfd_tfs, i))

if tfd_showFooter
    tfd_footerRow = tfd_activeRows - 1
    table.merge_cells(tfd_tbl, 0, tfd_footerRow, 1, tfd_footerRow)
    table.cell(tfd_tbl, 0, tfd_footerRow, tfd_pad_label(tfd_footerText),
               text_color = color.white, bgcolor = tfd_panelBg,
               text_halign = text.align_center, text_valign = text.align_center, text_size = tfd_const_ftr_size)

// ========================================
// LINHA DA MEIA-NOITE
// ========================================

var g_midnight = 'Linha da Meia-Noite'
showLine       = input.bool(true,  "Mostrar Linha", group = g_midnight)

lineColor      = color.gray
lineWidth      = 1
onlyIntraday   = true
midnightWindow = "0000-0001:1234567"
endHour        = 17

tz = "America/New_York"

is_newbar(sess, _tz) =>
    t = time("D", sess, _tz)
    (na(t[1]) and not na(t)) or (t[1] < t)

is_end_time() =>
    current_hour = hour(time, tz)
    current_hour >= endHour

is_intraday = not timeframe.isdaily and not timeframe.isweekly and not timeframe.ismonthly
canPlot     = showLine and (not onlyIntraday or is_intraday)

var line currentMidnightLine = na
var float currentMidnightPrice = na
var int midnightBarIndex = na
var bool lineActive = false

isMidnightBar = is_newbar(midnightWindow, tz)

if isMidnightBar and canPlot
    currentMidnightPrice := open
    midnightBarIndex := bar_index
    lineActive := true

    if not na(currentMidnightLine)
        line.delete(currentMidnightLine)

    currentMidnightLine := line.new(
         x1=bar_index,
         y1=open,
         x2=bar_index,
         y2=open,
         color=lineColor,
         width=lineWidth,
         style=line.style_solid
     )

if not na(currentMidnightLine) and lineActive and canPlot
    if is_end_time()
        line.set_x2(currentMidnightLine, bar_index)
        line.set_y2(currentMidnightLine, currentMidnightPrice)
        lineActive := false
    else
        line.set_x2(currentMidnightLine, bar_index)
        line.set_y2(currentMidnightLine, currentMidnightPrice)

plot(lineActive and canPlot and not na(currentMidnightPrice) ? currentMidnightPrice : na,
     title="Midnight Level",
     color=color.new(lineColor, 30),
     linewidth=1,
     style=plot.style_linebr)

plotshape(
     isMidnightBar and canPlot ? open : na,
     style=shape.circle,
     location=location.absolute,
     color=color.new(lineColor, 0),
     size=size.small,
     title="Início - Meia-Noite"
     )

// ========================================
// SEPARADOR DE DIAS (LINHA VERTICAL + NOME)
// ========================================

var g_day_sep = 'Separador de Dias'
enable_day_sep   = input.bool(true, 'Mostrar Separador', group = g_day_sep)
day_sep_color    = input.color(color.new(color.gray, 30), 'Cor', group = g_day_sep)
day_sep_crypto   = input.bool(false, 'Mercado 24/7 (Crypto)', tooltip = 'Ativar para BTC, ETH e outros que não fecham fim de semana', group = g_day_sep)

daySep_isIntraday = not timeframe.isdaily and not timeframe.isweekly and not timeframe.ismonthly
daySep_canShow = enable_day_sep and daySep_isIntraday

var array<line> daySep_lines = array.new<line>(0)
var int daySep_maxHistory = 10

daySep_pruneHistory() =>
    while array.size(daySep_lines) > daySep_maxHistory
        oldLine = array.shift(daySep_lines)
        if not na(oldLine)
            line.delete(oldLine)

if isMidnightBar and daySep_canShow and barstate.isconfirmed
    newLine = line.new(
         x1 = bar_index,
         y1 = low,
         x2 = bar_index,
         y2 = high,
         color = day_sep_color,
         width = 2,
         style = line.style_dashed,
         extend = extend.both
         )
    array.push(daySep_lines, newLine)
    daySep_pruneHistory()

daySep_hour = hour(time, tz)
daySep_minute = minute(time, tz)
daySep_is12am = daySep_hour == 12 and daySep_minute == 0

plotchar(daySep_canShow and daySep_is12am and dayofweek == dayofweek.sunday and day_sep_crypto ? true : false, 
         text='Sunday', char = ".", color=day_sep_color, location=location.top, offset=0)
plotchar(daySep_canShow and daySep_is12am and dayofweek == dayofweek.monday ? true : false, 
         text='Monday', char = ".", color=day_sep_color, location=location.top, offset=0)
plotchar(daySep_canShow and daySep_is12am and dayofweek == dayofweek.tuesday ? true : false, 
         text='Tuesday', char = ".", color=day_sep_color, location=location.top, offset=0)
plotchar(daySep_canShow and daySep_is12am and dayofweek == dayofweek.wednesday ? true : false, 
         text='Wednesday', char = ".", color=day_sep_color, location=location.top, offset=0)
plotchar(daySep_canShow and daySep_is12am and dayofweek == dayofweek.thursday ? true : false, 
         text='Thursday', char = ".", color=day_sep_color, location=location.top, offset=0)
plotchar(daySep_canShow and daySep_is12am and dayofweek == dayofweek.friday ? true : false, 
         text='Friday', char = ".", color=day_sep_color, location=location.top, offset=0)
plotchar(daySep_canShow and daySep_is12am and dayofweek == dayofweek.saturday and day_sep_crypto ? true : false, 
         text='Saturday', char = ".", color=day_sep_color, location=location.top, offset=0)

Overview

NXOG + FVG identifies institutional opening gaps such as NDOG, NWOG, and NMOG with colored boxes and labels. Killzones for major trading sessions are visualized with customizable boxes, high-low lines, and colors. Mitigation filters automatically remove gaps based on percentage thresholds, using body or wick for calculations, with options to delete all mitigated gaps. Boxes have adjustable transparency and optional text labels. The script draws elements directly on the main price chart.

Details

Published by

TavaresBugs on GitHub

Type

Indicator, Pine Script v6

Draws

On the price chart

Category

SMC / ICT / price action, Support & resistance, Sessions & time

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.

Settings

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

SettingTypeDefaultVariable
Texto dentro da BOXbooltrueshowLabels
Transparência dos Boxesint85boxTransparency
Mitigando em X %int50mitig_pct_threshold
Usar o corpo ao invés do pavioboolfalsemitig_use_close
Remover TODOS Gaps (NXOG) mitigadosboolfalsedeleteAllMitigatedNXOG
Remover TODOS FVGs mitigadosbooltruedeleteAllMitigatedFVG
Mostrar NDOGbooltrueenable_ndog
Remover NDOGs mitigadosboolfalsedeleteFilledNDOG
Máx NDOGint5max_ndog
Cor Altacolorcolor.new(color.lime, 75)color_ndog_up
Cor Baixacolorcolor.new(color.lime, 75)color_ndog_dn
Mostrar NWOGbooltrueenable_nwog
Show all 40 settings
SettingTypeDefaultVariable
Remover NWOGs mitigadosboolfalsedeleteFilledNWOG
Máx NWOGint3max_nwog
Cor Altacolorcolor.new(color.teal, 75)color_nwog_up
Cor Baixacolorcolor.new(color.teal, 75)color_nwog_dn
Mostrar NMOGbooltrueenable_nmog
Remover NMOGs mitigadosboolfalsedeleteFilledNMOG
Máx NMOGint3max_nmog
Cor Altacolorcolor.new(color.maroon, 75)color_nmog_up
Cor Baixacolorcolor.new(color.maroon, 75)color_nmog_dn
FVG 15Mbooltrueenable_15m
Remover FVGs mitigadosbooltruedeleteFilled15m
Máx FVGsint8max_fvg_15m
Cor FVG Altacolorcolor.new(color.blue, 85)color_up_15m
Cor FVG Baixacolorcolor.new(color.blue, 85)color_dn_15m
FVG 1Hbooltrueenable_1h
Remover FVGs mitigadosbooltruedeleteFilled1h
Máx FVGsint6max_fvg_1h
Cor FVG Altacolorcolor.new(color.orange, 85)color_up_1h
Cor FVG Baixacolorcolor.new(color.orange, 85)color_dn_1h
FVG 4Hbooltrueenable_4h
Remover FVGs mitigadosbooltruedeleteFilled4h
Máx FVGsint5max_fvg_4h
Cor FVG Altacolorcolor.new(color.purple, 85)color_up_4h
Cor FVG Baixacolorcolor.new(color.purple, 85)color_dn_4h
FVG 1Dbooltrueenable_1d
Remover FVGs mitigadosbooltruedeleteFilled1d
Máx FVGsint4max_fvg_1d
Cor FVG Altacolorcolor.new(color.yellow, 85)color_up_1d
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

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

This indicator detects and draws the first Fair Value Gap (FVG) that forms in each hourly session based on a selected lower timeframe, with specialized handling for Regular Trading Hours (RTH) confluence windows. It draws FVG range boxes, midpoint lines, hourl

Indicator v5 SMC / ICT / price actionSupport & resistance Repaints

This indicator combines six trading tools into a single chart overlay to optimize indicator limits. It calculates Bollinger Bands, projects weekend price boundaries, plots market structure elements like order blocks, breaker blocks, and MSB lines, marks New Yo

Indicator v6 Support & resistanceSMC / ICT / price action Repaints
19 stars

This indicator highlights Inner Circle Trader (ICT) killzones and key session pivot points directly on the price chart. It projects shaded boxes for specific trading sessions, including Asia, London, NY AM, NY Lunch, and NY PM, to help traders identify high-li

Indicator v5 SMC / ICT / price actionSessions & time
Updated 3 years ago · MIT GitHub repository of ICT Killzones & Pivots

Bandz Session Ranges + ORG + Opening Prices plots trading session ranges, killzones, opening RTH gaps, standard deviation projections, and key opening prices directly on the price chart. It calculates gap ranges based on prior regular trading hours close sourc

Indicator v6 Sessions & timeSupport & resistance
Updated 2 weeks ago · MPL-2.0 GitHub repository of Bandz Session Ranges + ORG + Opening Prices

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