R1: Umbau auf Multi-Projekt-Struktur (Vorbild PolytraderSharp)

Kurskorrektur: Vorbild ist PolytraderSharp (C#), nicht die veraltete Python-Version.
Reiner Strukturumbau, Verhalten unveraendert.

- src/IBKRTrader.Core (classlib): Core-Code + UI-Contract (ModuleFormBase, WindowManager)
- src/IBKRTrader.Modules.CongressTrading (classlib, referenziert nur Core)
- Root: IBKRTrader.App (WinExe) referenziert Core + Modul
- tests/IBKRTrader.Tests: Referenzen auf Core + Modul
- Neue .slnx; ungenutztes HtmlAgilityPack entfernt
- docs/ARCHITECTURE.md: korrigierter Ziel-Stand + R-Phasenplan
- Build + 38/38 Tests gruen; App startet

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@
This commit is contained in:
Richard
2026-07-27 20:26:25 +02:00
parent 2ad4b55db1
commit c20538c8d8
61 changed files with 171 additions and 129 deletions
@@ -0,0 +1,57 @@
using FluentAssertions;
using IBKRTrader.Core.Logging;
using IBKRTrader.Modules.CongressTrading.Scraper;
namespace IBKRTrader.Tests.Modules;
[Trait("cat", "unit")]
public class CapitolTradesScraperTests
{
private static string LoadFixture()
{
var path = Path.Combine(AppContext.BaseDirectory, "Fixtures", "ct_raw.html");
File.Exists(path).Should().BeTrue($"Fixture fehlt: {path}");
return File.ReadAllText(path);
}
[Fact]
public void ParseTradesFromHtml_ExtractsTradesFromRscStream()
{
var scraper = new CapitolTradesScraper(new LoggingService());
var (trades, memberIds) = scraper.ParseTradesFromHtml(LoadFixture());
trades.Should().NotBeEmpty("die Fixture enthält einen RSC-Datenstrom mit Trades");
memberIds.Should().NotBeEmpty();
var first = trades[0];
first.TradeId.Should().NotBeNullOrWhiteSpace();
first.TradeType.Should().NotBeNullOrWhiteSpace();
}
[Fact]
public void ParseTradesFromHtml_MapsMemberSnapshotConsistently()
{
var scraper = new CapitolTradesScraper(new LoggingService());
var (trades, memberIds) = scraper.ParseTradesFromHtml(LoadFixture());
// Jeder Member-Snapshot muss eine BioId haben, die auch im MemberBioIds-Set liegt.
foreach (var trade in trades.Where(t => t.MemberSnapshot is not null))
{
trade.MemberSnapshot!.BioId.Should().NotBeNullOrWhiteSpace();
memberIds.Should().Contain(trade.MemberBioId);
}
}
[Fact]
public void ParseTradesFromHtml_EmptyHtml_ReturnsNoTrades()
{
var scraper = new CapitolTradesScraper(new LoggingService());
var (trades, memberIds) = scraper.ParseTradesFromHtml("<html><body>nichts</body></html>");
trades.Should().BeEmpty();
memberIds.Should().BeEmpty();
}
}