Блог им. elektroyar

"Мост" между MetaTrader и программой через socket

В жизни бывают такие моменты, когда очень хочется торговать из программы на С++, но по каким-то причинам у брокера нет API, зато есть MetaTrader. Конечно, можно просто писать код на MQL4/MQL5, на этом урезанном варианте-мутанте Си и С++, но мне как-то не в кайф это делать. Поэтому я решил сделать «мост» между MetaTrader и программой через socket. Встречайте — MT-Bridge
"Мост" между MetaTrader и программой через socket

На данный момент MT-Bridge позволяет только передавать поток котировок в программу с заданной частотой + добавлена инициализация исторических данных. Пока мне этого достаточно, но возможно в будущем функционал MT-Bridge будет расширен. Поэтому извиняйте, если здесь вы не нашли полноценного функционала, что есть то есть пока. Библиотека для подключения к советнику написана на С++11 и зависит от boost.asio, но нужны только файлы-заголовки. Вот github репозиторий с советником и библиотекой. Передача данных реализована через сокеты, советник является клинетом, а программа на С++ — сервером. Данные передаются через сокет в бинарном виде. 

Как пользоваться


В терминале MetTrader нужно добавить советник MT-Bridge на график. Советнику нужно разрешить использовать dll.

Настройки советника:
  • Server hostname or IP address  — Имя хоста, по умолчанию localhost
  • Server port  — Порт сервера, по умолчанию 5555
  • Array of used currency pairs  — Массив имен валютны пар, данные которых должен передавать советник. По умолчанию представлен следующий список: EURUSD,USDJPY,GBPUSD,USDCHF,USDCAD,EURJPY,AUDUSD,NZDUSD, EURGBP,EURCHF,AUDJPY,GBPJPY,CHFJPY,EURCAD,AUDCAD,CADJPY, NZDJPY,AUDNZD,GBPAUD,EURAUD,GBPCHF,EURNZD,AUDCHF,GBPNZD, GBPCAD,XAUUSD
  • Data update period (milliseconds)  — Период обновления данных (в миллисекундах). Чем меньше это время, тем чаще будут поступать данные на сервер.
  • Depth of history to initialize  — Глубина исторических данных во время инициализации. Это количество баров, которое будет передано на сервер во время подключения.
Затем нужно написать программу, в которой подключить header-only библиотеку mt-bridge.hpp
Программа будет пытаться установить связь с советником. Можно использовать метод wait(), чтобы дождаться соединения. После установки соединения можно получать поток котировок, получить список используемых валютных пар или обратиться к небольшому промежутку исторических данных, которые передал советник при подключении.

Пример кода

Данный код после подключения к эксперту выведет на экран список символов, затем выведет исторические данные баров нулевого символа и после этого будет показывать текущую цену ask, цену закрытия бара, объем бара, время открытия бара и время сервера.
#include <iostream>
#include <mt-bridge.hpp>

int main() {
    const uint32_t port = 5555;
    mt_bridge::MtBridge iMT(port);

    if(!iMT.wait()) {
        std::cout << "no connection" << std::endl;
        return 0;
    }
    std::cout << "connection established" << std::endl;

    const uint32_t DELAY_WAIT = 5000;
    std::this_thread::sleep_for(std::chrono::milliseconds(DELAY_WAIT));

    /* list all symbols */
    std::vector<std::string> symbol_list = iMT.get_symbol_list();
    std::for_each(symbol_list.begin(), symbol_list.end(), [&](std::string &symbol) {
        static int n = 0;
        std::cout
            << "symbol["
            << std::to_string(n++)
            << "]: "
            << symbol
            << std::endl;
    });
    std::cout << "mt-bridge version: " << iMT.get_mt_bridge_version() << std::endl;

    std::this_thread::sleep_for(std::chrono::milliseconds(DELAY_WAIT));

    const uint32_t symbol_index = 0; // first symbol in the list
    /* get historical data to initialize your indicators */
    std::cout << iMT.get_symbol_list()[symbol_index] << std::endl;
    std::vector<mt_bridge::MtCandle> candles = iMT.get_candles(symbol_index);
    for(size_t i = 0; i < candles.size(); ++i) {
        std::cout << "candle, o: " << candles[i].open
            << " h: " << candles[i].high
            << " l: " << candles[i].low
            << " c: " << candles[i].close
            << " v: " << candles[i].volume
            << " t: " << candles[i].timestamp
            << std::endl;
    }

    std::this_thread::sleep_for(std::chrono::milliseconds(DELAY_WAIT));

    /* quotations stream */
    while(true) {
        if(!iMT.update_server_timestamp()) continue;
        mt_bridge::MtCandle candle = iMT.get_candle(symbol_index);
        std::cout
            << iMT.get_symbol_list()[symbol_index]
            << " candle,"
            << " ask: " << iMT.get_ask(symbol_index)
            << " c: " << candle.close
            << " v: " << candle.volume
            << " t: " << candle.timestamp
            << " s: " << iMT.get_server_timestamp()
            << std::endl;
    }
    return 0;
}

Зависимости

  • boost.asio (нужны только заголовочные файлы)
  • для компилятора mingw добавить библиотеки ws2_32 и wsock32
Может кому пригодится)
★14
21 комментарий
Полезные труды спс. Интересно что за столько лет существования Смартлаба, ничего полезного с точки зрения софта не было произведено сообществом из 73976 Человек. 
Так сложно понять о чем речь, добавьте фото пайпов или через что вы бинарные данные передаете.
СОЛДАТ Low Risk High Reward, советник, исходник которого тут, является клиентом, пытается подключиться к серверу через tcp socket. Сервер может быть написан на чем угодно, но на данный момент есть лишь библиотека для С++. 

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

Name Length (bytes) Type 
Application version 4 uint
Number of currency pairs -   "N" uint
Currency pair name (1) 32 string
Currency pair name (2) 32 string
Currency pair name (3) 32 string
 ... ... ...
Currency pair name (  N) 32 string
Depth of historical data -   "depth" 4 uint

Эти данные передаются только 1 раз. Но на этом инициализация еще не закончилась. Дальше, пред тем как начнется поток котировок, сначала передаются исторические данные. Пакет данных для передачи информации по одному бару сразу для всех используемых валютных пар всегда одинаковый, не важно, исторические это данные или актуальные. И в пакете всегда содержится текущая цена ask и bid, помимо цен бара (open, high, low, close), и всегда есть актуальное время сервера. В таблице ниже представлена структура пакета:

Name Length (bytes) Type
Current bid price of a currency pair (1) 8 double
Current ask price of a currency pair (1) 8 double 
Price of an  open currency pair bar (1) double 
Price of an  high currency pair bar (1)  double
Price of an  low currency pair bar (1)  double
Price of an  close currency pair bar (1)  double
Volume pair  volume bar of a currency pair (1)  8 ulong
Currency bar start timestamp (1)  8 ulong
Current bid price of a currency pair (2) 8 double
Current ask price of a currency pair (2) 8 double
 ... ... ...
Currency bar start timestamp (  N ulong
Server timestamp 8 ulong

Соответственно, если мы указали глубину истории 1440 баров, то сначала советник передаст 1440 таких пакетов данных для исторических данных. Это произойдет лишь 1 раз при подключении. Потом советник будет передавать только актуальные значения цен баров всех валютных пар. Когда появляется новый бар, советник передаст два пакета — один для старого бара (чтобы цены close, low, high были обновлены), другой пакет для нового  актуального бара.

В советнике для передачи данных через сокет используется библиотека socket-library-mt4-mt5.mqh, в которой я добавил новый метод для передачи бинарных данных.
avatar
elektroyar, Прочитала, интересно, но не поняла что это… Столько советников в Метаке, этот шлюз выполняет какие функции, обеспечит быстроту подключения? Извиняюсь за детские вопросы
avatar
Ola-la, замеры не проводил, чтобы говорить о скорости. Но по идее бинарные данные должны работать чуть быстрее текстовых, так как не тратится время со стороны клиента на преобразование данных в текстовый формат, а со стороны сервера нет надобности в парсинге данных. Ранее я делал нечто похожее через файлы. Так вот через файлы передавать данные однозначно тормознее во много раз. Но это уже способ реализации моста, а не сравнение форматов данных. 
avatar
СОЛДАТ Low Risk High Reward, пайпы в винде, знающие люди говорят, очень тормозные, так что сокеты — то, что надо. А так, мост между мт5 и внешним миром я для себя произвел еще в начале 16 года. Но какой смысл делиться трудами свомими? Тем более, что они под собственную задачу были заточены.
avatar
tranquility, на хабре было сравнение под win7, там пайпы многократно уделали сокеты при тестах на локальной машине. Впрочем, если не стоит задача добиться более высокой скорости на локалке, тогда сокеты действительно более удобны, благодаря своей универсальности
avatar
Спасибо за ваш труд…
avatar
соккеты это сурово...
можно было сделать на базе  Com(DCOM) сервера.
за основу взять например из экселя интерфейсы IRtdServer & IRTDUpdateEvent
… по крайней мере не надо было бы с бинарными данными возиться)
Niktesla (бывш. Бабёр-Енот), а DCOM не сурово? =)
avatar
Андрей К, хе-хе, не, ну, по мне так самое то ^^)'
Niktesla (бывш. Бабёр-Енот), про детство вы верно подметили =)
avatar
Niktesla (бывш. Бабёр-Енот), там тебе могут всякие хендшейки со обменом ключами и шифрованием забабахать, как в случае пайпов происходит. Так что сокет — самый легкий и быстрый вариант. Вопрос только в разработке протокола. В принципе, немного пожертвовав скоростью, можно json какой-нибудь через них гонять. Вполне толерантная к небольшим изменениям и модернизации структура должна получиться.
avatar
C++ еще ужаснее чем MQL5 )
avatar
robomakerr, я не так хорошо знаю MQL, но например в MQL4 я использовал memcpy и понял, что с указателями работать в MQL сложнее, чем в Си или С++. Я сам сейчас разрабатываю достаточно сложную торговую систему, она в себе сочетает множество стратегий, есть самооптимизация и т.д. И могу сказать, что может язык С++ и не прост, и лучше было бы писать на другом языке типа питон. Но MQL вряд ли подошел бы мне на эту роль лучше. Он накладывает достаточно много ограничений, потому что он создан для других целей — для удобства продажи роботов в маркете. В остальном это по сути просто сильно урезанный С++ с готовыми функциями, которые отнюдь не являются главной головной болью в сложных системах. Если нужно за вечер написать индикатор — конечно MQL. В остальных случаях проще делать что-то не зависимое от MQL. 
avatar
elektroyar, ну, у меня сейчас боевой робот — это всего две тыщи строк кода на mql. С логированием, обработкой ошибок, и т.п. Потребности в memcpy и указателях никогда не возникало. Для отладки вполне хватает Print(), на мой взгляд. Я не критикую, делюсь опытом просто.
Но mql тоже ужасен, да) точнее, ужасен сам МТ, многие его архитектурные решения.
avatar
robomakerr, мой робот состоит из отдельных модулей-подпрограмм, решил не делать «монолит». Подпрограммы можно расположить в виде «слоев». Сначала идет загрузка исторических данных котировок и экономических новостей. Потом идет сбор статистики индикаторов. Потом статистика индикаторов поступает на вход оптимизатору параметров индикаторов стратегий, на выходе — оптимальные параметры индикаторов. Потом идет сбор статистики самой стратегии. Потом еще идет дополнительная фильтрация сигналов стратегии, здесь тоже могут быть оптимизаторы параметров фильтров, поэтому им нужны предварительно рассчитанные сигналы стратегии. Уже после всех этих этапов получается сформировать правило торговли для торгового робота и начинается непосредственно торговля.  Все это работает сразу с множеством валютных пар одновременно. И стратегий тоже сразу несколько. Самооптимизация — каждый день перед торговлей. В будущем планирую добавить еще отчет о работе, который сможет отсылаться мне в телеграм, и прочие полезные штуки. 
avatar
robomakerr, ужасно написать большой проект (десятки-сотни тысяч строк) под какую-то конкретную платформу, а потом переписывать все под новую ее версию. С++ от такого очень страхует. Ну и пользоваться, отлаживать С++ гораздо приятнее, чем даже тот же питон.
avatar
tranquility, я как-то попробовал переписать свой старый проект с Builder на Visual, так все функции другие оказались)
avatar
robomakerr, это да, если используются фичи оси. Я недавно тоже переписывал старый проект 6й вижуал студии под последнюю. Ничего страшного, но там и проект совсем маленький. А с обычным кросплатформенным кодом, использующим новые фичи от новых стандартов по минимуму — вообще проблем никаких не должно возникать.
avatar
Предложенное автором решение ориентировано на metatrader 4.

Но если кому надо под метатрейдер 5, тогда за основу можно взять пайпы:
www.mql5.com/ru/articles/503
Связь через именованные каналы без применения DLL

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

А для передачи сложных структур может пригодиться такая библиотека:
www.mql5.com/ru/code/13663
Де/сериализация на основе JSON в mt5



P.S. Ну и сюда же, до кучи:

--для тех кто предпочитает работу с сокетами

www.mql5.com/en/blogs/post/706665
Socket library for MT4 and MT5 [универсальная библиотека с актуальными обновлениями]

www.mql5.com/ru/code/169
Работа с сокетами в MQL5 [библиотека давно не обновлялась]

www.mql5.com/ru/articles/2599
статья про сокеты в MQL5 [на примере функций портированных из WinAPI]


--для буферизации данных, если возникнет подобная необходимость

www.mql5.com/ru/articles/3047
здесь пример организации кольцевого буфера на MQL5



P.P.S. кстати, тут выяснилось, что в прошлом году в metatrader5 добавили нативную поддержку сокетов (правда частично урезанную, но зато официальную, и без использования динамических библиотек). Неплохая статья с разборами и отладкой примеров:
  www.mql5.com/ru/articles/7117
  Работа с сетевыми функциями, или MySQL без DLL

Ну и, помимо прочего, тем у кого в приоритете находится скорость программы, есть интересная статейка (хоть и старая, но вполне актуальная) о том как лучше портировать библиотеки из С на MQL:
  www.mql5.com/ru/articles/364
  Избавляемся от балласта самодельных DLL
  [опять же, с примерами для сокетов, и с примерами корректной обработки указателей от API функций!]
avatar

теги блога elektroyar

....все тэги



UPDONW
Новый дизайн