
Теория адаптивных индикаторов и ее реализация в MQL5
Введение
Материалы этой статьи основаны на двух отличных книгах Джона Элерса "Rocket Science for Traders" и "Cybernetic Analysis for Stock and Futures".
Необычный подход к анализу рынков методами цифровой обработки сигналов и применения комплексных чисел для распознавания рыночных циклов заставил меня углубиться в эту тему и впоследствии реализовать в MQL5 три адаптивных индикатора, предложенных Джоном Элерсом.
В этой статье будут описаны основы теории адаптивных индикаторов и ее реализация на языке MQL5. Кроме того, мы сравним адаптивные индикаторы с их неадаптивными аналогами.
Использование комплексных чисел и фазовых векторов для измерения рыночных циклов
Некоторые понятия теории комплексных чисел могут привести в недоумение читателей, не имеющих технического образования, поэтому перед прочтением статьи я рекомендую ознакомиться с теорией комплексных чисел в wikipedia и посмотреть учебник по операциям с комплексными числами.
Фазовый вектор
Фазовый вектор (Phase Vector) - это вектор, показывающий амплитуду и фазу периодического процесса (цикла). Согласно формуле Эйлера комплексное число (состоящее из действительной и мнимой части) можно представить в показательной форме, где в аргументом в фазе является угол. Это позволяет наглядно иллюстрировать периодические процессы.
На рисунке ниже приведено видео, иллюстрирующее вращение фазового вектора синусоиды.
Возможно, впервые увидев эту анимацию, у вас возникнет вопрос о том, какое же отношение имеет фазовый вектор к циклу. Для понимания лучше рассматривать периодический процесс как вращающийся фазовый вектор, изображенный справа, а не в виде обычной синусоиды, приведенной слева.
Я представляю это следующим образом: полный оборот фазового вектора на 360 градусов (или радиан) является одинаковым для всего цикла. Текущий угол фазового вектора показывает, в какой части цикла (фазы) мы находимся в данный момент. Ось Y отображает текущее значение амплитуды периодического процесса.
Фазовый вектор может быть разложен на 2 компоненты: InPhase (cosine) и Quadrature (sine). Подробное объяснение получения этих компонент приведено в разделе 6 "Преобразования Гильберта" книги "Rocket Science for Traders". Этот раздел я рекомендую всем, кто хочет разобраться в деталях.
Сейчас нам важно лишь то, что для вычисления адаптивных индикаторов нам нужно преобразовать сигнал (waveform) в комплексный сигнал, состоящий из двух компонент. Как мы достигнем этого? Я упоминал о преобразовании Гильберта? Именно это оно и способно сделать.
Измерение периода цикла
Для практического применения преобразования Гильберта в своей книге Джон Элерс разбил его на ряд из четырех элементов.
Выражение для компоненты Quadrature выглядит следующим образом:
а компонента InPhase представляет собой цену, запаздывающую на 3 бара:
Вычислив компоненты InPhase и Quadrature (напомним, это действительная и мнимая части комплексного числа), можно вычислить сдвиг фазы между текущим и предыдущим барами. Для текущего бара фаза равна , для предыдущего бара фаза равна
.
Воспользовавшись тригонометрическим тождеством:
мы получаем выражение для дифференциальной фазы DeltaPhase.
На величину DeltaPhase Джон Элерс наложил дополнительные ограничения: она не может быть отрицательной и ограничена интервалом <0.1, 1.1> (длина цикла от 6 до 63 баров). Оказалось, что для реальных данных DeltaPhase не выглядит гладкой, поэтому необходимо сглаживание.
Наилучшим способом сглаживания для данных с выбросами (spiky data) является медианный фильтр, поэтому применяется медианное сглаживание пяти данных DeltaPhase, результат помещается в переменную MedianDelta. Значение MedianDelta, деленное на далее используется для вычисления периода основного цикла (Dominant Cycle), который является нашей целью.
В процессе тестирования выяснилось, что при измерении существует сдвиг примерно 0.5, который нужно убирать, поэтому был добавлен соответствующий член в расчетной формуле. Наконец, вычисленное значение Dominant Cycle дважды сглаживается при помощи EMA со значениями alpha, равными 0.33 and 0.1 соответственно. Рекомендую посмотреть книгу "Rocket Science for Traders", в которой приведена иллюстрация робастности алгоритма на примере синусоподобного сигнала, период которого постепенно увеличивался с 6 до 40.
Вооружившись теоретическими знаниями, теперь мы готовы к тому, чтобы реализовать индикатор CyclePeriod на MQL5.
Индикатор периода цикла (Cycle Period indicator)
Индикатор Cycle Period состоит из двух линий: линия cycle, показывающая период цикла и сигнальной линии (trigger line), в качестве которой обычно берется линия cycle, запаздывающая на 1 бар.
Посмотрев описание в предыдущем разделе и исходный код в функции OnCalculate(), вы легко сможете понять, какие линии отвечают за измерение периода цикла.
//+------------------------------------------------------------------+ //| CyclePeriod.mq5 | //| Copyright 2011, Investeo.pl | //| http://Investeo.pl | //+------------------------------------------------------------------+ #property copyright "Copyright 2011, Investeo.pl" #property link "http://Investeo.pl" #property version "1.00" #property indicator_separate_window #property description "Индикатор CyclePeriod - предложен Джоном Элерсом (John F. Ehlers)" #property description "в книге \"Cybernetic Analysis for Stocks and Futures\"" #property indicator_buffers 2 #property indicator_plots 2 #property indicator_width1 1 #property indicator_width2 1 #property indicator_type1 DRAW_LINE #property indicator_type2 DRAW_LINE #property indicator_color1 Green #property indicator_color2 Red #property indicator_label1 "Cycle" #property indicator_label2 "Trigger Line" #define Price(i) ((high[i]+low[i])/2.0) double Smooth[]; double Cycle[]; double Trigger[]; double Q1[]; // компонента Quadrature double I1[]; // компонента InPhase double DeltaPhase[]; double InstPeriod[]; double CyclePeriod[]; input double InpAlpha=0.07; // alpha //+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- индикаторные буферы ArraySetAsSeries(Cycle,true); ArraySetAsSeries(CyclePeriod,true); ArraySetAsSeries(Trigger,true); ArraySetAsSeries(Smooth,true); SetIndexBuffer(0,CyclePeriod,INDICATOR_DATA); SetIndexBuffer(1,Trigger,INDICATOR_DATA); PlotIndexSetDouble(0,PLOT_EMPTY_VALUE,0.0); PlotIndexSetDouble(1,PLOT_EMPTY_VALUE,0.0); return(0); } //+------------------------------------------------------------------+ //| Custom indicator iteration function | //+------------------------------------------------------------------+ 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[]) { //--- long tickCnt[1]; int i; int ticks=CopyTickVolume(Symbol(), 0, 0, 1, tickCnt); if(ticks!=1) return(rates_total); double DC, MedianDelta; Comment(tickCnt[0]); if(prev_calculated==0 || tickCnt[0]==1) { //--- последний рассчитанный бар будет пересчитан int nLimit=rates_total-prev_calculated-1; // начальный индекс для расчетов ArraySetAsSeries(high,true); ArraySetAsSeries(low,true); ArrayResize(Smooth,Bars(_Symbol,_Period)); ArrayResize(Cycle,Bars(_Symbol,_Period)); //ArrayResize(Price,Bars(_Symbol,_Period)); ArrayResize(CyclePeriod,Bars(_Symbol,_Period)); ArrayResize(InstPeriod,Bars(_Symbol,_Period)); ArrayResize(Q1,Bars(_Symbol,_Period)); ArrayResize(I1,Bars(_Symbol,_Period)); ArrayResize(DeltaPhase,Bars(_Symbol,_Period)); if (nLimit>rates_total-7) // последние бары nLimit=rates_total-7; for(i=nLimit;i>=0 && !IsStopped();i--) { Smooth[i] = (Price(i)+2*Price(i+1)+2*Price(i+2)+Price(i+3))/6.0; if (i<rates_total-7) { Cycle[i] = (1.0-0.5*InpAlpha) * (1.0-0.5*InpAlpha) * (Smooth[i]-2.0*Smooth[i+1]+Smooth[i+2]) +2.0*(1.0-InpAlpha)*Cycle[i+1]-(1.0-InpAlpha)*(1.0-InpAlpha)*Cycle[i+2]; } else { Cycle[i]=(Price(i)-2.0*Price(i+1)+Price(i+2))/4.0; } Q1[i] = (0.0962*Cycle[i]+0.5769*Cycle[i+2]-0.5769*Cycle[i+4]-0.0962*Cycle[i+6])*(0.5+0.08*InstPeriod[i+1]); I1[i] = Cycle[i+3]; if (Q1[i]!=0.0 && Q1[i+1]!=0.0) DeltaPhase[i] = (I1[i]/Q1[i]-I1[i+1]/Q1[i+1])/(1.0+I1[i]*I1[i+1]/(Q1[i]*Q1[i+1])); if (DeltaPhase[i] < 0.1) DeltaPhase[i] = 0.1; if (DeltaPhase[i] > 0.9) DeltaPhase[i] = 0.9; MedianDelta = Median(DeltaPhase, i, 5); if (MedianDelta == 0.0) DC = 15.0; else DC = (6.28318/MedianDelta) + 0.5; InstPeriod[i] = 0.33 * DC + 0.67 * InstPeriod[i+1]; CyclePeriod[i] = 0.15 * InstPeriod[i] + 0.85 * CyclePeriod[i+1]; Trigger[i] = CyclePeriod[i+1]; } } //--- возвращаем значение prev_calculated для следующего вызова return(rates_total); } //+------------------------------------------------------------------+ double Median(double& arr[], int idx, int m_len) { double MedianArr[]; int copied; double result = 0.0; ArraySetAsSeries(MedianArr, true); ArrayResize(MedianArr, m_len); copied = ArrayCopy(MedianArr, arr, 0, idx, m_len); if (copied == m_len) { ArraySort(MedianArr); if (m_len %2 == 0) result = (MedianArr[m_len/2] + MedianArr[(m_len/2)+1])/2.0; else result = MedianArr[m_len / 2]; } else Print(__FILE__+__FUNCTION__+" ошибка в функции median - неверное количество скопированных данных."); return result; }
Мы можем проверить его, присоединив к любому графику - он будет работать на любом инструменте и любом таймфрейме (см. рис).
При помощи этого индикатора мы сможем реализовать новое поколение адаптивных индикаторов - индикаторов, которые адаптируются к текущему периоду рыночного цикла.
Индикатор Cyber Cycle
Индикатор Cyber Cycle представляет собой высокочастотный фильтр из книги "Cybernetic analysis for stocks and futures".
Данный фильтр оставляет только одну компоненту цикла временного ряда. Дополнительно, двухбарные и трехбарные компоненты цикла выделяются из результата путем сглаживания его при помощи низкочастотного фильтра с конечной импульсной характеристикой.
Код этого и других индикаторов статьи переписан на MQL5 на основе индикаторов на языке EFL (Tradestation), приведенных в книге.
//+------------------------------------------------------------------+ //| CyberCycle.mq5 | //| Copyright 2011, Investeo.pl | //| http://Investeo.pl | //+------------------------------------------------------------------+ #property copyright "Copyright 2011, Investeo.pl" #property link "http://Investeo.pl" #property version "1.00" #property indicator_separate_window #property description "Индикатор CyberCycle - предложен Джоном Элерсом (John F. Ehlers)" #property description "в книге \"Cybernetic Analysis for Stocks and Futures\"" #property indicator_buffers 2 #property indicator_plots 2 #property indicator_width1 1 #property indicator_width2 1 #property indicator_type1 DRAW_LINE #property indicator_type2 DRAW_LINE #property indicator_color1 Green #property indicator_color2 Red #property indicator_label1 "Cycle" #property indicator_label2 "Trigger Line" #define Price(i) ((high[i]+low[i])/2.0) double Smooth[]; double Cycle[]; double Trigger[]; input double InpAlpha=0.07; // alpha //+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- индикаторные буферы ArraySetAsSeries(Cycle,true); ArraySetAsSeries(Trigger,true); ArraySetAsSeries(Smooth,true); SetIndexBuffer(0,Cycle,INDICATOR_DATA); SetIndexBuffer(1,Trigger,INDICATOR_DATA); PlotIndexSetDouble(0,PLOT_EMPTY_VALUE,0.0); PlotIndexSetDouble(1,PLOT_EMPTY_VALUE,0.0); return(0); } //+------------------------------------------------------------------+ //| Custom indicator iteration function | //+------------------------------------------------------------------+ 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[]) { //--- long tickCnt[1]; int i; int ticks=CopyTickVolume(Symbol(), 0, 0, 1, tickCnt); if(ticks!=1) return(rates_total); Comment(tickCnt[0]); if(prev_calculated==0 || tickCnt[0]==1) { //--- последний рассчитанный бар будет пересчитан int nLimit=rates_total-prev_calculated-1; // индекс начального бара для расчетов ArraySetAsSeries(high,true); ArraySetAsSeries(low,true); ArrayResize(Smooth,Bars(_Symbol,_Period)); ArrayResize(Cycle,Bars(_Symbol,_Period)); if(nLimit>rates_total-4) // adjust for last bars nLimit=rates_total-4; for(i=nLimit;i>=0 && !IsStopped();i--) { Smooth[i]=(Price(i)+2*Price(i+1)+2*Price(i+2)+Price(i+3))/6.0; if(i<rates_total-5) { Cycle[i]=(1.0-0.5*InpAlpha) *(1.0-0.5*InpAlpha) *(Smooth[i]-2.0*Smooth[i+1]+Smooth[i+2]) +2.0*(1.0-InpAlpha)*Cycle[i+1]-(1.0-InpAlpha)*(1.0-InpAlpha)*Cycle[i+2]; } else { Cycle[i]=(Price(i)-2.0*Price(i+1)+Price(i+2))/4.0; } //Print(__FILE__+__FUNCTION__+" получено значений: ",rCnt); Trigger[i]=Cycle[i+1]; } } //--- возвращаем значение prev_calculated для следующего вызова return(rates_total); } //+------------------------------------------------------------------+
Скриншот индикатора приведен ниже. Возможно, вы заметили, что все индикаторы в данной статье выглядят похожим образом, но алгоритмы их реализации разные.
Алгоритм торговли по этому индикатору простой: покупать, если линия cycle пересекла снизу вверх сигнальную линию. Продавать следует, если линия cycle пересекла сигнальную линию сверху вниз. Предлагаю вам самостоятельно реализовать стратегию на базе этого индикатора в советнике или модуле торговых сигналов.
Адаптивный индикатор Cyber Cycle
Суть данной статьи - показать, каким образом мы можем сделать индикаторы адаптивными, а именно, каким образом вычислять их с динамическим периодом цикла вместо статического. Для этого мы должны подключить индикатор CyclePeriod для чтения текущего периода цикла и в дальнейшем использовать его значения в функции OnCalculate().
Сначала нам нужно получить хэндл индикатора:
hCyclePeriod=iCustom(NULL,0,"CyclePeriod",InpAlpha); if(hCyclePeriod==INVALID_HANDLE) { Print("Индикатор CyclePeriod не найден!"); return(-1); }
а затем запросить данные индикаторного буфера в функции OnCalculate():
int copied=CopyBuffer(hCyclePeriod,0,i,1,CyclePeriod); if(copied<=0) { Print("Ошибка: Ошибка получения значений индикатора CyclePeriod."); return -1; } alpha1 = 2.0/(CyclePeriod[0]+1.0);
Значение alpha в экспоненциальном сглаживании связано с периодом простой скользящей средней Length соотношением , при вычислении alpha в индикаторе адаптивного Cyber Cycle Джон Элерс использует период Dominant Cycle в качестве значения Length.
Полный код индикатора приведен ниже:
//+------------------------------------------------------------------+ //| AdaptiveCyberCycle.mq5 | //| Copyright 2011, Investeo.pl | //| http://Investeo.pl | //+------------------------------------------------------------------+ #property copyright "Copyright 2011, Investeo.pl" #property link "http://Investeo.pl" #property version "1.00" #property indicator_separate_window #property description "Адаптивная версия индикатора CyberCycle - предложен Джоном Элерсом (John F. Ehlers)" #property description "в книге \"Cybernetic Analysis for Stocks and Futures\"" #property indicator_buffers 2 #property indicator_plots 2 #property indicator_width1 1 #property indicator_width2 1 #property indicator_type1 DRAW_LINE #property indicator_type2 DRAW_LINE #property indicator_color1 Green #property indicator_color2 Red #property indicator_label1 "Cycle" #property indicator_label2 "Trigger Line" #define Price(i) ((high[i]+low[i])/2.0) double Smooth[]; double Cycle[]; double Trigger[]; int hCyclePeriod; input double InpAlpha=0.07; // значение alpha для периода Cycle //+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- индикаторные буферы ArraySetAsSeries(Cycle,true); ArraySetAsSeries(Trigger,true); ArraySetAsSeries(Smooth,true); SetIndexBuffer(0,Cycle,INDICATOR_DATA); SetIndexBuffer(1,Trigger,INDICATOR_DATA); PlotIndexSetDouble(0,PLOT_EMPTY_VALUE,0.0); PlotIndexSetDouble(1,PLOT_EMPTY_VALUE,0.0); hCyclePeriod=iCustom(NULL,0,"CyclePeriod",InpAlpha); if(hCyclePeriod==INVALID_HANDLE) { Print("Индикатор CyclePeriod не найден!"); return(-1); } return(0); } //+------------------------------------------------------------------+ //| Custom indicator iteration function | //+------------------------------------------------------------------+ 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[]) { //--- long tickCnt[1]; int i; int ticks=CopyTickVolume(Symbol(), 0, 0, 1, tickCnt); if(ticks!=1) return(rates_total); double CyclePeriod[1],alpha1; Comment(tickCnt[0]); if(prev_calculated==0 || tickCnt[0]==1) { //--- последний рассчитанный бар будет пересчитан int nLimit=rates_total-prev_calculated-1; // начальный индекс для расчетов ArraySetAsSeries(high,true); ArraySetAsSeries(low,true); ArrayResize(Smooth,Bars(_Symbol,_Period)); ArrayResize(Cycle,Bars(_Symbol,_Period)); if(nLimit>rates_total-4) // для последних баров nLimit=rates_total-4; for(i=nLimit;i>=0 && !IsStopped();i--) { Smooth[i]=(Price(i)+2*Price(i+1)+2*Price(i+2)+Price(i+3))/6.0; int copied=CopyBuffer(hCyclePeriod,0,i,1,CyclePeriod); if(copied<=0) { Print("Ошибка: Ошибка получения значений индикатора CyclePeriod."); return -1; } alpha1 = 2.0/(CyclePeriod[0]+1.0); //Print(alpha1); //Print(CyclePeriod[0]); if(i>=0) { Cycle[i]=(1.0-0.5*alpha1) *(1.0-0.5*alpha1) *(Smooth[i]-2.0*Smooth[i+1]+Smooth[i+2]) +2.0*(1.0-alpha1)*Cycle[i+1]-(1.0-alpha1)*(1.0-alpha1)*Cycle[i+2]; //Print("Smooth["+IntegerToString(i)+"]="+DoubleToString(Smooth[i])+" Cycle["+IntegerToString(i)+"]="+DoubleToString(Cycle[i])); } else { Cycle[i]=(Price(i)-2.0*Price(i+1)+Price(i+2))/4.0; } //Print(__FILE__+__FUNCTION__+" получено значений: ",rCnt); Trigger[i]=Cycle[i+1]; } } //--- возвращаем значение prev_calculated для следующего вызова return(rates_total); } //+------------------------------------------------------------------+
Индикатор приведен на рисунке:
Наш первый адаптивный индикатор готов. Согласно книге, он должен быть более чувствительный, чем неадаптивная версия. Зачастую сигналы на покупку и продажу возникают на бар раньше, чем в неадаптивном варианте.
То же самое мы можем проделать еще с двумя индикаторами, этого будет достаточно для того, чтобы общая схема создания адаптивных индикаторов стала ясной.
Индикатор Center of Gravity
Под центром тяжести (center of gravity) физических объектов подразумевают точку равновесия. Идея заключается в том, чтобы ввести это понятие в трейдинг, основываясь на связи величины запаздывания (lags) различных фильтров с их коэффицинтами.
Для простой скользящей средней (SMA, Simple Moving Average) все коэффициенты одинаковы, центр тяжести находится посередине.
Для взвешенной скользящей средней (WMA, Weighted Moving Average) последние цены являются более важными, чем старые. Более точно, коэффициенты WMA описывают контур треугольника.
Общее выражение для вычисления центра тяжести при заданном окне наблюдения выглядит следующим образом:
Положение точки равновесия получается суммированием произведения положения цены в окне на соответствующую цену (+1 введено из-за того, что нумерация производится от 0 до N, а не от 1 до N)
Главной особенностью индикатора Center of gravity является то, что он увеличивается и уменьшается вдоль колебаний и по сути является осциллятором без запаздывания (zero-lag oscillator).
Исходный код приведен ниже:
//+------------------------------------------------------------------+ //| CenterOfGravity.mq5 | //| Copyright 2011, Investeo.pl | //| http://Investeo.pl | //+------------------------------------------------------------------+ #property copyright "Copyright 2011, Investeo.pl" #property link "http://Investeo.pl" #property version "1.00" #property indicator_separate_window #property description "Индикатор CG - предложен Джоном Элерсом (John F. Ehlers)" #property description "в книге \"Cybernetic Analysis for Stocks and Futures\"" #property indicator_buffers 2 #property indicator_plots 2 #property indicator_width1 1 #property indicator_width2 1 #property indicator_type1 DRAW_LINE #property indicator_type2 DRAW_LINE #property indicator_color1 Green #property indicator_color2 Red #property indicator_label1 "Cycle" #property indicator_label2 "Trigger Line" #define Price(i) ((high[i]+low[i])/2.0) double Smooth[]; double Cycle[]; double Trigger[]; input double InpAlpha=0.07; // alpha input int InpCGLength=10; // размер окна CG //+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- индикаторные буферы ArraySetAsSeries(Cycle,true); ArraySetAsSeries(Trigger,true); ArraySetAsSeries(Smooth,true); SetIndexBuffer(0,Cycle,INDICATOR_DATA); SetIndexBuffer(1,Trigger,INDICATOR_DATA); PlotIndexSetDouble(0,PLOT_EMPTY_VALUE,0.0); PlotIndexSetDouble(1,PLOT_EMPTY_VALUE,0.0); return(0); } //+------------------------------------------------------------------+ //| Custom indicator iteration function | //+------------------------------------------------------------------+ 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[]) { //--- long tickCnt[1]; int i; double Num, Denom; // числитель и знаменатель для CG int ticks=CopyTickVolume(Symbol(), 0, 0, 1, tickCnt); if(ticks!=1) return(rates_total); Comment(tickCnt[0]); if(prev_calculated==0 || tickCnt[0]==1) { //--- последний рассчитанный бар будет пересчитан int nLimit=rates_total-prev_calculated-1; // начальный индекс для расчета ArraySetAsSeries(high,true); ArraySetAsSeries(low,true); ArrayResize(Smooth,Bars(_Symbol,_Period)); ArrayResize(Cycle,Bars(_Symbol,_Period)); if(nLimit>rates_total-InpCGLength) // для последних баров nLimit=rates_total-InpCGLength; for(i=nLimit;i>=0 && !IsStopped();i--) { Num = 0.0; Denom = 0.0; for (int count=0; count<InpCGLength; count++) { Num += (1.0+count)*Price(i+count); Denom += Price(i+count); } if (Denom != 0.0) Cycle[i] = -Num/Denom+(InpCGLength+1.0)/2.0; else Cycle[i] = 0.0; //Print(__FILE__+__FUNCTION__+" получено значений: ",rCnt); Trigger[i]=Cycle[i+1]; } } //--- возвращаем значение prev_calculated для следующего вызова return(rates_total); } //+------------------------------------------------------------------+
Скриншот приведен ниже. Обратите внимание на небольшое запаздывание (lag).
Адаптивная версия индикатора Center of Gravity
Осциллятор Center of Gravity определяет "центр тяжести" данных окна фиксированной длины. Адаптивный осциллятор Center of Gravity в качестве длины динамического окна использует период преобладающего цикла (Dominant Cycle).
Для нахождения периода преобладающего цикла используется следующий код:
copied=CopyBuffer(hCyclePeriod,0,i,1,CyclePeriod); if(copied<=0) { Print("Ошибка: Ошибка получения значений индикатора CyclePeriod."); return -1; } CG_len = floor(CyclePeriod[0]/2.0);
Полный исходный код индикатора приведен ниже, сравните его с неадаптивной версией (см. также с индикатор Adaptive Cyber Cycle).
//+------------------------------------------------------------------+ //| AdaptiveCenterOfGravity.mq5 | //| Copyright 2011, Investeo.pl | //| http://Investeo.pl | //+------------------------------------------------------------------+ #property copyright "Copyright 2011, Investeo.pl" #property link "http://Investeo.pl" #property version "1.00" #property indicator_separate_window #property description "Адаптивная версия индикатора CG - предложен Джоном Элерсом (John F. Ehlers)" #property description "в книге \"Cybernetic Analysis for Stocks and Futures\"" #property indicator_buffers 2 #property indicator_plots 2 #property indicator_width1 1 #property indicator_width2 1 #property indicator_type1 DRAW_LINE #property indicator_type2 DRAW_LINE #property indicator_color1 Green #property indicator_color2 Red #property indicator_label1 "Cycle" #property indicator_label2 "Trigger Line" #define Price(i) ((high[i]+low[i])/2.0) double Smooth[]; double Cycle[]; double Trigger[]; int hCyclePeriod; input double InpAlpha=0.07; // alpha //+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- индикаторные буферы ArraySetAsSeries(Cycle,true); ArraySetAsSeries(Trigger,true); ArraySetAsSeries(Smooth,true); SetIndexBuffer(0,Cycle,INDICATOR_DATA); SetIndexBuffer(1,Trigger,INDICATOR_DATA); PlotIndexSetDouble(0,PLOT_EMPTY_VALUE,0.0); PlotIndexSetDouble(1,PLOT_EMPTY_VALUE,0.0); hCyclePeriod=iCustom(NULL,0,"CyclePeriod",InpAlpha); if(hCyclePeriod==INVALID_HANDLE) { Print("Индикатор CyclePeriod не найден!"); return(-1); } return(0); } //+------------------------------------------------------------------+ //| Custom indicator iteration function | //+------------------------------------------------------------------+ 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[]) { //--- long tickCnt[1]; int i, copied; double Num,Denom; // Числитель и знаменатель индикатора CG double CG_len; int ticks=CopyTickVolume(Symbol(), 0, 0, 1, tickCnt); if(ticks!=1) return(rates_total); double CyclePeriod[1]; Comment(tickCnt[0]); if(prev_calculated==0 || tickCnt[0]==1) { //--- последний рассчитанный бар будет пересчитан int nLimit=rates_total-prev_calculated-1; // начальный индекс для расчета ArraySetAsSeries(high,true); ArraySetAsSeries(low,true); ArrayResize(Smooth,Bars(_Symbol,_Period)); ArrayResize(Cycle,Bars(_Symbol,_Period)); copied=CopyBuffer(hCyclePeriod,0,0,1,CyclePeriod); if(copied<=0) { Print("Ошибка: Ошибка получения значений индикатора CyclePeriod."); return -1; } if(nLimit>rates_total-int(CyclePeriod[0])-2) // для последних баров nLimit=rates_total-int(CyclePeriod[0])-2; for(i=nLimit;i>=0 && !IsStopped();i--) { copied=CopyBuffer(hCyclePeriod,0,i,1,CyclePeriod); if(copied<=0) { Print("Ошибка: Ошибка получения значений индикатора CyclePeriod."); return -1; } CG_len = floor(CyclePeriod[0]/2.0); //Print("CG_len="+DoubleToString(CG_len)); Num=0.0; Denom=0.0; for(int count=0; count<int(CG_len); count++) { Num+=(1.0+count)*Price(i+count); Denom+=Price(i+count); } if(Denom!=0.0) Cycle[i]=-Num/Denom+(CG_len+1.0)/2.0; else Cycle[i]=0.0; //Print(__FILE__+__FUNCTION__+" получено значений: ",rCnt); Trigger[i]=Cycle[i+1]; } } //--- возвращаем значение prev_calculated для следующего вызова return(rates_total); } //+------------------------------------------------------------------+
Рисунок индикатора AdaptiveCG приведен на рисунке:
Индикатор RVI
RVI - это индикатор Relative Vigor Index, который основан на том факте, что для бычьих рынков цена закрытия обычно выше цены открытия, а для медвежьих рынков цена закрытия, как правило, ниже цены открытия.
Сила (vigor) движения измеряется как отношение разности цен закрытия и открытия к торговому диапазону.
Этот индикатор хорошо известен многим пользователям терминала MetaTrader, теперь он включен в стандартную поставку.
Тем не менее, я приведу здесь его код:
//+------------------------------------------------------------------+ //| RVI.mq5 | //| Copyright 2009, MetaQuotes Software Corp. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "2009, MetaQuotes Software Corp." #property link "https://www.mql5.com" #property description "Relative Vigor Index" //--- настройки индикатора #property indicator_separate_window #property indicator_buffers 2 #property indicator_plots 2 #property indicator_type1 DRAW_LINE #property indicator_type2 DRAW_LINE #property indicator_color1 Green #property indicator_color2 Red #property indicator_label1 "RVI" #property indicator_label2 "Signal" //--- входные параметры input int InpRVIPeriod=10; // Период //--- индикаторные буферы double ExtRVIBuffer[]; double ExtSignalBuffer[]; //--- #define TRIANGLE_PERIOD 3 #define AVERAGE_PERIOD (TRIANGLE_PERIOD*2) //+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ void OnInit() { //--- индикаторные буферы SetIndexBuffer(0,ExtRVIBuffer,INDICATOR_DATA); SetIndexBuffer(1,ExtSignalBuffer,INDICATOR_DATA); IndicatorSetInteger(INDICATOR_DIGITS,3); //--- установим начало отрисовки PlotIndexSetInteger(0,PLOT_DRAW_BEGIN,(InpRVIPeriod-1)+TRIANGLE_PERIOD); PlotIndexSetInteger(1,PLOT_DRAW_BEGIN,(InpRVIPeriod-1)+AVERAGE_PERIOD); //--- установка наименований линий, которые будут показываться в окне DataWindow IndicatorSetString(INDICATOR_SHORTNAME,"RVI("+string(InpRVIPeriod)+")"); PlotIndexSetString(0,PLOT_LABEL,"RVI("+string(InpRVIPeriod)+")"); PlotIndexSetString(1,PLOT_LABEL,"Signal("+string(InpRVIPeriod)+")"); //--- инициализация завершена } //+------------------------------------------------------------------+ //| Relative Vigor Index | //+------------------------------------------------------------------+ 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 i,j,nLimit; double dValueUp,dValueDown,dNum,dDeNum; //--- проверка количества баров if(rates_total<=InpRVIPeriod+AVERAGE_PERIOD+2) return(0); // выходим с кодом возврата 0 //--- проверка возможных ошибок if(prev_calculated<0) return(0); // выходим с кодом возврата 0 //--- последний рассчитанный бар будет пересчитан nLimit=InpRVIPeriod+2; if(prev_calculated>InpRVIPeriod+TRIANGLE_PERIOD+2) nLimit=prev_calculated-1; //--- установка empty value для нерасчитанных баров if(prev_calculated==0) { for(i=0;i<InpRVIPeriod+TRIANGLE_PERIOD;i++) ExtRVIBuffer[i]=0.0; for(i=0;i<InpRVIPeriod+AVERAGE_PERIOD;i++) ExtSignalBuffer[i]=0.0; } //--- RVI вычисляется в 1-м буфере for(i=nLimit;i<rates_total && !IsStopped();i++) { dNum=0.0; dDeNum=0.0; for(j=i;j>i-InpRVIPeriod;j--) { dValueUp=Close[j]-Open[j]+2*(Close[j-1]-Open[j-1])+2*(Close[j-2]-Open[j-2])+Close[j-3]-Open[j-3]; dValueDown=High[j]-Low[j]+2*(High[j-1]-Low[j-1])+2*(High[j-2]-Low[j-2])+High[j-3]-Low[j-3]; dNum+=dValueUp; dDeNum+=dValueDown; } if(dDeNum!=0.0) ExtRVIBuffer[i]=dNum/dDeNum; else ExtRVIBuffer[i]=dNum; } //--- сигнальная линия вычилсяется во 2-м буфере nLimit=InpRVIPeriod+TRIANGLE_PERIOD+2; if(prev_calculated>InpRVIPeriod+AVERAGE_PERIOD+2) nLimit=prev_calculated-1; for(i=nLimit;i<rates_total && !IsStopped();i++) ExtSignalBuffer[i]=(ExtRVIBuffer[i]+2*ExtRVIBuffer[i-1]+2*ExtRVIBuffer[i-2]+ExtRVIBuffer[i-3])/AVERAGE_PERIOD; //--- Завершение OnCalculate. Возвращаем новое значение prev_calculated. return(rates_total); } //+------------------------------------------------------------------+
На рисунке ниже приведен скриншот стандартного индикатора RVI с периодом 10 по умолчанию:
Адаптивная версия индикатора RVI
Как и для предыдущих индикаторов (точнее, их адаптивных версий), нам нужно получить значение Dominant Cycle из индикатора CyclePeriod и применить его к периоду индикатора RVI. Переменная Length вычисляется как взвешенное значение скользящей средней периода CyclePeriod, усредненное по 4 последним барам.
copied=CopyBuffer(hCyclePeriod,0,0,4,CyclePeriod); if(copied<=0) { Print("Ошибка: Ошибка получения значений индикатора CyclePeriod."); return -1; } AdaptiveRVIPeriod = int(floor((4*CyclePeriod[0]+3*CyclePeriod[1]+2*CyclePeriod[2]+CyclePeriod[3])/20.0));
Ниже приведен полный исходный код адаптивной версии индикатора RVI.
//+------------------------------------------------------------------+ //| Adaptive RVI.mq5 | //| Based on RVI by MetaQuotes Software Corp. | //| Copyright 2009, MetaQuotes Software Corp. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "2009, MetaQuotes Software Corp." #property copyright "2011, Adaptive version Investeo.pl" #property link "https://www.mql5.com" #property description "Adaptive Relative Vigor Index" //--- настройки индикатора #property indicator_separate_window #property indicator_buffers 2 #property indicator_plots 2 #property indicator_type1 DRAW_LINE #property indicator_type2 DRAW_LINE #property indicator_color1 Green #property indicator_color2 Red #property indicator_label1 "AdaptiveRVI" #property indicator_label2 "Signal" #define Price(i) ((high[i]+low[i])/2.0) //--- входные параметры input int InpRVIPeriod=10; // Начальное значение периода RVI //--- индикаторные буферы double ExtRVIBuffer[]; double ExtSignalBuffer[]; //--- int hCyclePeriod; input double InpAlpha=0.07; // alpha для Cycle Period int AdaptiveRVIPeriod; #define TRIANGLE_PERIOD 3 #define AVERAGE_PERIOD (TRIANGLE_PERIOD*2) //+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- индикаторные буферы SetIndexBuffer(0,ExtRVIBuffer,INDICATOR_DATA); SetIndexBuffer(1,ExtSignalBuffer,INDICATOR_DATA); IndicatorSetInteger(INDICATOR_DIGITS,3); hCyclePeriod=iCustom(NULL,0,"CyclePeriod",InpAlpha); if(hCyclePeriod==INVALID_HANDLE) { Print("Не найден индикатор CyclePeriod!"); return(-1); } //--- установка начального бара для отображения PlotIndexSetInteger(0,PLOT_DRAW_BEGIN,(InpRVIPeriod-1)+TRIANGLE_PERIOD); PlotIndexSetInteger(1,PLOT_DRAW_BEGIN,(InpRVIPeriod-1)+AVERAGE_PERIOD); //--- установка наименований линий, которые будут показываться в окне DataWindow IndicatorSetString(INDICATOR_SHORTNAME,"AdaptiveRVI"); PlotIndexSetString(0,PLOT_LABEL,"AdaptiveRVI"); PlotIndexSetString(1,PLOT_LABEL,"Signal"); //--- инициализация завершена return 0; } //+------------------------------------------------------------------+ //| Relative Vigor Index | //+------------------------------------------------------------------+ 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 i,j,nLimit; double dValueUp,dValueDown,dNum,dDeNum; double CyclePeriod[4]; int copied; copied=CopyBuffer(hCyclePeriod,0,0,4,CyclePeriod); if(copied<=0) { Print("Ошибка: Ошибка получения значений индикатора CyclePeriod."); return -1; } AdaptiveRVIPeriod = int(floor((4*CyclePeriod[0]+3*CyclePeriod[1]+2*CyclePeriod[2]+CyclePeriod[3])/20.0)); //--- проверка количества баров if(rates_total<=AdaptiveRVIPeriod+AVERAGE_PERIOD+2) return(0); // возвращаем 0 //--- проверка возможных ошибок if(prev_calculated<0) return(0); // возвращаем 0 //--- последний рассчитанный бар будет пересчитан nLimit=AdaptiveRVIPeriod+2; if(prev_calculated>AdaptiveRVIPeriod+TRIANGLE_PERIOD+2) nLimit=prev_calculated-1; //--- установка empty value для нерасчитанных баров if(prev_calculated==0) { for(i=0;i<AdaptiveRVIPeriod+TRIANGLE_PERIOD;i++) ExtRVIBuffer[i]=0.0; for(i=0;i<AdaptiveRVIPeriod+AVERAGE_PERIOD;i++) ExtSignalBuffer[i]=0.0; } //--- RVI вычисляется в 1-м буфере for(i=nLimit;i<rates_total && !IsStopped();i++) { copied=CopyBuffer(hCyclePeriod,0,rates_total-i-1,4,CyclePeriod); if(copied<=0) { Print("FAILURE: Could not get values from CyclePeriod indicator."); return -1; } AdaptiveRVIPeriod = int(floor((4*CyclePeriod[0]+3*CyclePeriod[1]+2*CyclePeriod[2]+CyclePeriod[3])/20.0)); dNum=0.0; dDeNum=0.0; for(j=i;j>MathMax(i-AdaptiveRVIPeriod, 3);j--) { //Print("rates_total="+IntegerToString(rates_total)+" nLimit="+IntegerToString(nLimit)+ // " AdaptiveRVIPeriod="+IntegerToString(AdaptiveRVIPeriod)+" j="+IntegerToString(j)); dValueUp=Close[j]-Open[j]+2*(Close[j-1]-Open[j-1])+2*(Close[j-2]-Open[j-2])+Close[j-3]-Open[j-3]; dValueDown=High[j]-Low[j]+2*(High[j-1]-Low[j-1])+2*(High[j-2]-Low[j-2])+High[j-3]-Low[j-3]; dNum+=dValueUp; dDeNum+=dValueDown; } if(dDeNum!=0.0) ExtRVIBuffer[i]=dNum/dDeNum; else ExtRVIBuffer[i]=dNum; } //--- сигнальная линия вычисляется во 2-м буфере nLimit=AdaptiveRVIPeriod+TRIANGLE_PERIOD+2; if(prev_calculated>AdaptiveRVIPeriod+AVERAGE_PERIOD+2) nLimit=prev_calculated-1; for(i=nLimit;i<rates_total && !IsStopped();i++) ExtSignalBuffer[i]=(ExtRVIBuffer[i]+2*ExtRVIBuffer[i-1]+2*ExtRVIBuffer[i-2]+ExtRVIBuffer[i-3])/AVERAGE_PERIOD; //--- Завершение OnCalculate. Возвратим новое значение prev_calculated. return(rates_total); } //+------------------------------------------------------------------+
Скриншот адаптивного индикатора RVI с динамической шириной окна:
Выводы
В статье рассмотрены адаптивные версии трех технических индикаторов индикаторов и представлена их реализация на MQL5.
После чтения статьи, я надеюсь, теперь вам понятен механизм написания адаптивных индикаторов.
Автор призывает к экспериментам и созданию адаптивных версий других индикаторов.
Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/288





- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования
Статья интересная , только вот почему то я так и не смог посмотреть в деле не одного адаптированного индюка . Все которые были предложены на скачивание не захотели работать. Для меня MQL5 язык новый и разбираться самому сложновато. Или может я что не так сделал?
Индикаторы CenterOfGravity, CyberCycle, CyclePeriod работают исправно.
Индикаторы AdaptiveRVI, AdaptiveCenterOfGravity, AdaptiveCyberCycle используют в своем коде индикатор CyberCycle. Имя индикатора указано явно.
Скорее всего Вы положили индикаторы в поддиректорию, в следствии чего AdaptiveRVI, AdaptiveCenterOfGravity и AdaptiveCyberCycle не запустились. Проблема решаема 2мя путями:
1. Разместить индикатор CyberCycle в корне.
2. В коде индикаторов AdaptiveRVI, AdaptiveCenterOfGravity, AdaptiveCyberCycle прописать путь до индикатора CyberCycl (например: Examples\\CyberCycle)
Индикаторы CenterOfGravity, CyberCycle, CyclePeriod работают исправно.
Индикаторы AdaptiveRVI, AdaptiveCenterOfGravity, AdaptiveCyberCycle используют в своем коде индикатор CyberCycle. Имя индикатора указано явно.
Скорее всего Вы положили индикаторы в поддиректорию, в следствии чего AdaptiveRVI, AdaptiveCenterOfGravity и AdaptiveCyberCycle не запустились. Проблема решаема 2мя путями:
1. Разместить индикатор CyberCycle в корне.
2. В коде индикаторов AdaptiveRVI, AdaptiveCenterOfGravity, AdaptiveCyberCycle прописать путь до индикатора CyberCycl (например: Examples\\CyberCycle)
Разместил все индикаторы в одной папке Examples, но все Adaptive так и не заработали.
Что делать?