
开发回放系统(第 45 部分):Chart Trade 项目(四)
概述
在上一篇文章开发回放系统(第 44 部分):Chart Trade 项目 (三) 中,我展示了如何为 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/en/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 项目(三)中解释过了。现在,在第 105 行,我们做了一些以前没有的事情。在这一行中,我们修复了一个小错误,即通过单击并拖动最大化/最小化按钮可以移动浮动窗口。这一行删除了它,紧接着在第 106 行,我们从模板中获取了所需的值。请注意,我们现在以参数为 false 作调用。这样,数值就会保存在正确的位置,以备将来使用。
第 108 行有一个有趣的地方:我们赋予浮动窗口最大化或最小化的功能。以前,这是在代码的其他地方完成的,但出于实用性的考虑,我决定把这个控制放在这里。这使得构建过程变得更加容易,因为与窗口相关的一切都在同一个地方。
同样,第 109 和 110 行让我们可以更方便地使用浮动窗口。用户往往希望浮动窗口能根据其状态改变位置。也就是说,当最大化时,它处于一个位置,而当最小化时,它又处于另一个位置。这正是第 109 行和第 110 行所做的:它们将浮动窗口定位在其最后所处的位置,这取决于它是最大化还是最小化。
第 112 行已在前一篇文章中讨论过,现在我们来看看第 114 和 115 行,它们之前是在一个单独的函数中。因此,在第 114 行,我们在 OBJ_CHART 对象中运行已经修改过的模板。因此,当执行第 115 行时,我们将在图表上显示并更新模板。关键是现在整个过程都集中在这个子程序中。因此,我们不必担心需要传递额外的数据来确保信息正确地呈现给用户。
该系统有可能被归入一个单独的类,但目前还不太可能。由于它只在 Chart Trade 中使用,我就不在这里介绍了。在类中实现它可能会很有趣,因为我决定将其他东西转化为模板。不过,现在我还是保持代码原样。
现在,我们有了一些不同的东西。第 137 行包含一个相当不寻常的代码类型。当我们有相同的程序但用于不同的类型时,这种类型的代码很常见。这意味着什么?🤔 首先,你需要明白:为什么要创建一个子程序来显示 double 型数值,另一个子程序来显示 int 型数值,第三个子程序来显示字符串?创建一个子程序岂不是容易得多,因为其中的代码基本上总是相同的?唯一的区别是,在一种情况下,值属于一种类型,而在另一种情况下,值属于另一种类型。这正是第 137 行的作用。
但等等,如果我们的目的是表示数值,难道不能直接以字符串的形式传递吗?是的,我们可以,但有些事我们没有考虑到。如果我们需要以特定方式表示值,并从代码中的不同点调用,该怎么办?想想我们要做的工作吧。但这样一来,我们只需传递原始类型的值,让编译器为我们创建一个子程序,并以我们想要的方式表示它。如果我们更改视图,只需更改代码中的一个点即可。编译器将负责设置一切。
少工作,多产出。
否则,工作量只会增加。我的建议是:尽可能让编译器为你工作。您会发现,您的代码将变得更易于维护,您的工作效率也将成倍提高。
第 137 行是在第 138 行使用的。除此以外,我们没有在其它地方使用第 137 行。不过,在第 151 行,我们使用了作为参数传递的值。请注意,我明确转换为字符串类型。我们可以在此步骤中或之后进行转换,没有任何区别。这是一种特殊情况。
请注意,在这个过程中,我们创建了一个额外的对象 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 型,但我们可以在其中存储不同的值。我已经多次解释过如何做到这一点。但在这里,我们的方式非常特别。因此,如果这些行中的任何一行无法访问或读取终端全局变量,我们将向调用者返回一个 false 值。实际调用该函数的唯一地方是构造函数,我们很快就会回到构造函数。
每次在特定行调用时,我们都会恢复之前保存的值。因此,在更改时间框架时,您可以继续使用 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 行的内容相对应。虽然这样做可行(相对较好),但我们会遇到其他问题。其中之一是,如果指标返回一个更严重的错误,相应的文件将不会被删除,而是保留在磁盘上。如果您试图再次将指标放到图表上,数据将是错误的,这可能导致指标再次被移除。这种情况会一直持续到有问题的文件被删除为止。
出于这些原因和其他原因,我更喜欢使用终端全局变量。但请注意,我不会直接使用它们。为了使用变量,我使用了一个宏。为什么呢?原因在于全局终端变量的寿命。
看看第 216 行出现的宏中发生了什么。请注意,我们首先尝试将变量创建为临时终端全局变量。这意味着当您关闭 MetaTrader 5 终端时,变量也会随之销毁。这样我们就能保证 Chart Trade 指标的完整性。
请注意,每个全局终端变量都将存储一个值。执行这些变量的顺序并不重要,真正重要的是变量名和变量值。请记住,名称不能包含超过 64 个字符。因此,我们使用宏来创建名称,这使我们在创建名称时具有一定的优势。
在保存模板数据的过程中,没有什么特别需要强调的。事实上,如果没有它,每当时间框架发生变化时,您就不得不考虑重新配置指标中的数据。考虑到许多用户往往会在交易期间多次更改时间框架,因此不断调整 Chart Trade 指标的值将是一个大问题。通过使用编程和 MetaTrader 5 的功能,我们可以将其放在一边,专注于其他事情。为此,我们使用第 214 行的过程。
还有另一种将数据保存在 "内存" 中的方法,但由于涉及到图形对象,我现在就不详细介绍了。我们下次再讨论这个问题。
这篇文章就快写完了,但首先,我们需要考虑另一件事。这就是信息处理功能。它从第 237 行开始,与看上去的情况相反,它比许多人想象的要简单和友好得多。不过,你可能会有疑问:如果我们实际上只使用鼠标指示器,为什么这个消息处理函数要包含 4 种事件呢?
我想再次强调,MetaTrader 5 是一个基于事件的平台。因此,您需要了解如何以这种风格开展工作。在上一篇文章中,我提到我们可以使用其他事件来简化我们的逻辑。虽然代码在某些方面有些混乱,但仍能发挥作用。不过,我们可以将上一篇文章中讨论过的大部分检查保留下来,这些检查确实应该出现在这段代码中。这些检查将由 MetaTrader 5 执行。因此,如果比较这两个类的代码,就会发现这个类代码包含的检查较少。为什么呢?
因为 MetaTrader 5 会执行这些操作。Chart Trade 中的对象点击事件现在已被分析对象点击(而不仅仅是鼠标点击)的版本所取代。这使得编码变得更加容易。这样,我们就能在更易读的代码中包含更多的事件。您可以查看第 315 至 343 行的代码,了解点击的情况。在这些代码行中,我们会处理模板中所有对象的点击。所有按钮,甚至是那些还没有任何相关功能的按钮,如买入、卖出和平仓按钮。
消息处理程序中值得注意的是第 258 行中的 CHARTEVENT_CHART_CHANGE 事件。当终端的尺寸发生变化时,会有一个细节。发生这种情况时,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



