Skip to content

Commit e6bcfe7

Browse files
committed
Add dedicated brokerage for inverse futures
1 parent 9554853 commit e6bcfe7

9 files changed

+269
-27
lines changed

QuantConnect.BybitBrokerage.Tests/BybitBrokerageTests.cs

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
using QuantConnect.BybitBrokerage.Api;
2323
using QuantConnect.BybitBrokerage.Models.Enums;
2424
using QuantConnect.Configuration;
25+
using QuantConnect.Data;
2526
using QuantConnect.Interfaces;
2627
using QuantConnect.Lean.Engine.DataFeeds;
2728
using QuantConnect.Logging;
@@ -63,8 +64,16 @@ protected override IBrokerage CreateBrokerage(IOrderProvider orderProvider, ISec
6364
var websocketUrl = Config.Get("bybit-websocket-url", "wss://stream-testnet.bybit.com");
6465

6566
_client = CreateRestApiClient(apiKey, apiSecret, apiUrl);
66-
return new BybitBrokerage(apiKey, apiSecret, apiUrl, websocketUrl, algorithm.Object, orderProvider,
67-
securityProvider, new AggregationManager(), null);
67+
68+
return CreateBrokerage(apiKey, apiSecret, apiUrl, websocketUrl, algorithm.Object, orderProvider, securityProvider, new AggregationManager());
69+
}
70+
71+
protected virtual IBrokerage CreateBrokerage(string apiKey, string apiSecret, string apiUrl,
72+
string websocketUrl, IAlgorithm algorithm, IOrderProvider orderProvider, ISecurityProvider securityProvider,
73+
IDataAggregator aggregator)
74+
{
75+
return new BybitBrokerage(apiKey, apiSecret, apiUrl, websocketUrl, algorithm, orderProvider, securityProvider, new AggregationManager(), null);
76+
6877
}
6978

7079
protected virtual decimal TakerFee => BybitFeeModel.TakerNonVIPFee;
@@ -198,8 +207,10 @@ public override void GetAccountHoldings()
198207
var beforeQuantity = beforeHoldings == null ? 0 : beforeHoldings.Amount;
199208
var afterQuantity = afterHoldings == null ? 0 : afterHoldings.Amount;
200209

210+
var fu = afterQuantity - beforeQuantity;
201211
var fee = order.Quantity * TakerFee;
202212

213+
203214
Assert.AreEqual(GetDefaultQuantity(), afterQuantity - beforeQuantity + fee);
204215
}
205216

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
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 NUnit.Framework;
17+
using QuantConnect.Brokerages;
18+
using QuantConnect.Interfaces;
19+
using QuantConnect.Lean.Engine.DataFeeds;
20+
21+
namespace QuantConnect.BybitBrokerage.Tests
22+
{
23+
[TestFixture, Explicit("Requires valid credentials to be setup and run outside USA")]
24+
public class BybitInverseFuturesBrokerageAdditionalTests : BybitBrokerageAdditionalTests
25+
{
26+
protected override string BrokerageName => nameof(BybitInverseFuturesBrokerage);
27+
28+
29+
protected override Brokerage CreateBrokerage(IAlgorithm algorithm, string apiKey, string apiSecret,
30+
string apiUrl, string websocketUrl)
31+
{
32+
return new BybitInverseFuturesBrokerage(apiKey, apiSecret, apiUrl, websocketUrl, algorithm, new AggregationManager(),
33+
null);
34+
}
35+
}
36+
}

QuantConnect.BybitBrokerage.Tests/BybitInverseFuturesBrokerageTests.cs

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,19 @@
1313
* limitations under the License.
1414
*/
1515

16+
using System;
17+
using System.Linq;
18+
using System.Threading;
1619
using NUnit.Framework;
1720
using QuantConnect.BybitBrokerage.Models.Enums;
21+
using QuantConnect.Data;
22+
using QuantConnect.Interfaces;
23+
using QuantConnect.Lean.Engine.DataFeeds;
24+
using QuantConnect.Logging;
25+
using QuantConnect.Orders;
26+
using QuantConnect.Securities;
1827
using QuantConnect.Tests.Brokerages;
28+
using QuantConnect.Util;
1929

2030
namespace QuantConnect.BybitBrokerage.Tests;
2131

@@ -27,10 +37,18 @@ public partial class BybitInverseFuturesBrokerageTests : BybitBrokerageTests
2737

2838
protected override SecurityType SecurityType => SecurityType.Future;
2939
protected override BybitProductCategory Category => BybitProductCategory.Inverse;
30-
protected override decimal TakerFee => 0.00025m;
40+
protected override decimal TakerFee => 0.0000015m;
3141

3242
protected override decimal GetDefaultQuantity() => 10m;
3343

44+
protected override IBrokerage CreateBrokerage(string apiKey, string apiSecret, string apiUrl,
45+
string websocketUrl, IAlgorithm algorithm, IOrderProvider orderProvider, ISecurityProvider securityProvider,
46+
IDataAggregator aggregator)
47+
{
48+
return new BybitInverseFuturesBrokerage(apiKey, apiSecret, apiUrl, websocketUrl, algorithm, orderProvider, securityProvider, new AggregationManager(), null);
49+
50+
}
51+
3452
/// <summary>
3553
/// Provides the data required to test each order type in various cases
3654
/// </summary>
@@ -88,10 +106,33 @@ public override void LongFromShort(OrderTestParameters parameters)
88106
{
89107
base.LongFromShort(parameters);
90108
}
91-
92-
[Ignore("The brokerage is shared between different product categories, therefore this test is only required in the base class")]
109+
110+
111+
[Test]
93112
public override void GetAccountHoldings()
94113
{
95-
base.GetAccountHoldings();
114+
Log.Trace("");
115+
Log.Trace("GET ACCOUNT HOLDINGS");
116+
Log.Trace("");
117+
var before = Brokerage.GetCashBalance();
118+
119+
var order = new MarketOrder(Symbol, GetDefaultQuantity(), DateTime.UtcNow);
120+
PlaceOrderWaitForStatus(order);
121+
122+
Thread.Sleep(3000);
123+
124+
var after = Brokerage.GetCashBalance();
125+
126+
CurrencyPairUtil.DecomposeCurrencyPair(Symbol, out var baseCurrency, out _);
127+
var beforeHoldings = before.FirstOrDefault(x => x.Currency == baseCurrency);
128+
var afterHoldings = after.FirstOrDefault(x => x.Currency == baseCurrency);
129+
130+
var beforeQuantity = beforeHoldings == null ? 0 : beforeHoldings.Amount;
131+
var afterQuantity = afterHoldings == null ? 0 : afterHoldings.Amount;
132+
133+
var fee = 0.00000015m;
134+
135+
Assert.AreEqual(0, afterQuantity - beforeQuantity + fee);
96136
}
137+
97138
}

QuantConnect.BybitBrokerage/Api/BybitAccountApiEndpoint.cs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
* limitations under the License.
1414
*/
1515

16+
using System;
1617
using System.Collections.Generic;
1718
using QuantConnect.Brokerages;
1819
using QuantConnect.BybitBrokerage.Models;
@@ -42,13 +43,19 @@ public BybitAccountApiEndpoint(ISymbolMapper symbolMapper, string apiPrefix, ISe
4243
/// <summary>
4344
/// Obtain wallet balance, query asset information of each currency, and account risk rate information
4445
/// </summary>
45-
/// <param name="category">The product category</param>
46+
/// <param name="accountType">The account type to fetch wallet balances for</param>
4647
/// <returns>The wallet balances</returns>
47-
public BybitBalance GetWalletBalances()
48+
public BybitBalance GetWalletBalances(BybitAccountType accountType)
4849
{
50+
if (accountType is not (BybitAccountType.Contract or BybitAccountType.Unified))
51+
{
52+
throw new ArgumentOutOfRangeException(nameof(accountType),
53+
"Wallet balances can only be fetched for 'UNIFIED' and 'CONTRACT'");
54+
}
55+
4956
var parameters = new KeyValuePair<string, string>[]
5057
{
51-
new("accountType", "UNIFIED")
58+
new("accountType", accountType.ToStringInvariant().ToUpperInvariant())
5259
};
5360

5461
var result =

QuantConnect.BybitBrokerage/BybitBrokerage.Brokerage.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ public override List<Holding> GetAccountHoldings()
104104
public override List<CashAmount> GetCashBalance()
105105
{
106106
return ApiClient.Account
107-
.GetWalletBalances().Assets
107+
.GetWalletBalances(WalletAccountType).Assets
108108
.Select(x => new CashAmount(x.WalletBalance, x.Asset)).ToList();
109109
}
110110

QuantConnect.BybitBrokerage/BybitBrokerage.cs

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -48,12 +48,6 @@ namespace QuantConnect.BybitBrokerage;
4848
[BrokerageFactory(typeof(BybitBrokerageFactory))]
4949
public partial class BybitBrokerage : BaseWebsocketsBrokerage, IDataQueueHandler
5050
{
51-
private static readonly List<BybitProductCategory> SupportedBybitProductCategories = new() { BybitProductCategory.Spot, BybitProductCategory.Linear, BybitProductCategory.Inverse };
52-
53-
private static readonly List<SecurityType> SuppotedSecurityTypes = new() { SecurityType.Crypto, SecurityType.CryptoFuture };
54-
55-
private static readonly string MarketName = Market.Bybit;
56-
5751
private readonly Dictionary<BybitProductCategory, BrokerageMultiWebSocketSubscriptionManager> _subscriptionManagers = new();
5852

5953
private IAlgorithm _algorithm;
@@ -63,6 +57,12 @@ public partial class BybitBrokerage : BaseWebsocketsBrokerage, IDataQueueHandler
6357
private Lazy<BybitApi> _apiClientLazy;
6458
private BrokerageConcurrentMessageHandler<WebSocketMessage> _messageHandler;
6559

60+
protected virtual string MarketName => Market.Bybit;
61+
protected virtual BybitAccountType WalletAccountType => BybitAccountType.Unified;
62+
protected virtual SecurityType[] SuppotedSecurityTypes { get; } = { SecurityType.Crypto, SecurityType.CryptoFuture };
63+
protected virtual BybitProductCategory[] SupportedBybitProductCategories { get; } =
64+
{ BybitProductCategory.Spot, BybitProductCategory.Linear };
65+
6666
/// <summary>
6767
/// Order provider
6868
/// </summary>
@@ -81,7 +81,7 @@ public partial class BybitBrokerage : BaseWebsocketsBrokerage, IDataQueueHandler
8181
/// <summary>
8282
/// Parameterless constructor for brokerage
8383
/// </summary>
84-
public BybitBrokerage() : base(MarketName)
84+
public BybitBrokerage() : base(Market.Bybit)
8585
{
8686
}
8787

@@ -120,7 +120,7 @@ public BybitBrokerage(string apiKey, string apiSecret, string restApiUrl, string
120120
public BybitBrokerage(string apiKey, string apiSecret, string restApiUrl, string webSocketBaseUrl,
121121
IAlgorithm algorithm, IOrderProvider orderProvider, ISecurityProvider securityProvider,
122122
IDataAggregator aggregator, LiveNodePacket job, BybitVIPLevel vipLevel = BybitVIPLevel.VIP0)
123-
: base(MarketName)
123+
: base(Market.Bybit)
124124
{
125125
Initialize(
126126
webSocketBaseUrl,
@@ -307,8 +307,9 @@ protected virtual bool CanSubscribe(Symbol symbol)
307307

308308
if (baseCanSubscribe && symbol.SecurityType == SecurityType.CryptoFuture)
309309
{
310-
//Can only subscribe to non-future pairs
311-
return CurrencyPairUtil.TryDecomposeCurrencyPair(symbol, out _, out var quoteCurrency) && quoteCurrency is "USDT" or "USD";
310+
return CurrencyPairUtil.TryDecomposeCurrencyPair(symbol, out _, out var quoteCurrency) &&
311+
(quoteCurrency is "USDT" || SupportedBybitProductCategories.Contains(BybitProductCategory.Inverse) &&
312+
quoteCurrency is "USD");
312313
}
313314

314315
return baseCanSubscribe;
@@ -457,7 +458,7 @@ private class ModulesReadLicenseRead : QuantConnect.Api.RestResponse
457458
/// Checks whether the specified symbol is supported by this brokerage by its security type
458459
/// </summary>
459460
[MethodImpl(MethodImplOptions.AggressiveInlining)]
460-
private static bool IsSupported(Symbol symbol)
461+
protected bool IsSupported(Symbol symbol)
461462
{
462463
return SuppotedSecurityTypes.Contains(symbol.SecurityType);
463464
}

QuantConnect.BybitBrokerage/BybitBrokerageFactory.cs

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,6 @@ namespace QuantConnect.BybitBrokerage
3131
/// </summary>
3232
public class BybitBrokerageFactory : BrokerageFactory
3333
{
34-
/// <summary>
35-
/// The default order book depth for this brokerage
36-
/// </summary>
37-
protected virtual int DefaultOrderBookDepth => 50;
38-
3934
/// <summary>
4035
/// Gets the brokerage data required to run the brokerage from configuration/disk
4136
/// </summary>
@@ -101,12 +96,30 @@ public override IBrokerage CreateBrokerage(LiveNodePacket job, IAlgorithm algori
10196
var aggregator = Composer.Instance.GetExportedValueByTypeName<IDataAggregator>(
10297
Config.Get("data-aggregator", "QuantConnect.Lean.Engine.DataFeeds.AggregationManager"),
10398
forceTypeNameOnExisting: false);
104-
var brokerage = new BybitBrokerage(apiKey, apiSecret, apiUrl, wsUrl, algorithm, aggregator, job, vipLevel);
99+
var brokerage = CreateBrokerage(apiKey, apiSecret, apiUrl, wsUrl, algorithm, aggregator, job, vipLevel);
105100
Composer.Instance.AddPart<IDataQueueHandler>(brokerage);
106101

107102
return brokerage;
108103
}
109104

105+
/// <summary>
106+
/// Creates a new BybitBrokerage instance
107+
/// </summary>
108+
/// <param name="apiKey">The api key</param>
109+
/// <param name="apiSecret">The api secret</param>
110+
/// <param name="apiUrl">The rest api url</param>
111+
/// <param name="wsUrl">The web socket base url</param>
112+
/// <param name="algorithm">The algorithm instance is required to retrieve account type</param>
113+
/// <param name="aggregator">The aggregator for consolidating ticks</param>
114+
/// <param name="job">The live job packet</param>
115+
/// <param name="vipLevel">Bybit VIP level</param>
116+
/// <returns>New BybitBrokerage instance</returns>
117+
protected virtual BybitBrokerage CreateBrokerage(string apiKey, string apiSecret, string apiUrl, string wsUrl,
118+
IAlgorithm algorithm, IDataAggregator aggregator, LiveNodePacket job, BybitVIPLevel vipLevel)
119+
{
120+
return new BybitBrokerage(apiKey, apiSecret, apiUrl, wsUrl, algorithm, aggregator, job, vipLevel);
121+
}
122+
110123
/// <summary>
111124
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
112125
/// </summary>
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
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 QuantConnect.Brokerages;
17+
using QuantConnect.BybitBrokerage.Models.Enums;
18+
using QuantConnect.Data;
19+
using QuantConnect.Interfaces;
20+
using QuantConnect.Packets;
21+
using QuantConnect.Securities;
22+
23+
namespace QuantConnect.BybitBrokerage;
24+
25+
/// <summary>
26+
/// Bybit inverse futures brokerage implementation
27+
/// </summary>
28+
[BrokerageFactory(typeof(BybitInverseFuturesBrokerageFactory))]
29+
public class BybitInverseFuturesBrokerage : BybitBrokerage
30+
{
31+
protected override SecurityType[] SuppotedSecurityTypes { get; } = { SecurityType.Crypto, SecurityType.CryptoFuture };
32+
protected override BybitProductCategory[] SupportedBybitProductCategories { get; } = { BybitProductCategory.Inverse };
33+
protected override BybitAccountType WalletAccountType => BybitAccountType.Contract;
34+
35+
/// <summary>
36+
/// Parameterless constructor for brokerage
37+
/// </summary>
38+
public BybitInverseFuturesBrokerage()
39+
{
40+
}
41+
42+
/// <summary>
43+
/// Constructor for brokerage
44+
/// </summary>
45+
/// <param name="apiKey">api key</param>
46+
/// <param name="apiSecret">api secret</param>
47+
/// <param name="restApiUrl">The rest api url</param>
48+
/// <param name="webSocketBaseUrl">The web socket base url</param>
49+
/// <param name="algorithm">the algorithm instance is required to retrieve account type</param>
50+
/// <param name="aggregator">the aggregator for consolidating ticks</param>
51+
/// <param name="job">The live job packet</param>
52+
/// <param name="vipLevel">Bybit VIP level</param>
53+
public BybitInverseFuturesBrokerage(string apiKey, string apiSecret, string restApiUrl, string webSocketBaseUrl,
54+
IAlgorithm algorithm, IDataAggregator aggregator, LiveNodePacket job,
55+
BybitVIPLevel vipLevel = BybitVIPLevel.VIP0)
56+
: base(apiKey, apiSecret, restApiUrl, webSocketBaseUrl, algorithm, algorithm?.Portfolio?.Transactions,
57+
algorithm?.Portfolio, aggregator, job, vipLevel)
58+
{
59+
}
60+
61+
/// <summary>
62+
/// Constructor for brokerage
63+
/// </summary>
64+
/// <param name="apiKey">The api key</param>
65+
/// <param name="apiSecret">The api secret</param>
66+
/// <param name="restApiUrl">The rest api url</param>
67+
/// <param name="webSocketBaseUrl">The web socket base url</param>
68+
/// <param name="algorithm">The algorithm instance is required to retrieve account type</param>
69+
/// <param name="orderProvider">The order provider is required to retrieve orders</param>
70+
/// <param name="securityProvider">The security provider is required</param>
71+
/// <param name="aggregator">The aggregator for consolidating ticks</param>
72+
/// <param name="job">The live job packet</param>
73+
/// <param name="vipLevel">Bybit VIP level</param>
74+
public BybitInverseFuturesBrokerage(string apiKey, string apiSecret, string restApiUrl, string webSocketBaseUrl,
75+
IAlgorithm algorithm, IOrderProvider orderProvider, ISecurityProvider securityProvider,
76+
IDataAggregator aggregator, LiveNodePacket job, BybitVIPLevel vipLevel = BybitVIPLevel.VIP0)
77+
: base(apiKey, apiSecret, restApiUrl, webSocketBaseUrl, algorithm, orderProvider,
78+
securityProvider, aggregator, job, vipLevel)
79+
{
80+
}
81+
}

0 commit comments

Comments
 (0)