Индикатор SessionsFlat

Советник SessionTrade_Expert

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

 

    Одной из характеристик торговой системы является периодичность принятия решений о совершении сделок. Другими словами - срочность торговли. По срочности торговлю разделяют на такие типы:

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

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

  • Краткосрочная - торговля в пределах одного дня. Еще ее называют внутридневной торговлей. Здесь в расчет могут приниматься данные даже с минутного периода.

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

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

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

    

Рис. 1. - Совершение сделок при сессионной торговле.

    Схематично суть сессионной торговли представлена на рис. 1. Каждая из сессий выделена цветом: азиатская сессия - желтым, европейская - синим, американская - красным (цвета выбраны, исходя из цветов олимпийских колец, символизирующих части света). Все сделки производятся отложенными ордерами Buy Stop и Sell Stop.

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

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

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

 
//+-------------------------------------------------------------------------------------+
//| Расчет границ канала                                                                |
//+-------------------------------------------------------------------------------------+
void GetSignal()
{
// - 1 - ==== Если канал не будет рассчитан, то все значения останутся нулевыми =========
 LowV = 0;
 HighV = 0;
 CurSession = -1;
 int DayBar = 0; 
 int k = 0;
// - 1 - ============================= Окончание блока ==================================

// - 2 - ==== Определение текущей торговой сессии =======================================
 for (k = 0; k < 3; k++)
   if (Hour() >= Start[k] && Hour() < Finish[k])
     {
      if (k == 0)
        {
         DayBar++;
         int j = 2;
        } 
       else
        j = k - 1; 
// - 2 - ============================= Окончание блока ==================================

// - 3 - ==== Расчет уровней ============================================================
      datetime BeginDay = iTime(Symbol(), PERIOD_D1, DayBar);         // Время начала дня
      if (!(TimeDayOfWeek(BeginDay) == 5 && k == 0))        // Исключаем азиатскую сессию
        {                                                                 // понедельника
         // Бар, соответствующий началу предыдущей сесии
         int StartBar = iBarShift(Symbol(), 0, BeginDay+Start[j]*3600);
         // Бар, соответствующий окончанию предыдущей сесии
         int FinishBar = iBarShift(Symbol(), 0, BeginDay+Finish[j]*3600)+1; 
         // Нижняя граница
         LowV = Low[iLowest(Symbol(), 0, MODE_LOW, StartBar-FinishBar+1, FinishBar)];
         // Верхняя граница  
         HighV = High[iHighest(Symbol(), 0, MODE_HIGH, StartBar-FinishBar+1, FinishBar)]; 
         // Минимум текущей сессии
         SessionLow =  Low[iLowest(Symbol(), 0, MODE_LOW, FinishBar+1)]; 
         // Максимум текущей сессии
         SessionHigh = High[iHighest(Symbol(), 0, MODE_HIGH, FinishBar+1)];
        }
      CurSession = k;// номер текущей сессии (0-азиатская, 1-европейская, 2-американская)
      break;
     } 
// - 3 - ============================= Окончание блока ==================================
}

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

    Для определения границ канала необходимо определить принадлежность текущего времени какой-либо торговой сессии, что производится во втором блоке. В одномерных массивах Start и Finish до вызова функции GetSignal должны быть сформированы часы начала и окончания каждой сессии. Количество элементов каждого массива равно количеству сессий, то есть 3. Именно поэтому максимальное число итераций цикла for равно трем. Если текущий час принадлежит одной из трех сессий, то окончательно определяются переменные DayBar и j. Переменная j должна указывать на предыдущую торговую сессию, поэтому ее приравниваем к значению k, уменьшенного на единицу. Соответственно, значение DayBar должно указывать на порядковый номер дневного бара, которому принадлежит предыдущая торговая сессия. В результате, если k равно нулю (азиатская сессия), то предыдущая для нее  сессия будет американская сессия прошедшего дня. Это соответствует значению j, равного двум. А порядковый номер дневного бара просто увеличивается на единицу.

    В третьем блоке по порядковому номеру дня, указанного в DayBar, определяется время открытия дня (BeginDay), которому принадлежит предыдущая торговая сессия. Затем исключается расчет границ канала для азиатской сессии понедельника. В этом случае BeginDay будет указывать на пятницу, как на предыдущий день, а k - на азиатскую сессию. Дальше происходит простой арифметический расчет.  Для вычисления границ канала требуется знать номера баров начала (StartBar) и окончания сессии (FinishBar) текущего временного периода. Для этого к времени начала дня прибавляется количество часов, умноженное на количество секунд в часе (3600). По рассчитанным StartBar и FinishBar вычисляются границы канала. Также, кроме границ канала, требуется всегда знать текущий максимум и минимум торговой сессии. Для этого подобным образом рассчитываются значения переменных SessionLow и SessionHigh. И в конце блока глобальной переменной CurSession присваивается номер текущей торговой сессии.

      Код функции start приобрел на редкость лаконичный вид:

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

// - 2 - == Контроль образования нового бара для ускорения работы эксперта ==============
   if (LastBar == Time[0])
     return(0);
// - 2 - === Окончание блока ============================================================
     
// - 3 - == Сбор информации об условиях торговли ========================================
   Spread = ND(MarketInfo(Symbol(), MODE_SPREAD)*Point);                 // текущий спрэд
   StopLevel = ND(MarketInfo(Symbol(), MODE_STOPLEVEL)*Point);  // текущий уровень стопов 
// - 3 - === Окончание блока ============================================================
   
// - 4 - == Определение времени начала текущего дня и расчет уровней ====================
   NowDay = iTime(Symbol(), PERIOD_D1, 0);
   GetSignal();
// - 4 - === Окончание блока ============================================================

// - 5 - == Установка отложенного ордера, если определены уровни ========================
   if (LowV != 0 && HighV != 0 && CurSession != -1)
      DoTransactions();
// - 5 - === Окончание блока ============================================================

//----
   return(0);
  }

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

    А вот теперь рассмотрим функцию DoTransactions, которая осуществляет установку отложенного ордера:

 
//+-------------------------------------------------------------------------------------+
//| Установка отложенных ордеров                                                        |
//+-------------------------------------------------------------------------------------+
void DoTransactions()
{
// - 1 - == Установка ордера Buy Stop ===================================================
 if (LastOpen[CurSession] < NowDay)   // Устанавливался ли ордер за время текущей сессии?
   if (Close[1] < LowV && Open[1] < LowV)   // Цена закрепилась за нижней границей канала
     {
      RefreshRates();
      double Price = ND(LowV + Spread + Delta*Tick);   // Цена открытия Buy Stop - нижняя
                                                                        // граница канала
      double SL = ND(SessionLow - (HighV - LowV) - Tick);            // Стоп для Buy Stop
      double TP = ND(HighV);      // профит для Buy Stop - противоположная граница канала
      if (OpenOrderCorrect(OP_BUYSTOP, Lots, Price, SL, TP, False) == 0)
        LastOpen[CurSession] = TimeCurrent();   // Ордер успешно открыт, записываем время
       else 
        return;
     }    
// - 1 - === Окончание блока ============================================================

// - 2 - === Установка ордера Sell Stop =================================================
 if (LastOpen[CurSession] < NowDay)   // Устанавливался ли ордер за время текущей сессии?
   if (Close[1] > HighV && Open[1] > HighV)// Цена закрепилась за верхней границей канала
     {
      RefreshRates();
      Price = ND(HighV - Delta*Tick); // Цена открытия Sell Stop - верхняя граница канала
      SL = ND(SessionHigh + (HighV - LowV) + Spread + Tick);        // Стоп для Sell Stop
      TP = ND(LowV + Spread);    // профит для Sell Stop - противоположная граница канала
      if (OpenOrderCorrect(OP_SELLSTOP, Lots, Price, SL, TP, False) == 0)
        LastOpen[CurSession] = TimeCurrent();   // Ордер успешно открыт, записываем время
       else 
        return;
     }    
// - 2 - === Окончание блока ============================================================
   
 LastBar = Time[0];                                 // Больше на текущем баре не работаем
}

    Функция состоит из двух подобных блоков. Различие лишь в методике расчета уровней цены открытия, стоп-приказа и профита.

    Обязательным условием стратегии является открытие всего одного ордера за одну торговую сессию. Это условие возможно выполнить, записывая время открытия последнего ордера для соответствующей торговой сессии. Как вы помните, сессии у нас пронумерованы от 0 до 2. Поэтому достаточно использовать массив из трех элементов, в котором будет сохраняться время каждого успешно открытого ордера. В эксперте таким массивом выступает LastOpen, значение которого сравнивается со временем открытия текущего дня.

    При работе в тестере значение массива сохраняется. А вот в реальной работе эти значения теряются. Поэтому при каждом новом запуске эксперта необходимо поэлементно восстанавливать массив. Для этого в функцию init нужно вставить блок поиска последних ордеров. Принадлежность ордеров торговой сессии можно отметить разными значениями поля Magic Number, которые будут устанавливаться еще при открытии отложенного ордера в функции OpenOrderCorrect (см. предыдущую статью Ловец трендов).

     Блок восстановления значений массива LastOpen будет выглядеть так:

 
// - 4 - === Восстановление значений элементов массива LastOpen =========================
   for (int i = OrdersHistoryTotal() - 1; i >= 0; i--)    
     if (OrderSelect(i, SELECT_BY_POS, MODE_HISTORY))            // поиск в истории счета
       if (OrderSymbol() == Symbol() && MathFloor(OrderMagicNumber()/10) == MagicNumber)
         {
          int ID = MathMod(OrderMagicNumber(), 10);
          LastOpen[ID] = MathMax(LastOpen[ID], OrderOpenTime());
         }
         
   for (i = OrdersTotal() - 1; i >= 0; i--)    
     if (OrderSelect(i, SELECT_BY_POS))                         // поиск в окне терминала
       if (OrderSymbol() == Symbol() && MathFloor(OrderMagicNumber()/10) == MagicNumber)
         {
          ID = MathMod(OrderMagicNumber(), 10);
          LastOpen[ID] = MathMax(LastOpen[ID], OrderOpenTime());
         }
// - 4 - === Окончание блока ============================================================

   В получившемся эксперте SessionTrade_Expert входных параметров немного. Здесь присутствуют уже привычные Lots, OrderOpenSound и MagicNumber. К ним добавился параметр Delta, который указывает, на сколько пунктов от расчетной цены открытия должен быть установлен ордер. Для Buy Stop значение Delta прибавляется, для Sell Stop - вычитается.

    Остальные шесть параметров по смыслу идентичны. Это время в часах, определяющее начало (AzianFlatStart, EuropeanFlatStart и AmericanFlatStart) и окончание (AzianFlatEnd, EuropeanFlatEnd и AmericanFlatEnd) каждой из торговых сессий. Если пользователю не не подходят установленные по умолчанию значениями начала и окончания сессий, то он всегда может их изменить. Можно даже уменьшить или увеличить продолжительность каждой сессии. Главное, чтобы получившиеся сессии не накладывались друг на друга.

   Как всегда после разработки советника, приступим к тестированию стратегии.

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

 

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

 

 

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

 

 

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

 

 

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

    Из всех результатов удачным можно назвать только один - по EURUSD, даже, несмотря на то, что на паре USDCHF показана прибыль. Ведь прибыль показана мизерная - 313.52, а просадка - 1747.31 долларов, что к рассмотрению просто не допускается.

    Поэтому рассмотрим результаты только валютной пары EURUSD. Из-за уменьшения тестируемого таймфрейма (с привычного Н1 до более мелкого М15) количество сделок получилось довольно большим - 569. Чистая прибыль 4059.70 против максимальной просадки 1821.91 долларов. Несмотря на большое значение максимальной просадки, фактор восстановления оказался больше двух - 2.23, что делает результат приемлемым. Из других статистических показателей следует отметить процент прибыльных сделок - 61.8 (прямо как одно из любимых чисел приверженцев волновой теории Эллиота). Вселяет оптимизм превалирование средней серии прибыльных сделок над убыточной 3/2. Еще больше этот разрыв в показателях максимальных серий прибыльных и убыточных сделок - 12/5.

    

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

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

Сентябрь 2009