Преодоление проблем доступности в торговых инструментах на MQL5 (Часть IV): Удаленная голосовая торговля
Добро пожаловать в четвертую часть нашей серии статей о преодолении проблем доступности в торговых инструментах MQL5. В этой статье рассматривается один практический метод удаленного выполнения голосовых команд с низкой пропускной способностью — полуавтоматический конвейер, который соединяет голосовое сообщение Telegram с вашим терминалом MetaTrader 5. Это не единственное возможное решение, и мы не утверждаем, что это окончательный ответ. По мере развития MQL5 платформа может внедрять новые модули, упрощающие удаленное взаимодействие. На данный момент описанная здесь pull-модель опроса является надежным и адаптируемым способом управления процессом вне рабочего места.
Содержание
- Введение
- Архитектура решения
- Реализация
- Тестирование и результаты
- Инженерные выводы
- Заключение
- Вложения
Введение
Голосовой советник, идеально работающий за вашим рабочим столом (Часть III, автономный Vosk), теряет все возможности удаленного управления, как только вы отходите. Ваш мобильный телефон не может выполнять логику MQL5 — среда выполнения предназначена только для настольных компьютеров. Ручное вмешательство при торговом сетапе с высокой вероятностью успеха становится невозможным во время поездок на работу, приготовления пищи или в условиях многозадачности. В этой статье описывается полуавтоматизированный конвейер обеспечения доступности, который позволяет вам произносить команду для совершения сделки из любого места, и ваш терминал MetaTrader 5 будет выполнять ее по HTTP-каналу с низкой пропускной способностью.
У вас уже есть терминал MetaTrader 5, работающий на компьютере с Windows, с установленным Market Watch, установленной средой советника и автономной системой голосового управления (Vosk из Части III), которая работает локально. Терминал может отправлять вызовы WebRequest (HTTP-клиент) на разрешенный URL-адрес. Трейдер физически находится за рабочим столом и может произнести команду, которую советник мгновенно выполнит.
Когда вы отходите от рабочего стола, терминал остается в сети, но нет возможности передавать ему команды. Голосовые сообщения в Telegram — это естественный способ удалённого ввода: вы записываете голосовое сообщение, и оно отправляется боту, но ничто не связывает его со средой выполнения MetaTrader 5. Цель состоит в том, чтобы замкнуть цикл: голосовое сообщение → расшифровка → команда JSON → HTTP-конечная точка → опрос советника → открытие сделки → результат обратно в Telegram. И все это без необходимости касаться рабочего стола.
Мы представляем промежуточное ПО на Python, которое выступает в роли бота Telegram и локального HTTP-сервера. Бот получает голосовые сообщения, преобразует их в текст с помощью Google STT, анализирует простую команду на естественном языке и добавляет ее в очередь. Отдельный HTTP-эндпоинт передает эту команду советнику. Советник опрашивает этот эндпоинт каждые две секунды, исполняет сделку с помощью CTrade и отправляет результат обратно по POST. Вся цепочка добавляет менее 3 секунд задержки и работает через мобильные данные (< 5 КБ на сделку). Система обеспечивает конфиденциальность: для ее работы не требуются ключи API облачных сервисов, кроме Telegram, и не взимается периодическая плата. Модуль преобразования речи в текст является взаимозаменяемым, поэтому в дальнейшем вы можете заменить его на офлайн-модель для еще большей независимости.
Архитектура решения
Обзор компонентов системы
Конвейер обработки данных состоит из четырех основных компонентов:
- Telegram-бот – принимает голосовые сообщения, отправляет статусные сообщения.
- Промежуточное ПО на Python – расшифровывает аудио, анализирует команды, предоставляет HTTP-эндпоинты.
- Советник MetaTrader 5 – опрашивает команды, открывает сделки, сообщает о результатах.
- Настольный терминал MetaTrader 5 — среда выполнения, которая должна оставаться запущенной и подключенной.
Все компоненты работают на одном компьютере с Windows; смартфону требуется только Telegram и доступ в интернет.
Промежуточное ПО на Python (Telegram-бот + HTTP-сервер)
Промежуточное ПО представляет собой один скрипт Python, который порождает два потока: один запускает BaseHTTPRequestHandler по адресу 127.0.0.1:8082, другой запускает приложение python-telegram-bot. Он предоставляет два эндпоинта:
- GET /get_command – возвращает самую старую команду из очереди в памяти в формате JSON или {"action": null}, если она пуста.
- POST /post_response – получает результат выполнения советника и устанавливает событие result_event, которое обработчик голосовых сообщений использует для ответа в Telegram.
Советник MQL5 как клиент WebRequest
Советник представляет собой чисто опрашивающий клиент. Он вызывает WebRequest("GET", ...) каждые две секунды для получения команды. Когда поступает действительная команда в формате JSON, он анализирует её с помощью CJAVal, открывает сделку через CTrade, форматирует результат в формате JSON и отправляет его обратно с помощью WebRequest("POST", ...). Важная деталь заключается в том, что StringToCharArray добавляет нулевой символ-терминатор; советник удаляет этот нулевой терминатор перед POST-запросом, чтобы избежать ошибок декодирования JSON в Python.
Поток данных и бюджет задержки
- Пользователь говорит “купить 0,001 евро” в Telegram → бот получает голосовое сообщение (~1-2 с).
- Скачивает файл .ogg, конвертирует его в монофонический WAV-файл с частотой 16 кГц, расшифровывает с помощью Google STT (~0,5–1 с).
- Анализирует текст в команду JSON, добавляет в очередь и ждет, пока советник получит данные (интервал опроса до 2 с).
- Советник получает команду GET, открывает сделку (50–200 мс), отправляет результат POST-запросом.
- Telegram-ответ с тикетом и ценой поступает на смартфон.
Общее время ответа обычно составляет менее 4 секунд. Пропускная способность на один опрос составляет < 1 КБ; объем загружаемого голосового сообщения составляет ~10–30 КБ. Постоянное подключение не требуется — полностью функционален в мобильной сети 2G/3G.
Реализация
С учетом созданной архитектуры, мы переходим от схемы к рабочей реализации. В следующих разделах вы узнаете, как настроить среду Windows, создать промежуточное ПО на Python, написать код для советника MQL5 и, наконец, запустить весь конвейер обработки данных. Откройте MetaEditor 5. Мы будем использовать его для советника и, если хотите, для редактирования скрипта Python в экосистеме MetaTrader. (Конечно, Notepad++, VS Code или любой другой удобный для вас редактор также подойдут).
Подготовка среды (Windows)
Установка Python – установите Python 3.10+ с ресурса python.org. Убедитесь, что python и pip добавлены в системную переменную PATH. Проверьте это с помощью команды python --version.
Виртуальная среда и зависимости
# Create and activate a virtual environment
python -m venv trading_env
trading_env\Scripts\activate
pip install python-telegram-bot[job-queue] speechrecognition pydub Также установите pyaudio, если вы планируете записывать звук локально (для удалённого доступа не требуется).
Установка ffmpeg и настройка PATH — для преобразования голосовых заметок Telegram в формате .ogg в формат WAV промежуточному ПО требуется ffmpeg. Установка с официального сайта часто приводит к ошибкам “ffmpeg not found”. Установщик может не обновить системную переменную PATH или среду выполнения подпроцесса Python. Надёжный способ — использовать менеджер пакетов Windows, winget, и явно указать путь к исполняемому файлу.
- Откройте командную строку (от имени администратора) и выполните команду: winget install ffmpeg
- После установки найдите точное местоположение файла ffmpeg.exe с помощью команды: where ffmpeg (обычно находится в папке, например, такой: C:\Users\YourUser\AppData\Local\Microsoft\WinGet\Packages\Gyan.FFmpeg…\bin).
- Скопируйте полный путь к каталогу bin. В константе FFMPEG_DIR скрипта Python точно вставьте этот путь. Скрипт принудительно добавляет этот каталог в переменную среды os.environ["PATH"] и настраивает pydub на его использование, полностью обходя любые отсутствующие системные параметры конфигурации.
Такой подход устраняет классическое предупреждение “ffmpeg not found”, поскольку промежуточное ПО больше не зависит от глобальной PATH.
Получение токена бота Telegram и идентификатора чата
- Откройте Telegram и найдите @BotFather. Создайте нового бота и скопируйте токен.
- Найдите свой числовой идентификатор чата, отправив сообщение на @userinfobot или начав чат с вашим ботом и изучив https://api.telegram.org/bot<TOKEN>/getUpdates.
- Вставьте токен и идентификатор чата в переменные TELEGRAM_TOKEN и ALLOWED_CHAT_ID скрипта Python.
Промежуточное ПО на Python (telegram_trading_bot.py)
Промежуточное ПО представляет собой единый, самодостаточный скрипт. Вы можете создать его в MetaEditor (Файл → Новый → “Python Script”) или в любом текстовом редакторе. Сохраните его как telegram_trading_bot.py в удобном месте — например, в папке MQL5\Experts, чтобы все файлы оставались вместе. Следующие шаги описывают его создание, при этом каждый блок кода размещается сразу после его пояснения. Полный файл также доступен во вложениях.
Шаг 1: Импорт и принудительное указание пути к ffmpeg
Первые строки скрипта решают наиболее распространенную проблему настройки: ошибку pydub “Couldn’t find ffmpeg”. Мы принудительно добавляем каталог, содержащий ffmpeg.exe, в переменную среды PATH и устанавливаем специальную переменную PYDUB_FFMPEG. Monkey-patch функции pydub.utils.which гарантирует, что каждый внутренний поиск возвращает наш явно заданный исполняемый файл. Это делает скрипт переносимым на машины, где ffmpeg установлен через winget, но не виден глобально.
#!/usr/bin/env python3 import os, sys, warnings # ========== FORCE FFMPEG PATH (NO WARNING) ========== FFMPEG_DIR = r"C:\Users\inter\AppData\Local\Microsoft\WinGet\Packages\Gyan.FFmpeg_Microsoft.Winget.Source_8wekyb3d8bbwe\ffmpeg-8.1.1-full_build\bin" os.environ["PATH"] = FFMPEG_DIR + os.pathsep + os.environ.get("PATH", "") os.environ["PYDUB_FFMPEG"] = os.path.join(FFMPEG_DIR, "ffmpeg.exe") warnings.filterwarnings("ignore", category=RuntimeWarning, module="pydub.utils") # Monkey-patch pydub's which() from pydub.utils import which _original_which = which def _forced_which(prog): if prog in ('ffmpeg', 'avconv'): return os.environ["PYDUB_FFMPEG"] return _original_which(prog) import pydub.utils pydub.utils.which = _forced_which import pydub pydub.AudioSegment.converter = os.environ["PYDUB_FFMPEG"] # Other imports import json, time, asyncio, threading from http.server import HTTPServer, BaseHTTPRequestHandler from telegram import Update from telegram.ext import Application, MessageHandler, filters, ContextTypes import speech_recognition as sr
Шаг 2: Конфигурация и очередь команд в памяти
Глобальные константы хранят учетные данные Telegram и HTTP-порт. Список Python используется как очередь команд, а потокобезопасность обеспечивается с помощью threading.Lock. Словарь результатов и событие threading.Event синхронизируют ответ: когда советник отправляет результат методом POST, устанавливается событие, и бот может ответить пользователю. Эта простая архитектура, работающая в оперативной памяти, позволяет избежать использования внешних баз данных, оставаясь при этом абсолютно надежной для однопользовательского конвейера.
# ==================== CONFIG ==================== TELEGRAM_TOKEN = "REPLACE WITH YOUR BOT TOKEN" ALLOWED_CHAT_ID = 000000000 #REPLACE WITH YOUR CHAT ID HTTP_PORT = 8082 command_queue = [] queue_lock = threading.Lock() result = None result_event = threading.Event()
Шаг 3: HTTP-сервер (GET /get_command, POST /post_response)
Пользовательский обработчик, созданный на основе BaseHTTPRequestHandler, прослушивает порт 127.0.0.1:8082. Эндпоинт GET в рамках защищенного блока извлекает и удаляет самую старую команду из очереди; если очередь пуста, он возвращает {"action":null}, чтобы советник знал, что выполнять нечего. Эндпоинт POST получает результат сделки советника, явно удаляет все нулевые байты (советник часто добавляет их с помощью StringToCharArray), а затем анализирует JSON. В случае успеха он устанавливает result_event, разблокируя обработчик Telegram. Логирование отключено, чтобы не засорять вывод.
# ==================== HTTP SERVER ==================== class CommandHandler(BaseHTTPRequestHandler): def log_message(self, format, *args): pass def do_GET(self): if self.path == '/get_command': self.send_response(200) self.send_header('Content-Type', 'application/json') self.end_headers() with queue_lock: if command_queue: cmd = command_queue.pop(0) self.wfile.write(json.dumps(cmd).encode()) print(f"Served: {cmd}") else: self.wfile.write(json.dumps({"action": None}).encode()) else: self.send_response(404) def do_POST(self): if self.path == '/post_response': length = int(self.headers.get('Content-Length', 0)) raw = self.rfile.read(length) try: data_str = raw.decode('utf-8').strip().rstrip('\x00') global result result = json.loads(data_str) result_event.set() print(f"Result: {result}") except Exception as e: print(f"POST error: {e}") self.send_response(200) self.end_headers() else: self.send_response(404) def start_http_server(): server = HTTPServer(('127.0.0.1', HTTP_PORT), CommandHandler) print(f"✅ HTTP server on {HTTP_PORT}") server.serve_forever()
Шаг 4: Транскрипция аудиозаписи (Google STT)
Функция transcribe_audio работает с монофоническим WAV-файлом с частотой 16 кГц. В ней используется встроенный в библиотеку speech_recognition метод recognize_google, который отправляет аудиоданные в бесплатный сервис STT от Google. Кратковременная корректировка фонового шума повышает точность записи из разных сред. Если API не может распознать речь (или интернет недоступен), функция возвращает пустую строку, чтобы вызывающая сторона могла корректно обработать ошибку.
# ==================== AUDIO ==================== def transcribe_audio(wav_path): recognizer = sr.Recognizer() with sr.AudioFile(wav_path) as src: recognizer.adjust_for_ambient_noise(src, 0.5) audio = recognizer.record(src) try: return recognizer.recognize_google(audio).lower() except: return ""
Шаг 5: Парсер команд на естественном языке (по умолчанию — микролоты)
Парсер преобразует текст в произвольной форме в структурированную команду JSON. Сначала он проверяет наличие специальных действий “close all” и “balance”. Для BUY/SELL он удаляет начальное ключевое слово и ищет спецификатор объема: слова типа “half” (0,5), “one”/“a” (1,0) или числовое значение; если ничего не найдено, значение по умолчанию составляет 0,001 лота (консервативный микролот). Оставшиеся слова сопоставляются с небольшим словарем алиасов инструментов (евро → EURUSD, золото → XAUUSD и т. д.), по умолчанию используется EURUSD, если ничего не распознано. Такая конструкция обеспечивает интуитивно понятный голосовой интерфейс и защищает от случайных крупных ордеров.
def parse_command(text): text = text.lower().strip() if "close all" in text: return {"action": "CLOSE_ALL"} if "balance" in text: return {"action": "BALANCE"} action = None if text.startswith("buy"): action = "BUY" text = text[3:].strip() elif text.startswith("sell"): action = "SELL" text = text[4:].strip() else: return None # ✅ DEFAULT LOT SIZE = 0.001 (micro lot, ~$117 for EURUSD) volume = 0.001 words = text.split() for i, w in enumerate(words): if w == "half": volume = 0.5 words.pop(i) break elif w in ["one", "a"]: volume = 1.0 words.pop(i) break else: try: volume = float(w) words.pop(i) break except: pass sym_text = " ".join(words) m = {"gold": "XAUUSD", "euro": "EURUSD", "pound": "GBPUSD", "silver": "XAGUSD", "oil": "USOIL"} symbol = "EURUSD" for k, v in m.items(): if k in sym_text: symbol = v break return {"action": action, "symbol": symbol, "volume": volume}
Шаг 6: Обработчик голосовых сообщений Telegram (асинхронный) и синхронизация результатов
Это основная часть бота. Функция handle_voice сначала проверяет идентификатор чата, чтобы отклонить неавторизованных пользователей. Она загружает файл .ogg с серверов Telegram, использует pydub для передискретизации до монофонического WAV-файла с частотой 16 кГц, транскрибирует и анализирует команду. Разобранная команда добавляется в общую очередь, после чего функция ожидает результата советника с помощью result_event.wait() (таймаут 10 секунд). Далее результат форматируется в удобочитаемый ответ. Небольшая вспомогательная функция удаляет временные аудиофайлы после каждого запроса, чтобы избежать засорения диска.
def cleanup(*files): for f in files: if os.path.exists(f): try: os.remove(f) except: pass async def handle_voice(update, context): if update.effective_chat.id != ALLOWED_CHAT_ID: await update.message.reply_text("Unauthorized") return await update.message.reply_text("🎤 Processing...") voice = await update.message.voice.get_file() ogg, wav = "temp.ogg", "temp.wav" try: await voice.download_to_drive(ogg) audio = pydub.AudioSegment.from_ogg(ogg) audio = audio.set_frame_rate(16000).set_channels(1) audio.export(wav, format="wav") text = transcribe_audio(wav) if not text: await update.message.reply_text("❌ Could not understand") return await update.message.reply_text(f"📝 Heard: {text}") cmd = parse_command(text) if not cmd: await update.message.reply_text("❌ Invalid command. Example: 'buy 0.001 euro'") return with queue_lock: command_queue.append(cmd) await update.message.reply_text("⏳ Sending to MT5...") global result result = None result_event.clear() if result_event.wait(timeout=10): if result and result.get("status") == "success": msg = f"✅ Success!\nTicket: {result['ticket']}\nPrice: {result['price']}" else: msg = f"❌ Error: {result.get('message') if result else 'Unknown'}" else: msg = "⏰ No response from MT5. Is EA running?" await update.message.reply_text(msg) except Exception as e: await update.message.reply_text(f"❌ {str(e)[:100]}") finally: cleanup(ogg, wav)
Шаг 7: Основная точка входа (многопоточность + опрос ботом)
Главная функция запускает HTTP-сервер в потоке-демоне, поэтому он автоматически завершает работу, когда бот останавливается. Затем создает приложение python‑telegram‑bot, регистрирует обработчик голосовых сообщений для сообщений типа VOICE и простой обработчик ошибок, а также начинает опрос. Вся система работает в одном процессе, что упрощает ее остановку и перезапуск.
async def error_handler(update, context): print(f"Telegram error: {context.error}") def main(): threading.Thread(target=start_http_server, daemon=True).start() app = Application.builder().token(TELEGRAM_TOKEN).build() app.add_handler(MessageHandler(filters.VOICE, handle_voice)) app.add_error_handler(error_handler) print("✅ Telegram bot polling...") app.run_polling() if __name__ == "__main__": main()
Советник MQL5 (OvercomingAccessibilityIV.mq5)
После подготовки промежуточного ПО мы переходим к стороне MetaTrader. Советник представляет собой простой опрашивающий клиент. Прежде чем писать какой-либо код, нам необходимо получить библиотеку парсера JSON JAson.mqh — она доступна в кодовой базе MQL5 или во вложениях к статье. Загрузите ее и поместите в отдельную папку, чтобы проект оставался организованным: <MetaTrader Data Folder>\MQL5\Include\OvercomingAccessibilityIV\JAson.mqh. Имя этой папки совпадает с именем файла советника и предотвращает конфликты с другими проектами.
Теперь в MetaEditor мы создадим новый советник: Файл → Новый → Советник, присвойте ему имя OvercomingAccessibilityIV.mq5, и сохраните в MQL5\Experts\. Мы будем создавать советник шаг за шагом, добавляя контекст и назначение каждому блоку. Помните, что вы можете использовать команду Styler в MetaEditor для автоматического форматирования кода в соответствии с руководством по стилю MetaQuotes.
Шаг 1: Включаемые файлы и константы
Мы включаем парсер JAson из его пользовательской папки и стандартную библиотеку Trade. Две константы препроцессора определяют локальный URL-адрес HTTP-сервера и интервал опроса. Экземпляр CTrade объявляется глобально для управления ордерами. Эта основа связывает советника как с сервером Python, так и с торговым модулем, и поскольку и включаемый файл, и библиотека Trade являются стандартными компонентами MQL5, в терминале не требуется никаких дополнительных внешних зависимостей.
//+------------------------------------------------------------------+ //| OvercomingAccessibilityIV.mq5 | //| Copyright 2026, Clemence Benjamin | //| http://www.mql5.com | //+------------------------------------------------------------------+ #include <OvercomingAccessibilityIV/JAson.mqh> #include <Trade/Trade.mqh> #define SERVER_URL "http://127.0.0.1:8082" #define POLL_INTERVAL 2 CTrade trade;
Шаг 2: Инициализация OnInit – внутри OnInit мы устанавливаем уникальное магическое число и допустимое проскальзывание (в пунктах). Оператор Print подтверждает успешную загрузку советника, и он появится на вкладке "Эксперты" после подключения. Возвращение INIT_SUCCEEDED сообщает терминалу, что инициализация завершена без ошибок. Магическое число помогает позже идентифицировать сделки, открытые этим советником, что полезно при просмотре истории ордеров.
//+------------------------------------------------------------------+ //| Expert Advisor initialization | //+------------------------------------------------------------------+ int OnInit() { trade.SetExpertMagicNumber(202504); trade.SetDeviationInPoints(10); Print("Voice EA started"); return(INIT_SUCCEEDED); }
Шаг 3: GetCommand – HTTP GET-запрос для получения команды
Эта функция формирует URL-адрес, вызывает WebRequest с методом GET и таймаутом в 2 секунды и возвращает необработанную JSON-строку. Если код ответа не равен 200, возвращается пустая строка, которую цикл опроса будет рассматривать как “команда недоступна”. Массив uchar resultData преобразуется в строку с помощью CharArrayToString. Обратите внимание, что таймаут намеренно сделан коротким, чтобы избежать слишком долгой блокировки потока графика в случае недоступности сервера.
//+------------------------------------------------------------------+ //| Fetch command from server via HTTP GET | //+------------------------------------------------------------------+ string GetCommand() { string url = SERVER_URL + "/get_command"; uchar resultData[]; string resultHeaders; uchar postData[]; int timeout = 2000; int res = WebRequest("GET", url, NULL, timeout, postData, resultData, resultHeaders); if(res != 200) return(""); return(CharArrayToString(resultData)); }
Шаг 4: PostResult – HTTP POST-запрос с удалением нулевого терминатора
При отправке результата выполнения обратно в Python необходимо преобразовать JSON-строку в массив uchar с помощью StringToCharArray. Ожидается критическая ошибка: StringToCharArray в MQL5 добавляет нулевой байт (0) в конец массива. Если мы отправим этот байт в json.loads Python, это приведет к ошибке. Таким образом, код проверяет последний элемент и изменяет размер массива, исключая его, перед отправкой. POST-запрос включает заголовок Content-Type и использует тот же 2-секундный таймаут. Этот шаг является прекрасным примером межъязыкового обмена данными, где критически важно для обеспечения надежной коммуникации корректно обрабатывать завершающий нулевой байт строки.
//+------------------------------------------------------------------+ //| Post execution result back to server | //+------------------------------------------------------------------+ bool PostResult(string json_result) { string url = SERVER_URL + "/post_response"; uchar postData[]; StringToCharArray(json_result, postData); int size = ArraySize(postData); //--- remove the null terminator if(size > 0 && postData[size-1] == 0) ArrayResize(postData, size-1); uchar resultData[]; string resultHeaders; string headers = "Content-Type: application/json\r\n"; int timeout = 2000; int res = WebRequest("POST", url, headers, timeout, postData, resultData, resultHeaders); if(res == 200) Print("Result sent: ", json_result); else Print("POST error: ", res); return(res == 200); }
Шаг 5: ExecuteCommand – логика BUY/SELL/CLOSE_ALL/BALANCE
Эта функция получает разобранный объект CJAVal и выполняет запрошенную операцию. Для BUY и SELL она сначала убеждается, что символ выбран в Market Watch (SymbolSelect), получает текущую цену Ask (для покупки) или Bid (для продажи), а затем вызывает trade.Buy или trade.Sell с указанным объемом. Комментарий "Telegram" помогает идентифицировать сделки, инициированные ботом. Если действие — CLOSE_ALL, бот проходит по всем открытым позициям в обратном порядке и закрывает их по одной. Если действие — BALANCE, бот просто возвращает баланс счета и эквити, не совершая никаких сделок. Все результаты упаковываются в JSON-строки, чтобы бот на Python мог передать их пользователю. Перебирая позиции в обратном порядке, мы избегаем проблем с индексацией при закрытии нескольких сделок в цикле.
//+------------------------------------------------------------------+ //| Execute parsed command (BUY/SELL/CLOSE_ALL/BALANCE) | //+------------------------------------------------------------------+ string ExecuteCommand(CJAVal &cmd) { string action = cmd["action"].ToStr(); if(action == "BUY" || action == "SELL") { string symbol = cmd["symbol"].ToStr(); double volume = cmd["volume"].ToDbl(); if(SymbolInfoInteger(symbol, SYMBOL_SELECT) == 0) SymbolSelect(symbol, true); double price = (action == "BUY") ? SymbolInfoDouble(symbol, SYMBOL_ASK) : SymbolInfoDouble(symbol, SYMBOL_BID); ulong ticket = (action == "BUY") ? trade.Buy(volume, symbol, price, 0, 0, "Telegram") : trade.Sell(volume, symbol, price, 0, 0, "Telegram"); if(ticket) return(StringFormat("{\"status\":\"success\",\"ticket\":%d,\"price\":%.5f}", ticket, price)); else return(StringFormat("{\"status\":\"error\",\"message\":\"Trade failed, error %d\"}", GetLastError())); } else if(action == "CLOSE_ALL") { int closed = 0; for(int i=PositionsTotal()-1; i>=0; i--) if(PositionGetTicket(i) && PositionSelectByTicket(PositionGetTicket(i))) if(trade.PositionClose(PositionGetTicket(i))) closed++; return(StringFormat("{\"status\":\"success\",\"message\":\"Closed %d positions\"}", closed)); } else if(action == "BALANCE") { double bal = AccountInfoDouble(ACCOUNT_BALANCE); double eq = AccountInfoDouble(ACCOUNT_EQUITY); return(StringFormat("{\"status\":\"success\",\"balance\":%.2f,\"equity\":%.2f}", bal, eq)); } return("{\"status\":\"error\",\"message\":\"Unknown action\"}"); }
Шаг 6: OnTick – цикл периодического опроса
Каждый тик советник проверяет, истек ли интервал опроса, используя статическую переменную datetime. Если пришло время опроса, он вызывает GetCommand и десериализует JSON. Проверяется поле action: если оно пустое или имеет значение "None", ничего делать не нужно. В противном случае вызывается ExecuteCommand, и его результат отправляется на сервер через PostResult. Такая архитектура обеспечивает отзывчивость советника, избегая при этом ненужных HTTP-запросов на каждом тике. Статическая переменная сохраняется между тиками, гарантируя постоянную частоту опроса даже в периоды высокой волатильности.
//+------------------------------------------------------------------+ //| Periodic command poll and execution | //+------------------------------------------------------------------+ void OnTick() { static datetime lastPoll = 0; if(TimeCurrent() - lastPoll < POLL_INTERVAL) return; lastPoll = TimeCurrent(); string json_str = GetCommand(); if(json_str == "") return; CJAVal json; if(!json.Deserialize(json_str)) return; string action = json["action"].ToStr(); if(action == "" || action == "None") return; Print("Command: ", json_str); string result = ExecuteCommand(json); PostResult(result); }
Компиляция и прикрепление советника
- Скопируйте JAson.mqh в <MetaTrader Data Folder>\MQL5\Include\OvercomingAccessibilityIV\.
- Поместите OvercomingAccessibilityIV.mq5 в <MetaTrader Data Folder>\MQL5\Experts\.
- В MetaEditor скомпилируйте советника (F7). Убедитесь в отсутствии ошибок.
- В терминале MetaTrader 5 откройте Навигатор, перетащите советник на график, предпочтительно EURUSD с периодом M1 или M5). Включите параметр “Разрешить веб-запросы к указанным URL” и добавьте http://127.0.0.1:8082 в список.
- Убедитесь, что в углу графика отображается смайлик советника, а на вкладке Эксперты — “Голосовой советник запущен”.
Запуск сервера Python и тестирование всего конвейера
- Активируйте виртуальную среду и запустите скрипт Python: python telegram_trading_bot.py . Вы должны увидеть сообщения “HTTP server on 8082” и “Telegram bot polling…”.
- Откройте Telegram, начните чат с ботом и запишите голосовое сообщение: “купить 0,001 евро”.
- Бот должен ответить “🎤 Обработка…“, затем “📝 Услышано: купить 0,001 евро“, далее “⏳ Отправка в MetaTrader 5…“.
- В течение 2–4 секунд вы должны получить “✅ Успех! Тикет: 1234567, Цена: 1.08500”.
- Проверьте вкладку Trade в MetaTrader 5 — должен появиться новый ордер BUY размером 0.001 лота по паре EURUSD.
Тестирование и результаты
Тестовые сценарии и ожидаемые результаты
| Голосовой ввод | Ожидаемая команда в формате JSON | Действие советника | Ответ Telegram |
|---|---|---|---|
| купить 0,001 евро | {"action":"BUY","symbol":"EURUSD","volume":0.001} | Открывает сделку на покупку объемом 0,001 лота | Успешно! Тикет: …, Цена: … |
| продай половину золота | {"action":"SELL","symbol":"XAUUSD","volume":0.5} | Открывает сделку на продажу объемом 0,5 лота | Успешно! … |
| закрыть всё | {"action":"CLOSE_ALL"} | Закрывает все открытые позиции | Успешно! Закрыто N позиций |
| баланс | {"action":"BALANCE"} | Без сделок – возвращает баланс/эквити | Баланс: …, Эквити: … (если реализовано в ответе) |
| купи евро (без объёма) | {"action":"BUY","symbol":"EURUSD","volume":0.001} | Покупка микролота по умолчанию | Успешно! … |
| ненужная информация | null / empty | Действие не выполняется (команда не распознана) | Недействительная команда. Пример: … |
Видеодемонстрация
Следующее видео показывает весь конвейер в действии. На нем показаны Telegram, сервер Python, работающий в командной строке, и терминал MetaTrader 5, совершающий сделки в реальном времени.

Рис. 1. Интерфейс Telegram для мобильных телефонов
Измерения задержки и пропускной способности
Тестирование проводилось на домашнем DSL-соединении (16 Мбит/с на скачивание, 2 Мбит/с на загрузку) с сервером Python, установленным на том же компьютере, что и MetaTrader 5. Измерения проводились от отправленного голосового сообщения до ответа в Telegram:
- Загрузка и преобразование голосового сообщения: 0,8 – 1,5 с
- Транскрипция (Google STT): 0,3 – 0,7 с
- Ожидание следующего опроса (в среднем 1 с при интервале 2 с): 0,2 – 1,9 с
- HTTP-запрос + открытие сделки: 50 – 200 мс
- Итоговый ответ: практически мгновенный
Итого: 1,5 – 4,2 с. Пропускная способность на запрос опроса ~200 байт, ответ (пустая очередь) ~20 байт. Полный цикл выполнения сделки потребляет менее 30 КБ, включая загрузку голосового сообщения. Система надежно работает даже при ограниченном соединении со скоростью 128 кбит/с.
Краевые случаи и обработка ошибок
- Терминал MetaTrader 5 закрыт: Очередь Python удерживает команду; советник никогда не выполняет опрос. Telegram ждет 10 секунд и отвечает: “Нет ответа от MetaTrader 5”.
- Недействительный символ: Если символ отсутствует в Market Watch, советник пытается выполнить SymbolSelect. Если он по-прежнему недоступен, сделка завершается с описательной ошибкой (например, “Рынок закрыт”).
- Таймаут сети: WebRequest советника использует таймаут в 2 секунды. Если сервер Python недоступен, GetCommand возвращает пустую строку, и попытка открытия сделки не предпринимается.
- Параллельные команды: Потокобезопасность обеспечивается блокировкой доступа к очереди. Если во время работы советника поступает несколько голосовых сообщений, они ставятся в очередь и обрабатываются по порядку.
- Перегрузка Google STT: Бесплатный API может иногда возвращать пустую строку. Бот отвечает “Не удалось понять”, и пользователь может отправить сообщение повторно.
Инженерные выводы
- Pull-модель опроса является надежной. MQL5 не может принимать входящие соединения, но периодический HTTP-опрос за NAT реализуется очень просто. 2-секундного интервала достаточно для ручной торговли, при этом нагрузка на сервер остается незначительной.
- Удаление нулевых терминаторов имеет решающее значение. Поведение советника StringToCharArray — типичный подводный камень. Неудаление нулевого байта приводит к сбою json.loads на стороне Python.
- Многопоточность должна быть явно указана. Сервер Python HTTP и бот Telegram работают в отдельных потоках. Использование потока-демона для сервера гарантирует корректное завершение работы скрипта при остановке бота.
- Использование микролотов по умолчанию снижает риски. Голосовая команда без указания объёма всегда открывает 0,001 лота, предотвращая случайные крупные ордера.
- Простота важнее сложности. Всё промежуточное ПО представляет собой один файл Python без базы данных, брокера сообщений и зависимостей от внешних сервисов, кроме Telegram. Его легко отлаживать, модифицировать и развертывать на любом VPS под управлением Windows.
Заключение
Мы создали функциональный полуавтоматизированный конвейер голосового сопровождения сделок, который освобождает трейдера от необходимости работать за компьютером. Голосовое сообщение в Telegram, произнесенное в любой точке мира, поступает в промежуточное ПО Python, работающее на ПК под управлением Windows, расшифровывается и преобразуется в команду JSON. Далее оно выполняется советником MQL5, опрашивающим локальный HTTP-сервер — все это за считанные секунды и с минимальной пропускной способностью. Для трейдеров с ограниченными возможностями, которые полагаются на голосовой ввод, или для всех, кому необходимо воспользоваться рыночной возможностью, находясь вдали от клавиатуры, эта система устраняет критически важный барьер доступности.
Код намеренно минималистичен: никаких облачных подписок, внешних баз данных и сложных очередей сообщений. Он достаточно модульный, чтобы можно было заменить движок преобразования речи в текст или даже заменить Telegram другим транспортным протоколом. Хотя такой подход с опросом по запросу эффективен, это не единственный способ удаленного управления — возможности MQL5 могут содержать еще нераскрытые сокровища или будущие функции, предлагающие еще более элегантные решения. Мы рекомендуем вам загрузить прикрепленные файлы, настроить среду, используя контрольный список развертывания, и выполнить несколько тестовых команд. Поделитесь своими открытиями, совершенствованиями и любыми трудностями, с которыми вы столкнулись, в разделе комментариев. Ваши отзывы помогут сформировать дальнейшее исследование решений по обеспечению доступности для трейдеров MetaTrader 5.
Вложения
Перечень файлов
| Название файла | Тип | Версия | Описание |
|---|---|---|---|
| telegram_trading_bot.py | Python 3 script | 1.0 | Голосовой бот Telegram, HTTP-сервер, STT и парсер команд |
| OvercomingAccessibilityIV.mq5 | MQL5 Expert Advisor | 1.0 | Советник с опросом, выполняющий удаленные голосовые команды |
| Jason.mqh | MQL5 Include | – | Сторонний парсер JSON (поместите в MQL5\Include\OvercomingAccessibilityIV\). |
Контрольный список развертывания
- Установите Python 3.10+ и создайте виртуальную среду с помощью python-telegram-bot, speechrecognition и pydub.
- Установите ffmpeg через winget, найдите путь к исполняемым файлам с помощью команды where ffmpeg и установите FFMPEG_DIR в скрипте.
- Получите токен бота Telegram и идентификатор чата; обновите TELEGRAM_TOKEN и ALLOWED_CHAT_ID.
- Поместите JAson.mqh по адресу MQL5/Include/OvercomingAccessibilityIV/, OvercomingAccessibilityIV.mq5 по адресу MQL5/Experts/.
- Скомпилируйте советник и прикрепите его к графику. Добавьте http://127.0.0.1:8082 в разрешенный список WebRequest в MetaTrader 5.
- Запустите скрипт Python и убедитесь, что появились сообщения “HTTP server on 8082” и “Telegram bot polling…”.
- Отправьте голосовую команду через Telegram и проверьте открытие сделки в MetaTrader 5.
Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/22388
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.
Нейросети в трейдинге: Когнитивная инерция в анализе финансовых рынков (модуль прогнозирования)
Встраивание торговой дисциплины в код (Часть 1): Как создать системную дисциплину в реальной торговле с помощью MQL5
Теория графов: Применение алгоритма поиска в ширину (BFS) в торговой системе
Моделирование рынка: Position View (VI)
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования