UI: Dashboard-Ueberarbeitung (Scope-Filter, KPIs, ScottPlot-Charts, Tradehistorie)
Das Dashboard ist keine stumpfe Trade-Liste mehr, sondern ein Auswertungs-Fenster:
- ToolStrip (via Designer): Scope-Filter Konto/Modul/Live-Demo/Zeitraum + Aktualisieren.
- TabControl mit 2 Tabs:
- Dashboard: KPI-Kacheln (Netto-PnL/Winrate/Trades/O-PnL/Profit-Faktor) + 3 Charts
(Equity-Kurve, PnL je Modul, PnL je Tag) fuer den gewaehlten Scope.
- Tradehistorie: gefilterte Trade-Liste (Spalten via Designer) + Suche + Gewinner/Verlierer.
- Charts via ScottPlot CORE-Paket (nur SkiaSharp, .NET-nativ) -> als Bitmap in PictureBoxen
gerendert; KEINE OpenTK/.NET-Framework-Transitiven (bewusst nicht ScottPlot.WinForms).
- Auswertungslogik pur in TradeAnalytics (getestet). In-Memory-Filter auf gecachtem Recent-Set.
- Smoke-UI konstruiert die DashboardView jetzt direkt -> verifiziert das Chart-Rendering headless.
Build 0 Fehler, 331 Tests gruen, --smoke-ui: [OK] core.dashboard konstruiert (inkl. Charts).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
7d63791e38
commit
b6dff7e210
@@ -70,10 +70,12 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Nethereum.Web3" Version="6.1.0" />
|
<PackageReference Include="Nethereum.Web3" Version="6.1.0" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
|
||||||
|
<!-- Dashboard-Charts (MIT, keine Umsatzschwelle, kein Phone-Home – siehe docs/sicherheit). -->
|
||||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.0" />
|
||||||
<!-- Angehoben, um Downgrade-Konflikt (EF Core zieht 8.0.2) aufzulösen. -->
|
<!-- Angehoben, um Downgrade-Konflikt (EF Core zieht 8.0.2) aufzulösen. -->
|
||||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.2" />
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.2" />
|
||||||
|
<PackageReference Include="ScottPlot" Version="5.1.59" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
+13
@@ -454,6 +454,19 @@ internal static class Program
|
|||||||
Console.WriteLine($"[FEHLER] LauncherForm: {ex.GetType().Name}: {ex.Message}");
|
Console.WriteLine($"[FEHLER] LauncherForm: {ex.GetType().Name}: {ex.Message}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Core-Dashboard direkt konstruieren (im Smoke nicht via uiHost registriert) - prueft das ScottPlot-Rendering headless.
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var dash = new PolyTraderSharp.Ui.Views.DashboardView();
|
||||||
|
dash.Initialize(host.Services.GetRequiredService<ITradeLogRepository>(), host.Services.GetRequiredService<TradingState>());
|
||||||
|
Console.WriteLine("[OK] core.dashboard konstruiert (inkl. Charts)");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
failures++;
|
||||||
|
Console.WriteLine($"[FEHLER] core.dashboard: {ex.GetType().Name}: {ex.Message}");
|
||||||
|
}
|
||||||
|
|
||||||
Console.WriteLine(failures == 0 ? "=== Smoke-UI OK ===" : $"=== Smoke-UI: {failures} Fehler ===");
|
Console.WriteLine(failures == 0 ? "=== Smoke-UI OK ===" : $"=== Smoke-UI: {failures} Fehler ===");
|
||||||
return failures;
|
return failures;
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
+344
-46
@@ -17,9 +17,29 @@ namespace PolyTraderSharp.Ui.Views
|
|||||||
|
|
||||||
private void InitializeComponent()
|
private void InitializeComponent()
|
||||||
{
|
{
|
||||||
this.pnlTop = new System.Windows.Forms.Panel();
|
this.toolStripDash = new System.Windows.Forms.ToolStrip();
|
||||||
this.lblSummary = new System.Windows.Forms.Label();
|
this.tslKonto = new System.Windows.Forms.ToolStripLabel();
|
||||||
this.btnRefresh = new System.Windows.Forms.Button();
|
this.cbAccount = new System.Windows.Forms.ToolStripComboBox();
|
||||||
|
this.tslModul = new System.Windows.Forms.ToolStripLabel();
|
||||||
|
this.cbModule = new System.Windows.Forms.ToolStripComboBox();
|
||||||
|
this.tslArt = new System.Windows.Forms.ToolStripLabel();
|
||||||
|
this.cbMode = new System.Windows.Forms.ToolStripComboBox();
|
||||||
|
this.tslZeit = new System.Windows.Forms.ToolStripLabel();
|
||||||
|
this.cbRange = new System.Windows.Forms.ToolStripComboBox();
|
||||||
|
this.tsRefresh = new System.Windows.Forms.ToolStripButton();
|
||||||
|
this.tabControlDash = new System.Windows.Forms.TabControl();
|
||||||
|
this.tabDashboard = new System.Windows.Forms.TabPage();
|
||||||
|
this.picEquity = new System.Windows.Forms.PictureBox();
|
||||||
|
this.pnlChartsBottom = new System.Windows.Forms.Panel();
|
||||||
|
this.picDay = new System.Windows.Forms.PictureBox();
|
||||||
|
this.picModule = new System.Windows.Forms.PictureBox();
|
||||||
|
this.flpKpis = new System.Windows.Forms.FlowLayoutPanel();
|
||||||
|
this.lblKpiPnl = new System.Windows.Forms.Label();
|
||||||
|
this.lblKpiWin = new System.Windows.Forms.Label();
|
||||||
|
this.lblKpiTrades = new System.Windows.Forms.Label();
|
||||||
|
this.lblKpiAvg = new System.Windows.Forms.Label();
|
||||||
|
this.lblKpiPf = new System.Windows.Forms.Label();
|
||||||
|
this.tabHistory = new System.Windows.Forms.TabPage();
|
||||||
this.dgvTrades = new System.Windows.Forms.DataGridView();
|
this.dgvTrades = new System.Windows.Forms.DataGridView();
|
||||||
this.colModule = new System.Windows.Forms.DataGridViewTextBoxColumn();
|
this.colModule = new System.Windows.Forms.DataGridViewTextBoxColumn();
|
||||||
this.colAccount = new System.Windows.Forms.DataGridViewTextBoxColumn();
|
this.colAccount = new System.Windows.Forms.DataGridViewTextBoxColumn();
|
||||||
@@ -32,37 +52,242 @@ namespace PolyTraderSharp.Ui.Views
|
|||||||
this.colPnl = new System.Windows.Forms.DataGridViewTextBoxColumn();
|
this.colPnl = new System.Windows.Forms.DataGridViewTextBoxColumn();
|
||||||
this.colPnlPct = new System.Windows.Forms.DataGridViewTextBoxColumn();
|
this.colPnlPct = new System.Windows.Forms.DataGridViewTextBoxColumn();
|
||||||
this.colClosedAt = new System.Windows.Forms.DataGridViewTextBoxColumn();
|
this.colClosedAt = new System.Windows.Forms.DataGridViewTextBoxColumn();
|
||||||
this.pnlTop.SuspendLayout();
|
this.pnlHistFilters = new System.Windows.Forms.Panel();
|
||||||
|
this.lblSuche = new System.Windows.Forms.Label();
|
||||||
|
this.tbSearch = new System.Windows.Forms.TextBox();
|
||||||
|
this.lblErgebnis = new System.Windows.Forms.Label();
|
||||||
|
this.cbWinLoss = new System.Windows.Forms.ComboBox();
|
||||||
|
this.toolStripDash.SuspendLayout();
|
||||||
|
this.tabControlDash.SuspendLayout();
|
||||||
|
this.tabDashboard.SuspendLayout();
|
||||||
|
((System.ComponentModel.ISupportInitialize)(this.picEquity)).BeginInit();
|
||||||
|
this.pnlChartsBottom.SuspendLayout();
|
||||||
|
((System.ComponentModel.ISupportInitialize)(this.picDay)).BeginInit();
|
||||||
|
((System.ComponentModel.ISupportInitialize)(this.picModule)).BeginInit();
|
||||||
|
this.flpKpis.SuspendLayout();
|
||||||
|
this.tabHistory.SuspendLayout();
|
||||||
((System.ComponentModel.ISupportInitialize)(this.dgvTrades)).BeginInit();
|
((System.ComponentModel.ISupportInitialize)(this.dgvTrades)).BeginInit();
|
||||||
|
this.pnlHistFilters.SuspendLayout();
|
||||||
this.SuspendLayout();
|
this.SuspendLayout();
|
||||||
//
|
//
|
||||||
// pnlTop
|
// toolStripDash
|
||||||
//
|
//
|
||||||
this.pnlTop.Controls.Add(this.lblSummary);
|
this.toolStripDash.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
|
||||||
this.pnlTop.Controls.Add(this.btnRefresh);
|
this.tslKonto, this.cbAccount, this.tslModul, this.cbModule, this.tslArt, this.cbMode,
|
||||||
this.pnlTop.Dock = System.Windows.Forms.DockStyle.Top;
|
this.tslZeit, this.cbRange, this.tsRefresh});
|
||||||
this.pnlTop.Location = new System.Drawing.Point(0, 0);
|
this.toolStripDash.Location = new System.Drawing.Point(0, 0);
|
||||||
this.pnlTop.Name = "pnlTop";
|
this.toolStripDash.Name = "toolStripDash";
|
||||||
this.pnlTop.Size = new System.Drawing.Size(1100, 40);
|
this.toolStripDash.Size = new System.Drawing.Size(1200, 27);
|
||||||
this.pnlTop.TabIndex = 0;
|
this.toolStripDash.TabIndex = 0;
|
||||||
//
|
//
|
||||||
// lblSummary
|
// tslKonto
|
||||||
//
|
//
|
||||||
this.lblSummary.AutoSize = true;
|
this.tslKonto.Name = "tslKonto";
|
||||||
this.lblSummary.Location = new System.Drawing.Point(148, 12);
|
this.tslKonto.Text = "Konto:";
|
||||||
this.lblSummary.Name = "lblSummary";
|
|
||||||
this.lblSummary.Size = new System.Drawing.Size(63, 15);
|
|
||||||
this.lblSummary.TabIndex = 1;
|
|
||||||
this.lblSummary.Text = "—";
|
|
||||||
//
|
//
|
||||||
// btnRefresh
|
// cbAccount
|
||||||
//
|
//
|
||||||
this.btnRefresh.Location = new System.Drawing.Point(8, 6);
|
this.cbAccount.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
|
||||||
this.btnRefresh.Name = "btnRefresh";
|
this.cbAccount.Name = "cbAccount";
|
||||||
this.btnRefresh.Size = new System.Drawing.Size(130, 28);
|
this.cbAccount.Size = new System.Drawing.Size(170, 27);
|
||||||
this.btnRefresh.TabIndex = 0;
|
//
|
||||||
this.btnRefresh.Text = "Aktualisieren";
|
// tslModul
|
||||||
this.btnRefresh.UseVisualStyleBackColor = true;
|
//
|
||||||
|
this.tslModul.Name = "tslModul";
|
||||||
|
this.tslModul.Text = "Modul:";
|
||||||
|
//
|
||||||
|
// cbModule
|
||||||
|
//
|
||||||
|
this.cbModule.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
|
||||||
|
this.cbModule.Name = "cbModule";
|
||||||
|
this.cbModule.Size = new System.Drawing.Size(150, 27);
|
||||||
|
//
|
||||||
|
// tslArt
|
||||||
|
//
|
||||||
|
this.tslArt.Name = "tslArt";
|
||||||
|
this.tslArt.Text = "Art:";
|
||||||
|
//
|
||||||
|
// cbMode
|
||||||
|
//
|
||||||
|
this.cbMode.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
|
||||||
|
this.cbMode.Name = "cbMode";
|
||||||
|
this.cbMode.Size = new System.Drawing.Size(100, 27);
|
||||||
|
//
|
||||||
|
// tslZeit
|
||||||
|
//
|
||||||
|
this.tslZeit.Name = "tslZeit";
|
||||||
|
this.tslZeit.Text = "Zeitraum:";
|
||||||
|
//
|
||||||
|
// cbRange
|
||||||
|
//
|
||||||
|
this.cbRange.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
|
||||||
|
this.cbRange.Name = "cbRange";
|
||||||
|
this.cbRange.Size = new System.Drawing.Size(110, 27);
|
||||||
|
//
|
||||||
|
// tsRefresh
|
||||||
|
//
|
||||||
|
this.tsRefresh.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Text;
|
||||||
|
this.tsRefresh.Name = "tsRefresh";
|
||||||
|
this.tsRefresh.Text = "Aktualisieren";
|
||||||
|
//
|
||||||
|
// tabControlDash
|
||||||
|
//
|
||||||
|
this.tabControlDash.Controls.Add(this.tabDashboard);
|
||||||
|
this.tabControlDash.Controls.Add(this.tabHistory);
|
||||||
|
this.tabControlDash.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||||
|
this.tabControlDash.Location = new System.Drawing.Point(0, 27);
|
||||||
|
this.tabControlDash.Name = "tabControlDash";
|
||||||
|
this.tabControlDash.SelectedIndex = 0;
|
||||||
|
this.tabControlDash.Size = new System.Drawing.Size(1200, 623);
|
||||||
|
this.tabControlDash.TabIndex = 1;
|
||||||
|
//
|
||||||
|
// tabDashboard
|
||||||
|
//
|
||||||
|
this.tabDashboard.Controls.Add(this.picEquity);
|
||||||
|
this.tabDashboard.Controls.Add(this.pnlChartsBottom);
|
||||||
|
this.tabDashboard.Controls.Add(this.flpKpis);
|
||||||
|
this.tabDashboard.Location = new System.Drawing.Point(4, 24);
|
||||||
|
this.tabDashboard.Name = "tabDashboard";
|
||||||
|
this.tabDashboard.Padding = new System.Windows.Forms.Padding(3);
|
||||||
|
this.tabDashboard.Size = new System.Drawing.Size(1192, 595);
|
||||||
|
this.tabDashboard.TabIndex = 0;
|
||||||
|
this.tabDashboard.Text = "Dashboard";
|
||||||
|
this.tabDashboard.UseVisualStyleBackColor = true;
|
||||||
|
//
|
||||||
|
// picEquity
|
||||||
|
//
|
||||||
|
this.picEquity.BackColor = System.Drawing.Color.White;
|
||||||
|
this.picEquity.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||||
|
this.picEquity.Location = new System.Drawing.Point(3, 79);
|
||||||
|
this.picEquity.Name = "picEquity";
|
||||||
|
this.picEquity.Size = new System.Drawing.Size(1186, 273);
|
||||||
|
this.picEquity.SizeMode = System.Windows.Forms.PictureBoxSizeMode.Zoom;
|
||||||
|
this.picEquity.TabIndex = 2;
|
||||||
|
this.picEquity.TabStop = false;
|
||||||
|
//
|
||||||
|
// pnlChartsBottom
|
||||||
|
//
|
||||||
|
this.pnlChartsBottom.Controls.Add(this.picDay);
|
||||||
|
this.pnlChartsBottom.Controls.Add(this.picModule);
|
||||||
|
this.pnlChartsBottom.Dock = System.Windows.Forms.DockStyle.Bottom;
|
||||||
|
this.pnlChartsBottom.Location = new System.Drawing.Point(3, 352);
|
||||||
|
this.pnlChartsBottom.Name = "pnlChartsBottom";
|
||||||
|
this.pnlChartsBottom.Size = new System.Drawing.Size(1186, 240);
|
||||||
|
this.pnlChartsBottom.TabIndex = 1;
|
||||||
|
//
|
||||||
|
// picDay
|
||||||
|
//
|
||||||
|
this.picDay.BackColor = System.Drawing.Color.White;
|
||||||
|
this.picDay.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||||
|
this.picDay.Location = new System.Drawing.Point(593, 0);
|
||||||
|
this.picDay.Name = "picDay";
|
||||||
|
this.picDay.Size = new System.Drawing.Size(593, 240);
|
||||||
|
this.picDay.SizeMode = System.Windows.Forms.PictureBoxSizeMode.Zoom;
|
||||||
|
this.picDay.TabIndex = 1;
|
||||||
|
this.picDay.TabStop = false;
|
||||||
|
//
|
||||||
|
// picModule
|
||||||
|
//
|
||||||
|
this.picModule.BackColor = System.Drawing.Color.White;
|
||||||
|
this.picModule.Dock = System.Windows.Forms.DockStyle.Left;
|
||||||
|
this.picModule.Location = new System.Drawing.Point(0, 0);
|
||||||
|
this.picModule.Name = "picModule";
|
||||||
|
this.picModule.Size = new System.Drawing.Size(593, 240);
|
||||||
|
this.picModule.SizeMode = System.Windows.Forms.PictureBoxSizeMode.Zoom;
|
||||||
|
this.picModule.TabIndex = 0;
|
||||||
|
this.picModule.TabStop = false;
|
||||||
|
//
|
||||||
|
// flpKpis
|
||||||
|
//
|
||||||
|
this.flpKpis.Controls.Add(this.lblKpiPnl);
|
||||||
|
this.flpKpis.Controls.Add(this.lblKpiWin);
|
||||||
|
this.flpKpis.Controls.Add(this.lblKpiTrades);
|
||||||
|
this.flpKpis.Controls.Add(this.lblKpiAvg);
|
||||||
|
this.flpKpis.Controls.Add(this.lblKpiPf);
|
||||||
|
this.flpKpis.Dock = System.Windows.Forms.DockStyle.Top;
|
||||||
|
this.flpKpis.Location = new System.Drawing.Point(3, 3);
|
||||||
|
this.flpKpis.Name = "flpKpis";
|
||||||
|
this.flpKpis.Padding = new System.Windows.Forms.Padding(4);
|
||||||
|
this.flpKpis.Size = new System.Drawing.Size(1186, 76);
|
||||||
|
this.flpKpis.TabIndex = 0;
|
||||||
|
this.flpKpis.WrapContents = false;
|
||||||
|
//
|
||||||
|
// lblKpiPnl
|
||||||
|
//
|
||||||
|
this.lblKpiPnl.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
|
||||||
|
this.lblKpiPnl.Font = new System.Drawing.Font("Segoe UI", 10F, System.Drawing.FontStyle.Bold);
|
||||||
|
this.lblKpiPnl.Margin = new System.Windows.Forms.Padding(4);
|
||||||
|
this.lblKpiPnl.MinimumSize = new System.Drawing.Size(190, 56);
|
||||||
|
this.lblKpiPnl.Name = "lblKpiPnl";
|
||||||
|
this.lblKpiPnl.Padding = new System.Windows.Forms.Padding(8);
|
||||||
|
this.lblKpiPnl.Size = new System.Drawing.Size(190, 56);
|
||||||
|
this.lblKpiPnl.TabIndex = 0;
|
||||||
|
this.lblKpiPnl.Text = "Netto-PnL\n—";
|
||||||
|
this.lblKpiPnl.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
|
||||||
|
//
|
||||||
|
// lblKpiWin
|
||||||
|
//
|
||||||
|
this.lblKpiWin.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
|
||||||
|
this.lblKpiWin.Font = new System.Drawing.Font("Segoe UI", 10F, System.Drawing.FontStyle.Bold);
|
||||||
|
this.lblKpiWin.Margin = new System.Windows.Forms.Padding(4);
|
||||||
|
this.lblKpiWin.MinimumSize = new System.Drawing.Size(150, 56);
|
||||||
|
this.lblKpiWin.Name = "lblKpiWin";
|
||||||
|
this.lblKpiWin.Padding = new System.Windows.Forms.Padding(8);
|
||||||
|
this.lblKpiWin.Size = new System.Drawing.Size(150, 56);
|
||||||
|
this.lblKpiWin.TabIndex = 1;
|
||||||
|
this.lblKpiWin.Text = "Winrate\n—";
|
||||||
|
this.lblKpiWin.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
|
||||||
|
//
|
||||||
|
// lblKpiTrades
|
||||||
|
//
|
||||||
|
this.lblKpiTrades.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
|
||||||
|
this.lblKpiTrades.Font = new System.Drawing.Font("Segoe UI", 10F, System.Drawing.FontStyle.Bold);
|
||||||
|
this.lblKpiTrades.Margin = new System.Windows.Forms.Padding(4);
|
||||||
|
this.lblKpiTrades.MinimumSize = new System.Drawing.Size(130, 56);
|
||||||
|
this.lblKpiTrades.Name = "lblKpiTrades";
|
||||||
|
this.lblKpiTrades.Padding = new System.Windows.Forms.Padding(8);
|
||||||
|
this.lblKpiTrades.Size = new System.Drawing.Size(130, 56);
|
||||||
|
this.lblKpiTrades.TabIndex = 2;
|
||||||
|
this.lblKpiTrades.Text = "Trades\n—";
|
||||||
|
this.lblKpiTrades.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
|
||||||
|
//
|
||||||
|
// lblKpiAvg
|
||||||
|
//
|
||||||
|
this.lblKpiAvg.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
|
||||||
|
this.lblKpiAvg.Font = new System.Drawing.Font("Segoe UI", 10F, System.Drawing.FontStyle.Bold);
|
||||||
|
this.lblKpiAvg.Margin = new System.Windows.Forms.Padding(4);
|
||||||
|
this.lblKpiAvg.MinimumSize = new System.Drawing.Size(170, 56);
|
||||||
|
this.lblKpiAvg.Name = "lblKpiAvg";
|
||||||
|
this.lblKpiAvg.Padding = new System.Windows.Forms.Padding(8);
|
||||||
|
this.lblKpiAvg.Size = new System.Drawing.Size(170, 56);
|
||||||
|
this.lblKpiAvg.TabIndex = 3;
|
||||||
|
this.lblKpiAvg.Text = "Ø PnL/Trade\n—";
|
||||||
|
this.lblKpiAvg.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
|
||||||
|
//
|
||||||
|
// lblKpiPf
|
||||||
|
//
|
||||||
|
this.lblKpiPf.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
|
||||||
|
this.lblKpiPf.Font = new System.Drawing.Font("Segoe UI", 10F, System.Drawing.FontStyle.Bold);
|
||||||
|
this.lblKpiPf.Margin = new System.Windows.Forms.Padding(4);
|
||||||
|
this.lblKpiPf.MinimumSize = new System.Drawing.Size(150, 56);
|
||||||
|
this.lblKpiPf.Name = "lblKpiPf";
|
||||||
|
this.lblKpiPf.Padding = new System.Windows.Forms.Padding(8);
|
||||||
|
this.lblKpiPf.Size = new System.Drawing.Size(150, 56);
|
||||||
|
this.lblKpiPf.TabIndex = 4;
|
||||||
|
this.lblKpiPf.Text = "Profit-Faktor\n—";
|
||||||
|
this.lblKpiPf.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
|
||||||
|
//
|
||||||
|
// tabHistory
|
||||||
|
//
|
||||||
|
this.tabHistory.Controls.Add(this.dgvTrades);
|
||||||
|
this.tabHistory.Controls.Add(this.pnlHistFilters);
|
||||||
|
this.tabHistory.Location = new System.Drawing.Point(4, 24);
|
||||||
|
this.tabHistory.Name = "tabHistory";
|
||||||
|
this.tabHistory.Padding = new System.Windows.Forms.Padding(3);
|
||||||
|
this.tabHistory.Size = new System.Drawing.Size(1192, 595);
|
||||||
|
this.tabHistory.TabIndex = 1;
|
||||||
|
this.tabHistory.Text = "Tradehistorie";
|
||||||
|
this.tabHistory.UseVisualStyleBackColor = true;
|
||||||
//
|
//
|
||||||
// dgvTrades
|
// dgvTrades
|
||||||
//
|
//
|
||||||
@@ -71,23 +296,14 @@ namespace PolyTraderSharp.Ui.Views
|
|||||||
this.dgvTrades.AutoGenerateColumns = false;
|
this.dgvTrades.AutoGenerateColumns = false;
|
||||||
this.dgvTrades.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
|
this.dgvTrades.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
|
||||||
this.dgvTrades.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] {
|
this.dgvTrades.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] {
|
||||||
this.colModule,
|
this.colModule, this.colAccount, this.colMarket, this.colOutcome, this.colSide,
|
||||||
this.colAccount,
|
this.colEntry, this.colExit, this.colSize, this.colPnl, this.colPnlPct, this.colClosedAt});
|
||||||
this.colMarket,
|
|
||||||
this.colOutcome,
|
|
||||||
this.colSide,
|
|
||||||
this.colEntry,
|
|
||||||
this.colExit,
|
|
||||||
this.colSize,
|
|
||||||
this.colPnl,
|
|
||||||
this.colPnlPct,
|
|
||||||
this.colClosedAt});
|
|
||||||
this.dgvTrades.Dock = System.Windows.Forms.DockStyle.Fill;
|
this.dgvTrades.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||||
this.dgvTrades.Location = new System.Drawing.Point(0, 40);
|
this.dgvTrades.Location = new System.Drawing.Point(3, 39);
|
||||||
this.dgvTrades.Name = "dgvTrades";
|
this.dgvTrades.Name = "dgvTrades";
|
||||||
this.dgvTrades.ReadOnly = true;
|
this.dgvTrades.ReadOnly = true;
|
||||||
this.dgvTrades.RowHeadersVisible = false;
|
this.dgvTrades.RowHeadersVisible = false;
|
||||||
this.dgvTrades.Size = new System.Drawing.Size(1100, 560);
|
this.dgvTrades.Size = new System.Drawing.Size(1186, 553);
|
||||||
this.dgvTrades.TabIndex = 1;
|
this.dgvTrades.TabIndex = 1;
|
||||||
//
|
//
|
||||||
// colModule
|
// colModule
|
||||||
@@ -167,26 +383,108 @@ namespace PolyTraderSharp.Ui.Views
|
|||||||
this.colClosedAt.Name = "colClosedAt";
|
this.colClosedAt.Name = "colClosedAt";
|
||||||
this.colClosedAt.Width = 140;
|
this.colClosedAt.Width = 140;
|
||||||
//
|
//
|
||||||
|
// pnlHistFilters
|
||||||
|
//
|
||||||
|
this.pnlHistFilters.Controls.Add(this.cbWinLoss);
|
||||||
|
this.pnlHistFilters.Controls.Add(this.lblErgebnis);
|
||||||
|
this.pnlHistFilters.Controls.Add(this.tbSearch);
|
||||||
|
this.pnlHistFilters.Controls.Add(this.lblSuche);
|
||||||
|
this.pnlHistFilters.Dock = System.Windows.Forms.DockStyle.Top;
|
||||||
|
this.pnlHistFilters.Location = new System.Drawing.Point(3, 3);
|
||||||
|
this.pnlHistFilters.Name = "pnlHistFilters";
|
||||||
|
this.pnlHistFilters.Size = new System.Drawing.Size(1186, 36);
|
||||||
|
this.pnlHistFilters.TabIndex = 0;
|
||||||
|
//
|
||||||
|
// lblSuche
|
||||||
|
//
|
||||||
|
this.lblSuche.AutoSize = true;
|
||||||
|
this.lblSuche.Location = new System.Drawing.Point(6, 9);
|
||||||
|
this.lblSuche.Name = "lblSuche";
|
||||||
|
this.lblSuche.Size = new System.Drawing.Size(45, 15);
|
||||||
|
this.lblSuche.TabIndex = 0;
|
||||||
|
this.lblSuche.Text = "Suche:";
|
||||||
|
//
|
||||||
|
// tbSearch
|
||||||
|
//
|
||||||
|
this.tbSearch.Location = new System.Drawing.Point(57, 6);
|
||||||
|
this.tbSearch.Name = "tbSearch";
|
||||||
|
this.tbSearch.PlaceholderText = "Markt / Outcome …";
|
||||||
|
this.tbSearch.Size = new System.Drawing.Size(280, 23);
|
||||||
|
this.tbSearch.TabIndex = 1;
|
||||||
|
//
|
||||||
|
// lblErgebnis
|
||||||
|
//
|
||||||
|
this.lblErgebnis.AutoSize = true;
|
||||||
|
this.lblErgebnis.Location = new System.Drawing.Point(360, 9);
|
||||||
|
this.lblErgebnis.Name = "lblErgebnis";
|
||||||
|
this.lblErgebnis.Size = new System.Drawing.Size(58, 15);
|
||||||
|
this.lblErgebnis.TabIndex = 2;
|
||||||
|
this.lblErgebnis.Text = "Ergebnis:";
|
||||||
|
//
|
||||||
|
// cbWinLoss
|
||||||
|
//
|
||||||
|
this.cbWinLoss.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
|
||||||
|
this.cbWinLoss.Location = new System.Drawing.Point(424, 6);
|
||||||
|
this.cbWinLoss.Name = "cbWinLoss";
|
||||||
|
this.cbWinLoss.Size = new System.Drawing.Size(150, 23);
|
||||||
|
this.cbWinLoss.TabIndex = 3;
|
||||||
|
//
|
||||||
// DashboardView
|
// DashboardView
|
||||||
//
|
//
|
||||||
this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);
|
this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);
|
||||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||||
this.Controls.Add(this.dgvTrades);
|
this.Controls.Add(this.tabControlDash);
|
||||||
this.Controls.Add(this.pnlTop);
|
this.Controls.Add(this.toolStripDash);
|
||||||
this.ClientSize = new System.Drawing.Size(1200, 650);
|
this.ClientSize = new System.Drawing.Size(1200, 650);
|
||||||
this.Name = "DashboardView";
|
this.Name = "DashboardView";
|
||||||
this.Text = "Dashboard";
|
this.Text = "Dashboard";
|
||||||
this.pnlTop.ResumeLayout(false);
|
this.toolStripDash.ResumeLayout(false);
|
||||||
this.pnlTop.PerformLayout();
|
this.toolStripDash.PerformLayout();
|
||||||
|
this.tabControlDash.ResumeLayout(false);
|
||||||
|
this.tabDashboard.ResumeLayout(false);
|
||||||
|
((System.ComponentModel.ISupportInitialize)(this.picEquity)).EndInit();
|
||||||
|
this.pnlChartsBottom.ResumeLayout(false);
|
||||||
|
((System.ComponentModel.ISupportInitialize)(this.picDay)).EndInit();
|
||||||
|
((System.ComponentModel.ISupportInitialize)(this.picModule)).EndInit();
|
||||||
|
this.flpKpis.ResumeLayout(false);
|
||||||
|
this.tabHistory.ResumeLayout(false);
|
||||||
((System.ComponentModel.ISupportInitialize)(this.dgvTrades)).EndInit();
|
((System.ComponentModel.ISupportInitialize)(this.dgvTrades)).EndInit();
|
||||||
|
this.pnlHistFilters.ResumeLayout(false);
|
||||||
|
this.pnlHistFilters.PerformLayout();
|
||||||
this.ResumeLayout(false);
|
this.ResumeLayout(false);
|
||||||
|
this.PerformLayout();
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
private System.Windows.Forms.Panel pnlTop;
|
private System.Windows.Forms.ToolStrip toolStripDash;
|
||||||
private System.Windows.Forms.Button btnRefresh;
|
private System.Windows.Forms.ToolStripLabel tslKonto;
|
||||||
private System.Windows.Forms.Label lblSummary;
|
private System.Windows.Forms.ToolStripComboBox cbAccount;
|
||||||
|
private System.Windows.Forms.ToolStripLabel tslModul;
|
||||||
|
private System.Windows.Forms.ToolStripComboBox cbModule;
|
||||||
|
private System.Windows.Forms.ToolStripLabel tslArt;
|
||||||
|
private System.Windows.Forms.ToolStripComboBox cbMode;
|
||||||
|
private System.Windows.Forms.ToolStripLabel tslZeit;
|
||||||
|
private System.Windows.Forms.ToolStripComboBox cbRange;
|
||||||
|
private System.Windows.Forms.ToolStripButton tsRefresh;
|
||||||
|
private System.Windows.Forms.TabControl tabControlDash;
|
||||||
|
private System.Windows.Forms.TabPage tabDashboard;
|
||||||
|
private System.Windows.Forms.FlowLayoutPanel flpKpis;
|
||||||
|
private System.Windows.Forms.Label lblKpiPnl;
|
||||||
|
private System.Windows.Forms.Label lblKpiWin;
|
||||||
|
private System.Windows.Forms.Label lblKpiTrades;
|
||||||
|
private System.Windows.Forms.Label lblKpiAvg;
|
||||||
|
private System.Windows.Forms.Label lblKpiPf;
|
||||||
|
private System.Windows.Forms.Panel pnlChartsBottom;
|
||||||
|
private System.Windows.Forms.PictureBox picModule;
|
||||||
|
private System.Windows.Forms.PictureBox picDay;
|
||||||
|
private System.Windows.Forms.PictureBox picEquity;
|
||||||
|
private System.Windows.Forms.TabPage tabHistory;
|
||||||
|
private System.Windows.Forms.Panel pnlHistFilters;
|
||||||
|
private System.Windows.Forms.Label lblSuche;
|
||||||
|
private System.Windows.Forms.TextBox tbSearch;
|
||||||
|
private System.Windows.Forms.Label lblErgebnis;
|
||||||
|
private System.Windows.Forms.ComboBox cbWinLoss;
|
||||||
private System.Windows.Forms.DataGridView dgvTrades;
|
private System.Windows.Forms.DataGridView dgvTrades;
|
||||||
private System.Windows.Forms.DataGridViewTextBoxColumn colModule;
|
private System.Windows.Forms.DataGridViewTextBoxColumn colModule;
|
||||||
private System.Windows.Forms.DataGridViewTextBoxColumn colAccount;
|
private System.Windows.Forms.DataGridViewTextBoxColumn colAccount;
|
||||||
|
|||||||
+209
-26
@@ -1,66 +1,244 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
|
using PolyTrader.Core.Analytics;
|
||||||
using PolyTrader.Core.Persistence;
|
using PolyTrader.Core.Persistence;
|
||||||
|
using PolyTraderSharp.Models;
|
||||||
|
|
||||||
namespace PolyTraderSharp.Ui.Views
|
namespace PolyTraderSharp.Ui.Views
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Modulübergreifendes Dashboard: zeigt die letzten Trades ALLER Module aus dem
|
/// Modulübergreifendes Dashboard aus dem generischen Core-Trade-Log (ITradeLogRepository).
|
||||||
/// generischen Core-Trade-Log (ITradeLogRepository) und eine Kurzauswertung.
|
/// Tab „Dashboard": KPI-Kacheln + Charts (Equity-Kurve, PnL je Modul, PnL je Tag) für den im
|
||||||
/// Designbar (DashboardView.Designer.cs).
|
/// ToolStrip gewählten Scope (Konto/Modul/Live-Demo/Zeitraum). Tab „Tradehistorie": gefilterte
|
||||||
|
/// Trade-Liste. Auswertungslogik pur in <see cref="TradeAnalytics"/>; Charts via ScottPlot
|
||||||
|
/// (als Bitmap gerendert – kein WinForms-GL-Control, saubere Dependencies).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public partial class DashboardView : Form
|
public partial class DashboardView : Form
|
||||||
{
|
{
|
||||||
private ITradeLogRepository? _tradeLog;
|
private ITradeLogRepository? _tradeLog;
|
||||||
private TradingState? _state;
|
private TradingState? _state;
|
||||||
|
|
||||||
|
private List<TradeRecord> _allTrades = new(); // zuletzt geladener Roh-Satz
|
||||||
|
private List<DashboardTradeRow> _historyBase = new(); // Scope-gefiltert, Basis für die Historie-Filter
|
||||||
|
private bool _loading;
|
||||||
|
|
||||||
public DashboardView()
|
public DashboardView()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
colEntry.DefaultCellStyle.Format = "F3";
|
colEntry.DefaultCellStyle.Format = "F3";
|
||||||
colExit.DefaultCellStyle.Format = "F3";
|
colExit.DefaultCellStyle.Format = "F3";
|
||||||
colSize.DefaultCellStyle.Format = "F2";
|
colSize.DefaultCellStyle.Format = "F2";
|
||||||
colPnl.DefaultCellStyle.Format = "F2";
|
colPnl.DefaultCellStyle.Format = "F2";
|
||||||
colPnlPct.DefaultCellStyle.Format = "F1";
|
colPnlPct.DefaultCellStyle.Format = "F1";
|
||||||
colClosedAt.DefaultCellStyle.Format = "dd.MM.yyyy HH:mm";
|
colClosedAt.DefaultCellStyle.Format = "dd.MM.yyyy HH:mm";
|
||||||
btnRefresh.Click += (_, _) => LoadData();
|
|
||||||
|
cbMode.Items.AddRange(new object[] { "Alle", "Live", "Demo" });
|
||||||
|
cbRange.Items.AddRange(new object[] { "7 Tage", "30 Tage", "90 Tage", "Alle" });
|
||||||
|
cbWinLoss.Items.AddRange(new object[] { "Alle", "Gewinner", "Verlierer" });
|
||||||
|
|
||||||
|
tsRefresh.Click += (_, _) => RefreshData();
|
||||||
|
cbAccount.SelectedIndexChanged += (_, _) => { if (!_loading) ApplyScope(); };
|
||||||
|
cbModule.SelectedIndexChanged += (_, _) => { if (!_loading) ApplyScope(); };
|
||||||
|
cbMode.SelectedIndexChanged += (_, _) => { if (!_loading) ApplyScope(); };
|
||||||
|
cbRange.SelectedIndexChanged += (_, _) => { if (!_loading) ApplyScope(); };
|
||||||
|
tbSearch.TextChanged += (_, _) => { if (!_loading) ApplyHistoryFilter(); };
|
||||||
|
cbWinLoss.SelectedIndexChanged += (_, _) => { if (!_loading) ApplyHistoryFilter(); };
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Initialize(ITradeLogRepository tradeLog, TradingState state)
|
public void Initialize(ITradeLogRepository tradeLog, TradingState state)
|
||||||
{
|
{
|
||||||
_tradeLog = tradeLog;
|
_tradeLog = tradeLog;
|
||||||
_state = state;
|
_state = state;
|
||||||
LoadData();
|
RefreshData();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void LoadData()
|
// ===== Daten laden / Scope =====
|
||||||
|
|
||||||
|
private void RefreshData()
|
||||||
{
|
{
|
||||||
if (_tradeLog == null) return;
|
if (_tradeLog == null) return;
|
||||||
|
try { _allTrades = _tradeLog.GetRecent(5000); }
|
||||||
|
catch { _allTrades = new List<TradeRecord>(); } // DB nicht bereit -> leer statt Absturz
|
||||||
|
|
||||||
var rows = _tradeLog.GetRecent(300).Select(r => new DashboardTradeRow
|
_loading = true;
|
||||||
{
|
PopulateScopeCombos();
|
||||||
Module = r.ModuleName,
|
_loading = false;
|
||||||
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);
|
ApplyScope();
|
||||||
|
|
||||||
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 void PopulateScopeCombos()
|
||||||
|
{
|
||||||
|
// Konten
|
||||||
|
var selectedAcc = (cbAccount.SelectedItem as FilterAccount)?.Id ?? -1;
|
||||||
|
cbAccount.Items.Clear();
|
||||||
|
cbAccount.Items.Add(new FilterAccount(-1, "Alle Konten"));
|
||||||
|
if (_state != null)
|
||||||
|
foreach (var a in _state.Accounts.Values.OrderBy(a => a.AccountId))
|
||||||
|
cbAccount.Items.Add(new FilterAccount(a.AccountId,
|
||||||
|
(string.IsNullOrEmpty(a.Name) ? $"#{a.AccountId}" : a.Name) + (a.IsDemo ? " (Demo)" : "")));
|
||||||
|
cbAccount.SelectedIndex = Math.Max(0, IndexOfAccount(selectedAcc));
|
||||||
|
|
||||||
|
// Module (aus den vorhandenen Daten)
|
||||||
|
string selectedMod = cbModule.SelectedItem as string ?? "Alle Module";
|
||||||
|
cbModule.Items.Clear();
|
||||||
|
cbModule.Items.Add("Alle Module");
|
||||||
|
foreach (var m in _allTrades.Select(t => t.ModuleName).Where(m => !string.IsNullOrEmpty(m)).Distinct().OrderBy(m => m))
|
||||||
|
cbModule.Items.Add(m);
|
||||||
|
int modIdx = cbModule.Items.IndexOf(selectedMod);
|
||||||
|
cbModule.SelectedIndex = modIdx >= 0 ? modIdx : 0;
|
||||||
|
|
||||||
|
if (cbMode.SelectedIndex < 0) cbMode.SelectedIndex = 0; // Alle
|
||||||
|
if (cbRange.SelectedIndex < 0) cbRange.SelectedIndex = 1; // 30 Tage
|
||||||
|
if (cbWinLoss.SelectedIndex < 0) cbWinLoss.SelectedIndex = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int IndexOfAccount(int id)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < cbAccount.Items.Count; i++)
|
||||||
|
if (cbAccount.Items[i] is FilterAccount fa && fa.Id == id) return i;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ApplyScope()
|
||||||
|
{
|
||||||
|
int accId = (cbAccount.SelectedItem as FilterAccount)?.Id ?? -1;
|
||||||
|
string module = cbModule.SelectedItem as string ?? "Alle Module";
|
||||||
|
string mode = cbMode.SelectedItem as string ?? "Alle";
|
||||||
|
int? days = (cbRange.SelectedItem as string) switch
|
||||||
|
{
|
||||||
|
"7 Tage" => 7,
|
||||||
|
"30 Tage" => 30,
|
||||||
|
"90 Tage" => 90,
|
||||||
|
_ => (int?)null
|
||||||
|
};
|
||||||
|
DateTime? since = days.HasValue ? DateTime.UtcNow.AddDays(-days.Value) : null;
|
||||||
|
|
||||||
|
var scoped = _allTrades.Where(t =>
|
||||||
|
(accId < 0 || t.AccountId == accId) &&
|
||||||
|
(module == "Alle Module" || t.ModuleName == module) &&
|
||||||
|
(mode == "Alle" || (mode == "Live" && !t.IsDemo) || (mode == "Demo" && t.IsDemo)) &&
|
||||||
|
(!since.HasValue || t.ClosedAt >= since.Value)).ToList();
|
||||||
|
|
||||||
|
UpdateKpis(scoped);
|
||||||
|
RenderCharts(scoped);
|
||||||
|
|
||||||
|
_historyBase = scoped
|
||||||
|
.OrderByDescending(t => t.ClosedAt)
|
||||||
|
.Select(ToRow)
|
||||||
|
.ToList();
|
||||||
|
ApplyHistoryFilter();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== KPIs =====
|
||||||
|
|
||||||
|
private void UpdateKpis(List<TradeRecord> scoped)
|
||||||
|
{
|
||||||
|
var k = TradeAnalytics.ComputeKpis(scoped);
|
||||||
|
lblKpiPnl.Text = $"Netto-PnL\n{k.NetPnl:N2} USDC";
|
||||||
|
lblKpiPnl.ForeColor = k.NetPnl >= 0 ? System.Drawing.Color.ForestGreen : System.Drawing.Color.Firebrick;
|
||||||
|
lblKpiWin.Text = $"Winrate\n{k.WinRatePct:N1} %";
|
||||||
|
lblKpiTrades.Text = $"Trades\n{k.TradeCount}";
|
||||||
|
lblKpiAvg.Text = $"Ø PnL/Trade\n{k.AvgPnlPerTrade:N2}";
|
||||||
|
lblKpiPf.Text = $"Profit-Faktor\n{(k.ProfitFactor >= TradeAnalytics.NoLossProfitFactor ? "∞" : k.ProfitFactor.ToString("N2"))}";
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== Charts (ScottPlot -> Bitmap) =====
|
||||||
|
|
||||||
|
private void RenderCharts(List<TradeRecord> scoped)
|
||||||
|
{
|
||||||
|
RenderPlot(picEquity, plot =>
|
||||||
|
{
|
||||||
|
var curve = TradeAnalytics.EquityCurve(scoped);
|
||||||
|
plot.Title("Equity-Kurve (kumulierter PnL)");
|
||||||
|
if (curve.Count == 0) return;
|
||||||
|
double[] xs = curve.Select(p => p.At.ToOADate()).ToArray();
|
||||||
|
double[] ys = curve.Select(p => (double)p.Cumulative).ToArray();
|
||||||
|
plot.Add.Scatter(xs, ys);
|
||||||
|
plot.Axes.DateTimeTicksBottom();
|
||||||
|
});
|
||||||
|
|
||||||
|
RenderPlot(picModule, plot =>
|
||||||
|
{
|
||||||
|
var byMod = TradeAnalytics.PnlByKey(scoped, t => string.IsNullOrEmpty(t.ModuleName) ? "—" : t.ModuleName);
|
||||||
|
plot.Title("PnL je Modul");
|
||||||
|
if (byMod.Count == 0) return;
|
||||||
|
plot.Add.Bars(byMod.Select(x => (double)x.Pnl).ToArray());
|
||||||
|
SetCategoryTicks(plot, byMod.Select(x => x.Key).ToArray());
|
||||||
|
});
|
||||||
|
|
||||||
|
RenderPlot(picDay, plot =>
|
||||||
|
{
|
||||||
|
var byDay = TradeAnalytics.PnlByDay(scoped);
|
||||||
|
plot.Title("PnL je Tag");
|
||||||
|
if (byDay.Count == 0) return;
|
||||||
|
plot.Add.Bars(byDay.Select(x => (double)x.Pnl).ToArray());
|
||||||
|
SetCategoryTicks(plot, byDay.Select(x => x.Day.ToString("dd.MM")).ToArray());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void SetCategoryTicks(ScottPlot.Plot plot, string[] labels)
|
||||||
|
{
|
||||||
|
var ticks = new ScottPlot.TickGenerators.NumericManual();
|
||||||
|
for (int i = 0; i < labels.Length; i++)
|
||||||
|
ticks.AddMajor(i, labels[i]);
|
||||||
|
plot.Axes.Bottom.TickGenerator = ticks;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void RenderPlot(PictureBox pic, Action<ScottPlot.Plot> build)
|
||||||
|
{
|
||||||
|
int w = Math.Max(pic.ClientSize.Width, 300);
|
||||||
|
int h = Math.Max(pic.ClientSize.Height, 200);
|
||||||
|
var plot = new ScottPlot.Plot();
|
||||||
|
try { build(plot); }
|
||||||
|
catch { /* Chart-Rendering darf die UI nie killen */ }
|
||||||
|
|
||||||
|
byte[] png = plot.GetImage(w, h).GetImageBytes();
|
||||||
|
using var ms = new MemoryStream(png);
|
||||||
|
using var img = System.Drawing.Image.FromStream(ms);
|
||||||
|
var old = pic.Image;
|
||||||
|
pic.Image = new System.Drawing.Bitmap(img);
|
||||||
|
old?.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== Tradehistorie =====
|
||||||
|
|
||||||
|
private void ApplyHistoryFilter()
|
||||||
|
{
|
||||||
|
string search = tbSearch.Text.Trim();
|
||||||
|
string winLoss = cbWinLoss.SelectedItem as string ?? "Alle";
|
||||||
|
|
||||||
|
IEnumerable<DashboardTradeRow> rows = _historyBase;
|
||||||
|
if (search.Length > 0)
|
||||||
|
rows = rows.Where(r =>
|
||||||
|
(r.Market?.Contains(search, StringComparison.OrdinalIgnoreCase) ?? false) ||
|
||||||
|
(r.Outcome?.Contains(search, StringComparison.OrdinalIgnoreCase) ?? false));
|
||||||
|
if (winLoss == "Gewinner") rows = rows.Where(r => r.RealizedPnl > 0m);
|
||||||
|
else if (winLoss == "Verlierer") rows = rows.Where(r => r.RealizedPnl < 0m);
|
||||||
|
|
||||||
|
dgvTrades.DataSource = new BindingList<DashboardTradeRow>(rows.ToList());
|
||||||
|
}
|
||||||
|
|
||||||
|
private DashboardTradeRow ToRow(TradeRecord r) => new()
|
||||||
|
{
|
||||||
|
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
|
||||||
|
};
|
||||||
|
|
||||||
private string ResolveAccount(int accountId, bool isDemo)
|
private string ResolveAccount(int accountId, bool isDemo)
|
||||||
{
|
{
|
||||||
string suffix = isDemo ? " (Demo)" : "";
|
string suffix = isDemo ? " (Demo)" : "";
|
||||||
@@ -68,9 +246,14 @@ namespace PolyTraderSharp.Ui.Views
|
|||||||
return acc.Name + suffix;
|
return acc.Name + suffix;
|
||||||
return $"#{accountId}{suffix}";
|
return $"#{accountId}{suffix}";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private sealed record FilterAccount(int Id, string Label)
|
||||||
|
{
|
||||||
|
public override string ToString() => Label;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Anzeige-Zeile für das Dashboard-Grid (Account bereits zu Name aufgelöst).</summary>
|
/// <summary>Anzeige-Zeile für das Historie-Grid (Account bereits zu Name aufgelöst).</summary>
|
||||||
public class DashboardTradeRow
|
public class DashboardTradeRow
|
||||||
{
|
{
|
||||||
public string Module { get; set; } = string.Empty;
|
public string Module { get; set; } = string.Empty;
|
||||||
|
|||||||
Reference in New Issue
Block a user