예제 #1
0
        public void TestGetGaps()
        {
            var candles   = AtomCandleStorage.Instance.GetAllMinuteCandles(Ticker);
            var startTime = timeHistStart.AddMinutes(700);
            var endTime   = timeHistStart.AddMinutes(26000);
            var gaps      = QuoteCacheManager.GetGaps(candles, startTime, endTime);

            Assert.NotNull(gaps, "TestGetGaps: gaps == null");
            Assert.AreEqual(startTime, gaps[0].start, "TestGetGaps: head gap not correct");
            Assert.AreEqual(endTime, gaps[gaps.Count - 1].end, "TestGetGaps: tail gap not correct");

            // частные случаи
            // все свечки правее интервала запроса
            startTime = new DateTime(2013, 3, 29);
            endTime   = startTime.AddMinutes(50);
            gaps      = QuoteCacheManager.GetGaps(candles, startTime, endTime);
            Assert.AreEqual(1, gaps.Count, "TestGetGaps(2): gaps count != 1");
            Assert.AreEqual(startTime, gaps[0].start, "TestGetGaps(2): head gap not correct");
            Assert.AreEqual(endTime, gaps[gaps.Count - 1].end, "TestGetGaps(2): tail gap not correct");

            // все свечки левее интервала запроса
            startTime = new DateTime(2013, 10, 1);
            endTime   = startTime.AddMinutes(50);
            gaps      = QuoteCacheManager.GetGaps(candles, startTime, endTime);
            Assert.AreEqual(1, gaps.Count, "TestGetGaps(3): gaps count != 1");
            Assert.AreEqual(startTime, gaps[0].start, "TestGetGaps(3): head gap not correct");
            Assert.AreEqual(endTime, gaps[gaps.Count - 1].end, "TestGetGap(3)s: tail gap not correct");
        }
예제 #2
0
        public void WeekendIsNotGap()
        {
            // Выходные дни: май 2013, 4,5    9   11,12
            // выходной
            var gap = new DateSpan(new DateTime(2013, 5, 4, 3, 51, 12),
                                   new DateTime(2013, 5, 5, 1, 19, 0));

            var miniHoles = DaysOff.Instance.GetIntersected(gap);
            var gaps      = QuoteCacheManager.SubtractPeriods(gap, miniHoles);

            Assert.AreEqual(gaps.Count, 0);

            // праздник
            gap = new DateSpan(new DateTime(2013, 5, 9, 3, 21, 0),
                               new DateTime(2013, 5, 9, 11, 19, 0));
            miniHoles = DaysOff.Instance.GetIntersected(gap);
            gaps      = QuoteCacheManager.SubtractPeriods(gap, miniHoles);
            Assert.AreEqual(gaps.Count, 0);

            // гэп присутствует
            gap = new DateSpan(new DateTime(2013, 5, 5, 16, 30, 0),
                               new DateTime(2013, 5, 6, 8, 30, 0));
            miniHoles = DaysOff.Instance.GetIntersected(gap);
            Assert.AreNotEqual(miniHoles.Count, 0, "DaysOff.Instance.GetIntersected");
            gaps = QuoteCacheManager.SubtractPeriods(gap, miniHoles);

            Assert.AreNotEqual(gaps.Count, 0, "gaps");
            // закомментировал, т.к. пересекается с другими тестами
            //moq.Verify(lw => lw.GetMetadataByCategory(It.Is<string>(s => s == "DayOff")), Times.Once(),
            //    "выходные дни должны быть зачитаны из базы ровно один раз");
        }
예제 #3
0
        private DateTime?GetFileLastDate(string fileName)
        {
            DateTime?startDate, endDate;
            bool     endsNewLine;

            QuoteCacheManager.GetFirstAndLastFileDates(fileName, out startDate, out endDate, out endsNewLine);
            return(endDate);
        }
예제 #4
0
        public void SmallGapIsStillAGap()
        {
            var gapInterval = new DateSpan(new DateTime(2013, 5, 31, 17, 49, 0),
                                           new DateTime(2013, 5, 31, 17, 51, 16));
            var miniHoles = DaysOff.Instance.GetIntersected(gapInterval);
            var gaps      = QuoteCacheManager.SubtractPeriods(gapInterval, miniHoles);

            Assert.AreNotEqual(gaps.Count, 0, "после склейки таки должны оставаться гэпы (SmallGapIsStillAGap)");
            // закомментировал, т.к. пересекается с другими тестами
            //moq.Verify(lw => lw.GetMetadataByCategory(It.Is<string>(s => s == "DayOff")), Times.Once(),
            //    "выходные дни должны быть зачитаны из базы ровно один раз");
        }
예제 #5
0
        /// <summary>
        /// сообщить о возникшем гэпе - предложить закачать котировки или же
        /// закачать их автоматом
        /// </summary>
        private void ReportOnGapFound(DateTime startOfGap)
        {
            // определить, не пришелся ли "гэп" на выходные
            var gapInterval = new DateSpan(startOfGap, DateTime.Now);
            var miniHoles   = DaysOff.Instance.GetIntersected(gapInterval);
            var gaps        = QuoteCacheManager.SubtractPeriods(gapInterval, miniHoles);

            if (gaps == null || gaps.Count == 0)
            {
                return;
            }
            var sumMinutes = gaps.Sum(g => (g.end - g.start).TotalMinutes);

            if (sumMinutes < MinutesOfGapInQuoteStream)
            {
                return;
            }

            // вывести уведомление
            var msg = gaps.Count == 1
                          ? string.Format("заполнить гэп ({0} минут)", (int)sumMinutes)
                          : string.Format("заполнить гэпы ({0} минут суммарно)", (int)sumMinutes);

            AddUrlToStatusPanelSafe(DateTime.Now, msg, LinkTargetFillGaps);
            var action = UserSettings.Instance.GetAccountEventAction(AccountEventCode.GapFound);

            if (action == AccountEventAction.StatusPanelOnly || action == AccountEventAction.DoNothing)
            {
                return;
            }

            // показать желтое окошко
            var repeatNotification = false;
            var shouldFill         = !UserSettings.Instance.ConfirmGapFilling ||
                                     (NotificationBox.Show(msg + Environment.NewLine + "Заполнить сейчас?", "Обнаружен гэп",
                                                           MessageBoxButtons.YesNo, out repeatNotification) == DialogResult.Yes);

            if (UserSettings.Instance.ConfirmGapFilling != repeatNotification)
            {
                UserSettings.Instance.ConfirmGapFilling = repeatNotification;
                UserSettings.Instance.SaveSettings();
            }
            if (!shouldFill)
            {
                return;
            }
            Invoke(new Action <string>(FillGapAfterReport), LinkTargetFillGaps);
        }
예제 #6
0
        private void EnsureStartDateSet()
        {
            var ticker = (string)cbTicker.SelectedItem;

            // узнать, сколько дней хранится в AtomCandleStorage - либо в файле
            DateTime?timeStart = null;
            var      range     = AtomCandleStorage.Instance.GetDataRange(ticker);

            if (range.HasValue)
            {
                timeStart = range.Value.a;
            }
            if (!timeStart.HasValue)
            {
                var path = ExecutablePath.ExecPath + TerminalEnvironment.QuoteCacheFolder +
                           "\\" + ticker + ".quote";
                timeStart = QuoteCacheManager.GetFirstDate(path);
            }

            // подставить дату
            var daysInRequest = ChartForm.DaysToQuotesRequest;

            if (timeStart.HasValue)
            {
                daysInRequest = (int)(DateTime.Now - timeStart.Value).TotalDays;
            }
            if (daysInRequest < ChartForm.MinDaysToQuotesRequest)
            {
                daysInRequest = ChartForm.MinDaysToQuotesRequest;
            }

            // инициализировать поле-счетчик дней
            tbDaysInRequest.Minimum   = ChartForm.MinDaysToQuotesRequest;
            tbDaysInRequest.Maximum   = Math.Max(daysInRequest, ChartForm.MaxDaysInQuotesRequest);
            tbDaysInRequest.Value     = daysInRequest;
            tbDaysInRequest.ForeColor = Color.Black;

            daysInHistoryDefault = daysInRequest;
        }
예제 #7
0
        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);
        }
예제 #8
0
        /// <summary>
        /// получение карты потенциальных гэпов;
        /// она составляются по отсутвующим данным в котировке ticker-а,
        /// но учитывает карту гэпов сервера, составленную по предыдущим запросам
        /// </summary>
        /// <param name="ticker"></param>
        /// <param name="timeStart"></param>
        /// <param name="quoteCacheFolder"></param>
        /// <param name="worker"></param>
        /// <param name="nowTime"></param>
        /// <returns></returns>
        public static List <GapInfo> VerifyTickerHistory(string ticker, DateTime timeStart,
                                                         string quoteCacheFolder,
                                                         BackgroundWorker worker,
                                                         DateTime nowTime = default(DateTime))
        {
            if (worker != null && worker.CancellationPending)
            {
                return(new List <GapInfo>());
            }
            if (nowTime == default(DateTime))
            {
                nowTime = DateTime.Now;
            }

            var histStart = GapMap.Instance.GetServerTickerHistoryStart(ticker);

            if (timeStart < histStart)
            {
                timeStart = histStart;
            }

            // получить список гэпов, предположительно, имеющихся на сервере
            var serverGaps = GapMap.Instance.GetServerGaps(ticker);

            // получить имеющуюся историю
            var candles = AtomCandleStorage.Instance.GetAllMinuteCandles(ticker);

            if (candles == null || candles.Count == 0)
            {
                var loadedHist = AtomCandleStorage.Instance.LoadFromFile(quoteCacheFolder, ticker);
                if (loadedHist.HasValue)
                {
                    candles = AtomCandleStorage.Instance.GetAllMinuteCandles(ticker);
                }
            }
            candles = candles ?? new List <CandleData>();

            if (worker != null && worker.CancellationPending)
            {
                return(new List <GapInfo>());
            }

            // определить гэпы
            var gaps = candles.Count > 0
                           ? QuoteCacheManager.GetGaps(candles, timeStart, nowTime)
                           : new List <DateSpan> {
                new DateSpan(timeStart, nowTime)
            };

            // исключить фрагменты, отсутствующие на сервере
            if (serverGaps != null && serverGaps.IsActual)
            {
                GapMap.ExcludeGapsThatLackedOnServer(ref gaps, serverGaps.serverGaps);
            }
            else
            {
                // получить время первой записи в БД и удалить / порезать все гэпы, начинающиеся
                // позже, чем первая запись базы
                var gapsTrimmed = new List <DateSpan>();

                foreach (var gap in gaps)
                {
                    if (gap.end <= histStart)
                    {
                        continue;
                    }
                    if (gap.start < histStart)
                    {
                        gapsTrimmed.Add(new DateSpan(histStart, gap.end));
                    }
                    else
                    {
                        gapsTrimmed.Add(gap);
                    }
                }

                gaps = gapsTrimmed;
            }

            var gapInfos = gaps.Select(g => new GapInfo {
                start = g.start, end = g.end
            }).ToList();

            // склеиваем рядом стоящие гэпы
            GapInfo.StickSmallGaps(ref gapInfos, 30);

            if (worker != null && worker.CancellationPending)
            {
                return(new List <GapInfo>());
            }

            return(gapInfos);
        }
예제 #9
0
        /// <summary>
        /// устранение гэпов
        /// </summary>
        /// <param name="ticker"></param>
        /// <param name="startTime"></param>
        /// <param name="gaps"></param>
        /// <param name="quoteCacheFolder"></param>
        /// <param name="worker"></param>
        /// <param name="gapsUpdatedAction"></param>
        public static void FillGapsByTicker(string ticker,
                                            DateTime startTime,
                                            List <GapInfo> gaps,
                                            string quoteCacheFolder,
                                            BackgroundWorker worker,
                                            Action <string, List <GapInfo> > gapsUpdatedAction)
        {
            // закачать с сервера
            // параллельно обновляя контрол
            var candlesOld = AtomCandleStorage.Instance.GetAllMinuteCandles(ticker) ?? new List <CandleData>();
            var candlesNew = new List <CandleData>();
            var oldIndex   = 0;

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

            for (var i = 0; i < gaps.Count; i++)
            {
                var gap = gaps[i];
                // добавить имеющиеся свечи
                for (; oldIndex < candlesOld.Count; oldIndex++)
                {
                    if (candlesOld[oldIndex].timeOpen > gap.start)
                    {
                        break;
                    }
                    candlesNew.Add(candlesOld[oldIndex]);
                }

                // подкачать свечи с сервера
                var indexToInsert = i;
                var wasInserted   = false;

                var status = LoadQuotesFromServer(ticker, gap.start, gap.end.AddMinutes(1),
                                                  candlesNew,
                                                  (time, gapStatus) =>
                {
                    // добавить или обновить временный гэп
                    var passedGap = new GapInfo {
                        start = gap.start, end = time, status = gapStatus
                    };
                    if (!wasInserted)
                    {
                        gaps.Insert(indexToInsert, passedGap);
                        wasInserted = true;
                    }
                    else
                    {
                        gaps[indexToInsert] = passedGap;
                    }
                    gaps[indexToInsert + 1] = new GapInfo
                    {
                        start  = time.AddMinutes(1),
                        end    = gaps[indexToInsert + 1].end,
                        status = gaps[indexToInsert + 1].status
                    };
                    // перерисовать контрол
                    gapsUpdatedAction(ticker, gaps);
                },
                                                  worker);
                if (wasInserted)
                {
                    gaps.RemoveAt(indexToInsert);
                }

                if (status == GapInfo.GapStatus.FailedToFill)
                {
                    // в старом списке могли быть какие-то котировки на интервале гэпа - добавить их
                    for (; oldIndex < candlesOld.Count; oldIndex++)
                    {
                        if (candlesOld[oldIndex].timeOpen > gap.end)
                        {
                            break;
                        }
                        candlesNew.Add(candlesOld[oldIndex]);
                    }
                }

                // перекрасить гэп другим цветом
                gaps[i] = new GapInfo
                {
                    start  = gap.start,
                    end    = gap.end,
                    status = status
                };
                if (worker != null && worker.CancellationPending)
                {
                    return;
                }
                if (currentTickerCancelled)
                {
                    currentTickerCancelled = false;
                    return;
                }

                gapsUpdatedAction(ticker, gaps);

                // сместиться вперед в списке старых свеч
                for (; oldIndex < candlesOld.Count; oldIndex++)
                {
                    if (candlesOld[oldIndex].timeOpen > gap.end)
                    {
                        break;
                    }
                }
            }

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

            if (candlesNew.Count == 0)
            {
                return;
            }

            // добавить "хвостик" - недостающие свечки
            var lastTime = candlesNew[candlesNew.Count - 1].timeOpen;

            for (; oldIndex < candlesOld.Count; oldIndex++)
            {
                if (candlesOld[oldIndex].timeOpen <= lastTime)
                {
                    continue;
                }
                candlesNew.Add(candlesOld[oldIndex]);
            }

            if (candlesNew.Count <= candlesOld.Count)
            {
                return;
            }

            // убрать дублирующиеся свечки
            for (var i = 1; i < candlesNew.Count; i++)
            {
                if (candlesNew[i].timeOpen <= candlesNew[i - 1].timeOpen)
                {
                    candlesNew.RemoveAt(i);
                    i--;
                }
            }

            if (worker != null && worker.CancellationPending)
            {
                return;
            }
            // дырки, оставшиеся в истории, и есть серверные гэпы
            if (candlesNew.Count > 0)
            {
                var updatedGapsInfo = QuoteCacheManager.GetGaps(candlesNew, startTime, DateTime.Now);
                GapMap.Instance.UpdateGaps(ticker, updatedGapsInfo);
                GapMap.Instance.SaveToFile();
            }
            if (worker != null && worker.CancellationPending)
            {
                return;
            }

            // сохранить свечи в хранилище и в файл
            AtomCandleStorage.Instance.RewriteCandles(ticker, candlesNew);
            AtomCandleStorage.Instance.FlushInFile(quoteCacheFolder, ticker);
        }