예제 #1
0
        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]);
        }
예제 #2
0
        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)}");
                }
            }
        }
예제 #3
0
        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)}");
                }
            }
        }
예제 #4
0
        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)}");
                }
            }
        }
예제 #5
0
        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}]");
            }
        }
예제 #6
0
        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)}");
                }
            }
        }
예제 #7
0
 private void WebSocketCallback(WebSocketStreamEventArgs args)
 {
     OnWebSocketEvent(args, Subscribers.ContainsKey(args.StreamName) ? Subscribers[args.StreamName] : null);
 }
예제 #8
0
 protected abstract void OnWebSocketEvent(WebSocketStreamEventArgs args, IEnumerable <Action <TEventArgs> > callbacks);
예제 #9
0
        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)}");
                }
            }
        }