English 中文 Español Deutsch 日本語 Português
Кроссплатформенный торговый советник: Пользовательские стопы, Безубыток и Трейлинг

Кроссплатформенный торговый советник: Пользовательские стопы, Безубыток и Трейлинг

MetaTrader 5Примеры | 20 октября 2017, 10:29
8 141 0
Enrico Lambino
Enrico Lambino

Оглавление

  1. Введение
  2. Пользовательские стоп-уровни
  3. Изменение стоп-уровней
  4. Безубыток
  5. Трейлинг-стоп
  6. Трейлинг уровня тейк-профита
  7. Реализация
  8. CTrails (Контейнер)
  9. Расширение CTrail
  10. Примеры

Введение

В предыдущей статье было показано, как использовать класс CStop для установки стоп-лосса и тейк-профита для сделки в кроссплатформенном торговом эксперте. Указанные уровни могут быть выражены в пипсах или пунктах, но далеко не для всех советников. Во многих экспертах стоп-лосс и тейк-профит должны выражаться не в расстоянии от входной цены, а от цены на графике (обычно в результате ряда других расчетов).

В этой статье мы и обсудим, как использовать класс CStop для определения стоп-уровней, выраженных в цене на графике. Также покажем, как можно изменять эти уровни с использованием специальной библиотеки. Кроме того, вводится новый класс CTrail, который очень похож на реализацию пользовательских стоп-уровней. Однако, в отличие от пользовательских стопов, CTrail используется для последовательного изменения стоп-уровней с течением времени.

Пользовательские стоп-уровни

Итак, мы уже знаем, как реализовать в кроссплатформенном советнике стоп-лосс/тейк-профит, выраженный в пунктах или пипсах. Но есть стратегии, которые требуют динамического вычисления стоп-уровней, обычно на основе данных таймсерий (OHLC) и по данным технических индикаторов.

Динамический расчет стоп-лосса и тейк-профита можно настроить в классе CStop через два его метода, StopLossCustom и TakeProfitCustom. Они являются перегрузками методов, уже существующих в этом классе. Вот их код:

bool CStopBase::StopLossCustom(void)
  {
   return m_stoploss==0;
  }

bool CStopBase::TakeProfitCustom(void)
  {
   return m_takeprofit==0;
  }

Программист может расширить их, чтобы добиться требуемого стоп-лосса или тейк-профита для любого экземпляра CStop.

Ниже перечислены альтернативные методы, которые возвращают значение типа bool:

bool CStopBase::StopLossCustom(void)
  {
   return m_stoploss==0;
  }

bool CStopBase::TakeProfitCustom(void)
  {
   return m_takeprofit==0;
  }

Обе функции возвращают true, если отдельный член класса (или m_stoploss, или m_takeprofit) имеет нулевое значение. Их назначение мы обсудим ниже.

Рассмотрим сценарий, по которому CStop рассчитывает стоп-уровень.  Для описания следующих шагов предположим, что мы работаем со стоп-лоссом (m_stoploss).

  1. Если m_stoploss равен 0, используем для расчета StopLossCustom.
  2. Если m_stoploss не равен 0, используем именно его, чтобы рассчитать текущий стоп-лосс сделки по отношению к цене входа.

Такая же последовательность действий используется для расчета тейк-профита с помощью метода TakeProfitCustom и члена класса m_takeprofit.

Эти четыре метода могут быть локализованы в двух местах, в зависимости от того, для чего они используются. Для главных стоп-уровней метод вызывается из менеджера ордеров (COrderManager). Для остальных стопов — из экземпляра каждого ордера.

В случаях, когда главные стоп-уровни отправлены брокеру вместе с торговым запросом, советнику требуется эта информация уже в момент отправки торгового запроса, а не после того, как успешно отправлена начальная сделка. Это верно для брокерских стопов в MetaTrader 4 и хеджингового режима MetaTrader 5. В этих режимах информация по стоп-лоссу и тейк-профиту должна быть включена в торговый запрос для функции OrderSend (MQL4, MQL5), и эти стоп-уровни применяются только к основной торговой сделке.

Внутри метода TradeOpen менеджера ордеров находится вызов двух методов CStop. Ниже показан код для версии MQL4:

bool COrderManager::TradeOpen(const string symbol,ENUM_ORDER_TYPE type,double price,bool in_points=true)
  {
   int trades_total = TradesTotal();
   int orders_total = OrdersTotal();
   m_symbol=m_symbol_man.Get(symbol);
   if(!CheckPointer(m_symbol))
      return false;
   if(!IsPositionAllowed(type))
      return true;
   if(m_max_orders>orders_total && (m_max_trades>trades_total || m_max_trades<=0))
     {
      ENUM_ORDER_TYPE ordertype=type;
      if(in_points)
         price=PriceCalculate(type);
      double sl=0,tp=0;
      if(CheckPointer(m_main_stop)==POINTER_DYNAMIC)
        {
         sl = m_main_stop.StopLossCustom()?m_main_stop.StopLossCustom(symbol,type,price):m_main_stop.StopLossCalculate(symbol,type,price);
         tp = m_main_stop.TakeProfitCustom()?m_main_stop.TakeProfitCustom(symbol,type,price):m_main_stop.TakeProfitCalculate(symbol,type,price);
        }
      double lotsize=LotSizeCalculate(price,type,sl);
      if(CheckPointer(m_main_stop)==POINTER_DYNAMIC)
      {
         if (!m_main_stop.Broker())
         {
            sl = 0;
            tp = 0;
         }
      }
      int ticket=(int)SendOrder(type,lotsize,price,sl,tp);
      if(ticket>0)
        {
         if(OrderSelect(ticket,SELECT_BY_TICKET))
         {
            COrder *order = m_orders.NewOrder(OrderTicket(),OrderSymbol(),OrderMagicNumber(),
(ENUM_ORDER_TYPE)::OrderType(),::OrderLots(),::OrderOpenPrice());            
            if (CheckPointer(order))
            {
               LatestOrder(GetPointer(order));
               return true;
            }   
         }         
        }
     }
   return false;
  }

Ниже представлен код для версии MQL5:

bool COrderManager::TradeOpen(const string symbol,ENUM_ORDER_TYPE type,double price,bool in_points=true)
  {
   bool ret=false;
   double lotsize=0.0;
   int trades_total =TradesTotal();
   int orders_total = OrdersTotal();
   m_symbol=m_symbol_man.Get(symbol);
   if(!IsPositionAllowed(type))
      return true;
   if(m_max_orders>orders_total && (m_max_trades>trades_total || m_max_trades<=0))
     {
      if(in_points)
         price=PriceCalculate(type);
      double sl=0.0,tp=0.0;
      if(CheckPointer(m_main_stop)==POINTER_DYNAMIC)
        {
         sl = m_main_stop.StopLossCustom()?m_main_stop.StopLossCustom(symbol,type,price):m_main_stop.StopLossCalculate(symbol,type,price);
         tp = m_main_stop.TakeProfitCustom()?m_main_stop.TakeProfitCustom(symbol,type,price):m_main_stop.TakeProfitCalculate(symbol,type,price);
        }
      lotsize=LotSizeCalculate(price,type,m_main_stop==NULL?0:m_main_stop.StopLossCalculate(symbol,type,price));  
      if(CheckPointer(m_main_stop)==POINTER_DYNAMIC)
      {
         if (!m_main_stop.Broker() || !IsHedging())
         {
            sl = 0;
            tp = 0;
         }
      }    
      ret=SendOrder(type,lotsize,price,sl,tp);
      if(ret)
      {
         COrder *order = m_orders.NewOrder((int)m_trade.ResultOrder(),m_trade.RequestSymbol(),(int)m_trade.RequestMagic(),
m_trade.RequestType(),m_trade.ResultVolume(),m_trade.ResultPrice());
         if (CheckPointer(order))
         {
            LatestOrder(GetPointer(order));
            return true;
         }
      }         
     }
   return ret;
  }

Несмотря на то, что имплементация TradeOpen раздельная, мы можем найти общий знаменатель в том, каким способом две версии метода рассчитывают главные стоп-уровни. Сначала рассчитываются стоп-лосс и тейк-профит (sl и tp, соответственно) с учетом основного стоп-уровня. Это делается независимо от того, требовались ли стоп-лосс и тейк-профит в текущем первоначальном торговом запросе, поскольку информация также может понадобиться для расчета объема лота (при мани-менеджменте).

После того, как объем лота рассчитан, в некоторых случаях сбросить стоп-уровни к 0. Так, например, в MQL4 переменные sl и tp приравниваются к 0, если главный стоп не является брокерским.

if(CheckPointer(m_main_stop)==POINTER_DYNAMIC)
{
   if (!m_main_stop.Broker())
   {
      sl = 0;
      tp = 0;
   }
}

В MQL5 мы стараемся избегать использования стоп-лосса и тейк-профита в неттинговом режиме, поскольку они будут применены ко всему объему позиции, а не только к объему вводимой сделки. Поэтому переменные устанавливаются на 0, если стоп не является брокерским, или платформа находится в неттинговом режиме.

if(CheckPointer(m_main_stop)==POINTER_DYNAMIC)
{
   if (!m_main_stop.Broker() || !IsHedging())
   {
      sl = 0;
      tp = 0;
   }
}  

Как обсуждалось в предыдущей статье, в неттинговом режиме советник превращает брокерские стопы в стопы на основе отложенных ордеров, так что отпадает необходимость отправлять стоп-лосс и тейк-профит в торговом запросе на первоначальную сделку.

Для сделок с несколькими уровнями стоп-лосса и тейк-профита эти уровни обрабатываются, как только успешно обработана главная сделка. В стоп-уровнях нет смысла, если главная сделка еще не введена в рынок, поэтому она должна быть обработана первой. Эти дополнительные стоп-уровни генерируются во время инициализации экземпляра COrder, доступного из метода CreateStops (через COrderBase, поскольку имплементация в MQL4 и MQL5 одинакова):

void COrderBase::CreateStops(CStops *stops)
  {
   if(!CheckPointer(stops))
      return;
   if(stops.Total()>0)
     {
      for(int i=0;i<stops.Total();i++)
        {
         CStop *stop=stops.At(i);
         if(CheckPointer(stop)==POINTER_INVALID)
            continue;
         m_order_stops.NewOrderStop(GetPointer(this),stop);
        }
     }
  }

Как показано в этом коде, экземпляр COrderStop создается для каждого экземпляра CStop, найденного экспертом. Эти дополнительные стоп-уровни, однако, создаются только после того, как главная сделка успешно открыта на рынке.

Изменение стоп-уровней

У CStop есть собственный отдельный экземпляр CTradeManager, а значит, и CExpertTradeX (расширение класса CExpertTrade из Стандартной Библиотеки MQL5). Этот экземпляр независим от экземпляра, находящегося в менеджере ордеров (COrderManager) и использующегося исключительно для обработки входов в основные сделки.

То есть, изменение стоп-уровней управляется не СOrderManager, а только собственным экземпляром CStop. Однако мы знаем, что стоп-уровни для каждого ордера должны изменяться по отдельности. Значит, изменение должно вызываться из сделки, которая, в свою очередь, сама должна изменяться — то есть, из экземпляра COrder, представляющего эту сделку.

Мониторинг стоп-уровней начинается с метода CheckStops класса COrder, код которого приведен ниже:

void COrderBase::CheckStops(void)
  {
   m_order_stops.Check(m_volume);
  }

Здесь просто вызывается метод Check одного из членов класса, который является экземпляром COrderStops. Как мы знаем из предыдущей статьи, СOrderStops — контейнер указателей на экземпляры COrderStop.

Рассмотрим метод Check класса COrderStops. Он показан в нижеследующем фрагменте кода:

COrderStopsBase::Check(double &volume)
  {
   if(!Active())
      return;
   for(int i=0;i<Total();i++)
     {
      COrderStop *order_stop=(COrderStop *)At(i);
      if(CheckPointer(order_stop))
        {
         if(order_stop.IsClosed())
            continue;
         order_stop.CheckTrailing();
         order_stop.Update();
         order_stop.Check(volume);
         if(!CheckNewTicket(order_stop))
            return;
        }
     }
  }

Как мы видим из кода, метод Check выполняет как минимум пять действий над каждым экземпляром COrderStop.

  1. Проверяет, закрыт ли уже отдельный экземпляр COrderStop (метод IsClosed). Это необходимо для определения дальнейших действий над стоп-уровнем: если он закрыт, то в дальнейшем советник больше не должен его проверять.
  2. Обновляет стоп-уровни на основании назначенных для него экземпляров трейлинг-стопа, если они есть (метод CheckTrailing). Это действие относится только к трейлингу стоп-лосса или тейк-профита.
  3. Обновляет стоп-уровень (метод Update). Метод используется, когда стоп-уровень изменен не внутри советника, а извне (например, при перетаскивании линии виртуального стопа).
  4. Проверяет, не достигла ли цена определенного стоп-уровня (метод Check).
  5. Проверяет, представляет ли новый тикет собой сделку. Функция используется только в MetaTrader 4, потому что только в этой платформе эксперт меняет ID позиции при ее частичном закрытии. Имплементация для MetaTrader 5 в этом случае проще, потому что эта платформа сохраняет всю историю развития сделки.

Экземпляр COrderStop представляет стоп-уровень сделки как определенный в CStop. Значит, любое перетаскивание стоп-уровня приведет в итоге к изменению экземпляра этого объекта класса.

Метод CheckTrailing представлен ниже:

муbool COrderStopBase::CheckTrailing(void)
  {
   if(!CheckPointer(m_stop) || m_order.IsClosed() || m_order.IsSuspended() || 
      (m_stoploss_closed && m_takeprofit_closed))
      return false;
   double stoploss=0,takeprofit=0;
   string symbol=m_order.Symbol();
   ENUM_ORDER_TYPE type=m_order.OrderType();
   double price=m_order.Price();
   double sl = StopLoss();
   double tp = TakeProfit();
   if(!m_stoploss_closed)
      stoploss=m_stop.CheckTrailing(symbol,type,price,sl,TRAIL_TARGET_STOPLOSS);
   if(!m_takeprofit_closed)
      takeprofit=m_stop.CheckTrailing(symbol,type,price,tp,TRAIL_TARGET_TAKEPROFIT);
   if(!IsStopLossValid(stoploss))
      stoploss=0;
   if(!IsTakeProfitValid(takeprofit))
      takeprofit=0;
   return Modify(stoploss,takeprofit);
  }

Из этого кода мы видим, что он следует основной идее: нельзя изменить стоп-уровень, если он уже закрыт. Если ордер для стоп-уровня еще действующий, то вызывается метод CheckTrailing соответствующего экземпляра CStop.

Посмотрим метод CheckTrailing класса CStop:

double CStopBase::CheckTrailing(const string symbol,const ENUM_ORDER_TYPE type,const double entry_price,const double price,const ENUM_TRAIL_TARGET mode)
  {
   if(!CheckPointer(m_trails))
      return 0;
   return m_trails.Check(symbol,type,entry_price,price,mode);
  }

Здесь CStop вызывает метод Check одного из его членов класса, m_trails. m_trails — контейнер для указателей на объекты трейлинга. Код метода показан ниже:

double CTrailsBase::Check(const string symbol,const ENUM_ORDER_TYPE type,const double entry_price,const double price,const ENUM_TRAIL_TARGET mode)
  {
   if(!Active())
      return 0;   
   double val=0.0,ret=0.0;
   for(int i=0;i<Total();i++)
     {
      CTrail *trail=At(i);
      if(!CheckPointer(trail))
         continue;
      if(!trail.Active())
         continue;
      int trail_target=trail.TrailTarget();
      if(mode!=trail_target)
         continue;
      val=trail.Check(symbol,type,entry_price,price,mode);
      if((type==ORDER_TYPE_BUY && trail_target==TRAIL_TARGET_STOPLOSS) || (type==ORDER_TYPE_SELL && trail_target==TRAIL_TARGET_TAKEPROFIT))
      {
         if(val>ret || ret==0.0)
            ret=val;
      }      
      else if((type==ORDER_TYPE_SELL && trail_target==TRAIL_TARGET_STOPLOSS) || (type==ORDER_TYPE_BUY && trail_target==TRAIL_TARGET_TAKEPROFIT))
      {
         if(val<ret || ret==0.0)
            ret=val;
      }      
     }
   return ret;
  }

К этому моменту достаточно понимать, что контейнер CTrails перебирает свои экземпляры CTrail и возвращает окончательное значение. Оно выражается в цене на графике выбранного символа, и поэтому имеет тип double. Это и будет новое значение стоп-лосса или тейк-профита после его успешного передвижения.

Теперь вернемся к методу CheckTrailing класса COrderStop, потому что именно из него выполняется фактический вызов для изменения стоп-уровня:

муbool COrderStopBase::CheckTrailing(void)
  {
   if(!CheckPointer(m_stop) || m_order.IsClosed() || m_order.IsSuspended() || 
      (m_stoploss_closed && m_takeprofit_closed))
      return false;
   double stoploss=0,takeprofit=0;
   string symbol=m_order.Symbol();
   ENUM_ORDER_TYPE type=m_order.OrderType();
   double price=m_order.Price();
   double sl = StopLoss();
   double tp = TakeProfit();
   if(!m_stoploss_closed)
      stoploss=m_stop.CheckTrailing(symbol,type,price,sl,TRAIL_TARGET_STOPLOSS);
   if(!m_takeprofit_closed)
      takeprofit=m_stop.CheckTrailing(symbol,type,price,tp,TRAIL_TARGET_TAKEPROFIT);
   if(!IsStopLossValid(stoploss))
      stoploss=0;
   if(!IsTakeProfitValid(takeprofit))
      takeprofit=0;
   return Modify(stoploss,takeprofit);
  }

Метод возвращает значение типа bool. Оно представляет собой результат изменения стоп-уровня методом Modify класса COrderStop (возвращает true в случае успеха). Но прежде чем отправить запрос на изменение, метод проверяет, действительны ли еще стоп-лосс и тейк-профит. Для этого используются методы IsStopLossValid и IsTakeProfitValid. Если предложенное значение недействительно, оно обнуляется.

bool COrderStopBase::IsStopLossValid(const double stoploss) const
  {
   return stoploss!=StopLoss();
  }

bool COrderStopBase::IsTakeProfitValid(const double takeprofit) const
  {
   return takeprofit!=TakeProfit();
  }

В вышеприведенном коде и для стоп-лосса, и для тейк-профита предложенные значения не должны быть равными текущему.

После оценки стоп-лосса и тейк-профита вызывается метод Modify класса COrderStop. Он показан ниже:

bool COrderStopBase::Modify(const double stoploss,const double takeprofit)
  {
   bool stoploss_modified=false,takeprofit_modified=false;
   if(stoploss>0 && takeprofit>0)
     {
      if(ModifyStops(stoploss,takeprofit))
        {
         stoploss_modified=true;
         takeprofit_modified=true;
        }
     }
   else if(stoploss>0 && takeprofit==0)
      stoploss_modified=ModifyStopLoss(stoploss);
   else if(takeprofit>0 && stoploss==0)
      takeprofit_modified=ModifyTakeProfit(takeprofit);
   return stoploss_modified || takeprofit_modified;
  }

К настоящему моменту выполняются три типа операций, в зависимости от значений стоп-лосса и тейк-профита. Обычно изменение может быть проведено одним действием, как в случае брокерских стопов.

Но может быть и по-другому. Каждый виртуальный стоп-уровень и стоп на основе отложенных ордеров должен обрабатываться индивидуально: отдельно стоп-лосс и отдельно — тейк-профит. Код для изменения обоих значений представлен в следующих методах:

bool COrderStopBase::ModifyStops(const double stoploss,const double takeprofit)
  {
   return ModifyStopLoss(stoploss) && ModifyTakeProfit(takeprofit);
  }


Задача метода ModifyStops — вызвать остальные два метода. С этого момента начинается раздельная имплементация. Она основывается на двух важных факторах: это тип компилятора (MQL4 или MQL5) и тип стопа (брокерский, отложенный или виртуальный). Если мы имеем дело с брокерским стопом, результатом будет торговый запрос на изменение главной позиции. В случае стопов на основе отложенных ордеров эксперт должен будет сдвинуть цену открытия отложенного ордера. Если стоп виртуальный, советник просто обновит свои внутренние данные, относящиеся к стоп-уровню.

У COrderStop нет торговых объектов (или указателей на них) в качестве членов класса, и поэтому он по своей природе не способен изменять собственные стоп-уровни. Чтобы сделать это, ему по-прежнему нужен экземпляр CStop. Следовательно, результатом любого изменения стоп-уровня будет вызов метода в CStop.

Безубыток

Безубыток — это точка, в которой выручка и расходы равны. В трейдинге выручка — это текущий профит по сделке, а расходы — это спред и/или комиссия. Иными словами, в точке безубытка сделка закрывается с нулевыми прибылями/потерями. В случае маркетмейкеров или брокеров, которые не взимают комиссий, точка безубытка обычно соответствует цене входа в сделку.

В советниках сделка достигает безубыточности, когда эксперт устанавливает стоп-лосс в точке цены входа. Когда сделка достигает этого уровня, ее максимальный риск становится равным нулю. Однако помним, что сделка все еще может быть закрыта и по другим причинам: например, по сигналу на выход, обычно в профите.

При работе с безубытком у советника есть как минимум два ограничения:

  1. Динамика стоп-лосса. Стоп-лосс сделки всегда должен быть ниже текущей рыночной цены для длинных позиций и выше — для коротких. С учетом этого ограничения, советник может сдвинуть стоп-лосс на точку безубытка, только если сделка находится в состоянии нереализованного профита.
  2. Минимальный отступ от текущей цены закрытия для установки стоп-ордера, назначенный брокером. Требование минимального расстояния между стоп-уровнями применяется и при изменении стоп-лосса сделки, а не только при ее открытии. Так образом, зачастую невозможно установить стоп-лосс сделки на уровне безубытка, как только произошел вход в рынок. Цена должна отодвинуться на определенное расстояние, чтобы советник мог сдвинуть стоп-лосс в направлении точки безубытка.

С учетом этих ограничений, в разработке функции для безубытка мы должны учитывать как минимум два фактора:

  1. Цену срабатывания.
  2. Новый стоп-лосс при срабатывании.

Цена активации, или цена срабатывания, — цена, которая должна быть достигнута рынком, чтобы советник мог передвинуть стоп-лосс сделки к точке безубытка. Она определяется в зависимости от развития позиции, поскольку сделки имеют разные цены входа.

После срабатывания советник должен передвинуть стоп-лосс в точку безубытка. Обычно (но не всегда!) она будет совпадать с ценой открытия позиции. Для брокеров, которые взимают комиссию, точка безубытка будет выше входной цены для длинных позиций и ниже — для коротких.

Эти значения обычно вычисляются с учетом другого значения — например, текущей рыночной цены и цены входа в сделку.

На рисунке ниже показана блок-схема расчета точки безубытка способом, описанным выше. Вычисляются три значения — активация, дезактивация и новый стоп-уровень. Если уровень текущей цены выше или равен минимальной цене, требуемой для начального этапа (установки стоп-лосса к уровню безубытка), тогда стоп-уровень, рассчитанный ранее, будет использоваться как предварительный новый стоп-уровень. В противном случае результатом будет 0. Следующим шагом нужно проверить, находится ли новый стоп-уровень пределах текущего стоп-уровня брокера. Проверка всегда возвращает true при удовлетворении предыдущего условия. Таким образом, в качестве конечного итога будет возвращен рсссчитанный стоп-уровень.


Безубыток


Трейлинг-стоп

Трейлинг-стоп можно рассматривать как частный случай перехода в безубыток. В то время как точка безубытка обычно одна, трейлинг-стопа мы можем достигать несколько раз. После активации трейлинг обычно используется в течение всей жизни позиции. При разработке трейлинг-стопа надо учитывать как минимум три фактора:

  1. Цену срабатывания.
  2. Новый стоп-лосс при срабатывании.
  3. Частоту трейлинга.

Функция трейлинг-стопа имеет две общих переменных с безубытком. Однако, в отличие от безубытка, цена при активации трейлинг-стопа может находиться в точке, где сделка находится в состоянии нереализованной потери или “out of the money”. Пока сделка остается убыточной, ее стоп-лосс можно изменять, а также отслеживать в динамике. Когда это происходит, достижение текущего стоп-лосса сделки все еще будет приводить к потерям, поскольку он еще не удовлетворяет предпосылкам для достижения безубыточности или даже получения прибыли.

Третий фактор — частота трейлинг-стопа, или "шаг". Он определяет расстояние (в пунктах или пипсах) от текущего стоп-лосса до следующего, после первого передвижения стоп-лосса. Она почти аналогична первой переменной — цене активации. Однако, в отличие от цены активации, цена, рассчитанная по этой третьей переменной, изменяется с каждым шагом трейлинга.

В некоторых трейлинг-стопах есть четвертый фактор — цена дезактивации. По достижению этой точки советник прекращает передвигать стоп-лосс. Это тоже рассчитывается отдельно для каждой сделки. 

На картинке ниже показана блок-схема передвижения стоп-лосса сделки. Отличий от предыдущей иллюстрации немного.

Если цена выше, чем начальный шаг трейлинга, то весьма вероятен выход стоп-уровня за рамки этого начального шага. В противном случае он будет использовать эту начальную точку трейлинга как значение для нового стоп-уровня для текущего прогона. Если стоп-уровень еще не вышел за пределы цены активации, он просто следует за остальной частью расчета безубытка (цена дезактивации не применяется к безубытку).


Трейлинг-стоп


Трейлинг уровня тейк-профита

Трейлинг, или трейлинг-стоп, обычно подразумевает под собой подтаскивание стоп-лосса, но возможен и трейлинг тейк-профита сделки. В этом случае, если цена подбирается к уровню тейк-профита, он плавно передвигается дальше (противоположность трейлинг-стопа).  Теоретически, цена никогда не должна достигнуть тейк-профита, который подвергается трейлингу. Однако иногда это  возможно: — например, при внезапных скачках цен и ценовых разрывах. Словом, это любое условие, при котором рынок движется быстрее, чем советник может на это отреагировать.

Чтобы трейлинг тейк-профита был значимым, должны быть удовлетворены несколько условий:

  1. Цена активации должна находиться внутри текущего уровня тейк-профита. Если цена активации выходит за пределы текущего уровня тейк-профита, цена рынка должна пробить этот уровень прежде, чем цену активации. И как только тейк-профит будет достигнут, сделка покинет рынок задолго до того, как может начаться трейлинг этого уровня. Для длинных позиций цена активации должна быть ниже уровня тейк-профита. Для коротких позиций цена активации должна находиться выше, чем тейк-профит.
  2. Цена, на котором произойдет следующее срабатывание трейлинга, должна находиться в пределах текущего уровня тейк-профита. Суть здесь та же, что в первом условии, с тем лишь различием, что второе применяется ко второму и дальнейшим срабатываниям.
  3. В идеале, трейлинг должен быть как можно менее частым. Дистанция между уровнями (шаг) должна быть достаточно широкой. Большая частота трейлинга тейк-профита уменьшает шансы на то, что рынок достигнет уровня тейк-профита, потому что он отодвигается от текущей цены на каждом этапе трейлинга.

Реализация

CTrailBase, который служит базовым классом для CTrail, показан в коде ниже:

class CTrailBase : public CObject
  {
protected:
   bool              m_active;
   ENUM_TRAIL_TARGET m_target;
   double            m_trail;
   double            m_start;
   double            m_end;
   double            m_step;
   CSymbolManager   *m_symbol_man;
   CSymbolInfo      *m_symbol;
   CEventAggregator *m_event_man;
   CObject          *m_container;
public:
                     CTrailBase(void);
                    ~CTrailBase(void);
   virtual int       Type(void) const {return CLASS_TYPE_TRAIL;}
   //--- инициализация                    
   virtual bool      Init(CSymbolManager*,CEventAggregator*);
   virtual CObject *GetContainer(void);
   virtual void      SetContainer(CObject*);
   virtual bool      Validate(void) const;
   //--- методы получения и установки    
   bool              Active(void) const;
   void              Active(const bool);
   double            End(void) const;
   void              End(const double);
   void              Set(const double,const double,const double,const double);
   double            Start(void) const;
   void              Start(const double);
   double            Step(void) const;
   void              Step(const double);
   double            Trail(void) const;
   void              Trail(const double);
   int               TrailTarget(void) const;
   void              TrailTarget(const ENUM_TRAIL_TARGET);
   //--- проверка
   virtual double    Check(const string,const ENUM_ORDER_TYPE,const double,const double,const ENUM_TRAIL_TARGET);
protected:
   //--- price calculation
   virtual double    ActivationPrice(const ENUM_ORDER_TYPE,const double);
   virtual double    DeactivationPrice(const ENUM_ORDER_TYPE,const double);
   virtual double    Price(const ENUM_ORDER_TYPE);
   virtual bool      Refresh(const string);
  };
Метод Set позволяет нам изменять настройки экземпляра CTrails. Он работает как обычный конструктор класса. Если необходимо, можно также объявить пользовательский конструктор, который вызывает этот метод:

void CTrailBase::Set(const double trail,const double st,const double step=1,const double end=0)
  {
   m_trail=trail;
   m_start=st;
   m_end=end;
   m_step=step;
  }

CTrail в своих расчетах опирается на данные по рынку. Таким образом, у него есть экземпляр менеджера символов (CSymbolManager) в качестве одного из членов класса. Прежде чем осуществить дальнейшие расчеты, необходимо обновить символ.

bool CTrailBase::Refresh(const string symbol)
  {
   if(!CheckPointer(m_symbol) || StringCompare(m_symbol.Name(),symbol)!=0)
      m_symbol=m_symbol_man.Get(symbol);
   return CheckPointer(m_symbol);
  }

Цена активации — цена, которая вызовет начальное передвижение стоп-лосса или тейк-профита посредством экземпляра CTrail. Начальные и конечные параметры, а также шаг выражаются в пунктах, поэтому класс должен рассчитывать цену срабатывания в виде цены на графике. Точно так же рассчитываются другие цены:

double CTrailBase::ActivationPrice(const ENUM_ORDER_TYPE type,const double entry_price)
  {
   if(type==ORDER_TYPE_BUY)
      return entry_price+m_start*m_symbol.Point();
   else if(type==ORDER_TYPE_SELL)
      return entry_price-m_start*m_symbol.Point();
   return 0;
  }

Цена дезактивации рассчитывается по тому же плану, но используется член класса m_end class.

double CTrailBase::DeactivationPrice(const ENUM_ORDER_TYPE type,const double entry_price)
  {
   if(type==ORDER_TYPE_BUY)
      return m_end==0?0:entry_price+m_end*m_symbol.Point();
   else if(type==ORDER_TYPE_SELL)
      return m_end==0?0:entry_price-m_end*m_symbol.Point();
   return 0;
  }

Если назначенная цена дезактивации 0 — значит, эта функция отключена. Трейлинг будет применяться до тех пор, пока главная сделка не будет закрыта.

Метод Price рассчитывает новое значение стоп-лосса или тейк-профита, если условия в настоящий момент позволяют трейлинг стоп-лосса:

double CTrailBase::Price(const ENUM_ORDER_TYPE type)
  {
   if(type==ORDER_TYPE_BUY)
     {
      if(m_target==TRAIL_TARGET_STOPLOSS)
         return m_symbol.Bid()-m_trail*m_symbol.Point();
      else if(m_target==TRAIL_TARGET_TAKEPROFIT)
         return m_symbol.Ask()+m_trail*m_symbol.Point();
     }
   else if(type==ORDER_TYPE_SELL)
     {
      if(m_target==TRAIL_TARGET_STOPLOSS)
         return m_symbol.Ask()+m_trail*m_symbol.Point();
      else if(m_target==TRAIL_TARGET_TAKEPROFIT)
         return m_symbol.Bid()-m_trail*m_symbol.Point();
     }
   return 0;
  }

Теперь поговорим о методе Check. Конкретный экземпляр СTrail может отслеживать стоп-лосс либо тейк-профит. Поэтому в CTrail нам нужна возможность указывать, какой именно из стопов сделки он отслеживает. Для этого пригодится перечисление ENUM_TRAIL_TARGET. Его объявление находится в MQLx\Common\Enum\ENUM_TRAIL_TARGET.mqh, а код приведен ниже:

enum ENUM_TRAIL_TARGET
  {
   TRAIL_TARGET_STOPLOSS,
   TRAIL_TARGET_TAKEPROFIT
  };

Теперь рассмотрим метод Check. В отличие от других методов этого класса, которые мы обсуждали до сих пор, он публичный. Метод вызывается, когда нужно проверить, не обновился ли уровень трейлинг-стопа.

double CTrailBase::Check(const string symbol,const ENUM_ORDER_TYPE type,const double entry_price,const double price,const ENUM_TRAIL_TARGET mode)
  {
   if(!Active())
      return 0;
   if(!Refresh(symbol))
      return 0;
   if(m_start==0 || m_trail==0)
      return 0;
   double next_stop=0.0,activation=0.0,deactivation=0.0,new_price=0.0,point=m_symbol.Point();
   activation=ActivationPrice(type,entry_price);
   deactivation=DeactivationPrice(type,entry_price);
   new_price=Price(type);
   if(type==ORDER_TYPE_BUY)
     {
      if (m_target==TRAIL_TARGET_STOPLOSS)
      {
         if(m_step>0 && (activation==0.0 || price>=activation-m_trail*point) && (new_price>price+m_step*point))
            next_stop=new_price;
         else next_stop=activation-m_trail*point;
         if(next_stop<price)
            next_stop=price;
         if((deactivation==0) || (deactivation>0 && next_stop>=deactivation && next_stop>0.0))
            if(next_stop<=new_price)
               return next_stop;
      }
      else if (m_target==TRAIL_TARGET_TAKEPROFIT)
      {
         if(m_step>0 && ( activation==0.0 || price>=activation) && (new_price>price+m_step*point))
            next_stop=new_price;
         else next_stop=activation+m_trail*point;
         if(next_stop<price)
            next_stop=price;
         if((deactivation==0) || (deactivation>0 && next_stop<=deactivation && next_stop>0.0))
            if(next_stop>=new_price)
               return next_stop;
      }
     }
   if(type==ORDER_TYPE_SELL)
     {
      if (m_target==TRAIL_TARGET_STOPLOSS)
      {
         if(m_step>0 && (activation==0.0 || price<=activation+m_trail*point) && (new_price<price-m_step*point))
            next_stop=new_price;
         else next_stop=activation+m_trail*point;
         if(next_stop>price)
            next_stop=price;     
         if((deactivation==0) || (deactivation>0 && next_stop<=deactivation && next_stop>0.0))
            if(next_stop>=new_price)
               return next_stop;
      }
      else if (m_target==TRAIL_TARGET_TAKEPROFIT)
      {
         if(m_step>0 && (activation==0.0 || price<=activation) && (new_price<price-m_step*point))
            next_stop=new_price;
         else next_stop=activation-m_trail*point;
         if(next_stop>price)
            next_stop=price;     
         if((deactivation==0) || (deactivation>0 && next_stop<=deactivation && next_stop>0.0))
            if(next_stop<=new_price)
               return next_stop;
      }
     }
   return 0;
  }

Здесь мы можем видеть, что расчет трейлинга стоп лосса и тейк-профита различается. Для расчета стоп-лосса нам хотелось бы, чтобы с каждым этапом трейлинга он становился ближе к рыночной цене. От значения тейк-профита нам хотелось бы обратного — отодвинуть его от текущей цены на определенное расстояние (в пунктах или пипсах) с каждым шагом трейлинга.

CTrails (Контейнер)

CTrail также имеет контейнер — CTrails. Его определение показано в коде ниже:

class CTrailsBase : public CArrayObj
  {
protected:
   bool              m_active;
   CEventAggregator *m_event_man;
   CStop            *m_stop;
public:
                     CTrailsBase(void);
                    ~CTrailsBase(void);
   virtual int       Type(void) const {return CLASS_TYPE_TRAILS;}
   //--- инициализация
   virtual bool      Init(CSymbolManager*,CEventAggregator*);
   virtual CStop    *GetContainer(void);
   virtual void      SetContainer(CStop*stop);
   virtual bool      Validate(void) const;
   //--- методы получения и установки
   bool              Active(void) const;
   void              Active(const bool activate);
   //--- проверка
   virtual double    Check(const string,const ENUM_ORDER_TYPE,const double,const double,const ENUM_TRAIL_TARGET);
  };

Контейнер должен взаимодействовать с CStop и объектами CTrail, на которые он ссылается. Будет у него и собственный метод Check:

double CTrailsBase::Check(const string symbol,const ENUM_ORDER_TYPE type,const double entry_price,const double price,const ENUM_TRAIL_TARGET mode)
  {
   if(!Active())
      return 0;   
   double val=0.0,ret=0.0;
   for(int i=0;i<Total();i++)
     {
      CTrail *trail=At(i);
      if(!CheckPointer(trail))
         continue;
      if(!trail.Active())
         continue;
      int trail_target=trail.TrailTarget();
      if(mode!=trail_target)
         continue;
      val=trail.Check(symbol,type,entry_price,price,mode);
      if((type==ORDER_TYPE_BUY && trail_target==TRAIL_TARGET_STOPLOSS) || (type==ORDER_TYPE_SELL && trail_target==TRAIL_TARGET_TAKEPROFIT))
      {
         if(val>ret || ret==0.0)
            ret=val;
      }      
      else if((type==ORDER_TYPE_SELL && trail_target==TRAIL_TARGET_STOPLOSS) || (type==ORDER_TYPE_BUY && trail_target==TRAIL_TARGET_TAKEPROFIT))
      {
         if(val<ret || ret==0.0)
            ret=val;
      }      
     }
   return ret;
  }

CTrails, как и другие контейнеры, использованные в статьях этой серии, является наследником CArrayObj, который разработан для хранения многочисленных указателей на экземпляры СObject и его наследников. Так, CTrails может хранить более одного экземпляра CTrail.Если CTrails содержит множественные указатели на экземпляры CTrail, при вызове метода Сheck он будет вызывать методы Check всех этих экземпляров. Но только ближайшее к текущему рынку значение цены будет возвращено в качестве окончательного результата. Такое поведение можно изменить, расширив СTrails.

Классы CTrail и CTrails были сведены к чистому расчету. Это означает, что все методы закодированы в базовом классе (CTrailBase), а не в какой-либо его конкретной специфичной для языка имплементации. Когда вызывается проверка статуса трейлинг-стопа (CStop), он получает новое значение для стоп-уровня, а CStop, соответственно, изменяет этот уровень.

Расширение CTrail

Класс CTrail применяется не только к трейлинг-стопу и безубытку. Его можно использовать для отслеживания всей эволюции стоп-уровня сделки с течением времени. Процесс очень похож на способ имплементации пользовательских стопов, описанных в этой статье. Но это изменение происходит путем расширения методов CTrail, чтобы применить изменения к стоп-уровню после того, как сделка вошла в рынок.

Примеры

Пример №1: Пользовательские стопы

Используем стоп-лосс и тейк-профит для сделки, которую собирается открыть советник. Расширим третий пример советника из предыдущей статьи, чтобы создать нового эксперта. Пользовательские стоп-уровни будут рассчитаны на основании максимума и минимума цены предыдущей свечи. В качестве дополнительной защиты добавим условие требуемого минимума: если рассчитанный стоп-уровень приблизится к цене входа менее чем на 200 пунктов, то вместо него будет установлен стоп-уровень на этой 200-пунктовой отметке.  Благодаря этому мы получим уверенность, что наш торговый запрос будет принят брокером.

Сначала объявим наследник класса CStop. В этом экземпляре вызовем наследник CCustomStop. Его определение показано ниже:

class CCustomStop : public CStop
  {
   public:
                     CCustomStop(const string);
                    ~CCustomStop(void);
   virtual double    StopLossCustom(const string,const ENUM_ORDER_TYPE,const double);
   virtual double    TakeProfitCustom(const string,const ENUM_ORDER_TYPE,const double);
  };

Мы должны расширить методы StopLossCustom и TakeProfitCustom. Метод StopLossCustom выглядит так:

double CCustomStop::StopLossCustom(const string symbol,const ENUM_ORDER_TYPE type,const double price)
  {
   double array[1];
   double val=0;
   if(type==ORDER_TYPE_BUY)
     {
      if(CopyHigh(symbol,PERIOD_CURRENT,1,1,array))
        {
         val=array[0];
        }
     }
   else if(type==ORDER_TYPE_SELL)
     {
      if(CopyLow(symbol,PERIOD_CURRENT,1,1,array))
        {
         val=array[0];
        }
     }
   if(val>0)
     {
      double distance=MathAbs(price-val)/m_symbol.Point();
      if(distance<200)
        {
         if(type==ORDER_TYPE_BUY)
            val = price+200*m_symbol.Point();
         else if(type==ORDER_TYPE_SELL)
            val=price-200*m_symbol.Point();
        }
     }
   return val;
  }

Сначала мы используем функции CopyLow и CopyHigh для расчета стоп-лосса в зависимости от типа сделки (покупка это или продажа). Получаем абсолютное значение его расстояния от цены входа в пунктах. Если оно ниже необходимого минимума, то устанавливаем стоп-лосс не на рассчитанный уровень, а на этот необходимый минимум.

Метод TakeProfitCustom повторяет похожий процесс:

double CCustomStop::TakeProfitCustom(const string symbol,const ENUM_ORDER_TYPE type,const double price)
  {
   double array[1];
   double val=0;
   m_symbol=m_symbol_man.Get(symbol);
   if(!CheckPointer(m_symbol))
      return 0;
   if(type==ORDER_TYPE_BUY)
     {
      if(CopyLow(symbol,PERIOD_CURRENT,1,1,array))
        {
         val=array[0];
        }
     }
   else if(type==ORDER_TYPE_SELL)
     {
      if(CopyHigh(symbol,PERIOD_CURRENT,1,1,array))
        {
         val=array[0];
        }
     }
   if(val>0)
     {
      double distance=MathAbs(price-val)/m_symbol.Point();
      if(distance<200)
        {
         if(type==ORDER_TYPE_BUY)
            val = price-200*m_symbol.Point();
         else if(type==ORDER_TYPE_SELL)
            val=price+200*m_symbol.Point();
        }
     }
   return val;
  }

Члены класса CStop, ответственные за присвоенные значения стоп-лосса и тейк-профита в пунктах (m_stoploss и m_takeprofit), по умолчанию инициализированы как 0. Таким образом, нам нужно только закомментировать следующие строки внутри функции OnInit:

//main.StopLoss(stop_loss);
//main.TakeProfit(take_profit);

Экземпляр CStop будет использовать пользовательский расчет только в том случае, если стоп-лоссу и тейк-профиту присвоены значения 0.

Пример № 2: Безубыток

Для функции безубытка мы также модифицируем главный заголовочный файл из третьего примера предыдущей статьи. Но на этот раз не будем расширять объекты классов. Вместо этого просто объявим экземпляры CTrail и CTrails:

CTrails *trails = new CTrails();
CTrail *trail = new CTrail();
trail.Set(breakeven_value,breakeven_start,0);  
trails.Add(trail);
main.Add(trails);

Последняя строка очень важна. Нам надо добавить экземпляр CTrails к уже созданному экземпляру CStop. Таким образом, поведение будет применено именно к этому отдельному экземпляру.

Третий аргумент метода Set класса CTrail — шаг. Его значение по умолчанию 1 (1 пункт). Поскольку нам только нужно использовать функцию безубытка, присвоим ей значение 0.

Код, показанный выше, переменной breakeven_start, которая представляет собой цену активации (в пунктах от входной цены), и breakeven_value, которая представляет собой новое расстояние стоп-лосса (в пунктах) от цены активации. Объявим входные параметры для этих двух переменных:

input int breakeven_value = 200;
input int breakeven_start = 200;

С этой настройкой, как только цена сдвинется минимум на 200 пунктов в направлении сделки, стоп-лосс тоже передвинется на 200 пунктов от цены активации. 200 - 200 = 0, и таким образом новым рассчитанным стоп-лоссом становится цена входа в сделку.

Пример № #3: Трейлинг-стоп

Теперь рассмотрим реализацию трейлинг-стопа. Этот пример очень похож на предыдущий. Напомним, что в предыдущем примере мы вставили в функцию OnInit следующий код:

CTrails *trails = new CTrails();
CTrail *trail = new CTrail();
trail.Set(breakeven_value,breakeven_start,0);  
trails.Add(trail);
main.Add(trails);

При использовании трейлинг-стопа процедура не изменяется:

CTrails *trails = new CTrails();
CTrail *trail = new CTrail();
trail.Set(trail_value,trail_start,trail_step);
trails.Add(trail);   
main.Add(trails);

На этот раз, метод Set класса CTrail использует три параметра. Первые два из них идентичны приведенным в предыдущем примере. Третий параметр — шаг, который представляет собой частоту трейлинга после активации. В предыдущей статье этот параметр метода имеет значение по умолчанию 0, и это означает, что после начальной пктивации больше не будет никаких добавочных перетаскиваний. Затем объявим в советнике входные параметры trail_value, trail_start и trail_step.

Пример №4: Пользовательский трейлинг

В этом примере реализуем функцию пользовательского трейлинга. Расширим советник, приведенный в первом примере в этой статье. Вспомним, что в нем объявлен пользовательский стоп на основании максимума/минимума предыдущей свечи. В этом примере расширим эту функцию: не только установим стоп-лосс и тейк профит со входом. Нам нужно еще, чтобы стоп-лосс сделки передвигался за рыночной ценой с использованием максимума или минимума предыдущей свечи. В качестве меры предосторожности также реализуем минимальный стоп-лосс для каждого шага трейлинга.

Чтобы создать этот советник, расширим сначала CTrail. Назовем этот класс-наследник CCustomTrail. Его определение показано ниже:

class CCustomTrail : public CTrail
  {
public:
                     CCustomTrail(void);
                    ~CCustomTrail(void);
   virtual double    Check(const string,const ENUM_ORDER_TYPE,const double,const double,const ENUM_TRAIL_TARGET);
  };

Теперь расширим метод Check. Отметим, что в этом случае метод не проверяет, к чему именно применяется трейлинг — к стоп-лоссу или к тейк-профиту. По умолчанию для CTrail назначен трейлинг стоп-лосса:

double CCustomTrail::Check(const string symbol,const ENUM_ORDER_TYPE type,const double entry_price,const double price,const ENUM_TRAIL_TARGET mode)
  {
   if(!Active())
      return 0;
   if(!Refresh(symbol))
      return 0;
   double array[1];
   double val=0;
   if(type==ORDER_TYPE_BUY)
     {
      if(CopyLow(symbol,PERIOD_CURRENT,1,1,array))
        {
         val=array[0];
        }
     }
   else if(type==ORDER_TYPE_SELL)
     {
      if(CopyHigh(symbol,PERIOD_CURRENT,1,1,array))
        {
         val=array[0];
        }
     }
   if(val>0)
     {
      double distance=MathAbs(price-val)/m_symbol.Point();
      if(distance<200)
        {
         if(type==ORDER_TYPE_BUY)
            val = m_symbol.Ask()-200*m_symbol.Point();
         else if(type==ORDER_TYPE_SELL)
            val=m_symbol.Bid()+200*m_symbol.Point();
        }
     }
   if((type==ORDER_TYPE_BUY && val<=price+10*m_symbol.Point()) || (type==ORDER_TYPE_SELL && val>=price-10*m_symbol.Point()))
      val = 0;
   return val;
  }
Как видим, расчет почти такой же, как мы уже видели среди методов CCustomStop. Кроме того, в заключительной части кода мы добавляем проверку возвращаемого значения: новый стоп-лосс должен быть на 100 пунктов (10 пипсов) больше предыдущего. Это нужно, чтобы предотвратить "блуждающий" в зависимости от значения недавнего максимума или минимума стоп-лосс. Вместо того, чтобы стоп-лосс колебался вверх-вниз вслед за значением локальных максимумов/минимумов, мы устанавливаем настройку, в соответствии с которой новый уровень стоп-лосса должен быть больше, чем заменяемый (выше для длинных позиций, ниже — для коротких).

Заключение

В статье мы продемонстрировали, как можно установить пользовательские стоп-уровни в кроссплатформенном торговом советнике. Вместо того, чтобы устанавливать стоп-лосс и тейк-профит в пипсах и пунктах, представленные методы используют способ, где эти уровни могут быть выражены в значениях цены на графике. Статья также демонстрирует способ, с помощью которого можно изменять (трейлить) стоп-лосс и тейк-профит в течение жизни сделки.

Программы, использованные в статье

#
Имя
Тип
Описание параметров
1.
breakeven_ha_ma.mqh
Заголовочный файл
Основной заголовочный файл, использованный в первом примере
2.
breakeven_ha_ma.mq4 Советник
Основной исходный файл, использованный для MQL4-версии первого примера
3. breakeven_ha_ma.mq5 Советник Основной исходный файл, использованный для MQL5-версии первого примера
4. trail_ha_ma.mqh Заголовочный файл
Основной заголовочный файл, использованный во втором примере
5. trail_ha_ma.mq4 Советник
Основной исходный файл, использованный для MQL4-версии второго примера
6. trail_ha_ma.mq5 Советник
Основной исходный файл, использованный для MQL5-версии второго примера
7. custom_stop_ha_ma.mqh Заголовочный файл Основной заголовочный файл, использованный в третьем примере
8. custom_stop_ha_ma.mq4 Советник Основной исходный файл, использованный для MQL4-версии третьего примера
9. custom_stop_ha_ma.mq5 Советник Основной исходный файл, использованный для MQL5-версии третьего примера
10. custom_trail_ha_ma.mqh
Заголовочный файл Основной заголовочный файл, использованный в четвертом примере
11. custom_trail_ha_ma.mq4 Советник Основной исходный файл, использованный для MQL4-версии четвертого примера
12.
custom_trail_ha_ma.mq5 Советник Основной исходный файл, использованный для MQL5-версии четвертого примера

Файлы классов, созданных в статье

 # Имя
Тип
 Описание параметров
1.
MQLx\Base\Stop\StopBase.mqh Заголовочный файл CStop (базовый класс)
2.
MQLx\MQL4\Stop\Stop.mqh Заголовочный файл CStop (MQL4 версия)
3.
MQLx\MQL5\Stop\Stop.mqh Заголовочный файл CStop (MQL5 версия)
4. MQLx\Base\Trail\TrailBase.mqh  Заголовочный файл CTrail (base class)
5. MQLx\MQL4\Trail\Trail.mqh  Заголовочный файл CTrail (MQL4 version)
6. MQLx\MQL5\Trail\Trail.mqh  Заголовочный файл CTrail (MQL5 version)
7. MQLx\Base\Trail\TrailsBase.mqh  Заголовочный файл CTrails (base class)
8. MQLx\MQL4\Trail\Trails.mqh  Заголовочный файл CTrails (MQL4 version)
9. MQLx\MQL5\Trail\Trails.mqh  Заголовочный файл CTrails (MQL5 version)

Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/3621

Прикрепленные файлы |
MQL5.zip (741.35 KB)
R-квадрат как оценка качества кривой баланса стратегии R-квадрат как оценка качества кривой баланса стратегии
Статья описывает построение пользовательского критерия оптимизации R-квадрат. По этому критерию можно оценить качество кривой баланса стратегии и выбрать наиболее равномерно растущие и стабильные стратегии. Материал описывает принципы его построения и статистические методы, используемые для оценки свойств и качества этой метрики.
Мини-эмулятор рынка, или Ручной тестер стратегий Мини-эмулятор рынка, или Ручной тестер стратегий
Мини-эмулятор рынка — индикатор, предназначенный для частичной эмуляции работы в терминале. Предположительно, его можно использовать для тестирования "ручных" стратегий анализа и торговли на рынке.
Индикатор NRTR и торговые модули на его основе для Мастера MQL5 Индикатор NRTR и торговые модули на его основе для Мастера MQL5
В статье описан индикатор NRTR и торговая система, созданная с его использованием. Для этих целей создаётся модуль торговых сигналов, с помощью которых создаются стратегии, основанные на комбинациях NRTR и дополнительных индикаторов, подтверждающих тренд.
Тестирование паттернов, возникающих при торговле корзинами валютных пар. Часть II Тестирование паттернов, возникающих при торговле корзинами валютных пар. Часть II
Продолжаем тестирование паттернов и проверку методик, описанных в статьях о торговле корзинами валютных пар. Рассмотрим на практике, можно ли использовать паттерны пересечения графиком объединенного WPR скользящей средней, и если можно, то как именно.