English 中文 Español Deutsch 日本語 Português 한국어 Français Italiano Türkçe
preview
Разработка торгового советника с нуля (Часть 10): Доступ к пользовательским индикаторам

Разработка торгового советника с нуля (Часть 10): Доступ к пользовательским индикаторам

MetaTrader 5Торговые системы | 1 июня 2022, 12:01
1 690 5
Daniel Jose
Daniel Jose

Введение

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

Но добавление индикаторов на график MetaTrader 5 — не самая сложная часть. Доступ к данным, рассчитанным этими индикаторами непосредственно в советнике, без надлежащего планирования становится почти невыполнимой задачей. И если мы не знаем, как это сделать, мы будем ограничены стандартными индикаторами, а это не совсем то, что нам нужно. Примером может служить VWAP (Volume Weighted Average Price), которая является очень важной средней для тех, кто торгует фьючерсами на бразильской бирже. И хотя эта средняя не доступна по умолчанию среди индикаторов MetaTrader, никто нам не мешает создавать собственный индикатор, который будет выполнять расчет и отображать VWAP на экране. Однако, все становится гораздо сложнее, если мы хотим использовать этот же индикатор в системе, которая будет анализироваться в советнике. Без соответствующих знаний мы не сможем использовать пользовательский индикатор внутри советника. Здесь мы посмотрим, как обойти это ограничение и решить данную задачу.


Планирование

Первое, что нужно сделать — это попытаться создать расчеты, которые мы будем использовать в пользовательском индикаторе. К счастью, формула расчета VWAP, который мы будем использовать в качестве примера, довольно проста.


Если переводить это на язык программирования, в случае с MQL5 мы имеем следующее:

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[])
{
        double          Price = 0;
        ulong           Volume = 0;
        static int      siPos = 0;

        if (macroGetDate(time[rates_total - 1]) != macroGetDate(time[siPos]))
        {
                for (int c0 = rates_total - 1; macroGetDate(time[siPos]) != macroGetDate(time[c0]); siPos++);
                ArrayInitialize(VWAP_Buff, EMPTY_VALUE);
        }
        for (int c0 = siPos; c0 < rates_total; c0++)
        {
                Price += ((high[c0] + low[c0] + close[c0]) / 3) * volume[c0];
                Volume += volume[c0];
                VWAP_Buff[c0] = Price / Volume;
        }

    return rates_total;
}

Строка с расчетами выделена, а остальная часть функции используется для правильной инициализации DAILY VWAP. Всё это хорошо, но несмотря на то, что наш индикатор всё еще нельзя запустить на графике, нужно добавить еще несколько моментов в код, остальную часть которого можно увидеть ниже:

#property copyright "Daniel Jose - Indicador VWAP ( IntraDay )"
#property version "1.01"
#property indicator_chart_window
#property indicator_buffers     1
#property indicator_plots       1
#property indicator_width1      2
#property indicator_type1 	DRAW_LINE
#property indicator_color1 	clrBlack
//+------------------------------------------------------------------+
#define macroGetDate(A) (A - (A % 86400))
//+------------------------------------------------------------------+
double VWAP_Buff[];
//+------------------------------------------------------------------+
int OnInit()
{
        SetIndexBuffer(0, VWAP_Buff, INDICATOR_DATA);   
        
        return INIT_SUCCEEDED;
}

При этом у нас есть возможность перемещать VWAP на графике, как показано ниже:


Так, эта часть не была сложной. Теперь нужно найти способ заставить советника видеть VWAP, чтобы он каким-то конкретным образом проанализировал его. В таком виде можно будет извлечь из него пользу при торговле.

Чтобы было легче работать, расположим VWAP так, чтобы к нему было легко получить доступ.


Сразу после этого приступим к новому способу проектирования. Хотя индикатор VWAP, по сути, правильный, он неправильно запрограммирован для использования в советнике. Как же так? Проблема в том, что советник не сможет понять, находится ли индикатор на графике или нет. А не зная этого, он не сможет прочитать индикатор.

И еще дело в том, что имя файла мало что значит для системы. Мы можем записать в файл любое имя, но имя индикатора должно отражать то, что он вычисляет, а у нашего индикатора пока нет имени, которое бы отражало это. Даже если бы он назывался VWAP, для системы это не имеет никакого значения, по этой причине советник не будет понимать, присутствует он на графике или нет.

Для того чтобы индикатор отражал то, что он вычисляет, нужно указать это в его коде, создав таким образом уникальное имя, которое не обязательно связано с именем файла. В нашем случае код инициализации индикатора должен выглядеть так:

int OnInit()
{
        SetIndexBuffer(0, VWAP_Buff, INDICATOR_DATA);   
        IndicatorSetString(INDICATOR_SHORTNAME, "VWAP");
        
        return INIT_SUCCEEDED;
}

Просто добавив выделенную строку мы уже решаем проблему. Бывают случаи, когда все может быть немного сложнее — мы рассмотрим этот момент позже. Для начала в качестве примера возьмем код индикатора CUSTOM MOVING AVERAGE из библиотеки MetaTrader. Вот его код:

void OnInit()
{
	SetIndexBuffer(0,ExtLineBuffer,INDICATOR_DATA);
	IndicatorSetInteger(INDICATOR_DIGITS,_Digits+1);
	PlotIndexSetInteger(0,PLOT_DRAW_BEGIN,InpMAPeriod);
	PlotIndexSetInteger(0,PLOT_SHIFT,InpMAShift);

	string short_name;
	switch(InpMAMethod)
	{
      		case MODE_EMA :
			short_name="EMA";
         		break;
      		case MODE_LWMA :
	         	short_name="LWMA";
	         	break;
      		case MODE_SMA :
         		short_name="SMA";
         		break;
      		case MODE_SMMA :
         		short_name="SMMA";
         		break;
      		default :
         		short_name="unknown ma";
     	}
   	IndicatorSetString(INDICATOR_SHORTNAME, short_name + "(" + string(InpMAPeriod) + ")");
   	PlotIndexSetDouble(0,PLOT_EMPTY_VALUE,0.0);
}

Выделенная часть указывает на нужное нам имя. Обратите внимание, что оно не имеет никакого отношения к названию файла. Но это нужно обязательно сделать внутри пользовательского индикатора.

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


Доступ к индикатору через советника

Можем продолжать делать так, как прежде. Но в идеале, чтобы действительно понять, что происходит, нужно создать совершенно новый код. Но поскольку идея заключается в разработке коммерческого советника с нуля, нужно пройти через этот этап. Поэтому в продолжении нашего путешествия создадим изолированный советник. А уже потом мы можем включить его или нет в окончательный код. Теперь давайте приступим к написанию кода. Советник начинается с CLEAN-кода, как можно видеть ниже:

//+------------------------------------------------------------------+
int OnInit()
{
        EventSetTimer(1);
        return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
        EventKillTimer();
}
//+------------------------------------------------------------------+
void OnTick(){}
//+------------------------------------------------------------------+
void OnTimer(){}
//+------------------------------------------------------------------+

Давайте сделаем следующее: сначала будем считать, что индикатор VWAP находится на графике, и будем загружать в советник последнее значение, рассчитанное индикатором — это будет происходить каждую секунду. Но как это делать? Все просто — давайте посмотрим, как выглядит код советника после изменения:

#property copyright "Daniel Jose"
#property version   "1.00"
//+------------------------------------------------------------------+
int     handle;
double  Buff[];
//+------------------------------------------------------------------+
int OnInit()
{
        handle = ChartIndicatorGet(ChartID(), 0, "VWAP");
        SetIndexBuffer(0, Buff, INDICATOR_DATA);
        
        EventSetTimer(1);
        return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
        EventKillTimer();
}
//+------------------------------------------------------------------+
void OnTick()
{
}
//+------------------------------------------------------------------+
void OnTimer()
{
        int i;
        
        if (handle != INVALID_HANDLE)
        {
                i = CopyBuffer(handle, 0, 0, 1, Buff);
                Print(Buff[0]);
        }
}
//+------------------------------------------------------------------+

Выделенные части - это те, которые были добавлены к чистому коду. Результат такой:


И почему же это сработало? Причина в том, что MQL5 предоставляет средства для чтения и записи данных между системами, одним из способов чтения является использование функции CopyBuffer, и она работает, как показано ниже:


Это позволяет нам считывать любую информацию из любого пользовательского индикатора, то есть мы не ограничены индикаторами из стандартной библиотеки MetaTrader 5. При желании, мы можем создать любой индикатор, и он будет работать.

Теперь рассмотрим другой сценарий. На этот раз индикатора VWAP нет на графике, но он нужен советнику, поэтому придется загрузить его на график. Как это сделать? Это тоже довольно просто. Более того, мы это уже использовали ранее, но для других цели — при создании подокна для советника. То что мы сделаем теперь — это использование функции iCustom, но на этот раз мы загрузим пользовательский индикатор. Тогда код советника будет таким:

#property copyright "Daniel Jose"
#property version   "1.00"
//+------------------------------------------------------------------+
int     handle;
double  Buff[];
//+------------------------------------------------------------------+
int OnInit()
{
        handle = ChartIndicatorGet(ChartID(), 0, "VWAP");
        SetIndexBuffer(0, Buff, INDICATOR_DATA);
        
        EventSetTimer(1);
        return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
        EventKillTimer();
}
//+------------------------------------------------------------------+
void OnTick()
{
}
//+------------------------------------------------------------------+
void OnTimer()
{
        int i;
        
        if (handle == INVALID_HANDLE) handle = iCustom(NULL, PERIOD_CURRENT, "VWAP.EX5");else
        {
                i = CopyBuffer(handle, 0, 0, 1, Buff);
                Print(Buff[0]);
        }
}
//+------------------------------------------------------------------+

Выделенный код — единственное дополнение, которое мы внесли в исходную систему, а запуск советника теперь дает следующий результат:


То, что мы реализовали, показано на изображении ниже:

Это всё, что нужно на самом базовом уровне. Но если смотреть внимательно, то можно заметить, что на графике не видно VWAP. Даже если советник использует его, пользователь не знает, что происходит. Но и это можно легко исправить, и окончательный код выглядит так, как показано ниже. Помните одну вещь: всегда хорошо иметь возможность анализировать и наблюдать за тем, что делает советник, потому что не очень безопасно и не рекомендуется давать ему полную свободу:

#property copyright "Daniel Jose"
#property version   "1.00"
//+------------------------------------------------------------------+
int     handle;
long    id;
double  Buff[];
string  szCmd;
//+------------------------------------------------------------------+
int OnInit()
{
        szCmd = "VWAP";
        handle = ChartIndicatorGet(id = ChartID(), 0, szCmd);
        SetIndexBuffer(0, Buff, INDICATOR_DATA);
        
        EventSetTimer(1);
        return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
        ChartIndicatorDelete(id, 0, szCmd);
        IndicatorRelease(handle);
        EventKillTimer();
}
//+------------------------------------------------------------------+
void OnTick()
{
}
//+------------------------------------------------------------------+
void OnTimer()
{
        int i;
        
        if (handle == INVALID_HANDLE)
        {
                if ((handle = iCustom(NULL, PERIOD_CURRENT, "VWAP.EX5")) != INVALID_HANDLE)
                        ChartIndicatorAdd(id, 0, handle);
        }else
        {
                i = CopyBuffer(handle, 0, 0, 1, Buff);
                Print(Buff[0]);
        }
}
//+------------------------------------------------------------------+

Приведенный выше код советника будет считывать последнее значение, рассчитанное индикатором VWAP, и отображать его на экране. Если его нет на графике, он загрузит индикатор в советника и отобразит его. Удаление советника приведет к удалению VWAP с экрана, т.е. мы сохраним для советника все необходимое для вычислений. Вот что у нас получается в результате:


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


Второй случай: использование скользящих средних

Расчет скользящей средней не имеет значения, сосредоточимся на том, что действительно важно: как передать параметры в пользовательский индикатор. Вот так будет выглядеть новый пользовательский индикатор:

#property copyright "Daniel Jose 16.05.2021"
#property description "Основные скользящие средние ( Оптимизированный расчет )"
#property indicator_chart_window
//+------------------------------------------------------------------+
enum eTypeMedia
{
        MME,    //экспоненциальное скользящее среднее
        MMA     //арифметическое скользящее среднее
};
//+------------------------------------------------------------------+
#property indicator_buffers             1
#property indicator_plots               1
#property indicator_type1               DRAW_LINE
#property indicator_width1              2
#property indicator_applied_price       PRICE_CLOSE
//+------------------------------------------------------------------+
input color      user00 = clrRoyalBlue; //Cor
input int        user01 = 9;            //Периоды
input eTypeMedia user02 = MME;          //Вид скользящей
input int user03 = 0;            //Displacement
//+------------------------------------------------------------------+
double Buff[], f_Expo;
//+------------------------------------------------------------------+
int OnInit()
{
        string sz0 = "MM" + (user02 == MME ? "E": (user02 == MMA ? "A" : "_")) + (string)user01;
        
        f_Expo = (double) (2.0 / (1.0 + user01));
        ArrayInitialize(Buff, EMPTY_VALUE);
        SetIndexBuffer(0, Buff, INDICATOR_DATA);
        PlotIndexSetInteger(0, PLOT_LINE_COLOR, user00);
        PlotIndexSetInteger(0, PLOT_DRAW_BEGIN, user01);
        PlotIndexSetInteger(0, PLOT_SHIFT, user03);
        IndicatorSetString(INDICATOR_SHORTNAME, sz0);
        
        return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
{
        double Value;
        int c0;

        switch (user02)
        {
                case MME:
                        if (user01 < rates_total)
                        {       
                                for (c0 = (prev_calculated > 0 ? prev_calculated - 1 : 0); c0 < rates_total - user03; c0++)
                                        Buff[c0] = (c0 > 0? ((price[c0] - Buff[c0 - 1]) * f_Expo) + Buff[c0 - 1] : price[c0] * f_Expo);
                                for (; c0 < rates_total; c0++)
                                        Buff[c0] = EMPTY_VALUE;
                        }
                        break;
                case MMA:
                        if (user01 < rates_total)
                        {
                                if (prev_calculated == 0) 
                                {       
                                        Value = 0;
                                        for (int c1 = 0; c1 < user01; c1++) Value += price[user01 - c1];
                                        Buff[user01] = Value / user01;
                                }
                                for (c0 = (prev_calculated > 0 ? prev_calculated - 1 : user01 + 1); c0 < rates_total - user03; c0++)
                                        Buff[c0] = ((Buff[c0 - 1] * user01) - price[c0 - user01] + price[c0]) / user01;
                                for (; c0 < rates_total; c0++)
                                        Buff[c0] = EMPTY_VALUE;
                        }
                        break;
        }
        
        return rates_total;
}
//+------------------------------------------------------------------+

Хорошо, теперь название индикатора будет зависеть от нескольких факторов. Потом мы сможем заставить советника проверять и подстраиваться под каждую ситуацию. Для примера, пусть наш советник использует две скользящие средние и отобразит их на графике. Код индикатора показан выше, но обратите внимание на выделенные фрагменты — именно они позволяют советнику, а в данном случае функции iCustom, изменять и настраивать параметры индикатора. Это очень важно понять, чтобы иметь возможность реализовать это при необходимости. Итак, одна из средних будет 17-периодной экспоненциальной средней, а другая — 52-периодной экспоненциальной средней и арифметической. 17-периодная средняя будет зеленой, а 52-периодная — красной. Таким образом, советник будет видеть индикатор как функцию в следующем виде:

Average (Color, Period, Type, Shift) то есть нтеперь для нас индикатор — это не отдельный файл, а функция советника. Это очень распространено в программировании, потому что мы вызываем программу с соответствующими параметрами для выполнения конкретной задачи, а в конце получаем результат более простым способом. Но вопрос в следующем: как заставить нашего эксперта создавать и управлять этим сценарием так же, как мы сделали с VWAP?

Для этого надо будет изменить код советника. Полный код нового советника показан ниже:

#property copyright "Daniel Jose"
#property version   "1.00"
//+------------------------------------------------------------------+
long    id;
int     handle1, handle2;
double  Buff1[], Buff2[];
string  szCmd1, szCmd2;
//+------------------------------------------------------------------+
int OnInit()
{
        szCmd1 = "MME17";
        szCmd2 = "MMA52";
        id = ChartID();
        handle1 = ChartIndicatorGet(id, 0, szCmd1);
        handle2 = ChartIndicatorGet(id, 0, szCmd2);
        SetIndexBuffer(0, Buff1, INDICATOR_DATA);
        SetIndexBuffer(0, Buff2, INDICATOR_DATA);
        
        EventSetTimer(1);
        return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
        ChartIndicatorDelete(id, 0, szCmd1);
        ChartIndicatorDelete(id, 0, szCmd2);
        IndicatorRelease(handle1);
        IndicatorRelease(handle2);
        EventKillTimer();
}
//+------------------------------------------------------------------+
void OnTick()
{
}
//+------------------------------------------------------------------+
void OnTimer()
{
        int i1, i2;
        
        if (handle1 == INVALID_HANDLE)
        {
                if ((handle1 = iCustom(NULL, PERIOD_CURRENT, "Media Movel.EX5", clrGreen, 17, 0)) != INVALID_HANDLE)
                        ChartIndicatorAdd(id, 0, handle1);
        };
        if (handle2 == INVALID_HANDLE)
        {
                if ((handle2 = iCustom(NULL, PERIOD_CURRENT, "Media Movel.EX5", clrRed, 52, 1)) != INVALID_HANDLE)
                        ChartIndicatorAdd(id, 0, handle2);
        };
        if ((handle1 != INVALID_HANDLE) && (handle2 != INVALID_HANDLE))
        {
                i1 = CopyBuffer(handle1, 0, 0, 1, Buff1);
                i2 = CopyBuffer(handle2, 0, 0, 1, Buff2);
                Print(Buff1[0], "<< --- >>", Buff2[0]);
        }
}
//+------------------------------------------------------------------+

А вот какой получился результат:


Но обратите внимание на выделенные части кода советника. Это именно то, что нужно было сделать: мы передаем параметры индикатору, используя тот же механизм, который использовали в VWAP. Но в случае с VWAP не было необходимости сообщать ему какие-либо параметры, в то время как в случае со средними необходимо сообщить ему желаемые параметры. Все это дает очень большую степень свободы.


Заключение

Данная статья — одна из немногих, где нет доступного для использования универсального кода, но мы все равно подробно разобрали два разных советника и два разных пользовательских индикатора, чтобы показать читателю, как использовать этот вид системы в более сложном и продуманном советнике. Я считаю, что того факта, что мы это сделали, будет уже достаточно для возможности использования разработанных нами пользовательских индикаторов. В анализе, проведенном советником, всё очень интересно. Все это доказывает, что MetaTrader 5 — самая универсальная и полная платформа, которую только может пожелать трейдер. А если кто-то еще этого не понял, то он просто не изучил ее до конца.

Пользуйтесь знаниями, которые представлены в этой статье, потому что MetaTrader 5 позволяет пойти гораздо дальше, чем многие могли до сих пор.

Увидимся в следующей статье.


Внешние ссылки

Способы вызова индикаторов в MQL5

Пользовательские индикаторы в MQL5 для начинающих

MQL5 для "чайников": Получение значений технических индикаторов в своих экспертах



Перевод с португальского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/pt/articles/10329

Прикрепленные файлы |
Последние комментарии | Перейти к обсуждению на форуме трейдеров (5)
Volodymyr Helei
Volodymyr Helei | 11 июн. 2022 в 20:42
Интересная и во многом познавательная статья. Информация в этой статье может помочь сделать более устойчивой автоматическую торговлю. В развитие темы могу предложить Вам написать статью ка во время компиляции советника включить в один исполняемый файл торговый эксперт и индикатор. Думаю многим трейдерам эта статья понравиться. Это позволит в торговле пользоваться только одним исполняемым файлом.
Daniel Jose
Daniel Jose | 13 июн. 2022 в 14:17
Volodymyr Helei # : Artigo interessante e muito informativo. As informações neste artigo a tornarão a negociação mais podem ajudar a sustentabilidade. No desenvolvimento do tópico, posso sugerir que você escreva um artigo sobre como incluir um especialista em negociação e um indicador em um arquivo executável durante a elaboração de um consultor. Eu acho que muitos comerciantes vão gostar deste artigo. Isso pode permitir que uma negociação use apenas um arquivo executável.

Sugestão anotada ... 😁👍

Volodymyr Helei
Volodymyr Helei | 13 июн. 2022 в 18:38
При написании новой статьи обратите внимание на это обсуждение на форуме по поводу скорости работы индикатора включённого в исполняемый файл торгового эксперта https://www.mql5.com/ru/forum/357579/page6#comment_21310421 . Думаю, что вы в своей статье сможете упорядочить и обобщить все возможные проблемы и предложить их решение. На форуме до сих пор статьи освещающей эту тему нету. Для новых пользователей будет намного легче получить полную информацию по этой проблеме. 
irbisbars
irbisbars | 26 февр. 2024 в 10:00
Это какая та чушь, как буд-то не кто не читал и не изучал код? Там в индикаторе, в самом начале статьи, автор пишет такое :
VWAP_Buff[c0] = Price / Volume;

где:

ulong           Volume = 0;

Это как понимать? Когда это у нас разрешили делить на ноль? Дальше все по одному месту пойдет... Можно не читать))


Rashid Umarov
Rashid Umarov | 26 февр. 2024 в 10:34
Запускайте на биржевых инструментах, где Volume != 0
Разработка торгового советника с нуля (Часть 11): Система кросс-ордеров Разработка торгового советника с нуля (Часть 11): Система кросс-ордеров
Создание системы кросс-ордеров. Есть один вид активов, который очень усложняет жизнь трейдерам — это активы фьючерсных контрактов. Но почему они усложняют жизнь трейдеру?
Нейросети — это просто (Часть 16): Практическое использование кластеризации Нейросети — это просто (Часть 16): Практическое использование кластеризации
В предыдущей статье мы построили класс для кластеризации данных. В этой статье я хочу с вами поделиться вариантами возможного использования полученных результатов для решения практических задач трейдинга.
DoEasy. Элементы управления (Часть 7): Элемент управления "Текстовая метка" DoEasy. Элементы управления (Часть 7): Элемент управления "Текстовая метка"
В статье создадим класс объекта элемента управления WinForms "Текстовая метка". Такой объект будет иметь возможность позиционирования в любом месте своего контейнера, а его собственный функционал будет повторять некоторый функционал текстовой метки MS Visual Studio — мы сможем задать для выводимого текста параметры шрифта.
Разработка торгового советника с нуля (Часть 9): Концептуальный скачок (II) Разработка торгового советника с нуля (Часть 9): Концептуальный скачок (II)
Размещение Chart Trade в плавающем окне. В предыдущей статье мы создали базовую систему для использования шаблонов внутри плавающего окна.