Files
maraphon-app/src/Marathon.Application/Configuration/AnomalyOptions.cs
T
alexei.dolgolyov 68f3229c35 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.
2026-05-29 01:03:47 +03:00

55 lines
2.1 KiB
C#

namespace Marathon.Application.Configuration;
/// <summary>
/// Strongly typed options for the anomaly-detection subsystem.
/// Bound from the <c>Anomaly</c> section of <c>appsettings.json</c>.
/// </summary>
public sealed class AnomalyOptions
{
/// <summary>Configuration section key.</summary>
public const string SectionName = "Anomaly";
/// <summary>
/// Minimum gap between adjacent live snapshots, in seconds, to classify as
/// a bookmaker suspension. Default: 60 s.
/// </summary>
public int SuspensionGapSeconds { get; init; } = 60;
/// <summary>
/// Minimum normalised implied-probability delta required for the post-suspension
/// odds change to qualify as a flip. Must be in (0, 1). Default: 0.30.
/// </summary>
public decimal OddsFlipThreshold { get; init; } = 0.30m;
/// <summary>
/// Minimum number of live snapshots an event must have before detection runs.
/// Default: 3. Must be at least 2 (one pair).
/// </summary>
public int MinSnapshotCount { get; init; } = 3;
/// <summary>
/// How long the <c>AnomalyDetectionPoller</c> sleeps between detection cycles,
/// in seconds. Default: 60 s.
/// </summary>
public int DetectionIntervalSeconds { get; init; } = 60;
/// <summary>
/// Trailing window, in seconds, over which the steam-move detector measures a
/// continuous one-directional probability drift. Default: 120 s.
/// </summary>
public int SteamMoveWindowSeconds { get; init; } = 120;
/// <summary>
/// Minimum one-directional normalised implied-probability rise within the window
/// to flag a steam move. Must be in (0, 1). Default: 0.20 (20 percentage points).
/// </summary>
public decimal SteamMoveDriftThreshold { get; init; } = 0.20m;
/// <summary>
/// Maximum normalised implied-probability change across a suspension for it to count
/// as a "freeze" (line resumed essentially unchanged). Must be in (0, 1).
/// Default: 0.05 (5 percentage points).
/// </summary>
public decimal SuspensionFreezeThreshold { get; init; } = 0.05m;
}