/// <summary> /// Добавить в очередь ответ на транзакцию /// </summary> /// <param name="tr"></param> public void PutPendingTransactionReply(QLTransactionReply tr) { using (locker.WriteLock()) { pendingTransactionReplies.Add(tr); } }
/// <summary> /// Обработка неудачной транзакции /// </summary> /// <param name="message"></param> private void ProcessFailedTransactionReply(QLTransactionReply message) { var newOrderTransaction = container.GetNewOrderTransaction(message.trans_id); var killOrderTransaction = container.GetKillOrderTransactionByTransId(message.trans_id); var modifyOrderTransaction = container.GetModifyOrderTransactionByTransId(message.trans_id); switch (message.status) { case 13: // отвергнута как кросс сделка Logger.Error().Print("Transaction failed, due to potential cross trade", LogFields.Message(message)); break; default: Logger.Error().Print("Transaction failed", LogFields.Message(message)); break; } OnMessageReceived(message: new TransactionReply { Success = message.Successful, Message = message.result_msg, TransactionId = newOrderTransaction?.TransactionId ?? killOrderTransaction?.TransactionId ?? modifyOrderTransaction?.TransactionId ?? Guid.Empty }); container.RemoveProcessedPendingReply(message); }
/// <summary> /// Удалить отложенный ответ на транзакцию /// </summary> public void RemoveProcessedPendingReply(QLTransactionReply tr) { log.Debug().Print("Remove processed TR", LogFields.TransactionId(tr.trans_id)); using (locker.WriteLock()) { pendingTransactionReplies.Remove(tr); } }
/// <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); }
/// <summary> /// Предварительная обработка ответа на транзакцию /// </summary> /// <param name="message"></param> private void PreprocessTransactionReply(QLTransactionReply message) { if (!string.IsNullOrEmpty(message.result_msg) && ( message.result_msg.Contains("Вы не можете снять данную заявку") || message.result_msg.Contains("Не найдена заявка для удаления") )) { Logger.Warn().Print( $"Failed kill transaction will be treated as successfull, status will be changed from {message.status} to 3", LogFields.Message(message) ); message.status = 3; } }
private void Handle(QLTransactionReply message) { Logger.Debug().PrintFormat("Handle: {0}", message); var orderExchangeId = ParseOrderIdFromTransactionReply(message); if (orderExchangeId > 0) { container.PutOrderExchangeId(orderExchangeId); } var pendingOscmsToProcess = container.PutTransactionReply(message, orderExchangeId); if (pendingOscmsToProcess != null) { Logger.Debug().Print($"Handle(TR) fires {pendingOscmsToProcess.Count} pending OSCMs to process", LogFields.Message(message)); foreach (var oscm in pendingOscmsToProcess) { // если у нас был получен oscm по данному номеру заявки, но с нулевым trans_id (да да, такое бывает), то тут // у нас есть прекрасная возможность проставить oscm.trans_id и запроцессить oscm по нормальному алгоритму if (oscm.order_num == orderExchangeId && oscm.trans_id == 0) { oscm.trans_id = message.trans_id; } Handle(oscm); } } PreprocessTransactionReply(message); if (message.Successful) { ProcessSuccessfulTransactionReply(message); } else { ProcessFailedTransactionReply(message); } }
/// <summary> /// Возвращает последний полученный статус заявки, связанный с транзакцией с идентификатором transId /// </summary> /// <param name="reply">Квиковый идентификатор транзакции</param> /// <returns></returns> public QLOrderStateChange GetLastOrderStateChangeForTransactionId(QLTransactionReply reply) { QLOrderStateChange rValue; long orderId; using (locker.ReadLock()) { if (!mapQuikTransIdOnOrderExchangeId.TryGetValue(reply.trans_id, out orderId)) { return(null); } List <QLOrderStateChange> orderStates; if (!orderStateChanges.TryGetValue(orderId, out orderStates)) { return(null); } rValue = orderStates.LastOrDefault(); } return(rValue); }
/// <summary> /// Парсинг не снятого количества из ответа на kill транзакцию /// </summary> /// <param name="message"></param> /// <returns></returns> private int ParseUnfilledQuantityFromTransactionReply(QLTransactionReply message) { if (string.IsNullOrEmpty(message.result_msg)) { return(-1); } var match = (message.result_msg.Contains("Неисполненный остаток") ? quantityFromKillTransReplRegex1 : quantityFromKillTransReplRegex2).Match(message.result_msg); // должено быть ровно одно совпадение if (match.Length == 0 || match.Groups.Count != 2) { return(-1); } int rValue = -1; int.TryParse(match.Groups["quantity"].Value, out rValue); Logger.Debug().PrintFormat("Parsed unfilled quantity is: {0}", LogFields.ActiveQuantity(rValue)); return(rValue); }
/// <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 void ProcessSuccessfulTransactionReply(QLTransactionReply message) { var newOrderTransaction = container.GetNewOrderTransaction(message.trans_id); var killOrderTransaction = container.GetKillOrderTransactionByTransId(message.trans_id); var modifyOrderTransaction = container.GetModifyOrderTransactionByTransId(message.trans_id); if (newOrderTransaction == null && killOrderTransaction == null && modifyOrderTransaction == null) { Logger.Warn().Print("TRANS_REPL received for transaction which wasn't sent from application", LogFields.Message(message)); container.RemoveProcessedPendingReply(message); return; } QLOrderStateChange lastState; if ((lastState = container.GetLastOrderStateChangeForTransactionId(message)) == null) { Logger.Debug().Print("Postpone TrRepl processing, no last OSCM found", LogFields.Message(message)); container.PutPendingTransactionReply(message); return; } // проверка соответствия последнего статуса типу транзакции // kill транзакция считатется завершённой, если заявка находится в одном из статусов ниже if (killOrderTransaction != null && lastState.State == OrderState.New) { Logger.Debug().Print($"Postpone KillTrRepl processing. Last ord state is {lastState.State}", LogFields.Message(message)); container.PutPendingTransactionReply(message); return; } // new транзакция считатется завершённой, если заявка не находится в одном из статусов ниже if (newOrderTransaction != null && (lastState.State == OrderState.New || lastState.State == OrderState.Undefined)) { Logger.Debug().Print($"Postpone NewTrRepl processing. Last ord state is {lastState.State}", LogFields.Message(message)); container.PutPendingTransactionReply(message); return; } if (modifyOrderTransaction != null && (lastState.State == OrderState.New || lastState.State == OrderState.Undefined || lastState.trans_id != message.trans_id)) { Logger.Debug().Print($"Postpone ModifyTrRepl processing. Last ord state is {lastState.State}", LogFields.Message(message)); Logger.Error().Print("Handle(TR): for modify transaction not implemented"); container.PutPendingTransactionReply(message); return; } if (killOrderTransaction != null) { var unfilledQuantity = ParseUnfilledQuantityFromTransactionReply(message); Logger.Debug().Print($"Handle(TR)[{message.trans_id}]: Create artifitial OSCM from successful kill transaction reply", LogFields.Message(message)); OnMessageReceived(new OrderStateChangeMessage { TransactionId = killOrderTransaction.TransactionId, ActiveQuantity = (uint)unfilledQuantity, OrderExchangeId = message.order_num.ToString(), //FilledQuantity = (uint)(message.filled), State = OrderState.Cancelled, Price = message.price, Quantity = (uint?)message.quantity }); } OnMessageReceived(new TransactionReply { Success = message.Successful, Message = message.result_msg, TransactionId = newOrderTransaction?.TransactionId ?? killOrderTransaction?.TransactionId ?? modifyOrderTransaction?.TransactionId ?? Guid.Empty }); container.RemoveProcessedPendingReply(message); }