Visual Testing of the Profitability of Indicators and Alerts

Sergey Kravchuk | 16 February, 2009

What indicator of trading alerts or just the methods of their calculating to use is usually decided when testing EAs using these alerts. However, it is not always possible/necessary/reasonable to write an EA for each indicator. You can promptly calculate the profitability of trading on the alerts from other indicators, using a special indicator that collects their alerts itself and draws a picture of ideal trading with them. It can help you both make a visual estimate of the results obtained and quickly choose most optimal parameters.

Remember how many times, looking at a randomly found or long time searched for and finally found indicator, you wanted to know immediately what the result of trading by its alerts would be. The same situation occurs when you are offered a new trading system for which there are no EAs or indicators yet. Before starting a serious work and writing a working EA, you would like to estimate whether there is a healthy grain in the materials on offer.

Or you can have the following situation: There is an indicator with good alerts, but you are having a gut feeling that its parameters are not optimal, that you can clear it up, and you even know how you can do that. How can you test your idea quickly without being locked into a long and complicated coding?

Problem Statement

Let's think of what we have and what we want to have. Let's take a well-known ZigZag from the standard delivery of MetaTrader 4 as an example. Attach it to any currency pair's chart on any timeframe: Do you really want to trade with it? Everything is clear: you should sell on the upper kink and close your position by buying on the lower one.

Now here is a question: How much money would this trading gain for you? It is quite easy to get an answer to this question: Sum up all highs of the segments and recalculate considering the volume of the position in lots and the price of one point in the deposit currency. You can access to the buffer of ZigZag values by calling for function iCustom:

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

where i is the number of the bar for which you're going to obtain a value.

All you have to do now is to search in all values of the buffer and find all points with nonzero values - these are the points to build the ZigZag on. Well, you also have to sum up the heights of the segments. For descriptive reasons, you can even draw those lines and color them according to the colors of trades and their profitability. This task is quite easy and can be successfully assigned to an indicator.

A Universal Solution

Fur such indicator to be able to test any other indicator, you should ensure its universality. Since any indicator or trading system, in a general case, assumes that only 4 operations are made: BUY, SELL, BUY CLOSE, and SELL CLOSE, we should have a special array for each of them. It must be possible to fill this array from any source. It is the only thing you have to edit yourself by adding the code of filling alerts arrays (well, you should probably place some parameters in external variables, too).

Functionally, the indicator consists of the following blocks:

Realization

Below is the code of the indicator itself, which contains an example of feeding the data from indicator 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);
}
//——————————————————————————————————————————————————————————————————————————————————————————————————————————
The parameters of the indicator contain a special parameter, Optimizm. It sets the desired degree of optimism in calculations. If its value is below zero, it means that the calculations will be fully pessimistic: for BUY prices it will choose the High of the alert bar, for SELL prices - Low. This is how the worst possible results are modeled that we can obtain by those alerts. If its value is above zero, the most optimistic calculation is modeled: buying at lowest and selling at highest. If the value of this parameter is equal to zero, the neutral behavior is formed at which we open and close orders at the OPEN prices of bars - it is this situation that is usually realized when trading with EAs.

Analyzing Results

Now let's see results. Here is the result obtained at the objective analysis of zigzag.

See, the chart shows eight losing parts. Well, the zigzag "works" on best prices and in order to reconstruct it again we should set the parameter Optimizm = 1.

What the result will be, if we trade as badly as possible, can be seen if we set the parameter Optimizm = -1


A Little Digression

Dear traders,

Do not put a blame on me, but this indicator can be a pretty lash for you if it occurs in your management's skillful hands, who are usually not interested in details of trading tactics - they are interested only in the result. Uploading into a tester the algorithm of ZigZag alerts and setting Optimizm = 1, they will obtain the figures of that maximal profit that could ever be obtained from this part of chart and that they would like you to obtain. And if you always trade less than a half, this will make them think that you might be too indifferent to your duties.

On the other hand this tool can be a good defense from unfairly steep demands. You will have ground arguments that plans set to you by them are unreal and cannot be obtained even at the most favorable conditions.

We see, the use of the single Optimizm parameter allows to use the indicator for the potential estimation of an indicator of alerts tested in it. And even if the most optimistic calculation does not make profit, the tested indicator of alerts should be amended: you should find other parameters for it or in the worst case forget it for its hopelessness saving time on EA development and money of your deposit that could be lost for getting proofs that the indicator does not work. Thus our testing indicator allows saving not only time, but also money.

Analyzing Alerts of Trading Systems

Buffers of other indicators are not the only source for filling out alert arrays, on which our testing indicator operates. Somewhere in books or in the Internet depths you can find description of alerts of a trading system, for which there are no indicators or Expert Advisors. This is just a set of rules that can though be easily implemented in MQL4. If you can write the calculation of alerts for such a system and fill out alert buffers by them, the testing indicator will show you results that you can obtain using the system.

If there is a ready code for a system, you can select calculation of alerts, on which the EA operates, and construct "results of its operation" directly on the chart. Of course the same can be obtained even more precisely in MetaTrader 4 strategy tester, but the indicator will perform reconstructions quicker and reflect them more vividly than strict lines of a tester.

An important moment in this case is the possibility to join the chart of obtained results with other indicators. Vertical mark lines crossing additional indicators perhaps will tell you how they can be used for specifying main alerts or will detect places of its inadequate operation. Finally only by the hit-and-miss method we can choose such parameters of values that probably would suit us more than results of optimizer.

Let us take as an example the ready code of the standard MACD. This is how it should look like. Block of filling alert arrays, the contents of which are copied and pasted from the indicator text:

  // 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;
  }

And here is the result:

Note that lines of alert marks fully match with crosspoints of MACD charts. Perhaps looking at these joint charts you will decide what exactly should be changed in alerts. For example that MaCurrent and MaPrevious do not need checking. A little corrected code:

  // 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;
  }

You see what the result is. The number of operations has increased from 19 to 51. Though the total profit has turned into losses, which shows that this was not a good idea of improving the alert quality.

Testing Fantasies

Each developer surely has some intrusive thoughts that are never handled. The tester of indicators will help to quickly visualize and estimate them. If ideas appear to be correct and obtained results are positive, a developer can think of writing an indicator or Expert Advisor. In this case results of the indicator tester will be at the same time an illustration to the requirements specification for development and a final example, according to which the ready product will be checked.

Here is a primitive "system" as an example: buy using simple MA indicator. Buy condition - MA grows, sell condition - MA falls. See, how to quickly check the profitability of this idea. Here is the block of alert filling:

  // 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++; }
  }

And here are results:

Not bad, but it is not the best result - because we have fancied incredible profits. Now let's try to diminish MA period to increase the possibility of winning on profit trades. Diminishing the period from 15 to 7 bars, we managed to increase the average daily profit almost twice:

What is left is making the correlation of profitable and losing trades equal to at least 80% to 20%. But you will have to do it without my assistance.

Conclusion

Now you have one more tool for express analysis of indicators and trading alerts. Naturally they do not substitute for the tester or real trading. All data obtained using this tool are of a descriptive, estimation character. However, the convenience of operation, visualization and the speed of obtained results let me believe that this tool may be very useful to any developer. It helps to quickly check trading ideas, not wasting much time and effort. To a certain extent it can be a proof of an idea's being promising or hopeless. And a thinking investigator or experimentalist this can be a tool of delicate optimization of indicator parameters.