private void MarketDataResolved(AdapterEventArgs <InformationReport> args) { var contractId = args.Message.symbol_resolution_report.contract_metadata.contract_id; ResolutionRequest request; using (resolutionRequestsLock.Lock()) { if (!resolutionRequestsById.TryGetValue(args.Message.id, out request)) { return; } using (cacheLock.Lock()) { cachedContractIds[request.Instrument] = contractId; cachedContracts[contractId] = request.Instrument; cachedContractPriceScales[contractId] = (decimal)args.Message.symbol_resolution_report.contract_metadata.correct_price_scale; } resolutionRequestsById.Remove(request.Id); resolutionRequestsByInstrument.Remove(request.Instrument); } request.Resolve(contractId); args.MarkHandled(); OnInstrumentResolved(contractId); _Log.Debug().Print("Instrument is resolved", LogFields.Instrument(request.Instrument), LogFields.ContractId(contractId), LogFields.RequestId(request.Id)); }
public static bool GetFormatKeyOf(LogFields field, out char chKey) { chKey = '\0'; if (field == LogFields.Location) { chKey = 'L'; } if (field == LogFields.Method) { chKey = 'M'; } if (field == LogFields.FileLocation) { chKey = 'f'; } if (field == LogFields.FullMessage) { chKey = 'F'; } if (field == LogFields.LogStack) { chKey = 'S'; } if (field == LogFields.LogCurrent) { chKey = 's'; } return(chKey != '\0'); }
/// <summary> /// Socket connection error event handler. Called when error rose up. /// </summary> private void OnSocketError(object sender, ErrorEventArgs e) { Log.Error().Print(e.Exception, "WS:OnSocketError", LogFields.Login(settings?.Username)); // статус Terminated мы не меняем, он финальный, сбрасывается только при перезапуске транспорта connectionStatus = connectionStatus != ConnectionStatus.Terminated ? ConnectionStatus.Disconnected : connectionStatus; OnConnectionStatusChanged(connectionStatus); using (socketLock.Lock()) { if (socket != null) { try { if (socket.State == WebSocketState.Open) { socket.Close(); } // NOTE вообще тут надо бы делать socket.Dispose(), но из-за этого возникает race condition // socket.Dispose(); } catch (Exception exception) { Log.Error().Print(exception, "WS:OnSocketError: Error while disposing error socket"); } finally { socket = null; } } } socketErrorEvent.Set(); }
/// <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> /// Socket connection close event handler. Called when connection closed. /// </summary> private void OnSocketClose(object sender, EventArgs e) { Log.Debug().Print("WS:OnSocketClose", LogFields.Login(settings?.Username)); // статус Terminated мы не меняем, он финальный, сбрасывается только при перезапуске транспорта connectionStatus = connectionStatus != ConnectionStatus.Terminated ? ConnectionStatus.Disconnected : connectionStatus; OnConnectionStatusChanged(connectionStatus); using (socketLock.Lock()) { try { socket?.Dispose(); } catch (Exception ex) { Log.Error().Print(ex, "WS:OnSocketClose failed to process an event"); } finally { socketClosedEvent.Set(); socket = null; } } }
/// <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); }
/// <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 RemoveProcessedPendingReply(QLTransactionReply tr) { log.Debug().Print("Remove processed TR", LogFields.TransactionId(tr.trans_id)); using (locker.WriteLock()) { pendingTransactionReplies.Remove(tr); } }
/// <summary> /// Обработка отказа по заявке /// </summary> private void OrderRequestRejectReceived(AdapterEventArgs <OrderRequestReject> args) { args.MarkHandled(); var message = $"[{args.Message.reject_code}] {args.Message.text_message}"; Logger.Error().Print("CQG order rejected", LogFields.Message(message)); TrySendTransactionReplyRejected(args.Message.request_id, message); }
/// <summary> /// Обработчик события изменения статуса подписки /// </summary> private void TradeSubscriptionStatusReceived(AdapterEventArgs <TradeSubscriptionStatus> args) { args.MarkHandled(); Logger.Debug().Print( "Trade subscription status received", LogFields.Id(args.Message.id), LogFields.Status((TradeSubscriptionStatus.StatusCode)args.Message.status_code), LogFields.Message(args.Message.text_message) ); }
public void SetUserField(string field, string value) { string convertedField = LogFields.ConvertField(field); if (ContainsFiled(convertedField)) { Remove(convertedField); } Put(convertedField, value); }
public static bool GetFormatKeyOf( LogFields field, out char chKey ) { chKey = '\0'; if(field == LogFields.Location) chKey = 'L'; if(field == LogFields.Method) chKey = 'M'; if(field == LogFields.FileLocation) chKey = 'f'; if(field == LogFields.FullMessage) chKey = 'F'; if(field == LogFields.LogStack) chKey = 'S'; if(field == LogFields.LogCurrent) chKey = 's'; return chKey != '\0'; }
private async Task HandleAsync(QLFill message) { try { Logger.Debug().Print($"Handle(QLFill): {message}"); var instrument = await adapter.ResolveInstrumentAsync(message.sec_code); if (instrument == null) { Logger.Error().Print($"Unable to resolve instrument for {message.sec_code}"); return; } // если заявка отправлялась в текущей сессии работы программы, то нужно убедиться, что oscm по ней уже отправлялся if (container.IsCurrentSessionOrder(message.order_num)) { var lastOscm = container.GetLastOrderStateChangeForOrderId(message.order_num); if (lastOscm == null) { Logger.Debug() .Print("Handle(QLFill): Fill will be processed later, no OSCM received", LogFields.Message(message)); container.PutPendingFill(message); return; } } else if (container.HasUnrepliedTransactions()) { Logger.Debug() .Print($"Handle(QLFill): Fill will be processed later, there are unreplied transactions", LogFields.Message(message)); container.PutPendingFill(message); return; } OnMessageReceived(new FillMessage { Instrument = instrument, Account = message.account, Quantity = (uint)message.qty, ClientCode = message.account, ExchangeId = message.trade_num.ToString(), ExchangeOrderId = message.order_num.ToString(), Price = message.price, Operation = message.Operation, DateTime = message.Time }); } catch (Exception e) { Logger.Error().Print(e, $"Failed to handle {message}"); } }
private static void MarketDataSubscriptionStatusReceived(AdapterEventArgs <MarketDataSubscriptionStatus> args) { args.MarkHandled(); _Log.Debug().Print( $"Subscription status for contract #{args.Message.contract_id}: Level={1}, Status={2}. {3}", LogFields.Level((MarketDataSubscription.Level)args.Message.level), LogFields.Status((MarketDataSubscriptionStatus.StatusCode)args.Message.status_code), LogFields.Message(args.Message.text_message) ); }
/// <summary> /// Обработка результата логина /// </summary> private void Handle(LogonResult msg) { var resultCode = (LogonResult.ResultCode)msg.result_code; switch (resultCode) { case LogonResult.ResultCode.SUCCESS: Log.Info().Print($"Connected to CQG with login {settings?.Username}"); if (!DateTime.TryParseExact( msg.base_time, "yyyy-MM-ddTHH:mm:ss", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal, out baseTime)) { baseTime = DateTime.UtcNow; Log.Warn().Print($"Unable to parse base_time = msg.base_time. Order and fill timestamps might be corrupted. Historical data won't work at all."); } //[ENABLE_CQGC_TRACE]WebApiTrace.SetBaseTime(baseTime); connectionStatus = ConnectionStatus.Connected; OnConnectionStatusChanged(connectionStatus); logoffEfent.Reset(); RequestInitializingData(); return; case LogonResult.ResultCode.FAILURE: break; case LogonResult.ResultCode.NO_ONETIME_PASSWORD: break; case LogonResult.ResultCode.PASSWORD_EXPIRED: return; case LogonResult.ResultCode.CONCURRENT_SESSION: break; case LogonResult.ResultCode.REDIRECTED: break; } Log.Error().Print("Unable to log in", LogFields.Result(resultCode), LogFields.Message(msg.text_message)); connectionStatus = ConnectionStatus.Terminated; OnConnectionStatusChanged(connectionStatus); using (socketLock.Lock()) { Log.Debug().Print("Closing CQG socket"); socket?.Close(); } }
private void CreateTcpClient() { if (tcpCient == null) { Log.Debug().Print("(Re)creating TcpClient", LogFields.IP(ip), LogFields.Port(port)); tcpCient = new TcpClient { ExclusiveAddressUse = true, NoDelay = true, ReceiveBufferSize = 1000000 }; } }
/// <summary> /// Сохранить сделку, требующую отложенной обработки /// </summary> /// <param name="message"></param> public void PutPendingFill(QLFill message) { log.Debug().Print("Postpone fill processing", LogFields.Message(message)); using (locker.WriteLock()) { if (pendingFills.ContainsKey(message.trade_num)) { log.Warn().Print($"Fill duplicate received", LogFields.Id(message.trade_num)); return; } pendingFills[message.trade_num] = message; } }
/// <summary> /// Socket connection open event handler. Called when connection opened successfully. /// </summary> private void OnSocketOpen(object sender, EventArgs args) { Log.Debug().Print("WS:OnSocketOpen", LogFields.Login(settings?.Username)); try { socketClosedEvent.Reset(); SendLogon(); } catch (Exception e) { Log.Error().Print(e, "WS:OnSocketOpen failed to process an event"); } }
/// <summary> /// Handels the initialization of the Field setups, creating a list of cruise methods and /// </summary> public void HandleFieldSetupLoad() { var allTreeFields = TreeFields; //check to see if Cruise method list has been initialized if (CruiseMethods == null) { try { var methods = Database.From <EditTemplateCruiseMethod>().Read().ToList(); foreach (var method in methods) { var treeFields = Database.From <TreeFieldSetupDefaultDO>() .Where("Method = @p1").OrderBy("FieldOrder").Read(method.Code).ToList(); method.TreeFields = new BindingList <TreeFieldSetupDefaultDO>(treeFields); var unselectedTreeFields = allTreeFields.Except(treeFields, TreeFieldDefaultComparer.Instance) .Select(tfs => new TreeFieldSetupDefaultDO(tfs) { Method = method.Code }) .ToList(); method.UnselectedTreeFields = new BindingList <TreeFieldSetupDefaultDO>(unselectedTreeFields); } CruiseMethods = new BindingList <EditTemplateCruiseMethod>(methods); } catch { } } if (SelectedLogFields == null) { try { var logFields = Database.From <LogFieldSetupDefaultDO>() .OrderBy("FieldOrder").Read().ToList(); SelectedLogFields = new BindingList <LogFieldSetupDefaultDO>(logFields); var unselectedLogFields = LogFields.Except(logFields, LogFieldDefaultComparer.Instance) .Select(lfs => new LogFieldSetupDefaultDO(lfs)).ToList(); UnselectedLogFields = new BindingList <LogFieldSetupDefaultDO>(unselectedLogFields); } catch { SelectedLogFields = null; } View.UpdateFieldSetup(); } }
/// <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; } }
/// <summary> /// Создаёт и открывает сокет /// </summary> private void CreateAndOpenSocket() { using (socketLock.Lock()) { Log.Info().Print("Connecting to CQG Continuum", LogFields.URL(settings.ConnectionUrl), LogFields.Login(settings.Username)); socket?.Dispose(); socket = new WebSocket(settings.ConnectionUrl); socket.Opened += OnSocketOpen; socket.Closed += OnSocketClose; socket.Error += OnSocketError; socket.DataReceived += OnSocketData; socket.Open(); } }
/// <summary> /// Сохранить идентификатор полученной сделки. Для дальнейшего анализа на получение дубликата сообщения /// </summary> /// <param name="id">Биржевой идентификатор сделки</param> /// <returns>true - если сделка уже есть в контейнере, иначе false</returns> public bool PutFillId(QLFill fill) { using (lockerReceivedFills.WriteLock()) { if (receivedFills.Contains(fill.trade_num)) { log.Warn().Print($"Fill duplicate received", LogFields.Id(fill.trade_num)); return(true); } receivedFills.Add(fill.trade_num); } return(false); }
private async Task <Contract> ResolveContractStubAsync(Instrument instrument) { var instrumentData = await instrumentConverter.ResolveInstrumentAsync(adapter, instrument); if (instrumentData == null) { _Log.Error().Print("Unable to resolve vendor symbol for an instrument", LogFields.Instrument(instrument)); return(null); } var contract = GetContractStub(instrumentData); return(contract); }
/// <summary> /// Обработка результата выхода из системы /// </summary> private void Handle(LoggedOff msg) { if (msg != null) { Log.Info().Print($"Logoff received. Closing socket", LogFields.Login(settings?.Username)); using (socketLock.Lock()) { socket?.Close(); } logoffEfent.Set(); } connectionStatus = ConnectionStatus.Connecting; OnConnectionStatusChanged(connectionStatus); }
/// <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> /// Отправить сообщение логина /// </summary> private void SendLogon() { var logon = new Logon { user_name = settings.Username, password = settings.Password, client_app_id = "ItGlobal", client_version = ClientVersion, collapsing_level = (uint)RealTimeCollapsing.Level.DOM_BBA_TRADES, drop_concurrent_session = true, protocol_version_major = (uint)ProtocolVersionMajor.PROTOCOL_VERSION_MAJOR, protocol_version_minor = (uint)ProtocolVersionMinor.PROTOCOL_VERSION_MINOR }; Log.Debug().Print("Sending logon message", LogFields.Login(settings?.Username)); SendMessage(logon); }
/// <summary> /// Обновить контракт по его метаданным /// </summary> /// <param name="metadata"> /// Метаданные контракта /// </param> /// <param name="dependentObjectDescription"> /// Строковое описание объекта, который зависит от инструмента /// </param> public async Task HandleMetadataAsync(ContractMetadata metadata, string dependentObjectDescription = null) { using (LogManager.Scope()) { _Log.Debug().Print( "Contract metadata received", LogFields.ContractId(metadata.contract_id), LogFields.Symbol(metadata.contract_symbol), LogFields.CorrectPriceScale(metadata.correct_price_scale), LogFields.DisplayPriceScale(metadata.display_price_scale) ); using (cacheLock.Lock()) { if (cachedContracts.ContainsKey(metadata.contract_id)) { _Log.Debug().Print("Already cached", LogFields.ContractId(metadata.contract_id)); return; } } Instrument instrument = await InstrumentConverter.ResolveSymbolAsync(adapter, metadata.contract_symbol, dependentObjectDescription); if (instrument == null) { _Log.Warn().Print("Instrument not resolved", LogFields.Symbol(metadata.contract_symbol)); return; } using (cacheLock.Lock()) { _Log.Debug().Print( "Caching instrument", LogFields.ContractId(metadata.contract_id), LogFields.Symbol(metadata.contract_symbol), LogFields.Instrument(instrument) ); cachedContractIds[instrument] = metadata.contract_id; cachedContracts[metadata.contract_id] = instrument; cachedContractPriceScales[metadata.contract_id] = (decimal)metadata.correct_price_scale; } OnInstrumentResolved(metadata.contract_id); } }
public void Send(Transaction transaction) { try { transaction.Accept(this); } catch (TransactionRejectedException exception) { Reject(transaction, exception.Message); } catch (Exception exception) { _Log.Error() .Print(exception, "Unable to send transaction", LogFields.Transaction(transaction), LogFields.Message(exception.Message)); Reject(transaction, "Transaction execution failed: {0}", exception.Message); } }
/// <summary> /// Преобразовать цену из формата CQG /// </summary> /// <param name="contractId"> /// Contract ID /// </param> /// <param name="price"> /// Цена в нотации CQG /// </param> /// <returns> /// Цена /// </returns> public decimal ConvertPriceBack(uint contractId, long price) { using (cacheLock.Lock()) { decimal scale; if (!cachedContractPriceScales.TryGetValue(contractId, out scale)) { _Log.Error().Print( "Can't find price scale for contract", LogFields.ContractId(contractId), LogFields.Instrument(GetInstrument(contractId)?.Code) ); return(price); } var correctedPrice = scale != 0 ? price * scale : price; return(correctedPrice); } }
/// <summary> /// Обновить контракт по его метаданным /// </summary> /// <param name="metadata"> /// Метаданные контракта /// </param> public void HandleMetadata(ContractMetadata metadata) { _Log.Debug().Print( "Contract metadata received", LogFields.ContractId(metadata.contract_id), LogFields.Symbol(metadata.contract_symbol), LogFields.CorrectPriceScale(metadata.correct_price_scale), LogFields.DisplayPriceScale(metadata.display_price_scale) ); using (cacheLock.Lock()) { if (cachedContracts.ContainsKey(metadata.contract_id)) { _Log.Debug().Print("Already cached", LogFields.ContractId(metadata.contract_id)); return; } } var instrument = InstrumentConverter.ResolveSymbolAsync(adapter, metadata.contract_symbol).Result; if (instrument == null) { _Log.Warn().Print("Instrument not resolved", LogFields.Symbol(metadata.contract_symbol)); return; } using (cacheLock.Lock()) { _Log.Debug().Print( "Caching instrument", LogFields.ContractId(metadata.contract_id), LogFields.Symbol(metadata.contract_symbol), LogFields.Instrument(instrument) ); cachedContractIds[instrument] = metadata.contract_id; cachedContracts[metadata.contract_id] = instrument; cachedContractPriceScales[metadata.contract_id] = (decimal)metadata.correct_price_scale; } OnInstrumentResolved(metadata.contract_id); }
/// <summary> /// Отправить сообщение в сокет /// </summary> private void SendMessageImpl(ClientMsg msg) { using (socketLock.Lock()) { if (socket == null || socket.State == WebSocketState.Closed || socket.State == WebSocketState.Closing || socket.State == WebSocketState.Connecting || socket.State == WebSocketState.None ) { Log.Error().Print($"CQG adapter is not connected", LogFields.Login(settings?.Username)); return; } try { byte[] serverMessageRaw; using (var memoryStream = new MemoryStream()) { Serializer.Serialize(memoryStream, msg); serverMessageRaw = memoryStream.ToArray(); } //[ENABLE_CQGC_TRACE]WebApiTrace.OutMessage(msg); socket.Send(serverMessageRaw, 0, serverMessageRaw.Length); } catch (Exception ex) { // В случае сбоя - отключаемся Log.Error().Print(ex, "Failed to send a message"); if (socket != null) { Stop(); socket?.Dispose(); } connectionStatus = ConnectionStatus.Disconnected; } } }