订单修改/关闭算法的开发人员面临无止境的痛苦 - 如何比较通过不同方法获得的结果?检查机制众所周知 - 它就是策略测试程序。但如何使 EA 同等地处理建立/关闭订单?本文将介绍一个能够重复建立大量订单的工具,让我们能够维持一个在数学上保持正确的平台,以比较针对跟踪止损和退出市场的不同算法的结果。
如果你正在调试一个应独立计算进入市场、跟踪止损和退出市场的时间的复杂 EA,则实际上几乎不可能获得一个可相互比较的可重复模式。假设存在一种建立订单的信号相当长的情况。在理想情况下,将建立此订单。然后,如果选择的方向正确并且价格在预测方向上变动,跟踪止损将开始工作。根据价格波动,设置过于接近的止损会导致过早关闭可能会增加利润的订单。如果此时建仓信号仍然有效,EA 将关闭新的订单。因而,我们需要将“正确”订单的结果与在提前关闭之后建立的一些其他订单的结果进行比较。为了避免这种情况,建议如下。
必须在市场反转时设置建立点 - 它们在历史记录中非常明显,这一点很好。然而,不应在价格达到反向的反转点之时而应在之后选择关闭点。不要忘记我们的任务是优化跟踪和退出市场,因此我们应让任何算法完成其操作,即使算法不正确。如果仍然无法修复收益,我们将看到亏损,这将成为我们需要重新设计算法的信号。
请看上图。紫线显示理想情况下的正确进场和出场。它可用于计算我们希望/能够获得的最大收益。然而,考虑到跟踪测试目的,我们将使用与蓝线类似的线条。它显示实际交易的特点:进场有些延迟(例如,我们正在等待反转确认)以及在保本位附近进行关闭(例如,我们担心会出现强势反转,而如果这样的话,我们会损失惨重)。
在“沿蓝线”进行的交易中,在跟踪之后,有三个可能会触发止损的点:
除此之外,点 4 周围的区域可能会错误地触发过于急躁的跟踪。
既然我们知道如何“标记”理想区域,接下来唯一的事情就是使其尽可能的妥当。
为了便于使用理想线条对图表标记,我们来准备一组脚本。两个脚本 TL_Buy 和 TL_Sell 将分别针对买入和卖出操作创建标记线条。脚本 TL_Write 将查看所有创建的线条并将它们的特点保存在一个文件中以便 Expert Advisor TL_Trade 使用。另一个脚本 TL_Read 将能够读取创建的文件并基于此文件重组所有线条。。这对更正可用线条、添加一些新的线条或者删除现有线条可能很有帮助。
为了读取/写入脚本能够使用它们的线条,我们将根据一定的规则命名所有线条:
因此,我们应在图表中获得具有例如以下名称的线条:TL_B1、TL_B2、TL_S3 等。 只需将绘制线条的脚本拖放在图表上,将在拖放点上出现相应的线条。你可以移动它的端点,以便它们标记交易所需的理想“蓝线”。读取/写入脚本在连接到图表后,会要求提供要保存和读取的文件名。这样,我们可以轻松地使用不同的线条集,例如针对不同的货币对。 这些脚本的代码相当透明,而且提供所有必要的注释,因此我冒昧地跳过它们的算法说明 - 你可以从它们的代码中看到算法。
/**************************************************************** PATTERN TRADING: TL_Buy - creation of a new, pattern buying line Copyright © 2006-2008, Sergey Kravchuk. http://forextools.com.ua *****************************************************************/ #include <WinUser32.mqh> #define _prefix_ "TL_" int start() { int MaxNo=0,i,No; if(WindowOnDropped()!=0) { MessageBox("Script should be dropped in the main window","ERROR", IDOK + MB_ICONERROR); return(1); } // find the maximum suffix number for all lines for(i=0;i<ObjectsTotal();i++) { if(StringFind(ObjectName(i),_prefix_)==0) { No=StrToInteger(StringSubstr(ObjectName(i),StringLen(_prefix_)+1)); // select the line number if(MaxNo<No) MaxNo=No; // store it, if it is larger } } datetime t0=WindowTimeOnDropped(); double p0=WindowPriceOnDropped(); // find the coordinates of the script dropping point int width = 5*Period()*60; // width of the created line in bars converted into time units double height = 20*MarketInfo(Symbol(),MODE_TICKSIZE); // height of the created line in ticks converted into price units string LineName = _prefix_+"B"+(MaxNo+1); // create a name for a new line ObjectCreate(LineName,OBJ_TREND,0,t0-width,p0-height, t0+width,p0+height); // create a line ObjectSet(LineName,OBJPROP_RAY,False); // make it a section, not a ray ObjectSet(LineName,OBJPROP_WIDTH,2); // set its width ObjectSet(LineName,OBJPROP_COLOR,Blue); // set its color }
/**************************************************************** PATTERN TRADING: TL_Sell - creation of a new, pattern selling line Copyright © 2006-2008, Sergey Kravchuk. http://forextools.com.ua *****************************************************************/ #include <WinUser32.mqh> #define _prefix_ "TL_" int start() { int MaxNo=0,i,No; if(WindowOnDropped()!=0) { MessageBox("Script should be dropped in the main window","ERROR", IDOK + MB_ICONERROR); return(1); } // find the maximum suffix number for all lines for(i=0;i<ObjectsTotal();i++) { if(StringFind(ObjectName(i),_prefix_)==0) { No=StrToInteger(StringSubstr(ObjectName(i),StringLen(_prefix_)+1)); // select the line number if(MaxNo<No) MaxNo=No; // store it, if it is larger } } datetime t0=WindowTimeOnDropped(); double p0=WindowPriceOnDropped(); // find the coordinates of the script dropping point int width = 5*Period()*60; // width of the created line in bars converted into time units double height = 20*MarketInfo(Symbol(),MODE_TICKSIZE); // height of the created line in ticks converted into price units string LineName = _prefix_+"S"+(MaxNo+1); // create a name for a new line ObjectCreate(LineName,OBJ_TREND,0,t0-width,p0+height, t0+width,p0-height); // create a line ObjectSet(LineName,OBJPROP_RAY,False); // make it a section, not a ray ObjectSet(LineName,OBJPROP_WIDTH,2); // set its width ObjectSet(LineName,OBJPROP_COLOR,Red); // set its color }
/**************************************************************** PATTERN TRADING: TL_Write - saving the coordinates of pattern lines in a file Copyright © 2006-2008, Sergey Kravchuk. http://forextools.com.ua *****************************************************************/ #include <WinUser32.mqh> #define _prefix_ "TL_" #property show_inputs extern string FileNameForWrite = "TL_DATA.TXT"; int start() { int LinesCNT=0,i; string Operation; double p; datetime t; int fh=FileOpen(FileNameForWrite,FILE_CSV|FILE_WRITE,';'); // look through all lines created and save the opening commands for the EA from them for(i=0;i<ObjectsTotal();i++) { if(StringFind(ObjectName(i),_prefix_)==0) // our line { string LineName = ObjectName(i); datetime t1=ObjectGet(LineName,OBJPROP_TIME1); datetime t2=ObjectGet(LineName,OBJPROP_TIME2); double p1=ObjectGet(LineName,OBJPROP_PRICE1); double p2=ObjectGet(LineName,OBJPROP_PRICE2); LinesCNT++; // increase the counter for producing the final message Operation = StringSubstr(ObjectName(i),StringLen(_prefix_),1); // prices are necessary only for restoring the line in the chart FileWrite(fh,Operation,TimeToStr(t1),DoubleToStr(p1,Digits),TimeToStr(t2),DoubleToStr(p2,Digits)); } } FileClose(fh); MessageBox("Stored sections "+(LinesCNT)+" pcs.","Done", IDOK + MB_ICONINFORMATION); }
/**************************************************************** PATTERN TRADING: TL_Read - drawing pattern lines from the file Copyright © 2006-2008, Sergey Kravchuk. http://forextools.com.ua *****************************************************************/ #include <WinUser32.mqh> #define _prefix_ "TL_" #property show_inputs extern string FileNameForRead = "TL_DATA.TXT"; int start() { int LinesCNT=0,i; int fh=FileOpen(FileNameForRead,FILE_CSV|FILE_READ,';'); if(fh<0) { MessageBox("Error opening file \"" + FileNameForRead + "\"","ERROR", IDOK + MB_ICONERROR); return(1); } // first of all, delete everything for(i=0;i<ObjectsTotal();i++) { if(StringFind(ObjectName(i),_prefix_)==0) { ObjectDelete(ObjectName(i)); i--; } } // look through all lines created and save the opening commands for the EA from them while(true) { string Operation=FileReadString(fh); if(FileIsEnding(fh)) break; // file ended? - exit // read the section's coordinates datetime t1=StrToTime(FileReadString(fh)); double p1=StrToDouble(FileReadString(fh)); datetime t2=StrToTime(FileReadString(fh)); double p2=StrToDouble(FileReadString(fh)); // draw a section LinesCNT++; string LineName = _prefix_+Operation+(LinesCNT); // create a name for a new line ObjectCreate(LineName,OBJ_TREND,0,t1,p1, t2,p2); // create a line ObjectSet(LineName,OBJPROP_RAY,False); // make it a section, not a ray ObjectSet(LineName,OBJPROP_WIDTH,2); // set its width if(Operation=="B") ObjectSet(LineName,OBJPROP_COLOR,Blue); else ObjectSet(LineName,OBJPROP_COLOR,Red);// set its color } FileClose(fh); MessageBox("Read sections "+(LinesCNT)+" pcs.","Done", IDOK + MB_ICONINFORMATION); }
/**************************************************************** PATTERN TRADING: TL_Clear - deletion of all pattern lines Copyright © 2006-2008, Sergey Kravchuk. http://forextools.com.ua *****************************************************************/ #include <WinUser32.mqh> #define _prefix_ "TL_" int start() { int LinesCNT=0,i; for(i=0;i<ObjectsTotal();i++) { if(StringFind(ObjectName(i),_prefix_)==0) { ObjectDelete(ObjectName(i)); i--; LinesCNT++; } } }
定位文件是非常重要的一点。使用标准方式,工作脚本只能在目录 c:\Program Files\MetaTrader 4\experts\files 下创建文件。然而,在测试 Expert Advisor 时,测试程序可以访问其所在目录中的同名文件夹,目录为 c:\Program Files\MetaTrader 4\tester\files。
这就是以下操作的原因:创建文件之后、在测试 EA 中使用这些文件之前,你应单独将它们从 c:\Program Files\MetaTrader 4\experts\files复制到 c:\Program Files\MetaTrader 4\tester\files。
在重新创建文件并更改了线条的某些内容之后,你需要重复上述操作。
测试 EA 在代码方面没有任何困难之处。其中,以下块是重点:
它们的作用在源代码中很明显。这里,只有少数注释是必须给定的:
/**************************************************************** PATTERN TRADING: TL_Trader - trading on pattern lines Copyright © 2006-2008, Sergey Kravchuk. http://forextools.com.ua *****************************************************************/ #include <WinUser32.mqh> #define _prefix_ "TL_" extern string FileNameForRead = "TL_DATA.TXT"; extern double Lots = 0.1; extern double StopLoss = 0; extern double TrailingStop = 30; extern bool ProcedTrailing=true; // process the trailing block double SL; // to calculate the SL values for opening an order int start() { int LinesCNT=0,i,ticket,pos; double p; datetime t; string s; int fh=FileOpen(FileNameForRead,FILE_CSV|FILE_READ,';'); // to test the file, it is necessary to pout it into tester\files\TL_DATA.txt if(fh<0) { MessageBox("Error opening file \"" + FileNameForRead + "\"","ERROR", IDOK + MB_ICONERROR); return(1); } // check all entries: if the opening time has already passed and no order with such a comment is found in history or in open orders // then it has not been opened yet - open it as it's said there while(true) { string Operation=FileReadString(fh); if(FileIsEnding(fh)) break; // file ended? - exit // count the section coordinates string st1=FileReadString(fh); string sp1=FileReadString(fh); string st2=FileReadString(fh); string sp2=FileReadString(fh); datetime t1=StrToTime(st1); double p1=StrToDouble(sp1); datetime t2=StrToTime(st2); double p2=StrToDouble(sp2); // what if sections' ends are mixed? if(t1>t2) { p=p1; p1=p2; p2=p; t=t1; t1=t2; t2=t; s=st1; st1=st2; st2=s; s=sp1; sp1=sp2; sp2=s; } string MarkComent = t1+"="+t2; //********************************************************************************** // 1) block closing the orders as soon as the end of the pattern section is reached. //********************************************************************************** for(i=0;i<OrdersTotal();i++) { if(OrderSelect(i,SELECT_BY_POS,MODE_TRADES)==false) continue; if(OrderComment()==MarkComent && TimeCurrent()>=t2) // order must be closed { if(OrderType()==OP_BUY) OrderClose(OrderTicket(),OrderLots(),Bid,3,Violet); // close position else OrderClose(OrderTicket(),OrderLots(),Ask,3,Violet); // close position } } //********************************************************************************** // 2) block opening orders as soon as the beginning of the pattern section is passed. //********************************************************************************** bool OrderNotPresent=true; // a sign showing that we haven't opened such an order yet if(t1<=TimeCurrent() && TimeCurrent()<t2) // time to open - make sure that this order is not opened or closed { for(i=0;i<OrdersTotal();i++) { if(OrderSelect(i,SELECT_BY_POS,MODE_TRADES)==false) continue; if(OrderComment()==MarkComent) { OrderNotPresent=false; break; } // order already exists } for(i=0;i<OrdersHistoryTotal() && OrderNotPresent;i++) { if(OrderSelect(i,SELECT_BY_POS,MODE_HISTORY)==false) continue; // order in history is added to the end, something like "[sl]" - it must be cut off!! pos = StringFind(OrderComment(),"["); string CurOrderComment = StringSubstr(OrderComment(),0,pos); if(CurOrderComment==MarkComent) { OrderNotPresent=false; break; } // order already exists } if(OrderNotPresent) // no such order - open it { // open an order if(Operation=="B") // Buy { if(StopLoss<=0) SL=0; else SL=Ask-StopLoss*Point; ticket=OrderSend(Symbol(),OP_BUY,Lots,Ask,3,SL,0,MarkComent,1235,0,Blue); OrderSelect(ticket,SELECT_BY_TICKET); } else // Sell { if(StopLoss<=0) SL=0; else SL=Bid+StopLoss*Point; ticket=OrderSend(Symbol(),OP_SELL,Lots,Bid,3,SL,0,MarkComent,1235,0,Red); OrderSelect(ticket,SELECT_BY_TICKET); } } } } FileClose(fh); //****************************************************** // 3) block testing trailing stop and exit the market //****************************************************** if(ProcedTrailing) { for(i=0;i<OrdersTotal();i++) { if(OrderSelect(i,SELECT_BY_POS,MODE_TRADES)==false) continue; if(OrderType()==OP_BUY) { if(Bid-OrderOpenPrice()>Point*TrailingStop) { if(OrderStopLoss()<Bid-Point*TrailingStop) { OrderModify(OrderTicket(),OrderOpenPrice(),Bid-Point*TrailingStop,OrderTakeProfit(),0,Green); return(0); } } } if(OrderType()==OP_SELL) { if((OrderOpenPrice()-Ask)>(Point*TrailingStop)) { if((OrderStopLoss()>(Ask+Point*TrailingStop)) || (OrderStopLoss()==0)) { OrderModify(OrderTicket(),OrderOpenPrice(),Ask+Point*TrailingStop,OrderTakeProfit(),0,Red); return(0); } } } } } return(0); }
为了测试系统,我们使用了一个 14 行的小集合。下面是名称为 TL_DATA.txt 的模式文件的内容:
B;2007.12.28 05:00;1.4605;2008.01.09 22:00;1.4658 B;2008.01.29 05:00;1.4767;2008.02.05 05:00;1.4811 B;2008.02.15 16:00;1.4687;2008.02.21 09:00;1.4735 B;2008.02.21 14:00;1.4738;2008.02.26 07:00;1.4812 B;2008.02.28 14:00;1.5129;2008.03.05 12:00;1.5186 B;2008.03.05 22:00;1.5261;2008.03.11 20:00;1.5316 B;2008.03.13 01:00;1.5539;2008.03.18 22:00;1.5620 B;2008.03.26 14:00;1.5724;2008.03.28 10:00;1.5758 S;2007.11.30 13:00;1.4761;2007.12.10 22:00;1.4711 S;2007.12.14 04:00;1.4626;2007.12.28 00:00;1.4610 S;2008.01.17 17:00;1.4688;2008.01.24 13:00;1.4671 S;2008.02.07 12:00;1.4633;2008.02.14 11:00;1.4617 S;2008.03.19 23:00;1.5641;2008.03.25 23:00;1.5629 S;2008.03.31 19:00;1.5811;2008.04.08 04:00;1.5796
下面是它在图表上的形式:
不是最有效的交易,但是用于测试跟踪的理想依据。;)
如果我们在已禁用跟踪止损 (ProcedTrailing=false) 情况下启动了测试程序,我们将获得一个糟糕的结果:
根据跟踪止损大小进行的简单优化可以提供 95 个点的最佳值。
产生最大收益的最佳值与测试期间的柱大小统计数据有很好的一致性:95 个点形成此时段所有柱的 98%,这一点在 ft.BarStat indicator 的图表中可清楚地看出来。
正如你所看到的,已优化的结果看起来更加吸引人:
它们在图表中的显示更加清楚。请注意,将在模式线条开始处精确地建立所有订单!
来看看第一部分(3 月 5 日)。收益被急剧大跌亏空,但大体的买入趋势保持不变。如果我们测试正常 EA 的跟踪,它很可能会建立一个保留直至第二条线结束(3 月 17 日以后)的买入仓位。在这种情况下,我们将获得无与伦比的结果。在第二种情况下获得的收益来自于成功地重复建立新订单,而非跟踪止损机制。然而,问题不在于获得最大的收益,而在于获得尽可能有效的跟踪止损机制。所以说,同时建立所有订单以及不让订单保留太长时间,这一点对于我们非常重要。然而,在这种情况中,优化带来的收益增加将反映跟踪算法的有效性!
我非常希望,有关本文的讨论不会淹没在诽谤我没有做的一些事情上 - 感兴趣的读者会设法找到提出的研究算法的用途,就像在此处显示的一样。本文介绍了一个我最后认真着手的工具。这个想法在我的脑海里停留了很长的时间,最终逐渐清晰起来,并作为 MQL4 代码实现。我没有时间进行研究,这个工具就是用于进行研究的。这就是本文没有提供各种跟踪算法的比较分析的原因 - 我打算在不久的将来处理这些,因此很快会准备发布本文的另一部分。然而事实上,本文对 MQL4 社区似乎很有帮助。
我决定发布此“裸版”工具的另一个原因是,我是一名专业程序员,同时也是一个刚入行的交易者。这意味着,我可以独立地开发我的 MQL4 代码以使其最大可能地简易或复杂,但是,只有与那些专长于交易就如同我专长于编程的专业人士一起,我才能开发如同程序代码一样有效的交易战术和跟踪算法。因此,我非常有兴趣与我的“外汇同行”一起进行交流,我乐意携手把我的工具应用于实际交易。如果你有兴趣与我一起合作,请通过我的个人资料联系我。
作为进一步开发,我们可以同时在同一个图表上测试多个跟踪算法。在模式部分的开头部分,我们将建立几个订单(进行测试的算法数量),根据算法对每个订单进行跟踪。在概念上,我们应获得显示多个图像相互重叠的图片。然后,我们将能够根据数量(净利润)和质量比较结果 - 我们将能够看到哪个算法更精确地实现其功能。然而,这需要我们告知 EA 通过跟踪选择辨别订单,但此问题完全可以得到解决。
顺便提一下,这些方法可用于比较进场和退场战术!因此,你仅应准备一组略有不同的行,确保根据你的方法的订单建立点尽可能地接近图表中的模式建立位置。然而,这是另一个研究主题,将在新的文章中进行讨论。
本社区仅针对特定人员开放
查看需注册登录并通过风险意识测评
5秒后跳转登录页面...
移动端课程