
Как сделать любой тип Trailing Stop и подключить к советнику
Содержание
- Введение
- Базовый класс трейлинга
- Классы тралов по индикаторам
- Класс трала по указанным значениям уровней StopLoss
- Подключаем трейлинг-стоп к советнику
- Заключение
Введение
В продолжении темы о трейлинг-стоп, начатой в прошлой статье, сегодня рассмотрим классы трейлингов для удобного создания различных алгоритмов трала StopLoss позиций. На базе созданных классов можно будет делать любые алгоритмы для смещения стопов: по отступу стопа от текущей цены, по индикаторам, по указанным значениям уровней StopLoss, и т.д. После прочтения статьи, мы сможем создавать и подключать к любым советникам любые алгоритмы смещения стопов позиций. При этом само подключение и использование трала будет удобным и понятным.
Рассмотрим вкратце алгоритм работы трейлинг-стоп. Условимся, что для каждого трала можно использовать три условия его работы:
- старт трейлинга — количество пунктов прибыли позиции, при достижении которых запускается трейлинг-стоп;
- шаг трала — количество пунктов, которые должна пройти цена в сторону прибыли позиции, для следующего смещения StopLoss позиции;
- дистанция трала — расстояние от текущей цены, на котором удерживается StopLoss.
Эти три параметра могут быть применимы к любому тралу. Любой из этих параметров может как присутствовать в настройках трала, так и отсутствовать в случае, если он не нужен либо заменяется каким-либо значением в алгоритме трейлинга. Примером замены параметра "дистанция трала" может служить значение индикатора, на которое устанавливается стоп-лосс позиции. При этом, если задействовать этот параметр, то стоп будет устанавливаться не на цену, указанную индикатором, а с отступом от указанной цены на значение дистанции в пунктах.
В общем, указанные три параметра являются наиболее используемыми в различных трейлингах, и их мы будем учитывать при создании классов тралов.
При перемещении стоп-лосс позиции на требуемую цену, обязательно должны быть выполнены проверки:
- цена StopLoss не должна быть ближе к текущей цене на значение уровня StopLevel символа (SYMBOL_TRADE_STOPS_LEVEL — минимальный отступ в пунктах от текущей цены закрытия для установки Stop ордеров);
- цена StopLoss не должна равняться уже установленной, и должна быть выше текущего уровня стоп-лосс для длинной позиции и ниже — для короткой;
- если в алгоритме трала используются перечисленные выше параметры, то дополнительно нужно проверить условия, установленные этими параметрами.
Это базовые проверки. Любой алгоритм трала работает одинаково: на вход подаётся требуемая для установки стопа цена, проверяются все нужные ограничения и, если все проверки пройдены, то стоп позиции переносится на указанный уровень.
Так может работать простой трал:
- в параметре "старт трейлинга" указывается прибыль в пунктах, при достижении которой нужно запустить трейлинг, либо ноль, если этот параметр не используется;
- в параметре "шаг трала" указывается, через сколько пунктов прибыли нужно подтягивать стоп за ценой, либо ноль, если этот параметр не используется;
- в параметре "дистанция трала" устанавливается расстояние от текущей цены до стопа позиции, на котором нужно держать стоп, либо ноль, если этот параметр не используется;
- дополнительно можно установить символ и магик позиций, которые нужно тралить, либо NULL и -1 соответственно — если тралить нужно позиции всех символов и с любым магиком.
При достижении прибыли позиции указанного количества пунктов, запускается трейлинг и устанавливается стоп позиции на указанное расстояние от текущей цены. Далее, после того как цена ещё прошла указанное количество пунктов в сторону прибыли позиции (шаг трала), стоп позиции опять переносится за ценой так, чтобы держать от неё заданную дистанцию. И так постоянно до тех пор, пока цена не пойдёт против позиции. В этом случае стоп остаётся на уже установленном уровне. Как только цена достигнет его, позиция будет закрыта в прибыли по StopLoss. Таким образом трейлинг-стоп позволяет сохранять прибыль, закрывая позицию при развороте цены, и при этом давая цене "гулять" в пределах заданного отступа стопа от цены, шаг за шагом подтягивая стоп за ценой, если та движется в направлении позиции.
Вместо параметра "дистанция трала" можно указать значение цены, получаемое, например, от индикатора. В этом случае мы получим трал по индикатору. Можно передать значение одной из предыдущих свечей (High, Low, например) — тогда получим трал по ценам баров, и т.д. Но базовый трал — его алгоритм, остаётся неизменным.
Таким образом, чтобы создавать любые алгоритмы трейлинг-стоп, необходимо сначала создать простой трал, позволяющий передавать в него нужные цены для стоп-лосс позиций, и передвигающий StopLoss позиции на этот уровень с выполнением всех необходимых проверок.
В прошлой статье мы уже рассматривали подключаемые функции, где был простой трал и тралы по различным индикаторам. Такой подход позволяет подключить трейлинг-стоп к советнику для трала позиций текущего символа. Но в нём есть и ограничения: для каждого трала по индикатору нужно в самом советнике создавать индикатор и отправлять в функцию трала его хендл. Сами же функции трейлинга могут работать только с позициями текущего символа.
Если же использовать классы трейлингов, то мы можем создать множество экземпляров одного класса трейлинга с различными настройками, и тогда в одном советнике смогут работать одновременно все созданные таким образом тралы по определённому, заданному программистом, алгоритму. Т.е. для каждого набора параметров практически "за пару строк кода" будет создан собственный трал, который будет работать по своему алгоритму. Каждые созданные тралы можно запускать в советнике по неким условиям, создавая сложные алгоритмы трейлинга StopLoss позиций.
Даже если создать два одинаковых трала, но для разных символов, то для каждого символа будет создан собственный трал, который будет работать по данным того символа, который был указан при создании трала. Это сильно упрощает использование тралов в своих программах: достаточно только создать трал с нужными параметрами и вызывать его в обработчиках советника. Справедливости ради, следует отметить, что такой подход в некоторых алгоритмах может быть неоптимален, так как для каждого типа тралов запускается свой собственный перебор существующих позиций в терминале. Если делать действительно универсальный алгоритм, использующий для каждого трала один и тот же список позиций терминала, то может получиться так, что затраты на его проектирование и разработку будут не сопоставимы с его востребованностью. И к тому же, это выходит за рамки данной статьи.
Итак, для начала нам необходимо написать основу: класс простого трейлинг-стоп, передвигающего стоп-лосс позиций на указанную цену с выполнением всех необходимых проверок.
В прошлой статье этот простой базовый трал назывался TrailingStopByValue(). Здесь мы его назовём SimpleTrailing — "Простой Трейлинг". И его можно будет полноценно использовать в своих программах.
Как и в прошлой статье, все классы трейлингов будем располагать в одном файле. Далее этот файл просто подключается к советнику и используется. Обычно подключаемые файлы хранятся в папке \MQL5\Include\, либо в подпапке этого каталога.
Базовый класс трейлинга
В папке терминала \MQL5\Include\ создадим новую подпапку Trailings\, а в ней новый файл класса с именем Trailings.mqh:
Имя класса должно быть CSimpleTrailing, имя создаваемого файла Trailings.mqh, а базовым классом должен быть класс базового объекта Стандартной Библиотеки CObject:
По завершении работы мастера MQL получаем такую заготовку класса в папке \MQL5\Include\Trailings\Trailings.mqh:
//+------------------------------------------------------------------+ //| Trailings.mqh | //| Copyright 2024, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2024, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" class CSimpleTrailing : public CObject { private: public: CSimpleTrailing(); ~CSimpleTrailing(); }; //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ CSimpleTrailing::CSimpleTrailing() { } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ CSimpleTrailing::~CSimpleTrailing() { } //+------------------------------------------------------------------+
Подключим файл базового объекта Стандартной Библиотеки к созданному файлу:
//+------------------------------------------------------------------+ //| Trailings.mqh | //| Copyright 2024, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2024, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" #include <Object.mqh> //+------------------------------------------------------------------+ //| Класс простого трала StopLoss позиций | //+------------------------------------------------------------------+ class CSimpleTrailing : public CObject
Для чего необходимо наследовать все создаваемые классы тралов от базового объекта Стандартной Библиотеки?
Это позволит легко создавать из этих классов коллекции тралов — помещать их в списки, искать в списках нужные объекты, и т.д. То есть, если мы хотим просто использовать будущие объекты классов как экземпляры в файлах советников, то наследоваться от CObject не обязательно. Но если мы хотим сделать из этих классов полноценные коллекции и использовать все возможности Стандартной Библиотеки, то эти классы должны быть наследниками базового класса CObject:
Класс CObject является базовым классом для построения стандартной библиотеки MQL5 и обеспечивает всем своим потомкам возможность быть элементом связанного списка.
Кроме того, определяется ряд виртуальных методов для дальнейшей реализации в классах-потомках.
В приватной секции класса объявим три метода:
class CSimpleTrailing : public CObject { private: //--- проверяет критерии модификации StopLoss позициии и возвращает флаг bool CheckCriterion(ENUM_POSITION_TYPE pos_type, double pos_open, double pos_sl, double value_sl, MqlTick &tick); //--- модифицирует StopLoss позиции по её тикету bool ModifySL(const ulong ticket, const double stop_loss); //--- возвращает размер StopLevel в пунктах int StopLevel(void); protected:
Метод CheckCriterion() будет возвращать флаг того, что все необходимые проверки для модификации StopLoss позиции успешно пройдены (либо нет).
Метод ModifySL() будет модифицировать стоп-лосс позиции по указанному значению.
Метод StopLevel() будет возвращать значение свойства StopLevel символа.
Все эти методы в прошлой статье были отдельными функциями. Теперь же они будут работать в составе класса базового трейлинга, от которого далее будем наследовать другие классы других трейлингов.
В защищённой секции класса объявим все необходимые переменные для работы класса:
protected: string m_symbol; // торговый символ long m_magic; // идентификатор эксперта double m_point; // Point символа int m_digits; // Digits символа int m_offset; // дистанция стопа от цены int m_trail_start; // прибыль в пунктах для запуска трала uint m_trail_step; // шаг трала uint m_spread_mlt; // множитель спреда для возврата значения StopLevel bool m_active; // флаг активности трала //--- рассчитывает и возвращает уровень StopLoss выбранной позиции virtual double GetStopLossValue(ENUM_POSITION_TYPE pos_type, MqlTick &tick); public:
Из всех переменных, объявленных здесь, пояснений требует только "множитель спреда". Значение StopLevel символа — это дистанция до текущей цены, ближе которой нельзя поставить стоп-лосс. И, если значение StopLevel установлено на сервере нулевым, то это не означает отсутствия StopLevel. Это означает то, что уровень стопов плавающий и обычно равен двум спредам. Иногда трём. Всё зависит от настроек сервера. Поэтому здесь можно будет самостоятельно задать этот множитель. Если используется двойной спред для значения StopLevel, то значение в переменную m_spread_mlt устанавливается равное двум (по умолчанию). Если тройной спред — то трём, и т.д.
Виртуальный метод GetStopLossValue() рассчитывает и возвращает цену StopLoss для позиции. В разных типах трейлингов уровень стоп-лосс, на который необходимо поставить стоп позиции, рассчитывается по-разному. Именно по этой причине этот метод объявлен виртуальным, и должен переопределяться в каждом наследуемом классе трейлингов, если в них расчёт стоп-лосс отличается от того, что будет написан в этом методе этого класса.
В публичной секции класса напишем методы для установки и возврата значений переменным, объявленным в защищённой секции класса, а также конструкторы и деструктор класса:
public: //--- установка параметров трала void SetSymbol(const string symbol) { this.m_symbol = (symbol==NULL || symbol=="" ? ::Symbol() : symbol); this.m_point =::SymbolInfoDouble(this.m_symbol, SYMBOL_POINT); this.m_digits = (int)::SymbolInfoInteger(this.m_symbol, SYMBOL_DIGITS); } void SetMagicNumber(const long magic) { this.m_magic = magic; } void SetStopLossOffset(const int offset) { this.m_offset = offset; } void SetTrailingStart(const int start) { this.m_trail_start = start; } void SetTrailingStep(const uint step) { this.m_trail_step = step; } void SetSpreadMultiplier(const uint value) { this.m_spread_mlt = value; } void SetActive(const bool flag) { this.m_active = flag; } //--- возврат параметров трала string Symbol(void) const { return this.m_symbol; } long MagicNumber(void) const { return this.m_magic; } int StopLossOffset(void) const { return this.m_offset; } int TrailingStart(void) const { return this.m_trail_start; } uint TrailingStep(void) const { return this.m_trail_step; } uint SpreadMultiplier(void) const { return this.m_spread_mlt; } bool IsActive(void) const { return this.m_active; } //--- запуск трала с отступом StopLoss от цены bool Run(void); //--- конструкторы CSimpleTrailing() : m_symbol(::Symbol()), m_point(::Point()), m_digits(::Digits()), m_magic(-1), m_trail_start(0), m_trail_step(0), m_offset(0), m_spread_mlt(2) {} CSimpleTrailing(const string symbol, const long magic, const int trailing_start, const uint trailing_step, const int offset); //--- деструктор ~CSimpleTrailing() {} };
Класс будет иметь два конструктора:
- Первый конструктор — по умолчанию, где в качестве символа будет использован текущий, магик будет установлен как -1 (для всех без исключения позиций текущего символа), а параметры трала установлены в ноль.
- Второй конструктор будет параметрическим, где в его формальных параметрах будут передаваться в конструктор наименование символа, значение магика позиций и три параметра трала — старт, шаг и отступ.
Рассмотрим реализацию параметрического конструктора:
//+------------------------------------------------------------------+ //| Параметрический конструктор | //+------------------------------------------------------------------+ CSimpleTrailing::CSimpleTrailing(const string symbol, const long magic, const int trail_start, const uint trail_step, const int offset) : m_spread_mlt(2) { this.SetSymbol(symbol); this.m_magic = magic; this.m_trail_start= trail_start; this.m_trail_step = trail_step; this.m_offset = offset; }
В отличие от конструктора по умолчанию, где в строке инициализации всем переменным устанавливаются значения текущего символа и нулевые значения параметров трала, здесь в конструктор передаются значения, которые и устанавливаются в соответствующие переменные. Метод SetSymbol() устанавливает значения сразу нескольким переменным:
void SetSymbol(const string symbol) { this.m_symbol = (symbol==NULL || symbol=="" ? ::Symbol() : symbol); this.m_point =::SymbolInfoDouble(this.m_symbol, SYMBOL_POINT); this.m_digits = (int)::SymbolInfoInteger(this.m_symbol, SYMBOL_DIGITS); }
В обоих конструкторах множитель спреда устанавливается в значение 2. При необходимости, его можно будет изменить методом SetSpreadMultiplier().
Виртуальный метод, рассчитывающий и возвращающий уровень StopLoss выбранной позиции:
//+------------------------------------------------------------------+ //| Рассчитывает и возвращает уровень StopLoss выбранной позиции | //+------------------------------------------------------------------+ double CSimpleTrailing::GetStopLossValue(ENUM_POSITION_TYPE pos_type, MqlTick &tick) { //--- в зависимости от типа позиции рассчитываем и возвращаем уровень StopLoss switch(pos_type) { case POSITION_TYPE_BUY : return(tick.bid - this.m_offset * this.m_point); case POSITION_TYPE_SELL : return(tick.ask + this.m_offset * this.m_point); default : return 0; } }
Так как это простой трал, то здесь стоплосс позиции всегда удерживается на заданной дистанции от текущей цены.
Рассчитанные значения StopLoss позиций возвращаются из метода и далее проверяются в методе CheckCriterion():
//+------------------------------------------------------------------+ //|Проверяет критерии модификации StopLoss позициии и возвращает флаг| //+------------------------------------------------------------------+ bool CSimpleTrailing::CheckCriterion(ENUM_POSITION_TYPE pos_type, double pos_open, double pos_sl, double value_sl, MqlTick &tick) { //--- если стоп позиции и уровень стопа для модификации равны, или передан нулевой StopLoss - возвращаем false if(::NormalizeDouble(pos_sl - value_sl, this.m_digits) == 0 || value_sl==0) return false; //--- переменные трала double trailing_step = this.m_trail_step * this.m_point; // переводим шаг трала в цену double stop_level = this.StopLevel() * this.m_point; // переводим StopLevel символа в цену int pos_profit_pt = 0; // прибыль позиции в пунктах //--- в зависимости от типа позиции проверяем условия для модицикации StopLoss switch(pos_type) { //--- длинная позиция case POSITION_TYPE_BUY : pos_profit_pt = int((tick.bid - pos_open) / this.m_point); // рассчитываем прибыль позиции в пунктах if(tick.bid - stop_level > value_sl // если уровень StopLoss ниже цены с отложенным вниз от неё уровнем StopLevel (соблюдена дистанция по StopLevel) && pos_sl + trailing_step < value_sl // если уровень StopLoss выше, чем шаг трала, отложенный вверх от текущего StopLoss позиции && (this.m_trail_start == 0 || pos_profit_pt > this.m_trail_start) // если тралим при любой прибыли, или прибыль позиции в пунктах больше значения начала трейлинга - возвращаем true ) return true; break; //--- короткая позиция case POSITION_TYPE_SELL : pos_profit_pt = int((pos_open - tick.ask) / this.m_point); // рассчитываем прибыль позиции в пунктах if(tick.ask + stop_level < value_sl // если уровень StopLoss выше цены с отложенным вверх от неё уровнем StopLevel (соблюдена дистанция по StopLevel) && (pos_sl - trailing_step > value_sl || pos_sl == 0) // если уровень StopLoss ниже, чем шаг трала, отложенный вниз от текущего StopLoss позиции, или у позиции ещё не установлен StopLoss && (this.m_trail_start == 0 || pos_profit_pt > this.m_trail_start) // если тралим при любой прибыли, или прибыль позиции в пунктах больше значения начала трейлинга - возвращаем true ) return true; break; //--- по умолчанию вернём false default: break; } //--- условия не подошли - возвращаем false return false; }
Если переданное в метод значение StopLoss удовлетворяет всем критериям всех фильтров, то метод возвращает true, и стоп-лосс позиция модифицируется при помощи метода ModifySL():
//+------------------------------------------------------------------+ //| Модифицирует StopLoss позиции по тикету | //+------------------------------------------------------------------+ bool CSimpleTrailing::ModifySL(const ulong ticket, const double stop_loss) { //--- если взведён флаг остановки эксперта - сообщаем об этом в журнал и возвращаем false if(::IsStopped()) { Print("The Expert Advisor is stopped, trading is disabled"); return false; } //--- если позицию не удалось выбрать по тикету - сообщаем об этом в журнал и возвращаем false ::ResetLastError(); if(!::PositionSelectByTicket(ticket)) { ::PrintFormat("%s: Failed to select position by ticket number %I64u. Error %d", __FUNCTION__, ticket, ::GetLastError()); return false; } //--- объявляем структуры торгового запроса и результата запроса MqlTradeRequest request= {}; MqlTradeResult result = {}; //--- заполняем структуру запроса request.action = TRADE_ACTION_SLTP; request.symbol = ::PositionGetString(POSITION_SYMBOL); request.magic = ::PositionGetInteger(POSITION_MAGIC); request.tp = ::PositionGetDouble(POSITION_TP); request.position = ticket; request.sl = ::NormalizeDouble(stop_loss, this.m_digits); //--- если торговую операцию отправить не удалось - сообщаем об этом в журнал и возвращаем false if(!::OrderSend(request, result)) { PrintFormat("%s: OrderSend() failed to modify position #%I64u. Error %d",__FUNCTION__, ticket, ::GetLastError()); return false; } //--- успешно отправлен запрос на изменение StopLoss позиции return true; }
Для получения уровня стопов используем метод StopLevel():
//+------------------------------------------------------------------+ //| Возвращает размер StopLevel в пунктах | //+------------------------------------------------------------------+ int CSimpleTrailing::StopLevel(void) { int spread = (int)::SymbolInfoInteger(this.m_symbol, SYMBOL_SPREAD); int stop_level = (int)::SymbolInfoInteger(this.m_symbol, SYMBOL_TRADE_STOPS_LEVEL); return int(stop_level == 0 ? spread * this.m_spread_mlt : stop_level); }
Так как уровень стопов — величина не постоянная и может динамически изменяться, то каждый раз при обращении к методу запрашиваем нужные значения из свойств символа. Если StopLevel символа установлен равным нулю, то используем значение спреда символа, умноженного на 2. Это значение установлено по умолчанию.
Все три вышерассмотренных метода мы уже делали в прошлой статье как отдельные функции. Теперь же эти методы сокрыты в приватной секции класса и выполняют свои функции без доступа к ним извне.
Основным же методом трала является метод Run(), запускающий цикл выбора позиций и перемещение их стопов на рассчитанные значения:
//+------------------------------------------------------------------+ //| Запускает простой трейлинг с отступом StopLoss от цены | //+------------------------------------------------------------------+ bool CSimpleTrailing::Run(void) { //--- если отключен - уходим if(!this.m_active) return false; //--- переменные трала MqlTick tick = {}; // структура цен bool res = true; // результат модификации всех позиций //--- проверка корректности данных по символу if(this.m_point==0) { //--- попробуем ещё раз получить данные ::ResetLastError(); this.SetSymbol(this.m_symbol); if(this.m_point==0) { ::PrintFormat("%s: Correct data was not received for the %s symbol. Error %d",__FUNCTION__, this.m_symbol, ::GetLastError()); return false; } } //--- в цикле по общему количеству открытых позиций int total =::PositionsTotal(); for(int i = total - 1; i >= 0; i--) { //--- получаем тикет очередной позиции ulong pos_ticket =::PositionGetTicket(i); if(pos_ticket == 0) continue; //--- получаем символ и магик позиции string pos_symbol = ::PositionGetString(POSITION_SYMBOL); long pos_magic = ::PositionGetInteger(POSITION_MAGIC); //--- если позиция не соответствует фильтру по символу и магику - уходим if((this.m_magic != -1 && pos_magic != this.m_magic) || (pos_symbol != this.m_symbol)) continue; //--- если цены получить не удалось - идём дальше if(!::SymbolInfoTick(this.m_symbol, tick)) continue; //--- получаем тип позиции, цену её открытия и уровень StopLoss ENUM_POSITION_TYPE pos_type = (ENUM_POSITION_TYPE)::PositionGetInteger(POSITION_TYPE); double pos_open =::PositionGetDouble(POSITION_PRICE_OPEN); double pos_sl =::PositionGetDouble(POSITION_SL); //--- получаем рассчитанный уровень StopLoss double value_sl = this.GetStopLossValue(pos_type, tick); //--- если условия для модификации StopLoss подходят - модифицируем стоп позиции и добавляем результат к переменной res if(this.CheckCriterion(pos_type, pos_open, pos_sl, value_sl, tick)) res &=this.ModifySL(pos_ticket, value_sl); } //--- по окончании цикла возвращаем результат модификации каждой подходящей по фильтру "символ/магик" позиции return res; }
Метод перебирает позиции в списке действующих позиций терминала, фильтрует их по символу и магику и перемещает стопы позиций на рассчитанную дистанцию от текущей цены. Результат модификации StopLoss каждой позиции добавляется к переменной res. По окончании цикла модификации стопов всех позиций, в переменной будет записан либо флаг true — только при успешной модификации стопов всех позиций, подходящих по фильтру символ/магик. Либо, если хоть одна позиция не была успешно модифицирована, флаг будет содержать значение false. Сделано это для дополнительного контроля модификации стопов извне.
Теперь подключим этот класс к советнику для проверки его работоспособности.
Для теста возьмём советник Moving Average.mq5 из папки \MQL5\Experts\Examples\ терминала и сохраним его под именем MovingAverageWithSimpleTrail.mq5.
Подключим к советнику файл класса трейлинга, впишем дополнительные параметры в настройки и создадим экземпляр класса простого трала:
//+------------------------------------------------------------------+ //| MovingAverageWithSimpleTrail.mq5 | //| Copyright 2000-2024, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2000-2024, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" #include <Trade\Trade.mqh> #include <Trailings\Trailings.mqh> input group " - Moving Averages Expert Parameters -" input double MaximumRisk = 0.02; // Maximum Risk in percentage input double DecreaseFactor = 3; // Descrease factor input int MovingPeriod = 12; // Moving Average period input int MovingShift = 6; // Moving Average shift input group " - Simple Trailing Parameters -" input int InpTrailingStart = 10; // Trailing start input int InpTrailingStep = 20; // Trailing step in points input int InpTrailingOffset = 30; // Trailing offset in points input bool InpUseTrailing = true; // Use Trailing Stop //--- int ExtHandle=0; bool ExtHedging=false; CTrade ExtTrade; CSimpleTrailing ExtTrailing; // экземпляр класса простого трала #define MA_MAGIC 1234501 //+------------------------------------------------------------------+
В обработчике OnInit() установим параметры трала:
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit(void) { //--- prepare trade class to control positions if hedging mode is active ExtHedging=((ENUM_ACCOUNT_MARGIN_MODE)AccountInfoInteger(ACCOUNT_MARGIN_MODE)==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING); ExtTrade.SetExpertMagicNumber(MA_MAGIC); ExtTrade.SetMarginMode(); ExtTrade.SetTypeFillingBySymbol(Symbol()); //--- Moving Average indicator ExtHandle=iMA(_Symbol,_Period,MovingPeriod,MovingShift,MODE_SMA,PRICE_CLOSE); if(ExtHandle==INVALID_HANDLE) { printf("Error creating MA indicator"); return(INIT_FAILED); } //--- устанавливаем параметры трала ExtTrailing.SetActive(InpUseTrailing); ExtTrailing.SetSymbol(Symbol()); ExtTrailing.SetMagicNumber(MA_MAGIC); ExtTrailing.SetTrailingStart(InpTrailingStart); ExtTrailing.SetTrailingStep(InpTrailingStep); ExtTrailing.SetStopLossOffset(InpTrailingOffset); //--- ok return(INIT_SUCCEEDED); }
В обработчике OnTick() запустим трейлинг:
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick(void) { //--- if(SelectPosition()) CheckForClose(); else CheckForOpen(); //--- запускаем трейлинг ExtTrailing.Run(); }
Это всё, что нужно вписать дополнительно в файл советника. Скомпилируем его и запустим в тестере стратегий на графике EURUSD, M15, одиночный тест за последний год. Все тики без задержек исполнения.
Параметры установим такими (трал отключен):
Запустим тест с отключенным тралом и посмотрим результат:
Теперь включим использование трала:
и запустим точно такой же тест.
Результат тестирования получили такой:
Результат немного лучше. Видно, что трал немного улучшает статистику на этих настройках.
Теперь на основе созданного простого трала можно делать любые другие трейлинги.
Сделаем несколько классов тралов по индикаторам — по индикатору Parabolic SAR и по различным скользящим средним из списка трендовых индикаторов, представленных в клиентском терминале.
Классы тралов по индикаторам
Продолжим писать код в том же файле. Но прежде чем начать новый класс трала по индикатору, вспомним, что для тралов по значениям индикаторов необходимо создавать сам индикатор, с данных которого будем получать значения уровней стоп-лосс. Для индикаторов — для их создания, необходимо указывать символ и таймфрейм, на данных которых индикатор строит свою серию данных. И ещё нужно сохранить хендл созданного индикатора в переменной — чтобы по этому хендлу обращаться к индикатору за получением данных. Получение данных по хендлу не зависит от типа индикатора — данные получаются функцией CopyBuffer() с указанием индикатора по хендлу.
Все действия по получению данных от индикатора должны быть прописаны в классах тралов по индикаторам. Но в этом случае придётся все эти действия прописывать в каждом классе каждого трала, работающих по разным индикаторам. Это можно, но не оптимально. Лучше создать базовый класс тралов по индикаторам, в котором будут реализованы методы для получения данных от индикатора по хендлу и прописаны некоторые переменные, одинаковые для каждого индикатора (символ, таймфрейм, индекс таймсерии данных и хендл индикатора). А сами классы тралов наследовать от этого базового. Тогда структура класса будет упорядочена — в базовом классе организован доступ к данным индикатора по хендлу, а в наследуемом — создание требуемого индикатора и работа с его данными.
Продолжим писать код в файле Trailings.mqh. Напишем базовый класс тралов по индикаторам:
//+------------------------------------------------------------------+ //| Базовый класс тралов по индикаторам | //+------------------------------------------------------------------+ class CTrailingByInd : public CSimpleTrailing { protected: ENUM_TIMEFRAMES m_timeframe; // таймфрейм индикатора int m_handle; // хэндл индикатора uint m_data_index; // бар данных индикатора string m_timeframe_descr; // описание таймфрейма //--- возвращает данные индикатора double GetDataInd(void) const { return this.GetDataInd(this.m_data_index); } //--- рассчитывает и возвращает уровень StopLoss выбранной позиции virtual double GetStopLossValue(ENUM_POSITION_TYPE pos_type, MqlTick &tick); public: //--- установка параметров void SetTimeframe(const ENUM_TIMEFRAMES timeframe) { this.m_timeframe=(timeframe==PERIOD_CURRENT ? ::Period() : timeframe); this.m_timeframe_descr=::StringSubstr(::EnumToString(this.m_timeframe), 7); } void SetDataIndex(const uint index) { this.m_data_index=index; } //--- возврат параметров ENUM_TIMEFRAMES Timeframe(void) const { return this.m_timeframe; } uint DataIndex(void) const { return this.m_data_index; } string TimeframeDescription(void) const { return this.m_timeframe_descr; } //--- возвращает данные индикатора с указанного индекса таймсерии double GetDataInd(const int index) const; //--- конструкторы CTrailingByInd(void) : CSimpleTrailing(::Symbol(), -1, 0, 0, 0), m_timeframe(::Period()), m_handle(INVALID_HANDLE), m_data_index(1), m_timeframe_descr(::StringSubstr(::EnumToString(::Period()), 7)) {} CTrailingByInd(const string symbol, const ENUM_TIMEFRAMES timeframe, const long magic, const int trail_start, const uint trail_step, const int trail_offset) : CSimpleTrailing(symbol, magic, trail_start, trail_step, trail_offset), m_data_index(1), m_handle(INVALID_HANDLE) { this.SetTimeframe(timeframe); } //--- деструктор ~CTrailingByInd(void) { if(this.m_handle!=INVALID_HANDLE) { ::IndicatorRelease(this.m_handle); this.m_handle=INVALID_HANDLE; } } };
Класс унаследован от класса простого трала, поэтому в нём доступен весь функционал этого класса, плюс реализованы методы для доступа к данным индикатора по хендлу.
В защищённой секции класса объявлены переменные класса, метод для получения данных от индикатора по хендлу и виртуальный метод для расчёта уровней StopLoss, переопределяющий одноимённый метод родительского класса.
В публичной секции класса расположены методы установки и получения свойств индикатора и конструкторы с деструктором.
В конструкторе по умолчанию, в его строке инициализации, в родительский класс передаётся текущий символ, магик со значением -1 (все позиции) и нулевые параметры для трала. В качестве таймфрейма графика для расчёта индикатора устанавливается период текущего графика.
В параметрическом конструкторе символ и период графика для расчёта индикатора, и все параметры трала передаются в формальных параметрах конструктора.
В деструкторе класса освобождается расчётная часть индикатора, а переменной, хранящей хендл, устанавливается значение INVALID_HANDLE.
Метод, возвращающий данные индикатора с указанного индекса таймсерии:
//+------------------------------------------------------------------+ //| Возвращает данные индикатора с указанного индекса таймсерии | //+------------------------------------------------------------------+ double CTrailingByInd::GetDataInd(const int index) const { //--- если хендл невалидный - сообщаем об этом и возвращаем "пустое значение" if(this.m_handle==INVALID_HANDLE) { ::PrintFormat("%s: Error. Invalid handle",__FUNCTION__); return EMPTY_VALUE; } //--- получаем значение буфера индикатора по указанному индексу double array[1]; ::ResetLastError(); if(::CopyBuffer(this.m_handle, 0, index, 1, array)!=1) { ::PrintFormat("%s: CopyBuffer() failed. Error %d", __FUNCTION__, ::GetLastError()); return EMPTY_VALUE; } //--- возвращаем полученное значение return array[0]; }
При помощи CopyBuffer() получаем значение одного бара индикатора по указанному индексу и возвращаем его при успешном получении. При ошибке — выводим сообщение в журнал и возвращаем пустое значение (EMPTY_VALUE).
Функция CopyBuffer() работает с данными любого индикатора — по его хендлу. Таким образом, этот метод является универсальным и используется без изменений в каждом классе трейлингов по индикаторам.
Виртуальный метод, рассчитывающий и возвращающий уровень StopLoss выбранной позиции:
//+------------------------------------------------------------------+ //| Рассчитывает и возвращает уровень StopLoss выбранной позиции | //+------------------------------------------------------------------+ double CTrailingByInd::GetStopLossValue(ENUM_POSITION_TYPE pos_type, MqlTick &tick) { //--- получаем значение индикатора как уровень для StopLoss double data=this.GetDataInd(); //--- в зависимости от типа позиции рассчитываем и возвращаем уровень StopLoss switch(pos_type) { case POSITION_TYPE_BUY : return(data!=EMPTY_VALUE ? data - this.m_offset * this.m_point : tick.bid); case POSITION_TYPE_SELL : return(data!=EMPTY_VALUE ? data + this.m_offset * this.m_point : tick.ask); default : return 0; } }
В этом методе, в родительском классе, просто выполняется расчёт отступа уровня стоп-лосс от цены. В этом же классе в этом методе необходимо получить данные от индикатора и передать их в качестве уровня StopLoss. Если данные от индикатора не были получены, то метод возвратит текущую цену, что не даст выставить стоп-лосс позиции, так как выставить стоп на текущую цену не позволит уровень StopLevel.
В итоге в этом классе реализован доступ к данным индикатора по его хендлу и расчёт уровня для StopLoss позиции по значению индикатора, и все его методы будут доступны в дочерних классах.
Теперь можно на основе созданного класса создать классы трейлингов по индикаторам.
Класс трала по Parabolic SAR
Продолжим писать код в том же файле \MQL5\Include\Trailings\Trailings.mqh.
Класс трала по Parabolic SAR, как и все другие классы трейлингов по индикаторам, должен быть унаследован от базового класса тралов по индикаторам:
//+------------------------------------------------------------------+ //| Класс трала StopLoss позиций по Parabolic SAR | //+------------------------------------------------------------------+ class CTrailingBySAR : public CTrailingByInd { private: double m_sar_step; // параметр Step Parabolic SAR double m_sar_max; // параметр Maximum Parabolic SAR public: //--- установка параметров Parabolic SAR void SetSARStep(const double step) { this.m_sar_step=(step<0.0001 ? 0.0001 : step); } void SetSARMaximum(const double max) { this.m_sar_max =(max <0.0001 ? 0.0001 : max); } //--- возврат параметров Parabolic SAR double SARStep(void) const { return this.m_sar_step; } double SARMaximum(void) const { return this.m_sar_max; } //--- создаёт индикатор Parabolic SAR, возвращает результат bool Initialize(const string symbol, const ENUM_TIMEFRAMES timeframe, const double sar_step, const double sar_maximum); //--- конструкторы CTrailingBySAR() : CTrailingByInd(::Symbol(), ::Period(), -1, 0, 0, 0) { this.Initialize(this.m_symbol, this.m_timeframe, 0.02, 0.2); } CTrailingBySAR(const string symbol, const ENUM_TIMEFRAMES timeframe, const long magic, const double sar_step, const double sar_maximum, const int trail_start, const uint trail_step, const int trail_offset); //--- деструктор ~CTrailingBySAR(){} };
В приватной секции класса объявлены переменные для хранения значений параметров индикатора Parabolic SAR.
В публичной секции написаны методы для установки и возврата значений свойств индикатора, объявлен метод для создания индикатора и два конструктора — по умолчанию и параметрический.
В конструкторе по умолчанию создаётся индикатор на текущем символе и периоде графика, а тралу устанавливаются нулевые значения его параметров.
В параметрическом конструкторе все параметры передаются через входные переменные конструктора и далее создаётся индикатор на переданных в конструктор параметрах:
//+------------------------------------------------------------------+ //| Параметрический конструктор | //+------------------------------------------------------------------+ CTrailingBySAR::CTrailingBySAR(const string symbol, const ENUM_TIMEFRAMES timeframe, const long magic, const double sar_step, const double sar_maximum, const int trail_start, const uint trail_step, const int trail_offset) : CTrailingByInd(symbol, timeframe, magic, trail_start, trail_step, trail_offset) { this.Initialize(symbol, timeframe, sar_step, sar_maximum); }
Метод Initialize() создаёт индикатор Parabolic SAR и возвращает результат его создания:
//+------------------------------------------------------------------+ //| создаёт индикатор Parabolic SAR, возвращает результат | //+------------------------------------------------------------------+ bool CTrailingBySAR::Initialize(const string symbol, const ENUM_TIMEFRAMES timeframe, const double sar_step, const double sar_maximum) { this.SetSymbol(symbol); this.SetTimeframe(timeframe); this.SetSARStep(sar_step); this.SetSARMaximum(sar_maximum); ::ResetLastError(); this.m_handle=::iSAR(this.m_symbol, this.m_timeframe, this.m_sar_step, this.m_sar_max); if(this.m_handle==INVALID_HANDLE) { ::PrintFormat("Failed to create iSAR(%s, %s, %.3f, %.2f) handle. Error %d", this.m_symbol, this.TimeframeDescription(), this.m_sar_step, this.m_sar_max, ::GetLastError()); } return(this.m_handle!=INVALID_HANDLE); }
Протестируем созданный класс трала по Parabolic SAR.
Для теста возьмём советник из стандартной поставки \MQL5\Experts\Advisors\ExpertMACD.mq5, сохраним его под новым именем ExpertMACDWithTrailingBySAR.mq5 и добавим необходимые строки кода.
Подключим файл с классами тралов, добавим новые входные переменные и объявим экземпляр класса трейлинга:
//+------------------------------------------------------------------+ //| ExpertMACD.mq5 | //| Copyright 2000-2024, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2000-2024, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" //+------------------------------------------------------------------+ //| Include | //+------------------------------------------------------------------+ #include <Expert\Expert.mqh> #include <Expert\Signal\SignalMACD.mqh> #include <Expert\Trailing\TrailingNone.mqh> #include <Expert\Money\MoneyNone.mqh> #include <Trailings\Trailings.mqh> //+------------------------------------------------------------------+ //| Inputs | //+------------------------------------------------------------------+ //--- inputs for expert input group " - ExpertMACD Parameters -" input string Inp_Expert_Title ="ExpertMACD"; int Expert_MagicNumber =10981; bool Expert_EveryTick =false; //--- inputs for signal input int Inp_Signal_MACD_PeriodFast =12; input int Inp_Signal_MACD_PeriodSlow =24; input int Inp_Signal_MACD_PeriodSignal=9; input int Inp_Signal_MACD_TakeProfit =50; input int Inp_Signal_MACD_StopLoss =20; input group " - Trailing By SAR Parameters -" input ENUM_TIMEFRAMES InpTimeframeSAR = PERIOD_CURRENT; // Parabolic SAR Timeframe input double InpStepSAR = 0.02; // Parabolic SAR Step input double InpMaximumSAR = 0.2; // Parabolic SAR Maximum input int InpTrailingStart = 0; // Trailing Start input uint InpTrailingStep = 0; // Trailing Step input int InpTrailingOffset = 0; // Trailing Offset input bool InpUseTrailing = true; // Use Trailing Stop //+------------------------------------------------------------------+ //| Global expert object | //+------------------------------------------------------------------+ CExpert ExtExpert; CTrailingBySAR ExtTrailing;
В обработчике OnInit() инициализируем трал по параболику:
//+------------------------------------------------------------------+ //| Initialization function of the expert | //+------------------------------------------------------------------+ int OnInit(void) { //--- Initializing trailing if(ExtTrailing.Initialize(NULL,PERIOD_CURRENT,InpStepSAR,InpMaximumSAR)) { ExtTrailing.SetMagicNumber(Expert_MagicNumber); ExtTrailing.SetTrailingStart(InpTrailingStart); ExtTrailing.SetTrailingStep(InpTrailingStep); ExtTrailing.SetStopLossOffset(InpTrailingOffset); ExtTrailing.SetActive(InpUseTrailing); } //--- Initializing expert
В обработчике OnTick() запустим трал в работу:
//+------------------------------------------------------------------+ //| Function-event handler "tick" | //+------------------------------------------------------------------+ void OnTick(void) { ExtExpert.OnTick(); ExtTrailing.Run(); }
Скомпилируем советник и запустим в тестере с параметрами по умолчанию:
Видим, как стопы позиций исправно тралятся по значениям первого бара Parabolic SAR.
Теперь создадим классы тралов по различным скользящим средним, представленным в клиентском терминале.
Классы тралов по скользящим средним
Эти классы отличаются от класса трейлинга по Parabolic SAR только набором входных параметров и тем, что в методе Initialize() создаётся соответствующий классу индикатор.
Исходя из этого, рассмотрим лишь один класс: класс трейлинга по адаптивной скользящей средней:
//+------------------------------------------------------------------+ //| Класс трала StopLoss позиций по Adaptive Moving Average | //+------------------------------------------------------------------+ class CTrailingByAMA : public CTrailingByInd { private: int m_period; // параметр Period AMA int m_fast_ema; // параметр Fast EMA Period int m_slow_ema; // параметр Slow EMA Period int m_shift; // параметр Shift AMA ENUM_APPLIED_PRICE m_price; // параметр Applied Price AMA public: //--- установка параметров AMA void SetPeriod(const uint period) { this.m_period=int(period<1 ? 9 : period); } void SetFastEMAPeriod(const uint period) { this.m_fast_ema=int(period<1 ? 2 : period); } void SetSlowEMAPeriod(const uint period) { this.m_slow_ema=int(period<1 ? 30 : period); } void SetShift(const int shift) { this.m_shift = shift; } void SetPrice(const ENUM_APPLIED_PRICE price) { this.m_price = price; } //--- возврат параметров AMA int Period(void) const { return this.m_period; } int FastEMAPeriod(void) const { return this.m_fast_ema; } int SlowEMAPeriod(void) const { return this.m_slow_ema; } int Shift(void) const { return this.m_shift; } ENUM_APPLIED_PRICE Price(void) const { return this.m_price; } //--- создаёт индикатор AMA, возвращает результат bool Initialize(const string symbol, const ENUM_TIMEFRAMES timeframe, const int period, const int fast_ema, const int slow_ema, const int shift, const ENUM_APPLIED_PRICE price); //--- конструкторы CTrailingByAMA() : CTrailingByInd(::Symbol(), ::Period(), -1, 0, 0, 0) { this.Initialize(this.m_symbol, this.m_timeframe, 9, 2, 30, 0, PRICE_CLOSE); } CTrailingByAMA(const string symbol, const ENUM_TIMEFRAMES timeframe, const long magic, const int period, const int fast_ema, const int slow_ema, const int shift, const ENUM_APPLIED_PRICE price, const int trail_start, const uint trail_step, const int trail_offset); //--- деструктор ~CTrailingByAMA(){} }; //+------------------------------------------------------------------+ //| Параметрический конструктор | //+------------------------------------------------------------------+ CTrailingByAMA::CTrailingByAMA(const string symbol, const ENUM_TIMEFRAMES timeframe, const long magic, const int period, const int fast_ema, const int slow_ema, const int shift, const ENUM_APPLIED_PRICE price, const int trail_start, const uint trail_step, const int trail_offset) : CTrailingByInd(symbol, timeframe, magic, trail_start, trail_step, trail_offset) { this.Initialize(symbol, timeframe, period, fast_ema, slow_ema, shift, price); } //+------------------------------------------------------------------+ //| создаёт индикатор AMA, возвращает результат | //+------------------------------------------------------------------+ bool CTrailingByAMA::Initialize(const string symbol, const ENUM_TIMEFRAMES timeframe, const int period, const int fast_ema, const int slow_ema, const int shift, const ENUM_APPLIED_PRICE price) { this.SetSymbol(symbol); this.SetTimeframe(timeframe); this.SetPeriod(period); this.SetFastEMAPeriod(fast_ema); this.SetSlowEMAPeriod(slow_ema); this.SetShift(shift); this.SetPrice(price); ::ResetLastError(); this.m_handle=::iAMA(this.m_symbol, this.m_timeframe, this.m_period, this.m_fast_ema, this.m_slow_ema, this.m_shift, this.m_price); if(this.m_handle==INVALID_HANDLE) { ::PrintFormat("Failed to create iAMA(%s, %s, %d, %d, %d, %s) handle. Error %d", this.m_symbol, this.TimeframeDescription(), this.m_period, this.m_fast_ema, this.m_slow_ema, ::StringSubstr(::EnumToString(this.m_price),6), ::GetLastError()); } return(this.m_handle!=INVALID_HANDLE); }
Класс абсолютно идентичен выше рассмотренному классу трала по Parabolic SAR. В каждом классе тралов по скользящим средним будет собственный набор переменных для хранения параметров индикатора и соответствующие этим переменным методы установки и возврата значений. Все классы тралов по скользящим средним идентичны друг другу, и изучить их можно, посмотрев коды в прикреплённом к статье файлу классов трейлингов Trailings.mqh. Протестировать работу класса трейлинга по простой скользящей средней можно, запустив тестовый советник из файла ExpertMACDWithTrailingByMA.mq5, также прикреплённый в конце статьи.
Мы создали классы простого трала и тралов по стандартным индикаторам, представленным в терминале, и подходящим для указания уровней для размещения StopLoss позиций.
Но это не все типы тралов, которые возможно создать при помощи представленных классов. Есть трейлинги, которые смещают стопы позиций на определённые ценовые уровни, указываемые раздельно для длинных и коротких позиций. Примером такого трала может быть трал по High/Low свечей, либо, например, по индикатору фракталов.
Для реализации такого трала у нас должна быть возможность указать раздельно для каждого типа позиций свой уровень для установки стоп-лосс. Создадим такой класс.
Класс трала по указанным значениям уровней StopLoss
Всё, что необходимо сделать для создания трала по указанным значениям — это вместо параметров индикатора определить переменные для записи в них значений стоп-лосс для длинных и коротких позиций. А вместо получения данных от индикатора, нужно просто рассчитывать значения для StopLoss позиций от значений, указанных в этих переменных. Значения в переменные должны заноситься из управляющей программы, непосредственно при вызове метода Run() трала.
Напишем такой класс:
//+------------------------------------------------------------------+ //| Класс трала по указанному значению | //+------------------------------------------------------------------+ class CTrailingByValue : public CSimpleTrailing { protected: double m_value_sl_long; // значение уровня StopLoss длинных позиций double m_value_sl_short; // значение уровня StopLoss коротких позиций //--- рассчитывает и возвращает уровень StopLoss выбранной позиции virtual double GetStopLossValue(ENUM_POSITION_TYPE pos_type, MqlTick &tick); public: //--- возвращает уровень StopLoss (2) длинных, (2) коротких позиций double StopLossValueLong(void) const { return this.m_value_sl_long; } double StopLossValueShort(void) const { return this.m_value_sl_short; } //--- запуск трала с указанным отступом StopLoss от цены bool Run(const double value_sl_long, double value_sl_short); //--- конструкторы CTrailingByValue(void) : CSimpleTrailing(::Symbol(), -1, 0, 0, 0), m_value_sl_long(0), m_value_sl_short(0) {} CTrailingByValue(const string symbol, const long magic, const int trail_start, const uint trail_step, const int trail_offset) : CSimpleTrailing(symbol, magic, trail_start, trail_step, trail_offset), m_value_sl_long(0), m_value_sl_short(0) {} //--- деструктор ~CTrailingByValue(void){} };
Видно, что класс практически идентичен всем вышерассмотренным классам трейлингов по индикаторам. Но здесь нет метода Initialize(), так как никакой индикатор создавать не требуется.
В виртуальном методе, рассчитывающем и возвращающем уровень StopLoss выбранной позиции, значения для StopLoss рассчитываются от установленных уровней для стопов длинных и коротких позиций:
//+------------------------------------------------------------------+ //| Рассчитывает и возвращает уровень StopLoss выбранной позиции | //+------------------------------------------------------------------+ double CTrailingByValue::GetStopLossValue(ENUM_POSITION_TYPE pos_type, MqlTick &tick) { //--- в зависимости от типа позиции рассчитываем и возвращаем уровень StopLoss switch(pos_type) { case POSITION_TYPE_BUY : return(this.m_value_sl_long - this.m_offset * this.m_point); case POSITION_TYPE_SELL : return(this.m_value_sl_short + this.m_offset * this.m_point); default : return 0; } }
В методе Run(), посредством его формальных параметров передаются в класс значения стоплосс для длинных и для коротких позиций:
//+------------------------------------------------------------------+ //| Запуск трала с указанным отступом StopLoss от цены | //+------------------------------------------------------------------+ bool CTrailingByValue::Run(const double value_sl_long, double value_sl_short) { this.m_value_sl_long =value_sl_long; this.m_value_sl_short=value_sl_short; return CSimpleTrailing::Run(); }
Сначала в переменные-члены класса записываются переданные в формальных параметрах значения, а затем вызывается метод Run() родительского класса. А уже из родительского класса будет вызван виртуальный метод GetStopLossValue(), переопределённый в этом классе, и стопы позиций будут устанавливаться на рассчитанные в нём значения.
Подключаем трейлинг-стоп к советнику
Как подключить трал к советнику мы уже рассматривали выше, при тестировании трейлингов по индикаторам. Рассмотрим теперь, как подключить и запустить трал по передаваемым в него значениям, а именно — по High и Low свечей.
Трал подключим к советнику из стандартной поставки \MQL5\Experts\Advisors\ExpertMACD.mq5. Сохраним его под новым именем ExpertMACDWithTrailingByValue.mq5 и внесём необходимые доработки.
Подключим к советнику файл с классами трейлингов, добавим входные переменные для настройки трала и объявим экземпляр класса трала по указанным значениям:
//+------------------------------------------------------------------+ //| ExpertMACD.mq5 | //| Copyright 2000-2024, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2000-2024, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" //+------------------------------------------------------------------+ //| Include | //+------------------------------------------------------------------+ #include <Expert\Expert.mqh> #include <Expert\Signal\SignalMACD.mqh> #include <Expert\Trailing\TrailingNone.mqh> #include <Expert\Money\MoneyNone.mqh> #include <Trailings\Trailings.mqh> //+------------------------------------------------------------------+ //| Inputs | //+------------------------------------------------------------------+ //--- inputs for expert input group " - ExpertMACD Parameters -" input string Inp_Expert_Title ="ExpertMACD"; int Expert_MagicNumber =10981; bool Expert_EveryTick =false; //--- inputs for signal input int Inp_Signal_MACD_PeriodFast =12; input int Inp_Signal_MACD_PeriodSlow =24; input int Inp_Signal_MACD_PeriodSignal=9; input int Inp_Signal_MACD_TakeProfit =50; input int Inp_Signal_MACD_StopLoss =20; input group " - Trailing By Value Parameters -" input ENUM_TIMEFRAMES InpTimeframe = PERIOD_CURRENT; // Data Rates Timeframe input uint InpDataRatesIndex = 2; // Data Rates Index for StopLoss input uint InpTrailingStep = 0; // Trailing Step input int InpTrailingStart = 0; // Trailing Start input int InpTrailingOffset = 0; // Trailing Offset input bool InpUseTrailing = true; // Use Trailing Stop //+------------------------------------------------------------------+ //| Global expert object | //+------------------------------------------------------------------+ CExpert ExtExpert; CTrailingByValue ExtTrailing;
Входная переменная Data Rates Timeframe — это период графика, с которого будут браться цены High для стопов коротких, и Low — для стопов длинных позиций.
Входная переменная Data Rates Index for StopLoss — индекс бара на графике с периодом Data Rates Timeframe, с которого будут браться цены High и Low для установки StopLoss позиций.
В обработчике OnInit() советника инициализируем параметры трала:
//+------------------------------------------------------------------+ //| Initialization function of the expert | //+------------------------------------------------------------------+ int OnInit(void) { //--- Initializing trailing ExtTrailing.SetMagicNumber(Expert_MagicNumber); ExtTrailing.SetTrailingStart(InpTrailingStart); ExtTrailing.SetTrailingStep(InpTrailingStep); ExtTrailing.SetStopLossOffset(InpTrailingOffset); ExtTrailing.SetActive(InpUseTrailing); //--- Initializing expert
Тралиться будут только стопы тех позиций, магик которых совпадает с магиком советника, т.е. — тралим только позиции, открытые этим советником.
В обработчике OnTick() советника получим данные бара с индексом, указанным во входной переменной InpDataRatesIndex, и запускаем трал с указанием цен StopLoss (High и Low бара) для длинных и коротких позиций:
//+------------------------------------------------------------------+ //| Function-event handler "tick" | //+------------------------------------------------------------------+ void OnTick(void) { ExtExpert.OnTick(); MqlRates rates[1]={}; if(CopyRates(ExtTrailing.Symbol(),InpTimeframe,InpDataRatesIndex,1,rates)) ExtTrailing.Run(rates[0].low,rates[0].high); }
Это всё, что необходимо сделать для подключения трала к советнику. Как можно уже было заметить, вся разница при подключении к советникам различных тралов, заключается лишь в объявлении экземпляров разных типов тралов. Все остальные действия по подключению трейлингов практически одинаковы, и не должны вызывать никаких вопросов и сомнений.
Скомпилируем советник и запустим его в визуальном режиме тестера с настройками по умолчанию:
Видно, как стопы позиций выставляются на значения High и Low свечей, имеющих индекс в таймсерии 2.
Насколько такой трал может дать выигрыш в торговой системе — это уже предмет самостоятельного тестирования.
Кроме того, каждый может создать свой собственный трейлинг-стоп по своему алгоритму при помощи представленных в статье классов — здесь простор для исследований большой и практически ничем не ограничен.
Заключение
Сегодня мы создали классы различных трейлингов, которые позволяют легко подключить трейлинг-стоп к любому советнику. Созданные классы также являются неплохим набором инструментов для создания трейлинга стопов по собственным алгоритмам.
В примерах был рассмотрен лишь один способ работы с объектами классов — создание экземпляра объекта класса трейлинга. Такой подход даёт возможность заранее в программе определить, какие типы тралов потребуются, и какие параметры тралов должны быть. Для каждого трала создаётся один объект в глобальной области. Такой подход имеет право на использование — он прост и понятен. Но чтобы сделать динамическое создание объектов тралов при помощи оператора создания объекта new прямо во время выполнения программы, лучше воспользоваться возможностями, предоставляемыми Стандартной Библиотекой для создания связанных списков объектов. Именно для этих целей все классы трейлингов являются наследниками базового объекта Стандартной Библиотеки CObject. Этот подход можно будет обсудить в комментариях к статье, так как уже выходит за рамки данной темы.
Все представленные в статье классы можно использовать "как есть" в своих собственных разработках, а можно доработать под свои нужды и задачи.





- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования
Но, как обычно, всё, что использует Position...(), полностью непригодно для неттинга при работе более одного робота на символе или при ручной торговле параллельно с роботом.
Для этого надо вести виртуальные позиции в роботе. И стопы ставить отложенными ордерами.
Для этого надо вести виртуальные позиции в роботе. И стопы ставить отложенными ордерами.
Я-то в курсе, у меня и позиции, и стопы виртуальные.
Но вот большинство форексного кода, который тут выкладывается, для такой работы (в частности, на MOEX), непригодно.
Артём, Ваш трейлинг по PSAR работает совершенно неправильно. Не объяснить на словах, внимательно рассмотрите каждое закрытие. Закрывается не вовремя, и не закрывается вовремя. Может закрыться лонг при нисходящем PSAR, а должен только при восходящем. Может вообще пропустить несколько переключений PSAR, хотя во время них были условия закрытия.
В коде слишком большое упрощение - просто берётся значение PSAR и используется как SL. Наверное, для мувингов это должно работать.
Посмотрите, как я контролирую условие закрытия:
Здесь PSAR используется не для установки скользящего SL, а для выдачи сигнала, но суть та же.
PSAR: И сюда неплохо дополнить такую вещь как спред... для форекса обычно цена рисуется по BID и для длинных позиций все будет работать +-:)), а вот короткие позиции по тому же Parabolic будут закрываться по ASK, а это на тонком рынке чревато закрытием сделки, хотя индикатор будет показывать что закрытия нет... да и по МА будет такой же эффект...