public void Store(int tickerId, HistoryTaskCompletionSource tcs) { using (historyPointsLock.Lock()) { tasks[tickerId] = tcs; } }
/// <summary> /// Выставить заявку /// </summary> /// <param name="transaction"> /// Транзакция /// </param> /// <param name="order"> /// IB-заявка /// </param> public async Task PlaceOrderAsync(NewOrderTransaction transaction, IBOrder order) { var instrument = transaction.Instrument; var contract = await connector.ContractContainer.GetContractAsync(instrument, null); if (contract == null) { throw new TransactionRejectedException($"Details of contract {instrument.Code} are not available"); } using (orderInfoContainerLock.Lock()) { var tickerId = NextTickerId(); order.OrderId = tickerId; // Сохраняем заявку по ее тикеру var orderInfo = new OrderInfo(order, transaction.Instrument) { State = OrderState.New, NewOrderTransactionId = transaction.TransactionId }; orderInfoContainer.StoreByTickerId(tickerId, orderInfo, true); Socket.placeOrder(tickerId, contract, order); } }
/// <summary> /// Останов адаптера /// </summary> public void Stop() { try { using (socketLock.Lock()) { if (socket == null || stopped) { return; } } stopped = true; Log.Info().Print("Stopping CQGCAdapter"); SendLogoff(); cancellationTokenSource.Cancel(); Log.Info().Print("Waiting for logoff reply"); WaitHandle.WaitAny(new WaitHandle[] { socketClosedEvent, logoffEfent }); } catch (CQGCAdapterConnectionException e) { // Адаптер не был подключен, logoff не нужен Log.Warn().Print(e, "No active connection on stop"); } }
public void Store(int tickerId, PendingTestResult testResult) { using (syncRoot.Lock()) { testResultByTicker[tickerId] = testResult; } }
internal async Task <IList <HistoryDataPoint> > FetchHistoryDataAsync( string instrumentSymbol, DateTime begin, DateTime end, HistoryProviderSpan span, CancellationToken cancellationToken) { // Создаем операцию для ожидания var operation = new HistoryRequest(span, begin, end); var requestId = HistorySocketWrapper.HistoryRequestIdPefix + Guid.NewGuid().ToString("N"); using (historyRequestsLock.Lock()) { historyRequests[requestId] = operation; } // Подписываемся на исторические данные historySocket.RequestHistoryData(instrumentSymbol, begin, end, span, requestId); // Разрешаем отмену операции cancellationToken.RegisterSafe(() => operation.TrySetCanceled()); // Дожидаемся результатов var points = await operation.Task; return(points); }
/// <summary> /// Проверить подписку по вендорскому коду /// </summary> public async Task <Tuple <bool, string> > TrySubscribeAsync(string code, SecurityType type) { using (LogManager.Scope()) { await securityTypeIndexCompleted.Task; int typeId; if (!securityTypeIndex.TryGetValue(type, out typeId)) { var message = LogMessage.Format($"Failed to find an identifier for {type}").ToString(); Logger.Warn().Print(message); return(Tuple.Create(false, message)); } var operation = new SubscriptionTest(code, type); var requestId = LookupSocketWrapper.RequestIdPrefix + Guid.NewGuid().ToString("N"); using (subscriptionTestsLock.Lock()) { subscriptionTests[requestId] = operation; } lookupSocket.RequestSymbolLookup(code, typeId, requestId); var result = await operation.Task; return(Tuple.Create(result, operation.Message)); } }
/// <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); }
private async Task SendResolutionRequestAsync(ResolutionRequest request) { var instrument = request.Instrument; var data = await InstrumentConverter.ResolveInstrumentAsync(adapter, instrument); // если символ не зарезолвился, выходим if (data == null) { request.Resolve(uint.MaxValue); return; } var message = new InformationRequest { symbol_resolution_request = new SymbolResolutionRequest { symbol = data.Symbol }, id = request.Id }; request.OnSent(); using (requestBatchLock.Lock()) { requestBatch.Add(message); } _Log.Debug().PrintFormat( "Sending a symbol resolution request {0} for instrument {1} mapped to symbol {2}", request.Id, instrument, data.Symbol ); }
/// <summary> /// Запомнить транзакцию /// </summary> private void StoreTransaction(uint requestId, Transaction transaction) { using (transactionIdsByRequestIdLock.Lock()) { transactionIdsByRequestId[requestId] = transaction.TransactionId; transactionRequestIds[transaction.TransactionId] = requestId; } }
public void Store(int tickerId, Instrument instrument) { using (syncRoot.Lock()) { instrumentByTicker[tickerId] = instrument; tickerByInstrument[instrument] = tickerId; } }
private async Task HandleAsync(QLInstrumentParams message) { var instrument = await adapter.ResolveInstrumentAsync(message.code); if (instrument == null) { Logger.Error().Print($"Unable to resolve instrument for {message.code}"); return; } decimal pricemin = 0, pricemax = 0; decimal.TryParse(message.pricemin, out pricemin); decimal.TryParse(message.pricemax, out pricemax); // цены шага var stepPrices = new[] { message.stepPriceT, message.stepPrice, message.stepPriceCl, message.stepPricePrCl }; // первая ненулевая цена шага var stepPriceValue = stepPrices.FirstOrDefault(x => x != 0); // проверить утверждение: любая цена шага равна либо stepPriceValue, либо 0 //Debug.Assert(stepPrices.All(x => x == stepPriceValue || x == 0)); // NOTE выяснилось, что утверждение иногда не выполняется. Замечен случай с BRM6 stepPricePrCl=stepPrice=stepPriceT=6.61737, stepPriceCl=6.63015 var ip = new InstrumentParams { Instrument = instrument, BestBidPrice = message.bid, LastPrice = message.last, LotSize = message.lotsize, BestOfferPrice = message.offer, Vola = message.volatility, BestBidQuantity = message.bidQuantity, BestOfferQuantity = message.offerQuantity, PriceStep = message.priceStep, PriceStepValue = stepPriceValue, Settlement = message.settlement, PreviousSettlement = message.previousSettlement, BottomPriceLimit = pricemin, TopPriceLimit = pricemax, VolaTranslatedByFeed = true, SessionEndTime = message.endTime, OpenInterest = message.openinterest }; OnMessageReceived(ip); using (fullCodesLock.Lock()) { fullCodes[ip.Instrument] = message.fullCode; } }
/// <summary> /// Обработчик события по статусу заявки /// </summary> private async void OrderStatusReceived(AdapterEventArgs <OrderStatus> args) { using (LogManager.Scope()) { try { Logger.Debug().Print( "Order status received", LogFields.ExchangeOrderId(args.Message.order_id), LogFields.ChainOrderId(args.Message.chain_order_id), LogFields.State(ConvertionHelper.GetOrderState(args.Message)), LogFields.AccountId(args.Message.account_id), LogFields.ExecOrderId(args.Message.exec_order_id) ); args.MarkHandled(); // Обрабатываем метаданные контрактов if (args.Message.contract_metadata != null) { foreach (var metadata in args.Message.contract_metadata) { await instrumentResolver.HandleMetadataAsync(metadata); } } // Пытаемся выбрать заявку из контейнера Order order; using (ordersLock.Lock()) ordersByOrderExchangeId.TryGetValue(args.Message.chain_order_id, out order); Message message; // Обрабатываем изменение статуса заявки if (order != null) { message = HandleOrderStateAsOrderStateChange(order, args.Message); } // Обрабатываем заявку как новую else { message = HandleOrderStateAsNewOrder(args.Message, out order); } // Отправляем сообщение и TransactionReply if (message != null) { OnMessageReceived(message); TryEmitTransactionReplies(args.Message); // Обрабатываем сделки TryEmitFills(args.Message, order); } } catch (Exception e) { Logger.Error().Print(e, $"Failed to process {args.Message}"); } } }
/// <summary> /// Запуск транспорта /// </summary> public void Start() { using (syncRoot.Lock()) { if (isConnected) { return; } isConnected = Adapter.Connect(settings.Host, settings.Port, settings.ClientId); } }
public bool Update(ref L1FundamentalMsg msg) { Confirm(); using (syncRoot.Lock()) { InstrumentParams.DecimalPlaces = msg.DecimalPlaces; InstrumentParams.PriceStep = msg.PriceStep; InstrumentParams.PriceStepValue = msg.PriceStepValue; return(true); } }
private void RequestMarketDataSubscription(InstrumentSubscription subscription, MarketDataSubscription.Level level) { var marketDataSubscription = new MarketDataSubscription { contract_id = subscription.ContractId, level = (uint)level }; subscription.StartWaitForData(level); using (requestBatchLock.Lock()) { requestBatch.Add(marketDataSubscription); } }
public async Task <string[]> LookupSymbols(string code, int?maxResults) { var operation = new SymbolLookupRequest(maxResults); var requestId = LookupSocketWrapper.RequestIdPrefix + Guid.NewGuid().ToString("N"); using (symbolLookupRequestsLock.Lock()) { symbolLookupRequests[requestId] = operation; } lookupSocket.RequestSymbolLookup(code, null, requestId); var result = await operation.Task; return(result); }
public void AddPoint(HistoryDataPoint point) { using (historyPointsLock.Lock()) { points.Add(point); } }
/// <summary> /// Подписаться на инструмент. /// </summary> /// <param name="instrument"> /// Инструмент для подписки. /// </param> public async Task <SubscriptionResult> Subscribe(Instrument instrument) { using (LogManager.Scope()) { var data = await instrumentConverter.ResolveInstrumentAsync(this, instrument); if (data == null) { return(SubscriptionResult.Error(instrument, $"Unable to resolve symbol for {instrument}")); } // Забираем подписку из коллекции или создаем новую InstrumentSubscription subscription; bool isNewSubscription; using (instrumentSubscriptionsLock.Lock()) { if (instrumentSubscriptionsByInstrument.TryGetValue(instrument, out subscription)) { isNewSubscription = false; } else { subscription = new InstrumentSubscription(instrument, data, OnInstrumentSubscriptionTimedOut); instrumentSubscriptionsByInstrument.Add(instrument, subscription); instrumentSubscriptionsByCode[subscription.Code] = subscription; isNewSubscription = true; } } if (isNewSubscription) { // Если подписка уже существовала, то ничего не делаем // В противном случае требуется подписаться // Подписываемся socketL1.Subscribe(subscription.Code); subscription.BeginTimeout(); } return(await subscription.Task); } }
public OrderBook Update(int position, int operation, int side, double price, int size) { using (syncRoot.Lock()) { // Выбираем нужную сторону стакана List <OrderBookItem> targetList; switch (side) { case 0: targetList = asks; break; case 1: targetList = bids; break; default: throw new ArgumentOutOfRangeException(nameof(side)); } // Дополняем стакан до нужной глубины var op = side == 0 ? OrderOperation.Sell : OrderOperation.Buy; while (targetList.Count <= position) { targetList.Add(new OrderBookItem { Operation = op }); } var item = targetList[position]; switch (operation) { case 0: // Добавляем строку в стакан item.Price = (decimal)price; item.Quantity = size; break; case 1: // Обновляем строку в стакане item.Price = (decimal)price; item.Quantity = size; break; case 2: // Удаляем строку из стакана item.Price = (decimal)price; item.Quantity = 0; break; } // Собираем стакан return(BuildOrderBook()); } }
/// <summary> /// Получить Contract ID для инструмента /// </summary> /// <param name="instrument"> /// Инструмент /// </param> /// <returns> /// Contract ID /// </returns> public async Task <uint> GetContractIdAsync(Instrument instrument) { using (LogManager.Scope()) { // Ищем ID контракта в кеше uint contractId; using (cacheLock.Lock()) { if (cachedContractIds.TryGetValue(instrument, out contractId)) { return(contractId); } } // Ищем или создаем запрос инструмента ResolutionRequest request; var sendRequest = false; using (resolutionRequestsLock.Lock()) { if (!resolutionRequestsByInstrument.TryGetValue(instrument, out request)) { var requestId = adapter.GetNextRequestId(); request = new ResolutionRequest(requestId, instrument); resolutionRequestsById.Add(requestId, request); resolutionRequestsByInstrument.Add(instrument, request); sendRequest = true; } } // Отправляем запрос инструмента, если он был создан if (sendRequest) { await SendResolutionRequestAsync(request); } // Дожидаемся отработки запроса contractId = await request.Task; return(contractId); } }
public bool Connect() { try { using (socketSyncLock.Lock()) { socket.Connect(endPoint); } OnConnected(); WaitForData(); } catch (Exception e) { log.Error().Print(e, $"Failed to connect to IQConnect ({socketConnectionType})"); return(false); } SendCommand(CommandFormatter.GetConnectCommand(socketConnectionType)); return(true); }
public string Add(Transaction transaction) { using (_transactionIdsLock.Lock()) { if (!_transactionIdToClientOrderIdMap.TryGetValue(transaction.TransactionId, out var clOrderId)) { clOrderId = Xid.NewXid().ToString(); _transactionIdToClientOrderIdMap[transaction.TransactionId] = clOrderId; _clientOrderIdToTransactionIdMap[clOrderId] = transaction.TransactionId; } return clOrderId; } }
public override void execDetails(int reqId, Contract contract, Execution execution) { //Сделки с нулевым количеством //Пропускаем BAG, т.к. это контракт для конструкций. Сейчас мы его не используем. //BAG is the security type for COMBO order //https://www.interactivebrokers.com/en/software/api/apiguide/c/placing_a_combination_order.htm //https://www.interactivebrokers.com/en/software/api/apiguide/tables/api_message_codes.htm if (contract.SecType == "BAG") { return; } Log.Debug().PrintFormat("< execDetails {0} {1} {2} {3}", reqId, contract.ConId, execution.ExecId); connector.ContractContainer.GetInstrumentAsync( contract, string.Format("A fill on order \"{0}\", account \"{1}\"", execution.ExecId, execution.AcctNumber), async instrument => { var fill = new FillMessage { Account = execution.AcctNumber, Instrument = instrument, DateTime = ParseExecutionTime(execution.Time), ExchangeId = execution.ExecId, ExchangeOrderId = execution.PermId.ToString(CultureInfo.InvariantCulture), Operation = execution.Side == "BOT" ? OrderOperation.Buy : OrderOperation.Sell, Price = (decimal)execution.Price, Quantity = (uint)execution.CumQty }; // В случае частичного исполнения заявки мы запоминаем прежнее значение CumQty // и в соответствии с ним пересчитываем fill.Quantity using (lastExecCumQtiesLock.Lock()) { var orderKey = new Tuple <Instrument, int>(instrument, execution.PermId); int lastQty; if (lastExecCumQties.TryGetValue(orderKey, out lastQty)) { fill.Quantity = (uint)(execution.CumQty - lastQty); } lastExecCumQties[orderKey] = execution.CumQty; } // Ждём пока в наш адаптер не придут данные по заявке await orderInfoContainer.WaitOrderForFill(execution); // Выплевываем наружу сообщение connector.IBOrderRouter.Transmit(fill); }); }
/// <summary> /// Проверить подписку /// </summary> async Task <SubscriptionTestResult> ISubscriptionTester <InstrumentData> .TestSubscriptionAsync(InstrumentData data) { var id = GetNextRequestId(); var request = new ResolutionRequest(data.Symbol, id); using (resolutionRequestsLock.Lock()) { resolutionRequestsById.Add(id, request); } var message = new InformationRequest { symbol_resolution_request = new SymbolResolutionRequest { symbol = data.Symbol }, id = id }; SendMessage(message); return(await request.Task); }
/// <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); }
public InstrumentParams GetInstrumentParams(Instrument instrument) { using (syncRoot.Lock()) { InstrumentParams instrumentParams; if (!cachedInstrumentParams.TryGetValue(instrument, out instrumentParams)) { instrumentParams = new InstrumentParams { Instrument = instrument }; cachedInstrumentParams.Add(instrument, instrumentParams); } return(instrumentParams); } }
/// <summary> /// Сгенерировать <see cref="FillMessage"/> /// </summary> private void TryEmitFills(OrderStatus orderStatus, Order order) { if (orderStatus.transaction_status != null) { foreach (var transactionStatus in orderStatus.transaction_status) { switch ((TransactionStatus.Status)transactionStatus.status) { case TransactionStatus.Status.FILL: if (transactionStatus.trade == null) { continue; } foreach (var trade in transactionStatus.trade) { if (trade != null) { using (processedFillsLock.Lock()) { if (!processedFillIds.Add(trade.trade_id)) { // Сделка уже обработана continue; } } var fill = new FillMessage { Instrument = order.Instrument, DateTime = adapter.ResolveDateTime(trade.trade_utc_time), Account = order.Account, Operation = ConvertionHelper.GetOrderOperation((WebAPI.Order.Side)trade.side), Price = instrumentResolver.ConvertPriceBack(trade.contract_id, trade.price), Quantity = trade.qty, ExchangeId = trade.trade_id, ExchangeOrderId = order.OrderExchangeId }; OnMessageReceived(fill); } } break; } } } }
public void SaveOrderParams(Guid transactionId, string clOrderId, string symbol, decimal qty, char side) { using (_syncRoot.Lock()) { if (!_orderByTransactionIdMap.ContainsKey(transactionId)) { var orderParams = new OrderParams { Symbol = symbol, Qty = qty, Side = side }; _orderByTransactionIdMap.Add(transactionId, orderParams); _clOrderIdToTransactionIdMap.Add(clOrderId, transactionId); _transactionIdToClOrderIdMap.Add(transactionId, clOrderId); } } }
/// <summary> /// Обработчик события получения позиции /// </summary> private async void PositionStatusReceived(AdapterEventArgs <PositionStatus> args) { using (LogManager.Scope()) { try { args.MarkHandled(); // Ищем счет для позиции string accountCode; using (accountsLock.ReadLock()) { if (!accountCodesById.TryGetValue(args.Message.account_id, out accountCode)) { Logger.Error().PrintFormat( "Unable to process position on contract #{0}: account #{1} is unknown", args.Message.contract_id, args.Message.account_id); return; } } // Обрабатываем метаданные контрактов if (args.Message.contract_metadata != null) { await instrumentResolver.HandleMetadataAsync(args.Message.contract_metadata, $"A position in \"{accountCode}\" account"); } // Ищем инструмент для позиции var instrument = instrumentResolver.GetInstrument(args.Message.contract_id); if (instrument == null) { Logger.Warn().PrintFormat( "Received a position on contract #{0} in account {1} but no matching instrument is found", args.Message.contract_id, accountCode); return; } // Рассчитываем позицию var position = new PositionMessage { Account = accountCode, Instrument = instrument }; var message = new StringBuilder(); message.AppendFormat("Position received: {0} [ ", instrument.Code); using (openPositionsLock.Lock()) { Dictionary <uint, Dictionary <int, OpenPosition> > d1; if (!openPositions.TryGetValue(args.Message.account_id, out d1)) { d1 = new Dictionary <uint, Dictionary <int, OpenPosition> >(); openPositions.Add(args.Message.account_id, d1); } Dictionary <int, OpenPosition> d2; if (!d1.TryGetValue(args.Message.contract_id, out d2)) { d2 = new Dictionary <int, OpenPosition>(); d1.Add(args.Message.contract_id, d2); } if (args.Message.is_snapshot) { d2.Clear(); } position.Quantity = 0; foreach (var p in args.Message.open_position) { var fmt = ObjectLogFormatter.Create(PrintOption.Nested, "CQGC_POSITION"); fmt.AddField(LogFieldNames.Id, p.id); fmt.AddField(LogFieldNames.Price, p.price); fmt.AddField(LogFieldNames.Quantity, p.qty); fmt.AddField(LogFieldNames.TradeDate, p.trade_date); fmt.AddField(LogFieldNames.TradeUtcTime, p.trade_utc_time); fmt.AddField(LogFieldNames.StatementDate, p.statement_date); message.Append(fmt.Print(PrintOption.Nested)); message.Append(", "); position.Quantity = (int)p.qty; position.Price = (decimal)p.price; d2[p.id] = p; } var volume = 0d; var quantity = 0u; foreach (var pair in d2) { volume += pair.Value.price * pair.Value.qty; quantity += pair.Value.qty; } position.Quantity = (int)quantity; position.Price = quantity != 0u ? (decimal)(volume / quantity) : 0; } if (args.Message.is_short_open_position) { position.Quantity *= -1; } message.Append("]: "); message.Append(LogFields.Price(position.Price)); message.Append(LogFields.Quantity(position.Quantity)); message.Append(LogFields.IsSnapshot(args.Message.is_snapshot)); Logger.Debug().Print(message.ToString().Preformatted()); // Отправляем сообщение OnMessageReceived(position); } catch (Exception e) { Logger.Error().Print(e, $"Failed to process {args.Message}"); } } }
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); } } }