mirror of
https://github.com/JKorf/CryptoExchange.Net.git
synced 2026-08-12 08:53:01 +00:00
Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c62775813f | |||
| f11b3754f0 | |||
| 3cbe0465e9 | |||
| 5048aea722 | |||
| c3316a51e7 | |||
| 7ecf37064b | |||
| 18954f4f53 | |||
| 00bc245102 | |||
| 273cab9fdb |
@@ -71,7 +71,7 @@ namespace CryptoExchange.Net
|
||||
rateLimiters.Add(rateLimiter);
|
||||
RateLimiters = rateLimiters;
|
||||
|
||||
RequestFactory.Configure(options.RequestTimeout, httpClient);
|
||||
RequestFactory.Configure(options.Proxy, options.RequestTimeout, httpClient);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -6,16 +6,16 @@
|
||||
<PackageId>CryptoExchange.Net</PackageId>
|
||||
<Authors>JKorf</Authors>
|
||||
<Description>A base package for implementing cryptocurrency API's</Description>
|
||||
<PackageVersion>6.0.0</PackageVersion>
|
||||
<AssemblyVersion>6.0.0</AssemblyVersion>
|
||||
<FileVersion>6.0.0</FileVersion>
|
||||
<PackageVersion>6.0.3</PackageVersion>
|
||||
<AssemblyVersion>6.0.3</AssemblyVersion>
|
||||
<FileVersion>6.0.3</FileVersion>
|
||||
<PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance>
|
||||
<RepositoryType>git</RepositoryType>
|
||||
<RepositoryUrl>https://github.com/JKorf/CryptoExchange.Net.git</RepositoryUrl>
|
||||
<PackageProjectUrl>https://github.com/JKorf/CryptoExchange.Net</PackageProjectUrl>
|
||||
<NeutralLanguage>en</NeutralLanguage>
|
||||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||
<PackageReleaseNotes>6.0.0 - Updated ApiCredentials to support RSA signing as well as the default Hmac signature, Removed custom logging implementation in favor of using `Microsoft.Extensions.Logging` ILogger directly, Refactored client options for easier use, Added easier way of switching environments, Added ResponseLength and ToString() override on WebCallResult object, Fixed memory leak in AsyncResetEvent</PackageReleaseNotes>
|
||||
<PackageReleaseNotes>6.0.3 - Fixed Proxy not getting applied in rest clients when not using DI</PackageReleaseNotes>
|
||||
<Nullable>enable</Nullable>
|
||||
<LangVersion>10.0</LangVersion>
|
||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using CryptoExchange.Net.Objects;
|
||||
using CryptoExchange.Net.Objects.Options;
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
|
||||
@@ -23,6 +24,7 @@ namespace CryptoExchange.Net.Interfaces
|
||||
/// </summary>
|
||||
/// <param name="requestTimeout">Request timeout to use</param>
|
||||
/// <param name="httpClient">Optional shared http client instance</param>
|
||||
void Configure(TimeSpan requestTimeout, HttpClient? httpClient=null);
|
||||
/// <param name="proxy">Optional proxy to use when no http client is provided</param>
|
||||
void Configure(ApiProxy? proxy, TimeSpan requestTimeout, HttpClient? httpClient=null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,329 +0,0 @@
|
||||
//using System;
|
||||
//using System.Collections.Generic;
|
||||
//using System.Linq;
|
||||
//using System.Net.Http;
|
||||
//using CryptoExchange.Net.Authentication;
|
||||
//using CryptoExchange.Net.Interfaces;
|
||||
//using CryptoExchange.Net.Logging;
|
||||
//using Microsoft.Extensions.Logging;
|
||||
|
||||
//namespace CryptoExchange.Net.Objects
|
||||
//{
|
||||
// /// <summary>
|
||||
// /// Client options
|
||||
// /// </summary>
|
||||
// public abstract class ClientOptions
|
||||
// {
|
||||
// internal event Action? OnLoggingChanged;
|
||||
|
||||
// private LogLevel _logLevel = LogLevel.Information;
|
||||
// /// <summary>
|
||||
// /// The minimum log level to output
|
||||
// /// </summary>
|
||||
// public LogLevel LogLevel
|
||||
// {
|
||||
// get => _logLevel;
|
||||
// set
|
||||
// {
|
||||
// _logLevel = value;
|
||||
// OnLoggingChanged?.Invoke();
|
||||
// }
|
||||
// }
|
||||
|
||||
// private List<ILogger> _logWriters = new List<ILogger> { new DebugLogger() };
|
||||
// /// <summary>
|
||||
// /// The log writers
|
||||
// /// </summary>
|
||||
// public List<ILogger> LogWriters
|
||||
// {
|
||||
// get => _logWriters;
|
||||
// set
|
||||
// {
|
||||
// _logWriters = value;
|
||||
// OnLoggingChanged?.Invoke();
|
||||
// }
|
||||
// }
|
||||
|
||||
// /// <summary>
|
||||
// /// Proxy to use when connecting
|
||||
// /// </summary>
|
||||
// public ApiProxy? Proxy { get; set; }
|
||||
|
||||
// /// <summary>
|
||||
// /// The api credentials used for signing requests to this API.
|
||||
// /// </summary>
|
||||
// public ApiCredentials? ApiCredentials { get; set; }
|
||||
|
||||
// /// <summary>
|
||||
// /// ctor
|
||||
// /// </summary>
|
||||
// public ClientOptions()
|
||||
// {
|
||||
// }
|
||||
|
||||
// /// <summary>
|
||||
// /// ctor
|
||||
// /// </summary>
|
||||
// /// <param name="clientOptions">Copy values for the provided options</param>
|
||||
// public ClientOptions(ClientOptions? clientOptions)
|
||||
// {
|
||||
// if (clientOptions == null)
|
||||
// return;
|
||||
|
||||
// LogLevel = clientOptions.LogLevel;
|
||||
// LogWriters = clientOptions.LogWriters.ToList();
|
||||
// Proxy = clientOptions.Proxy;
|
||||
// ApiCredentials = clientOptions.ApiCredentials?.Copy();
|
||||
// }
|
||||
|
||||
// /// <summary>
|
||||
// /// ctor
|
||||
// /// </summary>
|
||||
// /// <param name="baseOptions">Copy values for the provided options</param>
|
||||
// /// <param name="newValues">Copy values for the provided options</param>
|
||||
// internal ClientOptions(ClientOptions baseOptions, ClientOptions? newValues)
|
||||
// {
|
||||
// Proxy = newValues?.Proxy ?? baseOptions.Proxy;
|
||||
// LogLevel = baseOptions.LogLevel;
|
||||
// LogWriters = baseOptions.LogWriters.ToList();
|
||||
// ApiCredentials = newValues?.ApiCredentials?.Copy() ?? baseOptions.ApiCredentials?.Copy();
|
||||
// }
|
||||
|
||||
// /// <inheritdoc />
|
||||
// public override string ToString()
|
||||
// {
|
||||
// return $"LogLevel: {LogLevel}, Writers: {LogWriters.Count}, Proxy: {(Proxy == null ? "-" : Proxy.Host)}";
|
||||
// }
|
||||
// }
|
||||
|
||||
// /// <summary>
|
||||
// /// API client options
|
||||
// /// </summary>
|
||||
// public class ApiClientOptions
|
||||
// {
|
||||
// /// <summary>
|
||||
// /// If true, the CallResult and DataEvent objects will also include the originally received json data in the OriginalData property
|
||||
// /// </summary>
|
||||
// public bool OutputOriginalData { get; set; } = false;
|
||||
|
||||
// /// <summary>
|
||||
// /// The base address of the API
|
||||
// /// </summary>
|
||||
// public string BaseAddress { get; set; }
|
||||
|
||||
// /// <summary>
|
||||
// /// The api credentials used for signing requests to this API. Overrides API credentials provided in the client options
|
||||
// /// </summary>
|
||||
// public ApiCredentials? ApiCredentials { get; set; }
|
||||
|
||||
// /// <summary>
|
||||
// /// ctor
|
||||
// /// </summary>
|
||||
//#pragma warning disable 8618 // Will always get filled by the implementation
|
||||
// public ApiClientOptions()
|
||||
// {
|
||||
// }
|
||||
//#pragma warning restore 8618
|
||||
|
||||
// /// <summary>
|
||||
// /// ctor
|
||||
// /// </summary>
|
||||
// /// <param name="baseAddress">Base address for the API</param>
|
||||
// public ApiClientOptions(string baseAddress)
|
||||
// {
|
||||
// BaseAddress = baseAddress;
|
||||
// }
|
||||
|
||||
// /// <summary>
|
||||
// /// ctor
|
||||
// /// </summary>
|
||||
// /// <param name="baseOptions">Copy values for the provided options</param>
|
||||
// /// <param name="newValues">Copy values for the provided options</param>
|
||||
// public ApiClientOptions(ApiClientOptions baseOptions, ApiClientOptions? newValues)
|
||||
// {
|
||||
// BaseAddress = newValues?.BaseAddress ?? baseOptions.BaseAddress;
|
||||
// ApiCredentials = newValues?.ApiCredentials?.Copy() ?? baseOptions.ApiCredentials?.Copy();
|
||||
// OutputOriginalData = newValues?.OutputOriginalData ?? baseOptions.OutputOriginalData;
|
||||
// }
|
||||
|
||||
// /// <inheritdoc />
|
||||
// public override string ToString()
|
||||
// {
|
||||
// return $"OutputOriginalData: {OutputOriginalData}, Credentials: {(ApiCredentials == null ? "-" : "Set")}, BaseAddress: {BaseAddress}";
|
||||
// }
|
||||
// }
|
||||
|
||||
// /// <summary>
|
||||
// /// Rest API client options
|
||||
// /// </summary>
|
||||
// public class RestApiClientOptions: ApiClientOptions
|
||||
// {
|
||||
// /// <summary>
|
||||
// /// The time the server has to respond to a request before timing out
|
||||
// /// </summary>
|
||||
// public TimeSpan RequestTimeout { get; set; } = TimeSpan.FromSeconds(30);
|
||||
|
||||
// /// <summary>
|
||||
// /// Http client to use. If a HttpClient is provided in this property the RequestTimeout and Proxy options provided in these options will be ignored in requests and should be set on the provided HttpClient instance
|
||||
// /// </summary>
|
||||
// public HttpClient? HttpClient { get; set; }
|
||||
|
||||
// /// <summary>
|
||||
// /// List of rate limiters to use
|
||||
// /// </summary>
|
||||
// public List<IRateLimiter> RateLimiters { get; set; } = new List<IRateLimiter>();
|
||||
|
||||
// /// <summary>
|
||||
// /// What to do when a call would exceed the rate limit
|
||||
// /// </summary>
|
||||
// public RateLimitingBehaviour RateLimitingBehaviour { get; set; } = RateLimitingBehaviour.Wait;
|
||||
|
||||
// /// <summary>
|
||||
// /// Whether or not to automatically sync the local time with the server time
|
||||
// /// </summary>
|
||||
// public bool AutoTimestamp { get; set; }
|
||||
|
||||
// /// <summary>
|
||||
// /// How often the timestamp adjustment between client and server is recalculated. If you need a very small TimeSpan here you're probably better of syncing your server time more often
|
||||
// /// </summary>
|
||||
// public TimeSpan TimestampRecalculationInterval { get; set; } = TimeSpan.FromHours(1);
|
||||
|
||||
// /// <summary>
|
||||
// /// ctor
|
||||
// /// </summary>
|
||||
// public RestApiClientOptions()
|
||||
// {
|
||||
// }
|
||||
|
||||
// /// <summary>
|
||||
// /// ctor
|
||||
// /// </summary>
|
||||
// /// <param name="baseAddress">Base address for the API</param>
|
||||
// public RestApiClientOptions(string baseAddress): base(baseAddress)
|
||||
// {
|
||||
// }
|
||||
|
||||
// /// <summary>
|
||||
// /// ctor
|
||||
// /// </summary>
|
||||
// /// <param name="baseOn">Copy values for the provided options</param>
|
||||
// /// <param name="newValues">Copy values for the provided options</param>
|
||||
// public RestApiClientOptions(RestApiClientOptions baseOn, RestApiClientOptions? newValues): base(baseOn, newValues)
|
||||
// {
|
||||
// HttpClient = newValues?.HttpClient ?? baseOn.HttpClient;
|
||||
// RequestTimeout = newValues == default ? baseOn.RequestTimeout : newValues.RequestTimeout;
|
||||
// RateLimitingBehaviour = newValues?.RateLimitingBehaviour ?? baseOn.RateLimitingBehaviour;
|
||||
// AutoTimestamp = newValues?.AutoTimestamp ?? baseOn.AutoTimestamp;
|
||||
// TimestampRecalculationInterval = newValues?.TimestampRecalculationInterval ?? baseOn.TimestampRecalculationInterval;
|
||||
// RateLimiters = newValues?.RateLimiters.ToList() ?? baseOn?.RateLimiters.ToList() ?? new List<IRateLimiter>();
|
||||
// }
|
||||
|
||||
// /// <inheritdoc />
|
||||
// public override string ToString()
|
||||
// {
|
||||
// return $"{base.ToString()}, RequestTimeout: {RequestTimeout:c}, HttpClient: {(HttpClient == null ? "-" : "set")}, RateLimiters: {RateLimiters?.Count}, RateLimitBehaviour: {RateLimitingBehaviour}, AutoTimestamp: {AutoTimestamp}, TimestampRecalculationInterval: {TimestampRecalculationInterval}";
|
||||
// }
|
||||
// }
|
||||
|
||||
// /// <summary>
|
||||
// /// Rest API client options
|
||||
// /// </summary>
|
||||
// public class SocketApiClientOptions : ApiClientOptions
|
||||
// {
|
||||
// /// <summary>
|
||||
// /// Whether or not the socket should automatically reconnect when losing connection
|
||||
// /// </summary>
|
||||
// public bool AutoReconnect { get; set; } = true;
|
||||
|
||||
// /// <summary>
|
||||
// /// Time to wait between reconnect attempts
|
||||
// /// </summary>
|
||||
// public TimeSpan ReconnectInterval { get; set; } = TimeSpan.FromSeconds(5);
|
||||
|
||||
// /// <summary>
|
||||
// /// Max number of concurrent resubscription tasks per socket after reconnecting a socket
|
||||
// /// </summary>
|
||||
// public int MaxConcurrentResubscriptionsPerSocket { get; set; } = 5;
|
||||
|
||||
// /// <summary>
|
||||
// /// The max time to wait for a response after sending a request on the socket before giving a timeout
|
||||
// /// </summary>
|
||||
// public TimeSpan SocketResponseTimeout { get; set; } = TimeSpan.FromSeconds(10);
|
||||
|
||||
// /// <summary>
|
||||
// /// The max time of not receiving any data after which the connection is assumed to be dropped. This can only be used for socket connections where a steady flow of data is expected,
|
||||
// /// for example when the server sends intermittent ping requests
|
||||
// /// </summary>
|
||||
// public TimeSpan SocketNoDataTimeout { get; set; }
|
||||
|
||||
// /// <summary>
|
||||
// /// The amount of subscriptions that should be made on a single socket connection. Not all API's support multiple subscriptions on a single socket.
|
||||
// /// Setting this to a higher number increases subscription speed because not every subscription needs to connect to the server, but having more subscriptions on a
|
||||
// /// single connection will also increase the amount of traffic on that single connection, potentially leading to issues.
|
||||
// /// </summary>
|
||||
// public int? SocketSubscriptionsCombineTarget { get; set; }
|
||||
|
||||
// /// <summary>
|
||||
// /// The max amount of connections to make to the server. Can be used for API's which only allow a certain number of connections. Changing this to a high value might cause issues.
|
||||
// /// </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; } = TimeSpan.Zero;
|
||||
|
||||
// /// <summary>
|
||||
// /// ctor
|
||||
// /// </summary>
|
||||
// public SocketApiClientOptions()
|
||||
// {
|
||||
// }
|
||||
|
||||
// /// <summary>
|
||||
// /// ctor
|
||||
// /// </summary>
|
||||
// /// <param name="baseAddress">Base address for the API</param>
|
||||
// public SocketApiClientOptions(string baseAddress) : base(baseAddress)
|
||||
// {
|
||||
// }
|
||||
|
||||
// /// <summary>
|
||||
// /// ctor
|
||||
// /// </summary>
|
||||
// /// <param name="baseOptions">Copy values for the provided options</param>
|
||||
// /// <param name="newValues">Copy values for the provided options</param>
|
||||
// public SocketApiClientOptions(SocketApiClientOptions baseOptions, SocketApiClientOptions? newValues) : base(baseOptions, newValues)
|
||||
// {
|
||||
// if (baseOptions == null)
|
||||
// return;
|
||||
|
||||
// AutoReconnect = newValues?.AutoReconnect ?? baseOptions.AutoReconnect;
|
||||
// ReconnectInterval = newValues?.ReconnectInterval ?? baseOptions.ReconnectInterval;
|
||||
// MaxConcurrentResubscriptionsPerSocket = newValues?.MaxConcurrentResubscriptionsPerSocket ?? baseOptions.MaxConcurrentResubscriptionsPerSocket;
|
||||
// SocketResponseTimeout = newValues?.SocketResponseTimeout ?? baseOptions.SocketResponseTimeout;
|
||||
// SocketNoDataTimeout = newValues?.SocketNoDataTimeout ?? baseOptions.SocketNoDataTimeout;
|
||||
// SocketSubscriptionsCombineTarget = newValues?.SocketSubscriptionsCombineTarget ?? baseOptions.SocketSubscriptionsCombineTarget;
|
||||
// MaxSocketConnections = newValues?.MaxSocketConnections ?? baseOptions.MaxSocketConnections;
|
||||
// DelayAfterConnect = newValues?.DelayAfterConnect ?? baseOptions.DelayAfterConnect;
|
||||
// }
|
||||
|
||||
// /// <inheritdoc />
|
||||
// public override string ToString()
|
||||
// {
|
||||
// return $"{base.ToString()}, AutoReconnect: {AutoReconnect}, ReconnectInterval: {ReconnectInterval}, MaxConcurrentResubscriptionsPerSocket: {MaxConcurrentResubscriptionsPerSocket}, SocketResponseTimeout: {SocketResponseTimeout:c}, SocketNoDataTimeout: {SocketNoDataTimeout}, SocketSubscriptionsCombineTarget: {SocketSubscriptionsCombineTarget}, MaxSocketConnections: {MaxSocketConnections}";
|
||||
// }
|
||||
// }
|
||||
|
||||
// /// <summary>
|
||||
// /// Base for order book options
|
||||
// /// </summary>
|
||||
// public class OrderBookOptions : ClientOptions
|
||||
// {
|
||||
// /// <summary>
|
||||
// /// Whether or not checksum validation is enabled. Default is true, disabling will ignore checksum messages.
|
||||
// /// </summary>
|
||||
// public bool ChecksumValidationEnabled { get; set; } = true;
|
||||
// }
|
||||
|
||||
//}
|
||||
@@ -9,8 +9,19 @@ namespace CryptoExchange.Net.Objects
|
||||
/// </summary>
|
||||
public class TraceLoggerProvider : ILoggerProvider
|
||||
{
|
||||
private readonly LogLevel _logLevel;
|
||||
|
||||
/// <summary>
|
||||
/// ctor
|
||||
/// </summary>
|
||||
/// <param name="logLevel"></param>
|
||||
public TraceLoggerProvider(LogLevel? logLevel = null)
|
||||
{
|
||||
_logLevel = logLevel ?? LogLevel.Trace;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ILogger CreateLogger(string categoryName) => new TraceLogger(categoryName);
|
||||
public ILogger CreateLogger(string categoryName) => new TraceLogger(categoryName, _logLevel);
|
||||
/// <inheritdoc />
|
||||
public void Dispose() { }
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Runtime.InteropServices;
|
||||
using CryptoExchange.Net.Interfaces;
|
||||
using CryptoExchange.Net.Objects;
|
||||
using CryptoExchange.Net.Objects.Options;
|
||||
|
||||
namespace CryptoExchange.Net.Requests
|
||||
{
|
||||
@@ -14,12 +16,27 @@ namespace CryptoExchange.Net.Requests
|
||||
private HttpClient? _httpClient;
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Configure(TimeSpan requestTimeout, HttpClient? client = null)
|
||||
public void Configure(ApiProxy? proxy, TimeSpan requestTimeout, HttpClient? client = null)
|
||||
{
|
||||
_httpClient = client ?? new HttpClient()
|
||||
if (client == null)
|
||||
{
|
||||
Timeout = requestTimeout
|
||||
};
|
||||
var handler = new HttpClientHandler();
|
||||
if (proxy != null)
|
||||
{
|
||||
handler.Proxy = new WebProxy
|
||||
{
|
||||
Address = new Uri($"{proxy.Host}:{proxy.Port}"),
|
||||
Credentials = proxy.Password == null ? null : new NetworkCredential(proxy.Login, proxy.Password)
|
||||
};
|
||||
}
|
||||
|
||||
client = new HttpClient(handler)
|
||||
{
|
||||
Timeout = requestTimeout
|
||||
};
|
||||
}
|
||||
|
||||
_httpClient = client;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -107,6 +107,11 @@ namespace CryptoExchange.Net.Sockets
|
||||
/// Tag for identificaion
|
||||
/// </summary>
|
||||
public string Tag { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Additional properties for this connection
|
||||
/// </summary>
|
||||
public Dictionary<string, object> Properties { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// If activity is paused
|
||||
@@ -170,6 +175,7 @@ namespace CryptoExchange.Net.Sockets
|
||||
_logger = logger;
|
||||
ApiClient = apiClient;
|
||||
Tag = tag;
|
||||
Properties = new Dictionary<string, object>();
|
||||
|
||||
_pendingRequests = new List<PendingRequest>();
|
||||
_subscriptions = new List<SocketSubscription>();
|
||||
|
||||
@@ -5,20 +5,15 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Binance.Net" Version="8.0.6" />
|
||||
<PackageReference Include="Bitfinex.Net" Version="5.0.3" />
|
||||
<PackageReference Include="Bittrex.Net" Version="7.0.4" />
|
||||
<PackageReference Include="Bybit.Net" Version="0.0.4" />
|
||||
<PackageReference Include="CoinEx.Net" Version="5.0.3" />
|
||||
<PackageReference Include="FTX.Net" Version="1.0.4" />
|
||||
<PackageReference Include="Huobi.Net" Version="4.0.4" />
|
||||
<PackageReference Include="KrakenExchange.Net" Version="3.0.3" />
|
||||
<PackageReference Include="Kucoin.Net" Version="4.0.3" />
|
||||
<PackageReference Include="Serilog.AspNetCore" Version="4.1.1-dev-00250" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Data\" />
|
||||
<PackageReference Include="Binance.Net" Version="9.0.1" />
|
||||
<PackageReference Include="Bitfinex.Net" Version="6.0.0" />
|
||||
<PackageReference Include="Bittrex.Net" Version="8.0.0" />
|
||||
<PackageReference Include="Bybit.Net" Version="3.0.0" />
|
||||
<PackageReference Include="CoinEx.Net" Version="6.0.0" />
|
||||
<PackageReference Include="Huobi.Net" Version="5.0.0" />
|
||||
<PackageReference Include="KrakenExchange.Net" Version="4.0.0" />
|
||||
<PackageReference Include="Kucoin.Net" Version="5.0.0" />
|
||||
<PackageReference Include="Serilog.AspNetCore" Version="6.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
@page "/"
|
||||
@inject IBinanceClient binanceClient
|
||||
@inject IBitfinexClient bitfinexClient
|
||||
@inject IBittrexClient bittrexClient
|
||||
@inject IBybitClient bybitClient
|
||||
@inject ICoinExClient coinexClient
|
||||
@inject IFTXClient ftxClient
|
||||
@inject IHuobiClient huobiClient
|
||||
@inject IKrakenClient krakenClient
|
||||
@inject IKucoinClient kucoinClient
|
||||
@inject IBinanceRestClient binanceClient
|
||||
@inject IBitfinexRestClient bitfinexClient
|
||||
@inject IBittrexRestClient bittrexClient
|
||||
@inject IBybitRestClient bybitClient
|
||||
@inject ICoinExRestClient coinexClient
|
||||
@inject IHuobiRestClient huobiClient
|
||||
@inject IKrakenRestClient krakenClient
|
||||
@inject IKucoinRestClient kucoinClient
|
||||
|
||||
<h3>BTC-USD prices:</h3>
|
||||
@foreach(var price in _prices.OrderBy(p => p.Key))
|
||||
@@ -23,14 +22,13 @@
|
||||
var binanceTask = binanceClient.SpotApi.ExchangeData.GetTickerAsync("BTCUSDT");
|
||||
var bitfinexTask = bitfinexClient.SpotApi.ExchangeData.GetTickerAsync("tBTCUSD");
|
||||
var bittrexTask = bittrexClient.SpotApi.ExchangeData.GetTickerAsync("BTC-USDT");
|
||||
var bybitTask = bybitClient.SpotApi.ExchangeData.GetTickerAsync("BTCUSDT");
|
||||
var bybitTask = bybitClient.V5Api.ExchangeData.GetSpotTickersAsync("BTCUSDT");
|
||||
var coinexTask = coinexClient.SpotApi.ExchangeData.GetTickerAsync("BTCUSDT");
|
||||
var ftxTask = ftxClient.TradeApi.ExchangeData.GetSymbolAsync("BTC/USD");
|
||||
var huobiTask = huobiClient.SpotApi.ExchangeData.GetTickerAsync("btcusdt");
|
||||
var krakenTask = krakenClient.SpotApi.ExchangeData.GetTickerAsync("XBTUSD");
|
||||
var kucoinTask = kucoinClient.SpotApi.ExchangeData.GetTickerAsync("BTC-USDT");
|
||||
|
||||
await Task.WhenAll(binanceTask, bitfinexTask, bittrexTask, bybitTask, coinexTask, ftxTask, huobiTask, krakenTask, kucoinTask);
|
||||
await Task.WhenAll(binanceTask, bitfinexTask, bittrexTask, bybitTask, coinexTask, huobiTask, krakenTask, kucoinTask);
|
||||
|
||||
if (binanceTask.Result.Success)
|
||||
_prices.Add("Binance", binanceTask.Result.Data.LastPrice);
|
||||
@@ -42,14 +40,11 @@
|
||||
_prices.Add("Bittrex", bittrexTask.Result.Data.LastPrice);
|
||||
|
||||
if (bybitTask.Result.Success)
|
||||
_prices.Add("Bybit", bybitTask.Result.Data.LastPrice);
|
||||
_prices.Add("Bybit", bybitTask.Result.Data.List.First().LastPrice);
|
||||
|
||||
if (coinexTask.Result.Success)
|
||||
_prices.Add("CoinEx", coinexTask.Result.Data.Ticker.LastPrice);
|
||||
|
||||
if (ftxTask.Result.Success)
|
||||
_prices.Add("FTX", ftxTask.Result.Data.LastPrice ?? 0);
|
||||
|
||||
if (huobiTask.Result.Success)
|
||||
_prices.Add("Huobi", huobiTask.Result.Data.ClosePrice ?? 0);
|
||||
|
||||
|
||||
@@ -4,21 +4,12 @@
|
||||
@inject IBittrexSocketClient bittrexSocketClient
|
||||
@inject IBybitSocketClient bybitSocketClient
|
||||
@inject ICoinExSocketClient coinExSocketClient
|
||||
@inject IFTXSocketClient ftxSocketClient
|
||||
@inject IHuobiSocketClient huobiSocketClient
|
||||
@inject IKrakenSocketClient krakenSocketClient
|
||||
@inject IKucoinSocketClient kucoinSocketClient
|
||||
@using Binance.Net.Clients.SpotApi
|
||||
@using Bitfinex.Net.Clients.SpotApi
|
||||
@using Bittrex.Net.Clients.SpotApi
|
||||
@using Bybit.Net.Clients.SpotApi
|
||||
@using CoinEx.Net.Clients.SpotApi
|
||||
@using System.Collections.Concurrent
|
||||
@using CryptoExchange.Net.Objects
|
||||
@using CryptoExchange.Net.Sockets
|
||||
@using Huobi.Net.Clients.SpotApi
|
||||
@using Kraken.Net.Clients.SpotApi
|
||||
@using Kucoin.Net.Clients.SpotApi
|
||||
@using System.Collections.Concurrent
|
||||
@implements IDisposable
|
||||
|
||||
<h3>ETH-BTC prices, live updates:</h3>
|
||||
@@ -35,15 +26,14 @@
|
||||
{
|
||||
var tasks = new Task<CallResult<UpdateSubscription>>[]
|
||||
{
|
||||
binanceSocketClient.SpotStreams.SubscribeToTickerUpdatesAsync("ETHBTC", data => UpdateData("Binance", data.Data.LastPrice)),
|
||||
bitfinexSocketClient.SpotStreams.SubscribeToTickerUpdatesAsync("tETHBTC", data => UpdateData("Bitfinex", data.Data.LastPrice)),
|
||||
bittrexSocketClient.SpotStreams.SubscribeToTickerUpdatesAsync("ETH-BTC", data => UpdateData("Bittrex", data.Data.LastPrice)),
|
||||
bybitSocketClient.SpotStreams.SubscribeToTickerUpdatesAsync("ETHBTC", data => UpdateData("Bybit", data.Data.LastPrice)),
|
||||
coinExSocketClient.SpotStreams.SubscribeToTickerUpdatesAsync("ETHBTC", data => UpdateData("CoinEx", data.Data.LastPrice)),
|
||||
ftxSocketClient.Streams.SubscribeToTickerUpdatesAsync("ETH/BTC", data => UpdateData("FTX", data.Data.LastPrice ?? 0)),
|
||||
huobiSocketClient.SpotStreams.SubscribeToTickerUpdatesAsync("ethbtc", data => UpdateData("Huobi", data.Data.ClosePrice ?? 0)),
|
||||
krakenSocketClient.SpotStreams.SubscribeToTickerUpdatesAsync("ETH/XBT", data => UpdateData("Kraken", data.Data.LastTrade.Price)),
|
||||
kucoinSocketClient.SpotStreams.SubscribeToTickerUpdatesAsync("ETH-BTC", data => UpdateData("Kucoin", data.Data.LastPrice ?? 0)),
|
||||
binanceSocketClient.SpotApi.ExchangeData.SubscribeToTickerUpdatesAsync("ETHBTC", data => UpdateData("Binance", data.Data.LastPrice)),
|
||||
bitfinexSocketClient.SpotApi.SubscribeToTickerUpdatesAsync("tETHBTC", data => UpdateData("Bitfinex", data.Data.LastPrice)),
|
||||
bittrexSocketClient.SpotApi.SubscribeToTickerUpdatesAsync("ETH-BTC", data => UpdateData("Bittrex", data.Data.LastPrice)),
|
||||
bybitSocketClient.V5SpotApi.SubscribeToTickerUpdatesAsync("ETHBTC", data => UpdateData("Bybit", data.Data.LastPrice)),
|
||||
coinExSocketClient.SpotApi.SubscribeToTickerUpdatesAsync("ETHBTC", data => UpdateData("CoinEx", data.Data.LastPrice)),
|
||||
huobiSocketClient.SpotApi.SubscribeToTickerUpdatesAsync("ethbtc", data => UpdateData("Huobi", data.Data.ClosePrice ?? 0)),
|
||||
krakenSocketClient.SpotApi.SubscribeToTickerUpdatesAsync("ETH/XBT", data => UpdateData("Kraken", data.Data.LastTrade.Price)),
|
||||
kucoinSocketClient.SpotApi.SubscribeToTickerUpdatesAsync("ETH-BTC", data => UpdateData("Kucoin", data.Data.LastPrice ?? 0)),
|
||||
};
|
||||
|
||||
await Task.WhenAll(tasks);
|
||||
|
||||
@@ -1,19 +1,24 @@
|
||||
@page "/OrderBooks"
|
||||
@using Binance.Net.SymbolOrderBooks
|
||||
@using Bitfinex.Net.SymbolOrderBooks
|
||||
@using Bittrex.Net.SymbolOrderBooks
|
||||
@using Bybit.Net.SymbolOrderBooks
|
||||
@using CryptoExchange.Net.Interfaces
|
||||
@using CryptoExchange.Net.Objects
|
||||
@using CryptoExchange.Net.Sockets
|
||||
@using CoinEx.Net.SymbolOrderBooks
|
||||
@using FTX.Net.SymbolOrderBooks
|
||||
@using Huobi.Net.SymbolOrderBooks
|
||||
@using Kraken.Net.SymbolOrderBooks
|
||||
@using Kucoin.Net.Clients
|
||||
@using Kucoin.Net.SymbolOrderBooks
|
||||
@using System.Collections.Concurrent
|
||||
@using System.Timers
|
||||
@using Binance.Net.Interfaces
|
||||
@using Bitfinex.Net.Interfaces
|
||||
@using Bittrex.Net.Interfaces
|
||||
@using Bybit.Net.Interfaces
|
||||
@using CoinEx.Net.Interfaces
|
||||
@using CryptoExchange.Net.Interfaces
|
||||
@using Huobi.Net.Interfaces
|
||||
@using Kraken.Net.Interfaces
|
||||
@using Kucoin.Net.Clients
|
||||
@using Kucoin.Net.Interfaces
|
||||
@inject IBinanceOrderBookFactory binanceFactory
|
||||
@inject IBitfinexOrderBookFactory bitfinexFactory
|
||||
@inject IBittrexOrderBookFactory bittrexFactory
|
||||
@inject IBybitOrderBookFactory bybitFactory
|
||||
@inject ICoinExOrderBookFactory coinExFactory
|
||||
@inject IHuobiOrderBookFactory huobiFactory
|
||||
@inject IKrakenOrderBookFactory krakenFactory
|
||||
@inject IKucoinOrderBookFactory kucoinFactory
|
||||
@implements IDisposable
|
||||
|
||||
<h3>ETH-BTC books, live updates:</h3>
|
||||
@@ -40,22 +45,21 @@
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
// Since the Kucoin order book stream needs authentication we will need to provide API credentials beforehand
|
||||
KucoinClient.SetDefaultOptions(new Kucoin.Net.Objects.KucoinClientOptions
|
||||
{
|
||||
ApiCredentials = new Kucoin.Net.Objects.KucoinApiCredentials("KEY", "SECRET", "PASSPHRASE")
|
||||
});
|
||||
KucoinRestClient.SetDefaultOptions(options =>
|
||||
{
|
||||
options.ApiCredentials = new Kucoin.Net.Objects.KucoinApiCredentials("KEY", "SECRET", "PASSPHRASE");
|
||||
});
|
||||
|
||||
_books = new Dictionary<string, ISymbolOrderBook>
|
||||
{
|
||||
{ "Binance", new BinanceSpotSymbolOrderBook("ETHBTC") },
|
||||
{ "Bitfinex", new BitfinexSymbolOrderBook("tETHBTC") },
|
||||
{ "Bittrex", new BittrexSymbolOrderBook("ETH-BTC") },
|
||||
{ "Bybit", new BybitSpotSymbolOrderBook("ETHBTC") },
|
||||
{ "CoinEx", new CoinExSpotSymbolOrderBook("ETHBTC") },
|
||||
{ "FTX", new FTXSymbolOrderBook("ETH/BTC") },
|
||||
{ "Huobi", new HuobiSpotSymbolOrderBook("ethbtc") },
|
||||
{ "Kraken", new KrakenSpotSymbolOrderBook("ETH/XBT") },
|
||||
{ "Kucoin", new KucoinSpotSymbolOrderBook("ETH-BTC") },
|
||||
{ "Binance", binanceFactory.CreateSpot("ETHBTC") },
|
||||
{ "Bitfinex", bitfinexFactory.Create("tETHBTC") },
|
||||
{ "Bittrex", bittrexFactory.Create("ETH-BTC") },
|
||||
{ "Bybit", bybitFactory.Create("ETHBTC", Bybit.Net.Enums.Category.Spot) },
|
||||
{ "CoinEx", coinExFactory.CreateSpot("ETHBTC") },
|
||||
{ "Huobi", huobiFactory.CreateSpot("ethbtc") },
|
||||
{ "Kraken", krakenFactory.CreateSpot("ETH/XBT") },
|
||||
{ "Kucoin", kucoinFactory.CreateSpot("ETH-BTC") },
|
||||
};
|
||||
|
||||
await Task.WhenAll(_books.Select(b => b.Value.StartAsync()));
|
||||
@@ -70,7 +74,7 @@
|
||||
{
|
||||
_timer.Stop();
|
||||
_timer.Dispose();
|
||||
foreach (var book in _books)
|
||||
foreach (var book in _books.Where(b => b.Value.Status != CryptoExchange.Net.Objects.OrderBookStatus.Disconnected))
|
||||
// It's not necessary to wait for this
|
||||
_ = book.Value.StopAsync();
|
||||
}
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
@page "/SpotClient"
|
||||
@inject IBinanceClient binanceClient
|
||||
@inject IBitfinexClient bitfinexClient
|
||||
@inject IBittrexClient bittrexClient
|
||||
@inject IBybitClient bybitClient
|
||||
@inject ICoinExClient coinexClient
|
||||
@inject IFTXClient ftxClient
|
||||
@inject IHuobiClient huobiClient
|
||||
@inject IKrakenClient krakenClient
|
||||
@inject IKucoinClient kucoinClient
|
||||
@inject IBinanceRestClient binanceClient
|
||||
@inject IBitfinexRestClient bitfinexClient
|
||||
@inject IBittrexRestClient bittrexClient
|
||||
@inject IBybitRestClient bybitClient
|
||||
@inject ICoinExRestClient coinexClient
|
||||
@inject IHuobiRestClient huobiClient
|
||||
@inject IKrakenRestClient krakenClient
|
||||
@inject IKucoinRestClient kucoinClient
|
||||
@using Binance.Net.Clients.SpotApi
|
||||
@using Bitfinex.Net.Clients.SpotApi
|
||||
@using Bittrex.Net.Clients.SpotApi
|
||||
@@ -15,7 +14,6 @@
|
||||
@using CoinEx.Net.Clients.SpotApi
|
||||
@using CryptoExchange.Net.Interfaces
|
||||
@using CryptoExchange.Net.Interfaces.CommonClients
|
||||
@using FTX.Net.Clients.TradeApi
|
||||
@using Huobi.Net.Clients.SpotApi
|
||||
@using Kraken.Net.Clients.SpotApi
|
||||
@using Kucoin.Net.Clients.SpotApi
|
||||
@@ -37,9 +35,8 @@
|
||||
binanceClient.SpotApi.CommonSpotClient,
|
||||
bitfinexClient.SpotApi.CommonSpotClient,
|
||||
bittrexClient.SpotApi.CommonSpotClient,
|
||||
bybitClient.SpotApi.CommonSpotClient,
|
||||
coinexClient.SpotApi.CommonSpotClient,
|
||||
ftxClient.TradeApi.CommonSpotClient,
|
||||
bybitClient.SpotApiV1.CommonSpotClient,
|
||||
coinexClient.SpotApi.CommonSpotClient,
|
||||
huobiClient.SpotApi.CommonSpotClient,
|
||||
krakenClient.SpotApi.CommonSpotClient,
|
||||
kucoinClient.SpotApi.CommonSpotClient
|
||||
|
||||
@@ -2,15 +2,11 @@ using System.Collections.Generic;
|
||||
using Binance.Net;
|
||||
using Binance.Net.Clients;
|
||||
using Binance.Net.Interfaces.Clients;
|
||||
using Binance.Net.Objects;
|
||||
using Bitfinex.Net;
|
||||
using Bittrex.Net;
|
||||
using Bybit.Net;
|
||||
using CoinEx.Net;
|
||||
using CoinEx.Net.Clients;
|
||||
using CoinEx.Net.Interfaces.Clients;
|
||||
using CryptoExchange.Net.Authentication;
|
||||
using FTX.Net;
|
||||
using Huobi.Net;
|
||||
using Kraken.Net;
|
||||
using Kucoin.Net;
|
||||
@@ -43,35 +39,18 @@ namespace BlazorClient
|
||||
services.AddServerSideBlazor();
|
||||
|
||||
// Register the clients, options can be provided in the callback parameter
|
||||
services.AddBinance((restClientOptions, socketClientOptions) => {
|
||||
restClientOptions.ApiCredentials = new ApiCredentials("KEY", "SECRET");
|
||||
restClientOptions.LogLevel = LogLevel.Trace;
|
||||
|
||||
// Point the logging to use the ILogger configuration, which uses Serilog here
|
||||
restClientOptions.LogWriters = new List<ILogger> { _loggerFactory.CreateLogger<IBinanceClient>() };
|
||||
|
||||
socketClientOptions.ApiCredentials = new ApiCredentials("KEY", "SECRET");
|
||||
});
|
||||
|
||||
BinanceClient.SetDefaultOptions(new BinanceClientOptions
|
||||
services.AddBinance(restOptions =>
|
||||
{
|
||||
ApiCredentials = new ApiCredentials("KEY", "SECRET"),
|
||||
LogLevel = LogLevel.Trace
|
||||
});
|
||||
|
||||
BinanceSocketClient.SetDefaultOptions(new BinanceSocketClientOptions
|
||||
restOptions.ApiCredentials = new ApiCredentials("KEY", "SECRET");
|
||||
}, socketOptions =>
|
||||
{
|
||||
ApiCredentials = new ApiCredentials("KEY", "SECRET"),
|
||||
socketOptions.ApiCredentials = new ApiCredentials("KEY", "SECRET");
|
||||
});
|
||||
|
||||
services.AddTransient<IBinanceClient, BinanceClient>();
|
||||
services.AddScoped<IBinanceSocketClient, BinanceSocketClient>();
|
||||
|
||||
services.AddBitfinex();
|
||||
services.AddBittrex();
|
||||
services.AddBybit();
|
||||
services.AddCoinEx();
|
||||
services.AddFTX();
|
||||
services.AddHuobi();
|
||||
services.AddKraken();
|
||||
services.AddKucoin();
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
@using Bittrex.Net.Interfaces.Clients;
|
||||
@using Bybit.Net.Interfaces.Clients;
|
||||
@using CoinEx.Net.Interfaces.Clients;
|
||||
@using FTX.Net.Interfaces.Clients;
|
||||
@using Huobi.Net.Interfaces.Clients;
|
||||
@using Kraken.Net.Interfaces.Clients;
|
||||
@using Kucoin.Net.Interfaces.Clients;
|
||||
@@ -6,15 +6,14 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Binance.Net" Version="8.0.6" />
|
||||
<PackageReference Include="Bitfinex.Net" Version="5.0.3" />
|
||||
<PackageReference Include="Bittrex.Net" Version="7.0.4" />
|
||||
<PackageReference Include="Bybit.Net" Version="0.0.4" />
|
||||
<PackageReference Include="CoinEx.Net" Version="5.0.3" />
|
||||
<PackageReference Include="FTX.Net" Version="1.0.4" />
|
||||
<PackageReference Include="Huobi.Net" Version="4.0.4" />
|
||||
<PackageReference Include="KrakenExchange.Net" Version="3.0.3" />
|
||||
<PackageReference Include="Kucoin.Net" Version="4.0.3" />
|
||||
<PackageReference Include="Binance.Net" Version="9.0.1" />
|
||||
<PackageReference Include="Bitfinex.Net" Version="6.0.0" />
|
||||
<PackageReference Include="Bittrex.Net" Version="8.0.0" />
|
||||
<PackageReference Include="Bybit.Net" Version="3.0.0" />
|
||||
<PackageReference Include="CoinEx.Net" Version="6.0.0" />
|
||||
<PackageReference Include="Huobi.Net" Version="5.0.0" />
|
||||
<PackageReference Include="KrakenExchange.Net" Version="4.0.0" />
|
||||
<PackageReference Include="Kucoin.Net" Version="5.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -17,21 +17,21 @@ namespace ConsoleClient.Exchanges
|
||||
|
||||
public async Task<WebCallResult> CancelOrder(string symbol, string id)
|
||||
{
|
||||
using var client = new BinanceClient();
|
||||
using var client = new BinanceRestClient();
|
||||
var result = await client.SpotApi.Trading.CancelOrderAsync(symbol, long.Parse(id));
|
||||
return result.AsDataless();
|
||||
}
|
||||
|
||||
public async Task<Dictionary<string, decimal>> GetBalances()
|
||||
{
|
||||
using var client = new BinanceClient();
|
||||
using var client = new BinanceRestClient();
|
||||
var result = await client.SpotApi.Account.GetAccountInfoAsync();
|
||||
return result.Data.Balances.ToDictionary(b => b.Asset, b => b.Total);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<OpenOrder>> GetOpenOrders()
|
||||
{
|
||||
using var client = new BinanceClient();
|
||||
using var client = new BinanceRestClient();
|
||||
var result = await client.SpotApi.Trading.GetOpenOrdersAsync();
|
||||
// Should check result success status here
|
||||
return result.Data.Select(o => new OpenOrder
|
||||
@@ -49,7 +49,7 @@ namespace ConsoleClient.Exchanges
|
||||
|
||||
public async Task<decimal> GetPrice(string symbol)
|
||||
{
|
||||
using var client = new BinanceClient();
|
||||
using var client = new BinanceRestClient();
|
||||
var result = await client.SpotApi.ExchangeData.GetPriceAsync(symbol);
|
||||
// Should check result success status here
|
||||
return result.Data.Price;
|
||||
@@ -57,7 +57,7 @@ namespace ConsoleClient.Exchanges
|
||||
|
||||
public async Task<WebCallResult<string>> PlaceOrder(string symbol, string side, string type, decimal quantity, decimal? price)
|
||||
{
|
||||
using var client = new BinanceClient();
|
||||
using var client = new BinanceRestClient();
|
||||
var result = await client.SpotApi.Trading.PlaceOrderAsync(
|
||||
symbol,
|
||||
side.ToLower() == "buy" ? Binance.Net.Enums.OrderSide.Buy: Binance.Net.Enums.OrderSide.Sell,
|
||||
@@ -70,7 +70,7 @@ namespace ConsoleClient.Exchanges
|
||||
|
||||
public async Task<UpdateSubscription> SubscribePrice(string symbol, Action<decimal> handler)
|
||||
{
|
||||
var sub = await _socketClient.SpotStreams.SubscribeToMiniTickerUpdatesAsync(symbol, data => handler(data.Data.LastPrice));
|
||||
var sub = await _socketClient.SpotApi.ExchangeData.SubscribeToMiniTickerUpdatesAsync(symbol, data => handler(data.Data.LastPrice));
|
||||
return sub.Data;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
using Bybit.Net.Clients;
|
||||
using Bybit.Net.Interfaces.Clients;
|
||||
using ConsoleClient.Models;
|
||||
using CryptoExchange.Net.Objects;
|
||||
using CryptoExchange.Net.Sockets;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace ConsoleClient.Exchanges
|
||||
{
|
||||
internal class BybitExchange : IExchange
|
||||
{
|
||||
private IBybitSocketClient _socketClient = new BybitSocketClient();
|
||||
|
||||
public async Task<WebCallResult> CancelOrder(string symbol, string id)
|
||||
{
|
||||
using var client = new BybitRestClient();
|
||||
var result = await client.V5Api.Trading.CancelOrderAsync(Bybit.Net.Enums.Category.Spot, symbol, id);
|
||||
return result.AsDataless();
|
||||
}
|
||||
|
||||
public async Task<Dictionary<string, decimal>> GetBalances()
|
||||
{
|
||||
using var client = new BybitRestClient();
|
||||
var result = await client.V5Api.Account.GetBalancesAsync(Bybit.Net.Enums.AccountType.Spot);
|
||||
return result.Data.List.First().Assets.ToDictionary(d => d.Asset, d => d.WalletBalance);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<OpenOrder>> GetOpenOrders()
|
||||
{
|
||||
using var client = new BybitRestClient();
|
||||
var order = await client.V5Api.Trading.GetOrdersAsync(Bybit.Net.Enums.Category.Spot);
|
||||
return order.Data.List.Select(o => new OpenOrder
|
||||
{
|
||||
Symbol = o.Symbol,
|
||||
OrderSide = o.Side.ToString(),
|
||||
OrderStatus = o.Status.ToString(),
|
||||
OrderTime = o.CreateTime,
|
||||
OrderType = o.OrderType.ToString(),
|
||||
Price = o.Price ?? 0,
|
||||
Quantity = o.Quantity,
|
||||
QuantityFilled = o.QuantityFilled ?? 0
|
||||
});
|
||||
}
|
||||
|
||||
public async Task<decimal> GetPrice(string symbol)
|
||||
{
|
||||
using var client = new BybitRestClient();
|
||||
var result = await client.V5Api.ExchangeData.GetSpotTickersAsync(symbol);
|
||||
return result.Data.List.First().LastPrice;
|
||||
}
|
||||
|
||||
public async Task<WebCallResult<string>> PlaceOrder(string symbol, string side, string type, decimal quantity, decimal? price)
|
||||
{
|
||||
using var client = new BybitRestClient();
|
||||
var result = await client.V5Api.Trading.PlaceOrderAsync(
|
||||
Bybit.Net.Enums.Category.Spot,
|
||||
symbol,
|
||||
side.ToLower() == "buy" ? Bybit.Net.Enums.OrderSide.Buy : Bybit.Net.Enums.OrderSide.Sell,
|
||||
type == "market" ? Bybit.Net.Enums.NewOrderType.Market : Bybit.Net.Enums.NewOrderType.Limit,
|
||||
quantity,
|
||||
price: price);
|
||||
return result.As(result.Data?.OrderId.ToString());
|
||||
}
|
||||
|
||||
public async Task<UpdateSubscription> SubscribePrice(string symbol, Action<decimal> handler)
|
||||
{
|
||||
var sub = await _socketClient.V5SpotApi.SubscribeToTickerUpdatesAsync(symbol, data => handler(data.Data.LastPrice));
|
||||
return sub.Data;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
using ConsoleClient.Models;
|
||||
using CryptoExchange.Net.Objects;
|
||||
using CryptoExchange.Net.Sockets;
|
||||
using FTX.Net.Clients;
|
||||
using FTX.Net.Interfaces.Clients;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace ConsoleClient.Exchanges
|
||||
{
|
||||
internal class FTXExchange : IExchange
|
||||
{
|
||||
private IFTXSocketClient _socketClient = new FTXSocketClient();
|
||||
|
||||
public async Task<WebCallResult> CancelOrder(string symbol, string id)
|
||||
{
|
||||
using var client = new FTXClient();
|
||||
var result = await client.TradeApi.Trading.CancelOrderAsync(long.Parse(id));
|
||||
return result.AsDataless();
|
||||
}
|
||||
|
||||
public async Task<Dictionary<string, decimal>> GetBalances()
|
||||
{
|
||||
using var client = new FTXClient();
|
||||
var result = await client.TradeApi.Account.GetBalancesAsync();
|
||||
return result.Data.ToDictionary(d => d.Asset, d => d.Total);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<OpenOrder>> GetOpenOrders()
|
||||
{
|
||||
using var client = new FTXClient();
|
||||
var order = await client.TradeApi.Trading.GetOpenOrdersAsync();
|
||||
return order.Data.Select(o => new OpenOrder
|
||||
{
|
||||
Symbol = o.Symbol,
|
||||
OrderSide = o.Side.ToString(),
|
||||
OrderStatus = o.Status.ToString(),
|
||||
OrderTime = o.CreateTime,
|
||||
OrderType = o.Type.ToString(),
|
||||
Price = o.Price ?? 0,
|
||||
Quantity = o.Quantity,
|
||||
QuantityFilled = o.QuantityFilled ?? 0
|
||||
});
|
||||
}
|
||||
|
||||
public async Task<decimal> GetPrice(string symbol)
|
||||
{
|
||||
using var client = new FTXClient();
|
||||
var result = await client.TradeApi.ExchangeData.GetSymbolAsync(symbol);
|
||||
return result.Data.LastPrice ?? 0;
|
||||
}
|
||||
|
||||
public async Task<WebCallResult<string>> PlaceOrder(string symbol, string side, string type, decimal quantity, decimal? price)
|
||||
{
|
||||
using var client = new FTXClient();
|
||||
var result = await client.TradeApi.Trading.PlaceOrderAsync(
|
||||
symbol,
|
||||
side.ToLower() == "buy" ? FTX.Net.Enums.OrderSide.Buy : FTX.Net.Enums.OrderSide.Sell,
|
||||
type == "market" ? FTX.Net.Enums.OrderType.Market : FTX.Net.Enums.OrderType.Limit,
|
||||
quantity,
|
||||
price: price);
|
||||
return result.As(result.Data?.Id.ToString());
|
||||
}
|
||||
|
||||
public async Task<UpdateSubscription> SubscribePrice(string symbol, Action<decimal> handler)
|
||||
{
|
||||
var sub = await _socketClient.Streams.SubscribeToTickerUpdatesAsync(symbol, data => handler(data.Data.LastPrice ?? 0));
|
||||
return sub.Data;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,12 +5,10 @@ using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Binance.Net.Clients;
|
||||
using Binance.Net.Objects;
|
||||
using Bybit.Net.Clients;
|
||||
using ConsoleClient.Exchanges;
|
||||
using CryptoExchange.Net.Authentication;
|
||||
using CryptoExchange.Net.Sockets;
|
||||
using FTX.Net.Clients;
|
||||
using FTX.Net.Objects;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace ConsoleClient
|
||||
{
|
||||
@@ -19,20 +17,18 @@ namespace ConsoleClient
|
||||
static Dictionary<string, IExchange> _exchanges = new Dictionary<string, IExchange>
|
||||
{
|
||||
{ "Binance", new BinanceExchange() },
|
||||
{ "FTX", new FTXExchange() }
|
||||
{ "Bybit", new BybitExchange() }
|
||||
};
|
||||
|
||||
static async Task Main(string[] args)
|
||||
{
|
||||
BinanceClient.SetDefaultOptions(new BinanceClientOptions
|
||||
BinanceRestClient.SetDefaultOptions(options =>
|
||||
{
|
||||
LogLevel = LogLevel.Trace,
|
||||
ApiCredentials = new ApiCredentials("APIKEY", "APISECRET")
|
||||
options.ApiCredentials = new ApiCredentials("APIKEY", "APISECRET");
|
||||
});
|
||||
FTXClient.SetDefaultOptions(new FTXClientOptions
|
||||
BybitRestClient.SetDefaultOptions(options =>
|
||||
{
|
||||
LogLevel = LogLevel.Trace,
|
||||
ApiCredentials = new ApiCredentials("APIKEY", "APISECRET")
|
||||
options.ApiCredentials = new ApiCredentials("APIKEY", "APISECRET");
|
||||
});
|
||||
|
||||
while (true)
|
||||
|
||||
@@ -25,14 +25,22 @@ Use one of the following following referral links to signup to a new exchange to
|
||||
### Donate
|
||||
Make a one time donation in a crypto currency of your choice. If you prefer to donate a currency not listed here please contact me.
|
||||
|
||||
**Btc**: 12KwZk3r2Y3JZ2uMULcjqqBvXmpDwjhhQS
|
||||
**Eth**: 0x069176ca1a4b1d6e0b7901a6bc0dbf3bb0bf5cc2
|
||||
**Nano**: xrb_1ocs3hbp561ef76eoctjwg85w5ugr8wgimkj8mfhoyqbx4s1pbc74zggw7gs
|
||||
**Btc**: bc1qz0jv0my7fc60rxeupr23e75x95qmlq6489n8gh
|
||||
**Eth**: 0x8E21C4d955975cB645589745ac0c46ECA8FAE504
|
||||
|
||||
### Sponsor
|
||||
Alternatively, sponsor me on Github using [Github Sponsors](https://github.com/sponsors/JKorf).
|
||||
|
||||
## Release notes
|
||||
* Version 6.0.3 - 23 Jul 2023
|
||||
* Fixed Proxy not getting applied in rest clients when not using DI
|
||||
|
||||
* Version 6.0.2 - 05 Jul 2023
|
||||
* Added properties generic dictionary to SocketConnection
|
||||
|
||||
* Version 6.0.1 - 29 Jun 2023
|
||||
* Added LogLevel optional parameter to TraceLoggerProvider
|
||||
|
||||
* Version 6.0.0 - 25 Jun 2023
|
||||
* Updated ApiCredentials to support RSA signing as well as the default Hmac signature
|
||||
* Removed custom logging implementation in favor of using `Microsoft.Extensions.Logging` ILogger directly
|
||||
|
||||
+19
-19
@@ -11,15 +11,15 @@ Each implementation generally provides two different clients, which will be the
|
||||
The rest client gives access to the Rest endpoint of the API. Rest endpoints are accessed by sending an HTTP request and receiving a response. The client is split in different sub-clients, which are named API Clients. These API clients are then again split in different topics. Typically a Rest client will look like this:
|
||||
|
||||
- [ExchangeName]RestClient
|
||||
- SpotApi
|
||||
- Account
|
||||
- ExchangeData
|
||||
- Trading
|
||||
- FuturesApi
|
||||
- Account
|
||||
- ExchangeData
|
||||
- Trading
|
||||
|
||||
- SpotApi
|
||||
- Account
|
||||
- ExchangeData
|
||||
- Trading
|
||||
- FuturesApi
|
||||
- Account
|
||||
- ExchangeData
|
||||
- Trading
|
||||
|
||||
This rest client has 2 different API clients, the `SpotApi` and the `FuturesApi`, each offering their own set of endpoints.
|
||||
|
||||
*Requesting ticker info on the spot API*
|
||||
@@ -64,8 +64,8 @@ When processing the result of a call it should always be checked for success. No
|
||||
var callResult = await kucoinClient.SpotApi.ExchangeData.GetTickersAsync();
|
||||
if(!callResult.Success)
|
||||
{
|
||||
Console.WriteLine("Request failed: " + callResult.Error);
|
||||
return;
|
||||
Console.WriteLine("Request failed: " + callResult.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
Console.WriteLine("Result: " + callResult.Data);
|
||||
@@ -78,8 +78,8 @@ Just like the Rest client is divided in Rest Api clients, the Socket client is d
|
||||
```csharp
|
||||
|
||||
- KucoinSocketClient
|
||||
- SpotStreams
|
||||
- FuturesStreams
|
||||
- SpotStreams
|
||||
- FuturesStreams
|
||||
|
||||
```
|
||||
*Subscribing to updates for all tickers on the Spot Api*
|
||||
@@ -95,7 +95,7 @@ await kucoinSocketClient.SpotStreams.SubscribeToAllTickerUpdatesAsync(DataHandle
|
||||
|
||||
private static void DataHandler(DataEvent<KucoinStreamTick> updateData)
|
||||
{
|
||||
// Process updateData
|
||||
// Process updateData
|
||||
}
|
||||
```
|
||||
|
||||
@@ -103,7 +103,7 @@ private static void DataHandler(DataEvent<KucoinStreamTick> updateData)
|
||||
```csharp
|
||||
await kucoinSocketClient.SpotStreams.SubscribeToAllTickerUpdatesAsync(updateData =>
|
||||
{
|
||||
// Process updateData
|
||||
// Process updateData
|
||||
});
|
||||
```
|
||||
|
||||
@@ -122,16 +122,16 @@ Subscribing to a stream will return a `CallResult<UpdateSubscription>` object. T
|
||||
var subscriptionResult = await kucoinSocketClient.SpotStreams.SubscribeToAllTickerUpdatesAsync(DataHandler);
|
||||
if(!subscriptionResult.Success)
|
||||
{
|
||||
Console.WriteLine("Failed to connect: " + subscriptionResult.Error);
|
||||
return;
|
||||
Console.WriteLine("Failed to connect: " + subscriptionResult.Error);
|
||||
return;
|
||||
}
|
||||
subscriptionResult.Data.ConnectionLost += () =>
|
||||
{
|
||||
Console.WriteLine("Connection lost");
|
||||
Console.WriteLine("Connection lost");
|
||||
};
|
||||
subscriptionResult.Data.ConnectionRestored += (time) =>
|
||||
{
|
||||
Console.WriteLine("Connection restored");
|
||||
Console.WriteLine("Connection restored");
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
+3
-3
@@ -28,7 +28,7 @@ private void SomeMethod()
|
||||
{
|
||||
var socketClient = new BinanceSocketClient();
|
||||
socketClient.Spot.SubscribeToOrderBookUpdates("BTCUSDT", data => {
|
||||
// Handle data
|
||||
// Handle data
|
||||
});
|
||||
}
|
||||
```
|
||||
@@ -41,7 +41,7 @@ private BinanceSocketClient _socketClient = new BinanceSocketClient();
|
||||
private void SomeMethod()
|
||||
{
|
||||
_socketClient.Spot.SubscribeToOrderBookUpdates("BTCUSDT", data => {
|
||||
// Handle data
|
||||
// Handle data
|
||||
});
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ Yes, generally these are all supported and can be configured by setting the Envi
|
||||
```csharp
|
||||
var client = new BinanceRestClient(options =>
|
||||
{
|
||||
options.Environment = BinanceEnvironment.Testnet;
|
||||
options.Environment = BinanceEnvironment.Testnet;
|
||||
});
|
||||
```
|
||||
|
||||
|
||||
+5
-5
@@ -10,7 +10,7 @@ The library offers extensive logging, which depends on the dotnet `Microsoft.Ext
|
||||
```csharp
|
||||
IServiceCollection services = new ServiceCollection();
|
||||
services
|
||||
.AddBinance()
|
||||
.AddBinance()
|
||||
.AddLogging(options =>
|
||||
{
|
||||
options.SetMinimumLevel(LogLevel.Trace);
|
||||
@@ -66,8 +66,8 @@ IServiceCollection serviceCollection = new ServiceCollection();
|
||||
serviceCollection.AddBinance();
|
||||
serviceCollection.AddLogging(options =>
|
||||
{
|
||||
options.SetMinimumLevel(LogLevel.Trace);
|
||||
options.AddConsole();
|
||||
options.SetMinimumLevel(LogLevel.Trace);
|
||||
options.AddConsole();
|
||||
}).BuildServiceProvider();
|
||||
|
||||
var client = serviceCollection.GetRequiredService<IBinanceRestClient>();
|
||||
@@ -91,7 +91,7 @@ By default the `OriginalData` property in the `WebCallResult`/`DataEvent` object
|
||||
```csharp
|
||||
var client = new BinanceClient(options =>
|
||||
{
|
||||
options.OutputOriginalData = true
|
||||
options.OutputOriginalData = true
|
||||
});
|
||||
```
|
||||
|
||||
@@ -103,7 +103,7 @@ var originallyReceivedData = tickerResult.OriginalData;
|
||||
|
||||
// Socket update
|
||||
await client.SpotStreams.SubscribeToAllTickerUpdatesAsync(update => {
|
||||
var originallyRecievedData = update.OriginalData;
|
||||
var originallyRecievedData = update.OriginalData;
|
||||
});
|
||||
```
|
||||
|
||||
|
||||
@@ -27,9 +27,9 @@ The HttpClient will now be received by the DI container instead of having to pas
|
||||
```csharp
|
||||
var client = new BinanceClient(new BinanceClientOptions(){
|
||||
OutputOriginalData = true,
|
||||
SpotApiOptions = new RestApiOptions {
|
||||
BaseAddress = BinanceApiAddresses.TestNet.RestClientAddress
|
||||
}
|
||||
SpotApiOptions = new RestApiOptions {
|
||||
BaseAddress = BinanceApiAddresses.TestNet.RestClientAddress
|
||||
}
|
||||
// Other options
|
||||
});
|
||||
```
|
||||
@@ -38,7 +38,7 @@ var client = new BinanceClient(new BinanceClientOptions(){
|
||||
```csharp
|
||||
var client = new BinanceClient(options => {
|
||||
options.OutputOriginalData = true;
|
||||
options.Environment = BinanceEnvironment.Testnet;
|
||||
options.Environment = BinanceEnvironment.Testnet;
|
||||
// Other options
|
||||
});
|
||||
```
|
||||
@@ -53,10 +53,10 @@ With the change in options providing the DI extension methods for the IServiceCo
|
||||
```csharp
|
||||
builder.Services.AddKucoin((restOpts, socketOpts) =>
|
||||
{
|
||||
restOpts.LogLevel = LogLevel.Debug;
|
||||
restOpts.ApiCredentials = new KucoinApiCredentials("KEY", "SECRET", "PASS");
|
||||
socketOpts.LogLevel = LogLevel.Debug;
|
||||
socketOpts.ApiCredentials = new KucoinApiCredentials("KEY", "SECRET", "PASS");
|
||||
restOpts.LogLevel = LogLevel.Debug;
|
||||
restOpts.ApiCredentials = new KucoinApiCredentials("KEY", "SECRET", "PASS");
|
||||
socketOpts.LogLevel = LogLevel.Debug;
|
||||
socketOpts.ApiCredentials = new KucoinApiCredentials("KEY", "SECRET", "PASS");
|
||||
}, ServiceLifetime.Singleton);
|
||||
```
|
||||
|
||||
|
||||
+12
-8
@@ -12,8 +12,10 @@ Each implementation can be configured using client options. There are 2 ways to
|
||||
|
||||
BinanceClient.SetDefaultOptions(options =>
|
||||
{
|
||||
options.OutputOriginalData = true;
|
||||
options.ApiCredentials = new ApiCredentials("KEY", "SECRET");
|
||||
options.OutputOriginalData = true;
|
||||
options.ApiCredentials = new ApiCredentials("KEY", "SECRET");
|
||||
// Override the api credentials for the Spot API
|
||||
options.SpotOptions.ApiCredentials = new ApiCredentials("SPOT-KEY", "SPOT-SECRET");
|
||||
});
|
||||
|
||||
```
|
||||
@@ -23,8 +25,10 @@ BinanceClient.SetDefaultOptions(options =>
|
||||
|
||||
var client = new BinanceClient(options =>
|
||||
{
|
||||
options.OutputOriginalData = true;
|
||||
options.ApiCredentials = new ApiCredentials("KEY", "SECRET");
|
||||
options.OutputOriginalData = true;
|
||||
options.ApiCredentials = new ApiCredentials("KEY", "SECRET");
|
||||
// Override the api credentials for the Spot API
|
||||
options.SpotOptions.ApiCredentials = new ApiCredentials("SPOT-KEY", "SPOT-SECRET");
|
||||
});
|
||||
|
||||
```
|
||||
@@ -34,12 +38,12 @@ When calling `SetDefaultOptions` each client created after that will use the opt
|
||||
|
||||
BinanceClient.SetDefaultOptions(options =>
|
||||
{
|
||||
options.OutputOriginalData = true;
|
||||
options.OutputOriginalData = true;
|
||||
});
|
||||
|
||||
var client = new BinanceClient(options =>
|
||||
{
|
||||
options.OutputOriginalData = false;
|
||||
options.OutputOriginalData = false;
|
||||
});
|
||||
|
||||
```
|
||||
@@ -54,8 +58,8 @@ The options are divided in two categories. The basic options, which will apply t
|
||||
|
||||
var client = new BinanceRestClient(options =>
|
||||
{
|
||||
options.ApiCredentials = new ApiCredentials("GENERAL-KEY", "GENERAL-SECRET"),
|
||||
options.SpotOptions.ApiCredentials = new ApiCredentials("SPOT-KEY", "SPOT-SECRET");
|
||||
options.ApiCredentials = new ApiCredentials("GENERAL-KEY", "GENERAL-SECRET"),
|
||||
options.SpotOptions.ApiCredentials = new ApiCredentials("SPOT-KEY", "SPOT-SECRET");
|
||||
});
|
||||
|
||||
```
|
||||
|
||||
+5
-5
@@ -18,15 +18,15 @@ book.OnStatusChange += (oldState, newState) => Console.WriteLine($"State changed
|
||||
var startResult = await book.StartAsync();
|
||||
if (!startResult.Success)
|
||||
{
|
||||
Console.WriteLine("Failed to start order book: " + startResult.Error);
|
||||
return;
|
||||
Console.WriteLine("Failed to start order book: " + startResult.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
while(true)
|
||||
{
|
||||
Console.Clear();
|
||||
Console.WriteLine(book.ToString(3);
|
||||
await Task.Delay(500);
|
||||
Console.Clear();
|
||||
Console.WriteLine(book.ToString(3);
|
||||
await Task.Delay(500);
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
+8
-8
@@ -13,12 +13,12 @@ A rate limiter can be configured in the options like so:
|
||||
```csharp
|
||||
new ClientOptions
|
||||
{
|
||||
RateLimitingBehaviour = RateLimitingBehaviour.Wait,
|
||||
RateLimiters = new List<IRateLimiter>
|
||||
{
|
||||
new RateLimiter()
|
||||
.AddTotalRateLimit(50, TimeSpan.FromSeconds(10))
|
||||
}
|
||||
RateLimitingBehaviour = RateLimitingBehaviour.Wait,
|
||||
RateLimiters = new List<IRateLimiter>
|
||||
{
|
||||
new RateLimiter()
|
||||
.AddTotalRateLimit(50, TimeSpan.FromSeconds(10))
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -26,8 +26,8 @@ This will add a rate limiter for 50 requests per 10 seconds.
|
||||
A rate limiter can have multiple limits:
|
||||
```csharp
|
||||
new RateLimiter()
|
||||
.AddTotalRateLimit(50, TimeSpan.FromSeconds(10))
|
||||
.AddEndpointLimit("/api/order", 10, TimeSpan.FromSeconds(2))
|
||||
.AddTotalRateLimit(50, TimeSpan.FromSeconds(10))
|
||||
.AddEndpointLimit("/api/order", 10, TimeSpan.FromSeconds(2))
|
||||
```
|
||||
This adds another limit of 10 requests per 2 seconds in addition to the 50 requests per 10 seconds limit.
|
||||
These are the available rate limit configurations:
|
||||
|
||||
Reference in New Issue
Block a user