private void MarketDataResolved(AdapterEventArgs <InformationReport> args) { var contractId = args.Message.symbol_resolution_report.contract_metadata.contract_id; ResolutionRequest request; using (resolutionRequestsLock.Lock()) { if (!resolutionRequestsById.TryGetValue(args.Message.id, out request)) { return; } using (cacheLock.Lock()) { cachedContractIds[request.Instrument] = contractId; cachedContracts[contractId] = request.Instrument; cachedContractPriceScales[contractId] = (decimal)args.Message.symbol_resolution_report.contract_metadata.correct_price_scale; } resolutionRequestsById.Remove(request.Id); resolutionRequestsByInstrument.Remove(request.Instrument); } request.Resolve(contractId); args.MarkHandled(); OnInstrumentResolved(contractId); _Log.Debug().Print("Instrument is resolved", LogFields.Instrument(request.Instrument), LogFields.ContractId(contractId), LogFields.RequestId(request.Id)); }
private async Task <Contract> ResolveContractStubAsync(Instrument instrument) { var instrumentData = await instrumentConverter.ResolveInstrumentAsync(adapter, instrument); if (instrumentData == null) { _Log.Error().Print("Unable to resolve vendor symbol for an instrument", LogFields.Instrument(instrument)); return(null); } var contract = GetContractStub(instrumentData); return(contract); }
/// <summary> /// Обновить контракт по его метаданным /// </summary> /// <param name="metadata"> /// Метаданные контракта /// </param> /// <param name="dependentObjectDescription"> /// Строковое описание объекта, который зависит от инструмента /// </param> public async Task HandleMetadataAsync(ContractMetadata metadata, string dependentObjectDescription = null) { using (LogManager.Scope()) { _Log.Debug().Print( "Contract metadata received", LogFields.ContractId(metadata.contract_id), LogFields.Symbol(metadata.contract_symbol), LogFields.CorrectPriceScale(metadata.correct_price_scale), LogFields.DisplayPriceScale(metadata.display_price_scale) ); using (cacheLock.Lock()) { if (cachedContracts.ContainsKey(metadata.contract_id)) { _Log.Debug().Print("Already cached", LogFields.ContractId(metadata.contract_id)); return; } } Instrument instrument = await InstrumentConverter.ResolveSymbolAsync(adapter, metadata.contract_symbol, dependentObjectDescription); if (instrument == null) { _Log.Warn().Print("Instrument not resolved", LogFields.Symbol(metadata.contract_symbol)); return; } using (cacheLock.Lock()) { _Log.Debug().Print( "Caching instrument", LogFields.ContractId(metadata.contract_id), LogFields.Symbol(metadata.contract_symbol), LogFields.Instrument(instrument) ); cachedContractIds[instrument] = metadata.contract_id; cachedContracts[metadata.contract_id] = instrument; cachedContractPriceScales[metadata.contract_id] = (decimal)metadata.correct_price_scale; } OnInstrumentResolved(metadata.contract_id); } }
/// <summary> /// Преобразовать цену из формата CQG /// </summary> /// <param name="contractId"> /// Contract ID /// </param> /// <param name="price"> /// Цена в нотации CQG /// </param> /// <returns> /// Цена /// </returns> public decimal ConvertPriceBack(uint contractId, long price) { using (cacheLock.Lock()) { decimal scale; if (!cachedContractPriceScales.TryGetValue(contractId, out scale)) { _Log.Error().Print( "Can't find price scale for contract", LogFields.ContractId(contractId), LogFields.Instrument(GetInstrument(contractId)?.Code) ); return(price); } var correctedPrice = scale != 0 ? price * scale : price; return(correctedPrice); } }
/// <summary> /// Обновить контракт по его метаданным /// </summary> /// <param name="metadata"> /// Метаданные контракта /// </param> public void HandleMetadata(ContractMetadata metadata) { _Log.Debug().Print( "Contract metadata received", LogFields.ContractId(metadata.contract_id), LogFields.Symbol(metadata.contract_symbol), LogFields.CorrectPriceScale(metadata.correct_price_scale), LogFields.DisplayPriceScale(metadata.display_price_scale) ); using (cacheLock.Lock()) { if (cachedContracts.ContainsKey(metadata.contract_id)) { _Log.Debug().Print("Already cached", LogFields.ContractId(metadata.contract_id)); return; } } var instrument = InstrumentConverter.ResolveSymbolAsync(adapter, metadata.contract_symbol).Result; if (instrument == null) { _Log.Warn().Print("Instrument not resolved", LogFields.Symbol(metadata.contract_symbol)); return; } using (cacheLock.Lock()) { _Log.Debug().Print( "Caching instrument", LogFields.ContractId(metadata.contract_id), LogFields.Symbol(metadata.contract_symbol), LogFields.Instrument(instrument) ); cachedContractIds[instrument] = metadata.contract_id; cachedContracts[metadata.contract_id] = instrument; cachedContractPriceScales[metadata.contract_id] = (decimal)metadata.correct_price_scale; } OnInstrumentResolved(metadata.contract_id); }
/// <summary> /// Обработка ответа квика на запрос исторических данных /// </summary> /// <param name="response"></param> public void ProcessResponse(QLHistoryDataResponse response) { var points = response.candles .Select(_ => new HistoryDataPoint(_.Time, _.h, _.l, _.o, _.c, 0, 0)) .ToList(); foreach (var point in points.Where(_ => _.Point >= data.Begin && _.Point <= data.End)) { data.Points.Add(point); } QLAdapter.Log.Debug().Print($"{data.Points.Count} candles selected from QLHistoryDataResponse and will be pushed to consumer"); if (data.Points.Count == 0) { QLAdapter.Log.Debug().Print("Historical data depth limit has been reached", LogFields.Instrument(data.Instrument), LogFields.Span(data.Span)); TrySetException(new NoHistoryDataException("Historical data depth limit has been reached")); return; } TrySetResult(data); }
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); } } }
/// <summary> /// Запросить инструмент по контракту (асинхронно) /// </summary> /// <param name="contract"> /// Контракт /// </param> /// <param name="objectDescription"> /// Описание объекта, зависящего от контракта /// </param> /// <param name="continuation"> /// Асинхронный коллбек. В случае неудачи резолва инструмента не вызывается. /// </param> public async void GetInstrumentAsync(Contract contract, string objectDescription, Action <Instrument> continuation) { try { string code; Instrument instrument = null; var tryNumber = 0; // Если поле Exchange не указано в contract, тогда нужно сделать дополнительный запрос if (string.IsNullOrEmpty(contract.Exchange)) { // Используем временный инструмент с заведомо уникальным кодом contract = await RequestContract(new Instrument($"TEMPORARY_INSTRUMENT_{Guid.NewGuid():N}"), contract); } // Попытка 1 - сначала пытаемся найти по коду с доп.полями и с указанием родной биржи // Поиск будет идти только по локальному кэшу конвертера if (!string.IsNullOrEmpty(contract.Exchange)) { code = $"exchange:{contract.Exchange};{contract.LocalSymbol}"; instrument = await instrumentConverter.ResolveSymbolAsync(adapter, code, objectDescription); tryNumber++; if (instrument != null) { continuation(instrument); _Log.Debug().Print( $"Instrument {contract.LocalSymbol} was resolved by InstrumentConverter as {instrument}.", LogFields.Code(code), LogFields.Symbol(contract.LocalSymbol), LogFields.Exchange(contract.Exchange), LogFields.Instrument(instrument), LogFields.Attempt(tryNumber) ); return; } } // Попытка 2 - сначала пытаемся найти по коду с доп.полями и с указанием квазибиржи SMART или GLOBEX // поиск будет идти только по локальному кэшу конвертера var exchangeCode = contract.SecType == "FUT" || contract.SecType == "FOP" ? GLOBEX : SMART; code = $"exchange:{exchangeCode};{contract.LocalSymbol}"; instrument = await instrumentConverter.ResolveSymbolAsync(adapter, code, objectDescription); tryNumber++; if (instrument != null) { continuation(instrument); _Log.Debug().Print( $"Instrument {contract.LocalSymbol} was resolved by InstrumentConverter as {instrument}.", LogFields.Code(code), LogFields.Symbol(contract.LocalSymbol), LogFields.Exchange(contract.Exchange), LogFields.Instrument(instrument), LogFields.Attempt(tryNumber) ); return; } // Попытка 3 - ищем просто по коду code = contract.LocalSymbol; instrument = await instrumentConverter.ResolveSymbolAsync(adapter, code, objectDescription); tryNumber++; if (instrument != null) { continuation(instrument); _Log.Debug().Print( $"Instrument {contract.LocalSymbol} was resolved by InstrumentConverter as {instrument}.", LogFields.Code(code), LogFields.Symbol(contract.LocalSymbol), LogFields.Exchange(contract.Exchange), LogFields.Instrument(instrument), LogFields.Attempt(tryNumber) ); return; } _Log.Error().Print( $"Instrument {contract.LocalSymbol} wasn't resolved by InstrumentConverter.", LogFields.Code(code), LogFields.Symbol(contract.LocalSymbol), LogFields.Exchange(contract.Exchange), LogFields.Attempt(tryNumber) ); } catch (Exception e) { _Log.Error().Print(e, $"Failed to get instrument from {contract}"); } }
public void Process(TimeBarReport report, out bool shouldRemoveHandler) { shouldRemoveHandler = true; // Проверяем статус отчета var status = (TimeBarReport.StatusCode)report.status_code; switch (status) { case TimeBarReport.StatusCode.SUCCESS: break; case TimeBarReport.StatusCode.DISCONNECTED: TrySetCanceled(); return; case TimeBarReport.StatusCode.OUTSIDE_ALLOWED_RANGE: // Мы зашли слишком глубоко в прошлое CQGCAdapter.Log.Debug().Print( "Historical data depth limit has been reached", LogFields.Instrument(data.Instrument), LogFields.Span(data.Span)); TrySetException(new NoHistoryDataException(nameof(TimeBarReport.StatusCode.OUTSIDE_ALLOWED_RANGE))); return; default: TrySetException(new Exception($"Request ended with {status:G} status code. {report.text_message}")); return; } // Складываем точки данных в пакет var dataPoints = provider.PrepareDataPoints(message.time_bar_parameters.contract_id, 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); continue; } if (p != point) { points[point.Point] = point; } } // Если отчет не полон, то ожидаем прихода следующей пачки данных 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)); shouldRemoveHandler = false; consumer.Warning("This operation might take some time..."); return; } // Если все данные пришли, то собираем ответ и выставляем его потребителю 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; TrySetResult(data); }
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; } }