
Разработка системы репликации (Часть 75): Новый Chart Trade (II)
Введение
В предыдущей статье, "Разработка системы репликации (Часть 74): Новый Chart Trade (I)", в котором мы в общих чертах рассказали о коде индикатора Chart Trade. Мы рассмотрели причины, по которым следует использовать тот или иной метод для программирования Chart Trade. Были подробно описаны некоторые аспекты кода, но основной код еще предстоит обсудить и объяснить.
Одна из причин, по которой не был показан основной код, заключается в том, что более 100 строк кода были удалены. Поэтому я придумал простой способ показать, что в нем произошло. В сегодняшней статьи я представлю лучший способ, который я нашел.
Далее мы рассмотрим основной код Chart Trade Однако, стоит отметить, что при желании можно интегрировать этот код непосредственно в советник. Очевидно, с небольшими изменениями, о которых расскажем в этой статье. Но не забывайте о причинах, которые объяснили в предыдущей статье, для включения кода в индикатор, а не в советник. Однако вы можете использовать код по своему усмотрению.
Итак, давайте приступим к делу.
Мы уже понимаем исходный код класса C_ChartFloatingRAD
Код класса C_ChartFloatingRAD содержится в одноименном заголовочном файле. Ниже можно увидеть его почти целиком: Единственная недостающая часть - это процедура DispatchMessage.
001. //+------------------------------------------------------------------+ 002. #property copyright "Daniel Jose" 003. //+------------------------------------------------------------------+ 004. #include "../Auxiliar/C_Mouse.mqh" 005. #include "C_AdjustTemplate.mqh" 006. //+------------------------------------------------------------------+ 007. #define macro_NameGlobalVariable(A) StringFormat("ChartTrade_%u%s", GetInfoTerminal().ID, A) 008. #define macro_CloseIndicator(A) { \ 009. OnDeinit(REASON_INITFAILED); \ 010. SetUserError(A); \ 011. return; \ 012. } 013. //+------------------------------------------------------------------+ 014. class C_ChartFloatingRAD : private C_Terminal 015. { 016. private : 017. 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}; 018. struct st00 019. { 020. short x, y, minx, miny, 021. Leverage; 022. string szObj_Chart, 023. szObj_Editable, 024. szFileNameTemplate; 025. long WinHandle; 026. double FinanceTake, 027. FinanceStop; 028. bool IsMaximized, 029. IsDayTrade, 030. IsSaveState; 031. struct st01 032. { 033. short x, y, w, h; 034. color bgcolor; 035. int FontSize; 036. string FontName; 037. }Regions[MSG_NULL]; 038. }m_Info; 039. C_Mouse *m_Mouse; 040. //+------------------------------------------------------------------+ 041. void CreateWindowRAD(int w, int h) 042. { 043. m_Info.szObj_Chart = "Chart Trade IDE"; 044. m_Info.szObj_Editable = m_Info.szObj_Chart + " > Edit"; 045. ObjectCreate(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJ_CHART, 0, 0, 0); 046. ObjectSetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_XDISTANCE, m_Info.x); 047. ObjectSetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_YDISTANCE, m_Info.y); 048. ObjectSetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_XSIZE, w); 049. ObjectSetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_YSIZE, h); 050. ObjectSetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_DATE_SCALE, false); 051. ObjectSetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_PRICE_SCALE, false); 052. m_Info.WinHandle = ObjectGetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_CHART_ID); 053. }; 054. //+------------------------------------------------------------------+ 055. void AdjustEditabled(C_AdjustTemplate &Template, bool bArg) 056. { 057. for (eObjectsIDE c0 = 0; c0 <= MSG_STOP_VALUE; c0++) 058. if (bArg) 059. { 060. Template.Add(EnumToString(c0), "bgcolor", NULL); 061. Template.Add(EnumToString(c0), "fontsz", NULL); 062. Template.Add(EnumToString(c0), "fontnm", NULL); 063. } 064. else 065. { 066. m_Info.Regions[c0].bgcolor = (color) StringToInteger(Template.Get(EnumToString(c0), "bgcolor")); 067. m_Info.Regions[c0].FontSize = (int) StringToInteger(Template.Get(EnumToString(c0), "fontsz")); 068. m_Info.Regions[c0].FontName = Template.Get(EnumToString(c0), "fontnm"); 069. } 070. } 071. //+------------------------------------------------------------------+ 072. inline void AdjustTemplate(const bool bFirst = false) 073. { 074. #define macro_PointsToFinance(A) A * (GetInfoTerminal().VolumeMinimal + (GetInfoTerminal().VolumeMinimal * (m_Info.Leverage - 1))) * GetInfoTerminal().AdjustToTrade 075. 076. C_AdjustTemplate *Template; 077. 078. if (bFirst) 079. { 080. Template = new C_AdjustTemplate(m_Info.szFileNameTemplate = IntegerToString(GetInfoTerminal().ID) + ".tpl", true); 081. for (eObjectsIDE c0 = 0; c0 <= MSG_CLOSE_POSITION; c0++) 082. { 083. (*Template).Add(EnumToString(c0), "size_x", NULL); 084. (*Template).Add(EnumToString(c0), "size_y", NULL); 085. (*Template).Add(EnumToString(c0), "pos_x", NULL); 086. (*Template).Add(EnumToString(c0), "pos_y", NULL); 087. } 088. AdjustEditabled(Template, true); 089. }else Template = new C_AdjustTemplate(m_Info.szFileNameTemplate); 090. if (_LastError >= ERR_USER_ERROR_FIRST) 091. { 092. delete Template; 093. 094. return; 095. } 096. m_Info.Leverage = (m_Info.Leverage <= 0 ? 1 : m_Info.Leverage); 097. m_Info.FinanceTake = macro_PointsToFinance(FinanceToPoints(MathAbs(m_Info.FinanceTake), m_Info.Leverage)); 098. m_Info.FinanceStop = macro_PointsToFinance(FinanceToPoints(MathAbs(m_Info.FinanceStop), m_Info.Leverage)); 099. (*Template).Add("MSG_NAME_SYMBOL", "descr", GetInfoTerminal().szSymbol); 100. (*Template).Add("MSG_LEVERAGE_VALUE", "descr", IntegerToString(m_Info.Leverage)); 101. (*Template).Add("MSG_TAKE_VALUE", "descr", DoubleToString(m_Info.FinanceTake, 2)); 102. (*Template).Add("MSG_STOP_VALUE", "descr", DoubleToString(m_Info.FinanceStop, 2)); 103. (*Template).Add("MSG_DAY_TRADE", "state", (m_Info.IsDayTrade ? "1" : "0")); 104. (*Template).Add("MSG_MAX_MIN", "state", (m_Info.IsMaximized ? "1" : "0")); 105. if (!(*Template).Execute()) 106. { 107. delete Template; 108. 109. macro_CloseIndicator(C_Terminal::ERR_FileAcess); 110. }; 111. if (bFirst) 112. { 113. for (eObjectsIDE c0 = 0; c0 <= MSG_CLOSE_POSITION; c0++) 114. { 115. m_Info.Regions[c0].x = (short) StringToInteger((*Template).Get(EnumToString(c0), "pos_x")); 116. m_Info.Regions[c0].y = (short) StringToInteger((*Template).Get(EnumToString(c0), "pos_y")); 117. m_Info.Regions[c0].w = (short) StringToInteger((*Template).Get(EnumToString(c0), "size_x")); 118. m_Info.Regions[c0].h = (short) StringToInteger((*Template).Get(EnumToString(c0), "size_y")); 119. } 120. m_Info.Regions[MSG_TITLE_IDE].w = m_Info.Regions[MSG_MAX_MIN].x; 121. AdjustEditabled(Template, false); 122. }; 123. ObjectSetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_YSIZE, (m_Info.IsMaximized ? 210 : m_Info.Regions[MSG_TITLE_IDE].h + 6)); 124. ObjectSetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_XDISTANCE, (m_Info.IsMaximized ? m_Info.x : m_Info.minx)); 125. ObjectSetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_YDISTANCE, (m_Info.IsMaximized ? m_Info.y : m_Info.miny)); 126. 127. delete Template; 128. 129. ChartApplyTemplate(m_Info.WinHandle, "/Files/" + m_Info.szFileNameTemplate); 130. ChartRedraw(m_Info.WinHandle); 131. 132. #undef macro_PointsToFinance 133. } 134. //+------------------------------------------------------------------+ 135. eObjectsIDE CheckMousePosition(const short x, const short y) 136. { 137. int xi, yi, xf, yf; 138. 139. for (eObjectsIDE c0 = 0; c0 <= MSG_CLOSE_POSITION; c0++) 140. { 141. xi = (m_Info.IsMaximized ? m_Info.x : m_Info.minx) + m_Info.Regions[c0].x; 142. yi = (m_Info.IsMaximized ? m_Info.y : m_Info.miny) + m_Info.Regions[c0].y; 143. xf = xi + m_Info.Regions[c0].w; 144. yf = yi + m_Info.Regions[c0].h; 145. if ((x > xi) && (y > yi) && (x < xf) && (y < yf)) return c0; 146. } 147. return MSG_NULL; 148. } 149. //+------------------------------------------------------------------+ 150. inline void DeleteObjectEdit(void) 151. { 152. ChartRedraw(); 153. ObjectsDeleteAll(GetInfoTerminal().ID, m_Info.szObj_Editable); 154. } 155. //+------------------------------------------------------------------+ 156. template <typename T > 157. void CreateObjectEditable(eObjectsIDE arg, T value) 158. { 159. long id = GetInfoTerminal().ID; 160. 161. DeleteObjectEdit(); 162. CreateObjectGraphics(m_Info.szObj_Editable, OBJ_EDIT, clrBlack, 0); 163. ObjectSetInteger(id, m_Info.szObj_Editable, OBJPROP_XDISTANCE, m_Info.Regions[arg].x + m_Info.x + 3); 164. ObjectSetInteger(id, m_Info.szObj_Editable, OBJPROP_YDISTANCE, m_Info.Regions[arg].y + m_Info.y + 3); 165. ObjectSetInteger(id, m_Info.szObj_Editable, OBJPROP_XSIZE, m_Info.Regions[arg].w); 166. ObjectSetInteger(id, m_Info.szObj_Editable, OBJPROP_YSIZE, m_Info.Regions[arg].h); 167. ObjectSetInteger(id, m_Info.szObj_Editable, OBJPROP_BGCOLOR, m_Info.Regions[arg].bgcolor); 168. ObjectSetInteger(id, m_Info.szObj_Editable, OBJPROP_ALIGN, ALIGN_CENTER); 169. ObjectSetInteger(id, m_Info.szObj_Editable, OBJPROP_FONTSIZE, m_Info.Regions[arg].FontSize - 1); 170. ObjectSetString(id, m_Info.szObj_Editable, OBJPROP_FONT, m_Info.Regions[arg].FontName); 171. ObjectSetString(id, m_Info.szObj_Editable, OBJPROP_TEXT, (typename(T) == "double" ? DoubleToString(value, 2) : (string) value)); 172. ChartRedraw(); 173. } 174. //+------------------------------------------------------------------+ 175. bool RestoreState(void) 176. { 177. uCast_Double info; 178. bool bRet; 179. C_AdjustTemplate *Template; 180. 181. if (bRet = GlobalVariableGet(macro_NameGlobalVariable("POST"), info.dValue)) 182. { 183. m_Info.x = (short) info._16b[0]; 184. m_Info.y = (short) info._16b[1]; 185. m_Info.minx = (short) info._16b[2]; 186. m_Info.miny = (short) info._16b[3]; 187. Template = new C_AdjustTemplate(m_Info.szFileNameTemplate = IntegerToString(GetInfoTerminal().ID) + ".tpl"); 188. if (_LastError >= ERR_USER_ERROR_FIRST) bRet = false; else 189. { 190. (*Template).Add("MSG_LEVERAGE_VALUE", "descr", NULL); 191. (*Template).Add("MSG_TAKE_VALUE", "descr", NULL); 192. (*Template).Add("MSG_STOP_VALUE", "descr", NULL); 193. (*Template).Add("MSG_DAY_TRADE", "state", NULL); 194. (*Template).Add("MSG_MAX_MIN", "state", NULL); 195. if (!(*Template).Execute()) bRet = false; else 196. { 197. m_Info.IsDayTrade = (bool) StringToInteger((*Template).Get("MSG_DAY_TRADE", "state")) == 1; 198. m_Info.IsMaximized = (bool) StringToInteger((*Template).Get("MSG_MAX_MIN", "state")) == 1; 199. m_Info.Leverage = (short)StringToInteger((*Template).Get("MSG_LEVERAGE_VALUE", "descr")); 200. m_Info.FinanceTake = (double) StringToDouble((*Template).Get("MSG_TAKE_VALUE", "descr")); 201. m_Info.FinanceStop = (double) StringToDouble((*Template).Get("MSG_STOP_VALUE", "descr")); 202. } 203. }; 204. delete Template; 205. }; 206. 207. GlobalVariablesDeleteAll(macro_NameGlobalVariable("")); 208. 209. return bRet; 210. } 211. //+------------------------------------------------------------------+ 212. public : 213. //+------------------------------------------------------------------+ 214. C_ChartFloatingRAD(string szShortName, C_Mouse *MousePtr, const short Leverage, const double FinanceTake, const double FinanceStop) 215. :C_Terminal(0) 216. { 217. m_Mouse = MousePtr; 218. m_Info.IsSaveState = false; 219. if (!IndicatorCheckPass(szShortName)) return; 220. if (!RestoreState()) 221. { 222. m_Info.Leverage = Leverage; 223. m_Info.IsDayTrade = true; 224. m_Info.FinanceTake = FinanceTake; 225. m_Info.FinanceStop = FinanceStop; 226. m_Info.IsMaximized = true; 227. m_Info.minx = m_Info.x = 115; 228. m_Info.miny = m_Info.y = 64; 229. } 230. CreateWindowRAD(170, 210); 231. AdjustTemplate(true); 232. } 233. //+------------------------------------------------------------------+ 234. ~C_ChartFloatingRAD() 235. { 236. ChartRedraw(); 237. ObjectsDeleteAll(GetInfoTerminal().ID, m_Info.szObj_Chart); 238. if (!m_Info.IsSaveState) 239. FileDelete(m_Info.szFileNameTemplate); 240. 241. delete m_Mouse; 242. } 243. //+------------------------------------------------------------------+ 244. void SaveState(void) 245. { 246. #define macro_GlobalVariable(A, B) if (GlobalVariableTemp(A)) GlobalVariableSet(A, B); 247. 248. uCast_Double info; 249. 250. info._16b[0] = m_Info.x; 251. info._16b[1] = m_Info.y; 252. info._16b[2] = m_Info.minx; 253. info._16b[3] = m_Info.miny; 254. macro_GlobalVariable(macro_NameGlobalVariable("POST"), info.dValue); 255. m_Info.IsSaveState = true; 256. 257. #undef macro_GlobalVariable 258. } 259. //+------------------------------------------------------------------+ 260. void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam) 261. { .... The internal code of this procedure will be seen in the next article 386. } 387. //+------------------------------------------------------------------+ 388. }; 389. //+------------------------------------------------------------------+ 390. #undef macro_NameGlobalVariable 391. #undef macro_CloseIndicator 392. //+------------------------------------------------------------------+
Исходный код файла C_ChartFloatingRAD.mqh
Данный исходный код больше не содержит строк, которые были удалены из исходного кода. Он также включает в себя необходимые изменения, чтобы всё работало как надо. Поскольку я предполагаю, что многие из вас не видели исходный код, я хочу, чтобы вы обратили внимание на пояснения по поводу его работы, но прежде всего на то, как ему удается заставить Chart Trade создаваться и работать. Потому что, если мы будем наблюдать и искать, мы не найдем элементов, которые мы видим в Chart Trade. Как же это может быть? Как возможно, что код, который практически не содержит графических элементов, может отображать графические элементы? Что за колдовство здесь происходит?
Что ж, это не магия: это программирование и использование множества различных техник, которые, в конечном итоге обеспечивают правильное представление Chart Trade. Самое главное - они делают его функциональным, безопасным и стабильным.
В исходной версии некоторые элементы были общедоступными, например, структуры и перечисления, показанные в строке 17. Однако мы уже приняли меры по предотвращению утечки переменных из кода. Также внедрили и приняли инкапсуляцию. Так почему же сейчас у нас нет таких же публичных элементов? Причина проста: наша идея состоит в том, чтобы генерировать протокол и использовать его, не требуя, чтобы класс C_ChartFloatingRAD декодировал данные. Об этом мы поговорим позже в наше статье.
Всё, что нам нужно в этом коде с точки зрения переменных, объявлено в структуре, начиная со строки 18. Теперь обратите внимание на используемые типы. Большинство из них не 32-битные или, говоря точнее, из группы INT. Почему? Даже переменные, которые явно будут использоваться в качестве экранных координат, являются 16-битными переменными, то есть из группы short.
Вопреки привычке некоторых программистов, когда устанавливают эти переменные как стандартный тип INT, я не вижу в этом необходимости. В случае с разрешением экрана значение в 16 бит более чем достаточно для современных технологий. Максимальное значение 65535 вполне хватает для работы с графикой на экране с разрешением 4K. На самом деле, такое значение легко справится с координатами на экране 8K или даже больше. Поэтому я не вижу причин использовать здесь тип INT. Кроме того, использование типа SHORT позволяет уплотнять данные. \ниже показано, как это будет выглядеть:
Разъяснив этот небольшой момент, можно увидеть, что в строке 31 есть еще одна внутренняя структура по отношению к первой. Данная структура содержит относительное положение графических элементов. Эти элементы являются неотъемлемой частью Chart Trade и находятся в непосредственном взаимодействии с пользователем.
В начале кода есть два макроса. Пока что не надо беспокоиться о них, просто нужно знать, что они есть. Еще важнее - знать, где они объявлены, поскольку они будут использоваться во всем коде. Возможно, сейчас вас больше всего волнует макрос, определенный в строке 8, но скоро мы поймем, что он на самом деле делает.
В любом случае, всё сводится к этому. В строке 14 мы частным образом наследуем класс C_Terminal, чтобы расширить его функциональность и создать Chart Trade. Между строками 20 и 39 находятся объявления внутренних переменных класса, то есть приватных. Теперь можно перейти к части, в которой описывается код. Для большей простоты мы будем анализировать функцию за функцией по мере их появления в коде. Начнем с процедуры CreateWindowRAD, которая объявлена в строке 41.
Данная процедура вызывается только из строки 230 и не вызывается ни из какого другого места. Его цель очень проста: она создает на графике объект OBJ_CHAT. Вот и всё. Но почему на график добавляется объект типа OBJ_CHAT? Причина в том, что при этом не нужно будет создавать все объекты. Данный подход может показаться непонятным тем, кто привык рисовать график кодом, но я уже объяснял его в других статьях. В конце этой статьи мы приведем несколько своих предыдущих публикаций, чтобы вы могли ознакомиться с ними поближе. В них объясняется, как и почему это нужно делать. Если вы не разобрались до конца в теме, изложенной в этой статье, можете опираться на эти ссылки.
Действительно важная для нашей работы часть находится в строке 52. В данной строке мы храним значение идентификатора, который MetaTrader 5 предоставляет при создании объекта OBJ_CHART. Очень важно сохранить эту информацию, так как она пригодится нам в дальнейшем. Теперь мы можем перейти к следующей процедуре в списке. Это AdjustEditabled, который начинается в строке 55. Что делает данная процедура? Как ни странно, эта процедура отвечает за настройку и захват состояния объектов, которые будут отображаться в OBJ_CHART. Но как это работает? Чтобы всё стало полностью понятно, необходимо подробно разобраться в работе процедуры AdjustTemplate, поскольку именно в ней используется AdjustEditabled. Давайте посмотрим, что происходит между строками 72 и 133 - это одна из самых сложных процедур в данном классе, поскольку именно здесь происходит настоящая "магия".
В строке 74 мы определяем макрос, который будет использоваться только в данной процедуре. Обратите внимание, что в конце процедуры, в строке 132, этот же макрос удаляется, чтобы мы не могли его больше использовать. Затем, в строке 78, мы запускаем тест, чтобы определить, что будет сделано на самом деле. Если идея состоит просто в том, чтобы создать Chart Trade, данный тест будет провален, и нам придется выполнить целую серию задач. Если Chart Trade уже существует, тогда выполнится строка 89. На данном этапе вам нужно обратиться к предыдущей статье, чтобы понять, какие изменения будут внесены в шаблон, так как строки 80 и 89 в любом случае начнут построение шаблона. Однако то, что будет сделано на самом деле, зависит от результатов теста в строке 78.
Теперь мы подошли к первому сложному моменту. Если мы создаем шаблон, то цикл в строке 81 выполнит подготовку, чтобы определить, где находятся объекты, и эта же техника будет использована позже. Но что это за объекты? Это объекты, которые добавляются в Chart Trade. Как мы видим, мы не знаем, где именно они находятся, но нам известно их количество благодаря перечислению, определенному в строке 17. В строке 88 мы вызываем процедуру AdjustEditabled, чтобы сделать тест в строке 58 равным true. Это позволит нам добавить больше элементов, которые мы получим из шаблона.
Если класс C_AdjustTemplate работает успешно, строка 90 сделает возможным продолжение кода. Если нет, то мы вернем всё в код, который осуществил вызов, вы можете увидеть это в предыдущей статье. Но не волнуйтесь, мы пока ничего не сделали. До строки 90 мы только просили класс C_AdjustTemplate открыть для нас файл шаблона, и не более того.
Однако между строками 96 и 98 мы исправим некоторые данные для отображения. Сразу после этого, между строками 99 и 104, мы добавим некоторые элементы, которые будут скорректированы в вызове в строке 105. Именно в этот момент и происходит волшебство. Чтобы понять это, давайте вернемся к предыдущей статье и рассмотрим функцию Execute класса C_AdjustTemplate. На данном этапе мы фактически изменяем значения, которые изначально были в файле шаблона. Однако, мы не изменяем значения напрямую, а обращаемся к копии данного файла. Именно поэтому мы провели тест на строке 78. Если бы не этот тест, мы бы не знали, какой файл нужно изменить в строке 105.
Не знаю, получилось ли у вас понять, что происходит. Попытаюсь объяснить: всё, что было сделано до сих пор, между строками 78 и 104, генерирует данные для класса C_AdjustTemplate для модификации файла, содержащего Chart Trade. Любая информация, которая видна в Chart Trade, помещается туда благодаря строкам с 99 по 104. Это кажется странным, но это рабочий вариант.
Советую изучить эту часть кода, чтобы понять, как всё работает на самом деле, ведь все объекты создаются и управляются одинаково.
Хорошо, будем считать, что вы поняли, что происходит до строки 105. В случае успеха код продолжит своё выполнение. Если это не удастся, произойдет нечто странное. Поэтому обратите внимание на то, что мы сейчас расскажем, потому что это очень важно.
Если вызов в строке 105 завершится неудачей, мы должны удалить шаблон и завершить работу Chart Trade. Удалить шаблон очень просто; для этого мы используем строку 107. Здесь ничего сверхъестественного нет. Однако это не приведет к удалению индикатора с графика. Для этого мы должны вызвать процедуру OnDeInit. Теперь наступает самый сложный момент, где вам нужно быть внимательным. Давайте посмотрим на фрагмент кода OnDeInit, расположенный ниже:
41. void OnDeinit(const int reason) 42. { 43. switch (reason) 44. { 45. case REASON_INITFAILED: 46. ChartIndicatorDelete(ChartID(), 0, def_ShortName); 47. break; 48. case REASON_CHARTCHANGE: 49. (*chart).SaveState(); 50. break; 51. } 52. 53. delete chart; 54. }
Фрагмент кода индикатора Chart Trade
Данный отрывок приведен здесь только для удобства объяснения. Давайте посмотрим: при выполнении строки 109 вводится код макроса, который можно увидеть в начале кода в строке 8. В данном макросе при выполнении строки 9 будет выполнено содержимое строки 46 фрагмента.
Одновременно с этим выполнится строка 53 фрагмента, которая обеспечит удаление индикатора Chart Trade. Однако код вернется и выполнится строка 10 макроса. Это приведет к ошибке и не позволит продолжить выполнение кода. Наконец, в строке 11 снова вызывается процедура, которая вызвала AdjustTemplate. Неважно, кто вызвал процедуру; если шаблон не может быть изменен, индикатор будет удален без возникновения дополнительных ошибок. Это одна из самых сложных частей системы. Но мы еще не закончили, есть еще много аспектов, которые надо проанализировать.
Теперь предположим, что модификация шаблона прошла успешно. В этом случае мы выполним новый тест в строке 111, чтобы зафиксировать положение кликабельных объектов. Данное действие выполняется только во время создания шаблона. Так что цикл в строке 113 делает именно это: он фиксирует, где находятся объекты, на которые можно нажать, а строки 120 и 121 дополняют данный захват.
Далее мы разместим объект OBJ_CHAT на графике. Это происходит между строками 123 и 125. Строка 127 завершает манипуляции с шаблоном, позволяя нам использовать строку 129, чтобы указать OBJ_CHAT, какой шаблон использовать. Затем, в строке 130, мы принудительно обновляем содержимое объекта OBJ_CHAT. Таким образом, мы завершаем построение всей конструкции внутри объекта OBJ_CHAT, и все элементы отображаются, создавая Chart Trade.
Если вы еще не поняли, как обновить шаблон так, чтобы не понадобилось бы написать код Chart Trade, ознакомьтесь со всей процедурой AdjustTemplate. Здесь осуществляется управление и отображение Chart Trade. Все объекты, появляющиеся в Chart Trade, создаются, устанавливаются и настраиваются в рамках данной процедуры.
Нет смысла искать в коде такие объекты, как кнопки, иконки, изображения или подобные элементы, поскольку в виде кода они не существуют. Код определяет, где находятся данные элементы, и преобразует их в доступные пользователю ресурсы. Не пытайтесь понять его с точки зрения обычного создания объектов с использованием кода. Здесь мы поступим ровно наоборот: сначала создается желаемый внешний вид, а затем просим код сделать эту концепцию функциональной. Если вы не поймете эту идею, то почувствуете себя совершенно потерянным.
Уровень сложности таков, что можно изменять форму, расположение и даже шрифты объектов, не написав ни строчки кода. Всё, что вам нужно сделать, - это открыть MetaTrader 5, загрузить шаблон Chart Trade и изменить его отображение на графике. Всё остальное делается именно благодаря процедуре AdjustTemplate.
Однако, как всегда, есть кое-что, что усложняет дело. По этой причине нам нужно больше процедур для поддержки данной концепции приложений RAD (Rapid Application Development). В данном контексте следующее, что мы рассмотрим, будет расположенная в строке 135 функция CheckMousePosition. Это очень простая функция. Её единственная задача - проверить, находится ли мышь внутри одного из кликабельных или выбираемых объектов. Если да, то строка 145 вернет нужный объект. Если нет, то строка 147 сообщит, что мышь не находится над объектом, над которым можно щелкнуть или который можно выбрать.
Теперь у нас есть две последовательные процедуры для поддержки случаев, когда мы не можем найти оптимальное решение. На самом деле есть один конкретный случай, когда процедура AdjustTemplate не может решить проблему достаточно приемлемо. В данном случае речь идет об объекте OBJ_EDIT, который позволяет пользователю вводить текст.
Процедура в строке 150 служит только для удаления созданных объектов OBJ_EDIT. Процедура, которая определяется в строке 156, позаботится о создании только одного такого объекта, с которым пользователь будет взаимодействовать и вводить значение в Chart Trade.
Объявление процедуры CreateObjectEditable может показаться странным, но в этом нет ничего необычного. Это объявление создает перегрузку в вызове и позволяет строке 171 адаптировать OBJ_EDIT к типу редактируемой переменной. Иными словами, если мы работаем со значением типа double, оно будет иметь два знака после запятой. В противном случае значение будет рассматриваться как строка. В этом случае отображаемый текст будет совпадать с текстом, предоставленным вызывающей программой.
В принципе, здесь нет никаких сложностей, по крайней мере, для тех, кто привык программировать на MQL5. Всё, что делается, широко известно даже тем, кто имеет опыт работы с MQL5 и с манипулированием объектами.
После этого мы можем перейти к следующей функции: RestoreState, объявленный в строке 175 и реализованный до строки 210. Данная процедура не работает сама по себе, а нуждается в другой процедуре, SaveState, расположенной в строке 244. Однако обе процедуры несколько нарушают общие правила, поскольку работают вместе и нацелены на временное хранение основной информации. Эти данные относятся к последней известной позиции Chart Trade, точнее, к тому месту, где объект OBJ_CHAT находился до обновления графика.
Для хранения данной информации мы используем глобальные переменные терминала. Тут важно отметить, что для хранения этих значений мы используем только одну глобальную переменную. Однако я хочу, чтобы читатель обратил внимание на еще одну важную деталь. Прошу заметить, что в строке 187 мы повторно используем класс C_AdjustTemplate. Но почему? Будем ли мы изменять что-либо в шаблоне на данном этапе? Мой ответ - нет. Теперь мы будем использовать шаблон по-другому.
Во время его создания, в процедуре AdjustTemplate, в строке 72, мы упомянули, что мы можем добавлять и удалять элементы из шаблона. Именно этим мы и займемся здесь. Для восстановления данных в Chart Trade, мы будем использовать уже измененные значения шаблона. Но зачем нам восстанавливать эти данные, если объект OBJ_CHAT получает шаблон для создания Chart Trade? Потому что возможно взаимодействие пользователей с кликабельными объектами, особенно с кнопками покупки и продажи по рыночной цене. Данные кнопки генерируют события, о которых мы расскажем позже. Чтобы избежать очень большой задержки системы при поиске информации или отсутствия данных в нужный момент, как только запрашивается перезапуск Chart Trade, мы восстанавливаем всё.
Чтобы правильно восстановить информацию, которая находится в шаблоне, мы должны сначала открыть его, что и делается в строке 187. Если всё прошло успешно, тест в строке 188 позволит выполнить строки 190-194. Это добавит имена и местоположение значений, которые надо получить. Затем, в строке 195, предпринимается попытка выполнить преобразование. Поскольку мы ничего не добавляем и не изменяем в шаблоне, при выполнении строки 195 получим запрашиваемые данные между строками 190 и 194. Если выполнение прошло успешно, то между строками 197 и 201 мы можем восстановить приватные переменные класса C_ChartFloatingRAD. Не обязательно восстанавливать все из них, только основные, которые будут важны при взаимодействии с событиями, поскольку мы обеспечим доступность этих значений. Наконец, мы закрываем шаблон в строке 204 и в строке 207 удаляем глобальные переменные, которые временно создались в терминале.
Нужно понимать, что эти переменные имеют очень короткий срок службы - менее одной секунды. Их цель - хранить графические координаты положения объекта OBJ_CHAT до изменения временных рамок графика.
Хорошо, эта статья уже содержит много информации, которую вам предстоит проанализировать и понять. Однако прежде, чем завершить сегодняшний материал, мы можем вкратце рассказать о трех дополнительных процедурах, поскольку они просты и понятны.
Процедура в строке 214 является конструктором данного класса. Его функциональность очень проста, поскольку она использует концепции, описанные ранее в этой статье. В строке 218 устанавливается отметка, которая указывает, будет ли сохранено текущее состояние. По умолчанию ничего не сохраняется. В строке 219 мы пытаемся создать индикатор. Если это невозможно, мы возвращаемся к коду, который вызывает конструктор, что приведет к закрытию индикатора. В строке 220 мы пытаемся восстановить предыдущее состояние. Если не получается, мы используем данные, предоставленные пользователем. В строке 230 мы создаем объект OBJ_CHAT, а в строке 231 задаем необходимые параметры.
Процедура в строке 234 является деструктором класса. Этот вариант ещё проще. В строке 237 мы удаляем все объекты, связанные с OBJ_CHAT. Это важно для того, чтобы в графике не оставалось лишних элементов. В строке 238 мы оцениваем, нужно ли сохранять текущее состояние шаблона или нет. Если мы не выберем сохранение, строка 239 удалит файл шаблона с диска. В результате мы не сможем использовать функцию RestoreState, реализованную между строками 175 и 210, поскольку тест в строке 188 будет провален, и значения будут теми, которые были предоставлены пользователем во время последней загрузки индикатора.
В строке 244 мы находим функцию SaveState. При этом используется внутренний макрос, который определяется в строке 246 и удаляется в строке 257. Хотя данный макрос практически не нужен, он используется для упрощения использования имени глобальной переменной терминала, так как оно используется дважды: один раз для ее временного создания и один раз для хранения ее значения. Я думаю, что проще использовать макрос. Однако здесь важна строка 255, которая гарантирует, что тест в строке 238 провалится и файл шаблона останется нетронутым.
Заключительные идеи
Хотя процедура DispatchMessage, объявленная в строке 260 и реализованная вплоть до строки 386, ещё требует объяснения, мы не будем рассматривать ее в этой статье. Причина в том, что данная процедура довольно сложная и мы не можем ее объяснить за короткий промежуток времени. Кроме того, она гораздо лучше воспринимается, когда можно ее наблюдать вместе с другой программой. Это связано с тем, что эта процедура создает события, которые позволяют пользователю взаимодействовать с Chart Trade для открытия или закрытия рыночных позиций.
Данный тип функциональности заслуживает более подробного объяснения. Поэтому, а также для поддержания интереса начинающего программиста к чтению следующей статьи, я решил не показывать ее внутреннее содержание сегодня. Данный контент будет подробно представлен в следующей статье. Пока сохраните код класса C_ChartFloatingRAD в файл и внимательно изучите его, так как он полностью функционален. Приготовьтесь к следующей статье, в которой мы проанализируем код между строками 260 и 386.
А мы с вами увидимся в следующей статье. Ниже приведены ссылки по RAD-программированию на MQL5. Это те статьи, в которых мы ранее объясняли концепции, использованные для построения этого Chart Trade таким образом, как показано в данной статье. Как вы могли заметить, у нас нет нескольких объектов, но нам всё равно удается взаимодействовать и представлять элементы на графике. Однако, если использовать окно объектов с помощью сочетания клавиш Ctrl + B, мы увидим только один объект.
В прикрепленном файле можно найти всё необходимое, чтобы увидеть функционирование индикатора и испытать его взаимодействие, как показано на видео ниже:
Ссылки на статьи RAD на MQL5
Перевод с португальского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/pt/articles/12442





- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования