Español Português
preview
Разработка системы репликации (Часть 45): Проект Chart Trade (IV)

Разработка системы репликации (Часть 45): Проект Chart Trade (IV)

MetaTrader 5Примеры | 27 августа 2024, 10:11
30 0
Daniel Jose
Daniel Jose

Введение

В предыдущей статье Разработка системы репликации (Часть 44): Проект Chart Trade(III) я показал, как можно привнести некоторую интерактивность в окно Chart Trade, чтобы оно вело себя так, как если бы в нем присутствовали объекты. Даже когда единственным реальным объектом, представленным на графике, был OBJ_CHART.

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


Видео 01 — Демонстрация возможностей этой версии

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


Новый индикатор

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

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. #property description "Base version for Chart Trade (DEMO version)"
04. #property version   "1.45"
05. #property icon "/Images/Market Replay/Icons/Indicators.ico"
06. #property link "https://www.mql5.com/es/articles/11701"
07. #property indicator_chart_window
08. #property indicator_plots 0
09. //+------------------------------------------------------------------+
10. #include <Market Replay\Chart Trader\C_ChartFloatingRAD.mqh>
11. //+------------------------------------------------------------------+
12. C_ChartFloatingRAD *chart = NULL;
13. //+------------------------------------------------------------------+
14. input int           user01 = 1;             //Leverage
15. input double        user02 = 100.1;         //Finance Take
16. input double        user03 = 75.4;          //Finance Stop
17. //+------------------------------------------------------------------+
18. #define macro_ERROR(A) if (_LastError != ERR_SUCCESS) { Print(__FILE__, " - [Error]: ", _LastError); if (A) ResetLastError(); }
19. //+------------------------------------------------------------------+
20. int OnInit()
21. {
22.     chart = new C_ChartFloatingRAD("Indicator Chart Trade", new C_Mouse("Indicator Mouse Study"), user01, user02, user03);
23.     
24.     macro_ERROR(false);
25.             
26.     return (_LastError == ERR_SUCCESS ? INIT_SUCCEEDED : INIT_FAILED);
27. }
28. //+------------------------------------------------------------------+
29. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
30. {
31.     return rates_total;
32. }
33. //+------------------------------------------------------------------+
34. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
35. {
36.     (*chart).DispatchMessage(id, lparam, dparam, sparam);
37. 
38.     macro_ERROR(true);
39.     
40.     ChartRedraw();
41. }
42. //+------------------------------------------------------------------+
43. void OnDeinit(const int reason)
44. {
45.     if (reason == REASON_CHARTCHANGE) (*chart).SaveState();
46. 
47.     delete chart;
48. }
49. //+------------------------------------------------------------------+

Исходный код индикатора Chart Trade

Как видим, код практически не претерпел серьезных изменений с момента его последней демонстрации. Но те изменения, которые имеют место быть, радикально меняют принцип работы индикатора.

Сначала был добавлен макрос. Он находится в строке 18 исходного кода. Этот макрос по сути стандартизирует сообщение об ошибке, отображаемое в терминале. Теперь обратите внимание, что он принимает параметр, цель которого — указать, будет ли макрос сбрасывать константу ошибки. Это можно увидеть в точках использования макроса. Первая точка находится в строке 24, сразу после попытки инициализировать индикатор. В этом случае мы не хотим и не нуждаемся в сбросе константы, поэтому аргумент имеет значение false. Вторая точка находится в строке 38. Здесь может оказаться, что возникшая ошибка в некоторой степени приемлема, поэтому аргумент имеет значение true, чтобы сбросить значение константы. Поэтому важно обращать внимание на сообщения, которые появляются в терминале, чтобы оставаться в курсе происходящего.

Есть еще один довольно интересный момент, который находится в строке 45. Это мера защиты, которая будет лучше понятна в контексте объяснения кода класса C_ChartFloatingRAD. Но в основном, причина заключается в необходимости как-то поддерживать работоспособность индикатора Chart Trade. Обратите внимание, что я использую именно вызов обновления графика. Это событие происходит всякий раз, когда мы меняем таймфрейм графика. Отмечу, что помимо всего прочего, нашей главной проблемой является смена таймфрейма.

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


Делаем класс C_ChartFloatingRAD почти полностью функциональным

Главное в этой статье — представление и объяснение класса C_ChartFloatingRAD. Как вы могли видеть на видео, которое я представил в начале статьи, у нас есть индикатор Chart Trade, который работает довольно интересным образом. Как вы могли заметить, у нас на графике все еще достаточно небольшое количество объектов, и тем не менее, мы получили ожидаемое функционирование. Значения, присутствующие в индикаторе, можно редактировать. Вопрос в том, как это возможно?

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

001. //+------------------------------------------------------------------+
002. #property copyright "Daniel Jose"
003. //+------------------------------------------------------------------+
004. #include "../Auxiliar/C_Mouse.mqh"
005. #include "../Auxiliar/Interprocess.mqh"
006. #include "C_AdjustTemplate.mqh"
007. //+------------------------------------------------------------------+
008. #define macro_NameGlobalVariable(A) StringFormat("ChartTrade_%u%s", GetInfoTerminal().ID, A)
009. //+------------------------------------------------------------------+
010. class C_ChartFloatingRAD : private C_Terminal
011. {
012.    private :
013.            enum eObjectsIDE {MSG_LEVERAGE_VALUE, MSG_TAKE_VALUE, MSG_STOP_VALUE, MSG_MAX_MIN, MSG_TITLE_IDE, MSG_DAY_TRADE, MSG_BUY_MARKET, MSG_SELL_MARKET, MSG_CLOSE_POSITION, MSG_NULL};
014.            struct st00
015.            {
016.                    int     x, y, minx, miny;
017.                    string  szObj_Chart,
018.                            szObj_Editable,
019.                            szFileNameTemplate;
020.                    long    WinHandle;
021.                    double  FinanceTake,
022.                            FinanceStop;
023.                    int     Leverage;
024.                    bool    IsDayTrade,
025.                            IsMaximized;
026.                    struct st01
027.                    {
028.                            int    x, y, w, h;
029.                            color  bgcolor;
030.                            int    FontSize;
031.                            string FontName;
032.                    }Regions[MSG_NULL];
033.            }m_Info;
034. //+------------------------------------------------------------------+
035.            C_Mouse *m_Mouse;
036. //+------------------------------------------------------------------+
037.            void CreateWindowRAD(int w, int h)
038.                    {
039.                            m_Info.szObj_Chart = "Chart Trade IDE";
040.                            m_Info.szObj_Editable = m_Info.szObj_Chart + " > Edit";
041.                            ObjectCreate(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJ_CHART, 0, 0, 0);
042.                            ObjectSetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_XDISTANCE, m_Info.x);
043.                            ObjectSetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_YDISTANCE, m_Info.y);
044.                            ObjectSetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_XSIZE, w);
045.                            ObjectSetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_YSIZE, h);
046.                            ObjectSetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_DATE_SCALE, false);
047.                            ObjectSetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_PRICE_SCALE, false);
048.                            m_Info.WinHandle = ObjectGetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_CHART_ID);
049.                    };
050. //+------------------------------------------------------------------+
051.            void AdjustEditabled(C_AdjustTemplate &Template, bool bArg)
052.                    {
053.                            for (eObjectsIDE c0 = 0; c0 <= MSG_STOP_VALUE; c0++)
054.                                    if (bArg)
055.                                    {
056.                                            Template.Add(EnumToString(c0), "bgcolor", NULL);
057.                                            Template.Add(EnumToString(c0), "fontsz", NULL);
058.                                            Template.Add(EnumToString(c0), "fontnm", NULL);
059.                                    }
060.                                    else
061.                                    {
062.                                            m_Info.Regions[c0].bgcolor = (color) StringToInteger(Template.Get(EnumToString(c0), "bgcolor"));
063.                                            m_Info.Regions[c0].FontSize = (int) StringToInteger(Template.Get(EnumToString(c0), "fontsz"));
064.                                            m_Info.Regions[c0].FontName = Template.Get(EnumToString(c0), "fontnm");
065.                                    }
066.                    }
067. //+------------------------------------------------------------------+
068. inline void AdjustTemplate(const bool bFirst = false)
069.                    {
070. #define macro_AddAdjust(A) {                     \
071.              (*Template).Add(A, "size_x", NULL); \
072.              (*Template).Add(A, "size_y", NULL); \
073.              (*Template).Add(A, "pos_x", NULL);  \
074.              (*Template).Add(A, "pos_y", NULL);  \
075.                            }
076. #define macro_GetAdjust(A) {                                                                                                                                                                                                                                               \
077.              m_Info.Regions[A].x = (int) StringToInteger((*Template).Get(EnumToString(A), "pos_x"));  \
078.              m_Info.Regions[A].y = (int) StringToInteger((*Template).Get(EnumToString(A), "pos_y"));  \
079.              m_Info.Regions[A].w = (int) StringToInteger((*Template).Get(EnumToString(A), "size_x")); \
080.              m_Info.Regions[A].h = (int) StringToInteger((*Template).Get(EnumToString(A), "size_y")); \
081.                            }
082. #define macro_PointsToFinance(A) A * (GetInfoTerminal().VolumeMinimal + (GetInfoTerminal().VolumeMinimal * (m_Info.Leverage - 1))) * GetInfoTerminal().AdjustToTrade
083.                            
084.                            C_AdjustTemplate *Template;
085.                            
086.                            if (bFirst)
087.                            {
088.                                    Template = new C_AdjustTemplate("Chart Trade/IDE_RAD.tpl", m_Info.szFileNameTemplate = StringFormat("Chart Trade/%u.tpl", GetInfoTerminal().ID));
089.                                    for (eObjectsIDE c0 = 0; c0 <= MSG_CLOSE_POSITION; c0++) macro_AddAdjust(EnumToString(c0));
090.                                    AdjustEditabled(Template, true);
091.                            }else Template = new C_AdjustTemplate(m_Info.szFileNameTemplate);
092.                            m_Info.Leverage = (m_Info.Leverage <= 0 ? 1 : m_Info.Leverage);
093.                            m_Info.FinanceTake = macro_PointsToFinance(FinanceToPoints(MathAbs(m_Info.FinanceTake), m_Info.Leverage));
094.                            m_Info.FinanceStop = macro_PointsToFinance(FinanceToPoints(MathAbs(m_Info.FinanceStop), m_Info.Leverage));
095.                            (*Template).Add("MSG_NAME_SYMBOL", "descr", GetInfoTerminal().szSymbol);
096.                            (*Template).Add("MSG_LEVERAGE_VALUE", "descr", (string)m_Info.Leverage);
097.                            (*Template).Add("MSG_TAKE_VALUE", "descr", (string)m_Info.FinanceTake);
098.                            (*Template).Add("MSG_STOP_VALUE", "descr", (string)m_Info.FinanceStop);
099.                            (*Template).Add("MSG_DAY_TRADE", "state", (m_Info.IsDayTrade ? "1" : "0"));
100.                            (*Template).Add("MSG_MAX_MIN", "state", (m_Info.IsMaximized ? "1" : "0"));
101.                            (*Template).Execute();
102.                            if (bFirst)
103.                            {
104.                                    for (eObjectsIDE c0 = 0; c0 <= MSG_CLOSE_POSITION; c0++) macro_GetAdjust(c0);
105.                                    m_Info.Regions[MSG_TITLE_IDE].w = m_Info.Regions[MSG_MAX_MIN].x;
106.                                    AdjustEditabled(Template, false);
107.                            };
108.                            ObjectSetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_YSIZE, (m_Info.IsMaximized ? 210 : m_Info.Regions[MSG_TITLE_IDE].h + 6));
109.                            ObjectSetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_XDISTANCE, (m_Info.IsMaximized ? m_Info.x : m_Info.minx));
110.                            ObjectSetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_YDISTANCE, (m_Info.IsMaximized ? m_Info.y : m_Info.miny));
111.                            
112.                            delete Template;
113.                            
114.                            ChartApplyTemplate(m_Info.WinHandle, "/Files/" + m_Info.szFileNameTemplate);
115.                            ChartRedraw(m_Info.WinHandle);
116. 
117. #undef macro_PointsToFinance
118. #undef macro_GetAdjust
119. #undef macro_AddAdjust
120.                    }
121. //+------------------------------------------------------------------+
122.            eObjectsIDE CheckMousePosition(const int x, const int y)
123.                    {
124.                            int xi, yi, xf, yf;
125.                            
126.                            for (eObjectsIDE c0 = 0; c0 <= MSG_CLOSE_POSITION; c0++)
127.                            {
128.                                    xi = (m_Info.IsMaximized ? m_Info.x : m_Info.minx) + m_Info.Regions[c0].x;
129.                                    yi = (m_Info.IsMaximized ? m_Info.y : m_Info.miny) + m_Info.Regions[c0].y;
130.                                    xf = xi + m_Info.Regions[c0].w;
131.                                    yf = yi + m_Info.Regions[c0].h;
132.                                    if ((x > xi) && (y > yi) && (x < xf) && (y < yf)) return c0;
133.                            }
134.                            return MSG_NULL;
135.                    }
136. //+------------------------------------------------------------------+
137.            template <typename T >
138.            void CreateObjectEditable(eObjectsIDE arg, T value)
139.                    {
140.                            long id = GetInfoTerminal().ID;
141.                            ObjectDelete(id, m_Info.szObj_Editable);
142.                            CreateObjectGraphics(m_Info.szObj_Editable, OBJ_EDIT, clrBlack, 0);
143.                            ObjectSetInteger(id, m_Info.szObj_Editable, OBJPROP_XDISTANCE, m_Info.Regions[arg].x + m_Info.x + 3);
144.                            ObjectSetInteger(id, m_Info.szObj_Editable, OBJPROP_YDISTANCE, m_Info.Regions[arg].y + m_Info.y + 3);
145.                            ObjectSetInteger(id, m_Info.szObj_Editable, OBJPROP_XSIZE, m_Info.Regions[arg].w);
146.                            ObjectSetInteger(id, m_Info.szObj_Editable, OBJPROP_YSIZE, m_Info.Regions[arg].h);
147.                            ObjectSetInteger(id, m_Info.szObj_Editable, OBJPROP_BGCOLOR, m_Info.Regions[arg].bgcolor);
148.                            ObjectSetInteger(id, m_Info.szObj_Editable, OBJPROP_ALIGN, ALIGN_CENTER);
149.                            ObjectSetInteger(id, m_Info.szObj_Editable, OBJPROP_FONTSIZE, m_Info.Regions[arg].FontSize - 1);
150.                            ObjectSetString(id, m_Info.szObj_Editable, OBJPROP_FONT, m_Info.Regions[arg].FontName);
151.                            ObjectSetString(id, m_Info.szObj_Editable, OBJPROP_TEXT, (string)value);
152.                            ChartRedraw();
153.                    }
154. //+------------------------------------------------------------------+
155.            bool RestoreState(void)
156.                    {
157.                            uCast_Double info;
158.                            bool bRet;
159.                            
160.                            if (bRet = GlobalVariableGet(macro_NameGlobalVariable("P"), info.dValue))
161.                            {
162.                                    m_Info.x = info._int[0];
163.                                    m_Info.y = info._int[1];
164.                            }
165.                            if (bRet = (bRet ? GlobalVariableGet(macro_NameGlobalVariable("M"), info.dValue) : bRet))
166.                            {
167.                                    m_Info.minx = info._int[0];
168.                                    m_Info.miny = info._int[1];
169.                            }
170.                            if (bRet = (bRet ? GlobalVariableGet(macro_NameGlobalVariable("B"), info.dValue) : bRet))
171.                            {
172.                                    m_Info.IsDayTrade = info._char[0];
173.                                    m_Info.IsMaximized = info._char[1];
174.                            }
175.                            if (bRet = (bRet ? GlobalVariableGet(macro_NameGlobalVariable("L"), info.dValue) : bRet))
176.                                    m_Info.Leverage = info._int[0];
177.                            bRet = (bRet ? GlobalVariableGet(macro_NameGlobalVariable("T"), m_Info.FinanceTake) : bRet);
178.                            bRet = (bRet ? GlobalVariableGet(macro_NameGlobalVariable("S"), m_Info.FinanceStop) : bRet);
179.                            
180.                            GlobalVariablesDeleteAll(macro_NameGlobalVariable(""));
181.                            
182.                            return bRet;
183.                    }
184. //+------------------------------------------------------------------+
185.    public  :
186. //+------------------------------------------------------------------+
187.            C_ChartFloatingRAD(string szShortName, C_Mouse *MousePtr, const int Leverage, const double FinanceTake, const double FinanceStop)
188.                    :C_Terminal()
189.                    {
190.                            if (!IndicatorCheckPass(szShortName)) SetUserError(C_Terminal::ERR_Unknown);
191.                            m_Mouse = MousePtr;
192.                            if (!RestoreState())
193.                            {
194.                                    m_Info.Leverage = Leverage;
195.                                    m_Info.FinanceTake = FinanceTake;
196.                                    m_Info.FinanceStop = FinanceStop;
197.                                    m_Info.IsDayTrade = true;
198.                                    m_Info.IsMaximized = true;
199.                                    m_Info.minx = m_Info.x = 115;
200.                                    m_Info.miny = m_Info.y = 64;
201.                            }
202.                            CreateWindowRAD(170, 210);
203.                            AdjustTemplate(true);
204.                    }
205. //+------------------------------------------------------------------+
206.            ~C_ChartFloatingRAD()
207.                    {
208.                            ObjectsDeleteAll(GetInfoTerminal().ID, m_Info.szObj_Chart);
209.                            FileDelete(m_Info.szFileNameTemplate);
210.                                                            
211.                            delete m_Mouse;
212.                    }
213. //+------------------------------------------------------------------+
214.            void SaveState(void)
215.                    {
216. #define macro_GlobalVariable(A, B) if (GlobalVariableTemp(A)) GlobalVariableSet(A, B);
217.                            
218.                            uCast_Double info;
219.                            
220.                            info._int[0] = m_Info.x;
221.                            info._int[1] = m_Info.y;
222.                            macro_GlobalVariable(macro_NameGlobalVariable("P"), info.dValue);
223.                            info._int[0] = m_Info.minx;
224.                            info._int[1] = m_Info.miny;
225.                            macro_GlobalVariable(macro_NameGlobalVariable("M"), info.dValue);
226.                            info._char[0] = m_Info.IsDayTrade;
227.                            info._char[1] = m_Info.IsMaximized;
228.                            macro_GlobalVariable(macro_NameGlobalVariable("B"), info.dValue);
229.                            info._int[0] = m_Info.Leverage;
230.                            macro_GlobalVariable(macro_NameGlobalVariable("L"), info.dValue);
231.                            macro_GlobalVariable(macro_NameGlobalVariable("T"), m_Info.FinanceTake);
232.                            macro_GlobalVariable(macro_NameGlobalVariable("S"), m_Info.FinanceStop);
233.                            
234. #undef macro_GlobalVariable
235.                    }
236. //+------------------------------------------------------------------+
237.            void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
238.                    {
239. #define macro_AdjustMinX(A, B) {                             \
240.              B = (A + m_Info.Regions[MSG_TITLE_IDE].w) > x;  \
241.              mx = x - m_Info.Regions[MSG_TITLE_IDE].w;       \
242.              A = (B ? (mx > 0 ? mx : 0) : A);                \
243.                                }
244. #define macro_AdjustMinY(A, B) {                             \
245.              B = (A + m_Info.Regions[MSG_TITLE_IDE].h) > y;  \
246.              my = y - m_Info.Regions[MSG_TITLE_IDE].h;       \
247.              A = (B ? (my > 0 ? my : 0) : A);                \
248.                                }
249.                                                                            
250.                            static int sx = -1, sy = -1;
251.                            int x, y, mx, my;
252.                            static eObjectsIDE obj = MSG_NULL;
253.                            double dvalue;
254.                            bool b1, b2, b3, b4;
255.    
256.                            switch (id)
257.                            {
258.                                    case CHARTEVENT_CHART_CHANGE:
259.                                            x = (int)ChartGetInteger(GetInfoTerminal().ID, CHART_WIDTH_IN_PIXELS);
260.                                            y = (int)ChartGetInteger(GetInfoTerminal().ID, CHART_HEIGHT_IN_PIXELS);
261.                                            macro_AdjustMinX(m_Info.x, b1);
262.                                            macro_AdjustMinY(m_Info.y, b2);
263.                                            macro_AdjustMinX(m_Info.minx, b3);
264.                                            macro_AdjustMinY(m_Info.miny, b4);
265.                                            if (b1 || b2 || b3 || b4) AdjustTemplate();
266.                                            break;
267.                                    case CHARTEVENT_MOUSE_MOVE:
268.                                            if ((*m_Mouse).CheckClick(C_Mouse::eClickLeft)) switch (CheckMousePosition(x = (int)lparam, y = (int)dparam))
269.                                            {
270.                                                    case MSG_TITLE_IDE:
271.                                                            if (sx < 0)
272.                                                            {
273.                                                                    ObjectDelete(GetInfoTerminal().ID, m_Info.szObj_Editable);
274.                                                                    ChartSetInteger(GetInfoTerminal().ID, CHART_MOUSE_SCROLL, false);
275.                                                                    sx = x - (m_Info.IsMaximized ? m_Info.x : m_Info.minx);
276.                                                                    sy = y - (m_Info.IsMaximized ? m_Info.y : m_Info.miny);
277.                                                            }
278.                                                            if ((mx = x - sx) > 0) ObjectSetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_XDISTANCE, mx);
279.                                                            if ((my = y - sy) > 0) ObjectSetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_YDISTANCE, my);
280.                                                            if (m_Info.IsMaximized)
281.                                                            {
282.                                                                    m_Info.x = (mx > 0 ? mx : m_Info.x);
283.                                                                    m_Info.y = (my > 0 ? my : m_Info.y);
284.                                                            }else
285.                                                            {
286.                                                                    m_Info.minx = (mx > 0 ? mx : m_Info.minx);
287.                                                                    m_Info.miny = (my > 0 ? my : m_Info.miny);
288.                                                            }
289.                                                            break;
290.                                            }else if (sx > 0)
291.                                            {
292.                                                    ChartSetInteger(GetInfoTerminal().ID, CHART_MOUSE_SCROLL, true);                                                
293.                                                    sx = sy = -1;
294.                                            }
295.                                            break;
296.                                    case CHARTEVENT_OBJECT_ENDEDIT:
297.                                            switch (obj)
298.                                            {
299.                                                    case MSG_LEVERAGE_VALUE:
300.                                                    case MSG_TAKE_VALUE:
301.                                                    case MSG_STOP_VALUE:
302.                                                            dvalue = StringToDouble(ObjectGetString(GetInfoTerminal().ID, m_Info.szObj_Editable, OBJPROP_TEXT));
303.                                                            if (obj == MSG_TAKE_VALUE)
304.                                                                    m_Info.FinanceTake = (dvalue <= 0 ? m_Info.FinanceTake : dvalue);
305.                                                            else if (obj == MSG_STOP_VALUE)
306.                                                                    m_Info.FinanceStop = (dvalue <= 0 ? m_Info.FinanceStop : dvalue);
307.                                                            else
308.                                                                    m_Info.Leverage = (dvalue <= 0 ? m_Info.Leverage : (int)MathFloor(dvalue));
309.                                                            AdjustTemplate();
310.                                                            obj = MSG_NULL;
311.                                                            ObjectDelete(GetInfoTerminal().ID, m_Info.szObj_Editable);
312.                                                            break;
313.                                            }
314.                                            break;
315.                                    case CHARTEVENT_OBJECT_CLICK:
316.                                            if (sparam == m_Info.szObj_Chart) switch (obj = CheckMousePosition(x = (int)lparam, y = (int)dparam))
317.                                            {
318.                                                    case MSG_DAY_TRADE:
319.                                                            m_Info.IsDayTrade = (m_Info.IsDayTrade ? false : true);
320.                                                            ObjectDelete(GetInfoTerminal().ID, m_Info.szObj_Editable);
321.                                                            break;
322.                                                    case MSG_MAX_MIN:
323.                                                            m_Info.IsMaximized = (m_Info.IsMaximized ? false : true);
324.                                                            ObjectDelete(GetInfoTerminal().ID, m_Info.szObj_Editable);
325.                                                            break;
326.                                                    case MSG_LEVERAGE_VALUE:
327.                                                            CreateObjectEditable(obj, m_Info.Leverage);
328.                                                            break;
329.                                                    case MSG_TAKE_VALUE:
330.                                                            CreateObjectEditable(obj, m_Info.FinanceTake);
331.                                                            break;
332.                                                    case MSG_STOP_VALUE:
333.                                                            CreateObjectEditable(obj, m_Info.FinanceStop);
334.                                                            break;
335.                                                    case MSG_BUY_MARKET:
336.                                                            ObjectDelete(GetInfoTerminal().ID, m_Info.szObj_Editable);
337.                                                            break;
338.                                                    case MSG_SELL_MARKET:
339.                                                            ObjectDelete(GetInfoTerminal().ID, m_Info.szObj_Editable);
340.                                                            break;
341.                                                    case MSG_CLOSE_POSITION:
342.                                                            ObjectDelete(GetInfoTerminal().ID, m_Info.szObj_Editable);
343.                                                            break;
344.                                            }
345.                                            if (obj != MSG_NULL) AdjustTemplate();
346.                                            break;
347.                            }
348.                    }
349. //+------------------------------------------------------------------+
350. };
351. //+------------------------------------------------------------------+
352. #undef macro_NameGlobalVariable
353. //+------------------------------------------------------------------+
354. 

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

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

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

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

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

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

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

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

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

После этого, между строками 92 и 94, мы настраиваем и корректируем значения, которые будут отображаться на индикаторе Chart Trade. Ранее это делалось в конструкторе класса, но никакого взаимодействия с пользователем не было. Однако теперь у нас есть это взаимодействие, и поэтому мы должны гарантировать, что значения будут репрезентативны. Таким образом, мы настраиваем значения здесь, в момент обновления шаблона.

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

Остальные строки уже были объяснены в предыдущей статье Разработка системы репликации (Часть 44): Проект Chart Trade (III), а вот в строке 105 мы делаем нечто, чего раньше здесь не было. В этой строке мы исправляем небольшой недочет, из-за которого можно было перемещать плавающее окно, кликая и перетаскивая кнопку развертывания и сворачивания. Эта строка его устраняет, а сразу после этого, в строке 106, мы получаем из шаблона нужные значения. Обратите внимание, что теперь мы сообщаем о вызове как о ложном. Таким образом значения будут сохранены в правильных местах для дальнейшего использования.

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

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

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

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

Теперь у нас есть кое-что немного другое. В строке 137 можно увидеть довольно необычный тип кода. В большинстве случаев. Этот тип кода довольно обычен, когда у нас есть одинаковые процедуры, но для разных типов. И что это значит? 🤔 Ну, сначала нужно понять следующее: зачем нужно создавать одну подпрограмму для отображения значения типа double, другую для значений int и третью для строковых переменных (strings)? Разве не было бы гораздо проще создать одну подпрограмму, поскольку, по сути, представленный в них код, всегда был бы одинаков? Единственное отличие заключалось бы в том, что в одном случае значение было бы одного типа, а в другом — другого, верно? Вот именно это и делает строка 137.

Но погодите, если идея состоит в том, чтобы представить значение, не могли бы мы просто передать его напрямую, как строку (string)? Да, могли бы, но есть кое-что, что мы не учли. А что, если нам понадобится, чтобы значение было представлено определенным образом и вызывалось из разных точек кода? Подумайте о работе, которую пришлось бы проделать. Но таким образом мы можем просто передать значение в его исходном типе, позволить компилятору создать для нас подпрограмму и представить его так, как мы хотим. Если мы изменим представление, все, что нам нужно будет сделать, это изменить всего один пункт в коде. Компилятор позаботится о том, чтобы все настроить за нас. 

Меньше работы, больше продуктивности.

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

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

Теперь обратите внимание на следующий факт: здесь, в этой процедуре, мы создаем дополнительный объект, OBJ_EDIT. Зачем мы это делаем? Для того, чтобы облегчить использование индикатора Chart Trade. На самом деле создание такого объекта не является необходимостью. Но это затруднит использование индикатора. Дело не в том, что сложно запрограммировать необходимую логику, а в том, что у пользователя возникнут трудности с работой и использованием индикатора. По этой причине мы обращаемся за помощью к MetaTrader 5 и просим его создать объект редактирования.

Однако нам нужно, чтобы этот объект находился в нужном месте, с правильным форматом и стилем. Делается это следующим образом: при вызове этой процедуры, мы удаляем созданный объект редактирования, если он существует. Это делается в строке 141. Но есть нюанс  этот объект будет существовать только тогда, когда это необходимо. Итак, между строками 142 и 150 мы будем использовать значения, которые определены в шаблоне. Так созданный объект будет таким же, как тот, который используется в шаблоне.

Есть деталь, которая присутствует в строках 143 и 144. Это небольшая корректировка, где мы добавляем 3 к размерам. Это значение не случайно, поскольку OBJ_CHART использует 3 пикселя по краям, и объект OBJ_EDIT должен быть смещен именно на эти 3 пикселя. Таким образом он будет находиться точно там, где на графике расположен шаблон.

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

Каждая из строк, включая: 160, 165, 170, 175, 177 и 178, будет восстанавливать данные, присутствующие в глобальных переменных терминала. Такие переменные имеют тип double, но мы можем хранить в них разные значения. Я уже неоднократно объяснял, как это делается. Но здесь мы делаем это очень специфическим образом. Итак, если в любой из этих указанных строк глобальная переменная терминала не может быть доступна или прочитана, мы вернем вызывающей стороне ложное значение. И единственная точка, в которой действительно будет вызываться эта функция, — это конструктор, и мы скоро к этому вернемся.

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

Независимо от того, было ли чтение успешным или нет, в строке 180 мы удалим все глобальные переменные терминала, которые привязаны к индикатору Chart Trade. Однако обратите внимание, что в MetaTrader 5 может присутствовать большее количество таких переменных. Чтобы узнать, какие из них следует удалить, мы используем макрос, определенный в начале кода класса.

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

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

Хорошо, теперь давайте посмотрим, что происходит в процедуре, представленной в строке 214. На этом этапе мы временно сохраняем состояние индикатора Chart Trade. Но почему мы это делаем? И для чего сохраняем это состояние в глобальных переменных терминала? Нельзя ли это сделать это как-то иначе? Давайте разберемся поэтапно.

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

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

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

43. void OnDeinit(const int reason)
44. {
45.     if (reason != REASON_CHARTCHANGE) (*chart).RemoveTemplate();
46. 
47.     delete chart;
48. }

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

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

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

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

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

Существует другой способ сохранения данных в «памяти», но я не буду сейчас вдаваться в подробности, поскольку он предполагает работу с графическими объектами. Об этом мы поговорим в другой раз.

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

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

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

Одним из вопросов, заслуживающих внимания здесь, в обработчике сообщений, является событие CHARTEVENT_CHART_CHANGE, присутствующее в строке 258. Есть одна деталь, когда терминал претерпевает некоторые изменения в своих размерах. Когда это происходит, MetaTrader 5 инициирует событие, информируя об этом наши программы. Это событие обрабатывается через CHARTEVENT_CHART_CHANGE, чтобы мы могли проверить, остается ли плавающее окно видимым на графике. Если не обработать это событие, может случиться так, что окно останется скрытым, но индикатор продолжит быть активным. Из-за того, что эта обработка одинакова как в случае свернутого окна, так и для развернутого режима, я использую макрос для выполнения необходимых настроек. Таким образом, если произойдет какое-либо изменение, требующее соответствующего изменения положения окна, строка 265 сделает это за нас.

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


Заключение

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

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

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


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

Прикрепленные файлы |
Indicators.zip (149.46 KB)
Особенности написания Пользовательских Индикаторов Особенности написания Пользовательских Индикаторов
Написание пользовательских индикаторов в торговой системе MetaTrader 4
Разработка MQTT-клиента для MetaTrader 5: методология TDD (финал) Разработка MQTT-клиента для MetaTrader 5: методология TDD (финал)
Статья является последней частью серии, описывающей этапы разработки нативного MQL5-клиента для протокола MQTT 5.0. Хотя библиотека еще не готова к использованию, в этой части мы будем использовать наш клиент для обновления пользовательского символа с помощью тиков (или цен), полученных от другого брокера. В конце статьи вы найдете дополнительную информацию о текущем состоянии библиотеки и узнаете о том, чего не хватает для ее полного соответствия протоколу MQTT 5.0, о возможном плане действий и о том, как следить за развитием библиотеки и вносить в нее свой вклад.
Особенности написания экспертов Особенности написания экспертов
Написание и тестирование экспертов в торговой системе MetaTrader 4.
Введение в MQL5 (Часть 6): Функции для работы с массивами для начинающих (II) Введение в MQL5 (Часть 6): Функции для работы с массивами для начинающих (II)
Продолжим изучение возможностей языка программирования MQL5. В этой статье, предназначенной для начинающих, мы продолжим изучать функции для работы массивами, перейдя к более сложным концепциям, которые обязательно пригодятся при разработке эффективных торговых стратегий. В этот раз познакомимся с функциями ArrayPrint, ArrayInsert, ArraySize, ArrayRange, ArrarRemove, ArraySwap, ArrayReverse и ArraySort. Функции массивы знать обязательно, если вы хотите достичь высокого уровня в области алготрейдинга. Это очередная глава на пути к мастерству.