
Передача тиковых данных из MetaTrader в Python через сокеты с помощью MQL5-сервисов
Введение
Иногда не все можно запрограммировать на языке MQL5. И даже если возможно конвертировать существующие передовые библиотеки в MQL5, это займет много времени. Лучшим вариантом будет интеграция или использование существующих библиотек для выполнения задачи. Например, в Python существует множество библиотек машинного обучения. Для нас не лучшим вариантом является создавать идентичные копии в форме MQL5 только для того, чтобы выполнить задачу машинного обучения для торговли. Лучше экспортировать данные, необходимые для библиотеки машинного обучения Python, выполнить необходимую обработку в среде Python и импортировать результат обратно в программу MQL5. В данной статье речь идет о переносе таких данных из терминала MetaTrader в среду Python.
Существует пакет Python под названием MetaTrader 5, который можно использовать для доступа к таким данным графиков MetaTrader, как информация о барах, тиках, пользовательская информация, торговая информация и т. д. Но этот пакет доступен только для Windows, а это значит, что если вы хотите использовать пакет, вам понадобится ОС Windows. В настоящей статье я покажу вам, как можно получить доступ к тиковым данным из MetaTrader в программе Python с помощью сокетов.
С появлением сокетов Беркли в операционной системе Unix 4.2BSD в 1983 году связь между машинами стала простой и широко распространенной. Более продвинутые библиотеки и протоколы прикладного уровня зависят от протоколов сокетов транспортного уровня, которые мы будем использовать в этой статье.
В демонстрационных целях я собираюсь передавать только тиковые данные (такие данные о тике, как bid, ask и time) в приложение Python через порты сокетов для использования в приложении Python. Используя идею, представленную в настоящей статье, мы можем экспортировать все виды данных графиков, такие как информация о барах, информация о пользователях, информация о сделках и т. д. И для решения этой задачи мы можем использовать не только сервисы, но и скрипты, индикаторы или советники.
Ход выполнения программы
В данной статье рассматривается использование MQL5-сервисов для экспорта тиковых данных, включая bid, ask и time, на сервер Python. В свою очередь, сервер Python будет передавать эти данные на все клиентские сокеты, подключенные к серверу. Это более наглядно показано на следующем рисунке.
Как видно из рисунка, сервис MetaTrader подключается к серверу Python, прослушивающему порт 9070. Все тиковые данные графиков, открытых в терминале MetaTrader 5, будут отправляться на сервер Python на порт 9070. Затем сервер Python анализирует данные, полученные от MetaTrader 5, выполняет необходимый анализ данных и далее передает, т.е. транслирует эти тиковые данные подключенным клиентам. Далее клиенты могут использовать полученные данные для выполнения необходимых задач, использовать различные алгоритмы для анализа, а результат передавать обратно в сервис MetaTrader для дальнейшей обработки.
Почему сервисы и Python?
Нет каких-либо конкретных причин выбирать сервисы MetaTrader 5 для отправки тиковой информации. Для решения подобных задач также можно использовать скрипты, индикаторы и советники, и я сделаю это в следующих статьях. Я просто хотел показать, что тиковую и другую связанную с графиками информацию можно переносить в другие приложения с помощью сервисов MetaTrader, использующих программирование сокетов. И вместо сервера и клиентов Python, для разработки сервера и клиентов можно использовать другие платформы и фреймворки. Мне Python показался удобным.
MQL5-cервисы
Как мы знаем, сервисы в MetaTrader 5 имеют только одну функцию OnStart, и все должно выполняться в рамках этой функции. Сервисы не привязаны к каким-либо окнам, а встроенные функции, такие как _Symbol, _Period, _Point и другие, недоступны в сервисах, в отличие от того, как они доступны в скриптах, индикаторах и советниках. А запуск и управление сервисами можно осуществлять только через окно навигатора. Сервисы нельзя перетаскивать, чтобы открыть окна графиков для запуска. Вы можете увидеть, как запускать сервисы, в демонстрационном разделе ниже, куда я включил GIF-файл.
Сервис начинается с определения переменных сокета, сервера и порта.
int socket; string server = "localhost"; int port = 9070;
Функция SocketInit() создаёт хэндл сокета с помощью метода SocketCreate(). Хэндл сокета вместе с адресом сервера и портом передается в функцию SocketConnect() для подключения к серверу, который возвращает значение true в случае успешного подключения.
void SocketInit() { socket=SocketCreate(); bool connect = SocketConnect(socket, server, port, 1000); if(connect) { Print("socket is connected", " ", server, " port ", port); } }
При добавлении и запуске сервиса из окна навигатора в терминале MetaTrader, вызывается функция OnStart, в которой мы вызываем функцию SocketInit().
void OnStart() { SocketInit();
Затем определяются три переменные: первая — для хранения информации о тиках открытого графика, вторая — для хранения идентификатора графика и третья — для хранения информации о тиках, которая будет отправлена на сервер.
MqlTick latestTick; long next; string payload;
Поскольку нам необходимо постоянно отправлять тиковую информацию на сервер, определяется цикл while со значением true, а затем проверяется, подключен ли сервер. Если соединение с сокетом не удалось, то SocketIsConnected(socket handle) вернет значение false, а сервис будет остановлен.
while(true) { if(!SocketIsConnected(socket)){ Print("socket is not initialized yet so stopping the service"); break; }
Переменные next и payload инициализируются первым идентификатором диаграммы и пустой строкой.
next = ChartFirst(); payload = "";
Далее выполняется еще один цикл while для перебора всех окон диаграммы и получения символа диаграммы.
while (next != -1) { string chartSymbol = ChartSymbol(next);
Извлекаются последние тиковые данные, такие как bid, ask и time для графика.
SymbolInfoTick(chartSymbol, latestTick); double bid = latestTick.bid; double ask = latestTick.ask; string tickTime = TimeToString(latestTick.time, TIME_SECONDS);
Формируется полезная нагрузка для отправки на сервер с использованием значений symbol, time, bid и ask. Я попытался создать строковое значение JSON для полезной нагрузки, чтобы можно было использовать библиотеку JSON на сервере Python для декодирования строки JSON в объект JSON для извлечения данных. Можно использовать любой удобный для вас формат.
bool stringAdded = StringAdd(payload, StringFormat("{\"pair\": \"%s\", \"time\": \"%s\", \"bid\": %f, \"ask\": %f}", chartSymbol, tickTime, bid, ask));
Если открыто несколько окон графиков, то добавляется информация о тиках следующего окна графика с использованием #@# в качестве разделителя.
next = ChartNext(next); if (next != -1 && stringAdded) { stringAdded = StringAdd(payload, "#@#"); }
Когда все открытые окна графиков просканированы на наличие тиковых данных и полезная нагрузка заполнена, они готовы к отправке на сервер. Метод SocketSend отправляет данные в порт сокета, который был определен хэндлом сокета. Убедитесь, что значение в поле длины верно, в противном случае вместе с данными будут отправлены лишние символы, что создаст дополнительную нагрузку на сервер при анализе данных.
uchar data[]; int len = StringToCharArray(payload, data); SocketSend(socket, data, len-1);
И цикл продолжается снова с первого открытого окна графика до тех пор, пока не будет открыто соединение с сервером.
Серверная программа Python
Этот сервер должен прослушивать прием тиковой информации по порту 9070 от терминала MetaTrader, а также прослушивать клиентов для отправки полученной тиковой информации по порту 9071. Таким образом, два сокетных соединения привязаны к этим портам и продолжают прослушивать эти порты.
host = '127.0.0.1' MT5_RECIEVING_PORT = 9070 CLIENT_SENDING_PORT = 9071 mt5Socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) mt5Socket.bind((host, MT5_RECIEVING_PORT)) mt5Socket.listen() clientSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) clientSocket.bind((host, CLIENT_SENDING_PORT)) clientSocket.listen()
Я запрограммировал сервер таким образом, что только одному MetaTrader 5 разрешено подключаться и обмениваться данными. Это делается для того, чтобы избежать некорректного управления и хаоса, которые могут возникнуть при наличии нескольких подключений MetaTrader 5. С другой стороны, клиенты должны получать тиковые данные для дальнейшей обработки, одни и те же данные могут использоваться разными клиентами для применения разных алгоритмов в целях достижения желаемого результата. Таким образом, возникает необходимость управления клиентскими соединениями.
Следующая переменная dict используется для хранения активных подключенных клиентов в целях управления подключенными клиентами на стороне сервера.
connectedClients = {}
MetaTrader и клиентский поток запускаются как отдельные потоки, чтобы они могли работать независимо друг от друга.
if __name__ == "__main__": thread = threading.Thread(target=acceptFromMt5) thread.start() thread = threading.Thread(target=acceptFromClients) thread.start()
Поток MetaTrader 5 продолжает ожидать, пока к нему не подключится сервис MetaTrader. После принятия соединения вызывается функция processMt5Data, где данные принимаются, обрабатываются и отправляются соответствующим клиентам. Подробнее я расскажу об этом позже.
Если при принятии соединения от сервиса MetaTrader произошла ошибка, то он будет ожидать следующего соединения.
def acceptFromMt5(): try: print("Server is listening for mt5") mt5Client, address = mt5Socket.accept() print(f"mt5 service is connected at {str(address)}") processMt5Data(mt5Client) except Exception as ex: print(f"error in accepting mt5 client {ex}") acceptFromMt5()
Прежде чем обсуждать раздел анализа тиковой информации, крайне важно понять, как происходит подключение и управление клиентами. Как и в случае с сервисом MetaTrader 5, сервер ожидает нового клиентского подключения. Клиенту необходимо отправить свою идентификацию, чтобы ею можно было управлять на сервере. Идентификация представляет собой пару символов окна графика. Эта идентификация используется как ключ к объекту dict и клиент добавляется в этот словарь. Цикл while используется для того, чтобы обеспечить возможность подключения большого количества клиентов.
def acceptFromClients(): try: while True: print("Server is listening for clients") client, address = clientSocket.accept() pair = client.recv(7).decode("ascii") print(f"{pair} client service is connected at {str(address)}") clients = connectedClients[pair] if connectedClients and pair in connectedClients.keys() else [] clients.append(client) connectedClients[pair] = clients except Exception as ex: print(f"error in accepting other clients {ex}") acceptFromClients()
Теперь, когда мы поняли, как добавляются вновь подключенные клиенты, будет легко понять, как получаемые тиковые данные транслируются этим клиентам. Тиковые данные преобразуются в объект JSON и из него извлекается символ. А тиковая информация передается клиентам на основе идентификационного ключа клиента, отправляемого на сервер при подключении.
Тиковые данные разделяются с использованием того же разделителя, который используется в сервисной программе MetaTrader 5. Дополнительное разделение необходимо, если данные по нескольким тикам графика отправляются одновременно. Второе разделение выполнено, поскольку библиотека Python для выполнения анализа JSON не могла проанализировать информацию о тиках, если из службы MetaTrader на сервер одновременно отправлялась информация о множестве тиков.
def processMt5Data(mt5Client): data = "mt5 client connected" repeatativeEmpty = 0 while(len(data) > 0): try: data = mt5Client.recv(1024000).decode("ascii") if len(data) > 0: for jsn in data.split("#@#"): if "}{" in jsn: splittedTickData = jsn.split("}{") jsn = splittedTickData[0] + "}" jsonTickData = json.loads(jsn) pair = jsonTickData["pair"] if pair in connectedClients.keys(): broadcastToClients(pair, jsonTickData) repeatativeEmpty = repeatativeEmpty + 1 if len(data) == 0 else 0 if repeatativeEmpty > 10: print(f"data is not recieved for 10 times in a row {data}") break except Exception as ex: print(f"error in processing mt5 data {ex}") break time.sleep(0.1) acceptFromMt5()
Отправка данных клиентам проста. Если при отправке данных произошла ошибка, то клиент удаляется из списка подключенных клиентов (при условии, что клиент не подключен), а словарь обновляется.
def broadcastToClients(pair, message): for client in connectedClients[pair]: try: client.send(str(message).encode("ascii")) except Exception as ex: print(f"error while sending {message} to {client} for {pair}") client.close() clients = connectedClients[pair] clients.remove(client) connectedClients[pair] = clientsНа этом программа сервера завершена.
Клиент на Python
Клиентская программа подключается к серверу через порт 9071. Для управления клиентами на сервере необходим ключ, представляющий пару символ/валюта на сервере. Таким образом, нам нужны варианты для отображения списка доступных ключей, которые можно выбрать из списка для идентификации ID, который будет отправлен на сервер.
CURRENCY_PAIRS = [ "AUDUSD", "AUDJPY", "AUDCAD", "AUDNZD", "AUDCHF", "CADJPY", "CADCHF", "CHFJPY", "EURUSD", "EURJPY", "EURGBP", "EURCAD", "EURAUD", "EURNZD", "EURCHF", "GBPUSD", "GBPJPY", "GBPCAD", "GBPAUD", "GBPNZD", "GBPCHF", "NZDUSD", "NZDJPY", "NZDCAD", "USDCHF", "USDJPY", "USDCAD", "NZDCHF" ] server = "127.0.0.1" port = 9071
Как мы видим, после подключения один вариант из списка выше выбирается и отправляется на сервер, а затем ожидает поступления данных с сервера.
if __name__ == "__main__": print(f"please choose from the currency pairs given below as name \n {', '.join(CURRENCY_PAIRS)}") name = input("enter the name for the client : ") if name in CURRENCY_PAIRS: client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) if client.connect_ex((server, port)) == 0: client.send(name.encode("ascii")) receiveData(client) else: print("server could not be connected.") else: print("you didn't choose from the above list")
Клиентская программа, которую я написал, просто выводит на экран данные о тиках, но мы можем делать с ними все, что угодно в соответствии с нашими требованиями. Например, мы можем проводить дальнейшую обработку данных с помощью доступных фреймворков и библиотек, применять библиотеки машинного обучения и т. д.
def receiveData(client): repeatativeEmpty = 0 while True: data = client.recv(1024).decode("ascii") print("data received ", data) repeatativeEmpty = repeatativeEmpty + 1 if len(data) == 0 else 0 if repeatativeEmpty > 10: print(f"data is not recieved for 10 times in a row {data}") break
На этом клиентская программа завершена.
Демонстрация потока данных
Для корректного преобразования данных необходимо сначала запустить сервер, в противном случае ни одна из служб не будет работать. В целях тестирования сервер Python запускается в виртуальной среде, что можно увидеть на следующем рисунке.
Нам необходимо, чтобы сервер получал тиковые данные и затем транслировал их клиентам. Поэтому вторая задача — запустить сервисную программу MetaTrader, как показано на следующем скриншоте.
Если сервис MetaTrader не запущен, то подключенные клиенты будут ожидать получения данных. Как только клиент отправляет идентификационные ключи на сервер и если есть отправленные из сервисов MetaTrader тиковые данные, то эти данные на данный момент выводятся в клиентском терминале. Это делается для EURUSD и GBPUSD, поскольку в этот момент в терминале MetaTrader 5 открыты оба окна графиков. Это наглядно представлено на следующих скриншотах.
Я попытался запечатлеть весь процесс, показанный на скриншотах выше, в формате GIF, чтобы весь процесс был более наглядным.
Заключение
В данной статье рассматривается использование программирования сокетов для отправки тиковых данных из MetaTrader 5 в приложение Python с использованием сервисов MetaTrader, а также сервера и клиента Python. Поскольку мы можем переносить тиковые данные, как это сделано в этой статье, мы можем переносить и другую информацию о графиках. Я пользовался сервисами, но также можно использовать скрипты, индикаторы или советники. А кроме языка Python можно использовать и другие языки программирования.
Более того, в данной статье также делается попытка отойти от зависимости от ОС Windows. MetaTrader и библиотека Python для MQL5, зависят от ОС Windows. Вместо использования библиотеки Python в MetaTrader 5 можно использовать протокол сокетов для передачи данных из MetaTrader в нужную библиотеку, чтобы максимально удобно и полно обрабатывать и анализировать данные из MetaTrader.
Название файла | Описание |
---|---|
TickSocketService.mq5 | MQL-файл, содержащий коды для подключения к сокет-серверу по адресу 9070 и последующей отправки тиковых данных |
tick_server.py | Сокет сервера Python открывает порт 9070 для MetaTrader и порт 9071 для других клиентов |
tick_client.py | Клиентский сокет Python подключается к порту 9071 и получает данные, отправленные сервером |
Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/18680





- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования
Опубликована статья Передача тиковых данных из MetaTrader в Python через сокеты с помощью MQL5-сервисов:
Автор: lazymesh