feat: detailed Telegram scan reports with open positions and EV signals
This commit is contained in:
@@ -453,14 +453,49 @@ def tg_signal(city: str, horizon: str, date: str, bucket_label: str,
|
|||||||
)
|
)
|
||||||
send_telegram(msg)
|
send_telegram(msg)
|
||||||
|
|
||||||
def tg_scan_summary(new_trades: int, errors: int, balance: float, cities: int):
|
def tg_scan_summary(new_trades: int, errors: int, balance: float, cities: int,
|
||||||
"""Send a scan summary to Telegram."""
|
top_signals: list = None, open_positions: list = None):
|
||||||
|
"""Send a detailed scan summary to Telegram."""
|
||||||
status_emoji = "✅" if errors == 0 else "⚠️"
|
status_emoji = "✅" if errors == 0 else "⚠️"
|
||||||
msg = (
|
|
||||||
f"🔔 <b>Weather Bot Scan Complete</b>\n"
|
# Build header
|
||||||
f"{status_emoji} Cities: {cities} | New trades: {new_trades} | Errors: {errors}\n"
|
lines = [
|
||||||
f"💰 Balance: <b>${balance:.4f}</b> USDC.e"
|
f"🔔 <b>Weather Bot — Scan Report</b>",
|
||||||
)
|
f"{status_emoji} Cities: {cities} | New trades: {new_trades} | Errors: {errors}",
|
||||||
|
f"💰 Balance: <b>${balance:.4f}</b> USDC.e",
|
||||||
|
]
|
||||||
|
|
||||||
|
# Open positions
|
||||||
|
if open_positions:
|
||||||
|
lines.append("")
|
||||||
|
lines.append(f"📊 <b>Open Positions ({len(open_positions)}):</b>")
|
||||||
|
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("📊 <b>Open Positions:</b> 0")
|
||||||
|
|
||||||
|
# Top signals from this scan
|
||||||
|
if top_signals:
|
||||||
|
lines.append("")
|
||||||
|
lines.append(f"🎯 <b>Top EV Signals ({len(top_signals)} found):</b>")
|
||||||
|
for sig in top_signals[:5]: # Max 5 shown
|
||||||
|
lines.append(
|
||||||
|
f" • {sig['city']} {sig['horizon']} | "
|
||||||
|
f"{sig['bucket']} | EV <b>+{sig['ev']:.2f}</b> | "
|
||||||
|
f"${sig['price']:.3f} (market) vs ${sig['true_prob']:.3f} (model)"
|
||||||
|
)
|
||||||
|
|
||||||
|
msg = "\n".join(lines)
|
||||||
send_telegram(msg)
|
send_telegram(msg)
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
@@ -1115,6 +1150,39 @@ def scan_and_trade():
|
|||||||
print("ok", end="", flush=True)
|
print("ok", end="", flush=True)
|
||||||
print() # newline after city
|
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
|
# Save updated balance
|
||||||
state["balance"] = round(balance, 4)
|
state["balance"] = round(balance, 4)
|
||||||
save_state(state)
|
save_state(state)
|
||||||
@@ -1128,7 +1196,9 @@ def scan_and_trade():
|
|||||||
|
|
||||||
# Telegram scan summary
|
# Telegram scan summary
|
||||||
tg_scan_summary(new_trades=new_trades, errors=len(errors),
|
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
|
return new_trades, errors
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user