English Русский 中文 Español Deutsch Português
preview
リプレイシステムの開発(第57回):テストサービスについて

リプレイシステムの開発(第57回):テストサービスについて

MetaTrader 5 | 25 2月 2025, 09:18
164 0
Daniel Jose
Daniel Jose

はじめに

前回の「リプレイシステムの開発(第56回):モジュールの適応」稿では、コントロールインジケーターモジュールと、特に重要なマウスインジケーターモジュールにいくつかの変更を加えました。

この記事にはすでに多くの情報が含まれていたため、新しいデータを追加するのは控えました。なぜなら、これ以上情報を加えると、物事の仕組みを明確に説明するどころか、かえって読者の皆さんを混乱させる可能性が高いからです。

前回の記事の添付ファイルには、マウスインジケーターとサービスが含まれています。サービスを実行すると、カスタム銘柄が作成され、マウスインジケーターとコントロールインジケーターを含むパネルが追加されます。これらのモジュールはカスタム銘柄のチャート上に配置されており、サービス自体の処理はおこなわないものの、ユーザーと両モジュールの間で何らかの相互作用が発生する様子が確認できます。

特に MQL5を初めて使用する場合、この仕組みがどのように動作するのか全く分からないかもしれません。また、これら2つのインジケーターのコードを見ても、インジケーター内、より正確にはコントロールモジュール内のデータ損失を防ぐ処理が見当たらないことがあります。

そこで、本記事では サービスが実際にどのように機能するのか を詳しく解説します。この説明は非常に重要です。なぜなら、サービスの仕組みを正しく理解することは、リプレイ/シミュレーターシステム全体の動作を理解する上で不可欠だからです。一般的に、複雑なコードをいきなり理解しようとするよりも、まずは少ないコンポーネントで構成されたコードを作成し、それを説明する方がはるかに簡単です。

そのため、次に紹介するコードは実際に使用するものではありませんが、その詳細を理解することが非常に重要です。このシンプルなコードをしっかりと把握すれば、コントロールモジュール、マウスモジュール、そしてサービスがどのように相互作用するのかという全体像をより深く理解できるようになります。

それでは、前回の記事で実装し紹介したサービスのソースコードを見ていきましょう。最後に掲載されているビデオの内容も理解できるようにしていきましょう。


サービスコードを分析してみる

サービスのソースコード全体は次のとおりです。

01. //+------------------------------------------------------------------+
02. #property service
03. #property copyright "Daniel Jose"
04. #property description "Data synchronization demo service."
05. #property link "https://www.mql5.com/ja/articles/12000"
06. #property version   "1.00"
07. //+------------------------------------------------------------------+
08. #include <Market Replay\Defines.mqh>
09. //+------------------------------------------------------------------+
10. #define def_IndicatorControl   "Indicators\\Market Replay.ex5"
11. #resource "\\" + def_IndicatorControl
12. //+------------------------------------------------------------------+
13. #define def_Loop ((!_StopFlag) && (ChartSymbol(id) != ""))
14. //+------------------------------------------------------------------+
15. void OnStart()
16. {
17.    uCast_Double info;
18.    long id;
19.    int handle;
20.    short iPos, iMode;
21.    double Buff[];
22.    MqlRates Rate[1];
23.    
24.    SymbolSelect(def_SymbolReplay, false);
25.    CustomSymbolDelete(def_SymbolReplay);
26.    CustomSymbolCreate(def_SymbolReplay, StringFormat("Custom\\%s", def_SymbolReplay));
27.    CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE, 0.5);
28.    CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_VALUE, 5);
29.    Rate[0].close = 110;
30.    Rate[0].open = 100;
31.    Rate[0].high = 120;
32.    Rate[0].low = 90;
33.    Rate[0].tick_volume = 5;
34.    Rate[0].time = D'06.01.2023 09:00';
35.    CustomRatesUpdate(def_SymbolReplay, Rate, 1);
36.    SymbolSelect(def_SymbolReplay, true);
37.    id = ChartOpen(def_SymbolReplay, PERIOD_M30);   
38.    if ((handle = iCustom(ChartSymbol(id), ChartPeriod(id), "\\Indicators\\Mouse Study.ex5", id)) != INVALID_HANDLE)
39.       ChartIndicatorAdd(id, 0, handle);
40.    IndicatorRelease(handle);      
41.    if ((handle = iCustom(ChartSymbol(id), ChartPeriod(id), "::" + def_IndicatorControl, id)) != INVALID_HANDLE)
42.       ChartIndicatorAdd(id, 0, handle);
43.    IndicatorRelease(handle);   
44.    Print("Service maintaining sync state. Version Demo...");
45.    iPos = 0;
46.    iMode = SHORT_MIN;
47.    while (def_Loop)
48.    {
49.       while (def_Loop && ((handle = ChartIndicatorGet(id, 0, "Market Replay Control")) == INVALID_HANDLE)) Sleep(50);
50.       info.dValue = 0;
51.       if (CopyBuffer(handle, 0, 0, 1, Buff) == 1) info.dValue = Buff[0];
52.       IndicatorRelease(handle);
53.       if ((short)(info._16b[0]) == SHORT_MIN)
54.       {
55.          info._16b[0] = (ushort)iPos;
56.          info._16b[1] = (ushort)iMode;
57.          EventChartCustom(id, evCtrlReplayInit, 0, info.dValue, "");
58.       }else if (info._16b[1] != 0)
59.       {
60.          iPos = (short)info._16b[0];
61.          iMode = (short)info._16b[1];
62.       }
63.       Sleep(250);
64.    }
65.    ChartClose(id);
66.    SymbolSelect(def_SymbolReplay, false);
67.    CustomSymbolDelete(def_SymbolReplay);
68.    Print("Finished service...");   
69. }
70. //+------------------------------------------------------------------+

テストサービスのソースコード

MQLの経験があまりない人や、特にMetaTrader 5のサービスをプログラミングする方法にまだあまり詳しくない人でも、何が起こっているのかを本当に理解できるように、2行目からコードを詳しく見てみましょう。

MQL5コードの2行目に宣言されたプロパティに遭遇した場合、それがサービスであることを理解する必要があります。このプロパティがない場合、コードはスクリプトとして扱う必要があります。サービスとスクリプトの主な違いは、まず第一に、コード内にこのプロパティが存在するかどうかです。しかし、実行の観点から見ると、主な違いは、スクリプトは常にチャートにリンクされるのに対し、サービスはどのチャートにも依存しないという点です。

残りの3行目から6行目までは、MQL5プログラミングについて最低限の知識がある人にはお馴染みなので、それらの説明は省略できます。

8行目には、このコードにヘッダファイルを含めるためのincludeディレクティブを追加しました。この場合、ヘッダファイルはMarketReplayフォルダ内のIncludeフォルダにDefines.mqhという名前で配置されています。IncludeフォルダはメインMQL5ディレクトリのルートディレクトリにあり、コンパイル中にMetaEditorによって参照されます。

ここで、10行目と11行目に特に注意する必要があります。これらの行により、インジケーター、つまり制御モジュールが、コンパイルされたサービスコードの内部リソースになることが保証されます。これは何を意味するのでしょうか。つまり、すでにコンパイルされたコードを移行する場合、制御モジュールコードはすでにサービスリソースとして埋め込まれているため、そのコードを移行する必要はありません。このため、前回の記事の添付ファイルには実行可能ファイルが2つしか含まれていませんでしたが、サービスが開始された時点では実際には3つのファイルが実行されていました。

しかし、コントロールモジュールの場合と同様に、インジケーター、つまりマウスモジュールもサービスリソースとして含めなかったのはなぜでしょうか。理由は簡単です。ユーザーがマウスモジュールを使用できるようにし、アクセスしやすくするためです。このモジュールがサービス実行可能ファイルに埋め込まれている場合、ユーザーがマウスモジュールにアクセスして、サービスによって作成されたチャートとは異なるチャートに配置することは困難になります。

このため、特定の実行可能ファイルの内部リソースにする要素、特にその理由を決定する必要があることがよくあります。

次に13行目を見てください。この行では、実行するテストの一部を簡素化、または標準化するための定義を宣言します。複雑なプログラムでは、同じタイプのテストを異なる場所で実行することはよくあります。このようなテストを標準化するための定義を作成すると、コードがシンプルになり、保守が容易になるだけでなく、常に同じタイプのテストが実行されるようになります。多くの場合、一部のテストポイントを変更することを忘れ、何らかの理由でコードが特定のテストでは正常に実行されるものの、他のポイントでは失敗する可能性があるため、これはほとんどのプログラマーにとって非常に望ましいことです。これは通常、多くの頭痛の種を引き起こします。

よし。15行目では、実際にコードの実行可能部分に入ります。OnStartはMQL5コード内のスクリプトとサービスの両方で同じエントリポイントであることに注意してください。ただし、2行目の宣言から、サービスを扱っていることがわかります。MetaTrader 5は、コードが実行されるたびに1つのOnStartイベント呼び出しのみを生成するため、必要な期間コードが実行されるようにするには、何らかの操作が必要になります。これは特定のケース、つまり47行目でのみ発生します。しかし、まずはもう少しやるべきことがあります。

つまり、私たちにとって役立つ何かができるように、サービスを初期化して適応させる必要があります。必要な手順の中には、MetaTrader 5で当社のサービスによって課される変数と条件の宣言と初期化があります。これらはすべて、サービスが開始された後に実行されます。これを念頭に置いて、17行目から22行目では、使用する変数を宣言し、必要なルールを適切に初期化することに進みます。これから議論される内容の多くは奇妙に思えるかもしれません。

これらは一見単純なもので、本来は別の種類のアプリケーションの一部である可能性もありますが、すべてを1つのコードに集中させたいため、このように作業する必要があります。

したがって、24行目では、参照している銘柄を気配値表示ウィンドウから削除する必要があることをMetaTrader 5に伝えます。その直後の25行目で、銘柄のリストからそれを削除します。このリストには、アクセスできるすべての銘柄が含まれています。しかし、私たちが本当に興味を持っているのは26行目です。この行では、MetaTrader 5にカスタム銘柄を作成し、作成場所を指定することを伝えます。これに気を付けないと、このアクション中にマーケット銘柄を上書きし、後で実際の商品にアクセスしようとしたときに、実際にはカスタムの商品にアクセスすることになる可能性があるためです。しかし、通常、私たちはこのような事態を防ぐために何らかの予防策を講じています。

行26と27は必須です。これらがないと、マウスモジュールはチャート上でマウスを移動したときに価格ラインを正しい位置に正しく設定できません。

ここで、29行目と34行目の間で、チャートに表示される最初のバーとなるバーを定義します。理由はよくわかりませんが、このバーは常に高値と安値が見えないように配置されています。しかし、ここでの目標は、単にチャートにバーを表示し、モジュールが範囲エラーを生成しないようにすることなので、バー全体が表示されているかどうかは実際には気にしません。

この時点まで、チャート上にはチャート自体さえもまったく何も表示されていません。35行目では、バーを記述する前の行で定義された値を、作成する資産の最初のバーとして配置するようにMetaTrader 5に指示します。続く36行目では、プラットフォームに対して、その資産を気配値表示ウィンドウに追加するよう指示します。もしコードがここで終了した場合、ユーザーは手動でカスタム銘柄を開き、チャート上に配置したバーを表示することができます。しかし、さらに自動化を進めたいので、37行目が登場します。

37行目の実行時に、MetaTrader 5は指定された銘柄と時間枠でチャートを開きます。本連載の前回の記事で、モジュールにChart IDを通知する理由を説明しました。ここで重要なのは、現在チャートが開いており、どのテンプレートを使用するかを指定していないため、デフォルトのテンプレートで開かれるという点です。この考えは、次の行を理解するために必要です。

次に、38行目でハンドルを生成し、マウスモジュールをチャートに配置しようとします。特に、指定された実行ファイルの場所と名前に注意してください。何らかの理由でハンドルの作成が失敗すると、39行目は実行されません。通常、原因は実行ファイルが指定された場所に存在しないことです。この点については後で詳しく説明します。しかし、ハンドルが正常に作成されると、39行目でチャートにモジュールが追加されます。これにより、インジケーターが利用可能になります。ただし、必ずしも可視化されるわけではなく、チャート上で実行中のインジケーターの一覧には表示されるようになります。

40行目では、ハンドルは不要になるため、MetaTrader 5に対して割り当てられたメモリをシステムに返却するよう指示します。ただし、モジュールはすでにチャート上にあるため、ターミナルに明示的に指示しない限り、MetaTrader 5 はモジュールをチャートから削除しません。

この同じスクリプトを削除しても効果は同じです。つまり、マウス モジュールがチャート上に配置されます。これは、チャート テンプレートにモジュールが含まれている場合に発生します。これをおこなうには、任意のチャートを開き、マウスインジケーターを追加して、このテンプレートをDefault.tplとして保存します。または、より明確にするために、マウスインジケーターがすでに含まれている既定のテンプレートを作成できるようになりました。これにより、38行目から40行目にコマンドを表示する必要がなくなり、マウスインジケーターを最も便利な場所に配置できるようになります。

制御モジュールの場合は状況が少し異なります。これは、制御モジュールがサービス実行可能ファイルに統合されているためです。これが起こるという単純な事実により、サービスがモジュールをチャート上に配置することが容易になります。これは41行目と42行目でおこなわれます。41行目では、モジュールにアクセスするためのハンドルを生成し、42行目ではそれをチャートに追加します。42行目を使用しない場合、モジュールはチャート上に配置されず、メモリにロードされるだけで、MetaTrader 5は必要なチャート上でモジュールを実行しないことに注意してください。

43行目では、40行目と同様に、ハンドルは不要になったため削除します。

この時点まで、サービスはすぐに終了するプログラムのように動作します。しかしその前に、44行目を使用してメッセージボックスにメッセージを出力し、実行がどの程度進んでいるかを確認します。次に、45行目と46行目の間で、実際に使用する最後の変数を初期化します。これらの変数は、制御モジュールの起動に使用される値で初期化されることに注意してください。チャート上で起動されたばかりですが、まだ初期化されていないため、チャートには表示されません。

最後に、47行目でループに入ります。このループは、13行目に定義された条件のいずれかが満たされると終了し、条件がfalseになり、ループが終了します。この瞬間から、私たちは無作為に物事をおこなうことはもうありません。この時点から、サービスはアクションの実行を停止し、すでに実行されているものを管理する責任を負います。この概念を念頭に置き、この区別をどのようにおこなうかを知ることが重要です。そうしないと、ループ内で実行すべきでない操作を実行しようとする可能性があり、この場合もすべてが非常に不安定になり、問題が発生する可能性があります。

次に、49行目に新しいループが表示されます。49行目からのこのループは、適切に計画されていない場合は危険なタイプのループになります。その理由は、13行目の定義を使用しないと、このループに無期限に陥る可能性があるからです。これは、コントロールモジュールがチャート上に存在しない可能性があり、サービスはインジケーターまたはコントロールモジュールにアクセスする前に、MetaTrader 5がハンドラー値を伝えるのを待機するためです。

ただし、チャートコントロールモジュールを削除すると、MetaTrader 5はチャートを閉じます。これにより、13行目の定義はfalseとなり、49行目と47行目のループは終了します。

そのため、より単純なシステムを例に、その仕組みを説明します。より複雑なシステムでは、そのような微妙な点に気付いて理解するのははるかに困難になります。したがって、何かをテストしたいときはいつでも、後で設計するシステムと同じ動作ロジックに従う簡単なプログラムを使用してテストしてください。

したがって、MetaTrader 5が有効なハンドルを返すと仮定すると、50行目で、51行目で制御インジケーターバッファーの読み取りが成功した場合に定義する値をゼロにします。

何らかの理由でバッファの読み取りに失敗した場合、変数はゼロになります。しかし、読み取りが成功すると、制御インジケーターバッファーにデータが格納されます。

データを受信したら、ハンドラーを削除できます。これは52行目でおこなわれます。その理由は、次のステップでは何が起こるか分からないため、ループが繰り返されるときにfalseを取得したくないからです。これにより、システム全体のパフォーマンスが低下するように思われるかもしれませんが、無効または不必要な可能性のあるデータを分析するよりも、パフォーマンスが少し低下する方がよいでしょう。

ここで、次の点に注意してください。インジケーター、または制御モジュールはまだ初期化されていません。ただし、そのバッファには、インジケーター操作の最初の段階でそこに配置した値が含まれています。これを理解するには、このシリーズの以前の記事を参照してください。したがって、バッファを読み取るときにnull値が返されるとは思わないでください。これは発生しません。

これには2つの条件テストがあります。1つは、コントロールモジュールを初期化して、どのコントロールを表示するかを認識できるようにすることです。2番目のテストでは、制御モジュールで何が起こっているかに関するすべての情報をサービスに保存できます。これは、モジュールの最後の動作状態を再度報告する必要があるときに、モジュールに渡す値がわかるようにするためにおこなわれます。

したがって、53行目では、モジュールがチャートに追加されたかどうかを確認します。これは2つの時点で発生する可能性があります。1つ目は、47行目から開始されたループの最初の実行がまだ続いているときです。この場合、制御モジュールに報告される値は、行45と46から取得されます。2番目のポイントは、何らかの理由でチャートの時間枠が変更されたため、MetaTrader 5がチャート上の制御モジュールをリセットして再配置する必要がある場合です。この場合、MetaTrader 5がコントロールインジケーターをチャートに戻す前にコントロールインジケーターにあった最後の値が使用されます。

いずれにせよ、最後に57行目を実行し、MetaTrader 5がサービスによって監視されているチャートにカスタムイベントを生成するようにします。この方法では、制御インジケーターは新しい値または古い値を取得し、何らかの理由で何も魔法のように失われなかったという印象を与えます。これが発生するのは、インジケーターの制御モジュールで、インジケーターが外部ソースから更新された値を必要とし、受け取る必要があることを示す特定の値をバッファーに配置したためです。この場合、MetaTrader 5プラットフォームで動作するサービスです。

ここで、インジケータバッファから取得したコンテンツでない場合は、58行目で値がゼロでないかどうかを確認する新しいチェックが実行されます。これにより、コントロールインジケーターが再生モードになっているか、一時停止モードになっているかを把握できます。デモサービスでは、どちらの状況も、サービスがインジケーターバッファーの内容をキャプチャし、チャートの時間枠の変更によりMetaTrader 5がインジケーターを削除してチャートに再度配置することを決定した場合に使用される値を保存する必要があることを示しています。

実行は非常に速くおこなわれ、超高速更新は必要ないため、63行目を使用して、重要な処理をおこなわない休止時間をサービスに与えます。この行は、47行目で開始されたループが終了したことも示しています。

さらに、ユーザーがサービスを閉じるか、何らかの理由でチャートが閉じられると、47行目で開始されたループを終了し、最初に65行目を実行します。この行では、ユーザーがサービスを終了してもチャートは開いたままになるため、チャートを閉じます。これを回避するには、MetaTrader 5に、使用中のチャートを閉じることができるように指示します。

チャートを閉じた後、作成したカスタム銘柄を気配値表示ウィンドウから削除してみます。これは66行目でおこなわれ、この銘柄を含む他のチャートが開いていない場合は、MetaTrader 5に強制的に気配値表示から削除させることができます。ただし、削除した後でも、作成された場所、つまり26行目には引き続き表示されます。

銘柄が実際に気配値表示から削除されている場合は、行67を使用して、システムで使用可能な銘柄のリストから銘柄を削除します。そして最後に、68行目では、すべてがうまくいって、すべての手順が期待どおりに実行された場合、サービスが終了して実行されなくなったことを通知するメッセージがターミナルメッセージウィンドウに出力されます。


結論

このような状況は、長期的には重要でない、あるいは役に立たないように見えるかもしれませんが、実際には後で本当に必要になる知識や情報を提供しています。ソリューションやアプリケーションを開発する際には、未知のシナリオや不利なシナリオに直面することがよくあります。したがって、最初に考えていたよりもはるかに複雑なものを設計、プログラム、または開発する必要がある場合には、理解しやすく学習しやすい小さなプログラムを作成し、それでも長期的に必要な作業を一部実行できるようにすることが重要です。

すべてを正しくおこなえば、作成した小さなプログラムの一部を、実際の大規模で複雑なプロジェクトに活用することができます。このため、私はこの記事を執筆しました。時には非常に複雑で、さまざまなレベルの知識を必要とするプログラムが、空から突然生み出されるわけではないということを理解してもらいたいからです。

プログラムは常に段階的に、機能的な部分ごとに作成され、その後、アイデアの一部がより大きなソリューションに組み込まれます。そして、これこそが私たちがリプレイ/シミュレーターシステムでおこなうことです。ここで説明されたサービスの仕組みによって、主要なシステムを実装するために必要な要素がすべて整います。

この記事でおこなった適切な分析と調査がなければ、最小限の労力でモジュールをリプレイ/シミュレーターシステムに適切に組み込むことはできなかったでしょう。コードには多くの調整や変更が必要でしたが、それらはすべてC_Replayクラスに焦点を当てたものです。しかし、C_Replayクラスをどこで、どのように、またはどの方法で適応させるべきかを理解していなければ、行き詰まってしまい、ここで示した実装が非常に困難になっていたでしょう。

システムが実際にどのように動作するかがわかったので、あとはC_Replayクラスにいくつかの小さな変更を加えて、47行目から64行目のループをシステムのコードに組み込むだけです。ただし、ひとつ小さなポイントがあります。ここで示されているものとは異なり、リプレイ/シミュレーターシステムでは、すでにグラフに表示された内容に基づいて計算を実行します。この点については次の記事で詳しく説明します。この記事で示された内容をじっくりと学び、システムがどのように機能しているのかを理解することで、今後紹介される内容をより深く理解できるようになるでしょう。前回の記事のビデオで紹介したモジュールを使用して、リプレイ/シミュレーターサービスを実装していきます。

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

添付されたファイル |
Anexo.zip (420.65 KB)
リプレイシステムの開発(第58回):サービスへの復帰 リプレイシステムの開発(第58回):サービスへの復帰
リプレイ/シミュレーターサービスの開発と改良を一時中断していましたが、再開することにしました。ターミナルグローバルのようなリソースの使用をやめたため、いくつかの部分を完全に再構築しなければなりません。ご心配なく。このプロセスを詳細に説明することで、誰もが私たちのサービスの進展についていけるようにします。
PythonとMQL5による多銘柄分析(第2回):ポートフォリオ最適化のための主成分分析 PythonとMQL5による多銘柄分析(第2回):ポートフォリオ最適化のための主成分分析
取引口座のリスク管理は、すべてのトレーダーにとっての課題です。MetaTrader 5で、さまざまな銘柄に対して高リスク、中リスク、低リスクモードを動的に学習する取引アプリケーションを開発するにはどうすればよいでしょうか。PCA(主成分分析)を使用することで、ポートフォリオの分散をより効果的に管理できるようになります。MetaTrader 5から取得した市場データを基に、これら3つのリスクモードを学習するアプリケーションの作成方法を説明します。
カスタムインジケーター:ネット口座の部分的なエントリー、エグジット、リバーサル取引のプロット カスタムインジケーター:ネット口座の部分的なエントリー、エグジット、リバーサル取引のプロット
この記事では、MQL5でインジケーターを作成する非標準的な方法について説明します。トレンドやチャートパターンに注目するのではなく、部分的なエントリーやエグジットを含めた独自のポジション管理を目的とします。取引履歴やポジションに関連する動的マトリックスと、いくつかの取引機能を広範に活用し、これらの取引がおこなわれた場所をチャート上に表示します。
PythonとMQL5を使用した特徴量エンジニアリング(第1回):長期AIモデルの移動平均の予測 PythonとMQL5を使用した特徴量エンジニアリング(第1回):長期AIモデルの移動平均の予測
移動平均は、AIモデルが予測するのに最適な指標です。しかし、データを慎重に変換することで、さらなる精度向上が可能です。本記事では、現在の手法よりもさらに先の未来を、高い精度を維持しながら予測できるAIモデルの構築方法を解説します。移動平均がこれほど有用な指標であることには驚かされます。