English 中文 Español Deutsch 日本語 Português
preview
Разработка торгового советника с нуля (Часть 30): CHART TRADE теперь как индикатор?!

Разработка торгового советника с нуля (Часть 30): CHART TRADE теперь как индикатор?!

MetaTrader 5Примеры | 2 декабря 2022, 09:42
1 356 0
Daniel Jose
Daniel Jose

1.0 - Введение

В предыдущей статье Разработка торгового советника с нуля (Часть 29), мы удалили Chart Trade из советника. Мы это уже сделали с другими вещами, такими как Volume At Price и Times & Trade, чтобы улучшить производительность и надежность нашего советника. Итак, убрав Chart Trade из советника, мы остаемся только с базовой системой ордеров. Хотя некоторым он может показаться недостаточным, на самом деле он может делать всю работу. Но есть люди, которым нравится входить и выходить из сделок на рынке, но им не нравится выставлять их так, чтобы они были отложенными в ожидании момента, когда цена достигнет определенного уровня, чтобы войти или выйти из сделки.

Когда мы используем платформу MetaTrader 5 с активом, которым мы торгуем (я упоминаю об этом, потому что мы можем использовать систему кросс-ордеров, и мы видели, как это сделать в (части 11) данного цикла), у нас будет доступ к QUICK TRADING - это две кнопки, которые позволяют нам выставить рыночные ордера. Мы можем наблюдать их в верхнем левом углу, и они выглядят приблизительно так, как показано ниже:

Эти кнопки работают как базовый Chart Trading, но они не подойдут или, скорее, даже не появятся, если будет использоваться система кросс-ордеров. В этом случае нам придется вернуться к нашему Chart Trade, но мы больше не будем использовать его внутри советника, он не будет частью кода советника, потому что с этого момента он будет просто индикатором.

Но зачем превращать Chart Trading в индикатор? Причина в том, что советник должен отвечать только за торговую систему, а всё, что не является частью этой системы - должно быть каким-то образом удалено из кода советника. Возможно, сейчас это вам кажется бессмысленным, но вскоре вы всё поймете, поскольку я уже готовлю продолжение данной статьи, где объясню точную причину такого явления.

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

Однако при использовании индикатора у нас больше не будет этого неудобства, в том числе и потому, что Chart Trade не повлияет на поток исполнения индикаторов; таким образом советник будет свободен, что позволит сосредоточить его код исключительно на том, что необходимо для управления ордерами и позициями.

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

Так что давайте приступим к работе, потому что тема будет интересна...


2.0 - Разработка индикатора Chart Trade

Чтобы сделать его, нужно внести немало изменений. Фактически они будут накапливаться до тех пор, пока мы не вернем Chart Trade на график, не делая его частью кода советника.

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

#property copyright "Daniel Jose"
#property indicator_chart_window
#property indicator_plots 0
//+------------------------------------------------------------------+
#include <NanoEA-SIMD\SubWindow\C_TemplateChart.mqh>
//+------------------------------------------------------------------+
C_TemplateChart Chart;
C_Terminal                      Terminal;
//+------------------------------------------------------------------+
int OnInit()
{
        Chart.AddThese("IDE(,,170, 240)");

        return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
{
        return rates_total;
}
//+------------------------------------------------------------------+

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

Первое, что нам нужно сделать - изолировать систему, которая создает или управляет подокнами. Поскольку индикатор Chart Trade не будет использовать данные подокна, то нам не нужно, чтобы этот код был встроен в индикатор. Это довольно просто сделать. В файле C_Chart_IDE.mqh сделаем следующее изменение, чтобы код выглядел так, как показано ниже:

#ifdef def_INTEGRATION_CHART_TRADER
        #include <NanoEA-SIMD\SubWindow\C_SubWindow.mqh>
        #include <NanoEA-SIMD\Trade\Control\C_IndicatorTradeView.mqh>
#else
        #include <NanoEA-SIMD\SubWindow\C_ChartFloating.mqh>
        #include <NanoEA-SIMD\Auxiliar\C_Terminal.mqh>
#endif  
//+------------------------------------------------------------------+
#ifdef def_INTEGRATION_CHART_TRADER
        class C_Chart_IDE : public C_SubWindow
#else 
        class C_Chart_IDE : public C_ChartFloating
#endif 

Таким образом, мы полностью изолируем систему от подокон, избегая при этом какой-либо связи Chart Trade с системой отображения ордеров советника. Надо учитывать, что определение def_INTEGRATION_CHART_TRADER будет управлять этим, но поскольку это определение используется только в советнике, то всё, что находится внутри него уже не будет скомпилировано в индикаторе.

Так как индикатор не будет использовать подокна, мы должны избегать некоторых вещей, одна из которых показана ниже:

                bool Create(bool bFloat)
                        {
                                m_CountObject = 0;
                                if ((m_fp = FileOpen("Chart Trade\\IDE.tpl", FILE_BIN | FILE_READ)) == INVALID_HANDLE) return false;
                                FileReadInteger(m_fp, SHORT_VALUE);
                                
                                for (m_CountObject = eRESULT; m_CountObject <= eEDIT_STOP; m_CountObject++) m_ArrObject[m_CountObject].szName = "";
                                m_SubWindow = ((m_IsFloating = bFloat) ? 0 : GetIdSubWinEA());
                                m_szLine = "";
                                while (m_szLine != "</chart>")

// ... Остальной код ....

Функция GetIdSubWinEA вернет номер окна, в котором находится индикатор. Этот вызов выполняется во множестве разных точек. У нас есть два решения для этого. Первое заключается в том, что в каждой точке, где осуществляется этот вызов, мы помещаем что-то похожее на модификацию (как показано ниже) той же самой функции, которую мы видели раньше.

                bool Create(bool bFloat)
                        {
                                m_CountObject = 0;
                                if ((m_fp = FileOpen("Chart Trade\\IDE.tpl", FILE_BIN | FILE_READ)) == INVALID_HANDLE) return false;
                                FileReadInteger(m_fp, SHORT_VALUE);
                                
                                for (m_CountObject = eRESULT; m_CountObject <= eEDIT_STOP; m_CountObject++) m_ArrObject[m_CountObject].szName = "";
#ifdef def_INTEGRATION_CHART_TRADER
                                m_SubWindow = ((m_IsFloating = bFloat) ? 0 : GetIdSubWinEA());
#else 
                                m_SubWindow = 0;
#endif 

// ... Остальной код ...

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

#ifndef def_INTEGRATION_CHART_TRADER
        #define GetIdSubWinEA() 0
        #define ExistSubWin() false
#endif 

Этот простой код решит нашу проблему с вызовами. Этого будет достаточно (с некоторыми дополнительными деталями) для составления нашего индикатора.

Вторая большая проблема касается функций, связанных с событиями мыши, но не с работой самой мыши, а с классом C_Mouse.

Надо понять следующее: когда Chart Trade был частью советника, можно было получить доступ к позициям мыши (среди прочего), но если мы просто добавим класс C_Mouse в индикатор, то у нас возникнет конфликт интересов между индикатором и советником. Я не буду сейчас показывать то, как решается этот конфликт. Мы собираемся выбрать другое направление, но только временно, пока всё не решится и мы не получим хотя бы часть функциональности, которая была в оригинальном Chart Trade.

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

void DispatchMessage(int id, long lparam, double dparam, string sparam)
{
        datetime dt;
        double p;

        C_Chart_IDE::DispatchMessage(id, lparam, dparam, sparam);
        switch (id)
        {
                case CHARTEVENT_MOUSE_MOVE:
#ifdef def_INTEGRATION_CHART_TRADER
                        Mouse.GetPositionDP(dt, p);
#else
                        {
                                int w;
                                ChartXYToTimePrice(Terminal.Get_ID(), (int)lparam, (int)dparam, w, dt, p);
                        }
#endif 
                        for (int c0 = 0; c0 < m_Counter; c0++)  if (m_Info[c0].szVLine != "")

// ... Остальной код ....

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

void DispatchMessage(int id, long lparam, double dparam, string sparam)
{
        int mx, my;
        datetime dt;
        double p;
        static int six = -1, siy = -1, sic = -1;
                                
        switch (id)
        {
                case CHARTEVENT_MOUSE_MOVE:
#ifdef def_INTEGRATION_CHART_TRADER
                        Mouse.GetPositionXY(mx, my);
                        if ((Mouse.GetButtonStatus() & 0x01) == 1)
#else 
                        mx = (int)lparam;
                        my = (int)dparam;
                        if (((uint)sparam & 0x01) == 1)
#endif 
                        {
                                if (sic == -1)  for (int c0 = m_MaxCounter - 1; (sic < 0) && (c0 >= 0); c0--)

// ... Остальной код ....

Теперь мы можем скомпилировать индикатор и в результате получим результат, который показан на видео ниже:



Несмотря на то, что индикатор еще не совсем функционален, нужно принять решение: либо у Chart Trade будут те же возможности, как и у советника, либо мы можем их урезать, чтобы иметь что-то среднее между тем, что было раньше и тем, что сейчас есть в MetaTrader 5. Поскольку у меня по этому вопросу довольно радикальное мнение, я собираюсь оставить Chart Trade практически с такими же функциями, как и в советнике. Вы можете убрать что-то на ваше усмотрение.

Приняв это решение, можем переходить к следующей теме, поскольку Chart Trade только что стал индикатором.


2.1 - Как сделать индикатор Chart Trade функциональным

Сейчас всё станет сложнее. Располагайтесь поудобнее и посмотрите, как сделать так, чтобы этот индикатор был функциональным и позволил отправлять ордера, закрывать позиции и даже сообщать о результатах сделок. На самом деле это не так сложно, как кажется на первый взгляд, ведь сама платформа MetaTrader 5 предлагает нам путь следования, и помогает нам добиться этого с минимальными усилиями.

Здесь мы будем делать то же самое, что и в статье Разработка советника с нуля (Часть 16), там мы использовали некоторые возможности MetaTrader 5, чтобы создать внутреннюю систему клиент-сервер (внутри платформы) для передачи данных между различными процессами. Здесь мы сделаем нечто подобное, только моделирование будет несколько иным, поскольку нам понадобится двусторонняя связь, и она должна оставаться невидимой для пользователя.

Метод, который мы собираемся использовать, не является единственно возможным, у нас есть и другие способы, один из них - использование DLL, чтобы иметь возможность выполнить эту передачу. Однако мы будем использовать переменные MetaTrader 5, так как они, безусловно, более простые в навигации, обслуживании и изменении по мере необходимости.

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

#property copyright "Daniel Jose"
//+------------------------------------------------------------------+
#define def_GlobalVariableLeverage      (_Symbol + "_Leverage")
#define def_GlobalVariableTake          (_Symbol + "_Take")
#define def_GlobalVariableStop          (_Symbol + "_Stop")
#define def_GlobalVariableResult        (_Symbol + "_Result")
#define def_GlobalVariableButton        (_Symbol + "_ButtonState")
//+------------------------------------------------------------------+
#define def_ButtonDTSelect              0x01
#define def_ButtonSWSelect              0x02
#define def_ButtonBuyMarket             0x04
#define def_ButtonSellMarket            0x08
#define def_ButtonClosePosition         0x10
//+------------------------------------------------------------------+

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

Обратите внимание, что мы будем использовать 5 переменных, для каждого актива, в котором присутствует набор Chart Trade EA. Имена этих переменных будут зависеть от актива, к которому привязан набор, так что мы можем использовать набор в нескольких активах одновременно, без взаимного вмешательства.

Теперь один важный момент: кто будет выполнять работу сервера? Ведь он будет отвечать за создание такого вида переменных. Лично я считаю более практичным разместить советника в качестве сервера и оставить Chart Trade в качестве клиента, поскольку идея будет заключаться в том, чтобы советник всегда был на графике, а Chart Trade - в определенное время, если это необходимо. Таким образом, советник будет отвечать за создание 4 из 5 переменных, поскольку одна из них будет отвечать за оповещение о том, какая кнопка была нажата, за создание и поддержание которой отвечает Chart Trade.

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

  1. Советник создаст глобальные переменные, которые будут содержать начальные значения, указывающие на коэффициент плеча, финансовый тейк-профит и финансовый стоп-лосс. В конечном итоге он также создаст переменную, которая сообщит, каков результат дня, чтобы Chart Trade мог показать его пользователю, и ему не пришлось искать эту информацию в другом месте.
  2. В конечном итоге Chart Trade создаст переменную, представляющую значение нажатия кнопок. Данная переменная сообщит советнику, что делать, например, закрыть открытую позицию или совершить покупку или продажу на рынке. Эта же переменная будет существовать только в течение этого периода и исчезнет после того, как заявка будет завершена советником.

Этот поток, хотя и простой, уже гарантирует, что Chart Trade сможет взаимодействовать с советником, вплоть до того, что вся система будет отправлять ордера или закрывать позиции так же, как и раньше.

Чтобы гарантировать, что Chart Trade будет существовать на графике только при наличии советника, мы должны проводить некоторые проверки, поскольку нет смысла держать Chart Trade на графике, если советник недоступен для отправки ордеров. Эти проверки проводятся в следующие 2 момента:

  • Первая совершается при инициализации индикатора;
  •  Вторая - когда происходит любое событие на графике.

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

#define def_SHORTNAME "CHART TRADE"
//+------------------------------------------------------------------+
int OnInit()
{
        long lparam = 0;
        double dparam = 0.0;
        string sparam = "";
        
        IndicatorSetString(INDICATOR_SHORTNAME, def_SHORTNAME);
        if(!GlobalVariableGet(def_GlobalVariableLeverage, dparam)) return INIT_FAILED;
        Terminal.Init();
        Chart.AddThese("IDE(,,170, 215)");
        Chart.InitilizeChartTrade(dparam * Terminal.GetVolumeMinimal(), GlobalVariableGet(def_GlobalVariableTake), GlobalVariableGet(def_GlobalVariableStop), true);
        OnChartEvent(CHARTEVENT_OBJECT_ENDEDIT, lparam, dparam, sparam);

        return INIT_SUCCEEDED;
}

Выделенная строка будет оценивать одну из глобальных переменных, в данном случае ту, которая указывает на уровень плеча. Если эта переменная отсутствует, инициализация завершится неудачно. Давайте вспомним, что за создание этой переменной отвечает советник, а не Chart Trade, поэтому индикатор будет знать, присутствует или нет советник на графике, и когда советник завершит свою работу, он удалит эту переменную из MetaTrader 5, заставив удалить и Chart Trade. Это делается во второй точке, где проводится оценка этого же условия относительно наличия или отсутствия глобальной переменной плеча.

void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
        if (!GlobalVariableCheck(def_GlobalVariableLeverage)) OnDeinit(REASON_INITFAILED);
        Chart.DispatchMessage(id, lparam, dparam, sparam);
}

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

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

Независимо от того, кто запускает удаление индикатора Chart Trade с графика, это произойдет в одной и той же точке, что видно чуть ниже:

void OnDeinit(const int reason) 
{ 
        if (reason == REASON_INITFAILED)
        {
                Print("Невозможно использовать Chart Trade. Советник не находится на графике данного актива...");
                if (!ChartIndicatorDelete(0, 0, def_SHORTNAME))
                        Print("Невозможно удалить Chart Trade с графика.");
        }
}

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

Таким образом, мы всегда должны быть в курсе любой информации, представленной в данном окне.

Но прежде чем мы перейдем к более глубокому анализу изменений, внесенных в класс C_Chart_IDE, у нас остается еще одна функция, которая показана ниже:

int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
{
        double value;
        
        if (GlobalVariableGet(def_GlobalVariableResult, value))
        {
                GlobalVariableDel(def_GlobalVariableResult);
                Chart.DispatchMessage(CHARTEVENT_CHART_CHANGE, 0, value, C_Chart_IDE::szMsgIDE[C_Chart_IDE::eROOF_DIARY]);
        }
   
        return rates_total;
}

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

Таким образом мы заканчиваем начальную часть создания функционального индикатора Chart Trade. Но есть еще вторая часть - кнопки. Мы должны сделать кнопки функциональными. Это очень просто можно сделать в функции, обрабатывающей сообщения в классе C_Chart_IDE, что видно в следующем фрагменте:

// ... Предыдущий код ...

case CHARTEVENT_OBJECT_CLICK:
        if (StringSubstr(sparam, 0, StringLen(def_HeaderMSG)) != def_HeaderMSG)
	{
		Resize(-1);
		return;
	}
	sparam = StringSubstr(sparam, 9, StringLen(sparam));
	StringToUpper(sparam);
#ifdef def_INTEGRATION_CHART_TRADER
	if ((sparam == szMsgIDE[eBTN_SELL]) || (sparam == szMsgIDE[eBTN_BUY]))
		TradeView.ExecuteOrderInMarket(m_BaseFinance.Leverange, m_BaseFinance.FinanceTake, m_BaseFinance.FinanceStop, sparam == szMsgIDE[eBTN_BUY], m_BaseFinance.IsDayTrade);
	if (sparam == szMsgIDE[eBTN_CANCEL])
	{
		TradeView.CloseAllsPosition();
		ObjectSetInteger(Terminal.Get_ID(), m_ArrObject[eBTN_CANCEL].szName, OBJPROP_STATE, false);
	}
#else 
	{
		union u00
		{
			double Value;
			ulong c;
		}u_local;
                                                        
		u_local.c = 0;
		if (sparam == szMsgIDE[eBTN_BUY]) u_local.c = (m_BaseFinance.IsDayTrade ? def_ButtonDTSelect : def_ButtonSWSelect) + def_ButtonBuyMarket; else
		if (sparam == szMsgIDE[eBTN_SELL]) u_local.c = (m_BaseFinance.IsDayTrade ? def_ButtonDTSelect : def_ButtonSWSelect) + def_ButtonSellMarket; else
		if (sparam == szMsgIDE[eBTN_CANCEL])
		{
			u_local.c = def_ButtonClosePosition;
			ObjectSetInteger(Terminal.Get_ID(), m_ArrObject[eBTN_CANCEL].szName, OBJPROP_STATE, false);
		}
                if (u_local.Value > 0) GlobalVariableSet(def_GlobalVariableButton, u_local.Value);
	}
#endif 
	if (sparam == szMsgIDE[eCHECK_DAYTRADE]) InitilizeChartTrade(0, 0, 0, m_BaseFinance.IsDayTrade ? false : true);
	break;

//... Остальной код ....

Обратите внимание, что в данном фрагменте мы имеем 2 кода. Код СИНЕГО цвета используется, когда Chart Trade интегрирован в советник, и код ЗЕЛЕНОГО цвета используется, когда Chart Trade присутствует в качестве индикатора.

Здесь нас интересует код ЗЕЛЕНОГО цвета. Он создаст глобальную переменную и установит значение состояния кнопок. Таким образом, если трейдер закрывает позицию, то соответствующее кнопке закрытия значение будет помещено в переменную, и только это значение. Но если вы отправляете рыночный ордер, это значение будет другим и теперь станет представлять собой сочетание двух других значений: одно указывает, является ли ордер ордером на покупку или продажу, в то время как другое указывает, хотим ли мы совершить Day Trade или более длительную сделку. Это всё, что Chart Trade укажет советнику.

ВАЖНО: Эта система является самоисключающей, т.е. если мы нажали кнопку продажи и до того, как советник что-либо сделает, мы нажимаем кнопку покупки и советник действительно совершит покупку, поскольку значение, которое указывало на продажу, будет потеряно из-за включения значения, которое указывало на покупку. Кроме того, если мы запрашиваем совершать ли нам продажу или покупку, а у нас уже есть открытая позиция, то перед тем, как советник совершит покупку или продажу, мы нажимаем отмену, и позиция будет закрыта.

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


2.2 - Модифицируем советника для получения сообщений от индикатора Chart Trade

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

В предыдущей статье Разработка торгового советника с нуля (Часть 29), при продвижении удаления Chart Trade, мы внесли некоторые изменения, часть из которых будет отменена. Нам не потребуется изменять что-то еще, связанное с этим вопросом. Но, как говорилось ранее: "некоторые вещи решаемы, а некоторые - нет", поэтому в следующей теме данной статьи мы устраним некоторые небольшие проблемы во взаимоотношениях между Chart Trade и советником.

Давайте посмотрим сначала, что нам придется отменить и активировать в советнике, чтобы между советником и Chart Trade был хоть какой-то уровень связи.

Первым делом мы изменим следующие записи:

input int       user20      = 1;        //Коэффициент плеча
input double    user21      = 100;      //Take Profit ( ФИНАНСОВЫЙ )
input double    user22      = 81.74;    //Stop Loss ( ФИНАНСОВЫЙ )
input bool      EA_user23   = true;     //Day Trade ?

Значение, которое указывает на то, будет ли советник преимущественно открывать длинную или короткую позицию, остается неизменным. Это надо сделать в Chart Trade или в отложенном ордере, который мы разместим на графике. В предыдущих статьях я уже показывал как это сделать, так что давайте продолжим работу по написанию кода. В этом первом пункте нам нужно внести следующие изменения в событие OnInit:

int OnInit()
{
        if (!TerminalInfoInteger(TERMINAL_TRADE_ALLOWED))
        {
                Sound.PlayAlert(C_Sounds::TRADE_ALLOWED);
                return INIT_FAILED;
        }
        
        Terminal.Init();

#ifdef def_INTEGRATION_TAPE_READING
        VolumeAtPrice.Init(user32, user33, user30, user31);
        TimesAndTrade.Init(user41);
        EventSetTimer(1);
#endif 

        Mouse.Init(user50, user51, user52);
        
#ifdef def_INTEGRATION_CHART_TRADER
        static string   memSzUser01 = "";
        if (memSzUser01 != user01)
        {
                Chart.ClearTemplateChart();
                Chart.AddThese(memSzUser01 = user01);
        }
        Chart.InitilizeChartTrade(EA_user20 * Terminal.GetVolumeMinimal(), EA_user21, EA_user22, EA_user23);
        TradeView.Initilize();
   OnTrade();
#else
        GlobalVariableTemp(def_GlobalVariableLeverage);
        GlobalVariableTemp(def_GlobalVariableTake);
        GlobalVariableTemp(def_GlobalVariableStop);
        GlobalVariableTemp(def_GlobalVariableResult);
        GlobalVariableSet(def_GlobalVariableLeverage, user20 * Terminal.GetVolumeMinimal());
        GlobalVariableSet(def_GlobalVariableTake, user21);
        GlobalVariableSet(def_GlobalVariableStop, user22);
        TradeView.Initilize();
        GlobalVariableSet(def_GlobalVariableResult, TradeView.GetFinanceRoof());
#endif 
   
        return INIT_SUCCEEDED;
}

Мы видим, что здесь мы добавляем глобальные переменные, которые будут использоваться для связи. Как мы уже упомянули, советник всегда должен входить в рынок до Chart Trade, иначе мы не сможем инициализировать индикатор. Обратите внимание, что значения, которые будут изначально использоваться Chart Trade, указаны в советнике. И инициализация будет завершена, даже в том случае, если были предыдущие сделки - накопленное значение также передается в Chart Trade снова.

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

Следующее дополнение, которое нам нужно будет реализовать, показано ниже:

void OnDeinit(const int reason)
{
        Mouse.Destroy();
        TradeView.Finish();
#ifndef def_INTEGRATION_CHART_TRADER
        GlobalVariableDel(def_GlobalVariableLeverage);
        GlobalVariableDel(def_GlobalVariableTake);
        GlobalVariableDel(def_GlobalVariableStop);
        GlobalVariableDel(def_GlobalVariableResult);
        GlobalVariableDel(def_GlobalVariableButton);
#endif
#ifdef def_INTEGRATION_TAPE_READING
        EventKillTimer();
#endif 
}

Даже если переменные временные, нужно просить советника принудительно удалить их. Таким образом мы убедимся в том, что Chart Trade также не останется на графике. Есть небольшая проблема с этим событием OnDeinit, но мы о ней расскажем в следующей теме, поэтому давайте рассмотрим другой момент, довольно любопытный. Можно сделать это двумя способами, и результаты будут ПРАКТИЧЕСКИ идентичны. Мы должны подчеркнуть это слово, ПРАКТИЧЕСКИ идентичны, потому что есть небольшие различия, которые могут иметь значение. (Извините за каламбур, просто не смог удержаться... 😂👍)

Помните, что в Chart Trade есть несколько кнопок и что они отправляют сообщения советнику через глобальную переменную? Ниже показана функция внутри советника, которая реагирует на нажатия кнопок соответствующим образом:

inline void ChartTrade_ClickButton(void)
{
        union u00
        {
                double Value;
                ulong c;
        }u_local;
        
        if (GlobalVariableGet(def_GlobalVariableButton, u_local.Value))
        {
                GlobalVariableDel(def_GlobalVariableButton);
                                
                if (u_local.c == def_ButtonClosePosition) TradeView.CloseAllsPosition(); else
                        TradeView.ExecuteOrderInMarket(GlobalVariableGet(def_GlobalVariableLeverage), GlobalVariableGet(def_GlobalVariableTake), GlobalVariableGet(def_GlobalVariableStop), ((u_local.c & def_ButtonBuyMarket) == def_ButtonBuyMarket), ((u_local.c & def_ButtonDTSelect) == def_ButtonDTSelect));
                TradeView.Initilize();
        }
}

Эта функция объявляется как inline,то есть она должна быть помещена компилятором в ту позицию, где она объявлена, что позволит выполнить её как можно быстрее.

Очень важная деталь: где мы разместим эту функцию? Минута на размышление... Мы видим, что у нас есть проверка, чтобы она не осуществлялась постоянно; таким образом у нас есть 2 возможности. Первый - поместить функцию внутрь события OnTick, второй - внутрь события OnTime. Этот выбор должен быть основан на какой-то логике, иначе мы можем столкнуться с проблемами.

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

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

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

В действительности чаще всего он срабатывает каждую секунду, но мы можем установить более короткое время, используя немного другую функцию, EventSetMillisecondTimer. Таким образом, мы сможем запустить событие менее чем за 1 секунду. Я думаю, что 500 мс будет достаточно для широкого спектра случаев, поэтому в событии OnInit советника мы должны запустить следующую строку:

#else
        GlobalVariableTemp(def_GlobalVariableLeverage);
        GlobalVariableTemp(def_GlobalVariableTake);
        GlobalVariableTemp(def_GlobalVariableStop);
        GlobalVariableTemp(def_GlobalVariableResult);
        GlobalVariableSet(def_GlobalVariableLeverage, user20 * Terminal.GetVolumeMinimal());
        GlobalVariableSet(def_GlobalVariableTake, user21);
        GlobalVariableSet(def_GlobalVariableStop, user22);
        TradeView.Initilize();
        GlobalVariableSet(def_GlobalVariableResult, TradeView.GetFinanceRoof());
        EventSetMillisecondTimer(500);
#endif 

И мы не должны забывать также завершить это событие в функции OnDeinit, и для этого достаточно просто добавить следующую строку в событие:

void OnDeinit(const int reason)
{
        EventKillTimer();

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

void OnTimer()
{
#ifndef def_INTEGRATION_CHART_TRADER
        ChartTrade_ClickButton();
#endif

#ifdef def_INTEGRATION_TAPE_READING
        VolumeAtPrice.Update();
        TimesAndTrade.Connect();
#endif 
}

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

        case CHARTEVENT_MOUSE_MOVE:
                Mouse.GetPositionDP(dt, price);
                mKeys   = Mouse.GetButtonStatus();
                bEClick  = (mKeys & 0x01) == 0x01;    //Клик по левой кнопке
                bKeyBuy  = (mKeys & 0x04) == 0x04;    //Нажатый SHIFT
                bKeySell = (mKeys & 0x08) == 0x08;    //Нажатый CTRL
                if (bKeyBuy != bKeySell)
                {
                        if (!bMounting)
                        {
#ifdef def_INTEGRATION_CHART_TRADER
                                m_Selection.bIsDayTrade = Chart.GetBaseFinance(m_Selection.vol, valueTp, valueSl);
#else 
                                m_Selection.vol = GlobalVariableGet(def_GlobalVariableLeverage) * Terminal.GetVolumeMinimal();
                                valueTp = GlobalVariableGet(def_GlobalVariableTake);
                                valueSl = GlobalVariableGet(def_GlobalVariableStop);
                                m_Selection.bIsDayTrade = EA_user23;
#endif 

Теперь выделенные коды захватывают значения, которые находятся в глобальных переменных, поэтому всё, что находится в Chart Trade, будет помещено в систему ордеров, так что мы можем размещать их с минимальной работой. Единственная деталь заключается в том, что все отложенные ордера будут следовать тому времени, которое указывает советник, но это можно изменить прямо на графике. Для более подробной информации смотрите Разработка советника с нуля (Часть 27), в этой статье показано, как изменять отложенные ордера прямо на графике, без необходимости проходить через Chart Trade.

Еще есть небольшая модификация в системе звуков. Они были добавлены в класс C_Router, чтобы оповещать о входе в позицию и закрытии её в случае, если мы торгуем через Chart Trade. Если вы хотите удалить или изменить что-то в звуковой системе, следует помнить, что есть и другие моменты, которые необходимо учитывать.

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


2.3 - Как предотвратить преждевременное завершение Chart Trade на графике

Когда мы делаем что-то, например, меняем таймфрейм графика либо параметры советника (это может что угодно), MetaTrader 5 генерирует событие, имя которого - OnDeint. Факт срабатывания этого события означает, что все вещи должны быть заново проанализированы, чтобы всё продолжало функционировать так, как ожидалось.

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

Функция, обрабатывающая исходное событие OnDeinit, выглядит следующим образом:

void OnDeinit(const int reason)
{
        EventKillTimer();
        Mouse.Destroy();
        TradeView.Finish();
#ifndef def_INTEGRATION_CHART_TRADER
        GlobalVariableDel(def_GlobalVariableLeverage);
        GlobalVariableDel(def_GlobalVariableTake);
        GlobalVariableDel(def_GlobalVariableStop);
        GlobalVariableDel(def_GlobalVariableResult);
        GlobalVariableDel(def_GlobalVariableButton);
#endif
#ifdef def_INTEGRATION_TAPE_READING
        EventKillTimer();
#endif 
}

Выделенная строка удалит глобальную переменную, именно ту, которую мы используем в индикаторе Chart Trade для проверки того, находится ли советник на графике или нет. Данная проверка выполняется в следующем коде:

void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
        if (!GlobalVariableCheck(def_GlobalVariableLeverage)) OnDeinit(REASON_INITFAILED);
        Chart.DispatchMessage(id, lparam, dparam, sparam);
}

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

Затем, заглянув в документацию, я нашел решение проблемы. Это можно увидеть в разделе Причины деинициализации. Как только мы увидим эти коды, мы сможем настроить функцию обработки события OnDeinit так, чтобы переменные не удалялись, пока советник не будет удален из графика.

Таким образом, решение и новый код обработки будут такими, как показано ниже:

void OnDeinit(const int reason)
{
        EventKillTimer();
        Mouse.Destroy();
        TradeView.Finish();
#ifndef def_INTEGRATION_CHART_TRADER
        switch (reason)
        {
                case REASON_CHARTCHANGE:
                        break;
                default:                
                        GlobalVariableDel(def_GlobalVariableLeverage);
                        GlobalVariableDel(def_GlobalVariableTake);
                        GlobalVariableDel(def_GlobalVariableStop);
                        GlobalVariableDel(def_GlobalVariableResult);
                        GlobalVariableDel(def_GlobalVariableButton);
        };
#endif
#ifdef def_INTEGRATION_TAPE_READING
        EventKillTimer();
#endif 
}

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


3.0 - Заключение

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


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

Прикрепленные файлы |
EA_-_p_Parte_30_u.zip (14532.23 KB)
Разработка торговой системы на основе Accelerator Oscillator Разработка торговой системы на основе Accelerator Oscillator
Новая статья из серии, в которой мы учимся создавать торговые системы по показателям самых популярных технических индикаторов. На этот раз будем изучать индикатор Accelerator Oscillator — узнаем, как его использовать и как создавать торговые системы на его основе.
Нейросети — это просто (Часть 34): Полностью параметризированная квантильная функция Нейросети — это просто (Часть 34): Полностью параметризированная квантильная функция
Продолжаем изучение алгоритмов распределенного Q-обучения. В предыдущих статьях мы рассмотрели алгоритмы распределенного и квантильного Q-обучения. В первом мы учили вероятности заданных диапазонов значений. Во втором учили диапазоны с заданной вероятностью. И в первом, и во втором алгоритме мы использовали априорные знания одного распределения и учили другое. В данной статье мы рассмотрим алгоритм, позволяющей модели учить оба распределения.
DoEasy. Элементы управления (Часть 28): Стили полосы в элементе управления "ProgressBar" DoEasy. Элементы управления (Часть 28): Стили полосы в элементе управления "ProgressBar"
В статье будут разработаны стили отображения и текст описания полосы прогресса элемента управления ProgressBar
Разработка торговой системы на основе Awesome Oscillator Разработка торговой системы на основе Awesome Oscillator
Это очередная статья из серии, и в ней мы познакомимся с еще одним полезным техническим инструментом для торговли — индикатором Awesome Oscillator (AO). Узнаем, как разрабатывать торговые системы на основе показателей от этого индикатора.