UI Slice 3: CopyTrading - Zeilenfaerbung, Filter, Offene-Trades-Tab, Master-Grid-Fix

- Geschlossene Trades: Zeilenfaerbung nach PnL-% (TradeRowColoring, pur+getestet: <0 rot,
  0-10% hellgruen, >10% gruen) via DataBindingComplete. Filter-Panel (Designer): Markt (Text),
  Master-Trader (Combo, dynamisch), Ergebnis (Alle/Gewinner/Verlierer), Von/Bis (optionale
  DateTimePicker mit Checkbox), Zuruecksetzen. Summary zeigt gefiltert/gesamt.
- Neuer Tab 'Offene Trades' (OpenTradesView, designerfaehig): alle offenen Positionen aus dem
  Laufzeit-State mit Entry/Aktuell/Wert/Buchgewinn/-% und Status (offen/Exit laeuft), Zeilenfaerbung
  nach Buchgewinn-%, Summenzeile. In CopyTradingMainForm zwischen Master-Trader und Geschlossene eingehaengt.
- Master-Trader-Grid: AutoSizeColumnsMode=Fill + FillWeights -> Spaltenbreiten teilen sich immer die
  Breite (Fix der 'verbuggten' Breiten); RowHeader war bereits aus.

Tests: +8 (TradeRowColoring-Schwellen). Build 0 Fehler, 396 Tests gruen, --smoke-ui gruen.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Richard
2026-07-20 20:49:41 +02:00
co-authored by Claude Opus 4.8
parent 25f0d34042
commit 0725a81dbd
13 changed files with 694 additions and 9 deletions
@@ -0,0 +1,18 @@
using System.Drawing;
namespace PolyTrader.Modules.CopyTrading.Logic
{
/// <summary>
/// Reine Zeilenfärbung nach realisiertem/unrealisiertem PnL-Prozent (getestet, damit die
/// Schwellen belastbar sind): Verlust rot, 010 % hellgrün, > 10 % kräftiges Grün.
/// </summary>
public static class TradeRowColoring
{
public static readonly Color Loss = Color.FromArgb(245, 200, 200); // < 0 % rot
public static readonly Color SmallWin = Color.FromArgb(212, 240, 212); // 010 % hellgrün
public static readonly Color BigWin = Color.FromArgb(140, 214, 140); // > 10 % grün
public static Color ForPnlPercent(decimal pnlPercent) =>
pnlPercent < 0m ? Loss : (pnlPercent > 10m ? BigWin : SmallWin);
}
}
@@ -35,9 +35,22 @@ namespace PolyTrader.Modules.CopyTrading.Ui
this.colClosedAt = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.statusPanel = new System.Windows.Forms.Panel();
this.lblSummary = new System.Windows.Forms.Label();
this.pnlFilters = new System.Windows.Forms.Panel();
this.lblMarkt = new System.Windows.Forms.Label();
this.tbMarket = new System.Windows.Forms.TextBox();
this.lblMaster = new System.Windows.Forms.Label();
this.cbMaster = new System.Windows.Forms.ComboBox();
this.lblErgebnis = new System.Windows.Forms.Label();
this.cbResult = new System.Windows.Forms.ComboBox();
this.lblVon = new System.Windows.Forms.Label();
this.dtFrom = new System.Windows.Forms.DateTimePicker();
this.lblBis = new System.Windows.Forms.Label();
this.dtTo = new System.Windows.Forms.DateTimePicker();
this.btnReset = new System.Windows.Forms.Button();
this.toolbar.SuspendLayout();
((System.ComponentModel.ISupportInitialize)(this.dgvTrades)).BeginInit();
this.statusPanel.SuspendLayout();
this.pnlFilters.SuspendLayout();
this.SuspendLayout();
//
// toolbar
@@ -210,12 +223,126 @@ namespace PolyTrader.Modules.CopyTrading.Ui
this.lblSummary.Text = "—";
this.lblSummary.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
//
// pnlFilters
//
this.pnlFilters.Controls.Add(this.lblMarkt);
this.pnlFilters.Controls.Add(this.tbMarket);
this.pnlFilters.Controls.Add(this.lblMaster);
this.pnlFilters.Controls.Add(this.cbMaster);
this.pnlFilters.Controls.Add(this.lblErgebnis);
this.pnlFilters.Controls.Add(this.cbResult);
this.pnlFilters.Controls.Add(this.lblVon);
this.pnlFilters.Controls.Add(this.dtFrom);
this.pnlFilters.Controls.Add(this.lblBis);
this.pnlFilters.Controls.Add(this.dtTo);
this.pnlFilters.Controls.Add(this.btnReset);
this.pnlFilters.Dock = System.Windows.Forms.DockStyle.Top;
this.pnlFilters.Location = new System.Drawing.Point(0, 25);
this.pnlFilters.Name = "pnlFilters";
this.pnlFilters.Size = new System.Drawing.Size(1100, 34);
this.pnlFilters.TabIndex = 3;
//
// lblMarkt
//
this.lblMarkt.AutoSize = true;
this.lblMarkt.Location = new System.Drawing.Point(6, 9);
this.lblMarkt.Name = "lblMarkt";
this.lblMarkt.Size = new System.Drawing.Size(41, 15);
this.lblMarkt.Text = "Markt:";
//
// tbMarket
//
this.tbMarket.Location = new System.Drawing.Point(51, 6);
this.tbMarket.Name = "tbMarket";
this.tbMarket.PlaceholderText = "Suchtext …";
this.tbMarket.Size = new System.Drawing.Size(200, 23);
this.tbMarket.TabIndex = 0;
//
// lblMaster
//
this.lblMaster.AutoSize = true;
this.lblMaster.Location = new System.Drawing.Point(263, 9);
this.lblMaster.Name = "lblMaster";
this.lblMaster.Size = new System.Drawing.Size(48, 15);
this.lblMaster.Text = "Master:";
//
// cbMaster
//
this.cbMaster.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
this.cbMaster.Location = new System.Drawing.Point(315, 6);
this.cbMaster.Name = "cbMaster";
this.cbMaster.Size = new System.Drawing.Size(170, 23);
this.cbMaster.TabIndex = 1;
//
// lblErgebnis
//
this.lblErgebnis.AutoSize = true;
this.lblErgebnis.Location = new System.Drawing.Point(497, 9);
this.lblErgebnis.Name = "lblErgebnis";
this.lblErgebnis.Size = new System.Drawing.Size(58, 15);
this.lblErgebnis.Text = "Ergebnis:";
//
// cbResult
//
this.cbResult.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
this.cbResult.Items.AddRange(new object[] { "Alle", "Gewinner", "Verlierer" });
this.cbResult.Location = new System.Drawing.Point(559, 6);
this.cbResult.Name = "cbResult";
this.cbResult.Size = new System.Drawing.Size(110, 23);
this.cbResult.TabIndex = 2;
//
// lblVon
//
this.lblVon.AutoSize = true;
this.lblVon.Location = new System.Drawing.Point(681, 9);
this.lblVon.Name = "lblVon";
this.lblVon.Size = new System.Drawing.Size(31, 15);
this.lblVon.Text = "Von:";
//
// dtFrom
//
this.dtFrom.Checked = false;
this.dtFrom.Format = System.Windows.Forms.DateTimePickerFormat.Short;
this.dtFrom.Location = new System.Drawing.Point(714, 6);
this.dtFrom.Name = "dtFrom";
this.dtFrom.ShowCheckBox = true;
this.dtFrom.Size = new System.Drawing.Size(120, 23);
this.dtFrom.TabIndex = 3;
//
// lblBis
//
this.lblBis.AutoSize = true;
this.lblBis.Location = new System.Drawing.Point(840, 9);
this.lblBis.Name = "lblBis";
this.lblBis.Size = new System.Drawing.Size(26, 15);
this.lblBis.Text = "Bis:";
//
// dtTo
//
this.dtTo.Checked = false;
this.dtTo.Format = System.Windows.Forms.DateTimePickerFormat.Short;
this.dtTo.Location = new System.Drawing.Point(868, 6);
this.dtTo.Name = "dtTo";
this.dtTo.ShowCheckBox = true;
this.dtTo.Size = new System.Drawing.Size(120, 23);
this.dtTo.TabIndex = 4;
//
// btnReset
//
this.btnReset.Location = new System.Drawing.Point(994, 5);
this.btnReset.Name = "btnReset";
this.btnReset.Size = new System.Drawing.Size(90, 25);
this.btnReset.TabIndex = 5;
this.btnReset.Text = "Zurücksetzen";
this.btnReset.UseVisualStyleBackColor = true;
//
// ClosedTradesView
//
this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.Controls.Add(this.dgvTrades);
this.Controls.Add(this.statusPanel);
this.Controls.Add(this.pnlFilters);
this.Controls.Add(this.toolbar);
this.Name = "ClosedTradesView";
this.Size = new System.Drawing.Size(1100, 600);
@@ -223,6 +350,8 @@ namespace PolyTrader.Modules.CopyTrading.Ui
this.toolbar.PerformLayout();
((System.ComponentModel.ISupportInitialize)(this.dgvTrades)).EndInit();
this.statusPanel.ResumeLayout(false);
this.pnlFilters.ResumeLayout(false);
this.pnlFilters.PerformLayout();
this.ResumeLayout(false);
this.PerformLayout();
}
@@ -247,5 +376,17 @@ namespace PolyTrader.Modules.CopyTrading.Ui
private System.Windows.Forms.DataGridViewTextBoxColumn colClosedAt;
private System.Windows.Forms.Panel statusPanel;
private System.Windows.Forms.Label lblSummary;
private System.Windows.Forms.Panel pnlFilters;
private System.Windows.Forms.Label lblMarkt;
private System.Windows.Forms.TextBox tbMarket;
private System.Windows.Forms.Label lblMaster;
private System.Windows.Forms.ComboBox cbMaster;
private System.Windows.Forms.Label lblErgebnis;
private System.Windows.Forms.ComboBox cbResult;
private System.Windows.Forms.Label lblVon;
private System.Windows.Forms.DateTimePicker dtFrom;
private System.Windows.Forms.Label lblBis;
private System.Windows.Forms.DateTimePicker dtTo;
private System.Windows.Forms.Button btnReset;
}
}
@@ -1,7 +1,9 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Windows.Forms;
using PolyTrader.Modules.CopyTrading.Logic;
using PolyTrader.Modules.CopyTrading.Persistence;
using PolyTraderSharp;
using PolyTraderSharp.Models;
@@ -19,6 +21,9 @@ namespace PolyTrader.Modules.CopyTrading.Ui
private TradingState? _state;
private CopyTradingState? _copyState;
private List<ClosedTradeRow> _allRows = new();
private bool _loading;
// Parameterloser Konstruktor für den WinForms-Designer.
public ClosedTradesView()
{
@@ -33,6 +38,18 @@ namespace PolyTrader.Modules.CopyTrading.Ui
colClosedAt.DefaultCellStyle.Format = "dd.MM.yyyy HH:mm";
tsRefresh.Click += (_, _) => LoadData();
// Zeilenfärbung nach PnL-% (grün/hellgrün/rot) nach jedem (Neu-)Binden.
dgvTrades.DataBindingComplete += (_, _) => ColorRows();
// Filter: Markt (Text), Master (Combo), Ergebnis (Win/Loss), Datum (Von/Bis, optional).
tbMarket.TextChanged += (_, _) => ApplyFilter();
cbMaster.SelectedIndexChanged += (_, _) => ApplyFilter();
cbResult.SelectedIndexChanged += (_, _) => ApplyFilter();
dtFrom.ValueChanged += (_, _) => ApplyFilter();
dtTo.ValueChanged += (_, _) => ApplyFilter();
btnReset.Click += (_, _) => ResetFilters();
if (cbResult.Items.Count > 0) cbResult.SelectedIndex = 0;
}
/// <summary>Injiziert die Abhängigkeiten (nach der DI-Auflösung) und lädt die Daten.</summary>
@@ -48,7 +65,7 @@ namespace PolyTrader.Modules.CopyTrading.Ui
{
if (_tradeLog == null) return;
var rows = _tradeLog.Find(_ => true)
_allRows = _tradeLog.Find(_ => true)
.OrderByDescending(t => t.ClosedAt)
.Select(t => new ClosedTradeRow
{
@@ -75,12 +92,71 @@ namespace PolyTrader.Modules.CopyTrading.Ui
})
.ToList();
PopulateMasters();
ApplyFilter();
}
private void PopulateMasters()
{
_loading = true;
string prev = cbMaster.SelectedItem as string ?? "Alle";
cbMaster.Items.Clear();
cbMaster.Items.Add("Alle");
foreach (var m in _allRows.Select(r => r.SourceTraderName)
.Where(s => !string.IsNullOrEmpty(s)).Distinct().OrderBy(s => s))
cbMaster.Items.Add(m);
int idx = cbMaster.Items.IndexOf(prev);
cbMaster.SelectedIndex = idx >= 0 ? idx : 0;
_loading = false;
}
private void ApplyFilter()
{
if (_loading) return;
IEnumerable<ClosedTradeRow> q = _allRows;
string market = tbMarket.Text.Trim();
if (market.Length > 0)
q = q.Where(r => (r.MarketQuestion?.Contains(market, StringComparison.OrdinalIgnoreCase) ?? false)
|| (r.MarketSlug?.Contains(market, StringComparison.OrdinalIgnoreCase) ?? false));
if (cbMaster.SelectedItem is string master && master != "Alle")
q = q.Where(r => r.SourceTraderName == master);
string result = cbResult.SelectedItem as string ?? "Alle";
if (result == "Gewinner") q = q.Where(r => r.RealizedPnl > 0m);
else if (result == "Verlierer") q = q.Where(r => r.RealizedPnl < 0m);
if (dtFrom.Checked) { DateTime f = dtFrom.Value.Date; q = q.Where(r => r.ClosedAt >= f); }
if (dtTo.Checked) { DateTime t = dtTo.Value.Date.AddDays(1).AddTicks(-1); q = q.Where(r => r.ClosedAt <= t); }
var rows = q.ToList();
dgvTrades.DataSource = new BindingList<ClosedTradeRow>(rows);
decimal totalPnl = rows.Sum(x => x.RealizedPnl);
int wins = rows.Count(x => x.RealizedPnl > 0);
double winrate = rows.Count > 0 ? (double)wins / rows.Count * 100 : 0;
lblSummary.Text = $"{rows.Count} Trades | PnL gesamt: {totalPnl:F2} USDC | Winrate: {winrate:F1}%";
lblSummary.Text = $"{rows.Count} / {_allRows.Count} Trades | PnL: {totalPnl:F2} USDC | Winrate: {winrate:F1}%";
}
private void ResetFilters()
{
_loading = true;
tbMarket.Text = string.Empty;
if (cbMaster.Items.Count > 0) cbMaster.SelectedIndex = 0;
if (cbResult.Items.Count > 0) cbResult.SelectedIndex = 0;
dtFrom.Checked = false;
dtTo.Checked = false;
_loading = false;
ApplyFilter();
}
private void ColorRows()
{
foreach (DataGridViewRow row in dgvTrades.Rows)
if (row.DataBoundItem is ClosedTradeRow r)
row.DefaultCellStyle.BackColor = TradeRowColoring.ForPnlPercent(r.PnlPercent);
}
private string ResolveAccount(int accountId, bool isDemo)
@@ -19,13 +19,16 @@ namespace PolyTrader.Modules.CopyTrading.Ui
{
this.tabControl = new System.Windows.Forms.TabControl();
this.tabMasters = new System.Windows.Forms.TabPage();
this.tabOpen = new System.Windows.Forms.TabPage();
this.tabClosed = new System.Windows.Forms.TabPage();
this.tabSettings = new System.Windows.Forms.TabPage();
this.masterTradersView = new PolyTrader.Modules.CopyTrading.Ui.MasterTradersView();
this.openTradesView = new PolyTrader.Modules.CopyTrading.Ui.OpenTradesView();
this.closedTradesView = new PolyTrader.Modules.CopyTrading.Ui.ClosedTradesView();
this.accountSettingsView = new PolyTrader.Modules.CopyTrading.Ui.AccountSettingsView();
this.tabControl.SuspendLayout();
this.tabMasters.SuspendLayout();
this.tabOpen.SuspendLayout();
this.tabClosed.SuspendLayout();
this.tabSettings.SuspendLayout();
this.SuspendLayout();
@@ -33,6 +36,7 @@ namespace PolyTrader.Modules.CopyTrading.Ui
// tabControl
//
this.tabControl.Controls.Add(this.tabMasters);
this.tabControl.Controls.Add(this.tabOpen);
this.tabControl.Controls.Add(this.tabClosed);
this.tabControl.Controls.Add(this.tabSettings);
this.tabControl.Dock = System.Windows.Forms.DockStyle.Fill;
@@ -53,6 +57,25 @@ namespace PolyTrader.Modules.CopyTrading.Ui
this.tabMasters.Text = "Master-Trader";
this.tabMasters.UseVisualStyleBackColor = true;
//
// tabOpen
//
this.tabOpen.Controls.Add(this.openTradesView);
this.tabOpen.Location = new System.Drawing.Point(4, 24);
this.tabOpen.Name = "tabOpen";
this.tabOpen.Padding = new System.Windows.Forms.Padding(3);
this.tabOpen.Size = new System.Drawing.Size(1192, 672);
this.tabOpen.TabIndex = 3;
this.tabOpen.Text = "Offene Trades";
this.tabOpen.UseVisualStyleBackColor = true;
//
// openTradesView
//
this.openTradesView.Dock = System.Windows.Forms.DockStyle.Fill;
this.openTradesView.Location = new System.Drawing.Point(3, 3);
this.openTradesView.Name = "openTradesView";
this.openTradesView.Size = new System.Drawing.Size(1186, 666);
this.openTradesView.TabIndex = 0;
//
// tabClosed
//
this.tabClosed.Controls.Add(this.closedTradesView);
@@ -111,6 +134,7 @@ namespace PolyTrader.Modules.CopyTrading.Ui
this.Text = "Copytrading";
this.tabControl.ResumeLayout(false);
this.tabMasters.ResumeLayout(false);
this.tabOpen.ResumeLayout(false);
this.tabClosed.ResumeLayout(false);
this.tabSettings.ResumeLayout(false);
this.ResumeLayout(false);
@@ -120,9 +144,11 @@ namespace PolyTrader.Modules.CopyTrading.Ui
private System.Windows.Forms.TabControl tabControl;
private System.Windows.Forms.TabPage tabMasters;
private System.Windows.Forms.TabPage tabOpen;
private System.Windows.Forms.TabPage tabClosed;
private System.Windows.Forms.TabPage tabSettings;
private MasterTradersView masterTradersView;
private OpenTradesView openTradesView;
private ClosedTradesView closedTradesView;
private AccountSettingsView accountSettingsView;
}
@@ -29,6 +29,8 @@ namespace PolyTrader.Modules.CopyTrading.Ui
masterTradersView.Initialize(
services.GetRequiredService<ITrackedTraderRepository>(), state, copyState);
openTradesView.Initialize(state, copyState);
closedTradesView.Initialize(
services.GetRequiredService<ICopyTradeLogRepository>(), state, copyState);
@@ -102,6 +102,7 @@ namespace PolyTrader.Modules.CopyTrading.Ui
this.grid.AllowUserToAddRows = false;
this.grid.AllowUserToDeleteRows = false;
this.grid.AutoGenerateColumns = false;
this.grid.AutoSizeColumnsMode = System.Windows.Forms.DataGridViewAutoSizeColumnsMode.Fill;
this.grid.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
this.grid.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] {
this.colId,
@@ -35,6 +35,21 @@ namespace PolyTrader.Modules.CopyTrading.Ui
colCopyPf.DefaultCellStyle.Format = "F2";
colCopyAvg.DefaultCellStyle.Format = "F2";
// Spaltenbreiten proportional (grid-weites Fill): keine überlaufenden Fixbreiten mehr,
// die Spalten teilen sich immer exakt die verfügbare Breite (Fix des „verbuggten" Grids).
colId.FillWeight = 40;
colWallet.FillWeight = 150;
colName.FillWeight = 200;
colCategory.FillWeight = 90;
colActive.FillWeight = 50;
colTrades.FillWeight = 85;
colWinrate.FillWeight = 90;
colPnl.FillWeight = 90;
colCopyPnl.FillWeight = 110;
colCopyPf.FillWeight = 95;
colCopyAvg.FillWeight = 95;
colCopyCount.FillWeight = 95;
tsNew.Click += (_, _) => AddNew();
tsSave.Click += (_, _) => SaveCurrent();
tsDelete.Click += (_, _) => DeleteCurrent();
@@ -0,0 +1,241 @@
namespace PolyTrader.Modules.CopyTrading.Ui
{
partial class OpenTradesView
{
private System.ComponentModel.IContainer components = null;
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Vom Komponenten-Designer generierter Code
private void InitializeComponent()
{
this.toolbar = new System.Windows.Forms.ToolStrip();
this.tsRefresh = new System.Windows.Forms.ToolStripButton();
this.dgvOpen = new System.Windows.Forms.DataGridView();
this.colAccount = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.colTrader = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.colMarket = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.colOutcome = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.colSide = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.colEntry = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.colCurrent = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.colSize = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.colAmount = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.colValue = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.colUnrealized = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.colUnrealizedPct = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.colStatus = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.statusPanel = new System.Windows.Forms.Panel();
this.lblSummary = new System.Windows.Forms.Label();
this.toolbar.SuspendLayout();
((System.ComponentModel.ISupportInitialize)(this.dgvOpen)).BeginInit();
this.statusPanel.SuspendLayout();
this.SuspendLayout();
//
// toolbar
//
this.toolbar.GripStyle = System.Windows.Forms.ToolStripGripStyle.Hidden;
this.toolbar.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { this.tsRefresh });
this.toolbar.Location = new System.Drawing.Point(0, 0);
this.toolbar.Name = "toolbar";
this.toolbar.Size = new System.Drawing.Size(1100, 25);
this.toolbar.TabIndex = 0;
//
// tsRefresh
//
this.tsRefresh.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Text;
this.tsRefresh.Name = "tsRefresh";
this.tsRefresh.Size = new System.Drawing.Size(90, 22);
this.tsRefresh.Text = "Aktualisieren";
//
// dgvOpen
//
this.dgvOpen.AllowUserToAddRows = false;
this.dgvOpen.AllowUserToDeleteRows = false;
this.dgvOpen.AutoGenerateColumns = false;
this.dgvOpen.AutoSizeColumnsMode = System.Windows.Forms.DataGridViewAutoSizeColumnsMode.Fill;
this.dgvOpen.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
this.dgvOpen.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] {
this.colAccount, this.colTrader, this.colMarket, this.colOutcome, this.colSide,
this.colEntry, this.colCurrent, this.colSize, this.colAmount, this.colValue,
this.colUnrealized, this.colUnrealizedPct, this.colStatus});
this.dgvOpen.Dock = System.Windows.Forms.DockStyle.Fill;
this.dgvOpen.Location = new System.Drawing.Point(0, 25);
this.dgvOpen.Name = "dgvOpen";
this.dgvOpen.ReadOnly = true;
this.dgvOpen.RowHeadersVisible = false;
this.dgvOpen.SelectionMode = System.Windows.Forms.DataGridViewSelectionMode.FullRowSelect;
this.dgvOpen.Size = new System.Drawing.Size(1100, 549);
this.dgvOpen.TabIndex = 1;
//
// colAccount
//
this.colAccount.DataPropertyName = "AccountName";
this.colAccount.HeaderText = "Account";
this.colAccount.Name = "colAccount";
this.colAccount.ReadOnly = true;
this.colAccount.FillWeight = 120;
//
// colTrader
//
this.colTrader.DataPropertyName = "SourceTraderName";
this.colTrader.HeaderText = "Master-Trader";
this.colTrader.Name = "colTrader";
this.colTrader.ReadOnly = true;
this.colTrader.FillWeight = 120;
//
// colMarket
//
this.colMarket.DataPropertyName = "MarketQuestion";
this.colMarket.HeaderText = "Markt";
this.colMarket.Name = "colMarket";
this.colMarket.ReadOnly = true;
this.colMarket.FillWeight = 240;
//
// colOutcome
//
this.colOutcome.DataPropertyName = "Outcome";
this.colOutcome.HeaderText = "Outcome";
this.colOutcome.Name = "colOutcome";
this.colOutcome.ReadOnly = true;
this.colOutcome.FillWeight = 80;
//
// colSide
//
this.colSide.DataPropertyName = "Side";
this.colSide.HeaderText = "Side";
this.colSide.Name = "colSide";
this.colSide.ReadOnly = true;
this.colSide.FillWeight = 55;
//
// colEntry
//
this.colEntry.DataPropertyName = "EntryPrice";
this.colEntry.HeaderText = "Entry";
this.colEntry.Name = "colEntry";
this.colEntry.ReadOnly = true;
this.colEntry.FillWeight = 65;
//
// colCurrent
//
this.colCurrent.DataPropertyName = "CurrentPrice";
this.colCurrent.HeaderText = "Aktuell";
this.colCurrent.Name = "colCurrent";
this.colCurrent.ReadOnly = true;
this.colCurrent.FillWeight = 65;
//
// colSize
//
this.colSize.DataPropertyName = "Size";
this.colSize.HeaderText = "Size";
this.colSize.Name = "colSize";
this.colSize.ReadOnly = true;
this.colSize.FillWeight = 70;
//
// colAmount
//
this.colAmount.DataPropertyName = "AmountUsd";
this.colAmount.HeaderText = "Einsatz";
this.colAmount.Name = "colAmount";
this.colAmount.ReadOnly = true;
this.colAmount.FillWeight = 80;
//
// colValue
//
this.colValue.DataPropertyName = "CurrentValueUsd";
this.colValue.HeaderText = "Wert";
this.colValue.Name = "colValue";
this.colValue.ReadOnly = true;
this.colValue.FillWeight = 80;
//
// colUnrealized
//
this.colUnrealized.DataPropertyName = "UnrealizedPnl";
this.colUnrealized.HeaderText = "Buchgewinn";
this.colUnrealized.Name = "colUnrealized";
this.colUnrealized.ReadOnly = true;
this.colUnrealized.FillWeight = 90;
//
// colUnrealizedPct
//
this.colUnrealizedPct.DataPropertyName = "UnrealizedPct";
this.colUnrealizedPct.HeaderText = "Buchgew. %";
this.colUnrealizedPct.Name = "colUnrealizedPct";
this.colUnrealizedPct.ReadOnly = true;
this.colUnrealizedPct.FillWeight = 80;
//
// colStatus
//
this.colStatus.DataPropertyName = "Status";
this.colStatus.HeaderText = "Status";
this.colStatus.Name = "colStatus";
this.colStatus.ReadOnly = true;
this.colStatus.FillWeight = 90;
//
// statusPanel
//
this.statusPanel.Controls.Add(this.lblSummary);
this.statusPanel.Dock = System.Windows.Forms.DockStyle.Bottom;
this.statusPanel.Location = new System.Drawing.Point(0, 574);
this.statusPanel.Name = "statusPanel";
this.statusPanel.Size = new System.Drawing.Size(1100, 26);
this.statusPanel.TabIndex = 2;
//
// lblSummary
//
this.lblSummary.Dock = System.Windows.Forms.DockStyle.Fill;
this.lblSummary.Location = new System.Drawing.Point(0, 0);
this.lblSummary.Name = "lblSummary";
this.lblSummary.Padding = new System.Windows.Forms.Padding(8, 0, 0, 0);
this.lblSummary.Size = new System.Drawing.Size(1100, 26);
this.lblSummary.TabIndex = 0;
this.lblSummary.Text = "—";
this.lblSummary.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
//
// OpenTradesView
//
this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.Controls.Add(this.dgvOpen);
this.Controls.Add(this.statusPanel);
this.Controls.Add(this.toolbar);
this.Name = "OpenTradesView";
this.Size = new System.Drawing.Size(1100, 600);
this.toolbar.ResumeLayout(false);
this.toolbar.PerformLayout();
((System.ComponentModel.ISupportInitialize)(this.dgvOpen)).EndInit();
this.statusPanel.ResumeLayout(false);
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private System.Windows.Forms.ToolStrip toolbar;
private System.Windows.Forms.ToolStripButton tsRefresh;
private System.Windows.Forms.DataGridView dgvOpen;
private System.Windows.Forms.DataGridViewTextBoxColumn colAccount;
private System.Windows.Forms.DataGridViewTextBoxColumn colTrader;
private System.Windows.Forms.DataGridViewTextBoxColumn colMarket;
private System.Windows.Forms.DataGridViewTextBoxColumn colOutcome;
private System.Windows.Forms.DataGridViewTextBoxColumn colSide;
private System.Windows.Forms.DataGridViewTextBoxColumn colEntry;
private System.Windows.Forms.DataGridViewTextBoxColumn colCurrent;
private System.Windows.Forms.DataGridViewTextBoxColumn colSize;
private System.Windows.Forms.DataGridViewTextBoxColumn colAmount;
private System.Windows.Forms.DataGridViewTextBoxColumn colValue;
private System.Windows.Forms.DataGridViewTextBoxColumn colUnrealized;
private System.Windows.Forms.DataGridViewTextBoxColumn colUnrealizedPct;
private System.Windows.Forms.DataGridViewTextBoxColumn colStatus;
private System.Windows.Forms.Panel statusPanel;
private System.Windows.Forms.Label lblSummary;
}
}
@@ -0,0 +1,123 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Windows.Forms;
using PolyTrader.Modules.CopyTrading.Logic;
using PolyTraderSharp;
using PolyTraderSharp.Models;
namespace PolyTrader.Modules.CopyTrading.Ui
{
/// <summary>
/// Listet ALLE aktuell offenen Positionen (Portfolios der Accounts) mit Buchgewinn/-verlust.
/// Quelle ist der Laufzeit-State (<see cref="TradingState.Accounts"/>), nicht die DB so ist die
/// Anzeige live. Zeilenfärbung nach Buchgewinn-% (<see cref="TradeRowColoring"/>).
/// Layout im Designer (OpenTradesView.Designer.cs).
/// </summary>
public partial class OpenTradesView : UserControl
{
private TradingState? _state;
private CopyTradingState? _copyState;
public OpenTradesView()
{
InitializeComponent();
colEntry.DefaultCellStyle.Format = "F3";
colCurrent.DefaultCellStyle.Format = "F3";
colSize.DefaultCellStyle.Format = "F2";
colAmount.DefaultCellStyle.Format = "F2";
colValue.DefaultCellStyle.Format = "F2";
colUnrealized.DefaultCellStyle.Format = "F2";
colUnrealizedPct.DefaultCellStyle.Format = "F1";
tsRefresh.Click += (_, _) => LoadData();
dgvOpen.DataBindingComplete += (_, _) => ColorRows();
}
public void Initialize(TradingState state, CopyTradingState copyState)
{
_state = state;
_copyState = copyState;
LoadData();
}
private void LoadData()
{
if (_state == null) return;
var rows = _state.Accounts.Values
.SelectMany(a => a.OpenPositions.Values.Select(p => (acc: a, pos: p)))
.OrderByDescending(x => x.pos.CurrentValueUsd)
.Select(x =>
{
decimal unrealized = x.pos.CurrentValueUsd - x.pos.AmountUsd;
decimal pct = x.pos.AmountUsd > 0m ? unrealized / x.pos.AmountUsd * 100m : 0m;
return new OpenTradeRow
{
AccountName = ResolveAccount(x.acc.AccountId, x.pos.IsDemo),
SourceTraderName = ResolveTrader(x.pos.SourceTraderId, x.pos.SourceTraderName),
MarketQuestion = x.pos.MarketQuestion,
Outcome = x.pos.Outcome,
Side = x.pos.Side,
EntryPrice = x.pos.EntryPrice,
CurrentPrice = x.pos.CurrentPrice,
Size = x.pos.Size,
AmountUsd = x.pos.AmountUsd,
CurrentValueUsd = x.pos.CurrentValueUsd,
UnrealizedPnl = unrealized,
UnrealizedPct = pct,
Status = x.pos.ExitPending ? "Exit läuft" : "offen"
};
})
.ToList();
dgvOpen.DataSource = new BindingList<OpenTradeRow>(rows);
decimal totalValue = rows.Sum(r => r.CurrentValueUsd);
decimal totalUnrealized = rows.Sum(r => r.UnrealizedPnl);
lblSummary.Text = $"{rows.Count} offene Positionen | Wert: {totalValue:F2} USDC | Buchgewinn: {totalUnrealized:F2} USDC";
}
private void ColorRows()
{
foreach (DataGridViewRow row in dgvOpen.Rows)
if (row.DataBoundItem is OpenTradeRow r)
row.DefaultCellStyle.BackColor = TradeRowColoring.ForPnlPercent(r.UnrealizedPct);
}
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}";
}
private string ResolveTrader(int traderId, string fallback)
{
if (_copyState != null && _copyState.Traders.TryGetValue(traderId, out var t) && !string.IsNullOrEmpty(t.DisplayName))
return t.DisplayName;
return !string.IsNullOrEmpty(fallback) ? fallback : (traderId > 0 ? $"#{traderId}" : "System");
}
/// <summary>Anzeige-Zeile für offene Positionen.</summary>
private sealed class OpenTradeRow
{
public string AccountName { get; set; } = string.Empty;
public string SourceTraderName { get; set; } = string.Empty;
public string MarketQuestion { 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 CurrentPrice { get; set; }
public decimal Size { get; set; }
public decimal AmountUsd { get; set; }
public decimal CurrentValueUsd { get; set; }
public decimal UnrealizedPnl { get; set; }
public decimal UnrealizedPct { get; set; }
public string Status { get; set; } = string.Empty;
}
}
}