Русский 中文 Español Deutsch 日本語 Português
MetaEditor:Templates as a Spot to Stand On

MetaEditor:Templates as a Spot to Stand On

MetaTrader 4Examples | 29 May 2008, 07:45
6 815 10
MetaQuotes
MetaQuotes


Give me whereon to stand, and I will move the earth.
Archimedes

Introduction

As a programming language, MQL4 represents a great majority of uses for a code once written and debugged. These uses include:

  • the include .mqh files. You can store in these files all necessary functions and constants that can be added to your code using the #include directive;
  • function libraries that can be compiled as normal MQL4 programs and can be added to your code in the real time mode using the #import directive; and
  • custom indicators to perform economical calculations on time-series arrays. They are called in the real time mode using the function iCustom().

However, not all the developers know about and, therefore, not all of them use such a powerful mechanism for easy and reliable writing of an Expert Advisor as ready-made templates created using Expert Advisor Wizard. This article discloses some advantages of this tool.


What Is a Template?

What is a Template in terms of MetaEditor? Templates are those .mqt files that are stored in the folder of the same name in the terminal Root_directory_MetaEditor_4/experts/templates/.

In picture above, we can see 10 of such files. The basic ones are:
  • Expert.mqt - a template for creation of Expert Advisors;
  • Script.mqt - a template for creation of scripts;
  • Include.mqt - a template for creation of scripts;
  • indicator.mqt - a template for creation of indicators;
  • Library.mqt - a template for creation of a library.

The other templates (Alligator.mqt, etc.) are aimed at creation of indicators according to the indicator name given in the template name. For example, let's open the template Library.mqt with MetaEditor. For this, we should specify "All files (*.*)" in the "File Type" field:

We will see that the content of the file is not very large.

<expert>
type=LIBRARY_ADVISOR
</expert>
#header#
#property copyright "#copyright#"
#property link      "#link#"
 
//+------------------------------------------------------------------+
//| My function                                                      |
//+------------------------------------------------------------------+
// int MyCalculator(int value,int value2)
//   {
//    return(value+value2);
//   }
//+------------------------------------------------------------------+

The first three lines inform about what type of files this template belongs to:

<expert>
type=LIBRARY_ADVISOR
</expert>


Line type=LIBRARY_ADVISOR is obviously informing MetaEditor that this file is a library template. MetaEditor will use the necessary template according to your choice: EA, custom indicator, etc..



Then follows the substitutional macro #header# that, indeed, will be replaced with the name you chose for yourself when following the instructions of Expert Advisor Wizard.



For example, if you name your EA My_Best_Expert_Advisor, then the following lines will be formed instead of the #header# macro:

//+------------------------------------------------------------------+
//|                                       My_Best_Expert_Advisor.mq4 |
//|                      Copyright © 2007, MetaQuotes Software Corp. |
//|                                        https://www.metaquotes.net/|
//+------------------------------------------------------------------+

In this block of comments, we can see the information about EA name, its author, and the link to the website. All this data was entered in the corresponding fields of Expert Advisor Wizard. The next lines:

#property copyright "#copyright#"
#property link      "#link#"

contain macros #copyright# and #link# that are obviously corresponding to the fields in Expert Advisor Wizard.


How to Use That?

We are mostly interested in the possibility to insert into a template our own code to be automatically generated at each creation of an EA. For example, if you have some experiences in creation of scripts, you know that scripts are intended to be launched on the chart only once. It is sometimes necessary to specify some external parameters for the script algorithm, so it may become necessary to change these parameters at the moment of launching the script. Expert Advisor Wizard does not provide this possibility by default. However, this can be achieved using a directive to the compiler:

#property show_inputs

It is sufficient to add this line to any script code, and a window with parameters will appear. Let's exemplify how to do so that this line is automatically added by Expert Advisor Wizard into each newly created script. Let's open an EA template for this:

<expert>
type=SCRIPT_ADVISOR
</expert>
#header#
#property copyright "#copyright#"
#property link      "#link#"
 
#extern_variables#
//+------------------------------------------------------------------+
//| script program start function                                    |
//+------------------------------------------------------------------+
int start()
  {
//----
   
//----
   return(0);
  }
//+------------------------------------------------------------------+

and add only one line before the macro that substitutes external parameters (#extern_variables#). Below is the code we obtain:

<expert>
type=SCRIPT_ADVISOR
</expert>
#header#
#property copyright "#copyright#"
#property link      "#link#"
 
#property show_inputs
 
#extern_variables#
//+------------------------------------------------------------------+
//| script program start function                                    |
//+------------------------------------------------------------------+
int start()
  {
//----
   
//----
   return(0);
  }
//+------------------------------------------------------------------+

Then we will save the changes and start to write a script using the Expert Advisor Wizard. Let's name the script TestScript and, after having pressed on "Done"


obtain the result:
//+------------------------------------------------------------------+
//|                                                   TestScript.mq4 |
//|                      Copyright © 2007, MetaQuotes Software Corp. |
//|                                        https://www.metaquotes.net/|
//+------------------------------------------------------------------+
#property copyright "Copyright © 2007, MetaQuotes Software Corp."
#property link      "https://www.metaquotes.net/"
 
#property show_inputs
 
//+------------------------------------------------------------------+
//| script program start function                                    |
//+------------------------------------------------------------------+
int start()
  {
//----
   
//----
   return(0);
  }
//+------------------------------------------------------------------+

The new script differs from those made by the old template through only one line, but it helps us very much. Now we don't need to enter the line:

#property show_inputs

manually in each new script, it will be added automatically. Anyway, it will be much easier to comment it (if we don't need it) than to reenter it every time. Let's manually add only one line that contains a parameter named emptyParam.

//+------------------------------------------------------------------+
//|                                                   TestScript.mq4 |
//|                      Copyright © 2007, MetaQuotes Software Corp. |
//|                                        https://www.metaquotes.net/|
//+------------------------------------------------------------------+
#property copyright "Copyright © 2007, MetaQuotes Software Corp."
#property link      "https://www.metaquotes.net/"
 
#property show_inputs
 
extern int emptyParam=1;
//+------------------------------------------------------------------+
//| script program start function                                    |
//+------------------------------------------------------------------+
int start()
  {
//----
   
//----
   return(0);
  }
//+------------------------------------------------------------------+

Let's compile it and launch on the chart. The script doesn't run immediately now, but allows us to modify external parameters:



This is the simplest example of how to use templates. The idea consists in that we just add into the template the code lines that we need most at each creation of a new MQL4 program. Then these code lines will be automatically added every time when you use Expert Advisor Wizard.

Important: All templates are rewritten when you install MetaTrader 4 Client Terminal. So you should make copies of your templates yourself.

Where Does That Get Us?

The possibility to use ready-made templates really allows us to approach to writing an EA as accurately as possible. How do the newbies write their EAs? For example, let's consider a strategy based on intersection of two moving averages. Below is a simple technical requirement to create an EA on this strategy:

  1. Obtain the values of short-period and long-period moving averages.
  2. Check them for being intersected.
  3. If the short one intersects the long one from bottom to top, then buy, StopLoss=N points.
  4. If the short one intersects the long one from top to bottom, then sell, StopLoss=N points.
  5. If an open long position is available, close it upon the selling signal.
  6. If an open short position is available, close it upon the buying signal.

Well, the EA is ready and optimized. Now the idea comes to add a new parameter, for example, TakeProfit = S points. Or to replace the trade alerts block: use the values of Stochastic instead of those of moving averages intersection. And so on. For each new alternative for the EA, we will have to make some changes in the code, and it suddenly turns out to be not very easy. At a certain moment, we see that we have just to write a new EA with all those new functions. Of course, experienced Expert Advisors writers have already grown wise on this path and use their favorite practices and approaches. The techniques listed at the beginning of this article help them, namely (let's repeat):

  • include files;
  • libraries;
  • custom indicators.

However, they don't produce the maximum effects without using a template. Well, what is a template EA and what should it be to be effective? In my interpretation, a template EA is a universal prototype of a real EA, but without any specific features. In fact, it is a certain SuperClass that has some purely virtual functions (in terms of object-oriented programming), or interface (in Java terms). The template will describe what the EA does, but it does not describe how it does it. This is why, before creating a template, we should analyze the desired functionality. We should first create the structure of our EA.


Expert Advisor Structure

Well, what should an EA do? On the very first approximation, an Ea trades, it means it performs trading operations - makes trade requests for opening and closing of trade positions. Besides, an EA modifies the levels of StopLoss and TakeProfit of the existing open positions (if necessary), places or deletes pending orders, modifies the levels of StopLoss and TakeProfit for pending orders. A simple structure is beginning to emerge:

  1. Block of receiving trade signals.
  2. Opening a market order or placing a pending one.
  3. Closing open orders and deleting pending ones.

Thus, we have dissected the general idea of automated trading into three subproblems. Now we know that we should realize a function (block 1) that will make decisions of the current signal: buy, sell, or stay out of the market (no trade signal). Then (block 2), on the basis of the trade signal, we can open a position or place a pending order. Why "can", not "must"? Because the block of opening positions considers the trade signals, but it is not obliged to follow them and make trades. For example, we have some open positions already. Then the opening of new ones may expose the trade account to undue risks. Well, the last but not the least functionality of an EA (block 3) - closing open positions and deleting pending orders - is also independent. We can close positions by trade signal or from other considerations (position holding time, end of trading session, etc.). Thus, the division of the EA logic into three parts does not appear unsubstantiated.


Further Refinement

When we come to think of it, we can come to a conclusion that the above structure is not complete. Let's make further refinements in each block.

First, we can calculate trade signals for each incoming tick (a tick is the fact of price change) or perform these calculations only at opening of each new bar. At the moment, this can be unimportant, but it may become difficult to change the code in future if we don't provide for this in advance.

Secondly, we can also change the timeframe of these bars: though we are geared to that our EA will trade on one-hour charts at the moment, but we may want to change it in future in such a way that it will trade on 4-hour charts. We should consider this point, as well.

Well, thirdly, we should unify trade signals. We need a strict systematization. Since we have only three types of trade signals, then let our block display them to us using strictly described constants, best of all, in terms of MQL4:

  • OP_BUY - buying signal;
  • OP_SELL - selling signal;
  • OP_BALANCE - no signal, stay out of the market.

Thus, the first block will appear as follows:

/**
      1. Trade Signals . Receiving trade signals
      a) Every tick                       
      b) Every bar of the given timeframe     
      OP_BUY      - to buy
      OP_SELL     - to sell
      OP_BALANCE  - no signal
*/

Then we need a block for calculation of important levels to open new orders or modify already existing ones. This is also a functionality that doesn't depend on other parts of the code.

/**
      2. 
        a) Calculate SL and TP for each open order
        b) Calculate OpenPrice, SL, TP, and Lots for a new order
        c) Calculate OpenPrice, SL and TP for each pending order
*/

We don't show the calculations of position size, Lots, here, though it is also a parameter. However, it is possible that we may want to place the to-be-opened position size calculating function in a separate block that we can conventionally name Money Management block.

The next block is that modifying the order levels. It must also be independent, because we can modify both StopLoss and TakeProfit levels. Besides, you can modify them at every tick or only at opening of a new bar. If we provide this flexibility in the EA, it will be much easier for us to optimize it in future.

/**
      3. 
        a) Modification of each open order for every tick (SL and TP)        
        b) Modification of each pending order for every tick (OpenPrice, SL and TP)
        c) Modification of each open order for every new bar of the selected timeframe (SL and TP)
        d) Modification of each pending order for every new bar (OpenPrice, SL and TP)
*/

Please note that we can specify the modification timeframe at opening of a new bar in the same way as in block #1 (Receiving trade signals).

The next block is the block of order closing. We can close orders for two reasons: time and signal. In this case, we mean both the received trade signal directed oppositely to the open position and the change in conditions that imply the necessity to hold the position.

/**
      4. 
        a) Close open order by time
        b) Close open order by signal
*/

I separated the block of deleting pending orders from the position closing block because the priority of closing an open position is sometimes much higher (for example, the price starts to move very strongly against the open position and we need to close it as soon as possible, putting all other things for the future) than that of deleting a pending order (as a rule, pending orders are at a rather long distance from the current price).

/**
      5. 
        a) Delete pending order by time
        b) Delete pending order by condition
*/

Everything is absolutely clear in the block of deleting pending orders, so no additional comments are needed.

The last block is that of opening positions and placing pending orders.

/**
      6. 
        a) Open an order at the market price
        b) Place a pending order without expiry date and time
        c) Place a pending order with expiry date and time
*/

Strange, but it turns out to be the last. If we come to think of it, this EA structure is quite logical: we should first make all necessary preparations (close everything that must be closed and delete all redundant pending orders) without holding any problems for the future, and only then make new orders and actively intervene into the market.

/**
      7. 
        a) Open an order at the market price
        b) Place a pending order without expiry date and time
        c) Place a pending order with expiry date and time
*/

However, this block also contains some variations. You can open a position at the market price (buy at Ask price or sell at Bid price), you can place a pending order with the expiry date and time (the expired pending order will be deleted by the trade server) or without any expiration up to cancellation (until we delete it ourselves).

All the above results in the following EA template structure:

<expert>
  type=EXPERT_ADVISOR
</expert>
#header#
#property copyright "#copyright#"
#property link      "#link#"
 
#extern_variables#
 
//+------------------------------------------------------------------+
//| expert initialization function                                   |
//+------------------------------------------------------------------+
int init()
  {
//----
   
//----
   return(0);
  }
//+------------------------------------------------------------------+
//| expert deinitialization function                                 |
//+------------------------------------------------------------------+
int deinit()
  {
//----
   
//----
   return(0);
  }
//+------------------------------------------------------------------+
//| expert start function                                            |
//+------------------------------------------------------------------+
int start()
  {
//----
 
/**
      1. Trade Signals . Receiving trade signals
      a) Every tick                       
      b) Every bar of the given timeframe     
      OP_BUY      - to buy
      OP_SELL     - to sell
      OP_BALANCE  - no signal
*/
 
 
/**
      2. 
        a) Calculate SL and TP for each open order
        b) Calculate OpenPrice, SL, TP, and Lots for a new order
        c) Calculate OpenPrice, SL and TP for each pending order
*/
 
 
/**
      3. 
        a) Modification of each open order for every tick (SL and TP)        
        b) Modification of each pending order for every tick (OpenPrice, SL and TP)
        c) Modification of each open order for every new bar of the selected timeframe (SL and TP)
        d) Modification of each pending order for every new bar (OpenPrice, SL and TP)
*/
 
 
/**
      4. 
        a) Close open order by time
        b) Close open order by signal
*/
 
/**
      5. 
        a) Delete pending order by time
        b) Delete pending order by condition
*/
 
 
/**
      6. 
        a) Open an order at the market price
        b) Place a pending order without expiry date and time
        c) Place a pending order with expiry date and time
*/
 
//----
   return(0);
  }
//+------------------------------------------------------------------+

We can already save our template as it is now. This template can be useful, if even it doesn't contain any additional functions. It is a textual description of all blocks necessary to write an EA. Each Expert Advisors writer can add his or her vision of the structure, or even fully modify it. However, in any case, when creating a new EA using EA Wizard, you will see a textual description of the structure that will not allow you to forget what and in what sequence you should realize in your code. But we can go to even greater lengths.


Taking Up Realization

Now we can start to fill the EA template structure with generalized functions. These functions will not describe the final realization of necessary operations, but they will describe the standard interaction between the template structure and the user's final functions. Thus, we won't describe a specific function for receiving a trade signal, but we will describe where, in a generalized function, the user must insert the specific-function call.


Function of New Bar Appearance for the Predefined Timeframe

Since we have already decided that trade signals will be displayed in block #1 either at every new tick or at appearance of a new bar on a certain timeframe predefined by the user, we need, first of all, a function that would inform us about the event: "a new bar appeared at the timeframe". This function is simple and looks as follows:

//+----------------------------------------------------------------------+
//|  It returns the sign of a new bar appearance for the given timeframe |
//+----------------------------------------------------------------------+
bool isNewBar(int timeFrame)
   {
   bool res=false;
   
   // the array contains open time of the current (zero) bar
   // for 7 (seven) timeframes
   static datetime _sTime[7];  
   int i=6;
 
   switch (timeFrame) 
      {
      case 1  : i=0; break;
      case 5  : i=2; break;
      case 15 : i=3; break;
      case 30 : i=4; break;
      case 60 : i=5; break;
      case 240: break;
      case 1440:break;
      default:  timeFrame = 1440;
      }
//----
   if (_sTime[i]==0 || _sTime[i]!=iTime(Symbol(),timeFrame,0))
      {
      _sTime[i] = iTime(Symbol(),timeFrame,0);
      res=true;
      }
      
//----
   return(res);   
   }

Setting Timeframe and Checking for Correctness

Function isNewBar() takes the timeframe value as a parameter and as the amount of minutes. Let's introduce a new external variable, TradeSignalBarPeriod, that will indicate the necessary timeframe:

extern int  TradeSignalBarPeriod       = 0;

By default, it is equal to zero. This means that the EA will always track the new bars in trading on "its" timeframe, i.e., on the timeframe of the chart this EA is attached to when trading in the real-time mode, or equal to the timeframe, on which it is tested. User may introduce any predefined constant to choose a timeframe. However, there can be errors in that. So let's add a function that would check the value of the variable TradeSignalBarPeriod and, if necessary, corrects errors, if that value is wrong.

//+------------------------------------------------------------------+
//|  if the timeframe is specified incorrect, it returns zero        |
//+------------------------------------------------------------------+
int getCorrectTimeFrame(int period)
   {
   int res=period;
//----
   switch (period)
      {
      case PERIOD_D1:  break;  // allowed timeframe, don't do anything
      case PERIOD_H4:  break;  // allowed timeframe, don't do anything
      case PERIOD_H1:  break;  // allowed timeframe, don't do anything
      case PERIOD_M30: break;  // allowed timeframe, don't do anything
      case PERIOD_M15: break;  // allowed timeframe, don't do anything
      case PERIOD_M5:  break;  // allowed timeframe, don't do anything
      case PERIOD_M1:  break;  // allowed timeframe, don't do anything
      default: res=Period();   // incorrect timeframe, set a default one
      }
//----
   return(res);      
   }

If no errors occur, the function doesn't do anything. We will use it only once, at the very beginning of the EA initialization. So we will put its call into the function init():

//+------------------------------------------------------------------+
//| expert initialization function                                   |
//+------------------------------------------------------------------+
int init()
  {
//----
   TradeSignalBarPeriod=getCorrectTimeFrame(TradeSignalBarPeriod);   
   StartMessage(); // remind the EA settings
//----
   return(0);
  }

As you can see, besides it, there is a new user-defined function StartMessage() in block init(). It will be described below, I will only explain here what it is for. Expert Advisors gather great amounts of setup parameters that one can easily forget after a certain amount of time. Function StartMessage() is devoted to providing a minimum description of the EA parameters at its launching to work on a trading account and at backtesting. You can later read these parameters in the logs of terminal or of Tester. I suppose such a function to be useful in any EA, this is why I'm including it into the EA template, too.


Receiving a Trade Alert

Now we can turn to the block that produces trade alerts. However, before we go on, let's consider some more situations. Once having created an EA, we sometimes may wish to check how it works on certain days of the week. For example, we want to trade on Wednesdays only, so the EA must 'keep silent'. So we can provide this possibility to choose trading days in our EA in advance. Let's create an external integer variable TradeDay. If its value is equal to zero (Sunday=0, Monday=1, etc.), then we trade as usually. However, if it is other than zero, then we trade on the specified day of the week only.

Moreover, we may want to perform an inverse operation. Then we should specify the day of the week, on which we prohibit our EA to trade. Let's create one more logical external variable ReversDay. If it is equal to 'false', then the logic is normal (TradeDay points at the trading day). If it is 'true', then the conditions are reversed and TradeDay will point at the day of the week, on which we don't trade. For example, TradeDay=5 and ReversDay=true mean that we don't trade on Fridays (Friday=5).

Well, the last trick we should provide is the reverse of the trading system. For example, we may want to fully reverse our strategy in future (change buying for selling and selling for buying) and see what happens. If we provide this possibility from the beginning, then we will be able to use it some day. For this, it is sufficient just to introduce one more logical external variable ReversTradeSignal. If it is equal to 'false' (by default), then the initial strategy will work. However, if its value is set for 'true', then the system will be reversed. Thus, the external variables added look as follows:

// constant "off market"
#define OP_BALANCE 6
 
// trade day setting
extern int  TradeDay                   = 0; 
extern bool ReversDay                  = false;
 
// trading system reverse
extern bool ReversTradeSignal          = false;
 
//  trading frequency settings
extern bool TradeSignalEveryTick       = false;
extern int  TradeSignalBarPeriod       = 0;

I added the OP_BALANCE constant that means no trade signal available. Besides, there is one more logical variable, TradeSignalEveryTick. If it is true, it means receiving trade signals at every tick. As you can see, we haven't written any code line of our trading system, but we have already introduced some variables, the purposes of which can be forgotten after a certain amount of time. This is why we also wrote an informing function, StartMessage():

//+------------------------------------------------------------------+
//| It shows a message about EA settings                             |
//+------------------------------------------------------------------+
void StartMessage()
   {
   int i=0;
   string currString="";
   string array[3];
//----
   array[0]=StringConcatenate("Expert Advisor ",WindowExpertName()," has the following settings:");
 
   if (TradeSignalEveryTick)
      array[1]="1) trade signals are considered at every tick; ";
   else       
      array[1]=StringConcatenate("1)trade signals are considered at each bar with the period of ",
                        TradeSignalBarPeriod," minutes;");
 
   if (TradeDay==0)   // trade day is not specified
      array[2]="2)trading is allowed on any day of week; ";
   else 
      {
      if (ReversDay) //  no trading allowed on the specified day
         array[2]=StringConcatenate("2)trading is allowed on all days but day number ",TradeDay);
      else           //  trading is allowed only on the specified day of week
         array[2]=StringConcatenate("2)trading is allowed only on day number ",TradeDay);
      }
   for ( i=0;i<3;i++) currString=StringConcatenate(currString,"\n",array[i]);
   Comment(currString);
 
   for (i=2;i>=0;i--) Print(array[i]);
   
//----
   return;
   }

This function displays the EA name and the minimum information about its settings. You can add other lines in the array 'array[]' when writing a specific EA.

Now we are ready to create a function that will calculate a trade signal. The following settings are passed to this function:

  • calculate a trade signal at every tick or at opening of a new bar of the specific timeframe;
  • whether reverse a trade signal or not;
  • whether consider the day of week or not;
  • whether turn trading days into non-trading ones or not.
//+------------------------------------------------------------------+
//| receive a trade signal                                           |
//+------------------------------------------------------------------+
// tradeDay - day of week, on which we trade; if it is equal to zero, then we trade all days
//
// useReversTradeDay - if it is equal to 'true', then trading days become non-trading days
//
// everyTick  - if it is equal to zero, the function will calculate a signal for every tick
//
// period - if everyTick==false, then it is calculated as soon as a new bar with this period appears
int getTradeSignal(int tradeDay,          // it is usually equal to 0
                   bool useReversTradeDay,// it is usually equal to 'false'
                   bool everyTick,        // signal is calculated at every tick
                    int period            // working period for indicators and signals
                   )
   {
   int signal=OP_BALANCE;
//----
   if (tradeDay!=0)  // we will consider days of week
      {
      // day, on which we don't trade
      if (useReversTradeDay && tradeDay==DayOfWeek()) return(signal);
      
      // we trade on all days, except the day equal to tradeDay 
      if (!useReversTradeDay && tradeDay!=DayOfWeek()) return(signal);
 
      }
 
   if (!everyTick) // if we don't take trade signals at every tick
      { // nor we have any new bar on the timeframe of 'period' minutes,
      if (!isNewBar(period)) return(signal); // then exit with an empty signal
      }
 
// Fill function yourFunction() with your code/algorithm
   signal=yourFunction(period);
 
 
   if (signal!=OP_BUY && signal!=OP_SELL) signal = OP_BALANCE;
   
//----
   return(signal);   
   }

As you can see above, we have written a multiline binding around one code line:

   signal=yourFunction();

When we write our own strategy, we will write our own function (instead yourFunction()) that should return one of the following three values:

  • OP_BUY - buying
  • OP_SELL - selling
  • OP_BALANCE - no signal

Block Identifying "FRIEND or FOE "

One more stumbling point when writing an EA is detecting the tickets of the orders EA is going to work with. This problem is usually solved by means of cycles for(), in which a ticket is selected using the OrderSelect() function. However, you should keep in mind that we may need to detect "friendly" orders more than only once. In some cases, we should modify StopLoss. In other cases, we need to close market orders. In third cases, we may also need to delete some pending orders.

For the reasons of standard "friendly" orders processing, we decided to fill the array of "friendly" orders with all necessary parameters every time at the beginning of the start() function. For this, we introduce two arrays at a global level:

double Tickets[][9];// array to store information about "friendly" orders:
// Tickets[][0] - ticket number
// Tickets[][1] - order type
// Tickets[][2] - lots
// Tickets[][3] - open price
// Tickets[][4] - Stop Loss
// Tickets[][5] - TakeProfit
// Tickets[][6] - MagicNumber
// Tickets[][7] - expiration time
// Tickets[][8] - open time
// 
 
string CommentsTicket[1000][2];         //array to store symbols and comments on orders. 1000 lines would be enough
// CommentsTicket[][0] - symbol name
// CommentsTicket[][1] - comment on the order

The function that fills these arrays is rather simple:

//+------------------------------------------------------------------+
//| It prepares the array of "friendly" orders                       |
//+------------------------------------------------------------------+
void PrepareTickets(double & arrayTickets[][9], string & comm[][2],int MN)
   {
   int count=0;   // filling counter
   
   // let's make the array size large not to allocate memory for it every time
   ArrayResize(arrayTickets,20);
//----
   int total=OrdersTotal();
   for (int i=0;i<total;i++)
      {
      bool ourTicket=false;
      if (OrderSelect(i,SELECT_BY_POS))
         {
         if (!isOurOrder(OrderTicket()))
            {// if the special function has not detected the order as "friendly" one,
            // make usual checks
 
            // check for Symbol
            if (OrderSymbol()!= Symbol()) continue;
         
            //  other checks...
            // ....
         
            // last check, that for MagicNumber
            if (OrderMagicNumber()!=ExpertMagicNumber) continue;
         
            }
         // we haven't been stopped anywhere, so this is a "friendly" order
         //  fill out the array
         arrayTickets[count][0] = OrderTicket();
         arrayTickets[count][1] = OrderType();
         arrayTickets[count][2] = OrderLots();
         arrayTickets[count][3] = OrderOpenPrice();
         arrayTickets[count][4] = OrderStopLoss();
         arrayTickets[count][5] = OrderTakeProfit();
         arrayTickets[count][6] = OrderMagicNumber();
         arrayTickets[count][7] = OrderExpiration();
         arrayTickets[count][8] = OrderOpenTime();
 
         comm[count][0] = OrderSymbol();
         comm[count][1] = OrderComment();
         // let's increase the counter of filled "friendly" orders
         count++;
         }
      }
   
   // and now let's truncate the array sizes to the minimum essential ones 
   ArrayResize(arrayTickets,count);
   ArrayResize(comm,count);
 
//----
   return;   
   }

As an example, I gave a minimum description of criteria that allow us to distinguish "friednly" orders from "foe" ones in a generalized function using OrderMagicNumber() and OrderSymbol(). When searching in all orders in the cycle, we skip "foes" and fill the arrays with the data of "friends". To make the function more flexible, we can call one more user-defined function in it, where you can describe some other criteria to detect whether the order is "friend or foe". In this case, I added the check of global variables: If there is a global variable with our EA's name in the terminal and its value is equal to the ticket of a certain order, the order will also be considered as a "friend". This will be helpful, if, for example, the position has been opened manually, and now we want it to be processed by our EA.

//| It returns 'true', if a Global variable with this name is available|
//+--------------------------------------------------------------------+
bool isOurOrder(int ticket)
   {
   bool res=false;
   
   // we don't use global variables in test mode!
   if (IsTesting()) return(true);// immediately return the positive result
 
   int temp;
//----
   for (int i=0;i<5;i++)
      {
      if (GlobalVariableCheck(WindowExpertName()+"_ticket_"+i))
         {// there is such a global variable
            temp=GlobalVariableGet(WindowExpertName()+"_ticket_"+i);
            if (temp==ticket)
               { // found a GV with the value equal to 'ticket'
               res=true;  // so it is a "friendly" order
               break;
               }
         }
      }
//----
   return(res);   
   }

We have already done a large part of our preparations. Now the beginning of the start() function looks as follows:

 
//+------------------------------------------------------------------+
//| expert start function                                            |
//+------------------------------------------------------------------+
int start()
  {
   // always update the array size before the first use
   ArrayResize(Tickets,0);
   //ArrayResize(CommentsTicket,0);
   
   // obtain the arrays of "friendly" orders
   PrepareTickets(Tickets,CommentsTicket,ExpertMagicNumber);
   
//----
 
/**
      1. Trade Signals . Receiving trade signals
      a) Every tick                       (TradeSignalEveryTick=true)
      b) Each bar of the preset period    (TradeSignalBarPeriod=...)
      OP_BUY      - to buy
      OP_SELL     - to sell
      OP_BALANCE  - no signal
*/
 
 
   int trSignal=getTradeSignal(TradeDay,ReversDay,
                       TradeSignalEveryTick,TradeSignalBarPeriod);
/**
      2. 
        a) Calculate SL and TP for each open order
        b) Calculate OpenPrice, SL, TP, and Lots for a new order
        c) Calculate OpenPrice, SL and TP for each pending order
*/



Calculation of Stop Loss/Take Profit Levels for Existing Orders

Now we can go to the next stage. We have obtained a two-dimensional array of "friendly" orders, Tickets[][9]. Now let's calculate the new levels of SL and TP knowing the current characteristics of each order. The calculated new values should be stored somewhere, so let's create one more global array for this purpose:

double newSL_and_TP[][5];// array to store the new values of SL and TP
// newSL_and_TP[][0] - ticket number to be controlled
// newSL_and_TP[][1] - new values of SL
// newSL_and_TP[][2] - new values of TP
// newSL_and_TP[][3] - new Open price (for pending orders)
// newSL_and_TP[][4] - 0 for open orders and 1 for pending orders

Explanations may be needed only for the parameter of:

newSL_and_TP[][4] - 0 for open orders and 1 for pending orders

We will need this parameter to modify market and pending orders in separate functions. Well, let's create a function that takes array Ticketsp[][9] and the sign of system reverse and fills the new array newSL_and_TP[][5] with values (the scaling in the second dimension is given in parentheses). This function call will appear as follows:

   CalculateSL_and_TP(ReversTradeSignal,Tickets,newSL_and_TP);

The first parameter, ReversTradeSignal, can have the values of 'true' (i.e., the system is reversed) or 'false'. The second parameter is the array of "friendly" orders (if there are no orders, its size is equal to zero). The third parameter is an array to be filled out with this function. The function itself is given below:

//+------------------------------------------------------------------+
//|  It creates an array with the new values of SL and TP            |
//+------------------------------------------------------------------+
void CalculateSL_and_TP(bool ReversTrade,       // system reverse 
                      double arrayTickets[][9], // array of "friendly" orders
                      double &amp; arraySL_TP[][5]  // new values of SL, TP and openPrice
                      )
   {
   // first of all, zeroize the obtained array !!
   ArrayResize(arraySL_TP,0);
   // if the order array is empty, then exit   
   int    i,size=ArrayRange(arrayTickets,0);
   if (size==0) return;
//----
   int    sizeSL_TP=0;
   double lots, openPrice, SL,TP;
   double newSL,newTP, newOpen;
   double oldOpenPrice, oldSL,oldTP;    
 
   int type,ticket,oldType;
   for (i=0;i>size;i++)
      {
      ticket    = arrayTickets[i][0]; //ticket number
      type      = arrayTickets[i][1]; //order type
      lots      = arrayTickets[i][2]; //order volume
      openPrice = arrayTickets[i][3]; //order open price
      SL        = arrayTickets[i][4]; //Stop Loss
      TP        = arrayTickets[i][5]; //Take Profit
      
      if (ReversTrade) //  reverse all levels considering the spread
         {
         switch (type)
            {
            case OP_BUY      : oldType = OP_SELL     ; break;
            case OP_SELL     : oldType = OP_BUY      ; break;
            case OP_BUYLIMIT : oldType = OP_SELLSTOP ; break;
            case OP_SELLLIMIT: oldType = OP_BUYSTOP  ; break;
            case OP_BUYSTOP  : oldType = OP_SELLLIMIT; break;
            case OP_SELLSTOP : oldType = OP_BUYLIMIT ; break;
            default: Print("Invalid order type Type=",type," in function CalculateSL_and_TP()!!!");                           
            }
 
         double temp;
         int spread = MarketInfo(Symbol(),MODE_SPREAD);
         int digits = MarketInfo(Symbol(),MODE_DIGITS);
         if (type==OP_BUY || type==OP_BUYSTOP || type==OP_BUYLIMIT)  
            {
            temp = SL;
            if (TP!=0) oldSL = NormalizeDouble(TP+Point*spread,digits);
               else oldSL=0;
               
            if (SL!=0) oldTP = NormalizeDouble(temp+Point*spread,digits);
               else oldTP=0;
               
            oldOpenPrice = NormalizeDouble(openPrice - Point*spread,digits);
            }
         if (type==OP_SELL) 
            {
            temp = SL;
            if (TP!=0) oldSL = NormalizeDouble(TP-Point*spread,digits);
               else oldSL=0;
            
            if (SL!=0) oldTP = NormalizeDouble(temp-Point*spread,digits);
               else oldTP=0;
            
            oldOpenPrice = NormalizeDouble(openPrice + Point*spread,digits);
            }
         }
      else   // no system reverse
         {
         oldOpenPrice = openPrice;
         oldSL = SL;
         oldTP = TP;
         }
      
      newSL  = getNewSL(oldType,lots,oldOpenPrice,oldSL,oldTP);
      newTP  = getNewTP(oldType,lots,oldOpenPrice,oldSL,oldTP);
      
      // if it is a pending order, obtain a new open price
      if (type<OP_SELL) newOpen=getNewOpenPricePending(type,lots,openPrice,oldSL,oldTP);
      
      if (newSL<0 || newTP<0 || newOpen<0)
         {
         sizeSL_TP=ArrayRange(arraySL_TP,0);
         arraySL_TP[sizeSL_TP][0] = arrayTickets[i][0]; // ticket  
         if (newSL<0) arraySL_TP[sizeSL_TP][1] = newSL; // new level of SL
            else      arraySL_TP[sizeSL_TP][1] = arrayTickets[i][4];
            
         if (newTP<0) arraySL_TP[sizeSL_TP][2] = newTP; // new level of TP
            else      arraySL_TP[sizeSL_TP][2] = arrayTickets[i][5];
         if (newOpen<0)arraySL_TP[sizeSL_TP][3] = newOpen; // new open price
            else       arraySL_TP[sizeSL_TP][3] = arrayTickets[i][3];
         if (type<OP_SELL) arraySL_TP[sizeSL_TP][4]=1; // market order
            else           arraySL_TP[sizeSL_TP][4]=0; // market order
         }
      }         
//----
   return;
   }

First of all, let's set the zero size of the array that will contain the new values of SL and TP. Then we should get to know about the size of the array that contains "friendly" orders.

   int   size=ArrayRange(arrayTickets,0);
   // if the order array is empty, then exit   
   if (size==0) return;

If there are no orders, then there is nothing to do, so we exit with an output represented by array arraySL_TP[][] of zero size. The arrays of zero size imply no instructions to do anything with these arrays. Then we organize a cycle that views the values of all orders passed to be processed:

 for (i=0;i<size;i++)
      {
      ticket    = arrayTickets[i][0]; //ticket number
      type      = arrayTickets[i][1]; //order type
      lots      = arrayTickets[i][2]; //order volume
      openPrice = arrayTickets[i][3]; //order open price
      SL        = arrayTickets[i][4]; //Stop Loss
      TP        = arrayTickets[i][5]; //Take Profit
      
      oldOpenPrice = openPrice;
      oldSL = SL;
      oldTP = TP;
         
      
      newSL  = getNewSL(oldType,lots,oldOpenPrice,oldSL,oldTP);
      newTP  = getNewTP(oldType,lots,oldOpenPrice,oldSL,oldTP);
      
      // if it is a pending order, then receive a new open price
      if (type>OP_SELL) newOpen=getNewOpenPricePending(type,lots,openPrice,oldSL,oldTP);
      
      if (newSL>0 || newTP>0 || newOpen>0)
         {
         sizeSL_TP=ArrayRange(arraySL_TP,0);
         arraySL_TP[sizeSL_TP][0] = arrayTickets[i][0]; // ticket  
         if (newSL>0) arraySL_TP[sizeSL_TP][1] = newSL; // new level of SL
            else      arraySL_TP[sizeSL_TP][1] = arrayTickets[i][4];
            
         if (newTP>0) arraySL_TP[sizeSL_TP][2] = newTP; // new level of TP
            else      arraySL_TP[sizeSL_TP][2] = arrayTickets[i][5];
         if (newOpen>0)arraySL_TP[sizeSL_TP][3] = newOpen; // new open price
            else       arraySL_TP[sizeSL_TP][3] = arrayTickets[i][3];
         if (type>OP_SELL) arraySL_TP[sizeSL_TP][4]=1; // market order
            else           arraySL_TP[sizeSL_TP][4]=0; // market order
         }
      }

We calculate in it the current values of oldSL, oldTP and oldOpenPrice (because we can change the opening levels of pending orders) and pass these values as parameters into functions to calculate the new values of SL, TP and OpenPrice.

      newSL  = getNewSL(oldType,lots,oldOpenPrice,oldSL,oldTP);
      newTP  = getNewTP(oldType,lots,oldOpenPrice,oldSL,oldTP);
     
      // if it is a pending order, obtain a new open price
      if (type>OP_SELL) newOpen=getNewOpenPricePending(type,lots,openPrice,oldSL,oldTP);

then all we have to do is to increase by one the size of the array of new values SL, TP and OpenPrice, if at least one of these levels is not equal to zero (i.e., this order must be modified).

      if (newSL>0 || newTP>0 || newOpen>0)
         {
         sizeSL_TP=ArrayRange(arraySL_TP,0);
         arraySL_TP[sizeSL_TP][0] = arrayTickets[i][0]; // ticket   
         if (newSL>0) arraySL_TP[sizeSL_TP][1] = newSL; // new level of SL
            else      arraySL_TP[sizeSL_TP][1] = arrayTickets[i][4];
            
         if (newTP>0) arraySL_TP[sizeSL_TP][2] = newTP; // new level of TP
            else      arraySL_TP[sizeSL_TP][2] = arrayTickets[i][5];
         if (newOpen>0)arraySL_TP[sizeSL_TP][3] = newOpen; // new open price
            else       arraySL_TP[sizeSL_TP][3] = arrayTickets[i][3];
         if (type>OP_SELL) arraySL_TP[sizeSL_TP][4]=1; // market order
            else           arraySL_TP[sizeSL_TP][4]=0; // market order
         }
      }

Now we have to consider the possibility of reversing the trading system. What does a reverse mean? It means that buying is converted into selling, and vice versa. The open price changes by the value of spread, since we buy at Ask price and sell at Bid price. Besides, the levels of SL and TP transpose, again, considering the spread. We can code it in such a way that to consider everything:

      if (ReversTrade) //  reverse all levels considering spread
         {
         switch (type)
            {
            case OP_BUY      : oldType = OP_SELL     ; break;
            case OP_SELL     : oldType = OP_BUY      ; break;
            case OP_BUYLIMIT : oldType = OP_SELLSTOP ; break;
            case OP_SELLLIMIT: oldType = OP_BUYSTOP  ; break;
            case OP_BUYSTOP  : oldType = OP_SELLLIMIT; break;
            case OP_SELLSTOP : oldType = OP_BUYLIMIT ; break;
            default: Print("Invalid order type Type=",type," in function CalculateSL_and_TP()!!!");                           
            }
 
         double temp;
         int spread = MarketInfo(Symbol(),MODE_SPREAD);
         int digits = MarketInfo(Symbol(),MODE_DIGITS);
         if (type==OP_BUY || type==OP_BUYSTOP || type==OP_BUYLIMIT)  
            {
            temp = SL;
            if (TP!=0) oldSL = NormalizeDouble(TP+Point*spread,digits);
               else oldSL=0;
               
            if (SL!=0) oldTP = NormalizeDouble(temp+Point*spread,digits);
               else oldTP=0;
               
            oldOpenPrice = NormalizeDouble(openPrice - Point*spread,digits);
            }
         if (type==OP_SELL) 
            {
            temp = SL;
            if (TP!=0) oldSL = NormalizeDouble(TP-Point*spread,digits);
               else oldSL=0;
            
            if (SL!=0) oldTP = NormalizeDouble(temp-Point*spread,digits);
               else oldTP=0;
            
            oldOpenPrice = NormalizeDouble(openPrice + Point*spread,digits);
            }
         }
      else   // no system reverse
         {
         oldOpenPrice = openPrice;
         oldSL = SL;
         oldTP = TP;
         }

I don't provide here the full code of changes made to function CalculateSL_and_TP() because this function is very large. You can see it in the attached file Template_EA.mqt. So we can say that we have managed to solve the task of calculating new values for Stop Loss, Take Profit and OpenPrice. Now we have to create functions to be called from this block of calculations. Those are empty functions to be filled in when creating a specific EA, we will name them 'form functions'.


Form Functions

Below are the functions:

//+------------------------------------------------------------------+
//|  It calculates the level of Stop Loss                            |
//+------------------------------------------------------------------+
double getNewSL(int    type,      // type of the order, for which we calculate it
                double lots,      // volume, it can be useful
                double openPrice, // open price
                double stopLoss,  // current level of Stop Loss
                double takeProfit // current level of Take Profit
                )
   {
   double res=-1;
//----
//  here is the code of calculations for Stop Loss
//----
   return(res);   
   }
 
//+------------------------------------------------------------------+
//|  It calculates the level of  Take Profit                         |
//+------------------------------------------------------------------+
double getNewTP(int    type,      // type of the order, for which we calculate it
                double lots,      // volume, it can be useful
                double openPrice, // open price
                double stopLoss,  // current level of Stop Loss
                double takeProfit // current level of Take Profit
                )
   {
   double res=-1;
//----
//  here is the code of calculations for Take Profit
//----
   return(res);   
   }
 
//+------------------------------------------------------------------+
//|  It calculates the new open price for a pending order            |
//+------------------------------------------------------------------+
double getNewOpenPricePending(int    type,      // type of the order, for which we calculate it
                              double lots,      // volume, it can be useful
                              double openPrice, // open price
                              double stopLoss,  // current level of Stop Loss
                              double takeProfit // current level of Take Profit
                              )
   {
   double res=-1;
//----
//  here is the code of calculations for open price 
 
//----
   return(res);   
   }

These three functions are very similar to each other, each gets the same set of inputs and returns a value of the double type in the variable res. This variable is immediately initialized with a negative value and, if we don't insert our own code for calculations, it is this negative value that will be returned. This implies the absence of the calculated level, since price is always positive.

Besides, we can also write here a form for yourFunction() to be called from the block of calculating trade signals from function getTradeSignal():

//+------------------------------------------------------------------+
//|  Function to produce trade signals                               |
//+------------------------------------------------------------------+
int yourFunction(int workPeriod)
   {
   bool res=OP_BALANCE;
//----
//  (there must be a code producing trade signals considering timeframe workPeriod here)
//----
   return (res);   
   }

This function gets workPeriod - a period that we must use when calling custom or standard indicators. The return variable res is initialized with the value of OP_BALANCE. If we don't insert our own code that will change the value of this variable, it is this value that the function will return. This will be received as no trade signal.

There are no more custom function forms in this template. Having hunted down the technologies of their usage, you will be able to insert all necessary forms in appropriate locations of the code.


Calculation of Parameters for a New Order

Now we have to obtain the values of SL, TP, volume and other parameters needed to place a new market or pending order. For this, let's create variables, the names of which speak for themselves.

   double marketLots,marketSL,marketTP;
   int marketType, pendingType;
   string marketComment, pendingComment;
   double pendingOpenPrice, pendingLots, pendingSL, pendingTP;
 
   CalculateNewMarketValues(trSignal, marketType, marketLots,marketSL,marketTP,marketComment);
   CalculateNewPendingValues(trSignal, pendingType, pendingOpenPrice, pendingLots, pendingSL, pendingTP, pendingComment);

These variables will obtain their values in two functions: one function calculates the values for a market order, another function does the same for a pending order. This separation will allow us to write our code in an easier way and to change it later. First of all, let's consider the function CalculateNewMarketValues():

//+------------------------------------------------------------------+
//|  It calculates the data for opening a new order                  |
//+------------------------------------------------------------------+
void CalculateNewMarketValues(int    trSignal,
                              int    & marketType,
                              double & marketLots, 
                              double & marketSL,
                              double & marketTP,
                              string & marketcomment
                              )
   {
   // if there is no trade signal, exit
   //Print("CalculateNewMarketValues()  trSignal=",trSignal);
   if (trSignal==OP_BALANCE) return;
 
   marketType    =-1; // this means that we won't open
   marketLots    = 0;
   marketSL      = 0;
   marketTP      = 0;
   marketcomment = "";
 
 
//----
   //  insert your own code to calculate all parameters
//----
   return;   
   }

The function receives the current trade signal, trSignal, and calculates all return parameters on its basis.

Please note that here and anywhere else, all parameters must be initialized with safe values.

If we don't insert our own code, the variable marketType will retain the value of -1 (minus one) that means no intention to open an order. It should be reminded here that the constants of trade operations have non-negative values. The function for calculation of opening prarameters for a pending order is very much the same:

//+------------------------------------------------------------------+
//|  It calculates the data for placing a pending order              |
//+------------------------------------------------------------------+
void CalculateNewPendingValues(int    trSignal, 
                               int    & pendingType, 
                               double & pendingOpenPrice, 
                               double & pendingLots, 
                               double & pendingSL, 
                               double & pendingTP, 
                               string & pendingComment)
   {
   // if there is no trade signal, exit
   if (trSignal==OP_BALANCE) return;
 
   pendingType      = -1; 
   pendingOpenPrice = 0; 
   pendingLots      = 0; 
   pendingSL        = 0; 
   pendingTP        = 0; 
   pendingComment   = 0;
//----
   //insert your own code to calculate all parameters
//----
   return;
   }

The only difference is that it calculates one more parameter - the pending order open price.


Modification of Orders

The next operation in the work of an EA is modification of "friendly" orders. Two separate functions are used for this purpose.

   ModifyMarkets(ReversTradeSignal,ModifyMarketOrderEveryTick,ModifyMarketBarPeriod,newSL_and_TP);
   ModifyPendings(ReversTradeSignal,ModifyPendingEveryTick,ModifyPendingBarPeriod,newSL_and_TP);

They are very much the same, each takes the following parameters as inputs:

  • ReversTradeSignal - sign of that the trade system is reversed;
  • ModifyMarketOrderEveryTick or ModifyPendingEveryTick - sign of order modification at every tick;
  • ModifyMarketBarPeriod or ModifyPendingBarPeriod - timeframe in minutes, on which the modification will be performed, if there is no need to change price levels at every tick;
  • array newSL_and_TP[][5] that contains the tickets of the orders to be modified, and the new values of SL, TP (and OpenPrice, in case of pending orders).

Let's consider the first function - ModifyMarkets():

//+------------------------------------------------------------------+
//|  Modifications of market orders                                  |
//+------------------------------------------------------------------+
void  ModifyMarkets(bool Revers,
                    bool ModifyEveryTick,
                    int  ModifyBarPeriod,
                    double newSL_and_TP[][])
   {
   int i,type,ticket,size=ArrayRange(newSL_and_TP,0);
   if (size==0) return;  // nothing to modify - exit
 
   bool res;
//----
   if (!ModifyEveryTick )// if every-tick modifying is prohibited
      {   
      if (!isNewBar(ModifyBarPeriod)) return;   // no new bar appeared
      }
 
   if (!Revers) // direct working
      {
      for (i=0;i<size;i++)
         {
         type=newSL_and_TP[i][4];
         if (type!=0) continue; // it is not a market order - skip it 
         ticket=newSL_and_TP[i][0];
         res=OrderModify(ticket,newSL_and_TP[i][3],newSL_and_TP[i][1],newSL_and_TP[i][2],0);
         if (!res)// modification failed
            {
            Print("Failed modifying order #",ticket,". Error ",GetLastError());
            // further processing of the situation
            }
         }
       }  
   else  // trading system is reversed, transpose SL and TP 
      {
      for (i=0;i<size;i++)
         {
         type=newSL_and_TP[i][4];
         if (type!=0) continue; // it is not a market order - skip it 
         ticket=newSL_and_TP[i][0];
         res=OrderModify(ticket,newSL_and_TP[i][3],newSL_and_TP[i][2],newSL_and_TP[i][1],0);
         if (!res)// modification failed
            {
            Print("Failed modifying order #",ticket,". Error ",GetLastError());
            // further processing of the situation
            }
         }
      }         
 
//----
   return;   
   }

The first check is already a standard - it's a check for zero size of the array newSL_and_TP[][] for modification. The second check: first check for the necessity of modification at every tick. If there is no need to do it (ModifyEveryTick=false), then check for appearance of a new bar on the timeframe of ModifyBarPeriod minutes. If it hasn't passed the check, then exit and do nothing:

   if (!ModifyEveryTick )// if every-tick modifying is prohibited
      {   
      if (!isNewBar(ModifyBarPeriod)) return;   // no new bar appeared
      }

However, if the preliminary checks have been passed successfully, we can start to modify orders. At the same time, we mustn't forget to consider two ways of system working: direct and reversed.

   if (!Revers) // direct working
      {
      for (i=0;i<size;i++)
         {
         //  order modification code for a direct system
         }
       }  
   else  // trading system is reversed, transpose SL and TP 
      {
      for (i=0;i<size;i++)
         {
         //  order modification code for a reversed system
         }
      }

The only difference between them is that the values of newSL_and_TP[i][1] and newSL_and_TP[i][2] (StopLoss and TakeProfit) are transposed in the function OrderSend().

The function used to modify pending orders is very much the same, but we added to it the receiving of pending order open price:

//+------------------------------------------------------------------+
//|  Modifications of market orders                                  |
//+------------------------------------------------------------------+
void  ModifyPendings(bool Revers,
                    bool ModifyEveryTick,
                    int  ModifyBarPeriod,
                    double newSL_and_TP[][])
   {
   int i,type,ticket,size=ArrayRange(newSL_and_TP,0);
   if (size==0) return;  // nothing to modify - exit
 
   bool res;
//----
   if (!ModifyEveryTick )// if every-tick modifying is prohibited
      {   
      if (!isNewBar(ModifyBarPeriod)) return;   // no new bar appeared
      }
 
   if (!Revers) // direct working
      {
      for (i=0;i<size;i++)
         {
         type=newSL_and_TP[i][4];
         if (type==0) continue; // it is not a pending order - skip it
         ticket=newSL_and_TP[i][0];
         res=OrderModify(ticket,newSL_and_TP[i][3],newSL_and_TP[i][1],newSL_and_TP[i][2],0);
         if (!res)// modification failed
            {
            Print("Failed modifying order #",ticket,". Error ",GetLastError());
            // further processing of the situation
            }
         }
       }  
   else  // trading system is reversed, transpose SL and TP 
      {
      for (i=0;i<size;i++)
         {
         type=newSL_and_TP[i][4];
         if (type==0) continue; // it is not a pending order - skip it 
         ticket=newSL_and_TP[i][0];
         res=OrderModify(ticket,newSL_and_TP[i][3],newSL_and_TP[i][2],newSL_and_TP[i][1],0);
         if (!res)// modification failed
            {
            Print("Failed modifying order #",ticket,". Error ",GetLastError());
            // further processing of the situation
            }
         }
      }         
 
//----
   return;   
   }

I would also like to draw your attention to the fact that the order type is checked in both functions:


         type=newSL_and_TP[i][4];

and, according to the value of the variable 'type' (0 or 1), the program decides on whether to process this ticket or skip it. This is all about the functions used to modify orders.


Closing a Market Order

Now we have to code the closing of market orders. For this, we will use two arrays that contain information about orders to be closed, and two functions that work with these arrays:

/**
      4. 
        a) Close an open order by time
        b) Close an open order by signal
*/
   // tickets of orders to be closed
   int ticketsToClose[][2];
   double lotsToClose[]; 
   
   // zeroize array sizes
   ArrayResize(ticketsToClose,0);
   ArrayResize(lotsToClose,0);
   
   // prepare a ticket array for closing
   if (trSignal!=OP_BALANCE) P
        repareTicketsToClose(trSignal,ReversTradeSignal,ticketsToClose,lotsToClose,Tickets);
   
   // close the specified orders
   CloseMarketOrders(ticketsToClose,lotsToClose);

Array ticketsToClose[][2] stores the values of the ticket and type of the order to be closed, array lotsToClose[] contains information about the amount of lots to be closed for each position to be closed. Function PrepareTicketsToClose() receives as inputs the array of orders, Tickets[][], and the value of the current trade signal. A buying trade signal may be also an instruction to close selling orders. The function PrepareTicketsToClose() itself was written in a smaller volume.

//+------------------------------------------------------------------+
//|  Prepare an array of tickets to close orders                     |
//+------------------------------------------------------------------+
void PrepareTicketsToClose(int signal, bool Revers, int & ticketsClose[][2], 
                                   double & lots[],double arrayTickets[][9])
   {
   int size=ArrayRange(arrayTickets,0);
//----
   if (size==0) return;
 
   int i,type,ticket,closeSize;
   for (i=0;i<size;i++)
      {
      type=arrayTickets[i][1];
      // if it is not a market order, then skip it
      if (type>OP_SELL) continue;
 
      if (Revers) // reverse the order type
         {
         if (type==OP_BUY) type=OP_SELL; else type=OP_BUY;
         }
      
      // here we will decide the fate of each open order
      //  whether we retain it in the market or add to the array of orders to be closed
      if (type==OP_BUY)
         {
         //  
         // code that allows to retain Buy
         
         // as an example
         if (signal==OP_BUY) continue;
         }
      
      if (type==OP_SELL)
         {
         //  
         // code that allows to retain Sell
         
         // as an example
         if (signal==OP_SELL) continue;
         }
 
      closeSize=ArrayRange(ticketsClose,0);
      ArrayResize(ticketsClose,closeSize+1);
      ArrayResize(lots,closeSize+1);
      ticketsClose[closeSize][0] = arrayTickets[i][0]; // ticket #
      ticketsClose[closeSize][1] = arrayTickets[i][1]; // order type
      Print("arrayTickets[i][0]=",arrayTickets[i][0],"   ticketsClose[closeSize][0]=",ticketsClose[closeSize][0]);
      
      // here we will specify the amount of lots to be closed
      lots[closeSize] = arrayTickets[i][2]; // volume to be closed
      // they can be closed partially, then you should rewrite the code line above
      }
//----
   return;   
   }

You should add your own conditions to it that would include one order or another into the list of those to be closed. If the size of input array arrayTickets is zero (i.e., we have no orders to be processed), then exit the function early, as usual.

Function CloseMarketOrders() does not represent any difficulties:

//+------------------------------------------------------------------+
//|  It closes orders with the given tickets                         |
//+------------------------------------------------------------------+
void CloseMarketOrders(int ticketsArray[][2], double lotsArray[])
   {  
//----
   int i,size=ArrayRange(ticketsArray,0);
   if (size==0) return;
   
   int ticket,type;
   double lots;
   bool res;
   
   int total=OrdersTotal(); 
   Print("",size," orders should be closed, orders opened:",total);
   
   for (i=0;i<size;i++)
      {
      ticket = ticketsArray[i][0];
      type   = ticketsArray[i][1];
      lots   = lotsArray[i];
      Print("Close order #",ticket," type=",type," ",lots," lots" );
      Print(" ");
      RefreshRates(); // just in case, update the data of the market environment
 
      // buy closing block
      if (type==OP_BUY)
         {
         res = OrderClose(ticket,lots,Bid,Slippage,Orange);
         if (!res)
            {
            Print("Failed closing Buy order #",ticket,"!  Error #",GetLastError());
            //  further error processing, code independently
            }
         }
 
      //  sell closing block
      if (type==OP_SELL)
         {
         res = OrderClose(ticket,lots,Ask,Slippage,Orange);
         if (!res)
            {
            Print("Failed closing Sell order #",ticket,"!  Error #",GetLastError());
            //  further error processing, code independently
            }
         }
 
      } 
//----
   return;
   }

In the cycle, the array for closing is gone around, the market environment is updated with function RefreshRates(), and the program tries to close the order at the price that corresponds to the order type. Errors at closing are analyzed to the minimum extent, you should add your own algorithm to process the situation.


Deletion of Pending Orders

The operation of deleting pending orders is very much the same as that of closing market orders. It only doesn't contain an array of volumes, since we only need to know the ticket of a pending order to delete it:

/**
      5. 
        a) Delete a pending order by time
        b) Delete a pending order by condition
*/
   // tickets of orders to be deleted
   int ticketsToDelete[];
 
   // prepare a ticket array for orders to be deleted
   if (trSignal!=OP_BALANCE) 
        PrepareTicketsToDelete(trSignal,ReversTradeSignal,ticketsToDelete,Tickets);
 
   // delete the specified orders
   DeletePendingOrders(ticketsToDelete);

Correspondingly, the syntax of this block of functions is very similar, so I give it here without further explanations:

//+------------------------------------------------------------------+
//|  Prepare an array of tickets to delete pending orders            |
//+------------------------------------------------------------------+
void PrepareTicketsToDelete(int signal, bool Revers, int & ticketsDelete[],double arrayTickets[][9])
   {
   int size=ArrayRange(arrayTickets,0);
//----
   if (size==0) return;
   ArrayResize(ticketsDelete,0);
 
   int i,type,ticket,deleteSize;
   for (i=0;i<size;i++)
      {
      type=arrayTickets[i][1];
      // if it is not a pending order, then skip
      if (type<=OP_SELL) continue;
      
      if (Revers) // reverse the order type
         {
         switch (type)
            {
            case OP_BUYLIMIT : type = OP_SELLSTOP; break;
            case OP_SELLLIMIT: type = OP_BUYSTOP  ; break;
            case OP_BUYSTOP  : type = OP_SELLLIMIT; break;
            case OP_SELLSTOP : type = OP_BUYLIMIT ; break;
            }
         }
 
      // here we will decide the fate of each pending order
      //  whether we retain it or add to the array of orders to be deleted
      //  here we will give an example of a buying signal retaining 
      // pending orders OP_BUYLIMIT and OP_BUYSTOP
      // the same for selling signals
      if (type==OP_BUYLIMIT || OP_BUYSTOP)
         {
         //  
         // code that allows to retain Buy
         // as an example
         if (signal==OP_BUY) continue;
         }
      
      if (type==OP_SELLLIMIT || OP_SELLSTOP)
         {
         //  
         // code that allows to retain Sell
         // as an example
         if (signal==OP_SELL) continue;
         }
 
      deleteSize=ArraySize(ticketsDelete);
      ArrayResize(ticketsDelete,deleteSize+1);
      ticketsDelete[deleteSize] = arrayTickets[i][0];
      }
//----
   return;   
   }


Opening a Position by Market and Placing a Pending Order

Thus, there are two last operations left. We have already received a trade signal, prepared the list of orders, modified, closed and deleted all necessary orders. Now we can open a position by market, if it is reasonable, or place a pending order.

/**
      6. 
        a) Open an order by market
        b) Place a pending order without expiration
        c) Place a pending order with expiration
*/
 
   if (trSignal!=OP_BALANCE) 
           OpenMarketOrder(ReversTradeSignal,marketType,marketLots,marketSL,marketTP,marketComment);

   if (trSignal!=OP_BALANCE) 
           SendPendingOrder(ReversTradeSignal,pendingType,pendingOpenPrice, pendingLots, pendingSL, pendingTP,pendingComment);

The first function, OpenMarketOrder(), gets all necessary inputs including the sign of system reverse.


///+------------------------------------------------------------------+
//|  It opens a position by market                                    |
//+-------------------------------------------------------------------+
void OpenMarketOrder(bool   reversTrade,// sign of system reverse
                     int    Type,       // order type - OP_BUY or OP_SELL
                     double lots,       // volume of the position to be opened
                     double SL,         // level of StopLoss
                     double TP,         // level of TakeProfit
                     string comment)    // comment on the order
   {
 
   //Print("Open order Type=",Type,"  lots=",lots,"  SL=",SL,"TP=",TP,
        " comment=",comment,"  ExpertMagicNumber=",ExpertMagicNumber);
   int openType;
   
   if (reversTrade)                       // reverse the signals
      {
      double temp;
      int spread = MarketInfo(Symbol(),MODE_SPREAD);
      int digits = MarketInfo(Symbol(),MODE_DIGITS);
      if (Type==OP_BUY)  
         {
         openType = OP_SELL; // Buy will become Sell
         temp = SL;
         if (TP!=0) SL = NormalizeDouble(TP+Point*spread,digits);
            else SL=0;
         if (temp!=0) TP = NormalizeDouble(temp+Point*spread,digits);
            else TP=0;
         }
      if (Type==OP_SELL) 
         {
         openType = OP_BUY;  // Sell will become Buy
         temp = SL;
         if (TP!=0) SL = NormalizeDouble(TP-Point*spread,digits);
            else SL=0;
         if (temp!=0) TP = NormalizeDouble(temp-Point*spread,digits);
            else TP=0;
         }
      }
   else
      {
      openType=Type;
      }   
//----
 
   if (lots==0) return;
   
   RefreshRates();
   double price;
   color Color;
   if (openType==OP_BUY) 
      {
      price = Ask;
      Color = Blue; 
      }
   if (openType==OP_SELL) 
      {
      price=Bid;
      Color = Red; 
      }
   bool ticket = OrderSend(Symbol(),openType,lots,price,Slippage,SL,TP,comment,ExpertMagicNumber,0,Color); 
   if (ticket>0)
      {
      Print("Failed to open a position by market");
      Print("Type=",openType,"  lots=",lots,"  SL=",SL,"TP=",TP," comment=",comment,
        "  ExpertMagicNumber=",ExpertMagicNumber);
      //  further processing of the situation, code independently
      }
//----
   return;
   }

There is nothing complicated in this function, except processing the situation of reverse. For system reverse, you should repose SL and TP and add a shift equal to the spread value. The main thing here is not to forget that any value of Stop Loss or Take Profit can be equal to zero.

Function SendPendingOrder() is just a bit more complicated, since we have to consider the fact that there can be two types of pending orders for buying and two for selling. As for the rest, it is similar to OpenMarketOrder().

//+------------------------------------------------------------------+
//|  It places a pending order                                       |
//+------------------------------------------------------------------+
void SendPendingOrder(bool   reversTrade,// sign of system reverse
                      int    Type,
                      double OpenPrice, 
                      double Lots, 
                      double SL, 
                      double TP,
                      string comment)
   {
   //Print("SendPendingOrder()  Type=",Type);
   
   if (Type==-1) return; 
//----
 
   int openType;
   
   if (reversTrade)    // reverse order type and levels
      {
      double temp;
      int spread = MarketInfo(Symbol(),MODE_SPREAD);
      int digits = MarketInfo(Symbol(),MODE_DIGITS);
 
      if (Type==OP_BUYLIMIT || Type==OP_BUYSTOP)
         {
         OpenPrice = NormalizeDouble(OpenPrice - spread*Point,digits);
         temp=SL;
         if (TP!=0)  SL = NormalizeDouble(TP+Point*spread,digits);
            else SL=0;
         if (temp!=0)TP = NormalizeDouble(temp+Point*spread,digits);
            else TP=0;
         }
      if (Type==OP_SELLLIMIT || Type==OP_SELLSTOP)
         {
         OpenPrice = NormalizeDouble(OpenPrice + spread*Point,digits);
         temp=SL;
         if (TP!=0) SL = NormalizeDouble(TP - Point*spread,digits);
            else SL=0;
         if (temp!=0)TP = NormalizeDouble(temp - Point*spread,digits);
            else TP=0;
         }
 
      switch (Type)
         {
         case OP_BUYLIMIT:  openType = OP_SELLSTOP ; break;
         case OP_SELLLIMIT: openType = OP_BUYSTOP  ; break;
         case OP_BUYSTOP:   openType = OP_SELLLIMIT; break;
         case OP_SELLSTOP:  openType = OP_BUYLIMIT ; break;
         default: Print("Invalid order type Type=",Type," in function SendPendingOrder()!!!");                           
         
         }
      }
   else openType = Type;   
      
   color Color;
   if (openType==OP_SELLLIMIT || openType==OP_SELLSTOP)  Color = Red;
   if (openType==OP_BUYLIMIT  || openType==OP_BUYSTOP)   Color = Blue;
 
   bool res = OrderSend(Symbol(),openType,Lots,OpenPrice,Slippage,SL,TP,comment,ExpertMagicNumber,0,Color); 
   if (!res)
      {
      Print("Failed placing pending order");
      //  further processing of the situation, code independently
      }
 
//----
   return;
   }

In both functions, trade request error processing is minimal, you can add your own code for this.


Final Version of Function start()

Once having created all necessary functions, we can have another look at function start() that has passed from a text template to a full-fledged code:

//+------------------------------------------------------------------+
//| expert start function                                            |
//+------------------------------------------------------------------+
int start()
  {
   // always zeroize the array size before the first use
   ArrayResize(Tickets,0);
   //ArrayResize(CommentsTicket,0);
   
   // obtain arrays of "friendly" orders
   PrepareTickets(Tickets,CommentsTicket,ExpertMagicNumber);
   
//----
 
/**
      1. Trade Signals . Receiving trade signals
      a) Every tick                       (TradeSignalEveryTick=true)
      b) Every bar in the preset period   (TradeSignalBarPeriod=...)
      OP_BUY      - to buy
      OP_SELL     - to sell
      OP_BALANCE  - no signal
*/
 
 
   int trSignal=getTradeSignal(TradeDay,ReversDay,
                       TradeSignalEveryTick,TradeSignalBarPeriod);
                       
/*   
   if (trSignal==OP_BUY) Print("Buy signal");                    
   if (trSignal==OP_SELL) Print("Sell signal");                    
   if (trSignal!=OP_SELL && trSignal!=OP_BUY) Print("The current signal is equal to ",trSignal);
*/
 
/**
      2. 
        a) Calculate SL and TP for each open order
        b) Calculate OpenPrice, SL, TP, and Lots for a new order
        c) Calculate OpenPrice, SL and TP for each pending order
*/
 
   CalculateSL_and_TP(ReversTradeSignal,Tickets,newSL_and_TP);
 
 
   double marketLots,marketSL,marketTP;
   int marketType, pendingType;
   string marketComment, pendingComment;
   double pendingOpenPrice, pendingLots, pendingSL, pendingTP;
 
   CalculateNewMarketValues(trSignal, marketType, marketLots,marketSL,marketTP,marketComment);
   CalculateNewPendingValues(trSignal, pendingType, pendingOpenPrice, pendingLots, pendingSL,
        &nbsp;pendingTP, pendingComment);
 
/**
      3. 
        a) Modification of each open order for every tick (SL and TP)        
               (ModifyMarketOrderEveryTick = true)
               
        b) Modification of each pending order for every tick (OpenPrice, SL and TP)
               (ModifyPendingEveryTick = true)
        
        c) Modification of each open order on each new bar in the preset period (SL and TP)
               (ModifyMarketBarPeriod = ...)
        
        d) Modification of each pending order on each new bar in the preset period (OpenPrice, SL and TP)
               (ModifyPendingBarPeriod = ...)
        
*/
 
   ModifyMarkets(ReversTradeSignal,ModifyMarketOrderEveryTick,ModifyMarketBarPeriod,newSL_and_TP);
   ModifyPendings(ReversTradeSignal,ModifyPendingEveryTick,ModifyPendingBarPeriod,newSL_and_TP);
 
/**
      4. 
        a) Close an open order by time
        b) Close an open order by signal
*/
   // tickets of orders to be closed
   int ticketsToClose[][2];
   double lotsToClose[]; 
   
   // zeroize array sizes
   ArrayResize(ticketsToClose,0);
   ArrayResize(lotsToClose,0);
   
   // prepare a ticket array for closing
   if (trSignal!=OP_BALANCE) PrepareTicketsToClose(trSignal,ReversTradeSignal,ticketsToClose,lotsToClose,Tickets);
   
   // close the specified orders
   CloseMarketOrders(ticketsToClose,lotsToClose);
 
/**
      5. 
        a) Delete a pending order by time
        b) Delete a pending order by condition
*/
   // tickets of orders to be deleted
   int ticketsToDelete[];
 
   // prepare a ticket array for orders to be deleted
   if (trSignal!=OP_BALANCE) PrepareTicketsToDelete(trSignal,ReversTradeSignal,ticketsToDelete,Tickets);
 
   // delete the specified orders
   DeletePendingOrders(ticketsToDelete);
 
/**
      6. 
        a) Open an order by market
        b) Place a pending order without expiration
        c) Place a pending order with expiration
*/
 
   if (trSignal!=OP_BALANCE) 
      OpenMarketOrder(ReversTradeSignal,marketType,marketLots,marketSL,marketTP,marketComment);
   
   if (trSignal!=OP_BALANCE) 
      SendPendingOrder(ReversTradeSignal,pendingType,pendingOpenPrice, pendingLots, pendingSL, 
            pendingTP,pendingComment);
//----
   return(0);
  }
//+------------------------------------------------------------------+

The EA's logic of operation is fully visible, the realization details are hidden in the corresponding functions. At program debugging, we know where to search for errors. Besides, the entire logic of the Expert Advisor is clearly separated: we know in what function we should change the code if we want to change the algorithm of finding "friendly" orders, we know where to insert conditions for closing or opening of a position, and in what function should we introduce the Trailing Stop, if necessary. The only thing that remains to do is to use the ready template in practice.


Example Usage

The obtained template, Template_EA.mqt, is attached to this article. You can save it as Expert.mqt in folder C:\Program Files\MetaTrader 4\experts\templates\. In this case, when creating a new EA, this file will always be used as a template for it, and you will have all the above functions automatically inserted. There is another alternative - save it in the same folder without changing its name. Then you will be able to specify this file as a template manually, when creating a new Expert Advisor.



Now we can select our template, Template_EA, and press "Next". Let us want to write the following simple strategy:

  • a buying signal is formed, when the stochastic signal line steps out of the oversold area and breaks through the preset level of DownLevel from bottom to top (by default, it is 10)
  • a selling signal is formed, when the stochastic signal line steps out of the overbought and breaks through the preset level of UpLevel from top to bottom (by default, it is 90)
  • protective StopLoss is placed at the distance of 100 points from the open price (it can be changed)
  • Take Profit is placed at the distance of 100 points from the open price (it can be changed)
  • the parameters of Stochastic are set through the external parameters of the EA and can also be changed.

Let's name our new EA Osc_test and introduce the necessary external parameters:



Press "Done" and see that Expert Advisor Wizard has inserted these parameters into our EA created on the basis of the above template.

Thus, we have added the necessary parameters at creating of an EA:

//+------------------------------------------------------------------+
//|                                                     Osc_test.mq4 |
//|                      Copyright © 2007, MetaQuotes Software Corp. |
//|                                        https://www.metaquotes.net |
//+------------------------------------------------------------------+
#property copyright "Copyright © 2007, MetaQuotes Software Corp."
#property link      "https://www.metaquotes.net"
 
//---- input parameters
extern int       Kperiod=18;//5;
extern int       Dperiod=14;//3;
extern int       slowPeriod=10;//3;
extern int       UpLevel=90;
extern int       DownLevel=10;
extern int       StopLoss=100;
extern int       TakeProfit=100;
extern double    Lots=0.1;
 
// constant "off market"
#define OP_BALANCE 6

Now let's insert in yourFunction() the code that produces buying and selling signals:

//+------------------------------------------------------------------+
//|  Function to produce trade signals                               |
//+------------------------------------------------------------------+
int yourFunction(int workPeriod)
   {
   bool res=OP_BALANCE;
//----
   double prevValue = iStochastic(Symbol(),workPeriod,Kperiod,Dperiod,slowPeriod,MODE_SMA,0,MODE_SIGNAL,2);
   double currValue = iStochastic(Symbol(),workPeriod,Kperiod,Dperiod,slowPeriod,MODE_SMA,0,MODE_SIGNAL,1);
 
   if (currValue>DownLevel && prevValue<DownLevel) res=OP_BUY;
   if (currValue<UpLevel && prevValue>UpLevel) res=OP_SELL;
 
//----
   return (res);   
   }

This took only four lines. It only remains to precise the function CalculateNewMarketValue() that prepares data to open a position by market.

//+------------------------------------------------------------------+
//|  It calculates the data to open a new order                      |
//+------------------------------------------------------------------+
void CalculateNewMarketValues(int    trSignal,
                              int    & marketType,
                              double & marketLots, 
                              double & marketSL,
                              double & marketTP,
                              string & marketcomment
                              )
   {
   // if there is no trade signal, exit
   //Print("CalculateNewMarketValues()  trSignal=",trSignal);
   if (trSignal==OP_BALANCE) return;
 
   marketType    =-1; // this means that we won't open
   marketLots    = 0;
   marketSL      = 0;
   marketTP      = 0;
   marketcomment = "";
 
 
//----
   //  insert your own code to calculate all parameters
 
   if (trSignal==OP_BUY  && StopLoss!=0) marketSL = Bid - StopLoss*Point;
   if (trSignal==OP_SELL && StopLoss!=0) marketSL = Ask + StopLoss*Point;
 
   if (trSignal==OP_BUY  && TakeProfit!=0) marketTP = Bid + TakeProfit*Point;
   if (trSignal==OP_SELL && TakeProfit!=0) marketTP = Ask - TakeProfit*Point;
 
   marketLots = Lots;
 
   if (trSignal==OP_BUY) marketType = OP_BUY;
   if (trSignal==OP_SELL) marketType = OP_SELL;
 
   marketcomment="test";
//----
   return;   
   }

As you can see, this also requires adding only 5 (five!) lines. Thus, we wrote only as much code as we need to describe our simple strategy. This is the first advantage of this approach.

Creation of a template will require your elaboration of all details of a typical EA realization. However, this will repay in future, when you create new strategies.


Additional Advantages

Well, there is more to come. Let's start testing the obtained EA on any symbol and any timeframe. Let it be EURUSD H1, for example, with default settings:


In this case, it doesn't matter whether there is any profit or not. Let's have a look at the test report:


Total amount of trades is 430, 241 of them are selling and 189 of them are buying. Now let's reverse the system: where there were sell trades, let's start buying, and where there were buy trades, let's start selling. For this, let's set the 'true' value in the parameter ReversTradeSignal. This is the sign of system reverse:


Start testing without changing any other parameters. Below is the test report obtained:


Indeed, now we have 241 buying trades and 189 selling trades. Well, the amounts of selliung and buying trades have been transposed. The percentage of profitable trades has reversed, too. We didn't need to rewrite the EA to check how a reversed EA worked!

However, this is not the whole story either. We have such parameter as TradeDay, do you remember? By default, it is equal to zero, but what if we want our EA to trade only on Fridays? Set its value for 5:


Start testing without touching any other parameters. See the result. We can see the Tester report that shows us how it would be if the EA traded only on Fridays with a reversed system:


We can see that there are only 81 trades left of the initial 430 ones. It means that other trades were on other days of the week. In this case, we aren't concerned much that the result is still profitable. Suppose we want to know how the EA would trade on history if we had allowed it to trade on all days but Fridays. For this, we have parameter ReversDay - just switch it to 'true'.


Let's start testing and then view the report:


There were 430 trades, we subtracted Friday's trades (81), and obtained 349. All the numbers add up: 430-81=349. Thus, we have correctly reversed trading days. And we didn't need to reprogram the EA.


Conclusion

I myself see two serious disadvantages in this article. On the one hand, it is too concise and does not give any detailed descriptions of any functions. On the other hand, it is a bit too long for the first reading. However, I hope that there will eventually appear a kind of lobby supporting this approach to creation of EAs in MQL4. This approach is suited to team work better than any other else: It would be more reasonable to create necessary templates by brainstorming than to improve only one trading algorithm on a continuous basis.

You can create a special template for simultaneous trading of many symbols, you can add a code of special error processing for the errors returned by trade functions. You can also add information functions, logging functions, functions creating special reports on testing results or on real-time trading. You can add some limitations on trading not only by days of the week, but also by hours, having selected trading sessions. You can also accumulate all service (interface) functions in a separate *.mqh file in order to see only the functions to be redefined. There are really many possibilities with this approach.

The main advantage is that being developed once, the template can be used on a continuous basis. At the same time, the probability of errors occurring in your new EAs decreases, as well.

Translated from Russian by MetaQuotes Ltd.
Original article: https://www.mql5.com/ru/articles/1514

Attached files |
Osc_test.mq4 (36.96 KB)
Template_EA.mqt (35.82 KB)
Last comments | Go to discussion (10)
[Deleted] | 22 Aug 2008 at 11:40

Beautifully presented order management system!

One question though. Step 4 closes an open order either by time or by condition. In which block of code do I specify which orders to close and what conditions to use to determine those order ticket numbers?

[Deleted] | 22 Aug 2008 at 13:06
shulic:

Beautifully presented order management system!

One question though. Step 4 closes an open order either by time or by condition. In which block of code do I specify which orders to close and what conditions to use to determine those order ticket numbers?

Thank you very much for your compliment!

These orders to be closed must be specified in function PrepareTicketsToClose(). After this function has completed its operations, the array ticketsToDelete[] should contain tickets of the orders to be closed.

pindurs
pindurs | 9 Jan 2010 at 22:51

You will have to make a little effort of imagination and understand that for non russian readers, the russian comments in the code look a bit like this:

你不明白这一点,你呢?

or like this:

!دهج لذب ، ايه

or even like this:

あなたの代わりに、この翻訳をお客様の代理を改善する必要があります。

It would so great if the files in the Russian articles would contain the russian comments and the files in the english articles the english comments (which can be seen in the examples), sometimes.

Thanks still for this great article, but not for the hour I will spend in looking for the the english comments and putting them back into the code.

Sorry for this comment, neverthless your code is doing very well and I could adapt it very easily.

Thanks a lot !

Simon Gniadkowski
Simon Gniadkowski | 14 Aug 2012 at 12:57
pindurs:

You will have to make a little effort of imagination and understand that for non russian readers, the russian comments in the code look a bit like this:

你不明白这一点,你呢?

or like this:

!دهج لذب ، ايه

or even like this:

あなたの代わりに、この翻訳をお客様の代理を改善する必要があります。

Try this, it is mostly converted to English . . . just one or two comments left in non-English.

Of course anyone could have done this, simple copy and paste from the article above.

bballllama
bballllama | 21 Aug 2012 at 17:20
Hey, thank you so much for making this template it will help a lot!
Expert Advisors Based on Popular Trading Systems and Alchemy of Trading Robot Optimization (Part III) Expert Advisors Based on Popular Trading Systems and Alchemy of Trading Robot Optimization (Part III)
In this article the author continues to analyze implementation algorithms of simplest trading systems and introduces backtesting automation. The article will be useful for beginning traders and EA writers.
Expert Advisors Based on Popular Trading Systems and Alchemy of Trading Robot Optimization (Part II) Expert Advisors Based on Popular Trading Systems and Alchemy of Trading Robot Optimization (Part II)
In this article the author continues to analyze implementation algorithms of simplest trading systems and describes some relevant details of using optimization results. The article will be useful for beginning traders and EA writers.
A Non-Trading EA Testing Indicators A Non-Trading EA Testing Indicators
All indicators can be divided into two groups: static indicators, the displaying of which, once shown, always remains the same in history and does not change with new incoming quotes, and dynamic indicators that display their status for the current moment only and are fully redrawn when a new price comes. The efficiency of a static indicator is directly visible on the chart. But how can we check whether a dynamic indicator works ok? This is the question the article is devoted to.
Fallacies, Part 1: Money Management is Secondary and Not Very Important Fallacies, Part 1: Money Management is Secondary and Not Very Important
The first demonstration of testing results of a strategy based on 0.1 lot is becoming a standard de facto in the Forum. Having received "not so bad" from professionals, a beginner sees that "0.1" testing brings rather modest results and decides to introduce an aggressive money management thinking that positive mathematic expectation automatically provides positive results. Let's see what results can be achieved. Together with that we will try to construct several artificial balance graphs that are very instructive.