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; namespace PolyTrader.Modules.CopyTrading.Ui { /// /// Zeigt die geschlossenen Copytrades des Moduls (mod_copytrading_closed_trades) mit /// aufgelösten Account-/Master-Trader-Namen und einer Kurzauswertung. /// Layout im Designer (ClosedTradesView.Designer.cs), Daten/Logik hier. /// public partial class ClosedTradesView : UserControl { private ICopyTradeLogRepository? _tradeLog; private TradingState? _state; private CopyTradingState? _copyState; private List _allRows = new(); private bool _loading; // Parameterloser Konstruktor für den WinForms-Designer. public ClosedTradesView() { InitializeComponent(); colEntry.DefaultCellStyle.Format = "F3"; colExit.DefaultCellStyle.Format = "F3"; colSize.DefaultCellStyle.Format = "F2"; colPnl.DefaultCellStyle.Format = "F2"; colPnlPct.DefaultCellStyle.Format = "F1"; colOpenedAt.DefaultCellStyle.Format = "dd.MM.yyyy HH:mm"; 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; } /// Injiziert die Abhängigkeiten (nach der DI-Auflösung) und lädt die Daten. public void Initialize(ICopyTradeLogRepository tradeLog, TradingState state, CopyTradingState copyState) { _tradeLog = tradeLog; _state = state; _copyState = copyState; LoadData(); } private void LoadData() { if (_tradeLog == null) return; _allRows = _tradeLog.Find(_ => true) .OrderByDescending(t => t.ClosedAt) .Select(t => new ClosedTradeRow { TradeId = t.TradeId, AccountId = t.AccountId, SourceTraderId = t.SourceTraderId, IsDemo = t.IsDemo, TokenId = t.TokenId, MarketSlug = t.MarketSlug, MarketQuestion = t.MarketQuestion, Outcome = t.Outcome, Side = t.Side, EntryPrice = t.EntryPrice, ExitPrice = t.ExitPrice, Size = t.Size, RealizedPnl = t.RealizedPnl, PnlPercent = t.PnlPercent, TotalFees = t.TotalFees, OpenedAt = t.OpenedAt, ClosedAt = t.ClosedAt, ExitReason = t.ExitReason, AccountName = ResolveAccount(t.AccountId, t.IsDemo), SourceTraderName = ResolveTrader(t.SourceTraderId) }) .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 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(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} / {_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) { 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) { if (_copyState != null && _copyState.Traders.TryGetValue(traderId, out var t) && !string.IsNullOrEmpty(t.DisplayName)) return t.DisplayName; return traderId > 0 ? $"#{traderId}" : "Unbekannt"; } } }