Phase 5-UI: Generischer Core-Trade-Log + modulübergreifendes Dashboard

- Core: TradeRecord (Modell) + ITradeLogRepository (+ Mongo-Impl, Collection
  "trade_log"), in AddCorePersistence registriert. Realisiert den generischen,
  modulneutralen Trade-Log aus Entscheidung #2.
- Dual-Write: PersistenceService schreibt geschlossene Copy-Trades zusaetzlich als
  generischen TradeRecord (ModuleName="CopyTrading").
- Neue DashboardView (UserControl + Designer): zeigt die letzten Trades ALLER
  Module (GetRecent) mit Konto-Aufloesung + Kurzauswertung (Gesamt-PnL, Anzahl,
  Aufschluesselung je Modul). btn_dashboard im Launcher, View "core.dashboard".
- Build 0 Fehler.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Richard
2026-07-02 12:16:20 +02:00
co-authored by Claude Opus 4.8
parent 8e2a92ad2e
commit 4f6c7fdb86
13 changed files with 500 additions and 41 deletions
+88
View File
@@ -0,0 +1,88 @@
using System;
using System.ComponentModel;
using System.Linq;
using System.Windows.Forms;
using PolyTrader.Core.Persistence;
namespace PolyTraderSharp.Ui.Views
{
/// <summary>
/// Modulübergreifendes Dashboard: zeigt die letzten Trades ALLER Module aus dem
/// generischen Core-Trade-Log (ITradeLogRepository) und eine Kurzauswertung.
/// Designbar (DashboardView.Designer.cs).
/// </summary>
public partial class DashboardView : UserControl
{
private ITradeLogRepository? _tradeLog;
private TradingState? _state;
public DashboardView()
{
InitializeComponent();
colEntry.DefaultCellStyle.Format = "F3";
colExit.DefaultCellStyle.Format = "F3";
colSize.DefaultCellStyle.Format = "F2";
colPnl.DefaultCellStyle.Format = "F2";
colPnlPct.DefaultCellStyle.Format = "F1";
colClosedAt.DefaultCellStyle.Format = "dd.MM.yyyy HH:mm";
btnRefresh.Click += (_, _) => LoadData();
}
public void Initialize(ITradeLogRepository tradeLog, TradingState state)
{
_tradeLog = tradeLog;
_state = state;
LoadData();
}
private void LoadData()
{
if (_tradeLog == null) return;
var rows = _tradeLog.GetRecent(300).Select(r => new DashboardTradeRow
{
Module = r.ModuleName,
Account = ResolveAccount(r.AccountId, r.IsDemo),
Market = r.MarketQuestion,
Outcome = r.Outcome,
Side = r.Side,
EntryPrice = r.EntryPrice,
ExitPrice = r.ExitPrice,
Size = r.Size,
RealizedPnl = r.RealizedPnl,
PnlPercent = r.PnlPercent,
ClosedAt = r.ClosedAt
}).ToList();
dgvTrades.DataSource = new BindingList<DashboardTradeRow>(rows);
decimal totalPnl = rows.Sum(x => x.RealizedPnl);
var perModule = rows.GroupBy(x => x.Module).Select(g => $"{g.Key}: {g.Count()}");
lblSummary.Text = $"{rows.Count} Trades | PnL gesamt: {totalPnl:F2} USDC | {string.Join(" · ", perModule)}";
}
private string ResolveAccount(int accountId, bool isDemo)
{
string suffix = isDemo ? " (Demo)" : "";
if (_state != null && _state.Accounts.TryGetValue(accountId, out var acc) && !string.IsNullOrEmpty(acc.Name))
return acc.Name + suffix;
return $"#{accountId}{suffix}";
}
}
/// <summary>Anzeige-Zeile für das Dashboard-Grid (Account bereits zu Name aufgelöst).</summary>
public class DashboardTradeRow
{
public string Module { get; set; } = string.Empty;
public string Account { get; set; } = string.Empty;
public string Market { get; set; } = string.Empty;
public string Outcome { get; set; } = string.Empty;
public string Side { get; set; } = string.Empty;
public decimal EntryPrice { get; set; }
public decimal ExitPrice { get; set; }
public decimal Size { get; set; }
public decimal RealizedPnl { get; set; }
public decimal PnlPercent { get; set; }
public DateTime ClosedAt { get; set; }
}
}