Индикатор FiboPivot

Советник FiboPivot_Expert

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

 

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

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

    Классическим способом ответа на два вечных "КОГДА?" является торговля по пробитию уровня поддержки или сопротивления. Здесь встает уже другая проблема - как  определить эти уровни? В литературе по трейдингу предлагается множество вариантов, но очень мало из них отличаются четкостью, которую можно заложить в программу и получить нужные значения автоматически. В основном встречаются формулировки вида: "найдите область наиболее плотной штриховки, экстремум которой и будет уровнем поддержки или сопротивления". Такое определение страдает субъективизмом на обе ноги. Насколько плотной должна быть штриховка? Это десять свечей, двадцать или хватит пяти?

    Одним из любимых инструментов искателей поддержек и сопротивлений является "сетка Фибоначчи". Более любим он разве что у сторонников теории волн Элиота. Основными уровнями сетки считаются 38.2%, 61.8% и 100%. Рассчитываются уровни от любого выбранного трейдером движения. Тут мы снова свернули на тропу неопределенности. Что это за движение, как его идентифицировать?

    Зная склонность многих трейдеров к внутридневной торговле, можно дать четкое определение искомому движению - это предыдущий день! От него нам потребуется три характеристики - максимум, минимум и цена закрытия. Из них можно получить среднее значение, которое получило распространенное название - Pivot (точка вращения). В результате у нас появляется уровень отсчета, от которого можно производить любые построения. В данном случае можно спроецировать возможные движения цены от полученной точки в оба направления (вверх и вниз), используя разницу максимума и минимума дня в качестве 100% движения. Имея уже две точки отсчета (Pivot и 100% прошлого движения) можно рассчитывать любые другие величины, выраженные в процентах.

      На рис. 1 показан индикатор FiboPivot, построенный по значениям предыдущего дня. Значениями, соответствующими 100% прошлого дня, отложенными от Pivot, являются Resistance2 и Support2. Остальные уровни вычислены уже исходя из них. Так, Support1 и Resistance1 - это 61.8%, а Support3 и Resistance3 - 132.8%.

Рис. 1. - Индикатор FiboPivot.

    Как интерпретировать показания индикатора? Для волатильного рынка, которым является рынок Форекс последний год (июль 2008 - май 2009), лучше работать на пробой. Сигнальными уровнями будут Support1 и Resistance1. Их пробитие означает открытие сделок соответственно SELL и BUY. В случае открытия короткой сделки (на рис. 1 отмечены красными стрелками), уровнем стопа будет выступать Pivot, а ближайшей целью - Support2. Точно также, в случае открытия длинной сделки (на рис. 1 отмечены синими стрелками), уровнем стопа будет Pivot, а ближайшей целью - Resistance2.

    Почему "ближайшей целью", а не просто "целью"? Дело в том, что во многих случаях движение достигает и третьего уровня (Support3 и Resistance3). Глупо не воспользоваться шансом и получить более солидный куш, тем более, если сделка уже открыта. Поэтому на втором уровне (Support2 или Resistance2) предлагается закрывать не весь объем сделки, а только ее часть, например половину. А оставшейся половине ставить целью третий уровень и переносить уровень стопа на цену открытия сделки (безубыток).

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

 
//+-------------------------------------------------------------------------------------+
//| Расчет уровней поддержки/сопротивления                                              |
//+-------------------------------------------------------------------------------------+
void GetSignal()
{
// - 1 - == Расчет уровней сопротивления и поддержки для текущего дня ===================
 double HighDay = iHigh(Symbol(), PERIOD_D1, 1);              // Максимум предыдущего дня
 double LowDay = iLow(Symbol(), PERIOD_D1, 1);                 // Минимум предыдущего дня
 double CloseDay = iClose(Symbol(), PERIOD_D1, 1);            // Закрытие предыдущего дня
 double Width = HighDay - LowDay;                         // Волатильность вчерашнего дня
 Pivot = (HighDay+LowDay+CloseDay)/3;             //Стандартный Pivot - типичная цена дня
 Res1 = Pivot + Width*(Resistance1/100.0);                        // Первое сопротивление
 Res2 = Pivot + Width*(Resistance2/100.0);                        // Второе сопротивление
 Res3 = Pivot + Width*(Resistance3/100.0);                        // Третье сопротивление
 Supp1 = Pivot - Width*(Support1/100.0);                              // Первая поддержка
 Supp2 = Pivot - Width*(Support2/100.0);                              // Вторая поддержка
 Supp3 = Pivot - Width*(Support3/100.0);                              // Третья поддержка
// - 1 - == Окончание блока =============================================================
}

    Учитывая то обстоятельство, что советник может быть запущен пользователем абсолютно на любом таймфрейме, при расчете значений переменных HighDay, LowDay и CloseDay используем явное указание необходимого периода графика - PERIOD_D1. Переменная Width - это те 100% дня, о которых велась речь выше. Именно это значение используется для вычисления каждого из уровней поддержки/сопротивления.

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

    В коде это выглядит так:

 
//+-------------------------------------------------------------------------------------+
//| Функция START эксперта                                                              |
//+-------------------------------------------------------------------------------------+
int start()
  {
// - 1 -  == Разрешено ли советнику работать? ===========================================
   if (!Activate || FatalError)             // Отключается работа советника, если функция
    return(0);           //  init завершилась с ошибкой  или имела место фатальная ошибка
// - 1 -  == Окончание блока ============================================================

// - 2 - == Частичное закрытие позиции и перенос стопа в безубыток ======================
   PartiallyClose();
// - 2 -  == Окончание блока ============================================================

// - 3 -  == Контроль открытия нового дня ===============================================
   datetime NowDay = iTime(Symbol(), PERIOD_D1, 0);
   if (LastBuy == NowDay && LastSell == NowDay)  //Если открывались сегодня, то больше не
     return(0);                                                               // работаем
// - 3 -  == Окончание блока ============================================================

// - 4 - == Сбор информации об условиях торговли ========================================
   Spread = ND(MarketInfo(Symbol(), MODE_SPREAD)*Point);                  // текщий спрэд
   StopLevel = ND(MarketInfo(Symbol(), MODE_STOPLEVEL)*Point);  // текущий уровень стопов
// - 4 -  == Окончание блока ============================================================
     
// - 5 - == Расчет текущего сигнала =====================================================
   GetSignal();
// - 5 -  == Окончание блока ============================================================
   
// - 6 - == Установка ордера BuyStop ====================================================
   double Price = ND(Res1 + Spread);
   bool Res = CloseAll();                     // Закрыть все позиции и удалить все ордера
   RefreshRates();
   if (Res && Ask < Price && LastBuy < NowDay)     // если нет открытых позиций и цена не
     {                                                   //  пробила первое сопротивление
      double TP = ND(IF(Res3 - Price > StopLevel, Res3, Price + StopLevel));
      double SL = ND(IF(Price - Pivot - Tick > StopLevel+Spread, Pivot - Tick,
                        Price - StopLevel - Spread));
      if (ND(Price - Ask) <= StopLevel)        // Если невозможно отложить ордер, то ждем
        return(0);                                                     // следующего тика
      if (!OpenOrder(OP_BUYSTOP, Price, SL, TP))      // если не удалось установить ордер
        return(0);                                     // то попробуем со следующим тиком
      LastBuy = NowDay;  
     }
   if (!Res) return(0);           //закрыть все позиции не вышло, ждем до следующего тика
// - 6 -  == Окончание блока ============================================================
     
// - 7 - == Установка ордера SellStop ===================================================
   Price = ND(Supp1);
   if (Bid > Price && LastSell < NowDay)   // если нет открытых позиций и цена не пробила
     {                                                                // первую поддержку
      TP = ND(IF(Price - Supp3 + Spread > StopLevel, Supp3 + Spread, 
                 Price - StopLevel));
      SL = ND(IF(Pivot + Tick - Price > StopLevel, Pivot + Spread + Tick,
                        Price + StopLevel + Spread));
      if (ND(Bid - Price) <= StopLevel)        // Если невозможно отложить ордер, то ждем
         return(0);                                                    // следующего тика
      if (!OpenOrder(OP_SELLSTOP, Price, SL, TP))     // если не удалось установить ордер
        return(0);                                     // то попробуем со следующим тиком
      LastSell = NowDay;
     }
// - 7 -  == Окончание блока ============================================================
   return(0);
  }

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

    Третий блок решает проблему оптимальной работы кода программы. Он прекращает работу советника на текущем тике, если в течение дня уже были открыты оба отложенных ордера - Sell Stop и Buy Stop. Для этого используются две переменные - LastSell и LastBuy. Каждая из них примет значение времени открытия текущего дня, когда будет установлен соответствующий ордер.

    Если один из ордеров до сих пор не был установлен, то выполняются команды из блоков 4 и 5 - сбор информации о текущих настройках и расчет уровней посредством вызова функции GetSignal. После них управление доходит до блока 6, где первым делом рассчитывается цена открытия ордера BuyStop. Следующий за этим вызов функции CloseAll закрывает все имеющиеся позиции, перешедшие в новый день с прошлых суток. В случае наличия несработавших ордеров, они удаляются. Если закрытие позиций прошло успешно (возведен флаг Res) и нахождения цены Ask ниже цены открытия планируемого ордера, ордер BuyStop устанавливается. Завершение его установки с ошибкой приведет к окончанию работы советника на текущем тике без присвоения переменной LastBuy значения времени открытия нового дня.

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

    Рассмотрим, как же решается проблема частичного закрытия позиции:

 
//+-------------------------------------------------------------------------------------+
//| Частичное закрытие позиции и перенос стопа в безубыток                              |
//+-------------------------------------------------------------------------------------+
void PartiallyClose()
{
 for (int i = OrdersTotal()-1; i >= 0; i--)
   if (OrderSelect(i, SELECT_BY_POS))
     if (OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber 
        && OrderType() < 2)                                  // поиск сработавшего ордера
        {
         if (StringFind(OrderComment(), "from #", 0) < 0)
           {                  // Цена достигла уровня второго сопротивления или поддержки
            if ((OrderType() == OP_BUY && ND(Bid) >= ND(Res2)) ||
                (OrderType() == OP_SELL && ND(Bid) <= ND(Supp2)))
              {                                          // Производим частичное закрытие
               double LotsClose = MathMax(MinLot,           // Расчет закрываемого объема
                                          MathRound((OrderLots()/2)/LotStep)*LotStep);
               if (WaitForTradeContext())                           
                 if (!OrderClose(OrderTicket(), LotsClose, IF(OrderType() == OP_BUY, 
                                 ND(Bid), ND(Ask)), 3))                           
                   return;              
              }  
           }
          else
           {                                                 // Перенос стопа в безубыток
            bool Res = False;
            if (OrderType() == OP_BUY)
              if (ND(OrderStopLoss()) < ND(OrderOpenPrice()) &&
                  ND(Bid - OrderOpenPrice()) > StopLevel)
                Res = True;  
            if (OrderType() == OP_SELL)
              if (ND(OrderStopLoss()) > ND(OrderOpenPrice()) &&
                  ND(OrderOpenPrice() - Ask) > StopLevel)
                Res = True;  
            if (Res)    
              if (WaitForTradeContext())
                if (!OrderModify(OrderTicket(), 0, ND(OrderOpenPrice()), 
                                 OrderTakeProfit(), 0))
                return;               
           } 
        }
}

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

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

    Полученный на данный момент советник без особых проблем будет работать в двух случаях: в тестере стратегий и при непрерывной работе онлайн. А вот отключение советника и повторное его включение приведет к новой установке ордеров, так как переменные LastSell и LastBuy будут обнулены.

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

 
// - 4 - == Сбор сведений об истори сделок до старта советника ==========================
   LastSell = 0;
   LastBuy = 0;
   for (int i = 0; i < OrdersTotal(); i++)
     if (OrderSelect(i, SELECT_BY_POS))
       if (OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber)
         {
          if (OrderType() == OP_BUY && OrderOpenTime() >= NowDay)
            LastBuy = NowDay;
          if (OrderType() == OP_SELL && OrderOpenTime() >= NowDay)
            LastSell = NowDay;
         } 
   for (i = 0; i < OrdersHistoryTotal(); i++)
     if (OrderSelect(i, SELECT_BY_POS, MODE_HISTORY))
       if (OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber)
         {
          if (OrderType() == OP_BUY && OrderOpenTime() >= NowDay)
            LastBuy = NowDay;
          if (OrderType() == OP_SELL && OrderOpenTime() >= NowDay)
            LastSell = NowDay;
         } 
// - 4 - == Окончание блока =============================================================


   Вот теперь советник готов к работе, как в тестере, так и онлайн. Но перед реальным использованием стоит протестировать стратегию и увидеть ее слабые места.

   Эксперт FiboPivot тестируется на промежутке 01.01.2008 - 25.07.2009, таймфрейм H1 (хотя для данного советника особой разницы нет), значения входных параметров по умолчанию. Результаты приведены на рис. 2 - 5.

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

 

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

 

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

 

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

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

    EURUSD.   Количество сделок 519. Чистая прибыль 1877.36 долларов против максимальной просадки 880.19, что дает фактор восстановления 2.13. Значение ФВ больше двух - это хороший показатель. Хотя, понятно, что чем он больше, тем лучше. Но на данный момент имеем пока только 2.13. Процент прибыльных сделок довольно высокий 74.37%. К тому же он подтверждается соотношением средний серий прибыльных и убыточных сделок - 5/2. То есть в среднем мы должны получать 5 прибыльных сделок подряд при двух убыточных. А вот максимум непрерывных убыточных сделок составляет 8, что довольно тяжело будет выдержать, но за то максимум непрерывной прибыли - 21, что может вовлечь в эйфорию.

    USDJPY.   Количество сделок 496. Чистая прибыль 1009.36 долларов против максимальной просадки 689.88. Соответственно, фактор восстановления 1.46. Посредственное значение ФВ. Но здесь присутствует другой фактор, говорящий в пользу именно йены, чем евро - стабильный рост графика кривой баланса. И что самое важное - этот рост продолжается до самого конца тестирования. А это свидетельствует о рабочем состоянии стратегии на данный момент, что можно попытаться использовать в ближайшее время для получения небольшой прибыли.

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

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

Август 2009