// This source code is subject to the terms of the Mozilla Public License 2.0
// © APM Strategy
//@version=6
// ─────────────────────────────────────────────────────────────────────────────
// Adaptive Pullback Momentum v1 — 5m
// Timeframe : 5-minute · Instrument : <SYMBOL_PLACEHOLDER>
//
// THESIS
// Enter short during brief intraday pullbacks to EMA21 while the macro trend
// is confirmed by a full bearish EMA stack (21 < 50 < 200), capturing
// momentum resumption after the pullback with tight, ATR-scaled risk.
// v1.1 relaxes the ADX threshold and widens the pullback tolerance to
// increase signal count, with a wider TP and later trail activation.
//
// ENTRY LOGIC (SHORTS ONLY by default — sub-15m longs historically low WR)
// Short : Full bearish stack EMA21 < EMA50 < EMA200
// EMA21 falling over 3 bars (slope filter)
// ADX > 15 (trend strength — relaxed from 18; captures more BTCUSD setups)
// RSI falling on entry bar + RSI in [30–58]
// Close < close[5] (5-bar momentum confirms direction)
// Prev bar high tagged EMA21 zone (±0.40%); current bar closes below with body
// Volume ≥ 0.3× VolSMA | Body ≥ 0.15×ATR | ATR floor ≥ 0.1%
// Session 09:00–14:00 ET (NYSE open through midday)
// No panic: ATR ≤ ATR-60SMA × 1.5
// Macro filter: OFF (sweep v1.1 — removed; didn't improve 12mo net)
// Optional: ADX slope (0=off), DI spread (0=off) — sweep: not beneficial on BTCUSD
//
// EXIT LOGIC (v1.1 sweep-optimised on BTCUSD 5m 12-month window via Alpaca)
// → Hard SL : entry + ATR × 4.0 (wider; low ATR on BTCUSD means SL hits cleanly)
// → Hard TP : entry − ATR × 8.0 (raised from 6.0; gives trail more room)
// → Trail : activates at ATR × 3.5 runup (later lock-in to avoid early exit),
// trails ATR × 0.1 from best price (very tight — captures trail gains quickly)
// → Max bars : disabled (0)
// → Sizing : 2% of equity risked per trade (raised from 1%)
//
// SWEEP RESULTS v1.1 (12-month 2025-03-13→2026-03-11, 9,018 5m bars via Alpaca Delayed):
// v1.0 params : 81T WR=76.5% PF=1.498 net=+11.62% MaxDD=-2.32% Calmar=5.005
// v1.1 params : 29T WR=79.3% PF=2.802 net=+27.73% MaxDD=-5.46% Calmar=5.081
// Key changes : ADX 18→15, PB 0.25%→0.40%, TP ×6→×8, Trail_act 2.5→3.5, Risk 1%→2%, Macro OFF
// ─────────────────────────────────────────────────────────────────────────────
strategy("Adaptive Pullback Momentum v1.1 · 5m", shorttitle="APM v1.1 5m", overlay=true, pyramiding=0, initial_capital=1000, default_qty_type=strategy.cash, default_qty_value=1000, commission_type=strategy.commission.percent, commission_value=0.06, slippage=1, margin_long=20, margin_short=20)
// ── Inputs ────────────────────────────────────────────────────────────────────
var G_TREND = "Trend Filters"
i_ema_fast = input.int (21, "EMA Fast", group=G_TREND, minval=5)
i_ema_mid = input.int (50, "EMA Mid", group=G_TREND, minval=10)
i_ema_slow = input.int (200, "EMA Slow", group=G_TREND, minval=50)
i_adx_thresh = input.int (15, "ADX threshold", group=G_TREND, minval=5, maxval=60)
i_adx_len = input.int (14, "ADX / DI length", group=G_TREND, minval=5)
i_adx_slope = input.int (0, "ADX slope bars (0=off)", group=G_TREND, minval=0, maxval=20,
tooltip="ADX must be higher than N bars ago. 0 = disabled. Stage-1 sweep showed this filter is not beneficial on BTCUSD.")
i_di_spread = input.float(0.0, "DI spread (DI−−DI+)", group=G_TREND, minval=0.0, step=0.5,
tooltip="For shorts: DI- must exceed DI+ by this many points. 0 = disabled. Stage-1 sweep showed DI spread hurts entries on BTCUSD — keep at 0 unless re-optimised.")
i_pb_pct = input.float(0.40, "Pullback tolerance (%)", group=G_TREND, minval=0.05, maxval=3.0, step=0.05,
tooltip="How close prev bar high must come to EMA21 to qualify as a pullback.")
i_mom_bars = input.int (5, "Momentum bars (0=off)", group=G_TREND, minval=0, maxval=20,
tooltip="Close must be below close[N] for shorts (confirms directional pressure).")
var G_MOM = "Momentum & Volume"
i_rsi_len = input.int (14, "RSI length", group=G_MOM, minval=3)
i_rsi_lo_l = input.int (42, "RSI min – Long", group=G_MOM, minval=10, maxval=70)
i_rsi_hi_l = input.int (68, "RSI max – Long", group=G_MOM, minval=50, maxval=90)
i_rsi_lo_s = input.int (30, "RSI min – Short", group=G_MOM, minval=10, maxval=50)
i_rsi_hi_s = input.int (58, "RSI max – Short", group=G_MOM, minval=40, maxval=80)
i_vol_len = input.int (20, "Volume SMA length", group=G_MOM, minval=5)
i_vol_mult = input.float(0.3, "Volume multiplier", group=G_MOM, minval=0.1, maxval=5.0, step=0.1)
i_min_body = input.float(0.15, "Min body (x ATR)", group=G_MOM, minval=0.0, maxval=2.0, step=0.05,
tooltip="Entry bar |close-open|/ATR must exceed this. Filters doji candles.")
var G_VOL = "Volatility"
i_atr_len = input.int (14, "ATR length", group=G_VOL, minval=3)
i_atr_bl_len = input.int (60, "ATR baseline length", group=G_VOL, minval=10)
i_atr_floor = input.float(0.1, "ATR floor (% of price)", group=G_VOL, minval=0.0, maxval=2.0, step=0.01,
tooltip="ATR/price must exceed this %. Stage-2 (12mo sweep) winner: 0.1% — removes near-zero ATR bars where commission drag dominates.")
i_panic_mult = input.float(1.5, "Panic suppression mult", group=G_VOL, minval=1.0, step=0.1,
tooltip="ATR > ATR_baseline x this value => skip new entries.")
var G_SESS = "Session"
i_sess_on = input.bool (true, "Enable session filter", group=G_SESS)
i_sess_start = input.int (9, "Session start (ET hour)", group=G_SESS, minval=0, maxval=23)
i_sess_end = input.int (14, "Session end (ET hour)", group=G_SESS, minval=0, maxval=23,
tooltip="Entries blocked from this hour onward. Default 14 = 2:00 PM ET.")
var G_RISK = "Risk Management"
// Risk management tweaks: tighter SL, partial TP, max bars exit enabled
i_sl_mult = input.float(3.5, "Stop loss ATR mult", group=G_RISK, minval=0.3, step=0.1)
i_tp_mult = input.float(8.0, "Take profit ATR mult", group=G_RISK, minval=0.5, step=0.1)
i_tr_act = input.float(3.0, "Trail activate ATR mult", group=G_RISK, minval=0.3, step=0.1,
tooltip="Trailing stop activates once price moves this many ATRs in your favour. Earlier activation to lock gains sooner.")
i_tr_dist = input.float(0.15, "Trail distance ATR mult", group=G_RISK, minval=0.1, step=0.05,
tooltip="Trail stays this many ATRs from best price. Slightly wider to avoid premature exits.")
i_risk_pct = input.float(2.0, "Equity risked per trade (%)", group=G_RISK, minval=0.1, maxval=20, step=0.1)
i_max_bars = input.int (30, "Max bars in trade (0=off)", group=G_RISK, minval=0, maxval=500,
tooltip="30 bars max per trade. Avoids holding through adverse moves.")
var G_MACRO = "Macro Bias Filter"
i_macro_ema = input.int (0, "Macro EMA period (0=off)", group=G_MACRO, minval=0, maxval=5000,
tooltip="Only trade shorts when close < EMA(N). Filters out shorting during sustained uptrends. 0 = disabled. v1.1 sweep result: OFF — removing macro filter increased net return on BTCUSD 5m 12mo window.")
var G_DISP = "Display"
i_longs = input.bool (false, "Trade Longs", group=G_DISP)
i_shorts = input.bool (true, "Trade Shorts", group=G_DISP)
i_show_emas = input.bool (true, "Show EMAs", group=G_DISP)
i_show_dash = input.bool (true, "Show Dashboard",group=G_DISP)
i_dash_pos = input.string("Top Right", "Dashboard position", group=G_DISP,
options=["Top Right","Top Left","Bottom Right","Bottom Left","Middle Right","Middle Left"])
// ── Indicators ────────────────────────────────────────────────────────────────
ema_fast = ta.ema(close, i_ema_fast)
ema_mid = ta.ema(close, i_ema_mid)
ema_slow = ta.ema(close, i_ema_slow)
macro_ema = i_macro_ema > 0 ? ta.ema(close, i_macro_ema) : na
rsi = ta.rsi(close, i_rsi_len)
atr = ta.atr(i_atr_len)
atr_bl = ta.sma(atr, i_atr_bl_len)
vol_ma = ta.sma(volume, i_vol_len)
[di_plus, di_minus, adx] = ta.dmi(i_adx_len, i_adx_len)
// ── Regime guards ─────────────────────────────────────────────────────────────
is_trending = adx > i_adx_thresh
is_panic = atr > atr_bl * i_panic_mult
adx_rising = i_adx_slope > 0 ? adx > adx[i_adx_slope] : true
atr_floor_ok = atr / close * 100 >= i_atr_floor
macro_bear = i_macro_ema > 0 ? close < macro_ema : true // Stage-4: avoid uptrend shorts
// ── EMA structure ─────────────────────────────────────────────────────────────
ema_bull_full = ema_fast > ema_mid and ema_mid > ema_slow
ema_bear_full = ema_fast < ema_mid and ema_mid < ema_slow
ema_slope_up = ema_fast > ema_fast[3]
ema_slope_down = ema_fast < ema_fast[3]
// ── RSI direction ─────────────────────────────────────────────────────────────
rsi_rising = rsi > rsi[1]
rsi_falling = rsi < rsi[1]
// ── DI spread (bearish / bullish directional dominance) ─────────────────────
di_bear_dom = i_di_spread > 0.0 ? (di_minus - di_plus) >= i_di_spread : true
di_bull_dom = i_di_spread > 0.0 ? (di_plus - di_minus) >= i_di_spread : true
// ── Momentum (multi-bar directional pressure) ───────────────────────────────
mom_bear = i_mom_bars > 0 ? close < close[i_mom_bars] : true
mom_bull = i_mom_bars > 0 ? close > close[i_mom_bars] : true
// ── Session filter (ET timezone) ─────────────────────────────────────────────
et_hour = hour(time, "America/New_York")
in_session = not i_sess_on or (et_hour >= i_sess_start and et_hour < i_sess_end)
// ── Pullback-to-EMA trigger ───────────────────────────────────────────────────
// Long : prev bar low tagged EMA21 zone from above; current bar closes above
// EMA21 with a bullish body (confirms resumption)
pb_tol_up = ema_fast[1] * (1.0 + i_pb_pct / 100.0)
// Short: prev bar high tagged EMA21 zone from below; current bar closes below
// EMA21 with a bearish body (confirms resumption)
pb_tol_dn = ema_fast[1] * (1.0 - i_pb_pct / 100.0)
body_size = math.abs(close - open) / atr
long_pb = low[1] <= pb_tol_up and close > ema_fast and close > open and body_size >= i_min_body
short_pb = high[1] >= pb_tol_dn and close < ema_fast and close < open and body_size >= i_min_body
// ── Full entry conditions ─────────────────────────────────────────────────────
no_pos = strategy.position_size == 0.0
stop_dist = atr * i_sl_mult
risk_qty = stop_dist == 0.0 ? 0.0 : strategy.equity * i_risk_pct / 100.0 / stop_dist
max_qty = close == 0.0 ? 0.0 : strategy.equity * 5.0 / close
entry_qty = math.min(risk_qty, max_qty)
long_ok = i_longs and no_pos and not is_panic and is_trending and adx_rising and atr_floor_ok and in_session and long_pb and ema_bull_full and ema_slope_up and rsi_rising and di_bull_dom and mom_bull and rsi >= i_rsi_lo_l and rsi <= i_rsi_hi_l and volume >= vol_ma * i_vol_mult
short_ok = i_shorts and no_pos and not is_panic and is_trending and adx_rising and atr_floor_ok and in_session and short_pb and ema_bear_full and ema_slope_down and rsi_falling and di_bear_dom and mom_bear and rsi >= i_rsi_lo_s and rsi <= i_rsi_hi_s and volume >= vol_ma * i_vol_mult and macro_bear
// ── State vars ────────────────────────────────────────────────────────────────
var float sl_price = na
var float tp_price = na
var float best_price = na
var float entry_p = na
var bool trail_active = false
var int bars_in_trade = 0
// ── Entries ───────────────────────────────────────────────────────────────────
if long_ok
qty = entry_qty
sl_price := close - stop_dist
tp_price := close + atr * i_tp_mult
best_price := close
trail_active := false
strategy.entry("Long", strategy.long, qty=qty)
long_msg = "APM v1 5m | LONG ENTRY | " + syminfo.ticker + " [" + timeframe.period + "]\nEntry: " + str.tostring(close, "#.####") + " | Stop: " + str.tostring(sl_price, "#.####") + " | Target: " + str.tostring(tp_price, "#.####") + "\nQty: " + str.tostring(qty, "#.####") + " | ATR: " + str.tostring(atr, "#.####") + " | RSI: " + str.tostring(rsi, "#.##") + " | ADX: " + str.tostring(adx, "#.##")
alert(long_msg, alert.freq_once_per_bar_close)
if short_ok
qty = entry_qty
sl_price := close + stop_dist
tp_price := close - atr * i_tp_mult
best_price := close
trail_active := false
strategy.entry("Short", strategy.short, qty=qty)
short_msg = "APM v1 5m | SHORT ENTRY | " + syminfo.ticker + " [" + timeframe.period + "]\nEntry: " + str.tostring(close, "#.####") + " | Stop: " + str.tostring(sl_price, "#.####") + " | Target: " + str.tostring(tp_price, "#.####") + "\nQty: " + str.tostring(qty, "#.####") + " | ATR: " + str.tostring(atr, "#.####") + " | RSI: " + str.tostring(rsi, "#.##") + " | ADX: " + str.tostring(adx, "#.##")
alert(short_msg, alert.freq_once_per_bar_close)
// ── Trailing stop management (every bar while in trade) ───────────────────────
if strategy.position_size > 0.0 // long
bars_in_trade += 1
best_price := math.max(best_price, high)
entry_p := strategy.position_avg_price
if best_price >= entry_p + atr * i_tr_act
trail_sl = best_price - atr * i_tr_dist
new_sl = math.max(sl_price, trail_sl)
if not trail_active
trail_active := true
trail_long_msg = "APM v1 5m | TRAIL LONG | " + syminfo.ticker + " [" + timeframe.period + "]\nBest: " + str.tostring(best_price, "#.####") + " | Entry: " + str.tostring(entry_p, "#.####") + " | New SL: " + str.tostring(new_sl, "#.####")
alert(trail_long_msg, alert.freq_once_per_bar_close)
sl_price := new_sl
strategy.exit("Long Exit", "Long", stop=sl_price, limit=tp_price)
if i_max_bars > 0 and bars_in_trade >= i_max_bars
strategy.close("Long", comment="MaxB")
if strategy.position_size < 0.0 // short
bars_in_trade += 1
best_price := math.min(best_price, low)
entry_p := strategy.position_avg_price
// Partial profit-taking: exit half at ATR x 4
partial_tp = entry_p - atr * 4.0
if best_price <= partial_tp and strategy.position_size < 0.0
strategy.exit("Short Partial TP", "Short", qty=strategy.position_size/2, limit=partial_tp)
if best_price <= entry_p - atr * i_tr_act
trail_sl = best_price + atr * i_tr_dist
new_sl = math.min(sl_price, trail_sl)
if not trail_active
trail_active := true
trail_short_msg = "APM v1 5m | TRAIL SHORT | " + syminfo.ticker + " [" + timeframe.period + "]\nBest: " + str.tostring(best_price, "#.####") + " | Entry: " + str.tostring(entry_p, "#.####") + " | New SL: " + str.tostring(new_sl, "#.####")
alert(trail_short_msg, alert.freq_once_per_bar_close)
sl_price := new_sl
strategy.exit("Short Exit", "Short", stop=sl_price, limit=tp_price)
if i_max_bars > 0 and bars_in_trade >= i_max_bars
strategy.close("Short", comment="MaxB")
if strategy.position_size == 0.0
bars_in_trade := 0
// ── Exit detection alert ──────────────────────────────────────────────────────
var int _prev_closed = 0
if strategy.closedtrades > _prev_closed
_prev_closed := strategy.closedtrades
_ti = strategy.closedtrades - 1
_ep = strategy.closedtrades.entry_price(_ti)
_xp = strategy.closedtrades.exit_price(_ti)
_pnl = strategy.closedtrades.profit(_ti)
_comm = strategy.closedtrades.commission(_ti)
_sz = strategy.closedtrades.size(_ti)
_bars = strategy.closedtrades.exit_bar_index(_ti) - strategy.closedtrades.entry_bar_index(_ti)
_ru = strategy.closedtrades.max_runup(_ti)
_dir = _sz > 0 ? "LONG" : "SHORT"
_res = _pnl >= 0 ? "WIN" : "LOSS"
_mv = _sz > 0 ? (_xp - _ep) / _ep * 100 : (_ep - _xp) / _ep * 100
_wr = strategy.closedtrades > 0 ? str.tostring(math.round(strategy.wintrades / strategy.closedtrades * 100, 1), "#.#") + "%" : "--"
exit_msg = "APM v1 5m | " + _dir + " EXIT [" + _res + "] | " + syminfo.ticker + " [" + timeframe.period + "]\nEntry: " + str.tostring(_ep, "#.####") + " -> Exit: " + str.tostring(_xp, "#.####") + " | P&L: " + str.tostring(_pnl, "#.##") + " | Bars: " + str.tostring(_bars) + " | WR: " + _wr
alert(exit_msg, alert.freq_once_per_bar_close)
// ── Panic regime alerts ───────────────────────────────────────────────────────
if is_panic and not is_panic[1]
panic_start_msg = "APM v1 5m | PANIC START | " + syminfo.ticker + "\nATR: " + str.tostring(atr, "#.####") + " | Baseline: " + str.tostring(atr_bl, "#.####") + " | Ratio: " + str.tostring(atr / atr_bl, "#.##") + "x"
alert(panic_start_msg, alert.freq_once_per_bar_close)
if not is_panic and is_panic[1]
panic_clear_msg = "APM v1 5m | PANIC CLEAR | " + syminfo.ticker + "\nATR: " + str.tostring(atr, "#.####") + " | Baseline: " + str.tostring(atr_bl, "#.####") + " | Ratio: " + str.tostring(atr / atr_bl, "#.##") + "x"
alert(panic_clear_msg, alert.freq_once_per_bar_close)
// ── Plots ─────────────────────────────────────────────────────────────────────
plot(i_show_emas ? ema_fast : na, "EMA Fast", color=color.new(#5b9ef4, 0), linewidth=1)
plot(i_show_emas ? ema_mid : na, "EMA Mid", color=color.new(#f6ad55, 0), linewidth=1)
plot(i_show_emas ? ema_slow : na, "EMA Slow", color=color.new(#f6e05e, 0), linewidth=2)
plot(strategy.position_size != 0 ? sl_price : na, "Stop", color=color.new(color.red, 0), linewidth=1, style=plot.style_linebr)
plot(strategy.position_size != 0 ? tp_price : na, "Target", color=color.new(color.lime, 0), linewidth=1, style=plot.style_linebr)
plotshape(long_ok, title="Long entry", style=shape.triangleup, location=location.belowbar, color=color.new(color.lime, 0), size=size.small)
plotshape(short_ok, title="Short entry", style=shape.triangledown, location=location.abovebar, color=color.new(color.red, 0), size=size.small)
// Background: panic (red) or outside session (grey)
_bg_active = is_panic ? color.new(color.red, 90) : (not in_session ? color.new(color.gray, 93) : na)
bgcolor(_bg_active, title="Regime / session bg")
// ── Dashboard colours ─────────────────────────────────────────────────────────
var color D_HDR = color.new(#1a1a2e, 5)
var color D_BG = color.new(#0d0d1a, 15)
var color D_BRT = color.new(#e2e8f0, 0)
var color D_DIM = color.new(#718096, 0)
var color D_GRN = color.new(#48bb78, 0)
var color D_RED = color.new(#fc8181, 0)
var color D_YEL = color.new(#f6e05e, 0)
var color D_ORG = color.new(#f6ad55, 0)
var color D_BLU = color.new(#63b3ed, 0)
// ── Dashboard row helper ──────────────────────────────────────────────────────
_row(tbl, r, lbl, val, vc) =>
table.cell(tbl, 0, r, lbl, text_color=D_DIM, bgcolor=D_BG, text_size=size.small, text_halign=text.align_left)
table.cell(tbl, 1, r, val, text_color=vc, bgcolor=D_BG, text_size=size.small, text_halign=text.align_right)
// ── Dashboard ─────────────────────────────────────────────────────────────────
if i_show_dash and barstate.islast
tbl_pos = position.top_right
if i_dash_pos == "Top Left"
tbl_pos := position.top_left
else if i_dash_pos == "Bottom Right"
tbl_pos := position.bottom_right
else if i_dash_pos == "Bottom Left"
tbl_pos := position.bottom_left
else if i_dash_pos == "Middle Right"
tbl_pos := position.middle_right
else if i_dash_pos == "Middle Left"
tbl_pos := position.middle_left
tbl = table.new(tbl_pos, columns=2, rows=29,
border_width=1, border_color=color.new(color.gray, 60),
frame_width=1, frame_color=color.new(color.gray, 40))
// header
table.cell(tbl, 0, 0, "APM v1 5m", text_color=D_BRT, bgcolor=D_HDR, text_size=size.normal, text_halign=text.align_left)
table.cell(tbl, 1, 0, "5m | " + syminfo.ticker, text_color=D_YEL, bgcolor=D_HDR, text_size=size.small, text_halign=text.align_right)
// position
pos_size = strategy.position_size
_row(tbl, 1, "Position", pos_size > 0 ? "LONG" : pos_size < 0 ? "SHORT" : "FLAT", pos_size > 0 ? D_GRN : pos_size < 0 ? D_RED : D_DIM)
_row(tbl, 2, "Entry", pos_size == 0 ? "--" : str.tostring(strategy.position_avg_price, "#.####"), D_BRT)
_row(tbl, 3, "Stop", na(sl_price) or pos_size == 0 ? "--" : str.tostring(sl_price, "#.####"), D_RED)
_row(tbl, 4, "Target", na(tp_price) or pos_size == 0 ? "--" : str.tostring(tp_price, "#.####"), D_GRN)
_row(tbl, 5, "Trail", pos_size == 0 ? "--" : trail_active ? "ACTIVE" : "waiting", trail_active ? D_GRN : D_DIM)
_row(tbl, 6, "Bars in trade", pos_size == 0 ? "--" : str.tostring(bars_in_trade) + " / " + str.tostring(i_max_bars > 0 ? i_max_bars : 999), D_BRT)
upnl = pos_size == 0 ? 0.0 : (close - strategy.position_avg_price) * math.abs(pos_size) * (pos_size > 0 ? 1 : -1)
upnl_str = pos_size == 0 ? "--" : (upnl >= 0 ? "+" : "") + str.tostring(upnl, "#.##")
_row(tbl, 7, "Unrealised P&L", upnl_str, upnl >= 0 ? D_GRN : D_RED)
// indicators header
table.cell(tbl, 0, 8, "Indicators", text_color=D_DIM, bgcolor=D_BG, text_size=size.tiny, text_halign=text.align_left)
table.cell(tbl, 1, 8, "", text_color=D_DIM, bgcolor=D_BG)
_row(tbl, 9, "ADX", str.tostring(adx, "#.#") + (is_trending ? " ok" : " --") + (adx_rising ? " rising" : ""), is_trending ? D_GRN : D_RED)
_row(tbl, 10, "DI+ / DI-", str.tostring(di_plus, "#.#") + " / " + str.tostring(di_minus, "#.#"), di_minus > di_plus ? D_RED : D_GRN)
_row(tbl, 11, "RSI", str.tostring(rsi, "#.#"), rsi > 70 ? D_RED : rsi < 30 ? D_GRN : D_BRT)
_row(tbl, 12, "ATR", str.tostring(atr, "#.####"), D_BLU)
_row(tbl, 13, "Vol/MA", str.tostring(volume / vol_ma, "#.##") + "x", volume >= vol_ma * i_vol_mult ? D_GRN : D_DIM)
// filters header
table.cell(tbl, 0, 14, "Filters", text_color=D_DIM, bgcolor=D_BG, text_size=size.tiny, text_halign=text.align_left)
table.cell(tbl, 1, 14, "", text_color=D_DIM, bgcolor=D_BG)
_row(tbl, 15, "Trend", is_trending ? "PASS" : "FAIL", is_trending ? D_GRN : D_RED)
_row(tbl, 16, "ADX slope", adx_rising ? "RISING" : "FLAT/FALL", adx_rising ? D_GRN : D_DIM)
_row(tbl, 17, "DI spread", di_bear_dom ? "BEAR DOM" : di_bull_dom ? "BULL DOM" : "WEAK", di_bear_dom ? D_RED : di_bull_dom ? D_GRN : D_DIM)
_row(tbl, 18, "Momentum", mom_bear ? "DOWN" : mom_bull ? "UP" : "--", mom_bear ? D_RED : mom_bull ? D_GRN : D_DIM)
_row(tbl, 19, "Panic", is_panic ? "ACTIVE" : "OK", is_panic ? D_YEL : D_GRN)
_row(tbl, 20, "ATR floor", atr_floor_ok ? "PASS" : "FAIL", atr_floor_ok ? D_GRN : D_RED)
_row(tbl, 21, "Session", in_session ? "ACTIVE" : "CLOSED", in_session ? D_GRN : D_DIM)
_row(tbl, 22, "Macro bias", ema_bull_full ? "Bullish" : ema_bear_full ? "Bearish" : "Neutral", ema_bull_full ? D_GRN : ema_bear_full ? D_RED : D_ORG)
_row(tbl, 23, "Macro EMA", macro_bear ? "PASS" : "BLOCKED", macro_bear ? D_GRN : D_RED)
// performance header
table.cell(tbl, 0, 24, "Performance", text_color=D_DIM, bgcolor=D_BG, text_size=size.tiny, text_halign=text.align_left)
table.cell(tbl, 1, 24, "", text_color=D_DIM, bgcolor=D_BG)
total_trades = strategy.wintrades + strategy.losstrades
wr_str = total_trades == 0 ? "--" : str.tostring(math.round(strategy.wintrades / total_trades * 100, 1)) + "%"
pf_raw = strategy.grossloss == 0 ? na : strategy.grossprofit / math.abs(strategy.grossloss)
var float _peak_equity = strategy.equity
_peak_equity := math.max(_peak_equity, strategy.equity)
drawdown_pct = _peak_equity == 0 ? 0.0 : ((_peak_equity - strategy.equity) / _peak_equity) * 100.0
_row(tbl, 25, "Net P&L", (strategy.netprofit >= 0 ? "+" : "") + str.tostring(strategy.netprofit, "#.##"), strategy.netprofit >= 0 ? D_GRN : D_RED)
_row(tbl, 26, "Win rate", wr_str + " (" + str.tostring(strategy.wintrades) + "/" + str.tostring(total_trades) + ")", D_BRT)
_row(tbl, 27, "Prof. factor", na(pf_raw) ? "--" : str.tostring(pf_raw, "#.##"), na(pf_raw) ? D_DIM : pf_raw >= 1.0 ? D_GRN : D_RED)
_row(tbl, 28, "Max DD", str.tostring(math.round(drawdown_pct, 2)) + "%", D_RED)
Show all 377 lines