Для изменения размера нажмите или перетащите

Адаптеры

Механизм сообщений позволяет создавать собственные подключения к любой внешней торговой системе. Для этого необходимо создать собственный класс адаптера сообщений, унаследованный от абстрактного класса MessageAdapter.

При разработке собственного адаптера сообщений необходимо решить следующие задачи:

  1. Написать код, преобразующий исходящие сообщения S# в команды внешней системы.

  2. Написать код, преобразующий информацию, поступающую от внешней системы, во входящие сообщения.

  3. Выполнить преобразование кодированной информации внешней системы (коды инструментов и площадок, перечисления и т.п.) в типы S#.

  4. Выполнить дополнительные настройки, связанные с особенностями внешней торговой системы.

Прежде чем приступить к описанию разработки собственного адаптера, рассмотрим процесс создания и обработки входящих и исходящих сообщений в S# на примере сообщения ConnectMessage. Предположим, что в программе был вызван метод Connect, тогда в базовом классе Connector будет происходить следующее:

  1. Вызывается защищенный метод ConnectorOnConnect, в котором создается сообщение и передается в метод ConnectorSendInMessage(Message).

    C#
    protected virtual void OnConnect()
    {
        SendInMessage(new ConnectMessage());
    }
  2. В методе ConnectorSendInMessage(Message) сообщение передается в одноименный метод адаптера.

    C#
    public void SendInMessage(Message message)
    {
        _inAdapter.SendInMessage(message);
    }
  3. В методе MessageAdapterSendInMessage(Message) адаптера выполняются дополнительные проверки. Если все нормально, то сообщение передается в метод MessageAdapterOnSendInMessage(Message) (см.ниже). Если сгенерирована ошибка, то создается создается новое входящее сообщение аналогичного типа, в свойство Error сообщения передается объект исключения. Это новое сообщение передается в метод SendOutMessage(Message), в котором будет сгенерировано событие появления нового входящего сообщения NewOutMessage, сигнализирующего об ошибке.

Входящие сообщения создаются при помощи метода MessageAdapterSendOutMessage(Message), в который передается объект сообщения. В этом методе генерируется событие нового входящего сообщения NewOutMessage. Далее это событие обрабатывается в базовом классе коннектора в защищенном методе ConnectorOnProcessMessage(Message), где в зависимости от ситуации сообщение преобразуется в соответствующий тип S# и генерируется событие коннектора, а также могут создаваться дополнительное исходящие сообщения.

Ниже описан процесс создания собственного адаптера для SmartCOM.

Пример создания адаптера сообщений SmartCom

  1. Создание класса адаптера.

    Вначале создаем класс адаптера сообщений SmartAdapter унаследованный от абстрактного класса MessageAdapter. В область пространств имен, наряду с другими ссылками, необходимо добавить SmartCOM3Lib. Также создадим переменную _smartCom для ссылки на объект библиотеки SmartCOM.

    C#
    public class SmartAdapter: MessageAdapter 
    {
        private SmartCOM3Lib.StServer _smartCom;
    }
  2. Конструктор адаптера.

    • В конструктор адаптера передается генератор идентификаторов транзакций, который будет использоваться для создания идентификаторов сообщений.

    • При помощи метода AddSupportedMessage(IMessageAdapter, MessageTypes) добавляем в массив SupportedMessages типы сообщений, которые будет поддерживать адаптер.

    C#
    public SmartAdapter(IdGenerator transactionIdGenerator)
                : base(transactionIdGenerator)
    {
        // Сообщения, связанные с маркет-данными
        this.AddSupportedMessage(MessageTypes.MarketData);
        this.AddSupportedMessage(MessageTypes.SecurityLookup);
    
        // Сообщения, связанные с транзакциями
        this.AddSupportedMessage(MessageTypes.OrderCancel);
        this.AddSupportedMessage(MessageTypes.OrderGroupCancel);
        this.AddSupportedMessage(MessageTypes.OrderPairReplace);
        this.AddSupportedMessage(MessageTypes.OrderRegister);
        this.AddSupportedMessage(MessageTypes.OrderReplace);
        // this.AddSupportedMessage(MessageTypes.OrderStatus);
        this.AddSupportedMessage(MessageTypes.Portfolio);
        this.AddSupportedMessage(MessageTypes.PortfolioLookup);
        this.AddSupportedMessage(MessageTypes.Position);
    }
  3. Свойства адаптера.

    Переопределяем свойства IsSupportNativePortfolioLookup и IsSupportNativeSecurityLookup. Поскольку SmartCOM поддерживает поиск портфелей и инструментов делаем, чтобы свойства возвращали true. Также добавляем свойства для логина, пароля, адреса и порта сервера SmartCOM.

    C#
    protected override bool IsSupportNativePortfolioLookup
    {
        get { return true; }
    }
    
    protected override bool IsSupportNativeSecurityLookup
    {
        get { return true; }
    }
    
     public string Address {set; get;}
     public ushort Port {set; get;}
     public string Login {set; get;}
     public string Password {set; get;}
  4. Метод OnSendInMessage(Message).

    Далее необходимо переопределить метод OnSendInMessage(Message). Как говорилось выше, в этот метод передаются все исходящие сообщения и для каждого типа сообщения нужно написать код, преобразующий сообщения в команды SmartCOM.

    При получении сообщения MessageTypesReset необходимо выполнить "обнуление" состояния и освободить ресурсы. По завершении этих операций нужно отправить исходящие сообщение ResetMessage.

    При поступлении сообщения MessageTypesConnect инициализируем переменную _smartCom, подписываемся на события SmartCOM и устанавливаем соединение при помощи метода Connect нативного API. В случае удачного соединения должно наступить событие StServer.Connected.

    C#
    protected override void OnSendInMessage(Message message)
    {
        switch (message.Type)
        {
             case MessageTypes.Reset:
             {
                  _lookupSecuritiesId = 0;
                  _lookupPortfoliosId = 0;
    
                  if (_smartCom != null)
                  {
                      try
                      {
                          DisposeSmartCom();
                      }
                      catch (Exception ex)
                      {
                          SendOutError(ex);
                      }
    
                      try
                      {
                          _smartCom.disconnect();
                      }
                      catch (Exception ex)
                      {
                          SendOutError(ex);
                      }
    
                       _smartCom = null;
                  }
    
                  SendOutMessage(new ResetMessage());
    
                  break;
            }
    
            case MessageTypes.Connect:
            {
                if (_smartCom != null)
                    throw new InvalidOperationException("smartCom != null");
    
                _smartCom = new StServer();
    
                _smartCom.Connected += smartCom_Connected;
                _smartCom.Disconnected += smartCom_Disconnected;
                _smartCom.AddPortfolio += smartCom_AddPortfolio;
                _smartCom.AddSymbol += smartCom_AddSymbol;
                _smartCom.SetPortfolio += smartCom_SetPortfolio;
                _smartCom.UpdatePosition += smartCom_UpdatePosition;
                _smartCom.AddTick += smartCom_AddTick;
                _smartCom.UpdateBidAsk += smartCom_UpdateBidAsk;
                _smartCom.UpdateQuote +=  smartCom_UpdateQuote;
                _smartCom.AddTrade += smartCom_AddTrade;
                _smartCom.UpdateOrder += smartCom_UpdateOrder;
                _smartCom.SetMyClosePos += smartCom_SetMyClosePos;
                _smartCom.SetMyOrder += smartCom_SetMyOrder;
                _smartCom.SetMyTrade += smartCom_SetMyTrade;
                _smartCom.OrderSucceeded += smartCom_OrderSucceeded;
                _smartCom.OrderFailed += smartCom_OrderFailed;
                _smartCom.OrderCancelFailed += smartCom_OrderCancelFailed;
                _smartCom.OrderCancelSucceeded += smartCom_OrderCancelSucceeded;
                _smartCom.OrderMoveFailed += smartCom_OrderMoveFailed;
                _smartCom.OrderMoveSucceeded += smartCom_OrderMoveSucceeded;
    
                _smartCom.connect(Address, Port, Login, Password);
    
                break;
            }
    
            case MessageTypes.Disconnect:
            {
                if (_smartCom == null)
                    throw new InvalidOperationException(LocalizedStrings.Str1856);
    
                _smartCom.disconnect();
    
                break;
            }
    
            case MessageTypes.OrderRegister:
                // TODO
                break;
    
            case MessageTypes.OrderCancel:
                // TODO
                break;
    
            case MessageTypes.OrderGroupCancel:
                 // TODO
                 break;
    
            case MessageTypes.OrderReplace:
                 // TODO
                 break;
    
            case MessageTypes.Portfolio:
                 // TODO
                 break;
    
            case MessageTypes.PortfolioLookup:
                 ProcessPortolioLookupMessage((PortfolioLookupMessage)message);
                 break;
    
            case MessageTypes.MarketData:
                 ProcessMarketDataMessage((MarketDataMessage)message);
                 break;
    
            case MessageTypes.SecurityLookup:
                 ProcessSecurityLookupMessage((SecurityLookupMessage)message);
                 break;
        }
    }
  5. Событие StServer.Connected.

    В обработчике события соединения нативного API необходимо послать исходящее сообщение ConnectMessage. При обработке этого сообщения в коде базового класса Connector будут проверены значения булевых свойств:

    Если значение true, то соответствующее сообщение будет послано. По умолчанию значения этих свойств зависят от наличия соответствующего типа сообщения в списке поддерживаемых сообщений. В нашем примере (см. Конструктор адаптера.) в этот список были добавлены типы MessageTypesSecurityLookup и MessageTypesPortfolioLookup, поэтому следует ожидать получения исходящих сообщений SecurityLookupMessage и PortfolioLookupMessage.

    C#
    private void smartCom_Connected()
    {
        SendOutMessage(new ConnectMessage());
    }
  6. Исходящие сообщения PortfolioLookupMessage и SecurityLookupMessage.

    При получении этих сообщений необходимо вызвать функции SmartCOM, запрашивающие портфели и инструменты соответственно. Вызов этих функций приведет к возникновению событий StServer.AddPortfolio (портфели) и StServer.AddSymbol (инструменты).

    C#
    // Запрашиваем список портфелей
    private void ProcessPortolioLookupMessage(PortfolioLookupMessage pfMsg)
    {
        if (_lookupPortfoliosId == 0)
        {
            _lookupPortfoliosId = pfMsg.TransactionId;
            _smartCom.GetPrortfolioList(); 
        }
        else
            SendOutError(LocalizedStrings.Str1868);
    }
    
    // Запрашиваем инструменты
    private void ProcessSecurityLookupMessage(SecurityLookupMessage message)
    {
        if (_lookupSecuritiesId == 0)
        {
            _lookupSecuritiesId = message.TransactionId;
            _smartCom.GetSymbols();
        }
        else
            SendOutError(LocalizedStrings.Str1854);
    }
  7. Событие StServer.AddPortfolio.

    В обработчике события StServer.AddPortfolio полученную информацию о портфеле необходимо преобразовать во входящее сообщение PortfolioMessage. А после получения всех портфелей необходимо послать входящее сообщение PortfolioLookupResultMessage. Обратите внимание, что в последнем случае свойству OriginalTransactionId нужно присвоить значение TransactionId соответствующего исходящего сообщения.

    C#
    private void smartCom_AddPortfolio(int row, int nrows, string portfolioName, string portfolioExch, SmartCOM3Lib.StPortfolioStatus portfolioStatus)
    {
        SendOutMessage(new PortfolioMessage
        {
            PortfolioName = portfolioName,
            BoardCode = PortfolioBoardCodes.TryGetValue(portfolioExch),
            ExtensionInfo = new Dictionary<object, object>
                        {
                            { "PortfolioStatus", portfolioStatus }
                        }
         });
    
         if ((row + 1) < nrows)
              return;
    
         SendOutMessage(new PortfolioLookupResultMessage { OriginalTransactionId = _lookupPortfoliosId });
         _lookupPortfoliosId = 0;
    }
  8. Событие StServer.AddSymbol.

    В обработчике события StServer.AddSymbol полученную информацию об инструменте необходимо преобразовать во входящее сообщение SecurityMessage. А после получения всех инструментов необходимо послать входящее сообщение SecurityLookupResultMessage. Обратите внимание, что свойству OriginalTransactionId обоих входящих сообщений нужно присвоить значение TransactionId соответствующего исходящего сообщения.

    C#
    private void smartCom_AddSymbol(int row, int rowCount, string smartId, string name, string secCode, string secClass, int decimals, int lotSize, double stepPrice, double priceStep,
                string isin, string board, System.DateTime expiryDate, double daysBeforeExpiry, double strike)
    {
        if (secCode.IsEmpty())
            secCode = smartId;
    
        var securityId = new SecurityId
        {
            SecurityCode = secCode,
            BoardCode = board,
            Native = smartId,
            Isin = isin
        };
    
        if (secClass.IsEmpty())
            secClass = board;
    
        DateTime? expDate = DateTime.FromOADate(0) == expiryDate ? (DateTime?)null : expiryDate;
    
    
        var secMsg = new SecurityMessage
        {
            PriceStep = priceStep.ToDecimal(),
            Decimals = decimals,
            Multiplier = lotSize,
            Name = name,
            ShortName = name,
            ExpiryDate = expDate == null ? (DateTimeOffset?)null : expDate.Value.ApplyTimeZone(TimeHelper.Moscow),
            ExtensionInfo = new Dictionary<object, object>
            {
                        { "Class", secClass }
            },
            OriginalTransactionId = _lookupSecuritiesId
         };
    
         if (secClass.CompareIgnoreCase("IDX"))
         {
             secMsg.SecurityType = SecurityTypes.Index;
    
             switch (secMsg.SecurityId.BoardCode)
             {
                 case "RUSIDX":
                      securityId.BoardCode = secCode.ContainsIgnoreCase("MICEX") || secCode.ContainsIgnoreCase("MCX") ? ExchangeBoard.Micex.Code : ExchangeBoard.Forts.Code;
                      break;
             }
         }
         else
         {
             var info = SecurityClassInfo.GetSecurityClassInfo(secClass);
    
             secMsg.SecurityType = info.Item1;
             securityId.BoardCode = info.Item2;
    
             if (ExchangeBoard.GetOrCreateBoard(info.Item2).IsMicex
                 &&
                 /* проверяем, что не началась ли трансляция правильных дробных шагов */
                        secMsg.PriceStep != null && secMsg.PriceStep == (int)secMsg.PriceStep)
             {
                 secMsg.PriceStep = 1m / 10m.Pow(secMsg.PriceStep.Value);
             }
         }
    
         secMsg.SecurityId = securityId;
    
         if (secMsg.SecurityType == SecurityTypes.Option)
         {
             var optionInfo = secMsg.Name.GetOptionInfo(ExchangeBoard.Forts);
    
             if (optionInfo != null)
             {
                 if (!secCode.IsEmpty())
                 {
                     var futureInfo = optionInfo.UnderlyingSecurityId.GetFutureInfo(secCode, ExchangeBoard.Forts);
                     if (futureInfo != null)
                            secMsg.UnderlyingSecurityCode = futureInfo.SecurityId.SecurityCode;
                 }
    
                 secMsg.ExpiryDate = optionInfo.ExpiryDate;
                 secMsg.OptionType = optionInfo.OptionType;
                 secMsg.Strike = optionInfo.Strike;
            }
         }
    
         SendOutMessage(secMsg);
    
         var stpPrice = stepPrice.ToDecimal();
    
         if (stepPrice != null)
         {
             SendOutMessage(
                 new Level1ChangeMessage
                 {
                            SecurityId = securityId,
                            ServerTime = CurrentTime.Convert(TimeHelper.Moscow),
                 }
                 .TryAdd(Level1Fields.StepPrice, stpPrice.Value));
         }
    
         if ((row + 1) < rowCount)
              return;
    
         SendOutMessage(new SecurityLookupResultMessage { OriginalTransactionId = _lookupSecuritiesId });
          _lookupSecuritiesId = 0;
    }
  9. Регистрация/отмена регистрации сделок.

    При вызове методов RegisterTrades(Security) или UnRegisterTrades(Security) будет получено исходящее сообщение MarketDataMessage.

    При обработке этого сообщения необходимо вызвать методы SmartCOM регистрирующий/отменяющий регистрацию получения сделок.

    Так как сообщение используется для работы со всеми типами рыночных данных, то для вычленения конкретного типа нужно использовать свойство DataType. Для сделок значение этого свойства равно MarketDataTypesTrades.

    После вызова метода StServer.ListenTicks сделки будут поступать в событии StServer.AddTick.

    C#
    private void ProcessMarketDataMessage(MarketDataMessage mdMsg)
    {
    
        // Получаем код инструмента, используемый в SmartCom
        var smartId = (string)mdMsg.SecurityId.Native;
    
        if (smartId.IsEmpty())
            throw new InvalidOperationException(LocalizedStrings.Str1853Params.Put(mdMsg.SecurityId));
    
        switch (mdMsg.DataType)
        {
            case MarketDataTypes.Level1:
            {
                 //TODO
                 break;
            }
            case MarketDataTypes.MarketDepth:
            {
                 //TODO
                 break;
            }
            case MarketDataTypes.Trades:
            {
                if (mdMsg.From == null)
                {
                    if (mdMsg.IsSubscribe)
                        _smartCom.ListenTicks(smartId);
                    else
                        _smartCom.CancelTicks(smartId);
               }
               else
               {
                    //TODO
               }
    
               break;
            }
            case MarketDataTypes.CandleTimeFrame:
            {
                 //TODO
                 break;
            }
            default:
            {
                SendOutMarketDataNotSupported(mdMsg.TransactionId);
                return;
         }
    }
  10. Событие StServer.AddTick.

    В обработчике события StServer.AddTick полученную информацию о сделке необходимо преобразовать во входящее сообщение ExecutionMessage. Обратите внимание, что сообщения ExecutionMessage используются как для сделок, так и для заявок. Поэтому в сообщении уточняется, что оно относится к сделке - ExecutionType = ExecutionTypesTick.

    C#
    private void smartCom_AddTick(string smartId, System.DateTime time, double price, double volume, string tradeId, SmartCOM3Lib.StOrder_Action action)
    {
        SendOutMessage(CreateTrade(smartId, time, price.ToDecimal(), volume.ToDecimal(), tradeId.To<long>(), action));
    }
    
    private static ExecutionMessage CreateTrade(string smartId, DateTime time, decimal? price, decimal? volume, long tradeId, SmartCOM3Lib.StOrder_Action action)
    {
        return new ExecutionMessage
        {
             SecurityId = new SecurityId { Native = smartId },
             TradeId = tradeId,
             TradePrice = price,
             Volume = volume,
             OriginSide = action.ToSide(),
             ServerTime = time.ApplyTimeZone(TimeHelper.Moscow),
             ExecutionType = ExecutionTypes.Tick
       };
    }