//@version=5 indicator("Phidias Master Engine v9.9.4 [Regime-Gated Alerts]", shorttitle="Phidias_v9.9.4", overlay=true, max_labels_count=500, max_boxes_count=500, max_lines_count=500) // ═══════════════════════════════════════════════════════════════════════════════ // ARCHITECTURE v9.9.4 // • All alerts regime-gated: no standalone grade alerts outside regime context // • All four alert types carry full pipe-delimited payload for Gemini ingestion // • Type 1: fires on regime entry/flip when aligned FVG is fresh or reacting // • Type 2: fires once per regime when price first touches/reacts to aligned FVG // • Type 3: fires on regime breakdown to NEUTRAL // • Type 4: fires on X→A+/B+ grade transition within active regime + FVG // • Dead grade-toggle inputs removed; single regime_alert_on controls all // • Birth bar deduplication and reset on mitigation preserved from v9.9.2 // ═══════════════════════════════════════════════════════════════════════════════ // ═══════════════════════════════════════════════════════════════════════════════ // 1. INPUTS // ═══════════════════════════════════════════════════════════════════════════════ nq_sym = input.symbol("NQ1!", "NQ Symbol", group="Institutional Sync") rty_sym = input.symbol("RTY1!", "RTY Symbol", group="Institutional Sync") pl_input = input.float(0, "Current P&L ($)", step=50, group="Preservation Gate") stop_pts = input.float(4.0, "Stop Loss (Pts)", group="Risk Audit") target_pts = input.float(8.0, "Target (Pts)", group="Risk Audit") rr_floor = 1.5 grp_auction = "Fixed Tick Auction (v9.6)" bin_res = input.float(1.0, "Bin Resolution (Points)", step=0.25, group=grp_auction) va_perc = input.float(70.0, "Value Area %", minval=10, maxval=100, group=grp_auction) / 100 max_bins = 300 grp_sync = "Index Sync (Embedded)" sync_corr_len = input.int(30, "Sync Correlation Lookback", minval=10, maxval=100, group=grp_sync) sync_dir_len = input.int(5, "Sync Directional Lookback", minval=2, maxval=20, group=grp_sync) sync_smooth_len = input.int(5, "Sync Score Smoothing", minval=1, maxval=20, group=grp_sync) sync_w_nq = input.float(40.0, "Sync Weight: MES-NQ %", minval=0, maxval=100, group=grp_sync) sync_w_rty = input.float(30.0, "Sync Weight: MES-RTY %", minval=0, maxval=100, group=grp_sync) sync_w_dir = input.float(30.0, "Sync Weight: Direction %", minval=0, maxval=100, group=grp_sync) grp_regime = "Regime Detection & Alerts" regime_window = input.int(20, "Regime Lookback (bars)", minval=10, maxval=60, group=grp_regime) regime_sync_thresh = input.float(70.0, "Sync Threshold for Regime", minval=50, maxval=95, group=grp_regime) regime_bias_thresh = input.float(60.0, "Directional Bias Threshold %", minval=51, maxval=80, group=grp_regime) regime_min_bars = input.int(5, "Min Bars to Confirm Regime", minval=2, maxval=20, group=grp_regime) regime_alert_on = input.bool(true, "Fire Regime + Grade Alerts", group=grp_regime) regime_fvg_proximity = input.int(10, "5M FVG Freshness Window (1M bars)", minval=2, maxval=40, group=grp_regime) grp_sweep = "Sweep Detection" z_threshold = input.float(2.5, "Z-Score Volume Threshold", minval=0.5, step=0.1, group=grp_sweep) touch_zone_ticks = input.int(2, "Touch Zone (ticks)", minval=0, maxval=10, group=grp_sweep) tick_size_input = input.float(0.25, "Tick Size", step=0.25, group=grp_sweep) max_tracked = input.int(10, "Max Tracked Boxes", minval=5, maxval=20, group=grp_sweep) sweep_stale_bars = input.int(15, "Sweep Stale After (bars)", minval=5, maxval=60, group=grp_sweep) sweep_expire_bars = input.int(30, "Sweep Expires After (bars)", minval=10, maxval=120, group=grp_sweep) displacement_pts = input.float(1.0, "Displacement Threshold (Pts)", minval=0.25, step=0.25, group=grp_sweep) grp_zones = "Zone Memory" zone_expire_bars = input.int(120, "Climax Zone Expires After (bars)", minval=30, maxval=500, group=grp_zones) grp_rsi = "RSI Divergence" rsi_len = input.int(14, "RSI Length", minval=5, maxval=30, group=grp_rsi) rsi_ob = input.float(70.0, "Overbought", minval=60, maxval=90, group=grp_rsi) rsi_os = input.float(30.0, "Oversold", minval=10, maxval=40, group=grp_rsi) div_lookback = input.int(20, "Divergence Lookback (bars)", minval=5, maxval=30, group=grp_rsi) div_confirm = input.int(2, "Divergence Confirm (bars)", minval=1, maxval=5, group=grp_rsi) grp_mdl = "Market Data Logger" sessionStart = input.session("0000-2359", "Trading Session (All Day)", group=grp_mdl) london_tz = input.string("Europe/London", "Timezone", group=grp_mdl) show_pivots = input.bool(false, "Show Pivot Levels", group=grp_mdl) show_vwap = input.bool(false, "Show VWAP Lines", group=grp_mdl) show_tick_auction = input.bool(true, "Show Tick Auction POC/VAL/VAH", group=grp_mdl) show_ob = input.bool(false, "Show Order Blocks", group=grp_mdl) show_fvg = input.bool(false, "Show Fair Value Gaps", group=grp_mdl) // ═══════════════════════════════════════════════════════════════════════════════ // 2. PRICE DATA // ═══════════════════════════════════════════════════════════════════════════════ mes_c = close mes_v = volume nq_c = request.security(nq_sym, timeframe.period, close) rty_c = request.security(rty_sym, timeframe.period, close) // ═══════════════════════════════════════════════════════════════════════════════ // 3. EMBEDDED SYNC SCORE // ═══════════════════════════════════════════════════════════════════════════════ float corr_mes_nq = ta.correlation(mes_c, nq_c, sync_corr_len) float corr_mes_rty = ta.correlation(mes_c, rty_c, sync_corr_len) float norm_mes_nq = na(corr_mes_nq) ? 50.0 : (corr_mes_nq + 1) / 2 * 100 float norm_mes_rty = na(corr_mes_rty) ? 50.0 : (corr_mes_rty + 1) / 2 * 100 f_sync_dir(src, lookback) => diff = src - src[lookback] diff > 0 ? 1 : diff < 0 ? -1 : 0 int sync_mes_dir = f_sync_dir(mes_c, sync_dir_len) int sync_nq_dir = f_sync_dir(nq_c, sync_dir_len) int sync_rty_dir = f_sync_dir(rty_c, sync_dir_len) float sync_dir_score = 50.0 if sync_mes_dir == 0 and sync_nq_dir == 0 and sync_rty_dir == 0 sync_dir_score := 50.0 else if sync_mes_dir == sync_nq_dir and sync_nq_dir == sync_rty_dir and sync_mes_dir != 0 sync_dir_score := 100.0 else if sync_mes_dir == sync_nq_dir and sync_mes_dir != 0 sync_dir_score := 66.0 else if sync_mes_dir == sync_rty_dir and sync_mes_dir != 0 sync_dir_score := 66.0 else if sync_nq_dir == sync_rty_dir and sync_nq_dir != 0 sync_dir_score := 40.0 else sync_dir_score := 33.0 float sync_total_w = sync_w_nq + sync_w_rty + sync_w_dir float sync_raw = sync_total_w > 0 ? ((norm_mes_nq * sync_w_nq) + (norm_mes_rty * sync_w_rty) + (sync_dir_score * sync_w_dir)) / sync_total_w : 50.0 sync_raw := math.min(100, math.max(0, sync_raw)) float sync_score = ta.ema(sync_raw, sync_smooth_len) // ═══════════════════════════════════════════════════════════════════════════════ // 4. LONDON MIDPOINT (08:00 UTC) // ═══════════════════════════════════════════════════════════════════════════════ var float lon_mid = na var bool lon_tested = false is_lon_start = not na(time(timeframe.period, "0800-0805", "UTC")) if is_lon_start lon_mid := (high + low) / 2 lon_tested := false if not na(lon_mid) and not lon_tested if low <= lon_mid and high >= lon_mid lon_tested := true // ═══════════════════════════════════════════════════════════════════════════════ // 5. ZONE MEMORY // ═══════════════════════════════════════════════════════════════════════════════ vol_ma = ta.sma(mes_v, 20) atr_val = ta.atr(14) atr_ma = ta.sma(atr_val, 20) is_climax = mes_v > vol_ma * 1.8 or atr_val > atr_ma * 1.5 var red_highs = array.new_float(3, na) var red_lows = array.new_float(3, na) var grn_highs = array.new_float(3, na) var grn_lows = array.new_float(3, na) var red_births = array.new_int(3, 0) var grn_births = array.new_int(3, 0) var int r_ghosts = 0 var int g_ghosts = 0 if is_lon_start r_ghosts := 0 g_ghosts := 0 if is_climax if close < open array.unshift(red_highs, high) array.unshift(red_lows, low) array.unshift(red_births, bar_index) array.pop(red_highs) array.pop(red_lows) array.pop(red_births) else array.unshift(grn_highs, high) array.unshift(grn_lows, low) array.unshift(grn_births, bar_index) array.pop(grn_highs) array.pop(grn_lows) array.pop(grn_births) // ═══════════════════════════════════════════════════════════════════════════════ // 6. REFINED ZONE ANALYSIS // ═══════════════════════════════════════════════════════════════════════════════ bool in_red = false bool in_grn = false bool box_broken = false string break_type = "" float nearest_resistance = na float nearest_support = na for i = 0 to 2 rh = array.get(red_highs, i) rl = array.get(red_lows, i) gh = array.get(grn_highs, i) gl = array.get(grn_lows, i) int rb = array.get(red_births, i) int gb = array.get(grn_births, i) if not na(rh) and rb > 0 and (bar_index - rb) > zone_expire_bars array.set(red_highs, i, na) array.set(red_lows, i, na) rh := na rl := na if not na(gh) and gb > 0 and (bar_index - gb) > zone_expire_bars array.set(grn_highs, i, na) array.set(grn_lows, i, na) gh := na gl := na if not na(rh) if mes_c >= rl and mes_c <= rh in_red := true float red_edge = rh if red_edge > mes_c if na(nearest_resistance) or (red_edge - mes_c) < (nearest_resistance - mes_c) nearest_resistance := red_edge if not na(gh) if mes_c >= gl and mes_c <= gh in_grn := true float grn_edge = gl if grn_edge < mes_c if na(nearest_support) or (mes_c - grn_edge) < (mes_c - nearest_support) nearest_support := grn_edge if not na(rh) and close < rl box_broken := true break_type := "SUPPLY" array.set(red_highs, i, na) array.set(red_lows, i, na) if not na(gh) and close > gh box_broken := true break_type := "DEMAND" array.set(grn_highs, i, na) array.set(grn_lows, i, na) if not na(rh) and close > rh array.set(red_highs, i, na) array.set(red_lows, i, na) r_ghosts += 1 if not na(gh) and close < gl array.set(grn_highs, i, na) array.set(grn_lows, i, na) g_ghosts += 1 // ═══════════════════════════════════════════════════════════════════════════════ // 7. FIXED TICK AUCTION ENGINE // ═══════════════════════════════════════════════════════════════════════════════ var float daily_anchor = na var float[] tick_bins = array.new_float(max_bins, 0.0) var float tick_poc = na var float tick_val = na var float tick_vah = na if ta.change(time("D")) daily_anchor := open f_get_idx(price, anchor) => if na(anchor) or price < anchor - (max_bins / 2) * bin_res or price > anchor + (max_bins / 2) * bin_res -1 else idx = math.floor((price - anchor) / bin_res) + (max_bins / 2) math.min(max_bins - 1, math.max(0, int(idx))) if not na(daily_anchor) idx = f_get_idx(hlc3, daily_anchor) if idx >= 0 array.set(tick_bins, idx, array.get(tick_bins, idx) + volume) bool recalc_tick_va = bar_index % 10 == 0 or ta.change(time("D")) if recalc_tick_va and not na(daily_anchor) max_vol = 0.0 poc_idx = 0 for j = 0 to max_bins - 1 bv = array.get(tick_bins, j) if bv > max_vol max_vol := bv poc_idx := j if max_vol > 0 total_vol = 0.0 for j = 0 to max_bins - 1 total_vol += array.get(tick_bins, j) if total_vol > 0 captured = max_vol lo_idx = poc_idx hi_idx = poc_idx target_vol = total_vol * va_perc while captured < target_vol and (lo_idx > 0 or hi_idx < max_bins - 1) vol_below = lo_idx > 0 ? array.get(tick_bins, lo_idx - 1) : 0.0 vol_above = hi_idx < max_bins - 1 ? array.get(tick_bins, hi_idx + 1) : 0.0 if lo_idx <= 0 hi_idx += 1 captured += vol_above else if hi_idx >= max_bins - 1 lo_idx -= 1 captured += vol_below else if vol_below >= vol_above lo_idx -= 1 captured += vol_below else hi_idx += 1 captured += vol_above tick_poc := daily_anchor + (poc_idx - (max_bins / 2)) * bin_res + (bin_res / 2) tick_val := daily_anchor + (lo_idx - (max_bins / 2)) * bin_res tick_vah := daily_anchor + (hi_idx + 1 - (max_bins / 2)) * bin_res if ta.change(time("D")) for j = 0 to max_bins - 1 array.set(tick_bins, j, 0.0) // ═══════════════════════════════════════════════════════════════════════════════ // 8. SWEEP DETECTION ENGINE // ═══════════════════════════════════════════════════════════════════════════════ float vol_sma_z = ta.sma(volume, 20) float vol_std_z = ta.stdev(volume, 20) float z_vol = vol_std_z != 0 ? (volume - vol_sma_z) / vol_std_z : 0 bool high_vol = z_vol >= z_threshold bool bullish_candle = close > open var float[] s_box_tops = array.new_float(0) var float[] s_box_bottoms = array.new_float(0) var int[] s_box_dirs = array.new_int(0) var int[] s_box_births = array.new_int(0) var int[] s_box_touches = array.new_int(0) var bool[] s_box_swept = array.new_bool(0) if high_vol array.push(s_box_tops, high) array.push(s_box_bottoms, low) array.push(s_box_dirs, bullish_candle ? 1 : -1) array.push(s_box_births, bar_index) array.push(s_box_touches, 0) array.push(s_box_swept, false) if array.size(s_box_tops) > max_tracked array.shift(s_box_tops) array.shift(s_box_bottoms) array.shift(s_box_dirs) array.shift(s_box_births) array.shift(s_box_touches) array.shift(s_box_swept) var int last_sweep_bar = 0 var int last_sweep_dir = 0 var int last_sweep_touches = 0 var float last_sweep_level = na var bool last_sweep_wick = false var bool sweep_displaced = false var bool sweep_has_fvg = false float touch_zone = touch_zone_ticks * tick_size_input int n_sboxes = array.size(s_box_tops) if n_sboxes > 0 for i = n_sboxes - 1 to 0 float b_top = array.get(s_box_tops, i) float b_bottom = array.get(s_box_bottoms, i) int b_dir = array.get(s_box_dirs, i) int b_birth = array.get(s_box_births, i) bool b_swept = array.get(s_box_swept, i) int b_touch = array.get(s_box_touches, i) if bar_index - b_birth > 200 or b_swept continue if b_dir == 1 bool near_bottom = low <= (b_bottom + touch_zone) and low >= (b_bottom - touch_zone) bool closed_above = close > b_bottom bool swept_below = low < (b_bottom - touch_zone) if near_bottom and closed_above and not swept_below array.set(s_box_touches, i, b_touch + 1) if swept_below array.set(s_box_swept, i, true) last_sweep_bar := bar_index last_sweep_dir := 1 last_sweep_touches := b_touch last_sweep_level := b_bottom last_sweep_wick := close > b_bottom sweep_displaced := false sweep_has_fvg := false if b_dir == -1 bool near_top = high >= (b_top - touch_zone) and high <= (b_top + touch_zone) bool closed_below = close < b_top bool swept_above = high > (b_top + touch_zone) if near_top and closed_below and not swept_above array.set(s_box_touches, i, b_touch + 1) if swept_above array.set(s_box_swept, i, true) last_sweep_bar := bar_index last_sweep_dir := -1 last_sweep_touches := b_touch last_sweep_level := b_top last_sweep_wick := close < b_top sweep_displaced := false sweep_has_fvg := false if bar_index == last_sweep_bar + 1 and last_sweep_bar > 0 if last_sweep_dir == 1 sweep_displaced := (close - last_sweep_level) >= displacement_pts else if last_sweep_dir == -1 sweep_displaced := (last_sweep_level - close) >= displacement_pts int sweep_age = bar_index - last_sweep_bar bool sweep_active = last_sweep_bar > 0 and sweep_age <= sweep_expire_bars bool sweep_fresh = sweep_active and sweep_age <= sweep_stale_bars bool sweep_stale = sweep_active and sweep_age > sweep_stale_bars // ═══════════════════════════════════════════════════════════════════════════════ // 9. RSI DIVERGENCE ENGINE // ═══════════════════════════════════════════════════════════════════════════════ float rsi_val = ta.rsi(close, rsi_len) float price_low_1 = ta.lowest(low, div_lookback) float price_low_2 = ta.lowest(low, div_lookback)[div_lookback] float price_high_1 = ta.highest(high, div_lookback) float price_high_2 = ta.highest(high, div_lookback)[div_lookback] float rsi_low_1 = ta.lowest(rsi_val, div_lookback) float rsi_low_2 = ta.lowest(rsi_val, div_lookback)[div_lookback] float rsi_high_1 = ta.highest(rsi_val, div_lookback) float rsi_high_2 = ta.highest(rsi_val, div_lookback)[div_lookback] bool raw_bull_div = price_low_1 < price_low_2 and rsi_low_1 > rsi_low_2 bool raw_bear_div = price_high_1 > price_high_2 and rsi_high_1 < rsi_high_2 var int bull_div_count = 0 var int bear_div_count = 0 if raw_bull_div bull_div_count += 1 else bull_div_count := 0 if raw_bear_div bear_div_count += 1 else bear_div_count := 0 bool bull_div_confirmed = bull_div_count >= div_confirm bool bear_div_confirmed = bear_div_count >= div_confirm string rsi_state = "" color rsi_bg = color.new(#000000, 40) color rsi_tc = color.gray if bull_div_confirmed and rsi_val < rsi_os rsi_state := "OS + BULL DIV" rsi_bg := color.new(color.green, 10) rsi_tc := color.white else if bear_div_confirmed and rsi_val > rsi_ob rsi_state := "OB + BEAR DIV" rsi_bg := color.new(color.red, 10) rsi_tc := color.white else if bull_div_confirmed rsi_state := "BULL DIV" rsi_bg := color.new(color.green, 40) rsi_tc := color.green else if bear_div_confirmed rsi_state := "BEAR DIV" rsi_bg := color.new(color.red, 40) rsi_tc := color.red else if rsi_val > rsi_ob rsi_state := "OB " + str.tostring(rsi_val, "#.#") rsi_bg := color.new(color.red, 60) rsi_tc := color.red else if rsi_val < rsi_os rsi_state := "OS " + str.tostring(rsi_val, "#.#") rsi_bg := color.new(color.green, 60) rsi_tc := color.green else rsi_state := str.tostring(rsi_val, "#.#") // ═══════════════════════════════════════════════════════════════════════════════ // 10. MARKET DATA LOGGER: PIVOTS, VWAP, ORDER BLOCKS, FVGs // ═══════════════════════════════════════════════════════════════════════════════ [xHigh, xLow, xClose] = request.security(syminfo.tickerid, "D", [high[1], low[1], close[1]]) pivot_p = (xHigh + xLow + xClose) / 3 pivot_r1 = pivot_p + (pivot_p - xLow) pivot_s1 = pivot_p - (xHigh - pivot_p) pivot_r2 = pivot_p + (xHigh - xLow) pivot_s2 = pivot_p - (xHigh - xLow) pivot_r3 = xHigh + 2 * (pivot_p - xLow) pivot_s3 = xLow - 2 * (xHigh - pivot_p) settlement = xClose [weekHigh, weekLow, weekClose] = request.security(syminfo.tickerid, "W", [high[1], low[1], close[1]]) week_pivot = (weekHigh + weekLow + weekClose) / 3 [monthHigh, monthLow, monthClose] = request.security(syminfo.tickerid, "M", [high[1], low[1], close[1]]) month_pivot = (monthHigh + monthLow + monthClose) / 3 // VWAP var float vwap_midnight_sum_price_vol = 0.0 var float vwap_midnight_sum_vol = 0.0 var float vwap_230pm_sum_price_vol = 0.0 var float vwap_230pm_sum_vol = 0.0 is_in_session = not na(time(timeframe.period, sessionStart, london_tz)) var bool new_session = false new_session := is_in_session and not is_in_session[1] var float session_midnight_start_time = na var float session_230pm_start_time = na if new_session and hour(time, london_tz) == 0 and minute(time, london_tz) == 0 session_midnight_start_time := time if is_in_session and hour(time, london_tz) == 14 and minute(time, london_tz) == 30 session_230pm_start_time := time if new_session and hour(time, london_tz) == 0 and minute(time, london_tz) == 0 vwap_midnight_sum_price_vol := 0.0 vwap_midnight_sum_vol := 0.0 if is_in_session and hour(time, london_tz) == 14 and minute(time, london_tz) == 30 vwap_230pm_sum_price_vol := 0.0 vwap_230pm_sum_vol := 0.0 typical_price = hlc3 if not na(session_midnight_start_time) and time >= session_midnight_start_time vwap_midnight_sum_price_vol += typical_price * volume vwap_midnight_sum_vol += volume if not na(session_230pm_start_time) and time >= session_230pm_start_time vwap_230pm_sum_price_vol += typical_price * volume vwap_230pm_sum_vol += volume vwap_8am = vwap_midnight_sum_vol > 0 ? vwap_midnight_sum_price_vol / vwap_midnight_sum_vol : na vwap_230pm = vwap_230pm_sum_vol > 0 ? vwap_230pm_sum_price_vol / vwap_230pm_sum_vol : na // Order Blocks (5M) var float bull_ob_top = na var float bull_ob_bottom = na var float bear_ob_top = na var float bear_ob_bottom = na var int bull_ob_time = na var int bear_ob_time = na [ob_open_5m, ob_high_5m, ob_low_5m, ob_close_5m] = request.security(syminfo.tickerid, "5", [open, high, low, close]) bullish_ob = ob_close_5m[2] < ob_open_5m[2] and ob_close_5m[1] > ob_open_5m[1] and (ob_close_5m[1] - ob_open_5m[1]) > math.abs(ob_open_5m[2] - ob_close_5m[2]) * 1.5 if bullish_ob and not na(ob_low_5m[2]) bull_ob_bottom := ob_low_5m[2] bull_ob_top := ob_high_5m[2] bull_ob_time := time bearish_ob = ob_close_5m[2] > ob_open_5m[2] and ob_close_5m[1] < ob_open_5m[1] and (ob_open_5m[1] - ob_close_5m[1]) > math.abs(ob_close_5m[2] - ob_open_5m[2]) * 1.5 if bearish_ob and not na(ob_high_5m[2]) bear_ob_top := ob_high_5m[2] bear_ob_bottom := ob_low_5m[2] bear_ob_time := time if not na(bull_ob_time) bool bull_ob_expired = (time - bull_ob_time) > 120 * 60 * 1000 bool bull_ob_mitigated = close < bull_ob_bottom if bull_ob_expired or bull_ob_mitigated bull_ob_top := na bull_ob_bottom := na if not na(bear_ob_time) bool bear_ob_expired = (time - bear_ob_time) > 120 * 60 * 1000 bool bear_ob_mitigated = close > bear_ob_top if bear_ob_expired or bear_ob_mitigated bear_ob_top := na bear_ob_bottom := na // ───────────────────────────────────────────────────────────────────────────── // Fair Value Gaps (5M) // FIX (from v9.9.2): birth bar stamps only once per unique FVG level. // Birth bars reset to 0 on expiry/mitigation. // ───────────────────────────────────────────────────────────────────────────── var float bull_fvg_top = na var float bull_fvg_bottom = na var float bear_fvg_top = na var float bear_fvg_bottom = na var int bull_fvg_time = na var int bear_fvg_time = na var int bull_fvg_birth_bar = 0 var int bear_fvg_birth_bar = 0 bullish_fvg = ob_low_5m[0] > ob_high_5m[2] and ob_low_5m[1] > ob_high_5m[2] bool bull_fvg_is_new = bullish_fvg and not na(ob_high_5m[2]) and ob_high_5m[2] != bull_fvg_bottom if bull_fvg_is_new bull_fvg_bottom := ob_high_5m[2] bull_fvg_top := ob_low_5m[0] bull_fvg_time := time bull_fvg_birth_bar := bar_index bearish_fvg = ob_high_5m[0] < ob_low_5m[2] and ob_high_5m[1] < ob_low_5m[2] bool bear_fvg_is_new = bearish_fvg and not na(ob_low_5m[2]) and ob_low_5m[2] != bear_fvg_top if bear_fvg_is_new bear_fvg_top := ob_low_5m[2] bear_fvg_bottom := ob_high_5m[0] bear_fvg_time := time bear_fvg_birth_bar := bar_index if not na(bull_fvg_time) bool bull_fvg_expired = (time - bull_fvg_time) > 120 * 60 * 1000 bool bull_fvg_mitigated = close < bull_fvg_bottom if bull_fvg_expired or bull_fvg_mitigated bull_fvg_top := na bull_fvg_bottom := na bull_fvg_birth_bar := 0 if not na(bear_fvg_time) bool bear_fvg_expired = (time - bear_fvg_time) > 120 * 60 * 1000 bool bear_fvg_mitigated = close > bear_fvg_top if bear_fvg_expired or bear_fvg_mitigated bear_fvg_top := na bear_fvg_bottom := na bear_fvg_birth_bar := 0 // ═══════════════════════════════════════════════════════════════════════════════ // 10b. FVG CONFLUENCE CHECK AT SWEEP LEVEL // ═══════════════════════════════════════════════════════════════════════════════ if sweep_active and not na(last_sweep_level) float fvg_tolerance = 2.0 if last_sweep_dir == 1 and not na(bull_fvg_top) and not na(bull_fvg_bottom) if last_sweep_level >= (bull_fvg_bottom - fvg_tolerance) and last_sweep_level <= (bull_fvg_top + fvg_tolerance) sweep_has_fvg := true if last_sweep_dir == -1 and not na(bear_fvg_top) and not na(bear_fvg_bottom) if last_sweep_level >= (bear_fvg_bottom - fvg_tolerance) and last_sweep_level <= (bear_fvg_top + fvg_tolerance) sweep_has_fvg := true // ═══════════════════════════════════════════════════════════════════════════════ // 10c. ALERT MESSAGE BUILDER (PAYLOAD FOR ALL ALERT TYPES) // ═══════════════════════════════════════════════════════════════════════════════ encode_distance(level) => if na(level) "NA" else dist = close - level dist >= 0 ? "+" + str.tostring(dist, "#.##") : str.tostring(dist, "#.##") build_alert_message() => string msg = syminfo.ticker + "|" + str.tostring(time, "#") + "|" + str.tostring(close, "#.##") + "|" msg := msg + "DailyP:" + (not na(pivot_p) ? str.tostring(pivot_p, "#.##") : "NA") + "(" + encode_distance(pivot_p) + ")|" msg := msg + "DailyR1:" + (not na(pivot_r1) ? str.tostring(pivot_r1, "#.##") : "NA") + "(" + encode_distance(pivot_r1) + ")|" msg := msg + "DailyS1:" + (not na(pivot_s1) ? str.tostring(pivot_s1, "#.##") : "NA") + "(" + encode_distance(pivot_s1) + ")|" msg := msg + "Settlement:" + (not na(settlement) ? str.tostring(settlement,"#.##") : "NA") + "(" + encode_distance(settlement) + ")|" msg := msg + "VWAP_8AM:" + (not na(vwap_8am) ? str.tostring(vwap_8am, "#.##") + "(" + encode_distance(vwap_8am) + ")" : "NA") + "|" msg := msg + "POC:" + (not na(tick_poc) ? str.tostring(tick_poc, "#.##") + "(" + encode_distance(tick_poc) + ")" : "NA") + "|" msg := msg + "VAH:" + (not na(tick_vah) ? str.tostring(tick_vah, "#.##") + "(" + encode_distance(tick_vah) + ")" : "NA") + "|" msg := msg + "VAL:" + (not na(tick_val) ? str.tostring(tick_val, "#.##") + "(" + encode_distance(tick_val) + ")" : "NA") + "|" msg := msg + "BullOB:" + (not na(bull_ob_top) ? str.tostring(bull_ob_bottom, "#.##") + "-" + str.tostring(bull_ob_top, "#.##") : "NA") + "|" msg := msg + "BearOB:" + (not na(bear_ob_top) ? str.tostring(bear_ob_bottom, "#.##") + "-" + str.tostring(bear_ob_top, "#.##") : "NA") + "|" msg := msg + "BullFVG:" + (not na(bull_fvg_top) ? str.tostring(bull_fvg_bottom, "#.##") + "-" + str.tostring(bull_fvg_top, "#.##") : "NA") + "|" msg := msg + "BearFVG:" + (not na(bear_fvg_top) ? str.tostring(bear_fvg_bottom, "#.##") + "-" + str.tostring(bear_fvg_top, "#.##") : "NA") + "|" msg := msg + "SweepType:" + (sweep_active ? (last_sweep_wick ? "WICK" : "BODY") : "NONE") + "|" msg := msg + "Displaced:" + (sweep_displaced ? "YES" : "NO") + "|" msg := msg + "FVG_Conf:" + (sweep_has_fvg ? "YES" : "NO") + "|" msg := msg + "Sync:" + str.tostring(sync_score, "#.#") + "|" msg string alert_message = build_alert_message() // ═══════════════════════════════════════════════════════════════════════════════ // 11. TARGET CALCULATIONS & DUAL GATE AUDIT // ═══════════════════════════════════════════════════════════════════════════════ float long_target = mes_c + target_pts float short_target = mes_c - target_pts float dyn_rr = target_pts / stop_pts bool is_rsw = not na(time(timeframe.period, "1430-1445", "UTC")) bool rr_fail = dyn_rr < rr_floor bool loss_limit = pl_input <= -500 bool long_trap = in_red bool long_nq_fail = nq_c < nq_c[1] and not box_broken bool long_target_blocked = not na(nearest_resistance) and nearest_resistance < long_target string long_veto = "" if loss_limit long_veto := "LOSS" else if is_rsw long_veto := "RSW" else if rr_fail long_veto := "R:R" else if long_trap long_veto := "ZONE" else if long_nq_fail long_veto := "NQ" else if long_target_blocked long_veto := "BLOCKED" bool long_pass = long_veto == "" bool short_trap = in_grn bool short_nq_fail = nq_c > nq_c[1] and not box_broken bool short_target_blocked = not na(nearest_support) and nearest_support > short_target string short_veto = "" if loss_limit short_veto := "LOSS" else if is_rsw short_veto := "RSW" else if rr_fail short_veto := "R:R" else if short_trap short_veto := "ZONE" else if short_nq_fail short_veto := "NQ" else if short_target_blocked short_veto := "BLOCKED" bool short_pass = short_veto == "" // ═══════════════════════════════════════════════════════════════════════════════ // 12. SETUP GRADING ENGINE // ═══════════════════════════════════════════════════════════════════════════════ bool sweep_long_aligned = sweep_fresh and last_sweep_dir == 1 bool sweep_short_aligned = sweep_fresh and last_sweep_dir == -1 int sweep_quality = 0 if sweep_active if last_sweep_wick sweep_quality += 1 if sweep_displaced sweep_quality += 1 if sweep_has_fvg sweep_quality += 1 bool rsi_supports_long = bull_div_confirmed or rsi_val < rsi_os bool rsi_supports_short = bear_div_confirmed or rsi_val > rsi_ob bool rsi_warns_long = bear_div_confirmed bool rsi_warns_short = bull_div_confirmed bool rsi_neutral = not rsi_supports_long and not rsi_supports_short and not rsi_warns_long and not rsi_warns_short string long_grade = "X" if not long_pass long_grade := "X" else if sweep_long_aligned and sweep_quality >= 2 and (rsi_supports_long or (rsi_neutral and not rsi_warns_long)) long_grade := "A+" else if rsi_warns_long long_grade := "C" else if sweep_long_aligned or rsi_supports_long long_grade := "B+" else long_grade := "B+" string short_grade = "X" if not short_pass short_grade := "X" else if sweep_short_aligned and sweep_quality >= 2 and (rsi_supports_short or (rsi_neutral and not rsi_warns_short)) short_grade := "A+" else if rsi_warns_short short_grade := "C" else if sweep_short_aligned or rsi_supports_short short_grade := "B+" else short_grade := "B+" f_grade_bg(string grade) => grade == "A+" ? color.new(#00e676, 15) : grade == "B+" ? color.new(#448aff, 25) : grade == "C" ? color.new(#ffd600, 25) : color.new(#444455, 50) f_grade_tc(string grade) => grade == "A+" ? color.white : grade == "B+" ? color.white : grade == "C" ? color.black : color.gray // ═══════════════════════════════════════════════════════════════════════════════ // 12b. REGIME DETECTION ENGINE // ═══════════════════════════════════════════════════════════════════════════════ float sync_avg = ta.sma(sync_score, regime_window) bool this_bar_long_active = (long_grade == "A+" or long_grade == "B+") bool this_bar_short_active = (short_grade == "A+" or short_grade == "B+") float long_active_f = this_bar_long_active ? 1.0 : 0.0 float short_active_f = this_bar_short_active ? 1.0 : 0.0 float long_pct = ta.sma(long_active_f, regime_window) * 100 float short_pct = ta.sma(short_active_f, regime_window) * 100 bool sync_sustained = not na(sync_avg) and sync_avg >= regime_sync_thresh bool long_dominates = long_pct >= regime_bias_thresh and long_pct > short_pct bool short_dominates = short_pct >= regime_bias_thresh and short_pct > long_pct string current_regime = "NEUTRAL" if sync_sustained and long_dominates current_regime := "BULL" else if sync_sustained and short_dominates current_regime := "BEAR" var string confirmed_regime = "NEUTRAL" var int regime_hold_count = 0 var string prev_raw_regime = "NEUTRAL" if current_regime == prev_raw_regime and current_regime != "NEUTRAL" regime_hold_count += 1 else if current_regime != prev_raw_regime regime_hold_count := current_regime != "NEUTRAL" ? 1 : 0 prev_raw_regime := current_regime string new_confirmed = confirmed_regime if current_regime != "NEUTRAL" and regime_hold_count >= regime_min_bars new_confirmed := current_regime else if current_regime == "NEUTRAL" and confirmed_regime != "NEUTRAL" new_confirmed := "NEUTRAL" bool regime_entered = new_confirmed != "NEUTRAL" and confirmed_regime == "NEUTRAL" bool regime_exited = new_confirmed == "NEUTRAL" and confirmed_regime != "NEUTRAL" bool regime_flipped = new_confirmed != "NEUTRAL" and confirmed_regime != "NEUTRAL" and new_confirmed != confirmed_regime string exiting_regime = confirmed_regime confirmed_regime := new_confirmed // ═══════════════════════════════════════════════════════════════════════════════ // 12c. FVG INTERACTION HELPERS // ═══════════════════════════════════════════════════════════════════════════════ bool bull_fvg_active = not na(bull_fvg_bottom) and not na(bull_fvg_top) bool bear_fvg_active = not na(bear_fvg_bottom) and not na(bear_fvg_top) bool price_in_bull_fvg = bull_fvg_active and close >= bull_fvg_bottom and close <= bull_fvg_top bool price_in_bear_fvg = bear_fvg_active and close >= bear_fvg_bottom and close <= bear_fvg_top bool bull_fvg_wick_overlap = bull_fvg_active and low <= bull_fvg_top and high >= bull_fvg_bottom bool bear_fvg_wick_overlap = bear_fvg_active and low <= bear_fvg_top and high >= bear_fvg_bottom bool bull_fvg_reaction_now = price_in_bull_fvg or (bull_fvg_wick_overlap and close > open) bool bear_fvg_reaction_now = price_in_bear_fvg or (bear_fvg_wick_overlap and close < open) int bull_fvg_age = bull_fvg_active ? bar_index - bull_fvg_birth_bar : 9999 int bear_fvg_age = bear_fvg_active ? bar_index - bear_fvg_birth_bar : 9999 bool bull_fvg_fresh = bull_fvg_age <= int(regime_fvg_proximity * 1.5) bool bear_fvg_fresh = bear_fvg_age <= int(regime_fvg_proximity * 1.5) var bool bull_fvg_touched_this_regime = false var bool bear_fvg_touched_this_regime = false if regime_entered or regime_flipped or regime_exited or confirmed_regime == "NEUTRAL" bull_fvg_touched_this_regime := false bear_fvg_touched_this_regime := false // ═══════════════════════════════════════════════════════════════════════════════ // 12d. REGIME ALERTS (ALL TYPES CARRY FULL PAYLOAD) // ═══════════════════════════════════════════════════════════════════════════════ // TYPE 1 — Transition alerts (entry / flip) + full payload if regime_alert_on and (regime_entered or regime_flipped) string regime_dir = confirmed_regime bool should_fire = false string reason = "" string fvg_detail = "" string tag = "" if regime_dir == "BULL" and bull_fvg_active if bull_fvg_reaction_now should_fire := true reason := "PRICE INTERACTING / REJECTING BULL FVG NOW" tag := "[FVG REACTION @ SHIFT]" fvg_detail := "Bull FVG " + str.tostring(bull_fvg_bottom, "#.##") + "–" + str.tostring(bull_fvg_top, "#.##") + " (age " + str.tostring(bull_fvg_age) + " bars)" else if bull_fvg_fresh should_fire := true reason := "FRESH BULL FVG PRESENT AT REGIME SHIFT" tag := "[FRESH FVG @ SHIFT]" fvg_detail := "Bull FVG " + str.tostring(bull_fvg_bottom, "#.##") + "–" + str.tostring(bull_fvg_top, "#.##") + " (age " + str.tostring(bull_fvg_age) + " bars)" else if regime_dir == "BEAR" and bear_fvg_active if bear_fvg_reaction_now should_fire := true reason := "PRICE INTERACTING / REJECTING BEAR FVG NOW" tag := "[FVG REACTION @ SHIFT]" fvg_detail := "Bear FVG " + str.tostring(bear_fvg_bottom, "#.##") + "–" + str.tostring(bear_fvg_top, "#.##") + " (age " + str.tostring(bear_fvg_age) + " bars)" else if bear_fvg_fresh should_fire := true reason := "FRESH BEAR FVG PRESENT AT REGIME SHIFT" tag := "[FRESH FVG @ SHIFT]" fvg_detail := "Bear FVG " + str.tostring(bear_fvg_bottom, "#.##") + "–" + str.tostring(bear_fvg_top, "#.##") + " (age " + str.tostring(bear_fvg_age) + " bars)" if should_fire string transition_type = regime_flipped ? "FLIP" : "ENTRY" string msg = "REGIME " + transition_type + ": " + regime_dir + " " + tag + "\n" + reason + "\n" + fvg_detail + "\n" + "Sync avg: " + str.tostring(sync_avg, "#.#") + " | " + "Bias L:" + str.tostring(long_pct, "#.#") + "% S:" + str.tostring(short_pct, "#.#") + "%\n" + alert_message alert(msg, alert.freq_once_per_bar) // TYPE 2 — Ongoing regime: first FVG touch / reaction + full payload bool new_bull_interaction = confirmed_regime == "BULL" and not bull_fvg_touched_this_regime and bull_fvg_reaction_now bool new_bear_interaction = confirmed_regime == "BEAR" and not bear_fvg_touched_this_regime and bear_fvg_reaction_now if new_bull_interaction and regime_alert_on bull_fvg_touched_this_regime := true string bull_msg = "BULL REGIME ACTIVE + 5M FVG TOUCH / REACTION\n" + "FVG: " + str.tostring(bull_fvg_bottom, "#.##") + "–" + str.tostring(bull_fvg_top, "#.##") + " | Age: " + str.tostring(bull_fvg_age) + " bars" + " | Sync: " + str.tostring(sync_score, "#.#") + "%\n" + alert_message alert(bull_msg, alert.freq_once_per_bar_close) if new_bear_interaction and regime_alert_on bear_fvg_touched_this_regime := true string bear_msg = "BEAR REGIME ACTIVE + 5M FVG TOUCH / REACTION\n" + "FVG: " + str.tostring(bear_fvg_bottom, "#.##") + "–" + str.tostring(bear_fvg_top, "#.##") + " | Age: " + str.tostring(bear_fvg_age) + " bars" + " | Sync: " + str.tostring(sync_score, "#.#") + "%\n" + alert_message alert(bear_msg, alert.freq_once_per_bar_close) // TYPE 3 — Regime breakdown + full payload if regime_alert_on and regime_exited alert("REGIME BREAKDOWN | Was: " + exiting_regime + " | Sync avg dropped below " + str.tostring(regime_sync_thresh, "#") + " or bias lost | Now NEUTRAL\n" + alert_message, alert.freq_once_per_bar) // ═══════════════════════════════════════════════════════════════════════════════ // 13. CANDLE COLOURING & VISUALS // ═══════════════════════════════════════════════════════════════════════════════ color candle_col = na if long_pass and not short_pass candle_col := color.new(color.green, 60) else if short_pass and not long_pass candle_col := color.new(color.red, 60) else if long_pass and short_pass candle_col := color.new(color.yellow, 60) else candle_col := color.new(color.gray, 80) barcolor(candle_col) plot(lon_mid, title="London Mid", color=lon_tested ? color.gray : color.aqua, linewidth=2) plot(long_target, title="Long Target", color=color.new(color.green, 70), style=plot.style_linebr, linewidth=1) plot(short_target, title="Short Target", color=color.new(color.red, 70), style=plot.style_linebr, linewidth=1) plot(show_pivots ? pivot_p : na, title="Daily Pivot", color=color.yellow, linewidth=1) plot(show_vwap and not na(vwap_8am) ? vwap_8am : na, title="VWAP 8AM", color=color.blue, linewidth=2) plot(show_tick_auction and not na(tick_poc) ? tick_poc : na, title="Tick Auction POC", color=color.orange, linewidth=2, style=plot.style_linebr) plot(show_tick_auction and not na(tick_vah) ? tick_vah : na, title="Tick Auction VAH", color=color.new(color.red, 50), linewidth=1, style=plot.style_linebr) plot(show_tick_auction and not na(tick_val) ? tick_val : na, title="Tick Auction VAL", color=color.new(color.green, 50), linewidth=1, style=plot.style_linebr) if show_ob and not na(bull_ob_top) and not na(bull_ob_bottom) box.new(left=bar_index - 50, top=bull_ob_top, right=bar_index, bottom=bull_ob_bottom, border_color=color.green, bgcolor=color.new(color.green, 90)) // ═══════════════════════════════════════════════════════════════════════════════ // 14. REGIME-GATED GRADE ALERTS — TYPE 4 (FVG REQUIRED + FULL PAYLOAD) // ═══════════════════════════════════════════════════════════════════════════════ var string prev_long_grade = "X" var string prev_short_grade = "X" var int conflict_cooldown = 0 var int alert_cooldown = 0 bool dual_signal = long_grade != "X" and short_grade != "X" if dual_signal conflict_cooldown := 5 if conflict_cooldown > 0 and not dual_signal conflict_cooldown -= 1 if alert_cooldown > 0 alert_cooldown -= 1 bool alerts_allowed = not dual_signal and conflict_cooldown == 0 and alert_cooldown == 0 // Gate: regime must be active + aligned FVG interaction required bool regime_bull_fvg = confirmed_regime == "BULL" and bull_fvg_reaction_now bool regime_bear_fvg = confirmed_regime == "BEAR" and bear_fvg_reaction_now bool long_grade_valid = (long_grade == "A+" or long_grade == "B+") bool short_grade_valid = (short_grade == "A+" or short_grade == "B+") bool long_alert_new = long_grade_valid and prev_long_grade == "X" and alerts_allowed and regime_bull_fvg bool short_alert_new = short_grade_valid and prev_short_grade == "X" and alerts_allowed and regime_bear_fvg if long_alert_new and regime_alert_on alert(long_grade + " LONG | BULL REGIME + FVG | " + alert_message, alert.freq_once_per_bar) alert_cooldown := 3 if short_alert_new and regime_alert_on alert(short_grade + " SHORT | BEAR REGIME + FVG | " + alert_message, alert.freq_once_per_bar) alert_cooldown := 3 prev_long_grade := long_grade prev_short_grade := short_grade // ═══════════════════════════════════════════════════════════════════════════════ // 15. MASTER DASHBOARD UI // ═══════════════════════════════════════════════════════════════════════════════ var table phidias_tbl = table.new(position.top_left, columns=2, rows=8, bgcolor=color.new(color.black, 15), border_color=color.new(color.gray, 50), border_width=1) if barstate.islast table.cell(phidias_tbl, 0, 0, "PHIDIAS ENGINE v9.9.4", text_color=color.white, text_size=size.small, text_halign=text.align_center) table.merge_cells(phidias_tbl, 0, 0, 1, 0) color regime_col = confirmed_regime == "BULL" ? color.new(color.green, 20) : confirmed_regime == "BEAR" ? color.new(color.red, 20) : color.new(color.gray, 50) table.cell(phidias_tbl, 0, 1, "REGIME:", text_color=color.white, text_halign=text.align_left) table.cell(phidias_tbl, 1, 1, confirmed_regime, bgcolor=regime_col, text_color=color.white, text_halign=text.align_center) color sync_col = sync_score > 75 ? color.green : sync_score > 60 ? color.orange : color.red table.cell(phidias_tbl, 0, 2, "INST. SYNC:", text_color=color.white, text_halign=text.align_left) table.cell(phidias_tbl, 1, 2, str.tostring(sync_score, "#.#") + "%", text_color=sync_col, text_halign=text.align_center) table.cell(phidias_tbl, 0, 3, "LONG GRADE:", text_color=color.white, text_halign=text.align_left) table.cell(phidias_tbl, 1, 3, long_grade, bgcolor=f_grade_bg(long_grade), text_color=f_grade_tc(long_grade), text_halign=text.align_center) table.cell(phidias_tbl, 0, 4, "SHORT GRADE:", text_color=color.white, text_halign=text.align_left) table.cell(phidias_tbl, 1, 4, short_grade, bgcolor=f_grade_bg(short_grade), text_color=f_grade_tc(short_grade), text_halign=text.align_center) table.cell(phidias_tbl, 0, 5, "LONG VETO:", text_color=color.white, text_halign=text.align_left) table.cell(phidias_tbl, 1, 5, long_pass ? "PASS" : long_veto, bgcolor=long_pass ? color.new(color.green, 70) : color.new(color.red, 70), text_halign=text.align_center) table.cell(phidias_tbl, 0, 6, "SHORT VETO:", text_color=color.white, text_halign=text.align_left) table.cell(phidias_tbl, 1, 6, short_pass ? "PASS" : short_veto, bgcolor=short_pass ? color.new(color.green, 70) : color.new(color.red, 70), text_halign=text.align_center) string fvg_status = bull_fvg_active or bear_fvg_active ? "[5M FVG ACTIVE]" : "[NO ACTIVE 5M FVG]" table.cell(phidias_tbl, 0, 7, fvg_status, text_color=color.gray, text_size=size.tiny, text_halign=text.align_center) table.merge_cells(phidias_tbl, 0, 7, 1, 7)