リプレイシステムの開発(第44回):Chart Tradeプロジェクト(III)
はじめに
前回の「リプレイシステムの開発(第43回):Chart Tradeプロジェクト(II)」では、OBJ_CHART用のテンプレートデータの操作について説明しました。ただし、あの記事ではトピックの概要に焦点を当て、詳細な部分には触れていませんでした。これは、説明をよりシンプルにするために、非常に簡略化された手法を用いたからです。物事は一見シンプルに見えることが多いですが、実際にはそうではないケースもあり、全体を正確に理解するためには、まず最も基本的な部分をしっかり押さえる必要があります。
このコードは、前回ご覧いただいたように動作しますが、いくつかの重要な機能が欠けています。言い換えれば、データモデリングに一部改良を加えない限り、特定のタスクを効果的に実行することが困難です。この改良には、やや複雑なコーディングが必要となりますが、使用する基本的なコンセプトは変わりません。あくまでコードが少し複雑になるだけです。
この小さな事実のほかに、もうひとつ問題を解決します。お気づきのように、記事中にも書きましたが、このコードはあまり効率的ではありません。この問題に対処するため、コードを少し修正して、呼び出し回数を大幅に削減し、同時により適切なデータモデリングができるようにします。
新クラスの誕生:C_AdjustTemplate
必要な改良を実装するには、新しいクラスを作る必要があります。その全コードを以下に示します。
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #include "../Auxiliar/C_Terminal.mqh" 05. //+------------------------------------------------------------------+ 06. class C_AdjustTemplate 07. { 08. private : 09. string m_szName[]; 10. string m_szFind[]; 11. string m_szReplace[]; 12. string m_szFileName; 13. int m_maxIndex; 14. int m_FileIn; 15. int m_FileOut; 16. bool m_bSimple; 17. public : 18. //+------------------------------------------------------------------+ 19. C_AdjustTemplate(const string szFileNameIn, string szFileNameOut = NULL) 20. :m_maxIndex(0), 21. m_szFileName(szFileNameIn), 22. m_FileIn(INVALID_HANDLE), 23. m_FileOut(INVALID_HANDLE) 24. { 25. ResetLastError(); 26. if ((m_FileIn = FileOpen(szFileNameIn, FILE_TXT | FILE_READ)) == INVALID_HANDLE) SetUserError(C_Terminal::ERR_FileAcess); 27. if (_LastError == ERR_SUCCESS) 28. { 29. if (!(m_bSimple = (StringLen(szFileNameOut) > 0))) szFileNameOut = szFileNameIn + "_T"; 30. if ((m_FileOut = FileOpen(szFileNameOut, FILE_TXT | FILE_WRITE)) == INVALID_HANDLE) SetUserError(C_Terminal::ERR_FileAcess); 31. } 32. } 33. //+------------------------------------------------------------------+ 34. ~C_AdjustTemplate() 35. { 36. FileClose(m_FileIn); 37. if (m_FileOut != INVALID_HANDLE) 38. { 39. FileClose(m_FileOut); 40. if ((!m_bSimple) && (_LastError == ERR_SUCCESS)) FileMove(m_szFileName + "_T", 0, m_szFileName, FILE_REWRITE); 41. if ((!m_bSimple) && (_LastError != ERR_SUCCESS)) FileDelete(m_szFileName + "_T"); 42. } 43. ArrayResize(m_szName, 0); 44. ArrayResize(m_szFind, 0); 45. ArrayResize(m_szReplace, 0); 46. } 47. //+------------------------------------------------------------------+ 48. void Add(const string szName, const string szFind, const string szReplace) 49. { 50. m_maxIndex++; 51. ArrayResize(m_szName, m_maxIndex); 52. ArrayResize(m_szFind, m_maxIndex); 53. ArrayResize(m_szReplace, m_maxIndex); 54. m_szName[m_maxIndex - 1] = szName; 55. m_szFind[m_maxIndex - 1] = szFind; 56. m_szReplace[m_maxIndex - 1] = szReplace; 57. } 58. //+------------------------------------------------------------------+ 59. string Get(const string szName, const string szFind) 60. { 61. for (int c0 = 0; c0 < m_maxIndex; c0++) if ((m_szName[c0] == szName) && (m_szFind[c0] == szFind)) return m_szReplace[c0]; 62. 63. return NULL; 64. } 65. //+------------------------------------------------------------------+ 66. void Execute(void) 67. { 68. string sz0, tmp, res[]; 69. int count0 = 0, i0; 70. 71. if ((m_FileIn != INVALID_HANDLE) && (m_FileOut != INVALID_HANDLE)) while ((!FileIsEnding(m_FileIn)) && (_LastError == ERR_SUCCESS)) 72. { 73. sz0 = FileReadString(m_FileIn); 74. if (sz0 == "<object>") count0 = 1; 75. if (sz0 == "</object>") count0 = 0; 76. if (count0 > 0) if (StringSplit(sz0, '=', res) > 1) 77. { 78. i0 = (count0 == 1 ? 0 : i0); 79. for (int c0 = 0; (c0 < m_maxIndex) && (count0 == 1); i0 = c0, c0++) count0 = (res[1] == (tmp = m_szName[c0]) ? 2 : count0); 80. for (int c0 = i0; (c0 < m_maxIndex) && (count0 == 2); c0++) if ((res[0] == m_szFind[c0]) && (tmp == m_szName[c0])) 81. { 82. if (StringLen(m_szReplace[c0])) sz0 = m_szFind[c0] + "=" + m_szReplace[c0]; 83. else m_szReplace[c0] = res[1]; 84. } 85. } 86. FileWriteString(m_FileOut, sz0 + "\r\n"); 87. }; 88. } 89. //+------------------------------------------------------------------+ 90. }; 91. //+------------------------------------------------------------------+
ソースコード:C_AdjustTemplate
このコードは、まさに私たちが必要としている機能を提供しています。このコードと、前回の記事で紹介したC_ChartFloatingRADクラスのコードを比較すると、38行目から90行目にかけての内容がこのコードにも含まれていることに気づくでしょう。ただし、見た目には異なる点があります。これは、このC_AdjustTemplateクラスのデータモデリングが、より効率的な実行を促進する方法でおこなわれているからです。詳細はこの記事の後半で、C_ChartFloatingRADクラスの新しいコードを紹介する際に説明しますので、まずはC_AdjustTemplateクラスの動作を理解していきましょう。
C_AdjustTemplateクラスのコードは一見すると複雑に思えるかもしれませんが、実際には非常にシンプルです。複数の機能を備えているにもかかわらず、全体としては1つの機能を実行するように設計されています。この概念を理解するためには、MetaTraderやMQL5という特定のプラットフォームに縛られず、あたかも機械の部品を扱っているかのように考えると理解が進むでしょう。このクラスはテンプレートファイルだと考えると分かりやすく、前回の記事で説明したテンプレートファイルと同じ役割を果たしています。
こうして考えることで、C_AdjustTemplateクラスがどのように機能しているのか、そしてなぜ今後このように扱う必要があるのかが明確になります。クラスのコンストラクタを使用することは、基本的にテンプレートファイルを開き、それを操作することを意味します。デストラクタの使用は、MetaTraderに対して「テンプレートを使っても良い」と通知することに相当します。その他の機能は、テンプレートを調整するためのツールとして機能しています。
それでは、このクラスの各部分がどのように動作しているかを見ていきましょう。 まず、コンストラクタに注目します。19行目で始まるコンストラクタは、オプションとして2番目の文字列を指定することができます。なぜこのような仕組みなのか、その理由は「オーバーロード」にあります。もしオーバーロードができなければ、複数のコンストラクタを書かなければならないのですが、この場合は1つのコンストラクタで対応しています。しかし実際には、この過負荷は通常のものではなく、このように使われることを意図しています。
続いて、20行目から23行目にかけて、いくつかの変数が初期化されます。 コンパイラは暗黙的に初期化をおこないますが、明示的に初期化する方が好ましいです。これにより、各変数の値が正確に把握できるからです。
25行目では、_LastError定数を「リセット」しています。コンストラクタを呼び出す前にエラーが発生していた場合、それを確認しないと、エラーの状態を失うことになります。この点については以前の記事で詳しく説明しているので、そちらを参照してください。
26行目では、ソースファイルを開こうとしています。この試みが失敗した場合は、_LastError定数でエラーが報告されます。ソースファイルを開くことができれば、_LastError定数にERR_SUCCESSが設定され、27行目の確認が成功します。これにより次のステップへ進むことができます。
この時点で、29行目で保存先ファイルに名前が指定されているかどうかを確認しています。指定がない場合は、オリジナルのファイル名に基づいた一時ファイル名を使用します。保存先ファイルの名前が決定したら、30行目で保存先ファイルの作成を試みます。この試みが失敗した場合は、_LastError定数でエラーを報告します。これらすべてのステップが正常に実行されると、_LastError 定数にはERR_SUCCESS値が設定され、ファイル識別子(ハンドル)が取得されます。このハンドルはファイル操作に使用されるため、ハンドルがクローズされるまでは、ファイルに対する外部の操作を試みてはいけません。マシンが開いていて電源が入っている状態だと、問題が発生する可能性があることを考慮してください。
では、コードを編集すべき順に見ていきましょう。まず34行目でクラスデストラクタのコードが始まります。36行目では、最初に入力ファイルを閉じます。ただし、このファイルはハンドルが有効な場合にのみ閉じられます。つまり、ファイルが開かれていることが前提です。次に37行目では、出力ファイルが開かれているかどうかを確認します。これは、後続の不要な処理を避けるためです。
ターゲットファイルが開かれていれば、40行目で名前が指定されているか、セットアップ時にエラーが発生していないかを確認します。問題がなければ、ファイル名を残りの処理に適合させます。そして41行目では、何か問題があった場合に使用する一時ファイルを削除しています。
次に、43行目から45行目にかけて、割り当てられたメモリを解放します。 このような処理は非常に重要ですが、多くの人が忘れがちです。良いプログラミングの習慣として、メモリを割り当てた場合は必ず解放する必要があります。これにより、MetaTrader 5が不要なリソースを消費することを防ぎます。
次に、50行目から始まる非常に基本的かつシンプルな手順に進みます。ここではカウンタをインクリメントし、その直後に後で使用するデータを格納するためのメモリを確保します。このメモリ割り当ては51行目から53行目の間でおこなわれます。また、54行目から56行目にかけて、情報の保存方法にも注目してください。このプロセスは単純なため、詳細な説明は省きます。そして、59行目には興味深い関数があります。
59行目から始まるこの関数も非常にシンプルですが、興味深いです。この関数が何をするかというより、どうするかがポイントです。60行目にある唯一の行では、50行目の関数で追加されたすべてのポジションをループ処理しています。問題は、なぜプログラマーが50行目の関数を使ってクラスに書き込まれた情報を読みたがるのかという点です。一見、無意味に思えるかもしれません。確かにクラスコードだけを見ると意味がありませんが、背後には微妙なニュアンスがあります。この点は、66行目から始まる部分で詳しく説明されています。
66行目から始まるExecute関数は非常に繊細です。なぜなら、さまざまな要因でエラーが発生する可能性があるからです。具体的には以下のようなエラーが考えられます。
- 入力ファイルの読み込みに失敗する可能性
- 出力ファイルが利用できない可能性
- StringSplit関数が失敗する可能性
これらの問題が発生すると、エラー定数が変更され、71行目のwhileループが早期に終了し、指標全体がチャート上で正しく機能しなくなります。重要なのは、_LastError定数にERR_SUCCESS以外の値が格納されている場合、デストラクタが実行される際にテンプレートの更新が失敗し、最初の呼び出しの場合には作成されないということです。もしそこになければ、指標はチャート上に配置されません。このため、Execute関数は非常に重要です。
では、すべてが正常に動作していると仮定して、whileループの中で何が起こるかを見ていきましょう。
73行目では、ソースファイルから1行の文字列を読み込んでいます。このように読み込むと、確認作業が容易になります。次に74行目で、特定のオブジェクトが定義されているかどうかを確認し、75行目でその定義が完了したかどうかをチェックします。
これらのチェックは、テンプレートの読み込みとカスタマイズを迅速に進めるために重要です。オブジェクトが不要な場合は、単に出力ファイルに情報を書き込むだけでよいです。この情報は86行目に書かれます。ここからは、オリジナルのテンプレートをどのように調整し、必要なカスタマイズを行っていくかを説明します。
76行目では、オブジェクト内の文字列を分割するための処理を行っています。この分割は等号(=)でおこなわれ、オブジェクトのプロパティにパラメータを定義していることを示します。もしプロパティが定義されているならば、このチェックを通過し、78行目が実行されます。この行は、単に一時的なメモリを調整するだけです。しかし、次の行で疑問が生じます。
79行目では、Addメソッド(48行目からのメソッド)で追加されたすべてのデータをループしています。偶然にもテンプレートにパラメータの値が見つかれば、それは目的のオブジェクトを扱っていることを示しています。そのオブジェクトの名前を一時的に保存し、第2レベルの分析をおこないます。第2レベルであるため、79行目は同じオブジェクト内で再度実行されることはありません。結果として、テンプレートの構造が前回の「リプレイシステムの開発(第43回):Chart Tradeプロジェクト(II)」稿と同じ構造になるようにしなければなりません。ファイルはまったく同じ形式でなければなりません。変更する場合は、できる限り以前提供した内容に近いものにする必要があります。
さて、80行目では第2レベルに進んでいますが、ここでもう1つループがあります。重要なのは、79行目と80行目のループが同時に実行されることはないということです。どちらか一方が実行され、両方が同時に実行されることはありません。ただし、最初の呼び出しだけは例外です。79行目と80行目の両方が実行されないことが奇妙に思えるかもしれませんが、実際にはそうなっています。しかし、80行目が実行されれば、目的のオブジェクトプロパティが存在するかどうかを確認できます。オブジェクト名は重要なので、79行目のループ中に一時的に保存します。
この確認でオブジェクトのプロパティが見つかった場合、82行目で2番目の確認をおこないます。この時点で、59行目で見つかった関数の存在が正当化されます。プログラミングの過程で、C_AdjustTemplateクラスにオブジェクトのプロパティで使用するパラメータがわからない場合、82行目の確認がおこなわれ、83行目でオブジェクトのプロパティに設定された値が固定されます。 使用する値を指定すれば、その値がテンプレートに書き込まれます。
C_AdjustTemplateクラスが非常に興味深いのは、このタイプの機能により、プロパティの値を報告したり、使用する値を指定したりできるからです。
これでC_AdjustTemplateクラスの説明は終わりです。では、C_ChartFloatingRADクラスがどうなったか見てみましょう。ご想像の通り、改良が加えられ、さらに魅力的なものとなっています。
C_ChartFloatingRADクラスの新しい外観
この記事ではこのクラスの最終的なコードは示しませんが(細部までゆっくり説明したいためです)、前回紹介したコードよりもかなり複雑になっていることにお気づきでしょう。とはいえ、コードのほとんどは変わりません。そのため、記事を順番に読むことをお勧めします。そうしないと、全体的な理解に影響する可能性のある詳細を見逃してしまう可能性があります。
以下に、C_ChartFloatingRADクラスの現在の時点までの完全なコードを示します。
001. //+------------------------------------------------------------------+ 002. #property copyright "Daniel Jose" 003. //+------------------------------------------------------------------+ 004. #include "../Auxiliar/C_Mouse.mqh" 005. #include "C_AdjustTemplate.mqh" 006. //+------------------------------------------------------------------+ 007. class C_ChartFloatingRAD : private C_Terminal 008. { 009. private : 010. enum eObjectsIDE {MSG_MAX_MIN, MSG_TITLE_IDE, MSG_DAY_TRADE, MSG_LEVERAGE_VALUE, MSG_TAKE_VALUE, MSG_STOP_VALUE, MSG_BUY_MARKET, MSG_SELL_MARKET, MSG_CLOSE_POSITION, MSG_NULL}; 011. struct st00 012. { 013. int x, y; 014. string szObj_Chart, 015. szFileNameTemplate; 016. long WinHandle; 017. double FinanceTake, 018. FinanceStop; 019. int Leverage; 020. bool IsDayTrade, 021. IsMaximized; 022. struct st01 023. { 024. int x, y, w, h; 025. }Regions[MSG_NULL]; 026. }m_Info; 027. //+------------------------------------------------------------------+ 028. C_Mouse *m_Mouse; 029. //+------------------------------------------------------------------+ 030. void CreateWindowRAD(int x, int y, int w, int h) 031. { 032. m_Info.szObj_Chart = (string)ObjectsTotal(GetInfoTerminal().ID); 033. ObjectCreate(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJ_CHART, 0, 0, 0); 034. ObjectSetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_XDISTANCE, m_Info.x = x); 035. ObjectSetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_YDISTANCE, m_Info.y = y); 036. ObjectSetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_XSIZE, w); 037. ObjectSetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_YSIZE, h); 038. ObjectSetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_DATE_SCALE, false); 039. ObjectSetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_PRICE_SCALE, false); 040. m_Info.WinHandle = ObjectGetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_CHART_ID); 041. }; 042. //+------------------------------------------------------------------+ 043. inline void UpdateChartTemplate(void) 044. { 045. ChartApplyTemplate(m_Info.WinHandle, "/Files/" + m_Info.szFileNameTemplate); 046. ChartRedraw(m_Info.WinHandle); 047. } 048. //+------------------------------------------------------------------+ 049. inline double PointsToFinance(const double Points) 050. { 051. return Points * (GetInfoTerminal().VolumeMinimal + (GetInfoTerminal().VolumeMinimal * (m_Info.Leverage - 1))) * GetInfoTerminal().AdjustToTrade; 052. }; 053. //+------------------------------------------------------------------+ 054. inline void AdjustTemplate(const bool bFirst = false) 055. { 056. #define macro_AddAdjust(A) { \ 057. (*Template).Add(A, "size_x", NULL); \ 058. (*Template).Add(A, "size_y", NULL); \ 059. (*Template).Add(A, "pos_x", NULL); \ 060. (*Template).Add(A, "pos_y", NULL); \ 061. } 062. #define macro_GetAdjust(A) { \ 063. m_Info.Regions[A].x = (int) StringToInteger((*Template).Get(EnumToString(A), "pos_x")); \ 064. m_Info.Regions[A].y = (int) StringToInteger((*Template).Get(EnumToString(A), "pos_y")); \ 065. m_Info.Regions[A].w = (int) StringToInteger((*Template).Get(EnumToString(A), "size_x")); \ 066. m_Info.Regions[A].h = (int) StringToInteger((*Template).Get(EnumToString(A), "size_y")); \ 067. } 068. 069. C_AdjustTemplate *Template; 070. 071. if (bFirst) 072. { 073. Template = new C_AdjustTemplate("Chart Trade/IDE_RAD.tpl", m_Info.szFileNameTemplate = StringFormat("Chart Trade/%u.tpl", GetInfoTerminal().ID)); 074. for (eObjectsIDE c0 = 0; c0 <= MSG_CLOSE_POSITION; c0++) macro_AddAdjust(EnumToString(c0)); 075. }else Template = new C_AdjustTemplate(m_Info.szFileNameTemplate); 076. Template.Add("MSG_NAME_SYMBOL", "descr", GetInfoTerminal().szSymbol); 077. Template.Add("MSG_LEVERAGE_VALUE", "descr", (string)m_Info.Leverage); 078. Template.Add("MSG_TAKE_VALUE", "descr", (string)m_Info.FinanceTake); 079. Template.Add("MSG_STOP_VALUE", "descr", (string)m_Info.FinanceStop); 080. Template.Add("MSG_DAY_TRADE", "state", (m_Info.IsDayTrade ? "1" : "0")); 081. Template.Add("MSG_MAX_MIN", "state", (m_Info.IsMaximized ? "1" : "0")); 082. Template.Execute(); 083. if (bFirst) for (eObjectsIDE c0 = 0; c0 <= MSG_CLOSE_POSITION; c0++) macro_GetAdjust(c0); 084. 085. delete Template; 086. 087. UpdateChartTemplate(); 088. 089. #undef macro_AddAdjust 090. #undef macro_GetAdjust 091. } 092. //+------------------------------------------------------------------+ 093. eObjectsIDE CheckMousePosition(const int x, const int y) 094. { 095. int xi, yi, xf, yf; 096. 097. for (eObjectsIDE c0 = 0; c0 <= MSG_CLOSE_POSITION; c0++) 098. { 099. xi = m_Info.x + m_Info.Regions[c0].x; 100. yi = m_Info.y + m_Info.Regions[c0].y; 101. xf = xi + m_Info.Regions[c0].w; 102. yf = yi + m_Info.Regions[c0].h; 103. if ((x > xi) && (y > yi) && (x < xf) && (y < yf) && ((c0 == MSG_MAX_MIN) || (c0 == MSG_TITLE_IDE) || (m_Info.IsMaximized))) return c0; 104. } 105. return MSG_NULL; 106. } 107. //+------------------------------------------------------------------+ 108. public : 109. //+------------------------------------------------------------------+ 110. C_ChartFloatingRAD(string szShortName, C_Mouse *MousePtr, const int Leverage, const double FinanceTake, const double FinanceStop) 111. :C_Terminal() 112. { 113. m_Mouse = MousePtr; 114. m_Info.Leverage = (Leverage < 0 ? 1 : Leverage); 115. m_Info.FinanceTake = PointsToFinance(FinanceToPoints(MathAbs(FinanceTake), m_Info.Leverage)); 116. m_Info.FinanceStop = PointsToFinance(FinanceToPoints(MathAbs(FinanceStop), m_Info.Leverage)); 117. m_Info.IsDayTrade = true; 118. m_Info.IsMaximized = true; 119. if (!IndicatorCheckPass(szShortName)) SetUserError(C_Terminal::ERR_Unknown); 120. CreateWindowRAD(115, 64, 170, 210); 121. AdjustTemplate(true); 122. } 123. //+------------------------------------------------------------------+ 124. ~C_ChartFloatingRAD() 125. { 126. ObjectDelete(GetInfoTerminal().ID, m_Info.szObj_Chart); 127. FileDelete(m_Info.szFileNameTemplate); 128. 129. delete m_Mouse; 130. } 131. //+------------------------------------------------------------------+ 132. void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam) 133. { 134. static int sx = -1, sy = -1; 135. int x, y, mx, my; 136. static eObjectsIDE it = MSG_NULL; 137. 138. switch (id) 139. { 140. case CHARTEVENT_MOUSE_MOVE: 141. if ((*m_Mouse).CheckClick(C_Mouse::eClickLeft)) 142. { 143. switch (it = CheckMousePosition(x = (int)lparam, y = (int)dparam)) 144. { 145. case MSG_TITLE_IDE: 146. if (sx < 0) 147. { 148. ChartSetInteger(GetInfoTerminal().ID, CHART_MOUSE_SCROLL, false); 149. sx = x - m_Info.x; 150. sy = y - m_Info.y; 151. } 152. if ((mx = x - sx) > 0) ObjectSetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_XDISTANCE, m_Info.x = mx); 153. if ((my = y - sy) > 0) ObjectSetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_YDISTANCE, m_Info.y = my); 154. break; 155. } 156. }else 157. { 158. if (it != MSG_NULL) 159. { 160. switch (it) 161. { 162. case MSG_MAX_MIN: 163. m_Info.IsMaximized = (m_Info.IsMaximized ? false : true); 164. ObjectSetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_YSIZE, (m_Info.IsMaximized ? 210 : m_Info.Regions[MSG_TITLE_IDE].h + 6)); 165. break; 166. case MSG_DAY_TRADE: 167. m_Info.IsDayTrade = (m_Info.IsDayTrade ? false : true); 168. break; 169. } 170. it = MSG_NULL; 171. AdjustTemplate(); 172. } 173. if (sx > 0) 174. { 175. ChartSetInteger(GetInfoTerminal().ID, CHART_MOUSE_SCROLL, true); 176. sx = sy = -1; 177. } 178. } 179. break; 180. } 181. } 182. //+------------------------------------------------------------------+ 183. }; 184. //+------------------------------------------------------------------+
C_ChartFloatingRADクラスのソースコード
このコードが、特に初心者の方にとっては、非常にわかりにくく、複雑に見えることは承知しています。しかし、この連載を最初から見ている人なら、もうMQL5プログラミングについて多くを学んでいるはずです。いずれにせよ、このコードは多くの人が普通に作るものよりずっと複雑です。
このコードを前回の記事で紹介したものと比較すると、より複雑になっていることがわかるでしょう。しかし、複雑さのほとんどは、前のセクションで説明したC_AdjustTemplateクラスに移されました。このコードで何をするのか考えてみましょう。ここにこそ、Chart Trade指標のマジックがあります。これは、前回の記事で示した指標コードが変わらないからです。しかし、ここに示したコードは変更され、指標に新しい機能を追加するのに十分なほど変わりました。
説明を始めるために、10行目を見てみましょう。テンプレートに存在するオブジェクトへのアクセスを容易にする列挙があります。しかし、同じ列挙にはMSG_NULLという値があり、これは制御のために使われます。これは今後の説明で明らかになるでしょう。
コードを見ると、22行目と25行目の間に、配列である変数が使用する構造体があります。しかし、配列の要素数を見れば、それが何なのか疑問に思うかもしれません。私たちには価値がありません。これが何を意味するのか、どこにも見当たらない」と思うかもしれません。落ち着いて、パニックになる必要はありません。配列の要素数を示すこのデータは、10行目でおこなわれた列挙の最後のデータに過ぎません。しかし、ここでひとつ細かいことがあります。この最後の値は、実際には要素やオブジェクトを表すものではありません。もしそうなら、宣言は違っていたはずです。
次に説明すべきは54番です。ここで実際にテンプレートにアクセスします。しかし、その説明に入る前に、もうひとつ見ておきましょう。この機能には2つの場所でアクセスします。1つ目はコンストラクタの121行目、2つ目はメッセージ処理部分の171行目です。なぜそれを知ることが重要なのでしょうか。その理由は、私たちがテンプレートで何をし、何をしたいかにあります。
コンストラクタで発生する最初のケースでは、テンプレートが特定の方法で動作するようにカスタマイズしたい。2つ目のケースでは、すでにあるものを使って作業します。テンプレートを変更するのではなく、私たちが望むものに正確に合うようにするのです。
この説明は少しわかりにくいかもしれませんが、54行目のメソッドがどのように機能するか見てみましょう。そうすることで、状況をより理解できるかもしれません。56行目から67行目にかけて、プログラミングを容易にする2つのマクロが定義されています。89行目と90行目がそのようなマクロを排除する役割を果たすのと同じように。私は通常、同じコードやパラメータを何度も繰り返すときにマクロを使いたいです。この特殊なケースでは、パラメータが繰り返されます。マクロのコードは非常にシンプルです。
最初の行は56行目から61行目までで、C_AdjustTemplateクラスが返す要素を追加します。62行目から67行目までの2番目のマクロでは、C_AdjustTemplateクラスが教えてくれる値を、25行目で宣言された配列に格納される値に変換しています。これに注目してください。オブジェクトがどこにあるのかを単純に推測するのではなく、テンプレートに尋ねるのです。
71行目では、テンプレートの調整を開始しているかどうかを確認しています。そうでない場合は、75行目の呼び出しを実行します。しかし、これが最初の呼び出しであれば、C_AdjustTemplateクラスにどのような名前が使われるかを伝えます。これは73行目でおこなわれます。74行目に注目してください。この行では、列挙型を使ってC_AdjustTemplateクラスにどのオブジェクトからデータを取得する必要があるかを伝えているのがわかるでしょう。そのためにループを使用します。こうすることで、C_AdjustTemplateクラスは、どのプロパティを修正する必要があるかを知ることができます。
いずれにせよ、76行目と81行目では、オブジェクトプロパティで使われるべき値をテンプレートに渡しています。各行は、オブジェクト、変更するプロパティ、使用する値を指定します。
最後に82行目で、C_AdjustTemplateクラスに、提供された情報に従ってテンプレートをカスタマイズできることを伝えています。これは前のトピックで示したようにおこなわれます。すべての作業が終わると、これが最初の呼び出しかどうかを83行目で確認します。もしそうなら、25行目で宣言した配列の値を調整します。これは、C_AdjustTemplateクラスにどのオブジェクトとプロパティについて知りたいかを伝えるループを使っておこなわれます。
この作業が完了したら、85行目でC_Templateクラスを閉じます。そして最後に、87行目でOBJ_CHARTオブジェクトに更新を依頼しています。こうすることで、記事末尾のデモビデオにあるように、マジックが実際に動くのを見ることができます。
注意:ここではエラーの確認はおこなわず、すべてが問題なく、期待通りに機能しているものとみなします。しかし、指標のコードにエラーがないか確認します。したがって、何か失敗があれば、それはここではなく、指標のコードで処理されます。
次に、別のものを見てみましょう。93 行目では、使用される場所に配置できる非常に興味深い関数が実行されます。これはコードを読みやすくするためです。この関数は97行目から始まるループを持ち、OBJ_CHARTに存在する各オブジェクトを通過します。OBJ_CHARTオブジェクトにはテンプレートが含まれており、このテンプレートにはこれから確認するオブジェクトが含まれています。確認中、各オブジェクトのクリックエリアとなる矩形を作成します。これは99行目と102行目でおこなわれています。
このクリックエリアができたら、呼び出しパラメータとして指定されたエリアと比較することができます。この比較は103行目でおこなわれます。エリアに加えて、いくつかの追加条件があります。問題がなければ、オブジェクトのインデックスが返され、そうでなければMSG_NULLが返されます。これが、冒頭で定義した列挙型にこの値を含める必要がある理由です。この値がなければ、Chart Trade指標で無効なオブジェクトをクリックしたことを報告することはできません。
次に説明するのは132行目です。これはイベントハンドラです。今はいくつかの新しいパーツが含まれています。しかし、これらの新しいパーツがあるからこそ、デモビデオにあるようなことが可能になるのです。では、何が起こっているのかをよく見てみましょう。そして、ここまではOBJ_CHART以外のオブジェクトを作成していないことに注目してください。それでも、私たちは期待される機能を備えています。
ほとんどのコードは、前回の記事で紹介したものとよく似ています。しかし、経験の浅い人にも何が起こっているのか理解できるように、コメントする価値のある小さな違いがいくつかあります。134-136行目では、いくつかの変数を定義しています。136行目で定義されている変数は、本稿の枠組みにおいて特に興味深いものです。
136行目でこの変数をメモリとして使用します。これは、クリック関連の問題を解決するために、MetaTrader 5からの追加的な支援に頼ることができないためです。通常、チャート上にオブジェクトがある場合、MetaTrader 5はクリックされたオブジェクトの名前を教えてくれます。これは、CHARTEVENT_OBJECT_CLICKイベントを通じておこなわれます。しかしこの場合、OBJ_CHART以外の実際のオブジェクトはありません。従って、MetaTrader 5は、Chart Trade指標エリアをクリックすると、OBJ_CHARTをクリックしたと解釈します。
私たちが扱っているイベントはCHARTEVENT_MOUSE_MOVEだけです。ただし、クリックが処理されるのは、マウス指標が学習状態でない場合のみです。これは141行目で確認されています。マウス指標が学習状態であるか、何か他のことが起こった場合は、156行目に移ります。そこで質問です。もし136行目で宣言された変数の値が違っていたら、何かが起こらなければなりません。その前に、この変数がいつ、どこで値を取得するのかを見てみましょう。
マウス指標がフリーでクリックが発生すると、141行目で実行される確認によって、クリックがどこで、何に対しておこなわれたかが特定されます。これは143行目でおこなわれます。この時点で、クリック時のマウスの位置を解析関数に伝えます。しかし、ここには小さな欠点があります。それは次回の記事で修正されるし、他にもまだやらなければならないことがあるので、今は触れない。確認と同時に、この関数はクリックを受けたオブジェクトの名前を返します。この名前はstatic変数に書き込まれます。
実用的な理由から、今はタイトルオブジェクトだけを確認しています。クリックされた場合、145行目でドラッグコードが実行されます。ここに他のオブジェクトを配置することもできるが、少なくとも現段階では、確認ロジックが複雑になります。マウスボタンが押されているので、オブジェクトはクリックメッセージを受信し続ける。
前述したように、私たちはこれを改善することができます。しかし今のところは、コードをできるだけシンプルに保ち、少しずつ変更を加えていきたい。ここに示したコンセプトは、多くの人が普段プログラミングしているものとは大きく異なるからです。
しかし、156行目に戻りましょう。この行が実行されると、この条件の中で2つの確認がおこなわれます。最初のものは、OBJ_CHARTに存在するオブジェクトがクリックされたかどうかを判定します。この場合、オブジェクトが実際にチャート上に存在し、MetaTrader 5がCHARTEVENT_OBJECT_CLICKイベントを生成した場合と同じ条件が満たされることになります。つまり、既存のシステムの動作をエミュレートするのです。これは、指標全体の適切な動作を保証するためです。
158行目の確認が成功したら、まず適切なイベント処理をおこなう。これを160行目と169行目で行い、170行目でオブジェクト参照を削除し、171行目でテンプレートの現在の状態を更新します。こうすることで、指標全体が更新され、チャート上にオブジェクトが存在しているような錯覚に陥りますが、実際のオブジェクトはOBJ_CHARTだけです。
C_ChartFloatingRADクラスの残りのコードはすべて、前回の記事ですでに説明したので、ここで改めてコメントする必要はないと思います。
デモ映像
結論
ご覧のように、この記事では、OBJ_CHARTでテンプレートを使用する方法を紹介しました。これにより、チャート上に実際のオブジェクトがある場合と非常によく似た動作を得ることができます。おそらく、今回ご紹介するものの最大の利点は、複雑なMQL5プログラミングを使用せずに、MetaTrader 5自体に存在する要素からインターフェースを素早く作成できることでしょう。
私が実演していることはかなり混乱し、複雑に見えるかもしれないが、それは読者の皆さんにとってまったく新しいことだからにほかなりません。練習を重ねるうちに、この知識がさまざまな場面や状況で幅広く活用できることに気づくでしょう。しかし、システムはまだ完成していません。それに、ひとつだけ小さな欠点があります。しかし、これは次回の記事で修正され、最終的にユーザーがChart Tradeに直接値を入力できるようになります。この部分は非常に興味深いです。次回の連載もお見逃しなく。
MetaQuotes Ltdによりポルトガル語から翻訳されました。
元の記事: https://www.mql5.com/pt/articles/11690
- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索