CryptoExchange.Net & Implementations

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.

When access to multiple or all exchange API's is needed, the CryptoClients.Net library combines all different client libraries in a single Nuget package. The following image illustrates the structure:

These Exchanges/API's are directly supported:

API Repository Nuget
BinanceJKorf/Binance.Net
BingXJKorf/BingX.Net
BitfinexJKorf/Bitfinex.Net
BitgetJKorf/Bitget.Net
BitMartJKorf/BitMart.Net
BitMEXJKorf/BitMEX.Net
BybitJKorf/Bybit.Net
CoinbaseJKorf/Coinbase.Net
CoinExJKorf/CoinEx.Net
CoinGeckoJKorf/CoinGecko.Net
Crypto.comJKorf/CryptoCom.Net
DeepCoinJKorf/DeepCoin.Net
Gate.ioJKorf/GateIo.Net
HTXJKorf/HTX.Net
HyperLiquidJKorf/HyperLiquid.Net
KrakenJKorf/Kraken.Net
KucoinJKorf/Kucoin.Net
MexcJKorf/Mexc.Net
OKXJKorf/OKX.Net
WhiteBitJKorf/WhiteBit.Net
XTJKorf/XT.Net

Note that there are 3rd party implementations going around, but only the listed ones here are created and supported by me.

When using multiple of these API's the CryptoClients.Net package can be used which combines these packages and allows easy access to all exchange API's.

CryptoClientsJKorf/CryptoClients.Net

Supported Frameworks

The libraries are targeting both .NET Standard 2.0 and .NET Standard 2.1 for optimal compatibility

.NET implementation Version Support
.NET Core2.0 and higher
.NET Framework4.6.1 and higher
Mono5.4 and higher
Xamarin.iOS10.14 and higher
Xamarin.Android8.0 and higher
UWP10.0.16299 and higher
Unity2018.1 and higher

Discord

A Discord server is available here. Feel free to join for discussion and/or questions around the CryptoExchange.Net and implementation libraries.

Support the project

Referral

When creating an account on new exchanges please consider using a referral link from below to support development

ExchangeLink
Bybithttps://partner.bybit.com/b/jkorf
Coinbasehttps://advanced.coinbase.com/join/T6H54H8
CoinExhttps://www.coinex.com/register?refer_code=hd6gn
Crypto.comhttps://crypto.com/exch/26ge92xbkn
DeepCoinhttps://s.deepcoin.com/jddhfca
HTXhttps://www.htx.com/invite/en-us/1f?invite_code=fxp9
HyperLiquidhttps://app.hyperliquid.xyz/join/JKORF
Kucoinhttps://www.kucoin.com/r/rf/QBS4FPED
OKXhttps://okx.com/join/48046699
WhiteBithttps://whitebit.com/referral/a8e59b59-186c-4662-824c-3095248e0edf
XThttps://www.xt.com/en/accounts/register?ref=1HRM5J
Donate

Make a one time donation in a crypto currency of your choice. If you prefer to donate a currency not listed here please contact me.

Btc: bc1q277a5n54s2l2mzlu778ef7lpkwhjhyvghuv8qf
Eth: 0xcb1b63aCF9fef2755eBf4a0506250074496Ad5b7
USDT (TRX): TKigKeJPXZYyMVDgMyXxMf17MWYia92Rjd

Sponsor

Alternatively, sponsor me on Github using Github Sponsors.


Getting Started

All packages are available on Nuget. After installing the package the API is available by using one of the library clients, or through the ICrypoRestClient, ICryptoSocketClient or ISharedClient interfaces.


Installation

Add the package via dotnet, or add it via the package manager. Any number of libraries can be installed, just make sure you're always using the latest at that moment. Instead of installing all libraries seperately the CryptoClients.Net library can be installed to include all the exchange package in one go.

dotnet add package CryptoClients.Net
dotnet add package Binance.Net
dotnet add package JK.BingX.Net
dotnet add package Bitfinex.Net
dotnet add package JK.Bitget.Net
dotnet add package BitMart.Net
dotnet add package Bybit.Net
dotnet add package CoinGecko.Net
dotnet add package JKorf.Coinbase.Net
dotnet add package CoinEx.Net
dotnet add package CryptoCom.Net
dotnet add package GateIo.Net
dotnet add package JKorf.HTX.Net
dotnet add package HyperLiquid.Net
dotnet add package KrakenExchange.Net
dotnet add package Kucoin.Net
dotnet add package JK.Mexc.Net
dotnet add package JK.OKX.Net
dotnet add package WhiteBit.Net
dotnet add package XT.Net

Dependency Injection

All client libraries support and encourage usage via the Dotnet dependency injection system. Add all necesary services per exchange by calling the Add[Library](); extension method on the service collection, or use AddCryptoClients() to add all exchange services in a single class. Options for the clients can be passed as parameters or read from the configuration. See Options.

Using the dependecy injection mechanism also makes sure the HttpClient is used correctly
builder.Services.AddCryptoClients();
builder.Services.AddBinance();
builder.Services.AddBingX();
builder.Services.AddBitfinex();
builder.Services.AddBitget();
builder.Services.AddBitMart();
builder.Services.AddBybit();
builder.Services.AddCoinbase();
builder.Services.AddCoinGecko();
builder.Services.AddCoinEx();
builder.Services.AddCryptoCom();
builder.Services.AddGateIo();
builder.Services.AddHTX();
builder.Services.AddHyperLiquid();
builder.Services.AddKraken();
builder.Services.AddKucoin();
builder.Services.AddMexc();
builder.Services.AddOKX();
builder.Services.AddWhiteBit();
builder.Services.AddXT();

This registers the following interfaces which can then be injected

InterfaceDescription
IExchangeRestClient The client for accessing all exchanges REST API's
IExchangeSocketClient The client for accessing all exchanges Websocket API's
IExchangeOrderBookFactory A factory class for accessing all exchanges SymbolOrderBook (locally synced order books) factory methods, (see orderbooks)
IExchangeTrackerFactory A factory class for creating Kline and Trade trackers (see trackers)
I[Library]RestClient All exchange specific REST clients, for example IBinanceRestClient and IMexcRestClient
I[Library]SocketClient All exchange specific Websocket clients, for example IBinanceSocketClient and IMexcSocketClient
I[Library]OrderBookFactory All exchange specific order book factories. The factory can be used for creating SymbolOrderBook (locally synced order books) instances for the exchange
ISharedClient Various interfaces deriving from ISharedClient which can be used for common functionality
InterfaceDescription
IBinanceRestClient The client for accessing the Binance REST API
IBinanceSocketClient The client for accessing the Binance Websocket API
IBinanceOrderBookFactory A factory for creating SymbolOrderBook instances for the Binance API
IBinanceTrackerFactory A factory for creating kline and trade Tracker instances for the Binance API
ICryptoRestClient An aggregating client from which multiple different library REST clients can be accessed
ICryptoSocketClient An aggregating client from which multiple different library Websocket clients can be accessed
ISharedClient Various interfaces deriving from ISharedClient which can be used for common functionality
InterfaceDescription
IBingXRestClient The client for accessing the BingX REST API
IBingXSocketClient The client for accessing the BingX Websocket API
IBingXOrderBookFactory A factory for creating SymbolOrderBook instances for the BingX API
IBingXTrackerFactory A factory for creating kline and trade Tracker instances for the BingX API
ICryptoRestClient An aggregating client from which multiple different library REST clients can be accessed
ICryptoSocketClient An aggregating client from which multiple different library Websocket clients can be accessed
ISharedClient Various interfaces deriving from ISharedClient which can be used for common functionality
InterfaceDescription
IBitfinexRestClient The client for accessing the Bitfinex REST API
IBitfinexSocketClient The client for accessing the Bitfinex Websocket API
IBitfinexOrderBookFactory A factory for creating SymbolOrderBook instances for the Bitfinex API
IBitfinexTrackerFactory A factory for creating kline and trade Tracker instances for the Bitfinex API
ICryptoRestClient An aggregating client from which multiple different library REST clients can be accessed
ICryptoSocketClient An aggregating client from which multiple different library Websocket clients can be accessed
ISharedClient Various interfaces deriving from ISharedClient which can be used for common functionality
InterfaceDescription
IBitgetRestClient The client for accessing the Bitget REST API
IBitgetSocketClient The client for accessing the Bitget Websocket API
IBitgetOrderBookFactory A factory for creating SymbolOrderBook instances for the Bitget API
IBitgetTrackerFactory A factory for creating kline and trade Tracker instances for the Bitget API
ICryptoRestClient An aggregating client from which multiple different library REST clients can be accessed
ICryptoSocketClient An aggregating client from which multiple different library Websocket clients can be accessed
ISharedClient Various interfaces deriving from ISharedClient which can be used for common functionality
InterfaceDescription
IBitMartRestClient The client for accessing the BitMart REST API
IBitMartSocketClient The client for accessing the BitMart Websocket API
IBitMartOrderBookFactory A factory for creating SymbolOrderBook instances for the Bitget API
IBitMartTrackerFactory A factory for creating kline and trade Tracker instances for the BitMart API
ICryptoRestClient An aggregating client from which multiple different library REST clients can be accessed
ICryptoSocketClient An aggregating client from which multiple different library Websocket clients can be accessed
ISharedClient Various interfaces deriving from ISharedClient which can be used for common functionality
InterfaceDescription
IBybitRestClient The client for accessing the Bybit REST API
IBybitSocketClient The client for accessing the Bybit Websocket API
IBybitOrderBookFactory A factory for creating SymbolOrderBook instances for the Bybit API
IBybitTrackerFactory A factory for creating kline and trade Tracker instances for the Bybit API
ICryptoRestClient An aggregating client from which multiple different library REST clients can be accessed
ICryptoSocketClient An aggregating client from which multiple different library Websocket clients can be accessed
ISharedClient Various interfaces deriving from ISharedClient which can be used for common functionality
InterfaceDescription
ICoinbaseRestClient The client for accessing the Coinbase REST API
ICoinbaseSocketClient The client for accessing the Coinbase Websocket API
ICoinbaseOrderBookFactory A factory for creating SymbolOrderBook instances for the Coinbase API
ICoinbaseTrackerFactory A factory for creating kline and trade Tracker instances for the Coinbase API
ICryptoRestClient An aggregating client from which multiple different library REST clients can be accessed
ICryptoSocketClient An aggregating client from which multiple different library Websocket clients can be accessed
ISharedClient Various interfaces deriving from ISharedClient which can be used for common functionality
InterfaceDescription
ICoinGeckoRestClient The client for accessing the CoinGecko REST API
ICryptoRestClient An aggregating client from which multiple different library REST clients can be accessed
InterfaceDescription
ICoinExRestClient The client for accessing the CoinEx REST API
ICoinExSocketClient The client for accessing the CoinEx Websocket API
ICoinExOrderBookFactory A factory for creating SymbolOrderBook instances for the CoinEx API
ICoinExTrackerFactory A factory for creating kline and trade Tracker instances for the CoinEx API
ICryptoRestClient An aggregating client from which multiple different library REST clients can be accessed
ICryptoSocketClient An aggregating client from which multiple different library Websocket clients can be accessed
ISharedClient Various interfaces deriving from ISharedClient which can be used for common functionality
InterfaceDescription
ICryptoComRestClient The client for accessing the Crypto.com REST API
ICryptoComSocketClient The client for accessing the Crypto.com Websocket API
ICryptoComOrderBookFactory A factory for creating SymbolOrderBook instances for the Crypto.com API
ICryptoComTrackerFactory A factory for creating kline and trade Tracker instances for the Crypto.com API
ICryptoRestClient An aggregating client from which multiple different library REST clients can be accessed
ICryptoSocketClient An aggregating client from which multiple different library Websocket clients can be accessed
ISharedClient Various interfaces deriving from ISharedClient which can be used for common functionality
InterfaceDescription
IGateIoRestClient The client for accessing the GateIo REST API
IGateIoSocketClient The client for accessing the GateIo Websocket API
IGateIoOrderBookFactory A factory for creating SymbolOrderBook instances for the GateIo API
IGateIoTrackerFactory A factory for creating kline and trade Tracker instances for the GateIo API
ICryptoRestClient An aggregating client from which multiple different library REST clients can be accessed
ICryptoSocketClient An aggregating client from which multiple different library Websocket clients can be accessed
ISharedClient Various interfaces deriving from ISharedClient which can be used for common functionality
InterfaceDescription
IHTXRestClient The client for accessing the HTX REST API
IHTXSocketClient The client for accessing the HTX Websocket API
IHTXOrderBookFactory A factory for creating SymbolOrderBook instances for the HTX API
IHTXTrackerFactory A factory for creating kline and trade Tracker instances for the HTX API
ICryptoRestClient An aggregating client from which multiple different library REST clients can be accessed
ICryptoSocketClient An aggregating client from which multiple different library Websocket clients can be accessed
ISharedClient Various interfaces deriving from ISharedClient which can be used for common functionality
InterfaceDescription
IHyperLiquidRestClient The client for accessing the HyperLiquid REST API
IHyperLiquidSocketClient The client for accessing the HyperLiquid Websocket API
IHyperLiquidOrderBookFactory A factory for creating SymbolOrderBook instances for the HyperLiquid API
IHyperLiquidTrackerFactory A factory for creating kline and trade Tracker instances for the HyperLiquid API
ICryptoRestClient An aggregating client from which multiple different library REST clients can be accessed
ICryptoSocketClient An aggregating client from which multiple different library Websocket clients can be accessed
ISharedClient Various interfaces deriving from ISharedClient which can be used for common functionality
InterfaceDescription
IKrakenRestClient The client for accessing the Kraken REST API
IKrakenSocketClient The client for accessing the Kraken Websocket API
IKrakenOrderBookFactory A factory for creating SymbolOrderBook instances for the Kraken API
IKrakenTrackerFactory A factory for creating kline and trade Tracker instances for the Kraken API
ICryptoRestClient An aggregating client from which multiple different library REST clients can be accessed
ICryptoSocketClient An aggregating client from which multiple different library Websocket clients can be accessed
ISharedClient Various interfaces deriving from ISharedClient which can be used for common functionality
InterfaceDescription
IKucoinRestClient The client for accessing the Kucoin REST API
IKucoinSocketClient The client for accessing the Kucoin Websocket API
IKucoinOrderBookFactory A factory for creating SymbolOrderBook instances for the Kucoin API
IKucoinTrackerFactory A factory for creating kline and trade Tracker instances for the Kucoin API
ICryptoRestClient An aggregating client from which multiple different library REST clients can be accessed
ICryptoSocketClient An aggregating client from which multiple different library Websocket clients can be accessed
ISharedClient Various interfaces deriving from ISharedClient which can be used for common functionality
InterfaceDescription
IMexcRestClient The client for accessing the Mexc REST API
IMexcSocketClient The client for accessing the Mexc Websocket API
IMexcOrderBookFactory A factory for creating SymbolOrderBook instances for the Mexc API
IMexcTrackerFactory A factory for creating kline and trade Tracker instances for the Mexc API
ICryptoRestClient An aggregating client from which multiple different library REST clients can be accessed
ICryptoSocketClient An aggregating client from which multiple different library Websocket clients can be accessed
ISharedClient Various interfaces deriving from ISharedClient which can be used for common functionality
InterfaceDescription
IOKXRestClient The client for accessing the OKX REST API
IOKXSocketClient The client for accessing the OKX Websocket API
IOKXOrderBookFactory A factory for creating SymbolOrderBook instances for the OKX API
IOKXTrackerFactory A factory for creating kline and trade Tracker instances for the OKX API
ICryptoRestClient An aggregating client from which multiple different library REST clients can be accessed
ICryptoSocketClient An aggregating client from which multiple different library Websocket clients can be accessed
ISharedClient Various interfaces deriving from ISharedClient which can be used for common functionality
InterfaceDescription
IWhiteBitRestClient The client for accessing the WhiteBit REST API
IWhiteBitSocketClient The client for accessing the WhiteBit Websocket API
IWhiteBitOrderBookFactory A factory for creating SymbolOrderBook instances for the WhiteBit API
IWhiteBitTrackerFactory A factory for creating kline and trade Tracker instances for the WhiteBit API
ICryptoRestClient An aggregating client from which multiple different library REST clients can be accessed
ICryptoSocketClient An aggregating client from which multiple different library Websocket clients can be accessed
ISharedClient Various interfaces deriving from ISharedClient which can be used for common functionality
InterfaceDescription
IXTRestClient The client for accessing the XT REST API
IXTSocketClient The client for accessing the XT Websocket API
IXTOrderBookFactory A factory for creating SymbolOrderBook instances for the XT API
IXTTrackerFactory A factory for creating kline and trade Tracker instances for the XT API
ICryptoRestClient An aggregating client from which multiple different library REST clients can be accessed
ICryptoSocketClient An aggregating client from which multiple different library Websocket clients can be accessed
ISharedClient Various interfaces deriving from ISharedClient which can be used for common functionality

General client usage

All clients work with the same principles:

  • Mandatory parameters are non-nullable while optional parameters are nullable and will have a default value of null.
  • Any operation will return a form of CallResult. This result can and should be checked for success using the Success property. If Success is false the Error property will have more info.
  • Clients will not throw exceptions.

REST API client

Each library provides a REST API client. This client follows the following naming convention: [Library]RestClient. The REST API client is split into different sub-API access clients, which in turn are split into different topics. This structure is the same for each library, which makes it easier to navigate the clients.

The client can be injected via dependency injection, or constructed manually.

var client = new ExchangeRestClient();
var tickersResult = await client.Binance.SpotApi.ExchangeData.GetTickersAsync();
if (!tickersResult.Success)
{
  // Handle error, tickersResult.Error contains more information
}
else
{
  // Handle data, tickersResult.Data will contain the actual data
}
var client = new BinanceRestClient();
var tickersResult = await client.SpotApi.ExchangeData.GetTickersAsync();
if (!tickersResult.Success)
{
  // Handle error, tickersResult.Error contains more information
}
else
{
  // Handle data, tickersResult.Data will contain the actual data
}
var client = new BingXRestClient();
var tickersResult = await client.SpotApi.ExchangeData.GetTickersAsync();
if (!tickersResult.Success)
{
  // Handle error, tickersResult.Error contains more information
}
else
{
  // Handle data, tickersResult.Data will contain the actual data
}
var client = new BitfinexRestClient();
var tickersResult = await client.SpotApi.ExchangeData.GetTickersAsync();
if (!tickersResult.Success)
{
  // Handle error, tickersResult.Error contains more information
}
else
{
  // Handle data, tickersResult.Data will contain the actual data
}
var client = new BitgetRestClient();
var tickersResult = await client.SpotApi.ExchangeData.GetTickersAsync();
if (!tickersResult.Success)
{
  // Handle error, tickersResult.Error contains more information
}
else
{
  // Handle data, tickersResult.Data will contain the actual data
}
var client = new BitMartRestClient();
var tickersResult = await client.SpotApi.ExchangeData.GetTickersAsync();
if (!tickersResult.Success)
{
  // Handle error, tickersResult.Error contains more information
}
else
{
  // Handle data, tickersResult.Data will contain the actual data
}
var client = new BybitRestClient();
var tickersResult = await client.V5Api.ExchangeData.GetSpotTickersAsync();
if (!tickersResult.Success)
{
  // Handle error, tickersResult.Error contains more information
}
else
{
  // Handle data, tickersResult.Data will contain the actual data
}
var client = new CoinbaseRestClient();
var tickersResult = await client.AdvancedTradeApi.ExchangeData.GetSymbolsAsync();
if (!tickersResult.Success)
{
  // Handle error, tickersResult.Error contains more information
}
else
{
  // Handle data, tickersResult.Data will contain the actual data
}
var client = new CoinGeckoRestClient();
var assetsResult = await client.Api.GetAssetsAsync();
if (!assetsResult.Success)
{
  // Handle error, assetsResult.Error contains more information
}
else
{
  // Handle data, assetsResult.Data will contain the actual data
}
var client = new CoinExRestClient();
var tickersResult = await client.SpotApiV2.ExchangeData.GetTickersAsync();
if (!tickersResult.Success)
{
  // Handle error, tickersResult.Error contains more information
}
else
{
  // Handle data, tickersResult.Data will contain the actual data
}
var client = new CryptoComRestClient();
var tickersResult = await client.ExchangeApi.ExchangeData.GetTickersAsync();
if (!tickersResult.Success)
{
  // Handle error, tickersResult.Error contains more information
}
else
{
  // Handle data, tickersResult.Data will contain the actual data
}
var client = new GateIoRestClient();
var tickersResult = await client.SpotApi.ExchangeData.GetTickersAsync();
if (!tickersResult.Success)
{
  // Handle error, tickersResult.Error contains more information
}
else
{
  // Handle data, tickersResult.Data will contain the actual data
}
var client = new HTXRestClient();
var tickersResult = await client.SpotApi.ExchangeData.GetTickersAsync();
if (!tickersResult.Success)
{
  // Handle error, tickersResult.Error contains more information
}
else
{
  // Handle data, tickersResult.Data will contain the actual data
}
var client = new HyperLiquidRestClient();
var tickersResult = await client.SpotApi.ExchangeData.GetExchangeInfoAndTickersAsync();
if (!tickersResult.Success)
{
  // Handle error, tickersResult.Error contains more information
}
else
{
  // Handle data, tickersResult.Data will contain the actual data
}
var client = new KrakenRestClient();
var tickersResult = await client.SpotApi.ExchangeData.GetTickersAsync();
if (!tickersResult.Success)
{
  // Handle error, tickersResult.Error contains more information
}
else
{
  // Handle data, tickersResult.Data will contain the actual data
}
var client = new KucoinRestClient();
var tickersResult = await client.SpotApi.ExchangeData.GetTickersAsync();
if (!tickersResult.Success)
{
  // Handle error, tickersResult.Error contains more information
}
else
{
  // Handle data, tickersResult.Data will contain the actual data
}
var client = new MexcRestClient();
var tickersResult = await client.SpotApi.ExchangeData.GetTickersAsync();
if (!tickersResult.Success)
{
  // Handle error, tickersResult.Error contains more information
}
else
{
  // Handle data, tickersResult.Data will contain the actual data
}
var client = new OKXRestClient();
var tickersResult = await client.UnifiedApi.ExchangeData.GetTickersAsync(OKXInstrumentType.Spot);
if (!tickersResult.Success)
{
  // Handle error, tickersResult.Error contains more information
}
else
{
  // Handle data, tickersResult.Data will contain the actual data
}
var client = new WhiteBitRestClient();
var tickersResult = await client.V4Api.ExchangeData.GetTickersAsync();
if (!tickersResult.Success)
{
  // Handle error, tickersResult.Error contains more information
}
else
{
  // Handle data, tickersResult.Data will contain the actual data
}
var client = new XTRestClient();
var tickersResult = await client.SpotApi.ExchangeData.GetTickersAsync();
if (!tickersResult.Success)
{
  // Handle error, tickersResult.Error contains more information
}
else
{
  // Handle data, tickersResult.Data will contain the actual data
}

The response object

Calls made with the REST API client will return a WebCallResult object. This object contains information about both the request that was send and the response that was received. The WebCallResult object exposes the following properties:

PropertyDescription
Success Whether or not the call was completed successfully
Data The parsed response object, only available when Success is true
Error Error information, only available when Success is false
OriginalData The raw response data, only filled when the OutputOriginalData is enabled in the client options
RequestMethod The HTTP method that was used for the request
RequestHeaders The list of headers which were send with the request
RequestId A unique request id
RequestUrl The full urls which was called
RequestBody The request body send with the request
ResponseLength The length of the response in bytes
ResponseHeaders The list of headers send along with the response
ResponseTime The time it took from sending the request to receiving the response

Websocket API client

If the API supports websocket connections then the library provides a Websocket API client. This client follows the following naming convention: [Library]SocketClient. The Websocket API client is split into different sub-API access clients, which in turn are sometimes split into different topics. This structure is the same for each library, which makes it easier to navigate the clients.

The client can be injected via dependency injection, or constructed manually. When constructing manually keep in mind that when the client is disposed all connections will get closed as well.

The socket client requires a continous connection to the server. The connection can be lost due to internet interuptions, or the server disconnecting the client. This is expected behaviour and the socket client will automatically reconnect when this happens.

Subscribing

var client = new ExchangeSocketClient();
var subscribeResult = await client.Binance.SpotApi.ExchangeData.SubscribeToAllTickerUpdatesAsync(update => {
  // Handle the data update, update.Data will contain the actual data
});
if (!subscribeResult.Success)
{
  // Handle error, subscribeResult.Error contains more information on why the subscription failed
}
// Subscribing was successfull, the data will now be streamed into the data handler
var client = new BinanceSocketClient();
var subscribeResult = await client.SpotApi.ExchangeData.SubscribeToAllTickerUpdatesAsync(update => {
  // Handle the data update, update.Data will contain the actual data
});
if (!subscribeResult.Success)
{
  // Handle error, subscribeResult.Error contains more information on why the subscription failed
}
// Subscribing was successfull, the data will now be streamed into the data handler
var client = new BingXSocketClient();
var subscribeResult = await client.SpotApi.SubscribeToTickerUpdatesAsync("ETH-USDT", update => {
  // Handle the data update, update.Data will contain the actual data
});
if (!subscribeResult.Success)
{
  // Handle error, subscribeResult.Error contains more information on why the subscription failed
}
// Subscribing was successfull, the data will now be streamed into the data handler
var client = new BitfinexSocketClient();
var subscribeResult = await client.SpotApi.SubscribeToTickerUpdatesAsync("tETHUST", update => {
  // Handle the data update, update.Data will contain the actual data
});
if (!subscribeResult.Success)
{
  // Handle error, subscribeResult.Error contains more information on why the subscription failed
}
// Subscribing was successfull, the data will now be streamed into the data handler
var client = new BitgetSocketClient();
var subscribeResult = await client.SpotApi.SubscribeToTickerUpdatesAsync("ETHUSDT", update => {
  // Handle the data update, update.Data will contain the actual data
});
if (!subscribeResult.Success)
{
  // Handle error, subscribeResult.Error contains more information on why the subscription failed
}
// Subscribing was successfull, the data will now be streamed into the data handler
var client = new BitMartSocketClient();
var subscribeResult = await client.SpotApi.SubscribeToTickerUpdatesAsync("ETH_USDT", update => {
  // Handle the data update, update.Data will contain the actual data
});
if (!subscribeResult.Success)
{
  // Handle error, subscribeResult.Error contains more information on why the subscription failed
}
// Subscribing was successfull, the data will now be streamed into the data handler
var client = new BybitSocketClient();
var subscribeResult = await client.V5SpotApi.SubscribeToTickerUpdatesAsync("ETHUSDT", update => {
    // Handle the data update, update.Data will contain the actual data
});
if (!subscribeResult.Success)
{
  // Handle error, subscribeResult.Error contains more information on why the subscription failed
}
// Subscribing was successfull, the data will now be streamed into the data handler
var client = new CoinbaseSocketClient();
var subscribeResult = await sclient.AdvancedTradeApi.SubscribeToTickerUpdatesAsync("ETH-USDT", update => {
    // Handle the data update, update.Data will contain the actual data
});
if (!subscribeResult.Success)
{
    // Handle error, subscribeResult.Error contains more information on why the subscription failed
}
// Subscribing was successfull, the data will now be streamed into the data handler
var client = new CoinExSocketClient();
var subscribeResult = await sclient.SpotApiV2.SubscribeToTickerUpdatesAsync(new[] { "ETHUSDT" }, update => {
    // Handle the data update, update.Data will contain the actual data
});
if (!subscribeResult.Success)
{
    // Handle error, subscribeResult.Error contains more information on why the subscription failed
}
// Subscribing was successfull, the data will now be streamed into the data handler
var client = new CryptoComSocketClient();
var subscribeResult = await client.ExchangeApi.SubscribeToTickerUpdatesAsync("ETH_USD", update => {
  // Handle the data update, update.Data will contain the actual data
});
if (!subscribeResult.Success)
{
  // Handle error, subscribeResult.Error contains more information on why the subscription failed
}
// Subscribing was successfull, the data will now be streamed into the data handler
var client = new GateIoSocketClient();
var subscribeResult = await client.SpotApi.SubscribeToTickerUpdatesAsync("ETH_USDT", update => {
  // Handle the data update, update.Data will contain the actual data
});
if (!subscribeResult.Success)
{
  // Handle error, subscribeResult.Error contains more information on why the subscription failed
}
// Subscribing was successfull, the data will now be streamed into the data handler
var client = new HTXSocketClient();
var subscribeResult = await client.SpotApi.SubscribeToTickerUpdatesAsync(update => {
  // Handle the data update, update.Data will contain the actual data
});
if (!subscribeResult.Success)
{
  // Handle error, subscribeResult.Error contains more information on why the subscription failed
}
// Subscribing was successfull, the data will now be streamed into the data handler
var client = new HyperLiquidSocketClient();
var subscribeResult = await client.SpotApi.SubscribeToSymbolUpdatesAsync("HYPE/USDC", update => {
  // Handle the data update, update.Data will contain the actual data
});
if (!subscribeResult.Success)
{
  // Handle error, subscribeResult.Error contains more information on why the subscription failed
}
// Subscribing was successfull, the data will now be streamed into the data handler
var client = new KrakenSocketClient();
var subscribeResult = await client.SpotApi.SubscribeToTickerUpdatesAsync("ETH/USD", update => {
  // Handle the data update, update.Data will contain the actual data
});
if (!subscribeResult.Success)
{
  // Handle error, subscribeResult.Error contains more information on why the subscription failed
}
// Subscribing was successfull, the data will now be streamed into the data handler
var client = new KucoinSocketClient();
var subscribeResult = await client.SpotApi.SubscribeToAllTickerUpdatesAsync(update => {
  // Handle the data update, update.Data will contain the actual data
});
if (!subscribeResult.Success)
{
  // Handle error, subscribeResult.Error contains more information on why the subscription failed
}
// Subscribing was successfull, the data will now be streamed into the data handler
var client = new MexcSocketClient();
var subscribeResult = await client.SpotApi.SubscribeToMiniTickerUpdatesAsync(update => {
  // Handle the data update, update.Data will contain the actual data
});
if (!subscribeResult.Success)
{
  // Handle error, subscribeResult.Error contains more information on why the subscription failed
}
// Subscribing was successfull, the data will now be streamed into the data handler
var client = new OKXSocketClient();
var subscribeResult = await client.UnifiedApi.ExchangeData.SubscribeToTickerUpdatesAsync("ETH-USDT", update => {
    // Handle the data update, update.Data will contain the actual data
});
if (!subscribeResult.Success)
{
  // Handle error, subscribeResult.Error contains more information on why the subscription failed
}
// Subscribing was successfull, the data will now be streamed into the data handler
var client = new WhiteBitSocketClient();
var subscribeResult = await client.V4Api.ExchangeData.SubscribeToTickerUpdatesAsync("ETH_USDT", update => {
    // Handle the data update, update.Data will contain the actual data
});
if (!subscribeResult.Success)
{
  // Handle error, subscribeResult.Error contains more information on why the subscription failed
}
// Subscribing was successfull, the data will now be streamed into the data handler
var client = new XTSocketClient();
var subscribeResult = await client.SpotApi.ExchangeData.SubscribeToTickerUpdatesAsync("eth_usdt", update => {
    // Handle the data update, update.Data will contain the actual data
});
if (!subscribeResult.Success)
{
  // Handle error, subscribeResult.Error contains more information on why the subscription failed
}
// Subscribing was successfull, the data will now be streamed into the data handler

The subscription result object

Subscriptions calls will return a CallResult<UpdateSubscription> object. This object contains information about the status of the intial subscription, and the UpdateSubscription data object exposes events for status changes and methods for managing the subscription. The CallResult<UpdateSubscription> object exposes the following properties:

PropertyDescription
Success Whether or not the subscription was completed successfully
Error Error information, only available when Success is false
Data The UpdateSubscription object, only available when Success is true
Data.Id Unique id of the subscription
Data.SocketId The id of the underlying websocket
Data.ConnectionLost Event which will be invoked whenever the connection to the server is lost and reconnecting will be started
Data.ConnectionRestored Event which will be invoked when the connection to the server is restored after being disconnected
Data.ActivityPaused Event which will be invoked when the server has indicated that currently no operations will be accepted
Data.ActivityUnpaused Event which will be invoked when the server has indicated that operations will be accepted again after a previous ActivityPaused event
Data.Exception Event which will be invoked when the data handler of the subscription throws an exception

The subscription update event object

Whenever new data is received for a subscription the data handler will be called with a DataEvent<T> object. This object contains information about the event and the actual update data. The DataEvent<T> object exposes the following properties:

PropertyDescription
Timestamp The timestamp the data was received
Topic The topic of the update, typically the symbol or stream name
OriginalData The raw update data, only filled when the OutputOriginalData is enabled in the client options
UpdateType The type of update. SocketUpdateType.Snapshot means the update is a snapshot, not an incremental update. SocketUpdateType.Update means it's an update with new data
Data The data received in the update

Unsubscribing

When no longer interested in updates from a specific subscription it can be unsubscribed. This can be done in one of the following ways:

Unsubscribe via UpdateSubscription object
When you have the reference to the UpdateSubscription object received from the Subscribe method you can call the CloseAsync() method on that to unsubscribe

var subscribeResult = await client.SpotApi.SubscribeToTickerUpdates(data => {});
await subscribeResult.Data.CloseAsync();

Unsubscribe via CancellationToken
Passing in a CancellationToken as parameter in the subscribe method will allow you to cancel subscriptions by canceling the token. This can be useful when you need to cancel some streams but not others. In this example, both BTCUSDT and ETHUSDT streams get canceled, while the XRPUSDT stream remains active.

var cts = new CancellationTokenSource();
var subscriptionResult1 = await client.SpotApi.SubscribeToTickerUpdatesAsync("BTCUSDT", DataHandler, cts.Token);
var subscriptionResult2 = await client.SpotApi.SubscribeToTickerUpdatesAsync("ETHUSDT", DataHandler, cts.Token);
var subscriptionResult3 = await client.SpotApi.SubscribeToTickerUpdatesAsync("XRPUSDT", DataHandler);
cts.Cancel();

Unsubscribe via the websocket client
The Websocket client has multiple ways of unsubscribing one or more subscriptions:

var subscribeResult = await client.SpotApi.SubscribeToTickerUpdates(data => {});
// Unsubscribe by passing the UpdateSubscription
await client.UnsubscribeAsync(subscribeResult.Data);
// OR store the ID and pass that
var subId = subscribeResult.Data.Id;
await client.UnsubscribeAsync(subId);
// OR unsubscribe all subscriptions at the same time
await client.UnsubscribeAllAsync();


Cross Exchange Development

Unfortunately there is no standard for exchanges when it comes to the API's they offer. Altough most of them offer roughly the same functionality each of them is structured differently and expects+ differently named and formatted parameters.

With this in mind all exhange clients implement various ISharedClient interfaces. These interfaces split the functionality from the exchange specifics allowing for a common implementation for multiple exchanges. Which interfaces are implemented for each exchange depends on what functionality the exchange API offers.

The ISharedClient interfaces allow for functionality to be defined once for every exchange instead of (partially) having to implement it for each exchange

// Abstract out the exchange request by using the shared ticker interface
public async Task GetTickerAsync(ISpotTickerRestClient client, SharedSymbol symbol)
{
    var result = await client.GetSpotTickerAsync(new GetTickerRequest(symbol));
    return result.Data;
}

// Abstract out the exchange response by using the shared response model
public bool CheckForTrigger(SharedSpotTicker ticker)
{
    return ticker.HighPrice - ticker.LastPrice > 10;
}


// Requesting the data from different exchanges
var sharedSymbol = new SharedSymbol(TradingMode.Spot, "ETH", "USDT");
var lastBybitTicker = await GetTickerAsync(bybitResClient.V5Api.SharedClient, sharedSymbol);
var lastGateIoTicker = await GetTickerAsync(gateioRestClient.SpotApi.SharedClient, sharedSymbol);
var lastBinanceTicker = await GetTickerAsync(binanceRestClient.SpotApi.SharedClient, sharedSymbol);

// Checking response with different exchange responses
var bybitTriggered = CheckForTrigger(lastBybitTicker);
var gateioTriggered = CheckForTrigger(lastGateIoTicker);
var binanceTriggered = CheckForTrigger(lastBinanceTicker);

See CryptoExchange.Net examples for an example of how to use this.

Shared clients for all exchanges:

// Spot API common functionality rest client
var spotSharedRestClients = binanceRestClient.SpotApi.SharedClient; 

// Spot API common functionality socket client
var spotSharedSocketClient = binanceSocketClient.SpotApi.SharedClient; 

// USD-M Futures API common functionality rest client
var usdFuturesSharedRestClient = binanceRestClient.UsdFuturesApi.SharedClient;

// USD-M Futures API common functionality socket client
var usdFuturesSharedSocketClient = binanceSocketClient.UsdFuturesApi.SharedClient;

// Coin-M Futures API common functionality rest client
var coinFuturesSharedRestClient = binanceRestClient.CoinFuturesApi.SharedClient;

// Coin-M Futures API common functionality socket client
var coinFuturesSharedSocketClient = binanceSocketClient.CoinFuturesApi.SharedClient;
// Spot API common functionality rest client
var spotSharedRestClients = bingXRestClient.SpotApi.SharedClient; 

// Spot API common functionality socket client
var spotSharedSocketClient = bingXSocketClient.SpotApi.SharedClient; 

// Perpetual Futures API common functionality rest client
var usdFuturesSharedRestClient = bingXRestClient.PerpetualFuturesApi.SharedClient;

// Perpetual Futures API common functionality socket client
var usdFuturesSharedSocketClient = bingXSocketClient.PerpetualFuturesApi.SharedClient;
// Spot API common functionality rest client
var spotSharedRestClients = bitfinexRestClient.SpotApi.SharedClient; 

// Spot API common functionality socket client
var spotSharedSocketClient = bitfinexSocketClient.SpotApi.SharedClient; 
// Spot API common functionality rest client
var spotSharedRestClients = bitgetRestClient.SpotApiV2.SharedClient;
 
// Spot API common functionality socket client
var spotSharedSocketClient = bitgetSocketClient.SpotApiV2.SharedClient; 

// Perpetual Futures API common functionality rest client
var usdFuturesSharedRestClient = bitgetRestClient.FuturesApiV2.SharedClient;

// Perpetual Futures API common functionality socket client
var usdFuturesSharedSocketClient = bitgetSocketClient.FuturesApiV2.SharedClient;
// Spot API common functionality rest client
var spotSharedRestClients = bitMartRestClient.SpotApi.SharedClient;

// Spot API common functionality socket client
var spotSharedSocketClient = bitMartSocketClient.SpotApi.SharedClient;

// USD Futures API common functionality rest client
var usdFuturesSharedRestClient = bitMartRestClient.UsdFuturesApi.SharedClient;

// USD Futures API common functionality socket client
var usdFuturesSharedSocketClient = bitMartSocketClient.UsdFuturesApi.SharedClient;
// Spot and Futures API common functionality rest client
var spotSharedRestClients = bybitRestClient.V5Api.SharedClient;

// Spot and Futures API common functionality socket client
var spotSharedSocketClient = bybitSocketClient.V5SpotApi.SharedClient;

// Linear Futures API common functionality socket client
var linearFuturesSharedSocketClient = bybitSocketClient.V5LinearApi.SharedClient;

// Inverse Futures API common functionality socket client
var inverseFuturesspotSharedSocketClient = bybitSocketClient.V5InverseApi.SharedClient;

// Private Futures API common functionality socket client
var privateFuturesSharedSocketClient = bybitSocketClient.V5PrivateApi.SharedClient;
// Advanced Trade API common functionality rest client
var spotSharedRestClients = coinbaseRestClient.AdvancedTradeApi.SharedClient;

// Advanced Trade API common functionality socket client
var spotSharedSocketClient = coinbaseSocketClient.AdvancedTradeApi.SharedClient;
// Spot API common functionality rest client
var spotSharedRestClients = coinExRestClient.SpotApiV2.SharedClient;

// Spot API common functionality socket client
var spotSharedSocketClient = coinExSocketClient.SpotApiV2.SharedClient;

// Futures API common functionality rest client
var usdFuturesSharedRestClient = coinExRestClient.FuturesApi.SharedClient;

// Futures API common functionality socket client
var usdFuturesSharedSocketClient = coinExSocketClient.FuturesApi.SharedClient;
// Spot/Futures API common functionality rest client
var sharedRestClients = cryptoComRestClient.ExchangeApi.SharedClient;

// Spot/Futures API common functionality socket client
var sharedSocketClient = cryptoComSocketClient.ExchangeApi.SharedClient;
// Spot API common functionality rest client
var spotSharedRestClients = gateioRestClient.SpotApi.SharedClient;

// Spot API common functionality socket client
var spotSharedSocketClient = gateioSocketClient.SpotApi.SharedClient;

// Perpetual Futures API common functionality rest client
var perpFuturesSharedRestClient = gateioRestClient.PerpetualFuturesApi.SharedClient;

// Perpetual Futures API common functionality socket client
var perpFuturesSharedSocketClient = gateioSocketClient.PerpetualFuturesApi.SharedClient;
// Spot API common functionality rest client
var spotSharedRestClients = htxRestClient.SpotApi.SharedClient;

// Spot API common functionality socket client
var spotSharedSocketClient = htxSocketClient.SpotApi.SharedClient;

// USDT Futures API common functionality rest client
var usdFuturesSharedRestClient = htxRestClient.UsdtFuturesApi.SharedClient;

// USDT Futures API common functionality socket client
var usdFuturesSharedSocketClient = htxSocketClient.UsdtFuturesApi.SharedClient;
// Spot API common functionality rest client
var spotSharedRestClients = hyperliquidRestClient.SpotApi.SharedClient;

// Spot API common functionality socket client
var spotSharedSocketClient = hyperliquidSocketClient.SpotApi.SharedClient;

// Perpetual Futures API common functionality rest client
var futuresSharedRestClient = hyperliquidRestClient.FuturesApi.SharedClient;

// Perpetual Futures API common functionality socket client
var futuresSharedSocketClient = hyperliquidSocketClient.FuturesApi.SharedClient;
// Spot API common functionality rest client
var spotSharedRestClients = krakenRestClient.SpotApi.SharedClient;

// Spot API common functionality socket client
var spotSharedSocketClient = krakenSocketClient.SpotApi.SharedClient;

// Futures API common functionality rest client
var usdFuturesSharedRestClient = krakenRestClient.FuturesApi.SharedClient;

// Futures API common functionality socket client
var usdFuturesSharedSocketClient = krakenSocketClient.FuturesApi.SharedClient;
// Spot API common functionality rest client
var spotSharedRestClients = kucoinRestClient.SpotApi.SharedClient;

// Spot API common functionality socket client
var spotSharedSocketClient = kucoinSocketClient.SpotApi.SharedClient;

// Futures API common functionality rest client
var usdFuturesSharedRestClient = kucoinRestClient.FuturesApi.SharedClient;

// Futures API common functionality socket client
var usdFuturesSharedSocketClient = kucoinSocketClient.FuturesApi.SharedClient;
// Spot API common functionality rest client
var spotSharedRestClients = mexcRestClient.SpotApi.SharedClient;

// Spot API common functionality socket client
var spotSharedSocketClient = mexcSocketClient.SpotApi.SharedClient;
// Futures and Spot API common functionality rest client
var spotSharedRestClients = okxRestClient.UnifiedApi.SharedClient;

// Futures and Spot API common functionality socket client
var spotSharedSocketClient = okxSocketClient.UnifiedApi.SharedClient;
// Futures and Spot API common functionality rest client
var spotSharedRestClients = whitebitRestClient.V4Api.SharedClient;

// Futures and Spot API common functionality socket client
var spotSharedSocketClient = whitebitSocketClient.V4Api.SharedClient;
// Spot API common functionality rest client
var spotSharedRestClients = xtRestClient.SpotApi.SharedClient;

// Futures API common functionality rest client
var futuresSharedRestClients = xtRestClient.UsdtFuturesApi.SharedClient;

// Spot API common functionality socket client
var spotSharedSocketClient = xtSocketClient.SpotApi.SharedClient;

// Futures API common functionality socket client
var futuresSharedSocketClient = xtSocketClient.FuturesApi.SharedClient;

TradingMode

The shared client interfaces and functionality work with the TradingMode enum. This enums determines what part of the API a request targets. Many exchanges support multiple trading modes, which might be split over different sub-clients:

Console.WriteLine(string.Join(", ", binanceClient.UsdFuturesApi.SharedClient.SupportedTradingModes));
// Output: DeliveryLinear, PerpetualLinear 

Console.WriteLine(string.Join(", ", binanceClient.CoinFuturesApi.SharedClient.SupportedTradingModes));
// Output: DeliveryInverse, PerpetualInverse

Console.WriteLine(string.Join(", ", binanceClient.SpotApi.SharedClient.SupportedTradingModes));
// Output: Spot
ModeDescription
TradingMode.SpotSpot trading
TradingMode.PerpetualLinearFutures contract trading without delivery date using stablecoin as settlement
TradingMode.DeliveryLinearFutures contract trading with specific delivery date using stablecoin as settlement
TradingMode.PerpetualInverseFutures contract trading without delivery date using a crypto asset as settlement
TradingMode.DeliveryInverseFutures contract trading with specific delivery date using a crypto asset as settlement

SharedSymbol

One of the main challenges of working with different exchanges is the different names and formats used for symbols. For example, the ETH/USDT spot trading pair is called ETHUSDT on Binance, ETH-USDT on Kucoin and ETH_USDT on GateIo. With futures trading it's even worse, ranging from ETH-USD-SWAP to ETHU24

To combat this problem the SharedSymbol class was introduced. This class takes a TradingMode, base asset name and quote asset name which are used to format the symbol according to the exchange it will be used by.

// Create a symbol reference for ETH/USDT spot trading
var symbol1 = new SharedSymbol(TradingMode.Spot, "ETH", "USDT");

// Create a symbol reference for BTC/USDC perpetual linear futures trading
var symbol2 = new SharedSymbol(TradingMode.PerpetualLinear, "BTC", "USDC");

// Create a symbol reference for BTC/USD perpetual inverse futures trading
var symbol3 = new SharedSymbol(TradingMode.PerpetualInverse, "BTC", "USD");

// Create a symbol reference for BTC/USDT linear futures trading with delivery on 27 Sept 2024
var symbol4 = new SharedSymbol(TradingMode.DeliveryLinear, "BTC", "USDT", new DateTime(2024, 9, 27));

// Create a symbol reference for ETH/USD inverse futures trading with delivery on 27 Sept 2024
var symbol5 = new SharedSymbol(TradingMode.DeliveryLinear, "ETH", "USD", new DateTime(2024, 9, 27));
		  

With the SharedSymbol functionality available we can reference symbols without having to think about how the exchange expects a symbol.

var symbol = new SharedSymbol(TradingMode.Spot, "ETH", "USDT");
		  
var tickerMexcResult = await mexcSharedRestClients.GetSpotTickerAsync(new GetTickerRequest(symbol));
Console.WriteLine($"{mexcSharedRestClients.Exchange} {tickerMexcResult.Data.Symbol} last price: {tickerMexcResult.Data.LastPrice}");
// Output: Mexc ETHUSDT last price: 2614,67

var tickerKucoinResult = await kucoinSharedRestClients.GetSpotTickerAsync(new GetTickerRequest(symbol));
Console.WriteLine($"{kucoinSharedRestClients.Exchange} {tickerKucoinResult.Data.Symbol} last price: {tickerKucoinResult.Data.LastPrice}");
// Output: Kucoin ETH-USDT last price: 2614,49

Pagination

Some Rest history requests support pagination. These requests take a INextPageToken as parameter and results include a NextPageToken property on the result. These tokens can be used to automatically execute multiple paginated requests

var symbol = new SharedSymbol(TradingMode.PerpetualLinear, "ETH", "USDT");

INextPageToken? nextToken = null;
while (true)
{
    // Execute a request specifying the next page token which was returned from by the previous request (or null for the first request)
    var pageResult = await client.GetClosedFuturesOrdersAsync(new GetClosedOrdersRequest(symbol, limit: 2), nextToken);

    Console.WriteLine($"{pageResult.Data.Count()} items");

    // If NextPagToken is null there is no next page available
    if (pageResult.NextPageToken == null)
        break;

    // Store next page token for the next request
    nextToken = pageResult.NextPageToken;
}

Or simplify this by using the ExchangeHelpers.ExecutePages helper function to stream each page as a result in the form of a IAsyncEnumerable

await foreach (var pageResult in ExchangeHelpers.ExecutePages(client.GetClosedFuturesOrdersAsync, new GetClosedOrdersRequest(symbol, limit: 2)))
    Console.WriteLine($"{pageResult.Data.Count()} items");

Optional and exchange specific parameters

Not all exchanges require the same parameters. Some parameters are defined on the request model, but only required for specific exchanges. For example a listenKey parameter when subscribing to a user stream. When not provided for an exchange that needs it an ArgumentError will be returned.

Other parameters are not defined on the request model at all because they're very specific to a single exchange. For example the AccountId parameter for Spot authenticated requests on HTX. To not pollute the request models with every single possible parameter for each single exchange there is an ExchangeParameters structure to provide these values.

// Set as static parameter, automatically used if not overridden
ExchangeParameters.SetStaticParameter("HTX", "AccountId", 123123123);
var balances1 = await restClient.HTX.SpotApi.SharedClient.GetBalancesAsync(new GetBalancesRequest());

// Specify exchange parameters for a request
var exchangeParameters = new ExchangeParameters(new ExchangeParameter("HTX", "AccountId", 456456456));
var balances = await restClient.HTX.SpotApi.SharedClient.GetBalancesAsync(new GetBalancesRequest(exchangeParameters: exchangeParameters));

To determine which parameters are required for an exchange request either inspect the ArgmumentError when the call fails, or output the request info:

Console.WriteLine(restClient.HTX.SpotApi.SharedClient.GetBalancesOptions.ToString(Exchange.HTX));
// Output:
// HTX GetBalancesRequest
// Needs authentication: True
// Required exchange specific parameters: [Int64] AccountId: Account id of the user | example: 123123123

Available Interfaces

What interfaces are actually available and implemented on each exchange clients depends on what functionality is available in the specific API.

Available REST shared interfaces

InterfaceDescription
IAssetsRestClientFor requesting withdrawal and deposit info for assets
IKlineRestClientFor requesting public kline/candlestick data
IOrderBookRestClientFor requesting public order book data
IRecentTradeRestClientFor requesting the most recent public trades for a symbol
ITradeHistoryRestClientFor requesting historic public trade data for a symbol
ISpotSymbolRestClientFor requesting Spot symbols info
ISpotTickerRestClientFor requesting Spot ticker information
IFundingRateRestClientFor requesting funding rate history for a Futures symbol
IFuturesSymbolRestClientFor requesting Futures symbols info
IFuturesTickerRestClientFor requesting Futures ticker information
IIndexPriceKlineRestClientFor requesting index price kline history for a Futures symbol
IMarkPriceKlineRestClientFor requesting mark price kline history for a Futures symbol
IOpenInterestRestClientFor requesting the open interest for a Futures symbol
IBalanceRestClientFor requesting user asset balances
IDepositRestClientFor requesting user deposit history
IListenKeyRestClientFor managing the user listen key which can be used for subscribing to user data streams
IWithdrawalRestClient For requesting user withdrawal history
IWithdrawRestClientFor requesting to withdraw funds from the exchange
ISpotOrderRestClientFor placing and managing Spot orders
IFuturesOrderRestClientFor placing and managing Futures orders
ILeverageRestClientFor managing leverage for a Futures symbol
IPositionHistoryRestClientFor requesting the user position closing history
IPositionModeRestClientFor managing the position mode for the user
IFeeRestClientFor requesting maker and taker trading fee percentages for the user

Available Socket shared interfaces

InterfaceDescription
IBookTickerSocketClientFor subscribing to book ticker (best bid/ask) updates for a symbol
IKlineSocketClientFor subscribing to kline/candlestick updates for a symbol
IOrderBookSocketClientFor subscribing to order book snapshot updates for a symbol
ITickerSocketClientFor subscribing to ticker updates for a symbol
ITickersSocketClientFor subscribing to ticker updates for all symbols
ITradeSocketClientFor subscribing to public trade updates for a symbol
IBalanceSocketClientFor subscribing to user balance updates
IUserTradeSocketClientFor subscribing to user trade updates
ISpotOrderSocketClientFor subscribing to user Spot order updates
IFuturesOrderSocketClientFor subscribing to user Futures order updates
IPositionSocketClientFor subscribing to user position updates

CryptoClients.Net

The CryptoClients.Net library has some additional tools for dynamically accessing different exchanges. Both the (I)ExchangeRestClient and (I)ExchangeSocketClient expose methods for executing requests and subscriptions on dynamically selected exchanges.

Rest

var restClient = new ExchangeRestClient();
var symbol = new SharedSymbol(TradingMode.Spot, "ETH", "USDT");

// Get ticker results from Binance and Kucoin
var tickers = await restClient.GetSpotTickerAsync(new GetTickerRequest(symbol), [Exchange.Binance, Exchange.Kucoin]);

// Get ticker results for all exchanges
var tickers = await restClient.GetSpotTickerAsync(new GetTickerRequest(symbol));

Socket

var socketClient = new ExchangeSocketClient();
var symbol = new SharedSymbol(TradingMode.Spot, "ETH", "USDT");

// Subscribe to ticker updates on Binance and Kucoin
var tickers = await socketClient.SubscribeToTickerUpdatesAsync(new SubscribeTickerRequest(symbol), update =>
{
    Console.WriteLine($"{update.Exchange} update: {update.Data.LastPrice}");
},
[Exchange.Binance, Exchange.Kucoin]);

// Subscribe to ticker updates on all exchanges
var tickers = await socketClient.SubscribeToTickerUpdatesAsync(new SubscribeTickerRequest(symbol), update =>
{
    Console.WriteLine($"{update.Exchange} update: {update.Data.LastPrice}");
});

Options & Authorization

Options for the clients can be provided in a couple of different ways. If no options are configured the default options will be used. For accessing private endpoints and streams API credentials have to be provided.


Authorization

For private endpoints and data streams the clients will need to know the API credentials of the user accessing the API. API credentials are a way of identifying the user and validating that the user is who he says he is. You can compare it to a username and password login.

API credentials van be provided via the client options, see next section on how to set these options. There are currently 2 variants of API credentials supported, HMAC and RSA.

HMAC
HMAC authentication involves 2 values, the API key and API secret. The combination of the two gives access to the account. HMAC is the default authentication method and can be configured as such:

options.ApiCredentials = new ApiCredentials("YOUR API KEY", "YOUR API SECRET");

RSA
RSA authentication involves generating a private and public key and then uploading the public key to the server. After using the private key to sign the request the server can validate the request by comparing the signature using the public key. Not every exchange supports this authentication method. Depending on the version of dotnet used there are 2 ways of configuring the RSA authentication.

When running Dotnet version 3.0 or later the easiest way is to use the RsaPem type. This allows you to use the Private key directly. When running from an older Dotnet/.NET framework version you're forced to use the RsaXml type due to framework limitations. This means you'll have to convert the private key to XML format before using it.

RsaXml
// when using the .netstandard2.0 compiled version, from .NET framework or Dotnet core 2.2 or lower
// Private key should look something like this: <RSAKeyValue><Modulus>...</RSAKeyValue>
options.ApiCredentials = new ApiCredentials("YOUR PUBLIC KEY", "YOUR PRIVATE KEY", ApiCredentialsType.RsaXml);
RsaPem
// when using the .netstandard2.1 compiled version, from Dotnet core 3.0 or later
// Private key should look something like this: -----BEGIN PRIVATE KEY----- .. -----END PRIVATE KEY-----, or just a long random character string
options.ApiCredentials = new ApiCredentials("YOUR PUBLIC KEY", "YOUR PRIVATE KEY", ApiCredentialsType.RsaPem);

Setting options

Dependency injection

When adding a library to the service collection (see Dependency Injection) the options for the clients can be provided as argument to the calls or read from configuration.

builder.Services.AddCryptoClients(globalOptions => {
    globalOptions.RequestTimeout = TimeSpan.FromSeconds(30);
  },
  // Exchange specific options can be provided as well
  bybitRestOptions: bybitOptions => {
    // Set options specific for the Bybit rest client here
  });
builder.Services.AddBinance(
  options => {
    options.Rest.RequestTimeout = TimeSpan.FromSeconds(30);
    options.Socket.RequestTimeout = TimeSpan.FromSeconds(5);
  });
  
// OR
  
// see https://github.com/JKorf/CryptoExchange.Net/tree/master/Examples/example-config.json for an example configuration
builder.Services.AddBinance(builder.Configuration.GetSection("Binance"));
builder.Services.AddBingX(
  options => {
    options.Rest.RequestTimeout = TimeSpan.FromSeconds(30);
    options.Socket.RequestTimeout = TimeSpan.FromSeconds(5);
  });
  
// OR
  
// see https://github.com/JKorf/CryptoExchange.Net/tree/master/Examples/example-config.json for an example configuration
builder.Services.AddBingX(builder.Configuration.GetSection("BingX"));
builder.Services.AddBitfinex(
  options => {
    options.Rest.RequestTimeout = TimeSpan.FromSeconds(30);
    options.Socket.RequestTimeout = TimeSpan.FromSeconds(5);
  });
  
// OR
  
// see https://github.com/JKorf/CryptoExchange.Net/tree/master/Examples/example-config.json for an example configuration
builder.Services.AddBitfinex(builder.Configuration.GetSection("Bitfinex"));
builder.Services.AddBitget(
  options => {
    options.Rest.RequestTimeout = TimeSpan.FromSeconds(30);
    options.Socket.RequestTimeout = TimeSpan.FromSeconds(5);
  });
  
// OR
  
// see https://github.com/JKorf/CryptoExchange.Net/tree/master/Examples/example-config.json for an example configuration
builder.Services.AddBitget(builder.Configuration.GetSection("Bitget"));
builder.Services.AddBitMart(
  options => {
    options.Rest.RequestTimeout = TimeSpan.FromSeconds(30);
    options.Socket.RequestTimeout = TimeSpan.FromSeconds(5);
  });
  
// OR
  
// see https://github.com/JKorf/CryptoExchange.Net/tree/master/Examples/example-config.json for an example configuration
builder.Services.AddBitMart(builder.Configuration.GetSection("BitMart"));
builder.Services.AddBybit(
  options => {
    options.Rest.RequestTimeout = TimeSpan.FromSeconds(30);
    options.Socket.RequestTimeout = TimeSpan.FromSeconds(5);
  });
  
// OR
  
// see https://github.com/JKorf/CryptoExchange.Net/tree/master/Examples/example-config.json for an example configuration
builder.Services.AddBybit(builder.Configuration.GetSection("Bybit"));
builder.Services.AddCoinbase(
  options => {
    options.Rest.RequestTimeout = TimeSpan.FromSeconds(30);
    options.Socket.RequestTimeout = TimeSpan.FromSeconds(5);
  });
  
// OR
  
// see https://github.com/JKorf/CryptoExchange.Net/tree/master/Examples/example-config.json for an example configuration
builder.Services.AddCoinbase(builder.Configuration.GetSection("Coinbase"));
builder.Services.AddCoinGecko(
  options => {
    options.RequestTimeout = TimeSpan.FromSeconds(30);
  });
  
// OR
  
// see https://github.com/JKorf/CryptoExchange.Net/tree/master/Examples/example-config.json for an example configuration
builder.Services.AddCoinGecko(builder.Configuration.GetSection("CoinGecko"));
builder.Services.AddCoinEx(
  options => {
    options.Rest.RequestTimeout = TimeSpan.FromSeconds(30);
    options.Socket.RequestTimeout = TimeSpan.FromSeconds(5);
  });
  
// OR
  
// see https://github.com/JKorf/CryptoExchange.Net/tree/master/Examples/example-config.json for an example configuration
builder.Services.AddCoinEx(builder.Configuration.GetSection("CoinEx"));
builder.Services.AddCryptoCom(
  options => {
    options.Rest.RequestTimeout = TimeSpan.FromSeconds(30);
    options.Socket.RequestTimeout = TimeSpan.FromSeconds(5);
  });
  
// OR
  
// see https://github.com/JKorf/CryptoExchange.Net/tree/master/Examples/example-config.json for an example configuration
builder.Services.AddCryptoCom(builder.Configuration.GetSection("CryptoCom"));
builder.Services.AddGateIo(
  options => {
    options.Rest.RequestTimeout = TimeSpan.FromSeconds(30);
    options.Socket.RequestTimeout = TimeSpan.FromSeconds(5);
  });
  
// OR
  
// see https://github.com/JKorf/CryptoExchange.Net/tree/master/Examples/example-config.json for an example configuration
builder.Services.AddGateIo(builder.Configuration.GetSection("GateIo"));
builder.Services.AddHTX(
  options => {
    options.Rest.RequestTimeout = TimeSpan.FromSeconds(30);
    options.Socket.RequestTimeout = TimeSpan.FromSeconds(5);
  });
  
// OR
  
// see https://github.com/JKorf/CryptoExchange.Net/tree/master/Examples/example-config.json for an example configuration
builder.Services.AddHTX(builder.Configuration.GetSection("HTX"));
builder.Services.AddHyperLiquid(
  options => {
    options.Rest.RequestTimeout = TimeSpan.FromSeconds(30);
    options.Socket.RequestTimeout = TimeSpan.FromSeconds(5);
  });
  
// OR
  
// see https://github.com/JKorf/CryptoExchange.Net/tree/master/Examples/example-config.json for an example configuration
builder.Services.AddHyperLiquid(builder.Configuration.GetSection("HyperLiquid"));
builder.Services.AddKraken(
  options => {
    options.Rest.RequestTimeout = TimeSpan.FromSeconds(30);
    options.Socket.RequestTimeout = TimeSpan.FromSeconds(5);
  });
  
// OR
  
// see https://github.com/JKorf/CryptoExchange.Net/tree/master/Examples/example-config.json for an example configuration
builder.Services.AddKraken(builder.Configuration.GetSection("Kraken"));
builder.Services.AddKucoin(
  options => {
    options.Rest.RequestTimeout = TimeSpan.FromSeconds(30);
    options.Socket.RequestTimeout = TimeSpan.FromSeconds(5);
  });
  
// OR
  
// see https://github.com/JKorf/CryptoExchange.Net/tree/master/Examples/example-config.json for an example configuration
builder.Services.AddKucoin(builder.Configuration.GetSection("Kucoin"));
builder.Services.AddMexc(
  options => {
    options.Rest.RequestTimeout = TimeSpan.FromSeconds(30);
    options.Socket.RequestTimeout = TimeSpan.FromSeconds(5);
  });
  
// OR
  
// see https://github.com/JKorf/CryptoExchange.Net/tree/master/Examples/example-config.json for an example configuration
builder.Services.AddMexc(builder.Configuration.GetSection("Mexc"));
builder.Services.AddOKX(
  options => {
    options.Rest.RequestTimeout = TimeSpan.FromSeconds(30);
    options.Socket.RequestTimeout = TimeSpan.FromSeconds(5);
  });
  
// OR
  
// see https://github.com/JKorf/CryptoExchange.Net/tree/master/Examples/example-config.json for an example configuration
builder.Services.AddOKX(builder.Configuration.GetSection("OKX"));
builder.Services.AddWhiteBit(
  options => {
    options.Rest.RequestTimeout = TimeSpan.FromSeconds(30);
    options.Socket.RequestTimeout = TimeSpan.FromSeconds(5);
  });
  
// OR
  
// see https://github.com/JKorf/CryptoExchange.Net/tree/master/Examples/example-config.json for an example configuration
builder.Services.AddWhiteBit(builder.Configuration.GetSection("WhiteBit"));
builder.Services.AddXT(
  options => {
    options.Rest.RequestTimeout = TimeSpan.FromSeconds(30);
    options.Socket.RequestTimeout = TimeSpan.FromSeconds(5);
  });
  
// OR
  
// see https://github.com/JKorf/CryptoExchange.Net/tree/master/Examples/example-config.json for an example configuration
builder.Services.AddXT(builder.Configuration.GetSection("XT"));
Client constructor

When creating a client via the constructor options can be provided as parameters

var exchangeRestClient = new ExchangeRestClient(opts =>
{
    opts.RequestTimeout = TimeSpan.FromSeconds(30);
});
var binanceRestClient = new BinanceRestClient(opts =>
{
    opts.RequestTimeout = TimeSpan.FromSeconds(30);
});
var client = new BingXRestClient(opts =>
{
    opts.RequestTimeout = TimeSpan.FromSeconds(30);
});
var client = new BitfinexRestClient(opts =>
{
    opts.RequestTimeout = TimeSpan.FromSeconds(30);
});
var client = new BitgetRestClient(opts =>
{
    opts.RequestTimeout = TimeSpan.FromSeconds(30);
});
var client = new BitMartRestClient(opts =>
{
    opts.RequestTimeout = TimeSpan.FromSeconds(30);
});
var client = new BybitRestClient(opts =>
{
    opts.RequestTimeout = TimeSpan.FromSeconds(30);
});
var client = new CoinbaseRestClient(opts =>
{
    opts.RequestTimeout = TimeSpan.FromSeconds(30);
});
var client = new CoinGeckoRestClient(opts =>
{
    opts.RequestTimeout = TimeSpan.FromSeconds(30);
});
var client = new CoinExRestClient(opts =>
{
    opts.RequestTimeout = TimeSpan.FromSeconds(30);
});
var client = new CryptoComRestClient(opts =>
{
    opts.RequestTimeout = TimeSpan.FromSeconds(30);
});
var client = new GateIoRestClient(opts =>
{
    opts.RequestTimeout = TimeSpan.FromSeconds(30);
});
var client = new HTXRestClient(opts =>
{
    opts.RequestTimeout = TimeSpan.FromSeconds(30);
});
var client = new HyperLiquidRestClient(opts =>
{
    opts.RequestTimeout = TimeSpan.FromSeconds(30);
});
var client = new KrakenRestClient(opts =>
{
    opts.RequestTimeout = TimeSpan.FromSeconds(30);
});
var client = new KucoinRestClient(opts =>
{
    opts.RequestTimeout = TimeSpan.FromSeconds(30);
});
var client = new MexcRestClient(opts =>
{
    opts.RequestTimeout = TimeSpan.FromSeconds(30);
});
var client = new OKXRestClient(opts =>
{
    opts.RequestTimeout = TimeSpan.FromSeconds(30);
});
var client = new WhiteBitRestClient(opts =>
{
    opts.RequestTimeout = TimeSpan.FromSeconds(30);
});
var client = new XTRestClient(opts =>
{
    opts.RequestTimeout = TimeSpan.FromSeconds(30);
});
SetDefaultOptions

The options can be defined using the static SetDefaultOptions method on the client BEFORE creating the client. Any client created after this call will use the specified options

BinanceRestClient.SetDefaultOptions(options =>
{
    options.RequestTimeout = TimeSpan.FromSeconds(30);
});
var client = new BinanceRestClient();
BingXRestClient.SetDefaultOptions(options =>
{
    options.RequestTimeout = TimeSpan.FromSeconds(30);
});
var client = new BingXRestClient();
BitfinexRestClient.SetDefaultOptions(options =>
{
    options.RequestTimeout = TimeSpan.FromSeconds(30);
});
var client = new BitfinexRestClient();
BitgetRestClient.SetDefaultOptions(options =>
{
    options.RequestTimeout = TimeSpan.FromSeconds(30);
});
var client = new BitgetRestClient();
BitMartRestClient.SetDefaultOptions(options =>
{
    options.RequestTimeout = TimeSpan.FromSeconds(30);
});
var client = new BitMartRestClient();
BybitRestClient.SetDefaultOptions(options =>
{
    options.RequestTimeout = TimeSpan.FromSeconds(30);
});
var client = new BybitRestClient();
CoinbaseClient.SetDefaultOptions(options =>
{
    options.RequestTimeout = TimeSpan.FromSeconds(30);
});
var client = new CoinbaseRestClient();
CoinGeckoRestClient.SetDefaultOptions(options =>
{
    options.RequestTimeout = TimeSpan.FromSeconds(30);
});
var client = new CoinGeckoRestClient();
CoinExRestClient.SetDefaultOptions(options =>
{
    options.RequestTimeout = TimeSpan.FromSeconds(30);
});
var client = new CoinExRestClient();
CryptoComRestClient.SetDefaultOptions(options =>
{
    options.RequestTimeout = TimeSpan.FromSeconds(30);
});
var client = new CryptoComRestClient();
GateIoRestClient.SetDefaultOptions(options =>
{
    options.RequestTimeout = TimeSpan.FromSeconds(30);
});
var client = new GateIoRestClient();
HTXRestClient.SetDefaultOptions(options =>
{
    options.RequestTimeout = TimeSpan.FromSeconds(30);
});
var client = new HTXRestClient();
HyperLiquidRestClient.SetDefaultOptions(options =>
{
    options.RequestTimeout = TimeSpan.FromSeconds(30);
});
var client = new HyperLiquidRestClient();
KrakenRestClient.SetDefaultOptions(options =>
{
    options.RequestTimeout = TimeSpan.FromSeconds(30);
});
var client = new KrakenRestClient();
KucoinRestClient.SetDefaultOptions(options =>
{
    options.RequestTimeout = TimeSpan.FromSeconds(30);
});
var client = new KucoinRestClient();
MexcRestClient.SetDefaultOptions(options =>
{
    options.RequestTimeout = TimeSpan.FromSeconds(30);
});
var client = new MexcRestClient();
OKXRestClient.SetDefaultOptions(options =>
{
    options.RequestTimeout = TimeSpan.FromSeconds(30);
});
var client = new OKXRestClient();
WhiteBitRestClient.SetDefaultOptions(options =>
{
    options.RequestTimeout = TimeSpan.FromSeconds(30);
});
var client = new WhiteBitRestClient();
XTRestClient.SetDefaultOptions(options =>
{
    options.RequestTimeout = TimeSpan.FromSeconds(30);
});
var client = new XTRestClient();

Option definitions

General options
Options available for all clients

OptionDescriptionDefault value
RequestTimeout The time to wait for an answer from the server on a request TimeSpan.FromSeconds(20)
ApiCredentials The credentials to use for private endpoints and streams. See Authorization for more info. For CryptoClients this option allows the setting of ApiCredentials for all exchanges. null
Proxy The proxy to use for connecting to the API null
OutputOriginalData When enabled the originally received string data will be available as well as the deserialized object. For REST API client calls the data will be in the WebCallResult.OriginalData property, for Websocket API client subscriptions the data will be available in the DataEvent.OriginalData property when receiving an update. false
RateLimiterEnabled Whether or not client side rate limiting should be applied. Note that not all libraries have ratelimiting implemented, if it's not implemented this flag does nothing true
RateLimitingBehaviour What should happen when a rate limit is reached. RateLimitingBehaviour.Wait: the request waits until it can be send while staying within the limits, RateLimitingBehaviour.Fail: the request will return an error RateLimitingBehaviour.Wait
Environment The environment the library should connect to. Some exchanges have testnet/sandbox environments which can be used instead of the real exchange. The environment option can be used to switch between different trade environments Live environment

REST client options
Options available for REST clients

OptionDescriptionDefault value
AutoTimestamp Whether or not the client should attempt to sync the time between the client and server. If the time between server and client is not in sync authentication errors might occur. This option should be disabled when the client time sure is to be in sync true
TimestampRecalculationInterval The interval of how often the time synchronization between client and server should be executed TimeSpan.FromHours(1)
CachingEnabled Whether or not client side caching should be enabled for GET requests, see Caching false
CachingMaxAge The max age of data to return from the cache. If the same data is requested and the data is available in the client side cache and not older than this value the cached value is returned, else a new request will be done TimeSpan.FromSeconds(5)
[API].ApiCredentials Same as the in the base options, allows overriding per sub-API null
[API].OutputOriginalData Same as the in the base options, allows overriding per sub-API null
[API].AutoTimestamp Same as the in the base REST options, allows overriding per sub-API null
[API].TimestampRecalculationInterval Same as the in the base REST options, allows overriding per sub-API null

Websocket client options
Options available for websocket clients

OptionDescriptionDefault value
ReconnectPolicy The reconnect policy to execute when the connection is lost ReconnectPolicy.FixedDelay
ReconnectInterval The time to wait between connection tries when reconnecting, only applied when reconnect policy is FixedDelay TimeSpan.FromSeconds(5)
SocketNoDataTimeout If no data is received during this timespan the connection is assumed to be dropped. This is mainly used for API's which have some sort of ping/keepalive system. For example; the Bitfinex API will sent a heartbeat message every 15 seconds, so the `SocketNoDataTimeout` could be set to 20 seconds. On API's without such a mechanism this might not work because there just might not be any update while still being fully connected default(TimeSpan)
SocketSubscriptionsCombineTarget The number of subscriptions that should be made on a single socket connection before setting up a new connection. Not all exchanges support multiple subscriptions on a single socket and some have limits on the amount of connections. Setting this to a higher number increases subscription speed because not every subscription needs to connect to the server, but having more subscriptions on a single connection will also increase the amount of traffic on that single connection, potentially leading to delays in updates if the data isn't handled quickly enough Dependent on the library
MaxConcurrentResubscriptionsPerSocket The maximum number of concurrent resubscriptions per socket when resubscribing after reconnecting 5
MaxSocketConnections The maximum number of distinct socket connections null
DelayAfterConnect The time to wait before sending messages after connecting to the server null
[API].SocketNoDataTimeout Same as the in the base websocket client options, allows overriding per sub-API null
[API].MaxSocketConnections Same as the in the base websocket client options, allows overriding per sub-API null


Additional Features


Orderbooks

Each client library provides an local orderbook implementation. These implementations will provide a client side orderbook, take care of synchronization with the server, and handle reconnecting and resynchronizing in case of a dropped connection. Orderbook implementations use the following naming convention: [ExchangeName][(Type)]SymbolOrderBook

Creation and starting
The order book implementations can be created directly, or can be instantiated via the I[Exchange]OrderBookFactory factory. After creation the synchronization can be started by calling the StartAync method

// Assuming IExchangeOrderBookFactory is injected as bookFactoryClient
var book = bookFactoryClient.Binance.Spot.Create("ETH", "USDT");
var startResult = await book.StartAsync();
if (!startResult.Success)
{
    // Handle error, error info available in startResult.Error
}
// Book has successfully started and synchronized

// Once no longer needed you can stop the live sync functionality by calling StopAsync()
await book.StopAsync();
var book = new BinanceSpotSymbolOrderBook("ETHUSDT");
var startResult = await book.StartAsync();
if (!startResult.Success)
{
  // Handle error, error info available in startResult.Error
}
// Book has successfully started and synchronized

// Once no longer needed you can stop the live sync functionality by calling StopAsync()
await book.StopAsync();
var book = new BingXSpotSymbolOrderBook("ETH-USDT");
var startResult = await book.StartAsync();
if (!startResult.Success)
{
  // Handle error, error info available in startResult.Error
}
// Book has successfully started and synchronized

// Once no longer needed you can stop the live sync functionality by calling StopAsync()
await book.StopAsync();
var book = new BitfinexSymbolOrderBook("tETHUSD");
var startResult = await book.StartAsync();
if (!startResult.Success)
{
  // Handle error, error info available in startResult.Error
}
// Book has successfully started and synchronized

// Once no longer needed you can stop the live sync functionality by calling StopAsync()
await book.StopAsync();
var book = new BitgetSpotSymbolOrderBook("ETHUSDT");
var startResult = await book.StartAsync();
if (!startResult.Success)
{
  // Handle error, error info available in startResult.Error
}
// Book has successfully started and synchronized

// Once no longer needed you can stop the live sync functionality by calling StopAsync()
await book.StopAsync();
var book = new BitMartSymbolOrderBook("ETH_USDT", Category.Spot);
var startResult = await book.StartAsync();
if (!startResult.Success)
{
  // Handle error, error info available in startResult.Error
}
// Book has successfully started and synchronized

// Once no longer needed you can stop the live sync functionality by calling StopAsync()
await book.StopAsync();
var book = new BybitSymbolOrderBook("ETHUSDT", Category.Spot);
var startResult = await book.StartAsync();
if (!startResult.Success)
{
  // Handle error, error info available in startResult.Error
}
// Book has successfully started and synchronized

// Once no longer needed you can stop the live sync functionality by calling StopAsync()
await book.StopAsync();
var book = new CoinbaseSymbolOrderBook("ETH-USDT");
var startResult = await book.StartAsync();
if (!startResult.Success)
{
  // Handle error, error info available in startResult.Error
}
// Book has successfully started and synchronized

// Once no longer needed you can stop the live sync functionality by calling StopAsync()
await book.StopAsync();
var book = new CoinExSpotSymbolOrderBook("ETHUSDT");
var startResult = await book.StartAsync();
if (!startResult.Success)
{
  // Handle error, error info available in startResult.Error
}
// Book has successfully started and synchronized

// Once no longer needed you can stop the live sync functionality by calling StopAsync()
await book.StopAsync();
var book = new GateIoSpotSymbolOrderBook("ETH_USDT");
var startResult = await book.StartAsync();
if (!startResult.Success)
{
  // Handle error, error info available in startResult.Error
}
// Book has successfully started and synchronized

// Once no longer needed you can stop the live sync functionality by calling StopAsync()
await book.StopAsync();
var book = new CryptoComSpotSymbolOrderBook("ETH_USDT");
var startResult = await book.StartAsync();
if (!startResult.Success)
{
  // Handle error, error info available in startResult.Error
}
// Book has successfully started and synchronized

// Once no longer needed you can stop the live sync functionality by calling StopAsync()
await book.StopAsync();
var book = new HTXSpotSymbolOrderBook("ethusdt");
var startResult = await book.StartAsync();
if (!startResult.Success)
{
  // Handle error, error info available in startResult.Error
}
// Book has successfully started and synchronized

// Once no longer needed you can stop the live sync functionality by calling StopAsync()
await book.StopAsync();
var book = new HyperLiquidSymbolOrderBook("HYPE/USDC");
var startResult = await book.StartAsync();
if (!startResult.Success)
{
  // Handle error, error info available in startResult.Error
}
// Book has successfully started and synchronized

// Once no longer needed you can stop the live sync functionality by calling StopAsync()
await book.StopAsync();
var book = new KrakenSpotSymbolOrderBook("ETH/USD");
var startResult = await book.StartAsync();
if (!startResult.Success)
{
  // Handle error, error info available in startResult.Error
}
// Book has successfully started and synchronized

// Once no longer needed you can stop the live sync functionality by calling StopAsync()
await book.StopAsync();
var book = new KucoinSpotSymbolOrderBook("ETH-USDT");
var startResult = await book.StartAsync();
if (!startResult.Success)
{
  // Handle error, error info available in startResult.Error
}
// Book has successfully started and synchronized

// Once no longer needed you can stop the live sync functionality by calling StopAsync()
await book.StopAsync();
var book = new MexcSpotSymbolOrderBook("ETHUSDT");
var startResult = await book.StartAsync();
if (!startResult.Success)
{
  // Handle error, error info available in startResult.Error
}
// Book has successfully started and synchronized

// Once no longer needed you can stop the live sync functionality by calling StopAsync()
await book.StopAsync();
var book = new OKXSymbolOrderBook("ETH-USDT");
var startResult = await book.StartAsync();
if (!startResult.Success)
{
  // Handle error, error info available in startResult.Error
}
// Book has successfully started and synchronized

// Once no longer needed you can stop the live sync functionality by calling StopAsync()
await book.StopAsync();
var book = new WhiteBitSymbolOrderBook("ETH_USDT");
var startResult = await book.StartAsync();
if (!startResult.Success)
{
  // Handle error, error info available in startResult.Error
}
// Book has successfully started and synchronized

// Once no longer needed you can stop the live sync functionality by calling StopAsync()
await book.StopAsync();
var book = new XTSymbolOrderBook("eth_usdt");
var startResult = await book.StartAsync();
if (!startResult.Success)
{
  // Handle error, error info available in startResult.Error
}
// Book has successfully started and synchronized

// Once no longer needed you can stop the live sync functionality by calling StopAsync()
await book.StopAsync();

The order book object
The order book implementations for each client use a common base class and interface, which means you can use books from different exchanges in a common function.

// Create order books for different exchanges
var books = new List<ISymbolOrderBook>();
books.Add(new BinanceSpotSymbolOrderBook("ETHUSDT"));
books.Add(new BitgetSpotSymbolOrderBook("ETHUSDT"));
books.Add(new KrakenSpotSymbolOrderBook("ETH/USD"));
books.Add(new KucoinSpotSymbolOrderBook("ETH-USDT"));
books.Add(new OKXSymbolOrderBook("ETH-USDT"));

// Start the books
var results = await Task.WhenAll(books.Select(b => b.StartAsync()));

// Output the current best ask/bid for each exchange
foreach (var book in books.Where(b => b.Status == OrderBookStatus.Synced))
{
	Console.WriteLine(book.Id);
	Console.WriteLine($"{book.BestAsk} - {book.BestBid}");
}

The following properties and events are exposed by the order books:

FieldDescription
Status The current status of the order book. Note that the book is only acurate and up to date when the status is Synced
Id Identifier for the book, referencing the exchange it's for
Symbol The symbol the book is for
LastSequenceNumber The last sequence number that was processed. Order book update messages typically have sequence numbers to correctly sync the book
UpdateTime Timestamp of the last update that was processed
AskCount The current number of asks in the book
BidCount The current number of bids in the book
Asks Returns a snapshot of the current list of asks. This creates a copy of the list values at that moment
Bids Returns a snapshot of the current list of bids. This creates a copy of the list values at that moment
Book Returns a snapshot of the both the asks and bids. This makes sure the snapshot of the bids and asks are of the same exact time
BestAsk The best ask at that moment
BestBid The best bid at that moment
BestOffers The best bid and best ask at that moment
OnStatusChange Event called when the status of the order book changes
OnOrderBookUpdate Event called whenever the book changes. Note that this event can trigger a lot on large/high liquidity markets
OnBestOffersChanged Event called whenever the best ask or bid changes in the order book


Trackers

Trackers offer a way to keep track of live data. This data can than be aggregated into statistics and different time slices can be compared to get realtime insights.

The basic workings of the trackers are simple, an initial request is made for a snapshot of the history (or a partial snapshot depending on what the API supports). At the same time a websocket subscription is set up to provide the tracker with new data.

Currently there are 2 different trackers available, the TradeTracker and the KlineTracker.

Creation and starting
The example uses the TradeTracker, but the same logic can be applied to the KlineTracker.

var symbol = new SharedSymbol(TradingMode.Spot, "ETH", "USDT");

// Assuming IExchangeTrackerFactory is injected as trackerFactory
// Create tracker dynamically by exchange name
var tracker = trackerFactory.CreateTradeTracker("Binance", symbol);
// OR by directly referencing the specific exchange
tracker = trackerFactory.Binance.CreateTradeTracker(symbol);

var startResult = await tracker.StartAsync();
if (!startResult.Success)
{
    // Handle error, error info available in startResult.Error
}
// Tracker has successfully started
// Note that it might not be fully synced yet, check tracker.Status for this.

// Once no longer needed you can stop the live sync functionality by calling StopAsync()
await tracker.StopAsync();
// Either create a new factory or inject the IBinanceTrackerFactory interface
var factory = new BinanceTrackerFactory();

var symbol = new SharedSymbol(TradingMode.Spot, "ETH", "USDT");

// Create a tracker for ETH/USDT keeping track of trades in the last 5 minutes
var tracker = factory.CreateTradeTracker(symbol, period: TimeSpan.FromMinutes(5));
var startResult = await tracker.StartAsync();
if (!startResult.Success)
{
  // Handle error, error info available in startResult.Error
}
// Tracker has successfully started
// Note that it might not be fully synced yet, check tracker.Status for this.

// Once no longer needed you can stop the live sync functionality by calling StopAsync()
await tracker.StopAsync();
// Either create a new factory or inject the IBingXTrackerFactory interface
var factory = new BingXTrackerFactory();

var symbol = new SharedSymbol(TradingMode.Spot, "ETH", "USDT");

// Create a tracker for ETH/USDT keeping track of trades in the last 5 minutes
var tracker = factory.CreateTradeTracker(symbol, period: TimeSpan.FromMinutes(5));
var startResult = await tracker.StartAsync();
if (!startResult.Success)
{
  // Handle error, error info available in startResult.Error
}
// Tracker has successfully started
// Note that it might not be fully synced yet, check tracker.Status for this.

// Once no longer needed you can stop the live sync functionality by calling StopAsync()
await tracker.StopAsync();
// Either create a new factory or inject the IBitfinexTrackerFactory interface
var factory = new BitfinexTrackerFactory();

var symbol = new SharedSymbol(TradingMode.Spot, "ETH", "USDT");

// Create a tracker for ETH/USDT keeping track of trades in the last 5 minutes
var tracker = factory.CreateTradeTracker(symbol, period: TimeSpan.FromMinutes(5));
var startResult = await tracker.StartAsync();
if (!startResult.Success)
{
  // Handle error, error info available in startResult.Error
}
// Tracker has successfully started
// Note that it might not be fully synced yet, check tracker.Status for this.

// Once no longer needed you can stop the live sync functionality by calling StopAsync()
await tracker.StopAsync();
// Either create a new factory or inject the IBitgetTrackerFactory interface
var factory = new BitgetTrackerFactory();

var symbol = new SharedSymbol(TradingMode.Spot, "ETH", "USDT");

// Create a tracker for ETH/USDT keeping track of trades in the last 5 minutes
var tracker = factory.CreateTradeTracker(symbol, period: TimeSpan.FromMinutes(5));
var startResult = await tracker.StartAsync();
if (!startResult.Success)
{
  // Handle error, error info available in startResult.Error
}
// Tracker has successfully started
// Note that it might not be fully synced yet, check tracker.Status for this.

// Once no longer needed you can stop the live sync functionality by calling StopAsync()
await tracker.StopAsync();
// Either create a new factory or inject the IBitMartTrackerFactory interface
var factory = new BitMartTrackerFactory();

var symbol = new SharedSymbol(TradingMode.Spot, "ETH", "USDT");

// Create a tracker for ETH/USDT keeping track of trades in the last 5 minutes
var tracker = factory.CreateTradeTracker(symbol, period: TimeSpan.FromMinutes(5));
var startResult = await tracker.StartAsync();
if (!startResult.Success)
{
  // Handle error, error info available in startResult.Error
}
// Tracker has successfully started
// Note that it might not be fully synced yet, check tracker.Status for this.

// Once no longer needed you can stop the live sync functionality by calling StopAsync()
await tracker.StopAsync();
// Either create a new factory or inject the IBybitTrackerFactory interface
var factory = new BybitTrackerFactory();

var symbol = new SharedSymbol(TradingMode.Spot, "ETH", "USDT");

// Create a tracker for ETH/USDT keeping track of trades in the last 5 minutes
var tracker = factory.CreateTradeTracker(symbol, period: TimeSpan.FromMinutes(5));
var startResult = await tracker.StartAsync();
if (!startResult.Success)
{
  // Handle error, error info available in startResult.Error
}
// Tracker has successfully started
// Note that it might not be fully synced yet, check tracker.Status for this.

// Once no longer needed you can stop the live sync functionality by calling StopAsync()
await tracker.StopAsync();
// Either create a new factory or inject the ICoinbaseTrackerFactory interface
var factory = new CoinbaseTrackerFactory();

var symbol = new SharedSymbol(TradingMode.Spot, "ETH", "USDT");

// Create a tracker for ETH/USDT keeping track of trades in the last 5 minutes
var tracker = factory.CreateTradeTracker(symbol, period: TimeSpan.FromMinutes(5));
var startResult = await tracker.StartAsync();
if (!startResult.Success)
{
  // Handle error, error info available in startResult.Error
}
// Tracker has successfully started
// Note that it might not be fully synced yet, check tracker.Status for this.

// Once no longer needed you can stop the live sync functionality by calling StopAsync()
await tracker.StopAsync();
// Either create a new factory or inject the ICoinExTrackerFactory interface
var factory = new CoinExTrackerFactory();

var symbol = new SharedSymbol(TradingMode.Spot, "ETH", "USDT");

// Create a tracker for ETH/USDT keeping track of trades in the last 5 minutes
var tracker = factory.CreateTradeTracker(symbol, period: TimeSpan.FromMinutes(5));
var startResult = await tracker.StartAsync();
if (!startResult.Success)
{
  // Handle error, error info available in startResult.Error
}
// Tracker has successfully started
// Note that it might not be fully synced yet, check tracker.Status for this.

// Once no longer needed you can stop the live sync functionality by calling StopAsync()
await tracker.StopAsync();
// Either create a new factory or inject the IGateIoTrackerFactory interface
var factory = new GateIoTrackerFactory();

var symbol = new SharedSymbol(TradingMode.Spot, "ETH", "USDT");

// Create a tracker for ETH/USDT keeping track of trades in the last 5 minutes
var tracker = factory.CreateTradeTracker(symbol, period: TimeSpan.FromMinutes(5));
var startResult = await tracker.StartAsync();
if (!startResult.Success)
{
  // Handle error, error info available in startResult.Error
}
// Tracker has successfully started
// Note that it might not be fully synced yet, check tracker.Status for this.

// Once no longer needed you can stop the live sync functionality by calling StopAsync()
await tracker.StopAsync();
// Either create a new factory or inject the ICryptoComTrackerFactory interface
var factory = new CryptoComTrackerFactory();

var symbol = new SharedSymbol(TradingMode.Spot, "ETH", "USDT");

// Create a tracker for ETH/USDT keeping track of trades in the last 5 minutes
var tracker = factory.CreateTradeTracker(symbol, period: TimeSpan.FromMinutes(5));
var startResult = await tracker.StartAsync();
if (!startResult.Success)
{
  // Handle error, error info available in startResult.Error
}
// Tracker has successfully started
// Note that it might not be fully synced yet, check tracker.Status for this.

// Once no longer needed you can stop the live sync functionality by calling StopAsync()
await tracker.StopAsync();
// Either create a new factory or inject the IHTXTrackerFactory interface
var factory = new HTXTrackerFactory();

var symbol = new SharedSymbol(TradingMode.Spot, "ETH", "USDT");

// Create a tracker for ETH/USDT keeping track of trades in the last 5 minutes
var tracker = factory.CreateTradeTracker(symbol, period: TimeSpan.FromMinutes(5));
var startResult = await tracker.StartAsync();
if (!startResult.Success)
{
  // Handle error, error info available in startResult.Error
}
// Tracker has successfully started
// Note that it might not be fully synced yet, check tracker.Status for this.

// Once no longer needed you can stop the live sync functionality by calling StopAsync()
await tracker.StopAsync();
// Either create a new factory or inject the IHyperLiquidTrackerFactory interface
var factory = new HyperLiquidTrackerFactory();

var symbol = new SharedSymbol(TradingMode.Spot, "HYPE", "USDC");

// Create a tracker for HYPE/USDC keeping track of trades in the last 5 minutes
var tracker = factory.CreateTradeTracker(symbol, period: TimeSpan.FromMinutes(5));
var startResult = await tracker.StartAsync();
if (!startResult.Success)
{
  // Handle error, error info available in startResult.Error
}
// Tracker has successfully started
// Note that it might not be fully synced yet, check tracker.Status for this.

// Once no longer needed you can stop the live sync functionality by calling StopAsync()
await tracker.StopAsync();
// Either create a new factory or inject the IKrakenTrackerFactory interface
var factory = new KrakenTrackerFactory();

var symbol = new SharedSymbol(TradingMode.Spot, "ETH", "USD");

// Create a tracker for ETH/USD keeping track of trades in the last 5 minutes
var tracker = factory.CreateTradeTracker(symbol, period: TimeSpan.FromMinutes(5));
var startResult = await tracker.StartAsync();
if (!startResult.Success)
{
  // Handle error, error info available in startResult.Error
}
// Tracker has successfully started
// Note that it might not be fully synced yet, check tracker.Status for this.

// Once no longer needed you can stop the live sync functionality by calling StopAsync()
await tracker.StopAsync();
// Either create a new factory or inject the IKucoinTrackerFactory interface
var factory = new KucoinTrackerFactory();

var symbol = new SharedSymbol(TradingMode.Spot, "ETH", "USDT");

// Create a tracker for ETH/USDT keeping track of trades in the last 5 minutes
var tracker = factory.CreateTradeTracker(symbol, period: TimeSpan.FromMinutes(5));
var startResult = await tracker.StartAsync();
if (!startResult.Success)
{
  // Handle error, error info available in startResult.Error
}
// Tracker has successfully started
// Note that it might not be fully synced yet, check tracker.Status for this.

// Once no longer needed you can stop the live sync functionality by calling StopAsync()
await tracker.StopAsync();
// Either create a new factory or inject the IMexcTrackerFactory interface
var factory = new MexcTrackerFactory();

var symbol = new SharedSymbol(TradingMode.Spot, "ETH", "USDT");

// Create a tracker for ETH/USDT keeping track of trades in the last 5 minutes
var tracker = factory.CreateTradeTracker(symbol, period: TimeSpan.FromMinutes(5));
var startResult = await tracker.StartAsync();
if (!startResult.Success)
{
  // Handle error, error info available in startResult.Error
}
// Tracker has successfully started
// Note that it might not be fully synced yet, check tracker.Status for this.

// Once no longer needed you can stop the live sync functionality by calling StopAsync()
await tracker.StopAsync();
// Either create a new factory or inject the IOKXTrackerFactory interface
var factory = new OKXTrackerFactory();

var symbol = new SharedSymbol(TradingMode.Spot, "ETH", "USDT");

// Create a tracker for ETH/USDT keeping track of trades in the last 5 minutes
var tracker = factory.CreateTradeTracker(symbol, period: TimeSpan.FromMinutes(5));
var startResult = await tracker.StartAsync();
if (!startResult.Success)
{
  // Handle error, error info available in startResult.Error
}
// Tracker has successfully started
// Note that it might not be fully synced yet, check tracker.Status for this.

// Once no longer needed you can stop the live sync functionality by calling StopAsync()
await tracker.StopAsync();
// Either create a new factory or inject the IWhiteBitTrackerFactory interface
var factory = new WhiteBitTrackerFactory();

var symbol = new SharedSymbol(TradingMode.Spot, "ETH", "USDT");

// Create a tracker for ETH/USDT keeping track of trades in the last 5 minutes
var tracker = factory.CreateTradeTracker(symbol, period: TimeSpan.FromMinutes(5));
var startResult = await tracker.StartAsync();
if (!startResult.Success)
{
  // Handle error, error info available in startResult.Error
}
// Tracker has successfully started
// Note that it might not be fully synced yet, check tracker.Status for this.

// Once no longer needed you can stop the live sync functionality by calling StopAsync()
await tracker.StopAsync();
// Either create a new factory or inject the IXTTrackerFactory interface
var factory = new XTTrackerFactory();

var symbol = new SharedSymbol(TradingMode.Spot, "ETH", "USDT");

// Create a tracker for ETH/USDT keeping track of trades in the last 5 minutes
var tracker = factory.CreateTradeTracker(symbol, period: TimeSpan.FromMinutes(5));
var startResult = await tracker.StartAsync();
if (!startResult.Success)
{
  // Handle error, error info available in startResult.Error
}
// Tracker has successfully started
// Note that it might not be fully synced yet, check tracker.Status for this.

// Once no longer needed you can stop the live sync functionality by calling StopAsync()
await tracker.StopAsync();

Stats and comparing data
Using the tracker.GetData(fromTime, toTime) method the trackers exposes the data, or a subset of the data, can be retrieved:

// Get all the data currently tracked:
var data = tracker.GetData();

// Get the data for the last minute:
var data = tracker.GetData(DateTime.UtcNow.AddMinutes(-1));

// Get the data for the second last minute:
var data = tracker.GetData(DateTime.UtcNow.AddMinutes(-2), DateTime.UtcNow.AddMinutes(-1));

In a similar way statistics about the full data, or a subset of it, can be retrieved using the tracker.GetStats(fromTime, toTime) method:

// Get statistics on all the data:
var stats = tracker.GetStats();

// Get statistics for the last minute:
var stats = tracker.GetStats(DateTime.UtcNow.AddMinutes(-1));

// Get statistics for the second last minute:
var stats = tracker.GetStats(DateTime.UtcNow.AddMinutes(-2), DateTime.UtcNow.AddMinutes(-1));
See below for an overview of what these stats include.

Stats can also be compared to eachother to see how much values have changed using the stats.CompareTo(otherStats) method:

var statsSecondLastMinute = tracker.GetStats(compareTime.AddMinutes(-2), compareTime.AddMinutes(-1));
var statsLastMinute = tracker.GetStats(compareTime.AddMinutes(-1), compareTime);
var comparison = statsLastMinute.CompareTo(statsSecondLastMinute);

Console.WriteLine($"The volume of last minute compared to the minute before that: {comparison.VolumeDif?.Difference} ({comparison.VolumeDif?.PercentageDifference}%)");
// Output: The volume of last minute compared to the minute before that: -1261,57350000 (-85,2572%)

The TradeTracker object

The following properties and events are exposed by the TradeTracker object:

FieldDescription
Count The total number of trades currently tracked
Exchange The name of the exchange
SymbolName The name of the symbol being tracked
Symbol The symbol as passed in the constructor
Limit The max number of results tracked
Period The max age of results tracked
SyncedFrom The timestamp from which on the trades are registered
Status The current synchronization status. Note the PartiallySynced means that the connection is active, but the data set is not yet complete. For example if the tracker is set to track 5 minutes of trades, it could be that currently only 3 minutes of data is tracked and it will take 2 more minutes to be fully synced.
Last The current last trade
OnAdded Event for when a new trade is added
OnRemoved Event for when a trade is removed from the tracker due to not being within the set tracking period/limit anymore
OnStatusChanged Event for when the status of the tracker changes

The following properties are available in the TradesStats object:

FieldDescription
TradeCount The number trades in this data set
FirstTradeTime The timestamp of the first trade in this data set
LastTradeTime The timestamp of the last trade in this data set
AveragePrice The average price of the trades in this dataset
VolumeWeightedAveragePrice The volume weighted average price for trades in this dataset
Volume The total volume of all trades in this set
QuoteVolume The total volume of all trades in this set denoted in the quote asset
BuySellRatio The buy sell ratio of all trades in this data set. The factor of how much of the trades were a buy.
Complete Whether the data set is complete. A set is not complete when not all data is available. For example, when only 1 hour of data is available in the tracker but stats are requested for the last 2 hours.

The KlineTracker object

The following properties and events are exposed by the KlineTracker object:

FieldDescription
Count The total number of klines currently tracked
Exchange The name of the exchange
SymbolName The name of the symbol being tracked
Symbol The symbol as passed in the constructor
Limit The max number of results tracked
Period The max age of results tracked
SyncedFrom The timestamp from which on the klines are registered
Status The current synchronization status. Note the PartiallySynced means that the connection is active, but the data set is not yet complete. For example if the tracker is set to track 10 hours of klines, it could be that currently only 9 hours of data is tracked and it will take another hour to be fully synced.
Last The current last kline
OnAdded Event for when a new kline is added
OnRemoved Event for when a kline is removed from the tracker due to not being within the set tracking period/limit anymore
OnStatusChanged Event for when the status of the tracker changes

The following properties are available on the KlinesStats object:

FieldDescription
KlineCount The number klines in this data set
FirstOpenTime The open time of the first kline in this data set
LastOpenTime The open time of the last kline in this data set
LowPrice The lowest price for all klines in this set
HighPrice The highest price for all kline in this set
Volume The total volume of all klines in this set
AverageVolume The average volume per kline for all klines in this set
Complete Whether the data set is complete. A set is not complete when not all data is available. For example, when only 1 hour of data is available in the tracker but stats are requested for the last 2 hours.


Logging

The library provides extensive logging, which depends on the dotnet `Microsoft.Extensions.Logging.ILogger` interface. This should provide ease of use when connecting the library logging to your existing logging implementation.

When using the Dotnet dependency injection the logging configuration will de determined by the ILogger configuration.

External log providers
External logging libraries can be configured as expected. For example Serilog:

using Binance.Net;
using Serilog;

Log.Logger = new LoggerConfiguration()
        .MinimumLevel.Debug()
        .WriteTo.Console()
        .CreateLogger();

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddBinance();
builder.Host.UseSerilog();
var app = builder.Build();

// startup

app.Run();

Logging without dependency injection
A LoggerFactory instance can be provided to the client to configure the logging

var logFactory = new LoggerFactory();
logFactory.AddProvider(new ConsoleLoggerProvider());
var binanceClient = new BinanceRestClient(new HttpClient(), logFactory, options => { });


Ratelimiting

The client libraries have build in support for rate limiting. Rate limiting in this case means that requests are throttled (or failed before sending based on configuration) when the client detects a server rate limit will be exceeded. Whether or not rate limiting is applied can be configured in the DI registration or client options. Not all libraries currently have rate limiting configured.

What to do when a limit is reached can be configured with the RateLimitingBehaviour client options, either Fail for returning an error or Wait to wait until the request can safely be send.

Client side rate limiting can only correctly work if there is only a single program talking to the exchange. When multiple different application send requests at the same time it's impossible for the client side to keep track of the rate limits. When using multiple concurrent applications it is advised to turn off rate limiting. Also note that when requests are rate limited with RateLimitBehaviour.Wait that the order of the requests being send is not guarenteed.

Client side rate limiting is currently implemented for the following libraries:

services.AddCryptoClients(x =>
    x.RatelimiterEnabled = true;
    x.RateLimitingBehaviour = RateLimitingBehaviour.Wait;
});

To be notified of when a rate limit is hit the static Exchanges.RateLimiter exposes an event which triggers when a rate limit is reached

BinanceExchange.RateLimiter.RateLimitTriggered += (rateLimitEvent) => Console.WriteLine("Limit triggered: " + rateLimitEvent);

// Output: Limit triggered: RateLimitEvent { ApiLimit = Spot Socket, LimitDescription = Limit of 6000 per 00:01:00, RequestDefinition = GET 1, Host = wss://ws-api.binance.com, Current = 5752, RequestWeight = 250, Limit = 6000, TimePeriod = 00:01:00, DelayTime = 00:00:38.7784145, Behaviour = Wait }
services.AddBinance(x =>
    x.RatelimiterEnabled = true;
    x.RateLimitingBehaviour = RateLimitingBehaviour.Wait;
}, x =>
{
    x.RatelimiterEnabled = true;
    x.RateLimitingBehaviour = RateLimitingBehaviour.Wait;
});

To be notified of when a rate limit is hit the static BinanceExchange.RateLimiter exposes an event which triggers when a rate limit is reached

BinanceExchange.RateLimiter.RateLimitTriggered += (rateLimitEvent) => Console.WriteLine("Limit triggered: " + rateLimitEvent);

// Output: Limit triggered: RateLimitEvent { ApiLimit = Spot Socket, LimitDescription = Limit of 6000 per 00:01:00, RequestDefinition = GET 1, Host = wss://ws-api.binance.com, Current = 5752, RequestWeight = 250, Limit = 6000, TimePeriod = 00:01:00, DelayTime = 00:00:38.7784145, Behaviour = Wait }
services.AddBingX(x =>
    x.RatelimiterEnabled = true;
    x.RateLimitingBehaviour = RateLimitingBehaviour.Wait;
}, x =>
{
    x.RatelimiterEnabled = true;
    x.RateLimitingBehaviour = RateLimitingBehaviour.Wait;
});

To be notified of when a rate limit is hit the static BingXExchange.RateLimiter exposes an event which triggers when a rate limit is reached

BingXExchange.RateLimiter.RateLimitTriggered += (rateLimitEvent) => Console.WriteLine("Limit triggered: " + rateLimitEvent);

services.AddBitget(x =>
    x.RatelimiterEnabled = true;
    x.RateLimitingBehaviour = RateLimitingBehaviour.Wait;
}, x =>
{
    x.RatelimiterEnabled = true;
    x.RateLimitingBehaviour = RateLimitingBehaviour.Wait;
});

To be notified of when a rate limit is hit the static BitgetExchange.RateLimiter exposes an event which triggers when a rate limit is reached

BitgetExchange.RateLimiter.RateLimitTriggered += (rateLimitEvent) => Console.WriteLine("Limit triggered: " + rateLimitEvent);

services.AddBitMart(x =>
    x.RatelimiterEnabled = true;
    x.RateLimitingBehaviour = RateLimitingBehaviour.Wait;
}, x =>
{
    x.RatelimiterEnabled = true;
    x.RateLimitingBehaviour = RateLimitingBehaviour.Wait;
});

To be notified of when a rate limit is hit the static BitMartExchange.RateLimiter exposes an event which triggers when a rate limit is reached

BitMartExchange.RateLimiter.RateLimitTriggered += (rateLimitEvent) => Console.WriteLine("Limit triggered: " + rateLimitEvent);

services.AddCoinbase(x =>
    x.RatelimiterEnabled = true;
    x.RateLimitingBehaviour = RateLimitingBehaviour.Wait;
}, x =>
{
    x.RatelimiterEnabled = true;
    x.RateLimitingBehaviour = RateLimitingBehaviour.Wait;
});

To be notified of when a rate limit is hit the static CoinbaseExchange.RateLimiter exposes an event which triggers when a rate limit is reached

CoinbaseExchange.RateLimiter.RateLimitTriggered += (rateLimitEvent) => Console.WriteLine("Limit triggered: " + rateLimitEvent);

services.AddCryptoCom(x =>
    x.RatelimiterEnabled = true;
    x.RateLimitingBehaviour = RateLimitingBehaviour.Wait;
}, x =>
{
    x.RatelimiterEnabled = true;
    x.RateLimitingBehaviour = RateLimitingBehaviour.Wait;
});

To be notified of when a rate limit is hit the static CryptoComExchange.RateLimiter exposes an event which triggers when a rate limit is reached

CryptoComExchange.RateLimiter.RateLimitTriggered += (rateLimitEvent) => Console.WriteLine("Limit triggered: " + rateLimitEvent);

services.AddGateIo(x =>
    x.RatelimiterEnabled = true;
    x.RateLimitingBehaviour = RateLimitingBehaviour.Wait;
}, x =>
{
    x.RatelimiterEnabled = true;
    x.RateLimitingBehaviour = RateLimitingBehaviour.Wait;
});

To be notified of when a rate limit is hit the static GateIoExchange.RateLimiter exposes an event which triggers when a rate limit is reached

GateIoExchange.RateLimiter.RateLimitTriggered += (rateLimitEvent) => Console.WriteLine("Limit triggered: " + rateLimitEvent);

services.AddHTX(x =>
    x.RatelimiterEnabled = true;
    x.RateLimitingBehaviour = RateLimitingBehaviour.Wait;
}, x =>
{
    x.RatelimiterEnabled = true;
    x.RateLimitingBehaviour = RateLimitingBehaviour.Wait;
});

To be notified of when a rate limit is hit the static HTXExchange.RateLimiter exposes an event which triggers when a rate limit is reached

HTXExchange.RateLimiter.RateLimitTriggered += (rateLimitEvent) => Console.WriteLine("Limit triggered: " + rateLimitEvent);

services.AddHyperLiquid(x =>
    x.RatelimiterEnabled = true;
    x.RateLimitingBehaviour = RateLimitingBehaviour.Wait;
}, x =>
{
    x.RatelimiterEnabled = true;
    x.RateLimitingBehaviour = RateLimitingBehaviour.Wait;
});

To be notified of when a rate limit is hit the static HyperLiquidExchange.RateLimiter exposes an event which triggers when a rate limit is reached

HyperLiquidExchange.RateLimiter.RateLimitTriggered += (rateLimitEvent) => Console.WriteLine("Limit triggered: " + rateLimitEvent);

services.AddKraken(x =>
    x.RatelimiterEnabled = true;
    x.RateLimitingBehaviour = RateLimitingBehaviour.Wait;
}, x =>
{
    x.RatelimiterEnabled = true;
    x.RateLimitingBehaviour = RateLimitingBehaviour.Wait;
});

To be notified of when a rate limit is hit the static KrakenExchange.RateLimiter exposes an event which triggers when a rate limit is reached

KrakenExchange.RateLimiter.RateLimitTriggered += (rateLimitEvent) => Console.WriteLine("Limit triggered: " + rateLimitEvent);

// Output: Limit triggered: RateLimitEvent { ApiLimit = Spot Rest, LimitDescription = Limit of 15 with a decay rate of 0,33, RequestDefinition = POST 0/private/TradesHistory authenticated, Host = api.kraken.com, Current = 14, RequestWeight = 2, Limit = 15, TimePeriod = 00:00:01, DelayTime = 00:00:04, Behaviour = Wait }

Kraken applies different rate limits based on the account verification tier. By default the rate limit is set to the most conservative Starter tier. To change the rate limit tier call the Configure method

KrakenExchange.RateLimiter.Configure(Kraken.Net.Enums.RateLimitTier.Pro);
services.AddKucoin(x =>
    x.RatelimiterEnabled = true;
    x.RateLimitingBehaviour = RateLimitingBehaviour.Wait;
}, x =>
{
    x.RatelimiterEnabled = true;
    x.RateLimitingBehaviour = RateLimitingBehaviour.Wait;
});

To be notified of when a rate limit is hit the static KucoinExchange.RateLimiter exposes an event which triggers when a rate limit is reached

KucoinExchange.RateLimiter.RateLimitTriggered += (rateLimitEvent) => Console.WriteLine("Limit triggered: " + rateLimitEvent);

// Output: Limit triggered: RateLimitEvent { ApiLimit = Public Rest, LimitDescription = Limit of 2000 per 00:00:30, RequestDefinition = GET api/v1/market/stats, Host = https://api.kucoin.com/, Current = 1995, RequestWeight = 15, Limit = 2000, TimePeriod = 00:00:30, DelayTime = 00:00:19.8111238, Behaviour = Wait }

Kucoin applies different rate limits based on the account VIP level. By default the rate limit is set to the most conservative VIP0 tier. To change the rate limit tier call the Configure method

KucoinExchange.RateLimiter.Configure(Kucoin.Net.Enums.VipLevel.Vip5);
services.AddMexc(x =>
    x.RatelimiterEnabled = true;
    x.RateLimitingBehaviour = RateLimitingBehaviour.Wait;
}, x =>
{
    x.RatelimiterEnabled = true;
    x.RateLimitingBehaviour = RateLimitingBehaviour.Wait;
});

To be notified of when a rate limit is hit the static MexcExchange.RateLimiter exposes an event which triggers when a rate limit is reached

MexcExchange.RateLimiter.RateLimitTriggered += (rateLimitEvent) => Console.WriteLine("Limit triggered: " + rateLimitEvent);

services.AddOKX(x =>
    x.RatelimiterEnabled = true;
    x.RateLimitingBehaviour = RateLimitingBehaviour.Wait;
}, x =>
{
    x.RatelimiterEnabled = true;
    x.RateLimitingBehaviour = RateLimitingBehaviour.Wait;
});

To be notified of when a rate limit is hit the static OKXExchange.RateLimiter exposes an event which triggers when a rate limit is reached

OKXExchange.RateLimiter.RateLimitTriggered += (rateLimitEvent) => Console.WriteLine("Limit triggered: " + rateLimitEvent);

services.AddWhiteBit(x =>
    x.RatelimiterEnabled = true;
    x.RateLimitingBehaviour = RateLimitingBehaviour.Wait;
}, x =>
{
    x.RatelimiterEnabled = true;
    x.RateLimitingBehaviour = RateLimitingBehaviour.Wait;
});

To be notified of when a rate limit is hit the static WhiteBitExchange.RateLimiter exposes an event which triggers when a rate limit is reached

WhiteBitExchange.RateLimiter.RateLimitTriggered += (rateLimitEvent) => Console.WriteLine("Limit triggered: " + rateLimitEvent);

services.AddXT(x =>
    x.RatelimiterEnabled = true;
    x.RateLimitingBehaviour = RateLimitingBehaviour.Wait;
}, x =>
{
    x.RatelimiterEnabled = true;
    x.RateLimitingBehaviour = RateLimitingBehaviour.Wait;
});

To be notified of when a rate limit is hit the static XTExchange.RateLimiter exposes an event which triggers when a rate limit is reached

XTExchange.RateLimiter.RateLimitTriggered += (rateLimitEvent) => Console.WriteLine("Limit triggered: " + rateLimitEvent);

Caching

Every REST API client based on the CryptoExchange.Net base library automatically supports caching of GET HTTP requests. A few advantages of caching:

  1. Performance improvement, data response will be much faster as no roundtrip to the server is needed
  2. Reduced resource usage, returning data from the cache uses less resources than reading the server response, though there is some memory overhead
  3. Prevent rate limiting, the cache can be queried as many times as you like without having to worry about getting rate limited by the server
Caching is only applied for successful GET requests as GET requests by definition should not change state. Other HTTP methods (POST, DELETE, etc) generally do change state, so caching those call would prevent an action being executed.

To enable caching for GET requests set CachingEnabled to true in the client options. Optionally set the CachingMaxAge option to the desired value (default is 5 seconds).

To determine whether a request has gotten the data from the server or from the local cache the DataSource property on the call result can inspected:

var result = await bitfinexRestClient.SpotApi.Account.Get30DaySummaryAndFeesAsync();
var responseSource = result.DataSource;


Examples

See also the Examples folder in the source

Get Symbols

Get a list of supported symbols on the exchange and information about the symbols

// This example uses Binance, but can be any supported exchange. For example Bybit, Kraken, Kucoin etc

// Directly reference the Exchange API:
await exchangeRestClient.Binance.SpotApi.ExchangeData.GetExchangeInfoAsync();

// Or make it fully dynamic, either request on a single exchange:
await exchangeRestClient.GetSpotSymbolClient("Binance")!.GetSpotSymbolsAsync(new GetSymbolsRequest());
// Or request multiple exchanges at the same time:
await exchangeRestClient.GetSpotSymbolsAsync(new GetSymbolsRequest(), ["Binance", "Kraken", "OKX"]);
await binanceClient.SpotApi.ExchangeData.GetExchangeInfoAsync();
await bingXClient.SpotApi.ExchangeData.GetSymbolsAsync();
await bitfinexClient.SpotApi.ExchangeData.GetSymbolsAsync();
await bitgetClient.SpotApiV2.ExchangeData.GetSymbolsAsync();
await bitMartClient.SpotApi.ExchangeData.GetSymbolsAsync();
await bybitClient.V5Api.ExchangeData.GetSpotSymbolsAsync();
await coinbaseClient.AdvancedTradeApi.ExchangeData.GetSymbolsAsync();
await coinExClient.SpotApiV2.ExchangeData.GetSymbolsAsync();
await cryptoComClient.ExchangeApi.ExchangeData.GetSymbolsAsync();
await gateIoClient.SpotApi.ExchangeData.GetSymbolsAsync();
await htxClient.SpotApi.ExchangeData.GetSymbolsAsync();
await hyperLiquidClient.SpotApi.ExchangeData.GetExchangeInfoAsync();
await krakenClient.SpotApi.ExchangeData.GetSymbolsAsync();
await kucoinClient.SpotApi.ExchangeData.GetSymbolsAsync();
await mexcClient.SpotApi.ExchangeData.GetExchangeInfoAsync();
await okxClient.UnifiedApi.ExchangeData.GetSymbolsAsync(OKXInstrumentType.Spot);
await whitebitClient.V4Api.ExchangeData.GetSymbolsAsync();
await xtClient.SpotApi.ExchangeData.GetSymbolsAsync();
Getting Ticker

Get ticker/price statistics for a specific asset pair

// This example uses Binance, but can be any supported exchange. For example Bybit, Kraken, Kucoin etc

// Directly reference the Exchange API:
await exchangeRestClient.Binance.SpotApi.ExchangeData.GetTickerAsync("ETHUSDT");

// Or make it fully dynamic, either request on a single exchange:
var symbol = new SharedSymbol(TradingMode.Spot, "ETH", "USDT");
await exchangeRestClient.GetSpotTickerClient("Binance")!.GetSpotTickerAsync(new GetTickerRequest(symbol));
// Or request multiple exchanges at the same time:
await exchangeRestClient.GetSpotTickerAsync(new GetTickerRequest(symbol), ["Binance", "Kraken", "OKX"]);
await binanceClient.SpotApi.ExchangeData.GetTickerAsync("BTCUSDT");
await bingXClient.SpotApi.ExchangeData.GetTickersAsync("BTC-USDT");
await bitfinexClient.SpotApi.ExchangeData.GetTickerAsync("tBTCUST");
await bitgetClient.SpotApiV2.ExchangeData.GetTickersAsync("BTCUSDT");
await bitMartClient.SpotApi.ExchangeData.GetTickerAsync("BTC_USDT");
await bybitClient.V5Api.ExchangeData.GetSpotTickersAsync("BTCUSDT");
// Symbol endpoint and ticker endpoints are combined for Coinbase
await coinbaseClient.AdvancedTradeApi.ExchangeData.GetSymbolAsync("BTC-USDT");
await coinExClient.SpotApiV2.ExchangeData.GetTickersAsync(new[] { "BTCUSDT" });
await cryptoComClient.ExchangeApi.ExchangeData.GetTickersAsync("BTC_USDT");
await gateioClient.SpotApi.ExchangeData.GetTickersAsync("BTC_USDT");
await htxClient.SpotApi.ExchangeData.GetTickerAsync("BTCUSDT");
// HyperLiquid API doesn't offer a symbol filter, so we have to filter client side
var tickersResult = await hyperLiquidClient.SpotApi.ExchangeData.GetExchangeInfoAndTickersAsync();
var ticker = tickersResult.Data.Tickers.Single(x => x.Symbol == "HYPE/USDC");
await krakenClient.SpotApi.ExchangeData.GetTickerAsync("BTCUSDT");
await kucoinClient.SpotApi.ExchangeData.GetTickerAsync("BTC-USDT");
await mexcClient.SpotApi.ExchangeData.GetTickerAsync("BTCUSDT");
await okxClient.UnifiedApi.ExchangeData.GetTickerAsync("BTC-USDT");
// WhiteBit API doesn't offer a symbol filter, so we have to filter client side
var tickersResult = await whitebitClient.V4Api.ExchangeData.GetTickersAsync();
var ticker = tickersResult.Data.Single(x => x.Symbol == "BTC_USDT");
await xtClient.SpotApi.ExchangeData.GetTickersAsync("btc-usdt");
Get Balances

Get balance information. Requires API credentials to be set in the client options

// This example uses Binance, but can be any supported exchange. For example Bybit, Kraken, Kucoin etc

// Directly reference the Exchange API:
await exchangeRestClient.Binance.SpotApi.Account.GetBalancesAsync();

// Or make it fully dynamic, either request on a single exchange:
await exchangeRestClient.GetBalancesClient(TradingMode.Spot, "Binance")!.GetBalancesAsync(new GetBalancesRequest());
// Or request multiple exchanges at the same time:
await exchangeRestClient.GetBalancesAsync(new GetBalancesRequest(TradingMode.Spot), ["Binance", "Kraken", "OKX"]);
await binanceClient.SpotApi.Account.GetBalancesAsync();
await bingXClient.SpotApi.Account.GetBalancesAsync();
await bitfinexClient.SpotApi.Account.GetBalancesAsync();
await bitgetClient.SpotApiV2.Account.GetSpotBalancesAsync();
await bitMartClient.SpotApi.Account.GetSpotBalancesAsync();
await bybitClient.V5Api.Account.GetBalancesAsync(AccountType.Spot);
await coinbaseClient.AdvancedTradeApi.Account.GetAccountsAsync();
await coinExClient.SpotApiV2.Account.GetBalancesAsync();
await cryptoComClient.ExchangeApi.Account.GetBalancesAsync();
await gateioClient.SpotApi.Account.GetBalancesAsync();
// Need an account id, you probably want to already have done this before placing the order
var accounts = await htxClient.SpotApi.Account.GetAccountsAsync();
var account = accounts.Data.Single(a => a.Type == AccountType.Spot);

var result = await htxClient.SpotApi.Account.GetBalancesAsync();
await hyperLiquidClient.SpotApi.Account.GetBalancesAsync();
await krakenClient.SpotApi.Account.GetBalancesAsync();
await kucoinClient.SpotApi.Account.GetAccountsAsync();
await mexcClient.SpotApi.Account.GetAccountInfoAsync();
await okxClient.UnifiedApi.Account.GetAccountBalanceAsync();
await whitebitClient.V4Api.Account.GetSpotBalancesAsync();
await xtClient.SpotApi.Account.GetBalancesAsync();
Placing Order

Place a limit buy order for 0.1 BTC at a price of 50.000 USDT. Requires API credentials to be set in the client options

// This example uses Binance, but can be any supported exchange. For example Bybit, Kraken, Kucoin etc

// Directly reference the Exchange API:
await exchangeRestClient.Binance.SpotApi.Trading.PlaceOrderAsync("BTCUSDT", Binance.Net.Enums.OrderSide.Buy, Binance.Net.Enums.SpotOrderType.Limit, 0.1m, price: 50000, timeInForce: Binance.Net.Enums.TimeInForce.GoodTillCanceled);

// Or make it fully dynamic:
await exchangeRestClient.GetSpotOrderClient("Binance")!.PlaceSpotOrderAsync(new PlaceSpotOrderRequest(new SharedSymbol(TradingMode.Spot, "ETH", "USDT"), SharedOrderSide.Buy, SharedOrderType.Limit, 0.1m, price: 50000));
await binanceClient.SpotApi.Trading.PlaceOrderAsync("BTCUSDT", OrderSide.Buy, SpotOrderType.Limit, 0.1m, price: 50000, timeInForce: TimeInForce.GoodTillCanceled);
await bingXClient.SpotApi.Trading.PlaceOrderAsync("BTC-USDT", OrderSide.Buy, OrderType.Limit, 0.1m, price: 50000);
await bitfinexClient.SpotApi.Trading.PlaceOrderAsync("tBTCUST", OrderSide.Buy, OrderType.Limit, 0.1m, 50000);
await bitgetRestClient.SpotApiV2.Trading.PlaceOrderAsync("BTCUSDT_SPBL", OrderSide.Buy, OrderType.Limit, 0.1m, timeInForce: TimeInForce.GoodTillCanceled, price: 50000);
await bitMartClient.SpotApi.Trading.PlaceOrderAsync("BTC_USDT", OrderSide.Buy, OrderType.Limit, 0.1m, price: 50000);
await bybitClient.V5Api.Trading.PlaceOrderAsync(Category.Spot, "BTCUSDT", OrderSide.Buy, NewOrderType.Limit, 0.1m, price: 50000);
await coinbaseClient.AdvancedTradeApi.Trading.PlaceOrderAsync("BTC-USDT", OrderSide.Buy, NewOrderType.Limit, 0.1m, price: 50000);
await coinExClient.SpotApiV2.Trading.PlaceOrderAsync("BTCUSDT", AccountType.Spot, OrderSide.Buy, OrderTypeV2.Limit, 0.1m, 50000);
await cryptoComClient.ExchangeApi.Trading.PlaceOrderAsync("BTC_USDT", OrderSide.Buy, OrderType.Limit, 0.1m, price: 50000, timeInForce: TimeInForce.GoodTillCancel);
await gateIoClient.SpotApi.Trading.PlaceOrderAsync("BTC_USDT", OrderSide.Buy, NewOrderType.Limit, 0.1m, 50000, timeInForce: TimeInForce.GoodTillCancel);
// Need an account id, you probably want to already have done this before placing the order
var accounts = await htxClient.SpotApi.Account.GetAccountsAsync();
var account = accounts.Data.Single(a => a.Type == AccountType.Spot);

var result = await htxClient.SpotApi.Trading.PlaceOrderAsync(account.Id, "BTCUSDT", OrderSide.Buy, OrderType.Limit, 0.1m, price: 50000);
// BTC not support on HyperLiquid Spot trading, example uses HYPE/USDC Pair
await hyperLiquidClient.SpotApi.Trading.PlaceOrderAsync("HYPE/USDC",OrderSide.Buy, OrderType.Limit, 1m, 20);
await krakenClient.SpotApi.Trading.PlaceOrderAsync("BTCUSDT",OrderSide.Buy, OrderType.Limit, 0.1m, 50000);
await kucoinClient.SpotApi.Trading.PlaceOrderAsync("BTC-USDT", OrderSide.Buy, NewOrderType.Limit, 0.1m, 50000);
await mexcClient.SpotApi.Trading.PlaceOrderAsync("BTCUSDT", Mexc.Net.Enums.OrderSide.Buy, Mexc.Net.Enums.OrderType.Limit, 0.1m, price: 50000);
await okxClient.UnifiedApi.Trading.PlaceOrderAsync("BTC-USDT", OKXOrderSide.Buy, OKXOrderType.LimitOrder, 0.1m, 50000);
await whitebitClient.V4Api.Trading.PlaceSpotOrderAsync("BTC_USDT", OrderSide.Buy, NewOrderType.Limit, 0.1m, price: 50000);
await xtClient.SpotApi.Trading.PlaceOrderAsync("eth_usdt", OrderSide.Buy, OrderType.Limit, TimeInForce.GoodTillCanceled, BusinessType.Spot, 0.1m, price: 50000);
Subscribe Ticker Updates

Subscribe to the websocket ticker update stream

// This example uses Binance, but can be any supported exchange. For example Bybit, Kraken, Kucoin etc

// Directly reference the Exchange API:
await exchangeSocketClient.Binance.SpotApi.ExchangeData.SubscribeToTickerUpdatesAsync("ETHUSDT", data => {
    // Handle update
});

// Or make it fully dynamic, either subscribe on a single exchange:
var symbol = new SharedSymbol(TradingMode.Spot, "ETH", "USDT");
await exchangeSocketClient.GetTickerClient(TradingMode.Spot, "Binance")!.SubscribeToTickerUpdatesAsync(new SubscribeTickerRequest(symbol), data => {
    // Handle update
});
// Or subscribe on multiple exchanges at the same time:
await exchangeSocketClient.SubscribeToTickerUpdatesAsync(new SubscribeTickerRequest(symbol), data => {
    // Handle update
}, ["Binance", "Kraken", "OKX"]);
await binanceSocketClient.SpotApi.ExchangeData.SubscribeToTickerUpdatesAsync("ETHUSDT", data => {
    // Handle update
});
await bingXSocketClient.SpotApi.SubscribeToTickerUpdatesAsync("ETH-USDT", data => {
    // Handle update
});
await bitfinexSocketClient.SpotApi.SubscribeToTickerUpdatesAsync("tETHUST", data => {
    // Handle update
});
await bitgetSocketClient.SpotApiV2.SubscribeToTickerUpdatesAsync("ETHUSDT", data => {
    // Handle update
});
await bitmartSocketClient.SpotApi.SubscribeToTickerUpdatesAsync("ETH_USDT", data => {
    // Handle update
});
await bybitSocketClient.V5SpotApi.SubscribeToTickerUpdatesAsync("ETHUSDT", data => {
    // Handle update
});
await coinbaseSocketClient.AdvancedTradeApi.SubscribeToTickerUpdatesAsync("ETH-USDT", data => {
    // Handle update
});
await coinExSocketClient.SpotApiV2.SubscribeToTickerUpdatesAsync(new[] { "ETHUSDT" }, data => {
    // Handle update
});
await cryptoComSocketClient.ExchangeApi.SubscribeToTickerUpdatesAsync("ETH_USDT", data => {
    // Handle update
});
await gateioSocketClient.SpotApi.SubscribeToTickerUpdatesAsync("ETH_USDT", data => {
    // Handle update
});
await hyperLiquidSocketClient.SpotApi.SubscribeToSymbolUpdatesAsync("HYPE/USDC", data => {
    // Handle update
});
await htxSocketClient.SpotApi.SubscribeToTickerUpdatesAsync("ethusdt", data => {
    // Handle update
});
await krakenSocketClient.SpotApi.SubscribeToTickerUpdatesAsync("ETHUSD", data => {
    // Handle update
});
await kucoinSocketClient.SpotApi.SubscribeToTickerUpdatesAsync("ETH-USDT", data => {
    // Handle update
});
await mexcSocketClient.SpotApi.SubscribeToMiniTickerUpdatesAsync("ETHUSDT", data => {
    // Handle update
});
await okxSocketClient.UnifiedApi.ExchangeData.SubscribeToTickerUpdatesAsync("ETHUSDT", data => {
    // Handle update
});
await whitebitSocketClient.V4Api.SubscribeToTickerUpdatesAsync("ETH_USDT", data => {
    // Handle update
});
await xtSocketClient.SpotApi.SubscribeToTickerUpdatesAsync("eth_usdt", data => {
    // Handle update
});
Subscribe Order Updates

Subscribe to the websocket authenticated user order update stream

// This example uses Binance, but can be any exchange support. For example Bybit, Kraken, Kucoin etc
// Binance requires a listenkey to start the user stream

//Directly reference the Exchange API:
var listenKey = await exchangeRestClient.Binance.SpotApi.Account.StartUserStreamAsync();
await exchangeSocketClient.Binance.SpotApi.Account.SubscribeToUserDataUpdatesAsync(listenKey.Data, data =>
{
    // Handle update
}, null, null, null);


// Or make it fully dynamic:
string? listenKey = null;
var listenkeyClient = exchangeRestClient.GetListenKeyClient(TradingMode.Spot, "Binance");
if (listenkeyClient != null)
{
    // Exchange needs a listenkey; so request that
    var listenKeyResult = await listenkeyClient.StartListenKeyAsync(new StartListenKeyRequest());
    listenKey = listenKeyResult.Data;
}
await exchangeSocketClient.GetSpotOrderClient("Binance")!.SubscribeToSpotOrderUpdatesAsync(new SubscribeSpotOrderRequest(listenKey: listenKey), data =>
{
    // Handle update
});
// Retrieve the listen key
var listenKey = await binanceClient.SpotApi.Account.StartUserStreamAsync();

// Subscribe using the key
await binanceSocketClient.SpotApi.Account.SubscribeToUserDataUpdatesAsync(listenKey.Data, data => {
    // Handle update
}, null, null, null);

// The listen key will stay valid for 60 minutes, after this no updates will be send anymore
// To extend the life time of the listen key it is recommended to call the KeepAliveUserStreamAsync method every 30 minutes
_ = Task.Run(async () => {
    while (true)
    {
        await Task.Delay(Timespan.FromMinutes(30));
        await binanceClient.SpotApi.Account.KeepAliveUserStreamAsync(listenKey.Data);
    }
});
// Retrieve the listen key
var listenKey = await bingXRestClient.SpotApi.Account.StartUserStreamAsync();

// Subscribe using the key
await bingXSocketClient.SpotApi.SubscribeToBalanceUpdatesAsync(listenKey.Data, data => {
    // Handle update
});

// The listen key will stay valid for 60 minutes, after this no updates will be send anymore
// To extend the life time of the listen key it is recommended to call the KeepAliveUserStreamAsync method every 30 minutes
_ = Task.Run(async () => {
    while (true)
    {
        await Task.Delay(Timespan.FromMinutes(30));
        await bingXClient.SpotApi.Account.KeepAliveUserStreamAsync(listenKey.Data);
    }
});
await bitfinexSocketClient.SpotApi.SubscribeToUserUpdatesAsync(orderHandler: data => {
    // Handle update
});
await bitgetSocketClient.SpotApiV2.SubscribeToOrderUpdatesAsync(data => {
    // Handle update
});
await bitMartSocketClient.SpotApi.SubscribeToOrderUpdatesAsync(data => {
    // Handle update
});
await bybitSocketClient.V5PrivateApi.SubscribeToOrderUpdatesAsync(data => {
    // Handle update
});
await coinbaseSocketClient.AdvancedTradeApi.SubscribeToUserUpdatesAsync(data => {
    // Handle update
});
await coinExSocketClient.SpotApiV2.SubscribeToOrderUpdatesAsync(data => {
    // Handle update
});
await cryptoComSocketClient.ExchangeApi.SubscribeToOrderUpdatesAsync(data => {
    // Handle update
});
await gateioSocketClient.SpotApi.SubscribeToOrderUpdatesAsync(data => {
    // Handle update
});
await htxSocketClient.SpotApi.SubscribeToOrderUpdatesAsync(onOrderMatched: data => {
    // Handle update
});
await hyperLiquidSocketClient.SpotApi.SubscribeToOrderUpdatesAsync(null, data => {
    // Handle update
});
// Retrieve the token
var token = await krakenClient.SpotApi.Account.GetWebsocketTokenAsync();

// Subscribe using the token
await krakenSocketClient.SpotApi.SubscribeToOrderUpdatesAsync(token.Data.Token, data => {
    // Handle update
});
await kucoinSocketClient.SpotApi.SubscribeToOrderUpdatesAsync(data => {
    // Handle update
}, null, null);
// Retrieve the listen key
var token = await mexcClient.SpotApi.Account.StartUserStreamAsync();

// Subscribe using the key
await mexcSocketClient.SpotApi.SubscribeToOrderUpdatesAsync(token.Data, data => {
    // Handle update
});

// The listen key will stay valid for 60 minutes, after this the connection is closed and reconnecting with the same listen key will fail
// To extend the life time of the listen key it is recommended to call the KeepAliveUserStreamAsync method every 30 minutes
_ = Task.Run(async () => {
    while (true)
    {
        await Task.Delay(Timespan.FromMinutes(30));
        await mexcClient.SpotApi.Account.KeepAliveUserStreamAsync(token.Data);
    }
});
await okxSocketClient.UnifiedApi.Trading.SubscribeToOrderUpdatesAsync(OKXInstrumentType.Spot, null, null, data => {
    // Handle update
});
// It's required for the WhiteBit API to specify which symbols to subscribe to
await whitebitSocketClient.V4Api.SubscribeToOpenOrderUpdatesAsync(["ETH_USDT", "BTC_USDT"], data => {
    // Handle update
});
// Retrieve the token
var listenKey = await xtRestClient.SpotApi.Account.GetWebsocketTokenAsync();

// Subscribe using the key
await xtSocketClient.SpotApi.SubscribeToBalanceUpdatesAsync(listenKey.Data, data => {
    // Handle update
});

// The listen key will stay valid for 48 hours, after this no updates will be send anymore
// To extend the life time of the token it is recommended to call the GetWebsocketTokenAsync method at a set interval which will extend the lifetime
_ = Task.Run(async () => {
    while (true)
    {
        await Task.Delay(Timespan.FromHours(4));
        await xtRestClient.SpotApi.Account.GetWebsocketTokenAsync();
    }
});
Minimal API

A minimal API example allowing the caller to retrieve ticker information for a specific exchange and asset pair. This is using the CryptoClient.Net Nuget package.

This API returns ticker information for the following path

/Ticker/[Exchange]/[BaseAsset]/[QuoteAsset] for example /Ticker/Kraken/ETH/BTC

using CryptoExchange.Net.Interfaces;
using CryptoClients.Net.Enums;
using CryptoClients.Net.Interfaces;
using Microsoft.AspNetCore.Mvc;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddCryptoClients();
var app = builder.Build();

app.MapGet("Ticker/{exchange}/{baseAsset}/{quoteAsset}", async ([FromServices] IExchangeRestClient client, Exchange exchange, string baseAsset, string quoteAsset) =>
{
    var spotClient = client.GetSpotTickerClient(exchange)!;
    var result = await spotClient.GetSpotTickerAsync(new GetTickerRequest(new SharedSymbol(TradingMode.Spot, baseAsset, quoteAsset)));
    return result.Data;
});

app.Run();
		  

Glossary

DefinitionSynonymsMeaning
Symbol Market, Pair An asset pair on which can be traded, for example BTC-ETH
Asset Currency, Coin A coin for which you can hold balance and which makes up Symbols. For example both BTC, ETH or USD
Trade Execution, Fill The (partial) execution of an order. Orders can have multiple trades
Quantity Amount, Size The amount of asset
QuoteQuantity Value The amount of quote asset
Fee Commission The fee paid for an order, trade or withdrawal
Kline Candlestick, OHLC K-line data, used for candlestick charts. Contains Open/High/Low/Close/Volume
KlineInterval Period The time period of a single kline
Open order Active order, Unexecuted order An order which has not yet been fully filled
Closed order Completed order, executed order An order which is no longer active. Can be canceled or fully filled
Network Chain The network of an asset. For example ETH allows multiple networks like ERC20 and BEP2
Orderbook Market depth A list of (the top rows of) the current best bids and asks
Ticker Stats Statistics over the last 24 hours

FAQ

I occasionally get a NullReferenceException, what's wrong?

You probably don't check the result status of a call and just assume the data is always there. NullReferenceExecption will happen when you have code like this var symbol = client.GetTickersAync().Result.Data.Symbol because the Data property is null when the call fails. Instead check if the call is successful:

var tickerResult = await client.SpotApi.ExchangeData.GetTickersAync();
if(!tickerResult.Success)
{
  // Handle error
}
else
{
  // Handle result, it is now safe to access the Data property
  var symbol = tickerResult.Data.Symbol;
}

The socket client stops sending updates after a little while

You probably didn't keep a reference to the socket client and it got disposed.

// WRONG
private void SomeMethod()
{
  var socketClient = new BinanceSocketClient();
  socketClient.Spot.SubscribeToOrderBookUpdatesAsync("BTCUSDT", data => {
    // Handle data
  });
}
// RIGHT
private BinanceSocketClient _socketClient = new BinanceSocketClient();

// .. rest of the class

private void SomeMethod()
{
  _socketClient.Spot.SubscribeToOrderBookUpdates("BTCUSDT", data => {
    // Handle data
  });
}

Can I use the TestNet/US/other API with this library?

Yes, generally these are all supported and can be configured by setting the Environment in the client options. Some known environments should be available in the [Exchange]Environment class:

// Change environment to test
var client = new BinanceRestClient(options =>
{
    options.Environment = BinanceEnvironment.Testnet;
});

How are timestamps handled?

Exchange API's treat all timestamps as UTC, both incoming and outgoing. The client libraries do no conversion so received timestamps are always in UTC. When sending requests make sure to use UTC time as well.