Книга: Язык программирования MQL5: Продвинутое использование торговой платформы MetaTrader 5
Назад: Функция PlaySound
Дальше: Общая структура советника

Объектно-ориентированный подход

В объектно-ориентированных языках все – это классы. В том числе и точка входа в приложение является классом, который содержит специфический метод – точку входа в приложение, или который расширяет специфический класс фреймворка или платформы.

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

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

Напомним базовые понятия объектно-ориентированного программирования.

Инкапсуляция – это когда код представлен классами, которые предоставляют открытые методы для доступа и изменения данных, таким образом защищая данные.

Расширяемость типов – это возможность добавлять пользовательские типы данных, что как раз основано на использовании классов, так как каждый новый пользовательский класс представляет новый тип данных.

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

Полиморфизм – это возможность иметь всем классам одной и той же иерархии наследования метод с одним именем, но разной реализацией.

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

В качестве примера использования объектно-ориентированного подхода рассмотрим создание нашего пользовательского индикатора Impulse keeper с применением классов.

В данном случае, использование класса CIndicator и его наследников CiMA и CiSAR, обеспечивающих доступ к индикаторам MA и PSAR, позволяет вообще обойтись без буферов индикатора Impulse keeper. Так как они были нужны нам для копирования буферов индикаторов MA и PSAR, а классы CiMA и CiSAR предоставляют напрямую доступ к своим буферам.

Для использования класса CIndicator и его потомков, в код необходимо включить файл Trend.mqh:

#include <Indicators\Trend.mqh>

Далее в коде индикатора Impulse keeper объявим экземпляры классов CiMA и CiSAR:

#property indicator_chart_window



CiMA MA34H;

CiMA MA34L;

CiMA MA125;

CiSAR SAR;



int bars_calculated=0;

В функции OnInit () создадим индикаторы:

int OnInit ()

{

MA34H.Create (_Symbol, PERIOD_CURRENT,34,0,MODE_EMA, PRICE_HIGH);

MA34L.Create (_Symbol, PERIOD_CURRENT,34,0,MODE_EMA, PRICE_LOW);

MA125.Create (_Symbol, PERIOD_CURRENT,125,0,MODE_EMA, PRICE_CLOSE);

SAR.Create (_Symbol, PERIOD_CURRENT,0.02, 0.2);

// – —

return (INIT_SUCCEEDED);

}

В функции OnCalculate после вычисления начальной позиции расчета индикатора установим размеры буферов индикаторов CiMA и CiSAR:

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 start;

int calculated=MA34H.BarsCalculated ();

if (calculated <=0)

{

return (0);

}

if (prev_calculated==0 || calculated!=bars_calculated)

{

start=rates_total-1;

}

else

{

start=1;

}

ArraySetAsSeries (time, true);

ArraySetAsSeries (high, true);

ArraySetAsSeries (low, true);

ArraySetAsSeries (open, true);

ArraySetAsSeries (close, true);



//Print (MA34H. BufferSize ());



MA34H. BufferResize (rates_total);

MA34L. BufferResize (rates_total);

MA125.BufferResize (rates_total);

SAR. BufferResize (rates_total);

Если это не сделать, размеры буферов используемых индикаторов будут по умолчанию иметь величину 100, и наш индикатор будет рассчитываться только до 100 бара.

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

MA34H.Refresh ();

MA34L.Refresh ();

MA125.Refresh ();

SAR.Refresh ();

for (int i=start; i> =1;i – )

{

if(close[i]>open[i]&&close[i]>MA34H.Main(i)&&close[i]>MA34L.Main(i)&&low[i]>MA125.Main(i)&&low[i]>SAR.Main(i)&&MA125.Main(i)<MA34L.Main(i)&&MA125.Main(i)<MA34H.Main (i)) {

if (!ObjectCreate (0,«Buy»+i, OBJ_ARROW,0,time [i],high [i]))

{

return (false);

}

ObjectSetInteger (0,«Buy»+i, OBJPROP_COLOR, clrGreen);

ObjectSetInteger (0,«Buy»+i, OBJPROP_ARROWCODE,233);

ObjectSetInteger (0,«Buy»+i, OBJPROP_WIDTH,2);

ObjectSetInteger (0,«Buy»+i, OBJPROP_ANCHOR, ANCHOR_UPPER);

ObjectSetInteger (0,«Buy»+i, OBJPROP_HIDDEN, true);

ObjectSetString (0,«Buy»+i, OBJPROP_TOOLTIP, close [i]);

}

if(close[i]<open[i]&&close[i]<MA34H.Main(i)&&close[i]<MA34L.Main(i)&&high[i]<MA125.Main(i)&&high[i]<SAR.Main(i)&&MA125.Main(i)>MA34L.Main(i)&&MA125.Main(i)>MA34H.Main (i)) {

if (!ObjectCreate (0,«Sell»+i, OBJ_ARROW,0,time [i],low [i]))

{

return (false);

}

ObjectSetInteger (0,«Sell»+i, OBJPROP_COLOR, clrRed);

ObjectSetInteger (0,«Sell»+i, OBJPROP_ARROWCODE,234);

ObjectSetInteger (0,«Sell»+i, OBJPROP_WIDTH,2);

ObjectSetInteger (0,«Sell»+i, OBJPROP_ANCHOR, ANCHOR_LOWER);

ObjectSetInteger (0,«Sell»+i, OBJPROP_HIDDEN, true);

ObjectSetString (0,«Sell»+i, OBJPROP_TOOLTIP, close [i]);

}

}

ChartRedraw (0);

bars_calculated=calculated;

// – - return value of prev_calculated for next call

return (rates_total);

}

//+ – — – — – — – — – — – — – — – — – — – — – — – — – — – — – — – — – +

void OnDeinit (const int reason) {

ObjectsDeleteAll (0, -1, -1);

}

Чтобы быть последовательными в объектно-ориентированном подходе, весь код по расчету и отрисовке нашего индикатора можно выделить в отдельный пользовательский класс. Благо мастер редактора MQL5 предоставляет возможность создания каркаса пользовательского класса.

#include <Indicators\Trend.mqh>

//+ – — – — – — – — – — – — – — – — – — – — – — – — – — – — – — – — – +

//| |

//+ – — – — – — – — – — – — – — – — – — – — – — – — – — – — – — – — – +

class IKSignal

{

private:

int _start;

datetime _time [];

double _open [];

double _high [];

double _low [];

double _close [];



public:

IKSignal (

int start,

const datetime &time [],

const double &open [],

const double &high [],

const double &low [],

const double &close []

);

bool draw (CiMA &MA34H, CiMA &MA34L, CiMA &MA125, CiSAR &SAR);

~IKSignal ();

};

//+ – — – — – — – — – — – — – — – — – — – — – — – — – — – — – — – — – +

//| |

//+ – — – — – — – — – — – — – — – — – — – — – — – — – — – — – — – — – +

IKSignal::IKSignal (int start,

const datetime &time [],

const double &open [],

const double &high [],

const double &low [],

const double &close []

)

{

_start=start;

if (ArraySize (time)> 0)

{

ArrayResize (_time, ArraySize (time));

ArrayCopy (_time, time);

}

if (ArraySize (open)> 0)

{

ArrayResize (_open, ArraySize (open));

ArrayCopy (_open, open);

}

if (ArraySize (high)> 0)

{

ArrayResize (_high, ArraySize (high));

ArrayCopy (_high, high);

}

if (ArraySize (low)> 0)

{

ArrayResize (_low, ArraySize (low));

ArrayCopy (_low, low);

}

if (ArraySize (close)> 0)

{

ArrayResize (_close, ArraySize (close));

ArrayCopy (_close, close);

}

ArraySetAsSeries (_time, true);

ArraySetAsSeries (_high, true);

ArraySetAsSeries (_low, true);

ArraySetAsSeries (_open, true);

ArraySetAsSeries (_close, true);

}

//+ – — – — – — – — – — – — – — – — – — – — – — – — – — – — – — – — – +

//| |

//+ – — – — – — – — – — – — – — – — – — – — – — – — – — – — – — – — – +

IKSignal::~IKSignal ()

{

}

//+ – — – — – — – — – — – — – — – — – — – — – — – — – — – — – — – — – +

bool IKSignal::draw (CiMA &MA34H, CiMA &MA34L, CiMA &MA125, CiSAR &SAR) {

for (int i=_start; i> =1;i – )

{if(_close[i]>_open[i]&&_close[i]>MA34H.Main(i)&&_close[i]>MA34L.Main(i)&&_low[i]>MA125.Main(i)&&_low[i]>SAR.Main(i)&&MA125.Main(i)<MA34L.Main(i)&&MA125.Main(i)<MA34H.Main (i)) {

if (!ObjectCreate (0,«Buy»+i, OBJ_ARROW,0,_time [i],_high [i]))

{

return (false);

}

ObjectSetInteger (0,«Buy»+i, OBJPROP_COLOR, clrGreen);

ObjectSetInteger (0,«Buy»+i, OBJPROP_ARROWCODE,233);

ObjectSetInteger (0,«Buy»+i, OBJPROP_WIDTH,2);

ObjectSetInteger (0,«Buy»+i, OBJPROP_ANCHOR, ANCHOR_UPPER);

ObjectSetInteger (0,«Buy»+i, OBJPROP_HIDDEN, true);

ObjectSetString (0,«Buy»+i, OBJPROP_TOOLTIP,_close [i]);

}

if(_close[i]<_open[i]&&_close[i]<MA34H.Main(i)&&_close[i]<MA34L.Main(i)&&_high[i]<MA125.Main(i)&&_high[i]<SAR.Main(i)&&MA125.Main(i)>MA34L.Main(i)&&MA125.Main(i)>MA34H.Main (i)) {

if (!ObjectCreate (0,«Sell»+i, OBJ_ARROW,0,_time [i],_low [i]))

{

return (false);

}

ObjectSetInteger (0,«Sell»+i, OBJPROP_COLOR, clrRed);

ObjectSetInteger (0,«Sell»+i, OBJPROP_ARROWCODE,234);

ObjectSetInteger (0,«Sell»+i, OBJPROP_WIDTH,2);

ObjectSetInteger (0,«Sell»+i, OBJPROP_ANCHOR, ANCHOR_LOWER);

ObjectSetInteger (0,«Sell»+i, OBJPROP_HIDDEN, true);

ObjectSetString (0,«Sell»+i, OBJPROP_TOOLTIP,_close [i]);

}

}

ChartRedraw (0);

return (true);

}

Здесь в классе IKSignal мы объявляем закрытые поля класса, представляющие начальную позицию расчета индикатора и ценовую историю.

В конструкторе класса мы копируем его параметры в поля класса и меняем порядок доступа к полям-массивам.

Также в классе объявляется открытая функция draw, в которой фактически и будет производиться расчет и отрисовка индикатора.

В качестве параметров этой функции выступают экземпляры классов CiMA и CiSAR.

Тут мы просто переносим код из функции OnCalculate.

Теперь в коде основного файла нам не нужно включать файл Trend.mqh, так как мы уже сделали это в коде класса IKSignal, вместо этого нам нужно включить файл класса IKSignal.

Поместим файл класса IKSignal в каталог Include и включим его в основной файл индикатора:

#include <IKSignal.mqh>

Теперь функция OnCalculate примет следующий вид:

int OnCalculate (const int rates_total,

const int prev_calculated,

const datetime &time [],

const double &open [],

const double &high [],

const double &low [],

const double &close [],

const long &tick_volume [],

const long &volume [],

const int &spread [])

{

// – —

int start;

int calculated=MA34H.BarsCalculated ();

if (calculated <=0)

{

return (0);

}

if (prev_calculated==0 || calculated!=bars_calculated)

{

start=rates_total-1;

}

else

{

start=1;

}

IKSignal iks (start, time, open, high, low, close)

//Print (MA34H. BufferSize ());



MA34H. BufferResize (rates_total);

MA34L. BufferResize (rates_total);

MA125.BufferResize (rates_total);

SAR. BufferResize (rates_total);



MA34H.Refresh ();

MA34L.Refresh ();

MA125.Refresh ();

SAR.Refresh ();



if (!iks. draw (MA34H, MA34L, MA125, SAR)) {

return (false);

}

bars_calculated=calculated;

// – - return value of prev_calculated for next call

return (rates_total);

}

Здесь мы создаем экземпляр класса IKSignal с указанными в правильном порядке параметрами и применяем к нему функцию draw.

Как видно, код основного файла индикатора значительно упрощается.

Назад: Функция PlaySound
Дальше: Общая структура советника