using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Windows.Forms; using Microsoft.Extensions.DependencyInjection; using PolyTrader.Core.Analytics; using PolyTrader.Core.Modularity; using PolyTrader.Core.Persistence; using PolyTraderSharp.Models; namespace PolyTraderSharp.Ui { /// /// „Startleiste" der PolyTrader.App. Fenster werden über die im Designer platzierten /// Buttons in toolstrip_windows geöffnet bzw. in den Vordergrund geholt. /// toolstrip_quickbar ist für Schnellaktionen (z.B. Trading an/aus) reserviert, /// statusStrip_info zeigt Kernkennzahlen. Der Launcher kennt selbst kein Modul — /// Buttons werden per stabiler View-ID an registrierte Views gebunden. /// public partial class LauncherForm : Form { private readonly ShellUiHost _uiHost; private readonly IServiceProvider _services; private readonly TradingState _state; private readonly System.Windows.Forms.Timer _statusTimer = new() { Interval = 1000 }; private readonly Dictionary _viewButtons; private int _statusTicks; public LauncherForm(ShellUiHost uiHost, IServiceProvider services) { _uiHost = uiHost; _services = services; _state = services.GetRequiredService(); InitializeComponent(); // Fenster-Buttons an stabile View-IDs binden (Views werden per DI registriert). _viewButtons = new Dictionary { ["core.dashboard"] = btn_dashboard, ["core.settings"] = btn_settings, ["core.terminal"] = btn_terminal, ["core.jobs"] = btn_jobs, }; foreach (var (id, btn) in _viewButtons) { var viewId = id; btn.Click += (_, _) => _uiHost.OpenView(viewId); } // Modul-Views dynamisch anhängen (der Launcher kennt keine Modulnamen; er bindet // nur an registrierte View-IDs). Core-Views sind bereits fest verdrahtet. var moduleViews = _uiHost.Views .Where(v => !_viewButtons.ContainsKey(v.Id)) .OrderBy(v => v.Order) .ToList(); if (moduleViews.Count > 0) toolstrip_windows.Items.Add(new ToolStripSeparator()); foreach (var view in moduleViews) { var btn = new ToolStripButton(view.Title) { Name = "btn_" + view.Id, Overflow = ToolStripItemOverflow.AsNeeded, TextImageRelation = TextImageRelation.ImageAboveText, AutoSize = true }; var viewId = view.Id; btn.Click += (_, _) => _uiHost.OpenView(viewId); toolstrip_windows.Items.Add(btn); _viewButtons[view.Id] = btn; } btn_liveTrading.Click += (_, _) => CycleLiveTrading(); btn_demoTrading.Click += (_, _) => CycleDemoTrading(); miBeenden.Click += (_, _) => Close(); // Offen-Status der Fenster spiegeln (Button „checked", wenn Fenster offen). _uiHost.OpenStateChanged += UpdateWindowButtonStates; // Account-Übersicht (dgv_accountlist): Zahlenformate + Polymarket-Button. colAccBalance.DefaultCellStyle.Format = "N2"; colAccPnl3d.DefaultCellStyle.Format = "N2"; colAccWin3d.DefaultCellStyle.Format = "N1"; colAccOverall.DefaultCellStyle.Format = "N2"; dgv_accountlist.CellContentClick += AccountList_CellContentClick; _statusTimer.Tick += (_, _) => UpdateStatus(); _statusTimer.Start(); UpdateStatus(); UpdateTradingToggles(); UpdateWindowButtonStates(); } private void UpdateWindowButtonStates() { if (IsDisposed) return; foreach (var (id, btn) in _viewButtons) btn.Checked = _uiHost.IsOpen(id); } private void CycleLiveTrading() { _state.LiveTradingMode = _state.LiveTradingMode switch { TradingMode.Inactive => TradingMode.SellOnly, TradingMode.SellOnly => TradingMode.Active, _ => TradingMode.Inactive }; UpdateTradingToggles(); } private void CycleDemoTrading() { _state.DemoTradingMode = _state.DemoTradingMode switch { TradingMode.Inactive => TradingMode.SellOnly, TradingMode.SellOnly => TradingMode.Active, _ => TradingMode.Inactive }; UpdateTradingToggles(); } private void UpdateTradingToggles() { ApplyToggle(btn_liveTrading, "LiveTrading", _state.LiveTradingMode); ApplyToggle(btn_demoTrading, "DemoTrading", _state.DemoTradingMode); static void ApplyToggle(ToolStripButton btn, string label, TradingMode mode) { switch (mode) { case TradingMode.Active: btn.Text = $"{label} (AKTIV)"; btn.BackColor = System.Drawing.Color.LightGreen; break; case TradingMode.SellOnly: btn.Text = $"{label} (SELL-ONLY)"; btn.BackColor = System.Drawing.Color.Orange; break; default: btn.Text = $"{label} (DEAKTIVIERT)"; btn.BackColor = System.Drawing.Color.IndianRed; break; } } } private void UpdateStatus() { string trading = _state.GlobalTradingPaused ? "Pausiert" : $"Live={_state.LiveTradingMode} / Demo={_state.DemoTradingMode}"; lbl_trading.Text = $"Trading: {trading}"; int moduleCount = _services.GetServices().Count(); lbl_modules.Text = $"Module: {moduleCount}"; lbl_ratelimit.Text = $"API: {(_state.IsAlchemyHealthy ? "WSS aktiv" : "Polling")}"; UpdateTradingToggles(); UpdateWindowButtonStates(); // Account-Übersicht alle 30 s aktualisieren (DB-Abfrage je Account – nicht jede Sekunde). if (_statusTicks++ % 30 == 0) LoadAccountOverview(); } // ===== Account-Übersicht (dgv_accountlist) ===== private void LoadAccountOverview() { if (IsDisposed) return; var tradeLog = _services.GetService(); if (tradeLog == null) return; DateTime since3d = DateTime.UtcNow.AddDays(-3); var rows = new List(); foreach (var acc in _state.Accounts.Values.OrderBy(a => a.AccountId)) { List trades; try { trades = tradeLog.Find(t => t.AccountId == acc.AccountId); } catch { trades = new List(); } // DB nicht bereit -> leer statt Absturz var (pnl3d, win3d, _) = TradeAnalytics.WindowSummary(trades.Where(t => t.ClosedAt >= since3d)); string modules = trades .Select(t => t.ModuleName) .Where(m => !string.IsNullOrEmpty(m)) .Distinct().OrderBy(m => m) .DefaultIfEmpty("—") .Aggregate((a, b) => a + ", " + b); rows.Add(new AccountOverviewRow { AccountId = acc.AccountId, Name = (string.IsNullOrEmpty(acc.Name) ? $"#{acc.AccountId}" : acc.Name) + (acc.IsDemo ? " (Demo)" : ""), Modules = modules, WalletAddress = acc.WalletAddress, Balance = acc.TotalBalance, Pnl3d = pnl3d, WinRate3d = win3d, OverallPnl = trades.Sum(t => t.RealizedPnl) }); } dgv_accountlist.DataSource = rows; } private void AccountList_CellContentClick(object? sender, DataGridViewCellEventArgs e) { if (e.RowIndex < 0 || e.ColumnIndex < 0) return; if (dgv_accountlist.Columns[e.ColumnIndex].Name != "colAccPoly") return; if (dgv_accountlist.Rows[e.RowIndex].DataBoundItem is AccountOverviewRow row) OpenPolymarketProfile(row.WalletAddress); } private void OpenPolymarketProfile(string walletAddress) { if (string.IsNullOrWhiteSpace(walletAddress)) { MessageBox.Show("Für diesen Account ist keine Wallet-Adresse hinterlegt.", "Polymarket", MessageBoxButtons.OK, MessageBoxIcon.Information); return; } try { Process.Start(new ProcessStartInfo { FileName = $"https://polymarket.com/profile/{walletAddress}", UseShellExecute = true }); } catch (Exception ex) { MessageBox.Show($"Konnte Polymarket nicht öffnen: {ex.Message}", "Fehler", MessageBoxButtons.OK, MessageBoxIcon.Error); } } /// Anzeige-Zeile der Account-Übersicht (Bindung an dgv_accountlist über DataPropertyName). private sealed class AccountOverviewRow { public int AccountId { get; set; } public string Name { get; set; } = string.Empty; public string Modules { get; set; } = string.Empty; public string WalletAddress { get; set; } = string.Empty; public decimal Balance { get; set; } public decimal Pnl3d { get; set; } public decimal WinRate3d { get; set; } public decimal OverallPnl { get; set; } } } }