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:
Richard
2026-07-15 09:59:10 +02:00
co-authored by Claude Opus 4.8
parent 0aedddabe8
commit 7d63791e38
4 changed files with 365 additions and 1 deletions
+76 -1
View File
@@ -33,6 +33,13 @@ namespace PolyTraderSharp.Ui
lbl_modules = new ToolStripStatusLabel();
lbl_ratelimit = new ToolStripStatusLabel();
dgv_accountlist = new DataGridView();
colAccName = new DataGridViewTextBoxColumn();
colAccModules = new DataGridViewTextBoxColumn();
colAccPoly = new DataGridViewButtonColumn();
colAccBalance = new DataGridViewTextBoxColumn();
colAccPnl3d = new DataGridViewTextBoxColumn();
colAccWin3d = new DataGridViewTextBoxColumn();
colAccOverall = new DataGridViewTextBoxColumn();
btn_accounting = new ToolStripButton();
toolStripSeparator1 = new ToolStripSeparator();
menuStrip.SuspendLayout();
@@ -172,15 +179,76 @@ namespace PolyTraderSharp.Ui
//
dgv_accountlist.AllowUserToAddRows = false;
dgv_accountlist.AllowUserToDeleteRows = false;
dgv_accountlist.AutoGenerateColumns = false;
dgv_accountlist.ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.AutoSize;
dgv_accountlist.Columns.AddRange(new DataGridViewColumn[] {
colAccName, colAccModules, colAccPoly, colAccBalance, colAccPnl3d, colAccWin3d, colAccOverall });
dgv_accountlist.Dock = DockStyle.Fill;
dgv_accountlist.Location = new Point(0, 139);
dgv_accountlist.Name = "dgv_accountlist";
dgv_accountlist.ReadOnly = true;
dgv_accountlist.RowHeadersVisible = false;
dgv_accountlist.RowHeadersWidth = 62;
dgv_accountlist.SelectionMode = DataGridViewSelectionMode.FullRowSelect;
dgv_accountlist.Size = new Size(2599, 498);
dgv_accountlist.TabIndex = 6;
//
//
// colAccName
//
colAccName.DataPropertyName = "Name";
colAccName.HeaderText = "Account";
colAccName.Name = "colAccName";
colAccName.ReadOnly = true;
colAccName.Width = 200;
//
// colAccModules
//
colAccModules.DataPropertyName = "Modules";
colAccModules.HeaderText = "Module";
colAccModules.Name = "colAccModules";
colAccModules.ReadOnly = true;
colAccModules.Width = 220;
//
// colAccPoly
//
colAccPoly.HeaderText = "Polymarket";
colAccPoly.Name = "colAccPoly";
colAccPoly.Text = "Öffnen";
colAccPoly.UseColumnTextForButtonValue = true;
colAccPoly.Width = 100;
//
// colAccBalance
//
colAccBalance.DataPropertyName = "Balance";
colAccBalance.HeaderText = "Wallet (USDC)";
colAccBalance.Name = "colAccBalance";
colAccBalance.ReadOnly = true;
colAccBalance.Width = 130;
//
// colAccPnl3d
//
colAccPnl3d.DataPropertyName = "Pnl3d";
colAccPnl3d.HeaderText = "3T PnL";
colAccPnl3d.Name = "colAccPnl3d";
colAccPnl3d.ReadOnly = true;
colAccPnl3d.Width = 110;
//
// colAccWin3d
//
colAccWin3d.DataPropertyName = "WinRate3d";
colAccWin3d.HeaderText = "3T Winrate %";
colAccWin3d.Name = "colAccWin3d";
colAccWin3d.ReadOnly = true;
colAccWin3d.Width = 110;
//
// colAccOverall
//
colAccOverall.DataPropertyName = "OverallPnl";
colAccOverall.HeaderText = "Overall P/L";
colAccOverall.Name = "colAccOverall";
colAccOverall.ReadOnly = true;
colAccOverall.Width = 130;
//
// btn_accounting
//
btn_accounting.Image = Properties.Resources.coins_in_hand;
@@ -245,5 +313,12 @@ namespace PolyTraderSharp.Ui
private ToolStripButton btn_accounting;
private ToolStripSeparator toolStripSeparator1;
private DataGridView dgv_accountlist;
private DataGridViewTextBoxColumn colAccName;
private DataGridViewTextBoxColumn colAccModules;
private DataGridViewButtonColumn colAccPoly;
private DataGridViewTextBoxColumn colAccBalance;
private DataGridViewTextBoxColumn colAccPnl3d;
private DataGridViewTextBoxColumn colAccWin3d;
private DataGridViewTextBoxColumn colAccOverall;
}
}
+100
View File
@@ -1,9 +1,13 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Windows.Forms;
using Microsoft.Extensions.DependencyInjection;
using PolyTrader.Core.Analytics;
using PolyTrader.Core.Modularity;
using PolyTrader.Core.Persistence;
using PolyTraderSharp.Models;
namespace PolyTraderSharp.Ui
{
@@ -21,6 +25,7 @@ namespace PolyTraderSharp.Ui
private readonly TradingState _state;
private readonly System.Windows.Forms.Timer _statusTimer = new() { Interval = 1000 };
private readonly Dictionary<string, ToolStripButton> _viewButtons;
private int _statusTicks;
public LauncherForm(ShellUiHost uiHost, IServiceProvider services)
{
@@ -74,6 +79,13 @@ namespace PolyTraderSharp.Ui
// Offen-Status der Fenster spiegeln (Button „checked", wenn Fenster offen).
_uiHost.OpenStateChanged += UpdateWindowButtonStates;
// Account-Übersicht (dgv_accountlist): Zahlenformate + Polymarket-Button.
colAccBalance.DefaultCellStyle.Format = "N2";
colAccPnl3d.DefaultCellStyle.Format = "N2";
colAccWin3d.DefaultCellStyle.Format = "N1";
colAccOverall.DefaultCellStyle.Format = "N2";
dgv_accountlist.CellContentClick += AccountList_CellContentClick;
_statusTimer.Tick += (_, _) => UpdateStatus();
_statusTimer.Start();
UpdateStatus();
@@ -149,6 +161,94 @@ namespace PolyTraderSharp.Ui
UpdateTradingToggles();
UpdateWindowButtonStates();
// Account-Übersicht alle 30 s aktualisieren (DB-Abfrage je Account nicht jede Sekunde).
if (_statusTicks++ % 30 == 0) LoadAccountOverview();
}
// ===== Account-Übersicht (dgv_accountlist) =====
private void LoadAccountOverview()
{
if (IsDisposed) return;
var tradeLog = _services.GetService<ITradeLogRepository>();
if (tradeLog == null) return;
DateTime since3d = DateTime.UtcNow.AddDays(-3);
var rows = new List<AccountOverviewRow>();
foreach (var acc in _state.Accounts.Values.OrderBy(a => a.AccountId))
{
List<TradeRecord> trades;
try { trades = tradeLog.Find(t => t.AccountId == acc.AccountId); }
catch { trades = new List<TradeRecord>(); } // DB nicht bereit -> leer statt Absturz
var (pnl3d, win3d, _) = TradeAnalytics.WindowSummary(trades.Where(t => t.ClosedAt >= since3d));
string modules = trades
.Select(t => t.ModuleName)
.Where(m => !string.IsNullOrEmpty(m))
.Distinct().OrderBy(m => m)
.DefaultIfEmpty("—")
.Aggregate((a, b) => a + ", " + b);
rows.Add(new AccountOverviewRow
{
AccountId = acc.AccountId,
Name = (string.IsNullOrEmpty(acc.Name) ? $"#{acc.AccountId}" : acc.Name) + (acc.IsDemo ? " (Demo)" : ""),
Modules = modules,
WalletAddress = acc.WalletAddress,
Balance = acc.TotalBalance,
Pnl3d = pnl3d,
WinRate3d = win3d,
OverallPnl = trades.Sum(t => t.RealizedPnl)
});
}
dgv_accountlist.DataSource = rows;
}
private void AccountList_CellContentClick(object? sender, DataGridViewCellEventArgs e)
{
if (e.RowIndex < 0 || e.ColumnIndex < 0) return;
if (dgv_accountlist.Columns[e.ColumnIndex].Name != "colAccPoly") return;
if (dgv_accountlist.Rows[e.RowIndex].DataBoundItem is AccountOverviewRow row)
OpenPolymarketProfile(row.WalletAddress);
}
private void OpenPolymarketProfile(string walletAddress)
{
if (string.IsNullOrWhiteSpace(walletAddress))
{
MessageBox.Show("Für diesen Account ist keine Wallet-Adresse hinterlegt.", "Polymarket",
MessageBoxButtons.OK, MessageBoxIcon.Information);
return;
}
try
{
Process.Start(new ProcessStartInfo
{
FileName = $"https://polymarket.com/profile/{walletAddress}",
UseShellExecute = true
});
}
catch (Exception ex)
{
MessageBox.Show($"Konnte Polymarket nicht öffnen: {ex.Message}", "Fehler",
MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
/// <summary>Anzeige-Zeile der Account-Übersicht (Bindung an dgv_accountlist über DataPropertyName).</summary>
private sealed class AccountOverviewRow
{
public int AccountId { get; set; }
public string Name { get; set; } = string.Empty;
public string Modules { get; set; } = string.Empty;
public string WalletAddress { get; set; } = string.Empty;
public decimal Balance { get; set; }
public decimal Pnl3d { get; set; }
public decimal WinRate3d { get; set; }
public decimal OverallPnl { get; set; }
}
}
}