mirror of
https://github.com/JKorf/CryptoExchange.Net.git
synced 2026-10-14 15:11:44 +00:00
Compare commits
18 Commits
927ffe198b
..
master
| Author | SHA1 | Date | |
|---|---|---|---|
| f079e6f0f4 | |||
| c6647f6815 | |||
| 5135047097 | |||
| 4f25a6e7d6 | |||
| a0f3dcaa4c | |||
| 550afd41c3 | |||
| afdced3bb8 | |||
| f8c657f308 | |||
| c9ae1bf379 | |||
| 59acbd648e | |||
| 7a11bd0c5e | |||
| 9502db6d58 | |||
| e6ab899654 | |||
| a8517d2c76 | |||
| bda81d8170 | |||
| 666ca76776 | |||
| ae7504c26c | |||
| cecfbcba48 |
@@ -1,87 +1,92 @@
|
||||
---
|
||||
description: Conventions for cross-exchange code using CryptoExchange.Net SharedApis abstractions. Apply when generating C# code that interacts with multiple cryptocurrency exchanges through a unified interface.
|
||||
description: Conventions for cross-exchange C# code using CryptoExchange.Net Shared API V2 capabilities.
|
||||
globs:
|
||||
- "**/*.cs"
|
||||
- "**/*.csproj"
|
||||
alwaysApply: false
|
||||
---
|
||||
|
||||
# CryptoExchange.Net Conventions
|
||||
# CryptoExchange.Net conventions
|
||||
|
||||
This codebase uses **CryptoExchange.Net** abstractions for multi-exchange access. Each exchange has its own library (Binance.Net, Bybit.Net, OKX.Net, ...). Use `CryptoExchange.Net.SharedApis` for code that should work across exchanges.
|
||||
CryptoExchange.Net is the base dependency for exchange-specific libraries. Install Binance.Net, Bybit.Net, JK.OKX.Net, or another exchange library; use `CryptoClients.Net` when the application needs the bundle.
|
||||
|
||||
## Multi-exchange pattern
|
||||
## Use Shared API V2
|
||||
|
||||
For new cross-exchange code, use the fine-grained interfaces in `CryptoExchange.Net.SharedApis` and each API surface's `.SharedApi` property:
|
||||
|
||||
```csharp
|
||||
using Binance.Net.Clients;
|
||||
using OKX.Net.Clients;
|
||||
using CryptoExchange.Net.SharedApis;
|
||||
|
||||
ISpotTickerRestClient binance = new BinanceRestClient().SpotApi.SharedClient;
|
||||
ISpotTickerRestClient okx = new OKXRestClient().UnifiedApi.SharedClient;
|
||||
IGetTickerRest binance = new BinanceRestClient().SpotApi.SharedApi;
|
||||
IGetTickerRest okx = new OKXRestClient().UnifiedApi.SharedApi;
|
||||
|
||||
var symbol = new SharedSymbol(TradingMode.Spot, "BTC", "USDT");
|
||||
|
||||
var ticker = await binance.GetSpotTickerAsync(new GetTickerRequest(symbol));
|
||||
// ticker.Data.LastPrice — same model regardless of exchange
|
||||
var result = await binance.GetTickerAsync(new GetTickerRequest(symbol));
|
||||
```
|
||||
|
||||
## Symbol normalization
|
||||
Use one capability per operation, such as `IGetTickerRest`, `IGetOrderBookRest`, `IPlaceSpotOrderRest`, or `ISubscribeTradesSocket`. Do not use a broad V1 interface for new code. `.SharedClient` remains only for incremental V1 migration.
|
||||
|
||||
`SharedSymbol(TradingMode.Spot, "BTC", "USDT")` is portable. Each library translates to its native format internally. Don't pass raw strings like `"BTCUSDT"` to shared methods.
|
||||
## Capability discovery
|
||||
|
||||
## Symbol metadata and catalogs
|
||||
|
||||
In 12.2.0, `SharedSpotSymbol` and `SharedFuturesSymbol` include `DisplayName` and base/quote asset classification through `SharedAssetType` (`Crypto`, `Fiat`, `TradFi`) and `SharedAssetSubType` (`StableCoin`, `Equity`, `Commodity`). Pass the matching base/quote filters to `GetSymbolsRequest` when discovery should return only a class of markets.
|
||||
|
||||
After calling `GetSpotSymbolsAsync`, `ISpotSymbolRestClient.SpotSymbolCatalog` maps asset and symbol names to shared metadata. `IFuturesSymbolRestClient.FuturesSymbolCatalog` works the same way after `GetFuturesSymbolsAsync`. Treat either property as unavailable before its corresponding request has populated the cache.
|
||||
|
||||
When implementing an exchange library, use `LibraryHelpers.IsStableCoin`, `IsCommodity`, and `IsEquity` only as best-effort classifiers and supply exchange-specific additions where needed.
|
||||
|
||||
## Shared market-data quantities
|
||||
|
||||
In 12.4.0, use `SharedOrderQuantity`-valued `Volumes` on shared spot/futures tickers and klines, and `Quantities` on shared trades. The scalar `Volume`, `QuoteVolume`, and `Quantity` members are obsolete.
|
||||
|
||||
## WebSocket order commands
|
||||
|
||||
In 12.5.0, `ISpotOrderManagementSocketClient` and `IFuturesOrderManagementSocketClient` optionally provide place/cancel order commands over WebSocket. Their methods return `QueryResult<SharedId>`, not `WebSocketResult<UpdateSubscription>`. Check whether the exchange implements the interface.
|
||||
|
||||
## Result pattern
|
||||
|
||||
REST methods return `HttpResult<T>` and websocket subscription methods return `WebSocketResult<UpdateSubscription>`. Always check `.Success`. `.Exchange` property identifies which exchange responded — useful for logging.
|
||||
|
||||
## Available shared interfaces
|
||||
|
||||
- REST tickers/symbols/orderbook/klines/trades, orders (spot/futures, regular/trigger/TP-SL), balances, positions, fees, deposits/withdrawals, transfers
|
||||
- WebSocket tickers, book tickers, order book, trades, klines, user data, and optional spot/futures order management
|
||||
|
||||
Each exchange documents which it implements. Not every exchange supports every operation.
|
||||
|
||||
## Multi-exchange aggregation
|
||||
|
||||
Run requests across exchanges concurrently via `Task.WhenAll` — the library is async-safe and concurrent requests are the norm.
|
||||
When the exchange and API surface are known, assign the typed `.SharedApi` to the required capability. When selection is dynamic, use an exchange-wide `I[Exchange]SharedApiClient` and a strongly typed reference:
|
||||
|
||||
```csharp
|
||||
var clients = new ISpotTickerRestClient[] { binance, okx, bybit };
|
||||
var tasks = clients.Select(c => c.GetSpotTickerAsync(new GetTickerRequest(symbol)));
|
||||
var match = sharedClient.GetCapability(
|
||||
SharedCapabilities.Orders.Futures.PlaceOrder.Rest,
|
||||
TradingMode.PerpetualLinear);
|
||||
|
||||
if (match is not null)
|
||||
await match.Capability.PlaceFuturesOrderAsync(request);
|
||||
```
|
||||
|
||||
`GetCapability` returns one preferred match or `null`; `GetCapabilities` returns all matches. Specify `TradingMode` when multiple API surfaces may implement the same capability. A `SharedCapabilities` reference is a lookup key, not proof of exchange support.
|
||||
|
||||
## Symbols and parameters
|
||||
|
||||
Use `SharedSymbol` instead of native strings such as `BTCUSDT`. The exchange library performs formatting.
|
||||
|
||||
Capability presence does not imply support for every request property. For dynamic code, inspect `match.Options.RequestParameterRules`, `ExchangeParameterRules`, and `SupportedTradingModes`. Supply exchange-specific values through the request's `ExchangeParameters`.
|
||||
|
||||
## Transports and results
|
||||
|
||||
- Transport-agnostic capabilities return `IExchangeCallResult<T>`.
|
||||
- `...Rest` capabilities return `HttpResult<T>`.
|
||||
- socket command capabilities return `QueryResult<T>`.
|
||||
- `ISubscribe...Socket` capabilities return `WebSocketResult<UpdateSubscription>`.
|
||||
|
||||
Use `.Rest` or `.Socket` on capability references when the transport matters. Otherwise exchange-wide lookup honors `PreferredTransport`, normally REST. Always check `.Success` before `.Data` and include `.Exchange` in multi-exchange logs.
|
||||
|
||||
## Concurrent aggregation
|
||||
|
||||
```csharp
|
||||
var clients = new IGetTickerRest[] { binance, okx, bybit };
|
||||
var tasks = clients.Select(x =>
|
||||
x.GetTickerAsync(new GetTickerRequest(symbol)));
|
||||
var results = await Task.WhenAll(tasks);
|
||||
```
|
||||
|
||||
Reuse clients through dependency injection and run independent exchange calls concurrently.
|
||||
|
||||
## V2 semantics
|
||||
|
||||
- `GetTickerAsync` and `GetAllTickersAsync` return `SharedTicker` for spot and futures.
|
||||
- WebSocket order updates use `SharedSpotOrderUpdate` and `SharedFuturesOrderUpdate`.
|
||||
- `ICloseFullPosition` closes the entire position; use an order capability for partial closes.
|
||||
- Each exchange supports a subset of capabilities and parameter combinations.
|
||||
|
||||
## Hard rules
|
||||
|
||||
- ❌ Never install `CryptoExchange.Net` alone and expect to call exchanges — it's a base library; you need exchange-specific packages
|
||||
- ❌ Never mix exchange-specific models in cross-exchange code (use `SharedSymbol`, `SharedSpotTicker`, etc.)
|
||||
- ❌ Never use `.Result` / `.Wait()` — async-only
|
||||
- ❌ Never iterate sequentially when concurrency is fine — `Task.WhenAll` is your friend
|
||||
- ❌ Never instantiate clients per-request — reuse via DI
|
||||
- ✅ Always use `.SharedClient` for cross-exchange code
|
||||
- ✅ Always check `.Success` before reading `.Data`
|
||||
- ✅ Always log with `.Exchange` so multi-exchange logs are decipherable
|
||||
- ✅ Always handle "not supported on this exchange" errors gracefully
|
||||
- Never install CryptoExchange.Net alone and expect exchange endpoints.
|
||||
- Never pass exchange-native models or symbols through shared services.
|
||||
- Never assume a capability or request field is supported; resolve and inspect it.
|
||||
- Never use `.Result` or `.Wait()`.
|
||||
- Never instantiate exchange clients per request.
|
||||
- Prefer `Task.WhenAll` for independent cross-exchange calls.
|
||||
- Prefer `.SharedApi` and fine-grained V2 interfaces for new code.
|
||||
|
||||
## Reference
|
||||
|
||||
- `AGENTS.md` in repo root has fuller examples
|
||||
- `llms.txt` for AI context
|
||||
- `Examples/ai-friendly/` for compilable examples
|
||||
- For single-exchange code, see that exchange's library (e.g., Binance.Net `AGENTS.md`)
|
||||
- `AGENTS.md` for fuller examples
|
||||
- `docs/ai-api-map.md` for the V2 interface map
|
||||
- `docs/SHARED_API_V2_MIGRATION.md` for V1 migration details
|
||||
- `llms-full.txt` for expanded AI context
|
||||
|
||||
@@ -1,66 +1,70 @@
|
||||
# Copilot Instructions for CryptoExchange.Net
|
||||
|
||||
This repository is **CryptoExchange.Net** — the base library powering 28+ cryptocurrency exchange wrappers in C#/.NET (Binance.Net, Bybit.Net, OKX.Net, Kraken.Net, Coinbase.Net, etc.).
|
||||
CryptoExchange.Net is the base library behind exchange-specific C#/.NET clients such as Binance.Net, Bybit.Net, OKX.Net, Kraken.Net, and Coinbase.Net.
|
||||
|
||||
When generating code in this ecosystem, follow these conventions:
|
||||
## Package choice
|
||||
|
||||
## You don't install CryptoExchange.Net directly
|
||||
Do not install CryptoExchange.Net alone to call an exchange. Install the exchange-specific package, or use `CryptoClients.Net` for the full bundle. For single-exchange code, prefer that library's native client. For portable multi-exchange code, use `CryptoExchange.Net.SharedApis`.
|
||||
|
||||
Install the exchange-specific library you need (`Binance.Net`, `JK.OKX.Net`, `Bybit.Net`, ...) or `CryptoClients.Net` for the bundle. CryptoExchange.Net is pulled in as a dependency.
|
||||
## Generate Shared API V2 code
|
||||
|
||||
## Multi-exchange code uses SharedApis
|
||||
|
||||
For code that must work against multiple exchanges, use `CryptoExchange.Net.SharedApis` interfaces accessed via `.SharedClient` properties on each exchange's API surface:
|
||||
Use fine-grained V2 capabilities through `.SharedApi`:
|
||||
|
||||
```csharp
|
||||
using CryptoExchange.Net.SharedApis;
|
||||
|
||||
ISpotTickerRestClient binance = new BinanceRestClient().SpotApi.SharedClient;
|
||||
ISpotTickerRestClient okx = new OKXRestClient().UnifiedApi.SharedClient;
|
||||
IGetTickerRest binance = new BinanceRestClient().SpotApi.SharedApi;
|
||||
IGetTickerRest okx = new OKXRestClient().UnifiedApi.SharedApi;
|
||||
|
||||
var symbol = new SharedSymbol(TradingMode.Spot, "BTC", "USDT");
|
||||
var ticker = await binance.GetSpotTickerAsync(new GetTickerRequest(symbol));
|
||||
var result = await binance.GetTickerAsync(new GetTickerRequest(symbol));
|
||||
```
|
||||
|
||||
Same code works on every exchange that implements the interface. Use `Task.WhenAll` for concurrent multi-exchange calls.
|
||||
Choose the interface for the operation: for example, `IGetTickerRest`, `IGetOrderBookRest`, `IPlaceSpotOrderRest`, `ICancelFuturesOrderRest`, or `ISubscribeTradesSocket`. V1 broad interfaces remain on `.SharedClient` for migration, but new code should use `.SharedApi`.
|
||||
|
||||
## Shared symbol metadata
|
||||
Use `SharedSymbol`; do not hard-code exchange-native symbol formatting in shared code.
|
||||
|
||||
CryptoExchange.Net 12.2.0 classifies the base and quote sides of `SharedSpotSymbol` and `SharedFuturesSymbol` with `SharedAssetType` (`Crypto`, `Fiat`, `TradFi`) and optional `SharedAssetSubType` (`StableCoin`, `Equity`, `Commodity`). The models also expose `DisplayName`. Use the corresponding base/quote fields on `GetSymbolsRequest` to filter symbol discovery.
|
||||
## Runtime capability selection
|
||||
|
||||
`ISpotSymbolRestClient.SpotSymbolCatalog` is populated by `GetSpotSymbolsAsync`; `IFuturesSymbolRestClient.FuturesSymbolCatalog` is populated by `GetFuturesSymbolsAsync`. Do not assume a catalog is available before that request. For exchange-library implementations, `LibraryHelpers.IsStableCoin`, `IsCommodity`, and `IsEquity` offer best-effort classification and can be extended with exchange-specific values.
|
||||
When the API surface is known, assign its typed `.SharedApi` directly. When support or the API surface is selected at runtime, use the exchange-wide `I[Exchange]SharedApiClient`:
|
||||
|
||||
## Shared market-data quantities
|
||||
```csharp
|
||||
var match = sharedClient.GetCapability(
|
||||
SharedCapabilities.Orders.Futures.PlaceOrder.Rest,
|
||||
TradingMode.PerpetualLinear);
|
||||
|
||||
CryptoExchange.Net 12.4.0 uses `SharedOrderQuantity` for market-data quantities. Prefer `Volumes` on `SharedSpotTicker`, `SharedFuturesTicker`, and `SharedKline`, and `Quantities` on `SharedTrade`; the scalar `Volume`, `QuoteVolume`, and `Quantity` members are obsolete.
|
||||
if (match is null)
|
||||
return;
|
||||
|
||||
## WebSocket order commands
|
||||
var result = await match.Capability.PlaceFuturesOrderAsync(request);
|
||||
```
|
||||
|
||||
CryptoExchange.Net 12.5.0 adds optional `ISpotOrderManagementSocketClient` and `IFuturesOrderManagementSocketClient` interfaces for placing and canceling orders over WebSocket. These command methods return `QueryResult<SharedId>` rather than a subscription result. Check exchange support before relying on either interface.
|
||||
`GetCapability` returns one preferred `SharedCapabilityResolution<T>` or `null`; `GetCapabilities` returns all matching implementations. Include `TradingMode` when an exchange can expose multiple futures surfaces. `SharedCapabilities` entries are lookup references, not guarantees of support.
|
||||
|
||||
## Single-exchange code uses the exchange's own client
|
||||
Before constructing dynamic requests, inspect `match.Options.RequestParameterRules`, `ExchangeParameterRules`, and `SupportedTradingModes`. A capability may exist while a particular request field is unsupported.
|
||||
|
||||
For Binance-only code, use `BinanceRestClient` directly (see Binance.Net repo `AGENTS.md`). SharedApis is for portability — use it when you need that.
|
||||
## Results and transports
|
||||
|
||||
## Result pattern
|
||||
- Transport-agnostic operation: `IExchangeCallResult<T>`
|
||||
- REST capability: `HttpResult<T>`
|
||||
- WebSocket command capability: `QueryResult<T>`
|
||||
- WebSocket subscription capability: `WebSocketResult<UpdateSubscription>`
|
||||
|
||||
REST methods return `HttpResult<T>` and websocket subscription methods return `WebSocketResult<UpdateSubscription>`. Check `.Success` before `.Data`. `.Error` has structured info. `.Exchange` on shared clients identifies which exchange responded.
|
||||
Select `.Rest` or `.Socket` when transport-specific behavior matters. Otherwise exchange-wide selection uses `PreferredTransport`, normally REST. Always check `.Success` before `.Data`; use `.Error` and `.Exchange` for diagnostics.
|
||||
|
||||
## Available shared interfaces
|
||||
## Current V2 semantics
|
||||
|
||||
REST: tickers, symbols, orderbook, klines, trades, orders (spot/futures, trigger, TP-SL), balances, positions, fees, deposits/withdrawals, transfers.
|
||||
WebSocket: tickers, book tickers, orderbook, trades, klines, user data, and optional spot/futures order management.
|
||||
- Ticker operations are `GetTickerAsync` and `GetAllTickersAsync`, returning `SharedTicker` for both spot and futures.
|
||||
- Socket order streams use `SharedSpotOrderUpdate` and `SharedFuturesOrderUpdate`.
|
||||
- `ICloseFullPosition` closes a complete position, not a partial quantity.
|
||||
- Exchange support varies by operation, transport, trading mode, and request parameter.
|
||||
|
||||
Each exchange library implements a subset. Check exchange docs for support matrix.
|
||||
## Engineering conventions
|
||||
|
||||
## Avoid
|
||||
- Reuse clients through dependency injection.
|
||||
- Use `await`; never use `.Result` or `.Wait()`.
|
||||
- Use `Task.WhenAll` for independent requests across exchanges.
|
||||
- Keep exchange-native models out of portable services.
|
||||
- Do not infer feature support from a broad interface or request model.
|
||||
|
||||
- Installing `CryptoExchange.Net` alone and trying to call exchange APIs (need exchange-specific packages)
|
||||
- Mixing exchange-native models in cross-exchange code (use Shared* types)
|
||||
- Synchronous `.Result` / `.Wait()` (use `await`)
|
||||
- Instantiating clients per-request (use DI, reuse instances)
|
||||
- Sequential per-exchange calls when parallel is fine (`Task.WhenAll`)
|
||||
|
||||
## Reference
|
||||
|
||||
For detailed patterns see `AGENTS.md` and `llms.txt` in repo root, `examples/ai-friendly/` for compilable examples.
|
||||
See `AGENTS.md`, `docs/ai-api-map.md`, and `docs/SHARED_API_V2_MIGRATION.md` for expanded guidance.
|
||||
|
||||
@@ -1,40 +1,39 @@
|
||||
---
|
||||
name: cryptoexchange-net
|
||||
description: Use CryptoExchange.Net abstractions when generating C#/.NET code that needs to work across MULTIPLE cryptocurrency exchanges (Binance + Bybit + OKX + Kraken + Coinbase + ...) — for arbitrage, best-execution routing, multi-exchange portfolio dashboards, exchange-agnostic trading bots, or comparison tools. Triggers on requests mentioning multi-exchange, cross-exchange, arbitrage, exchange-agnostic, or unified crypto API in C#. Also triggers when the user is implementing a new exchange library following the CryptoExchange.Net pattern.
|
||||
description: Use CryptoExchange.Net Shared API V2 abstractions when generating C#/.NET code for multiple cryptocurrency exchanges, including arbitrage, routing, portfolio aggregation, exchange-agnostic bots, comparison tools, and new exchange-library implementations.
|
||||
---
|
||||
|
||||
# CryptoExchange.Net Skill
|
||||
|
||||
## When to use
|
||||
|
||||
CryptoExchange.Net is the **base library** powering 28+ exchange-specific libraries (Binance.Net, Bybit.Net, OKX.Net, Kraken.Net, Coinbase.Net, etc.). You don't install it directly — you install the exchange libraries, which depend on it.
|
||||
CryptoExchange.Net is the base library behind exchange-specific libraries such as Binance.Net, Bybit.Net, OKX.Net, Kraken.Net, and Coinbase.Net. Do not install it alone to call an exchange.
|
||||
|
||||
**Three usage modes:**
|
||||
Choose one of these approaches:
|
||||
|
||||
1. **You target ONE exchange** → use that exchange's library directly (e.g., Binance.Net), see its CLAUDE.md.
|
||||
2. **You target MULTIPLE exchanges** → install each library you need + use `CryptoExchange.Net.SharedApis` interfaces — write code once, runs against any exchange. **This is the main use case for this skill.**
|
||||
3. **You want ALL exchanges in one package** → install `CryptoClients.Net`, get `ExchangeRestClient` and `ExchangeSocketClient` with everything bundled.
|
||||
1. One exchange: install and use that exchange's library directly.
|
||||
2. Multiple exchanges: install the required exchange libraries and use `CryptoExchange.Net.SharedApis` V2 capabilities.
|
||||
3. All exchanges in one package: install `CryptoClients.Net` and use its combined clients and shared capability lookup.
|
||||
|
||||
For new cross-exchange code, use Shared API V2. V1 aggregate interfaces remain available through `.SharedClient` for incremental migration.
|
||||
|
||||
## Installation
|
||||
|
||||
For a multi-exchange project:
|
||||
|
||||
```bash
|
||||
dotnet add package Binance.Net
|
||||
dotnet add package JK.OKX.Net
|
||||
dotnet add package Bybit.Net
|
||||
# ... etc
|
||||
```
|
||||
|
||||
Or the bundle:
|
||||
Or install the bundle:
|
||||
|
||||
```bash
|
||||
dotnet add package CryptoClients.Net
|
||||
```
|
||||
|
||||
## Core Pattern: Shared Interfaces
|
||||
## Core pattern: fine-grained capabilities
|
||||
|
||||
Every exchange library exposes `.SharedClient` properties on its API surfaces. These implement the same interfaces from `CryptoExchange.Net.SharedApis`.
|
||||
Each exchange API surface exposes a typed `.SharedApi` aggregate. Assign it to the capability for the single operation being used:
|
||||
|
||||
```csharp
|
||||
using Binance.Net.Clients;
|
||||
@@ -42,155 +41,187 @@ using OKX.Net.Clients;
|
||||
using Bybit.Net.Clients;
|
||||
using CryptoExchange.Net.SharedApis;
|
||||
|
||||
// All three implement ISpotTickerRestClient
|
||||
ISpotTickerRestClient binance = new BinanceRestClient().SpotApi.SharedClient;
|
||||
ISpotTickerRestClient okx = new OKXRestClient().UnifiedApi.SharedClient;
|
||||
ISpotTickerRestClient bybit = new BybitRestClient().V5Api.SharedClient;
|
||||
IGetTickerRest binance = new BinanceRestClient().SpotApi.SharedApi;
|
||||
IGetTickerRest okx = new OKXRestClient().UnifiedApi.SharedApi;
|
||||
IGetTickerRest bybit = new BybitRestClient().V5Api.SharedApi;
|
||||
|
||||
// Single agnostic call — works against any of them
|
||||
var symbol = new SharedSymbol(TradingMode.Spot, "BTC", "USDT");
|
||||
var ticker = await binance.GetSpotTickerAsync(new GetTickerRequest(symbol));
|
||||
// ticker.Data.LastPrice, ticker.Data.HighPrice, etc. — same model regardless of exchange
|
||||
```
|
||||
var result = await binance.GetTickerAsync(new GetTickerRequest(symbol));
|
||||
|
||||
## Core Pattern: SharedSymbol
|
||||
|
||||
Different exchanges format symbols differently — Binance uses `BTCUSDT`, OKX uses `BTC-USDT`, others may have other formats. `SharedSymbol` normalizes this:
|
||||
|
||||
```csharp
|
||||
var btcusdt = new SharedSymbol(TradingMode.Spot, "BTC", "USDT");
|
||||
// Each exchange library translates SharedSymbol → its native format internally.
|
||||
|
||||
// For futures:
|
||||
var btcusdtPerp = new SharedSymbol(TradingMode.PerpetualLinear, "BTC", "USDT");
|
||||
```
|
||||
|
||||
For exchanges that use exotic asset names, see the AssetAliases configuration.
|
||||
|
||||
## Symbol Metadata and Asset Classification
|
||||
|
||||
Since CryptoExchange.Net 12.2.0, shared symbol responses describe both sides of a market with `BaseAssetType`, `BaseAssetSubType`, `QuoteAssetType`, and `QuoteAssetSubType`. `SharedAssetType` distinguishes `Crypto`, `Fiat`, and `TradFi`; `SharedAssetSubType` distinguishes `StableCoin`, `Equity`, and `Commodity`. `SharedSpotSymbol` and `SharedFuturesSymbol` also expose `DisplayName`.
|
||||
|
||||
The same fields on `GetSymbolsRequest` filter spot or futures symbol discovery:
|
||||
|
||||
```csharp
|
||||
var request = new GetSymbolsRequest(
|
||||
baseAssetType: SharedAssetType.Crypto,
|
||||
quoteAssetSubType: SharedAssetSubType.StableCoin);
|
||||
|
||||
var result = await symbolClient.GetSpotSymbolsAsync(request);
|
||||
```
|
||||
|
||||
After calling `GetSpotSymbolsAsync` or `GetFuturesSymbolsAsync`, use the client's `SpotSymbolCatalog` or `FuturesSymbolCatalog` to look up normalized asset and symbol metadata by name. The catalog is unavailable until the corresponding symbol request has populated the cache.
|
||||
|
||||
For exchange-library implementations, `LibraryHelpers.IsStableCoin`, `IsCommodity`, and `IsEquity` provide best-effort classification of known assets and accept exchange-specific additions. These helpers are heuristics, not an exhaustive source of truth.
|
||||
|
||||
## Shared Market-Data Quantities
|
||||
|
||||
Since CryptoExchange.Net 12.4.0, shared market-data models use `SharedOrderQuantity` so base-asset, quote-asset, and contract quantities remain explicit. Read `SharedSpotTicker.Volumes`, `SharedFuturesTicker.Volumes`, and `SharedKline.Volumes`; read `SharedTrade.Quantities`. The former scalar `Volume`, `QuoteVolume`, and `Quantity` members are obsolete.
|
||||
|
||||
## WebSocket Order Management
|
||||
|
||||
Since CryptoExchange.Net 12.5.0, exchanges can implement `ISpotOrderManagementSocketClient` and `IFuturesOrderManagementSocketClient` to place and cancel orders over WebSocket. These are command interfaces, not subscription interfaces: `Place*OrderAsync` and `Cancel*OrderAsync` return `QueryResult<SharedId>`. Check exchange support before using them.
|
||||
|
||||
## Available Shared Interfaces
|
||||
|
||||
**REST:**
|
||||
|
||||
- Market data: `ISpotTickerRestClient`, `IBookTickerRestClient`, `ISpotSymbolRestClient`, `IFuturesSymbolRestClient`, `IOrderBookRestClient`, `IRecentTradeRestClient`, `IKlineRestClient`
|
||||
- Orders: `ISpotOrderRestClient`, `IFuturesOrderRestClient`, `ISpotOrderClientIdRestClient`, `IFuturesOrderClientIdRestClient`, `ISpotTriggerOrderRestClient`, `IFuturesTriggerOrderRestClient`, `IFuturesTpSlRestClient`
|
||||
- Account: `IBalanceRestClient`, `IPositionRestClient`, `IFeeRestClient`, `ITransferRestClient`, `IDepositRestClient`, `IWithdrawalRestClient`
|
||||
|
||||
**WebSocket:**
|
||||
|
||||
- `ITickerSocketClient`, `IBookTickerSocketClient`
|
||||
- `IOrderBookSocketClient`, `ITradeSocketClient`, `IKlineSocketClient`
|
||||
- `IUserTradeSocketClient`, `ISpotOrderSocketClient`, `IFuturesOrderSocketClient`, `IPositionSocketClient`, `IBalanceSocketClient`
|
||||
- Order commands: `ISpotOrderManagementSocketClient`, `IFuturesOrderManagementSocketClient`
|
||||
|
||||
Each exchange documents which interfaces it implements (some exchanges don't support every operation).
|
||||
|
||||
## Core Pattern: Result Handling
|
||||
|
||||
Same as exchange-specific libraries: REST calls return `HttpResult<T>` and websocket subscription calls return `WebSocketResult<UpdateSubscription>`, both with `.Success`, `.Data`, and `.Error`. Always check `.Success` first.
|
||||
|
||||
```csharp
|
||||
var result = await sharedClient.GetSpotTickerAsync(new GetTickerRequest(symbol));
|
||||
if (!result.Success)
|
||||
{
|
||||
Console.WriteLine($"[{sharedClient.Exchange}] Error: {result.Error}");
|
||||
return;
|
||||
}
|
||||
Console.WriteLine($"[{sharedClient.Exchange}] {result.Data.LastPrice}");
|
||||
Console.WriteLine($"[{result.Exchange}] {result.Error}");
|
||||
else
|
||||
Console.WriteLine($"[{result.Exchange}] {result.Data!.LastPrice}");
|
||||
```
|
||||
|
||||
`.Exchange` property on every shared client tells you which exchange you're talking to — useful for logging.
|
||||
V2 interfaces describe operations, not broad feature groups. Examples include `IGetTickerRest`, `IGetOrderBookRest`, `IPlaceSpotOrderRest`, `ICancelFuturesOrderRest`, and `ISubscribeTradesSocket`. An exchange may implement one operation without implementing adjacent operations.
|
||||
|
||||
## Core Pattern: Multi-Exchange Aggregation
|
||||
## Shared symbols
|
||||
|
||||
Use `SharedSymbol`; never pass exchange-native symbol strings to shared requests:
|
||||
|
||||
```csharp
|
||||
var clients = new ISpotTickerRestClient[]
|
||||
{
|
||||
new BinanceRestClient().SpotApi.SharedClient,
|
||||
new OKXRestClient().UnifiedApi.SharedClient,
|
||||
new BybitRestClient().V5Api.SharedClient,
|
||||
};
|
||||
var spot = new SharedSymbol(TradingMode.Spot, "BTC", "USDT");
|
||||
var linearPerpetual = new SharedSymbol(
|
||||
TradingMode.PerpetualLinear,
|
||||
"BTC",
|
||||
"USDT");
|
||||
```
|
||||
|
||||
Each exchange library converts the shared symbol to its native format. For unusual exchange asset names, configure asset aliases.
|
||||
|
||||
## Known surface versus runtime selection
|
||||
|
||||
Use the typed `.SharedApi` surface when the exchange and API are known. This is the simplest and most discoverable approach:
|
||||
|
||||
```csharp
|
||||
IPlaceSpotOrderRest orders = restClient.SpotApi.SharedApi;
|
||||
```
|
||||
|
||||
For runtime selection, inject the exchange-wide `I[Exchange]SharedApiClient` registered by `services.Add[Exchange](...)`. Resolve a capability with the strongly typed `SharedCapabilities` catalog:
|
||||
|
||||
```csharp
|
||||
var match = sharedClient.GetCapability(
|
||||
SharedCapabilities.Orders.Futures.PlaceOrder.Rest,
|
||||
TradingMode.PerpetualLinear);
|
||||
|
||||
if (match is null)
|
||||
return; // This exchange/API/mode does not provide the operation.
|
||||
|
||||
var result = await match.Capability.PlaceFuturesOrderAsync(request);
|
||||
```
|
||||
|
||||
`GetCapability` returns one `SharedCapabilityResolution<T>` containing `Capability` and `Options`, or `null` if no supported implementation matches. `GetCapabilities` returns every match on that exchange-wide client. Always specify `TradingMode` when multiple spot, linear, inverse, or delivery surfaces could match.
|
||||
|
||||
`SharedCapabilities` references identify interface types; they do not guarantee that an exchange implements the operation and do not send a request.
|
||||
|
||||
## Transport selection and result types
|
||||
|
||||
Some commands have three interfaces:
|
||||
|
||||
- Transport-agnostic, such as `IPlaceSpotOrder`, returns `IExchangeCallResult<T>`.
|
||||
- REST-specific, such as `IPlaceSpotOrderRest`, returns `HttpResult<T>`.
|
||||
- socket-specific, such as `IPlaceSpotOrderSocket`, returns `QueryResult<T>`.
|
||||
|
||||
Socket subscription capabilities such as `ISubscribeTickerSocket` return `WebSocketResult<UpdateSubscription>`.
|
||||
|
||||
Use `.Rest` or `.Socket` on a capability reference when transport matters. Without a transport filter, exchange-wide lookup uses `PreferredTransport`, configured through the exchange's `SharedApi.PreferredTransport` option and normally defaulting to REST.
|
||||
|
||||
Always check `.Success` before `.Data`. Use `.Error` for failures and the result or capability `.Exchange` value for multi-exchange logging.
|
||||
|
||||
## Inspect parameter support
|
||||
|
||||
The presence of a capability does not mean every shared request property is accepted by every exchange. Inspect its `CapabilityOptions`:
|
||||
|
||||
- `RequestParameterRules`: whether each shared request field is `Required`, `Optional`, or `NotSupported`.
|
||||
- `ExchangeParameterRules`: required or optional exchange-specific values supplied through the request's `ExchangeParameters`.
|
||||
- `SupportedTradingModes`: the trading modes supported by this implementation.
|
||||
|
||||
```csharp
|
||||
var match = sharedClient.GetCapability(
|
||||
SharedCapabilities.Orders.Futures.PlaceOrder.Rest,
|
||||
TradingMode.PerpetualLinear);
|
||||
|
||||
var leverageRule = match?.Options.RequestParameterRules
|
||||
.FirstOrDefault(x => x.Name == nameof(PlaceFuturesOrderRequest.Leverage));
|
||||
```
|
||||
|
||||
Do not infer support from the request model alone. Handle a `null` resolution and exchange-specific parameter rules before issuing dynamic trading calls.
|
||||
|
||||
## Common capabilities
|
||||
|
||||
- Market data: `IGetTickerRest`, `IGetAllTickersRest`, `IGetOrderBookRest`, `IGetKlinesRest`, `IGetRecentTradesRest`; ticker, trade, kline, and order-book socket subscriptions.
|
||||
- Trading: fine-grained spot and futures place, edit, cancel, get, open-order, closed-order, and order-update capabilities.
|
||||
- Account: balances, positions, user trades, fees, deposits, withdrawals, and transfers.
|
||||
- Futures data: funding, open interest, leverage, mark price, and index price capabilities.
|
||||
|
||||
Check the typed exchange Shared API surface or use capability lookup instead of assuming universal support.
|
||||
|
||||
## Multi-exchange aggregation
|
||||
|
||||
Run independent exchange requests concurrently:
|
||||
|
||||
```csharp
|
||||
var clients = new IGetTickerRest[] { binance, okx, bybit };
|
||||
var symbol = new SharedSymbol(TradingMode.Spot, "BTC", "USDT");
|
||||
|
||||
// Fetch concurrently from all exchanges
|
||||
var tasks = clients.Select(c => c.GetSpotTickerAsync(new GetTickerRequest(symbol))).ToArray();
|
||||
var tasks = clients.Select(client =>
|
||||
client.GetTickerAsync(new GetTickerRequest(symbol)));
|
||||
var results = await Task.WhenAll(tasks);
|
||||
|
||||
for (int i = 0; i < clients.Length; i++)
|
||||
foreach (var result in results.Where(x => x.Success))
|
||||
Console.WriteLine($"{result.Exchange}: {result.Data!.LastPrice}");
|
||||
```
|
||||
|
||||
Reuse clients through dependency injection; do not instantiate them per request.
|
||||
|
||||
## Dependency injection
|
||||
|
||||
Each exchange library provides `services.Add[Exchange](...)`. Registration includes its native clients, V1 shared interfaces, V2 operation capabilities, and exchange-wide shared client.
|
||||
|
||||
Inject a capability directly only when the container has one intended implementation:
|
||||
|
||||
```csharp
|
||||
public sealed class TickerService(IGetTickerRest ticker)
|
||||
{
|
||||
if (results[i].Success)
|
||||
Console.WriteLine($"{clients[i].Exchange}: {results[i].Data!.LastPrice}");
|
||||
public Task<HttpResult<SharedTicker>> GetAsync(
|
||||
SharedSymbol symbol,
|
||||
CancellationToken ct = default)
|
||||
=> ticker.GetTickerAsync(new GetTickerRequest(symbol), ct);
|
||||
}
|
||||
```
|
||||
|
||||
## Per-Exchange Setup
|
||||
If multiple exchanges or multiple API surfaces register the same capability, inject the exchange-specific shared client and select its typed API property or call `GetCapability`. Plain single-service resolution does not express which implementation you want.
|
||||
|
||||
Each exchange library has its own credentials class and options. See each library's CLAUDE.md for specifics. The pattern is consistent: `XxxRestClient(options => { options.ApiCredentials = new XxxCredentials(...); })`.
|
||||
For the bundle, use `services.AddCryptoClients(...)`. CryptoClients.Net provides `IExchangeSharedApiClient` for capability lookup across exchanges.
|
||||
|
||||
## Dependency Injection
|
||||
## V1 migration
|
||||
|
||||
Each exchange library has its own `services.AddXxx(...)` extension. They all share the same option-builder pattern. Register only the ones you use:
|
||||
V1 `.SharedClient` facades and broad interfaces such as `ISpotTickerRestClient` remain available. Migrate one operation at a time:
|
||||
|
||||
```csharp
|
||||
services.AddBinance(restOpts => { /*...*/ }, socketOpts => { /*...*/ });
|
||||
services.AddOKX(restOpts => { /*...*/ }, socketOpts => { /*...*/ });
|
||||
// Inject IBinanceRestClient, IOKXRestClient, etc.
|
||||
// V1
|
||||
await restClient.SpotApi.SharedClient.GetSpotTickerAsync(request);
|
||||
|
||||
// V2
|
||||
await restClient.SpotApi.SharedApi.GetTickerAsync(request);
|
||||
```
|
||||
|
||||
For one-package access: `services.AddCryptoClients(...)` from `CryptoClients.Net`.
|
||||
Important semantic changes:
|
||||
|
||||
## Common Pitfalls — AVOID
|
||||
- V2 ticker methods are `GetTickerAsync` and `GetAllTickersAsync` and return `SharedTicker` for both spot and futures.
|
||||
- WebSocket order updates use `SharedSpotOrderUpdate` and `SharedFuturesOrderUpdate`; REST order retrieval keeps the ordinary order models.
|
||||
- `ICloseFullPosition` closes the complete position. Use an order capability for partial closes where supported.
|
||||
- Transport-agnostic operations return `IExchangeCallResult<T>`; select the REST or socket interface when code needs a transport-specific result.
|
||||
|
||||
- **Do NOT install `CryptoExchange.Net` and try to call exchange APIs directly** — it's a base abstraction; you need an exchange library.
|
||||
- **Do NOT try to use one exchange's models with another's client** — use the SharedApis types (`SharedSymbol`, `SharedSpotTicker`, `SharedSpotOrder`, etc.) for cross-exchange code.
|
||||
- **Do NOT block on async operations** — use `await` throughout. `Task.WhenAll` for parallelism across exchanges.
|
||||
- **Do NOT assume every exchange supports every operation** — check exchange docs or the library's implementation. Operations may return errors like "not supported on this exchange".
|
||||
- **Do NOT instantiate clients per-request** — reuse via DI.
|
||||
- **Do NOT iterate exchanges sequentially when concurrency is fine** — use `Task.WhenAll` for ~Nx speedup.
|
||||
See `docs/SHARED_API_V2_MIGRATION.md` for detailed mappings.
|
||||
|
||||
## Implementing a New Exchange Library
|
||||
## Common pitfalls
|
||||
|
||||
If you're building a NEW exchange wrapper following the CryptoExchange.Net pattern (rare but valuable):
|
||||
- Do not install `CryptoExchange.Net` alone and expect exchange endpoints.
|
||||
- Do not use `.SharedClient` for new V2 code; use `.SharedApi` and fine-grained capabilities.
|
||||
- Do not mix exchange-native request or response models into cross-exchange services.
|
||||
- Do not assume capability presence or request-parameter support; resolve and inspect it.
|
||||
- Do not rely on preferred transport when REST or socket semantics matter.
|
||||
- Do not block with `.Result` or `.Wait()`; use async calls throughout.
|
||||
- Do not query exchanges sequentially when requests are independent.
|
||||
|
||||
- Inherit from `RestApiClient` and `SocketApiClient` base classes
|
||||
- Define your own `XxxCredentials` extending `ApiCredentials` (or use `ApiCredentials` directly)
|
||||
- Implement `AuthenticationProvider` for the exchange's signing scheme
|
||||
- Implement the relevant `Shared*` interfaces on your API client classes for cross-exchange support
|
||||
- Follow the same `XxxRestOptions` / `XxxSocketOptions` pattern
|
||||
## Implementing a new exchange library
|
||||
|
||||
See existing libraries (Binance.Net, Bybit.Net) as reference implementations.
|
||||
- Derive API clients from `RestApiClient` and `SocketApiClient`.
|
||||
- Follow the exchange credentials and options patterns used by existing libraries.
|
||||
- Implement the relevant fine-grained V2 capability interfaces on typed Shared API classes.
|
||||
- Publish accurate `CapabilityOptions`, including supported trading modes and request/exchange parameter rules.
|
||||
- Register REST/socket Shared APIs and the exchange-wide shared client with the library's DI extension.
|
||||
- Retain V1 facades only where compatibility with existing consumers is required.
|
||||
|
||||
Use maintained exchange libraries such as Binance.Net and Bybit.Net as implementation references.
|
||||
|
||||
## Reference
|
||||
|
||||
- Source: https://github.com/JKorf/CryptoExchange.Net
|
||||
- Documentation: https://cryptoexchange.jkorf.dev/
|
||||
- SharedApis docs: https://cryptoexchange.jkorf.dev/CryptoExchange.Net/idocs_shared.html
|
||||
- Bundle (all exchanges): https://github.com/JKorf/CryptoClients.Net
|
||||
- Shared API migration: https://github.com/JKorf/CryptoExchange.Net/blob/master/docs/SHARED_API_V2_MIGRATION.md
|
||||
- Bundle: https://github.com/JKorf/CryptoClients.Net
|
||||
- Demo app: https://github.com/JKorf/CryptoManager.Net
|
||||
- Discord: https://discord.gg/MSpeEtSY8t
|
||||
|
||||
@@ -62,6 +62,31 @@ namespace CryptoExchange.Net.UnitTests.ConverterTests
|
||||
Assert.That(deserialized.Prop8!.Prop31, Is.EqualTo(5));
|
||||
Assert.That(deserialized.Prop8.Prop32, Is.EqualTo("101"));
|
||||
}
|
||||
|
||||
[TestCase("[\"4.803E-5\",\"9.623e-4\",\"0.0010025\"]", 0.00004803, 0.0009623, 0.0010025)]
|
||||
[TestCase("[\"81251.5\",\"1E+2\",\"-2.5E-3\"]", 81251.5, 100, -0.0025)]
|
||||
[TestCase("[4.803E-5,9.623e-4,0.0010025]", 0.00004803, 0.0009623, 0.0010025)]
|
||||
public void TestArrayConverterDecimalInScientificNotation(string json, decimal expected1, decimal expected2, decimal expected3)
|
||||
{
|
||||
// Some exchanges send small prices as strings in scientific notation, for example the
|
||||
// Kucoin futures kline stream: "candles":["1789830000","4.803E-5","4.807E-5",...]
|
||||
var deserialized = JsonSerializer.Deserialize<TestDecimal>(json);
|
||||
|
||||
Assert.That(deserialized!.Prop1, Is.EqualTo(expected1));
|
||||
Assert.That(deserialized.Prop2, Is.EqualTo(expected2));
|
||||
Assert.That(deserialized.Prop3, Is.EqualTo(expected3));
|
||||
}
|
||||
}
|
||||
|
||||
[JsonConverter(typeof(ArrayConverter<TestDecimal>))]
|
||||
public record TestDecimal
|
||||
{
|
||||
[ArrayProperty(0)]
|
||||
public decimal Prop1 { get; set; }
|
||||
[ArrayProperty(1)]
|
||||
public decimal? Prop2 { get; set; }
|
||||
[ArrayProperty(2)]
|
||||
public decimal Prop3 { get; set; }
|
||||
}
|
||||
|
||||
[JsonConverter(typeof(ArrayConverter<Test>))]
|
||||
|
||||
@@ -286,7 +286,7 @@ namespace CryptoExchange.Net.Clients
|
||||
while (true)
|
||||
{
|
||||
// Get a new or existing socket connection
|
||||
var socketResult = await GetSocketConnection(url, subscription.Authenticated, false, ct, subscription.Topic, subscription.IndividualSubscriptionCount).ConfigureAwait(false);
|
||||
var socketResult = await GetSocketConnection(url, subscription.Authenticated, false, ct, subscription, subscription.Topic, subscription.IndividualSubscriptionCount).ConfigureAwait(false);
|
||||
if (!socketResult.Success)
|
||||
return WebSocketResult.Fail<UpdateSubscription>(Exchange, socketResult.Error);
|
||||
|
||||
@@ -309,7 +309,7 @@ namespace CryptoExchange.Net.Clients
|
||||
|
||||
var needsConnecting = !socketConnection.Connected;
|
||||
|
||||
var connectResult = await ConnectIfNeededAsync(socketConnection, subscription.Authenticated, ct).ConfigureAwait(false);
|
||||
var connectResult = await ConnectIfNeededAsync(socketConnection, subscription.Authenticated, ct, subscription).ConfigureAwait(false);
|
||||
if (!connectResult.Success)
|
||||
return WebSocketResult.Fail<UpdateSubscription>(Exchange, connectResult.Error!);
|
||||
|
||||
@@ -482,7 +482,7 @@ namespace CryptoExchange.Net.Clients
|
||||
await semaphoreSlim.WaitAsync().ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
var socketResult = await GetSocketConnection(url, query.Authenticated, true, ct).ConfigureAwait(false);
|
||||
var socketResult = await GetSocketConnection(url, query.Authenticated, true, ct, processor: query).ConfigureAwait(false);
|
||||
if (!socketResult.Success)
|
||||
return QueryResult.Fail<THandlerResponse>(Exchange, socketResult.Error);
|
||||
|
||||
@@ -495,7 +495,7 @@ namespace CryptoExchange.Net.Clients
|
||||
released = true;
|
||||
}
|
||||
|
||||
var connectResult = await ConnectIfNeededAsync(socketConnection, query.Authenticated, ct).ConfigureAwait(false);
|
||||
var connectResult = await ConnectIfNeededAsync(socketConnection, query.Authenticated, ct, query).ConfigureAwait(false);
|
||||
if (!connectResult.Success)
|
||||
return QueryResult.Fail<THandlerResponse>(Exchange, connectResult.Error!);
|
||||
}
|
||||
@@ -524,7 +524,18 @@ namespace CryptoExchange.Net.Clients
|
||||
/// <param name="authenticated">Whether the socket should authenticated</param>
|
||||
/// <param name="ct">Cancellation token</param>
|
||||
/// <returns></returns>
|
||||
protected virtual async Task<CallResult> ConnectIfNeededAsync(ISocketConnection socket, bool authenticated, CancellationToken ct)
|
||||
protected virtual Task<CallResult> ConnectIfNeededAsync(ISocketConnection socket, bool authenticated, CancellationToken ct)
|
||||
=> ConnectIfNeededAsync(socket, authenticated, ct, null);
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a socket needs to be connected and does so if needed. Also authenticates on the socket if needed
|
||||
/// </summary>
|
||||
/// <param name="socket">The connection to check</param>
|
||||
/// <param name="authenticated">Whether the socket should authenticated</param>
|
||||
/// <param name="processor">The message processor requesting a connection</param>
|
||||
/// <param name="ct">Cancellation token</param>
|
||||
/// <returns></returns>
|
||||
protected virtual async Task<CallResult> ConnectIfNeededAsync(ISocketConnection socket, bool authenticated, CancellationToken ct, IMessageProcessor? processor)
|
||||
{
|
||||
if (socket.Connected)
|
||||
return CallResult.Ok();
|
||||
@@ -533,16 +544,13 @@ namespace CryptoExchange.Net.Clients
|
||||
if (!connectResult.Success)
|
||||
return connectResult;
|
||||
|
||||
if (ClientOptions.DelayAfterConnect != TimeSpan.Zero)
|
||||
await Task.Delay(ClientOptions.DelayAfterConnect).ConfigureAwait(false);
|
||||
|
||||
if (!authenticated || socket.Authenticated)
|
||||
return CallResult.Ok();
|
||||
|
||||
if (socket is not SocketConnection sc)
|
||||
throw new InvalidOperationException("HighPerfSocketConnection not supported for authentication");
|
||||
|
||||
var result = await AuthenticateSocketAsync(sc).ConfigureAwait(false);
|
||||
var result = await AuthenticateSocketAsync(sc, processor).ConfigureAwait(false);
|
||||
if (!result.Success)
|
||||
await socket.CloseAsync().ConfigureAwait(false);
|
||||
|
||||
@@ -554,13 +562,22 @@ namespace CryptoExchange.Net.Clients
|
||||
/// </summary>
|
||||
/// <param name="socket">Socket to authenticate</param>
|
||||
/// <returns></returns>
|
||||
public virtual async Task<CallResult> AuthenticateSocketAsync(SocketConnection socket)
|
||||
public virtual Task<CallResult> AuthenticateSocketAsync(SocketConnection socket)
|
||||
=> AuthenticateSocketAsync(socket, null);
|
||||
|
||||
/// <summary>
|
||||
/// Authenticate a socket connection
|
||||
/// </summary>
|
||||
/// <param name="socket">Socket to authenticate</param>
|
||||
/// <param name="processor">The message processor requesting authentication</param>
|
||||
/// <returns></returns>
|
||||
public virtual async Task<CallResult> AuthenticateSocketAsync(SocketConnection socket, IMessageProcessor? processor)
|
||||
{
|
||||
if (GetAuthenticationProvider() == null)
|
||||
return CallResult.Fail(new NoApiCredentialsError());
|
||||
|
||||
_logger.AttemptingToAuthenticate(socket.SocketId);
|
||||
var authRequest = await GetAuthenticationRequestAsync(socket).ConfigureAwait(false);
|
||||
var authRequest = await GetAuthenticationRequestAsync(socket, processor).ConfigureAwait(false);
|
||||
if (authRequest != null)
|
||||
{
|
||||
var result = await socket.SendAndWaitQueryAsync(authRequest).ConfigureAwait(false);
|
||||
@@ -581,13 +598,21 @@ namespace CryptoExchange.Net.Clients
|
||||
return CallResult.Ok();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Should return the request which can be used to authenticate a socket connection
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
protected internal virtual Task<Query?> GetAuthenticationRequestAsync(SocketConnection connection) =>
|
||||
protected internal virtual Task<Query?> GetAuthenticationRequestAsync(SocketConnection connection) =>
|
||||
Task.FromResult(GetAuthenticationProvider()!.GetAuthenticationQuery(this, connection));
|
||||
|
||||
/// <summary>
|
||||
/// Should return the request which can be used to authenticate a socket connection
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
protected internal virtual Task<Query?> GetAuthenticationRequestAsync(SocketConnection connection, IMessageProcessor? processor) =>
|
||||
GetAuthenticationRequestAsync(connection);
|
||||
|
||||
/// <summary>
|
||||
/// Adds a system subscription. Used for example to reply to ping requests
|
||||
/// </summary>
|
||||
@@ -645,6 +670,18 @@ namespace CryptoExchange.Net.Clients
|
||||
&& (AllowTopicsOnTheSameConnection || !connection.Topics.Contains(topic));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether the connection can be used for a new subscription or query with the provided parameters
|
||||
/// </summary>
|
||||
/// <param name="connection">The connection to check</param>
|
||||
/// <param name="address">The address set by the request</param>
|
||||
/// <param name="authenticated">Whether the request needs an authenticated connection</param>
|
||||
/// <param name="processor">The processor requesting a connection</param>
|
||||
/// <param name="topic">Topic of the request</param>
|
||||
/// <returns>True if connection can be used</returns>
|
||||
protected virtual bool ConnectionCanBeUsedFor(SocketConnection connection, string address, bool authenticated, IMessageProcessor? processor, string? topic = null)
|
||||
=> ConnectionCanBeUsedFor(connection, address, authenticated, topic);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a connection for a new subscription or query. Can be an existing if there are open position or a new one.
|
||||
/// </summary>
|
||||
@@ -655,15 +692,36 @@ namespace CryptoExchange.Net.Clients
|
||||
/// <param name="topic">The subscription topic, can be provided when multiple of the same topics are not allowed on a connection</param>
|
||||
/// <param name="individualSubscriptionCount">The number of individual subscriptions in this subscribe request</param>
|
||||
/// <returns></returns>
|
||||
protected virtual Task<CallResult<SocketConnection>> GetSocketConnection(
|
||||
string address,
|
||||
bool authenticated,
|
||||
bool dedicatedRequestConnection,
|
||||
CancellationToken ct,
|
||||
string? topic = null,
|
||||
int individualSubscriptionCount = 1)
|
||||
=> GetSocketConnection(address, authenticated, dedicatedRequestConnection, ct, null, topic, individualSubscriptionCount);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a connection for a new subscription or query. Can be an existing if there are open position or a new one.
|
||||
/// </summary>
|
||||
/// <param name="address">The address the socket is for</param>
|
||||
/// <param name="authenticated">Whether the socket should be authenticated</param>
|
||||
/// <param name="dedicatedRequestConnection">Whether a dedicated request connection should be returned</param>
|
||||
/// <param name="ct">Cancellation token</param>
|
||||
/// <param name="topic">The subscription topic, can be provided when multiple of the same topics are not allowed on a connection</param>
|
||||
/// <param name="individualSubscriptionCount">The number of individual subscriptions in this subscribe request</param>
|
||||
/// <param name="processor">The processor requesting a connection</param>
|
||||
/// <returns></returns>
|
||||
protected virtual async Task<CallResult<SocketConnection>> GetSocketConnection(
|
||||
string address,
|
||||
bool authenticated,
|
||||
bool dedicatedRequestConnection,
|
||||
CancellationToken ct,
|
||||
IMessageProcessor? processor,
|
||||
string? topic = null,
|
||||
int individualSubscriptionCount = 1)
|
||||
{
|
||||
var socketQuery = _socketConnections.Where(s => ConnectionCanBeUsedFor(s.Value, address, authenticated, topic)).Select(x => x.Value); // Don't ToList this so the query is executed again when called
|
||||
var socketQuery = _socketConnections.Where(s => ConnectionCanBeUsedFor(s.Value, address, authenticated, processor, topic)).Select(x => x.Value); // Don't ToList this so the query is executed again when called
|
||||
|
||||
// If all current socket connections are reconnecting or resubscribing wait for that to finish as we can probably use the existing connection
|
||||
var delayStart = DateTime.UtcNow;
|
||||
@@ -862,7 +920,7 @@ namespace CryptoExchange.Net.Clients
|
||||
RateLimitAdmissionCallbackRequest = () => AdmissionOverride.Value,
|
||||
Proxy = ClientOptions.Proxy,
|
||||
Timeout = ApiOptions.SocketNoDataTimeout ?? ClientOptions.SocketNoDataTimeout,
|
||||
ReceiveBufferSize = ClientOptions.ReceiveBufferSize,
|
||||
ReceiveBufferSize = ClientOptions.ReceiveBufferSize
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -173,6 +173,9 @@ namespace CryptoExchange.Net.Converters.SystemTextJson
|
||||
|
||||
if (targetType.IsAssignableFrom(value?.GetType()))
|
||||
attribute.PropertyInfo.SetValue(result, value);
|
||||
else if (value is string stringValue && targetType == typeof(decimal))
|
||||
// Convert.ChangeType doesn't accept scientific notation, which some exchanges use for small values ("4.803E-5")
|
||||
attribute.PropertyInfo.SetValue(result, ExchangeHelpers.ParseDecimal(stringValue));
|
||||
else
|
||||
attribute.PropertyInfo.SetValue(result, value == null ? null : Convert.ChangeType(value, targetType, CultureInfo.InvariantCulture));
|
||||
}
|
||||
|
||||
@@ -6,9 +6,9 @@
|
||||
<PackageId>CryptoExchange.Net</PackageId>
|
||||
<Authors>JKorf</Authors>
|
||||
<Description>CryptoExchange.Net is a base library which is used to implement different cryptocurrency (exchange) API's. It provides a standardized way of implementing different API's, which results in a very similar experience for users of the API implementations.</Description>
|
||||
<PackageVersion>12.5.1</PackageVersion>
|
||||
<AssemblyVersion>12.5.1</AssemblyVersion>
|
||||
<FileVersion>12.5.1</FileVersion>
|
||||
<PackageVersion>13.2.0</PackageVersion>
|
||||
<AssemblyVersion>13.2.0</AssemblyVersion>
|
||||
<FileVersion>13.2.0</FileVersion>
|
||||
<PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance>
|
||||
<PackageTags>OKX;OKX.Net;Mexc;Mexc.Net;Kucoin;Kucoin.Net;Kraken;Kraken.Net;Huobi;Huobi.Net;CoinEx;CoinEx.Net;Bybit;Bybit.Net;Bitget;Bitget.Net;Bitfinex;Bitfinex.Net;Binance;Binance.Net;CryptoCurrency;CryptoCurrency Exchange;CryptoExchange.Net</PackageTags>
|
||||
<RepositoryType>git</RepositoryType>
|
||||
|
||||
@@ -19,6 +19,11 @@ namespace CryptoExchange.Net.Objects.Options
|
||||
/// </summary>
|
||||
public int? MaxSocketConnections { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The time to wait after connecting a socket before sending messages. Can be used for API's which will rate limit if you subscribe directly after connecting.
|
||||
/// </summary>
|
||||
public TimeSpan? DelayAfterConnect { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Set the values of this options on the target options
|
||||
/// </summary>
|
||||
@@ -28,6 +33,7 @@ namespace CryptoExchange.Net.Objects.Options
|
||||
item.SocketNoDataTimeout = SocketNoDataTimeout;
|
||||
item.AutoTimestamp = AutoTimestamp;
|
||||
item.MaxSocketConnections = MaxSocketConnections;
|
||||
item.DelayAfterConnect = DelayAfterConnect;
|
||||
return item;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,7 +97,7 @@ public record CallResult<T> : CallResult, ICallResult<T>
|
||||
/// Call result for an exchange
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Data type</typeparam>
|
||||
public record ExchangeCallResult<T> : CallResult<T>
|
||||
public record ExchangeCallResult<T> : CallResult<T>, IExchangeCallResult<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// Exchange name
|
||||
|
||||
@@ -10,12 +10,8 @@ namespace CryptoExchange.Net.Objects
|
||||
/// <summary>
|
||||
/// HTTP call result
|
||||
/// </summary>
|
||||
public interface IHttpResult : ICallResult
|
||||
public interface IHttpResult : ICallResult, IExchangeCallResult
|
||||
{
|
||||
/// <summary>
|
||||
/// Exchange name
|
||||
/// </summary>
|
||||
string Exchange { get; init; }
|
||||
/// <summary>
|
||||
/// The original data returned by the call, only available when `OutputOriginalData` is set to `true` in the client options
|
||||
/// </summary>
|
||||
|
||||
@@ -10,13 +10,8 @@ namespace CryptoExchange.Net.Objects
|
||||
/// <summary>
|
||||
/// WebSocket call result
|
||||
/// </summary>
|
||||
public interface IWebSocketResult : ICallResult
|
||||
public interface IWebSocketResult : ICallResult, IExchangeCallResult
|
||||
{
|
||||
/// <summary>
|
||||
/// Exchange name
|
||||
/// </summary>
|
||||
string Exchange { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// The request id
|
||||
/// </summary>
|
||||
|
||||
@@ -77,8 +77,8 @@ namespace CryptoExchange.Net.RateLimiting.Guards
|
||||
/// <param name="decayPerTimeSpan">The decay per timespan if windowType is DecayWindowTracker</param>
|
||||
/// <param name="connectionWeight">The weight of a new connection</param>
|
||||
/// <param name="shared">Whether this guard is shared between multiple gates</param>
|
||||
public RateLimitGuard(Func<RequestDefinition, string?, string> keySelector, IGuardFilter filter, int limit, TimeSpan timeSpan, RateLimitWindowType windowType, TimeSpan? safetyMargin = null, double? decayPerTimeSpan = null, int? connectionWeight = null, bool shared = false)
|
||||
: this(keySelector, new[] { filter }, limit, timeSpan, windowType, safetyMargin, decayPerTimeSpan, connectionWeight, shared)
|
||||
public RateLimitGuard(Func<RequestDefinition, string?, string> keySelector, IGuardFilter filter, int limit, TimeSpan timeSpan, RateLimitWindowType windowType, double? decayPerTimeSpan = null, int? connectionWeight = null, bool shared = false, TimeSpan? safetyMargin = null)
|
||||
: this(keySelector, new[] { filter }, limit, timeSpan, windowType, decayPerTimeSpan, connectionWeight, shared, safetyMargin)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -94,7 +94,7 @@ namespace CryptoExchange.Net.RateLimiting.Guards
|
||||
/// <param name="decayPerTimeSpan">The decay per timespan if windowType is DecayWindowTracker</param>
|
||||
/// <param name="connectionWeight">The weight of a new connection</param>
|
||||
/// <param name="shared">Whether this guard is shared between multiple gates</param>
|
||||
public RateLimitGuard(Func<RequestDefinition, string?, string> keySelector, IEnumerable<IGuardFilter> filters, int limit, TimeSpan timeSpan, RateLimitWindowType windowType, TimeSpan? safetyMargin = null, double? decayPerTimeSpan = null, int? connectionWeight = null, bool shared = false)
|
||||
public RateLimitGuard(Func<RequestDefinition, string?, string> keySelector, IEnumerable<IGuardFilter> filters, int limit, TimeSpan timeSpan, RateLimitWindowType windowType, double? decayPerTimeSpan = null, int? connectionWeight = null, bool shared = false, TimeSpan? safetyMargin = null)
|
||||
{
|
||||
_filters = filters;
|
||||
_trackers = new Dictionary<string, IWindowTracker>();
|
||||
|
||||
@@ -48,8 +48,8 @@ namespace CryptoExchange.Net.RateLimiting.Guards
|
||||
TimeSpan period,
|
||||
RateLimitWindowType windowType,
|
||||
TimeSpan? safetyMargin = null,
|
||||
double? decayRate = null,
|
||||
Func<RequestDefinition, string?, string>? keySelector = null)
|
||||
Func<RequestDefinition, string?, string>? keySelector = null,
|
||||
double? decayRate = null)
|
||||
{
|
||||
_limit = limit;
|
||||
_period = period;
|
||||
|
||||
@@ -23,6 +23,10 @@ namespace CryptoExchange.Net.SharedApis
|
||||
/// Get user funding history capability.
|
||||
/// </summary>
|
||||
public static SharedRestCapability<IGetUserFundingHistory, IGetUserFundingHistoryRest> GetUserFundingHistory { get; } = new();
|
||||
/// <summary>
|
||||
/// Subscribe to funding info updates capability.
|
||||
/// </summary>
|
||||
public static SharedCapabilityReference<ISubscribeFundingInfoSocket> SubscribeFundingInfo { get; } = new();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
using CryptoExchange.Net.Interfaces;
|
||||
|
||||
namespace CryptoExchange.Net.SharedApis
|
||||
{
|
||||
/// <summary>
|
||||
/// Order book info
|
||||
/// </summary>
|
||||
public record SharedIncrementalOrderBook : SharedOrderBook
|
||||
{
|
||||
/// <summary>
|
||||
/// The sequence number of the first book update in this update.
|
||||
/// </summary>
|
||||
public long? StartSequenceNumber { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// ctor
|
||||
/// </summary>
|
||||
public SharedIncrementalOrderBook(SharedQuantityType quantityType, long? startSequenceNumber, long? endSequenceNumber, ISymbolOrderBookEntry[] asks, ISymbolOrderBookEntry[] bids)
|
||||
:base(quantityType, endSequenceNumber, asks, bids)
|
||||
{
|
||||
StartSequenceNumber = startSequenceNumber;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,6 +2,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace CryptoExchange.Net.SharedApis
|
||||
{
|
||||
@@ -104,6 +105,11 @@ namespace CryptoExchange.Net.SharedApis
|
||||
SharedCapabilityResolution<T>? GetCapability<T>(SharedCapabilityReference<T> capability, TradingMode tradingMode)
|
||||
where T : ISharedApiCapability;
|
||||
|
||||
/// <summary>
|
||||
/// Unsubscribes all subscriptions created by the underlying socket API
|
||||
/// client and closes its subscription connections.
|
||||
/// </summary>
|
||||
Task UnsubscribeAllAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -209,6 +215,15 @@ namespace CryptoExchange.Net.SharedApis
|
||||
return GetCapabilities<T>(tradingMode);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task UnsubscribeAllAsync()
|
||||
{
|
||||
return Task.WhenAll(
|
||||
_sharedApis
|
||||
.OfType<ISharedSubscription>()
|
||||
.Select(x => x.UnsubscribeAllAsync()));
|
||||
}
|
||||
|
||||
object? ISharedApiClientResolver.GetCapability(Type capabilityType)
|
||||
{
|
||||
return _sharedApis
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
using CryptoExchange.Net.Objects;
|
||||
using CryptoExchange.Net.Objects.Sockets;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace CryptoExchange.Net.SharedApis
|
||||
{
|
||||
@@ -78,6 +80,474 @@ namespace CryptoExchange.Net.SharedApis
|
||||
return services;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Execute GetAssetAsync for all capabilities in parallel and return results as they arrive
|
||||
/// </summary>
|
||||
public static IAsyncEnumerable<IExchangeCallResult<SharedAsset>> ExecuteAllAsync(
|
||||
this IEnumerable<SharedCapabilityResolution<IGetAsset>> capabilities,
|
||||
GetAssetRequest request,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
return capabilities
|
||||
.Select(x => x.Capability.GetAssetAsync(request, ct))
|
||||
.ParallelEnumerateAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Execute GetAssetAsync for all capabilities in parallel and return results as they arrive
|
||||
/// </summary>
|
||||
public static IAsyncEnumerable<HttpResult<SharedAsset>> ExecuteAllAsync(
|
||||
this IEnumerable<SharedCapabilityResolution<IGetAssetRest>> capabilities,
|
||||
GetAssetRequest request,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
return capabilities
|
||||
.Select(x => x.Capability.GetAssetAsync(request, ct))
|
||||
.ParallelEnumerateAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Execute GetAllAssetsAsync for all capabilities in parallel and return results as they arrive
|
||||
/// </summary>
|
||||
public static IAsyncEnumerable<IExchangeCallResult<SharedAsset[]>> ExecuteAllAsync(
|
||||
this IEnumerable<SharedCapabilityResolution<IGetAllAssets>> capabilities,
|
||||
GetAssetsRequest request,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
return capabilities
|
||||
.Select(x => x.Capability.GetAllAssetsAsync(request, ct))
|
||||
.ParallelEnumerateAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Execute GetAllAssetsAsync for all capabilities in parallel and return results as they arrive
|
||||
/// </summary>
|
||||
public static IAsyncEnumerable<HttpResult<SharedAsset[]>> ExecuteAllAsync(
|
||||
this IEnumerable<SharedCapabilityResolution<IGetAllAssetsRest>> capabilities,
|
||||
GetAssetsRequest request,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
return capabilities
|
||||
.Select(x => x.Capability.GetAllAssetsAsync(request, ct))
|
||||
.ParallelEnumerateAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Execute GetBalancesAsync for all capabilities in parallel and return results as they arrive
|
||||
/// </summary>
|
||||
public static IAsyncEnumerable<HttpResult<SharedBalance[]>> ExecuteAllAsync(
|
||||
this IEnumerable<SharedCapabilityResolution<IGetBalancesRest>> capabilities,
|
||||
GetBalancesRequest request,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
return capabilities
|
||||
.Select(x => x.Capability.GetBalancesAsync(request, ct))
|
||||
.ParallelEnumerateAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Execute GetBalancesAsync for all capabilities in parallel and return results as they arrive
|
||||
/// </summary>
|
||||
public static IAsyncEnumerable<IExchangeCallResult<SharedBalance[]>> ExecuteAllAsync(
|
||||
this IEnumerable<SharedCapabilityResolution<IGetBalances>> capabilities,
|
||||
GetBalancesRequest request,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
return capabilities
|
||||
.Select(x => x.Capability.GetBalancesAsync(request, ct))
|
||||
.ParallelEnumerateAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Execute GetFeesAsync for all capabilities in parallel and return results as they arrive
|
||||
/// </summary>
|
||||
public static IAsyncEnumerable<HttpResult<SharedFee>> ExecuteAllAsync(
|
||||
this IEnumerable<SharedCapabilityResolution<IGetFeesRest>> capabilities,
|
||||
GetFeeRequest request,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
return capabilities
|
||||
.Select(x => x.Capability.GetFeesAsync(request, ct))
|
||||
.ParallelEnumerateAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Execute GetFeesAsync for all capabilities in parallel and return results as they arrive
|
||||
/// </summary>
|
||||
public static IAsyncEnumerable<IExchangeCallResult<SharedFee>> ExecuteAllAsync(
|
||||
this IEnumerable<SharedCapabilityResolution<IGetFees>> capabilities,
|
||||
GetFeeRequest request,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
return capabilities
|
||||
.Select(x => x.Capability.GetFeesAsync(request, ct))
|
||||
.ParallelEnumerateAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Execute GetFundingInfoAsync for all capabilities in parallel and return results as they arrive
|
||||
/// </summary>
|
||||
public static IAsyncEnumerable<HttpResult<SharedFundingInfo>> ExecuteAllAsync(
|
||||
this IEnumerable<SharedCapabilityResolution<IGetFundingInfoRest>> capabilities,
|
||||
GetFundingInfoRequest request,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
return capabilities
|
||||
.Select(x => x.Capability.GetFundingInfoAsync(request, ct))
|
||||
.ParallelEnumerateAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Execute GetFundingInfoAsync for all capabilities in parallel and return results as they arrive
|
||||
/// </summary>
|
||||
public static IAsyncEnumerable<IExchangeCallResult<SharedFundingInfo>> ExecuteAllAsync(
|
||||
this IEnumerable<SharedCapabilityResolution<IGetFundingInfo>> capabilities,
|
||||
GetFundingInfoRequest request,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
return capabilities
|
||||
.Select(x => x.Capability.GetFundingInfoAsync(request, ct))
|
||||
.ParallelEnumerateAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Execute GetIndexPriceAsync for all capabilities in parallel and return results as they arrive
|
||||
/// </summary>
|
||||
public static IAsyncEnumerable<HttpResult<SharedIndexPrice>> ExecuteAllAsync(
|
||||
this IEnumerable<SharedCapabilityResolution<IGetIndexPriceRest>> capabilities,
|
||||
GetIndexPriceRequest request,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
return capabilities
|
||||
.Select(x => x.Capability.GetIndexPriceAsync(request, ct))
|
||||
.ParallelEnumerateAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Execute GetIndexPriceAsync for all capabilities in parallel and return results as they arrive
|
||||
/// </summary>
|
||||
public static IAsyncEnumerable<IExchangeCallResult<SharedIndexPrice>> ExecuteAllAsync(
|
||||
this IEnumerable<SharedCapabilityResolution<IGetIndexPrice>> capabilities,
|
||||
GetIndexPriceRequest request,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
return capabilities
|
||||
.Select(x => x.Capability.GetIndexPriceAsync(request, ct))
|
||||
.ParallelEnumerateAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Execute GetMarkPriceAsync for all capabilities in parallel and return results as they arrive
|
||||
/// </summary>
|
||||
public static IAsyncEnumerable<HttpResult<SharedMarkPrice>> ExecuteAllAsync(
|
||||
this IEnumerable<SharedCapabilityResolution<IGetMarkPriceRest>> capabilities,
|
||||
GetMarkPriceRequest request,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
return capabilities
|
||||
.Select(x => x.Capability.GetMarkPriceAsync(request, ct))
|
||||
.ParallelEnumerateAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Execute GetMarkPriceAsync for all capabilities in parallel and return results as they arrive
|
||||
/// </summary>
|
||||
public static IAsyncEnumerable<IExchangeCallResult<SharedMarkPrice>> ExecuteAllAsync(
|
||||
this IEnumerable<SharedCapabilityResolution<IGetMarkPrice>> capabilities,
|
||||
GetMarkPriceRequest request,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
return capabilities
|
||||
.Select(x => x.Capability.GetMarkPriceAsync(request, ct))
|
||||
.ParallelEnumerateAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Execute GetAllMarkPricesAsync for all capabilities in parallel and return results as they arrive
|
||||
/// </summary>
|
||||
public static IAsyncEnumerable<HttpResult<SharedMarkPrice[]>> ExecuteAllAsync(
|
||||
this IEnumerable<SharedCapabilityResolution<IGetAllMarkPricesRest>> capabilities,
|
||||
GetAllMarkPricesRequest request,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
return capabilities
|
||||
.Select(x => x.Capability.GetAllMarkPricesAsync(request, ct))
|
||||
.ParallelEnumerateAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Execute GetAllMarkPricesAsync for all capabilities in parallel and return results as they arrive
|
||||
/// </summary>
|
||||
public static IAsyncEnumerable<IExchangeCallResult<SharedMarkPrice[]>> ExecuteAllAsync(
|
||||
this IEnumerable<SharedCapabilityResolution<IGetAllMarkPrices>> capabilities,
|
||||
GetAllMarkPricesRequest request,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
return capabilities
|
||||
.Select(x => x.Capability.GetAllMarkPricesAsync(request, ct))
|
||||
.ParallelEnumerateAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Execute GetAllIndexPricesAsync for all capabilities in parallel and return results as they arrive
|
||||
/// </summary>
|
||||
public static IAsyncEnumerable<HttpResult<SharedIndexPrice[]>> ExecuteAllAsync(
|
||||
this IEnumerable<SharedCapabilityResolution<IGetAllIndexPricesRest>> capabilities,
|
||||
GetAllIndexPricesRequest request,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
return capabilities
|
||||
.Select(x => x.Capability.GetAllIndexPricesAsync(request, ct))
|
||||
.ParallelEnumerateAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Execute GetAllIndexPricesAsync for all capabilities in parallel and return results as they arrive
|
||||
/// </summary>
|
||||
public static IAsyncEnumerable<IExchangeCallResult<SharedIndexPrice[]>> ExecuteAllAsync(
|
||||
this IEnumerable<SharedCapabilityResolution<IGetAllIndexPrices>> capabilities,
|
||||
GetAllIndexPricesRequest request,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
return capabilities
|
||||
.Select(x => x.Capability.GetAllIndexPricesAsync(request, ct))
|
||||
.ParallelEnumerateAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Execute GetLeverageAsync for all capabilities in parallel and return results as they arrive
|
||||
/// </summary>
|
||||
public static IAsyncEnumerable<HttpResult<SharedLeverage>> ExecuteAllAsync(
|
||||
this IEnumerable<SharedCapabilityResolution<IGetLeverageRest>> capabilities,
|
||||
GetLeverageRequest request,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
return capabilities
|
||||
.Select(x => x.Capability.GetLeverageAsync(request, ct))
|
||||
.ParallelEnumerateAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Execute GetLeverageAsync for all capabilities in parallel and return results as they arrive
|
||||
/// </summary>
|
||||
public static IAsyncEnumerable<IExchangeCallResult<SharedLeverage>> ExecuteAllAsync(
|
||||
this IEnumerable<SharedCapabilityResolution<IGetLeverage>> capabilities,
|
||||
GetLeverageRequest request,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
return capabilities
|
||||
.Select(x => x.Capability.GetLeverageAsync(request, ct))
|
||||
.ParallelEnumerateAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Execute GetLeverageTiersAsync for all capabilities in parallel and return results as they arrive
|
||||
/// </summary>
|
||||
public static IAsyncEnumerable<HttpResult<SharedLeverageTier[]>> ExecuteAllAsync(
|
||||
this IEnumerable<SharedCapabilityResolution<IGetLeverageTiersRest>> capabilities,
|
||||
GetLeverageTiersRequest request,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
return capabilities
|
||||
.Select(x => x.Capability.GetLeverageTiersAsync(request, ct))
|
||||
.ParallelEnumerateAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Execute GetLeverageTiersAsync for all capabilities in parallel and return results as they arrive
|
||||
/// </summary>
|
||||
public static IAsyncEnumerable<IExchangeCallResult<SharedLeverageTier[]>> ExecuteAllAsync(
|
||||
this IEnumerable<SharedCapabilityResolution<IGetLeverageTiers>> capabilities,
|
||||
GetLeverageTiersRequest request,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
return capabilities
|
||||
.Select(x => x.Capability.GetLeverageTiersAsync(request, ct))
|
||||
.ParallelEnumerateAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Execute GetOpenInterestAsync for all capabilities in parallel and return results as they arrive
|
||||
/// </summary>
|
||||
public static IAsyncEnumerable<HttpResult<SharedOpenInterest>> ExecuteAllAsync(
|
||||
this IEnumerable<SharedCapabilityResolution<IGetOpenInterestRest>> capabilities,
|
||||
GetOpenInterestRequest request,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
return capabilities
|
||||
.Select(x => x.Capability.GetOpenInterestAsync(request, ct))
|
||||
.ParallelEnumerateAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Execute GetOpenInterestAsync for all capabilities in parallel and return results as they arrive
|
||||
/// </summary>
|
||||
public static IAsyncEnumerable<IExchangeCallResult<SharedOpenInterest>> ExecuteAllAsync(
|
||||
this IEnumerable<SharedCapabilityResolution<IGetOpenInterest>> capabilities,
|
||||
GetOpenInterestRequest request,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
return capabilities
|
||||
.Select(x => x.Capability.GetOpenInterestAsync(request, ct))
|
||||
.ParallelEnumerateAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Execute GetBookTickerAsync for all capabilities in parallel and return results as they arrive
|
||||
/// </summary>
|
||||
public static IAsyncEnumerable<HttpResult<SharedBookTicker>> ExecuteAllAsync(
|
||||
this IEnumerable<SharedCapabilityResolution<IGetBookTickerRest>> capabilities,
|
||||
GetBookTickerRequest request,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
return capabilities
|
||||
.Select(x => x.Capability.GetBookTickerAsync(request, ct))
|
||||
.ParallelEnumerateAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Execute GetBookTickerAsync for all capabilities in parallel and return results as they arrive
|
||||
/// </summary>
|
||||
public static IAsyncEnumerable<IExchangeCallResult<SharedBookTicker>> ExecuteAllAsync(
|
||||
this IEnumerable<SharedCapabilityResolution<IGetBookTicker>> capabilities,
|
||||
GetBookTickerRequest request,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
return capabilities
|
||||
.Select(x => x.Capability.GetBookTickerAsync(request, ct))
|
||||
.ParallelEnumerateAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Execute GetOrderBookAsync for all capabilities in parallel and return results as they arrive
|
||||
/// </summary>
|
||||
public static IAsyncEnumerable<HttpResult<SharedOrderBook>> ExecuteAllAsync(
|
||||
this IEnumerable<SharedCapabilityResolution<IGetOrderBookRest>> capabilities,
|
||||
GetOrderBookRequest request,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
return capabilities
|
||||
.Select(x => x.Capability.GetOrderBookAsync(request, ct))
|
||||
.ParallelEnumerateAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Execute GetOrderBookAsync for all capabilities in parallel and return results as they arrive
|
||||
/// </summary>
|
||||
public static IAsyncEnumerable<IExchangeCallResult<SharedOrderBook>> ExecuteAllAsync(
|
||||
this IEnumerable<SharedCapabilityResolution<IGetOrderBook>> capabilities,
|
||||
GetOrderBookRequest request,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
return capabilities
|
||||
.Select(x => x.Capability.GetOrderBookAsync(request, ct))
|
||||
.ParallelEnumerateAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Execute GetPositionModeAsync for all capabilities in parallel and return results as they arrive
|
||||
/// </summary>
|
||||
public static IAsyncEnumerable<HttpResult<SharedPositionModeResult>> ExecuteAllAsync(
|
||||
this IEnumerable<SharedCapabilityResolution<IGetPositionModeRest>> capabilities,
|
||||
GetPositionModeRequest request,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
return capabilities
|
||||
.Select(x => x.Capability.GetPositionModeAsync(request, ct))
|
||||
.ParallelEnumerateAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Execute GetPositionModeAsync for all capabilities in parallel and return results as they arrive
|
||||
/// </summary>
|
||||
public static IAsyncEnumerable<IExchangeCallResult<SharedPositionModeResult>> ExecuteAllAsync(
|
||||
this IEnumerable<SharedCapabilityResolution<IGetPositionMode>> capabilities,
|
||||
GetPositionModeRequest request,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
return capabilities
|
||||
.Select(x => x.Capability.GetPositionModeAsync(request, ct))
|
||||
.ParallelEnumerateAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Execute GetPositionsAsync for all capabilities in parallel and return results as they arrive
|
||||
/// </summary>
|
||||
public static IAsyncEnumerable<HttpResult<SharedPosition[]>> ExecuteAllAsync(
|
||||
this IEnumerable<SharedCapabilityResolution<IGetPositionsRest>> capabilities,
|
||||
GetPositionsRequest request,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
return capabilities
|
||||
.Select(x => x.Capability.GetPositionsAsync(request, ct))
|
||||
.ParallelEnumerateAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Execute GetPositionsAsync for all capabilities in parallel and return results as they arrive
|
||||
/// </summary>
|
||||
public static IAsyncEnumerable<IExchangeCallResult<SharedPosition[]>> ExecuteAllAsync(
|
||||
this IEnumerable<SharedCapabilityResolution<IGetPositions>> capabilities,
|
||||
GetPositionsRequest request,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
return capabilities
|
||||
.Select(x => x.Capability.GetPositionsAsync(request, ct))
|
||||
.ParallelEnumerateAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Execute GetSpotSymbolsAsync for all capabilities in parallel and return results as they arrive
|
||||
/// </summary>
|
||||
public static IAsyncEnumerable<HttpResult<SharedSpotSymbol[]>> ExecuteAllAsync(
|
||||
this IEnumerable<SharedCapabilityResolution<IGetSpotSymbolsRest>> capabilities,
|
||||
GetSymbolsRequest request,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
return capabilities
|
||||
.Select(x => x.Capability.GetSpotSymbolsAsync(request, ct))
|
||||
.ParallelEnumerateAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Execute GetSpotSymbolsAsync for all capabilities in parallel and return results as they arrive
|
||||
/// </summary>
|
||||
public static IAsyncEnumerable<IExchangeCallResult<SharedSpotSymbol[]>> ExecuteAllAsync(
|
||||
this IEnumerable<SharedCapabilityResolution<IGetSpotSymbols>> capabilities,
|
||||
GetSymbolsRequest request,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
return capabilities
|
||||
.Select(x => x.Capability.GetSpotSymbolsAsync(request, ct))
|
||||
.ParallelEnumerateAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Execute GetFuturesSymbolsAsync for all capabilities in parallel and return results as they arrive
|
||||
/// </summary>
|
||||
public static IAsyncEnumerable<HttpResult<SharedFuturesSymbol[]>> ExecuteAllAsync(
|
||||
this IEnumerable<SharedCapabilityResolution<IGetFuturesSymbolsRest>> capabilities,
|
||||
GetSymbolsRequest request,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
return capabilities
|
||||
.Select(x => x.Capability.GetFuturesSymbolsAsync(request, ct))
|
||||
.ParallelEnumerateAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Execute GetFuturesSymbolsAsync for all capabilities in parallel and return results as they arrive
|
||||
/// </summary>
|
||||
public static IAsyncEnumerable<IExchangeCallResult<SharedFuturesSymbol[]>> ExecuteAllAsync(
|
||||
this IEnumerable<SharedCapabilityResolution<IGetFuturesSymbols>> capabilities,
|
||||
GetSymbolsRequest request,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
return capabilities
|
||||
.Select(x => x.Capability.GetFuturesSymbolsAsync(request, ct))
|
||||
.ParallelEnumerateAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Execute GetTickerAsync for all capabilities in parallel and return results as they arrive
|
||||
/// </summary>
|
||||
@@ -91,6 +561,19 @@ namespace CryptoExchange.Net.SharedApis
|
||||
.ParallelEnumerateAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Execute GetTickerAsync for all capabilities in parallel and return results as they arrive
|
||||
/// </summary>
|
||||
public static IAsyncEnumerable<IExchangeCallResult<SharedTicker>> ExecuteAllAsync(
|
||||
this IEnumerable<SharedCapabilityResolution<IGetTicker>> capabilities,
|
||||
GetTickerRequest request,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
return capabilities
|
||||
.Select(x => x.Capability.GetTickerAsync(request, ct))
|
||||
.ParallelEnumerateAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Execute GetAllTickersAsync for all capabilities in parallel and return results as they arrive
|
||||
/// </summary>
|
||||
@@ -105,55 +588,250 @@ namespace CryptoExchange.Net.SharedApis
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Execute GetAssetAsync for all capabilities in parallel and return results as they arrive
|
||||
/// Execute GetAllTickersAsync for all capabilities in parallel and return results as they arrive
|
||||
/// </summary>
|
||||
public static IAsyncEnumerable<HttpResult<SharedAsset>> ExecuteAllAsync(
|
||||
this IEnumerable<SharedCapabilityResolution<IGetAssetRest>> capabilities,
|
||||
GetAssetRequest request,
|
||||
public static IAsyncEnumerable<IExchangeCallResult<SharedTicker[]>> ExecuteAllAsync(
|
||||
this IEnumerable<SharedCapabilityResolution<IGetAllTickers>> capabilities,
|
||||
GetTickersRequest request,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
return capabilities
|
||||
.Select(x => x.Capability.GetAssetAsync(request, ct))
|
||||
.Select(x => x.Capability.GetAllTickersAsync(request, ct))
|
||||
.ParallelEnumerateAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Execute GetAllAssetsAsync for all capabilities in parallel and return results as they arrive
|
||||
/// Execute GetRecentTradesAsync for all capabilities in parallel and return results as they arrive
|
||||
/// </summary>
|
||||
public static IAsyncEnumerable<HttpResult<SharedAsset[]>> ExecuteAllAsync(
|
||||
this IEnumerable<SharedCapabilityResolution<IGetAllAssetsRest>> capabilities,
|
||||
GetAssetsRequest request,
|
||||
public static IAsyncEnumerable<HttpResult<SharedTrade[]>> ExecuteAllAsync(
|
||||
this IEnumerable<SharedCapabilityResolution<IGetRecentTradesRest>> capabilities,
|
||||
GetRecentTradesRequest request,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
return capabilities
|
||||
.Select(x => x.Capability.GetAllAssetsAsync(request, ct))
|
||||
.Select(x => x.Capability.GetRecentTradesAsync(request, ct))
|
||||
.ParallelEnumerateAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Execute GetAllBalancesAsync for all capabilities in parallel and return results as they arrive
|
||||
/// Execute GetRecentTradesAsync for all capabilities in parallel and return results as they arrive
|
||||
/// </summary>
|
||||
public static IAsyncEnumerable<HttpResult<SharedBalance[]>> ExecuteAllAsync(
|
||||
this IEnumerable<SharedCapabilityResolution<IGetBalancesRest>> capabilities,
|
||||
GetBalancesRequest request,
|
||||
public static IAsyncEnumerable<IExchangeCallResult<SharedTrade[]>> ExecuteAllAsync(
|
||||
this IEnumerable<SharedCapabilityResolution<IGetRecentTrades>> capabilities,
|
||||
GetRecentTradesRequest request,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
return capabilities
|
||||
.Select(x => x.Capability.GetBalancesAsync(request, ct))
|
||||
.Select(x => x.Capability.GetRecentTradesAsync(request, ct))
|
||||
.ParallelEnumerateAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Execute GetAllBalancesAsync for all capabilities in parallel and return results as they arrive
|
||||
/// Subscribe to trade updates for all capabilities in parallel and return results as they arrive
|
||||
/// </summary>
|
||||
public static IAsyncEnumerable<ICallResult<SharedBalance[]>> ExecuteAllAsync(
|
||||
this IEnumerable<SharedCapabilityResolution<IGetBalances>> capabilities,
|
||||
GetBalancesRequest request,
|
||||
public static IAsyncEnumerable<WebSocketResult<UpdateSubscription>> SubscribeAllAsync(
|
||||
this IEnumerable<SharedCapabilityResolution<ISubscribeTradesSocket>> capabilities,
|
||||
SubscribeTradeRequest request,
|
||||
Action<DataEvent<SharedTrade[]>> onData,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
return capabilities
|
||||
.Select(x => x.Capability.GetBalancesAsync(request, ct))
|
||||
.Select(x => x.Capability.SubscribeToTradeUpdatesAsync(request, onData, ct))
|
||||
.ParallelEnumerateAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Subscribe to balance updates for all capabilities in parallel and return results as they arrive
|
||||
/// </summary>
|
||||
public static IAsyncEnumerable<WebSocketResult<UpdateSubscription>> SubscribeAllAsync(
|
||||
this IEnumerable<SharedCapabilityResolution<ISubscribeBalancesSocket>> capabilities,
|
||||
SubscribeBalancesRequest request,
|
||||
Action<DataEvent<SharedBalance[]>> onData,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
return capabilities
|
||||
.Select(x => x.Capability.SubscribeToBalanceUpdatesAsync(request, onData, ct))
|
||||
.ParallelEnumerateAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Subscribe to index price updates for all capabilities in parallel and return results as they arrive
|
||||
/// </summary>
|
||||
public static IAsyncEnumerable<WebSocketResult<UpdateSubscription>> SubscribeAllAsync(
|
||||
this IEnumerable<SharedCapabilityResolution<ISubscribeIndexPriceSocket>> capabilities,
|
||||
SubscribeIndexPriceRequest request,
|
||||
Action<DataEvent<SharedIndexPrice>> onData,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
return capabilities
|
||||
.Select(x => x.Capability.SubscribeToIndexPriceUpdatesAsync(request, onData, ct))
|
||||
.ParallelEnumerateAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Subscribe to funding info updates for all capabilities in parallel and return results as they arrive
|
||||
/// </summary>
|
||||
public static IAsyncEnumerable<WebSocketResult<UpdateSubscription>> SubscribeAllAsync(
|
||||
this IEnumerable<SharedCapabilityResolution<ISubscribeFundingInfoSocket>> capabilities,
|
||||
SubscribeFundingInfoRequest request,
|
||||
Action<DataEvent<SharedFundingInfo>> onData,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
return capabilities
|
||||
.Select(x => x.Capability.SubscribeToFundingInfoUpdatesAsync(request, onData, ct))
|
||||
.ParallelEnumerateAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Subscribe to kline updates for all capabilities in parallel and return results as they arrive
|
||||
/// </summary>
|
||||
public static IAsyncEnumerable<WebSocketResult<UpdateSubscription>> SubscribeAllAsync(
|
||||
this IEnumerable<SharedCapabilityResolution<ISubscribeKlinesSocket>> capabilities,
|
||||
SubscribeKlineRequest request,
|
||||
Action<DataEvent<SharedKline>> onData,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
return capabilities
|
||||
.Select(x => x.Capability.SubscribeToKlineUpdatesAsync(request, onData, ct))
|
||||
.ParallelEnumerateAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Subscribe to mark price updates for all capabilities in parallel and return results as they arrive
|
||||
/// </summary>
|
||||
public static IAsyncEnumerable<WebSocketResult<UpdateSubscription>> SubscribeAllAsync(
|
||||
this IEnumerable<SharedCapabilityResolution<ISubscribeMarkPriceSocket>> capabilities,
|
||||
SubscribeMarkPriceRequest request,
|
||||
Action<DataEvent<SharedMarkPrice>> onData,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
return capabilities
|
||||
.Select(x => x.Capability.SubscribeToMarkPriceUpdatesAsync(request, onData, ct))
|
||||
.ParallelEnumerateAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Subscribe to order book updates for all capabilities in parallel and return results as they arrive
|
||||
/// </summary>
|
||||
public static IAsyncEnumerable<WebSocketResult<UpdateSubscription>> SubscribeAllAsync(
|
||||
this IEnumerable<SharedCapabilityResolution<ISubscribeOrderBookSocket>> capabilities,
|
||||
SubscribeOrderBookRequest request,
|
||||
Action<DataEvent<SharedOrderBook>> onData,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
return capabilities
|
||||
.Select(x => x.Capability.SubscribeToOrderBookUpdatesAsync(request, onData, ct))
|
||||
.ParallelEnumerateAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Subscribe to incremental order book updates for all capabilities in parallel and return results as they arrive
|
||||
/// </summary>
|
||||
public static IAsyncEnumerable<WebSocketResult<UpdateSubscription>> SubscribeAllAsync(
|
||||
this IEnumerable<SharedCapabilityResolution<ISubscribeIncrementalOrderBookSocket>> capabilities,
|
||||
SubscribeOrderBookRequest request,
|
||||
Action<DataEvent<SharedIncrementalOrderBook>> onData,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
return capabilities
|
||||
.Select(x => x.Capability.SubscribeToIncrementalOrderBookUpdatesAsync(request, onData, ct))
|
||||
.ParallelEnumerateAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Subscribe to futures order updates for all capabilities in parallel and return results as they arrive
|
||||
/// </summary>
|
||||
public static IAsyncEnumerable<WebSocketResult<UpdateSubscription>> SubscribeAllAsync(
|
||||
this IEnumerable<SharedCapabilityResolution<ISubscribeFuturesOrdersSocket>> capabilities,
|
||||
SubscribeFuturesOrderRequest request,
|
||||
Action<DataEvent<SharedFuturesOrderUpdate[]>> onData,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
return capabilities
|
||||
.Select(x => x.Capability.SubscribeToFuturesOrderUpdatesAsync(request, onData, ct))
|
||||
.ParallelEnumerateAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Subscribe to spot order updates for all capabilities in parallel and return results as they arrive
|
||||
/// </summary>
|
||||
public static IAsyncEnumerable<WebSocketResult<UpdateSubscription>> SubscribeAllAsync(
|
||||
this IEnumerable<SharedCapabilityResolution<ISubscribeSpotOrdersSocket>> capabilities,
|
||||
SubscribeSpotOrderRequest request,
|
||||
Action<DataEvent<SharedSpotOrderUpdate[]>> onData,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
return capabilities
|
||||
.Select(x => x.Capability.SubscribeToSpotOrderUpdatesAsync(request, onData, ct))
|
||||
.ParallelEnumerateAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Subscribe to position updates for all capabilities in parallel and return results as they arrive
|
||||
/// </summary>
|
||||
public static IAsyncEnumerable<WebSocketResult<UpdateSubscription>> SubscribeAllAsync(
|
||||
this IEnumerable<SharedCapabilityResolution<ISubscribePositionsSocket>> capabilities,
|
||||
SubscribePositionRequest request,
|
||||
Action<DataEvent<SharedPosition[]>> onData,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
return capabilities
|
||||
.Select(x => x.Capability.SubscribeToPositionUpdatesAsync(request, onData, ct))
|
||||
.ParallelEnumerateAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Subscribe to ticker updates for all capabilities in parallel and return results as they arrive
|
||||
/// </summary>
|
||||
public static IAsyncEnumerable<WebSocketResult<UpdateSubscription>> SubscribeAllAsync(
|
||||
this IEnumerable<SharedCapabilityResolution<ISubscribeTickerSocket>> capabilities,
|
||||
SubscribeTickerRequest request,
|
||||
Action<DataEvent<SharedTicker>> onData,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
return capabilities
|
||||
.Select(x => x.Capability.SubscribeToTickerUpdatesAsync(request, onData, ct))
|
||||
.ParallelEnumerateAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Subscribe to all ticker updates for all capabilities in parallel and return results as they arrive
|
||||
/// </summary>
|
||||
public static IAsyncEnumerable<WebSocketResult<UpdateSubscription>> SubscribeAllAsync(
|
||||
this IEnumerable<SharedCapabilityResolution<ISubscribeAllTickersSocket>> capabilities,
|
||||
SubscribeAllTickersRequest request,
|
||||
Action<DataEvent<SharedTicker[]>> onData,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
return capabilities
|
||||
.Select(x => x.Capability.SubscribeToAllTickersUpdatesAsync(request, onData, ct))
|
||||
.ParallelEnumerateAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Subscribe to user trade updates for all capabilities in parallel and return results as they arrive
|
||||
/// </summary>
|
||||
public static IAsyncEnumerable<WebSocketResult<UpdateSubscription>> SubscribeAllAsync(
|
||||
this IEnumerable<SharedCapabilityResolution<ISubscribeUserTradesSocket>> capabilities,
|
||||
SubscribeUserTradeRequest request,
|
||||
Action<DataEvent<SharedUserTrade[]>> onData,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
return capabilities
|
||||
.Select(x => x.Capability.SubscribeToUserTradeUpdatesAsync(request, onData, ct))
|
||||
.ParallelEnumerateAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wait for all results from an IAsyncEnumerable and return them as an array
|
||||
/// </summary>
|
||||
public static async Task<T[]> WaitAllAsync<T>(this IAsyncEnumerable<T> enumerable)
|
||||
{
|
||||
var result = new List<T>();
|
||||
await foreach (var itemResult in enumerable.ConfigureAwait(false))
|
||||
result.Add(itemResult);
|
||||
|
||||
return result.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
using CryptoExchange.Net.Objects;
|
||||
using CryptoExchange.Net.Objects.Sockets;
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace CryptoExchange.Net.SharedApis
|
||||
{
|
||||
/// <summary>
|
||||
/// Operation for subscribing to funding info updates
|
||||
/// </summary>
|
||||
public interface ISubscribeFundingInfoSocket : ISharedSubscription
|
||||
{
|
||||
/// <summary>
|
||||
/// Funding info subscription options
|
||||
/// </summary>
|
||||
SubscribeFundingInfoOptions SubscribeFundingInfoOptions { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Subscribe to funding info updates
|
||||
/// </summary>
|
||||
/// <param name="request">Request info</param>
|
||||
/// <param name="handler">Update handler</param>
|
||||
/// <param name="ct">Cancellation token, can be used to stop the updates</param>
|
||||
/// <returns></returns>
|
||||
Task<WebSocketResult<UpdateSubscription>> SubscribeToFundingInfoUpdatesAsync(SubscribeFundingInfoRequest request, Action<DataEvent<SharedFundingInfo>> handler, CancellationToken ct = default);
|
||||
}
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
using CryptoExchange.Net.Objects;
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace CryptoExchange.Net.SharedApis
|
||||
{
|
||||
/// <summary>
|
||||
/// Options for subscribing to funding info updates
|
||||
/// </summary>
|
||||
public class SubscribeFundingInfoOptions : CapabilityOptions<SubscribeFundingInfoRequest, ISubscribeFundingInfoSocket>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override string Description => "Subscribe to funding info updates for a symbol";
|
||||
|
||||
private static readonly RequestParameterDescription[] _defaultParameterRules = new[]
|
||||
{
|
||||
RequestParameterRule<SubscribeFundingInfoRequest>.Optional(x => x.Symbol, "The symbol to subscribe to", new SharedSymbol(TradingMode.PerpetualLinear, "ETH", "USDT")),
|
||||
RequestParameterRule<SubscribeFundingInfoRequest>.Optional(x => x.Symbols, "The symbols to subscribe to", new[] { new SharedSymbol(TradingMode.PerpetualLinear, "ETH", "USDT") }),
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// ctor
|
||||
/// </summary>
|
||||
public SubscribeFundingInfoOptions(string exchange, bool needsAuthentication)
|
||||
: base(exchange, needsAuthentication, nameof(ISubscribeFundingInfoSocket.SubscribeToFundingInfoUpdatesAsync), _defaultParameterRules, SharedTradingModeSets.Futures)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace CryptoExchange.Net.SharedApis
|
||||
{
|
||||
/// <summary>
|
||||
/// Request to subscribe to funding info updates for a symbol
|
||||
/// </summary>
|
||||
public record SubscribeFundingInfoRequest : SharedSymbolRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// ctor
|
||||
/// </summary>
|
||||
/// <param name="symbol">The symbol to subscribe to</param>
|
||||
/// <param name="exchangeParameters">Exchange specific parameters</param>
|
||||
public SubscribeFundingInfoRequest(SharedSymbol symbol, ExchangeParameters? exchangeParameters = null)
|
||||
: base(symbol, exchangeParameters)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ctor
|
||||
/// </summary>
|
||||
/// <param name="symbols">The symbols to subscribe to</param>
|
||||
/// <param name="exchangeParameters">Exchange specific parameters</param>
|
||||
public SubscribeFundingInfoRequest(IEnumerable<SharedSymbol> symbols, ExchangeParameters? exchangeParameters = null)
|
||||
: base(symbols, exchangeParameters)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,19 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace CryptoExchange.Net.SharedApis
|
||||
{
|
||||
/// <summary>
|
||||
/// Shared WebSocket API subscription capabitlity
|
||||
/// Shared WebSocket API subscription capability
|
||||
/// </summary>
|
||||
public interface ISharedSubscription : ISharedSocket
|
||||
{
|
||||
/// <summary>
|
||||
/// Unsubscribes all subscriptions created by the underlying socket API
|
||||
/// client and closes its subscription connections.
|
||||
/// </summary>
|
||||
Task UnsubscribeAllAsync();
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -14,7 +14,7 @@ namespace CryptoExchange.Net.SharedApis
|
||||
/// <summary>
|
||||
/// Order book subscription options
|
||||
/// </summary>
|
||||
SubscribeOrderBookOptions SubscribeOrderBookOptions { get; }
|
||||
SubscribeIncrementalOrderBookOptions SubscribeIncrementalOrderBookOptions { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Subscribe to incremental order book updates for a symbol
|
||||
@@ -23,6 +23,6 @@ namespace CryptoExchange.Net.SharedApis
|
||||
/// <param name="handler">Update handler</param>
|
||||
/// <param name="ct">Cancellation token, can be used to stop the updates</param>
|
||||
/// <returns></returns>
|
||||
Task<WebSocketResult<UpdateSubscription>> SubscribeToOrderBookUpdatesAsync(SubscribeOrderBookRequest request, Action<DataEvent<SharedOrderBook>> handler, CancellationToken ct = default);
|
||||
Task<WebSocketResult<UpdateSubscription>> SubscribeToIncrementalOrderBookUpdatesAsync(SubscribeOrderBookRequest request, Action<DataEvent<SharedIncrementalOrderBook>> handler, CancellationToken ct = default);
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -33,7 +33,7 @@ namespace CryptoExchange.Net.SharedApis
|
||||
/// ctor
|
||||
/// </summary>
|
||||
public SubscribeIncrementalOrderBookOptions(string exchange, bool needsAuthentication, int[] limits, SharedOrderBookSubscriptionType updateType)
|
||||
: base(exchange, needsAuthentication, nameof(ISubscribeIncrementalOrderBookSocket.SubscribeToOrderBookUpdatesAsync), _defaultParameterRules)
|
||||
: base(exchange, needsAuthentication, nameof(ISubscribeIncrementalOrderBookSocket.SubscribeToIncrementalOrderBookUpdatesAsync), _defaultParameterRules)
|
||||
{
|
||||
SupportedLimits = limits;
|
||||
UpdateType = updateType;
|
||||
|
||||
@@ -47,7 +47,7 @@ namespace CryptoExchange.Net.SharedApis
|
||||
/// </summary>
|
||||
/// <param name="request">Request info</param>
|
||||
/// <param name="ct">Cancellation token</param>
|
||||
Task<ICallResult<SharedFuturesSymbol[]>> GetFuturesSymbolsAsync(GetSymbolsRequest request, CancellationToken ct = default);
|
||||
Task<IExchangeCallResult<SharedFuturesSymbol[]>> GetFuturesSymbolsAsync(GetSymbolsRequest request, CancellationToken ct = default);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -47,7 +47,7 @@ namespace CryptoExchange.Net.SharedApis
|
||||
/// </summary>
|
||||
/// <param name="request">Request info</param>
|
||||
/// <param name="ct">Cancellation token</param>
|
||||
Task<ICallResult<SharedSpotSymbol[]>> GetSpotSymbolsAsync(GetSymbolsRequest request, CancellationToken ct = default);
|
||||
Task<IExchangeCallResult<SharedSpotSymbol[]>> GetSpotSymbolsAsync(GetSymbolsRequest request, CancellationToken ct = default);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -424,6 +424,9 @@ namespace CryptoExchange.Net.Sockets.Default
|
||||
{
|
||||
try
|
||||
{
|
||||
if ((ApiClient.ApiOptions.DelayAfterConnect ?? ApiClient.ClientOptions.DelayAfterConnect) != TimeSpan.Zero)
|
||||
await Task.Delay(ApiClient.ApiOptions.DelayAfterConnect ?? ApiClient.ClientOptions.DelayAfterConnect).ConfigureAwait(false);
|
||||
|
||||
var reconnectSuccessful = await ProcessReconnectAsync().ConfigureAwait(false);
|
||||
if (!reconnectSuccessful.Success)
|
||||
{
|
||||
@@ -616,7 +619,17 @@ namespace CryptoExchange.Net.Sockets.Default
|
||||
/// Connect the websocket
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public async Task<CallResult> ConnectAsync(CancellationToken ct) => await _socket.ConnectAsync(ct).ConfigureAwait(false);
|
||||
public async Task<CallResult> ConnectAsync(CancellationToken ct)
|
||||
{
|
||||
var result = await _socket.ConnectAsync(ct).ConfigureAwait(false);
|
||||
if (!result.Success)
|
||||
return result;
|
||||
|
||||
if ((ApiClient.ApiOptions.DelayAfterConnect ?? ApiClient.ClientOptions.DelayAfterConnect) != TimeSpan.Zero)
|
||||
await Task.Delay(ApiClient.ApiOptions.DelayAfterConnect ?? ApiClient.ClientOptions.DelayAfterConnect).ConfigureAwait(false);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieve the underlying socket
|
||||
@@ -689,7 +702,7 @@ namespace CryptoExchange.Net.Sockets.Default
|
||||
if (subscription.CancellationTokenRegistration.HasValue)
|
||||
subscription.CancellationTokenRegistration.Value.Dispose();
|
||||
|
||||
bool anyDuplicateSubscription = _listeners.OfType<Subscription>().Any(x => x != subscription && x.MessageRouter.Routes.All(l => subscription.MessageRouter.ContainsCheck(l)));
|
||||
bool anyDuplicateSubscription = SubscriptionIsCovered(subscription);
|
||||
bool shouldCloseConnection = _listeners.OfType<Subscription>().All(r => !r.UserSubscription || r.Status == SubscriptionStatus.Closing || r.Status == SubscriptionStatus.Closed) && !DedicatedRequestConnection.IsDedicatedRequestConnection;
|
||||
|
||||
if (!anyDuplicateSubscription)
|
||||
@@ -1056,6 +1069,32 @@ namespace CryptoExchange.Net.Sockets.Default
|
||||
return CallResult.Ok();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether there is currently a subscription covering all the routes the subscription lists
|
||||
/// </summary>
|
||||
protected bool SubscriptionIsCovered(Subscription subscription)
|
||||
=> _listeners.OfType<Subscription>().Any(
|
||||
x => x != subscription
|
||||
&& x.Active
|
||||
&& subscription.MessageRouter.Routes.All(l => x.MessageRouter.ContainsCheck(l)));
|
||||
|
||||
/// <summary>
|
||||
/// Whether there is a subscription earlier in the list covering all the routes the subscription lists
|
||||
/// </summary>
|
||||
protected bool IsCoveredByEarlierSubscription(Subscription subscription)
|
||||
{
|
||||
foreach (var other in _listeners.OfType<Subscription>())
|
||||
{
|
||||
if (other == subscription)
|
||||
return false;
|
||||
|
||||
if (other.Active && subscription.MessageRouter.Routes.All(x => other.MessageRouter.ContainsCheck(x)))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Try to subscribe a new subscription by sending the subscribe query and wait for the result as needed
|
||||
/// </summary>
|
||||
@@ -1090,11 +1129,11 @@ namespace CryptoExchange.Net.Sockets.Default
|
||||
return WebSocketResult.Ok(ApiClient.Exchange, SocketId, default, 0, ConnectionUriString);
|
||||
}
|
||||
|
||||
var subCompleteHandler = () =>
|
||||
var subCompleteHandler = (bool success) =>
|
||||
{
|
||||
subscription.Status = subQuery.Success ? SubscriptionStatus.Subscribed : SubscriptionStatus.Pending;
|
||||
subscription.Status = success ? SubscriptionStatus.Subscribed : SubscriptionStatus.Pending;
|
||||
subscription.HandleSubQueryResponse(this, subQuery.Response);
|
||||
if (newSubscription && subQuery.Success)
|
||||
if (newSubscription && success)
|
||||
{
|
||||
subscription.TokenExpired += HandleTokenExpired;
|
||||
if (subCancelToken != default)
|
||||
@@ -1109,6 +1148,14 @@ namespace CryptoExchange.Net.Sockets.Default
|
||||
};
|
||||
subQuery.OnComplete = subCompleteHandler;
|
||||
|
||||
// For duplicate subscription don't send another subscribe request but just handle it internally
|
||||
// Unsubscribe also doesn't unsubscribe when there is still a duplicate subscription running
|
||||
if (IsCoveredByEarlierSubscription(subscription))
|
||||
{
|
||||
subQuery.Complete();
|
||||
return WebSocketResult.Ok(ApiClient.Exchange, SocketId, default, subQuery.Id, ConnectionUriString);
|
||||
}
|
||||
|
||||
var subQueryResult = await SendAndWaitQueryAsync(subQuery).ConfigureAwait(false);
|
||||
if (!subQueryResult.Success)
|
||||
{
|
||||
@@ -1119,9 +1166,6 @@ namespace CryptoExchange.Net.Sockets.Default
|
||||
return WebSocketResult.Fail(ApiClient.Exchange, subQueryResult.Error!);
|
||||
}
|
||||
|
||||
if (!subQuery.ExpectsResponse)
|
||||
subCompleteHandler();
|
||||
|
||||
return subQueryResult;
|
||||
}
|
||||
|
||||
|
||||
@@ -198,8 +198,13 @@ namespace CryptoExchange.Net.Sockets.HighPerf
|
||||
{
|
||||
var result = await _socket.ConnectAsync(ct).ConfigureAwait(false);
|
||||
if (result.Success)
|
||||
{
|
||||
_processTask = ProcessAsync(_cts.Token);
|
||||
|
||||
if ((ApiClient.ApiOptions.DelayAfterConnect ?? ApiClient.ClientOptions.DelayAfterConnect) != TimeSpan.Zero)
|
||||
await Task.Delay(ApiClient.ApiOptions.DelayAfterConnect ?? ApiClient.ClientOptions.DelayAfterConnect).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@@ -124,7 +124,7 @@ namespace CryptoExchange.Net.Sockets
|
||||
/// <summary>
|
||||
/// On complete callback
|
||||
/// </summary>
|
||||
public Action? OnComplete { get; set; }
|
||||
public Action<bool>? OnComplete { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public event Action? OnMessageRouterUpdated;
|
||||
@@ -157,8 +157,7 @@ namespace CryptoExchange.Net.Sockets
|
||||
}
|
||||
else
|
||||
{
|
||||
Completed = true;
|
||||
_event.Set();
|
||||
Complete();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -175,6 +174,11 @@ namespace CryptoExchange.Net.Sockets
|
||||
/// </summary>
|
||||
public abstract void Timeout();
|
||||
|
||||
/// <summary>
|
||||
/// Mark request as successfully completed
|
||||
/// </summary>
|
||||
public abstract void Complete();
|
||||
|
||||
/// <summary>
|
||||
/// Mark request as failed
|
||||
/// </summary>
|
||||
@@ -248,7 +252,7 @@ namespace CryptoExchange.Net.Sockets
|
||||
{
|
||||
Completed = true;
|
||||
_event.Set();
|
||||
OnComplete?.Invoke();
|
||||
OnComplete?.Invoke(Success);
|
||||
}
|
||||
|
||||
return handled;
|
||||
@@ -267,7 +271,19 @@ namespace CryptoExchange.Net.Sockets
|
||||
|
||||
Completed = true;
|
||||
_event.Set();
|
||||
OnComplete?.Invoke();
|
||||
OnComplete?.Invoke(Success);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Complete()
|
||||
{
|
||||
if (Completed)
|
||||
return;
|
||||
|
||||
Result = CallResult<THandlerResponse>.Ok(default!);
|
||||
Completed = true;
|
||||
_event.Set();
|
||||
OnComplete?.Invoke(Success);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -280,7 +296,7 @@ namespace CryptoExchange.Net.Sockets
|
||||
Completed = true;
|
||||
|
||||
_event.Set();
|
||||
OnComplete?.Invoke();
|
||||
OnComplete?.Invoke(Success);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,37 +5,36 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Binance.Net" Version="13.5.0" />
|
||||
<PackageReference Include="Bitfinex.Net" Version="11.4.0" />
|
||||
<PackageReference Include="BitMart.Net" Version="4.4.0" />
|
||||
<PackageReference Include="BloFin.Net" Version="3.4.0" />
|
||||
<PackageReference Include="Bybit.Net" Version="7.5.0" />
|
||||
<PackageReference Include="CoinEx.Net" Version="11.4.0" />
|
||||
<PackageReference Include="CoinW.Net" Version="3.4.0" />
|
||||
<PackageReference Include="CryptoCom.Net" Version="4.4.0" />
|
||||
<PackageReference Include="DeepCoin.Net" Version="4.4.0" />
|
||||
<PackageReference Include="GateIo.Net" Version="4.6.0" />
|
||||
<PackageReference Include="HyperLiquid.Net" Version="5.7.0" />
|
||||
<PackageReference Include="JK.BingX.Net" Version="4.5.0" />
|
||||
<PackageReference Include="JK.Bitget.Net" Version="4.5.0" />
|
||||
<PackageReference Include="JK.Mexc.Net" Version="6.6.0" />
|
||||
<PackageReference Include="JK.OKX.Net" Version="5.4.0" />
|
||||
<PackageReference Include="Jkorf.Aster.Net" Version="4.4.0" />
|
||||
<PackageReference Include="JKorf.BitMEX.Net" Version="4.4.0" />
|
||||
<PackageReference Include="JKorf.Coinbase.Net" Version="4.5.0" />
|
||||
<PackageReference Include="JKorf.HTX.Net" Version="9.5.0" />
|
||||
<PackageReference Include="JKorf.Lighter.Net" Version="1.5.0" />
|
||||
<PackageReference Include="JKorf.Upbit.Net" Version="3.4.0" />
|
||||
<PackageReference Include="KrakenExchange.Net" Version="8.4.0" />
|
||||
<PackageReference Include="Kucoin.Net" Version="9.5.0" />
|
||||
<PackageReference Include="LBank.Net" Version="1.1.0" />
|
||||
<PackageReference Include="Pionex.Net" Version="1.2.0" />
|
||||
<PackageReference Include="Binance.Net" Version="13.6.0" />
|
||||
<PackageReference Include="Bitfinex.Net" Version="11.6.0" />
|
||||
<PackageReference Include="BitMart.Net" Version="4.5.0" />
|
||||
<PackageReference Include="BloFin.Net" Version="3.5.0" />
|
||||
<PackageReference Include="Bybit.Net" Version="7.6.0" />
|
||||
<PackageReference Include="CoinEx.Net" Version="11.5.0" />
|
||||
<PackageReference Include="CoinW.Net" Version="3.5.0" />
|
||||
<PackageReference Include="CryptoCom.Net" Version="4.5.0" />
|
||||
<PackageReference Include="DeepCoin.Net" Version="4.5.0" />
|
||||
<PackageReference Include="GateIo.Net" Version="4.7.0" />
|
||||
<PackageReference Include="HyperLiquid.Net" Version="5.8.0" />
|
||||
<PackageReference Include="JK.BingX.Net" Version="4.6.0" />
|
||||
<PackageReference Include="JK.Bitget.Net" Version="4.6.0" />
|
||||
<PackageReference Include="JK.Mexc.Net" Version="6.7.0" />
|
||||
<PackageReference Include="JK.OKX.Net" Version="5.5.0" />
|
||||
<PackageReference Include="Jkorf.Aster.Net" Version="4.5.0" />
|
||||
<PackageReference Include="JKorf.Coinbase.Net" Version="4.6.0" />
|
||||
<PackageReference Include="JKorf.HTX.Net" Version="9.6.0" />
|
||||
<PackageReference Include="JKorf.Lighter.Net" Version="1.6.0" />
|
||||
<PackageReference Include="JKorf.Upbit.Net" Version="3.5.0" />
|
||||
<PackageReference Include="KrakenExchange.Net" Version="8.5.0" />
|
||||
<PackageReference Include="Kucoin.Net" Version="9.6.0" />
|
||||
<PackageReference Include="LBank.Net" Version="1.2.0" />
|
||||
<PackageReference Include="Pionex.Net" Version="1.3.0" />
|
||||
<PackageReference Include="Serilog.AspNetCore" Version="10.0.0" />
|
||||
<PackageReference Include="Tapbit.Net" Version="1.0.0" />
|
||||
<PackageReference Include="Toobit.Net" Version="4.4.0" />
|
||||
<PackageReference Include="Weex.Net" Version="2.4.0" />
|
||||
<PackageReference Include="WhiteBit.Net" Version="4.4.0" />
|
||||
<PackageReference Include="XT.Net" Version="4.5.0" />
|
||||
<PackageReference Include="Tapbit.Net" Version="1.1.0" />
|
||||
<PackageReference Include="Toobit.Net" Version="4.5.0" />
|
||||
<PackageReference Include="Weex.Net" Version="2.5.0" />
|
||||
<PackageReference Include="WhiteBit.Net" Version="4.5.0" />
|
||||
<PackageReference Include="XT.Net" Version="4.6.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
@inject IBitfinexRestClient bitfinexClient
|
||||
@inject IBitgetRestClient bitgetClient
|
||||
@inject IBitMartRestClient bitmartClient
|
||||
@inject IBitMEXRestClient bitmexClient
|
||||
@inject IBloFinRestClient bloFinClient
|
||||
@inject IBybitRestClient bybitClient
|
||||
@inject ICoinbaseRestClient coinbaseClient
|
||||
@@ -47,7 +46,6 @@
|
||||
var bitfinexTask = bitfinexClient.ExchangeApi.ExchangeData.GetTickerAsync("tBTCUSD");
|
||||
var bitgetTask = bitgetClient.SpotApiV2.ExchangeData.GetTickersAsync("BTCUSDT");
|
||||
var bitmartTask = bitmartClient.SpotApi.ExchangeData.GetTickerAsync("BTC_USDT");
|
||||
var bitmexTask = bitmexClient.ExchangeApi.ExchangeData.GetSymbolsAsync("XBT_USDT");
|
||||
var bloFinTask = bloFinClient.FuturesApi.ExchangeData.GetTickersAsync("BTC-USDT");
|
||||
var bybitTask = bybitClient.V5Api.ExchangeData.GetSpotTickersAsync("BTCUSDT");
|
||||
var coinbaseTask = coinbaseClient.AdvancedTradeApi.ExchangeData.GetSymbolAsync("BTC-USDT");
|
||||
@@ -71,7 +69,7 @@
|
||||
var whitebitTask = whitebitClient.V4Api.ExchangeData.GetTickersAsync();
|
||||
var xtTask = xtClient.SpotApi.ExchangeData.GetTickersAsync("btc_usdt");
|
||||
|
||||
await Task.WhenAll(asterTask, binanceTask, bingXTask, bitfinexTask, bitgetTask, bitmartTask, bloFinTask, bitmexTask, bybitTask, coinexTask, coinWTask, deepCoinTask, gateioTask,
|
||||
await Task.WhenAll(asterTask, binanceTask, bingXTask, bitfinexTask, bitgetTask, bitmartTask, bloFinTask, bybitTask, coinexTask, coinWTask, deepCoinTask, gateioTask,
|
||||
htxTask, krakenTask, kucoinTask, lBankTask, mexcTask, okxTask, pionexTask, toobitTask, upbitTask, weexTask, whitebitTask, xtTask);
|
||||
|
||||
if (asterTask.Result.Success)
|
||||
@@ -92,9 +90,6 @@
|
||||
if (bitmartTask.Result.Success)
|
||||
_prices.Add("BitMart", bitmartTask.Result.Data.LastPrice);
|
||||
|
||||
if (bitmexTask.Result.Success)
|
||||
_prices.Add("BitMEX", bitmexTask.Result.Data.First().LastPrice);
|
||||
|
||||
if (bloFinTask.Result.Success)
|
||||
_prices.Add("BloFin", bloFinTask.Result.Data.First().LastPrice);
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
@inject IBitfinexSocketClient bitfinexSocketClient
|
||||
@inject IBitgetSocketClient bitgetSocketClient
|
||||
@inject IBitMartSocketClient bitmartSocketClient
|
||||
@inject IBitMEXSocketClient bitmexSocketClient
|
||||
@inject IBloFinSocketClient bloFinSocketClient
|
||||
@inject IBybitSocketClient bybitSocketClient
|
||||
@inject ICoinbaseSocketClient coinbaseSocketClient
|
||||
@@ -56,7 +55,6 @@
|
||||
bitfinexSocketClient.ExchangeApi.SubscribeToTickerUpdatesAsync("tETHBTC", data => UpdateData("Bitfinex", data.Data.LastPrice)),
|
||||
bitgetSocketClient.SpotApiV2.SubscribeToTickerUpdatesAsync("ETHBTC", data => UpdateData("Bitget", data.Data.First().LastPrice)),
|
||||
bitmartSocketClient.SpotApi.SubscribeToTickerUpdatesAsync("ETH_BTC", data => UpdateData("BitMart", data.Data.LastPrice)),
|
||||
bitmexSocketClient.ExchangeApi.SubscribeToSymbolUpdatesAsync("ETH_XBT", data => UpdateData("BitMEX", data.Data.LastPrice ?? 0)),
|
||||
// BloFin doesn't support the ETH/BTC pair
|
||||
//bloFinSocketClient.FuturesApi.SubscribeToTickerUpdatesAsync("ETH-BTC", data => UpdateData("BloFin", data.Data.LastPrice)),
|
||||
bybitSocketClient.V5SpotApi.SubscribeToTickerUpdatesAsync("ETHBTC", data => UpdateData("Bybit", data.Data.LastPrice)),
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
@using Bitfinex.Net.Interfaces
|
||||
@using Bitget.Net.Interfaces;
|
||||
@using BitMart.Net.Interfaces;
|
||||
@using BitMEX.Net.Interfaces;
|
||||
@using BloFin.Net.Interfaces;
|
||||
@using Bybit.Net.Interfaces
|
||||
@using CoinEx.Net.Interfaces
|
||||
@@ -40,7 +39,6 @@
|
||||
@inject IBitfinexOrderBookFactory bitfinexFactory
|
||||
@inject IBitgetOrderBookFactory bitgetFactory
|
||||
@inject IBitMartOrderBookFactory bitmartFactory
|
||||
@inject IBitMEXOrderBookFactory bitmexFactory
|
||||
@inject IBloFinOrderBookFactory bloFinFactory
|
||||
@inject IBybitOrderBookFactory bybitFactory
|
||||
@inject ICoinbaseOrderBookFactory coinbaseFactory
|
||||
@@ -103,7 +101,6 @@
|
||||
{ "Bitfinex", bitfinexFactory.Create("tETHBTC") },
|
||||
{ "Bitget", bitgetFactory.CreateSpot("ETHBTC") },
|
||||
{ "BitMart", bitmartFactory.CreateSpot("ETH_BTC", null) },
|
||||
{ "BitMEX", bitmexFactory.Create("ETH_XBT") },
|
||||
{ "BloFin", bloFinFactory.CreateFutures("ETH-USDT") },
|
||||
{ "Bybit", bybitFactory.Create("ETHBTC", Bybit.Net.Enums.Category.Spot) },
|
||||
{ "Coinbase", coinbaseFactory.Create("ETH-BTC", null) },
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
@page "/SpotClient"
|
||||
@using CryptoExchange.Net.SharedApis
|
||||
@using System.Diagnostics
|
||||
@inject IEnumerable<ISpotTickerRestClient> restClients
|
||||
@inject IEnumerable<IGetTicker> restClients
|
||||
|
||||
<h3>ETH-BTC prices:</h3>
|
||||
@foreach(var price in _prices.OrderBy(p => p.Key))
|
||||
@@ -15,7 +15,7 @@
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
var symbol = new SharedSymbol(TradingMode.Spot, "ETH", "BTC");
|
||||
var tasks = restClients.Select(x => x.GetSpotTickerAsync(new GetTickerRequest(symbol)));
|
||||
var tasks = restClients.Where(x => x.SupportedTradingModes.Contains(TradingMode.Spot)).Select(x => x.GetTickerAsync(new GetTickerRequest(symbol)));
|
||||
await foreach (var ticker in ParallelEnumerateAsync(tasks))
|
||||
{
|
||||
if (ticker.Success)
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
@using BingX.Net.Interfaces
|
||||
@using Bitfinex.Net.Interfaces
|
||||
@using Bitget.Net.Interfaces;
|
||||
@using BitMEX.Net.Interfaces;
|
||||
@using BitMart.Net.Interfaces;
|
||||
@using BloFin.Net.Interfaces
|
||||
@using Bybit.Net.Interfaces
|
||||
@@ -40,7 +39,6 @@
|
||||
@inject IBitfinexTrackerFactory bitfinexFactory
|
||||
@inject IBitgetTrackerFactory bitgetFactory
|
||||
@inject IBitMartTrackerFactory bitmartFactory
|
||||
@inject IBitMEXTrackerFactory bitmexFactory
|
||||
@inject IBloFinTrackerFactory bloFinFactory
|
||||
@inject IBybitTrackerFactory bybitFactory
|
||||
@inject ICoinbaseTrackerFactory coinbaseFactory
|
||||
@@ -96,7 +94,6 @@
|
||||
{ bitfinexFactory.CreateTradeTracker(symbol, period: TimeSpan.FromMinutes(5)) },
|
||||
{ bitgetFactory.CreateTradeTracker(symbol, period: TimeSpan.FromMinutes(5)) },
|
||||
{ bitmartFactory.CreateTradeTracker(symbol, period: TimeSpan.FromMinutes(5)) },
|
||||
{ bitmexFactory.CreateTradeTracker(symbol, period: TimeSpan.FromMinutes(5)) },
|
||||
{ bloFinFactory.CreateTradeTracker(futuresSymbol, period: TimeSpan.FromMinutes(5)) },
|
||||
{ bybitFactory.CreateTradeTracker(symbol, period: TimeSpan.FromMinutes(5)) },
|
||||
{ coinbaseFactory.CreateTradeTracker(symbol, period: TimeSpan.FromMinutes(5)) },
|
||||
|
||||
@@ -39,7 +39,6 @@ namespace BlazorClient
|
||||
services.AddBitfinex();
|
||||
services.AddBitget();
|
||||
services.AddBitMart();
|
||||
services.AddBitMEX();
|
||||
services.AddBloFin();
|
||||
services.AddBybit();
|
||||
services.AddCoinbase();
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
@using Bitfinex.Net.Interfaces.Clients;
|
||||
@using Bitget.Net.Interfaces.Clients;
|
||||
@using BitMart.Net.Interfaces.Clients;
|
||||
@using BitMEX.Net.Interfaces.Clients;
|
||||
@using BloFin.Net.Interfaces.Clients;
|
||||
@using Bybit.Net.Interfaces.Clients;
|
||||
@using Coinbase.Net.Interfaces.Clients;
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Binance.Net" Version="12.13.0" />
|
||||
<PackageReference Include="Bybit.Net" Version="6.13.0" />
|
||||
<PackageReference Include="Binance.Net" Version="13.6.0" />
|
||||
<PackageReference Include="Bybit.Net" Version="7.6.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -25,18 +25,22 @@ var exchanges = new Dictionary<string, ExchangeClients>(StringComparer.OrdinalIg
|
||||
{
|
||||
["binance"] = new ExchangeClients(
|
||||
"Binance",
|
||||
binanceRest.SpotApi.SharedClient,
|
||||
binanceRest.SpotApi.SharedClient,
|
||||
binanceRest.SpotApi.SharedClient,
|
||||
binanceRest.SpotApi.SharedClient,
|
||||
binanceSocket.SpotApi.SharedClient),
|
||||
binanceRest.SpotApi.SharedApi,
|
||||
binanceRest.SpotApi.SharedApi,
|
||||
binanceRest.SpotApi.SharedApi,
|
||||
binanceRest.SpotApi.SharedApi,
|
||||
binanceRest.SpotApi.SharedApi,
|
||||
binanceRest.SpotApi.SharedApi,
|
||||
binanceSocket.SpotApi.SharedApi),
|
||||
["bybit"] = new ExchangeClients(
|
||||
"Bybit",
|
||||
bybitRest.V5Api.SharedClient,
|
||||
bybitRest.V5Api.SharedClient,
|
||||
bybitRest.V5Api.SharedClient,
|
||||
bybitRest.V5Api.SharedClient,
|
||||
bybitSocket.V5SpotApi.SharedClient)
|
||||
bybitRest.V5Api.SharedApi,
|
||||
bybitRest.V5Api.SharedApi,
|
||||
bybitRest.V5Api.SharedApi,
|
||||
bybitRest.V5Api.SharedApi,
|
||||
bybitRest.V5Api.SharedApi,
|
||||
bybitRest.V5Api.SharedApi,
|
||||
bybitSocket.V5SpotApi.SharedApi)
|
||||
};
|
||||
|
||||
PrintHelp();
|
||||
@@ -98,7 +102,7 @@ async Task PrintPricesAsync(string[] args)
|
||||
var symbol = GetSymbol(args, 1);
|
||||
var tasks = exchanges.Values.Select(async exchange =>
|
||||
{
|
||||
var result = await exchange.TickerRest.GetSpotTickerAsync(new GetTickerRequest(symbol));
|
||||
var result = await exchange.TickerRest.GetTickerAsync(new GetTickerRequest(symbol));
|
||||
if (!result.Success)
|
||||
{
|
||||
Console.WriteLine($"{exchange.Name,-8} error: {result.Error}");
|
||||
@@ -171,7 +175,7 @@ async Task PrintOpenOrdersAsync(string[] args)
|
||||
var exchange = GetExchange(args, 1);
|
||||
var symbol = GetSymbol(args, 2);
|
||||
|
||||
var result = await exchange.OrderRest.GetOpenSpotOrdersAsync(new GetOpenOrdersRequest(symbol));
|
||||
var result = await exchange.GetOpenOrdersRest.GetOpenSpotOrdersAsync(new GetOpenOrdersRequest(symbol));
|
||||
if (!result.Success)
|
||||
{
|
||||
Console.WriteLine($"Open orders request failed: {result.Error}");
|
||||
@@ -203,23 +207,9 @@ async Task PlaceLimitOrderAsync(string[] args)
|
||||
SharedQuantity.Base(quantity),
|
||||
price,
|
||||
SharedTimeInForce.GoodTillCanceled,
|
||||
exchange.OrderRest.GenerateClientOrderId());
|
||||
exchange.PlaceOrderRest.GenerateClientOrderId());
|
||||
|
||||
var validationError = exchange.OrderRest.PlaceSpotOrderOptions.ValidateRequest(
|
||||
exchange.Name,
|
||||
request,
|
||||
TradingMode.Spot,
|
||||
exchange.OrderRest.SupportedTradingModes,
|
||||
exchange.OrderRest.SpotSupportedOrderTypes,
|
||||
exchange.OrderRest.SpotSupportedTimeInForce,
|
||||
exchange.OrderRest.SpotSupportedOrderQuantity);
|
||||
if (validationError != null)
|
||||
{
|
||||
Console.WriteLine($"Order request is not valid for {exchange.Name}: {validationError}");
|
||||
return;
|
||||
}
|
||||
|
||||
var result = await exchange.OrderRest.PlaceSpotOrderAsync(request);
|
||||
var result = await exchange.PlaceOrderRest.PlaceSpotOrderAsync(request);
|
||||
Console.WriteLine(result.Success
|
||||
? $"Order placed. Id: {result.Data.Id}"
|
||||
: $"Order failed: {result.Error}");
|
||||
@@ -233,7 +223,7 @@ async Task CancelOrderAsync(string[] args)
|
||||
if (string.IsNullOrWhiteSpace(orderId))
|
||||
throw new ArgumentException("Missing order id. Example: cancel-order binance BTC USDT 123456");
|
||||
|
||||
var result = await exchange.OrderRest.CancelSpotOrderAsync(new CancelOrderRequest(symbol, orderId));
|
||||
var result = await exchange.CancelOrderRest.CancelSpotOrderAsync(new CancelOrderRequest(symbol, orderId));
|
||||
Console.WriteLine(result.Success
|
||||
? $"Order canceled. Id: {result.Data.Id}"
|
||||
: $"Cancel failed: {result.Error}");
|
||||
@@ -296,8 +286,10 @@ static void PrintHelp()
|
||||
|
||||
internal record ExchangeClients(
|
||||
string Name,
|
||||
ISpotTickerRestClient TickerRest,
|
||||
IOrderBookRestClient OrderBookRest,
|
||||
IBalanceRestClient BalanceRest,
|
||||
ISpotOrderRestClient OrderRest,
|
||||
ITickerSocketClient TickerSocket);
|
||||
IGetTickerRest TickerRest,
|
||||
IGetOrderBookRest OrderBookRest,
|
||||
IGetBalancesRest BalanceRest,
|
||||
IPlaceSpotOrderRest PlaceOrderRest,
|
||||
IGetOpenSpotOrdersRest GetOpenOrdersRest,
|
||||
ICancelSpotOrderRest CancelOrderRest,
|
||||
ISubscribeTickerSocket TickerSocket);
|
||||
|
||||
@@ -5,13 +5,13 @@ using CryptoExchange.Net.SharedApis;
|
||||
using OKX.Net.Clients;
|
||||
|
||||
var symbol = new SharedSymbol(TradingMode.Spot, "ETH", "USDT");
|
||||
var binanceSpotRestClient = new BinanceRestClient().SpotApi.SharedClient;
|
||||
var okxSpotRestClient = new OKXRestClient().UnifiedApi.SharedClient;
|
||||
var bitmartSpotRestClient = new BitMartRestClient().SpotApi.SharedClient;
|
||||
var binanceSpotRestClient = new BinanceRestClient().SpotApi.SharedApi;
|
||||
var okxSpotRestClient = new OKXRestClient().UnifiedApi.SharedApi;
|
||||
var bitmartSpotRestClient = new BitMartRestClient().SpotApi.SharedApi;
|
||||
|
||||
var binanceSpotSocketClient = new BinanceSocketClient().SpotApi.SharedClient;
|
||||
var okxSpotSocketClient = new OKXSocketClient().UnifiedApi.SharedClient;
|
||||
var bitmartSpotSocketClient = new BitMartSocketClient().SpotApi.SharedClient;
|
||||
var binanceSpotSocketClient = new BinanceSocketClient().SpotApi.SharedApi;
|
||||
var okxSpotSocketClient = new OKXSocketClient().UnifiedApi.SharedApi;
|
||||
var bitmartSpotSocketClient = new BitMartSocketClient().SpotApi.SharedApi;
|
||||
|
||||
await GetLastTradePriceAsync(binanceSpotRestClient, symbol);
|
||||
await GetLastTradePriceAsync(okxSpotRestClient, symbol);
|
||||
@@ -31,9 +31,9 @@ Console.ReadLine();
|
||||
foreach (var subscription in subscriptions)
|
||||
await subscription.CloseAsync();
|
||||
|
||||
async Task GetLastTradePriceAsync(ISpotTickerRestClient client, SharedSymbol symbol)
|
||||
async Task GetLastTradePriceAsync(IGetTickerRest client, SharedSymbol symbol)
|
||||
{
|
||||
var result = await client.GetSpotTickerAsync(new GetTickerRequest(symbol));
|
||||
var result = await client.GetTickerAsync(new GetTickerRequest(symbol));
|
||||
if (!result.Success)
|
||||
{
|
||||
Console.WriteLine($"Failed to get ticker: {result.Error}");
|
||||
@@ -43,7 +43,7 @@ async Task GetLastTradePriceAsync(ISpotTickerRestClient client, SharedSymbol sym
|
||||
Console.WriteLine($"{client.Exchange} {result.Data.Symbol}: {result.Data.LastPrice}");
|
||||
}
|
||||
|
||||
async Task SubscribeTickerUpdatesAsync(ITickerSocketClient client, SharedSymbol symbol, ICollection<UpdateSubscription> subscriptions)
|
||||
async Task SubscribeTickerUpdatesAsync(ISubscribeTickerSocket client, SharedSymbol symbol, ICollection<UpdateSubscription> subscriptions)
|
||||
{
|
||||
var result = await client.SubscribeToTickerUpdatesAsync(new SubscribeTickerRequest(symbol), update =>
|
||||
{
|
||||
|
||||
@@ -8,9 +8,9 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Binance.Net" Version="12.13.0" />
|
||||
<PackageReference Include="BitMart.Net" Version="3.11.2" />
|
||||
<PackageReference Include="JK.OKX.Net" Version="4.14.0" />
|
||||
<PackageReference Include="Binance.Net" Version="13.6.0" />
|
||||
<PackageReference Include="BitMart.Net" Version="4.5.0" />
|
||||
<PackageReference Include="JK.OKX.Net" Version="5.5.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -23,9 +23,9 @@ For single-exchange code, see also the AI files in each exchange's repository (B
|
||||
See [cryptoexchange-skills-hub](https://github.com/JKorf/cryptoexchange-skills-hub) for installable skills.
|
||||
|
||||
**Quick prompt to verify your assistant is using these:**
|
||||
> "Show me how to fetch BTC/USDT spot tickers from Binance and OKX concurrently in C# using the SharedApis pattern."
|
||||
> "Show me how to fetch BTC/USDT spot tickers from Binance and OKX concurrently in C# using the SharedApis V2 pattern."
|
||||
|
||||
The expected output should use `.SharedClient` properties, `SharedSymbol`, `ISpotTickerRestClient`, and `Task.WhenAll`.
|
||||
The expected output should use `.SharedApi` properties, `SharedSymbol`, `IGetTickerRest`, and `Task.WhenAll`.
|
||||
|
||||
### CryptoExchange.Net Ecosystem
|
||||
Full list of all libraries part of the CryptoExchange.Net ecosystem. Consider using a referral link to support development, as well as potentially get some trading fee discount!
|
||||
@@ -38,7 +38,6 @@ Full list of all libraries part of the CryptoExchange.Net ecosystem. Consider us
|
||||
||Bitfinex|CEX|[JKorf/Bitfinex.Net](https://github.com/JKorf/Bitfinex.Net)|[](https://www.nuget.org/packages/Bitfinex.Net)|-|-|
|
||||
||Bitget|CEX|[JKorf/Bitget.Net](https://github.com/JKorf/Bitget.Net)|[](https://www.nuget.org/packages/JK.Bitget.Net)|[Link](https://partner.bitget.com/bg/1qlf6pj1)|20%|
|
||||
||BitMart|CEX|[JKorf/BitMart.Net](https://github.com/JKorf/BitMart.Net)|[](https://www.nuget.org/packages/BitMart.Net)|[Link](https://www.bitmart.com/invite/JKorfAPI/en-US)|30%|
|
||||
||BitMEX|CEX|[JKorf/BitMEX.Net](https://github.com/JKorf/BitMEX.Net)|[](https://www.nuget.org/packages/JKorf.BitMEX.Net)|[Link](https://www.bitmex.com/app/register/94f98e)|30%|
|
||||
||Bitstamp|CEX|[JKorf/Bitstamp.Net](https://github.com/JKorf/Bitstamp.Net)|[](https://www.nuget.org/packages/Bitstamp.Net)|-|-|
|
||||
||BloFin|CEX|[JKorf/BloFin.Net](https://github.com/JKorf/BloFin.Net)|[](https://www.nuget.org/packages/BloFin.Net)|-|-|
|
||||
||Bybit|CEX|[JKorf/Bybit.Net](https://github.com/JKorf/Bybit.Net)|[](https://www.nuget.org/packages/Bybit.Net)|[Link](https://partner.bybit.com/b/jkorf)|-|
|
||||
@@ -128,6 +127,55 @@ Various:
|
||||
* PlatformInfo now required support environment names in the constructor
|
||||
|
||||
## Release notes
|
||||
* Version 13.2.0 - 13 Oct 2026
|
||||
* Added Shared API ISubscribeFundingInfoSocket
|
||||
* Added DelayAfterConnect to SocketApiOptions
|
||||
* Added processor parameter for some methods in SocketApiClient to allow specific overriding behavior
|
||||
* Updated WebSocket duplicate subscription handling
|
||||
* Updated Shared APIs ISubscribeIncrementalOrderBook
|
||||
* Fixed incorrect status keeping of WebSocket subscriptions which don't expect a response
|
||||
* Fixed DelayAfterConnect not being respected on reconnection
|
||||
|
||||
* Version 13.1.0 - 28 Sep 2026
|
||||
* Added UnsubscribeAllAsync method to Shared APIs ISharedSubscription and ISharedApiClientBase
|
||||
* Added WaitAllAsync extension method for waiting on an IAsyncEnumerable to finish
|
||||
* Added SubscribeAllAsync extension methods for Shared API resolved capabilities
|
||||
|
||||
* Version 13.0.0 - 23 Sep 2026
|
||||
* Shared APIs
|
||||
* Added Shared API V2 with fine-grained capability interfaces for individual REST requests, WebSocket requests and subscriptions
|
||||
* Added transport-agnostic capability interfaces with REST- and WebSocket-specific variants where applicable
|
||||
* Added dynamic capability resolution support with optional transport preference option
|
||||
* Added `RequestParameterRules` and `ExchangeParameterRules` properties on Shared API options making request parameter rules and support clearer
|
||||
* Added new Shared API capabilities for ledger history, funding information and history, leverage tiers, transfers, mark prices and index prices
|
||||
* Added new capabilities for placing and editing multiple spot and futures orders
|
||||
* Added new capabilities for cancelling all open orders and all open symbol orders
|
||||
* Added new incremental order book, mark price and index price subscriptions
|
||||
* Added ReduceOnly parameter to PlaceFuturesTriggerOrderRequest
|
||||
* Added new `SharedTicker` as the common V2 ticker model for spot and futures markets
|
||||
* For additional futures ticker info like mark/index price and funding info separate interfaces are available in V2
|
||||
* Added `SharedSpotOrderUpdate` and `SharedFuturesOrderUpdate` models for WebSocket order updates
|
||||
* Added `IExchangeCallResult` and `IExchangeCallResult<T>` for transport-agnostic Shared API results
|
||||
* Retained the V1 aggregate Shared API interfaces for backwards compatibility
|
||||
* Renamed GetAssetsOptions to GetAllAssetsOptions
|
||||
* Renamed GetWithdrawalsOptions to GetWithdrawalHistoryOptions
|
||||
* Renamed GetDepositsOptions to GetDepositHistoryOptions
|
||||
* GetFuturesTickerOptions and GetSpotTickerOptions have been replaced by GetTickerOptions
|
||||
* Deprecated Subscribe request constructors using `params` for `SharedSymbol` parameter
|
||||
* Rate limiting
|
||||
* Added `RateLimitAdmission` for restricting requests to a configurable maximum rate limit utilization
|
||||
* Added the `RateLimitAdmission` client option callback for assigning admission rules based on request definition and weight
|
||||
* Added `WithRateLimitAdmissionAsync` for applying a rate limit admission rule to a specific REST, WebSocket or Shared API operation
|
||||
* Added configurable safety margins to fixed, sliding and fixed-after-first rate limit windows
|
||||
* Fixed decay rate limiter calculations and handling of partial decay progress
|
||||
* Fixed fixed and sliding window boundary calculations
|
||||
* Added automatic in-flight request coalescing for identical public REST GET requests
|
||||
* Sending identical public GET requests on the same client will only send a single request to the server and use the same response
|
||||
* Coalescing is enabled by default and can be disabled with the `RequestCoalescingEnabled` client option
|
||||
* Added `DataTime`, `DataTimeLocal` and `SequenceNumber` propagation when converting `DataEvent<T>` instances
|
||||
* Fixed form data URL encoding on .NET Framework when parameter values contain special characters
|
||||
* Fixed array converter not correctly handling decimal parsing for certain notations
|
||||
|
||||
* Version 12.5.1 - 01 Sep 2026
|
||||
* Fixed KlineTracker reporting incorrect High/Low price on GetStats result
|
||||
* Fixed caching issue for auth requests
|
||||
|
||||
@@ -35,7 +35,7 @@ var placementCapabilityResolution = sharedClient.GetCapability(
|
||||
|
||||
if (placementCapabilityResolution is not null)
|
||||
{
|
||||
IPlaceFuturesOrderRest api = placement.Capability;
|
||||
IPlaceFuturesOrderRest api = placementCapabilityResolution.Capability;
|
||||
// placement.Options describes parameter and trading-mode support for this implementation.
|
||||
}
|
||||
```
|
||||
|
||||
@@ -0,0 +1,147 @@
|
||||
# CryptoExchange.Net Shared API V2 map
|
||||
|
||||
This is a compact map for choosing Shared API V2 types. All listed types are in `CryptoExchange.Net.SharedApis`. Exchange libraries implement subsets; the map describes available abstractions, not guaranteed exchange support.
|
||||
|
||||
## Entry points
|
||||
|
||||
| Need | Use |
|
||||
| --- | --- |
|
||||
| Known exchange and API surface | Its typed `.SharedApi` property |
|
||||
| Runtime selection within one exchange | Exchange-wide `I[Exchange]SharedApiClient` |
|
||||
| Strongly typed runtime lookup key | `SharedCapabilities` |
|
||||
| One preferred match | `GetCapability(...)` |
|
||||
| Every matching surface/transport in one exchange | `GetCapabilities(...)` |
|
||||
| V1 compatibility during migration | `.SharedClient` |
|
||||
|
||||
Prefer a direct typed `.SharedApi` when the API surface is known. Dynamic lookup returns `SharedCapabilityResolution<T>?`; handle `null`.
|
||||
|
||||
```csharp
|
||||
IGetTickerRest ticker = restClient.SpotApi.SharedApi;
|
||||
var result = await ticker.GetTickerAsync(request);
|
||||
```
|
||||
|
||||
```csharp
|
||||
var match = sharedClient.GetCapability(
|
||||
SharedCapabilities.Tickers.GetTicker.Rest,
|
||||
TradingMode.Spot);
|
||||
```
|
||||
|
||||
## Interface suffixes and results
|
||||
|
||||
| Interface shape | Transport | Typical result |
|
||||
| --- | --- | --- |
|
||||
| Operation with no suffix, e.g. `IPlaceSpotOrder` | selected/preferred | `IExchangeCallResult<T>` |
|
||||
| `...Rest` | REST | `HttpResult<T>` |
|
||||
| socket command, e.g. `IPlaceSpotOrderSocket` | WebSocket | `QueryResult<T>` |
|
||||
| subscription, e.g. `ISubscribeTickerSocket` | WebSocket | `WebSocketResult<UpdateSubscription>` |
|
||||
|
||||
When a `SharedCapabilities` entry supports multiple transports, use the base entry for preferred transport, `.Rest` for REST, or `.Socket` for socket. Exchange-wide preferred transport normally defaults to REST.
|
||||
|
||||
## Market data
|
||||
|
||||
| Operation | Capability interface | Lookup reference |
|
||||
| --- | --- | --- |
|
||||
| Get one ticker | `IGetTickerRest` | `SharedCapabilities.Tickers.GetTicker.Rest` |
|
||||
| Get all tickers | `IGetAllTickersRest` | `SharedCapabilities.Tickers.GetAllTickers.Rest` |
|
||||
| Subscribe to one ticker | `ISubscribeTickerSocket` | `SharedCapabilities.Tickers.SubscribeTicker` |
|
||||
| Subscribe to all tickers | `ISubscribeAllTickersSocket` | `SharedCapabilities.Tickers.SubscribeAllTickers` |
|
||||
| Subscribe to book ticker | `ISubscribeBookTickerSocket` | `SharedCapabilities.Tickers.SubscribeBookTicker` |
|
||||
| Get order book | `IGetOrderBookRest` | `SharedCapabilities.OrderBooks.GetOrderBook.Rest` |
|
||||
| Get book ticker | `IGetBookTickerRest` | `SharedCapabilities.OrderBooks.GetBookTicker.Rest` |
|
||||
| Subscribe to order book | `ISubscribeOrderBookSocket` | `SharedCapabilities.OrderBooks.SubscribeOrderBook` |
|
||||
| Subscribe to incremental book | `ISubscribeIncrementalOrderBookSocket` | `SharedCapabilities.OrderBooks.SubscribeIncrementalOrderBook` |
|
||||
| Get klines | `IGetKlinesRest` | `SharedCapabilities.Klines.GetKlines.Rest` |
|
||||
| Subscribe to klines | `ISubscribeKlinesSocket` | `SharedCapabilities.Klines.SubscribeKlines` |
|
||||
| Get recent trades | `IGetRecentTradesRest` | `SharedCapabilities.Trades.GetRecentTrades.Rest` |
|
||||
| Subscribe to trades | `ISubscribeTradesSocket` | `SharedCapabilities.Trades.SubscribeTrades` |
|
||||
| Get spot symbols | `IGetSpotSymbolsRest` | `SharedCapabilities.Symbols.GetSpotSymbols.Rest` |
|
||||
| Get futures symbols | `IGetFuturesSymbolsRest` | `SharedCapabilities.Symbols.GetFuturesSymbols.Rest` |
|
||||
|
||||
Ticker V2 methods are `GetTickerAsync` and `GetAllTickersAsync`; both use `SharedTicker` for spot and futures. Use separate mark-price, index-price, funding, or open-interest capabilities for derivatives-specific data.
|
||||
|
||||
## Spot orders
|
||||
|
||||
| Operation | Capability family | Lookup reference |
|
||||
| --- | --- | --- |
|
||||
| Place | `IPlaceSpotOrder`, `IPlaceSpotOrderRest`, `IPlaceSpotOrderSocket` | `SharedCapabilities.Orders.Spot.PlaceOrder[.Rest/.Socket]` |
|
||||
| Edit | `IEditSpotOrder`, `IEditSpotOrderRest`, `IEditSpotOrderSocket` | `SharedCapabilities.Orders.Spot.EditOrder[.Rest/.Socket]` |
|
||||
| Cancel | `ICancelSpotOrder`, `ICancelSpotOrderRest`, `ICancelSpotOrderSocket` | `SharedCapabilities.Orders.Spot.CancelOrder[.Rest/.Socket]` |
|
||||
| Get one | `IGetSpotOrderRest` | `SharedCapabilities.Orders.Spot.GetOrder.Rest` |
|
||||
| Get open | `IGetOpenSpotOrdersRest` | `SharedCapabilities.Orders.Spot.GetOpenOrders.Rest` |
|
||||
| Get closed | `IGetClosedSpotOrdersRest` | `SharedCapabilities.Orders.Spot.GetClosedOrders.Rest` |
|
||||
| Subscribe to updates | `ISubscribeSpotOrdersSocket` | `SharedCapabilities.Orders.Spot.SubscribeOrders` |
|
||||
|
||||
Client-order-id variants exist for edit, cancel, and get. Batch placement and cancel-all capabilities also exist; resolve them only when the application needs those operations and the exchange supports them.
|
||||
|
||||
## Futures orders
|
||||
|
||||
| Operation | Capability family | Lookup reference |
|
||||
| --- | --- | --- |
|
||||
| Place | `IPlaceFuturesOrder`, `IPlaceFuturesOrderRest`, `IPlaceFuturesOrderSocket` | `SharedCapabilities.Orders.Futures.PlaceOrder[.Rest/.Socket]` |
|
||||
| Edit | `IEditFuturesOrder`, `IEditFuturesOrderRest`, `IEditFuturesOrderSocket` | `SharedCapabilities.Orders.Futures.EditOrder[.Rest/.Socket]` |
|
||||
| Cancel | `ICancelFuturesOrder`, `ICancelFuturesOrderRest`, `ICancelFuturesOrderSocket` | `SharedCapabilities.Orders.Futures.CancelOrder[.Rest/.Socket]` |
|
||||
| Get one | `IGetFuturesOrderRest` | `SharedCapabilities.Orders.Futures.GetOrder.Rest` |
|
||||
| Get open | `IGetOpenFuturesOrdersRest` | `SharedCapabilities.Orders.Futures.GetOpenOrders.Rest` |
|
||||
| Get closed | `IGetClosedFuturesOrdersRest` | `SharedCapabilities.Orders.Futures.GetClosedOrders.Rest` |
|
||||
| Subscribe to updates | `ISubscribeFuturesOrdersSocket` | `SharedCapabilities.Orders.Futures.SubscribeOrders` |
|
||||
|
||||
Specify `TradingMode.PerpetualLinear`, `PerpetualInverse`, or the appropriate delivery mode when multiple futures APIs can match.
|
||||
|
||||
## Account and derivatives
|
||||
|
||||
Use the following capability families for common account workflows:
|
||||
|
||||
| Area | Common interfaces |
|
||||
| --- | --- |
|
||||
| Balances | `IGetBalancesRest`, `ISubscribeBalancesSocket` |
|
||||
| Positions | `IGetPositionsRest`, `ISubscribePositionsSocket`, `IGetPositionHistoryRest` |
|
||||
| User trades | `IGetSpotUserTradeHistoryRest`, `IGetFuturesUserTradeHistoryRest`, `ISubscribeUserTradesSocket` |
|
||||
| Fees | `IGetFeesRest` |
|
||||
| Deposits | `IGetDepositAddressesRest`, `IGetDepositHistoryRest` |
|
||||
| Withdrawals | `IWithdrawRest`, `IGetWithdrawalHistoryRest` |
|
||||
| Transfers | `ITransferRest`, `IGetTransferHistoryRest` |
|
||||
| Leverage | `IGetLeverageRest`, `ISetLeverageRest`, `IGetLeverageTiersRest` |
|
||||
| Funding | `IGetFundingInfoRest`, `IGetFundingRateHistoryRest`, `IGetUserFundingHistoryRest` |
|
||||
| Mark/index data | `IGetMarkPriceRest`, `IGetIndexPriceRest` and their all-market/subscription variants |
|
||||
| Open interest | `IGetOpenInterestRest` |
|
||||
|
||||
The `SharedCapabilities` catalog groups these under matching plural categories such as `Balances`, `Positions`, `Funding`, `Leverage`, `MarkPrices`, and `IndexPrices`.
|
||||
|
||||
## Parameter support map
|
||||
|
||||
Every successful runtime resolution includes `Options`:
|
||||
|
||||
| Property | Meaning |
|
||||
| --- | --- |
|
||||
| `RequestParameterRules` | Shared request fields that are required, optional, or unsupported |
|
||||
| `ExchangeParameterRules` | Exchange-specific fields accepted through `ExchangeParameters` |
|
||||
| `SupportedTradingModes` | Modes supported by this capability implementation |
|
||||
| `NeedsAuthentication` | Whether credentials are required |
|
||||
|
||||
Capability presence is not proof that all properties on its request model are accepted. Inspect rules for dynamically generated requests.
|
||||
|
||||
## Common V1 to V2 mappings
|
||||
|
||||
| V1 call | V2 capability call |
|
||||
| --- | --- |
|
||||
| `ISpotTickerRestClient.GetSpotTickerAsync` | `IGetTickerRest.GetTickerAsync` |
|
||||
| `ISpotTickerRestClient.GetSpotTickersAsync` | `IGetAllTickersRest.GetAllTickersAsync` |
|
||||
| `ISpotOrderRestClient.PlaceSpotOrderAsync` | `IPlaceSpotOrderRest.PlaceSpotOrderAsync` |
|
||||
| `ISpotOrderRestClient.CancelSpotOrderAsync` | `ICancelSpotOrderRest.CancelSpotOrderAsync` |
|
||||
| `IFuturesOrderRestClient.PlaceFuturesOrderAsync` | `IPlaceFuturesOrderRest.PlaceFuturesOrderAsync` |
|
||||
| `IFuturesOrderRestClient.CancelFuturesOrderAsync` | `ICancelFuturesOrderRest.CancelFuturesOrderAsync` |
|
||||
| `IFuturesOrderRestClient.GetPositionsAsync` | `IGetPositionsRest.GetPositionsAsync` |
|
||||
|
||||
V1 uses `.SharedClient`; V2 uses `.SharedApi`. Migrate operation by operation.
|
||||
|
||||
## Selection checklist
|
||||
|
||||
1. Use the exchange-native API if portability is unnecessary.
|
||||
2. For portable code, identify the single operation capability required.
|
||||
3. Use the typed `.SharedApi` directly when the surface is known.
|
||||
4. Otherwise resolve through the exchange-wide shared client and handle `null`.
|
||||
5. Specify trading mode and transport when ambiguity matters.
|
||||
6. Inspect parameter rules for dynamic requests.
|
||||
7. Check `Success` before using `Data`.
|
||||
|
||||
For migration edge cases and CryptoClients.Net cross-exchange lookup, see `SHARED_API_V2_MIGRATION.md`.
|
||||
+323
@@ -0,0 +1,323 @@
|
||||
# CryptoExchange.Net — full AI context
|
||||
|
||||
Library: CryptoExchange.Net
|
||||
Library version: 13.2.0
|
||||
CryptoExchange.Net package version: 13.2.0
|
||||
Language: C#/.NET
|
||||
Targets: netstandard2.0, netstandard2.1, net8.0, net9.0, net10.0
|
||||
|
||||
## Purpose
|
||||
|
||||
CryptoExchange.Net is the base library used by 28+ exchange-specific clients. It provides shared REST and WebSocket infrastructure, authentication and result conventions, rate limiting, order-book support, and exchange-agnostic Shared APIs.
|
||||
|
||||
CryptoExchange.Net does not provide exchange endpoints by itself. Install the exchange libraries required by the application, such as Binance.Net, Bybit.Net, JK.OKX.Net, Kraken.Net, or Coinbase.Net. Install `CryptoClients.Net` when one package should provide the full exchange bundle.
|
||||
|
||||
Use an exchange's native API for code tied to that exchange. Use Shared API V2 for portable code that performs the same operation against multiple exchanges.
|
||||
|
||||
## Shared API V2 mental model
|
||||
|
||||
Shared API V2 models each operation as a fine-grained capability. A typed exchange API surface exposes implemented capabilities through `.SharedApi`. Examples include:
|
||||
|
||||
- `IGetTickerRest`
|
||||
- `IGetOrderBookRest`
|
||||
- `IGetKlinesRest`
|
||||
- `IPlaceSpotOrderRest`
|
||||
- `ICancelFuturesOrderRest`
|
||||
- `ISubscribeTickerSocket`
|
||||
- `ISubscribeTradesSocket`
|
||||
|
||||
This replaces the V1 assumption that one broad interface represents a complete feature group. An exchange may support order placement without editing, or REST placement without socket placement. Write services against only the capabilities they require.
|
||||
|
||||
V1 aggregate interfaces remain available through `.SharedClient` for incremental migration. Use `.SharedApi` for new code.
|
||||
|
||||
## Installation
|
||||
|
||||
Install only the exchange packages needed:
|
||||
|
||||
```bash
|
||||
dotnet add package Binance.Net
|
||||
dotnet add package JK.OKX.Net
|
||||
dotnet add package Bybit.Net
|
||||
```
|
||||
|
||||
Or install the combined package:
|
||||
|
||||
```bash
|
||||
dotnet add package CryptoClients.Net
|
||||
```
|
||||
|
||||
Do not add CryptoExchange.Net alone and expect to create an exchange client.
|
||||
|
||||
## Direct typed capability usage
|
||||
|
||||
When the exchange and API surface are known, assign `.SharedApi` to the operation interface. The API surface's compile-time type exposes only the capabilities implemented there.
|
||||
|
||||
```csharp
|
||||
using Binance.Net.Clients;
|
||||
using OKX.Net.Clients;
|
||||
using CryptoExchange.Net.SharedApis;
|
||||
|
||||
IGetTickerRest binance = new BinanceRestClient().SpotApi.SharedApi;
|
||||
IGetTickerRest okx = new OKXRestClient().UnifiedApi.SharedApi;
|
||||
|
||||
var symbol = new SharedSymbol(TradingMode.Spot, "BTC", "USDT");
|
||||
var result = await binance.GetTickerAsync(new GetTickerRequest(symbol));
|
||||
|
||||
if (!result.Success)
|
||||
{
|
||||
Console.WriteLine($"[{result.Exchange}] {result.Error}");
|
||||
return;
|
||||
}
|
||||
|
||||
Console.WriteLine($"[{result.Exchange}] {result.Data!.LastPrice}");
|
||||
```
|
||||
|
||||
Use `SharedSymbol` rather than exchange-native strings. Each exchange implementation converts base asset, quote asset, and trading mode into its own symbol format.
|
||||
|
||||
Common trading modes include `Spot`, `PerpetualLinear`, and `PerpetualInverse`. Use the mode that corresponds to the intended exchange API surface.
|
||||
|
||||
## Multi-exchange concurrency
|
||||
|
||||
Shared capabilities allow uniform concurrent calls:
|
||||
|
||||
```csharp
|
||||
var clients = new IGetTickerRest[]
|
||||
{
|
||||
new BinanceRestClient().SpotApi.SharedApi,
|
||||
new OKXRestClient().UnifiedApi.SharedApi,
|
||||
new BybitRestClient().V5Api.SharedApi,
|
||||
};
|
||||
|
||||
var symbol = new SharedSymbol(TradingMode.Spot, "BTC", "USDT");
|
||||
var tasks = clients.Select(client =>
|
||||
client.GetTickerAsync(new GetTickerRequest(symbol)));
|
||||
|
||||
var results = await Task.WhenAll(tasks);
|
||||
foreach (var ticker in results.Where(x => x.Success))
|
||||
Console.WriteLine($"{ticker.Exchange}: {ticker.Data!.LastPrice}");
|
||||
```
|
||||
|
||||
Independent requests should normally run concurrently. Reuse long-lived clients or dependency-injected clients instead of creating clients for each request.
|
||||
|
||||
## Dynamic capability resolution
|
||||
|
||||
Each exchange library registers an exchange-wide `I[Exchange]SharedApiClient`. Use this client when the API surface, trading mode, or transport is selected at runtime.
|
||||
|
||||
`SharedCapabilities` is a catalog of strongly typed references. A reference identifies the interface to find; it does not guarantee support and does not perform an exchange request.
|
||||
|
||||
```csharp
|
||||
var resolution = sharedClient.GetCapability(
|
||||
SharedCapabilities.Orders.Futures.PlaceOrder.Rest,
|
||||
TradingMode.PerpetualLinear);
|
||||
|
||||
if (resolution is null)
|
||||
{
|
||||
Console.WriteLine("Futures REST placement is unavailable.");
|
||||
return;
|
||||
}
|
||||
|
||||
var result = await resolution.Capability.PlaceFuturesOrderAsync(request);
|
||||
```
|
||||
|
||||
`GetCapability` returns one `SharedCapabilityResolution<T>` or `null`. The resolution exposes:
|
||||
|
||||
- `Capability`: the callable typed interface.
|
||||
- `Options`: capability metadata and request rules.
|
||||
- `Exchange`: the exchange name.
|
||||
- `Transport`: REST or socket.
|
||||
|
||||
`GetCapabilities` returns every matching implementation on that exchange-wide client. Pass a `TradingMode` when an exchange can expose multiple matching API surfaces, especially separate linear and inverse futures APIs.
|
||||
|
||||
Use the typed `.SharedApi` property when the desired surface is already known. Use dynamic resolution only when the choice or support is genuinely runtime-dependent.
|
||||
|
||||
## Transport selection
|
||||
|
||||
Some operations have transport-agnostic, REST-specific, and socket-specific variants. Order placement is representative:
|
||||
|
||||
- `IPlaceSpotOrder`: transport-agnostic, returns `IExchangeCallResult<SharedId>`.
|
||||
- `IPlaceSpotOrderRest`: REST-specific, returns `HttpResult<SharedId>`.
|
||||
- `IPlaceSpotOrderSocket`: socket-specific, returns `QueryResult<SharedId>`.
|
||||
|
||||
Subscription interfaces such as `ISubscribeTickerSocket` return `WebSocketResult<UpdateSubscription>`.
|
||||
|
||||
Use a capability reference without a suffix when either transport is acceptable:
|
||||
|
||||
```csharp
|
||||
var preferred = sharedClient.GetCapability(
|
||||
SharedCapabilities.Orders.Spot.PlaceOrder);
|
||||
```
|
||||
|
||||
Use `.Rest` or `.Socket` when the transport or concrete result type matters:
|
||||
|
||||
```csharp
|
||||
var rest = sharedClient.GetCapability(
|
||||
SharedCapabilities.Orders.Spot.PlaceOrder.Rest);
|
||||
|
||||
var socket = sharedClient.GetCapability(
|
||||
SharedCapabilities.Orders.Spot.PlaceOrder.Socket);
|
||||
```
|
||||
|
||||
Exchange-wide transport-agnostic lookup follows the configured `SharedApi.PreferredTransport`, which normally defaults to REST. Do not rely on that preference when application semantics require a particular transport.
|
||||
|
||||
## Result handling
|
||||
|
||||
All result types expose `Success`, `Data`, and `Error`. Check `Success` before reading `Data`.
|
||||
|
||||
- REST capability: `HttpResult<T>`
|
||||
- socket command capability: `QueryResult<T>`
|
||||
- socket subscription capability: `WebSocketResult<UpdateSubscription>`
|
||||
- transport-agnostic capability: `IExchangeCallResult<T>`
|
||||
|
||||
Shared results carry the exchange name, which should be included in logs and aggregation records.
|
||||
|
||||
```csharp
|
||||
if (!result.Success)
|
||||
{
|
||||
logger.LogWarning(
|
||||
"{Exchange} ticker request failed: {Error}",
|
||||
result.Exchange,
|
||||
result.Error);
|
||||
return;
|
||||
}
|
||||
```
|
||||
|
||||
## Request parameter discovery
|
||||
|
||||
A capability can exist even when an exchange does not accept every property on the shared request. Inspect the selected capability's `Options` before building fully dynamic requests:
|
||||
|
||||
- `RequestParameterRules`: each shared request field is `Required`, `Optional`, or `NotSupported`.
|
||||
- `ExchangeParameterRules`: exchange-specific required or optional parameters supplied through the request's `ExchangeParameters`.
|
||||
- `SupportedTradingModes`: modes supported by this particular implementation.
|
||||
|
||||
```csharp
|
||||
var placement = sharedClient.GetCapability(
|
||||
SharedCapabilities.Orders.Futures.PlaceOrder.Rest,
|
||||
TradingMode.PerpetualLinear);
|
||||
|
||||
var leverageRule = placement?.Options.RequestParameterRules
|
||||
.FirstOrDefault(rule =>
|
||||
rule.Name == nameof(PlaceFuturesOrderRequest.Leverage));
|
||||
|
||||
if (leverageRule?.Support == RequestParameterSupport.NotSupported)
|
||||
Console.WriteLine("Set leverage with a separate capability.");
|
||||
```
|
||||
|
||||
Do not infer field support from the request model alone. Handle a missing capability, unsupported trading mode, required field, and required exchange parameter before submitting a trading request.
|
||||
|
||||
## Dependency injection
|
||||
|
||||
Each exchange library provides a `services.Add[Exchange](...)` extension. Registration includes native clients, V1 shared facades, V2 operation capabilities, and the exchange-wide shared client.
|
||||
|
||||
Direct capability injection is appropriate when one implementation is intended:
|
||||
|
||||
```csharp
|
||||
public sealed class TickerService
|
||||
{
|
||||
private readonly IGetTickerRest _ticker;
|
||||
|
||||
public TickerService(IGetTickerRest ticker)
|
||||
=> _ticker = ticker;
|
||||
|
||||
public Task<HttpResult<SharedTicker>> GetAsync(
|
||||
SharedSymbol symbol,
|
||||
CancellationToken ct = default)
|
||||
=> _ticker.GetTickerAsync(new GetTickerRequest(symbol), ct);
|
||||
}
|
||||
```
|
||||
|
||||
If multiple exchanges or multiple API surfaces register the same capability interface, inject the exchange-specific shared client and select a typed API property or use `GetCapability`. Resolving one unqualified capability does not express which registered implementation is desired.
|
||||
|
||||
`CryptoClients.Net` registers `IExchangeSharedApiClient` for V2 lookup across exchanges. Its `GetCapabilities` returns one preferred matching implementation per exchange, while `GetImplementations` returns every matching transport and API surface.
|
||||
|
||||
## Common capability families
|
||||
|
||||
Frequently used market-data capabilities:
|
||||
|
||||
- Tickers: `IGetTickerRest`, `IGetAllTickersRest`, `ISubscribeTickerSocket`, `ISubscribeAllTickersSocket`, `ISubscribeBookTickerSocket`.
|
||||
- Order books: `IGetOrderBookRest`, `IGetBookTickerRest`, `ISubscribeOrderBookSocket`, `ISubscribeIncrementalOrderBookSocket`.
|
||||
- Klines and trades: `IGetKlinesRest`, `ISubscribeKlinesSocket`, `IGetRecentTradesRest`, `ISubscribeTradesSocket`.
|
||||
- Symbols: `IGetSpotSymbolsRest`, `IGetFuturesSymbolsRest`.
|
||||
|
||||
Frequently used trading and account capabilities:
|
||||
|
||||
- Spot and futures order placement, editing, cancellation, retrieval, open/closed orders, and order-update subscriptions.
|
||||
- Balances, positions, user trades, fees, deposits, withdrawals, and transfers.
|
||||
- Funding, open interest, leverage, mark price, and index price operations for derivatives.
|
||||
|
||||
Use `docs/ai-api-map.md` or the typed API surface for names. Do not assume every exchange implements every family.
|
||||
|
||||
## V2 ticker behavior
|
||||
|
||||
V2 uses `GetTickerAsync` and `GetAllTickersAsync` for both spot and futures and returns `SharedTicker`. Futures-specific mark price, index price, and funding data have separate capabilities rather than being assumed to exist on the common ticker.
|
||||
|
||||
```csharp
|
||||
IGetTickerRest ticker = restClient.SpotApi.SharedApi;
|
||||
var result = await ticker.GetTickerAsync(
|
||||
new GetTickerRequest(
|
||||
new SharedSymbol(TradingMode.Spot, "ETH", "USDT")));
|
||||
```
|
||||
|
||||
## Order and subscription behavior
|
||||
|
||||
V2 exposes REST and socket order commands under the same operation capability when both are supported. Choose the transport-specific interface when the caller needs `HttpResult<T>` or `QueryResult<T>`.
|
||||
|
||||
Order-update subscriptions use `SharedSpotOrderUpdate` and `SharedFuturesOrderUpdate`. REST order retrieval returns `SharedSpotOrder` or `SharedFuturesOrder`. Keep update-only stream semantics out of ordinary order snapshots.
|
||||
|
||||
`ICloseFullPosition` closes the entire position and has no quantity parameter. It is not a replacement for partial close logic; use a suitable order capability when an exchange supports partial reduction.
|
||||
|
||||
## V1 migration essentials
|
||||
|
||||
V1 remains available through `.SharedClient`; V2 is exposed through `.SharedApi`. Both are views over the exchange API, so migration can happen operation by operation.
|
||||
|
||||
```csharp
|
||||
var symbol = new SharedSymbol(TradingMode.Spot, "BTC", "USDT");
|
||||
var request = new GetTickerRequest(symbol);
|
||||
|
||||
// V1 compatibility facade
|
||||
var oldResult = await restClient.SpotApi.SharedClient
|
||||
.GetSpotTickerAsync(request);
|
||||
|
||||
// V2 capability
|
||||
var newResult = await restClient.SpotApi.SharedApi
|
||||
.GetTickerAsync(request);
|
||||
```
|
||||
|
||||
Representative mappings:
|
||||
|
||||
- `ISpotTickerRestClient.GetSpotTickerAsync` -> `IGetTickerRest.GetTickerAsync`
|
||||
- `ISpotOrderRestClient.PlaceSpotOrderAsync` -> `IPlaceSpotOrderRest.PlaceSpotOrderAsync`
|
||||
- `ISpotOrderRestClient.CancelSpotOrderAsync` -> `ICancelSpotOrderRest.CancelSpotOrderAsync`
|
||||
- `IFuturesOrderRestClient.PlaceFuturesOrderAsync` -> `IPlaceFuturesOrderRest.PlaceFuturesOrderAsync`
|
||||
- `IFuturesOrderRestClient.GetPositionsAsync` -> `IGetPositionsRest.GetPositionsAsync`
|
||||
|
||||
Transport-agnostic V2 methods return `IExchangeCallResult<T>`. Select `...Rest` or `...Socket` if old code depends on a concrete transport result. See `docs/SHARED_API_V2_MIGRATION.md` for detailed migration examples and CryptoClients.Net behavior.
|
||||
|
||||
## Code-generation rules
|
||||
|
||||
- Use `.SharedApi` and fine-grained V2 interfaces for new portable code.
|
||||
- Use `.SharedClient` only when maintaining or incrementally migrating V1 code.
|
||||
- Use `SharedSymbol` instead of native symbol strings.
|
||||
- Resolve capability support rather than assuming it.
|
||||
- Inspect capability options when requests are generated dynamically.
|
||||
- Select REST or socket explicitly when transport semantics matter.
|
||||
- Check `Success` before accessing `Data`.
|
||||
- Reuse clients through DI and keep calls asynchronous.
|
||||
- Run independent exchange calls concurrently.
|
||||
- Keep exchange-native models out of cross-exchange service contracts.
|
||||
|
||||
## Implementing an exchange library
|
||||
|
||||
Exchange implementations derive from the shared REST and socket client bases, provide their authentication and option types, and expose typed Shared API classes. Implement the fine-grained capability interfaces actually supported by each API surface and publish accurate `CapabilityOptions`, including trading modes and request/exchange parameter rules. Register the Shared APIs and exchange-wide shared client in the exchange library's DI extension.
|
||||
|
||||
Use established exchange libraries as references. Do not advertise a capability merely because a similar native endpoint exists; its shared request and result semantics must be implemented correctly.
|
||||
|
||||
## References
|
||||
|
||||
- Source: https://github.com/JKorf/CryptoExchange.Net
|
||||
- Documentation: https://cryptoexchange.jkorf.dev/
|
||||
- Shared API V2 migration: https://github.com/JKorf/CryptoExchange.Net/blob/master/docs/SHARED_API_V2_MIGRATION.md
|
||||
- AI API map: https://github.com/JKorf/CryptoExchange.Net/blob/master/docs/ai-api-map.md
|
||||
- CryptoClients.Net: https://github.com/JKorf/CryptoClients.Net
|
||||
- CryptoManager.Net: https://github.com/JKorf/CryptoManager.Net
|
||||
- NuGet: https://www.nuget.org/packages/CryptoExchange.Net
|
||||
- Discord: https://discord.gg/MSpeEtSY8t
|
||||
@@ -1,40 +1,37 @@
|
||||
# CryptoExchange.Net
|
||||
|
||||
> Base C#/.NET library for cryptocurrency exchange API client implementations. Provides a standardized abstraction (REST, WebSocket, authentication, rate limiting, error handling, order book management, shared cross-exchange interfaces) that 28+ exchange-specific libraries are built on top of.
|
||||
> Base C#/.NET library used by 28+ cryptocurrency exchange clients. It standardizes REST, WebSocket, authentication, rate limiting, results, and exchange-agnostic Shared APIs.
|
||||
|
||||
CryptoExchange.Net itself is not used directly — install one of the exchange-specific libraries (Binance.Net, Bybit.Net, OKX.Net, Kraken.Net, Coinbase.Net, etc.) or `CryptoClients.Net` to access all exchanges via a single bundle. The base library is what makes the entire ecosystem feel consistent: same `HttpResult<T>` REST result pattern, same `WebSocketResult<UpdateSubscription>` websocket subscription pattern, same DI registration, same shared interfaces across all exchanges. Current version: 12.5.1. Targets netstandard2.0, netstandard2.1, net8.0, net9.0, net10.0. Native AOT supported.
|
||||
CryptoExchange.Net is not an exchange client by itself. Install exchange-specific packages such as Binance.Net, Bybit.Net, JK.OKX.Net, Kraken.Net, or Coinbase.Net, or install `CryptoClients.Net` for the bundle. Current release: 13.2.0. Targets netstandard2.0, netstandard2.1, net8.0, net9.0, and net10.0; Native AOT is supported.
|
||||
|
||||
The standout feature for cross-exchange code is `CryptoExchange.Net.SharedApis` — a set of interfaces (`ISpotTickerRestClient`, `ISpotOrderRestClient`, `IBalanceRestClient`, etc.) implemented by every exchange library. Same call signature works against any exchange.
|
||||
## Shared API V2
|
||||
|
||||
Version 12.2.0 adds typed asset metadata to shared symbol discovery. `SharedSpotSymbol` and `SharedFuturesSymbol` expose `DisplayName` plus base/quote `SharedAssetType` and `SharedAssetSubType` values. `GetSymbolsRequest` can filter on those four type fields. After symbol discovery, `ISpotSymbolRestClient.SpotSymbolCatalog` and `IFuturesSymbolRestClient.FuturesSymbolCatalog` provide asset and symbol dictionaries; each catalog is available only after the corresponding `Get*SymbolsAsync` call. `LibraryHelpers.IsStableCoin`, `IsCommodity`, and `IsEquity` are best-effort helpers for exchange-library implementations.
|
||||
For new cross-exchange code, use fine-grained capability interfaces from `CryptoExchange.Net.SharedApis` through each API surface's `.SharedApi` property. Examples: `IGetTickerRest`, `IGetOrderBookRest`, `IPlaceSpotOrderRest`, and `ISubscribeTradesSocket`.
|
||||
|
||||
Version 12.4.0 represents market-data quantities with `SharedOrderQuantity`: use `Volumes` on `SharedSpotTicker`, `SharedFuturesTicker`, and `SharedKline`, and `Quantities` on `SharedTrade`. The old scalar `Volume`, `QuoteVolume`, and `Quantity` members are obsolete. Exchange-library implementations must pass `SharedOrderQuantity` to these model constructors.
|
||||
Use `SharedSymbol` to normalize symbols and `Task.WhenAll` for independent calls across exchanges. Use an exchange-wide `I[Exchange]SharedApiClient` plus `SharedCapabilities` and `GetCapability` when selecting operations dynamically. A resolution can be `null`; its `Options` describes supported trading modes and request/exchange parameter rules.
|
||||
|
||||
Version 12.5.0 adds optional shared WebSocket order commands through `ISpotOrderManagementSocketClient` and `IFuturesOrderManagementSocketClient`. Their place/cancel methods return `QueryResult<SharedId>`; they are commands rather than update subscriptions, and support is exchange-specific.
|
||||
Transport-agnostic capabilities return `IExchangeCallResult<T>`. REST capabilities return `HttpResult<T>`, socket commands return `QueryResult<T>`, and socket subscriptions return `WebSocketResult<UpdateSubscription>`. Always check `.Success` before `.Data`.
|
||||
|
||||
V1 broad interfaces remain accessible through `.SharedClient` for incremental migration. Prefer `.SharedApi` for new code. V2 ticker calls use `GetTickerAsync`/`GetAllTickersAsync` and the common `SharedTicker` model.
|
||||
|
||||
## Documentation
|
||||
|
||||
- [README](https://github.com/JKorf/CryptoExchange.Net/blob/master/README.md): Overview, full ecosystem table (28+ exchange libraries), installation per exchange, complete release notes
|
||||
- [Documentation Site](https://cryptoexchange.jkorf.dev/): Full documentation hub with sections per topic
|
||||
- [SharedApis Documentation](https://cryptoexchange.jkorf.dev/CryptoExchange.Net/idocs_shared.html): Cross-exchange shared interface guide
|
||||
- [README](https://github.com/JKorf/CryptoExchange.Net/blob/master/README.md): ecosystem overview, packages, and release notes
|
||||
- [Shared API V2 migration](https://github.com/JKorf/CryptoExchange.Net/blob/master/docs/SHARED_API_V2_MIGRATION.md): V1-to-V2 mapping, capability selection, transports, and parameter discovery
|
||||
- [Documentation site](https://cryptoexchange.jkorf.dev/): complete library documentation
|
||||
- [Shared API documentation](https://cryptoexchange.jkorf.dev/CryptoExchange.Net/idocs_shared.html): cross-exchange API guide
|
||||
- [AI API map](https://github.com/JKorf/CryptoExchange.Net/blob/master/docs/ai-api-map.md): concise V2 interface and selection map
|
||||
|
||||
## Examples
|
||||
## AI context files
|
||||
|
||||
- [AI-friendly examples directory](https://github.com/JKorf/CryptoExchange.Net/tree/master/Examples/ai-friendly): Compact, fully runnable examples optimized for AI assistants
|
||||
- [Shared Clients Quickstart](https://github.com/JKorf/CryptoExchange.Net/blob/master/Examples/ai-friendly/01-shared-clients-quickstart.cs): Same code calling multiple exchanges via SharedApis
|
||||
- [Multi-Exchange Tickers](https://github.com/JKorf/CryptoExchange.Net/blob/master/Examples/ai-friendly/02-multi-exchange-tickers.cs): Aggregating ticker data across N exchanges concurrently
|
||||
- [Cross-Exchange Arbitrage Skeleton](https://github.com/JKorf/CryptoExchange.Net/blob/master/Examples/ai-friendly/03-cross-exchange-arbitrage-skeleton.cs): Pattern for building a price difference scanner
|
||||
- [Full Examples Repository](https://github.com/JKorf/CryptoExchange.Net/tree/master/Examples): ConsoleClient with multiple exchange exchanges, BlazorClient, SharedClients
|
||||
- [AGENTS.md](https://github.com/JKorf/CryptoExchange.Net/blob/master/AGENTS.md): practical generation rules and examples
|
||||
- [llms-full.txt](https://github.com/JKorf/CryptoExchange.Net/blob/master/llms-full.txt): expanded library and Shared API V2 context
|
||||
- [.github/copilot-instructions.md](https://github.com/JKorf/CryptoExchange.Net/blob/master/.github/copilot-instructions.md): compact repository conventions
|
||||
- [.cursor/rules/CryptoExchange-Net.mdc](https://github.com/JKorf/CryptoExchange.Net/blob/master/.cursor/rules/CryptoExchange-Net.mdc): Cursor rules for cross-exchange C# code
|
||||
|
||||
## Reference
|
||||
## Related projects
|
||||
|
||||
- [Ecosystem libraries list](https://github.com/JKorf/CryptoExchange.Net#cryptoexchangenet-ecosystem): Aster, Binance, BingX, Bitfinex, Bitget, BitMart, BitMEX, Bitstamp, BloFin, Bybit, Coinbase, CoinEx, CoinW, CoinGecko, Crypto.com, DeepCoin, Gate.io, HTX, HyperLiquid, Kraken, Kucoin, Mexc, OKX, Pionex, Polymarket, Toobit, Upbit, Weex, WhiteBit, XT
|
||||
- [CryptoClients.Net](https://github.com/JKorf/CryptoClients.Net): Single bundle package for all exchange libraries
|
||||
- [CryptoManager.Net](https://github.com/JKorf/CryptoManager.Net): Full demo application using CryptoClients.Net
|
||||
- [NuGet Package](https://www.nuget.org/packages/CryptoExchange.Net): Latest stable release on NuGet
|
||||
|
||||
## Optional
|
||||
|
||||
- [Discord Community](https://discord.gg/MSpeEtSY8t): Maintainer-supported Discord for ecosystem-wide discussion
|
||||
- [GitHub Issues](https://github.com/JKorf/CryptoExchange.Net/issues): Bug reports and feature requests
|
||||
- [GitHub Sponsors](https://github.com/sponsors/JKorf): Support the maintainer
|
||||
- [CryptoClients.Net](https://github.com/JKorf/CryptoClients.Net): bundle and cross-exchange capability lookup
|
||||
- [CryptoManager.Net](https://github.com/JKorf/CryptoManager.Net): full demo application
|
||||
- [NuGet](https://www.nuget.org/packages/CryptoExchange.Net): package releases
|
||||
- [Discord](https://discord.gg/MSpeEtSY8t): maintainer-supported community
|
||||
|
||||
Reference in New Issue
Block a user