private void WebSocketCallback(WebSocketStreamEventArgs args) { if (!Subscribers.ContainsKey(args.StreamName)) { Logger?.LogDebug($"{nameof(BinanceWebSocketClient<TEventArgs>)}.{nameof(WebSocketCallback)} - Ignoring event for non-subscribed stream: \"{args.StreamName}\" [thread: {Thread.CurrentThread.ManagedThreadId}]"); return; } OnWebSocketEvent(args, Subscribers[args.StreamName]); }
protected override void OnWebSocketEvent(WebSocketStreamEventArgs args, IEnumerable <Action <SymbolStatisticsEventArgs> > callbacks) { Logger?.LogDebug($"{nameof(SymbolStatisticsWebSocketClient)}: \"{args.Json}\""); try { SymbolStatisticsEventArgs eventArgs; if (args.Json.IsJsonArray()) { // Simulate a single event time. var eventTime = DateTime.UtcNow.ToTimestamp().ToDateTime(); var statistics = JArray.Parse(args.Json).Select(DeserializeSymbolStatistics).ToArray(); eventArgs = new SymbolStatisticsEventArgs(eventTime, args.Token, statistics); } else { var jObject = JObject.Parse(args.Json); var eventType = jObject["e"].Value <string>(); if (eventType == "24hrTicker") { var eventTime = jObject["E"].Value <long>().ToDateTime(); var statistics = DeserializeSymbolStatistics(jObject); eventArgs = new SymbolStatisticsEventArgs(eventTime, args.Token, statistics); } else { Logger?.LogWarning($"{nameof(SymbolStatisticsWebSocketClient)}.{nameof(OnWebSocketEvent)}: Unexpected event type ({eventType})."); return; } } try { if (callbacks != null) { foreach (var callback in callbacks) { callback(eventArgs); } } StatisticsUpdate?.Invoke(this, eventArgs); } catch (OperationCanceledException) { } catch (Exception e) { if (!args.Token.IsCancellationRequested) { Logger?.LogError(e, $"{nameof(SymbolStatisticsWebSocketClient)}: Unhandled aggregate trade event handler exception."); } } } catch (OperationCanceledException) { } catch (Exception e) { if (!args.Token.IsCancellationRequested) { Logger?.LogError(e, $"{nameof(SymbolStatisticsWebSocketClient)}.{nameof(OnWebSocketEvent)}"); } } }
protected override void OnWebSocketEvent(WebSocketStreamEventArgs args, IEnumerable <Action <UserDataEventArgs> > callbacks) { var user = _listenKeys.First(_ => _.Value == args.StreamName).Key; Logger?.LogDebug($"{nameof(UserDataWebSocketClient)}: \"{args.Json}\""); try { var jObject = JObject.Parse(args.Json); var eventType = jObject["e"].Value <string>(); var eventTime = jObject["E"].Value <long>(); // ReSharper disable once ConvertIfStatementToSwitchStatement if (eventType == "outboundAccountInfo") { var commissions = new AccountCommissions( jObject["m"].Value <int>(), // maker jObject["t"].Value <int>(), // taker jObject["b"].Value <int>(), // buyer jObject["s"].Value <int>()); // seller var status = new AccountStatus( jObject["T"].Value <bool>(), // can trade jObject["W"].Value <bool>(), // can withdraw jObject["D"].Value <bool>()); // can deposit var balances = jObject["B"] .Select(entry => new AccountBalance( entry["a"].Value <string>(), // asset entry["f"].Value <decimal>(), // free amount entry["l"].Value <decimal>())) // locked amount .ToList(); var eventArgs = new AccountUpdateEventArgs(eventTime, args.Token, new AccountInfo(user, commissions, status, jObject["u"].Value <long>(), balances)); try { if (callbacks != null) { foreach (var callback in callbacks) { callback(eventArgs); } } AccountUpdate?.Invoke(this, eventArgs); } catch (OperationCanceledException) { } catch (Exception e) { if (!args.Token.IsCancellationRequested) { Logger?.LogError(e, $"{nameof(UserDataWebSocketClient)}: Unhandled account update event handler exception."); } } } else if (eventType == "executionReport") { var order = new Order(user); FillOrder(order, jObject); var executionType = ConvertOrderExecutionType(jObject["x"].Value <string>()); var rejectedReason = ConvertOrderRejectedReason(jObject["r"].Value <string>()); var newClientOrderId = jObject["c"].Value <string>(); if (executionType == OrderExecutionType.Trade) // trade update event. { var trade = new AccountTrade( jObject["s"].Value <string>(), // symbol jObject["t"].Value <long>(), // ID jObject["i"].Value <long>(), // order ID jObject["L"].Value <decimal>(), // price (price of last filled trade) jObject["z"].Value <decimal>(), // quantity (accumulated quantity of filled trades) jObject["n"].Value <decimal>(), // commission jObject["N"].Value <string>(), // commission asset jObject["T"].Value <long>(), // timestamp order.Side == OrderSide.Buy, // is buyer jObject["m"].Value <bool>(), // is buyer maker jObject["M"].Value <bool>()); // is best price var quantityOfLastFilledTrade = jObject["l"].Value <decimal>(); var eventArgs = new AccountTradeUpdateEventArgs(eventTime, args.Token, order, rejectedReason, newClientOrderId, trade, quantityOfLastFilledTrade); try { if (callbacks != null) { foreach (var callback in callbacks) { callback(eventArgs); } } TradeUpdate?.Invoke(this, eventArgs); } catch (OperationCanceledException) { } catch (Exception e) { if (!args.Token.IsCancellationRequested) { Logger?.LogError(e, $"{nameof(UserDataWebSocketClient)}: Unhandled trade update event handler exception."); } } } else // order update event. { var eventArgs = new OrderUpdateEventArgs(eventTime, args.Token, order, executionType, rejectedReason, newClientOrderId); try { if (callbacks != null) { foreach (var callback in callbacks) { callback(eventArgs); } } OrderUpdate?.Invoke(this, eventArgs); } catch (OperationCanceledException) { } catch (Exception e) { if (!args.Token.IsCancellationRequested) { Logger?.LogError(e, $"{nameof(UserDataWebSocketClient)}: Unhandled order update event handler exception."); } } } } else { Logger?.LogWarning($"{nameof(UserDataWebSocketClient)}.{nameof(OnWebSocketEvent)}: Unexpected event type ({eventType}) - \"{args.Json}\""); } } catch (OperationCanceledException) { } catch (Exception e) { if (!args.Token.IsCancellationRequested) { Logger?.LogError(e, $"{nameof(UserDataWebSocketClient)}.{nameof(OnWebSocketEvent)}"); } } }
protected override void OnWebSocketEvent(WebSocketStreamEventArgs args, IEnumerable <Action <CandlestickEventArgs> > callbacks) { Logger?.LogDebug($"{nameof(CandlestickWebSocketClient)}: \"{args.Json}\""); try { var jObject = JObject.Parse(args.Json); var eventType = jObject["e"].Value <string>(); if (eventType == "kline") { //var symbol = jObject["s"].Value<string>(); var eventTime = jObject["E"].Value <long>(); var firstTradeId = jObject["k"]["f"].Value <long>(); var lastTradeId = jObject["k"]["L"].Value <long>(); var isFinal = jObject["k"]["x"].Value <bool>(); var candlestick = new Candlestick( jObject["k"]["s"].Value <string>(), // symbol jObject["k"]["i"].Value <string>() .ToCandlestickInterval(), // interval jObject["k"]["t"].Value <long>(), // open time jObject["k"]["o"].Value <decimal>(), // open jObject["k"]["h"].Value <decimal>(), // high jObject["k"]["l"].Value <decimal>(), // low jObject["k"]["c"].Value <decimal>(), // close jObject["k"]["v"].Value <decimal>(), // volume jObject["k"]["T"].Value <long>(), // close time jObject["k"]["q"].Value <decimal>(), // quote asset volume jObject["k"]["n"].Value <long>(), // number of trades jObject["k"]["V"].Value <decimal>(), // taker buy base asset volume (volume of active buy) jObject["k"]["Q"].Value <decimal>() // taker buy quote asset volume (quote volume of active buy) ); var eventArgs = new CandlestickEventArgs(eventTime, args.Token, candlestick, firstTradeId, lastTradeId, isFinal); try { if (callbacks != null) { foreach (var callback in callbacks) { callback(eventArgs); } } Candlestick?.Invoke(this, eventArgs); } catch (OperationCanceledException) { } catch (Exception e) { if (!args.Token.IsCancellationRequested) { Logger?.LogError(e, $"{nameof(CandlestickWebSocketClient)}: Unhandled candlestick event handler exception."); } } } else { Logger?.LogWarning($"{nameof(CandlestickWebSocketClient)}.{nameof(OnWebSocketEvent)}: Unexpected event type ({eventType})."); } } catch (OperationCanceledException) { } catch (Exception e) { if (!args.Token.IsCancellationRequested) { Logger?.LogError(e, $"{nameof(CandlestickWebSocketClient)}.{nameof(OnWebSocketEvent)}"); } } }
public async Task StreamAsync(CancellationToken token) { if (!token.CanBeCanceled) { throw new ArgumentException("Token must be capable of being in the canceled state.", nameof(token)); } token.ThrowIfCancellationRequested(); if (Client.IsStreaming) { throw new InvalidOperationException($"{nameof(BinanceWebSocketStream)}: Already streaming ({nameof(IWebSocketClient)}.{nameof(IWebSocketClient.StreamAsync)} Task is not completed)."); } try { _bufferBlock = new BufferBlock <string>(new DataflowBlockOptions { EnsureOrdered = true, CancellationToken = token, BoundedCapacity = DataflowBlockOptions.Unbounded, MaxMessagesPerTask = DataflowBlockOptions.Unbounded }); _actionBlock = new ActionBlock <string>(json => { try { if (IsCombined) { var jObject = JObject.Parse(json); var stream = jObject["stream"].Value <string>(); if (!_subscribers.ContainsKey(stream)) { _logger?.LogDebug($"{nameof(BinanceWebSocketStream)}: No subscriber exists for stream: \"{stream}\" [thread: {Thread.CurrentThread.ManagedThreadId}]"); return; // ignore. } var args = new WebSocketStreamEventArgs(stream, jObject["data"].ToString(Formatting.None), token); foreach (var callback in _subscribers[stream]) { callback(args); } } else { var args = new WebSocketStreamEventArgs(_subscribers.Keys.Single(), json, token); foreach (var callback in _subscribers.Values.Single()) { callback(args); } } } catch (OperationCanceledException) { } catch (Exception e) { if (!token.IsCancellationRequested) { _logger?.LogError(e, $"{nameof(BinanceWebSocketStream)}: Unhandled {nameof(StreamAsync)} exception. [thread: {Thread.CurrentThread.ManagedThreadId}]"); } } }, new ExecutionDataflowBlockOptions { BoundedCapacity = 1, EnsureOrdered = true, //MaxMessagesPerTask = 1, MaxDegreeOfParallelism = 1, CancellationToken = token, SingleProducerConstrained = true }); _bufferBlock.LinkTo(_actionBlock); var uri = IsCombined ? new Uri($"{BaseUri}/stream?streams={string.Join("/", _subscribers.Keys)}") : new Uri($"{BaseUri}/ws/{_subscribers.Keys.Single()}"); Client.Message += OnClientMessage; _logger?.LogInformation($"{nameof(BinanceWebSocketStream)}.{nameof(StreamAsync)}: \"{uri.AbsoluteUri}\" [thread: {Thread.CurrentThread.ManagedThreadId}]"); await Client.StreamAsync(uri, token) .ConfigureAwait(false); } catch (OperationCanceledException) { } catch (Exception e) { if (!token.IsCancellationRequested) { _logger?.LogError(e, $"{nameof(BinanceWebSocketStream)}.{nameof(StreamAsync)}: Failed. [thread: {Thread.CurrentThread.ManagedThreadId}]"); throw; } } finally { Client.Message -= OnClientMessage; _bufferBlock?.Complete(); _actionBlock?.Complete(); _logger?.LogInformation($"{nameof(BinanceWebSocketStream)}.{nameof(StreamAsync)}: Task complete. [thread: {Thread.CurrentThread.ManagedThreadId}]"); } }
protected override void OnWebSocketEvent(WebSocketStreamEventArgs args, IEnumerable <Action <TradeEventArgs> > callbacks) { Logger?.LogDebug($"{nameof(TradeWebSocketClient)}: \"{args.Json}\""); try { var jObject = JObject.Parse(args.Json); var eventType = jObject["e"].Value <string>(); if (eventType == "trade") { var eventTime = jObject["E"].Value <long>().ToDateTimeK(); var trade = new Trade( jObject["s"].Value <string>(), // symbol jObject["t"].Value <long>(), // trade ID jObject["p"].Value <decimal>(), // price jObject["q"].Value <decimal>(), // quantity jObject["b"].Value <long>(), // buyer order ID jObject["a"].Value <long>(), // seller order ID jObject["T"].Value <long>() .ToDateTimeK(), // trade time jObject["m"].Value <bool>(), // is buyer the market maker? jObject["M"].Value <bool>()); // is best price match? var eventArgs = new TradeEventArgs(eventTime, args.Token, trade); try { if (callbacks != null) { foreach (var callback in callbacks) { callback(eventArgs); } } Trade?.Invoke(this, eventArgs); } catch (OperationCanceledException) { } catch (Exception e) { if (!args.Token.IsCancellationRequested) { Logger?.LogError(e, $"{nameof(TradeWebSocketClient)}: Unhandled aggregate trade event handler exception."); } } } else { Logger?.LogWarning($"{nameof(TradeWebSocketClient)}.{nameof(OnWebSocketEvent)}: Unexpected event type ({eventType})."); } } catch (OperationCanceledException) { } catch (Exception e) { if (!args.Token.IsCancellationRequested) { Logger?.LogError(e, $"{nameof(TradeWebSocketClient)}.{nameof(OnWebSocketEvent)}"); } } }
private void WebSocketCallback(WebSocketStreamEventArgs args) { OnWebSocketEvent(args, Subscribers.ContainsKey(args.StreamName) ? Subscribers[args.StreamName] : null); }
protected abstract void OnWebSocketEvent(WebSocketStreamEventArgs args, IEnumerable <Action <TEventArgs> > callbacks);
protected override void OnWebSocketEvent(WebSocketStreamEventArgs args, IEnumerable <Action <DepthUpdateEventArgs> > callbacks) { Logger?.LogDebug($"{nameof(DepthWebSocketClient)}: \"{args.Json}\""); try { var jObject = JObject.Parse(args.Json); var eventType = jObject["e"]?.Value <string>(); DepthUpdateEventArgs eventArgs; switch (eventType) { case null: // partial depth stream. { var symbol = args.StreamName.Split('@')[0].ToUpperInvariant(); // Simulate event time. var eventTime = DateTime.UtcNow.ToTimestampK().ToDateTimeK(); var lastUpdateId = jObject["lastUpdateId"].Value <long>(); var bids = jObject["bids"].Select(entry => (entry[0].Value <decimal>(), entry[1].Value <decimal>())).ToArray(); var asks = jObject["asks"].Select(entry => (entry[0].Value <decimal>(), entry[1].Value <decimal>())).ToArray(); eventArgs = new DepthUpdateEventArgs(eventTime, args.Token, symbol, lastUpdateId, lastUpdateId, bids, asks); break; } case "depthUpdate": { var symbol = jObject["s"].Value <string>(); var eventTime = jObject["E"].Value <long>().ToDateTimeK(); var firstUpdateId = jObject["U"].Value <long>(); var lastUpdateId = jObject["u"].Value <long>(); var bids = jObject["b"].Select(entry => (entry[0].Value <decimal>(), entry[1].Value <decimal>())).ToArray(); var asks = jObject["a"].Select(entry => (entry[0].Value <decimal>(), entry[1].Value <decimal>())).ToArray(); eventArgs = new DepthUpdateEventArgs(eventTime, args.Token, symbol, firstUpdateId, lastUpdateId, bids, asks); break; } default: Logger?.LogWarning($"{nameof(DepthWebSocketClient)}.{nameof(OnWebSocketEvent)}: Unexpected event type ({eventType})."); return; } try { if (callbacks != null) { foreach (var callback in callbacks) { callback(eventArgs); } } DepthUpdate?.Invoke(this, eventArgs); } catch (OperationCanceledException) { } catch (Exception e) { if (!args.Token.IsCancellationRequested) { Logger?.LogError(e, $"{nameof(DepthWebSocketClient)}: Unhandled depth update event handler exception."); } } } catch (OperationCanceledException) { } catch (Exception e) { if (!args.Token.IsCancellationRequested) { Logger?.LogError(e, $"{nameof(DepthWebSocketClient)}.{nameof(OnWebSocketEvent)}"); } } }