Instrumente sind waehrungsfaehig

Die Waehrung gehoert zur Identitaet eines Instruments und nicht zu
seiner Beschreibung: derselbe Ticker kann an mehreren Plaetzen in
verschiedenen Waehrungen notieren, und ein Kauf darf nur aus Bargeld
derselben Waehrung finanziert werden (Kapitalmodell §4 Schicht C). Zwei
Notierungen als eine Position zu fuehren, wuerde Bestand und Deckung
vermischen.

USD bleibt im Instrumentenschluessel IMPLIZIT ("AAPL", nicht
"AAPL:USD"). Andernfalls waere jede Bestandszeile ab sofort ein anderes
Instrument als vorher und aus einer Position wuerden zwei. Trennzeichen
ist der Doppelpunkt und nicht der Punkt - Ticker enthalten Punkte
(BRK.B).

Neu: Currencies (kanonische Schreibweise an allen vier Stellen, die sie
gleich haben muessen), Currency an OptionSpec, InstrumentRef,
TradeSignal, OrderRequest und core_position. CapitalService nimmt die
Waehrung ab jetzt aus dem Instrument statt vom Aufrufer - wer sie
uebergeben koennte, koennte die Deckungspruefung gegen den falschen Topf
laufen lassen. --verify-order kennt --currency.

Anlass: das Paper-Konto haelt EUR 91.832 und USD -3.751. Ein
USD-Nachweis waere ein USD-Kredit gewesen und wurde von Schicht C
zu Recht abgelehnt.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Richard
2026-09-04 15:39:30 +02:00
co-authored by Claude Opus 5
parent bd04642735
commit 2a73172e27
16 changed files with 873 additions and 33 deletions
+21 -8
View File
@@ -28,14 +28,17 @@ internal static class VerifyOrder
/// <summary>Nominalwert der Prüforder. Klein genug, dass ein Fehlschlag nichts kostet.</summary>
private const decimal Notional = 500m;
public static async Task<int> RunAsync(IServiceProvider services, string symbol, bool confirm)
public static async Task<int> RunAsync(
IServiceProvider services, string symbol, string currency, bool confirm)
{
var inv = CultureInfo.InvariantCulture;
var settings = services.GetRequiredService<SettingsService>().Settings;
var trading = settings.Trading;
var ibkr = settings.IBKR;
Console.WriteLine($"Order-Nachweis (#293) für {symbol}: {ibkr.Host}:{ibkr.Port}, " +
var instrument = InstrumentRef.ForStock(symbol, currency);
Console.WriteLine($"Order-Nachweis (#293) für {instrument}: {ibkr.Host}:{ibkr.Port}, " +
$"Modus {trading.Mode}, Client-ID {ibkr.ClientId}.");
// ── Sicherungen ──────────────────────────────────────────────────────────
@@ -81,6 +84,8 @@ internal static class VerifyOrder
return 1;
}
// Die Kursabfrage laeuft weiterhin ueber das Symbol; der Adapter loest den Kontrakt
// anhand des Instruments samt Waehrung auf.
var quote = await broker.GetQuoteAsync(symbol, cts.Token).ConfigureAwait(false);
if (quote is null || quote.Last <= 0)
{
@@ -107,14 +112,21 @@ internal static class VerifyOrder
var headroom = await capital.GetHeadroomAsync(book.Value, account, cts.Token).ConfigureAwait(false);
// Der haeufigste Grund fuer eine Ablehnung durch Schicht C ist nicht "kein Geld", sondern
// "Geld in der falschen Waehrung". Deshalb steht die Aufschluesselung hier, bevor
// irgendetwas passiert.
var cashHere = account.CashIn(instrument.Currency);
Console.WriteLine($"Bargeld: {instrument.Currency} " +
(cashHere is { } c ? c.ToString("N2", inv) : "keine Auskunft"));
Console.WriteLine($"Konto: NetLiquidation {account.NetLiquidation.ToString("N2", inv)}, " +
$"Bargeld {account.TotalCashValue.ToString("N2", inv)}");
Console.WriteLine($"Kurs: {quote}");
Console.WriteLine($"Buch: {book} – Cap {headroom.Cap.ToString("N0", inv)}, " +
$"gebunden {headroom.Bound.ToString("N0", inv)}, " +
$"frei {headroom.Available.ToString("N0", inv)}");
Console.WriteLine($"Vorhaben: KAUF {quantity} x {symbol} zum Marktpreis " +
$"(~{(quantity * quote.Last).ToString("N2", inv)} USD).");
Console.WriteLine($"Vorhaben: KAUF {quantity} x {instrument} zum Marktpreis " +
$"(~{(quantity * quote.Last).ToString("N2", inv)} {instrument.Currency}).");
if (!confirm)
{
@@ -134,7 +146,8 @@ internal static class VerifyOrder
Side = TradeSide.Buy,
SourceModule = BookId.Diagnostics.Value,
Reason = "Verifikation der Orderstrecke (#293)",
SuggestedNotional = Notional
SuggestedNotional = Notional,
Currency = instrument.Currency
};
var result = await execution.ExecuteAsync(signal, cts.Token).ConfigureAwait(false);
@@ -161,9 +174,9 @@ internal static class VerifyOrder
$"frei {afterHeadroom.Available.ToString("N0", inv)}");
Console.WriteLine();
Console.WriteLine("=== Order-Nachweis OK ===");
Console.WriteLine($"Zu prüfen bleibt von Hand: core_trade_history, core_position (Buch {book}), " +
$"core_reservation (Zustand Filled) und core_order_events – " +
$"Signal-ID {signal.SignalId}.");
Console.WriteLine($"Zu prüfen bleibt von Hand: core_trade_history, core_position (Buch {book}, " +
$"Instrument {instrument}), core_reservation (Zustand Filled) und " +
$"core_order_events – Signal-ID {signal.SignalId}.");
return 0;
}
}