feat: pattern calibration — progressive AI→observed expected_move blending
DB (database.py): - 3 new columns on custom_patterns: calibrated_expected_move, calibration_weight, observed_avg_win_pct - update_bayesian_posteriors() now also computes credibility blend w=n/(n+5): calibrated = (1-w)*ai_estimate + w*observed_avg_win_pct (only when wins exist) - log_trade_entries() prefers calibrated_expected_move when w>10% - get_calibration_summary() returns per-pattern state (source: pure_ai/early/mixed/data_driven) Backend (patterns.py, auto_cycle.py): - GET /api/patterns/calibration endpoint - calibration_report block in cycle report: counts by source, avg weight, per-pattern detail Frontend (PatternExplorer.tsx, RapportIA.tsx, useApi.ts): - MaturityBadge on each PatternCard: blend bar (AI→observed), win rate, AI estimate vs calibrated - usePatternCalibration hook - Cycle report: calibration section with global bar + per-pattern table (weight%, n_trades, WR, AI→calibrated) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1506,6 +1506,52 @@ Réponds en JSON avec ce schéma EXACT:
|
||||
except Exception:
|
||||
commentary_parsed = {"commentary": str(commentary)}
|
||||
|
||||
# ── Calibration report ───────────────────────────────────────────────────
|
||||
calibration_report: Dict = {}
|
||||
try:
|
||||
from services.database import get_calibration_summary as _get_calib
|
||||
calib_rows = _get_calib()
|
||||
if calib_rows:
|
||||
pure_ai = [r for r in calib_rows if r["source"] == "pure_ai"]
|
||||
early = [r for r in calib_rows if r["source"] == "early"]
|
||||
mixed = [r for r in calib_rows if r["source"] == "mixed"]
|
||||
driven = [r for r in calib_rows if r["source"] == "data_driven"]
|
||||
with_data = [r for r in calib_rows if (r["n_mature_trades"] or 0) > 0]
|
||||
avg_w = (
|
||||
round(sum(r["calibration_weight"] for r in with_data) / len(with_data), 3)
|
||||
if with_data else 0.0
|
||||
)
|
||||
calibration_report = {
|
||||
"total_patterns": len(calib_rows),
|
||||
"pure_ai_count": len(pure_ai),
|
||||
"early_count": len(early),
|
||||
"mixed_count": len(mixed),
|
||||
"data_driven_count": len(driven),
|
||||
"avg_calibration_weight": avg_w,
|
||||
"avg_calibration_weight_pct": round(avg_w * 100, 1),
|
||||
"patterns_with_data": len(with_data),
|
||||
"detail": [
|
||||
{
|
||||
"name": r["pattern_name"],
|
||||
"asset_class": r["asset_class"],
|
||||
"source": r["source"],
|
||||
"weight_pct": r["calibration_weight_pct"],
|
||||
"ai_estimate": r["ai_estimate"],
|
||||
"observed": r["observed_avg_win_pct"],
|
||||
"calibrated": r["calibrated_expected_move"],
|
||||
"n_trades": r["n_mature_trades"],
|
||||
"win_rate_pct": round((r["bayes_win_rate"] or 0) * 100, 1),
|
||||
}
|
||||
for r in calib_rows if (r["n_mature_trades"] or 0) > 0
|
||||
][:15],
|
||||
}
|
||||
logger.info(
|
||||
f"[CycleReport] Calibration: {len(pure_ai)} pure-AI, "
|
||||
f"{len(mixed)+len(driven)} with data, avg_weight={avg_w:.1%}"
|
||||
)
|
||||
except Exception as _ce:
|
||||
logger.warning(f"[CycleReport] Calibration report failed: {_ce}")
|
||||
|
||||
# ── Assemble full report ──────────────────────────────────────────────────
|
||||
report = {
|
||||
"run_id": run_id,
|
||||
@@ -1553,6 +1599,8 @@ Réponds en JSON avec ce schéma EXACT:
|
||||
"n_alert": (options_assessment or {}).get("n_alert", 0),
|
||||
"assessments": (options_assessment or {}).get("assessments", []),
|
||||
} if options_assessment is not None else None,
|
||||
# Calibration: AI vs observed expected_move blend
|
||||
"calibration_report": calibration_report,
|
||||
}
|
||||
return report
|
||||
|
||||
|
||||
Reference in New Issue
Block a user