public async Task <(int deletedCandlesCount, int replacedCandlesCount)> FixExtremeCandlesAsync(IReadOnlyList <ICandle> extremeCandles, CandlePriceType priceType) { // Okay, the incorrect candles are listed. Now we need to group it by time intervals and iterate from the // smallest interval to the biggest, back. But now we will delete the smallest candles at all, and then // recalculate (and replace in storage) the bigger candles. int deletedCountSummary = 0, replacedCountSummary = 0; var candlesByInterval = extremeCandles .GroupBy(c => (int)c.TimeInterval) .ToSortedDictionary(g => g.Key); if (candlesByInterval.Count != Constants.DbStoredIntervals.Length) { throw new ArgumentException($"Something is wrong: the amount of (unique) time intervals in extreme candles list is not equal to stored intervals array length. " + $"Filtration for {priceType} is impossible."); } foreach (var candleBatch in candlesByInterval) { var interval = (CandleTimeInterval)candleBatch.Key; // The Second time interval is the smallest, so, we simply delete such a candles from storage and go next. if (interval == CandleTimeInterval.Sec) { var deletedCountQuant = await _candlesHistoryRepository.DeleteCandlesAsync(candleBatch.Value.ToList()); deletedCountSummary += deletedCountQuant; continue; } var smallerInterval = GetSmallerInterval(interval); // My dear friend, who will read or refactor this code in future, please, excuse me for such a terrible // loop cascade. Currently, I just can't imagine how to make it shorter and more comfortable for reading. // And from the other side, there is no such a necessity to invent an ideal bicycle here. // ReSharper disable once PossibleNullReferenceException var currentCandlesToReplace = new List <ICandle>(); var currentCandlesToDelete = new List <ICandle>(); foreach (var candle in candleBatch.Value.AsEnumerable()) { var dateFrom = candle.Timestamp; var dateTo = candle.Timestamp.AddIntervalTicks(1, interval); var smallerCandles = await _candlesHistoryRepository.GetCandlesAsync(candle.AssetPairId, smallerInterval, priceType, dateFrom, dateTo); // Trying to reconstruct the candle from the corresponfing smaller interval candles, if any. ICandle updatedCandle = null; foreach (var smallerCandle in smallerCandles) { if (updatedCandle == null) { updatedCandle = smallerCandle .RebaseToInterval(interval); } else { updatedCandle = updatedCandle .ExtendBy(smallerCandle .RebaseToInterval(interval)); } } // If the candle is not empty (e.g., there are some smaller interval candles for its construction), // we should update it in storage. Otherwise, it is to be deleted from there. if (updatedCandle != null) { currentCandlesToReplace.Add(updatedCandle); } else { currentCandlesToDelete.Add(candle); } } if (currentCandlesToDelete.Count > 0) { var deletedCountQuant = await _candlesHistoryRepository.DeleteCandlesAsync(currentCandlesToDelete); deletedCountSummary += deletedCountQuant; } if (currentCandlesToReplace.Count > 0) { var replacedCountQuant = await _candlesHistoryRepository.ReplaceCandlesAsync(currentCandlesToReplace); replacedCountSummary += replacedCountQuant; } } return(deletedCandlesCount : deletedCountSummary, replacedCandlesCount : replacedCountSummary); }
/// <summary> /// Extends a candle by a trade, if trade's DateTime corresponds to candle's TimeStamp (i.e., the trade belongs to the same time period). /// </summary> public static ICandle ExtendBy(this ICandle self, TradeHistoryItem trade, decimal volumeMultiplier = 1.0M) { var tradeCandle = trade.CreateCandle(self.AssetPairId, self.PriceType, self.TimeInterval, volumeMultiplier); return(self.ExtendBy(tradeCandle)); }