using System.Globalization; using FluentAssertions; using IBKRTrader.Core.Analytics; using IBKRTrader.Core.Persistence.Entities; using IBKRTrader.Core.Trading; namespace IBKRTrader.Tests.Trading; /// /// Zahlen und Zeitpunkte, die in Logs, Dossiers oder in die Datenbank gehen, dürfen nicht an der /// Kultur des Hosts hängen. Wir betreiben Instanzen in EU und US und vergleichen deren Ausgaben /// miteinander – "373,67" auf der einen und "373.67" auf der anderen wäre dasselbe Ereignis in /// zwei Schreibweisen. /// /// Die Tests setzen bewusst eine Kultur mit Dezimalkomma und prüfen, dass sich davon nichts /// beeindrucken lässt. Der laufende Prozess stellt zusätzlich die invariante Kultur ein /// (AppHostBuilder), aber genau darauf soll sich der einzelne Codepfad nicht verlassen. /// [Trait("cat", "unit")] public class CultureIndependenceTests { /// Führt die Prüfung unter einer Kultur mit Dezimalkomma aus. private static void UnderGermanCulture(Action assert) { var previous = CultureInfo.CurrentCulture; try { CultureInfo.CurrentCulture = new CultureInfo("de-DE"); assert(); } finally { CultureInfo.CurrentCulture = previous; } } private static BrokerPosition Position() => new() { Symbol = "GOOG", SecType = "STK", Currency = "USD", ConId = 208813720, Quantity = 10, AvgCost = 373.67m }; [Fact] public void BrokerPosition_ToString_UsesInvariantNumbers() { UnderGermanCulture(() => Position().ToString().Should().Be("GOOG (STK) 10 @ 373.67 USD")); } [Fact] public void BrokerExecution_ToString_UsesInvariantNumbers() { var execution = new BrokerExecution { ExecId = "0001", Time = new DateTime(2026, 9, 3, 18, 26, 13, DateTimeKind.Utc), Symbol = "AAPL", SecType = "STK", Side = TradeSide.Buy, Quantity = 5, Price = 327.27m }; UnderGermanCulture(() => execution.ToString().Should().Be("2026-09-03 18:26:13 Buy 5x AAPL @ 327.27")); } [Fact] public void Dossier_WritesInvariantNumbers() { var dossier = new TradeDossier( SignalId: "sig-1", Decisions: [], OrderEvents: [], Trades: [ new CoreTrade { Module = "CT", Symbol = "AAPL", Action = "BUY", Quantity = 3, Price = 327.27m, TotalValue = 981.81m, Status = "Filled", TradedAt = new DateTime(2026, 9, 3, 18, 26, 13, DateTimeKind.Utc) } ], LogLines: []); UnderGermanCulture(() => { var markdown = DossierBuilder.ToMarkdown(dossier); markdown.Should().Contain("327.27").And.Contain("981.81"); markdown.Should().NotContain("327,27"); }); } }