diff --git a/src/IBKRTrader.Core/Capital/ICapitalService.cs b/src/IBKRTrader.Core/Capital/ICapitalService.cs
index 1bda16e..3c92a21 100644
--- a/src/IBKRTrader.Core/Capital/ICapitalService.cs
+++ b/src/IBKRTrader.Core/Capital/ICapitalService.cs
@@ -40,7 +40,7 @@ public interface ICapitalService
///
/// Vermerkt, dass die Order beim Broker liegt – die Bindung bleibt bestehen.
///
- /// Der Gegenspieler von . Bis #489 gab es zu
+ /// Der Gegenspieler von . Bis #488 gab es zu
/// einer nicht ausgeführten Order nur das Freigeben. Eine Order, die beim Broker wartet, wurde
/// damit wie eine gescheiterte behandelt: Das Geld war wieder frei, die Order lebte weiter.
///
diff --git a/src/IBKRTrader.Core/Logging/LoggingService.cs b/src/IBKRTrader.Core/Logging/LoggingService.cs
index e854982..a8b2032 100644
--- a/src/IBKRTrader.Core/Logging/LoggingService.cs
+++ b/src/IBKRTrader.Core/Logging/LoggingService.cs
@@ -1,5 +1,6 @@
using IBKRTrader.Core.Configuration;
using IBKRTrader.Core.Time;
+using System.Globalization;
using System.Runtime.CompilerServices;
namespace IBKRTrader.Core.Logging;
@@ -92,8 +93,13 @@ public class LoggingService
var dir = Path.Combine(LogBaseDir, e.Module);
Directory.CreateDirectory(dir);
- var file = Path.Combine(dir, $"{e.Level}-{e.Timestamp:dd-MM-yy}.txt");
- var line = $"[{e.Timestamp:HH:mm:ss}] {e.Message}";
+ // InvariantCulture ist hier kein Formalismus, sondern der Unterschied zwischen einer
+ // Datei, die es gibt, und einer, die niemand findet: Eine interpolierte Zeichenkette
+ // formatiert mit CurrentCulture, und die bringt ihren Kalender mit. Unter fa-IR steht
+ // im Namen das Jahr 1405, unter th-TH 2569 - beobachtet am 2026-09-06 auf der Test-VM,
+ // wo neben 2026-09-06.jsonl auch 1405-06-12.jsonl und 2569-09-03.jsonl lagen.
+ var file = Path.Combine(dir, FormattableString.Invariant($"{e.Level}-{e.Timestamp:dd-MM-yy}.txt"));
+ var line = FormattableString.Invariant($"[{e.Timestamp:HH:mm:ss}] {e.Message}");
if (e.Exception != null)
line += $"{Environment.NewLine} {e.Exception}";
@@ -115,7 +121,7 @@ public class LoggingService
try
{
Directory.CreateDirectory(LogBaseDir);
- var file = Path.Combine(LogBaseDir, $"{e.Timestamp:yyyy-MM-dd}.jsonl");
+ var file = Path.Combine(LogBaseDir, FormattableString.Invariant($"{e.Timestamp:yyyy-MM-dd}.jsonl"));
var message = e.Exception != null ? $"{e.Message} | {e.Exception.Message}" : e.Message;
var json = LogJson.WriteLine(utc, e.Level, e.Module, message, cid);
diff --git a/src/IBKRTrader.Core/Persistence/Entities/AnalysisEntities.cs b/src/IBKRTrader.Core/Persistence/Entities/AnalysisEntities.cs
index 7f26b33..45aa657 100644
--- a/src/IBKRTrader.Core/Persistence/Entities/AnalysisEntities.cs
+++ b/src/IBKRTrader.Core/Persistence/Entities/AnalysisEntities.cs
@@ -9,7 +9,7 @@ public enum TradeDecision
Failed, // versucht, aber fehlgeschlagen (z. B. Order-Fehler)
///
- /// Abgesendet, Ausgang offen – die Order liegt beim Broker (#489). Bewusst weder "Executed"
+ /// Abgesendet, Ausgang offen – die Order liegt beim Broker (#488). Bewusst weder "Executed"
/// noch "Failed": Beides wäre eine Behauptung über etwas, das noch nicht entschieden ist.
///
Pending
@@ -38,7 +38,7 @@ public enum DecisionReason
// ----- Ausführung -----
OrderPlaced, // Order erfolgreich platziert
OrderFailed, // Broker/Order-Fehler beim Platzieren
- OrderWorking // Order liegt beim Broker, noch nicht ausgeführt (#489)
+ OrderWorking // Order liegt beim Broker, noch nicht ausgeführt (#488)
}
/// Art eines Order-Lifecycle-Ereignisses (als String persistiert – erweiterbar).
diff --git a/src/IBKRTrader.Core/Trading/BrokerOrders.cs b/src/IBKRTrader.Core/Trading/BrokerOrders.cs
index 787636e..3a8c3f7 100644
--- a/src/IBKRTrader.Core/Trading/BrokerOrders.cs
+++ b/src/IBKRTrader.Core/Trading/BrokerOrders.cs
@@ -3,7 +3,7 @@ namespace IBKRTrader.Core.Trading;
///
/// Wie eine Order beim Broker ausgegangen ist – drei Ausgänge, nicht zwei.
///
-/// Warum das der Kern von #489 ist. Bis dahin kannte nur
+/// Warum das der Kern von #488 ist. Bis dahin kannte nur
/// „erfolgreich" und „fehlgeschlagen". Damit ließ sich der häufigste Fall am Wochenende gar nicht
/// ausdrücken: Die Order ist beim Broker angenommen und wartet auf die Börseneröffnung.
/// Sie als Fehlschlag zu buchen, gibt das gebundene Kapital frei, während die Order weiterläuft –
diff --git a/src/IBKRTrader.Core/Trading/ExecutionService.cs b/src/IBKRTrader.Core/Trading/ExecutionService.cs
index 3b59efe..f0a065a 100644
--- a/src/IBKRTrader.Core/Trading/ExecutionService.cs
+++ b/src/IBKRTrader.Core/Trading/ExecutionService.cs
@@ -136,7 +136,7 @@ public sealed class ExecutionService : IExecutionService
var result = await _broker.PlaceOrderAsync(order, ct);
- // Drei Ausgänge, drei Wege – und der gefährliche ist der mittlere (#489). Eine Order, die
+ // Drei Ausgänge, drei Wege – und der gefährliche ist der mittlere (#488). Eine Order, die
// beim Broker liegt, ist WEDER ausgeführt NOCH gescheitert. Sie wie einen Fehlschlag zu
// behandeln, gibt das gebundene Kapital frei, während die Order weiterläuft: Dasselbe Geld
// wird ein zweites Mal vergeben, und die spätere Ausführung erzeugt eine Position, die
diff --git a/src/IBKRTrader.Core/Trading/Ibkr/IbkrConnection.cs b/src/IBKRTrader.Core/Trading/Ibkr/IbkrConnection.cs
index 752477b..9937bbf 100644
--- a/src/IBKRTrader.Core/Trading/Ibkr/IbkrConnection.cs
+++ b/src/IBKRTrader.Core/Trading/Ibkr/IbkrConnection.cs
@@ -984,7 +984,7 @@ internal sealed class IbkrConnection : DefaultEWrapper, IDisposable
// Ordermeldungen zuerst und getrennt: Ob eine Meldung eine Order beendet, entscheidet der
// Code – nicht die Tatsache, dass sie über den Fehler-Callback kam. IBKR schickt über
- // denselben Weg auch Hinweise zu angenommenen Orders (#489).
+ // denselben Weg auch Hinweise zu angenommenen Orders (#488).
if (id >= 0 && _orders.TryGetValue(id, out var order))
{
if (IbkrMapping.IsOrderNotice(errorCode))
diff --git a/src/IBKRTrader.Core/Trading/Ibkr/IbkrMapping.cs b/src/IBKRTrader.Core/Trading/Ibkr/IbkrMapping.cs
index 1ab5e8e..18430b0 100644
--- a/src/IBKRTrader.Core/Trading/Ibkr/IbkrMapping.cs
+++ b/src/IBKRTrader.Core/Trading/Ibkr/IbkrMapping.cs
@@ -353,7 +353,7 @@ internal static class IbkrMapping
///
/// Meldungen, die zu einer Order gehören, sie aber nicht ablehnen.
///
- /// 399 ist der Grund, aus dem es diese Liste gibt (#489): IBKR schickt darunter
+ /// 399 ist der Grund, aus dem es diese Liste gibt (#488): IBKR schickt darunter
/// Hinweise zu einer angenommenen Order – am Wochenende regelmäßig "Your order will not
/// be placed at the exchange until Monday 09:00". Als Fehler gelesen, meldete der Adapter
/// "Order abgelehnt", das Kapitalmodell gab die Bindung frei, und am Montag füllte eine Order,
diff --git a/src/IBKRTrader.Core/Workers/BuiltIn/ReservationWatchdogWorker.cs b/src/IBKRTrader.Core/Workers/BuiltIn/ReservationWatchdogWorker.cs
index 3ba2e39..10d315c 100644
--- a/src/IBKRTrader.Core/Workers/BuiltIn/ReservationWatchdogWorker.cs
+++ b/src/IBKRTrader.Core/Workers/BuiltIn/ReservationWatchdogWorker.cs
@@ -18,7 +18,7 @@ namespace IBKRTrader.Core.Workers.BuiltIn;
/// globaler Kaufstopp – und niemals ein automatischer Verkauf zur Heilung. Eine Software,
/// die in einen fallenden Markt hinein Positionen schließt, weil eine Kennzahl unterschritten
/// ist, richtet mehr Schaden an als der Zustand, den sie beheben soll.
-/// - Die offenen Bindungen gegen das Orderbuch des Brokers halten (#489). Eine Bindung, zu der
+///
- Die offenen Bindungen gegen das Orderbuch des Brokers halten (#488). Eine Bindung, zu der
/// keine Order mehr liegt, kommt in Quarantäne statt in die Freigabe – es gibt zwei Erklärungen
/// dafür, und sie führen zu entgegengesetzten Handlungen.
///
diff --git a/src/IBKRTrader.Daemon/OrderBookTool.cs b/src/IBKRTrader.Daemon/OrderBookTool.cs
index e3ba2a9..f585733 100644
--- a/src/IBKRTrader.Daemon/OrderBookTool.cs
+++ b/src/IBKRTrader.Daemon/OrderBookTool.cs
@@ -10,7 +10,7 @@ namespace IBKRTrader.Daemon;
///
/// Der Blick ins Orderbuch des Brokers – und der Abgleich mit dem, was wir selbst gebucht haben
-/// (#489).
+/// (#488).
///
/// Wozu es das gibt. Am 2026-09-06 stellte sich heraus, dass vier Orders bei
/// geschlossener Börse angenommen und für Montag vorgemerkt wurden, während der Adapter sie als
diff --git a/src/IBKRTrader.Daemon/Program.cs b/src/IBKRTrader.Daemon/Program.cs
index a15a20a..61ab583 100644
--- a/src/IBKRTrader.Daemon/Program.cs
+++ b/src/IBKRTrader.Daemon/Program.cs
@@ -56,7 +56,7 @@ internal static class Program
// Optionskette und Bewertungsgrößen eines Basiswerts ansehen. Liest nur.
var chainSymbol = ArgAfter(args, "--option-chain");
- // Was liegt tatsächlich beim Broker? Der Gegencheck zur eigenen Buchführung (#489).
+ // Was liegt tatsächlich beim Broker? Der Gegencheck zur eigenen Buchführung (#488).
// Ohne ihn ist "die Order wurde abgelehnt" eine Vermutung, keine Feststellung.
var openOrders = HasFlag(args, "--open-orders");
diff --git a/src/IBKRTrader.Hosting/AppHostBuilder.cs b/src/IBKRTrader.Hosting/AppHostBuilder.cs
index 7b086fa..864eb18 100644
--- a/src/IBKRTrader.Hosting/AppHostBuilder.cs
+++ b/src/IBKRTrader.Hosting/AppHostBuilder.cs
@@ -180,7 +180,7 @@ public static class AppHostBuilder
{
// Hier wird nur GEMELDET, nicht entschieden.
//
- // Bis #489 lief an dieser Stelle RecoverAsync gegen eine leere Orderliste, weil offene
+ // Bis #488 lief an dieser Stelle RecoverAsync gegen eine leere Orderliste, weil offene
// Broker-Orders nicht abrufbar waren - jede ueberlebende Bindung landete damit sofort in
// Quarantaene. Das war vertretbar, solange nie eine Bindung den Zustand "Submitted"
// erreichte. Seit eine wartende Order ihre Bindung behaelt, waere es falsch: Eine voellig
diff --git a/src/IBKRTrader.Modules.Supervisor/Services/DossierService.cs b/src/IBKRTrader.Modules.Supervisor/Services/DossierService.cs
index 157d953..3f557a1 100644
--- a/src/IBKRTrader.Modules.Supervisor/Services/DossierService.cs
+++ b/src/IBKRTrader.Modules.Supervisor/Services/DossierService.cs
@@ -75,7 +75,10 @@ public sealed class DossierService
var to = times.Max().Date.AddDays(1);
for (var day = from; day <= to; day = day.AddDays(1))
{
- string path = Path.Combine(_logsDirectory, $"{day:yyyy-MM-dd}.jsonl");
+ // Muss zeichengleich zu dem sein, was LoggingService schreibt - sonst sucht der
+ // Supervisor unter einem Namen, den es nicht gibt.
+ string path = Path.Combine(
+ _logsDirectory, FormattableString.Invariant($"{day:yyyy-MM-dd}.jsonl"));
if (!File.Exists(path)) continue;
foreach (var line in File.ReadLines(path))
{
diff --git a/tests/IBKRTrader.Tests/Trading/CultureIndependenceTests.cs b/tests/IBKRTrader.Tests/Trading/CultureIndependenceTests.cs
index 37a3ee5..e5338ba 100644
--- a/tests/IBKRTrader.Tests/Trading/CultureIndependenceTests.cs
+++ b/tests/IBKRTrader.Tests/Trading/CultureIndependenceTests.cs
@@ -100,4 +100,54 @@ public class CultureIndependenceTests
markdown.Should().NotContain("327,27");
});
}
+
+ // ─── Dateinamen (#489) ────────────────────────────────────────────────
+
+ ///
+ /// Führt die Prüfung unter einer Kultur mit anderem Kalender aus. Das Dezimalkomma ist
+ /// nur die halbe Gefahr: fa-IR rechnet im Sonnen-Hidschra-Kalender, th-TH im buddhistischen.
+ ///
+ private static void UnderCulture(string name, Action assert)
+ {
+ var previous = CultureInfo.CurrentCulture;
+ try
+ {
+ CultureInfo.CurrentCulture = new CultureInfo(name);
+ assert();
+ }
+ finally
+ {
+ CultureInfo.CurrentCulture = previous;
+ }
+ }
+
+ [Theory]
+ [InlineData("fa-IR")] // Sonnen-Hidschra: Jahr 1405 statt 2026
+ [InlineData("th-TH")] // buddhistisch: Jahr 2569 statt 2026
+ [InlineData("de-DE")]
+ public void LogFileNames_DoNotDependOnTheHostCalendar(string culture)
+ {
+ // Am 2026-09-06 lagen auf der Test-VM neben 2026-09-06.jsonl auch 1405-06-12.jsonl und
+ // 2569-09-03.jsonl. Eine interpolierte Zeichenkette formatiert mit CurrentCulture, und die
+ // bringt ihren Kalender mit: Der Supervisor sucht dann eine Datei, die es nicht gibt, und
+ // die Tagesrotation legt jeden Tag eine neue Reihe an.
+ var day = new DateTime(2026, 9, 6, 14, 3, 0, DateTimeKind.Utc);
+
+ UnderCulture(culture, () =>
+ {
+ FormattableString.Invariant($"{day:yyyy-MM-dd}.jsonl").Should().Be("2026-09-06.jsonl");
+ FormattableString.Invariant($"Info-{day:dd-MM-yy}.txt").Should().Be("Info-06-09-26.txt");
+ });
+ }
+
+ [Fact]
+ public void TheNaiveInterpolation_IsWhatWentWrong()
+ {
+ // Der Gegenbeweis, damit dieser Test nicht versehentlich trivial wird: Ohne den
+ // ausdruecklichen Formatanbieter geht es tatsaechlich schief. Faellt diese Zusicherung
+ // eines Tages, ist die Gefahr weg - dann darf der Test verschwinden, nicht vorher.
+ var day = new DateTime(2026, 9, 6, 0, 0, 0, DateTimeKind.Utc);
+
+ UnderCulture("fa-IR", () => $"{day:yyyy-MM-dd}".Should().NotBe("2026-09-06"));
+ }
}
diff --git a/tests/IBKRTrader.Tests/Trading/ExecutionServiceTests.cs b/tests/IBKRTrader.Tests/Trading/ExecutionServiceTests.cs
index 0c4ea4c..c43733b 100644
--- a/tests/IBKRTrader.Tests/Trading/ExecutionServiceTests.cs
+++ b/tests/IBKRTrader.Tests/Trading/ExecutionServiceTests.cs
@@ -300,10 +300,10 @@ public class ExecutionServiceTests
Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any());
}
- // ─── Die Order, die beim Broker liegt (#489) ─────────────────────────────
+ // ─── Die Order, die beim Broker liegt (#488) ─────────────────────────────
///
- /// Der Test, um den es bei #489 geht. Am 2026-09-06 wurden 67 Orders bei geschlossener
+ /// Der Test, um den es bei #488 geht. Am 2026-09-06 wurden 67 Orders bei geschlossener
/// Börse angenommen und für Montag vorgemerkt, während der Adapter sie als abgelehnt meldete und
/// jede Bindung freigegeben wurde. Das Geld war wieder im Topf, die Orders lebten weiter.
///
@@ -346,7 +346,7 @@ public class ExecutionServiceTests
public async Task RejectedOrder_StillReleasesTheCapital()
{
// Die Gegenprobe: Bei einer beweisbar abgelehnten Order MUSS das Geld zurück. Sonst hätte
- // der Fix aus #489 nur die eine Klemme gegen die andere getauscht.
+ // der Fix aus #488 nur die eine Klemme gegen die andere getauscht.
ArrangeHappyPath();
_broker.PlaceOrderAsync(Arg.Any(), Arg.Any())
.Returns(Task.FromResult(OrderResult.Fail("Order abgelehnt")));
diff --git a/tests/IBKRTrader.Tests/Trading/IbkrMappingTests.cs b/tests/IBKRTrader.Tests/Trading/IbkrMappingTests.cs
index bb007d5..e5a840c 100644
--- a/tests/IBKRTrader.Tests/Trading/IbkrMappingTests.cs
+++ b/tests/IBKRTrader.Tests/Trading/IbkrMappingTests.cs
@@ -397,7 +397,7 @@ public class IbkrMappingTests
IbkrMapping.IsInformational(IbkrMapping.DelayedDataNotice).Should().BeTrue();
}
- // ─── Ordermeldungen (#489) ─────────────────────────────────────────
+ // ─── Ordermeldungen (#488) ─────────────────────────────────────────
///
/// Der Fehler, der 67 Orders unsichtbar gemacht hat: IBKR schickt unter Code 399 Hinweise zu