English 中文 Español Deutsch 日本語 Português 한국어 Français Italiano Türkçe
Несколько способов определения тренда на MQL5

Несколько способов определения тренда на MQL5

MetaTrader 5Трейдинг | 13 августа 2010, 18:11
36 505 8
Dmitriy Skub
Dmitriy Skub

Введение

Любой трейдер знает правило "Тренд - твой друг, следуй за трендом", но практически каждый имеет свое представление о том, что из себя тренд представляет. Практически каждый трейдер слышал или читал ужасные истории, в которых рассказывается о том, как разорился трейдер, торговавший против тренда.

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


1. Что такое тренд и зачем его знать

Для начала, сформулируем самое общее понятие тренда.

Итак, тренд - это длительная тенденция (направление) изменения цен на рынке. Из этого общего определения тренда сразу вытекают следствия:

  • направление изменения цен зависит от временного интервала, на котором рассматривается временной ряд цен;
  • направление изменения цен зависит от точки отсчета, с которой начинается анализ временного ряда на предмет выявления тренда.

Проиллюстрируем сказанное картинками:

Рисунок 1. Анализ тренда

Рисунок 1. Анализ тренда

Глядя на рисунок можно увидеть, что общая тенденция с конца 2005 г. по май 2006 г. восходящая (зеленая стрелка на графике). Однако если рассмотреть более мелкие участки графика цены, то можно обнаружить, что в феврале 2006г. тренд был явно нисходящий (красная стрелка на графике), а почти весь январь цена стояла в боковом коридоре (желтая стрелка).

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

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

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

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

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


2. Как выявить тренд

Не претендуя на полноту всех вариантов, приведем некоторые способы определения тренда:

  1. По скользящим средним;
  2. По вершинам зигзага;
  3. По показаниям ADX;
  4. По NRTR;
  5. По цвету свечей Heiken Ashi.

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

2.1. Определение направления тренда скользящей средней

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

Сформулируем простое правило для одной скользящей средней:
  • тренд направлен вверх, если на заданном таймфрейме цена закрытия бара находится выше скользящей средней;
  • тренд направлен вниз, если на заданном таймфрейме цена закрытия бара находится ниже скользящей средней;

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

Проиллюстрируем этот способ картинкой:

Рисунок 2. Определение тренда по скользящей средней

Рисунок 2. Определение тренда по скользящей средней

Здесь используется график EURUSD D1 и простая скользящая средняя с периодом 200, построенная по ценам закрытия (линия красного цвета на графике). В нижней части картинки отображается специально разработанный  нами индикатор тренда - MATrendDetector. Направления тренда указывается положением гистограммы индикатора относительно нулевой оси. Плюс единице соответствует направление тренда вверх. Минус единице - вниз. О нем и других, использованных в этой статье, будет рассказано ниже.

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

2.2. Определение направления тренда по трем скользящим средним

Что же можно сделать, чтобы улучшить качество определения тренда по скользящим средним? Например, использовать две и более скользящих средних с разными периодами. Тогда правило определения тренда для любого числа (больше, чем одна) скользящих средних с разными периодами будет выглядеть так:

  • тренд направлен вверх, если на заданном таймфрейме все скользящие средние выстроены в правильном порядке повышения при закрытии бара;
  • тренд направлен вниз, если на заданном таймфрейме все скользящие средние выстроены в правильном порядке понижения при закрытии бара.

Здесь используются термины:

  • "в правильном порядке повышения" - каждая скользящая средняя должна быть выше всех остальных средних с большим периодом;
  • "в правильном порядке понижения" - каждая скользящая средняя должна быть ниже всех остальных средних с большим периодом.

Такой "правильный порядок средних" также называют раскрытием веера средних вверх/вниз из-за внешнего сходства.

Проиллюстрируем этот способ картинкой:

Рисунок 3. Определение тренда по нескольким скользящим средним

Рисунок 3. Определение тренда по нескольким скользящим средним

Здесь используется график EURUSD D1 и простые скользящие средние с периодами 200 (толстая линия красного цвета), 50 (линия желтого цвета средней толщины), 21 (тонкая линия фиолетового цвета), построенные по ценам закрытия.

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

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

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

2.3. Определение направления тренда по максимумам и минимумам индикатора Зигзаг

Попробуем теперь подойти к определению тренда с позиции классиков технического анализа. А именно, используем следующее правило Чарльза Доу:

  • тренд направлен вверх, если каждый последующий локальный максимум графика цены выше предыдущего локального максимума и, при этом, каждый последующий локальный минимум графика цены также выше предыдущего локального минимума;
  • тренд направлен вниз, если каждый последующий локальный минимум графика цены ниже предыдущего локального минимума и, при этом, каждый последующий локальный максимум графика цены также ниже предыдущего локального максимума.

Локальные максимумы/минимумы будем находить по вершинам индикатора Зигзаг.

Проиллюстрируем этот способ картинкой:

Рисунок 4. Определение тренда при помощи индикатора Зигзаг

Рисунок 4. Определение тренда при помощи индикатора Зигзаг

Здесь используется график EURUSD D1 и Зигзаг с параметрами: ExtDepth = 5, ExtDeviation = 5, ExtBackstep = 3.

В нижней части картинки отображается разработанный  нами индикатор тренда - ZigZagTrendDetector.

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

Этот недостаток делает данный способ малоценным для практического использования в торговле. Зато он весьма полезен при техническом анализе исторических данных с целью поиска закономерностей и для оценки качества работы различных торговых систем.

2.4. Определение направления тренда по показаниям индикатора ADX

Следующий из рассматриваемых нами способов - определение тренда по показаниям индикатора ADX (Average Directional Movement Index) или индекса среднего направленного движения. Данный индикатор используется не только для определения направления тренда, но также для оценки его силы. Это очень ценное свойство индикатора ADX. Сила тренда определяется по основной линии ADX - если значение больше 20 (общепринятый уровень, но не обязательно оптимальный в данный момент), то тренд достаточно сильный.

Направление тренда определяется по расположению линий +DI и -DI относительно друг друга. Данный индикатор использует сглаживание всех трех линий при помощи экспоненциального усреднения, поэтому имеет задержку реакции на изменение тренда.

Сформулируем правило определения тренда:

  • тренд направлен вверх, если линия +DI находится выше, чем линия -DI;
  • тренд направлен вниз, если линия +DI находится ниже, чем линия -DI.

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

Проиллюстрируем данный способ картинкой:

Рисунок 5. Определение тренда по индикатору ADX

Рисунок 5. Определение тренда по индикатору ADX

Здесь используется график EURUSD D1 и индикатор ADX с параметрами: PeriodADX = 21 (толстая синяя линия - значение силы тренда ADX, тонкая зеленая линия - значение +DI, тонкая красная линия - значение -DI).

В нижней части картинки отображается разработанный нами индикатор тренда - ADXTrendDetector. Для сравнения, верхний график индикатора ADXTrendDetector (малинового цвета) установлен с выключенным фильтром по силе тренда (значение ADXTrendLevel = 0), а график под ним (голубого цвета) - с включенным (параметром ADXTrendLevel = 20).

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

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

2.5. Определение направления тренда по показаниям индикатора NRTR

Следующий способ определения тренда - по показаниям индикатора NRTR (Nick Rypock Trailing Reverse) - трендследящий индикатор Ника Рипока. Этот индикатор всегда находится на постоянном удалении от достигнутых экстремумов цен – ниже цены на восходящих трендах и выше цены на нисходящих трендах. Основная идея индикатора - небольшие коррекционные движения против основного тренда должны игнорироваться, а движение против основной тенденции, превышающее некоторый уровень, сигнализирует о смене направления тенденции.

Отсюда следует правило для определения направления тренда:

  • тренд направлен вверх - если при закрытии бара линия индикатора соответствует восходящему тренду;
  • тренд направлен вниз - если при закрытии бара линия индикатора соответствует нисходящему тренду.

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

Проиллюстрируем данный способ картинкой:


Рисунок 6. Определение тренда по индикатору NRTR

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

2.6. Определение направления тренда по свечам Heiken Ashi

Достаточно популярным является способ определения тренда по свечам Heiken Ashi. Графики Heiken Ashi являются видоизменёнными графиками японских свечей, но их значения частично усредняются с предыдущей свечой.

Проиллюстрируем данный способ картинкой:

Рисунок 7. Определение тренда по цвету свечей Heiken Ashi

Рисунок 7. Определение тренда по цвету свечей Heiken Ashi

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


3. Индикаторы тренда

Напишем теперь индикаторы тренда.

3.1. Индикатор тренда, основанный на скользящей средней

Самый простой индикатор, как и самый простой способ определения тренда, основан на скользящей средней. Рассмотрим последовательно, из каких частей он состоит. Полный текст индикатора находится в файле MATrendDetector.MQ5, который приложен к статье.

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

#include <MovingAverages.mqh>

Мы используем из нее одну функцию, вычисляющую простую скользящую среднюю:

double SimpleMA(const int position, const int period, const double &price[])

Здесь задаются входные параметры, имеющие такое назначение:

  • position - начальный индекс в массиве price[], с которого начинаются расчеты средней;
  • period - период скользящей средней, должен быть больше нуля;
  • price[] - массив, содержащий ценовой ряд, заданный при установке индикатора на график, по умолчанию используются цены закрытия бара Close[].

Функция возвращает рассчитанное значение скользящей средней.

Следующая часть текста содержит начальные настройки для отображения индикатора на экране в нужном нам виде:

//---------------------------------------------------------------------
#property indicator_separate_window
//---------------------------------------------------------------------
#property indicator_applied_price       PRICE_CLOSE
#property indicator_minimum             -1.4
#property indicator_maximum             +1.4
//---------------------------------------------------------------------
#property indicator_buffers             1
#property indicator_plots               1
//---------------------------------------------------------------------
#property indicator_type1               DRAW_HISTOGRAM
#property indicator_color1              Black
#property indicator_width1              2
//---------------------------------------------------------------------

Задаются следующие настройки:

  • #property indicator_separate_window сообщает терминалу MetaTrader 5, что надо выводить график индикатора в отдельном окне;
  • #property indicator_applied_price    PRICE_CLOSE - тип цен, который используется по умолчанию;
  • #property indicator_minimum   -1.4 - минимальное значение на вертикальной оси, отображаемое в окне индикатора;
  • #property indicator_maximum  +1.4 - максимальное значение на вертикальной оси, отображаемое в окне индикатора.

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

  • #property indicator_buffers   1 - количество буферов для расчета индикатора, у нас используется только один расчетный буфер;
  • #property indicator_plots       1 - количество графических серий в индикаторе, у нас выводится один график на экран;
  • #property indicator_type1     DRAW_HISTOGRAM - задает отображение графика индикатора в виде гистограммы;
  • #property indicator_color1     Black - задает цвет графика индикатора по умолчанию;
  • #property indicator_width1    2 - задает толщину линии графика индикатора, в данном случае толщину столбиков гистограммы;

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

input int   MAPeriod = 200;

Здесь только один параметр - значение периода скользящей средней.

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

Первая функция - инициализации OnInit(). Вызывается сразу после загрузки индикатора. В нашем индикаторе она выглядит следующим образом:

void OnInit( )
{
  SetIndexBuffer( 0, TrendBuffer, INDICATOR_DATA );
  PlotIndexSetInteger( 0, PLOT_DRAW_BEGIN, MAPeriod );
}

Функция SetIndexBuffer() связывает объявленный ранее массив, в котором мы будем хранить значения направления тренда TrendBuffer[], с одним из индикаторных буферов. Индикаторный буфер у нас всего один и его индекс равен нулю.

Функция PlotIndexSetInteger() задает количество начальных баров без отрисовки в окне индикатора.

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

Далее идет функция-обработчик события о необходимости пересчета индикатора OnCalculate():

int OnCalculate( const int _rates_total, 
               const int _prev_calculated,
               const int _begin, 
               const double& _price[] )
{
  int  start, i;

//   Если число баров на экране меньше, чем период усреднения, то расчеты не возможны:
  if( _rates_total < MAPeriod )
  {
    return( 0 );
  }

//  Определим начальный бар для расчета индикаторного буфера:
  if( _prev_calculated == 0 )
  {
    start = MAPeriod;
  }
  else
  {
    start = _prev_calculated - 1;
  }

//      Цикл расчета значений индикаторного буфера:
  for( i = start; i < _rates_total; i++ )
  {
    TrendBuffer[ i ] = TrendDetector( i, _price );
  }

  return( _rates_total );
}

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

Сначала проверяем, достаточное ли количество баров содержит график - если оно меньше периода средней, то дальше нам рассчитывать нечего и на этом выполнение функции завершается оператором return. Если же число баров достаточно для расчетов, то определяем начальный бар, с которого будет рассчитываться индикатор. Это делается для того, чтобы не пересчитывать все значения индикатора на каждом ценовом тике.

Здесь мы используем механизм, предоставляемый терминалом. Каждый раз при вызове обработчика проверяем значение аргумента функции _prev_calculated - это число баров, обработанное на предыдущем вызове функции OnCalculate(). Если он равен нулю, то пересчитываем все значения индикатора. Иначе пересчитываем только последний бар с индексом _prev_calculated - 1.

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

Рассмотрим теперь саму функцию определения тренда TrendDetector.

int TrendDetector(int _shift, const double& _price[])
{
  double  current_ma;
  int     trend_direction = 0;

  current_ma = SimpleMA(_shift, MAPeriod, _price);

  if(_price[_shift] > current_ma)
  {
    trend_direction = 1;
  }
  else if(_price[_shift] < current_ma)
  {
    trend_direction = -1;
  }

  return(trend_direction);
}

Функция выполняет следующие задачи:

  • вычисляет значение простой скользящей средней, начиная с заданного аргументом _shift бара - с помощью вызова библиотечной функции SimpleMA;
  • сравнивает значение цены на этом баре с значением средней;
  • если значение цены больше значения средней, то возвращается +1, если значение цены меньше значения средней, то возвращается -1, иначе возвращается ноль.

Если функция возвратила ноль, то это значит, что направление тренда определить не удалось.

Результат работы получившегося индикатора можно увидеть на рис.2 и рис.3.

3.2. Индикатор тренда, основанный на "веере" скользящих средних

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

Полный текст индикатора находится в файле FanTrendDetector.MQ5, который приложен к статье.

Отличия этого индикатора от предыдущего в следующем:

  • периоды трех скользящих средних задаются во внешних задаваемых параметрах:
input int MA1Period = 200; // значение периода старшей скользящей средней
input int MA2Period = 50;  // значение периода средней скользящей средней
input int MA3Period = 21;  // значение периода младшей скользящей средней
  • другая функция TrendDetector:
int TrendDetector(int _shift, const double& _price[])
{
  double  current_ma1, current_ma2, current_ma3;
  int     trend_direction = 0;

  current_ma1 = SimpleMA(_shift, MA1Period, _price);
  current_ma2 = SimpleMA(_shift, MA2Period, _price);
  current_ma3 = SimpleMA(_shift, MA3Period, _price);

  if(current_ma3 > current_ma2 && current_ma2 > current_ma1)
  {
    trend_direction = 1;
  }
  else if(current_ma3 < current_ma2 && current_ma2 < current_ma1)
  {
    trend_direction = -1;
  }

  return(trend_direction);
}

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

Остальные части индикатора точно такие же, как и у предыдущего.

3.3. Индикатор тренда, основанный на индикаторе Зигзаг

Теперь рассмотрим построение индикатора, использующего переломы Зигзага для определения наличия экстремумов и определения направления тренда по Чарльзу Доу. Полный текст индикатора находится в файле ZigZagTrendDetector.MQ5, который приложен к статье.

Во внешних переменных задаются значения параметров вызываемого внешнего индикатора ZigZag:

//---------------------------------------------------------------------
//  Внешние задаваемые параметры:
//---------------------------------------------------------------------
input int   ExtDepth = 5;
input int   ExtDeviation = 5;
input int   ExtBackstep = 3;
//---------------------------------------------------------------------

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

#property indicator_buffers  3

Добавим два дополнительных буфера. В них будут храниться экстремумы, получаемые из внешнего индикатора ZigZag:

double ZigZagHighs[];  // верхние переломы зигзага
double ZigZagLows[];   // нижние переломы зигзага

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

//  Буферы для хранения переломов зигзага:
SetIndexBuffer(1, ZigZagHighs, INDICATOR_CALCULATIONS);
SetIndexBuffer(2, ZigZagLows, INDICATOR_CALCULATIONS);

В расчетной части кода функции OnCalculate также необходимо предусмотреть считывание переломов зигзага в наши буферы. Это сделано так:

//  Скопируем верхние и нижние переломы зигзага в буферы:
  CopyBuffer(indicator_handle, 1, 0, _rates_total - _prev_calculated, ZigZagHighs);
  CopyBuffer(indicator_handle, 2, 0, _rates_total - _prev_calculated, ZigZagLows);

//  Цикл расчета значений индикаторного буфера:
  for(i = start; i < _rates_total; i++)
  {
    TrendBuffer[i] = TrendDetector(i);
  }

Функция TrendDetector выглядит так:

//---------------------------------------------------------------------
//  Определяет направление текущего тренда:
//---------------------------------------------------------------------
//  Возвращает:
//    -1 - тренд вниз;
//    +1 - тренд вверх;
//     0 - тренд не определен;
//---------------------------------------------------------------------
double    ZigZagExtHigh[2];
double    ZigZagExtLow[2];
//---------------------------------------------------------------------
int TrendDetector(int _shift)
{
  int    trend_direction = 0;

//  Ищем последние четыре перелома зигзага:
  int    ext_high_count = 0;
  int    ext_low_count = 0;
  for(int i = _shift; i >= 0; i--)
  {
    if(ZigZagHighs[i] > 0.1)
    {
      if(ext_high_count < 2)
      {
        ZigZagExtHigh[ext_high_count] = ZigZagHighs[i];
        ext_high_count++;
      }
    }
    else if(ZigZagLows[i] > 0.1)
    {
      if(ext_low_count < 2)
      {
        ZigZagExtLow[ext_low_count] = ZigZagLows[i];
        ext_low_count++;
      }
    }

//  Если две пары экстремумов найдены, то цикл прерываем:
    if(ext_low_count == 2 && ext_high_count == 2)
    {
      break;
    }
  }

//  Если необходимое число экстремумов не найдено, то тренд определить не возможно:
  if(ext_low_count != 2 || ext_high_count != 2)
  {
    return(trend_direction);
  }

//  Проверим выполнение условий Доу:
  if(ZigZagExtHigh[0] > ZigZagExtHigh[1] && ZigZagExtLow[0] > ZigZagExtLow[1])
  {
    trend_direction = 1;
  }
  else if(ZigZagExtHigh[0] < ZigZagExtHigh[1] && ZigZagExtLow[0] < ZigZagExtLow[1])
  {
    trend_direction = -1;
  }

  return(trend_direction);
}

Здесь ищутся последние четыре экстремума зигзага. Особенность поиска в том, что поиск идет вглубь истории. Поэтому  индекс в цикле for уменьшается на каждой итерации поиска вплоть до нулевого значения. Если экстремумы находятся, то проводится их сравнение между собой на предмет соответствия определению тренда по Доу. Возможно два варианта расположения экстремумов - для повышательного тренда и для понижательного. Эти варианты проверяются операторами if...else.

3.4. Индикатор тренда, основанный на индикаторе ADX

Рассмотрим построение индикатора тренда ADXTrendDetector, использующего ADX. Полный текст индикатора находится в файле ADXTrendDetector.MQ5, который приложен к статье. Во внешних параметрах задаются значения для вызываемого внешнего индикатора ADX:

//---------------------------------------------------------------------
//      Внешние задаваемые параметры:
//---------------------------------------------------------------------
input int  PeriodADX     = 14;
input int  ADXTrendLevel = 20;

Функция TrendDetector выглядит так:

//---------------------------------------------------------------------
//  Определяет направление текущего тренда:
//---------------------------------------------------------------------
//  Возвращает:
//    -1 - тренд вниз;
//    +1 - тренд вверх;
//     0 - тренд не определен;
//---------------------------------------------------------------------
int TrendDetector(int _shift)
{
  int     trend_direction = 0;
  double  ADXBuffer[ 1 ];
  double  PlusDIBuffer[ 1 ];
  double  MinusDIBuffer[ 1 ];

//  Скопируем значения индикатора ADX в буферы:
  CopyBuffer(indicator_handle, 0, _shift, 1, ADXBuffer);
  CopyBuffer(indicator_handle, 1, _shift, 1, PlusDIBuffer);
  CopyBuffer(indicator_handle, 2, _shift, 1, MinusDIBuffer);

//  Если учитывается значение ADX ( сила тренда ):
  if(ADXTrendLevel > 0)
  {
    if(ADXBuffer[0] < ADXTrendLevel)
    {
      return(trend_direction);
    }
  }

//  Проверяем положение +-DI относительно друг друга:
  if(PlusDIBuffer[0] > MinusDIBuffer[0])
  {
    trend_direction = 1;
  }
  else if(PlusDIBuffer[0] < MinusDIBuffer[0])
  {
    trend_direction = -1;
  }

  return( trend_direction );
}

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

3.5. Индикатор тренда, основанный на индикаторе NRTR

Структура индикатора тренда NRTRTrendDetector, построенного на NRTR - аналогична предыдущему. Полный текст индикатора находится в файле NRTRTrendDetector.MQ5, который приложен к статье.

Первое отличие - в блоке задания внешних параметров:

//---------------------------------------------------------------------
//      Внешние задаваемые параметры:
//---------------------------------------------------------------------
input int     ATRPeriod =  40;    // Период ATR в барах
input double  Koeff     = 2.0;    // Коэффициент изменения значения ATR   
//---------------------------------------------------------------------

Второе отличие - в функции определения направления тренда TrendDetector:

//---------------------------------------------------------------------
//      Определяет направление текущего тренда:
//---------------------------------------------------------------------
//  Возвращает:
//    -1 - тренд вниз;
//    +1 - тренд вверх;
//     0 - тренд не определен;
//---------------------------------------------------------------------
int TrendDetector(int _shift)
{
  int     trend_direction = 0;
  double  Support[1];
  double  Resistance[1];

//      Скопируем значения индикатора NRTR в буферы:
  CopyBuffer(indicator_handle, 0, _shift, 1, Support);
  CopyBuffer(indicator_handle, 1, _shift, 1, Resistance);

//  Проверяем значения линий индикатора:
  if(Support[0] > 0.0 && Resistance[0] == 0.0)
  {
    trend_direction = 1;
  }
  else if(Resistance[0] > 0.0 && Support[0] == 0.0)
  {
    trend_direction = -1;
  }

  return( trend_direction );
}

Здесь мы считываем значения из двух буферов внешнего индикатора NRTR с индексами ноль и один. Значения в буфере Support отличны от нуля, когда тренд повышательный, а значения в буфере Resistance отличны от нуля, когда тренд понижательный.

3.6. Индикатор тренда, основанный на свечах Heiken Ashi

Рассмотрим теперь структуру индикатора тренда, использующего свечи Heiken Ashi.

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

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

Функция OnCalculate() теперь выглядит так:

int OnCalculate(const int _rates_total, 
                const int _prev_calculated,
              const datetime& Time[],
              const double& Open[],
              const double& High[],
              const double& Low[],
              const double& Close[],
              const long& TickVolume[],
              const long& Volume[], 
              const int& Spread[])
{
  int     start, i;
  double  open, close, ha_open, ha_close;

//  Определим начальный бар для расчета индикаторного буфера:
  if(_prev_calculated == 0)
  {
    open = Open[0];
    close = Close[0];
    start = 1;
  }
  else
  {
    start = _prev_calculated - 1;
  }

//  Цикл расчета значений индикаторного буфера:
  for(i = start; i < _rates_total; i++)
  {
//  Цена открытия свечи Heiken Ashi:
    ha_open = (open + close) / 2.0;

//  Цена закрытия свечи Heiken Ashi:
    ha_close = (Open[i] + High[i] + Low[i] + Close[i]) / 4.0;

    TrendBuffer[i] = TrendDetector(ha_open, ha_close);

    open = ha_open;
    close = ha_close;
  }

  return(_rates_total);
}

Поскольку для определения цвета свечи Heiken Ashi нужны только две цены - открытия и закрытия, то рассчитываем только их. 

После определения направления тренда через вызов функции TrendDetector, запоминаем текущие значения цен свечей Heiken Ashi в промежуточных переменных open и close. Сама функция TrendDetector выглядит очень просто. Вполне можно было вставить ее тело в обработчик OnCalculate, но, для большей универсальности в случае дальнейшего развития и усложнения алгоритма, мы оставляем эту функцию. Вот ее вид:

int TrendDetector(double _open, double _close)
{
  int    trend_direction = 0;

  if(_close > _open)         // если свеча растущая, то тренд вверх
  {
    trend_direction = 1;
  }
  else if(_close < _open)     // если свеча падающая, то тренд вниз
  {
    trend_direction = -1;
  }

  return(trend_direction);
}

На входе у функции две цены для свечи Heiken Ashi - открытия и закрытия, по которым определяется ее направление.


4. Пример использования индикатора тренда в эксперте

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

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

  • позиция на покупку открывается при смене направления тренда с нисходящего на восходящий или с неопределенного на восходящий;
  • позиция на продажу открывается при смене направления тренда с восходящего на нисходящий или с неопределенного на нисходящий;
  • позиция закрывается при изменении направления тренда на противоположное или на неопределенное;
  • эксперт должен открывать/закрывать позицию при открытии нового бара ( при наличии соответствующего сигнала );

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

Поскольку нам нужны торговые функции, то мы подключили библиотеку, поставляемую в составе MetaTrader 5. Данная библиотека содержит класс CTrade и несколько методов для работы с позициями и ордерами. Это упрощает рутинную работу с торговыми функциями. Библиотека подключается в следующей строке:

#include <Trade\Trade.mqh>

Из нее мы будем использовать два метода. Открытие позиции и закрытие позиции. Первый метод позволяет открыть позицию заданного направления и объема:

PositionOpen(const string symbol, 
               ENUM_ORDER_TYPE order_type,
             double volume, double price,
             double sl, double tp, const string comment )

Входные аргументы такие:

  • symbol - название инструмента для торговли, например, "EURUSD";
  • order_type - направление открытия позиции, шорт или лонг;
  • volume - объем открываемой позиции в лотах, например, 0.10;
  • price - цена открытия;
  • sl - цена стоп-лосса;
  • tp - цена тэйк-профита;
  • comment - комментарий, добавляемый при отображении позиции в торговом терминале;

Второй метод позволяет закрыть позицию:

PositionClose( const string symbol, ulong deviation )

Входные аргументы:

  •  symbol - название инструмента для торговли, например "EURUSD";
  •  deviation - максимально допустимое отклонение от текущей цены при закрытии позиции, в пунктах;

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

input double Lots = 0.1;
input int    MAPeriod = 200;

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

Второй важный блок эксперта - обработчик события инициализации эксперта.

//---------------------------------------------------------------------
//      Обработчик события инициализации:
//---------------------------------------------------------------------
int OnInit()
{
//  Создадим хэндл внешнего индикатора для дальнейшего обращения к нему:
  ResetLastError();
  indicator_handle = iCustom(Symbol(), PERIOD_CURRENT, "Examples\\MATrendDetector", MAPeriod);

// Если инициализация прошла неудачно, то возвратим ненулевой код:
  if(indicator_handle == INVALID_HANDLE)
  {
    Print("Ошибка инициализации MATrendDetector, Код = ", GetLastError());
    return(-1);
  }
  return(0);
}

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

Следующий блок эксперта - обработчик события деинициализации эксперта.

//---------------------------------------------------------------------
//      Обработчик события деинициализации:
//---------------------------------------------------------------------
void OnDeinit(const int _reason)
{
//  Удалим хэндл индикатора:
  if(indicator_handle != INVALID_HANDLE)
  {
    IndicatorRelease(indicator_handle);
  }
}

Здесь производится удаление хэндла индикатора с освобождением занимаемой им памяти.

Больше никаких действий по деинициализации производить не требуется.

Далее идет основной блок эксперта - обработчик события о поступлении нового тика по текущему символу.

//---------------------------------------------------------------------
//  Обработчик события о поступлении нового тика по текущему символу:
//---------------------------------------------------------------------
int    current_signal = 0;
int    prev_signal = 0;
bool   is_first_signal = true;
//---------------------------------------------------------------------
void OnTick()
{
//  Ждем начала нового бара:
  if(CheckNewBar() != 1)
  {
    return;
  }

//  Получим сигнал на открытие/закрытие позиции:
  current_signal = GetSignal();
  if(is_first_signal == true)
  {
    prev_signal = current_signal;
    is_first_signal = false;
  }

//  Выберем позицию по текущему символу:
  if(PositionSelect(Symbol()) == true)
  {
//  Проверим, не надо ли закрыть противоположную позицию:
    if(CheckPositionClose(current_signal) == 1)
    {
      return;
    }
  }

//  Проверяем наличие сигнала на BUY:
  if(CheckBuySignal(current_signal, prev_signal) == 1)
  {
    CTrade  trade;
    trade.PositionOpen(Symbol(), ORDER_TYPE_BUY, Lots, SymbolInfoDouble(Symbol(), SYMBOL_ASK ), 0, 0);
  }

//  Проверяем наличие сигнала на SELL:
  if(CheckSellSignal(current_signal, prev_signal) == 1)
  {
    CTrade  trade;
    trade.PositionOpen(Symbol(), ORDER_TYPE_SELL, Lots, SymbolInfoDouble(Symbol(), SYMBOL_BID ), 0, 0);
  }

//  Сохраним текущий сигнал:
  prev_signal = current_signal;
}

Рассмотрим вспомогательные функции, которые использует эксперт.

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

//---------------------------------------------------------------------
//  Возвращает признак появления нового бара:
//---------------------------------------------------------------------
//  - если возвращает 1, то есть новый  бар;
//---------------------------------------------------------------------
int CheckNewBar()
{
  MqlRates  current_rates[1];

  ResetLastError();
  if(CopyRates(Symbol(), Period(), 0, 1, current_rates)!= 1)
  {
    Print("Ошибка копирования CopyRates, Код = ", GetLastError());
    return(0);
  }

  if(current_rates[0].tick_volume>1)
  {
    return(0);
  }

  return(1);
}

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

В приведенной функции мы создаем массив current_rates[] структур типа MqlRates из одного элемента, копируем в него текущую информацию о ценах и объемах, затем проверяем значение тикового объема. 

В нашем обработчике события о поступлении нового тика по текущему символу мы используем эту функцию таким образом:

//  Ждем начала нового бара:
if(CheckNewBar()!= 1)
{
  return;
}

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

//  Получим сигнал на открытие/закрытие позиции:
  current_signal = GetSignal();
  if(is_first_signal == true)
  {
    prev_signal = current_signal;
    is_first_signal = false;
  }

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

Здесь используется функция GetSignal, которая возвращает направление текущего тренда, полученное из нашего индикатора. Выглядит она так:

//---------------------------------------------------------------------
//      Получение сигнала на открытие/закрытие позиции:
//---------------------------------------------------------------------
int GetSignal()
{
  double    trend_direction[1];

//  Получаем сигнал из индикатора тренда:
  ResetLastError();
  if(CopyBuffer(indicator_handle, 0, 0, 1, trend_direction) != 1)
  {
    Print("Ошибка копирования CopyBuffer, Код = ", GetLastError());
    return(0);
  }

  return((int)trend_direction[0]);
}

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

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

//  Выберем позицию по текущему символу:
  if(PositionSelect(Symbol()) == true)
  {
//  Проверим, не надо ли закрыть противоположную позицию:
    if(CheckPositionClose(current_signal) == 1)
    {
      return;
    }
  }

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

Для закрытия противоположной позиции используется функция CheckPositionClose:

//---------------------------------------------------------------------
//  Проверим, не надо ли закрыть позицию:
//---------------------------------------------------------------------
//  возвращает:
//    0 - открытой позиции нет;
//    1 - позиция уже открыта в направлении сигнала;
//---------------------------------------------------------------------
int CheсkPositionClose(int _signal)
{
  long    position_type = PositionGetInteger(POSITION_TYPE);

  if(_signal == 1)
  {
//  Если уже открыта позиция BUY, то возврат:
    if(position_type == (long)POSITION_TYPE_BUY)
    {
      return(1);
    }
  }

  if(_signal==-1)
  {
//  Если уже открыта позиция SELL, то возврат:
    if( position_type == ( long )POSITION_TYPE_SELL )
    {
      return(1);
    }
  }

//  Закрытие позиции:
  CTrade  trade;
  trade.PositionClose(Symbol(), 10);

  return(0);
}

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

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

//  Проверяем наличие сигнала на BUY:
if(CheckBuySignal(current_signal, prev_signal)==1)
{
  CTrade  trade;
  trade.PositionOpen(Symbol(), ORDER_TYPE_BUY, Lots, SymbolInfoDouble(Symbol(), SYMBOL_ASK), 0, 0);
}

Если есть сигнал покупки, то открывается длинная позиция с заданным нами объемом по текущей цене SYMBOL_ASK. Поскольку все позиции закрываются по противоположному сигналу, то тейк-профит и стоп-лосс не используется. Эксперт "всегда в рынке". 

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

Для сигналов продажи все аналогично:

//  Проверяем наличие сигнала на SELL:
if(CheckSellSignal(current_signal, prev_signal) == 1)
{
  CTrade  trade;
  trade.PositionOpen(Symbol(), ORDER_TYPE_SELL, Lots, SymbolInfoDouble(Symbol(), SYMBOL_BID), 0, 0);
}

Отличие только в цене продажи - SYMBOL_BID.

Наличие сигнала проверяется функциями CheckBuySignal - на покупку и CheckSellSignal - на продажу. Функции очень простые и понятные:

//---------------------------------------------------------------------
//  Проверка наличия сигнала на BUY:
//---------------------------------------------------------------------
//  возвращает:
//    0 - сигнала нет;
//    1 - есть сигнал на BUY;
//---------------------------------------------------------------------
int CheckBuySignal(int _curr_signal, int _prev_signal)
{
//  Проверим, было ли изменение направления сигнала на BUY:
  if((_curr_signal==1 && _prev_signal==0) || (_curr_signal==1 && _prev_signal==-1))
  {
    return(1);
  }

  return(0);
}

//---------------------------------------------------------------------
//  Проверка наличия сигнала на SELL:
//---------------------------------------------------------------------
//  возвращает:
//    0 - сигнала нет;
//    1 - есть сигнал на SELL;
//---------------------------------------------------------------------
int CheckSellSignal(int _curr_signal, int _prev_signal)
{
//  Проверим, было ли изменение направления сигнала на SELL:
  if((_curr_signal==-1 && _prev_signal==0) || (_curr_signal==-1 && _prev_signal==1))
  {
    return(1);
  }

  return(0);
}

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

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

Остальные эксперты построены совершенно так же. Существенные отличия только в блоке задания внешних параметров - они должны соответствовать используемому индикатору тренда и передаваться в качестве аргументов при создании хэндла индикатора.

Рассмотрим результаты работы нашего первого эксперта на исторических данных. Будем использовать историю EURUSD, в интервале 01.04.2004 г. - 06.08.2010 г., на дневных барах. После прогона эксперта в тестере с параметрами по умолчанию, получаем следующую картинку:

Рисунок 8. Результат тестирования эксперта на индикаторе MATrendDetector

Рисунок 8. Результат тестирования эксперта на индикаторе MATrendDetector

Отчет Тестера стратегий
MetaQuotes-Demo (Build 302)

Настройки
Советник: MATrendExpert
Символ: EURUSD
Период: Daily (2004.04.01 - 2010.08.06)
Входные параметры: Lots=0.100000

MAPeriod=200
Брокер: MetaQuotes Software Corp.
Валюта: USD
Начальный депозит: 10 000.00

Результаты
Бары: 1649 Тики: 8462551
Чистая прибыль: 3 624.59 Общая прибыль: 7 029.16 Общий убыток: -3 404.57
Прибыльность: 2.06 Матожидание выигрыша: 92.94
Фактор восстановления: 1.21 Коэффициент Шарпа: 0.14

Просадка баланса:
Абсолютная просадка по балансу: 2 822.83 Максимальная просадка по балансу: 2 822.83 (28.23%) Относительная просадка по балансу: 28.23% (2 822.83)
Просадка средств:
Абсолютная просадка по средствам: 2 903.68 Максимальная просадка по средствам: 2 989.93 (29.64%) Относительная просадка по средствам: 29.64% (2 989.93)

Всего сделок: 39 Короткие сделки (% выигравших): 20 (20.00%) Длинные сделки (% выигравших): 19 (15.79%)
Total Deals: 78 Прибыльные сделки (% от всех): 7 (17.95%) Убыточные сделки (% от всех): 32 (82.05%)

Самая большая прибыльная сделка: 3 184.14 Самая большая Убыточные сделки (% от всех): -226.65

Средняя прибыльная сделка: 1 004.17 Средняя Убыточные сделки (% от всех): -106.39

Максимальное количество непрерывных выигрышей (прибыль): 4 (5 892.18) Максимальное количество непрерывных проигрышей (убыток): 27 (-2 822.83)

Максимальная непрерывная прибыль (число выигрышей): 5 892.18 (4) Максимальная непрерывный убыток (число проигрышей): -2 822.83 (27)

Средняя непрерывный выигрыш: 2 Средняя непрерывный проигрыш: 8


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

Рисунок 9. Участок с боковым движением

Рисунок 9. Участок с боковым движением

Также на графике присутствует скользящая средняя SMA200.

Теперь посмотрим, что покажет более "продвинутый" эксперт на индикаторе с несколькими скользящими средними - на том же интервале и с параметрами по умолчанию:

 Рисунок 10. Результат тестирования эксперта на индикаторе FanTrendDetector

Рисунок 10. Результат тестирования эксперта на индикаторе FanTrendDetector

Отчет Тестера стратегий
MetaQuotes-Demo (Build 302)

Настройки
Советник: FanTrendExpert
Символ: EURUSD
Период: Daily (2004.04.01 - 2010.08.06)
Входные параметры: Lots=0.100000

MA1Period=200

MA2Period=50

MA3Period=21
Брокер: MetaQuotes Software Corp.
Валюта: USD
Начальный депозит: 10 000.00

Результаты
Бары: 1649 Тики: 8462551
Чистая прибыль: 2 839.63 Общая прибыль: 5 242.93 Общий убыток: -2 403.30
Прибыльность: 2.18 Матожидание выигрыша: 149.45
Фактор восстановления: 1.06 Коэффициент Шарпа: 0.32

Просадка баланса:
Абсолютная просадка по балансу: 105.20 Максимальная просадка по балансу: 1 473.65 (11.73%) Относительная просадка по балансу: 11.73% (1 473.65)
Просадка средств:
Абсолютная просадка по средствам: 207.05 Максимальная просадка по средствам: 2 671.98 (19.78%) Относительная просадка по средствам: 19.78% (2 671.98)

Всего сделок: 19 Короткие сделки (% выигравших): 8 (50.00%) Длинные сделки (% выигравших): 11 (63.64%)
Total Deals: 38 Прибыльные сделки (% от всех): 11 (57.89%) Убыточные сделки (% от всех): 8 (42.11%)

Самая большая прибыльная сделка: 1 128.30 Самая большая Убыточные сделки (% от всех): -830.20

Средняя прибыльная сделка: 476.63 Средняя Убыточные сделки (% от всех): -300.41

Максимальное количество непрерывных выигрышей (прибыль): 2 (1 747.78) Максимальное количество непрерывных проигрышей (убыток): 2 (-105.20)

Максимальная непрерывная прибыль (число выигрышей): 1 747.78 (2) Максимальная непрерывный убыток (число проигрышей): -830.20 (1)

Средняя непрерывный выигрыш: 2 Средняя непрерывный проигрыш: 1

Уже намного лучше. Если посмотреть наш "проблемный" участок, перед которым спасовал предыдущий эксперт, то картинка будет следующая:

 Рисунок 11. Результат работы FanTrendExpert на участке с боковым движением

Рисунок 11. Результат работы FanTrendExpert на участке с боковым движением

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


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

Проведем тестирование наших экспертов. Результаты работы на всем доступном в терминале интервале истории с 1993 г. по 2010 г. на паре EURUSD и таймфрейме D1 представлены ниже.

Рисунок 12. Тестирование эксперта MATrendExpert

Рисунок 12. Тестирование эксперта MATrendExpert


Рисунок 13. Тестирование эксперта FanTrendExpert

Рисунок 13. Тестирование эксперта FanTrendExpert


Рисунок 14. Тестирование эксперта ADXTrendExpert (ADXTrendLevel = 0)

Рисунок 14. Тестирование эксперта ADXTrendExpert (ADXTrendLevel = 0)


Рисунок 15. Тестирование эксперта ADXTrendExpert (ADXTrendLevel = 20)

Рисунок 15. Тестирование эксперта ADXTrendExpert (ADXTrendLevel = 20)


Рисунок 16. Тестирование эксперта NRTRTrendExpert

Рисунок 16. Тестирование эксперта NRTRTrendExpert


Рисунок 17. Тестирование эксперта Heiken Ashi

Рисунок 17. Тестирование эксперта Heiken Ashi

Рассмотрим получившиеся результаты тестирования.

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

Малое количество сделок у этих экспертов не является недостатком, так как время удержания позиции доходит до нескольких месяцев - следуем за 200-дневным трендом. Интересно отметить, как у эксперта MATrendExpert чередуются трендовые участки, где идет набор баланса, и флэтовые (в контексте данного эксперта), где идут потери средств.

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

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

Эксперт на Heiken Ashi оказался откровенно убыточным. Хотя на истории все выглядит красиво, видимо, из-за перерисовки в реальном времени, результаты тестирования далеки от идеала. Возможно, лучшие результаты даст использование сглаженной версии индикатора, Smoothed Heiken Ashi, которая не так подвержена перерисовке.

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


Заключение

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

Прикрепленные файлы |
experts.zip (10.4 KB)
indicators.zip (8.46 KB)
Последние комментарии | Перейти к обсуждению на форуме трейдеров (8)
sam
sam | 24 июл. 2011 в 18:04

Логика ZigZagTrendDetector не доработана, поэтому

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

 Начало тренда нужно показывать позже - при пробитии вершины/дна первого зигзага в направлении тренда.

Окончание тренда нужно показывать раньше - при пробитии последней вершины/дна последней коррекции тренда. 

Andrey Shpilev
Andrey Shpilev | 13 сент. 2011 в 00:29
На мой взгляд, есть изъяны не только в логике определения тренда по ЗигЗагу, но и по МАшкам: имхо, необходимо учитывать не только факт нахождения цены выше или ниже скользящего среднего, но и, хотя бы, направление линии МА...
iJSmile
iJSmile | 6 мар. 2015 в 14:22
Огромнейшее спасибо за проделанную работу!!! Отличная статья!!! 
Olexiy Polyakov
Olexiy Polyakov | 20 мар. 2015 в 23:22
Как то Вы забыли про то что по фракталам так же можно определять тренд (особенно на большем периоде)
AlikMsk
AlikMsk | 4 февр. 2017 в 16:33
в тестере ничего не работает и не отображается.
Применение функции TesterWithdrawal() для моделирования снятия прибыли Применение функции TesterWithdrawal() для моделирования снятия прибыли
В статье рассмотрено применение функции TesterWithDrawal() для оценки рисков в торговых системах, выполняющих снятие определенной части средств в процессе работы. Наряду с этим показано, как применение данной функции влияет на алгоритм расчета просадки по средствам в тестере. Использование данной функции может быть полезным при оптимизации параметров вашего советника.
Как создать свой Trailing Stop Как создать свой Trailing Stop
Основное правило трейдера - дай прибыли расти, обрезай убытки! В статье рассматривается один из основных технических приемов, позволяющий следовать этому правилу - перемещение уровня защитной остановки (уровня Stoploss) вслед за растущей прибылью позиции, другими словами - скользящий стоп или трейлинг стоп (trailingstop). Приводится пошаговая процедура создания класса для трейлинг стопа на индикаторах SAR и NRTR, который каждый желающий сможет за 5 минут встроить в своего эксперта или использовать независимо для управления позициями на своем счете.
Прототип торгового робота Прототип торгового робота
Целью данной статьи является обобщение и систематизация принципов создания алгоритмов и элементов торговых систем. В статье рассматривается вопрос проектирования алгоритма работы эксперта, в качестве примера представлен класс CExpertAdvisor, который может быть использован для быстрой и удобной разработки торговых систем.
Исследование паттернов (моделей) японских свечей Исследование паттернов (моделей) японских свечей
Построение графиков японских свечей и анализ свечных моделей — удивительное направление технического анализа. Преимущество японских свечей в том, что они представляют данные таким образом, что появляется возможность увидеть динамику внутри данных. В данной статье мы рассмотрим типы свечей, классификацию свечных моделей и напишем индикатор, распознающий свечные паттерны.