public async Task FlushCandlesIfAny()
            {
                var dataWritingTasks = new List <Task>();

                foreach (var interval in Candles.Constants.DbStoredIntervals)
                {
                    // If there still remain any active candles which have not been added to persistent queue till this moment.
                    if (_activeCandles.TryGetValue(interval, out var unsavedActiveCandle) &&
                        _persistenceCandleQueue[interval].All(c => c.Timestamp != unsavedActiveCandle.Timestamp))
                    {
                        // But we save only candles which are fully completed by the _upperDateLimit moment.
                        // I.e., if we have _upperDateLimit = 2018.03.05 16:00:15, we should store only the:
                        // - second candles with timestamp not later than 16:00:14;
                        // - minute candles not later than 15:59:00;
                        // - hour candles not later than 15:00:00;
                        // - day candle not later than 2018.03.04;
                        // - week candles not later than 2018.02.26;
                        // - and, finally, month candles not later than 2018.02.
                        if (_upperDateLimit == null ||
                            unsavedActiveCandle.Timestamp.AddIntervalTicks(1, unsavedActiveCandle.TimeInterval) <= _upperDateLimit)
                        {
                            _persistenceCandleQueue[interval].Add(unsavedActiveCandle);
                        }
                    }

                    // And now, we save the resulting candles to storage (if any).
                    if (!_persistenceCandleQueue[interval].Any())
                    {
                        continue;
                    }

                    dataWritingTasks.Add(_historyRepo.ReplaceCandlesAsync(_persistenceCandleQueue[interval]));

                    _healthService[_assetPairId].SummarySavedCandles +=
                        _persistenceCandleQueue[interval].Count;

                    _persistenceCandleQueue[interval].Clear(); // Protection against multiple calls
                }

                await Task.WhenAll(dataWritingTasks);

                _healthService.Health.PersistenceQueueSize = 0;
            }
        private async Task FixMidPricesAsync(string assetPairId, int assetPairAccuracy, bool analyzeOnly)
        {
            try
            {
                _log.Info($"Starting mid candle prices fix for {assetPairId}...");

                Health.Message = "Getting candles to fix...";
                var candles = await GetFixedCandlesAsync(assetPairId, assetPairAccuracy);

                if (!candles.Any())
                {
                    _log.Info($"There are no candles to fix for {assetPairId}. Skipping.");
                    return;
                }

                if (analyzeOnly)
                {
                    _log.Info($"Mid candle prices fix for {assetPairId} finished: analyze only. Candles to fix: {Health.CorruptedCandlesCount}");
                    return;
                }

                //sort by interval
                var candlesByInterval = candles
                                        .GroupBy(c => (int)c.TimeInterval)
                                        .ToSortedDictionary(g => g.Key);

                Health.Message = "Fixing candles from Sec to bigger intervals...";

                //replace candles from Sec to bigger intervals
                foreach (var candleBatch in candlesByInterval)
                {
                    var candlesToReplace = candleBatch.Value.ToList();
                    await _candlesHistoryRepository.ReplaceCandlesAsync(candlesToReplace);

                    Health.FixedCandlesCount += candlesToReplace.Count;
                }

                _log.Info($"Mid candle prices fix for {assetPairId} finished.");
            }
            catch (Exception ex)
            {
                Health.Errors.Add($"{assetPairId}: {ex.Message}");
                _log.Error(nameof(FixMidPricesAsync), ex);
            }
        }
Beispiel #3
0
        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);
        }