A Simlpe Moving Average Strategy migrating from MT4/MT5 EA

Trading Strategy


This article provides the full source code of an example presented in a webinar on 2021.07.10. In the webinar, it discussed how to convert a Simple Moving Average Strategy from MT4 EA to ALGOGENE.

webinar

MetaTrader

Many experienced retail traders should already be familiar with MetaTrader (MT4/MT5), which has a popular feature called Expert Advisor (EA) for trading strategy automation. When we open MetaEditor, we can find a number of pre-built technical indicators and trading strategies.

MetaEditor

Simlpe Moving Average Strategy

Let's take the Moving Average as example. The trading idea is that if the market price crosses the MA line from bottom, it indicates a short-term up trend which suggests a buy signal. On the other hand, it indicates a sell signal if the market price crosses the MA line from the top.


SMA

The trading logic can be formulated as follows:

  • if Open < MA and Close > MA, then BUY
  • If Open > MA and Close < MA, then SELL

MT4 Source Code

Below is the source code of the Simple Moving Average strategy exported from MT4. The syntax is equivalent to C++.


  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
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
//+------------------------------------------------------------------+
//|                                               Moving Average.mq4 |
//|                   Copyright 2005-2014, MetaQuotes Software Corp. |
//|                                              http://www.mql4.com |
//+------------------------------------------------------------------+
#property copyright   "2005-2014, MetaQuotes Software Corp."
#property link        "http://www.mql4.com"
#property description "Moving Average sample expert advisor"

#define MAGICMA  20131111
//--- Inputs
input double Lots          =0.1;
input double MaximumRisk   =0.02;
input double DecreaseFactor=3;
input int    MovingPeriod  =12;
input int    MovingShift   =0;
//+------------------------------------------------------------------+
//| Calculate open positions                                         |
//+------------------------------------------------------------------+
int CalculateCurrentOrders(string symbol)
  {
   int buys=0,sells=0;
//---
   for(int i=0;i<OrdersTotal();i++)
     {
      if(OrderSelect(i,SELECT_BY_POS,MODE_TRADES)==false) break;
      if(OrderSymbol()==Symbol() && OrderMagicNumber()==MAGICMA)
        {
         if(OrderType()==OP_BUY)  buys++;
         if(OrderType()==OP_SELL) sells++;
        }
     }
//--- return orders volume
   if(buys>0) return(buys);
   else       return(-sells);
  }
//+------------------------------------------------------------------+
//| Calculate optimal lot size                                       |
//+------------------------------------------------------------------+
double LotsOptimized()
  {
   double lot=Lots;
   int    orders=HistoryTotal();     // history orders total
   int    losses=0;                  // number of losses orders without a break
//--- select lot size
   lot=NormalizeDouble(AccountFreeMargin()*MaximumRisk/1000.0,1);
//--- calcuulate number of losses orders without a break
   if(DecreaseFactor>0)
     {
      for(int i=orders-1;i>=0;i--)
        {
         if(OrderSelect(i,SELECT_BY_POS,MODE_HISTORY)==false)
           {
            Print("Error in history!");
            break;
           }
         if(OrderSymbol()!=Symbol() || OrderType()>OP_SELL)
            continue;
         //---
         if(OrderProfit()>0) break;
         if(OrderProfit()<0) losses++;
        }
      if(losses>1)
         lot=NormalizeDouble(lot-lot*losses/DecreaseFactor,1);
     }
//--- return lot size
   if(lot<0.1) lot=0.1;
   return(lot);
  }
//+------------------------------------------------------------------+
//| Check for open order conditions                                  |
//+------------------------------------------------------------------+
void CheckForOpen()
  {
   double ma;
   int    res;
//--- go trading only for first tiks of new bar
   if(Volume[0]>1) return;
//--- get Moving Average 
   ma=iMA(NULL,0,MovingPeriod,MovingShift,MODE_SMA,PRICE_CLOSE,0);
//--- sell conditions
   if(Open[1]>ma && Close[1]<ma)
     {
      res=OrderSend(Symbol(),OP_SELL,LotsOptimized(),Bid,3,0,0,"",MAGICMA,0,Red);
      return;
     }
//--- buy conditions
   if(Open[1]<ma && Close[1]>ma)
     {
      res=OrderSend(Symbol(),OP_BUY,LotsOptimized(),Ask,3,0,0,"",MAGICMA,0,Blue);
      return;
     }
//---
  }
//+------------------------------------------------------------------+
//| Check for close order conditions                                 |
//+------------------------------------------------------------------+
void CheckForClose()
  {
   double ma;
//--- go trading only for first tiks of new bar
   if(Volume[0]>1) return;
//--- get Moving Average 
   ma=iMA(NULL,0,MovingPeriod,MovingShift,MODE_SMA,PRICE_CLOSE,0);
//---
   for(int i=0;i<OrdersTotal();i++)
     {
      if(OrderSelect(i,SELECT_BY_POS,MODE_TRADES)==false) break;
      if(OrderMagicNumber()!=MAGICMA || OrderSymbol()!=Symbol()) continue;
      //--- check order type 
      if(OrderType()==OP_BUY)
        {
         if(Open[1]>ma && Close[1]<ma)
           {
            if(!OrderClose(OrderTicket(),OrderLots(),Bid,3,White))
               Print("OrderClose error ",GetLastError());
           }
         break;
        }
      if(OrderType()==OP_SELL)
        {
         if(Open[1]<ma && Close[1]>ma)
           {
            if(!OrderClose(OrderTicket(),OrderLots(),Ask,3,White))
               Print("OrderClose error ",GetLastError());
           }
         break;
        }
     }
//---
  }
//+------------------------------------------------------------------+
//| OnTick function                                                  |
//+------------------------------------------------------------------+
void OnTick()
  {
//--- check for history and trading
   if(Bars<100 || IsTradeAllowed()==false)
      return;
//--- calculate open orders by current symbol
   if(CalculateCurrentOrders(Symbol())==0) CheckForOpen();
   else                                    CheckForClose();
//---
  }
//+------------------------------------------------------------------+

Implementation on ALGOGENE

Following a similar programming structure, we can easily convert the EA as follows.

  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
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
from AlgoAPI import AlgoAPIUtil, AlgoAPI_Backtest
from datetime import datetime, timedelta
import numpy as np
from talib import EMA

MAGICMA = 20131111
Lots = 0.1
MaximumRisk = 0.02
DecreaseFactor = 3
MovingPeriod = 12
MovingShift = 0


class AlgoEvent:
    def __init__(self):
        self.lasttradetime = datetime(2000,1,1)
        self.pos, self.osOrder, self.pendOrder = {}, {}, {}

    def start(self, mEvt):
        self.evt = AlgoAPI_Backtest.AlgoEvtHandler(self, mEvt)
        self.evt.start()
        
    def on_marketdatafeed(self, md, ab):
        if md.timestamp>self.lasttradetime+timedelta(hours=1):
            self.lasttradetime = md.timestamp
        
            # get latest closing price
            fullbar = self.evt.getHistoricalBar(contract={"instrument":md.instrument}, numOfBar=MovingPeriod+MovingShift, interval="D")
            self.obs_open = [fullbar[t]["o"] for t in fullbar]
            self.obs_close = [fullbar[t]["c"] for t in fullbar]

            if self.CalculateCurrentOrders(md.instrument)==0:
                self.CheckForOpen(md, ab)
            else:
                self.CheckForClose(md, ab)

    def CheckForOpen(self,md,ab):
        ma = EMA(np.array(self.obs_close), MovingPeriod)[-1-MovingShift]
        # sell conditions
        if self.obs_open[-1]>ma and self.obs_close[-1]<ma:
            order = AlgoAPIUtil.OrderObject(
                instrument = md.instrument,
                orderRef = MAGICMA,
                openclose = 'open', 
                buysell = -1,        #1=buy, -1=sell
                ordertype = 0,      #0=market, 1=limit
                volume = self.LotsOptimized(md,ab)
            )
            self.evt.sendOrder(order)
        # buy condition
        if self.obs_open[-1]<ma and self.obs_close[-1]>ma:
            order = AlgoAPIUtil.OrderObject(
                instrument = md.instrument,
                orderRef = MAGICMA,
                openclose = 'open', 
                buysell = 1,        #1=buy, -1=sell
                ordertype = 0,      #0=market, 1=limit
                volume = self.LotsOptimized(md,ab)
            )
            self.evt.sendOrder(order)

    def CheckForClose(self, md, ab):
        ma = EMA(np.array(self.obs_close), MovingPeriod)[-1-MovingShift]
        for _id in list(self.osOrder):
            if self.osOrder[_id]["buysell"]==1:
                if self.obs_open[-1]>ma and self.obs_close[-1]<ma:
                    order = AlgoAPIUtil.OrderObject(
                        tradeID = _id,
                        openclose = 'close'
                    )
                    self.evt.sendOrder(order)
            elif self.osOrder[_id]["buysell"]==-1:
                if self.obs_open[-1]<ma and self.obs_close[-1]>ma:
                    order = AlgoAPIUtil.OrderObject(
                        tradeID = _id,
                        openclose = 'close'
                    )
                    self.evt.sendOrder(order)

    def CalculateCurrentOrders(self, symbol):
        self.pos, self.osOrder, self.pendOrder = self.evt.getSystemOrders()
        if symbol in self.pos:
            return self.pos[symbol]["netVolume"]
        return 0

    def LotsOptimized(self,md,ab):
        losses = 0
        # select lot size
        lot = round(ab["availableBalance"]*MaximumRisk/1000.0,1)
        # calcuulate number of losses orders without a break
        if DecreaseFactor>0:
            for _id in self.osOrder:
                if self.osOrder[_id]["buysell"]==1: #buy
                    if md.bidPrice < self.osOrder[_id]["openprice"]:
                        losses+=1
                elif self.osOrder[_id]["buysell"]==-1: #sell
                    if md.askPrice > self.osOrder[_id]["openprice"]:
                        losses+=1
            if losses>1:
                lot = round(lot-lot*losses/DecreaseFactor,1)
        # return lot size
        if lot<Lots: lot = Lots
        return lot

    def on_bulkdatafeed(self, isSync, bd, ab):
        pass

    def on_newsdatafeed(self, nd):
        pass

    def on_weatherdatafeed(self, wd):
        pass
    
    def on_econsdatafeed(self, ed):
        pass
    
    def on_orderfeed(self, of):
        pass

    def on_dailyPLfeed(self, pl):
        pass

    def on_openPositionfeed(self, op, oo, uo):
        pass

We can then reproduce a similar backtest result as in MT4/MT5.

result