Привет! Соскучились? Надеюсь, Вам хватило времени познакомиться с торговыми функциями MQL4, потому что сегодня они нам проигодятся.
В предыдущих двух частях этого урока мы познакомились с нашим первым советником и поняли, какая идея за ним скрывается. Сегодня мы продолжим разбирать код нашего советника.
Итак, начнём.
Код, который мы уже имеем:
Код:
//+------------------------------------------------------------------+
//| My_First_EA.mq4 |
//| Kirill |
//| StockProgrammer@mail.ru |
//+------------------------------------------------------------------+
#property copyright "Kirill"
#property link "StockProgrammer@mail.ru"
//---- input parameters
extern double TakeProfit=350.0;
extern double Lots=0.1;
extern double TrailingStop=35.0;
//+------------------------------------------------------------------+
//| expert initialization function |
//+------------------------------------------------------------------+
int init()
{
//----
//----
return(0);
}
//+------------------------------------------------------------------+
//| expert deinitialization function |
//+------------------------------------------------------------------+
int deinit()
{
//----
//----
return(0);
}
int Crossed (double line1 , double line2)
{
static int last_direction = 0;
static int current_dirction = 0;
if(line1>line2)current_dirction = 1; //up
if(line1<line2)current_dirction = 2; //down
if(current_dirction != last_direction) //changed
{
last_direction = current_dirction;
return (last_direction);
}
else
{
return (0);
}
}
//+------------------------------------------------------------------+
//| expert start function |
//+------------------------------------------------------------------+
int start()
{
//----
int cnt, ticket, total;
double shortEma, longEma;
if(Bars<100)
{
Print("bars less than 100");
return(0);
}
if(TakeProfit<10)
{
Print("TakeProfit less than 10");
return(0); // check TakeProfit
}
shortEma = iMA(NULL,0,8,0,MODE_EMA,PRICE_CLOSE,0);
longEma = iMA(NULL,0,13,0,MODE_EMA,PRICE_CLOSE,0);
int isCrossed = Crossed (shortEma,longEma);
total = OrdersTotal();
if(total < 1)
{
if(isCrossed == 1)
{
ticket=OrderSend(Symbol(),OP_BUY,Lots,Ask,3,0,Ask+TakeProfit*Point, "My EA",12345,0,Green);
if(ticket>0)
{
if(OrderSelect(ticket,SELECT_BY_TICKET,MODE_TRADES))
Print("BUY order opened : ",OrderOpenPrice());
}
else Print("Error opening BUY order : ",GetLastError());
return(0);
}
if(isCrossed == 2)
{
ticket=OrderSend(Symbol(),OP_SELL,Lots,Bid,3,0,
Bid-TakeProfit*Point,"My EA",12345,0,Red);
if(ticket>0)
{
if(OrderSelect(ticket,SELECT_BY_TICKET,MODE_TRADES))
Print("SELL order opened : ",OrderOpenPrice());
}
else Print("Error opening SELL order : ",GetLastError());
return(0);
}
return(0);
}
for(cnt=0;cnt<total;cnt++)
{
OrderSelect(cnt, SELECT_BY_POS, MODE_TRADES);
if(OrderType()<=OP_SELL && OrderSymbol()==Symbol())
{
if(OrderType()==OP_BUY) // long position is opened
{
// should it be closed?
if(isCrossed == 2)
{
OrderClose(OrderTicket(),OrderLots(),Bid,3,Violet); // close position
return(0); // exit
}
// check for trailing stop
if(TrailingStop>0)
{
if(Bid-OrderOpenPrice()>Point*TrailingStop)
{
if(OrderStopLoss()<Bid-Point*TrailingStop)
{
OrderModify(OrderTicket(),OrderOpenPrice(),Bid-Point*TrailingStop,OrderTakeProfit(),0,Green);
return(0);
}
}
}
}
else // go to short position
{
// should it be closed?
if(isCrossed == 1)
{
OrderClose(OrderTicket(),OrderLots(),Ask,3,Violet); // close position
return(0); // exit
}
// check for trailing stop
if(TrailingStop>0)
{
if((OrderOpenPrice()-Ask)>(Point*TrailingStop))
{
if((OrderStopLoss()>(Ask+Point*TrailingStop)) || (OrderStopLoss()==0))
{
OrderModify(OrderTicket(),OrderOpenPrice(),Ask+Point*TrailingStop,OrderTakeProfit(),0,Red);
return(0);
}
}
}
}
}
}
return(0);
}
//+------------------------------------------------------------------+
Последее, что мы разобрали в прошлый раз, - это функция Crossed(). Таким образом, мы подошли вплотную к функции start().
Код:
int cnt, ticket, total;
Замечание: Чтобы объявить несколько переменных в одной строчке, необходимо начать эту строчку с названия их типа, затем перечислить идентификаторы переменных через запятую.
С тем же успехом можно было разбить эту строчку на три части:
Код:
int cnt;
int ticket;
int total;
Переменную ticket мы будем использовать для хранения тикета (идентификационного номера ордера), возвращаемого функцией OrderSend().
Переменную total мы будем использовать для хранения числа уже открытых ордеров.
Код:
double shortEma, longEma;
Мы будем использовать эти переменные для хранения значений быстрой EMA и медленной EMA.
Я надеюсь, Вы помните из предыдущей части этого урока, что мы должны следить за их пересечениями и взаиморасположениями. Исходя из этой информации мы будем принимать решения об открытии ордеров на покупку или продажу, а также их закрытии.
Код:
if(Bars<100)
{
Print("bars less than 100");
return(0);
}
Число баров на графике хранится в переменной Bars. С помощью этой переменной мы проверяем количество баров на графике в данный момент и, если оно меньше 100, мы сделаем 2 вещи: во-первых сообщим пользователю, напечатав в лог эксперта сообщение "bars less than 100", во-вторых мы завершим функцию start() оператором return(0);
Таким образом, мы отказываемся работать, если баров на графике меньше 100.
Код:
if(TakeProfit<10)
{
Print("TakeProfit less than 10");
return(0); // check TakeProfit
}
Переменная TakeProfit является внешней (extern), что означает, что пользователь может изменить её в окошке настроек советника.
Мы хотим защитить пользователя нашего советника от его же ошибок. Короче говоря, сделать программу дуракоустойчивой ;)
Мы предполагаем, что любое значение меньше 10 для переменной TakeProfit является плохим выбором. Поэтому мы проверяем значение, заданное пользователем, и сравниваем его с 10. Если окажется, что значение меньше 10, мы сообщим об этом пользователю, распечатав сообщение "TakeProfit less than 10", и завершим функцию start() оператором return(0);
Код:
shortEma = iMA(NULL,0,8,0,MODE_EMA,PRICE_CLOSE,0);
longEma = iMA(NULL,0,13,0,MODE_EMA,PRICE_CLOSE,0);
Мы воспользуемся встроенным в MQL4 индикатором iMA, который рассчитает скользящие среднии.
Здесь нам придётся немного остановиться, чтобы поподробнее узнать о функции iMA.
iMA
Синтаксис:
double iMA( string symbol, int timeframe, int period, int ma_shift, int ma_method, int applied_price, int shift)
Описание:
Расчет скользящего среднего.
Параметры:
symbol - Символьное имя инструмента, на данных которого будет вычисляться индикатор. NULL означает текущий символ.
timeframe - Период. Может быть одним из периодов графика. 0 означает период текущего графика.
period - Период усреднения для вычисления скользящего среднего.
ma_shift - Сдвиг индикатора относительно ценового графика.
ma_method - Метод усреднения. Может быть любым из значений методов скользящего среднего (Moving Average).
applied_price - Используемая цена. Может быть любой из ценовых констант.
shift - Индекс получаемого значения из индикаторного буфера (сдвиг относительно текущего бара на указанное количество периодов назад).
Периоды графиков:

Методы скользящих:

Ценовые константы:

Всю эту информацию можно получить во встроенном справочнике MQL4. Достаточно лишь в окне Навигатор во вкладке Поиск набрать: iMA.
Код:
shortEma = iMA(NULL,0,8,0,MODE_EMA,PRICE_CLOSE,0);
longEma = iMA(NULL,0,13,0,MODE_EMA,PRICE_CLOSE,0);
Переменной shortEma мы присвоили значение, равное:
Усреднённую методом экспоненциального среднего цену Close последних 8 баров, начиная с текущего.
Коротко можно говорить EMA8.
Переменной longEma мы присвоили значение, равное:
Усреднённую методом экспоненциального среднего цену Close последних 13 баров, начиная с текущего.
Коротко можно говорить EMA13.
Код:
int isCrossed = Crossed (shortEma,longEma);
Первый параметр - это тек. значение первой линии, за которой мы хоти наблюдать. Второй - второй линии.
Функция будет следить за двумя линиями при каждом её вызове посредством сохранения их тек. значений в статических переменных, чтобы запоминать их предыдущее состояние.
- Функция возвращает 0, если взаиморасположение линий не изменилось.
- Функция возвращает 1, если взаиморасположение линий изменилось, и первая линия оказалась над второй.
- Функция возвращает 2, если взаиморасположение линий изменилось, и первая линия оказалась под второй.
В этой строчке помимо вызова функции мы объявили переменную isCrossed для хранения возвращаемого значения. Это значение мы будем использовать при принятии решений.
Код:
total = OrdersTotal();
if(total < 1)
{
...
}
Замечание: Функция OrdersTotal() возвращает общее количество открытых и ожидающих ордеров (см. урок 15).
Следующий за этим блок if сработает, только если нет ни одного открытого или ждущего ордера.
Блок if:
Код:
{
if(isCrossed == 1)
{
ticket=OrderSend(Symbol(),OP_BUY,Lots,Ask,3,0,Ask+TakeProfit*Point, "My EA",12345,0,Green);
if(ticket>0)
{
if(OrderSelect(ticket,SELECT_BY_TICKET,MODE_TRADES))
Print("BUY order opened : ",OrderOpenPrice());
}
else Print("Error opening BUY order : ",GetLastError());
return(0);
}
if(isCrossed == 2)
{
ticket=OrderSend(Symbol(),OP_SELL,Lots,Bid,3,0,Bid-TakeProfit*Point,"My EA",12345,0,Red);
if(ticket>0)
{
if(OrderSelect(ticket,SELECT_BY_TICKET,MODE_TRADES))
Print("SELL order opened : ",OrderOpenPrice());
}
else Print("Error opening SELL order : ",GetLastError());
return(0);
}
OrderSend()
Синтаксис:
int OrderSend( string symbol, int cmd, double volume, double price, int slippage, double stoploss, double takeprofit, string comment=NULL, int magic=0, datetime expiration=0, color arrow_color=CLR_NONE)
Параметры:
symbol
- Мы воспользовались функцией Symbol(), чтобы получить строку с названием валюты, и передали её (строку) функции OrderSend().
cmd
- Мы использовали OP_BUY, чтобы открыть сделку на покупку.
volume
- Мы использовали переменную Lots, значение которой нам предоставил пользователь.
price
- Мы воспользовались переменной (на самом деле функцией) Ask, чтобы получить тек. значение цены ask, и передали его функции OrderSend().
slippage
- Мы использовали 3 в качестве значения допустимого проскальзывания.
stoploss
- Мы использовали 0 в качествет стоп-лосса, что означает, что у этого ордера нет стоп-лосса.
takeprofit
- Мы воспользовались переменной TakeProfit, значение которой нам предоставил пользователь. Умножили её на цену одного пункта, хранящуюся в переменной Point, и прибавили результат к тек. значению Ask (цена открытия ордера). Это Важный момент обратите на него внимание. Аналогичным образом надо рассчитывать стоп-лосс, если он не равен нулю.
comment
- Мы использовали строку "My EA" в качестве комментария.
magic
- Мы испольлзовали число 12345 в качестве magic-числа. Магическое число нужно только для того, чтобы советник отличал свои ордера от чужих.
expiration
- Мы использовали 0, потому что нашему ордеру не нужен срок истечения. Этот параметр применим только к отложенным ордерам.
arrow_color
- Мы выбрали зелёный, потому что мы любим деньги, а деньги зелёные ;)
Функция OrderSend() при успешном исполнении возвращает тикет открытого ордера, что мы используем в проверке:
Код:
if(ticket>0)
Если всё прошло успешно, и функция OrderSend() вернула нормальный тикет (больший 0), а функция OrderSelect() благополучно выбрала нужный ордер, то в лог эксперта распечатается сообщение "BUY order opened : " + цена открытия.
Для более подробной информации о OrderSelect() и OrderOpenPrice() см. урок 15.
Иначе, если функция OrderSend() вернула -1, что означает, что было ошибка, мы должны об этом сообщить пользователю, выдав сообщение: :Error opening BUY order: " + номер ошибки, предоставленный функцией GetLastError(). При этом нам придётся завершить функцию start() опреатором return(0);
Код:
if(isCrossed == 2)
{
ticket=OrderSend(Symbol(),OP_SELL,Lots,Bid,3,0,
Bid-TakeProfit*Point,"My EA",12345,0,Red);
if(ticket>0)
{
if(OrderSelect(ticket,SELECT_BY_TICKET,MODE_TRADES))
Print("SELL order opened : ",OrderOpenPrice());
}
else Print("Error opening SELL order : ",GetLastError());
return(0);
}
Можете найти различия в вызовах функции OrderSend() ?
Правильно!
Следующие параметры не изменились:
symbol
volume
slippage
stoploss
comment
magic
expiration
А эти изменились:
cmd
- Мы использовали OP_SELL, чтобы открыть сделку на продажу.
price
- Мы воспользовались переменной (на самом деле функцией) Bid, чтобы получить тек. значение цены bid, и передали его функции OrderSend().
takeprofit
- Мы воспользовались переменной TakeProfit, значение которой нам предоставил пользователь. Умножили её на цену одного пункта, хранящуюся в переменной Point, и вычли результат к тек. значению Bid (цена открытия ордера).
arrow_color
- Мы выбрали красный, потому что мы любим деньги, а деньги зелёные, но нам нужен другой цвет для позиций на продажу ;)
Далее в этом куске кода всё аналогично.
Вот и всё... Мы закончили с блоком if(total<1). В следующ...
- Постой!
- Что?
- А я заметил ещё одну строчку в этом блоке, которую мы не разобрали!
- Где?
- Вот она:
Код:
return(0);
Посмотрите внимательно на эти строчки:
Код:
if(total < 1)
{
if(isCrossed == 1)
{
...
}
if(isCrossed == 2)
{
...
}
return(0); // <<-- Вот она
}
Если EMA8 пересекает EMA13 сверху вниз, то мы продаём.
А что если они ещё не пересеклись? За это отвечает данная строчка. Она прервёт функцию start(), если ещё не было пересечения (в случае, если переменная isCrossed равна 1 или 2).
Теперь мы знаем, как наш советник открывает сделки. В последней части урока мы рассмотрим остаток кода и обсудим детали Тестера Стратегий MetaTrader.
Надеюсь, этот урок Вам понравился.