feat(anomaly): overround-compression detector

Adds the 4th IAnomalyDetector: flags a sharp drop in the bookmaker's
overround (raw implied-probability sum / margin) over a continuous live
window. Informational/non-directional — excluded from outcome grading
and backtest staking via AnomalyKind.IsDirectional().

- OverroundCompressionDetector: sliding-window scan mirroring SteamMove,
  score = min(1, compression / 0.10 reference), suspension-gap aware.
- MatchWinEvidence.Probabilities gains Overround (pre-normalisation
  margin); evidence JSON shape unchanged.
- Wired into DetectAnomaliesUseCase fan-out; AnomalyOptions + appsettings
  (window 120s, threshold 0.02); en/ru resx + KindLabel arms.
- 11 detector tests incl. score saturation/scaling, inclusive threshold
  boundary, and a three-way (draw-leg) overround case.
This commit is contained in:
2026-05-29 01:46:56 +03:00
parent 5eb3dec24b
commit 115872aad0
13 changed files with 368 additions and 12 deletions
+5 -4
View File
@@ -211,10 +211,11 @@
private string KindLabel(AnomalyKind kind) => kind switch
{
AnomalyKind.SuspensionFlip => L["Anomaly.Kind.SuspensionFlip"],
AnomalyKind.SteamMove => L["Anomaly.Kind.SteamMove"],
AnomalyKind.SuspensionFreeze => L["Anomaly.Kind.SuspensionFreeze"],
_ => kind.ToString(),
AnomalyKind.SuspensionFlip => L["Anomaly.Kind.SuspensionFlip"],
AnomalyKind.SteamMove => L["Anomaly.Kind.SteamMove"],
AnomalyKind.SuspensionFreeze => L["Anomaly.Kind.SuspensionFreeze"],
AnomalyKind.OverroundCompression => L["Anomaly.Kind.OverroundCompression"],
_ => kind.ToString(),
};
private string SportLabel(int code) => SportLabels.Resolve(L, code);