Skip to content

Commit

Permalink
Trackers (#1438)
Browse files Browse the repository at this point in the history
Updated examples
Updated CryptoExchange.Net to v8.1.0
Moved FormatSymbol to BinanceExchange class
Added support Side setting on SharedTrade model
Added BinanceTrackerFactory
Added overload to Create method on BinanceOrderBookFactory support SharedSymbol parameter
Fixed Shared rest GetTradeHistoryAsync pagination
Added catch around HttpClientHandler.AutomaticDecompression setting as it's not support on Blazor WASM
  • Loading branch information
JKorf authored Oct 28, 2024
1 parent 2a9ad8d commit bb82a77
Show file tree
Hide file tree
Showing 32 changed files with 548 additions and 59 deletions.
2 changes: 1 addition & 1 deletion Binance.Net/Binance.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 Binance.Net/Binance.Net.xml
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,16 @@
Urls to the API documentation
</summary>
</member>
<member name="M:Binance.Net.BinanceExchange.FormatSymbol(System.String,System.String,CryptoExchange.Net.SharedApis.TradingMode,System.Nullable{System.DateTime})">
<summary>
Format a base and quote asset to a Binance 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>
</member>
<member name="P:Binance.Net.BinanceExchange.RateLimiter">
<summary>
Rate limiter configuration for the Binance API
Expand Down Expand Up @@ -148,6 +158,26 @@
<param name="number"></param>
<returns></returns>
</member>
<member name="T:Binance.Net.BinanceTrackerFactory">
<inheritdoc />
</member>
<member name="M:Binance.Net.BinanceTrackerFactory.#ctor">
<summary>
ctor
</summary>
</member>
<member name="M:Binance.Net.BinanceTrackerFactory.#ctor(System.IServiceProvider)">
<summary>
ctor
</summary>
<param name="serviceProvider">Service provider for resolving logging and clients</param>
</member>
<member name="M:Binance.Net.BinanceTrackerFactory.CreateKlineTracker(CryptoExchange.Net.SharedApis.SharedSymbol,CryptoExchange.Net.SharedApis.SharedKlineInterval,System.Nullable{System.Int32},System.Nullable{System.TimeSpan})">
<inheritdoc />
</member>
<member name="M:Binance.Net.BinanceTrackerFactory.CreateTradeTracker(CryptoExchange.Net.SharedApis.SharedSymbol,System.Nullable{System.Int32},System.Nullable{System.TimeSpan})">
<inheritdoc />
</member>
<member name="T:Binance.Net.Clients.BinanceRestClient">
<inheritdoc cref="T:Binance.Net.Interfaces.Clients.IBinanceRestClient" />
</member>
Expand Down Expand Up @@ -13261,6 +13291,14 @@
Coin Futures order book factory methods
</summary>
</member>
<member name="M:Binance.Net.Interfaces.IBinanceOrderBookFactory.Create(CryptoExchange.Net.SharedApis.SharedSymbol,System.Action{Binance.Net.Objects.Options.BinanceOrderBookOptions})">
<summary>
Create a SymbolOrderBook for the symbol
</summary>
<param name="symbol">The symbol</param>
<param name="options">Book options</param>
<returns></returns>
</member>
<member name="M:Binance.Net.Interfaces.IBinanceOrderBookFactory.CreateSpot(System.String,System.Action{Binance.Net.Objects.Options.BinanceOrderBookOptions})">
<summary>
Create a Spot SymbolOrderBook
Expand Down Expand Up @@ -13395,6 +13433,30 @@
The quantity of the best ask price in the order book
</summary>
</member>
<member name="T:Binance.Net.Interfaces.IBinanceTrackerFactory">
<summary>
Tracker factory
</summary>
</member>
<member name="M:Binance.Net.Interfaces.IBinanceTrackerFactory.CreateKlineTracker(CryptoExchange.Net.SharedApis.SharedSymbol,CryptoExchange.Net.SharedApis.SharedKlineInterval,System.Nullable{System.Int32},System.Nullable{System.TimeSpan})">
<summary>
Create a new kline tracker
</summary>
<param name="symbol">The symbol</param>
<param name="interval">Kline interval</param>
<param name="limit">The max amount of klines to retain</param>
<param name="period">The max period the data should be retained</param>
<returns></returns>
</member>
<member name="M:Binance.Net.Interfaces.IBinanceTrackerFactory.CreateTradeTracker(CryptoExchange.Net.SharedApis.SharedSymbol,System.Nullable{System.Int32},System.Nullable{System.TimeSpan})">
<summary>
Create a new trade tracker for a symbol
</summary>
<param name="symbol">The symbol</param>
<param name="limit">The max amount of klines to retain</param>
<param name="period">The max period the data should be retained</param>
<returns></returns>
</member>
<member name="T:Binance.Net.Interfaces.IBinanceTrade">
<summary>
Information about a trade
Expand Down Expand Up @@ -29542,6 +29604,9 @@
<member name="P:Binance.Net.SymbolOrderBooks.BinanceOrderBookFactory.CoinFutures">
<inheritdoc />
</member>
<member name="M:Binance.Net.SymbolOrderBooks.BinanceOrderBookFactory.Create(CryptoExchange.Net.SharedApis.SharedSymbol,System.Action{Binance.Net.Objects.Options.BinanceOrderBookOptions})">
<inheritdoc />
</member>
<member name="M:Binance.Net.SymbolOrderBooks.BinanceOrderBookFactory.CreateSpot(System.String,System.Action{Binance.Net.Objects.Options.BinanceOrderBookOptions})">
<inheritdoc />
</member>
Expand Down
28 changes: 24 additions & 4 deletions Binance.Net/BinanceExchange.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using CryptoExchange.Net.RateLimiting.Filters;
using CryptoExchange.Net.RateLimiting.Guards;
using CryptoExchange.Net.RateLimiting.Interfaces;
using CryptoExchange.Net.SharedApis;

namespace Binance.Net
{
Expand All @@ -27,6 +28,25 @@ public static class BinanceExchange
"https://binance-docs.github.io/apidocs/spot/en/#change-log"
};

/// <summary>
/// Format a base and quote asset to a Binance 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)
{
if (tradingMode == TradingMode.Spot)
return baseAsset.ToUpperInvariant() + quoteAsset.ToUpperInvariant();

if (tradingMode.IsLinear())
return baseAsset.ToUpperInvariant() + quoteAsset.ToUpperInvariant() + (deliverTime == null ? string.Empty : "_" + deliverTime.Value.ToString("yyMMdd"));

return baseAsset.ToUpperInvariant() + quoteAsset.ToUpperInvariant() + (deliverTime == null ? "_PERP" : "_" + deliverTime.Value.ToString("yyMMdd"));
}

/// <summary>
/// Rate limiter configuration for the Binance API
/// </summary>
Expand Down Expand Up @@ -61,15 +81,15 @@ private void Initialize()
.AddGuard(new RateLimitGuard(RateLimitGuard.PerApiKeyPerEndpoint, new PathStartFilter("sapi/"), 180000, TimeSpan.FromMinutes(1), RateLimitWindowType.Fixed)); // Uid limit of 180000 request weight per minute to /sapi endpoints
SpotSocket = new RateLimitGate("Spot Socket")
.AddGuard(new RateLimitGuard(RateLimitGuard.PerHost, new IGuardFilter[] { new LimitItemTypeFilter(RateLimitItemType.Connection) }, 300, TimeSpan.FromMinutes(5), RateLimitWindowType.Fixed)) // 300 connections per 5 minutes per host
.AddGuard(new RateLimitGuard(RateLimitGuard.PerEndpoint, new IGuardFilter[] { new HostFilter("wss://stream.binance.com"), new LimitItemTypeFilter(RateLimitItemType.Request) }, 4, TimeSpan.FromSeconds(1), RateLimitWindowType.Sliding)) // 5 requests per second per path (connection)
.AddGuard(new RateLimitGuard(RateLimitGuard.PerConnection, new IGuardFilter[] { new HostFilter("wss://stream.binance.com"), new LimitItemTypeFilter(RateLimitItemType.Request) }, 4, TimeSpan.FromSeconds(1), RateLimitWindowType.Sliding)) // 5 requests per second per path (connection)
.AddGuard(new RateLimitGuard(RateLimitGuard.PerHost, new IGuardFilter[] { new HostFilter("wss://ws-api.binance.com") }, 6000, TimeSpan.FromMinutes(1), RateLimitWindowType.Fixed, connectionWeight: 2)); // 6000 request weight per minute in total
FuturesRest = new RateLimitGate("Futures Rest")
.AddGuard(new RateLimitGuard(RateLimitGuard.PerHost, new IGuardFilter[] { new HostFilter("https://fapi.binance.com") }, 2400, TimeSpan.FromMinutes(1), RateLimitWindowType.Fixed)) // IP limit of 2400 request weight per minute to fapi.binance.com host
.AddGuard(new RateLimitGuard(RateLimitGuard.PerHost, new IGuardFilter[] { new HostFilter("https://dapi.binance.com") }, 2400, TimeSpan.FromMinutes(1), RateLimitWindowType.Fixed)); // IP limit of 2400 request weight per minute to dapi.binance.com host
FuturesSocket = new RateLimitGate("Futures Socket")
.AddGuard(new RateLimitGuard(RateLimitGuard.PerEndpoint, new IGuardFilter[] { new LimitItemTypeFilter(RateLimitItemType.Request), new HostFilter("wss://dstream.binance.com") }, 10, TimeSpan.FromSeconds(1), RateLimitWindowType.Fixed)) // 10 requests per second per path (connection)
.AddGuard(new RateLimitGuard(RateLimitGuard.PerEndpoint, new IGuardFilter[] { new LimitItemTypeFilter(RateLimitItemType.Request), new HostFilter("wss://fstream.binance.com") }, 10, TimeSpan.FromSeconds(1), RateLimitWindowType.Fixed)) // 10 requests per second per path (connection)
.AddGuard(new RateLimitGuard(RateLimitGuard.PerEndpoint, new IGuardFilter[] { new HostFilter("wss://ws-fapi.binance.com") }, 2400, TimeSpan.FromMinutes(1), RateLimitWindowType.Fixed, connectionWeight: 5));
.AddGuard(new RateLimitGuard(RateLimitGuard.PerConnection, new IGuardFilter[] { new LimitItemTypeFilter(RateLimitItemType.Request), new HostFilter("wss://dstream.binance.com") }, 10, TimeSpan.FromSeconds(1), RateLimitWindowType.Fixed)) // 10 requests per second per path (connection)
.AddGuard(new RateLimitGuard(RateLimitGuard.PerConnection, new IGuardFilter[] { new LimitItemTypeFilter(RateLimitItemType.Request), new HostFilter("wss://fstream.binance.com") }, 10, TimeSpan.FromSeconds(1), RateLimitWindowType.Fixed)) // 10 requests per second per path (connection)
.AddGuard(new RateLimitGuard(RateLimitGuard.PerHost, new IGuardFilter[] { new HostFilter("wss://ws-fapi.binance.com") }, 2400, TimeSpan.FromMinutes(1), RateLimitWindowType.Fixed, connectionWeight: 5));

EndpointLimit.RateLimitTriggered += (x) => RateLimitTriggered?.Invoke(x);
SpotRestIp.RateLimitTriggered += (x) => RateLimitTriggered?.Invoke(x);
Expand Down
102 changes: 102 additions & 0 deletions Binance.Net/BinanceTrackerFactory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
using Binance.Net.Clients;
using Binance.Net.Interfaces;
using Binance.Net.Interfaces.Clients;
using CryptoExchange.Net.SharedApis;
using CryptoExchange.Net.Trackers.Klines;
using CryptoExchange.Net.Trackers.Trades;
using Microsoft.Extensions.DependencyInjection;

namespace Binance.Net
{
/// <inheritdoc />
public class BinanceTrackerFactory : IBinanceTrackerFactory
{
private readonly IServiceProvider? _serviceProvider;

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

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

/// <inheritdoc />
public IKlineTracker CreateKlineTracker(SharedSymbol symbol, SharedKlineInterval interval, int? limit = null, TimeSpan? period = null)
{
var restClient = _serviceProvider?.GetRequiredService<IBinanceRestClient>() ?? new BinanceRestClient();
var socketClient = _serviceProvider?.GetRequiredService<IBinanceSocketClient>() ?? new BinanceSocketClient();

IKlineRestClient sharedRestClient;
IKlineSocketClient sharedSocketClient;
if (symbol.TradingMode == TradingMode.Spot)
{
sharedRestClient = restClient.SpotApi.SharedClient;
sharedSocketClient = socketClient.SpotApi.SharedClient;
}
else if (symbol.TradingMode.IsLinear())
{
sharedRestClient = restClient.UsdFuturesApi.SharedClient;
sharedSocketClient = socketClient.UsdFuturesApi.SharedClient;
}
else
{
sharedRestClient = restClient.CoinFuturesApi.SharedClient;
sharedSocketClient = socketClient.CoinFuturesApi.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<IBinanceRestClient>() ?? new BinanceRestClient();
var socketClient = _serviceProvider?.GetRequiredService<IBinanceSocketClient>() ?? new BinanceSocketClient();

ITradeHistoryRestClient sharedRestClient;
ITradeSocketClient sharedSocketClient;
if (symbol.TradingMode == TradingMode.Spot)
{
sharedRestClient = restClient.SpotApi.SharedClient;
sharedSocketClient = socketClient.SpotApi.SharedClient;
}
else if (symbol.TradingMode.IsLinear())
{
sharedRestClient = restClient.UsdFuturesApi.SharedClient;
sharedSocketClient = socketClient.UsdFuturesApi.SharedClient;
}
else
{
sharedRestClient = restClient.CoinFuturesApi.SharedClient;
sharedSocketClient = socketClient.CoinFuturesApi.SharedClient;
}

return new TradeTracker(
_serviceProvider?.GetRequiredService<ILoggerFactory>().CreateLogger(restClient.Exchange),
null,
sharedRestClient,
sharedSocketClient,
symbol,
limit,
period
);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -77,9 +77,7 @@ protected override AuthenticationProvider CreateAuthenticationProvider(ApiCreden

/// <inheritdoc />
public override string FormatSymbol(string baseAsset, string quoteAsset, TradingMode tradingMode, DateTime? deliverTime = null)
{
return baseAsset.ToUpperInvariant() + quoteAsset.ToUpperInvariant() + (deliverTime == null ? "_PERP" : "_" + deliverTime.Value.ToString("yyMMdd"));
}
=> BinanceExchange.FormatSymbol(baseAsset, quoteAsset, tradingMode, deliverTime);

internal Uri GetUrl(string endpoint, string api, string? version = null)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,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.BaseQuantity, x.Price, x.TradeTime)).ToArray());
return result.AsExchangeResult<IEnumerable<SharedTrade>>(Exchange, request.Symbol.TradingMode, result.Data.Select(x => new SharedTrade(x.BaseQuantity, x.Price, x.TradeTime)
{
Side = x.BuyerIsMaker ? SharedOrderSide.Buy : SharedOrderSide.Sell
}).ToArray());
}

#endregion
Expand Down Expand Up @@ -678,10 +681,13 @@ async Task<ExchangeWebResult<IEnumerable<SharedTrade>>> ITradeHistoryRestClient.

FromIdToken? nextToken = null;
if (result.Data.Any() && result.Data.Last().TradeTime < request.EndTime)
nextToken = new FromIdToken(result.Data.Max(x => x.Id).ToString());
nextToken = new FromIdToken((result.Data.Max(x => x.Id) + 1).ToString());

// Return
return result.AsExchangeResult<IEnumerable<SharedTrade>>(Exchange, request.Symbol.TradingMode, result.Data.Where(x => x.TradeTime < request.EndTime).Select(x => new SharedTrade(x.Quantity, x.Price, x.TradeTime)).ToArray(), nextToken);
return result.AsExchangeResult<IEnumerable<SharedTrade>>(Exchange, request.Symbol.TradingMode, result.Data.Where(x => x.TradeTime < request.EndTime).Select(x => new SharedTrade(x.Quantity, x.Price, x.TradeTime)
{
Side = x.BuyerIsMaker ? SharedOrderSide.Buy : SharedOrderSide.Sell
}).ToArray(), nextToken);
}
#endregion

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,7 @@ protected override AuthenticationProvider CreateAuthenticationProvider(ApiCreden

/// <inheritdoc />
public override string FormatSymbol(string baseAsset, string quoteAsset, TradingMode tradingMode, DateTime? deliverTime = null)
{
return baseAsset.ToUpperInvariant() + quoteAsset.ToUpperInvariant() + (deliverTime == null ? "_PERP" : "_" + deliverTime.Value.ToString("yyMMdd"));
}
=> BinanceExchange.FormatSymbol(baseAsset, quoteAsset, tradingMode, deliverTime);

#region methods

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ async Task<ExchangeResult<UpdateSubscription>> ITradeSocketClient.SubscribeToTra
return new ExchangeResult<UpdateSubscription>(Exchange, validationError);

var symbol = request.Symbol.GetSymbol(FormatSymbol);
var result = await SubscribeToAggregatedTradeUpdatesAsync(symbol, update => handler(update.AsExchangeEvent<IEnumerable<SharedTrade>>(Exchange, new[] { new SharedTrade(update.Data.Quantity, update.Data.Price, update.Data.TradeTime) })), ct:ct).ConfigureAwait(false);
var result = await SubscribeToAggregatedTradeUpdatesAsync(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:ct).ConfigureAwait(false);

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

0 comments on commit bb82a77

Please sign in to comment.