1
0
mirror of https://github.com/JKorf/CryptoExchange.Net.git synced 2026-10-04 02:11:11 +00:00

Compare commits

..

9 Commits

29 changed files with 919 additions and 109 deletions
@@ -533,9 +533,6 @@ namespace CryptoExchange.Net.Clients
if (!connectResult.Success)
return connectResult;
if (ClientOptions.DelayAfterConnect != TimeSpan.Zero)
await Task.Delay(ClientOptions.DelayAfterConnect).ConfigureAwait(false);
if (!authenticated || socket.Authenticated)
return CallResult.Ok();
@@ -862,7 +859,7 @@ namespace CryptoExchange.Net.Clients
RateLimitAdmissionCallbackRequest = () => AdmissionOverride.Value,
Proxy = ClientOptions.Proxy,
Timeout = ApiOptions.SocketNoDataTimeout ?? ClientOptions.SocketNoDataTimeout,
ReceiveBufferSize = ClientOptions.ReceiveBufferSize,
ReceiveBufferSize = ClientOptions.ReceiveBufferSize
};
/// <summary>
+3 -3
View File
@@ -6,9 +6,9 @@
<PackageId>CryptoExchange.Net</PackageId>
<Authors>JKorf</Authors>
<Description>CryptoExchange.Net is a base library which is used to implement different cryptocurrency (exchange) API's. It provides a standardized way of implementing different API's, which results in a very similar experience for users of the API implementations.</Description>
<PackageVersion>13.0.0</PackageVersion>
<AssemblyVersion>13.0.0</AssemblyVersion>
<FileVersion>13.0.0</FileVersion>
<PackageVersion>13.1.0</PackageVersion>
<AssemblyVersion>13.1.0</AssemblyVersion>
<FileVersion>13.1.0</FileVersion>
<PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance>
<PackageTags>OKX;OKX.Net;Mexc;Mexc.Net;Kucoin;Kucoin.Net;Kraken;Kraken.Net;Huobi;Huobi.Net;CoinEx;CoinEx.Net;Bybit;Bybit.Net;Bitget;Bitget.Net;Bitfinex;Bitfinex.Net;Binance;Binance.Net;CryptoCurrency;CryptoCurrency Exchange;CryptoExchange.Net</PackageTags>
<RepositoryType>git</RepositoryType>
@@ -19,6 +19,11 @@ namespace CryptoExchange.Net.Objects.Options
/// </summary>
public int? MaxSocketConnections { get; set; }
/// <summary>
/// The time to wait after connecting a socket before sending messages. Can be used for API's which will rate limit if you subscribe directly after connecting.
/// </summary>
public TimeSpan? DelayAfterConnect { get; set; }
/// <summary>
/// Set the values of this options on the target options
/// </summary>
@@ -28,6 +33,7 @@ namespace CryptoExchange.Net.Objects.Options
item.SocketNoDataTimeout = SocketNoDataTimeout;
item.AutoTimestamp = AutoTimestamp;
item.MaxSocketConnections = MaxSocketConnections;
item.DelayAfterConnect = DelayAfterConnect;
return item;
}
}
@@ -0,0 +1,25 @@
using CryptoExchange.Net.Interfaces;
namespace CryptoExchange.Net.SharedApis
{
/// <summary>
/// Order book info
/// </summary>
public record SharedIncrementalOrderBook : SharedOrderBook
{
/// <summary>
/// The sequence number of the first book update in this update.
/// </summary>
public long? StartSequenceNumber { get; set; }
/// <summary>
/// ctor
/// </summary>
public SharedIncrementalOrderBook(SharedQuantityType quantityType, long? startSequenceNumber, long? endSequenceNumber, ISymbolOrderBookEntry[] asks, ISymbolOrderBookEntry[] bids)
:base(quantityType, endSequenceNumber, asks, bids)
{
StartSequenceNumber = startSequenceNumber;
}
}
}
@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace CryptoExchange.Net.SharedApis
{
@@ -104,6 +105,11 @@ namespace CryptoExchange.Net.SharedApis
SharedCapabilityResolution<T>? GetCapability<T>(SharedCapabilityReference<T> capability, TradingMode tradingMode)
where T : ISharedApiCapability;
/// <summary>
/// Unsubscribes all subscriptions created by the underlying socket API
/// client and closes its subscription connections.
/// </summary>
Task UnsubscribeAllAsync();
}
/// <summary>
@@ -209,6 +215,15 @@ namespace CryptoExchange.Net.SharedApis
return GetCapabilities<T>(tradingMode);
}
/// <inheritdoc />
public Task UnsubscribeAllAsync()
{
return Task.WhenAll(
_sharedApis
.OfType<ISharedSubscription>()
.Select(x => x.UnsubscribeAllAsync()));
}
object? ISharedApiClientResolver.GetCapability(Type capabilityType)
{
return _sharedApis
@@ -1,10 +1,12 @@
using CryptoExchange.Net.Objects;
using CryptoExchange.Net.Objects.Sockets;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace CryptoExchange.Net.SharedApis
{
@@ -624,5 +626,212 @@ namespace CryptoExchange.Net.SharedApis
.ParallelEnumerateAsync();
}
/// <summary>
/// Subscribe to trade updates for all capabilities in parallel and return results as they arrive
/// </summary>
public static IAsyncEnumerable<WebSocketResult<UpdateSubscription>> SubscribeAllAsync(
this IEnumerable<SharedCapabilityResolution<ISubscribeTradesSocket>> capabilities,
SubscribeTradeRequest request,
Action<DataEvent<SharedTrade[]>> onData,
CancellationToken ct = default)
{
return capabilities
.Select(x => x.Capability.SubscribeToTradeUpdatesAsync(request, onData, ct))
.ParallelEnumerateAsync();
}
/// <summary>
/// Subscribe to balance updates for all capabilities in parallel and return results as they arrive
/// </summary>
public static IAsyncEnumerable<WebSocketResult<UpdateSubscription>> SubscribeAllAsync(
this IEnumerable<SharedCapabilityResolution<ISubscribeBalancesSocket>> capabilities,
SubscribeBalancesRequest request,
Action<DataEvent<SharedBalance[]>> onData,
CancellationToken ct = default)
{
return capabilities
.Select(x => x.Capability.SubscribeToBalanceUpdatesAsync(request, onData, ct))
.ParallelEnumerateAsync();
}
/// <summary>
/// Subscribe to index price updates for all capabilities in parallel and return results as they arrive
/// </summary>
public static IAsyncEnumerable<WebSocketResult<UpdateSubscription>> SubscribeAllAsync(
this IEnumerable<SharedCapabilityResolution<ISubscribeIndexPriceSocket>> capabilities,
SubscribeIndexPriceRequest request,
Action<DataEvent<SharedIndexPrice>> onData,
CancellationToken ct = default)
{
return capabilities
.Select(x => x.Capability.SubscribeToIndexPriceUpdatesAsync(request, onData, ct))
.ParallelEnumerateAsync();
}
/// <summary>
/// Subscribe to funding info updates for all capabilities in parallel and return results as they arrive
/// </summary>
public static IAsyncEnumerable<WebSocketResult<UpdateSubscription>> SubscribeAllAsync(
this IEnumerable<SharedCapabilityResolution<ISubscribeFundingInfoSocket>> capabilities,
SubscribeFundingInfoRequest request,
Action<DataEvent<SharedFundingInfo>> onData,
CancellationToken ct = default)
{
return capabilities
.Select(x => x.Capability.SubscribeToFundingInfoUpdatesAsync(request, onData, ct))
.ParallelEnumerateAsync();
}
/// <summary>
/// Subscribe to kline updates for all capabilities in parallel and return results as they arrive
/// </summary>
public static IAsyncEnumerable<WebSocketResult<UpdateSubscription>> SubscribeAllAsync(
this IEnumerable<SharedCapabilityResolution<ISubscribeKlinesSocket>> capabilities,
SubscribeKlineRequest request,
Action<DataEvent<SharedKline>> onData,
CancellationToken ct = default)
{
return capabilities
.Select(x => x.Capability.SubscribeToKlineUpdatesAsync(request, onData, ct))
.ParallelEnumerateAsync();
}
/// <summary>
/// Subscribe to mark price updates for all capabilities in parallel and return results as they arrive
/// </summary>
public static IAsyncEnumerable<WebSocketResult<UpdateSubscription>> SubscribeAllAsync(
this IEnumerable<SharedCapabilityResolution<ISubscribeMarkPriceSocket>> capabilities,
SubscribeMarkPriceRequest request,
Action<DataEvent<SharedMarkPrice>> onData,
CancellationToken ct = default)
{
return capabilities
.Select(x => x.Capability.SubscribeToMarkPriceUpdatesAsync(request, onData, ct))
.ParallelEnumerateAsync();
}
/// <summary>
/// Subscribe to order book updates for all capabilities in parallel and return results as they arrive
/// </summary>
public static IAsyncEnumerable<WebSocketResult<UpdateSubscription>> SubscribeAllAsync(
this IEnumerable<SharedCapabilityResolution<ISubscribeOrderBookSocket>> capabilities,
SubscribeOrderBookRequest request,
Action<DataEvent<SharedOrderBook>> onData,
CancellationToken ct = default)
{
return capabilities
.Select(x => x.Capability.SubscribeToOrderBookUpdatesAsync(request, onData, ct))
.ParallelEnumerateAsync();
}
/// <summary>
/// Subscribe to incremental order book updates for all capabilities in parallel and return results as they arrive
/// </summary>
public static IAsyncEnumerable<WebSocketResult<UpdateSubscription>> SubscribeAllAsync(
this IEnumerable<SharedCapabilityResolution<ISubscribeIncrementalOrderBookSocket>> capabilities,
SubscribeOrderBookRequest request,
Action<DataEvent<SharedIncrementalOrderBook>> onData,
CancellationToken ct = default)
{
return capabilities
.Select(x => x.Capability.SubscribeToIncrementalOrderBookUpdatesAsync(request, onData, ct))
.ParallelEnumerateAsync();
}
/// <summary>
/// Subscribe to futures order updates for all capabilities in parallel and return results as they arrive
/// </summary>
public static IAsyncEnumerable<WebSocketResult<UpdateSubscription>> SubscribeAllAsync(
this IEnumerable<SharedCapabilityResolution<ISubscribeFuturesOrdersSocket>> capabilities,
SubscribeFuturesOrderRequest request,
Action<DataEvent<SharedFuturesOrderUpdate[]>> onData,
CancellationToken ct = default)
{
return capabilities
.Select(x => x.Capability.SubscribeToFuturesOrderUpdatesAsync(request, onData, ct))
.ParallelEnumerateAsync();
}
/// <summary>
/// Subscribe to spot order updates for all capabilities in parallel and return results as they arrive
/// </summary>
public static IAsyncEnumerable<WebSocketResult<UpdateSubscription>> SubscribeAllAsync(
this IEnumerable<SharedCapabilityResolution<ISubscribeSpotOrdersSocket>> capabilities,
SubscribeSpotOrderRequest request,
Action<DataEvent<SharedSpotOrderUpdate[]>> onData,
CancellationToken ct = default)
{
return capabilities
.Select(x => x.Capability.SubscribeToSpotOrderUpdatesAsync(request, onData, ct))
.ParallelEnumerateAsync();
}
/// <summary>
/// Subscribe to position updates for all capabilities in parallel and return results as they arrive
/// </summary>
public static IAsyncEnumerable<WebSocketResult<UpdateSubscription>> SubscribeAllAsync(
this IEnumerable<SharedCapabilityResolution<ISubscribePositionsSocket>> capabilities,
SubscribePositionRequest request,
Action<DataEvent<SharedPosition[]>> onData,
CancellationToken ct = default)
{
return capabilities
.Select(x => x.Capability.SubscribeToPositionUpdatesAsync(request, onData, ct))
.ParallelEnumerateAsync();
}
/// <summary>
/// Subscribe to ticker updates for all capabilities in parallel and return results as they arrive
/// </summary>
public static IAsyncEnumerable<WebSocketResult<UpdateSubscription>> SubscribeAllAsync(
this IEnumerable<SharedCapabilityResolution<ISubscribeTickerSocket>> capabilities,
SubscribeTickerRequest request,
Action<DataEvent<SharedTicker>> onData,
CancellationToken ct = default)
{
return capabilities
.Select(x => x.Capability.SubscribeToTickerUpdatesAsync(request, onData, ct))
.ParallelEnumerateAsync();
}
/// <summary>
/// Subscribe to all ticker updates for all capabilities in parallel and return results as they arrive
/// </summary>
public static IAsyncEnumerable<WebSocketResult<UpdateSubscription>> SubscribeAllAsync(
this IEnumerable<SharedCapabilityResolution<ISubscribeAllTickersSocket>> capabilities,
SubscribeAllTickersRequest request,
Action<DataEvent<SharedTicker[]>> onData,
CancellationToken ct = default)
{
return capabilities
.Select(x => x.Capability.SubscribeToAllTickersUpdatesAsync(request, onData, ct))
.ParallelEnumerateAsync();
}
/// <summary>
/// Subscribe to user trade updates for all capabilities in parallel and return results as they arrive
/// </summary>
public static IAsyncEnumerable<WebSocketResult<UpdateSubscription>> SubscribeAllAsync(
this IEnumerable<SharedCapabilityResolution<ISubscribeUserTradesSocket>> capabilities,
SubscribeUserTradeRequest request,
Action<DataEvent<SharedUserTrade[]>> onData,
CancellationToken ct = default)
{
return capabilities
.Select(x => x.Capability.SubscribeToUserTradeUpdatesAsync(request, onData, ct))
.ParallelEnumerateAsync();
}
/// <summary>
/// Wait for all results from an IAsyncEnumerable and return them as an array
/// </summary>
public static async Task<T[]> WaitAllAsync<T>(this IAsyncEnumerable<T> enumerable)
{
var result = new List<T>();
await foreach (var itemResult in enumerable.ConfigureAwait(false))
result.Add(itemResult);
return result.ToArray();
}
}
}
@@ -0,0 +1,28 @@
using CryptoExchange.Net.Objects;
using CryptoExchange.Net.Objects.Sockets;
using System;
using System.Threading;
using System.Threading.Tasks;
namespace CryptoExchange.Net.SharedApis
{
/// <summary>
/// Operation for subscribing to funding info updates
/// </summary>
public interface ISubscribeFundingInfoSocket : ISharedSubscription
{
/// <summary>
/// Funding info subscription options
/// </summary>
SubscribeFundingInfoOptions SubscribeFundingInfoOptions { get; }
/// <summary>
/// Subscribe to funding info updates
/// </summary>
/// <param name="request">Request info</param>
/// <param name="handler">Update handler</param>
/// <param name="ct">Cancellation token, can be used to stop the updates</param>
/// <returns></returns>
Task<WebSocketResult<UpdateSubscription>> SubscribeToFundingInfoUpdatesAsync(SubscribeFundingInfoRequest request, Action<DataEvent<SharedFundingInfo>> handler, CancellationToken ct = default);
}
}
@@ -0,0 +1,29 @@
using CryptoExchange.Net.Objects;
using System;
using System.Linq;
namespace CryptoExchange.Net.SharedApis
{
/// <summary>
/// Options for subscribing to funding info updates
/// </summary>
public class SubscribeFundingInfoOptions : CapabilityOptions<SubscribeFundingInfoRequest, ISubscribeFundingInfoSocket>
{
/// <inheritdoc />
public override string Description => "Subscribe to funding info updates for a symbol";
private static readonly RequestParameterDescription[] _defaultParameterRules = new[]
{
RequestParameterRule<SubscribeFundingInfoRequest>.Optional(x => x.Symbol, "The symbol to subscribe to", new SharedSymbol(TradingMode.PerpetualLinear, "ETH", "USDT")),
RequestParameterRule<SubscribeFundingInfoRequest>.Optional(x => x.Symbols, "The symbols to subscribe to", new[] { new SharedSymbol(TradingMode.PerpetualLinear, "ETH", "USDT") }),
};
/// <summary>
/// ctor
/// </summary>
public SubscribeFundingInfoOptions(string exchange, bool needsAuthentication)
: base(exchange, needsAuthentication, nameof(ISubscribeFundingInfoSocket.SubscribeToFundingInfoUpdatesAsync), _defaultParameterRules, SharedTradingModeSets.Futures)
{
}
}
}
@@ -0,0 +1,31 @@
using System;
using System.Collections.Generic;
namespace CryptoExchange.Net.SharedApis
{
/// <summary>
/// Request to subscribe to funding info updates for a symbol
/// </summary>
public record SubscribeFundingInfoRequest : SharedSymbolRequest
{
/// <summary>
/// ctor
/// </summary>
/// <param name="symbol">The symbol to subscribe to</param>
/// <param name="exchangeParameters">Exchange specific parameters</param>
public SubscribeFundingInfoRequest(SharedSymbol symbol, ExchangeParameters? exchangeParameters = null)
: base(symbol, exchangeParameters)
{
}
/// <summary>
/// ctor
/// </summary>
/// <param name="symbols">The symbols to subscribe to</param>
/// <param name="exchangeParameters">Exchange specific parameters</param>
public SubscribeFundingInfoRequest(IEnumerable<SharedSymbol> symbols, ExchangeParameters? exchangeParameters = null)
: base(symbols, exchangeParameters)
{
}
}
}
@@ -1,13 +1,19 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
namespace CryptoExchange.Net.SharedApis
{
/// <summary>
/// Shared WebSocket API subscription capabitlity
/// Shared WebSocket API subscription capability
/// </summary>
public interface ISharedSubscription : ISharedSocket
{
/// <summary>
/// Unsubscribes all subscriptions created by the underlying socket API
/// client and closes its subscription connections.
/// </summary>
Task UnsubscribeAllAsync();
}
}
@@ -14,7 +14,7 @@ namespace CryptoExchange.Net.SharedApis
/// <summary>
/// Order book subscription options
/// </summary>
SubscribeOrderBookOptions SubscribeOrderBookOptions { get; }
SubscribeIncrementalOrderBookOptions SubscribeIncrementalOrderBookOptions { get; }
/// <summary>
/// Subscribe to incremental order book updates for a symbol
@@ -23,6 +23,6 @@ namespace CryptoExchange.Net.SharedApis
/// <param name="handler">Update handler</param>
/// <param name="ct">Cancellation token, can be used to stop the updates</param>
/// <returns></returns>
Task<WebSocketResult<UpdateSubscription>> SubscribeToOrderBookUpdatesAsync(SubscribeOrderBookRequest request, Action<DataEvent<SharedOrderBook>> handler, CancellationToken ct = default);
Task<WebSocketResult<UpdateSubscription>> SubscribeToIncrementalOrderBookUpdatesAsync(SubscribeOrderBookRequest request, Action<DataEvent<SharedIncrementalOrderBook>> handler, CancellationToken ct = default);
}
}
@@ -33,7 +33,7 @@ namespace CryptoExchange.Net.SharedApis
/// ctor
/// </summary>
public SubscribeIncrementalOrderBookOptions(string exchange, bool needsAuthentication, int[] limits, SharedOrderBookSubscriptionType updateType)
: base(exchange, needsAuthentication, nameof(ISubscribeIncrementalOrderBookSocket.SubscribeToOrderBookUpdatesAsync), _defaultParameterRules)
: base(exchange, needsAuthentication, nameof(ISubscribeIncrementalOrderBookSocket.SubscribeToIncrementalOrderBookUpdatesAsync), _defaultParameterRules)
{
SupportedLimits = limits;
UpdateType = updateType;
@@ -424,6 +424,9 @@ namespace CryptoExchange.Net.Sockets.Default
{
try
{
if ((ApiClient.ApiOptions.DelayAfterConnect ?? ApiClient.ClientOptions.DelayAfterConnect) != TimeSpan.Zero)
await Task.Delay(ApiClient.ApiOptions.DelayAfterConnect ?? ApiClient.ClientOptions.DelayAfterConnect).ConfigureAwait(false);
var reconnectSuccessful = await ProcessReconnectAsync().ConfigureAwait(false);
if (!reconnectSuccessful.Success)
{
@@ -616,7 +619,17 @@ namespace CryptoExchange.Net.Sockets.Default
/// Connect the websocket
/// </summary>
/// <returns></returns>
public async Task<CallResult> ConnectAsync(CancellationToken ct) => await _socket.ConnectAsync(ct).ConfigureAwait(false);
public async Task<CallResult> ConnectAsync(CancellationToken ct)
{
var result = await _socket.ConnectAsync(ct).ConfigureAwait(false);
if (!result.Success)
return result;
if ((ApiClient.ApiOptions.DelayAfterConnect ?? ApiClient.ClientOptions.DelayAfterConnect) != TimeSpan.Zero)
await Task.Delay(ApiClient.ApiOptions.DelayAfterConnect ?? ApiClient.ClientOptions.DelayAfterConnect).ConfigureAwait(false);
return result;
}
/// <summary>
/// Retrieve the underlying socket
+29 -30
View File
@@ -5,37 +5,36 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Binance.Net" Version="13.5.0" />
<PackageReference Include="Bitfinex.Net" Version="11.4.0" />
<PackageReference Include="BitMart.Net" Version="4.4.0" />
<PackageReference Include="BloFin.Net" Version="3.4.0" />
<PackageReference Include="Bybit.Net" Version="7.5.0" />
<PackageReference Include="CoinEx.Net" Version="11.4.0" />
<PackageReference Include="CoinW.Net" Version="3.4.0" />
<PackageReference Include="CryptoCom.Net" Version="4.4.0" />
<PackageReference Include="DeepCoin.Net" Version="4.4.0" />
<PackageReference Include="GateIo.Net" Version="4.6.0" />
<PackageReference Include="HyperLiquid.Net" Version="5.7.0" />
<PackageReference Include="JK.BingX.Net" Version="4.5.0" />
<PackageReference Include="JK.Bitget.Net" Version="4.5.0" />
<PackageReference Include="JK.Mexc.Net" Version="6.6.0" />
<PackageReference Include="JK.OKX.Net" Version="5.4.0" />
<PackageReference Include="Jkorf.Aster.Net" Version="4.4.0" />
<PackageReference Include="JKorf.BitMEX.Net" Version="4.4.0" />
<PackageReference Include="JKorf.Coinbase.Net" Version="4.5.0" />
<PackageReference Include="JKorf.HTX.Net" Version="9.5.0" />
<PackageReference Include="JKorf.Lighter.Net" Version="1.5.0" />
<PackageReference Include="JKorf.Upbit.Net" Version="3.4.0" />
<PackageReference Include="KrakenExchange.Net" Version="8.4.0" />
<PackageReference Include="Kucoin.Net" Version="9.5.0" />
<PackageReference Include="LBank.Net" Version="1.1.0" />
<PackageReference Include="Pionex.Net" Version="1.2.0" />
<PackageReference Include="Binance.Net" Version="13.6.0" />
<PackageReference Include="Bitfinex.Net" Version="11.6.0" />
<PackageReference Include="BitMart.Net" Version="4.5.0" />
<PackageReference Include="BloFin.Net" Version="3.5.0" />
<PackageReference Include="Bybit.Net" Version="7.6.0" />
<PackageReference Include="CoinEx.Net" Version="11.5.0" />
<PackageReference Include="CoinW.Net" Version="3.5.0" />
<PackageReference Include="CryptoCom.Net" Version="4.5.0" />
<PackageReference Include="DeepCoin.Net" Version="4.5.0" />
<PackageReference Include="GateIo.Net" Version="4.7.0" />
<PackageReference Include="HyperLiquid.Net" Version="5.8.0" />
<PackageReference Include="JK.BingX.Net" Version="4.6.0" />
<PackageReference Include="JK.Bitget.Net" Version="4.6.0" />
<PackageReference Include="JK.Mexc.Net" Version="6.7.0" />
<PackageReference Include="JK.OKX.Net" Version="5.5.0" />
<PackageReference Include="Jkorf.Aster.Net" Version="4.5.0" />
<PackageReference Include="JKorf.Coinbase.Net" Version="4.6.0" />
<PackageReference Include="JKorf.HTX.Net" Version="9.6.0" />
<PackageReference Include="JKorf.Lighter.Net" Version="1.6.0" />
<PackageReference Include="JKorf.Upbit.Net" Version="3.5.0" />
<PackageReference Include="KrakenExchange.Net" Version="8.5.0" />
<PackageReference Include="Kucoin.Net" Version="9.6.0" />
<PackageReference Include="LBank.Net" Version="1.2.0" />
<PackageReference Include="Pionex.Net" Version="1.3.0" />
<PackageReference Include="Serilog.AspNetCore" Version="10.0.0" />
<PackageReference Include="Tapbit.Net" Version="1.0.0" />
<PackageReference Include="Toobit.Net" Version="4.4.0" />
<PackageReference Include="Weex.Net" Version="2.4.0" />
<PackageReference Include="WhiteBit.Net" Version="4.4.0" />
<PackageReference Include="XT.Net" Version="4.5.0" />
<PackageReference Include="Tapbit.Net" Version="1.1.0" />
<PackageReference Include="Toobit.Net" Version="4.5.0" />
<PackageReference Include="Weex.Net" Version="2.5.0" />
<PackageReference Include="WhiteBit.Net" Version="4.5.0" />
<PackageReference Include="XT.Net" Version="4.6.0" />
</ItemGroup>
</Project>
+1 -6
View File
@@ -5,7 +5,6 @@
@inject IBitfinexRestClient bitfinexClient
@inject IBitgetRestClient bitgetClient
@inject IBitMartRestClient bitmartClient
@inject IBitMEXRestClient bitmexClient
@inject IBloFinRestClient bloFinClient
@inject IBybitRestClient bybitClient
@inject ICoinbaseRestClient coinbaseClient
@@ -47,7 +46,6 @@
var bitfinexTask = bitfinexClient.ExchangeApi.ExchangeData.GetTickerAsync("tBTCUSD");
var bitgetTask = bitgetClient.SpotApiV2.ExchangeData.GetTickersAsync("BTCUSDT");
var bitmartTask = bitmartClient.SpotApi.ExchangeData.GetTickerAsync("BTC_USDT");
var bitmexTask = bitmexClient.ExchangeApi.ExchangeData.GetSymbolsAsync("XBT_USDT");
var bloFinTask = bloFinClient.FuturesApi.ExchangeData.GetTickersAsync("BTC-USDT");
var bybitTask = bybitClient.V5Api.ExchangeData.GetSpotTickersAsync("BTCUSDT");
var coinbaseTask = coinbaseClient.AdvancedTradeApi.ExchangeData.GetSymbolAsync("BTC-USDT");
@@ -71,7 +69,7 @@
var whitebitTask = whitebitClient.V4Api.ExchangeData.GetTickersAsync();
var xtTask = xtClient.SpotApi.ExchangeData.GetTickersAsync("btc_usdt");
await Task.WhenAll(asterTask, binanceTask, bingXTask, bitfinexTask, bitgetTask, bitmartTask, bloFinTask, bitmexTask, bybitTask, coinexTask, coinWTask, deepCoinTask, gateioTask,
await Task.WhenAll(asterTask, binanceTask, bingXTask, bitfinexTask, bitgetTask, bitmartTask, bloFinTask, bybitTask, coinexTask, coinWTask, deepCoinTask, gateioTask,
htxTask, krakenTask, kucoinTask, lBankTask, mexcTask, okxTask, pionexTask, toobitTask, upbitTask, weexTask, whitebitTask, xtTask);
if (asterTask.Result.Success)
@@ -92,9 +90,6 @@
if (bitmartTask.Result.Success)
_prices.Add("BitMart", bitmartTask.Result.Data.LastPrice);
if (bitmexTask.Result.Success)
_prices.Add("BitMEX", bitmexTask.Result.Data.First().LastPrice);
if (bloFinTask.Result.Success)
_prices.Add("BloFin", bloFinTask.Result.Data.First().LastPrice);
@@ -5,7 +5,6 @@
@inject IBitfinexSocketClient bitfinexSocketClient
@inject IBitgetSocketClient bitgetSocketClient
@inject IBitMartSocketClient bitmartSocketClient
@inject IBitMEXSocketClient bitmexSocketClient
@inject IBloFinSocketClient bloFinSocketClient
@inject IBybitSocketClient bybitSocketClient
@inject ICoinbaseSocketClient coinbaseSocketClient
@@ -56,7 +55,6 @@
bitfinexSocketClient.ExchangeApi.SubscribeToTickerUpdatesAsync("tETHBTC", data => UpdateData("Bitfinex", data.Data.LastPrice)),
bitgetSocketClient.SpotApiV2.SubscribeToTickerUpdatesAsync("ETHBTC", data => UpdateData("Bitget", data.Data.First().LastPrice)),
bitmartSocketClient.SpotApi.SubscribeToTickerUpdatesAsync("ETH_BTC", data => UpdateData("BitMart", data.Data.LastPrice)),
bitmexSocketClient.ExchangeApi.SubscribeToSymbolUpdatesAsync("ETH_XBT", data => UpdateData("BitMEX", data.Data.LastPrice ?? 0)),
// BloFin doesn't support the ETH/BTC pair
//bloFinSocketClient.FuturesApi.SubscribeToTickerUpdatesAsync("ETH-BTC", data => UpdateData("BloFin", data.Data.LastPrice)),
bybitSocketClient.V5SpotApi.SubscribeToTickerUpdatesAsync("ETHBTC", data => UpdateData("Bybit", data.Data.LastPrice)),
@@ -7,7 +7,6 @@
@using Bitfinex.Net.Interfaces
@using Bitget.Net.Interfaces;
@using BitMart.Net.Interfaces;
@using BitMEX.Net.Interfaces;
@using BloFin.Net.Interfaces;
@using Bybit.Net.Interfaces
@using CoinEx.Net.Interfaces
@@ -40,7 +39,6 @@
@inject IBitfinexOrderBookFactory bitfinexFactory
@inject IBitgetOrderBookFactory bitgetFactory
@inject IBitMartOrderBookFactory bitmartFactory
@inject IBitMEXOrderBookFactory bitmexFactory
@inject IBloFinOrderBookFactory bloFinFactory
@inject IBybitOrderBookFactory bybitFactory
@inject ICoinbaseOrderBookFactory coinbaseFactory
@@ -103,7 +101,6 @@
{ "Bitfinex", bitfinexFactory.Create("tETHBTC") },
{ "Bitget", bitgetFactory.CreateSpot("ETHBTC") },
{ "BitMart", bitmartFactory.CreateSpot("ETH_BTC", null) },
{ "BitMEX", bitmexFactory.Create("ETH_XBT") },
{ "BloFin", bloFinFactory.CreateFutures("ETH-USDT") },
{ "Bybit", bybitFactory.Create("ETHBTC", Bybit.Net.Enums.Category.Spot) },
{ "Coinbase", coinbaseFactory.Create("ETH-BTC", null) },
+2 -2
View File
@@ -1,7 +1,7 @@
@page "/SpotClient"
@using CryptoExchange.Net.SharedApis
@using System.Diagnostics
@inject IEnumerable<ISpotTickerRestClient> restClients
@inject IEnumerable<IGetTicker> restClients
<h3>ETH-BTC prices:</h3>
@foreach(var price in _prices.OrderBy(p => p.Key))
@@ -15,7 +15,7 @@
protected override async Task OnInitializedAsync()
{
var symbol = new SharedSymbol(TradingMode.Spot, "ETH", "BTC");
var tasks = restClients.Select(x => x.GetSpotTickerAsync(new GetTickerRequest(symbol)));
var tasks = restClients.Where(x => x.SupportedTradingModes.Contains(TradingMode.Spot)).Select(x => x.GetTickerAsync(new GetTickerRequest(symbol)));
await foreach (var ticker in ParallelEnumerateAsync(tasks))
{
if (ticker.Success)
@@ -6,7 +6,6 @@
@using BingX.Net.Interfaces
@using Bitfinex.Net.Interfaces
@using Bitget.Net.Interfaces;
@using BitMEX.Net.Interfaces;
@using BitMart.Net.Interfaces;
@using BloFin.Net.Interfaces
@using Bybit.Net.Interfaces
@@ -40,7 +39,6 @@
@inject IBitfinexTrackerFactory bitfinexFactory
@inject IBitgetTrackerFactory bitgetFactory
@inject IBitMartTrackerFactory bitmartFactory
@inject IBitMEXTrackerFactory bitmexFactory
@inject IBloFinTrackerFactory bloFinFactory
@inject IBybitTrackerFactory bybitFactory
@inject ICoinbaseTrackerFactory coinbaseFactory
@@ -96,7 +94,6 @@
{ bitfinexFactory.CreateTradeTracker(symbol, period: TimeSpan.FromMinutes(5)) },
{ bitgetFactory.CreateTradeTracker(symbol, period: TimeSpan.FromMinutes(5)) },
{ bitmartFactory.CreateTradeTracker(symbol, period: TimeSpan.FromMinutes(5)) },
{ bitmexFactory.CreateTradeTracker(symbol, period: TimeSpan.FromMinutes(5)) },
{ bloFinFactory.CreateTradeTracker(futuresSymbol, period: TimeSpan.FromMinutes(5)) },
{ bybitFactory.CreateTradeTracker(symbol, period: TimeSpan.FromMinutes(5)) },
{ coinbaseFactory.CreateTradeTracker(symbol, period: TimeSpan.FromMinutes(5)) },
-1
View File
@@ -39,7 +39,6 @@ namespace BlazorClient
services.AddBitfinex();
services.AddBitget();
services.AddBitMart();
services.AddBitMEX();
services.AddBloFin();
services.AddBybit();
services.AddCoinbase();
-1
View File
@@ -14,7 +14,6 @@
@using Bitfinex.Net.Interfaces.Clients;
@using Bitget.Net.Interfaces.Clients;
@using BitMart.Net.Interfaces.Clients;
@using BitMEX.Net.Interfaces.Clients;
@using BloFin.Net.Interfaces.Clients;
@using Bybit.Net.Interfaces.Clients;
@using Coinbase.Net.Interfaces.Clients;
+2 -2
View File
@@ -8,8 +8,8 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Binance.Net" Version="12.13.0" />
<PackageReference Include="Bybit.Net" Version="6.13.0" />
<PackageReference Include="Binance.Net" Version="13.6.0" />
<PackageReference Include="Bybit.Net" Version="7.6.0" />
</ItemGroup>
</Project>
+26 -34
View File
@@ -25,18 +25,22 @@ var exchanges = new Dictionary<string, ExchangeClients>(StringComparer.OrdinalIg
{
["binance"] = new ExchangeClients(
"Binance",
binanceRest.SpotApi.SharedClient,
binanceRest.SpotApi.SharedClient,
binanceRest.SpotApi.SharedClient,
binanceRest.SpotApi.SharedClient,
binanceSocket.SpotApi.SharedClient),
binanceRest.SpotApi.SharedApi,
binanceRest.SpotApi.SharedApi,
binanceRest.SpotApi.SharedApi,
binanceRest.SpotApi.SharedApi,
binanceRest.SpotApi.SharedApi,
binanceRest.SpotApi.SharedApi,
binanceSocket.SpotApi.SharedApi),
["bybit"] = new ExchangeClients(
"Bybit",
bybitRest.V5Api.SharedClient,
bybitRest.V5Api.SharedClient,
bybitRest.V5Api.SharedClient,
bybitRest.V5Api.SharedClient,
bybitSocket.V5SpotApi.SharedClient)
bybitRest.V5Api.SharedApi,
bybitRest.V5Api.SharedApi,
bybitRest.V5Api.SharedApi,
bybitRest.V5Api.SharedApi,
bybitRest.V5Api.SharedApi,
bybitRest.V5Api.SharedApi,
bybitSocket.V5SpotApi.SharedApi)
};
PrintHelp();
@@ -98,7 +102,7 @@ async Task PrintPricesAsync(string[] args)
var symbol = GetSymbol(args, 1);
var tasks = exchanges.Values.Select(async exchange =>
{
var result = await exchange.TickerRest.GetSpotTickerAsync(new GetTickerRequest(symbol));
var result = await exchange.TickerRest.GetTickerAsync(new GetTickerRequest(symbol));
if (!result.Success)
{
Console.WriteLine($"{exchange.Name,-8} error: {result.Error}");
@@ -171,7 +175,7 @@ async Task PrintOpenOrdersAsync(string[] args)
var exchange = GetExchange(args, 1);
var symbol = GetSymbol(args, 2);
var result = await exchange.OrderRest.GetOpenSpotOrdersAsync(new GetOpenOrdersRequest(symbol));
var result = await exchange.GetOpenOrdersRest.GetOpenSpotOrdersAsync(new GetOpenOrdersRequest(symbol));
if (!result.Success)
{
Console.WriteLine($"Open orders request failed: {result.Error}");
@@ -203,23 +207,9 @@ async Task PlaceLimitOrderAsync(string[] args)
SharedQuantity.Base(quantity),
price,
SharedTimeInForce.GoodTillCanceled,
exchange.OrderRest.GenerateClientOrderId());
exchange.PlaceOrderRest.GenerateClientOrderId());
var validationError = exchange.OrderRest.PlaceSpotOrderOptions.ValidateRequest(
exchange.Name,
request,
TradingMode.Spot,
exchange.OrderRest.SupportedTradingModes,
exchange.OrderRest.SpotSupportedOrderTypes,
exchange.OrderRest.SpotSupportedTimeInForce,
exchange.OrderRest.SpotSupportedOrderQuantity);
if (validationError != null)
{
Console.WriteLine($"Order request is not valid for {exchange.Name}: {validationError}");
return;
}
var result = await exchange.OrderRest.PlaceSpotOrderAsync(request);
var result = await exchange.PlaceOrderRest.PlaceSpotOrderAsync(request);
Console.WriteLine(result.Success
? $"Order placed. Id: {result.Data.Id}"
: $"Order failed: {result.Error}");
@@ -233,7 +223,7 @@ async Task CancelOrderAsync(string[] args)
if (string.IsNullOrWhiteSpace(orderId))
throw new ArgumentException("Missing order id. Example: cancel-order binance BTC USDT 123456");
var result = await exchange.OrderRest.CancelSpotOrderAsync(new CancelOrderRequest(symbol, orderId));
var result = await exchange.CancelOrderRest.CancelSpotOrderAsync(new CancelOrderRequest(symbol, orderId));
Console.WriteLine(result.Success
? $"Order canceled. Id: {result.Data.Id}"
: $"Cancel failed: {result.Error}");
@@ -296,8 +286,10 @@ static void PrintHelp()
internal record ExchangeClients(
string Name,
ISpotTickerRestClient TickerRest,
IOrderBookRestClient OrderBookRest,
IBalanceRestClient BalanceRest,
ISpotOrderRestClient OrderRest,
ITickerSocketClient TickerSocket);
IGetTickerRest TickerRest,
IGetOrderBookRest OrderBookRest,
IGetBalancesRest BalanceRest,
IPlaceSpotOrderRest PlaceOrderRest,
IGetOpenSpotOrdersRest GetOpenOrdersRest,
ICancelSpotOrderRest CancelOrderRest,
ISubscribeTickerSocket TickerSocket);
+9 -9
View File
@@ -5,13 +5,13 @@ using CryptoExchange.Net.SharedApis;
using OKX.Net.Clients;
var symbol = new SharedSymbol(TradingMode.Spot, "ETH", "USDT");
var binanceSpotRestClient = new BinanceRestClient().SpotApi.SharedClient;
var okxSpotRestClient = new OKXRestClient().UnifiedApi.SharedClient;
var bitmartSpotRestClient = new BitMartRestClient().SpotApi.SharedClient;
var binanceSpotRestClient = new BinanceRestClient().SpotApi.SharedApi;
var okxSpotRestClient = new OKXRestClient().UnifiedApi.SharedApi;
var bitmartSpotRestClient = new BitMartRestClient().SpotApi.SharedApi;
var binanceSpotSocketClient = new BinanceSocketClient().SpotApi.SharedClient;
var okxSpotSocketClient = new OKXSocketClient().UnifiedApi.SharedClient;
var bitmartSpotSocketClient = new BitMartSocketClient().SpotApi.SharedClient;
var binanceSpotSocketClient = new BinanceSocketClient().SpotApi.SharedApi;
var okxSpotSocketClient = new OKXSocketClient().UnifiedApi.SharedApi;
var bitmartSpotSocketClient = new BitMartSocketClient().SpotApi.SharedApi;
await GetLastTradePriceAsync(binanceSpotRestClient, symbol);
await GetLastTradePriceAsync(okxSpotRestClient, symbol);
@@ -31,9 +31,9 @@ Console.ReadLine();
foreach (var subscription in subscriptions)
await subscription.CloseAsync();
async Task GetLastTradePriceAsync(ISpotTickerRestClient client, SharedSymbol symbol)
async Task GetLastTradePriceAsync(IGetTickerRest client, SharedSymbol symbol)
{
var result = await client.GetSpotTickerAsync(new GetTickerRequest(symbol));
var result = await client.GetTickerAsync(new GetTickerRequest(symbol));
if (!result.Success)
{
Console.WriteLine($"Failed to get ticker: {result.Error}");
@@ -43,7 +43,7 @@ async Task GetLastTradePriceAsync(ISpotTickerRestClient client, SharedSymbol sym
Console.WriteLine($"{client.Exchange} {result.Data.Symbol}: {result.Data.LastPrice}");
}
async Task SubscribeTickerUpdatesAsync(ITickerSocketClient client, SharedSymbol symbol, ICollection<UpdateSubscription> subscriptions)
async Task SubscribeTickerUpdatesAsync(ISubscribeTickerSocket client, SharedSymbol symbol, ICollection<UpdateSubscription> subscriptions)
{
var result = await client.SubscribeToTickerUpdatesAsync(new SubscribeTickerRequest(symbol), update =>
{
+3 -3
View File
@@ -8,9 +8,9 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Binance.Net" Version="12.13.0" />
<PackageReference Include="BitMart.Net" Version="3.11.2" />
<PackageReference Include="JK.OKX.Net" Version="4.14.0" />
<PackageReference Include="Binance.Net" Version="13.6.0" />
<PackageReference Include="BitMart.Net" Version="4.5.0" />
<PackageReference Include="JK.OKX.Net" Version="5.5.0" />
</ItemGroup>
</Project>
+5
View File
@@ -127,6 +127,11 @@ Various:
* PlatformInfo now required support environment names in the constructor
## Release notes
* Version 13.1.0 - 28 Sep 2026
* Added UnsubscribeAllAsync method to Shared APIs ISharedSubscription and ISharedApiClientBase
* Added WaitAllAsync extension method for waiting on an IAsyncEnumerable to finish
* Added SubscribeAllAsync extension methods for Shared API resolved capabilities
* Version 13.0.0 - 23 Sep 2026
* Shared APIs
* Added Shared API V2 with fine-grained capability interfaces for individual REST requests, WebSocket requests and subscriptions
+147
View File
@@ -0,0 +1,147 @@
# CryptoExchange.Net Shared API V2 map
This is a compact map for choosing Shared API V2 types. All listed types are in `CryptoExchange.Net.SharedApis`. Exchange libraries implement subsets; the map describes available abstractions, not guaranteed exchange support.
## Entry points
| Need | Use |
| --- | --- |
| Known exchange and API surface | Its typed `.SharedApi` property |
| Runtime selection within one exchange | Exchange-wide `I[Exchange]SharedApiClient` |
| Strongly typed runtime lookup key | `SharedCapabilities` |
| One preferred match | `GetCapability(...)` |
| Every matching surface/transport in one exchange | `GetCapabilities(...)` |
| V1 compatibility during migration | `.SharedClient` |
Prefer a direct typed `.SharedApi` when the API surface is known. Dynamic lookup returns `SharedCapabilityResolution<T>?`; handle `null`.
```csharp
IGetTickerRest ticker = restClient.SpotApi.SharedApi;
var result = await ticker.GetTickerAsync(request);
```
```csharp
var match = sharedClient.GetCapability(
SharedCapabilities.Tickers.GetTicker.Rest,
TradingMode.Spot);
```
## Interface suffixes and results
| Interface shape | Transport | Typical result |
| --- | --- | --- |
| Operation with no suffix, e.g. `IPlaceSpotOrder` | selected/preferred | `IExchangeCallResult<T>` |
| `...Rest` | REST | `HttpResult<T>` |
| socket command, e.g. `IPlaceSpotOrderSocket` | WebSocket | `QueryResult<T>` |
| subscription, e.g. `ISubscribeTickerSocket` | WebSocket | `WebSocketResult<UpdateSubscription>` |
When a `SharedCapabilities` entry supports multiple transports, use the base entry for preferred transport, `.Rest` for REST, or `.Socket` for socket. Exchange-wide preferred transport normally defaults to REST.
## Market data
| Operation | Capability interface | Lookup reference |
| --- | --- | --- |
| Get one ticker | `IGetTickerRest` | `SharedCapabilities.Tickers.GetTicker.Rest` |
| Get all tickers | `IGetAllTickersRest` | `SharedCapabilities.Tickers.GetAllTickers.Rest` |
| Subscribe to one ticker | `ISubscribeTickerSocket` | `SharedCapabilities.Tickers.SubscribeTicker` |
| Subscribe to all tickers | `ISubscribeAllTickersSocket` | `SharedCapabilities.Tickers.SubscribeAllTickers` |
| Subscribe to book ticker | `ISubscribeBookTickerSocket` | `SharedCapabilities.Tickers.SubscribeBookTicker` |
| Get order book | `IGetOrderBookRest` | `SharedCapabilities.OrderBooks.GetOrderBook.Rest` |
| Get book ticker | `IGetBookTickerRest` | `SharedCapabilities.OrderBooks.GetBookTicker.Rest` |
| Subscribe to order book | `ISubscribeOrderBookSocket` | `SharedCapabilities.OrderBooks.SubscribeOrderBook` |
| Subscribe to incremental book | `ISubscribeIncrementalOrderBookSocket` | `SharedCapabilities.OrderBooks.SubscribeIncrementalOrderBook` |
| Get klines | `IGetKlinesRest` | `SharedCapabilities.Klines.GetKlines.Rest` |
| Subscribe to klines | `ISubscribeKlinesSocket` | `SharedCapabilities.Klines.SubscribeKlines` |
| Get recent trades | `IGetRecentTradesRest` | `SharedCapabilities.Trades.GetRecentTrades.Rest` |
| Subscribe to trades | `ISubscribeTradesSocket` | `SharedCapabilities.Trades.SubscribeTrades` |
| Get spot symbols | `IGetSpotSymbolsRest` | `SharedCapabilities.Symbols.GetSpotSymbols.Rest` |
| Get futures symbols | `IGetFuturesSymbolsRest` | `SharedCapabilities.Symbols.GetFuturesSymbols.Rest` |
Ticker V2 methods are `GetTickerAsync` and `GetAllTickersAsync`; both use `SharedTicker` for spot and futures. Use separate mark-price, index-price, funding, or open-interest capabilities for derivatives-specific data.
## Spot orders
| Operation | Capability family | Lookup reference |
| --- | --- | --- |
| Place | `IPlaceSpotOrder`, `IPlaceSpotOrderRest`, `IPlaceSpotOrderSocket` | `SharedCapabilities.Orders.Spot.PlaceOrder[.Rest/.Socket]` |
| Edit | `IEditSpotOrder`, `IEditSpotOrderRest`, `IEditSpotOrderSocket` | `SharedCapabilities.Orders.Spot.EditOrder[.Rest/.Socket]` |
| Cancel | `ICancelSpotOrder`, `ICancelSpotOrderRest`, `ICancelSpotOrderSocket` | `SharedCapabilities.Orders.Spot.CancelOrder[.Rest/.Socket]` |
| Get one | `IGetSpotOrderRest` | `SharedCapabilities.Orders.Spot.GetOrder.Rest` |
| Get open | `IGetOpenSpotOrdersRest` | `SharedCapabilities.Orders.Spot.GetOpenOrders.Rest` |
| Get closed | `IGetClosedSpotOrdersRest` | `SharedCapabilities.Orders.Spot.GetClosedOrders.Rest` |
| Subscribe to updates | `ISubscribeSpotOrdersSocket` | `SharedCapabilities.Orders.Spot.SubscribeOrders` |
Client-order-id variants exist for edit, cancel, and get. Batch placement and cancel-all capabilities also exist; resolve them only when the application needs those operations and the exchange supports them.
## Futures orders
| Operation | Capability family | Lookup reference |
| --- | --- | --- |
| Place | `IPlaceFuturesOrder`, `IPlaceFuturesOrderRest`, `IPlaceFuturesOrderSocket` | `SharedCapabilities.Orders.Futures.PlaceOrder[.Rest/.Socket]` |
| Edit | `IEditFuturesOrder`, `IEditFuturesOrderRest`, `IEditFuturesOrderSocket` | `SharedCapabilities.Orders.Futures.EditOrder[.Rest/.Socket]` |
| Cancel | `ICancelFuturesOrder`, `ICancelFuturesOrderRest`, `ICancelFuturesOrderSocket` | `SharedCapabilities.Orders.Futures.CancelOrder[.Rest/.Socket]` |
| Get one | `IGetFuturesOrderRest` | `SharedCapabilities.Orders.Futures.GetOrder.Rest` |
| Get open | `IGetOpenFuturesOrdersRest` | `SharedCapabilities.Orders.Futures.GetOpenOrders.Rest` |
| Get closed | `IGetClosedFuturesOrdersRest` | `SharedCapabilities.Orders.Futures.GetClosedOrders.Rest` |
| Subscribe to updates | `ISubscribeFuturesOrdersSocket` | `SharedCapabilities.Orders.Futures.SubscribeOrders` |
Specify `TradingMode.PerpetualLinear`, `PerpetualInverse`, or the appropriate delivery mode when multiple futures APIs can match.
## Account and derivatives
Use the following capability families for common account workflows:
| Area | Common interfaces |
| --- | --- |
| Balances | `IGetBalancesRest`, `ISubscribeBalancesSocket` |
| Positions | `IGetPositionsRest`, `ISubscribePositionsSocket`, `IGetPositionHistoryRest` |
| User trades | `IGetSpotUserTradeHistoryRest`, `IGetFuturesUserTradeHistoryRest`, `ISubscribeUserTradesSocket` |
| Fees | `IGetFeesRest` |
| Deposits | `IGetDepositAddressesRest`, `IGetDepositHistoryRest` |
| Withdrawals | `IWithdrawRest`, `IGetWithdrawalHistoryRest` |
| Transfers | `ITransferRest`, `IGetTransferHistoryRest` |
| Leverage | `IGetLeverageRest`, `ISetLeverageRest`, `IGetLeverageTiersRest` |
| Funding | `IGetFundingInfoRest`, `IGetFundingRateHistoryRest`, `IGetUserFundingHistoryRest` |
| Mark/index data | `IGetMarkPriceRest`, `IGetIndexPriceRest` and their all-market/subscription variants |
| Open interest | `IGetOpenInterestRest` |
The `SharedCapabilities` catalog groups these under matching plural categories such as `Balances`, `Positions`, `Funding`, `Leverage`, `MarkPrices`, and `IndexPrices`.
## Parameter support map
Every successful runtime resolution includes `Options`:
| Property | Meaning |
| --- | --- |
| `RequestParameterRules` | Shared request fields that are required, optional, or unsupported |
| `ExchangeParameterRules` | Exchange-specific fields accepted through `ExchangeParameters` |
| `SupportedTradingModes` | Modes supported by this capability implementation |
| `NeedsAuthentication` | Whether credentials are required |
Capability presence is not proof that all properties on its request model are accepted. Inspect rules for dynamically generated requests.
## Common V1 to V2 mappings
| V1 call | V2 capability call |
| --- | --- |
| `ISpotTickerRestClient.GetSpotTickerAsync` | `IGetTickerRest.GetTickerAsync` |
| `ISpotTickerRestClient.GetSpotTickersAsync` | `IGetAllTickersRest.GetAllTickersAsync` |
| `ISpotOrderRestClient.PlaceSpotOrderAsync` | `IPlaceSpotOrderRest.PlaceSpotOrderAsync` |
| `ISpotOrderRestClient.CancelSpotOrderAsync` | `ICancelSpotOrderRest.CancelSpotOrderAsync` |
| `IFuturesOrderRestClient.PlaceFuturesOrderAsync` | `IPlaceFuturesOrderRest.PlaceFuturesOrderAsync` |
| `IFuturesOrderRestClient.CancelFuturesOrderAsync` | `ICancelFuturesOrderRest.CancelFuturesOrderAsync` |
| `IFuturesOrderRestClient.GetPositionsAsync` | `IGetPositionsRest.GetPositionsAsync` |
V1 uses `.SharedClient`; V2 uses `.SharedApi`. Migrate operation by operation.
## Selection checklist
1. Use the exchange-native API if portability is unnecessary.
2. For portable code, identify the single operation capability required.
3. Use the typed `.SharedApi` directly when the surface is known.
4. Otherwise resolve through the exchange-wide shared client and handle `null`.
5. Specify trading mode and transport when ambiguity matters.
6. Inspect parameter rules for dynamic requests.
7. Check `Success` before using `Data`.
For migration edge cases and CryptoClients.Net cross-exchange lookup, see `SHARED_API_V2_MIGRATION.md`.
+323
View File
@@ -0,0 +1,323 @@
# CryptoExchange.Net — full AI context
Library: CryptoExchange.Net
Library version: 13.1.0
CryptoExchange.Net package version: 13.1.0
Language: C#/.NET
Targets: netstandard2.0, netstandard2.1, net8.0, net9.0, net10.0
## Purpose
CryptoExchange.Net is the base library used by 28+ exchange-specific clients. It provides shared REST and WebSocket infrastructure, authentication and result conventions, rate limiting, order-book support, and exchange-agnostic Shared APIs.
CryptoExchange.Net does not provide exchange endpoints by itself. Install the exchange libraries required by the application, such as Binance.Net, Bybit.Net, JK.OKX.Net, Kraken.Net, or Coinbase.Net. Install `CryptoClients.Net` when one package should provide the full exchange bundle.
Use an exchange's native API for code tied to that exchange. Use Shared API V2 for portable code that performs the same operation against multiple exchanges.
## Shared API V2 mental model
Shared API V2 models each operation as a fine-grained capability. A typed exchange API surface exposes implemented capabilities through `.SharedApi`. Examples include:
- `IGetTickerRest`
- `IGetOrderBookRest`
- `IGetKlinesRest`
- `IPlaceSpotOrderRest`
- `ICancelFuturesOrderRest`
- `ISubscribeTickerSocket`
- `ISubscribeTradesSocket`
This replaces the V1 assumption that one broad interface represents a complete feature group. An exchange may support order placement without editing, or REST placement without socket placement. Write services against only the capabilities they require.
V1 aggregate interfaces remain available through `.SharedClient` for incremental migration. Use `.SharedApi` for new code.
## Installation
Install only the exchange packages needed:
```bash
dotnet add package Binance.Net
dotnet add package JK.OKX.Net
dotnet add package Bybit.Net
```
Or install the combined package:
```bash
dotnet add package CryptoClients.Net
```
Do not add CryptoExchange.Net alone and expect to create an exchange client.
## Direct typed capability usage
When the exchange and API surface are known, assign `.SharedApi` to the operation interface. The API surface's compile-time type exposes only the capabilities implemented there.
```csharp
using Binance.Net.Clients;
using OKX.Net.Clients;
using CryptoExchange.Net.SharedApis;
IGetTickerRest binance = new BinanceRestClient().SpotApi.SharedApi;
IGetTickerRest okx = new OKXRestClient().UnifiedApi.SharedApi;
var symbol = new SharedSymbol(TradingMode.Spot, "BTC", "USDT");
var result = await binance.GetTickerAsync(new GetTickerRequest(symbol));
if (!result.Success)
{
Console.WriteLine($"[{result.Exchange}] {result.Error}");
return;
}
Console.WriteLine($"[{result.Exchange}] {result.Data!.LastPrice}");
```
Use `SharedSymbol` rather than exchange-native strings. Each exchange implementation converts base asset, quote asset, and trading mode into its own symbol format.
Common trading modes include `Spot`, `PerpetualLinear`, and `PerpetualInverse`. Use the mode that corresponds to the intended exchange API surface.
## Multi-exchange concurrency
Shared capabilities allow uniform concurrent calls:
```csharp
var clients = new IGetTickerRest[]
{
new BinanceRestClient().SpotApi.SharedApi,
new OKXRestClient().UnifiedApi.SharedApi,
new BybitRestClient().V5Api.SharedApi,
};
var symbol = new SharedSymbol(TradingMode.Spot, "BTC", "USDT");
var tasks = clients.Select(client =>
client.GetTickerAsync(new GetTickerRequest(symbol)));
var results = await Task.WhenAll(tasks);
foreach (var ticker in results.Where(x => x.Success))
Console.WriteLine($"{ticker.Exchange}: {ticker.Data!.LastPrice}");
```
Independent requests should normally run concurrently. Reuse long-lived clients or dependency-injected clients instead of creating clients for each request.
## Dynamic capability resolution
Each exchange library registers an exchange-wide `I[Exchange]SharedApiClient`. Use this client when the API surface, trading mode, or transport is selected at runtime.
`SharedCapabilities` is a catalog of strongly typed references. A reference identifies the interface to find; it does not guarantee support and does not perform an exchange request.
```csharp
var resolution = sharedClient.GetCapability(
SharedCapabilities.Orders.Futures.PlaceOrder.Rest,
TradingMode.PerpetualLinear);
if (resolution is null)
{
Console.WriteLine("Futures REST placement is unavailable.");
return;
}
var result = await resolution.Capability.PlaceFuturesOrderAsync(request);
```
`GetCapability` returns one `SharedCapabilityResolution<T>` or `null`. The resolution exposes:
- `Capability`: the callable typed interface.
- `Options`: capability metadata and request rules.
- `Exchange`: the exchange name.
- `Transport`: REST or socket.
`GetCapabilities` returns every matching implementation on that exchange-wide client. Pass a `TradingMode` when an exchange can expose multiple matching API surfaces, especially separate linear and inverse futures APIs.
Use the typed `.SharedApi` property when the desired surface is already known. Use dynamic resolution only when the choice or support is genuinely runtime-dependent.
## Transport selection
Some operations have transport-agnostic, REST-specific, and socket-specific variants. Order placement is representative:
- `IPlaceSpotOrder`: transport-agnostic, returns `IExchangeCallResult<SharedId>`.
- `IPlaceSpotOrderRest`: REST-specific, returns `HttpResult<SharedId>`.
- `IPlaceSpotOrderSocket`: socket-specific, returns `QueryResult<SharedId>`.
Subscription interfaces such as `ISubscribeTickerSocket` return `WebSocketResult<UpdateSubscription>`.
Use a capability reference without a suffix when either transport is acceptable:
```csharp
var preferred = sharedClient.GetCapability(
SharedCapabilities.Orders.Spot.PlaceOrder);
```
Use `.Rest` or `.Socket` when the transport or concrete result type matters:
```csharp
var rest = sharedClient.GetCapability(
SharedCapabilities.Orders.Spot.PlaceOrder.Rest);
var socket = sharedClient.GetCapability(
SharedCapabilities.Orders.Spot.PlaceOrder.Socket);
```
Exchange-wide transport-agnostic lookup follows the configured `SharedApi.PreferredTransport`, which normally defaults to REST. Do not rely on that preference when application semantics require a particular transport.
## Result handling
All result types expose `Success`, `Data`, and `Error`. Check `Success` before reading `Data`.
- REST capability: `HttpResult<T>`
- socket command capability: `QueryResult<T>`
- socket subscription capability: `WebSocketResult<UpdateSubscription>`
- transport-agnostic capability: `IExchangeCallResult<T>`
Shared results carry the exchange name, which should be included in logs and aggregation records.
```csharp
if (!result.Success)
{
logger.LogWarning(
"{Exchange} ticker request failed: {Error}",
result.Exchange,
result.Error);
return;
}
```
## Request parameter discovery
A capability can exist even when an exchange does not accept every property on the shared request. Inspect the selected capability's `Options` before building fully dynamic requests:
- `RequestParameterRules`: each shared request field is `Required`, `Optional`, or `NotSupported`.
- `ExchangeParameterRules`: exchange-specific required or optional parameters supplied through the request's `ExchangeParameters`.
- `SupportedTradingModes`: modes supported by this particular implementation.
```csharp
var placement = sharedClient.GetCapability(
SharedCapabilities.Orders.Futures.PlaceOrder.Rest,
TradingMode.PerpetualLinear);
var leverageRule = placement?.Options.RequestParameterRules
.FirstOrDefault(rule =>
rule.Name == nameof(PlaceFuturesOrderRequest.Leverage));
if (leverageRule?.Support == RequestParameterSupport.NotSupported)
Console.WriteLine("Set leverage with a separate capability.");
```
Do not infer field support from the request model alone. Handle a missing capability, unsupported trading mode, required field, and required exchange parameter before submitting a trading request.
## Dependency injection
Each exchange library provides a `services.Add[Exchange](...)` extension. Registration includes native clients, V1 shared facades, V2 operation capabilities, and the exchange-wide shared client.
Direct capability injection is appropriate when one implementation is intended:
```csharp
public sealed class TickerService
{
private readonly IGetTickerRest _ticker;
public TickerService(IGetTickerRest ticker)
=> _ticker = ticker;
public Task<HttpResult<SharedTicker>> GetAsync(
SharedSymbol symbol,
CancellationToken ct = default)
=> _ticker.GetTickerAsync(new GetTickerRequest(symbol), ct);
}
```
If multiple exchanges or multiple API surfaces register the same capability interface, inject the exchange-specific shared client and select a typed API property or use `GetCapability`. Resolving one unqualified capability does not express which registered implementation is desired.
`CryptoClients.Net` registers `IExchangeSharedApiClient` for V2 lookup across exchanges. Its `GetCapabilities` returns one preferred matching implementation per exchange, while `GetImplementations` returns every matching transport and API surface.
## Common capability families
Frequently used market-data capabilities:
- Tickers: `IGetTickerRest`, `IGetAllTickersRest`, `ISubscribeTickerSocket`, `ISubscribeAllTickersSocket`, `ISubscribeBookTickerSocket`.
- Order books: `IGetOrderBookRest`, `IGetBookTickerRest`, `ISubscribeOrderBookSocket`, `ISubscribeIncrementalOrderBookSocket`.
- Klines and trades: `IGetKlinesRest`, `ISubscribeKlinesSocket`, `IGetRecentTradesRest`, `ISubscribeTradesSocket`.
- Symbols: `IGetSpotSymbolsRest`, `IGetFuturesSymbolsRest`.
Frequently used trading and account capabilities:
- Spot and futures order placement, editing, cancellation, retrieval, open/closed orders, and order-update subscriptions.
- Balances, positions, user trades, fees, deposits, withdrawals, and transfers.
- Funding, open interest, leverage, mark price, and index price operations for derivatives.
Use `docs/ai-api-map.md` or the typed API surface for names. Do not assume every exchange implements every family.
## V2 ticker behavior
V2 uses `GetTickerAsync` and `GetAllTickersAsync` for both spot and futures and returns `SharedTicker`. Futures-specific mark price, index price, and funding data have separate capabilities rather than being assumed to exist on the common ticker.
```csharp
IGetTickerRest ticker = restClient.SpotApi.SharedApi;
var result = await ticker.GetTickerAsync(
new GetTickerRequest(
new SharedSymbol(TradingMode.Spot, "ETH", "USDT")));
```
## Order and subscription behavior
V2 exposes REST and socket order commands under the same operation capability when both are supported. Choose the transport-specific interface when the caller needs `HttpResult<T>` or `QueryResult<T>`.
Order-update subscriptions use `SharedSpotOrderUpdate` and `SharedFuturesOrderUpdate`. REST order retrieval returns `SharedSpotOrder` or `SharedFuturesOrder`. Keep update-only stream semantics out of ordinary order snapshots.
`ICloseFullPosition` closes the entire position and has no quantity parameter. It is not a replacement for partial close logic; use a suitable order capability when an exchange supports partial reduction.
## V1 migration essentials
V1 remains available through `.SharedClient`; V2 is exposed through `.SharedApi`. Both are views over the exchange API, so migration can happen operation by operation.
```csharp
var symbol = new SharedSymbol(TradingMode.Spot, "BTC", "USDT");
var request = new GetTickerRequest(symbol);
// V1 compatibility facade
var oldResult = await restClient.SpotApi.SharedClient
.GetSpotTickerAsync(request);
// V2 capability
var newResult = await restClient.SpotApi.SharedApi
.GetTickerAsync(request);
```
Representative mappings:
- `ISpotTickerRestClient.GetSpotTickerAsync` -> `IGetTickerRest.GetTickerAsync`
- `ISpotOrderRestClient.PlaceSpotOrderAsync` -> `IPlaceSpotOrderRest.PlaceSpotOrderAsync`
- `ISpotOrderRestClient.CancelSpotOrderAsync` -> `ICancelSpotOrderRest.CancelSpotOrderAsync`
- `IFuturesOrderRestClient.PlaceFuturesOrderAsync` -> `IPlaceFuturesOrderRest.PlaceFuturesOrderAsync`
- `IFuturesOrderRestClient.GetPositionsAsync` -> `IGetPositionsRest.GetPositionsAsync`
Transport-agnostic V2 methods return `IExchangeCallResult<T>`. Select `...Rest` or `...Socket` if old code depends on a concrete transport result. See `docs/SHARED_API_V2_MIGRATION.md` for detailed migration examples and CryptoClients.Net behavior.
## Code-generation rules
- Use `.SharedApi` and fine-grained V2 interfaces for new portable code.
- Use `.SharedClient` only when maintaining or incrementally migrating V1 code.
- Use `SharedSymbol` instead of native symbol strings.
- Resolve capability support rather than assuming it.
- Inspect capability options when requests are generated dynamically.
- Select REST or socket explicitly when transport semantics matter.
- Check `Success` before accessing `Data`.
- Reuse clients through DI and keep calls asynchronous.
- Run independent exchange calls concurrently.
- Keep exchange-native models out of cross-exchange service contracts.
## Implementing an exchange library
Exchange implementations derive from the shared REST and socket client bases, provide their authentication and option types, and expose typed Shared API classes. Implement the fine-grained capability interfaces actually supported by each API surface and publish accurate `CapabilityOptions`, including trading modes and request/exchange parameter rules. Register the Shared APIs and exchange-wide shared client in the exchange library's DI extension.
Use established exchange libraries as references. Do not advertise a capability merely because a similar native endpoint exists; its shared request and result semantics must be implemented correctly.
## References
- Source: https://github.com/JKorf/CryptoExchange.Net
- Documentation: https://cryptoexchange.jkorf.dev/
- Shared API V2 migration: https://github.com/JKorf/CryptoExchange.Net/blob/master/docs/SHARED_API_V2_MIGRATION.md
- AI API map: https://github.com/JKorf/CryptoExchange.Net/blob/master/docs/ai-api-map.md
- CryptoClients.Net: https://github.com/JKorf/CryptoClients.Net
- CryptoManager.Net: https://github.com/JKorf/CryptoManager.Net
- NuGet: https://www.nuget.org/packages/CryptoExchange.Net
- Discord: https://discord.gg/MSpeEtSY8t
+1 -1
View File
@@ -2,7 +2,7 @@
> Base C#/.NET library used by 28+ cryptocurrency exchange clients. It standardizes REST, WebSocket, authentication, rate limiting, results, and exchange-agnostic Shared APIs.
CryptoExchange.Net is not an exchange client by itself. Install exchange-specific packages such as Binance.Net, Bybit.Net, JK.OKX.Net, Kraken.Net, or Coinbase.Net, or install `CryptoClients.Net` for the bundle. Current release: 13.0.0. Targets netstandard2.0, netstandard2.1, net8.0, net9.0, and net10.0; Native AOT is supported.
CryptoExchange.Net is not an exchange client by itself. Install exchange-specific packages such as Binance.Net, Bybit.Net, JK.OKX.Net, Kraken.Net, or Coinbase.Net, or install `CryptoClients.Net` for the bundle. Current release: 13.1.0. Targets netstandard2.0, netstandard2.1, net8.0, net9.0, and net10.0; Native AOT is supported.
## Shared API V2