简介
在寻找或者开发交易系统的过程中,很多交易者肯定听说过由Alexander Elder博士提出的三重滤网策略。在互联网上很多人认为这个策略不佳,然而,也有很多人认为它可以帮人获利,您不一定要相信任何一种观点,每件事都应该做亲手验证,如果您学习编程,您就可以使用回溯测试来亲手检验这个交易策略的效果了。
在本文中,我们将基于三重滤网(Triple Screen)策略,使用MQL5开发一个交易系统的框架。EA交易不会从头开始开发,我们会简单地修改前一篇文章,即“MQL5 Cookbook: 在EA交易中使用指标设置交易条件”中的程序。所以这篇文章也会向您展示如何简单地修改已经完成的程序的模式。
前文中的EA交易已经有了很多功能,它可以启用/禁用止损/获利和跟踪止损,仓位交易量增加以及在相反信号下做仓位反转,所有所需的函数都已经做好了。所以我们的任务就集中在修改外部参数列表,增加额外的选项以及修改一些已有函数上。
为了说明我们的目标, 我们将使用移动平均指标在三个时间范围上产生信号,晚些时候,当我们继续在开发框架上做实验时,您将只要少量修改代码即可使用任何其他指标。我们也会实现在每个滤网上设置时间范围,如果指标周期参数为0, 则说明不使用对应的滤网,换句话说,系统可以设置为只有一个或两个时间范围。
在您开始之前,请把来自前文的EA交易文件所在文件夹复制一份并重新命名。
EA 交易开发
让我们从外部参数开始,以下是更新过的列表的代码,新增行已经标出,时间范围声明为ENUM_TIMEFRAMES枚举类型,您将可以从下拉列表中选取任何时间范围。
//--- EA交易的外部参数 sinput long MagicNumber=777; // 幻数 sinput int Deviation=10; // 滑点 //--- input ENUM_TIMEFRAMES Screen01TimeFrame=PERIOD_W1; // 第一个滤网的时间范围 input int Screen01IndicatorPeriod=14; // 第一个滤网的指标周期数 //--- input ENUM_TIMEFRAMES Screen02TimeFrame=PERIOD_D1; // 第二滤网的时间范围 input int Screen02IndicatorPeriod=24; // 第二滤网的指标周期数 //--- input ENUM_TIMEFRAMES Screen03TimeFrame=PERIOD_H4; // 第三滤网的时间范围 input int Screen03IndicatorPeriod=44; // 第三滤网的指标周期数 //--- input double Lot=0.1; // 手数 input double VolumeIncrease=0.1; // 仓位交易量增加量 input double VolumeIncreaseStep=10; // 仓位交易量增加步长 input double StopLoss=50; // 止损 input double TakeProfit=100; // 获利 input double TrailingStop=10; // 跟踪止损 input bool Reverse=true; // 仓位反转 sinput bool ShowInfoPanel=true; // 显示信息面板
为了使例子更加简单,IndicatorSegments 参数,AllowedNumberOfSegments变量以及CorrectInputParameters()函数已经被删除。如果您还对此条件有兴趣,您可以自己实现它。您也应该在Enums.mqh文件中删除指标的枚举,因为这个EA交易只使用一个指标。
因为每个时间范围会有一个独立的指标,我们将需要一个独立的变量来处理每一个指标:
//--- 指标句柄 int Screen01IndicatorHandle=INVALID_HANDLE; // 第一滤网的指标句柄 int Screen02IndicatorHandle=INVALID_HANDLE; // 第二滤网的指标句柄 int Screen03IndicatorHandle=INVALID_HANDLE; // 第三滤网的指标句柄
新柱将会使用最小时间范围来检查。当在外部参数中设置最小时间范围时,我们不一定要按照特别顺序,比如最大,中等,最小。相反的顺序或者任何顺序都可以。所以我们需要一个函数来从所有指定的时间范围中找到最小时间范围,
因为EA交易可以设为在三个时间范围上运行,一个或者两个也可以,在确定最小时间范围方面所有选项都要考虑到,以下是GetMinimumTimeframe()函数的代码:
//+------------------------------------------------------------------+ //| 为检查新柱确定最小时间范围 | //+------------------------------------------------------------------+ ENUM_TIMEFRAMES GetMinimumTimeframe(ENUM_TIMEFRAMES timeframe1,int period1, ENUM_TIMEFRAMES timeframe2,int period2, ENUM_TIMEFRAMES timeframe3,int period3) { //--- 默认最小时间范围 ENUM_TIMEFRAMES timeframe_min=PERIOD_CURRENT; //--- 把时间范围值转换为秒用于计算 int t1= PeriodSeconds(timeframe1); int t2= PeriodSeconds(timeframe2); int t3= PeriodSeconds(timeframe3); //--- 检查不正确的周期值 if(period1<=0 && period2<=0 && period3<=0) return(timeframe_min); //--- 只有一个时间范围的条件 if(period1>0 && period2<=0 && period3<=0) return(timeframe1); if(period2>0 && period1<=0 && period3<=0) return(timeframe2); if(period3>0 && period1<=0 && period2<=0) return(timeframe3); //--- 只有两个时间范围的条件 if(period1>0 && period2>0 && period3<=0) { timeframe_min=(MathMin(t1,t2)==t1) ? timeframe1 : timeframe2; return(timeframe_min); } if(period1>0 && period3>0 && period2<=0) { timeframe_min=(MathMin(t1,t3)==t1) ? timeframe1 : timeframe3; return(timeframe_min); } if(period2>0 && period3>0 && period1<=0) { timeframe_min=(MathMin(t2,t3)==t2) ? timeframe2 : timeframe3; return(timeframe_min); } //--- 三个时间范围的条件 if(period1>0 && period2>0 && period3>0) { timeframe_min=(int)MathMin(t1,t2)==t1 ? timeframe1 : timeframe2; int t_min=PeriodSeconds(timeframe_min); timeframe_min=(int)MathMin(t_min,t3)==t_min ? timeframe_min : timeframe3; return(timeframe_min); } return(WRONG_VALUE); }
为了保存最小时间范围值,我们需要创建另外一个全局范围变量:
//--- 用于决定最小时间范围的变量 ENUM_TIMEFRAMES MinimumTimeframe=WRONG_VALUE;
GetMinimumTimeframe()将需要在EA交易初始化时OnInit()函数中调用。
//+------------------------------------------------------------------+ //| EA初始化函数 | //+------------------------------------------------------------------+ int OnInit() { //--- 为检查新柱确定最小时间范围 MinimumTimeframe=GetMinimumTimeframe(Screen01TimeFrame,Screen01IndicatorPeriod, Screen02TimeFrame,Screen02IndicatorPeriod, Screen03TimeFrame,Screen03IndicatorPeriod); //--- 取得指标句柄 GetIndicatorHandles(); //--- 初始化新柱 CheckNewBar(); //--- 取得属性 GetPositionProperties(P_ALL); //--- 设置信息面板 SetInfoPanel(); //--- return(0); }
MinimumTimeframe 变量值随后会在CheckNewBar()和GetBarsData()函数中使用。
GetIndicatorHandle()函数现在看起来如下,每个指标有指定的周期数和时间范围。
//+------------------------------------------------------------------+ //| 取得指标句柄 | //+------------------------------------------------------------------+ void GetIndicatorHandles() { //--- 取得参数指定的指标的句柄 if(Screen01IndicatorPeriod>0) Screen01IndicatorHandle=iMA(_Symbol,Screen01TimeFrame,Screen01IndicatorPeriod,0,MODE_SMA,PRICE_CLOSE); if(Screen02IndicatorPeriod>0) Screen02IndicatorHandle=iMA(_Symbol,Screen02TimeFrame,Screen02IndicatorPeriod,0,MODE_SMA,PRICE_CLOSE); if(Screen03IndicatorPeriod>0) Screen03IndicatorHandle=iMA(_Symbol,Screen03TimeFrame,Screen03IndicatorPeriod,0,MODE_SMA,PRICE_CLOSE); //--- 如果无法获得第一个时间范围的指标句柄 if(Screen01IndicatorHandle==INVALID_HANDLE) Print("获取滤网1指标句柄失败!"); //--- 如果无法获得第二个时间范围的指标句柄 if(Screen01IndicatorHandle==INVALID_HANDLE) Print("获取滤网2指标句柄失败!"); //--- 如果无法获得第三个时间范围的指标句柄 if(Screen01IndicatorHandle==INVALID_HANDLE) Print("获取滤网3指标句柄失败!"); }
下一步,我们需要增加用于取得指标值(每个时间范围分开)的数组:
//--- 用于指标值的数组 double indicator_buffer1[]; double indicator_buffer2[]; double indicator_buffer3[];
GetIndicatorsData() 函数用于取得指标值,现在看起来如下,获得的句柄需要检查正确性,如果没有问题,数组将使用指标值填充。
//+------------------------------------------------------------------+ //| 取得指标值 | //+------------------------------------------------------------------+ bool GetIndicatorsData() { //--- 为判断交易信号的指标缓冲区数值的数量 int NumberOfValues=3; //--- 如果没有获得指标句柄 if((Screen01IndicatorPeriod>0 && Screen01IndicatorHandle==INVALID_HANDLE) || (Screen02IndicatorPeriod>0 && Screen02IndicatorHandle==INVALID_HANDLE) || (Screen03IndicatorPeriod>0 && Screen03IndicatorHandle==INVALID_HANDLE)) //--- 尝试重新获取 GetIndicatorHandles(); //--- 如果使用了第一滤网时间范围并且获得了指标句柄 if(Screen01TimeFrame>0 && Screen01IndicatorHandle!=INVALID_HANDLE) { //--- 反转索引顺序 (... 3 2 1 0) ArraySetAsSeries(indicator_buffer1,true); //--- 取得指标值 if(CopyBuffer(Screen01IndicatorHandle,0,0,NumberOfValues,indicator_buffer1)<NumberOfValues) { Print("复制数值失败 ("+ _Symbol+"; "+TimeframeToString(Period())+") (至 indicator_buffer1 数组)错误 ("+ IntegerToString(GetLastError())+"): "+ErrorDescription(GetLastError())); //--- return(false); } } //--- 如果使用了第二滤网的时间范围并且获得了指标句柄 if(Screen02TimeFrame>0 && Screen02IndicatorHandle!=INVALID_HANDLE) { //--- 反转索引顺序 (... 3 2 1 0) ArraySetAsSeries(indicator_buffer2,true); //--- 取得指标值 if(CopyBuffer(Screen02IndicatorHandle,0,0,NumberOfValues,indicator_buffer2)<NumberOfValues) { Print("复制数值失败 ("+ _Symbol+"; "+TimeframeToString(Period())+") (至indicator_buffer2数组)错误 ("+ IntegerToString(GetLastError())+"): "+ErrorDescription(GetLastError())); //--- return(false); } } //--- 如果使用了第三滤网的时间范围并且获得了指标句柄 if(Screen03TimeFrame>0 && Screen03IndicatorHandle!=INVALID_HANDLE) { //--- 反转索引顺序 (... 3 2 1 0) ArraySetAsSeries(indicator_buffer3,true); //--- 取得指标值 if(CopyBuffer(Screen03IndicatorHandle,0,0,NumberOfValues,indicator_buffer3)<NumberOfValues) { Print("复制数值失败 ("+ _Symbol+"; "+TimeframeToString(Period())+") (至indicator_buffer3数组)错误 ("+ IntegerToString(GetLastError())+"): "+ErrorDescription(GetLastError())); //--- return(false); } } //--- return(true); }
GetTradingSignal()和GetSignal()函数也应该随之修改,以下是这些函数的代码,便于您的参考.
//+------------------------------------------------------------------+ //| 判断交易信号 | //+------------------------------------------------------------------+ ENUM_ORDER_TYPE GetTradingSignal() { //--- 如果没有持仓 if(!pos.exists) { //--- 卖出信号 if(GetSignal()==ORDER_TYPE_SELL) return(ORDER_TYPE_SELL); //--- 买入信号 if(GetSignal()==ORDER_TYPE_BUY) return(ORDER_TYPE_BUY); } //--- 如果有持仓 if(pos.exists) { //--- 读取仓位类型 GetPositionProperties(P_TYPE); //--- 取得最后一笔交易的价格 GetPositionProperties(P_PRICE_LAST_DEAL); //--- 卖出信号 if(pos.type==POSITION_TYPE_BUY && GetSignal()==ORDER_TYPE_SELL) return(ORDER_TYPE_SELL); if(pos.type==POSITION_TYPE_SELL && GetSignal()==ORDER_TYPE_SELL && close_price[1]<pos.last_deal_price-CorrectValueBySymbolDigits(VolumeIncreaseStep*_Point)) return(ORDER_TYPE_SELL); //--- 买入信号 if(pos.type==POSITION_TYPE_SELL && GetSignal()==ORDER_TYPE_BUY) return(ORDER_TYPE_BUY); if(pos.type==POSITION_TYPE_BUY && GetSignal()==ORDER_TYPE_BUY && close_price[1]>pos.last_deal_price+CorrectValueBySymbolDigits(VolumeIncreaseStep*_Point)) return(ORDER_TYPE_BUY); } //--- 没有信号 return(WRONG_VALUE); }
GetSignal()函数和确定最小时间范围的函数一样,会考虑到外部参数的状态与建仓条件相关的所有可能变数。此函数的代码如下:
//+------------------------------------------------------------------+ //| 检查条件并返回信号 | //+------------------------------------------------------------------+ ENUM_ORDER_TYPE GetSignal() { //--- 卖出信号: 指标在已完成柱的当前值比前一柱低 //--- 只有一个时间范围的条件 if(Screen01IndicatorPeriod>0 && Screen02IndicatorPeriod<=0 && Screen03IndicatorPeriod<=0) { if(indicator_buffer1[1]<indicator_buffer1[2]) return(ORDER_TYPE_SELL); } if(Screen01IndicatorPeriod<=0 && Screen02IndicatorPeriod>0 && Screen03IndicatorPeriod<=0) { if(indicator_buffer2[1]<indicator_buffer2[2]) return(ORDER_TYPE_SELL); } //--- if(Screen01IndicatorPeriod<=0 && Screen02IndicatorPeriod<=0 && Screen03IndicatorPeriod>0) { if(indicator_buffer3[1]<indicator_buffer3[2]) return(ORDER_TYPE_SELL); } //--- 只有两个时间范围的条件 if(Screen01IndicatorPeriod>0 && Screen02IndicatorPeriod>0 && Screen03IndicatorPeriod<=0) { if(indicator_buffer1[1]<indicator_buffer1[2] && indicator_buffer2[1]<indicator_buffer2[2]) return(ORDER_TYPE_SELL); } if(Screen01IndicatorPeriod<=0 && Screen02IndicatorPeriod>0 && Screen03IndicatorPeriod>0) { if(indicator_buffer2[1]<indicator_buffer2[2] && indicator_buffer3[1]<indicator_buffer3[2]) return(ORDER_TYPE_SELL); } if(Screen01IndicatorPeriod>0 && Screen02IndicatorPeriod<=0 && Screen03IndicatorPeriod>0) { if(indicator_buffer1[1]<indicator_buffer1[2] && indicator_buffer3[1]<indicator_buffer3[2]) return(ORDER_TYPE_SELL); } //--- 三个时间范围的条件 if(Screen01IndicatorPeriod>0 && Screen02IndicatorPeriod>0 && Screen03IndicatorPeriod>0) { if(indicator_buffer1[1]<indicator_buffer1[2] && indicator_buffer2[1]<indicator_buffer2[2] && indicator_buffer3[1]<indicator_buffer3[2] ) return(ORDER_TYPE_SELL); } //--- 买入信号: 在已完成柱上的指标当前值比前一柱指标值高 //--- 只有一个时间范围的条件 if(Screen01IndicatorPeriod>0 && Screen02IndicatorPeriod<=0 && Screen03IndicatorPeriod<=0) { if(indicator_buffer1[1]>indicator_buffer1[2]) return(ORDER_TYPE_BUY); } if(Screen01IndicatorPeriod<=0 && Screen02IndicatorPeriod>0 && Screen03IndicatorPeriod<=0) { if(indicator_buffer2[1]>indicator_buffer2[2]) return(ORDER_TYPE_BUY); } if(Screen01IndicatorPeriod<=0 && Screen02IndicatorPeriod<=0 && Screen03IndicatorPeriod>0) { if(indicator_buffer3[1]>indicator_buffer3[2]) return(ORDER_TYPE_BUY); } //--- 只有两个时间范围的条件 if(Screen01IndicatorPeriod>0 && Screen02IndicatorPeriod>0 && Screen03IndicatorPeriod<=0) { if(indicator_buffer1[1]>indicator_buffer1[2] && indicator_buffer2[1]>indicator_buffer2[2]) return(ORDER_TYPE_BUY); } if(Screen01IndicatorPeriod<=0 && Screen02IndicatorPeriod>0 && Screen03IndicatorPeriod>0) { if(indicator_buffer2[1]>indicator_buffer2[2] && indicator_buffer3[1]>indicator_buffer3[2]) return(ORDER_TYPE_BUY); } if(Screen01IndicatorPeriod>0 && Screen02IndicatorPeriod<=0 && Screen03IndicatorPeriod>0) { if(indicator_buffer1[1]>indicator_buffer1[2] && indicator_buffer3[1]>indicator_buffer3[2]) return(ORDER_TYPE_BUY); } //--- 三个时间范围的条件 if(Screen01IndicatorPeriod>0 && Screen02IndicatorPeriod>0 && Screen03IndicatorPeriod>0) { if(indicator_buffer1[1]>indicator_buffer1[2] && indicator_buffer2[1]>indicator_buffer2[2] && indicator_buffer3[1]>indicator_buffer3[2] ) return(ORDER_TYPE_BUY); } //--- 没有信号 return(WRONG_VALUE); }
现在我们只需要在OnInit()和OnDeinit()函数上做一点小的改动了,您可以看一下代码中高亮部分的改变:
//+------------------------------------------------------------------+ //| EA初始化函数 | //+------------------------------------------------------------------+ int OnInit() { //--- 为检查新柱确定最小时间范围 MinimumTimeframe=GetMinimumTimeframe(Screen01TimeFrame,Screen01IndicatorPeriod, Screen02TimeFrame,Screen02IndicatorPeriod, Screen03TimeFrame,Screen03IndicatorPeriod); //--- 取得指标句柄 GetIndicatorHandles(); //--- 初始化新柱 CheckNewBar(); //--- 取得属性 GetPositionProperties(P_ALL); //--- 设置信息面板 SetInfoPanel(); //--- return(0); } //+------------------------------------------------------------------+ //| EA去初始化函数 | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- 在日志中打印去初始化原因 Print(GetDeinitReasonText(reason)); //--- 当从图表上移除时 if(reason==REASON_REMOVE) { //--- 从图表上删除所有有关信息面板的对象 DeleteInfoPanel(); //--- 删除指标句柄 IndicatorRelease(Screen01IndicatorHandle); IndicatorRelease(Screen02IndicatorHandle); IndicatorRelease(Screen03IndicatorHandle); } }
基于三重滤网策略的交易系统框架就这样完成了,如有必要改变指标或者增加额外条件,它可以在任何时候修改。
优化参数和测试EA交易
让我们进一步进行参数优化并检查结果,策略测试器按照下图设置 (请确保指定了三个时间范围的最小值):
图 1. 策略测试器设置.
EA交易的参数优化设置如下:时间范围可以设置优化,但是我倾向于人工设置它们。.
图 2. EA交易的设置.
优化过程在一个双核处理器上大约耗时30分钟,优化图如下:
图 3. 优化图解.
最大余额测试结果比最大采收率测试结果显示出较小的回撤,这也是为何最大余额测试结果作为演示的原因:
图 4. 最大余额测试结果.
图 5. 最大余额测试图解.
结论
本文展示了如果主要功能具备,EA交易可以很快地修改,您只需要修改信号区块和指标就可以获得一个全新的交易系统。本文的附件中是包含以上所述EA交易源代码的存档,您可以下载用于进一步自学,附件中还包含了输入参数设置的设定文件。
本文译自 MetaQuotes Software Corp. 撰写的俄文原文
原文地址: https://www.mql5.com/ru/articles/647
MyFxtop迈投(www.myfxtop.com)-靠谱的外汇跟单社区,免费跟随高手做交易!
免责声明:本文系转载自网络,如有侵犯,请联系我们立即删除,另:本文仅代表作者个人观点,与迈投财经无关。其原创性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容、文字的真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
著作权归作者所有。
商业转载请联系作者获得授权,非商业转载请注明出处。