Telegram alert redesign: verdict-first layout + informational 100-pip TP

format_signal_message restyled (display-only, zero signal-logic impact):
verdict header first (signal - pair - TF - quality), trade setup moved
above the detail, per-indicator breakdown condensed to one arrow line
(_indicator_summary), heavy dividers dropped. New TP 100p line in every
setup block: entry +/- 100 pips ($10.00 on XAU) with its R-multiple vs
the stop (_INFO_TP_PIPS, formatter-only - not stamped on TradeSetup,
not in CLI/JSON/backtester). Trailing-plan logic unchanged (stamped
per-TF params, pullback fallback). Removed dead _indicator_line and
_SIGNAL_TAG. Tests 210 -> 212 (100p TP math BUY/SELL/no-setup, one-line
indicator summary).

Claude-Session: https://claude.ai/code/session_01BoZKi67Ct6QTeiFJmybXSw
This commit is contained in:
Harwinder Sekhon
2026-07-24 10:58:22 -04:00
parent 7a810c4a4c
commit c3329d6cd5
3 changed files with 127 additions and 58 deletions
+73 -53
View File
@@ -223,15 +223,6 @@ _QUALITY_EMOJI = {
"A+": "🏆", "A": "🥇", "B": "🥈", "C": "🥉", "D": "⚠️", "F": "❌",
}
_SIGNAL_TAG = {
SignalStrength.STRONG_BUY: "Strong Buy",
SignalStrength.BUY: "Bullish",
SignalStrength.NEUTRAL: "Neutral",
SignalStrength.SELL: "Bearish",
SignalStrength.STRONG_SELL: "Strong Sell",
}
def _signal_label(sig: SignalStrength) -> str:
return sig.name.replace("_", " ")
@@ -246,10 +237,36 @@ def _mtf_line(report: SignalReport) -> str:
return " | ".join(parts)
def _indicator_line(name: str, ind) -> str:
tag = _SIGNAL_TAG.get(ind.signal, "Neutral")
short = ind.description.split(" | ")[0] if " | " in ind.description else ind.description
return f"[{tag}] *{_escape_md(ind.name)}*: {_escape_md(short)}"
# Informational fixed-pip target shown on every trade setup (display-only,
# not part of the backtested exit model).
_INFO_TP_PIPS = 100
_IND_SHORT = {
"ema_trend": "EMA", "rsi": "RSI", "macd": "MACD", "bollinger": "BB",
"adx": "ADX", "roc": "ROC", "keltner": "KC", "divergence": "Div",
"candlestick": "Cndl",
}
_IND_ARROW = {
SignalStrength.STRONG_BUY: "⇈",
SignalStrength.BUY: "↑",
SignalStrength.NEUTRAL: "→",
SignalStrength.SELL: "↓",
SignalStrength.STRONG_SELL: "⇊",
}
def _indicator_summary(report: SignalReport) -> str:
"""One-line condensed indicator readout: 'EMA↑ RSI↑ MACD→ …'."""
if not report.primary_analysis:
return ""
parts = []
for name, ind in report.primary_analysis.indicators.items():
if name.startswith("_") or ind.weight == 0:
continue
label = _IND_SHORT.get(name, name)
parts.append(f"{label}{_IND_ARROW.get(ind.signal, '→')}")
return " ".join(parts)
def _live_price_line(symbol: str) -> Optional[str]:
@@ -276,46 +293,26 @@ def format_signal_message(report: SignalReport) -> str:
emoji = _SIGNAL_EMOJI.get(sig, "⬜")
qe = _QUALITY_EMOJI.get(report.signal_quality, "")
label = _signal_label(sig)
mtf_tag = "✅ MTF aligned" if report.mtf_agreement else "❌ MTF diverged"
lines: list[str] = []
mtf_tag = "MTF ✅" if report.mtf_agreement else "MTF ❌"
tf = report.primary_analysis.timeframe if report.primary_analysis else ""
now = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M UTC")
regime_str = _escape_md(report.market_regime.replace("_", " ").title())
vol_str = _escape_md(str(report.volatility_regime))
lines += [
f"🔍 *{report.display_name} Signal Analysis* ({report.primary_analysis.timeframe if report.primary_analysis else ''})",
f"🕐 {now}",
"",
f"💰 Close: *{_fmt_price(report.current_price)}*",
lines: list[str] = [
f"{emoji} *{label} · {report.display_name} · {tf}* {qe}{report.signal_quality}",
f"🕐 {now} · 💰 {_fmt_price(report.current_price)}",
]
live = _live_price_line(report.symbol)
if live:
lines.append(live)
lines += [
f"📊 Regime: {regime_str} | Vol: {vol_str}",
"",
"━━━━━━━━━━━━━━━━━━━━",
f"{emoji} *{label}* {qe}{report.signal_quality} | Score: {report.final_score:+.1f} | {report.final_confidence:.0f}% conf",
f"_{_escape_md(report.action_recommendation)}_",
f"{mtf_tag}",
f"Score *{report.final_score:+.1f}* · Conf *{report.final_confidence:.0f}%* · {mtf_tag}",
f"📊 Regime: {regime_str} · Vol: {vol_str}",
]
mtf = _mtf_line(report)
if mtf:
lines += ["", f"📈 *MTF*: {mtf}"]
if report.primary_analysis:
lines += ["", "━━━━━━━━━━━━━━━━━━━━", "📋 *Indicators*"]
for name, ind in report.primary_analysis.indicators.items():
if name.startswith("_") or ind.weight == 0:
continue
lines.append(_indicator_line(name, ind))
if report.warnings:
lines.append("")
for w in report.warnings:
lines.append(f"⚠️ {_escape_md(w)}")
if report.action_recommendation:
lines.append(f"_{_escape_md(report.action_recommendation)}_")
if report.trade_setup and sig not in (SignalStrength.NEUTRAL,):
ts = report.trade_setup
@@ -324,16 +321,28 @@ def format_signal_message(report: SignalReport) -> str:
reward_usd = ts.reward_pips * ts.pip_value
lines += [
"",
"━━━━━━━━━━━━━━━━━━━━",
f"🚀 *TRADE SETUP*",
"🚀 *TRADE SETUP*",
f"{dir_emoji} Direction: *{ts.direction}*",
f"▶ Entry: `{_fmt_price(ts.entry_price)}`",
f"🛑 SL: `{_fmt_price(ts.stop_loss)}` (${risk_usd:.2f} / {ts.risk_pips:.0f} pips)",
f"🎯 TP1: `{_fmt_price(ts.take_profit_1)}` (${reward_usd:.2f} / {ts.reward_pips:.0f} pips)",
f"▶ Entry `{_fmt_price(ts.entry_price)}`",
f"🛑 SL `{_fmt_price(ts.stop_loss)}` (${risk_usd:.2f} / {ts.risk_pips:.0f}p)",
f"🎯 TP1 `{_fmt_price(ts.take_profit_1)}` (${reward_usd:.2f} / {ts.reward_pips:.0f}p)",
]
if ts.take_profit_2:
lines.append(f"🎯 TP2: `{_fmt_price(ts.take_profit_2)}`")
lines.append(f"⚖️ RR: 1:{ts.risk_reward:.1f}")
lines.append(f"🎯 TP2 `{_fmt_price(ts.take_profit_2)}`")
# Informational fixed-pip target: where a 100-pip TP would sit for
# this setup (display-only — not part of the backtested exit model).
info_dist = _INFO_TP_PIPS * ts.pip_value
if ts.direction == "BUY":
info_price = ts.entry_price + info_dist
else:
info_price = ts.entry_price - info_dist
risk_dist = abs(ts.entry_price - ts.stop_loss)
info_r = f" · ≈{info_dist / risk_dist:.1f}R" if risk_dist > 0 else ""
lines.append(
f"🎯 TP {_INFO_TP_PIPS}p: `{_fmt_price(info_price)}` (${info_dist:.2f}{info_r})"
)
lines.append(f"⚖️ RR 1:{ts.risk_reward:.1f}")
# Trailing-stop management plan (matches backtested exit model:
# TP1 is advisory, the trail is the exit). Params are stamped on
@@ -348,7 +357,6 @@ def format_signal_message(report: SignalReport) -> str:
arm_r = sig_cfg.pb_trail_arm_r if is_pb else sig_cfg.trail_arm_r
dist_r = sig_cfg.pb_trail_dist_r if is_pb else sig_cfg.trail_dist_r
if arm_r > 0:
risk_dist = abs(ts.entry_price - ts.stop_loss)
if ts.direction == "BUY":
arm_price = ts.entry_price + arm_r * risk_dist
else:
@@ -356,12 +364,24 @@ def format_signal_message(report: SignalReport) -> str:
trail_dist = dist_r * risk_dist
tag = " (pullback)" if is_pb else ""
lines.append(
f"🔄 Trail{tag}: once price hits `{_fmt_price(arm_price)}` "
f"🔄 Trail{tag}: arm `{_fmt_price(arm_price)}` "
f"(+{arm_r:.1f}R), trail SL ${trail_dist:.2f} "
f"behind best price — TP1 is a guide, let it run"
)
lines += ["", "━━━━━━━━━━━━━━━━━━━━", _DISCLAIMER]
extra: list[str] = []
mtf = _mtf_line(report)
if mtf:
extra.append(f"📈 MTF: {mtf}")
ind_summary = _indicator_summary(report)
if ind_summary:
extra.append(f"📋 {ind_summary}")
for w in report.warnings:
extra.append(f"⚠️ {_escape_md(w)}")
if extra:
lines += [""] + extra
lines += ["", _DISCLAIMER]
return "\n".join(lines)