R10: Lesender Bestandsabgleich (IBrokerPortfolioReader) + Datenlage-Konzepte
Eigener Seam neben IBrokerClient: Wer handelt, braucht ihn nicht; wer die eigene Buchfuehrung gegen den Broker abstimmt, braucht nur ihn. Zuteilung und Verfall aendern Positionen ohne Order von uns - ohne Abgleich laeuft das Managementbuch zwangslaeufig auseinander. - IBrokerPortfolioReader mit GetPositionsAsync/GetExecutionsAsync; implementiert von IbkrBrokerClient und NullBrokerClient (DI registriert beide Rollen auf derselben Instanz). - IbkrConnection: reqAccountUpdates statt reqPositions (nur dieser Weg liefert Marktwert und unrealisierten G/V), reqExecutions inkl. Zuordnung der verspaetet eintreffenden commissionReport-Callbacks ueber die ExecId. - BrokerPosition/BrokerExecution als Broker-Wahrheit neben Position; IbkrMapping: ParseSide, ParseExecutionTime, FormatExecutionFilterTime (UTC wegen TWS-Warnung 2174) - mit Unit-Tests. - Verifiziert gegen Paper-Konto DUR371528: 2 Positionen, 2 Ausfuehrungen inkl. Kommissionen. Doku: Kapital- und Buchmodell (drei Wahrheiten, Kapitalzuteilung), KONZEPT-Datenlage-und-Strategien (gemessen, was die API auf diesem Konto liefert). Options-Wheel: Greeks bei verzoegerten Daten funktionieren (Feld 83); Earnings-Termine sind ueber die TWS API nicht erreichbar (Fehler 10358) - Behelf ueber IV-Filter statt Fremddatenquelle. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -151,6 +151,48 @@ public class IbkrMappingTests
|
||||
public void BuildQuote_WithoutAnyPrice_ReturnsNull() =>
|
||||
IbkrMapping.BuildQuote("AAPL", last: 0, bid: 0, ask: 0, close: 0).Should().BeNull();
|
||||
|
||||
// ─── Ausführungen ─────────────────────────────────────────────────────────
|
||||
|
||||
[Theory]
|
||||
[InlineData("BOT", TradeSide.Buy)]
|
||||
[InlineData("SLD", TradeSide.Sell)]
|
||||
[InlineData("sld", TradeSide.Sell)]
|
||||
[InlineData("SELL", TradeSide.Sell)]
|
||||
public void ParseSide_MapsTwsExecutionSides(string raw, TradeSide expected) =>
|
||||
IbkrMapping.ParseSide(raw).Should().Be(expected);
|
||||
|
||||
[Fact]
|
||||
public void ParseExecutionTime_HandlesDoubleSpaceFormat()
|
||||
{
|
||||
// So liefert TWS es bei execDetails.
|
||||
IbkrMapping.ParseExecutionTime("20260804 17:39:18")
|
||||
.Should().Be(new DateTime(2026, 8, 4, 17, 39, 18));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseExecutionTime_IgnoresTrailingTimeZone()
|
||||
{
|
||||
IbkrMapping.ParseExecutionTime("20260804 17:52:56 Europe/Berlin")
|
||||
.Should().Be(new DateTime(2026, 8, 4, 17, 52, 56));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("")]
|
||||
[InlineData(" ")]
|
||||
[InlineData("20260804")]
|
||||
[InlineData("Unsinn")]
|
||||
public void ParseExecutionTime_ReturnsNullForUnusableInput(string raw) =>
|
||||
IbkrMapping.ParseExecutionTime(raw).Should().BeNull();
|
||||
|
||||
[Fact]
|
||||
public void FormatExecutionFilterTime_UsesUtcWithExplicitFormat()
|
||||
{
|
||||
// TWS warnt (2174) bei Zeitangaben ohne Zeitzone und entfernt das Format künftig.
|
||||
var since = new DateTime(2026, 8, 4, 12, 0, 0, DateTimeKind.Utc);
|
||||
|
||||
IbkrMapping.FormatExecutionFilterTime(since).Should().Be("20260804-12:00:00");
|
||||
}
|
||||
|
||||
// ─── Tick- und Status-Klassifizierung ─────────────────────────────────────
|
||||
|
||||
[Theory]
|
||||
|
||||
Reference in New Issue
Block a user