Skip to content

Commit

Permalink
Add support for inverse futures
Browse files Browse the repository at this point in the history
  • Loading branch information
arodus committed Nov 2, 2023
1 parent b57b6bc commit 27b3f59
Show file tree
Hide file tree
Showing 8 changed files with 288 additions and 21 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* 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;

namespace QuantConnect.BybitBrokerage.Tests
{
[TestFixture, Explicit("Requires valid credentials to be setup and run outside USA")]
public class BybitInverseFuturesBrokerageHistoryProviderTests : BybitBrokerageHistoryProviderTests
{
private static readonly Symbol ETHUSD = Symbol.Create("ETHUSDT", SecurityType.CryptoFuture, Market.Bybit);


private static TestCaseData[] ValidHistory
{
get
{
return new[]
{
// valid
new TestCaseData(ETHUSD, Resolution.Tick, Time.OneMinute, TickType.Trade),
new TestCaseData(ETHUSD, Resolution.Minute, Time.OneHour, TickType.Trade),
new TestCaseData(ETHUSD, Resolution.Hour, Time.OneDay, TickType.Trade),
new TestCaseData(ETHUSD, Resolution.Daily, TimeSpan.FromDays(15), TickType.Trade),
new TestCaseData(ETHUSD, Resolution.Hour, Time.OneDay, TickType.OpenInterest)
};
}
}


[Test, TestCaseSource(nameof(ValidHistory))]
public override void GetsHistory(Symbol symbol, Resolution resolution, TimeSpan period, TickType tickType)
{
base.GetsHistory(symbol, resolution, period, tickType);
}

[Ignore("The brokerage is shared between different product categories, therefore this test is only required in the base class")]
public override void GetEmptyHistory(Symbol symbol, Resolution resolution, TimeSpan period, TickType tickType)
{
base.GetEmptyHistory(symbol, resolution, period, tickType);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* 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;

namespace QuantConnect.BybitBrokerage.Tests
{
[TestFixture]
public partial class BybitInverseFuturesBrokerageTests
{
private static TestCaseData[] TestParameters
{
get
{
return new[]
{
// valid parameters, for example
new TestCaseData(BTCUSD, Resolution.Tick, false),
new TestCaseData(BTCUSD, Resolution.Minute, true),
new TestCaseData(BTCUSD, Resolution.Second, true),
};
}
}

[Test, TestCaseSource(nameof(TestParameters))]
public override void StreamsData(Symbol symbol, Resolution resolution, bool throwsException)
{
base.StreamsData(symbol, resolution, throwsException);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/*
* 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.BybitBrokerage.Models.Enums;
using QuantConnect.Tests.Brokerages;

namespace QuantConnect.BybitBrokerage.Tests;

[TestFixture, Explicit("Requires valid credentials to be setup and run outside USA")]
public partial class BybitInverseFuturesBrokerageTests : BybitBrokerageTests
{
private static Symbol BTCUSD = Symbol.Create("BTCUSD", SecurityType.CryptoFuture, "bybit");
protected override Symbol Symbol { get; } = BTCUSD;

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

protected override decimal GetDefaultQuantity() => 10m;


/// <summary>
/// Provides the data required to test each order type in various cases
/// </summary>
private static TestCaseData[] OrderParameters()
{
return new[]
{
new TestCaseData(new MarketOrderTestParameters(BTCUSD)).SetName("MarketOrder"),
new TestCaseData(new LimitOrderTestParameters(BTCUSD, 50000m, 10000m)).SetName("LimitOrder"),
new TestCaseData(new StopMarketOrderTestParameters(BTCUSD, 50000m, 10000m)).SetName("StopMarketOrder"),
new TestCaseData(new StopLimitOrderTestParameters(BTCUSD, 50000m, 10000m)).SetName("StopLimitOrder"),
new TestCaseData(new LimitIfTouchedOrderTestParameters(BTCUSD, 50000m, 20000)).SetName(
"LimitIfTouchedOrder")
};
}


[Test, TestCaseSource(nameof(OrderParameters))]
public override void CancelOrders(OrderTestParameters parameters)
{
base.CancelOrders(parameters);
}

[Test, TestCaseSource(nameof(OrderParameters))]
public override void LongFromZero(OrderTestParameters parameters)
{
base.LongFromZero(parameters);
}

[Test, TestCaseSource(nameof(OrderParameters))]
public override void CloseFromLong(OrderTestParameters parameters)
{
base.CloseFromLong(parameters);
}

[Test, TestCaseSource(nameof(OrderParameters))]
public override void ShortFromZero(OrderTestParameters parameters)
{
base.ShortFromZero(parameters);
}

[Test, TestCaseSource(nameof(OrderParameters))]
public override void CloseFromShort(OrderTestParameters parameters)
{
base.CloseFromShort(parameters);
}

[Test, TestCaseSource(nameof(OrderParameters))]
public override void ShortFromLong(OrderTestParameters parameters)
{
base.ShortFromLong(parameters);
}

[Test, TestCaseSource(nameof(OrderParameters))]
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")]
public override void GetAccountHoldings()
{
base.GetAccountHoldings();
}
}
29 changes: 26 additions & 3 deletions QuantConnect.BybitBrokerage/Api/BybitPositionApiEndpoint.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,33 @@ public IEnumerable<BybitPositionInfo> GetPositions(BybitProductCategory category
{
if (category == BybitProductCategory.Spot) return Array.Empty<BybitPositionInfo>();

var parameters = new KeyValuePair<string, string>[]
var parameters = new List<KeyValuePair<string, string>>();

if (category == BybitProductCategory.Linear)
{
new("settleCoin", "USDT")
};
parameters.Add(KeyValuePair.Create("settleCoin", "USDT"));
}

return FetchAll<BybitPositionInfo>("/position/list", category, 200, parameters, true);
}

/// <summary>
/// It supports to switch the position mode for USDT perpetual and Inverse futures.
/// If you are in one-way Mode, you can only open one position on Buy or Sell side. If you are in hedge mode, you can open both Buy and Sell side positions simultaneously.
/// </summary>
/// <param name="category">The product category</param>
/// <param name="symbol">The symbol for which the mode should be changed</param>
/// <param name="mode">The mode which should be set</param>
public void SwitchPositionMode(BybitProductCategory category, Symbol symbol, PositionMode mode)
{
var ticker = SymbolMapper.GetBrokerageSymbol(symbol);
var requestBody = new
{
category,
mode = (int)mode,
symbol = ticker
};

ExecutePostRequest<ByBitResponse>("/position/switch-mode", requestBody);
}
}
4 changes: 2 additions & 2 deletions QuantConnect.BybitBrokerage/BybitBrokerage.Messaging.cs
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ private void HandleOrderExecution(JToken message)
var currency = tradeUpdate.Category switch
{
BybitProductCategory.Linear => "USDT",
BybitProductCategory.Inverse => GetBaseCurrency(symbol),
BybitProductCategory.Inverse => GetBaseCurrency(leanSymbol),
BybitProductCategory.Spot => GetSpotFeeCurrency(leanSymbol, tradeUpdate),
_ => throw new NotSupportedException($"category {tradeUpdate.Category} not implemented")
};
Expand Down Expand Up @@ -178,7 +178,7 @@ static string GetSpotFeeCurrency(Symbol symbol, BybitTradeUpdate tradeUpdate)
return tradeUpdate.Side == OrderSide.Buy ? quote : @base;
}

static string GetBaseCurrency(string pair)
static string GetBaseCurrency(Symbol pair)
{
CurrencyPairUtil.DecomposeCurrencyPair(pair, out var baseCurrency, out _);
return baseCurrency;
Expand Down
21 changes: 13 additions & 8 deletions QuantConnect.BybitBrokerage/BybitBrokerage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ namespace QuantConnect.BybitBrokerage;
[BrokerageFactory(typeof(BybitBrokerageFactory))]
public partial class BybitBrokerage : BaseWebsocketsBrokerage, IDataQueueHandler
{
private static readonly List<BybitProductCategory> SupportedBybitProductCategories = new() { BybitProductCategory.Spot, BybitProductCategory.Linear };
private static readonly List<BybitProductCategory> SupportedBybitProductCategories = new() { BybitProductCategory.Spot, BybitProductCategory.Linear, BybitProductCategory.Inverse };

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

Expand Down Expand Up @@ -307,8 +307,8 @@ protected virtual bool CanSubscribe(Symbol symbol)

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

return baseCanSubscribe;
Expand Down Expand Up @@ -483,13 +483,18 @@ private static BybitProductCategory GetBybitProductCategory(Symbol symbol)
return BybitProductCategory.Spot;

case SecurityType.CryptoFuture:
if (!CurrencyPairUtil.TryDecomposeCurrencyPair(symbol, out _, out var quoteCurrency) ||
quoteCurrency != "USDT")
if (CurrencyPairUtil.TryDecomposeCurrencyPair(symbol, out _, out var quoteCurrency))
{
throw new ArgumentException($"Invalid symbol: {symbol}. Only linear futures are supported.");
if (quoteCurrency == "USDT")
{
return BybitProductCategory.Linear;
}
if (quoteCurrency == "USD")
{
return BybitProductCategory.Inverse;
}
}

return BybitProductCategory.Linear;
throw new ArgumentException($"Invalid symbol: {symbol}. Only linear futures are supported.");

default:
throw new ArgumentOutOfRangeException(nameof(symbol), symbol, "Not supported security type");
Expand Down
24 changes: 16 additions & 8 deletions QuantConnect.BybitBrokerage/Models/ByBitResponse.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,7 @@ namespace QuantConnect.BybitBrokerage.Models
/// <summary>
/// Bybits default http response message
/// </summary>
/// <typeparam name="T"></typeparam>
public class ByBitResponse<T>
public class ByBitResponse
{
/// <summary>
/// Success/Error code
Expand All @@ -43,16 +42,25 @@ public class ByBitResponse<T>
/// </summary>
[JsonProperty("retExtInfo")]
public object ExtendedInfo { get; set; }

/// <summary>
/// Business data result
/// </summary>
public T Result { get; set; }


/// <summary>
/// Current time
/// </summary>
[JsonConverter(typeof(BybitTimeConverter))]
public DateTime Time { get; set; }
}

/// <summary>
/// Bybits default http data response message
/// </summary>
/// <typeparam name="T"></typeparam>
public class ByBitResponse<T> : ByBitResponse
{
/// <summary>
/// Business data result
/// </summary>
public T Result { get; set; }

}

}
33 changes: 33 additions & 0 deletions QuantConnect.BybitBrokerage/Models/Enums/PositionMode.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* 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.Runtime.Serialization;

namespace QuantConnect.BybitBrokerage.Models.Enums;

/// <summary>
/// Bybit position mode
/// </summary>
public enum PositionMode
{
/// <summary>
/// One way mode
/// </summary>
MergedSingle = 0,
/// <summary>
/// Hedge mode
/// </summary>
BothSides = 3,
}

0 comments on commit 27b3f59

Please sign in to comment.