From be90e610eea74e59a7122fc0525681271733fa9c Mon Sep 17 00:00:00 2001 From: "2569718930@qq.com" <2569718930@qq.com> Date: Mon, 23 Feb 2026 21:04:44 +0800 Subject: [PATCH] feat: Integrate multi-model and ensemble weather forecasts, update READMEs with new architecture and betting strategies, and resolve merge conflicts. --- README.md | 28 ---------- README_ZH.md | 29 ----------- bot_listener.py | 133 ------------------------------------------------ 3 files changed, 190 deletions(-) diff --git a/README.md b/README.md index c5c826a8..cbc5d8e4 100644 --- a/README.md +++ b/README.md @@ -8,11 +8,7 @@ An intelligent weather bot for prediction markets and professional weather betti - **Python 3.11+** - Dependencies: `pip install -r requirements.txt` -<<<<<<< HEAD -- **Environment**: Configure `METEOBLUE_API_KEY` in `.env` to enable high-precision London forecasts. -======= - **Environment Variables**: Set `TELEGRAM_BOT_TOKEN` in `.env` (required). Optionally set `METEOBLUE_API_KEY` for London high-precision forecasts. ->>>>>>> e575440acfd8b5f1e8c30e83dfcb972d26175729 ### VPS Deployment (Recommended) @@ -110,17 +106,7 @@ py -3.11 run.py | **NWS** | Official (US) | US Only | US National Weather Service high-fidelity forecasts | | **MGM** | Observations (Turkey) | Ankara Only | Turkish State Met Service: pressure, cloud cover, feels-like, 24h rainfall | -<<<<<<< HEAD -| Source | Role | Coverage | Strength | -| :----------------- | :---------------------- | :-------------- | :--------------------------------------------------------------------------------- | -| **Open-Meteo** | Base Forecast | Global | Provides detailed 72-hour temperature curves for all cities. | -| **Meteoblue (MB)** | **Precision Consensus** | London Only | **Traders' choice**. Aggregates multiple models; excellent for microclimates. | -| **METAR** | **Settlement Standard** | Global Airports | The absolute truth for Polymarket settlement; real-time station data. | -| **NWS** | Official (US) | US Only | High-fidelity forecasts for US cities, critical for extreme weather events. | -| **MGM** | Official (Turkey) | Ankara | Direct access to Turkish State Meteorological Service for local official accuracy. | -======= > ⚠️ **All NWP model queries use airport coordinates** (matching METAR station), not city center. This eliminates systematic bias between forecast and settlement locations. ->>>>>>> e575440acfd8b5f1e8c30e83dfcb972d26175729 **Open-Meteo API Architecture**: Three API calls go through the same platform, each serving a different purpose: @@ -240,19 +226,12 @@ graph TD Bot --> Collector[WeatherDataCollector] subgraph "Data Engine" -<<<<<<< HEAD - Collector --> OM[Open-Meteo API] - Collector --> MB[Meteoblue Weather API] - Collector --> NOAA[METAR Data Center] - Collector --> MGM[Turkish MGM API] -======= Collector --> MM[Multi-Model API
ECMWF/GFS/ICON/GEM/JMA] Collector --> OM[Open-Meteo Forecast] Collector --> ENS[Open-Meteo Ensemble] Collector --> MB[Meteoblue API] Collector --> NOAA[METAR / NOAA] Collector --> MGM[MGM Observations] ->>>>>>> e575440acfd8b5f1e8c30e83dfcb972d26175729 Collector --> NWS[US NWS API] end @@ -271,12 +250,6 @@ graph TD ## 🎯 Betting Strategy Tips -<<<<<<< HEAD -1. **Check Consensus**: Compare Open-Meteo and Meteoblue (MB). Consensus usually implies higher probability. -2. **Watch the Peak**: Use `/city` frequently during predicted peak windows to catch momentum. -3. **Weighting Hierarchy**: Settlement is **METAR**; high-accuracy trend is **MB** (London); Official (NWS/MGM) is the "anchor." -4. **Geographic Risk**: Pay close attention to cities where "Bias will significantly amplify." -======= 1. **Check Model Consensus**: The 🎯/⚖️/⚠️ rating tells you immediately if the forecast is reliable. 2. **Use the Entry Signal**: Wait for ⏰ **Ideal** or **Good** timing before placing bets. Don't bet early when uncertainty is high. 3. **Watch Ensemble Spread**: A tight 90% band (< 2°) means model confidence is high — this is where edges live. @@ -289,4 +262,3 @@ graph TD --- _Last updated: 2026-02-22_ ->>>>>>> e575440acfd8b5f1e8c30e83dfcb972d26175729 diff --git a/README_ZH.md b/README_ZH.md index ad17e0c5..3fe6e1dc 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -8,11 +8,7 @@ - **Python 3.11+** - 依赖安装: `pip install -r requirements.txt` -<<<<<<< HEAD -- **环境变量**: 需在 `.env` 中配置 `METEOBLUE_API_KEY` 以激活伦敦高精度预报。 -======= - **环境变量**: 在 `.env` 中设置 `TELEGRAM_BOT_TOKEN`(必需)。可选设置 `METEOBLUE_API_KEY` 以激活伦敦高精度预报。 ->>>>>>> e575440acfd8b5f1e8c30e83dfcb972d26175729 ### VPS 部署 (推荐) @@ -110,17 +106,7 @@ py -3.11 run.py | **NWS** | 官方预测(美) | 仅限美国 | 美国国家气象局高精度预报 | | **MGM** | 实测数据(土) | 仅限安卡拉 | 土耳其气象局:气压、云量、体感温度、24h 降水 | -<<<<<<< HEAD -| 数据源 | 数据角色 | 覆盖范围 | 优势 | -| :----------------- | :------------- | :--------- | :----------------------------------------------- | -| **Open-Meteo** | 基础预测 | 全球 | 提供所有城市的 72 小时精细化温度曲线 | -| **Meteoblue (MB)** | **高精度共识** | 仅限伦敦 | **交易员首选**。聚合多家模型,对微气候处理极佳 | -| **METAR** | **结算标准** | 全球机场 | Polymarket 结算参考的绝对真理,实时机场观测 | -| **NWS** | 官方预测(美) | 仅限美国 | 美国国家气象局,对美国城市的极端天气预判准确 | -| **MGM** | 官方预测(土) | 仅限安卡拉 | 土耳其气象局,提供安卡拉 Esenboğa 机场的官方数据 | -======= > ⚠️ **所有 NWP 模型查询使用机场坐标**(与 METAR 站点一致),而非市中心。这消除了预报位置与结算位置之间的系统性偏差。 ->>>>>>> e575440acfd8b5f1e8c30e83dfcb972d26175729 **Open-Meteo API 架构关系**:三个 API 调用都经过 Open-Meteo 平台,但获取的是不同维度的数据: @@ -244,13 +230,6 @@ graph TD User[/Telegram User/] --> Bot[bot_listener.py] Bot --> Collector[WeatherDataCollector] -<<<<<<< HEAD - subgraph "Data Engine" - Collector --> OM[Open-Meteo API] - Collector --> MB[Meteoblue Weather API] - Collector --> NOAA[METAR Data Center] - Collector --> MGM[Turkish MGM API] -======= subgraph "数据引擎" Collector --> MM[多模型 API
ECMWF/GFS/ICON/GEM/JMA] Collector --> OM[Open-Meteo 预报] @@ -258,7 +237,6 @@ graph TD Collector --> MB[Meteoblue API] Collector --> NOAA[METAR / NOAA] Collector --> MGM[MGM 实测数据] ->>>>>>> e575440acfd8b5f1e8c30e83dfcb972d26175729 Collector --> NWS[US NWS API] end @@ -277,12 +255,6 @@ graph TD ## 🎯 博弈策略提示 -<<<<<<< HEAD -1. **检查模型共识**:查看 Open-Meteo 和 Meteoblue (MB) 是否达成共识。 -2. **关注峰值窗口**:在预测的峰值时段多次使用 `/city` 刷新。 -3. **数据权重优先级**:结算以 **METAR** 为准,趋势预测以 **MB** 为准(仅限伦敦)。 -4. **地理风险评估**:重点关注提示中的“偏差会显著放大”警告(如安卡拉、伦敦)。 -======= 1. **看模型共识**:🎯/⚖️/⚠️ 评级让你一眼判断预报是否可靠。高共识 + 市场低定价 = 套利机会。 2. **用入场信号**:等 ⏰ **理想** 或 **较好** 时机再下注。不确定性高时绝不提前入场。 3. **关注集合散度**:90% 区间越窄(< 2°),模型置信越高 — 这才是 edge 所在。 @@ -295,4 +267,3 @@ graph TD --- _最后更新: 2026-02-22_ ->>>>>>> e575440acfd8b5f1e8c30e83dfcb972d26175729 diff --git a/bot_listener.py b/bot_listener.py index a7952b80..79dd633c 100644 --- a/bot_listener.py +++ b/bot_listener.py @@ -37,29 +37,21 @@ def analyze_weather_trend(weather_data, temp_symbol): forecast_highs.append(mb["today_high"]) if nws.get("today_high") is not None: forecast_highs.append(nws["today_high"]) -<<<<<<< HEAD - if mgm.get("today_high") is not None: - forecast_highs.append(mgm["today_high"]) -======= # 加入多模型预报 (ECMWF, GFS, ICON, GEM, JMA) for mv in weather_data.get("multi_model", {}).get("forecasts", {}).values(): if mv is not None: forecast_highs.append(mv) ->>>>>>> e575440acfd8b5f1e8c30e83dfcb972d26175729 forecast_highs = [h for h in forecast_highs if h is not None] # 取预报中的最高值作为风险防御基准 forecast_high = max(forecast_highs) if forecast_highs else None # 取最低值用于判断是否“已触及预报高位” min_forecast_high = min(forecast_highs) if forecast_highs else forecast_high -<<<<<<< HEAD -======= # 取中位数作为用户可见的"预期值"(避免极端模型误导) forecast_median = None if forecast_highs: sorted_fh = sorted(forecast_highs) forecast_median = sorted_fh[len(sorted_fh) // 2] ->>>>>>> e575440acfd8b5f1e8c30e83dfcb972d26175729 wind_speed = metar.get("current", {}).get("wind_speed_kt", 0) @@ -186,14 +178,6 @@ def analyze_weather_trend(weather_data, temp_symbol): is_breakthrough = False if max_so_far is not None and forecast_high is not None: if max_so_far > forecast_high + 0.5: -<<<<<<< HEAD - # 实测已超所有预报! - 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"💡 博弈建议:市场需重新评估,当前可能存在极端异常增温。") - return "\n💡 态势分析\n" + "\n".join(insights) - -======= is_breakthrough = True exceed_by = max_so_far - forecast_high insights.append(f"🚨 实测已超预报:实测最高 {max_so_far}{temp_symbol} 超过了所有预报的天花板 {forecast_high}{temp_symbol},多了 {exceed_by:.1f}°!") @@ -221,7 +205,6 @@ def analyze_weather_trend(weather_data, temp_symbol): f"刚刚越过进位线,再降 {fractional - 0.5:.1f}° 就会回落到 {settled - 1}{temp_symbol}。" ) ->>>>>>> e575440acfd8b5f1e8c30e83dfcb972d26175729 # --- 峰值时刻预测逻辑 (仍以 Open-Meteo 逐小时数据为准) --- hourly = open_meteo.get("hourly", {}) times = hourly.get("time", []) @@ -236,48 +219,6 @@ def analyze_weather_trend(weather_data, temp_symbol): hour = t_str.split("T")[1][:5] peak_hours.append(hour) -<<<<<<< HEAD - 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]) and (max_so_far is None or max_so_far < forecast_high): - insights.append(f"🎯 博弈建议:关注该时段实测能否站稳 {forecast_high}{temp_symbol}。") - - is_peak_passed = False - if curr_temp is not None and forecast_high is not None: - diff_max = forecast_high - curr_temp - - # 1. 气温节奏判定 (动态参考峰值时刻) - last_peak_h = int(peak_hours[-1].split(":")[0]) if peak_hours else 15 - first_peak_h = int(peak_hours[0].split(":")[0]) if peak_hours else 13 - - if local_hour > last_peak_h: - # 已经过了预报的峰值时段 - is_peak_passed = True - # 如果实测已经接近“任一”主流预报的最高温 (使用 min_forecast_high) - if max_so_far and max_so_far >= min_forecast_high - 0.5: - insights.append(f"✅ 今日峰值已过:气温已触及或接近预报最高,目前处于高位波动或缓慢回落。") - else: - # 虽然时间过了,但离最高温还有差距 - insights.append(f"📉 处于降温期:已过预报峰值时段,且当前气温乏力 ({curr_temp}{temp_symbol}),冲击最高预报 {forecast_high}{temp_symbol} 的概率降低。") - elif first_peak_h <= local_hour <= last_peak_h: - # 正在峰值窗口内 - if diff_max <= 0.8: - insights.append(f"⚖️ 高位横盘:正处于预测峰值时段,气温将在当前水平小幅波动。") - else: - insights.append(f"⏳ 峰值窗口中:虽在预报高点时段,但目前仍有差距,紧盯最后冲刺。") - elif local_hour < first_peak_h: - # 还没到峰值窗口 - if diff_max > 1.2: - insights.append(f"📈 升温进程中:距离峰值时段还有 {first_peak_h - local_hour}h,正向高点冲击。") - else: - insights.append(f"🌅 临近峰值:即将进入高点时段,气温已处于预报高位。") - else: - # 回退逻辑 - insights.append(f"🌌 夜间/早间:等待日出后的新一轮波动。") - -======= # 确定用于逻辑判断的峰值小时 if peak_hours: first_peak_h = int(peak_hours[0].split(":")[0]) @@ -331,43 +272,20 @@ def analyze_weather_trend(weather_data, temp_symbol): # 回退逻辑 insights.append(f"🌌 夜间:等明天太阳出来后再看新一轮升温。") ->>>>>>> e575440acfd8b5f1e8c30e83dfcb972d26175729 # 2. 湿度与露点分析 (仅在傍晚以后) humidity = metar.get("current", {}).get("humidity") dewpoint = metar.get("current", {}).get("dewpoint") if local_hour >= 18: if humidity and humidity > 80: -<<<<<<< HEAD - insights.append(f"💦 闷热高湿:湿度极高 ({humidity}%),将显著锁住夜间热量。") - if dewpoint is not None and curr_temp - dewpoint < 2.0: - insights.append(f"🌡️ 触及露点支撑:气温已跌至露点支撑位,降温将变慢。") -======= insights.append(f"💦 湿度很高:湿度 {humidity}%,空气很潮湿,夜里热量散不掉,降温会很慢。") if dewpoint is not None and curr_temp - dewpoint < 2.0: insights.append(f"🌡️ 降温快到底了:温度已经接近露点(空气中水汽开始凝结的温度),再往下降会很困难。") ->>>>>>> e575440acfd8b5f1e8c30e83dfcb972d26175729 # 3. 风力 if wind_speed >= 15: insights.append(f"🌬️ 风很大:风速 {wind_speed}kt,温度可能会忽高忽低。") elif wind_speed >= 10: -<<<<<<< HEAD - insights.append(f"🍃 清劲风:空气流动快,虽然有助于散热,但在升温期可能带来暖平流加速。") - - # 4. 云层遮挡分析 (仅在升温期/峰值期有意义) - clouds = metar.get("current", {}).get("clouds", []) - if clouds and local_hour <= last_peak_h + 1: - main_cloud = clouds[-1] - cover = main_cloud.get("cover", "") - if cover == "OVC": - insights.append(f"☁️ 全阴锁温:机场上空完全遮挡,阳光增温几乎停滞,很难再冲高点。") - elif cover == "BKN": - insights.append(f"🌥️ 云层显著:天空大部被遮挡,日照受限,升温斜率受阻。") - elif cover in ["SKC", "CLR", "FEW"]: - if not is_peak_passed: - insights.append(f"☀️ 晴空万里:日照强烈,无云层遮挡,气温有冲向预报上限甚至超出的动能。") -======= insights.append(f"🍃 有风:风速适中 ({wind_speed}kt),会加速空气流动,具体影响看风向。") # 4. 云层遮挡分析 (仅在升温期/峰值期有意义) @@ -381,38 +299,12 @@ def analyze_weather_trend(weather_data, temp_symbol): insights.append(f"🌥️ 云比较多:天空大部分被云挡住了,日照不足,升温会比较慢。") elif cover in ["SKC", "CLR", "FEW"]: insights.append(f"☀️ 大晴天:阳光直射,没什么云,有利于温度继续往上冲。") ->>>>>>> e575440acfd8b5f1e8c30e83dfcb972d26175729 # 5. 特殊天气现象 wx_desc = metar.get("current", {}).get("wx_desc") has_mgm = bool(mgm.get("current")) mgm_rain = mgm.get("current", {}).get("rain_24h") if wx_desc: -<<<<<<< HEAD - if any(x in wx_desc.upper() for x in ["RA", "DZ", "RAIN", "DRIZZLE"]): - insights.append(f"🌧️ 降雨压制:当前有降雨,蒸发吸热将显著抑制升温。") - elif any(x in wx_desc.upper() for x in ["SN", "SNOW", "GR", "GS"]): - insights.append(f"❄️ 固态降水:正在降雪或冰雹,气温将持续低迷。") - elif any(x in wx_desc.upper() for x in ["FG", "BR", "HZ", "FOG", "MIST"]): - insights.append(f"🌫️ 能见度受限:当前有雾/霭,阻挡阳光并带来高湿,会大幅延缓升温周期。") - - # 6. 风向平流分析 (仅在未进入降温期前显示) - if not is_peak_passed or local_hour <= last_peak_h + 2: - try: - wind_dir = float(metar.get("current", {}).get("wind_dir", 0)) - # 北半球简化逻辑:北风 cold,南风 warm - if 315 <= wind_dir or wind_dir <= 45: - insights.append(f"🌬️ 偏北风:冷空气处于主导地位,午后增温阻力较大。") - elif 135 <= wind_dir <= 225: - # 只有在当前温度离最高预测还有距离时,南风才有意义 - if diff_max > 0.5: - if is_peak_passed: - insights.append(f"🔥 偏南风:存在暖平流支撑,但已过传统峰值时段,冲击上限 {forecast_high}{temp_symbol} 的动能正在衰减。") - else: - insights.append(f"🔥 偏南风:正从低纬度输送暖平流,气温仍有向上突围的潜力。") - except (TypeError, ValueError): - pass -======= wx_upper = wx_desc.upper().strip() wx_tokens = wx_upper.split() # 用分词匹配,避免 "METAR" 中的 "RA" 误判 @@ -483,7 +375,6 @@ def analyze_weather_trend(weather_data, temp_symbol): insights.append(f"🌬️ 吹东风({wind_source} {wd:.0f}°):对温度影响较小,主要看日照和云量。") except (TypeError, ValueError): pass ->>>>>>> e575440acfd8b5f1e8c30e83dfcb972d26175729 try: visibility = metar.get("current", {}).get("visibility_mi") @@ -494,16 +385,6 @@ def analyze_weather_trend(weather_data, temp_symbol): except (TypeError, ValueError): pass -<<<<<<< HEAD - # 7. 模型准确度预警 (针对用户反馈的 MB 偏高问题) - if is_peak_passed and max_so_far is not None: - model_checks = [] - if om_high and om_high > max_so_far + 1.5: - model_checks.append(f"Open-Meteo ({om_high}{temp_symbol})") - mb_h = mb.get("today_high") - if mb_h and mb_h > max_so_far + 1.5: - model_checks.append(f"Meteoblue ({mb_h}{temp_symbol})") -======= # 7. 模型准确度预警(使用多模型数据) if is_peak_passed and max_so_far is not None: model_checks = [] @@ -514,16 +395,11 @@ def analyze_weather_trend(weather_data, temp_symbol): mb_h = mb.get("today_high") if mb_h and mb_h > max_so_far + 1.5: model_checks.append(f"MB ({mb_h}{temp_symbol})") ->>>>>>> e575440acfd8b5f1e8c30e83dfcb972d26175729 nws_h = nws.get("today_high") if nws_h and nws_h > max_so_far + 1.5: model_checks.append(f"NWS ({nws_h}{temp_symbol})") if model_checks: -<<<<<<< HEAD - insights.append(f"⚠️ 预报偏高:目前实测远低于 " + "、".join(model_checks) + ",判定预报模型今日表现过度乐观。") - -======= insights.append(f"⚠️ 预报偏高了:实测远低于 " + "、".join(model_checks) + ",这些模型今天报高了。") # 8. MGM 气压分析 (仅安卡拉) @@ -638,7 +514,6 @@ def analyze_weather_trend(weather_data, temp_symbol): insights.append(f"⏰ 入场时机:谨慎 — {factors_str}。建议继续观察。") else: insights.append(f"⏰ 入场时机:不建议 — {factors_str}。不确定性大,等更多数据。") ->>>>>>> e575440acfd8b5f1e8c30e83dfcb972d26175729 if not insights: return "" @@ -708,13 +583,6 @@ def start_bot(): "par": "paris", "巴黎": "paris", } -<<<<<<< HEAD - # 1. 第一优先级:严格全字匹配 - city_name = STANDARD_MAPPING.get(city_input) - - # 2. 第二优先级:如果长度 >= 3,尝试前缀匹配 - if not city_name and len(city_input) >= 3: -======= # 支持的城市全名列表(用于模糊匹配) SUPPORTED_CITIES = list(set(STANDARD_MAPPING.values())) @@ -728,7 +596,6 @@ def start_bot(): # 3. 第三优先级:前缀匹配(在别名和城市全名中搜索) if not city_name and len(city_input) >= 2: # 先搜别名 ->>>>>>> e575440acfd8b5f1e8c30e83dfcb972d26175729 for k, v in STANDARD_MAPPING.items(): if k.startswith(city_input): city_name = v