diff --git a/Common/Securities/CryptoFuture/CryptoFuture.cs b/Common/Securities/CryptoFuture/CryptoFuture.cs index 65e473dd1475..832c5439837d 100644 --- a/Common/Securities/CryptoFuture/CryptoFuture.cs +++ b/Common/Securities/CryptoFuture/CryptoFuture.cs @@ -60,7 +60,7 @@ public CryptoFuture(Symbol symbol, cache, new SecurityPortfolioModel(), new ImmediateFillModel(), - IsCryptoCoinFuture(quoteCurrency.Symbol) ? new BinanceCoinFuturesFeeModel() : new BinanceFuturesFeeModel(), + IsCryptoCoinFuture(symbol.ID.Market, quoteCurrency.Symbol) ? new BinanceCoinFuturesFeeModel() : new BinanceFuturesFeeModel(), NullSlippageModel.Instance, new ImmediateSettlementModel(), Securities.VolatilityModel.Null, @@ -83,16 +83,26 @@ public CryptoFuture(Symbol symbol, /// True if the security is a crypto coin future public bool IsCryptoCoinFuture() { - return IsCryptoCoinFuture(QuoteCurrency.Symbol); + return IsCryptoCoinFuture(Symbol.ID.Market, QuoteCurrency.Symbol); } /// /// Checks whether the security is a crypto coin future /// + /// The security market /// The security quote currency /// True if the security is a crypto coin future - private static bool IsCryptoCoinFuture(string quoteCurrency) + private static bool IsCryptoCoinFuture(string market, string quoteCurrency) { + // Binance coin-margined futures are nominally quoted in USD (e.g. BTCUSD_PERP) but settle in the base + // currency, so on Binance any quote currency other than one of its known USD-pegged stablecoins implies + // a coin-margined contract. Other venues, notably EU MiCA-compliant exchanges, quote linear (USD-margined) + // futures directly in fiat USD, so outside of Binance the quote currency alone can't be used this way. + if (market != Market.Binance) + { + return false; + } + return quoteCurrency != "USDT" && quoteCurrency != "BUSD" && quoteCurrency != "USDC"; } diff --git a/Tests/Common/Securities/CryptoFuture/CryptoFutureMarginModelTests.cs b/Tests/Common/Securities/CryptoFuture/CryptoFutureMarginModelTests.cs index 2f3d8cc246fb..330fbf401051 100644 --- a/Tests/Common/Securities/CryptoFuture/CryptoFutureMarginModelTests.cs +++ b/Tests/Common/Securities/CryptoFuture/CryptoFutureMarginModelTests.cs @@ -207,6 +207,42 @@ public void DefaultMarginModelDoesNotIncludeSupplementaryCollateral() Assert.AreEqual(0, buyingPower.Value); } + [Test] + public void BinanceUsdQuotedFutureIsCoinMargined() + { + var symbol = Symbol.Create("BTCUSD", SecurityType.CryptoFuture, Market.Binance); + var cryptoFuture = new QuantConnect.Securities.CryptoFuture.CryptoFuture( + symbol, + SecurityExchangeHours.AlwaysOpen(TimeZones.NewYork), + new Cash("USD", 0, 1m), + new Cash("BTC", 0, 1m), + SymbolProperties.GetDefault("USD"), + ErrorCurrencyConverter.Instance, + RegisteredSecurityDataTypesProvider.Null, + new SecurityCache()); + + Assert.IsTrue(cryptoFuture.IsCryptoCoinFuture()); + } + + [Test] + public void NonBinanceUsdQuotedFutureIsNotCoinMargined() + { + // EU MiCA-compliant exchanges (e.g. Kraken Futures, OKX EU) quote linear, USD-margined + // futures directly in fiat USD rather than a stablecoin. + var symbol = Symbol.Create("BTCUSD", SecurityType.CryptoFuture, Market.Kraken); + var cryptoFuture = new QuantConnect.Securities.CryptoFuture.CryptoFuture( + symbol, + SecurityExchangeHours.AlwaysOpen(TimeZones.NewYork), + new Cash("USD", 0, 1m), + new Cash("BTC", 0, 1m), + SymbolProperties.GetDefault("USD"), + ErrorCurrencyConverter.Instance, + RegisteredSecurityDataTypesProvider.Null, + new SecurityCache()); + + Assert.IsFalse(cryptoFuture.IsCryptoCoinFuture()); + } + private static QCAlgorithm GetAlgorithm() { // Initialize algorithm