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.
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.
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.
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.
Like and follow me
If you find my articles inspiring, like this post and follow me here to receive my latest updates.
Enter my promote code "AjpQDMOSmzG2" for any purchase on ALGOGENE, you will automatically get 5% discount.