Skip to content

Commit

Permalink
CryptoExchange.Net v8.0.0, shared interfaces
Browse files Browse the repository at this point in the history
  • Loading branch information
JKorf authored Sep 27, 2024
1 parent 4426a56 commit e654fd6
Show file tree
Hide file tree
Showing 25 changed files with 2,279 additions and 53 deletions.
15 changes: 13 additions & 2 deletions Kucoin.Net/Clients/FuturesApi/KucoinRestClientFuturesApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using CryptoExchange.Net.Interfaces;
using CryptoExchange.Net.Interfaces.CommonClients;
using CryptoExchange.Net.Objects;
using CryptoExchange.Net.SharedApis;
using Kucoin.Net.Enums;
using Kucoin.Net.Interfaces.Clients.FuturesApi;
using Kucoin.Net.Objects;
Expand All @@ -21,7 +22,7 @@
namespace Kucoin.Net.Clients.FuturesApi
{
/// <inheritdoc cref="IKucoinRestClientFuturesApi" />
internal class KucoinRestClientFuturesApi : RestApiClient, IKucoinRestClientFuturesApi, IFuturesClient
internal partial class KucoinRestClientFuturesApi : RestApiClient, IKucoinRestClientFuturesApi, IFuturesClient
{
private readonly KucoinRestClient _baseClient;
private readonly KucoinRestOptions _options;
Expand Down Expand Up @@ -68,7 +69,16 @@ protected override AuthenticationProvider CreateAuthenticationProvider(ApiCreden
=> new KucoinAuthenticationProvider((KucoinApiCredentials)credentials);

/// <inheritdoc />
public override string FormatSymbol(string baseAsset, string quoteAsset) => baseAsset.ToUpperInvariant() + quoteAsset.ToUpperInvariant();
public override string FormatSymbol(string baseAsset, string quoteAsset, TradingMode tradingMode, DateTime? deliverTime = null)
{
if (baseAsset.Equals("BTC", StringComparison.OrdinalIgnoreCase))
baseAsset = "XBT";

if (!deliverTime.HasValue)
return baseAsset.ToUpperInvariant() + quoteAsset.ToUpperInvariant() + "M";

return baseAsset.ToUpperInvariant() + "M" + ExchangeHelpers.GetDeliveryMonthSymbol(deliverTime.Value) + deliverTime.Value.ToString("yy");
}

internal async Task<WebCallResult> SendAsync(RequestDefinition definition, ParameterCollection? parameters, CancellationToken cancellationToken, int? weight = null)
{
Expand Down Expand Up @@ -379,6 +389,7 @@ async Task<WebCallResult<IEnumerable<Position>>> IFuturesClient.GetPositionsAsyn

/// <inheritdoc />
public IFuturesClient CommonFuturesClient => this;
public IKucoinRestClientFuturesApiShared SharedClient => this;

private static FuturesKlineInterval GetKlineIntervalFromTimespan(TimeSpan timeSpan)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,11 @@ public async Task<WebCallResult<IEnumerable<KucoinFundingRateHistory>>> GetFundi
parameters.AddMilliseconds("to", endTime);

var request = _definitions.GetOrCreate(HttpMethod.Get, $"api/v1/contract/funding-rates", KucoinExchange.RateLimiter.PublicRest, 5);
return await _baseClient.SendAsync<IEnumerable<KucoinFundingRateHistory>>(request, parameters, ct).ConfigureAwait(false);
var result = await _baseClient.SendAsync<IEnumerable<KucoinFundingRateHistory>>(request, parameters, ct).ConfigureAwait(false);
if (result && result.Data == null)
return result.As<IEnumerable<KucoinFundingRateHistory>>(Array.Empty<KucoinFundingRateHistory>());

return result;
}

#endregion
Expand Down
739 changes: 739 additions & 0 deletions Kucoin.Net/Clients/FuturesApi/KucoinRestClientFuturesApiShared.cs

Large diffs are not rendered by default.

16 changes: 14 additions & 2 deletions Kucoin.Net/Clients/FuturesApi/KucoinSocketClientFuturesApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,12 @@
using CryptoExchange.Net.Clients;
using Newtonsoft.Json;
using Kucoin.Net.Converters;
using CryptoExchange.Net.SharedApis;

namespace Kucoin.Net.Clients.FuturesApi
{
/// <inheritdoc cref="IKucoinSocketClientFuturesApi" />
internal class KucoinSocketClientFuturesApi : SocketApiClient, IKucoinSocketClientFuturesApi
internal partial class KucoinSocketClientFuturesApi : SocketApiClient, IKucoinSocketClientFuturesApi
{
private static readonly MessagePath _idPath = MessagePath.Get().Property("id");
private static readonly MessagePath _typePath = MessagePath.Get().Property("type");
Expand Down Expand Up @@ -64,12 +65,23 @@ public override string GetListenerIdentifier(IMessageAccessor message)
return message.GetValue<string>(_topicPath)!;
}

public IKucoinSocketClientFuturesApiShared SharedClient => this;

/// <inheritdoc />
protected override AuthenticationProvider CreateAuthenticationProvider(ApiCredentials credentials)
=> new KucoinAuthenticationProvider((KucoinApiCredentials)credentials);

/// <inheritdoc />
public override string FormatSymbol(string baseAsset, string quoteAsset) => baseAsset.ToUpperInvariant() + quoteAsset.ToUpperInvariant();
public override string FormatSymbol(string baseAsset, string quoteAsset, TradingMode tradingMode, DateTime? deliverTime = null)
{
if (baseAsset.Equals("BTC", StringComparison.OrdinalIgnoreCase))
baseAsset = "XBT";

if (!deliverTime.HasValue)
return baseAsset.ToUpperInvariant() + quoteAsset.ToUpperInvariant() + "M";

return baseAsset.ToUpperInvariant() + "M" + ExchangeHelpers.GetDeliveryMonthSymbol(deliverTime.Value) + deliverTime.Value.ToString("yy");
}

/// <inheritdoc />
protected override Query? GetAuthenticationRequest(SocketConnection connection) => null;
Expand Down
189 changes: 189 additions & 0 deletions Kucoin.Net/Clients/FuturesApi/KucoinSocketClientFuturesApiShared.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
using CryptoExchange.Net.Objects;
using CryptoExchange.Net.SharedApis;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using CryptoExchange.Net.Objects.Sockets;
using Kucoin.Net.Enums;
using Kucoin.Net.Interfaces.Clients.FuturesApi;
using Kucoin.Net.Objects.Models.Futures.Socket;

namespace Kucoin.Net.Clients.FuturesApi
{
internal partial class KucoinSocketClientFuturesApi: IKucoinSocketClientFuturesApiShared
{
public string Exchange => KucoinExchange.ExchangeName;
public TradingMode[] SupportedTradingModes { get; } = new[] { TradingMode.PerpetualLinear, TradingMode.DeliveryLinear, TradingMode.PerpetualInverse, TradingMode.DeliveryInverse };

public void SetDefaultExchangeParameter(string key, object value) => ExchangeParameters.SetStaticParameter(Exchange, key, value);
public void ResetDefaultExchangeParameters() => ExchangeParameters.ResetStaticParameters();

#region Ticker client
EndpointOptions<SubscribeTickerRequest> ITickerSocketClient.SubscribeTickerOptions { get; } = new EndpointOptions<SubscribeTickerRequest>(false);
async Task<ExchangeResult<UpdateSubscription>> ITickerSocketClient.SubscribeToTickerUpdatesAsync(SubscribeTickerRequest request, Action<ExchangeEvent<SharedSpotTicker>> handler, CancellationToken ct)
{
var validationError = ((ITickerSocketClient)this).SubscribeTickerOptions.ValidateRequest(Exchange, request, request.Symbol.TradingMode, SupportedTradingModes);
if (validationError != null)
return new ExchangeResult<UpdateSubscription>(Exchange, validationError);

var symbol = request.Symbol.GetSymbol(FormatSymbol);
var result = await SubscribeTo24HourSnapshotUpdatesAsync(symbol, update => handler(update.AsExchangeEvent(Exchange, new SharedSpotTicker(symbol, update.Data.LastPrice, null, null, update.Data.Volume, update.Data.PriceChangePercentage * 100)))).ConfigureAwait(false);

return new ExchangeResult<UpdateSubscription>(Exchange, result);
}
#endregion

#region Trade client

EndpointOptions<SubscribeTradeRequest> ITradeSocketClient.SubscribeTradeOptions { get; } = new EndpointOptions<SubscribeTradeRequest>(false);
async Task<ExchangeResult<UpdateSubscription>> ITradeSocketClient.SubscribeToTradeUpdatesAsync(SubscribeTradeRequest request, Action<ExchangeEvent<IEnumerable<SharedTrade>>> handler, CancellationToken ct)
{
var validationError = ((ITradeSocketClient)this).SubscribeTradeOptions.ValidateRequest(Exchange, request, request.Symbol.TradingMode, SupportedTradingModes);
if (validationError != null)
return new ExchangeResult<UpdateSubscription>(Exchange, validationError);

var symbol = request.Symbol.GetSymbol(FormatSymbol);
var result = await SubscribeToTradeUpdatesAsync(symbol, update => handler(update.AsExchangeEvent<IEnumerable<SharedTrade>>(Exchange, new[] { new SharedTrade(update.Data.Quantity, update.Data.Price, update.Data.Timestamp) })), ct).ConfigureAwait(false);

return new ExchangeResult<UpdateSubscription>(Exchange, result);
}
#endregion

#region Book Ticker client

EndpointOptions<SubscribeBookTickerRequest> IBookTickerSocketClient.SubscribeBookTickerOptions { get; } = new EndpointOptions<SubscribeBookTickerRequest>(false);
async Task<ExchangeResult<UpdateSubscription>> IBookTickerSocketClient.SubscribeToBookTickerUpdatesAsync(SubscribeBookTickerRequest request, Action<ExchangeEvent<SharedBookTicker>> handler, CancellationToken ct)
{
var validationError = ((IBookTickerSocketClient)this).SubscribeBookTickerOptions.ValidateRequest(Exchange, request, request.Symbol.TradingMode, SupportedTradingModes);
if (validationError != null)
return new ExchangeResult<UpdateSubscription>(Exchange, validationError);

var symbol = request.Symbol.GetSymbol(FormatSymbol);
var result = await SubscribeToTickerUpdatesAsync(symbol, update => handler(update.AsExchangeEvent(Exchange, new SharedBookTicker(update.Data.BestAskPrice, update.Data.BestAskQuantity, update.Data.BestBidPrice, update.Data.BestBidQuantity))), ct).ConfigureAwait(false);

return new ExchangeResult<UpdateSubscription>(Exchange, result);
}
#endregion

#region Kline client
SubscribeKlineOptions IKlineSocketClient.SubscribeKlineOptions { get; } = new SubscribeKlineOptions(false);
async Task<ExchangeResult<UpdateSubscription>> IKlineSocketClient.SubscribeToKlineUpdatesAsync(SubscribeKlineRequest request, Action<ExchangeEvent<SharedKline>> handler, CancellationToken ct)
{
var interval = (Enums.KlineInterval)request.Interval;
if (!Enum.IsDefined(typeof(Enums.KlineInterval), interval))
return new ExchangeResult<UpdateSubscription>(Exchange, new ArgumentError("Interval not supported"));

var validationError = ((IKlineSocketClient)this).SubscribeKlineOptions.ValidateRequest(Exchange, request, request.Symbol.TradingMode, SupportedTradingModes);
if (validationError != null)
return new ExchangeResult<UpdateSubscription>(Exchange, validationError);

var symbol = request.Symbol.GetSymbol(FormatSymbol);
var result = await SubscribeToKlineUpdatesAsync(symbol, interval, update => handler(update.AsExchangeEvent(Exchange, new SharedKline(update.Data.OpenTime, update.Data.ClosePrice, update.Data.HighPrice, update.Data.LowPrice, update.Data.OpenPrice, update.Data.Volume))), ct).ConfigureAwait(false);

return new ExchangeResult<UpdateSubscription>(Exchange, result);
}
#endregion

#region Order Book client
SubscribeOrderBookOptions IOrderBookSocketClient.SubscribeOrderBookOptions { get; } = new SubscribeOrderBookOptions(false, new[] { 5, 50 });
async Task<ExchangeResult<UpdateSubscription>> IOrderBookSocketClient.SubscribeToOrderBookUpdatesAsync(SubscribeOrderBookRequest request, Action<ExchangeEvent<SharedOrderBook>> handler, CancellationToken ct)
{
var validationError = ((IOrderBookSocketClient)this).SubscribeOrderBookOptions.ValidateRequest(Exchange, request, request.Symbol.TradingMode, SupportedTradingModes);
if (validationError != null)
return new ExchangeResult<UpdateSubscription>(Exchange, validationError);

var symbol = request.Symbol.GetSymbol(FormatSymbol);
var result = await SubscribeToPartialOrderBookUpdatesAsync(symbol, request.Limit ?? 5, update => handler(update.AsExchangeEvent(Exchange, new SharedOrderBook(update.Data.Asks, update.Data.Bids))), ct).ConfigureAwait(false);

return new ExchangeResult<UpdateSubscription>(Exchange, result);
}
#endregion

#region Balance client
EndpointOptions<SubscribeBalancesRequest> IBalanceSocketClient.SubscribeBalanceOptions { get; } = new EndpointOptions<SubscribeBalancesRequest>(false);
async Task<ExchangeResult<UpdateSubscription>> IBalanceSocketClient.SubscribeToBalanceUpdatesAsync(SubscribeBalancesRequest request, Action<ExchangeEvent<IEnumerable<SharedBalance>>> handler, CancellationToken ct)
{
var validationError = ((IBalanceSocketClient)this).SubscribeBalanceOptions.ValidateRequest(Exchange, request, request.TradingMode, SupportedTradingModes);
if (validationError != null)
return new ExchangeResult<UpdateSubscription>(Exchange, validationError);
var result = await SubscribeToBalanceUpdatesAsync(
onBalanceUpdate: update => handler(update.AsExchangeEvent<IEnumerable<SharedBalance>>(Exchange, new[] { new SharedBalance(update.Data.Asset, update.Data.AvailableBalance, update.Data.AvailableBalance + update.Data.HoldBalance) })),
ct: ct).ConfigureAwait(false);

return new ExchangeResult<UpdateSubscription>(Exchange, result);
}
#endregion

#region Futures Order client

EndpointOptions<SubscribeFuturesOrderRequest> IFuturesOrderSocketClient.SubscribeFuturesOrderOptions { get; } = new EndpointOptions<SubscribeFuturesOrderRequest>(false);
async Task<ExchangeResult<UpdateSubscription>> IFuturesOrderSocketClient.SubscribeToFuturesOrderUpdatesAsync(SubscribeFuturesOrderRequest request, Action<ExchangeEvent<IEnumerable<SharedFuturesOrder>>> handler, CancellationToken ct)
{
var validationError = ((IFuturesOrderSocketClient)this).SubscribeFuturesOrderOptions.ValidateRequest(Exchange, request, request.TradingMode, SupportedTradingModes);
if (validationError != null)
return new ExchangeResult<UpdateSubscription>(Exchange, validationError);

var result = await SubscribeToOrderUpdatesAsync(
null,
update => handler(update.AsExchangeEvent<IEnumerable<SharedFuturesOrder>>(Exchange, new[] { ParseOrder(update.Data) })),
ct: ct).ConfigureAwait(false);

return new ExchangeResult<UpdateSubscription>(Exchange, result);
}

private SharedFuturesOrder ParseOrder(KucoinStreamFuturesOrderUpdate update)
{
return new SharedFuturesOrder(
update.Symbol,
update.OrderId.ToString(),
update.OrderType == Enums.OrderType.Limit ? SharedOrderType.Limit : update.OrderType == Enums.OrderType.Market ? SharedOrderType.Market : SharedOrderType.Other,
update.Side == Enums.OrderSide.Buy ? SharedOrderSide.Buy : SharedOrderSide.Sell,
ParseOrderStatus(update.Status, update.UpdateType),
update.OrderTime)
{
ClientOrderId = update.ClientOrderId?.ToString(),
Quantity = update.Quantity,
QuantityFilled = update.QuantityFilled,
OrderPrice = update.Price == 0 ? null : update.Price,
LastTrade = update.UpdateType != MatchUpdateType.Match ? null : new SharedUserTrade(update.Symbol, update.OrderId, update.TradeId!, update.Side == OrderSide.Buy ? SharedOrderSide.Buy : SharedOrderSide.Sell, update.MatchQuantity ?? 0, update.MatchPrice ?? 0, update.Timestamp)
{
Role = update.Liquidity == LiquidityType.Maker ? SharedRole.Maker : SharedRole.Taker
}
};
}

private SharedOrderStatus ParseOrderStatus(ExtendedOrderStatus status, MatchUpdateType updateType)
{
if (status == ExtendedOrderStatus.New || status == ExtendedOrderStatus.Open || updateType == MatchUpdateType.Open || updateType == MatchUpdateType.Received) return SharedOrderStatus.Open;
if (updateType == MatchUpdateType.Canceled) return SharedOrderStatus.Canceled;
if (updateType == MatchUpdateType.Filled) return SharedOrderStatus.Filled;
return SharedOrderStatus.Open;
}
#endregion

#region Position client
EndpointOptions<SubscribePositionRequest> IPositionSocketClient.SubscribePositionOptions { get; } = new EndpointOptions<SubscribePositionRequest>(true);
async Task<ExchangeResult<UpdateSubscription>> IPositionSocketClient.SubscribeToPositionUpdatesAsync(SubscribePositionRequest request, Action<ExchangeEvent<IEnumerable<SharedPosition>>> handler, CancellationToken ct)
{
var validationError = ((IPositionSocketClient)this).SubscribePositionOptions.ValidateRequest(Exchange, request, request.TradingMode, SupportedTradingModes);
if (validationError != null)
return new ExchangeResult<UpdateSubscription>(Exchange, validationError);

var result = await SubscribeToPositionUpdatesAsync(
update => handler(update.AsExchangeEvent<IEnumerable<SharedPosition>>(Exchange, new[] { new SharedPosition(update.Data.Symbol, update.Data.CurrentQuantity, update.Data.CurrentTime)
{
AverageOpenPrice = update.Data.AverageEntryPrice,
PositionSide = update.Data.CurrentQuantity < 0 ? SharedPositionSide.Short : SharedPositionSide.Long,
LiquidationPrice = update.Data.LiquidationPrice,
Leverage = update.Data.RealLeverage,
UnrealizedPnl = update.Data.UnrealizedPnl
}})),
ct: ct).ConfigureAwait(false);

return new ExchangeResult<UpdateSubscription>(Exchange, result);
}

#endregion
}
}
7 changes: 5 additions & 2 deletions Kucoin.Net/Clients/SpotApi/KucoinRestClientSpotApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using CryptoExchange.Net.Interfaces;
using CryptoExchange.Net.Interfaces.CommonClients;
using CryptoExchange.Net.Objects;
using CryptoExchange.Net.SharedApis;
using Kucoin.Net.Enums;
using Kucoin.Net.Interfaces.Clients.SpotApi;
using Kucoin.Net.Objects;
Expand All @@ -22,7 +23,7 @@
namespace Kucoin.Net.Clients.SpotApi
{
/// <inheritdoc cref="IKucoinRestClientSpotApi" />
internal class KucoinRestClientSpotApi : RestApiClient, IKucoinRestClientSpotApi, ISpotClient
internal partial class KucoinRestClientSpotApi : RestApiClient, IKucoinRestClientSpotApi, ISpotClient
{
internal static TimeSyncState _timeSyncState = new TimeSyncState("Spot Api");

Expand Down Expand Up @@ -73,7 +74,7 @@ protected override AuthenticationProvider CreateAuthenticationProvider(ApiCreden
=> new KucoinAuthenticationProvider((KucoinApiCredentials)credentials);

/// <inheritdoc />
public override string FormatSymbol(string baseAsset, string quoteAsset) => baseAsset.ToUpperInvariant() + "-" + quoteAsset.ToUpperInvariant();
public override string FormatSymbol(string baseAsset, string quoteAsset, TradingMode tradingMode, DateTime? deliverTime = null) => baseAsset.ToUpperInvariant() + "-" + quoteAsset.ToUpperInvariant();

#region common interface

Expand Down Expand Up @@ -436,5 +437,7 @@ protected override Task<WebCallResult<DateTime>> GetServerTimestampAsync()

/// <inheritdoc />
public ISpotClient CommonSpotClient => this;
public IKucoinRestClientSpotApiShared SharedClient => this;

}
}
Loading

0 comments on commit e654fd6

Please sign in to comment.