Skip to content

Commit

Permalink
Trackers (#19)
Browse files Browse the repository at this point in the history
Updated examples
Updated CryptoExchange.Net to v8.1.0
Moved FormatSymbol to BingXExchange class
Added support Side setting on SharedTrade model
Added BingXTrackerFactory
Added overload to Create method on BingXOrderBookFactory support SharedSymbol parameter
Added Shared websocket kline subscription implementation for futures and spot APIs
  • Loading branch information
JKorf authored Oct 28, 2024
1 parent a52b1f4 commit 1e155c7
Show file tree
Hide file tree
Showing 26 changed files with 533 additions and 18 deletions.
2 changes: 1 addition & 1 deletion BingX.Net/BingX.Net.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,10 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="CryptoExchange.Net" Version="8.1.0" />
<PackageReference Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="8.0.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="CryptoExchange.Net" Version="8.0.3" />
</ItemGroup>
</Project>
65 changes: 65 additions & 0 deletions BingX.Net/BingX.Net.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 14 additions & 0 deletions BingX.Net/BingXExchange.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using CryptoExchange.Net.RateLimiting;
using CryptoExchange.Net.RateLimiting.Guards;
using CryptoExchange.Net.RateLimiting.Interfaces;
using CryptoExchange.Net.SharedApis;
using System;
using System.Collections.Generic;

Expand Down Expand Up @@ -29,6 +30,19 @@ public static class BingXExchange
"https://bingx-api.github.io/docs"
};

/// <summary>
/// Format a base and quote asset to a BingX recognized symbol
/// </summary>
/// <param name="baseAsset">Base asset</param>
/// <param name="quoteAsset">Quote asset</param>
/// <param name="tradingMode">Trading mode</param>
/// <param name="deliverTime">Delivery time for delivery futures</param>
/// <returns></returns>
public static string FormatSymbol(string baseAsset, string quoteAsset, TradingMode tradingMode, DateTime? deliverTime = null)
{
return baseAsset.ToUpperInvariant() + "-" + quoteAsset.ToUpperInvariant();
}

/// <summary>
/// Rate limiter configuration for the BingX API
/// </summary>
Expand Down
92 changes: 92 additions & 0 deletions BingX.Net/BingXTrackerFactory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
using BingX.Net.Clients;
using BingX.Net.Interfaces;
using BingX.Net.Interfaces.Clients;
using CryptoExchange.Net.SharedApis;
using CryptoExchange.Net.Trackers.Klines;
using CryptoExchange.Net.Trackers.Trades;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using System;

namespace BingX.Net
{
/// <inheritdoc />
public class BingXTrackerFactory : IBingXTrackerFactory
{
private readonly IServiceProvider? _serviceProvider;

/// <summary>
/// ctor
/// </summary>
public BingXTrackerFactory()
{
}

/// <summary>
/// ctor
/// </summary>
/// <param name="serviceProvider">Service provider for resolving logging and clients</param>
public BingXTrackerFactory(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}

/// <inheritdoc />
public IKlineTracker CreateKlineTracker(SharedSymbol symbol, SharedKlineInterval interval, int? limit = null, TimeSpan? period = null)
{
var restClient = _serviceProvider?.GetRequiredService<IBingXRestClient>() ?? new BingXRestClient();
var socketClient = _serviceProvider?.GetRequiredService<IBingXSocketClient>() ?? new BingXSocketClient();

IKlineRestClient sharedRestClient;
IKlineSocketClient sharedSocketClient;
if (symbol.TradingMode == TradingMode.Spot)
{
sharedRestClient = restClient.SpotApi.SharedClient;
sharedSocketClient = socketClient.SpotApi.SharedClient;
}
else
{
sharedRestClient = restClient.PerpetualFuturesApi.SharedClient;
sharedSocketClient = socketClient.PerpetualFuturesApi.SharedClient;
}

return new KlineTracker(
_serviceProvider?.GetRequiredService<ILoggerFactory>().CreateLogger(restClient.Exchange),
sharedRestClient,
sharedSocketClient,
symbol,
interval,
limit,
period
);
}

/// <inheritdoc />
public ITradeTracker CreateTradeTracker(SharedSymbol symbol, int? limit = null, TimeSpan? period = null)
{
var restClient = _serviceProvider?.GetRequiredService<IBingXRestClient>() ?? new BingXRestClient();
var socketClient = _serviceProvider?.GetRequiredService<IBingXSocketClient>() ?? new BingXSocketClient();

IRecentTradeRestClient sharedRestClient;
ITradeSocketClient sharedSocketClient;
if (symbol.TradingMode == TradingMode.Spot) {
sharedRestClient = restClient.SpotApi.SharedClient;
sharedSocketClient = socketClient.SpotApi.SharedClient;
}
else {
sharedRestClient = restClient.PerpetualFuturesApi.SharedClient;
sharedSocketClient = socketClient.PerpetualFuturesApi.SharedClient;
}

return new TradeTracker(
_serviceProvider?.GetRequiredService<ILoggerFactory>().CreateLogger(restClient.Exchange),
sharedRestClient,
null,
sharedSocketClient,
symbol,
limit,
period
);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,9 @@ internal BingXRestClientPerpetualFuturesApi(ILogger logger, HttpClient? httpClie
#endregion

/// <inheritdoc />
public override string FormatSymbol(string baseAsset, string quoteAsset, TradingMode tradingMode, DateTime? deliverTime = null) => baseAsset.ToUpperInvariant() + "-" + quoteAsset.ToUpperInvariant();
public override string FormatSymbol(string baseAsset, string quoteAsset, TradingMode tradingMode, DateTime? deliverTime = null)
=> BingXExchange.FormatSymbol(baseAsset, quoteAsset, tradingMode, deliverTime);

public IBingXRestClientPerpetualFuturesApiShared SharedClient => this;

/// <inheritdoc />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,10 @@ async Task<ExchangeWebResult<IEnumerable<SharedTrade>>> IRecentTradeRestClient.G
if (!result)
return result.AsExchangeResult<IEnumerable<SharedTrade>>(Exchange, null, default);

return result.AsExchangeResult<IEnumerable<SharedTrade>>(Exchange, request.Symbol.TradingMode, result.Data.Reverse().Select(x => new SharedTrade(x.Quantity, x.Price, x.Timestamp)).ToArray());
return result.AsExchangeResult<IEnumerable<SharedTrade>>(Exchange, request.Symbol.TradingMode, result.Data.Reverse().Select(x => new SharedTrade(x.Quantity, x.Price, x.Timestamp)
{
Side = x.BuyerIsMaker ? SharedOrderSide.Buy : SharedOrderSide.Sell
}).ToArray());
}

#endregion
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@ internal BingXSocketClientPerpetualFuturesApi(ILogger logger, BingXSocketOptions
#endregion

/// <inheritdoc />
public override string FormatSymbol(string baseAsset, string quoteAsset, TradingMode tradingMode, DateTime? deliverTime = null) => baseAsset.ToUpperInvariant() + "-" + quoteAsset.ToUpperInvariant();
public override string FormatSymbol(string baseAsset, string quoteAsset, TradingMode tradingMode, DateTime? deliverTime = null)
=> BingXExchange.FormatSymbol(baseAsset, quoteAsset, tradingMode, deliverTime);

/// <inheritdoc />
protected override AuthenticationProvider CreateAuthenticationProvider(ApiCredentials credentials)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,42 @@ async Task<ExchangeResult<UpdateSubscription>> ITradeSocketClient.SubscribeToTra
return new ExchangeResult<UpdateSubscription>(Exchange, validationError);

var symbol = request.Symbol.GetSymbol(FormatSymbol);
var result = await SubscribeToTradeUpdatesAsync(symbol, update => handler(update.AsExchangeEvent<IEnumerable<SharedTrade>>(Exchange, update.Data.Select(x => new SharedTrade(x.Quantity, x.Price, x.TradeTime)).ToArray())), ct).ConfigureAwait(false);
var result = await SubscribeToTradeUpdatesAsync(symbol, update => handler(update.AsExchangeEvent<IEnumerable<SharedTrade>>(Exchange, update.Data.Select(x => new SharedTrade(x.Quantity, x.Price, x.TradeTime)
{
Side = x.BuyerIsMaker ? SharedOrderSide.Buy : SharedOrderSide.Sell
}).ToArray())), ct).ConfigureAwait(false);

return new ExchangeResult<UpdateSubscription>(Exchange, result);
}

#endregion

#region Kline client
SubscribeKlineOptions IKlineSocketClient.SubscribeKlineOptions { get; } = new SubscribeKlineOptions(false);
async Task<ExchangeResult<UpdateSubscription>> IKlineSocketClient.SubscribeToKlineUpdatesAsync(SubscribeKlineRequest request, Action<ExchangeEvent<SharedKline>> handler, CancellationToken ct)
{
var interval = (Enums.KlineInterval)request.Interval;
if (!Enum.IsDefined(typeof(Enums.KlineInterval), interval))
return new ExchangeResult<UpdateSubscription>(Exchange, new ArgumentError("Interval not supported"));

var validationError = ((IKlineSocketClient)this).SubscribeKlineOptions.ValidateRequest(Exchange, request, request.Symbol.TradingMode, SupportedTradingModes);
if (validationError != null)
return new ExchangeResult<UpdateSubscription>(Exchange, validationError);

var symbol = request.Symbol.GetSymbol(FormatSymbol);
var result = await SubscribeToKlineUpdatesAsync(symbol, interval, update =>
{
if (update.UpdateType == SocketUpdateType.Snapshot)
return;
foreach (var item in update.Data)
handler(update.AsExchangeEvent(Exchange, new SharedKline(item.Timestamp, item.ClosePrice, item.HighPrice, item.LowPrice, item.OpenPrice, item.Volume)));
}, ct).ConfigureAwait(false);

return new ExchangeResult<UpdateSubscription>(Exchange, result);
}
#endregion

#region Book Ticker client

EndpointOptions<SubscribeBookTickerRequest> IBookTickerSocketClient.SubscribeBookTickerOptions { get; } = new EndpointOptions<SubscribeBookTickerRequest>(false);
Expand Down
3 changes: 2 additions & 1 deletion BingX.Net/Clients/SpotApi/BingXRestClientSpotApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,8 @@ internal BingXRestClientSpotApi(ILogger logger, HttpClient? httpClient, BingXRes
#endregion

/// <inheritdoc />
public override string FormatSymbol(string baseAsset, string quoteAsset, TradingMode tradingMode, DateTime? deliverTime = null) => baseAsset.ToUpperInvariant() + "-" + quoteAsset.ToUpperInvariant();
public override string FormatSymbol(string baseAsset, string quoteAsset, TradingMode tradingMode, DateTime? deliverTime = null)
=> BingXExchange.FormatSymbol(baseAsset, quoteAsset, tradingMode, deliverTime);

/// <inheritdoc />
protected override IMessageSerializer CreateSerializer() => new SystemTextJsonMessageSerializer();
Expand Down
5 changes: 4 additions & 1 deletion BingX.Net/Clients/SpotApi/BingXRestClientSpotApiShared.cs
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,10 @@ async Task<ExchangeWebResult<IEnumerable<SharedTrade>>> IRecentTradeRestClient.G
if (!result)
return result.AsExchangeResult<IEnumerable<SharedTrade>>(Exchange, null, default);

return result.AsExchangeResult<IEnumerable<SharedTrade>>(Exchange, request.Symbol.TradingMode, result.Data.Select(x => new SharedTrade(x.Quantity, x.Price, x.Timestamp)).ToArray());
return result.AsExchangeResult<IEnumerable<SharedTrade>>(Exchange, request.Symbol.TradingMode, result.Data.Select(x => new SharedTrade(x.Quantity, x.Price, x.Timestamp)
{
Side = x.BuyerIsMaker ? SharedOrderSide.Buy : SharedOrderSide.Sell
}).ToArray());
}

#endregion
Expand Down
3 changes: 2 additions & 1 deletion BingX.Net/Clients/SpotApi/BingXSocketClientSpotApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,8 @@ protected override AuthenticationProvider CreateAuthenticationProvider(ApiCreden
=> new BingXAuthenticationProvider(credentials);

/// <inheritdoc />
public override string FormatSymbol(string baseAsset, string quoteAsset, TradingMode tradingMode, DateTime? deliverTime = null) => baseAsset.ToUpperInvariant() + "-" + quoteAsset.ToUpperInvariant();
public override string FormatSymbol(string baseAsset, string quoteAsset, TradingMode tradingMode, DateTime? deliverTime = null)
=> BingXExchange.FormatSymbol(baseAsset, quoteAsset, tradingMode, deliverTime);

public IBingXSocketClientSpotApiShared SharedClient => this;

Expand Down
24 changes: 23 additions & 1 deletion BingX.Net/Clients/SpotApi/BingXSocketClientSpotApiShared.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,35 @@ async Task<ExchangeResult<UpdateSubscription>> ITradeSocketClient.SubscribeToTra
return new ExchangeResult<UpdateSubscription>(Exchange, validationError);

var symbol = request.Symbol.GetSymbol(FormatSymbol);
var result = await SubscribeToTradeUpdatesAsync(symbol, update => handler(update.AsExchangeEvent<IEnumerable<SharedTrade>>(Exchange, new[] { new SharedTrade(update.Data.Quantity, update.Data.Price, update.Data.TradeTime) })), ct).ConfigureAwait(false);
var result = await SubscribeToTradeUpdatesAsync(symbol, update => handler(update.AsExchangeEvent<IEnumerable<SharedTrade>>(Exchange, new[] { new SharedTrade(update.Data.Quantity, update.Data.Price, update.Data.TradeTime)
{
Side = update.Data.BuyerIsMaker ? SharedOrderSide.Buy : SharedOrderSide.Sell
} })), ct).ConfigureAwait(false);

return new ExchangeResult<UpdateSubscription>(Exchange, result);
}

#endregion

#region Kline client
SubscribeKlineOptions IKlineSocketClient.SubscribeKlineOptions { get; } = new SubscribeKlineOptions(false);
async Task<ExchangeResult<UpdateSubscription>> IKlineSocketClient.SubscribeToKlineUpdatesAsync(SubscribeKlineRequest request, Action<ExchangeEvent<SharedKline>> handler, CancellationToken ct)
{
var interval = (Enums.KlineInterval)request.Interval;
if (!Enum.IsDefined(typeof(Enums.KlineInterval), interval))
return new ExchangeResult<UpdateSubscription>(Exchange, new ArgumentError("Interval not supported"));

var validationError = ((IKlineSocketClient)this).SubscribeKlineOptions.ValidateRequest(Exchange, request, request.Symbol.TradingMode, SupportedTradingModes);
if (validationError != null)
return new ExchangeResult<UpdateSubscription>(Exchange, validationError);

var symbol = request.Symbol.GetSymbol(FormatSymbol);
var result = await SubscribeToKlineUpdatesAsync(symbol, interval, update => handler(update.AsExchangeEvent(Exchange, new SharedKline(update.Data.Kline.OpenTime, update.Data.Kline.ClosePrice, update.Data.Kline.HighPrice, update.Data.Kline.LowPrice, update.Data.Kline.OpenPrice, update.Data.Kline.Volume))), ct).ConfigureAwait(false);

return new ExchangeResult<UpdateSubscription>(Exchange, result);
}
#endregion

#region Book Ticker client

EndpointOptions<SubscribeBookTickerRequest> IBookTickerSocketClient.SubscribeBookTickerOptions { get; } = new EndpointOptions<SubscribeBookTickerRequest>(false);
Expand Down
Loading

0 comments on commit 1e155c7

Please sign in to comment.