Logdateinamen haengen nicht mehr am Kalender der Host-Kultur
Im Logverzeichnis der Test-VM lagen neben 2026-09-03.jsonl auch 1405-06-12.jsonl und 2569-09-03.jsonl - dieselben Tage im Sonnen-Hidschra- und im buddhistischen Kalender. Eine interpolierte Zeichenkette formatiert mit CurrentCulture, und die bringt ihren Kalender mit. Betroffen waren beide Schreibpfade des LoggingService und die Lesestelle im DossierService, die denselben Namen wieder zusammensetzt. CA1305 greift hier nicht: die Regel sieht string.Format und ToString, nicht die interpolierte Form. Die .editorconfig deckt diesen Fall also nicht ab. FormattableString.Invariant an allen drei Stellen, dazu ein Regressionstest ueber fa-IR, th-TH und de-DE - mit Gegenbeweis, dass die naive Form tatsaechlich abweicht, damit der Test nicht eines Tages unbemerkt trivial wird. Ausserdem: Verweise auf die Bugtracker-Nummern richtiggestellt (488/489). 461 Tests gruen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -40,7 +40,7 @@ public interface ICapitalService
|
||||
/// <summary>
|
||||
/// Vermerkt, dass die Order beim Broker liegt – die Bindung bleibt bestehen.
|
||||
///
|
||||
/// <para><b>Der Gegenspieler von <see cref="CancelReservationAsync"/>.</b> Bis #489 gab es zu
|
||||
/// <para><b>Der Gegenspieler von <see cref="CancelReservationAsync"/>.</b> 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.</para>
|
||||
/// </summary>
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ public enum TradeDecision
|
||||
Failed, // versucht, aber fehlgeschlagen (z. B. Order-Fehler)
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
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)
|
||||
}
|
||||
|
||||
/// <summary>Art eines Order-Lifecycle-Ereignisses (als String persistiert – erweiterbar).</summary>
|
||||
|
||||
@@ -3,7 +3,7 @@ namespace IBKRTrader.Core.Trading;
|
||||
/// <summary>
|
||||
/// Wie eine Order beim Broker ausgegangen ist – <b>drei</b> Ausgänge, nicht zwei.
|
||||
///
|
||||
/// <para><b>Warum das der Kern von #489 ist.</b> Bis dahin kannte <see cref="OrderResult"/> nur
|
||||
/// <para><b>Warum das der Kern von #488 ist.</b> Bis dahin kannte <see cref="OrderResult"/> nur
|
||||
/// „erfolgreich" und „fehlgeschlagen". Damit ließ sich der häufigste Fall am Wochenende gar nicht
|
||||
/// ausdrücken: Die Order ist beim Broker <b>angenommen</b> und wartet auf die Börseneröffnung.
|
||||
/// Sie als Fehlschlag zu buchen, gibt das gebundene Kapital frei, während die Order weiterläuft –
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -353,7 +353,7 @@ internal static class IbkrMapping
|
||||
/// <summary>
|
||||
/// Meldungen, die zu einer Order gehören, sie aber <b>nicht</b> ablehnen.
|
||||
///
|
||||
/// <para><b>399 ist der Grund, aus dem es diese Liste gibt</b> (#489): IBKR schickt darunter
|
||||
/// <para><b>399 ist der Grund, aus dem es diese Liste gibt</b> (#488): IBKR schickt darunter
|
||||
/// Hinweise zu einer <i>angenommenen</i> 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,
|
||||
|
||||
@@ -18,7 +18,7 @@ namespace IBKRTrader.Core.Workers.BuiltIn;
|
||||
/// globaler Kaufstopp – <b>und niemals ein automatischer Verkauf zur Heilung</b>. 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.</item>
|
||||
/// <item>Die offenen Bindungen gegen das Orderbuch des Brokers halten (#489). Eine Bindung, zu der
|
||||
/// <item>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.</item>
|
||||
/// </list>
|
||||
|
||||
Reference in New Issue
Block a user