
Постфактумный анализ торговли: подбираем TrailingStop и новые стопы в тестере стратегий
Содержание
- Введение
- Выбираем и дорабатываем класс трейлингов позиций
- Тестируем различные типы TrailingStop
- Заключение
Введение
В прошлой статье мы создали советник, торгующий в тестере стратегий клиентского терминала по результатам торговли на реальном счёте. Мы добавили возможность установки новых размеров StopLoss и TakeProfit для тестирования своей торговли в тестере с иными размерами стоп-приказов. Результат оказался неожиданным: вместо убытка, получили прибыль, соразмерную с полученным в реале убытком. Значит, если бы мы в своей торговле на счёте использовали уровни StopLoss и TakeProfit такими, при которых в тестере получили прибыль, то торговля и на реальном счёте была бы прибыльной. И это всего лишь простое изменение размеров стопов. Интересно, а что, если добавить трейлинг StopLoss? Как при этом изменится торговля?
Сегодня подключим к советнику несколько различных трейлингов и протестируем свою торговлю в тестере стратегий. Посмотрим, какой будет получен результат.
Выбираем и дорабатываем класс трейлингов позиций
Попробуем подключить трал по индикатору Parabolic SAR и по скользящей средней. Индикатор Parabolic SAR, судя по его описанию, да и по названию (SAR = Stop And Reverse), идеально должен подходить для перемещения линии стопа по его значениям:
Технический индикатор Параболическая Система SAR (Parabolic SAR) был разработан для анализа трендовых рынков. Индикатор строится на ценовом графике. По своему смыслу данный индикатор аналогичен скользящей средней, с той лишь разницей, что Parabolic SAR движется с большим ускорением и может менять положение относительно цены. На "бычьем тренде" (Up Trend) индикатор располагается ниже цен, на "медвежьем тренде" (Down Trend) — выше.
Если цена пересекает линию Parabolic SAR, то происходит разворот индикатора, а следующие его значения располагаются по другую сторону от цены. При этом "перевороте" индикатора, точкой отсчета будет служить максимальная или минимальная цена за предыдущий период. Переворот индикатора — это сигнал либо об окончании (переходе в коррекцию или флэт) тренда, либо об его развороте.
Parabolic SAR превосходно определяет точки выхода из рынка. Длинные позиции следует закрывать, когда цена опускается ниже линии технического индикатора, а короткие — когда цена поднимается выше линии Parabolic SAR. То есть, необходимо отслеживать направление движения Parabolic SAR и держать открытыми на рынке позиции только в направлении этого движения. Часто данный индикатор используют в качестве линии скользящего стопа (trailing stop).
Если открыта длинная позиция (то есть цена выше линии Parabolic SAR), то линия индикатора будет перемещаться вверх, независимо от того, в каком направлении движутся цены. Величина перемещения линии Parabolic SAR зависит от величины ценового движения.
А скользящая средняя, ввиду её естественного запаздывания, тоже подходит для подтягивания линии стопа за ценой. Для длинной позиции, если линия индикатора находится ниже цены, то по значению скользящей средней можно подтягивать стоп за ценой. Цена пересечётся со скользящей средней естественным образом, и стоп сработает. Для короткой позиции стоп должен двигаться за линией скользящей средней в случае, если цена находится под ней.
Ранее мною уже была написана статья о подключении любых тралов к советникам: "Как сделать любой тип Trailing Stop и подключить к советнику". В ней предлагается создать классы трейлингов и просто подключать их к советнику.
Воспользуемся этой возможностью. Единственное, что не очень нам подходит — там каждый символ или магик "крутит" собственный цикл перебора открытых позиций. Значит, придётся доработать класс из статьи. Благо, это не сложно.
Загрузим из прилагаемых к статье файлов единственный нужный нам файл Trailings.mqh. В прошлой статье мы создали папку, где были расположены файлы классов и советников для торговли по истории сделок: \MQL5\Experts\TradingByHistoryDeals. Сохраним в эту папку загруженный файл Trailings.mqh и откроем его в редакторе кода MetaEditor.
Основной и главный метод, вызываемый из программы — метод 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; }
Если для каждого торгового объекта символа (см. первую статью) вызывать этот метод, то у нас получится столько циклов перебора открытых позиций, сколько символов использовалось в торговле. Это не оптимально. Сделаем так, что в самой программе будет цикл перебора всех открытых позиций, а эти методы будем вызывать уже из этого цикла. Но в методе тоже встроен цикл... Значит, нам в классе трейлингов нужно сделать ещё один метод Run(), но с указанием тикета позиции, стоп которой нужно перемещать, и из основного цикла вызывать уже его.
В классе простого трала объявим новый метод, в который будет передаваться тикет нужной позиции:
//+------------------------------------------------------------------+ //| Класс простого трала StopLoss позиций | //+------------------------------------------------------------------+ 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: 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: //--- установка параметров трала 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); bool Run(const ulong pos_ticket); //--- конструкторы 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() {} };
За пределами тела класса напишем его реализацию:
//+------------------------------------------------------------------+ //| Запускает простой трейлинг с отступом StopLoss от цены | //+------------------------------------------------------------------+ bool CSimpleTrailing::Run(const ulong pos_ticket) { //--- если трейлинг отключен, или невалидный тикет - уходим if(!this.m_active || pos_ticket==0) return false; //--- переменные трала MqlTick tick = {}; // структура цен //--- проверка корректности данных по символу ::ResetLastError(); if(this.m_point==0) { //--- попробуем ещё раз получить данные 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; } } //--- выбираем позицию по тикету if(!::PositionSelectByTicket(pos_ticket)) { ::PrintFormat("%s: PositionSelectByTicket(%I64u) failed. Error %d",__FUNCTION__, pos_ticket, ::GetLastError()); return false; } //--- если цены получить не удалось - возвращаем false if(!::SymbolInfoTick(this.m_symbol, tick)) return false; //--- получаем тип позиции, цену её открытия и уровень 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 подходят - возвращаем результат модифицикации стопа позиции if(this.CheckCriterion(pos_type, pos_open, pos_sl, value_sl, tick)) return(this.ModifySL(pos_ticket, value_sl)); //--- условия для модификации не подходят return false; }
Теперь этот метод будет вызываться либо из метода Run(), не имеющего формальных параметров, но в котором организован цикл по всем открытым позициям, либо напрямую из программы с передачей в него нужного тикета.
Доработаем метод Run() без формальных параметров. Удалим из цикла блок кода:
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); }
Вместо него впишем вызов нового метода:
//+------------------------------------------------------------------+ //| Запускает простой трейлинг с отступом StopLoss от цены | //+------------------------------------------------------------------+ bool CSimpleTrailing::Run(void) { //--- если отключен - уходим if(!this.m_active) return false; //--- переменные трала 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; res &=this.Run(pos_ticket); } //--- по окончании цикла возвращаем результат модификации каждой подходящей по фильтру "символ/магик" позиции return res; }
В классе трейлинга по указанному значению точно так же объявим новый метод 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); bool Run(const ulong pos_ticket, 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){} };
И за пределами тела класса напишем его реализацию:
//+------------------------------------------------------------------+ //| Запуск трала с указанным отступом StopLoss от цены | //+------------------------------------------------------------------+ bool CTrailingByValue::Run(const ulong pos_ticket,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(pos_ticket); }
Здесь вызывается добавленный в класс простого трала метод Run() с указанием тикета позиции.
С классами тралов желательно ознакомиться в статье, из которой мы взяли файл с трейлингами.
Торговый класс символа, созданный в прошлой статье хранит в себе списки сделок и торговый класс CTrade Стандартной Библиотеки. Чтобы его не дорабатывать для подключения классов трейлингов, создадим новый класс на его основе, в который и добавим работу с тралами.
В каталоге терминала \MQL5\Experts\TradingByHistoryDeals\ создадим новый файл SymbolTradeExt.mqh класса CSymbolTradeExt. К файлу должны быть подключены файл классов трейлингов и файл класса CSymbolTrade, от которого и должен быть унаследован создаваемый класс:
//+------------------------------------------------------------------+ //| SymbolTradeExt.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 "Trailings.mqh" #include "SymbolTrade.mqh" class CSymbolTradeExt : public CSymbolTrade { }
При использовании взятого из статьи класса трейлингов, нам предоставляются в пользование тралы по параболику и по всем типам стандартных скользящих средних. Также в классе есть трал по указанным значениям, но его мы использовать здесь не будем — он требует собственного расчёта уровней стопов в своей программе и передаче их в параметрах вызываемого метода Run() класса трейлинга. Это может быть, например, расчёт уровней по индикатору ATR и передача рассчитанных значений в класс трейлинга.
Напишем перечисление режимов трейлинга:
//+------------------------------------------------------------------+ //| SymbolTradeExt.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 "Trailings.mqh" #include "SymbolTrade.mqh" enum ENUM_TRAILING_MODE // Перечисление режимов трейлинга { TRAILING_MODE_SIMPLE=2, // Простой трал TRAILING_MODE_SAR, // Трал по Parabolic SAR TRAILING_MODE_AMA, // Трал по адартивной скользящей средней TRAILING_MODE_DEMA, // Трал по двойной экспоненциальной скользящей средней TRAILING_MODE_FRAMA, // Трал по фрактальной адартивной скользящей средней TRAILING_MODE_MA, // Трал по простой скользящей средней TRAILING_MODE_TEMA, // Трал по тройной экспоненциальной скользящей средней TRAILING_MODE_VIDYA, // Трал по скользящей средней с динамическим периодом усреднения }; class CSymbolTradeExt : public CSymbolTrade { }
Почему значения констант перечисления начинаются не с нуля, а со значения 2? В советнике, на основе которого будем делать новый, тоже есть перечисление режимов тестирования:
enum ENUM_TESTING_MODE { TESTING_MODE_ORIGIN, /* Original trading */ // Оригинальная торговля TESTING_MODE_SLTP, /* Specified StopLoss and TakeProfit values */ // Указанные значения StopLoss и TakeProfit };
Здесь есть две константы: оригинальная торговля (0) и торговля с указанными значениями стоп-приказов (1). Сюда позже добавим новые константы, соответствующие константам перечисления трейлингов. Именно по этой причине значения констант перечисления трейлингов начинаются со значения 2.
В приватной секции класса объявим указатель на объект класса трейлингов и переменную для хранения периода графика для расчёта индикаторов, используемых в трейлингах. В публичной секции объявим метод для установки параметров трала, метод, запускающий трейлинг позиций, конструкторы и деструктор:
class CSymbolTradeExt : public CSymbolTrade { private: CSimpleTrailing *m_trailing; // Объект класса трейлингов ENUM_TIMEFRAMES m_timeframe; // Таймфрейм расчёта индикатора для трала public: //--- Устанавливает трал и его параметры bool SetTrailing(const ENUM_TRAILING_MODE trailing_mode, const int data_index, const long magic, const int start, const int step, const int offset, const MqlParam ¶m[]); //--- Запускает трал указанной по тикету позиции void Trailing(const ulong pos_ticket); //--- Конструктор/деструктор CSymbolTradeExt() : m_trailing(NULL), m_timeframe(::Period()) { this.SetSymbol(::Symbol()); } CSymbolTradeExt(const string symbol, const ENUM_TIMEFRAMES timeframe); ~CSymbolTradeExt(); };
В конструкторе класса, в строке инициализации, в конструктор родительского класса передаётся символ, для которого строится объект, значению таймфрейма расчёта индикатора устанавливается переданное в формальных параметрах значение, а указатель на объект класса трейлинга инициализируется значением NULL:
//+------------------------------------------------------------------+ //| Конструктор | //+------------------------------------------------------------------+ CSymbolTradeExt::CSymbolTradeExt(const string symbol, const ENUM_TIMEFRAMES timeframe) : CSymbolTrade(symbol) { this.m_trailing=NULL; this.m_timeframe=timeframe; }
В деструкторе класса, если объект трейлинга был создан, он удаляется:
//+------------------------------------------------------------------+ //| Деструктор | //+------------------------------------------------------------------+ CSymbolTradeExt::~CSymbolTradeExt() { //--- удаляем созданный объект трала if(this.m_trailing!=NULL) delete this.m_trailing; }
Так как различные типы трейлингов, работающих на основе разных типов индикаторов, имеют отличные друг от друга параметры настройки, то всё это множество параметров будем передавать в метод установки параметров трейлинга через структуру MqlParam. Просто для каждого трала набор параметров будет своим, и каждое поле структуры будет нести в себе значение своего параметра индикатора, присущее только ему.
//+------------------------------------------------------------------+ //| Устанавливает параметры трала | //+------------------------------------------------------------------+ bool CSymbolTradeExt::SetTrailing(const ENUM_TRAILING_MODE trailing_mode, const int data_index, const long magic, const int start, const int step, const int offset, const MqlParam ¶m[]) { //--- Устанавливаем параметры трала (для каждого типа индикаторов используются только свои, нужные индикатору, поля структуры) int ma_period = (int)param[0].integer_value; int ma_shift = (int)param[1].integer_value; ENUM_APPLIED_PRICE ma_price = (ENUM_APPLIED_PRICE)param[2].integer_value; ENUM_MA_METHOD ma_method = (ENUM_MA_METHOD)param[3].integer_value; int fast_ema = (int)param[4].integer_value; int slow_ema = (int)param[5].integer_value; int period_cmo = (int)param[6].integer_value; double sar_step = param[0].double_value; double sar_max = param[1].double_value; //--- в зависимости от типа трейлинга создаём объект трала //---если в качестве периода расчёта передано значение, меньше допустимого, то для каждого индикатора устанавливается его собственное значение по умолчанию switch(trailing_mode) { case TRAILING_MODE_SIMPLE : this.m_trailing=new CSimpleTrailing(this.Symbol(), magic, start, step, offset); break; case TRAILING_MODE_AMA : this.m_trailing=new CTrailingByAMA(this.Symbol(), this.m_timeframe, magic, (ma_period<1 ? 9 : ma_period), (fast_ema<1 ? 2 : fast_ema), (slow_ema<1 ? 30 : slow_ema), ma_shift, ma_price, start, step, offset); break; case TRAILING_MODE_DEMA : this.m_trailing=new CTrailingByDEMA(this.Symbol(), this.m_timeframe, magic, (ma_period==0 ? 14 : ma_period), ma_shift, ma_price, start, step, offset); break; case TRAILING_MODE_FRAMA : this.m_trailing=new CTrailingByFRAMA(this.Symbol(), this.m_timeframe, magic, (ma_period==0 ? 14 : ma_period), ma_shift, ma_price, start, step, offset); break; case TRAILING_MODE_MA : this.m_trailing=new CTrailingByMA(this.Symbol(), this.m_timeframe, magic, ma_period, (ma_period==0 ? 10 : ma_period), ma_method, ma_price, start, step, offset); break; case TRAILING_MODE_TEMA : this.m_trailing=new CTrailingByTEMA(this.Symbol(), this.m_timeframe, magic, (ma_period==0 ? 14 : ma_period), ma_shift, ma_price, start, step, offset); break; case TRAILING_MODE_VIDYA : this.m_trailing=new CTrailingByVIDYA(this.Symbol(), this.m_timeframe, magic, (period_cmo<1 ? 9 : period_cmo), (ma_period==0 ? 12 : ma_period), ma_shift, ma_price, start, step, offset); break; case TRAILING_MODE_SAR : this.m_trailing=new CTrailingBySAR(this.Symbol(), this.m_timeframe, magic, (sar_step<0.0001 ? 0.02 : sar_step), (sar_max<0.02 ? 0.2 : sar_max), start, step, offset); break; default : break; } //--- что-то пошло не так - возвращаем false if(this.m_trailing==NULL) return false; //--- всё успешно - делаем трал активным и возвращаем true this.m_trailing.SetActive(true); return true; }
При вызове этого метода для различных типов трейлинга важно правильно заполнять структуру MqlParam. Это мы проконтролируем при доработке советника под использование трейлинга.
Метод, запускающий трал указанной по тикету позиции:
//+------------------------------------------------------------------+ //| Запускает трал указанной по тикету позиции | //+------------------------------------------------------------------+ void CSymbolTradeExt::Trailing(const ulong pos_ticket) { if(this.m_trailing!=NULL) this.m_trailing.Run(pos_ticket); }
Из советника, из его цикла по открытым позициям, в зависимости от символа позиции, будем получать из списка торговый объект символа, а из него — вызывать данный метод для трала стопа позиции, тикет которой будем передавать в метод. Если объект трала существует, то вызывается его новый метод Run() с указанием тикета позиции.
Тестируем различные типы TrailingStop
Для тестирования возьмём файл советника TradingByHistoryDeals_SLTP.mq5из прошлой статьи и сохраним его в той же папке \MQL5\Experts\TradingByHistoryDeals\под новым именем TradingByHistoryDeals_Ext.mq5.
Подключение файла класса CSymbolTrade заменим на подключение файла класса CSymbolTradeExt, подключим файл классов трейлингов и расширим список констант перечисления режимов тестирования, вписав выбор нужного трала:
//+------------------------------------------------------------------+ //| TradingByHistoryDeals_Ext.mq5 | //| 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 "SymbolTradeExt.mqh" #include "Trailings.mqh" enum ENUM_TESTING_MODE { TESTING_MODE_ORIGIN, /* Original trading */ // Оригинальная торговля TESTING_MODE_SLTP, /* Specified StopLoss and TakeProfit values */ // Указанные значения StopLoss и TakeProfit TESTING_MODE_TRAIL_SIMPLE, /* Simple Trailing */ // Простой трал TESTING_MODE_TRAIL_SAR, /* Trailing by Parabolic SAR indicator */ // Трал по Parabolic SAR TESTING_MODE_TRAIL_AMA, /* Trailing by AMA indicator */ // Трал по адаптивной скользящей средней TESTING_MODE_TRAIL_DEMA, /* Trailing by DEMA indicator */ // Трал по двойной экспоненциальной скользящей средней TESTING_MODE_TRAIL_FRAMA, /* Trailing by FRAMA indicator */ // Трал по фрактальной адаптивной скользящей средней TESTING_MODE_TRAIL_MA, /* Trailing by MA indicator */ // Трал по простой скользящей средней TESTING_MODE_TRAIL_TEMA, /* Trailing by TEMA indicator */ // Трал по тройной экспоненциальной скользящей средней TESTING_MODE_TRAIL_VIDYA, /* Trailing by VIDYA indicator */ // Трал по скользящей средней с динамическим периодом усреднения }; //+------------------------------------------------------------------+ //| Expert | //+------------------------------------------------------------------+
Во входных параметрах советника добавим переменные для выбора параметров трейлинга:
//+------------------------------------------------------------------+ //| Expert | //+------------------------------------------------------------------+ //--- input parameters input group " - Strategy parameters - " input string InpTestedSymbol = ""; /* The symbol being tested in the tester */ // Тестируемый символ input long InpTestedMagic = -1; /* The magic number being tested in the tester */ // Тестируемый магик sinput bool InpShowDataInLog = false; /* Show collected data in the log */ // Показать собранные данные сделок в журнале input group " - Stops parameters - " input ENUM_TESTING_MODE InpTestingMode = TESTING_MODE_ORIGIN; /* Testing Mode */ // Режим тестирования input int InpStopLoss = 300; /* StopLoss in points */ // Отступ StopLoss в пунктах input int InpTakeProfit = 500; /* TakeProfit in points */ // Отступ TakeProfit в пунктах input group " - Trailing Parameters -" input bool InpSetStopLoss = true; /* Set Initial StopLoss */ // Устанавливать StopLoss input bool InpSetTakeProfit = true; /* Set Initial TakeProfit */ // Устанавливать TakeProfit input int InpTrailingStart = 150; /* Trailing start */ // Прибыль в пунктах для старта трейлинга input int InpTrailingStep = 50; /* Trailing step in points */ // Шаг трала в пунктах цены input int InpTrailingOffset = 0; /* Trailing offset in points */ // Отступ трала от цены в пунктах input group " - Indicator Parameters -" input ENUM_TIMEFRAMES InpIndTimeframe = PERIOD_CURRENT; /* Indicator's timeframe */ // Таймфрейм индикатора, используемого в расчёте трала input int InpMAPeriod = 0; /* MA Period */ // Период расчёта скользящей средней input int InpMAShift = 0; /* MA Shift */ // Горизонтальный сдвиг скользящей средней input int InpFastEMAPeriod = 2; /* AMA Fast EMA Period */ // Период расчёта быстрой EMA адаптивной скользящей средней input int InpSlowEMAPeriod = 30; /* AMA Slow EMA Period */ // Период расчёта медленной EMA адаптивной скользящей средней input int InpCMOPeriod = 9; /* VIDYA CMO Period */ // Период CMO скользящей средней с динамическим периодом усреднения input double InpSARStep = 0.02; /* Parabolic SAR Step */ // Шаг Parabolic SAR input double InpSARMax = 0.2; /* Parabolic SAR Max */ // Максимум Parabolic SAR input ENUM_APPLIED_PRICE InpAppliedPrice = PRICE_CLOSE; /* MA Applied Price */ // Цена для расчёта скользящей средней input ENUM_MA_METHOD InpMAMethod = MODE_SMA; /* MA Smoothing Method */ // Тип сглаживания скользящей средней input int InpDataIndex = 1; /* Indicator data index */ // Бар данных, получаемых от индикатора
InpMAPeriod по умолчанию здесь установлен в ноль. Причиной тому послужило то, что у каждого типа скользящей средней значение периода по умолчанию — своё. А так, как при нулевом значении этого параметра в класс трейлинга передаётся значение по умолчанию, установленное для индикатора, то все данные будут корректными, если требуются простые значения по умолчанию для используемого в трале индикатора.
Во всём коде заменим все вхождения строки "CSymbolTrade" на "CSymbolTradeExt". Теперь у нас новый класс торгового объекта символа CSymbolTradeExt унаследован от прошлого класса CSymbolTrade, написанном в прошлой статье. И в этом классе объявлен объект класса трейлингов. Поэтому мы заменили тип прошлого класса на новый. По сути, везде это делать не обязательно, а только там, где потребуется трейлинг. Но не будем здесь вдаваться в подробности наследования классов, а просто заменим старый класс на новый.
Тралы будут вызываться для каждой позиции по её тикету. А для того, чтобы получить доступ к тралу позиции, нужно получить из списка торговый объект символа, по которому открыта позиция. Для этого напишем функцию, возвращающую указатель на торговый объект из списка по имени символа:
//+------------------------------------------------------------------+ //| Возвращает указатель на торговый объект символа по имени | //+------------------------------------------------------------------+ CSymbolTrade *GetSymbolTrade(const string symbol, CArrayObj *list) { SymbTradeTmp.SetSymbol(symbol); list.Sort(); int index=list.Search(&SymbTradeTmp); return list.At(index); }
В функцию передаётся имя символа, для которого нужно вернуть торговый объект и указатель на список, в котором хранятся указатели на торговые объекты символов. Во временный торговый объект устанавливается имя символа, переданное в функцию и индекс объекта с таким именем символа ищется в списке. В итоге, из списка по индексу возвращается указатель на искомый объект. Если такого объекта нет в списке, то индекс будет равен -1, и из списка будет возвращено значение NULL.
При создании массива используемых символов создаются торговые объекты символов, а в них нужно инициализировать трейлинги, заданные во входных параметрах. Доработаем функцию, создающую массив используемых символов:
//+------------------------------------------------------------------+ //| Создаёт массив используемых символов | //+------------------------------------------------------------------+ bool CreateListSymbolTrades(SDeal &array_deals[], CArrayObj *list_symbols) { bool res=true; // результат MqlParam param[7]={}; // параметры тралов int total=(int)array_deals.Size(); // общее количество сделок в массиве //--- если массив сделок пустой - возвращаем false if(total==0) { PrintFormat("%s: Error! Empty deals array passed",__FUNCTION__); return false; } //--- в цикле по массиву сделок CSymbolTradeExt *SymbolTrade=NULL; for(int i=0; i<total; i++) { //--- получаем очередную сделку и, если это не покупка и не продажа - идём к следующей SDeal deal_str=array_deals[i]; if(deal_str.type!=DEAL_TYPE_BUY && deal_str.type!=DEAL_TYPE_SELL) continue; //--- найдём торговый объект в списке, у которого символ равен символу сделки string symbol=deal_str.Symbol(); SymbTradeTmp.SetSymbol(symbol); list_symbols.Sort(); int index=list_symbols.Search(&SymbTradeTmp); //--- если индекс искомого объекта в списке равен -1 - такого объекта в списке нет if(index==WRONG_VALUE) { //--- создаём новый торговый объект символа и, если создать не получилось - //--- добавляем к результату значение false и идём к следующей сделке SymbolTrade=new CSymbolTradeExt(symbol, InpIndTimeframe); if(SymbolTrade==NULL) { res &=false; continue; } //--- если торговый объект символа не удалось добавить в список - //--- удаляем вновь созданный объект, добавляем к результату значение false //--- и идём к следующей сделке if(!list_symbols.Add(SymbolTrade)) { delete SymbolTrade; res &=false; continue; } //--- инициализируем в торговом объекте заданный в настройках трейлинг ENUM_TRAILING_MODE mode=(ENUM_TRAILING_MODE)InpTestingMode; SetTrailingParams(mode, param); SymbolTrade.SetTrailing(mode, InpDataIndex, InpTestedMagic, InpTrailingStart, InpTrailingStep, InpTrailingOffset, param); } //--- иначе, если торговый объект уже существует в списке - получаем его по индексу else { SymbolTrade=list_symbols.At(index); if(SymbolTrade==NULL) continue; } //--- если текущей сделки ещё нет в списке сделок торгового объекта символа if(SymbolTrade.GetDealByTime(deal_str.time)==NULL) { //--- создаём объект сделки по её образцу-структуре CDeal *deal=CreateDeal(deal_str); if(deal==NULL) { res &=false; continue; } //--- к значению результата добавляем результат добавления объекта сделки в список сделок торгового объекта символа res &=SymbolTrade.AddDeal(deal); } } //--- возвращаем итоговый результат создания торговых объектов и добавления сделок в их списки return res; }
Здесь мы добавили объявление структуры входных параметров индикатора и небольшой блок кода, где инициализируется трал в торговом объекте.
Для установки параметров выбранного в настройках трала напишем специальную функцию:
//+------------------------------------------------------------------+ //| Устанавливает параметры трала по выбранному его типу | //+------------------------------------------------------------------+ void SetTrailingParams(const ENUM_TRAILING_MODE mode, MqlParam ¶m[]) { //--- обнуляем все параметры ZeroMemory(param); //--- в зависимости от выбранного типа трейлинга устанавливаем параметры индикатора switch(mode) { case TRAILING_MODE_SAR : param[0].type=TYPE_DOUBLE; param[0].double_value=InpSARStep; param[1].type=TYPE_DOUBLE; param[1].double_value=InpSARMax; break; case TRAILING_MODE_AMA : param[0].type=TYPE_INT; param[0].integer_value=InpMAPeriod; param[1].type=TYPE_INT; param[1].integer_value=InpMAShift; param[2].type=TYPE_INT; param[2].integer_value=InpAppliedPrice; param[4].type=TYPE_INT; param[4].integer_value=InpFastEMAPeriod; param[5].type=TYPE_INT; param[5].integer_value=InpSlowEMAPeriod; break; case TRAILING_MODE_DEMA : param[0].type=TYPE_INT; param[0].integer_value=InpMAPeriod; param[1].type=TYPE_INT; param[1].integer_value=InpMAShift; param[2].type=TYPE_INT; param[2].integer_value=InpAppliedPrice; break; case TRAILING_MODE_FRAMA : param[0].type=TYPE_INT; param[0].integer_value=InpMAPeriod; param[1].type=TYPE_INT; param[1].integer_value=InpMAShift; param[2].type=TYPE_INT; param[2].integer_value=InpAppliedPrice; break; case TRAILING_MODE_MA : param[0].type=TYPE_INT; param[0].integer_value=InpMAPeriod; param[1].type=TYPE_INT; param[1].integer_value=InpMAShift; param[2].type=TYPE_INT; param[2].integer_value=InpAppliedPrice; param[3].type=TYPE_INT; param[3].integer_value=InpMAMethod; break; case TRAILING_MODE_TEMA : param[0].type=TYPE_INT; param[0].integer_value=InpMAPeriod; param[1].type=TYPE_INT; param[1].integer_value=InpMAShift; param[2].type=TYPE_INT; param[2].integer_value=InpAppliedPrice; break; case TRAILING_MODE_VIDYA : param[0].type=TYPE_INT; param[0].integer_value=InpMAPeriod; param[1].type=TYPE_INT; param[1].integer_value=InpMAShift; param[2].type=TYPE_INT; param[2].integer_value=InpAppliedPrice; param[6].type=TYPE_INT; param[6].integer_value=InpCMOPeriod; break; case TRAILING_MODE_SIMPLE : break; default: break; } }
Здесь для каждого из типов тралов, для используемых в трале индикаторов устанавливаются в структуру значения, заданные во входных параметрах советника. Присвоенные полям структуры величины проверяются на правильность и корректируются в новом классе CSymbolTradeExt торгового объекта символа, рассмотренном нами выше.
Доработаем функцию для торговли по истории сделок. Теперь нам нужно чётко разделять какой тип тестирования используется.
Для тестирования оригинальной торговли стоп-приказы выставлять не нужно — все позиции закрываются по закрывающим историческим сделкам.
Если тестируется торговля с иными размерами стоп-приказов, то нужно получить корректные их размеры и установить для открываемой позиции. В случае же с тестированием тралов, стопы выставлять не обязательно, но, во-избежание больших просадок и "пересиживаний", изначальные стопы рекомендуется выставлять, и уже потом они трейлингом будут смещаться по заданной логике. В настройках советника есть параметры, определяющие необходимость выставления изначальных стоп-приказов:
input bool InpSetStopLoss = true; /* Set Initial StopLoss */ // Устанавливать начальный StopLoss input bool InpSetTakeProfit = true; /* Set Initial TakeProfit */ // Устанавливать начальный TakeProfit
При их использовании тоже нужно получить корректные размеры стопов и установить их для открываемой позиции:
//+------------------------------------------------------------------+ //| Торговля по истории | //+------------------------------------------------------------------+ void TradeByHistory(const string symbol="", const long magic=-1) { datetime time=0; int total=ExtListSymbols.Total(); // количество торговых объектов в списке //--- в цикле по всем торговым объектам символов for(int i=0; i<total; i++) { //--- получаем очередной торговый объект CSymbolTradeExt *obj=ExtListSymbols.At(i); if(obj==NULL) continue; //--- получаем текущую сделку, на которую указывает индекс списка сделок CDeal *deal=obj.GetDealCurrent(); if(deal==NULL) continue; //--- фильтруем сделку по магику и символу if((magic>-1 && deal.Magic()!=magic) || (symbol!="" && deal.Symbol()!=symbol)) continue; //--- фильтруем сделку по типу (только сделки покупки/продажи) ENUM_DEAL_TYPE type=deal.TypeDeal(); if(type!=DEAL_TYPE_BUY && type!=DEAL_TYPE_SELL) continue; //--- если это уже обработанная в тестере сделка - идём к следующей if(deal.TicketTester()>0) continue; //--- если время сделки ещё не настало - идём к следующему торговому объекту следующего символа if(!obj.CheckTime(deal.Time())) continue; //--- если сделка входа в рынок ENUM_DEAL_ENTRY entry=deal.Entry(); if(entry==DEAL_ENTRY_IN) { //--- устанавливаем размеры стоп-приказов в зависимости от метода установки стопов //--- изначально стоп-приказы не используются (для оригинальной торговли) ENUM_ORDER_TYPE order_type=(deal.TypeDeal()==DEAL_TYPE_BUY ? ORDER_TYPE_BUY : ORDER_TYPE_SELL); double sl=0; double tp=0; //--- если режим установки указанных значений стоп-приказов if(InpTestingMode==TESTING_MODE_SLTP) { //--- получаем корректные значения для StopLoss и TakeProfit sl=CorrectStopLoss(deal.Symbol(), order_type, ExtStopLoss); tp=CorrectTakeProfit(deal.Symbol(), order_type, ExtTakeProfit); } //--- иначе, если тестирование с трейлингами else { if(InpTestingMode!=TESTING_MODE_ORIGIN) { //--- если разрешено в настройках, выставляем корректные стоп-приказы открываемым позициям if(InpSetStopLoss) sl=CorrectStopLoss(deal.Symbol(), order_type, ExtStopLoss); if(InpSetTakeProfit) tp=CorrectTakeProfit(deal.Symbol(), order_type, ExtTakeProfit); } } //--- открываем позицию по типу сделки ulong ticket=(type==DEAL_TYPE_BUY ? obj.Buy(deal.Volume(), deal.Magic(), sl, tp, deal.Comment()) : type==DEAL_TYPE_SELL ? obj.Sell(deal.Volume(),deal.Magic(), sl, tp, deal.Comment()) : 0); //--- если позиция открыта (получили её тикет) if(ticket>0) { //--- увеличиваем количество обработанных тестером сделок и записываем тикет сделки в тестере в свойства объекта сделки obj.SetNumProcessedDeals(obj.NumProcessedDeals()+1); deal.SetTicketTester(ticket); //--- получаем идентификатор позиции в тестере и записываем его в свойства объекта сделки long pos_id_tester=0; if(HistoryDealSelect(ticket)) { pos_id_tester=HistoryDealGetInteger(ticket, DEAL_POSITION_ID); deal.SetPosIDTester(pos_id_tester); } } } //--- если сделка выхода из рынка if(entry==DEAL_ENTRY_OUT || entry==DEAL_ENTRY_INOUT || entry==DEAL_ENTRY_OUT_BY) { //--- получаем сделку, на основании которой была открыта позиция CDeal *deal_in=obj.GetDealInByPosID(deal.PositionID()); if(deal_in==NULL) continue; //--- получаем тикет позиции в тестере из свойств открывающей сделки //--- если тикет равен нулю, значит скорее всего позиция в тестере уже закрыта ulong ticket_tester=deal_in.TicketTester(); if(ticket_tester==0) { PrintFormat("Could not get position ticket, apparently position #%I64d (#%I64d) is already closed \n", deal.PositionID(), deal_in.PosIDTester()); obj.SetNextDealIndex(); continue; } //--- если воспроизводим в тестере оригинальную торговую историю, if(InpTestingMode==TESTING_MODE_ORIGIN) { //--- если позиция закрыта по тикету if(obj.ClosePos(ticket_tester)) { //--- увеличиваем количество обработанных тестером сделок и записываем тикет сделки в тестере в свойства объекта сделки obj.SetNumProcessedDeals(obj.NumProcessedDeals()+1); deal.SetTicketTester(ticket_tester); } } //--- иначе - в тестере работаем со стоп-приказами, выставляемыми по различным алгоритмам, и сделки закрытия пропускаются; //--- соответственно, для закрывающей сделки просто увеличиваем количество обработанных тестером сделок и //--- записываем тикет сделки в тестере в свойства объекта сделки else { obj.SetNumProcessedDeals(obj.NumProcessedDeals()+1); deal.SetTicketTester(ticket_tester); } } //--- если теперь в объекте сделки записан тикет - значит сделка успешно обработана - //--- устанавливаем индекс сделки в списке на следующую сделку if(deal.TicketTester()>0) { obj.SetNextDealIndex(); } } }
Теперь напишем функцию, осуществляющую трейлинг позиций:
//+------------------------------------------------------------------+ //| Трейлинг позиций | //+------------------------------------------------------------------+ void Trailing(void) { //--- переменные для получения свойств позиции long magic=-1; string symbol=""; //--- в цикле по всем позициям int total=PositionsTotal(); for(int i=total-1; i>=0; i--) { //--- получаем тикет очередной позиции ulong ticket=PositionGetTicket(i); if(ticket==0) continue; //--- получаем магик и символ позиции ResetLastError(); if(!PositionGetInteger(POSITION_MAGIC, magic)) { Print("PositionGetInteger() failed. Error ", GetLastError()); continue; } if(!PositionGetString(POSITION_SYMBOL, symbol)) { Print("PositionGetString() failed. Error ", GetLastError()); continue; } //--- если позиция не проходит по заданным условиям магика и символа - идём к следующей if((InpTestedMagic>-1 && magic!=InpTestedMagic) || (InpTestedSymbol!="" && symbol!=InpTestedSymbol)) continue; //--- получаем торговый объект по имени символа и вызываем его метод трейлинга позиции по тикету CSymbolTradeExt *obj=GetSymbolTrade(symbol, &ExtListSymbols); if(obj!=NULL) obj.Trailing(ticket); } }
Ну здесь уже всё просто: выбираем в цикле каждую позицию, проверяем соответствие её магика и символа с теми, что введены в настройках советника и, если это нужная позиция^ — получаем торговый объект символа этой позиции и вызываем из него трейлинг с указанием тикета позиции.
Впишем вызов этой функции в обработчик OnTick() советника:
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- работаем только в тестере стратегий if(!MQLInfoInteger(MQL_TESTER)) return; //--- Тралим открытые позиции Trailing(); //--- Обрабатываем список сделок из файла TradeByHistory(InpTestedSymbol, InpTestedMagic); }
Всё. Советник готов. С полным кодом советника можно ознакомиться в прилагаемых к статье файлах.
Давайте протестируем различные виды тралов и сравним результаты оригинальной торговли и торговли с применением трейлинга позиций по разным алгоритмам.
При запуске советника на графике символа, он собирает историю торговли, записывает её в файл и выводит алерт с указанием желательных настроек тестера — начальную дату тестирования, стартовый депозит и плечо:
В журнал выводит используемые в торговле символы и количество совершённых на каждом символе сделок:
Alert: Now you can run testing Interval: 2024.09.13 - current date Initial deposit: 3000.00, leverage 1:500 Symbols used in trading: 1. AUDUSD trade object. Total deals: 222 2. EURJPY trade object. Total deals: 120 3. EURUSD trade object. Total deals: 526 4. GBPUSD trade object. Total deals: 352 5. NZDUSD trade object. Total deals: 182 6. USDCAD trade object. Total deals: 22 7. USDCHF trade object. Total deals: 250 8. USDJPY trade object. Total deals: 150 9. XAUUSD trade object. Total deals: 118
Теперь запустим советник в тестере с рекомендуемыми параметрами тестера, указанными в алерте и продублированными в журнале.
Оригинальная торговля:
Видим, что оригинальная торговля принесла убыток в 658 долларов.
Попробуем различные трейлинги. Разрешим устанавливать изначальный стоп размером в 100 пунктов, и запустим каждый трал в тестере.
Просто по очереди, без изменения других параметров, и поглядим, как изменится торговля.
Простой трейлинг:
Получили убыток в 746.1 долларов. Даже больше, чем в оригинальной торговле.
Проверим тралы по индикаторам.
Трал по Parabolic SAR:
Профит - 541.8 доллара.
Теперь рассмотрим тралы на основе различных типов скользящих средних.
Трал по АМА:
Профит - 806.5 доллара.
Трал по DEMA:
Профит - 1397.1 долларов.
Трал по FRAMA:
Профит - 1291.6 долларов.
Трал по MA:
Профит - 563.1 доллара.
Трал по TEMA:
Профит - 1355.1 долларов.
Трал по VIDYA:
Профит - 283.3 долларов.
Итак, в итоговой таблице расположены результаты при использовании различных типов тралов StopLoss относительно оиригинальной торговли:
# | Тип трала | Размер StopLoss | Размер TakeProfit | Итоговая прибыль |
---|---|---|---|---|
1 | Оригинальная торговля | 100 | 500 | - 658.0 |
2 | Простой TrailingStop | Изначально 100, далее стоп на 100 пунктов от цены | 0 | - 746.1 |
3 | TrailingStop по Parabolic SAR | Изначально 100, далее стоп по значению индикатора | 0 | + 541.8 |
4 | TrailingStop по VIDYA | Изначально 100, далее стоп по значению индикатора | 0 | -283.3 |
5 | TrailingStop по MA | Изначально 100, далее стоп по значению индикатора | 0 | + 563.1 |
6 | TrailingStop по АМА | Изначально 100, далее стоп по значению индикатора | 0 | + 806.5 |
7 | TrailingStop по FRAMA | Изначально 100, далее стоп по значению индикатора | 0 | + 1291.6 |
8 | TrailingStop по TEMA | Изначально 100, далее стоп по значению индикатора | 0 | + 1355.1 |
9 | TrailingStop по DEMA | Изначально 100, далее стоп по значению индикатора | 0 | + 1397.1 |
В итоге, при убыточной оригинальной торговле, обычный простой трал, примерный аналог трала в клиентском терминале, даёт ещё больший убыток. Ладно, смотрим трал на основе Parabolic SAR, позиционируемый как индикатор, отображающий развороты и уровни стопов (Stop And Reverse). Да, при следовании уровня StopLoss за линией индикатора на первом баре мы получили прибыль в 540 долларов.
Посмотрим теперь на результаты перемещения стопов позиций по значениям различных типов скользящих средних на первом баре. Трейлинг по индикатору VIDYA дал убыток в 280 долларов, но все остальные показали прибыль. Причём, следование стопа за простой скользящей средней дало прибыль большую, чем трал по Parabolic SAR. А абсолютным "чемпионом" по улучшению результатов торговли стала двойная экспоненциальная скользящая средняя — +1397 долларов прибыли.
Заключение
Мы создали советник, позволяющий протестировать свою торговлю в тестере стратегий и выбрать для своего стиля торговли наиболее подходящий трейлинг стопов позиций. Следует учитывать, что все параметры индикаторов, использованные при тестировании в трейлингах, имели стандартные значения, а настройки тралов были выбраны случайно, "на глаз".
Наиболее правильным решением будет проводить тестирование по каждому отдельному символу, и подбирать приемлемые настройки тралов с учётом особенностей движения и волатильности валютной пары. Но в этом советнике нет раздельных настроек для установки "своих" параметров для каждого отдельного проторгованного символа. Но главное, что была показана возможность при помощи тестера стратегий протестировать, проанализировать и улучшить торговлю. А значит, можно дальше доработать советник для возможности задать свои параметры для каждого символа — это несложно. Главное — желание.
К статье прилагаются все рассмотренные файлы классов и советника. Также приложен архив, распаковав который, можно сразу же получить установленные файлы для теста в нужных папках терминала.
Программы, используемые в статье:
# | Имя | Тип | Описание |
---|---|---|---|
1 | Trailings.mqh | Библиотека класса | Библиотека классов трейлингов |
2 | SymbolTrade.mqh | Библиотека класса | Библиотека структуры и класса сделки, торговый класс символа |
3 | SymbolTradeExt.mqh | Библиотека класса | Торговый класс символа и трейлинга |
4 | TradingByHistoryDeals_Ext.mq5 | Советник | Советник для просмотра и изменения при помощи StopLoss, TakeProfit и трейлингов в тестере стратегий сделок и трейдов, проторгованных на счёте |
5 | MQL5.zip | Архив | Архив файлов, представленных выше, для распаковки в каталог MQL5 клиентского терминала |
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.





- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования
Трал по VIDYA:
Профит - 283.3 долларов.Ошибка: Убыток- 283.3 долларов.
Ошибка: Убыток- 283.3 долларов.
В статье, написано отрицательное значение профита.
Правда пробел после минуса закрался случайно.