From cac2110b166b0d86445db6bee448518a4b6e4764 Mon Sep 17 00:00:00 2001 From: "2569718930@qq.com" <2569718930@qq.com> Date: Fri, 6 Mar 2026 10:20:42 +0800 Subject: [PATCH] feat: Introduce Telegram trade alert functionality and a `/tradealert` command for previewing alerts. --- bot_listener.py | 79 +++++++++++++++++++++++++++++++++++++- src/utils/telegram_push.py | 13 ++++--- 2 files changed, 86 insertions(+), 6 deletions(-) diff --git a/bot_listener.py b/bot_listener.py index d6854b73..1b79c77a 100644 --- a/bot_listener.py +++ b/bot_listener.py @@ -10,8 +10,9 @@ if project_root not in sys.path: sys.path.insert(0, project_root) from src.utils.config_loader import load_config # type: ignore # noqa: E402 -from src.utils.telegram_push import start_trade_alert_push_loop # type: ignore # noqa: E402 +from src.utils.telegram_push import start_trade_alert_push_loop, build_trade_alert_for_city # type: ignore # noqa: E402 from src.data_collection.weather_sources import WeatherDataCollector # type: ignore # noqa: E402 +from src.data_collection.city_registry import ALIASES, CITY_REGISTRY # type: ignore # noqa: E402 from src.data_collection.city_risk_profiles import get_city_risk_profile # type: ignore # noqa: E402 from src.analysis.deb_algorithm import calculate_dynamic_weights, update_daily_record # noqa: E402 @@ -41,6 +42,7 @@ def start_bot(): "可用指令:\n" "/city [城市名] - 查询城市天气预测与实测\n" "/deb [城市名] - 查看 DEB 融合预测准确率\n" + "/tradealert [城市名] - 预览当前异动预警推送\n" "/id - 获取当前聊天的 Chat ID\n\n" "示例: /city 伦敦" ) @@ -236,6 +238,81 @@ def start_bot(): except Exception as e: bot.reply_to(message, f"❌ 查询失败: {e}") + @bot.message_handler(commands=["tradealert"]) + def tradealert_preview(message): + """Preview the current trade-alert push payload for one city.""" + try: + parts = message.text.split(maxsplit=1) + if len(parts) < 2: + bot.reply_to( + message, + "用法: /tradealert ankara", + parse_mode="HTML", + ) + return + + city_input = parts[1].strip().lower() + city_name = ALIASES.get(city_input) + + if not city_name and city_input in CITY_REGISTRY: + city_name = city_input + + if not city_name and len(city_input) >= 2: + for alias, resolved in ALIASES.items(): + if alias.startswith(city_input): + city_name = resolved + break + if not city_name: + for full_name in CITY_REGISTRY: + if full_name.startswith(city_input): + city_name = full_name + break + + if not city_name: + bot.reply_to( + message, + f"❌ 未找到城市: {city_input}", + parse_mode="HTML", + ) + return + + bot.send_message( + message.chat.id, + f"📡 正在生成 {city_name.title()} 的异动预警预览...", + ) + + payload = build_trade_alert_for_city( + city_name, + config, + force_refresh=True, + ) + + preview = ((payload.get("telegram") or {}).get("zh") or "").strip() + trigger_count = int(payload.get("trigger_count") or 0) + severity = str(payload.get("severity") or "none") + header = ( + f"[TEST PREVIEW] city={city_name} " + f"severity={severity} triggers={trigger_count}" + ) + + if not preview: + bot.reply_to(message, f"{header}\n\nNo Telegram preview available.") + return + + if trigger_count <= 0: + bot.send_message( + message.chat.id, + f"{header}\n\n当前没有 active trade alert。\n\n{preview}", + ) + return + + bot.send_message(message.chat.id, f"{header}\n\n{preview}") + except Exception as e: + import traceback + + logger.error(f"trade alert preview failed: {e}\n{traceback.format_exc()}") + bot.reply_to(message, f"❌ tradealert failed: {e}") + @bot.message_handler(commands=["city"]) def get_city_info(message): """查询指定城市的天气详情""" diff --git a/src/utils/telegram_push.py b/src/utils/telegram_push.py index d33ff080..b971005c 100644 --- a/src/utils/telegram_push.py +++ b/src/utils/telegram_push.py @@ -134,12 +134,16 @@ def _alert_signature(alert_payload: Dict[str, Any]) -> str: return hashlib.sha1(raw.encode("utf-8")).hexdigest() -def _build_trade_alert_for_city(city: str, config: Dict[str, Any]) -> Dict[str, Any]: +def build_trade_alert_for_city( + city: str, + config: Dict[str, Any], + force_refresh: bool = False, +) -> Dict[str, Any]: from web.app import _analyze from src.analysis.market_alert_engine import build_trading_alerts from src.data_collection.polymarket_client import build_city_market_snapshot - city_weather = _analyze(city, force_refresh=False) + city_weather = _analyze(city, force_refresh=force_refresh) target_date = city_weather.get("local_date") proxy = ( @@ -150,7 +154,7 @@ def _build_trade_alert_for_city(city: str, config: Dict[str, Any]) -> Dict[str, city=city, target_date=target_date, proxy=proxy, - force_refresh=False, + force_refresh=force_refresh, ) map_url = os.getenv("POLYWEATHER_MAP_URL") or "https://polyweather.vercel.app" alert_payload = build_trading_alerts( @@ -231,7 +235,7 @@ def start_trade_alert_push_loop(bot: Any, config: Dict[str, Any]) -> Optional[th changed = False for city in cities: try: - alert_payload = _build_trade_alert_for_city(city, config) + alert_payload = build_trade_alert_for_city(city, config) if _maybe_send_alert( bot=bot, chat_id=chat_id, @@ -264,4 +268,3 @@ def start_trade_alert_push_loop(bot: Any, config: Dict[str, Any]) -> Optional[th ) thread.start() return thread -