English 中文 Español Deutsch 日本語 Português
preview
Разработка MQTT-клиента для MetaTrader 5: методология TDD (финал)

Разработка MQTT-клиента для MetaTrader 5: методология TDD (финал)

MetaTrader 5Интеграция | 26 августа 2024, 13:57
511 1
Jocimar Lopes
Jocimar Lopes
"Наша цель — всегда работать на самом высоком уровне абстракции, который допускает поставленная задача и сформулированные ограничения" (Бьёрн Страуструп, "Программирование. Принципы и практика использования C++")

Введение

Поскольку это последняя статья серии, приведем краткий обзор всех частей.

В первой части мы узнали, что MQTT — это протокол обмена сообщениями, основанный на модели взаимодействия "издатель/подписчик" (pub/sub), который может быть полезен в торговле, позволяя пользователю обмениваться любыми данными в режиме реального времени: сделками, информацией о счетах и статистическими данными для машинного обучения в виде обычного текста, XML, JSON или двоичных данных, включая изображения. MQTT — это легкий протокол, устойчивый к нестабильности или сбоям сети и не зависящий от контента. Кроме того, этот протокол проверен в боевых условиях. Располагает открытым стандартом, поддерживаемым OASIS. Две наиболее используемые его версии, предыдущая 3.1.1 и текущая 5.0, входят в число наиболее используемых протоколов для подключения практически неограниченного количества устройств в так называемом Интернете вещей. MQTT можно использовать в любом сценарии, где требуется обмен данными в реальном времени между несвязанными друг с другом устройствами.

Существует множество брокеров MQTT, доступных для разработки и тестирования, как с открытым исходным кодом, так и коммерческих, а также множество клиентов MQTT практически для любого современного языка программирования. Вы можете найти их в списке программного обеспечения MQTT, включающего брокеров, библиотеки и инструменты.

Во второй статье серии мы описали организацию кода для клиента и прокомментировали некоторые первоначальные решения по разработке, такие как объектно-ориентированная парадигма. Большая часть этого кода изменилась в ходе нашего первого крупного рефакторинга, но функциональность осталась прежней. В этой статье мы также описали некоторые соединения, которые мы установили с локальным брокером, работающим в подсистеме Windows для Linux (WSL), и увидели, что наш класс CONNECT генерирует плохие пакеты. Мы улучшили его и сообщили об этом в следующей статье.

Третья часть серии была посвящена рабочим характеристикам протокола и их связи с флагами CONNECT. Мы описали семантику этих флагов и то, как мы их устанавливаем. Мы также сделали несколько заметок о практике разработки через тестирование, которую мы используем в этом проекте. Наконец, мы объяснили, как мы тестируем защищенные методы наших классов.

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

В пятой части серии были рассмотрены пакет управления PUBLISH и его уникальные (незарезервированные) фиксированные флаги заголовка. Мы подробно показали, как работают эти фиксированные флаги заголовка PUBLISH на уровне битов. Мы выделили характеристики различных уровней качества обслуживания MQTT (QoS 0, QoS 1 и QoS 2) с помощью нескольких наглядных диаграмм, показывающих обмен пакетами между клиентом и брокером в каждом из уровней QoS.

В шестой статье мы провели наш первый серьезный рефакторинг кода. Мы изменили схему наших классов пакета управления и удалили дублирующиеся функции и устаревший тестовый код. Статья в основном представляет собой документацию этих изменений с некоторыми заметками о пакете управления PUBACK. В статье описывается семантика кодов причин PUBACK с соответствующими им строками причин.

Наконец, в седьмой и последней части я хочу поделиться с вами рабочим кодом, который призван решить очень распространенную проблему трейдеров при построении сигналов индикаторов для использования в советниках: отсутствие необходимого символа для индикатора на торговом счете.

Мы предлагаем одно из возможных решений с использованием пользовательских символов и пары клиентов MQTT, работающих как службы в терминале MetaTrader 5. Несмотря на то, что демонстрационный код чрезмерно упрощен и работает на одном экземпляре терминала, благодаря основной характеристике самого протокола MQTT — разделению отправителя и получателя посредством посредничества "брокера" — это решение можно расширить для поддержки любого количества экземпляров устройств и символов.

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

В последующих описаниях мы используем слова НЕОБХОДИМО/ДОЛЖНО (MUST) и МОЖЕТ/МОЖНО (MAY) так, как они используются в Стандарте OASIS, который, в свою очередь, использует их, как описано в документе IETF RFC 2119.

Также, если не указано иное, все цитаты взяты из Стандарта OASIS.


Потребности трейдеров

Предположим, вы трейдер, специализирующийся на криптовалюте. Вы узнали, что существует устойчивая отрицательная корреляция между индексом S&P 500 и малоизвестным мемкоином под названием AncapCoin. Вы заметили, что когда S&P 500 растет, AncapCoin падает, и наоборот. Эти знания дают вам преимущество на рынках, и вы зарабатываете деньги, торгуя AncapCoin, учитывая его отрицательную корреляцию с S&P 500. Чтобы максимизировать прибыль, вы хотите автоматизировать свои операции и в конечном итоге запустить своего советника круглосуточно на виртуальном выделенном сервере (VPS). Все, что вам нужно, — это стандартный индикатор S&P 500 для поддержки решений вашего советника о покупке и продаже.

Но AncapBroker также специализируется на криптовалюте. Поэтому среди его символов нет S&P 500. А брокеры, имеющие S&P 500, не предлагают AncapCoin. Как можно запустить индикатор S&P 500 на вашем торговом счете, предоставленном AncapBroker?

Несмотря на то, что AncapBroker и AncapCoin выдуманы, этот сценарий вовсе не такой уж фантастический. Обычно трейдер, работающий с индексами, оценит наличие индикатора, составленного на основе выбранных акций, которые не предлагаются брокером, предлагающим индексы, и наоборот. Рассмотрим товары, доступные для торговли на централизованной бирже, и соответствующие им CFD, доступные для торговли у CFD-брокеров, и вы столкнетесь с типичным несоответствием, когда трейдер имеет доступ к данным, но данные поступают от разных поставщиков. Для ручной торговли эта проблема не актуальна. С помощью отдельного монитора или даже отдельного окна на том же мониторе вы можете следить за необходимым символом. Но когда дело доходит до автоматизации, даже когда оба поставщика предлагают MetaTrader 5, для обычного трейдера очень сложно, если не сказать практически невозможно, иметь котировки в режиме реального времени там, где они необходимы для принятия торговых решений - на торговом счете.

Если вы разработчик, который получил такое техническое задание от клиента, вы можете рассмотреть шаблон обмена сообщениями "издатель/подписчик" (pub/sub) как способ решения задачи. Среди доступных открытых и хорошо поддерживаемых спецификаций pub/sub MQTT, вероятно, является одной из самых простых, дешевых и надежных. MQTT был разработан именно для удовлетворения требований, изложенных выше в нашем вымышленном сценарии, а именно - для сбора и распространения данных из нескольких источников, возможно, разбросанных географически, в режиме реального времени и с минимальными сетевыми издержками.

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

В торговом счете мы:

  1. Создаем пользовательский символ, представляющий наш отсутствующий индекс S&P 500
  2. Пишем MQTT-сервис SUBSCRIBE, чтобы получать котировки S&P 500 от MQTT-брокера
  3. Обновляем наш пользовательский символ, представляющий S&P 500, с помощью функций MQL5

В учетной записи источника данных мы:

  1. Пишем сервис PUBLISH MQTT для сбора котировок S&P 500 и отправки их MQTT-брокеру

Обратите внимание, что для простоты и краткости мы говорим об одном торговом счете и одном счете источника данных, но количество этих счетов теоретически не ограничено. На практике количество подключенных учетных записей с обеих сторон ограничивается только физическими пределами устройств (память, процессор, пропускная способность сети и т. д.) и ограничениями, налагаемыми вашим MQTT-брокером.

Также имейте в виду, что моя цель не заключается в предоставлении вам готового решения. Моя цель — показать вам основные шаги, необходимые для одной из возможных реализаций,и, конечно же, подогреть ваш интерес к потенциальному использованию шаблона публикации/подписки MQTT для ваших торговых операций или для предоставления гибко настроенных приложений вашим клиентам. Как видите, мои цели выходят далеко за рамки копирования котировок мемкоина.


Создаем пользовательский символ

Пользовательский символ — это символ, который вы создаете с желаемыми или требуемыми характеристиками и обновляете его с учетом предоставленных вами котировок. Поскольку вы контролируете спецификации и котировки, пользовательские символы полезны для создания составных индексов, а также для тестирования советников и индикаторов.

Вы можете создать пользовательский символ с помощью графического интерфейса редактора MetaTrader 5 или программно - с помощью специальных функций MQL5. В документации есть подробные инструкции о том, как создавать, настраивать и применять пользовательские символы программно или через графический интерфейс. Для наших целей достаточно графического интерфейса.

В терминале MetaTrader 5, где размещен ваш торговый счет, кликните "Вид" > "Символы" > "Создать символ".

MetaTrader 5. Создание пользовательского символа с использованием графического интерфейса

Рис. 01. MetaTrader 5. Создание пользовательского символа с использованием графического интерфейса

В появившемся окне в поле "Скопировать из:" выберите символ S&P 500, чтобы создать пользовательский символ на основе реального индекса. Введите имя и краткое описание вашего пользовательского символа.

Настройка пользовательских характеристик символа с использованием графического интерфейса

Рис. 02. MetaTrader 5. Настройка пользовательских характеристик символа с помощью графического интерфейса

Внимательный читатель, возможно, заметил, что мы создаем свой символ на торговом счете, но согласно нашему примеру, у этого счета нет символа S&P 500, который можно было бы использовать в качестве модели для нашего пользовательского символа. Мы должны находиться в учетной записи источника данных. В конце концов, то, что мы здесь делаем, происходит именно потому, что на нашем торговом счете нет индекса S&P 500. Да, вы правы! В реальной ситуации вам нужно будет заполнить эти спецификации символов в соответствии с вашими потребностями, возможно, вручную скопировав параметры S&P 500. Я здесь упрощаю пример, поскольку создание пользовательских символов не входит в нашу задачу. Вместо этого мы заинтересованы в подключении аккаунтов через MQTT. Если вам необходимо настроить символ для вашей реальной ситуации, обратитесь к документации, ссылка на которую приведена выше.

После нажатия на ОК ваш новый символ должен появиться в древовидном списке слева.

Проверка дерева символов после создания пользовательского символа с использованием графического интерфейса

Рис. 03. MetaTrader 5. Проверка дерева символов после создания пользовательского символа с использованием графического интерфейса

Затем добавьте его в "Обзор рынка", чтобы он был доступен для визуализации на графике. Этот шаг необходим для обновления тиков. 

"Функция CustomTicksAdd работает только для пользовательских символов, открытых в окне "Обзор рынка". Если символ не выбран в "Обзоре рынка", то для вставки тиков необходимо использовать CustomTicksReplace" (Справочник MQL5)

MetaTrader 5. Проверка окна "Обзор рынка" после создания пользовательского символа с использованием графического интерфейса

Рис. 04. MetaTrader 5. Проверка окна "Обзор рынка" после создания пользовательского символа с использованием графического интерфейса

В окне "Обзор рынка" вы увидите, что котировок пока нет. Итак, давайте подпишем этот аккаунт на MQTT-брокера, который будет отправлять котировки в реальном времени для обновления нашего недавно созданного пользовательского символа MySPX500.


Создаем MQTT-службу SUBSCRIBE

На текущем этапе наш клиент может оформить подписку с QoS 0 и QoS 1, но для обновления котировок/тиков мы считаем, что QoS 0 будет достаточно, поскольку в данном контексте потеря тика не является критической. Мы отправляем один тик каждые 500 мс, поэтому, если какой-либо из тиков будет потерян, его место будет немедленно занято следующим.

Мы начинаем наш код для службы подписки с входных параметров для хоста и порта брокера. Не забудьте заменить их фактическими данными вашего брокера. В следующем разделе приведены некоторые заметки о том, как настроить среду разработки/тестирования с помощью локального брокера.

#property service
//--- input parameters
input string   host = "172.20.106.92";
input int      port = 80;

Далее мы объявляем некоторые глобальные переменные для наших объектов из классов Connect и Subscribe. Нам понадобятся эти переменные для удаления (очистки) при остановке или возврате из ошибки.

//--- global vars
int skt;
CConnect *conn;
CSubscribe *sub;

"Все объекты, созданные выражением object_pointer=new Class_name, должны быть затем удалены оператором delete(object_pointer)" (Справочник MQL5).

Прямо в методе OnStart() нашего сервиса мы настраиваем объект соединения как соединение с чистым стартом (то есть у нас нет сеанса брокера для этого соединения) с внушительным периодом Keep Alive, чтобы избежать необходимости отправлять периодические запросы ping во время разработки, устанавливаем идентификатор клиента и, наконец, создаем наш пакет CONNECT. Обязательно установите идентификатор клиента, отличный от того, который используется в вашей службе publish.

   uchar conn_pkt[];
   conn = new CConnect(host, port);
   conn.SetCleanStart(true);
   conn.SetKeepAlive(3600);
   conn.SetClientIdentifier("MT5_SUB");
   conn.Build(conn_pkt);

В том же методе OnStart() мы также настраиваем и создаем наш пакет subscribe с его фильтром тем (Topic Filter). Для ясности и интуитивности в качестве фильтра тем мы используем выбранное нами имя для нашего пользовательского символа. Конечно, это опциональное условие, при условии, что вы используете тот же фильтр тем в своей службе publish.

  uchar sub_pkt[];
  sub = new CSubscribe();
  sub.SetTopicFilter("MySPX500");
  sub.Build(sub_pkt);

Наконец, мы вызываем функции для последовательной отправки обоих пакетов, делегируя обработку ошибок этим функциям и возвращая -1, если что-то пойдет не так с нашим запросом на подписку.

if(SendConnect(host, port, conn_pkt) == 0)
     {
      Print("Client connected ", host);
     }
   if(!SendSubscribe(sub_pkt))
     {
      return -1;
     }

Функция SendConnect содержит всего несколько строк кода, связанного с MQTT. Большая часть из них связана с сетями/сокетами, и мы не будем здесь их подробно описывать. Вы можете обратиться к разделу "Сетевые функции" справочника. Если вы хотите глубже понимать сетевые функции MQL5, мы настоятельно рекомендуем вам прочитать соответствующие главы в книге "Программирование на MQL5 для трейдеров", где вы найдете подробные объяснения и полезные примеры простого и безопасного соединения (TLS) с MQL5.

В книге вы найдете информацию, подобную приведенному ниже отрывку, который помог нам выявить и обойти непостоянное и недетерминированное поведение нашей функции (см. комментарии к коду в прикрепленных файлах).

"Программисты, знакомые с системными API сокетов в Windows/Linux, знают, что значение 0 также может быть нормальным состоянием, когда во внутреннем буфере сокета нет входящих данных. Однако данная функция в MQL5 ведет себя иначе. При пустом системном буфере сокета, она спекулятивно возвращает 1, откладывая реальную проверку на доступность данных на следующий вызов одной из функций чтения. В частности, данная ситуация с фиктивным результатом 1 байт возникает, как правило, при первом вызове функции на сокете, когда приемный внутренний буфер еще пуст" ("Программирование на MQL5 для трейдеров").

Для стороны MQTT все, что мы делаем в SendConnect, — это проверяем ответ CONNACK от брокера, а также значение связанного кода причины.

if(rsp[0] >> 4 != CONNACK)
     {
      Print("Not Connect acknowledgment");
      CleanUp();
      return -1;
     }
   if(rsp[3] != MQTT_REASON_CODE_SUCCESS)  // Connect Return code (Connection accepted)
     {
      Print("Connection Refused");
      CleanUp();
      return -1;
     }

Как видите, в обоих случаях мы возвращаем -1 в случае ошибки после очистки динамических указателей на объекты нашего класса.

Такая же доля кода, связанного с сетью/MQTT, применяется к функции SendSubscribe. После проверки ответа SUBACK от брокера и соответствующего ему кода причины мы удаляем динамические указатели объектов класса, если возникает какая-либо ошибка.

if(((rsp[0] >> 4) & SUBACK) != SUBACK)
     {
      Print("Not Subscribe acknowledgment");
     }
   else
      Print("Subscribed");
   if(rsp[5] > 2)  // Suback Reason Code (Granted QoS 2)
     {
      Print("Subscription Refused with error code %d ", rsp[4]);
      CleanUp();
      return false;
     }

В бесконечном цикле мы ждем сообщений брокера и считываем их с помощью статического метода класса Publish.

msg += CPublish().ReadMessageRawBytes(inpkt);
               //printf("New quote arrived for MySPX500: %s", msg);
               //UpdateRates(msg);
               printf("New tick arrived for MySPX500: %s", msg);
               UpdateTicks(msg);

Обратите внимание, что мы оставили закомментированный код разработки для обновления котировок вместо тиков. Если вы хотите протестировать этот способ, вы можете раскомментировать эти строки. Имейте в виду, что при обновлении только котировок, но не тиков, некоторая информация может отсутствовать в окне "Обзор рынка" и на графике. Но это разумная альтернатива, если вы хотите сократить потребление оперативной памяти, процессорного времени и пропускной способности и вас больше интересуют данные для автоматизации торговли, а не визуальные эффекты.

Функция UpdateRates работает совместно со своим аналогом в службе publish. Мы оплачиваем стоимость преобразования строки to/from с обеих сторон, пока разрабатываем наши пользовательские свойства MQTT и обеспечиваем более надежный обмен бинарными данными. Это главный приоритет в нашем импровизированном плане действий.

void UpdateTicks(string new_ticks)
  {
   string new_ticks_arr[];

   StringSplit(new_ticks, 45, new_ticks_arr);

   MqlTick last_tick[1];

   last_tick[0].time          = StringToTime(new_ticks_arr[0]);
   last_tick[0].bid           = StringToDouble(new_ticks_arr[1]);
   last_tick[0].ask           = StringToDouble(new_ticks_arr[2]);
   last_tick[0].last          = StringToDouble(new_ticks_arr[3]);
   last_tick[0].volume        = StringToInteger(new_ticks_arr[4]);
   last_tick[0].time_msc      = StringToInteger(new_ticks_arr[5]);
   last_tick[0].flags         = (uint)StringToInteger(new_ticks_arr[6]);
   last_tick[0].volume_real   = StringToDouble(new_ticks_arr[7]);

   if(CustomTicksAdd("MySPX500", last_tick) < 1)
     {
      Print("Update ticks failed: ", _LastError);
     }
  }

После запуска службы subscribe вы должны увидеть на вкладке "Эксперты" примерно следующее.

MetaTrader 5. Ошибка 5270 на вкладке "Эксперты"

Рис. 05. MetaTrader 5. Ошибка 5270 на вкладке "Эксперты"

Наша служба subscribe пока разговаривает сама с собой. Давайте исправим это, запустив нашего MQTT-брокера.


Настраиваем местного брокера для разработки и тестирования

Наша среда разработки использует подсистему Windows для Linux (WSL) на компьютере под управлением Windows. Если все, что вам нужно, это запустить примеры, вы можете запустить в цикле и клиент, и брокер на одной и той же машине, при условии, что вы используете разные идентификаторы клиентов для услуг publish и subscribe. Но если вам требуется настроить среду разработки не только для запуска примеров, я рекомендую настроить отдельную машину. Как вы, вероятно, знаете, при разработке клиент-серверных приложений (а шаблон pub/sub может быть включен в эту архитектуру) считается хорошей практикой запускать каждую сторону на своем хосте. Благодаря этому вы сможете раньше устранить неполадки подключения, аутентификации и другие сетевые проблемы.

Настройка с WSL довольно проста. Я подробно описал установку, активацию и настройку WSL в другой статье, опубликованной год назад. Ниже приведены некоторые советы по использованию брокера Mosquitto на WSL. Это мелкие детали, которые облегчили нам жизнь и, возможно, вы также найдете их полезными.

  • Если вы активируете WSL с настройками по умолчанию и установите Mosquitto рекомендуемым простым способом, вы, вероятно, установите его с помощью менеджера пакетов и запустите как службу Ubuntu. То есть Mosquitto будет запускаться автоматически при запуске оболочки WSL. Это хорошо и удобно для регулярного использования, но для разработки мы рекомендуем остановить службу Mosquitto и перезапустить ее вручную через командную строку с флагом verbose (-v). Это позволит избежать необходимости использования команды tail для отслеживания логов, поскольку Mosquitto будет работать в фоновом режиме и перенаправлять все логи в STDOUT. Кроме того, в логах не содержится вся информация, которую вы получите при запуске с флагом verbose.

Подсистема Windows для Linux — остановка сервера Mosquitto и перезапуск с флагом verbose

Рис. 06. Подсистема Windows для Linux — остановка сервера Mosquitto и перезапуск с флагом verbose

  • Помните, что вы должны включить имя хоста WSL в разрешенные URL-адреса для сетевого взаимодействия в терминале MetaTrader 5.

Подсистема Windows для Linux - получение имени хоста WSL

Рис. 07. Подсистема Windows для Linux - получение имени хоста WSL

MetaTrader 5. Включение разрешенных URL-адресов в меню параметров терминала

Рис. 08. MetaTrader 5. Включение разрешенных URL-адресов в меню параметров терминала

  • В целях повышения безопасности последние версии Mosquitto по умолчанию разрешают только локальные подключения, то есть подключения с одного и того же компьютера. Чтобы подключиться с другой машины (а ваш компьютер с Windows в данном контексте считается другой машиной), вам необходимо включить однострочный прослушиватель для порта 1883 (или другого порта по вашему выбору) в файл Mosquitto.conf.

Подсистема Windows для Linux - настройка сервера Mosquitto для прослушивания порта 1883

Рис. 09. Подсистема Windows для Linux - настройка сервера Mosquitto для прослушивания порта 1883

  • Наконец, помните, что Mosquitto по умолчанию будет работать на порту 1883 для соединений, не использующих TLS. Терминал Metatrader 5 допускает подключения только к портам 80 (HTTP) и 443 (HTTPS). Поэтому вам необходимо перенаправить трафик с порта 80 на порт 1883. Это можно сделать с помощью короткой однострочной команды, если вы установите утилиту Linux redir. Вы также можете установить ее с помощью менеджера пакетов.

Подсистема Windows для Linux. Перенаправление портов с помощью утилиты Redir

Рис. 10. Подсистема Windows для Linux. Перенаправление портов с помощью утилиты Redir

  • Если вы забудете включить имя хоста WSL в список разрешенных URL-адресов или перенаправить порты, соединение будет отклонено, и, вероятно, на вкладке "Эксперты" появится сообщение об ошибке.

MetaTrader 5. Ошибка 5273

Рис. 11. MetaTrader 5. Ошибка 5273 - не удалось отправить/получить данные из сокета

Даже если вы не знакомы с Linux, настройка не займет более десяти-пятнадцати минут, если все пойдет хорошо. После этого вы сможете проверить, работает ли ваша служба subscribe так, как задумано.

Служба subscribe MQTT запущена

Рис. 12. Служба subscribe MQTT запущена



Создаем MQTT-службу PUBLISH

Служба publish имеет ту же структуру, что и subscribe, поэтому я не буду повторяться. Еще раз напоминаю о необходимости использовать разные идентификаторы клиента для publish и subscribe. Эту особенность легко упустить из виду при копировании-вставке. В итоге можно потратить время на отладку несуществующей ошибки, из-за которой Mosquitto не доставляет сообщения должным образом.)

   uchar conn_pkt[];
   conn = new CConnect(host, port);
   conn.SetCleanStart(true);
   conn.SetKeepAlive(3600);
   conn.SetClientIdentifier("MT5_PUB");
   conn.Build(conn_pkt);

Служба выполняется в непрерывном цикле до тех пор, пока не будет остановлена. Не забудьте установить тот же фильтр тем, который вы используете в службе subscribe. Если вы хотите обновить котировки (вместо тиков) раскомментируйте назначение полезной нагрузки GetRates (и закомментируйте назначение GetLastTick). 

   do
     {
      uchar pub_pkt[];
      pub = new CPublish();
      pub.SetTopicName("MySPX500");
      //string payload = GetRates();
      string payload = GetLastTick();
      pub.SetPayload(payload);
      pub.Build(pub_pkt);
      delete(pub);
     //ArrayPrint(pub_pkt);
      if(!SendPublish(pub_pkt))
        {
         return -1;
         CleanUp();
        }
      ZeroMemory(pub_pkt);
      Sleep(500);
     }
   while(!IsStopped());

Замечания, которые мы сделали относительно соотношения сетевого кода и специфичного для MQTT кода в сервисе subscribe, применимы и здесь. Нам даже не нужно проверять наличие PUBACK, потому что мы его не получим, так как используем QoS 0. Итак, вопрос только в формировании пакетов, их подключении и отправке.

Стоит отметить, что в сервисе publish мы также платим цену за преобразование строк, по крайней мере до тех пор, пока наши пользовательские свойства не будут полностью реализованы, и мы не сможем с уверенностью обмениваться бинарными данными.

string GetLastTick()
  {
   MqlTick last_tick;
   if(SymbolInfoTick("#USSPX500", last_tick))
     {
      string format = "%G-%G-%G-%d-%I64d-%d-%G";
      string out;
      out = TimeToString(last_tick.time, TIME_SECONDS);
      out += "-" + StringFormat(format,
                                last_tick.bid, //double
                                last_tick.ask, //double
                                last_tick.last, //double
                                last_tick.volume, //ulong
                                last_tick.time_msc, //long
                                last_tick.flags, //uint
                                last_tick.volume_real);//double
      Print(last_tick.time,
            ": Bid = ", last_tick.bid,
            " Ask = ", last_tick.ask,
            " Last = ", last_tick.last,
            " Volume = ", last_tick.volume,
            " Time msc = ", last_tick.time_msc,
            " Flags = ", last_tick.flags,
            " Vol Real = ", last_tick.volume_real
           );
      Print(out);
      return out;
     }
   else
      Print("Failed to get rates for #USSPX500");
   return "";
  }

Запустив службу publish в терминале MetaTrader 5, вы должны увидеть на вкладке "Эксперты" что-то вроде следующего.

Служба publish MQTT запущена и подключена

Рис. 13. Служба publish MQTT запущена и подключена

Вы можете подписаться на тему брокера Mosquitto и проверить флаг verbose.

Подсистема Windows для Linux, отображающая журналирование сервера Mosquitto с флагом verbose

Рис. 14. Подсистема Windows для Linux, отображающая журналирование сервера Mosquitto с флагом verbose

Если сообщение было успешно получено, вы увидите его на вкладке подписок.

Подсистема Windows для Linux - вывод утилиты mosquitto_sub

Рис. 15. Подсистема Windows для Linux - вывод утилиты mosquitto_sub


Обновляем пользовательский символ

После того, как обе службы установлены и каждая из них проверена вашим брокером, пришло время запустить их в терминале MetaTrader 5 и увидеть результаты работы.

Окно "Навигатор" MetaTrader 5 с запущенными MQTT-службами Publish и Subscribe

Рис. 16. Окно "Навигатор" MetaTrader 5 с запущенными MQTT-службами Publish и Subscribe

Если все работает как надо, вы увидите примерно следующее на вкладке "Тики" окна "Обзор рынка".

 Вкладка "Тики" в окне "Обзор рынка" MetaTrader 5 с обновлениями тиков пользовательских символов

Рис. 17. Вкладка "Тики" в окне "Обзор рынка" MetaTrader 5 с обновлениями тиков пользовательских символов

На графике вашего пользовательского символа также должны отражаться обновления тиков.

Рыночный график MetaTrader 5 Market Graph с обновлениями тиков пользовательских символов

Рис. 18. Рыночный график MetaTrader 5 Market Graph с обновлениями тиков пользовательских символов

На вкладке "Эксперты" вы увидите более подробный вывод с отладочной информацией. Там вы можете увидеть, среди прочей информации, вывод функции PrintArray для обмениваемых пакетов. Этот вывод может избавить от необходимости применять анализатор пакетов, например Wireshark, для проверки их содержимого.

Вкладка "Эксперты". Журналирование MQTT-сервисов для разработки и отладки

Рис. 19. Журналирование MQTT-сервисов для разработки и отладки



Заключение

В статье представлен рабочий код для обмена котировками в реальном времени между брокерами и счетами через MQTT. Хотя демо-версия работает на том же экземпляре MetaTrader 5 и на том же устройстве, она демонстрирует гибкость и надежность MQTT. MQTT-клиент все еще находится в стадии разработки. Моя цель — выпустить полностью рабочую версию к июню. Для этого необходимо закончить реализацию QoS_2 и свойства пользователя. Я планирую доработать код и опубликовать его на GitHub к концу апреля.

Мы приветствуем участников нашего проекта с открытым исходным кодом, независимо от их опыта работы с MQL5. Наш подход к разработке через тестирование гарантирует отсутствие ошибок даже для неспециалистов. Начав с базовых тестов, мы постепенно продвигались вперед по документации MQL5. Мы стремимся совершенствовать наш клиент до тех пор, пока он не будет соответствовать всем стандартам MQTT.

Присоединяйтесь к нам, даже если у вас базовые навыки. Ваш вклад очень ценен. Добро пожаловать на борт!

Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/14677

Прикрепленные файлы |
mqtt-headers.zip (23.78 KB)
mqtt-services.zip (3.51 KB)
mqtt-tests.zip (19.52 KB)
Последние комментарии | Перейти к обсуждению на форуме трейдеров (1)
Реter Konow
Реter Konow | 27 авг. 2024 в 09:28
Очень хорошая и интересная статья. Узнал много нового и полезного. Откровенно говоря, такое бывает не часто, но это тот самый случай. Буду внимательно изучать остальные ваши статьи.
Разработка системы репликации (Часть 45): Проект Chart Trade (IV) Разработка системы репликации (Часть 45): Проект Chart Trade (IV)
Главное в этой статье — представление и объяснение класса C_ChartFloatingRAD. У нас есть индикатор Chart Trade, который работает довольно интересным образом. Как вы могли заметить, у нас на графике все еще достаточно небольшое количество объектов, и тем не менее, мы получили ожидаемое функционирование. Значения, присутствующие в индикаторе, можно редактировать. Вопрос в том, как это возможно? В этой статье все начнет проясняться.
Введение в MQL5 (Часть 6): Функции для работы с массивами для начинающих (II) Введение в MQL5 (Часть 6): Функции для работы с массивами для начинающих (II)
Продолжим изучение возможностей языка программирования MQL5. В этой статье, предназначенной для начинающих, мы продолжим изучать функции для работы массивами, перейдя к более сложным концепциям, которые обязательно пригодятся при разработке эффективных торговых стратегий. В этот раз познакомимся с функциями ArrayPrint, ArrayInsert, ArraySize, ArrayRange, ArrarRemove, ArraySwap, ArrayReverse и ArraySort. Функции массивы знать обязательно, если вы хотите достичь высокого уровня в области алготрейдинга. Это очередная глава на пути к мастерству.
Поиск с запретами — Tabu Search (TS) Поиск с запретами — Tabu Search (TS)
В статье рассматривается алгоритм табу-поиска — один из первых и наиболее известных методов метаэвристики. Мы подробно разберем, как работает алгоритм, начиная с выбора начального решения и исследования соседних вариантов, с акцентом на использование табу-листа. Статья охватывает ключевые аспекты алгоритма и его особенности.
Факторизация матриц: основы Факторизация матриц: основы
Поскольку цель здесь дидактическая, мы будем действовать максимально просто. То есть мы будем реализовывать только то, что нам необходимо: умножение матриц. Вы сегодня увидите, что этого достаточно для симуляции умножения матрицы на скаляр. Самая существенная трудность, с которой многие сталкиваются при реализации кода с использованием матричной факторизации, заключается в следующем: в отличие от скалярной факторизации, где почти во всех случаях порядок факторов не меняет результат, при использовании матриц это не так.