public static void FillGapsByTickerFast(string ticker,
                                                DateTime?startTime,
                                                DateTime?endTime,
                                                List <GapInfo> gaps,
                                                string quoteCacheFolder,
                                                BackgroundWorker worker,
                                                Action <string, List <GapInfo> > gapsUpdatedAction)
        {
            if (gaps == null || gaps.Count == 0)
            {
                return;
            }

            // старые свечи
            var candlesOld = AtomCandleStorage.Instance.GetAllMinuteCandles(ticker) ?? new List <CandleData>();
            var visualGaps = new List <GapInfo>();

            if (worker != null && worker.CancellationPending)
            {
                return;
            }

            // корректируем интервал запроса
            var requestGaps = new List <GapInfo>();
            var histStart   = GapMap.Instance.GetServerTickerHistoryStart(ticker);

            var startFillTime = startTime.HasValue
                                    ? (startTime.Value < histStart ? histStart : startTime.Value)
                                    : histStart;
            var endFillTime = endTime.HasValue ? endTime.Value : DateTime.Now;

            if (startFillTime > endFillTime)
            {
                return;
            }

            // отсекаем части гэпов или гэпы целиком, находящиеся за пределами интервала запроса
            foreach (var gap in gaps)
            {
                if (gap.end < startFillTime || gap.start > endFillTime)
                {
                    visualGaps.Add(new GapInfo
                    {
                        start  = gap.start,
                        end    = gap.end,
                        status = GapInfo.GapStatus.FailedToFill
                    });
                    continue;
                }
                var start = gap.start;
                var end   = gap.end;
                if (start < startFillTime)
                {
                    visualGaps.Add(new GapInfo
                    {
                        start  = gap.start,
                        end    = startFillTime.AddMinutes(-1),
                        status = GapInfo.GapStatus.FailedToFill
                    });
                    start = startFillTime;
                }
                if (end > endFillTime)
                {
                    visualGaps.Add(new GapInfo
                    {
                        start  = endFillTime.AddMinutes(1),
                        end    = gap.end,
                        status = GapInfo.GapStatus.FailedToFill
                    });
                    end = endFillTime;
                }
                requestGaps.Add(new GapInfo {
                    start = start, end = end
                });
                visualGaps.Add(new GapInfo
                {
                    start = start,
                    end   = end
                });
            }

            // группируем - составляем запросы
            var gapLists = GapList.CreateGapList(requestGaps, daysInQuoteServerRequest);

            // обновляем контрол
            gapsUpdatedAction(ticker, visualGaps);

            if (gapLists.Count == 0 || gapLists.First().Gaps.Count == 0)
            {
                return;
            }

            // в результат добавляем старые начальные свечи, не вошедшие в запрос
            var candlesOldIndex = 0;
            var candlesNew      = new List <CandleData>(); // результат - обновленные свечи
            var nextGap         = gapLists.First().Gaps.First();

            for (; candlesOldIndex < candlesOld.Count; candlesOldIndex++)
            {
                if (candlesOld[candlesOldIndex].timeOpen > nextGap.start)
                {
                    break;
                }
                candlesNew.Add(candlesOld[candlesOldIndex]);
            }

            // отправляем запросы, параллельно обновляя контрол
            for (var gapListIndex = 0; gapListIndex < gapLists.Count; gapListIndex++)
            {
                // проверяем на завершение операции
                if (worker != null && worker.CancellationPending)
                {
                    return;
                }
                if (currentTickerCancelled)
                {
                    currentTickerCancelled = false;
                    return;
                }

                var gapList = gapLists[gapListIndex];
                if (gapList.Gaps.Count == 0)
                {
                    continue;
                }
                var ok = LoadQuotesFromServerFast(ticker, gapList, candlesNew);

                // добавляем старые свечи, встретившиеся между гэпами (после gapIndex до gapIndex + 1)
                DateTime lastTime;
                for (var gapIndex = 0; gapIndex < gapList.Gaps.Count - 1; gapIndex++)
                {
                    lastTime = gapList.Gaps[gapIndex].end;
                    var nextTime = gapList.Gaps[gapIndex + 1].start;
                    var candlesOldFoundIndexForGap = candlesOld.FindIndex(candlesOldIndex, c => c.timeOpen > lastTime);
                    if (candlesOldFoundIndexForGap == -1)
                    {
                        continue;
                    }
                    var insertPosition = candlesNew.FindIndex(c => c.timeOpen >= nextTime);
                    if (insertPosition == -1)
                    {
                        continue;
                    }
                    var candlesOldForGap = new List <CandleData>();
                    for (; candlesOldFoundIndexForGap < candlesOld.Count; candlesOldFoundIndexForGap++)
                    {
                        if (candlesOld[candlesOldFoundIndexForGap].timeOpen >= nextTime)
                        {
                            break;
                        }
                        candlesOldForGap.Add(candlesOld[candlesOldFoundIndexForGap]);
                    }
                    candlesOldIndex = candlesOldFoundIndexForGap;
                    candlesNew.InsertRange(insertPosition, candlesOldForGap);
                }

                // обновляем контрол
                foreach (var gap in gapList.Gaps)
                {
                    var thisGap = gap;
                    visualGaps.RemoveByPredicate(g => g.start == thisGap.start && g.end == thisGap.end, true);
                    visualGaps.Add(new GapInfo
                    {
                        start  = gap.start,
                        end    = gap.end,
                        status = ok ? GapInfo.GapStatus.Filled : GapInfo.GapStatus.FailedToFill
                    });
                }
                gapsUpdatedAction(ticker, visualGaps);

                // добавляем старые свечи, встретившиеся между запросами
                lastTime = gapList.Gaps.Last().end;
                var candlesOldFoundIndex = candlesOld.FindIndex(candlesOldIndex, c => c.timeOpen > lastTime);
                if (candlesOldFoundIndex == -1)
                {
                    continue;
                }
                candlesOldIndex = candlesOldFoundIndex;
                // если есть еще гэп, то добавляем свечки до него
                if (gapListIndex + 1 >= gapLists.Count)
                {
                    continue;
                }
                nextGap = gapLists[gapListIndex + 1].Gaps.First();
                for (; candlesOldIndex < candlesOld.Count; candlesOldIndex++)
                {
                    if (candlesOld[candlesOldIndex].timeOpen >= nextGap.start)
                    {
                        break;
                    }
                    candlesNew.Add(candlesOld[candlesOldIndex]);
                }
            }

            // сохраняем результат
            // сохраняем карту гэпов
            // дырки, оставшиеся в истории, и есть серверные гэпы
            if (candlesNew.Count > 0)
            {
                var updatedGapsInfo = QuoteCacheManager.GetGaps(candlesNew, startFillTime, endFillTime);
                GapMap.Instance.UpdateGaps(ticker, updatedGapsInfo);
                GapMap.Instance.SaveToFile();
            }
            // сохранить свечи в хранилище и в файл
            AtomCandleStorage.Instance.RewriteCandles(ticker, candlesNew);
            AtomCandleStorage.Instance.FlushInFile(quoteCacheFolder, ticker);
        }
 private static bool LoadQuotesFromServerFast(string ticker, GapList gaps, List<CandleData> candles)
 {
     // запросить данные на сервере
     var intervals = gaps.Gaps.Select(g => new Cortege2<DateTime, DateTime>(g.start, g.end)).ToList();
     IQuoteStorage storage;
     try
     {
         storage = Contract.Util.Proxy.QuoteStorage.Instance.proxy;
     }
     catch (Exception)
     {
         Logger.Error("LoadQuotesFromServerFast: Связь с сервером (IQuoteStorageFastBinding) не установлена");
         return false;
     }
     try
     {
         var packedQuoteStream = storage.GetMinuteCandlesPackedFast(ticker, intervals);
         if (packedQuoteStream == null || packedQuoteStream.count == 0)
             return true;
         var packedCandles = packedQuoteStream.GetCandles();
         candles.AddRange(packedCandles.Select(c => new CandleData(c, DalSpot.Instance.GetPrecision10(ticker))));
         return true;
     }
     catch (Exception ex)
     {
         Logger.ErrorFormat("LoadQuotesFromServerFast: Ошибка закачки котировок {0}: {1}", ticker, ex.Message);
         return false;
     }
 }