コード例 #1
0
        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));
        }
コード例 #2
0
        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);
        }
コード例 #3
0
        /// <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);
            }
        }
コード例 #4
0
        /// <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);
            }
        }
コード例 #5
0
        /// <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);
        }
コード例 #6
0
        /// <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);
        }
コード例 #7
0
        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);
                }
            }
        }
コード例 #8
0
        /// <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}");
            }
        }
コード例 #9
0
        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);
        }
コード例 #10
0
        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;
            }
        }