feat(simulation): add quant simulation microservice with virtual backtest broker and replay engine
This commit is contained in:
@@ -0,0 +1,51 @@
|
||||
using System;
|
||||
using FinlyticCore.Dtos.Simulation;
|
||||
|
||||
namespace FinlyticSimulation.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Pure scoring function for FinlyticSimulation's backtest-reliability matrix, extracted out of
|
||||
/// <c>QuantSimulationEngine.RunBacktestAsync</c> (which previously mixed candle-fetch-with-fallback, replay
|
||||
/// orchestration, DB persistence, AND this scoring math into one large method with inline magic numbers). No
|
||||
/// I/O, no DB access - just <see cref="BacktestReportDto"/> + threshold settings in, a verdict out, so this is
|
||||
/// independently unit-testable without spinning up a DbContext or a real backtest.
|
||||
/// </summary>
|
||||
public static class ReliabilityMatrixCalculator
|
||||
{
|
||||
/// <param name="ReliabilityScore">0-100, a blend of profit factor (max 1.5x weight, capped) and win rate.</param>
|
||||
/// <param name="IsApproved">
|
||||
/// Whether <see cref="Scoring.ICompositeOpportunityScorer"/>-style consumers should trust this
|
||||
/// strategy/asset combination. Defaults to approved when there isn't yet enough sample data to judge it
|
||||
/// (Rules.md §4: "not enough data" must never read the same as "actively vetoed").
|
||||
/// </param>
|
||||
/// <param name="RecommendedAction">"BOOST_SCORE" / "NEUTRAL" / "VETO_DISABLE" - see <see cref="Calculate"/>.</param>
|
||||
public record Result(decimal ReliabilityScore, bool IsApproved, string RecommendedAction);
|
||||
|
||||
/// <summary>
|
||||
/// Scores a single completed backtest report against the given approval thresholds
|
||||
/// (<c>SimulationSettingKeys.MinSampleTradesForApproval</c>/<c>HighProfitFactorThreshold</c>/<c>LowProfitFactorThreshold</c>).
|
||||
/// </summary>
|
||||
public static Result Calculate(
|
||||
BacktestReportDto report,
|
||||
decimal minSampleTrades,
|
||||
decimal highProfitFactorThreshold,
|
||||
decimal lowProfitFactorThreshold)
|
||||
{
|
||||
// 0..100 blend: profit factor contributes up to 75 points (capped at PF=3.0 -> 1.5 * 50), win rate
|
||||
// contributes up to 50 points (100% WR * 0.5) - deliberately not a simple average, since a high win
|
||||
// rate with a poor profit factor (many tiny wins, rare huge losses) should not score as "reliable".
|
||||
decimal rawScore = (Math.Clamp(report.ProfitFactor / 2.0m, 0m, 1.5m) * 50m) + (report.WinRatePercent * 0.5m);
|
||||
decimal reliabilityScore = Math.Clamp(Math.Round(rawScore, 2), 0m, 100m);
|
||||
|
||||
bool isApproved = report.ProfitFactor >= lowProfitFactorThreshold || report.TotalTrades < minSampleTrades;
|
||||
string recommendedAction = "NEUTRAL";
|
||||
|
||||
if (report.TotalTrades >= minSampleTrades)
|
||||
{
|
||||
if (report.ProfitFactor >= highProfitFactorThreshold) recommendedAction = "BOOST_SCORE";
|
||||
else if (report.ProfitFactor < lowProfitFactorThreshold) recommendedAction = "VETO_DISABLE";
|
||||
}
|
||||
|
||||
return new Result(reliabilityScore, isApproved, recommendedAction);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user