using System; using System.Collections.Generic; using System.Linq; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.DependencyInjection; using PolyTrader.Core.Analytics; using PolyTrader.Core.Persistence; using PolyTraderSharp; using PolyTraderSharp.Models; namespace PolyTrader.Web.Endpoints { public static class AnalyticsEndpoints { public static IEndpointRouteBuilder MapAnalyticsEndpoints(this IEndpointRouteBuilder routes) { var group = routes.MapGroup("/api/analytics"); group.MapGet("/", ( IServiceProvider sp, TradingState state, int? accountId, string? module, string? mode, int? days, string? search, string? winLoss, int? page, int? pageSize) => { var tradeLog = sp.GetService(); List allTrades; try { allTrades = tradeLog?.GetRecent(5000) ?? new List(); } catch { allTrades = new List(); } // Filter options var availableAccounts = new List { new { id = -1, name = "Alle Konten" } }; availableAccounts.AddRange(state.Accounts.Values .OrderBy(a => a.AccountId) .Select(a => new { id = a.AccountId, name = (string.IsNullOrEmpty(a.Name) ? $"#{a.AccountId}" : a.Name) + (a.IsDemo ? " (Demo)" : "") })); var availableModules = new List { "Alle Module" }; availableModules.AddRange(allTrades .Select(t => t.ModuleName) .Where(m => !string.IsNullOrEmpty(m)) .Distinct() .OrderBy(m => m)); // Apply Scope Filtering int accId = accountId.GetValueOrDefault(-1); string selectedModule = module ?? "Alle Module"; string selectedMode = mode ?? "Alle"; DateTime? since = days.HasValue && days.Value > 0 ? DateTime.UtcNow.AddDays(-days.Value) : null; var scoped = allTrades.Where(t => (accId < 0 || t.AccountId == accId) && (selectedModule == "Alle Module" || string.Equals(t.ModuleName, selectedModule, StringComparison.OrdinalIgnoreCase)) && (selectedMode == "Alle" || (selectedMode == "Live" && !t.IsDemo) || (selectedMode == "Demo" && t.IsDemo)) && (!since.HasValue || t.ClosedAt >= since.Value) ).ToList(); // Compute KPIs var kpis = TradeAnalytics.ComputeKpis(scoped); // Compute Charts var equityCurve = TradeAnalytics.EquityCurve(scoped) .Select(p => new { at = p.At.ToString("yyyy-MM-dd HH:mm:ss"), cumulative = p.Cumulative }) .ToList(); var pnlByModule = TradeAnalytics.PnlByKey(scoped, t => string.IsNullOrEmpty(t.ModuleName) ? "Unbekannt" : t.ModuleName) .Select(p => new { module = p.Key, pnl = p.Pnl, count = p.Count }) .ToList(); var pnlByDay = TradeAnalytics.PnlByDay(scoped) .Select(p => new { day = p.Day.ToString("yyyy-MM-dd"), pnl = p.Pnl }) .ToList(); // Filtered History var historyBase = scoped.OrderByDescending(t => t.ClosedAt); IEnumerable filteredHistory = historyBase; if (!string.IsNullOrWhiteSpace(search)) { string q = search.Trim(); filteredHistory = filteredHistory.Where(t => (t.MarketQuestion != null && t.MarketQuestion.Contains(q, StringComparison.OrdinalIgnoreCase)) || (t.Outcome != null && t.Outcome.Contains(q, StringComparison.OrdinalIgnoreCase))); } if (!string.IsNullOrWhiteSpace(winLoss)) { if (winLoss.Equals("Gewinner", StringComparison.OrdinalIgnoreCase)) filteredHistory = filteredHistory.Where(t => t.RealizedPnl > 0); else if (winLoss.Equals("Verlierer", StringComparison.OrdinalIgnoreCase)) filteredHistory = filteredHistory.Where(t => t.RealizedPnl < 0); } var filteredList = filteredHistory.ToList(); int totalTradesCount = filteredList.Count; int curPage = Math.Max(1, page.GetValueOrDefault(1)); int curPageSize = Math.Clamp(pageSize.GetValueOrDefault(50), 10, 200); int totalPages = (int)Math.Ceiling(totalTradesCount / (double)curPageSize); var pagedTrades = filteredList .Skip((curPage - 1) * curPageSize) .Take(curPageSize) .Select(t => { string accName = $"#{t.AccountId}"; if (state.Accounts.TryGetValue(t.AccountId, out var acc) && !string.IsNullOrEmpty(acc.Name)) accName = acc.Name + (t.IsDemo ? " (Demo)" : ""); return new { id = t.Id, closedAt = t.ClosedAt.ToString("yyyy-MM-dd HH:mm"), module = t.ModuleName ?? "—", account = accName, accountId = t.AccountId, isDemo = t.IsDemo, market = t.MarketQuestion ?? "—", outcome = t.Outcome ?? "—", side = t.Side ?? "—", entryPrice = t.EntryPrice, exitPrice = t.ExitPrice, size = t.Size, realizedPnl = t.RealizedPnl, pnlPercent = t.PnlPercent }; }) .ToList(); return Results.Ok(new { filterOptions = new { accounts = availableAccounts, modules = availableModules, modes = new[] { "Alle", "Live", "Demo" }, ranges = new[] { new { days = 7, label = "7 Tage" }, new { days = 30, label = "30 Tage" }, new { days = 90, label = "90 Tage" }, new { days = 0, label = "Alle" } } }, kpis = new { tradeCount = kpis.TradeCount, netPnl = kpis.NetPnl, winRatePct = kpis.WinRatePct, avgPnlPerTrade = kpis.AvgPnlPerTrade, profitFactor = kpis.ProfitFactor >= TradeAnalytics.NoLossProfitFactor ? "∞" : kpis.ProfitFactor.ToString("N2"), isNoLoss = kpis.ProfitFactor >= TradeAnalytics.NoLossProfitFactor }, charts = new { equityCurve, pnlByModule, pnlByDay }, trades = new { totalCount = totalTradesCount, page = curPage, pageSize = curPageSize, totalPages, items = pagedTrades } }); }); return routes; } } }