
开发回放系统(第 47 部分):Chart Trade 项目(六)
概述
在上一篇文章开发回放系统(第 46 部分):Chart Trade Project (五)中,我展示了如何将数据添加到可执行文件中,这样就不必单独传输数据了。这些知识对于不久的将来要做的事情非常重要。但现在,我们将继续开发需要首先实现的内容。
在本文中,我们将改进 Chart Trade 指标,使其功能足以与一些 EA 配合使用。这样,我们就可以访问 Chart Trade 指标,并像实际连接 EA 一样使用它。但是,让我们把它变得比过去的一篇文章更有趣从零开始开发 EA 交易(第 30 部分):把 Chart Trader 作为指标? 。在那篇文章中,我们使用 Chart Trade 作为一个条件指标。而这一次,它将成为一个真正的指标。
为此,我们将以一种非常特殊的方式使其发挥作用,就像使用任何其他类型的指标一样。为此,我们将创建一个对应的数据缓冲区。这个过程在其他文章中有过描述,相关信息可在此处找到:
这三篇文章包含了我们实际要做的工作的基础。如果您还没有读过,我建议您读一读。否则,您可能会在阅读本文时感到困惑,并且由于缺乏作为本文基础的深层知识而难以理解本文。因此,您应该阅读上述文章,并充分理解其内容。
在开始改动之前,我们需要做一些小修改。它们都包含在现有的 C_ChartFloatingRAD 类代码中,以便于我们轻松、充分地访问所需的数据。因此,我们将进入 Chart Trade 指标工作的最后阶段。
小改变,大成果
将做出的修改很少,也很简单。当然,前提是您一直在关注本系列文章。现在,我们正处于回放/模拟系统文章的第二阶段。以下是 Chart Trade 指标的完整代码。请务必先复习这些代码,以便更轻松地解释类的代码。
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. #property description "Chart Trade base indicator." 04. #property description "This version communicates via buffer with the EA." 05. #property description "See the articles for more details." 06. #property version "1.47" 07. #property icon "/Images/Market Replay/Icons/Indicators.ico" 08. #property link "https://www.mql5.com/es/articles/11760" 09. #property indicator_chart_window 10. #property indicator_plots 0 11. #property indicator_buffers 1 12. //+------------------------------------------------------------------+ 13. #include <Market Replay\Chart Trader\C_ChartFloatingRAD.mqh> 14. //+------------------------------------------------------------------+ 15. C_ChartFloatingRAD *chart = NULL; 16. //+------------------------------------------------------------------+ 17. input int user01 = 1; //Leverage 18. input double user02 = 100.1; //Finance Take 19. input double user03 = 75.4; //Finance Stop 20. //+------------------------------------------------------------------+ 21. double m_Buff[]; 22. //+------------------------------------------------------------------+ 23. int OnInit() 24. { 25. bool bErr; 26. 27. chart = new C_ChartFloatingRAD("Indicator Chart Trade", new C_Mouse("Indicator Mouse Study"), user01, user02, user03); 28. 29. if (bErr = (_LastError != ERR_SUCCESS)) Print(__FILE__, " - [Error]: ", _LastError); 30. 31. SetIndexBuffer(0, m_Buff, INDICATOR_DATA); 32. ArrayInitialize(m_Buff, EMPTY_VALUE); 33. 34. return (bErr ? INIT_FAILED : INIT_SUCCEEDED); 35. } 36. //+------------------------------------------------------------------+ 37. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 38. { 39. (*chart).MountBuffer(m_Buff, rates_total); 40. 41. return rates_total; 42. } 43. //+------------------------------------------------------------------+ 44. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 45. { 46. (*chart).DispatchMessage(id, lparam, dparam, sparam); 47. (*chart).MountBuffer(m_Buff); 48. 49. ChartRedraw(); 50. } 51. //+------------------------------------------------------------------+ 52. void OnDeinit(const int reason) 53. { 54. if (reason == REASON_CHARTCHANGE) (*chart).SaveState(); 55. 56. delete chart; 57. } 58. //+------------------------------------------------------------------+
Chart Trade 指标源代码
请注意,上面的所有代码包含了 Chart Trade 指标运行所需的所有内容。不过,出于实用的考虑,大部分代码都被移到了一个类中,稍后将对此进行讨论。但这些代码是如何工作的呢?它是如何让我们向 EA 发送命令以执行操作的?等一下,让我们先弄清楚指标代码中发生了什么。
第 11 行包含我们需要的第一个阶段,在这一行中,我们定义将使用一个缓冲区。我们可以使用更多的缓冲区,但一个就足够了。
我们将使用的缓冲区在第 21 行中声明,但我们只是在第 31 行定义了如何使用它。由于我们不希望缓冲区被 "垃圾" 填满,我们在第 32 行将其初始化为只包含零值。
正如你所看到的,与之前的文章相比,指标代码没有发生任何重大变化。但它现在有了两行新代码:第 39 行和第 47 行。这两行都调用了类中的同一个函数,我们很快就会看到。由于数量和参数不同,您可以认为这些是不同的函数。不过,你很快就会发现,它们都是一样的。为了理解这一点,让我们看看该类的完整代码,如下所示。
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. public : 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 stData 015. { 016. int Leverage; 017. double PointsTake, 018. PointsStop; 019. bool IsDayTrade; 020. union u01 021. { 022. ulong TickCount; 023. double dValue; 024. }uCount; 025. eObjectsIDE Msg; 026. }; 027. private : 028. struct st00 029. { 030. int x, y, minx, miny; 031. string szObj_Chart, 032. szObj_Editable, 033. szFileNameTemplate; 034. long WinHandle; 035. double FinanceTake, 036. FinanceStop; 037. bool IsMaximized; 038. stData ConfigChartTrade; 039. struct st01 040. { 041. int x, y, w, h; 042. color bgcolor; 043. int FontSize; 044. string FontName; 045. }Regions[MSG_NULL]; 046. }m_Info; 047.//+------------------------------------------------------------------+ 048. C_Mouse *m_Mouse; 049. string m_szShortName; 050.//+------------------------------------------------------------------+ 051. void CreateWindowRAD(int w, int h) 052. { 053. m_Info.szObj_Chart = "Chart Trade IDE"; 054. m_Info.szObj_Editable = m_Info.szObj_Chart + " > Edit"; 055. ObjectCreate(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJ_CHART, 0, 0, 0); 056. ObjectSetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_XDISTANCE, m_Info.x); 057. ObjectSetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_YDISTANCE, m_Info.y); 058. ObjectSetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_XSIZE, w); 059. ObjectSetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_YSIZE, h); 060. ObjectSetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_DATE_SCALE, false); 061. ObjectSetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_PRICE_SCALE, false); 062. m_Info.WinHandle = ObjectGetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_CHART_ID); 063. }; 064.//+------------------------------------------------------------------+ 065. void AdjustEditabled(C_AdjustTemplate &Template, bool bArg) 066. { 067. for (eObjectsIDE c0 = 0; c0 <= MSG_STOP_VALUE; c0++) 068. if (bArg) 069. { 070. Template.Add(EnumToString(c0), "bgcolor", NULL); 071. Template.Add(EnumToString(c0), "fontsz", NULL); 072. Template.Add(EnumToString(c0), "fontnm", NULL); 073. } 074. else 075. { 076. m_Info.Regions[c0].bgcolor = (color) StringToInteger(Template.Get(EnumToString(c0), "bgcolor")); 077. m_Info.Regions[c0].FontSize = (int) StringToInteger(Template.Get(EnumToString(c0), "fontsz")); 078. m_Info.Regions[c0].FontName = Template.Get(EnumToString(c0), "fontnm"); 079. } 080. } 081.//+------------------------------------------------------------------+ 082.inline void AdjustTemplate(const bool bFirst = false) 083. { 084.#define macro_AddAdjust(A) { \ 085. (*Template).Add(A, "size_x", NULL); \ 086. (*Template).Add(A, "size_y", NULL); \ 087. (*Template).Add(A, "pos_x", NULL); \ 088. (*Template).Add(A, "pos_y", NULL); \ 089. } 090.#define macro_GetAdjust(A) { \ 091. m_Info.Regions[A].x = (int) StringToInteger((*Template).Get(EnumToString(A), "pos_x")); \ 092. m_Info.Regions[A].y = (int) StringToInteger((*Template).Get(EnumToString(A), "pos_y")); \ 093. m_Info.Regions[A].w = (int) StringToInteger((*Template).Get(EnumToString(A), "size_x")); \ 094. m_Info.Regions[A].h = (int) StringToInteger((*Template).Get(EnumToString(A), "size_y")); \ 095. } 096.#define macro_PointsToFinance(A) A * (GetInfoTerminal().VolumeMinimal + (GetInfoTerminal().VolumeMinimal * (m_Info.ConfigChartTrade.Leverage - 1))) * GetInfoTerminal().AdjustToTrade 097. 098. C_AdjustTemplate *Template; 099. 100. if (bFirst) 101. { 102. Template = new C_AdjustTemplate(m_Info.szFileNameTemplate = IntegerToString(GetInfoTerminal().ID) + ".tpl", true); 103. for (eObjectsIDE c0 = 0; c0 <= MSG_CLOSE_POSITION; c0++) macro_AddAdjust(EnumToString(c0)); 104. AdjustEditabled(Template, true); 105. }else Template = new C_AdjustTemplate(m_Info.szFileNameTemplate); 106. m_Info.ConfigChartTrade.Leverage = (m_Info.ConfigChartTrade.Leverage <= 0 ? 1 : m_Info.ConfigChartTrade.Leverage); 107. m_Info.FinanceTake = macro_PointsToFinance(FinanceToPoints(MathAbs(m_Info.FinanceTake), m_Info.ConfigChartTrade.Leverage)); 108. m_Info.FinanceStop = macro_PointsToFinance(FinanceToPoints(MathAbs(m_Info.FinanceStop), m_Info.ConfigChartTrade.Leverage)); 109. m_Info.ConfigChartTrade.PointsTake = FinanceToPoints(m_Info.FinanceTake, m_Info.ConfigChartTrade.Leverage); 110. m_Info.ConfigChartTrade.PointsStop = FinanceToPoints(m_Info.FinanceStop, m_Info.ConfigChartTrade.Leverage); 111. (*Template).Add("MSG_NAME_SYMBOL", "descr", GetInfoTerminal().szSymbol); 112. (*Template).Add("MSG_LEVERAGE_VALUE", "descr", IntegerToString(m_Info.ConfigChartTrade.Leverage)); 113. (*Template).Add("MSG_TAKE_VALUE", "descr", DoubleToString(m_Info.FinanceTake, 2)); 114. (*Template).Add("MSG_STOP_VALUE", "descr", DoubleToString(m_Info.FinanceStop, 2)); 115. (*Template).Add("MSG_DAY_TRADE", "state", (m_Info.ConfigChartTrade.IsDayTrade ? "1" : "0")); 116. (*Template).Add("MSG_MAX_MIN", "state", (m_Info.IsMaximized ? "1" : "0")); 117. (*Template).Execute(); 118. if (bFirst) 119. { 120. for (eObjectsIDE c0 = 0; c0 <= MSG_CLOSE_POSITION; c0++) macro_GetAdjust(c0); 121. m_Info.Regions[MSG_TITLE_IDE].w = m_Info.Regions[MSG_MAX_MIN].x; 122. AdjustEditabled(Template, false); 123. }; 124. ObjectSetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_YSIZE, (m_Info.IsMaximized ? 210 : m_Info.Regions[MSG_TITLE_IDE].h + 6)); 125. ObjectSetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_XDISTANCE, (m_Info.IsMaximized ? m_Info.x : m_Info.minx)); 126. ObjectSetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_YDISTANCE, (m_Info.IsMaximized ? m_Info.y : m_Info.miny)); 127. 128. delete Template; 129. 130. ChartApplyTemplate(m_Info.WinHandle, "/Files/" + m_Info.szFileNameTemplate); 131. ChartRedraw(m_Info.WinHandle); 132. 133.#undef macro_PointsToFinance 134.#undef macro_GetAdjust 135.#undef macro_AddAdjust 136. } 137.//+------------------------------------------------------------------+ 138. eObjectsIDE CheckMousePosition(const int x, const int y) 139. { 140. int xi, yi, xf, yf; 141. 142. for (eObjectsIDE c0 = 0; c0 <= MSG_CLOSE_POSITION; c0++) 143. { 144. xi = (m_Info.IsMaximized ? m_Info.x : m_Info.minx) + m_Info.Regions[c0].x; 145. yi = (m_Info.IsMaximized ? m_Info.y : m_Info.miny) + m_Info.Regions[c0].y; 146. xf = xi + m_Info.Regions[c0].w; 147. yf = yi + m_Info.Regions[c0].h; 148. if ((x > xi) && (y > yi) && (x < xf) && (y < yf)) return c0; 149. } 150. return MSG_NULL; 151. } 152.//+------------------------------------------------------------------+ 153.inline void DeleteObjectEdit(void) 154. { 155. ChartRedraw(); 156. ObjectsDeleteAll(GetInfoTerminal().ID, m_Info.szObj_Editable); 157. m_Info.ConfigChartTrade.Msg = MSG_NULL; 158. } 159.//+------------------------------------------------------------------+ 160. template <typename T > 161. void CreateObjectEditable(eObjectsIDE arg, T value) 162. { 163. long id = GetInfoTerminal().ID; 164. 165. DeleteObjectEdit(); 166. CreateObjectGraphics(m_Info.szObj_Editable, OBJ_EDIT, clrBlack, 0); 167. ObjectSetInteger(id, m_Info.szObj_Editable, OBJPROP_XDISTANCE, m_Info.Regions[arg].x + m_Info.x + 3); 168. ObjectSetInteger(id, m_Info.szObj_Editable, OBJPROP_YDISTANCE, m_Info.Regions[arg].y + m_Info.y + 3); 169. ObjectSetInteger(id, m_Info.szObj_Editable, OBJPROP_XSIZE, m_Info.Regions[arg].w); 170. ObjectSetInteger(id, m_Info.szObj_Editable, OBJPROP_YSIZE, m_Info.Regions[arg].h); 171. ObjectSetInteger(id, m_Info.szObj_Editable, OBJPROP_BGCOLOR, m_Info.Regions[arg].bgcolor); 172. ObjectSetInteger(id, m_Info.szObj_Editable, OBJPROP_ALIGN, ALIGN_CENTER); 173. ObjectSetInteger(id, m_Info.szObj_Editable, OBJPROP_FONTSIZE, m_Info.Regions[arg].FontSize - 1); 174. ObjectSetString(id, m_Info.szObj_Editable, OBJPROP_FONT, m_Info.Regions[arg].FontName); 175. ObjectSetString(id, m_Info.szObj_Editable, OBJPROP_TEXT, (typename(T) == "double" ? DoubleToString(value, 2) : (string) value)); 176. ChartRedraw(); 177. m_Info.ConfigChartTrade.Msg = MSG_NULL; 178. } 179.//+------------------------------------------------------------------+ 180. bool RestoreState(void) 181. { 182. uCast_Double info; 183. bool bRet; 184. 185. if (bRet = GlobalVariableGet(macro_NameGlobalVariable("P"), info.dValue)) 186. { 187. m_Info.x = info._int[0]; 188. m_Info.y = info._int[1]; 189. } 190. if (bRet = (bRet ? GlobalVariableGet(macro_NameGlobalVariable("M"), info.dValue) : bRet)) 191. { 192. m_Info.minx = info._int[0]; 193. m_Info.miny = info._int[1]; 194. } 195. if (bRet = (bRet ? GlobalVariableGet(macro_NameGlobalVariable("B"), info.dValue) : bRet)) 196. { 197. m_Info.ConfigChartTrade.IsDayTrade = info._char[0]; 198. m_Info.IsMaximized = info._char[1]; 199. m_Info.ConfigChartTrade.Msg = (eObjectsIDE)info._char[2]; 200. } 201. if (bRet = (bRet ? GlobalVariableGet(macro_NameGlobalVariable("L"), info.dValue) : bRet)) 202. m_Info.ConfigChartTrade.Leverage = info._int[0]; 203. bRet = (bRet ? GlobalVariableGet(macro_NameGlobalVariable("Y"), m_Info.ConfigChartTrade.uCount.dValue) : bRet); 204. bRet = (bRet ? GlobalVariableGet(macro_NameGlobalVariable("T"), m_Info.FinanceTake) : bRet); 205. bRet = (bRet ? GlobalVariableGet(macro_NameGlobalVariable("S"), m_Info.FinanceStop) : bRet); 206. 207. 208. GlobalVariablesDeleteAll(macro_NameGlobalVariable("")); 209. 210. return bRet; 211. } 212.//+------------------------------------------------------------------+ 213. public : 214.//+------------------------------------------------------------------+ 215. C_ChartFloatingRAD(const string szShortName) 216. :m_Mouse(NULL), 217. m_szShortName(szShortName) 218. { 219. } 220.//+------------------------------------------------------------------+ 221. C_ChartFloatingRAD(string szShortName, C_Mouse *MousePtr, const int Leverage, const double FinanceTake, const double FinanceStop) 222. :C_Terminal(), 223. m_szShortName(NULL) 224. { 225. if (!IndicatorCheckPass(szShortName)) SetUserError(C_Terminal::ERR_Unknown); 226. m_Mouse = MousePtr; 227. if (!RestoreState()) 228. { 229. m_Info.ConfigChartTrade.Leverage = Leverage; 230. m_Info.FinanceTake = FinanceTake; 231. m_Info.FinanceStop = FinanceStop; 232. m_Info.ConfigChartTrade.IsDayTrade = true; 233. m_Info.ConfigChartTrade.uCount.TickCount = 0; 234. m_Info.ConfigChartTrade.Msg = MSG_NULL; 235. m_Info.IsMaximized = true; 236. m_Info.minx = m_Info.x = 115; 237. m_Info.miny = m_Info.y = 64; 238. } 239. CreateWindowRAD(170, 210); 240. AdjustTemplate(true); 241. } 242.//+------------------------------------------------------------------+ 243. ~C_ChartFloatingRAD() 244. { 245. if (m_Mouse == NULL) return; 246. ChartRedraw(); 247. ObjectsDeleteAll(GetInfoTerminal().ID, m_Info.szObj_Chart); 248. FileDelete(m_Info.szFileNameTemplate); 249. 250. delete m_Mouse; 251. } 252.//+------------------------------------------------------------------+ 253. void SaveState(void) 254. { 255.#define macro_GlobalVariable(A, B) if (GlobalVariableTemp(A)) GlobalVariableSet(A, B); 256. 257. uCast_Double info; 258. 259. if (m_Mouse == NULL) return; 260. info._int[0] = m_Info.x; 261. info._int[1] = m_Info.y; 262. macro_GlobalVariable(macro_NameGlobalVariable("P"), info.dValue); 263. info._int[0] = m_Info.minx; 264. info._int[1] = m_Info.miny; 265. macro_GlobalVariable(macro_NameGlobalVariable("M"), info.dValue); 266. info._char[0] = m_Info.ConfigChartTrade.IsDayTrade; 267. info._char[1] = m_Info.IsMaximized; 268. info._char[2] = (char)m_Info.ConfigChartTrade.Msg; 269. macro_GlobalVariable(macro_NameGlobalVariable("B"), info.dValue); 270. info._int[0] = m_Info.ConfigChartTrade.Leverage; 271. macro_GlobalVariable(macro_NameGlobalVariable("L"), info.dValue); 272. macro_GlobalVariable(macro_NameGlobalVariable("T"), m_Info.FinanceTake); 273. macro_GlobalVariable(macro_NameGlobalVariable("S"), m_Info.FinanceStop); 274. macro_GlobalVariable(macro_NameGlobalVariable("Y"), m_Info.ConfigChartTrade.uCount.dValue); 275. 276.#undef macro_GlobalVariable 277. } 278.//+------------------------------------------------------------------+ 279.inline void MountBuffer(double &Buff[], const int iPos = -1) 280. { 281. static int posBuff = 0; 282. uCast_Double info; 283. 284. if ((m_szShortName != NULL) || (m_Info.ConfigChartTrade.Msg == MSG_NULL)) return; 285. posBuff = (iPos > 5 ? iPos - 5 : posBuff); 286. Buff[posBuff + 0] = m_Info.ConfigChartTrade.uCount.dValue; 287. info._char[0] = (char)m_Info.ConfigChartTrade.IsDayTrade; 288. info._char[1] = (char)m_Info.ConfigChartTrade.Msg; 289. Buff[posBuff + 1] = info.dValue; 290. info._int[0] = m_Info.ConfigChartTrade.Leverage; 291. Buff[posBuff + 2] = info.dValue; 292. Buff[posBuff + 3] = m_Info.ConfigChartTrade.PointsTake; 293. Buff[posBuff + 4] = m_Info.ConfigChartTrade.PointsStop; 294. } 295.//+------------------------------------------------------------------+ 296.inline const stData GetDataBuffer(void) 297. { 298. double Buff[]; 299. int handle; 300. uCast_Double info; 301. stData data; 302. 303. ZeroMemory(data); 304. if (m_szShortName == NULL) return data; 305. if ((handle = ChartIndicatorGet(ChartID(), 0, m_szShortName)) == INVALID_HANDLE) return data; 306. if (CopyBuffer(handle, 0, 0, 5, Buff) == 5) 307. { 308. data.uCount.dValue = Buff[0]; 309. info.dValue = Buff[1]; 310. data.IsDayTrade = (bool)info._char[0]; 311. data.Msg = (C_ChartFloatingRAD::eObjectsIDE) info._char[1]; 312. info.dValue = Buff[2]; 313. data.Leverage = info._int[0]; 314. data.PointsTake = Buff[3]; 315. data.PointsStop = Buff[4]; 316. } 317. if (handle != INVALID_HANDLE) IndicatorRelease(handle); 318. 319. return data; 320. }; 321.//+------------------------------------------------------------------+ 322. void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam) 323. { 324.#define macro_AdjustMinX(A, B) { \ 325. B = (A + m_Info.Regions[MSG_TITLE_IDE].w) > x; \ 326. mx = x - m_Info.Regions[MSG_TITLE_IDE].w; \ 327. A = (B ? (mx > 0 ? mx : 0) : A); \ 328. } 329.#define macro_AdjustMinY(A, B) { \ 330. B = (A + m_Info.Regions[MSG_TITLE_IDE].h) > y; \ 331. my = y - m_Info.Regions[MSG_TITLE_IDE].h; \ 332. A = (B ? (my > 0 ? my : 0) : A); \ 333. } 334. 335. static int sx = -1, sy = -1; 336. int x, y, mx, my; 337. static eObjectsIDE obj = MSG_NULL; 338. double dvalue; 339. bool b1, b2, b3, b4; 340. eObjectsIDE tmp; 341. 342. if (m_szShortName == NULL) switch (id) 343. { 344. case CHARTEVENT_CHART_CHANGE: 345. x = (int)ChartGetInteger(GetInfoTerminal().ID, CHART_WIDTH_IN_PIXELS); 346. y = (int)ChartGetInteger(GetInfoTerminal().ID, CHART_HEIGHT_IN_PIXELS); 347. macro_AdjustMinX(m_Info.x, b1); 348. macro_AdjustMinY(m_Info.y, b2); 349. macro_AdjustMinX(m_Info.minx, b3); 350. macro_AdjustMinY(m_Info.miny, b4); 351. if (b1 || b2 || b3 || b4) AdjustTemplate(); 352. break; 353. case CHARTEVENT_MOUSE_MOVE: 354. if ((*m_Mouse).CheckClick(C_Mouse::eClickLeft)) switch (tmp = CheckMousePosition(x = (int)lparam, y = (int)dparam)) 355. { 356. case MSG_TITLE_IDE: 357. if (sx < 0) 358. { 359. DeleteObjectEdit(); 360. ChartSetInteger(GetInfoTerminal().ID, CHART_MOUSE_SCROLL, false); 361. sx = x - (m_Info.IsMaximized ? m_Info.x : m_Info.minx); 362. sy = y - (m_Info.IsMaximized ? m_Info.y : m_Info.miny); 363. } 364. if ((mx = x - sx) > 0) ObjectSetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_XDISTANCE, mx); 365. if ((my = y - sy) > 0) ObjectSetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_YDISTANCE, my); 366. if (m_Info.IsMaximized) 367. { 368. m_Info.x = (mx > 0 ? mx : m_Info.x); 369. m_Info.y = (my > 0 ? my : m_Info.y); 370. }else 371. { 372. m_Info.minx = (mx > 0 ? mx : m_Info.minx); 373. m_Info.miny = (my > 0 ? my : m_Info.miny); 374. } 375. break; 376. case MSG_BUY_MARKET: 377. case MSG_SELL_MARKET: 378. case MSG_CLOSE_POSITION: 379. DeleteObjectEdit(); 380. m_Info.ConfigChartTrade.Msg = tmp; 381. m_Info.ConfigChartTrade.uCount.TickCount = GetTickCount64(); 382. break; 383. }else if (sx > 0) 384. { 385. ChartSetInteger(GetInfoTerminal().ID, CHART_MOUSE_SCROLL, true); 386. sx = sy = -1; 387. } 388. break; 389. case CHARTEVENT_OBJECT_ENDEDIT: 390. switch (obj) 391. { 392. case MSG_LEVERAGE_VALUE: 393. case MSG_TAKE_VALUE: 394. case MSG_STOP_VALUE: 395. dvalue = StringToDouble(ObjectGetString(GetInfoTerminal().ID, m_Info.szObj_Editable, OBJPROP_TEXT)); 396. if (obj == MSG_TAKE_VALUE) 397. m_Info.FinanceTake = (dvalue <= 0 ? m_Info.FinanceTake : dvalue); 398. else if (obj == MSG_STOP_VALUE) 399. m_Info.FinanceStop = (dvalue <= 0 ? m_Info.FinanceStop : dvalue); 400. else 401. m_Info.ConfigChartTrade.Leverage = (dvalue <= 0 ? m_Info.ConfigChartTrade.Leverage : (int)MathFloor(dvalue)); 402. AdjustTemplate(); 403. obj = MSG_NULL; 404. ObjectDelete(GetInfoTerminal().ID, m_Info.szObj_Editable); 405. break; 406. } 407. break; 408. case CHARTEVENT_OBJECT_CLICK: 409. if (sparam == m_Info.szObj_Chart) switch (obj = CheckMousePosition(x = (int)lparam, y = (int)dparam)) 410. { 411. case MSG_DAY_TRADE: 412. m_Info.ConfigChartTrade.IsDayTrade = (m_Info.ConfigChartTrade.IsDayTrade ? false : true); 413. DeleteObjectEdit(); 414. break; 415. case MSG_MAX_MIN: 416. m_Info.IsMaximized = (m_Info.IsMaximized ? false : true); 417. DeleteObjectEdit(); 418. break; 419. case MSG_LEVERAGE_VALUE: 420. CreateObjectEditable(obj, m_Info.ConfigChartTrade.Leverage); 421. break; 422. case MSG_TAKE_VALUE: 423. CreateObjectEditable(obj, m_Info.FinanceTake); 424. break; 425. case MSG_STOP_VALUE: 426. CreateObjectEditable(obj, m_Info.FinanceStop); 427. break; 428. } 429. if (obj != MSG_NULL) AdjustTemplate(); 430. break; 431. } 432. } 433.//+------------------------------------------------------------------+ 434.}; 435.//+------------------------------------------------------------------+ 436.#undef macro_NameGlobalVariable 437.//+------------------------------------------------------------------+
C_ChartFloatingRAD 类源代码
我在文章中公布源代码似乎是在开玩笑,但这不是玩笑。事实上,我想尽可能详细地解释一下正在发生的事情。这样做并不是为了让你真正使用这些代码,而是为了让你理解这些代码并创造出更好的内容。
所以,让我们来弄清楚这到底是怎么回事。如果不了解这个类是如何运作的,就无法理解接下来要开发的内容。
我们一开始就有一些变化。在第 12 行,我们声明了一个公共子句。我们用它来使数据可以在类外访问。你可能会认为,我们可以把同样的数据放在其他地方。在这里,我只声明了一个结构和一个枚举。枚举对我们来说已经很熟悉了,但结构却是新的。在第 14 行和第 26 行之间,我们可以看到一个用于组织所需数据的结构声明。
这种结构大多比较容易理解,几乎不需要解释。不过,这里有些东西看起来很奇怪。第 20 行和第 24 行之间的联合有什么作用?第 25 行是做什么用的?联合将使我们能够更容易地传递数据。而且,第 25 行将在一个非常特殊的时刻使用。我们稍后再谈。
第 38 行提到了相同的结构。请记住:类中的数据不能在类外直接访问,因此,我们需要一些方法来访问这些数据。实际上,我们不会直接解决这个问题。从该指标的源代码中可以看出,并没有这种访问。接下来是第 49 行,它做了一件有趣的事情。它将作为一种选择器,但这一点稍后会更清楚。
在第 106 行,我们收集并调整了第 38 行结构中声明的第一个变量。请注意,这与我们之前所做的基本相同,但是我们现在使用的模型略有不同。
因此,在第 109 行,我们调整了其中一个变量,这是获利值点数的变量。请注意,我说的不是金融数值,而是点数。不要混淆这两种东西。我也不是指基于当前交易价格的点数,我说的是一般的点数。价格并不重要,重要的是我们有多少个偏移点数。同样重要的是,这一点数值随后将在金融方面进行调整。这一调整是在第 107 行完成的。
在第 110 行,我们也有类似的操作,只不过这次是针对止损值。正如第 107 行所做的那样,在考虑到第 109 行的点数之前调整金融值。在第 108 行,我们在考虑止损点之前调整止损金融数据。了解此时此刻正在发生的事情非常重要,如果您不能理解这一点,您就很难理解稍后下订单时所作的调整。
为了让您更容易理解,附件中包含了 Mouse 和 Chart Trade 指标以及一个相当简单的 EA 交易,这样您就可以了解发生了什么。出于安全考虑,附件中的 EA 不会下订单,只会打印输入的数值。这可以帮助您了解系统的实际运作方式。
现在,在第 157 行和第 177 行,同样出于实际考虑,我们还要调整一个变量的值。迄今为止,还没有进行过访问,我们要做的就是设置和调整变量的值。但看第 203 行,似乎有些奇怪。为什么要将该值保存在全局终端变量中?我们真的需要这样做吗,还是在浪费时间?事实上,这样做是必要的。原因是删除 Chart Trade 指标并重新加载到图表上时,内存中的所有值都会丢失。但这一数值对我们来说非常重要。
因此,我们在这里恢复之前保存的值。因此,我们在第 203 行恢复的这个值实际上是在第 274 行保存的。您可能已经注意到,我在解释中跳过了一些函数。那么,让我们一起回头看看它们吧。让我们从构造函数开始。是的,现在我们有了两个、而非一个类构造函数。其原因与我们在前一篇文章中讨论的 Mouse 指标相同。我们需要一种从缓冲区传输数据的方法。
说实话,我们并不是真的需要它。我们可以直接在 EA 或指标中实现这一点。不过,为了方便,我更喜欢把所有东西放在一起。使用这种方法,如果我以后需要做任何更改,只需修改 C_ChartFloatingRAD 类即可。我不必为实现模块标准化而对每个程序进行单独配置。回到构造函数,我们有一个从第 221 行开始的旧构造函数。基本上,它会从第 223 行获取数据,我们在该行初始化了指标名称。请注意:该名称不是用于向缓冲区写入数据的,而是用于从缓冲区读取数据的。此外,我们在第 232 行至第 234 行引入了新变量。不是全部,因为其余的都是根据模板设置过程配置的。
第二个构造函数非常简单,从第 215 行开始。在这里,我们主要赋值默认值。这发生在转换阶段。与 Mouse 指标一样,这里也有两个阶段:一个阶段是向缓冲区写入数据,另一个阶段是读取缓冲区。但这并不是因为编程出了问题。如果你在这里看到了一些错误,那么你可能没有理解我们在做什么。事实上,当我们使用指标时,我们会将数据写入缓冲区。这些数据由其他程序(通常是 EA)通过 CopyBuffer 读取。这就是为什么我们有两个阶段,也是为什么我们有两个构造函数。
与构造函数不同,我们只能有一个析构函数,它从第 243 行开始。我们在这里添加的唯一内容是第 245 行的代码。如果我们在指标中使用该类,当调用析构函数时,第 245 行的检查将通过,从而可以删除创建的对象。但如果该类用于读取指标缓冲区,第 245 行中的测试就会失败,然后阻止删除任何对象。这是一种简单而实用的机制。
之前是简单的部分,现在到了我们解释的关键部分。让我们看看如何进行交互,以及如何将数据保存到缓冲区和从缓冲区读取数据。这部分内容对于刚入门的人来说可能会有些困惑。因此,理解前几篇文章中讨论的概念非常重要。为了方便起见,我们来看看图 01。
图 01 - 交互图
在图 01 中,我们可以看到一个用于从指标向 EA 传输信息的通信系统。请注意,缓冲区实际上不是指标的一部分。尽管缓冲区是在指标中声明的,但它不应被视为指标内存的一部分。事实上,MetaTrader 5 支持这种方法,当我们从图表中删除指标时,后者会释放缓冲区所用的内存。但危险就在这里,数据实际上并没有被销毁,只是释放了内存。这样做的全部危险是什么,我试着用简单的语言来解释一下。
如果在 MetaTrader 5 中缓冲区所在的内存被释放 - 因为指标已从图表中删除,则在写入缓冲区后可能会出现这种情况。如果将指标放回到图表上,缓冲区可能仍包含一些数据。最后更新的时候可能会出现这种情况。因此,当 EA 使用 CopyBuffer 读取这些数据时,可能会读取到错误的数据。
这是非常严重的问题,我们在访问缓冲区时必须格外小心。为了在写入和读取之间提供一些同步,我们需要修改 C_ChartFloatingRAD 类中的 DispatchMessage 方法。写入缓冲区和读取缓冲区之间的同步非常重要,如果操作不当,用户提交的执行内容与实际执行内容之间就会出现延迟。也就是说,用户可能提交买入指令,但该指令不会被执行。但是,如果您在此之后立即发送卖出指令,则又会执行买入指令。这类故障并非由 MetaTrader 5 平台造成,而是由于误解了如何编写代码以确保正确的事件同步。
事实上,EA 和 Chart Trade 指标之间的代码没有任何联系。这种通信是通过使用缓冲区来实现的。因此,EA 不知道 Chart Trade 在做什么,就像 Chart Trade 不知道 EA 实际在做什么一样。不过,MetaTrader 5 知道这两者在做什么。由于指标和 EA 之间的共同点是 MetaTrader 5,因此我们使用 MetaTrader 5 进行操作。当您或用户觉得 EA 和 Chart Trade 指标是同一个程序时,这就就像变魔术一样。
还有一点同样重要,要知道,如果没有前面文章中提到的 Mouse 指标,Chart Trade 指标将不起作用。您需要在图表上使用所有三个应用程序:Mouse 指标、Chart Trade 指标和 EA,以及我们将来要讨论的其他东西。没有这一点,整个系统将什么都不能做。
这一切是如何工作的呢?所有这些都由位于 C_ChartFloatingRAD 类第 353 行的 CHARTEVENT_MOUSE_MOVE 事件来保证。如果 Mouse 指标不在图表上,则可以点击、编辑和修改 Chart Trade 指标的值。这是因为此类事件不一定与 Mouse 指标有关。但是,除非将 Mouse 指标放在图表上,否则您将无法发送买入、卖出、平仓指令或移动 Chart Trade 指标。
但等等,与 Chart Trade 交互时,是否需要在图表上显示 Mouse 指标?是的。虽然可以移除这种依赖关系,但如果以后要使用我将要谈到的其他方法,就会产生另外的问题。如果你想像我一样工作,就必须承受这些代价。因此,您必须充分了解该系统的工作原理,否则,您最终将无法信任该系统。
但让我们回到代码上来,了解我是如何实现系统同步的。要完全理解这一点,你需要了解指标源代码的作用,但如果你读过我在本文开头链接的文章,就应该不会有任何问题。因此,我们假设您已经了解了指标代码如何工作,这样我就可以专注于类的代码。
每次点击或移动鼠标,MetaTrader 5 都会生成一个事件。点击事件通常使用 CHARTEVENT_OBJECT_CLICK 来处理。但是,使用该处理函数无法保持同步。其中的原因解释起来相当复杂,它与执行操作的顺序有关。因此,为了在点击三个 Chart Trade 按钮(买入按钮、卖出按钮和平仓按钮)中的一个按钮时同时在 EA 中生成事件,我们采用了一些不同的方法。
因此,如果将 DispatchMessage 方法中的代码与前一篇文章中的相同代码进行比较,就会发现两者略有不同。不同之处在于处理上述按钮点击的方式。在以前的版本中,这种点击是在 CHARTEVENT_OBJECT_CLICK 事件中处理的,而现在我们是在 CHARTEVENT_MOUSE_MOVE 事件中处理的。由于代码本身会告诉我们哪个对象被点击了,所以我创建了一个新变量,使事情变得更有条理。该变量在第 340 行声明,其值在第 354 行设置。
现在请仔细听我接下来的解释。在第 376 至 378 行中,我们放置了按钮代码。因此,当 Mouse 指标向我们发送数据时,我们就可以向 EA 发送执行命令。但这里有一个小细节。如何告知 EA 按了哪个按钮?这很简单。我们将按钮的代码发送给 EA。这是在第 380 行完成的。现在,我们在第 381 行记录分时报价的数量,以生成一个唯一的数字。这对 EA 是必需的,你很快就会明白它到底是如何工作的。
因此,对于生成的每个事件,除了调用 DispatchMessage 函数准备将数据发送到缓冲区外,我们还将调用另一个函数。就是将数据实际放入缓冲区的那个函数。您可以从该类的第 279 行开始查看其代码。现在让我们看看第 281 行,该行有一个静态变量。它将存储 OnCalculate 事件传递的值。也就是说,我们需要保存 rates_total 的值,原因前面已经解释过了。请阅读开头列出的文章,了解其中的原因。这样,当 OnChartEvent 处理函数被调用时,我们就知道该把数据放在缓冲区的哪个位置了。
请注意,在第 284 行,我们执行了一项检查,以确保只有 Chart Trade 指标会向内存写入数据。此外,只有按下所需的一个按钮时,才会向内存中做记录。这一切都非常好。但是,如果 EA 无法解释 Chart Trade 指标发送给它的数据,这些都没有任何价值。为了了解 EA 如何解释数据,我们需要考虑一些其他代码,这些代码将尽可能基础。
使用测试 EA 交易
既然一切都需要测试,我们就必须确保测试的正确性,清楚地展示实际发生的情况。在这种情况下,为了测试 Chart Trade 指标、Mouse 指标和 EA 之间的交互作用,我们需要使用一个非常简单的系统。但与此同时,我们需要这个系统除了简单之外,还能以我们将来实际使用的方式运行。
根据这一标准,我们将使用以下代码:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. #property description "Demo version between interaction of Chart Trade and EA" 04. #property version "1.47" 05. #property link "https://www.mql5.com/es/articles/11760" 06. //+------------------------------------------------------------------+ 07. #include <Market Replay\Chart Trader\C_ChartFloatingRAD.mqh> 08. //+------------------------------------------------------------------+ 09. C_ChartFloatingRAD *chart = NULL; 10. //+------------------------------------------------------------------+ 11. int OnInit() 12. { 13. chart = new C_ChartFloatingRAD("Indicator Chart Trade"); 14. 15. return (CheckPointer(chart) != POINTER_INVALID ? INIT_SUCCEEDED : INIT_FAILED); 16. } 17. //+------------------------------------------------------------------+ 18. void OnDeinit(const int reason) 19. { 20. delete chart; 21. } 22. //+------------------------------------------------------------------+ 23. void OnTick() {} 24. //+------------------------------------------------------------------+ 25. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 26. { 27. static ulong st_uTime = 0; 28. C_ChartFloatingRAD::stData info; 29. 30. switch (id) 31. { 32. case CHARTEVENT_OBJECT_CLICK: 33. info = (*chart).GetDataBuffer(); 34. if (st_uTime != info.uCount.TickCount) 35. { 36. st_uTime = info.uCount.TickCount; 37. PrintFormat("%u -- %s [%s] %d : %f <> %f", info.uCount.TickCount, info.IsDayTrade ? "DT" : "SW", EnumToString(info.Msg), info.Leverage, info.PointsTake, info.PointsStop); 38. }else Print("IGNORADO..."); 39. break; 40. } 41. } 42. //+------------------------------------------------------------------+
测试 EA 交易的源代码
请注意,这段代码非常简单、紧凑,大部分内容不言自明。不过,有些读者,尤其是初学者,可能无法真正理解它的工作原理。不必为此难过,所有初学者都会遇到类似的困难。但是,只要你努力学习,投入时间,严于律己,不断进取,将来一定会成为一名优秀的专业人员。向困难屈服是不专业的。
现在让我们快速看看代码,因为只有几行,应该很容易理解。
上一主题中介绍的 C_ChartFloatingRAD 类中的所有代码在 EA 这里使用时都进行了总结。虽然我们在第 7 行完全包含了该类,但编译器并不完全这么认为。因此,在第 9 行,我们在全局声明了指针。它将用于访问 C_ChartFloatingRAD 类。这似乎令人困惑,但这只是因为你认为访问该类的方式与访问指标代码的方式相同。
事实上,这样做也是可行的,只是不太实用。原因是该类不打算在没有指标的情况下使用。C_Mouse 类也是如此,该类用于 Mouse 指标,不应在非指标代码中使用。我知道,很多人可能都想这么做。但你不应该这样做。因为所有预期的安全性、建模和性能都不是为了在原始代码之外的代码中使用类。也就是说,如果在 EA 中采用与指标相同的编码方法,实际上就可以达到无需使用 Chart Trade 指标的目的。这是事实。
但是,如果将指标代码转移到 EA 中,整个系统的稳定性和安全性可能会出现问题,因为 EA 的设计并不具备这种稳定性。万物皆有其位置。因此,当我们在第 13 行调用构造函数创建指针时,请注意我使用了与 Chart Trade 指标名称相同的名称。这样,指标和 EA 都能完成各自的工作 。每个组件都各就各位。如果其中一个出现问题,只需在图表上重新启动即可。
现在请注意,几乎所有的代码都可以归结为这一点。指定指标名称,然后在 OnChartEvent 事件处理程序中捕获 CHARTEVENT_OBJECT_CLICK 事件并分析发生了什么。这里有一点很重要。每次点击鼠标时,都会在对象上产生一个点击事件,即使你认为你没有点击任何重要的东西。原因是 Mouse 指标必须始终在图表上。该指标有一条水平线,这就是对象。因此,只要您点击鼠标,这条水平线就会产生一个事件。
但是,系统如何区分对这条线的点击和对另一个对象的点击呢?这是一个相当有趣的问题,很快将成为另一篇文章的主题。不过,在图表上使用不同对象时要注意这一点,主要是因为我们即将添加其他指标。但这是未来的事情。
问题在于第 33 行,对象点击事件将尝试读取 Chart Trade 指标缓冲区的内容。如果成功,我们将获得返回的数据。然后,在第 34 行,我们将检查这是否是与 Chart Trade 相关的事件,还是其他可以忽略的事件。
如果 Chart Trade 中确实产生了一个事件,而且该事件是由点击订单系统交互按钮引起的,我们就会像第 36 行那样更新静态变量的值。之后,我们将向终端输出一条信息,以便分析发生了什么。如果由于某种原因需要忽略该事件,我们就将执行第 38 行。
结论
视频 01 展示了该系统的实际运行情况。然而,亲眼目睹系统运行的机会是任何东西都无法替代的。因此,文章附件中包含了当前状态的系统。
视频 01 - 演示
本文由MetaQuotes Ltd译自葡萄牙语
原文地址: https://www.mql5.com/pt/articles/11760

