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

Разработка торгового советника с нуля (Часть 9): Концептуальный скачок (II)

MetaTrader 5Индикаторы | 27 мая 2022, 14:37
1 248 0
Daniel Jose
Daniel Jose

Введение

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

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


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

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

#property copyright "Daniel Jose"
#property indicator_chart_window
#property indicator_plots 0
//+------------------------------------------------------------------+
int OnInit()
{
        long id = ChartID();
        string sz0 = (string)ObjectsTotal(id, -1, -1) + (string)MathRand();
        ObjectCreate(id, sz0, OBJ_CHART, 0, 0, 0);
        ObjectSetInteger(id, sz0, OBJPROP_XDISTANCE, 10);
        ObjectSetInteger(id, sz0, OBJPROP_YDISTANCE, 10);
        ObjectSetInteger(id, sz0, OBJPROP_XSIZE, 300);
        ObjectSetInteger(id, sz0, OBJPROP_YSIZE, 300);
        ObjectSetInteger(id, sz0, OBJPROP_PRICE_SCALE, false);
        ObjectSetInteger(id, sz0, OBJPROP_DATE_SCALE, false);
        
  
        return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const int begin,
                const double &price[])
{
        return rates_total;
}
//+------------------------------------------------------------------+


Здесь нет ничего сложного, так как эффект был именно таким, как и ожидалось. Но MQL5 позволяет пойти немного дальше, хотя возникают сложности при попытке сделать что-то сверх того, для чего изначально была разработана система. Итак, если мы изменим приведенный выше код на что-то вроде того, что показано ниже, всё станет интереснее.

int OnInit()
{
        long id = ChartID(), handle;
        string sz0 = (string)ObjectsTotal(id, -1, -1) + (string)MathRand(), sz1 = (string)MathRand();
        
        ObjectCreate(id, sz0, OBJ_CHART, 0, 0, 0);
        ObjectSetInteger(id, sz0, OBJPROP_XDISTANCE, 10);
        ObjectSetInteger(id, sz0, OBJPROP_YDISTANCE, 10);
        ObjectSetInteger(id, sz0, OBJPROP_XSIZE, 300);
        ObjectSetInteger(id, sz0, OBJPROP_YSIZE, 300);
        ObjectSetInteger(id, sz0, OBJPROP_PRICE_SCALE, false);
        ObjectSetInteger(id, sz0, OBJPROP_DATE_SCALE, false);
        
        handle = ObjectGetInteger(id, sz0, OBJPROP_CHART_ID);
        ObjectCreate(handle, sz1, OBJ_CHART, 0, 0, 0);
        ObjectSetInteger(handle, sz1, OBJPROP_XDISTANCE, 50);
        ObjectSetInteger(handle, sz1, OBJPROP_YDISTANCE, 50);
        ObjectSetInteger(handle, sz1, OBJPROP_XSIZE, 300);
        ObjectSetInteger(handle, sz1, OBJPROP_YSIZE, 300);
        ObjectSetInteger(handle, sz1, OBJPROP_PRICE_SCALE, false);
        ObjectSetInteger(handle, sz1, OBJPROP_DATE_SCALE, false);
        ChartRedraw(handle);    
  
        return INIT_SUCCEEDED;
}

В исходный код добавились выделенные строки. Если запустить его на графике, результат будет таким:


Что же случилось? Мы поместили график в график. Мы могли поместить любой объект — MQL5 позволяет нам это сделать, но у этого есть как преимущества, так и недостатки, поэтому давайте добавим еще одно изменение кода, чтобы проверить преимущество этого действия.

int OnInit()
{
        long id = ChartID(), handle;
        string sz0 = (string)ObjectsTotal(id, -1, -1) + (string)MathRand(), sz1 = (string)MathRand();
        
        ObjectCreate(id, sz0, OBJ_CHART, 0, 0, 0);
        ObjectSetInteger(id, sz0, OBJPROP_XDISTANCE, 10);
        ObjectSetInteger(id, sz0, OBJPROP_YDISTANCE, 10);
        ObjectSetInteger(id, sz0, OBJPROP_XSIZE, 300);
        ObjectSetInteger(id, sz0, OBJPROP_YSIZE, 300);
        ObjectSetInteger(id, sz0, OBJPROP_PRICE_SCALE, false);
        ObjectSetInteger(id, sz0, OBJPROP_DATE_SCALE, false);
        ObjectSetInteger(id, sz0, OBJPROP_SELECTABLE, true);
        ObjectSetInteger(id, sz0, OBJPROP_SELECTED, true);
        
        handle = ObjectGetInteger(id, sz0, OBJPROP_CHART_ID);
        ObjectCreate(handle, sz1, OBJ_CHART, 0, 0, 0);
        ObjectSetInteger(handle, sz1, OBJPROP_XDISTANCE, 50);
        ObjectSetInteger(handle, sz1, OBJPROP_YDISTANCE, 50);
        ObjectSetInteger(handle, sz1, OBJPROP_XSIZE, 300);
        ObjectSetInteger(handle, sz1, OBJPROP_YSIZE, 300);
        ObjectSetInteger(handle, sz1, OBJPROP_PRICE_SCALE, false);
        ObjectSetInteger(handle, sz1, OBJPROP_DATE_SCALE, false);
        ChartRedraw(handle);    
  
        return INIT_SUCCEEDED;
}

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


Это означает, что всё, что находится внутри объекта, останется внутри объекта, это необходимо для возможности использования плавающих окон, так как это значительно упрощает логику управления. Но не все так прекрасно: MetaTrader 5 изначально не предназначен для этого. Поэтому появляется проблема, когда один объект находится внутри другого, и главное — мы не можем отправлять события внутренним объектам. Чтобы разобраться с этим, давайте реализуем еще несколько изменений в коде. Теперь окончательный код можно увидеть ниже:

#property copyright "Daniel Jose"
#property indicator_chart_window
#property indicator_plots 0
//+------------------------------------------------------------------+
int OnInit()
{
        long id = ChartID(), handle;
        string sz0 = (string)ObjectsTotal(id, -1, -1) + (string)MathRand(), sz1 = (string)MathRand();
        
        ObjectCreate(id, sz0, OBJ_CHART, 0, 0, 0);
        ObjectSetInteger(id, sz0, OBJPROP_XDISTANCE, 10);
        ObjectSetInteger(id, sz0, OBJPROP_YDISTANCE, 10);
        ObjectSetInteger(id, sz0, OBJPROP_XSIZE, 300);
        ObjectSetInteger(id, sz0, OBJPROP_YSIZE, 300);
        ObjectSetInteger(id, sz0, OBJPROP_PRICE_SCALE, false);
        ObjectSetInteger(id, sz0, OBJPROP_DATE_SCALE, false);
        ObjectSetInteger(id, sz0, OBJPROP_SELECTABLE, true);
        ObjectSetInteger(id, sz0, OBJPROP_SELECTED, true);
        
        handle = ObjectGetInteger(id, sz0, OBJPROP_CHART_ID);
        ObjectCreate(handle, sz1, OBJ_CHART, 0, 0, 0);
        ObjectSetInteger(handle, sz1, OBJPROP_XDISTANCE, 50);
        ObjectSetInteger(handle, sz1, OBJPROP_YDISTANCE, 50);
        ObjectSetInteger(handle, sz1, OBJPROP_XSIZE, 300);
        ObjectSetInteger(handle, sz1, OBJPROP_YSIZE, 300);
        ObjectSetInteger(handle, sz1, OBJPROP_PRICE_SCALE, false);
        ObjectSetInteger(handle, sz1, OBJPROP_DATE_SCALE, false);
        ObjectSetInteger(handle, sz1, OBJPROP_SELECTABLE, true);
        ObjectSetInteger(handle, sz1, OBJPROP_SELECTED, true);
        ChartRedraw(handle);    
  
        return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const int begin,
                const double &price[])
{
        return rates_total;
}
//+------------------------------------------------------------------+
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
        if (id == CHARTEVENT_OBJECT_CLICK) Print(sparam);
}
//+------------------------------------------------------------------+

И результат, когда мы запускаем его на платформе, выглядит так:


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

В этом процессе планирования есть еще одна последняя фаза. Хотя для современных компьютеров это кажется неактуальным, все равно надо это предусмотреть: оптимизация времени обработки. Проблема здесь больше связана с количеством операций, которые должен выполнить процессор, а не со временем, которое потребуется для обработки указанной информации. Предлагаемая система плавающих окон содержит четыре объекта, которые нужно перемещать, что бы вы ни делали. Соответственно любая информация, размещенная в области просмотра, будет подвержена собственным модификациям окна. Дело в том, что как минимум CHART TRADE увеличит количество объектов. И хотя здесь нет соответствующих вычислительных затрат, но код становится неприглядным, создается впечатление, что он плохо оптимизирован. Можно было бы просто добавить систему управления, и это решило бы проблему. Но есть более элегантное предложение, хотя оно кажется более трудоемким, но фактически уменьшает количество объектов, которые нужно поддерживать и манипулировать.


Реализация

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

//+------------------------------------------------------------------+
bool StageLocal01(string sz0, ENUM_TIMEFRAMES TimeFrame = PERIOD_CURRENT, int Scale = -1)
{
        m_LimitX = (int)ChartGetInteger(Terminal.Get_ID(), CHART_WIDTH_IN_PIXELS);
        m_LimitY = (int)ChartGetInteger(Terminal.Get_ID(), CHART_HEIGHT_IN_PIXELS);
        if (m_MaxCounter >= def_MaxFloating) return false;
        CreateBarTitle();
        CreateCaption(sz0);
        CreateBtnMaxMin();
        CreateRegion(TimeFrame, Scale);
	m_Win[m_MaxCounter].handle = ObjectGetInteger(Terminal.Get_ID(), m_Win[m_MaxCounter].szRegionChart, OBJPROP_CHART_ID);
                                
        return true;
}
//+------------------------------------------------------------------+
void StageLocal02(int x, int y, int w, int h)
{
        y = (y < 0 ? m_MaxCounter * def_SizeBarCaption : y);                            
        m_Win[m_MaxCounter].PosX        = -1;
        m_Win[m_MaxCounter].PosY        = -1;
        m_Win[m_MaxCounter].PosX_Minimized = m_Win[m_MaxCounter].PosX_Maximized = x;
        m_Win[m_MaxCounter].PosY_Minimized = m_Win[m_MaxCounter].PosY_Maximized = y;
        SetDimension(w, h, true, m_MaxCounter);
        SetPosition(x, y, m_MaxCounter);
        ChartRedraw(m_Win[m_MaxCounter].handle);
        m_MaxCounter++;
}
//+------------------------------------------------------------------+

При этом новый код для добавления плавающего окна будет выглядеть так:

bool AddIndicator(string sz0, int x = 0, int y = -1, int w = 300, int h = 200, ENUM_TIMEFRAMES TimeFrame = PERIOD_CURRENT, int Scale = -1)
{
	if (!StageLocal01(sz0, TimeFrame, Scale)) return false;
        ChartApplyTemplate(m_Win[m_MaxCounter].handle, sz0 + ".tpl");   
        m_Win[m_MaxCounter].szVLine = (string)ObjectsTotal(Terminal.Get_ID(), -1, -1) + (string)MathRand();
        ObjectCreate(m_Win[m_MaxCounter].handle, m_Win[m_MaxCounter].szVLine, OBJ_VLINE, 0, 0, 0);
        ObjectSetInteger(m_Win[m_MaxCounter].handle, m_Win[m_MaxCounter].szVLine, OBJPROP_COLOR, clrBlack);
        StageLocal02(x, y, w, h);

        return true;
}

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

bool Add_RAD_IDE(string sz0, int x, int y, int w, int h)
{
        if (!StageLocal01(sz0, PERIOD_CURRENT, -1)) return false;
        StageLocal02(x, y, w, h);
        return true;
}

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

void AddTemplate(void)
{

// .... Код функции....

        if (h == 0)
        {
                SetBase(m_Params.Param[TEMPLATE], (bIsSymbol ? m_Params.Param[TEMPLATE] : _Symbol), timeframe, i, w);
                if (!ChartApplyTemplate(m_Info[m_Counter - 1].handle, m_Params.Param[TEMPLATE] + ".tpl")) if (bIsSymbol) ChartApplyTemplate(m_Info[m_Counter - 1].handle, "Default.tpl");
        }
        if (m_Params.Param[TEMPLATE] == def_NameTemplateRAD)
        {
                if ((h > 0) && (w > 0)) Add_RAD_IDE(m_Params.Param[TEMPLATE], 0, -1, w, h); else
                {
                        C_Chart_IDE::Create(GetIdSubWinEA());
                        m_Info[m_Counter - 1].szVLine = "";
                }
        }else
        {
                if ((w > 0) && (h > 0)) AddIndicator(m_Params.Param[TEMPLATE], 0, -1, w, h, timeframe, i); else
                {
                        m_Info[m_Counter - 1].szVLine = (string)ObjectsTotal(Terminal.Get_ID(), -1, -1) + (string)MathRand();
                        ObjectCreate(m_Info[m_Counter - 1].handle, m_Info[m_Counter - 1].szVLine, OBJ_VLINE, 0, 0, 0);
                        ObjectSetInteger(m_Info[m_Counter - 1].handle, m_Info[m_Counter - 1].szVLine, OBJPROP_COLOR, clrBlack);
                }
        }
}

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

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

bool Add_RAD_IDE(string sz0, int x, int y, int w, int h)
{
        if (!StageLocal01(sz0, PERIOD_CURRENT, -1)) return false;
        ChartApplyTemplate(m_Win[m_MaxCounter].handle, "\\Files\\Chart Trade\\IDE.tpl");
        StageLocal02(x, y, w, h);
        return true;
}

Результат работы:


Всё было бы очень хорошо и красиво, если бы была возможность доступа к объектам, которые есть в шаблоне (шаблон загружается в строке, выделенной в коде). Но у нас нет такой возможности, и именно здесь кроется важная деталь: вместо того, чтобы создавать все объекты как показно выше, мы создаем только те объекты, которыми нужно манипулировать! Это сэкономит кучу времени на обработку, когда нам нужно будет переместить окно. У нас всё еще есть другая проблема. Давайте сначала решим проблему с обработками и сделаем систему функциональной. Да, эта часть в каком-то смысле уже сделана, просто нужно немного исправить ситуацию, чтобы всё заработало.

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


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

bool Add_RAD_IDE(string sz0, int x, int y, int w, int h)
{
	if ((w <= 0) || (h <= 0)) return false;
        if (!StageLocal01(sz0, PERIOD_CURRENT, -1)) return false;
        ChartApplyTemplate(m_Win[m_MaxCounter].handle, "\\Files\\Chart Trade\\IDE.tpl");
        StageLocal02(x, y, w, h);
        return true;
}
void AddTemplate(void)
{
// ..... Código ....
        if (h == 0)
        {
                SetBase(m_Params.Param[TEMPLATE], (bIsSymbol ? m_Params.Param[TEMPLATE] : _Symbol), timeframe, i, w);
                if (!ChartApplyTemplate(m_Info[m_Counter - 1].handle, m_Params.Param[TEMPLATE] + ".tpl")) if (bIsSymbol) ChartApplyTemplate(m_Info[m_Counter - 1].handle, "Default.tpl");
        }
        if (m_Params.Param[TEMPLATE] == def_NameTemplateRAD)
        {
		C_Chart_IDE::Create(Add_RAD_IDE(m_Params.Param[TEMPLATE], 0, -1, w, h));
                m_Info[m_Counter - 1].szVLine = "";
        }else
        {
                if ((w > 0) && (h > 0)) AddIndicator(m_Params.Param[TEMPLATE], 0, -1, w, h, timeframe, i); else
                {
                        m_Info[m_Counter - 1].szVLine = (string)ObjectsTotal(Terminal.Get_ID(), -1, -1) + (string)MathRand();
                        ObjectCreate(m_Info[m_Counter - 1].handle, m_Info[m_Counter - 1].szVLine, OBJ_VLINE, 0, 0, 0);
                        ObjectSetInteger(m_Info[m_Counter - 1].handle, m_Info[m_Counter - 1].szVLine, OBJPROP_COLOR, clrBlack);
                }
        }
}
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>")
        {
                if (!FileReadLine()) return false;
                if (m_szLine == "<object>")
                {
                        if (!FileReadLine()) return false;
                        if (m_szLine == "type")
                        {
                                if (m_szValue == "102") if (!LoopCreating(OBJ_LABEL)) return false;
                                if (m_szValue == "103") if (!LoopCreating(OBJ_BUTTON)) return false;
                                if (m_szValue == "106") if (!LoopCreating(OBJ_BITMAP_LABEL)) return false;
                                if (m_szValue == "107") if (!LoopCreating(OBJ_EDIT)) return false;
                                if (m_szValue == "110") if (!LoopCreating(OBJ_RECTANGLE_LABEL)) return false;
                        }
                }
        }
        FileClose(m_fp);
        DispatchMessage(CHARTEVENT_CHART_CHANGE, 0, 0, szMsgIDE[eLABEL_SYMBOL]);
        return true;
}
bool LoopCreating(ENUM_OBJECT type)
{
#define macro_SetInteger(A, B) ObjectSetInteger(Terminal.Get_ID(), m_ArrObject[c0].szName, A, B)
#define macro_SetString(A, B) ObjectSetString(Terminal.Get_ID(), m_ArrObject[c0].szName, A, B)
        int c0;
        bool b0;
        string sz0 = m_szValue;
        while (m_szLine != "</object>") if (!FileReadLine()) return false; else
        {
                if (m_szLine == "name")
                {
                        b0 = false;
                        StringToUpper(m_szValue);
                        for(c0 = eRESULT; (c0 <= eEDIT_STOP) && (!(b0 = (m_szValue == szMsgIDE[c0]))); c0++);
                        if (!b0 && m_IsFloating) return true; else c0 = (b0 ? c0 : m_CountObject);
                        m_ArrObject[c0].szName = StringFormat("%s%04s>%s", def_HeaderMSG, sz0, m_szValue);

//... Остальная часть функции ....

}

Вы можете подумать: Что это он делает?! Но здесь ничего сложного. Помните, если мы не используем плавающее окно, система уже способна обрабатывать события, выполняемые в нашей IDE. Но из-за плавающего окна придется собрать всё заново. Это факт, но также факт, что нам не нужно делать это с нуля — будем модифицировать существующий код, чтобы добавить IDE в нужное место, но не со всеми элементами, а добавим только объекты, принимающие события. И эти изменения позволяют знать, нужно ли создавать все элементы или только часть.

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

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

//+------------------------------------------------------------------+
struct IDE_Struct
{
        int     X,
                Y,
                Index;
        bool    IsMaximized;
};
//+------------------------------------------------------------------+
class C_ChartFloating
{

// ... Код класса ...

//+------------------------------------------------------------------+
                void SetPosition(const int X, const int Y, const int c0)
                        {

// ... Код функции ...

                                if (c0 == m_IDEStruct.Index)
                                {
                                        m_IDEStruct.X = m_Win[c0].PosX + 3;
                                        m_IDEStruct.Y = m_Win[c0].PosY + def_SizeBarCaption + 3;
                                        m_IDEStruct.IsMaximized = m_Win[c0].IsMaximized;
                                }
                        }
//+------------------------------------------------------------------+

// ... Код класса ...

//+------------------------------------------------------------------+
inline IDE_Struct GetIDE_Struct(void) const { return m_IDEStruct; }
//+------------------------------------------------------------------+
                bool Add_RAD_IDE(string sz0, int x, int y, int w, int h)
                        {
                                if ((w <= 0) || (h <= 0)) return false;
                                if (!StageLocal01(sz0, PERIOD_CURRENT, -1)) return false;
                                ChartApplyTemplate(m_Win[m_MaxCounter].handle, "\\Files\\Chart Trade\\IDE.tpl");
                                m_IDEStruct.Index = m_MaxCounter;
                                StageLocal02(x, y, w, h);
                                return true;
                        }
//+------------------------------------------------------------------+

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

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

void Resize(int x)
{
        for (int c0 = 0; c0 < m_CountObject; c0++) if (m_IsFloating)
        {
                ObjectSetInteger(Terminal.Get_ID(), m_ArrObject[c0].szName, OBJPROP_XDISTANCE, GetIDE_Struct().X + m_ArrObject[c0].iPosX);
                ObjectSetInteger(Terminal.Get_ID(), m_ArrObject[c0].szName, OBJPROP_YDISTANCE, GetIDE_Struct().Y + m_ArrObject[c0].iPosY);
        }else   ObjectSetInteger(Terminal.Get_ID(), m_ArrObject[c0].szName, OBJPROP_XDISTANCE, x + m_ArrObject[c0].iPosX);
};

Вышеупомянутая функция только первоначально позиционирует объекты правильно, и результат можно увидеть ниже:


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


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

void Resize(int x)
{
        for (int c0 = 0; c0 < m_CountObject; c0++) if (m_IsFloating)
        {
                ObjectSetInteger(Terminal.Get_ID(), m_ArrObject[c0].szName, OBJPROP_XDISTANCE, GetIDE_Struct().X + m_ArrObject[c0].iPosX + (GetIDE_Struct().IsMaximized ? 0 : Terminal.GetWidth()));
                ObjectSetInteger(Terminal.Get_ID(), m_ArrObject[c0].szName, OBJPROP_YDISTANCE, GetIDE_Struct().Y + m_ArrObject[c0].iPosY + (GetIDE_Struct().IsMaximized ? 0 : Terminal.GetHeight()));
        }else   ObjectSetInteger(Terminal.Get_ID(), m_ArrObject[c0].szName, OBJPROP_XDISTANCE, x + m_ArrObject[c0].iPosX);
};

И вот результат:

Хорошо, теперь давайте решим проблему движения. Это так же легко решить, вот код:

void DispatchMessage(int id, long lparam, double dparam, string sparam)
{
        static double AccumulatedRoof = 0.0;
        bool            b0;
        double  d0;
        static int px = -1, py = -1;
                                
        C_ChartFloating::DispatchMessage(id, lparam, dparam, sparam);
        if (m_CountObject < eEDIT_STOP) return;
        switch (id)
        {
                case CHARTEVENT_MOUSE_MOVE:
                        if ((GetIDE_Struct().X != px) || (GetIDE_Struct().Y != py))
                        {
                                px = GetIDE_Struct().X;
                                py = GetIDE_Struct().Y;
                                Resize(-1);
                        }
                        break;

//... Остальная часть функции ...

А вот конечный результат:

 


Заключение:

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


Важное примечание: Если вы видите странную информацию на фоне Chart Trade, это связано с обновлением 3228, которое делает объекты с цветом в clrNONE более непрозрачными. Используйте прикрепленный файл IDE, который устраняет проблему.


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

Прикрепленные файлы |
EA_1.09.zip (3617.03 KB)
IDE.tpl (10.13 KB)
Нейросети — это просто (Часть 16): Практическое использование кластеризации Нейросети — это просто (Часть 16): Практическое использование кластеризации
В предыдущей статье мы построили класс для кластеризации данных. В этой статье я хочу с вами поделиться вариантами возможного использования полученных результатов для решения практических задач трейдинга.
DoEasy. Элементы управления (Часть 6): Элемент управления "Панель", автоизменение размеров контейнера под внутреннее содержимое DoEasy. Элементы управления (Часть 6): Элемент управления "Панель", автоизменение размеров контейнера под внутреннее содержимое
В статье продолжим работу над WinForms-объектом "Панель" и реализуем автоизменение его размеров под общие размеры Dock-объектов, расположенных внутри панели. Кроме того добавим новые свойства в объект библиотеки "Символ".
Разработка торгового советника с нуля (Часть 10): Доступ к пользовательским индикаторам Разработка торгового советника с нуля (Часть 10): Доступ к пользовательским индикаторам
Как получить доступ к пользовательским индикаторам непосредственно в советнике? Торговый советник будет действительно полезен только в том случае, если в нем можно будет использовать пользовательские индикаторы, иначе это будет просто набор кодов и инструкций.
Индикаторы с интерактивным управлением на графике Индикаторы с интерактивным управлением на графике
Новый взгляд на интерфейс индикаторов. Главное — удобство. Перепробовав за долгие годы десятки различных торговых стратегий, а также протестировав сотни различных индикаторов, я сделал для себя некоторые выводы, которыми хочу с вами поделиться в этой статье.