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"); }
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(), // "выходные дни должны быть зачитаны из базы ровно один раз"); }
private DateTime?GetFileLastDate(string fileName) { DateTime?startDate, endDate; bool endsNewLine; QuoteCacheManager.GetFirstAndLastFileDates(fileName, out startDate, out endDate, out endsNewLine); return(endDate); }
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(), // "выходные дни должны быть зачитаны из базы ровно один раз"); }
/// <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); }
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; }
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); }
/// <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); }
/// <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); }