В жизни бывают такие моменты, когда очень хочется торговать из программы на
С++, но по каким-то причинам у брокера нет
API, зато есть
MetaTrader. Конечно, можно просто писать код на
MQL4/MQL5, на этом урезанном варианте-мутанте
Си и
С++, но мне как-то не в кайф это делать. Поэтому я решил сделать «мост» между
MetaTrader и программой через socket. Встречайте —
MT-Bridge.
На данный момент
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
Может кому пригодится)
Во время подключения к серверу сначала отправляются данные о версии советника, глубине исторических данных, количестве валютных пар и их имена. В правильной последовательности все это представлено в таблице ниже:
Эти данные передаются только 1 раз. Но на этом инициализация еще не закончилась. Дальше, пред тем как начнется поток котировок, сначала передаются исторические данные. Пакет данных для передачи информации по одному бару сразу для всех используемых валютных пар всегда одинаковый, не важно, исторические это данные или актуальные. И в пакете всегда содержится текущая цена ask и bid, помимо цен бара (open, high, low, close), и всегда есть актуальное время сервера. В таблице ниже представлена структура пакета:
Соответственно, если мы указали глубину истории 1440 баров, то сначала советник передаст 1440 таких пакетов данных для исторических данных. Это произойдет лишь 1 раз при подключении. Потом советник будет передавать только актуальные значения цен баров всех валютных пар. Когда появляется новый бар, советник передаст два пакета — один для старого бара (чтобы цены close, low, high были обновлены), другой пакет для нового актуального бара.
В советнике для передачи данных через сокет используется библиотека socket-library-mt4-mt5.mqh, в которой я добавил новый метод для передачи бинарных данных.
можно было сделать на базе Com(DCOM) сервера.
за основу взять например из экселя интерфейсы IRtdServer & IRTDUpdateEvent
… по крайней мере не надо было бы с бинарными данными возиться)
Но mql тоже ужасен, да) точнее, ужасен сам МТ, многие его архитектурные решения.
Но если кому надо под метатрейдер 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 функций!]