#!/usr/bin/env python3
"""
HK Weather Prediction Market — Dev Dashboard
Flask web server with interactive charts:
- Real-time HKO observations + WeatherNext forecasts
- Temperature, rain probability, wind speed charts
- HKO vs WeatherNext model comparison
- Trading signals + Kelly sizing panel
Usage:
python web_dashboard.py
# Open http://localhost:5000
"""
import json
import math
from datetime import datetime, timedelta
from pathlib import Path
from flask import Flask, jsonify, render_template_string
from weather.hk_extractor import HKExtractor
from weather.hko_client import HKOClient
from weather.openmeteo_client import OpenMeteoClient
from strategy.kelly import KellyCriterion
from config import HK_COORDS
app = Flask(__name__)
HTML_TEMPLATE = r'''
HK Weather Prediction Market — Dev Dashboard
Current Conditions HKO
Loading...
Temperature
--
Feels Like
--
Humidity
--
Wind
--
Pressure
--
Tomorrow
Temp Max
--
Temp Min
--
Rain Probability
--
Rain Total
--
Wind Max
--
Gusts Max
--
5-Day Temperature Forecast
Open-Meteo + HKO comparison
HKO vs WeatherNext Model Comparison
Trading Signals
Computing...
Kelly Sizing Simulator fraction=0.25
Computing...
WeatherNext Raw Output (Open-Meteo)
Loading...
'''
@app.route("/api/dashboard")
def api_dashboard():
"""Return all dashboard data as JSON."""
try:
hko = HKOClient()
om = OpenMeteoClient()
forecast = om.get_forecast(lead_days=5)
current_data = {}
try:
hko_cur = hko.get_current_weather()
om_cur = om.get_current_conditions()
if hko_cur and hko_cur.get("temperature"):
current_data["temperature"] = hko_cur["temperature"][0]["value"]
else:
current_data["temperature"] = round(float(om_cur.get("temperature", 0)), 1)
if hko_cur and hko_cur.get("humidity"):
current_data["humidity"] = hko_cur["humidity"][0]["value"]
else:
current_data["humidity"] = round(float(om_cur.get("humidity", 0)), 1)
current_data["feels_like"] = round(float(om_cur.get("apparent_temp", 0)), 1)
current_data["wind_speed"] = round(float(om_cur.get("wind_speed", 0)), 1)
current_data["wind_direction"] = round(float(om_cur.get("wind_direction", 0)), 0)
current_data["pressure"] = round(float(om_cur.get("surface_pressure", 0)), 1)
w = hko_cur.get("warning_message", "") if hko_cur else ""
current_data["warning"] = w[0] if isinstance(w, list) and w else (w if isinstance(w, str) else "")
except Exception:
current_data = {"temperature": "--", "humidity": "--"}
typhoon_data = {"signal": hko.get_current_signal_level()}
tomorrow_data = {}
if forecast is not None and len(forecast) > 0:
d0 = forecast.iloc[0]
d1 = forecast.iloc[1] if len(forecast) > 1 else d0
tomorrow_data = {
"date": d1.name.strftime("%Y-%m-%d"),
"temp_max": round(float(d1.get("temperature_2m_max", 0)), 1),
"temp_min": round(float(d1.get("temperature_2m_min", 0)), 1),
"rain_prob": round(float(d1.get("precipitation_probability_max", 0)), 1),
"rain_sum": round(float(d1.get("precipitation_sum", 0)), 1),
"wind_max": round(float(d1.get("wind_speed_10m_max", 0)), 1),
"wind_gust": round(float(d1.get("wind_gusts_10m_max", 0)), 1),
}
fc_series = {"dates": [], "tmax": [], "tmin": [], "rain_prob": [],
"rain_sum": [], "wind_max": [], "wind_gust": [],
"hko_tmax": [], "hko_tmin": []}
if forecast is not None:
for idx, row in forecast.iterrows():
fc_series["dates"].append(idx.strftime("%m/%d"))
fc_series["tmax"].append(round(float(row.get("temperature_2m_max", 0)), 1))
fc_series["tmin"].append(round(float(row.get("temperature_2m_min", 0)), 1))
fc_series["rain_prob"].append(round(float(row.get("precipitation_probability_max", 0)), 1))
fc_series["rain_sum"].append(round(float(row.get("precipitation_sum", 0)), 1))
fc_series["wind_max"].append(round(float(row.get("wind_speed_10m_max", 0)), 1))
fc_series["wind_gust"].append(round(float(row.get("wind_gusts_10m_max", 0)), 1))
try:
hko_fc = hko.get_forecast()
if hko_fc:
for day in hko_fc[:5]:
fc_series["hko_tmax"].append(int(day.get("forecast_temp_max", 0)) if day.get("forecast_temp_max") else None)
fc_series["hko_tmin"].append(int(day.get("forecast_temp_min", 0)) if day.get("forecast_temp_min") else None)
while len(fc_series["hko_tmax"]) < len(fc_series["tmax"]):
fc_series["hko_tmax"].append(None)
fc_series["hko_tmin"].append(None)
except Exception:
pass
signals = []
if forecast is not None and len(forecast) > 1:
d1 = forecast.iloc[1]
tmrw = (datetime.now() + timedelta(days=1)).strftime("%Y-%m-%d")
rain_p = round(float(d1.get("precipitation_probability_max", 50)), 1)
temp_p = round(float(d1.get("temperature_2m_max", 30)), 1)
signals = [
{
"question": f"Rain in HK on {tmrw}?",
"model_prob": rain_p,
"market_prob": 50.0,
"edge_bps": round((rain_p - 50) * 100, 0),
"signal": "buy_no" if rain_p < 45 else ("buy_yes" if rain_p > 55 else "pass"),
"size": "—"
},
{
"question": f"Temp > 35°C in HK on {tmrw}?",
"model_prob": min(95, max(5, 50 + (temp_p - 35) * 20)),
"market_prob": 40.0,
"edge_bps": round((min(95, max(5, 50 + (temp_p - 35) * 20)) - 40) * 100, 0),
"signal": "buy_yes" if temp_p >= 35 else "pass",
"size": "—"
},
{
"question": f"Temp > 33°C in HK on {tmrw}?",
"model_prob": min(95, max(5, 50 + (temp_p - 33) * 20)),
"market_prob": 70.0,
"edge_bps": round((min(95, max(5, 50 + (temp_p - 33) * 20)) - 70) * 100, 0),
"signal": "buy_yes" if temp_p >= 33 else "pass",
"size": "—"
},
]
kelly = KellyCriterion(bankroll_usdc=1000.0)
kelly_results = []
if forecast is not None and len(forecast) > 1:
d1 = forecast.iloc[1]
rain_p = round(float(d1.get("precipitation_probability_max", 50)), 1)
temp_p = round(float(d1.get("temperature_2m_max", 30)), 1)
scenarios = [
("Rain tomorrow", rain_p, 50),
("Temp > 35°C", min(95, max(5, 50 + (temp_p - 35) * 20)), 35),
("Temp > 33°C", min(95, max(5, 50 + (temp_p - 33) * 20)), 75),
]
for name, our_p, mkt_p in scenarios:
r_yes = kelly.size_bet(our_p, mkt_p, "buy_yes")
r_no = kelly.size_bet(our_p, mkt_p, "buy_no")
if r_yes.kelly_active and r_yes.size_usdc > r_no.size_usdc:
r = r_yes
side = "buy_yes"
elif r_no.kelly_active:
r = r_no
side = "buy_no"
else:
r = r_yes
side = "pass"
kelly_results.append({
"name": name,
"our_prob": round(our_p, 1),
"mkt_prob": round(mkt_p, 1),
"edge": round(r.edge, 3),
"side": side,
"active": bool(r.kelly_active),
"size": round(r.size_usdc, 2) if r.kelly_active else 0,
})
return jsonify({
"fetch_time": datetime.now().strftime("%H:%M:%S"),
"current": current_data,
"typhoon": typhoon_data,
"tomorrow": tomorrow_data,
"forecast": fc_series,
"signals": signals,
"kelly": kelly_results,
})
except Exception as e:
return jsonify({"error": str(e)}), 500
@app.route("/")
def index():
return render_template_string(HTML_TEMPLATE)
if __name__ == "__main__":
import os
debug = os.environ.get("FLASK_DEBUG", "").lower() in ("1", "true", "yes")
print("\n" + "=" * 56)
print(" HK Weather Prediction Market — Dev Dashboard")
print(f" Starting at: http://localhost:5000")
print("=" * 56 + "\n")
app.run(host="0.0.0.0", port=5000, debug=debug)