/// <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> /// Сгенерировать <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; } } } }
/// <summary> /// Постановка заявки /// </summary> private async void SendTransactionInternal(NewOrderTransaction transaction) { using (LogManager.Scope()) { try { // Получаем счет для транзакции int accountId; var hasAccountId = true; using (accountsLock.ReadLock()) { if (!accountIdsByCode.TryGetValue(transaction.Account, out accountId)) { hasAccountId = false; } } if (!hasAccountId) { OnMessageReceived(TransactionReply.Rejected( transaction, $"Account \"{transaction.Account}\" is unknown")); return; } // Получаем инструмент для транзации uint contractId; try { contractId = await instrumentResolver.GetContractIdAsync(transaction.Instrument); if (contractId == uint.MaxValue) { return; } } catch (OperationCanceledException) { OnMessageReceived(TransactionReply.Rejected( transaction, $"Instrument \"{transaction.Instrument.Code}\" is unknown")); return; } // Пребразовываем цену var price = instrumentResolver.ConvertPrice(contractId, transaction.Price); if (price == null) { OnMessageReceived(TransactionReply.Rejected(transaction, "Unable to convert price")); return; } // Формируем запрос var msg = new OrderRequest { new_order = new NewOrder { order = new WebAPI.Order { cl_order_id = transaction.TransactionId.ToString("N"), account_id = accountId, contract_id = contractId, side = (uint)ConvertionHelper.GetSide(transaction.Operation), order_type = (uint)ConvertionHelper.GetOrderType(transaction.Type), duration = (uint)ConvertionHelper.GetDuration(transaction.ExecutionCondition), limit_price = price.Value, qty = transaction.Quantity, is_manual = transaction.IsManual, user_attribute = { new UserAttribute { name = CommentAttributeName, value = transaction.Comment } } }, suspend = false }, request_id = adapter.GetNextRequestId() }; // Запоминаем заявку using (ordersLock.Lock()) { var order = new Order(transaction); ordersByTransactionId.Add(transaction.TransactionId, order); } // Запоминаем транзакцию StoreTransaction(msg.request_id, transaction); Logger.Debug().PrintFormat("Sending {0}", transaction); adapter.SendMessage(msg); } catch (Exception e) { Logger.Error().Print(e, $"Failed to send {transaction}"); OnMessageReceived(TransactionReply.Rejected(transaction, "Unable to send order")); } } }
/// <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 }); } }