utkarshpathak48 commited on
Commit
fd39eb4
·
1 Parent(s): 8de16b3

Alert gate: confidence >=7.0 AND PA grade A/A+ (was conf>=8.8, no grade check)

Browse files
Files changed (2) hide show
  1. main.py +24 -7
  2. telegram_alert.py +19 -4
main.py CHANGED
@@ -60,6 +60,27 @@ def is_synthetic(symbol: str) -> bool:
60
  return any(p.search(symbol) for p in _SYNTHETIC_PATTERNS)
61
 
62
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
63
  # ── Website -> Telegram alert config sync ──────────────────────────────────
64
  # Root cause fixed here: the dashboard's account/risk/top/leverage settings
65
  # used to live only in the browser tab and were never seen by the background
@@ -120,9 +141,7 @@ async def _alert_loop():
120
  try:
121
  card = score_symbol(src, c["symbol"], alert_account, alert_risk_pct,
122
  secondary=secondary, user_leverage=alert_leverage)
123
- conf = card.get("confidence", 0)
124
- vetoed = card.get("vetoed", False)
125
- if conf >= 8.8 and not vetoed:
126
  tv_sym = src.tv_symbol(card["symbol"])
127
  sent = await send_alert(card, tv_sym)
128
  # Only log alerts that actually reached Telegram (sent=True) —
@@ -331,11 +350,9 @@ def scan(top: int = 8, min_volume: float = None, account: float = 100000,
331
  log_signal(card)
332
  except Exception:
333
  pass
334
- # Fire Telegram alert if confidence >= 9.0
335
  try:
336
- conf = card.get("confidence", 0)
337
- vetoed = card.get("vetoed", False)
338
- if conf >= 8.8 and not vetoed and _event_loop is not None:
339
  from telegram_alert import send_alert
340
  tv_sym = src.tv_symbol(card["symbol"])
341
  asyncio.run_coroutine_threadsafe(
 
60
  return any(p.search(symbol) for p in _SYNTHETIC_PATTERNS)
61
 
62
 
63
+ # ── Alert trigger rule (confidence + PA confluence grade) ──────────────────
64
+ # Changed 13 Jul 2026 per explicit instruction: previously any card with
65
+ # confidence >= 8.8 fired an alert, no grade check. Now the confidence floor
66
+ # is lowered to 7.0, but a chart-pattern (confluence) grade of A or A+ is
67
+ # REQUIRED regardless of how high confidence is — grade B/C/D never fires,
68
+ # even at confidence 9+. Single shared gate used by both the background loop
69
+ # and manual /api/scan so the two paths can't drift apart again (that exact
70
+ # drift was the earlier website/Telegram discrepancy bug).
71
+ ALERT_CONF_MIN = 7.0
72
+ ALERT_GRADES_OK = {"A+", "A"}
73
+
74
+
75
+ def _passes_alert_gate(card: dict) -> bool:
76
+ if card.get("vetoed", False):
77
+ return False
78
+ if card.get("confidence", 0) < ALERT_CONF_MIN:
79
+ return False
80
+ grade = (card.get("price_action") or {}).get("confluence_grade")
81
+ return grade in ALERT_GRADES_OK
82
+
83
+
84
  # ── Website -> Telegram alert config sync ──────────────────────────────────
85
  # Root cause fixed here: the dashboard's account/risk/top/leverage settings
86
  # used to live only in the browser tab and were never seen by the background
 
141
  try:
142
  card = score_symbol(src, c["symbol"], alert_account, alert_risk_pct,
143
  secondary=secondary, user_leverage=alert_leverage)
144
+ if _passes_alert_gate(card):
 
 
145
  tv_sym = src.tv_symbol(card["symbol"])
146
  sent = await send_alert(card, tv_sym)
147
  # Only log alerts that actually reached Telegram (sent=True) —
 
350
  log_signal(card)
351
  except Exception:
352
  pass
353
+ # Fire Telegram alert per _passes_alert_gate (confidence >= 7.0 AND PA grade A/A+)
354
  try:
355
+ if _passes_alert_gate(card) and _event_loop is not None:
 
 
356
  from telegram_alert import send_alert
357
  tv_sym = src.tv_symbol(card["symbol"])
358
  asyncio.run_coroutine_threadsafe(
telegram_alert.py CHANGED
@@ -106,15 +106,28 @@ def _rr(card: dict) -> str:
106
  def _tier_line(conf: float) -> str:
107
  """Confidence tier prefix (Strategy Fix Plan F1a).
108
 
109
- Alert threshold stays 8.8 (data collection continues); this only labels
110
- the message so Utkarsh can tell trade-grade from watch-only at a glance.
111
- Auto-trade gate (CONF_MIN=9.0, per ADDENDUM) is a separate, later concern.
 
112
  """
113
  if conf >= 9.0:
114
  return "💎 <b>TRADE-GRADE</b>"
115
  if conf >= 8.8:
116
  return "🔭 <b>WATCH ONLY</b> — below auto-trade bar"
117
- return ""
 
 
 
 
 
 
 
 
 
 
 
 
118
 
119
 
120
  def _momentum_line(card: dict) -> str:
@@ -184,12 +197,14 @@ def build_message(card: dict, tv_sym: str) -> str:
184
  )
185
  flow_line = _order_flow_line(card)
186
  tier_line = _tier_line(float(conf))
 
187
  momentum_line = _momentum_line(card)
188
  tp_dyn = lvl.get("tp_dyn")
189
 
190
  lines = [
191
  *([tier_line] if tier_line else []),
192
  f"{dir_emoji} <b>{sym} {direction} · {conf:.1f}/10</b>",
 
193
  "",
194
  f"📍 <b>Entry</b> <code>{_fmt_price(card.get('close'))}</code>",
195
  f"🛑 <b>SL</b> <code>{_fmt_price(lvl.get('sl'))}</code>",
 
106
  def _tier_line(conf: float) -> str:
107
  """Confidence tier prefix (Strategy Fix Plan F1a).
108
 
109
+ Cosmetic label only — the actual send/no-send decision lives in
110
+ main.py's _passes_alert_gate() (confidence >= 7.0 AND PA grade A/A+,
111
+ changed 13 Jul 2026). This just tells Utkarsh at a glance where in the
112
+ confidence range a delivered alert sits.
113
  """
114
  if conf >= 9.0:
115
  return "💎 <b>TRADE-GRADE</b>"
116
  if conf >= 8.8:
117
  return "🔭 <b>WATCH ONLY</b> — below auto-trade bar"
118
+ if conf >= 8.0:
119
+ return "🟡 <b>STRONG</b>"
120
+ return "🟠 <b>WATCH</b>"
121
+
122
+
123
+ def _grade_line(card: dict) -> str:
124
+ """PA confluence grade line — required by the alert gate itself now, so
125
+ surface it on every message (grade is always A or A+ for delivered
126
+ alerts, since B/C/D never pass _passes_alert_gate)."""
127
+ grade = (card.get("price_action") or {}).get("confluence_grade")
128
+ if not grade:
129
+ return ""
130
+ return f"📐 <b>Grade {grade}</b>"
131
 
132
 
133
  def _momentum_line(card: dict) -> str:
 
197
  )
198
  flow_line = _order_flow_line(card)
199
  tier_line = _tier_line(float(conf))
200
+ grade_line = _grade_line(card)
201
  momentum_line = _momentum_line(card)
202
  tp_dyn = lvl.get("tp_dyn")
203
 
204
  lines = [
205
  *([tier_line] if tier_line else []),
206
  f"{dir_emoji} <b>{sym} {direction} · {conf:.1f}/10</b>",
207
+ *([grade_line] if grade_line else []),
208
  "",
209
  f"📍 <b>Entry</b> <code>{_fmt_price(card.get('close'))}</code>",
210
  f"🛑 <b>SL</b> <code>{_fmt_price(lvl.get('sl'))}</code>",