/// <summary> /// Возвращает фолс, если такой статус уже приходил /// (иногда квик присылает один и тот же QLOrderStateChange по нескольку раз, зафиксированы случаи по три присыла). /// /// ВАЖНО: этот метод также расчитывает реально исполненное количество в текущем PartiallyFilled изменение /// </summary> public bool PutOrderStateChange(QLOrderStateChange osc) { List <QLOrderStateChange> changes; using (locker.WriteLock()) { if (!orderStateChanges.TryGetValue(osc.order_num, out changes)) { orderStateChanges.Add(osc.order_num, (changes = new List <QLOrderStateChange>())); } if (changes.Contains(osc)) { return(false); } var prevFilledQuantity = changes.Sum(_ => _.State == OrderState.PartiallyFilled ? _.filled : 0); var totalFilledQuantiry = osc.qty - osc.balance; osc.filled = totalFilledQuantiry - prevFilledQuantity; changes.Add(osc); if (currentSeccionTransactionIds.Contains(osc.trans_id)) { mapQuikTransIdOnOrderExchangeId[osc.trans_id] = osc.order_num; } } return(true); }
/// <summary> /// Положить OSCM в отложенную обработку /// </summary> /// <param name="oscm"></param> public void PutPendingOrderStateChange(QLOrderStateChange oscm) { log.Info().Print("Postpone OSCM processing, there are transactions without replies.", LogFields.Message(oscm)); List <QLOrderStateChange> oscms; using (locker.WriteLock()) { if (!mapOrderIdOnPendingOrderStateChange.TryGetValue(oscm.order_num, out oscms)) { mapOrderIdOnPendingOrderStateChange[oscm.order_num] = oscms = new List <QLOrderStateChange>(); } oscms.Add(oscm); } }
/// <summary> /// Обработка статуса по заявке, которая была выставлена транзакцией из прикладного приложения из текущей сессии /// </summary> /// <param name="message">Изменения</param> /// <param name="newTransactionId">ID связанной с заявкой транзакции на постановку</param> /// <param name="killTransactionId">ID связанной с заявкой транзакции на снятие</param> /// <param name="modifyTransactionId">ID связанной с заявок транзакции на изменение заявки</param> private void ProcessKnownOrderStateChange(QLOrderStateChange message, Guid?newTransactionId, Guid?killTransactionId, Guid?modifyTransactionId) { Guid transactionId = Guid.Empty; // если есть и new и kill транзакция для этого номера заявки, то нужно понять, результатом какой из транзакицй // является конкретно это изменение ордера if (newTransactionId.HasValue && killTransactionId.HasValue) { // если заявка снялась, то это результат килл транзакции и именно её transId мы проставляем в OSCM, чтобы правильно запроцессить в TransactionMessageHandler-е transactionId = message.State == OrderState.Cancelled ? killTransactionId.Value : newTransactionId.Value; Logger.Debug().PrintFormat( "OSCM with two associated transactions received: not={0}, kot={1}. State is {2}, select {3} for further OSCM processing.", newTransactionId, killTransactionId, message.State, transactionId ); } // если есть только одна транзакция, то выбираем её else { transactionId = (Guid)(newTransactionId ?? killTransactionId); } OnMessageReceived(new OrderStateChangeMessage { TransactionId = transactionId, ActiveQuantity = (uint)message.balance, OrderExchangeId = message.order_num.ToString(), FilledQuantity = (uint)(message.filled), State = message.State, Price = message.price, Quantity = (uint?)message.qty, ChangeTime = message.Time }); }
/// <summary> /// Обработка статуса по заявке, которая либо была выставлена не из прикладного приложения или из другой сессии, либо по которой пока не пришёл ответ на транзакцию /// </summary> private void ProcessUnknownOrderStateChange(QLOrderStateChange message) { try { var order = container.GetOrder(message.order_num); // если это первый статус по заявке, то отправляем ExternalOrderMessage if (order == null) { var instrument = adapter.ResolveInstrumentAsync(message.sec_code).Result; if (instrument == null) { Logger.Error().Print($"Unable to resolve instrument for {message.sec_code}"); return; } order = new Order { OrderExchangeId = message.order_num.ToString(), Instrument = instrument, Account = message.account, ActiveQuantity = (uint)message.balance, Quantity = (uint)message.qty, State = message.State, Price = message.price, ClientCode = message.account, Comment = ExtractCommentFromBrokerref(message.brokerref), // TODO Тут проставляется 4100Y2b//00007, а мы для кодирования стратегий используется только то, что после // Operation = message.Operation, DateTime = message.Time, TransactionId = Guid.NewGuid() }; container.PutOrder(message.order_num, order); OnMessageReceived(new ExternalOrderMessage { Order = order }); OnMessageReceived(new OrderStateChangeMessage { ActiveQuantity = (uint)message.balance, OrderExchangeId = message.order_num.ToString(), FilledQuantity = (uint)(message.filled), State = message.State, Price = message.price, Quantity = (uint?)message.qty, ChangeTime = message.Time, TransactionId = order.TransactionId }); } // иначе мы уже отправляли информацию о внешней заявке и теперь должны слать статусы else { OnMessageReceived(new OrderStateChangeMessage { ActiveQuantity = (uint)message.qty, OrderExchangeId = message.order_num.ToString(), FilledQuantity = (uint)(message.filled), State = message.State, Price = message.price, Quantity = (uint?)message.balance, ChangeTime = message.Time, TransactionId = order.TransactionId }); } } catch (Exception e) { Logger.Error().Print(e, $"Failed to process {message}"); } }
/// <summary> /// Обработка сообщения об изменении статуса заявки /// </summary> /// <param name="message"></param> private void Handle(QLOrderStateChange message) { Logger.Debug().PrintFormat("Handle: {0}", message); // если у нас есть транзакции без ответа, то откладываем обратку статуса заявки, будем процессить их после получения transReply if (container.HasUnrepliedTransactions()) { // бывает квик присылает и такое if (message.trans_id == 0) { container.PutPendingOrderStateChange(message); return; } // если есть неотвеченные транзакции, то по OSCM можно создать ответ на транзакцию if (container.IsTransactionUnreplied(message.trans_id)) { if (message.State == OrderState.Active || message.State == OrderState.PartiallyFilled || message.State == OrderState.Filled) { Logger.Info().Print($"Handle(OSCM): transaction with trans_id = {message.trans_id} is pending and order state {message.State} received. Create and handle artifitial QLTransactionReply"); Handle(new QLTransactionReply { status = 3, result_msg = message.reject_reason, trans_id = message.trans_id, account = message.account, order_num = message.order_num, price = message.price, balance = message.balance, quantity = message.qty }); } } } // Сохраняем сообщение. Если такое сообщение уже приходило, не обрабатываем его. if (!container.PutOrderStateChange(message)) { Logger.Debug().PrintFormat("Handle: {0} - skip duplicate event", message); return; } // пробуем получить транзакцию, в результате которой была создана заявка, по которой мы получили данный OSCM // TODO Нужно пытаться получить newOrderTransaction не только по trans_id, но и по номеру заявки var newOrderTransaction = container.GetNewOrderTransaction(message.trans_id, message.order_num); var killOrderTransaction = container.GetKillOrderTransactionByOrderId(message.order_num); var modifyOrderTransaction = container.GetModifyOrderTransactionByOrderId(message.order_num); if (newOrderTransaction != null || killOrderTransaction != null) { ProcessKnownOrderStateChange(message, newOrderTransaction?.TransactionId, killOrderTransaction?.TransactionId, modifyOrderTransaction?.TransactionId); } else { ProcessUnknownOrderStateChange(message); } processPendingMessagesEvent.Set(); }