From 97589531e8039e5ce2a9f62a8d5531714c464f90 Mon Sep 17 00:00:00 2001
From: "2569718930@qq.com" <2569718930@qq.com>
Date: Sun, 8 Feb 2026 03:11:22 +0800
Subject: [PATCH] feat: Add city risk profile data collection.
---
bot_listener.py | 30 ++-
src/data_collection/city_risk_profiles.py | 225 ++++++++++++++++++++++
2 files changed, 250 insertions(+), 5 deletions(-)
create mode 100644 src/data_collection/city_risk_profiles.py
diff --git a/bot_listener.py b/bot_listener.py
index b72f1766..dd5f23dd 100644
--- a/bot_listener.py
+++ b/bot_listener.py
@@ -11,6 +11,7 @@ if project_root not in sys.path:
from src.utils.config_loader import load_config
from src.data_collection.weather_sources import WeatherDataCollector
+from src.data_collection.city_risk_profiles import get_city_risk_profile, format_risk_warning
def analyze_weather_trend(weather_data, temp_symbol):
"""根据实测与预测分析气温态势,增加峰值时刻预测"""
@@ -23,6 +24,7 @@ def analyze_weather_trend(weather_data, temp_symbol):
return ""
curr_temp = metar.get("current", {}).get("temp")
+ max_so_far = metar.get("current", {}).get("max_temp_so_far") # 今日实测最高
daily = open_meteo.get("daily", {})
forecast_high = daily.get("temperature_2m_max", [None])[0]
wind_speed = metar.get("current", {}).get("wind_speed_kt", 0)
@@ -36,17 +38,27 @@ def analyze_weather_trend(weather_data, temp_symbol):
local_date_str = datetime.now().strftime("%Y-%m-%d")
local_hour = datetime.now().hour
- # --- 增加:峰值时刻预测逻辑 ---
+ # === 核心判断:实测是否已超预报 ===
+ if max_so_far is not None and forecast_high is not None:
+ if max_so_far > forecast_high + 0.5:
+ # 实测已超预报!
+ exceed_by = max_so_far - forecast_high
+ insights.append(f"🚨 预报已被击穿:实测最高 {max_so_far}{temp_symbol} 已超预报 {forecast_high}{temp_symbol} 约 {exceed_by:.1f}°!")
+ insights.append(f"💡 博弈建议:市场需重新评估,关注更高温度区间。")
+ # 直接返回,不再显示过时的建议
+ if wind_speed >= 10:
+ insights.append(f"🍃 清劲风:空气流动快,可能伴随阵风引起微小波动。")
+ return "\n💡 态势分析\n" + "\n".join(insights)
+
+ # --- 峰值时刻预测逻辑 ---
hourly = open_meteo.get("hourly", {})
times = hourly.get("time", [])
- # 优先寻找高精模型的逐小时数据
- temps = hourly.get("temperature_2m_hrrr_conus") or hourly.get("temperature_2m_ecmwf_ifs") or hourly.get("temperature_2m", [])
+ temps = hourly.get("temperature_2m", [])
peak_hours = []
if times and temps and forecast_high is not None:
for t_str, temp in zip(times, temps):
if t_str.startswith(local_date_str):
- # 记录所有接近最高温的小时 (容差 0.2)
if abs(temp - forecast_high) <= 0.2:
hour = t_str.split("T")[1][:5]
peak_hours.append(hour)
@@ -54,7 +66,8 @@ def analyze_weather_trend(weather_data, temp_symbol):
if peak_hours:
window = f"{peak_hours[0]} - {peak_hours[-1]}" if len(peak_hours) > 1 else peak_hours[0]
insights.append(f"⏱️ 预计峰值时刻:今天 {window} 之间。")
- if local_hour < int(peak_hours[0].split(":")[0]):
+ # 只有在实测还没超预报时才给这个建议
+ if local_hour < int(peak_hours[0].split(":")[0]) and (max_so_far is None or max_so_far < forecast_high):
insights.append(f"🎯 博弈建议:关注该时段实测能否站稳 {forecast_high}{temp_symbol}。")
if curr_temp is not None and forecast_high is not None:
@@ -177,6 +190,13 @@ def start_bot():
time_only = local_time.split(" ")[1] if " " in local_time else local_time
msg_lines.append(f"🕐 当地时间: {time_only}")
+ # 显示城市风险档案
+ risk_profile = get_city_risk_profile(city_name)
+ if risk_profile:
+ risk_warning = format_risk_warning(risk_profile, temp_symbol)
+ if risk_warning:
+ msg_lines.append(f"\n{risk_warning}")
+
daily = open_meteo.get("daily", {})
dates = daily.get("time", [])
max_temps = daily.get("temperature_2m_max", [])
diff --git a/src/data_collection/city_risk_profiles.py b/src/data_collection/city_risk_profiles.py
new file mode 100644
index 00000000..d3ab4846
--- /dev/null
+++ b/src/data_collection/city_risk_profiles.py
@@ -0,0 +1,225 @@
+# Polymarket 城市温度市场 - 数据偏差风险档案
+# 基于 METAR 机场站与市区实际温度的系统性差异
+
+CITY_RISK_PROFILES = {
+ # 🔴 高危城市 - 数据偏差大,容易误判
+ "seoul": {
+ "risk_level": "high",
+ "risk_emoji": "🔴",
+ "icao": "RKSI",
+ "airport_name": "仁川国际机场",
+ "distance_km": 48.8,
+ "elevation_diff_m": 0,
+ "typical_bias_f": 5.8,
+ "bias_direction": "机场靠海偏暖,市区内陆更冷",
+ "warning": "距离太远,根本不是同一个天气区",
+ "season_notes": None,
+ },
+ "chicago": {
+ "risk_level": "high",
+ "risk_emoji": "🔴",
+ "icao": "KORD",
+ "airport_name": "O'Hare 国际机场",
+ "distance_km": 25.3,
+ "elevation_diff_m": 42,
+ "typical_bias_f": 4.0,
+ "bias_direction": "密歇根湖效应:风向变化时湖边vs内陆可差10°F+",
+ "warning": "冬天温差最不稳定",
+ "season_notes": "冬季",
+ },
+
+ # 🟡 中危城市 - 存在系统偏差,需注意
+ "ankara": {
+ "risk_level": "medium",
+ "risk_emoji": "🟡",
+ "icao": "LTAC",
+ "airport_name": "Esenboğa 机场",
+ "distance_km": 24.5,
+ "elevation_diff_m": 65,
+ "typical_bias_f": 2.0,
+ "bias_direction": "机场海拔更高",
+ "warning": "内陆高原城市,昼夜温差大(可达15°C+)",
+ "season_notes": "下午最高温时偏差会放大",
+ },
+ "london": {
+ "risk_level": "low",
+ "risk_emoji": "🟢",
+ "icao": "EGLC",
+ "airport_name": "London City 机场",
+ "distance_km": 12.7,
+ "elevation_diff_m": 4,
+ "typical_bias_f": 0.5,
+ "bias_direction": "河水调节效应:泰晤士河 Royal Docks 使得夏天偏凉,冬天偏暖",
+ "warning": "极端天气日(热浪/寒潮)偏差会显著放大",
+ "season_notes": None,
+ },
+ "dallas": {
+ "risk_level": "medium",
+ "risk_emoji": "🟡",
+ "icao": "KDAL",
+ "airport_name": "Dallas Love Field 机场",
+ "distance_km": 11.2,
+ "elevation_diff_m": 0,
+ "typical_bias_f": 1.1,
+ "bias_direction": "比 DFW 更接近市中心,数据更准",
+ "warning": "城市热岛效应在夏季午后会使温度略高于郊区",
+ "season_notes": None,
+ },
+ "buenos aires": {
+ "risk_level": "medium",
+ "risk_emoji": "🟡",
+ "icao": "SAEZ",
+ "airport_name": "Ezeiza 国际机场",
+ "distance_km": 28.1,
+ "elevation_diff_m": 0,
+ "typical_bias_f": 1.2,
+ "bias_direction": "夏天城区可比郊区高2-3°C",
+ "warning": "距离远但地形平坦,偏差稳定可预测",
+ "season_notes": "夏季",
+ },
+
+ # 🟢 低危城市 - 数据相对靠谱
+ "toronto": {
+ "risk_level": "low",
+ "risk_emoji": "🟢",
+ "icao": "CYYZ",
+ "airport_name": "Pearson 国际机场",
+ "distance_km": 19.6,
+ "elevation_diff_m": 0,
+ "typical_bias_f": 0.3,
+ "bias_direction": None,
+ "warning": "冬季湖效应偶尔炸裂",
+ "season_notes": "冬季",
+ },
+ "new york": {
+ "risk_level": "low",
+ "risk_emoji": "🟢",
+ "icao": "KLGA",
+ "airport_name": "LaGuardia 机场",
+ "distance_km": 14.5,
+ "elevation_diff_m": 0,
+ "typical_bias_f": 0.7,
+ "bias_direction": "相比 JFK 更靠近曼哈顿",
+ "warning": "东河水汽可能在春季产生微小的降温效果",
+ "season_notes": None,
+ },
+ "seattle": {
+ "risk_level": "low",
+ "risk_emoji": "🟢",
+ "icao": "KSEA",
+ "airport_name": "Sea-Tac 国际机场",
+ "distance_km": 17.4,
+ "elevation_diff_m": 0,
+ "typical_bias_f": 0.6,
+ "bias_direction": "微气候差异存在但较小",
+ "warning": None,
+ "season_notes": None,
+ },
+ "atlanta": {
+ "risk_level": "low",
+ "risk_emoji": "🟢",
+ "icao": "KATL",
+ "airport_name": "Hartsfield-Jackson 机场",
+ "distance_km": 12.6,
+ "elevation_diff_m": 0,
+ "typical_bias_f": 0.5,
+ "bias_direction": None,
+ "warning": None,
+ "season_notes": None,
+ },
+ "miami": {
+ "risk_level": "low",
+ "risk_emoji": "🟢",
+ "icao": "KMIA",
+ "airport_name": "Miami 国际机场",
+ "distance_km": 10.3,
+ "elevation_diff_m": 0,
+ "typical_bias_f": 0.3,
+ "bias_direction": None,
+ "warning": None,
+ "season_notes": None,
+ },
+ "wellington": {
+ "risk_level": "low",
+ "risk_emoji": "🟢",
+ "icao": "NZWN",
+ "airport_name": "Wellington 机场",
+ "distance_km": 5.1,
+ "elevation_diff_m": 0,
+ "typical_bias_f": 0.2,
+ "bias_direction": None,
+ "warning": "12城最近,数据最靠谱",
+ "season_notes": None,
+ },
+}
+
+
+def get_city_risk_profile(city_name: str) -> dict:
+ """获取城市的风险档案"""
+ city_lower = city_name.lower().strip()
+
+ # 别名映射
+ aliases = {
+ "nyc": "new york",
+ "ny": "new york",
+ "chi": "chicago",
+ "atl": "atlanta",
+ "sea": "seattle",
+ "dal": "dallas",
+ "mia": "miami",
+ "tor": "toronto",
+ "ank": "ankara",
+ "sel": "seoul",
+ "wel": "wellington",
+ "ba": "buenos aires",
+ "首尔": "seoul",
+ "芝加哥": "chicago",
+ "纽约": "new york",
+ "伦敦": "london",
+ "达拉斯": "dallas",
+ "迈阿密": "miami",
+ "亚特兰大": "atlanta",
+ "西雅图": "seattle",
+ "多伦多": "toronto",
+ "惠灵顿": "wellington",
+ "安卡拉": "ankara",
+ "布宜诺斯艾利斯": "buenos aires",
+ }
+
+ city_key = aliases.get(city_lower, city_lower)
+ return CITY_RISK_PROFILES.get(city_key)
+
+
+def format_risk_warning(profile: dict, temp_symbol: str) -> str:
+ """格式化风险警告信息"""
+ if not profile:
+ return ""
+
+ lines = []
+
+ # 风险等级标题
+ risk_labels = {
+ "high": "高危",
+ "medium": "中危",
+ "low": "低危"
+ }
+ risk_label = risk_labels.get(profile["risk_level"], "未知")
+ lines.append(f"⚠️ 数据偏差风险: {profile['risk_emoji']} {risk_label}")
+
+ # 机场信息
+ lines.append(f" 📍 机场: {profile['airport_name']} ({profile['icao']})")
+ lines.append(f" 📏 距市区: {profile['distance_km']}km")
+
+ # 典型偏差
+ if profile["typical_bias_f"] >= 1.0:
+ lines.append(f" 📊 典型偏差: ±{profile['typical_bias_f']}{temp_symbol}")
+
+ # 偏差方向说明
+ if profile["bias_direction"]:
+ lines.append(f" 💡 {profile['bias_direction']}")
+
+ # 特别警告
+ if profile["warning"]:
+ lines.append(f" 🚨 {profile['warning']}")
+
+ return "\n".join(lines)