
リプレイシステムの開発(第56回):モジュールの適応
はじめに
前回の記事「リプレイシステムの開発(第55回):コントロールモジュール」では、ターミナルのグローバル変数を使用せずにコントロールインジケーターを作成できるよう、いくつかの変更を実装しました。少なくとも、情報の保存やユーザーが指定した設定の保持という点においては、期待通りに機能するようになりました。
すべて正常に動作し、かなり安定していましたが、一定数のバーを持つチャートにシステムを配置すると、カスタム銘柄に関連するデータを使用する際に範囲制限を超えてしまい、クラッシュが発生する問題がありました。
この原因は、カスタム銘柄自体が壊れていることでも、問題を引き起こしていることでもありません。むしろ、システム全体を正常に動作させるためのデータベースが十分ではないことが主な要因です。そして、その中でも特に重要な問題はバッファにあります。
バッファが問題なのかと疑問に思うかもしれません。実際の銘柄を使えば正常に動作するのに、カスタム銘柄を使うとクラッシュしてしまいます。その原因がインジケーターバッファにあるというのでしょうか。
その通りです。しかし、それはおそらく想像しているような単純な理由ではありません。カスタム銘柄を作成する際、バッファが必要なデータを格納するのに十分なサイズを確保できていない場合があるのです。
それなら、もっとメモリを割り当てれば解決するのではないかと思うかもしれません。しかし、MQL5では話がそう単純ではありません。インジケーターバッファ用にメモリを割り当てても、期待通りには動作しません。なぜなら、メモリの割り当てはチャート上のバーの数によって決まるからです。そのため、関数を使ってメモリを確保しようとしても、実際には期待通りに活用されないことになります。
問題の把握
実際の問題はコントロールインジケーターではなく、マウスインジケーターにあります。この問題を修正することで、結果的にコントロールインジケーターにも影響を与えることになります。これらの変更点については、この記事の後半で詳しく説明します。まずは、問題の発生メカニズムを理解しましょう。
前回の記事で紹介したマウスインジケーターを、たとえば1分足バーが60本あるカスタム銘柄のチャートに適用した場合、10分足以下の時間枠では問題なく動作します。しかし、10分足を超える時間枠を選択すると、MetaTrader 5から「Mouse indicator:Range error」というエラーメッセージが表示されます。
なぜこのようなエラーが発生するのでしょうか。その理由は、前回の記事で紹介したマウスインジケーターが、データをインジケーターバッファに格納するために6つのポジションを必要とするためです。数学的に考えてみましょう。1分足で60本のバーがある場合、10分足に変更すると、チャート上には6本のバーが表示されます。この6本のバーによって、データを格納するために必要な6つのバッファポジションが確保されます。しかし、より大きな時間枠を選択すると、チャート上のバーの数は6本未満になってしまいます。
このとき、マウスインジケーターはMetaTrader 5に割り当てられていないメモリ領域にデータを書き込もうとするため、範囲エラーが発生してしまうのです。
この問題を解決する方法は2つあります。1つ目の方法は、カスタム銘柄に十分な数のバーを配置し、どの時間枠でも少なくとも6本のバーが存在するようにすることです。しかし、この方法はあまり現実的ではありません。なぜなら、月足時間枠を使用するためには、カスタム銘柄のチャートに少なくとも6か月分の1分足バーをロードする必要があるからです。これは、単に範囲エラーを防ぐためだけに必要な措置であり、非常に非効率的です。
個人的には、この方法が最適な解決策であるとは思いませんし、多くの人も同じでしょう。もしこのシステムが単なるリプレイシステムであり、特定の時間枠内でのみ動作させることを前提とするのであれば、この方法でも問題はないかもしれません。しかし、このシステムは市場の動きをモデル化することも目的としているため、この方法では不十分です。より洗練された解決策が求められます。
そこで、2つ目の方法として、マウスインジケーターを修正し、情報がバッファ内の1つのポジションにコンパクトに格納されるようにします。これにより、チャート上にバーが1本でもあれば、インジケーターが正常に機能するようになります。
ソリューション導入の開始
情報を1つのバッファポジションにコンパクトに格納することにしたのは、リプレイ/シミュレーターシステムにとって、チャート上に最低1本のバーを追加し維持するだけで済むため、他の方法よりもはるかに簡単だからです。
しかし、これを採用する主な理由は、膨大な数のバーを使用せずに市場シミュレーションを実行したい場合があるからです。この方法を使えば、必要な分だけのデータを確保し、リプレイ/シミュレーターサービス自体がシステムの安定性を保証できるようになります。これにより、リアル口座とデモ口座の両方で同じ仕組みを活用することが可能になります。
要約すると、マウスインジケーターバッファのサイズを大幅に縮小し、1つのポジションのみを使用するようにします。それでも、インジケーターにバッファデータの読み取りを要求すれば、以前と同じ情報を取得することができます。ただし、注意すべき点として、マウスインジケーターバッファから直接データを取得すると、そのバッファの1つの位置に格納されたデータしか得られません。このデータを適切に解釈し、有用な情報として活用するためには、適切な変換処理が必要です。C_Mouseクラスには、この変換を正しくおこなうための特定の関数が用意されています。
ということで、次は実際の実装に進みます。最初に変更すべきなのはヘッダファイルです。コードは以下の通りです。
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define def_VERSION_DEBUG 05. //+------------------------------------------------------------------+ 06. #ifdef def_VERSION_DEBUG 07. #define macro_DEBUG_MODE(A) \ 08. Print(__FILE__, " ", __LINE__, " ", __FUNCTION__ + " " + #A + " = " + (string)(A)); 09. #else 10. #define macro_DEBUG_MODE(A) 11. #endif 12. //+------------------------------------------------------------------+ 13. #define def_SymbolReplay "RePlay" 14. #define def_MaxPosSlider 400 15. //+------------------------------------------------------------------+ 16. union uCast_Double 17. { 18. double dValue; 19. long _long; // 1 Information 20. datetime _datetime; // 1 Information 21. uint _32b[sizeof(double) / sizeof(uint)]; // 2 Informations 22. ushort _16b[sizeof(double) / sizeof(ushort)]; // 4 Informations 23. uchar _8b [sizeof(double) / sizeof(uchar)]; // 8 Informations 24. }; 25. //+------------------------------------------------------------------+ 26. enum EnumEvents { 27. evHideMouse, //Hide mouse price line 28. evShowMouse, //Show mouse price line 29. evHideBarTime, //Hide bar time 30. evShowBarTime, //Show bar time 31. evHideDailyVar, //Hide daily variation 32. evShowDailyVar, //Show daily variation 33. evHidePriceVar, //Hide instantaneous variation 34. evShowPriceVar, //Show instantaneous variation 35. evSetServerTime, //Replay/simulation system timer 36. evCtrlReplayInit //Initialize replay control 37. }; 38. //+------------------------------------------------------------------+
Defines.mqhファイルのソースコード
少なくとも一般的な観点では、大きな違いはありません。しかし、よく見ると21行目と23行目に変更が加えられています。これらの変更は、特にビットをより効率的に使用するために実装されました。扱う情報の種類を識別しやすくするために、シンプルな表記を採用しました。_32bは32ビット、_16bは16ビット、_8bは8ビットを表します。これにより、どのようなアクセスでも、使用するビット数を正確に把握できるようになります。また、double型の値は64ビットであるため、各データパケットの長さはこの64ビットの範囲内に制限される点も重要です。
したがって、_32bには2つの値、_16bには4つの値、_8bには8つの値を格納できます。ただし、ここでは共用体(union)を使用しているため、これらのデータセットを組み合わせることが可能です。これを正しく扱うためには、MQL5の配列がC/C++の番号付けシステムに基づいていることを理解する必要があります。つまり、配列のインデックスは常に0から始まり、その後の各要素は1ずつ増えていきます。
この時点で、C/C++の動作原理に関する基礎知識がないと、混乱する方もいるかもしれません。なぜなら、ここで言う「単位」という言葉が、パケット内でデータを正しく配置するためにインデックス値を増やす際に何を意味するのか、十分に伝わらない可能性があるからです。この概念を誤解すると、情報の全体像を見失ってしまったり、少なくともデータがどのように圧縮されているのかを理解することが難しくなってしまいます。
マウスインジケーターに加えた変更について説明する前に、まずはコントロールインジケーターを簡単に見ていきましょう。その理由は単純で、コントロールインジケーターでは、ヘッダファイルDefines.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_PrefixCtrlName "MarketReplayCTRL_" 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 eObjectControl {ePlay, eLeft, eRight, ePin, eNull}; 038. //+------------------------------------------------------------------+ 039. struct st_00 040. { 041. string szBarSlider, 042. szBarSliderBlock; 043. short Minimal; 044. }m_Slider; 045. struct st_01 046. { 047. C_DrawImage *Btn; 048. bool state; 049. short x, y, w, h; 050. }m_Section[eObjectControl::eNull]; 051. C_Mouse *m_MousePtr; 052. //+------------------------------------------------------------------+ 053. inline void CreteBarSlider(short x, short size) 054. { 055. ObjectCreate(GetInfoTerminal().ID, m_Slider.szBarSlider = def_PrefixCtrlName + "B1", OBJ_RECTANGLE_LABEL, 0, 0, 0); 056. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_XDISTANCE, def_PosXObjects + x); 057. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_YDISTANCE, m_Section[ePin].y + 11); 058. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_XSIZE, size); 059. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_YSIZE, 9); 060. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_BGCOLOR, clrLightSkyBlue); 061. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_BORDER_COLOR, clrBlack); 062. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_WIDTH, 3); 063. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_BORDER_TYPE, BORDER_FLAT); 064. ObjectCreate(GetInfoTerminal().ID, m_Slider.szBarSliderBlock = def_PrefixCtrlName + "B2", OBJ_RECTANGLE_LABEL, 0, 0, 0); 065. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_XDISTANCE, def_PosXObjects + x); 066. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_YDISTANCE, m_Section[ePin].y + 6); 067. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_YSIZE, 19); 068. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_BGCOLOR, clrRosyBrown); 069. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_BORDER_TYPE, BORDER_RAISED); 070. } 071. //+------------------------------------------------------------------+ 072. void SetPlay(bool state) 073. { 074. if (m_Section[ePlay].Btn == NULL) 075. m_Section[ePlay].Btn = new C_DrawImage(GetInfoTerminal().ID, 0, def_PrefixCtrlName + EnumToString(ePlay), def_ColorFilter, "::" + def_ButtonPlay, "::" + def_ButtonPause); 076. 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) ? 0 : 1)); 077. } 078. //+------------------------------------------------------------------+ 079. void CreateCtrlSlider(void) 080. { 081. CreteBarSlider(77, 436); 082. m_Section[eLeft].Btn = new C_DrawImage(GetInfoTerminal().ID, 0, def_PrefixCtrlName + EnumToString(eLeft), def_ColorFilter, "::" + def_ButtonLeft, "::" + def_ButtonLeftBlock); 083. m_Section[eRight].Btn = new C_DrawImage(GetInfoTerminal().ID, 0, def_PrefixCtrlName + EnumToString(eRight), def_ColorFilter, "::" + def_ButtonRight, "::" + def_ButtonRightBlock); 084. m_Section[ePin].Btn = new C_DrawImage(GetInfoTerminal().ID, 0, def_PrefixCtrlName + EnumToString(ePin), def_ColorFilter, "::" + def_ButtonPin); 085. PositionPinSlider(m_Slider.Minimal); 086. } 087. //+------------------------------------------------------------------+ 088. inline void RemoveCtrlSlider(void) 089. { 090. ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, false); 091. for (eObjectControl c0 = ePlay + 1; c0 < eNull; c0++) 092. { 093. delete m_Section[c0].Btn; 094. m_Section[c0].Btn = NULL; 095. } 096. ObjectsDeleteAll(GetInfoTerminal().ID, def_PrefixCtrlName + "B"); 097. ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, true); 098. } 099. //+------------------------------------------------------------------+ 100. inline void PositionPinSlider(short p) 101. { 102. int iL, iR; 103. 104. m_Section[ePin].x = (p < m_Slider.Minimal ? m_Slider.Minimal : (p > def_MaxPosSlider ? def_MaxPosSlider : p)); 105. iL = (m_Section[ePin].x != m_Slider.Minimal ? 0 : 1); 106. iR = (m_Section[ePin].x < def_MaxPosSlider ? 0 : 1); 107. m_Section[ePin].x += def_PosXObjects; 108. m_Section[ePin].x += 95 - (def_SizeButtons / 2); 109. for (eObjectControl c0 = ePlay + 1; c0 < eNull; c0++) 110. 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))); 111. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_XSIZE, m_Slider.Minimal + 2); 112. } 113. //+------------------------------------------------------------------+ 114. inline eObjectControl CheckPositionMouseClick(short &x, short &y) 115. { 116. C_Mouse::st_Mouse InfoMouse; 117. 118. InfoMouse = (*m_MousePtr).GetInfoMouse(); 119. x = (short) InfoMouse.Position.X_Graphics; 120. y = (short) InfoMouse.Position.Y_Graphics; 121. for (eObjectControl c0 = ePlay; c0 < eNull; c0++) 122. { 123. 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)) 124. return c0; 125. } 126. 127. return eNull; 128. } 129. //+------------------------------------------------------------------+ 130. public : 131. //+------------------------------------------------------------------+ 132. C_Controls(const long Arg0, const string szShortName, C_Mouse *MousePtr) 133. :C_Terminal(Arg0), 134. m_MousePtr(MousePtr) 135. { 136. if ((!IndicatorCheckPass(szShortName)) || (CheckPointer(m_MousePtr) == POINTER_INVALID)) SetUserError(C_Terminal::ERR_Unknown); 137. if (_LastError != ERR_SUCCESS) return; 138. ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, false); 139. ObjectsDeleteAll(GetInfoTerminal().ID, def_PrefixCtrlName); 140. ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, true); 141. for (eObjectControl c0 = ePlay; c0 < eNull; c0++) 142. { 143. m_Section[c0].h = m_Section[c0].w = def_SizeButtons; 144. m_Section[c0].y = 25; 145. m_Section[c0].Btn = NULL; 146. } 147. m_Section[ePlay].x = def_PosXObjects; 148. m_Section[eLeft].x = m_Section[ePlay].x + 47; 149. m_Section[eRight].x = m_Section[ePlay].x + 511; 150. m_Slider.Minimal = SHORT_MIN; 151. } 152. //+------------------------------------------------------------------+ 153. ~C_Controls() 154. { 155. for (eObjectControl c0 = ePlay; c0 < eNull; c0++) delete m_Section[c0].Btn; 156. ObjectsDeleteAll(GetInfoTerminal().ID, def_PrefixCtrlName); 157. delete m_MousePtr; 158. } 159. //+------------------------------------------------------------------+ 160. void SetBuffer(const int rates_total, double &Buff[]) 161. { 162. uCast_Double info; 163. 164. info._16b[0] = (ushort) m_Slider.Minimal; 165. info._16b[1] = (ushort) (m_Section[ePlay].state ? SHORT_MAX : SHORT_MIN); 166. if (rates_total > 0) 167. Buff[rates_total - 1] = info.dValue; 168. } 169. //+------------------------------------------------------------------+ 170. void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam) 171. { 172. short x, y; 173. static short iPinPosX = -1, six = -1, sps; 174. uCast_Double info; 175. 176. switch (id) 177. { 178. case (CHARTEVENT_CUSTOM + evCtrlReplayInit): 179. info.dValue = dparam; 180. iPinPosX = m_Slider.Minimal = (short) info._16b[0]; 181. if (info._16b[1] == 0) SetUserError(C_Terminal::ERR_Unknown); else 182. { 183. SetPlay((short)(info._16b[1]) == SHORT_MAX); 184. if ((short)(info._16b[1]) == SHORT_MIN) CreateCtrlSlider(); 185. } 186. break; 187. case CHARTEVENT_OBJECT_DELETE: 188. if (StringSubstr(sparam, 0, StringLen(def_PrefixCtrlName)) == def_PrefixCtrlName) 189. { 190. if (sparam == (def_PrefixCtrlName + EnumToString(ePlay))) 191. { 192. delete m_Section[ePlay].Btn; 193. m_Section[ePlay].Btn = NULL; 194. SetPlay(m_Section[ePlay].state); 195. }else 196. { 197. RemoveCtrlSlider(); 198. CreateCtrlSlider(); 199. } 200. } 201. break; 202. case CHARTEVENT_MOUSE_MOVE: 203. if ((*m_MousePtr).CheckClick(C_Mouse::eClickLeft)) switch (CheckPositionMouseClick(x, y)) 204. { 205. case ePlay: 206. SetPlay(!m_Section[ePlay].state); 207. if (m_Section[ePlay].state) 208. { 209. RemoveCtrlSlider(); 210. m_Slider.Minimal = iPinPosX; 211. }else CreateCtrlSlider(); 212. break; 213. case eLeft: 214. PositionPinSlider(iPinPosX = (iPinPosX > m_Slider.Minimal ? iPinPosX - 1 : m_Slider.Minimal)); 215. break; 216. case eRight: 217. PositionPinSlider(iPinPosX = (iPinPosX < def_MaxPosSlider ? iPinPosX + 1 : def_MaxPosSlider)); 218. break; 219. case ePin: 220. if (six == -1) 221. { 222. six = x; 223. sps = iPinPosX; 224. ChartSetInteger(GetInfoTerminal().ID, CHART_MOUSE_SCROLL, false); 225. } 226. iPinPosX = sps + x - six; 227. PositionPinSlider(iPinPosX = (iPinPosX < m_Slider.Minimal ? m_Slider.Minimal : (iPinPosX > def_MaxPosSlider ? def_MaxPosSlider : iPinPosX))); 228. break; 229. }else if (six > 0) 230. { 231. six = -1; 232. ChartSetInteger(GetInfoTerminal().ID, CHART_MOUSE_SCROLL, true); 233. } 234. break; 235. } 236. ChartRedraw(GetInfoTerminal().ID); 237. } 238. //+------------------------------------------------------------------+ 239. }; 240. //+------------------------------------------------------------------+ 241. #undef def_PosXObjects 242. #undef def_ButtonPlay 243. #undef def_ButtonPause 244. #undef def_ButtonLeft 245. #undef def_ButtonRight 246. #undef def_ButtonPin 247. #undef def_PrefixCtrlName 248. #undef def_PathBMP 249. //+------------------------------------------------------------------+
C_Control.mqhのソースコード
コードを調べてみると、上に示したコードと、このヘッダファイルについて説明した前回の記事で紹介した同じコードとの間に、少なくとも一見した限りでは目に見える違いはありません。しかし、定義ファイルに変更が加えられたことで、一部のコードにわずかな修正が施されました。そのうちの1つが、164行目と165行目に見られるように、型キャストを明示的に使用する点です。
165行目の変更に注目してください。以前はint定数を使用していましたが、現在はshort定数を使用しています。これらは符号付き定数であり、負の値を表すことができますが、配列に格納される値は符号なしです。この時点で、値が誤って解釈される可能性があるのではないかと疑問に思うかもしれません。しかし、もしそう考えたのであれば、値が2進数形式でどのように表現されるかを正しく理解していない可能性があります。負の値を明示的に処理しないシステムにおいて、情報の品質を損なうことなく負の値をどのように表現できるのかを理解するために、2進数表現について学ぶことをおすすめします。
同じコードの180行目にも、明示的な型変換がおこなわれています。ここでは、符号なし変数に格納された値を符号付き変数に代入しています。この型変換により、負の値が適切に表現されます。カスタムイベント処理コード全体や、コントロールインジケーターの初期化の過程で、この型変換が多用されています。178行目から186行目の間のコードは、この変換手法が集中的に使われていることを示しているため、じっくりと確認してみてください。
なお、このコントロールインジケーターで行われた圧縮は、double型のビットを最大限に効率よく活用するほど深いものではありませんでした。これは、今回のケースでは、そのような高度な最適化を行う必要がないためです。最後に、コントロールインジケーターのコードについてですが、変更されたのはバージョン番号とリンクのみです。したがって、ここでコード全体を再掲載することはしません。必要なのは、前回の記事で使用したヘッダファイルを、本記事で提供するものに置き換えるだけです。その他のインジケーターコードに変更はなく、そのまま問題なく使用できます。
さて、マウスインジケーターに関しては、さらに複雑になります。そのため、インジケーターの作成に必要な3つのファイルすべてが必要になります。これらのファイルについては、次のセクションで詳しく見ていきましょう。
マウスインジケーターのソリューションを実装する
前のセクションでご覧いただいたように、コントロールインジケーターのコードにはわずかな修正が加えられましたが、それらの変更はヘッダファイルに限定されていました。しかし、マウスインジケーターの場合、状況はまったく異なり、はるかに複雑になります。
それでは、変更点を見ていきましょう。まず、新しいC_Mouseクラスを確認してみます。
001. //+------------------------------------------------------------------+ 002. #property copyright "Daniel Jose" 003. //+------------------------------------------------------------------+ 004. #include "C_Terminal.mqh" 005. //+------------------------------------------------------------------+ 006. #define def_MousePrefixName "MouseBase_" 007. #define def_NameObjectLineH def_MousePrefixName + "H" 008. #define def_NameObjectLineV def_MousePrefixName + "TV" 009. #define def_NameObjectLineT def_MousePrefixName + "TT" 010. #define def_NameObjectStudy def_MousePrefixName + "TB" 011. //+------------------------------------------------------------------+ 012. class C_Mouse : public C_Terminal 013. { 014. public : 015. enum eStatusMarket {eCloseMarket, eAuction, eInTrading, eInReplay}; 016. enum eBtnMouse {eKeyNull = 0x00, eClickLeft = 0x01, eClickRight = 0x02, eSHIFT_Press = 0x04, eCTRL_Press = 0x08, eClickMiddle = 0x10}; 017. struct st_Mouse 018. { 019. struct st00 020. { 021. short X_Adjusted, 022. Y_Adjusted, 023. X_Graphics, 024. Y_Graphics; 025. double Price; 026. datetime dt; 027. }Position; 028. uchar ButtonStatus; 029. bool ExecStudy; 030. }; 031. //+------------------------------------------------------------------+ 032. protected: 033. //+------------------------------------------------------------------+ 034. void CreateObjToStudy(int x, int w, string szName, color backColor = clrNONE) const 035. { 036. if (m_Mem.szShortName != NULL) return; 037. CreateObjectGraphics(szName, OBJ_BUTTON, clrNONE); 038. ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_STATE, true); 039. ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_BORDER_COLOR, clrBlack); 040. ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_COLOR, clrBlack); 041. ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_BGCOLOR, backColor); 042. ObjectSetString(GetInfoTerminal().ID, szName, OBJPROP_FONT, "Lucida Console"); 043. ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_FONTSIZE, 10); 044. ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_CORNER, CORNER_LEFT_UPPER); 045. ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_XDISTANCE, x); 046. ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_YDISTANCE, TerminalInfoInteger(TERMINAL_SCREEN_HEIGHT) + 1); 047. ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_XSIZE, w); 048. ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_YSIZE, 18); 049. } 050. //+------------------------------------------------------------------+ 051. private : 052. enum eStudy {eStudyNull, eStudyCreate, eStudyExecute}; 053. struct st01 054. { 055. st_Mouse Data; 056. color corLineH, 057. corTrendP, 058. corTrendN; 059. eStudy Study; 060. }m_Info; 061. struct st_Mem 062. { 063. bool CrossHair, 064. IsFull; 065. datetime dt; 066. string szShortName; 067. }m_Mem; 068. bool m_OK; 069. //+------------------------------------------------------------------+ 070. void GetDimensionText(const string szArg, int &w, int &h) 071. { 072. TextSetFont("Lucida Console", -100, FW_NORMAL); 073. TextGetSize(szArg, w, h); 074. h += 5; 075. w += 5; 076. } 077. //+------------------------------------------------------------------+ 078. void CreateStudy(void) 079. { 080. if (m_Mem.IsFull) 081. { 082. CreateObjectGraphics(def_NameObjectLineV, OBJ_VLINE, m_Info.corLineH); 083. CreateObjectGraphics(def_NameObjectLineT, OBJ_TREND, m_Info.corLineH); 084. ObjectSetInteger(GetInfoTerminal().ID, def_NameObjectLineT, OBJPROP_WIDTH, 2); 085. CreateObjToStudy(0, 0, def_NameObjectStudy); 086. } 087. m_Info.Study = eStudyCreate; 088. } 089. //+------------------------------------------------------------------+ 090. void ExecuteStudy(const double memPrice) 091. { 092. double v1 = GetInfoMouse().Position.Price - memPrice; 093. int w, h; 094. 095. if (!CheckClick(eClickLeft)) 096. { 097. m_Info.Study = eStudyNull; 098. ChartSetInteger(GetInfoTerminal().ID, CHART_MOUSE_SCROLL, true); 099. if (m_Mem.IsFull) ObjectsDeleteAll(GetInfoTerminal().ID, def_MousePrefixName + "T"); 100. }else if (m_Mem.IsFull) 101. { 102. string sz1 = StringFormat(" %." + (string)GetInfoTerminal().nDigits + "f [ %d ] %02.02f%% ", 103. MathAbs(v1), Bars(GetInfoTerminal().szSymbol, PERIOD_CURRENT, m_Mem.dt, GetInfoMouse().Position.dt) - 1, MathAbs((v1 / memPrice) * 100.0)); 104. GetDimensionText(sz1, w, h); 105. ObjectSetString(GetInfoTerminal().ID, def_NameObjectStudy, OBJPROP_TEXT, sz1); 106. ObjectSetInteger(GetInfoTerminal().ID, def_NameObjectStudy, OBJPROP_BGCOLOR, (v1 < 0 ? m_Info.corTrendN : m_Info.corTrendP)); 107. ObjectSetInteger(GetInfoTerminal().ID, def_NameObjectStudy, OBJPROP_XSIZE, w); 108. ObjectSetInteger(GetInfoTerminal().ID, def_NameObjectStudy, OBJPROP_YSIZE, h); 109. ObjectSetInteger(GetInfoTerminal().ID, def_NameObjectStudy, OBJPROP_XDISTANCE, GetInfoMouse().Position.X_Adjusted - w); 110. ObjectSetInteger(GetInfoTerminal().ID, def_NameObjectStudy, OBJPROP_YDISTANCE, GetInfoMouse().Position.Y_Adjusted - (v1 < 0 ? 1 : h)); 111. ObjectMove(GetInfoTerminal().ID, def_NameObjectLineT, 1, GetInfoMouse().Position.dt, GetInfoMouse().Position.Price); 112. ObjectSetInteger(GetInfoTerminal().ID, def_NameObjectLineT, OBJPROP_COLOR, (memPrice > GetInfoMouse().Position.Price ? m_Info.corTrendN : m_Info.corTrendP)); 113. } 114. m_Info.Data.ButtonStatus = eKeyNull; 115. } 116. //+------------------------------------------------------------------+ 117. inline void DecodeAlls(int xi, int yi) 118. { 119. int w = 0; 120. 121. ChartXYToTimePrice(GetInfoTerminal().ID, m_Info.Data.Position.X_Graphics = (short) xi, m_Info.Data.Position.Y_Graphics = (short)yi, w, m_Info.Data.Position.dt, m_Info.Data.Position.Price); 122. m_Info.Data.Position.dt = AdjustTime(m_Info.Data.Position.dt); 123. m_Info.Data.Position.Price = AdjustPrice(m_Info.Data.Position.Price); 124. ChartTimePriceToXY(GetInfoTerminal().ID, w, m_Info.Data.Position.dt, m_Info.Data.Position.Price, xi, yi); 125. m_Info.Data.Position.X_Adjusted = (short) xi; 126. m_Info.Data.Position.Y_Adjusted = (short) yi; 127. } 128. //+------------------------------------------------------------------+ 129. public : 130. //+------------------------------------------------------------------+ 131. C_Mouse(const long id, const string szShortName) 132. :C_Terminal(id), 133. m_OK(false) 134. { 135. m_Mem.szShortName = szShortName; 136. } 137. //+------------------------------------------------------------------+ 138. C_Mouse(const long id, const string szShortName, color corH, color corP, color corN) 139. :C_Terminal(id) 140. { 141. if (!(m_OK = IndicatorCheckPass(szShortName))) SetUserError(C_Terminal::ERR_Unknown); 142. if (_LastError != ERR_SUCCESS) return; 143. m_Mem.szShortName = NULL; 144. m_Mem.CrossHair = (bool)ChartGetInteger(GetInfoTerminal().ID, CHART_CROSSHAIR_TOOL); 145. ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_MOUSE_MOVE, true); 146. ChartSetInteger(GetInfoTerminal().ID, CHART_CROSSHAIR_TOOL, false); 147. ZeroMemory(m_Info); 148. m_Info.corLineH = corH; 149. m_Info.corTrendP = corP; 150. m_Info.corTrendN = corN; 151. m_Info.Study = eStudyNull; 152. if (m_Mem.IsFull = (corP != clrNONE) && (corH != clrNONE) && (corN != clrNONE)) 153. CreateObjectGraphics(def_NameObjectLineH, OBJ_HLINE, m_Info.corLineH); 154. } 155. //+------------------------------------------------------------------+ 156. ~C_Mouse() 157. { 158. if (!m_OK) return; 159. ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, false); 160. ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_MOUSE_MOVE, false); 161. ChartSetInteger(GetInfoTerminal().ID, CHART_CROSSHAIR_TOOL, m_Mem.CrossHair); 162. ObjectsDeleteAll(GetInfoTerminal().ID, def_MousePrefixName); 163. } 164. //+------------------------------------------------------------------+ 165. inline bool CheckClick(const eBtnMouse value) 166. { 167. return (GetInfoMouse().ButtonStatus & value) == value; 168. } 169. //+------------------------------------------------------------------+ 170. inline const st_Mouse GetInfoMouse(void) 171. { 172. if (m_Mem.szShortName != NULL) 173. { 174. double Buff[]; 175. uCast_Double loc; 176. int handle = ChartIndicatorGet(GetInfoTerminal().ID, 0, m_Mem.szShortName); 177. 178. ZeroMemory(m_Info.Data); 179. if (CopyBuffer(handle, 0, 0, 1, Buff) == 1) 180. { 181. loc.dValue = Buff[0]; 182. m_Info.Data.ButtonStatus = loc._8b[0]; 183. DecodeAlls((int)loc._16b[1], (int)loc._16b[2]); 184. } 185. IndicatorRelease(handle); 186. } 187. 188. return m_Info.Data; 189. } 190. //+------------------------------------------------------------------+ 191. inline void SetBuffer(const int rates_total, double &Buff[]) 192. { 193. uCast_Double info; 194. 195. info._8b[0] = (uchar)(m_Info.Study == C_Mouse::eStudyNull ? m_Info.Data.ButtonStatus : 0); 196. info._16b[1] = (ushort) m_Info.Data.Position.X_Graphics; 197. info._16b[2] = (ushort) m_Info.Data.Position.Y_Graphics; 198. Buff[rates_total - 1] = info.dValue; 199. } 200. //+------------------------------------------------------------------+ 201. void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam) 202. { 203. int w = 0; 204. static double memPrice = 0; 205. 206. if (m_Mem.szShortName == NULL) 207. { 208. C_Terminal::DispatchMessage(id, lparam, dparam, sparam); 209. switch (id) 210. { 211. case (CHARTEVENT_CUSTOM + evHideMouse): 212. if (m_Mem.IsFull) ObjectSetInteger(GetInfoTerminal().ID, def_NameObjectLineH, OBJPROP_COLOR, clrNONE); 213. break; 214. case (CHARTEVENT_CUSTOM + evShowMouse): 215. if (m_Mem.IsFull) ObjectSetInteger(GetInfoTerminal().ID, def_NameObjectLineH, OBJPROP_COLOR, m_Info.corLineH); 216. break; 217. case CHARTEVENT_MOUSE_MOVE: 218. DecodeAlls((int)lparam, (int)dparam); 219. if (m_Mem.IsFull) ObjectMove(GetInfoTerminal().ID, def_NameObjectLineH, 0, 0, m_Info.Data.Position.Price = AdjustPrice(m_Info.Data.Position.Price)); 220. if ((m_Info.Study != eStudyNull) && (m_Mem.IsFull)) ObjectMove(GetInfoTerminal().ID, def_NameObjectLineV, 0, m_Info.Data.Position.dt, 0); 221. m_Info.Data.ButtonStatus = (uchar) sparam; //Mudança no tipo ... 222. if (CheckClick(eClickMiddle)) 223. if ((!m_Mem.IsFull) || ((color)ObjectGetInteger(GetInfoTerminal().ID, def_NameObjectLineH, OBJPROP_COLOR) != clrNONE)) CreateStudy(); 224. if (CheckClick(eClickLeft) && (m_Info.Study == eStudyCreate)) 225. { 226. ChartSetInteger(GetInfoTerminal().ID, CHART_MOUSE_SCROLL, false); 227. if (m_Mem.IsFull) ObjectMove(GetInfoTerminal().ID, def_NameObjectLineT, 0, m_Mem.dt = GetInfoMouse().Position.dt, memPrice = GetInfoMouse().Position.Price); 228. m_Info.Study = eStudyExecute; 229. } 230. if (m_Info.Study == eStudyExecute) ExecuteStudy(memPrice); 231. m_Info.Data.ExecStudy = m_Info.Study == eStudyExecute; 232. break; 233. case CHARTEVENT_OBJECT_DELETE: 234. if ((m_Mem.IsFull) && (sparam == def_NameObjectLineH)) CreateObjectGraphics(def_NameObjectLineH, OBJ_HLINE, m_Info.corLineH); 235. break; 236. } 237. } 238. } 239. //+------------------------------------------------------------------+ 240. }; 241. //+------------------------------------------------------------------+ 242. #undef def_NameObjectLineV 243. #undef def_NameObjectLineH 244. #undef def_NameObjectLineT 245. #undef def_NameObjectStudy 246. //+------------------------------------------------------------------+
C_Mouse.mqhファイルのソースコード
変更点にすぐには気づかないかもしれませんが、その主な理由は、変更のほとんどが変数の型に関するものであるためです。しかし、これらの変更には言及する価値があり、正しく理解することで、このマウスインジケーターの制約を把握できるようになります。確かに制限は存在しますが、それを理解すれば、適切に使用することで問題を回避できます。
まず、21行目から24行目にかけて、以前はint型を使用していた箇所がshort型に変更されていることに気づくでしょう。同様に、28行目でも変数の型が変更されています。
こうした型の変更によって混乱する前に、short型とint型の特性や違いを思い出してください。
MQL5では、int型は32ビットデータ型であり、符号付きの場合は-2,147,483,648から2,147,483,647までの範囲を持ちます。符号なしの場合(正の値のみを扱う場合)は、0から4,294,967,295の範囲となります。一方、short型は16ビットのデータ型で、符号付きの場合は-32,768から32,767、符号なしの場合は0から65,535の範囲を持ちます。
さて、ご自身のモニターのピクセル数をご存じでしょうか。なぜこんなことを聞くのかというと、モニター上の座標(X座標とY座標)を表すのにint型を使用するのは非効率だからです。誤解しないでほしいのですが、事実として、多くのメモリを無駄にしてしまいます。たとえば、非常に高解像度な8Kモニターでさえ、解像度は水平方向7,680ピクセルx垂直方向4,320ピクセルです。この解像度は、それぞれ2^13以下の範囲に収まり、符号付きshort型(2^6)で十分に対応できます。その結果、データとして利用可能な未使用ビットが3つ残ります。
このような単純な最適化で十分な効率化が図れます。int型を使用すると、64ビットのデータ領域に2つの値しか格納できませんが、short型を使用すると、同じ領域に4つの値を格納できます。マウスの位置を表すのに必要なのはX座標とY座標の2つのshort値のみであるため、さらに2つのshortスロットを別の目的に活用できます。加えて、たとえ8Kディスプレイを使用していても、マウス座標の保存に利用するshort値には、さらに3ビットの未使用領域が残っています。
この制限が影響するのは座標データではありません。実際の制約は別の部分にあります。この連載をお読みの方なら、マウスインジケーターはシンプルかつ迅速な分析のための情報提供を目的としていることにお気づきでしょう。この点において、価格情報や時間データは、直接マウスインジケーターのバッファに保存することができません。なぜなら、これらの値はそれぞれ64ビットを必要とし、それを格納するために2つのポジションを割り当てる余裕がないからです。この問題に対処するため、C_Mouseクラスに変更が加えられました。
最初に注目すべき変更点は117行目です。注意:マウスインジケーター全体の型を大幅に修正することを避けるため、クラス内では位置情報をintのまま維持しています。しかし、クラス外では必要に応じて適切に調整されるようになっています。
117行目の処理は、OSが提供する画面座標をMQL5の基準に合わせた座標に変換し、チャート上に適切に表示されるようにするものです。この点に特に注意してください。この処理はクラスのprivateメソッドであり、クラス外のコードからはアクセスできません。しかし、データの変換を保証することで、機能の整合性を保っています。もしこのインジケーターを通じてクラス内のデータを変換しているのであれば、問題は発生しません。
次に、170行目に注目してください。この関数は、インジケーターのデータを変換するものです。内部コードは変更されていますが、関数のインターフェイスは変わっていません。以前は6つの値を返していましたが、現在は1つだけ返します。どう対処するかに細心の注意を払ってください。179行目では、インジケーターバッファから値を取得し、181行目でその値を変換システムに代入します。182行目では、ボタンのステータスを決定するための値を取得し、適切に変換します。使用されるインデックスとビットの長さに注意してください。183行目では、画面座標X・YをMetaTrader 5やその他のアプリケーションで使用できる型に変換する処理を実行します。これらの変更は関数内部にとどまり、クラス外のコードには影響を与えません。
続いて、191行目では、インジケーターバッファへのデータ書き込み処理が定義されています。以前はこの処理はインジケーターのコード内にありましたが、クラスデータへのアクセスを制限するため、クラス内へ移動されました。
順を追って見ていきましょう。193行目では圧縮用の変数を宣言しています。195行目からデータ圧縮処理が始まります。配列にアクセスする際のインデックスの使い方に特に注意してください。
_8b配列には8つの要素があり、_16b配列には4つの要素があります。どちらもインデックスは0から始まります。ただし、_16b配列のインデックス0は_8b配列のインデックス0と1に対応していることを理解しておく必要があります。例えば、195行目では_8b配列のインデックス0を使用することで_16b配列のインデックス0を占有します。この情報の格納には_8bインデックス0のみを使用するため、_8bのインデックス1は空のままとなります。しかし、_16bのインデックス0は_8bの2つのインデックスを必要とするため、再利用することはできません。
その結果、_8bインデックス1は空のままとなり、マウスの位置情報は_8bインデックス2から格納されます。_8bインデックス2は_16bインデックス1に対応しており、196行目ではこのインデックスを参照しながらデータの整理をおこなっています。197行目でも同様のインデックス管理の原則が適用されています。見た目にはすべてのビットが使用されているように見えるかもしれませんが、実際には_16bインデックス3、_8bインデックス1、そしてXとYの2つのshort値の6ビット分は未使用のままとなっています。これらの未使用ビットは必要に応じて他のデータに再利用することも可能です。
わかりやすくするために、下の画像をご覧ください。
画像は、バッファの内容をバイトごとに示したもので、各バイトは8ビットを表しています。青い部分は使用されているビットを示し、白い部分は将来のデータ用に空いています。XはグラフィカルなX座標を、YはY座標を表しています。この画像をご覧いただくことで、私が実際に行っていることをよりよく理解していただけると幸いです。
残りのマウスインジケーターのコードについては、比較的単純なため、詳しい説明は必要ありません。ただし、コードを修正したため、以下に更新版を掲載します。これにより、すべてが意図したとおりに動作するようになります。
001. //+------------------------------------------------------------------+ 002. #property copyright "Daniel Jose" 003. //+------------------------------------------------------------------+ 004. #include "..\C_Mouse.mqh" 005. //+------------------------------------------------------------------+ 006. #define def_ExpansionPrefix def_MousePrefixName + "Expansion_" 007. #define def_ExpansionBtn1 def_ExpansionPrefix + "B1" 008. #define def_ExpansionBtn2 def_ExpansionPrefix + "B2" 009. #define def_ExpansionBtn3 def_ExpansionPrefix + "B3" 010. //+------------------------------------------------------------------+ 011. class C_Study : public C_Mouse 012. { 013. private : 014. //+------------------------------------------------------------------+ 015. struct st00 016. { 017. eStatusMarket Status; 018. MqlRates Rate; 019. string szInfo; 020. color corP, 021. corN; 022. int HeightText; 023. bool bvT, bvD, bvP; 024. datetime TimeDevice; 025. }m_Info; 026. //+------------------------------------------------------------------+ 027. const datetime GetBarTime(void) 028. { 029. datetime dt; 030. int i0 = PeriodSeconds(); 031. 032. if (m_Info.Status == eInReplay) 033. { 034. if ((dt = m_Info.TimeDevice) == ULONG_MAX) return ULONG_MAX; 035. }else dt = TimeCurrent(); 036. if (m_Info.Rate.time <= dt) 037. m_Info.Rate.time = (datetime)(((ulong) dt / i0) * i0) + i0; 038. 039. return m_Info.Rate.time - dt; 040. } 041. //+------------------------------------------------------------------+ 042. void Draw(void) 043. { 044. double v1; 045. 046. if (m_Info.bvT) 047. { 048. ObjectSetInteger(GetInfoTerminal().ID, def_ExpansionBtn1, OBJPROP_YDISTANCE, GetInfoMouse().Position.Y_Adjusted - 18); 049. ObjectSetString(GetInfoTerminal().ID, def_ExpansionBtn1, OBJPROP_TEXT, m_Info.szInfo); 050. } 051. if (m_Info.bvD) 052. { 053. v1 = NormalizeDouble((((GetInfoMouse().Position.Price - m_Info.Rate.close) / m_Info.Rate.close) * 100.0), 2); 054. ObjectSetInteger(GetInfoTerminal().ID, def_ExpansionBtn2, OBJPROP_YDISTANCE, GetInfoMouse().Position.Y_Adjusted - 1); 055. ObjectSetInteger(GetInfoTerminal().ID, def_ExpansionBtn2, OBJPROP_BGCOLOR, (v1 < 0 ? m_Info.corN : m_Info.corP)); 056. ObjectSetString(GetInfoTerminal().ID, def_ExpansionBtn2, OBJPROP_TEXT, StringFormat("%.2f%%", MathAbs(v1))); 057. } 058. if (m_Info.bvP) 059. { 060. v1 = NormalizeDouble((((iClose(GetInfoTerminal().szSymbol, PERIOD_D1, 0) - m_Info.Rate.close) / m_Info.Rate.close) * 100.0), 2); 061. ObjectSetInteger(GetInfoTerminal().ID, def_ExpansionBtn3, OBJPROP_YDISTANCE, GetInfoMouse().Position.Y_Adjusted - 1); 062. ObjectSetInteger(GetInfoTerminal().ID, def_ExpansionBtn3, OBJPROP_BGCOLOR, (v1 < 0 ? m_Info.corN : m_Info.corP)); 063. ObjectSetString(GetInfoTerminal().ID, def_ExpansionBtn3, OBJPROP_TEXT, StringFormat("%.2f%%", MathAbs(v1))); 064. } 065. } 066. //+------------------------------------------------------------------+ 067. inline void CreateObjInfo(EnumEvents arg) 068. { 069. switch (arg) 070. { 071. case evShowBarTime: 072. C_Mouse::CreateObjToStudy(2, 110, def_ExpansionBtn1, clrPaleTurquoise); 073. m_Info.bvT = true; 074. break; 075. case evShowDailyVar: 076. C_Mouse::CreateObjToStudy(2, 53, def_ExpansionBtn2); 077. m_Info.bvD = true; 078. break; 079. case evShowPriceVar: 080. C_Mouse::CreateObjToStudy(58, 53, def_ExpansionBtn3); 081. m_Info.bvP = true; 082. break; 083. } 084. } 085. //+------------------------------------------------------------------+ 086. inline void RemoveObjInfo(EnumEvents arg) 087. { 088. string sz; 089. 090. switch (arg) 091. { 092. case evHideBarTime: 093. sz = def_ExpansionBtn1; 094. m_Info.bvT = false; 095. break; 096. case evHideDailyVar: 097. sz = def_ExpansionBtn2; 098. m_Info.bvD = false; 099. break; 100. case evHidePriceVar: 101. sz = def_ExpansionBtn3; 102. m_Info.bvP = false; 103. break; 104. } 105. ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, false); 106. ObjectDelete(GetInfoTerminal().ID, sz); 107. ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, true); 108. } 109. //+------------------------------------------------------------------+ 110. public : 111. //+------------------------------------------------------------------+ 112. C_Study(long IdParam, string szShortName, color corH, color corP, color corN) 113. :C_Mouse(IdParam, szShortName, corH, corP, corN) 114. { 115. if (_LastError != ERR_SUCCESS) return; 116. ZeroMemory(m_Info); 117. m_Info.Status = eCloseMarket; 118. m_Info.Rate.close = iClose(GetInfoTerminal().szSymbol, PERIOD_D1, ((GetInfoTerminal().szSymbol == def_SymbolReplay) || (macroGetDate(TimeCurrent()) != macroGetDate(iTime(GetInfoTerminal().szSymbol, PERIOD_D1, 0))) ? 0 : 1)); 119. m_Info.corP = corP; 120. m_Info.corN = corN; 121. CreateObjInfo(evShowBarTime); 122. CreateObjInfo(evShowDailyVar); 123. CreateObjInfo(evShowPriceVar); 124. } 125. //+------------------------------------------------------------------+ 126. void Update(const eStatusMarket arg) 127. { 128. datetime dt; 129. 130. switch (m_Info.Status = (m_Info.Status != arg ? arg : m_Info.Status)) 131. { 132. case eCloseMarket : 133. m_Info.szInfo = "Closed Market"; 134. break; 135. case eInReplay : 136. case eInTrading : 137. if ((dt = GetBarTime()) < ULONG_MAX) 138. { 139. m_Info.szInfo = TimeToString(dt, TIME_SECONDS); 140. break; 141. } 142. case eAuction : 143. m_Info.szInfo = "Auction"; 144. break; 145. default : 146. m_Info.szInfo = "ERROR"; 147. } 148. Draw(); 149. } 150. //+------------------------------------------------------------------+ 151. virtual void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam) 152. { 153. C_Mouse::DispatchMessage(id, lparam, dparam, sparam); 154. switch (id) 155. { 156. case CHARTEVENT_CUSTOM + evHideBarTime: 157. RemoveObjInfo(evHideBarTime); 158. break; 159. case CHARTEVENT_CUSTOM + evShowBarTime: 160. CreateObjInfo(evShowBarTime); 161. break; 162. case CHARTEVENT_CUSTOM + evHideDailyVar: 163. RemoveObjInfo(evHideDailyVar); 164. break; 165. case CHARTEVENT_CUSTOM + evShowDailyVar: 166. CreateObjInfo(evShowDailyVar); 167. break; 168. case CHARTEVENT_CUSTOM + evHidePriceVar: 169. RemoveObjInfo(evHidePriceVar); 170. break; 171. case CHARTEVENT_CUSTOM + evShowPriceVar: 172. CreateObjInfo(evShowPriceVar); 173. break; 174. case (CHARTEVENT_CUSTOM + evSetServerTime): 175. m_Info.TimeDevice = (datetime)dparam; 176. break; 177. case CHARTEVENT_MOUSE_MOVE: 178. Draw(); 179. break; 180. } 181. ChartRedraw(GetInfoTerminal().ID); 182. } 183. //+------------------------------------------------------------------+ 184. }; 185. //+------------------------------------------------------------------+ 186. #undef def_ExpansionBtn3 187. #undef def_ExpansionBtn2 188. #undef def_ExpansionBtn1 189. #undef def_ExpansionPrefix 190. #undef def_MousePrefixName 191. //+------------------------------------------------------------------+
C_Study.mqhファイルのソースコード
C_Studyクラスはほとんど変わっていないので、これ以上の説明は必要ありません。次に、インジケーターのコードを見てみましょう。
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. #property description "This is an indicator for graphical studies using the mouse." 04. #property description "This is an integral part of the Replay / Simulator system." 05. #property description "However it can be used in the real market." 06. #property version "1.56" 07. #property icon "/Images/Market Replay/Icons/Indicators.ico" 08. #property link "https://www.mql5.com/pt/articles/12000" 09. #property indicator_chart_window 10. #property indicator_plots 0 11. #property indicator_buffers 1 12. //+------------------------------------------------------------------+ 13. #include <Market Replay\Auxiliar\Study\C_Study.mqh> 14. //+------------------------------------------------------------------+ 15. C_Study *Study = NULL; 16. //+------------------------------------------------------------------+ 17. input long user00 = 0; //ID 18. input C_Study::eStatusMarket user01 = C_Study::eAuction; //Market Status 19. input color user02 = clrBlack; //Price Line 20. input color user03 = clrPaleGreen; //Positive Study 21. input color user04 = clrLightCoral; //Negative Study 22. //+------------------------------------------------------------------+ 23. C_Study::eStatusMarket m_Status; 24. int m_posBuff = 0; 25. double m_Buff[]; 26. //+------------------------------------------------------------------+ 27. int OnInit() 28. { 29. ResetLastError(); 30. Study = new C_Study(user00, "Indicator Mouse Study", user02, user03, user04); 31. if (_LastError != ERR_SUCCESS) return INIT_FAILED; 32. if ((*Study).GetInfoTerminal().szSymbol != def_SymbolReplay) 33. { 34. MarketBookAdd((*Study).GetInfoTerminal().szSymbol); 35. OnBookEvent((*Study).GetInfoTerminal().szSymbol); 36. m_Status = C_Study::eCloseMarket; 37. }else 38. m_Status = user01; 39. SetIndexBuffer(0, m_Buff, INDICATOR_DATA); 40. ArrayInitialize(m_Buff, EMPTY_VALUE); 41. 42. return INIT_SUCCEEDED; 43. } 44. //+------------------------------------------------------------------+ 45. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 46. { 47. m_posBuff = rates_total; 48. (*Study).Update(m_Status); 49. 50. return rates_total; 51. } 52. //+------------------------------------------------------------------+ 53. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 54. { 55. (*Study).DispatchMessage(id, lparam, dparam, sparam); 56. (*Study).SetBuffer(m_posBuff, m_Buff); 57. 58. ChartRedraw((*Study).GetInfoTerminal().ID); 59. } 60. //+------------------------------------------------------------------+ 61. void OnBookEvent(const string &symbol) 62. { 63. MqlBookInfo book[]; 64. C_Study::eStatusMarket loc = m_Status; 65. 66. if (symbol != (*Study).GetInfoTerminal().szSymbol) return; 67. MarketBookGet((*Study).GetInfoTerminal().szSymbol, book); 68. m_Status = (ArraySize(book) == 0 ? C_Study::eCloseMarket : C_Study::eInTrading); 69. for (int c0 = 0; (c0 < ArraySize(book)) && (m_Status != C_Study::eAuction); c0++) 70. if ((book[c0].type == BOOK_TYPE_BUY_MARKET) || (book[c0].type == BOOK_TYPE_SELL_MARKET)) m_Status = C_Study::eAuction; 71. if (loc != m_Status) (*Study).Update(m_Status); 72. } 73. //+------------------------------------------------------------------+ 74. void OnDeinit(const int reason) 75. { 76. if (reason != REASON_INITFAILED) 77. { 78. if ((*Study).GetInfoTerminal().szSymbol != def_SymbolReplay) 79. MarketBookRelease((*Study).GetInfoTerminal().szSymbol); 80. } 81. delete Study; 82. } 83. //+------------------------------------------------------------------+
マウス指標のソースコード
以前のコードとは異なる部分がいくつかありますが、コードの理解を妨げるほど重要な変更ではありません。ただし、簡単にコメントしておきたい点が2つあります。それは、47行目と56行目です。
47行目では、以前とは異なり、MetaTrader 5が提供する値をそのまま保存し、変更を加えないようになっています。値の変更は別の箇所でおこなうためです。
そして56行目では、C_Mouseクラスに情報を渡し、そこで処理をおこなった後にインジケーターバッファへ書き込みます。すべての複雑な処理をクラス内に移したことで、ここでは非常にシンプルな記述になっている点に注目してください。
結論
コードの修正コードの修正と適応を行った結果、現在開発中のリプレイ/シミュレーターシステムでマウスインジケーターを実際に使用できるようになりました。これにより、関係するアプリケーション間でメッセージ交換システムを利用しながら、バッファリーディングを通じて情報をやり取りすることが可能になります。唯一の制約として、常にバッファの1つの位置のみを使用する必要があります。これは、イベントシステムへの過負荷を防ぐために必要な制限です。しかし、この点については今後の議論で詳しく取り上げる予定です。
以下のビデオでは、システムの動作デモをご覧いただけます。また、添付ファイルにはコンパイル済みのコードが含まれているため、実際にテストを実行し、動作を確認していただけます。最後に、範囲エラーのテストについて補足しておきます。マウスインジケーターを前回の記事で使用したものに置き換えたうえで、本記事のサービスを実行すると、エラーが発生する様子を確認できます。
なお、本記事では使用するサービスコードについての説明はおこなっていません。このコードは、次回の記事で実際の開発の基盤として取り上げる予定です。次回の記事を楽しみにお待ちください。日々、システムが進化し、より興味深いものになっていきます。
デモビデオ
MetaQuotes Ltdによりポルトガル語から翻訳されました。
元の記事: https://www.mql5.com/pt/articles/12000




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