Рад представить новую штуку - сервис и библиотеки MTWorkers, MTСlient для параллельного исполнения команд. Одновременно в разных потоках могут исполняться команды терминала, системные задачи, сетевые запросы. И конечно скрипты. Можно распараллеливать свои алгоритмы и поручать длительные функции параллельным нитям, освобождая драгоценное время советников.
Часть 1 : низкий уровень
Будет ещё и вторая часть, посвященная классу MTClient. Решил начать публикации с более низкого уровня, чтобы ознакомить с внутренним устройством. Чтобы при необходимости вы могли например сделать свой клиент, не полагаясь на мою ОО-реализацию.
Первый тестово-демонстрационный пример, спрашиваем у воркеров список чартов
Что тут произошло ?
запустили скрипт, скрипт обратился к воркерам, первый-же свободный воркер выполнил запрос и отдел список чартов с некоторыми данными.
тестовый скрипт максимально простой, и по нему понятно как всё устроено:
void OnStart() { // берём ATcl ATcl_OnInit(); ATcl *tcl=new ATcl(); // пакет MTClient if (tcl.Eval("package require MTClient")!=TCL_OK) { Alert("package require : %s",tcl.StringResult()); delete tcl; return; } // запускаем клиент if (tcl.Eval("set mtc [ MTClient new ]")!=TCL_OK) { Alert("MTClient new : %s",tcl.StringResult()); } // запрашиваем список чартов Tcl_Obj list=tcl.ObjEval("$mtc invoke charts"); tcl.Ref(list); int total=tcl.Count(list); PrintFormat("Всего %d чартов",total); for(int i=0;i<total;i++) { PrintFormat("#%d %s",i,tcl.String(list,i)); } tcl.Unref(list); // удаляем клиент tcl.Eval("$mtc destroy"); delete tcl; }
по скрипту видно - мы просто накидывали команды tcl, получали и разбирали результат. То-же самое могли повторить "руками" из консоли TkCon. Внутренние механизмы реализованы на tcl. На уровне MQL просто обращения к интерпретатору.
Как сайд-эффект такого решения - из скриптов можно обращаться к воркерам и общаться с терминалом. Можно писать алгоритмы или расширения программ на скриптах.
Теперь посмотрим как оно реализуется на стороне воркера:
// функция charts ?-dict|-json? // опциональный -dict : данные будут возвращены в виде словаря // -json : данные будут в виде json // @arg worker инстанс воркера // @arg timeout таймаут (дедлайн) в милисек на исполнение // @arg args список параметров переданный юзером (args[0] - имя команды) // @arg raise [inout] - признак ошибки. Функция должна выставить значение при ошибках // @return Tcl_Obj результат исполнения Tcl_Obj MTWorker_charts(MTWorker &worker,ulong timeout,Tcl_Obj args,int &raise) { ATcl *tcl=worker.tcl; tcl.Ref(args); int format=0; // формат if ( tcl.String(args,1)=="-dict") { format=1; } else if ( tcl.String(args,1)=="-json") { format=2; } Tcl_Obj list=tcl.Obj(); for(long chart=ChartFirst();chart!=-1;chart=ChartNext(chart)) { ChartData data(chart); Tcl_Obj record=data.ToObj(tcl,format); tcl.ListAppend(list,record); } tcl.Unref(args); return list; } /// --- some code skipped --- MTWorker *serv=NULL; void OnStart() { serv=new MTWorker(); if (serv==NULL) return; if (serv.OnInit()!=INIT_SUCCEEDED) { serv.OnDeinit(REASON_INITFAILED); delete serv; return; } serv.Extend("charts",MTWorker_charts); // регестрируем команду charts и функцию которая её исполнит serv.Extend("orders",MTWorker_orders); serv.Run(); serv.OnDeinit(REASON_PROGRAM); delete serv; serv=NULL; }
тоже всё максимально просто - пишется функция которая реализует команду и регестрируется в воркере.
Основной принцип - используем Tcl_Obj как универсальный контейнер. Он может нести практически всё что угодно - текст/список/ассоц.массив/бинарные данные.
Аргументы команды получили в виде Tcl_Obj, разобрали его как список. И результирующие данные тоже собрали в Tcl_Obj и отдали.
Второй пример: async/await и замеряем время
Так как непосредственное исполнение команд мы теперь возложили на воркеров, то между запросом и получением ответа можем заниматься другими полезными и срочными делами.
Главное чтобы запрос уходил быстро. Вот тест и показывает что на запрос уходит 5-10 микросек.
В положении когда советник для обработки тика должен тратить не более 200 милисек (тики приходят до 5шт./сек), советник может в начале OnTick() быстро оправить запрос,
провести торговые операции и уже под конец забрать результат и поработать с ним.
С точки зрения прикладного программиста, исполнение разбито на 2 части :
- async - отправляет асинхронный/параллельный запрос воркерам, получает некий идентификатор (promise)
- await - получает непосредственный результат по указанному promise
между двумя этими вызовами можно делать более актуальные дела.
конечно это имеет накладные расходы: потратится время на отправку запроса (несколько микросек), на доставку до воркера, на отработку в воркере и на доставку ответа.
То есть не все функции имеет смысл выносить в паралелель. Но точно просятся:
- функции имеющие неопределённое время исполнения. Которые могут исполнится быстро, а могут и долго, и заранее непредсказуемо как. Например практически вся работа с объектами чарта. Их категорически нельзя использовать внутри OnTick() и даже OnTimer плохое место - и куда-то надо их деть.
- групповые и "интеллектуальные" торговые операции. Например закрытие сетки ордеров. Или "открыть позицию, рассчитав объём от риска как %маржи и за время не более T и цене не хуже P"
- вызов внешних программ
- веб-запросы или запросы к внешним базам.
- запуск и исполнение больших рассчётов. Все любимые некоторыми нейро-сети и маш.обуч плохо укладываются в торговые лимиты времени
Третий пример: запуск внешних программ

в этом примере, ровно теми-же методами вызвали команду dir. И получили результат. MTWorkers умеет исполнять внешние программы, причём таким-же образом как и свои функции. В параметрах передаются параметры, как результат возвращается то что программа печатает.
В тестовом примере - вызвали dir, как результат получили 12 строчек, вывели их в журнал и в комментарий.
Особенность MTWorkers - для исполнения внешних программ используется отдельный пул процессов. То есть системная команда не занимает воркер. Всего одновременно может работать 5 (сейчас константа, а в дальнейшем можно будет настраивать). Остальные будут дожидаться места в пуле исполнения.
и ещё веб-запросы конечно
Совсем кратко - и точно так-же конечно можно кидать веб-запросы. Их обработка тоже ведётся в отдельном пуле и не занимает воркер.
Воркеры вообще только для исполнения задач MQL, где требуется взаимодействие с терминалом.
В примере - обращение к главной странице сайта и выбор оттуда непрочитанных сообщений.
!!! CRAZY тест !!!
Запускаем...Возникает какое-то чёрненькое окошко...что-то жужжит, потом что-то печатается
ЧТО ЭТО ?
а это мы запустили КЛАСТЕР.
На полном серьёзе - мы запустили рассчётную задачу на кластере MPI, передали ей данные непосредственно из терминала и получили результат.
просто если уж умеем запускать внешние задачи, то отчего-бы не попробовать толковую вещь:-)
потенциально можно настроить mpi (что кстати не так сложно, только кропотливо) на всех имеющихся под рукой компах и задействовать все имеющиеся мощности.
Пример кстати показывает и "ахиллесову пяту" облачных и параллельных вычислений - передача и преобразование данных. Мы передавали данные через stdout/stdin в текстовом виде.
Чего конечно-же не делают. Для того чтобы эффективно использовать мощные вычисляторы, нужны ещё и быстрые данные.
Резюмируя
Получили начальную мульти-задачную системы, в которой:
- можно параллельно исполнять функции терминала в воркерах. Кол-во одновременно-исполняемых = кол-во запущенных воркеров
- можем параллельно исполнять веб-запросы и внешние программы. Кол-во одновременно исполняемых, до 5 штук (можно увеличить, это просто константа с потолка)
- функции воркеров также доступны в скриптах tcl - можем расширять программы mql скриптами
- умеем запускать mpiexec и передавать в него данные
- рассмотрели как расширять воркер. (пишем простую функцию, и регестрируем её в воркере)
- для клиента пока рассмотрели только прямое обращение через интерпретатор, без отдельного класса. Доступные функции и внутренний код можно глянуть в исходниках tcl/lib/mtworkers/mtworker.tcl (файл небольшой и более-менее прокомментирован)
Установка и запуск.
Текущий дистрибутив ATcl 1.09с (tcl/tk для MetaTrader) можно скачать на sourceforge https://sourceforge.net/projects/mt-atcl/
Если у вас был установлен ATcl и не хочется скачивать,запускать инсталлятор то можно взять только апдейт библиотеки из аттача или с yandex https://disk.yandex.ru/d/GHOajyMlehx1wg и распаковать в каталог данных терминала
MTWorkers приложен к статье, но можно также кануть с яндекс https://disk.yandex.ru/d/727wDfdvcCZTpQ
в архив MTWorkers в каталог tcl/opt включены :
tidy , https://www.html-tidy.org/ программа и библиотека для исправлений не вполне корректных html
mpich2 1.4.1. https://www.mpich.org/ библиотеки, клиент и сервер MPI
Для использования ATcl (теперь, усилиями параноиков MQ, надеюсь икается) должны быть предварительно настроены переменные окружения, либо запуск терминала отдельным bat файлом.
Я использую вот такой :
@echo off REM redefine and uncomment for alternative path REM ATCL_HOME=c:/ATcl REM если ATCL_HOME не задан, попробовать умолчания IF "%ATCL_HOME%" == "" SET ATCL_HOME=C:/ATcl REM убедиться что ATCL_HOME указывает на каталог IF NOT EXIST %ATCL_HOME%/ ( ECHO "ATCL_HOME is not correct" EXIT ) SET MT_COMMON=%APPDATA%/MetaQuotes/Terminal/Common REM === каталоги opt === REM == opt/MPICH IF EXIST %CD%/tcl/opt/MPICH2/bin/ SET PATH=%CD%/tcl/opt/MPICH2/bin;%PATH% ELSE IF EXISTS %MT_COMMON%/Tcl/opt/MPICH2/bin SET PATH=%MT_COMMON%/Tcl/opt/MPICH2/bin;%PATH% REM == opt/tidy IF EXIST %CD%/tcl/opt/tidy/bin/ SET PATH=%CD%/tcl/opt/tidy/bin;%PATH% ELSE IF EXISTS %MT_COMMON%/Tcl/opt/tidy/bin SET PATH=%MT_COMMON%/Tcl/opt/tidy/bin;%PATH% REM === Python === IF "%PYTHONHOME%" == "" SET PYTHONHOME=%ATCL_HOME%/python IF EXIST %PYTHONHOME%/ SET PATH=%PYTHONHOME%;%PYTHONHOME%/DLLs;%PATH% IF EXIST %PYTHONHOME%/ SET PYTHONPATH=%PYTHONHOME%/lib REM === Tcl === SET TCL_LIBRARY=%ATCL_HOME%/lib/tcl8.6 SET TCLLIBPATH=%ATCL_HOME%/lib/tcl8.6 %ATCL_HOME%/lib SET PATH=%CD%/bin;%MT_COMMON%/bin;%ATCL_HOME%/bin;%PATH% start "" /B terminal64.exe /portable
весь его смысл - правильно задать переменные окружения TCL_LIBRARY TCLLIBPATH и добавить каталоги содержащие нужные DLL в %PATH%. Раньше это авто-делалось при ините основной DLL, теперь запретили
я использую /portable при запуске терминалов. Чтобы использовать без ключика /portable в начале bat добавьте "cd полный_путь_к_каталогу_данных", а конце уберите ключ /portable