тел.: 8 800 200 86 47|+7 (812) 336 61 11Заказать звонок
Admiral Markets UK Admiral Markets UK Choose your country

MQLabs: Статистика простых паттернов Price Action. Часть 2.

Советник PriceAction_Statistic

 

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

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

 

Превращение индикатора в советник

В качестве исходного материала для построения советника PriceAction_Statistic будет взят индикатор, разработанный в предыдущей статье. Из его кода будут заимствованы все функции вида IsBullsXXXXPattern и IsBearsXXXXPattern, где вместо XXXX - должно находиться название паттерна. Также будут заимствованы функции-прослойки, задачей которых является определение наличия паттерна определенного типа на указанном баре с возвратом признака его направленности (бычий, медвежий). Это функции вида XXXXPatternType. С целью установления боле точного соответствия между результатом функции и ее названием (функции возвращают не тип паттерна, а его направление), эти функции в советнике будут названы XXXXPatternDir.

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

 
int GetSignal(double& sl, double& tp)
{
// - 1 - == Определение наличия паттерна на последнем баре ==============================
   int patternDir;                                 // Бычий или медвежий паттерн
   int patternStart;                               // Индекс бара начала паттерна
   int patternType = GetAnyPatternTypeAndDir(1, patternDir, patternStart);
   
   if (patternType < 0)                            // Паттерн не определен - сигнала нет
      return (PATTERN_NO);
// - 1 - == Окончание блока =============================================================
      
// - 2 - == Расчет умолчательных значений стоп-приказа и профита ========================
   if (!useOptimization)                           // Если оптимизация не требуется, то..
   {                                               // ..вычисляем умолчательные уровни..
      sl = GetPatternSL(1, patternStart,           //  позиции и уходим
                        patternDir, defaultSLOffset);
      tp = GetTP(patternDir, defaultTP);
      return (patternDir);                         
   }
// - 2 - == Окончание блока =============================================================
   
// - 3 - == Проведение подбора оптимальных параметров ===================================
   int bestSLOffset, bestTP;                       // Размеры SL и TP в пунктах
   if (!IsOptimalParameters(patternType, patternDir, bestSLOffset, bestTP))
      return (PATTERN_NO);                         // Нет торгового сигнала
      
   sl = GetPatternSL(1, patternStart, patternDir, bestSLOffset);
   tp = GetTP(patternDir, bestTP);
   
   return (patternDir);                            // Паттерн найден            
// - 3 - == Окончание блока =============================================================
}

Алгоритм работы функции очень похож на алгоритм функции StatisticShow индикатора PriceActionStatistic. В первом блоке проверяется наличие на последнем сформированном баре паттерна любого типа, что достигается вызовом функции GetAnyPattern. Функция возвращает индекс найденного паттерна, соответствующий положению имени паттерна в массиве typesOfPatterns, или отрицательное значение, если ни один из разрешенных пользователем паттернов не найден. В этом случае функция GetSignal досрочно заканчивает свое исполнение, возвращая значение 0 (PATTERN_NO - нет паттерна, а, следовательно, и сделки нет).

Второй блок включается в работу, если пользователю не требуется проведение автоматического подбора оптимальных параметров, за что отвечает настроечный параметр советника useOptimization. Это позволяет использовать советник с размерами стоп-приказа и профита, установленными пользователем, а не теми значениями, которые являются оптимальными с точки зрения методики подбора параметров. Также стоит учитывать, что сделка будет совершена без проверки успешности паттерна на истории. Уровень stop loss новой сделки устанавливается за границей паттерна, противоположной направлению сделки (ниже минимума паттерна для длинной сделки и выше максимума паттерна для короткой сделки), на расстоянии defaultSLOffset пунктов. Уровень take profit располагается в defaultTP пунктах от цены открытия сделки.

Третий блок дает возможность автоматизированного вычисления размеров профита и отступа уровня стоп-приказа от границы паттерна. Успешными считаются такие параметры bestSLOffset и bestTP, при которых на участке истории optimizationBars было произведено не менее minStatDeals сделок, которые привели к получению математического ожидания работы стратегии величиной не менее minExpectation пунктов. Напомним, математическое ожидание в МТ4 рассчитывается как величина чистой прибыли, деленная на количество совершенных сделок. Функция IsOptimalParameters возвращает значение false, если оптимальные параметры не были найдены, и значение true, если подбор параметров завершен успешно. Далее по полученным значениям bestSLOffset и bestTP рассчитываются уровни stop loss и take profit следующей сделки.

Функция определения паттерна на указанном баре GetAnyPatternTypeAndDir подобна содержимому тела цикла функции start индикатора PriceAction - в ней происходит последовательное обращение к функциям XXXXPatternDir до тех пор, пока не будет зафиксировано наличие паттерна. С этой целью паттерны перечислены в порядке, соответствующем приоритету: сначала проверяется наличие паттернов с наивысшим приоритетом (IR, затем MCM и т.д.), а в конце - паттерны с наинизшим приоритетом (последний - паттерн "Внутренний бар").

 
int GetAnyPatternTypeAndDir(int index, int& patternDir, int& patternStart)
{
   int total = Bars - 2;
   if (useIRpattern)                               // Если разрешено обрабатывать..
   {                                               // ..паттерн IR, то определим его..
      patternDir = IRPatternDir(index, total, patternStart);// ..наличие
      if (patternDir != PATTERN_NO)
         return (IR_INDEX);
   }
   
   if (useMCMpattern)                              // Если разрешено обрабатывать..
   {                                               // ..паттерн MCM, то определим его..
      patternDir = MCMPatternDir(index, total, patternStart); // ..наличие
      if (patternDir != PATTERN_NO)
         return (MCM_INDEX);
   }
   
   ...

   if (useDBpattern)                               // Если разрешено обрабатывать..
   {                                               // ..паттерн DB, то определим его..
      patternDir = DBPatternDir(index, total, patternStart); // ..наличие
      if (patternDir != PATTERN_NO)
         return (DB_INDEX);
   }

   if (useIBpattern)                               // Если разрешено обрабатывать..
   {                                               // ..паттерн IB, то определим его..
      patternDir = IBPatternDir(index, total, patternStart); // ..наличие
      if (patternDir != PATTERN_NO)
         return (IB_INDEX);
   }
   
   return (-1);                                    // Ни один из паттернов не определен
}

При обнаружении паттерна в переменную patternDir, переданную по указателю, записывается направление паттерна (PATTERN_BULL или PATTERN_BEAR), а в переменной patternStart сохраняется индекс бара начала паттерна. Функция возвращает индекс элемента массива, под которым тип паттерна записан в массиве typesOfPatterns.

Если ни один из распознаваемых советником паттернов (11 различных паттернов) не обнаружен, то функция возвращает значение -1.

Расчет значения stop loss позиции производится функцией GetPatternSL:

 
double GetPatternSL(int index, int patternStart, int patternDir, int offset)
{
   if (patternDir == PATTERN_BEAR)
      return (High[iHighest(NULL, 0, MODE_HIGH, patternStart - index + 1, index)] +
              offset*Point + g_spread);
              
   return (Low[iLowest(NULL, 0, MODE_LOW, patternStart - index + 1, index)] -
           offset*Point);
}

В зависимости от направления паттерна функция находит минимальную или максимальную цену паттерна. С этой целью осуществляется поиск минимума или максимума цены на участке от свечи index до свечи patternStart включительно. К найденной цене прибавляется (или вычитается) величина отступа стоп-приказа offset.

Аналогичным образом работает функция GetTP с той разницей, что базовая цена не вычисляется, т.к. это всегда Bid или Ask.

Заместителем функции CreateStatistic индикатора в коде советника является функция IsOptimalParameters:

 
bool IsOptimalParameters(int patternType, int patternDir, int& bestSLOffset, int& bestTP)
{
// - 1 - == Нахождение всех подобных паттернов на выбранном участке истории =============
   int patternCnt = 0;
   int patternIndex[];                             // Индексы баров, на которых найдены..
                                                   // ..подобные паттерны
   int patternDirN[];                              // Направления паттернов
   double patternSL[];                             // Цена стоп-приказа паттерна
   ArrayResize(patternIndex, optimizationBars);    // Максимальное кол-во паттернов на..
   ArrayResize(patternDirN, optimizationBars);     // ..этом участке истории
   ArrayResize(patternSL, optimizationBars);
   FindPatterns(patternType, optimizationBars,  
                patternCnt, patternIndex, patternDirN, patternSL);
// - 1 - == Окончание блока =============================================================

// - 2 - == Проведение подбора параметров ===============================================
   int totalNetProfit;                             // Итог виртуальной торговли с..
                                                   // ..использованием оптимальных..
                                                   // ..параметров, в пп.
   SelectParameters(patternCnt, patternIndex,      // Рассчитаем оптимальные параметры
                    patternDirN, patternSL, totalNetProfit, 
                    bestSLOffset, bestTP);
// - 2 - == Окончание блока =============================================================

// - 3 - == Принятие решения о проведении сделки ========================================
   double expectation = 0;                         // Если есть виртуальные сделки, то..
   if (patternCnt != 0)                            // ..рассчитаем математическое..
      expectation = totalNetProfit/1.0/patternCnt; // ..ожидание
   if (patternCnt >= minStatDeals)                 // Оптимальные параметры найдены
      if (expectation >= minExpectation)            
         return (true);
// - 3 - == Окончание блока =============================================================

   return (false);                                 // Оптимальные параметры не найдены
}

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

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

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

 

Торговая часть эксперта

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

В итоге получаем следующее простое устройство торговой функции:

 
bool Trade(int signal, double sl, double tp)
{
// - 1 - == Открытие длинной позиции ====================================================
   if (signal == PATTERN_BULL)                     // Активен сигнал покупки
      if (!OpenBuy(sl, tp))                        // Открытие позиции
         return(false);
// - 1 - == Окончание блока =============================================================

// - 2 - == Открытие короткой позиции ===================================================
   if (signal == PATTERN_BEAR)                     // Активен сигнал продажи
      if (!OpenSell(sl, tp))                       // Открытие позиции
         return(false);
// - 2 - == Окончание блока =============================================================

   return(true);    
}

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

 
bool OpenBuy(double sl, double tp)
{
// - 1 - == Закрытие противоположных позиций ============================================
   if (g_type == OP_SELL)
      if (!CloseDeal(g_ticket))
         return(false);
// - 1 - == Окончание блока =============================================================

// - 2 - == Модификация существующей позиции ============================================
   if (g_type == OP_BUY)
      return (ModifySLAndTP(g_ticket, sl, tp));
// - 2 - == Окончание блока =============================================================

// - 3 - == Покупка по рынку ============================================================
   return (OpenByMarket(OP_BUY, GetLots(), sl, tp, MagicNumber, false));
// - 3 - == Окончание блока =============================================================
}

Первый блок производит проверку наличия сделки, противоположной сигналу. Если таковая обнаружена, то совершается попытка закрытия короткой сделки. Длинная позиция не будет открыта до тех пор, пока не произойдет успешное закрытие короткой позиции.

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

Третий блок - открытие позиции по текущей рыночной цене.

 
bool ModifySLAndTP(int ticket, double sl, double tp)
{
   if (!(OrderSelect(ticket, SELECT_BY_TICKET) &&  // Выберем позицию по тикету и..
         OrderCloseTime() == 0))                   // ..удостоверимся, что она не закрыта
      return (true);                               // Уходим, если нет позиции
      
   if (MathAbs(sl - OrderStopLoss()) < g_tickSize &&// Проверим уровень стоп-приказа..
       MathAbs(tp - OrderTakeProfit()) < g_tickSize)// ..и профита
      return (true);                               // Уходим, если оба уровня двигать..
                                                   // ..не нужно
                                                   
   if ((OrderType() == OP_BUY && Bid - sl <= g_stopLevel) ||// Если цена слишком близка..
       (OrderType() == OP_SELL && sl - Ask <= g_stopLevel))// ..к новому уровню..
       return(false);                              // ..стоп-приказа - вернем ошибку
                                                      
   if ((OrderType() == OP_BUY && tp - Bid <= g_stopLevel) ||// Если цена слишком близка..
       (OrderType() == OP_SELL && Ask - tp <= g_stopLevel))// ..к новому уровню..
       return(false);                              // ..профита - вернем ошибку

   if (OrderModify(OrderTicket(), 0, sl, tp, 0))   // Изменяем стоп-приказ
      return (true);                               // Успешная модификация

   return(false);                                  // Неудачная модификация
}

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

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

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

Функция возвращает итог проведения операции модификации.

 

Тестирование эксперта

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

Первый способ тяжело назвать подходящим, т.к. из всех "одиночных" паттернов автору удалось подобрать только один паттерн, торговля по которому может дать прибыль. Это паттерн "Рельсы", используемый на таймфрейме Н1 валютной пары USDJPY (см. рис. 1). Использовался исторический период 01.01.2011 - 04.08.2012. Сразу заметим, что количество сделок не является достаточным для проведения серьезного статистического анализа.

 Рис. 1. Результаты тестирования советника PriceAction_Statistic на валютной паре USDJPY с использованием паттерна "Рельсы".

 

Для второго способа достаточно быстро был подобран первый список подходящих паттернов: IB, TB, Рельсы, PPR, Pin Bar. Как ни странно, наиболее успешной валютной парой вновь стала USDJPY, и вновь на таймфрейме H1 (см. рис. 2).

 Рис. 2. Результаты тестирования советника PriceAction_Statistic на валютной паре USDJPY с использованием группы паттернов.

 

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

 

Резюме

1. Советник PriceAction_Statistic ведет торговлю по простым паттернам PriceAction, которые были указаны трейдером.

    1.1 Короткая сделка открывается при нахождении медвежьего паттерна, длинная - при нахождении бычьего паттерна.

    1.2 Уровень stop loss устанавливается на расстоянии defaultSLOffset пунктов от границы паттерна, противоположной сделке.

    1.3 Уровень take profit устанавливается на расстоянии defaultTP пунктов от цены открытия сделки.

2. При получении сигнала открытия сделки, который совпадает с направлением уже открытой сделки, советник изменяет уровни stop loss и take profit существующей сделки согласно новым данным так, будто была открыта новая позиция.

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

    3.1 Участок истории, на котором производится оптимизация, указывается в барах - параметр optimizationBars - от текущего бара вглубь истории.

    3.2 Минимальное количество виртуальных сделок, считающееся достаточным для проведения анализа - параметр minStatDeals.

    3.3 Минимальное значение математического ожидания (величина прибыли, деленная на количество сделок), при котором процесс подбора параметров считается успешным - параметр minExpectation.

    3.4 Диапазон проведения оптимизации по значениям стоп-приказа и профита - от 0 и до maxSLTP с шагом stepSLTP.

 

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

 

 

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

Август 20122

Специально для компании Admiral Markets

2.083335
 
 
X
Loading