forked from QuantConnect/Lean
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add Bybit integration (QuantConnect#5898)
- Loading branch information
Showing
17 changed files
with
2,066 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,149 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using QuantConnect.Benchmarks; | ||
using QuantConnect.Orders; | ||
using QuantConnect.Orders.Fees; | ||
using QuantConnect.Securities; | ||
using QuantConnect.Util; | ||
|
||
namespace QuantConnect.Brokerages; | ||
|
||
/// <summary> | ||
/// | ||
/// </summary> | ||
public class BybitBrokerageModel : DefaultBrokerageModel | ||
{ | ||
/// <summary> | ||
/// Market name | ||
/// </summary> | ||
protected virtual string MarketName => Market.Bybit; | ||
|
||
/// <summary> | ||
/// Gets a map of the default markets to be used for each security type | ||
/// </summary> | ||
public override IReadOnlyDictionary<SecurityType, string> DefaultMarkets { get; } = GetDefaultMarkets(Market.Bybit); | ||
|
||
|
||
public override IFeeModel GetFeeModel(Security security) => new BybitFeeModel(); | ||
|
||
|
||
public BybitBrokerageModel(AccountType accountType = AccountType.Cash) : base(accountType) | ||
{ | ||
} | ||
|
||
/// <summary> | ||
/// Bybit global leverage rule | ||
/// </summary> | ||
/// <param name="security"></param> | ||
/// <returns></returns> | ||
public override decimal GetLeverage(Security security) | ||
{ | ||
if (AccountType == AccountType.Cash || security.IsInternalFeed() || security.Type == SecurityType.Base) | ||
{ | ||
return 1m; | ||
} | ||
|
||
return security.Symbol.SecurityType == SecurityType.CryptoFuture ? 10 : 10; | ||
} | ||
|
||
/// <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("BTCUSDC", SecurityType.Crypto, MarketName); | ||
return SecurityBenchmark.CreateInstance(securities, symbol); | ||
} | ||
|
||
|
||
public override bool CanUpdateOrder(Security security, Order order, UpdateOrderRequest request, | ||
out BrokerageMessageEvent message) | ||
{ | ||
security = security ?? throw new ArgumentNullException(nameof(security)); | ||
order = order ?? throw new ArgumentNullException(nameof(order)); | ||
request = request ?? throw new ArgumentNullException(nameof(request)); | ||
|
||
//can only update linear, inverse, and options | ||
if (security.Type != SecurityType.CryptoFuture) | ||
{ | ||
message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported", | ||
Messages.DefaultBrokerageModel.OrderUpdateNotSupported); | ||
} | ||
|
||
if (order.Status is not (OrderStatus.New or OrderStatus.PartiallyFilled)) | ||
{ | ||
message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported", | ||
Messages.DefaultBrokerageModel.OrderUpdateNotSupported); | ||
return false; | ||
} | ||
|
||
if (request.Quantity.HasValue && !IsOrderSizeLargeEnough(security, Math.Abs(request.Quantity.Value))) | ||
{ | ||
message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported", | ||
Messages.DefaultBrokerageModel.InvalidOrderQuantity(security, request.Quantity.Value)); | ||
return false; | ||
} | ||
|
||
message = null; | ||
return true; | ||
} | ||
|
||
protected static IReadOnlyDictionary<SecurityType, string> GetDefaultMarkets(string marketName) | ||
{ | ||
var map = DefaultMarketMap.ToDictionary(); | ||
map[SecurityType.Crypto] = marketName; | ||
return map.ToReadOnlyDictionary(); | ||
} | ||
|
||
|
||
public override bool CanSubmitOrder(Security security, Order order, out BrokerageMessageEvent message) | ||
{ | ||
security = security ?? throw new ArgumentNullException(nameof(security)); | ||
order = order ?? throw new ArgumentNullException(nameof(order)); | ||
|
||
if (security.Type != SecurityType.Crypto && security.Type != SecurityType.CryptoFuture) | ||
{ | ||
message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported", | ||
Messages.DefaultBrokerageModel.UnsupportedSecurityType(this, security)); | ||
|
||
return false; | ||
} | ||
|
||
message = null; | ||
var quantityIsValid = true; | ||
|
||
switch (order) | ||
{ | ||
case StopLimitOrder: | ||
case StopMarketOrder: | ||
case LimitOrder: | ||
case MarketOrder: | ||
quantityIsValid = IsOrderSizeLargeEnough(security, Math.Abs(order.Quantity)); | ||
break; | ||
default: | ||
message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported", | ||
Messages.DefaultBrokerageModel.UnsupportedOrderType(this, order, | ||
new[] { OrderType.StopMarket, OrderType.StopLimit, OrderType.Market, OrderType.Limit })); | ||
return false; | ||
} | ||
|
||
if (!quantityIsValid) | ||
{ | ||
message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported", | ||
Messages.DefaultBrokerageModel.InvalidOrderQuantity(security, order.Quantity)); | ||
|
||
return false; | ||
} | ||
|
||
|
||
return base.CanSubmitOrder(security, order, out message); | ||
} | ||
|
||
protected virtual bool IsOrderSizeLargeEnough(Security security, decimal orderQuantity) | ||
{ | ||
return !security.SymbolProperties.MinimumOrderSize.HasValue || | ||
orderQuantity > security.SymbolProperties.MinimumOrderSize; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
using System; | ||
using QuantConnect.Benchmarks; | ||
using QuantConnect.Orders.Fees; | ||
using QuantConnect.Securities; | ||
using QuantConnect.Securities.CryptoFuture; | ||
|
||
namespace QuantConnect.Brokerages; | ||
|
||
public class BybitFuturesBrokerageModel : BybitBrokerageModel | ||
{ | ||
public BybitFuturesBrokerageModel(AccountType accountType = AccountType.Margin) : base(accountType) | ||
{ | ||
if (accountType == AccountType.Cash) | ||
{ | ||
throw new InvalidOperationException($"{SecurityType.CryptoFuture} can only be traded using a {AccountType.Margin} account type"); | ||
} | ||
} | ||
|
||
/// <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("BTCUSDT", SecurityType.CryptoFuture, MarketName); | ||
return SecurityBenchmark.CreateInstance(securities, symbol); | ||
} | ||
|
||
/// <summary> | ||
/// Provides Binance 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 margin interest rate model for the security | ||
/// </summary> | ||
/// <param name="security">The security to get a margin interest rate model for</param> | ||
/// <returns>The margin interest rate model for this brokerage</returns> | ||
public override IMarginInterestRateModel GetMarginInterestRateModel(Security security) | ||
{ | ||
//todo is there something else needed to support this? | ||
// only applies for perpetual futures | ||
if (security.Symbol.SecurityType == SecurityType.CryptoFuture && security.Symbol.ID.Date == SecurityIdentifier.DefaultDate) | ||
{ | ||
return new BybitFutureMarginInterestRateModel(); | ||
} | ||
return base.GetMarginInterestRateModel(security); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
namespace QuantConnect.Orders; | ||
|
||
public class BybitOrderProperties : OrderProperties | ||
{ | ||
|
||
/// <summary> | ||
/// This flag will ensure the order executes only as a maker (no fee) order. | ||
/// If part of the order results in taking liquidity rather than providing, | ||
/// it will be rejected and no part of the order will execute. | ||
/// Note: this flag is only applied to Limit orders. | ||
/// </summary> | ||
public bool PostOnly { get; set; } | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using QuantConnect.Securities; | ||
using QuantConnect.Util; | ||
|
||
namespace QuantConnect.Orders.Fees; | ||
|
||
public class BybitFeeModel : FeeModel | ||
{ | ||
/// <summary> | ||
/// Tier 1 maker fees | ||
/// https://learn.bybit.com/bybit-guide/bybit-trading-fees/ | ||
/// </summary> | ||
public const decimal MakerNonVIPFee = 0.001m; | ||
|
||
/// <summary> | ||
/// Tier 1 taker fees | ||
/// https://learn.bybit.com/bybit-guide/bybit-trading-fees/ | ||
/// </summary> | ||
public const decimal TakerNonVIPFee = 0.001m; | ||
|
||
private readonly decimal _makerFee; | ||
private readonly decimal _takerFee; | ||
|
||
/// <summary> | ||
/// Creates Binance fee model setting fees values | ||
/// </summary> | ||
/// <param name="mFee">Maker fee value</param> | ||
/// <param name="tFee">Taker fee value</param> | ||
public BybitFeeModel(decimal mFee = MakerNonVIPFee, decimal tFee = TakerNonVIPFee) | ||
{ | ||
_makerFee = mFee; | ||
_takerFee = tFee; | ||
} | ||
|
||
public override OrderFee GetOrderFee(OrderFeeParameters parameters) | ||
{ | ||
var security = parameters.Security; | ||
var order = parameters.Order; | ||
|
||
var fee = GetFee(order); | ||
|
||
if(security.Symbol.ID.SecurityType == SecurityType.CryptoFuture) | ||
{ | ||
var positionValue = security.Holdings.GetQuantityValue(order.AbsoluteQuantity, security.Price); | ||
return new OrderFee(new CashAmount(positionValue.Amount * fee, positionValue.Cash.Symbol)); | ||
} | ||
|
||
if (order.Direction == OrderDirection.Buy) | ||
{ | ||
// fees taken in the received currency | ||
CurrencyPairUtil.DecomposeCurrencyPair(order.Symbol, out var baseCurrency, out _); | ||
return new OrderFee(new CashAmount(order.AbsoluteQuantity * fee, baseCurrency)); | ||
} | ||
|
||
// get order value in quote currency | ||
var unitPrice = order.Direction == OrderDirection.Buy ? security.AskPrice : security.BidPrice; | ||
if (order.Type == OrderType.Limit) | ||
{ | ||
// limit order posted to the order book | ||
unitPrice = ((LimitOrder)order).LimitPrice; | ||
} | ||
|
||
unitPrice *= security.SymbolProperties.ContractMultiplier; | ||
|
||
return new OrderFee(new CashAmount( | ||
unitPrice * order.AbsoluteQuantity * fee, | ||
security.QuoteCurrency.Symbol)); | ||
} | ||
|
||
/// <summary> | ||
/// Gets the fee factor for the given order | ||
/// </summary> | ||
/// <param name="order">The order to get the fee factor for</param> | ||
/// <returns>The fee factor for the given order</returns> | ||
protected virtual decimal GetFee(Order order) | ||
{ | ||
return GetFee(order, _makerFee, _takerFee); | ||
} | ||
|
||
protected static decimal GetFee(Order order, decimal makerFee, decimal takerFee) | ||
{ | ||
// apply fee factor, currently we do not model 30-day volume, so we use the first tier | ||
var fee = takerFee; | ||
var props = order.Properties as BybitOrderProperties; | ||
|
||
if (order.Type == OrderType.Limit && ((props != null && props.PostOnly) || !order.IsMarketable)) | ||
{ | ||
// limit order posted to the order book | ||
fee = makerFee; | ||
} | ||
|
||
return fee; | ||
} | ||
} |
Oops, something went wrong.