В объектно-ориентированных языках все – это классы. В том числе и точка входа в приложение является классом, который содержит специфический метод – точку входа в приложение, или который расширяет специфический класс фреймворка или платформы.
С языком 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.
Как видно, код основного файла индикатора значительно упрощается.