UI: Launcher-Account-Uebersicht + pure TradeAnalytics-Fundament
- TradeAnalytics (Core, pur/testbar): KPIs (Netto-PnL/Winrate/Ø/Profit-Faktor), Equity-Kurve, PnL je Modul/Account/Tag, Window-Summary. Speist Dashboard + Launcher. 7 Tests. - Launcher dgv_accountlist: Spalten via Designer (Account, Module, Polymarket-Button, Wallet-USDC, 3T-PnL, 3T-Winrate, Overall P/L). Daten je Account aus dem Core-Trade-Log via TradeAnalytics; Auto-Refresh alle 30 s; Polymarket-Button oeffnet das Wallet-Profil. DB-Abfragen fehlertolerant. Build 0 Fehler, 331 Tests gruen, --smoke-ui ok (Launcher laedt Uebersicht). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
0aedddabe8
commit
7d63791e38
@@ -0,0 +1,82 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using PolyTraderSharp.Models;
|
||||
|
||||
namespace PolyTrader.Core.Analytics
|
||||
{
|
||||
/// <summary>
|
||||
/// Reine, seiteneffektfreie Auswertungslogik über den generischen Core-Trade-Log
|
||||
/// (<see cref="TradeRecord"/>). Speist Dashboard-Kennzahlen/Charts und die Launcher-Account-Übersicht.
|
||||
/// Die Filterung (Account/Modul/Demo/Zeitraum) trifft der Aufrufer; hier wird nur aggregiert.
|
||||
/// Vollständig unit-getestet, weil „profitabel ja/nein" davon abhängt.
|
||||
/// </summary>
|
||||
public static class TradeAnalytics
|
||||
{
|
||||
/// <summary>Kernkennzahlen eines Trade-Sets (ohne Fees – die liegen nur in den Modul-Logs).</summary>
|
||||
public readonly record struct Kpis(
|
||||
int TradeCount, decimal NetPnl, decimal WinRatePct, decimal AvgPnlPerTrade, decimal ProfitFactor);
|
||||
|
||||
/// <summary>Profit-Faktor bei verlustfreiem Set (∞) – als großer, endlicher Anzeigewert.</summary>
|
||||
public const decimal NoLossProfitFactor = 999m;
|
||||
|
||||
public static Kpis ComputeKpis(IEnumerable<TradeRecord> trades)
|
||||
{
|
||||
var list = trades as IReadOnlyList<TradeRecord> ?? trades.ToList();
|
||||
int n = list.Count;
|
||||
if (n == 0) return new Kpis(0, 0m, 0m, 0m, 0m);
|
||||
|
||||
decimal net = list.Sum(t => t.RealizedPnl);
|
||||
int wins = list.Count(t => t.RealizedPnl > 0m);
|
||||
decimal grossWin = list.Where(t => t.RealizedPnl > 0m).Sum(t => t.RealizedPnl);
|
||||
decimal grossLoss = list.Where(t => t.RealizedPnl < 0m).Sum(t => -t.RealizedPnl);
|
||||
decimal pf = grossLoss > 0m ? grossWin / grossLoss : (grossWin > 0m ? NoLossProfitFactor : 0m);
|
||||
|
||||
return new Kpis(n, net, 100m * wins / n, net / n, pf);
|
||||
}
|
||||
|
||||
/// <summary>Equity-Kurve: nach Abschlusszeit sortiert, kumulierter realisierter PnL.</summary>
|
||||
public static List<(DateTime At, decimal Cumulative)> EquityCurve(IEnumerable<TradeRecord> trades)
|
||||
{
|
||||
var result = new List<(DateTime, decimal)>();
|
||||
decimal cum = 0m;
|
||||
foreach (var t in trades.OrderBy(t => t.ClosedAt))
|
||||
{
|
||||
cum += t.RealizedPnl;
|
||||
result.Add((t.ClosedAt, cum));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>PnL + Anzahl je Gruppierungsschlüssel (z. B. Modul oder Account), absteigend nach PnL.</summary>
|
||||
public static List<(string Key, decimal Pnl, int Count)> PnlByKey(
|
||||
IEnumerable<TradeRecord> trades, Func<TradeRecord, string> keySelector)
|
||||
{
|
||||
return trades
|
||||
.GroupBy(keySelector)
|
||||
.Select(g => (Key: g.Key, Pnl: g.Sum(t => t.RealizedPnl), Count: g.Count()))
|
||||
.OrderByDescending(x => x.Pnl)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
/// <summary>PnL je Kalendertag (nach <see cref="TradeRecord.ClosedAt"/>), chronologisch.</summary>
|
||||
public static List<(DateTime Day, decimal Pnl)> PnlByDay(IEnumerable<TradeRecord> trades)
|
||||
{
|
||||
return trades
|
||||
.GroupBy(t => t.ClosedAt.Date)
|
||||
.Select(g => (Day: g.Key, Pnl: g.Sum(t => t.RealizedPnl)))
|
||||
.OrderBy(x => x.Day)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
/// <summary>Kurz-Zusammenfassung für die Launcher-Übersicht (z. B. „letzte 3 Tage" je Account).</summary>
|
||||
public static (decimal Pnl, decimal WinRatePct, int Count) WindowSummary(IEnumerable<TradeRecord> trades)
|
||||
{
|
||||
var list = trades as IReadOnlyList<TradeRecord> ?? trades.ToList();
|
||||
int n = list.Count;
|
||||
if (n == 0) return (0m, 0m, 0);
|
||||
int wins = list.Count(t => t.RealizedPnl > 0m);
|
||||
return (list.Sum(t => t.RealizedPnl), 100m * wins / n, n);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user