Преодоление проблем доступности в торговых инструментах на MQL5 (Часть III): Двунаправленное голосовое взаимодействие между трейдером и советником
Каждый раз, когда появляется новый торговый сетап, необходимо прекращать анализ графиков, переключаться на терминал и вручную вводить параметры ордера. Если вы будете находиться вдали от рабочего стола, торговая возможность будет упущена. Если вы физически ослаблены или просто работаете в режиме многозадачности, платформа превращается из инструмента в препятствие. Существующие решения для голосового управления либо зависят от облачных сервисов (риск для конфиденциальности и задержки), либо используют ненадежный опрос файлов, который не работает в реальном времени. В этой статье предлагается простой, локальный, двунаправленный голосовой конвейер: распознавание слов активации, разбор команд на естественном языке, мгновенное открытие сделок и устная обратная связь. Вы перейдете от ручного наведения курсора мыши к работе без помощи рук и голосовому взаимодействию с вашим советником.
Содержание
Введение
Ручной ввод ордеров в MetaTrader 5 точен, но мучительно медлен. Трейдер, заметивший пробой, должен навести курсор мыши, нажать несколько кнопок, ввести размер лота, установить стоп‑лосс – и все это при движении цены. Для алгоритмических трейдеров отсутствие голосовой интеграции вынуждает их оставаться прикованными к экрану. Существующие обходные пути включают в себя:
- Сторонние макрорекордеры: Они имитируют нажатия клавиш, но не могут реагировать на рыночные условия. Они хрупкие и невоспроизводимые.
- API для преобразования речи в текст в облачной среде: Сервисы Google, Amazon или Microsoft обеспечивают высокую точность, но требуют подключения к интернету, вносят задержку и вызывают опасения по поводу конфиденциальности (ваши торговые команды покидают ваш компьютер).
- Опрос на основе файлов: Python‑скрипт записывает команды в текстовый файл, а советник считывает его каждую секунду. Это работает, но страдает от несоответствия кодировки, конфликтов блокировок файлов и заметной задержки. Более того, отсутствует обратная связь — трейдер никогда не знает, была ли команда понята или выполнена.
В этой статье представлено более легкое, автономное решение, которое полностью работает на вашем локальном компьютере. Оно использует Vosk — автономный, легковесный механизм распознавания речи — и двусторонний слой взаимодействия по HTTP. Советник не опрашивает файл; он отправляет WebRequest на локальный Python-сервер, который содержит последнюю распознанную команду. Второй Python-сервер предоставляет обратную связь по преобразованию текста в речь, используя встроенный в Windows синтезатор. В результате получается двусторонний голосовой интерфейс: вы говорите, советник выполняет сделку и озвучивает ответ. Никакого облака, никакой порчи файлов, никаких догадок.
Ограничения файлового подхода (искаженный текст, пропущенные команды, отсутствие подтверждения) устранены. Давайте теперь разработаем архитектуру, которая сделает это возможным.
Архитектура решения

Рис. 1. Системная архитектура двунаправленного советника с голосовым управлением
Система состоит из трех независимых процессов, которые взаимодействуют исключительно по протоколу HTTP на локальном хосте (127.0.0.1). Такая конструкция делает каждый компонент простым и заменяемым.
Listener.py (захват голоса + сервер команд)
Этот скрипт прослушивает микрофон в реальном времени с помощью Vosk. После обнаружения фразы активации (“hello trader”, “okay trader”, “hey trader”) он распознаёт следующую произнесённую фразу, разбирает её в пару “действие-символ” (например, BUY_EURUSD) и сохраняет в глобальной переменной. Он также запускает небольшой HTTP-сервер на порту 8080, который отправляет последнюю команду через GET-запрос к /command и немедленно очищает ее после прочтения.
VoiceControlledEA.mq5 (Советник MetaTrader 5)
Советник не использует опрос файлов. Вместо этого он каждую секунду вызывает WebRequest("GET", "http://127.0.0.1:8080/command"). Если приходит новая команда, советник выполняет соответствующее действие (BUY/SELL/CLOSE/CLOSE_ALL), а затем отправляет POST-запрос на сервер TTS с голосовым подтверждением или сообщением об ошибке.
tts_server.py (Преобразование текста в речь через Flask)
Легковесный сервер Flask прослушивает порт 5001 для POST-запросов в формате JSON, например, {"text":"BUY EURUSD executed"}. Он ставит каждое сообщение в очередь и произносит его, используя System.Speech.Synthesis из PowerShell. Голос естественный, женский (или мужской в зависимости от настроек Windows) и работает сразу же без каких-либо дополнительных драйверов.
Поток данных:
Пользователь произносит фразу активации (например, “hello trader”), делает короткую паузу, затем произносит команду (например, “buy euro usd”)→ → Listener.py распознает фразу активации, переходит в режим прослушивания, затем записывает следующую фразу в качестве команды → преобразует “buy euro usd” в “BUY_EURUSD” → сохраняет команду → следующий WebRequest советника получает ее → советник размещает рыночный ордер BUY по паре EURUSD → советник вызывает TTS-сервер с помощью сообщения “BUY EURUSD executed” → ваш компьютер произносит подтверждение.
Эта архитектура является децентрализованной, тестируемой и добавляет всего около 30 мс задержки — незаметной для ручной голосовой торговли. Пауза между фразой активации и командой полностью гибкая; скрипт ждет следующей произнесенной фразы после слова активации, чтобы вы могли перевести дыхание или немного подумать.
Реализация
В этом разделе мы пошагово разработаем всю двунаправленную голосовую систему. Сначала настраиваем сервер захвата голоса и команд на Python (listener.py). Далее разрабатываем сервер преобразования текста в речь (tts_server.py), который преобразует текст в звуковую обратную связь. Наконец, мы пишем советника на языке MQL5, который получает команды по HTTP и озвучивает ответ. Каждый компонент описан в пронумерованных этапах разработки.
Настройка окружения Python
У вас должен быть установлен Python версии 3.7 или более поздней. Откроем терминал и установим необходимые библиотеки:
pip install vosk sounddevice flask
Также скачаем модель Vosk (достаточно небольшой англоязычной модели) с сайта https://alphacephei.com/vosk/models и поместим её в C:/VoiceEA/vosk-model (или изменим путь в скрипте). Модель занимает около 40 МБ — достаточно лёгкая, чтобы работать на любом ноутбуке без зависимостей от облачных сервисов.
1. Listener.py (захват голоса и сервер команд)
Этот скрипт выполняет три действия: он прослушивает ваш микрофон, обнаруживает фразу активации, за которой следует торговая команда, анализирует эту команду в виде “действие-пара символов” (например, BUY_EURUSD) и отправляет последнюю команду через небольшой HTTP-сервер на порту 8080. Советник будет подключаться к этому серверу для получения команд. В отличие от опроса на основе файлов, этот HTTP-подход обеспечивает мгновенный отклик и позволяет избежать конфликтов кодировки.
Шаг 1. Импорт необходимых модулей
Нам необходима очередь для потокобезопасной связи, sounddevice для захвата звука, json для анализа результатов Vosk и построения грамматики, sys для завершения работы при ошибках, vosk для распознавания речи, http.server для HTTP‑endpoint команд, а также многопоточность для одновременного запуска аудиоцикла и HTTP-сервера. Все это стандартные библиотеки Python, за исключением sounddevice и vosk, которые мы установили ранее.
import queue import sounddevice as sd import json import sys from vosk import Model, KaldiRecognizer from http.server import HTTPServer, BaseHTTPRequestHandler import threading
Шаг 2. Конфигурация и грамматика
Укажем путь к модели Vosk, фразы активации, которые переводят систему в “режим прослушивания команды”, и HTTP-порт. Список грамматики ограничивает возможности распознавания Vosk, что значительно повышает точность и снижает количество ложных срабатываний. Он включает фразы активации, торговые глаголы и псевдонимы символов. Ограничивая распознавание этими конкретными фразами, распознаватель работает быстрее и игнорирует фоновый шум или посторонние разговоры.
MODEL_PATH = "C:/VoiceEA/vosk-model" WAKE_PHRASES = ["hello trader", "okay trader", "hey trader"] PORT = 8080 GRAMMAR = [ "hello trader", "okay trader", "hey trader", "buy euro usd", "sell euro usd", "buy gold", "sell gold", "close all", "close trade", "status", "exit" ] latest_command = ""
Шаг 3. Загрузим модель Vosk и инициализируем распознаватель с помощью грамматики
Загрузим модель из указанной папки. Если модель отсутствует, скрипт завершит работу с сообщением об ошибке. Распознаватель устанавливается на частоту дискретизации 16 кГц (частота, которую мы будем использовать), а грамматика передается в виде строки JSON с помощью SetGrammar(). Это гарантирует, что Vosk будет пытаться распознавать только фразы из нашего списка. Без этого ограничения грамматики Vosk будет пытаться распознать каждое возможное слово на английском языке, что замедляет обработку и увеличивает количество ложных срабатываний.
print("🔄 Loading Vosk model...") try: model = Model(MODEL_PATH) except Exception as e: print(f"❌ Model loading failed: {e}") sys.exit(1) recognizer = KaldiRecognizer(model, 16000) recognizer.SetGrammar(json.dumps(GRAMMAR))
Шаг 4. Настройка аудиопотока и обратный вызов
Потокобезопасная очередь (q) будет хранить аудиофрагменты с микрофона. Функция обратного вызова вызывается каждый раз, когда появляются новые аудиоданные; она просто помещает необработанные байты в очередь. Мы будем использовать необработанный входной поток с частотой 16 кГц, блоки по 8000 байт (примерно 0,5 секунды аудио), 16-битные целочисленные сэмплы, моно. Такой размер блока обеспечивает хороший баланс между быстродействием и использованием ЦП — слишком маленькие блоки приводят к высокому потреблению ресурсов, слишком большие — к задержке.
q = queue.Queue() def callback(indata, frames, time, status): if status: print(status, file=sys.stderr) q.put(bytes(indata))
Шаг 5. Вспомогательные функции для обнаружения активации и анализа команд
Функция wake_detected() проверяет, встречается ли какая-либо фраза активации в качестве подстроки в распознанном тексте. Этот простой подход с подстрокой надежен, поскольку грамматика уже ограничивает распознавание именно этими фразами. Функция extract_command() удаляет фразу активации из текста, так что остается только торговая команда. Функция parse_command() ищет ключевые слова, такие как “buy”, “sell”, “close”, а также псевдонимы символов (например, “euro usd” → EURUSD, “gold” → XAUUSD) и возвращает стандартизированную строку команды (BUY_EURUSD, SELL_XAUUSD, CLOSE или CLOSE_ALL). Если пользователь после фразы активации произносит “buy euro usd”, на выходе получается “BUY_EURUSD” — формат, который советник может легко разделить и выполнить.
def wake_detected(text): return any(phrase in text for phrase in WAKE_PHRASES) def extract_command(text): for phrase in WAKE_PHRASES: if phrase in text: return text.replace(phrase, "").strip() return text.strip() def parse_command(text): words = text.lower().split() action = None symbol = None if "buy" in words: action = "BUY" elif "sell" in words: action = "SELL" elif "close" in words: if "all" in words: return "CLOSE_ALL" return "CLOSE" if any(w in words for w in ["euro", "eur", "usd", "eu", "eurodollar"]): symbol = "EURUSD" elif any(w in words for w in ["gold", "xau"]): symbol = "XAUUSD" if not action: return None if action in ["BUY", "SELL"] and not symbol: print("⚠️ No valid symbol detected") return None if action in ["BUY", "SELL"]: return f"{action}_{symbol}" return action
Шаг 6. Основной аудиоцикл
Функция audio_loop() выполняется в отдельном потоке и открывает поток с микрофона с помощью sd.RawInputStream. Она непрерывно считывает аудиофрагменты из очереди и передает их распознавателю. Когда распознана полная фраза, она проверяет текст. Если в данный момент мы не ожидаем команду, мы ищем фразу активации. Как только фраза активации обнаружена, мы устанавливаем флаг и ждем следующую фразу, которая рассматривается как команда. Команда извлекается, анализируется и сохраняется в глобальной переменной latest_command. Затем флаг сбрасывается. Этот двухэтапный процесс (слово активации, затем команда) предотвращает реагирование советника на каждое случайное слово, которое вы произносите — пересылаются только преднамеренные команды.
def audio_loop(): global latest_command listening_for_command = False with sd.RawInputStream(samplerate=16000, blocksize=8000, dtype='int16', channels=1, callback=callback): while True: data = q.get() if recognizer.AcceptWaveform(data): result = json.loads(recognizer.Result()) text = result.get("text", "").lower().strip() if not text or len(text.split()) < 2: continue print(f"🗣 You said: {text}") if not listening_for_command: if wake_detected(text): print("👂 Wake phrase detected. Listening for command...") listening_for_command = True continue raw_command = extract_command(text) parsed_command = parse_command(raw_command) if not parsed_command: print("❌ Invalid command") listening_for_command = False continue print(f"✅ Parsed Command: {parsed_command}") latest_command = parsed_command listening_for_command = False
Шаг 7. HTTP-сервер для обработки команды
Мы определяем простой обработчик HTTP-запросов, который отвечает на GET-запрос /command. Он возвращает текущее значение latest_command в виде обычного текста, а затем очищает его (таким образом, каждая команда загружается только один раз). Любой другой путь возвращает ошибку 404. Метод log_message переопределен для подавления спама в консоли. Этот легковесный сервер работает только на localhost, поэтому к нему не могут получить доступ внешние машины — безопасность сохраняется.
class CommandHandler(BaseHTTPRequestHandler): def do_GET(self): global latest_command if self.path == "/command": self.send_response(200) self.send_header("Content-Type", "text/plain") self.end_headers() self.wfile.write(latest_command.encode("ascii")) latest_command = "" # clear after read else: self.send_response(404) self.end_headers() def log_message(self, format, *args): pass # suppress HTTP logs def start_server(): server = HTTPServer(("127.0.0.1", PORT), CommandHandler) print(f"🌐 HTTP server running on http://127.0.0.1:{PORT}") server.serve_forever()
Шаг 8. Запустим оба потока и запустим сервер
В основном блоке мы запускаем аудиоцикл как поток-демон (так что он останавливается при завершении основной программы), а затем запускаем HTTP-сервер (который работает бесконечно). Такое разделение позволяет захватывать звук с микрофона, а веб-серверу работать независимо — аудиопоток никогда не блокирует HTTP-ответы, и наоборот.
if __name__ == "__main__": threading.Thread(target=audio_loop, daemon=True).start() start_server()
2. tts_server.py – Сервер преобразования текста в речь
Следующий компонент — это сервер Flask, который принимает POST-запросы, содержащие текстовое сообщение, и произносит его вслух, используя встроенный в Windows синтез речи. Поскольку множество запросов может поступать один за другим (например, приветственное сообщение, за которым сразу следует подтверждение сделки), мы используем очередь и фоновый рабочий поток для озвучивания сообщений по одному, предотвращая наложения и сбои. Такая конструкция также гарантирует, что советнику никогда не придется ждать завершения озвучивания — POST-запрос возвращается немедленно.
Шаг 1. Импорт библиотек
Для веб-сервера нам нужен Flask, для рабочего потока — очередь и многопоточность, а для вызова механизма озвучивания PowerShell — подпроцесс. Внешняя библиотека TTS не требуется — всё нативно есть в Windows.
from flask import Flask, request, jsonify import queue import threading import subprocess
Шаг 2. Создадим приложение Flask и очередь сообщений
Приложение Flask будет прослушивать HTTP-запросы. В message_queue хранятся строки, которые необходимо озвучить. Это отделяет входящие запросы от фактического синтеза речи, поэтому конечная точка возвращается немедленно, не дожидаясь завершения озвучивания.
app = Flask(__name__) message_queue = queue.Queue()
Шаг 3. Функция синтеза речи PowerShell
Функция powershell_speak() создает команду PowerShell, которая загружает сборку System.Speech, создает SpeechSynthesizer и озвучивает заданный текст. Мы экранируем двойные кавычки внутри текста, поскольку команда заключается в одинарные кавычки. Вызов subprocess.run запускает PowerShell в фоновом режиме (окно не появляется). Этот метод более надежен, чем pyttsx3, который часто страдает от проблем с многопоточностью и ошибок типа “цикл выполнения уже запущен”.
def powershell_speak(text): """Speak text using PowerShell's built-in speech synthesis.""" # Escape double quotes inside the text text = text.replace('"', '`"') # Build PowerShell command ps_command = f"Add-Type -AssemblyName System.Speech; $synth = New-Object System.Speech.Synthesis.SpeechSynthesizer; $synth.Speak('{text}')" # Run PowerShell silently subprocess.run(["powershell", "-Command", ps_command], capture_output=True)
Шаг 4. Фоновый рабочий поток
Рабочий процесс tts_worker работает в бесконечном цикле, ожидая появления сообщения в очереди. Когда сообщение поступает, он выводит подтверждение (для отладки) и вызывает powershell_speak. Любое исключение перехватывается и выводится на экран, но воркер продолжает работу. task_done() сигнализирует о том, что сообщение обработано, что полезно, если нам когда-либо потребуется дождаться опустошения очереди перед завершением работы.
def tts_worker(): while True: text = message_queue.get() print(f"Speaking: {text}") try: powershell_speak(text) except Exception as e: print(f"TTS error: {e}") message_queue.task_done()
Шаг 5. Запустим рабочий поток как демон
Мы создаем новый поток-демон, который запускает воркер. Потоки-демоны автоматически завершают работу при остановке основной программы, что удобно для корректного завершения работы с помощью Ctrl+C. Без этого пришлось бы вручную завершать поток.
# Start worker thread threading.Thread(target=tts_worker, daemon=True).start()
Шаг 6. Конечная точка Flask для получения сообщений
Маршрут /speak ожидает POST-запрос с телом JSON, содержащим текстовое поле. Если поле отсутствует, он возвращает ошибку 400. В противном случае он помещает текст в очередь и возвращает ответ об успешном выполнении. Конечная точка возвращает результат практически мгновенно, поскольку фактическая обработка речи осуществляется фоновым рабочим потоком. Это означает, что советник никогда не ждет подтверждения запроса от TTS-сервера более нескольких миллисекунд.
@app.route('/speak', methods=['POST']) def handle_speak(): data = request.get_json() if not data or 'text' not in data: return jsonify({'error': 'Missing text'}), 400 text = data['text'] message_queue.put(text) return jsonify({'status': 'ok'})
Шаг 7. Запуск сервера Flask
Сервер запускается на IP-адресе 127.0.0.1 (localhost) порту 5001. Мы используем threaded=False, чтобы сервер оставался однопоточным, что упрощает нашу модель многопоточности. Для локального сервера, обрабатывающего не более нескольких запросов в секунду, этого вполне достаточно.
if __name__ == '__main__': app.run(host='127.0.0.1', port=5001, threaded=False)
3. VoiceControlledEA.mq5—Советник
Наконец, мы пишем советника на MQL5, который опрашивает командный сервер каждую секунду, открывает сделки и отправляет голосовые подтверждения на сервер TTS. Советник использует класс CTrade для управления ордерами и WebRequest для HTTP-связи. Код соответствует стилю MetaEditor: отступы в два пробела, //--- для комментариев в строках и описательные заголовки функций.
Шаг 1. Метаданные и включенные файлы (includes)
Мы включаем библиотеку Trade.mqh для торговых функций. Директивы #property устанавливают авторские права, версию и краткое описание. Этот заголовок необходим для корректного отображения каждого советника в навигаторе MetaTrader.
//+------------------------------------------------------------------+ //| VoiceControlledEA.mq5 | //| Voice commands via HTTP + TTS feedback (port 5001) | //+------------------------------------------------------------------+ #property copyright "Clemence Benjamin" #property version "1.00" #property strict #include <Trade/Trade.mqh>
Шаг 2. Входные параметры
Пользователь может настроить размер лота, проскальзывание и магическое число непосредственно в окне свойств советника. Эти переменные доступны в качестве входных, чтобы трейдеры могли изменять их без перекомпиляции кода.
//--- Input parameters input double InpLotSize = 0.1; input int InpSlippage = 30; input ulong InpMagicNumber = 123456;
Шаг 3. Глобальные переменные
Мы сохраняем последнюю выполненную команду, чтобы избежать повторов, объект CTrade для торговых операций и временную метку для управления частотой опроса. Объект CTrade обрабатывает отправку ордеров и закрытие позиций со встроенной проверкой ошибок.
//--- Internal variables string lastCommand = ""; CTrade trade; datetime lastRequestTime = 0;
Шаг 4. OnInit: инициализирует советник и произносит приветственное сообщение
В OnInit() мы устанавливаем магическое число, проскальзывание и политику исполнения ордеров для объекта CTrade. Затем мы выводим инструкции и отправляем приветственное сообщение на сервер TTS для подтверждения работоспособности соединения. Если вы слышите “Голосовой советник запущен по паре EURUSD”, значит, конвейер TTS работает.
//+------------------------------------------------------------------+ //| Expert initialization | //+------------------------------------------------------------------+ int OnInit() { trade.SetExpertMagicNumber(InpMagicNumber); trade.SetDeviationInPoints(InpSlippage); trade.SetTypeFilling(ORDER_FILLING_FOK); Print("====== VOICE EA (WebRequest + TTS) STARTED ======"); Print("Required allowed URLs in MT5 Options:"); Print(" - http://127.0.0.1:8080 (command server)"); Print(" - http://127.0.0.1:5001 (TTS server)"); //--- Welcome message Speak("Voice controlled expert advisor started on " + _Symbol); return(INIT_SUCCEEDED); }
Шаг 5. OnTick: выполняет опрос сервера команд один раз в секунду
OnTick() проверяет время с момента последнего опроса; если прошло менее одной секунды, возвращается. В противном случае вызывает FetchCommand() для получения последней команды. Если получена новая, непустая команда, она выводится на экран, произносится “Command received: …”, обрабатывается и обновляется параметр lastCommand. Опрос каждую секунду — это хороший баланс между быстродействием и нагруженностью сети.
//+------------------------------------------------------------------+ //| Expert tick | //+------------------------------------------------------------------+ void OnTick() { if(TimeCurrent() - lastRequestTime < 1) return; lastRequestTime = TimeCurrent(); string cmd = FetchCommand(); if(cmd == "" || cmd == lastCommand) return; Print("🎯 New command: ", cmd); Speak("Command received: " + cmd); ProcessCommand(cmd); lastCommand = cmd; }
Шаг 6. FetchCommand: HTTP GET-запрос к серверу-слушателю
Эта функция отправляет WebRequest по адресу http://127.0.0.1:8080/command и возвращает ответ в виде строки. При статусе, отличном от 200, возвращает пустую строку. Для уменьшения количества сообщений об ошибках в логах, сообщение об ошибке выводится только один раз каждые 50 тиков. Функция WebRequest — единственный способ в MQL5 выполнять HTTP-запросы — она синхронная, но достаточно быстрая для локального сервера.
//+------------------------------------------------------------------+ //| Fetch command from Python HTTP server (port 8080) | //+------------------------------------------------------------------+ string FetchCommand() { string url = "http://127.0.0.1:8080/command"; string headers = ""; char postData[]; uchar resultData[]; string resultHeaders = ""; int timeout = 1000; int res = WebRequest("GET", url, headers, timeout, postData, resultData, resultHeaders); if(res != 200) { static int errorCount = 0; if(errorCount++ % 50 == 0) Print("WebRequest error code: ", res, " (Python server running?)"); return ""; } string result = CharArrayToString(resultData, 0, WHOLE_ARRAY, CP_UTF8); StringTrimLeft(result); StringTrimRight(result); return result; }
Шаг 7. Speak: отправка сообщения на сервер TTS
Эта функция формирует JSON-тело запроса {"text":"message"} и отправляет методом POST на сервер TTS через порт 5001. Она экранирует двойные кавычки, устанавливает тип содержимого и использует таймаут в 3 секунды. Если запрос не удался, она выводит сообщение об ошибке и возвращает false. Функцию Speak() можно скопировать в любой другой советник — она полностью самодостаточна.
//+------------------------------------------------------------------+ //| Send text to TTS server via WebRequest | //+------------------------------------------------------------------+ bool Speak(string message) { //--- The TTS server must be running: python tts_server.py (Flask on port 5001) string url = "http://127.0.0.1:5001/speak"; string headers = "Content-Type: application/json\r\n"; //--- Escape double quotes in message (if any) StringReplace(message, "\"", "\\\""); //--- Build JSON payload string data = "{\"text\":\"" + message + "\"}"; char post_data[]; char result_data[]; string result_headers; ArrayResize(post_data, StringToCharArray(data, post_data, 0, WHOLE_ARRAY) - 1); int timeout = 3000; // 3 seconds int res = WebRequest("POST", url, headers, timeout, post_data, result_data, result_headers); if(res == -1) { int err = GetLastError(); Print("WebRequest TTS error: ", err, " - Message: ", message); //--- Common errors: 4060 - URL not allowed, 4062 - connection refused return false; } return true; }
Шаг 8. ProcessCommand: интерпретирует и выполняет команду
Функция ProcessCommand() обрабатывает команды CLOSE_ALL, CLOSE и ACTION_SYMBOL (например, BUY_EURUSD). Она разделяет команду, проверяет существование символа (при необходимости добавляя его в “Обзор рынка (Market Watch)”), получает текущий тик и вызывает trade.Buy() или trade.Sell(). После выполнения выводит сообщение об успехе или неудаче. Эта функция также обрабатывает случай, когда символ еще не добавлен в “Обзор рынка (Market Watch)” — она автоматически добавляет его и повторяет попытку.
//+------------------------------------------------------------------+ //| Process and execute command | //+------------------------------------------------------------------+ void ProcessCommand(string cmd) { if(cmd == "CLOSE_ALL") { CloseAllPositions(); Speak("All positions closed"); return; } if(cmd == "CLOSE") { ClosePositionsBySymbol(Symbol()); Speak("Closed positions for " + Symbol()); return; } string parts[]; int count = StringSplit(cmd, '_', parts); if(count != 2) { string errMsg = "Invalid command: " + cmd; Print("❌ ", errMsg); Speak(errMsg); return; } string action = parts[0]; string symbol = parts[1]; if(!SymbolInfoInteger(symbol, SYMBOL_EXIST)) { Print("❌ Symbol ", symbol, " not found. Adding..."); SymbolSelect(symbol, true); Sleep(100); if(!SymbolInfoInteger(symbol, SYMBOL_EXIST)) { string errMsg = "Symbol " + symbol + " not available"; Print("❌ ", errMsg); Speak(errMsg); return; } } MqlTick tick; if(!SymbolInfoTick(symbol, tick)) { string errMsg = "No price for " + symbol; Print("❌ ", errMsg); Speak(errMsg); return; } bool res = false; if(action == "BUY") { Print("Attempting BUY ", InpLotSize, " lots of ", symbol, " at ", tick.ask); res = trade.Buy(InpLotSize, symbol, tick.ask, 0, 0, "Voice Buy"); } else if(action == "SELL") { Print("Attempting SELL ", InpLotSize, " lots of ", symbol, " at ", tick.bid); res = trade.Sell(InpLotSize, symbol, tick.bid, 0, 0, "Voice Sell"); } else { string errMsg = "Unknown action: " + action; Print("⚠️ ", errMsg); Speak(errMsg); return; } if(res) { string successMsg = action + " " + symbol + " executed"; Print("✅ ", successMsg); Speak(successMsg); } else { int error = GetLastError(); string errMsg = "Trade failed, error " + IntegerToString(error); Print("❌ ", errMsg); Speak(errMsg); } }
Шаг 9. Закрывает все позиции и закрывает позицию по символу
Две вспомогательные функции закрывают позиции: CloseAllPositions() закрывает все открытые позиции независимо от символа; ClosePositionsBySymbol() закрывает только те, которые соответствуют заданному символу. Они выполняют перебор PositionsTotal() и используют trade.PositionClose(). Эти функции также выводят подтверждение, чтобы вы знали, что действие выполнено.
//+------------------------------------------------------------------+ //| Close all positions | //+------------------------------------------------------------------+ void CloseAllPositions() { int closed = 0; for(int i = PositionsTotal() - 1; i >= 0; i--) { ulong ticket = PositionGetTicket(i); if(ticket > 0 && PositionSelectByTicket(ticket)) { if(trade.PositionClose(ticket)) closed++; else Print("❌ Failed to close position ", ticket, ", error: ", GetLastError()); } } Print("✅ Closed ", closed, " position(s)"); } //+------------------------------------------------------------------+ //| Close positions by symbol | //+------------------------------------------------------------------+ void ClosePositionsBySymbol(string targetSymbol) { int closed = 0; for(int i = PositionsTotal() - 1; i >= 0; i--) { ulong ticket = PositionGetTicket(i); if(ticket > 0 && PositionSelectByTicket(ticket)) { if(PositionGetString(POSITION_SYMBOL) == targetSymbol) { if(trade.PositionClose(ticket)) closed++; else Print("❌ Failed to close ", targetSymbol, " position ", ticket, ", error: ", GetLastError()); } } } Print("✅ Closed ", closed, " position(s) for ", targetSymbol); } //+------------------------------------------------------------------+
Включение WebRequest и запуск системы
Перед прикреплением советника запустите оба сервера Python в отдельных терминалах:
python listener.py python tts_server.py
В MetaTrader 5 перейдите в Инструменты → Параметры → Советники, установите флажок “Разрешить веб-запросы для указанных URL-адресов” и добавьте http://127.0.0.1:8080 и http://127.0.0.1:5001. Далее прикрепите советник к любому графику (например, EURUSD M5). Вы услышите приветственное сообщение. Скажите “hello trader buy euro usd” (Привет, трейдер, покупай евро/USD). Советник должен разместить рыночный ордер на покупку и ответить сообщением “BUY EURUSD executed” (Операция покупки EURUSD исполнена).
Все файлы с кодом можно найти во вложениях. Теперь система готова к торговле на реальном или демо-счете с полным голосовым управлением и звуковой обратной связью.
Тестирование и результаты
Видеодемонстрация
Система протестирована на компьютере с ОС Windows 11 и установленной программой MetaTrader 5 сборки 4100. Видеозапись демонстрации в реальном времени прилагается. Следующие сценарии были успешно выполнены:
- Фраза активации “hello trader”, за которой следует “buy euro usd”. Советник разместил рыночный ордер на покупку EURUSD, и было слышно голосовое подтверждение “BUY EURUSD executed”.
- “hey trader sell gold” – ордер на продажу по паре XAUUSD с голосовым подтверждением.
- “okay trader close trade” – закрыл все позиции по текущему символу графика (EURUSD).
- “hello trader close all” – закрыл все позиции по всем символам.
- Недействительная команда (например, “hello trader buy nothing”) – советник ответил “Invalid command: BUY_”.
- Символ отсутствует в “Обзоре рынка (Market Watch)” — советник автоматически добавил его и повторил попытку.
Задержка от голосовой команды до открытия сделки в среднем составляла 0,8–1,2 секунды, в основном из-за 1-секундного интервала опроса и времени распознавания речи. Проблем с кодированием или блокировкой файлов не возникало. Сервер TTS надежно отвечал даже при последовательных командах.
Обработка пограничных случаев: Если сервер команд Python не запущен, советник выводит ошибку WebRequest каждые 50 тиков, но продолжает работу без зависания. Если сервер TTS находится в офлайне, советник продолжает торговлю, но регистрирует сбой – торговля никогда не блокируется из-за отсутствия голосовой обратной связи.
Заключение
Вы создали полноценный двунаправленный голосовой интерфейс для MetaTrader 5. То, что раньше было ручным, трудоемким и недоступным процессом, теперь стало бесконтактным и с голосовой обратной связью. Решение полностью локальное — никаких облачных API-ключей, никаких ежемесячных платежей, никаких проблем с конфиденциальностью. Три компонента (захват голоса, сервер команд, сервер преобразования текста в речь) работают на любом компьютере с ОС Windows, на которой установлен Python и модель Vosk.
Основные выводы:
- Воспроизводимый, открытый исходный код голосового конвейера, который можно расширить для любой торговой логики.
- Подтверждение каждой команды голосом в реальном времени — вам никогда не придется гадать, понял ли вас советник.
- Надежная доставка команд по HTTP вместо нестабильного опроса файлов.
- Возможность торговать, не используя клавиатуру или сосредоточившись на анализе графиков.
Теперь вы можете подключить советника к любому графику, запустить два сервера Python и начать говорить. Система понимает команды “buy”, “sell”, “close”, and “close all” для EURUSD и XAUUSD. Вы можете легко расширить грамматику и логику синтаксического анализа, чтобы поддерживать больше символов, размеров лотов или даже условные ордера (например, “buy 0.2 lots gold”). Архитектура модульная — замените Vosk другим движком STT или замените TTS PowerShell на более естественную библиотеку голосов, не затрагивая советника.
Вы перешли от статической, привязанной к вводу торговой системы к динамичной, интерактивной среде с голосовым управлением. Препятствие в виде ручного ввода ордеров исчезло. Теперь произносите свои сделки вслух, и советник будет исполнять их и озвучивать результат.
Полное руководство по развертыванию:
Структура папки и расположение модели
Создайте C:\VoiceEA . Поместите файлы listener.py и tts_server.py в эту папку. Загрузите небольшую англоязычную модель Vosk с alphacephei.com/vosk/models (имя файла vosk-model-small-en-us-0.15.zip). Распакуйте содержимое zip-архива непосредственно в C:\VoiceEA. После распаковки вы увидите папку с именем vosk-model-small-en-us-0.15. Переименуйте эту папку в vosk-model. Конечный путь должен быть C:\VoiceEA\vosk-model, и внутри неё вы должны увидеть файлы типа am.bin, conf/, graph/ и т. д. Не создавайте дополнительные подпапки — скрипт ожидает, что модель будет находиться именно в этом месте.
Открытие терминала в нужной папке
Откройте Windows Explorer и перейдите в C:\VoiceEA. Щёлкните в адресной строке, введите cmd и нажмите Enter. Окно командной строки откроется непосредственно в этой папке. Это самый простой способ для начинающих. В качестве альтернативы, нажмите Windows+R, введите cmd, затем введите cd C:\VoiceEA и нажмите Enter.
Запуск двух серверов Python
В первой командной строке (уже в C:\VoiceEA) введите: python listener.py и нажмите Enter. Подождите, пока не увидите сообщение “🌐 HTTP server running on http://127.0.0.1:8080”. Оставьте этот терминал открытым. Откройте вторую командную строку, используя тот же метод (снова перейдите в папку C:\VoiceEA). Во втором терминале введите: python tts_server.py и нажмите Enter. Вы увидите “Running on http://127.0.0.1:5001”. Оставьте оба терминала открытыми в фоновом режиме.
Установка необходимых пакетов Python
Если вы не установили зависимости, в каждом терминале (или в новом) введите: pip install vosk sounddevice flask. Дождитесь окончания установки. Это нужно сделать только один раз.
Настройка MetaTrader 5
В MetaTrader 5 перейдите в Tools → Options → Expert Advisors. Отметьте галочкой “Allow WebRequest for listed URLs”. В списке добавьте два адреса: http://127.0.0.1:8080 and http://127.0.0.1:5001. Нажмите OK. Скопируйте файл VoiceControlledEA.mq5 в папку Experts на MetaTrader 5 (обычно C:\Users\YourUsername\AppData\Roaming\MetaQuotes\Terminal\InstanceID\MQL5\Experts). Откройте MetaEditor (F4), найдите советник в Навигаторе, щелкните правой кнопкой мыши и скомпилируйте (F7). Прикрепите советник к любому графику (например, EURUSD M5). Убедитесь, что кнопка AutoTrading (зеленый треугольник) включена.
Тестирование системы
Четко произнесите в микрофон: “hello trader buy euro usd”. На терминале слушателя отобразится “👂 Wake phrase detected”, а затем “✅ Parsed Command: BUY_EURUSD”. Советник разместит рыночный ордер на покупку EURUSD, и ваш компьютер произнесет “BUY EURUSD executed”. Если вы не слышите речь, проверьте громкость Windows и убедитесь, что на терминале сервера TTS отображает “Speaking: BUY EURUSD executed”. Если сделка не исполняется, убедитесь, что автоматическая торговля разрешена и что символ EURUSD отображается в “Обзоре рынка (Market Watch)”.
Поиск и устранение распространенных ошибок
- “Model loading failed” (Сбой загрузки модели): Папка vosk-model отсутствует или имеет некорректное название. Убедитесь, что папка находится точно по адресу C:\VoiceEA\vosk-model и содержит файлы модели напрямую (а не в подпапке).
- Ошибка WebRequest 4060: URL-адрес не допускается в параметрах MetaTrader 5 — добавьте оба адреса, как показано выше.
- Ошибка WebRequest 4062: Серверы Python не запущены — убедитесь, что оба терминала открыты и отображают сообщения о запущенных серверах.
- “No price for XAUUSD”: Символ не находится в “Обзоре рынка (Market Watch)”. Щелкните правой кнопкой мыши на Market Watch, выберите “Показать все (Show All)”, затем найдите пару XAUUSD и включите. Советник автоматически добавит этот символ при следующей команде.
- Python not recognised: Введите команду `python -version`, чтобы подтвердить установку Python. В противном случае загрузите и установите Python с сайта python.org, и во время установки поставьте галочку на “Add Python to PATH”.
Вложения
| Название файла | Тип | Версия | Краткое описание |
|---|---|---|---|
| listener.py | Python-скрипт | 1.0 | Захват голоса, распознавание слов активации, анализ команд, HTTP-сервер команд (порт 8080). |
| tts_server.py | Python-скрипт | 1.0 | Сервер преобразования текста в речь на основе Flask с использованием PowerShell (порт 5001). |
| VoiceControlledEA.mq5 | Советник MQL5 | 1.0 | Советник, который опрашивает командный сервер и отвечает через TTS. |
Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/21533
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.
Особенности написания Пользовательских Индикаторов
Рыночные секреты Ларри Уильямса (Часть 8): Объединение волатильности, структуры и временных фильтров
Адаптивная архитектура Smart Money (ASMA): Интеграция логики SMC с анализом сентимента для динамического переключения стратегий
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования