diff --git a/src/IBKRTrader.Core/Trading/ExecutionService.cs b/src/IBKRTrader.Core/Trading/ExecutionService.cs
index 2022151..05a9f55 100644
--- a/src/IBKRTrader.Core/Trading/ExecutionService.cs
+++ b/src/IBKRTrader.Core/Trading/ExecutionService.cs
@@ -60,7 +60,7 @@ public sealed class ExecutionService : IExecutionService
}
// 2. Kurs
- var quote = await _broker.GetQuoteAsync(signal.Symbol, ct);
+ var quote = await _broker.GetQuoteAsync(signal.Instrument, ct);
if (quote is null || quote.Last <= 0)
{
Journal(signal, TradeDecision.Skipped, DecisionReason.NoQuote, $"Kein Kurs für {signal.Symbol} verfügbar.");
diff --git a/src/IBKRTrader.Core/Trading/IBrokerClient.cs b/src/IBKRTrader.Core/Trading/IBrokerClient.cs
index 982a5c6..8e784db 100644
--- a/src/IBKRTrader.Core/Trading/IBrokerClient.cs
+++ b/src/IBKRTrader.Core/Trading/IBrokerClient.cs
@@ -6,8 +6,14 @@ namespace IBKRTrader.Core.Trading;
///
public interface IBrokerClient
{
- /// Aktuelle Kurs-Momentaufnahme oder null, wenn nicht verfügbar.
- Task GetQuoteAsync(string symbol, CancellationToken ct = default);
+ ///
+ /// Aktuelle Kurs-Momentaufnahme oder null, wenn nicht verfügbar.
+ ///
+ /// Nimmt das Instrument und nicht nur das Symbol: ohne Währung wird jeder Kontrakt
+ /// als USD gesucht, und ein Wert, der nur in EUR notiert, ist dann schlicht "nicht auflösbar".
+ /// Ohne Kontraktangaben gälte dasselbe für jede Option.
+ ///
+ Task GetQuoteAsync(InstrumentRef instrument, CancellationToken ct = default);
/// Kontostand-Momentaufnahme.
Task GetAccountStateAsync(CancellationToken ct = default);
diff --git a/src/IBKRTrader.Core/Trading/Ibkr/IbkrBrokerClient.cs b/src/IBKRTrader.Core/Trading/Ibkr/IbkrBrokerClient.cs
index c6f231d..ba385e2 100644
--- a/src/IBKRTrader.Core/Trading/Ibkr/IbkrBrokerClient.cs
+++ b/src/IBKRTrader.Core/Trading/Ibkr/IbkrBrokerClient.cs
@@ -53,14 +53,14 @@ public sealed class IbkrBrokerClient : IBrokerClient, IBrokerPortfolioReader, ID
$"TWS-Broker aktiv: {ibkr.Host}:{ibkr.Port} (Client {ibkr.ClientId}), Modus {mode}.");
}
- public async Task GetQuoteAsync(string symbol, CancellationToken ct = default)
+ public async Task GetQuoteAsync(InstrumentRef instrument, CancellationToken ct = default)
{
if (!await IsReadyAsync(ct).ConfigureAwait(false)) return null;
- var contract = await _connection!.ResolveContractAsync(InstrumentRef.ForStock(symbol), _requestTimeout, ct).ConfigureAwait(false);
+ var contract = await _connection!.ResolveContractAsync(instrument, _requestTimeout, ct).ConfigureAwait(false);
if (contract is null) return null;
- return await _connection.RequestQuoteAsync(contract, symbol, _requestTimeout, ct).ConfigureAwait(false);
+ return await _connection.RequestQuoteAsync(contract, instrument.Symbol, _requestTimeout, ct).ConfigureAwait(false);
}
public async Task GetAccountStateAsync(CancellationToken ct = default)
diff --git a/src/IBKRTrader.Core/Trading/NullBrokerClient.cs b/src/IBKRTrader.Core/Trading/NullBrokerClient.cs
index 8709084..b7d4e5b 100644
--- a/src/IBKRTrader.Core/Trading/NullBrokerClient.cs
+++ b/src/IBKRTrader.Core/Trading/NullBrokerClient.cs
@@ -19,7 +19,7 @@ public sealed class NullBrokerClient : IBrokerClient, IBrokerPortfolioReader
public Task> GetExecutionsAsync(DateTime? since = null, CancellationToken ct = default)
=> Task.FromResult>(Array.Empty());
- public Task GetQuoteAsync(string symbol, CancellationToken ct = default)
+ public Task GetQuoteAsync(InstrumentRef instrument, CancellationToken ct = default)
=> Task.FromResult(null);
public Task GetAccountStateAsync(CancellationToken ct = default)
diff --git a/src/IBKRTrader.Daemon/Program.cs b/src/IBKRTrader.Daemon/Program.cs
index af308e1..4aeb0d1 100644
--- a/src/IBKRTrader.Daemon/Program.cs
+++ b/src/IBKRTrader.Daemon/Program.cs
@@ -182,7 +182,7 @@ internal static class Program
foreach (var p in positions.Take(10))
Console.WriteLine($" {p}");
- var quote = await broker.GetQuoteAsync(symbol, cts.Token).ConfigureAwait(false);
+ var quote = await broker.GetQuoteAsync(InstrumentRef.ForStock(symbol), cts.Token).ConfigureAwait(false);
Console.WriteLine(quote is null
? $"Kurs {symbol}: keiner geliefert (ohne Marktdaten-Abo bei geschlossener Börse normal)"
: $"Kurs {symbol}: {quote}");
diff --git a/src/IBKRTrader.Daemon/VerifyOrder.cs b/src/IBKRTrader.Daemon/VerifyOrder.cs
index 9eac446..324503f 100644
--- a/src/IBKRTrader.Daemon/VerifyOrder.cs
+++ b/src/IBKRTrader.Daemon/VerifyOrder.cs
@@ -84,13 +84,12 @@ 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);
+ var quote = await broker.GetQuoteAsync(instrument, cts.Token).ConfigureAwait(false);
if (quote is null || quote.Last <= 0)
{
- Console.Error.WriteLine($"ABBRUCH: kein Kurs für {symbol}. Bei geschlossener Börse und ohne " +
- $"Marktdaten-Abo ist das normal – dann später erneut versuchen.");
+ Console.Error.WriteLine($"ABBRUCH: kein Kurs für {instrument}. Entweder ist der Kontrakt " +
+ $"nicht auflösbar (Symbol/Währung prüfen – die Meldung des Adapters " +
+ $"steht oben), oder die Börse ist zu und es liegt kein Marktdaten-Abo vor.");
return 1;
}
diff --git a/tests/IBKRTrader.Tests/Trading/ExecutionServiceTests.cs b/tests/IBKRTrader.Tests/Trading/ExecutionServiceTests.cs
index 1f089d6..e5fb5da 100644
--- a/tests/IBKRTrader.Tests/Trading/ExecutionServiceTests.cs
+++ b/tests/IBKRTrader.Tests/Trading/ExecutionServiceTests.cs
@@ -58,7 +58,7 @@ public class ExecutionServiceTests
private void ArrangeHappyPath()
{
EnableTrading();
- _broker.GetQuoteAsync(Arg.Any(), Arg.Any())
+ _broker.GetQuoteAsync(Arg.Any(), Arg.Any())
.Returns(Task.FromResult(new Quote("AAPL", 100m, 99m, 101m)));
_broker.GetAccountStateAsync(Arg.Any())
.Returns(Task.FromResult(new AccountState(10_000m, 10_000m)));
@@ -87,7 +87,7 @@ public class ExecutionServiceTests
public async Task NoQuote_Skips()
{
EnableTrading();
- _broker.GetQuoteAsync(Arg.Any(), Arg.Any())
+ _broker.GetQuoteAsync(Arg.Any(), Arg.Any())
.Returns(Task.FromResult(null));
var result = await CreateSut().ExecuteAsync(BuySignal);
@@ -100,7 +100,7 @@ public class ExecutionServiceTests
public async Task RiskRejects_Skips_WithReason()
{
EnableTrading();
- _broker.GetQuoteAsync(Arg.Any(), Arg.Any())
+ _broker.GetQuoteAsync(Arg.Any(), Arg.Any())
.Returns(Task.FromResult(new Quote("AAPL", 100m, 99m, 101m)));
_broker.GetAccountStateAsync(Arg.Any())
.Returns(Task.FromResult(new AccountState(10_000m, 10_000m)));