Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion Common/Brokerages/BrokerageName.cs
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,11 @@ public enum BrokerageName
/// <summary>
/// Transaction and submit/execution rules will use dYdX models
/// </summary>
DYDX
DYDX,

/// <summary>
/// Bybit Inverse Futures COIN-Margined contracts are settled and collateralized in their base cryptocurrency.
/// </summary>
BybitInverseFutures,
}
}
71 changes: 71 additions & 0 deletions Common/Brokerages/BybitInverseFuturesBrokerageModel.cs
Original file line number Diff line number Diff line change
@@ -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;

/// <summary>
/// Provides Bybit Inverse Futures specific properties.
/// Inverse (COIN-Margined) contracts are settled and collateralized in their base cryptocurrency (e.g. BTC for BTCUSD).
/// </summary>
public class BybitInverseFuturesBrokerageModel : BybitBrokerageModel
{
/// <summary>
/// Initializes a new instance of the <see cref="BybitInverseFuturesBrokerageModel"/> class
/// </summary>
/// <param name="accountType">The type of account to be modeled, defaults to <see cref="AccountType.Margin"/></param>
public BybitInverseFuturesBrokerageModel(AccountType accountType = AccountType.Margin) : base(accountType)
{
}

/// <summary>
/// Get the benchmark for this model
/// </summary>
/// <param name="securities">SecurityService to create the security with if needed</param>
/// <returns>The benchmark for this brokerage</returns>
public override IBenchmark GetBenchmark(SecurityManager securities)
{
var symbol = Symbol.Create("BTCUSD", SecurityType.CryptoFuture, MarketName);
return SecurityBenchmark.CreateInstance(securities, symbol);
}

/// <summary>
/// Provides Bybit Inverse Futures fee model
/// </summary>
/// <param name="security">The security to get a fee model for</param>
/// <returns>The new fee model for this brokerage</returns>
public override IFeeModel GetFeeModel(Security security)
{
return new BybitFuturesFeeModel();
}

/// <summary>
/// Gets a new buying power model for the security
/// </summary>
/// <param name="security">The security to get a buying power model for</param>
/// <returns>The buying power model for this brokerage/security</returns>
public override IBuyingPowerModel GetBuyingPowerModel(Security security)
{
if (security.Type == SecurityType.CryptoFuture)
{
return new BybitInverseFuturesMarginModel(GetLeverage(security));
}
return base.GetBuyingPowerModel(security);
}
}
6 changes: 6 additions & 0 deletions Common/Brokerages/IBrokerageModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down Expand Up @@ -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;

Expand Down
32 changes: 32 additions & 0 deletions Common/Securities/CryptoFuture/BybitInverseFuturesMarginModel.cs
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// Margin model for Bybit Inverse Futures using the Unified Trading Account (UTA).
/// In UTA, <see cref="BybitBrokerage.GetCashBalance"/> reports <c>TotalAvailableBalance</c> as USD,
/// so we use the quote currency (USD) as collateral instead of the base crypto (e.g. ADA).
/// </summary>
public class BybitInverseFuturesMarginModel : CryptoFutureMarginModel
{
public BybitInverseFuturesMarginModel(decimal leverage) : base(leverage) { }

private protected override Cash GetCollateralCash(Security security)
{
return (security as CryptoFuture).QuoteCurrency;
}
}
}
2 changes: 1 addition & 1 deletion Common/Securities/CryptoFuture/CryptoFutureMarginModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ protected override decimal GetMarginRemaining(SecurityPortfolioManager portfolio
/// <summary>
/// Helper method to determine what's the collateral currency for the given crypto future
/// </summary>
private static Cash GetCollateralCash(Security security)
private protected virtual Cash GetCollateralCash(Security security)
{
var cryptoFuture = (CryptoFuture)security;

Expand Down
108 changes: 108 additions & 0 deletions Tests/Common/Brokerages/BybitInverseFuturesBrokerageModelTests.cs
Original file line number Diff line number Diff line change
@@ -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<BybitFuturesFeeModel>(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<BybitInverseFuturesBrokerageModel>(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);
}
}
}
Loading