简介
本文是A.Emelyanov的文章“MetaTrader 4和 MATLAB之间的交互的进一步发展,它提供了有关解决用户使用的所有平台的现代64位版本的类似任务的信息。在过去的一段时间里,创建共享DLL库的方法已经在 MATLAB 包中进行了实质性的升级,因此,原文讨论的方法需要修改。这是因为现在必须使用 MATLAB 编译器 SDK 或 MATLAB 编码器而不是 MATLAB 编译器。此外,在 MATLAB 中使用动态内存的实践也发生了变化,这意味着对源代码进行了一定的调整,以处理用 MATLAB 语言编写的库。
本文的名称指出,本文的工作旨在帮助开发人员将 MATLAB 的计算能力与 MQL5 程序合并,为此,本文以一个基于季节自回归综合移动平均(SARIMA)模型的价格时间序列预测指标的创建为例,在 MATLAB 平台上进行了模型选择和数据外推的任务。
为了演示将 MATLAB 2018 环境的计算能力连接到 MQL5 的细节,本文着重于 MATLAB 编译器SDK,以及在 Visual C++上创建一个用于将 MATLAB 库与 MQL5 配对的适配器库。这使人们能够快速获得创建程序的指南,并避免在过程中出现常见错误。
A. Emelyanov 所写文章第一章中所描述的简单和复杂的数据类型不会被改变。为了不重复定性陈述的材料,最好自己熟悉此处所述的描述。差异是在从MATLAB环境创建共享C++库的阶段出现的。
本文根据以下方案提出材料:
- 利用预先编制的一组 MATLAB 模块,建立了基于季节自回归综合移动平均值>模型的C++共享库。
- 然后把计算模块包含在MQL5程序中。为此,还创建了一个中间库,它解决了MQL5(用C/C++风格组织的存储器)和MATLAB(以矩阵形式组织的存储器)之间传输数据的问题。
- 描述了一个预测模型,并将其嵌入到所创建的指标中,并对其性能进行了验证。
1. 利用 MATLAB 编译器 SDK 从 MATLAB 函数中创建共享C++库
2015年,在 MATLAB 中创建 DLL 库的过程发生了变化。至于与 MQL5 程序的集成,问题归结为 MATLAB 编译器不再用于创建库,而是面向生成自主可执行文件。自2015年以来,用于创建 DLL 库的函数已转到 MATLAB 编译器SDK。MATLAB 编译器 SDK 扩展了 MATLAB 编译器的功能,允许它从 MATLAB 程序中创建C/C++共享库、Microsoft .NET程序集和Java类。
和以前一样,使用 MATLAB 编译器SDK包中的程序组件创建的应用程序可以在不需要 MATLAB 的用户之间免费重新分发。这些应用程序使用 MATLAB 运行时和一组使用编译应用程序或 MATLAB 组件的共享库。
先前分配给 MATLAB 编译器应用程序的任务已委托给库编译器程序。为了给出完整的流程,让我们探讨从 MATLAB 环境创建C/C++共享库的过程。附加到文章的zip存档包含扩展名为.m的文件,这些文件用于创建库。在 MATLAB 中,启动库编译器应用程序(在 APPS 选项卡上),打开 LibSARIMA.prj 项目,并形成一个类似下图所示的结构。
图 1. 库编译器界面。
在这里,重要的是要注意图1中用线和数字1-4突出显示的位置。
- 创建C++标准的共享库
- ForecastSARIMA.m中显示的函数被导出,而其他函数不受导出的影响,也不被授予访问外部程序的权限。
- 生成标准接口中用于链接的文件(matlabsarima.dll、matlabsarima.h、matlabsarima.lib)。
- mwArray结构上采用了 MATLAB 矩阵访问接口。
按下“打包(Package)”按钮后,将生成库。可以选择要求用户从 Internet 下载 MATLAB 引擎的库生成模式,或者在包内容中包含必要的 MATLAB 引擎组件。
在此阶段,将创建一个库,其中包含一个用于过滤时间序列、构建 SARIMA 模型和预测的程序。档案 MatlabSArima.zip 提供一组源代码和生成的库构建。
2. 在微软 Visual C++ 中创建中介库
一旦创建了主库,下一个任务就是连接到它,传递数据,并在计算后收集结果。这包括创建一个适配器库,它提供MQL5(用C/C++风格组织的内存)和MATLAB(以矩阵形式组织的内存)之间的数据转换。
在 MATLAB x64的新版本中,Visual C++编译器是其中的主要部分,为此准备了所有必要的软件。因此,编写辅助适配器库的最快、最方便和可靠的方法是在 Studio 2017中使用 VisualC++。
图 2. Diagram of MetaTrader 5 and MATLAB 2018 interaction via an adapter DLL
2017的一个重要创新是引入了这样的结构:把 mwArray API 引入到 MATLAB,它允许将打包的函数创建和集成到C++应用程序中。之前使用的 mxArray 升级到了新的矩阵接口。还有一个集成共享库的选项 — MATLAB Data API 接口, 但是它与我们的情况不相关。
要开始为数据编写适配器,需要在操作系统中准备并注册三个系统环境变量。例如,可以通过“系统属性”使用资源管理器,转到“高级系统设置”和“环境变量”。
- 第一个变量 — MATLAB_2018 应指向装有 MATLAB 或 MATLAB 运行时的目录;
- 第二个变量 — MATLAB_2018_LIB64 应指向包含外部库的目录: <MATLAB_2018>\extern\lib\win64;
- 第三个变量 — MATLIB_USER 应该指向放置原始库的目录。为了解决搜索原始用户库的问题,还必须将此目录添加到系统变量“Path”中。
2.1在Visual Studio 2017中编写适配器
在Visual Studio 2017中创建动态链接库项目后,需要设置一些属性。为了明确哪些属性需要控制,下面是便于装配项目配置的图。
图 3. 需要修改的属性页面 (A, B, C, D, E)
图 4. 要搜索必要项目文件的目录
在图4中, the $(MatLib_User) 目录被加到标签位 "Library Directories" 的栏位中,这个目录便于放置通用库,这两个库都需要Visual C/C++中的编程和 MetaTrader 5中的计算。在这种情况下,它们是matlabsarima.lib和matlabsarima.dll。
图 5. 设置预处理器的定义
图 6. 根据MQL5的要求调用约定
图 7. 指定其他依赖项 (*.lib)
以下是项目设置中所需更改的列表:
- 指定具有所需头文件的目录;
- 指定包含所需库文件的目录;
- 设置预处理器定义-宏,其功能在下面会探讨;
- 指定工作所需的特定库(由 MATLAB 编写)。
两个使用 MATLAB 生成的文件 — matlabsarima.lib 和 matlabsarima.dll 应当被放到共享目录中,在系统变量中标记为 $(MATLIB_USER) 。matlabsarima.h文件应该位于项目组装目录中。它应该包含在项目的“头文件”中。
要组装适配器,需要创建几个文件,其中两个文件值得探讨。
1. AdapterSArima.h 文件
#pragma once #ifdef ADAPTERSARIMA_EXPORTS #define _DLLAPI extern "C" __declspec(dllexport) // 此定义对于将DLL和LIB库配对是必需的 #else #define _DLLAPI extern "C" __declspec(dllimport) // 绑定DLL库需要此定义 #endif _DLLAPI int prepareSARIMA(void); _DLLAPI int goSarima(double *Res, double *DataArray, int idx0, int nLoad, int iSeasonPriod = 28, int npredict = 25, int filterOn = 1, int PlotOn = 0); _DLLAPI void closeSARIMA(void);
adaptersarima.h文件使用设置中指定的宏,来指出prepareSARIMA(), closeSARIMA() and goSarima(...) 是可以用于绑定外部程序的。
2. GoSArima.cpp 文件
#pragma once #include "stdafx.h" #include "matlabsarima.h" #include "AdapterSArima.h" bool SArimaStarted = false; bool MLBEngineStarted = false; //----------------------------------------------------------------------------------- _DLLAPI int prepareSARIMA(void) { if (!MLBEngineStarted) { MLBEngineStarted = mclInitializeApplication(nullptr, 0); if (!MLBEngineStarted) { std::cerr << "无法初始化 Matlab Runtime (MCR)" << std::endl; return 0; } } if (!SArimaStarted) { try { SArimaStarted = matlabsarimaInitialize(); if (!SArimaStarted) { std::cerr << "无法初始化库属性" << std::endl; return false; } return (SArimaStarted)?1:0; } catch (const mwException &e) { std::cerr << e.what() << std::endl; return -2; } catch (...) { std::cerr << "抛出未知错误" << std::endl; return -3; } } return 1; } //----------------------------------------------------------------------------------- _DLLAPI void closeSARIMA(void) { // 调用应用程序和库终止例程 //if (SArimaStarted) { matlabsarimaTerminate(); SArimaStarted = false; } } //----------------------------------------------------------------------------------- _DLLAPI int goSarima(double *Res, double *DataSeries, int idx0, int nLoad, int iSeasonPeriod, int npredict, int filterOn, int PlotOn) { // // 必须提前为结果分配内存,同时考虑指标中的预测。 // 用于 Res[] 的内存必须分配. Length = nLoad+npredict !!! // if (!SArimaStarted) { SArimaStarted = (prepareSARIMA() > 0) ? true : false; } mwArray nSeries(1, 1, mxDOUBLE_CLASS), TimeHor(1, 1, mxDOUBLE_CLASS), MAlen(1, 1, mxDOUBLE_CLASS); mwArray SeasonLag(1, 1, mxDOUBLE_CLASS), DoPlot(1, 1, mxDOUBLE_CLASS), DoFilter(1, 1, mxDOUBLE_CLASS); if (SArimaStarted) { try { MAlen = 20; // MA 平均窗口 DoFilter = (filterOn != 0) ? 1 : 0; TimeHor = npredict; // 预测点 SeasonLag = iSeasonPeriod; // SARIMA 模型的季节性周期 DoPlot = PlotOn; // 测试模式的绘图 nSeries = nLoad; // 数据片段的长度 nLoad mwArray Series(nLoad,1, mxDOUBLE_CLASS, mxREAL); mwArray Result; // 结果 (过滤 (if specified filterOn!=0) 数据和预测) // 把数据载入到 MATLAB 矩阵 Series.SetData(&DataSeries[idx0], nLoad); // 片段 DataSeries[idx0: idx+nLoad] ForecastBySARIMA(1, Result, nSeries, TimeHor, Series, SeasonLag, DoFilter, MAlen, DoPlot); size_t nres = Result.NumberOfElements(); Result.GetData(Res, nres); return 0; } catch (const mwException& e) { std::cerr << e.what() << std::endl; return -2; } catch (...) { std::cerr << "抛出未知错误" << std::endl; return -3; } } return 0; }
为了完整起见,zip存档包含用于组装adaptersarima.dll中间库的所有文件。如有必要,建议在“C:\temp”中解压缩存档文件并重新组装适配器。
3. 创建指标
3.1问题定义及解决方法
自回归和移动平均模型对于描述实践中遇到的一些时间序列非常有用。该模型结合了一个低通过滤器,其形式为移动平均阶数q=自回归滤波值的阶数p过程。如果模型使用的不是时间序列值,而是它们的顺序差异d(在实践中,有必要确定d,但在大多数情况下d≤2)作为输入数据,则该模型称为综合移动平均值的自回归。这样的模型 - ARIMA(p,d,q)(自动回归的整合移动平均)允许减少原始序列的非平稳性。
为了模拟长期变化的影响,有一种被称为季节性假象的变化。这个模型对应于暴露在周期因素下的时间序列。股票报价受季节性因素的影响,因此,将其纳入模型适合于在指标中建立价格预测。
为了减少输入股票报价中噪声因素的影响,需要提供额外的过滤和清除错误数据的能力。数据越嘈杂,处理起来就越困难。Kalman 过滤器是一种有效的递归过滤算法,用于各个领域。该算法由预测和调整两个重复阶段组成。首先,计算下一个时间点的状态预测(考虑到测量的不精确性)。然后,考虑到新的信息,对预测值进行校正(同时考虑到该信息的不准确度和噪声)。
3.2使用 MQL5 的指标程序
指标所需的 adaptersarima.dll 和 matlabsarima.dll 库应放在MetaTrader 5工作目录的 Libraries目录中。
调试和测试有一定的细节。在这种模式下,MetaEditor 从辅助目录中载入库 "<MetaQuotes\Tester\....\Agent-127.0.0.1-300x>", 其中 300x 可以是数值 3000, 3001, 3002, 等等。在这种情况下,将自动复制 AdapterSArima.dll库,但matlabsarima.dll不是。为了防止这影响指标操作,matlabsarima.dll 库应该放在系统搜索目录中。建议将该目录指定为$(MATLIB_USER),并在搜索路径的系统列表中指定该目录,或复制到Windows或Windows\System32。然后将检测到库并连接,指标就将启动。
根据所考虑的模型实现预测的指标程序可在 ISArimaForecast.mq5 文件和附加的存档中使用。
//+------------------------------------------------------------------+ //| ISArimaForecast.mq5 | //| Roman Korotchenko | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Roman Korotchenko" #property link "https://login.mql5.com/ru/users/Solitonic" #property description "本指标演示了根据 SARIMA(2,1,2) 来进行预测." #property description "该程序积极使用MATLAB,具有专业开发的工具箱和扩展能力。" #property version "1.00" #property indicator_chart_window #import "AdapterSArima.dll" int prepareSARIMA(void); int goSarima(double &Res[],double &DataArray[],int idx0,int nLoad,int iSeasonPeriod,int npredict,int filterOn,int PlotOn); void closeSARIMA(void); #import #property indicator_buffers 2 //---- 用于计算和绘制指标的缓冲区 #property indicator_plots 1 //---- 图形构造 #property indicator_type1 DRAW_COLOR_LINE #property indicator_color1 clrChocolate, clrBlue // clrWhite, clrBlue #property indicator_width1 3 //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ enum ENUM_TIMERECALC { TimeRecalc05 = 5, // 5 秒 TimeRecalc10 = 10, // 10 秒 TimeRecalc15 = 15, // 15 秒 TimeRecalc30 = 30, // 30 秒 TimeRecalc60 = 60 // 60 秒 }; //--- 输入参数 input ENUM_TIMERECALC RefreshPeriod=TimeRecalc30; // 重计算周期数 input int SegmentLength = 450; // N: 数据片段 input int BackwardShift = 0; // 反向偏移 (测试) input int ForecastPoints = 25; // 预测点 input int SeasonLag=32; // SARIMA(2,1,2) 的季节延迟 input bool DoFilter=true; // 对数据序列做 Kalman 过滤 // input string _INTERFACE_ = "* INTERFACE *"; //input long magic_numb=19661021777; // 幻数 //--- 指标缓冲区 double DataBuffer[],ColorBuffer[]; //double LastTrend[],LastData[]; double wrkResult[],wrkSegment[]; static int wRKLength; uint CalcCounter; // uint calc_data; uint start_data; // 构建图表的开始时间 uint now_data; // 当前时间 static int libReady=0,ErrorHFRange,ErrorDataLength; static bool IsCalcFinished; //+------------------------------------------------------------------+ //| 自定义指标初始化函数 | //+------------------------------------------------------------------+ static int LengthWithPrediction; static int PlotOn; //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void OnDeinit() { closeSARIMA(); Alert("SARIMA DLL - DeInit"); Print("SARIMA DLL - DeInit"); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ int OnInit() { if(!TerminalInfoInteger(TERMINAL_DLLS_ALLOWED)) Alert("在终端设置 DLL 中检查连接许可!"); else { libReady=prepareSARIMA(); if(libReady<0) { Alert("DLL 未能连接!"); return(INIT_FAILED); } } LengthWithPrediction=SegmentLength+ForecastPoints; //--- 指标缓冲区映射 SetIndexBuffer(0,DataBuffer,INDICATOR_DATA); ArraySetAsSeries(DataBuffer,true); SetIndexBuffer(1,ColorBuffer,INDICATOR_COLOR_INDEX); ArraySetAsSeries(ColorBuffer,true); // SetIndexBuffer(2,LastTrend, INDICATOR_CALCULATIONS); ArraySetAsSeries(LastTrend,true); //用于 EA // SetIndexBuffer(3,LastData, INDICATOR_CALCULATIONS); ArraySetAsSeries(LastData,true); //用于EA PlotIndexSetInteger(0,PLOT_SHIFT,ForecastPoints-BackwardShift); wRKLength = ForecastPoints+ SegmentLength; // 在结果数组中元素的数量 ArrayResize(wrkResult,wRKLength,0); // 为函数结果分配内存 memory for function results ArrayResize(wrkSegment,SegmentLength,0); // 为输入数据分配内存 //--- string shortname; StringConcatenate(shortname,"SARIMA(2,1,2). Season Lag: ",SeasonLag," // "); //--- 用于在数据窗口显示的标签 PlotIndexSetString(0,PLOT_LABEL,shortname); IndicatorSetString(INDICATOR_SHORTNAME,shortname); now_data = 0.001*GetTickCount(); start_data= 0.001*GetTickCount(); calc_data = 0; CalcCounter = 1; IsCalcFinished = true; ErrorHFRange = 0; ErrorDataLength= 0; PlotOn=0; // Auxiliary drawing, executed by MATLAB (for testing) return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| 自定义指标迭代函数 | //+------------------------------------------------------------------+ int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[] ) { //--- int ShiftIdx=rates_total-SegmentLength-BackwardShift; // 开始操作数据段的索引 if(ShiftIdx<0) { if(!ErrorDataLength) { PrintFormat("SARIMA 指标错误: 没有足够数据."); ErrorDataLength=1; } return(0); } ErrorDataLength=0; //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - now_data=0.001*GetTickCount(); if(now_data-calc_data<RefreshPeriod || !IsCalcFinished) // 不需要计算或者没有完成 { // ReloadBuffers(prev_calculated,rates_total); return(prev_calculated); } if(prev_calculated!=0 && !IsCalcFinished) { return(prev_calculated); // 新数据来的比计算结束更快 } //--------------------------------------------------------------------------- IsCalcFinished=false; // 阻止请求执行新计算,直到执行当前计算 int idx=0,iBnd2=ShiftIdx+SegmentLength; for(int icnt=ShiftIdx; icnt<iBnd2; icnt++) { wrkSegment[idx++]=price[icnt]; } ErrorHFRange=0; // MATLAB SUBROUTINE goSarima(wrkResult,wrkSegment,0,SegmentLength,SeasonLag,ForecastPoints,DoFilter,PlotOn); ReloadBuffers(LengthWithPrediction,rates_total); ++CalcCounter; IsCalcFinished=true; // 准备好进行新的计算 calc_data=0.001*GetTickCount(); return(rates_total); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void EmptyBuffers(int n) { for(int i=0; i<n; i++) { DataBuffer[i] = EMPTY_VALUE; ColorBuffer[i]= EMPTY_VALUE; } } //+------------------------------------------------------------------+ void ReloadBuffers(int npoint,int ntotal) { ResetLastError(); EmptyBuffers(ntotal); // ntotal = rates_total if(npoint== 0) return; int k=0;//BackwardShift; for(int i=0; i<npoint; i++) // npoint = LengthWithPrediction { if(i>=ntotal) break; DataBuffer [k]=wrkResult[LengthWithPrediction-1-i]; ColorBuffer[k]=(i<ForecastPoints)? 1:0; k++; } } //=============================================================================
4. 指标操作展示
该指标的表现通过 MetaTrader 平台提供的 EURUSD H1 交易数据进行测试。选择的数据段不太大,等于450个单位。长期“季节性”滞后等于28,30和32个单位进行了测试。在考虑的历史时期,32个单位的滞后是其中最好的。
对不同历史片段进行了一系列计算。模型中,数据段长度450个单位,季节滞后32个单位,预测长度30个单位,均为一次设定,未发生变化。为了评估预测的质量,将不同碎片得到的结果与实际数据进行了比较。
下图显示了指标操作的结果。在所有图中,用于选择Sarima(2,1,2)模型的片段的完成都显示在巧克力色中,而在其基础上获得的结果显示在蓝色中。
图 8. 交易时间 30.12.2018. 使用了 Kalman 过滤。基于数据构建的模型将180个单元移到了过去
图 9. 每日交易时段 30.12.2018. 没有使用 Kalman 过滤基于数据构建的模型将170个单元移到了过去
图 10. 交易时段 31.12.2018. 使用了 Kalman 过滤。基于数据构建的模型将140个单元移到了过去
图 11. 交易时段 1.02.2019. 使用了 Kalman 过滤。基于数据构建的模型将120个单元移到了过去
图 12. 交易时段 4.02.2019. 使用了 Kalman 过滤。基于数据构建的模型将100个单元移到了过去
图 13. 交易时段 6.02.2019. 使用了 Kalman 过滤。基于数据构建的模型将50个单元移到了过去
图 14. 交易时段 7.02.2019. 使用了 Kalman 过滤。基于数据构建的模型将20个单元移到了过去
图 15. 交易时段 8.02.2019. 使用了 Kalman 过滤。基于数据构建的模型将10个单元移到了过去
建模结果显示,在前10-12个单元中生成的价格与实时观察到的值匹配的可能性很高。此外,有趣的是,建立模型只需要交易者做一些工作。模型段长度和季节性周期需要两个参数,可通过对最新历史数据进行连续扫描来选择。其余的计算负载转到MATLAB。在未来,可以考虑对分段长度和季节性周期的参数进行优化选择,作为改进指标的一种方法。
结论
本文使用64位版本的 MQL5 和 MATLAB 2018包演示了整个软件开发周期。此外,它还显示了使用Visual C++ 2017(X64)来创建一个适配器,它提供MQL5(具有C/C++风格的存储器)和 MATLAB(以矩阵形式组织的内存)之间的数据转换。
提出了一种基于 SARIMA 模型和 Kalman 过滤的预测指标,用以论证在计量经济学应用中使用 MATLAB 的可能性。它具有很大的发展潜力,提供了一个基于MATLAB的数据处理和季节性成分的自动检测,以优化工作预测模型。
作为一个例子,该指标说明了MATLAB 的使用,MATLAB 包允许快速有效地将 MetaTrader 系统与神经网络、模糊逻辑算法以及其他复杂和现代的股票报价处理方法集成在一起。
附加的存档(MatlabSArima.zip)包含目录MatlabSArima\LibSARIMA\for_distribution,这意味着从Internet下载 MATALAB Runtime。为了减少 SARIMA 指标在 MATLAB 运行时的信息量,需要下载一组10个文件,然后使用 Total Commander 解包并合并它们。
文件 | 下载路径 |
---|---|
sarima_plusMCR00.zip 89.16 MB | https://pinapfile.com/aKrU7 |
sarima_plusMCR01.zip 94.75 MB | https://pinapfile.com/fvZNS |
sarima_plusMCR02.zip 94.76 MB | https://pinapfile.com/k7wB5 |
sarima_plusMCR03.zip 94.76 MB | https://pinapfile.com/jwehs |
sarima_plusMCR04.zip 94.76 MB | https://pinapfile.com/dv8vK |
sarima_plusMCR05.zip 94.76 MB | https://pinapfile.com/hueKe |
sarima_plusMCR06.zip 94.76 MB | https://pinapfile.com/c4qzo |
sarima_plusMCR07.zip 94.76 MB | https://pinapfile.com/eeCkr |
sarima_plusMCR08.zip 94.76 MB | https://pinapfile.com/jDKTS |
sarima_plusMCR09.zip 94.76 MB | https://pinapfile.com/dZDJM |