OrderRequest entsteht aus dem Instrument, nicht aus Einzelfeldern

Beim ersten erfolgreichen Order-Nachweis (#293) gegen das Paper-Konto
lief die Order auf die falsche Notierung: geprueft, bepreist und
reserviert wurde SAP:EUR (XETRA, 188,74 EUR), ausgefuehrt wurde die
SAP-ADR in New York zu 216,68 USD. Das USD-Guthaben rutschte dadurch von
-3.751 auf -4.185 - also genau der Margin-Kredit, den Schicht C
verhindern soll, aufgenommen an der Sicherung vorbei.

Ursache: ExecutionService baute die OrderRequest aus Einzelfeldern und
setzte Currency nicht; die Eigenschaft fiel still auf USD zurueck. Es
gab keine Fehlermeldung, und die Buecher wiesen anschliessend eine
EUR-Position aus, die es beim Broker nie gab.

Behebung nicht durch Nachtragen der Zuweisung, sondern baulich:
OrderRequest.For(instrument, ...) uebertraegt die Identitaet
vollstaendig. Wer die Felder einzeln setzt, baut ein zweites, halb
ausgefuelltes Instrument - das darf im Orderpfad nicht mehr moeglich
sein.

Zwei Regressionstests, die genau diesen Fall fangen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Richard
2026-09-04 15:44:08 +02:00
co-authored by Claude Opus 5
parent 9e0d3b229a
commit b517f7a637
3 changed files with 65 additions and 9 deletions
@@ -247,6 +247,42 @@ public class ExecutionServiceTests
await _capital.Received(1).SettleReservationAsync(1, 5, 500m, Arg.Any<CancellationToken>());
}
[Fact]
public async Task TheOrderCarriesTheSignalsCurrency_NotTheDefault()
{
// Am 2026-09-04 gegen das Paper-Konto beobachtet: geprueft und reserviert wurde SAP:EUR,
// gehandelt wurde die SAP-ADR in New York, weil die OrderRequest aus Einzelfeldern gebaut
// wurde und die Waehrung dabei auf USD zurueckfiel. Ohne Fehlermeldung.
ArrangeHappyPath();
var signal = new TradeSignal
{
Symbol = "SAP", Side = TradeSide.Buy, SourceModule = "CT", Currency = "EUR"
};
await CreateSut().ExecuteAsync(signal);
await _broker.Received(1).PlaceOrderAsync(
Arg.Is<OrderRequest>(o => o.Currency == "EUR" && o.Instrument.Key == "SAP:EUR"),
Arg.Any<CancellationToken>());
}
[Fact]
public async Task TheOrderCarriesTheOptionContract()
{
ArrangeHappyPath();
var spec = new OptionSpec("AAPL", new DateOnly(2026, 9, 18), 150m, OptionRight.Put);
var signal = new TradeSignal
{
Symbol = "AAPL", Side = TradeSide.Sell, SourceModule = "CT", Option = spec
};
await CreateSut().ExecuteAsync(signal);
await _broker.Received(1).PlaceOrderAsync(
Arg.Is<OrderRequest>(o => o.Option == spec && o.Instrument.Kind == InstrumentKind.Option),
Arg.Any<CancellationToken>());
}
[Fact]
public async Task OrderFails_ReturnsError_AndDoesNotBook()
{