Lean  $LEAN_TAG$
BybitBrokerageModel.cs
1 /*
2 * QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
3 * Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15 
16 using System;
17 using System.Collections.Generic;
19 using QuantConnect.Orders;
23 using QuantConnect.Util;
24 
25 namespace QuantConnect.Brokerages;
26 
27 /// <summary>
28 /// Provides Bybit specific properties
29 /// </summary>
30 public class BybitBrokerageModel : DefaultBrokerageModel
31 {
32  /// <summary>
33  /// Market name
34  /// </summary>
35  protected virtual string MarketName => Market.Bybit;
36 
37  /// <summary>
38  /// Gets a map of the default markets to be used for each security type
39  /// </summary>
40  public override IReadOnlyDictionary<SecurityType, string> DefaultMarkets { get; } = GetDefaultMarkets(Market.Bybit);
41 
42  /// <summary>
43  /// Initializes a new instance of the <see cref="BybitBrokerageModel"/> class
44  /// </summary>
45  /// <param name="accountType">The type of account to be modeled, defaults to <see cref="AccountType.Cash"/></param>
46  public BybitBrokerageModel(AccountType accountType = AccountType.Cash) : base(accountType)
47  {
48  }
49 
50  /// <summary>
51  /// Bybit global leverage rule
52  /// </summary>
53  /// <param name="security"></param>
54  /// <returns></returns>
55  public override decimal GetLeverage(Security security)
56  {
57  if (AccountType == AccountType.Cash || security.IsInternalFeed() || security.Type == SecurityType.Base)
58  {
59  return 1m;
60  }
61 
62  return 10;
63  }
64 
65  /// <summary>
66  /// Provides Bybit fee model
67  /// </summary>
68  /// <param name="security"></param>
69  /// <returns></returns>
70  public override IFeeModel GetFeeModel(Security security)
71  {
72  return security.Type switch
73  {
74  SecurityType.Crypto => new BybitFeeModel(),
75  SecurityType.CryptoFuture => new BybitFuturesFeeModel(),
76  SecurityType.Base => base.GetFeeModel(security),
77  _ => throw new ArgumentOutOfRangeException(nameof(security), security, $"Not supported security type {security.Type}")
78  };
79  }
80 
81  /// <summary>
82  /// Gets a new margin interest rate model for the security
83  /// </summary>
84  /// <param name="security">The security to get a margin interest rate model for</param>
85  /// <returns>The margin interest rate model for this brokerage</returns>
87  {
88  // only applies for perpetual futures
89  if (security.Type == SecurityType.CryptoFuture &&
90  security.Symbol.ID.Date == SecurityIdentifier.DefaultDate)
91  {
93  }
94 
95  return base.GetMarginInterestRateModel(security);
96  }
97 
98  /// <summary>
99  /// Get the benchmark for this model
100  /// </summary>
101  /// <param name="securities">SecurityService to create the security with if needed</param>
102  /// <returns>The benchmark for this brokerage</returns>
103  public override IBenchmark GetBenchmark(SecurityManager securities)
104  {
105  var symbol = Symbol.Create("BTCUSDC", SecurityType.Crypto, MarketName);
106  return SecurityBenchmark.CreateInstance(securities, symbol);
107  //todo default conversion?
108  }
109 
110  /// <summary>
111  /// Returns true if the brokerage could accept this order update. This takes into account
112  /// order type, security type, and order size limits. Bybit can only update inverse, linear, and option orders
113  /// </summary>
114  /// <param name="security">The security of the order</param>
115  /// <param name="order">The order to be updated</param>
116  /// <param name="request">The requested update to be made to the order</param>
117  /// <param name="message">If this function returns false, a brokerage message detailing why the order may not be updated</param>
118  /// <returns>True if the brokerage could update the order, false otherwise</returns>
119  public override bool CanUpdateOrder(Security security, Order order, UpdateOrderRequest request,
120  out BrokerageMessageEvent message)
121  {
122  //can only update linear, inverse, and options
123  if (security.Type != SecurityType.CryptoFuture)
124  {
125  message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
126  Messages.DefaultBrokerageModel.OrderUpdateNotSupported);
127  return false;
128  }
129 
130  if (order.Status is not (OrderStatus.New or OrderStatus.PartiallyFilled or OrderStatus.Submitted or OrderStatus.UpdateSubmitted))
131  {
132  message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
133  $"Order with status {order.Status} can't be modified");
134  return false;
135  }
136 
137  if (request.Quantity.HasValue && !IsOrderSizeLargeEnough(security, Math.Abs(request.Quantity.Value)))
138  {
139  message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
140  Messages.DefaultBrokerageModel.InvalidOrderQuantity(security, request.Quantity.Value));
141  return false;
142  }
143 
144  message = null;
145  return true;
146  }
147 
148  /// <summary>
149  /// Returns true if the brokerage could accept this order. This takes into account
150  /// order type, security type, and order size limits.
151  /// </summary>
152  /// <remarks>
153  /// For example, a brokerage may have no connectivity at certain times, or an order rate/size limit
154  /// </remarks>
155  /// <param name="security">The security of the order</param>
156  /// <param name="order">The order to be processed</param>
157  /// <param name="message">If this function returns false, a brokerage message detailing why the order may not be submitted</param>
158  /// <returns>True if the brokerage could process the order, false otherwise</returns>
159  public override bool CanSubmitOrder(Security security, Order order, out BrokerageMessageEvent message)
160  {
161  if (security.Type != SecurityType.Crypto && security.Type != SecurityType.CryptoFuture && security.Type != SecurityType.Base)
162  {
163  message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
164  Messages.DefaultBrokerageModel.UnsupportedSecurityType(this, security));
165 
166  return false;
167  }
168 
169  message = null;
170  bool quantityIsValid;
171 
172  switch (order)
173  {
174  case StopLimitOrder:
175  case StopMarketOrder:
176  case LimitOrder:
177  case MarketOrder:
178  quantityIsValid = IsOrderSizeLargeEnough(security, Math.Abs(order.Quantity));
179  break;
180  default:
181  message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
182  Messages.DefaultBrokerageModel.UnsupportedOrderType(this, order,
183  new[] { OrderType.StopMarket, OrderType.StopLimit, OrderType.Market, OrderType.Limit }));
184  return false;
185  }
186 
187  if (!quantityIsValid)
188  {
189  message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
190  Messages.DefaultBrokerageModel.InvalidOrderQuantity(security, order.Quantity));
191 
192  return false;
193  }
194 
195  return base.CanSubmitOrder(security, order, out message);
196  }
197 
198  /// <summary>
199  /// Returns true if the order size is large enough for the given security.
200  /// </summary>
201  /// <param name="security">The security of the order</param>
202  /// <param name="orderQuantity">The order quantity</param>
203  /// <returns>True if the order size is large enough, false otherwise</returns>
204  protected virtual bool IsOrderSizeLargeEnough(Security security, decimal orderQuantity)
205  {
206  return !security.SymbolProperties.MinimumOrderSize.HasValue ||
207  orderQuantity > security.SymbolProperties.MinimumOrderSize;
208  }
209 
210  private static IReadOnlyDictionary<SecurityType, string> GetDefaultMarkets(string marketName)
211  {
212  var map = DefaultMarketMap.ToDictionary();
213  map[SecurityType.Crypto] = marketName;
214  map[SecurityType.CryptoFuture] = marketName;
215  return map.ToReadOnlyDictionary();
216  }
217 }