1
0
mirror of https://github.com/JKorf/CryptoExchange.Net.git synced 2026-08-11 16:32:57 +00:00

Compare commits

...

8 Commits

Author SHA1 Message Date
Jkorf 860d753ad6 Update README.md 2026-08-07 09:09:51 +02:00
Jkorf e16c91792c Added LBank reference 2026-08-06 10:29:02 +02:00
Jkorf a3d95da9fa Updated examples 2026-08-06 10:26:39 +02:00
JKorf 907399b878 Added ManualUpdateSubscription and UpdateSubscription additional constructor to allow producing websocket events without actual connection 2026-08-05 21:31:59 +02:00
JKorf 9eab0d967e Merge branch 'master' of https://github.com/JKorf/CryptoExchange.Net 2026-08-05 17:53:51 +02:00
JKorf 0dee68e8ae Fixed timing issue in test 2026-08-05 17:53:43 +02:00
Jkorf d838d3377f Updated logging unhandled websocket message 2026-08-05 13:13:48 +02:00
Jkorf 2141ad9061 Fixed incorrect check test output 2026-07-29 13:05:42 +02:00
15 changed files with 483 additions and 1260 deletions
@@ -91,28 +91,25 @@ namespace CryptoExchange.Net.UnitTests
var evnt = new AsyncResetEvent(false, true);
var waiters = new List<Task<bool>>();
for(var i = 0; i < 10; i++)
for (var i = 0; i < 10; i++)
{
waiters.Add(evnt.WaitAsync());
}
List<bool>? results = null;
var resultsWaiter = Task.Run(async () =>
{
await Task.WhenAll(waiters);
results = waiters.Select(w => w.Result).ToList();
});
var remaining = waiters.ToList();
for(var i = 1; i <= 10; i++)
for (var i = 0; i < 10; i++)
{
evnt.Set();
await Task.Delay(1); // Wait for the continuation.
Assert.That(10 - i == waiters.Count(w => w.Status != TaskStatus.RanToCompletion));
var completed = await Task.WhenAny(remaining);
Assert.That(await completed, Is.True);
remaining.Remove(completed);
Assert.That(remaining.Count(w => w.IsCompleted), Is.Zero);
}
await resultsWaiter;
Assert.That(10 == results?.Count(r => r));
Assert.That(remaining, Is.Empty);
}
[Test]
@@ -0,0 +1,136 @@
using CryptoExchange.Net.Objects;
using CryptoExchange.Net.Objects.Errors;
using CryptoExchange.Net.Objects.Sockets;
using CryptoExchange.Net.Sockets.Default;
using NUnit.Framework;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace CryptoExchange.Net.UnitTests
{
[TestFixture]
public class ManualUpdateSubscriptionTests
{
[Test]
public void Constructor_Should_CreateSubscribedVirtualSubscription()
{
var controller = new ManualUpdateSubscription(socketId: 12);
Assert.That(controller.Subscription.SocketId, Is.EqualTo(12));
Assert.That(controller.Subscription.Id, Is.GreaterThan(0));
Assert.That(controller.Subscription.SocketStatus, Is.EqualTo(SocketStatus.Connected));
Assert.That(controller.Subscription.SubscriptionStatus, Is.EqualTo(SubscriptionStatus.Subscribed));
Assert.That(controller.Subscription.LastReceiveTime, Is.Null);
}
[Test]
public void StateChanges_Should_BeVisibleOnSubscription()
{
var controller = new ManualUpdateSubscription();
var timestamp = new DateTime(2026, 8, 5, 12, 0, 0, DateTimeKind.Utc);
var statuses = new List<SubscriptionStatus>();
controller.Subscription.SubscriptionStatusChanged += statuses.Add;
controller.SetLastReceiveTime(timestamp);
controller.SetSocketStatus(SocketStatus.Reconnecting);
controller.SetSubscriptionStatus(SubscriptionStatus.Subscribing);
controller.SetSubscriptionStatus(SubscriptionStatus.Subscribed);
Assert.That(controller.Subscription.LastReceiveTime, Is.EqualTo(timestamp));
Assert.That(controller.Subscription.SocketStatus, Is.EqualTo(SocketStatus.Reconnecting));
Assert.That(controller.Subscription.SubscriptionStatus, Is.EqualTo(SubscriptionStatus.Subscribed));
Assert.That(statuses, Is.EqualTo(new[]
{
SubscriptionStatus.Subscribing,
SubscriptionStatus.Subscribed
}));
}
[Test]
public void LifecycleMethods_Should_InvokeSubscriptionEvents()
{
var controller = new ManualUpdateSubscription();
var error = new ServerError("Test error", ErrorInfo.Unknown);
var exception = new InvalidOperationException("Test exception");
var disconnectedPeriod = TimeSpan.FromMinutes(2);
var lost = 0;
var restored = TimeSpan.Zero;
Error? resubscribeError = null;
var paused = 0;
var unpaused = 0;
Exception? receivedException = null;
controller.Subscription.ConnectionLost += () => lost++;
controller.Subscription.ConnectionRestored += x => restored = x;
controller.Subscription.ResubscribingFailed += x => resubscribeError = x;
controller.Subscription.ActivityPaused += () => paused++;
controller.Subscription.ActivityUnpaused += () => unpaused++;
controller.Subscription.Exception += x => receivedException = x;
controller.InvokeConnectionLost();
controller.InvokeConnectionRestored(disconnectedPeriod);
controller.InvokeResubscribingFailed(error);
controller.InvokeActivityPaused();
controller.InvokeActivityUnpaused();
controller.InvokeException(exception);
Assert.That(lost, Is.EqualTo(1));
Assert.That(restored, Is.EqualTo(disconnectedPeriod));
Assert.That(resubscribeError, Is.SameAs(error));
Assert.That(paused, Is.EqualTo(1));
Assert.That(unpaused, Is.EqualTo(1));
Assert.That(receivedException, Is.SameAs(exception));
}
[Test]
public void InvokeConnectionClosed_Should_CloseAndOnlyInvokeOnce()
{
var controller = new ManualUpdateSubscription();
var closed = 0;
controller.Subscription.ConnectionClosed += () => closed++;
controller.InvokeConnectionClosed();
controller.InvokeConnectionClosed();
Assert.That(closed, Is.EqualTo(1));
Assert.That(controller.Subscription.SocketStatus, Is.EqualTo(SocketStatus.Closed));
Assert.That(controller.Subscription.SubscriptionStatus, Is.EqualTo(SubscriptionStatus.Closed));
}
[Test]
public async Task SubscriptionOperations_Should_InvokeCallbacks()
{
var closes = 0;
var reconnects = 0;
var resubscribes = 0;
var controller = new ManualUpdateSubscription(
closeAsync: () =>
{
closes++;
return Task.CompletedTask;
},
reconnectAsync: () =>
{
reconnects++;
return Task.CompletedTask;
},
resubscribeAsync: () =>
{
resubscribes++;
return Task.FromResult(CallResult.Ok());
});
await controller.Subscription.ReconnectAsync();
var resubscribeResult = await controller.Subscription.ResubscribeAsync();
await controller.Subscription.CloseAsync();
await controller.Subscription.CloseAsync();
Assert.That(reconnects, Is.EqualTo(1));
Assert.That(resubscribes, Is.EqualTo(1));
Assert.That(resubscribeResult.Success, Is.True);
Assert.That(closes, Is.EqualTo(1));
Assert.That(controller.Subscription.SubscriptionStatus, Is.EqualTo(SubscriptionStatus.Closed));
}
}
}
@@ -180,7 +180,7 @@ namespace CryptoExchange.Net.Logging.Extensions
_receivedMessageNotMatchedToAnyListener = LoggerMessage.Define<int, string, string, string>(
LogLevel.Warning,
new EventId(2029, "ReceivedMessageNotMatchedToAnyListener"),
"[Sckt {SocketId}] received message not matched to any listener. TypeIdentifier: {TypeIdentifier}, ListenId: {ListenId}, current listeners: [{ListenIds}]");
"[Sckt {SocketId}] received message not matched to any listener. TypeIdentifier: {TypeIdentifier}, TopicFilter: {ListenId}, registered TopicFilters for type: [{TopicFilters}]");
_failedToParse = LoggerMessage.Define<int, string>(
LogLevel.Warning,
@@ -326,9 +326,9 @@ namespace CryptoExchange.Net.Logging.Extensions
_sendingData(logger, socketId, requestId, data, null);
}
public static void ReceivedMessageNotMatchedToAnyListener(this ILogger logger, int socketId, string typeIdentifier, string listenId, string listenIds)
public static void ReceivedMessageNotMatchedToAnyListener(this ILogger logger, int socketId, string typeIdentifier, string topicFilter, string topicFilters)
{
_receivedMessageNotMatchedToAnyListener(logger, socketId, typeIdentifier, listenId, listenIds, null);
_receivedMessageNotMatchedToAnyListener(logger, socketId, typeIdentifier, topicFilter, topicFilters, null);
}
public static void SendingByteData(this ILogger logger, int socketId, int requestId, int length)
@@ -0,0 +1,209 @@
using CryptoExchange.Net.Sockets;
using CryptoExchange.Net.Sockets.Default;
using CryptoExchange.Net.Sockets.Default.Routing;
using Microsoft.Extensions.Logging.Abstractions;
using System;
using System.Threading;
using System.Threading.Tasks;
namespace CryptoExchange.Net.Objects.Sockets
{
/// <summary>
/// Controller for an update subscription which isn't backed by a websocket connection. Can be used for testing.
/// </summary>
public class ManualUpdateSubscription
{
private readonly Func<Task> _closeAsync;
private readonly Func<Task> _reconnectAsync;
private readonly Func<Task<CallResult>> _resubscribeAsync;
private readonly ManualSubscription _manualSubscription;
private int _closedEventInvoked;
/// <summary>
/// The update subscription
/// </summary>
public UpdateSubscription Subscription { get; }
/// <summary>
/// The virtual socket id
/// </summary>
public int SocketId { get; }
/// <summary>
/// The last timestamp anything was received by the subscription
/// </summary>
public DateTime? LastReceiveTime { get; private set; }
/// <summary>
/// The current virtual websocket status
/// </summary>
public SocketStatus SocketStatus { get; private set; }
/// <summary>
/// Create a manually controlled update subscription
/// </summary>
/// <param name="socketId">The virtual socket id</param>
/// <param name="closeAsync">Callback when the subscription is closed</param>
/// <param name="reconnectAsync">Callback when a reconnect is requested</param>
/// <param name="resubscribeAsync">Callback when a resubscribe is requested</param>
public ManualUpdateSubscription(
int socketId = 0,
Func<Task>? closeAsync = null,
Func<Task>? reconnectAsync = null,
Func<Task<CallResult>>? resubscribeAsync = null)
{
SocketId = socketId;
SocketStatus = SocketStatus.Connected;
_closeAsync = closeAsync ?? (() => Task.CompletedTask);
_reconnectAsync = reconnectAsync ?? (() => Task.CompletedTask);
_resubscribeAsync = resubscribeAsync ?? (() => Task.FromResult(CallResult.Ok()));
_manualSubscription = new ManualSubscription();
_manualSubscription.Status = SubscriptionStatus.Subscribed;
Subscription = new UpdateSubscription(this, _manualSubscription);
}
/// <summary>
/// Set the last timestamp anything was received by the subscription
/// </summary>
/// <param name="timestamp">The receive timestamp</param>
public void SetLastReceiveTime(DateTime? timestamp)
{
LastReceiveTime = timestamp;
}
/// <summary>
/// Set the virtual websocket status
/// </summary>
/// <param name="status">The status</param>
public void SetSocketStatus(SocketStatus status)
{
SocketStatus = status;
}
/// <summary>
/// Set the subscription status
/// </summary>
/// <param name="status">The status</param>
public void SetSubscriptionStatus(SubscriptionStatus status)
{
_manualSubscription.Status = status;
}
/// <summary>
/// Invoke the connection lost event
/// </summary>
public void InvokeConnectionLost()
{
Subscription.HandleConnectionLostEvent();
}
/// <summary>
/// Invoke the connection restored event
/// </summary>
/// <param name="disconnectedPeriod">The period the connection was disconnected</param>
public void InvokeConnectionRestored(TimeSpan disconnectedPeriod)
{
Subscription.HandleConnectionRestoredEvent(disconnectedPeriod);
}
/// <summary>
/// Invoke the connection closed event
/// </summary>
public void InvokeConnectionClosed()
{
if (Interlocked.Exchange(ref _closedEventInvoked, 1) != 0)
return;
SocketStatus = SocketStatus.Closed;
_manualSubscription.Status = SubscriptionStatus.Closed;
Subscription.HandleConnectionClosedEvent();
}
/// <summary>
/// Invoke the resubscribing failed event
/// </summary>
/// <param name="error">The resubscribe error</param>
public void InvokeResubscribingFailed(Error error)
{
if (error == null)
throw new ArgumentNullException(nameof(error));
Subscription.HandleResubscribeFailedEvent(error);
}
/// <summary>
/// Invoke the activity paused event
/// </summary>
public void InvokeActivityPaused()
{
Subscription.HandlePausedEvent();
}
/// <summary>
/// Invoke the activity unpaused event
/// </summary>
public void InvokeActivityUnpaused()
{
Subscription.HandleUnpausedEvent();
}
/// <summary>
/// Invoke the exception event
/// </summary>
/// <param name="exception">The exception</param>
public void InvokeException(Exception exception)
{
if (exception == null)
throw new ArgumentNullException(nameof(exception));
_manualSubscription.InvokeExceptionHandler(exception);
}
internal async Task CloseAsync()
{
if (_manualSubscription.Status == SubscriptionStatus.Closed
|| _manualSubscription.Status == SubscriptionStatus.Closing)
return;
_manualSubscription.Status = SubscriptionStatus.Closing;
try
{
await _closeAsync().ConfigureAwait(false);
}
finally
{
_manualSubscription.Status = SubscriptionStatus.Closed;
}
}
internal Task ReconnectAsync()
{
return _reconnectAsync();
}
internal Task<CallResult> ResubscribeAsync()
{
return _resubscribeAsync();
}
private class ManualSubscription : Subscription
{
public ManualSubscription()
: base(NullLogger.Instance, false)
{
MessageRouter = MessageRouter.Create();
}
protected override Query? GetSubQuery(SocketConnection connection)
{
return null;
}
protected override Query? GetUnsubQuery(SocketConnection connection)
{
return null;
}
}
}
}
@@ -12,7 +12,8 @@ namespace CryptoExchange.Net.Objects.Sockets
/// </summary>
public class UpdateSubscription
{
private readonly SocketConnection _connection;
private readonly SocketConnection? _connection;
private readonly ManualUpdateSubscription? _manualSubscription;
internal readonly Subscription _subscription;
#if NET9_0_OR_GREATER
@@ -102,7 +103,7 @@ namespace CryptoExchange.Net.Objects.Sockets
/// <summary>
/// The id of the socket
/// </summary>
public int SocketId => _connection.SocketId;
public int SocketId => _connection?.SocketId ?? _manualSubscription!.SocketId;
/// <summary>
/// The id of the subscription
@@ -112,12 +113,12 @@ namespace CryptoExchange.Net.Objects.Sockets
/// <summary>
/// The last timestamp anything was received from the server
/// </summary>
public DateTime? LastReceiveTime => _connection.LastReceiveTime;
public DateTime? LastReceiveTime => _connection?.LastReceiveTime ?? _manualSubscription!.LastReceiveTime;
/// <summary>
/// The current websocket status
/// </summary>
public SocketStatus SocketStatus => _connection.Status;
public SocketStatus SocketStatus => _connection?.Status ?? _manualSubscription!.SocketStatus;
/// <summary>
/// The current subscription status
@@ -143,6 +144,18 @@ namespace CryptoExchange.Net.Objects.Sockets
_subscription.StatusChanged += (x) => SubscriptionStatusChanged?.Invoke(x);
}
/// <summary>
/// ctor
/// </summary>
/// <param name="manualSubscription">The manual subscription for controlling events and data</param>
/// <param name="subscription">The subscription</param>
internal UpdateSubscription(ManualUpdateSubscription manualSubscription, Subscription subscription)
{
_manualSubscription = manualSubscription;
_subscription = subscription;
_subscription.StatusChanged += (x) => SubscriptionStatusChanged?.Invoke(x);
}
private void UnsubscribeConnectionEvents()
{
lock (_eventLock)
@@ -150,22 +163,26 @@ namespace CryptoExchange.Net.Objects.Sockets
if (!_connectionEventsSubscribed)
return;
_connection.ConnectionClosed -= HandleConnectionClosedEvent;
_connection.ConnectionLost -= HandleConnectionLostEvent;
_connection.ConnectionRestored -= HandleConnectionRestoredEvent;
_connection.ResubscribingFailed -= HandleResubscribeFailedEvent;
_connection.ActivityPaused -= HandlePausedEvent;
_connection.ActivityUnpaused -= HandleUnpausedEvent;
if (_connection != null)
{
_connection.ConnectionClosed -= HandleConnectionClosedEvent;
_connection.ConnectionLost -= HandleConnectionLostEvent;
_connection.ConnectionRestored -= HandleConnectionRestoredEvent;
_connection.ResubscribingFailed -= HandleResubscribeFailedEvent;
_connection.ActivityPaused -= HandlePausedEvent;
_connection.ActivityUnpaused -= HandleUnpausedEvent;
}
_connectionEventsSubscribed = false;
}
}
private void HandleConnectionClosedEvent()
internal void HandleConnectionClosedEvent()
{
UnsubscribeConnectionEvents();
// If we're not the subscription closing this connection don't bother emitting
if (!_subscription.IsClosingConnection)
if (_connection != null && !_subscription.IsClosingConnection)
return;
List<Action> handlers;
@@ -176,7 +193,7 @@ namespace CryptoExchange.Net.Objects.Sockets
callback();
}
private void HandleConnectionLostEvent()
internal void HandleConnectionLostEvent()
{
if (!_subscription.Active)
{
@@ -192,7 +209,7 @@ namespace CryptoExchange.Net.Objects.Sockets
callback();
}
private void HandleConnectionRestoredEvent(TimeSpan period)
internal void HandleConnectionRestoredEvent(TimeSpan period)
{
if (!_subscription.Active)
{
@@ -208,7 +225,7 @@ namespace CryptoExchange.Net.Objects.Sockets
callback(period);
}
private void HandleResubscribeFailedEvent(Error error)
internal void HandleResubscribeFailedEvent(Error error)
{
if (!_subscription.Active)
{
@@ -224,7 +241,7 @@ namespace CryptoExchange.Net.Objects.Sockets
callback(error);
}
private void HandlePausedEvent()
internal void HandlePausedEvent()
{
if (!_subscription.Active)
{
@@ -240,7 +257,7 @@ namespace CryptoExchange.Net.Objects.Sockets
callback();
}
private void HandleUnpausedEvent()
internal void HandleUnpausedEvent()
{
if (!_subscription.Active)
{
@@ -262,7 +279,10 @@ namespace CryptoExchange.Net.Objects.Sockets
/// <returns></returns>
public Task CloseAsync()
{
return _connection.CloseAsync(_subscription);
if (_connection != null)
return _connection.CloseAsync(_subscription);
return _manualSubscription!.CloseAsync();
}
/// <summary>
@@ -271,7 +291,10 @@ namespace CryptoExchange.Net.Objects.Sockets
/// <returns></returns>
public Task ReconnectAsync()
{
return _connection.TriggerReconnectAsync();
if (_connection != null)
return _connection.TriggerReconnectAsync();
return _manualSubscription!.ReconnectAsync();
}
/// <summary>
@@ -280,7 +303,13 @@ namespace CryptoExchange.Net.Objects.Sockets
/// <returns></returns>
internal async Task UnsubscribeAsync()
{
await _connection.UnsubscribeAsync(_subscription).ConfigureAwait(false);
if (_connection != null)
{
await _connection.UnsubscribeAsync(_subscription).ConfigureAwait(false);
return;
}
await _manualSubscription!.CloseAsync().ConfigureAwait(false);
}
/// <summary>
@@ -289,7 +318,10 @@ namespace CryptoExchange.Net.Objects.Sockets
/// <returns></returns>
internal async Task<CallResult> ResubscribeAsync()
{
return await _connection.ResubscribeAsync(_subscription).ConfigureAwait(false);
if (_connection != null)
return await _connection.ResubscribeAsync(_subscription).ConfigureAwait(false);
return await _manualSubscription!.ResubscribeAsync().ConfigureAwait(false);
}
}
}
@@ -607,7 +607,7 @@ namespace CryptoExchange.Net.Sockets.Default
SocketId,
typeIdentifier,
topicFilter!,
string.Join(",", _listeners.Select(x => string.Join(",", x.MessageRouter.Routes.Where(x => x.TypeIdentifier == typeIdentifier).Select(x => x.TopicFilter != null ? string.Join(",", x.TopicFilter) : "[null]")))));
string.Join(",", _listeners.SelectMany(x => x.MessageRouter.Routes.Where(x => x.TypeIdentifier == typeIdentifier).Select(x => x.TopicFilter ?? "[null]"))));
}
}
}
@@ -142,10 +142,15 @@ namespace CryptoExchange.Net.Testing
var issues = SystemTextJsonComparer.CompareData(expressionBody.Method.Name, data, originalData, compareNestedProperty, ignoreProperties, useSingleArrayItem ?? false);
foreach(var issue in issues)
{
if (issue is MissingPropertyException && !warnings?.Any(x => x.Message == issue.Message) == true)
warnings?.Add(issue);
if (issue is MissingPropertyException)
{
if (!warnings?.Any(x => x.Message == issue.Message) == true)
warnings?.Add(issue);
}
else
{
errors.Add(issue);
}
}
if (errors.Count > 0)
+29 -27
View File
@@ -5,34 +5,36 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Binance.Net" Version="13.0.0" />
<PackageReference Include="Bitfinex.Net" Version="11.0.0" />
<PackageReference Include="BitMart.Net" Version="4.0.0" />
<PackageReference Include="BloFin.Net" Version="3.0.0" />
<PackageReference Include="Bybit.Net" Version="7.0.0" />
<PackageReference Include="CoinEx.Net" Version="11.0.0" />
<PackageReference Include="CoinW.Net" Version="3.0.0" />
<PackageReference Include="CryptoCom.Net" Version="4.0.0" />
<PackageReference Include="DeepCoin.Net" Version="4.0.0" />
<PackageReference Include="GateIo.Net" Version="4.0.0" />
<PackageReference Include="HyperLiquid.Net" Version="5.0.0" />
<PackageReference Include="JK.BingX.Net" Version="4.0.0" />
<PackageReference Include="JK.Bitget.Net" Version="4.0.0" />
<PackageReference Include="JK.Mexc.Net" Version="6.0.0" />
<PackageReference Include="JK.OKX.Net" Version="5.0.1" />
<PackageReference Include="Jkorf.Aster.Net" Version="4.0.0" />
<PackageReference Include="JKorf.BitMEX.Net" Version="4.0.0" />
<PackageReference Include="JKorf.Coinbase.Net" Version="4.0.0" />
<PackageReference Include="JKorf.HTX.Net" Version="9.0.0" />
<PackageReference Include="JKorf.Lighter.Net" Version="1.0.0" />
<PackageReference Include="JKorf.Upbit.Net" Version="3.0.0" />
<PackageReference Include="KrakenExchange.Net" Version="8.0.0" />
<PackageReference Include="Kucoin.Net" Version="9.0.0" />
<PackageReference Include="Binance.Net" Version="13.3.0" />
<PackageReference Include="Bitfinex.Net" Version="11.3.0" />
<PackageReference Include="BitMart.Net" Version="4.3.0" />
<PackageReference Include="BloFin.Net" Version="3.3.0" />
<PackageReference Include="Bybit.Net" Version="7.3.0" />
<PackageReference Include="CoinEx.Net" Version="11.3.0" />
<PackageReference Include="CoinW.Net" Version="3.3.0" />
<PackageReference Include="CryptoCom.Net" Version="4.3.0" />
<PackageReference Include="DeepCoin.Net" Version="4.3.0" />
<PackageReference Include="GateIo.Net" Version="4.4.0" />
<PackageReference Include="HyperLiquid.Net" Version="5.4.0" />
<PackageReference Include="JK.BingX.Net" Version="4.3.0" />
<PackageReference Include="JK.Bitget.Net" Version="4.3.2" />
<PackageReference Include="JK.Mexc.Net" Version="6.4.0" />
<PackageReference Include="JK.OKX.Net" Version="5.3.1" />
<PackageReference Include="Jkorf.Aster.Net" Version="4.3.0" />
<PackageReference Include="JKorf.BitMEX.Net" Version="4.3.0" />
<PackageReference Include="JKorf.Coinbase.Net" Version="4.4.0" />
<PackageReference Include="JKorf.HTX.Net" Version="9.3.0" />
<PackageReference Include="JKorf.Lighter.Net" Version="1.4.0" />
<PackageReference Include="JKorf.Upbit.Net" Version="3.3.0" />
<PackageReference Include="KrakenExchange.Net" Version="8.3.0" />
<PackageReference Include="Kucoin.Net" Version="9.3.0" />
<PackageReference Include="LBank.Net" Version="1.0.0" />
<PackageReference Include="Pionex.Net" Version="1.1.0" />
<PackageReference Include="Serilog.AspNetCore" Version="10.0.0" />
<PackageReference Include="Toobit.Net" Version="4.0.0" />
<PackageReference Include="Weex.Net" Version="2.0.0" />
<PackageReference Include="WhiteBit.Net" Version="4.0.0" />
<PackageReference Include="XT.Net" Version="4.0.0" />
<PackageReference Include="Toobit.Net" Version="4.3.0" />
<PackageReference Include="Weex.Net" Version="2.3.0" />
<PackageReference Include="WhiteBit.Net" Version="4.3.0" />
<PackageReference Include="XT.Net" Version="4.3.0" />
</ItemGroup>
</Project>
+11 -1
View File
@@ -18,9 +18,11 @@
@inject IHyperLiquidRestClient hyperLiquidClient
@inject IKrakenRestClient krakenClient
@inject IKucoinRestClient kucoinClient
@inject ILBankRestClient lbankClient
@inject ILighterRestClient lighterClient
@inject IMexcRestClient mexcClient
@inject IOKXRestClient okxClient
@inject IPionexRestClient pionexClient
@inject IToobitRestClient toobitClient
@inject IUpbitRestClient upbitClient
@inject IWeexRestClient weexClient
@@ -57,16 +59,18 @@
var hyperLiquidTask = hyperLiquidClient.FuturesApi.ExchangeData.GetExchangeInfoAndTickersAsync();
var krakenTask = krakenClient.SpotApi.ExchangeData.GetTickerAsync("XBTUSD");
var kucoinTask = kucoinClient.SpotApi.ExchangeData.GetTickerAsync("BTC-USDT");
var lBankTask = lbankClient.SpotApi.ExchangeData.GetTickersAsync("btc_usdt");
var lighterTask = lighterClient.ExchangeApi.ExchangeData.GetSymbolDetailsAsync("BTC");
var mexcTask = mexcClient.SpotApi.ExchangeData.GetTickerAsync("BTCUSDT");
var okxTask = okxClient.UnifiedApi.ExchangeData.GetTickerAsync("BTC-USDT");
var pionexTask = pionexClient.SpotApi.ExchangeData.GetTickersAsync("BTC_USDT");
var toobitTask = toobitClient.SpotApi.ExchangeData.GetTickersAsync("BTCUSDT");
var upbitTask = upbitClient.SpotApi.ExchangeData.GetTickerAsync("USDT-BTC");
var weexTask = weexClient.SpotApi.ExchangeData.GetTickersAsync(["BTCUSDT"]);
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, htxTask, krakenTask, kucoinTask, mexcTask, okxTask);
await Task.WhenAll(asterTask, binanceTask, bingXTask, bitfinexTask, bitgetTask, bitmartTask, bloFinTask, bitmexTask, bybitTask, coinexTask, coinWTask, deepCoinTask, gateioTask, htxTask, krakenTask, kucoinTask, lBankTask, mexcTask, okxTask, pionexTask);
if (asterTask.Result.Success)
_prices.Add("Aster", asterTask.Result.Data.LastPrice);
@@ -133,6 +137,9 @@
if (kucoinTask.Result.Success)
_prices.Add("Kucoin", kucoinTask.Result.Data.LastPrice ?? 0);
if (lBankTask.Result.Success)
_prices.Add("LBank", lBankTask.Result.Data.Single().Ticker.LastPrice);
if (lighterTask.Result.Success)
_prices.Add("Lighter", lighterTask.Result.Data.PerpSymbols[0].LastPrice);
@@ -142,6 +149,9 @@
if (okxTask.Result.Success)
_prices.Add("OKX", okxTask.Result.Data.LastPrice ?? 0);
if (pionexTask.Result.Success)
_prices.Add("Pionex", pionexTask.Result.Data.Single().ClosePrice);
if (toobitTask.Result.Success)
_prices.Add("Toobit", toobitTask.Result.Data.Single().LastPrice ?? 0);
+4 -1
View File
@@ -18,9 +18,11 @@
@inject IHyperLiquidSocketClient hyperLiquidSocketClient
@inject IKrakenSocketClient krakenSocketClient
@inject IKucoinSocketClient kucoinSocketClient
@inject ILBankSocketClient lBankSocketClient
@inject ILighterSocketClient lighterSocketClient
@inject IMexcSocketClient mexcSocketClient
@inject IOKXSocketClient okxSocketClient
@inject IPionexSocketClient pionexSocketClient
@inject IToobitSocketClient toobitSocketClient
@inject IUpbitSocketClient upbitSocketClient
@inject IWeexSocketClient weexSocketClient
@@ -65,11 +67,11 @@
deepCoinSocketClient.ExchangeApi.SubscribeToSymbolUpdatesAsync("ETH-BTC", data => UpdateData("DeepCoin", data.Data.LastPrice ?? 0)),
gateioSocketClient.SpotApi.SubscribeToTickerUpdatesAsync("ETH_BTC", data => UpdateData("GateIo", data.Data.LastPrice)),
htxSocketClient.SpotApi.SubscribeToTickerUpdatesAsync("ethbtc", data => UpdateData("HTX", data.Data.ClosePrice ?? 0)),
xtSocketClient.SpotApi.SubscribeToTickerUpdatesAsync("eth_btc", data => UpdateData("XT", data.Data.LastPrice ?? 0)),
// HyperLiquid doesn't support the ETH/BTC pair
//hyperLiquidSocketClient.SpotApi.SubscribeToSymbolUpdatesAsync("ETH", data => UpdateData("HyperLiquid", data.Data.MidPrice ?? 0)),
krakenSocketClient.SpotApi.SubscribeToTickerUpdatesAsync("ETH/BTC", data => UpdateData("Kraken", data.Data.LastPrice)),
kucoinSocketClient.SpotApi.SubscribeToTickerUpdatesAsync("ETH-BTC", data => UpdateData("Kucoin", data.Data.LastPrice ?? 0)),
lBankSocketClient.SpotApi.SubscribeToTickerUpdatesAsync("eth_btc", data => UpdateData("LBank", data.Data.LastPrice)),
// Mexc doesn't offer a ticker stream currently
//mexcSocketClient.SpotApi.SubscribeToMiniTickerUpdatesAsync("ETHBTC", data => UpdateData("Mexc", data.Data.LastPrice)),
okxSocketClient.UnifiedApi.ExchangeData.SubscribeToTickerUpdatesAsync("ETH-BTC", data => UpdateData("OKX", data.Data.LastPrice ?? 0)),
@@ -77,6 +79,7 @@
//toobitSocketClient.SpotApi.SubscribeToTickerUpdatesAsync("ETHBTC", data => UpdateData("Toobit", data.Data.LastPrice ?? 0)),
upbitSocketClient.SpotApi.SubscribeToTickerUpdatesAsync("BTC-ETH", data => UpdateData("Upbit", data.Data.LastPrice)),
whitebitSocketClient.V4Api.SubscribeToTickerUpdatesAsync("ETH_BTC", data => UpdateData("WhiteBit", data.Data.Ticker.LastPrice)),
xtSocketClient.SpotApi.SubscribeToTickerUpdatesAsync("eth_btc", data => UpdateData("XT", data.Data.LastPrice ?? 0)),
};
await Task.WhenAll(tasks);
@@ -24,9 +24,11 @@
@using Kucoin.Net
@using Kucoin.Net.Clients
@using Kucoin.Net.Interfaces
@using LBank.Net.Interfaces
@using Lighter.Net.Interfaces
@using Mexc.Net.Interfaces
@using OKX.Net.Interfaces;
@using Pionex.Net.Interfaces;
@using Upbit.Net.Interfaces;
@using Toobit.Net.Interfaces;
@using Weex.Net.Interfaces
@@ -51,9 +53,11 @@
@inject IHyperLiquidOrderBookFactory hyperLiquidFactory
@inject IKrakenOrderBookFactory krakenFactory
@inject IKucoinOrderBookFactory kucoinFactory
@inject ILBankOrderBookFactory lBankFactory
@inject ILighterOrderBookFactory lighterFactory
@inject IMexcOrderBookFactory mexcFactory
@inject IOKXOrderBookFactory okxFactory
@inject IPionexOrderBookFactory pionexFactory
@inject IToobitOrderBookFactory toobitFactory
@inject IUpbitOrderBookFactory upbitFactory
@inject IWeexOrderBookFactory weexFactory
@@ -112,9 +116,11 @@
{ "HyperLiquid", hyperLiquidFactory.Create("UETH/USDC") },
{ "Kraken", krakenFactory.CreateSpot("ETH/BTC") },
{ "Kucoin", kucoinFactory.CreateSpot("ETH-BTC") },
{ "LBank", lBankFactory.CreateSpot("eth_usdt") },
{ "Lighter", lighterFactory.Create("ETH/USDC") },
{ "Mexc", mexcFactory.CreateSpot("ETHBTC") },
{ "OKX", okxFactory.Create("ETH-BTC") },
{ "Pionex", pionexFactory.CreateSpot("ETH_USDT") },
{ "Toobit", toobitFactory.CreateSpot("ETHUSDT") },
{ "Upbit", upbitFactory.CreateSpot("BTC-ETH") },
{ "Weex", weexFactory.CreateSpot("ETHUSDT") },
@@ -24,9 +24,11 @@
@using Kraken.Net.Interfaces
@using Kucoin.Net.Clients
@using Kucoin.Net.Interfaces
@using LBank.Net.Interfaces
@using Lighter.Net.Interfaces
@using Mexc.Net.Interfaces
@using OKX.Net.Interfaces;
@using Pionex.Net.Interfaces;
@using Upbit.Net.Interfaces;
@using Toobit.Net.Interfaces;
@using Weex.Net.Interfaces
@@ -51,9 +53,11 @@
@inject IHyperLiquidTrackerFactory hyperLiquidFactory
@inject IKrakenTrackerFactory krakenFactory
@inject IKucoinTrackerFactory kucoinFactory
@inject ILBankTrackerFactory lBankFactory
@inject ILighterTrackerFactory lighterFactory
@inject IMexcTrackerFactory mexcFactory
@inject IOKXTrackerFactory okxFactory
@inject IPionexTrackerFactory pionexFactory
@inject IToobitTrackerFactory toobitFactory
@inject IUpbitTrackerFactory upbitFactory
@inject IWeexTrackerFactory weexFactory
@@ -105,9 +109,11 @@
{ hyperLiquidFactory.CreateTradeTracker(symbol, period: TimeSpan.FromMinutes(5)) },
{ krakenFactory.CreateTradeTracker(symbol, period: TimeSpan.FromMinutes(5)) },
{ kucoinFactory.CreateTradeTracker(symbol, period: TimeSpan.FromMinutes(5)) },
{ lBankFactory.CreateTradeTracker(symbol, period: TimeSpan.FromMinutes(5)) },
{ lighterFactory.CreateTradeTracker(futuresSymbol, period: TimeSpan.FromMinutes(5)) },
{ mexcFactory.CreateTradeTracker(symbol, period: TimeSpan.FromMinutes(5)) },
{ okxFactory.CreateTradeTracker(symbol, period: TimeSpan.FromMinutes(5)) },
{ pionexFactory.CreateTradeTracker(symbol, period: TimeSpan.FromMinutes(5)) },
{ toobitFactory.CreateTradeTracker(symbol, period: TimeSpan.FromMinutes(5)) },
{ upbitFactory.CreateTradeTracker(symbol, period: TimeSpan.FromMinutes(5)) },
{ weexFactory.CreateTradeTracker(symbol, period: TimeSpan.FromMinutes(5)) },
+2
View File
@@ -52,9 +52,11 @@ namespace BlazorClient
services.AddHTX();
services.AddKraken();
services.AddKucoin();
services.AddLBank();
services.AddLighter();
services.AddMexc();
services.AddOKX();
services.AddPionex();
services.AddToobit();
services.AddUpbit();
services.AddWeex();
+2
View File
@@ -27,9 +27,11 @@
@using HyperLiquid.Net.Interfaces.Clients;
@using Kraken.Net.Interfaces.Clients;
@using Kucoin.Net.Interfaces.Clients;
@using LBank.Net.Interfaces.Clients
@using Lighter.Net.Interfaces.Clients
@using Mexc.Net.Interfaces.Clients;
@using OKX.Net.Interfaces.Clients;
@using Pionex.Net.Interfaces.Clients;
@using Upbit.Net.Interfaces.Clients;
@using Toobit.Net.Interfaces.Clients;
@using Weex.Net.Interfaces.Clients
+4 -1191
View File
File diff suppressed because it is too large Load Diff