feat(backtest): saved strategy presets (strategy editor v1)
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.
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
using Marathon.Domain.Backtesting;
|
||||
using Marathon.Domain.Entities;
|
||||
using Marathon.Domain.Enums;
|
||||
using Marathon.Domain.ValueObjects;
|
||||
@@ -222,4 +223,33 @@ internal static class Mapping
|
||||
NameRu: entity.NameRu,
|
||||
NameEn: entity.NameEn,
|
||||
Category: entity.Category);
|
||||
|
||||
// ─── SavedStrategy ─────────────────────────────────────────────────────────
|
||||
|
||||
public static SavedStrategyEntity ToEntity(SavedStrategy domain) =>
|
||||
new()
|
||||
{
|
||||
Id = domain.Id.ToString(),
|
||||
Name = domain.Name,
|
||||
StartingBankroll = domain.Strategy.StartingBankroll,
|
||||
MinScore = domain.Strategy.MinScore,
|
||||
StakeRule = (int)domain.Strategy.StakeRule,
|
||||
FlatStake = domain.Strategy.FlatStake,
|
||||
PercentOfBankroll = domain.Strategy.PercentOfBankroll,
|
||||
KellyFraction = domain.Strategy.KellyFraction,
|
||||
CreatedAt = SqliteDateText.Key(domain.CreatedAt),
|
||||
};
|
||||
|
||||
public static SavedStrategy ToDomain(SavedStrategyEntity entity) =>
|
||||
new(
|
||||
Id: Guid.Parse(entity.Id),
|
||||
Name: entity.Name,
|
||||
Strategy: new BacktestStrategy(
|
||||
StartingBankroll: entity.StartingBankroll,
|
||||
MinScore: entity.MinScore,
|
||||
StakeRule: (StakeRule)entity.StakeRule,
|
||||
FlatStake: entity.FlatStake,
|
||||
PercentOfBankroll: entity.PercentOfBankroll,
|
||||
KellyFraction: entity.KellyFraction),
|
||||
CreatedAt: SqliteDateText.Parse(entity.CreatedAt));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user