Skip to content

Commit

Permalink
Add dedicated brokerage for inverse futures
Browse files Browse the repository at this point in the history
  • Loading branch information
arodus committed Dec 2, 2023
1 parent 9554853 commit e6bcfe7
Show file tree
Hide file tree
Showing 9 changed files with 269 additions and 27 deletions.
15 changes: 13 additions & 2 deletions QuantConnect.BybitBrokerage.Tests/BybitBrokerageTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
using QuantConnect.BybitBrokerage.Api;
using QuantConnect.BybitBrokerage.Models.Enums;
using QuantConnect.Configuration;
using QuantConnect.Data;
using QuantConnect.Interfaces;
using QuantConnect.Lean.Engine.DataFeeds;
using QuantConnect.Logging;
Expand Down Expand Up @@ -63,8 +64,16 @@ protected override IBrokerage CreateBrokerage(IOrderProvider orderProvider, ISec
var websocketUrl = Config.Get("bybit-websocket-url", "wss://stream-testnet.bybit.com");

_client = CreateRestApiClient(apiKey, apiSecret, apiUrl);
return new BybitBrokerage(apiKey, apiSecret, apiUrl, websocketUrl, algorithm.Object, orderProvider,
securityProvider, new AggregationManager(), null);

return CreateBrokerage(apiKey, apiSecret, apiUrl, websocketUrl, algorithm.Object, orderProvider, securityProvider, new AggregationManager());
}

protected virtual IBrokerage CreateBrokerage(string apiKey, string apiSecret, string apiUrl,
string websocketUrl, IAlgorithm algorithm, IOrderProvider orderProvider, ISecurityProvider securityProvider,
IDataAggregator aggregator)
{
return new BybitBrokerage(apiKey, apiSecret, apiUrl, websocketUrl, algorithm, orderProvider, securityProvider, new AggregationManager(), null);

}

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

var fu = afterQuantity - beforeQuantity;
var fee = order.Quantity * TakerFee;


Assert.AreEqual(GetDefaultQuantity(), afterQuantity - beforeQuantity + fee);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* 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 NUnit.Framework;
using QuantConnect.Brokerages;
using QuantConnect.Interfaces;
using QuantConnect.Lean.Engine.DataFeeds;

namespace QuantConnect.BybitBrokerage.Tests
{
[TestFixture, Explicit("Requires valid credentials to be setup and run outside USA")]
public class BybitInverseFuturesBrokerageAdditionalTests : BybitBrokerageAdditionalTests
{
protected override string BrokerageName => nameof(BybitInverseFuturesBrokerage);


protected override Brokerage CreateBrokerage(IAlgorithm algorithm, string apiKey, string apiSecret,
string apiUrl, string websocketUrl)
{
return new BybitInverseFuturesBrokerage(apiKey, apiSecret, apiUrl, websocketUrl, algorithm, new AggregationManager(),
null);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,19 @@
* limitations under the License.
*/

using System;
using System.Linq;
using System.Threading;
using NUnit.Framework;
using QuantConnect.BybitBrokerage.Models.Enums;
using QuantConnect.Data;
using QuantConnect.Interfaces;
using QuantConnect.Lean.Engine.DataFeeds;
using QuantConnect.Logging;
using QuantConnect.Orders;
using QuantConnect.Securities;
using QuantConnect.Tests.Brokerages;
using QuantConnect.Util;

namespace QuantConnect.BybitBrokerage.Tests;

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

protected override SecurityType SecurityType => SecurityType.Future;
protected override BybitProductCategory Category => BybitProductCategory.Inverse;
protected override decimal TakerFee => 0.00025m;
protected override decimal TakerFee => 0.0000015m;

protected override decimal GetDefaultQuantity() => 10m;

protected override IBrokerage CreateBrokerage(string apiKey, string apiSecret, string apiUrl,
string websocketUrl, IAlgorithm algorithm, IOrderProvider orderProvider, ISecurityProvider securityProvider,
IDataAggregator aggregator)
{
return new BybitInverseFuturesBrokerage(apiKey, apiSecret, apiUrl, websocketUrl, algorithm, orderProvider, securityProvider, new AggregationManager(), null);

}

/// <summary>
/// Provides the data required to test each order type in various cases
/// </summary>
Expand Down Expand Up @@ -88,10 +106,33 @@ public override void LongFromShort(OrderTestParameters parameters)
{
base.LongFromShort(parameters);
}

[Ignore("The brokerage is shared between different product categories, therefore this test is only required in the base class")]


[Test]
public override void GetAccountHoldings()
{
base.GetAccountHoldings();
Log.Trace("");
Log.Trace("GET ACCOUNT HOLDINGS");
Log.Trace("");
var before = Brokerage.GetCashBalance();

var order = new MarketOrder(Symbol, GetDefaultQuantity(), DateTime.UtcNow);
PlaceOrderWaitForStatus(order);

Thread.Sleep(3000);

var after = Brokerage.GetCashBalance();

CurrencyPairUtil.DecomposeCurrencyPair(Symbol, out var baseCurrency, out _);
var beforeHoldings = before.FirstOrDefault(x => x.Currency == baseCurrency);
var afterHoldings = after.FirstOrDefault(x => x.Currency == baseCurrency);

var beforeQuantity = beforeHoldings == null ? 0 : beforeHoldings.Amount;
var afterQuantity = afterHoldings == null ? 0 : afterHoldings.Amount;

var fee = 0.00000015m;

Assert.AreEqual(0, afterQuantity - beforeQuantity + fee);
}

}
13 changes: 10 additions & 3 deletions QuantConnect.BybitBrokerage/Api/BybitAccountApiEndpoint.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
* limitations under the License.
*/

using System;
using System.Collections.Generic;
using QuantConnect.Brokerages;
using QuantConnect.BybitBrokerage.Models;
Expand Down Expand Up @@ -42,13 +43,19 @@ public BybitAccountApiEndpoint(ISymbolMapper symbolMapper, string apiPrefix, ISe
/// <summary>
/// Obtain wallet balance, query asset information of each currency, and account risk rate information
/// </summary>
/// <param name="category">The product category</param>
/// <param name="accountType">The account type to fetch wallet balances for</param>
/// <returns>The wallet balances</returns>
public BybitBalance GetWalletBalances()
public BybitBalance GetWalletBalances(BybitAccountType accountType)
{
if (accountType is not (BybitAccountType.Contract or BybitAccountType.Unified))
{
throw new ArgumentOutOfRangeException(nameof(accountType),
"Wallet balances can only be fetched for 'UNIFIED' and 'CONTRACT'");
}

var parameters = new KeyValuePair<string, string>[]
{
new("accountType", "UNIFIED")
new("accountType", accountType.ToStringInvariant().ToUpperInvariant())
};

var result =
Expand Down
2 changes: 1 addition & 1 deletion QuantConnect.BybitBrokerage/BybitBrokerage.Brokerage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ public override List<Holding> GetAccountHoldings()
public override List<CashAmount> GetCashBalance()
{
return ApiClient.Account
.GetWalletBalances().Assets
.GetWalletBalances(WalletAccountType).Assets
.Select(x => new CashAmount(x.WalletBalance, x.Asset)).ToList();
}

Expand Down
23 changes: 12 additions & 11 deletions QuantConnect.BybitBrokerage/BybitBrokerage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,6 @@ namespace QuantConnect.BybitBrokerage;
[BrokerageFactory(typeof(BybitBrokerageFactory))]
public partial class BybitBrokerage : BaseWebsocketsBrokerage, IDataQueueHandler
{
private static readonly List<BybitProductCategory> SupportedBybitProductCategories = new() { BybitProductCategory.Spot, BybitProductCategory.Linear, BybitProductCategory.Inverse };

private static readonly List<SecurityType> SuppotedSecurityTypes = new() { SecurityType.Crypto, SecurityType.CryptoFuture };

private static readonly string MarketName = Market.Bybit;

private readonly Dictionary<BybitProductCategory, BrokerageMultiWebSocketSubscriptionManager> _subscriptionManagers = new();

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

protected virtual string MarketName => Market.Bybit;
protected virtual BybitAccountType WalletAccountType => BybitAccountType.Unified;
protected virtual SecurityType[] SuppotedSecurityTypes { get; } = { SecurityType.Crypto, SecurityType.CryptoFuture };
protected virtual BybitProductCategory[] SupportedBybitProductCategories { get; } =
{ BybitProductCategory.Spot, BybitProductCategory.Linear };

/// <summary>
/// Order provider
/// </summary>
Expand All @@ -81,7 +81,7 @@ public partial class BybitBrokerage : BaseWebsocketsBrokerage, IDataQueueHandler
/// <summary>
/// Parameterless constructor for brokerage
/// </summary>
public BybitBrokerage() : base(MarketName)
public BybitBrokerage() : base(Market.Bybit)
{
}

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

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

return baseCanSubscribe;
Expand Down Expand Up @@ -457,7 +458,7 @@ private class ModulesReadLicenseRead : QuantConnect.Api.RestResponse
/// Checks whether the specified symbol is supported by this brokerage by its security type
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool IsSupported(Symbol symbol)
protected bool IsSupported(Symbol symbol)
{
return SuppotedSecurityTypes.Contains(symbol.SecurityType);
}
Expand Down
25 changes: 19 additions & 6 deletions QuantConnect.BybitBrokerage/BybitBrokerageFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,6 @@ namespace QuantConnect.BybitBrokerage
/// </summary>
public class BybitBrokerageFactory : BrokerageFactory
{
/// <summary>
/// The default order book depth for this brokerage
/// </summary>
protected virtual int DefaultOrderBookDepth => 50;

/// <summary>
/// Gets the brokerage data required to run the brokerage from configuration/disk
/// </summary>
Expand Down Expand Up @@ -101,12 +96,30 @@ public override IBrokerage CreateBrokerage(LiveNodePacket job, IAlgorithm algori
var aggregator = Composer.Instance.GetExportedValueByTypeName<IDataAggregator>(
Config.Get("data-aggregator", "QuantConnect.Lean.Engine.DataFeeds.AggregationManager"),
forceTypeNameOnExisting: false);
var brokerage = new BybitBrokerage(apiKey, apiSecret, apiUrl, wsUrl, algorithm, aggregator, job, vipLevel);
var brokerage = CreateBrokerage(apiKey, apiSecret, apiUrl, wsUrl, algorithm, aggregator, job, vipLevel);
Composer.Instance.AddPart<IDataQueueHandler>(brokerage);

return brokerage;
}

/// <summary>
/// Creates a new BybitBrokerage instance
/// </summary>
/// <param name="apiKey">The api key</param>
/// <param name="apiSecret">The api secret</param>
/// <param name="apiUrl">The rest api url</param>
/// <param name="wsUrl">The web socket base url</param>
/// <param name="algorithm">The algorithm instance is required to retrieve account type</param>
/// <param name="aggregator">The aggregator for consolidating ticks</param>
/// <param name="job">The live job packet</param>
/// <param name="vipLevel">Bybit VIP level</param>
/// <returns>New BybitBrokerage instance</returns>
protected virtual BybitBrokerage CreateBrokerage(string apiKey, string apiSecret, string apiUrl, string wsUrl,
IAlgorithm algorithm, IDataAggregator aggregator, LiveNodePacket job, BybitVIPLevel vipLevel)
{
return new BybitBrokerage(apiKey, apiSecret, apiUrl, wsUrl, algorithm, aggregator, job, vipLevel);
}

/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
Expand Down
81 changes: 81 additions & 0 deletions QuantConnect.BybitBrokerage/BybitInverseFuturesBrokerage.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/*
* 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.Brokerages;
using QuantConnect.BybitBrokerage.Models.Enums;
using QuantConnect.Data;
using QuantConnect.Interfaces;
using QuantConnect.Packets;
using QuantConnect.Securities;

namespace QuantConnect.BybitBrokerage;

/// <summary>
/// Bybit inverse futures brokerage implementation
/// </summary>
[BrokerageFactory(typeof(BybitInverseFuturesBrokerageFactory))]
public class BybitInverseFuturesBrokerage : BybitBrokerage
{
protected override SecurityType[] SuppotedSecurityTypes { get; } = { SecurityType.Crypto, SecurityType.CryptoFuture };
protected override BybitProductCategory[] SupportedBybitProductCategories { get; } = { BybitProductCategory.Inverse };
protected override BybitAccountType WalletAccountType => BybitAccountType.Contract;

/// <summary>
/// Parameterless constructor for brokerage
/// </summary>
public BybitInverseFuturesBrokerage()
{
}

/// <summary>
/// Constructor for brokerage
/// </summary>
/// <param name="apiKey">api key</param>
/// <param name="apiSecret">api secret</param>
/// <param name="restApiUrl">The rest api url</param>
/// <param name="webSocketBaseUrl">The web socket base url</param>
/// <param name="algorithm">the algorithm instance is required to retrieve account type</param>
/// <param name="aggregator">the aggregator for consolidating ticks</param>
/// <param name="job">The live job packet</param>
/// <param name="vipLevel">Bybit VIP level</param>
public BybitInverseFuturesBrokerage(string apiKey, string apiSecret, string restApiUrl, string webSocketBaseUrl,
IAlgorithm algorithm, IDataAggregator aggregator, LiveNodePacket job,
BybitVIPLevel vipLevel = BybitVIPLevel.VIP0)
: base(apiKey, apiSecret, restApiUrl, webSocketBaseUrl, algorithm, algorithm?.Portfolio?.Transactions,
algorithm?.Portfolio, aggregator, job, vipLevel)
{
}

/// <summary>
/// Constructor for brokerage
/// </summary>
/// <param name="apiKey">The api key</param>
/// <param name="apiSecret">The api secret</param>
/// <param name="restApiUrl">The rest api url</param>
/// <param name="webSocketBaseUrl">The web socket base url</param>
/// <param name="algorithm">The algorithm instance is required to retrieve account type</param>
/// <param name="orderProvider">The order provider is required to retrieve orders</param>
/// <param name="securityProvider">The security provider is required</param>
/// <param name="aggregator">The aggregator for consolidating ticks</param>
/// <param name="job">The live job packet</param>
/// <param name="vipLevel">Bybit VIP level</param>
public BybitInverseFuturesBrokerage(string apiKey, string apiSecret, string restApiUrl, string webSocketBaseUrl,
IAlgorithm algorithm, IOrderProvider orderProvider, ISecurityProvider securityProvider,
IDataAggregator aggregator, LiveNodePacket job, BybitVIPLevel vipLevel = BybitVIPLevel.VIP0)
: base(apiKey, apiSecret, restApiUrl, webSocketBaseUrl, algorithm, orderProvider,
securityProvider, aggregator, job, vipLevel)
{
}
}
Loading

0 comments on commit e6bcfe7

Please sign in to comment.