Files
maraphon-app/src/Marathon.Infrastructure/Migrations/MarathonDbContextModelSnapshot.cs
T
alexei.dolgolyov f622dadf95 feat(paper-trading): forward-test ledger engine
Adds a background forward-test engine that records flat-stake "paper" bets
for directional anomalies as they fire and settles them when results arrive,
measuring the detector's live, out-of-sample edge — the antidote to backtest
overfitting. The results UI is a follow-up.

- Domain: PaperBet entity (Rate>1 / Stake>0 invariants, Open factory,
  SettleAgainst — Won pays stake x rate, else Lost) + AnomalyEvidenceSide.RateFor.
- Application: OpenPaperBetsUseCase (directional + score gate, dedups by
  AnomalyId, picks the post-flip favourite and its locked-in rate) and
  SettlePaperBetsUseCase (Won when pick == winner else Lost; ungraded events
  stay open; batched result lookup).
- Infrastructure: PaperBetEntity + config (TEXT decimals, unique AnomalyId index,
  Outcome index), repository, mapping, additive AddPaperBets migration, and
  PaperTradingWorker (config-gated, baseline since-marker, open+settle per cycle).
- Config: PaperTradingOptions / appsettings PaperTrading (Enabled:false default).
- 25 tests: domain settlement, both use cases, and a real-SQLite round-trip
  incl. the unique-AnomalyId double-open backstop.
2026-05-29 02:25:54 +03:00

437 lines
15 KiB
C#

// <auto-generated />
using System;
using Marathon.Infrastructure.Persistence;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace Marathon.Infrastructure.Migrations
{
[DbContext(typeof(MarathonDbContext))]
partial class MarathonDbContextModelSnapshot : ModelSnapshot
{
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "8.0.12");
modelBuilder.Entity("Marathon.Infrastructure.Persistence.Entities.AnomalyEntity", b =>
{
b.Property<string>("Id")
.HasColumnType("TEXT");
b.Property<string>("DetectedAt")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("EventCode")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("EvidenceJson")
.IsRequired()
.HasColumnType("TEXT");
b.Property<int>("Kind")
.HasColumnType("INTEGER");
b.Property<decimal>("Score")
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("EventCode")
.HasDatabaseName("IX_Anomalies_EventCode");
b.ToTable("Anomalies", (string)null);
});
modelBuilder.Entity("Marathon.Infrastructure.Persistence.Entities.BetEntity", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<int?>("PeriodNumber")
.HasColumnType("INTEGER");
b.Property<decimal>("Rate")
.HasColumnType("TEXT");
b.Property<int>("Scope")
.HasColumnType("INTEGER");
b.Property<int>("Side")
.HasColumnType("INTEGER");
b.Property<long>("SnapshotId")
.HasColumnType("INTEGER");
b.Property<int>("Type")
.HasColumnType("INTEGER");
b.Property<decimal?>("Value")
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("SnapshotId")
.HasDatabaseName("IX_Bets_SnapshotId");
b.ToTable("Bets", (string)null);
});
modelBuilder.Entity("Marathon.Infrastructure.Persistence.Entities.EventEntity", b =>
{
b.Property<string>("EventCode")
.HasColumnType("TEXT");
b.Property<string>("Category")
.IsRequired()
.ValueGeneratedOnAdd()
.HasColumnType("TEXT")
.HasDefaultValue("");
b.Property<string>("CountryCode")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("EventPath")
.HasColumnType("TEXT");
b.Property<string>("LeagueId")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("ScheduledAt")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("Side1Name")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("Side2Name")
.IsRequired()
.HasColumnType("TEXT");
b.Property<int>("SportCode")
.HasColumnType("INTEGER");
b.HasKey("EventCode");
b.HasIndex("ScheduledAt")
.HasDatabaseName("IX_Events_ScheduledAt");
b.HasIndex("SportCode", "ScheduledAt")
.HasDatabaseName("IX_Events_SportCode_ScheduledAt");
b.ToTable("Events", (string)null);
});
modelBuilder.Entity("Marathon.Infrastructure.Persistence.Entities.EventResultEntity", b =>
{
b.Property<string>("EventCode")
.HasColumnType("TEXT");
b.Property<string>("CompletedAt")
.IsRequired()
.HasColumnType("TEXT");
b.Property<int>("Side1Score")
.HasColumnType("INTEGER");
b.Property<int>("Side2Score")
.HasColumnType("INTEGER");
b.Property<int>("WinnerSide")
.HasColumnType("INTEGER");
b.HasKey("EventCode");
b.ToTable("EventResults", (string)null);
});
modelBuilder.Entity("Marathon.Infrastructure.Persistence.Entities.LeagueEntity", b =>
{
b.Property<string>("Id")
.HasColumnType("TEXT");
b.Property<string>("Category")
.IsRequired()
.ValueGeneratedOnAdd()
.HasColumnType("TEXT")
.HasDefaultValue("");
b.Property<string>("Country")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("NameEn")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("NameRu")
.IsRequired()
.HasColumnType("TEXT");
b.Property<int>("SportCode")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.ToTable("Leagues", (string)null);
});
modelBuilder.Entity("Marathon.Infrastructure.Persistence.Entities.PaperBetEntity", b =>
{
b.Property<string>("Id")
.HasColumnType("TEXT");
b.Property<string>("AnomalyId")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("EventCode")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("OpenedAt")
.IsRequired()
.HasColumnType("TEXT");
b.Property<int>("Outcome")
.HasColumnType("INTEGER");
b.Property<decimal?>("Payout")
.HasColumnType("TEXT");
b.Property<int>("PickedSide")
.HasColumnType("INTEGER");
b.Property<decimal>("Rate")
.HasColumnType("TEXT");
b.Property<string>("SettledAt")
.HasColumnType("TEXT");
b.Property<decimal>("Stake")
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("AnomalyId")
.IsUnique()
.HasDatabaseName("IX_PaperBets_AnomalyId");
b.HasIndex("Outcome")
.HasDatabaseName("IX_PaperBets_Outcome");
b.ToTable("PaperBets", (string)null);
});
modelBuilder.Entity("Marathon.Infrastructure.Persistence.Entities.PlacedBetEntity", b =>
{
b.Property<string>("Id")
.HasColumnType("TEXT");
b.Property<string>("EventCode")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("Notes")
.HasColumnType("TEXT");
b.Property<int>("Outcome")
.HasColumnType("INTEGER");
b.Property<int?>("PeriodNumber")
.HasColumnType("INTEGER");
b.Property<string>("PlacedAt")
.IsRequired()
.HasColumnType("TEXT");
b.Property<decimal>("Rate")
.HasColumnType("TEXT");
b.Property<int>("Scope")
.HasColumnType("INTEGER");
b.Property<int>("Side")
.HasColumnType("INTEGER");
b.Property<decimal>("Stake")
.HasColumnType("TEXT");
b.Property<int>("Type")
.HasColumnType("INTEGER");
b.Property<decimal?>("Value")
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("EventCode")
.HasDatabaseName("IX_PlacedBets_EventCode");
b.HasIndex("Outcome")
.HasDatabaseName("IX_PlacedBets_Outcome");
b.ToTable("PlacedBets", (string)null);
});
modelBuilder.Entity("Marathon.Infrastructure.Persistence.Entities.SavedStrategyEntity", b =>
{
b.Property<string>("Id")
.HasColumnType("TEXT");
b.Property<string>("CreatedAt")
.IsRequired()
.HasColumnType("TEXT");
b.Property<decimal>("FlatStake")
.HasColumnType("TEXT");
b.Property<decimal>("KellyFraction")
.HasColumnType("TEXT");
b.Property<decimal>("MinScore")
.HasColumnType("TEXT");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT")
.UseCollation("NOCASE");
b.Property<decimal>("PercentOfBankroll")
.HasColumnType("TEXT");
b.Property<int>("StakeRule")
.HasColumnType("INTEGER");
b.Property<decimal>("StartingBankroll")
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("Name")
.IsUnique()
.HasDatabaseName("IX_SavedStrategies_Name");
b.ToTable("SavedStrategies", (string)null);
});
modelBuilder.Entity("Marathon.Infrastructure.Persistence.Entities.SnapshotEntity", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("CapturedAt")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("EventCode")
.IsRequired()
.HasColumnType("TEXT");
b.Property<int>("Source")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("EventCode")
.HasDatabaseName("IX_Snapshots_EventCode");
b.HasIndex("EventCode", "CapturedAt")
.HasDatabaseName("IX_Snapshots_EventCode_CapturedAt");
b.HasIndex("EventCode", "Source", "CapturedAt")
.HasDatabaseName("IX_Snapshots_EventCode_Source_CapturedAt");
b.ToTable("Snapshots", (string)null);
});
modelBuilder.Entity("Marathon.Infrastructure.Persistence.Entities.SportEntity", b =>
{
b.Property<int>("Code")
.HasColumnType("INTEGER");
b.Property<string>("NameEn")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("NameRu")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("Code");
b.ToTable("Sports", (string)null);
});
modelBuilder.Entity("Marathon.Infrastructure.Persistence.Entities.AnomalyEntity", b =>
{
b.HasOne("Marathon.Infrastructure.Persistence.Entities.EventEntity", "Event")
.WithMany("Anomalies")
.HasForeignKey("EventCode")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Event");
});
modelBuilder.Entity("Marathon.Infrastructure.Persistence.Entities.BetEntity", b =>
{
b.HasOne("Marathon.Infrastructure.Persistence.Entities.SnapshotEntity", "Snapshot")
.WithMany("Bets")
.HasForeignKey("SnapshotId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Snapshot");
});
modelBuilder.Entity("Marathon.Infrastructure.Persistence.Entities.EventResultEntity", b =>
{
b.HasOne("Marathon.Infrastructure.Persistence.Entities.EventEntity", "Event")
.WithOne("Result")
.HasForeignKey("Marathon.Infrastructure.Persistence.Entities.EventResultEntity", "EventCode")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Event");
});
modelBuilder.Entity("Marathon.Infrastructure.Persistence.Entities.SnapshotEntity", b =>
{
b.HasOne("Marathon.Infrastructure.Persistence.Entities.EventEntity", "Event")
.WithMany("Snapshots")
.HasForeignKey("EventCode")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Event");
});
modelBuilder.Entity("Marathon.Infrastructure.Persistence.Entities.EventEntity", b =>
{
b.Navigation("Anomalies");
b.Navigation("Result");
b.Navigation("Snapshots");
});
modelBuilder.Entity("Marathon.Infrastructure.Persistence.Entities.SnapshotEntity", b =>
{
b.Navigation("Bets");
});
#pragma warning restore 612, 618
}
}
}