下载MetaTrader 5

跟踪止损和退出市场的模式

18 三月 2016, 08:55
Sergey Kravchuk
0
1 946

简介

订单修改/关闭算法的开发人员面临无止境的痛苦 - 如何比较通过不同方法获得的结果?检查机制众所周知 - 它就是策略测试程序。但如何使 EA 同等地处理建立/关闭订单?本文将介绍一个能够重复建立大量订单的工具,让我们能够维持一个在数学上保持正确的平台,以比较针对跟踪止损和退出市场的不同算法的结果。

如果你正在调试一个应独立计算进入市场、跟踪止损和退出市场的时间的复杂 EA,则实际上几乎不可能获得一个可相互比较的可重复模式。假设存在一种建立订单的信号相当长的情况。在理想情况下,将建立此订单。然后,如果选择的方向正确并且价格在预测方向上变动,跟踪止损将开始工作。根据价格波动,设置过于接近的止损会导致过早关闭可能会增加利润的订单。如果此时建仓信号仍然有效,EA 将关闭新的订单。因而,我们需要将“正确”订单的结果与在提前关闭之后建立的一些其他订单的结果进行比较。为了避免这种情况,建议如下。



问题说明

  1. 订单建立/关闭点模式已在图表中进行标记。
  2. 建立/关闭时间和交易方向(买入/卖出)已保存在文件中。
  3. 已创建用于读取准备的文件并严格执行其命令的 Expert Advisor。

必须在市场反转时设置建立点 - 它们在历史记录中非常明显,这一点很好。然而,不应在价格达到反向的反转点之时而应在之后选择关闭点。不要忘记我们的任务是优化跟踪和退出市场,因此我们应让任何算法完成其操作,即使算法不正确。如果仍然无法修复收益,我们将看到亏损,这将成为我们需要重新设计算法的信号。

请看上图。紫线显示理想情况下的正确进场和出场。它可用于计算我们希望/能够获得的最大收益。然而,考虑到跟踪测试目的,我们将使用与蓝线类似的线条。它显示实际交易的特点:进场有些延迟(例如,我们正在等待反转确认)以及在保本位附近进行关闭(例如,我们担心会出现强势反转,而如果这样的话,我们会损失惨重)。

在“沿蓝线”进行的交易中,在跟踪之后,有三个可能会触发止损的点:

  1. 与当前价格距离最近时触发积极跟踪。
  2. 通常,触发“耐心”跟踪。
  3. 触发理想跟踪,使收益最大化。

除此之外,点 4 周围的区域可能会错误地触发过于急躁的跟踪。

既然我们知道如何“标记”理想区域,接下来唯一的事情就是使其尽可能的妥当。



标记工具

为了便于使用理想线条对图表标记,我们来准备一组脚本。两个脚本 TL_Buy 和 TL_Sell 将分别针对买入和卖出操作创建标记线条。脚本 TL_Write 将查看所有创建的线条并将它们的特点保存在一个文件中以便 Expert Advisor TL_Trade 使用。另一个脚本 TL_Read 将能够读取创建的文件并基于此文件重组所有线条。。这对更正可用线条、添加一些新的线条或者删除现有线条可能很有帮助。

为了读取/写入脚本能够使用它们的线条,我们将根据一定的规则命名所有线条:

  1. 所有理想线条的名称以同一前缀 (TL_) 开头。你以后可以使用此前缀来选择和删除线条;
  2. 前缀后面跟的是一个表示操作代码的字符:B-买入,S-卖出;
  3. 在线条名称中,操作代码后面跟的是线条之间相互区分的线条编号。

因此,我们应在图表中获得具有例如以下名称的线条: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

测试 EA 在代码方面没有任何困难之处。其中,以下块是重点:

  1. 到达模式部分的结尾部分时的订单关闭块;
  2. 到达模式部分的开头部分的订单建立块;
  3. 测试跟踪止损和退出市场的块。

它们的作用在源代码中很明显。这里,只有少数注释是必须给定的:

  1. 由于可以不分先后创建线条,因此将在测试程序每次“价格变动”时测试整个线条集,以找到需要在其上建立/关闭订单的线条。
  2. 将使用内部的时间和日期显示格式在订单上的注释部分写入建立/关闭时间。对于我们而言最重要的是,如果在模式线条结束之前订单已被跟踪提前关闭,我们不需要多次打开同一个订单。其次,在检查建立的订单以及从注释中提取关闭时间时,我们将正好在控制线条结束时关闭订单,因为关闭时间已写入建立订单自身。
  3. 参数 ProcedTrailing 启用/禁用跟踪处理。这样,我们能够传递完全没有跟踪的 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 通过跟踪选择辨别订单,但此问题完全可以得到解决。

顺便提一下,这些方法可用于比较进场和退场战术!因此,你仅应准备一组略有不同的行,确保根据你的方法的订单建立点尽可能地接近图表中的模式建立位置。然而,这是另一个研究主题,将在新的文章中进行讨论。


本文译自 MetaQuotes Software Corp. 撰写的俄文原文
原文地址: https://www.mql5.com/ru/articles/1527

附加的文件 |
TL_Trader.zip (7.01 KB)
通过 DDE 在 MetaTrader 4 与 Matlab 之间进行交互 通过 DDE 在 MetaTrader 4 与 Matlab 之间进行交互

分步说明如何使用 DDE 将数据从 Matlab 传输到 MetaTrader 4。

已打开头寸的两步修改 已打开头寸的两步修改

两步法让你在邻近趋势的情况下和可能发生偏离的时候,避免不必要的关闭和重新打开头寸。

创建手动交易策略的模糊逻辑 创建手动交易策略的模糊逻辑

本文提出使用模糊理论来改善手动交易策略的方法。作为例子,我们给出了如何一步步构建策略和选则参数,然后将模糊逻辑应用与进入市场的标准选择上。这样,在修改策略后,我们获得了能对市场情况做出合理反应的弹性开仓条件。

通过"单元测试"的帮助来提高代码质量 通过"单元测试"的帮助来提高代码质量

就算是简单程序也会经常出现看似难以置信的错误。 “我怎么会编出这种东西?”是我们发现这种错误时的第一反应。 “我应该如何避免它?”则是较少会映入脑海的第二个问题。 编写完美无缺的代码是不可能的,特别是在大型项目里,但可通过技术手段及时检测出这些错误。 本文介绍如何借助通用的“单元测试”方法来提高 MQL4 代码质量。