На прошлом уроке мы с вами писали индикатор, который указывает входы в рынок. Соответственно сегодня мы займёмся разработкой эксперта, использующего его сигналы.
Начнём с создания шаблона советника(как это сделать мы разбирали на уроке №10).
Опишем входные параметры эксперта:
extern double Lots = 0.1; // объём сделки в лотах extern int StopLoss = 40; // ограничение убытка в пунктах extern int TakeProfit = 70; // профит в пунктах extern int Magic = 123; // магический номер ордеров extern int Slippage = 5; // проскальзывание в пунктах extern string comment = "Мой первый советник";
Значения StopLoss, TakeProfit и Slippage, присвоенные в параметрах, указаны для 4-х значного брокера, соответственно, чтобы эти же значения корректно работали у 5-ти значного ДЦ их нужно умножить на 10, что мы и сделаем в функции OnInit:
//+------------------------------------------------------------------+
//| Expert initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
if (Digits == 3 || Digits == 5)
{
StopLoss *= 10;
TakeProfit *= 10;
Slippage *= 10;
}
return(INIT_SUCCEEDED);
}
Начало нашего советника положено, что уже хорошо. Давайте теперь рассмотрим входные параметры индикатора, а заодно и номера буферов для сигналов:

Обычно я просто копирую параметры индикатора в код своего эксперта, в дальнейшем это позволит нам оптимизировать сам советник. В итоге получаем:
//+------------------------------------------------------------------+ extern double Lots = 0.1; // объём сделки в лотах extern int StopLoss = 40; // ограничение убытка в пунктах extern int TakeProfit = 70; // профит в пунктах extern int Magic = 123; // магический номер ордеров extern int Slippage = 5; // проскальзывание в пунктах extern string comment = "Мой первый советник"; //+------------------------------------------------------------------+ extern int MA_Period = 100; extern double MACDOpenLevel = 5; extern int fast_ema_period = 8; // период быстрой средней extern int slow_ema_period = 16; // период медленной средней extern int signal_period = 9; // период сигнальной линии //+------------------------------------------------------------------+
Переходим к функции OnTick и пишем заготовку обработчика, где описываю алгоритм работы эксперта:
//+------------------------------------------------------------------+
//| Expert tick function |
//+------------------------------------------------------------------+
void OnTick()
{
if (CountBuy() == 0)
{
BuySignal = ...
if (BuySignal > 0)
{
CloseSell();
// Open buy order
}
}
if (CountSell() == 0)
{
SellSignal = ...
if (SellSignal > 0)
{
CloseBuy();
// Open sell order
}
}
} Итак, по шагам:
- if (CountBuy() == 0) — я проверяю есть-ли у меня ордера на покупку, находящиеся в рынке, и если нет, то шаг 2.
- BuySignal = … — проверяем наличие сигнала на покупку по индикатору, кстати, это будет переменная, которую необходимо объявить заранее.
- if (BuySignal > 0) — если есть сигнал на покупку, то переходим к шагу 4.
- CloseSell() — закрываем открытые ордера на продажу, если они есть и переходим к шагу 5.
- Открываем ордер на покупку.
Точно такой же блок делается и для продаж.
Исходя из написанного алгоритма сразу видно, что предварительно нам потребуется несколько функций: CountBuy(), CountSell(), CloseBuy(), CloseSell(), а также написать код получения сигнала с индикатора.
Объявим в коде несколько переменных для дальнейшей работы и необходимые функции:
double BuySignal, SellSignal, SL, TP; int ticket; bool res;
Функция CountBuy():
//+------------------------------------------------------------------+
//| Функция возвращает количество ордеров на покупку | //| находящихся в рынке | //+------------------------------------------------------------------+
int CountBuy()
{ // объявляем переменную, в которой будем хранить количество ордеров
// с типом OP_BUY
int count = 0;
// Объявляем цикл с перебором ордеров
for (int trade = OrdersTotal() - 1; trade >= 0; trade--)
{
// Если удалось выбрать ордер, находящийся в рынке
if (OrderSelect(trade, SELECT_BY_POS, MODE_TRADES))
{
// у этого ордера совпадает валютная пара и его магический номер
if (OrderSymbol() == Symbol() && OrderMagicNumber() == Magic)
{
// а также ордер является ордером на покупку,
if (OrderType() == OP_BUY)
// то увеличиваем значение счётчика count на единицу
count++;
}
}
}
// возвращаем количество ордеров на покупку
return (count);
} Аналогично пишется функция CountSell(), разве что проверяется тип ордера OP_SELL:
//+------------------------------------------------------------------+
//| Функция возвращает количество ордеров на продажу | //| находящихся в рынке | //+------------------------------------------------------------------+
int CountSell()
{
int count = 0;
for (int trade = OrdersTotal() - 1; trade >= 0; trade--)
{
if (OrderSelect(trade, SELECT_BY_POS, MODE_TRADES))
{
if (OrderSymbol() == Symbol() && OrderMagicNumber() == Magic)
{
if (OrderType() == OP_SELL)
count++;
}
}
}
return (count);
}
//+------------------------------------------------------------------+
Далее пишем функцию CloseBuy():
//+------------------------------------------------------------------+
//| Фукнция закрывает все ордера на покупку, находящиеся в рынке | //+------------------------------------------------------------------+
void CloseBuy()
{
// объявляем цикл, в котором выполним перебор все ордеров
// находящихся в рынке
for(int index = OrdersTotal()-1; index >= 0; index--)
{
// если удалось выбрать рыночный ордер
if (OrderSelect(index, SELECT_BY_POS, MODE_TRADES))
{
// если валютная пара ордера, магический номер ордера и тип ордера совпадают
// с тем, что нам необходимо,
if (OrderSymbol() == Symbol() && OrderMagicNumber() == Magic && OrderType() == OP_BUY)
// то пробуем закрыть ордер по рыночной цене
if (!OrderClose(OrderTicket(), OrderLots(), MarketInfo(OrderSymbol(), MODE_BID), Slippage, Black))
// иначе выводим в журнал регистрации сообщение об ошибке
Print("Ошибка закрытия ордера на покупку, ticket ="+ DoubleToStr(OrderTicket()));
}
}
} Пишем практически всё то же самое и для закрытия ордеров на продажу, разница лишь в типе проверяемого ордера и цене закрытия, функция CloseSell():
//+------------------------------------------------------------------+
//| Фукнция закрывает все ордера на продажу, находящиеся в рынке | //+------------------------------------------------------------------+
void CloseSell()
{
for(int index = OrdersTotal()-1; index >= 0; index--)
{
if (OrderSelect(index, SELECT_BY_POS, MODE_TRADES))
{
if (OrderSymbol() == Symbol() && OrderMagicNumber() == Magic && OrderType() == OP_SELL)
if (!OrderClose(OrderTicket(), OrderLots(), MarketInfo(OrderSymbol(), MODE_BID), Slippage, Black))
Print("Ошибка закрытия ордера на продажу, ticket ="+ DoubleToStr(OrderTicket()));
}
}
}
На текущий момент нам осталось получить сигналы от индикаторы и написать код, открывающий ордера по его сигналам.
Для получения сигнала от любого пользовательского индикатора используется функция iCustom, обратившись к справке (F1) видим формат вызова функции:

Теперь мы можем с легкостью получить сигнал на покупку в нашем советнике:
BuySignal = iCustom(NULL, 0, "MyIndicator", MA_Period, MACDOpenLevel, fast_ema_period, slow_ema_period, signal_period, 0, 1);
заодно и сигнал на продажу:
SellSignal = iCustom(NULL, 0, "MyIndicator", MA_Period, MACDOpenLevel, fast_ema_period, slow_ema_period, signal_period, 1, 1);
кстати, обратите внимание на предпоследний параметр mode, который мы передаём в функцию iCustom — это номер буфера индикатора, где хранятся сигналы покупок и продаж.
Таким образом наша функция OnTick() будет выглядеть следующим образом:
//+------------------------------------------------------------------+
//| Expert tick function | //+------------------------------------------------------------------+
void OnTick()
{
if (CountBuy() == 0)
{
// Получаем сигнал на покупку
BuySignal = iCustom(NULL, 0, "MyIndicator", MA_Period, MACDOpenLevel, fast_ema_period, slow_ema_period, signal_period, 0, 1);
// если есть сигнал индикатора
if (BuySignal > 0)
{
// закрываем открытые ордера SELL
CloseSell();
// Open buy order
// открываем ордер на покупку
ticket = OrderSend(Symbol(), OP_BUY, Lots, Ask, Slippage, 0, 0, comment, Magic, 0, Blue);
// если ордер был успешно открыт,
if (ticket > 0)
{
// то его необходимо выбрать, для дальнейшей установки ему StopLoss и TakeProfit
if (OrderSelect(ticket, SELECT_BY_TICKET, MODE_TRADES))
{
// рассчитываем StopLoss и нормализуем цену
SL = NormalizeDouble(Ask-StopLoss*Point, Digits);
// рассчитываем TakeProfit и нормализуем цену
TP = NormalizeDouble(Ask+TakeProfit*Point, Digits);
// попытаемся установить ордеру рассчитанные уровни StopLoss и TakeProfit
res = OrderModify(OrderTicket(), OrderOpenPrice(), SL, TP, 0);
if (!res)
// и если не удалось, то выведем сообщение в журнал регистрации для дальнейшего анализа причин
Print("Ошибка модификации ордера на покупку, ASK="+ DoubleToStr(Ask) +", SL="+ DoubleToStr(SL) +", TP="+ DoubleToStr(TP));
}
}
}
}
if (CountSell() == 0)
{
SellSignal = iCustom(NULL, 0,"MyIndicator", MA_Period, MACDOpenLevel, fast_ema_period, slow_ema_period, signal_period, 1, 1);
if (SellSignal > 0)
{
CloseBuy();
ticket = OrderSend(Symbol(), OP_SELL, Lots, Bid, Slippage, 0, 0, comment, Magic, 0, Red);
if (ticket > 0)
{
if (OrderSelect(ticket, SELECT_BY_TICKET, MODE_TRADES))
{
SL = NormalizeDouble(Bid+StopLoss*Point, Digits);
TP = NormalizeDouble(Bid-TakeProfit*Point, Digits);
res = OrderModify(OrderTicket(), OrderOpenPrice(), SL, TP, 0);
if (!res)
Print("Ошибка модификации ордера на продажу, BID="+ DoubleToStr(Bid) +", SL="+ DoubleToStr(SL) +", TP="+ DoubleToStr(TP));
}
}
}
}
}
Собственно говоря, на этом всё, разработка советника на пользовательском индикаторе завершена.
Полный код эксперта:
//+------------------------------------------------------------------+ //| My_Expert.mq4 | //| xbms, / | //| mailto:[email protected] | //+------------------------------------------------------------------+ #property copyright " xbms" #property link "/" #property version "1.00" #property strict //+------------------------------------------------------------------+ extern double Lots = 0.1; extern int StopLoss = 40; extern int TakeProfit = 70; extern int Magic = 123; extern int Slippage = 5; extern string comment = "Мой первый советник"; //+------------------------------------------------------------------+ extern int MA_Period = 100; extern double MACDOpenLevel = 5; extern int fast_ema_period = 8; // период быстрой средней extern int slow_ema_period = 16; // период медленной средней extern int signal_period = 9; // период сигнальной линии //+------------------------------------------------------------------+ double BuySignal, SellSignal, SL, TP; int ticket; bool res; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { if (Digits == 3 || Digits == 5) { StopLoss *= 10; TakeProfit *= 10; Slippage *= 10; } return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { if (CountBuy() == 0) { // Получаем сигнал на покупку BuySignal = iCustom(NULL, 0, "MyIndicator", MA_Period, MACDOpenLevel, fast_ema_period, slow_ema_period, signal_period, 0, 1); // если есть сигнал индикатора if (BuySignal > 0) { // закрываем открытые ордера SELL CloseSell(); // Open buy order // открываем ордер на покупку ticket = OrderSend(Symbol(), OP_BUY, Lots, Ask, Slippage, 0, 0, comment, Magic, 0, Blue); // если ордер был успешно открыт, if (ticket > 0) { // то его необходимо выбрать, для дальнейшей установки ему StopLoss и TakeProfit if (OrderSelect(ticket, SELECT_BY_TICKET, MODE_TRADES)) { // рассчитываем StopLoss и нормализуем цену SL = NormalizeDouble(Ask-StopLoss*Point, Digits); // рассчитываем TakeProfit и нормализуем цену TP = NormalizeDouble(Ask+TakeProfit*Point, Digits); // попытаемся установить ордеру рассчитанные уровни StopLoss и TakeProfit res = OrderModify(OrderTicket(), OrderOpenPrice(), SL, TP, 0); if (!res) // и если не удалось, то выведем сообщение в журнал регистрации для дальнейшего анализа причин Print("Ошибка модификации ордера на покупку, ASK="+ DoubleToStr(Ask) +", SL="+ DoubleToStr(SL) +", TP="+ DoubleToStr(TP)); } } } } if (CountSell() == 0) { SellSignal = iCustom(NULL, 0,"MyIndicator", MA_Period, MACDOpenLevel, fast_ema_period, slow_ema_period, signal_period, 1, 1); if (SellSignal > 0) { CloseBuy(); ticket = OrderSend(Symbol(), OP_SELL, Lots, Bid, Slippage, 0, 0, comment, Magic, 0, Red); if (ticket > 0) { if (OrderSelect(ticket, SELECT_BY_TICKET, MODE_TRADES)) { SL = NormalizeDouble(Bid+StopLoss*Point, Digits); TP = NormalizeDouble(Bid-TakeProfit*Point, Digits); res = OrderModify(OrderTicket(), OrderOpenPrice(), SL, TP, 0); if (!res) Print("Ошибка модификации ордера на продажу, BID="+ DoubleToStr(Bid) +", SL="+ DoubleToStr(SL) +", TP="+ DoubleToStr(TP)); } } } } } //+------------------------------------------------------------------+ //| Фукнция закрывает все ордера на покупку, находящиеся в рынке | //+------------------------------------------------------------------+ void CloseBuy() { // объявляем цикл, в котором выполним перебор все ордеров // находящихся в рынке for(int index = OrdersTotal()-1; index >= 0; index--) { // если удалось выбрать рыночный ордер if (OrderSelect(index, SELECT_BY_POS, MODE_TRADES)) { // если валютная пара ордера, магический номер ордера и тип ордера совпадают // с тем, что нам необходимо, if (OrderSymbol() == Symbol() && OrderMagicNumber() == Magic && OrderType() == OP_BUY) // то пробуем закрыть ордер по рыночной цене if (!OrderClose(OrderTicket(), OrderLots(), MarketInfo(OrderSymbol(), MODE_BID), Slippage, Black)) // иначе выводим в журнал регистрации сообщение об ошибке Print("Ошибка закрытия ордера на покупку, ticket ="+ DoubleToStr(OrderTicket())); } } } //+------------------------------------------------------------------+ //| Фукнция закрывает все ордера на продажу, находящиеся в рынке | //+------------------------------------------------------------------+ void CloseSell() { for(int index = OrdersTotal()-1; index >= 0; index--) { if (OrderSelect(index, SELECT_BY_POS, MODE_TRADES)) { if (OrderSymbol() == Symbol() && OrderMagicNumber() == Magic && OrderType() == OP_SELL) if (!OrderClose(OrderTicket(), OrderLots(), MarketInfo(OrderSymbol(), MODE_BID), Slippage, Black)) Print("Ошибка закрытия ордера на продажу, ticket ="+ DoubleToStr(OrderTicket())); } } } //+------------------------------------------------------------------+ //| Функция возвращает количество ордеров на покупку | //| находящихся в рынке | //+------------------------------------------------------------------+ int CountBuy() { // объявляем переменную, в которой будем хранить количество ордеров // с типом OP_BUY int count = 0; // Объявляем цикл с перебором ордеров for (int trade = OrdersTotal() - 1; trade >= 0; trade--) { // Если удалось выбрать ордер, находящийся в рынке if (OrderSelect(trade, SELECT_BY_POS, MODE_TRADES)) { // у этого ордера совпадает валютная пара и его магический номер if (OrderSymbol() == Symbol() && OrderMagicNumber() == Magic) { // а также ордер является ордером на покупку, if (OrderType() == OP_BUY) // то увеличиваем значение счётчика count на единицу count++; } } } // возвращаем количество ордеров на покупку return (count); } //+------------------------------------------------------------------+ //| Функция возвращает количество ордеров на продажу | //| находящихся в рынке | //+------------------------------------------------------------------+ int CountSell() { int count = 0; for (int trade = OrdersTotal() - 1; trade >= 0; trade--) { if (OrderSelect(trade, SELECT_BY_POS, MODE_TRADES)) { if (OrderSymbol() == Symbol() && OrderMagicNumber() == Magic) { if (OrderType() == OP_SELL) count++; } } } return (count); } //+------------------------------------------------------------------+
Исходный код советника доступен для скачивания My_Expert.mq4