diff --git a/src/IBKRTrader.Core/Trading/ExecutionService.cs b/src/IBKRTrader.Core/Trading/ExecutionService.cs
index 05a9f55..8aa498a 100644
--- a/src/IBKRTrader.Core/Trading/ExecutionService.cs
+++ b/src/IBKRTrader.Core/Trading/ExecutionService.cs
@@ -129,15 +129,10 @@ public sealed class ExecutionService : IExecutionService
var reservationId = reservation.Reservation!.Id;
// 7. Order platzieren
- var order = new OrderRequest
- {
- Symbol = signal.Symbol,
- Side = signal.Side,
- Quantity = decision.Quantity,
- Type = orderType,
- LimitPrice = signal.LimitPrice,
- Option = signal.Option
- };
+ // Aus dem Instrument gebaut, nicht aus Einzelfeldern: sonst faellt die Waehrung still auf
+ // USD zurueck und die Order trifft eine andere Notierung als die, die geprueft wurde.
+ var order = OrderRequest.For(
+ signal.Instrument, signal.Side, decision.Quantity, orderType, signal.LimitPrice);
var result = await _broker.PlaceOrderAsync(order, ct);
if (!result.Success)
diff --git a/src/IBKRTrader.Core/Trading/TradingModels.cs b/src/IBKRTrader.Core/Trading/TradingModels.cs
index be97768..35f3eba 100644
--- a/src/IBKRTrader.Core/Trading/TradingModels.cs
+++ b/src/IBKRTrader.Core/Trading/TradingModels.cs
@@ -82,6 +82,31 @@ public sealed record OrderRequest
public InstrumentRef Instrument =>
Option is null ? InstrumentRef.ForStock(Symbol, Currency) : InstrumentRef.ForOption(Option);
+
+ ///
+ /// Baut den Auftrag aus dem Instrument – der einzige Weg, der die Identität vollständig
+ /// überträgt.
+ ///
+ /// Warum es diese Fabrik gibt. Wer die Felder einzeln setzt, kann die Währung
+ /// vergessen; sie fällt dann auf USD zurück. Am 2026-09-04 ist genau das passiert: geprüft und
+ /// reserviert wurde SAP:EUR, gehandelt wurde die SAP-ADR in New York – Kurs 188,74 EUR
+ /// gegen Ausführung zu 216,68 USD, und das USD-Guthaben rutschte um 434 ins Minus. Es gab keine
+ /// Fehlermeldung; die Bücher wiesen eine EUR-Position aus, die es beim Broker nie gab. Ein
+ /// Auftrag muss aus dem Instrument entstehen, sonst ist er ein zweites, halb ausgefülltes
+ /// Instrument.
+ ///
+ public static OrderRequest For(
+ InstrumentRef instrument, TradeSide side, int quantity, OrderType type, decimal? limitPrice = null) =>
+ new()
+ {
+ Symbol = instrument.Symbol,
+ Side = side,
+ Quantity = quantity,
+ Type = type,
+ LimitPrice = limitPrice,
+ Option = instrument.Option,
+ Currency = instrument.Currency
+ };
}
/// Ergebnis einer Order-Platzierung.
diff --git a/tests/IBKRTrader.Tests/Trading/ExecutionServiceTests.cs b/tests/IBKRTrader.Tests/Trading/ExecutionServiceTests.cs
index e5fb5da..c0db0ea 100644
--- a/tests/IBKRTrader.Tests/Trading/ExecutionServiceTests.cs
+++ b/tests/IBKRTrader.Tests/Trading/ExecutionServiceTests.cs
@@ -247,6 +247,42 @@ public class ExecutionServiceTests
await _capital.Received(1).SettleReservationAsync(1, 5, 500m, Arg.Any());
}
+ [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(o => o.Currency == "EUR" && o.Instrument.Key == "SAP:EUR"),
+ Arg.Any());
+ }
+
+ [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(o => o.Option == spec && o.Instrument.Kind == InstrumentKind.Option),
+ Arg.Any());
+ }
+
[Fact]
public async Task OrderFails_ReturnsError_AndDoesNotBook()
{