在我之前的文章“神经网络在交易中的实际应用”里,我讲述了利用神经网络模块(NNM)创建交易系统的要点。 在本文中,我们将在实践中测试 NNM。 我们还将尝试创建基于 NNM 的自动交易系统。
针对 EURUSD 货币对执行所有操作。 不过,神经网络模块程序是通用的,并且可以与不同的交易工具及其组合一起操作。 因此,可把针对不同货币对进行训练的多个神经网络集成到一个 NNM。
有关为神经网络准备所需训练历史数据的技术不在本文讨论范围之内。 我们的 NNM 所含神经网络,是依据距文章发布时间最近的历史数据训练过的。
由此产生的 NNM 已完全准备好用于交易。 为了能够在一篇文章中厘清复杂内容,我必须对其进行修改,从而在一个程序中组合若干个神经网络模块函数。 此外,我把某些交易执行条件从智能交易系统转移到 NNM。 后者仅涉及真实交易的 NNM 部分。 至于离线模式下的神经网络测试及其响应优化,所有相关功能和交易执行条件仍由相应的智能交易系统执行。 对于我来说,我更喜欢 NN 模块仅含有交易功能。 我坚信用于交易的 NNM 必须尽可能简单,并且不应包含任何其他功能。 功能本身应在交易综合体之外实现。 在我们的案例中,这些功能包括培训、测试和优化。 交易执行条件最好在 NNM 中实现,从而可接收二元形式的信号。 尽管所有 NNM 执行选项都已在实践中证实了其可行性。
在下一篇文章里将更详细地讲述在 Matlab 平台上筹备 NNM,并进行培训的技术。
另外,未来系统可能会移植到 Python。 本文末尾有相关的短视频。
依据来自您的经纪商处获得的历史数据进行测试时,NNM 在很长测试期内产生了积极的结果。 这部分也可以统一,但是我认为这用处不大。
在首次使用经 Matlab 编译的应用程序之前,您必须先运行 MyAppInstaller_mcr.exe。 该应用程序将安装 MATLAB Runtime 和神经网络模块本身。
程序安装后,会把 EURUSD_MT5.exe 快捷方式放置到数据目录中的 ...\Common\Files 文件夹之中。 如此这般,我们提供了一种便捷的方式来启动系统。 所有智能交易系统和 NNM 都会把文件写入该文件夹。 NNM 可从快捷方式所在的目录中搜索文件。
1. 快捷 EURUSD_MT5
分配一个工作文件夹。
2. 指定路径
接下来,我们有四个使用 NNM 的选项:
您也许会说上述几点的逻辑顺序会有所不同。 然而,最初创建神经网络模块是执行任务的出发点。 因此,我更喜欢这种实现。 第二、第三和第四选项超出了 NNM,因为它们可以在常规系统设置过程中执行。 在本文中,我将所有这些阶段合并为一个整体,从而为实际操作来更好地了解一般系统的准备过程。
我们来更详尽地研究这些选项。
1. 在模拟或真实账户里进行在线交易
正如我们所见,系统启动花费的时间不到五分钟。
在此之前,有必要配置 Excel。 配置的目的是确保来自脚本和智能交易系统的数据能以数字格式书写。 否则 Matlab 将错误地读取该数据。 在整数和小数部分之间设置一个点作为分隔符。 这可直接完成,也可利用系统分隔符完成。
3. Excel 参数
在首次启动 NNM 之前,请用智能交易系统创建历史记录下载文件。 在策略测试器里启动 ExpertMatlabPodkach_MT5.ex5。
4. 启动 ExpertMatlabPodkach_MT5.ex5
如您所见,应该选择 EA 启动时间,从而令其涵盖自操作开始前三天的时间。
结果则为,我们得到了 EURUSDTestPodkach.csv 文件。
5. 文件 EURUSDTestPodkach.csv
打开文件并编辑它,除了系统启动日之前最后一个小时的开盘时间,删除其余的所有行。
6. 删除不必要的行
现在,我们可以启动 ExpertMatlabReal_MT5.ex5。
#include<Trade\Trade.mqh> //--- An object for performing trading operations CTrade trade; input int LossBuy; input int ProfitBuy; input int LossSell; input int ProfitSell; int BarMax; int BarMin; int handleInput; int handleInputPodkach; int handleBar; int Con; int Bar; double DibMax; double DibMin; double in[32]; int Order01; int Order1; ulong TicketBuy1; ulong TicketSell0; bool send1; bool send0; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- handleInputPodkach=FileOpen("EURUSDTestPodkach.csv",FILE_READ|FILE_CSV|FILE_ANSI|FILE_COMMON,";"); if(handleInputPodkach==INVALID_HANDLE) Alert("No file EURUSDTestPodkach.csv"); in[0]=1/StringToDouble(FileReadString(handleInputPodkach))-1; in[1]=1/StringToDouble(FileReadString(handleInputPodkach))-1; in[2]=1/StringToDouble(FileReadString(handleInputPodkach))-1; in[3]=1/StringToDouble(FileReadString(handleInputPodkach))-1; in[4]=1/StringToDouble(FileReadString(handleInputPodkach))-1; in[5]=1/StringToDouble(FileReadString(handleInputPodkach))-1; in[6]=1/StringToDouble(FileReadString(handleInputPodkach))-1; in[7]=1/StringToDouble(FileReadString(handleInputPodkach))-1; in[8]=1/StringToDouble(FileReadString(handleInputPodkach))-1; in[9]=1/StringToDouble(FileReadString(handleInputPodkach))-1; in[10]=1/StringToDouble(FileReadString(handleInputPodkach))-1; in[11]=1/StringToDouble(FileReadString(handleInputPodkach))-1; in[12]=1/StringToDouble(FileReadString(handleInputPodkach))-1; in[13]=1/StringToDouble(FileReadString(handleInputPodkach))-1; in[14]=1/StringToDouble(FileReadString(handleInputPodkach))-1; in[15]=1/StringToDouble(FileReadString(handleInputPodkach))-1; in[16]=1/StringToDouble(FileReadString(handleInputPodkach))-1; in[17]=1/StringToDouble(FileReadString(handleInputPodkach))-1; in[18]=1/StringToDouble(FileReadString(handleInputPodkach))-1; in[19]=1/StringToDouble(FileReadString(handleInputPodkach))-1; in[20]=1/StringToDouble(FileReadString(handleInputPodkach))-1; in[21]=1/StringToDouble(FileReadString(handleInputPodkach))-1; in[22]=1/StringToDouble(FileReadString(handleInputPodkach))-1; in[23]=1/StringToDouble(FileReadString(handleInputPodkach))-1; in[24]=1/StringToDouble(FileReadString(handleInputPodkach))-1; in[25]=1/StringToDouble(FileReadString(handleInputPodkach))-1; in[26]=1/StringToDouble(FileReadString(handleInputPodkach))-1; in[27]=1/StringToDouble(FileReadString(handleInputPodkach))-1; in[28]=1/StringToDouble(FileReadString(handleInputPodkach))-1; in[29]=1/StringToDouble(FileReadString(handleInputPodkach))-1; FileClose(handleInputPodkach); //--- Setting MagicNumber to identify EA's orders int MagicNumber=123456; trade.SetExpertMagicNumber(MagicNumber); //--- Setting allowable slippage in points for buying/selling int deviation=10; trade.SetDeviationInPoints(deviation); //--- order filling mode, use the mode that is allowed by the server trade.SetTypeFilling(ORDER_FILLING_RETURN); //--- The function to be used for trading: true - OrderSendAsync(), false - OrderSend() trade.SetAsyncMode(true); //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- FileClose(handleInput); FileClose(handleBar); } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { MqlDateTime stm; TimeToStruct(TimeCurrent(),stm); if(stm.hour==1) DibMax=iHigh(NULL,PERIOD_H1,1); if(stm.hour>0) { if(iHigh(NULL,PERIOD_H1,1)>DibMax && iTime(NULL,PERIOD_H1,0)>1) { in[20]=iOpen(NULL,PERIOD_D1,0)-iLow(NULL,PERIOD_H1,1); in[21]=iHigh(NULL,PERIOD_H1,1)-iOpen(NULL,PERIOD_D1,0); in[22]=iHigh(NULL,PERIOD_D1,1)-iLow(NULL,PERIOD_D1,1); in[23]=iHigh(NULL,PERIOD_D1,1)-iOpen(NULL,PERIOD_H1,0); in[24]=iOpen(NULL,PERIOD_H1,0)-iLow(NULL,PERIOD_D1,1); } } if(iHigh(NULL,PERIOD_H1,1)>DibMax) DibMax=iHigh(NULL,PERIOD_H1,1); //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ if(stm.hour==1) DibMin=iLow(NULL,PERIOD_H1,1); if(stm.hour>0) { if(iLow(NULL,PERIOD_H1,1)<DibMin && iTime(NULL,PERIOD_H1,0)>1) { in[25]=iOpen(NULL,PERIOD_D1,0)-iLow(NULL,PERIOD_H1,1); in[26]=iHigh(NULL,PERIOD_H1,1)-iOpen(NULL,PERIOD_D1,0); in[27]=iHigh(NULL,PERIOD_D1,1)-iLow(NULL,PERIOD_D1,1); in[28]=iHigh(NULL,PERIOD_D1,1)-iOpen(NULL,PERIOD_H1,0); in[29]=iOpen(NULL,PERIOD_H1,0)-iLow(NULL,PERIOD_D1,1); } } if(iLow(NULL,PERIOD_H1,1)<DibMin) DibMin=iLow(NULL,PERIOD_H1,1); in[30]=iHigh(NULL,PERIOD_D1,1)-iOpen(NULL,PERIOD_H1,0); in[31]=iOpen(NULL,PERIOD_H1,0)-iLow(NULL,PERIOD_D1,1); if(Bar<Bars(NULL,PERIOD_H1)&& stm.hour==0) { for(int i=19; i>=10; i--) { in[i-10]=in[i]; } for(int i=29; i>=20; i--) { in[i-10]=in[i]; } } handleInput=FileOpen("Input_mat.txt",FILE_TXT|FILE_WRITE|FILE_ANSI|FILE_SHARE_READ|FILE_COMMON,";"); FileWrite(handleInput, 1/(in[0]+1),1/(in[1]+1),1/(in[2]+1),1/(in[3]+1),1/(in[4]+1),1/(in[5]+1),1/(in[6]+1),1/(in[7]+1),1/(in[8]+1),1/(in[9]+1),1/(in[10]+1),1/(in[11]+1),1/(in[12]+1),1/(in[13]+1),1/(in[14]+1),1/(in[15]+1), 1/(in[16]+1),1/(in[17]+1),1/(in[18]+1),1/(in[19]+1),1/(in[20]+1),1/(in[21]+1),1/(in[22]+1),1/(in[23]+1),1/(in[24]+1),1/(in[25]+1),1/(in[26]+1),1/(in[27]+1),1/(in[28]+1),1/(in[29]+1),1/(in[30]+1),1/(in[31]+1)); FileClose(handleInput); handleBar=FileOpen("Bar.txt",FILE_TXT|FILE_WRITE|FILE_ANSI|FILE_SHARE_READ|FILE_COMMON,";"); FileWrite(handleBar,stm.hour); FileClose(handleBar); Order01=FileOpen("Open1.txt",FILE_CSV|FILE_READ|FILE_ANSI|FILE_SHARE_READ|FILE_COMMON," "); Order1=StringToInteger(FileReadString(Order01)); FileClose(Order01); int digits=(int)SymbolInfoInteger(_Symbol,SYMBOL_DIGITS); double point=SymbolInfoDouble(_Symbol,SYMBOL_POINT); double PriceAsk=SymbolInfoDouble(_Symbol,SYMBOL_ASK); double PriceBid=SymbolInfoDouble(_Symbol,SYMBOL_BID); double SL1=NormalizeDouble(PriceBid-LossBuy*point,digits); double TP1=NormalizeDouble(PriceAsk+ProfitBuy*point,digits); double SL0=NormalizeDouble(PriceAsk+LossSell*point,digits); double TP0=NormalizeDouble(PriceBid-ProfitSell*point,digits); if(Bar<Bars(NULL,PERIOD_H1)) Con=0; Comment(Order1," ",Con); if(LossBuy==0) SL1=0; if(ProfitBuy==0) TP1=0; if(LossSell==0) SL0=0; if(ProfitSell==0) TP0=0; if(Order1==0 && Bar<Bars(NULL,PERIOD_H1) && Con==0) send0=true; if(Order1==1 && Bar<Bars(NULL,PERIOD_H1) && Con==0) send1=true; //---------Buy0 if(send1==false && Bar==Bars(NULL,PERIOD_H1) && Order1==1 && Con>=1 && iLow(NULL,PERIOD_H1,1)<iLow(NULL,PERIOD_H1,2) && stm.hour>15 && stm.hour<20) { send1=trade.PositionOpen(_Symbol,ORDER_TYPE_BUY,1,PriceAsk,SL1,TP1); TicketBuy1 = trade.ResultDeal(); } if(send1==true && Bar==Bars(NULL,PERIOD_H1) && Order1==0 && Con>=1 && iHigh(NULL,PERIOD_H1,1)>iHigh(NULL,PERIOD_H1,2)) { trade.PositionClose(TicketBuy1); send1=false; } //---------Sell0 if(send0==false && Bar==Bars(NULL,PERIOD_H1) && Order1==0 && Con>=1 && iHigh(NULL,PERIOD_H1,1)>iHigh(NULL,PERIOD_H1,2) && stm.hour> 11 && stm.hour<14) { send0=trade.PositionOpen(_Symbol,ORDER_TYPE_SELL,1,PriceBid,SL0,TP0); TicketSell0 = trade.ResultDeal(); } if(send0==true && Bar==Bars(NULL,PERIOD_H1) && Order1==1 && Con>=1 && iLow(NULL,PERIOD_H1,1)<iLow(NULL,PERIOD_H1,2)) { trade.PositionClose(TicketSell0); send0=false; } //----------------------------------------------------------------------- Bar=Bars(NULL,PERIOD_H1); Con++; } //------------------------------------------------------------------------
至于在 EA 中为交易而设计的时间限制,我更喜欢将它们直接添加到代码中,在优化之后。 我这样做是为了最大程度地减少外部变量可能引发错误的风险。
7. 启动 ExpertMatlabReal_MT5.ex5
至于为优化而设计的智能交易系统,应将其作为外部变量。
7.1 Matlab_MT5.ex5
如果您忘记生成历史记录下载文件,则会收到警告,从而防止 EA 生成假信号。
8. 警告
EA 会将两个文件写入 ...\Common\Files 文件夹。 Input_mat.txt 文件包含 NNM 的输入数据。
9. 智能交易系统响应
启动神经网络模块 EURUSD_MT5.exe。 参阅工作空间的显示。
10. NSM 工作空间
该程序 Net2 和 Net3 模块的可修改参数不能更改。
按下 "Start" 并选择 net1.m 文件 (或是其它神经网络文件)。
11. 按下 "Start"
您也学已注意到,自第一篇文章以来,NNM 的外观已经发生了一些变化。 然而,其功能保持不变。
神经网络模块将开始操作。
12. NNM 处于交易状态
在 NNM 操作期间,我们无法更改其任何参数。
图表左上角显示了智能交易系统从 NNM 接收的数字:1.0,-1.0 和 0.0。 这意味着 EA 已收到买入(卖出)的信号。 来自 Net2 — 远离市场。 来自 Net3 — 买入(卖出)信号。 由于该程序版本仅用于评估目的,因此我们不会从 Net1 和 Net2 接收可变信号。
在此示例中,我们的系统使用 5 小时平滑 NNM 响应信号线。 为了避免收到假的初始信号,必须根据开仓条件在交易时间前五个小时启动模块。 时间取决于平滑参数。
请注意,用文件记录下载的历史记录,数据交换则通过磁盘,并等待所需的双向控制数据传入。 在 NNM 窗口、图表和文件 Open1,2,3 中的数据必须相同。
13. 信息控制
该示意图展示的是神经网络模块的一种变体,它仅将来自 net1、net2 和 net3 的响应传递给 EA。 在智能交易系统中指定开仓条件。 这是我的首选方式。 在我们的情况下,NNM 提供就绪信号,而 EA 交易仅受时间限制。
在调试系统时,这种控制特别有用。 另外,有必要在测试和交易期间直观地控制性能。
14. NNM 变体
上图展示的是神经网络模块的另一个变体。 在该变体中,最好将神经网络直接集成到可执行文件中。 单击绿色按钮时,简单地选择 Input_mat.txt 文件。 我建议用类似的模型进行操作。 该变体只有一个交易模块,没有训练和测试模块。 不要忘记,系统准备阶段看似复杂,却为交易提供了最大的简便性。 主要的市场分析是在 NNM 中进行的 - 由于使用了神经网络,因此这一步骤是即时的。 如果您不用其他优化条件,则交易机器人仅需要解释两个接收到的数字。
if(send1==false && Order1==1) { send1=trade.Buy(1); TicketBuy1 = trade.ResultDeal(); } if(send1==true && Order1==0) { trade.PositionClose(TicketBuy1); send1=false; }
总之,在 MetaTrader 4 终端里启动 ExpertMatlabReal_MT4.ex4 操作时,我想谈一些区别。 这与策略测试器的特定功能有关,即与测试中确定它们的控制点方式有关。 在 MetaTrader 5 中,测试会结束于前一天的最后一根柱线。 而在 MetaTrader 4 里则是直至当前柱线。 因此,针对 MetaTrader 4 我在 EA 中引入了 “Hours” 外部变量。
14.1 外部变量 "Hours"
使用 ExpertMatlabPodkach_MT4.ex4,我们生成了含有当前柱线的历史记录下载文件,这就是为什么在首次启动时应在 “Hours” 变量中指定当前时间柱线的原因。 YA 启动后,打开其属性,并将变量设置回 0。 这样就可确保在 00:00 执行进一步的数据换位。
14.2 Hours -0
2. 在可视模式里测试 NNM
测试的准备工作既已完成了。 因此,根据准备,神经网络训练,接收到的响应数组,可视化的响应,以及进一步的优化,神经网络模块将为我们提供期望的结果。 现在,我们从第 1 点开始重复操作,即像进行交易一样启动 NNM。 还有,在策略测试器中启动 ExpertMatlabReal_MT5.ex5,使用可视化模式。 在 MetaTrader 5 中启用 “1 分钟 OHLC” 模式。 在MetaTrader 4中,启用“控制点”。 应该使用这些模型来获得可靠的测试结果,因为使用 NNM 时,成交将随柱线开盘后的即时报价时执行。 如果我们用的是“仅开盘价”模型,则在测试期间将延迟一根柱线开仓。 在这种模式下,可考虑用 EURUSD_MT5var.exe 模块来轻松响应延迟。 当然,在实盘交易中不会发生这种情况。 一般来说,所有类型的测试,该系统显示的结果几乎相同。 这是对其可行性的又一次确认。
15. 可视化模式
3. 神经网络训练
在所提供版本中,该神经网络模块模式用于演示神经网络训练过程,并为实际操作做准备。 这是一个演示模型,由于这些神经网络不可重新训练,因为该模块会用到已训练的神经网络。 无需在您的工作程序中添加这样的子模块,因为训练是在离线模式下进行的,而最有效和可靠的方式应是直接在 MATLAB 环境中训练神经网络。
为了测试 Net1 子模块的两个神经网络,我们需要在 ...\Common\Files 目录中含有神经网络 net1-2,及含有训练数据的 EURUSDData.csv 文件。
16. 数据文件
训练用数据是真实的 - 这些数据是为 Online 模块所用交易系统准备的。 我应当再次提醒运用神经网络评估行情状况的优势。 EURUSDData.csv 表格含有 90 列,它们按顺序值呈现,这些顺序值用作神经网络的输入数据。 简单地把这些作为输入。 想象一下,每列都是一个单独的指标,该指标由智能交易系统程序进行初步计算,然后提炼成训练神经网络的数据。 在准备系统时,所有这些操作都是在离线模式下完成的。 现在想象一下,我们将尝试于交易过程中在操作时的智能交易系统里直接分析这些令人印象深刻的数据集。
在撰写本文时,我重命名了按钮名称,从而能更好地理解操作顺序。
17. 更新的按钮名称
我们按 “Train net1”,打开 net1.mat 文件,并为 Net1 模块训练两个网络 net1 和 net2。 依此类推:Net2 的 net3,4,和 Net3 的 net5,6。
18. Train net1
再次,最方便的方法是将程序模块快捷方式放在工作文件所在的文件夹中,并在其属性中将路径更改为“工作文件夹”。 接下来,NNM 将训练网络。
4. 从神经网络模块接收响应,以便优化交易策略
在神经网络模块当中,我添加了在离线模式下测试神经网络的功能。 换句话说,它能够接收基于历史数据的神经网络响应,努力创建一个信号指标,来优化系统。 本示例从 Net1 接收响应。
首先,在 ...\AppData\Roaming\MetaQuotes\Terminal\Common\Files 文件夹里准备好 EURUSDTestPodkach_MT5.csv 文件。 为此,请在策略测试器中启动 ExpertMatlabPodkach_MT5 EA。 这应该从测试开始日期之前的日期开始(请确保已下载所需的历史记录数量)。
举例:
19. 启动 ExpertMatlabPodkach_MT5.ex5
结果文件中仅保留这一行,删除之外的所有行。
20. 只留一行
为了生成包含测试期间数据的文件,请在策略测试器中启动 ExpertMatlab_MT5.ex5。
21. 启动 ExpertMatlab_MT5.ex5
请注意如何选择开始日期。
22. 我们得到 EURUSDTest.csv 和 EURUSDDate.csv
ExpertMatlab_MT5.ex5 将生成两个测试文件 EURUSDDate.csv 和 EURUSDTest.csv。 启动神经网络模块,单击 “Test net1”,然后选择 net1.mat。
23. 按下 "Test net1"
等待一段时间,直到生成 Indicator1.csv 响应文件。
24. 响应 Indicator1.csv 文件已生成
将 Indicator1 保存为指标。 如果神经网络模块已将 “Indicator1.csv” 保存为“兼容模式”,则应通过“另存为”选项卡将其保存为 csv 格式。
25. 将 Indicator1.csv 另存为 Indicator.csv
我们在 EURUSD H1 图表上以可视模式检查 NNM 响应。 为此,要用到 NWI.ex5 指标。
26. 启动 NWI.ex5 指标
默认周期值是 5。 根据我的实验,该周期值的信号线为 EURUSD H1 提供了最佳结果。不过,您可以尝试查找自己的值。
27. NWI 可视化
利用 Matlab_MT5.ex5,我们可以测试并进一步优化收到的响应。
#include<Trade\Trade.mqh> CTrade trade; input int Period=5; input int H1; input int H2; input int H3; input int H4; input int LossBuy; input int ProfitBuy; input int LossSell; input int ProfitSell; ulong TicketBuy1; ulong TicketSell0; datetime Count; double Per; double Buf_0[]; double Buf_1[]; bool send1; bool send0; int h=4; int k; int K; int bars; int Handle; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Handle=FileOpen("Indicator.csv",FILE_CSV|FILE_SHARE_READ|FILE_ANSI|FILE_COMMON,";"); while(!FileIsEnding(Handle)&& !IsStopped()) { StringToTime(FileReadString(Handle)); bars++; } FileClose(Handle); ArrayResize(Buf_0,bars); ArrayResize(Buf_1,bars); Handle=FileOpen("Indicator.csv",FILE_CSV|FILE_SHARE_READ|FILE_ANSI|FILE_COMMON,";"); while(!FileIsEnding(Handle)&& !IsStopped()) { Count=StringToTime(FileReadString(Handle)); Buf_0[k]=StringToDouble(FileReadString(Handle)); h=Период-1; if(k>=h) { while(h>=0) { Buf_1[k]=Buf_1[k]+Buf_0[k-h]; h--; } Buf_1[k]=Buf_1[k]/Period; } k++; } FileClose(Handle); int deviation=10; trade.SetDeviationInPoints(deviation); trade.SetTypeFilling(ORDER_FILLING_RETURN); trade.SetAsyncMode(true); //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- MqlDateTime stm; TimeToStruct(TimeCurrent(),stm); int digits=(int)SymbolInfoInteger(_Symbol,SYMBOL_DIGITS); double point=SymbolInfoDouble(_Symbol,SYMBOL_POINT); double PriceAsk=SymbolInfoDouble(_Symbol,SYMBOL_ASK); double PriceBid=SymbolInfoDouble(_Symbol,SYMBOL_BID); double SL1=NormalizeDouble(PriceBid-LossBuy*point,digits); double TP1=NormalizeDouble(PriceAsk+ProfitBuy*point,digits); double SL0=NormalizeDouble(PriceAsk+LossSell*point,digits); double TP0=NormalizeDouble(PriceBid-ProfitSell*point,digits); if(LossBuy==0) SL1=0; if(ProfitBuy==0) TP1=0; if(LossSell==0) SL0=0; if(ProfitSell==0) TP0=0; //---------Buy1 if(send1==false && K>0 && Buf_0[K-1]<Buf_1[K-1] && Buf_0[K]>Buf_1[K] && iLow(NULL,PERIOD_H1,1)<iLow(NULL,PERIOD_H1,2) && stm.hour>H1 && stm.hour<H2 && H1<H2) { send1=trade.PositionOpen(_Symbol,ORDER_TYPE_BUY,1,PriceAsk,SL1,TP1); TicketBuy1 = trade.ResultDeal(); } if(send1==true && K>0 && Buf_0[K-1]>Buf_1[K-1] && Buf_0[K]<Buf_1[K] && iHigh(NULL,PERIOD_H1,1)>iHigh(NULL,PERIOD_H1,2)) { trade.PositionClose(TicketBuy1); send1=false; } //---------Sell0 if(send0==false && K>0 && Buf_0[K-1]>Buf_1[K-1] && Buf_0[K]<Buf_1[K] && iHigh(NULL,PERIOD_H1,1)>iHigh(NULL,PERIOD_H1,2) && stm.hour>H3 && stm.hour<H4 && H3<H4) { send0=trade.PositionOpen(_Symbol,ORDER_TYPE_SELL,1,PriceBid,SL0,TP0); TicketSell0 = trade.ResultDeal(); } if(send0==true && K>0 && Buf_0[K-1]<Buf_1[K-1] && Buf_0[K]>Buf_1[K] && iLow(NULL,PERIOD_H1,1)<iLow(NULL,PERIOD_H1,2)) { trade.PositionClose(TicketSell0); send0=false; } K++; } //+------------------------------------------------------------------+ //| Tester function | //+------------------------------------------------------------------+ double OnTester() { //--- double ret=0.0; //--- //--- return(ret); } //+------------------------------------------------------------------+
28. 启动 Matlab_MT5.ex5
29. 余额
没必要进行正向验证测试。 复盘测试之后,正向验证测试将从下一个日期开始,但是该文件会加载复盘测试的起始日期。 因此,结果将不正确。
我们来运行优化。 之前的测试没有限制级别,并且根据书中所述的系统准备原则进行。 而在优化时已引入更多级别。
30. 运行优化
优化区间为红线之前的 2011-2013 年。 之后,测试区间使用 2013-2016 年。 当然,在实践中,没有人会在优化后进行如此长久的交易,因为神经网络必须不时地重新训练。 根据我的实践,应该每个月进行重新测试。 在图表上,红线和蓝线之间的区间为 4 个月。
我们在这段时间分别测试一下。
31. 参考测试
我之前不曾进行过这些测试,因此所有这些操作都是在撰写本文这一部分的过程中完成的。 当然也可以使用其他选项。 并且还需要准备对冲型神经网络。 我只想强调神经网络在很大程度上简化了系统的准备操作和交易过程。
我们可以说,在此测试期间模拟的交易伴随理想条件。 因此,我们会把测试结果作为参考。
5. 纠正错误
在处理这一部分时,我决定返回开头,并解释为什么这部分如此之长。 事实是,在撰写文章的这部分时,“纠正错误”是实时执行的。 这也与上一章节有关。 针对 MetaTrader 4 一切都已做好准备,但对于 MetaTrader 5,我必须以实时模式工作。 对我来说,这是不可多得的经验。 无论如何,请注意准备步骤非常重要,因此您应该非常小心。
有必要返回到第 2 点,并在可视化模式下测试结果(我们称为参考)。
这就是我们所拥有的。
32. 失败测试
尽管结果是积极的,但它与上一个完全不同。 这意味着我们需要查找程序代码中的错误。 有用的功能是我们可以跟踪所有数据传输步骤。
我们拿 Net1 子模块输出字段(4)中的响应,和使用 ExpertMtatlab_MT5.ex5 EA 接收到的 NNM 响应进行比较。 使用相同的柱线。
33. 比较响应
如您所见,Net1 子模块的响应和 NNM 响应不匹配。 由于我们使用相同的神经网络模块,因此可以得出结论,在这两个测试期间输入的数据是不同的。
任意选择柱线,比较文件 EURUSDTest.csv 和 Input_mat.txt 中的数据。
34. 比较数据是否匹配
我们是对的,数据确实不同。 因此,我们检查 ExpertMtatlab_MT5.ex5 和 ExpertMatlabReal_MT5.ex5 的程序代码。
ExpertMtatlab_MT5.ex5 if(stm.hour==0) { for(int i=19; i>=10; i--) { in[i-10]=in[i]; } for(int i=29; i>=20; i--) { in[i-10]=in[i]; } } ExpertMatlabReal_MT5.ex5 if(Bar<Bars(NULL,PERIOD_H1) && stm.hour==0) { for(int i=19; i>=10; i--) { in[i-10]=in[i]; } for(int i=29; i>=20; i--) { in[i-10]=in[i]; } }
在 ExpertMatlabReal_MT5.ex5 代码中发现了错误。 该文件未包含将数据偏移至零时的主要条件。 在实际操作中,我们的 NNM 从终端接收的信息,与网络应当接收的信息不对应。 这就会产生“假响应”。 我之所以加上引号,是因为实际上神经网络会产生正常的响应,但它对我们来说是假的,因为我们给网络提供的信息就是错误的。
再次,我们拿 Net1 子模块的输出字段(4)中的响应,和使用 ExpertMtatlab_MT5.ex5 EA 收到的 NNM 响应进行比较。
35. 比较响应
现在我们看到输入到 NNM 的信息是正确的。
修复错误后,再次以可视模式进行测试。
36. 测试与参考不同
尽管结果接近,但仍然有很大的不同。 甚至,神经网络模块自行终止了。
经检查,在 ExpertMatlabReal_MT5.mq5 中的空头平仓条件中发现错误。 在 1 分钟 OHLC 可视化模式下再次测试。
37. 不等待即时报价进行测试
结果与参考值不匹配。
38. 参考
经过分析,我们可得出结论,该模拟交易的结果是在不利条件下进行。 这导致了大量假信号和不必要的交易。 当前根柱线出现开仓信号,而下一根柱线上出现开立逆反交易,或离场的信号时,就会发生这种情况。 由于测试是以加速模式执行的,而来自 NNM 的信息可能会延迟,因此 EA 可能根据先前的信号执行交易。 但在这种情况下,这种测试很有用,因为它令我们有机会了解系统在不利的市场条件下的表现。
我们来看看针对上述情况在 EA 里添加开仓限制后的结果。
if(Order1==0 && Bar<Bars(NULL,PERIOD_H1) && Con==0) send0=true; if(Order1==1 && Bar<Bars(NULL,PERIOD_H1) && Con==0) send1=true; //---------Buy
该测试以最高的可视化速度,按即时报价模式进行。 您可在视频中观看该过程。
39. 结果与参考接近
如您所见,结果与参考接近。 虽然,我认为如果我们降低测试速度,以前的测试也会与参考接近。 您可以尝试自行执行此操作。
现在,我们来找找 NNM 为何终止。 我可以跳过这一部分。 但我们在测试模式下发现了此错误是件好事。 如果在实盘交易中才发现问题,情况会更糟。 如果 NNM 中的神经网络未经训练,或者若我们在交易 EA 启动之前启动了模块,那么届时将不会生成 Input_mat.txt 文件,并且在单击“开始”后 NNM 将不会提供响应。 最初,我添加了警告窗口,并从内部计时器强制退出。 我删除了此逻辑错误,但留下了警告。 然而,如果在操作过程中弹出窗口,则只需单击“确定”即可隐藏该窗口。 该程序将继续运行。
40. 错误窗口
您还应该注意另一个问题。 在 MetaTrader 5 中,如果我们用策略数据编写数据,则生成的柱线数量大于图表上显示的数量。 因此,结果指标有些“超长”。 不过,这不会影响测试质量。 测试当中会取用额外的柱线,但不会显示在图表上。 这可分步检查。
41. MetaTrader 5 中的 NWI 指标
42. 还有一个时移
MetaTrader 4 中不存在此问题。
43. MetaTrader 4 中的指标 1_MT4
44. 无偏移
下图显示了为什么会发生这种情况。
45. 寻找偏移的原因
使用神经网络的目的是什么?
在撰写本文时,我最初只是想将 Net1 神经网络集成到 NNM 当中,而 Net1 神经网络已在文章发布时进行了训练。 然而,完成之后,我决定将所集成神经网络 Net2 训练到 2019 年 12 月 31 日。 以及训练 Net3 至 2010 年 12 月 31 日的数据。 在当前行情条件下测试它们很有趣。 对于 NNM 内的所有其他操作,仅用到 Net1 的神经网络。
至于视频的长度,我深表歉意,整个过程几乎在一帧内就完成了。
本社区仅针对特定人员开放
查看需注册登录并通过风险意识测评
5秒后跳转登录页面...
移动端课程