Строим и оптимизируем торговую систему, основанную на объемах торгов (Chaikin Money Flow - CMF)
Введение
Добро пожаловать в новую статью, в которой мы исследуем новые индикаторы с точки зрения их создания, построения торговых систем на основе их концепции и оптимизации этих систем для получения более полной информации и результатов в отношении прибыли и рисков. В настоящей статье мы представим новый основанный на объемах технический индикатор, который называется Денежный поток Чайкина (CMF).
Здесь лучше упомянуть кое-что важное, основная цель статей такого типа - поделиться новыми техническими инструментами, которые можно использовать отдельно или в сочетании с другими инструментами, исходя из характера этих инструментов, в дополнение к тестированию, оптимизации их для получения лучших результатов, чтобы посмотреть, могут ли эти инструменты быть полезными.
Мы разработаем этот новый индикатор (CMF) в соответствии со следующими разделами:
- Денежный поток Чайкина: получите базовые знания об этом техническом инструменте, определив его, как его можно рассчитать и как его можно использовать.
- Пользовательский индикатор Денежный поток Чайкина: узнайте, как запрограммировать наш пользовательский индикатор путем изменения или применения наших предпочтений.
- Стратегии Денежного потока Чайкина: мы рассмотрим несколько простых торговых стратегий, которые являются частью нашей торговой системы.
- Торговая система Денежный поток Чайкина: создавайте, тестируйте и оптимизируйте эти простые торговые системы.
- Заключение
Отказ от ответственности: Содержание настоящей статьи предоставляется "как есть", предназначено только для целей обучения и не является торговой рекомендацией. Статья не несет в себе каких-либо гарантий результатов. Все, что вы применяете на практике на основе этой статьи, вы делаете исключительно на свой страх и риск, автор не гарантирует никаких результатов.
Денежный поток Чайкина
Денежный поток Чайкина (CMF) это технический индикатор, основанный на объеме и учитывающий движение цены. Как мы увидим, его можно использовать отдельно или в сочетании с другими инструментами для получения более подробной информации. CMF - это индикатор, разработанный Марком Чайкиным (Marc Chaikin) для отслеживания накопления и распределения инструмента в течение определенного периода времени. Основная идея CMF заключается в том, что по мере приближения цены закрытия к максимуму происходит накопление. С другой стороны, когда цена закрытия приближается к минимуму, это признак распределения. Положительный результат Денежного потока Чайкина достигается, когда движение цены последовательно закрывается выше средней точки бара при растущем объеме. Отрицательный результат Денежного потока Чайкина достигается, когда движение цены последовательно закрывается ниже средней точки бара при растущем объеме.
Ниже приведены шаги, на которых рассчитывается показатель CMF:
- Расчет множителя денежного потока
- Расчет объёма денежного потока
Объём денежного потока = множитель денежного потока * объём за период
- Расчет CMF
CMF за период n = сумма объёма денежного потока за период n / сумма объёма за период n
После расчета индикатор покажет диапазон от +1 до -1, и будет выглядеть так же, как на следующем рисунке:

Как мы видим, индикатор может представлять собой линию, колеблющуюся вокруг нуля, изменения в 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); }
После того, как собрать этот код воедино и скомпилировать его, вы найдете файл в своей папке с индикаторами. После добавления его на график увидите его там, как показано на рисунке ниже

Как мы видим, этот индикатор выглядит и действует точно так же, как в нашем пользовательском коде. Вы можете изменить его в соответствии с вашими потребностями, в зависимости от того, что помогает вам в вашей системе. На данный момент у нас есть пользовательский индикатор 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 на графике такое же, как и на вставленном индикаторе (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); }
Как только мы соберем этот код воедино и запустим его, прикрепив к графику, мы увидим, что позиции расположены так же, как в приведенном ниже примере, который относится к этапу тестирования.
Позиция на покупку:

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

Теперь нам нужно протестировать все стратегии по паре EURUSD на год, с 1.01.2023 по 31.12.2023. Мы собираемся использовать 300 точек для SL и 900 для TP. Наш основной подход к оптимизации заключается в тестировании другой концепции или добавлении другого инструмента, такого как скользящая средняя. Вы также можете протестировать другие таймфреймы, чтобы понять, какой из них будет работать лучше. Этот тип оптимизации также стоит попробовать.
Что касается результатов тестирования стратегии для сравнения между ними, будем оценивать ключевые показатели:
- Чистая прибыль (Net Profit) — рассчитывается путем вычитания валового убытка из валовой прибыли. Чем выше значение, тем лучше.
- Относительная просадка по балансу (Balance DD relative) — максимальный убыток на счете во время работы. Чем ниже значение, тем лучше.
- Коэффициент прибыльности: Это отношение валовой прибыли к валовому убытку. Чем выше значение, тем лучше.
- Матожидание выигрыша: Это средняя прибыль или убыток сделки. Чем выше значение, тем лучше.
- Фактор восстановления (Recovery factor) — насколько хорошо протестированная стратегия восстанавливается после потерь. Чем выше значение, тем лучше.
- Коэффициент Шарпа: Он определяет риск и стабильность протестированной торговой системы, сравнивая доходность с безрисковой доходностью. Чем выше значение коэффициента Шарпа, тем лучше.
Результаты 15-минутного теста на таймфрейме будут такими же, как показано ниже:



По результатам 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); }
Как только мы соберем этот код воедино и запустим его на графике, можно увидеть, где позиции соответствуют условиям стратегии, точно так же, как в примерах из этапа тестирования.
Позиция на покупку:

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

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



По результатам 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); } }
После исполнения этого советника можно найти размещенный ордер, как показано на рисунках ниже:
Позиция на покупку:

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

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



По результатам 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
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.
Модель портфельного риска с использованием критерия Келли и моделирования по методу Монте-Карло
Торговая стратегия "Захват ликвидности" (Liquidity Grab)
Нейросети в трейдинге: Адаптивная периодическая сегментация (Создание токенов)
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования
Одна из вероятных возможностей заключается в том, что вы торгуете у другого брокера-дилера. В США ваши сделки на рынке Форекс в конечном итоге должны проходить клиринг на межбанковском рынке. В Египте автор, скорее всего, торгует CFD, которые привязаны к конкретному брокеру-дилеру и/или его поставщикам ликвидности.
Даже два брокера-дилера в пределах одной юрисдикции могут иметь немного разные ценовые потоки.