IBKR: Zeitkontext einer Verbindung nachvollziehbar machen

ResolveExecutionTime liefert neben dem UTC-Zeitpunkt jetzt die Herkunft der
verwendeten Zeitzone (gemeldet / angenommen / unbekannt / unlesbar). Bisher war
im Nachhinein nicht unterscheidbar, ob ein Buchungszeitpunkt von TWS stammte
oder eine Annahme war - genau der Fehler, der beim Umzug zwischen EU- und
US-Host lautlos entsteht.

IbkrConnection schreibt beim Verbinden einmalig Betriebszeitzone, Systemzeitzone
und den Versatz zur TWS-Serverzeit ins Log; ab 5 s Abweichung gilt die Uhr des
Hosts als verstellt.

TWS-Setup-Checkliste um den Linux-Abschnitt ergaenzt (Betrieb und Umgebung
unterscheiden sich, das Protokoll nicht).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Richard
2026-08-22 10:44:41 +02:00
co-authored by Claude Opus 5
parent c176b05ea1
commit 87194bfc48
4 changed files with 342 additions and 12 deletions
@@ -165,6 +165,71 @@ public class IbkrMappingTests
private static readonly TimeZoneInfo Berlin = TimeZoneInfo.FindSystemTimeZoneById("Europe/Berlin");
private static readonly TimeZoneInfo NewYork = TimeZoneInfo.FindSystemTimeZoneById("America/New_York");
// ─── Herkunft der Zeitangabe (Beobachtbarkeit) ────────────────────────────
//
// Ohne die Herkunft lässt sich später nicht mehr unterscheiden, ob ein Buchungszeitpunkt von
// TWS gemeldet oder von uns angenommen wurde. Genau daran hängt die Diagnose, wenn eine
// Instanz von einem EU- auf einen US-Host umzieht.
[Fact]
public void ResolveExecutionTime_MitZone_MeldetGemeldeteHerkunft()
{
var stamp = IbkrMapping.ResolveExecutionTime("20260804 09:30:00 America/New_York", Berlin);
stamp.Source.Should().Be(IbkrMapping.ExecutionTimeSource.ReportedZone);
stamp.IsAssumed.Should().BeFalse();
stamp.ReportedZone.Should().Be("America/New_York");
stamp.ZoneUsed.Should().Contain("New_York");
stamp.Utc.Should().Be(new DateTime(2026, 8, 4, 13, 30, 0, DateTimeKind.Utc));
}
[Fact]
public void ResolveExecutionTime_OhneZone_MeldetAnnahme()
{
// Der Normalfall bei execDetails - und damit die Stelle, die beobachtet werden muss.
var stamp = IbkrMapping.ResolveExecutionTime("20260804 17:39:18", Berlin);
stamp.Source.Should().Be(IbkrMapping.ExecutionTimeSource.FallbackZone);
stamp.IsAssumed.Should().BeTrue();
stamp.ReportedZone.Should().BeNull();
stamp.ZoneUsed.Should().Be(Berlin.Id);
}
[Fact]
public void ResolveExecutionTime_UnbekannteZone_IstAlsAnnahmeErkennbar()
{
// Weicht auf die Betriebszeitzone aus - aber unterscheidbar von "TWS meldete nichts",
// denn hier hat TWS etwas gemeldet, das dieses System nicht kennt.
var stamp = IbkrMapping.ResolveExecutionTime("20260804 17:39:18 Gibt/EsNicht", Berlin);
stamp.Source.Should().Be(IbkrMapping.ExecutionTimeSource.UnknownZoneFallback);
stamp.IsAssumed.Should().BeTrue();
stamp.ReportedZone.Should().Be("Gibt/EsNicht");
stamp.ZoneUsed.Should().Be(Berlin.Id);
}
[Fact]
public void ResolveExecutionTime_Unlesbar_IstAlsDefektErkennbar()
{
var stamp = IbkrMapping.ResolveExecutionTime("Unsinn", Berlin);
stamp.Source.Should().Be(IbkrMapping.ExecutionTimeSource.Unparsable);
stamp.Utc.Should().BeNull();
// Unlesbar ist keine Annahme, sondern ein Defekt - die Warnung im Log ist eine andere.
stamp.IsAssumed.Should().BeFalse();
}
[Fact]
public void ResolveExecutionTime_UsHost_LiefertDenselbenZeitpunktWieEuHost()
{
// Der eigentliche Regressionsschutz fuer den geplanten Umzug: Meldet TWS die Zone, darf die
// Betriebszeitzone der Instanz das Ergebnis NICHT mehr veraendern.
const string raw = "20260804 09:30:00 America/New_York";
IbkrMapping.ResolveExecutionTime(raw, Berlin).Utc
.Should().Be(IbkrMapping.ResolveExecutionTime(raw, NewYork).Utc);
}
[Fact]
public void ParseExecutionTime_OhneZone_RechnetGegenDieBetriebszeitzone()
{