using System; using System.Collections.Generic; using System.Linq; using NT8.Core.Intelligence; using NT8.Core.Logging; namespace NT8.Core.Analytics { /// /// Filterable and sortable trade blotter service. /// public class TradeBlotter { private readonly ILogger _logger; private readonly object _lock; private readonly List _trades; public TradeBlotter(ILogger logger) { if (logger == null) throw new ArgumentNullException("logger"); _logger = logger; _lock = new object(); _trades = new List(); } /// /// Replaces blotter trade set. /// public void SetTrades(List 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; } } /// /// Appends one trade and supports real-time update flow. /// 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; } } /// /// Filters by date range. /// public List 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; } } /// /// Filters by symbol. /// public List 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; } } /// /// Filters by grade. /// public List 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; } } /// /// Filters by realized pnl range. /// public List 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; } } /// /// Sorts by named column. /// public List SortBy(string column, SortDirection direction) { if (string.IsNullOrEmpty(column)) throw new ArgumentNullException("column"); try { lock (_lock) { IEnumerable 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(input.Metadata); return copy; } } }