Рассматриваем на практике адаптивный метод следования за рынком

Dmitriy Gizlyk | 17 августа, 2017

Введение

Представленная в этой статье торговая стратегия впервые была описана Владимиром Кравчуком в журнале "Валютный спекулянт" в 2001 — 2002 гг. Система основана на использовании цифровых фильтров и спектральной оценке дискретных временных рядов.

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

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

1. Теоретические аспекты стратегии

Напомню, фильтрация — это изменение частотного спектра сигнала в нужном направлении. Такое преобразование может усилить или ослабить частотные составляющие в определенном диапазоне, подавить или выделить какую-либо конкретную из них. Цифровым фильтром называют цифровую систему для преобразования сигналов, определенных только в дискретные моменты времени.

В работе с цифровыми фильтрами и дискретными временными рядами есть важные аспекты. 

Во-первых, большинство популярных технических инструментов (MA, RSI, Momentum, Stochastic и т.д.) основаны на изменении частотного спектра сигнала, а значит, являются цифровыми фильтрами. Коэффициент усиления их передаточной функции зависит от частоты.Но мало кто задумывается об этой их передаточной функции. Поэтому большинство пользователей не знают, в каком направлении изменяется частотный спектр сигнала, а значит, не понимают саму природу воздействия индикатора на сигнал. Это затрудняет настройку индикатора и трактовку его сигналов.

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

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

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

Цель адаптивного метода следования за рынком — найти тот разумный минимум технических инструментов, который позволит создать алгоритм для трейдинга с максимальной доходностью при минимальном риске. Для этого последовательно проходятся несколько шагов.

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

1.1. Выбор метода спектрального анализа

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

Казалось бы, решение напрашивается само: нужно провести спектральный или гармонический анализ. Но какой именно метод выбрать? Сегодня известны два основных класса методов спектрального анализа: параметрические и непараметрические.

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

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

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

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

Также широко используется метод Блекмэна и Тьюки, в котором для исследуемого временного ряда находится преобразование Фурье взвешенной оценки корреляционной последовательности.

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

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

1.2. Применяемые инструменты технического анализа

Главное отличие представляемой стратегии — адаптивная трендовая линия. Ее направление — индикатор направления текущего тренда.

Адаптивная линия тренда представляет собой низкочастотную компоненту входного временного ряда. За ее получение отвечает ФНЧ (фильтр низкой частоты). Чем ниже частота отсечки fc ФНЧ, тем сильнее сглаживается линия тренда.

Между точками адаптивной линии тренда есть внутренняя связь, сила которой обратно пропорциональна расстоянию между ними. Нет связи только между значениями точек, расстояние между которыми равно или выше так называемого интервала Найквиста TN=1/(2 fc). Следовательно, с понижением частоты отсечки фильтра эта связь усиливается, а момент разворота тренда отодвигается.

Для определения тренда в торговой системе используются две адаптивных линии тренда с разными временными периодами.

FATL (Fast Adaptive Trend Line) — "быстрая" адаптивная линия тренда. Для построения нужен фильтр ФНЧ-1. Он подавляет высокочастотные шумы и рыночные циклы с очень коротким периодом колебания.

SATL (Slow Adaptive Trend Line) — "медленная" адаптивная линия тренда. Для построения нужен фильтр ФНЧ-2. В отличие от ФНЧ-1, он пропускает рыночные циклы с более долгим периодом колебаний.

Параметры вышеописанных фильтров (частота среза fc и затухание σ в полосе задерживания) вычисляются исходя из оценок спектра интересующего нас инструмента. ФНЧ-1 и ФНЧ-2 обеспечивают затухание в полосе задерживания не менее 40 Дб. Их использование абсолютно не сказывается на амплитуде и фазе входного сигнала в полосе пропускания. Это свойство цифровых фильтров обеспечивает эффективное подавление шумов и, в сравнении с простыми МА, дают меньше ложных сигналов.

С математической точки зрения, значение FATL(k) — матожидание цены Close(k), где k — номер торгового дня.

RFTL (Reference Fast Trend Line) и RSTL (Reference Slow Trend Line) — опорные "быстрая" и "медленная" линии  тренда. Они представляют собой значения, выданные цифровыми фильтрами ФНЧ-1 и ФНЧ-2 в ответ на на входной сигнал, и взятые с задержками, равными соответствующему интервалу Нейквиста .

FTLM (Fast Trend Line Momentum) и STLM (Slow Trend Line Momentum) демонстрируют сдвиги FATL и SATL. Вычисляются аналогично индикатору Momentum, только вместо цен закрытия используются сглаженные фильтрацией трендовые линии. В итоге линии получаются более гладкими и регулярными, нежели у привычного нам Momentum.

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

RBCI (Range Bound Channel Index) — индекс канала, ограниченный по пропускной полосе. Он рассчитывается полосным фильтром, в задачи которого входит следующее:

Периоды Т1 и Т2 выбраны так, чтобы удовлетворялось условие Т2 > T1. При этом частоты отсечки fc1 и fc2 должны быть такими, чтобы все доминирующие рыночные циклы попадали в рассмотрение.

Упрощенно, RBCI(k) = FATL(k) - SATL(k). Действительно, когда RBCI подходит к своим локальным экстремумам, цены приближаются к верхней или нижней границе торгового коридора (в зависимости от того, максимум это или минимум, соответственно).

PCCI (Perfect Commodity Channel Index) — совершенный индекс товарного канала. Формула его расчета: PCCI(k) = close(k) – FATL(k).

Метод его расчета похож на вычисление индекса товарного канала CCI. Действительно, CCI — это нормированная разность между текущей ценой и ее скользящей средней, а PCCI — разность между ценой закрытия дня и ее матожиданием (которое, как мы уже выяснили выше, берется из значения FATL).

Значит, индекс PCCI — это высокочастотная составляющая колебаний котировок, нормированная на свое стандартное отклонение.

Пример индикаторовПример индикаторов


1.3. Правила интерпретации сигналов индикаторов.

Обозначим основные принципы торговой системы.

Вышеприведенные инструменты следует интерпретировать по таким правилам.

Из приведенных правил сформулируем основные торговые сигналы.

  1. В начале долгосрочного тренда появляется надежный разворотный сигнал: STLM падает, говоря о сближении адаптивной и опорной «медленных» трендовых линий (SATL и RSTL). Во время формирования сигнала резко вырастает волатильность цен. Это характерный признак момента смены тренда. Поэтому, выбирая точки открытия сделки, необходимо учитывать PCCI. При медвежьем сигнале мы продаем, если к закрытию последней свечи осциллятор PCCI находится выше уровня -100.Если же значение PCCI ниже -100, то сделку мы не открываем, а ждем, когда осциллятор превысит этот уровень.

  2. Следующий сигнал говорит о продолжении сформировавшегося и окрепшего тренда после недолгой коррекции. Волатильность при этом, как правило, слабее, чем при развороте тренда. Поэтому условия формирования сигнала здесь жестче, а сам сигнал надежнее.
    Торгуем, если индикаторы FATL, FTLM и RBCI двигаются синхронно. Ложные сигналы отфильтровываются индикатором STLM (его абсолютное значение либо растет, либо не изменяется). Если STLM снижается, это говорит о сближении SATL и RSTL. При этом при поступлении медвежьего сигнала по RBCI проверяется, не перепродан ли рынок. Цена входа в рынок выбирается равной или лучше цены Open следующего бара после прихода сигнала.

  3. Этот сигнал основывается на сумме активных циклов в заданной частотной полосе, определяемой индексом RBCI. Не учитывается, куда направлена линия STLM и как SATL сходится/расходится с RSTL. Важно только, положительно или отрицательно значение STLM, которое говорит о направлении основного тренда, сформированного на рынке. Дополнительный фильтр — направление и поведение линии FATL.
    Формирование сигнала происходит, когда при нейтральном или нисходящем долгосрочном тренде композитный волновой цикл достигает локального максимума в зоне сильной перекупленности. При флэте потенциал движения за счет рыночных циклов будет выше потенциала движений в силу волатильности. Если тренд сформирован, потенциал движения на основании рыночных циклов будет складываться с потенциалом долгосрочного тренда.
    Цена открытия сделки тоже выбирается равной или лучше цены open следующего бара после прихода сигнала.

  4. Основание сигнала — две дивергенции: между направлением движения RBCI и линией FATL, а также между индексами RBCI и FTLM. Если в течение определенного периода FATL и индексы RBCI и FTLM двигаются разнонаправленно, мы переходим в стадию ожидания. Медвежий сигнал — локальный минимум FTLM, при том, что FATL и RBCI не изменяют направления движения. На графиках это выглядит как "кивок" FTLM в направлении движения  FATL.
    Чаще всего этот сигнал мы видим рядом с точкой окончания «быстрого» понижательного тренда (значение FTLM около "0"). Система формирует довольно точный сигнал на короткое трендовое движение.
    Это опережающий сигнал, предшествующий развороту тенденции.
    Цена входа в рынок также выбирается равной или лучше цены открытия следующей после прихода сигнала свечи.

  5. Сигнал формируется во время длинного тренда. Индексы RBCI и PCCI одновременно доходят до значений перекупленности рынка (для понижательного тренда). Чаще всего такие сигналы образуются при последней стадии тренда, когда котировки вдруг резко прыгают в противоположную сторону, а потом снова "переламываются" в направлении всё еще сильной основной тенденции.
    Цена входа в рынок также выбирается равной или лучше цены открытия свечи, следующей за свечей прихода сигнала.

  6. При формировании долгосрочной медвежьей тенденции (SATL уже падает, но значение STLM еще положительно) индекс PCCI достигает значений выше 100 (зона перекупленности рынка). Разворотный сигнал основан на использовании высокой волатильности рынка в момент формирования тенденции.
    Цена входа в рынок также выбирается равной или лучше цены открытия следующей после прихода сигнала свечи.

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

  8. И, наконец, еще один сигнал на разворот формируется, когда одновременно происходят два пересечения: "быстрой" и "медленной" адаптивных линий FATL и SATL, а также FATL и RFTL (при пробое вниз — продаем, при  пробое вверх покупаем). Этим сигналом помечается момент резкого перелома старой и уже ослабевшей тенденции. Цена продажи превышает или равна цене открытия следующей за приходом сигнала свечи.

2. Строим фильтры низких частот

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

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

Как уже говорилось выше, оценка спектральной плотности мощности автором проводилась при помощи метода максимальной энтропии. Этот метод относится к параметрическим и проводится по математической модели. Математическую модель мы построим по методу авторегрессии.

2.1. Анализ спектральной плотности мощности.

Для оценки спектральной плотности создадим класс CSpertrum (с полным кодом можно ознакомиться во вложении). При инициализации класса мы будем передавать наименование инструмента, рабочий таймфрейм и количество баров для анализа.

class CSpectrum
  {
private:
   int               ci_HistoryBars;               //Bars for analysis
   string            cs_Symbol;                    //Symbol
   ENUM_TIMEFRAMES   ce_Timeframe;                 //Timeframe
   double            cda_AR[];                     //Autoregression coefficients
   int               ci_NumberCoeffs;              //Number of coefficients
  
public:
                     CSpectrum(int bars=2880, string symbol=NULL, ENUM_TIMEFRAMES period=PERIOD_CURRENT);
                    ~CSpectrum();
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CSpectrum::CSpectrum(int bars=2880, string symbol=NULL, ENUM_TIMEFRAMES period=PERIOD_CURRENT)
  {
   ci_HistoryBars =  bars;
   cs_Symbol      =  (symbol==NULL ? _Symbol : symbol);
   ce_Timeframe   =  period;
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CSpectrum::~CSpectrum()
  {
  }

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

Спектральная плотность мощности EURUSD

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

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

bool CSpectrum::GetPeriods(int &FAT,int &SAT)
  {
   if(!Spectrum())
      return false;
   FAT=SAT=0;
   int total=ArraySize(cad_Spectr)-1;
   for(int i=1;(i<total);i++)
     {
      int temp=2*(total+1)/i;
      if(cad_Spectr[i]==0 || temp>(int)ci_HistoryBars/4)
         continue;
      if((cad_Spectr[i]-cad_Spectr[i+1])>=0 && (cad_Spectr[i]-cad_Spectr[i-1])>0)
        {
         if(SAT==0)
            SAT=temp;
         else
           {
            if(cad_Spectr[i]<-40)
              {
               if(FAT==0)
                  FAT=temp;
               break;
              }
            if(temp>=20)
               FAT=temp;
           }
        }
     }
   if(SAT==0 || FAT==0)
      return false;
   return true;
  }

2.2. Расчет коэффициентов фильтра низкой частоты.

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


Где y — выход фильтра; x — массив исходных данных; hk — импульсные характеристики; N — число импульсных характеристик.

В качестве исходных значений выступают ценовые данные инструмента, число импульсных характеристик установим равное интервалу Нейквиста. А сами импульсные характеристики нам предстоит рассчитать. Идеальные импульсные характеристики для фильтра низкой частоты можно рассчитать по формуле


Где fc и wc — частота среза.

К сожалению, наш мир далек от идеала. Поэтому нам нужна «реальная» импульсная характеристика. Для её расчета нам понадобится весовая функция w(n). Существует несколько разновидностей весовых функций. Я использую функцию Блэкмена, которая имеет вид


Где N — количество элементов фильтра.

Для получения «реальной» импульсной характеристики нужно перемножить идеальную импульсную характеристику с соответствующей весовой функцией


Теперь, когда мы определились с формулами расчета, создадим класс CFLF, в котором будем рассчитывать импульсные характеристики и фильтровать входящие данные. Для расчета коэффициентов импульсной характеристики создадим public функцию CalcImpulses, в которую передадим период фильтрации. Далее алгоритм функции повторит приведенные выше формулы. Затем нормализуем импульсные характеристики, приведя их сумму к "1".

bool CFLF::CalcImpulses(int period)
  {
   if(period<20)
      return false;
   int N=(int)(period/2);
   if(ArraySize(cda_H)!=N)
      if(ArrayResize(cda_H,N)<N)
         return false;
   double H_id[],W[];
   if(ArrayResize(H_id,N)<N || ArrayResize(W,N)<N)
      return false;
  
   cd_Fs=1/(double)period;
   for (int i=0;i<N;i++)
     {
      if (i==0)
         H_id[i] = 2*M_PI*cd_Fs;
      else
         H_id[i] = MathSin(2*M_PI*cd_Fs*i )/(M_PI*i);
      
      W[i] = 0.42 - 0.5 * MathCos((2*M_PI*i) /( N-1)) + 0.08 * MathCos((4*M_PI*i) /( N-1));
      cda_H[i] = H_id[i] * W[i];
     }
      
   //Normalization
   double SUM=MathSum(cda_H);
   if(SUM==QNaN || SUM==0)
      return false;
   for (int i=0; i<N; i++)
      cda_H[i]/=SUM; //summ of coefficients equal 1 
   //---
   return true;
  }

2.3. Расчет индикаторов FATL, SATL, RTFL,RSTL.

После того, как мы получили импульсные характеристики, можно приступить к расчету значений используемых индикаторов. Непосредственно из класса фильтра будет удобно получать значение индикаторов FATL, SATL, RFTL и RSTL.

Так как мы будем использовать различные экземпляры класса для быстрого и медленного фильтра, то в классе нам достаточно создать функции AdaptiveTrendLine и ReferenceTrendLine. В функции будем передавать используемые инструмент, таймфрейм и сдвиг относительно текущей свечи. Функции будут возвращать отфильтрованное значение.

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

double CFLF::AdaptiveTrendLine(string symbol=NULL,ENUM_TIMEFRAMES timeframe=0,int shift=1)
  {
   string symb=(symbol==NULL ? _Symbol : symbol);
   int bars=ArraySize(cda_H);
   double values[];
   if(CopyClose(symb,timeframe,shift,bars,values)<=0)
      return QNaN;
   double mean=MathMean(values);
   double result=0;
   for(int i=0;i<bars;i++)
      result+=cda_H[i]*(values[bars-i-1]-mean);
   result+=mean;
   return result;
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
double CFLF::ReferenceTrendLine(string symbol=NULL,ENUM_TIMEFRAMES timeframe=0,int shift=1)
  {
   shift+=(int)(1/(2*cd_Fs));
   return AdaptiveTrendLine(symbol,timeframe,shift);
  }

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

3. Создаем модуль торговых сигналов для генератора экспертов MQL5

Сегодня я решил немного отойти от обычного написания советника и напомнить о существовании в МetaТrader 5 мастера генерирования советников. Эта полезная функция является своеобразным конструктором, в котором собирается советник из ранее заготовленных модулей. Это позволяет без особого труда создать новый советник, добавив новые или удалив неиспользуемые функции. Поэтому я предлагаю вложить алгоритм принятия решения нашего советника в такой модуль. Подробно об этом методе уже не раз рассказывалось в статьях на этом сайте [4], [5]. Поэтому в этой статье я остановлюсь только на моментах, касающихся нашей стратегии.

Начнем с создания сигнального класса CSignalATCF на базе класса CExpertSignal и подключим к нему наши классы, созданные ранее.

class CSignalATCF : public CExpertSignal
  {
private:
   CSpectrum         *Spectrum;     //Class for spectr calculation
   CFLF              *FFLF;         //Class of fast low frequency filter
   CFLF              *SFLF;         //Class of slow low frequency filter

public:                      CSignalATCF();                     ~CSignalATCF();   };

При инициализации мы должны передать в модуль наименование инструмента, используемый таймфрейм, количество баров истории для анализа спектральной плотности мощности, а также количество баров усреднения (используется при расчете индикаторов RBCI и PCCI). Кроме того, нужно указать, какие из паттернов использовать для открытия позиций. Общий вид описания модуля будет иметь вид:

// wizard description start
//+---------------------------------------------------------------------------+
//| Description of the class                                                  |
//| Title=Signals degign by DNG for Adaptive Trend & Cycles Following Method  |
//| Type=SignalAdvanced                                                       |
//| Name=Signals Adaptive Trend & Cycles Following Method                     |
//| ShortName=ATCF                                                            |
//| Class=CSignalATCF                                                         |
//| Page=https://www.mql5.com/ru/articles/3456                                |
//| Parameter=TimeFrame,ENUM_TIMEFRAMES,PERIOD_H4,Timeframe                   |
//| Parameter=HistoryBars,uint,1560,Bars in history to analysis               |
//| Parameter=AveragePeriod,uint,500,Period for RBCI and PCCI                 |
//| Parameter=Pattern1,bool,true,Use pattern 1                                |
//| Parameter=Pattern2,bool,true,Use pattern 2                                |
//| Parameter=Pattern3,bool,true,Use pattern 3                                |
//| Parameter=Pattern4,bool,true,Use pattern 4                                |
//| Parameter=Pattern5,bool,true,Use pattern 5                                |
//| Parameter=Pattern6,bool,true,Use pattern 6                                |
//| Parameter=Pattern7,bool,true,Use pattern 7                                |
//| Parameter=Pattern8,bool,true,Use pattern 8                                |
//+---------------------------------------------------------------------------+
// wizard description end

Теперь объявим необходимые  переменные и функции:

class CSignalATCF : public CExpertSignal
  {
private:
   ENUM_TIMEFRAMES   ce_Timeframe;     //Timeframe
   uint              ci_HistoryBars;   //Bars in history to analysis
   uint              ci_AveragePeriod; //Period for RBCI and PCCI
   CSpectrum         *Spectrum;        //Class for spectr calculation
   CFLF              *FFLF;            //Class of fast low frequency filter
   CFLF              *SFLF;            //Class of slow low frequency filter
   //--- Indicators data
   double             FATL, FATL1, FATL2;
   double             SATL, SATL1;
   double             RFTL, RFTL1, RFTL2;
   double             RSTL, RSTL1;
   double             FTLM, FTLM1, FTLM2;
   double             STLM, STLM1;
   double             RBCI, RBCI1, RBCI2;
   double             PCCI, PCCI1, PCCI2;
   //--- Patterns flags
   bool               cb_UsePattern1;
   bool               cb_UsePattern2;
   bool               cb_UsePattern3;
   bool               cb_UsePattern4;
   bool               cb_UsePattern5;
   bool               cb_UsePattern6;
   bool               cb_UsePattern7;
   bool               cb_UsePattern8;
   //---
   datetime           cdt_LastSpectrCalc;
   datetime           cdt_LastCalcIndicators;
   bool               cb_fast_calced;
   bool               cb_slow_calced;
   
   bool              CalculateIndicators(void);
       
public:
                     CSignalATCF();
                    ~CSignalATCF();
   //---
   void              TimeFrame(ENUM_TIMEFRAMES value);
   void              HistoryBars(uint value);
   void              AveragePeriod(uint value);
   void              Pattern1(bool value)                {  cb_UsePattern1=value;   }
   void              Pattern2(bool value)                {  cb_UsePattern2=value;   }
   void              Pattern3(bool value)                {  cb_UsePattern3=value;   }
   void              Pattern4(bool value)                {  cb_UsePattern4=value;   }
   void              Pattern5(bool value)                {  cb_UsePattern5=value;   }
   void              Pattern6(bool value)                {  cb_UsePattern6=value;   }
   void              Pattern7(bool value)                {  cb_UsePattern7=value;   }
   void              Pattern8(bool value)                {  cb_UsePattern8=value;   }
   //--- method of verification of settings
   virtual bool      ValidationSettings(void);
   //--- method of creating the indicator and timeseries
   virtual bool      InitIndicators(CIndicators *indicators);
   //--- methods of checking if the market models are formed
   virtual int       LongCondition(void);
   virtual int       ShortCondition(void);
  };

Создадим функцию расчета значений индикаторов:

bool CSignalATCF::CalculateIndicators(void)
  {
   //--- Check time of last calculation
   datetime current=(datetime)SeriesInfoInteger(m_symbol.Name(),ce_Timeframe,SERIES_LASTBAR_DATE);
   if(current==cdt_LastCalcIndicators)
      return true;                  // Exit if data alredy calculated on this bar
   //--- Check fo recalc spectrum
   MqlDateTime Current;
   TimeToStruct(current,Current);
   Current.hour=0;
   Current.min=0;
   Current.sec=0;
   datetime start_day=StructToTime(Current);
   
   if(!cb_fast_calced || !cb_slow_calced || (!PositionSelect(m_symbol.Name()) && start_day>cdt_LastSpectrCalc))
     {
      if(CheckPointer(Spectrum)==POINTER_INVALID)
        {
         Spectrum=new CSpectrum(ci_HistoryBars,m_symbol.Name(),ce_Timeframe);
         if(CheckPointer(Spectrum)==POINTER_INVALID)
           {
            cb_fast_calced=false;
            cb_slow_calced=false;
            return false;
           }
        }
      
      int fast,slow;
      if(Spectrum.GetPeriods(fast,slow))
        {
         cdt_LastSpectrCalc=(datetime)SeriesInfoInteger(m_symbol.Name(),ce_Timeframe,SERIES_LASTBAR_DATE);
         if(CheckPointer(FFLF)==POINTER_INVALID)
           {
            FFLF=new CFLF();
            if(CheckPointer(FFLF)==POINTER_INVALID)
               return false;
           }
         cb_fast_calced=FFLF.CalcImpulses(fast);
         if(CheckPointer(SFLF)==POINTER_INVALID)
           {
            SFLF=new CFLF();
            if(CheckPointer(SFLF)==POINTER_INVALID)
               return false;
           }
         cb_slow_calced=SFLF.CalcImpulses(slow);
        }
     }
   if(!cb_fast_calced || !cb_slow_calced)
      return false;                       // Exit on some error
   
   //--- Calculate indicators data
   int shift=StartIndex();
   double rbci[],pcci[],close[];
   if(ArrayResize(rbci,ci_AveragePeriod)<(int)ci_AveragePeriod || ArrayResize(pcci,ci_AveragePeriod)<(int)ci_AveragePeriod ||
      m_close.GetData(shift,ci_AveragePeriod,close)<(int)ci_AveragePeriod)
     {
      return false;
     }
   for(uint i=0;i<ci_AveragePeriod;i++)
     {
      double fatl=FFLF.AdaptiveTrendLine(m_symbol.Name(),ce_Timeframe,shift+i);
      double satl=SFLF.AdaptiveTrendLine(m_symbol.Name(),ce_Timeframe,shift+i);
      switch(i)
        {
         case 0:
            FATL=fatl;
            SATL=satl;
            break;
         case 1:
            FATL1=fatl;
            SATL1=satl;
            break;
         case 2:
            FATL2=fatl;
            break;
        }
      rbci[i]=fatl-satl;
      pcci[i]=close[i]-fatl;
     }
   RFTL=FFLF.ReferenceTrendLine(m_symbol.Name(),ce_Timeframe,shift);
   RSTL=SFLF.ReferenceTrendLine(m_symbol.Name(),ce_Timeframe,shift);
   RFTL1=FFLF.ReferenceTrendLine(m_symbol.Name(),ce_Timeframe,shift+1);
   RSTL1=SFLF.ReferenceTrendLine(m_symbol.Name(),ce_Timeframe,shift+1);
   RFTL2=FFLF.ReferenceTrendLine(m_symbol.Name(),ce_Timeframe,shift+2);
   FTLM=FATL-RFTL;
   STLM=SATL-RSTL;
   FTLM1=FATL1-RFTL1;
   STLM1=SATL1-RSTL1;
   FTLM2=FATL2-RFTL2;
   double dev=MathStandardDeviation(rbci);
   if(dev==0 || dev==QNaN)
      return false;
   RBCI=rbci[0]/dev;
   RBCI1=rbci[1]/dev;
   RBCI2=rbci[2]/dev;
   dev=MathAverageDeviation(pcci);
   if(dev==0 || dev==QNaN)
      return false;
   PCCI=pcci[0]/(dev*0.015);
   PCCI1=pcci[1]/(dev*0.015);
   PCCI2=pcci[2]/(dev*0.015);
   cdt_LastCalcIndicators=current;
  //---
   return true;
  }

Затем закодируем паттерны открытия и закрытия позиций, указав соответствующие веса (40 — для закрытия и 80 — для открытия). Ниже приведена функция для открытия длинных позиций. Для коротких позиций функция построена аналогично.

int CSignalATCF::LongCondition(void)
  {
   if(!CalculateIndicators() || m_open.GetData(1)>m_close.GetData(1))
      return 0;
   int result=0;
   //--- Close
   if(m_high.GetData(2)<m_close.GetData(1) || (STLM1<=0 && STLM>0) || (PCCI1<PCCI && PCCI1<=PCCI2) || (RBCI>RBCI1 && RBCI1>=RBCI2 && RBCI1<-1) || (RBCI1<=0 && RBCI>0))
      result=40;
   //--- Pattern 1
   if(cb_UsePattern1 && FTLM>0 && STLM>STLM1 && PCCI<100)
      result=80;
   else
   //--- Pattern 2
   if(cb_UsePattern2 && STLM>0 && FATL>FATL1 && FTLM>FTLM1 && RBCI>RBCI1 && (STLM>=STLM1 || (STLM<STLM1 && RBCI<1)))
      result=80;
   else
   //--- Pattern 3
   if(cb_UsePattern3 && STLM>0 && FATL>FATL1 && RBCI>RBCI1 && RBCI1<-1 && RBCI1<=RBCI2 && FTLM>FTLM1)
      result=80;
   else
   //--- Pattern 4
   if(cb_UsePattern4 && SATL>SATL1 && FATL>FATL1 && RBCI>RBCI1 && FTLM<FTLM1 && FTLM2<=FTLM1)
      result=80;
   else
   //--- Pattern 5
   if(cb_UsePattern5 && SATL>SATL1 && STLM>=0 && PCCI1<=-100 && PCCI1<PCCI && PCCI>-100 && RBCI>RBCI1 && RBCI1<=RBCI2 && RBCI1<-1)
      result=80;
   else
   //--- Pattern 6
   if(cb_UsePattern6 && SATL>SATL1 && STLM<0 && PCCI1<=-100 && PCCI>-100)
      result=80;
   else
   //--- Pattern 7
   if(cb_UsePattern7 && FATL>FATL1 && FATL1<=SATL1 && FATL>SATL && FATL1<=FATL2)
      result=80;
   //--- Pattern 8
   if(cb_UsePattern8 && FATL>FATL1 && FATL1<=SATL1 && FATL>SATL && FATL1<=RFTL1 && FATL>RFTL)
      result=80;
   
   return result;
  }


4. Создаем советник адаптивного следования за рынком

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

Добавление сигнального модуля

Список сигнальных модулей

Выбор модуля трейлинга

Выбор модуля манимеджмента

5. Тестирование советника.

После того, как советник создан, мы можем протестировать адаптивный метод следования за рынком в Тестере стратегий. При тестировании обязательно указываем вес для открытия позиции на уровне 60 и вес для закрытия позиции на уровне 10.

5.1. Тест без использования стоп-лосса, тейк-профита и трейлинг-стопа.

Для проверки качества генерируемых советником сигналов первый тест был проведен без использования стоп-лосса, тейк-профита и трейлинг-стопа. Тестирование проводилось на таймфрейме H4 за 7 месяцев 2017 года.

Тест 1

Тест 1

К сожалению, первый тест показал убыточность применения стратегии без использования стоп-лоссов.

Тест 1. Результат

Тест 1. Результат

Тест 1. Результат

Тест 1. Результат

Тест 1. Результат 

Детальный анализ сделок на ценовом графике показал две проблемные зоны стратегии:

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

Тест 1 Сделки на графике

5.2. Тест с использованием стоп-лосса и трейлинг-стопа.

Для минимизации убытков по первому пункту был установлен стоп-лосс и применен трейлинг-стоп.

Тест 2

Тест 2

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

Тест 2. Результат

Тест 2. Результат

Тест 2. Результат

Тест 2. Результат

Тест 2. Результат

Тем не менее, доля прибыльных сделок составила 39.26%. И сохранилась вторая проблемная зона (убыточные сделки во флэте).

5.3. Тестирование с использованием стоп-ордеров.

Для снижения убытков, связанных с сериями убыточных сделок в флэтовых движениях, было проведено тестирование с использованием стоп-ордеров.

Тест 3

Тест 3

В результате третьего тестирования количество сделок снизилось почти в 2 раза, при этом общая прибыль увеличилась, и доля прибыльных сделок возросла до 44.57%.

Тест 3. Результат

Тест 3. Результат

Тест 3. Результат

Тест 3. Результат

Тест 3. Результат


Заключение

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

Ссылки

  1. "Валютный спекулянт", Декабрь 2000 - Июнь 2001.
  2. Анализ основных характеристик временных рядов.
  3. Авторегрессивная модель (AR) экстраполяции цен - индикатор для MetaTrader 5
  4. Мастер MQL5: Как написать свой модуль торговых сигналов
  5. Создай торгового робота за 6 шагов!
  6. Мастер MQL5: Новая версия

Программы, используемые в статье:

#
 Имя
Тип 
Описание 
 1 Spectrum.mqh Библиотека класса Класс для оценки спектральной плотности мощности исследуемого инструмента
 2 FLF.mqh Библиотека класса Класс для построения фильтра низкой частоты и фильтрации исходных данных
 3 SignalATCF.mqh Библиотека класса Модуль торговых сигналов по методу адаптивного следования за рынком
 4 ATCF.mq5 Эксперт Эксперт по методу адаптивного следования за рынком
 5 ACTF_Test.zip Архив Архив содержит результаты тестирования советника в тестере стратегий.