Советник Bollinger_AndyBo_Expert

Советник Bollinger_AndyBo_Expert_Correct

Развернутые отчеты тестирования эксперта

 

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

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

  1. Покупка. Закрытие свечи Н1 выше верхней полосы Боллинджера и при этом цена закрытия предыдущего дня выше простой средней скользящей, взятой с дневного таймфрейма.
  2. Продажа. Закрытие свечи Н1 ниже нижней полосы Боллинджера, а закрытие предыдущего дня ниже простой средней скользящей, взятой с дневного таймфрейма.
  3. Продажа. Возврат цены в канал, образованный полосами Боллинджера, сверху вниз и закрытие свечи ниже верхней полосы. Тело следующей свечи должно полностью находиться выше нижней полосы и ниже верхней. Только после фиксации такой свечи совершается сделка.
  4. Покупка. Возврат цены в канал снизу вверх и закрытие свечи выше нижней полосы Боллинджера. Тело следующей свечи должно полностью находиться выше нижней полосы и ниже верхней. Только после фиксации такой свечи совершается сделка.

        Эти четыре сигнала проиллюстрированы на рис. 1. Синими стрелками отмечены сигналы покупки 1 и 4, а красными - сигналы продажи 2 и 3.

Рис. 1. - Принципы открытия сделок по индикатору Bollinger Bands.

        А вот синими и красными крестиками отмечены результаты применения тактики управления открытыми позициями. Заключается такая тактика в закрытии половины сделки при достижении некоторого количества пунктов прибыли. Автор стратегии предлагает установить это количество равным 30 пунктам. После успешного закрытия половины объема сделки, уровень стоп-приказа переносится ближе к текущей цене. Предлагается передвижение на уровень 20 пунктов убытка против изначального уровня 50 пунктов. В дальнейшем советник ожидает срабатывания стопа или профита сделки, не реагируя на другие сигналы открытия сделок.

        В результате функция генерации сигнала для открытия сделок GetSignal примет такой вид:

 
//+-------------------------------------------------------------------------------------+
//| Расчет значений Bollinger с формированием сигналов для открытия позиций             |
//+-------------------------------------------------------------------------------------+
void GetSignal()
{
 Signal = 0;
// - 1 - == Получение значений индикаторов ==============================================
 double BandsUp1 = iBands(Symbol(), 0, BandsPeriod, BandsDeviation, BandsShift, 
                         BandsPrice, MODE_UPPER, 1);
 double BandsDn1 = iBands(Symbol(), 0, BandsPeriod, BandsDeviation, BandsShift, 
                         BandsPrice, MODE_LOWER, 1);
 double BandsUp2 = iBands(Symbol(), 0, BandsPeriod, BandsDeviation, BandsShift, 
                         BandsPrice, MODE_UPPER, 2);
 double BandsDn2 = iBands(Symbol(), 0, BandsPeriod, BandsDeviation, BandsShift, 
                         BandsPrice, MODE_LOWER, 2);
 double DaySMA = iMA(Symbol(), MATF, MAPeriod, MAShift, MAMethod, MAPrice, 1);
 double DayClose = iClose(Symbol(), MATF, 1); 
// - 1 - == Окончание блока =============================================================

// - 2 - == Генерация сигнала ===========================================================
 if (Close[1] > BandsUp1 && Close[2] < BandsUp2 && DayClose>DaySMA)// Цена закрытия свечи
   {                    // выше Боллинджера, а цена закрытия предыдущего дня выше средней
    Signal = 1;                                        // Открытие BUY по первому сигналу
   }   
 
 if (Close[1] < BandsDn1 && Close[2] > BandsDn2 && DayClose<DaySMA)// Цена закрытия свечи
   {                    // ниже Боллинджера, а цена закрытия предыдущего дня ниже средней
    Signal = 2;                                       // Открытие SELL по второму сигналу
   }   
 if (Close[1] < BandsUp1 && High[1] < BandsUp1 && Close[2]>BandsUp2)//Цена закрытия свечи
   {            // ниже верхней полосы Боллинджера, а цена закрытия предыдущей свечи выше
    Signal = 3;                                      // Открытие SELL по третьему сигналу
   }   
 if (Close[1] > BandsDn1 && Low[1] > BandsDn1 && Close[2]<BandsDn2)// Цена закрытия свечи
   {             // выше нижней полосы Боллинджера, а цена закрытия предыдущей свечи ниже
    Signal = 4;                                     // Открытие BUY по четвертому сигналу
   }   
// - 2 - == Окончание блока =============================================================
}

        В блоке 1 производится расчет всех необходимых значений индикаторов - значений верхней и нижней полос Bollinger Bands на последних двух барах и значения дневной средней скользящей предыдущего дня. Также вычисляется цена закрытия предыдущего дня. Переменные BandsPeriod, BandsDeviation, BandsShift, BandsPrice, MAPeriod, MAShift, MAMethod и MAPrice являются внешними и могут быть изменены пользователем без внесения изменений в код советника. Переменная MATF тоже является внешней и указывает период таймфрейма, с которого берется значение средней скользящей.

        Во втором блоке словесное описание всех четырех типов сигналов просто преобразовано в программный код. В результате переменная Signal будет принимать пять значений - от 0 до 4.

        Функцией GetSignal мы реализовали только сигнальную часть стратегии. Нам же еще потребуется реализация управления частичным закрытием позиции. Для этого отведем отдельную функцию IsOwnOrder:

 
//+-------------------------------------------------------------------------------------+
//| Если присутствует открытая позиция, то результат True.                              |
//|   При этом проверяется достижение позицией HalfClosePips. Если значение достигнуто, |
//|   то закрывается половина позиции и стоп переносится на уровень HalfStop            |
//| В остальных случаях результат False                                                 |
//+-------------------------------------------------------------------------------------+
bool IsOwnOrder()
{
// - 1 - =============== Нахождение своей сделки ========================================
 for (int i = 0; i < OrdersTotal(); i--)
   if (OrderSelect(i, SELECT_BY_POS))
     if (OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber 
         && OrderType() < 2)                                      
// - 1 - =============== Окончание блока ================================================
       {
       
// - 2 - =============== Перенос стопа после частичного закрытия сделки =================
        // Если до этого было успешное частичное закрытие, то необходимо перенести стоп
        if (IsClose)
          {
           // Вычисляем уровень стопа
           if (OrderType() == OP_BUY)                                   // для сделки BUY
             {
              double SL = ND(OrderOpenPrice() - HalfStop*Tick);
              SL = ND(IF(Bid - SL - StopLevel > 0, SL, Bid - StopLevel));
             }
            else                                                       // для сделки SELL
             {
              SL = ND(OrderOpenPrice() + HalfStop*Tick);
              SL = ND(IF(SL - Ask - StopLevel > 0, SL, Ask + StopLevel));
             }
           // Производим модификацию
           if (WaitForTradeContext())
             if (OrderModify(OrderTicket(), 0, SL, OrderTakeProfit(), 0))
               IsClose = False;                        // Сброс флага частичного закрытия
           return(True);         
          }
// - 2 - =============== Окончание блока ================================================

// - 3 - ========================== Частичное закрытие сделки ===========================
        // Проверяем стоп сделки, значение которого является сигналом частичного закрытия
        if (ND(MathAbs(OrderStopLoss() - OrderOpenPrice())) == ND(StopLoss*Tick))
          // Если стоп равен первоначальному, то проверяем текущую прибыль
          if ((OrderType()==OP_BUY && Bid - OrderOpenPrice() - HalfClosePips*Tick>=0) ||
              (OrderType()==OP_SELL && OrderOpenPrice() - Ask - HalfClosePips*Tick >= 0))
            {// Нахождение объема частичного закрытия
             double CloseLots = MathMax(MathRound(OrderLots()/2/LotStep)*LotStep,MinLot);
             // Определение цены закрытия
             if (OrderType() == OP_BUY)
               double Price = Bid;
              else
               Price = Ask;
             // Частичное закрытие сделки  
             if (WaitForTradeContext())
               if (OrderClose(OrderTicket(), CloseLots, ND(Price), 3))
                 IsClose = True;   // Отмечаем, что произошло успешное частичное закрытие
            }  
        return(True);  
// - 3 - =============== Окончание блока ================================================
       }
        
 return(False);                                               
}

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

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

         Третий блок занимается как раз проверкой частичного закрытия сделки. Признаком частичного закрытия сделки в данном случае решено сделать уровень стоп-приказа, хотя это можно было определить по комментарию или объему сделки. Если уровень стоп-приказа равен первоначально установленному, значит, частичного закрытия еще не было и происходит проверка достигнутой прибыли. Уровень необходимой прибыли для частичного закрытия сделки тоже был вынесен во внешние параметры эксперта. Имя этого параметра - HalfClosePips. 

         Если необходимый уровень прибыли достигнут, то советник приступает к вычислению объема частичного закрытия сделки CloseLots. Особенностью выражения, в котором вычисляется значение CloseLots, является возврат корректного значения объема. Даже в случае, если текущая сделка открыта минимальным лотом, то деление объема на два не приведет к получению нулевого объема. Результатом все равно будет минимально допустимый объем, что даст полное закрытие существующей сделки.

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

            Связывание описанных функций производится в функции start:

 
//+-------------------------------------------------------------------------------------+
//| Функция START эксперта                                                              |
//+-------------------------------------------------------------------------------------+
int start()
  {
// - 1 -  == Разрешено ли советнику работать? ===========================================
   if (!Activate || FatalError)             // Отключается работа советника, если функция
    return(0);           //  init завершилась с ошибкой  или имела место фатальная ошибка
// - 1 -  == Окончание блока ============================================================
     
// - 2 - == Сбор информации об условиях торговли ========================================
   Spread = ND(MarketInfo(Symbol(), MODE_SPREAD)*Point);                  // текщий спрэд
   StopLevel = ND(MarketInfo(Symbol(), MODE_STOPLEVEL)*Point);  // текущий уровень стопов 
// - 2 -  == Окончание блока ============================================================

// - 3 - ======== Обработка существующей позиции и контроль открытия нового бара ========
   if (IsOwnOrder())
     {
      LastBar = Time[0];
      return(0);  
     } 
   
   if (LastBar == Time[0])
     return(0);
// - 3 -  == Окончание блока ============================================================

// - 4 - ======================== Расчет сигнала ========================================
   GetSignal();
// - 4 -  == Окончание блока ============================================================

// - 5 - == Открытие позиций ============================================================
   if (Signal == 1 || Signal == 4)                                        // Открытие BUY
     {
      RefreshRates();
      double SL = ND(IF(StopLoss == 0, 0, Ask - StopLoss*Tick));               
      double TP = ND(IF(TakeProfit == 0, 0, Ask + TakeProfit*Tick));
      if (OpenOrderCorrect(OP_BUY, ND(Ask), SL, TP, True) != 0)       // открытие позиции
        return(0);    // если не удалось открыть, то попытка переносится на следующий тик
     }

   if (Signal == 2 || Signal == 3)                                       // Открытие SELL
     {
      RefreshRates();
      SL = ND(IF(StopLoss == 0, 0, Bid + StopLoss*Tick));               
      TP = ND(IF(TakeProfit == 0, 0, Bid - TakeProfit*Tick));
      if (OpenOrderCorrect(OP_SELL, ND(Bid), SL, TP, True) != 0)      // открытие позиции
        return(0);    // если не удалось открыть, то попытка переносится на следующий тик
     }
// - 5 -  == Окончание блока ============================================================

   LastBar = Time[0];

   return(0);
  }

             Первый и второй блоки - стандартные и назначение их прозрачно.

             Третий блок досрочно завершает выполнение функции start в двух случаях - при наличии открытой экспертом позиции и если текущий бар уже обрабатывался. 

             Четвертый блок состоит из вызова функции генерации сигнала.

            А вот пятый блок отвечает за открытие позиций. Если выполнение программы дошло до этого места, значит, открытых позиций нет и можно открывать новые. Открытие позиций возможно как с установленными пользователем уровнями стопа и профита, так и без них. В этом случае значение внешних параметров TakeProfit или StopLoss должны содержать 0. 

            Основные функции описаны и можно приступать к тестированию полученного эксперта Bollinger_AndyBo_Expert. Все параметры, которые предложил автор стратегии, установлены по умолчанию. Таймфрейм для всех валютных пар Н1, а исторический период представлен только 2009-м годом 01.01.2009 - 13.11.2009 (см. рис. 2-5):             

 

Рис. 2. - Результаты тестирования эксперта Bollinger_AndyBo_Expert на валютной паре EURUSD.

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

 

Рис. 3. - Результаты тестирования эксперта Bollinger_AndyBo_Expert на валютной паре USDCHF.

               Как это часто бывает, валютная пара USDCHF вторит своей младшей (по возрасту) сестре - результаты практически одинаковые.

 

Рис. 4 - Результаты тестирования эксперта Bollinger_AndyBo_Expert на валютной паре GBPUSD.

            Чуть лучше с этой стратегией чувствует себя фунт, но итоговый результат снова отрицательный.

 

Рис. 5. - Результаты тестирования эксперта Bollinger_AndyBo_Expert на валютной паре USDJPY.

            И только независимая йена показывает итоговую прибыль. Но вид кривой баланса не внушает доверия, так как рост наблюдался только во второй половине тестирования. Чистая прибыль показана довольно скромная - 1161.89 долларов, хотя и максимальная просадка небольшая - 963.69. Учитывая же факт начавшегося спада, который отчетливо виден, начиная с 210-ой сделки, трудно отделаться от мысли, что в ближайшем будущем с такой стратегией на йене не озолотишься.

           Что же стало причиной столь плачевных результатов? По моему мнению, причина как раз в очень маленьком абсолютном значении уровней стопа и профита. Ведь не секрет, что стоп в 50 пунктов - это минимально рекомендуемый показатель. В то время как оптимальными значениями являются величины хотя бы в полтора раза больше. Второй причиной видится отсутствие фильтрации сигналов с номерами 3 и 4, что как раз и приносит большую часть убытков. Изобретать велосипед не будем и поставим точно такие же фильтры, которые использовались у сигналов 1 и 2 - закрытие дня выше 20-типериодной простой средней.

            В результате получим такой вид функции GetSignal:

 
//+-------------------------------------------------------------------------------------+
//| Расчет значений Bollinger с формированием сигналов для открытия позиций             |
//+-------------------------------------------------------------------------------------+
void GetSignal()
{
 Signal = 0;
// - 1 - == Получение значений индикаторов ==============================================
 double BandsUp1 = iBands(Symbol(), 0, BandsPeriod, BandsDeviation, BandsShift, 
                         BandsPrice, MODE_UPPER, 1);
 double BandsDn1 = iBands(Symbol(), 0, BandsPeriod, BandsDeviation, BandsShift, 
                         BandsPrice, MODE_LOWER, 1);
 double BandsUp2 = iBands(Symbol(), 0, BandsPeriod, BandsDeviation, BandsShift, 
                         BandsPrice, MODE_UPPER, 2);
 double BandsDn2 = iBands(Symbol(), 0, BandsPeriod, BandsDeviation, BandsShift, 
                         BandsPrice, MODE_LOWER, 2);
 double DaySMA = iMA(Symbol(), MATF, MAPeriod, MAShift, MAMethod, MAPrice, 1);
 double DayClose = iClose(Symbol(), MATF, 1); 
// - 1 - == Окончание блока =============================================================

// - 2 - == Генерация сигнала ===========================================================
 if (Close[1] > BandsUp1 && Close[2] < BandsUp2 && DayClose>DaySMA)// Цена закрытия свечи
   {                    // выше Боллинджера, а цена закрытия предыдущего дня выше средней
    Signal = 1;                                        // Открытие BUY по первому сигналу
   }   
 
 if (Close[1] < BandsDn1 && Close[2] > BandsDn2 && DayClose<DaySMA)// Цена закрытия свечи
   {                    // ниже Боллинджера, а цена закрытия предыдущего дня ниже средней
    Signal = 2;                                       // Открытие SELL по второму сигналу
   }   
 if (Close[1] < BandsUp1 && High[1] < BandsUp1 && Close[2]>BandsUp2 && DayClose<DaySMA)
   {   //Закрытия свечи ниже верхней полосы Боллинджера, а закрытие предыдущей свечи выше
    Signal = 3;                                      // Открытие SELL по третьему сигналу
   }   
 if (Close[1] > BandsDn1 && Low[1] > BandsDn1 && Close[2]<BandsDn2 && DayClose>DaySMA)
   {   // Закрытие свечи выше нижней полосы Боллинджера, а закрытие предыдущей свечи ниже
    Signal = 4;                                     // Открытие BUY по четвертому сигналу
   }   
// - 2 - == Окончание блока =============================================================
}

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

        Вторым изменением является корректировка умолчательных значений профита, стопа и параметров частичного закрытия. Так, профит увеличен в два раза до 160 пунктов. Стоп подвергся увеличению всего лишь в 1.6 раза - до 80 пунктов. Уровень прибыли, при котором производится частичное закрытие позиции (HalfClosePips), увеличен более чем в два раза - 80 пунктов. А новый уровень стопа после закрытия половины позиции (HalfStop) стал нулевым. Все остальное в советнике Bollinger_AbdyBo_Expert_Correct по сравнению с Bollinger_AndyBo_Expert осталось на своих местах.

        Попробуем протестировать новую версию эксперта, да еще и с новыми параметрами (см. рис. 6-9):

 

Рис. 6. - Результаты тестирования эксперта Bollinger_AndyBo_Expert_Correct на валютной паре EURUSD.

        По сравнению с предыдущими результатами, новые, безусловно, лучше. Но все равно не настолько хорошие, чтобы дать итоговую прибыль.

 

Рис. 7. - Результаты тестирования эксперта Bollinger_AndyBo_Expert_Correct на валютной паре USDCHF.

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

 

Рис. 8. - Результаты тестирования эксперта Bollinger_AndyBo_Expert_Correct на валютной паре GBPUSD.

        А вот результаты фунта преподнесли неплохой сюрприз. Чистая прибыль 3295.85 долларов (более 30% менее чем за 1 год!) при максимальной просадке 889.20 долларов. Такой размер просадки совершенно нехарактерен для фунта и причина здесь, видимо, в фиксированных уровнях стопа, что не дает развиваться более серьезным убыткам. Еще одна характеристика, достойная того, чтобы ее отметили, соотношение серий среднего непрерывного выигрыша и проигрыша - 5/2. 

 

Рис. 9. - Результаты тестирования эксперта Bollinger_AndyBo_Expert_Correct на валютной паре USDJPY.

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

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

Использование полученного советника рекомендуется только в полуавтоматическом режиме под присмотром трейдера и после всестороннего изучения слабых и сильных сторон стратегии.