using System; using System.Collections.Generic; using Microsoft.VisualStudio.TestTools.UnitTesting; using NT8.Core.Analytics; using NT8.Core.Common.Models; using NT8.Core.Intelligence; using NT8.Core.Logging; namespace NT8.Core.Tests.Analytics { [TestClass] public class PnLAttributorTests { private PnLAttributor _target; [TestInitialize] public void TestInitialize() { _target = new PnLAttributor(new BasicLogger("PnLAttributorTests")); } [TestMethod] public void AttributeByGrade_ReturnsSlices() { var r = _target.AttributeByGrade(Sample()); Assert.IsTrue(r.Slices.Count > 0); } [TestMethod] public void AttributeByRegime_ReturnsSlices() { var r = _target.AttributeByRegime(Sample()); Assert.IsTrue(r.Slices.Count > 0); } [TestMethod] public void AttributeByStrategy_ReturnsSlices() { var r = _target.AttributeByStrategy(Sample()); Assert.IsTrue(r.Slices.Count > 0); } [TestMethod] public void AttributeByTimeOfDay_ReturnsSlices() { var r = _target.AttributeByTimeOfDay(Sample()); Assert.IsTrue(r.Slices.Count > 0); } [TestMethod] public void MultiDimensional_ReturnsSlices() { var r = _target.AttributeMultiDimensional(Sample(), new List { AttributionDimension.Grade, AttributionDimension.Strategy }); Assert.IsTrue(r.Slices.Count > 0); } [TestMethod] public void MultiDimensional_EmptyDims_Throws() { Assert.ThrowsException(() => _target.AttributeMultiDimensional(Sample(), new List())); } [TestMethod] public void Grade_Null_Throws() { Assert.ThrowsException(() => _target.AttributeByGrade(null)); } [TestMethod] public void Regime_Null_Throws() { Assert.ThrowsException(() => _target.AttributeByRegime(null)); } [TestMethod] public void Strategy_Null_Throws() { Assert.ThrowsException(() => _target.AttributeByStrategy(null)); } [TestMethod] public void Time_Null_Throws() { Assert.ThrowsException(() => _target.AttributeByTimeOfDay(null)); } [TestMethod] public void Multi_NullTrades_Throws() { Assert.ThrowsException(() => _target.AttributeMultiDimensional(null, new List { AttributionDimension.Strategy })); } [TestMethod] public void Multi_NullDims_Throws() { Assert.ThrowsException(() => _target.AttributeMultiDimensional(Sample(), null)); } [TestMethod] public void Contribution_SumsCloseToOneWhenTotalNonZero() { var r = _target.AttributeByStrategy(Sample()); var sum = 0.0; foreach (var s in r.Slices) sum += s.Contribution; Assert.IsTrue(sum > 0.5 && sum < 1.5); } [TestMethod] public void Slice_HasDimensionName() { var r = _target.AttributeByGrade(Sample()); Assert.IsFalse(string.IsNullOrEmpty(r.Slices[0].DimensionName)); } [TestMethod] public void Slice_WinRateInRange() { var r = _target.AttributeByGrade(Sample()); Assert.IsTrue(r.Slices[0].WinRate >= 0 && r.Slices[0].WinRate <= 1); } [TestMethod] public void Report_TotalTradesMatches() { var s = Sample(); var r = _target.AttributeByGrade(s); Assert.AreEqual(s.Count, r.TotalTrades); } [TestMethod] public void Report_TotalPnLMatches() { var s = Sample(); var r = _target.AttributeByGrade(s); double p = 0; foreach (var t in s) p += t.RealizedPnL; Assert.AreEqual(p, r.TotalPnL, 0.0001); } [TestMethod] public void TimeBuckets_Assigned() { var r = _target.AttributeByTimeOfDay(Sample()); Assert.IsTrue(r.Slices.Count > 0); } private static List Sample() { return new List { Trade("S1", TradeGrade.A, 50, VolatilityRegime.Normal, TrendRegime.StrongUp, DateTime.UtcNow.Date.AddHours(9.5)), Trade("S1", TradeGrade.B, -20, VolatilityRegime.Elevated, TrendRegime.Range, DateTime.UtcNow.Date.AddHours(11)), Trade("S2", TradeGrade.C, 30, VolatilityRegime.Low, TrendRegime.WeakUp, DateTime.UtcNow.Date.AddHours(15.5)), Trade("S2", TradeGrade.A, -10, VolatilityRegime.Normal, TrendRegime.WeakDown, DateTime.UtcNow.Date.AddHours(10)) }; } private static TradeRecord Trade(string strategy, TradeGrade grade, double pnl, VolatilityRegime vol, TrendRegime trend, DateTime time) { var t = new TradeRecord(); t.TradeId = Guid.NewGuid().ToString(); t.Symbol = "ES"; t.StrategyName = strategy; t.EntryTime = time; t.ExitTime = time.AddMinutes(5); t.Side = OrderSide.Buy; t.Quantity = 1; t.EntryPrice = 100; t.ExitPrice = 101; t.RealizedPnL = pnl; t.Grade = grade; t.RiskMode = RiskMode.PCP; t.VolatilityRegime = vol; t.TrendRegime = trend; t.StopTicks = 8; t.TargetTicks = 16; t.Duration = TimeSpan.FromMinutes(5); return t; } } }