/// <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> private OrderStateChangeMessage HandleOrderStateAsOrderStateChange(Order order, OrderStatus message) { Logger.Debug().Print( "Order state change 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) ); using (ordersLock.Lock()) { Guid transactionId; if (!Guid.TryParse(message.order.cl_order_id, out transactionId)) { Logger.Debug().Print( $"Unable to parse {LogFieldNames.TransactionId} from {LogFields.ClOrderId(message.order.cl_order_id)}. Use order's {LogFieldNames.TransactionId}" ); transactionId = order.TransactionId; } var change = new OrderStateChangeMessage { OrderExchangeId = message.chain_order_id, TransactionId = transactionId, ActiveQuantity = message.remaining_qty, FilledQuantity = message.fill_qty, Price = instrumentResolver.ConvertPriceBack(order.Instrument, message.order.limit_price), ChangeTime = adapter.ResolveDateTime(message.status_utc_time), Quantity = message.remaining_qty + message.fill_qty, State = ConvertionHelper.GetOrderState(message) }; // Обработка изменения order_id (происходит при модификации заявки) if (order.OrderExchangeId != change.OrderExchangeId) { ordersByOrderExchangeId.Remove(order.OrderExchangeId); ordersByOrderExchangeId.Add(change.OrderExchangeId, order); } orderStatusByChainOrderId[message.chain_order_id] = message; //order.AcceptStateMessage(change); return(change); } }
/// <summary> /// Разбор биржевого ID заявки из ответа на транзакцию /// </summary> /// <param name="message"></param> /// <returns></returns> private long ParseOrderIdFromTransactionReply(QLTransactionReply message) { if (string.IsNullOrEmpty(message.result_msg)) { return(-1); } var match = orderIdFromTransReplRegex.Match(message.result_msg); // должено быть ровно одно совпадение if (match.Length == 0 || match.Groups.Count != 2) { return(-1); } long rValue = -1; long.TryParse(match.Groups[1].Value, out rValue); Logger.Debug().PrintFormat("Parsed order exchange id is: {0}", LogFields.ExchangeOrderId(rValue)); return(rValue); }
/// <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 }); } }
/// <summary> /// Обработать ответ на постановку транзакции, возвращает список QLOrderStateChange, обработка которых была отложена из-за /// отсутсвия trans_id /// </summary> /// <param name="message"></param> /// <param name="orderExchangeId"></param> public List <QLOrderStateChange> PutTransactionReply(QLTransactionReply message, long orderExchangeId) { // необходимо отправить на обработку отложенные OrderStateChange сообщения List <QLOrderStateChange> oscmToFire; using (locker.WriteLock()) { if (!newOrderTransactionsWithoutReplies.Contains(message.trans_id)) { return(null); } var newOrderTransaction = mapQuikTransIdOnNewOrderTransaction[message.trans_id]; newOrderTransactionsWithoutReplies.Remove(message.trans_id); mapOrderIdOnNewOrderTransaction[orderExchangeId] = newOrderTransaction; // сначала смотрим, есть ли отложенные oscm конкретно для этого номера заявки if (mapOrderIdOnPendingOrderStateChange.TryGetValue(orderExchangeId, out oscmToFire)) { log.Info().Print( $"PutTransactionReply: to fire {oscmToFire.Count} pending OSCMs for {LogFields.ExchangeOrderId(orderExchangeId)} and {LogFields.TransactionId(message.trans_id)}" ); mapOrderIdOnPendingOrderStateChange.Remove(orderExchangeId); // проставляем trans_id в oscm, т.к. проблема заключается в том, что когда oscm приходит раньше transReply, то osc.trans_id == 0 foreach (var oscm in oscmToFire) { oscm.trans_id = message.trans_id; } } // если транзакций без ответа не осталось, тогда нужно запроцессить все оставшиеся oscm, т.к. ответов на транзакции больше не последует if (newOrderTransactionsWithoutReplies.Count == 0 && mapOrderIdOnPendingOrderStateChange.Count != 0) { log.Info().Print("PutTransactionReply: to fire all other pending OSCMs, since there is no more NewOrderTransaction without reply"); if (oscmToFire == null) { oscmToFire = new List <QLOrderStateChange>(); } foreach (var pendingOscms in mapOrderIdOnPendingOrderStateChange) { oscmToFire.AddRange(pendingOscms.Value); } } } if (oscmToFire == null) { return(null); } return(oscmToFire); }