//@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)