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:
co-authored by
Claude Opus 4.8
parent
25f0d34042
commit
0725a81dbd
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user