Skip to content

Commit 65f2427

Browse files
authored
Implement KuCoin orderbook WebSocket listener (#828)
1 parent 3400769 commit 65f2427

File tree

3 files changed

+130
-4
lines changed

3 files changed

+130
-4
lines changed

src/ExchangeSharp/API/Exchanges/Kraken/ExchangeKrakenAPI.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1484,7 +1484,7 @@ private async Task<List<string>> GetMarketSymbolList(string[] marketSymbols)
14841484
/// <returns></returns>
14851485
protected override async Task<IWebSocket> OnGetDeltaOrderBookWebSocketAsync(
14861486
Action<ExchangeOrderBook> callback,
1487-
int maxCount = 20,
1487+
int maxCount = 100,
14881488
params string[] marketSymbols
14891489
)
14901490
{
@@ -1639,7 +1639,7 @@ params string[] marketSymbols
16391639
{
16401640
Event = ActionType.Subscribe,
16411641
Pairs = marketSymbols.ToList(),
1642-
SubscriptionSettings = new Subscription { Name = "book", Depth = 100 }
1642+
SubscriptionSettings = new Subscription { Name = "book", Depth = maxCount }
16431643
};
16441644
await _socket.SendMessageAsync(channelAction);
16451645
}

src/ExchangeSharp/API/Exchanges/KuCoin/ExchangeKuCoinAPI.cs

Lines changed: 127 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ The above copyright notice and this permission notice shall be included in all c
2020
using System.Security.Cryptography;
2121
using System.Text;
2222
using System.Threading.Tasks;
23+
using ExchangeSharp.API.Exchanges.Kraken.Models.Request;
24+
using ExchangeSharp.API.Exchanges.Kraken.Models.Types;
2325
using Newtonsoft.Json;
2426
using Newtonsoft.Json.Linq;
2527

@@ -39,6 +41,7 @@ private ExchangeKuCoinAPI()
3941
NonceEndPointStyle = NonceStyle.UnixMilliseconds;
4042
MarketSymbolSeparator = "-";
4143
RateLimit = new RateGate(20, TimeSpan.FromSeconds(60.0));
44+
WebSocketOrderBookType = WebSocketOrderBookType.FullBookFirstThenDeltas;
4245
}
4346

4447
public override string PeriodSecondsToString(int seconds)
@@ -233,7 +236,9 @@ protected override async Task<ExchangeOrderBook> OnGetOrderBookAsync(
233236
JToken token = await MakeJsonRequestAsync<JToken>(
234237
"/market/orderbook/level2_" + maxCount + "?symbol=" + marketSymbol
235238
);
236-
return token.ParseOrderBookFromJTokenArrays(asks: "asks", bids: "bids");
239+
var book = token.ParseOrderBookFromJTokenArrays(asks: "asks", bids: "bids", sequence: "sequence");
240+
book.MarketSymbol = marketSymbol;
241+
return book;
237242
}
238243

239244
protected override async Task<ExchangeTicker> OnGetTickerAsync(string marketSymbol)
@@ -804,6 +809,127 @@ await _socket.SendMessageAsync(
804809
);
805810
}
806811

812+
protected override async Task<IWebSocket> OnGetDeltaOrderBookWebSocketAsync(
813+
Action<ExchangeOrderBook> callback,
814+
int maxCount = 20,
815+
params string[] marketSymbols
816+
)
817+
{
818+
if (marketSymbols == null || marketSymbols.Length == 0)
819+
{
820+
marketSymbols = (await GetMarketSymbolsAsync(true)).ToArray();
821+
}
822+
823+
var initialSequenceIds = new Dictionary<string, long>();
824+
825+
var websocketUrlToken = GetWebsocketBulletToken();
826+
827+
return await ConnectPublicWebSocketAsync(
828+
$"?token={websocketUrlToken}&acceptUserMessage=true",
829+
messageCallback: (_socket, msg) =>
830+
{
831+
var message = msg.ToStringFromUTF8();
832+
var deserializedMessage = JsonConvert.DeserializeObject(message) as JObject;
833+
834+
var data = deserializedMessage["data"];
835+
if (data is null)
836+
{
837+
return Task.CompletedTask;
838+
}
839+
840+
var changes = data["changes"];
841+
var time = data["time"];
842+
var symbol = data["symbol"];
843+
if (changes is null || time is null || symbol is null)
844+
{
845+
return Task.CompletedTask;
846+
}
847+
848+
var parsedTime = time.ConvertInvariant<long>();
849+
var lastUpdatedDateTime = DateTimeOffset
850+
.FromUnixTimeMilliseconds(parsedTime)
851+
.DateTime;
852+
853+
var deltaBook = new ExchangeOrderBook
854+
{
855+
IsFromSnapshot = false,
856+
ExchangeName = ExchangeName.Kucoin,
857+
SequenceId = parsedTime,
858+
MarketSymbol = symbol.ToString(),
859+
LastUpdatedUtc = lastUpdatedDateTime,
860+
};
861+
862+
var rawAsks = changes["asks"] as JArray;
863+
foreach (var rawAsk in rawAsks)
864+
{
865+
var sequence = rawAsk[2].ConvertInvariant<long>();
866+
if (sequence <= initialSequenceIds[deltaBook.MarketSymbol])
867+
{
868+
// A deprecated update should be ignored
869+
continue;
870+
}
871+
872+
var price = rawAsk[0].ConvertInvariant<decimal>();
873+
var quantity = rawAsk[1].ConvertInvariant<decimal>();
874+
875+
deltaBook.Asks[price] = new ExchangeOrderPrice
876+
{
877+
Price = price,
878+
Amount = quantity,
879+
};
880+
}
881+
882+
var rawBids = changes["bids"] as JArray;
883+
foreach (var rawBid in rawBids)
884+
{
885+
var sequence = rawBid[2].ConvertInvariant<long>();
886+
if (sequence <= initialSequenceIds[deltaBook.MarketSymbol])
887+
{
888+
// A deprecated update should be ignored
889+
continue;
890+
}
891+
892+
var price = rawBid[0].ConvertInvariant<decimal>();
893+
var quantity = rawBid[1].ConvertInvariant<decimal>();
894+
895+
deltaBook.Bids[price] = new ExchangeOrderPrice
896+
{
897+
Price = price,
898+
Amount = quantity,
899+
};
900+
}
901+
902+
callback(deltaBook);
903+
904+
return Task.CompletedTask;
905+
},
906+
connectCallback: async (_socket) =>
907+
{
908+
// Get full order book snapshot when connecting
909+
foreach (var marketSymbol in marketSymbols)
910+
{
911+
var initialBook = await OnGetOrderBookAsync(marketSymbol, maxCount);
912+
initialBook.IsFromSnapshot = true;
913+
914+
callback(initialBook);
915+
916+
initialSequenceIds[marketSymbol] = initialBook.SequenceId;
917+
}
918+
919+
var id = CryptoUtility.UtcNow.Ticks;
920+
var topic = $"/market/level2:{string.Join(",", marketSymbols)}";
921+
await _socket.SendMessageAsync(
922+
new
923+
{
924+
id = id++,
925+
type = "subscribe",
926+
topic = topic,
927+
}
928+
);
929+
}
930+
);
931+
}
932+
807933
#endregion Websockets
808934

809935
#region Private Functions

src/ExchangeSharp/API/Exchanges/_Base/ExchangeAPIExtensions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ public static class ExchangeAPIExtensions
4545
public static async Task<IWebSocket> GetFullOrderBookWebSocketAsync(
4646
this IOrderBookProvider api,
4747
Action<ExchangeOrderBook> callback,
48-
int maxCount = 20,
48+
int maxCount = 100,
4949
params string[] symbols
5050
)
5151
{

0 commit comments

Comments
 (0)