/// <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="consumer"> /// Потребитель исторических данных /// </param> /// <param name="instrument"> /// Инструмент /// </param> /// <param name="begin"> /// Начало диапазона /// </param> /// <param name="span"> /// Интервал свечей для исторических данных /// </param> /// <returns> /// Подписка на исторические данные /// </returns> /// <remarks> /// Провайдер вправе переопределить параметры исторических графиков - диапазон, интервал, /// если он не в состоянии предоставить запрошенные данные. /// </remarks> public async Task <IHistoryDataSubscription> SubscribeToHistoryDataAsync( IHistoryDataConsumer consumer, Instrument instrument, DateTime begin, HistoryProviderSpan span) { using (LogManager.Scope()) { var message = await PrepareTimeBarRequestAsync(instrument, begin, DateTime.Now, span, TimeBarRequest.RequestType.SUBSCRIBE); if (message == null) { throw new ArgumentException($"Unable to resolve instrument {instrument}"); } message.time_bar_parameters.to_utc_time = 0; var request = new HistoryDataSubscription( this, new HistoryData(instrument, begin, DateTime.Now, span), consumer, message.time_bar_parameters.contract_id, message.request_id); using (requestsLock.Lock()) { requests[message.request_id] = request; } CQGCAdapter.Log.Debug().Print( "Requesting history data stream", LogFields.RequestId(message.request_id), LogFields.ContractId(message.time_bar_parameters.contract_id)); adapter.SendMessage(message); return(request); } }
/// <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 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)); }
/// <summary> /// Обработка заявки как новой /// </summary> private Message HandleOrderStateAsNewOrder(OrderStatus message, out Order order) { Logger.Debug().Print( "New order received", LogFields.ExchangeOrderId(message.order_id), LogFields.ChainOrderId(message.chain_order_id), LogFields.State(ConvertionHelper.GetOrderState(message)), LogFields.AccountId(message.account_id), LogFields.ExecOrderId(message.exec_order_id) ); // Ищем счет для заявки string accountCode; using (accountsLock.ReadLock()) { if (!accountCodesById.TryGetValue(message.account_id, out accountCode)) { Logger.Error().Print( "Unable to process order: account is unknown", LogFields.ExchangeOrderId(message.order_id), LogFields.AccountId(message.account_id) ); order = null; return(null); } } // Ищем инструмент для заявки var instrument = instrumentResolver.GetInstrument(message.order.contract_id); if (instrument == null) { // Пришла заявка по неизвестному инструменту Logger.Error().Print( "Received an order but no matching instrument is found. Put to pending orders", LogFields.ExchangeOrderId(message.order_id), LogFields.ContractId(message.order.contract_id), LogFields.Account(accountCode) ); using (ordersLock.Lock()) { pendingOrders.Enqueue(message); pendingOrderAddedEvent.Set(); } order = null; return(null); } using (ordersLock.Lock()) { if (ordersByOrderExchangeId.TryGetValue(message.chain_order_id, out order)) { // Race condition, такая заявка уже есть, надобно обработать ее через OSCM return(HandleOrderStateAsOrderStateChange(order, message)); } Guid transactionId; if (Guid.TryParse(message.order.cl_order_id, out transactionId)) { if (ordersByTransactionId.TryGetValue(transactionId, out order)) { // Такая заявка уже есть, надобно обработать ее через OSCM order.OrderExchangeId = message.chain_order_id; ordersByOrderExchangeId.Add(message.chain_order_id, order); return(HandleOrderStateAsOrderStateChange(order, message)); } } else { transactionId = Guid.Empty; } // Создаем новую внешнюю заявку order = new Order { OrderExchangeId = message.chain_order_id, Instrument = instrument, Type = ConvertionHelper.GetOrderType(message), Account = accountCode, Price = instrumentResolver.ConvertPriceBack(message.order.contract_id, message.order.limit_price), Quantity = message.order.qty, ActiveQuantity = message.remaining_qty, Operation = message.order.side == (uint)WebAPI.Order.Side.BUY ? OrderOperation.Buy : OrderOperation.Sell, State = ConvertionHelper.GetOrderState(message), DateTime = adapter.ResolveDateTime(message.status_utc_time), Comment = ConvertionHelper.GetComment(message), TransactionId = transactionId }; ordersByOrderExchangeId.Add(message.chain_order_id, order); orderStatusByChainOrderId[message.chain_order_id] = message; return(new ExternalOrderMessage { Order = order }); } }