Persist named backtest-strategy presets so a staking config (bankroll,
min-score, stake rule, flat/percent/Kelly params) can be saved, listed,
loaded back into the form, and deleted. The per-run date range is not
part of a preset.
- Domain: SavedStrategy record (name trimmed + bounded to 80 chars,
Create() factory) wrapping the pure BacktestStrategy.
- Persistence: SavedStrategyEntity + config (TEXT decimals, unique
case-insensitive NOCASE index on Name), repository, mapping, and a
hand-trimmed AddSavedStrategies migration (additive — only the new
table). Case-insensitive names mean save-by-name overwrites instead of
creating near-duplicates.
- Application: SaveStrategyUseCase (upsert by name, keeps Id+CreatedAt) +
DeleteStrategyUseCase.
- UI: presets panel on the Backtest page (load/save/delete) + service
methods; fraction<->percent round-trip; en/ru resx.
- Fix: pin Sports.Code as ValueGeneratedNever — it is the bookmaker's
natural sport id, not an autoincrement surrogate. Corrects long-standing
model-snapshot drift; the snapshot is regenerated to match the DB.
- 25 tests across all four layers: domain validation, real-SQLite
round-trip incl. case-insensitive lookup/uniqueness, the upsert use
case, and the service percent mapping.
Adds an interactive backtester that replays the SuspensionFlip detector over
all flagged anomalies under a chosen score threshold and staking rule
(flat / percent-of-bankroll / Kelly), and reports the headline numbers a
user needs to judge edge: final bankroll, ROI, max drawdown (peak-to-trough),
win/loss streaks, plus per-bet equity curve.
Domain (pure):
- StakeRule enum + BacktestStrategy params (with validation).
- BacktestSimulator: deterministic function taking strategy + chronological
candidates → BacktestResult. Implements Kelly with post-flip implied prob
as p (skipping negative-edge bets), peak-to-trough drawdown tracking, and
win/loss streak rollups. Mirrors AnomalyOutcomeEvaluator on the 2-way Draw
guard so tennis data inconsistencies are refused rather than miss-counted.
- Skipped counter split into SkippedByThreshold / SkippedByDataQuality /
SkippedByBankroll so the UI can distinguish "strategy choice" from
"data-quality" from "bankroll empty".
Application:
- RunBacktestUseCase: loads anomalies + events + results, parses evidence,
builds candidates, hands event titles into the simulator so the UI does
zero repository round-trips of its own.
UI:
- Pages/Anomalies/Backtest.razor: hero, strategy form (MudBlazor — conditional
sub-field per staking rule), 4-card KPI strip (final bankroll / net profit
/ ROI / max drawdown), counters row, inline-SVG equity curve, trade-trace
table with per-bet outcome pills and link-back to the source anomaly.
- Nav entry under Analysis. RU + EN i18n.
Tests: +20 (16 simulator math — flat / percent compounding / Kelly +/-
edge / quarter-Kelly / bankroll-exceeded / out-of-order chronology / Draw
favourite / multi-window drawdown / event-title pass-through + 4 use-case
join). All 399 tests pass.
Money rounding switched to MidpointRounding.AwayFromZero throughout the
simulator output for accounting convention.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>