Download MetaTrader 5

How To Implement Your Own Optimization Criteria

17 August 2007, 10:02
Nikolai Shevchuk
3 185


From time to time we hear opinions about the necessity of extending the set of optimization criteria in MT4 tester. Still you can guess than no matter what criteria developers add, there will always be users and situations requiring other criteria. Can this problem be solved inside MQL4 and MetaTrader? Yes, it can. This article shows the implementation of using a custom optimization criterion on the example of the standard Expert Advisor Moving Average. The criterion here is the relation profit/drawdown.

Expert Advisor

Let us start with the optimization criterion. For its calculation we need to trace during testing the maximal balance and drawdown. In order not to depend on the logic of the Expert Advisor operation, we will add the existing code strings in the very beginning of the function start().

   if (AccountEquity() > MaxEqu) MaxEqu = AccountEquity();
   if (MaxEqu-AccountEquity() > MaxDD) MaxDD = MaxEqu-AccountEquity();

For processing the last tick they should be replicated in deinit(). After that we can calculate the value of the optimization criterion.

    Criterion = (AccountBalance()-StartBalance)/MaxDD;

Now we can start the main part - maintenance of the optimization process. We have one problem - MQL4 does not have the built-in means of determining the end of the optimization. The only way out, known to me, is the so called "optimization on a counter". The meaning is the following: the only changeable parameter of the Expert Advisor is the special external variable-counter. Still here is a serious consequence - we lose the possibility to change real parameters of the Expert Advisor in a standard way and should provide this ourselves. Another disadvantage is that the optimization cache from our friend turns into our enemy. But the end justifies the means, so let us continue.

Let us add external variables:

extern int Counter                    = 1;    // Counter of the tester's running
extern int TestsNumber                = 200;  // he check digit - total number of runs 
extern int MovingPeriodStepsNumber    = 20;   // Number of optimization steps for MovingPeriod 
extern int MovingShiftStepsNumber     = 10;   // Number of optimization steps for MovingShift
extern double MovingPeriodLow         = 150;  // Lower limit of the optimization range for MovingPeriod
extern double MovingShiftLow          = 1;    // Lower limit of the optimization range for MovingShift
extern double MovingPeriodStep        = 1;    // Optimization step for MovingPeriod 
extern double MovingShiftStep         = 1;    // Optimization step for MovingShift

First comes the counter. The next variable is the check one (and information). Then we assign number of steps, lower limit and optimization step for the two built-in variables of the Moving Average, intended for optimization. You can see some excessiveness: if we are going take the complete examination (and we are going to do it) the product of MovingPeriodStepsNumber and MovingShiftStepsNumber should be equal to TestsNumber.

After each test run the Expert Advisor fully terminates its work and the next run can be considered its reincarnation. We have two means of organizing "the genetical storage": global variables and a separate file. We will use both.

Let us modify the function init():

int init() {
  if (IsTesting() && TestsNumber > 0) {
    if (GlobalVariableCheck("FilePtr")==false || Counter == 1) {
      FilePtr = 0; 
    } else {
      FilePtr = GlobalVariableGet("FilePtr"); 
    MovingPeriod = MovingPeriodLow+((Counter-1)/MovingShiftStepsNumber)*MovingPeriodStep;
    MovingShift = MovingShiftLow+((Counter-1)%MovingShiftStepsNumber)*MovingShiftStep;    StartBalance = AccountBalance();
    MaxEqu = 0;
    MaxDD = 0;

Our addition is located inside the operation conditions only in the tester and at non-zero TestsNumber. Thus the task TestsNumber=0 will turn the Expert Advisor into a standard Moving Average. While we are discussing the process of optimization, we must use any possibility for the process speed-up. That is why the code starts with the provision of managing a through (through tester runs) file pointer using a global variable. Then we calculate values of changeable parameters and initialize variables used for optimization criterion calculation.

The main work should be done in the function deinit(). Upon the testing results we will save in a text file values of the optimization criterion, values of the optimized parameters and number of the test run. After the end of the optimization the results will be sorted according to the optimization criterion and saved in the same file. So, we need to process three situations: the first start, the last start and all others. Let us use the tester runs counter for their separation. Processing the first start:

    if (Counter == 1) {
// First run, create/initialize a datafile.
// Remember the position of the file pointer after writing in the global variable
      FilePtr = FileTell(h); 

The peculiarity of processing other starts is that the new data is added into the file:

    } else {
//  After the first start is processed, the data are added into the file
//  It is time to use the file pointer written in the global variable
      FilePtr = GlobalVariableGet("FilePtr");
      FileSeek(h,FilePtr, SEEK_SET);
//  Remember the file pointer position once again
      FilePtr = FileTell(h); 

Now let us process the last start:

      if (Counter == TestsNumber) {
// Returns the file pointer to the beginning       
// Read from the file the results of all testings
        int i = 0;
        while (i<TestsNumber && FileIsEnding(h)== false) {
          for (int j=0;j<4;j++) {
// Sort the array according to our optimization criterion
// Now let us arrange the results. Reopen the file        
        h=FileOpen("test.txt",FILE_CSV|FILE_WRITE,' ');
        FileWrite(h,"  Criterion","     MovingPeriod"," MovingShift"," Counter");
        for (i=0;i<TestsNumber;i++) {
          FileWrite(h,DoubleToStr(Data[i][0],10),"        ",Data[i][1],"        ",Data[i][2],"        ",Data[i][3]);

The array was preliminarily initialized as double Data[][4]. That is all. Let us clean up:


Compile, open the tester and select the Expert Advisor. Then open the property sheet of the Expert Advisor and check four positions:

- The product of MovingPeriodStepsNumber by MovingShiftStepsNumber MUST be equal to TestsNumber.
- Optimisation must be conducted ONLY for the Counter,
- The optimization range MUST be from 1 to TestsNumber with the step 1.
- Genetical algorithm must be disabled.

Start the optimization. After the end move to the folder [Meta Trader]\tester\files and view the result in the file test.txt. The author did it for EURUSD_H1 from the middle of 2004 on the opening prices and got the following results:

And now let us get back to the statement that the cache is our enemy. The fact is, when we take the testing results from the cache, the functions init() and deinit() are not started. As a result, at the restart of the optimization all or part of variants are unaccounted. Moreover, while the actual number of runs will be less than TestsNumber, the array Data will contain some zeros. The author suggests two ways of eliminating the "cache effect": recompilation of an Expert Advisor or closing/pausing/opening the tester window.
The cache interference can be detected by an independent counting of test runs. There are three commented insertions for the organization of such a counting using a special global variable, suggested in the attached EA code:

// Code of the independent counter
    if (GlobalVariableCheck("TestsCnt")==false || Counter == 1) {
      TestsCnt = 0; 
    } else {
      TestsCnt = GlobalVariableGet("TestsCnt"); 

// Code of the independent counter

// Code of the independent counter

And the last thing. An attentive reader must have noticed the fact that we can do without the variable FilePtr (and the accompanying global variable) - the data is written at the end of the file and read from the beginning. Then what is it for? The answer is the following: this Expert Advisor is intended for the demonstration of the optimization maintenance method. The method allows managing the operation on-the-fly with the results of previous testings. And here the trough file pointer can be rather useful, as well as the independent counter. As an example of tasks requiring working with previous results on-the-fly we can name the management of the out-of-sample testing and the implementation of one's own genetic algorithm.


The causes for such an interest to this problem were topics from the forum

Translated from Russian by MetaQuotes Software Corp.
Original article:

Attached files |
Last comments | Go to discussion (1)
MQL4 Comments
MQL4 Comments | 24 May 2008 at 22:06


I need my expert befor starting save the equity of the account and when the equity grown to the exampel( equity+1000$) close the all open postions.

I can not using account balance for this programin because account balance aways is more than account equity.

please hepl me about this brograming

Technical Analysis: Make the Impossible Possible! Technical Analysis: Make the Impossible Possible!

The article answers the question: Why can the impossible become possible where much suggests otherwise? Technical analysis reasoning.

Breakpoints in Tester: It's Possible! Breakpoints in Tester: It's Possible!

The article deals with breakpoint emulation when passed through Tester, debug information being displayed.

Principles of Time Transformation in Intraday Trading Principles of Time Transformation in Intraday Trading

This article contains the concept of operation time that allows to receive more even price flow. It also contains the code of the changed moving average with an allowance for this time transformation.

Universal Expert Advisor Template Universal Expert Advisor Template

The article will help newbies in trading to create flexibly adjustable Expert Advisors.