From 4cf559688f6159f4805b6e356397663c62c5e0d2 Mon Sep 17 00:00:00 2001 From: Romazes Date: Fri, 16 Jan 2026 16:10:18 +0200 Subject: [PATCH 1/5] fix: load market cryptoFutures to find conversion rate --- Common/Securities/Cash.cs | 53 ++++++++++++++++++++++++++++----------- 1 file changed, 38 insertions(+), 15 deletions(-) diff --git a/Common/Securities/Cash.cs b/Common/Securities/Cash.cs index 0c317ab21814..6195ffa657af 100644 --- a/Common/Securities/Cash.cs +++ b/Common/Securities/Cash.cs @@ -36,6 +36,12 @@ public class Cash private readonly object _locker = new object(); + /// + /// Tracks whether the CryptoFuture-based currency conversion fallback + /// has already been logged to prevent duplicate messages. + /// + private bool _cryptoFutureFallbackLogged; + /// /// Event fired when this instance is updated /// , , @@ -262,27 +268,34 @@ public List EnsureCurrencyDataFeed(SecurityManager secur var cfdEntries = GetAvailableSymbolPropertiesDatabaseEntries(SecurityType.Cfd, marketMap, markets); var cryptoEntries = GetAvailableSymbolPropertiesDatabaseEntries(SecurityType.Crypto, marketMap, markets); - if (marketMap.TryGetValue(SecurityType.CryptoFuture, out var cryptoFutureMarket) && cryptoFutureMarket == Market.DYDX) - { - // Put additional logic for dYdX crypto futures as they don't have Crypto (Spot) market - // Also need to add them first to give the priority - // TODO: remove once dydx SPOT market will be imlemented - cryptoEntries = GetAvailableSymbolPropertiesDatabaseEntries(SecurityType.CryptoFuture, marketMap, markets).Concat(cryptoEntries); - } - var potentialEntries = forexEntries .Concat(cfdEntries) .Concat(cryptoEntries) .ToList(); - if (!potentialEntries.Any(x => - Symbol == x.Key.Symbol.Substring(0, x.Key.Symbol.Length - x.Value.QuoteCurrency.Length) || - Symbol == x.Value.QuoteCurrency)) + if (!HasMatchingCurrency(Symbol, potentialEntries)) { - // currency not found in any tradeable pair - Log.Error(Messages.Cash.NoTradablePairFoundForCurrencyConversion(Symbol, accountCurrency, marketMap.Where(kvp => ProvidesConversionRate(kvp.Key)))); - CurrencyConversion = ConstantCurrencyConversion.Null(accountCurrency, Symbol); - return null; + if (marketMap.ContainsKey(SecurityType.CryptoFuture)) + { + var cryptoFutures = GetAvailableSymbolPropertiesDatabaseEntries(SecurityType.CryptoFuture, marketMap, markets); + + if (HasMatchingCurrency(Symbol, cryptoFutures)) + { + if (!_cryptoFutureFallbackLogged) + { + _cryptoFutureFallbackLogged = true; + Log.Trace("Cash.EnsureCurrencyDataFeed(): Currency conversion resolved using CryptoFuture instruments as a fallback."); + } + potentialEntries.AddRange(cryptoFutures); + } + } + else + { + // currency not found in any tradeable pair + Log.Error(Messages.Cash.NoTradablePairFoundForCurrencyConversion(Symbol, accountCurrency, marketMap.Where(kvp => ProvidesConversionRate(kvp.Key)))); + CurrencyConversion = ConstantCurrencyConversion.Null(accountCurrency, Symbol); + return null; + } } // Special case for crypto markets without direct pairs (They wont be found by the above) @@ -399,5 +412,15 @@ private void OnUpdate() { Updated?.Invoke(this, EventArgs.Empty); } + + /// + /// Checks whether the given entries contain a tradable pair for the current currency. + /// + private static bool HasMatchingCurrency(string symbol, IEnumerable> entries) + { + return entries.Any(x => + symbol == x.Key.Symbol.Substring(0, x.Key.Symbol.Length - x.Value.QuoteCurrency.Length) + || symbol == x.Value.QuoteCurrency); + } } } From 24c4934383ca1968e4e9c41081df5fcc0ee53bc5 Mon Sep 17 00:00:00 2001 From: Romazes Date: Fri, 16 Jan 2026 16:22:59 +0200 Subject: [PATCH 2/5] fix: missed stable coins without pairs in Bybit --- Common/Currencies.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Common/Currencies.cs b/Common/Currencies.cs index 894693b15232..b631b826e7ed 100644 --- a/Common/Currencies.cs +++ b/Common/Currencies.cs @@ -243,7 +243,8 @@ public static class Currencies "BUSDUSD", "USTUSD", "TUSDUSD", - "DAIUSD" + "DAIUSD", + "EURUSD" }; /// From 0fb0bbae83a6766e2ee8ad3d4a072323c943e44e Mon Sep 17 00:00:00 2001 From: Romazes Date: Fri, 16 Jan 2026 16:52:12 +0200 Subject: [PATCH 3/5] Revert "fix: missed stable coins without pairs in Bybit" This reverts commit 24c4934383ca1968e4e9c41081df5fcc0ee53bc5. --- Common/Currencies.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Common/Currencies.cs b/Common/Currencies.cs index b631b826e7ed..894693b15232 100644 --- a/Common/Currencies.cs +++ b/Common/Currencies.cs @@ -243,8 +243,7 @@ public static class Currencies "BUSDUSD", "USTUSD", "TUSDUSD", - "DAIUSD", - "EURUSD" + "DAIUSD" }; /// From 6735fd94ecefb042bba71d826369b02954caef6b Mon Sep 17 00:00:00 2001 From: Romazes Date: Sat, 17 Jan 2026 00:58:32 +0200 Subject: [PATCH 4/5] Revert "fix: load market cryptoFutures to find conversion rate" This reverts commit 4cf559688f6159f4805b6e356397663c62c5e0d2. --- Common/Securities/Cash.cs | 53 +++++++++++---------------------------- 1 file changed, 15 insertions(+), 38 deletions(-) diff --git a/Common/Securities/Cash.cs b/Common/Securities/Cash.cs index 6195ffa657af..0c317ab21814 100644 --- a/Common/Securities/Cash.cs +++ b/Common/Securities/Cash.cs @@ -36,12 +36,6 @@ public class Cash private readonly object _locker = new object(); - /// - /// Tracks whether the CryptoFuture-based currency conversion fallback - /// has already been logged to prevent duplicate messages. - /// - private bool _cryptoFutureFallbackLogged; - /// /// Event fired when this instance is updated /// , , @@ -268,34 +262,27 @@ public List EnsureCurrencyDataFeed(SecurityManager secur var cfdEntries = GetAvailableSymbolPropertiesDatabaseEntries(SecurityType.Cfd, marketMap, markets); var cryptoEntries = GetAvailableSymbolPropertiesDatabaseEntries(SecurityType.Crypto, marketMap, markets); + if (marketMap.TryGetValue(SecurityType.CryptoFuture, out var cryptoFutureMarket) && cryptoFutureMarket == Market.DYDX) + { + // Put additional logic for dYdX crypto futures as they don't have Crypto (Spot) market + // Also need to add them first to give the priority + // TODO: remove once dydx SPOT market will be imlemented + cryptoEntries = GetAvailableSymbolPropertiesDatabaseEntries(SecurityType.CryptoFuture, marketMap, markets).Concat(cryptoEntries); + } + var potentialEntries = forexEntries .Concat(cfdEntries) .Concat(cryptoEntries) .ToList(); - if (!HasMatchingCurrency(Symbol, potentialEntries)) + if (!potentialEntries.Any(x => + Symbol == x.Key.Symbol.Substring(0, x.Key.Symbol.Length - x.Value.QuoteCurrency.Length) || + Symbol == x.Value.QuoteCurrency)) { - if (marketMap.ContainsKey(SecurityType.CryptoFuture)) - { - var cryptoFutures = GetAvailableSymbolPropertiesDatabaseEntries(SecurityType.CryptoFuture, marketMap, markets); - - if (HasMatchingCurrency(Symbol, cryptoFutures)) - { - if (!_cryptoFutureFallbackLogged) - { - _cryptoFutureFallbackLogged = true; - Log.Trace("Cash.EnsureCurrencyDataFeed(): Currency conversion resolved using CryptoFuture instruments as a fallback."); - } - potentialEntries.AddRange(cryptoFutures); - } - } - else - { - // currency not found in any tradeable pair - Log.Error(Messages.Cash.NoTradablePairFoundForCurrencyConversion(Symbol, accountCurrency, marketMap.Where(kvp => ProvidesConversionRate(kvp.Key)))); - CurrencyConversion = ConstantCurrencyConversion.Null(accountCurrency, Symbol); - return null; - } + // currency not found in any tradeable pair + Log.Error(Messages.Cash.NoTradablePairFoundForCurrencyConversion(Symbol, accountCurrency, marketMap.Where(kvp => ProvidesConversionRate(kvp.Key)))); + CurrencyConversion = ConstantCurrencyConversion.Null(accountCurrency, Symbol); + return null; } // Special case for crypto markets without direct pairs (They wont be found by the above) @@ -412,15 +399,5 @@ private void OnUpdate() { Updated?.Invoke(this, EventArgs.Empty); } - - /// - /// Checks whether the given entries contain a tradable pair for the current currency. - /// - private static bool HasMatchingCurrency(string symbol, IEnumerable> entries) - { - return entries.Any(x => - symbol == x.Key.Symbol.Substring(0, x.Key.Symbol.Length - x.Value.QuoteCurrency.Length) - || symbol == x.Value.QuoteCurrency); - } } } From 80c60630c7ca99cf66dc3fe8ab516a2ebd7c2a94 Mon Sep 17 00:00:00 2001 From: Romazes Date: Sat, 17 Jan 2026 01:02:10 +0200 Subject: [PATCH 5/5] refactor: subs accurate get conversion rate --- Common/Securities/Cash.cs | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/Common/Securities/Cash.cs b/Common/Securities/Cash.cs index 0c317ab21814..c6120974057e 100644 --- a/Common/Securities/Cash.cs +++ b/Common/Securities/Cash.cs @@ -285,16 +285,9 @@ public List EnsureCurrencyDataFeed(SecurityManager secur return null; } - // Special case for crypto markets without direct pairs (They wont be found by the above) - // This allows us to add cash for "StableCoins" that are 1-1 with our account currency without needing a conversion security. - // Check out the StableCoinsWithoutPairs static var for those that are missing their 1-1 conversion pairs - if (marketMap.TryGetValue(SecurityType.Crypto, out var market) - && - (Currencies.IsStableCoinWithoutPair(Symbol + accountCurrency, market) - || Currencies.IsStableCoinWithoutPair(accountCurrency + Symbol, market))) + if (markets.ContainsKey(SecurityType.CryptoFuture)) { - CurrencyConversion = ConstantCurrencyConversion.Identity(accountCurrency, Symbol); - return null; + cryptoEntries = cryptoEntries.Concat(GetAvailableSymbolPropertiesDatabaseEntries(SecurityType.CryptoFuture, marketMap, markets)); } var requiredSecurities = new List(); @@ -339,7 +332,7 @@ public List EnsureCurrencyDataFeed(SecurityManager secur CurrencyConversion = SecurityCurrencyConversion.LinearSearch(Symbol, accountCurrency, securitiesToSearch.ToList(), - potentials, + potentials.Where(x => markets.ContainsKey(x.SecurityType)), makeNewSecurity); return requiredSecurities;