MQL5 : A portfolio moving average sample expert

[Versiunea romaneasca] [MQLmagazine.com in romana] [English edition]

I wrote this expert advisor to as a full demonstration on writing portfolio expert advisors, following the principles presented in Guidelines for writing portfolio expert advisors in MQL5.

The expert will work on the chosen timeframe, only when a new bar forms up on every participating instrument. Upon checking moving average intersections, it will manage positions. There is also the option to filter by volatility (I repaired the BBoverSTD indicator from the Volatility analysis : bridging the gap from volatility forecasting to price forecasting article).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//+------------------------------------------------------------------+
//|                                                  PortfolioMA.mq5 |
//|                                       Copyright Bogdan Caramalac |
//|                                           http://mqlmagazine.com |
//+------------------------------------------------------------------+
#property copyright "Copyright Bogdan Caramalac"
#property link      "http://mqlmagazine.com"
#property version   "1.00"
 
#define FLAT        0
#define LONG        1
#define SHORT       2
 
input int Slippage=30;
input double MarginUsagePerPosition=0.5;
input bool VolatilityFilter=false;
input int StopLoss=300;
input int TakeProfit=1000;
 
input int SlowMALength=14;
input int FastMALength=9;

This section defines the constants for working with positions (FLAT,LONG,SHORT) and the parameters of the system.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int SymbolsCount;
 
struct IndicatorHandlersStruct
  {
   int SlowMovingAverageHandler;
   int FastMovingAverageHandler;
   int BBoverSTDHandler;
  };
 
struct SymbolData
  {
    string Symbol;
    IndicatorHandlersStruct IndicatorHandlers;
    datetime LastBarTime;    
  };
 
SymbolData SymbolsTable[30];

This section defines the data structure holding the symbols and indicator handlers.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
void MakeIndicatorHandlers()
  {
   datetime datetime_array[1];
   int error1,error2,error3;
   for (int i=0;i<SymbolsCount;i++)
      {
       error1=0;
       error2=0;
       error3=0;
       SymbolsTable[i].IndicatorHandlers.SlowMovingAverageHandler=iMA(SymbolsTable[i].Symbol,Period(),SlowMALength,0,MODE_SMA,PRICE_OPEN);
       Print("SymbolsTable[",i,"].IndicatorHandlers.SlowMovingAverageHandler=",SymbolsTable[i].IndicatorHandlers.SlowMovingAverageHandler);
       if (SymbolsTable[i].IndicatorHandlers.SlowMovingAverageHandler<0)
         { 
          Print("Invalid handle.");
          error1=GetLastError();
          Print("Error ",error1," while initializing iMA(",SlowMALength,") for ",SymbolsTable[i].Symbol);
         }
       SymbolsTable[i].IndicatorHandlers.FastMovingAverageHandler=iMA(SymbolsTable[i].Symbol,Period(),FastMALength,0,MODE_SMA,PRICE_OPEN);
       Print("SymbolsTable[",i,"].IndicatorHandlers.FastMovingAverageHandler=",SymbolsTable[i].IndicatorHandlers.FastMovingAverageHandler);
       if (SymbolsTable[i].IndicatorHandlers.FastMovingAverageHandler<0)
         {
          Print("Invalid handle.");
          error2=GetLastError();
          Print("Error ",error2," while initializing iMA(",FastMALength,") for ",SymbolsTable[i].Symbol);
         }
       SymbolsTable[i].IndicatorHandlers.BBoverSTDHandler=iCustom(SymbolsTable[i].Symbol,Period(),"BBoverSTD",20,10,1.5,"SMA",2,0);
       Print("SymbolsTable[",i,"].IndicatorHandlers.BBoverSTDHandler=",SymbolsTable[i].IndicatorHandlers.BBoverSTDHandler);
       if (SymbolsTable[i].IndicatorHandlers.BBoverSTDHandler<0)
         { 
          Print("Invalid handle.");
          error3=GetLastError();
          Print("Error ",error3," while initializing BBoverSTD for ",SymbolsTable[i].Symbol);
         }                   
      }
   return;
  }

This is the MakeIndicatorHandlers() routine tailored for this EA.

Now follow UnitsToLots(), GetPositionType(), PositionSetSLTP() and ManagePosition() that are the ones presented in the Guidelines for writing portfolio expert advisors in MQL5.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
string MASignal(int asset_index)
  {
   double maslowarray[1];
   double mafastarray[1];
   double mafast,maslow;
   datetime t;
   int copycount1,copycount2;
   int error1=0;
   int error2=0;
   string signal;
   copycount1=CopyBuffer(SymbolsTable[asset_index].IndicatorHandlers.SlowMovingAverageHandler,0,0,1,maslowarray);
   if (copycount1==-1)
     {
      Print("Nothing copied (1). Retrieving error.");
      error1=GetLastError();
     }
   copycount2=CopyBuffer(SymbolsTable[asset_index].IndicatorHandlers.FastMovingAverageHandler,0,0,1,mafastarray);
   if (copycount2==-1)
     {
      Print("Nothing copied (2). Retrieving error.");
      error2=GetLastError();
     }
   maslow=NormalizeDouble(maslowarray[0],SymbolInfoInteger(SymbolsTable[asset_index].Symbol,SYMBOL_DIGITS));
   mafast=NormalizeDouble(mafastarray[0],SymbolInfoInteger(SymbolsTable[asset_index].Symbol,SYMBOL_DIGITS));   
   t=TimeCurrent();
   if (maslow<mafast)
     signal="L";
   if (maslow==mafast)
     signal="-";
   if (maslow>mafast)
     signal="S";
   return(signal);
  }

This returns the moving average signal for the requested asset. Signal can be “L”, “S” or “-” .

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
void TradeLogics(int asset_index)
  {
   string s;
   double stddev[1];
   double bbhi[1];
   double bblo[1];
   int copycount;
   s=MASignal(asset_index);   
   if (VolatilityFilter==true)
     {
      copycount=CopyBuffer(SymbolsTable[asset_index].IndicatorHandlers.BBoverSTDHandler,0,0,1,stddev);
      copycount=CopyBuffer(SymbolsTable[asset_index].IndicatorHandlers.BBoverSTDHandler,2,0,1,bbhi);
     }
   if (GetPositionType(asset_index)==FLAT)
     {      
      if (s=="L")
        {
         if (VolatilityFilter==true)
           {
            if (stddev[0]>bbhi[0])
              ManagePosition(asset_index,LONG);
           }
         else
           ManagePosition(asset_index,LONG);
        }
      if (s=="S")
        {
         if (VolatilityFilter==true)
           {
            if (stddev[0]>bbhi[0])
              ManagePosition(asset_index,SHORT);
           }
         else
           ManagePosition(asset_index,SHORT);
        }
     }//else if (GetPositionType(asset_index)==FLAT)
   else//position is not flat
     {
      if (GetPositionType(asset_index)==LONG)
        {
         if (VolatilityFilter==true)
           {
            if (s=="S"||stddev[0]<bbhi[0])
              ManagePosition(asset_index,FLAT);
           }           
         else
           {
            if (s=="S")
              ManagePosition(asset_index,FLAT);           
           }
        }
      if (GetPositionType(asset_index)==SHORT)
        {
         if (VolatilityFilter==true)
           {
            if (s=="L"||stddev[0]<bbhi[0])
              ManagePosition(asset_index,FLAT);
           }
         else
           {
            if (s=="L")
              ManagePosition(asset_index,FLAT);           
           }        
        }
     }           
   return;
  }

This is the TradeLogics(). First it checks if VolatilityFilter is enabled, and only in this case calls the BBoverSTD indicator, to save time for the situation the filter is disabled. Then it tests if position is flat.
If it is, goes long or short according to the MASignal() , eventually filtered by the BBoverSTD values, if VolatilityFilter is enabled. If the position is long or short already, it checks only for contrarian signals (of course, same filtering). If contrarian signal comes, position is flattened, otherwise there is no adaptation of position to the new equity size (although the ManagePosition() is able to do it, but not asked to do so).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
   string crtsymbol;
   SymbolsTable[0].Symbol="EURUSD";
   SymbolsTable[1].Symbol="USDJPY";
   SymbolsTable[2].Symbol="GBPUSD";
   SymbolsTable[3].Symbol="USDCHF";
   SymbolsCount=1;
   MakeIndicatorHandlers();
   return(0);
  }

This is the OnInit(). It is already preset to work with four currency pairs. If SymbolsCount remains 1 , only EURUSD will be traded. If you set it to 4, all four pairs will be traded. You can alter the SymbolsTable[] however you wish (problems may arise however at the lot calculus while trading CFDs). OnInit() must end with a call to MakeIndicatorHandlers() to get the indicator handlers ready for usage.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
   datetime datetime_array[1];
   for (int i=0;i<SymbolsCount;i++)
      {
       CopyTime(SymbolsTable[i].Symbol,Period(),0,1,datetime_array);
       if (datetime_array[0]!=SymbolsTable[i].LastBarTime)
         {
          //Print(SymbolsTable[i].Symbol," :: ",TimeToString(SymbolsTable[i].LastBarTime,TIME_DATE)," ",TimeToString(SymbolsTable[i].LastBarTime,TIME_MINUTES),"  vs. ",TimeToString(datetime_array[0],TIME_DATE)," ",TimeToString(datetime_array[0],TIME_SECONDS) );
          TradeLogics(i);
          SymbolsTable[i].LastBarTime=datetime_array[0];          
         }//if (datetime_array[0]!=LastBarOccurred[i])
      }//for (int i=0;i<SymbolsCount;i++)
   return;
  }

This is the OnTick(). The OnTick() contains the symbol loop, and requires the time of the last bar of the given timeframe on the current symbol given by the cycle. If this time is different than remembered time, it means a new bar appeared on that symbol chart. Then TradeLogics() is called for decision and subsequent call to ManagePosition().

1
2
3
4
5
6
7
8
9
10
11
12
13
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   for (int i=0;i<SymbolsCount;i++)
      {   
       IndicatorRelease(SymbolsTable[i].IndicatorHandlers.SlowMovingAverageHandler);
       IndicatorRelease(SymbolsTable[i].IndicatorHandlers.FastMovingAverageHandler);
       IndicatorRelease(SymbolsTable[i].IndicatorHandlers.BBoverSTDHandler);
      }
   return;
  }

This is the OnDeinit(). It releases indicator handlers. Should OnInit() be run again, it will make other handlers. It is not completely necessary to do this, however wasting memory with new handlers every time OnInit() runs is not clean programming.

The following backtests were made with no StopLoss and TakeProfit set, also with VolatilityFilter disabled.

As we can see, portfolying was not a good choice. It added extra instability and deeper drawdowns. However, now we are dealing with trading systems, while portfolio management theory supposes a generally long or out strategy. This why, in financial theory, negative correlations would drive risks down, because pluses on some assets would compensate with minuses on other assets. But, what we have now is a portfolio of trading systems. We can think a trading system as an equity function of instruments:

 equity = trading system (instrument) 

On two perfectly negatively correlated assets, the trading system function would output two positively correlated equity series. A rigorous approach would ask that a Markowitz or CAPM selection over trading systems to be done with based on trading system outputs rather than market data.

See also the article about trading systems virtualization article, which is analysing the problem from another point of view.

File link:
PortfolioMA.mq5

2 Responses to “ MQL5 : A portfolio moving average sample expert ”

  1. dinhero on June 30, 2010 at 2:38 pm

    you have a nice blog, but you are really unclear in your post ! while actually you are one of the only one to talk about mql5 !

    Be more structured and clear !

  2. Bogdan Caramalac, MQLmagazine sr.editor on June 30, 2010 at 7:16 pm

    I admit that some things are unclear, but there is a lot of code there. The best approach for a reader is to load the EA and analyse carefully each function in respect to what the function has to do. This way, the meaning of the code will come to light.