/// <summary> /// Получить исторические данные /// </summary> /// <param name="consumer"> /// Потребитель исторических данных /// </param> /// <param name="instrument"> /// Инструмент /// </param> /// <param name="begin"> /// Начало диапазона /// </param> /// <param name="end"> /// Конец диапазона /// </param> /// <param name="span"> /// Интервал свечей для исторических данных /// </param> /// <param name="cancellationToken"> /// Токен отмены /// </param> /// <returns> /// Исторические данные /// </returns> /// <remarks> /// Провайдер вправе переопределить параметры исторических графиков - диапазон, интервал, /// если он не в состоянии предоставить запрошенные данные. /// </remarks> /// <exception cref="NoHistoryDataException"> /// Бросается, если исторические данные за указанный период недоступны /// </exception> public async Task GetHistoryDataAsync( IHistoryDataConsumer consumer, Instrument instrument, DateTime begin, DateTime end, HistoryProviderSpan span, CancellationToken cancellationToken = new CancellationToken()) { var symbol = await adapter.ResolveSymbolAsync(instrument); if (symbol == null) { consumer.Error($"Unable to resolve symbol for {instrument}"); return; } QLAdapter.Log.Debug().Print($"Candles request: {symbol}, span {span}, from {begin} to {end}"); var dataRequestMessage = new QLHistoryDataRequest(symbol, span); var request = new HistoryDataRequest(dataRequestMessage.id, instrument, begin, end, span); using (requestsLock.Lock()) { requests[request.Id] = request; } // Поддержка отмены запроса cancellationToken.RegisterSafe(() => request.TrySetCanceled()); adapter.SendMessage(dataRequestMessage); var data = await request.Task; QLAdapter.Log.Debug().Print("Push candles to consumer. ", LogFields.RequestId(request.Id)); consumer.Update(data, HistoryDataUpdateType.Batch); }
/// <summary> /// Получить исторические данные /// </summary> /// <param name="consumer"> /// Потребитель исторических данных /// </param> /// <param name="instrument"> /// Инструмент /// </param> /// <param name="begin"> /// Начало диапазона /// </param> /// <param name="end"> /// Конец диапазона /// </param> /// <param name="span"> /// Интервал свечей для исторических данных /// </param> /// <param name="cancellationToken"> /// Токен отмены /// </param> /// <returns> /// Исторические данные /// </returns> /// <remarks> /// Провайдер вправе переопределить параметры исторических графиков - диапазон, интервал, /// если он не в состоянии предоставить запрошенные данные. /// </remarks> /// <exception cref="NoHistoryDataException"> /// Бросается, если исторические данные за указанный период недоступны /// </exception> public async Task GetHistoryDataAsync( IHistoryDataConsumer consumer, Instrument instrument, DateTime begin, DateTime end, HistoryProviderSpan span, CancellationToken cancellationToken = new CancellationToken()) { using (LogManager.Scope()) { var instrumentData = await instrumentConverter.ResolveInstrumentAsync(this, instrument); if (instrumentData == null) { consumer.Error($"Unable to resolve symbol for {instrument}"); return; } // Выгружаем список точек var points = await FetchHistoryDataAsync(instrumentData.Symbol, begin, end, span, cancellationToken); // Собираем результат var data = new HistoryData(instrument, begin, end, span); foreach (var p in points.OrderBy(_ => _.Point)) { data.Points.Add(p); } // Передаем результат потребителю consumer.Update(data, HistoryDataUpdateType.Batch); } }
public void ProcessUpdate(QLHistoryDataUpdate update) { var hd = new HistoryData(Instrument, update.begin, update.end, Span); foreach (var candle in update.candles) { hd.Points.Add(new HistoryDataPoint(candle.Time, candle.h, candle.l, candle.o, candle.c, 0, 0)); } consumer.Update(hd, update.update_type == "added" ? HistoryDataUpdateType.OnePointAdded : update.update_type == "updated" ? HistoryDataUpdateType.OnePointUpdated : HistoryDataUpdateType.Batch); }
/// <summary> /// Получить исторические данные /// </summary> /// <param name="consumer"> /// Потребитель исторических данных /// </param> /// <param name="instrument"> /// Инструмент /// </param> /// <param name="begin"> /// Начало диапазона /// </param> /// <param name="end"> /// Конец диапазона /// </param> /// <param name="span"> /// Интервал свечей для исторических данных /// </param> /// <param name="cancellationToken"> /// Токен отмены /// </param> /// <returns> /// Исторические данные /// </returns> /// <remarks> /// Провайдер вправе переопределить параметры исторических графиков - диапазон, интервал, /// если он не в состоянии предоставить запрошенные данные. /// </remarks> /// <exception cref="NoHistoryDataException"> /// Бросается, если исторические данные за указанный период недоступны /// </exception> public async Task GetHistoryDataAsync( IHistoryDataConsumer consumer, Instrument instrument, DateTime begin, DateTime end, HistoryProviderSpan span, CancellationToken cancellationToken = new CancellationToken()) { using (LogManager.Scope()) { // Получаем контракт по инструменту var contract = await connector.ContractContainer.GetContractAsync(instrument, cancellationToken : cancellationToken); if (contract == null) { throw new InvalidOperationException($"Can't find instrument \"{instrument}\""); } // Загружаем блок исторических данных var points = await FetchHistoryDataBlock(consumer, contract, begin, end, span, cancellationToken); // Собираем объект HistoryData var minDate = DateTime.MaxValue; var maxDate = DateTime.MinValue; var data = new HistoryData(instrument, begin, end, span); foreach (var point in points.OrderBy(_ => _.Point)) { data.Points.Add(point); if (minDate > point.Point) { minDate = point.Point; } if (maxDate < point.Point) { maxDate = point.Point; } } data.Begin = minDate; data.End = maxDate; consumer.Update(data, HistoryDataUpdateType.Batch); } }
/// <summary> /// Получить исторические данные /// </summary> /// <param name="consumer"> /// Потребитель исторических данных /// </param> /// <param name="instrument"> /// Инструмент /// </param> /// <param name="begin"> /// Начало диапазона /// </param> /// <param name="end"> /// Конец диапазона /// </param> /// <param name="span"> /// Интервал свечей для исторических данных /// </param> /// <param name="cancellationToken"> /// Токен отмены /// </param> /// <returns> /// Исторические данные /// </returns> /// <remarks> /// Провайдер вправе переопределить параметры исторических графиков - диапазон, интервал, /// если он не в состоянии предоставить запрошенные данные. /// </remarks> /// <exception cref="NoHistoryDataException"> /// Бросается, если исторические данные за указанный период недоступны /// </exception> public async Task GetHistoryDataAsync( IHistoryDataConsumer consumer, Instrument instrument, DateTime begin, DateTime end, HistoryProviderSpan span, CancellationToken cancellationToken = new CancellationToken()) { var message = await PrepareTimeBarRequestAsync(instrument, begin, end, span, TimeBarRequest.RequestType.GET); if (message == null) { throw new ArgumentException($"Unable to resolve instrument {instrument}"); } var request = new HistoryDataRequest(this, consumer, instrument, begin, end, span, message); using (requestsLock.Lock()) { requests[message.request_id] = request; } CQGCAdapter.Log.Debug().Print( "Requesting history data block", LogFields.RequestId(message.request_id), LogFields.ContractId(message.time_bar_parameters.contract_id)); adapter.SendMessage(message); // Поддержка отмены запроса cancellationToken.RegisterSafe(() => request.TrySetCanceled()); var data = await request.Task; consumer.Update(data, HistoryDataUpdateType.Batch); }
private async Task UpdateLoopAsync(DateTime begin) { using (LogManager.Scope()) { try { while (true) { // Загружаем блок исторических данных var end = DateTime.Now; // TODO handle OperationCanceledException var fetchedPoints = await gateway.FetchHistoryDataAsync(instrumentSymbol, begin, end, span, token); // Обновляем точку старта на последнюю свечу, чтобы не грузить повторно if (fetchedPoints.Any()) { begin = fetchedPoints.Select(_ => _.Point).OrderByDescending(_ => _).First(); } using (syncRoot.Lock()) { // Объединяем с набором данных int added, updated; MergePoints(fetchedPoints, out added, out updated); // Оповещаем потребителя if (added > 0 || updated > 0) { begin = data.End; if (added == 1 && updated == 0) { consumer.Update(data, HistoryDataUpdateType.OnePointAdded); } else if (added == 0 && updated == 1) { consumer.Update(data, HistoryDataUpdateType.OnePointUpdated); } else { consumer.Update(data, HistoryDataUpdateType.Batch); } } else { begin = end; } } // Ждем до следущего обновления await Task.Delay(FetchInterval, token); } } catch (OperationCanceledException) { } catch (NoHistoryDataException) { _Log.Debug().Print($"No more historical data is available", LogFields.Symbol(instrumentSymbol)); } catch (Exception e) { HandleException(e, e.Message); } } }
private async Task UpdateLoopAsync(DateTime begin) { using (LogManager.Scope()) { try { while (true) { // Ждем до следущего обновления await Task.Delay(FetchInterval); if (terminated.IsSet) { return; } // Загружаем блок исторических данных var end = DateTime.Now; // TODO handle OperationCanceledException var fetchedPoints = await adapter.FetchHistoryDataBlock(consumer, contract, begin, end, span); using (syncRoot.Lock()) { // Объединяем с набором данных int added, updated; MergePoints(fetchedPoints, out added, out updated); // Оповещаем потребителя if (added > 0 || updated > 0) { begin = historyData.End; if (added == 1 && updated == 0) { consumer.Update(historyData, HistoryDataUpdateType.OnePointAdded); } else if (added == 0 && updated == 1) { consumer.Update(historyData, HistoryDataUpdateType.OnePointUpdated); } else { consumer.Update(historyData, HistoryDataUpdateType.Batch); } } else { begin = end; } } } } //catch (OperationCanceledException) { } catch (NoHistoryDataException) { _Log.Debug().Print($"No more historical data is available", LogFields.Instrument(historyData.Instrument)); } catch (Exception e) { HandleException(e, e.Message); } } }
public void Process(TimeBarReport report, out bool shouldRemoveHandler) { shouldRemoveHandler = false; // Проверяем статус отчета var status = (TimeBarReport.StatusCode)report.status_code; switch (status) { case TimeBarReport.StatusCode.SUCCESS: case TimeBarReport.StatusCode.SUBSCRIBED: case TimeBarReport.StatusCode.UPDATE: break; case TimeBarReport.StatusCode.DISCONNECTED: case TimeBarReport.StatusCode.DROPPED: shouldRemoveHandler = true; return; default: shouldRemoveHandler = true; consumer.Error(report.text_message); return; } // Складываем точки в словарь var dataPoints = provider.PrepareDataPoints(contractId, report) .Where(p => p.High != 0 && p.Low != 0 && p.Open != 0 && p.Close != 0); // иногда нули приходят foreach (var point in dataPoints) { HistoryDataPoint p; if (!points.TryGetValue(point.Point, out p)) { points.Add(point.Point, point); newPoints++; continue; } if (p != point) { // TODO это неоптимально, в data.Points уже есть точка для этой даты, надо ее обновить points[point.Point] = point; updatedPoints++; } } // Если хотя бы одна точка изменилась - перестраиваем набор данных if (newPoints >= 0 || updatedPoints > 0) { var minDate = DateTime.MaxValue; var maxDate = DateTime.MinValue; data.Points.Clear(); foreach (var p in points.OrderBy(_ => _.Key)) { data.Points.Add(p.Value); if (p.Key > maxDate) { maxDate = p.Key; } if (p.Key < minDate) { minDate = p.Key; } } data.Begin = minDate; data.End = maxDate; } // Если еще не все данные пришли - выходим if (!report.is_report_complete) { CQGCAdapter.Log.Debug().Print( $"Got a {nameof(TimeBarReport)} but it's incomplete", LogFields.Instrument(data.Instrument), LogFields.Span(data.Span)); return; } if (newPoints > 0 || updatedPoints > 0) { if (newPoints == 1 && updatedPoints == 0) { // Пришла одна новая свечка consumer.Update(data, HistoryDataUpdateType.OnePointAdded); } else if (newPoints == 0 && updatedPoints == 1) { // Одна свечка обновилась consumer.Update(data, HistoryDataUpdateType.OnePointUpdated); } else { // Свалилась пачка новых данных consumer.Update(data, HistoryDataUpdateType.Batch); } newPoints = 0; updatedPoints = 0; } }