The first stage of creating a trading robot
This article is a continuation of the one started by in the 61st issue of ForTrader.org of the series "Creating a trading robot: from A to Z". It is assumed that at this stage the reader is already familiar with the trading system, which we are converting into a robot, so within the framework of this article we will consider the initial phase of writing the Expert Advisor. This version of the robot will be somewhat simplified to make the source code easier to understand.
Logic modules of the robot
For realization trading robot It must first be determined which logic modules it should consist of.
Trading signals detection module
The purpose of this module is to trading instrument searchfor which there is a signal at the given moment of time. The module will determine the signal for each specific trading instrument. Thus, to determine the general trend for a group of instruments, you will need to use this module as many times as the number of instruments provided in the robot settings.
From the program's point of view, this might look like two-dimensional array fillingwhere the zero index of the second dimension is the name of the trading instrument, and the first one is the signal by instrument. That is, to find a common signal (common direction) at any moment of time it will only be necessary to traverse the array, without redefining the signals by instruments. If we refer to the description of the proposed trading system, we can remember that also for each specific instrument will be determined trading lot. In this regard, it makes sense to use the second index in the second dimension of the array, where the lot of this trading instrument will be specified. In this implementation of the robot, this module will not be present (to simplify code perception), so the second index of the second dimension of the array will be filled with the same number - the working lot from the robot settings.
Let's return to the idea of a module for determining the signal by a particular instrument. The module will be a separate subroutine (function). The code of the module itself will be as follows:
//+——————————————————————————————————————-+
//| Trading signals search function |
int find_signal(string Symb) {
if(iOpen(Symb,0,0)>iMA(Symb,0,MA_Period,MA_Shift,MA_Method,MA_Method,MA_Applied_Price,0))
/* если цена открытия по символу Symb на данном таймфрейме выше, чем средняя скользящая — сигнал повышения*/
return(1);
if(iOpen(Symb,0,0)<iMA(Symb,0,MA_Period,MA_Shift,MA_Method,MA_Method,MA_Applied_Price,0))
/* если цена открытия по символу Symb на данном таймфрейме ниже, чем средняя скользящая — сигнал понижения*/
return(-1);
/* if there is no signal, return 0 */
return(0);
}
//| Trading signals search function |
//+——————————————————————————————————————-+
In this case, the parameters of the moving average are placed in the input parameters of the robot (at the top of the robot code, you will see it later):
extern int MA_Period = 8; //moving average averaging period
extern int MA_Shift = 0; //shift moving average
extern int MA_Method = 1; /* moving average calculation method:
0 — простое скользящее среднее,
1 — экспоненциальное скользящее среднее,
2 — сглаженное скользящее среднее,
3 — линейно-взвешенное скользящее среднее
*/
extern int MA_Applied_Price = 1; /* used price for moving average calculation:
0 — цена закрытия,
1 — цена открытия,
2 — максимальная цена,
3 — минимальная цена,
4 — средняя цена, (high+low)/2,
5 — типичная цена, (high+low+close)/3,
6 — взвешенная цена закрытия, (high+low+close+close)/4
*/
Thus, when the function find_signal (string Symb) is called, a signal will be obtained by Symb: 1 - rise, -1 - fall and 0 - no signal.
1. Module for checking the presence of an open order
It is necessary to understand whether there is an open order of this robot on this instrument or not. If there is no deal, the robot will look for an opportunity to open it. If there is one, the robot will follow it and look for an opportunity to close it.
Moving on to the topic of closing the deal (getting ahead of ourselves), it should be noted that mark-to-market is possible in the case when the overall signal changes in the opposite direction (relative to the direction of the open order). Since the trading robot will use both ***USD and USD*** instruments (for example, GBPUSD and USDCHF), it is necessary to introduce some labels - how exactly the robot should evaluate this or that instrument. In the terminology of our team (due to the experience of creating trading robotsThis mark is called "instrument coefficient", and for instruments of *** type it is equal to 1, and for *** -1 (where in our case is USD). I.e. in the example for GBPUSD it will be equal to 1 and for USDCHF it will be equal to -1. These coefficients are taken as input parameters and added to the array of instruments in the robot code as another index of the second dimension. Thus, our target two-dimensional array will also store the label of where this instrument is "looking" relative to the whole group.
Let us return to this module. It will also be implemented as a separate subroutine (function). The code is quite simple:
//+——————————————————————————————————————-+
//| Open order search function |
int find_orders(int type=-1, int magic=-1, string Symb=NULL) {
int i = 0; //counter for the loop
for (i=OrdersTotal()-1; i>=0; i-) {
if (!OrderSelect(i,SELECT_BY_POS,MODE_TRADES)) continue;
//if no order is selected, go to the next step of the loop
if(((OrderType()==type) || (type==-1))
&& ((OrderMagicNumber()==magic) || (magic==-1))
&& (OrderSymbol()==Symb))) {
/* IF (((order type==type) OR (type==-1))
AND ((magic number of order==magic) OR (magic==-1))
AND (order symbol==Symb))
then return the order ticket */
return(OrderTicket());
break; //exit from the loop
}
}
//если дошли до этого места — значит ордер не найден — возвращаем -1
return(-1);
}
//| Open order search function |
//+——————————————————————————————————————-+
If an order is detected, the function will return its ticket, otherwise the value is "-1"
2. Transaction opening module
A trading robot must open trades. A separate module (subroutine, function) should be used to open them. Often developers limit themselves to the standard function of opening orders, which is not always convenient. Thus, when using a separate subroutine, you can initially lay down a handler of erroneous situations, make calculations of working levels. StopLoss и TakeProfit etc.
The code of this module includes not only the function of opening orders, but also the calculation of StopLoss and TakeProfit levels, as well as the function that is used to decipher possible error codes (which is often convenient to see error explanations in the journal):
//+——————————————————————————————————————-+
//| Order opening function |
void open_positions(int signal, double lot, int magic, double price=0.0, string symb=»NONE») {
//signal=0 -> buy opening signal
//signal=1 -> signal to open a sale
int i = 0; //variable for loop counter
int count = Count_Of_Trade_Try; // number of attempts to open an order in case it cannot be executed at once
int err=0;
if(symb==»NONE») symb=Symbol();
if(signal==0)
price=MarketInfo(symb,MODE_ASK); //open price for purchases
if(signal==1)
price=MarketInfo(symb,MODE_BID); //open price for sales
while(i<=count) {
//the function of order opening itself (built-in). For ease of perception the parameters are separated on different lines:
int ticket = OrderSend(Symbol(), //symbol
signal, //order type
lot, //volume
price, //open price
Slipage, //level of permissible requote
sl(SL,signal,price), // Stop Loss value
tp(TP,signal, price), //Take Profit value
Order_Comment, //order commentary
magic, // magic number
0, //expiration time (used for pending orders)
CLR_NONE); //цвет отображаемой стрелки на графике (CLR_NONE — стрелка не рисуется)
if(ticket!=-1) //if the opening was successful, apply the graphical object and exit the loop
break;
err=GetLastError();
if(err!=0) Print(«Ошибка: «+Market_Err_To_Str(err));
i++;
Sleep(Pause_Of_Trade_Try*100); //in case of an error, pause before a new attempt
} //end while(i<=count)
} //end void open_positions(int signal, double lot, int magic, double price=0.0, string symb=»NONE»)
//| Order opening function |
//+——————————————————————————————————————-+
//+——————————————————————————————————————-+
//| Function for calculating Stop Loss value for orders |
double sl(int sl_value, int type, string symb=»NONE», int rmode=1) {
//type=0 -> market purchases
//type=1 -> market sales
if(symb==»NONE») symb=Symbol();
if(sl_value<=0) return(0);
if(rmode==1) {
if(type==0) return(MarketInfo(symb,MODE_ASK)-sl_value*MarketInfo(symb,MODE_POINT)); //for purchases
if(type==1) return(MarketInfo(symb,MODE_BID)+sl_value*MarketInfo(symb,MODE_POINT)); //for sales
}
if(rmode==2) {
if(type==0) return(MarketInfo(symb,MODE_BID)-sl_value*MarketInfo(symb,MODE_POINT)); //for purchases
if(type==1) return(MarketInfo(symb,MODE_ASK)+sl_value*MarketInfo(symb,MODE_POINT)); //for sales
}
} //end double sl(int sl_value, int type, double price=0.0, string symb=»NONE», int rmode=1)
//| Stop Loss calculation function for orders |
//+——————————————————————————————————————-+
//+——————————————————————————————————————-+
//| Function for calculation of Take Profit value for orders |
double tp(int tp_value, int type, string symb=»NONE») {
//type=0 -> market purchases
//type=1 -> market sales
if(symb==»NONE») symb=Symbol();
if(tp_value<=0) return(0);
if(type==0) return(MarketInfo(symb,MODE_ASK)+tp_value*MarketInfo(symb,MODE_POINT)); //for purchases
if(type==1) return(MarketInfo(symb,MODE_BID)-tp_value*MarketInfo(symb,MODE_POINT)); //for sales
} //end double tp(int tp_value, int type, double price=0.0, string symb=»NONE»)
//| function for calculating the Take Profit value for orders |
//+——————————————————————————————————————-+
//+——————————————————————————————————-+
//| Error code decoding function |
string Market_Err_To_Str(int err) {
/* function covers only error codes of trade operations */
switch(err) {
case(0): return(«Нет ошибки»);
case(1): return(«Нет ошибки, но результат неизвестен»);
case(2): return(«Общая ошибка»);
case(3): return(«Неправильные параметры»);
case(4): return(«Торговый сервер занят»);
case(5): return(«Старая версия клиентского терминала»);
case(6): return(«Нет связи с торговым сервером»);
case(7): return(«Недостаточно прав»);
case(8): return(«Слишком частые запросы»);
case(9): return(«Недопустимая операция, нарушающая функционирование сервера»);
case(64): return(«Счет заблокирован»);
case(65): return(«Неправильный номер счета»);
case(128): return(«Истек срок ожидания совершения сделки»);
case(129): return(«Неправильная цена»);
case(130): return(«Неправильные стопы»);
case(131): return(«Неправильный объём»);
case(132): return(«Рынок закрыт»);
case(133): return(«Торговля запрещена»);
case(134): return(«Недостаточно денег для совершения операции»);
case(135): return(«Цена изменилась»);
case(136): return(«Нет цен»);
case(137): return(«Брокер занят»);
case(138): return(«Новые цены»);
case(139): return(«Ордер заблокирован и уже обрабатывается»);
case(140): return(«Разрешена только покупка»);
case(141): return(«Слишком много запросов»);
case(145): return(«Модификация запрещена, т.к. ордер слишком близок к рынку»);
case(146): return(«Подсистема торговли занята»);
case(147): return(«Использование даты истечения запрещено брокером»);
case(148): return(«Количество открытых и отложенных ордеров достигло предела, установленного брокером»);
case(149): return(«Попытка открыть противоположную позицию к уже существующей в случае, если хеджирование запрещено»);
case(150): return(«Попытка закрыть позицию по инструменту в противоречии с правилом FIFO»);
default: return(«»);
} //end switch(err)
} //end string Err_To_Str(int err)
//| Error code decoding function |
//+——————————————————————————————————-+
3. order closing module
The robot should close the trade in case the general trend changes to the opposite one. Since the function of searching for an open order returns the order's ticket, it will be reasonable to close the order directly by its ticket within the framework of this module. The code of such a module is also very simple and will be quite understandable even for beginners in MQL4:
//+——————————————————————————————————————-+
//| Function of order closing by its number (ticket) |
bool close_by_ticket(int c_ticket, int slipage) {
/*
function of closing a deal by its number (ticket).
When closing a market order, the level of maximum allowable slipage (slipage) is taken into account
*/
int i = 0, //variable for loop counter
err = 0;
bool ticket = false; // the variable to indicate (non)success of the fact of closing a deal
double price = 0.0; //price for closed deal (for market orders)
if(OrderSelect(c_ticket,SELECT_BY_TICKET,MODE_TRADES)) { //select an order by ticket
if(OrderType()==OP_BUY) price = Bid; //price for purchases
if(OrderType()==OP_SELL) price = Ask; //price for sales
for(i=0;i<=Count_Of_Trade_Try;i++) {
if(OrderType()<=1) //если рыночный ордер — закрываем его, если отложенный — удаляем
ticket=OrderClose(OrderTicket(),OrderLots(),price,slipage,CLR_NONE); //close market order
else
ticket=OrderDelete(OrderTicket()); //delete pending order
if(ticket) { //если закрытие или удаление прошло успешно — возвращаем true и выходим из цикла
return(true);
break;
} //end if(ticket)
err=GetLastError();
if(err!=0) Print(«Ошибка: «+Market_Err_To_Str(err));
Sleep(Pause_Of_Trade_Try*100); //in case of an error, pause before a new attempt
} //end for(i=0;i<=Count_Of_Trade_Try;i++)
} //end if(OrderSelect(c_ticket,SELECT_BY_TICKET,MODE_TRADES))
return(false); //return false (in case the order could not be closed or deleted).
} //end bool close_by_ticket(int c_ticket)
//| Function of order closing by its number (ticket) |
//+——————————————————————————————————————-+
4. Final robot code
For simplicity of perception, we did not add the money management module to this version of the robot (see the previous article), it will appear in the next article. Also, this version of the robot does not include trailing stop loss functionwhich will also be included in the code of the next article.
Now it's time to assemble the resulting modules into a single whole and add the missing links between them. All this will be done in the predefined language function start().
The code itself is presented below, it is provided with comments and should not be difficult to understand.
You can download and analyze the source code of the resulting Expert Advisor with a detailed description and missing functional parts here.
5. Testing the robot
The robot will be tested on H4 tamframe. The parameters that are entered by default are used. If changes are made, this will be reported separately.