feat: Complete Phase 5 Analytics & Reporting implementation
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:
2026-02-16 21:30:51 -05:00
parent e93cbc1619
commit 0e36fe5d23
26 changed files with 6756 additions and 0 deletions

View File

@@ -0,0 +1,264 @@
using System;
using System.Collections.Generic;
using System.Linq;
using NT8.Core.Intelligence;
using NT8.Core.Logging;
namespace NT8.Core.Analytics
{
/// <summary>
/// Filterable and sortable trade blotter service.
/// </summary>
public class TradeBlotter
{
private readonly ILogger _logger;
private readonly object _lock;
private readonly List<TradeRecord> _trades;
public TradeBlotter(ILogger logger)
{
if (logger == null)
throw new ArgumentNullException("logger");
_logger = logger;
_lock = new object();
_trades = new List<TradeRecord>();
}
/// <summary>
/// Replaces blotter trade set.
/// </summary>
public void SetTrades(List<TradeRecord> trades)
{
if (trades == null)
throw new ArgumentNullException("trades");
try
{
lock (_lock)
{
_trades.Clear();
_trades.AddRange(trades.Select(Clone));
}
}
catch (Exception ex)
{
_logger.LogError("SetTrades failed: {0}", ex.Message);
throw;
}
}
/// <summary>
/// Appends one trade and supports real-time update flow.
/// </summary>
public void AddOrUpdateTrade(TradeRecord trade)
{
if (trade == null)
throw new ArgumentNullException("trade");
try
{
lock (_lock)
{
var index = _trades.FindIndex(t => t.TradeId == trade.TradeId);
if (index >= 0)
_trades[index] = Clone(trade);
else
_trades.Add(Clone(trade));
}
}
catch (Exception ex)
{
_logger.LogError("AddOrUpdateTrade failed: {0}", ex.Message);
throw;
}
}
/// <summary>
/// Filters by date range.
/// </summary>
public List<TradeRecord> FilterByDate(DateTime start, DateTime end)
{
try
{
lock (_lock)
{
return _trades
.Where(t => t.EntryTime >= start && t.EntryTime <= end)
.OrderBy(t => t.EntryTime)
.Select(Clone)
.ToList();
}
}
catch (Exception ex)
{
_logger.LogError("FilterByDate failed: {0}", ex.Message);
throw;
}
}
/// <summary>
/// Filters by symbol.
/// </summary>
public List<TradeRecord> FilterBySymbol(string symbol)
{
if (string.IsNullOrEmpty(symbol))
throw new ArgumentNullException("symbol");
try
{
lock (_lock)
{
return _trades
.Where(t => string.Equals(t.Symbol, symbol, StringComparison.OrdinalIgnoreCase))
.OrderBy(t => t.EntryTime)
.Select(Clone)
.ToList();
}
}
catch (Exception ex)
{
_logger.LogError("FilterBySymbol failed: {0}", ex.Message);
throw;
}
}
/// <summary>
/// Filters by grade.
/// </summary>
public List<TradeRecord> FilterByGrade(TradeGrade grade)
{
try
{
lock (_lock)
{
return _trades
.Where(t => t.Grade == grade)
.OrderBy(t => t.EntryTime)
.Select(Clone)
.ToList();
}
}
catch (Exception ex)
{
_logger.LogError("FilterByGrade failed: {0}", ex.Message);
throw;
}
}
/// <summary>
/// Filters by realized pnl range.
/// </summary>
public List<TradeRecord> FilterByPnL(double minPnL, double maxPnL)
{
try
{
lock (_lock)
{
return _trades
.Where(t => t.RealizedPnL >= minPnL && t.RealizedPnL <= maxPnL)
.OrderBy(t => t.EntryTime)
.Select(Clone)
.ToList();
}
}
catch (Exception ex)
{
_logger.LogError("FilterByPnL failed: {0}", ex.Message);
throw;
}
}
/// <summary>
/// Sorts by named column.
/// </summary>
public List<TradeRecord> SortBy(string column, SortDirection direction)
{
if (string.IsNullOrEmpty(column))
throw new ArgumentNullException("column");
try
{
lock (_lock)
{
IEnumerable<TradeRecord> ordered;
var normalized = column.Trim().ToLowerInvariant();
switch (normalized)
{
case "time":
case "entrytime":
ordered = direction == SortDirection.Asc
? _trades.OrderBy(t => t.EntryTime)
: _trades.OrderByDescending(t => t.EntryTime);
break;
case "symbol":
ordered = direction == SortDirection.Asc
? _trades.OrderBy(t => t.Symbol)
: _trades.OrderByDescending(t => t.Symbol);
break;
case "pnl":
ordered = direction == SortDirection.Asc
? _trades.OrderBy(t => t.RealizedPnL)
: _trades.OrderByDescending(t => t.RealizedPnL);
break;
case "grade":
ordered = direction == SortDirection.Asc
? _trades.OrderBy(t => t.Grade)
: _trades.OrderByDescending(t => t.Grade);
break;
case "rmultiple":
ordered = direction == SortDirection.Asc
? _trades.OrderBy(t => t.RMultiple)
: _trades.OrderByDescending(t => t.RMultiple);
break;
case "duration":
ordered = direction == SortDirection.Asc
? _trades.OrderBy(t => t.Duration)
: _trades.OrderByDescending(t => t.Duration);
break;
default:
ordered = direction == SortDirection.Asc
? _trades.OrderBy(t => t.EntryTime)
: _trades.OrderByDescending(t => t.EntryTime);
break;
}
return ordered.Select(Clone).ToList();
}
}
catch (Exception ex)
{
_logger.LogError("SortBy failed: {0}", ex.Message);
throw;
}
}
private static TradeRecord Clone(TradeRecord input)
{
var copy = new TradeRecord();
copy.TradeId = input.TradeId;
copy.Symbol = input.Symbol;
copy.StrategyName = input.StrategyName;
copy.EntryTime = input.EntryTime;
copy.ExitTime = input.ExitTime;
copy.Side = input.Side;
copy.Quantity = input.Quantity;
copy.EntryPrice = input.EntryPrice;
copy.ExitPrice = input.ExitPrice;
copy.RealizedPnL = input.RealizedPnL;
copy.UnrealizedPnL = input.UnrealizedPnL;
copy.Grade = input.Grade;
copy.ConfluenceScore = input.ConfluenceScore;
copy.RiskMode = input.RiskMode;
copy.VolatilityRegime = input.VolatilityRegime;
copy.TrendRegime = input.TrendRegime;
copy.StopTicks = input.StopTicks;
copy.TargetTicks = input.TargetTicks;
copy.RMultiple = input.RMultiple;
copy.Duration = input.Duration;
copy.Metadata = new Dictionary<string, object>(input.Metadata);
return copy;
}
}
}