using IBKRTrader.Core.Time; using IBKRTrader.Core.Logging; using IBKRTrader.Core.Trading; using IBKRTrader.Core.Workers; using IBKRTrader.Modules.CongressTrading; using IBKRTrader.Modules.CongressTrading.Database; namespace IBKRTrader.UI.Views.Modules; /// /// Eigenständiges Fenster des CongressTrading-Moduls: DB-Kennzahlen, manueller Scrape-Trigger /// und die offenen Positionen des Moduls (aus dem Core-Portfolio). /// public sealed class CongressTradingForm : Form { private const string ScrapeWorkerName = "CT-ScrapeWorker"; private readonly CongressRepository _repo; private readonly WorkerEngine _engine; private readonly IPortfolioService _portfolio; private readonly LoggingService _logger; private readonly Label _lblTrades = new() { AutoSize = true, Location = new Point(20, 70) }; private readonly Label _lblMembers = new() { AutoSize = true, Location = new Point(20, 100) }; private readonly Button _btnRefresh = new() { Text = "Aktualisieren", Location = new Point(20, 140), Width = 140 }; private readonly Button _btnScrape = new() { Text = "Scrape jetzt", Location = new Point(170, 140), Width = 140 }; private readonly Label _lblStatus = new() { AutoSize = true, Location = new Point(20, 185), ForeColor = SystemColors.GrayText }; private readonly DataGridView _positions = new() { Location = new Point(20, 250), Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right, ReadOnly = true, AllowUserToAddRows = false, RowHeadersVisible = false, AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill }; public CongressTradingForm(CongressRepository repo, WorkerEngine engine, IPortfolioService portfolio, LoggingService logger) { _repo = repo; _engine = engine; _portfolio = portfolio; _logger = logger; Text = "Congress Trading [CT]"; Width = 900; Height = 600; StartPosition = FormStartPosition.CenterScreen; MinimumSize = new Size(600, 400); BuildLayout(); } private void BuildLayout() { var title = new Label { Text = "Congress Trading", Font = new Font(Font.FontFamily, 14f, FontStyle.Bold), Location = new Point(18, 20), AutoSize = true }; var posLabel = new Label { Text = "Offene Positionen (Modul CT):", Location = new Point(20, 225), AutoSize = true }; _positions.Size = new Size(ClientSize.Width - 40, ClientSize.Height - 270); _btnRefresh.Click += async (_, _) => await RefreshStatsAsync(); _btnScrape.Click += async (_, _) => await TriggerScrapeAsync(); Controls.Add(title); Controls.Add(_lblTrades); Controls.Add(_lblMembers); Controls.Add(_btnRefresh); Controls.Add(_btnScrape); Controls.Add(_lblStatus); Controls.Add(posLabel); Controls.Add(_positions); } protected override async void OnShown(EventArgs e) { base.OnShown(e); await RefreshStatsAsync(); } private async Task RefreshStatsAsync() { try { var trades = await _repo.GetTradeCountAsync(); var members = await _repo.GetMemberCountAsync(); _lblTrades.Text = $"Trades in DB: {trades:N0}"; _lblMembers.Text = $"Mitglieder in DB: {members:N0}"; var positions = await _portfolio.GetPositionsAsync(CongressTradingModule.LogTag); _positions.DataSource = positions .Select(p => new { p.Symbol, Stück = p.Quantity, Ø_Kurs = p.AvgPrice, Wert = p.Notional }) .ToList(); _lblStatus.Text = $"Aktualisiert: {AppTimeZone.Now:HH:mm:ss}"; } catch (Exception ex) { _lblTrades.Text = "Trades in DB: n/v"; _lblMembers.Text = "Mitglieder in DB: n/v"; _lblStatus.Text = $"DB nicht erreichbar: {ex.Message}"; _logger.Warn(CongressTradingModule.LogTag, $"Kennzahlen konnten nicht geladen werden: {ex.Message}"); } } private async Task TriggerScrapeAsync() { try { _lblStatus.Text = "Scrape angestoßen..."; await _engine.TriggerWorkerAsync(ScrapeWorkerName); _logger.Info(CongressTradingModule.LogTag, "Scrape-Worker manuell ausgelöst (aus Modul-Fenster)."); } catch (Exception ex) { _lblStatus.Text = $"Scrape fehlgeschlagen: {ex.Message}"; _logger.Error(CongressTradingModule.LogTag, "Manueller Scrape-Trigger fehlgeschlagen.", ex); } } }