diff --git a/ai-engine/models/calibration/btts_calibrator.pkl b/ai-engine/models/calibration/btts_calibrator.pkl new file mode 100644 index 0000000..6d06a3a Binary files /dev/null and b/ai-engine/models/calibration/btts_calibrator.pkl differ diff --git a/ai-engine/models/calibration/btts_metrics.json b/ai-engine/models/calibration/btts_metrics.json new file mode 100644 index 0000000..b3261b1 --- /dev/null +++ b/ai-engine/models/calibration/btts_metrics.json @@ -0,0 +1,8 @@ +{ + "brier_score": 0.2444, + "calibration_error": 0.0, + "sample_count": 5000, + "last_trained": "2026-05-24T23:10:58.117788", + "mean_predicted": 0.4015, + "mean_actual": 0.5454 +} \ No newline at end of file diff --git a/ai-engine/models/calibration/ht_away_calibrator.pkl b/ai-engine/models/calibration/ht_away_calibrator.pkl new file mode 100644 index 0000000..a522161 Binary files /dev/null and b/ai-engine/models/calibration/ht_away_calibrator.pkl differ diff --git a/ai-engine/models/calibration/ht_away_metrics.json b/ai-engine/models/calibration/ht_away_metrics.json new file mode 100644 index 0000000..f692b55 --- /dev/null +++ b/ai-engine/models/calibration/ht_away_metrics.json @@ -0,0 +1,8 @@ +{ + "brier_score": 0.1852, + "calibration_error": 0.0, + "sample_count": 4989, + "last_trained": "2026-05-24T23:10:58.129841", + "mean_predicted": 0.2305, + "mean_actual": 0.263 +} \ No newline at end of file diff --git a/ai-engine/models/calibration/ht_draw_calibrator.pkl b/ai-engine/models/calibration/ht_draw_calibrator.pkl new file mode 100644 index 0000000..94800e6 Binary files /dev/null and b/ai-engine/models/calibration/ht_draw_calibrator.pkl differ diff --git a/ai-engine/models/calibration/ht_draw_metrics.json b/ai-engine/models/calibration/ht_draw_metrics.json new file mode 100644 index 0000000..a211293 --- /dev/null +++ b/ai-engine/models/calibration/ht_draw_metrics.json @@ -0,0 +1,8 @@ +{ + "brier_score": 0.2383, + "calibration_error": 0.0, + "sample_count": 4989, + "last_trained": "2026-05-24T23:10:58.142015", + "mean_predicted": 0.4549, + "mean_actual": 0.4001 +} \ No newline at end of file diff --git a/ai-engine/models/calibration/ht_home_calibrator.pkl b/ai-engine/models/calibration/ht_home_calibrator.pkl new file mode 100644 index 0000000..2466928 Binary files /dev/null and b/ai-engine/models/calibration/ht_home_calibrator.pkl differ diff --git a/ai-engine/models/calibration/ht_home_metrics.json b/ai-engine/models/calibration/ht_home_metrics.json new file mode 100644 index 0000000..c8c4a1f --- /dev/null +++ b/ai-engine/models/calibration/ht_home_metrics.json @@ -0,0 +1,8 @@ +{ + "brier_score": 0.2137, + "calibration_error": 0.0, + "sample_count": 4989, + "last_trained": "2026-05-24T23:10:58.153390", + "mean_predicted": 0.3146, + "mean_actual": 0.3369 +} \ No newline at end of file diff --git a/ai-engine/models/calibration/ms_away_calibrator.pkl b/ai-engine/models/calibration/ms_away_calibrator.pkl new file mode 100644 index 0000000..3e532fe Binary files /dev/null and b/ai-engine/models/calibration/ms_away_calibrator.pkl differ diff --git a/ai-engine/models/calibration/ms_away_metrics.json b/ai-engine/models/calibration/ms_away_metrics.json new file mode 100644 index 0000000..f9384cc --- /dev/null +++ b/ai-engine/models/calibration/ms_away_metrics.json @@ -0,0 +1,8 @@ +{ + "brier_score": 0.1956, + "calibration_error": 0.0, + "sample_count": 5000, + "last_trained": "2026-05-24T23:10:58.164629", + "mean_predicted": 0.3215, + "mean_actual": 0.3196 +} \ No newline at end of file diff --git a/ai-engine/models/calibration/ms_draw_calibrator.pkl b/ai-engine/models/calibration/ms_draw_calibrator.pkl new file mode 100644 index 0000000..3532842 Binary files /dev/null and b/ai-engine/models/calibration/ms_draw_calibrator.pkl differ diff --git a/ai-engine/models/calibration/ms_draw_metrics.json b/ai-engine/models/calibration/ms_draw_metrics.json new file mode 100644 index 0000000..d85f5fb --- /dev/null +++ b/ai-engine/models/calibration/ms_draw_metrics.json @@ -0,0 +1,8 @@ +{ + "brier_score": 0.1816, + "calibration_error": 0.0, + "sample_count": 5000, + "last_trained": "2026-05-24T23:10:58.175615", + "mean_predicted": 0.2453, + "mean_actual": 0.2426 +} \ No newline at end of file diff --git a/ai-engine/models/calibration/ms_home_calibrator.pkl b/ai-engine/models/calibration/ms_home_calibrator.pkl new file mode 100644 index 0000000..4353a9f Binary files /dev/null and b/ai-engine/models/calibration/ms_home_calibrator.pkl differ diff --git a/ai-engine/models/calibration/ms_home_metrics.json b/ai-engine/models/calibration/ms_home_metrics.json new file mode 100644 index 0000000..da084fe --- /dev/null +++ b/ai-engine/models/calibration/ms_home_metrics.json @@ -0,0 +1,8 @@ +{ + "brier_score": 0.2241, + "calibration_error": 0.0, + "sample_count": 5000, + "last_trained": "2026-05-24T23:10:58.186473", + "mean_predicted": 0.4332, + "mean_actual": 0.4378 +} \ No newline at end of file diff --git a/ai-engine/models/calibration/ou15_calibrator.pkl b/ai-engine/models/calibration/ou15_calibrator.pkl index dc72e87..725c54d 100644 Binary files a/ai-engine/models/calibration/ou15_calibrator.pkl and b/ai-engine/models/calibration/ou15_calibrator.pkl differ diff --git a/ai-engine/models/calibration/ou15_metrics.json b/ai-engine/models/calibration/ou15_metrics.json index 5291e0a..19f9798 100644 --- a/ai-engine/models/calibration/ou15_metrics.json +++ b/ai-engine/models/calibration/ou15_metrics.json @@ -1,8 +1,8 @@ { - "brier_score": 0.196, + "brier_score": 0.1708, "calibration_error": 0.0, - "sample_count": 13, - "last_trained": "2026-05-11T23:38:11.500966", - "mean_predicted": 0.7239, - "mean_actual": 0.6154 + "sample_count": 5000, + "last_trained": "2026-05-24T23:10:58.196981", + "mean_predicted": 0.7586, + "mean_actual": 0.7732 } \ No newline at end of file diff --git a/ai-engine/models/calibration/ou25_calibrator.pkl b/ai-engine/models/calibration/ou25_calibrator.pkl index 8cb6eb6..dbbd8f3 100644 Binary files a/ai-engine/models/calibration/ou25_calibrator.pkl and b/ai-engine/models/calibration/ou25_calibrator.pkl differ diff --git a/ai-engine/models/calibration/ou25_metrics.json b/ai-engine/models/calibration/ou25_metrics.json index c100b67..27321d9 100644 --- a/ai-engine/models/calibration/ou25_metrics.json +++ b/ai-engine/models/calibration/ou25_metrics.json @@ -1,8 +1,8 @@ { - "brier_score": 0.2222, + "brier_score": 0.2386, "calibration_error": 0.0, - "sample_count": 18, - "last_trained": "2026-05-11T23:38:11.508353", - "mean_predicted": 0.6258, - "mean_actual": 0.5 + "sample_count": 5000, + "last_trained": "2026-05-24T23:10:58.207151", + "mean_predicted": 0.5013, + "mean_actual": 0.5538 } \ No newline at end of file diff --git a/ai-engine/models/calibration/ou35_calibrator.pkl b/ai-engine/models/calibration/ou35_calibrator.pkl new file mode 100644 index 0000000..271ca67 Binary files /dev/null and b/ai-engine/models/calibration/ou35_calibrator.pkl differ diff --git a/ai-engine/models/calibration/ou35_metrics.json b/ai-engine/models/calibration/ou35_metrics.json new file mode 100644 index 0000000..69ca406 --- /dev/null +++ b/ai-engine/models/calibration/ou35_metrics.json @@ -0,0 +1,8 @@ +{ + "brier_score": 0.215, + "calibration_error": 0.0, + "sample_count": 5000, + "last_trained": "2026-05-24T23:10:58.217663", + "mean_predicted": 0.305, + "mean_actual": 0.3408 +} \ No newline at end of file diff --git a/ai-engine/models/v25/feature_importance.png b/ai-engine/models/v25/feature_importance.png new file mode 100644 index 0000000..4f66553 Binary files /dev/null and b/ai-engine/models/v25/feature_importance.png differ diff --git a/ai-engine/services/betting_brain.py b/ai-engine/services/betting_brain.py index e2e3a45..53ab84c 100644 --- a/ai-engine/services/betting_brain.py +++ b/ai-engine/services/betting_brain.py @@ -312,6 +312,52 @@ class BettingBrain: if market in {"HT", "HTFT", "OE"} and score < 86.0: vetoes.append("volatile_market_requires_exceptional_evidence") + # Cross-market reversal risk for MS/DC picks. + # If HTFT model says the *opposite* of our MS pick is likely (a + # reversal scenario like 1/2 or 2/1), the MS pick is fragile even + # when its own calibrated_confidence is high. The Manchester City + # 1-0 → 1-2 case is exactly this: MS=1 looked solid but HTFT 1/2 + # carried real probability mass that the MS scorer ignored. + if market == "MS" and pick in ("1", "2"): + board = package.get("market_board") or {} + htft_payload = board.get("HTFT") if isinstance(board, dict) else None + htft_probs = ( + htft_payload.get("probs", {}) + if isinstance(htft_payload, dict) else {} + ) + risk_payload = package.get("risk") or {} + if not htft_probs and isinstance(risk_payload, dict): + htft_probs = risk_payload.get("ht_ft_probs", {}) or {} + # Reversal outcomes that would make this MS pick lose despite + # the team leading at half-time / trailing at half-time. + if pick == "1": + # Picked home win. Threats: 1/2 (led, lost) and 1/X (led, drew). + reversal_keys = ("1/2", "1/X") + drift_keys = ("X/2",) + else: + # Picked away win. Threats: 2/1, 2/X. Drift: X/1. + reversal_keys = ("2/1", "2/X") + drift_keys = ("X/1",) + reversal_prob = sum( + self._safe_float(htft_probs.get(key), 0.0) or 0.0 + for key in reversal_keys + ) + drift_prob = sum( + self._safe_float(htft_probs.get(key), 0.0) or 0.0 + for key in drift_keys + ) + combined_risk = reversal_prob + 0.5 * drift_prob + if combined_risk >= 0.25: + score -= 28.0 + vetoes.append("htft_reversal_risk_high") + issues.append(f"htft_reversal_prob={combined_risk:.2f}") + elif combined_risk >= 0.15: + score -= 14.0 + issues.append(f"htft_reversal_prob_moderate={combined_risk:.2f}") + elif combined_risk >= 0.10: + score -= 6.0 + issues.append(f"htft_reversal_prob_minor={combined_risk:.2f}") + # Sniper override: bypass eligible vetoes when value sniper triggered sniper_bypassed: List[str] = [] if is_value_sniper and vetoes: