Преодоление проблем доступности в торговых инструментах на MQL5 (Часть V): Торговля с помощью жестов на основе компьютерного зрения
Во время выхода новостей, вызывающих высокую волатильность, трейдер с двигательным нарушением может заметить пробой, но упустить момент входа в сделку, поскольку из-за тремора руки курсор смещается. Через пять секунд цена откатывается, и возможность упущена. Постановка задачи ясна: для торговых инструментов MQL5 отсутствует стандартный способ ввода с низкой задержкой без использования мыши и клавиатуры, который учитывал бы двигательные нарушения в условиях рыночного стресса. В этой статье представлена торговая система с управлением жестами, которая использует веб-камеру, MediaPipe Hands и OpenCV для преобразования простых жестов рук (вытянутый указательный палец, поднятый большой палец, кулак) непосредственно в команды исполнения сделок MQL5, позволяя полностью отказаться от мыши и клавиатуры.
Сквозная задержка от обнаружения жеста до исполнения сделок измерялась на графике демо-счета в реальных рыночных условиях. Результаты представляют собой среднее значение по 100 тестам для каждого типа жеста, проведенным на ноутбуке среднего класса (Intel i7-1165G7, 16 ГБ ОЗУ, веб-камера с интерфейсом USB 3.0 и частотой 30 кадров в секунду).
| Жест | Средняя задержка (мс) | 95-й процентиль (мс) | Точность распознавания жестов (%) |
|---|---|---|---|
| Вытянутый указательный палец (покупка) | 68 | 92 | 97,2 |
| Поднятый большой палец (продажа) | 72 | 98 | 96,8 |
| Кулак (закрытие всех позиций) | 85 | 110 | 95,4 |
Параметры настройки: порог уверенности MediaPipe = 0,7, разрешение кадра OpenCV = 640×480, интервал опроса глобальной переменной MQL5 = 10 мс. Все измерения проводились на демо-счете в режиме реального времени с использованием конвейера GlobalVariable.
Содержание
- Почему существующие методы ввода не подходят для трейдеров с двигательными нарушениями
- Класс GestureTrader — бесконтактный мост между MediaPipe и MQL5
- Обзор архитектуры системы
- Подробный анализ существующих ограничений в области доступности
- Концептуальное проектирование — математический конвейер
- Реализация конвейера распознавания жестов — пошаговое руководство
- Объединяем всё воедино — запуск советника с управлением жестами
- Тесты производительности — задержка и точность
- Заключение
- Практические итоги — таблица выводов и компонентов
Почему существующие методы ввода не подходят для трейдеров с двигательными нарушениями
При выходе новостей, вызывающих высокую волатильность, трейдер с двигательным нарушением может заметить пробой, но упустить момент входа в рынок, поскольку дрожание руки приводит к смещению курсора. Через пять секунд цена уже откатывается, и возможность упущена. Умножьте это на дюжину сделок в день, и совокупный эффект будет не только эмоциональным, но и финансовым.
По данным Всемирной организации здравоохранения, более миллиарда человек живут с той или иной формой инвалидности. В торговле двигательные нарушения — будь то вследствие эссенциального тремора, болезни Паркинсона, церебрального паралича или травмы от повторяющихся нагрузок — превращают наведение курсора и щелчки мышью, требующие ловкости рук, в препятствие. Стандартные инструменты MQL5 требуют от пользователя взаимодействия с графическим интерфейсом: нажатия кнопки, перетаскивания линии стоп-лосса, двойного щелчка для изменения ордера. Эти действия требуют точных, быстрых и воспроизводимых мелких движений рук. В условиях высокой волатильности напряжение усиливает тремор и усугубляет последствия каждого промаха при щелчке мышью.
Проблема не сводится только к упущенным сделкам. Она также связана с истощением от борьбы с собственным телом, пока рынок продолжает двигаться. Многие талантливые трейдеры отказываются от активных стратегий, поскольку средства ввода — мышь и клавиатура — не приспособлены к их физиологическим особенностям.
Суть проблемы ясна: для торговых инструментов MQL5 отсутствует стандартный метод ввода данных без использования рук с низкой задержкой, который учитывал бы двигательные нарушения пользователей в периоды рыночного стресса. Существующие средства обеспечения доступности — голосовые команды, отслеживание движения глаз или специально адаптированное оборудование — либо ненадёжны в условиях шума, либо чрезмерно дороги, либо требуют использования сложных нестандартных программных стеков, которые несовместимы с архитектурой MetaTrader.
В частях I–IV мы решали вопросы доступности четырьмя способами: (1) контекстные голосовые оповещения, (2) динамический синтез речи (TTS) в советниках, (3) двунаправленные голосовые команды и (4) торговля с помощью голосовых сообщений в Telegram. Каждая часть позволяла преодолеть реальную преграду. Оставалась самая сложная проблема: как торговать, если вы вообще не можете уверенно пользоваться руками или если движения рук слишком нестабильны для работы с мышью.
В этой части решением становится система торговли с помощью жестов на основе компьютерного зрения. Благодаря сочетанию стандартной веб-камеры, фреймворка MediaPipe Hands для отслеживания рук в режиме реального времени и библиотеки OpenCV для предварительной обработки изображений простые жесты рук — вытянутый указательный палец для команды "Купить", поднятый большой палец для команды "Продать" и кулак для команды "Закрыть все" — напрямую преобразуются в торговые команды MQL5 для исполнения сделок. Это полностью устраняет необходимость в мыши и клавиатуре. Пользователь видит изображение своей руки поверх графика; он перемещает указательный палец в виртуальную зону "Купить", а при жесте "поднятый большой палец" советник выставляет ордер на продажу.
В этой части создаётся именно такая система: полноценная библиотека MQL5, которую можно протестировать и которая получает данные отслеживания рук из внешнего процесса Python через общие глобальные переменные и исполняет сделки с помощью класса CTrade. Ниже представлены архитектура, результаты тестирования задержек и компромиссы, благодаря которым распознавание жестов остается достаточно быстрым даже в самые нестабильные моменты — как правило, время от жеста до размещения рыночного ордера составляет менее 100 миллисекунд.
Класс GestureTrader — бесконтактный мост между MediaPipe и MQL5
Проанализировав различные варианты отслеживания рук, мы пришли к выводу, что наиболее практичный путь к надёжной торговле с помощью жестов — передать задачу отслеживания рук специализированной библиотеке. MediaPipe Hands предоставляет 21 ключевую точку для каждой руки с задержкой порядка миллисекунд, а его интерфейс для Python достаточно стабилен для промышленного использования. Таким образом, задача состоит в том, чтобы детерминированно и безопасно преобразовывать данные этих ключевых точек в торговые команды MQL5.
Решением является новый класс MQL5 под названием CGestureTrader. Он отвечает за три задачи:
- Прием событий жестов от внешнего процесса Python через глобальные переменные MQL5.
- Сопоставление распознанных жестов с торговыми действиями (BUY, SELL, CLOSE, MODIFY_SL, MODIFY_TP) и состоянием DO_NOTHING/NEUTRAL.
- Исполнение сделок через объект CTrade с предотвращением случайного повторного исполнения с помощью простого механизма флага занятости.
На приведенной ниже схеме показан поток данных. Python захватывает видеопоток с веб-камеры, запускает MediaPipe, классифицирует жест и записывает (1) GESTURE_CODE и (2) GESTURE_TIMESTAMP в глобальные переменные MQL5 для подавления повторных срабатываний. Советник MQL5 в обработчике OnTimer или OnTick считывает эти переменные и вызывает метод CGestureTrader::ProcessGesture().

Рис. 1. Блок-схема, поток данных в системе компьютерного зрения
Проектирование класса и компромиссы
Одним из ключевых проектных решений стало использование булевого флага занятости (m_busyFlag). Без этого флага, если пользователь удерживает жест "КУПИТЬ" в течение 500 мс, советник будет отправлять ордер на покупку на каждом тике. Если устанавливать флаг после первого успешного исполнения сделки и требовать для его сброса НЕЙТРАЛЬНЫЙ жест (код 0), система будет работать в режиме однократного срабатывания: каждый жест инициирует не более одной сделки, пока пользователь не вернет кисть в раскрытое, расслабленное положение.
Целочисленные глобальные переменные были выбраны вместо общих файлов или файлов, отображаемых в память, поскольку функции GlobalVariableSet/Get работают на уровне терминала и являются потокобезопасными. Накладные затраты обычно составляют менее 1 мс, что приемлемо для системы, которая уже работает с частотой 30 кадров в секунду (≈33 мс на кадр). Использование именованного канала через DLL с вызовами WinAPI может сократить задержку на 2–4 мс. Однако для этого потребуется поставлять скомпилированную DLL, что усложнит установку. Подход с использованием глобальных переменных не требует никаких внешних зависимостей, помимо стандартной установки MQL5.
Класс GestureTrader
В приведённом ниже листинге представлены полное определение и реализация класса. Ключевые моменты поясняются непосредственно в коде.
//+------------------------------------------------------------------+ //| GestureTrader.mqh | //| Copyright 2026, MetaQuotes Ltd. | //| http://www.mql5.com| //+------------------------------------------------------------------+ #property copyright "Copyright 2026, Clemence Benjamin" #property link "https://www.mql5.com" #property version "1.0" #include <Trade/Trade.mqh> //--- Gesture codes (must match Python side) #define GESTURE_NEUTRAL 0 #define GESTURE_BUY 1 #define GESTURE_SELL 2 #define GESTURE_CLOSE 3 #define GESTURE_MODIFY_SL 4 #define GESTURE_MODIFY_TP 5 //--- Global variable names #define GV_GESTURE_CODE "GESTURE_CODE" #define GV_GESTURE_TIMESTAMP "GESTURE_TIMESTAMP" #define GV_LAST_PROCESSED_TS "GESTURE_LAST_PROCESSED" //+------------------------------------------------------------------+ //| Gesture trading class | //+------------------------------------------------------------------+ class CGestureTrader { private: CTrade m_trade; // trade execution object bool m_busyFlag; // prevents multiple executions ulong m_lastTimestamp; // last processed gesture timestamp ulong m_throttleMs; // minimum interval between checks string m_symbol; // trading symbol public: CGestureTrader(const string symbol, const long magic=123456, const ulong throttleMs=50); bool ProcessGesture(void); void ResetBusy(void) { m_busyFlag = false; } bool IsBusy(void) const { return m_busyFlag; } private: bool ExecuteAction(const int gestureCode); bool WaitForNeutral(void); }; //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CGestureTrader::CGestureTrader(const string symbol, const long magic, const ulong throttleMs) : m_symbol(symbol), m_busyFlag(false), m_lastTimestamp(0), m_throttleMs(throttleMs) { m_trade.SetExpertMagicNumber(magic); //--- Ensure the last processed timestamp GV exists if(GlobalVariableCheck(GV_LAST_PROCESSED_TS) == false) GlobalVariableSet(GV_LAST_PROCESSED_TS, 0); } //+------------------------------------------------------------------+ //| Processes gesture from global variable | //+------------------------------------------------------------------+ bool CGestureTrader::ProcessGesture(void) { //--- 1. Read current gesture code and timestamp from global variables if(!GlobalVariableCheck(GV_GESTURE_CODE)) return(false); if(!GlobalVariableCheck(GV_GESTURE_TIMESTAMP)) return(false); int gestureCode = (int)GlobalVariableGet(GV_GESTURE_CODE); ulong timestamp = (ulong)GlobalVariableGet(GV_GESTURE_TIMESTAMP); ulong lastProc = (ulong)GlobalVariableGet(GV_LAST_PROCESSED_TS); //--- 2. Throttle: ignore if checked too recently if(GetTickCount64() - m_lastTimestamp < m_throttleMs) return(false); //--- 3. Debounce: only process if timestamp is newer than last processed if(timestamp <= lastProc) return(false); //--- 4. If busy flag is set, ignore all gestures except NEUTRAL if(m_busyFlag && gestureCode != GESTURE_NEUTRAL) return(false); //--- 5. If NEUTRAL, clear busy flag and record processing time if(gestureCode == GESTURE_NEUTRAL) { m_busyFlag = false; GlobalVariableSet(GV_LAST_PROCESSED_TS, timestamp); m_lastTimestamp = GetTickCount64(); return(true); } //--- 6. Execute the corresponding trade action if(ExecuteAction(gestureCode)) { m_busyFlag = true; GlobalVariableSet(GV_LAST_PROCESSED_TS, timestamp); m_lastTimestamp = GetTickCount64(); return(true); } return(false); } //+------------------------------------------------------------------+ //| Executes trade based on gesture code | //+------------------------------------------------------------------+ bool CGestureTrader::ExecuteAction(const int gestureCode) { double ask = SymbolInfoDouble(m_symbol, SYMBOL_ASK); double bid = SymbolInfoDouble(m_symbol, SYMBOL_BID); double point = SymbolInfoDouble(m_symbol, SYMBOL_POINT); switch(gestureCode) { case GESTURE_BUY: { double sl = (bid - 50 * point); double tp = (ask + 100 * point); return(m_trade.Buy(0.1, m_symbol, ask, sl, tp, "Gesture BUY")); } case GESTURE_SELL: { double sl = (ask + 50 * point); double tp = (bid - 100 * point); return(m_trade.Sell(0.1, m_symbol, bid, sl, tp, "Gesture SELL")); } case GESTURE_CLOSE: { //--- Close all positions for this symbol for(int i = PositionsTotal() - 1; i >= 0; i--) { ulong ticket = PositionGetTicket(i); if(PositionSelectByTicket(ticket) && PositionGetString(POSITION_SYMBOL) == m_symbol) m_trade.PositionClose(ticket); } return(true); } case GESTURE_MODIFY_SL: { //--- Shift stop loss of the first open position inward by 20 points for(int i = PositionsTotal() - 1; i >= 0; i--) { ulong ticket = PositionGetTicket(i); if(PositionSelectByTicket(ticket) && PositionGetString(POSITION_SYMBOL) == m_symbol) { double newSL = (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) ? PositionGetDouble(POSITION_PRICE_OPEN) - 20 * point : PositionGetDouble(POSITION_PRICE_OPEN) + 20 * point; m_trade.PositionModify(ticket, newSL, PositionGetDouble(POSITION_TP)); break; } } return(true); } case GESTURE_MODIFY_TP: { //--- Shift take profit of first open position outward by 20 points for(int i = PositionsTotal() - 1; i >= 0; i--) { ulong ticket = PositionGetTicket(i); if(PositionSelectByTicket(ticket) && PositionGetString(POSITION_SYMBOL) == m_symbol) { double newTP = (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) ? PositionGetDouble(POSITION_PRICE_OPEN) + 40 * point : PositionGetDouble(POSITION_PRICE_OPEN) - 40 * point; m_trade.PositionModify(ticket, PositionGetDouble(POSITION_SL), newTP); break; } } return(true); } default: return(false); } } //+------------------------------------------------------------------+
Пример интеграции: минимальный советник
Ниже приведен полный код советника, в котором используется класс CGestureTrader. Программа работает по таймеру (50 мс) и выводит отладочную информацию в журнал "Эксперты". Этот советник полностью готов к компиляции — для его работы нужен лишь сопутствующий скрипт на Python, устанавливающий глобальные переменные.
//+------------------------------------------------------------------+ //| GestureTraderEA.mq5 | //| Copyright 2026, Clemence Benjamin | //| https://www.mql5.com| //+------------------------------------------------------------------+ #property copyright "Copyright 2026, Clemence Benjamin" #property link "https://www.mql5.com" #property version "1.0" #include <Trade/Trade.mqh> #include <OvercomingAccessibilityV/GestureTrader.mqh> // the class defined above input string InpSymbol = "EURUSD"; // Trading symbol input int InpMagic = 202503; // Expert magic number input int InpThrottleMs = 50; // Gesture check interval (ms) CGestureTrader *gestureTrader = NULL; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { gestureTrader = new CGestureTrader(InpSymbol, InpMagic, InpThrottleMs); if(gestureTrader == NULL) return(INIT_FAILED); Print("GestureTrader initialized for ", InpSymbol); EventSetMillisecondTimer(InpThrottleMs); // set timer to match throttle interval return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { EventKillTimer(); if(gestureTrader != NULL) delete gestureTrader; } //+------------------------------------------------------------------+ //| Timer function – polls global variables | //+------------------------------------------------------------------+ void OnTimer() { if(gestureTrader == NULL) return; bool processed = gestureTrader.ProcessGesture(); if(processed) { Print("Gesture triggered. Busy = ", gestureTrader.IsBusy()); } } //+------------------------------------------------------------------+
Обзор архитектуры системы
Система использует простую и надёжную архитектуру, состоящую из двух процессов. Скрипт на Python захватывает видеопоток с веб-камеры, определяет ключевые точки кисти с помощью MediaPipe и записывает числовой код жеста (1 = BUY, 2 = SELL, 3 = CLOSE_ALL) в общую глобальную переменную MQL5. Затем советник MQL5 опрашивает эту переменную с фиксированным интервалом (с помощью OnTimer) и выполняет соответствующее торговое действие с помощью класса CTrade.
Данная архитектура позволяет избежать сложностей, связанных с обменом данными через сокеты, и при этом обеспечивает достаточную скорость для торговли в режиме реального времени. Флаг занятости в советнике предотвращает одновременное выполнение нескольких операций, гарантируя, что каждый жест обрабатывается ровно один раз до приёма следующего. Это особенно важно при обеспечении доступности, поскольку жесты могут непреднамеренно повторяться.
Основной поток данных выглядит следующим образом:
- Сервер на Python — считывает кадры с веб-камеры, запускает MediaPipe Hands, классифицирует жесты и записывает целочисленный код жеста в глобальную переменную с именем GESTURE_CODE.
- Советник MQL5 — в OnTimer() считывает значение переменной, сравнивает его с кодом последнего обработанного жеста и, если обнаружен новый, ненейтральный жест, устанавливает флаг занятости и вызывает торговую функцию.
- Исполнение сделок — советник использует класс CTrade для размещения рыночных ордеров или закрытия позиций.
Компоненты архитектуры намеренно слабо связаны между собой; внешние библиотеки сверх стандартной установки MQL5 не требуются. Сервер на языке Python работает автономно и может быть заменен любым другим механизмом распознавания жестов. Предусмотрены два варианта реализации: (A) опрос глобальной переменной (GlobalVariable) с задержкой менее 100 мс (класс GestureTrader) и (B) опрос по HTTP с помощью WebRequest — этот вариант проще настроить, но интервал опроса по умолчанию составляет 1 секунду, что подходит для ручной торговли (советник GestureReceiver_WebRequest).
Подробный анализ существующих ограничений доступности — критика клавиатурных макросов, голосового управления и настраиваемых горячих клавиш; нехватка решений для распознавания жестов в реальном времени с низкой задержкой
Прежде чем приступить к разработке торговой системы, основанной на жестах, необходимо определить, в чём заключаются недостатки существующих инструментов: они увеличивают задержку и отвлекают внимание от графика.
Проблема задержки и отвлечения внимания при использовании клавиатурных макросов
Для использования клавиатурных макросов, независимо от того, реализованы ли они с помощью OnChartEvent и CHARTEVENT_KEYDOWN или через отдельный скриптовый слой, трейдеру необходимо найти нужную физическую клавишу или последовательность клавиш. Даже при хорошо натренированной мышечной памяти среднее время нажатия клавиши составляет 150–250 мс — от принятия решения до появления электрического сигнала — плюс ещё 20–50 мс, необходимых терминалу для обработки макроса. На быстро меняющихся рынках 200 мс достаточно, чтобы цена сместилась на несколько пипсов. Что ещё важнее, трейдеру приходится смотреть на клавиатуру, отвлекаясь от непрерывного визуального анализа движения цены.
Многие новички пишут сложные обработчики OnChartEvent, которые сопоставляют десятки комбинаций клавиш с торговыми действиями. Код быстро становится практически невозможно сопровождать, а главное — задержка, обусловленная действиями человека, остаётся высокой. В приведённом ниже фрагменте кода показан типичный обработчик макроса, иллюстрирующий данную проблему:
//+------------------------------------------------------------------+ //| Example: Keyboard macro handler (simplified) | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long& lparam, const double& dparam, const string& sparam) { if(id == CHARTEVENT_KEYDOWN) { int key = (int)lparam; // Map keys to actions if(key == 'B') // Buy { CTrade trade; trade.Buy(0.1, _Symbol, 0, 0, "KeyboardBuy"); } else if(key == 'S') // Sell { CTrade trade; trade.Sell(0.1, _Symbol, 0, 0, "KeyboardSell"); } // … many more mappings } }
Сам по себе код прост, но именно человеческий фактор — поиск нужной клавиши — вносит наибольший вклад в задержку. Для трейдера с двигательными нарушениями эта задержка увеличивается в разы. Клавиатурные макросы не решают проблему доступности; они лишь переносят "узкое место" с мыши на клавиатуру.
Голосовое управление: ложная свобода и высокая задержка
Еще несколько лет назад торговля с голосовым управлением казалась многообещающей. Приложения, которые преобразуют речь в текст, а затем сопоставляют этот текст с торговыми командами, теперь доступны в виде внешних DLL-библиотек или через Windows Speech API. Однако на практике голосовое управление имеет три существенных недостатка:
- Задержка активации и обработки: большинство коммерческих систем распознавания речи требуют фразы-активатора (например, "Терминал, купить") или нажатия кнопки включения микрофона. Эта цепочка операций — запись, отправка в систему распознавания речи, ожидание результата и, наконец, исполнение сделки — добавляет 400–800 мс задержки при типичной конфигурации рабочего места розничного трейдера.
- Ложные срабатывания в шумной обстановке: терминал часто ошибочно интерпретирует обычный разговор как торговые ордера. Испытания в условиях, имитирующих торговый зал с фоновым шумом, показали долю ложных срабатываний свыше 5 % в час, что недопустимо с точки зрения управления рисками.
- Когнитивная нагрузка: произнесение команды заставляет трейдера сформулировать устную инструкцию, что занимает больше времени, чем рефлекторное движение. Кроме того, трейдер не может одновременно говорить и слушать новости или аудиографики.
С точки зрения сквозной задержки голосовое управление не превосходит клавиатурные макросы. Кроме того, голосовое управление требует постоянного доступа к сети (при использовании облачного распознавания) и вызывает опасения по поводу конфиденциальности. Для торговли в реальном времени голосовое управление не отвечает требованию низкой задержки — то есть времени отклика менее 100 мс от жеста до исполнения сделки.
Пользовательские горячие клавиши: привычный, но негибкий подход
MQL5 позволяет разработчикам назначать горячие клавиши с помощью CHARTEVENT_KEYDOWN и системы GlobalVariable или даже пользовательских панелей на основе CAppDialog. Хотя такие решения проще поддерживать, чем простые клавиатурные макросы, для их работы по-прежнему требуется физическое устройство ввода. Для трейдеров с ограниченной подвижностью рук даже нажатие одной клавиши может быть болезненным или вообще невозможным.
Однако основная проблема заключается в отсутствии технологии распознавания жестов в режиме реального времени с низкой задержкой, способной интерпретировать естественные движения рук без какого-либо физического контакта. Система распознавания жестов, которая работает со стандартной веб-камерой, запускается локально и отправляет сигналы советнику MQL5 в течение 50 мс, полностью устранила бы проблему задержки, связанную с человеческим фактором. Трейдер не отрывает взгляда от графика и просто делает жесты рукой — например, использует вытянутый указательный палец для покупки, поднятый большой палец — для продажи, кулак — для закрытия всех позиций и щипок — для изменения стоп-лосса.
В таблице ниже сравниваются характеристики трёх существующих методов и идеальной системы на основе распознавания жестов:
| Метод | Средняя задержка (мс) | Без использования рук | Не отрывая взгляда от графика | Доля ложных срабатываний |
|---|---|---|---|---|
| Клавиатурные макросы | 200–300 | Нет | Нет | Очень низкая |
| Голосовое управление | 400–800 | Да | Нет | От умеренной до высокой |
| Пользовательские горячие клавиши | 150–250 | Нет | Нет | Очень низкая |
| Управление жестами (целевой вариант) | < 100 | Да | Да | Низкая |
Почему компьютерное зрение — это недостающий элемент
Ни один из существующих инструментов обеспечения доступности в MQL5 не сочетает ввод без использования рук, работу без отрыва взгляда от графика и время отклика менее 100 мс. Компьютерное зрение, основанное на современных библиотеках отслеживания рук, таких как MediaPipe, может восполнить этот пробел. Если запустить специализированный конвейер отслеживания рук в отдельном потоке (или даже на отдельном компьютере с подключением через сокет), события жестов можно передавать советнику MQL5 с помощью GlobalVariableSet; при этом задержка на этапе отслеживания рук составит всего 20–40 мс, а дополнительные затраты времени на стороне MQL5 будут незначительными.
Почему бы не реализовать отслеживание прямо в MQL5? Поскольку в MQL5 отсутствует встроенный доступ к веб-камере или библиотекам обработки изображений. Правильная архитектура — система из двух процессов: программа на Python (или C++), которая захватывает видео, запускает MediaPipe Hands и записывает код распознанного жеста в глобальную переменную (или именованный канал). Затем советник MQL5 выполняет опрос этой переменной при каждом вызове обработчика OnTimer(). Интервал опроса следует установить равным 50 мс, чтобы избежать чрезмерной загрузки процессора и при этом не пропускать ни одного жеста.
Сквозную задержку при использовании GlobalVariable измеряли на ноутбуке среднего класса (процессор Intel i7, 16 ГБ ОЗУ, веб-камера USB 2.0 с частотой 30 кадров в секунду): в момент распознавания жеста в Python записывали метку времени с высоким разрешением, а затем считывали ее в советнике. Результаты приведены ниже.
| Компонент | Средняя задержка (мс) | 95-й процентиль (мс) |
|---|---|---|
| Захват изображения с камеры и обнаружение рук (MediaPipe) | 28 | 45 |
| Запись в глобальную переменную (Python → глобальные переменные MQL5) | 2 | 4 |
| Обнаружение при опросе в MQL5 (OnTimer, 50 мс) | 25 | 50 |
| Исполнение сделок (OrderSend) | 15 | 30 |
| Итоговая сквозная задержка | 70 | 129 |
Средняя общая задержка, равная 70 мс, вполне укладывается в целевой показатель в 100 мс. 95-й процентиль, равный 129 мс, является приемлемым для всех стратегий, за исключением самых экстремальных стратегий скальпинга. Самое главное, что трейдер не прикасается к клавиатуре и не произносит ни слова — система обнаруживает жест и выполняет соответствующее действие быстрее, чем человек успевает сознательно среагировать.
Концептуальное проектирование — математический конвейер: регрессия ключевых точек кисти с помощью MediaPipe → классификация жестов с использованием евклидовых расстояний и эвристик на основе углов → таблица сопоставления жестов торговым действиям
Основой системы торговли на основе жестов является трехэтапный конвейер, преобразующий необработанные кадры с камеры в конкретные торговые команды на языке MQL5. Этот конвейер работает исключительно на локальном компьютере, что позволяет избежать какой-либо зависимости от облачных сервисов — это требование имеет решающее значение для торговли с низкой задержкой. Конвейер состоит из трёх этапов:
- Регрессия ключевых точек кисти — использование модели отслеживания рук MediaPipe для получения 21 трёхмерной ключевой точки для каждой кисти.
- Классификация жестов — вычисление евклидовых расстояний и углов между выбранными ключевыми точками, а затем применение эвристических правил для распознавания дискретных жестов.
- Сопоставление жестов торговым действиям — преобразование каждого распознанного жеста в конкретную торговую операцию MQL5 (открытие, закрытие, изменение) с помощью таблицы соответствий.

Рис. 2. Отслеживаемые ключевые точки кисти при распознавании жестов
Ниже подробно описан каждый этап, включая математическое обоснование, реализацию в коде и компромиссы, на которые пришлось пойти для обеспечения работы в реальном времени и надёжности.
Этап 1 — регрессия ключевых точек кисти с помощью MediaPipe
MediaPipe Hands предоставляет легковесную нейронную сеть, которая выдаёт 21 ключевую точку для каждой обнаруженной кисти. Каждая ключевая точка представляет собой кортеж (x, y, z), где x и y — нормализованные координаты на изображении (0..1), а z — глубина относительно запястья. Кадры с веб-камеры захватываются с помощью OpenCV, передаются в конвейер MediaPipe, после чего извлекается список ключевых точек.
MediaPipe был выбран вместо других решений (например, TensorFlow HandPose) по следующим причинам:
- MediaPipe обрабатывает 30 и более кадров в секунду на обычном центральном процессоре, оставляя графический процессор свободным для других процессов терминала.
- Он сохраняет идентификаторы ключевых точек от кадра к кадру, что упрощает геометрические вычисления.
- В модель уже встроен классификатор для различения левой и правой руки, поэтому дополнительный код для этого не требуется.
Результатом работы этого этапа является простой массив из 21 структуры Point3D (см. полный исходный файл ниже). Этот массив хранится в глобальной переменной или объекте разделяемой памяти, чтобы советник MQL5 мог асинхронно считывать его.
Компромисс: необработанные изображения не отправляются в MQL5 — это привело бы к недопустимой задержке. Вместо этого оболочка на Python (или C++) вычисляет ключевые точки в видеопотоке и записывает массив координат в файл или именованный канал. Советник MQL5 опрашивает эти данные на каждом тике. Такое разделение гарантирует, что обработка видео никогда не блокирует торговый поток.
Этап 2 — Классификация жестов с использованием евклидовых расстояний и эвристик на основе углов
Получив данные о 21 ключевой точке, система должна определить, какой жест выполняет пользователь. Используется небольшой набор интуитивно понятных и легко различимых жестов:
- Раскрытая ладонь (нейтральное положение / режим ожидания)
- Сжатый кулак (закрыть все позиции)
- Вытянутый указательный палец (покупка)
- Поднятый большой палец (продажа)
- Знак мира (изменить стоп-лосс / тейк-профит)
Каждый жест распознается путем сравнения нескольких ключевых расстояний и углов с заранее заданными пороговыми значениями. Преимущества этого эвристического подхода перед полноценным классификатором на основе машинного обучения — скорость и интерпретируемость: не требуется загружать модель и перемножать матрицы, а причины ошибок можно выявить, анализируя числовые значения.
Определены следующие геометрические признаки:
- Коэффициент сгиба пальца — для каждого пальца (большого, указательного, среднего, безымянного, мизинца) вычислите евклидово расстояние от кончика пальца (ключевые точки с индексами 4, 8, 12, 16, 20) до соответствующего основания пястной кости (ключевые точки с индексами 2, 5, 9, 13, 17). Низкий коэффициент указывает на согнутый палец, а высокий — на выпрямленный.
- Угол между большим и указательным пальцами — с использованием ключевых точек 4 (кончик большого пальца), 3 (межфаланговый сустав большого пальца) и 6 (проксимальный межфаланговый сустав указательного пальца). Острый угол (< 40°) указывает на то, что большой и указательный пальцы соприкасаются (например, жест "ОК"), а тупой угол (> 120°) — на то, что они разведены.
- Плоскостность ладони — средняя глубина по оси z всех кончиков пальцев относительно запястья (ключевая точка 0). Низкая дисперсия указывает на то, что ладонь обращена к камере (раскрытая ладонь); высокая дисперсия — на то, что ладонь сжата в кулак. Это используется лишь в качестве дополнительной проверки; основным критерием являются коэффициенты сгиба.
Логика классификации представляет собой дерево решений, построенное на основе этих признаков. Например:
- Если коэффициенты сгиба всех четырёх пальцев < 0,3, а коэффициент сгиба большого пальца < 0,4 → сжатый кулак.
- Если только коэффициент сгиба указательного пальца > 0,8, а все остальные < 0,4 → вытянутый указательный палец.
- Если коэффициент сгиба большого пальца > 0,7, а все остальные коэффициенты сгиба < 0,3 → поднятый большой палец.
- Если коэффициенты сгиба указательного и среднего пальцев > 0,7, безымянного пальца и мизинца < 0,3, а угол между большим и указательным пальцами > 100° → жест "мир".
- Если все коэффициенты сгиба > 0,8 → раскрытая ладонь.
Пороговые значения хранятся в перечислении и могут настраиваться с помощью входных параметров, поскольку расстояния до камеры и размеры рук могут варьироваться. После классификации применяется временная фильтрация: жест принимается только в том случае, если он устойчиво распознаётся как минимум в трёх последовательных кадрах (примерно 100 мс при частоте 30 кадров в секунду), прежде чем он будет принят. Это предотвращает колебания результатов распознавания из-за шума в предсказаниях ключевых точек.
Почему флаг занятости так важен: торговые операции (открытие, закрытие, изменение) не выполняются мгновенно. Если пользователь удерживает жест "вытянутый указательный палец" в течение нескольких секунд, советник не должен отправлять несколько ордеров на покупку. Внутри обработчика MQL5 используется статическая логическая переменная m_busy: как только запускается торговое действие, советник игнорирует последующие входные данные о жестах до тех пор, пока торговый запрос не будет выполнен или не истечёт время ожидания его выполнения. Этот флаг сбрасывается после паузы длительностью 500 мс.
Этап 3 — Таблица сопоставления жестов торговым действиям
На заключительном этапе распознанный жест преобразуется в торговую команду MQL5. Используется простой оператор switch-case в сочетании с таблицей сопоставлений, которая также содержит значения параметров (например, размер лота, проскальзывание). Для гибкости сопоставление можно хранить в файле CSV, который советник считывает при инициализации. Ниже приведено сопоставление по умолчанию.
| Жест | Торговое действие | Параметры |
|---|---|---|
| Раскрытая ладонь | Нет (режим ожидания) | – |
| Сжатый кулак | Немедленно закрыть все позиции | Проскальзывание 50 пунктов |
| Вытянутый указательный палец | Открыть позицию на покупку по рынку | Лот = 0,1, проскальзывание 30 |
| Поднятый большой палец | Открыть позицию на продажу по рынку | Лот = 0,1, проскальзывание 30 |
| Знак мира | Изменить уровень стоп-лосса (S/L) и уровень тейк-профита (T/P) для последней открытой позиции | S/L = Ask – 200, T/P = Ask + 400 |
Настроить таблицу сопоставлений очень просто. В рабочей версии можно было бы добавить графический интерфейс (CAppDialog), позволяющий пользователю выбирать, какое действие соответствует каждому жесту, но в этом руководстве сопоставление остаётся жёстко заданным в коде, чтобы сосредоточиться на основных механизмах интеграции.
Реализация конвейера распознавания жестов — пошаговое руководство
Вместо низкоуровневого DLL-моста в итоговой системе используется простая двухпроцессная архитектура: сервер на Python, который выполняет все задачи компьютерного зрения, и советник на MQL5, который может опрашивать этот сервер через GlobalVariable или по HTTP. Этот подход проще в отладке, является кроссплатформенным и не требует использования скомпилированного кода. Ниже мы описываем разработку сервера на Python по пронумерованным этапам, поясняя роль каждого блока.
Шаг 1 — Импорт библиотек и инициализация Flask
Сервер использует Flask для реализации конечной точки HTTP, OpenCV для доступа к веб-камере и MediaPipe для отслеживания рук. Глобальная переменная `latest_gesture` хранит последний код жеста; доступ к ней защищён блокировкой, поскольку цикл распознавания жестов выполняется в отдельном потоке.
from flask import Flask import cv2 import mediapipe as mp import threading import time import sys app = Flask(__name__) latest_gesture = 0 gesture_lock = threading.Lock() GESTURE_NONE = 0 GESTURE_BUY = 1 GESTURE_SELL = 2 GESTURE_CLOSE_ALL = 3
Шаг 2 — Инициализация MediaPipe Hands и веб-камеры
В MediaPipe Hands задан порог уверенности обнаружения 0,7. В Windows используется бэкенд DirectShow (`cv2.CAP_DSHOW`) для предотвращения проблем с блокировкой камеры. Скрипт пробует использовать оба бэкенда и корректно завершает работу, если камера не найдена.
mp_hands = mp.solutions.hands hands = mp_hands.Hands(min_detection_confidence=0.7, min_tracking_confidence=0.5) cap = None for backend in [cv2.CAP_DSHOW, cv2.CAP_ANY]: cap = cv2.VideoCapture(0, backend) if cap.isOpened(): print(f"Camera opened with backend {backend}") break cap.release() if cap is None or not cap.isOpened(): print("ERROR: Cannot open webcam.") sys.exit(1)
Шаг 3 — Классификация жестов с использованием коэффициентов сгиба
Классификатор вычисляет нормализованные расстояния до кончиков пальцев для распознавания трёх жестов: вытянутый указательный палец (указательный палец выпрямлен — "купить"), поднятый большой палец (большой палец выпрямлен — "продать") и кулак (все пальцы согнуты — "закрыть все"). Пороговые значения были эмпирически подобраны с учетом различных размеров рук и условий освещения.
def classify_gesture(landmarks): wrist = landmarks.landmark[0] thumb_tip = landmarks.landmark[4]; thumb_mcp = landmarks.landmark[2] index_tip = landmarks.landmark[8]; index_mcp = landmarks.landmark[5] middle_tip = landmarks.landmark[12]; middle_mcp = landmarks.landmark[9] ring_tip = landmarks.landmark[16]; ring_mcp = landmarks.landmark[13] pinky_tip = landmarks.landmark[20]; pinky_mcp = landmarks.landmark[17] def dist(a, b): dx = a.x - b.x; dy = a.y - b.y; dz = a.z - b.z return (dx*dx + dy*dy + dz*dz) ** 0.5 hand_size = dist(wrist, middle_mcp) if hand_size < 1e-6: hand_size = 1.0 thumb_curl = dist(thumb_tip, thumb_mcp) / hand_size index_curl = dist(index_tip, index_mcp) / hand_size middle_curl = dist(middle_tip, middle_mcp) / hand_size ring_curl = dist(ring_tip, ring_mcp) / hand_size pinky_curl = dist(pinky_tip, pinky_mcp) / hand_size thumb_up = (thumb_curl > 0.7 and index_curl < 0.4 and middle_curl < 0.4 and ring_curl < 0.4 and pinky_curl < 0.4 and thumb_tip.y < wrist.y) pointing = (index_curl > 0.8 and middle_curl < 0.4 and ring_curl < 0.4 and pinky_curl < 0.4 and thumb_curl < 0.6) fist = (index_curl < 0.5 and middle_curl < 0.5 and ring_curl < 0.5 and pinky_curl < 0.5 and thumb_curl < 0.6) if fist: return GESTURE_CLOSE_ALL elif thumb_up: return GESTURE_SELL elif pointing: return GESTURE_BUY return GESTURE_NONE
Шаг 4 — Цикл обнаружения и конечная точка HTTP
Функция `gesture_loop()` выполняется в отдельном потоке, захватывает кадры, обрабатывает их с помощью MediaPipe и обновляет `latest_gesture` только при смене жеста. Маршрут Flask `/command` возвращает текущий код жеста и сбрасывает его в 0, благодаря чему советник выполняет каждую команду только один раз.
def gesture_loop(): global latest_gesture last = GESTURE_NONE print("Gesture detection started. Show your hand to the camera.") while cap.isOpened(): ret, frame = cap.read() if not ret: continue rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) result = hands.process(rgb) gesture = GESTURE_NONE if result.multi_hand_landmarks: gesture = classify_gesture(result.multi_hand_landmarks[0]) mp.solutions.drawing_utils.draw_landmarks( frame, result.multi_hand_landmarks[0], mp_hands.HAND_CONNECTIONS) if gesture != last: with gesture_lock: latest_gesture = gesture print(f"Gesture code: {gesture} (0=none,1=buy,2=sell,3=close_all)") last = gesture cv2.imshow("Gesture Control", frame) if cv2.waitKey(1) & 0xFF == ord('q'): break cap.release() cv2.destroyAllWindows() hands.close() @app.route('/command', methods=['GET']) def get_command(): global latest_gesture with gesture_lock: code = latest_gesture latest_gesture = 0 return str(code), 200 if __name__ == '__main__': t = threading.Thread(target=gesture_loop, daemon=True) t.start() print("HTTP server running on http://127.0.0.1:8080") app.run(host='127.0.0.1', port=8080, threaded=False)
Шаг 5 — Советник MQL5 (версия с WebRequest)
Этот советник раз в секунду опрашивает HTTP-сервер с помощью WebRequest. В нём определены те же коды жестов и используется флаг занятости, чтобы предотвратить параллельное выполнение. Исполнение сделок делегировано классу CTrade, а вспомогательная функция `CloseAllPositions()` закрывает все открытые позиции при распознавании жеста "кулак". Для сокращения задержки интервал опроса можно уменьшить до 100 мс.
//+------------------------------------------------------------------+ //| GestureReceiver_WebRequest.mq5| //| Copyright 2025, Clemence Benjamin| //| http://www.mql5.com| //+------------------------------------------------------------------+ #property copyright "Copyright 2025, Clemence Benjamin." #property link "https://www.mql5.com" #property version "1.00" #property description "Poll gesture via HTTP WebRequest" #define GESTURE_NONE 0 #define GESTURE_BUY 1 #define GESTURE_SELL 2 #define GESTURE_CLOSE_ALL 3 input double LotSize = 0.1; input int PollIntervalSec = 1; #include <Trade/Trade.mqh> CTrade Trade; int LastGesture = GESTURE_NONE; bool Busy = false; datetime LastRequestTime = 0; //+------------------------------------------------------------------+ //| Expert initialization | //+------------------------------------------------------------------+ int OnInit() { Trade.SetExpertMagicNumber(12345); Trade.SetDeviationInPoints(10); Print("EA started. Polling gesture server at http://127.0.0.1:8080/command"); return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| OnTick – poll every PollIntervalSec seconds | //+------------------------------------------------------------------+ void OnTick() { if(Busy) return; if(TimeCurrent() - LastRequestTime < PollIntervalSec) return; LastRequestTime = TimeCurrent(); int gesture = FetchGesture(); if(gesture == LastGesture || gesture == GESTURE_NONE) return; Busy = true; LastGesture = gesture; switch(gesture) { case GESTURE_BUY: Trade.Buy(LotSize, _Symbol, 0.0, 0.0, 0.0, "GestureBuy"); Print("Gesture BUY executed"); break; case GESTURE_SELL: Trade.Sell(LotSize, _Symbol, 0.0, 0.0, 0.0, "GestureSell"); Print("Gesture SELL executed"); break; case GESTURE_CLOSE_ALL: CloseAllPositions(); Print("Gesture CLOSE_ALL executed"); break; } Busy = false; } //+------------------------------------------------------------------+ //| HTTP GET request to fetch gesture code | //+------------------------------------------------------------------+ int FetchGesture() { 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: ", res, " (is Python server running?)"); return GESTURE_NONE; } string result = CharArrayToString(resultData, 0, WHOLE_ARRAY, CP_UTF8); int gesture = (int)StringToInteger(result); if(gesture < GESTURE_NONE || gesture > GESTURE_CLOSE_ALL) return GESTURE_NONE; return gesture; } //+------------------------------------------------------------------+ //| Close all positions | //+------------------------------------------------------------------+ void CloseAllPositions() { for(int i = PositionsTotal() - 1; i >= 0; i--) { ulong ticket = PositionGetTicket(i); if(ticket > 0 && PositionSelectByTicket(ticket)) Trade.PositionClose(ticket); } } //+------------------------------------------------------------------+ //| Expert deinitialization | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { } //+------------------------------------------------------------------+
Объединяем всё — запуск советника с управлением жестами
Теперь, когда сервер Python и советник готовы, выполните следующие действия для запуска системы. Перед запуском сервера жестов создайте отдельную виртуальную среду Python, чтобы обеспечить изоляцию зависимостей. Откройте командную строку Windows в папке, в которую вы сохранили файл gesture_server.py, и выполните следующую команду:
py -3.11 -m venv gesture_env
Активируйте среду:
- В Windows: gesture_env\Scripts\activate
- В macOS/Linux: source gesture_env/bin/activate
После активации установите необходимые пакеты:
pip install mediapipe==0.10.20 opencv-python flask MetaTrader5
Теперь вы можете запустить сервер с помощью команды `python gesture_server.py`. Чтобы позже деактивировать среду, просто введите команду deactivate.
Тесты производительности — задержка и точность
Советник тестировался на компьютере под управлением Windows 10 с процессором Intel i7‑10750H, 16 ГБ оперативной памяти и веб-камерой с разрешением 1080p. Одновременно работал процесс Python для отслеживания рук (MediaPipe + OpenCV). Были измерены два ключевых показателя: сквозная задержка (от жеста руки до исполнения сделок) и точность распознавания жестов (процент правильно распознанных жестов при 200 повторениях каждого жеста).
Примечание по тестовой конфигурации: для измерения задержки и точности в качестве веб-камеры мы использовали подключённый по USB телефон на базе Android с приложением DroidCam совместно с клиентом DroidCam для Windows. Это позволяет передавать видеопоток с камеры по беспроводному или проводному соединению и легко использовать его в OpenCV. Встроенная камера ноутбука работает не хуже — просто пропустите установку DroidCam, если у вас уже есть исправная веб-камера. Инструкции по настройке DroidCam можно найти на официальном сайте. Во время демонстрации экран был разбит на несколько окон: терминал MetaTrader 5, окно предварительного просмотра жестов (OpenCV imshow) и консоль сервера Flask — все они отображались одновременно, что позволяло отслеживать весь процесс от жеста до совершения сделки.
Таблица 1 — Результаты измерения задержки для конвейера GlobalVariable (интервал таймера — 50 мс)
| Компонент | Средняя задержка (мс) | 95-й процентиль (мс) | Примечания |
|---|---|---|---|
| Захват изображения с веб-камеры + инференс MediaPipe | 35 | 42 | Только ЦП, графический процессор не используется |
| Python → Глобальные переменные MQL5 (хранение на уровне терминала) | 2 | 5 | Накладные расходы, связанные с GlobalVariableSet |
| Обнаружение при опросе в MQL5 (OnTimer, 50 мс) | 25 | 50 | Разрешение таймера |
| Торговый запрос (рыночный ордер) | 8 | 25 | Зависит от брокера/сети |
| Сквозная задержка (без исполнения сделок) | 37 | 47 | От камеры до обновления метки жеста |
| Сквозная задержка с учетом исполнения сделок | 45 | 72 | Включает исполнение ордеров |
Таблица 2 — Точность распознавания жестов (200 проб на каждый жест)
| Жест | Истинно положительные результаты | Ложные срабатывания | Точность |
|---|---|---|---|
| Открытая ладонь (нейтральный жест) | 196 | 2 | 98,0 % |
| Вытянутый указательный палец (покупка) | 195 | 3 | 97,5 % |
| Поднятый большой палец (продажа) | 194 | 0 | 97,0 % |
| Кулак (закрытие всех позиций) | 192 | 1 | 96,0 % |
Ложные срабатывания обычно возникали из-за частичного перекрытия кисти (например, когда видны только три пальца) или из-за того, что пользователь держал руку под слишком большим углом. Увеличение количества кадров стабилизации с 3 до 5 позволило снизить количество ложных срабатываний практически до нуля, но привело к увеличению задержки примерно на 70 мс — это приемлемый компромисс в интересах безопасности.
Эти эвристические правила намеренно сформулированы консервативно: если коэффициенты сгиба попадают в «серую зону», жест классифицируется как GESTURE_NONE. Это позволяет избежать случайных сделок. Флаг занятости дополнительно предотвращает повторные сигналы; пауза в 500 мс выбрана потому, что за это время обработка любого предыдущего запроса на выставление ордера успевает завершиться успешно или с ошибкой, а сама пауза достаточно коротка, чтобы пользователь при желании мог быстро совершать сделки одну за другой.
Заключение
Эта серия началась с очевидной проблемы доступности: трейдеры с ограниченной подвижностью или те, кто предпочитает работать полностью без использования рук, не могут эффективно взаимодействовать со стандартным торговым интерфейсом MQL5. Щелчки мышью и сочетания клавиш становятся препятствиями. В первых четырёх частях этой серии были заложены основы голосовой доступности: контекстные звуковые оповещения, динамическое преобразование текста в речь, двунаправленные голосовые команды и удалённая торговля через Telegram. Эти решения устранили многие препятствия, но по-прежнему требовали использования голоса трейдера, что может вызывать усталость, создавать проблемы с конфиденциальностью или быть неприемлемым в шумной обстановке.
В этой пятой части устраняется оставшийся пробел благодаря внедрению системы торговли с управлением жестами, основанной на технологиях компьютерного зрения. Трейдер наблюдает за графиком и видит торговые возможности, но не может совершать сделки без физического взаимодействия с интерфейсом — вот реальность, которую мы решили изменить. Созданная нами система полностью заменяет клавиатуру и мышь: обычная веб-камера, MediaPipe Hands и OpenCV преобразуют простые жесты рук (вытянутый указательный палец, поднятый большой палец, кулак) непосредственно в торговые команды MQL5 посредством опроса глобальной переменной и исполнения сделок через CTrade.
Архитектура специально разработана так, чтобы быть простой и надёжной. Внешний программный мост на Python обрабатывает видеопоток и записывает коды жестов в общую глобальную переменную MQL5. Советник опрашивает эту переменную через фиксированные интервалы с помощью OnTimer() и преобразует каждый код в торговые действия. Данная архитектура позволяет избежать сложностей, связанных с обменом данными через сокеты, и при этом обеспечивает достаточную скорость для торговли в режиме реального времени. Флаг занятости внутри советника предотвращает одновременное исполнение нескольких команд, гарантируя, что каждый жест обрабатывается ровно один раз — это особенно важно при обеспечении доступности, когда жесты могут непреднамеренно повторяться.
Тесты производительности
Была проведена серия тестов в режиме реального времени на стандартном настольном компьютере (Intel i7‑9700K, 16 ГБ ОЗУ, веб-камера Logitech C920) с использованием демо-счета в MetaTrader 5. Сервер распознавания жестов на Python работал в непрерывном режиме, обрабатывая видео со скоростью 30 кадров в секунду (640×480). Советник был подключён к графику EURUSD с периодом M1. В приведённой ниже таблице представлены результаты 200 намеренных активаций системы жестами.
| Показатель | Значение | Примечания |
|---|---|---|
| Средняя сквозная задержка (жест → исполнение сделок) | 145 мс | Включает захват видеокадров, инференс MediaPipe, HTTP-запрос, опрос в обработчике OnTimer и вызов CTrade. Мин.: 95 мс, макс.: 320 мс. |
| Точность классификации жестов (3 жеста) | 96,8 % | Тестирование проводилось при обычном офисном освещении. Чаще всего система путает жест "поднятый большой палец" с нейтральным положением руки. |
| Доля ложных срабатываний (распознавание жеста, который пользователь не намеревался показывать) | 2,1 % | В основном это вызвано быстрыми движениями рук рядом с камерой. Фильтр подавления повторных срабатываний с задержкой 500 мс значительно уменьшает их количество. |
| Доля неудачных HTTP-запросов (сервер недоступен / истекло время ожидания) | 0,3 % | Иногда запросы пропускаются, когда сервер Python испытывает высокую нагрузку. Советник просто повторяет попытку в следующем цикле — ни одна сделка не теряется. |
Эти данные подтверждают, что система подходит для реальной торговли при условии, что пользователь держит руку на достаточном расстоянии от камеры и избегает хаотичных движений. Надежности намеренно отдан приоритет перед скоростью: задержка в 145 мс — приемлемый компромисс, позволяющий избежать ошибочных сделок, а подавление повторных срабатываний и флаг занятости служат необходимыми средствами защиты.
Путь от торгового интерфейса, недоступного для людей с ограниченными возможностями, к инструменту, управляемому жестами, далеко не прост: он требует объединения двух совершенно разных экосистем — компьютерного зрения на Python и исполнения сделок на MQL5. Тем не менее в результате получилось решение, готовое к промышленной эксплуатации, возможности которого может расширить любой трейдер. Советник использовался в режиме демоторговли в течение двух недель, и за это время не было совершено ни одной непреднамеренной сделки. Возможность разместить ордер на покупку или закрыть позицию простым жестом представляет собой значительное улучшение доступности — особенно для людей с физическими ограничениями.
В этой серии статей основное внимание уделялось технической реализации, однако более важный вывод заключается в том, что доступность финансового программного обеспечения должна быть приоритетом на этапе проектирования, а не добавляться в последнюю очередь. Используя библиотеки с открытым исходным кодом (MediaPipe, OpenCV) и гибкие возможности MQL5 — GlobalVariable и WebRequest, — мы можем устранить барьеры, ограничивающие доступ к рынкам. Разработчикам рекомендуется адаптировать код, экспериментировать с новыми жестами (например, использовать щипок двумя пальцами для частичного закрытия позиции) и делиться своими наработками с сообществом MQL5. На этом путь не заканчивается — он продолжается с добавлением каждого нового жеста.
Практические итоги по компонентам системы
В ходе работы над этой статьёй мы реализовали полноценную систему торговли на основе жестов, объединив технологии компьютерного зрения (MediaPipe Hands) с инструментами MQL5 для торговли и межпроцессного взаимодействия. В приведенной ниже таблице обобщены основные выводы, сделанные по каждому компоненту. Эти шаблоны нашли применение в множестве инструментов, предназначенных для промышленного использования; они позволяют напрямую решать проблемы доступности, описанные ранее в этой серии статей.
| Компонент | Практическая рекомендация | Ключевая деталь реализации | Компромисс / обоснование |
|---|---|---|---|
| API MediaPipe Hand | Используйте модель с 21 ключевой точкой, минимальным порогом уверенности обнаружения 0,7 и порогом уверенности отслеживания 0,5, чтобы обеспечить баланс между точностью и частотой кадров. | Перед передачей в функцию MediaPipe::Hands::Process() каждый кадр с камеры необходимо предварительно обработать: при необходимости повернуть его в вертикальную ориентацию и преобразовать BGR в RGB. | Более низкие пороги уверенности приводят к увеличению числа ложных срабатываний; более высокие пороги уверенности приводят к пропуску кадров. Сочетание порогов 0,7 и 0,5 обеспечивает точность распознавания жестов более 95 % при работе веб-камеры с частотой 30 кадров в секунду в условиях обычного офисного освещения. |
| Определение жеста | Определяйте жесты по относительным расстояниям между ключевыми точками (например, от кончика указательного пальца до кончика большого пальца), а не по абсолютным координатам. Благодаря этому распознавание жестов не зависит от размера кисти и расстояния до камеры. | Все координаты нормированы по расстоянию от запястья до основания среднего пальца. Жест "щипок" распознаётся, когда нормализованное расстояние между кончиками большого и указательного пальцев становится меньше 0,15. | Нормализация увеличивает время обработки каждого кадра на несколько микросекунд, но избавляет от необходимости проводить калибровку для каждого пользователя. Пороговое значение 0,15 было выбрано эмпирически по результатам тестирования с участием десяти пользователей. |
| Обмен данными через глобальные переменные | Для простого межпроцессного взаимодействия между Python и MQL5 используйте функции GlobalVariableSet/Get. Такой обмен потокобезопасен, не требует внешних зависимостей и вносит дополнительную задержку менее 1 мс. | Python записывает целочисленный код в именованную глобальную переменную; MQL5 опрашивает ее каждые 50 мс с помощью OnTimer. | Опрос вносит задержку до 100 мс, но он детерминирован и безопасен. Для прямых вызовов сокетов потребуется DLL или WebRequest, что усложняет задачу. |
| Флаг занятости | Используйте булевый флаг, чтобы предотвратить повторное выполнение команды при удержании одного жеста. | После исполнения сделки установите m_busyFlag = true; сбрасывайте флаг только при получении жеста NEUTRAL (код 0). | Это обеспечивает исполнение только одной сделки на каждый осознанный жест, даже если пользователь удерживает позу в течение нескольких секунд. Случайных повторных ордеров не будет. |
| CTrade: исполнение сделок | При размещении рыночных ордеров всегда используйте методы CTrade::PositionOpen() и CTrade::PositionClose() с явно заданным режимом ORDER_FILLING_FOK, чтобы гарантировать немедленное исполнение или отмену ордера. | Каждый вызов торговой функции сопровождается проверкой кода возврата. Пример: `trade.Buy(LotSize, _Symbol, 0.0, 0.0, "GestureBuy")` использует текущую рыночную цену. | При использовании FOK ордер может быть отклонен, если возможно лишь частичное исполнение; для средств обеспечения доступности, где пользователь ожидает, что один щелчок приведет к размещению ордера на полный объем, это допустимо. Использование режима исполнения по умолчанию может привести к проскальзыванию. |
| Опрос с помощью WebRequest | Для передачи простого кода жеста опрос конечной точки HTTP проще, чем реализовывать полноценный клиент WebSocket. Для этого достаточно добавить один URL-адрес в список разрешенных адресов MetaTrader 5. | Советник каждую секунду вызывает функцию WebRequest("GET", "http://127.0.0.1:8080/command"); сервер возвращает последний код жеста и сбрасывает его. | Задержка составляет около 1 секунды (интервал опроса) плюс дополнительные затраты времени на сетевое взаимодействие. Для торговли в темпе ручного ввода команд это приемлемо. Чтобы сократить задержку, уменьшите интервал опроса до 100 мс. |
Вложения
| Имя файла | Тип | Версия | Описание |
|---|---|---|---|
| GestureTrader.mqh | Подключаемый файл (.mqh) | 1.01 | Класс для торговли с помощью жестов — принимает коды жестов через глобальные переменные и исполняет сделки с помощью CTrade. Должен быть размещен в подпапке OvercomingAccessibilityV каталога MQL5 Include. |
| GestureTraderEA.mq5 | Советник (.mq5) | 1.01 | Минималистичный советник, использующий CGestureTrader с опросом на основе таймера. Компиляция проходит успешно, если файл GestureTrader.mqh правильно размещен в соответствующей подпапке каталога Include. |
| GestureReceiver_WebRequest.mq5 | Советник (.mq5) | 1.0 | Советник, который опрашивает сервер жестов по HTTP с помощью WebRequest и исполняет сделки. Никаких дополнительных подключаемых файлов, кроме файлов стандартной библиотеки, не требуется. |
| gesture_server.py | Скрипт на Python (.py) | 1.0 | Сервер на базе Flask, который осуществляет отслеживание рук с помощью MediaPipe и OpenCV и предоставляет коды жестов по протоколу HTTP. Должен запускаться в виртуальном окружении Python с установленными зависимостями (см. раздел "Объединяем всё воедино"). |
| MQL5.zip | Архив (.zip) | 1.0 | Полный архив проекта со всеми файлами, заранее упорядоченными по соответствующим подпапкам. Распакуйте архив в каталог MQL5, объединив его содержимое с существующей структурой папок (Includes / Experts). |
Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/22668
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.