Introduction
Developers of order modification/closing algorithms suffer from an imperishable woe - how to compare results obtained by different methods? The mechanism of checking is well known - it is Strategy Tester. But how to make an EA to work equally for opening/closing orders? The article describes a tool that provides strong repetition of order openings that allows us to maintain a mathematically correct platform to compare the results of different algorithms for trailing stops and for exiting the market.
If you are debugging a complex EA that is supposed to independently calculate the time to enter the market, trailing stops, and exiting the market, then it is practically impossible to get a repeatable pattern to be compared to other ones. Imagine a situation where there is a rather long signal for opening an order. Ideally, the order will be opened. Then, if the direction chosen is correct and the price moves in the predicted direction, trailing stop starts to work.According to price fluctuations, a too closely placed stop can too early close an order that could otherwise "top up" the profits. If the opening signal is still valid at that time, the EA will close a new order. As a result, we will have to compare the results of one, "correct" order with the results of a few other orders that have been opened after early closures. To avoid this situation, the following will be suggested.
Problem Statement
- Order opening/closing point patterns are marked in the chart.
- Opening/closing times and trade direction (buy/sell) are saved in a file.
- Expert Advisor to read the prepared file and to strictly execute its commands is created.
Opening points must be set at market reversals - it's good that they are quite obvious on history. However, the closing points should be selected not when the price reaches the opposite reversal point, but after that. Don't forget that our task is to optimize trailing and exiting the market, so we should allow any algorithm, even improper one, to work "to its end". If it is still unable to fix the profit, we will see its losses that will become a signal to us to rework the algorithm.

Look at the picture above. The violet line shows ideally correct enter and exit. It can be used for calculation of the maximal profits we want to/can gain. However, for the purposes of trailing tests, we will use something similar to the blue line. It shows the nature of real trading: enter with some delay (like, we were waiting for reversal confirmation) and closing on the edge of break-even (like we were frightened of a strong reversal started and of that we could lose everything).
In trading performed "along the blue line", there are three potential points of triggering stops after trailings:
- Aggressive trailing at the minimal distance from the current price.
- Normal, "patient" trailing.
- Ideal trailing "sweating" profits up to the last drop.
Besides, a false triggering of the too "impatient" trailing may take place in the area round point 4.
Now that we know how to "mark" ideal areas, the only thing remaining is to make it as comfortable as possible.
Marking Tools
To facilitate the chart marking with ideal lines, let's prepare a set of scripts. Two scripts, TL_Buy and TL_Sell, will create marking lines for buying and selling operations, respectively. Script TL_Write will look through all created lines and save their characteristics in a file for Expert Advisor TL_Trade to work with them. One more script TL_Read will be able to read the created file, and to re-form all lines on its basis. This may become useful for correcting the available lines or for adding some new ones, or for removing the existing ones.
For read/write scripts to be able to work with their lines, we will name all the lines according certain rules:
- names of all ideal lines start with the same prefix (TL_). You can later use the prefix to select and delete the lines;
- the prefix is followed by one character that is the operation code: B-buy, S-sell;
- the operation code in the line name is followed by the line number to distinguish the lines from each other.
As a result, we should get in the chart the lines of, for example, the following names: TL_B1 TL_B2, TL_S3, etc.
Scripts that build lines are simply dropped on the chart, and the corresponding line appears in the dropping point. You can move its ends in such a way that they mark the necessary ideal "blue line" of trading. When attached to the chart, the read/write scripts request the name of the file to be saved and read. This will allow us to easily use different sets of lines, for example, for different currency pairs.
The code of the scripts is quite transparent and provided with all necessary comments, so I'll take the liberty to skip the description of their algorithms - you can see them from their codes.
#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); }
for(i=0;i<ObjectsTotal();i++)
{
if(StringFind(ObjectName(i),_prefix_)==0)
{
No=StrToInteger(StringSubstr(ObjectName(i),StringLen(_prefix_)+1));
if(MaxNo<No) MaxNo=No;
}
}
datetime t0=WindowTimeOnDropped(); double p0=WindowPriceOnDropped();
int width = 5*Period()*60;
double height = 20*MarketInfo(Symbol(),MODE_TICKSIZE);
string LineName = _prefix_+"B"+(MaxNo+1);
ObjectCreate(LineName,OBJ_TREND,0,t0-width,p0-height, t0+width,p0+height);
ObjectSet(LineName,OBJPROP_RAY,False);
ObjectSet(LineName,OBJPROP_WIDTH,2);
ObjectSet(LineName,OBJPROP_COLOR,Blue);
}
#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); }
for(i=0;i<ObjectsTotal();i++)
{
if(StringFind(ObjectName(i),_prefix_)==0)
{
No=StrToInteger(StringSubstr(ObjectName(i),StringLen(_prefix_)+1));
if(MaxNo<No) MaxNo=No;
}
}
datetime t0=WindowTimeOnDropped(); double p0=WindowPriceOnDropped();
int width = 5*Period()*60;
double height = 20*MarketInfo(Symbol(),MODE_TICKSIZE);
string LineName = _prefix_+"S"+(MaxNo+1);
ObjectCreate(LineName,OBJ_TREND,0,t0-width,p0+height, t0+width,p0-height);
ObjectSet(LineName,OBJPROP_RAY,False); ObjectSet(LineName,OBJPROP_WIDTH,2);
ObjectSet(LineName,OBJPROP_COLOR,Red);
}
#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,';');
for(i=0;i<ObjectsTotal();i++)
{
if(StringFind(ObjectName(i),_prefix_)==0)
{
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++;
Operation = StringSubstr(ObjectName(i),StringLen(_prefix_),1);
FileWrite(fh,Operation,TimeToStr(t1),DoubleToStr(p1,Digits),TimeToStr(t2),DoubleToStr(p2,Digits));
}
}
FileClose(fh);
MessageBox("Stored sections "+(LinesCNT)+" pcs.","Done", IDOK + MB_ICONINFORMATION);
}
#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); }
for(i=0;i<ObjectsTotal();i++) { if(StringFind(ObjectName(i),_prefix_)==0) { ObjectDelete(ObjectName(i)); i--; } }
while(true)
{
string Operation=FileReadString(fh);
if(FileIsEnding(fh)) break;
datetime t1=StrToTime(FileReadString(fh));
double p1=StrToDouble(FileReadString(fh));
datetime t2=StrToTime(FileReadString(fh));
double p2=StrToDouble(FileReadString(fh));
LinesCNT++;
string LineName = _prefix_+Operation+(LinesCNT);
ObjectCreate(LineName,OBJ_TREND,0,t1,p1, t2,p2);
ObjectSet(LineName,OBJPROP_RAY,False);
ObjectSet(LineName,OBJPROP_WIDTH,2);
if(Operation=="B") ObjectSet(LineName,OBJPROP_COLOR,Blue); else ObjectSet(LineName,OBJPROP_COLOR,Red);
}
FileClose(fh);
MessageBox("Read sections "+(LinesCNT)+" pcs.","Done", IDOK + MB_ICONINFORMATION);
}
#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++; }
}
}
File Positioning
Positioning files is a very important point. Using standard means, the working scripts can only create files in directory c:\Program Files\MetaTrader 4\experts\files. However, when testing Expert Advisors, the Tester has access to the folder of the same name located "inside its own directory", c:\Program Files\MetaTrader 4\tester\files.
This is why, after creation of files and before using them in the testing EA, you should independently copy them from c:\Program Files\MetaTrader 4\experts\files into c:\Program Files\MetaTrader 4\tester\files.
You will have to repeat this operation after re-creation of the file having changed something in the lines.
Testing EA
The testing EA doesn't produce any difficulties in its code. The following blocks are emphasized in it:
- order closing block when reaching the end of the pattern section;
- order opening block when reaching the start of the pattern section;
- block testing trailing stop and exiting the market.
Their work is quite obvious in the source code. Only a few comments must be given here:
- Since the lines can be created in no particular order, the entire set of lines will be tested at each "tick" of the Tester, in order to find those, on which it is necessary to open/close.
- Opening/closing times are written in the comment on the order in the internal format of time and date representation. This is necessary, first of all, for us not to open the same order several times if it is closed by trailing well in advance before its pattern line ends. Secondly, when checking open orders and taking their closing times from their comments, we will close the order exactly at the time when its control line is ended, since the closing time is written in the open order itself.
- Parameter ProcedTrailing enables/disables trailing processing. This will allow us to pass the EA without trailing at all to get the most pessimistic result in order to compare it to the obtained optimization results.
#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;
double SL;
int start()
{
int LinesCNT=0,i,ticket,pos; double p; datetime t; string s;
int fh=FileOpen(FileNameForRead,FILE_CSV|FILE_READ,';');
if(fh<0) { MessageBox("Error opening file \"" + FileNameForRead + "\"","ERROR", IDOK + MB_ICONERROR); return(1); }
while(true)
{
string Operation=FileReadString(fh);
if(FileIsEnding(fh)) break;
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);
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;
for(i=0;i<OrdersTotal();i++)
{
if(OrderSelect(i,SELECT_BY_POS,MODE_TRADES)==false) continue;
if(OrderComment()==MarkComent && TimeCurrent()>=t2)
{
if(OrderType()==OP_BUY) OrderClose(OrderTicket(),OrderLots(),Bid,3,Violet);
else OrderClose(OrderTicket(),OrderLots(),Ask,3,Violet);
}
}
bool OrderNotPresent=true;
if(t1<=TimeCurrent() && TimeCurrent()<t2)
{
for(i=0;i<OrdersTotal();i++)
{
if(OrderSelect(i,SELECT_BY_POS,MODE_TRADES)==false) continue;
if(OrderComment()==MarkComent) { OrderNotPresent=false; break; }
}
for(i=0;i<OrdersHistoryTotal() && OrderNotPresent;i++)
{
if(OrderSelect(i,SELECT_BY_POS,MODE_HISTORY)==false) continue;
pos = StringFind(OrderComment(),"[");
string CurOrderComment = StringSubstr(OrderComment(),0,pos);
if(CurOrderComment==MarkComent) { OrderNotPresent=false; break; }
}
if(OrderNotPresent)
{
if(Operation=="B")
{
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
{
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);
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);
}
How the System Works When "Fully Integrated"
To test the system, we used a microset of 14 lines. Below is the content of the pattern file named 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
This is how it looks on a chart:

Not the most efficient trading, but an ideal ground to test trailing. ;)
If we start the Tester with trailing stops disabled (ProcedTrailing=false), we will obtain such a poor result:

A simple optimization by trailing-stop size provides the optimal value of 95 points.

The optimal value producing the largest profit is in good agreement with the bar size statistics of the period under test: 95 points make 98% of all bars of this period, which is clearly seen in the chart of the ft.BarStat indicator.

As you can see, the optimized results look much more attractively:

They are seen in the chart even more clearly. Please note that all orders were opened precisely in the beginning of pattern lines!

Look at the very first section (March, 5). The profits were licked off by a down spike, but the general buy trend remained the same. If we tested trailing with a normal EA, it quite probably open a buying position that would retain up to the end of the second line (after March 17). In this case, we would obtain incomparable results. The profit, gained in the second case, would result from the successful repeated opening a new order, not to the trailing stop mechanism. However, the problem is not to gain the largest profit, but to get as efficient trailing stop mechanism as possible. This why it is very important to us that all orders are opened at the same time and not retain for too long. However, in this case, the increase in profits resulted from optimization will reflect the efficiency of trailing algorithm!
Conclusion
I hope very much that the discussion about this present article will not be drowned in speaking ill of something that I have NOT done - an interested reader will find a use for the proposed research algorithm even as it is represented here. The article describes a tool I have finally got down to. The idea long time inhabited my mind finally became clear and was realized as an MQL4 code. I didn't have time to conduct the research, for which this tool had been created. This is why the article doesn't provide comparative analysis of various trailing algorithms - I'm going to deal with them in the nearest future, so the second part of the article will be ready for publishing soon. Nevertheless, this articles seems to be useful for MQL4 Community as it is.
One more reason why I decided to publish the "naked" tool is that I'm a professional programmer and a novice trader. This means that I can independently develop my MQL4 code to the highest possible simplicity or complexity, but I can only develop trading tactics and trailing algorithms as efficiently as program the code only together with traders who are as professional in trading as I am in programming. This is why I'm highly interested in construction communications with my colleagues Forex "guild fellows" and will be glad to work in cooperation on bringing my tool to a state when it is possible to use it in real trading. If you are interested in cooperation with me, please contact me through my profile.
As a further development, we can test several trailing algorithms on one chart simultaneously. At the beginning of the pattern section, we will open a few orders (to the amount of algorithms under test), each being trailed according to its algorithm. Conceptually, we should obtain the picture representing several images superimposed over each other. Then we will be able to compare the results both by quantity (net profits) and by quality - we will be able to see which algorithm fulfills its functions more precisely. However, this will require from us to teach our EA to distinguish orders by their trailing alternative, but this problem is quite solvable.
By the way, these methods can be used for comparing both entering and exiting tactics! For this, you should just prepare a bit different set of lines and make sure that your order opening points according to your methods are located as close to the pattern openings in the chart as possible. However, this is a subject of a separate research and of a new article.
Translated from Russian by MetaQuotes Software Corp.
Original article: http://articles.mql4.com/ru/605