
リプレイシステムの開発(第75回):新しいChart Trade(II)
はじめに
前回の「リプレイシステムの開発(第74回):新しいChart Trade (I)」では、主にChart Tradeインジケーターのコードについて解説しました。Chart Tradeをプログラムする際に、なぜあるアプローチを選ぶのかという理由や、コードのいくつかの部分について説明しました。しかし、重要な部分、つまり中核となるコードについては触れませんでした。
このメインコードを以前紹介しなかった理由の1つは、コードから100行以上を削除していたためです。その変更点をわかりやすく示す方法を考える必要がありました。そして、今回紹介する方法が最適だと判断しました。
ここでは、Chart Tradeのメインコードを詳しく見ていきます。希望すれば、このコードをそのままエキスパートアドバイザー(EA)に組み込むこともできます。ただし、その場合はいくつかの調整が必要になります。本日はその調整方法についても説明します。なお、前回の記事で解説した「なぜコードをEAではなくインジケーターに置いているのか」という理由も忘れないでください。とはいえ、最終的にはあなたの目的に合った形で自由に利用できます。
それでは、さっそく始めましょう。
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を期待どおりに動作させています。さらに、この方法により、Chart Tradeは信頼性と安全性を備えて動作します。
オリジナル版では、構造体や列挙型(17行目にあるもの)など、一部がpublicとして公開されていました。ただし、変数がコード外に漏れないように注意が払われており、カプセル化はすでに主要な設計原則の1つでした。では、なぜそれらのpublic要素が現在は見えなくなっているのでしょうか。それは、今後はプロトコルを確立し、それを使用することで、C_ChartFloatingRADクラスがデータをデコードする必要をなくすという方針になったからです。この点については、記事の後半で触れます。
現在必要な変数はすべて、18行目から始まる構造体内で宣言されています。ここで使用されている型に注目してください。ほとんどの場合、32ビット型、つまりINT型は使用していません。なぜでしょうか。画面座標用の変数でさえ、16ビット型(SHORTグループ)を使用しています。
多くのプログラマーがデフォルトでINTを使うのとは違い、ここではその必要性を感じません。画面解像度に関しては、16ビットで現代の技術には十分対応できます。最大65,535であれば、4K画面での描画には十分ですし、正直なところ、8K画面でも問題ありません。したがって、ここでINTを使う利点はありません。さらにSHORTを使えばデータ圧縮にも有利です。この点については後ほど説明します。
では、次に進みましょう。31行目には、最初の構造体内にもう1つ構造体が定義されています。この内部構造体は、グラフィカル要素の相対位置を保持しています。これらの要素は、Chart Tradeのインターフェイスでユーザーが操作する部分です。
コードの冒頭には2つのマクロが定義されています。今は気にしなくても構いませんが、場所だけは把握しておいてください。後で使います。そのうち1つ(8行目に定義)は、最初は少しわかりにくいかもしれませんが、すぐにその目的が理解できるでしょう。
さて、肝心な部分です。14行目では、C_Terminalクラスをprivate継承して機能を拡張し、Chart Tradeを構築しています。20行目から39行目までは、このクラスのprivate内部変数を宣言しています。ここまで準備が整ったところで、コードのレビューを始めましょう。シンプルにするため、コード内に現れる順に関数ごとに解析します。最初は41行目に宣言されているCreateWindowRAD手続きです。
この手続きは230行目から一度だけ呼び出されます。他では呼ばれません。その役割は非常にシンプルで、チャート上にOBJ_CHARTオブジェクトを作成することだけです。それだけです。では、なぜOBJ_CHARTオブジェクトを追加するのでしょうか。その理由は、こうすることで他のグラフィカルオブジェクトを手動で作成する必要がなくなるからです。コードでチャート上に描画することに慣れている人にとっては混乱するかもしれませんが、この理由についてはすでに過去の記事で説明済みです。本記事の最後に、それらの参考リンクを載せますので、より深く学びたい方は参照してください。この記事だけで十分理解できない場合は、それらの資料が役に立つでしょう。
現在の作業にとって重要なのは52行目です。ここで、OBJ_CHARTオブジェクトが作成された際にMetaTrader 5から返されるIDを格納しています。このIDは後に重要な役割を果たします。では次に、55行目から始まるAdjustEditabled手続きに進みましょう。この手続きは、少し奇妙に聞こえるかもしれませんが、OBJ_CHART内に表示されるオブジェクトの状態を調整・取得する役割を持っています。しかし、その動作を理解するには、AdjustTemplate手続きを詳しく見る必要があります。なぜなら、AdjustEditabledはその中で使用されているからです。72行目から133行目までの処理を見ていきましょう。ここはクラスの中でもっとも複雑な部分であり、「魔法」が本当に起きている部分です。
74行目では、この手続き内だけで使うマクロを定義しています。そして最後に132行目でそのマクロをundefineしており、他では使えないようになっています。78行目では必要なアクションを判定しています。Chart Tradeを構築している最中であれば、この条件が真となり、一連の処理が実行されます。すでにChart Tradeが存在する場合は、代わりに89行目を実行します。テンプレート内でおこなわれる処理の種類をより理解するためには、前回の記事を読み返すとよいでしょう。78行目の判定結果によって、80行目または89行目のどちらがテンプレート作成を開始するかが決まります。
さて、最初の難所です。テンプレートを作成している場合、81行目のループでオブジェクトの特定を準備します。では、このオブジェクトとは何でしょうか。これはChart Tradeに追加されるオブジェクトです。どこにあるのかはまだわかりませんが、いくつあるのかは17行目の列挙型によってわかっています。88行目では再びAdjustEditabledを呼び出し、58行目のチェックが通るようにします。このステップにより、テンプレートから追加データが得られます。
C_AdjustTemplateクラスがこれを正常に処理できれば、90行目でコードが続行します。失敗すれば、前回の記事で示したように、呼び出し元に処理が戻ります。ここまでで重要なのは、まだ大きな処理は何もしていないということです。90行目までは、C_AdjustTemplateにテンプレートファイルを開くよう依頼しただけです。それ以上のことはありません。
96~98行目ではいくつかの表示値を調整し、99~104行目で105行目の処理に備えてデータを準備します。そして、この105行目こそが本当の「魔法」が発生する瞬間です。これを理解するには、前回の記事でC_AdjustTemplateクラスのExecute関数を復習する必要があります。ここでおこなっているのは、テンプレートファイルに元々保存されている値を変更することです。ただし、元のファイルを直接変更しているわけではなく、そのコピーを操作しています。これが78行目のチェックが重要な理由です。この判定が、105行目でどのファイルを変更するかを決定します。
ここで起きていることを完全に理解できていないかもしれませんので、補足します。78~104行目まででおこなっているのは、C_AdjustTemplateがChart Tradeを含むファイルを編集できるようにデータを準備する作業です。後でChart Tradeに表示されるすべての要素は、99~104行目の間で定義されています。奇妙に聞こえるかもしれませんが、実際にこれで動作します。
ですので、このコード部分はしっかり読み込み、その仕組みを理解することを強くおすすめします。すべてのオブジェクトは、この手法を使って作成・管理されているのです。
さて、105行目までのすべてを理解できたと仮定しましょう。105行目が成功すればコードは続行しますが、失敗すると非常に珍しい処理が発生します。ここから先は特に注意して読んでください。
105行目の呼び出しが失敗した場合は、テンプレートを破棄してチャート取引を終了する必要があります。テンプレートの破棄は簡単で、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手続きから戻ります。テンプレートの変更が正常に実行できない場合は、インジケーターが即座に削除され、追加の問題は発生しません。これはシステムの中でも複雑な部分の1つです。しかし、まだカバーすべき内容が残っているので、まだ終わりではありません。
次に、テンプレート変更が成功した場合を考えましょう。この場合、111行目の新たなチェックに進みます。このチェックでは、クリック可能なオブジェクトの位置を取得します。この処理はテンプレート作成時にのみおこなわれます。113行目から始まるループがこの作業を担当し、クリック可能なオブジェクトの位置を取得します。そして120行目と121行目がこの処理を補完します。
次に、OBJ_CHARTオブジェクトをチャート上に配置します。これは123~125行目でおこなわれます。127行目でテンプレート操作を終了し、129行目でOBJ_CHARTに適切なテンプレートを割り当てます。そして130行目でオブジェクトの内容更新を強制します。これにより、OBJ_CHARTオブジェクト全体の構造が完成し、すべてのコンポーネントが表示され、Chart Tradeのインターフェイスが生成されます。
もし、なぜテンプレートを更新することでChart Tradeインターフェイスを手動でコーディングする必要がなくなるのかまだ理解できていない場合は、AdjustTemplate手続き全体を復習してください。これこそがChart Tradeを構成し、表示するための中核的な処理です。Chart Tradeに表示されるすべてのオブジェクトは、この手続き内で作成され、位置が決められ、調整されます。
コード内を探しても、ボタン、アイコン、画像などの明示的なオブジェクト宣言は見つかりません。なぜなら、そうしたものはコード内に存在しないからです。このコードがおこなっているのは、既存のオブジェクトを見つけ出し、それらをユーザーが操作できるようにすることです。これを従来のオブジェクト生成パターンとして理解しようとしないでください。ここで私がやっているのは全く逆のことです。まず望む外観を設計し、その後でコードにその概念を機能的にするよう指示しているのです。この考え方を理解しないと、ほぼ確実に混乱してしまうでしょう。
このシステムは非常に洗練されているため、レイアウト、構造、さらにはオブジェクトのフォントまで、コードを1行も書き換えることなく変更できます。必要なのはMetaTrader 5を開き、Chart Tradeのテンプレートを読み込み、視覚的な見た目を変更するだけです。残りはすべてAdjustTemplate手続きが処理します。
しかし当然ながら、このRAD (Rapid Application Development)コンセプトをサポートするためには、いくつかの追加手続きが必要です。次に見ていくのは、135行目にあるCheckMousePosition関数です。この関数は非常にシンプルで、マウスポインタがクリック可能または選択可能なオブジェクト上にあるかどうかを確認します。もしそうであれば、145行目で該当オブジェクトを返します。そうでなければ、147行目でマウスがインタラクティブ要素上にないことを報告します。
その後、特別なケースをサポートする2つの手続きがあります。これはAdjustTemplate手続き単体では必要な品質で処理できないケースです。その対象はOBJ_EDITオブジェクトで、ユーザーが任意のテキストを入力できるようにするものです。
150行目の手続きは、作成されている可能性のあるOBJ_EDITオブジェクトを削除します。一方、156行目に定義されている手続きは、ユーザーがChart Tradeインターフェイスに値を入力できるようにする単一のOBJ_EDITオブジェクトを作成します。
CreateObjectEditable手続きの宣言は少し奇妙に見えるかもしれませんが、特に変わった点はありません。ここではオーバーロードを導入しており、171行目で編集中の変数型に応じてOBJ_EDITオブジェクトを適応させられるようにしています。たとえば変数がdouble型であれば、小数点以下2桁で表示されます。それ以外の場合は文字列として扱われ、呼び出し元が構築したテキストがそのまま表示されます。
ここには特に難しい点はなく、MQL5プログラミングに慣れている人にとってはごく普通の範囲内です。グラフィカルオブジェクトの操作経験が少なくても問題なく理解できるでしょう。
では次に、175行目で宣言され210行目まで実装されているRestoreState関数に移ります。この手続きは単独では動作せず、244行目で始まるSaveState手続きのサポートが必要です。両者は通常のルールの外で動作し、メイン情報を一時的に保存します。これには、Chart Tradeインターフェイスの最後に確認された位置、より正確には時間軸変更前のOBJ_CHARTオブジェクトの位置が含まれます。
この情報を保存するには、グローバル端末変数を使用します。使うグローバル変数は1つだけで、これに値を保持します。しかし、もう1つ重要な点に注目してください。187行目で再びC_AdjustTemplateクラスを使用しています。なぜでしょうか。またテンプレートを変更しているのでしょうか。答えは「いいえ」です。今回は別の方法でテンプレートを利用しています。
AdjustTemplate手続き(72行目~)でテンプレート作成時に「項目を追加・削除できる」と言ったのを覚えていますか。まさにここでそれをおこなっています。既存の修正版テンプレートの値を使って、Chart Trade内のデータを復元するのです。なぜ必要かというと、OBJ_CHARTオブジェクトはテンプレートを読み込んでChart Tradeインターフェイスを構築しますが、クリック可能なオブジェクト、特に[買]ボタンや[売]Buyボタンといった成行注文ボタンへのユーザー操作に対応するためです。これらのボタンは後ほど説明するイベントを発生させます。イベント発生時の不要な遅延やデータ欠落を避けるため、Chart Trade再起動時に必要な値を事前に復元します。
この情報をテンプレートから正しく復元するため、まず187行目でテンプレートを開きます。成功した場合、188行目のチェックを経て190~194行目が実行されます。ここでは、取得するオブジェクト名と対応する位置情報を追加します。そして195行目で変換を実行します。今回はテンプレートを変更・追加するわけではないので、この行は単に必要なデータを取得します。これが成功したら、197~201行目でC_ChartFloatingRADクラスの必要なprivate変数を復元します。すべてを復元する必要はなく、今後のイベント処理に必要な主要な値だけを復元します。最後に204行目でテンプレートを閉じ、207行目で一時的なグローバル端末変数を削除します。
これらの変数は非常に短命で、通常は1秒未満しか存在しません。これは、OBJ_CHARTのチャート上での座標を時間軸変更前に一時的に保持するためだけに使われます。
この記事はすでにかなり情報量が多くなっていますが、最後に簡単に理解できる3つの手続きを確認して締めくくりましょう。
214行目の手続きはクラスコンストラクタです。これまで説明してきた内容を活用しており、ほぼ自明です。218行目では現在の状態を保存するかどうかをフラグで管理します。デフォルトでは保存されません。219行目でインジケーターの作成を試み、失敗した場合、呼び出し元に戻って終了します。220行目で前回の状態復元を試み、失敗した場合、ユーザーの入力値を使用します。230行目でOBJ_CHARTオブジェクトを作成し、231行目で最終調整をおこないます。
234行目から始まる手続きはクラスデストラクタです。さらに簡単です。237行目でOBJ_CHARTに関連するオブジェクトを削除します。これは不要な残骸でチャートが散らからないようにする重要なステップです。238行目で現在の状態を保存するかどうかを確認し、保存しない場合は239行目でテンプレートファイルをディスクから削除します。これにより、RestoreState関数(175~210行目)は機能せず、188行目のチェックが失敗し、値はユーザーが最後に入力したものに戻ります。
そして、244行目から始まるSaveState関数があります。これは246行目で定義され、257行目で削除される内部マクロを使用します。このマクロは必須ではありませんが、グローバル変数名が一時作成と値保存で2回使われるため、マクロを使うことでコードが整理されます。ここで重要なのは255行目で、この行が238行目のチェックを失敗させ、テンプレートファイルを保持するという点です。
最後に
260行目で宣言され、386行目まで実装されているDispatchMessage手続きについては、今回は説明しません。理由は単純で、この限られたスペースで扱うには複雑すぎるからです。この関数は、他のプログラムと連動して実際に動作しているところを見る方が理解しやすいでしょう。なぜなら、このプロシージャこそがユーザーにChart Tradeを操作させ、市場ポジションのオープンやクローズをおこなうためのイベントを生成しているからです。
このテーマは、より詳細かつ丁寧な説明に値します。そして、次回の記事を読むモチベーションを維持してもらうために、あえてこの関数の内部的な詳細は今回は伏せています。この部分については、次回の記事で余すところなく解説します。それまでの間に、C_ChartFloatingRADクラスのコードを別ファイルとして保存し、しっかり学習してください。この時点ですでに機能はします。次の記事では、260行目から386行目までのコードを順を追って見ていきます。
次の記事でまたお会いしましょう。それまでの間に、以下にMQL5でのRADプログラミングに関する参考記事を掲載しておきます。これらは私が過去に執筆したもので、今回示したChart Tradeの構築に使用されている概念について説明しています。オブジェクトを明示的に宣言することなく、チャート上に表示し、かつそれらと対話できる仕組みについて扱っています。ただし、ショートカットCTRL + Bを使ってオブジェクトウィンドウを開くと、表示されるのは1つのオブジェクトだけです。
添付ファイルには、このインジケーターを実際に動かし、動画で示した通りに操作するために必要なすべてのものが含まれています。
MQL5のRAD記事へのリンク
MetaQuotes Ltdによりポルトガル語から翻訳されました。
元の記事: https://www.mql5.com/pt/articles/12442
警告: これらの資料についてのすべての権利はMetaQuotes Ltd.が保有しています。これらの資料の全部または一部の複製や再プリントは禁じられています。
この記事はサイトのユーザーによって執筆されたものであり、著者の個人的な見解を反映しています。MetaQuotes Ltdは、提示された情報の正確性や、記載されているソリューション、戦略、または推奨事項の使用によって生じたいかなる結果についても責任を負いません。





- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索