From 69f57a9ff92e9dfbafa92da99ceba32b1abd672c Mon Sep 17 00:00:00 2001 From: John Doe Date: Sat, 18 Apr 2026 19:28:46 +0800 Subject: [PATCH] feat: detailed Telegram scan reports with open positions and EV signals --- bot_v3.py | 86 +++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 78 insertions(+), 8 deletions(-) diff --git a/bot_v3.py b/bot_v3.py index 86f4c71..7c7bb58 100644 --- a/bot_v3.py +++ b/bot_v3.py @@ -453,14 +453,49 @@ def tg_signal(city: str, horizon: str, date: str, bucket_label: str, ) send_telegram(msg) -def tg_scan_summary(new_trades: int, errors: int, balance: float, cities: int): - """Send a scan summary to Telegram.""" +def tg_scan_summary(new_trades: int, errors: int, balance: float, cities: int, + top_signals: list = None, open_positions: list = None): + """Send a detailed scan summary to Telegram.""" status_emoji = "✅" if errors == 0 else "⚠️" - msg = ( - f"🔔 Weather Bot Scan Complete\n" - f"{status_emoji} Cities: {cities} | New trades: {new_trades} | Errors: {errors}\n" - f"💰 Balance: ${balance:.4f} USDC.e" - ) + + # Build header + lines = [ + f"🔔 Weather Bot — Scan Report", + f"{status_emoji} Cities: {cities} | New trades: {new_trades} | Errors: {errors}", + f"💰 Balance: ${balance:.4f} USDC.e", + ] + + # Open positions + if open_positions: + lines.append("") + lines.append(f"📊 Open Positions ({len(open_positions)}):") + for pos in open_positions[:5]: # Max 5 shown + label = f"{pos['bucket_low']}-{pos['bucket_high']}°F" + pnl_str = f"${pos.get('pnl', 0):.2f}" if pos.get('pnl') else "pending" + entry = pos.get('entry_price', 0) + cost = pos.get('cost', 0) + lines.append( + f" • {pos['city_name']} {pos['date']} | {label} | " + f"entry ${entry:.3f} | cost ${cost:.2f} | PnL {pnl_str}" + ) + if len(open_positions) > 5: + lines.append(f" ...and {len(open_positions) - 5} more") + else: + lines.append("") + lines.append("📊 Open Positions: 0") + + # Top signals from this scan + if top_signals: + lines.append("") + lines.append(f"🎯 Top EV Signals ({len(top_signals)} found):") + for sig in top_signals[:5]: # Max 5 shown + lines.append( + f" • {sig['city']} {sig['horizon']} | " + f"{sig['bucket']} | EV +{sig['ev']:.2f} | " + f"${sig['price']:.3f} (market) vs ${sig['true_prob']:.3f} (model)" + ) + + msg = "\n".join(lines) send_telegram(msg) # ============================================================================= @@ -1115,6 +1150,39 @@ def scan_and_trade(): print("ok", end="", flush=True) print() # newline after city + # Build top signals from this scan for Telegram + top_signals = [] + for city_slug, loc, outcomes, forecastsnap, horizon, end_date, date in city_market_data: + if not outcomes or not forecastsnap: + continue + forecast_temp = forecastsnap.get("temp") + if forecast_temp is None: + continue + sigma = get_sigma(city_slug) + for o in outcomes: + t_low, t_high = o["range"] + if not in_bucket(forecast_temp, t_low, t_high): + continue + p = bucket_prob(forecast_temp, t_low, t_high, sigma) + ev = calc_ev(p, o["ask"]) + if ev > 0: + top_signals.append({ + "city": loc["name"], + "horizon": horizon, + "bucket": f"{t_low}-{t_high}°F", + "ev": ev, + "price": o["ask"], + "true_prob": p, + }) + top_signals.sort(key=lambda x: x["ev"], reverse=True) + + # Open positions + markets = load_all_markets() + open_positions = [ + m for m in markets + if m.get("position") and m["position"].get("status") == "open" + ] + # Save updated balance state["balance"] = round(balance, 4) save_state(state) @@ -1128,7 +1196,9 @@ def scan_and_trade(): # Telegram scan summary tg_scan_summary(new_trades=new_trades, errors=len(errors), - balance=balance, cities=len(LOCATIONS)) + balance=balance, cities=len(LOCATIONS), + top_signals=top_signals, + open_positions=open_positions) return new_trades, errors