Moftah
Calibrate forecasts and add market blend gate
a68e63f
Raw
History Blame Contribute Delete
1.85 kB
"""Combine two probabilistic forecasts via a logarithmic opinion pool.
For each outcome i, the blended probability is proportional to
``p_i_1 ** weight * p_i_2 ** (1 - weight)``, then renormalized so the three
outcomes sum to 1. This is the standard way to combine independently-derived
probability forecasts (the "log-odds" or "logarithmic opinion pool" blend) --
simple, well-precedented, and requires no additional fitting beyond the
blend weight itself.
"""
from __future__ import annotations
from types import SimpleNamespace
from typing import Any
def blend_probabilities(first: Any, second: Any, weight: float) -> dict[str, float]:
"""Return a logarithmic pool with ``weight`` assigned to ``first``."""
if not 0.0 <= weight <= 1.0:
raise ValueError("weight must be between 0 and 1")
raw = {
outcome: (
getattr(first, f"p_{outcome}") ** weight
* getattr(second, f"p_{outcome}") ** (1.0 - weight)
)
for outcome in ("home", "draw", "away")
}
total = sum(raw.values())
return {outcome: value / total for outcome, value in raw.items()}
def blend_forecasts(first: Any, second: Any, weight: float) -> SimpleNamespace:
"""Blend two forecasts with ``weight`` on ``first`` (0 <= weight <= 1)."""
blended = blend_probabilities(first, second, weight)
lambda_home = weight * first.lambda_home + (1.0 - weight) * second.lambda_home
lambda_away = weight * first.lambda_away + (1.0 - weight) * second.lambda_away
most_likely_score = first.most_likely_score if weight >= 0.5 else second.most_likely_score
return SimpleNamespace(
p_home=blended["home"],
p_draw=blended["draw"],
p_away=blended["away"],
lambda_home=lambda_home,
lambda_away=lambda_away,
most_likely_score=most_likely_score,
)