На прошлом уроке мы с вами писали индикатор, который указывает входы в рынок. Соответственно сегодня мы займёмся разработкой эксперта, использующего его сигналы.
Начнём с создания шаблона советника(как это сделать мы разбирали на уроке №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 | //| Copyright 2017, xbms, http://mql.su | //| mailto:xbms@mail.ru | //+------------------------------------------------------------------+ #property copyright "Copyright 2017, xbms" #property link "http://mql.su" #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