
リプレイシステムの開発(第61回):サービスの再生(II)
はじめに
前回の「リプレイシステムの開発(第60回):サービスの再生(I)」では、リプレイ/シミュレーターサービスがチャート上で新しいデータを生成できるよう、いくつかの調整をおこないました。最小限の変更でデータ生成を開始できるようになったものの、すぐに何か異常が起きていることに気付きました。大きな修正を加えたわけではないにもかかわらず、システム全体が著しく低速化し、大きな後退を強いられたように見えたのです。その結果、このシステムが実用に耐えないものになってしまったかのような印象を受けました。果たして本当にそうなのでしょうか。もしそうであれば、どのように対処すべきなのでしょうか。本記事では、すべてをオブジェクト指向プログラミングの原則に沿って設計しているという前提を忘れずに、これらの疑問に答えていきます。
実際にパフォーマンスは低下しましたが、コードの特定の側面を正しく理解し、適切に調整することで、その多くの問題は解消できます。本記事では、MetaEditorに搭載されているツールのいくつかを紹介し、コードの改良・最適化を効率よく進める方法を解説していきます。今思えば、このトピックは数回前の時点で取り上げるべきだったかもしれません。しかし、現在のように「なぜこのようなパフォーマンス低下が起きたのか」を深く理解することが重要になって初めて、その必要性を強く感じています。
最も明白かつ直接的な改善の実施
MetaTrader 5およびMQL5の仕組みに関する誤解や、十分な解説がおこなわれていないことが原因で、特定の実装において大きな障害が生じることがあります。とはいえ、コミュニティ内ではこうした知識を整理・共有することが可能であり、たとえそれが目の前の課題をすぐに解決するものではなくても、有用な知見となることは少なくありません。いずれにしても、正確で質の高い知識を持つことは常に価値があります。
今回は、そうした重要な知識のひとつについて説明していきます。私がこれから述べる内容の多くは、実際にMQL5を使ってみることで理解が深まり、MetaTrader 5を活用するうえで、他の多くの開発者が通常到達できないような高度な操作を可能にしてくれます。
おそらく、多くのMQL5プログラマーが最も誤解しているテーマのひとつが、グラフィカルオブジェクトの扱いです。これらはチャート上に存在するインジケーターやスクリプト、エキスパートアドバイザーからでしかアクセス・操作・調整できないと思われがちですが、これは真実からは程遠いものです。
これまで私たちは、カスタム資産チャートウィンドウに表示される要素と、MetaTrader 5内部で実行される処理の間に依存関係が生じないよう注意しながら作業してきました。しかし、MetaTrader 5内で実行されている複数のアプリケーション間で情報をやり取りする既存の手法に加えて、より洗練された(ただしリスクの高い)アプローチを導入することも理論的には可能です。誤解しないでいただきたいのは、こうした依存関係を導入すると、思いがけないトラブルを招く恐れがあるという点です。
確かにこの方法は、多くの場合に機能するかもしれませんが、システム全体を複雑かつ不安定な状態にし、貴重な時間を浪費してしまうリスクがあります。加えて、その後の機能拡張や改善が著しく困難になることも多いのです。私が提案している内容を理解するためには、まずシステム全体の仕組みを正しく把握することが不可欠です。
最初に押さえておくべきポイントは、コントロールインジケーターモジュールは、リプレイ/シミュレーションサービスが動作しているときにのみ、チャート上に表示されるということです。このモジュールを手動でチャートに追加してはいけません。手動で追加すると、これから実装するすべての仕組みに支障をきたします。
次に重要なのは、このモジュールによって作成されるすべてのグラフィカルオブジェクトは、厳格かつ一貫した命名規則に従う必要があるという点です。これを守らないと、後に深刻な不具合が発生する可能性があります。
加えて、コードの可読性を大きく向上させるための変更も同時に行っていきます。意味の明確でない記号やマーカーの使用は避けるべきです。ただし、これらの変更の目的はコードの実行速度を高めることではなく、個別の調整を理解しやすくするためのものです。この点は、後ほどソースコードを見ながら詳しく説明していきます。
まず最初の変更対象となるのは、C_Controls.mqhヘッダーファイルです。ただし、なぜこの変更が必要なのかを詳しく検討する前に、まずはこのファイルに加えられた実際の変更点を確認していきましょう。以下に、新しいコードを示します。
001. //+------------------------------------------------------------------+ 002. #property copyright "Daniel Jose" 003. //+------------------------------------------------------------------+ 004. #include "..\Auxiliar\C_DrawImage.mqh" 005. #include "..\Defines.mqh" 006. //+------------------------------------------------------------------+ 007. #define def_PathBMP "Images\\Market Replay\\Control\\" 008. #define def_ButtonPlay def_PathBMP + "Play.bmp" 009. #define def_ButtonPause def_PathBMP + "Pause.bmp" 010. #define def_ButtonLeft def_PathBMP + "Left.bmp" 011. #define def_ButtonLeftBlock def_PathBMP + "Left_Block.bmp" 012. #define def_ButtonRight def_PathBMP + "Right.bmp" 013. #define def_ButtonRightBlock def_PathBMP + "Right_Block.bmp" 014. #define def_ButtonPin def_PathBMP + "Pin.bmp" 015. #resource "\\" + def_ButtonPlay 016. #resource "\\" + def_ButtonPause 017. #resource "\\" + def_ButtonLeft 018. #resource "\\" + def_ButtonLeftBlock 019. #resource "\\" + def_ButtonRight 020. #resource "\\" + def_ButtonRightBlock 021. #resource "\\" + def_ButtonPin 022. //+------------------------------------------------------------------+ 023. #define def_ObjectCtrlName(A) "MarketReplayCTRL_" + (typename(A) == "enum eObjectControl" ? EnumToString((C_Controls::eObjectControl)(A)) : (string)(A)) 024. #define def_PosXObjects 120 025. //+------------------------------------------------------------------+ 026. #define def_SizeButtons 32 027. #define def_ColorFilter 0xFF00FF 028. //+------------------------------------------------------------------+ 029. #include "..\Auxiliar\C_Terminal.mqh" 030. #include "..\Auxiliar\C_Mouse.mqh" 031. //+------------------------------------------------------------------+ 032. class C_Controls : private C_Terminal 033. { 034. protected: 035. private : 036. //+------------------------------------------------------------------+ 037. enum eMatrixControl {eCtrlPosition, eCtrlStatus}; 038. enum eObjectControl {ePause, ePlay, eLeft, eRight, ePin, eNull, eTriState = (def_MaxPosSlider + 1)}; 039. //+------------------------------------------------------------------+ 040. struct st_00 041. { 042. string szBarSlider, 043. szBarSliderBlock; 044. ushort Minimal; 045. }m_Slider; 046. struct st_01 047. { 048. C_DrawImage *Btn; 049. bool state; 050. short x, y, w, h; 051. }m_Section[eObjectControl::eNull]; 052. C_Mouse *m_MousePtr; 053. //+------------------------------------------------------------------+ 054. inline void CreteBarSlider(short x, short size) 055. { 056. ObjectCreate(GetInfoTerminal().ID, m_Slider.szBarSlider = def_ObjectCtrlName("B1"), OBJ_RECTANGLE_LABEL, 0, 0, 0); 057. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_XDISTANCE, def_PosXObjects + x); 058. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_YDISTANCE, m_Section[ePin].y + 11); 059. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_XSIZE, size); 060. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_YSIZE, 9); 061. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_BGCOLOR, clrLightSkyBlue); 062. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_BORDER_COLOR, clrBlack); 063. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_WIDTH, 3); 064. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_BORDER_TYPE, BORDER_FLAT); 065. ObjectCreate(GetInfoTerminal().ID, m_Slider.szBarSliderBlock = def_ObjectCtrlName("B2"), OBJ_RECTANGLE_LABEL, 0, 0, 0); 066. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_XDISTANCE, def_PosXObjects + x); 067. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_YDISTANCE, m_Section[ePin].y + 6); 068. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_YSIZE, 19); 069. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_BGCOLOR, clrRosyBrown); 070. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_BORDER_TYPE, BORDER_RAISED); 071. } 072. //+------------------------------------------------------------------+ 073. void SetPlay(bool state) 074. { 075. if (m_Section[ePlay].Btn == NULL) 076. m_Section[ePlay].Btn = new C_DrawImage(GetInfoTerminal().ID, 0, def_ObjectCtrlName(ePlay), def_ColorFilter, "::" + def_ButtonPause, "::" + def_ButtonPlay); 077. m_Section[ePlay].Btn.Paint(m_Section[ePlay].x, m_Section[ePlay].y, m_Section[ePlay].w, m_Section[ePlay].h, 20, ((m_Section[ePlay].state = state) ? 1 : 0)); 078. if (!state) CreateCtrlSlider(); 079. } 080. //+------------------------------------------------------------------+ 081. void CreateCtrlSlider(void) 082. { 083. CreteBarSlider(77, 436); 084. m_Section[eLeft].Btn = new C_DrawImage(GetInfoTerminal().ID, 0, def_ObjectCtrlName(eLeft), def_ColorFilter, "::" + def_ButtonLeft, "::" + def_ButtonLeftBlock); 085. m_Section[eRight].Btn = new C_DrawImage(GetInfoTerminal().ID, 0, def_ObjectCtrlName(eRight), def_ColorFilter, "::" + def_ButtonRight, "::" + def_ButtonRightBlock); 086. m_Section[ePin].Btn = new C_DrawImage(GetInfoTerminal().ID, 0, def_ObjectCtrlName(ePin), def_ColorFilter, "::" + def_ButtonPin); 087. PositionPinSlider(m_Slider.Minimal); 088. } 089. //+------------------------------------------------------------------+ 090. inline void RemoveCtrlSlider(void) 091. { 092. ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, false); 093. for (eObjectControl c0 = ePlay + 1; c0 < eNull; c0++) 094. { 095. delete m_Section[c0].Btn; 096. m_Section[c0].Btn = NULL; 097. } 098. ObjectsDeleteAll(GetInfoTerminal().ID, def_ObjectCtrlName("B")); 099. ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, true); 100. } 101. //+------------------------------------------------------------------+ 102. inline void PositionPinSlider(ushort p) 103. { 104. int iL, iR; 105. 106. m_Section[ePin].x = (short)(p < m_Slider.Minimal ? m_Slider.Minimal : (p > def_MaxPosSlider ? def_MaxPosSlider : p)); 107. iL = (m_Section[ePin].x != m_Slider.Minimal ? 0 : 1); 108. iR = (m_Section[ePin].x < def_MaxPosSlider ? 0 : 1); 109. m_Section[ePin].x += def_PosXObjects; 110. m_Section[ePin].x += 95 - (def_SizeButtons / 2); 111. for (eObjectControl c0 = ePlay + 1; c0 < eNull; c0++) 112. m_Section[c0].Btn.Paint(m_Section[c0].x, m_Section[c0].y, m_Section[c0].w, m_Section[c0].h, 20, (c0 == eLeft ? iL : (c0 == eRight ? iR : 0))); 113. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_XSIZE, m_Slider.Minimal + 2); 114. } 115. //+------------------------------------------------------------------+ 116. inline eObjectControl CheckPositionMouseClick(short &x, short &y) 117. { 118. C_Mouse::st_Mouse InfoMouse; 119. 120. InfoMouse = (*m_MousePtr).GetInfoMouse(); 121. x = (short) InfoMouse.Position.X_Graphics; 122. y = (short) InfoMouse.Position.Y_Graphics; 123. for (eObjectControl c0 = ePlay; c0 < eNull; c0++) 124. { 125. if ((m_Section[c0].Btn != NULL) && (m_Section[c0].x <= x) && (m_Section[c0].y <= y) && ((m_Section[c0].x + m_Section[c0].w) >= x) && ((m_Section[c0].y + m_Section[c0].h) >= y)) 126. return c0; 127. } 128. 129. return eNull; 130. } 131. //+------------------------------------------------------------------+ 132. public : 133. //+------------------------------------------------------------------+ 134. C_Controls(const long Arg0, const string szShortName, C_Mouse *MousePtr) 135. :C_Terminal(Arg0), 136. m_MousePtr(MousePtr) 137. { 138. if ((!IndicatorCheckPass(szShortName)) || (CheckPointer(m_MousePtr) == POINTER_INVALID)) SetUserError(C_Terminal::ERR_Unknown); 139. if (_LastError != ERR_SUCCESS) return; 140. ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, false); 141. ObjectsDeleteAll(GetInfoTerminal().ID, def_ObjectCtrlName("")); 142. ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, true); 143. for (eObjectControl c0 = ePlay; c0 < eNull; c0++) 144. { 145. m_Section[c0].h = m_Section[c0].w = def_SizeButtons; 146. m_Section[c0].y = 25; 147. m_Section[c0].Btn = NULL; 148. } 149. m_Section[ePlay].x = def_PosXObjects; 150. m_Section[eLeft].x = m_Section[ePlay].x + 47; 151. m_Section[eRight].x = m_Section[ePlay].x + 511; 152. m_Slider.Minimal = eTriState; 153. } 154. //+------------------------------------------------------------------+ 155. ~C_Controls() 156. { 157. for (eObjectControl c0 = ePlay; c0 < eNull; c0++) delete m_Section[c0].Btn; 158. ObjectsDeleteAll(GetInfoTerminal().ID, def_ObjectCtrlName("")); 159. delete m_MousePtr; 160. } 161. //+------------------------------------------------------------------+ 162. void SetBuffer(const int rates_total, double &Buff[]) 163. { 164. uCast_Double info; 165. 166. info._16b[eCtrlPosition] = m_Slider.Minimal; 167. info._16b[eCtrlStatus] = (ushort)(m_Slider.Minimal > def_MaxPosSlider ? m_Slider.Minimal : (m_Section[ePlay].state ? ePlay : ePause));//SHORT_MAX : SHORT_MIN); 168. if (rates_total > 0) 169. Buff[rates_total - 1] = info.dValue; 170. } 171. //+------------------------------------------------------------------+ 172. void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam) 173. { 174. short x, y; 175. static ushort iPinPosX = 0; 176. static short six = -1, sps; 177. uCast_Double info; 178. 179. switch (id) 180. { 181. case (CHARTEVENT_CUSTOM + evCtrlReplayInit): 182. info.dValue = dparam; 183. if ((info._8b[7] != 'D') || (info._8b[6] != 'M')) break; 184. x = (short) info._16b[eCtrlPosition]; 185. iPinPosX = m_Slider.Minimal = (info._16b[eCtrlPosition] > def_MaxPosSlider ? def_MaxPosSlider : (info._16b[eCtrlPosition] < iPinPosX ? iPinPosX : info._16b[eCtrlPosition])); 186. SetPlay((eObjectControl)(info._16b[eCtrlStatus]) == ePlay); 187. break; 188. case CHARTEVENT_OBJECT_DELETE: 189. if (StringSubstr(sparam, 0, StringLen(def_ObjectCtrlName(""))) == def_ObjectCtrlName("")) 190. { 191. if (sparam == def_ObjectCtrlName(ePlay)) 192. { 193. delete m_Section[ePlay].Btn; 194. m_Section[ePlay].Btn = NULL; 195. SetPlay(m_Section[ePlay].state); 196. }else 197. { 198. RemoveCtrlSlider(); 199. CreateCtrlSlider(); 200. } 201. } 202. break; 203. case CHARTEVENT_MOUSE_MOVE: 204. if ((*m_MousePtr).CheckClick(C_Mouse::eClickLeft)) switch (CheckPositionMouseClick(x, y)) 205. { 206. case ePlay: 207. SetPlay(!m_Section[ePlay].state); 208. if (m_Section[ePlay].state) 209. { 210. RemoveCtrlSlider(); 211. m_Slider.Minimal = iPinPosX; 212. }else CreateCtrlSlider(); 213. break; 214. case eLeft: 215. PositionPinSlider(iPinPosX = (iPinPosX > m_Slider.Minimal ? iPinPosX - 1 : m_Slider.Minimal)); 216. break; 217. case eRight: 218. PositionPinSlider(iPinPosX = (iPinPosX < def_MaxPosSlider ? iPinPosX + 1 : def_MaxPosSlider)); 219. break; 220. case ePin: 221. if (six == -1) 222. { 223. six = x; 224. sps = (short)iPinPosX; 225. ChartSetInteger(GetInfoTerminal().ID, CHART_MOUSE_SCROLL, false); 226. } 227. iPinPosX = sps + x - six; 228. PositionPinSlider(iPinPosX = (iPinPosX < m_Slider.Minimal ? m_Slider.Minimal : (iPinPosX > def_MaxPosSlider ? def_MaxPosSlider : iPinPosX))); 229. break; 230. }else if (six > 0) 231. { 232. six = -1; 233. ChartSetInteger(GetInfoTerminal().ID, CHART_MOUSE_SCROLL, true); 234. } 235. break; 236. } 237. ChartRedraw(GetInfoTerminal().ID); 238. } 239. //+------------------------------------------------------------------+ 240. }; 241. //+------------------------------------------------------------------+ 242. #undef def_PosXObjects 243. #undef def_ButtonPlay 244. #undef def_ButtonPause 245. #undef def_ButtonLeft 246. #undef def_ButtonRight 247. #undef def_ButtonPin 248. #undef def_PathBMP 249. //+------------------------------------------------------------------+
C_Controls.mqhファイルのソースコード
コントロールオブジェクトの厳格な形式と宣言の簡易性を両立させる方法は、行23に定義されています。この行は一見すると非常に複雑に見えるかもしれませんが、その独特な構文に惑わされないでください。もし疑問があれば、その動作を個別にテストしてみてください。
ここで、重要な点に注目してください。行37と38には2つの列挙型が定義されています。行37の列挙型は、以前のバージョンには存在していなかったもので、バッファ内のデータに簡単にアクセスできるようにするために新たに追加されました。この仕組みは、行162にあるSetBufferプロシージャを見れば理解できるでしょう。同様の考え方がメッセージ処理の手順にも適用されていますが、こちらは若干異なる実装です。行182〜186を確認してみてください。特に行184に注目してください。この行は元のコードから削除された箇所です。
列挙型に関して話を戻すと、行38の列挙は以前のバージョンから変更されています。これは、コードの可読性を高めるための修正です。たとえば、行44で使用されている変数は、もともとは符号付き型でしたが、現在は符号なし型に変更されています。この変更により、行152のような微細な調整や、行186のような処理も可能になります。
このような一連の改良は、従来とは少し異なる設計アプローチを導入することで、コードの可読性と保守性を高めることを目的としています。
では、これから何を行うのかを具体的に見ていきましょう。これらの変更は、結果的にわずかながらCPUサイクルの節約にもつながる可能性があります。ただし、その前に重要な仕組みを理解しておく必要があります。行77では、グラフィカルオブジェクトに表示される画像を変更するリクエストをおこなっています。このオブジェクトは、「再生中」か「一時停止中」かを示すボタンです。本来であれば、サービスはコントロールインジケーターのバッファを常に監視していますが、別の方法で再生/一時停止の状態を判定することも可能です。そしてその方法は、まさに行77で操作されているオブジェクトに直接関係しています。
ボタンのステータスに素早くアクセスする
前のセクションでも述べたとおり、これらの単純な変更だけでは、実装を正当化するほどのパフォーマンス向上は得られません。しかし、サービスが最もパフォーマンスを必要とする箇所を分析すると、話は変わってきます。
前回の記事では、この最適化が必要なポイントを示しました。思い出してください。重要なポイントはLoopEventOnTimeにあります。この関数は、定期的に別の関数を呼び出して、コントロールインジケーターボタンの状態を確認し、現在が「一時停止」モードなのか「再生」モードなのかを判断しています。
この判定は、当初はコントロールインジケーターのバッファに格納されたデータを参照することでおこなわれていました。しかし、やや洗練された(ただし複雑さが増す)別のアプローチとして、コントロールオブジェクト自体を直接調べる方法が存在します。ここで重要なのは、コントロールオブジェクトはOBJ_BITMAP_LABELであるという点です。これは、内部に2つの状態を持つ特定の変数を含むオブジェクト型であり、その値を確認することで状態を把握できます。
チャート上に表示されるOBJ_BITMAP_LABELオブジェクト内の特定の変数の値を参照することで、再生モードか一時停止モードかを判断する際に、バッファの読み取り処理をスキップすることが可能になります。
ただし、C_DrawImage.mqhヘッダーファイルを確認しても、OBJ_BITMAP_LABELオブジェクト内で必要となる変数に対する変更はまだ加えられていません。これは、サービス側に変更を加える前の段階でも同様です。しかし、C_Controls.mqhファイルを分析すると、行77においてオブジェクトの更新要求がおこなわれていることが確認できます。これが、今後の実装に向けて必要な変更を加えるための出発点となります。理論上、これにより呼び出しごとにいくらかのCPUサイクルを節約できるはずです。
これらの変更はごく小規模なため、ここではヘッダーファイル全体は掲載しません。代わりに、C_DrawImage.mqhファイルを開き、以下のコードスニペットのように内容を修正してください。
174. //+------------------------------------------------------------------+ 175. void Paint(const int x, const int y, const int w, const int h, const uchar cView, const int what) 176. { 177. 178. if ((m_szRecName == NULL) || (what < 0) || (what >= def_MaxImages)) return; 179. ReSizeImage(w, h, cView, what); 180. ObjectSetInteger(GetInfoTerminal().ID, m_szObjName, OBJPROP_XDISTANCE, x); 181. ObjectSetInteger(GetInfoTerminal().ID, m_szObjName, OBJPROP_YDISTANCE, y); 182. if (ResourceCreate(m_szRecName, m_Pixels, w, h, 0, 0, 0, COLOR_FORMAT_ARGB_NORMALIZE)) 183. { 184. ObjectSetString(GetInfoTerminal().ID, m_szObjName, OBJPROP_BMPFILE, m_szRecName); 185. ObjectSetString(GetInfoTerminal().ID, m_szObjName, OBJPROP_BMPFILE, what, m_szRecName); 186. ObjectSetInteger(GetInfoTerminal().ID, m_szObjName, OBJPROP_STATE, what == 1); 187. ChartRedraw(GetInfoTerminal().ID); 188. } 189. } 190. //+------------------------------------------------------------------+
C_DrawImage.mqhソースコードスニペット
行184にはイメージインデックスを指定するパラメータが含まれているため、行185に置き換えられていることに注意してください。ただし、私たちが本当に注目すべきなのは行186です。ここでは、OBJ_BITMAP_LABELオブジェクトの状態を表す変数が更新されています。この変更により、オブジェクトのOBJPROP_STATEプロパティが現在の状態を直接反映するようになります。状態は「再生中」と「一時停止中」の2つしかないことを思い出してください。
次に確認すべきは、C_Replay.mqhヘッダーファイル内のコードです。ここでは、サービスがOBJ_BITMAP_LABELオブジェクトに直接アクセスし、その状態から「再生」モードか「一時停止」モードかを判断することができるようになります。
この動作をサービスが正しく理解し、反映するためには、まず新しい変更点を追加する必要があります。最初の変更は以下のようになります。
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #include "C_ConfigService.mqh" 05. #include "C_Controls.mqh" 06. //+------------------------------------------------------------------+ 07. #define def_IndicatorControl "Indicators\\Market Replay.ex5" 08. #resource "\\" + def_IndicatorControl 09. //+------------------------------------------------------------------+ 10. #define def_CheckLoopService ((!_StopFlag) && (ChartSymbol(m_Infos.IdReplay) != "")) 11. //+------------------------------------------------------------------+ 12. #define def_ShortNameIndControl "Market Replay Control" 13. //+------------------------------------------------------------------+ 14. class C_Replay : public C_ConfigService 15. { 16. private : 17. struct st00 18. { 19. C_Controls::eObjectControl Mode; 20. uCast_Double Memory; 21. ushort Position; 22. int Handle; 23. }m_IndControl;
C_Replay.mqhソースコードスニペット
まず、行5に注目してください。ここでは、コントロールインジケーターのヘッダーファイルへの参照が追加されています。現時点では、コントロールクラスを直接使用する予定はありませんが、クラスによって生成されるオブジェクト名などの定義にアクセスする必要があります。焦らず大丈夫です、この点についてはすぐに詳しく説明します。
同じコードブロック内にも、他にいくつかの重要な変更が加えられています。行19では、変数の型が変更されています。これは主にコードの可読性を向上させるためのものです。さらに、行20には新しい変数が追加されました。これは、コントロールインジケーターバッファから取得した特定の値を保持するために使用されます。ただし、この変数は後に示すように、意図通りには利用されない場面もあります。これらの準備が完了したら、次におこなうべきはC_Replayクラスのコンストラクタの修正です。以下に、その変更された部分を示します。
131. //+------------------------------------------------------------------+ 132. C_Replay() 133. :C_ConfigService() 134. { 135. Print("************** Market Replay Service **************"); 136. srand(GetTickCount()); 137. SymbolSelect(def_SymbolReplay, false); 138. CustomSymbolDelete(def_SymbolReplay); 139. CustomSymbolCreate(def_SymbolReplay, StringFormat("Custom\\%s", def_SymbolReplay)); 140. CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE, 0); 141. CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_VALUE, 0); 142. CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_VOLUME_STEP, 0); 143. CustomSymbolSetString(def_SymbolReplay, SYMBOL_DESCRIPTION, "Symbol for replay / simulation"); 144. CustomSymbolSetInteger(def_SymbolReplay, SYMBOL_DIGITS, 8); 145. SymbolSelect(def_SymbolReplay, true); 146. m_Infos.CountReplay = 0; 147. m_IndControl.Handle = INVALID_HANDLE; 148. m_IndControl.Mode = C_Controls::ePause; 149. m_IndControl.Position = 0; 150. m_IndControl.Memory._16b[C_Controls::eCtrlPosition] = C_Controls::eTriState; 151. } 152. //+------------------------------------------------------------------+
C_Replay.mqhソースコードスニペット
m_IndControl構造体の値がどのように初期化されるかに注意してください。この初期化処理の方法を理解することはもちろんのこと、なぜその特定の値が使用されているのかを理解することが最も重要です。現時点ではその理由がはっきりしないかもしれませんが、まもなく明らかになるでしょう。この仕組みの背後にあるアイデアは、コントロールインジケーターモジュールによって作成・管理されている、チャート上のオブジェクト(特にOBJ_BITMAP_LABEL)にアクセスすることです。
この機能を本格的に活用し、サービスから直接、グラフ上のOBJ_BITMAP_LABELオブジェクトにアクセスするには、C_Replayクラス内にすでに存在しているUpdateIndicatorControl関数のコードを少しだけ修正する必要があります。以下に、その変更を含むコードスニペットを示します。
34. //+------------------------------------------------------------------+ 35. inline void UpdateIndicatorControl(void) 36. { 37. static bool bTest = false; 38. double Buff[]; 39. 40. if (m_IndControl.Handle == INVALID_HANDLE) return; 41. if (m_IndControl.Memory._16b[C_Controls::eCtrlPosition] == m_IndControl.Position) 42. { 43. if (bTest) 44. m_IndControl.Mode = (ObjectGetInteger(m_Infos.IdReplay, def_ObjectCtrlName((C_Controls::eObjectControl)C_Controls::ePlay), OBJPROP_STATE) == 1 ? C_Controls::ePause : C_Controls::ePlay); 45. else 46. { 47. if (CopyBuffer(m_IndControl.Handle, 0, 0, 1, Buff) == 1) 48. m_IndControl.Memory.dValue = Buff[0]; 49. if ((C_Controls::eObjectControl)m_IndControl.Memory._16b[C_Controls::eCtrlStatus] != C_Controls::eTriState) 50. if (bTest = ((m_IndControl.Mode = (C_Controls::eObjectControl)m_IndControl.Memory._16b[C_Controls::eCtrlStatus]) == C_Controls::ePlay)) 51. m_IndControl.Position = m_IndControl.Memory._16b[C_Controls::eCtrlPosition]; 52. } 53. }else 54. { 55. m_IndControl.Memory._16b[C_Controls::eCtrlPosition] = m_IndControl.Position; 56. m_IndControl.Memory._16b[C_Controls::eCtrlStatus] = (ushort)m_IndControl.Mode; 57. m_IndControl.Memory._8b[7] = 'D'; 58. m_IndControl.Memory._8b[6] = 'M'; 59. EventChartCustom(m_Infos.IdReplay, evCtrlReplayInit, 0, m_IndControl.Memory.dValue, ""); 60. bTest = false; 61. } 62. } 63. //+------------------------------------------------------------------+
C_Replay.mqhソースコードスニペット
このコードが以前確認したものと大きく異なることに気付いたかもしれません。これらの変更の主な理由は、コントロールインジケーターモジュールに関連する要素にアクセスするための、安全で構造化されたアプローチを実装することです。
このコードフラグメントがどのように機能するかを完全に理解するには、システムフローの3つの主要なポイントを思い出すことが重要です。
1)m_IndControlの値はコンストラクタで初期化されます。 2)このロジックを最初に呼び出す関数は、コントロールインジケーターモジュールを初期化します。 3)ループプロシージャは定期的にコントロールボタンの状態をチェックします。
これらのステップは順番通りに実行されますが、3番目のステップが最もパフォーマンスに影響を与えやすい部分です。なぜなら、このステップはチャートに新しいティックを挿入し、コントロールインジケーターの状態を継続的に監視する役割を担っているからです。チャートオブジェクトから直接データを読み取る機能があることで、バッファにアクセスしたり、特定の場合にのみコントロールインジケーターにイベントを送信したりするのではなく、リプレイ/シミュレーションサービスの重要なフェーズにおいて、UpdateIndicatorControlプロシージャがパフォーマンス低下を引き起こすのを防ぐ可能性があります。
このフラグメントがどのように機能するかを見てみましょう。まず、行40で、有効なハンドラがあるかどうかを確認します。ある場合、プロセスは続行されます。次のステップは、メモリ値が位置と一致するかどうかを確認することです。これは行41で実行されます。一致する場合は、行43でstatic変数がtrueかどうかを確認します。trueの場合は、オブジェクトアクセス関数を使用して、OBJ_BITMAP_LABELの現在の値を取得します。これがどのようにおこなわれるかに細心の注意を払ってください。C_Controls.mqhヘッダーファイルの要素を参照しているため、少し不自然に思えるかもしれませんが、実際にオブジェクトへのアクセスがおこなわれています。
static変数がfalseの場合、わずかに遅いデータ読み取りを実行しても問題がないことを示しています。この場合、コントロールインジケーターバッファからデータを取得します。重要な注意点:これは、バッファの読み取りが本質的に遅いという意味ではなく、関係する操作数を比較したときに、グラフィカルオブジェクトのプロパティを直接読み取る方がタスクとしては単純であるということです。
バッファが読み取られると、行49ではTriStateモードになっていないかどうかがチェックされます。この条件が満たされた場合、再生モードかどうかを判断する前に行50で一連の操作を実行し、静的変数をtrueまたはfalseに設定します。これらの操作は実際には変数への値の代入ですが、構文上は少し複雑に見えるように構成されています。ただし、これはコンパイラにとって問題はなく、値は期待どおりに代入されるため、このように記述しても構いません。行50がtrueと評価された場合、行51でバッファの前の値を内部の位置変数に保存します。
この一連の操作は、ユーザーがスライダーを操作して、リプレイ/シミュレーターの開始位置を変更する場合にのみ発生します。つまり、通常の再生モードではこのコードは実行されません。しかし、一時停止モードから再生モードへ移行する際にはこのコードがトリガーされ、これは後の処理において重要になります。
行41の条件がfalseと評価された場合、行55から60までの命令が実行され、コントロールインジケーターモジュールを更新するためのカスタムイベントがトリガーされます。今後、システムはこのように動作するようになります。
オブジェクトをチャートから直接読み取ることで、必ずしもパフォーマンスが大幅に向上するとは限りません。ただし、チャートオブジェクトをより効率的に操作したい開発者にとっては、新たな可能性が広がります。このアプローチを使用することで、MetaTrader 5プラットフォームに負荷をかけずに、より洗練されたツールの開発が可能となり、オブジェクト操作のためだけに不要なインジケーターをチャートに表示する必要がなくなります。
真のパフォーマンス向上の実現
これまでに数々の改善点を説明してきましたが、それらはリプレイ/シミュレーションサービスにおいて本質的なパフォーマンス向上をもたらしていません。少なくとも、目に見える大きな変化は確認されていません。しかし、これらの変更はコードの一部セクションをより効率的にし、全体の構造を整理する助けになります。中でも最も大きな利点は、コードの可読性が向上することです。これは、C_Controls.mqhファイルで定義された内容がC_Replay.mqhヘッダーファイル内で効果的に利用されていることによるものです。コード全体をまだ確認していないとしても、おそらくどこを変更すればC_Replayクラスの読みやすさを高められるかは予想できるでしょう。
ここからは、実際にパフォーマンスを改善する修正について取り上げます。目標は、1分足を期待された周期で確実に生成できるよう、機能を回復させることです。
最初にこの変更案を見ると、非常に奇妙に感じたり、直感に反しているように思えるかもしれません。しかし、信じてください。ご自身で試していただければわかります。この単純な変更だけで、驚くほどのパフォーマンス向上が得られます。実際にその動作を確認するために、C_Replay.mqhヘッダーファイルの完全なコードを見ていきましょう。以下がその全体像です。行番号はこれまでのコードスニペットとは若干異なりますが、それに気を取られる必要はありません。コードの中身をしっかり確認していきましょう。
MetaQuotes Ltdによりポルトガル語から翻訳されました。
元の記事: https://www.mql5.com/pt/articles/12121





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