utkarshpathak48 commited on
Commit
3e4e49f
·
1 Parent(s): 9e26574

Sync website settings (account/risk/top/leverage) to Telegram alert loop

Browse files

- New GET/POST /api/alert-config: server-side store the background alert loop reads each 5-min cycle
- _alert_loop now reads account/risk_pct/user_leverage/top from this store, falling back to today's exact original defaults (ALERT_ACCOUNT secret, 6x, 1%, top-30) until the user actively changes a setting
- Bounds-clamped server-side (top 1-50, leverage 1-100x, etc.)
- static/index.html: saveSettings() (existing localStorage persistence, fires on real change/blur only) now also POSTs to /api/alert-config

Fixes: background alert loop previously ignored the dashboard entirely and
used fixed literals, causing Telegram alerts to fire on symbols/sizing the
website never reflected.

Files changed (2) hide show
  1. main.py +74 -8
  2. static/index.html +16 -0
main.py CHANGED
@@ -59,15 +59,47 @@ _SYNTHETIC_PATTERNS = [
59
  def is_synthetic(symbol: str) -> bool:
60
  return any(p.search(symbol) for p in _SYNTHETIC_PATTERNS)
61
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
62
  async def _alert_loop():
63
- """Background task: scan every 5 min and fire Telegram alerts for high-confidence cards."""
 
 
 
 
 
 
 
 
64
  from telegram_alert import send_alert, prune_sent_cache, send_raw_message
65
- # Read account size from HF Secret — defaults to 10 if not set
66
  try:
67
- alert_account = float(os.environ.get("ALERT_ACCOUNT", "10"))
68
  except ValueError:
69
- alert_account = 10.0
70
- logger.info("Alert loop starting — account=$%.2f", alert_account)
71
 
72
  await asyncio.sleep(60) # give app time to fully boot before first scan
73
  loop_count = 0
@@ -78,11 +110,15 @@ async def _alert_loop():
78
  try:
79
  src = get_source()
80
  secondary = get_secondary()
81
- candidates, _synth_excluded = rank_universe(src, src.min_vol, 30)
 
 
 
 
82
  for c in candidates:
83
  try:
84
- card = score_symbol(src, c["symbol"], alert_account, 1.0,
85
- secondary=secondary, user_leverage=6.0)
86
  conf = card.get("confidence", 0)
87
  vetoed = card.get("vetoed", False)
88
  if conf >= 8.8 and not vetoed:
@@ -218,6 +254,36 @@ def status():
218
  return JSONResponse({"ok": False, "error": str(e)}, status_code=503)
219
 
220
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
221
  @app.get("/api/scan")
222
  def scan(top: int = 8, min_volume: float = None, account: float = 100000,
223
  risk_pct: float = 1.0,
 
59
  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
66
+ # alert loop, which hardcoded its own numbers instead. This in-memory store
67
+ # is the shared source of truth both sides now read/write. It only ever
68
+ # contains a key once the user has ACTIVELY changed that setting on the
69
+ # website — untouched keys fall back to today's existing defaults below, so
70
+ # nothing changes for anyone who never touches the settings. Resets on Space
71
+ # restart/redeploy, same as the existing dedup cache (see telegram_alert.py).
72
+ _alert_config: dict = {}
73
+ ALERT_CONFIG_BOUNDS = {
74
+ "top": (1, 50),
75
+ "user_leverage": (1.0, 100.0),
76
+ "risk_pct": (0.01, 100.0),
77
+ "account": (1.0, 10_000_000.0),
78
+ }
79
+
80
+
81
+ def _clamp(key: str, value: float) -> float:
82
+ lo, hi = ALERT_CONFIG_BOUNDS[key]
83
+ return max(lo, min(hi, value))
84
+
85
+
86
  async def _alert_loop():
87
+ """Background task: scan every 5 min and fire Telegram alerts for high-confidence cards.
88
+
89
+ account/risk_pct/user_leverage/top scan-depth are read fresh from
90
+ _alert_config each cycle so the website's live settings govern what the
91
+ background loop does (fixes the website/Telegram discrepancy — previously
92
+ this loop ignored the dashboard entirely and used fixed literals). Falls
93
+ back to the original defaults (ALERT_ACCOUNT secret, 6x, 1%, top-30) for
94
+ any setting the user hasn't touched yet.
95
+ """
96
  from telegram_alert import send_alert, prune_sent_cache, send_raw_message
97
+ # Fallback account size from HF Secret — used only until the website sets a custom one
98
  try:
99
+ default_account = float(os.environ.get("ALERT_ACCOUNT", "10"))
100
  except ValueError:
101
+ default_account = 10.0
102
+ logger.info("Alert loop starting — default account=$%.2f (website can override live)", default_account)
103
 
104
  await asyncio.sleep(60) # give app time to fully boot before first scan
105
  loop_count = 0
 
110
  try:
111
  src = get_source()
112
  secondary = get_secondary()
113
+ alert_account = _alert_config.get("account", default_account)
114
+ alert_risk_pct = _alert_config.get("risk_pct", 1.0)
115
+ alert_leverage = _alert_config.get("user_leverage", 6.0)
116
+ alert_top = int(_alert_config.get("top", 30))
117
+ candidates, _synth_excluded = rank_universe(src, src.min_vol, alert_top)
118
  for c in candidates:
119
  try:
120
+ card = score_symbol(src, c["symbol"], alert_account, alert_risk_pct,
121
+ secondary=secondary, user_leverage=alert_leverage)
122
  conf = card.get("confidence", 0)
123
  vetoed = card.get("vetoed", False)
124
  if conf >= 8.8 and not vetoed:
 
254
  return JSONResponse({"ok": False, "error": str(e)}, status_code=503)
255
 
256
 
257
+ @app.get("/api/alert-config")
258
+ def get_alert_config():
259
+ """Current website->Telegram alert sync settings. is_custom=False means
260
+ the background loop is still using its original hardcoded defaults."""
261
+ return {
262
+ "account": _alert_config.get("account"),
263
+ "risk_pct": _alert_config.get("risk_pct"),
264
+ "top": _alert_config.get("top"),
265
+ "user_leverage": _alert_config.get("user_leverage"),
266
+ "is_custom": bool(_alert_config),
267
+ }
268
+
269
+
270
+ @app.post("/api/alert-config")
271
+ def set_alert_config(account: float = None, risk_pct: float = None,
272
+ top: int = None, user_leverage: float = None):
273
+ """Called by the dashboard whenever the user changes a setting. The
274
+ background alert loop picks these up on its next 5-min cycle."""
275
+ if account is not None:
276
+ _alert_config["account"] = _clamp("account", account)
277
+ if risk_pct is not None:
278
+ _alert_config["risk_pct"] = _clamp("risk_pct", risk_pct)
279
+ if top is not None:
280
+ _alert_config["top"] = int(_clamp("top", top))
281
+ if user_leverage is not None:
282
+ _alert_config["user_leverage"] = _clamp("user_leverage", user_leverage)
283
+ logger.info("Alert config updated from website: %s", _alert_config)
284
+ return {"ok": True, "config": _alert_config}
285
+
286
+
287
  @app.get("/api/scan")
288
  def scan(top: int = 8, min_volume: float = None, account: float = 100000,
289
  risk_pct: float = 1.0,
static/index.html CHANGED
@@ -2739,6 +2739,22 @@ function saveSettings() {
2739
  const out = {};
2740
  SETTINGS_FIELDS.forEach(f => { out[f.id] = $(f.id).value; });
2741
  try { localStorage.setItem(SETTINGS_KEY, JSON.stringify(out)); } catch(e) {}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2742
  }
2743
 
2744
  function loadSettings() {
 
2739
  const out = {};
2740
  SETTINGS_FIELDS.forEach(f => { out[f.id] = $(f.id).value; });
2741
  try { localStorage.setItem(SETTINGS_KEY, JSON.stringify(out)); } catch(e) {}
2742
+ syncAlertConfig();
2743
+ }
2744
+
2745
+ // Push account/risk/top/leverage to the server so the background Telegram
2746
+ // alert loop uses the same settings as the dashboard, instead of its own
2747
+ // fixed defaults. Only fires from real user edits (saveSettings only runs on
2748
+ // 'change'/'blur'), never automatically on page load — so nothing changes
2749
+ // for the alert loop until you actually touch a setting here. Fire-and-forget:
2750
+ // failures never block or affect the on-screen scan.
2751
+ function syncAlertConfig() {
2752
+ const account = +$('account').value || 10000;
2753
+ const risk = +$('risk').value || 1;
2754
+ const top = +$('top').value || 8;
2755
+ const userLev = +$('user-leverage').value || 6;
2756
+ const qs = `account=${account}&risk_pct=${risk}&top=${top}&user_leverage=${userLev}`;
2757
+ fetch(`/api/alert-config?${qs}`, { method: 'POST' }).catch(() => {});
2758
  }
2759
 
2760
  function loadSettings() {