public async Task PerformArbitrageCheck(IStockModel stock, CancellationToken cancellationToken) { if (stock.LastUpdateUSA == null || Math.Abs(stock.LastUpdate.Subtract(stock.LastUpdateUSA.Value).TotalMinutes) > 1) { return; } if (_arbitrageEvents.ContainsKey(stock.Ticker) && _arbitrageEvents[stock.Ticker] is ArbitrageEventData arbitrageEvent && (arbitrageEvent.EventTime.Elapsed().TotalMinutes < 1 || (arbitrageEvent.BestAskSpb == stock.BestAskSpb && arbitrageEvent.BestBidSpb == stock.BestBidSpb))) { return; } var message = new StringBuilder(); decimal diff; string line; var quote = DataManager.QuoteDatas[stock.Ticker]; OrderbookModel spbOrders = null; bool bLong = false, bShort = false; if (stock.USBidRUAskDiff > 0.01m) { bLong = true; spbOrders = spbOrders ?? StocksManager.OrderbookInfoSpb[stock.Ticker]; diff = stock.BidUSA - stock.BestAskSpb; message.AppendLine($"*{stock.Ticker}* - {stock.Name}"); message.AppendLine($"✅ *{stock.USBidRUAskDiff.FormatPercent()}* ({diff.FormatPrice(stock.Currency)})"); message.AppendLine($"SPB *B {spbOrders.Bids[0][0]}* ({spbOrders.Bids[0][1]}) *A {spbOrders.Asks[0][0]}* ({spbOrders.Asks[0][1]})"); message.AppendLine($"USA *B {quote.Bid}* ({quote.BidSize}) *A {quote.Ask}* ({quote.AskSize})"); line = $"SPB ASK *{stock.BestAskSpb.FormatPrice(stock.Currency)}* USA BID *{stock.BidUSA.FormatPrice(stock.Currency)}* Diff {diff.FormatPrice(stock.Currency)} ({stock.USBidRUAskDiff.FormatPercent()})"; MainModel.AddMessage(MessageKind.ArbitrageLong, stock.Ticker, DateTime.Now, diff, quote.BidSize, line); } if (stock.RUBidUSAskDiff > 0.01m) { bShort = true; spbOrders = spbOrders ?? StocksManager.OrderbookInfoSpb[stock.Ticker]; diff = stock.BestBidSpb - stock.AskUSA; message.AppendLine($"*{stock.Ticker}* - {stock.Name}"); message.AppendLine($"⛔️ *{stock.RUBidUSAskDiff.FormatPercent()}* ({diff.FormatPrice(stock.Currency)})"); message.AppendLine($"SPB *B {spbOrders.Bids[0][0]}* ({spbOrders.Bids[0][1]}) *A {spbOrders.Asks[0][0]}* ({spbOrders.Asks[0][1]})"); message.AppendLine($"USA *B {quote.Bid}* ({quote.BidSize}) *A {quote.Ask}* ({quote.AskSize})"); line = $"USA ASK *{stock.AskUSA.FormatPrice(stock.Currency)}* SPB BID *{stock.BestBidSpb.FormatPrice(stock.Currency)}* Diff {diff.FormatPrice(stock.Currency)} ({stock.RUBidUSAskDiff.FormatPercent()})"; if (stock.CanBeShorted) { MainModel.AddMessage(MessageKind.ArbitrageShort, stock.Ticker, DateTime.Now, diff, quote.AskSize, line); } } if (message.Length > 0) { _arbitrageEvents[stock.Ticker] = new ArbitrageEventData() { EventTime = DateTime.Now, Ticker = stock.Ticker, BestBidSpb = stock.BestBidSpb, BestAskSpb = stock.BestAskSpb }; message.AppendLine("`"); int i = 0; foreach (var spbOrdersBid in spbOrders.Bids) { message.Append($"\tSPB BID {spbOrdersBid[0].ToString().PadLeft(2).PadRight(6)} Size {spbOrdersBid[1].ToString().PadLeft(2).PadRight(5)}"); if (i < spbOrders.Asks.Count) { var ask = spbOrders.Asks[i++]; message.Append($" Ask {ask[0].ToString().PadRight(6)} Size {ask[1]}"); } message.AppendLine(); } message.Append("`"); var text = message.ToString(); if (_chatIdLong != 0 && bLong) { StocksManager.Telegram.PostMessage(new TelegramMessage(stock.Ticker, text, _chatIdLong) { AddTickerImage = false, MessageMode = ParseMode.MarkdownV2 }); } if (_chatIdShort != 0 && bShort && stock.CanBeShorted) { StocksManager.Telegram.PostMessage(new TelegramMessage(stock.Ticker, text, _chatIdShort) { AddTickerImage = false, MessageMode = ParseMode.MarkdownV2 }); } } }
public async Task PerformQuotePercentChangeCheck(IStockModel stock) { if (stock.Price == 0 || stock.MinuteCandles.Count == 0) { return; } var lastCandleTime = stock.MinuteCandles.Max(c => c.Value.Time); var candle = stock.MinuteCandles.Last(c => c.Value.Time == lastCandleTime).Value; if (Math.Abs(stock.DayChange) > Settings.MinDayPriceChange && Settings.MinDayPriceChange > 0 && (stock.LastAboveThresholdDate == null || stock.LastAboveThresholdDate.Value.Date < stock.LastUpdate.Date)) { stock.LastAboveThresholdDate = stock.LastUpdate; if (!await EnsureHistoryLoaded(stock)) { return; } var volPerc = stock.DayVolume / stock.AvgDayVolumePerMonth; if (volPerc > Settings.MinVolumeDeviationFromDailyAverage && Settings.MinVolumeDeviationFromDailyAverage > 0) { string chatId = Settings.TgChatId; if (stock.Currency.Equals("RUB", StringComparison.InvariantCultureIgnoreCase) || stock.Ticker == "TCS") { chatId = Settings.TgChatIdRu; } if (IsSendToTelegramEnabled && long.TryParse(chatId, out var lChatId) && lChatId != 0) { StocksManager.Telegram.PostMessage(stock.GetDayChangeInfoText(), stock.Ticker, lChatId); } MainModel.AddMessage( MessageKind.DayChange, stock.Ticker, DateTime.Now, stock.DayChange, stock.DayVolume, $"{stock.Ticker}: {stock.DayChange.Arrow(true)} {stock.DayChange:P2} с начала дня ({stock.TodayOpenF} → {stock.PriceF})" ); } } var change = stock.GetLastXMinChange( Settings.NumOfMinToCheck, Settings.NumOfMinToCheckVol, Settings.MinXMinutesPriceChange, Settings.MinXMinutesVolChange); if (!change.volumeTrigger && Settings.MinXMinutesPriceChange > 0 && Math.Abs(change.change) > Settings.MinXMinutesPriceChange && (stock.LastAboveThresholdCandleTime == null || stock.LastAboveThresholdCandleTime < candle.Time.AddMinutes(-change.minutes))) { stock.LastAboveThresholdCandleTime = candle.Time; if (!await EnsureHistoryLoaded(stock)) { return; } var changeInfo = stock.GetMinutesChangeInfo(change.change, change.minutes, change.candles); string chatId = Settings.TgChatId; if (stock.Currency.Equals("RUB", StringComparison.InvariantCultureIgnoreCase) || stock.Ticker == "TCS") { chatId = Settings.TgChatIdRu; } if (IsSendToTelegramEnabled && long.TryParse(chatId, out var lChatId) && lChatId != 0) { if (changeInfo.volPercent >= Settings.MinVolumeDeviationFromDailyAverage) { StocksManager.Telegram.PostMessage(changeInfo.message, stock.Ticker, lChatId); } } int lastIdx = change.candles.Length - 1; MainModel.AddMessage( MessageKind.MinutesChanges, stock.Ticker, DateTime.Now, stock.DayChange, candle.Volume, $"{stock.Ticker}: {change.change:P2} за {change.minutes} мин. ({change.candles[lastIdx].Open.FormatPrice(stock.Currency),2} → {change.candles[0].Close.FormatPrice(stock.Currency),-2}) " ); } if (change.volumeTrigger && Settings.MinXMinutesVolChange > 0 && change.volChange > Settings.MinXMinutesVolChange && (stock.LastAboveVolThresholdCandleTime == null || stock.LastAboveVolThresholdCandleTime < candle.Time.AddMinutes(-change.minutes))) { stock.LastAboveVolThresholdCandleTime = candle.Time; if (!await EnsureHistoryLoaded(stock)) { return; } var changeInfo = stock.GetMinutesVolumeChangeInfo(change.change, change.minutes, change.candles); string chatId = Settings.TgChatId; if (stock.Currency.Equals("RUB", StringComparison.InvariantCultureIgnoreCase) || stock.Ticker == "TCS") { chatId = Settings.TgChatIdRu; } if (IsSendToTelegramEnabled && long.TryParse(chatId, out var lChatId) && lChatId != 0) { if (changeInfo.volPercent >= Settings.MinVolumeDeviationFromDailyAverage) { StocksManager.Telegram.PostMessage(changeInfo.message, stock.Ticker, lChatId); } } int lastIdx = change.candles.Length - 1; MainModel.AddMessage( MessageKind.VolumeChange, stock.Ticker, DateTime.Now, stock.DayChange, candle.Volume, $"{stock.Ticker}: VOL {change.volChange:P2} за {change.minutes} мин. ({change.candles[lastIdx].Open.FormatPrice(stock.Currency),2} → {change.candles[0].Close.FormatPrice(stock.Currency),-2}) " ); } }