HK Weather Prediction Market Pipeline: WeatherNext + HKO + Polymarket
- Open-Meteo WeatherNext API client for HK forecasts - HKO public data client (current conditions, 9-day forecast, typhoon warnings) - HK-specific weather extraction and calibration - Polymarket market scanning, price discovery, and market creation proposals - Trading strategy engine: edge detection, Kelly criterion sizing, probability calibration - End-to-end pipeline with dry-run mode and scheduled runner - Interactive dashboard with live HK weather + forecasts + trading signals Dependencies: Python 3.10+, openmeteo-requests, pandas No API keys needed for dry-run mode. Polymarket trading requires private key in .env.
This commit is contained in:
@@ -0,0 +1,177 @@
|
||||
"""Hong Kong Observatory data client.
|
||||
|
||||
Fetches real-time weather observations, warnings, and forecasts from HKO.
|
||||
Uses public RSS/JSON feeds where available.
|
||||
"""
|
||||
|
||||
import json
|
||||
from datetime import datetime
|
||||
from typing import Optional, List
|
||||
|
||||
import requests
|
||||
|
||||
|
||||
class HKOClient:
|
||||
"""Fetch data from Hong Kong Observatory's public data feeds."""
|
||||
|
||||
BASE_URL = "https://data.weather.gov.hk"
|
||||
|
||||
ENDPOINTS = {
|
||||
"current_weather": f"{BASE_URL}/weatherAPI/opendata/weather.php?dataType=rhrread&lang=en",
|
||||
"9day_forecast": f"{BASE_URL}/weatherAPI/opendata/weather.php?dataType=fnd&lang=en",
|
||||
"warnings": f"{BASE_URL}/weatherAPI/opendata/weather.php?dataType=warnsum&lang=en",
|
||||
"local_forecast": f"{BASE_URL}/weatherAPI/opendata/weather.php?dataType=flw&lang=en",
|
||||
"swt": f"{BASE_URL}/weatherAPI/opendata/weather.php?dataType=swt&lang=en",
|
||||
"rainfall": f"{BASE_URL}/weatherAPI/opendata/weather.php?dataType=rfmap",
|
||||
"lightning": f"{BASE_URL}/weatherAPI/opendata/weather.php?dataType=lmap",
|
||||
"uv_index": f"{BASE_URL}/weatherAPI/opendata/weather.php?dataType=uvi",
|
||||
"earthquake": f"{BASE_URL}/weatherAPI/opendata/weather.php?dataType=eq",
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
self.session = requests.Session()
|
||||
self.session.headers.update({
|
||||
"User-Agent": "HK-Weather-Market/1.0",
|
||||
"Accept": "application/json",
|
||||
})
|
||||
|
||||
def get_current_weather(self) -> Optional[dict]:
|
||||
"""Get current weather observations for Hong Kong."""
|
||||
try:
|
||||
resp = self.session.get(self.ENDPOINTS["current_weather"], timeout=15)
|
||||
resp.raise_for_status()
|
||||
data = resp.json()
|
||||
|
||||
result = {
|
||||
"update_time": data.get("updateTime", ""),
|
||||
"temperature": [],
|
||||
"humidity": [],
|
||||
"rainfall": [],
|
||||
"warning_message": data.get("warningMessage", ""),
|
||||
"rainstorm_reminder": data.get("rainstormReminder", ""),
|
||||
"mintemp_from00to09": data.get("mintempFrom00To09", ""),
|
||||
}
|
||||
|
||||
# Parse temperature data
|
||||
for record in data.get("temperature", {}).get("data", []):
|
||||
result["temperature"].append({
|
||||
"place": record.get("place"),
|
||||
"value": record.get("value"),
|
||||
"unit": record.get("unit", "C"),
|
||||
})
|
||||
|
||||
for record in data.get("humidity", {}).get("data", []):
|
||||
result["humidity"].append({
|
||||
"place": record.get("place"),
|
||||
"value": record.get("value"),
|
||||
"unit": record.get("unit", "%"),
|
||||
})
|
||||
|
||||
# Parse rainfall
|
||||
for record in data.get("rainfall", {}).get("data", []):
|
||||
result["rainfall"].append({
|
||||
"place": record.get("place"),
|
||||
"max": record.get("max"),
|
||||
"min": record.get("min"),
|
||||
"unit": record.get("unit", "mm"),
|
||||
})
|
||||
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
print(f"HKO current weather error: {e}")
|
||||
return None
|
||||
|
||||
def get_forecast(self) -> Optional[List[dict]]:
|
||||
"""Get 9-day weather forecast from HKO."""
|
||||
try:
|
||||
resp = self.session.get(self.ENDPOINTS["9day_forecast"], timeout=15)
|
||||
resp.raise_for_status()
|
||||
data = resp.json()
|
||||
|
||||
forecasts = []
|
||||
for day in data.get("weatherForecast", []):
|
||||
forecasts.append({
|
||||
"date": day.get("forecastDate"),
|
||||
"week": day.get("week"),
|
||||
"forecast_wind": day.get("forecastWind"),
|
||||
"forecast_weather": day.get("forecastWeather"),
|
||||
"forecast_temp_max": day.get("forecastMaxtemp", {}).get("value"),
|
||||
"forecast_temp_min": day.get("forecastMintemp", {}).get("value"),
|
||||
"forecast_humidity_max": day.get("forecastMaxrh", {}).get("value"),
|
||||
"forecast_humidity_min": day.get("forecastMinrh", {}).get("value"),
|
||||
"forecast_rain_probability": self._parse_probability(
|
||||
day.get("forecastRainProbability", {})
|
||||
),
|
||||
"psr": day.get("PSR"),
|
||||
})
|
||||
|
||||
return forecasts
|
||||
|
||||
except Exception as e:
|
||||
print(f"HKO forecast error: {e}")
|
||||
return None
|
||||
|
||||
def get_warnings(self) -> Optional[dict]:
|
||||
"""Get current weather warnings and signals."""
|
||||
try:
|
||||
resp = self.session.get(self.ENDPOINTS["warnings"], timeout=15)
|
||||
resp.raise_for_status()
|
||||
return resp.json()
|
||||
except Exception as e:
|
||||
print(f"HKO warnings error: {e}")
|
||||
return None
|
||||
|
||||
def get_typhoon_info(self) -> Optional[dict]:
|
||||
"""Get tropical cyclone warnings and tracks from HKO."""
|
||||
try:
|
||||
resp = self.session.get(self.ENDPOINTS["swt"], timeout=15)
|
||||
resp.raise_for_status()
|
||||
return resp.json()
|
||||
except Exception as e:
|
||||
print(f"HKO typhoon info error: {e}")
|
||||
return None
|
||||
|
||||
def get_rainfall_map(self) -> Optional[dict]:
|
||||
"""Get recent rainfall distribution."""
|
||||
try:
|
||||
resp = self.session.get(self.ENDPOINTS["rainfall"], timeout=15)
|
||||
resp.raise_for_status()
|
||||
return resp.json()
|
||||
except Exception as e:
|
||||
print(f"HKO rainfall map error: {e}")
|
||||
return None
|
||||
|
||||
def is_t8_active(self) -> bool:
|
||||
"""Check if T8 or higher typhoon signal is active."""
|
||||
warnings = self.get_warnings()
|
||||
if not warnings:
|
||||
return False
|
||||
|
||||
for detail in warnings.get("details", []):
|
||||
name = detail.get("name", "")
|
||||
if any(signal in name for signal in ["8", "9", "10"]):
|
||||
if "tropical cyclone" in name.lower() or "typhoon" in name.lower():
|
||||
return True
|
||||
return False
|
||||
|
||||
def get_current_signal_level(self) -> int:
|
||||
"""Get current typhoon signal level (0, 1, 3, 8, 9, 10)."""
|
||||
typhoon = self.get_typhoon_info()
|
||||
if not typhoon:
|
||||
return 0
|
||||
|
||||
for signal_text in ["T1", "T3", "T8", "T9", "T10"]:
|
||||
if signal_text in str(typhoon):
|
||||
return int(signal_text[1:])
|
||||
return 0
|
||||
|
||||
@staticmethod
|
||||
def _parse_probability(prob_data: dict) -> Optional[str]:
|
||||
"""Parse HKO rainfall probability data."""
|
||||
values = []
|
||||
for period in prob_data.get("probability", []):
|
||||
perc = period.get("value")
|
||||
if perc:
|
||||
values.append(perc)
|
||||
return " / ".join(values) if values else None
|
||||
Reference in New Issue
Block a user