using System; using System.Linq; using System.Windows.Forms; using Microsoft.Extensions.DependencyInjection; using PolyTrader.Modules.ResolutionFarming.Models; using PolyTrader.Modules.ResolutionFarming.Persistence; using PolyTraderSharp; namespace PolyTrader.Modules.ResolutionFarming.Ui { /// /// Hauptfenster des ResolutionFarming-Moduls: Tabs Kandidaten, Positionen, Historie/Statistik, /// Settings. Layout im Designer (ResolutionFarmingMainForm.Designer.cs), Verhalten/Daten hier. /// DB-Zugriffe defensiv (Guarded), damit die UI auch ohne angewendete rf_-Migration bedienbar bleibt. /// public partial class ResolutionFarmingMainForm : Form { private IRfCandidateRepository? _candidates; private IRfPositionRepository? _positions; private IRfClosedTradeRepository? _closed; private IRfSettingsRepository? _settingsRepo; private TradingState? _state; private RfSettings? _currentSettings; public ResolutionFarmingMainForm() { InitializeComponent(); btnCandRefresh.Click += (_, _) => RefreshCandidates(); cbCandAccount.SelectedIndexChanged += (_, _) => RefreshCandidates(); btnPosRefresh.Click += (_, _) => RefreshPositions(); btnHistRefresh.Click += (_, _) => RefreshHistory(); btnSettingsSave.Click += (_, _) => SaveSettings(); cbSettingsAccount.SelectedIndexChanged += (_, _) => LoadSettings(); } /// Injiziert Repos/State (nach DI-Auflösung) und lädt die Ansichten. public void Initialize(IServiceProvider services) { _candidates = services.GetRequiredService(); _positions = services.GetRequiredService(); _closed = services.GetRequiredService(); _settingsRepo = services.GetRequiredService(); _state = services.GetRequiredService(); PopulateAccounts(); RefreshCandidates(); RefreshPositions(); RefreshHistory(); } // ---------------- Daten ---------------- private void PopulateAccounts() { if (_state == null) return; var items = _state.Accounts.Values .OrderBy(a => a.AccountId) .Select(a => new AccountItem(a.AccountId, string.IsNullOrEmpty(a.Name) ? $"#{a.AccountId}" : $"{a.Name} (#{a.AccountId}){(a.IsDemo ? " · Demo" : "")}")) .ToList(); foreach (var combo in new[] { cbCandAccount, cbSettingsAccount }) { combo.DisplayMember = nameof(AccountItem.Label); combo.ValueMember = nameof(AccountItem.Id); combo.DataSource = items.ToList(); } if (items.Count > 0) LoadSettings(); else SetStatus("Keine Accounts vorhanden."); } private int? SelectedAccountId(ComboBox combo) => combo.SelectedItem is AccountItem it ? it.Id : (int?)null; private void RefreshCandidates() { if (_candidates == null || SelectedAccountId(cbCandAccount) is not int accId) return; Guarded("Kandidaten", () => dgvCandidates.DataSource = _candidates.GetRecent(accId, 200)); } private void RefreshPositions() { if (_positions == null) return; Guarded("Positionen", () => dgvPositions.DataSource = _positions.GetAllOpen()); } private void RefreshHistory() { if (_closed == null) return; Guarded("Historie", () => { var trades = _closed.Find(_ => true); dgvHistory.DataSource = trades; lblHistSummary.Text = BuildSummary(trades); }); } private static string BuildSummary(System.Collections.Generic.List trades) { if (trades.Count == 0) return "Noch keine abgeschlossenen Trades."; int wins = trades.Count(t => t.RealizedPnl > 0m); decimal pnl = trades.Sum(t => t.RealizedPnl); decimal fees = trades.Sum(t => t.TotalFees); double winrate = 100.0 * wins / trades.Count; // Winrate je Preisband (Kalibrierung: realisierte Winrate sollte > Band-Mitte liegen). var bands = trades .GroupBy(t => $"{Math.Floor(t.EntryPrice * 20m) / 20m:F2}") // 5-¢-Bänder .OrderBy(g => g.Key) .Select(g => $"{g.Key}: {100.0 * g.Count(x => x.RealizedPnl > 0m) / g.Count():F0}% ({g.Count()})"); return $"Trades: {trades.Count} | Winrate: {winrate:F1}% | Netto-PnL: {pnl:F2} USDC | Fees: {fees:F2}\n" + $"Winrate je Preisband: {string.Join(" | ", bands)}"; } private void LoadSettings() { if (_settingsRepo == null || SelectedAccountId(cbSettingsAccount) is not int accId) return; Guarded("Settings", () => { _currentSettings = _settingsRepo.Get(accId) ?? new RfSettings { AccountId = accId }; pgSettings.SelectedObject = _currentSettings; SetStatus($"Einstellungen für Konto #{accId}."); }); } private void SaveSettings() { if (_settingsRepo == null || _currentSettings == null) return; Guarded("Speichern", () => { _settingsRepo.Upsert(_currentSettings); SetStatus($"Gespeichert für Konto #{_currentSettings.AccountId} um {DateTime.Now:HH:mm:ss}."); }); } private void Guarded(string what, Action action) { try { action(); } catch (Exception ex) { SetStatus($"{what}: DB nicht bereit ({ex.Message}). Migration angewendet?"); } } private void SetStatus(string text) => lblRfStatus.Text = text; private sealed record AccountItem(int Id, string Label); } }