fix(anomaly): exclude non-directional kinds from grading and backtest
Review follow-up (HIGH): the three detectors fed the same evaluator/backtest, but SuspensionFreeze is non-directional (favourite unchanged) — grading it as "favourite won" polluted the hit-rate with the base favourite-win rate, and its high frozen-ness score always cleared the backtest threshold. - Add AnomalyKind.IsDirectional() (flip + steam = true, freeze = false). - AnomalyOutcomeEvaluator returns Unresolved for non-directional kinds (favourites still surfaced for display) so they don't distort calibration. - RunBacktestUseCase skips non-directional anomalies when building candidates. - Tests for the classification, the evaluator path, and the backtest skip.
This commit is contained in:
@@ -85,6 +85,25 @@ public sealed class RunBacktestUseCaseTests
|
||||
result.BetsPlaced.Should().BeGreaterThan(0, "the in-range graded anomaly produces a bet");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Should_SkipNonDirectionalAnomalies_When_BuildingCandidates()
|
||||
{
|
||||
// A SuspensionFreeze anomaly (favourite unchanged) must not be staked.
|
||||
var id = new EventId("88888888");
|
||||
var freeze = new Anomaly(
|
||||
Guid.NewGuid(), id, BaseTime, AnomalyKind.SuspensionFreeze, 0.9m, FlipEvidence);
|
||||
|
||||
_anomalies.ListAsync(Arg.Any<CancellationToken>())
|
||||
.Returns(new[] { freeze }.ToList().AsReadOnly());
|
||||
_events.GetAsync(id, Arg.Any<CancellationToken>()).Returns(MakeEvent(id));
|
||||
_results.GetAsync(id, Arg.Any<CancellationToken>())
|
||||
.Returns(new EventResult(id, 0, 2, Side.Side2, DateTimeOffset.UtcNow));
|
||||
|
||||
var result = await CreateSut().ExecuteAsync(DefaultStrategy(), CancellationToken.None);
|
||||
|
||||
result.BetsPlaced.Should().Be(0, "SuspensionFreeze is non-directional and must not be staked");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Should_ReturnEmptyResult_When_NoAnomaliesExist()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user