
Простое создание сложных индикаторов с помощью объектов
1. Введение
Если вы когда-нибудь пробовали создавать или модифицировать сложный индикатор, вы наверняка сталкивались с проблемами, возникающими при увеличении количества буферов: нужно объявлять тонны двойных массивов, устанавливать их как буферы и настраивать...
А ведь еще есть и графики: необходимо объявить тип графика, настроить все его свойства, а затем убедиться, что всё правильно стыкуется друг с другом и что вы объявили правильное количество буферов и графиков (если их меньше, вы получите ошибку выхода за пределы массива Array Out Of Range или невидимый график, который будет обнаружен только в ходе дальнейшей работы).
Наконец дело доходит до буферных данных: если вы хотите объединить данные большого количества буферов (например, получить среднее/максимум/минимум 10 буферов в отдельном буфере), то вам нужно будет написать очень длинные строки повторяющегося кода, сравнивающего/объединяющего каждый буфер, или применять трюки с макросами или функциями, чтобы сэкономить место. В результате вы, скорее всего, получите сложный код, подверженный ошибкам, с кучей строк и дублирующимся функционалом. Если вы допустили где-то хоть одну опечатку, найти ее будет сущим кошмаром!
Подобные ситуации могут отпугнуть начинающих (и даже продвинутых) программистов от создания визуально или функционально сложных индикаторов. Однако есть небольшая неочевидная хитрость, которая может сделать кодирование индикаторов быстрее и проще:
Вы можете установить в качестве буферов массивы, содержащиеся внутри объектов
В этой статье я покажу, что дает нам эта особенность и как ее можно использовать в объектно-ориентированном программировании.
2. Первый пример
Прежде чем мы начнем создавать индикатор, давайте посмотрим, как выглядит самая простая форма объекта, который будет содержать буферные массивы:
class CIndicatorPlot { public: double array[]; };
Здесь есть только публичный массив. Это необходимо, чтобы мы могли получить к нему доступ при установке его в качестве буфера или настройке/доступе к его данным (как и в случае с любым другим индикатором).
Создадим индикатор, который будет отображать 10 RSI с разными периодами и их среднее значение. Начнем со свойств, входных параметров и функции OnInit .
#property indicator_buffers 11 #property indicator_plots 11 input int firstPeriod = 6; input int increment = 2; CIndicatorPlot indicators[]; int handles[]; //+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { ArrayResize(indicators, 11); //--- indicator buffers mapping for (int i=0; i<11; i++) { SetIndexBuffer(i, indicators[i].array, INDICATOR_DATA); PlotIndexSetInteger(i, PLOT_DRAW_TYPE, DRAW_LINE); } for (int i=0; i<10; i++) PlotIndexSetInteger(i, PLOT_LINE_COLOR, clrRed); PlotIndexSetInteger(10, PLOT_LINE_COLOR, clrCyan); PlotIndexSetInteger(10, PLOT_LINE_STYLE, STYLE_DASH); PlotIndexSetInteger(10, PLOT_LINE_WIDTH, 2); ArrayResize(handles, 10); for (int i=0; i<10; i++) handles[i] = iRSI(NULL, PERIOD_CURRENT, firstPeriod+i*increment, PRICE_CLOSE); //--- return(INIT_SUCCEEDED); }
Обратите внимание, что мы использовали только два свойства: indicator_buffers и indicator_plots. Это два постоянно используемых свойства, если не считать общих (copyright, link, version, separate/chart window и др.). Другие свойства (цвет линии, тип отрисовки...) являются необязательными, но для более компактного кода мы настроим их с помощью PlotIndexSetInteger в цикле.
Для этого индикатора нам понадобятся 10 буферов для каждого RSI с разным периодом и еще один для их среднего значения. Мы поместим их все внутрь массива, а также создадим хэндлы индикатора в OnInit.
Теперь проведем вычисления и копирование данных...
//+------------------------------------------------------------------+ //| 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[]) { //--- int limit = MathMax(0, prev_calculated-1); for (int i=0; i<10; i++) { if (limit==0) CopyBuffer(handles[i], 0, 0, rates_total-limit, indicators[i].array); else { double newValues[]; CopyBuffer(handles[i], 0, 0, rates_total-limit, newValues); for (int k=0; k<rates_total-limit; k++) { indicators[i].array[limit+k] = newValues[k]; } } } for (int i=limit; i<rates_total; i++) { indicators[10].array[i] = 0.0; for (int j=0; j<10; j++) indicators[10].array[i] +=indicators[j].array[i]; indicators[10].array[i]/=10.0; } //--- return value of prev_calculated for next call return(rates_total); }
Обратите внимание, что вычисление среднего из всех буферов теперь так же просто, как выполнение цикла. Если бы каждый буфер был объявлен на глобальном уровне как двойной массив (как обычно), их сложение было бы не таким простым и заняло бы большее количество строк.
Не забудьте отпустить хэндлы...
//+------------------------------------------------------------------+ //| Custom indicator deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { for (int i=0; i<10; i++) IndicatorRelease(handles[i]); } //+------------------------------------------------------------------+
Результат показан ниже:
Не так уж плохо, учитывая длину кода, но можно сделать лучше. Займемся улучшением в следующем разделе.
3. Добавляем больше возможностей
Несмотря на то, что мы сэкономили немного места, используя этот класс и настроив свойства инициализации (вместо #property), нам все равно пришлось вручную настраивать буферы и графики, что в обычных условиях не всегда просто. Можно ли упростить этот процесс? Да, можно. Для этого нужно делегировать ряд функций классу.
Во-первых, добавим в класс несколько дополнительных функций, которые нам понадобятся позже.
class CIndicatorPlot { private: int indicator_plot; public: double array[]; void SetBuffer(int &buffer, int &plot); void SetLineWidth(int width); void SetLineStyle(ENUM_LINE_STYLE style); void SetLineColor(color line_color); void SetLabel(string label); };
Функция SetBuffer установит индикаторный буфер и график. Когда две переменные передаются по ссылке, изменения, вносимые в них одним объектом, будут отражаться в следующих вызовах другими объектами. Индекс графика сохраняется для установки других свойств.
Остальные функции множества являются простыми установщиками свойств графика.
//+------------------------------------------------------------------+ void CIndicatorPlot::SetBuffer(int &buffer,int &plot) { indicator_plot = plot; SetIndexBuffer(buffer, array, INDICATOR_DATA); PlotIndexSetInteger(indicator_plot, PLOT_DRAW_TYPE, DRAW_LINE); buffer++; //Increment for other steps (One buffer in this case) plot++; //Increment one plot in any case } //+------------------------------------------------------------------+ void CIndicatorPlot::SetLineWidth(int width) { PlotIndexSetInteger(indicator_plot, PLOT_LINE_WIDTH, width); } //--- //...
Чтобы сделать индикатор визуально более привлекательным, создадим функцию для интерполяции цветов, которую будем использовать позже:
//+------------------------------------------------------------------+ //| Function to linearly interpolate 2 colors | //+------------------------------------------------------------------+ color InterpolateColors(color colorA, color colorB, double factor) { if (factor<=0.0) return colorA; if (factor>=1.0) return colorB; int result = 0; for (int i=0; i<3; i++) //R-G-B { int subcolor = int( ((colorA>>(8*i))&(0xFF))*(1.0-factor) + ((colorB>>(8*i))&(0xFF))*factor ); subcolor = subcolor>0xFF?0xFF:( subcolor<0x00?0x00: subcolor); result |= subcolor<<(8*i); } return (color)result; }
Now the OnInit function looks like this:
CIndicatorPlot* indicators[]; CIndicatorPlot average; int handles[]; //+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- indicator buffers mapping ArrayResize(indicators, 10); ArrayResize(handles, 10); int index=0, plot=0; for (int i=0; i<10; i++) { indicators[i] = new CIndicatorPlot(); indicators[i].SetBuffer(index, plot); indicators[i].SetLineColor(InterpolateColors(clrYellow, clrRed, i/9.0)); indicators[i].SetLabel("RSI ("+IntegerToString(firstPeriod+i*increment)+")"); handles[i] = iRSI(NULL, PERIOD_CURRENT, firstPeriod+i*increment, PRICE_CLOSE); } average.SetBuffer(index, plot); average.SetLineColor(clrBlue); average.SetLineStyle(STYLE_DASH); average.SetLineWidth(2); average.SetLabel("Average"); //--- return(INIT_SUCCEEDED); }
Обратите внимание, что мы нигде не ссылались на какой-либо буфер или график по его номеру. С этой проблемой справились классы. Теперь проще правильно установить любое свойство графика или изменить порядок буферов, поскольку мы можем ссылаться на них с помощью объекта, а не индекса. Я также добавил цвета и метки к графикам.
В этом примере я также изменил структуру индикатора, используя массив указателей для RSI (чтобы доказать возможность использования динамически создаваемых объектов) и отделив среднее значение от массива. Теперь необходимо изменить ссылки на среднее значение в OnCalculate и удалить индикаторы в массиве указателей в OnDeInit.
void OnDeinit(const int reason) { for (int i=0; i<10; i++) IndicatorRelease(handles[i]); for (int i=0; i<10; i++) delete indicators[i]; }
Результат выглядит так:
Единственное визуальное изменение — это цвета (и метки в окне данных). Внутренне мы улучшили наш рабочий процесс, упростив работу с графиками и буферами, но внутри еще есть возможности для улучшения его организации.
Если хорошо присмотреться, видно, что каждый дескриптор используется только одним из буферов: каждый буфер RSI может быть рассчитан независимо, поэтому мы можем заставить класс проводить расчеты внутри (а не в OnCalculate). Среднему значению требуется доступ к остальным буферам, но эти вычисления также можно делегировать классу. Мы можем использовать наследование, чтобы добавить определенный функционал без изменения функций или добавления условий в базовый класс.
Во-первых, добавим в базовый класс виртуальные пустые обработчики событий:
class CIndicatorPlot { //... public: //... virtual void Init() { } virtual void DeInit() { } virtual void Update(const int start, const int rates_total) { } };
Как мы уже видели ранее в этом примере, Update нуждается лишь в start и rates_total для выполнения своих вычислений, поэтому остальные значения опускаются.
Теперь создадим класс Individual RSI для создания и удаления необходимых дескрипторов. Кроме того, я включил функцию для установки периода этого хэндла, но также можно включить этот параметр в Init().
class CRSIIndividual : public CIndicatorPlot { private: int handle; int rsi_period; public: void SetPeriodRSI(int period); virtual void Init(); virtual void DeInit(); virtual void Update(const int start, const int rates_total); }; //+------------------------------------------------------------------+ void CRSIIndividual::SetPeriodRSI(int period) { rsi_period = period; } //+------------------------------------------------------------------+ void CRSIIndividual::Init(void) { handle = iRSI(NULL, PERIOD_CURRENT, rsi_period, PRICE_CLOSE); } //+------------------------------------------------------------------+ void CRSIIndividual::Update(const int start,const int rates_total) { if (start==0) CopyBuffer(handle, 0, 0, rates_total-start, array); else { double newValues[]; CopyBuffer(handle, 0, 0, rates_total-start, newValues); for (int k=0; k<rates_total-start; k++) { array[start+k] = newValues[k]; } } } //+------------------------------------------------------------------+ void CRSIIndividual::DeInit(void) { IndicatorRelease(handle); }
Для класса Average нам нужно хранить указатели для доступа к остальным объектам графика индикатора (Individual RSI). В этом случае Init() и DeInit() не нужны.
class CRSIAverage : public CIndicatorPlot { private: CRSIIndividual* rsi_indicators[]; public: void SetRSIPointers(const CRSIIndividual &rsi_objects[]); virtual void Update(const int start, const int rates_total); }; //+------------------------------------------------------------------+ void CRSIAverage::SetRSIPointers(const CRSIIndividual &rsi_objects[]) { int total = ArraySize(rsi_objects); ArrayResize(rsi_indicators, total); for (int i=0; i<total; i++) rsi_indicators[i] = (CRSIIndividual*)GetPointer(rsi_objects[i]); } //+------------------------------------------------------------------+ void CRSIAverage::Update(const int start,const int rates_total) { for (int i=start; i<rates_total; i++) { array[i] = 0.0; for (int j=0; j<10; j++) array[i] +=rsi_indicators[j].array[i]; array[i]/=10.0; } }
Создание массива указателей может показаться чрезмерным усложнением, ведь можно получить доступ к объектам непосредственно с глобального уровня, но это упростит повторное использование класса в других индикаторах без внесения дополнительных изменений. В этом примере мы снова будем использовать массив объектов вместо указателей для индикаторов RSI, поэтому нам нужно получить от них указатели.
На финальном этапе функция OnInit (и объявления объектов выше) будет выглядеть так...
CRSIIndividual indicators[]; CRSIAverage average; //+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- indicator buffers mapping ArrayResize(indicators, 10); int index=0, plot=0; for (int i=0; i<10; i++) { indicators[i].SetBuffer(index, plot); indicators[i].SetLineColor(InterpolateColors(clrYellow, clrRed, i/9.0)); indicators[i].SetLabel("RSI ("+IntegerToString(firstPeriod+i*increment)+")"); indicators[i].SetPeriodRSI(firstPeriod+i*increment); indicators[i].Init(); } average.SetBuffer(index, plot); average.SetLineColor(clrBlue); average.SetLineStyle(STYLE_DASH); average.SetLineWidth(2); average.SetLabel("Average"); average.SetRSIPointers(indicators); //--- return(INIT_SUCCEEDED); }
...и мы сможем сделать другие функции обработки событий намного чище:
//+------------------------------------------------------------------+ //| 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[]) { //--- int limit = MathMax(0, prev_calculated-1); for (int i=0; i<10; i++) indicators[i].Update(limit, rates_total); average.Update(limit, rates_total); //--- return value of prev_calculated for next call return(rates_total); } //+------------------------------------------------------------------+ //| Custom indicator deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { for (int i=0; i<10; i++) indicators[i].DeInit(); } //+------------------------------------------------------------------+
Визуально индикатор будет выглядеть точно так же, как и во втором примере.
4. Расширяем класс
На данный момент все эти классы хорошо справляются со своей работой, но они очень специфичны для типа индикатора, с которым мы имеем дело: мы использовали только несколько свойств графика и только линейный рисунок, но что если мы хотим использовать график с цветными буферами? Или гистограмму, или зигзаг?.. Чтобы повторно использовать то, что мы сделали, нам нужно будет обобщать классы. Для этого необходимо выполнить три условия:
- Нужно иметь возможность создавать любой тип графика/буфера или изменять свойства графика, не выходя из класса или не зная о деталях индексов буфера/графика.
- Нужно иметь возможность добавлять графики с любым стилем рисования (линии, гистограммы, свечи...), не беспокоясь о количестве и типах буферов, которые есть у каждого из них (тем не менее, вы всегда отвечаете за данные, которые вы размещаете в этих массивах).
- Нужно иметь возможность легко добавлять определенные функции к классам с помощью наследования (необязательно).
Имея это в виду, я сначала объясню, как реализованы классы и как структурировано наследование.
Во-первых, классы структурированы следующим образом:
- CIndicatorBufferBase
- CIndicatorCalculations
- CIndicatorPlotBase
- CIndicator_1Data
- CIndicatorPlotLine
- CIndicatorPlotHistogram
- ...
- CIndicator_1Data1Color
- CIndicatorPlotColorLine
- ...
- CIndicator_2Data
- CIndicatorPlotHistogram2
- ...
- CIndicator_2Data1Color
- CIndicatorPlotColorHistogram2
- ...
- CIndicator_4Data
- CIndicatorPlotCandles
- ...
- CIndicator_4Data1Color
- CIndicatorPlotColorCandles
- ...
Несколько ключевых моментов:
- Три точки означают, что существует больше классов, наследуемых от того же, что и выше (они отличаются только стилем рисования, который косвенно относится к каждому классу).
- Классы, отмеченные красным, являются абстрактными классами, которые не могут иметь копий, но могут хранить указатели на другие классы, производные от них (полиморфизм).
- Остальные классы наследуются от базового класса, который имеет соответствующее количество буферов данных/цвета. В этом случае полиморфизм тоже возможен, поскольку у вас может быть индикатор, которому требуется доступ к классу, имеющему один буфер данных, независимо от того, является ли он линией, гистограммой и т. д.
- Цветовые классы наследуются от буферов данных по той же причине, что и в пункте выше.
- CIndicatorCalculations используется для вспомогательных расчетных буферов, не имеющих графиков.
В обобщенном виде реализация выглядит так:
//+------------------------------------------------------------------+ //| Base class for plots and calculation buffers | //+------------------------------------------------------------------+ class CIndicatorBufferBase { public: virtual void SetBuffer(int &buffer, int &plot)=NULL; virtual void SetAsSeries(bool set)=NULL; virtual void Init() { } virtual void DeInit() { } virtual void Update(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[]) { } }; //+------------------------------------------------------------------+ //| Calculations Buffer (with no plot) | //+------------------------------------------------------------------+ class CIndicatorCalculations : public CIndicatorBufferBase { public: double array[]; virtual void SetBuffer(int &buffer, int &plot); virtual void SetAsSeries(bool set); }; //+------------------------------------------------------------------+ void CIndicatorCalculations::SetBuffer(int &index, int &plot) { SetIndexBuffer(index, array, INDICATOR_CALCULATIONS); index++; //No plot is used } //+------------------------------------------------------------------+ void CIndicatorCalculations::SetAsSeries(bool set) { ArraySetAsSeries(array, set); } //+------------------------------------------------------------------+ //| Base indicator plot class | //+------------------------------------------------------------------+ class CIndicatorPlotBase : public CIndicatorBufferBase { protected: int indicator_plot; virtual void SetDrawType()=NULL; //Implicit in each class public: void SetArrow(uchar arrow); void SetArrowShift(int shift); void SetDrawBegin(int begin); void SetShowData(bool show); void SetShift(int shift); void SetLineStyle(ENUM_LINE_STYLE style); void SetLineWidth(int width); void SetColorIndexes(color &color_array[]); void SetLineColor(color line_color); void SetLineColor(color line_color, int index); void SetEmptyValue(double empty); void SetLabel(string label); int GetInteger(ENUM_PLOT_PROPERTY_INTEGER property_id, int property_modifier=0); }; //... //... //+------------------------------------------------------------------+ //| Base for indicators with 1 Data Buffer | //+------------------------------------------------------------------+ class CIndicator_1Data : public CIndicatorPlotBase { public: double array[]; virtual void SetBuffer(int &buffer, int &plot); virtual void SetAsSeries(bool set); }; //+------------------------------------------------------------------+ void CIndicator_1Data::SetBuffer(int &buffer,int &plot) { indicator_plot = plot; SetIndexBuffer(buffer, array, INDICATOR_DATA); SetDrawType(); buffer++; plot++; } //... //+------------------------------------------------------------------+ //| Plot Line (1 data buffer) | //+------------------------------------------------------------------+ class CIndicatorPlotLine : public CIndicator_1Data { protected: virtual void SetDrawType() final; }; //+------------------------------------------------------------------+ void CIndicatorPlotLine::SetDrawType(void) { PlotIndexSetInteger(indicator_plot, PLOT_DRAW_TYPE, DRAW_LINE); } //... //... //+------------------------------------------------------------------+ //| Base for indicators with 2 Data Buffers | //+------------------------------------------------------------------+ class CIndicator_2Data : public CIndicatorPlotBase { public: double first_array[]; double second_array[]; virtual void SetBuffer(int &buffer, int &plot); virtual void SetAsSeries(bool set); }; //+------------------------------------------------------------------+ void CIndicator_2Data::SetBuffer(int &buffer, int &plot) { indicator_plot = plot; SetIndexBuffer(buffer, first_array, INDICATOR_DATA); SetIndexBuffer(buffer+1, second_array, INDICATOR_DATA); SetDrawType(); buffer+=2; plot++; } //... //... //+------------------------------------------------------------------+ //| Base for indicators with 1 Data Buffer & 1 Color Buffer | //+------------------------------------------------------------------+ class CIndicator_1Data1Color : public CIndicator_1Data { public: double color_buffer[]; virtual void SetBuffer(int &buffer, int &plot); virtual void SetAsSeries(bool set); }; //+------------------------------------------------------------------+ void CIndicator_1Data1Color::SetBuffer(int &buffer, int &plot) { CIndicator_1Data::SetBuffer(buffer, plot); SetIndexBuffer(buffer, color_buffer, INDICATOR_COLOR_INDEX); buffer++; //Add color buffer } //+------------------------------------------------------------------+ void CIndicator_1Data1Color::SetAsSeries(bool set) { CIndicator_1Data::SetAsSeries(set); ArraySetAsSeries(color_buffer, set); } //...
Каждый класс содержит (и устанавливает) необходимое количество буферов. CIndicatorBufferBase имеет обработчики событий, которые могут быть опционально переопределены классом буфера любого типа. CIndicatorPlotBase содержит установщики всех свойств графика (и один получатель). Каждый класс базовых данных (с цветом или без него) содержит объявления массивов и функции настройки для буферов, и каждый специфический класс переопределяет функцию SetDrawType() и объявляет его окончательным, чтобы его нельзя было переопределить снова (если вам нужен класс с неопределенным типом рисования, вы можете наследовать от соответствующего базового класса данных и переопределить эту функцию).
Следует отметить, что в этой реализации Update имеет все значения, которые используются в событии OnCalculate, но их можно переопределить с меньшим количеством параметров, если вам не нужно использовать полиморфизм.
ArraySetAsSeries также был включен, так как это очень распространенная функция, которая почти всегда требует, чтобы все буферы устанавливались одинаково.
Теперь, когда у нас есть классы, мы можем создать индикатор. Добавим кое-что еще в качестве примера:
- Сначала создадим полосы на основе индикатора ATR и отобразим их заполненными цветом.
- Затем создадим 10 скользящих средних с разными периодами и отобразим их на графике в виде линий.
- Наконец используем окраску свечей, чтобы менять окраску свечей в зависимости от того, сколько скользящих средних находится выше/ниже полос.
Сначала объявим входные данные и включим файлы для классов индикаторов и цветовую интерполяцию, которую мы сделали в разделе 3:
#property indicator_buffers 19 #property indicator_plots 13 #include <OOPIndicators/IndicatorClass.mqh> #include <OOPIndicators/ColorLerp.mqh> input int atr_period = 10; //ATR Period input double atr_band_multiplier = 0.8; //ATR Multiplier for bands input bool show_bands = true; //Show Bands input bool show_data = false; //Show Extra Data input int ma_faster_period = 14; //MA Faster Period input int ma_step = 2; //MA Step input ENUM_MA_METHOD ma_method = MODE_SMA; //MA Method
Мы уже определили количество необходимых буферов и графиков. Нам необязательно знать заранее, сколько нам потребуется тех и других, но, как вы увидите ниже, можно довольно легко узнать значения (в OnInit()).
Затем мы создадим классы для каждой части индикатора.
Начнем с диапазонов ATR:
//+------------------------------------------------------------------+ //| ATR Bands class (inherit from Filling Plot) | //+------------------------------------------------------------------+ class CATRBand : public CIndicatorPlotFilling { private: int handle; public: virtual void Init(); virtual void DeInit(); virtual void Update(const int limit, const int rates_total, const double &close[]); }; //+------------------------------------------------------------------+ void CATRBand::Init(void) { handle = iATR(NULL, PERIOD_CURRENT, atr_period); } //+------------------------------------------------------------------+ void CATRBand::Update(const int limit,const int rates_total,const double &close[]) { double atr[]; CopyBuffer(handle, 0, 0, rates_total-limit, atr); for (int i=limit; i<rates_total; i++) { first_array[i] = close[i]+atr[i-limit]*atr_band_multiplier; second_array[i] = close[i]-atr[i-limit]*atr_band_multiplier; } } //+------------------------------------------------------------------+ void CATRBand::DeInit(void) { IndicatorRelease(handle); }
Класс скользящей средней, имеющий параметры в Init() для периода и метода:
//+------------------------------------------------------------------+ //| Moving Averages class (inherit from Line Plot) | //+------------------------------------------------------------------+ class CMA : public CIndicatorPlotLine { private: int handle; public: virtual void Init(int period, ENUM_MA_METHOD mode); virtual void DeInit(); virtual void Update(const int limit, const int rates_total); }; //+------------------------------------------------------------------+ void CMA::Init(int period, ENUM_MA_METHOD mode) { handle = iMA(NULL, PERIOD_CURRENT, period, 0, mode, PRICE_CLOSE); } //+------------------------------------------------------------------+ void CMA::Update(const int limit,const int rates_total) { if (limit==0) CopyBuffer(handle, 0, 0, rates_total, array); else { double newVals[]; CopyBuffer(handle, 0, 0, rates_total-limit, newVals); for (int i=limit; i<rates_total; i++) array[i] = newVals[i-limit]; } } //+------------------------------------------------------------------+ void CMA::DeInit(void) { IndicatorRelease(handle); }
А также класс свечей. В этом случае и во избежание дополнительной сложности примера мы будем обращаться к объектам глобального уровня. Однако я не рекомендую так поступать, если вы планируете повторно использовать какой-либо класс.
Он также содержит макросы, которые мы также объявим ниже. Примечание: функции находятся ниже макроса в коде, но последовательность функций было изменена в статье.
//+------------------------------------------------------------------+ //| Color Candles class (inherit from Color Candles Plot) | //+------------------------------------------------------------------+ class CColorCandles : public CIndicatorPlotColorCandles { public: virtual void Update(const int limit, const int rates_total, const double &open[], const double &high[], const double &low[], const double &close[]); }; //+------------------------------------------------------------------+ void CColorCandles::Update(const int limit, const int rates_total, const double &open[], const double &high[], const double &low[], const double &close[]) { for (int i=limit; i<rates_total; i++) { open_array[i] = open[i]; high_array[i] = high[i]; low_array[i] = low[i]; close_array[i] = close[i]; int count_ma = TOTAL_MA; for (int m=0; m<TOTAL_MA; m++) { if (maIndicators[m].array[i] > bands.first_array[i]) count_ma++; if (maIndicators[m].array[i] < bands.second_array[i]) count_ma--; } color_buffer[i] = count_ma; //Update inside of this other object (to avoid making an extra inheritance, or an external loop) showIndex.array[i] = TOTAL_MA - count_ma; } }
Теперь нам нужно объявить объекты и настроить визуальные эффекты буферов и графиков:
#define TOTAL_MA 10 CMA maIndicators[TOTAL_MA]; CATRBand bands; CColorCandles candles; CIndicatorPlotNone showIndex; //To show MAs above/below //+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- indicator buffers mapping int buffer=0, plot=0; bands.SetBuffer(buffer, plot); candles.SetBuffer(buffer, plot); for (int i=0; i<TOTAL_MA; i++) maIndicators[i].SetBuffer(buffer, plot); showIndex.SetBuffer(buffer, plot); //Print("Buffers: ", buffer, " Plots: ", plot); //--- plot settings if (show_bands) bands.SetLineColor(clrDarkSlateGray); else bands.SetLineColor(clrNONE); bands.SetShowData(show_data); if (show_data) bands.SetLabel("Close + ATR;Close - ATR"); for (int i=0; i<TOTAL_MA; i++) { maIndicators[i].SetLineColor(InterpolateColors(clrAqua, clrRoyalBlue, i/(TOTAL_MA-1.0))); maIndicators[i].SetLabel("MA("+IntegerToString(ma_faster_period+i*ma_step)+")"); maIndicators[i].SetShowData(show_data); if (i>0 && i <TOTAL_MA-1) maIndicators[i].SetLineStyle(STYLE_DOT); else maIndicators[i].SetLineWidth(2); } color arrow_colors[TOTAL_MA*2+1]; for (int i=0; i<TOTAL_MA; i++) arrow_colors[i] = InterpolateColors(clrGreenYellow, clrGray, i/double(TOTAL_MA)); arrow_colors[TOTAL_MA] = clrGray; for (int i=TOTAL_MA+1; i<TOTAL_MA*2+1; i++) arrow_colors[i] = InterpolateColors(clrGray, clrOrange, (i-TOTAL_MA)/double(TOTAL_MA)); candles.SetColorIndexes(arrow_colors); candles.SetLabel("Open;High;Low;Close"); candles.SetShowData(false); showIndex.SetLabel("MAs above/below"); showIndex.SetShowData(true); //--- initialize classes bands.Init(); for (int i=0; i<TOTAL_MA; i++) maIndicators[i].Init(ma_faster_period+i*ma_step, ma_method); return(INIT_SUCCEEDED); }
Сначала настраиваются буферы, затем свойства графика, а затем инициализируются субиндикаторы (как указано в их классах).
Как было сказано ранее, вы можете легко узнать количество буферов и графиков, которые вам нужны, печатая значения переменных buffer и plot. Это позволит вам правильно установить свойства (в начале вы можете установить их на большее число, чем нужно, чтобы избежать ошибок).
Обратите также внимание, что мы включили экземпляр класса Plot None. Этот объект обновляется объектом свечей, поэтому ему не нужны специальные обработчики событий. Он отображает количество скользящих средних, которые находятся выше или ниже полос в окне данных.
Наконец, в других обработчиках событий функционала не так много, так как всё находится внутри объектов, нужно только вызывать функции из объектов в правильном порядке:
//+------------------------------------------------------------------+ //| 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[]) { //--- int limit = MathMax(0, prev_calculated-1); bands.Update(limit, rates_total, close); for (int i=0; i<TOTAL_MA; i++) maIndicators[i].Update(limit, rates_total); candles.Update(limit, rates_total, open, high, low, close); //--- return value of prev_calculated for next call return(rates_total); } //+------------------------------------------------------------------+ //| Custom indicator deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { bands.DeInit(); for (int i=0; i<TOTAL_MA; i++) maIndicators[i].DeInit(); }
Финальный результат выглядит так:
5. Ограничения метода
Несмотря на свое удобство, использование функций вместо свойств имеет несколько недостатков: наиболее заметным из них является интерференция при изменении цветов/стиля любого графика. Иногда они остаются, а иногда перезаписываются при инициализации.
Этой проблемы можно избежать, используя входные данные для цветов (вместо их изменения на вкладке Colors) или проверяя, есть ли цвет по умолчанию, отличный от черного (0x000000). Это работает со всеми цветами, кроме черного.
if (obj.GetInteger(PLOT_LINE_COLOR)==clrBlack) obj.SetLineColor(clrYellow);
Кроме того, в этой статье мы не анализировали влияние использования этих классов на производительность. Теоретически прямое использование свойств и меньшего количества функций должно ускорить работу, но в большинстве случаев незначительно.
Наконец, как вы могли заметить, классы не содержат обработчиков событий графика или OnTimer. Причина в том, что события графика лучше обрабатывать непосредственно в OnChartEvent, вызывая определенные функции после обработки (вместо того, чтобы вызывать обработчик для каждого индикатора каждый раз, когда происходит событие, и обрабатывать каждое событие несколько раз). В случае с таймером вы можете использовать обработчик обновления по-другому, если ваш индикатор является мультитаймфреймным или мультивалютным (у вас не будет прямого доступа к массивам OnCalculate). Другое решение, которое многим может показаться спорным, заключается в объявлении массивов, используемых в качестве буферов с публичной видимостью: можно установить массивы с защищенной видимостью. При этом индикатор по-прежнему будет работать, но вам может потребоваться добавить методы-получатели, чтобы получить доступ к данным извне.
6. Заключение
В этой статье мы разработали метод, позволяющий проще и с меньшим количеством строк создавать сложные индикаторы. Мы начали с небольших организационных приемов для конкретного случая, затем реализовали структуру классов, позволяющую повторно использовать и настраивать функционал, и, наконец, собрали всё вместе в пример индикатора, который использует большинство функций, описанных в статье.
Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/11233





- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования
Думал графические объекты имеются в виду - удивился и стал смотреть. Оказалось объекты классов) И в чем новизна?
Да просто хороший подход к реализации в статье, ничего нового, просто не плохое решение задачи.