English 中文 Español Deutsch 日本語 Português
preview
Строим и оптимизируем торговую систему, основанную на объемах торгов (Chaikin Money Flow - CMF)

Строим и оптимизируем торговую систему, основанную на объемах торгов (Chaikin Money Flow - CMF)

MetaTrader 5Трейдинг |
713 5
Mohamed Abdelmaaboud
Mohamed Abdelmaaboud

Введение

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

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

Мы разработаем этот новый индикатор (CMF) в соответствии со следующими разделами:

  1. Денежный поток Чайкина: получите базовые знания об этом техническом инструменте, определив его, как его можно рассчитать и как его можно использовать.
  2. Пользовательский индикатор Денежный поток Чайкина: узнайте, как запрограммировать наш пользовательский индикатор путем изменения или применения наших предпочтений.
  3. Стратегии Денежного потока Чайкина: мы рассмотрим несколько простых торговых стратегий, которые являются частью нашей торговой системы.
  4. Торговая система Денежный поток Чайкина: создавайте, тестируйте и оптимизируйте эти простые торговые системы.
  5. Заключение

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



Денежный поток Чайкина

Денежный поток Чайкина (CMF) это технический индикатор, основанный на объеме и учитывающий движение цены. Как мы увидим, его можно использовать отдельно или в сочетании с другими инструментами для получения более подробной информации. CMF - это индикатор, разработанный Марком Чайкиным (Marc Chaikin) для отслеживания накопления и распределения инструмента в течение определенного периода времени. Основная идея CMF заключается в том, что по мере приближения цены закрытия к максимуму происходит накопление. С другой стороны, когда цена закрытия приближается к минимуму, это признак распределения. Положительный результат Денежного потока Чайкина достигается, когда движение цены последовательно закрывается выше средней точки бара при растущем объеме. Отрицательный результат Денежного потока Чайкина достигается, когда движение цены последовательно закрывается ниже средней точки бара при растущем объеме.

Ниже приведены шаги, на которых рассчитывается показатель CMF:

  • Расчет множителя денежного потока
Множитель денежного потока = [(Close - Low) - (High - Close)] / (High - Low)
  • Расчет объёма денежного потока

Объём денежного потока = множитель денежного потока * объём за период

  • Расчет CMF

CMF за период n = сумма объёма денежного потока за период n / сумма объёма за период n

После расчета индикатор покажет диапазон от +1 до -1, и будет выглядеть так же, как на следующем рисунке:

cmfInd

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

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


Пользовательский индикатор Денежный поток Чайкина

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

Ниже приведены шаги, которые мы выполним для создания кода этого настраиваемого индикатора:

Указание дополнительных параметров рядом с #property,  чтобы  определить приведенные ниже значения для поведения и внешнего вида индикатора

  • Описание индикатора, используя  константу описания и указав  "Денежный поток Чайкина".
#property description "Chaikin Money Flow"
  • Размещение индикатора в виде отдельного окна на графике с помощью константы (indicator_separate_window).
#property indicator_separate_window
  • Количество буферов для расчета индикатора, используя константу (indicator_buffers) , мы устанавливаем ее равной 4.
#property indicator_buffers 4
  • Количество графических рядов в индикаторе, используя константу (indicator_plots), мы устанавливаем ее равной 1.
#property indicator_plots   1
  • Толщина линии в графическом ряду с помощью константы (indicator_width1),  где 1 - номер графического ряда, мы устанавливаем ее равной 3.
#property indicator_width1  3
  • Горизонтальные уровни 1,2 и 3 в отдельном окне индикатора, используя константы (indicator_level1),  (indicator_level2 ) и  (indicator_level3) для уровня (0), (0,20) и (-0,20).
#property indicator_level1  0
#property indicator_level2  0.20
#property indicator_level3  -0.20
  • Стиль горизонтальных уровней индикатора с помощью (indicator_levelstyle), здесь установим значение STYLE_DOT
#property indicator_levelstyle STYLE_DOT
  • Толщина горизонтальных уровней индикатора с помощью (indicator_levelwidth), здесь установим значение 0
#property indicator_levelwidth 0
  • Цвет горизонтальных уровней индикатора с помощью (indicator_levelcolor), установим значение clrBlack
#property indicator_levelcolor clrBlack
  • Тип графического построения с помощью (indicator_type1), задаем как DRAW_HISTOGRAM
#property indicator_type1   DRAW_HISTOGRAM
  • Цвет отображения строки N выбирается с помощью (indicator_color1), здесь мы используем clrBlue
#property indicator_color1  clrBlue
  • Указание входных данных для настройки пользовательских предпочтений в отношении периодов и типов объемов, которые будут использоваться при расчете индикатора с помощью функции ввода
input int                 periods=20; // Periods
input ENUM_APPLIED_VOLUME volumeTypeInp=VOLUME_TICK;  // Volume Type
  • Объявление массива cmfBuffer
double                    cmfBuffer[];

В функции OnInit() для настройки индикатора,

Связывание одномерного динамического массива типа double с индикаторным буфером CMF с помощью (SetIndexBuffer), а его параметрами являются:

  • index: чтобы указать индекс буфера, который будет равен 0
  • buffer[]: чтобы указать массив, который будет являться cmfBuffer 
  • data_type: чтобы указать тип данных, хранящихся в массиве индикаторов.
SetIndexBuffer(0,cmfBuffer,INDICATOR_DATA);

Установим значение соответствующего свойства индикатора с помощью функции (IndicatorSetInteger), а её параметрами являются:

  • prop_id: чтобы определить идентификатор, который будет (INDICATOR_DIGITS).
  • prop_value: чтобы определить десятичное значение, которое нужно установить, оно будет равно (5).
IndicatorSetInteger(INDICATOR_DIGITS,5);

Установим, когда начнется построение чертежа, с помощью функции (PlotIndexSetInteger) с такими параметрами:

  • plot_index: чтобы указать индекс стиля графического вывода, который будет равен (0).
  • prop_id: чтобы указать идентификатор свойства, который будет (PLOT_DRAW_BEGIN) в качестве одного из перечислений  ENUM_PLOT_PROPERTY_INTEGER.
  • prop_value: чтобы указать значение, устанавливаемое в качестве свойства, оно будет равно (0).
PlotIndexSetInteger(0,PLOT_DRAW_BEGIN,0);

Укажем название и период индикатора с помощью функции (IndicatorSetString) с такими параметрами:

  • prop_id: чтобы указать идентификатор, который может быть одним из перечислений ENUM_CUSTOMIND_PROPERTY_STRING, а также указать (INDICATOR_SHORTNAME).
  • prop_value: укажем текстовое значение индикатора, которое будет выглядеть следующим образом: ("Chaikin Money Flow("+string(periods)+")").
IndicatorSetString(INDICATOR_SHORTNAME,"Chaikin Money Flow("+string(periods)+")");

Раздел  OnCalculate для вычисления значения CMF

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 &tick_volume[],
                const long &volume[],
                const int &spread[])

Проверим наличие данных для расчета значений CMF, используя функцию if

   if(rates_total<periods)
      return(0);

Когда у меня будут данные и я предварительно рассчитаю CM, я не начну вычислять текущее значение

   int initPos = prev_calculated -1;
   if(initPos<0) initPos = 0;

Рассчитаем CMF, выполнив следующие действия

  • Выберем тип объема.
  • Цикл для вычисления значения CMF для каждого бара.
  • Объявляем sumAccDis, sumVol.
  • Цикл для вычисления объявленных переменных (sumAccDis, sumVol) после объявления и определения переменной (thisTickVolume).
  • Сохраним результат в буфере cmfBuffer.
  • Возвращаем рассчитанные значения.
   if(volumeTypeInp==VOLUME_TICK)
   {
      for(int pos = initPos;pos<=rates_total-periods;pos++)
      {
         double sumAccDis = 0;
         long sumVol = 0;
         
         for(int i = 0; i < periods && !IsStopped(); ++i)
         {
            long thisTickVolume = tick_volume[pos+i];
            sumVol += thisTickVolume;
            sumAccDis += AccDis(high[pos+i], low[pos+i], close[pos+i], thisTickVolume);
         }
         
         cmfBuffer[pos+periods-1] = sumAccDis/sumVol;
      }
   }
   else
   {
      for(int pos = initPos;pos<=rates_total-periods;pos++)
      {
         double sumAccDis = 0;
         long sumVol = 0;
         
         for(int i = 0; i < periods && !IsStopped(); ++i)
         {
            long thisTickVolume = volume[pos+i];
            sumVol += thisTickVolume;
            sumAccDis += AccDis(high[pos+i], low[pos+i], close[pos+i], thisTickVolume);
         }
         
         cmfBuffer[pos+periods-1] = sumAccDis/sumVol;
      }
   }
   
   return (rates_total-periods-10);

Объявляем, что функция (AccDis), которая использовалась в коде, содержит 4 переменные (high, low, close и volume), которые помогают нам рассчитать Множитель денежного потока и Объем денежного потока для бара, которые используются для расчета CMF.

double AccDis(double high,double low,double close,long volume)
{
   double res=0;
   
   if(high!=low)
      res=(2*close-high-low)/(high-low)*volume;
   
   return(res);
}

Теперь мы завершили разработку нашего кода для создания пользовательского индикатора CMF, полный код в одном блоке можно найти ниже:

#property description "Chaikin Money Flow"
#property indicator_separate_window
#property indicator_buffers 4
#property indicator_plots   1
#property indicator_width1  3
#property indicator_level1  0
#property indicator_level2  0.20
#property indicator_level3  -0.20
#property indicator_levelstyle STYLE_DOT
#property indicator_levelwidth 0
#property indicator_levelcolor clrBlack
#property indicator_type1   DRAW_HISTOGRAM
#property indicator_color1  clrBlue
input int                 periods=20; // Periods
input ENUM_APPLIED_VOLUME volumeTypeInp=VOLUME_TICK;  // Volume Type
double                    cmfBuffer[];
void OnInit()
{
   SetIndexBuffer(0,cmfBuffer,INDICATOR_DATA);
   IndicatorSetInteger(INDICATOR_DIGITS,5);
   PlotIndexSetInteger(0,PLOT_DRAW_BEGIN,0);
   IndicatorSetString(INDICATOR_SHORTNAME,"Chaikin Money Flow("+string(periods)+")");
}
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 &tick_volume[],
                const long &volume[],
                const int &spread[])
{
   if(rates_total<periods)
      return(0);
   int initPos = prev_calculated -1;
   if(initPos<0) initPos = 0;
   if(volumeTypeInp==VOLUME_TICK)
   {
      for(int pos = initPos;pos<=rates_total-periods;pos++)
      {
         double sumAccDis = 0;
         long sumVol = 0;
         
         for(int i = 0; i < periods && !IsStopped(); ++i)
         {
            long thisTickVolume = tick_volume[pos+i];
            sumVol += thisTickVolume;
            sumAccDis += AccDis(high[pos+i], low[pos+i], close[pos+i], thisTickVolume);
         }
         cmfBuffer[pos+periods-1] = sumAccDis/sumVol;
      }
   }
   else
   {
      for(int pos = initPos;pos<=rates_total-periods;pos++)
      {
         double sumAccDis = 0;
         long sumVol = 0;
         
         for(int i = 0; i < periods && !IsStopped(); ++i)
         {
            long thisTickVolume = volume[pos+i];
            sumVol += thisTickVolume;
            sumAccDis += AccDis(high[pos+i], low[pos+i], close[pos+i], thisTickVolume);
         }
         
         cmfBuffer[pos+periods-1] = sumAccDis/sumVol;
      }
   }
   return (rates_total-periods-10);
}
double AccDis(double high,double low,double close,long volume)
{
   double res=0;
   
   if(high!=low)
      res=(2*close-high-low)/(high-low)*volume;
   
   return(res);
}

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

CMFInd

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


Стратегии индикатора Денежный поток Чайкина

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

Будем использовать следующие три простые стратегии:

  • Стратегия пересечение нуля CMF
  • Стратегия перекупленности и перепроданности CMF
  • Стратегия подтверждения тренда CMF

Стратегия пересечение нуля CMF:

Эта стратегия довольно проста. Она использует значение индикатора CMF для определения того, когда следует покупать или продавать. Если предыдущее значение CMF отрицательное, а текущее или последнее значение положительное, это сигнал на покупку. Обратное верно, когда предыдущее значение CMF положительное, а текущее или последнее значение - отрицательное.

Имеем следующее:

Previous CMF < 0 and Current CMF > 0 --> Buy

Previous CMF > 0 and Current CMF < 0 --> Sell 

Стратегия перекупленности и перепроданности CMF:

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

Имеем следующее:

CMF <= -0.20 --> Buy

CMF >= 0.20 --> Sell

Стратегия подтверждения тренда CMF:

У этой стратегии есть и другой подход, так как при получении ее сигналов мы будем комбинировать другой индикатор, который может подтвердить тренд или, по крайней мере, движение цен вверх или вниз, с получением сигнала от пересечения нуля. Мы будем комбинировать использование скользящей средней с индикатором CMF. Когда предыдущая цена закрытия ниже предыдущего значения скользящей средней и в то же время текущая цена ask выше скользящей средней, а текущее значение CMF выше нуля, срабатывает сигнал на покупку, и наоборот. Когда  предыдущая цена закрытия выше предыдущего значения скользящей средней и в то же время текущая цена bid ниже скользящей средней, а текущее значение CMF ниже нуля, срабатывает сигнал на продажу.

Имеем следующее:

prevClose < prevMA, ask > MA, and CMF > 0 --> Buy

prevClose > prevMA, bid < MA, and CMF < 0 --> Sell

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


Торговая система Денежный поток Чайкина

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

Вот что мы будем делать дальше с этим простым советником:

Зададим входные параметры советника, чтобы настроить желаемые периоды и тип объема, которые будут использоваться в функции вызова индикатора

input int                 periods=20; // Periods
input ENUM_APPLIED_VOLUME volumeTypeInp=VOLUME_TICK;  // Volume Type

Объявление целочисленной переменной для cmf

int cmf;

В событии OnInit() мы инициализируем наш пользовательский индикатор CMF, вызвав его с помощью функции iCustom со следующими параметрами:

  • Symbol: Здесь вы введёте название символа в виде строки. Это будет (_Symbol), применяемый к текущему символу.
  • Period: Здесь вы введёте период в виде ENUM_TIMEFRAMES. Это будет PERIOD_CURRENT при котором будет применен текущий таймфрейм.
  • Name: Здесь вы введете имя пользовательского индикатора, который вы вызываете, с правильным путем на вашем локальном компьютере, поэтому надо будет ввести folder/custom_indicator_name. Это будет  "Chaikin_Money_Flow".
  • ...: Здесь вы введете список входных параметров индикатора, если он существует. Это будут периоды и входные данные volumeTypeInp.

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

int OnInit()
  {
   cmf = iCustom(_Symbol,PERIOD_CURRENT,"Chaikin_Money_Flow",periods,volumeTypeInp);
   return(INIT_SUCCEEDED);
  }

В событии OnDeinit() мы укажем, что сообщение советника удаляется при возникновении события деинициализации.

void OnDeinit(const int reason)
  {
   Print("EA is removed");
  }

В событии OnTick() мы объявим массив для cmfInd[] как тип данных double

double cmfInd[];

Получим данные из указанного буфера индикатора cmf с помощью функции CopyBuffer. Параметры:

  • indicator_handle: чтобы указать хэндл double (cmf), который был объявлен ранее. 
  • buffer_num: чтобы указать номер буфера cmf (0). 
  • start_pos: чтобы указать начальную позицию (0).
  • count: чтобы указать количество, которое будет скопировано (3).
  • buffer[]:  чтобы указать копируемый массив cmfInd[].
CopyBuffer(cmf,0,0,3,cmfInd);

установите для флага AS_SERIES значение cmfInd[], чтобы проиндексировать элемент во временном ряду. Можно использовать array[] и флаг, который будет равен true в случае успеха, в качестве параметров.

ArraySetAsSeries(cmfInd,true);

Определим двойную переменную для текущего значения CMF и нормализуем ее с помощью ограничения в 5 знаков после запятой, вы можете определить ее.

double cmfVal = NormalizeDouble(cmfInd[0], 5);

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

Comment("CMF value = ",cmfVal);

Полный код в одном блоке представлен ниже.

//+------------------------------------------------------------------+
//|                                                      CMF_Val.mq5 |
//+------------------------------------------------------------------+
input int                 periods=20; // Periods
input ENUM_APPLIED_VOLUME volumeTypeInp=VOLUME_TICK;  // Volume Type
int cmf;
int OnInit()
  {
   cmf = iCustom(_Symbol,PERIOD_CURRENT,"Chaikin_Money_Flow",periods,volumeTypeInp);
   return(INIT_SUCCEEDED);
  }
void OnDeinit(const int reason)
  {
   Print("EA is removed");
  }
void OnTick()
  {
    double cmfInd[];
    CopyBuffer(cmf,0,0,3,cmfInd);
    ArraySetAsSeries(cmfInd,true);
    double cmfVal = NormalizeDouble(cmfInd[0], 5);
    Comment("CMF value = ",cmfVal);
  }

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

CMF_Val

Как мы видим, значение CMF на графике такое же, как и на вставленном индикаторе (0.15904). Мы можем использовать это в качестве начальной точки для других советников и стратегий.

Стратегия пересечение нуля CMF:

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

Вот полный код, который выполнит эту работу:
//+------------------------------------------------------------------+
//|                                           CMF_Zero_Crossover.mq5 |
//+------------------------------------------------------------------+
#include <trade/trade.mqh>
input int                 periods=20; // Periods
input ENUM_APPLIED_VOLUME volumeTypeInp=VOLUME_TICK;  // Volume Type
input double cmfPosLvls = 0.20; // CMF OB Level
input double cmfNegLvls = -0.20; // CMF OS Level
input int maPeriodInp=20; //MA Period
input double      lotSize=1;
input double      slLvl=300;
input double      tpLvl=900;
int cmf;
CTrade trade;
int barsTotal;
int OnInit()
  {
   barsTotal=iBars(_Symbol,PERIOD_CURRENT);
   cmf = iCustom(_Symbol,PERIOD_CURRENT,"Chaikin_Money_Flow",maPeriodInp,volumeTypeInp);
   return(INIT_SUCCEEDED);
  }
void OnDeinit(const int reason)
  {
   Print("EA is removed");
  }
void OnTick()
  {
   int bars=iBars(_Symbol,PERIOD_CURRENT);
   if(barsTotal != bars)
     {
      barsTotal=bars;
      double cmfInd[];
      CopyBuffer(cmf,0,0,3,cmfInd);
      ArraySetAsSeries(cmfInd,true);
      double cmfVal = NormalizeDouble(cmfInd[0], 5);
      double cmfPreVal = NormalizeDouble(cmfInd[1], 5);
      double ask = SymbolInfoDouble(_Symbol,SYMBOL_ASK);
      double bid = SymbolInfoDouble(_Symbol,SYMBOL_BID);
      if(cmfPreVal<0 && cmfVal>0)
        {
         double slVal=ask - slLvl*_Point;
         double tpVal=ask + tpLvl*_Point;
         trade.Buy(lotSize,_Symbol,ask,slVal,tpVal);
        }
      if(cmfPreVal>0 && cmfVal<0)
        {
         double slVal=bid + slLvl*_Point;
         double tpVal=bid - tpLvl*_Point;
         trade.Sell(lotSize,_Symbol,bid,slVal,tpVal);
	}
     }
  }
//+------------------------------------------------------------------+

Просто чтобы вы знали, в этом коде есть различия:

Включен торговый файл для вызова торговых функций

#include <trade/trade.mqh>

Добавим дополнительные исходные данные, определяемые пользователем, cmfPosLvls для уровня перепроданности, cmfNegLvls для уровня перекупленности, размер лота, стоп-лосс, тейк-профит

input double cmfPosLvls = 0.20; // CMF OB Level
input double cmfNegLvls = -0.20; // CMF OS Level
input double      lotSize=1;
input double      slLvl=300;
input double      tpLvl=900;

Объявление торговых объектов, целочисленной переменной barsTotal

CTrade trade;
int barsTotal;

В событии OnTick() нам просто нужно проверить, есть ли новый бар, чтобы код продолжал работать. Мы можем это сделать, используя условие if после того, как мы определили целочисленную переменную bars.

int bars=iBars(_Symbol,PERIOD_CURRENT);
if(barsTotal != bars)

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

Обновляем значение barsTotal, чтобы оно стало равным значению bars

barsTotal=bars;

Объявление предыдущего значения cmf

double cmfPreVal = NormalizeDouble(cmfInd[1], 5);

Объявление двойных переменных ask и bid

double ask = SymbolInfoDouble(_Symbol,SYMBOL_ASK);
double bid = SymbolInfoDouble(_Symbol,SYMBOL_BID);

Установим условие покупки, если (cmfPreVal<0 и cmfVal>0), нам нужно объявить SL, TP и разместить ордер на покупку

      if(cmfPreVal<0 && cmfVal>0)
        {
         double slVal=ask - slLvl*_Point;
         double tpVal=ask + tpLvl*_Point;
         trade.Buy(lotSize,_Symbol,ask,slVal,tpVal);
        }

Установим условие продажи, если (cmfPreVal>0 и cmfVal<0), нам нужно объявить SL, TP и разместить ордер на продажу

      if(cmfPreVal>0 && cmfVal<0)
        {
         double slVal=bid + slLvl*_Point;
         double tpVal=bid - tpLvl*_Point;
         trade.Sell(lotSize,_Symbol,bid,slVal,tpVal);
        }

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

Позиция на покупку:

buy

Позиция на продажу:

sell

Теперь нам нужно протестировать все стратегии по паре EURUSD на год, с 1.01.2023 по 31.12.2023. Мы собираемся использовать 300 точек для SL и 900 для TP. Наш основной подход к оптимизации заключается в тестировании другой концепции или добавлении другого инструмента, такого как скользящая средняя. Вы также можете протестировать другие таймфреймы, чтобы понять, какой из них будет работать лучше. Этот тип оптимизации также стоит попробовать.

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

  • Чистая прибыль (Net Profit) — рассчитывается путем вычитания валового убытка из валовой прибыли. Чем выше значение, тем лучше.
  • Относительная просадка по балансу (Balance DD relative) — максимальный убыток на счете во время работы. Чем ниже значение, тем лучше.
  • Коэффициент прибыльности: Это отношение валовой прибыли к валовому убытку. Чем выше значение, тем лучше.
  • Матожидание выигрыша: Это средняя прибыль или убыток сделки. Чем выше значение, тем лучше.
  • Фактор восстановления (Recovery factor) — насколько хорошо протестированная стратегия восстанавливается после потерь. Чем выше значение, тем лучше.
  • Коэффициент Шарпа: Он определяет риск и стабильность протестированной торговой системы, сравнивая доходность с безрисковой доходностью. Чем выше значение коэффициента Шарпа, тем лучше.

Результаты 15-минутного теста на таймфрейме будут такими же, как показано ниже:

15min-Backtest1

 15min-Backtest2

15min-Backtest3

По результатам 15-минутных тестов можно определить следующие важные значения для тестовых чисел:

  • Чистая прибыль: 29019.10 USD.
  • Относительная просадка по балансу 23%.
  • Коэффициент прибыльности: 1.09.
  • Матожидание выигрыша: 19.21.
  • Фактор восстановления: 0.88.
  • Коэффициент Шарпа: 0.80.

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

Стратегия перекупленности и перепроданности CMF:

Как мы уже говорили, нам нужно запрограммировать эту стратегию, чтобы советник автоматически выставлял ордера на покупку и продажу на основе приближающихся зон перекупленности и перепроданности. Области, которые мы рассматриваем, составляют 0,20 для перекупленности и -0,20 для перепроданности. Итак, нам нужно, чтобы советник следил за каждым значением CMF и сравнивал его с этими областями. Если CMF равен или ниже -0,20, советник должен выставить ордер на покупку. Если значение CMF равно или превышает 0,20, советник должен выставить ордер на продажу.

Вы можете ознакомиться с шагами создания этой стратегии или советника в следующем полном коде:

//+------------------------------------------------------------------+
//|                                                 CMF_MA_OB&OS.mq5 |
//+------------------------------------------------------------------+
#include <trade/trade.mqh>
input int                 periods=20; // Periods
input ENUM_APPLIED_VOLUME volumeTypeInp=VOLUME_TICK;  // Volume Type
input double cmfPosLvls = 0.20; // CMF OB Level
input double cmfNegLvls = -0.20; // CMF OS Level
input double      lotSize=1;
input double      slLvl=300;
input double      tpLvl=900;
int cmf;
CTrade trade;
int barsTotal;
int OnInit()
  {
   barsTotal=iBars(_Symbol,PERIOD_CURRENT);
   cmf = iCustom(_Symbol,PERIOD_CURRENT,"Chaikin_Money_Flow",periods,volumeTypeInp);
   return(INIT_SUCCEEDED);
  }
void OnDeinit(const int reason)
  {
   Print("EA is removed");
  }
void OnTick()
  {
   int bars=iBars(_Symbol,PERIOD_CURRENT);
   if(barsTotal != bars)
     {
      barsTotal=bars;
      double cmfInd[];
      CopyBuffer(cmf,0,0,3,cmfInd);
      ArraySetAsSeries(cmfInd,true);
      double cmfVal = NormalizeDouble(cmfInd[0], 5);
      double cmfPreVal = NormalizeDouble(cmfInd[1], 5);
      double ask = SymbolInfoDouble(_Symbol,SYMBOL_ASK);
      double bid = SymbolInfoDouble(_Symbol,SYMBOL_BID);
      if(cmfVal<=cmfNegLvls)
        {
         double slVal=ask - slLvl*_Point;
         double tpVal=ask + tpLvl*_Point;
         trade.Buy(lotSize,_Symbol,ask,slVal,tpVal);
        }
      if(cmfVal>=cmfPosLvls)
        {
         double slVal=bid + slLvl*_Point;
         double tpVal=bid - tpLvl*_Point;
         trade.Sell(lotSize,_Symbol,bid,slVal,tpVal);
        }
     }
  }
//+------------------------------------------------------------------+

Различия в этом коде будут такими же, как показано ниже:

Установите два параметра для зон перекупленности и перепроданности, в зависимости от того, чего хочет пользователь. На данный момент используем значения по умолчанию (0.20 и -0.20).

input double cmfPosLvls = 0.20; // CMF OB Level
input double cmfNegLvls = -0.20; // CMF OS Level

Условия стратегии

Открываем позицию на покупку, когда значение CMF будет меньше или равно значению  cmfNegLvls

      if(cmfVal<=cmfNegLvls)
        {
         double slVal=ask - slLvl*_Point;
         double tpVal=ask + tpLvl*_Point;
         trade.Buy(lotSize,_Symbol,ask,slVal,tpVal);
        }

Открываем позицию на продажу, когда значение CMF больше или равно значению  cmfPosLvls

      if(cmfVal>=cmfPosLvls)
        {
         double slVal=bid + slLvl*_Point;
         double tpVal=bid - tpLvl*_Point;
         trade.Sell(lotSize,_Symbol,bid,slVal,tpVal);
        }

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

Позиция на покупку:

buy

Позиция на продажу:

sell

Мы собираемся протестировать эту стратегию, используя тот же подход, который мы использовали для предыдущего тестирования стратегии пересечения нуля CMF. Мы будем тестировать EURUSD с 01.01.2023 по 31.12.2023 на 15-минутном таймфрейме с 300 пунктами для SL и 900 для TP. На следующих рисунках показаны результаты этого тестирования:

15min-Backtest1

 15min-Backtest2

15min-Backtest3

По результатам 15-минутных тестов можно определить следующие важные значения для тестовых чисел:

  • Чистая прибыль: 58029.90 USD.
  • Относительная просадка по балансу: 55.60%.
  • Коэффициент прибыльности: 1.06.
  • Матожидание выигрыша: 14.15.
  • Фактор восстановления: 0.62.
  • Коэффициент Шарпа: 0.69.

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

Стратегия подтверждения тренда CMF:

Как мы уже говорили, нужно добавить или объединить другой технический индикатор, скользящую среднюю, с пересечением нуля CMF, чтобы подтвердить движение в обоих направлениях (вверх и вниз). Советник должен проверять каждую цену закрытия, ask и CMF. Это значение используется для определения положения каждого из них относительно других. Когда последняя цена закрытия ниже последнего значения скользящей средней, текущая цена ask выше текущей скользящей средней, а значение CMF выше нуля, нам нужно разместить ордер на покупку. С другой стороны, когда последняя цена закрытия выше последнего значения скользящей средней, текущая цена bid ниже текущей скользящей средней, а значение CMF ниже нуля, нужно размещать ордер на продажу.

Ниже приведен полный код этого советника:

//+------------------------------------------------------------------+
//|                                          CMF_trendValidation.mq5 |
//+------------------------------------------------------------------+
#include <trade/trade.mqh>
input int                 periods=20; // Periods
input ENUM_APPLIED_VOLUME volumeTypeInp=VOLUME_TICK;  // Volume Type
input int maPeriodInp=20; //MA Period
input double      lotSize=1;
input double      slLvl=300;
input double      tpLvl=900;
int cmf;
int ma;
CTrade trade;
int barsTotal;
int OnInit()
  {
   barsTotal=iBars(_Symbol,PERIOD_CURRENT);
   cmf = iCustom(_Symbol,PERIOD_CURRENT,"Chaikin_Money_Flow",periods,volumeTypeInp);
   ma = iMA(_Symbol,PERIOD_CURRENT,maPeriodInp,0,MODE_SMA,PRICE_CLOSE);
   return(INIT_SUCCEEDED);
  }
void OnDeinit(const int reason)
  {
   Print("EA is removed");
  }
void OnTick()
  {
   int bars=iBars(_Symbol,PERIOD_CURRENT);
   if(barsTotal != bars)
     {
      barsTotal=bars;
      double cmfInd[];
      double maInd[];
      CopyBuffer(cmf,0,0,3,cmfInd);
      CopyBuffer(ma,0,0,3,maInd);
      ArraySetAsSeries(cmfInd,true);
      ArraySetAsSeries(maInd,true);
      double cmfVal = NormalizeDouble(cmfInd[0], 5);
      double maVal= NormalizeDouble(maInd[0],5);
      double cmfPreVal = NormalizeDouble(cmfInd[1], 5);
      double maPreVal = NormalizeDouble(maInd[1],5);;
      double ask = SymbolInfoDouble(_Symbol,SYMBOL_ASK);
      double bid = SymbolInfoDouble(_Symbol,SYMBOL_BID);
      double prevClose = iClose(_Symbol,PERIOD_CURRENT,1);
      if(prevClose<maPreVal && ask>maVal)
        {
         if(cmfVal>0)
           {
            double slVal=ask - slLvl*_Point;
            double tpVal=ask + tpLvl*_Point;
            trade.Buy(lotSize,_Symbol,ask,slVal,tpVal);
           }
        }
      if(prevClose>maPreVal && bid<maVal)
        {
         if(cmfVal<0)
           {
            double slVal=bid + slLvl*_Point;
            double tpVal=bid - tpLvl*_Point;
            trade.Sell(lotSize,_Symbol,bid,slVal,tpVal);
           }
        }
     }
  }
//+------------------------------------------------------------------+

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

Добавим еще один ввод, позволяющий пользователю определить период используемой скользящей средней

input int maPeriodInp=20; //MA Period

Объявление целочисленной переменной для скользящей средней

int ma;

Определим переменную, созданную скользящей средней, с помощью функции iMA, ее параметрами являются:

  • symbol: чтобы задать название символа, оно будет (_Symbol) и применено на текущем символе. 
  • period: установить период скользящей средней, который будет (PERIOD_CURRENT) применяться на текущем таймфрейме.
  • ma_period: установить период усреднения, пользователь должен ввести значение (maPeriodInp).
  • ma_shift: установить горизонтальный сдвиг при необходимости.
  • ma_method: указать тип сглаживания или тип скользящей средней, это будет (MODE_SMA) для использования простой скользящей средней.
  • applied_price: указать тип цены, это будет (PRICE_CLOSE).
ma = iMA(_Symbol,PERIOD_CURRENT,maPeriodInp,0,MODE_SMA,PRICE_CLOSE);

Объявляем массив maInd[]

double maInd[];

Получим данные из указанного буфера индикатора МА с помощью функции CopyBuffer.

CopyBuffer(ma,0,0,3,maInd);

Установим для флага AS_SERIES значение maInd[], чтобы проиндексировать элемент во временном ряду.

ArraySetAsSeries(maInd,true);

Определяем текущее и предыдущее значение скользящей средней

double maVal= NormalizeDouble(maInd[0],5);
double prevClose = iClose(_Symbol,PERIOD_CURRENT,1);

Условия позиции на покупку, prevClose<maPreVal, ask>maVal, и cmfVal>0

      if(prevClose<maPreVal && ask>maVal)
        {
         if(cmfVal>0)
           {
            double slVal=ask - slLvl*_Point;
            double tpVal=ask + tpLvl*_Point;
            trade.Buy(lotSize,_Symbol,ask,slVal,tpVal);
           }
        }

Условия позиции на продажу, prevClose>maPreVal, bid<maVal, и cmfVal<0

      if(prevClose>maPreVal && bid<maVal)
        {
         if(cmfVal<0)
           {
            double slVal=bid + slLvl*_Point;
            double tpVal=bid - tpLvl*_Point;
            trade.Sell(lotSize,_Symbol,bid,slVal,tpVal);
           }
        }

После исполнения этого советника можно найти размещенный ордер, как показано на рисунках ниже:

Позиция на покупку:

buy

Позиция на продажу:

sell

Мы протестируем эту стратегию, используя тот же подход, что и в предыдущих тестах для пересечения нуля CMF и стратегий OB и OS. Мы протестируем EURUSD с 01.01.2023 по 31.12.2023 на 15-минутном таймфрейме с 300 пунктами для SL и 900 для TP. На следующих рисунках показаны результаты этого тестирования:

15min-Backtest1

15min-Backtest2

15min-Backtest3

По результатам 15-минутных тестов можно определить следующие важные значения для тестовых чисел:

  • Чистая прибыль: 40723.80 USD.
  • Относительная просадка по балансу: 6.30%.
  • Коэффициент прибыльности: 1.91.
  • Матожидание выигрыша: 167.59.
  • Фактор восстановления: 3.59.
  • Коэффициент Шарпа: 3.90.

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


Заключение

Как мы видели на протяжении всей этой статьи, объем является очень важным понятием в торговле, особенно в сочетании с другими важными инструментами, кроме того, мы поняли, что оптимизация является очень важной задачей при тестировании любой стратегии, поскольку на нее могут повлиять небольшие изменения. Я советую вам проводить свои тесты, изменяя различные аспекты, такие как таймфреймы, SL и TP, и добавляя другие инструменты, пока не получите обоснованные результаты.

Предполагается, что вы понимаете, как использовать индикатор объема Денежный поток Чайкина, как его настраивать и кодировать, а также как создавать, оптимизировать и тестировать советники на основе различных простых стратегий:

  • Стратегия пересечение нуля CMF.
  • Стратегия OB и OS CMF.
  • Стратегия подтверждения тренда CMF.

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

Вы можете найти мои прикрепленные файлы с исходным кодом, такие же, как показано ниже:

Название файла особенности
Chaikin_Money_Flow Это о нашем созданном пользовательском индикаторе CMF
CMF_Zero_Crossover Это о советнике CMF с торговой стратегией пересечения нуля
CMF_OBOS Это о советнике CMF с торговой стратегией перекупленности и перепроданности
CMF_trendValidation Это о советнике CMF с торговой стратегией проверки движения/тренда


Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/16469

Прикрепленные файлы |
CMF_OBeOS.mq5 (1.72 KB)
Последние комментарии | Перейти к обсуждению на форуме трейдеров (5)
Javier Santiago Gaston De Iriarte Cabrera
Javier Santiago Gaston De Iriarte Cabrera | 17 дек. 2024 в 18:51
Молодец!
Francis Laquerre
Francis Laquerre | 18 дек. 2024 в 04:49
Не работает в мета редакторе. Помогите, пожалуйста!
Kyle Young Sangster
Kyle Young Sangster | 27 апр. 2025 в 06:33
Отличная работа над статьей. Мне нравится ваш стиль написания: лаконичный и четкий. Также я ценю вашу настойчивость в том, чтобы программисты пачкали руки и учились на практике. Это действительно единственный способ
DayTradingSuccess
DayTradingSuccess | 2 нояб. 2025 в 17:53
Я запустил CMF с проверкой тренда на EURUSD с 1 января 2023 года по 31 декабря 2023 года и не получил тех же результатов, что и вы. Я установил пользовательский индикатор и "CMF с проверкой тренда".mq5, и максимальная просадка составила 50%, а не 6%. Как такое возможно?
Silk Road Trading LLC
Ryan L Johnson | 2 нояб. 2025 в 19:48
DayTradingSuccess пользовательский индикатор и "CMF с проверкой тренда".mq5, и максимальная просадка составила 50%, а не 6%. Как такое может быть?

Одна из вероятных возможностей заключается в том, что вы торгуете у другого брокера-дилера. В США ваши сделки на рынке Форекс в конечном итоге должны проходить клиринг на межбанковском рынке. В Египте автор, скорее всего, торгует CFD, которые привязаны к конкретному брокеру-дилеру и/или его поставщикам ликвидности.

Даже два брокера-дилера в пределах одной юрисдикции могут иметь немного разные ценовые потоки.

Алгоритм обратного поиска — Backtracking Search Algorithm (BSA) Алгоритм обратного поиска — Backtracking Search Algorithm (BSA)
Что если алгоритм оптимизации мог бы помнить свои прошлые путешествия и использовать эту память для поиска лучших решений? BSA делает именно это — балансируя между исследованием нового и возвращением к проверенному. В статье раскрываем секреты алгоритма. Простая идея, минимум параметров и стабильный результат.
Модель портфельного риска с использованием критерия Келли и моделирования по методу Монте-Карло Модель портфельного риска с использованием критерия Келли и моделирования по методу Монте-Карло
На протяжении десятилетий трейдеры использовали формулу критерия Келли для определения оптимальной доли капитала, которую можно направить на инвестиции или ставки, чтобы максимизировать долгосрочный рост при минимизации риска разорения. Однако слепое следование критерию Келли, основанному на результатах единственного бэк-тестирования, часто опасно для отдельных трейдеров, поскольку при реальной торговле торговое преимущество со временем тает, а прошлые результаты не являются предиктором будущих результатов. В настоящей статье я представлю реалистичный подход к применению критерия Келли для распределения рисков одного или нескольких советников в MetaTrader 5, основанный на результатах моделирования методом Монте-Карло с помощью Python.
Торговая стратегия "Захват ликвидности" (Liquidity Grab) Торговая стратегия "Захват ликвидности" (Liquidity Grab)
Торговая стратегия захвата ликвидности является ключевым компонентом Концепции умных денег (Smart Money Concepts (SMC), которая направлена на выявление и использование действий институциональных игроков на рынке. Она предполагает нацеливание на области с высокой ликвидностью, такие как зоны поддержки или сопротивления, где крупные ордера могут спровоцировать движение цены до того, как рынок возобновит свой тренд. В настоящей статье подробно объясняется концепция захвата ликвидности и описывается процесс разработки советника по торговой стратегии захвата ликвидности на MQL5.
Нейросети в трейдинге: Адаптивная периодическая сегментация (Создание токенов) Нейросети в трейдинге: Адаптивная периодическая сегментация (Создание токенов)
Предлагаем вам отправиться в захватывающее путешествие по миру адаптивного анализа финансовых временных рядов и узнать, как превратить сложный спектральный разбор и гибкую свёртку в реальные торговые сигналы. Вы увидите, как LightGTS слушает ритм рынка, подстраиваясь под его изменения шагом переменного окна, и как OpenCL-ускорение позволяет превратить вычисления в кратчайший путь к прибыльным решениям.