private async void StreamingEventReceivedHandler(object sender, StreamingEventReceivedEventArgs args) { if (args.Response is OrderbookResponse response) { var ticker = _tickersByFigi[response.Payload.Figi]; if (!Quotes.ContainsKey(ticker)) { Debug.WriteLine($"Received unexpected Tinkoff orderbook for ticker \"{ticker}\""); var request = StreamingRequest.UnsubscribeOrderbook(response.Payload.Figi, StockQuoteDepth); await _context.SendStreamingRequestAsync(request).ConfigureAwait(false); return; } var quote = Quotes[ticker]; if (response.Payload.Bids.Count > 0) { quote.TinkoffBidPrice = response.Payload.Bids[0][0]; quote.TinkoffBidSize = response.Payload.Bids[0][1]; } if (response.Payload.Asks.Count > 0) { quote.TinkoffAskPrice = response.Payload.Asks[0][0]; quote.TinkoffAskSize = response.Payload.Asks[0][1]; } quote.TinkoffMarketPrice = (quote.TinkoffBidPrice * quote.TinkoffBidSize + quote.TinkoffAskPrice * quote.TinkoffAskSize) / (quote.TinkoffBidSize + quote.TinkoffAskSize); quote.TinkoffUpdatedAt = response.Time; quote.LongDelta = quote.BidPrice - quote.TinkoffAskPrice; quote.ShortDelta = quote.TinkoffBidPrice - quote.AskPrice; quote.LongDeltaPercent = quote.LongDelta * 100 / quote.TinkoffMarketPrice; quote.ShortDeltaPercent = quote.ShortDelta * 100 / quote.TinkoffMarketPrice; } }
private void StreamingEventReceivedHandler(object sender, StreamingEventReceivedEventArgs args) { switch (args.Response) { case CandleResponse response: var ticker = _tickersByFigi[response.Payload.Figi]; var currentValue = (double)response.Payload.Close; var previousValue = (double?)null; if (!Candles.ContainsKey(ticker)) { Candles[ticker] = response.Payload; } else { previousValue = (double?)Candles[ticker].Close; } if (!NextSignalTime.ContainsKey(ticker) || NextSignalTime[ticker] < DateTime.UtcNow) { var signalValue = GetSignalValue(currentValue, previousValue); if (signalValue.HasValue) { RaiseSendingSignalEvent(ticker, signalValue.Value); } } break; case StreamingErrorResponse response: _logger.LogError("Error response received: {StreamingErrorResponse}", response); break; default: _logger.LogError("Received unrecognized type of streaming response. {StreamingResponse}", args.Response); break; } }
private void _sandboxContext_StreamingEventReceived(object sender, StreamingEventReceivedEventArgs e) { StreamingEventReceived(sender, e); }
private async void Broker_StreamingEventReceived(object sender, StreamingEventReceivedEventArgs e) { Debug.WriteLine(JsonConvert.SerializeObject(e.Response)); switch (e.Response) { case CandleResponse cr: { _lastEventReceived = DateTime.Now; var candle = cr.Payload; var stock = _tradingVM.Stocks.FirstOrDefault(s => s.Figi == candle.Figi); if (stock != null) { stock.IsNotifying = false; if (candle.Interval == CandleInterval.Day) { stock.TodayOpen = candle.Open; stock.TodayDate = candle.Time; stock.LastUpdate = DateTime.Now; stock.Price = candle.Close; if (stock.TodayOpen > 0) { stock.DayChange = (stock.Price - stock.TodayOpen) / stock.TodayOpen; } stock.DayVolume = candle.Volume; Interlocked.Increment(ref _refreshPendingCount); // отписываемся от дневного апдейта и подписываемся на 5минутную свечу QueueBrokerAction(b => b.SendStreamingRequestAsync( UnsubscribeCandle(stock.Figi, CandleInterval.Day)), $"Отписка от часовой свечи {stock.Ticker} ({stock.Figi})"); QueueBrokerAction(b => b.SendStreamingRequestAsync( SubscribeCandle(stock.Figi, CandleInterval.Minute)), $"Подписка на минутную свечу {stock.Ticker} ({stock.Figi})"); } #region 5min code //else if (candle.Interval == CandleInterval.FiveMinutes) //{ // if (candle.Time.Date > stock.TodayDate) // { // QueueBrokerAction(b => b.SendStreamingRequestAsync( // UnsubscribeCandle(stock.Figi, CandleInterval.FiveMinutes)), // $"Отписка от 5-ти минутной свечи {stock.Ticker} ({stock.Figi})"); // QueueBrokerAction(b => b.SendStreamingRequestAsync( // SubscribeCandle(stock.Figi, CandleInterval.Day)), // $"Подписка на дневную свечу {stock.Ticker} ({stock.Figi})"); // break; // } // var change = (candle.Close - candle.Open) / candle.Open; // stock.Price = candle.Close; // stock.LastUpdate = DateTime.Now; // if (stock.TodayOpen > 0) // stock.DayChange = (stock.Price - stock.TodayOpen) / stock.TodayOpen; // if (stock.DayChange > (decimal)0.05 // && (stock.LastAboveThreshholdDate == null // || stock.LastAboveThreshholdDate.Value.Date < stock.LastUpdate.Date)) // { // stock.LastAboveThreshholdDate = stock.LastUpdate; // var infoReq = StreamingRequest.SubscribeInstrumentInfo(stock.Figi); // QueueBrokerAction(b => b.SendStreamingRequestAsync(infoReq), // $"Подписка на статус инструмента {stock.Ticker} ({stock.Figi})"); // //if (stock.MonthVolume == 0) // //await GetMonthStats(stock); // //_telegram.PostMessage(stock.GetChangeInfoText()); // //_telegram.PostMessage($"Цена {stock.Ticker} изменилась на {stock.DayChange:P2} с начала дня. Объем (5 минут): {candle.Volume}" + // // $"\r\nЦена на начало дня: {stock.TodayOpenF}, Текущая: {stock.PriceF}"); // _uiContext.Post(obj => { // _tradingVM.Messages.Add(new MessageViewModel // { // Ticker = stock.Ticker, // Date = DateTime.Now, // Change = stock.DayChange, // Volume = candle.Volume, // Text = $"Цена {stock.Ticker} изменилась на {stock.DayChange:P2} с начала дня." // }); // }, null); // Interlocked.Increment(ref _refreshPendingCount); // } // if ((change > (decimal)0.05 || change < (decimal)-0.05) && (stock.LastAboveThreshholdCandleTime == null // || stock.LastAboveThreshholdCandleTime < candle.Time)) // { // stock.LastAboveThreshholdCandleTime = candle.Time; // if (stock.MonthVolume == 0) // await GetMonthStats(stock); // _telegram.PostMessage(stock.GetFiveMinChangeInfoText()); // //_telegram.PostMessage($"Цена {stock.Ticker} изменилась на {change:P2} за 5 минут. Объем за 5 минут: {candle.Volume}" + // // $"\r\nКурс на начало дня: {stock.TodayOpenF}, Текущий: {stock.PriceF}, Изменение за день: {stock.DayChangeF}"); // _uiContext.Post(obj => { // _tradingVM.Messages.Add(new MessageViewModel // { // Ticker = stock.Ticker, // Date = DateTime.Now, // Change = change, // Volume = candle.Volume, // Text = $"Цена {stock.Ticker} изменилась на {change:P2} за 5 минут." // }); // }, null); // Interlocked.Increment(ref _refreshPendingCount); // } // QueueBrokerAction(b => b.SendStreamingRequestAsync( // UnsubscribeCandle(stock.Figi, CandleInterval.FiveMinutes)), // $"Отписка от 5-ти минутной свечи {stock.Ticker} ({stock.Figi})"); // QueueBrokerAction(b => b.SendStreamingRequestAsync( // SubscribeCandle(stock.Figi, CandleInterval.Minute)), // $"Подписка на минутную свечу {stock.Ticker} ({stock.Figi})"); //} #endregion else if (candle.Interval == CandleInterval.Minute) { if (candle.Time.Date > stock.TodayDate) { QueueBrokerAction(b => b.SendStreamingRequestAsync( UnsubscribeCandle(stock.Figi, CandleInterval.Minute)), $"Отписка от минутной свечи {stock.Ticker} ({stock.Figi})"); QueueBrokerAction(b => b.SendStreamingRequestAsync( SubscribeCandle(stock.Figi, CandleInterval.Day)), $"Подписка на дневную свечу {stock.Ticker} ({stock.Figi})"); break; } stock.Price = candle.Close; if (stock.TodayOpen > 0) { stock.DayChange = (stock.Price - stock.TodayOpen) / stock.TodayOpen; } stock.LastUpdate = DateTime.Now; stock.LogCandle(candle); if (TradeBot != null) { await TradeBot.Check(stock); } if (stock.DayChange > DayChangeTrigger && (stock.LastAboveThreshholdDate == null || stock.LastAboveThreshholdDate.Value.Date < stock.LastUpdate.Date)) { stock.LastAboveThreshholdDate = stock.LastUpdate; var infoReq = StreamingRequest.SubscribeInstrumentInfo(stock.Figi); QueueBrokerAction(b => b.SendStreamingRequestAsync(infoReq), $"Подписка на статус инструмента {stock.Ticker} ({stock.Figi})"); if (IsTelegramEnabled) { _telegram.PostMessage(stock.GetDayChangeInfoText(), stock.Ticker); } BackgroundInvoke(() => { _tradingVM.Messages.Add(new MessageViewModel { Ticker = stock.Ticker, Date = DateTime.Now, Change = stock.DayChange, Volume = candle.Volume, Text = $"Цена {stock.Ticker} изменилась на {stock.DayChange:P2} с начала дня." }); }); Interlocked.Increment(ref _refreshPendingCount); } var change = stock.GetLast10MinChange(TenMinChangeTrigger); if (Math.Abs(change.change) > TenMinChangeTrigger && stock.DayChange > TenMinChangeTrigger && (stock.LastAboveThreshholdCandleTime == null || stock.LastAboveThreshholdCandleTime < candle.Time.AddMinutes(-change.minutes))) { stock.LastAboveThreshholdCandleTime = candle.Time; try { await GetMonthStats(stock); } catch (Exception ex) { await ResetConnection("Ошибка при получении статистики за месяц: " + ex.Message); } if (IsTelegramEnabled) { _telegram.PostMessage(stock.GetMinutesChangeInfoText(change.change, change.minutes, change.candles), stock.Ticker); } _uiContext.Post(obj => { _tradingVM.Messages.Add(new MessageViewModel { Ticker = stock.Ticker, Date = DateTime.Now, Change = change.change, Volume = candle.Volume, Text = $"Цена {stock.Ticker} изменилась на {change.change:P2} за {change.minutes} мин." }); }, null); if (TradeBot != null && change.change > TenMinChangeTrigger && stock.DayChange < 0.2m && change.candles.Sum(c => c.Volume) > 100) { await TradeBot.Buy(stock); } Interlocked.Increment(ref _refreshPendingCount); } } stock.IsNotifying = true; } break; } case OrderbookResponse or: { var stock = _tradingVM.Stocks.FirstOrDefault(s => s.Figi == or.Payload.Figi); if (stock != null && or.Payload.Asks.Count > 0 && or.Payload.Bids.Count > 0) { stock.Price = (or.Payload.Asks[0][0] + or.Payload.Bids[0][0]) / 2; if (stock.TodayOpen > 0) { stock.DayChange = (stock.Price - stock.TodayOpen) / stock.TodayOpen; stock.LastUpdate = or.Time.ToLocalTime(); } } break; } case InstrumentInfoResponse ir: { var info = ir.Payload; var stock = _tradingVM.Stocks.FirstOrDefault(s => s.Figi == info.Figi); if (stock != null) { stock.Status = info.TradeStatus; } break; } } }
private async void Broker_StreamingEventReceived(object sender, StreamingEventReceivedEventArgs e) { //Debug.WriteLine(JsonConvert.SerializeObject(e.Response)); _lastEventReceived = DateTime.Now; switch (e.Response) { case CandleResponse cr: { _stockProcessingQueue.Enqueue(cr); break; } case OrderbookResponse or: { var stock = _mainModel.Stocks.Values.FirstOrDefault(s => s.Figi == or.Payload.Figi); if (stock != null && or.Payload.Asks.Count > 0 && or.Payload.Bids.Count > 0) { var bids = or.Payload.Bids.Where(b => b[1] >= 1).ToList(); var asks = or.Payload.Asks.Where(a => a[1] >= 1).ToList(); OrderbookInfoSpb[stock.Ticker] = new OrderbookModel(or.Payload.Depth, bids, asks, or.Payload.Figi, stock.Ticker, stock.Isin); stock.BestBidSpb = bids[0][0]; stock.BestAskSpb = asks[0][0]; _stockProcessingQueue.Enqueue(stock); // raise stock update (but only in sync with other updates) } break; } case InstrumentInfoResponse ir: { var info = ir.Payload; var stock = _mainModel.Stocks.Values.FirstOrDefault(s => s.Figi == info.Figi); if (stock != null) { if (String.IsNullOrWhiteSpace(stock.Status)) { string status = info.TradeStatus; if (status == "normal_trading") { status = "Торгуется"; } stock.Status = status; if (String.IsNullOrEmpty(stock.Status)) { stock.Status = Instruments[stock.Ticker].InstrumentStatusShortDesc; } } if (stock.LimitDown != ir.Payload.LimitDown) { stock.LimitDown = ir.Payload.LimitDown; } if (stock.LimitUp != ir.Payload.LimitUp) { stock.LimitUp = ir.Payload.LimitUp; } if (stock.MinPriceIncrement != ir.Payload.MinPriceIncrement) { stock.MinPriceIncrement = ir.Payload.MinPriceIncrement; } } break; } } }