指标和提醒的盈利能力可视化测试

Sergey Kravchuk | 16 三月, 2016

通过这些提醒进行 EA 测试时,通常将决定是使用交易提醒的哪个指标或只是指标计算方式。但是,为每个指标编写一个 EA 并非总是可能/必要/合理的。你可以通过自己收集提醒并绘制理想交易图像的特殊指标,快速计算出基于其他指标提醒的交易盈利能力。这可帮助你既对所得结果进行可视化估计,又快速选择了最优参数。

想起曾有多少次,你在看到随机找到或经过长时间搜索后最终找到的指标时,就想马上知道通过提醒而获得的交易结果。同样情况在你获得还没有 EA 或指标的新交易系统时便会发生。开始重大工作和编写有效 EA 之前,你想要估计出售中的材料是否可带来健康食粮。

或者你会面对如下情况:指标的提醒良好,但是你的直觉告诉你,该指标的参数并非最优,你可以将其清除,你甚至知道清除的方式。如何可以在不被添加到长而复杂的代码中的情况下,快速地测试你的想法呢?

问题陈述

让我们想想我们有什么,我们需要什么。让我们以 MetaTrader 4 的标准交付版本中的著名指标 ZigZag 作为示例。将其附加至任何时间范围内的任何货币对的图表中:你确定要与其进行交易吗?一切都很清楚:你应在高位卖出,并在低位买入以平仓。

现在有个问题:你将从这起交易中获利多少?这个问题的答案很简单:将所有线段的最高价相加,并根据仓位手数和保证金货币的某个点位价格来重新计算。你可以通过调用函数 iCustom来访问 ZigZag 值的缓冲区:

P=iCustom(NULL,0,"ZigZag",0,i);

此处i代表你获取值的柱号。

现在你所需要做的,便是在缓冲区的所有值中进行搜索,并找到所有含非零值的点 - 这些点是用来建立 ZigZag 的。你还需要将线段的最高点相加。出于描写性原因,你甚至可以根据交易颜色及其盈利能力来绘制这些线并为其上色。这是个简单的工作,并可以成功分配给指标。

通用解决方案

为使此指标得以测试任何其他的指标,你应确保其通用性。鉴于在通常情况下任何指标或交易系统都假设只进行 4 种操作:BUY, SELL, BUY CLOSE, 和 SELL CLOSE,我们应让每一种操作拥有专用的数组。必须能够从任何来源填充此数组。这是你唯一需要通过添加填充提醒数组的代码来进行的自我编辑(好吧,你还应将部分参数放入外部变量)。

功能上讲,指标拥有以下程序块:

实现

以下是指标本身的代码,其中包含了指标 ZigZag 数据反馈的示例。

/*///———————————————————————————————————————————————————————————————————————————————————————————————————————
 
    IndicatorTester.mq4 
  
    Visual Testing the Profitability of Indicators and Alerts
    
    Copyright © 2006, Кравчук Сергей,  http://forextools.com.ua
 
/*///———————————————————————————————————————————————————————————————————————————————————————————————————————
#property copyright "Copyright © 2006-2008, Sergey Kravchuk. http://forextools.com.ua"
#property link      "http://forextools.com.ua"
 
#property indicator_chart_window
#property indicator_buffers 0
 
// parameters for displaying the elements of the chart
extern bool   ShowZZ          = true;           // should the ZZ be drawn?
extern bool   ShowMARKERS     = true;           // should the vertical marking lines be drawn?
 
//dates of drawing the chart
extern datetime DateStart     = D'1.01.1970';
extern datetime DateEnd       = D'31.12.2037';
 
//parameters of profit calculations
extern double LotForCalc      = 0.05;           // the amount of lots for profit calculations
extern int    Optimizm        = 0;              // -1 gives a pessimistic calculation; 0 gives the calculation on bar opening prices;  
                                                //+1 is optimistic
 
// BUY lines colors
extern color  ColorProfitBuy  = Blue;           // line color for profitable BUY trades
extern color  ColorLossBuy    = Red;            // line color for losing BUY trades
extern color  ColorZeroBuy    = Gray;           // line color for BUY trades with zero profits
 
// SELL lines colors
extern color  ColorProfitSell = Blue;           // line color for profitable SELL trades
extern color  ColorLossSell   = Red;            // line color for losing SELL trades
extern color  ColorZeroSell   = Gray;           // line color for SELL trades with zero profits
 
// alert lines colors
extern color  ColorBuy        = CornflowerBlue; // line color for BUY alerts
extern color  ColorSell       = HotPink;        // line color for SELL alerts
extern color  ColorClose      = Gainsboro;      // line color for closing alerts
 
//——————————————————————————————————————————————————————————————————————————————————————————————————————————
double sBuy[],sCloseBuy[],sSell[],sCloseSell[]; // arrays for alerts
int sBuyCnt,sSellCnt,sBuyCloseCnt,sSellCloseCnt;// alerts counters 
int i,DisplayBars;
//——————————————————————————————————————————————————————————————————————————————————————————————————————————
// service codes
#define MrakerPrefix "IT_"
#define OP_CLOSE_BUY  444
#define OP_CLOSE_SELL 555
//——————————————————————————————————————————————————————————————————————————————————————————————————————————
int init()   { ClearMarkers(); return(0); }
int deinit() { ClearMarkers(); return(0); }
//——————————————————————————————————————————————————————————————————————————————————————————————————————————
int start() 
{ 
  double Profit=0,P1,P2; int CntProfit=0,CntLoose=0,i1,i2;
 
  //————————————————————————————————————————————————————————————————————————————————————————————————————————
  // delete all marks, in case the indicator is going to be redrawn
  ClearMarkers(); 
  //————————————————————————————————————————————————————————————————————————————————————————————————————————
  // prepare alerts counters
  sBuyCnt=0; sSellCnt=0; sBuyCloseCnt=0; sSellCloseCnt=0; 
  //————————————————————————————————————————————————————————————————————————————————————————————————————————
  // allocate some memory for alerts arrays and zeroize their valuesя
  DisplayBars=Bars; // the amount of bars to be displayed
  ArrayResize(sBuy,DisplayBars);  ArrayInitialize(sBuy,0);
  ArrayResize(sSell,DisplayBars); ArrayInitialize(sSell,0);
  ArrayResize(sCloseBuy,DisplayBars);  ArrayInitialize(sCloseBuy,0);
  ArrayResize(sCloseSell,DisplayBars); ArrayInitialize(sCloseSell,0);
  //————————————————————————————————————————————————————————————————————————————————————————————————————————
  // find the first point and save its location and price
  for(i1=Bars-1;i1>=0;i1--) { P1=iCustom(NULL,0,"ZigZag",0,i1); if(P1!=0) break; }
  // process the ZigZag points
  for(i2=i1-1;i2>=0;i2--) 
  {
    // find the next point and save its location and price
    for(i2=i2;i2>=0;i2--) { P2=iCustom(NULL,0,"ZigZag",0,i2); if(P2!=0) break; }
    
    if(i2<0) break; // place the last point on the current price 
 
    // the opening conditions are at the same time the conditions of closing an opposite order
    if(P1>P2) { sSell[i1]=1; sBuy[i2]=1; sCloseSell[i2]=1; }
    if(P1<P2) { sBuy[i1]=1; sSell[i2]=1; sCloseBuy[i2]=1; }
 
    P1=P2; i1=i2; // save the bar in which the point has been found
  }
    //——————————————————————————————————————————————————————————————————————————————————————————————————————
  // delete the repeated alerts having saved only the very first ones located to the left on the chart
  for(i=0;i<DisplayBars;i++) 
  {
    if(sBuy[i]==sBuy[i+1]) sBuy[i]=0;
    if(sSell[i]==sSell[i+1]) sSell[i]=0;
    if(sCloseBuy[i]==sCloseBuy[i+1]) sCloseBuy[i]=0;
    if(sCloseSell[i]==sCloseSell[i+1]) sCloseSell[i]=0;
  }
  // delete the alerts outside the specified range of dates
  for(i=0;i<DisplayBars;i++) 
  {
    if(Time[i]<DateStart || DateEnd<Time[i]) { sBuy[i]=0; sSell[i]=0; sCloseBuy[i]=0; sCloseSell[i]=0; }
  }
  // add forcible closing marginal positions
  if(DateEnd<=Time[0]) { i=iBarShift(Symbol(),Period(),DateEnd); sBuy[i]=0; sSell[i]=0; 
                                                             sCloseBuy[i]=1; sCloseSell[i]=1; }
  if(DateEnd >Time[0]) { sCloseBuy[0]=1; sCloseSell[0]=1; }
  //————————————————————————————————————————————————————————————————————————————————————————————————————————
  // count the amount of alerts

  for(i=0;i<DisplayBars;i++) 
  {
    if(sBuy [i]!=0) sBuyCnt++;  if(sCloseBuy [i]!=0) sBuyCloseCnt++;
    if(sSell[i]!=0) sSellCnt++; if(sCloseSell[i]!=0) sSellCloseCnt++;
  }
  //————————————————————————————————————————————————————————————————————————————————————————————————————————
  // place the marks, draw a ZZ and calculate the profits
  //————————————————————————————————————————————————————————————————————————————————————————————————————————
  // process BUY trades
  for(i=DisplayBars-1;i>=0;i--) // go and collect the points
  {
    // find the next OPEN point and save its location and price
    for(i1=i;i1>=0;i1--) if(sBuy[i1]!=0) break; 
 
    // find the next CLOSE point of a BUY trade and save its location and price
    for(i2=i1-1;i2>=0;i2--) if(sCloseBuy[i2]!=0) break;
    if(i2<0) i2=0; // for the last unclosed position, calculate the CLOSE on the current price
    i=i2; // new bar to continue searching for OPEN points
 
    // define the prices for drawing according to the parameter of optimism, Optimizm
    if(Optimizm<0)  { P1=High[i1]; P2=Low[i2];  } 
    if(Optimizm==0) { P1=Open[i1]; P2=Open[i2]; } 
    if(Optimizm>0)  { P1=Low[i1];  P2=High[i2]; } 
    
    P1/=Point; P2/=Point; // express prices in points
    
    // find the profit and fill out the corresponding buffer
    if(i1>=0) 
    { 
      Profit=Profit+P2-P1; // collect the summed profit
      if(P2-P1>=0) CntProfit++; else CntLoose++; // count the number of orders
      DrawLine(i1,i2,OP_BUY,Optimizm); // draw the order line
      PlaceMarker(i1,OP_BUY); 
      PlaceMarker(i2,OP_CLOSE_BUY); 
    }
  }
  //————————————————————————————————————————————————————————————————————————————————————————————————————————
  // process the SELL trades
  for(i=DisplayBars-1;i>=0;i--) // go and collect the points
  {
    // find the next OPEN point and save its location and price
    for(i1=i;i1>=0;i1--) if(sSell[i1]!=0) break; 
 
    // find the next CLOSE point of a SELL trade and save its location and price
    for(i2=i1-1;i2>=0;i2--) if(sCloseSell[i2]!=0) break;
    if(i2<0) i2=0; // for the last unclosed position, calculate the CLOSE on the current price
    i=i2; // new bar to continue searching for OPEN points
 
    // define the prices for drawing according to the parameter of optimism, Optimizm
    if(Optimizm<0)  { P1=Low[i1];  P2=High[i2]; } 
    if(Optimizm==0) { P1=Open[i1]; P2=Open[i2]; } 
    if(Optimizm>0)  { P1=High[i1]; P2=Low[i2];  } 
    
    P1/=Point; P2/=Point; // express prices in points
    
    // if there are both points available, find the profit and fill the corresponding buffer
    if(i1>=0) 
    { 
      Profit=Profit+P1-P2; // collect the summed profit
      if(P1-P2>=0) CntProfit++; else CntLoose++; // count the number of orders
      DrawLine(i1,i2,OP_SELL,Optimizm); // draw the order line
      PlaceMarker(i1,OP_SELL); 
      PlaceMarker(i2,OP_CLOSE_SELL);
    }
  }
  //————————————————————————————————————————————————————————————————————————————————————————————————————————
  // calculating the totals for commenting  
  int Cnt=CntProfit+CntLoose; // total number of operations
  // coefficient to transfer points into the deposit currency
  double ToCurrency = MarketInfo(Symbol(),MODE_TICKVALUE)*LotForCalc;    
  string msg="Период: "+TimeToStr(MathMax(DateStart,Time[Bars-1]))+" - "
                                                      + TimeToStr(MathMin(DateEnd,Time[0]))+"\n\n";
  
  msg=msg+
  sBuyCnt+" BUY trades and "+sBuyCloseCnt+" their closings\n"+
  sSellCnt+" SELL trades and "+sSellCloseCnt+" their closings\n\n";
  
  // total time in days
  int TotalDays = (MathMin(DateEnd,Time[0])-MathMax(DateStart,Time[Bars-1]))/60/60/24; 
                                                                       //translate seconds into days
  if(TotalDays<=0) TotalDays=1; // to avoid zero divide for shorter days
  
  if(Cnt==0) msg=msg+("No operations");
  else msg=msg+
  (
    DoubleToStr(Profit,0)+" point on "+Cnt+" operations for "+TotalDays+" days\n"+
    DoubleToStr(Profit/Cnt,1)+" point for operation ("+
    DoubleToStr(Profit/TotalDays,1)+" within a day)\n\n"+
    "When trading with "+DoubleToStr(LotForCalc,2)+" obtain in "+AccountCurrency()+":\n"+
    DoubleToStr(Profit*ToCurrency,0)+" totally, by "+
    DoubleToStr(Profit/Cnt*ToCurrency,1)+" per trade ("+
    DoubleToStr(Profit/TotalDays*ToCurrency,1)+" within one day)\n\n"+
    CntProfit+" profitable ("+DoubleToStr(((CntProfit)*1.0/Cnt*1.0)*100.0,1)+"%)\n"+
    CntLoose+" losing ("+DoubleToStr(((CntLoose)*1.0/Cnt*1.0)*100.0,1)+"%)"
  );
  Comment(msg);
  
}
//——————————————————————————————————————————————————————————————————————————————————————————————————————————
 
 
 
//——————————————————————————————————————————————————————————————————————————————————————————————————————————
// deleting all objects from our chart
void ClearMarkers() 
{ 
  for(int i=0;i<ObjectsTotal();i++) 
  if(StringFind(ObjectName(i),MrakerPrefix)==0) { ObjectDelete(ObjectName(i)); i--; } 
}
//——————————————————————————————————————————————————————————————————————————————————————————————————————————
// placing a vertical line - marks of the operation of the op_type type
void PlaceMarker(int i, int op_type)
{
  if(!ShowMARKERS) return; // displaying markers disabled
 
  color MarkerColor; string MarkName; 
 
  // __ for the CLOSE line to be drawn below the OPEN line at sorting 
  if(op_type==OP_CLOSE_SELL) { MarkerColor=ColorClose; MarkName=MrakerPrefix+"__SELL_"+i; }
  if(op_type==OP_CLOSE_BUY)  { MarkerColor=ColorClose; MarkName=MrakerPrefix+"__BUY_"+i;  }  
  if(op_type==OP_SELL)       { MarkerColor=ColorSell;  MarkName=MrakerPrefix+"_SELL_"+i;  }
  if(op_type==OP_BUY)        { MarkerColor=ColorBuy;   MarkName=MrakerPrefix+"_BUY_"+i;   }
 
  ObjectCreate(MarkName,OBJ_VLINE,0,Time[i],0); 
  ObjectSet(MarkName,OBJPROP_WIDTH,1); 
  if(op_type==OP_CLOSE_BUY || op_type==OP_CLOSE_SELL) ObjectSet(MarkName,OBJPROP_STYLE,STYLE_SOLID); 
  else ObjectSet(MarkName,OBJPROP_STYLE,STYLE_DOT);
  ObjectSet(MarkName,OBJPROP_BACK,True);  
 
  ObjectSet(MarkName,OBJPROP_COLOR,MarkerColor);
}
//——————————————————————————————————————————————————————————————————————————————————————————————————————————
// placing a line on the chart for the operation of the op_type type at the prices of parameter Optimizm
void DrawLine(int i1,int i2, int op_type, int Optimizm)
{
  if(!ShowZZ) return; // ZZ displaying disabled
  
  color СurColor;
  string MarkName=MrakerPrefix+"_"+i1+"_"+i2;
  double P1,P2;
  
  // define prices for drawing, according to parameter Optimizm
  if(Optimizm<0 && op_type==OP_BUY)  { P1=High[i1]; P2=Low[i2];  } 
  if(Optimizm<0 && op_type==OP_SELL) { P1=Low[i1];  P2=High[i2]; } 
  if(Optimizm==0)                    { P1=Open[i1]; P2=Open[i2]; } 
  if(Optimizm>0 && op_type==OP_BUY)  { P1=Low[i1];  P2=High[i2]; } 
  if(Optimizm>0 && op_type==OP_SELL) { P1=High[i1]; P2=Low[i2];  } 
 
  ObjectCreate(MarkName,OBJ_TREND,0,Time[i1],P1,Time[i2],P2);
  
  ObjectSet(MarkName,OBJPROP_RAY,False); // draw segments, not lines
  ObjectSet(MarkName,OBJPROP_BACK,False);
  ObjectSet(MarkName,OBJPROP_STYLE,STYLE_SOLID);
  ObjectSet(MarkName,OBJPROP_WIDTH,2);
 
  // set line color depending on the profitability of the trade
  if(op_type==OP_BUY) 
  { 
    if(P1 <P2) СurColor = ColorProfitBuy;
    if(P1==P2) СurColor = ColorZeroBuy;
    if(P1 >P2) СurColor = ColorLossBuy;
  }
  if(op_type==OP_SELL) 
  { 
    if(P1 >P2) СurColor = ColorProfitSell;
    if(P1==P2) СurColor = ColorZeroSell;
    if(P1 <P2) СurColor = ColorLossSell;
  }
  ObjectSet(MarkName,OBJPROP_COLOR,СurColor);
}
//——————————————————————————————————————————————————————————————————————————————————————————————————————————
指标参数包含特别参数, Optimizm。该参数设置计算优化的所需程度。如果该值低于零,则说明计算结果将完全消极:对于 BUY 价会选择提醒柱的 High ,对于 SELL 价则会选择 - Low。这便是我们可以通过这些提醒获得的最差结果的模型。如果该值高于零,则建立最乐观计算的模型:在最低点买入并在最高点卖出。如果此参数的值等于零,则采取中性行为,我们在柱的 OPEN 价位打开和关闭订单 - 这是通常在使用 EA 进行交易时实现的情况。

分析结果

现在让我们来看看结果。这是对锯齿形调整浪进行客观分析所获得的结果。

请看,图表显示有八个失去的部分。锯齿形调整浪对最佳价位“有效”,为将其重建,我们应将参数 Optimizm设为 1。

如果我们进行了很差的交易后,若我们将参数 Optimizm 设为 -1,将会出现什么结果。


一点题外话

亲爱的交易员,

请勿责怪于我,但如果该指标在你管理层的能手中发生的话,则会非常棘手,这些能手通常对交易策略的细节并不感兴趣 - 他们仅对结果感兴趣。将 ZigZag 提醒的算法上传至测试程序并将Optimizm设为 1,他们将获得最大利润数据,该数据可自图表的该部分获取,并且他们也希望你去获取。如果你经常按低于一半的金额进行交易,这会让他们认为你可能对你的职责不太上心。

另一方面而言,这个工具可很好地应对不合理的苛刻要求。你有充分的理由可以申辩,他们为你设置的计划是不切实际的,甚至无法在最理想情况下获得。

我们可以看到,单独使用 Optimizm 参数可允许你使用该指标,以对在其中测试的提醒指标进行潜在估算。如果最乐观的计算也无法带来盈利,那么便应该对受测试的提醒指标进行修改:你应为指标找来其他参数或干脆将这个没救的指标遗忘,从而节省 EA 开发时间和保证金,避免在搜集证据证明指标无效时出现损失。因此,我们测试的指标不仅可为我们节省时间,还有金钱。

分析交易系统提醒

其他指标的缓冲区不是填充我们测试指标所依据的提醒数组的唯一来源。你可以在书中某处或网络深处找到交易系统提醒的描述,其中并没有指标或 Expert Advisor 的描述。这仅是一套可简单应用到 MQL4 中的规则。如果你可以为这个系统编写提醒的算法,并使用算法来填充提醒缓冲区,则测试指标将向你展示你通过系统所能获取的结果。

如果系统中有准备就绪的代码,你可以选择 EA 运行所依据提醒计算,并直接在图表示建立“运行结果”。当然你可以在 MetaTrader 4 策略测试程序上获取更加精确的相同结果,但指标会更快速地进行重建并可以比测试程序的严格规则更生动将其反映出来。

这种情况下的重要时刻是,所得结果的图表有机会与其他指标进行整合。交叉额外指标的垂直标记行可能会告诉你如何使用它们来指定主提醒或检测它们运行不足的位置。最后我们只可以通过乱枪打鸟的方法来选择可能会比优化程序结果更适合我们的参数值。

让我们来看一看标准 MACD的现成代码。这就是代码应有的样子。填充提醒数组块,其内容 从指标文本中复制并粘贴而来:

  // fill out alert arrays with values and count them
  for(i=DisplayBars;i>=0;i--) 
  {
    double MacdCurrent, MacdPrevious, SignalCurrent;
    double SignalPrevious, MaCurrent, MaPrevious;
  
    // to simplify the coding and speed up access
    // data are put into internal variables
    MacdCurrent=iMACD(NULL,0,12,26,9,PRICE_CLOSE,MODE_MAIN,i+0);
    MacdPrevious=iMACD(NULL,0,12,26,9,PRICE_CLOSE,MODE_MAIN,i+1);
    SignalCurrent=iMACD(NULL,0,12,26,9,PRICE_CLOSE,MODE_SIGNAL,i+0);
    SignalPrevious=iMACD(NULL,0,12,26,9,PRICE_CLOSE,MODE_SIGNAL,i+1);
    MaCurrent=iMA(NULL,0,MATrendPeriod,0,MODE_EMA,PRICE_CLOSE,i+0);
    MaPrevious=iMA(NULL,0,MATrendPeriod,0,MODE_EMA,PRICE_CLOSE,i+1);
    
    // check for long position (BUY) possibility
    if(MacdCurrent<0 && MacdCurrent>SignalCurrent && MacdPrevious<SignalPrevious &&
       MathAbs(MacdCurrent)>(MACDOpenLevel*Point) && MaCurrent>MaPrevious) sBuy[i]=1;
       
    // check for short position (SELL) possibility
    if(MacdCurrent>0 && MacdCurrent<SignalCurrent && MacdPrevious>SignalPrevious && 
       MathAbs(MacdCurrent)>(MACDOpenLevel*Point) && MaCurrent<MaPrevious) sSell[i]=1;
       
    if(MacdCurrent>0 && MacdCurrent<SignalCurrent && MacdPrevious>SignalPrevious &&
       MathAbs(MacdCurrent)>(MACDCloseLevel*Point)) sCloseBuy[i]=1;
 
    if(MacdCurrent<0 && MacdCurrent>SignalCurrent &&  MacdPrevious<SignalPrevious && 
       MathAbs(MacdCurrent)>(MACDCloseLevel*Point)) sCloseSell[i]=1;
  }

这是结果:

请注意提醒标记的行数完全匹配 MACD 图表的交叉点。也许看着这些相连的图表,你可以决定应在图表中作何修改了。举例而言,MaCurrent 和 MaPrevious 不需要检查。一段很小的纠正代码:

  // fill out alert arrays with values and count them
  for(i=DisplayBars;i>=0;i--) 
  {
    double MacdCurrent, MacdPrevious, SignalCurrent;
    double SignalPrevious, MaCurrent, MaPrevious;
  
    // to simplify the coding and speed up access
    // data are put into internal variables
    MacdCurrent=iMACD(NULL,0,12,26,9,PRICE_CLOSE,MODE_MAIN,i+0);
    MacdPrevious=iMACD(NULL,0,12,26,9,PRICE_CLOSE,MODE_MAIN,i+1);
    SignalCurrent=iMACD(NULL,0,12,26,9,PRICE_CLOSE,MODE_SIGNAL,i+0);
    SignalPrevious=iMACD(NULL,0,12,26,9,PRICE_CLOSE,MODE_SIGNAL,i+1);
    MaCurrent=iMA(NULL,0,MATrendPeriod,0,MODE_EMA,PRICE_CLOSE,i+0);
    MaPrevious=iMA(NULL,0,MATrendPeriod,0,MODE_EMA,PRICE_CLOSE,i+1);
    
    // check for long position (BUY) possibility
    if(MacdCurrent<0 && MacdCurrent>SignalCurrent && MacdPrevious<SignalPrevious &&
       MathAbs(MacdCurrent)>(MACDOpenLevel*Point)) sBuy[i]=1;
       
    // check for short position (SELL) possibility
    if(MacdCurrent>0 && MacdCurrent<SignalCurrent && MacdPrevious>SignalPrevious && 
       MathAbs(MacdCurrent)>(MACDOpenLevel*Point)) sSell[i]=1;
       
    if(MacdCurrent>0 && MacdCurrent<SignalCurrent && MacdPrevious>SignalPrevious &&
       MathAbs(MacdCurrent)>(MACDCloseLevel*Point)) sCloseBuy[i]=1;
 
    if(MacdCurrent<0 && MacdCurrent>SignalCurrent &&  MacdPrevious<SignalPrevious && 
       MathAbs(MacdCurrent)>(MACDCloseLevel*Point)) sCloseSell[i]=1;
  }

你可以看见结果。运行次数从 19 上升至 51。虽然总利润变为亏损,表明这不是改善提醒质量的好办法。

测试设想

每个开发员都会有些未曾实现过的侵入想法。指标测试程序可帮助你快速显现并估算这些想法。如果这些想法看来是正确的,而且获得正面结果,则开发员可考虑编写指标或 Expert Advisor。在此情况下,指标测试程序的结果同时可作为开发的要求规范说明及最终例子,现成产品将根据此结果进行检查。

这是一个原始”系统“示例:使用简单的 MA 指标进行买入。买入条件 - MA 上升,卖出条件 - MA 下降。看吧,如何快速检查这个想法的盈利能力。这是提醒填充的块:

  // fill out alert arrays with values and count them
  for(i=DisplayBars;i>=0;i--) 
  {
    double m1=iMA(NULL,0,MAPeriod,0,MAMode,MAPrice,i+1);
    double m2=iMA(NULL,0,MAPeriod,0,MAMode,MAPrice,i+2);
    
    // open conditions are at the same time close conditions of an opposite order
    if(m2<=m1 && MathAbs(m1-m2)>0.2*Point) { sBuy[i]=1; sCloseSell[i]=1; sBuyCnt++; sSellCloseCnt++; }
    if(m2>=m1 && MathAbs(m1-m2)>0.2*Point) { sSell[i]=1; sCloseBuy[i]=1; sSellCnt++; sBuyCloseCnt++; }
  }

这是结果:

虽然不差,但却不是最好结果 - 因为我们曾幻想过巨额利润。现在让我们尝试缩短 MA 周期以提高获得盈利交易的可能性。通过将周期从 15 个柱缩短至 7 个,我们成功将平均日利润提高了近两倍:

剩下的就只有将盈利交易和亏损交易的关联系数定为至少 80% 比 20%。但你须自行完成这个任务。

总结

现在你有一个新的工具来对指标和交易提醒进行快速分析。这些工具自然不会取代测试程序或真实交易。所有通过该工具所得数据都是描述和估算性的。但是,运行便利性、可视化和获取结果的速度让我相信这款工具对任何开发员而言都有极大裨益。它可以帮助你快速检验交易思路,却不会花费太多时间精力。就某种程度而言,该工具可用于证明思路可行还是无望的。作为一个喜欢思考的研究人员或试验主义者,我认为它可成为精优指标参数的一个工具。