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