Индикатор ShowChannel

Советник RollBackInTrend

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

 

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

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

    Итак, перед нами стоит две задачи:

    1) Определение тренда.

    2) Определение отката сложившегося тренда.

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

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

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

    На рис. 1 показаны примеры "правильного" и "неправильного" каналов. Для того чтобы не строить каналы вручную, удобно использовать индикатор ShowChannel, ссылка на который приведена в начале статьи.

Рис. 1. - Примеры входа на откате и неправильного канала.

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

    Для решения второй задачи нам понадобится дополнительный фактор, который будет свидетельствовать о конце отката и возобновлении тренда. Им является самый обычный MACD, входящий в стандартную поставку Meta Trader 4. На рис. 1 приведен немного измененный MACD, в котором повышение значения гистограммы маркируется темно-сиреневым цветом, а понижение значения -  пурпурным цветом. Так вот, когда значения MACD находятся над сигнальной линией и растут, а затем появляется одно уменьшающееся значение, то это подтверждение покупки. На рис. 1 такой случай показан и выделен вертикальной красной пунктирной линией и синей стрелкой. Подтверждением продажи является нахождение MACD ниже сигнальной линии с серией понижающихся значений, которые прерываются одним повышающимся значением.

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

  • 0 - нет сигналов. Эксперт ничего не предпринимает.

  • 1 - сигнал открытия длинной позиции. Эксперт закрывает текущую короткую позицию, если таковая имеется, и открывает длинную.

  • 2 - сигнал открытия короткой позиции. Эксперт закрывает текущую длинную позицию, если таковая имеется, и открывает короткую.

 

 
//+-------------------------------------------------------------------------------------+
//| Расчет сигнала по MACD и каналу, образованного фракталами                           |
//+-------------------------------------------------------------------------------------+
void GetSignal()
{
 Signal = 0;                                                         // обнуление сигнала
 
// - 1 - == Берем значения MACD на 1-4 барах ============================================
 double MACD1, MACD2, MACD3, MACD4, SMACD;
 MACD1 = iMACD(Symbol(), 0, FastMACD, SlowMACD, SignalMACD, PRICE_CLOSE, MODE_MAIN, 1);
 MACD2 = iMACD(Symbol(), 0, FastMACD, SlowMACD, SignalMACD, PRICE_CLOSE, MODE_MAIN, 2);
 MACD3 = iMACD(Symbol(), 0, FastMACD, SlowMACD, SignalMACD, PRICE_CLOSE, MODE_MAIN, 3);
 MACD4 = iMACD(Symbol(), 0, FastMACD, SlowMACD, SignalMACD, PRICE_CLOSE, MODE_MAIN, 4);
 SMACD = iMACD(Symbol(), 0, FastMACD, SlowMACD, SignalMACD, PRICE_CLOSE, MODE_SIGNAL, 1);
// - 1 - == Окончание блока =============================================================

// - 2 - == Сигнал открытия BUY =========================================================
 double DirectB = 0, DirectK = 0;
 if (SMACD < MACD1 && MACD1 < MACD2 && MACD2 > MACD3 && MACD3 > MACD4)   // откат на MACD
   if (FindFractals() == 1)                                  // построен восходящий канал
     {                                      
      double ThirdFractal = DownFractal1;   // в качестве одного нижнего фрактала сначала                 
      int ThirdNumber = TD1;                               // берем первый нижний фрактал
      DirectLineCalc(TU1, UpFractal1, TU2, UpFractal2, DirectB, DirectK); // расчет K и B
      double ParallelB = DownFractal1 - DirectK*TD1;    // Расчет B нижней границы канала
      if (DownFractal2 < DirectK*TD2+ParallelB)//Если второй нижний фрактал не помещается
        {                  // в полученный канал, то пересчитываем нижнюю границу по нему
         ThirdFractal = DownFractal2;
         ThirdNumber = TD2;
         ParallelB = DownFractal2 - DirectK*TD2;
        }           
                   // Проверяем, попадают ли цены закрытия всех свечей в полученный канал
      for (int i = MathMax(TD2, TU2); i >= MathMax(TD1, TU1); i--)
        if (Close[i] < DirectK*i+ParallelB || Close[i] > DirectK*i+DirectB)
          {
           DirectK = 0;                      // если не попадают, то канал не сформирован
           break;
          } 
      // ---------------
      if (DirectK != 0 && ND(Bid+StopLevel) < ND((DirectB+ParallelB)/2))
        {   // все цены закрытия попали в канал и цена находится в нижней половине канала
         TP = (DirectB-ParallelB)*Koeff+ParallelB;
         SL = ThirdFractal-2*Tick;
         Signal = 1;                                        // то это сигнал открытия BUY
        } 
     } 
// - 2 - == Окончание блока =============================================================

// - 3 - == Сигнал открытия SELL ========================================================
 if (SMACD > MACD1 && MACD1 > MACD2 && MACD2 < MACD3 && MACD3 < MACD4)   // откат на MACD
   if (FindFractals() == 2)                                  // построен нисходящий канал
     {
      ThirdFractal = UpFractal1;    // в качестве одного верхнего фрактала сначала  берем
      ThirdNumber = TU1;                                        // первый верхний фрактал
      DirectLineCalc(TD1, DownFractal1, TD2, DownFractal2, DirectB, DirectK);    // K и B
      ParallelB = UpFractal1 - DirectK*TU1;//Расчет коэффициента B верхней границы канала
      if (UpFractal2 > DirectK*TU2+ParallelB)// Если второй верхний фрактал не помещается
        {                 // в полученный канал, то пересчитываем верхнюю границу по нему
         ThirdFractal = UpFractal2;
         ThirdNumber = TU2;
         ParallelB = UpFractal2 - DirectK*TU2;
        }           
                   // Проверяем, попадают ли цены закрытия всех свечей в полученный канал
      for (i = MathMax(TD2, TU2); i >= MathMax(TD1, TU1); i--)
        if (Close[i] > DirectK*i+ParallelB || Close[i] < DirectK*i+DirectB)
          {
           DirectK = 0;                      // если не попадают, то канал не сформирован
           break;
          } 
      // ---------------
      if (DirectK != 0 && ND(Bid-StopLevel) > ND((DirectB+ParallelB)/2))
        {  // все цены закрытия попали в канал и цена находится в верхней половине канала
         SL = ThirdFractal + Spread + 2*Tick;
         TP = ParallelB - (ParallelB - DirectB)*Koeff+Spread;
         Signal = 2;                                       // то это сигнал открытия SELL
        }
     }   
// - 3 - == Окончание блока =============================================================
}

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

  • UpFractal1 и TU1 - значение первого верхнего фрактала и номер бара, на котором он сформирован в текущем представлении

  • UpFractal2 и TU2 - значение второго верхнего фрактала и номер бара, на котором он сформирован в текущем представлении

  • DownFractal1 и TD1 - значение первого нижнего фрактала и номер бара, на котором он сформирован в текущем представлении

  • DownFractal1 и TD1 - значение второго нижнего фрактала и номер бара, на котором он сформирован в текущем представлении

    Если вести речь о наличии восходящей конструкции фракталов (блок №2), то после вызова функции FindFractals в качестве третьей точки для построения канала поначалу берется первый нижний фрактал. Затем производится расчет коэффициентов K и B для уравнения прямой (вызов функции DirectLineCalc). Вспомним, что уравнение прямой Y = K*X + B, где в качестве Y выступает цена (это значения двух верхних фракталов), а в качестве X - номера баров. Поэтому для нахождения неизвестных коэффициентов нам достаточно решить систему уравнений, исходя из двух известных координат точек.

    Рассчитав коэффициенты для верхней границы канала (DirectK и DirectB), мы можем рассчитать коэффициенты для нижней границы. Так как нижняя граница параллельна верхней, то коэффициенты K у них совпадают. Остается рассчитать лишь коэффициент B (ParallelB). Это делается, исходя из того же уравнения прямой, где нам известны координаты точки (значение нижнего фрактала) и коэффициент К. При получении значения нижней границы канала, проверяется, не выходит ли второй нижний фрактал за границу. Если выходит, то производится пересчет значений нижней границы канала по второму нижнему фракталу.

    Когда рассчитаны границы канала, можно переходить к проверке нахождения цен закрытия свечей в пределах канала. Это производится в цикле от крайнего левого фрактала (максимальное из номеров баров вторых фракталов TU2 и TD2) до крайнего правого (минимальное из номеров баров TU1 и TD1). Значения цен закрытия сравниваются со значением верхней и нижней границ канала на том же баре. При выходе цены за указанный диапазон, цикл прерывается и обнуляется значение DirectK, которое не позволяет функции дать сигнал на открытие длинной позиции.

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

    Аналогичным образом производятся расчеты при подаче сигнала продажи (блок №3).

    Вернемся к упомянутой выше функции FindFractals. Она не представляет собой нечто сложное:  

 
//+-------------------------------------------------------------------------------------+
//| Находим два последних нижних и верхних фрактала, по которым определяем текущий тренд|
//| (0 - нет, 1 - восходящий, 2 - нисходящий)                                           |
//+-------------------------------------------------------------------------------------+
int FindFractals()
{
// - 1 - ======= Поиск фракталов ========================================================
   UpFractal1 = 0;
   UpFractal2 = 0;
   DownFractal1 = 0;
   DownFractal2 = 0;
   int i = 3;
   while ((UpFractal1 == 0 || UpFractal2 == 0 || DownFractal1 == 0 || DownFractal2 == 0)
           && i < Bars)   // цикл продолжается, пока не будут найдены все четыре фрактала
     {
      double Up = iFractals(Symbol(), 0, MODE_UPPER, i);               // верхний фрактал
      double Dn = iFractals(Symbol(), 0, MODE_LOWER, i);                // нижний фрактал
      if (Up != 0)                           // если на текущем баре есть верхний фрактал
        if (UpFractal1 == 0)                    // и это первый найденный верхний фрактал
          {
           UpFractal1 = Up;                                     // сохраняем его значение
           TU1 = i;                                            // и запоминаем номер бара
          } 
         else
          if (UpFractal2 == 0)           // если это уже второй найденный верхний фрактал
            {
             UpFractal2 = Up;           // то сохраняем его значение во вторую переменную
             TU2 = i;                                    // и снова запоминаем номер бара
            } 
      if (Dn != 0)                            // если на текущем баре есть нижний фрактал
        if (DownFractal1 == 0)                   // и это первый найденный нижний фрактал
          { 
           DownFractal1 = Dn;                                   // сохраняем его значение
           TD1 = i;                                            // и запоминаем номер бара
          } 
         else
          if (DownFractal2 == 0)          // если это уже второй найденный нижний фрактал
            {
             DownFractal2 = Dn;         // то сохраняем его значение во вторую переменную
             TD2 = i;                                    // и снова запоминаем номер бара
            }
      i++;
     }
// - 1 - ======= Окончание блока ========================================================
      
// - 2 - ======= Анализ полученных результатов ==========================================
   if (UpFractal1 == 0 || DownFractal1 == 0 ||       // Если не найден один из фракталов, 
       UpFractal2 == 0 || DownFractal2 == 0)// то это ошибка и функция вернет значение -1
     {
      Print("Ошибка в истории. Не найден один из фракталов!");
      return(-1);
     }
     
   // Определение тренда
   if(UpFractal1 > UpFractal2 && DownFractal1 > DownFractal2)
     return(1);   // Восходящий
     
   if(UpFractal1 < UpFractal2 && DownFractal1 < DownFractal2)
     return(2);   // Нисходящий
// - 2 - ======= Окончание блока ========================================================
     
   return(0); // нет тренда
   // ------------------  
}

    В первом блоке опрашивается каждый бар в поисках верхнего или нижнего фрактала. Когда таковой находится, то его значение сохраняется в переменной UpFractal1 или DownFractal1, в зависимости от типа фрактала. Если первый подобный фрактал найден, то его значение сохраняется в переменной UpFractal2 или DownFractal2. Цикл прерывается, когда обе пары фракталов будут найдены (ни одно из значений UpFractal1, UpFractal2, DownFractal1 и DownFractal2 не равно нулю) или не будет достигнут последний бар в истории (i станет равно Bars).

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

   

    Набор других функций эксперта стандартный, его описывать не будем.

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

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

    Эксперт RollBackInTrend тестируется на промежутке 01.01.2008 - 11.07.2009, таймфрейм H1, значения входных параметров по умолчанию. Результаты на рис. 2 - 5.

Рис. 2. - График кривой баланса, получаемый при тестировании советника на валютной паре EURUSD.

 

 

               Рис. 3. - График кривой баланса, получаемый при тестировании советника на валютной паре USDCHF.

 

               Рис. 4. - График кривой баланса, получаемый при тестировании советника на валютной паре GBPUSD.

 

                Рис. 5. - График кривой баланса, получаемый при тестировании советника на валютной паре USDJPY.

    На трех из четырех исследуемых пар мы получили прибыль. Это обнадеживает. Правда пара EURUSD дала значение очень маленькое, что всегда можно списать на обыкновенное везение. Но вот GBPUSD и USDJPY показали неплохую динамику и поэтому стоит остановиться на них подробнее.

    GBPUSD.   Количество сделок 285. Чистая прибыль 1729.40 долларов против максимальной просадки 992.61, что дает фактор восстановления 1.74. Не очень высокий, но и не такой уж маленький. Средний, одним словом. Средняя прибыльная и средняя убыточная сделки в абсолютном значении примерно равны: 72.52 и -77.79 соответственно. Поэтому накопление прибыли произошло из-за большего количества прибыльных сделок 55.79%. Средняя серия прибыльных сделок составила 2 сделки, а убыточных - всего 1, хотя максимальное их количество равно пяти.

    USDJPY.   Количество сделок 299. Чистая прибыль 1790.90 долларов против максимальной просадки 946.12. Соответственно, фактор восстановления 1.89. Недалеко йена ушла от фунта. Средняя прибыльная и средняя убыточная сделки в абсолютном значении опять же примерно равны: 51.08 и -52.63 соответственно. Среднее количество прибыльных сделок подряд составило 2, а убыточных также 2, хотя максимальное их количество достигает семи.

    Результаты тестирования показывают привлекательную статистику, что дает основания для дальнейшего изучения стратегии на парах GBPUSD и USDJPY, но ни в коем случае не гарантирует повторение их в будущем. Хотя и повышает вероятность успеха с обычных 50% до 55%.

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

Игорь Герасько

Июль 2009