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
                    });
                }
            }
        }
Beispiel #2
0
        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}) "
                    );
            }
        }