Индикатор AMKA

Индикатор CloseOnTheLimit_Reverse

Советник AMKA_Expert

Советник AMKA_SelfTest

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

 

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

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

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

        Попробуем осуществить сказанное на примере конкретной стратегии, которая основана на двух индикаторах - AMKA и CloseOnTheLimit_Reverse. Примеры совершения сделок по показаниям названных индикаторов можно увидеть на рис. 1.

                                 Рис. 1. - Пример совершения сделок по индикаторам AMKA и CloseOnTheLimit_Reverse.

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

    От торговых систем, описанных в предыдущих статьях, эта стратегия отличается только сигнальной частью. Поэтому приведем только код функции GetSignal:

 
//+-------------------------------------------------------------------------------------+
//| Расчет показаний AMKA и CloseOnTheLimit_Reverse                                     |
//+-------------------------------------------------------------------------------------+
void GetSignal(int Bar)
{
 Signal = 0;
// - 1 - == Расчет по индикатору AMKA ===================================================
 double RedDot = iCustom(Symbol(), 0, "AMKA", AMKAPeriod, FastEMAPeriod, 
                         SlowEMAPeriod, PowLevel, dK, UseStdDev, AMKAPrice, 1, Bar);
 double BlueDot = iCustom(Symbol(), 0, "AMKA", AMKAPeriod, FastEMAPeriod, 
                          SlowEMAPeriod, PowLevel, dK, UseStdDev, AMKAPrice, 2, Bar);
 if (RedDot != 0 && BlueDot == 0) 
   {
    CurRed = RedDot;
    CurBlue = 0.0;
   } 
 if (BlueDot != 0 && RedDot == 0) 
   {
    CurBlue = BlueDot;
    CurRed = 0.0;
   } 
// - 1 - == Окончание блока =============================================================

// - 2 - == Синтез сигнала ==============================================================
  if (CurRed != 0)
    if (ND(Open[Bar]) > ND(Close[Bar]))   // последний бар медвежий
      if (ND(Close[Bar]) < ND(Low[Bar+1])) // и он выступает за границы предыдущего бара
        if (ND(Open[Bar+1]) < ND(Close[Bar+1])) // а второй бар бычий
          Signal = 1;
    
  if (CurBlue != 0)
    if (ND(Open[Bar]) < ND(Close[Bar]))   // последний бар бычий
      if (ND(Close[Bar]) > ND(High[Bar+1])) // и он выступает за границы предыдущего бара
        if (ND(Open[Bar+1]) > ND(Close[Bar+1])) // а второй бар медвежий
          Signal = 2;
// - 2 - == Окончание блока =============================================================
}

   Код индикатора AMKA довольно громоздкий, поэтому переносить его в советник не имеет смысла (особенного прироста производительности не будет). Изящнее и проще получать его значения с использованием функции iCustom.

    Индикатор AMKA состоит из трех индикаторных буферов: нулевой - кривая линия, первый - красные точки, второй - синие точки. Так как нам нужен лишь факт наличия точек на предыдущей свече, то рассчитывать значения для нулевого буфера в эксперте не будем. Наличие же точки определяется ненулевым значением индикаторного буфера. Дополнительные переменные CurRed и CurBlue служат хранилищами значений последней точки. Это нужно для тех случаев, когда  сигнал от индикатора CloseOnTheLimit есть, а сигнала от AMKA именно на этой же свече нет.

    Во втором блоке функции GetSignal производится сложение сигналов от двух индикаторов. Алгоритм CloseOnTheLimit очень простой, поэтому его то и не составило труда реализовать в теле функции.

    В результате, если направление обоих индикаторов совпадает, общий сигнал (переменная Signal) принимает одно из двух направлений.

    Произведем проверку эксперта AMKA_Expert на истории. Настройки советника использовались по умолчанию, таймфрейм H1, диапазон дат 01.01.2008 - 01.08.2009. Несмотря на то, что уровни стопа и профита советник выставляет, они являются слишком большими для достижения их ценой. Поэтому в данном случае можно говорить о том, что закрытие сделки происходит по сигналу открытия противоположной. Результаты приведены на рис. 2-5.

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

 

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

 

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

 

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

        Как видим, даже без какого-либо подбора значений, имеем неплохие результаты. По стабильности (наименьшее количество провалов), как впрочем, и по прибыли, больше всего радует пара USDJPY. Ее чистая прибыль 4267.93 против максимальной просадки 1457.85 долларов, что дает близкий к тройке фактор восстановления (2.93). Не радует лишь общий вид кривой баланса. Уж слишком явно она разделена на две части - флэтовую и трендовую, выражаясь в ценовых категориях.

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

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

    Такой подход можно применить и к исходам сделок, тем более, когда нам доступна статистика их совершения. Если внимательно рассмотреть все полученные отчеты о тестировании эксперта AMKA_Expert, то можно обнаружить интересную особенность - значения среднего непрерывного выигрыша и проигрыша равны. Причем равны во всех тестах и равны одному и тому же значению - 2. Таким образом, можно утверждать, что после получения двух подряд прибыльных сделок, велика вероятность последовательного получения двух убыточных сделок. Понятно, что среднее значение нельзя рассматривать как аксиому, ведь на то оно и среднее, чтобы отлично вписываться в частые серии 1 и 3 сделок подряд с одинаковым исходом.

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

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

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

 
// - 3 - == Обновление исторических данных ==============================================
   CurRed = 0;
   CurBlue = 0;
   Comment("Подождите, производится сбор данных о работе эксперта на доступной истории...");
   for (int i = Bars-MathMax(AMKAPeriod, SlowEMAPeriod); i > 0; i--) 
     {
      GetSignal(i);
      DoTransaction(i-1);       
     }
   Comment(""); 
// - 3 - == Окончание блока =============================================================

   В свою очередь, непосредственно имитацией сделок занимается функция DoTransaction:

 
//+-------------------------------------------------------------------------------------+
//| Совершение виртуальных сделок                                                       |
//+-------------------------------------------------------------------------------------+
void DoTransaction(int Bar)
{
// - 1 - == Проверка срабатывания стопа или профита на предыдущей свече =================
    if (Trade != -1)                                      // если имеется открытая сделка
      if (Trade == 0)
        {  // Длинная позиция
         if (ND(Low[Bar+1]) <= SLPrice)                             // Срабатывание стопа
           {
            Trade = -1;                                   // закрываем виртуальную сделку
            LossCount();
           }
          else 
           if (ND(High[Bar+1]) >= TPPrice)                        // срабатывание профита
             {
              Trade = -1;                                 // закрываем виртуальную сделку
              ProfitCount();
             }
        }
       else
        { // Короткая позиция
         if (ND(High[Bar+1]+Spread) >= SLPrice)                     // Срабатывание стопа
           {
            Trade = -1;                                   // закрываем виртуальную сделку
            LossCount();
           }
          else 
           if (ND(Low[Bar+1]+Spread) <= TPPrice)                  // срабатывание профита
             {
              Trade = -1;                                 // закрываем виртуальную сделку
              ProfitCount();
             }
        }    
// - 1 -  == Окончание блока ============================================================

// - 2 - == Имитация открытия длинной и закрытия короткой сделки ========================
    if (Signal == 1 && Trade != 0)
     {
      double Price = ND(Open[Bar]+Spread);
      if (Trade == 1)                                 // закрываем короткую, если имеется
        {
         if (OpenPrice-Price >= 0)
           ProfitCount();
          else
           LossCount();
        }
      Trade = 0;                                              // Открытие длинной позиции
      OpenPrice = Price;                                          // Цена открытия сделки
      TPPrice = ND(Price + TakeProfit*Tick);                             // Профит сделки
      SLPrice = ND(Price - StopLoss*Tick);                                 // Стоп сделки
     }
// - 2 -  == Окончание блока ============================================================
     
// - 3 - == Имитация открытия короткой и закрытия длинной сделки ========================
    if (Signal == 2 && Trade != 1)
     {
      Price = ND(Open[Bar]);
      if (Trade == 0)                                  // закрываем длинную, если имеется
        {
         if (Price-OpenPrice-Spread >= 0)
           ProfitCount();
          else
           LossCount();
        }
      Trade = 1;                                             // Открытие короткой позиции
      OpenPrice = Price;                                          // Цена открытия сделки
      TPPrice = ND(Price - TakeProfit*Tick);                             // Профит сделки
      SLPrice = ND(Price + StopLoss*Tick);                                 // Стоп сделки
     }
// - 3 -  == Окончание блока ============================================================
}

Тип сделки записывается в переменную Trade. Длинной позиции соответствует значение 0, а короткой - 1. Если текущей сделки нет, то в Trade содержится значение -1. Поэтому выполнение первого блока функции возможно только при наличии открытой виртуальной сделки. Для позиции BUY сначала проверяется падение цены ниже уровня стопа, значение которого хранится в переменной SLPrice, а затем - рост цены выше уровня профита, значение которого хранится в TPPrice. При достижении стопа производится подсчет убыточной сделки (вызов функции LossCount), при достижении профита - подсчет прибыльной сделки (вызов ProfitCount). И в том, и в другом случае происходит закрытие сделки - присвоение переменной Trade значения -1. По точно такому же принципу происходит проверка достижения стопа и профита для позиций SELL с тем только различием, что к текущей цене прибавляется спрэд.

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

Вспомогательные функции LossCount и ProfitCount служат для подсчета количества серий, соответственно убыточных и прибыльных сделок, общего числа каждого типа сделки и количества сделок в текущей серии:

 
//+-------------------------------------------------------------------------------------+
//| Запись прибыльной сделки                                                            |
//+-------------------------------------------------------------------------------------+
void ProfitCount()
{
 ProfitSeries++;                                 // Увеличиваем количество сделок в серии
 EqualProfits++;                        // Увеличиваем общее количество прибыльных сделок
 if (LossSeries > 0)                                // Если до этого была убыточная серия
   {
    CountLosses++;                                // то увеличиваем количество серий на 1
    LossSeries = 0;       // и начинаем заново считать сделки в следующей убыточной серии
   } 
}

//+-------------------------------------------------------------------------------------+
//| Запись убыточной сделки                                                             |
//+-------------------------------------------------------------------------------------+
void LossCount()
{
 LossSeries++;                                   // Увеличиваем количество сделок в серии
 EqualLosses++;                         // Увеличиваем общее количество прибыльных сделок
 if (ProfitSeries > 0)                             // Если до этого была прибыльная серия
   {
    CountProfits++;                               // то увеличиваем количество серий на 1
    ProfitSeries = 0;    // и начинаем заново считать сделки в следующей прибыльной серии
   } 
}

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

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

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

Посмотрим, как повлияет на результаты тестирования дополнительная фильтрация. Для этого тестируем советник AMKA_SelfTest на той же истории, что и AMKA_Expert, и с идентичными параметрами (см. рис. 6-9).

           Рис. 6. - График изменения кривой баланса при тестировании советника AMKA_SelfTest на валютной паре EURUSD.

           Рис. 7. - График изменения кривой баланса при тестировании советника AMKA_SelfTest на валютной паре USDCHF.

           Рис. 8. - График изменения кривой баланса при тестировании советника AMKA_SelfTest на валютной паре GBPUSD.

           Рис. 9. - График изменения кривой баланса при тестировании советника AMKA_SelfTest на валютной паре USDJPY.

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

        Чистая прибыль 2901.14 против максимальной просадки 1201.86 (ФВ = 2.41) и соотношение средних прибыльных/убыточных серий сделок 2/2... На первый взгляд мы не добились улучшения соотношения средних серий сделок. Благодаря фильтрации по статистике мы просто сместили открытие и закрытие сделок во времени, получив тем самым другие цены их открытия и закрытия. Но ведь именно такую цель мы преследовали изначально - подстроиться под новый пульс рынка, не меняя систему по сути.

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

        В чем же был просчет выбранного типа фильтрации? Можно ли вообще улучшить результаты торговой системы, опираясь на ее статистические показатели? На эти вопросы попытаемся найти ответы во второй части статьи.

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

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

Август 2009