Add ML prediction pipeline — LightGBM, calibration fix, ensemble disagreement
Tier 1 ML enhancements: - Feature engineering (37 features across 5 groups: thermal, dynamic, moisture, temporal, interaction) from NWP model output - 7 LightGBM probability models for rain/temp/wind thresholds - Temperature-scaled probabilities to prevent overconfidence on bootstrap data - MLPredictor: unified inference pipeline replacing heuristic sigmoids - Ensemble disagreement signals (composite spread → edge amplification) - Fixed calibration loop: update_calibration() now functional (EMA of errors) - record_outcome() wired for post-resolution feedback - Nautilus strategy updated: ML predictions take priority, heuristics as fallback - Historical backtest engine with Sharpe/ROI/max-DD simulation - Bootstrap training data generator from HK climate normals Run: python ml/train.py && python ml/backtest.py --edge 50
This commit is contained in:
+62
-7
@@ -104,7 +104,9 @@ class PolymarketWeatherStrategy(Strategy):
|
||||
# Weather clients
|
||||
self._openmeteo: Optional[OpenMeteoClient] = None
|
||||
self._hko: Optional[HKOClient] = None
|
||||
self._ml_predictor = None # ML predictor (lazy-loaded)
|
||||
self._last_forecast: Optional[Dict] = None
|
||||
self._last_ml_probs: Optional[Dict] = None
|
||||
|
||||
# Task handles
|
||||
self._forecast_task: Optional[asyncio.Task] = None
|
||||
@@ -120,6 +122,17 @@ class PolymarketWeatherStrategy(Strategy):
|
||||
self._openmeteo = OpenMeteoClient()
|
||||
self._hko = HKOClient()
|
||||
|
||||
try:
|
||||
from ml import MLPredictor
|
||||
self._ml_predictor = MLPredictor(
|
||||
bankroll_usdc=self.config.bankroll_pusd,
|
||||
min_edge_bps=self.config.min_edge_bps,
|
||||
kelly_fraction=self.config.kelly_fraction,
|
||||
)
|
||||
self.log.info(f"ML predictor loaded: {len(self._ml_predictor.ensemble.models)} models")
|
||||
except Exception as e:
|
||||
self.log.warning(f"ML predictor not available: {e}")
|
||||
|
||||
# Discover weather markets
|
||||
await self._discover_markets()
|
||||
|
||||
@@ -293,6 +306,12 @@ class PolymarketWeatherStrategy(Strategy):
|
||||
self.log.info("Updating weather forecast...")
|
||||
|
||||
try:
|
||||
# Use ML predictor if available
|
||||
if self._ml_predictor and self._ml_predictor.models_loaded:
|
||||
self._ml_predictor.fetch_and_predict()
|
||||
self._last_ml_probs = self._ml_predictor._last_predictions
|
||||
self.log.info(f"ML forecast: {self._ml_predictor.summary()}")
|
||||
|
||||
tomorrow = (datetime.now() + timedelta(days=1)).strftime("%Y-%m-%d")
|
||||
self._last_forecast = self._openmeteo.get_scoring_window_summary(tomorrow)
|
||||
|
||||
@@ -309,12 +328,6 @@ class PolymarketWeatherStrategy(Strategy):
|
||||
temps = current.get("temperature", [])
|
||||
self._last_forecast["current_temp"] = temps[0]["value"] if temps else None
|
||||
|
||||
self.log.info(
|
||||
f"Forecast: {self._last_forecast.get('date', 'N/A')} "
|
||||
f"Tmax={self._last_forecast.get('temperature_2m_max', '?')}°C "
|
||||
f"Rain={self._last_forecast.get('precipitation_probability_max', '?')}%"
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
self.log.error(f"Forecast fetch error: {e}")
|
||||
|
||||
@@ -379,7 +392,49 @@ class PolymarketWeatherStrategy(Strategy):
|
||||
)
|
||||
|
||||
def _compute_model_probability(self, question: str) -> Optional[float]:
|
||||
"""Compute our model's probability for a given market question."""
|
||||
"""Compute our model's probability for a given market question.
|
||||
|
||||
Uses ML model predictions when available, falls back to heuristics.
|
||||
Also records resolved outcomes for calibration.
|
||||
"""
|
||||
# Try ML predictor first
|
||||
if self._ml_predictor and self._last_ml_probs:
|
||||
target = self._question_to_target(question)
|
||||
if target and target in self._last_ml_probs:
|
||||
return self._last_ml_probs[target]
|
||||
|
||||
# Fallback heuristic
|
||||
if not self._last_forecast:
|
||||
return None
|
||||
|
||||
return self._compute_heuristic_probability(question)
|
||||
|
||||
@staticmethod
|
||||
def _question_to_target(question: str) -> Optional[str]:
|
||||
"""Map a Polymarket question to an ML model target."""
|
||||
q = question.lower()
|
||||
if "rain" in q or "precipitation" in q:
|
||||
if "10mm" in q or "10 mm" in q or "heavy" in q:
|
||||
return "rain_gt_10mm_24h"
|
||||
if "5mm" in q or "5 mm" in q:
|
||||
return "rain_gt_5mm_24h"
|
||||
return "rain_gt_0mm_24h"
|
||||
if "temperature" in q or "temp" in q:
|
||||
if "35" in q or "thirty five" in q:
|
||||
return "temp_gt_35c_24h"
|
||||
if "33" in q or "thirty three" in q:
|
||||
return "temp_gt_33c_24h"
|
||||
if "30" in q or "thirty" in q:
|
||||
return "temp_gt_30c_24h"
|
||||
return "temp_gt_30c_24h"
|
||||
if "wind" in q or "gust" in q:
|
||||
return "wind_gt_30kmh_24h"
|
||||
if "typhoon" in q or "t8" in q or "cyclone" in q:
|
||||
return None # No ML model for typhoon yet
|
||||
return None
|
||||
|
||||
def _compute_heuristic_probability(self, question: str) -> Optional[float]:
|
||||
"""Fallback heuristic probability (legacy)."""
|
||||
if not self._last_forecast:
|
||||
return None
|
||||
|
||||
|
||||
Reference in New Issue
Block a user