English Русский 中文 Español Deutsch Português
preview
リプレイシステムの開発(第59回):新たな未来

リプレイシステムの開発(第59回):新たな未来

MetaTrader 5 | 26 2月 2025, 09:01
56 0
Daniel Jose
Daniel Jose

はじめに

前回の「リプレイシステムの開発(第58回):サービス業務への復帰」では、システムにいくつかの変更が加えられたことを紹介しました。その中で、スケジュールにテンプレートを適用してからサービスによってスケジュールが更新されるまでの間にわずかな遅延が生じる理由についても触れました。この遅延が発生しない場合、モジュールはサービスを強制的に早期終了させてしまいます。なぜこのようなことが起こるのか、不思議に思った方もいるかもしれません。本記事では、その仕組みについてさらに詳しく掘り下げます。

この内容を正しく理解し、概念を完全に把握するために、以下のビデオ01をご覧ください。


デモビデオ:物事が失敗する理由

このビデオでは、実際に何が起こっているのかを編集や変更を加えず、そのまま実演しています。しかし、なぜこの問題が発生するのでしょうか。説明を正しく理解するためには、前回の記事の要点をもう一度確認することが重要です。ここで簡単にまとめておきます。


サービスが停止する理由を理解する

MetaTrader 5がテンプレートを適用すると、すぐにサービスが早期シャットダウンしてしまうのには明確な理由があります。その説明はシンプルです。インジケーター、より正確には制御モジュールがチャートを強制的に閉じているのです。まだよく分からないでしょうか。では、この動作の原因となっているコントロールインジケーターのコードの関連セクションを見てみましょう。以下にコードスニペットを示します。

53. void OnDeinit(const int reason)
54. {
55.    switch (reason)
56.    {
57.       case REASON_TEMPLATE:
58.          Print("Modified template. Replay // simulation system shutting down.");
59.       case REASON_INITFAILED:
60.       case REASON_PARAMETERS:
61.       case REASON_REMOVE:
62.       case REASON_CHARTCLOSE:
63.          ChartClose(user00);
64.          break;
65.    }
66.    delete control;
67. }

制御モジュールのコードフラグメント

63行目に注目してください。この部分には、MetaTrader 5にチャートを閉じるよう指示する呼び出しがあります。しかし、実際にこの処理を実行しているのは誰でしょうか。もしテンプレートの変更によってのみトリガーされた場合、58行目のメッセージがMetaTrader 5のメッセージボックスに表示されるはずです。しかし、実際にはこのメッセージは出力されません。これは、チャートの閉鎖が単純にテンプレートの変更によって引き起こされたのではないことを示しています。

この挙動に疑問を持ち、説明にも納得がいかないのは理解できます。しかし、実際には、MetaTrader 5でテンプレートを適用すると、チャートからコントロールインジケーターが削除され、その結果としてチャートが閉じてしまいます。ここで重要なのは、このプロセスが非同期的に発生するという点です。つまり、イベントは予想される順序どおりに進行するとは限りません。

実際に発生するのは、MetaTrader 5がある時点でテンプレートの適用を完了すると、インジケーターにDeinitイベントを送信するという動作です。このイベントはOnDeinit関数によって処理されます。しかし、意外なことに、reason変数にはREASON_TEMPLATEではなくREASON_REMOVEが設定されます。これは、MetaTrader 5がテンプレート適用のプロセスの一環として、コントロールインジケーターを積極的に削除しているためです。その結果、58行目のメッセージは端末のメッセージウィンドウには表示されません。

次に疑問に思うかもしれません。なぜMetaTrader 5はテンプレートを同期的に適用しないのでしょうか。つまり、C_Replay.mqhヘッダーファイルの87行目にあるChartApplyTemplate関数を実行しても、テンプレートがすぐに完全に適用されないのはなぜでしょう。答えはパフォーマンスの最適化にあります。テンプレートには、複数のインジケーターやエキスパートアドバイザー(EA)が含まれている場合があります。EAの中には、適切に動作するために一定数のバーがチャート上に存在することを必要とするものもあります。

MetaTrader 5がChartApplyTemplateが同期的に完了するのを待機する場合、プラットフォームが数秒間フリーズしたり、テンプレート内の何かが重大な障害を引き起こした場合にクラッシュしたりする可能性があります。これを避けるため、MetaTrader 5はテンプレート適用を非同期的に処理しています。しかし、これが経験の浅いプログラマーにとっての課題となります。どの関数が非同期的に実行され、どの関数が即時に実行されるのかを理解する必要があるからです。

ChartRedrawを使用して即時にチャートの更新を強制すると、テンプレートが即座に適用されるとは限らないため、別のアプローチが必要です。デモビデオでは実装されたソリューションについて説明します。

コントロールインジケーターをテンプレートに直接含めることで、この問題を解決できないのはなぜでしょうか。これにより早期閉鎖の問題は回避できますが、別の問題が発生します。以前のバージョンでは、コントロールインジケーターが意図しないチャートに表示されないようにするため、グローバルターミナル変数を使用していました。しかし、現在はこの方法に頼らず、別のブロッキングメカニズムを使用するようになりました。そのため、テンプレートにコントロールインジケーターを含めることはできません。そうすると、解決が困難な複雑な問題が発生するからです。

前回の記事で述べたように、コントロールモジュールとマウスモジュールの両方に変更が加えられました。これは、リプレイ/シミュレーションサービスに再び重点を置いた際に行われた調整の結果です。それでは、両方のインジケーターの更新されたコードを確認し、主な変更点に焦点を当ててみましょう。


リプレイ/シミュレーションサービスの更新されたコード

以下に、コントロールモジュールのソースコードの最新バージョンを示します。

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.59"
07. #property link "https://www.mql5.com/ja/articles/12075"
08. #property indicator_chart_window
09. #property indicator_plots 0
10. #property indicator_buffers 1
11. //+------------------------------------------------------------------+
12. #include <Market Replay\Service Graphics\C_Controls.mqh>
13. //+------------------------------------------------------------------+
14. C_Controls *control = NULL;
15. //+------------------------------------------------------------------+
16. input long user00 = 0;      //ID
17. //+------------------------------------------------------------------+
18. double m_Buff[];
19. int    m_RatesTotal = 0;
20. //+------------------------------------------------------------------+
21. int OnInit()
22. {
23.    ResetLastError();   
24.    if (CheckPointer(control = new C_Controls(user00, "Market Replay Control", new C_Mouse(user00, "Indicator Mouse Study"))) == POINTER_INVALID)
25.       SetUserError(C_Terminal::ERR_PointerInvalid);
26.    if ((_LastError != ERR_SUCCESS) || (user00 == 0))
27.    {
28.       Print("Control indicator failed on initialization.");
29.       return INIT_FAILED;
30.    }
31.    SetIndexBuffer(0, m_Buff, INDICATOR_DATA);
32.    ArrayInitialize(m_Buff, EMPTY_VALUE);
33.    
34.    return INIT_SUCCEEDED;
35. }
36. //+------------------------------------------------------------------+
37. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
38. {
39.    return m_RatesTotal = rates_total;
40. }
41. //+------------------------------------------------------------------+
42. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
43. {
44.    (*control).DispatchMessage(id, lparam, dparam, sparam);
45.    if (_LastError >= ERR_USER_ERROR_FIRST + C_Terminal::ERR_Unknown)
46.    {
47.       Print("Internal failure in the messaging system...");
48.       ChartClose(user00);
49.    }
50.    (*control).SetBuffer(m_RatesTotal, m_Buff);
51. }
52. //+------------------------------------------------------------------+
53. void OnDeinit(const int reason)
54. {
55.    switch (reason)
56.    {
57.       case REASON_TEMPLATE:
58.          Print("Modified template. Replay // simulation system shutting down.");
59.       case REASON_INITFAILED:
60.       case REASON_PARAMETERS:
61.       case REASON_REMOVE:
62.       case REASON_CHARTCLOSE:
63.          ChartClose(user00);
64.          break;
65.    }
66.    delete control;
67. }
68. //+------------------------------------------------------------------+
69. 

コントロール指標のソースコード

ヘッダーファイル内のコードは変更されていないため、表示されていないことにお気づきかもしれません。上記の制御モジュールコード全体を見ることもできますが、ここでは変更された部分に焦点を当てます。唯一の違いは26行目にあります。これに従って、2つの条件を確認します。1つは_LastError値で、もう1つはuser00値です。このuser00は16行目に定義されており、制御モジュールを実行するチャートのIDをリプレイ/シミュレーションサービスから受信する役割を担っています。

ここで気を付けてください。通常、ユーザーとしては、16行目の先頭に割り当てるべき値を決定することはできません。それはシステムによって自動的に決定されます。しかし、もしシステムを騙して構成全体をテンプレートファイルとして保存しようとしたらどうなるでしょうか。これによってすべてが正しく機能し、テンプレートを適用する手間が省けるように思えますよね。いいえ。実際には期待どおりには動作しません。

サービスがすべてをロードし、MetaTrader 5がチャートを安定化させた後に、この完成したチャートをテンプレートとして保存すると、テンプレートファイルを開いた際に何かに気付くはずです。これは以下の断片から確認できます。

01. <indicator>
02. name=Custom Indicator
03. path=Services\Market Replay.ex5::Indicators\Market Replay.ex5
04. apply=0
05. show_data=1
06. scale_inherit=0
07. scale_line=0
08. scale_line_percent=50
09. scale_line_value=0.000000
10. scale_fix_min=0
11. scale_fix_min_val=0.000000
12. scale_fix_max=0
13. scale_fix_max_val=0.000000
14. expertmode=1610613824
15. fixed_height=-1
16. 
17. <graph>
18. name=
19. draw=0
20. style=0
21. width=1
22. color=
23. </graph>
24. <inputs>
25. user00=130652731570824061
26. </inputs>
27. </indicator>

テンプレートファイルフラグメント

ファイル内でのこのコードフラグメントの正確な位置はここでは関係ありません。行番号は、説明をより明確にするための単なる参照です。

1行目に開始タグが含まれ、27行目で構造が閉じられていることに注目してください。これら2つのタグの間には、チャートに適用されるインジケーターの位置(3行目に示されている)など、さまざまな詳細が指定されます。素晴らしい。さて、24行目に注目してください。ここでタグは、3行目に定義されたインジケーターによって期待される入力パラメーターのセクションを開きます。同様に、26行目はこのセクションの終了を示します。

25行目では、インジケーターのソースコードの16行目に宣言され、チャートIDを受け取る入力パラメーターを参照します。この行は、インジケーターのソースコードの26行目でテストされた条件がfalseと評価されるように、コントロールインジケーターに値を割り当て、インジケーターが適切に読み込まれて機能できるようにします。そうすれば正しく動作するはずですよね。しかし、実際にはそうなりません。リプレイ/シミュレーションサービスがコントロールインジケーターをチャートに正しく適用すると、インジケーターコードは別のインスタンスがすでに実行されていることを検出します。これが起こると、制御インジケーターコードの24行目が25行目をトリガーし、その結果、_LastErrorは26行目で期待される値を保持できなくなり、システム障害が発生します。その結果、MetaTrader 5はDeinitイベントを生成し、これは53行目にあるOnDeinitプロシージャによって処理されます。この時点で、実行はREASON_INITFAILEDで続行され、チャートが閉じられ、リプレイ/シミュレーションサービスが終了します。

これは、MetaTrader 5プラットフォームの機能を最大限に活用すると、すべてがシームレスに機能することを示しています。しかし、それはコントロールインジケーターの問題でした。マウスインジケーターはどうでしょうか。

マウスインジケーターの状況は、もう少し興味深いものです。このモジュールは特定のアクションの実行を許可されるが、排他的ではないことが決定されました。むしろ、このプロセスを最初に開始することになります。このため、マウスインジケーターモジュール内でいくつかの追加と削除が必要になりました。これらの調整については、次のセクションで詳しく説明します。


小さな変化が大きな改善をもたらす

マウスモジュールはもともとメインウィンドウに配置することを目的としていましたが、MQL5を使用するとそれ以上のことが可能になります。正直なところ、当初は特定の機能を実装することを考えていませんでした。しかし、MetaTrader 5は特定のアクションをより効率的に実行するための構成やカスタマイズのオプションを提供しているため、いくつかの変更を導入することにしました。次のセクションでは、以下に示すC_Terminalクラスをはじめとして、おこなわれた更新や追加について説明します。

001. //+------------------------------------------------------------------+
002. #property copyright "Daniel Jose"
003. //+------------------------------------------------------------------+
004. #include "Macros.mqh"
005. #include "..\Defines.mqh"
006. //+------------------------------------------------------------------+
007. class C_Terminal
008. {
009. //+------------------------------------------------------------------+
010.    protected:
011.       enum eErrUser {ERR_Unknown, ERR_FileAcess, ERR_PointerInvalid, ERR_NoMoreInstance};
012. //+------------------------------------------------------------------+
013.       struct st_Terminal
014.       {
015.          ENUM_SYMBOL_CHART_MODE    ChartMode;
016.          ENUM_ACCOUNT_MARGIN_MODE  TypeAccount;
017.          long           ID;
018.          string         szSymbol;
019.          int            Width,
020.                         Height,
021.                         nDigits,
022.                         SubWin;
023.          double         PointPerTick,
024.                         ValuePerPoint,
025.                         VolumeMinimal,
026.                         AdjustToTrade;
027.       };
028. //+------------------------------------------------------------------+
029.    private   :
030.       st_Terminal m_Infos;
031.       struct mem
032.       {
033.          long   Show_Descr,
034.                 Show_Date;
035.          bool   AccountLock;
036.       }m_Mem;
037. //+------------------------------------------------------------------+
038.       void CurrentSymbol(void)
039.          {
040.             MqlDateTime mdt1;
041.             string sz0, sz1;
042.             datetime dt = macroGetDate(TimeCurrent(mdt1));
043.             enum eTypeSymbol {WIN, IND, WDO, DOL, OTHER} eTS = OTHER;
044.       
045.             sz0 = StringSubstr(m_Infos.szSymbol = _Symbol, 0, 3);
046.             for (eTypeSymbol c0 = 0; (c0 < OTHER) && (eTS == OTHER); c0++) eTS = (EnumToString(c0) == sz0 ? c0 : eTS);
047.             switch (eTS)
048.             {
049.                case DOL   :
050.                case WDO   : sz1 = "FGHJKMNQUVXZ"; break;
051.                case IND   :
052.                case WIN   : sz1 = "GJMQVZ";       break;
053.                default    : return;
054.             }
055.             for (int i0 = 0, i1 = mdt1.year - 2000, imax = StringLen(sz1);; i0 = ((++i0) < imax ? i0 : 0), i1 += (i0 == 0 ? 1 : 0))
056.                if (dt < macroGetDate(SymbolInfoInteger(m_Infos.szSymbol = StringFormat("%s%s%d", sz0, StringSubstr(sz1, i0, 1), i1), SYMBOL_EXPIRATION_TIME))) break;
057.          }
058. //+------------------------------------------------------------------+
059.    public   :
060. //+------------------------------------------------------------------+      
061.       C_Terminal(const long id = 0, const uchar sub = 0)
062.          {
063.             m_Infos.ID = (id == 0 ? ChartID() : id);
064.             m_Mem.AccountLock = false;
065.             m_Infos.SubWin = (int) sub;
066.             CurrentSymbol();
067.             m_Mem.Show_Descr = ChartGetInteger(m_Infos.ID, CHART_SHOW_OBJECT_DESCR);
068.             m_Mem.Show_Date  = ChartGetInteger(m_Infos.ID, CHART_SHOW_DATE_SCALE);
069.             ChartSetInteger(m_Infos.ID, CHART_SHOW_OBJECT_DESCR, false);
070.             ChartSetInteger(m_Infos.ID, CHART_EVENT_OBJECT_DELETE, true);
071.             ChartSetInteger(m_Infos.ID, CHART_EVENT_OBJECT_CREATE, true);
072.             ChartSetInteger(m_Infos.ID, CHART_SHOW_DATE_SCALE, false);
073.             m_Infos.nDigits = (int) SymbolInfoInteger(m_Infos.szSymbol, SYMBOL_DIGITS);
074.             m_Infos.Width   = (int)ChartGetInteger(m_Infos.ID, CHART_WIDTH_IN_PIXELS);
075.             m_Infos.Height  = (int)ChartGetInteger(m_Infos.ID, CHART_HEIGHT_IN_PIXELS);
076.             m_Infos.PointPerTick  = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_TRADE_TICK_SIZE);
077.             m_Infos.ValuePerPoint = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_TRADE_TICK_VALUE);
078.             m_Infos.VolumeMinimal = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_VOLUME_STEP);
079.             m_Infos.AdjustToTrade = m_Infos.ValuePerPoint / m_Infos.PointPerTick;
080.             m_Infos.ChartMode   = (ENUM_SYMBOL_CHART_MODE) SymbolInfoInteger(m_Infos.szSymbol, SYMBOL_CHART_MODE);
081.             if(m_Infos.szSymbol != def_SymbolReplay) SetTypeAccount((ENUM_ACCOUNT_MARGIN_MODE)AccountInfoInteger(ACCOUNT_MARGIN_MODE));
082.             ResetLastError();
083.          }
084. //+------------------------------------------------------------------+
085.       ~C_Terminal()
086.          {
087.             ChartSetInteger(m_Infos.ID, CHART_SHOW_DATE_SCALE, m_Mem.Show_Date);
088.             ChartSetInteger(m_Infos.ID, CHART_SHOW_OBJECT_DESCR, m_Mem.Show_Descr);
089.             ChartSetInteger(m_Infos.ID, CHART_EVENT_OBJECT_DELETE, false);
090.             ChartSetInteger(m_Infos.ID, CHART_EVENT_OBJECT_CREATE, false);
091.          }
092. //+------------------------------------------------------------------+
093. inline void SetTypeAccount(const ENUM_ACCOUNT_MARGIN_MODE arg)
094.          {
095.             if (m_Mem.AccountLock) return; else m_Mem.AccountLock = true;
096.             m_Infos.TypeAccount = (arg == ACCOUNT_MARGIN_MODE_RETAIL_HEDGING ? arg : ACCOUNT_MARGIN_MODE_RETAIL_NETTING);
097.          }
098. //+------------------------------------------------------------------+
099. inline const st_Terminal GetInfoTerminal(void) const
100.          {
101.             return m_Infos;
102.          }
103. //+------------------------------------------------------------------+
104. const double AdjustPrice(const double arg) const
105.          {
106.             return NormalizeDouble(round(arg / m_Infos.PointPerTick) * m_Infos.PointPerTick, m_Infos.nDigits);
107.          }
108. //+------------------------------------------------------------------+
109. inline datetime AdjustTime(const datetime arg)
110.          {
111.             int nSeconds= PeriodSeconds();
112.             datetime   dt = iTime(m_Infos.szSymbol, PERIOD_CURRENT, 0);
113.             
114.             return (dt < arg ? ((datetime)(arg / nSeconds) * nSeconds) : iTime(m_Infos.szSymbol, PERIOD_CURRENT, Bars(m_Infos.szSymbol, PERIOD_CURRENT, arg, dt)));
115.          }
116. //+------------------------------------------------------------------+
117. inline double FinanceToPoints(const double Finance, const uint Leverage)
118.          {
119.             double volume = m_Infos.VolumeMinimal + (m_Infos.VolumeMinimal * (Leverage - 1));
120.             
121.             return AdjustPrice(MathAbs(((Finance / volume) / m_Infos.AdjustToTrade)));
122.          };
123. //+------------------------------------------------------------------+
124.       void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
125.          {
126.             static string st_str = "";
127.             
128.             switch (id)
129.             {
130.                case CHARTEVENT_CHART_CHANGE:
131.                   m_Infos.Width  = (int)ChartGetInteger(m_Infos.ID, CHART_WIDTH_IN_PIXELS);
132.                   m_Infos.Height = (int)ChartGetInteger(m_Infos.ID, CHART_HEIGHT_IN_PIXELS);
133.                   break;
134.                case CHARTEVENT_OBJECT_CLICK:
135.                   if (st_str != sparam) ObjectSetInteger(m_Infos.ID, st_str, OBJPROP_SELECTED, false);
136.                   if (ObjectGetInteger(m_Infos.ID, sparam, OBJPROP_SELECTABLE) == true)
137.                      ObjectSetInteger(m_Infos.ID, st_str = sparam, OBJPROP_SELECTED, true);
138.                   break;
139.                case CHARTEVENT_OBJECT_CREATE:
140.                   if (st_str != sparam) ObjectSetInteger(m_Infos.ID, st_str, OBJPROP_SELECTED, false);
141.                   st_str = sparam;
142.                   break;
143.             }
144.          }
145. //+------------------------------------------------------------------+
146. inline void CreateObjectGraphics(const string szName, const ENUM_OBJECT obj, const color cor = clrNONE, const int zOrder = -1) const
147.          {
148.             ChartSetInteger(m_Infos.ID, CHART_EVENT_OBJECT_CREATE, 0, false);
149.             ObjectCreate(m_Infos.ID, szName, obj, m_Infos.SubWin, 0, 0);
150.             ObjectSetString(m_Infos.ID, szName, OBJPROP_TOOLTIP, "\n");
151.             ObjectSetInteger(m_Infos.ID, szName, OBJPROP_BACK, false);
152.             ObjectSetInteger(m_Infos.ID, szName, OBJPROP_COLOR, cor);
153.             ObjectSetInteger(m_Infos.ID, szName, OBJPROP_SELECTABLE, false);
154.             ObjectSetInteger(m_Infos.ID, szName, OBJPROP_SELECTED, false);
155.             ObjectSetInteger(m_Infos.ID, szName, OBJPROP_ZORDER, zOrder);
156.             ChartSetInteger(m_Infos.ID, CHART_EVENT_OBJECT_CREATE, 0, true);
157.          }
158. //+------------------------------------------------------------------+
159.       bool IndicatorCheckPass(const string szShortName)
160.          {
161.             string szTmp = szShortName + "_TMP";
162.             
163.             if (_LastError != ERR_SUCCESS) return false;            
164.             IndicatorSetString(INDICATOR_SHORTNAME, szTmp);
165.             m_Infos.SubWin = ((m_Infos.SubWin = ChartWindowFind(m_Infos.ID, szTmp)) < 0 ? 0 : m_Infos.SubWin);
166.             if (ChartIndicatorGet(m_Infos.ID, m_Infos.SubWin, szShortName) != INVALID_HANDLE)
167.             {
168.                ChartIndicatorDelete(m_Infos.ID, 0, szTmp);
169.                Print("Only one instance is allowed...");
170.                SetUserError(C_Terminal::ERR_NoMoreInstance);
171.                
172.                return false;
173.             }
174.             IndicatorSetString(INDICATOR_SHORTNAME, szShortName);
175.             ResetLastError();
176.    
177.             return true;
178.          }
179. //+------------------------------------------------------------------+
180. };

C_Terminal.mqhヘッダーファイルのソースコード

C_Terminalクラスを含むヘッダーファイルのコードを見てみると、加えられた微妙な変更に気付かないかもしれません。これらは小さな変更ですが、特に今後実行すべきタスクにおいて、大きな効果を発揮することになります。

すべての変更は、22行目で導入された新しい変数に起因しています。以前はこの変数は存在せず、オブジェクトを特定のサブウィンドウに誘導できるようにするために追加されました。

この変数はクラス外からアクセスできるため、適切な初期値を持たせる必要があります。それを実現するために、61行目のクラスコンストラクタを変更しました。この変更により、コンストラクタは0から255までの範囲で値を受け取ることができます。チャートに2つ以上のサブウィンドウが含まれることは稀なので、この範囲は十分です。ただし、重要な点があります。65行目では、明示的な型変換を使用してこの処理をおこなっていますが、なぜ変数を最初からucharとして宣言しないのでしょうか。その理由は、下位互換性を保つためです。MQL5は符号付き整数型を前提にしており、この方法にすることで後の作業が容易になります。また、ucharを使用すると、サブウィンドウの数が最大で255に制限されます。このアプローチにより、互換性と柔軟性の両立が可能になります。

149行目では、この新しい値がどのように使われるかがわかります。具体的には、MetaTrader 5に対して、どのチャートウィンドウにオブジェクトを表示するかを通知しています。MQL5をよく知っている方には、この方法は一般的なものです。しかし、159行目から始まるIndicadorCheckPass関数を見ると、さらに興味深い点が見えてきます。

通常、チャートにインジケーターを配置する際、どのウィンドウにプロットするかを指定する必要はありません。しかし、グラフィカルオブジェクトを操作する場合、どのウィンドウにオブジェクトを含めるかを明示的に決定する必要があります。この情報がなければ、149行目の関数呼び出しでは常にオブジェクトが間違ったウィンドウに配置されます。では、どのウィンドウを選ぶべきか、どう決定すれば良いのでしょうか。

そのため、164行目でインジケーターに一時的な名前を付けるというシンプルな方法を使っています。次に、165行目で、MQL5のChartWindowFind関数を使って、MetaTrader 5にインジケーターがどのウィンドウにあるのかを正確に特定させます。重要なのは、誤って他のインジケーターにも同じ一時的な名前を付けないことです。もしChartWindowFindが有効なウィンドウインデックスを返さなければ、デフォルトでメインウィンドウ(インデックス0)が選ばれます。最後に、166行目でインジケーターがチャート上に一つだけ存在することを確認します。それ以外の部分は変更されていないため、これ以上説明は必要ありません。

これらの調整により、マウスインジケーターモジュールは任意のサブウィンドウに配置できるようになりました。ただし、正しく機能させるためには、クラスコード内のいくつかの部分を変更する必要があります。次に、C_Mouse.mqhヘッダーファイルに加えられた変更について簡単に見ていきましょう。これは以下でご覧いただけます。

001. //+------------------------------------------------------------------+
002. #property copyright "Daniel Jose"
003. //+------------------------------------------------------------------+
004. #include "C_Terminal.mqh"
005. //+------------------------------------------------------------------+
006. #define def_MousePrefixName "MouseBase" + (string)GetInfoTerminal().SubWin + "_"
007. #define macro_NameObjectStudy (def_MousePrefixName + "T" + (string)ObjectsTotal(0))
008. //+------------------------------------------------------------------+
009. class C_Mouse : public C_Terminal
010. {
011.    public   :
012.       enum eStatusMarket {eCloseMarket, eAuction, eInTrading, eInReplay};
013.       enum eBtnMouse {eKeyNull = 0x00, eClickLeft = 0x01, eClickRight = 0x02, eSHIFT_Press = 0x04, eCTRL_Press = 0x08, eClickMiddle = 0x10};
014.       struct st_Mouse
015.       {
016.          struct st00
017.          {
018.             short    X_Adjusted,
019.                      Y_Adjusted,
020.                      X_Graphics,
021.                      Y_Graphics;
022.             double   Price;
023.             datetime dt;
024.          }Position;
025.          uchar      ButtonStatus;
026.          bool       ExecStudy;
027.       };
028. //+------------------------------------------------------------------+
029.    protected:
030. //+------------------------------------------------------------------+
031.       void CreateObjToStudy(int x, int w, string szName, color backColor = clrNONE) const
032.          {
033.             if (!m_OK) return;
034.             CreateObjectGraphics(szName, OBJ_BUTTON);
035.             ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_STATE, true);
036.             ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_BORDER_COLOR, clrBlack);
037.             ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_COLOR, clrBlack);
038.             ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_BGCOLOR, backColor);
039.             ObjectSetString(GetInfoTerminal().ID, szName, OBJPROP_FONT, "Lucida Console");
040.             ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_FONTSIZE, 10);
041.             ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_CORNER, CORNER_LEFT_UPPER); 
042.             ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_XDISTANCE, x);
043.             ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_YDISTANCE, TerminalInfoInteger(TERMINAL_SCREEN_HEIGHT) + 1);
044.             ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_XSIZE, w); 
045.             ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_YSIZE, 18);
046.          }
047. //+------------------------------------------------------------------+
048.    private   :
049.       enum eStudy {eStudyNull, eStudyCreate, eStudyExecute};
050.       struct st01
051.       {
052.          st_Mouse Data;
053.          color    corLineH,
054.                   corTrendP,
055.                   corTrendN;
056.          eStudy   Study;
057.       }m_Info;
058.       struct st_Mem
059.       {
060.          bool     CrossHair,
061.                   IsFull;
062.          datetime dt;
063.          string   szShortName,
064.                   szLineH,
065.                   szLineV,
066.                   szLineT,
067.                   szBtnS;
068.       }m_Mem;
069.       bool m_OK;
070. //+------------------------------------------------------------------+
071.       void GetDimensionText(const string szArg, int &w, int &h)
072.          {
073.             TextSetFont("Lucida Console", -100, FW_NORMAL);
074.             TextGetSize(szArg, w, h);
075.             h += 5;
076.             w += 5;
077.          }
078. //+------------------------------------------------------------------+
079.       void CreateStudy(void)
080.          {
081.             if (m_Mem.IsFull)
082.             {
083.                CreateObjectGraphics(m_Mem.szLineV = macro_NameObjectStudy, OBJ_VLINE, m_Info.corLineH);
084.                CreateObjectGraphics(m_Mem.szLineT = macro_NameObjectStudy, OBJ_TREND, m_Info.corLineH);
085.                ObjectSetInteger(GetInfoTerminal().ID, m_Mem.szLineT, OBJPROP_WIDTH, 2);
086.                CreateObjToStudy(0, 0, m_Mem.szBtnS = macro_NameObjectStudy);
087.             }
088.             m_Info.Study = eStudyCreate;
089.          }
090. //+------------------------------------------------------------------+
091.       void ExecuteStudy(const double memPrice)
092.          {
093.             double v1 = GetInfoMouse().Position.Price - memPrice;
094.             int w, h;
095.             
096.             if (!CheckClick(eClickLeft))
097.             {
098.                m_Info.Study = eStudyNull;
099.                ChartSetInteger(GetInfoTerminal().ID, CHART_MOUSE_SCROLL, true);
100.                if (m_Mem.IsFull)   ObjectsDeleteAll(GetInfoTerminal().ID, def_MousePrefixName + "T");
101.             }else if (m_Mem.IsFull)
102.             {
103.                string sz1 = StringFormat(" %." + (string)GetInfoTerminal().nDigits + "f [ %d ] %02.02f%% ",
104.                   MathAbs(v1), Bars(GetInfoTerminal().szSymbol, PERIOD_CURRENT, m_Mem.dt, GetInfoMouse().Position.dt) - 1, MathAbs((v1 / memPrice) * 100.0));
105.                GetDimensionText(sz1, w, h);
106.                ObjectSetString(GetInfoTerminal().ID, m_Mem.szBtnS, OBJPROP_TEXT, sz1);                                                
107.                ObjectSetInteger(GetInfoTerminal().ID, m_Mem.szBtnS, OBJPROP_BGCOLOR, (v1 < 0 ? m_Info.corTrendN : m_Info.corTrendP));
108.                ObjectSetInteger(GetInfoTerminal().ID, m_Mem.szBtnS, OBJPROP_XSIZE, w);
109.                ObjectSetInteger(GetInfoTerminal().ID, m_Mem.szBtnS, OBJPROP_YSIZE, h);
110.                ObjectSetInteger(GetInfoTerminal().ID, m_Mem.szBtnS, OBJPROP_XDISTANCE, GetInfoMouse().Position.X_Adjusted - w);
111.                ObjectSetInteger(GetInfoTerminal().ID, m_Mem.szBtnS, OBJPROP_YDISTANCE, GetInfoMouse().Position.Y_Adjusted - (v1 < 0 ? 1 : h));            
112.                ObjectMove(GetInfoTerminal().ID, m_Mem.szLineT, 1, GetInfoMouse().Position.dt, GetInfoMouse().Position.Price);
113.                ObjectSetInteger(GetInfoTerminal().ID, m_Mem.szLineT, OBJPROP_COLOR, (memPrice > GetInfoMouse().Position.Price ? m_Info.corTrendN : m_Info.corTrendP));
114.             }
115.             m_Info.Data.ButtonStatus = eKeyNull;
116.          }
117. //+------------------------------------------------------------------+
118. inline void DecodeAlls(int xi, int yi)
119.          {
120.             int w = 0;
121. 
122.             xi = (xi > 0 ? xi : 0);
123.             yi = (yi > 0 ? yi : 0);
124.             ChartXYToTimePrice(GetInfoTerminal().ID, m_Info.Data.Position.X_Graphics = (short)xi, m_Info.Data.Position.Y_Graphics = (short)yi, w, m_Info.Data.Position.dt, m_Info.Data.Position.Price);
125.             m_Info.Data.Position.dt = AdjustTime(m_Info.Data.Position.dt);
126.             m_Info.Data.Position.Price = AdjustPrice(m_Info.Data.Position.Price);
127.             ChartTimePriceToXY(GetInfoTerminal().ID, w, m_Info.Data.Position.dt, m_Info.Data.Position.Price, xi, yi);
128.             yi -= (int)ChartGetInteger(GetInfoTerminal().ID, CHART_WINDOW_YDISTANCE, GetInfoTerminal().SubWin);
129.             m_Info.Data.Position.X_Adjusted = (short) xi;
130.             m_Info.Data.Position.Y_Adjusted = (short) yi;
131.          }
132. //+------------------------------------------------------------------+
133.    public   :
134. //+------------------------------------------------------------------+
135.       C_Mouse(const long id, const string szShortName)
136.          :C_Terminal(id),
137.          m_OK(false)
138.          {
139.             m_Mem.szShortName = szShortName;
140.          }
141. //+------------------------------------------------------------------+
142.       C_Mouse(const long id, const string szShortName, color corH, color corP, color corN)
143.          :C_Terminal(id)
144.          {
145.             if (!(m_OK = IndicatorCheckPass(m_Mem.szShortName = szShortName))) SetUserError(C_Terminal::ERR_Unknown);
146.             if (_LastError != ERR_SUCCESS) return;
147.             m_Mem.CrossHair = (bool)ChartGetInteger(GetInfoTerminal().ID, CHART_CROSSHAIR_TOOL);
148.             ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_MOUSE_MOVE, true);
149.              ChartSetInteger(GetInfoTerminal().ID, CHART_CROSSHAIR_TOOL, false);
150.             ZeroMemory(m_Info);
151.             m_Info.corLineH  = corH;
152.             m_Info.corTrendP = corP;
153.             m_Info.corTrendN = corN;
154.             m_Info.Study = eStudyNull;
155.             if (m_Mem.IsFull = (corP != clrNONE) && (corH != clrNONE) && (corN != clrNONE))
156.                CreateObjectGraphics(m_Mem.szLineH = (def_MousePrefixName + (string)ObjectsTotal(0)), OBJ_HLINE, m_Info.corLineH);
157.             ChartRedraw(GetInfoTerminal().ID);
158.          }
159. //+------------------------------------------------------------------+
160.       ~C_Mouse()
161.          {
162.             if (!m_OK) return;
163.             ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, false);
164.             ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_MOUSE_MOVE, ChartWindowFind(GetInfoTerminal().ID, m_Mem.szShortName) != -1);
165.              ChartSetInteger(GetInfoTerminal().ID, CHART_CROSSHAIR_TOOL, m_Mem.CrossHair);
166.             ObjectsDeleteAll(GetInfoTerminal().ID, def_MousePrefixName);
167.          }
168. //+------------------------------------------------------------------+
169. inline bool CheckClick(const eBtnMouse value) 
170.          {
171.             return (GetInfoMouse().ButtonStatus & value) == value;
172.          }
173. //+------------------------------------------------------------------+
174. inline const st_Mouse GetInfoMouse(void)
175.          {
176.             if (!m_OK)
177.             {
178.                double Buff[];
179.                uCast_Double loc;
180.                int handle = ChartIndicatorGet(GetInfoTerminal().ID, 0, m_Mem.szShortName);
181. 
182.                ZeroMemory(m_Info.Data);
183.                if (CopyBuffer(handle, 0, 0, 1, Buff) == 1)
184.                {
185.                   loc.dValue = Buff[0];
186.                   m_Info.Data.ButtonStatus = loc._8b[0];
187.                   DecodeAlls((int)loc._16b[1], (int)loc._16b[2]);
188.                }
189.                IndicatorRelease(handle);
190.             }
191. 
192.             return m_Info.Data;
193.          }
194. //+------------------------------------------------------------------+
195. inline void SetBuffer(const int rates_total, double &Buff[])
196.          {
197.             uCast_Double info;
198.             
199.             info._8b[0] = (uchar)(m_Info.Study == C_Mouse::eStudyNull ? m_Info.Data.ButtonStatus : 0);
200.             info._16b[1] = (ushort) m_Info.Data.Position.X_Graphics;
201.             info._16b[2] = (ushort) m_Info.Data.Position.Y_Graphics;
202.             Buff[rates_total - 1] = info.dValue;
203.          }
204. //+------------------------------------------------------------------+
205.       void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
206.          {
207.             int w = 0;
208.             static double memPrice = 0;
209.       
210.             if (m_OK)      
211.             {
212.                C_Terminal::DispatchMessage(id, lparam, dparam, sparam);
213.                switch (id)
214.                {
215.                   case (CHARTEVENT_CUSTOM + evHideMouse):
216.                      if (m_Mem.IsFull)   ObjectSetInteger(GetInfoTerminal().ID, m_Mem.szLineH, OBJPROP_COLOR, clrNONE);
217.                      break;
218.                   case (CHARTEVENT_CUSTOM + evShowMouse):
219.                      if (m_Mem.IsFull) ObjectSetInteger(GetInfoTerminal().ID, m_Mem.szLineH, OBJPROP_COLOR, m_Info.corLineH);
220.                      break;
221.                   case CHARTEVENT_MOUSE_MOVE:
222.                      DecodeAlls((int)lparam, (int)dparam);
223.                      if (m_Mem.IsFull) ObjectMove(GetInfoTerminal().ID, m_Mem.szLineH, 0, 0, m_Info.Data.Position.Price);
224.                      if ((m_Info.Study != eStudyNull) && (m_Mem.IsFull)) ObjectMove(GetInfoTerminal().ID, m_Mem.szLineV, 0, m_Info.Data.Position.dt, 0);
225.                      m_Info.Data.ButtonStatus = (uchar) sparam;
226.                      if (CheckClick(eClickMiddle))
227.                         if ((!m_Mem.IsFull) || ((color)ObjectGetInteger(GetInfoTerminal().ID, m_Mem.szLineH, OBJPROP_COLOR) != clrNONE)) CreateStudy();
228.                      if (CheckClick(eClickLeft) && (m_Info.Study == eStudyCreate))
229.                      {
230.                         ChartSetInteger(GetInfoTerminal().ID, CHART_MOUSE_SCROLL, false);
231.                         if (m_Mem.IsFull)   ObjectMove(GetInfoTerminal().ID, m_Mem.szLineT, 0, m_Mem.dt = GetInfoMouse().Position.dt, memPrice = GetInfoMouse().Position.Price);
232.                         m_Info.Study = eStudyExecute;
233.                      }
234.                      if (m_Info.Study == eStudyExecute) ExecuteStudy(memPrice);
235.                      m_Info.Data.ExecStudy = m_Info.Study == eStudyExecute;
236.                      break;
237.                   case CHARTEVENT_OBJECT_DELETE:
238.                      if ((m_Mem.IsFull) && (sparam == m_Mem.szLineH))
239.                         CreateObjectGraphics(m_Mem.szLineH, OBJ_HLINE, m_Info.corLineH);
240.                      break;
241.                }
242.             }
243.          }
244. //+------------------------------------------------------------------+
245. };
246. //+------------------------------------------------------------------+
247. #undef macro_NameObjectStudy
248. //+------------------------------------------------------------------+

C_Mouse.mqhヘッダーファイルのソースコード

最初に注目すべきは、6行目と7行目にあります。以前とは異なり、少し異なる命名規則が実装されていることに気づくでしょう。この変更は、チャート上で既存のオブジェクト名と競合しないようにするために必要です。そのため、名前はウィンドウやチャート上に存在するオブジェクトの数によって決まることになり、それぞれの名前が一意に設定されるようになっています。

コードには若干の違いがありますが、特に注目すべき部分ではありません。ただし、64行目と67行目の間では新しい変数が宣言されており、これらの変数は作成するオブジェクトの名前を格納するために使われます。この仕組みを理解するために、83行目でオブジェクトに名前を付ける例を見てみましょう。ここでは、マクロを使用して変数に名前を生成して割り当てる方法が示されています。

コード全体には特別に強調すべき部分は少ないですが、変更は微細で、ニーズに対するサポートを向上させることを目的としているため、いくつかの部分について説明が必要です。完璧ではありませんが、目的を達成するためには十分です。118行目から始まるDecodeAllsという手順について触れておきましょう。

この手順は、マウスインジケーターモジュールがメインウィンドウ(インデックス0のウィンドウ)にある場合には非常にうまく機能します。しかし、マウスインジケーターを別のウィンドウに配置すると問題が発生し始めます。多くの問題は解決しましたが、この記事の最後にあるビデオでわかるように、まだいくつかの問題が残っています。

読者の皆さんが混乱する重要なポイントは、128行目にあります。この行はなぜ追加されたのでしょうか。また、なぜ以前は存在しなかったのでしょうか。これを理解するには、他のことを理解する必要があります。マウスインジケーターモジュールはもともとメインウィンドウにのみ表示されることを目的としていました。このウィンドウの初期Y位置は上部にあり、Yは常にゼロから始まります。しかし、追加のウィンドウを加えると、メインウィンドウのY位置は変更されませんが、追加ウィンドウのY位置は特定の値だけオフセットされます。しかし、Windowsのオペレーティングシステムにおいては、これは問題になりません。MetaTrader 5は、マウスの正確な位置を通知します。

MetaTrader 5は、マウスの位置をチャートウィンドウ内に収めるように調整します。そのため、ウィンドウ外の位置では、マウス座標は負の値または正の値となります。マウスポインタがウィンドウの作業領域より上にある場合は、負の値が発生します。作業領域の範囲が不明な場合は、図01を参照してください。図では、ビットマップを含む領域全体がワークスペースとして見なされます。

図01

図01:ワークスペース領域

境界線とタイトルバーは、ウィンドウのワークスペースの一部ではないことに注意してください。そのため、マウスポインタがタイトルバー領域に入ると、値は負に修正されますが、これはオペレーティングシステムではなくMetaTrader 5がおこなっています。ポインタがタイトルバー領域に入るとY値が負になる理由は、オペレーティングシステムがそのようにしたからではなく、MetaTrader 5がワークスペース領域に収まるように値を修正したからです。

メインウィンドウに、チャートが含まれないストリップなどの領域を追加する要素が加えられても、MetaTrader 5はこれを別のウィンドウとして認識しません。このように定義しても、MetaTrader 5はウィンドウ全体を単一のエンティティとして扱い、ワークスペース全体はメインウィンドウの境界内に収まります。幸いなことに、MetaTrader 5は領域がどこから始まるかはわかりますが、どこで終わるかに関する情報は提供されません。この終了位置を決定する方法は私たち次第ですが、開始位置を知るだけでも有益です。

領域の開始位置を特定するには、定数CHART_WINDOW_YDISTANCEとサブウィンドウ番号を渡してChartGetIntegerを呼び出します。ここで「サブウィンドウ」という用語を使っていますが、この表現は完全に正確ではありません。

128行目の呼び出しによって返された値は、変換された値から減算されます。変換された値はMetaTrader 5が報告するもので、ウィンドウ内に収まるように調整されています。128行目の修正がなければ、マウスポインタがMetaTrader 5によってサブウィンドウ領域の一部として解釈される位置に入ると、水平線の位置が誤って表示されてしまいます。しかし、この修正を実施することで、その問題は回避されます。少なくとも、通常発生するような形では起こりませんでした。

もうひとつ言及すべき重要なポイントは、インジケーターの短縮名を格納する145行目にあります。なぜこれをおこなうのでしょうか。その理由は164行目で明らかになります。マウスインジケーター名がわからなければ、MetaTrader 5にマウスイベントの送信を停止するように指示することができません。イベントを常に有効にしておくべきだと考えるかもしれませんが、それは必要ありません。マウスが動くたびにMetaTrader 5はマウスイベントをトリガーしますが、誰も使用していない場合、イベントキューに無駄なイベントが追加されるだけです。この問題を避けるためには、不要になったイベントはすぐに無効にする必要があります。しかし、マウスインジケーターの名前がわからない場合、どのインジケーターがイベントを必要としているかをどうやって確認できるのでしょうか。その方法はありません。だからこそ、作業を簡単にするためにインジケーター名を保存し、それを元にマウスイベントをオフにできるかどうかを確認します。

上記の変更に加えて、いくつかの単純な変更もありますが、詳細な説明は省略します。これらを見つけるためには、このコードをC_Mouse.mqhヘッダーファイルの以前のバージョンと比較するだけで十分です。これにより、コーディングや変更を行う方法について、問題なく学ぶことができるでしょう。

予想通り、C_Study.mqhファイルにもいくつかの変更が加えられました。ただし、C_Mouse.mqhヘッダーファイルの変更と同様、詳細な説明は省略します。代わりに、C_Study.mqhの完全なコードを提供し、その変更点を確認していただきます。

001. //+------------------------------------------------------------------+
002. #property copyright "Daniel Jose"
003. //+------------------------------------------------------------------+
004. #include "..\C_Mouse.mqh"
005. //+------------------------------------------------------------------+
006. #define def_ExpansionPrefix def_MousePrefixName + "Expansion_"
007. //+------------------------------------------------------------------+
008. class C_Study : public C_Mouse
009. {
010.    private   :
011. //+------------------------------------------------------------------+
012.       struct st00
013.       {
014.          eStatusMarket  Status;
015.          MqlRates       Rate;
016.          string         szInfo,
017.                         szBtn1,
018.                         szBtn2,
019.                         szBtn3;
020.          color          corP,
021.                         corN;
022.          int            HeightText;
023.          bool           bvT, bvD, bvP;
024.          datetime       TimeDevice;
025.       }m_Info;
026. //+------------------------------------------------------------------+
027.       const datetime GetBarTime(void)
028.          {
029.             datetime dt;
030.             int i0 = PeriodSeconds();
031.             
032.             if (m_Info.Status == eInReplay)
033.             {
034.                if ((dt = m_Info.TimeDevice) == ULONG_MAX) return ULONG_MAX;
035.             }else dt = TimeCurrent();
036.             if (m_Info.Rate.time <= dt)
037.                m_Info.Rate.time = (datetime)(((ulong) dt / i0) * i0) + i0;
038. 
039.             return m_Info.Rate.time - dt;
040.          }
041. //+------------------------------------------------------------------+
042.       void Draw(void)
043.          {
044.             double v1;
045.             
046.             if (m_Info.bvT)
047.             {
048.                ObjectSetInteger(GetInfoTerminal().ID, m_Info.szBtn1, OBJPROP_YDISTANCE, GetInfoMouse().Position.Y_Adjusted - 18);
049.                ObjectSetString(GetInfoTerminal().ID, m_Info.szBtn1, OBJPROP_TEXT, m_Info.szInfo);
050.             }
051.             if (m_Info.bvD)
052.             {
053.                v1 = NormalizeDouble((((GetInfoMouse().Position.Price - m_Info.Rate.close) / m_Info.Rate.close) * 100.0), 2);
054.                ObjectSetInteger(GetInfoTerminal().ID, m_Info.szBtn2, OBJPROP_YDISTANCE, GetInfoMouse().Position.Y_Adjusted - 1);
055.                ObjectSetInteger(GetInfoTerminal().ID, m_Info.szBtn2, OBJPROP_BGCOLOR, (v1 < 0 ? m_Info.corN : m_Info.corP));
056.                ObjectSetString(GetInfoTerminal().ID, m_Info.szBtn2, OBJPROP_TEXT, StringFormat("%.2f%%", MathAbs(v1)));
057.             }
058.             if (m_Info.bvP)
059.             {
060.                v1 = NormalizeDouble((((iClose(GetInfoTerminal().szSymbol, PERIOD_D1, 0) - m_Info.Rate.close) / m_Info.Rate.close) * 100.0), 2);
061.                ObjectSetInteger(GetInfoTerminal().ID, m_Info.szBtn3, OBJPROP_YDISTANCE, GetInfoMouse().Position.Y_Adjusted - 1);
062.                ObjectSetInteger(GetInfoTerminal().ID, m_Info.szBtn3, OBJPROP_BGCOLOR, (v1 < 0 ? m_Info.corN : m_Info.corP));
063.                ObjectSetString(GetInfoTerminal().ID, m_Info.szBtn3, OBJPROP_TEXT, StringFormat("%.2f%%", MathAbs(v1)));
064.             }
065.          }
066. //+------------------------------------------------------------------+
067. inline void CreateObjInfo(EnumEvents arg)
068.          {
069.             switch (arg)
070.             {
071.                case evShowBarTime:
072.                   C_Mouse::CreateObjToStudy(2, 110, m_Info.szBtn1 = (def_ExpansionPrefix + (string)ObjectsTotal(0)), clrPaleTurquoise);
073.                   m_Info.bvT = true;
074.                   break;
075.                case evShowDailyVar:
076.                   C_Mouse::CreateObjToStudy(2, 53, m_Info.szBtn2 = (def_ExpansionPrefix + (string)ObjectsTotal(0)));
077.                   m_Info.bvD = true;
078.                   break;
079.                case evShowPriceVar:
080.                   C_Mouse::CreateObjToStudy(58, 53, m_Info.szBtn3 = (def_ExpansionPrefix + (string)ObjectsTotal(0)));
081.                   m_Info.bvP = true;
082.                   break;
083.             }
084.          }
085. //+------------------------------------------------------------------+
086. inline void RemoveObjInfo(EnumEvents arg)
087.          {
088.             string sz;
089.             
090.             switch (arg)
091.             {
092.                case evHideBarTime:
093.                   sz = m_Info.szBtn1;
094.                   m_Info.bvT = false;
095.                   break;
096.                case evHideDailyVar:
097.                   sz = m_Info.szBtn2;
098.                   m_Info.bvD   = false;
099.                   break;
100.                case evHidePriceVar:
101.                   sz = m_Info.szBtn3;
102.                   m_Info.bvP = false;
103.                   break;
104.             }
105.             ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, false);
106.             ObjectDelete(GetInfoTerminal().ID, sz);
107.             ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, true);
108.          }
109. //+------------------------------------------------------------------+
110.    public   :
111. //+------------------------------------------------------------------+
112.       C_Study(long IdParam, string szShortName, color corH, color corP, color corN)
113.          :C_Mouse(IdParam, szShortName, corH, corP, corN)
114.          {
115.             if (_LastError != ERR_SUCCESS) return;
116.             ZeroMemory(m_Info);
117.             m_Info.Status = eCloseMarket;
118.             m_Info.Rate.close = iClose(GetInfoTerminal().szSymbol, PERIOD_D1, ((GetInfoTerminal().szSymbol == def_SymbolReplay) || (macroGetDate(TimeCurrent()) != macroGetDate(iTime(GetInfoTerminal().szSymbol, PERIOD_D1, 0))) ? 0 : 1));
119.             m_Info.corP = corP;
120.             m_Info.corN = corN;
121.             CreateObjInfo(evShowBarTime);
122.             CreateObjInfo(evShowDailyVar);
123.             CreateObjInfo(evShowPriceVar);
124.          }
125. //+------------------------------------------------------------------+
126.       void Update(const eStatusMarket arg)
127.          {
128.             datetime dt;
129.             
130.             switch (m_Info.Status = (m_Info.Status != arg ? arg : m_Info.Status))
131.             {
132.                case eCloseMarket   :
133.                   m_Info.szInfo = "Closed Market";
134.                   break;
135.                case eInReplay      :
136.                case eInTrading   :
137.                   if ((dt = GetBarTime()) < ULONG_MAX)
138.                   {
139.                      m_Info.szInfo = TimeToString(dt, TIME_SECONDS);
140.                      break;
141.                   }
142.                case eAuction      :
143.                   m_Info.szInfo = "Auction";
144.                   break;
145.                default            :
146.                   m_Info.szInfo = "ERROR";
147.             }
148.             Draw();
149.          }
150. //+------------------------------------------------------------------+
151. virtual void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
152.          {
153.             C_Mouse::DispatchMessage(id, lparam, dparam, sparam);
154.             switch (id)
155.             {
156.                case CHARTEVENT_CUSTOM + evHideBarTime:
157.                   RemoveObjInfo(evHideBarTime);
158.                   break;
159.                case CHARTEVENT_CUSTOM + evShowBarTime:
160.                   CreateObjInfo(evShowBarTime);
161.                   break;
162.                case CHARTEVENT_CUSTOM + evHideDailyVar:
163.                   RemoveObjInfo(evHideDailyVar);
164.                   break;
165.                case CHARTEVENT_CUSTOM + evShowDailyVar:
166.                   CreateObjInfo(evShowDailyVar);
167.                   break;
168.                case CHARTEVENT_CUSTOM + evHidePriceVar:
169.                   RemoveObjInfo(evHidePriceVar);
170.                   break;
171.                case CHARTEVENT_CUSTOM + evShowPriceVar:
172.                   CreateObjInfo(evShowPriceVar);
173.                   break;
174.                case (CHARTEVENT_CUSTOM + evSetServerTime):
175.                   m_Info.TimeDevice = (datetime)dparam;
176.                   break;
177.                case CHARTEVENT_MOUSE_MOVE:
178.                   Draw();
179.                   break;
180.             }
181.             ChartRedraw(GetInfoTerminal().ID);
182.          }
183. //+------------------------------------------------------------------+
184. };
185. //+------------------------------------------------------------------+
186. #undef def_ExpansionPrefix
187. #undef def_MousePrefixName
188. //+------------------------------------------------------------------+

C_Study.mqhクラスのソースコード

ただし、この記事では、次のコードを示したいと思います。これはインジケーターのソースコードです。

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.59"
07. #property icon "/Images/Market Replay/Icons/Indicators.ico"
08. #property link "https://www.mql5.com/pt/articles/12075"
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 color user02   = clrBlack;                       //Price Line
18. input color user03   = clrPaleGreen;                   //Positive Study
19. input color user04   = clrLightCoral;                  //Negative Study
20. //+------------------------------------------------------------------+
21. C_Study::eStatusMarket m_Status;
22. int m_posBuff = 0;
23. double m_Buff[];
24. //+------------------------------------------------------------------+
25. int OnInit()
26. {
27.    ResetLastError();
28.    Study = new C_Study(0, "Indicator Mouse Study", user02, user03, user04);
29.    if (_LastError != ERR_SUCCESS) return INIT_FAILED;
30.    if ((*Study).GetInfoTerminal().szSymbol != def_SymbolReplay)
31.    {
32.       MarketBookAdd((*Study).GetInfoTerminal().szSymbol);
33.       OnBookEvent((*Study).GetInfoTerminal().szSymbol);
34.       m_Status = C_Study::eCloseMarket;
35.    }else
36.       m_Status = C_Study::eInReplay;
37.    SetIndexBuffer(0, m_Buff, INDICATOR_DATA);
38.    ArrayInitialize(m_Buff, EMPTY_VALUE);
39.    
40.    return INIT_SUCCEEDED;
41. }
42. //+------------------------------------------------------------------+
43. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
44. {
45.    m_posBuff = rates_total;
46.    (*Study).Update(m_Status);   
47.    
48.    return rates_total;
49. }
50. //+------------------------------------------------------------------+
51. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
52. {
53.    (*Study).DispatchMessage(id, lparam, dparam, sparam);
54.    (*Study).SetBuffer(m_posBuff, m_Buff);
55.    
56.    ChartRedraw((*Study).GetInfoTerminal().ID);
57. }
58. //+------------------------------------------------------------------+
59. void OnBookEvent(const string &symbol)
60. {
61.    MqlBookInfo book[];
62.    C_Study::eStatusMarket loc = m_Status;
63.    
64.    if (symbol != (*Study).GetInfoTerminal().szSymbol) return;
65.    MarketBookGet((*Study).GetInfoTerminal().szSymbol, book);
66.    m_Status = (ArraySize(book) == 0 ? C_Study::eCloseMarket : C_Study::eInTrading);
67.    for (int c0 = 0; (c0 < ArraySize(book)) && (m_Status != C_Study::eAuction); c0++)
68.       if ((book[c0].type == BOOK_TYPE_BUY_MARKET) || (book[c0].type == BOOK_TYPE_SELL_MARKET)) m_Status = C_Study::eAuction;
69.    if (loc != m_Status) (*Study).Update(m_Status);
70. }
71. //+------------------------------------------------------------------+
72. void OnDeinit(const int reason)
73. {
74.    if (reason != REASON_INITFAILED)
75.    {
76.       if ((*Study).GetInfoTerminal().szSymbol != def_SymbolReplay)
77.          MarketBookRelease((*Study).GetInfoTerminal().szSymbol);
78.    }
79.    delete Study;
80. }
81. //+------------------------------------------------------------------+
82. 

マウスポインタのソースコード

このコードでは、最初から特定の入力が使用されなくなっていることがわかります。これは、このインジケーターへのアプローチ方法が変更されたことを意味しています。この変更の目的は、インジケーターを適切な場所に自由に配置できるようにすることです。これにより、当初の想定を超えて、さまざまな用途に対応できるようになりました。そのため、もうこれ以上具体的な場所を指定することはありません。その結果、以前はユーザーがチャートIDや資産ステータスを設定する必要があった入力は不要となりました。

しかし、36行目の小さな変更に注目してください。この変更により、このモジュールをリプレイやシミュレーターサービスで使用する際、使用されているアセットのチャートに追加すると、行36で自動的に調整がおこなわれます。つまり、ユーザーは以前必要だったチャートIDの指定などをおこなうオプションがなくなりました。注記として、チャートIDを提供する必要はなくなりましたが、この記事を書いている時点では、関数呼び出しに関して以前の記事で説明した内容は依然として適用されます。


結論

少しの作業と事前の知識を活用することで、MetaTrader 5の特定の機能がどのように動作するのかを実証することができました。記事を読んでいると、進展が少ないように感じるかもしれませんが、実際にはペースは遅いものの、着実に進歩しています。なぜなら、MetaTrader 5で実際に動作するかどうかが不明なことを考案し、テストを重ねてきたからです。

プ自称プログラマーの中には、MetaTrader 5では特定の操作ができない、つまりプラットフォームにこの機能やあの機能が欠けていると主張する人が多くいます。しかし、私はそうした人々がしばしば誤った情報に基づいていることに気づきました。

この記事の最後に、マウスインジケーターモジュールがどのように機能するかを示すビデオを紹介します。ビデオをよく観察し、いくつかの不具合に気づかれるかもしれませんが、それについては認識しており、重大な問題ではないと考えています。MetaTrader 5の動作をより深く理解した後、適切な修正を加える予定です。

最後に、読者の皆さんにもこの学習プロセスに参加していただきたいと考えています。今後、リプレイやシミュレーション用に設計されたシステムが、実際の市場やデモ口座でも使用できるようにどのように開発されるかを引き続きご説明していきます。


デモビデオ:新しいマウスインジケーターの動作

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

添付されたファイル |
Anexo.zip (420.65 KB)
EAのサンプル EAのサンプル
一般的なMACDを使ったEAを例として、MQL4開発の原則を紹介します。
カスタムインジケーター:ネット口座の部分的なエントリー、エグジット、リバーサル取引のプロット カスタムインジケーター:ネット口座の部分的なエントリー、エグジット、リバーサル取引のプロット
この記事では、MQL5でインジケーターを作成する非標準的な方法について説明します。トレンドやチャートパターンに注目するのではなく、部分的なエントリーやエグジットを含めた独自のポジション管理を目的とします。取引履歴やポジションに関連する動的マトリックスと、いくつかの取引機能を広範に活用し、これらの取引がおこなわれた場所をチャート上に表示します。
エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法 エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法
この記事では、MT4において複数のEAの衝突をさける方法を扱います。ターミナルの操作、MQL4の基本的な使い方がわかる人にとって、役に立つでしょう。
リプレイシステムの開発(第58回):サービスへの復帰 リプレイシステムの開発(第58回):サービスへの復帰
リプレイ/シミュレーターサービスの開発と改良を一時中断していましたが、再開することにしました。ターミナルグローバルのようなリソースの使用をやめたため、いくつかの部分を完全に再構築しなければなりません。ご心配なく。このプロセスを詳細に説明することで、誰もが私たちのサービスの進展についていけるようにします。