feat(anomaly): suspension-freeze detector

- Add SuspensionFreezeDetector via the IAnomalyDetector seam: a suspension gap with
  the favourite unchanged and a negligible (< threshold) price move — the mirror of
  the flip. Score = how completely the line froze. Reuses MatchWinEvidence so UI +
  evaluator handle it unchanged. 6 tests.
- Add AnomalyKind.SuspensionFreeze + localized card/detail label, SuspensionFreezeThreshold
  option, and fan it into DetectAnomaliesUseCase.
This commit is contained in:
2026-05-29 01:03:47 +03:00
parent 005d4e794a
commit 68f3229c35
10 changed files with 240 additions and 7 deletions
+4 -3
View File
@@ -211,9 +211,10 @@
private string KindLabel(AnomalyKind kind) => kind switch
{
AnomalyKind.SuspensionFlip => L["Anomaly.Kind.SuspensionFlip"],
AnomalyKind.SteamMove => L["Anomaly.Kind.SteamMove"],
_ => kind.ToString(),
AnomalyKind.SuspensionFlip => L["Anomaly.Kind.SuspensionFlip"],
AnomalyKind.SteamMove => L["Anomaly.Kind.SteamMove"],
AnomalyKind.SuspensionFreeze => L["Anomaly.Kind.SuspensionFreeze"],
_ => kind.ToString(),
};
private string SportLabel(int code) => SportLabels.Resolve(L, code);
+4 -3
View File
@@ -105,9 +105,10 @@
private string KindLabel(AnomalyKind kind) => kind switch
{
AnomalyKind.SuspensionFlip => L["Anomaly.Kind.SuspensionFlip"],
AnomalyKind.SteamMove => L["Anomaly.Kind.SteamMove"],
_ => kind.ToString(),
AnomalyKind.SuspensionFlip => L["Anomaly.Kind.SuspensionFlip"],
AnomalyKind.SteamMove => L["Anomaly.Kind.SteamMove"],
AnomalyKind.SuspensionFreeze => L["Anomaly.Kind.SuspensionFreeze"],
_ => kind.ToString(),
};
private static string FormatGap(int seconds)
@@ -167,6 +167,7 @@
<data name="Anomaly.Live"><value>Anomaly</value></data>
<data name="Anomaly.Kind.SuspensionFlip"><value>Suspension flip</value></data>
<data name="Anomaly.Kind.SteamMove"><value>Steam move</value></data>
<data name="Anomaly.Kind.SuspensionFreeze"><value>Suspension freeze</value></data>
<data name="Anomaly.Score"><value>Confidence</value></data>
<!-- Phase 7 — Anomaly feed UI -->
@@ -180,6 +180,7 @@
<data name="Anomaly.Live"><value>Аномалия</value></data>
<data name="Anomaly.Kind.SuspensionFlip"><value>Разворот после заморозки</value></data>
<data name="Anomaly.Kind.SteamMove"><value>Движение линии</value></data>
<data name="Anomaly.Kind.SuspensionFreeze"><value>Заморозка линии</value></data>
<data name="Anomaly.Score"><value>Уверенность</value></data>
<!-- Phase 7 — Лента аномалий -->