市場シミュレーション(第8回):ソケット(II)
はじめに
前回の「市場シミュレーション(第7回):ソケット(I)」では、ソケットの学習を始めるための最初のステップを示しました。しかし、そこに示したアプリケーションはあまり面白くなかったかもしれません。正直に言うと、プログラミングで新しいことを学ぶときに最初に作る「HELLO WORLD」プログラムのようなものでした。それは長い学習の旅の最初の一歩に過ぎません。
今回の記事では、もう少し面白いものを作る方法を紹介します。正確に言うと、特に実用的なものではありませんが、概念を学びながら実験し、楽しむことができます。前回の記事と同様に、外部コードを利用する必要があります。外部コードを使う理由は、MetaTrader 5にDLLを追加したくないからです。少なくとも現時点では追加する予定はありません。
外部コードについては、動作の詳細には踏み込みません。同じ種類のタスクを実行するコードは他にも見つけられますし、前回の記事で紹介した参考資料を学習して自分で作成することも可能です。では、今回の記事で何をするのかというと、基本的には「ソケットが面白いことをできる」ということを示すことです。さらに、リプレイ/シミュレーターシステムでも多少利用する予定です。ミニチャットの作成という、最も簡単な方法で進めます。MetaTrader 5でミニチャットを作る方法をお見せします。
計画
ミニチャットのコンセプトは非常にシンプルでわかりやすいです。メッセージを入力するエリア、送信ボタン、そして他のユーザーのメッセージを見るための表示エリアがあれば十分です。要するに、実装は簡単です。編集ボックス、ボタン、投稿されたメッセージを表示するオブジェクトを追加する必要があります。MetaTrader 5はこれらのツールをすべて提供しています。
さて、通常、チャットプログラムはクライアント-サーバー方式を使います。しかしここでは、MQL5のみを使用するために、サーバーは外部プログラム、クライアントはMQL5で実装します。これがソケットを使う利点のひとつです。1つの方法に限定されることなく、複数の方法で実装できます。「ミニチャットで任意の人数の接続を管理するのはどうするのか?」と思うかもしれません。開発は非常に複雑になるのではないかと。でも、それは何を達成したいか、どう実装するかによります。
今回示すサーバープログラムは非常にシンプルで、Raspberry Pi上でも動作させ、ミニサーバーとして利用できます。この構成ではサーバーが動的なので、コードを再コンパイルせずに多くの参加者をサポートできます。「動的」というのは、接続上限がサーバーコードではなく、OSやハードウェアの能力によって決まるという意味です。サーバーコードに入る前に、まずクライアント側、つまりMQL5で実装する部分を見ていきましょう。
基本的な実装
この部分の実装は特にMQL5では少し変わったアプローチを使うため面白いです。まず、ミニチャットとやり取りするためのウィンドウを作る必要があります。MetaTrader 5でMQL5を使い、最も簡単にこれをおこなう方法はインジケーターを使うことです。しかし注意点があります。インジケーターはソケットを使用できません。ソケット実装によっては、他のインジケーターの計算をブロックしてしまうからです。すべてのインジケーターは同じ計算空間を共有しています。では、どうやってウィンドウを作るのか。最も簡単なコードは以下です。
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. #property indicator_separate_window 04. #property indicator_plots 0 05. //+------------------------------------------------------------------+ 06. int OnInit() 07. { 08. return INIT_SUCCEEDED; 09. } 10. //+------------------------------------------------------------------+ 11. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 12. { 13. return rates_total; 14. } 15. //+------------------------------------------------------------------+
初期インジケーターコード
この基本コードにより、3行目によってチャートに追加したときに新しいウィンドウが生成されます。しかし、問題があります。このままではユーザーが複数のチャットウィンドウをチャートに追加できてしまいます。また、インジケーターはソケットを使えません。
コードをソケット使用可能な環境で動かすために、すべてをエキスパートアドバイザー(EA)に埋め込みます。これはデモンストレーション用です。スクリプトでも可能ですが、スクリプトはチャートの時間軸変更時に削除されるため、ここではEAを使った実装を示します。最初のステップは、次のコードを作成することです。
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. #property version "1.00" 04. //+------------------------------------------------------------------+ 05. #define def_IndicatorMiniChat "Indicators\\Mini Chat\\Mini Chat.ex5" 06. #resource "\\" + def_IndicatorMiniChat 07. //+------------------------------------------------------------------+ 08. long gl_id; 09. int subWin; 10. //+------------------------------------------------------------------+ 11. int OnInit() 12. { 13. gl_id = ChartID(); 14. subWin = (int) ChartGetInteger(gl_id, CHART_WINDOWS_TOTAL); 15. 16. ChartIndicatorAdd(gl_id, subWin, iCustom(NULL, 0, "::" + def_IndicatorMiniChat)); 17. 18. return INIT_SUCCEEDED; 19. } 20. //+------------------------------------------------------------------+ 21. void OnDeinit(const int reason) 22. { 23. ChartIndicatorDelete(gl_id, subWin, ChartIndicatorName(gl_id, subWin, 0)); 24. } 25. //+------------------------------------------------------------------+ 26. void OnTick() 27. { 28. } 29. //+------------------------------------------------------------------+ 30. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 31. { 32. } 33. //+------------------------------------------------------------------+
EAの起動コード
このEAの基本構造により、ウィンドウが作成されます。5行目では、6行目でインジケーターをEAの内部リソースとして使用することを示しています。なぜなら、EA自体では直接ウィンドウを作れないため、インジケーターを利用してウィンドウを作るからです。これがインジケーターを使用する理由です。
EAがチャートに取り付けられると、16行目でインジケーターを追加して必要なウィンドウを作成します。しかし前回のインジケーターコードだけでは、ユーザーが直接チャートに追加できてしまうため、EAが自動で追加する仕組みが必要です。
解決策は簡単です。インジケーターをユーザーがチャートに配置できないように修正します。EAがコンパイルされると、インジケーター実行ファイルは削除してもよいですが、実行ファイルがアクセス可能な場合に備え、保護も必要です。解決策は次のコードにあります。
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. #property description "Base indicator for Mini Chat." 04. #property description "It cannot be used without outside assistance." 05. #property version "1.00" 06. #property indicator_chart_window 07. #property indicator_plots 0 08. //+------------------------------------------------------------------+ 09. #define def_ShortName "Mini Chat" 10. //+------------------------------------------------------------------+ 11. int OnInit() 12. { 13. long id = ChartID(); 14. string sz0 = def_ShortName + "_TMP"; 15. int i0; 16. 17. IndicatorSetString(INDICATOR_SHORTNAME, sz0); 18. for (int c0 = (int)ChartGetInteger(id, CHART_WINDOWS_TOTAL) - 1; c0 >= 0; c0--) 19. if (ChartIndicatorName(id, c0, 0) == def_ShortName) 20. { 21. ChartIndicatorDelete(id, ChartWindowFind(id, sz0), sz0); 22. return INIT_FAILED; 23. } 24. IndicatorSetString(INDICATOR_SHORTNAME, def_ShortName); 25. i0 = ChartWindowFind(id, def_ShortName); 26. if ((i0 == 0) || (ChartIndicatorsTotal(id, i0) > 1)) 27. { 28. ChartIndicatorDelete(id, i0, def_ShortName); 29. return INIT_FAILED; 30. } 31. 32. return INIT_SUCCEEDED; 33. } 34. //+------------------------------------------------------------------+ 35. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 36. { 37. return rates_total; 38. } 39. //+------------------------------------------------------------------+
インジケーターコードの最初の変更
このコードは、ユーザーがインジケーターをチャートに配置できないようにし、EAからのみ追加できるようにしています。なぜこのコードでユーザーがインジケーターをチャートに置けなくなるのかを理解してみましょう。6行目では、インジケーターが新しいウィンドウを作成しないことを指定しています。
9行目ではインジケーターの名前を定義しています。この名前はテストに使用されます。リストからインジケーターを削除するには、名前が必要です。この名前は最終的なものではなく、一時的な名前である必要があります。これは14行目でおこなわれ、17行目で一時的な名前をインジケーター名として設定しています。
ここからが重要な部分です。18行目のループでは、チャート上のインジケーター一覧から指定した名前を検索します。ただし、検索する名前は9行目で定義したものです。このテストは19行目でおこなわれます。インジケーターが見つかると、21行目が実行されます。この行は、すでにチャート上にあるインジケーターではなく、ユーザーが置こうとしているインジケーターを削除します。その直後、22行目で初期化失敗を返します。
もしインジケーターを置ける場合、24行目で埋め込みインジケーターの最終的な名前を設定し、EAのみが使用できるようにします。
さらにもう一つのポイントがあります。これにより、インジケーターが既にチャート上にある後にユーザーが追加することを防げます。しかし、ユーザーは最初に追加しようとするかもしれません。そのため、追加のチェックをおこないます。
25行目では、インジケーターがどのウィンドウに置かれたかを検出します。メインウィンドウの場合は0が返され、それ以外の場合はサブウィンドウ番号が返されます。26行目でこの値を確認します。0であればインジケーターを削除します。また、そのウィンドウにインジケーターが複数ある場合も削除します。
ここで混乱するかもしれません。「EAがチャートにインジケーターを置く場合、メインウィンドウに置くのではないか。6行目でメインウィンドウに置くと指定しているのでは」と。実際には、EAがインジケーターをチャートに置くときは、新しいウィンドウが作成されます。したがって、25行目で返される値が0であれば、安全にインジケーターを削除できます。また、同じ戻り値のウィンドウに他の要素があれば、それはユーザーが配置しようとしたことを示すため、その場合も削除します。
これにより、ユーザーが手動でインジケーターをチャートに追加することができなくなります。これが達成されたら、次のステップに進むことができます。
インタラクションオブジェクトの実装
MQL5アプリケーションを簡略化するために、次のようにします:インタラクションオブジェクトはインジケーター内に配置し、接続のロジックはEA内に配置します。この設計により、将来的にミニチャットを拡張することが可能です。作業はヘッダファイルを使って整理します。今回おこなうすべての内容はリプレイ/シミュレーターのプロジェクトの一部なので、ミニチャットも同じフレームワークに統合します。最初のステップとして、イベント列挙に2つの新しいイベントを追加します。このコードを以下に示します。
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. #define def_MaskTimeService 0xFED00000 16. #define def_IndicatorTimeFrame (_Period < 60 ? _Period : (_Period < PERIOD_D1 ? _Period - 16325 : (_Period == PERIOD_D1 ? 84 : (_Period == PERIOD_W1 ? 91 : 96)))) 17. #define def_IndexTimeFrame 4 18. //+------------------------------------------------------------------+ 19. union uCast_Double 20. { 21. double dValue; 22. long _long; // 1 Information 23. datetime _datetime; // 1 Information 24. uint _32b[sizeof(double) / sizeof(uint)]; // 2 Informations 25. ushort _16b[sizeof(double) / sizeof(ushort)]; // 4 Informations 26. uchar _8b [sizeof(double) / sizeof(uchar)]; // 8 Informations 27. }; 28. //+------------------------------------------------------------------+ 29. enum EnumEvents { 30. evTicTac, //Event of tic-tac 31. evHideMouse, //Hide mouse price line 32. evShowMouse, //Show mouse price line 33. evHideBarTime, //Hide bar time 34. evShowBarTime, //Show bar time 35. evHideDailyVar, //Hide daily variation 36. evShowDailyVar, //Show daily variation 37. evHidePriceVar, //Hide instantaneous variation 38. evShowPriceVar, //Show instantaneous variation 39. evCtrlReplayInit, //Initialize replay control 40. evChartTradeBuy, //Market buy event 41. evChartTradeSell, //Market sales event 42. evChartTradeCloseAll, //Event to close positions 43. evChartTrade_At_EA, //Event to communication 44. evEA_At_ChartTrade, //Event to communication 45. evChatWriteSocket, //Event to Mini Chat 46. evChatReadSocket //Event To Mini Chat 47. }; 48. //+------------------------------------------------------------------+
Defines.mqhファイルのソースコード
以前のDefines.mqhファイルに、45行目と46行目の2行が追加されたことに注目してください。この2行により、ソケット経由で通信が可能になります。しかし、これらのイベントが追加されたのは、ミニチャットをEAの内部コードから分離するためだけです。言い換えれば、インジケーターがソケット経由で受信したメッセージを表示する方法が必要でした。そして、インジケーターはソケットを直接使えないため、EAがソケットを監視し、メッセージが利用可能になった瞬間にミニチャットに転送します。これにより、インジケーター内でメッセージにアクセスできるようになります。
さて、次に、チャート上にオブジェクトを作成するためのヘッダファイルを見ていきましょう。以下でご覧ください。
001. //+------------------------------------------------------------------+ 002. #property copyright "Daniel Jose" 003. //+------------------------------------------------------------------+ 004. #include "..\Defines.mqh" 005. //+------------------------------------------------------------------+ 006. #define def_ShortName "Mini Chat" 007. #define def_MaxRows 256 008. #define def_FontName "Lucida Console" 009. #define def_FontSize 12 010. #define def_SizeControls (m_txtHeight + 6) 011. #define macroColorRGBA(A) ((uint)((0xFF << 24) | (A & 0x00FF00) | ((A & 0xFF0000) >> 16) | ((A & 0x0000FF) << 16))) 012. //+------------------------------------------------------------------+ 013. class C_Chat 014. { 015. private : 016. long m_id; 017. int m_sub; 018. int m_txtHeight; 019. bool m_full; 020. ushort m_Width, 021. m_Height, 022. m_index; 023. uint m_Pixel[]; 024. string m_ObjEdit, 025. m_ObjBtn, 026. m_ObjPanel; 027. struct st0 028. { 029. string info; 030. bool loc; 031. }m_Msgs[def_MaxRows + 1]; 032. //+------------------------------------------------------------------+ 033. void Add(string szMsg, bool isloc = false) 034. { 035. m_Msgs[m_index].info = szMsg; 036. m_Msgs[m_index].loc = isloc; 037. if ((++m_index) > def_MaxRows) 038. { 039. m_full = true; 040. m_index = 0; 041. } 042. Paint(); 043. }; 044. //+------------------------------------------------------------------+ 045. void Paint(void) 046. { 047. int max, count, p0, p1; 048. 049. ArrayInitialize(m_Pixel, macroColorRGBA(clrBlack)); 050. if ((p0 = m_Height - def_SizeControls) < 0) return; 051. max = (int)(floor(p0 / (m_txtHeight * 1.0))); 052. p1 = m_index - max; 053. if (m_full) 054. count = (max > def_MaxRows ? m_index + 1 : (p1 > 0 ? p1 : (def_MaxRows + p1 + 1))); 055. else 056. count = (p1 > 0 ? p1 : 0); 057. for (ushort row = 0; row < p0; count++) 058. { 059. count = (count > def_MaxRows ? 0 : count); 060. if (count == m_index) break; 061. TextOut(m_Msgs[count].info, 2, row, 0, m_Pixel, m_Width, m_Height, macroColorRGBA(m_Msgs[count].loc ? clrSkyBlue : clrLime), COLOR_FORMAT_ARGB_NORMALIZE); 062. row += (ushort) m_txtHeight; 063. } 064. ResourceCreate("::" + m_ObjPanel, m_Pixel, m_Width, m_Height, 0, 0, 0, COLOR_FORMAT_ARGB_NORMALIZE); 065. ChartRedraw(); 066. } 067. //+------------------------------------------------------------------+ 068. void CreateObjEdit(const string szArg) 069. { 070. ObjectCreate(m_id, szArg, OBJ_EDIT, m_sub, 0, 0); 071. ObjectSetInteger(m_id, szArg, OBJPROP_XDISTANCE, 2); 072. ObjectSetInteger(m_id, szArg, OBJPROP_YDISTANCE, 0); 073. ObjectSetInteger(m_id, szArg, OBJPROP_YSIZE, def_SizeControls); 074. ObjectSetString(m_id, szArg, OBJPROP_FONT, def_FontName); 075. ObjectSetInteger(m_id, szArg, OBJPROP_FONTSIZE, def_FontSize); 076. ObjectSetInteger(m_id, szArg, OBJPROP_BGCOLOR, clrDarkGray); 077. ObjectSetInteger(m_id, szArg, OBJPROP_COLOR, clrBlack); 078. ObjectSetInteger(m_id, szArg, OBJPROP_BORDER_COLOR, clrNavy); 079. } 080. //+------------------------------------------------------------------+ 081. void CreateObjButton(const string szArg, const string szTxt) 082. { 083. ObjectCreate(m_id, szArg, OBJ_BUTTON, m_sub, 0, 0); 084. ObjectSetInteger(m_id, szArg, OBJPROP_YDISTANCE, 0); 085. ObjectSetInteger(m_id, szArg, OBJPROP_XSIZE, 70); 086. ObjectSetInteger(m_id, szArg, OBJPROP_YSIZE, def_SizeControls); 087. ObjectSetString(m_id, szArg, OBJPROP_FONT, def_FontName); 088. ObjectSetInteger(m_id, szArg, OBJPROP_FONTSIZE, def_FontSize); 089. ObjectSetInteger(m_id, szArg, OBJPROP_BGCOLOR, clrSkyBlue); 090. ObjectSetInteger(m_id, szArg, OBJPROP_COLOR, clrBlack); 091. ObjectSetInteger(m_id, szArg, OBJPROP_BORDER_COLOR, clrBlack); 092. ObjectSetString(m_id, szArg, OBJPROP_TEXT, szTxt); 093. } 094. //+------------------------------------------------------------------+ 095. void CreateObjPanel(const string szArg) 096. { 097. ObjectCreate(m_id, szArg, OBJ_BITMAP_LABEL, m_sub, 0, 0); 098. ObjectSetInteger(m_id, szArg, OBJPROP_XDISTANCE, 2); 099. ObjectSetInteger(m_id, szArg, OBJPROP_YDISTANCE, m_txtHeight + 8); 100. ObjectSetString(m_id, szArg, OBJPROP_BMPFILE, "::" + m_ObjPanel); 101. } 102. //+------------------------------------------------------------------+ 103. public : 104. //+------------------------------------------------------------------+ 105. C_Chat() 106. :m_index(0), 107. m_full(false), 108. m_Width(0), 109. m_Height(0) 110. { 111. int tmp; 112. 113. m_sub = ChartWindowFind(m_id = ChartID(), def_ShortName); 114. TextSetFont(def_FontName, -10 * def_FontSize, 0, 0); 115. TextGetSize("M", tmp, m_txtHeight); 116. CreateObjEdit(m_ObjEdit = def_ShortName + " Edit" + (string)ObjectsTotal(m_id)); 117. CreateObjButton(m_ObjBtn = def_ShortName + " Button" + (string)ObjectsTotal(m_id), "Send"); 118. CreateObjPanel(m_ObjPanel = def_ShortName + " Panel" + (string)ObjectsTotal(m_id)); 119. } 120. //+------------------------------------------------------------------+ 121. ~C_Chat() 122. { 123. ObjectsDeleteAll(m_id, def_ShortName); 124. }; 125. //+------------------------------------------------------------------+ 126. void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam) 127. { 128. switch(id) 129. { 130. case CHARTEVENT_CHART_CHANGE: 131. m_Width = (ushort)ChartGetInteger(m_id, CHART_WIDTH_IN_PIXELS, m_sub); 132. m_Height = (ushort)ChartGetInteger(m_id, CHART_HEIGHT_IN_PIXELS, m_sub); 133. ObjectSetInteger(m_id, m_ObjEdit, OBJPROP_XSIZE, m_Width - 75); 134. ObjectSetInteger(m_id, m_ObjBtn, OBJPROP_XDISTANCE, m_Width - 72); 135. ObjectSetInteger(m_id, m_ObjPanel, OBJPROP_XSIZE, m_Width - 4); 136. ObjectSetInteger(m_id, m_ObjPanel, OBJPROP_YSIZE, m_Height - 4); 137. ArrayResize(m_Pixel, m_Width * m_Height); 138. Paint(); 139. break; 140. case CHARTEVENT_OBJECT_CLICK: 141. if (sparam == m_ObjBtn) 142. { 143. string sz0 = ObjectGetString(m_id, m_ObjEdit, OBJPROP_TEXT); 144. if (sz0 != "") 145. { 146. EventChartCustom(m_id, evChatWriteSocket, 0, 0, sz0); 147. Add(sz0, true); 148. ObjectSetString(m_id, m_ObjEdit, OBJPROP_TEXT, ""); 149. ObjectSetInteger(m_id, m_ObjBtn, OBJPROP_STATE, 0); 150. } 151. } 152. break; 153. case CHARTEVENT_CUSTOM + evChatReadSocket: 154. Add(sparam); 155. break; 156. } 157. } 158. //+------------------------------------------------------------------+ 159. }; 160. //+------------------------------------------------------------------+ 161. #undef macroColorRGBA 162. #undef def_MaxRows 163. //+------------------------------------------------------------------+
C_Chat.mqhファイルのコード
このコードは、私たちが操作するオブジェクトを生成します。多くの熱心な読者が本記事を読んでいることを承知しています。すでに高度なプログラミングスキルを持っている方には少し辛抱してほしいと思います。このコードの詳細な説明を提供するのは、使用・改良したい読者が自分で実装できるようにするためです。同時に、より多くの読者が自分の解決策を開発するモチベーションにもなることを意図しています。
では、説明を進めていきます。4行目では、いくつかの定義を含むヘッダファイルをインクルードしています。しかし、実際に使用する定義は最近追加されたものです。6行目ではインジケーターの名前を定義しています。7行目では、ミニチャットが表示する最大行数を設定しています。これは後で詳しく説明します。8行目と9行目ではフォント名とサイズを定義しています。これにより、全体の寸法をグローバルに調整しやすくなります。10行目はコントロールの高さを指定し、表示の問題を防ぎます。11行目はチャート上に文字を配置するためのマクロで、これについては後ほど詳しく説明します。
これらの定義が整ったら、クラスの作成を開始できます。15行目でprivateセクションを宣言しています。ここ以降で宣言された変数や関数はすべてクラスC_Chatにのみ属します。16〜26行目では、必要最小限の変数セットを定義しています。ミニチャットにさらに機能を追加したい場合は、この箇所でオブジェクトの数を増やすべきです。27行目では、他のユーザーが投稿したメッセージに高速アクセスするための構造体を宣言しています。
重要な点として、サーバーはメッセージを保存しません。接続前に投稿されたメッセージは失われます。また、チャートの時間軸を変更するとすべてのチャートオブジェクトが削除されるため、この構造体に保存されたメッセージも失われます。メッセージを保持したい場合は、ファイルに保存・復元する方法を実装する必要があります。やるべきことは、27行目で定義した構造体を保存し、読み戻す際に以下で説明する手順を呼び出すだけです。簡単です。
次に31行目を見てください。ここでは7行目で定義した値を使用しています。MQL5はC/C++と同様に0ベースのインデックスを使うため、1を加えています。これにより、宣言された最後の256行が保持されます。この数は必要に応じて調整可能です。
次にクラスの機能部分、つまりミニチャットを動作させる手続きに入ります。最初の手続きでは、27行目で宣言した構造体に新しいメッセージを追加します。35行目と36行目で対応するフィールドに値を代入します。
37行目ではメッセージカウンターが最大に達したかを確認します。達していれば、カウンタを0に設定します(40行目)。この限界に達したかどうかは39行目で、メッセージリストが満杯であることを示すフラグを立てることで確認します。
なぜこれが必要かというと、最大メッセージ数に達したら古いメッセージが上書きされるためです。これにより、古い位置に新しいメッセージを書き込みつつ、リストの容量を常に満杯に保つ循環バッファが形成されます。この動作は必要に応じて変更可能です。
次に重要なステップは、チャート上にメッセージを表示する処理で、45行目から始まる手続きでおこなわれます。
その前に、45行目を正しく理解するために他の設定手順を見てみましょう。95行目では、循環リストのテキストを表示するパネルを作成します。非常にシンプルに作っています。97行目でOBJ_BITMAP_LABELオブジェクトを作成し、98〜99行目でオブジェクトの左上の位置を設定します。本当に重要なのは100行目で、どのビットマップを使うかを指定しています。
「ビットマップ?」と思うかもしれません。なぜ単純なテキストではなくビットマップを使うのか疑問に思うでしょう。しかし、MQL5では、必要な正確な位置にテキストを描画する簡単な方法がありません。Comment()関数では不十分です。文字の表示位置や方法を正確に制御する必要があるためです。ビットマップを使用することで文字を正確に描画でき、MetaTrader 5が描画して表示してくれます。
複雑に聞こえますが、実際は単純です。100行目ではビットマップの名前を定義しているだけです。これで45行目に戻ると、そこでおこなわれる処理の意味がすぐに理解できます。
まず49行目でピクセル行列をクリアします。色は任意です。この場合はclrBlack、つまり背景色は黒です。好みの値に設定できます。その直後、ミニチャットウィンドウにテキストパネルを表示するかを判定する小さな計算をおこない、必要なければ終了します。表示する場合はテキストを描画します。
51行目ではテキストパネル内に表示可能な最大行数をカウントします。52行目では循環リストの開始点を探し、すべての行を表示します。ここで注意点があります。リストが満杯の場合、変数m_indexはパネル内の行数より小さいことも大きいことも等しいこともあります。そのため、この値を修正する必要があります。
53〜56行目でこのインデックスを正しく調整します。これでカウント用変数は、表示する最も古い行を正しく指すようになります。57行目のループに入ります。このループは一見複雑に見えるかもしれませんが、非常に単純です。循環リストを順番に走査し、61行目でMQL5ライブラリを呼び出してテキストをビットマップに描画します。
ループが終了すると、ビットマップにテキストが描画されます。その後、MetaTrader 5にビットマップが使用可能であることを通知します(64行目)。この一連の流れに注意してください。まずビットマップ名を定義し、次に描画し、最後にMetaTrader 5に表示可能であることを伝えます。これが64行目でおこなわれる処理です。シンプルですよね。
68〜93行目の手続きは、MQL5プログラミングでは比較的一般的なため、詳細な説明は省きます。しかし、まだ終わりではありません。次に、103行目のpublic句の宣言後にあるクラスコンストラクタに移ります。103行目以降のすべてはクラス外からも見えるようになります。105行目のコンストラクタでは、ミニチャットを初期化します。これは、非常に簡単な手順でおこなわれます。
まず113行目で、ミニチャットを表示するウィンドウのインデックスを取得します。直後の114行目では、MetaTrader5にビットマップ用テキストのフォントを指定します。115行目では文字の高さを取得します。OS設定により文字サイズが異なる場合があるため、見栄えが変になるのを防ぐためです。
次に116〜118行目で必要なオブジェクトを作成します。追加のオブジェクトが必要な場合は、この箇所で追加します。作成ルールはコード内のオブジェクトの例に従ってください。
121行目のデストラクタの役割は1つだけで、作成したオブジェクトをチャートから削除することです。そのため123行目だけが含まれています。
126行目では、MetaTrader5がインジケーターに送るメッセージを処理します。ここでもまだインジケーター内のコードです。できるだけ簡単にするために、3種類のイベント(メッセージ)だけを処理します。その1つがCHARTEVENT_CHART_CHANGEで、オブジェクトの位置と寸法を正しく定義します。これはコードがチャートに置かれた瞬間にMetaTrader 5が生成する最初のメッセージです。
さらに、CHARTEVENT_OBJECT_CLICKメッセージも処理します。これはチャート上でクリックが発生した場合です。この場合141行目でクリックが送信ボタン上かどうかを確認します。送信ボタンであれば、143行目で編集エリアにあるテキストを取得し、すぐ後で送信できるようにします。144行目では、テキストが空でないことを確認します。空でなければ146行目でカスタムイベントを発生させ、同じテキストをEAに送信します。
重要な点として、EAが編集オブジェクトのテキストに直接アクセスできても、それを取得させるべきではありません。インジケーターによって既に削除されている可能性があるためです(148行目の結果)。いずれにせよ、カスタムイベントで送信された同じテキストは147行目でパネルに表示されます。
最後に153行目でカスタムイベントがあります。これはEAがソケット経由で受信したテキストをキャプチャし、ミニチャットのテキストパネルに配置するためのものです。本記事に直接来た読者には少し混乱するかもしれません。本連載の以前の記事では、カスタムイベントの扱い方を詳細に説明しています。興味があればそちらを参照すると理解が深まります。
それでは、本記事で提示する最終コード、ミニチャットインジケーターの完成版を見てみましょう。以下をご覧ください。
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. #property description "Base indicator for Mini Chat." 04. #property description "It cannot be used without outside assistance." 05. #property version "1.00" 06. #property link "https://www.mql5.com/pt/articles/12672" 07. #property indicator_chart_window 08. #property indicator_plots 0 09. //+------------------------------------------------------------------+ 10. #include <Market Replay\Mini Chat\C_Chat.mqh> 11. //+------------------------------------------------------------------+ 12. C_Chat *Chat; 13. //+------------------------------------------------------------------+ 14. int OnInit() 15. { 16. long id = ChartID(); 17. string sz0 = def_ShortName + "_TMP"; 18. int i0; 19. 20. IndicatorSetString(INDICATOR_SHORTNAME, sz0); 21. for (int c0 = (int)ChartGetInteger(id, CHART_WINDOWS_TOTAL) - 1; c0 >= 0; c0--) 22. if (ChartIndicatorName(id, c0, 0) == def_ShortName) 23. { 24. ChartIndicatorDelete(id, ChartWindowFind(id, sz0), sz0); 25. return INIT_FAILED; 26. } 27. IndicatorSetString(INDICATOR_SHORTNAME, def_ShortName); 28. i0 = ChartWindowFind(id, def_ShortName); 29. if ((i0 == 0) || (ChartIndicatorsTotal(id, i0) > 1)) 30. { 31. ChartIndicatorDelete(id, i0, def_ShortName); 32. return INIT_FAILED; 33. } 34. 35. Chat = new C_Chat(); 36. 37. return INIT_SUCCEEDED; 38. } 39. //+------------------------------------------------------------------+ 40. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 41. { 42. return rates_total; 43. } 44. //+------------------------------------------------------------------+ 45. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 46. { 47. (*Chat).DispatchMessage(id, lparam, dparam, sparam); 48. } 49. //+------------------------------------------------------------------+ 50. void OnDeinit(const int reason) 51. { 52. delete Chat; 53. } 54. //+------------------------------------------------------------------+
インジケーターのソースコード
最終的な考察
本記事では、ミニチャットコードの前半部分を紹介しました。EAやサーバー側のコードについては、まだ説明していません。内容が長くなるため、まずはここまでのインジケーターコードを確認し、試してみてください。次回の記事では、サーバーを含めたミニチャットの実装を完成させ、実際に動作させる様子を紹介します。
| ファイル | 説明 |
|---|---|
| Experts\Expert Advisor.mq5 | Chart TradeとEAの連携を示す(Mouse Studyが必要) |
| Indicators\Chart Trade.mq5 | 送信する注文を設定するウィンドウを作成(Mouse Studyが必要) |
| Indicators\Market Replay.mq5 | リプレイ/シミュレーターサービスと対話するためのコントロールを作成する(Mouse Studyが必要) |
| Indicators\Mouse Study.mq5 | グラフィカルコントロールとユーザー間のインタラクションを実現する(リプレイ/シミュレーターおよび実取引の両方で必須) |
| Servicios\Market Replay.mq5 | マーケットリプレイおよびシミュレーションサービスを生成し、維持する(システム全体のメインファイル) |
| VS C++ Server.cpp | C++でソケットサーバーを作成および管理する(ミニチャットバージョン) |
| Python code Server.py | MetaTrader 5とExcel間の通信用のPythonソケットを作成および維持する |
| ScriptsCheckSocket.mq5 | 外部ソケットとの接続を確認する |
| Indicators\Mini Chat.mq5 | インジケーターとしてミニチャットを実装する(サーバーが必要) |
| Experts\Mini Chat.mq5 | ミニチャットをEAとして実装する(サーバーが必要) |
MetaQuotes Ltdによりポルトガル語から翻訳されました。
元の記事: https://www.mql5.com/pt/articles/12672
警告: これらの資料についてのすべての権利はMetaQuotes Ltd.が保有しています。これらの資料の全部または一部の複製や再プリントは禁じられています。
この記事はサイトのユーザーによって執筆されたものであり、著者の個人的な見解を反映しています。MetaQuotes Ltdは、提示された情報の正確性や、記載されているソリューション、戦略、または推奨事項の使用によって生じたいかなる結果についても責任を負いません。
外国為替におけるフィボナッチ(第1回):価格と時間の関係を調べる
初級から中級まで:構造体(II)
エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法
取引におけるニューラルネットワーク:ResNeXtモデルに基づくマルチタスク学習(最終回)
- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索