Взаимодействие между MеtaTrader 4 и MATLAB Engine (виртуальная машина MATLAB)
Введение
В настоящее время MetaTrader 4 и математический пакет MATLAB успели получить высокую оценку пользователей за многие свои положительные характеристики, в том числе за «гибкость» при создании сложных вычислительный систем. Математический пакет MATLAB имеет три основных способа связи с вешними приложениями, но лишь один из них рекомендован – использование виртуального рабочего стола (машины) MATLAB Engine, данный способ гарантирует полную совместимость со всем пакетом MATLAB. Многие программисты избегают данный способ из-за следующих причин:
- Многие считают его медленным. Да, если сравнивать с методом прямого вызова функций из Dll-библиотек MATLAB. Основная задержка происходит в начале работы, при вызове виртуальной машины, из-за вызова многочисленных библиотек, которые загружаются в виртуальное адресное пространство вызвавшего процесса (в нашем случае MetaTrader 4).
- Переносимость проекта. Да, при переносе проекта на другой компьютер необходимо таскать все Dll-библиотеки MATLAB, но и при прямом вызове тоже, a также знать «родственные связи» последних, т.е. очередность запуска!
- Обязательное знание С++ или Fortran. Ну на этот вопрос отвечу так: если знаешь MQL4, то выучить С++ не сложно и наоборот.
Почему я рекомендую этот способ:
- Самый надежный и независимый от версии MATLAB способ взаимодействия с внешними программами. Вы можете поменять версию MATLAB, а ваши индикаторы и советники этого не заметят, это считаю главным преимуществом.
- Относительно быстрый способ разработки. Нет необходимости в отладчиках, а написать «Dll-обертку» не составит труда.
- «Общий рабочий стол» для нескольких индикаторов и/или советников. Это свойство считаю полезным в тех случаях, когда необходимо принять решение на основе данных от нескольких индикаторов или же при реализации пирамидальной сделки.
В данной статье описывается способ связи MetaTrader 4 и MATLAB версии 7.4.0 (R2007a) через «Dll-обертку» написанную на Borland C++ Builder 6. Программистам, предпочитающим продукцию Microsoft, придется самим адаптировать примеры под свой компилятор (удачи Вам в нелегком труде!).
I . Постановка задачи
Прежде всего надо разобраться, с чего же начать проектирование? Разобьем процесс разработки на три части:
- Разработка M-функции в MATLAB, реализующей расчет индикатора/советника.
- Разработка «Dll-обертки» для связи MATLAB и MetaTrader 4.
- Разработка MQL программы.
II. Разработка M -функции
Это, наверное, самый интересный и продолжительный процесс, который включает в себя следующие действия:
1. Предварительный экспорт данных из MetaTrader 4 в MATLAB.
На рисунках подробно показан процесс ручного экспорта данных в MATLAB. После окончания экспорта в рабочем столе MATLAB будут созданы переменные.
2. Поиск верных формул, диапазона параметров формул, и т.п.
Данный процесс творческий и самый важный, но тема разработка математического алгоритма индикатора и/или советника в эту статью не входит, Вы можете поискать справку в литературе, посвященной MATLAB.
3. Создание M-функции в MATLAB.
Создание функции для программиста, знакомого с С++ и/или MQL4, не составляет особых проблем, особенно если учитывать тот факт, что все переменные имеют один тип данных «матрица». Т.е. несущественно и незначимо явно определять переменную как массив или многомерный массив, за вас сделает это язык. Так же считаю неважным процесс подбора типа данных, я лично использую всегда mxREAL, да, возможно, расходуется больше памяти, но зато никакой путаницы. Ну а более подробную справку можно получить в литературе № 1, 2. В приведенном примере реализован фильтр высоких частот.
III. Разработка «Dll-обертки»
На данном пункте остановимся подробно, так как он ВАЖЕН как воздух. Итак, любая Dll-библиотека позднего связывания должна удовлетворять следующим условиям:
- Иметь внутренние функции для сбора «мусора» и очистки памяти после своей работы.
- Быть по возможности многопоточной, т.е. поддерживать работу более одного потока одновременно.
- Располагаться в определенных каталогах, см. ниже расположение файлов проекта.
Основными внешними функциями «Dll-обертки» являться API-интерфейс MATLAB Engine и одна функция стандартной библиотеки ввода/вывода С++. API-интерфейс MATLAB Engine прост и лаконичен и содержит всего восемь функций:
Engine *pEng = engOpen(NULL) – функция вызова рабочего стола MATLAB, параметр всегда NULL, возвращает указатель на “дескриптор” рабочего стола, нужен для работы других функций, делаем переменную глобальной.
int exitCode = engClose(Engine *pEng) – функция закрытия рабочего стола, pEng указатель на «дескриптор» рабочего стола, возвращает значение, но его смысл неважен, т.к. данная функция вызывается при закрытии Dll и роли особой не играет, вернет число «пользователей» стола MATLAB.
mxArray *mxVector = mxCreateDoubleMatrix(int m, int n, int ComplexFlag) – функция создает матрицу для рабочего стола MATLAB, возвращает указатель на переменную матрицу. Нужна для создания переменной совместимой с MATLAB. Обычные массивы данных и/или простые типы данных в MATLAB отправлять нельзя!
mxArray *mxVector – указатель на переменную матрицу;
int m – кол-во рядов;
int n – кол-во колонок;
ComplexFlag – тип комплексного числа, всегда mxREAL для правильной работы с MetaTrader 4.
void = mxDestroyArray(mxArray *mxVector) – функция уничтожает матрицу MATLAB, необходима для очистки памяти. Всегда удаляйте данные, когда отпадает в них необходимось, иначе не избежать проблем с памятью, или еще хуже - "наложением" результатов.
mxArray *mxVector – указатель на переменную матрицу.
int = engPutVariable( Engine *pEng, char *Name, mxArray *mxVector) – функция отправки переменной в рабочий стол. Необходимо не только создовать переменные типа mxArray , но еще отправлять их в MATLAB.
Engine *pEng – указатель на “дескриптор” рабочего стола;
char *Name – имя переменной в столе MATLAB, тип char;
mxArray *mxVector – указатель на переменную матрицу.
mxArray *mxVector = engGetVariable(Engine *pEng, char *Name) – функция получения переменной из рабочего стола, обратная функция предыдущей. Принимать можно переменные типа mxArray.
mxArray *mxVector – указатель на переменную матрицу;
Engine *pEng – указатель на “дескриптор” рабочего стола;
char *Name – имя переменной в столе MATLAB, тип char.
double *p = mxGetPr(mxArray *mxVector) – функция получает указатель на массив значений, используется для копирования данных совместно с memcpy(…). Когда получаете/записываете переменную типа mxArray , для извлечения/вставки переменной простого типа(int, double...) используйте данную функцию.
double *p – указатель на массив типа double;
mxArray *mxVector – указатель на переменную матрицу.
int = engEvalString(Engine *pEng, char *Command) – функция отправки команды рабочему столу. Команда в строке Command будет выполнена рабочим столом MATLAB.
Engine *pEng – указатель на “дескриптор” рабочего стола;
char *Command – команда для MATLAB, строка тип char.
Функция для работы с памятью только одна:
void *pIn = memcpy (void *pIn, void *pOut, int nSizeByte) – функция копирования (клонирования) переменной (массива) pOut в pIn переменную размером nSizeByte байт.
ВНИМАНИЕ: следите за размерностью массивов, они должны либо совпадать, либо массив pIn должен быть больше pOut.
Требования к экспортным функциям «Dll-обертки»
Для того чтобы MetaTrader мог использовать Matlab, необходимо написать функции-переходники. Рассмотрим требования к проектированию данных функций. Любая функция, которая будет вызвана из MetaTrader 4, должна быть __stdcall – т.е. передача параметров через стек, функция чистит стек. Расмотрим, как объявить функцию:
extern "C" __declspec(dllexport) <тип_переменной> __stdcall Funcion(<тип> <имя>);
extern "C" __declspec(dllexport) - говорит компилятору С++, что функция внешняя, записывается в таблице экспорта.
<тип_переменной> - тип возвращаемой переменной, может быть: void, bool, int, double, составные типы и указатели передавать нельзя, см. ниже;
__stdcall – соглашение о передаче параметров в функцию и обратно;
Funcion – имя Вашей функции;
<тип> <имя> - тип и имя входной переменной, максимальное количество переменных - 64.
А вот прототип определения функции, также обращаем внимание на __stdcall
bool __stdcall Funcion (<тип> <имя>) { //…… }
Но кроме этого, еще нужно создать файл с расширением def, это обычный текстовый файл, описывающий имя библиотеки и имена экспортных функций. Если этого файла не будет, то Ваш компилятор "придумает" свои искаженные имена функций; думаю, это никому не надо, т.к. это усложнит использование Dll. Вот пример файла:
LIBRARY NameDll
EXPORTS
NameFunctionA
NameFunctionB
LIBRARY – служебное слово, указывает на имя Dll.
EXPORTS – служебное слово говорит, что ниже будут перечислены имена функций.
NameFunctionA, NameFunctionB – имена функций Dll.
Но есть ограничения, накладываемые MQL: т.к. в данном языке нет указателей, значит, нет динамической памяти, а значит нельзя передавать из DLL библиотеки массивы, структуры и т.п. Но кто сказал, что нельзя записывать данные в массивы MetaTrader переданные функции по ссылке? Никто не запрещает Вам писать результат в массив, который создал MetaTrader, и указатель которого получила Ваша Dll, но массив должен быть определенного размера, и не может быть индикаторной линией (это ограничение, по-видимому, связано со специфичной организацией памяти в MetaTrader 4).
Теперь, зная как написать и какие функции вызвать, рассмотрим типовой алгоритм «Dll-обертки»:
1. Запуск MATLAB Engine с помощью функции engOpen() при первом обращении к DLL;
2. Получение данный из MetaTrader и возврат обратно, Dll-функция;
2.1. Создание переменных функцией mxCreateDoubleMatrix();
2.2. Копирование данных в переменную mxVector, функции memcpy() и mxGetPr();
2.3. Передача переменных в рабочий стол MATLAB, функция engPutVariable();
2.4. Передача формулы/команды в рабочий стол MATLAB, функция engEvalString();
2.5. Получение ответа из рабочего стола MATLAB, функция engGetVariable();
2.6. Возврат значения в MetaTrader, функции memcpy() и mxGetPr();
3. Закрытие MATLAB Engine с помощью функции engClose(), удаление всех переменных mxDestroyArray() при выгрузке DLL из адресного пространства процесса MetaTrader.
Имея подробный план местности, составим скелет «Dll-обертки»:
/*--------------------------------------------------------------------------- ** Библиотеки + *.lib + *.def: ** libeng.lib** libmx.lib ** libmex.lib** имя_проекта.def */ #include <windows.h>#include <memory.h>#include "engine.h" //--------------------------------------------------------------------------- extern "C" __declspec(dllexport)<тип_переменной>__stdcall Funcion(<тип><имя>); //--------------------------------------------------------------------------- int WINAPI DllEntryPoint(HINSTANCE hinst,unsigned long reason,void *lpReserved) { /* ** причина вызова DLL */ switch(reason) { case DLL_PROCESS_ATTACH: /* ** DLL загружена в адресное пространство процесса */ break; case DLL_PROCESS_DETACH: /* **DLL выгружена из адресного пространства процесса */ break; } return TRUE; } //--------------------------------------------------------------------------- bool __stdcall Funcion(<тип><имя>) { …… } //---------------------------------------------------------------------------
Сборка проекта
На рисунке показано, как добавлять библиотеки и def файлы в проект:
Вот список файлов, необходимых проекту “Dll-обертки”:
- libeng.lib – расположен в паке: \Program Files\MATLAB\R2007a\extern\lib\win32\borland\
- libmx.lib – расположен в паке: \Program Files\MATLAB\R2007a\extern\lib\win32\borland\
- libmex.lib – расположен в паке: \Program Files\MATLAB\R2007a\extern\lib\win32\borland\
- имя_проекта.def – этот файл создаем в «блокноте», как описано выше.
Файл engine.h копируем из папки \Program Files\MATLAB\R2007a\extern\\include в папку \Program Files\Borland\CBuilder6\Include, чтобы каждый раз не указывать путь компилятору.
Внимание: Даны указания для сборки проекта только в Borland C++ Builder 6!
IV. Разработка MQL программы
Будем рассматривать вопросы, связанные только с объявлением функций “Dll-обертки” и передачей параметров. Итак, чтобы объявить функции, необходима следующая языковая конструкция:
#import "HighPass.dll" void ViewAnsFilter(); bool TestDllFilter(); bool AdaptiveHighFilter(double& nInVector[], int nSizeVector, double nSizeWind, double dAmplit); void MakeBuffFilter(int nSize); void DestrBuffFilter(); #import
где:
#import "HighPass.dll" – ключевое слово и имя dll библиотеки;
void MakeBuffFilter(int nSize); - имя функции, тип возвращаемого значения, имя и тип передаваемого значения.
Обратите внимание! При передаче массивов используют “[]”, знак амперсанд “&” – необходим, если dll пишет ответ в этот массив данных! Других способов передачи массивов из внешних программ в MQL 4 не предусмотрено! Передаваемый массив должен быть определенного размера и не может быть индикаторным массивом!
V. Расположение файлов
После сборки всего проекта необходимо правильно разместить файлы проекта:
*.dll и *.m - файлы библиотеки и m-функции в каталог \Program Files\MetaTrader\experts\libraries;
, а *.mql располагается в обычном месте, т.е. если индикатор - в папке indicators, если советник - в папке experts, если скрипт - в папке scripts.
ВНИМАНИЕ: При запуске индикатора или советника может появиться предупреждение:
В таком случае необходимо подождать 5-10 секунд до появления в панели задач Console Matlab и нажать «повторить».
P.S. У меня ноутбук с 512 ОЗУ, Celeron M 2100, задержек при работе фильтра не наблюдал, кол-во графиков 5, с суммарным буфером 500 х 8 х 5 = 20 000 байт. Так что- выбор за Вами! Лично я его уже сделал. Если появятся задержки, то в MATLAB легко можно организовать распределенную вычислительную систему, иными словами запустить несколько рабочих столов на разных PC, объединенных в локальную сеть.
Литература
- Встроенная справка по MATLAB.
- «Matlab 5.х вычисления, визуализация, программирование.» Н.Н. Мартынов.
- «C++ Builder 6. Справочное пособие.» А.Я. Архангельский.
- Встроенная справка по MQL4.
Заключение
В данной статье рассмотрели основы разработки "Dll-обертки" для связывания MetaTrader 4 c математическим пакетом MATLAB. Вопросы обеспечения работы нескольких индикаторов и/или советников остался без внимания, это будет раскрыто в следующей статье. В прикрепленном файле находиться стандартный индикатор MACD, усовершенствованный за счет использования фильтра высоких частот.
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования
Выложил адаптированный проект сюда http://neurotrading.ru/forum/20-56-1#909
С уважением Дмитрий
Вопрос. Насколько "коряво", по сравнению с предложенным методом, будет делать .exe файлы из нужных Matlab-овских функций с помощью Matlab Compiler и затем обращаться к exe-шникам посредством "командной строки" (http://www.metatrader4.com/forum/1476) из mql4 кода? Или это будет очень медленно?
Вопрос. Насколько "коряво", по сравнению с предложенным методом, будет делать .exe файлы из нужных Matlab-овских функций с помощью Matlab Compiler и затем обращаться к exe-шникам посредством "командной строки" (http://www.metatrader4.com/forum/1476) из mql4 кода? Или это будет очень медленно?
Вопрос больше системного характера, не желе касающегося MatLab и MetaTrader'а... С точки зрения ОС Windows файлы с расширением *.exe и *.dll различаются только наличием логической единицы у Dll по смещению 05Eh в файле относительно заголовка "PE\0\0", но есть еще одна тонкость, Dll грузиться в адресное пространство вызываемого процесса, в нашем случае MT4/MT5, а *.exe создает свой отдельный процесс и свое отдельное адресное пространство, естественно этот факт не может остаться незамеченным и комп тормозит, а если учесть тот факт, что данная программа будет постоянно загружаться-выгружать в память PC это будет очень накладно по времени.
Если бы MT4/MT5 был создан для Linux, то это было бы делом вкуса, создавать DLL или отдельную программу, в Linux есть расширенное понятие канал(Pipe), в отличии от Win32. Тут уж нечего не поделаешь, такова политика фирмы Micro$oft - изоляция программ их девиз.
Если нет возможности углубляться в Си и т.п. вот вариант попроще.
https://www.mql5.com/ru/forum/228342#comment_6712127