f622dadf95
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.
437 lines
15 KiB
C#
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
|
|
}
|
|
}
|
|
}
|