//@version=5 indicator(title="EMA 15 + FVG Pro Signal", shorttitle="EMA+FVG Pro", overlay=true, max_bars_back=500) // -------------------------------------------------------------- // EMA 15 + FVG PRO SIGNAL // ? Signals only in confirmed trend direction // ? Buy only when candle FILLS a bullish FVG in uptrend // ? Sell only when candle FILLS a bearish FVG in downtrend // ? No repaint — all signals on confirmed closed bar // -------------------------------------------------------------- // --- GROUPS --------------------------------------------------- string G1 = "? EMA Settings" string G2 = "? Trend Filter" string G3 = "? FVG Settings" string G4 = "? Signal Filter" string G5 = "? Display" // --- INPUTS --------------------------------------------------- ema_len = input.int(15, "EMA Length", minval=1, group=G1) trend_len = input.int(50, "Trend EMA Length", minval=1, group=G2, tooltip="Price above this EMA = uptrend (buy side only). Below = downtrend (sell side only).") use_fvg = input.bool(true, "Require FVG Fill", group=G3) fvg_lookback = input.int(30, "FVG Lookback Bars", minval=5, group=G3, tooltip="How many bars back to search for an unfilled FVG") fvg_min_size = input.float(0.0, "Min FVG Size (ticks)", minval=0, step=1.0, group=G3, tooltip="Ignore tiny FVGs smaller than this many ticks") body_filter = input.bool(true, "Strong Body Filter", group=G4, tooltip="Signal candle body must be > 40% of ATR to confirm conviction") ema_touch = input.bool(true, "Require EMA Touch", group=G4, tooltip="Price must touch or cross EMA 15 on the signal candle") show_ema = input.bool(true, "Show EMA 15", group=G5) show_trend = input.bool(true, "Show Trend EMA", group=G5) show_fvg = input.bool(true, "Show FVG Boxes", group=G5) show_signals = input.bool(true, "Show Buy/Sell Arrows", group=G5) show_dash = input.bool(true, "Show Dashboard", group=G5) // --- CORE CALCULATIONS ---------------------------------------- ema15 = ta.ema(close, ema_len) trend_ema = ta.ema(close, trend_len) atr14 = ta.atr(14) tick = syminfo.mintick // --- TREND DIRECTION ------------------------------------------ // Strict trend: price must be clearly above/below trend EMA uptrend = close > trend_ema downtrend = close < trend_ema // --- FVG DETECTION -------------------------------------------- // Bullish FVG: gap between candle[2] high and candle[0] low // The gap = low[0] > high[2] ? empty price space = liquidity void bull_fvg_top = low // top of the gap is current candle low bull_fvg_bottom = high[2] // bottom of the gap is 2 bars ago high bull_fvg_valid = low > high[2] and (low - high[2]) >= fvg_min_size * tick // Bearish FVG: gap between candle[2] low and candle[0] high bear_fvg_top = low[2] // top of the gap is 2 bars ago low bear_fvg_bottom = high // bottom is current high bear_fvg_valid = high < low[2] and (low[2] - high) >= fvg_min_size * tick // --- FVG FILL DETECTION --------------------------------------- // A FVG is "filled" when price comes back and trades through the gap zone // We look back fvg_lookback bars for an unfilled FVG and check if current // candle's range enters that gap zone // Store recent FVG zones var float[] bull_fvg_tops = array.new_float() var float[] bull_fvg_bots = array.new_float() var int[] bull_fvg_bars = array.new_int() var bool[] bull_fvg_filled = array.new_bool() var float[] bear_fvg_tops = array.new_float() var float[] bear_fvg_bots = array.new_float() var int[] bear_fvg_bars = array.new_int() var bool[] bear_fvg_filled = array.new_bool() // Register new FVGs if bull_fvg_valid array.push(bull_fvg_tops, bull_fvg_top) array.push(bull_fvg_bots, bull_fvg_bottom) array.push(bull_fvg_bars, bar_index) array.push(bull_fvg_filled, false) if bear_fvg_valid array.push(bear_fvg_tops, bear_fvg_top) array.push(bear_fvg_bots, bear_fvg_bottom) array.push(bear_fvg_bars, bar_index) array.push(bear_fvg_filled, false) // Prune old FVGs beyond lookback if array.size(bull_fvg_bars) > 0 for i = array.size(bull_fvg_bars) - 1 to 0 if bar_index - array.get(bull_fvg_bars, i) > fvg_lookback array.remove(bull_fvg_tops, i) array.remove(bull_fvg_bots, i) array.remove(bull_fvg_bars, i) array.remove(bull_fvg_filled, i) if array.size(bear_fvg_bars) > 0 for i = array.size(bear_fvg_bars) - 1 to 0 if bar_index - array.get(bear_fvg_bars, i) > fvg_lookback array.remove(bear_fvg_tops, i) array.remove(bear_fvg_bots, i) array.remove(bear_fvg_bars, i) array.remove(bear_fvg_filled, i) // Detect FVG fills on current bar bool bull_fvg_fill_now = false bool bear_fvg_fill_now = false int bull_fill_idx = -1 int bear_fill_idx = -1 // Bull FVG fill = price came DOWN into the gap (low enters the zone) then closes back up if array.size(bull_fvg_tops) > 0 for i = array.size(bull_fvg_tops) - 1 to 0 if not array.get(bull_fvg_filled, i) fvg_t = array.get(bull_fvg_tops, i) fvg_b = array.get(bull_fvg_bots, i) // Price wicks into or through the gap zone and candle closes above the bottom if low <= fvg_t and low >= fvg_b and close > fvg_b bull_fvg_fill_now := true bull_fill_idx := i array.set(bull_fvg_filled, i, true) break // Also count full body entry into gap if close >= fvg_b and close <= fvg_t and open >= fvg_b bull_fvg_fill_now := true bull_fill_idx := i array.set(bull_fvg_filled, i, true) break // Bear FVG fill = price came UP into the gap (high enters the zone) then closes back down if array.size(bear_fvg_tops) > 0 for i = array.size(bear_fvg_tops) - 1 to 0 if not array.get(bear_fvg_filled, i) fvg_t = array.get(bear_fvg_tops, i) fvg_b = array.get(bear_fvg_bots, i) // Price wicks into or through the gap zone and candle closes below the top if high >= fvg_b and high <= fvg_t and close < fvg_t bear_fvg_fill_now := true bear_fill_idx := i array.set(bear_fvg_filled, i, true) break // Also count full body entry into gap if close >= fvg_b and close <= fvg_t and open <= fvg_t bear_fvg_fill_now := true bear_fill_idx := i array.set(bear_fvg_filled, i, true) break // --- EMA TOUCH CHECK ------------------------------------------ touch_ema_bull = low <= ema15 and close > ema15 // touched or dipped below, closed above touch_ema_bear = high >= ema15 and close < ema15 // touched or spiked above, closed below ema_touch_ok_bull = ema_touch ? touch_ema_bull : true ema_touch_ok_bear = ema_touch ? touch_ema_bear : true // --- BODY STRENGTH FILTER ------------------------------------- bull_body = close - open bear_body = open - close body_ok_bull = body_filter ? (bull_body > 0 and bull_body > atr14 * 0.4) : true body_ok_bear = body_filter ? (bear_body > 0 and bear_body > atr14 * 0.4) : true // --- FINAL SIGNAL CONDITIONS ---------------------------------- // BUY: Must be in UPTREND + FVG fill (bull) + EMA touch + strong bull body // SELL: Must be in DOWNTREND + FVG fill (bear) + EMA touch + strong bear body fvg_cond_bull = use_fvg ? bull_fvg_fill_now : true fvg_cond_bear = use_fvg ? bear_fvg_fill_now : true buy_signal = uptrend and fvg_cond_bull and ema_touch_ok_bull and body_ok_bull sell_signal = downtrend and fvg_cond_bear and ema_touch_ok_bear and body_ok_bear // --- FVG BOX DRAWING ------------------------------------------ var box[] fvg_boxes = array.new_box() if show_fvg if array.size(fvg_boxes) >= 50 box.delete(array.shift(fvg_boxes)) if bull_fvg_valid b = box.new(bar_index - 2, bull_fvg_top, bar_index + 10, bull_fvg_bottom, border_color=color.new(#00C853, 40), bgcolor=color.new(#00C853, 88), border_width=1, border_style=line.style_dashed, extend=extend.none) array.push(fvg_boxes, b) if bear_fvg_valid b = box.new(bar_index - 2, bear_fvg_top, bar_index + 10, bear_fvg_bottom, border_color=color.new(#FF1744, 40), bgcolor=color.new(#FF1744, 88), border_width=1, border_style=line.style_dashed, extend=extend.none) array.push(fvg_boxes, b) // --- PLOTS ---------------------------------------------------- plot(show_ema ? ema15 : na, "EMA 15", color=color.new(#2962FF, 0), linewidth=2) plot(show_trend ? trend_ema : na, "Trend EMA", color=color.new(#FF9800, 50), linewidth=1) // Trend background (very subtle) bgcolor(uptrend ? color.new(#00C853, 97) : na, title="Uptrend BG") bgcolor(downtrend ? color.new(#FF1744, 97) : na, title="Downtrend BG") // Signal background flash bgcolor(buy_signal ? color.new(#00C853, 88) : na, title="Buy Flash") bgcolor(sell_signal ? color.new(#FF1744, 88) : na, title="Sell Flash") // --- SIGNAL ARROWS -------------------------------------------- plotshape(show_signals and buy_signal, title="Buy Signal", style=shape.triangleup, location=location.belowbar, color=#00C853, size=size.normal, text="BUY", textcolor=color.white) plotshape(show_signals and sell_signal, title="Sell Signal", style=shape.triangledown, location=location.abovebar, color=#FF1744, size=size.normal, text="SELL", textcolor=color.white) // --- SIGNAL LABELS -------------------------------------------- if buy_signal label.new(bar_index, low - atr14 * 1.4, "BUY\nFVG Fill", color=#00C853, textcolor=color.white, style=label.style_label_up, size=size.small) if sell_signal label.new(bar_index, high + atr14 * 1.4, "SELL\nFVG Fill", color=#FF1744, textcolor=color.white, style=label.style_label_down, size=size.small) // --- DASHBOARD ------------------------------------------------ var color D_H = #0D1117 var color D_BG = color.new(#0D1117, 15) var color D_WH = color.white var color D_GR = #00C853 var color D_RD = #FF1744 var color D_AM = #FF9800 var color D_CY = #00BCD4 var color D_GRAY = color.gray var string last_signal = "NONE" var int last_signal_bar = 0 if buy_signal last_signal := "BUY" last_signal_bar := bar_index if sell_signal last_signal := "SELL" last_signal_bar := bar_index var table dash = table.new(position.top_right, 2, 8, border_width=1, border_color=color.new(color.gray, 70), frame_width=2, frame_color=color.new(color.gray, 50)) if show_dash and barstate.islast // Header table.cell(dash, 0, 0, "EMA+FVG PRO", bgcolor=D_H, text_color=D_CY, text_size=size.small) table.cell(dash, 1, 0, syminfo.tickerid, bgcolor=D_H, text_color=D_WH, text_size=size.small) // EMA 15 table.cell(dash, 0, 1, "EMA 15", bgcolor=D_BG, text_color=D_WH, text_size=size.small) table.cell(dash, 1, 1, str.tostring(ema15, "#.#####"), bgcolor=D_BG, text_color=color.new(#2962FF, 0), text_size=size.small) // Trend EMA table.cell(dash, 0, 2, "Trend EMA", bgcolor=D_BG, text_color=D_WH, text_size=size.small) table.cell(dash, 1, 2, str.tostring(trend_ema, "#.#####"), bgcolor=D_BG, text_color=D_AM, text_size=size.small) // Trend status trend_txt = uptrend ? "UPTREND" : "DOWNTREND" trend_col = uptrend ? D_GR : D_RD table.cell(dash, 0, 3, "Trend", bgcolor=D_BG, text_color=D_WH, text_size=size.small) table.cell(dash, 1, 3, trend_txt, bgcolor=uptrend ? color.new(D_GR, 40) : color.new(D_RD, 40), text_color=D_WH, text_size=size.small) // FVG status fvg_txt = bull_fvg_fill_now ? "BULL FILL" : bear_fvg_fill_now ? "BEAR FILL" : bull_fvg_valid ? "BULL FORM" : bear_fvg_valid ? "BEAR FORM" : "NONE" fvg_col = bull_fvg_fill_now ? color.new(D_GR, 40) : bear_fvg_fill_now ? color.new(D_RD, 40) : color.new(D_GRAY, 60) table.cell(dash, 0, 4, "FVG Status", bgcolor=D_BG, text_color=D_WH, text_size=size.small) table.cell(dash, 1, 4, fvg_txt, bgcolor=fvg_col, text_color=D_WH, text_size=size.small) // Last signal sig_col = last_signal == "BUY" ? color.new(D_GR, 30) : last_signal == "SELL" ? color.new(D_RD, 30) : color.new(D_GRAY, 60) table.cell(dash, 0, 5, "Last Signal", bgcolor=D_BG, text_color=D_WH, text_size=size.small) table.cell(dash, 1, 5, last_signal, bgcolor=sig_col, text_color=D_WH, text_size=size.small) // Bars ago bars_ago = bar_index - last_signal_bar table.cell(dash, 0, 6, "Bars Ago", bgcolor=D_BG, text_color=D_WH, text_size=size.small) table.cell(dash, 1, 6, str.tostring(bars_ago), bgcolor=D_BG, text_color=D_AM, text_size=size.small) // ATR table.cell(dash, 0, 7, "ATR 14", bgcolor=D_H, text_color=D_WH, text_size=size.small) table.cell(dash, 1, 7, str.tostring(atr14, "#.#####"), bgcolor=D_H, text_color=D_CY, text_size=size.small) // --- ALERTS --------------------------------------------------- alertcondition(buy_signal, "BUY - EMA+FVG Fill", "BUY: Uptrend + FVG filled + EMA touch confirmed") alertcondition(sell_signal, "SELL - EMA+FVG Fill", "SELL: Downtrend + FVG filled + EMA touch confirmed") alertcondition(bull_fvg_valid, "New Bull FVG Formed", "Bullish Fair Value Gap detected") alertcondition(bear_fvg_valid, "New Bear FVG Formed", "Bearish Fair Value Gap detected") alertcondition(buy_signal or sell_signal, "Any Signal", "EMA+FVG signal fired")