Советник Support&Resistance

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

 

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

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

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

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

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

    Базовым индикатором системы выберем Awesome Oscillator Билла Вильямса. Хотя здесь может применяться любой другой осциллятор (рис. 1).

    

Рис. 1. - Расчет уровней поддержки и сопротивления на основании индикатора АО.

    Если индикатор АО находится в положительной зоне, то ждем появления красной линии гистограммы после зеленой. Если мы дождались выполнения этого условия, то, начиная со следующей свечи, можно построить верхний и нижний уровни. Движемся по рис. 1 слева направо. Первой точкой отката будет свеча от 01:00 07.01.2009, помеченная цифрой 1 синего цвета. Для определения уровней, нужно найти максимальное (сопротивление) и минимальное (поддержка) значения цены за все время, когда АО был положителен. К рассматриваемому моменту это всего лишь восемь свечей (отмеченную цифрой 1 свечу не считаем, она только открылась). В результате сопротивление окажется на уровне 1.4677, отмеченном красной горизонтальной линией. А сопротивление будет на уровне 1.4992, отмечается синей горизонтальной линией. По найденным уровням ставим отложенные ордера BuyStop (плюс спрэд+пункт = 1.4997) и SellStop (минус пункт = 1.4991).

    Движемся далее. Следующий откат на АО отмечен цифрой 2. Проводим такую же процедуру нахождения минимума и максимума, но в результате получаем такие же значения. Канал остается на месте. Цена его не пробивает и отложенные ордера не срабатывают. Но на участке 2-3 канал пробивается вверх и позиция Buy открывается. Цена взлетает почти на 300 пунктов. Этого более чем достаточно для одной позиции и можно фиксировать прибыль.

    После такого спурта цена откатывается и позволяет зафиксировать точку 3. Здесь снова ищем минимум и максимум. В результате поддержка остается на прежнем месте, а сопротивление улетает на уровень 1.5277, на который устанавливается новый ордер Buy Stop (Sell Stop у нас все еще на месте). В 13:00 следующего дня (08.01.2009) новый ордер Buy Stop срабатывает, но на этот раз цена проходит лишь 90 пунктов, поэтому прибыль вряд ли удалось бы зафиксировать. Тем временем на очередном откате фиксируется точка 4 и уровень сопротивления передвигается на цену 1.5372, оставаясь неизменным на протяжении следующих трех секций откатов.

    Но вот на открытии свечи 20:00 09.01.2009 значение индикатора АО принимает отрицательную величину. Теперь алгоритм расчета поддержки и сопротивления меняется на противоположный. Ждем формирования зеленой полосы гистограммы после красной и фиксируем точку 8. Получаем девять свечей, которые соответствуют отрицательным значениям АО. Среди них ищем минимум и максимум, которые соответственно назначаем поддержкой и сопротивлением. Это приводит к передвижению цены открытия ордера Sell Stop на 1.5051, куда перемещается и стоп зависшей в убытке позиции Buy.

    Спустя три часа после фиксации точки 8, уровень 1.5051 преодолевается ценой, что приводит к развороту нашей позиции в рынке - Buy закрывается, а Sell открывается. Дальнейшую судьбу открытого Sell вы можете увидеть, взглянув на историю котировок валютной пары GBPUSD после 12-го января 2009 года. Минимум в отрицательной зоне АО был зафиксирован на отметке 1.4472, что позволило закрыть позицию с хорошей прибылью.

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

    Реализация описанного алгоритма не совсем укладывается в стандартный советник, который оперирует позициями Buy и Sell. Поэтому немного изменим используемый нами шаблон для написания экспертов. Вместо привычной функции GetSignal, напишем функцию GetLevels, в которой будут рассчитываться уровни поддержки и сопротивления (переменные Supp и Resis):

 
//+-------------------------------------------------------------------------------------+
//| Расчет уровней поддержки и сопротивления                                            |
//+-------------------------------------------------------------------------------------+
void GetLevels()
{
 Supp = 0;
 Resis = 0;
// - 1 - == Расчет данных индикатора АО =================================================
 double AO1 = iAO(Symbol(), 0, 1);
 double AO2 = iAO(Symbol(), 0, 2); 
 double AO3 = iAO(Symbol(), 0, 3); 
// - 1 - == Окончание блока =============================================================

// - 2 - == Поиск сопротивления и поддержки в положительной зоне индикатора АО ==========
 if (AO1 > 0)
   if (AO1 < AO2 && AO2 > AO3)                   // Была коррекция вниз после восхождения
     {
      // Поиск бара, на котором значение АО становится отрицательным
      int i = 4;   
      while (AO3 > 0 && i < Bars)
        {
         AO3 = iAO(Symbol(), 0, i);
         i++;
        }
      if (i == Bars) return;                // если дошли до конца истории, то это ошибка
      // Расчет уровней
      Supp = Low[iLowest(Symbol(), 0, MODE_LOW, i-1)];
      Resis = High[iHighest(Symbol(), 0, MODE_HIGH, i-1)];
      ChanStart = Time[i-2];              // Запоминаем время начала для индикации канала
     }
// - 2 - == Окончание блока =============================================================

// - 3 - == Поиск сопротивления и поддержки в отрицательной зоне индикатора АО ==========
 if (AO1 < 0)
   if (AO1 > AO2 && AO2 < AO3)                      // Была коррекция вверх после падения
     {
      // Поиск бара, на котором значение АО становится положительным
      i = 4;
      while (AO3 < 0 && i < Bars)
        {
         AO3 = iAO(Symbol(), 0, i);
         i++;
        } 
      if (i == Bars) return;                // если дошли до конца истории, то это ошибка
      // Расчет уровней
      Supp = Low[iLowest(Symbol(), 0, MODE_LOW, i-1)];
      Resis = High[iHighest(Symbol(), 0, MODE_HIGH, i-1)];
      ChanStart = Time[i-2];              // Запоминаем время начала для индикации канала
     }
// - 3 - == Окончание блока =============================================================
 
 ShowChannel();                                                    // Отображение уровней
}

    Вначале эти переменные обнуляются, что позволяет впоследствии определить, получилось ли рассчитать уровни.

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

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

     Переменная ChanStart принимает значение номера бара, на котором АО поменял знак. Значение ChanStart потребуется в следующей функции.

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

 
//+-------------------------------------------------------------------------------------+
//| Отображение линий поддержки и сопротивления                                         |
//+-------------------------------------------------------------------------------------+
void ShowChannel()
{
 if (Supp != 0 && Resis != 0)                                   // Если уровни определены
   {
    if (ObjectFind(SName) < 0)      // и объекта поддержки нет, то рисуем линию поддержки
      {
       ObjectCreate(SName, OBJ_TREND, 0, ChanStart, Supp, Time[0], Supp); 
       ObjectSet(SName, OBJPROP_RAY, False);
       ObjectSet(SName, OBJPROP_COLOR, SupportColor);
      }
     else      // если линия поддержки уже есть, то просто переносим ее на нужный уровень
      {
       ObjectMove(SName, 0, ChanStart, Supp);
       ObjectMove(SName, 1, Time[0], Supp);
      } 
    if (ObjectFind(RName) < 0)   // если сопротивления нет, то рисуем линию сопротивления
      {
       ObjectCreate(RName, OBJ_TREND, 0, ChanStart, Resis, Time[0], Resis);
       ObjectSet(RName, OBJPROP_RAY, False);
       ObjectSet(RName, OBJPROP_COLOR, ResistanceColor);
      }
     else  // если линия сопротивления уже есть, то просто переносим ее на нужный уровень
      {
       ObjectMove(RName, 0, ChanStart, Resis);
       ObjectMove(RName, 1, Time[0], Resis);
      } 
   }
  else           // если уровни не определены, то удаляем линии поддержки и сопротивления
   {
    if (ObjectFind(SName) == 0)
      ObjectDelete(SName);
    if (ObjectFind(RName) == 0)
      ObjectDelete(RName);
   } 
}

    Объекты, соответствующие уровням поддержки и сопротивления, будут отображены только в случае, если переменные Supp и Resis не равны нулю. Это приводит к созданию трендовых линий, которые отображаются не как лучи, а как отрезки, проведенные горизонтально. Имена объектов SName и RName задаются на самом старте советника в функции init и состоят соответственно из строк "Support" и "Resistance", к которым прибавлено значение входного параметра MagicNumber.

    Если значение Supp или Resis равно нулю, то объекты, отображающие уровни, удаляются.

    Приведенный код связывается в функции 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 -  == Окончание блока ============================================================
   
   if (LastBar == Time[0])
     return(0);
     
// - 3 - == Расчет последних поддержки и сопротивления ==================================
   GetLevels();
// - 3 -  == Окончание блока ============================================================

// - 4 - == Установка ордеров ===========================================================
   if (Supp != 0 && Resis != 0)       // Установка ордеров только, если определены уровни
     if (!SetOrders()) return(0);
// - 4 -  == Окончание блока ============================================================

   LastBar = Time[0];                                             // Новый бар "посчитан"

   return(0);
  }

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

      Как несложно догадаться, именно в функции SetOrders происходит установка отложенных ордеров, а также модификация цен открытия и стопов ордеров:

 
//+-------------------------------------------------------------------------------------+
//| Установка ордеров, подтяжка стопов                                                  |
//+-------------------------------------------------------------------------------------+
bool SetOrders()
{
// - 1 - == Расчет уровней входа и выхода из сделок =====================================
 bool Buy = False, Sell = False;                                 // Сделки еще не найдены
 double BuyStop = NP(Resis + Spread + Tick);// Цена открытия для BuyStop и стопа для Sell
 double SellStop = NP(Supp - Tick);         // Цена открытия для SellStop и стопа для Buy
 double BuyTake = NP(2*BuyStop - SellStop);                       // Цена для профита Buy
 double SellTake = NP(2*SellStop - BuyStop);                     // Цена для профита Sell
// - 1 -  == Окончание блока ============================================================

// - 2 - == Поиск своих ордеров/позиций =================================================
 for (int i = OrdersTotal()-1; i >= 0; i--)
   if (OrderSelect(i, SELECT_BY_POS))
     if (OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber)
// - 2 -  == Окончание блока ============================================================

// - 3 - == Найден BUY ==================================================================
       {
        RefreshRates();
        if (OrderType() == OP_BUY)
          {
           Buy = True;
           // Если у BUY установлен неверный стоп, то изменяем стоп и профит
           if (ND(OrderStopLoss() - SellStop) != 0 && ND(Bid - StopLevel - SellStop) > 0
               && ND(BuyTake - Bid - StopLevel) > 0)
             if (WaitForTradeContext())
               if (!OrderModify(OrderTicket(), 0, SellStop, BuyTake, 0))
                 return(False);
          }       
// - 3 -  == Окончание блока ============================================================

// - 4 - == Найден BUYSTOP ==============================================================
        if (OrderType() == OP_BUYSTOP)
          {
           Buy = True;
           // Если у ордера неправильная цена открытия, то изменяем цену, стоп и профит
           if (ND(OrderOpenPrice() - BuyStop) != 0 && ND(BuyStop - Ask - StopLevel) > 0
               && ND(OrderOpenPrice() - Ask - FreezeLevel) > 0)
             if (WaitForTradeContext())
               if (!OrderModify(OrderTicket(), BuyStop, SellStop, BuyTake, 0))
                 return(False);
          }       
// - 4 -  == Окончание блока ============================================================

// - 5 - == Найден SELL =================================================================
        if (OrderType() == OP_SELL)
          {
           Sell = True;
           if (ND(OrderStopLoss() - BuyStop) != 0 && ND(BuyStop - Ask - StopLevel) > 0 &&
               ND(Bid - StopLevel - SellTake) > 0)
             if (WaitForTradeContext())
               if (!OrderModify(OrderTicket(), 0, BuyStop, SellTake, 0))
                 return(False);
          }       
// - 5 -  == Окончание блока ============================================================

// - 6 - == Найден SELLSTOP =============================================================
        if (OrderType() == OP_SELLSTOP)
          {
           Sell = True;
           if (ND(OrderOpenPrice() - SellStop) != 0 && ND(Bid - SellStop - StopLevel) > 0
               && ND(Bid - OrderOpenPrice() - FreezeLevel) > 0)
             if (WaitForTradeContext())
               if (!OrderModify(OrderTicket(), SellStop, BuyStop, SellTake, 0))
                 return(False);
          }       
       }
// - 6 - === Окончание блока ============================================================
       
// - 7 - == BuyStop или Buy не найдены - установка BuyStop ==============================
 if (!Buy && Ask < Resis)
   if (OpenOrderCorrect(OP_BUYSTOP, BuyStop, SellStop, BuyTake) > 0)
     return(False);
// - 7 - === Окончание блока ============================================================
 
// - 8 - == SellStop или Sell не найдены - установка SellStop ===========================
 if (!Sell && Bid > Supp)
   if (OpenOrderCorrect(OP_SELLSTOP, SellStop, BuyStop, SellTake) > 0)
     return(False);
// - 8 - === Окончание блока ============================================================
}

   В первом блоке рассчитываются значения цен открытия ордеров и уровней профита позиций. Уровень открытия ордера Buy Stop, который также является уровнем стопа для позиции Sell, рассчитывается как значение сопротивления (Supp) плюс спрэд и плюс один тик. Соответственно уровень открытия ордера Sell Stop, который равен уровню стопа позиции Buy, рассчитывается как значение поддержки минус один тик.

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

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

    Седьмой и восьмой блоки занимаются установкой отложенных ордеров, если в результате прохождения цикла (блоки 2 - 6) не были найдены ордера Buy, Sell, BuyStop и SellStop.

    После такого детального рассмотрения основных функций советника, можем переходить к тестированию эксперта Support&Resistance, который обладает очень маленьким набором входных параметров. Пользователь может изменять лишь значение объема сделки, звук, издаваемый при установке ордера, магик и цвета уровней поддержки и сопротивления.

   В качестве тестового периода берем привычный 01.01.2008 - 03.10.2009, таймфрейм Н1, значения входных параметров - по умолчанию. Результаты приведены на рис. 2 - 5.

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

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

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

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

    

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

   USDJPY. Обращаем внимание на большое количество совершенных сделок - 447. Плохо, что чистая прибыль в размере 1232 доллара не перекрывает максимальную просадку 1497 долларов. Также отмечаем, что доля прибыльных сделок меньше доли убыточных - 43.62% против 56.18%. Особо не радует и вид кривой баланса. Провал по центру в виде несимметричной буквы "V" свидетельствует о нестабильной работе стратегии на валютной паре USDJPY.

    GBPUSD. Как и в случае с йеной, можно говорить о более высокой достоверности результатов, чем обычно. Виной тому большое количество сделок (473), на которых подтвержден результат. Да, это не тысяча, и не две, но все же побольше обычных 200-300 транзакций. Чистая прибыль показана действительно впечатляющая - 5968 долларов, но и просадка солидная - 2051 доллар. В итоге фактор восстановления получаем близкий к трем - 2.91. Полученные цифры позволяют нарисовать такую картину. При стартовом капитале $10 000, торгуя на протяжении 21-го месяца = 1.75 года и имея просадку от общего капитала 20.5%, мы получаем прибыль в размере 59.6%. В пересчете на годовые это выходит 34%. В принципе неплохо. Стратегия стоит того, чтобы уделить ей внимание в дальнейшем.

 

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

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

Октябрь 2009