
Рецепты MQL5 - Как не получить ошибку при установке/изменении торговых уровней?
Введение
Продолжая работу над экспертом из предыдущей статьи Рецепты MQL5 - Изучение свойств позиции в тестере MetaTrader 5, на этот раз внедрим в него еще целый ряд полезных функций, а также усовершенствуем и оптимизируем уже имеющиеся.
Очень часто на форуме-(ах) по программированию на MQL можно увидеть вопросы от новичков, касающиеся ошибок при установке/модификации торговых уровней (Stop Loss, Take Profit, отложенные ордера). Думаю, уже многие знакомы с сообщением в журнале, в котором в конце строки содержится [Invalid stops]. В этой статье мы создадим функции, в которых нормализуются и проверяются на корректность значения торговых уровней перед открытием/модификацией позиции.
На этот раз эксперт будет снабжен внешними параметрами, которые можно будет оптимизировать в тестере MetaTrader 5. Это уже будет немного похоже на простую торговую систему. До настоящей торговой системы, конечно же, пока еще довольно далеко, но не все сразу - нам предстоит еще много работы.
Оптимизацию кода в уже имеющихся функциях будем рассматривать по ходу статьи. Информационную панель со свойствами позиции пока оставим, так как еще нужно рассмотреть некоторые свойства позиции, которые не получить стандартными идентификаторами (нужно обращаться к истории сделок). Но это тема будет раскрыта в одной из последующих статей.
Процесс разработки эксперта
Приступим. Начнем, как всегда, с добавления в начале файла дополнительных перечислений, переменных, массивов и с вспомогательных функций. Нам понадобится функция, которая позволит легко и просто получить свойства символа. Так же легко и просто нужно будет получать и свойства позиции.
В предыдущих статьях в функции GetPositionProperties глобальным переменным присваивались сразу все свойства позиции. Теперь мы сделаем так, чтобы можно было получать каждое свойство по отдельности. Ниже приведены два перечисления для реализации задуманного, а сами функции рассмотрим чуть позже.
//--- Перечисление свойств позиции enum ENUM_POSITION_PROPERTIES { P_SYMBOL = 0, P_MAGIC = 1, P_COMMENT = 2, P_SWAP = 3, P_COMMISSION = 4, P_PRICE_OPEN = 5, P_PRICE_CURRENT = 6, P_PROFIT = 7, P_VOLUME = 8, P_SL = 9, P_TP = 10, P_TIME = 11, P_ID = 12, P_TYPE = 13, P_ALL = 14 }; //--- Перечисление свойств символа enum ENUM_SYMBOL_PROPERTIES { S_DIGITS = 0, S_SPREAD = 1, S_STOPSLEVEL = 2, S_POINT = 3, S_ASK = 4, S_BID = 5, S_VOLUME_MIN = 6, S_VOLUME_MAX = 7, S_VOLUME_LIMIT = 8, S_VOLUME_STEP = 9, S_FILTER = 10, S_UP_LEVEL = 11, S_DOWN_LEVEL = 12, S_ALL = 13 };
Перечисление ENUM_SYMBOL_PROPERTIES содержит далеко не все свойства символа, но его в любой момент можно будет дополнить по мере необходимости. В перечислении присутствуют также пользовательские свойства (10, 11, 12), которые будут рассчитываться из показателей других свойств символа. Так же как и в перечислении свойств позиции, есть идентификатор, с помощью которого можно получить сразу все свойства из перечисления.
Далее идут внешние параметры эксперта:
//--- Внешние параметры эксперта input int NumberOfBars=2; // Кол-во Бычьих/Медвежьих баров для покупки/продажи input double Lot =0.1; // Лот input double StopLoss =50; // Стоп Лосс input double TakeProfit =100; // Тейк Профит input double TrailingStop=10; // Трейлинг Стоп input bool Reverse =true; // Разворот позиции
Рассмотрим подробнее внешние параметры:
- NumberOfBars - в этом параметре задается количество однонаправленных баров для открытия позиции;
- Lot - объем позиции;
- TakeProfit - уровень фиксации прибыли в пунктах. Нулевое значение означает, что Take Profit устанавливать не нужно.
- StopLoss - защитный уровень в пунктах. Нулевое значение означает, что Stop Loss устанавливать не нужно.
- TrailingStop - шаг защитного уровня Трейлинг Стоп в пунктах. Для позиции BUY расчет производится от минимума бара (минимум минус количество пунктов из параметра StopLoss). Для позиции SELL расчет производится от максимума бара (максимум плюс количество пунктов из параметра StopLoss). Нулевое значение означает, что трейлинг отключен.
- Reverse - включает/выключает переворот позиции.
Уточнения требует только параметр NumberOfBars. Не имеет смысла устанавливать в этом параметре значение, например, больше 5, так как это уже довольно редкое явление и открывать позицию уже поздно после такого движения. Поэтому понадобится переменная, с помощью которой можно будет корректировать значение этого параметра:
//--- Для проверки значения внешнего параметра NumberOfBars int AllowedNumberOfBars=0;
От этого параметра также будет зависеть, сколько данных баров будет приниматься в ценовые массивы. Об этом будет рассказано подробнее немного ниже, когда начнем модифицировать пользовательские функции.
Для свойств символа, так же как и для свойств позиции, объявим переменные на глобальном уровне, чтобы можно было получить доступ из любой функции:
//--- Свойства символа int sym_digits=0; // Количество знаков в цене после запятой int sym_spread=0; // Размер спреда в пунктах int sym_stops_level=0; // Ограничитель установки Stop ордеров double sym_point=0.0; // Значение одного пункта double sym_ask=0.0; // Цена ask double sym_bid=0.0; // Цена bid double sym_volume_min=0.0; // Минимальный объем для заключения сделки double sym_volume_max=0.0; // Максимальный объем для заключения сделки double sym_volume_limit=0.0; // Максимально допустимый объем для позиции и ордеров в одном направлении double sym_volume_step=0.0; // Минимальный шаг изменения объема для заключения сделки double sym_offset=0.0; // Отступ от максимально возможной цены для операции double sym_up_level=0.0; // Цена верхнего уровня stop level double sym_down_level=0.0; // Цена нижнего уровня stop level
Так как трейлинг должен рассчитываться от максимума и минимума баров, нам понадобятся массивы для этих данных баров:
//--- Массивы ценовых данных double close_price[]; // Close (цены закрытия бара) double open_price[]; // Open (цены открытия бара) double high_price[]; // High (цены максимума бара) double low_price[]; // Low (цены минимума бара)
Теперь приступим к модификации и созданию функций. У нас уже есть функция GetBarsData, в которой в ценовые массивы копируются цены открытия и закрытия баров. Теперь нам нужны еще максимумы и минимумы. Кроме того, нужно проводить корректировку значения из параметра NumberOfBars. Вот как выглядит эта функция после модификации:
//+------------------------------------------------------------------+ //| Получает значения баров | //+------------------------------------------------------------------+ void GetBarsData() { //--- Скорректируем значение количества баров для условия открытия позиции if(NumberOfBars<=1) AllowedNumberOfBars=2; // Нужно не менее двух баров if(NumberOfBars>=5) AllowedNumberOfBars=5; // и не более 5 else AllowedNumberOfBars=NumberOfBars+1; // и всегда на один больше //--- Установим обратный порядок индексации (... 3 2 1 0) ArraySetAsSeries(close_price,true); ArraySetAsSeries(open_price,true); ArraySetAsSeries(high_price,true); ArraySetAsSeries(low_price,true); //--- Получим цену закрытия бара // Если полученных значений меньше, чем запрошено, вывести сообщение об этом if(CopyClose(_Symbol,Period(),0,AllowedNumberOfBars,close_price)<AllowedNumberOfBars) { Print("Не удалось скопировать значения (" +_Symbol+", "+TimeframeToString(Period())+") в массив цен Close! " "Ошибка "+IntegerToString(GetLastError())+": "+ErrorDescription(GetLastError())); } //--- Получим цену открытия бара // Если полученных значений меньше, чем запрошено, вывести сообщение об этом if(CopyOpen(_Symbol,Period(),0,AllowedNumberOfBars,open_price)<AllowedNumberOfBars) { Print("Не удалось скопировать значения (" +_Symbol+", "+TimeframeToString(Period())+") в массив цен Open! " "Ошибка "+IntegerToString(GetLastError())+": "+ErrorDescription(GetLastError())); } //--- Получим цену максимума бара // Если полученных значений меньше, чем запрошено, вывести сообщение об этом if(CopyHigh(_Symbol,Period(),0,AllowedNumberOfBars,high_price)<AllowedNumberOfBars) { Print("Не удалось скопировать значения (" +_Symbol+", "+TimeframeToString(Period())+") в массив цен High! " "Ошибка "+IntegerToString(GetLastError())+": "+ErrorDescription(GetLastError())); } //--- Получим цену минимума бара // Если полученных значений меньше, чем запрошено, вывести сообщение об этом if(CopyLow(_Symbol,Period(),0,AllowedNumberOfBars,low_price)<AllowedNumberOfBars) { Print("Не удалось скопировать значения (" +_Symbol+", "+TimeframeToString(Period())+") в массив цен Low! " "Ошибка "+IntegerToString(GetLastError())+": "+ErrorDescription(GetLastError())); } }
Не менее двух баров и всегда на один больше нужно потому, что ориентироваться будем только на сформировавшиеся бары, которые начинаются с индекса [1]. Корректировку в этом случае на самом деле можно и не делать, ведь данные баров можно начать копировать с указанного индекса в третьем параметре функций CopyOpen, CopyClose, CopyHigh и CopyLow. Ограничение в пять сформировавшихся баров тоже можно изменить (больше/меньше) на ваше усмотрение.
Функция GetTradingSignal теперь немного усложнилась, так как в зависимости от того, сколько баров указано в параметре NumberOfBars, условие будет формироваться иначе. Кроме того, теперь используется более правильный тип возвращаемого значения - тип ордера:
//+------------------------------------------------------------------+ //| Определяет торговые сигналы | //+------------------------------------------------------------------+ ENUM_ORDER_TYPE GetTradingSignal() { //--- Сигнал на покупку (ORDER_TYPE_BUY) : if(AllowedNumberOfBars==2 && close_price[1]>open_price[1]) return(ORDER_TYPE_BUY); if(AllowedNumberOfBars==3 && close_price[1]>open_price[1] && close_price[2]>open_price[2]) return(ORDER_TYPE_BUY); if(AllowedNumberOfBars==4 && close_price[1]>open_price[1] && close_price[2]>open_price[2] && close_price[3]>open_price[3]) return(ORDER_TYPE_BUY); if(AllowedNumberOfBars==5 && close_price[1]>open_price[1] && close_price[2]>open_price[2] && close_price[3]>open_price[3] && close_price[4]>open_price[4]) return(ORDER_TYPE_BUY); if(AllowedNumberOfBars>=6 && close_price[1]>open_price[1] && close_price[2]>open_price[2] && close_price[3]>open_price[3] && close_price[4]>open_price[4] && close_price[5]>open_price[5]) return(ORDER_TYPE_BUY); //--- Сигнал на продажу (ORDER_TYPE_SELL) : if(AllowedNumberOfBars==2 && close_price[1]<open_price[1]) return(ORDER_TYPE_SELL); if(AllowedNumberOfBars==3 && close_price[1]<open_price[1] && close_price[2]<open_price[2]) return(ORDER_TYPE_SELL); if(AllowedNumberOfBars==4 && close_price[1]<open_price[1] && close_price[2]<open_price[2] && close_price[3]<open_price[3]) return(ORDER_TYPE_SELL); if(AllowedNumberOfBars==5 && close_price[1]<open_price[1] && close_price[2]<open_price[2] && close_price[3]<open_price[3] && close_price[4]<open_price[4]) return(ORDER_TYPE_SELL); if(AllowedNumberOfBars>=6 && close_price[1]<open_price[1] && close_price[2]<open_price[2] && close_price[3]<open_price[3] && close_price[4]<open_price[4] && close_price[5]<open_price[5]) return(ORDER_TYPE_SELL); //--- Отсутствие сигнала (WRONG_VALUE): return(WRONG_VALUE); }
Теперь модифицируем функцию GetPositionProperties. В предыдущих статьях в ней можно было получить все свойства сразу. Но иногда нужно получить только одно из них. Для этого можно, конечно же, пользоваться и стандартными функциями языка, но это не так удобно, как хотелось бы. Ниже представлен код модифицированной функции GetPositionProperties. Теперь, передавая тот или иной идентификатор из пользовательского перечисления ENUM_POSITION_PROPERTIES, можно получить либо отдельное свойство позиции, либо все сразу.
//+------------------------------------------------------------------+ //| Получает свойства позиции | //+------------------------------------------------------------------+ void GetPositionProperties(ENUM_POSITION_PROPERTIES position_property) { //--- Узнаем, есть ли позиция pos_open=PositionSelect(_Symbol); //--- Если позиция есть, получим её свойства if(pos_open) { switch(position_property) { case P_SYMBOL : pos_symbol=PositionGetString(POSITION_SYMBOL); break; case P_MAGIC : pos_magic=PositionGetInteger(POSITION_MAGIC); break; case P_COMMENT : pos_comment=PositionGetString(POSITION_COMMENT); break; case P_SWAP : pos_swap=PositionGetDouble(POSITION_SWAP); break; case P_COMMISSION : pos_commission=PositionGetDouble(POSITION_COMMISSION); break; case P_PRICE_OPEN : pos_price=PositionGetDouble(POSITION_PRICE_OPEN); break; case P_PRICE_CURRENT : pos_cprice=PositionGetDouble(POSITION_PRICE_CURRENT); break; case P_PROFIT : pos_profit=PositionGetDouble(POSITION_PROFIT); break; case P_VOLUME : pos_volume=PositionGetDouble(POSITION_VOLUME); break; case P_SL : pos_sl=PositionGetDouble(POSITION_SL); break; case P_TP : pos_tp=PositionGetDouble(POSITION_TP); break; case P_TIME : pos_time=(datetime)PositionGetInteger(POSITION_TIME); break; case P_ID : pos_id=PositionGetInteger(POSITION_IDENTIFIER); break; case P_TYPE : pos_type=(ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); break; case P_ALL : pos_symbol=PositionGetString(POSITION_SYMBOL); pos_magic=PositionGetInteger(POSITION_MAGIC); pos_comment=PositionGetString(POSITION_COMMENT); pos_swap=PositionGetDouble(POSITION_SWAP); pos_commission=PositionGetDouble(POSITION_COMMISSION); pos_price=PositionGetDouble(POSITION_PRICE_OPEN); pos_cprice=PositionGetDouble(POSITION_PRICE_CURRENT); pos_profit=PositionGetDouble(POSITION_PROFIT); pos_volume=PositionGetDouble(POSITION_VOLUME); pos_sl=PositionGetDouble(POSITION_SL); pos_tp=PositionGetDouble(POSITION_TP); pos_time=(datetime)PositionGetInteger(POSITION_TIME); pos_id=PositionGetInteger(POSITION_IDENTIFIER); pos_type=(ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); break; default: Print("Переданное свойство позиции не учтено в перечислении!"); return; } } //--- Если позиции нет, обнулим переменные свойств позиции else ZeroPositionProperties(); }
Аналогичным образом реализуем функцию GetSymbolProperties для получения свойств символа:
//+------------------------------------------------------------------+ //| Получает свойства символа | //+------------------------------------------------------------------+ void GetSymbolProperties(ENUM_SYMBOL_PROPERTIES symbol_property) { int lot_offset=1; // Количество пунктов для отступа от уровней stops level //--- switch(symbol_property) { case S_DIGITS : sym_digits=(int)SymbolInfoInteger(_Symbol,SYMBOL_DIGITS); break; case S_SPREAD : sym_spread=(int)SymbolInfoInteger(_Symbol,SYMBOL_SPREAD); break; case S_STOPSLEVEL : sym_stops_level=(int)SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL); break; case S_POINT : sym_point=SymbolInfoDouble(_Symbol,SYMBOL_POINT); break; //--- case S_ASK : sym_digits=(int)SymbolInfoInteger(_Symbol,SYMBOL_DIGITS); sym_ask=NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),sym_digits); break; case S_BID : sym_digits=(int)SymbolInfoInteger(_Symbol,SYMBOL_DIGITS); sym_bid=NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),sym_digits); break; //--- case S_VOLUME_MIN : sym_volume_min=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MIN); break; case S_VOLUME_MAX : sym_volume_max=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MAX); break; case S_VOLUME_LIMIT : sym_volume_limit=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_LIMIT); break; case S_VOLUME_STEP : sym_volume_step=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_STEP); break; //--- case S_FILTER : sym_digits=(int)SymbolInfoInteger(_Symbol,SYMBOL_DIGITS); sym_point=SymbolInfoDouble(_Symbol,SYMBOL_POINT); sym_offset=NormalizeDouble(CorrectValueBySymbolDigits(lot_offset*sym_point),sym_digits); break; //--- case S_UP_LEVEL : sym_digits=(int)SymbolInfoInteger(_Symbol,SYMBOL_DIGITS); sym_stops_level=(int)SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL); sym_point=SymbolInfoDouble(_Symbol,SYMBOL_POINT); sym_ask=NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),sym_digits); sym_up_level=NormalizeDouble(sym_ask+sym_stops_level*sym_point,sym_digits); break; //--- case S_DOWN_LEVEL : sym_digits=(int)SymbolInfoInteger(_Symbol,SYMBOL_DIGITS); sym_stops_level=(int)SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL); sym_point=SymbolInfoDouble(_Symbol,SYMBOL_POINT); sym_bid=NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),sym_digits); sym_down_level=NormalizeDouble(sym_bid-sym_stops_level*sym_point,sym_digits); break; //--- case S_ALL : sym_digits=(int)SymbolInfoInteger(_Symbol,SYMBOL_DIGITS); sym_spread=(int)SymbolInfoInteger(_Symbol,SYMBOL_SPREAD); sym_stops_level=(int)SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL); sym_point=SymbolInfoDouble(_Symbol,SYMBOL_POINT); sym_ask=NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),sym_digits); sym_bid=NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),sym_digits); sym_volume_min=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MIN); sym_volume_max=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MAX); sym_volume_limit=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_LIMIT); sym_volume_step=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_STEP); sym_offset=NormalizeDouble(CorrectValueBySymbolDigits(lot_offset*sym_point),sym_digits); sym_up_level=NormalizeDouble(sym_ask+sym_stops_level*sym_point,sym_digits); sym_down_level=NormalizeDouble(sym_bid-sym_stops_level*sym_point,sym_digits); break; //--- default: Print("Переданное свойство символа не учтено в перечислении!"); return; } }
Обратите внимание, что для получения некоторых свойств символа нужно запросить некоторые другие свойства.
Также появилась новая функция CorrectValueBySymbolDigits. В зависимости от того, сколько знаков в цене после запятой, она возвращает соответствующее значение. В функцию можно передавать целое или вещественное число. В зависимости от типа переданных данных используется соответствующий вариант функции. Такое явление называется перегрузкой функций.
//+------------------------------------------------------------------+ //| Коррекция значения по количеству знаков в цене (int) | //+------------------------------------------------------------------+ int CorrectValueBySymbolDigits(int value) { return (sym_digits==3 || sym_digits==5) ? value*=10 : value; } //+------------------------------------------------------------------+ //| Коррекция значения по количеству знаков в цене (double) | //+------------------------------------------------------------------+ double CorrectValueBySymbolDigits(double value) { return (sym_digits==3 || sym_digits==5) ? value*=10 : value; }
В нашем эксперте будет внешний параметр для указания объема (Lot) открываемой позиции. Сделаем функцию CalculateLot, которая будет корректировать объем в соответствии со спецификацией символа:
//+------------------------------------------------------------------+ //| Рассчитывает объем для позиции | //+------------------------------------------------------------------+ double CalculateLot(double lot) { //--- Для корректировки с учетом шага double corrected_lot=0.0; //--- GetSymbolProperties(S_VOLUME_MIN); // Получим минимально возможный лот GetSymbolProperties(S_VOLUME_MAX); // Получим максимально возможный лот GetSymbolProperties(S_VOLUME_STEP); // Получим шаг увеличения/уменьшения лота //--- Скорректируем с учетом шага лота corrected_lot=MathRound(lot/sym_volume_step)*sym_volume_step; //--- Если меньше минимального, вернем минимальный if(corrected_lot<sym_volume_min) return(NormalizeDouble(sym_volume_min,2)); //--- Если больше максимального, вернем максимальный if(corrected_lot>sym_volume_max) return(NormalizeDouble(sym_volume_max,2)); //--- return(NormalizeDouble(corrected_lot,2)); }
Теперь займемся функциями, которые имеют прямое отношение к названию статьи. Они довольно простые и по комментариям в коде будет не сложно разобраться в их назначении.
Функция CalculateTakeProfit предназначена для расчета значения уровня фиксации прибыли:
//+------------------------------------------------------------------+ //| Рассчитывает уровень Take Profit | //+------------------------------------------------------------------+ double CalculateTakeProfit(ENUM_ORDER_TYPE order_type) { //--- Если Take Profit нужен if(TakeProfit>0) { //--- Для рассчитанного значения Take Profit double tp=0.0; //--- Если нужно рассчитать значение для позиции SELL if(order_type==ORDER_TYPE_SELL) { //--- Рассчитаем уровень tp=NormalizeDouble(sym_bid-CorrectValueBySymbolDigits(TakeProfit*sym_point),sym_digits); //--- Вернем рассчитанное значение, если оно ниже нижней границы stops level // Если значение выше или равно, вернем скорректированное значение return(tp<sym_down_level ? tp : sym_down_level-sym_offset); } //--- Если нужно рассчитать значение для позиции BUY if(order_type==ORDER_TYPE_BUY) { //--- Рассчитаем уровень tp=NormalizeDouble(sym_ask+CorrectValueBySymbolDigits(TakeProfit*sym_point),sym_digits); //--- Вернем рассчитанное значение, если оно выше верхней границы stops level // Если значение ниже или равно, вернем скорректированное значение return(tp>sym_up_level ? tp : sym_up_level+sym_offset); } } //--- return(0.0); }
Функция CalculateStopLoss предназначена для расчета значения защитного уровня:
//+------------------------------------------------------------------+ //| Рассчитывает уровень Stop Loss | //+------------------------------------------------------------------+ double CalculateStopLoss(ENUM_ORDER_TYPE order_type) { //--- Если Stop Loss нужен if(StopLoss>0) { //--- Для рассчитанного значения Stop Loss double sl=0.0; //--- Если нужно рассчитать значение для позиции BUY if(order_type==ORDER_TYPE_BUY) { // Рассчитаем уровень sl=NormalizeDouble(sym_ask-CorrectValueBySymbolDigits(StopLoss*sym_point),sym_digits); //--- Вернем рассчитанное значение, если оно ниже нижней границы stops level // Если значение выше или равно, вернем скорректированное значение return(sl<sym_down_level ? sl : sym_down_level-sym_offset); } //--- Если нужно рассчитать значение для позиции SELL if(order_type==ORDER_TYPE_SELL) { //--- Рассчитаем уровень sl=NormalizeDouble(sym_bid+CorrectValueBySymbolDigits(StopLoss*sym_point),sym_digits); //--- Вернем рассчитанное значение, если оно выше верхней границы stops level // Если значение ниже или равно, вернем скорректированное значение return(sl>sym_up_level ? sl : sym_up_level+sym_offset); } } //--- return(0.0); }
Функция CalculateTrailingStop предназначена для расчета значения трейлинга защитного уровня:
//+------------------------------------------------------------------+ //| Рассчитывает уровень Trailing Stop | //+------------------------------------------------------------------+ double CalculateTrailingStop(ENUM_POSITION_TYPE position_type) { //--- Переменные для расчётов double level =0.0; double buy_point =low_price[1]; // Значение Low для Buy double sell_point =high_price[1]; // Значение High для Sell //--- Рассчитаем уровень для позиции BUY if(position_type==POSITION_TYPE_BUY) { //--- Минимум бара минус указанное количество пунктов level=NormalizeDouble(buy_point-CorrectValueBySymbolDigits(StopLoss*sym_point),sym_digits); //--- Если рассчитанный уровень ниже, чем нижний уровень ограничения (stops level), // то расчет закончен, вернем текущее значение уровня if(level<sym_down_level) return(level); //--- Если же не ниже, то попробуем рассчитать от цены bid else { level=NormalizeDouble(sym_bid-CorrectValueBySymbolDigits(StopLoss*sym_point),sym_digits); //--- Если рассчитанный уровень ниже ограничителя, вернем текущее значение уровня // Иначе установим максимально возможный близкий return(level<sym_down_level ? level : sym_down_level-sym_offset); } } //--- Рассчитаем уровень для позиции SELL if(position_type==POSITION_TYPE_SELL) { // Максимум бара плюс указанное кол-во пунктов level=NormalizeDouble(sell_point+CorrectValueBySymbolDigits(StopLoss*sym_point),sym_digits); //--- Если рассчитанный уровень выше, чем верхний уровень ограничения (stops level), // то расчёт закончен, вернем текущее значение уровня if(level>sym_up_level) return(level); //--- Если же не выше, то попробуем рассчитать от цены ask else { level=NormalizeDouble(sym_ask+CorrectValueBySymbolDigits(StopLoss*sym_point),sym_digits); //--- Если рассчитанный уровень выше ограничителя, вернем текущее значение уровня // Иначе установим максимально возможный близкий return(level>sym_up_level ? level : sym_up_level+sym_offset); } } //--- return(0.0); }
Мы подошли к тому, что у нас есть все необходимые функции, которые возвращают корректные значения для торговых операций. Далее напишем функцию ModifyTrailingStop, в которой будет производиться проверка условия на модификацию трейлинга и его модификация, если это условие выполняется. Ниже представлен код этой функции с подробными комментариями.
Обратите внимание, как используются все те функции, которые были созданы/модифицированы выше. С помощью переключателя switch в зависимости от того, какой тип у текущей позиции, определяется соответствующее условие, результат которого сохраняется в переменной condition. Для модификации позиции используется метод PositionModify из торгового класса CTrade стандартной библиотеки.
//+------------------------------------------------------------------+ //| Изменяет уровень Trailing Stop | //+------------------------------------------------------------------+ void ModifyTrailingStop() { //--- Если включен трейлинг и StopLoss if(TrailingStop>0 && StopLoss>0) { double new_sl=0.0; // Для расчета нового уровня Stop loss bool condition=false; // Для проверки условия на модификацию //--- Получим флаг наличия/отсутствия позиции pos_open=PositionSelect(_Symbol); //--- Если есть позиция if(pos_open) { //--- Получим свойства символа GetSymbolProperties(S_ALL); //--- Получим свойства позиции GetPositionProperties(P_ALL); //--- Получим уровень для Stop Loss new_sl=CalculateTrailingStop(pos_type); //--- В зависимости от типа позиции проверим соответствующее условие на модификацию Trailing Stop switch(pos_type) { case POSITION_TYPE_BUY : //--- Если новое значение для Stop Loss выше, // чем текущее значение плюс установленный шаг condition=new_sl>pos_sl+CorrectValueBySymbolDigits(TrailingStop*sym_point); break; case POSITION_TYPE_SELL : //--- Если новое значение для Stop Loss ниже, // чем текущее значение минус установленный шаг condition=new_sl<pos_sl-CorrectValueBySymbolDigits(TrailingStop*sym_point); break; } //--- Если Stop Loss есть, то сравним значения перед модификацией if(pos_sl>0) { //--- Если выполняется условие на модификацию ордера, т.е. новое значение ниже/выше, // чем текущее, модифицируем защитный уровень позиции if(condition) { if(!trade.PositionModify(_Symbol,new_sl,pos_tp)) Print("Ошибка при модификации позиции: ",GetLastError()," - ",ErrorDescription(GetLastError())); } } //--- Если Stop Loss нет, то просто установим его if(pos_sl==0) { if(!trade.PositionModify(_Symbol,new_sl,pos_tp)) Print("Ошибка при модификации позиции: ",GetLastError()," - ",ErrorDescription(GetLastError())); } } } }
Теперь приведем в порядок функцию TradingBlock в соответствии со всеми изменениями выше. Так же, как и в функции ModifyTrailingStop, будем определять все значения переменных для торгового приказа с помощью переключателя switch. Это значительно сокращает объем кода и упрощает последующие модификации, так как вместо одной ветки для двух типов позиции остается одна.
//+------------------------------------------------------------------+ //| Торговый блок | //+------------------------------------------------------------------+ void TradingBlock() { ENUM_ORDER_TYPE signal=WRONG_VALUE; // Переменная для приема сигнала string comment="hello :)"; // Комментарий для позиции double tp=0.0; // Take Profit double sl=0.0; // Stop Loss double lot=0.0; // Объем для расчета позиции в случае переворота позиции double position_open_price=0.0; // Цена открытия позиции ENUM_ORDER_TYPE order_type=WRONG_VALUE; // Тип ордера для открытия позиции ENUM_POSITION_TYPE opposite_position_type=WRONG_VALUE; // Противоположный тип позиции //--- Получим сигнал signal=GetTradingSignal(); //--- Если сигнала нет, выходим if(signal==WRONG_VALUE) return; //--- Узнаем, есть ли позиция pos_open=PositionSelect(_Symbol); //--- Получим все свойства символа GetSymbolProperties(S_ALL); //--- Определим значения торговым переменным switch(signal) { //--- Присвоим переменным значения для BUY case ORDER_TYPE_BUY : position_open_price=sym_ask; order_type=ORDER_TYPE_BUY; opposite_position_type=POSITION_TYPE_SELL; break; //--- Присвоим переменным значения для SELL case ORDER_TYPE_SELL : position_open_price=sym_bid; order_type=ORDER_TYPE_SELL; opposite_position_type=POSITION_TYPE_BUY; break; } //--- Рассчитаем уровни Take Profit и Stop Loss sl=CalculateStopLoss(order_type); tp=CalculateTakeProfit(order_type); //--- Если позиции нет if(!pos_open) { //--- Скорректируем объём lot=CalculateLot(Lot); //--- Откроем позицию // Если позиция не открылась, вывести сообщение об этом if(!trade.PositionOpen(_Symbol,order_type,lot,position_open_price,sl,tp,comment)) { Print("Ошибка при открытии позиции: ",GetLastError()," - ",ErrorDescription(GetLastError())); } } //--- Если позиция есть else { //--- Получим тип позиции GetPositionProperties(P_TYPE); //--- Если позиция противоположна сигналу и включен переворот позиции if(pos_type==opposite_position_type && Reverse) { //--- Получим объём позиции GetPositionProperties(P_VOLUME); //--- Скорректируем объём lot=pos_volume+CalculateLot(Lot); //--- Откроем позицию. Если позиция не открылась, вывести сообщение об этом if(!trade.PositionOpen(_Symbol,order_type,lot,position_open_price,sl,tp,comment)) { Print("Ошибка при открытии позиции: ",GetLastError()," - ",ErrorDescription(GetLastError())); } } } //--- return; }
Еще нужно внести одно довольно важное исправление в функцию SetInfoPanel, но для начала подготовим несколько вспомогательных функций, которые показывают, как/где в текущий момент используется программа:
//+------------------------------------------------------------------+ //| Возвращает флаг тестирования | //+------------------------------------------------------------------+ bool IsTester() { return(MQL5InfoInteger(MQL5_TESTER)); } //+------------------------------------------------------------------+ //| Возвращает флаг оптимизации | //+------------------------------------------------------------------+ bool IsOptimization() { return(MQL5InfoInteger(MQL5_OPTIMIZATION)); } //+------------------------------------------------------------------+ //| Возвращает флаг визуального режима тестирования | //+------------------------------------------------------------------+ bool IsVisualMode() { return(MQL5InfoInteger(MQL5_VISUAL_MODE)); } //+------------------------------------------------------------------+ //| Возвращает флаг режима реального времени вне тестера, | //| если все условия выполняются | //+------------------------------------------------------------------+ bool IsRealtime() { if(!IsTester() && !IsOptimization() && !IsVisualMode()) return(true); else return(false); }
В функции SetInfoPanel теперь нужно всего лишь добавить условие, которое будет говорить программе, что информационную панель нужно выводить только в режимах визуализации и реального времени. Если этого не сделать, то время тестирования будет возрастет примерно в 4-5 раз. Это особенно актуально, когда проводится оптимизация параметров.
//+------------------------------------------------------------------+ //| Устанавливает информационную панель | //|------------------------------------------------------------------+ void SetInfoPanel() { //--- Режим визуализации или реального времени if(IsVisualMode() || IsRealtime()) { // Остальной код функции SetInfoPanel() // ... } }
Осталось только внести изменения в главные функции программы, и можно будет начинать оптимизировать параметры и тестировать эксперта.
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Инициализируем новый бар CheckNewBar(); //--- Получим свойства и установим панель GetPositionProperties(P_ALL); //--- Установим информационную панель SetInfoPanel(); //--- return(0); }
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- Если бар не новый, выходим if(!CheckNewBar()) return; //--- Если есть новый бар else { GetBarsData(); // Получим данные баров TradingBlock(); // Проверим условия и торгуем ModifyTrailingStop(); // Изменим уровень Trailing Stop } //--- Получим свойства и обновим значения на панели GetPositionProperties(P_ALL); //--- Обновим информационную панель SetInfoPanel(); }
//+------------------------------------------------------------------+ //| Торговое событие | //+------------------------------------------------------------------+ void OnTrade() { //--- Получить свойства позиции и обновить значения на панели GetPositionProperties(P_ALL); //--- Обновим информационную панель SetInfoPanel(); }
Оптимизация параметров и тестирование эксперта
Теперь произведем оптимизацию параметров. Настройки тестера установим, например так, как на рисунке ниже:
Рис. 1. Настройки тестера для оптимизации параметров.
Настройки эксперта установим с широкими диапазонами параметров:
Рис. 2. Настройки эксперта для оптимизации параметров.
На двухъядерном процессоре (Intel Core2 Duo P7350 @ 2.00GHz) время оптимизации заняло около 7 минут. Результат по максимальному фактору восстановления получился вот таким:
Рис. 3. Результат теста по максимальному фактору восстановления.
Заключение
На этом закончим. Разбирайтесь, тестируйте, оптимизируйте, экспериментируйте и отпадайте. В конце статьи вы можете скачать исходный код этого эксперта для изучения.





- Бесплатные приложения для трейдинга
- Форексный VPS бесплатно на 24 часа
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования
Ну обычные флаги
0х1 0х2 0х4 0х8 .... затем смотреть наличие и подгружать что надо.
Да, точно. Не сразу понял, но так было бы ещё удобнее. Я пока не совсем разобрался с побитовыми операциями, попробую поэкспериментировать. В Справке что-то сложно-усваиваемый материал по этой теме. Не понимаю на все 100%, а хочется понимать, что делаешь. )) Может быть кто-нибудь напишет статью на эту тему.
Чтобы не получить ошибку при установке/изменении торговых уровней необходимо еще приводить эти уровни к кратности минимального изменения цены (SYMBOL_TRADE_TICK_SIZE). Минимальное изменение цены не всегда совпадает со значением пункта.
А как же закрыть позицию? Если в эксперте есть и условия (сигнал) на закрытие позиций, как это сделать, где можно посмотреть такие же примеры?