diff --git a/PolyTrader.App.csproj b/PolyTrader.App.csproj index 3159cd4..6afd692 100644 --- a/PolyTrader.App.csproj +++ b/PolyTrader.App.csproj @@ -70,10 +70,12 @@ + + diff --git a/Program.cs b/Program.cs index 3c362b3..dd9dd1f 100644 --- a/Program.cs +++ b/Program.cs @@ -454,6 +454,19 @@ internal static class Program 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(), host.Services.GetRequiredService()); + 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 ==="); return failures; } diff --git a/Ui/Views/DashboardView.Designer.cs b/Ui/Views/DashboardView.Designer.cs index fdb11b1..a9cc1c6 100644 --- a/Ui/Views/DashboardView.Designer.cs +++ b/Ui/Views/DashboardView.Designer.cs @@ -17,9 +17,29 @@ namespace PolyTraderSharp.Ui.Views private void InitializeComponent() { - this.pnlTop = new System.Windows.Forms.Panel(); - this.lblSummary = new System.Windows.Forms.Label(); - this.btnRefresh = new System.Windows.Forms.Button(); + this.toolStripDash = new System.Windows.Forms.ToolStrip(); + this.tslKonto = new System.Windows.Forms.ToolStripLabel(); + 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.colModule = 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.colPnlPct = 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(); + this.pnlHistFilters.SuspendLayout(); this.SuspendLayout(); // - // pnlTop + // toolStripDash // - this.pnlTop.Controls.Add(this.lblSummary); - this.pnlTop.Controls.Add(this.btnRefresh); - this.pnlTop.Dock = System.Windows.Forms.DockStyle.Top; - this.pnlTop.Location = new System.Drawing.Point(0, 0); - this.pnlTop.Name = "pnlTop"; - this.pnlTop.Size = new System.Drawing.Size(1100, 40); - this.pnlTop.TabIndex = 0; + this.toolStripDash.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.tslKonto, this.cbAccount, this.tslModul, this.cbModule, this.tslArt, this.cbMode, + this.tslZeit, this.cbRange, this.tsRefresh}); + this.toolStripDash.Location = new System.Drawing.Point(0, 0); + this.toolStripDash.Name = "toolStripDash"; + this.toolStripDash.Size = new System.Drawing.Size(1200, 27); + this.toolStripDash.TabIndex = 0; // - // lblSummary + // tslKonto // - this.lblSummary.AutoSize = true; - this.lblSummary.Location = new System.Drawing.Point(148, 12); - this.lblSummary.Name = "lblSummary"; - this.lblSummary.Size = new System.Drawing.Size(63, 15); - this.lblSummary.TabIndex = 1; - this.lblSummary.Text = "—"; + this.tslKonto.Name = "tslKonto"; + this.tslKonto.Text = "Konto:"; // - // btnRefresh + // cbAccount // - this.btnRefresh.Location = new System.Drawing.Point(8, 6); - this.btnRefresh.Name = "btnRefresh"; - this.btnRefresh.Size = new System.Drawing.Size(130, 28); - this.btnRefresh.TabIndex = 0; - this.btnRefresh.Text = "Aktualisieren"; - this.btnRefresh.UseVisualStyleBackColor = true; + this.cbAccount.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.cbAccount.Name = "cbAccount"; + this.cbAccount.Size = new System.Drawing.Size(170, 27); + // + // tslModul + // + 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 // @@ -71,23 +296,14 @@ namespace PolyTraderSharp.Ui.Views this.dgvTrades.AutoGenerateColumns = false; this.dgvTrades.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize; this.dgvTrades.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] { - this.colModule, - this.colAccount, - this.colMarket, - this.colOutcome, - this.colSide, - this.colEntry, - this.colExit, - this.colSize, - this.colPnl, - this.colPnlPct, - this.colClosedAt}); + this.colModule, this.colAccount, 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.Location = new System.Drawing.Point(0, 40); + this.dgvTrades.Location = new System.Drawing.Point(3, 39); this.dgvTrades.Name = "dgvTrades"; this.dgvTrades.ReadOnly = true; 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; // // colModule @@ -167,26 +383,108 @@ namespace PolyTraderSharp.Ui.Views this.colClosedAt.Name = "colClosedAt"; 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 // this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.Controls.Add(this.dgvTrades); - this.Controls.Add(this.pnlTop); + this.Controls.Add(this.tabControlDash); + this.Controls.Add(this.toolStripDash); this.ClientSize = new System.Drawing.Size(1200, 650); this.Name = "DashboardView"; this.Text = "Dashboard"; - this.pnlTop.ResumeLayout(false); - this.pnlTop.PerformLayout(); + this.toolStripDash.ResumeLayout(false); + 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(); + this.pnlHistFilters.ResumeLayout(false); + this.pnlHistFilters.PerformLayout(); this.ResumeLayout(false); + this.PerformLayout(); } #endregion - private System.Windows.Forms.Panel pnlTop; - private System.Windows.Forms.Button btnRefresh; - private System.Windows.Forms.Label lblSummary; + private System.Windows.Forms.ToolStrip toolStripDash; + private System.Windows.Forms.ToolStripLabel tslKonto; + 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.DataGridViewTextBoxColumn colModule; private System.Windows.Forms.DataGridViewTextBoxColumn colAccount; diff --git a/Ui/Views/DashboardView.cs b/Ui/Views/DashboardView.cs index ddeb3d2..3ad6404 100644 --- a/Ui/Views/DashboardView.cs +++ b/Ui/Views/DashboardView.cs @@ -1,66 +1,244 @@ using System; +using System.Collections.Generic; using System.ComponentModel; +using System.IO; using System.Linq; using System.Windows.Forms; +using PolyTrader.Core.Analytics; using PolyTrader.Core.Persistence; +using PolyTraderSharp.Models; namespace PolyTraderSharp.Ui.Views { /// - /// Modulübergreifendes Dashboard: zeigt die letzten Trades ALLER Module aus dem - /// generischen Core-Trade-Log (ITradeLogRepository) und eine Kurzauswertung. - /// Designbar (DashboardView.Designer.cs). + /// Modulübergreifendes Dashboard aus dem generischen Core-Trade-Log (ITradeLogRepository). + /// Tab „Dashboard": KPI-Kacheln + Charts (Equity-Kurve, PnL je Modul, PnL je Tag) für den im + /// ToolStrip gewählten Scope (Konto/Modul/Live-Demo/Zeitraum). Tab „Tradehistorie": gefilterte + /// Trade-Liste. Auswertungslogik pur in ; Charts via ScottPlot + /// (als Bitmap gerendert – kein WinForms-GL-Control, saubere Dependencies). /// public partial class DashboardView : Form { private ITradeLogRepository? _tradeLog; private TradingState? _state; + private List _allTrades = new(); // zuletzt geladener Roh-Satz + private List _historyBase = new(); // Scope-gefiltert, Basis für die Historie-Filter + private bool _loading; + 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(); + + 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) { _tradeLog = tradeLog; _state = state; - LoadData(); + RefreshData(); } - private void LoadData() + // ===== Daten laden / Scope ===== + + private void RefreshData() { if (_tradeLog == null) return; + try { _allTrades = _tradeLog.GetRecent(5000); } + catch { _allTrades = new List(); } // DB nicht bereit -> leer statt Absturz - 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(); + _loading = true; + PopulateScopeCombos(); + _loading = false; - dgvTrades.DataSource = new BindingList(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)}"; + ApplyScope(); } + 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 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 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 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 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(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) { string suffix = isDemo ? " (Demo)" : ""; @@ -68,9 +246,14 @@ namespace PolyTraderSharp.Ui.Views return acc.Name + suffix; return $"#{accountId}{suffix}"; } + + private sealed record FilterAccount(int Id, string Label) + { + public override string ToString() => Label; + } } - /// Anzeige-Zeile für das Dashboard-Grid (Account bereits zu Name aufgelöst). + /// Anzeige-Zeile für das Historie-Grid (Account bereits zu Name aufgelöst). public class DashboardTradeRow { public string Module { get; set; } = string.Empty;