feat: report bootstrap confidence intervals

This commit is contained in:
Hermes Agent
2026-07-26 23:15:37 +00:00
parent 3a2571df9f
commit f9e992c2b4
2 changed files with 27 additions and 8 deletions
+15 -7
View File
@@ -11,6 +11,7 @@ from datetime import datetime, timedelta
from scoring.policy import SCORE_BRACKETS, SCORE_VERSION, score_in_bracket
from ml.artifacts import validate_ml_artifact
from backtesting.statistics import summarize_returns
log = logging.getLogger(__name__)
@@ -419,6 +420,19 @@ def clear_backtest_cache():
_BACKTEST_CACHE.clear()
def _add_return_statistics(stats, period, returns):
"""Add return summaries and a moving-block-bootstrap mean interval."""
summary = summarize_returns(returns, block_size=min(30, len(returns)), n_resamples=400)
stats[f"avg_{period}"] = summary["mean"]
stats[f"median_{period}"] = summary["median"]
stats[f"win_rate_{period}"] = summary["win_rate"]
stats[f"avg_{period}_ci_low"] = summary["mean_ci_low"]
stats[f"avg_{period}_ci_high"] = summary["mean_ci_high"]
stats[f"max_gain_{period}"] = round(max(returns), 2)
stats[f"max_loss_{period}"] = round(min(returns), 2)
stats[f"n_{period}"] = summary["n"]
def run_backtest(ml_mode=False):
"""Return an isolated cached result keyed by all material input files."""
signature = (
@@ -537,13 +551,7 @@ def _compute_backtest(ml_mode=False):
for period in ["30d", "90d", "180d", "365d"]:
returns = [d["forward_returns"][period] for d in days_in if period in d["forward_returns"]]
if returns:
returns_sorted = sorted(returns)
stats[f"avg_{period}"] = round(sum(returns) / len(returns), 2)
stats[f"median_{period}"] = round(returns_sorted[len(returns_sorted) // 2], 2)
stats[f"win_rate_{period}"] = round(len([r for r in returns if r > 0]) / len(returns) * 100, 1)
stats[f"max_gain_{period}"] = round(max(returns), 2)
stats[f"max_loss_{period}"] = round(min(returns), 2)
stats[f"n_{period}"] = len(returns)
_add_return_statistics(stats, period, returns)
# Average max drawdown within 90 days
dd_list = []
+12 -1
View File
@@ -1,4 +1,4 @@
from backtesting import statistics
from backtesting import engine, statistics
def test_moving_block_bootstrap_is_deterministic_and_handles_constant_series():
@@ -32,3 +32,14 @@ def test_summarize_returns_reports_observations_and_block_bootstrap_interval():
assert summary["median"] == 2.5
assert summary["win_rate"] == 50.0
assert summary["mean_ci_low"] <= summary["mean"] <= summary["mean_ci_high"]
def test_backtest_brackets_publish_bootstrap_confidence_intervals():
stats = {}
engine._add_return_statistics(stats, "90d", [10.0, -5.0, 20.0, -10.0])
assert stats["avg_90d"] == 3.75
assert stats["median_90d"] == 2.5
assert stats["win_rate_90d"] == 50.0
assert stats["avg_90d_ci_low"] <= stats["avg_90d"] <= stats["avg_90d_ci_high"]