
リプレイシステムの開発(第58回):サービスへの復帰
はじめに
前回の「リプレイシステムの開発(第57回):テストサービスの内訳」稿では、リプレイ/シミュレーターシステムで使用するモジュール間の相互作用を示すために必要なソースコードについて詳しく説明しました。
このコードから、実際に実装するべきものが見えてきましたが、私たちのシステムにとって本当に役立つ重要なディテール、つまりテンプレートを使う機能がまだ欠けていました。もしコーディングやMetaTrader 5の設定においてテンプレートを使わない、またはその利点を十分に理解していなければ、これがそれほど重要ではないと思うかもしれません。
しかし、パターンを理解し、それを適用することで、私たちの作業量は大幅に軽減されます。テンプレートを使えば非常に簡単にできることでも、直接プログラムしようとすると非常に複雑になり、実装が難しくなる場合があります。将来的には、テンプレートだけを使って何かを実現する方法を紹介するかもしれませんが、今は他にもっと緊急な仕事があります。
正直なところ、コントロールモジュールとマウスモジュールの改良は必要ないと思っていました。しかし、次の記事で紹介するいくつかの詳細のために、両モジュールにはまだマイナーチェンジが必要です。この点については後述しますが、とりあえず今回は、前回の記事で得た知識を実現可能で機能的なものに変える方法を考えてみましょう。そのために、新しいトピックに移りましょう。
古いリプレイ/シミュレーターサービスの変更
最後にリプレイシミュレータのコードに修正や改良を加えてからしばらく経ちますが、リプレイシミュレータの実行ファイルのビルドに関わる一部のヘッダーファイルが変更されました。おそらく最も注目すべき変更点は、InterProcess.mqhヘッダーファイルが削除され、より広範な目的を持つDefines.mqhというファイルに置き換えられたことです。
この新しいヘッダー ファイルに対応するためにコントロールモジュールとマウスモジュールを既に調整しているため、同じ変更をリプレイ/シミュレーションサービスに適用する必要があります。その結果、図01に示すように、更新されたヘッダーファイル構造でリプレイ/シミュレーションサービスをコンパイルしようとすると、コンパイルエラーが発生します。
図01:レプリケーション/モデリングサービスのコンパイルを試みる
表示される可能性のあるさまざまなエラーのうち、まず強調表示された2つのエラーに対処する必要があります。これらを解決するには、C_Simulation.mqhヘッダーファイルを開き、以下のスニペットに示すようにコードを修正します。必要な変更は最小限であり、単に04行目を削除し、05行目に示されている調整に置き換えるだけです。この修正により、C_Simulation.mqhはここで実装している新しいフレームワークに適合するようになります。
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #include "..\..\Auxiliar\Interprocess.mqh" 05. #include "..\..\Defines.mqh" 06. //+------------------------------------------------------------------+ 07. #define def_MaxSizeArray 16777216 // 16 Mbytes of positions 08. //+------------------------------------------------------------------+ 09. class C_Simulation 10. { 11. private : 12. //+------------------------------------------------------------------+ 13. int m_NDigits; 14. bool m_IsPriceBID;
C_Simulation.mqhファイルのソースコードの一部
C_FilesBars.mqhファイルでもC_Simulation.mqhヘッダーファイルでおこなったのと同様のことをおこなう必要があります。それには、C_FilesBars.mqhヘッダーファイルを開き、以下のようにコードを変更します。
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #include "..\..\Auxiliar\Interprocess.mqh" 05. #include "..\..\Defines.mqh" 06. //+------------------------------------------------------------------+ 07. #define def_BarsDiary 1440 08. //+------------------------------------------------------------------+ 09. class C_FileBars 10. { 11. private : 12. int m_file;
C_FilesBars.mqhファイルのソースコードの断片
どちらのコード片でも、InterProcess.mqhヘッダーファイルを削除し、Defines.mqhに置き換えています。これら2つの修正により、ほとんどのコードは、リプレイ/シミュレーターサービスの期待される構造と一致することになります。しかし、問題があります。InterProcess.mqhとDefines.mqhの内容を比較すると、Defines.mqhはターミナルグローバル変数を参照していないことに気づくでしょう。にもかかわらず、リプレイ/シミュレーターシステムは依然としてこれらの変数を参照しています。
具体的には、これらの変数はC_Replay.mqhファイル内で使用されます。しかし、懸念はそれだけではありません。将来的には、コードの構成、安定性、柔軟性を向上させるために、さらにコードを再構築することになるかもしれません。しかし当面は、柔軟性と安定性のわずかな向上のためにシステム全体を大幅に変更するのではなく、既存の構造を適応させることに重点を置くつもりです。ただし、柔軟性と安定性はどちらも常に強化する価値があります。
物事を明確にするために、この説明をセクションに分けましょう。最初に取り上げる問題は、致命的ではないものの、オブジェクト指向プログラミングの基本原則のひとつである「カプセル化」に反する欠陥です。
コードのカプセル化を見直す
どのようなコードベースにおいても、最も深刻な問題のひとつは、セキュリティと保守性を確保するために、オブジェクト指向プログラミングの基本原則を守らないことです。長い間、リプレイ/シミュレーター機能に必要な特定のデータへの直接アクセスを容易にするために、コードの特定の部分を見落として誤用していたことがありました。
しかし、この時点から、この方法は使われなくなります。具体的には、C_ConfigServiceクラスに存在するカプセル化の違反について触れています。
C_ConfigServiceクラスのヘッダーファイル(C_ConfigService.mqh)を確認すると、いくつかの変数を含むprotected句があることがわかります。これらの変数は、C_ConfigServiceおよびその派生クラスであるC_Replayの中でのみ使用されるべきですが、このセクションにこれらの変数が存在することでカプセル化が解除されてしまいます。現在の形でこれらの変数をC_ConfigServiceの外部からアクセス可能にすることは適切ではありません。C_Replayクラスを見直すと、これらの変数を変更していることがわかります。これがまさにこのアプローチが問題になる原因です。C++では、クラスの変数をprivateにし、ベースクラス外からのアクセスや操作を制御する方法があります。しかし、このようなテクニックはしばしば過度に複雑でメンテナンスが難しいコードを生み出すことになります。また、将来的な改善が困難になることもあります。
MQL5はC++から派生した言語であるため、C++が許容する危険なプラクティスを避けることが推奨されています。そのため、適切なカプセル化を含め、オブジェクト指向プログラミングの3つの基本原則を厳守することが、より適切なアプローチです。
C_ConfigService.mqhヘッダーファイルを修正することで、システム内で適切なカプセル化を復元することができます。ただし、この変更にはコードベース全体の調整が必要になります。具体的には、C_Replay.mqhファイルにあるC_Replayクラスが大幅に変更されることになります。同時に、この機会を活かしてコード構造を改善し、リプレイ/シミュレーターサービスの入れ子構造を減らすことも予定しています。より小さな段階的な変更を実施することで、メンテナンスが簡素化され、各段階で何が起きているのかを管理しやすくなります。これは、今後のアップデートにおいて特に有益です。なぜなら、私たちは近い将来、複数の相互接続されたコンポーネントを含む、さらに複雑な機能を実装する必要があるからです。
これをより適切なものにするために、何が必要かを考えてみましょう。カプセル化を改善するには、C_ConfigService.mqhヘッダーファイルを開き、次のようにコードを修正します。残りのコードは変更せず、このフラグメントの変更によってカプセル化が適切に実行されるようにします。
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #include "Support\C_FileBars.mqh" 05. #include "Support\C_FileTicks.mqh" 06. #include "Support\C_Array.mqh" 07. //+------------------------------------------------------------------+ 08. class C_ConfigService : protected C_FileTicks 09. { 10. protected: 11. datetime m_dtPrevLoading; 12. int m_ReplayCount, 13. m_ModelLoading; 14. //+------------------------------------------------------------------+ 15. inline void FirstBarNULL(void) 16. { 17. MqlRates rate[1]; 18. int c0 = 0; 19. 20. for(; (m_Ticks.ModePlot == PRICE_EXCHANGE) && (m_Ticks.Info[c0].volume_real == 0); c0++); 21. rate[0].close = (m_Ticks.ModePlot == PRICE_EXCHANGE ? m_Ticks.Info[c0].last : m_Ticks.Info[c0].bid); 22. rate[0].open = rate[0].high = rate[0].low = rate[0].close; 23. rate[0].tick_volume = 0; 24. rate[0].real_volume = 0; 25. rate[0].time = macroRemoveSec(m_Ticks.Info[c0].time) - 86400; 26. CustomRatesUpdate(def_SymbolReplay, rate); 27. m_ReplayCount = 0; 28. } 29. //+------------------------------------------------------------------+ 30. private : 31. enum eWhatExec {eTickReplay, eBarToTick, eTickToBar, eBarPrev}; 32. enum eTranscriptionDefine {Transcription_INFO, Transcription_DEFINE}; 33. struct st001 34. { 35. C_Array *pTicksToReplay, *pBarsToTicks, *pTicksToBars, *pBarsToPrev; 36. int Line; 37. }m_GlPrivate; 38. string m_szPath; 39. bool m_AccountHedging; 40. datetime m_dtPrevLoading; 41. int m_ReplayCount, 42. m_ModelLoading; 43. //+------------------------------------------------------------------+ 44. inline void FirstBarNULL(void) 45. { 46. MqlRates rate[1]; 47. int c0 = 0; 48. 49. for(; (m_Ticks.ModePlot == PRICE_EXCHANGE) && (m_Ticks.Info[c0].volume_real == 0); c0++); 50. rate[0].close = (m_Ticks.ModePlot == PRICE_EXCHANGE ? m_Ticks.Info[c0].last : m_Ticks.Info[c0].bid); 51. rate[0].open = rate[0].high = rate[0].low = rate[0].close; 52. rate[0].tick_volume = 0; 53. rate[0].real_volume = 0; 54. rate[0].time = macroRemoveSec(m_Ticks.Info[c0].time) - 86400; 55. CustomRatesUpdate(def_SymbolReplay, rate); 56. m_ReplayCount = 0; 57. } 58. //+------------------------------------------------------------------+ 59. inline eTranscriptionDefine GetDefinition(const string &In, string &Out)
C_ConfigService.mqhファイルのソースコードの一部
なお、11~13行目の内容は、40行目と42行目に移動しています。つまり、C_ConfigServiceクラスの外部からはこれらの変数にアクセスできなくなります。これ以外にも、もう一つ変更が加えられました。この変更は無視することもできましたが、クラスの外では使用しないものもあるため、FirstBarNULLプロシージャをprivateにしました。つまり、15行目から28行目にあった内容は、44行目から57行目に移動しました。
実際のファイルにこのような変更を加えると、削除されたコードがクラスのコードの一部でなくなるため、行番号が異なるのは明らかです。しかし、わかりやすくするために、フラグメント全体をそのまま残すことにしました。こうすることで、どの部分が変更されたのかが明確になり、理解しやすくなると考えています。
素晴らしいですね。さて、これらの変更をおこなった後は、C_Replay.mqhファイルにあるコードを根本的に変更しなければなりません。しかし、物事を一つ一つ分けて考え、次のトピックでさらに詳しく見ていきましょう。
C_Replayクラスの実装を再開する
このセクションのタイトルは、私たちがすでに作成したものを再発明していることを暗示しており、少し残念に御壊れるかもしれませんが、そうではありません。C_Replayクラスの大部分を作り直す必要があるとはいえ、この連載を通して得た知識は依然として非常に貴重であることを強調しておきたいと思います。私たちがおこなっているのは、特定の事柄が以前の方法では実行できなくなったため、新しい構造と方法論に適応することです。
C_Replayクラスの完全な改訂コードを以下に示します。
001. //+------------------------------------------------------------------+ 002. #property copyright "Daniel Jose" 003. //+------------------------------------------------------------------+ 004. #include "C_ConfigService.mqh" 005. //+------------------------------------------------------------------+ 006. #define def_IndicatorControl "Indicators\\Market Replay.ex5" 007. #resource "\\" + def_IndicatorControl 008. //+------------------------------------------------------------------+ 009. #define def_CheckLoopService ((!_StopFlag) && (ChartSymbol(m_IdReplay) != "")) 010. //+------------------------------------------------------------------+ 011. #define def_ShortNameIndControl "Market Replay Control" 012. //+------------------------------------------------------------------+ 013. class C_Replay : public C_ConfigService 014. { 015. private : 016. long m_IdReplay; 017. struct st00 018. { 019. ushort Position; 020. short Mode; 021. }m_IndControl; 022. //+------------------------------------------------------------------+ 023. inline bool MsgError(string sz0) { Print(sz0); return false; } 024. //+------------------------------------------------------------------+ 025. inline void UpdateIndicatorControl(void) 026. { 027. uCast_Double info; 028. int handle; 029. double Buff[]; 030. 031. if ((handle = ChartIndicatorGet(m_IdReplay, 0, def_ShortNameIndControl)) == INVALID_HANDLE) return; 032. info.dValue = 0; 033. if (CopyBuffer(handle, 0, 0, 1, Buff) == 1) 034. info.dValue = Buff[0]; 035. IndicatorRelease(handle); 036. if ((short)(info._16b[0]) != SHORT_MIN) 037. m_IndControl.Mode = (short)info._16b[1]; 038. if (info._16b[0] != m_IndControl.Position) 039. { 040. if (((short)(info._16b[0]) != SHORT_MIN) && ((short)(info._16b[1]) == SHORT_MAX)) 041. m_IndControl.Position = info._16b[0]; 042. info._16b[0] = m_IndControl.Position; 043. info._16b[1] = (ushort)m_IndControl.Mode; 044. EventChartCustom(m_IdReplay, evCtrlReplayInit, 0, info.dValue, ""); 045. } 046. } 047. //+------------------------------------------------------------------+ 048. void SweepAndCloseChart(void) 049. { 050. long id; 051. 052. if ((id = ChartFirst()) > 0) do 053. { 054. if (ChartSymbol(id) == def_SymbolReplay) 055. ChartClose(id); 056. }while ((id = ChartNext(id)) > 0); 057. } 058. //+------------------------------------------------------------------+ 059. public : 060. //+------------------------------------------------------------------+ 061. C_Replay() 062. :C_ConfigService() 063. { 064. Print("************** Market Replay Service **************"); 065. srand(GetTickCount()); 066. SymbolSelect(def_SymbolReplay, false); 067. CustomSymbolDelete(def_SymbolReplay); 068. CustomSymbolCreate(def_SymbolReplay, StringFormat("Custom\\%s", def_SymbolReplay)); 069. CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE, 0); 070. CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_VALUE, 0); 071. CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_VOLUME_STEP, 0); 072. CustomSymbolSetString(def_SymbolReplay, SYMBOL_DESCRIPTION, "Symbol for replay / simulation"); 073. CustomSymbolSetInteger(def_SymbolReplay, SYMBOL_DIGITS, 8); 074. SymbolSelect(def_SymbolReplay, true); 075. } 076. //+------------------------------------------------------------------+ 077. bool OpenChartReplay(const ENUM_TIMEFRAMES arg1, const string szNameTemplate) 078. { 079. if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE) == 0) 080. return MsgError("Asset configuration is not complete, it remains to declare the size of the ticket."); 081. if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_VALUE) == 0) 082. return MsgError("Asset configuration is not complete, need to declare the ticket value."); 083. if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_VOLUME_STEP) == 0) 084. return MsgError("Asset configuration not complete, need to declare the minimum volume."); 085. SweepAndCloseChart(); 086. m_IdReplay = ChartOpen(def_SymbolReplay, arg1); 087. if (!ChartApplyTemplate(m_IdReplay, szNameTemplate + ".tpl")) 088. Print("Failed apply template: ", szNameTemplate, ".tpl Using template default.tpl"); 089. else 090. Print("Apply template: ", szNameTemplate, ".tpl"); 091. 092. return true; 093. } 094. //+------------------------------------------------------------------+ 095. bool InitBaseControl(const ushort wait = 1000) 096. { 097. int handle; 098. 099. Print("Waiting for Mouse Indicator..."); 100. Sleep(wait); 101. while ((def_CheckLoopService) && (ChartIndicatorGet(m_IdReplay, 0, "Indicator Mouse Study") == INVALID_HANDLE)) Sleep(200); 102. if (def_CheckLoopService) 103. { 104. Print("Waiting for Control Indicator..."); 105. if ((handle = iCustom(ChartSymbol(m_IdReplay), ChartPeriod(m_IdReplay), "::" + def_IndicatorControl, m_IdReplay)) == INVALID_HANDLE) return false; 106. ChartIndicatorAdd(m_IdReplay, 0, handle); 107. IndicatorRelease(handle); 108. m_IndControl.Position = 0; 109. m_IndControl.Mode = SHORT_MIN; 110. UpdateIndicatorControl(); 111. } 112. 113. return def_CheckLoopService; 114. } 115. //+------------------------------------------------------------------+ 116. bool LoopEventOnTime(void) 117. { 118. 119. while (def_CheckLoopService) 120. { 121. UpdateIndicatorControl(); 122. Sleep(250); 123. } 124. 125. return false; 126. } 127. //+------------------------------------------------------------------+ 128. ~C_Replay() 129. { 130. SweepAndCloseChart(); 131. SymbolSelect(def_SymbolReplay, false); 132. CustomSymbolDelete(def_SymbolReplay); 133. Print("Finished replay service..."); 134. } 135. //+------------------------------------------------------------------+
C_Replay.mqhファイルのソースコード
このコードでは、まだ欠けているコンポーネントがあるため、以前のようにリプレイ/シミュレーションを実行することはできませんが、その目的は、リプレイ/シミュレーションサービスが、前回の記事で取り上げられなかった要素を利用できるようにすることです。これらの要素の中には、リプレイとシミュレーションの両方に必要な小節だけでなく、以前のように以前のバーをロードする機能もあります。しかし、この記事では、これらのリプレイやシミュレーションバーを完全に活用することはまだできません。その代わり、これらの情報はロードされ、システムがカスタムアセットチャートに適切に表示できるようになったときに利用できるようになります。
上記のコードには、さらに説明が必要な点がいくつかあります。その構成要素の多くは、MQL5の確かな経験者であっても、すぐには理解できないかもしれません。しかし、ここで提供する説明は、このコードがなぜこのような特殊な構造になっているのかを純粋に理解したい方々に向けたものです。
コードの冒頭、5行目から11行目にかけて、特定のパラメーターを定義し、コンパイルされたインジケーターファイルをサービス実行ファイルにインクルードします。その理由は、リプレイ/シミュレーションに関する本連載の過去記事で散々論じてきた通りです。そのため、コントロール指示ファイルを手動で転送する必要はないことを思い出してもらうために、この点を強調しただけです。
そして13行目で、C_ConfigServiceクラスからのpublic継承を確立します。これは、作業負荷がC_Replayクラスだけに集中するのではなく、C_ReplayとC_ConfigServiceの間で分散されるようにするためです。これは、データと変数を適切にカプセル化するために必要な修正について説明した前節での変更の重要性を補強するものです。
C_Replayクラスのprivateセクションは15行目から始まり、publicセクションが始まる58行目まで続きます。まず、privateセクションがどのように機能するかを見てみましょう。16行目から21行目の間で宣言された、小さなグローバル変数のセットも含まれています。特に21行目に注目してほしいところです。ここでは変数が構造体として宣言されています。
23行目では、エラーメッセージを端末に表示してfalseを返すことだけを目的とした小さな関数を定義しています。しかし、なぜここでfalseを返すのでしょうか。この戻り値がなければ、エラーメッセージをターミナルに表示するたびに、追加のコード行が必要になります。わかりやすくするために、79行目を見てみましょう。ここではある条件をチェックしています。エラーが検出された場合、通常、エラーメッセージを表示する行とエラー表示を返す行の2つが必要になります。これは不必要な冗長性を生むことになります。しかし、23行目で宣言された関数を使うことで、メッセージを表示し、失敗の表示を返すことができます。これは80行目に見られ、実装を簡素化しています。私たちは、コーディングの手間を減らすような方法で物事を組み合わせています。
おそらくコードの中で最も重要な部分は、25行目と46行目の間にあります。このコードは私たちのために非常に重要な仕事をしてくれます。コントロールインジケーターからのデータの管理と調整です。この部分を完全に理解するには、関連する全てのコンポーネントがどのように連携しているのかを把握することが不可欠です。もし不明点があれば、過去の記事でコントロールインジケーターが外部コンポーネントとどのように通信しているかを説明した内容を再度確認してください。
31行目では、コントロールインジケーターへのアクセスを試みるためにハンドルを取得します。もし取得に失敗した場合でも、これは致命的なエラーとはなりません。関数はそのまま終了し、残りの処理をスキップします。逆に、ハンドルの取得に成功した場合、32行目でテスト用の値がリセットされます。これを確実におこなうことが非常に重要です。33行目では、インジケーターのバッファが読み取り可能かどうかをチェックします。もし読み取り可能であれば、34行目でテスト変数と調整用の変数に適切な値を代入します。この部分は今後の改善を予定していますが、基本的なロジックは変わることはありません。
その後、ハンドルは35行目で解放され、次にデータをテストして調整する処理が進みます。36行目では、コントロールインジケーターに有効なデータが含まれているかを確認し、有効であればシステムが現在のモード(停止中、もしくはアクティブなプレイモード)を保存します。保存は37行目でおこなわれます。これは変更がおこなわれる前に行う必要があります。そうしないと、取得されたデータが途中で変更され、情報の整合性が損なわれる可能性があります。重要なのは、常に最新のコントロールインジケーターのデータを提供できるようにすることです。以前はグローバルターミナル変数を使用していましたが、このアプローチを改善しています。
38行目に注目してください。インジケーターバッファの内容を全地球測位システムと比較します。不一致が発見された場合、40行目では、コントロールインジケーターが初期化されているかどうか、システムがプレイモードであるかどうかを確認する二次チェックを実行します。両方の条件が満たされた場合、41行目はバッファ値を保存します。一時停止モード中は、データを自動的に更新したくないので、これは非常に重要です。必要に応じて、ユーザーが手動でコントロールインジケーターを調整できるようにしたいと思います。
最後に、42行目と43行目で、コントロールインジケーターに渡す情報を組み立てます。これは、44行目でトリガーされるカスタムイベントによって送信されます。いったんトリガーされると、MetaTrader 5が引き継ぎ、サービスを並行して実行しながらタスクを実行します。
このプロシージャに含まれるコードは、何が起こっているのかがはっきりするまで、慎重に分析する必要があります。前回の記事でのアプローチと比べると、このバージョンは基本的に同じ機能を果たしているにもかかわらず、より複雑になっています。MetaTrader 5によってコントロールインジケーターがチャート上に配置されると、このコードによって初期化されます。それ以降は、その状態を監視します。ユーザーが時間枠を変更した場合、サービスはインジケータの最後の既知の状態を保持し、チャートに戻ったときに以前の設定で再初期化されるようにします。
では、再利用性を念頭に置いて作られたコードを見てみましょう。これは48行目にあります。このプロシージャでは、複製する銘柄を含むすべてのMetaTrader 5チャートウィンドウを閉じるだけです。ご覧の通り、複雑なことは何もありません。しかし、少なくとも2回はこれをおこなう必要があるので、コードの重複を避けるためにこのプロシージャを作成することにしました。
さて、ここからはC_Replayクラスのpublicプロシージャに移ります。基本的に、少なくともクラスのコンストラクターとデストラクタに関しては、以前とあまり変わらないコードであることがわかるでしょう。したがって、C_Replayクラスの機能については、以前の記事ですでに適切に説明されているため、これ以上のコメントはおこないません。ただし、ここには説明に値する3つの関数があります。コードに現れる順に見ていきましょう。
最初の関数はOpenChartReplayと呼ばれ、77行目から93行目で終わります。この関数は、再起動システムが収集した情報の完全性をチェックします。これは、リプレイやシミュレーターを実際におこなうために必要なことです。しかし、この関数の中に非常に複雑なものがあり、後で説明するInitBaseControl関数と合わせて、テンプレートを使用することができます。
テンプレートを使うという問題は、私たちにとって非常に重要です。正しく使用し、適切な方法で立ち上げることが必要です。しかし、これを実行するのは、私を含め多くの人が最初に考えたほど簡単ではありません。87行目では、86行目で開いたテンプレートをチャートに追加しようとしています。使用するテンプレートは、関数の引数の1つとして指定します。ユーザーが指定したテンプレートであれ、MetaTrader 5の標準テンプレートであれ、いずれにせよ、テンプレートはチャートに配置されます。しかし、ここでほとんど言及されない詳細があります。ChartApplyTemplate関数は、テンプレートを即座に適用しないということです。この関数は非同期であり、呼び出されてから数ミリ秒以内に実行されます。これは私たちにとって問題です。
問題の規模を理解するために、C_Replayクラスから少し離れて、以下のサービスコードを見てみましょう。
01. //+------------------------------------------------------------------+ 02. #property service 03. #property icon "/Images/Market Replay/Icons/Replay - Device.ico" 04. #property copyright "Daniel Jose" 05. #property version "1.58" 06. #property description "Replay-Simulator service for MetaTrade 5 platform." 07. #property description "This is dependent on the Market Replay indicator." 08. #property description "For more details on this version see the article." 09. #property link "https://www.mql5.com/pt/articles/" 10. //+------------------------------------------------------------------+ 11. #include <Market Replay\Service Graphics\C_Replay.mqh> 12. //+------------------------------------------------------------------+ 13. input string user00 = "Mini Dolar.txt"; //Replay Configuration File. 14. input ENUM_TIMEFRAMES user01 = PERIOD_M5; //Initial Graphic Time. 15. input string user02 = "Default"; //Template File Name 16. //+------------------------------------------------------------------+ 17. C_Replay *pReplay; 18. //+------------------------------------------------------------------+ 19. void OnStart() 20. { 21. pReplay = new C_Replay(); 22. 23. UsingReplay(); 24. 25. delete pReplay; 26. } 27. //+------------------------------------------------------------------+ 28. void UsingReplay(void) 29. { 30. if (!(*pReplay).SetSymbolReplay(user00)) return; 31. if (!(*pReplay).OpenChartReplay(user01, user02)) return; 32. if (!(*pReplay).InitBaseControl()) return; 33. Print("Permission granted. Replay service can now be used..."); 34. while ((*pReplay).LoopEventOnTime()); 35. } 36. //+------------------------------------------------------------------+
リプレイシミュレーションサービスのソースコード
30行目から34行目にあるように、特定の順序でタスクを実行していることに注目してください。21行目のコンストラクタで初期化した後、30行目に進み、ロード処理がすべて正しいことを確認します。そして31行目でチャートを開き、32行目でサービスをコントロールするために必要な要素をロードします。すべてが順調に進んだら、33行目でターミナルにメッセージを表示し、34行目で実行ループに入ります。
一見すると、31行目でチャートを開いてから32行目でコントロールを追加するまでの間に、何も変わったことは起きていないように見えます。しかし、C_Replayクラスでロードされたテンプレートを使用するため、予期せぬ問題が発生する可能性があります。この潜在的な問題をよりよく理解するために、クラスを見直して、テンプレートを使うことの本当の複雑さを調べてみましょう。
C_Replayクラスの87行目にあるように、テンプレートの適用をMetaTrader 5に指示した後、コードは理想的な実行速度よりもはるかに速く実行されます。その結果、99行目では、サービスがマウスインジケーターを待っていることをユーザーに知らせています。マウスインジケーターがテンプレートに存在する場合は自動的にロードされ、存在しない場合はユーザーが手動で追加する必要があります。
これは、テンプレートを適用する関数が非同期で実行されるために起こる問題です。潜在的な問題を軽減するため、100行目を使用し、チャートを安定させ、テンプレートアプリケーション機能を適切に実行できるよう、サービスを短時間一時停止します。この待機の後、101行目でマウスインジケーターがあるかどうかを確認します。このループは、マウスインジケーターがチャート上に表示されるか、ユーザーがチャートを閉じるまで続きます。
マウスインジケーターが検出されるか、チャートが閉じられると、コードは続行されます。すべてが予想通りであれば、105行目でチャートにコントロールインジケーターを追加してみます。これは見事に機能しますが、重要な詳細がある:コントロールインジケーターがすでにテンプレートの一部である場合、そのインジケーターは受け入れられません。これは後で紹介する改造のひとつで、コントロールインジケーターがテンプレートに表示されないようにするものです。マウスのインジケーターにも若干の変更が必要ですが、それについては後ほどお話しします。100行目がないと、チャートは開いた直後に閉じてしまいます。これはまさに私たちが防止しようとしていることです。
結論
これで終わりではないと思われるかもしれませんが、なぜテンプレートを適用した直後にチャートが閉じてしまうのかについて、さらに詳しく説明する必要があります。これは非常に複雑で、なぜ100行目のコードだけでこの問題が防げるのかを理解するには、他の要素についても触れる必要があります。そのため、インジケーターモジュールのテンプレートとその必要な修正に関する詳細な議論は次回に回すことにします。この変更によって、リプレイ/シミュレーションサービスが期待通りに動作することが保証されます。
ご覧の通り、このシステムは前回の記事で紹介したテストサービスとは異なるアプローチを取っています。最後に、このシステムを実行した結果を示すビデオをお見せしたいところですが、現時点ではビデオが準備できていないため、この記事には添付しません。
デモビデオ
MetaQuotes Ltdによりポルトガル語から翻訳されました。
元の記事: https://www.mql5.com/pt/articles/12039





- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索