using System; using System.Collections.Generic; using System.Linq; using PolyTrader.Core.Analytics; using PolyTraderSharp.Models; using Xunit; namespace PolyTrader.Tests.WebUi { public class AnalyticsEndpointsTests { private List CreateSampleTrades() { return new List { new TradeRecord { Id = "trade-1", AccountId = 1, ModuleName = "CopyTrading", MarketQuestion = "Will Bitcoin hit 100k in 2026?", Outcome = "Yes", Side = "BUY", IsDemo = false, EntryPrice = 0.40m, ExitPrice = 0.80m, Size = 100m, RealizedPnl = 40m, PnlPercent = 100m, ClosedAt = DateTime.UtcNow.AddDays(-2) }, new TradeRecord { Id = "trade-2", AccountId = 1, ModuleName = "CopyTrading", MarketQuestion = "Will Ethereum hit 5k in 2026?", Outcome = "No", Side = "BUY", IsDemo = false, EntryPrice = 0.50m, ExitPrice = 0.20m, Size = 50m, RealizedPnl = -15m, PnlPercent = -60m, ClosedAt = DateTime.UtcNow.AddDays(-1) }, new TradeRecord { Id = "trade-3", AccountId = 2, ModuleName = "ResolutionFarming", MarketQuestion = "Fed interest rate cut in Q1?", Outcome = "Yes", Side = "BUY", IsDemo = true, EntryPrice = 0.90m, ExitPrice = 1.00m, Size = 200m, RealizedPnl = 20m, PnlPercent = 11.1m, ClosedAt = DateTime.UtcNow.AddDays(-5) }, new TradeRecord { Id = "trade-4", AccountId = 2, ModuleName = "ResolutionFarming", MarketQuestion = "US GDP growth above 2%?", Outcome = "Yes", Side = "BUY", IsDemo = false, EntryPrice = 0.92m, ExitPrice = 1.00m, Size = 100m, RealizedPnl = 8m, PnlPercent = 8.7m, ClosedAt = DateTime.UtcNow.AddDays(-15) } }; } [Fact] public void Analytics_FilterByAccount_ReturnsOnlyMatchingTrades() { var trades = CreateSampleTrades(); int filterAccId = 1; var filtered = trades.Where(t => t.AccountId == filterAccId).ToList(); Assert.Equal(2, filtered.Count); Assert.All(filtered, t => Assert.Equal(1, t.AccountId)); } [Fact] public void Analytics_FilterByModule_ReturnsOnlyMatchingModuleTrades() { var trades = CreateSampleTrades(); string selectedModule = "ResolutionFarming"; var filtered = trades.Where(t => string.Equals(t.ModuleName, selectedModule, StringComparison.OrdinalIgnoreCase)).ToList(); Assert.Equal(2, filtered.Count); Assert.All(filtered, t => Assert.Equal("ResolutionFarming", t.ModuleName)); } [Fact] public void Analytics_FilterByMode_SeparatesLiveAndDemo() { var trades = CreateSampleTrades(); var liveTrades = trades.Where(t => !t.IsDemo).ToList(); var demoTrades = trades.Where(t => t.IsDemo).ToList(); Assert.Equal(3, liveTrades.Count); var singleDemo = Assert.Single(demoTrades); Assert.Equal("trade-3", singleDemo.Id); } [Fact] public void Analytics_FilterByDays_AppliesTimeWindow() { var trades = CreateSampleTrades(); var since7d = DateTime.UtcNow.AddDays(-7); var recentTrades = trades.Where(t => t.ClosedAt >= since7d).ToList(); Assert.Equal(3, recentTrades.Count); Assert.DoesNotContain(recentTrades, t => t.Id == "trade-4"); // Id trade-4 is 15 days ago } [Fact] public void Analytics_SearchFilter_MatchesMarketQuestionOrOutcome() { var trades = CreateSampleTrades(); string query = "Bitcoin"; var matched = trades.Where(t => (t.MarketQuestion != null && t.MarketQuestion.Contains(query, StringComparison.OrdinalIgnoreCase)) || (t.Outcome != null && t.Outcome.Contains(query, StringComparison.OrdinalIgnoreCase)) ).ToList(); var match = Assert.Single(matched); Assert.Equal("trade-1", match.Id); } [Fact] public void Analytics_WinLossFilter_SeparatesProfitableAndLosingTrades() { var trades = CreateSampleTrades(); var winners = trades.Where(t => t.RealizedPnl > 0).ToList(); var losers = trades.Where(t => t.RealizedPnl < 0).ToList(); Assert.Equal(3, winners.Count); var singleLoser = Assert.Single(losers); Assert.Equal("trade-2", singleLoser.Id); } [Fact] public void Analytics_ComputeKpis_ReturnsAccurateMetrics() { var trades = CreateSampleTrades(); var kpis = TradeAnalytics.ComputeKpis(trades); Assert.Equal(4, kpis.TradeCount); Assert.Equal(53m, kpis.NetPnl); // 40 - 15 + 20 + 8 = 53 Assert.Equal(75m, kpis.WinRatePct); // 3 wins out of 4 = 75% Assert.Equal(13.25m, kpis.AvgPnlPerTrade); // 53 / 4 = 13.25 Assert.True(kpis.ProfitFactor > 4.5m); // Gross wins: 68, Gross loss: 15 -> 4.53 } [Fact] public void Analytics_Charts_CalculatesSeriesProperly() { var trades = CreateSampleTrades(); var equityCurve = TradeAnalytics.EquityCurve(trades).ToList(); Assert.NotEmpty(equityCurve); Assert.Equal(53m, equityCurve.Last().Cumulative); var pnlByModule = TradeAnalytics.PnlByKey(trades, t => t.ModuleName).ToList(); Assert.Equal(2, pnlByModule.Count); var copyTradingPnl = pnlByModule.First(m => m.Key == "CopyTrading"); Assert.Equal(25m, copyTradingPnl.Pnl); // 40 - 15 = 25 var resFarmingPnl = pnlByModule.First(m => m.Key == "ResolutionFarming"); Assert.Equal(28m, resFarmingPnl.Pnl); // 20 + 8 = 28 } } }