English Русский 中文 Español Deutsch Português
preview
リプレイシステムの開発—市場シミュレーション(第7回):最初の改善(II)

リプレイシステムの開発—市場シミュレーション(第7回):最初の改善(II)

MetaTrader 5 | 16 11月 2023, 12:49
228 0
Daniel Jose
Daniel Jose

はじめに

前回の「リプレイシステムの開発 — 市場シミュレーション(第6回):最初の改善点(I) では、いくつかの点を修正し、リプレイシステムにテストを追加しました。これらのテストは、可能な限り高い安定性を確保することを目的としています。同時に、システムの構成ファイルの作成と使用を開始しました。

直感的で安定したリプレイシステムを作るために最善の努力を尽くしたにもかかわらず、私たちはまだいくつかの未解決の問題に直面しています。解決策には簡単なものもあれば、複雑なものもあります。

前回の記事の最後に掲載したビデオをご覧になった方は、まだ改善が必要なシステムの欠点にお気づきでしょう。より集中的な使用を開始する前に、まだ改善修正すべき点がたくさんあることを理解していただくために、これらの欠点を目に見える形で残すことにしました。だからといって、システムを練習に使えないというわけではありません。

ただし、システムやプラットフォームが不安定になるようなニュアンスを排除せずに新しい機能を追加することは避けたいと思います。市場が開いている間にリプレイを使って練習し、実際の取引が現れるのを待つか、プラットフォームの自動EAを使いたい方もいらっしゃるとおもいます。

しかし、リプレイシステムが十分に安定していない場合、実際の取引が発生する可能性があるのであれば、市場が開いている間、リプレイシステムを稼動させておくことは現実的ではありません。警告するのは、リプレイシステムが障害を引き起こしたり、他のプラットフォーム機能の正常な動作を妨げたりする可能性があるためです。

そのため、マーケットリプレイの安定性と利便性を向上させるための努力を続けるとともに、システムをさらに改善するために、いくつかの追加リソースをコードに導入する予定です。


コントロール指標をチャート上に確実に残す

最初の改善は、コントロール指標です。現在、リプレイが始まるとテンプレートが読み込まれます。このテンプレートファイルには、リプレイサービスの制御に使用する指標を含める必要があります。この指標がなければ、リプレイサービスでの操作は不可能です。したがって、この指標はサービスにとって極めて重要です。

しかし、ダウンロード後、現在に至るまで、この指標がチャートに表示され続けることを保証することはできませんでした。これからは状況が変わり、それがチャートに残るようにします。何らかの理由で指標が削除された場合は、リプレイサービスを直ちに停止しなければなりません。

しかし、どうすればいいのでしょうか。指標をチャートに残すにはどうすればいいでしょうか。いくつかの方法がありますが、私が考える最もシンプルでエレガントな方法は、MQL5言語を通して確認するためにMetaTrader 5プラットフォーム自体を使用することです。

原則として、指標には特別なイベントはありませんが、私たちにとって特に便利なイベントがあります。DeInitイベントです。 

このイベントは、何かが発生し、指標を閉じる必要がある場合に発生し、OnInitイベントによって直ちにトリガーされます。したがって、OnDeInit関数を呼び出すときに、指標が削除されたことをリプレイサービスに伝えることができるでしょうか。できますが、ひとつ重要な点があります。OnDeInitイベントが呼び出されるのは、指標が削除されたときやチャートが閉じられたときだけではありません。

チャートの期間が変更されたり、指標のパラメータが変更された場合にもトリガーされます。よって、事態はまたややこしくなりそうですが、OnDeInitイベントのドキュメントを見れば、初期化解除の理由コードを使用できることがわかるでしょう。

これらのコードを見ると、コントロール指標がチャートから取り除かれたことを示すのに非常に役立つコードが2つあることに気づきます。では、次のコードを作ってみましょう。

void OnDeinit(const int reason)
{
        switch (reason)
        {
                case REASON_REMOVE:
                case REASON_CHARTCLOSE:
                        GlobalVariableDel(def_GlobalVariableReplay);
                        break;
        }
}


ここでは、指標がチャートから削除されたことによってDeInitイベントがトリガーされたかどうかを確認します(それに応じてOnDeInit関数もトリガーされます)。この場合、指標がチャート上に存在しなくなったことをリプレイサービスに通知し、サービスを直ちに停止する必要があります。

そのためには、コントロール指標とリプレイサービスをつなぐグローバル変数をターミナルから削除する必要があります。この変数を削除すると、リプレイサービスはアクティビティが終了したことを理解し、終了します。

サービスコードを見ると、それが閉じられるとリプレイ銘柄チャートも閉じられることに気づくでしょう。正確には、これはサービスが以下のコードを実行したときに起こります。

void CloseReplay(void)
{
        ArrayFree(m_Ticks.Info);
        ChartClose(m_IdReplay);
        SymbolSelect(def_SymbolReplay, false);
        CustomSymbolDelete(def_SymbolReplay);
        GlobalVariableDel(def_GlobalVariableReplay);
}

コントロール指標を削除すると、リプレイサービスが強制的に閉じるため、チャートは指標と一緒に閉じます。ただし、サービスはリプレイ銘柄チャートを閉じる時間がないかもしれません。この場合、たとえ銘柄が気配値表示ウィンドウに残っていて、リプレイサービスが何度試みても削除できないとしても、このチャートが閉じていることを確認する必要があります。

そのために、コントロール指標のOnDeInit関数にもう1行追加しましょう。

void OnDeinit(const int reason)
{
        switch (reason)
        {
                case REASON_REMOVE:
                case REASON_CHARTCLOSE:
                        GlobalVariableDel(def_GlobalVariableReplay);
                        ChartClose(ChartID());
                        break;
        }
}


コントロール指標が何らかの理由でチャートから取り除かれ、リプレイサービスがチャートを閉じることができないと、指標自身がチャートを閉じようとします。これは少し直感に反するように思えるかもしれないが、私はリプレイサービスのように、チャートがプラットフォームを解放し、障害やエラーが発生した場合に不都合が生じないようにしたいのです。

この実装では、コントロール指標がチャートから取り除かれた場合、サービスが停止され、チャートが閉じられることが少なくとも保証されます。ただし、指標に関連して別の問題があります。


コントロール指標が削除されないようにする

これはかなり深刻な問題です。指標はチャート上に残るが、その構成要素が単に削除されるか破壊されるだけで、指標を正しく使用できなくなる可能性があるからです。

幸いなことに、この状況は簡単に解決できますが、この瞬間は、将来問題の種にならないよう、特別な注意が必要です。指標の破壊や、その要素やオブジェクトの除去を防ぐことで、多くの問題を引き起こす制御不能なモンスターを作り出してしまうかもしれません。この問題を解決するために、チャート上のオブジェクト破壊イベントをインターセプトして処理します。これが実際にどのようにおこなわれるか見てみましょう。

まず、C_Controlクラスに次のコードを追加してみます。

void Init(const bool state = false)
{
        if (m_szBtnPlay != NULL) return;
        m_id = ChartID();
        ChartSetInteger(m_id, CHART_EVENT_MOUSE_MOVE, true);
        ChartSetInteger(m_id, CHART_EVENT_OBJECT_DELETE, true);
        CreateBtnPlayPause(m_id, state);
        GlobalVariableTemp(def_GlobalVariableReplay);
        if (!state) CreteCtrlSlider();
        ChartRedraw();
}


このコード行を追加することで、チャートオブジェクトがスクリーンから取り除かれたときに、MetaTrader 5にイベントを送ってもらうようにします。この条件を満たすだけでは、オブジェクトが削除されないことは保証されませんが、少なくともMetaTrader 5プラットフォームが削除されたことを通知することは保証されます。

C_Controlクラスが削除されたときにオブジェクトが削除されるようにするには、オブジェクト削除イベントを送信しないタイミングをMetaTrader 5に伝える必要があります。この種の関数が使われるポイントのひとつを以下に示します。

~C_Controls()
{
        m_id = (m_id > 0? m_id : ChartID());
        ChartSetInteger(m_id, CHART_EVENT_OBJECT_DELETE, false);
        ObjectsDeleteAll(m_id, def_PrefixObjectName);
}

こうすることで、MetaTrader 5に、チャートからオブジェクトが削除されたときにイベントを送ってほしくないことを伝え、必要なオブジェクトを問題なく削除できるようになります。

ただし、これはそう単純ではなく、ここに潜在的な問題があります。下のコードを見てみましょう。

void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
{
        u_Interprocess Info;
        static int six =-1, sps;
        int x, y, px1, px2;
                
        switch (id)
        {
                case CHARTEVENT_OBJECT_CLICK:
                        if (sparam == m_szBtnPlay)
                        {
                                Info.s_Infos.isPlay =(bool) ObjectGetInteger(m_id, m_szBtnPlay, OBJPROP_STATE);
                                if (!Info.s_Infos.isPlay) CreteCtrlSlider(); else
                                {
                                        ObjectsDeleteAll(m_id, def_PrefixObjectName + "Slider");
                                        m_Slider.szBtnPin = NULL;
                                }
                                Info.s_Infos.iPosShift = m_Slider.posPinSlider;
                                GlobalVariableSet(def_GlobalVariableReplay, Info.Value);
                                ChartRedraw();
                        }else if (sparam == m_Slider.szBtnLeft) PositionPinSlider(m_Slider.posPinSlider - 1);
                        else if (sparam == m_Slider.szBtnRight) PositionPinSlider(m_Slider.posPinSlider + 1);                                                   
                break;

// ... The rest of the code....

この行は、オブジェクト除去イベントのトリガーとなるポジションコントロールバーを除去しなければなりません。

単純にイベントをオフにして、コントロールパネルを外してオンに戻せばいいと思われるかもしれません。それもそうですが、コードの量が増えるにつれて、こうしたオンとオフの動作は、一見したところよりもずっと一般的になることを覚えておいてほしいのです。さらにもうひとつ、正しく表現するためには、オブジェクトを一定の順序で並べる必要があります。

したがって、削除イベントのオンオフを切り替えるだけでは、そのイベントが正しく処理される保証はありません。オブジェクトの表示が常に同じであり、ユーザーが位置決めシステムの違いに気付かないように、オブジェクトの正しい順序を維持する、よりエレガントで堅牢なソリューションを作成する必要があります。

最も簡単な解決策は、deleteイベントをオフにして、同じチェーンのオブジェクトを削除し、deleteイベントをオンに戻す関数を作ることです。これは、コントロールバーでこのタスクを実行する以下のコードを使って簡単に実装できます。

inline void RemoveCtrlSlider(void)
{                       
        ChartSetInteger(m_id, CHART_EVENT_OBJECT_DELETE, false);
        ObjectsDeleteAll(m_id, def_NameObjectsSlider);
        ChartSetInteger(m_id, CHART_EVENT_OBJECT_DELETE, true);
}


これで、コントロールパネルのみを削除する必要があるたびに、この関数を呼び出し、望ましい結果を得ることができます。

これは些細なことに思えるかもしれませんが、現状ではこの手続きは一度だけでなく、以下に示すように同じ関数内で二度使われています。

void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
        {
                u_Interprocess Info;
                static int six =-1, sps;
                int x, y, px1, px2;
                        
                switch (id)
                {
                        case CHARTEVENT_OBJECT_DELETE:
                                if(StringSubstr(sparam, 0, StringLen(def_PrefixObjectName)) == def_PrefixObjectName)
                                {
                                        if(StringSubstr(sparam, 0, StringLen(def_NameObjectsSlider)) == def_NameObjectsSlider)
                                        {
                                                RemoveCtrlSlider();
                                                CreteCtrlSlider();
                                        }else
                                        {
                                                Info.Value = GlobalVariableGet(def_GlobalVariableReplay);
                                                CreateBtnPlayPause(Info.s_Infos.isPlay);
                                        }
                                        ChartRedraw();
                                }
                                break;
                        case CHARTEVENT_OBJECT_CLICK:
                                if (sparam == m_szBtnPlay)
                                {
                                        Info.s_Infos.isPlay =(bool) ObjectGetInteger(m_id, m_szBtnPlay, OBJPROP_STATE);
                                        if (!Info.s_Infos.isPlay) CreteCtrlSlider(); else
                                        {
                                                RemoveCtrlSlider();
                                                m_Slider.szBtnPin = NULL;
                                        }
                                        Info.s_Infos.iPosShift = m_Slider.posPinSlider;
                                        GlobalVariableSet(def_GlobalVariableReplay, Info.Value);
                                        ChartRedraw();
                                }else if (sparam == m_Slider.szBtnLeft) PositionPinSlider(m_Slider.posPinSlider - 1);
                                else if (sparam == m_Slider.szBtnRight) PositionPinSlider(m_Slider.posPinSlider + 1);                                                   
                                break;
                        case CHARTEVENT_MOUSE_MOVE:
                                x =(int)lparam;
                                y =(int)dparam;
                                px1 = m_Slider.posPinSlider + def_MinPosXPin - 14;
                                px2 = m_Slider.posPinSlider + def_MinPosXPin + 14;
                                if ((((uint)sparam & 0x01) == 1) && (m_Slider.szBtnPin != NULL))
                                {
                                        if ((y >= (m_Slider.posY - 14)) && (y <= (m_Slider.posY + 14)) && (x >= px1) && (x <= px2) && (six ==-1))
                                        {
                                                6 = x;
                                                sps = m_Slider.posPinSlider;
                                                ChartSetInteger(m_id, CHART_MOUSE_SCROLL, false);
                                        }
                                        if (six > 0) PositionPinSlider(sps + x - six);
                                }else if (6 > 0)
                                {
                                        6 =-1;
                                        ChartSetInteger(m_id, CHART_MOUSE_SCROLL, true);
                                }
                                break;
                }
        }


オブジェクトの削除イベントを処理する部分を詳しく見てみましょう。MetaTrader 5プラットフォームに、チャートからオブジェクトが削除されたときにイベントを受信したいと伝えると、削除されたオブジェクトごとにdeleteイベントが生成されます。そして、このイベントを捕捉し、どのオブジェクトが削除されたかを確認することができます。 

ひとつ重要なのは、どのオブジェクトが削除されるかではなく、どのオブジェクトが実際に削除されたかがわかるということです。この場合は、コントロール指標が使用するものであるかどうかを確認します。使用するものであれば、コントロールパネル上のオブジェクトの一つなのか、それともコントロールボタンなのかを再確認します。それがコントロールパネルの一部であった場合、パネルは完全に削除され、すぐに再び作成されます。 

この作成機能自体がすべての仕事をこなしてくれるので、何も知らせる必要はありません。さて、コントロールボタンに関しては、少し状況が異なります。この場合、ボタンの作成を要求する前に、端末のグローバル変数を読み込んでボタンの現在の状態を調べる必要があります。

最後に、すべてのオブジェクトをチャート上に強制的に配置し、ユーザーがオブジェクトが取り除かれたことに気づかないようにします。

これをおこなうのは、すべてのものが所定の位置にあることを確認するためです。さて、リプレイサービスの動作にとって重要な他のものを見てみましょう。


リプレイチャートは1つだけ

チャートを自動で開くシステムを使っていると、同じ銘柄のチャートを開き始めて、しばらくすると何を扱っているのかわからなくなることがよくあります。

これを回避するために、市場をリプレイするという同じ目的でリプレイ システムがチャートを次々と開き続ける場合の問題を解決する小さなテストを実装しました。この関数の存在で、グローバルターミナルの変数に含まれる値に関しても一定の安定性が保証されます。

同じアイデア(この場合はマーケットリプレイ)を反映した複数のチャートがある場合、そのうちの1つではコントロール指標が作成した指定値が表示され、もう1つではまったく異なる値が表示されることがあります。この問題はまだ完全に解決されたわけではありませんが、同じ銘柄を同時に参照する複数のチャートがなくなるという単純な事実は、多くの利点をもたらすでしょう。

以下のコードは、指定された商品に対して1つのチャートしか開かないようにする方法を示しています。

long ViewReplay(ENUM_TIMEFRAMES arg1)
{
        if ((m_IdReplay = ChartFirst()) > 0) do
        {
                if(ChartSymbol(m_IdReplay) == def_SymbolReplay) ChartClose(m_IdReplay);
        }while ((m_IdReplay = ChartNext(m_IdReplay)) > 0);
        m_IdReplay = ChartOpen(def_SymbolReplay, arg1);
        ChartApplyTemplate(m_IdReplay, "Market Replay.tpl");
        ChartRedraw(m_IdReplay);
        return m_IdReplay;
}


ここでは、MetaTrader 5プラットフォームのターミナルにチャートが開かれているかどうかを確認します。もし開かれていれば、それを出発点として、どの銘柄が開いているかを確認します。その銘柄がマーケットリプレイに使われるものであれば、このチャートを閉じます。

問題は、リプレイ銘柄チャートが開いている場合、2つの選択肢があることです。1 つ目はチャートを閉じることであり、それがまさに私たちがおこなうことです。2つ目の選択肢はループを終了することですが、この場合、同じアセットの複数のチャートが開いている可能性があります。したがって、開いているチャートはすべて閉じたいのです。最後のチャートが確認されるまでこれを繰り返します。そのため、開いているマーケットをリプレイする予定はありません。

次に、マーケットリプレイシステムを含むチャートを開き、コントロール指標を使用できるようにテンプレートを適用し、チャートを強制的に表示させ、開いているチャートのインデックスを返す必要があります。

しかし、システムがすでに読み込まれた後で、リプレイ銘柄のための新しいチャートを開くことを妨げるものは何もありません。リプレイの全期間中に1つのチャートのみが開かれたままになるように、サービスに追加のテストを加えることができますが、同じ銘柄の複数のチャートを同時に使いたいトレーダーがいることも分かっています。各チャートはそれぞれの時間間隔を使用します。

そのため、この追加テストはおこなわず、別のことをおこないます。本サービスによって開設されたチャート以外のチャート上では、コントロール指標の存在と操作を許可しません。さて、別のチャートに置き換えてみることで、元のチャートの指標を終了させることができますが、チャートは閉じ、リプレイサービスは停止し、変更ができなくなります。

次のトピックでは、コントロール指標が元のチャート以外のチャートで開かないようにする方法について説明します。


1セッションにつき1つのコントロール指標のみ

この部分は非常に興味深く、場合によっては役に立つでしょう。指標が1つのMetaTrader 5作業セッションで1つのチャートのみに属することを保証する方法を見てみましょう。

その方法を理解するために、次のコードを見てみます。

int OnInit()
{
        u_Interprocess Info;
        
        IndicatorSetString(INDICATOR_SHORTNAME, "Market Replay");
        if(GlobalVariableCheck(def_GlobalVariableReplay)) Info.Value = GlobalVariableGet(def_GlobalVariableReplay); else Info.Value = 0;
        Control.Init(Info.s_Infos.isPlay);
        
        return INIT_SUCCEEDED;
}


このコードは、グローバルターミナル変数の存在を確認します。そのような変数が存在する場合は、後で使用するためにそれを取り込みます。存在しなければ初期化します。

OnInit関数は、チャート上や指標のパラメータを更新するときなど、何かが起こるたびに呼び出されます。この場合、指標はパラメータを含まず、パラメータを受け取りません。したがって、チャートの時間間隔が変わるたびに発生するチャートイベント、つまり5分から4分になればOnInitが呼び出されるだけです。この場合、グローバルターミナル変数があれば、単純に指標の初期化をブロックすることで問題が発生します。チャートが閉じられる、つまりサービスが停止されるからです。難しいですね。

私たちが使う解決策は非常にシンプルで、同時に非常にエレガントなものです。グローバルターミナル変数を使って、コントロール指標がチャート上に既に存在するかどうかを知ります。存在する場合、現在のMetaTrader 5セッションで開いているチャートに存在する限り、他のチャートに配置することはできません。

これを実装するには、プロセス間の通信に使われるコードを編集する必要があります。

#property copyright "Daniel Jose"
//+------------------------------------------------------------------+
#define def_GlobalVariableReplay "Replay Infos".
//+------------------------------------------------------------------+
#define def_MaxPosSlider 400
//+------------------------------------------------------------------+
union u_Interprocess
{
        double Value;
        struct st_0
        {
                bool isPlay; // Specifies in which mode we are - Play or Pause ...
                bool IsUsing; // Specifies if the indicator is running or not ...
                int iPosShift; // Value from 0 to 400 ...
        }s_Infos;
};


Double変数がメモリ上で占有するサイズである8バイトの制限を超えない限り、構造体に内部変数を追加できることを覚えておいてください。しかし、ブール型は存在するために1ビットしか使用せず、isPlay変数が使用するバイトには7つの空きビットが残っているため、さらに7つのブール値を簡単に追加できます。そこで、この7つの空きビットを使って、チャートにコントロール指標があるかどうかを調べます。

注:このメカニズムは非常に適切ですが、ひとつ問題があります。ただし、今はこれには触れません。この問題については、将来、構造体を変更する必要が生じたときに、別の記事で検討する予定です。

これで十分だと思われるかもしれませんが、コードに何かを追加する必要があります。しかし、サービスコードについては気にせず、追加された変数が実際に役立つように指標コードだけを変更します。

最初にすべきことは、指標のコードに数行を追加することです。

void Init(const bool state = false)
{
        u_Interprocess Info;
                                
        if (m_szBtnPlay != NULL) return;
        m_id = ChartID();
        ChartSetInteger(m_id, CHART_EVENT_MOUSE_MOVE, true);
        ChartSetInteger(m_id, CHART_EVENT_OBJECT_DELETE, true);
        CreateBtnPlayPause(state);
        GlobalVariableTemp(def_GlobalVariableReplay);
        if (!state) CreteCtrlSlider();
        ChartRedraw();
        Info.Value = GlobalVariableGet(def_GlobalVariableReplay);
        Info.s_Infos.IsUsing = true;
        GlobalVariableSet(def_GlobalVariableReplay, Info.Value);
}


ここでは、コントロール指標が作成されたことをグローバルターミナル変数に通知し、記録します。しかし、なぜグローバルターミナル変数を作成するために前の呼び出しが必要なのでしょう。ぬかせませんか。この最初の呼び出しは、実際には、グローバル変数が一時的なものであり、維持すべきではないことをMetaTrader 5プラットフォームに通知する役割を果たします。グローバルターミナル変数のデータを保存するようにプラットフォームに依頼しても、一時的とみなされるこれらの変数の値は保存されず、失われます。

というのも、グローバルターミナル変数を保存してリセットする必要がある場合、実際には何もないのにコントロール指標の存在を報告する変数を持つのは現実的ではないからです。そのためには、こうしなければなりません。

この件に関しては気をつけるべきです。なぜなら、プラットフォームがチャート上で指標を再配置する際、すでにリプレイで進んでいるために、グローバルターミナル変数の値が異なる可能性があるからです。そして、もしこの行がなければ、システムはリプレイシステムをゼロからスタートさせます。

さらにやらなければならないことがあります。

case CHARTEVENT_OBJECT_CLICK:
        if (sparam == m_szBtnPlay)
        {
                Info.s_Infos.isPlay =(bool) ObjectGetInteger(m_id, m_szBtnPlay, OBJPROP_STATE);
                if (!Info.s_Infos.isPlay) CreteCtrlSlider(); else
                {
                        RemoveCtrlSlider();
                        m_Slider.szBtnPin = NULL;
                }
                Info.s_Infos.IsUsing = true;
                Info.s_Infos.iPosShift = m_Slider.posPinSlider;
                GlobalVariableSet(def_GlobalVariableReplay, Info.Value);
                ChartRedraw();
        }else if (sparam == m_Slider.szBtnLeft) PositionPinSlider(m_Slider.posPinSlider - 1);
        else if (sparam == m_Slider.szBtnRight) PositionPinSlider(m_Slider.posPinSlider + 1);
        break;


一時停止/再生ボタンが押されるたびに、グローバルターミナル変数の値が変更されます。しかし、以前のコードでは、ボタンがクリックされると、保存された値には、コントロール指標がチャート上に存在することを示す表示が含まれなくなります。このため、コード行を追加する必要があります。これを使えば、一時停止から再生に切り替えたり、逆に再生から一時停止に切り替えたりしても、誤った表示が出ないので、正しい表示ができます。

C_Replayクラスに関連する部分は完成していますが、まだ少し作業が残っています。単に表示を作っただけでは、その存在という事実以外は保証されません。指標のコードに移りましょう。ここでは、すべてが正しく動作し、奇妙な動作をするものにならないよう、もう少し注意する必要があります。

細部に注意を払いましょう。最初に注意すべきなのはOnInitのコードです。

int OnInit()
{
#define def_ShortName "Market Replay"
        u_Interprocess Info;
        
        IndicatorSetString(INDICATOR_SHORTNAME, def_ShortName);
        if(GlobalVariableCheck(def_GlobalVariableReplay))
        {
                Info.Value = GlobalVariableGet(def_GlobalVariableReplay);
                if (Info.s_Infos.IsUsing)
                {
                        ChartIndicatorDelete(ChartID(), 0, def_ShortName);
                        return INIT_FAILED;
                }
        } else Info.Value = 0;
        Control.Init(Info.s_Infos.isPlay);
        
        return INIT_SUCCEEDED;
#undef def_ShortName
}


実用的な理由から、ここでは指標の名前を指定する定義を作成しました。この名前によって、チャートに実際に表示されている指標を確認できるウィンドウに表示される指標のリストから、指標を削除することができます。視覚化されていないものもこのウィンドウに表示されます。無駄な指標は残したくありません。

そのため、指標をチャートから削除した後、その指標がすでにチャート上に存在するかどうかを調べる必要があります。そのために、ターミナルグローバル変数に作成された値を確認します。このため、確認は非常にシンプルで効率的です。確認する方法は他にもありますが、今回はグローバルターミナル変数を使用するので、その変数を通して確認する方が簡単です。

その他の機能は同じように継続されますが、1つのMetaTrader 5セッションで複数のチャートにコントロール指標を追加することはできなくなります。こちらと添付のコードには、指標がすでに別のチャートに存在しているという警告を追加していませんが、関数が初期化エラーを返す前にこの警告を追加することができます。

これで十分と思われるかもしれませんが、他にも直さなければならないことがあります。MetaTrader 5 が時間枠を変更するリクエストを受け取る(これはおそらくプラットフォーム上で最も一般的なイベントです)たびに、他の多くのものと同様に、すべての指標が削除されてリセットされることに注意してください。

さて、次のことを考えてみましょう。指標がグローバル変数を通じて、任意のチャート上でその指標のコピーが実行されていることを通知し、この特定のチャートの時間枠を変更すると、指標は削除されます。しかし、MetaTrader 5プラットフォームが指標をチャートに復元すると、指標をチャートに配置できなくなります。その理由は、まさにOnInit関数のコードで見たとおりです。どうにかしてグローバルターミナル変数を編集し、コントロール指標の存在を報告しないようにする必要があります。

この問題を解決する非常にエキゾチックな方法がありますが、MetaTrader 5プラットフォームとMQL5言語は、この問題を解決するためのかなりシンプルな手段を提供しています。次のコードを見てみましょう。

void OnDeinit(const int reason)
{
        u_Interprocess Info;
        
        switch (reason)
        {
                case REASON_CHARTCHANGE:
                        if(GlobalVariableCheck(def_GlobalVariableReplay))
                        {
                                Info.Value = GlobalVariableGet(def_GlobalVariableReplay);
                                Info.s_Infos.IsUsing = false;
                                GlobalVariableSet(def_GlobalVariableReplay, Info.Value);
                        }
                        break;
                case REASON_REMOVE:
                case REASON_CHARTCLOSE:
                        GlobalVariableDel(def_GlobalVariableReplay);
                        ChartClose(ChartID());
                        break;
        }
}


覚えているように、指標が削除されるとDeInitイベントが発生し、OnDeInit関数が呼び出されます。この関数は、呼び出しの理由を示す値をパラメータとして受け取ります。この値を使用します。

この値は、初期化解除の理由コードで見ることができます。 ここでは、REASON_CHARTCHANGEがチャートの期間が変更されたことを示しています。そこで、確認します。物事を確認するのは常に良いことです。決して想像したり思い込んだりせず、常に確認するのです。 期待される名前のターミナルグローバル変数があるかどうかを確認し、 あれば、変数の値を取り込みます。サービスが何かをしている可能性があり、していればそれを邪魔したくはないので、ここでコントロール指標がチャート上に存在しなくなるという情報を修正します。これが終わったら、グローバルターミナル変数に情報を書き戻します。

ここで、このシステムに起こりうる欠陥について警告しておかなければなりません。何かがうまくいかない確率は低いですが、その方法に欠陥があることを常に知っておき、起こり得る問題に備えるべきです。

ここで問題になるのは、変数の読み込みと書き込みの間にわずかなずれが生じることです。小さなものではありますが、サービスが指標よりも先にグローバルターミナル変数に値を書き込ム可能性がある場合に発生します。この種のイベントが発生すると、グローバルターミナル変数へのアクセス時にサービスが期待する値は、変数に実際に格納されている値とは異なるようになります。

この欠陥を回避する方法はありますが、マーケットリプレイと連動するこのシステムでは、致命的な問題ではないので、この欠点は無視できます。ただし、この同じメカニズムを、保存された値が重要な、より複雑なものに使いたいのであれば、共有メモリの読み書きをロックしたりロック解除したりする方法について、もっと詳しく調べることをお勧めします。ターミナルグローバル変数はまさに共有メモリです。

以下のビデオで、修正された部分とまだ修正されていない部分をご覧ください。事態は今、より深刻になっています。




結論

ここで説明したシステムは、コントロール指標の誤用による誤作動をなくすには理想的なようですが、実際に起こりうる問題の一部を回避しているに過ぎないため、真に効果的な解決策とは言えません。

ビデオをご覧になれば、別の問題を解決する必要があることがわかると思いますが、それは一見したよりもはるかに複雑です。


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

添付されたファイル |
Market_Replay.zip (13057.92 KB)
ニューラルネットワークが簡単に(第38回):不一致による自己監視型探索 ニューラルネットワークが簡単に(第38回):不一致による自己監視型探索
強化学習における重要な問題のひとつは、環境探索です。前回までに、「内因性好奇心」に基づく研究方法について見てきました。今日は別のアルゴリズムを見てみましょう。不一致による探求です。
MQL5の圏論(第18回):ナチュラリティスクエア(自然性の四角形) MQL5の圏論(第18回):ナチュラリティスクエア(自然性の四角形)
この記事では、圏論の重要な柱である自然変換を紹介します。一見複雑に見える定義に注目し、次に本連載の「糧」であるボラティリティ予測について例と応用を掘り下げていきます。
ニューラルネットワークが簡単に(第39回):Go-Explore、探検への異なるアプローチ ニューラルネットワークが簡単に(第39回):Go-Explore、探検への異なるアプローチ
強化学習モデルにおける環境の研究を続けます。この記事では、モデルの訓練段階で効果的に環境を探索することができる、もうひとつのアルゴリズム「Go-Explore」を見ていきます。
MQL5の圏論(第17回):関手とモノイド MQL5の圏論(第17回):関手とモノイド
関手を題材にしたシリーズの最終回となる今回は、圏としてのモノイドを再考します。この連載ですでに紹介したモノイドは、多層パーセプトロンとともに、ポジションサイジングの補助に使われます。