English Русский Español Português
preview
市場シミュレーション(第8回):ソケット(II)

市場シミュレーション(第8回):ソケット(II)

MetaTrader 5 |
13 0
Daniel Jose
Daniel Jose

はじめに

前回の「市場シミュレーション(第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.cppC++でソケットサーバーを作成および管理する(ミニチャットバージョン)
Python code Server.pyMetaTrader 5とExcel間の通信用のPythonソケットを作成および維持する
ScriptsCheckSocket.mq5外部ソケットとの接続を確認する
Indicators\Mini Chat.mq5インジケーターとしてミニチャットを実装する(サーバーが必要)
Experts\Mini Chat.mq5ミニチャットをEAとして実装する(サーバーが必要)

MetaQuotes Ltdによりポルトガル語から翻訳されました。
元の記事: https://www.mql5.com/pt/articles/12672

添付されたファイル |
Anexo.zip (560.03 KB)
外国為替におけるフィボナッチ(第1回):価格と時間の関係を調べる 外国為替におけるフィボナッチ(第1回):価格と時間の関係を調べる
市場はフィボナッチに基づく関係性をどのように観測しているのでしょうか。各項が直前の2つの項の和になっているこの数列(1, 1, 2, 3, 5, 8, 13, 21...)は、ウサギの個体数の増加を説明するだけのものではありません。私たちは、「世界のあらゆるものは数の一定の関係に従う」というピタゴラス派の仮説を考察します。
初級から中級まで:構造体(II) 初級から中級まで:構造体(II)
本記事では、MQL5のようなプログラミング言語において構造体が存在する理由を考察します。また、構造体を用いて関数や手続き間で値を受け渡すことが有効な場合と、必ずしもそうではない場合がある理由についても解説します。
エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法 エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法
この記事では、MT4において複数のEAの衝突をさける方法を扱います。ターミナルの操作、MQL4の基本的な使い方がわかる人にとって、役に立つでしょう。
取引におけるニューラルネットワーク:ResNeXtモデルに基づくマルチタスク学習(最終回) 取引におけるニューラルネットワーク:ResNeXtモデルに基づくマルチタスク学習(最終回)
ResNeXtに基づくマルチタスク学習フレームワークの探求を続けます。このフレームワークは、モジュール性が高く、計算効率に優れ、データ中の安定したパターンを特定できることが特徴です。単一のエンコーダーと専門化された「ヘッド」を使用することで、モデルの過学習のリスクを減らし、予測の精度を向上させます。