R5: CongressTrading als Strategie (Signal -> ExecutionService) + Positionen im Modul-Fenster

- CongressTradingStrategy: neuer Scrape-Trade -> TradeSignal (buy/sell-Mapping, sonst ignoriert)
  -> Core-IExecutionService; CongressScrapeWorker ruft sie je neuem Trade (try/catch-isoliert)
- Modul-Fenster: offene Positionen des Moduls via IPortfolioService.GetPositionsAsync("CT")
- Tests: CongressTradingStrategy (Mapping/Ausfuehrung, gemockter ExecutionService) -> 51/51 gruen
- Handel bleibt sicher aus (TradingEnabled=false + NullBrokerClient), bis Broker+Freigabe
- Build + smoke-ui + App-Start ok

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@
This commit is contained in:
Richard
2026-07-28 19:14:21 +02:00
parent 19dac18050
commit 331623d4f3
6 changed files with 180 additions and 25 deletions
@@ -1,13 +1,13 @@
using IBKRTrader.Core.Logging;
using IBKRTrader.Core.Trading;
using IBKRTrader.Core.Workers;
using IBKRTrader.Modules.CongressTrading.Database;
namespace IBKRTrader.Modules.CongressTrading.UI;
/// <summary>
/// Eigenständiges Fenster des CongressTrading-Moduls.
/// Grundgerüst mit DB-Kennzahlen und manuellem Scrape-Trigger.
/// Die vollständige Trade-/Positions-Ansicht folgt in einer späteren Phase.
/// Eigenständiges Fenster des CongressTrading-Moduls: DB-Kennzahlen, manueller Scrape-Trigger
/// und die offenen Positionen des Moduls (aus dem Core-Portfolio).
/// </summary>
public sealed class CongressTradingForm : Form
{
@@ -15,6 +15,7 @@ public sealed class CongressTradingForm : Form
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) };
@@ -23,11 +24,22 @@ public sealed class CongressTradingForm : Form
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 };
public CongressTradingForm(CongressRepository repo, WorkerEngine engine, LoggingService logger)
private readonly DataGridView _positions = new()
{
_repo = repo;
_engine = engine;
_logger = logger;
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;
@@ -47,14 +59,15 @@ public sealed class CongressTradingForm : Form
AutoSize = true
};
var hint = new Label
var posLabel = new Label
{
Text = "Vollständige Trade- und Positions-Ansicht folgt in Phase 4.",
Location = new Point(20, 230),
AutoSize = true,
ForeColor = SystemColors.GrayText
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();
@@ -64,7 +77,8 @@ public sealed class CongressTradingForm : Form
Controls.Add(_btnRefresh);
Controls.Add(_btnScrape);
Controls.Add(_lblStatus);
Controls.Add(hint);
Controls.Add(posLabel);
Controls.Add(_positions);
}
protected override async void OnShown(EventArgs e)
@@ -81,6 +95,12 @@ public sealed class CongressTradingForm : Form
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: {DateTime.Now:HH:mm:ss}";
}
catch (Exception ex)