English 中文 Español Deutsch 日本語 Português
preview
Разработка системы репликации (Часть 52): Всё усложняется (IV)

Разработка системы репликации (Часть 52): Всё усложняется (IV)

MetaTrader 5Примеры |
492 0
Daniel Jose
Daniel Jose

Введение

В предыдущей статье Разработка системы репликации (Часть 51): Всё усложняется (III), мы внесли некоторые изменения в наш старый указатель мыши, чтобы он правильно использовался в нашей системе репликации/моделирования. В той же статье я на практике продемонстрировал разницу между получением ID графика с помощью ChartOpen - функции, используемой программами для указания MetaTrader 5 на открытие графика, и получением того же идентификатора уже открытого графика, но с помощью функции ChartID.

Я думаю, было совершенно ясно, что разница и долгосрочные возможности позволят вам гораздо шире использовать программирование на MQL5.

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

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

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

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


Улучшения указателя мыши

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

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

001. //+------------------------------------------------------------------+
002. #property copyright "Daniel Jose"
003. #property description "This is an indicator for graphical studies using the mouse."
004. #property description "This is an integral part of the Replay / Simulator system."
005. #property description "However it can be used in the real market."
006. #property version "1.52"
007. #property icon "/Images/Market Replay/Icons/Indicators.ico"
008. #property link "https://www.mql5.com/ru/articles/11925"
009. #property indicator_chart_window
010. #property indicator_plots 0
011. #property indicator_buffers 1
012. //+------------------------------------------------------------------+
013. #include <Market Replay\Auxiliar\Study\C_Study.mqh>
014. //+------------------------------------------------------------------+
015. C_Study *Study     = NULL;
016. //+------------------------------------------------------------------+
017. input long user00  = 0;                                    //ID
018. input C_Study::eStatusMarket user01 = C_Study::eAuction;   //Market Status
019. input color user02 = clrBlack;                             //Price Line
020. input color user03 = clrPaleGreen;                         //Positive Study
021. input color user04 = clrLightCoral;                        //Negative Study
022. //+------------------------------------------------------------------+
023. C_Study::eStatusMarket m_Status;
024. int m_posBuff = 0;
025. double m_Buff[];
026. //+------------------------------------------------------------------+
027. int OnInit()
028. {
029.    ResetLastError();
030.    Study = new C_Study(user00, "Indicator Mouse Study", user02, user03, user04);
031.    if (_LastError != ERR_SUCCESS) return INIT_FAILED;
032.    if ((*Study).GetInfoTerminal().szSymbol != def_SymbolReplay)
033.    {
034.            MarketBookAdd((*Study).GetInfoTerminal().szSymbol);
035.            OnBookEvent((*Study).GetInfoTerminal().szSymbol);
036.            m_Status = C_Study::eCloseMarket;
037.    }else
038.            m_Status = user01;
039.    SetIndexBuffer(0, m_Buff, INDICATOR_DATA);
040.    ArrayInitialize(m_Buff, EMPTY_VALUE);
041.    
042.    return INIT_SUCCEEDED;
043. }
044. //+------------------------------------------------------------------+
045. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
046. {
047.    m_posBuff = rates_total - 5;
048.    (*Study).Update(m_Status);      
049.    
050.    return rates_total;
051. }
052. //+------------------------------------------------------------------+
053. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
054. {
055.    (*Study).DispatchMessage(id, lparam, dparam, sparam);
056.    SetBuffer();
057.    
058.    ChartRedraw((*Study).GetInfoTerminal().ID);
059. }
060. //+------------------------------------------------------------------+
061. void OnBookEvent(const string &symbol)
062. {
063.    MqlBookInfo book[];
064.    C_Study::eStatusMarket loc = m_Status;
065.    
066.    if (symbol != (*Study).GetInfoTerminal().szSymbol) return;
067.    MarketBookGet((*Study).GetInfoTerminal().szSymbol, book);
068.    m_Status = (ArraySize(book) == 0 ? C_Study::eCloseMarket : C_Study::eInTrading);
069.    for (int c0 = 0; (c0 < ArraySize(book)) && (m_Status != C_Study::eAuction); c0++)
070.            if ((book[c0].type == BOOK_TYPE_BUY_MARKET) || (book[c0].type == BOOK_TYPE_SELL_MARKET)) m_Status = C_Study::eAuction;
071.    if (loc != m_Status) (*Study).Update(m_Status);
072. }
073. //+------------------------------------------------------------------+
074. void OnDeinit(const int reason)
075. {
076.    if (reason != REASON_INITFAILED)
077.    {
078.            if ((*Study).GetInfoTerminal().szSymbol != def_SymbolReplay)
079.                    MarketBookRelease((*Study).GetInfoTerminal().szSymbol);
080.    }
081.    delete Study;
082. }
083. //+------------------------------------------------------------------+
084. inline void SetBuffer(void)
085. {
086.    uCast_Double Info;
087.    
088.    m_posBuff = (m_posBuff < 0 ? 0 : m_posBuff);
089.    m_Buff[m_posBuff + 0] = (*Study).GetInfoMouse().Position.Price;
090.    Info._datetime = (*Study).GetInfoMouse().Position.dt;
091.    m_Buff[m_posBuff + 1] = Info.dValue;
092.    Info._int[0] = (*Study).GetInfoMouse().Position.X_Adjusted;
093.    Info._int[1] = (*Study).GetInfoMouse().Position.Y_Adjusted;
094.    m_Buff[m_posBuff + 2] = Info.dValue;
095.    Info._int[0] = (*Study).GetInfoMouse().Position.X_Graphics;
096.    Info._int[1] = (*Study).GetInfoMouse().Position.Y_Graphics;
097.    m_Buff[m_posBuff + 3] = Info.dValue;
098.    Info._char[0] = ((*Study).GetInfoMouse().ExecStudy == C_Mouse::eStudyNull ? (char)(*Study).GetInfoMouse().ButtonStatus : 0);
099.    m_Buff[m_posBuff + 4] = Info.dValue;
100. }
101. //+------------------------------------------------------------------+

Исходный код указателя мыши

Скорее всего, вы не заметите разницы между данным кодом и предыдущими. Как я уже сказал, изменения очень просты и незначительны, но они есть.

Первое из них, и, возможно, единственное, заключается в индексе буфера, в который будет записываться индикатор. В строке 47 кода можно видеть, что значение смещения, которое раньше было равно четырем, теперь равно пяти. С чем связано данное изменение? Увеличение на одну единицу необходимо для обеспечения более широкого охвата, но для лучшего понимания давайте посмотрим на функцию, отвечающую за установку буфера индикатора в строке 84.

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

В частности, начиная со строки 92 и далее, всё начинает выглядеть по-другому, но не настолько. Почему я объявляю две переменные X и две переменные Y? И почему одна называется Adjusted, а другая Graphics? Именно в этом и заключается смысл. Ранее индикатор возвращал только графическую переменную, скорректированную по времени и цене, но это подходит не во всех случаях. Бывают ситуации, когда нам действительно нужно значение графической координаты, а не заданное значение. Чтобы выполнить обе ситуации, индикатор возвращает два этих значения, чтобы мы могли использовать их наилучшим образом.

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

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

001. //+------------------------------------------------------------------+
002. #property copyright "Daniel Jose"
003. //+------------------------------------------------------------------+
004. #include "..\C_Mouse.mqh"
005. //+------------------------------------------------------------------+
006. #define def_ExpansionPrefix def_MousePrefixName + "Expansion_"
007. #define def_ExpansionBtn1 def_ExpansionPrefix + "B1"
008. #define def_ExpansionBtn2 def_ExpansionPrefix + "B2"
009. #define def_ExpansionBtn3 def_ExpansionPrefix + "B3"
010. //+------------------------------------------------------------------+
011. class C_Study : public C_Mouse
012. {
013.    private :
014. //+------------------------------------------------------------------+
015.            struct st00
016.            {
017.                    eStatusMarket Status;
018.                    MqlRates      Rate;
019.                    string        szInfo;
020.                    color         corP,
021.                                  corN;
022.                    int           HeightText;
023.            }m_Info;
024. //+------------------------------------------------------------------+
025.            const datetime GetBarTime(void)
026.                    {
027.                            datetime dt;
028.                            u_Interprocess info;
029.                            int i0 = PeriodSeconds();
030.                            
031.                            if (m_Info.Status == eInReplay)
032.                            {
033.                                    if (!GlobalVariableGet(def_GlobalVariableServerTime, info.df_Value)) return ULONG_MAX;
034.                                    if ((dt = info.ServerTime) == ULONG_MAX) return ULONG_MAX;
035.                            }else dt = TimeCurrent();
036.                            if (m_Info.Rate.time <= dt)
037.                                    m_Info.Rate.time = (datetime)(((ulong) dt / i0) * i0) + i0;
038. 
039.                            return m_Info.Rate.time - dt;
040.                    }
041. //+------------------------------------------------------------------+
042.            void Draw(void)
043.                    {
044.                            double v1;
045.                            
046.                            ObjectSetInteger(GetInfoTerminal().ID, def_ExpansionBtn1, OBJPROP_YDISTANCE, GetInfoMouse().Position.Y_Adjusted - 18);
047.                            ObjectSetInteger(GetInfoTerminal().ID, def_ExpansionBtn2, OBJPROP_YDISTANCE, GetInfoMouse().Position.Y_Adjusted - 1);
048.                            ObjectSetInteger(GetInfoTerminal().ID, def_ExpansionBtn3, OBJPROP_YDISTANCE, GetInfoMouse().Position.Y_Adjusted - 1);
049.                            ObjectSetString(GetInfoTerminal().ID, def_ExpansionBtn1, OBJPROP_TEXT, m_Info.szInfo);
050.                            v1 = NormalizeDouble(100.0 - ((m_Info.Rate.close / GetInfoMouse().Position.Price) * 100.0), 2);
051.                            ObjectSetInteger(GetInfoTerminal().ID, def_ExpansionBtn2, OBJPROP_BGCOLOR, (v1 < 0 ? m_Info.corN : m_Info.corP));
052.                            ObjectSetString(GetInfoTerminal().ID, def_ExpansionBtn2, OBJPROP_TEXT, StringFormat("%.2f%%", MathAbs(v1)));
053.                            v1 = NormalizeDouble(100.0 - ((m_Info.Rate.close / iClose(GetInfoTerminal().szSymbol, PERIOD_D1, 0)) * 100.0), 2);
054.                            ObjectSetInteger(GetInfoTerminal().ID, def_ExpansionBtn3, OBJPROP_BGCOLOR, (v1 < 0 ? m_Info.corN : m_Info.corP));
055.                            ObjectSetString(GetInfoTerminal().ID, def_ExpansionBtn3, OBJPROP_TEXT, StringFormat("%.2f%%", MathAbs(v1)));
056.                    }
057. //+------------------------------------------------------------------+
058.    public  :
059. //+------------------------------------------------------------------+
060.            C_Study(long IdParam, string szShortName, color corH, color corP, color corN)
061.                    :C_Mouse(IdParam, szShortName, corH, corP, corN)
062.                    {
063.                            if (_LastError != ERR_SUCCESS) return;
064.                            ZeroMemory(m_Info);
065.                            m_Info.Status = eCloseMarket;
066.                            m_Info.Rate.close = iClose(GetInfoTerminal().szSymbol, PERIOD_D1, ((GetInfoTerminal().szSymbol == def_SymbolReplay) || (macroGetDate(TimeCurrent()) != macroGetDate(iTime(GetInfoTerminal().szSymbol, PERIOD_D1, 0))) ? 0 : 1));
067.                            m_Info.corP = corP;
068.                            m_Info.corN = corN;
069.                            CreateObjectInfo(2, 110, def_ExpansionBtn1, clrPaleTurquoise);
070.                            CreateObjectInfo(2, 53, def_ExpansionBtn2);
071.                            CreateObjectInfo(58, 53, def_ExpansionBtn3);
072.                    }
073. //+------------------------------------------------------------------+
074.            void Update(const eStatusMarket arg)
075.                    {
076.                            datetime dt;
077.                            
078.                            switch (m_Info.Status = (m_Info.Status != arg ? arg : m_Info.Status))
079.                            {
080.                                    case eCloseMarket : m_Info.szInfo = "Closed Market";
081.                                            break;
082.                                    case eInReplay    :
083.                                    case eInTrading   :
084.                                            if ((dt = GetBarTime()) < ULONG_MAX)
085.                                            {
086.                                                    m_Info.szInfo = TimeToString(dt, TIME_SECONDS);
087.                                                    break;
088.                                            }
089.                                    case eAuction     : m_Info.szInfo = "Auction";
090.                                            break;
091.                                    default           : m_Info.szInfo = "ERROR";
092.                            }
093.                            Draw();
094.                    }
095. //+------------------------------------------------------------------+
096. virtual void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
097.                    {
098.                            C_Mouse::DispatchMessage(id, lparam, dparam, sparam);
099.                            if (id == CHARTEVENT_MOUSE_MOVE) Draw();
100.                    }
101. //+------------------------------------------------------------------+
102. };
103. //+------------------------------------------------------------------+
104. #undef def_ExpansionBtn3
105. #undef def_ExpansionBtn2
106. #undef def_ExpansionBtn1
107. #undef def_ExpansionPrefix
108. #undef def_MousePrefixName
109. //+------------------------------------------------------------------+

Исходный код класса C_Study

По сути, в этом классе единственные реальные изменения были внесены в функцию Draw, которая находится в строке 42. Данные изменения направлены на сохранение логики исследований, основанных на времени и цене. Однако объекты, чья система координат имеет графический вид, вызывают эту ошибку. Данный вид объекта уже был объяснен при создании этого индикатора мыши несколько статей назад в этой же последовательности. Тогда я объяснил, почему и как работать с координатами "цена-время" и графическими координатами типа X-Y.

Чтобы сохранить обратную совместимость, мы используем скорректированные графические координаты. Это видно из строк 46-48. Однако если мы используем другие объекты, которым нужны графические координаты, и хотим, чтобы они следовали координатам Price-Time, нам нужно будет просто использовать скорректированные графические координаты, как показано здесь. Если вы замените эту скорректированную систему на графическую, которая не будет корректироваться, исследование может выглядеть несколько иначе.

Возможно, стоит испытать это на себе, чтобы только что сказанное мною, стало более понятным.

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

001. //+------------------------------------------------------------------+
002. #property copyright "Daniel Jose"
003. //+------------------------------------------------------------------+
004. #include "C_Terminal.mqh"
005. #include "Interprocess.mqh"
006. //+------------------------------------------------------------------+
007. #define def_MousePrefixName "MouseBase_"
008. #define def_NameObjectLineH def_MousePrefixName + "H"
009. #define def_NameObjectLineV def_MousePrefixName + "TV"
010. #define def_NameObjectLineT def_MousePrefixName + "TT"
011. #define def_NameObjectStudy def_MousePrefixName + "TB"
012. //+------------------------------------------------------------------+
013. class C_Mouse : public C_Terminal
014. {
015.    public  :
016.            enum eStatusMarket {eCloseMarket, eAuction, eInTrading, eInReplay};
017.            enum eBtnMouse {eKeyNull = 0x00, eClickLeft = 0x01, eClickRight = 0x02, eSHIFT_Press = 0x04, eCTRL_Press = 0x08, eClickMiddle = 0x10};
018.            struct st_Mouse
019.            {
020.                    struct st00
021.                    {
022.                            int      X_Adjusted,
023.                                     Y_Adjusted,
024.                                     X_Graphics,
025.                                     Y_Graphics;
026.                            double   Price;
027.                            datetime dt;
028.                    }Position;
029.                    uint    ButtonStatus;
030.                    bool    ExecStudy;
031.            };
032. //+------------------------------------------------------------------+
033.    protected:
034.            enum eEventsMouse {ev_HideMouse, ev_ShowMouse};
035. //+------------------------------------------------------------------+
036.            void CreateObjectInfo(int x, int w, string szName, color backColor = clrNONE) const
037.                    {
038.                            if (m_Mem.szShortName != NULL) return;
039.                            CreateObjectGraphics(szName, OBJ_BUTTON, clrNONE);
040.                            ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_STATE, true);
041.                            ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_BORDER_COLOR, clrBlack);
042.                            ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_COLOR, clrBlack);
043.                            ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_BGCOLOR, backColor);
044.                            ObjectSetString(GetInfoTerminal().ID, szName, OBJPROP_FONT, "Lucida Console");
045.                            ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_FONTSIZE, 10);
046.                            ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_CORNER, CORNER_LEFT_UPPER); 
047.                            ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_XDISTANCE, x);
048.                            ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_YDISTANCE, TerminalInfoInteger(TERMINAL_SCREEN_HEIGHT) + 1);
049.                            ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_XSIZE, w); 
050.                            ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_YSIZE, 18);
051.                    }
052. //+------------------------------------------------------------------+
053.    private :
054.            enum eStudy {eStudyNull, eStudyCreate, eStudyExecute};
055.            struct st01
056.            {
057.                    st_Mouse Data;
058.                    color    corLineH,
059.                             corTrendP,
060.                             corTrendN;
061.                    eStudy   Study;
062.            }m_Info;
063.            struct st_Mem
064.            {
065.                    bool     CrossHair,
066.                             IsFull;
067.                    datetime dt;
068.                    string   szShortName;
069.            }m_Mem;
070.            bool m_OK;
071. //+------------------------------------------------------------------+
072.            void GetDimensionText(const string szArg, int &w, int &h)
073.                    {
074.                            TextSetFont("Lucida Console", -100, FW_NORMAL);
075.                            TextGetSize(szArg, w, h);
076.                            h += 5;
077.                            w += 5;
078.                    }
079. //+------------------------------------------------------------------+
080.            void CreateStudy(void)
081.                    {
082.                            if (m_Mem.IsFull)
083.                            {
084.                                    CreateObjectGraphics(def_NameObjectLineV, OBJ_VLINE, m_Info.corLineH);
085.                                    CreateObjectGraphics(def_NameObjectLineT, OBJ_TREND, m_Info.corLineH);
086.                                    ObjectSetInteger(GetInfoTerminal().ID, def_NameObjectLineT, OBJPROP_WIDTH, 2);
087.                                    CreateObjectInfo(0, 0, def_NameObjectStudy);
088.                            }
089.                            m_Info.Study = eStudyCreate;
090.                    }
091. //+------------------------------------------------------------------+
092.            void ExecuteStudy(const double memPrice)
093.                    {
094.                            double v1 = GetInfoMouse().Position.Price - memPrice;
095.                            int w, h;
096.                            
097.                            if (!CheckClick(eClickLeft))
098.                            {
099.                                    m_Info.Study = eStudyNull;
100.                                    ChartSetInteger(GetInfoTerminal().ID, CHART_MOUSE_SCROLL, true);
101.                                    if (m_Mem.IsFull) ObjectsDeleteAll(GetInfoTerminal().ID, def_MousePrefixName + "T");
102.                            }else if (m_Mem.IsFull)
103.                            {
104.                                    string sz1 = StringFormat(" %." + (string)GetInfoTerminal().nDigits + "f [ %d ] %02.02f%% ",
105.                                            MathAbs(v1), Bars(GetInfoTerminal().szSymbol, PERIOD_CURRENT, m_Mem.dt, GetInfoMouse().Position.dt) - 1, MathAbs((v1 / memPrice) * 100.0)));
106.                                    GetDimensionText(sz1, w, h);
107.                                    ObjectSetString(GetInfoTerminal().ID, def_NameObjectStudy, OBJPROP_TEXT, sz1);                                                                                                                          
108.                                    ObjectSetInteger(GetInfoTerminal().ID, def_NameObjectStudy, OBJPROP_BGCOLOR, (v1 < 0 ? m_Info.corTrendN : m_Info.corTrendP));
109.                                    ObjectSetInteger(GetInfoTerminal().ID, def_NameObjectStudy, OBJPROP_XSIZE, w);
110.                                    ObjectSetInteger(GetInfoTerminal().ID, def_NameObjectStudy, OBJPROP_YSIZE, h);
111.                                    ObjectSetInteger(GetInfoTerminal().ID, def_NameObjectStudy, OBJPROP_XDISTANCE, GetInfoMouse().Position.X_Adjusted - w);
112.                                    ObjectSetInteger(GetInfoTerminal().ID, def_NameObjectStudy, OBJPROP_YDISTANCE, GetInfoMouse().Position.Y_Adjusted - (v1 < 0 ? 1 : h));                          
113.                                    ObjectMove(GetInfoTerminal().ID, def_NameObjectLineT, 1, GetInfoMouse().Position.dt, GetInfoMouse().Position.Price);
114.                                    ObjectSetInteger(GetInfoTerminal().ID, def_NameObjectLineT, OBJPROP_COLOR, (memPrice > GetInfoMouse().Position.Price ? m_Info.corTrendN : m_Info.corTrendP));
115.                            }
116.                            m_Info.Data.ButtonStatus = eKeyNull;
117.                    }
118. //+------------------------------------------------------------------+
119.    public  :
120. //+------------------------------------------------------------------+
121.            C_Mouse(const long id, const string szShortName)
122.                    :C_Terminal(id),
123.                    m_OK(false)
124.                    {
125.                            m_Mem.szShortName = szShortName;
126.                    }
127. //+------------------------------------------------------------------+
128.            C_Mouse(const long id, const string szShortName, color corH, color corP, color corN)
129.                    :C_Terminal(id)
130.                    {
131.                            if (!(m_OK = IndicatorCheckPass(szShortName))) SetUserError(C_Terminal::ERR_Unknown);
132.                            if (_LastError != ERR_SUCCESS) return;
133.                            m_Mem.szShortName = NULL;
134.                            m_Mem.CrossHair = (bool)ChartGetInteger(GetInfoTerminal().ID, CHART_CROSSHAIR_TOOL);
135.                            ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_MOUSE_MOVE, true);
136.                            ChartSetInteger(GetInfoTerminal().ID, CHART_CROSSHAIR_TOOL, false);
137.                            ZeroMemory(m_Info);
138.                            m_Info.corLineH  = corH;
139.                            m_Info.corTrendP = corP;
140.                            m_Info.corTrendN = corN;
141.                            m_Info.Study = eStudyNull;
142.                            if (m_Mem.IsFull = (corP != clrNONE) && (corH != clrNONE) && (corN != clrNONE))
143.                                    CreateObjectGraphics(def_NameObjectLineH, OBJ_HLINE, m_Info.corLineH);
144.                    }
145. //+------------------------------------------------------------------+
146.            ~C_Mouse()
147.                    {
148.                            if (!m_OK) return;
149.                            ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, 0, false);
150.                            ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_MOUSE_MOVE, false);
151.                            ChartSetInteger(GetInfoTerminal().ID, CHART_CROSSHAIR_TOOL, m_Mem.CrossHair);
152.                            ObjectsDeleteAll(GetInfoTerminal().ID, def_MousePrefixName);
153.                    }
154. //+------------------------------------------------------------------+
155. inline bool CheckClick(const eBtnMouse value) 
156.                    {
157.                            return (GetInfoMouse().ButtonStatus & value) == value;
158.                    }
159. //+------------------------------------------------------------------+
160. inline const st_Mouse GetInfoMouse(void)
161.                    {
162.                            if (m_Mem.szShortName != NULL)
163.                            {
164.                                    double Buff[];
165.                                    uCast_Double loc;
166.                                    int handle = ChartIndicatorGet(GetInfoTerminal().ID, 0, m_Mem.szShortName);
167.                                    
168.                                    ZeroMemory(m_Info.Data);
169.                                    if (CopyBuffer(handle, 0, 0, 5, Buff) == 5)
170.                                    {
171.                                            m_Info.Data.Position.Price = Buff[0];
172.                                            loc.dValue = Buff[1];
173.                                            m_Info.Data.Position.dt = loc._datetime;
174.                                            loc.dValue = Buff[2];
175.                                            m_Info.Data.Position.X_Adjusted = loc._int[0];
176.                                            m_Info.Data.Position.Y_Adjusted = loc._int[1];
177.                                            loc.dValue = Buff[3];
178.                                            m_Info.Data.Position.X_Graphics = loc._int[0];
179.                                            m_Info.Data.Position.Y_Graphics = loc._int[1];
180.                                            loc.dValue = Buff[4];
181.                                            m_Info.Data.ButtonStatus = loc._char[0];
182.                                            IndicatorRelease(handle);
183.                                    }
184.                            }
185. 
186.                            return m_Info.Data;
187.                    }
188. //+------------------------------------------------------------------+
189. virtual void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
190.                    {
191.                            int w = 0;
192.                            static double memPrice = 0;
193.                            
194.                            if (m_Mem.szShortName == NULL) switch (id)
195.                            {
196.                                    case (CHARTEVENT_CUSTOM + ev_HideMouse):
197.                                            if (m_Mem.IsFull) ObjectSetInteger(GetInfoTerminal().ID, def_NameObjectLineH, OBJPROP_COLOR, clrNONE);
198.                                            break;
199.                                    case (CHARTEVENT_CUSTOM + ev_ShowMouse):
200.                                            if (m_Mem.IsFull) ObjectSetInteger(GetInfoTerminal().ID, def_NameObjectLineH, OBJPROP_COLOR, m_Info.corLineH);
201.                                            break;
202.                                    case CHARTEVENT_MOUSE_MOVE:
203.                                            ChartXYToTimePrice(GetInfoTerminal().ID, m_Info.Data.Position.X_Graphics = (int)lparam, m_Info.Data.Position.Y_Graphics = (int)dparam, w, m_Info.Data.Position.dt, m_Info.Data.Position.Price);
204.                                            if (m_Mem.IsFull) ObjectMove(GetInfoTerminal().ID, def_NameObjectLineH, 0, 0, m_Info.Data.Position.Price = AdjustPrice(m_Info.Data.Position.Price));
205.                                            m_Info.Data.Position.dt = AdjustTime(m_Info.Data.Position.dt);
206.                                            ChartTimePriceToXY(GetInfoTerminal().ID, w, m_Info.Data.Position.dt, m_Info.Data.Position.Price, m_Info.Data.Position.X_Adjusted, m_Info.Data.Position.Y_Adjusted);
207.                                            if ((m_Info.Study != eStudyNull) && (m_Mem.IsFull)) ObjectMove(GetInfoTerminal().ID, def_NameObjectLineV, 0, m_Info.Data.Position.dt, 0);
208.                                            m_Info.Data.ButtonStatus = (uint) sparam;
209.                                            if (CheckClick(eClickMiddle))
210.                                                    if ((!m_Mem.IsFull) || ((color)ObjectGetInteger(GetInfoTerminal().ID, def_NameObjectLineH, OBJPROP_COLOR) != clrNONE)) CreateStudy();
211.                                            if (CheckClick(eClickLeft) && (m_Info.Study == eStudyCreate))
212.                                            {
213.                                                    ChartSetInteger(GetInfoTerminal().ID, CHART_MOUSE_SCROLL, false);
214.                                                    if (m_Mem.IsFull) ObjectMove(GetInfoTerminal().ID, def_NameObjectLineT, 0, m_Mem.dt = GetInfoMouse().Position.dt, memPrice = GetInfoMouse().Position.Price);
215.                                                    m_Info.Study = eStudyExecute;
216.                                            }
217.                                            if (m_Info.Study == eStudyExecute) ExecuteStudy(memPrice);
218.                                            m_Info.Data.ExecStudy = m_Info.Study == eStudyExecute;
219.                                            break;
220.                                    case CHARTEVENT_OBJECT_DELETE:
221.                                            if ((m_Mem.IsFull) && (sparam == def_NameObjectLineH)) CreateObjectGraphics(def_NameObjectLineH, OBJ_HLINE, m_Info.corLineH);
222.                                            break;
223.                            }
224.                    }
225. //+------------------------------------------------------------------+
226. };
227. //+------------------------------------------------------------------+
228. #undef def_NameObjectLineV
229. #undef def_NameObjectLineH
230. #undef def_NameObjectLineT
231. #undef def_NameObjectStudy
232. //+------------------------------------------------------------------+

Исходный код класса C_Mouse

Вы можете заметить, что ситуация здесь кажется напряженной, но не дайте себе быть обманутыми первым впечатлением. Переходим к начальным деталям. Присутствующие в строках 4 и 5 includes, можно получить из прошлых статей. Они не изменились, так что нет смысла повторять их здесь.

Если спуститься чуть ниже, то в строках с 22 по 25 мы найдем объявления переменных, которые мы заметили в предыдущих кодах. Следует обратить внимание на то, что они являются частью структуры и что данная структура является публичной. Таким образом, мы сможем использовать эту же структуру в дальнейшем, что сделает код более читабельным и качественным. Эти же переменные снова используются в строках 110 и 111 только для того, чтобы обеспечить правильное расположение некоторых объектов.

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

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

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

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

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

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

Но поскольку в некоторых точках нам нужно, чтобы значения X и Y были скорректированы по цене и времени, мы используем строку 203. Таким образом, мы получим скорректированные значения. Ранее не проводилось различие между графически скорректированным и нескорректированным значением. Однако, пытаясь манипулировать предметами и интегрировать индикатор мыши с индикатором управления, необходимо было сделать данное различие. Этим мы закрываем индикатор мыши, и можем переключить внимание на индикатор управления.


Новый индикатор управления

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

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

Исходный код класса можно найти чуть ниже.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #include "C_Terminal.mqh"
05. //+------------------------------------------------------------------+
06. #define def_MaxImages 2
07. //+------------------------------------------------------------------+
08. class C_DrawImage : protected C_Terminal
09. {
10. //+------------------------------------------------------------------+
11.     private :
12.             struct st_00
13.             {
14.                     int  widthMap,
15.                          heightMap;
16.                     uint Map[];
17.             }m_InfoImage[def_MaxImages];
18.             uint    m_Pixels[];
19.             string  m_szObjName,
20.                     m_szRecName;
21. //+------------------------------------------------------------------+
22.             void ReSizeImage(const int w, const int h, const uchar v, const int what)
23.                     {
24. #define _Transparency(A) (((A > 100 ? 100 : (100 - A)) * 2.55) / 255.0)
25.                             double fx = (w * 1.0) / m_InfoImage[what].widthMap;
26.                             double fy = (h * 1.0) / m_InfoImage[what].heightMap;
27.                             uint pyi, pyf, pxi, pxf, tmp;
28.                             uint uc;
29. 
30.                             ArrayResize(m_Pixels, w * h);
31.                             for (int cy = 0, y = 0; cy < m_InfoImage[what].heightMap; cy++, y += m_InfoImage[what].widthMap)
32.                             {
33.                                     pyf = (uint)(fy * cy) * w;
34.                                     tmp = pyi = (uint)(fy * (cy - 1)) * w;
35.                                     for (int x = 0; x < m_InfoImage[what].widthMap; x++)
36.                                     {
37.                                             pxf = (uint)(fx * x);
38.                                             pxi = (uint)(fx * (x - 1));
39.                                             uc = (uchar(double((uc = m_InfoImage[what].Map[x + y]) >> 24) * _Transparency(v)) << 24) | uc & 0x00FFFFFF;
40.                                             m_Pixels[pxf + pyf] = uc;
41.                                             for (pxi++; pxi < pxf; pxi++) m_Pixels[pxi + pyf] = uc;
42.                                     }
43.                                     for (pyi += w; pyi < pyf; pyi += w) 
44.                                             for (int x = 0; x < w; x++)
45.                                                     m_Pixels[x + pyi] = m_Pixels[x + tmp];
46.                             }
47. #undef _Transparency
48.                     }
49. //+------------------------------------------------------------------+
50.     public  :
51. //+------------------------------------------------------------------+
52.             C_DrawImage(long id, int sub, string szObjName, const color cFilter, const string szFile1, const string szFile2 = NULL)
53.                     :C_Terminal(id),
54.                     m_szObjName(NULL),
55.                     m_szRecName(NULL)
56.                     {
57.                             if (!ObjectCreate(GetInfoTerminal().ID, m_szObjName = szObjName, OBJ_BITMAP_LABEL, sub, 0, 0)) SetUserError(C_Terminal::ERR_Unknown);
58.                             m_szRecName = "::" + m_szObjName;                               
59.                             for (int c0 = 0; (c0 < def_MaxImages) && (_LastError == ERR_SUCCESS); c0++)
60.                             {
61.                                     ResourceReadImage((c0 == 0 ? szFile1 : (szFile2 == NULL ? szFile1 : szFile2)), m_InfoImage[c0].Map, m_InfoImage[c0].widthMap, m_InfoImage[c0].heightMap);
62.                                     ArrayResize(m_Pixels, m_InfoImage[c0].heightMap * m_InfoImage[c0].widthMap);
63.                                     ArrayInitialize(m_Pixels, 0);
64.                                     for (int c1 = (m_InfoImage[c0].heightMap * m_InfoImage[c0].widthMap) - 1; c1 >= 0; c1--)
65.                                             if ((m_InfoImage[c0].Map[c1] & 0x00FFFFFF) != cFilter) m_Pixels[c1] = m_InfoImage[c0].Map[c1];
66.                                     ArraySwap(m_InfoImage[c0].Map, m_Pixels);
67.                             }
68.                             ArrayResize(m_Pixels, 1);
69.                     }
70. //+------------------------------------------------------------------+
71.             ~C_DrawImage()
72.                     {
73.                             for (int c0 = 0; c0 < def_MaxImages; c0++)
74.                                     ArrayFree(m_InfoImage[c0].Map);
75.                             ArrayFree(m_Pixels);
76.                             ObjectDelete(GetInfoTerminal().ID, m_szObjName);
77.                             ResourceFree(m_szRecName);
78.                     }
79. //+------------------------------------------------------------------+
80.             void Paint(const int x, const int y, const int w, const int h, const uchar cView, const int what)
81.                     {
82.                             
83.                             if ((m_szRecName == NULL) || (what < 0) || (what >= def_MaxImages)) return;
84.                             ReSizeImage(w, h, cView, what);
85.                             ObjectSetInteger(GetInfoTerminal().ID, m_szObjName, OBJPROP_XDISTANCE, x);
86.                             ObjectSetInteger(GetInfoTerminal().ID, m_szObjName, OBJPROP_YDISTANCE, y);
87.                             if (ResourceCreate(m_szRecName, m_Pixels, w, h, 0, 0, 0, COLOR_FORMAT_ARGB_NORMALIZE))
88.                             {
89.                                     ObjectSetString(GetInfoTerminal().ID, m_szObjName, OBJPROP_BMPFILE, m_szRecName);
90.                                     ChartRedraw(GetInfoTerminal().ID);
91.                             }
92.                     }
93. //+------------------------------------------------------------------+
94. };
95. //+------------------------------------------------------------------+
96. #undef def_MaxImages
97. //+------------------------------------------------------------------+

Исходный код класса C_DrawImage

Обратите внимание, что код очень компактен. Это связано с тем, что мы не будем использовать внешние ресурсы. В нашем распоряжении MQL5 и всё, что он может предложить нам в помощь. По сути, данный класс представляет собой альтернативный способ использования объектов OBJ_BITMAP_LABEL или OBJ_BITMAP. Оба вида являются частью стандартной библиотеки MQL5, но не имеют возможности использовать прозрачность или невидимые точки на изображении. Поэтому мы создали класс, который можно увидеть выше, и таким образом можем расширять его по своему усмотрению.

Данный класс настолько прост, что мы используем всего лишь четыре процедуры, и все они довольно легкие. Хорошо, но давайте коротко объясним, как здесь всё устроено.

В строке 6 мы указываем максимальное количество изображений, которые мы будем использовать. Можно увеличить это число, но потребуется некоторая модификация кода. Вскоре я покажу, где нужно будет внести изменения. Затем мы перейдем в код класса в строке 8. Обратите внимание на то, что он наследует класс терминала. Это можно будет рассмотреть на предыдущих статьях.

Сразу после этого, в строке 11, мы используем приватную часть кода, чтобы указать, что объявленные данные будут закрыты для внутреннего кода класса. На самом деле нам не нужно изменять эту часть, где объявляются такие данные. Однако в строке 22 мы входим в процедуру предварительного рендеринга, где настраиваем размер изображения и уровень прозрачности. Этот уровень варьируется от 0 % до 100 % и меняется целыми шагами, т.е. по одному шагу за раз. Определение строки 24 гарантирует, что преобразование прозрачности изображения будет правильным.

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

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

В этом конструкторе в строке 61 мы получим доступ к изображениям, на которые мы будем ссылаться как на ресурсы. Помните, что нашей системе не нужны внешние ресурсы, поэтому транспортировать можно только исполняемые файлы. Строка 63 гарантирует, что изображение полностью прозрачно, но в строке 64 мы вводим цикл, который будет перебирать загруженное изображение, и при каждом взаимодействии в строке 65 мы будем проверять, найден ли указанный цвет в качестве прозрачного. Если равен true, то цвет не будет добавлен, если равен false, то будет добавлен.

В строке 66 мы используем функцию MQL5, чтобы изменить исходное изображение на модифицированное, так мы делаем всё насколько возможно быстро. Уже в строке 68 мы оставляем систему чистой.

Деструктор в начале строки 71 используется для возврата всех ресурсов (в данном случае памяти) в систему, освобождая их для использования другими программами. Особые детали или объяснения не требуются.

В строке 80 находится процедура рисования. Данная процедура приведет к тому, что изображение будет воспроизведено на графике в указанном положении и размерах с использованием первых четырех параметров в качестве ориентиров. Пятый параметр вызова должен быть значением от 0 до 100, где 0 - отсутствие прозрачности, а 100 - полная прозрачность. Шестой параметр указывает, какое изображение будет представлено на самом деле. Значение всегда должно начинаться с 0 - это индекс первого изображения, а максимальное значение - минус 1; то есть мы используем ту же систему, что и для объектов OBJ_BITMAP_LABEL и OBJ_BITMAP, в которых мы указываем индекс изображения, только здесь их может быть больше 2, если мы изменим указанные мной пункты. Данная функция рисования не требует пояснений и дополнительных изменений, если мы хотим расширить возможности.

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

Хорошо, теперь мы можем перейти к коду для индикатора и класса управления. Но прежде чем мы рассмотрим код класса управления, давайте бегло посмотрим на код индикатора. Ниже можно увидеть его в полном объеме.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. #property icon "/Images/Market Replay/Icons/Replay - Device.ico"
04. #property description "Control indicator for the Replay-Simulator service."
05. #property description "This one doesn't work without the service loaded."
06. #property version   "1.52"
07. #property link "https://www.mql5.com/ru/articles/11925"
08. #property indicator_chart_window
09. #property indicator_plots 0
10. //+------------------------------------------------------------------+
11. #include <Market Replay\Service Graphics\C_Controls.mqh>
12. //+------------------------------------------------------------------+
13. C_Controls *control = NULL;
14. //+------------------------------------------------------------------+
15. input long user00 = 0;    //ID
16. //+------------------------------------------------------------------+
17. int OnInit()
18. {
19.     u_Interprocess Info;
20. 
21.     ResetLastError();       
22.     if (CheckPointer(control = new C_Controls(user00, "Market Replay Control", new C_Mouse(user00, "Indicator Mouse Study"))) == POINTER_INVALID)
23.             SetUserError(C_Terminal::ERR_PointerInvalid);
24.     if (_LastError != ERR_SUCCESS)
25.     {
26.             Print("Control indicator failed on initialization.");
27.             return INIT_FAILED;
28.     }       
29.     if (GlobalVariableCheck(def_GlobalVariableReplay)) Info.df_Value = GlobalVariableGet(def_GlobalVariableReplay); else Info.df_Value = 0;
30.     EventChartCustom(user00, C_Controls::evInit, Info.s_Infos.iPosShift, Info.df_Value, "");
31.     GlobalVariableTemp(def_GlobalVariableReplay);
32.     
33.     return INIT_SUCCEEDED;
34. }
35. //+------------------------------------------------------------------+
36. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
37. {
38.     return rates_total;
39. }
40. //+------------------------------------------------------------------+
41. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
42. {
43.     (*control).DispatchMessage(id, lparam, dparam, sparam);
44. }
45. //+------------------------------------------------------------------+
46. void OnDeinit(const int reason)
47. {
48.     switch (reason)
49.     {
50.             case REASON_TEMPLATE:
51.                     Print("Modified template. Replay/simulation system shutting down.");
52.             case REASON_PARAMETERS:
53.             case REASON_REMOVE:
54.             case REASON_CHARTCLOSE:
55.                     if (ChartSymbol(user00) != def_SymbolReplay) break;
56.                     GlobalVariableDel(def_GlobalVariableReplay);
57.                     ChartClose(user00);
58.                     break;
59.     }
60.     delete control;
61. }
62. //+------------------------------------------------------------------+

Исходный код индикатора управления

Можно отметить, что этот код подвергся процессу уменьшения, но это временно, так как нам нужен более легкий код, чтобы протестировать его должным образом. В принципе, можно увидеть, что у нас гораздо меньше вызовов и ссылок на класс управления. Но почему? Причина в изменении организации работы. Мы решили, что у нас должно быть как можно меньше точек доступа. Обратите внимание, что код OnInit сильно отличается. Теперь в строке 30 мы видим нечто очень необычное. На этом этапе мы инициализируем данные класса управления после конструктора. Однако для конструктора, упомянутого в строке 22, мы решили, что он будет заниматься только инициализацией используемых указателей. Управление событиями возьмет на себя все остальные задачи. По этой причине мы видим то, что показано в строке 30, но здесь можно начать сомневаться. Итак, чтобы немного развеять сомнения, давайте взглянем на базовый код класса управления.

001. //+------------------------------------------------------------------+
002. #property copyright "Daniel Jose"
003. //+------------------------------------------------------------------+
004. #include "..\Auxiliar\Interprocess.mqh"
005. #include "..\Auxiliar\C_DrawImage.mqh"
006. //+------------------------------------------------------------------+
007. #define def_PathBMP                "Images\\Market Replay\\Control\\"
008. #define def_ButtonPlay             def_PathBMP + "Play.bmp"
009. #define def_ButtonPause            def_PathBMP + "Pause.bmp"
010. #define def_ButtonLeft             def_PathBMP + "Left.bmp"
011. #define def_ButtonLeftBlock        def_PathBMP + "Left_Block.bmp"
012. #define def_ButtonRight            def_PathBMP + "Right.bmp"
013. #define def_ButtonRightBlock       def_PathBMP + "Right_Block.bmp"
014. #define def_ButtonPin              def_PathBMP + "Pin.bmp"
015. #resource "\\" + def_ButtonPlay
016. #resource "\\" + def_ButtonPause
017. #resource "\\" + def_ButtonLeft
018. #resource "\\" + def_ButtonLeftBlock
019. #resource "\\" + def_ButtonRight
020. #resource "\\" + def_ButtonRightBlock
021. #resource "\\" + def_ButtonPin
022. //+------------------------------------------------------------------+
023. #define def_PrefixCtrlName         "MarketReplayCTRL_"
024. #define def_PosXObjects            120
025. //+------------------------------------------------------------------+
026. #define def_SizeButtons            32
027. #define def_ColorFilter            0xFF00FF
028. //+------------------------------------------------------------------+
029. #include "..\Auxiliar\C_Terminal.mqh"
030. #include "..\Auxiliar\C_Mouse.mqh"
031. //+------------------------------------------------------------------+
032. class C_Controls : private C_Terminal
033. {
034.    protected:
035.            enum EventCustom {evInit};
036.    private :
037. //+------------------------------------------------------------------+
038.            enum eObjectControl {ePlay, eLeft, eRight, ePin, eNull};
039. //+------------------------------------------------------------------+
040.            struct st_00
041.            {
042.                    string  szBarSlider,
043.                            szBarSliderBlock;
044.                    int     Minimal;
045.            }m_Slider;
046.            struct st_01
047.            {
048.                    C_DrawImage *Btn;
049.                    bool         state;
050.                    int          x, y, w, h;
051.            }m_Section[eObjectControl::eNull];
052.            C_Mouse *m_MousePtr;
053. //+------------------------------------------------------------------+
054. inline void CreteBarSlider(int x, int size)
055.                    {
056.                            ObjectCreate(GetInfoTerminal().ID, m_Slider.szBarSlider = def_PrefixCtrlName + "B1", OBJ_RECTANGLE_LABEL, 0, 0, 0);
057.                            ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_XDISTANCE, def_PosXObjects + x);
058.                            ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_YDISTANCE, m_Section[ePin].y + 11);
059.                            ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_XSIZE, size);
060.                            ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_YSIZE, 9);
061.                            ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_BGCOLOR, clrLightSkyBlue);
062.                            ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_BORDER_COLOR, clrBlack);
063.                            ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_WIDTH, 3);
064.                            ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_BORDER_TYPE, BORDER_FLAT);
065.                            ObjectCreate(GetInfoTerminal().ID, m_Slider.szBarSliderBlock = def_PrefixCtrlName + "B2", OBJ_RECTANGLE_LABEL, 0, 0, 0);
066.                            ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_XDISTANCE, def_PosXObjects + x);
067.                            ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_YDISTANCE, m_Section[ePin].y + 6);
068.                            ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_YSIZE, 19);
069.                            ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_BGCOLOR, clrRosyBrown);
070.                            ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_BORDER_TYPE, BORDER_RAISED);
071.                    }
072. //+------------------------------------------------------------------+
073.            void SetPlay(bool state)
074.                    {
075.                            if (m_Section[ePlay].Btn == NULL)
076.                                    m_Section[ePlay].Btn = new C_DrawImage(GetInfoTerminal().ID, 0, def_PrefixCtrlName + EnumToString(ePlay), def_ColorFilter, "::" + def_ButtonPlay, "::" + def_ButtonPause);
077.                            m_Section[ePlay].Btn.Paint(m_Section[ePlay].x, m_Section[ePlay].y, m_Section[ePlay].w, m_Section[ePlay].h, 20, ((m_Section[ePlay].state = state) ? 0 : 1));
078.                            ChartRedraw(GetInfoTerminal().ID);
079.                    }
080. //+------------------------------------------------------------------+
081.            void CreateCtrlSlider(void)
082.                    {
083.                            CreteBarSlider(77, 436);
084.                            m_Section[eLeft].Btn = new C_DrawImage(GetInfoTerminal().ID, 0, def_PrefixCtrlName + EnumToString(eLeft), def_ColorFilter, "::" + def_ButtonLeft, "::" + def_ButtonLeftBlock);
085.                            m_Section[eRight].Btn = new C_DrawImage(GetInfoTerminal().ID, 0, def_PrefixCtrlName + EnumToString(eRight), def_ColorFilter, "::" + def_ButtonRight, "::" + def_ButtonRightBlock);
086.                            m_Section[ePin].Btn = new C_DrawImage(GetInfoTerminal().ID, 0, def_PrefixCtrlName + EnumToString(ePin), def_ColorFilter, "::" + def_ButtonPin);
087.                            PositionPinSlider(m_Slider.Minimal);
088.                    }
089. //+------------------------------------------------------------------+
090. inline void RemoveCtrlSlider(void)
091.                    {                       
092.                            ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, false);
093.                            for (eObjectControl c0 = ePlay + 1; c0 < eNull; c0++)
094.                            {
095.                                    delete m_Section[c0].Btn;
096.                                    m_Section[c0].Btn = NULL;
097.                            }
098.                            ObjectsDeleteAll(GetInfoTerminal().ID, def_PrefixCtrlName + "B");
099.                            ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, true);
100.                    }
101. //+------------------------------------------------------------------+
102. inline void PositionPinSlider(int p)
103.                    {
104.                            int iL, iR;
105.                            
106.                            m_Section[ePin].x = (p < m_Slider.Minimal ? m_Slider.Minimal : (p > def_MaxPosSlider ? def_MaxPosSlider : p));
107.                            iL = (m_Section[ePin].x != m_Slider.Minimal ? 0 : 1);
108.                            iR = (m_Section[ePin].x < def_MaxPosSlider ? 0 : 1);
109.                            m_Section[ePin].x += def_PosXObjects;
110.                            m_Section[ePin].x += 95 - (m_Section[ePin].w / 2);
111.                            for (eObjectControl c0 = ePlay + 1; c0 < eNull; c0++)
112.                                    m_Section[c0].Btn.Paint(m_Section[c0].x, m_Section[c0].y, m_Section[c0].w, m_Section[c0].h, 20, (c0 == eLeft ? iL : (c0 == eRight ? iR : 0)));
113.                            ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_XSIZE, m_Slider.Minimal + 2);
114.                    }
115. //+------------------------------------------------------------------+
116. inline eObjectControl CheckPositionMouseClick(void)
117.                    {
118.                            C_Mouse::st_Mouse InfoMouse;
119.                            int x, y;
120.                            
121.                            InfoMouse = (*m_MousePtr).GetInfoMouse();
122.                            x = InfoMouse.Position.X_Graphics;
123.                            y = InfoMouse.Position.Y_Graphics;
124.                            for (eObjectControl c0 = ePlay; c0 < eNull; c0++)
125.                            {
126.                                    if ((m_Section[c0].x <= x) && (m_Section[c0].y <= y) && ((m_Section[c0].x + m_Section[c0].w) >= x) && ((m_Section[c0].y + m_Section[c0].h) >= y))
127.                                            return c0;
128.                            }
129.                            
130.                            return eNull;
131.                    }
132. //+------------------------------------------------------------------+
133.    public  :
134. //+------------------------------------------------------------------+
135.            C_Controls(const long Arg0, const string szShortName, C_Mouse *MousePtr)
136.                    :C_Terminal(Arg0),
137.                     m_MousePtr(MousePtr)
138.                    {
139.                            if (!IndicatorCheckPass(szShortName)) SetUserError(C_Terminal::ERR_Unknown);
140.                            ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, false);
141.                            ObjectsDeleteAll(GetInfoTerminal().ID, def_PrefixCtrlName);
142.                            ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, true);
143.                            for (eObjectControl c0 = ePlay; c0 < eNull; c0++)
144.                            {
145.                                    m_Section[c0].h = m_Section[c0].w = def_SizeButtons;
146.                                    m_Section[c0].y = 25;
147.                                    m_Section[c0].Btn = NULL;
148.                            }
149.                            m_Section[ePlay].x = def_PosXObjects;
150.                            m_Section[eLeft].x = m_Section[ePlay].x + 47;
151.                            m_Section[eRight].x = m_Section[ePlay].x + 511;
152.                    }
153. //+------------------------------------------------------------------+
154.            ~C_Controls()
155.                    {
156.                            delete m_MousePtr;
157.                            for (eObjectControl c0 = ePlay; c0 < eNull; c0++) delete m_Section[c0].Btn;
158.                            ObjectsDeleteAll(GetInfoTerminal().ID, def_PrefixCtrlName);
159.                    }
160. //+------------------------------------------------------------------+
161.            void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
162.                    {
163.                            u_Interprocess Info;
164.                            
165.                            switch (id)
166.                            {
167.                                    case CHARTEVENT_CUSTOM + C_Controls::evInit:
168.                                            Info.df_Value = dparam;
169.                                            m_Slider.Minimal = Info.s_Infos.iPosShift;
170.                                            SetPlay(Info.s_Infos.isPlay);
171.                                            if (!Info.s_Infos.isPlay) CreateCtrlSlider();
172.                                            break;
173.                                    case CHARTEVENT_OBJECT_DELETE:
174.                                            if (StringSubstr(sparam, 0, StringLen(def_PrefixCtrlName)) == def_PrefixCtrlName)
175.                                            {
176.                                                    if (sparam == (def_PrefixCtrlName + EnumToString(ePlay)))
177.                                                    {
178.                                                            delete m_Section[ePlay].Btn;
179.                                                            m_Section[ePlay].Btn = NULL;
180.                                                            SetPlay(false);
181.                                                    }else
182.                                                    {
183.                                                            RemoveCtrlSlider();
184.                                                            CreateCtrlSlider();
185.                                                    }
186.                                            }
187.                                            break;
188.                                    case CHARTEVENT_MOUSE_MOVE:
189.                                            if ((*m_MousePtr).CheckClick(C_Mouse::eClickLeft)) switch (CheckPositionMouseClick())
190.                                            {
191.                                                    case ePlay:
192.                                                            SetPlay(!m_Section[ePlay].state);
193.                                                            if (m_Section[ePlay].state) RemoveCtrlSlider();
194.                                                            else CreateCtrlSlider();
195.                                                            break;
196.                                                    case eLeft:
197.                                                            break;
198.                                                    case eRight:
199.                                                            break;
200.                                                    case ePin:
201.                                                            break;
202.                                            }
203.                                            break;
204.                            }
205.                            ChartRedraw(GetInfoTerminal().ID);
206.                    }
207. //+------------------------------------------------------------------+
208. };
209. //+------------------------------------------------------------------+
210. #undef def_PosXObjects
211. #undef def_ButtonPlay
212. #undef def_ButtonPause
213. #undef def_ButtonLeft
214. #undef def_ButtonRight
215. #undef def_ButtonPin
216. #undef def_PrefixCtrlName
217. #undef def_PathBMP
218. //+------------------------------------------------------------------+

Исходный код класса C_Controls

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

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

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

В данной процедуре в строке 75 мы проверим, был ли создан указатель для кнопки воспроизведения и паузы. Если он не был создан, в строке 76 выполнится конструктор класса рисования. Таким образом, мы указываем, какие изображения надо использовать. Точно так же, как если бы мы использовали функцию ObjectCreate из библиотеки MQL5. Затем, в строке 77, мы воспроизводим изображение в позиции, указанной кнопкой управления, и говорим, какое изображение будем отслеживать. Наконец, в строке 78 мы просим обновить график, чтобы рисовать изображение.

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

Когда MetaTrader 5 посылает нам событие мыши, мы спрашиваем у индикатора мыши, произошел ли щелчок левой кнопкой. Это будет подтверждено только в том случае, если индикатор мыши не находится в режиме исследования. Если щелчок произошел, то класс управления проверит, на какой из них был произведен щелчок. Возникла ошибка, которую мы устраним позже, но когда будет подтверждено, что кнопка воспроизведения/паузы была нажата, код между строками 191 и 195 будет выполнен, так что мы получим представление о взаимодействии, которое происходит между пользователем и всей системой.

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




Заключение

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

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

В следующей статье мы подробнее рассмотрим данный индикатор управления. Теперь всё начнет расширяться в геометрической прогрессии.

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

Прикрепленные файлы |
Anexo.zip (420.65 KB)
Нейросети в трейдинге: Контрастный Трансформер паттернов (Окончание) Нейросети в трейдинге: Контрастный Трансформер паттернов (Окончание)
В последней статье нашей серии мы рассмотрели фреймворк Atom-Motif Contrastive Transformer (AMCT), который использует контрастное обучение для выявления ключевых паттернов на всех уровнях — от базовых элементов до сложных структур. В этой статье мы продолжаем реализацию подходов AMCT средствами MQL5.
Поиск произвольных паттернов валютных пар на Python с использованием MetaTrader 5 Поиск произвольных паттернов валютных пар на Python с использованием MetaTrader 5
Есть ли повторяющиеся паттерны и закономерности на валютном рынке? Я решил создать свою собственную систему анализа паттернов, используя Python и MetaTrader 5. Этакий симбиоз математики и программирования для покорения Форекса.
Модифицированный советник Grid-Hedge в MQL5 (Часть IV): Оптимизация простой сеточной стратегии (I) Модифицированный советник Grid-Hedge в MQL5 (Часть IV): Оптимизация простой сеточной стратегии (I)
В четвертой части мы вернемся к советникам Simple Hedge и Simple Grid, разработанным ранее. В этот раз будем совершенствовать советник Simple Hedge. Будем использовать математический анализ и подход грубой силы (brute force) чтобы оптимизировать стратегию. Эта статья углубляется в математическую оптимизацию стратегии и закладывает основу для будущего исследования оптимизации на основе кода в последующих частях.
Разработка системы репликации (Часть 51): Все усложняется (III) Разработка системы репликации (Часть 51): Все усложняется (III)
В данной статье мы разберемся с одним из самых сложных вопросов сферы программирования на MQL5: как правильно получить ID графика, и почему иногда объекты не строятся на графике. Представленные здесь материалы носят исключительно дидактический характер. Ни в коем случае нельзя рассматривать приложение ни с какой иной целью, кроме как для изучения и освоения представленных концепций.