feat(simulation): add quant simulation microservice with virtual backtest broker and replay engine
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using FinlyticCore.Dtos.Simulation;
|
||||
|
||||
namespace FinlyticSimulation.Database.Entities;
|
||||
|
||||
[Table("simulation_runs")]
|
||||
public class SimulationRunEntity
|
||||
{
|
||||
[Key]
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
|
||||
[Required]
|
||||
[MaxLength(20)]
|
||||
public string Isin { get; set; } = string.Empty;
|
||||
|
||||
[MaxLength(30)]
|
||||
public string Symbol { get; set; } = string.Empty;
|
||||
|
||||
[Required]
|
||||
[MaxLength(50)]
|
||||
public string StrategyKey { get; set; } = string.Empty;
|
||||
|
||||
[Required]
|
||||
[MaxLength(10)]
|
||||
public string Timeframe { get; set; } = "15m";
|
||||
|
||||
public DateTime StartDateUtc { get; set; }
|
||||
|
||||
public DateTime EndDateUtc { get; set; }
|
||||
|
||||
[Column(TypeName = "decimal(18,4)")]
|
||||
public decimal StartingCapital { get; set; }
|
||||
|
||||
public int TotalTrades { get; set; }
|
||||
|
||||
public int WinningTrades { get; set; }
|
||||
|
||||
public int LosingTrades { get; set; }
|
||||
|
||||
[Column(TypeName = "decimal(6,2)")]
|
||||
public decimal WinRatePercent { get; set; }
|
||||
|
||||
[Column(TypeName = "decimal(8,4)")]
|
||||
public decimal ProfitFactor { get; set; }
|
||||
|
||||
[Column(TypeName = "decimal(6,2)")]
|
||||
public decimal MaxDrawdownPercent { get; set; }
|
||||
|
||||
[Column(TypeName = "decimal(8,2)")]
|
||||
public decimal TotalReturnPercent { get; set; }
|
||||
|
||||
[Column(TypeName = "decimal(18,4)")]
|
||||
public decimal ExpectancyEur { get; set; }
|
||||
|
||||
[Column(TypeName = "decimal(8,4)")]
|
||||
public decimal SharpeRatio { get; set; }
|
||||
|
||||
public BacktestReportDto ReportJson { get; set; } = null!;
|
||||
|
||||
public DateTime CreatedAtUtc { get; set; } = DateTime.UtcNow;
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace FinlyticSimulation.Database.Entities;
|
||||
|
||||
[Table("simulation_strategy_matrix")]
|
||||
public class SimulationStrategyMatrixEntity
|
||||
{
|
||||
[Required]
|
||||
[MaxLength(20)]
|
||||
public string Isin { get; set; } = string.Empty;
|
||||
|
||||
[Required]
|
||||
[MaxLength(50)]
|
||||
public string StrategyKey { get; set; } = string.Empty;
|
||||
|
||||
[Required]
|
||||
[MaxLength(10)]
|
||||
public string Timeframe { get; set; } = "15m";
|
||||
|
||||
public int SampleTradesCount { get; set; }
|
||||
|
||||
[Column(TypeName = "decimal(6,2)")]
|
||||
public decimal WinRatePercent { get; set; }
|
||||
|
||||
[Column(TypeName = "decimal(8,4)")]
|
||||
public decimal ProfitFactor { get; set; }
|
||||
|
||||
[Column(TypeName = "decimal(6,2)")]
|
||||
public decimal MaxDrawdownPercent { get; set; }
|
||||
|
||||
[Column(TypeName = "decimal(5,2)")]
|
||||
public decimal ReliabilityScore { get; set; } // 0 - 100
|
||||
|
||||
public bool IsApproved { get; set; } = true;
|
||||
|
||||
[MaxLength(30)]
|
||||
public string RecommendedAction { get; set; } = "NEUTRAL"; // "BOOST_SCORE", "NEUTRAL", "VETO_DISABLE"
|
||||
|
||||
public Guid? LastBacktestRunId { get; set; }
|
||||
|
||||
public DateTime UpdatedAtUtc { get; set; } = DateTime.UtcNow;
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace FinlyticSimulation.Database.Entities;
|
||||
|
||||
/// <summary>
|
||||
/// A saved, named-by-(Isin, StrategyKey) set of tunable indicator parameter overrides (see
|
||||
/// <c>TechnicalContext.ParameterOverrides</c>), so a parameter set found useful via repeated backtest
|
||||
/// experimentation can be reused without retyping it every time. Purely a backtesting-side convenience - never
|
||||
/// read by live scanning (<c>FinlyticTechnicals.Services.TechnicalScoringEngine</c> never queries this table).
|
||||
/// </summary>
|
||||
[Table("simulation_strategy_parameters")]
|
||||
public class SimulationStrategyParameterEntity
|
||||
{
|
||||
[Required]
|
||||
[MaxLength(20)]
|
||||
public string Isin { get; set; } = string.Empty;
|
||||
|
||||
[Required]
|
||||
[MaxLength(50)]
|
||||
public string StrategyKey { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>Keyed by <c>"{StrategyKey}.{ParameterName}"</c>, matching <c>TechnicalContext.ParameterOverrides</c> 1:1.</summary>
|
||||
public Dictionary<string, decimal> Parameters { get; set; } = new();
|
||||
|
||||
public DateTime UpdatedAtUtc { get; set; } = DateTime.UtcNow;
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json;
|
||||
using FinlyticCore.Database;
|
||||
using FinlyticCore.Dtos.Simulation;
|
||||
using FinlyticCore.Entities.Settings;
|
||||
using FinlyticSimulation.Database.Entities;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Design;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
namespace FinlyticSimulation.Database;
|
||||
|
||||
public class SimulationDbContext : DbContext, ISettingsDbContext
|
||||
{
|
||||
private static readonly JsonSerializerOptions JsonOptions = new()
|
||||
{
|
||||
PropertyNameCaseInsensitive = true,
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||
WriteIndented = false
|
||||
};
|
||||
|
||||
public SimulationDbContext(DbContextOptions<SimulationDbContext> options) : base(options)
|
||||
{
|
||||
}
|
||||
|
||||
public DbSet<SettingEntity> DynamicSettings => Set<SettingEntity>();
|
||||
public DbSet<SimulationRunEntity> SimulationRuns => Set<SimulationRunEntity>();
|
||||
public DbSet<SimulationStrategyMatrixEntity> StrategyMatrix => Set<SimulationStrategyMatrixEntity>();
|
||||
public DbSet<SimulationStrategyParameterEntity> StrategyParameters => Set<SimulationStrategyParameterEntity>();
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
base.OnModelCreating(modelBuilder);
|
||||
|
||||
// 1. Settings Table
|
||||
modelBuilder.Entity<SettingEntity>(entity =>
|
||||
{
|
||||
entity.HasKey(e => e.Id);
|
||||
entity.HasIndex(e => e.Key).IsUnique();
|
||||
});
|
||||
|
||||
// 2. Report JSONB Converter
|
||||
var reportConverter = new ValueConverter<BacktestReportDto, string>(
|
||||
v => JsonSerializer.Serialize(v, JsonOptions),
|
||||
v => JsonSerializer.Deserialize<BacktestReportDto>(v, JsonOptions) ?? new BacktestReportDto(
|
||||
Guid.Empty, "", "", "", "", DateTime.UtcNow, DateTime.UtcNow, 0, 0, 0, 0m, 0m, 0m, 0m, 0m, 0m, 0m, TimeSpan.Zero, new List<BacktestTradeDto>(), new List<EquityPointDto>())
|
||||
);
|
||||
|
||||
// 3. Simulation Runs Table
|
||||
modelBuilder.Entity<SimulationRunEntity>(entity =>
|
||||
{
|
||||
entity.HasKey(e => e.Id);
|
||||
entity.HasIndex(e => new { e.Isin, e.StrategyKey, e.Timeframe });
|
||||
entity.HasIndex(e => e.CreatedAtUtc);
|
||||
|
||||
entity.Property(e => e.ReportJson)
|
||||
.HasColumnType("jsonb")
|
||||
.HasConversion(reportConverter);
|
||||
});
|
||||
|
||||
// 4. Strategy Matrix Table
|
||||
modelBuilder.Entity<SimulationStrategyMatrixEntity>(entity =>
|
||||
{
|
||||
entity.HasKey(e => new { e.Isin, e.StrategyKey, e.Timeframe });
|
||||
entity.HasIndex(e => new { e.Isin, e.IsApproved });
|
||||
entity.HasIndex(e => e.ReliabilityScore);
|
||||
});
|
||||
|
||||
// 5. Saved Strategy Parameter Profiles Table
|
||||
var parametersConverter = new ValueConverter<Dictionary<string, decimal>, string>(
|
||||
v => JsonSerializer.Serialize(v, JsonOptions),
|
||||
v => JsonSerializer.Deserialize<Dictionary<string, decimal>>(v, JsonOptions) ?? new Dictionary<string, decimal>()
|
||||
);
|
||||
|
||||
modelBuilder.Entity<SimulationStrategyParameterEntity>(entity =>
|
||||
{
|
||||
entity.HasKey(e => new { e.Isin, e.StrategyKey });
|
||||
|
||||
entity.Property(e => e.Parameters)
|
||||
.HasColumnType("jsonb")
|
||||
.HasConversion(parametersConverter);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public class SimulationDbContextFactory : IDesignTimeDbContextFactory<SimulationDbContext>
|
||||
{
|
||||
public SimulationDbContext CreateDbContext(string[] args)
|
||||
{
|
||||
var optionsBuilder = new DbContextOptionsBuilder<SimulationDbContext>();
|
||||
optionsBuilder.UseNpgsql("Host=localhost;Database=finlytic_simulation;Username=postgres;Password=postgres");
|
||||
return new SimulationDbContext(optionsBuilder.Options);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user