//+------------------------------------------------------------------+ //| SimpleTrailing.mq4 | //| Copyright 2017, MetaQuotes Software Corp. | //| https://www.mql5.com | //+------------------------------------------------------------------+ /* ЭТАП 1. Для того чтобы наш советник работал необходимо описать вход в торговлю. Опишем вход в сделку на основании индикатора и соответственно к этому советнику Mouving Average мы опищем инструмент работы как ТрейлингСтоп. Как обычно зададим внешние параметры для советника. Зададим начальный лот и от скольких пунктов ниже-выше цены будет начинатся наш стоп-лосс Следующий параметр - TrailingStep по мере удаления цены (допутсим по ордеру buy) цена пошла вверх - через сколько шагов мы будем перемещать стоп-лос вслед за ценой. Далее параметр Magic, проскальзование Slippage. Также нам необходимы параметры для определения периодов Mouving Average - это MaPeriod (для определения периода Mouving Average)и MaShift (сдвиг). Переходим к ЭТАПУ 2. */ extern double Lots = 0.1; extern int TrailingStop = 30; extern int TrailingStep = 10; extern int Magic = 123; extern int Slippage = 5; extern int MaPeriod = 42; extern int MaShift = 1; int timeprev = 0; #property copyright "Copyright 2017, MetaQuotes Software Corp." #property link "https://www.mql5.com" #property version "1.00" #property strict //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ /* ЭТАП 2. В функции OnInit() мы сразу приведём к 5 знакам значения параметра int TrailingStop, int TrailingStep и int Slippage. В функции OnInit эта процедура выполняется только один раз при начале работы советника. Переходим к ЭТАПУ 3 в функции OnTick(). */ int OnInit() { if (Digits == 3 || Digits == 5) { TrailingStop *= 10; TrailingStep *= 10; Slippage *= 10; } return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ /* ЭТАП 3. Данная функция выполняется каждый раз при поступлении котировки. Предлагается открывать ордера, проверять индикатор Mouving Average на новой свече (баре) Объявим новую переменную. Это будет глобальная переменная типа int timeprev и зададим значение по умолчанию (инициализируем переменную) = 0; В функции OnTick мы получим время открытия свечи. Выделив функцию Time и нажав F1 мы вызываем учебник для ознакомления с выделенным значением. Итак, Time[] это не функция, а Массив-таймсерия, содержащий время открытия каждого бара текущего графика. Данные типа datetime представляют собой время в секундах, прошедшее с 00:00 1 января 1970 года. Индексация элементов таймсерий производится задом наперед, ОТ ПОСЛЕДНЕГО К ПЕРВОМУ. Текущий бар, самый последний в массиве, имеет индекс 0. Самый старый бар, первый на графике, имеет индекс Bars-1. См. также iTime(). То есть мы получили открытие текущего бара и далее будем сравнивать его с предыдущими. Если приходит котировка на том же самом баре, то ничего не делаем - просто выходим из события (функции OnTick), иначе мы присваиваем в качестве значения для timeprev значение функции Time[]. Таким образом мы избегаем повторное выполнение всех операций которые описаны в функции onTick(). Итак, необходимо получить теперь значение индикатор MA, объявим переменную здесь же типа double maprice и получим для нашей переменной значение индикатора iMA (Mouving Average). Выделяем iMA и нажимаем F1 для перехода в справку - Возвращает значение технического индикатора Moving Average. Пропишем в функцию требуемые ей параметры Symbol() - вид валютнйо пары - по умолчанию текущий вид пары, это нас устраивает, timeframe - преопределённый период графика валютной пары - ставим 0 это будет означать тайфрейм текущего открытого периода, далее параметр MaPeriod - Период усреднения для вычисления индикатора - это наша заявленная переменная в самом верху описания советника, просто указываем как MaPeriod, далее ma_shift - это Сдвиг индикатора относительно ценового графика также уже объявленная и предопределённая величина переменной, обозначим как MaShift, далее ma_method - метод усреднения Может быть любым из значений перечисления ENUM_MA_METHOD обозначим как EMU_SMA, далее applied_price[in] Используемая цена Может быть одним из значений перечисления ENUM_APPLIED_PRICE обозначим как PRISE_CLOSE (то есть ориентируемся по ценам закрытия), далее shift [in] Индекс получаемого значения из индикаторного буфера (сдвиг относительно текущего бара на указанное количество периодов назад) - данная переменная также нами заявлена ранее как MaShift сдвиг берём с прошлой свечи, поэтому значение данного параметра для функции iMa = 1. Теперь надо определить где у нас цена? Выше индикатора iMA или ниже? Если цена Ask выше индикатора iMa (maprice) значит открываем ордер на покупку buy, если ниже индикатора значит на продажу sell. Итак, если Ask больше мапрайс и если CountBuy() == о и если CountSell() == о, то есть ордера на поккупку и продажу не открыты иначе у нас пок аждой свече будет открываться по ордеру - нам это не надо. Функций CountBuy()и CountSell() у нас нет значит необходимо будет их описать, для этого перейдём в ЭТАП 4. ЭТАП 3. Продолжение. Вызываем функцию OrderSend() и заносим внеё известные нам параметры: переменную Symbol() типа string - это валютная пара по умолчанию текущая, int cmd - торговая операция, записываем как OP_BUY отправить ордер на покупку, double volume - Количество лотов, price [in] Цена открытия, slippage [in] Максимально допустимое отклонение цены для рыночных ордеров (ордеров на покупку или продажу), stoploss [in] Цена закрытия ордера при достижении уровня убыточности (0 в случае отсутствия уровня убыточности) - ВНИМАНИЕ пока укажем 0 для параметра стоп-лосс, но стоп-лосс у нас будет - его обеспечит нам TrailingStop, takeprofit [in] Цена закрытия ордера при достижении уровня прибыльности (0 в случае отсутствия уровня прибыльности) - тоже указывать не будем, указываться будет по стоп-лоссу, comment=NULL [in] Текст комментария ордера. Последняя часть комментария может быть изменена торговым сервером, magic=0 [in] Магическое число ордера. Может использоваться как определяемый пользователем идентификатор - укажем просто как Magic, expiration=0 [in] Срок истечения отложенного ордера укажем значение 0 (без срока), arrow_color=clrNONE [in] Цвет открывающей стрелки на графике. Если параметр отсутствует или его значение равно CLR_NONE, то открывающая стрелка не отображается на графике - задаём как Blue, ФУНКЦИЯ ВОЗВРАЩАЕТ НОМЕР ТИКЕТА, который назначен ордеру торговым сервером или -1 в случае неудачи. Чтобы получить информацию об ошибке, необходимо вызвать функцию GetLastError(). Если возращаемое значение будет -1 то есть < 0 тогда выведем сообщение об ошибке в журнал. Скопируем и подредактируем для открытия ордера на продажу. Механизм открытия ордеров готов, теперь переходим к ЭТАПУ 5 и опишем функцию Trailing(); */ void OnTick() { if (timeprev == Time[0]) return; timeprev = Time[0]; double maprice = iMA(Symbol(), 0, MaPeriod, MaShift, MODE_SMA, PRICE_CLOSE, 1); if (Ask > maprice && CountBuy() == 0 && CountSell() == 0) { if (OrderSend(Symbol(), OP_BUY, Lots, Ask, Slippage, 0, 0, "", Magic, 0, Blue) < 0) { Print("Ошибка открытия ордера на покупку"); } } if (Bid < maprice && CountBuy() == 0 && CountSell() == 0) { if (OrderSend(Symbol(), OP_SELL, Lots, Bid, Slippage, 0, 0, "", Magic, 0, Red) < 0) { Print("Ошибка открытия ордера на продажу"); } } Trailing(); } /* ЭТАП 5. Опишем функцию Trailing(); Функция типа void то есть она не возвращает никаких значений. Вспоминаем идею - мы открыли ордер на продажу, на падении цены если выставить трейлинг-стоп, который закроется тогда когда отобьётся промежуточный отрицательный результат. Начнём с цикла для перебирания всех ордеров в рынке записью if (OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) После чего проверяем наш ли это ордер записью if (OrderSymbol() == Symbol() && OrderMagicNumber() == Magic) тогда для ордера на продажу if (OrderType() == OP_BUY) будет свой расчёт - нам надо вычислить где должен находиться стоп-лосс записью Мы вычиляем разницу между ценой открытия и ценой закрытия и сраниваем эту разницу с величиной Трейлинг-стопа записью if (Bid - OrderOpenPrice() > TrailingStop*Point || OrderStopLoss == 0); - здесь мы не забываем что в нашей идее стоп-лос равен нулю); то выполняем ещё одну проверку для перемещения ордера с заданным шагом - это TrailingSrep записью if (OrderStopLoss < Bid - (TrailingStep+TrailingStep)*Point || (|| означает или) OrderStopLoss == 0) - где умножение на Point есть приведение к цене и возникает необходимость модификации ордера к 4 и 5 значной цене записью OrderModify(OrderTicket(), OrderOpenPrice, Bid - TrailingStop*Point) ОДНАКО Bid - TrailingStop*Point необходимо нормализовать отдельно иначе терминал не воспримет значение для стоп-лос (оно сейчас не корректно) у нас для этого функция Normalizedouble, в итоге запись будет выглядеть так - OrderModify(OrderTicket(), OrderOpenPrice, NormalizeDouble(Bid - TrailingStop*Point, Digits)); Вернёмся к OrderModify() типа bool то есть функция возвращает true при успешном завершении и false в случае ошибки. Необходимо здесь же выполнить проверку добавлением условия if (!OrderModify(OrderTicket(), OrderOpenPrice, NormalizeDouble(Bid - TrailingStop*Point, Digits))); и для ошибки необходимо сообщение записью Print("Ошибка модификации ордера на покупку"); TrailingStop готов, осталось проверить на ошибки. Проверено! 13 апреля 2017 года. */ void Trailing() { for (int i=OrdersTotal() -1; i>=0; i--) { if (OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) { if (OrderSymbol() == Symbol() && OrderMagicNumber() == Magic) { if (OrderType() == OP_BUY) { if (Bid - OrderOpenPrice() > TrailingStop*Point || OrderStopLoss() == 0) { if (OrderStopLoss() < Bid - (TrailingStep+TrailingStop)*Point || OrderStopLoss() == 0) { if (!OrderModify(OrderTicket(), OrderOpenPrice(), NormalizeDouble(Bid - TrailingStop*Point, Digits), 0, 0)) Print("Ошибка модификации ордера на покупку"); } } } if (OrderType() == OP_SELL) { if (OrderOpenPrice() - Ask > TrailingStop*Point || OrderStopLoss() == 0) { if (OrderStopLoss() > Ask + (TrailingStep+TrailingStop)*Point || OrderStopLoss() == 0) { if (!OrderModify(OrderTicket(), OrderOpenPrice(), NormalizeDouble(Ask + TrailingStop*Point, Digits), 0, 0)) Print("Ошибка модификации ордера на продажу"); } } } } } } } //+------------------------------------------------------------------+ /* ЭТАП 4. Опишем функцию тиап int CountBuy() Функция будет возвращать количество ордеров на покупку. Её значение - целое число. Объявим переменную int count и цикл для которого будет возвращено значение count. Проверяем ордера записью if (OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) и если да то проверяем наш ли это ордер открыт ли он советником? И к нашей ли валютной паре он относится? записью if (OrderSymbol() == Symbol() && OrderMagicNumber() == Magic && OrderType() == OP_BUY) ТОГДА строкой ниже описываем увеличение количества ордеров на 1 записью count++; Теперь необходимо описать функцию типа int CountSell(), скопируем описание int CountBuy() и отредактируем. После чего возвращаемся к ЭТАПУ 3. */ //--------------------------------------------------------------------------------------------- int CountBuy() { int count = 0; for (int i=OrdersTotal()-1; i>=0; i--) { if (OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) { if (OrderSymbol() == Symbol() && OrderMagicNumber() == Magic && OrderType() == OP_BUY) count++; } } return(count); } //--------------------------------------------------------------------------------------------- int CountSell() { int count = 0; for (int i=OrdersTotal()-1; i>=0; i--) { if (OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) { if (OrderSymbol() == Symbol() && OrderMagicNumber() == Magic && OrderType() == OP_SELL) count++; } } return(count); }