リプレイシステムの開発(第45回):Chart Tradeプロジェクト(IV)
はじめに
前回の「リプレイシステムの開発(第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/ja/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となります。2点目は38行目です。ここで、エラーはある程度許容範囲であることが判明するかもしれないので、定数値をリセットするための引数はtrueです。したがって、端末に表示されるメッセージをモニターして、何が起こっているかを追うことが重要です。
もう1つ、45行目にも興味深い点があります。これは安全対策です。C_ChartFloatingRADクラスのコードを説明する文脈で、より理解を深めることができるでしょう。基本的には、Chart Trade指標の機能を何とか維持する必要があるためです。チャートの更新呼び出しを使っていることに注意してください。このイベントは、チャートの時間枠を変更するたびに発生します。他のことに加えて、私たちの最大の問題は時間枠の変更であることを指摘しておきたいです。
時間枠を切り替えると、すべての指標がチャートから削除され、再度起動します。この時点で、チャート上で直接編集したデータは失われます。このデータ損失を防ぐ方法はいくつかあります。そのうちの1つが、これから使うものです。指標のソースコードについてこれ以上言うことはありません。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行目までが、テンプレートで探すパラメータを定義しています。この点については、前回の記事ですでに説明しました。しかし、ここではテンプレートで定義されているオブジェクトのプロパティを探すことにします。このようなプロパティは、指標で作成されるオブジェクトがテンプレートで意図されたのと同じように動作するために必要です。
2つ目のケースでは、テンプレートから情報を探していない場合、そのような値をローカルに保存します。これにより、その後のコード実行が大幅にスピードアップします。このデータは62行目と64行目に保存され、後で簡単にアクセスできます。
次に68行目をご覧ください。ここからが面白くなります。なぜなら、これから調整される別の情報があるからです。以前は、指標に表示されるすべてのことは、ユーザーが指標をチャート上に配置した瞬間に起こり、何もする必要はありませんでした。しかし、指標と直接対話できるようになった今、データがある程度代表的なものであることを確認する必要があります。
このため、82行目に新しいマクロを追加しました。このマクロの存在そのものが、古い関数がもはや存在しないことを意味します。このマクロ以外にも、90行目の外観など、コードにいくつかの変更が加えられていることに気付くかもしれません。ここでは上記の手順を使用します。また、パラメータを固定するよう指標に指示していることにも注意してください。これは一度だけ、指標がスタートする瞬間におこなわれます。詳しく言えば、この状況には2つ目の問題があり、この記事の後半で触れることにします。
その後、92行目から94行目にかけて、Chart Trade指標に表示される値を設定調整します。以前はクラスのコンストラクタでおこなわれていましたが、ユーザーとの対話はありませんでした。このような相互作用があるので、値が代表的なものであることを確認する必要があります。そのため、テンプレート更新時にここで値を設定します。
OBJ_CHARTだけがチャートに表示されるので、オブジェクトの値を直接編集することはありません。そこで、テンプレートが更新され、チャートに表示されるようにする必要があります。そのためには、この時点で正確にアップデートがおこなわれなければなりません。
残りの行程については、前回「リプレイシステムの開発(第44回):Chart Tradeプロジェクト(III)」ですでに説明しています。さて、105行目では、以前にはなかったことをしています。この行では、最大化/最小化ボタンをクリックしてドラッグすることでフローティングウィンドウを移動できてしまう小さなバグを修正しました。この行でこれを削除し、その直後の106行目でテンプレートから必要な値を取得しています。現在、この呼び出しはfalseと報告されていますのでご注意ください。こうすることで、将来の使用のために正しい場所に値が保存されます。
108行目に興味深いものがあります。フローティングウィンドウに最大化または最小化の機能を持たせているのです。以前は、これはコード内の別の場所でおこなわれていましたが、実用上の理由から、このコントロールをここに置くことにしました。ウィンドウに関連するものがすべて同じ場所にあるため、モデリング作業が非常に簡単になります。
同様に、109行目と110行目では、フローティングウィンドウをより便利に扱うことができます。ユーザーは、フローティングウィンドウがその状態によって位置を変えることを望むことがよくあります。つまり、最大化されたときにはある位置にあり、最小化されたときには別の位置にあります。そして、109行目と110行目はまさにそれを実行します。つまり、フローティングウィンドウが最大化されたか最小化されたかに応じて、フローティングウィンドウを最後の位置に配置します。
112行目は前回の記事ですでに説明したので、114行目と115行目を見てみましょう。114行目と115行目は、以前は別の関数にありました。そこで、114行目でOBJ_CHARTオブジェクトですでに修正されたテンプレートを実行します。つまり、115行目が実行されると、チャート上にテンプレートが表示され、更新されることになります。重要なのは、このサブルーチンに全手順が集約されていることです。そのため、情報がユーザーに正しく表示されるように、追加データの通信を心配する必要はありません。
このシステムを別のクラスに配置することは可能ですが、現時点では可能性は低いです。Chart Tradeでしか使わないので、ここに残しておきます。他のものをテンプレート化することにしたので、クラスで実装するのは面白いかもしれません。しかし、今のところ、コードはそのままにしておきます。
次に少し違うものがあります。137行目には、ちょっと珍しいタイプのコードが含まれています。このようなコードは、同じプロシージャを異なる型で使用する場合によく見られます。これは何を意味するのでしょうか。🤔まず理解するべきなのは、なぜ、double値を表示するサブルーチンを1つ作り、int値を表示するサブルーチンを1つ作り、文字列を表示するサブルーチンを3つ作るのかということです。基本的にサブルーチン内に表示されるコードは常に同じなので、1 つのサブルーチンを作成する方がはるかに簡単ではないでしょうか。唯一の違いは、一方の場合は値があるタイプで、もう一方の場合は別のタイプであるということです。まさに137行目がそうです。
でも待ってください。もし値を表現するのであれば、文字列として直接渡すことはできないのでしょうか。しかし、私たちが考慮しなかったことがあります。値を特定の方法で表現し、コード内の異なる箇所から呼び出す必要がある場合はどうするのかということです。そのために必要な作業について考えてみましょう。しかし、この方法であれば、値をそのままの型で渡し、コンパイラにサブルーチンを作らせ、望むように表現することができます。ビューを変更する場合、コードの1カ所だけを変更すればいいです。すべての設定はコンパイラがやってくれます。
仕事を減らし、生産量を増やします。
そうでなければ、仕事の量は増えるばかりです。私のアドバイス:可能な限り、コンパイラに仕事をさせることです。コードのメンテナンスがずっと簡単になり、生産性が飛躍的に向上することに気づくでしょう。
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指標を使い続けることができます。ここで1つ、実用的というより個人的な疑問が出てきますが、それについてはデータの保存方法を説明するときに話しましょう。
データが正常に読み込めたかどうかにかかわらず、180行目でChart Trade指標にリンクされているグローバル端末変数をすべて削除します。ただし、MetaTrader 5にはこのような変数がもっとあるかもしれないことに注意してください。どれを削除すべきかを知るために、クラスコードの最初に定義されたマクロを使用します。
では、クラスのコンストラクタに移りましょう。187行目から始まります。説明が必要なのは、192行目で起こる相互作用です。上記のプロシージャを呼び出します。失敗した場合は、194~200行目を実行し、Chart Trade指標のデフォルト値を生成します。しかし、指標の取り外しと再インストールのプロセスは、時間枠の変更により非常に短時間でおこなわれるため、プリセット値が使用される可能性は非常に低くなります。しかし、起こりうることなので、そのための準備をしておくことは役に立ちます。
以前とは異なり、デフォルト値が調整なしで初期化されることに注意してください。これは、このような調整がテンプレートを更新するプロシージャによっておこなわれるようになったためです。
では、214行目で何が起こるか見てみましょう。ここでChart Tradeの状態を一時的に保存します。なぜそうするのでしょうか。なぜこの状態をグローバル端末変数に保存するでしょうか。他に方法はないのでしょうか。順を追って見ていきましょう。
まず第一に、そうです。別のやり方もあります。基本的には、いくつかの方法が考えられます。問題は、どのように保存するかではなく、保存したデータをどのように復元するかです。グローバル端末変数を使う理由は、アクセスがはるかに簡単だからです。場合によっては、データを保存するよりも復元する方が難しいことを考慮し、データを直接テンプレートに入れることを真剣に考えました。実際、ポジションに関するデータを除けば、すでにそこにあるはずです。なぜなら、最大化されたウィンドウと最小化されたウィンドウには、それぞれ別のポジショニングがあるからです。
展開位置と折りたたみ位置の違いは、テンプレートを使いにくくしています。他の方法を使うこともできますが、いずれにせよ、これはシステムを不必要に複雑にし、労力に見合わないでしょう。繰り返しになりますが、データは常にテンプレートに存在しています。しかし、209行目が実行されると、テンプレートが削除され、データが消えてしまいます。最大化したウィンドウと最小化したウィンドウの位置を使い分けないとしても、209行目との関係で問題が生じるでしょう。
1つの解決策は、指標のソースコードにテンプレート削除の呼び出しを配置することです。そうすると、指標のコードは次のようになります。
43. void OnDeinit(const int reason) 44. { 45. if (reason != REASON_CHARTCHANGE) (*chart).RemoveTemplate(); 46. 47. delete chart; 48. }
このRemoveTemplate関数には、クラスコードの209行目に相当する呼び出しが1つ含まれています。これは(比較的うまく)機能するでしょうが、別の問題もあります。その1つは、指標がより深刻なエラーを返した場合、対応するファイルは削除されずにディスクに残るというものです。指標を再びチャートに配置しようとすると、データが正しくないため、指標が再び削除される可能性があります。これは、欠陥のあるファイルが削除されるまで続きます。
これらの理由やその他の理由から、私は端末グローバル変数を使うことを好みます。でも、直接は使っていないことに注意してください。変数を使うにはマクロを使用します。なぜでしょうか。その理由は、グローバル端末変数の寿命にあります。
216行目に出てくるマクロで何が起きているのか見てみましょう。まず、この変数を一時的な端末グローバル変数として作成しようとしていることに注目してください。つまり、MetaTrader 5端末を閉じると、変数も一緒に破棄されます。このようにして、Chart Trade指標の完全性を保証しています。
各グローバル端末変数には1つの値が格納されることに注意してください。これらの変数が実行される順番は重要ではなく、名前と値だけが本当に重要なのです。名前に64文字以上を含めることはできないことを覚えておきましょう。このため、マクロを使って名前を作成します。これにより、名前を作成する際に一定の利点が得られます。
テンプレートデータを保存するプロシージャには、特筆すべき点はありません。このプロシージャがなければ、時間枠が変わるたびに、指標内のデータの再設定に悩まされることになります。多くのユーザーが取引期間中に何度も時間枠を変更する傾向があることを考慮すると、Chart Trade指標の値を常に調整することは大きな問題でしょう。プログラミングとMetaTrader 5の機能を使えば、それを脇に置いて他のことに集中できます。そのためには、214行目の手順を使用します。
データをメモリーに保存する方法は他にもありますが、グラフィカルオブジェクトを扱うことになるので、今は詳しく触れません。これについてはまた別の機会に話しましょう。
この記事もあとわずかです。しかし、その前に考えなければならないことがあります。メッセージ処理関数です。これは237行目から始まりますが、多くの人が想像しているよりもずっとシンプルでフレンドリーです。しかし、実際にはマウス指標しか使わないのに、なぜこのメッセージ処理関数には4種類のイベントが含まれているのか不思議に思うかもしれません。
MetaTrader 5はイベント基本のプラットフォームであることをもう一度強調しておきます。したがって、このスタイルで仕事をする方法を理解する必要があります。前回の記事で、ロジックを単純化するために他のイベントを使うことができると述べました。コードは少々わかりにくい面もありますが、機能的には問題ありません。しかし、前回の記事で説明した、このコードに本当に存在させるべき確認のほとんどを残すことができます。これらの確認はMetaTrader 5によって実行されます。つまり、両方のクラスコードを比較すれば、こちらの方が確認項目が少ないことがわかるでしょう。なぜでしょうか。
MetaTrader 5がそれを実行するからです。Chart Tradeのオブジェクトクリックイベントは、マウスクリックだけでなく、オブジェクトクリックを分析するバージョンに置き換えられました。これによってコーディングがより簡単になります。こうすることで、より多くのイベントを、より読みやすいコードに含めることができます。315行目から343行目のコードを見れば、クリックに関することがわかります。これらの行では、テンプレートに存在するすべてのオブジェクトのクリックを処理します。買い、売り、クローズポジションボタンなど、まだ何の機能もついていないものまですべてです。
メッセージハンドラで注目すべきは、258行目のCHARTEVENT_CHART_CHANGEイベントです。端末の寸法が変更された場合、細かいことが1つあります。これが発生すると、MetaTrader 5はイベントをトリガーし、プログラムに通知します。このイベントはCHARTEVENT_CHART_CHANGEを介して処理されるため、フローティングウィンドウがチャート上に表示されたままかどうかを確認することができます。このイベントを処理しないと、ウィンドウは隠れたまま、指標はアクティブのままということが起こり得ます。この処理は最小化モードでも最大化モードでも同じなので、私はマクロを使って必要な調整をおこないます。そのため、それに応じてウィンドウの位置を変更する必要がある変更が発生した場合、265行目がそれをおこなってくれます。
もう1つ、CHARTEVENT_OBJECT_ENDEDITというイベントについても触れておく必要があります。この場合、OBJ_EDITオブジェクトが編集を終了したことをMetaTrader 5が検出するたびに、イベントがトリガーされます。こうすることで、テンプレート内で直接データを更新することができます。これは309行目でおこなわれます。ただし、この更新は必要に応じてデータを調整するものです。資産に対応しない値や数量を入力しようとすると、コードはその値を調整します。そうすることで、将来の問題を避けることができます。
結論
Chart Tradeの作成には多くの複雑さが伴いますが、今回のバージョンは旧バージョンに比べて大幅に安定性と拡張性が向上しています。前回紹介したコンセプトを踏襲しつつ、よりモジュール化されたシステムを目指しました。現在では、実際の市場やデモ口座に加え、市場をシミュレートして再生する機能も備えています。このためには、全く異なる方法でシステムを構築する必要があり、これが適切に行われなければ、多様なシステムや市場で同じ商品を使う際に大きな問題が発生する可能性があります。
Chart Trade指標は、現在のところ買い、売り、クローズポジションボタンの機能が欠けているため、完全には機能していませんが、コードのコア部分は正しく実装されています。これらのボタンを追加するためには、もう少し作業が必要です。現時点では、この指標はすでに期待に応えています。
記事に添付ファイルがないため、多くの人が不満を感じるかもしれませんが、理由があります。コードとシステムを見て、読み、理解することが重要です。多くの人が実際に記事を読まず、使用しているものを理解していないことに気づきました。これは危険であり、最適な使用方法ではありません。すべてのコードは公開され、記事に掲載されていますので、理解し、MetaEditorで編集することが求められます。これにより、コードを知らない人による使用は避けられます。
MetaQuotes Ltdによりポルトガル語から翻訳されました。
元の記事: https://www.mql5.com/pt/articles/11701
- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索