"""Closed-form statistics for Monte Carlo balance reports. Proportions follow the binomial model: p = wins/n with standard error sqrt(p(1-p)/n). The 3-sigma band is the default confidence interval used in the CLI report; clamp keeps degenerate proportions (0 or 1) exact. """ from __future__ import annotations import math def win_rate(wins: int, runs: int) -> float: """Fraction of runs won: wins / runs.""" if runs <= 0: msg = "runs must be positive" raise ValueError(msg) return wins / runs def win_rate_sigma(p: float, runs: int) -> float: """Standard error of a proportion: sqrt(p(1-p)/n).""" if runs <= 0: msg = "runs must be positive" raise ValueError(msg) return math.sqrt(p * (1 - p) / runs) def win_rate_band(p: float, runs: int, *, sigma_count: float = 3.0) -> tuple[float, float]: """p +/- sigma_count standard errors, clamped to [0, 1].""" sigma = win_rate_sigma(p, runs) return (max(0.0, p - sigma_count * sigma), min(1.0, p + sigma_count * sigma))