MеtaTrader 4 和 MATLAB Engine 的交互(虚拟 MATLAB 机)

Andrey Emelyanov | 12 四月, 2016

简介

MetaTrader 4 和 MATLAB 数学包由于其良好的特性(包括在创建复杂计算系统中的“灵活性”),非常受用户的欢迎。 MATLAB 跟外部应用程序连接有三种主要方式,但我只推荐其中的一种 - 使用虚拟桌面 MATLAB Engine。 这种方法保证跟完整的 MATLAB 包完全兼容。 很多程序员出于以下原因回避这种方法:

我推荐这种方法的原因:

  1. 这是跟外部程序连接的方法中最可靠和不受 MATLAB 版本影响的一个。 你可以更改 MATLAB 的版本,而你的指标或 Expert Advisor 根本不会注意到这点。 这是最重要的优势。
  2. 它具有相对快速的开发方法。 不需要调试程序,并且编写 DLL 包装程序也不困难。
  3. 多个指标和/或 Expert Advisor 的“普通桌面”。 当我们需要基于多个指标的数据进行决策或在实现金字塔交易时,我认为这种方法非常有用。

本文描述了连接 MetaTrader 4 和 MATLAB(ver.7.4.0(R2007a))的方法, 通过在 Borland C++ Builder 6 编写的“DLL 包装程序”来实现。 喜欢 Microsoft 产品的程序员必须使示例适应他们的编译器(考虑到任务的复杂性,祝你好运!)



I.设定任务

首先,需要确定从哪里着手启动项目。 我们将开发过程分为三部分:

  1. 开发 MATLAB 中实现指标/EA 计算的 M-函数。
  2. 开发连接 MATLAB 和 MetaTrader 4 的“DLL-包装程序”。
  3. 开发 MQL 程序。


II. 开发 M-函数

这可能是最有趣和运行时间较长的过程,包含以下操作:

1. 将数据从 MetaTrader 4 预导出到 MATLAB。

上图显示了手动将数据导出到 MATLAB 的过程。 当导出结束时,在 MATLAB 桌面将创建变量。

2. 查找正确的公式、公式参数的范围等等。

该过程很有创造性,也很重要,但指标和/或 Expert Advisor 的数学算法开发并非本文讨论的内容。 你可以在关于 MATLAB 的文献中找到相关信息。

3. MATLAB 中的 M-函数创建

了解 C++和/或 MQL4 的程序员在创建该函数时都没有困难 - 另外,所有的变量都具有相同的数据类型 - “矩阵”。 也就是说,清晰的定义一个变量为数组还是多维数组并不重要 - 语言自己会完成定义。 我认为数据类型的选择过程并不重要。 我始终使用 mxREAL。 虽然使用了更多的内存,但却没有造成任何困惑。 更多详情可见于参考文献 1 和 2。 在给出的示例中,使用了高频过滤器。



III. 开发“DLL-包装程序”

我们对这一点进行详细的阐述,因为它就像空气之于人一样重要。 每个后期绑定的 DLL 库必须符合以下条件:

“DLL-包装程序”的主要外部函数是 MATLAB Engine 的 API 界面和标准 C++ 输入/输出库的一个函数。 MATLAB Engine MATLAB Engine ,只包含 8 个函数:

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 m – 列数;

ComplexFlag – 复数类型,为了 MetaTrader 4 的正确运行,始终使用 mxREAL。

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 桌面字符类型的变量名称。

double *p = mxGetPr(mxArray *mxVector) – 接收指向数据数组指针的函数,用于复制数据以及 memcpy(…)。 在接收/写入 mxArray 类型的变量时,使用该函数提取/粘贴一个简单类型(int、double...)的变量。

double *p – 指向双精度类型数组的指针;

mxArray *mxVector – 指向变量矩阵的指针;

int = engEvalString(Engine *pEng, char *Command) – 发送命令到桌面的函数。 Command 行中 的命令将由 MATLAB 桌面执行。

Engine *pEng – 指向桌面“描述符”的指针;

char *Command – MATLAB 命令,字符类型行。

只有一个使用内存的函数:

void *pIn = memcpy (void *pIn, void *pOut, int nSizeByte) – 将 pOut 变量(数组)复制(克隆)至大小为 nSizeByte 字节的 pIn 变量的函数。

注意: 留意数组的维度。 它们必须相等,否则 pIn 数组应大于 pOut。

“DLL-包装程序”导出函数的要求

为了使 MetaTrader 4 能够使用 MATLAB,应该编写函数传递程序。 我们来看一下构建这种函数的要求。 从 MetaTrader 4 调用的任何函数必须是 __stdcall,即通过堆栈传递参数,函数清除堆栈。 下面是函数的声明方式:

extern "C" __declspec(dllexport) <variable_type> __stdcall Funcion(<type> <name>);

extern "C" __declspec(dllexport) - 告诉 C++ 编译器函数为外部函数,在导出表中编写。

<variable_type> - 要返回变量的类型,可以是:void、bool、int、double,复合类型和指针无法传递;查看更多;

__stdcall – 关于参数传递到函数和返回的协议;

Function – 你的函数名称;

<type> <name> - 输入变量的类型和名称;变量的最大数量是 64。

这是函数定义的原型,另请注意 __stdcall

bool __stdcall Funcion (<type> <name>)

{

//……

}

除此之外,还要创建一个扩展名为 def 的文件。 这通常是一个文本文件,描述库名称和导出函数的名称。 如果该文件不存在,你的文件将会自己“编造”失真的函数名称,这样会使 DLL 的使用复杂化。 下面是文件示例:


LIBRARY NameDll

EXPORTS

NameFunctionA

NameFunctionB

LIBRARY –指向 DLL 名称的助词。

EXPORTS – 助词,表示将枚举下面的函数名称。

NameFunctionA、NameFunctionB – DLL 函数的名称。

但 MQL 对其施加了限制: 因为该语言没有指针,它没有动态内存,所以无法从 DLL 库传递数组、结构等。 但在 MetaTrader 中,数据可以写成数组,通过函数的引用进行传递。 结果可以写在由 MetaTrader 创建的数组内,DLL 接收该数组的指针。 但该数组必须是某种维度,而且不能是指标线(该限制可能跟 MetaTrader 4 中特定的内存分配有关)。

现在,在了解了如何编写和调用哪些函数后,我们来看一个“DLL-包装程序”的典型算法:

1. 在第一次调用 DLL 时,使用 engOpen() 函数启动 MATLAB Engine;

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. 使用 engClose() 函数关闭 MATLAB,当从 MetaTrader 进程的地址区加载 DLL 时删除所有的 mxDestroyArray() 变量。

现在我们来创建“DLL-包装程序”的架构:

/*---------------------------------------------------------------------------
** Libraries + *.lib + *.def:
** libeng.lib** libmx.lib
** libmex.lib** project_name.def
*/
#include <windows.h>#include <memory.h>#include "engine.h"
//---------------------------------------------------------------------------
extern "C" __declspec(dllexport)<variable_type>__stdcall Funcion(<type><name>);
//---------------------------------------------------------------------------
int WINAPI DllEntryPoint(HINSTANCE hinst,unsigned long reason,void *lpReserved)
  {
   /*    
** reason for DLL call    
*/
   switch(reason)
     {
      case DLL_PROCESS_ATTACH:
         /*            
** DLL uploaded into the address space of the process            
*/
         break;
      case DLL_PROCESS_DETACH:
         /*            
**DLL loaded from the address space of the process            
*/
         break;
     }
   return TRUE;
  }
//---------------------------------------------------------------------------
bool __stdcall Funcion(<type><name>)
  {
   ……
  }
//---------------------------------------------------------------------------

项目程序集

下图显示了如何将库和 *.def 文件添加到项目:

下面是“DLL-包装程序”项目所需的文件列表:

  1. libeng.lib – 位于: \Program Files\MATLAB\R2007a\extern\lib\win32\borland\
  2. libmx.lib – 位于: \Program Files\MATLAB\R2007a\extern\lib\win32\borland\
  3. libmex.lib – 位于: \Program Files\MATLAB\R2007a\extern\lib\win32\borland\
  4. имя_проекта.def – 该文件应如上所述在记事本中创建。

应将 engine.h 文件从 \Program Files\MATLAB\R2007a\extern\\include 复制到 \Program Files\Borland\CBuilder6\Include 文件夹 - 这样你就不必每次为编译器指定路径了。

注意: 这些说明仅限于在 Borland C++ Builder 6 中汇编项目!



IV. 开发一个 MQL4 程序

我们将考虑只跟“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’文件夹,如果是 EA,则位于‘experts’,如果是脚本,则位于‘scripts’文件夹。

注意: 在启动指标或 Expert Advisor 时,可能会出现服务器繁忙的警告:

这种情况下,请等待 5-10 秒钟,直到工具栏出现 Console Matlab 并单击“重试”。

附言: 我有一个 512 RAM、Celeron M 2100 的笔记本电脑,在过滤器运行、图表数量为 5(总缓冲区为 500 х 8 х 5 = 20 000 字节大小)时没感觉到延迟。 所以,最终的选择在于你! 而我已经做出了选择。 如果出现延迟,在 MATLAB 中可以轻松实现分布式计算系统,也就是说,在连接到一个局域网的不同电脑上可以启动多个桌面。



参考文献列表

  1. Built-in MATLAB Help.
  2. "Matlab 5.х Calculations, Visualization, Programing" N.N. Martynov.
  3. C++ Builder 6. Reference Manual" A.Y. Arkhangelski.
  4. Built-in MQL4 Help.


总结

本文中,我们讨论了用于绑定 MetaTrader 4 和 MATLAB 数学包的“DLL-包装程序”开发的基础知识。 我们没有涉及提供多个指标和/或 Expert Advisor 运行的问题 - 将在下一篇文章中对其进行探讨。 附件中包含了因使用高频过滤器而改进过的 MACD。