# 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
--- | --- | ---
Binance | JKorf/Binance.Net |
BingX | JKorf/BingX.Net |
Bitfinex | JKorf/Bitfinex.Net |
Bitget | JKorf/Bitget.Net |
BitMart | JKorf/BitMart.Net |
BitMEX | JKorf/BitMEX.Net |
Bybit | JKorf/Bybit.Net |
Coinbase | JKorf/Coinbase.Net |
CoinEx | JKorf/CoinEx.Net |
CoinGecko | JKorf/CoinGecko.Net |
Crypto.com | JKorf/CryptoCom.Net |
DeepCoin | JKorf/DeepCoin.Net |
Gate.io | JKorf/GateIo.Net |
HTX | JKorf/HTX.Net |
HyperLiquid | JKorf/HyperLiquid.Net |
Kraken | JKorf/Kraken.Net |
Kucoin | JKorf/Kucoin.Net |
Mexc | JKorf/Mexc.Net |
OKX | JKorf/OKX.Net |
WhiteBit | JKorf/WhiteBit.Net |
XT | JKorf/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.

CryptoClients | JKorf/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 Core | `2.0` and higher
.NET Framework | `4.6.1` and higher
Mono | `5.4` and higher
Xamarin.iOS | `10.14` and higher
Xamarin.Android | `8.0` and higher
UWP | `10.0.16299` and higher
Unity | `2018.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

Exchange | Link
--- | ---
Bybit | https://partner.bybit.com/b/jkorf
Coinbase | https://advanced.coinbase.com/join/T6H54H8
CoinEx | https://www.coinex.com/register?refer_code=hd6gn
Crypto.com | https://crypto.com/exch/26ge92xbkn
DeepCoin | https://s.deepcoin.com/jddhfca
HTX | https://www.htx.com/invite/en-us/1f?invite_code=fxp9
HyperLiquid | https://app.hyperliquid.xyz/join/JKORF
Kucoin | https://www.kucoin.com/r/rf/QBS4FPED
OKX | https://okx.com/join/48046699
WhiteBit | https://whitebit.com/referral/a8e59b59-186c-4662-824c-3095248e0edf
XT | https://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

Interface | Description
--- | ---
`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

Interface | Description
--- | ---
`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

Interface | Description
--- | ---
`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

Interface | Description
--- | ---
`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

Interface | Description
--- | ---
`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

Interface | Description
--- | ---
`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

Interface | Description
--- | ---
`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

Interface | Description
--- | ---
`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

Interface | Description
--- | ---
`ICoinGeckoRestClient` | The client for accessing the CoinGecko REST API
`ICryptoRestClient` | An aggregating client from which multiple different library REST clients can be accessed

Interface | Description
--- | ---
`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

Interface | Description
--- | ---
`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

Interface | Description
--- | ---
`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

Interface | Description
--- | ---
`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

Interface | Description
--- | ---
`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

Interface | Description
--- | ---
`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

Interface | Description
--- | ---
`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

Interface | Description
--- | ---
`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

Interface | Description
--- | ---
`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

Interface | Description
--- | ---
`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

Interface | Description
--- | ---
`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:

Property | Description
--- | ---
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:

Property | Description
--- | ---
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:

Property | Description
--- | ---
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<SharedSpotTicker?> 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

Mode | Description
--- | ---
`TradingMode.Spot` | Spot trading
`TradingMode.PerpetualLinear` | Futures contract trading without delivery date using stablecoin as settlement
`TradingMode.DeliveryLinear` | Futures contract trading with specific delivery date using stablecoin as settlement
`TradingMode.PerpetualInverse` | Futures contract trading without delivery date using a crypto asset as settlement
`TradingMode.DeliveryInverse` | Futures 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

Interface | Description
--- | ---
`IAssetsRestClient` | For requesting withdrawal and deposit info for assets
`IKlineRestClient` | For requesting public kline/candlestick data
`IOrderBookRestClient` | For requesting public order book data
`IRecentTradeRestClient` | For requesting the most recent public trades for a symbol
`ITradeHistoryRestClient` | For requesting historic public trade data for a symbol
`ISpotSymbolRestClient` | For requesting Spot symbols info
`ISpotTickerRestClient` | For requesting Spot ticker information
`IFundingRateRestClient` | For requesting funding rate history for a Futures symbol
`IFuturesSymbolRestClient` | For requesting Futures symbols info
`IFuturesTickerRestClient` | For requesting Futures ticker information
`IIndexPriceKlineRestClient` | For requesting index price kline history for a Futures symbol
`IMarkPriceKlineRestClient` | For requesting mark price kline history for a Futures symbol
`IOpenInterestRestClient` | For requesting the open interest for a Futures symbol
`IBalanceRestClient` | For requesting user asset balances
`IDepositRestClient` | For requesting user deposit history
`IListenKeyRestClient` | For managing the user listen key which can be used for subscribing to user data streams
`IWithdrawalRestClient` | For requesting user withdrawal history
`IWithdrawRestClient` | For requesting to withdraw funds from the exchange
`ISpotOrderRestClient` | For placing and managing Spot orders
`IFuturesOrderRestClient` | For placing and managing Futures orders
`ILeverageRestClient` | For managing leverage for a Futures symbol
`IPositionHistoryRestClient` | For requesting the user position closing history
`IPositionModeRestClient` | For managing the position mode for the user
`IFeeRestClient` | For requesting maker and taker trading fee percentages for the user

Available Socket shared interfaces

Interface | Description
--- | ---
`IBookTickerSocketClient` | For subscribing to book ticker (best bid/ask) updates for a symbol
`IKlineSocketClient` | For subscribing to kline/candlestick updates for a symbol
`IOrderBookSocketClient` | For subscribing to order book snapshot updates for a symbol
`ITickerSocketClient` | For subscribing to ticker updates for a symbol
`ITickersSocketClient` | For subscribing to ticker updates for all symbols
`ITradeSocketClient` | For subscribing to public trade updates for a symbol
`IBalanceSocketClient` | For subscribing to user balance updates
`IUserTradeSocketClient` | For subscribing to user trade updates
`ISpotOrderSocketClient` | For subscribing to user Spot order updates
`IFuturesOrderSocketClient` | For subscribing to user Futures order updates
`IPositionSocketClient` | For 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

Option | Description | Default 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<T>.OriginalData` property, for Websocket API client subscriptions the data will be available in the `DataEvent<T>.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

Option | Description | Default 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

Option | Description | Default 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:

Field | Description
--- | ---
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:

Field | Description
--- | ---
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:

Field | Description
--- | ---
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:

Field | Description
--- | ---
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:

Field | Description
--- | ---
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

##### Show

    // 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

##### Show

    // 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

##### Show

    // 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

##### Show

    // 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

##### Show

    // 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

##### Show

    // 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.

##### Show

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

Definition | Synonyms | Meaning
--- | --- | ---
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.