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.
64 lines
2.6 KiB
C#
64 lines
2.6 KiB
C#
using Marathon.Application.Abstractions;
|
|
using Marathon.Application.Storage;
|
|
using Marathon.Infrastructure.Export;
|
|
using Marathon.Infrastructure.Persistence.Repositories;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using Microsoft.Extensions.Configuration;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
using Microsoft.Extensions.Options;
|
|
|
|
namespace Marathon.Infrastructure.Persistence;
|
|
|
|
/// <summary>
|
|
/// DI extension that wires up the persistence layer (DbContext, repositories, exporter).
|
|
/// Call this from the host's DI setup — do NOT call from DependencyInjection.cs (Phase 4).
|
|
/// </summary>
|
|
public static class PersistenceModule
|
|
{
|
|
/// <summary>
|
|
/// Registers EF Core DbContext, all repositories and the Excel exporter.
|
|
/// Reads <c>Storage:DatabasePath</c> from <paramref name="config"/>.
|
|
/// </summary>
|
|
public static IServiceCollection AddMarathonPersistence(
|
|
this IServiceCollection services,
|
|
IConfiguration config)
|
|
{
|
|
services.AddOptions<StorageOptions>()
|
|
.Bind(config.GetSection(StorageOptions.SectionName))
|
|
.ValidateOnStart();
|
|
|
|
services.AddDbContext<MarathonDbContext>((sp, opts) =>
|
|
{
|
|
var storageOptions = sp.GetRequiredService<IOptions<StorageOptions>>().Value;
|
|
var dbPath = storageOptions.DatabasePath;
|
|
|
|
// Ensure the directory exists
|
|
var dir = Path.GetDirectoryName(dbPath);
|
|
if (!string.IsNullOrEmpty(dir))
|
|
Directory.CreateDirectory(dir);
|
|
|
|
// Configure SQLite with WAL journal mode
|
|
opts.UseSqlite(
|
|
$"Data Source={dbPath}",
|
|
sqliteOpts => sqliteOpts.CommandTimeout(30));
|
|
});
|
|
|
|
// Register initializer — the HOST must resolve this at startup and call InitializeAsync().
|
|
// Example in Program.cs:
|
|
// using var scope = app.Services.CreateScope();
|
|
// await scope.ServiceProvider.GetRequiredService<MarathonDbContextInitializer>().InitializeAsync();
|
|
services.AddScoped<MarathonDbContextInitializer>();
|
|
|
|
services.AddScoped<IEventRepository, EventRepository>();
|
|
services.AddScoped<ISnapshotRepository, SnapshotRepository>();
|
|
services.AddScoped<IResultRepository, ResultRepository>();
|
|
services.AddScoped<IAnomalyRepository, AnomalyRepository>();
|
|
services.AddScoped<IPlacedBetRepository, PlacedBetRepository>();
|
|
services.AddScoped<ISavedStrategyRepository, SavedStrategyRepository>();
|
|
services.AddScoped<IPaperBetRepository, PaperBetRepository>();
|
|
services.AddScoped<IExcelExporter, ExcelExporter>();
|
|
|
|
return services;
|
|
}
|
|
}
|