diff --git a/Common/Brokerages/BrokerageName.cs b/Common/Brokerages/BrokerageName.cs
index 7f6edaab7a5e..e31cb11a6ff8 100644
--- a/Common/Brokerages/BrokerageName.cs
+++ b/Common/Brokerages/BrokerageName.cs
@@ -197,6 +197,11 @@ public enum BrokerageName
///
/// Transaction and submit/execution rules will use dYdX models
///
- DYDX
+ DYDX,
+
+ ///
+ /// Bybit Inverse Futures COIN-Margined contracts are settled and collateralized in their base cryptocurrency.
+ ///
+ BybitInverseFutures,
}
}
diff --git a/Common/Brokerages/BybitInverseFuturesBrokerageModel.cs b/Common/Brokerages/BybitInverseFuturesBrokerageModel.cs
new file mode 100644
index 000000000000..11d9425654e3
--- /dev/null
+++ b/Common/Brokerages/BybitInverseFuturesBrokerageModel.cs
@@ -0,0 +1,71 @@
+/*
+ * QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
+ * Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+*/
+
+using QuantConnect.Benchmarks;
+using QuantConnect.Orders.Fees;
+using QuantConnect.Securities;
+using QuantConnect.Securities.CryptoFuture;
+
+namespace QuantConnect.Brokerages;
+
+///
+/// Provides Bybit Inverse Futures specific properties.
+/// Inverse (COIN-Margined) contracts are settled and collateralized in their base cryptocurrency (e.g. BTC for BTCUSD).
+///
+public class BybitInverseFuturesBrokerageModel : BybitBrokerageModel
+{
+ ///
+ /// Initializes a new instance of the class
+ ///
+ /// The type of account to be modeled, defaults to
+ public BybitInverseFuturesBrokerageModel(AccountType accountType = AccountType.Margin) : base(accountType)
+ {
+ }
+
+ ///
+ /// Get the benchmark for this model
+ ///
+ /// SecurityService to create the security with if needed
+ /// The benchmark for this brokerage
+ public override IBenchmark GetBenchmark(SecurityManager securities)
+ {
+ var symbol = Symbol.Create("BTCUSD", SecurityType.CryptoFuture, MarketName);
+ return SecurityBenchmark.CreateInstance(securities, symbol);
+ }
+
+ ///
+ /// Provides Bybit Inverse Futures fee model
+ ///
+ /// The security to get a fee model for
+ /// The new fee model for this brokerage
+ public override IFeeModel GetFeeModel(Security security)
+ {
+ return new BybitFuturesFeeModel();
+ }
+
+ ///
+ /// Gets a new buying power model for the security
+ ///
+ /// The security to get a buying power model for
+ /// The buying power model for this brokerage/security
+ public override IBuyingPowerModel GetBuyingPowerModel(Security security)
+ {
+ if (security.Type == SecurityType.CryptoFuture)
+ {
+ return new BybitInverseFuturesMarginModel(GetLeverage(security));
+ }
+ return base.GetBuyingPowerModel(security);
+ }
+}
diff --git a/Common/Brokerages/IBrokerageModel.cs b/Common/Brokerages/IBrokerageModel.cs
index 4544c388d8c1..246f37140551 100644
--- a/Common/Brokerages/IBrokerageModel.cs
+++ b/Common/Brokerages/IBrokerageModel.cs
@@ -276,6 +276,9 @@ public static IBrokerageModel Create(IOrderProvider orderProvider, BrokerageName
case BrokerageName.Bybit:
return new BybitBrokerageModel(accountType);
+ case BrokerageName.BybitInverseFutures:
+ return new BybitInverseFuturesBrokerageModel(accountType);
+
case BrokerageName.Eze:
return new EzeBrokerageModel(accountType);
@@ -379,6 +382,9 @@ public static BrokerageName GetBrokerageName(IBrokerageModel brokerageModel)
case RBIBrokerageModel _:
return BrokerageName.RBI;
+ case BybitInverseFuturesBrokerageModel _:
+ return BrokerageName.BybitInverseFutures;
+
case BybitBrokerageModel _:
return BrokerageName.Bybit;
diff --git a/Common/Securities/CryptoFuture/BybitInverseFuturesMarginModel.cs b/Common/Securities/CryptoFuture/BybitInverseFuturesMarginModel.cs
new file mode 100644
index 000000000000..84b729e95734
--- /dev/null
+++ b/Common/Securities/CryptoFuture/BybitInverseFuturesMarginModel.cs
@@ -0,0 +1,32 @@
+/*
+ * QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
+ * Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+*/
+
+namespace QuantConnect.Securities.CryptoFuture
+{
+ ///
+ /// Margin model for Bybit Inverse Futures using the Unified Trading Account (UTA).
+ /// In UTA, reports TotalAvailableBalance as USD,
+ /// so we use the quote currency (USD) as collateral instead of the base crypto (e.g. ADA).
+ ///
+ public class BybitInverseFuturesMarginModel : CryptoFutureMarginModel
+ {
+ public BybitInverseFuturesMarginModel(decimal leverage) : base(leverage) { }
+
+ private protected override Cash GetCollateralCash(Security security)
+ {
+ return (security as CryptoFuture).QuoteCurrency;
+ }
+ }
+}
diff --git a/Common/Securities/CryptoFuture/CryptoFutureMarginModel.cs b/Common/Securities/CryptoFuture/CryptoFutureMarginModel.cs
index 0b6ce4528adf..f8f2080c019c 100644
--- a/Common/Securities/CryptoFuture/CryptoFutureMarginModel.cs
+++ b/Common/Securities/CryptoFuture/CryptoFutureMarginModel.cs
@@ -142,7 +142,7 @@ protected override decimal GetMarginRemaining(SecurityPortfolioManager portfolio
///
/// Helper method to determine what's the collateral currency for the given crypto future
///
- private static Cash GetCollateralCash(Security security)
+ private protected virtual Cash GetCollateralCash(Security security)
{
var cryptoFuture = (CryptoFuture)security;
diff --git a/Tests/Common/Brokerages/BybitInverseFuturesBrokerageModelTests.cs b/Tests/Common/Brokerages/BybitInverseFuturesBrokerageModelTests.cs
new file mode 100644
index 000000000000..f01ded481cd9
--- /dev/null
+++ b/Tests/Common/Brokerages/BybitInverseFuturesBrokerageModelTests.cs
@@ -0,0 +1,108 @@
+/*
+ * QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
+ * Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+using System;
+using NUnit.Framework;
+using QuantConnect.Brokerages;
+using QuantConnect.Data.Market;
+using QuantConnect.Orders;
+using QuantConnect.Orders.Fees;
+using QuantConnect.Securities;
+using QuantConnect.Tests.Brokerages;
+using QuantConnect.Tests.Engine.DataFeeds;
+
+namespace QuantConnect.Tests.Common.Brokerages
+{
+ [TestFixture, Parallelizable(ParallelScope.All)]
+ public class BybitInverseFuturesBrokerageModelTests
+ {
+ private static readonly Symbol BTCUSD_Future = Symbol.Create("BTCUSD", SecurityType.CryptoFuture, Market.Bybit);
+ private static readonly BybitInverseFuturesBrokerageModel Model = new();
+
+ [Test]
+ public void DefaultAccountTypeIsMargin()
+ {
+ Assert.AreEqual(AccountType.Margin, Model.AccountType);
+ }
+
+ [Test]
+ public void GetFeeModelReturnsBybitFuturesFeeModel_ForCryptoFuture()
+ {
+ var security = TestsHelpers.GetSecurity(symbol: BTCUSD_Future.Value,
+ securityType: SecurityType.CryptoFuture,
+ market: Market.Bybit,
+ quoteCurrency: "USD");
+
+ Assert.IsInstanceOf(Model.GetFeeModel(security));
+ }
+
+ [Test]
+ public void GetBrokerageNameReturnsBybitInverseFutures()
+ {
+ Assert.AreEqual(BrokerageName.BybitInverseFutures, BrokerageModel.GetBrokerageName(new BybitInverseFuturesBrokerageModel()));
+ }
+
+ [Test]
+ public void GetBrokerageModelReturnsInverseFuturesModel()
+ {
+ var model = BrokerageModel.Create(null, BrokerageName.BybitInverseFutures, AccountType.Margin);
+ Assert.IsInstanceOf(model);
+ }
+
+ [TestCase(AccountType.Cash, 1)]
+ [TestCase(AccountType.Margin, 10)]
+ public void GetLeverageReturnsCorrectValue(AccountType accountType, decimal expectedLeverage)
+ {
+ var security = TestsHelpers.GetSecurity(symbol: BTCUSD_Future.Value,
+ securityType: SecurityType.CryptoFuture,
+ market: Market.Bybit,
+ quoteCurrency: "USD");
+
+ var model = new BybitInverseFuturesBrokerageModel(accountType);
+ Assert.AreEqual(expectedLeverage, model.GetLeverage(security));
+ }
+
+ [TestCase(10, 0.40, Description = "leverage=10 => initialMargin ≈ 4 / 0.267 / 10 * 0.267 = 0.40 USD")]
+ [TestCase(25, 0.16, Description = "leverage=25 => initialMargin ≈ 4 / 0.267 / 25 * 0.267 = 0.16 USD")]
+ public void GetBuyingPowerUsesUsdBalance_WithDifferentLeverage(decimal leverage, double expectedInitialMarginUsd)
+ {
+ // Reproduces the live trading scenario: Bybit UTA reports TotalAvailableBalance as USD
+ // (no ADA in account), so the margin model must use USD as collateral.
+ var algo = new AlgorithmStub();
+ algo.SetBrokerageModel(BrokerageName.BybitInverseFutures, AccountType.Margin);
+ algo.SetFinishedWarmingUp();
+
+ var adaUsd = algo.AddCryptoFuture("ADAUSD");
+ adaUsd.SetLeverage(leverage);
+
+ const decimal adaPrice = 0.267m;
+ const decimal usdBalance = 100m;
+
+ adaUsd.QuoteCurrency.SetAmount(usdBalance); // USD = 100 (from GetCashBalance)
+ adaUsd.BaseCurrency.SetAmount(0m); // ADA = 0 (no base asset in account)
+ adaUsd.BaseCurrency.ConversionRate = adaPrice;
+ adaUsd.QuoteCurrency.ConversionRate = 1m;
+ adaUsd.SetMarketPrice(new TradeBar(new DateTime(2026, 1, 1), adaUsd.Symbol, adaPrice, adaPrice, adaPrice, adaPrice, volume: 1m));
+
+ // Buying power = USD balance regardless of leverage
+ var buyingPower = adaUsd.BuyingPowerModel.GetBuyingPower(new BuyingPowerParameters(algo.Portfolio, adaUsd, OrderDirection.Buy));
+ Assert.AreEqual((double)usdBalance, (double)buyingPower.Value, delta: 0.01);
+
+ // Initial margin scales inversely with leverage
+ var initialMargin = adaUsd.BuyingPowerModel.GetInitialMarginRequirement(new InitialMarginParameters(adaUsd, 4));
+ Assert.AreEqual(expectedInitialMarginUsd, (double)initialMargin.Value, delta: 0.05);
+ }
+ }
+}