
リプレイシステムの開発(第51回):物事は複雑になる(III)
はじめに
前回の「リプレイシステムの開発(第50回):物事は複雑になる(II)」では、コントロール指標をさらに改良し、チャートの制限内で適切に動作するよう調整しました。これにより、リプレイ/シミュレーターサービスによって開かれたチャート内でのみ動作する仕様となりました。また、システムに必要なテンプレートではなく、カスタムテンプレートを使用できる機能を実装しました。
この改良により、リプレイ/シミュレーターシステムの操作性が大幅に向上し、システムを利用して調査をおこないたいと考えるユーザーにとってより魅力的なものになりました。ユーザーは、自分で作成したテンプレートをリプレイ/シミュレーターシステムで利用し、その後、同じテンプレートをライブ口座で活用することが可能です。この柔軟性を実現するため、現在もシステム全体の変更を進めています。
しかしながら、コントロール指標とユーザーとのインタラクションにおいて、依然として課題が残っています。これは、システム内にいくつかの冗長なポイントが存在するためです。さらに大きな問題として、システムが見た目ほど安全で安定していなかったことが挙げられます。この原因は、開発初期段階で、想定外の方法でシステムを使用しようとするユーザーの行動を十分に考慮できていなかったことにあります。しかし、現在進行中の方向転換と位置付けの見直しにより、セキュリティと安定性の向上が見込まれています。
現時点では、ユーザーエクスペリエンスの観点からはまだ不安定な部分がありますが、これらの問題は近いうちに解消される予定です。修正が完了すれば、システム全体の品質向上が期待できます。
前回の記事をお読みになった方(未読の方はぜひお読みください)は、サービスを介してコントロール指標の直接使用が開始されたことをご存じかと思います。それ以降、指標の配置は自由ではなくなり、特定のチャートに限定されるようになりました。
コードを冷静に分析した結果、更なる改良の余地があることに気付きました。ただし、それを実現するには、リプレイ/シミュレーターシステムで使用されている他のモジュールにもいくつか変更を加える必要があります。この記事では、こうした変更点の概要について詳しく説明します。これらのモジュールはユーザーにも公開されているため、システムのユーザーであり、かつ開発者である皆さんには、何が操作可能で何がそうでないのかを理解していただくことが重要です。これにより、モジュールの使用が不安定になるリスクを最小限に抑えることができます。一部のモジュールはリプレイ/シミュレーターシステムだけでなく、ライブ口座やデモ口座でも使用可能です。
今回の変更点は一見すると小規模なものに思えるかもしれません。しかし、さらなる改良を加える前にコードを安定させることが重要です。このため、この記事の内容が漠然としているように感じられるかもしれません。それでも、システムの開発プロセスを理解していただくことは、システムを適切に活用する上で欠かせない要素です。これを理解していない場合、システムを適切に使用することはできません。
それでは、具体的な変更点について説明していきましょう。
モジュールの広範な使用
最初に変更が必要なのは、コントロール指標のソースコードです。前回の記事で取り上げたコードでは、すでに開発・作成されていた一部のモジュールが使用されていませんでした。このため、コントロール指標とシステムの他モジュールとの間で、処理の不整合が発生していました。
その一例がマウス指標モジュールです。このモジュールは、マウス操作に関連するあらゆる処理を集約する目的で開発されました。これにより、新たに作成されるコードにおいて特別なテストや分析をおこなう必要がなくなります。すべてのマウス関連処理は、マウス指標モジュールによって一括して管理されます。
このマウス指標は、このリプレイ/シミュレーターシステムに関する連載の中で以前に開発されたものです。ただし、元々はリアル口座やデモ口座での利用を想定して設計されたものであり、現在の形式での使用には適していません。この点については後ほど詳しく説明します。
以下に示すコードでは、マウス指標モジュールをコントロール指標とどのように統合するかを確認できます。このような統合を可能にするため、すでにいくつかの修正と調整が加えられています。
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. #property icon "/Images/Market Replay/Icons/Replay - Device.ico" 04. #property description "Control indicator for the Replay-Simulator service." 05. #property description "This one doesn't work without the service loaded." 06. #property version "1.51" 07. #property link "https://www.mql5.com/ja/articles/11877" 08. #property indicator_chart_window 09. #property indicator_plots 0 10. //+------------------------------------------------------------------+ 11. #include <Market Replay\Service Graphics\C_Controls.mqh> 12. //+------------------------------------------------------------------+ 13. C_Controls *control = NULL; 14. //+------------------------------------------------------------------+ 15. input long user00 = 0; //ID 16. //+------------------------------------------------------------------+ 17. int OnInit() 18. { 19. u_Interprocess Info; 20. 21. ResetLastError(); 22. if (CheckPointer(control = new C_Controls(user00, "Market Replay Control", "Indicator Mouse Study")) == POINTER_INVALID) 23. SetUserError(C_Terminal::ERR_PointerInvalid); 24. if (_LastError != ERR_SUCCESS) 25. { 26. Print("Control indicator failed on initialization."); 27. return INIT_FAILED; 28. } 29. if (GlobalVariableCheck(def_GlobalVariableReplay)) Info.df_Value = GlobalVariableGet(def_GlobalVariableReplay); else Info.df_Value = 0; 30. EventChartCustom(user00, C_Controls::ev_WaitOff, 1, Info.df_Value, ""); 31. (*control).Init(Info.s_Infos.isPlay); 32. 33. return INIT_SUCCEEDED; 34. } 35. //+------------------------------------------------------------------+ 36. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 37. { 38. static bool bWait = false; 39. u_Interprocess Info; 40. 41. Info.df_Value = GlobalVariableGet(def_GlobalVariableReplay); 42. if (!bWait) 43. { 44. if (Info.s_Infos.isWait) 45. { 46. EventChartCustom(user00, C_Controls::ev_WaitOn, 1, 0, ""); 47. bWait = true; 48. } 49. }else if (!Info.s_Infos.isWait) 50. { 51. EventChartCustom(user00, C_Controls::ev_WaitOff, 1, Info.df_Value, ""); 52. bWait = false; 53. } 54. 55. return rates_total; 56. } 57. //+------------------------------------------------------------------+ 58. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 59. { 60. (*control).DispatchMessage(id, lparam, dparam, sparam); 61. } 62. //+------------------------------------------------------------------+ 63. void OnDeinit(const int reason) 64. { 65. switch (reason) 66. { 67. case REASON_TEMPLATE: 68. Print("Modified template. Replay/simulation system shutting down."); 69. case REASON_PARAMETERS: 70. case REASON_REMOVE: 71. case REASON_CHARTCLOSE: 72. if (ChartSymbol(user00) != def_SymbolReplay) break; 73. GlobalVariableDel(def_GlobalVariableReplay); 74. ChartClose(user00); 75. break; 76. } 77. delete control; 78. } 79. //+------------------------------------------------------------------+
コントロール指標のソースコード
このコードが以前のものと大きく異なることにお気づきかもしれません。特に注目すべき点は、OnInit関数の変更です。22行目では、コントロールクラスへの参照に関するこれまでの宣言とは全く異なる宣言が登場します。
22行目の内容を理解しようとすると、少し戸惑うかもしれません。しかし、この行では次の2つの重要な処理をおこなっています。
- 制御指標をモジュールに変換します。このモジュールは、他のモジュールがこの指標の機能を利用する際に必要となるものです。
- コントロール指標に対し、「マウスの解析にはコーディングを使用しない」という意図を伝えます。つまり、コントロール指標はマウス指標に対し、「現在ユーザーが何をしているのか、または何をしようとしているのか」を問い合わせ、その情報に基づいて適切な処理を実行する仕組みになっています。
この変更、つまり、マウス指標にユーザー、マウス、グラフィックス間のインタラクションを担当させる設計方針は、非常に合理的です。冗長な処理を避けることができるからです。仮に、マウス指標の問題を修正しても、それがコントロール指標の問題を引き起こし、さらに両者が互いに干渉し合うようでは本末転倒です。私たちが目指すのは、両者が調和して動作する仕組みです。このアプローチであれば、インタラクションの実装を調整するだけで済み、修正に無駄な時間を費やさずに済みます。
ここで注目すべき点として、コントロール指標内に、以前のようなC_Terminalクラスの記述が見当たらないことに気づくかもしれません。なぜでしょうか。その理由は「継承」にあります。コントロールクラスをC_Terminalクラスの子クラスとして定義しました。この変更の理由を詳しく説明するのは少し難しいですが、今後の処理を考慮すると必要不可欠な設計でした。
それでは、指標コードの最初の説明を念頭に置きながら、コントロールクラスのコードを見ていきましょう。
001. //+------------------------------------------------------------------+ 002. #property copyright "Daniel Jose" 003. //+------------------------------------------------------------------+ 004. #include "..\Auxiliar\Interprocess.mqh" 005. //+------------------------------------------------------------------+ 006. #define def_PathBMP "Images\\Market Replay\\Control\\" 007. #define def_ButtonPlay def_PathBMP + "Play.bmp" 008. #define def_ButtonPause def_PathBMP + "Pause.bmp" 009. #define def_ButtonLeft def_PathBMP + "Left.bmp" 010. #define def_ButtonLeftBlock def_PathBMP + "Left_Block.bmp" 011. #define def_ButtonRight def_PathBMP + "Right.bmp" 012. #define def_ButtonRightBlock def_PathBMP + "Right_Block.bmp" 013. #define def_ButtonPin def_PathBMP + "Pin.bmp" 014. #define def_ButtonWait def_PathBMP + "Wait.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. #resource "\\" + def_ButtonWait 023. //+------------------------------------------------------------------+ 024. #define def_PrefixObjectName "Market Replay _ " 025. #define def_NameObjectsSlider def_PrefixObjectName + "Slider" 026. #define def_PosXObjects 120 027. //+------------------------------------------------------------------+ 028. #include "..\Auxiliar\C_Terminal.mqh" 029. #include "..\Auxiliar\C_Mouse.mqh" 030. //+------------------------------------------------------------------+ 031. class C_Controls : private C_Terminal 032. { 033. protected: 034. enum EventCustom {ev_WaitOn, ev_WaitOff}; 035. private : 036. //+------------------------------------------------------------------+ 037. string m_szBtnPlay; 038. bool m_bWait; 039. struct st_00 040. { 041. string szBtnLeft, 042. szBtnRight, 043. szBtnPin, 044. szBarSlider, 045. szBarSliderBlock; 046. int posPinSlider, 047. posY, 048. Minimal; 049. }m_Slider; 050. C_Mouse *m_MousePtr; 051. //+------------------------------------------------------------------+ 052. inline void CreateObjectBitMap(int x, int y, string szName, string Resource1, string Resource2 = NULL) 053. { 054. ObjectCreate(GetInfoTerminal().ID, szName, OBJ_BITMAP_LABEL, 0, 0, 0); 055. ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_XDISTANCE, def_PosXObjects + x); 056. ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_YDISTANCE, y); 057. ObjectSetString(GetInfoTerminal().ID, szName, OBJPROP_BMPFILE, 0, "::" + Resource1); 058. ObjectSetString(GetInfoTerminal().ID, szName, OBJPROP_BMPFILE, 1, "::" + (Resource2 == NULL ? Resource1 : Resource2)); 059. ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_ZORDER, 1); 060. } 061. //+------------------------------------------------------------------+ 062. inline void CreteBarSlider(int x, int size) 063. { 064. ObjectCreate(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJ_RECTANGLE_LABEL, 0, 0, 0); 065. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_XDISTANCE, def_PosXObjects + x); 066. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_YDISTANCE, m_Slider.posY - 4); 067. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_XSIZE, size); 068. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_YSIZE, 9); 069. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_BGCOLOR, clrLightSkyBlue); 070. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_BORDER_COLOR, clrBlack); 071. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_WIDTH, 3); 072. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_BORDER_TYPE, BORDER_FLAT); 073. ObjectCreate(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJ_RECTANGLE_LABEL, 0, 0, 0); 074. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_XDISTANCE, def_PosXObjects + x); 075. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_YDISTANCE, m_Slider.posY - 9); 076. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_YSIZE, 19); 077. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_BGCOLOR, clrRosyBrown); 078. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_BORDER_TYPE, BORDER_RAISED); 079. } 080. //+------------------------------------------------------------------+ 081. void CreateBtnPlayPause(bool state) 082. { 083. m_szBtnPlay = def_PrefixObjectName + "Play"; 084. CreateObjectBitMap(0, 25, m_szBtnPlay, (m_bWait ? def_ButtonWait : def_ButtonPause), (m_bWait ? def_ButtonWait : def_ButtonPlay)); 085. ObjectSetInteger(GetInfoTerminal().ID, m_szBtnPlay, OBJPROP_STATE, state); 086. } 087. //+------------------------------------------------------------------+ 088. void CreteCtrlSlider(void) 089. { 090. u_Interprocess Info; 091. 092. m_Slider.szBarSlider = def_NameObjectsSlider + " Bar"; 093. m_Slider.szBarSliderBlock = def_NameObjectsSlider + " Bar Block"; 094. m_Slider.szBtnLeft = def_NameObjectsSlider + " BtnL"; 095. m_Slider.szBtnRight = def_NameObjectsSlider + " BtnR"; 096. m_Slider.szBtnPin = def_NameObjectsSlider + " BtnP"; 097. m_Slider.posY = 40; 098. CreteBarSlider(77, 436); 099. CreateObjectBitMap(47, 25, m_Slider.szBtnLeft, def_ButtonLeft, def_ButtonLeftBlock); 100. CreateObjectBitMap(511, 25, m_Slider.szBtnRight, def_ButtonRight, def_ButtonRightBlock); 101. CreateObjectBitMap(0, m_Slider.posY, m_Slider.szBtnPin, def_ButtonPin); 102. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBtnPin, OBJPROP_ANCHOR, ANCHOR_CENTER); 103. if (GlobalVariableCheck(def_GlobalVariableReplay)) Info.df_Value = GlobalVariableGet(def_GlobalVariableReplay); else Info.df_Value = 0; 104. m_Slider.Minimal = Info.s_Infos.iPosShift; 105. PositionPinSlider(Info.s_Infos.iPosShift); 106. } 107. //+------------------------------------------------------------------+ 108. inline void RemoveCtrlSlider(void) 109. { 110. ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, false); 111. ObjectsDeleteAll(GetInfoTerminal().ID, def_NameObjectsSlider); 112. ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, true); 113. } 114. //+------------------------------------------------------------------+ 115. inline void PositionPinSlider(int p, const int minimal = 0) 116. { 117. m_Slider.posPinSlider = (p < minimal ? minimal : (p > def_MaxPosSlider ? def_MaxPosSlider : p)); 118. m_Slider.posPinSlider = (p < m_Slider.Minimal ? m_Slider.Minimal : (p > def_MaxPosSlider ? def_MaxPosSlider : p)); 119. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBtnPin, OBJPROP_XDISTANCE, m_Slider.posPinSlider + def_PosXObjects + 95); 120. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBtnLeft, OBJPROP_STATE, m_Slider.posPinSlider != minimal); 121. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBtnLeft, OBJPROP_STATE, m_Slider.posPinSlider != m_Slider.Minimal); 122. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBtnRight, OBJPROP_STATE, m_Slider.posPinSlider < def_MaxPosSlider); 123. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_XSIZE, minimal + 2); 124. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_XSIZE, m_Slider.Minimal + 2); 125. ChartRedraw(GetInfoTerminal().ID); 126. } 127. //+------------------------------------------------------------------+ 128. public : 129. //+------------------------------------------------------------------+ 130. C_Controls(const long Arg0, const string szShortName, C_Mouse *MousePtr) 131. :C_Terminal(Arg0), 132. m_bWait(false), 133. m_MousePtr(MousePtr) 134. { 135. if (!IndicatorCheckPass(szShortName)) SetUserError(C_Terminal::ERR_Unknown); 136. m_szBtnPlay = NULL; 137. m_Slider.szBarSlider = NULL; 138. m_Slider.szBtnPin = NULL; 139. m_Slider.szBtnLeft = NULL; 140. m_Slider.szBtnRight = NULL; 141. } 142. //+------------------------------------------------------------------+ 143. ~C_Controls() 144. { 145. ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, false); 146. ObjectsDeleteAll(GetInfoTerminal().ID, def_PrefixObjectName); 147. } 148. //+------------------------------------------------------------------+ 149. void Init(const bool state) 150. { 151. CreateBtnPlayPause(state); 152. GlobalVariableTemp(def_GlobalVariableReplay); 153. if (!state) CreteCtrlSlider(); 154. ChartRedraw(GetInfoTerminal().ID); 155. } 156. //+------------------------------------------------------------------+ 157. void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam) 158. { 159. u_Interprocess Info; 160. 161. switch (id) 162. { 163. case (CHARTEVENT_CUSTOM + C_Controls::ev_WaitOn): 164. if (lparam == 0) break; 165. m_bWait = true; 166. CreateBtnPlayPause(true); 167. break; 168. case (CHARTEVENT_CUSTOM + C_Controls::ev_WaitOff): 169. if (lparam == 0) break; 170. m_bWait = false; 171. Info.df_Value = dparam; 172. CreateBtnPlayPause(Info.s_Infos.isPlay); 173. break; 174. case CHARTEVENT_OBJECT_DELETE: 175. if (StringSubstr(sparam, 0, StringLen(def_PrefixObjectName)) == def_PrefixObjectName) 176. { 177. if (StringSubstr(sparam, 0, StringLen(def_NameObjectsSlider)) == def_NameObjectsSlider) 178. { 179. RemoveCtrlSlider(); 180. CreteCtrlSlider(); 181. }else 182. { 183. Info.df_Value = GlobalVariableGet(def_GlobalVariableReplay); 184. CreateBtnPlayPause(Info.s_Infos.isPlay); 185. } 186. ChartRedraw(GetInfoTerminal().ID); 187. } 188. break; 189. } 190. } 191. //+------------------------------------------------------------------+ 192. }; 193. //+------------------------------------------------------------------+ 194. #undef def_PosXObjects 195. #undef def_ButtonPlay 196. #undef def_ButtonPause 197. #undef def_ButtonLeft 198. #undef def_ButtonRight 199. #undef def_ButtonPin 200. #undef def_NameObjectsSlider 201. #undef def_PrefixObjectName 202. #undef def_PathBMP 203. //+------------------------------------------------------------------+
C_Controlクラスのソースコード
コードに大きな変更が見られないかもしれません。実際、変更は非常に微妙です。まず、コントロールクラスはC_Terminalクラスのprivate継承クラスです。これは31行目にあります。これにより、C_Terminalクラスにアクセスするためにポインタを使用する必要がなくなりました。言い換えると、C_Terminalクラスへの一括アクセスが直接実行可能となり、特定のコンパイルの詳細によっては、場合によって少し高速になりますが、現在ではそれほど重要ではありません。
クラスコンストラクターが宣言されている130行目には興味深い点があります。この初期段階では、サービスだけがこのコントロール指標にアクセスできることに注意してください。135行目を見てください。実際にはこれは必須ではありませんが、コードの再利用性を確保するために、指標が意図通りに動作することを保証しています。つまり、チャートごとに指標は1つだけである必要があります。繰り返しますが、この初期段階では、サービスだけがこの指標にアクセスしてチャートに追加できるため、この措置は必須ではありません。他の方法もありますが、仕組みを理解せずにリソースを使用することを推奨したくないため、ここでは詳細を省略します。
このコンストラクタに関して言及する価値があるもう1つの点は、133行目で、マウス指標にアクセスするためのポインタをprivateグローバルクラス変数に格納していることです。ただし、これは後ほど明らかになります。
よく見ると、このコードには微細ながらも重要な変更が施されています。MetaTrader 5において非常に大きな違いをもたらす変更です。具体的には、ChartRedraw呼び出しが値を受け取るようになっています。通常、この呼び出しには値は渡されません。では、なぜ現在これをおこなう必要があるのでしょうか。その理由は、チャートIDの値が異なるためです。
この違いについては前回の記事で説明しましたが、それでも十分に明確ではないかもしれません。ここで改めて知識を整理しましょう。チャートIDに関する主な問題は、チャートを誰が開くかではなく、チャートにオブジェクトを配置するのは誰かという点にあります。ChartRedraw呼び出しに値を渡すべき場合とそうでない場合について少し考えてみましょう。
サービスがチャートを開くと(チャートの種類は問いません)、このチャートにはMetaTrader 5からIDが割り当てられます。この識別子を確認すると、ChartOpenによって返される値として何らかの値が表示されます。問題ありません。しかし、たとえばこのチャートに指標を配置するには、プログラムがChartOpen関数によって返される同じ識別子を使用する必要があります。ここまでは明らかですが、この時点で問題が発生します。
ユーザーが同じチャートに指標を配置すると、プログラムがChartOpenを使用して取得したチャートIDと同じIDを取得できません。これにより混乱が生じるように思えますが、そうではありません。サービスがチャートを開いた場合、ChartOpenを通じてID値を取得し、この値を用いて指標ハンドルを作成し、目的のチャートに指標を配置する必要があります。一方で、指標がObjectCreate呼び出しを使用してチャートにオブジェクトを配置する場合には、MetaTrader 5がどのチャートが対象かを認識できるよう、適切な識別子を指定する必要があります。
もしチャートがChartOpenで開かれていた場合、提供されるIDはChartIDで取得されたIDと一致してはなりません。ChartIDを使用した場合、ChartOpenで開かれたチャートにObjectCreateを呼び出しても、オブジェクトは表示されません。しかし、同じコードがユーザーまたはObjectCreate関数を使用するテンプレートによってチャート上に配置される場合には、ChartIDが提供する値をIDとして使用する必要があります。
非常に混乱しているように感じるかもしれませんが、まさにそれが必要な考え方です。このため、数回前の記事で、この問題に対応するためにC_Terminalクラスが更新されました。
しかし、コード上では誰がチャートを操作したかを正確に把握できないため、正しいIDを返すためにC_Terminalクラスへの呼び出しを使用します。この結果、ChartRedraw関数は期待通りチャートを正しく更新します。
これらの複雑さにもかかわらず、157行目から始まるメッセージハンドラでは、このコードがMetaTrader 5で報告されるイベントを処理する役割を担っていることがわかります。マウスの動きやクリックイベントの解析は削除されましたが、それは同じ関数内でこれらのイベントを異なる方法で処理する必要があるからです。
ただし、処理を開始する前に、いくつかの追加変更をおこなう必要があります。現時点では、指標コードやサービスコードには変更がありませんが、マウス指標コードに戻り、いくつかの変更を加える必要があります。
マウス指標コードの更新
これからおこなうこの更新は、マウス指標で既に使用されているものと競合することはありません。この更新が必要なのは、まさに上述の理由からです。リプレイ/シミュレーターサービスを実行すると、チャート上でマウス指標が起動し、リプレイ/シミュレーターシステムとやり取りできるようになります。ただし、コードが必要な変更を自動でおこなうため、マウス指標は、ユーザーが手動でチャート上に配置した場合やテンプレートを使用して配置した場合と同じように動作します。そのため、この点について心配する必要はありません。
問題が発生するのは、リプレイ/シミュレーターシステムを適切に初期化するためのツールを使用する場合です。そこで、まず更新された最初の部分、すなわちマウス指標のソースコードを確認してみましょう。以下が完全なコードです。
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.51" 07. #property icon "/Images/Market Replay/Icons/Indicators.ico" 08. #property link "https://www.mql5.com/ja/articles/11877" 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 - 4; 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. SetBuffer(); 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. delete Study; 81. } 82. } 83. //+------------------------------------------------------------------+ 84. inline void SetBuffer(void) 85. { 86. uCast_Double Info; 87. 88. m_posBuff = (m_posBuff < 0 ? 0 : m_posBuff); 89. m_Buff[m_posBuff + 0] = (*Study).GetInfoMouse().Position.Price; 90. Info._datetime = (*Study).GetInfoMouse().Position.dt; 91. m_Buff[m_posBuff + 1] = Info.dValue; 92. Info._int[0] = (*Study).GetInfoMouse().Position.X; 93. Info._int[1] = (*Study).GetInfoMouse().Position.Y; 94. m_Buff[m_posBuff + 2] = Info.dValue; 95. Info._char[0] = ((*Study).GetInfoMouse().ExecStudy == C_Mouse::eStudyNull ? (char)(*Study).GetInfoMouse().ButtonStatus : 0); 96. m_Buff[m_posBuff + 3] = Info.dValue; 97. } 98. //+------------------------------------------------------------------+
マウス指標のソースコード
注意:このコードと以前のバージョンの間に違いはないように思えるかもしれません。しかし、確かに違いは存在します。そして、その違いには細心の注意を払う必要があります。
ユーザーにとって、主な違いは17行目にあります。この行はおそらく、コード全体で最も複雑な部分です。理想的には、ユーザーがこの行を変更したり、指標インターフェイス上で表示できないようにするべきです。なぜなら、新しいユーザーがこの行を入力として変更したくなる可能性があるからです。しかし、この値は絶対に変更してはならないことに注意してください。この値は指標を呼び出すプログラム、または指標自体によって設定される必要があります。ユーザーがこの値を変更することは決して許容されません。他の値については、ユーザーが自由に設定や変更をおこなうことができますが、ID値だけは例外です。
さらに、30行目の内容も興味深い点です。ここでは、スタディクラスのポインタを初期化しています。以前は、コンストラクタは主にマウス指標で使用される色の3つの値を受け取るのみでしたが、現在では2つの追加値を受け取ります。1つ目はチャートID、2つ目は指標名です。この指標名は、指標バッファーへの後続のアクセスに使用されます。そのため、マウス指標がチャート上に存在し、そのバッファーを読み取る必要がある場合は、ここで指定された名前を使用することになります。
コードにはさらにいくつかの違いがありますが、それらの理解はそれほど難しくないため、ここでは詳細には触れません。それでは次に、C_Studyクラスのコードを見ていきましょう。このコードでは、C_Terminalクラスへのアクセスにポインターではなく継承を使用しています。
001. //+------------------------------------------------------------------+ 002. #property copyright "Daniel Jose" 003. //+------------------------------------------------------------------+ 004. #include "..\C_Mouse.mqh" 005. //+------------------------------------------------------------------+ 006. #define def_ExpansionPrefix "MouseExpansion_" 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. }m_Info; 024. //+------------------------------------------------------------------+ 025. const datetime GetBarTime(void) 026. { 027. datetime dt; 028. u_Interprocess info; 029. int i0 = PeriodSeconds(); 030. 031. if (m_Info.Status == eInReplay) 032. { 033. if (!GlobalVariableGet(def_GlobalVariableServerTime, info.df_Value)) return ULONG_MAX; 034. if ((dt = info.ServerTime) == 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. ObjectSetInteger(GetInfoTerminal().ID, def_ExpansionBtn1, OBJPROP_YDISTANCE, GetInfoMouse().Position.Y - 18); 047. ObjectSetInteger(GetInfoTerminal().ID, def_ExpansionBtn2, OBJPROP_YDISTANCE, GetInfoMouse().Position.Y - 1); 048. ObjectSetInteger(GetInfoTerminal().ID, def_ExpansionBtn3, OBJPROP_YDISTANCE, GetInfoMouse().Position.Y - 1); 049. ObjectSetString(GetInfoTerminal().ID, def_ExpansionBtn1, OBJPROP_TEXT, m_Info.szInfo); 050. v1 = NormalizeDouble(100.0 - ((m_Info.Rate.close / GetInfoMouse().Position.Price) * 100.0), 2); 051. ObjectSetInteger(GetInfoTerminal().ID, def_ExpansionBtn2, OBJPROP_BGCOLOR, (v1 < 0 ? m_Info.corN : m_Info.corP)); 052. ObjectSetString(GetInfoTerminal().ID, def_ExpansionBtn2, OBJPROP_TEXT, StringFormat("%.2f%%", MathAbs(v1))); 053. v1 = NormalizeDouble(100.0 - ((m_Info.Rate.close / iClose(GetInfoTerminal().szSymbol, PERIOD_D1, 0)) * 100.0), 2); 054. ObjectSetInteger(GetInfoTerminal().ID, def_ExpansionBtn3, OBJPROP_BGCOLOR, (v1 < 0 ? m_Info.corN : m_Info.corP)); 055. ObjectSetString(GetInfoTerminal().ID, def_ExpansionBtn3, OBJPROP_TEXT, StringFormat("%.2f%%", MathAbs(v1))); 056. } 057. //+------------------------------------------------------------------+ 058. public : 059. //+------------------------------------------------------------------+ 060. C_Study(long IdParam, string szShortName, color corH, color corP, color corN) 061. :C_Mouse(IdParam, szShortName, corH, corP, corN) 062. { 063. if (_LastError != ERR_SUCCESS) return; 064. ZeroMemory(m_Info); 065. m_Info.Status = eCloseMarket; 066. m_Info.Rate.close = iClose(GetInfoTerminal().szSymbol, PERIOD_D1, ((GetInfoTerminal().szSymbol == def_SymbolReplay) || (macroGetDate(TimeCurrent()) != macroGetDate(iTime(GetInfoTerminal().szSymbol, PERIOD_D1, 0))) ? 0 : 1)); 067. m_Info.corP = corP; 068. m_Info.corN = corN; 069. CreateObjectInfo(2, 110, def_ExpansionBtn1, clrPaleTurquoise); 070. CreateObjectInfo(2, 53, def_ExpansionBtn2); 071. CreateObjectInfo(58, 53, def_ExpansionBtn3); 072. } 073. //+------------------------------------------------------------------+ 074. ~C_Study() 075. { 076. ObjectsDeleteAll(GetInfoTerminal().ID, def_ExpansionPrefix); 077. } 078. //+------------------------------------------------------------------+ 079. void Update(const eStatusMarket arg) 080. { 081. datetime dt; 082. 083. switch (m_Info.Status = (m_Info.Status != arg ? arg : m_Info.Status)) 084. { 085. case eCloseMarket : m_Info.szInfo = "Closed Market"; 086. break; 087. case eInReplay : 088. case eInTrading : 089. if ((dt = GetBarTime()) < ULONG_MAX) 090. { 091. m_Info.szInfo = TimeToString(dt, TIME_SECONDS); 092. break; 093. } 094. case eAuction : m_Info.szInfo = "Auction"; 095. break; 096. default : m_Info.szInfo = "ERROR"; 097. } 098. Draw(); 099. } 100. //+------------------------------------------------------------------+ 101. virtual void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam) 102. { 103. C_Mouse::DispatchMessage(id, lparam, dparam, sparam); 104. if (id == CHARTEVENT_MOUSE_MOVE) Draw(); 105. } 106. //+------------------------------------------------------------------+ 107. }; 108. //+------------------------------------------------------------------+ 109. #undef def_ExpansionBtn3 110. #undef def_ExpansionBtn2 111. #undef def_ExpansionBtn1 112. #undef def_ExpansionPrefix 113. //+------------------------------------------------------------------+
C_Studyクラスのソースコード
このコードと古いコードの間には実質的な違いはありません。唯一の違いは、グラフ識別子をC_Terminalクラスで直接探している点です。以前はポインタを使用していましたが、現在は継承を使用しています。それでは、この継承はどこから来ているのでしょうか。それはC_Mouseクラスから来ています。この継承の仕組みをより深く理解するために、次にC_Mouseクラスのコードを見てみましょう。
001. //+------------------------------------------------------------------+ 002. #property copyright "Daniel Jose" 003. //+------------------------------------------------------------------+ 004. #include "C_Terminal.mqh" 005. #include "Interprocess.mqh" 006. //+------------------------------------------------------------------+ 007. #define def_MousePrefixName "MouseBase_" 008. #define def_NameObjectLineH def_MousePrefixName + "H" 009. #define def_NameObjectLineV def_MousePrefixName + "TV" 010. #define def_NameObjectLineT def_MousePrefixName + "TT" 011. #define def_NameObjectStudy def_MousePrefixName + "TB" 012. //+------------------------------------------------------------------+ 013. class C_Mouse : public C_Terminal 014. { 015. public : 016. enum eStatusMarket {eCloseMarket, eAuction, eInTrading, eInReplay}; 017. enum eBtnMouse {eKeyNull = 0x00, eClickLeft = 0x01, eClickRight = 0x02, eSHIFT_Press = 0x04, eCTRL_Press = 0x08, eClickMiddle = 0x10}; 018. struct st_Mouse 019. { 020. struct st00 021. { 022. int X, 023. Y; 024. double Price; 025. datetime dt; 026. }Position; 027. uint ButtonStatus; 028. bool ExecStudy; 029. }; 030. //+------------------------------------------------------------------+ 031. protected: 032. enum eEventsMouse {ev_HideMouse, ev_ShowMouse}; 033. //+------------------------------------------------------------------+ 034. void CreateObjectInfo(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. //+------------------------------------------------------------------+ 069. void GetDimensionText(const string szArg, int &w, int &h) 070. { 071. TextSetFont("Lucida Console", -100, FW_NORMAL); 072. TextGetSize(szArg, w, h); 073. h += 5; 074. w += 5; 075. } 076. //+------------------------------------------------------------------+ 077. void CreateStudy(void) 078. { 079. if (m_Mem.IsFull) 080. { 081. CreateObjectGraphics(def_NameObjectLineV, OBJ_VLINE, m_Info.corLineH); 082. CreateObjectGraphics(def_NameObjectLineT, OBJ_TREND, m_Info.corLineH); 083. ObjectSetInteger(GetInfoTerminal().ID, def_NameObjectLineT, OBJPROP_WIDTH, 2); 084. CreateObjectInfo(0, 0, def_NameObjectStudy); 085. } 086. m_Info.Study = eStudyCreate; 087. } 088. //+------------------------------------------------------------------+ 089. void ExecuteStudy(const double memPrice) 090. { 091. double v1 = GetInfoMouse().Position.Price - memPrice; 092. int w, h; 093. 094. if (!CheckClick(eClickLeft)) 095. { 096. m_Info.Study = eStudyNull; 097. ChartSetInteger(GetInfoTerminal().ID, CHART_MOUSE_SCROLL, true); 098. if (m_Mem.IsFull) ObjectsDeleteAll(GetInfoTerminal().ID, def_MousePrefixName + "T"); 099. }else if (m_Mem.IsFull) 100. { 101. string sz1 = StringFormat(" %." + (string)GetInfoTerminal().nDigits + "f [ %d ] %02.02f%% ", 102. MathAbs(v1), Bars(GetInfoTerminal().szSymbol, PERIOD_CURRENT, m_Mem.dt, GetInfoMouse().Position.dt) - 1, MathAbs((v1 /memPrice) * 100.0))); 103. GetDimensionText(sz1, w, h); 104. ObjectSetString(GetInfoTerminal().ID, def_NameObjectStudy, OBJPROP_TEXT, sz1); 105. ObjectSetInteger(GetInfoTerminal().ID, def_NameObjectStudy, OBJPROP_BGCOLOR, (v1 < 0 ? m_Info.corTrendN : m_Info.corTrendP)); 106. ObjectSetInteger(GetInfoTerminal().ID, def_NameObjectStudy, OBJPROP_XSIZE, w); 107. ObjectSetInteger(GetInfoTerminal().ID, def_NameObjectStudy, OBJPROP_YSIZE, h); 108. ObjectSetInteger(GetInfoTerminal().ID, def_NameObjectStudy, OBJPROP_XDISTANCE, GetInfoMouse().Position.X - w); 109. ObjectSetInteger(GetInfoTerminal().ID, def_NameObjectStudy, OBJPROP_YDISTANCE, GetInfoMouse().Position.Y - (v1 < 0 ? 1 : h)); 110. ObjectMove(GetInfoTerminal().ID, def_NameObjectLineT, 1, GetInfoMouse().Position.dt, GetInfoMouse().Position.Price); 111. ObjectSetInteger(GetInfoTerminal().ID, def_NameObjectLineT, OBJPROP_COLOR, (memPrice > GetInfoMouse().Position.Price ? m_Info.corTrendN : m_Info.corTrendP)); 112. } 113. m_Info.Data.ButtonStatus = eKeyNull; 114. } 115. //+------------------------------------------------------------------+ 116. public : 117. //+------------------------------------------------------------------+ 118. C_Mouse(const long id, const string szShortName) 119. :C_Terminal(id) 120. { 121. m_Mem.szShortName = szShortName; 122. } 123. //+------------------------------------------------------------------+ 124. C_Mouse(const long id, const string szShortName, color corH, color corP, color corN) 125. :C_Terminal(id) 126. { 127. if (!IndicatorCheckPass(szShortName)) SetUserError(C_Terminal::ERR_Unknown); 128. m_Mem.szShortName = NULL; 129. if (_LastError != ERR_SUCCESS) return; 130. m_Mem.CrossHair = (bool)ChartGetInteger(GetInfoTerminal().ID, CHART_CROSSHAIR_TOOL); 131. ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_MOUSE_MOVE, true); 132. ChartSetInteger(GetInfoTerminal().ID, CHART_CROSSHAIR_TOOL, false); 133. ZeroMemory(m_Info); 134. m_Info.corLineH = corH; 135. m_Info.corTrendP = corP; 136. m_Info.corTrendN = corN; 137. m_Info.Study = eStudyNull; 138. if (m_Mem.IsFull = (corP != clrNONE) && (corH != clrNONE) && (corN != clrNONE)) 139. CreateObjectGraphics(def_NameObjectLineH, OBJ_HLINE, m_Info.corLineH); 140. } 141. //+------------------------------------------------------------------+ 142. ~C_Mouse() 143. { 144. ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, 0, false); 145. ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_MOUSE_MOVE, false); 146. ChartSetInteger(GetInfoTerminal().ID, CHART_CROSSHAIR_TOOL, m_Mem.CrossHair); 147. ObjectsDeleteAll(GetInfoTerminal().ID, def_MousePrefixName); 148. } 149. //+------------------------------------------------------------------+ 150. inline bool CheckClick(const eBtnMouse value) 151. { 152. return (GetInfoMouse().ButtonStatus & value) == value; 153. } 154. //+------------------------------------------------------------------+ 155. inline const st_Mouse GetInfoMouse(void) 156. { 157. if (m_Mem.szShortName != NULL) 158. { 159. double Buff[]; 160. uCast_Double loc; 161. int handle = ChartIndicatorGet(GetInfoTerminal().ID, 0, m_Mem.szShortName); 162. 163. ZeroMemory(m_Info.Data); 164. if (CopyBuffer(handle, 0, 0, 4, Buff) == 4) 165. { 166. m_Info.Data.Position.Price = Buff[0]; 167. loc.dValue = Buff[1]; 168. m_Info.Data.Position.dt = loc._datetime; 169. loc.dValue = Buff[2]; 170. m_Info.Data.Position.X = loc._int[0]; 171. m_Info.Data.Position.Y = loc._int[1]; 172. loc.dValue = Buff[3]; 173. m_Info.Data.ButtonStatus = loc._char[0]; 174. IndicatorRelease(handle); 175. } 176. } 177. 178. return m_Info.Data; 179. } 180. //+------------------------------------------------------------------+ 181. virtual void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam) 182. { 183. int w = 0; 184. static double memPrice = 0; 185. 186. if (m_Mem.szShortName == NULL) switch (id) 187. { 188. case (CHARTEVENT_CUSTOM + ev_HideMouse): 189. if (m_Mem.IsFull) ObjectSetInteger(GetInfoTerminal().ID, def_NameObjectLineH, OBJPROP_COLOR, clrNONE); 190. break; 191. case (CHARTEVENT_CUSTOM + ev_ShowMouse): 192. if (m_Mem.IsFull) ObjectSetInteger(GetInfoTerminal().ID, def_NameObjectLineH, OBJPROP_COLOR, m_Info.corLineH); 193. break; 194. case CHARTEVENT_MOUSE_MOVE: 195. ChartXYToTimePrice(GetInfoTerminal().ID, m_Info.Data.Position.X = (int)lparam, m_Info.Data.Position.Y = (int)dparam, w, m_Info.Data.Position.dt, m_Info.Data.Position.Price); 196. if (m_Mem.IsFull) ObjectMove(GetInfoTerminal().ID, def_NameObjectLineH, 0, 0, m_Info.Data.Position.Price = AdjustPrice(m_Info.Data.Position.Price)); 197. m_Info.Data.Position.dt = AdjustTime(m_Info.Data.Position.dt); 198. ChartTimePriceToXY(GetInfoTerminal().ID, w, m_Info.Data.Position.dt, m_Info.Data.Position.Price, m_Info.Data.Position.X, m_Info.Data.Position.Y); 199. if ((m_Info.Study != eStudyNull) && (m_Mem.IsFull)) ObjectMove(GetInfoTerminal().ID, def_NameObjectLineV, 0, m_Info.Data.Position.dt, 0); 200. m_Info.Data.ButtonStatus = (uint) sparam; 201. if (CheckClick(eClickMiddle)) 202. if ((!m_Mem.IsFull) || ((color)ObjectGetInteger(GetInfoTerminal().ID, def_NameObjectLineH, OBJPROP_COLOR) != clrNONE)) CreateStudy(); 203. if (CheckClick(eClickLeft) && (m_Info.Study == eStudyCreate)) 204. { 205. ChartSetInteger(GetInfoTerminal().ID, CHART_MOUSE_SCROLL, false); 206. if (m_Mem.IsFull) ObjectMove(GetInfoTerminal().ID, def_NameObjectLineT, 0, m_Mem.dt = GetInfoMouse().Position.dt, memPrice = GetInfoMouse().Position.Price); 207. m_Info.Study = eStudyExecute; 208. } 209. if (m_Info.Study == eStudyExecute) ExecuteStudy(memPrice); 210. m_Info.Data.ExecStudy = m_Info.Study == eStudyExecute; 211. break; 212. case CHARTEVENT_OBJECT_DELETE: 213. if ((m_Mem.IsFull) && (sparam == def_NameObjectLineH)) CreateObjectGraphics(def_NameObjectLineH, OBJ_HLINE, m_Info.corLineH); 214. break; 215. } 216. } 217. //+------------------------------------------------------------------+ 218. }; 219. //+------------------------------------------------------------------+ 220. #undef def_MousePrefixName 221. #undef def_NameObjectLineV 222. #undef def_NameObjectLineH 223. #undef def_NameObjectLineT 224. #undef def_NameObjectStudy 225. //+------------------------------------------------------------------+
C_Mouseクラスのソースコード
以前と同様に、このコードと古いコードの主な違いは、C_Terminalクラスへのアクセスにポインタが使用されなくなった点です。この役割は、13行目に記載されたC_TerminalクラスからC_Mouseクラスへの継承によって担われています。この結果、C_Terminalクラスのすべての手続きやpublic関数がC_Mouseクラスで拡張されるようになりました。コードの多くは、これまでのマウス指標に関連する記事ですでに説明されています。
このトピックに関する最後の記事は「リプレイシステムの開発(第31回):エキスパートアドバイザープロジェクト - C_Mouseクラス(V)」でした。この記事を参照することで、マウス指標に関する初期の記事にもアクセスできます。私のすべての記事と同様に、冒頭にリンクが含まれているため、これを利用してシステム開発の進化を追うことができます。
もしマウス指標の動作に関して疑問がある場合や、個人のニーズに合わせて調整したい場合は、指標の開発方法を説明した記事をご覧ください。その内容を理解した後で、この記事に戻り、必要な変更を加えてください。また、マウス指標に必要な機能を自由に追加することもできます。正しく作業をおこなえば、このリプレイ/シミュレーターシステムで独自のマウス指標を使用できるようになります。
プログラミングを本格的に学びたい方へのヒントとして、マウス指標に新しい機能を追加し、それをリプレイ/シミュレーターシステムで使用してみることをお勧めします。ただし、元の知識の出所を明記することを忘れないでください。それによって私も非常にうれしく感じます。それは単にモチベーションが上がるだけでなく、多くの人が解決不可能と考える問題をどのように克服したかを示すことも楽しいからです。
では、コードの説明に戻りましょう。C_Mouseクラスのコンストラクタを見てみると、125行目でC_Terminalクラスを初期化していることがわかります。この初期化時に使用される値は、指標コードのuser00という入力で提供されます。ユーザーがこの入力をそのままにしておくと、C_TerminalクラスがチャートIDを決定し、必要に応じてこのIDを使って目的のチャートにオブジェクトを配置します。
さらに、127行目では指標をロックして、同じチャート上に複数回配置できないようにします。これにより、ユーザーは別のマウス指標を同じチャート上に追加することができなくなります。ただし、指標はMetaTrader 5の指標リストには表示されます。
コードの残りの部分は、以前と同様の動作をします。このマウス指標は、MetaTrader 5にマウスイベントを受信し、それを指標が配置されているチャートに送信するよう指示します。MetaTrader 5でマウスイベントを受信するためには、マウス指標がチャート上に配置されている必要があります。別のチャートから指標にアクセスしようとしても意味がありません。たとえ別のチャート上で動作する機能があっても、MetaTrader 5は指標を含まないチャートに対してマウスイベントを生成しません。
131行目がこの役割を果たしています。そのため、指標がチャート上に存在している限り、任意のエキスパートアドバイザー(EA)や他の指標もマウスイベントを受け取ることができます。この点を理解しておけば、次のコードの動作や処理についての理解が深まるでしょう。次のコードでは、マウスイベントがMetaTrader 5によって他のコードに渡され、特定の方法で処理されるように、チャート上にマウス指標が存在している必要があります。
次に、コントロール指標の変更に進む前に、実際に何が起きているのかをより正確に説明する必要があると考えます。これにより、この記事の内容全体を正しく理解する助けとなるでしょう。
知識を理解し、吸収する
この記事で私が述べていることがナンセンスだと感じる方もいらっしゃるかもしれません。特に、MQL5の経験が豊富な方や、コーディングシステムの専門家を知っている方にとっては、私の説明が「当たり前」または冗長に思えるかもしれません。しかし、すべてを明確にするために、この記事にはマウス指標の実行可能ファイルを添付しました。これは、簡単に試せるようにするためです。連載で提供されている資料を使えば、この記事内に記載されたコードを手動で入力するのと同じ結果を得ることができます。このコードを実際に使ってみて、何が起こるのかを確認してください。この内容はリプレイ/シミュレーターシステムほど複雑ではありませんが、同じ原理で動作します。
01. //+------------------------------------------------------------------+ 02. #property service 03. #property copyright "Daniel Jose" 04. #property version "1.00" 05. //+------------------------------------------------------------------+ 06. input string user00 = "EURUSD"; // Symbol 07. //+------------------------------------------------------------------+ 08. void OnStart() 09. { 10. long id; 11. int handle; 12. 13. SymbolSelect(user00, true); 14. id = ChartOpen(user00, PERIOD_M5); 15. handle = iCustom(ChartSymbol(id), ChartPeriod(id), "\\Indicators\\Replay\\Mouse Study.ex5", id); 16. ChartIndicatorAdd(id, 0, handle); 17. IndicatorRelease(handle); 18. 19. Print("ID: ", id); 20. 21. while ((!_StopFlag) && (ChartSymbol(id) == user00)) Sleep(300); 22. 23. ChartClose(id); 24. SymbolSelect(user00, false); 25. } 26. //+------------------------------------------------------------------+
テストサービスのソースコード
6行目では、ユーザーに資産を指定するオプションを提供します。このオプションを使ってチャートを開くことができますが、銘柄は気配値表示ウィンドウ内に存在する必要があります。これを実現するために13行目を使用しています。14行目では、MetaTrader 5にチャートを開くように要求します。15行目では、チャートに指標追加するためのハンドルを作成し、MetaTrader 5にマウス指標をロードさせます。16行目では、チャートに指標を追加します。17行目では、ハンドルが不要になったため、それを解放します。19行目では、サービスによって開かれたチャートのIDを端末に表示するメッセージを出力します。次に、21行目では、ユーザーがチャートを閉じるかサービスを終了するのを待ちます。
ユーザーがチャートを閉じると、23行目は何も実行しません。しかし、ユーザーがサービスを終了すると、23行目は以前に開いたチャートを閉じます。24行目では、気配値表示ウィンドウから銘柄を削除します。もう1つの重要なポイントとして、銘柄を削除するにはその銘柄に関連する要素がない必要があることです。この場合、チャートはテスト専用のものであり、関連付けられた要素はありません。
このコードは一見、意味がないように思えるかもしれません。なぜこのようなものを作成し、どのように機能するかを説明しようとしているのでしょうか。これは、まさにすべての人が試みるべきことです。多くの人が慣れている範囲を超えて探求しようとすることが重要です。そうすることで初めて、すべての仕組みを本当に理解できるのです。
この考え方と本質を理解していただければ、それが次の記事にとって非常に重要な要素となります。ビデオ01を視聴してテストを確認してください。また、自分自身の基準を使ってテストしてみるのも良い方法です。強調したいのは、他人の「真実」を、自分にとって優れている、またはより有能であるように見えるからといって受け入れないことです。テストし、権威者に質問してください。そうすることで、真の知識が明らかになるのです。
ビデオ 01
結論
この記事は、これまでで最も難解な記事の1つでした。というのも、提示された情報が非常に複雑だったからです。プログラミング自体は私にとって非常にシンプルに感じられますが、その内容を説明するのは非常に難しいと感じます。その理由は、この情報が高度な知識を持つ人々だけでなく、このプログラミングの世界を理解しようと初めて試みる愛好者にも届く内容だからです。
次回の記事では、マウス指標、コントロール指標、リプレイ/シミュレーターシステム、およびシステムのユーザーである皆さんとのインタラクションを作成する方法について説明します。またすぐお会いしましょう。
MetaQuotes Ltdによりポルトガル語から翻訳されました。
元の記事: https://www.mql5.com/pt/articles/11877





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