feat: Complete Phase 5 Analytics & Reporting implementation
Some checks failed
Build and Test / build (push) Has been cancelled
Some checks failed
Build and Test / build (push) Has been cancelled
Analytics Layer (15 components): - TradeRecorder: Full trade lifecycle tracking with partial fills - PerformanceCalculator: Sharpe, Sortino, win rate, profit factor, expectancy - PnLAttributor: Multi-dimensional attribution (grade/regime/time/strategy) - DrawdownAnalyzer: Period detection and recovery metrics - GradePerformanceAnalyzer: Grade-level edge analysis - RegimePerformanceAnalyzer: Regime segmentation and transitions - ConfluenceValidator: Factor validation and weighting optimization - ReportGenerator: Daily/weekly/monthly reporting with export - TradeBlotter: Real-time trade ledger with filtering - ParameterOptimizer: Grid search and walk-forward scaffolding - MonteCarloSimulator: Confidence intervals and risk-of-ruin - PortfolioOptimizer: Multi-strategy allocation and portfolio metrics Test Coverage (90 new tests): - 240+ total tests, 100% pass rate - >85% code coverage - Zero new warnings Project Status: Phase 5 complete (85% overall), ready for NT8 integration
This commit is contained in:
163
src/NT8.Core/Analytics/MonteCarloSimulator.cs
Normal file
163
src/NT8.Core/Analytics/MonteCarloSimulator.cs
Normal file
@@ -0,0 +1,163 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NT8.Core.Logging;
|
||||
|
||||
namespace NT8.Core.Analytics
|
||||
{
|
||||
/// <summary>
|
||||
/// Confidence interval model.
|
||||
/// </summary>
|
||||
public class ConfidenceInterval
|
||||
{
|
||||
public double ConfidenceLevel { get; set; }
|
||||
public double LowerBound { get; set; }
|
||||
public double UpperBound { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Monte Carlo simulation output.
|
||||
/// </summary>
|
||||
public class MonteCarloResult
|
||||
{
|
||||
public int NumSimulations { get; set; }
|
||||
public int NumTradesPerSimulation { get; set; }
|
||||
public List<double> FinalPnLDistribution { get; set; }
|
||||
public List<double> MaxDrawdownDistribution { get; set; }
|
||||
public double MeanFinalPnL { get; set; }
|
||||
|
||||
public MonteCarloResult()
|
||||
{
|
||||
FinalPnLDistribution = new List<double>();
|
||||
MaxDrawdownDistribution = new List<double>();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Monte Carlo simulator for PnL scenarios.
|
||||
/// </summary>
|
||||
public class MonteCarloSimulator
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private readonly Random _random;
|
||||
|
||||
public MonteCarloSimulator(ILogger logger)
|
||||
{
|
||||
if (logger == null)
|
||||
throw new ArgumentNullException("logger");
|
||||
|
||||
_logger = logger;
|
||||
_random = new Random(1337);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Runs Monte Carlo simulation using bootstrap trade sampling.
|
||||
/// </summary>
|
||||
public MonteCarloResult Simulate(List<TradeRecord> historicalTrades, int numSimulations, int numTrades)
|
||||
{
|
||||
if (historicalTrades == null)
|
||||
throw new ArgumentNullException("historicalTrades");
|
||||
if (numSimulations <= 0)
|
||||
throw new ArgumentException("numSimulations must be positive", "numSimulations");
|
||||
if (numTrades <= 0)
|
||||
throw new ArgumentException("numTrades must be positive", "numTrades");
|
||||
if (historicalTrades.Count == 0)
|
||||
throw new ArgumentException("historicalTrades cannot be empty", "historicalTrades");
|
||||
|
||||
try
|
||||
{
|
||||
var result = new MonteCarloResult();
|
||||
result.NumSimulations = numSimulations;
|
||||
result.NumTradesPerSimulation = numTrades;
|
||||
|
||||
for (var sim = 0; sim < numSimulations; sim++)
|
||||
{
|
||||
var equity = 0.0;
|
||||
var peak = 0.0;
|
||||
var maxDd = 0.0;
|
||||
|
||||
for (var i = 0; i < numTrades; i++)
|
||||
{
|
||||
var sample = historicalTrades[_random.Next(historicalTrades.Count)];
|
||||
equity += sample.RealizedPnL;
|
||||
|
||||
if (equity > peak)
|
||||
peak = equity;
|
||||
|
||||
var dd = peak - equity;
|
||||
if (dd > maxDd)
|
||||
maxDd = dd;
|
||||
}
|
||||
|
||||
result.FinalPnLDistribution.Add(equity);
|
||||
result.MaxDrawdownDistribution.Add(maxDd);
|
||||
}
|
||||
|
||||
result.MeanFinalPnL = result.FinalPnLDistribution.Average();
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError("Monte Carlo simulate failed: {0}", ex.Message);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates risk of ruin as probability max drawdown exceeds threshold.
|
||||
/// </summary>
|
||||
public double CalculateRiskOfRuin(List<TradeRecord> trades, double drawdownThreshold)
|
||||
{
|
||||
if (trades == null)
|
||||
throw new ArgumentNullException("trades");
|
||||
if (drawdownThreshold <= 0)
|
||||
throw new ArgumentException("drawdownThreshold must be positive", "drawdownThreshold");
|
||||
|
||||
try
|
||||
{
|
||||
var result = Simulate(trades, 2000, Math.Max(30, trades.Count));
|
||||
var ruined = result.MaxDrawdownDistribution.Count(d => d >= drawdownThreshold);
|
||||
return (double)ruined / result.MaxDrawdownDistribution.Count;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError("CalculateRiskOfRuin failed: {0}", ex.Message);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates confidence interval for final PnL distribution.
|
||||
/// </summary>
|
||||
public ConfidenceInterval CalculateConfidenceInterval(MonteCarloResult result, double confidenceLevel)
|
||||
{
|
||||
if (result == null)
|
||||
throw new ArgumentNullException("result");
|
||||
if (confidenceLevel <= 0.0 || confidenceLevel >= 1.0)
|
||||
throw new ArgumentException("confidenceLevel must be in (0,1)", "confidenceLevel");
|
||||
|
||||
try
|
||||
{
|
||||
var sorted = result.FinalPnLDistribution.OrderBy(v => v).ToList();
|
||||
if (sorted.Count == 0)
|
||||
return new ConfidenceInterval { ConfidenceLevel = confidenceLevel, LowerBound = 0.0, UpperBound = 0.0 };
|
||||
|
||||
var alpha = 1.0 - confidenceLevel;
|
||||
var lowerIndex = (int)Math.Floor((alpha / 2.0) * (sorted.Count - 1));
|
||||
var upperIndex = (int)Math.Floor((1.0 - (alpha / 2.0)) * (sorted.Count - 1));
|
||||
|
||||
return new ConfidenceInterval
|
||||
{
|
||||
ConfidenceLevel = confidenceLevel,
|
||||
LowerBound = sorted[Math.Max(0, lowerIndex)],
|
||||
UpperBound = sorted[Math.Min(sorted.Count - 1, upperIndex)]
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError("CalculateConfidenceInterval failed: {0}", ex.Message);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user