Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions ByBit.Net/Bybit.Net.csproj
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net8.0;net9.0;net10.0;netstandard2.0;netstandard2.1</TargetFrameworks>
<Nullable>enable</Nullable>
Expand Down Expand Up @@ -52,7 +52,7 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="CryptoExchange.Net" Version="10.3.0" />
<PackageReference Include="CryptoExchange.Net" Version="10.4.0" />
<PackageReference Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="10.0.101">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
Expand Down
2 changes: 1 addition & 1 deletion ByBit.Net/BybitEnvironment.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ public BybitEnvironment() : base(TradeEnvironmentNames.Live)
TradeEnvironmentNames.Live => Live,
TradeEnvironmentNames.Testnet => Testnet,
"Eu" => Eu,
"Hongkong" => HongKong,
"HongKong" => HongKong,
"Turkey" => Turkey,
"Kazakhstan" => Kazakhstan,
"Georgia" => Georgia,
Expand Down
64 changes: 64 additions & 0 deletions ByBit.Net/BybitTrackerFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,16 @@
using Bybit.Net.Interfaces;
using Bybit.Net.Interfaces.Clients;
using CryptoExchange.Net;
using CryptoExchange.Net.Authentication;
using CryptoExchange.Net.SharedApis;
using CryptoExchange.Net.Trackers.Klines;
using CryptoExchange.Net.Trackers.Trades;
using CryptoExchange.Net.Trackers.UserData;
using CryptoExchange.Net.Trackers.UserData.Interfaces;
using CryptoExchange.Net.Trackers.UserData.Objects;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using System;

namespace Bybit.Net
Expand Down Expand Up @@ -116,5 +121,64 @@ public ITradeTracker CreateTradeTracker(SharedSymbol symbol, int? limit = null,
period
);
}

/// <inheritdoc />
public IUserSpotDataTracker CreateUserSpotDataTracker(SpotUserDataTrackerConfig? config = null)
{
var restClient = _serviceProvider?.GetRequiredService<IBybitRestClient>() ?? new BybitRestClient();
var socketClient = _serviceProvider?.GetRequiredService<IBybitSocketClient>() ?? new BybitSocketClient();
return new BybitUserSpotDataTracker(
_serviceProvider?.GetRequiredService<ILogger<BybitUserSpotDataTracker>>() ?? new NullLogger<BybitUserSpotDataTracker>(),
restClient,
socketClient,
null,
config
);
}

/// <inheritdoc />
public IUserSpotDataTracker CreateUserSpotDataTracker(string userIdentifier, ApiCredentials credentials, SpotUserDataTrackerConfig? config = null, BybitEnvironment? environment = null)
{
var clientProvider = _serviceProvider?.GetRequiredService<IBybitUserClientProvider>() ?? new BybitUserClientProvider();
var restClient = clientProvider.GetRestClient(userIdentifier, credentials, environment);
var socketClient = clientProvider.GetSocketClient(userIdentifier, credentials, environment);
return new BybitUserSpotDataTracker(
_serviceProvider?.GetRequiredService<ILogger<BybitUserSpotDataTracker>>() ?? new NullLogger<BybitUserSpotDataTracker>(),
restClient,
socketClient,
userIdentifier,
config
);
}

/// <inheritdoc />
public IUserFuturesDataTracker CreateUserFuturesDataTracker(FuturesUserDataTrackerConfig? config = null)
{
var restClient = _serviceProvider?.GetRequiredService<IBybitRestClient>() ?? new BybitRestClient();
var socketClient = _serviceProvider?.GetRequiredService<IBybitSocketClient>() ?? new BybitSocketClient();
return new BybitUserFuturesDataTracker(
_serviceProvider?.GetRequiredService<ILogger<BybitUserFuturesDataTracker>>() ?? new NullLogger<BybitUserFuturesDataTracker>(),
restClient,
socketClient,
null,
config
);
}

/// <inheritdoc />
public IUserFuturesDataTracker CreateUserFuturesDataTracker(string userIdentifier, ApiCredentials credentials, FuturesUserDataTrackerConfig? config = null, BybitEnvironment? environment = null)
{
var clientProvider = _serviceProvider?.GetRequiredService<IBybitUserClientProvider>() ?? new BybitUserClientProvider();
var restClient = clientProvider.GetRestClient(userIdentifier, credentials, environment);
var socketClient = clientProvider.GetSocketClient(userIdentifier, credentials, environment);
return new BybitUserFuturesDataTracker(
_serviceProvider?.GetRequiredService<ILogger<BybitUserFuturesDataTracker>>() ?? new NullLogger<BybitUserFuturesDataTracker>(),
restClient,
socketClient,
userIdentifier,
config
);
}

}
}
63 changes: 63 additions & 0 deletions ByBit.Net/BybitUserDataTracker.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
using Bybit.Net.Interfaces.Clients;
using CryptoExchange.Net.SharedApis;
using CryptoExchange.Net.Trackers.UserData;
using CryptoExchange.Net.Trackers.UserData.Objects;
using Microsoft.Extensions.Logging;

namespace Bybit.Net
{
/// <inheritdoc/>
public class BybitUserSpotDataTracker : UserSpotDataTracker
{
/// <summary>
/// ctor
/// </summary>
public BybitUserSpotDataTracker(
ILogger<BybitUserSpotDataTracker> logger,
IBybitRestClient restClient,
IBybitSocketClient socketClient,
string? userIdentifier,
SpotUserDataTrackerConfig? config) : base(
logger,
restClient.V5Api.SharedClient,
null,
restClient.V5Api.SharedClient,
socketClient.V5PrivateApi.SharedClient,
restClient.V5Api.SharedClient,
socketClient.V5PrivateApi.SharedClient,
socketClient.V5PrivateApi.SharedClient,
userIdentifier,
config ?? new SpotUserDataTrackerConfig())
{
}
}

/// <inheritdoc/>
public class BybitUserFuturesDataTracker : UserFuturesDataTracker
{
/// <inheritdoc/>
protected override bool WebsocketPositionUpdatesAreFullSnapshots => false;

/// <summary>
/// ctor
/// </summary>
public BybitUserFuturesDataTracker(
ILogger<BybitUserFuturesDataTracker> logger,
IBybitRestClient restClient,
IBybitSocketClient socketClient,
string? userIdentifier,
FuturesUserDataTrackerConfig? config) : base(logger,
restClient.V5Api.SharedClient,
null,
restClient.V5Api.SharedClient,
socketClient.V5PrivateApi.SharedClient,
restClient.V5Api.SharedClient,
socketClient.V5PrivateApi.SharedClient,
socketClient.V5PrivateApi.SharedClient,
socketClient.V5PrivateApi.SharedClient,
userIdentifier,
config ?? new FuturesUserDataTrackerConfig())
{
}
}
}
4 changes: 2 additions & 2 deletions ByBit.Net/Clients/BybitUserClientProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ public void ClearUserClients(string userIdentifier)
/// <inheritdoc />
public IBybitRestClient GetRestClient(string userIdentifier, ApiCredentials? credentials = null, BybitEnvironment? environment = null)
{
if (!_restClients.TryGetValue(userIdentifier, out var client))
if (!_restClients.TryGetValue(userIdentifier, out var client) || client.Disposed)
client = CreateRestClient(userIdentifier, credentials, environment);

return client;
Expand All @@ -73,7 +73,7 @@ public IBybitRestClient GetRestClient(string userIdentifier, ApiCredentials? cre
/// <inheritdoc />
public IBybitSocketClient GetSocketClient(string userIdentifier, ApiCredentials? credentials = null, BybitEnvironment? environment = null)
{
if (!_socketClients.TryGetValue(userIdentifier, out var client))
if (!_socketClients.TryGetValue(userIdentifier, out var client) || client.Disposed)
client = CreateSocketClient(userIdentifier, credentials, environment);

return client;
Expand Down
87 changes: 86 additions & 1 deletion ByBit.Net/Clients/V5/BybitRestClientApiShared.cs
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,44 @@ async Task<ExchangeWebResult<SharedSpotSymbol[]>> ISpotSymbolRestClient.GetSpotS
return response;
}

async Task<ExchangeResult<SharedSymbol[]>> ISpotSymbolRestClient.GetSpotSymbolsForBaseAssetAsync(string baseAsset)
{
if (!ExchangeSymbolCache.HasCached(_topicSpotId))
{
var symbols = await ((ISpotSymbolRestClient)this).GetSpotSymbolsAsync(new GetSymbolsRequest()).ConfigureAwait(false);
if (!symbols)
return new ExchangeResult<SharedSymbol[]>(Exchange, symbols.Error!);
}

return new ExchangeResult<SharedSymbol[]>(Exchange, ExchangeSymbolCache.GetSymbolsForBaseAsset(_topicSpotId, baseAsset));
}

async Task<ExchangeResult<bool>> ISpotSymbolRestClient.SupportsSpotSymbolAsync(SharedSymbol symbol)
{
if (symbol.TradingMode != TradingMode.Spot)
throw new ArgumentException(nameof(symbol), "Only Spot symbols allowed");

if (!ExchangeSymbolCache.HasCached(_topicSpotId))
{
var symbols = await ((ISpotSymbolRestClient)this).GetSpotSymbolsAsync(new GetSymbolsRequest()).ConfigureAwait(false);
if (!symbols)
return new ExchangeResult<bool>(Exchange, symbols.Error!);
}

return new ExchangeResult<bool>(Exchange, ExchangeSymbolCache.SupportsSymbol(_topicSpotId, symbol));
}

async Task<ExchangeResult<bool>> ISpotSymbolRestClient.SupportsSpotSymbolAsync(string symbolName)
{
if (!ExchangeSymbolCache.HasCached(_topicSpotId))
{
var symbols = await ((ISpotSymbolRestClient)this).GetSpotSymbolsAsync(new GetSymbolsRequest()).ConfigureAwait(false);
if (!symbols)
return new ExchangeResult<bool>(Exchange, symbols.Error!);
}

return new ExchangeResult<bool>(Exchange, ExchangeSymbolCache.SupportsSymbol(_topicSpotId, symbolName));
}
#endregion

#region Spot Ticker client
Expand Down Expand Up @@ -710,7 +748,15 @@ async Task<ExchangeWebResult<SharedDeposit[]>> IDepositRestClient.GetDepositsAsy
if (!string.IsNullOrEmpty(deposits.Data.NextPageCursor))
nextToken = new CursorToken(deposits.Data.NextPageCursor!);

return deposits.AsExchangeResult<SharedDeposit[]>(Exchange, TradingMode.Spot, deposits.Data.Deposits.Select(x => new SharedDeposit(x.Asset, x.Quantity, x.Status == DepositStatus.Success, x.SuccessTime ?? new DateTime())
return deposits.AsExchangeResult<SharedDeposit[]>(Exchange, TradingMode.Spot, deposits.Data.Deposits.Select(x =>
new SharedDeposit(
x.Asset,
x.Quantity,
x.Status == DepositStatus.Success,
x.SuccessTime ?? new DateTime(),
x.Status == DepositStatus.Success ? SharedTransferStatus.Completed
: x.Status == DepositStatus.DepositFailed ? SharedTransferStatus.Failed
: SharedTransferStatus.InProgress)
{
Network = x.Network,
TransactionId = x.TransactionId,
Expand Down Expand Up @@ -930,6 +976,44 @@ async Task<ExchangeWebResult<SharedFuturesSymbol[]>> IFuturesSymbolRestClient.Ge
return response;
}

async Task<ExchangeResult<SharedSymbol[]>> IFuturesSymbolRestClient.GetFuturesSymbolsForBaseAssetAsync(string baseAsset)
{
if (!ExchangeSymbolCache.HasCached(_topicFuturesId))
{
var symbols = await ((IFuturesSymbolRestClient)this).GetFuturesSymbolsAsync(new GetSymbolsRequest()).ConfigureAwait(false);
if (!symbols)
return new ExchangeResult<SharedSymbol[]>(Exchange, symbols.Error!);
}

return new ExchangeResult<SharedSymbol[]>(Exchange, ExchangeSymbolCache.GetSymbolsForBaseAsset(_topicFuturesId, baseAsset));
}

async Task<ExchangeResult<bool>> IFuturesSymbolRestClient.SupportsFuturesSymbolAsync(SharedSymbol symbol)
{
if (symbol.TradingMode == TradingMode.Spot)
throw new ArgumentException(nameof(symbol), "Spot symbols not allowed");

if (!ExchangeSymbolCache.HasCached(_topicFuturesId))
{
var symbols = await ((IFuturesSymbolRestClient)this).GetFuturesSymbolsAsync(new GetSymbolsRequest()).ConfigureAwait(false);
if (!symbols)
return new ExchangeResult<bool>(Exchange, symbols.Error!);
}

return new ExchangeResult<bool>(Exchange, ExchangeSymbolCache.SupportsSymbol(_topicFuturesId, symbol));
}

async Task<ExchangeResult<bool>> IFuturesSymbolRestClient.SupportsFuturesSymbolAsync(string symbolName)
{
if (!ExchangeSymbolCache.HasCached(_topicFuturesId))
{
var symbols = await ((IFuturesSymbolRestClient)this).GetFuturesSymbolsAsync(new GetSymbolsRequest()).ConfigureAwait(false);
if (!symbols)
return new ExchangeResult<bool>(Exchange, symbols.Error!);
}

return new ExchangeResult<bool>(Exchange, ExchangeSymbolCache.SupportsSymbol(_topicFuturesId, symbolName));
}
#endregion

#region Leverage client
Expand Down Expand Up @@ -1490,6 +1574,7 @@ async Task<ExchangeWebResult<SharedPosition[]>> IFuturesOrderRestClient.GetPosit
Leverage = x.Leverage,
StopLossPrice = x.StopLoss,
TakeProfitPrice = x.TakeProfit,
PositionMode = x.PositionIdx == PositionIdx.OneWayMode ? SharedPositionMode.OneWay : SharedPositionMode.HedgeMode,
PositionSide = x.Side == PositionSide.None ? SharedPositionSide.Long : x.Side == PositionSide.Sell ? SharedPositionSide.Short : SharedPositionSide.Long
}).ToArray());
}
Expand Down
1 change: 1 addition & 0 deletions ByBit.Net/Clients/V5/BybitSocketClientBaseApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ internal BybitSocketClientBaseApi(ILogger log, BybitSocketOptions options, strin
// For demo trading the live environment should be used for market data
_wsPublicAddress = options.Environment.Name == BybitEnvironment.DemoTrading.Name ? BybitEnvironment.Live.SocketBaseAddress : options.Environment.SocketBaseAddress;

_clientName = "BybitSocketClientApi";

UnhandledMessageExpected = true;
KeepAliveInterval = TimeSpan.Zero;
Expand Down
2 changes: 2 additions & 0 deletions ByBit.Net/Clients/V5/BybitSocketClientPrivateApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ internal BybitSocketClientPrivateApi(ILogger logger, BybitSocketOptions options)
UnhandledMessageExpected = true;
KeepAliveInterval = TimeSpan.Zero;

_clientName = "BybitSocketClientApi";

RegisterPeriodicQuery(
"Heartbeat",
options.V5Options.PingInterval,
Expand Down
1 change: 1 addition & 0 deletions ByBit.Net/Clients/V5/BybitSocketClientPrivateApiShared.cs
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,7 @@ async Task<ExchangeResult<UpdateSubscription>> IPositionSocketClient.SubscribeTo
update => handler(update.ToType(update.Data.Select(x => new SharedPosition(ExchangeSymbolCache.ParseSymbol(_topicFuturesId, x.Symbol), x.Symbol, x.Quantity, x.UpdateTime)
{
AverageOpenPrice = x.AveragePrice,
PositionMode = x.PositionIdx == PositionIdx.OneWayMode ? SharedPositionMode.OneWay : SharedPositionMode.HedgeMode,
PositionSide = x.PositionIdx == Enums.PositionIdx.OneWayMode ? (x.Side == Enums.PositionSide.Sell ? SharedPositionSide.Short : SharedPositionSide.Long) : x.PositionIdx == Enums.PositionIdx.BuyHedgeMode ? SharedPositionSide.Long : SharedPositionSide.Short,
LiquidationPrice = x.LiquidationPrice,
Leverage = x.Leverage,
Expand Down
2 changes: 2 additions & 0 deletions ByBit.Net/Clients/V5/BybitSocketClientSpreadApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ internal BybitSocketClientSpreadApi(ILogger log, BybitSocketOptions options)
KeepAliveInterval = TimeSpan.Zero; // Server doesn't respond to ping frames
_wsPublicAddress = options.Environment.Name == BybitEnvironment.DemoTrading.Name ? BybitEnvironment.Live.SocketBaseAddress : options.Environment.SocketBaseAddress;

_clientName = "BybitSocketClientApi";

RegisterPeriodicQuery(
"Heartbeat",
TimeSpan.FromSeconds(20),
Expand Down
1 change: 1 addition & 0 deletions ByBit.Net/Converters/BybitSourceGenerationContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ namespace Bybit.Net.Converters
[JsonSerializable(typeof(BybitRequestQueryResponse<BybitOrderId>))]
[JsonSerializable(typeof(BybitRequestQueryResponse<BybitList<BybitBatchOrderId>>))]
[JsonSerializable(typeof(Dictionary<string, string[]>))]
[JsonSerializable(typeof(Dictionary<string, List<string>>))]
[JsonSerializable(typeof(BybitResult<BybitList<BybitSpreadSymbol>>))]
[JsonSerializable(typeof(BybitResult<BybitList<BybitSpreadTicker>>))]
[JsonSerializable(typeof(BybitResult<BybitList<BybitSpreadTrade>>))]
Expand Down
32 changes: 31 additions & 1 deletion ByBit.Net/Interfaces/IBybitTrackerFactory.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
using CryptoExchange.Net.Interfaces;
using CryptoExchange.Net.Authentication;
using CryptoExchange.Net.Interfaces;
using CryptoExchange.Net.Trackers.UserData;
using CryptoExchange.Net.Trackers.UserData.Interfaces;
using CryptoExchange.Net.Trackers.UserData.Objects;

namespace Bybit.Net.Interfaces
{
Expand All @@ -7,5 +11,31 @@ namespace Bybit.Net.Interfaces
/// </summary>
public interface IBybitTrackerFactory : ITrackerFactory
{
/// <summary>
/// Create a new Spot user data tracker
/// </summary>
/// <param name="userIdentifier">User identifier</param>
/// <param name="config">Configuration</param>
/// <param name="credentials">Credentials</param>
/// <param name="environment">Environment</param>
IUserSpotDataTracker CreateUserSpotDataTracker(string userIdentifier, ApiCredentials credentials, SpotUserDataTrackerConfig? config = null, BybitEnvironment? environment = null);
/// <summary>
/// Create a new spot user data tracker
/// </summary>
/// <param name="config">Configuration</param>
IUserSpotDataTracker CreateUserSpotDataTracker(SpotUserDataTrackerConfig? config = null);
/// <summary>
/// Create a new futures user data tracker
/// </summary>
/// <param name="userIdentifier">User identifier</param>
/// <param name="config">Configuration</param>
/// <param name="credentials">Credentials</param>
/// <param name="environment">Environment</param>
IUserFuturesDataTracker CreateUserFuturesDataTracker(string userIdentifier, ApiCredentials credentials, FuturesUserDataTrackerConfig? config = null, BybitEnvironment? environment = null);
/// <summary>
/// Create a new futures user data tracker
/// </summary>
/// <param name="config">Configuration</param>
IUserFuturesDataTracker CreateUserFuturesDataTracker(FuturesUserDataTrackerConfig? config = null);
}
}