diff --git a/QuantConnect.BybitBrokerage.Tests/BybitInverseFuturesBrokerageHistoryProviderTests.cs b/QuantConnect.BybitBrokerage.Tests/BybitInverseFuturesBrokerageHistoryProviderTests.cs new file mode 100644 index 0000000..0bbe66c --- /dev/null +++ b/QuantConnect.BybitBrokerage.Tests/BybitInverseFuturesBrokerageHistoryProviderTests.cs @@ -0,0 +1,58 @@ +/* + * 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, false), + new TestCaseData(ETHUSD, Resolution.Minute, Time.OneHour, TickType.Trade, false), + new TestCaseData(ETHUSD, Resolution.Hour, Time.OneDay, TickType.Trade, false), + new TestCaseData(ETHUSD, Resolution.Daily, TimeSpan.FromDays(15), TickType.Trade, false), + new TestCaseData(ETHUSD, Resolution.Hour, Time.OneDay, TickType.OpenInterest, false) + }; + } + } + + + [Test, TestCaseSource(nameof(ValidHistory))] + public override void GetsHistory(Symbol symbol, Resolution resolution, TimeSpan period, TickType tickType, + bool throwsException) + { + base.GetsHistory(symbol, resolution, period, tickType, throwsException); + } + + [Ignore("Same as base")] + public override void GetEmptyHistory(Symbol symbol, Resolution resolution, TimeSpan period, TickType tickType, + bool throwsException) + { + base.GetEmptyHistory(symbol, resolution, period, tickType, throwsException); + } + } +} \ No newline at end of file diff --git a/QuantConnect.BybitBrokerage.Tests/BybitInverseFuturesBrokerageTests.Stream.cs b/QuantConnect.BybitBrokerage.Tests/BybitInverseFuturesBrokerageTests.Stream.cs new file mode 100644 index 0000000..0bdf798 --- /dev/null +++ b/QuantConnect.BybitBrokerage.Tests/BybitInverseFuturesBrokerageTests.Stream.cs @@ -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); + } + } +} \ No newline at end of file diff --git a/QuantConnect.BybitBrokerage.Tests/BybitInverseFuturesBrokerageTests.cs b/QuantConnect.BybitBrokerage.Tests/BybitInverseFuturesBrokerageTests.cs new file mode 100644 index 0000000..6231156 --- /dev/null +++ b/QuantConnect.BybitBrokerage.Tests/BybitInverseFuturesBrokerageTests.cs @@ -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; + + + /// + /// Provides the data required to test each order type in various cases + /// + 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("Base")] + public override void GetAccountHoldings() + { + base.GetAccountHoldings(); + } +} \ No newline at end of file diff --git a/QuantConnect.BybitBrokerage/Api/BybitPositionApiEndpoint.cs b/QuantConnect.BybitBrokerage/Api/BybitPositionApiEndpoint.cs index 363d3f6..b75a823 100644 --- a/QuantConnect.BybitBrokerage/Api/BybitPositionApiEndpoint.cs +++ b/QuantConnect.BybitBrokerage/Api/BybitPositionApiEndpoint.cs @@ -49,10 +49,33 @@ public IEnumerable GetPositions(BybitProductCategory category { if (category == BybitProductCategory.Spot) return Array.Empty(); - var parameters = new KeyValuePair[] + var parameters = new List>(); + + if (category == BybitProductCategory.Linear) { - new("settleCoin", "USDT") - }; + parameters.Add(KeyValuePair.Create("settleCoin", "USDT")); + } + return FetchAll("/position/list", category, 200, parameters, true); } + + /// + /// 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. + /// + /// The product category + /// The symbol for which the mode should be changed + /// The mode which should be set + public void SwitchPositionMode(BybitProductCategory category, Symbol symbol, PositionMode mode) + { + var ticker = SymbolMapper.GetBrokerageSymbol(symbol); + var requestBody = new + { + category, + mode = (int)mode, + symbol = ticker + }; + + ExecutePostRequest("/position/switch-mode", requestBody); + } } \ No newline at end of file diff --git a/QuantConnect.BybitBrokerage/BybitBrokerage.Messaging.cs b/QuantConnect.BybitBrokerage/BybitBrokerage.Messaging.cs index e36dcff..87dd524 100644 --- a/QuantConnect.BybitBrokerage/BybitBrokerage.Messaging.cs +++ b/QuantConnect.BybitBrokerage/BybitBrokerage.Messaging.cs @@ -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") }; @@ -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; diff --git a/QuantConnect.BybitBrokerage/BybitBrokerage.cs b/QuantConnect.BybitBrokerage/BybitBrokerage.cs index 2dacbb1..a9d401c 100644 --- a/QuantConnect.BybitBrokerage/BybitBrokerage.cs +++ b/QuantConnect.BybitBrokerage/BybitBrokerage.cs @@ -48,7 +48,7 @@ namespace QuantConnect.BybitBrokerage; [BrokerageFactory(typeof(BybitBrokerageFactory))] public partial class BybitBrokerage : BaseWebsocketsBrokerage, IDataQueueHandler { - private static readonly List SupportedBybitProductCategories = new() { BybitProductCategory.Spot, BybitProductCategory.Linear }; + private static readonly List SupportedBybitProductCategories = new() { BybitProductCategory.Spot, BybitProductCategory.Linear, BybitProductCategory.Inverse }; private static readonly List SuppotedSecurityTypes = new() { SecurityType.Crypto, SecurityType.CryptoFuture }; @@ -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; @@ -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"); diff --git a/QuantConnect.BybitBrokerage/Models/ByBitResponse.cs b/QuantConnect.BybitBrokerage/Models/ByBitResponse.cs index c8d0584..b26d1fb 100644 --- a/QuantConnect.BybitBrokerage/Models/ByBitResponse.cs +++ b/QuantConnect.BybitBrokerage/Models/ByBitResponse.cs @@ -22,8 +22,7 @@ namespace QuantConnect.BybitBrokerage.Models /// /// Bybits default http response message /// - /// - public class ByBitResponse + public class ByBitResponse { /// /// Success/Error code @@ -43,16 +42,25 @@ public class ByBitResponse /// [JsonProperty("retExtInfo")] public object ExtendedInfo { get; set; } - - /// - /// Business data result - /// - public T Result { get; set; } - + /// /// Current time /// [JsonConverter(typeof(BybitTimeConverter))] public DateTime Time { get; set; } } + + /// + /// Bybits default http data response message + /// + /// + public class ByBitResponse : ByBitResponse + { + /// + /// Business data result + /// + public T Result { get; set; } + + } + } \ No newline at end of file diff --git a/QuantConnect.BybitBrokerage/Models/Enums/PositionMode.cs b/QuantConnect.BybitBrokerage/Models/Enums/PositionMode.cs new file mode 100644 index 0000000..04d9973 --- /dev/null +++ b/QuantConnect.BybitBrokerage/Models/Enums/PositionMode.cs @@ -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; + +/// +/// Bybit position mode +/// +public enum PositionMode +{ + /// + /// One way mode + /// + MergedSingle = 0, + /// + /// Hedge mode + /// + BothSides = 3, +} \ No newline at end of file