English Русский 中文 Español Deutsch Português
preview
リプレイシステムの開発 - 市場シミュレーション(第13回):シミュレーターの誕生(III)

リプレイシステムの開発 - 市場シミュレーション(第13回):シミュレーターの誕生(III)

MetaTrader 5テスター | 19 12月 2023, 08:03
167 0
Daniel Jose
Daniel Jose


はじめに

前回の「リプレイシステムの開発 - 市場シミュレーション(第12回):シミュレーターの誕生(II)」は、この記事のための準備でした。今日は、データの一貫性を高めるためにシミュレーションシステムに変更を加えます。同時に、処理面でより効率的なシステムにするため、いくつかの大きな変更を加えます。これは、リプレイ/シミュレーションシステムを作る次のステップで必要になります。重要なのは、リプレイでもシミュレーターでも実際に使えるシステムにするためには、一貫性のある動作、少なくとも可能な限り一貫性のある動作が必要だということです。ある時点ではある方法で機能するシステムを、別の時点ではまったく異なる、予測不可能な方法で機能させることはできません。

リプレイシステムの開発 - 市場シミュレーション(第2回):最初の実験(II)」では、今のところ使い勝手の良いシステムを作ってきましたが、シミュレーションや擬似ランダムデータの生成を含むアイデアを思いついた瞬間に適さなくなります。正直なところ、(実際のチケットを使って)リプレイで動くとしても、現在のシステムは完全に適しているとは言えなくなります。これは、希望する資産や日の変動が激しい場合に特に当てはまります。このシナリオでは、1分バーを作成して表示する現在のシステムは非常に非効率的であり、時には同期の問題を引き起こす可能性があります。言い換えれば、1分でできるはずのバーが、もっと長くかかることがあり、ボラティリティの高い動きはフォローや取引が簡単だという誤った印象を与えますが、これは真実ではありません。

この問題の解決策は決して簡単ではありません。この問題を解決するには、実際の作成方法を変えなければならないからです。この仕事は簡単だと思うかもしれませんが、そうではありません。ある種のモデリングが必要で、自分たちが何をしているのか分かっていないとかなり難しくなります。バーを作るシステムに何か問題があることに気づくまで、私でさえ(そしてその方法を読者に教えているのは私です)ずっと時間がかかりました。このことに気づいたのはモデリング段階に入ってからで、経時的な違いが如実になったのは、後述するいくつかの計算を伴うからです。しかし、今でもこの問題を解決できるとは思わないでください。この問題は後に、次の作成段階で解決されます。まず、1分足バーを作成するためのいくつかの修正と新しいシステムの導入から始めましょう。


新しい市場リプレイサービス

1分足バーを実際に作成して、必要であればチェックできるようにするには、リプレイサービスにいくつかの変更を加える必要があります。最初に変更しなければならないのは、サービスファイルです。以下は、新しいリプレイサービスファイルの全体像です。

#property service
#property icon "\\Images\\Market Replay\\Icon.ico"
#property copyright "Daniel Jose"
#property version   "1.13"
#property description "Replay-simulation system for MT5."
#property description "It is independent from the Market Replay."
#property description "For details see the article:"
#property description "https://www.mql5.com/ja/articles/11034"
#property link "https://www.mql5.com/ja/articles/11034"
//+------------------------------------------------------------------+
#define def_Dependence  "\\Indicators\\Market Replay.ex5"
#resource def_Dependence
//+------------------------------------------------------------------+
#include <Market Replay\C_Replay.mqh>
//+------------------------------------------------------------------+
input string            user00 = "Config.txt";  //"Replay" config file.
input ENUM_TIMEFRAMES   user01 = PERIOD_M5;     //Initial timeframe for the chart.
input bool              user02 = true;          //Visual bar construction.
input bool              user03 = true;          //Visualize creation metrics.
//+------------------------------------------------------------------+
void OnStart()
{
        C_Replay        Replay;
        
        Replay.InitSymbolReplay();
        if (Replay.SetSymbolReplay(user00))
        {
                Print(<"Wait for permission from [Market Replay] indicator to start replay...");
                if (Replay.ViewReplay(user01))
                {
                        Print("Permission received. The replay service can now be used...");
                        while (Replay.LoopEventOnTime(user02, user03));
                }
        }       
        Replay.CloseReplay();
        Print("Replay service completed...");
}
//+------------------------------------------------------------------+


少なくとも一見したところでは、この方がずっとシンプルであることがわかるでしょう。複雑さはすべてオブジェクトクラスの中に移され、これには十分な理由があります。時間です。ここにはまだ微妙な問題があり、この記事全体で説明しますが、古いサービス ファイルの場合と同様に、一部の操作によりシステムの貴重なミリ秒が奪われていました。MetaTrader 5プラットフォームの効率を改善しようと試みたにもかかわらず、ミリ秒を費やした結果、最終的にパフォーマンスが低下し、実際に1分足を処理してプロットするのに時間がかかりました。

しかし、よく見ると、ユーザーのために新しい変数が追加されています。これにより、1分バーを作成するために費やした時間や必要な時間を確認することができます。バー作成ループをブロックする呼び出しが1つだけになったことに注意してください。この呼び出しは、非常に特殊な2つの状況を除いては戻りません。でも、私たちがどんな状況について話しているのか、すぐにわかるでしょう。このループは無限ループのように実行されますが、実際にはオブジェクトクラスの中にある関数によって制御されます。このように単純化しているため、実際のファイルはこの1つだけです。オブジェクトクラスに関しては、その複雑さが増しています。その結果、以前はpublicだったいくつかの関数がpublicでなくなり、クラスのprivateとなりました。これをメソッド隠蔽と呼びます。したがって、真にpublicな要素は、上で見たようにサービスファイルに現れる関数だけです。

そうすることで、変化が見えてきます。これにより、サービスファイルの簡素化がさらに進みます。最初の変更点を以下に示します。

                bool ViewReplay(ENUM_TIMEFRAMES arg1)
                        {
                                u_Interprocess info;
                                
                                if ((m_IdReplay = ChartFirst()) > 0) do
                                {
                                        if (ChartSymbol(m_IdReplay) == def_SymbolReplay)
                                        {
                                                ChartClose(m_IdReplay);
                                                ChartRedraw();
                                        }
                                }while ((m_IdReplay = ChartNext(m_IdReplay)) > 0);
                                info.u_Value.IdGraphic = m_IdReplay = ChartOpen(def_SymbolReplay, arg1);
                                ChartApplyTemplate(m_IdReplay, "Market Replay.tpl");
                                ChartRedraw(m_IdReplay);
                                GlobalVariableDel(def_GlobalVariableIdGraphics);
                                GlobalVariableTemp(def_GlobalVariableIdGraphics);
                                GlobalVariableSet(def_GlobalVariableIdGraphics, info.u_Value.df_Value);
                                while ((!GlobalVariableCheck(def_GlobalVariableReplay)) && (!_StopFlag) && (ChartSymbol(m_IdReplay) != "")) Sleep(750);
                                
                                return ((!_StopFlag) && (ChartSymbol(m_IdReplay) != ""));
                        }


基本的にコードは以前と同じですが、以前サービスファイルのコードで実行されていたテストを追加しました。こうすることで、サービスファイルは、どのチャートIDがリプレイ資産の表示に使用されているかを知る必要がなくなります。したがって、このポイントは、指標によってグローバルターミナル変数が作られることを期待します。ただし、ユーザーがチャートを閉じたり、サービスを停止すれば、このループは終了します。しかし、問題がなく、ユーザーがチャートまたはサービスを閉じていない間に変数が定義された場合、TRUE値が得られるので、サービスは次のステップに進みます。

                bool LoopEventOnTime(const bool bViewBuider, const bool bViewMetrics)
                        {

                                u_Interprocess Info;
                                int iPos, iTest;
                                
                                iTest = 0;
                                while ((iTest == 0) && (!_StopFlag))
                                {
                                        iTest = (ChartSymbol(m_IdReplay) != "" ? iTest : -1);
                                        iTest = (GlobalVariableGet(def_GlobalVariableReplay, Info.u_Value.df_Value) ? iTest : -1);
                                        iTest = (iTest == 0 ? (Info.s_Infos.isPlay ? 1 : iTest) : iTest);
                                        if (iTest == 0) Sleep(100);
                                }
                                if ((iTest < 0) || (_StopFlag)) return false;
                                AdjustPositionToReplay(bViewBuider);
                                m_MountBar.delay = 0;
                                while ((m_ReplayCount < m_Ticks.nTicks) && (!_StopFlag))
                                {
                                        CreateBarInReplay(bViewMetrics);
                                        iPos = (int)(m_ReplayCount < m_Ticks.nTicks ? m_Ticks.Info[m_ReplayCount].time_msc - m_Ticks.Info[m_ReplayCount - 1].time_msc : 0);
                                        m_MountBar.delay += (iPos < 0 ? iPos + 1000 : iPos);
                                        if (m_MountBar.delay > 400)
                                        {
                                                if (ChartSymbol(m_IdReplay) == "") break;
                                                GlobalVariableGet(def_GlobalVariableReplay, Info.u_Value.df_Value);
                                                if (!Info.s_Infos.isPlay) return true;
                                                Info.s_Infos.iPosShift = (ushort)((m_ReplayCount * def_MaxPosSlider) / m_Ticks.nTicks);
                                                GlobalVariableSet(def_GlobalVariableReplay, Info.u_Value.df_Value);
                                                Sleep(m_MountBar.delay - 20);
                                                m_MountBar.delay = 0;
                                        }
                                }                               
                                return (m_ReplayCount == m_Ticks.nTicks);
                        }


このルーティンは見かけ以上のものです。常に出たり入ったりしていると思うかもしれません。しかし実際には、サービスファイルのコードに戻るのは2つのケースだけです。1つ目は、ユーザーが1分足バーの作成を一時停止した場合です。もう1つは、チャートが閉じられたり、使用するティックがなくなったためにサービスが停止した場合です。つまり、何らかの理由でサービスが停止した場合です。それ以外の場合は、ティック終了に達した場合はTRUE、達っさなかった場合はFALSEとなります。FALSEを指定すると、サービスコードからわかるように、リプレイ/シミュレーションシステムが終了します。

では、このルーチンが含む内部ループで単にスタックした場合、残りの時間はどうなるかを見てみましょう。そう、私たちには2つのループがあり、それぞれが非常に特定のことを担当しています。ここで何が起こっているのかを明確にするために、両方に焦点を当ててみましょう。最初のループを以下に示します。

// ... declaring variables ...

                                iTest = 0;
                                while ((iTest == 0) && (!_StopFlag))
                                {
                                        iTest = (ChartSymbol(m_IdReplay) != "" ? iTest : -1);
                                        iTest = (GlobalVariableGet(def_GlobalVariableReplay, Info.u_Value.df_Value) ? iTest : -1);
                                        iTest = (iTest == 0 ? (Info.s_Infos.isPlay ? 1 : iTest) : iTest);
                                        if (iTest == 0) Sleep(100);
                                }
                                if ((iTest < 0) || (_StopFlag)) return false;
                                AdjustPositionToReplay(bViewBuider);
                                m_MountBar.delay = 0;

//... the rest of the code (will be discussed later) ...

このコードには、2つの状況で動作するループがあります。最初の状況は、チャートを閉じることによってサービスが終了していない場合です。変数もテストしていることに注意してください。では、どうすればいいのでしょうか。ループの中で、変数の値を変更するためにいくつかの条件をチェックしていることに注目してください。これらのチェックは、旧バージョンではサービスコード内で開始されていました。しかし、ひとつ問題がありました。これらのチェックは実行されるたびに数ミリ秒かかりますが、最もマシンサイクルを必要としたのは、チャートが開いているかどうかのチェックでした。

システムが実際に1分バーのプロットを開始する前にこのチェックをおこなうことで、マシンサイクルを節約することができます。しかし、このループから抜け出す方法が必要です。そのため、ユーザーがサービスを開始したら、ループを終了するよう指示することになります。バーを作るシステムに向かうことを確実にするため、テスト変数を正の値に設定します。何らかの理由でループが終了し、ユーザーがバーの作成を開始していない場合、FALSEを返します。こうすることで、サービスはリプレイ/シミュレーションを完了する必要があることを知ることができます。

しかし、トリガー条件が満たされた場合、実際には2つのことをしなければなりません。1つ目は、リプレイ/シミュレーションの開始点を見つけることです。これを行う関数については後述します。次にすべきことは、遅延システムをリセットすることです。こうすることで、以下に示す2つ目のループに入ることができます。

// ... Code from the previous loop...

                                while ((m_ReplayCount < m_Ticks.nTicks) && (!_StopFlag))
                                {
                                        CreateBarInReplay(bViewMetrics);
                                        iPos = (int)(m_ReplayCount < m_Ticks.nTicks ? m_Ticks.Info[m_ReplayCount].time_msc - m_Ticks.Info[m_ReplayCount - 1].time_msc : 0);
                                        m_MountBar.delay += (iPos < 0 ? iPos + 1000 : iPos);
                                        if (m_MountBar.delay > 400)
                                        {
                                                if (ChartSymbol(m_IdReplay) == "") break;
                                                GlobalVariableGet(def_GlobalVariableReplay, Info.u_Value.df_Value);
                                                if (!Info.s_Infos.isPlay) return true;
                                                Info.s_Infos.iPosShift = (ushort)((m_ReplayCount * def_MaxPosSlider) / m_Ticks.nTicks);
                                                GlobalVariableSet(def_GlobalVariableReplay, Info.u_Value.df_Value);
                                                Sleep(m_MountBar.delay - 20);
                                                m_MountBar.delay = 0;
                                        }
                                }                               

// ... The rest of the function ...

この2つ目のループが、システムを正しく機能させる役割を担っています。ここでは、実際に1分足バーを作成します。使用するデータがなくなるか、システムが終了するまで、システムはこのループを抜けることはありません。終了する条件はあと1つあります。ユーザーが一時停止したときです。この場合、関数は一旦停止し、再び呼び出され、上に示した最初のループに戻ります。では、なぜこのシステムが旧バージョンのリプレイ/シミュレーションサービスよりも効率的なのかを見てみましょう。ここでは、後で説明する関数を呼び出します。これは1分間のバーを作成します。仕組みについては気にしなくていいです。作成が別の場所でおこなわれていることを知っておいてください。

しかし、ここで重要な疑問が生じます。よく見ると、実際に取引されているかシミュレートされているかにかかわらず、各ティックは一定の時間に作成されなければなりません。該当するセグメントは下の画像でハイライトされています。

時間、分、秒があることに注意してください。これらは私たちにとってあまり意味がありません。ここでのリプレイ/シミュレーションにおいて本当に重要なのは、秒の後に来る数値、つまりミリ秒です。この数字を見れば、長い時間のように思えるでしょう。しかし、私たちが本当に理解しなければならないのは、ミリ秒単位の時間ではなく、最後のティックの時間と次に表示する時間との差です。場合によってはその差は非常に小さく、2ミリ秒に満たないこともあります。以前のシステムでは対応できませんでした。もっと早い、違うシステムが必要です。私たちの努力にもかかわらず、上の画像にあるように、時間が非常に短いときにはうまく機能しませんでした。1回の呼び出しから次の呼び出しまでの時間は、10ミリ秒をはるかに下回ります。

しかし、1分間の棒グラフをプロットするこの新しい方法を使えば、最新のコンピューターでは1ミリ秒以下に短縮できます。そして、後で見るように、バーの作成手順はかなり高速になったので、適切なパフォーマンスを得るためにOpenCLに頼る必要はなくなりました。GPUを必要とせず、CPUだけでこれを実現できます。ただし、今後はすべてのティックに遅延をかけることはありません。少し溜めてから、少し休みましょう。累積値と一時停止値は変更可能で、微調整ができます。こうすることで、下のビデオにあるように、かなり適切な結果を得ることができます。これは、システムが1分間のバー間で達成できた時間を示しています。

精度は完璧ではありませんが、より正確な時間を得るために数値を調整することができます。オペレーティングシステムの内部カウンタは16ミリ秒未満の時間を精度よく処理できないため、この正確な時間を実現するために使用することはできません。しかし、この仕事は研究であり、まだ完成していないことに注意してください。状況を改善する方法を見つけるには時間がかかるかもしれませんが、今はこれで十分だと思います。



チャートが開いているかどうかもチェックする必要があります。しかし、頻繁にやるわけではないので、発生する遅延はずっと少なくなります。すべての呼び出しは常にわずかな遅延を発生させます。また、ポジション値を更新し、コントロール指標の現在の状態をキャプチャする必要がありますが、これも若干の実行遅延につながります。また、チャートが開いているかどうかをチェックすると、パフォーマンスが多少低下します。しかし、1秒間に数回しか発信されないため、正確にはもっと低くなります。

これでこの部分を終えることができます。その前に、この1分間バー作成システムの他の2つの機能を見てみましょう。最初の機能は、リプレイ/シミュレーションを開始するポイントを探すものです。

                void AdjustPositionToReplay(const bool bViewBuider)
                        {
                                u_Interprocess Info;
                                MqlRates       Rate[def_BarsDiary];
                                int            iPos,   nCount;
                                
                                Info.u_Value.df_Value = GlobalVariableGet(def_GlobalVariableReplay);
                                if (Info.s_Infos.iPosShift == (int)((m_ReplayCount * def_MaxPosSlider * 1.0) / m_Ticks.nTicks)) return;
                                iPos = (int)(m_Ticks.nTicks * ((Info.s_Infos.iPosShift * 1.0) / (def_MaxPosSlider + 1)));
                                Rate[0].time = macroRemoveSec(m_Ticks.Info[iPos].time);
                                if (iPos < m_ReplayCount)
                                {
                                        CustomRatesDelete(def_SymbolReplay, Rate[0].time, LONG_MAX);
                                        if ((m_dtPrevLoading == 0) && (iPos == 0))
                                        {
                                                m_ReplayCount = 0;
                                                Rate[m_ReplayCount].close = Rate[m_ReplayCount].open = Rate[m_ReplayCount].high = Rate[m_ReplayCount].low = m_Ticks.Info[iPos].last;
                                                Rate[m_ReplayCount].tick_volume = Rate[m_ReplayCount].real_volume = 0;
                                                CustomRatesUpdate(def_SymbolReplay, Rate, 1);
                                        }else
                                        {
                                                for(Rate[0].time -= 60; (m_ReplayCount > 0) && (Rate[0].time <= macroRemoveSec(m_Ticks.Info[m_ReplayCount].time)); m_ReplayCount--);
                                                m_ReplayCount++;
                                        }
                                }else if (iPos > m_ReplayCount)
                                {
                                        if (bViewBuider)
                                        {
                                                Info.s_Infos.isWait = true;
                                                GlobalVariableSet(def_GlobalVariableReplay, Info.u_Value.df_Value);
                                        }else
                                        {
                                                for(; Rate[0].time > m_Ticks.Info[m_ReplayCount].time; m_ReplayCount++);
                                                for (nCount = 0; m_Ticks.Rate[nCount].time < macroRemoveSec(m_Ticks.Info[iPos].time); nCount++);
                                                CustomRatesUpdate(def_SymbolReplay, m_Ticks.Rate, nCount);
                                        }
                                }
                                for (iPos = (iPos > 0 ? iPos - 1 : 0); (m_ReplayCount < iPos) && (!_StopFlag);) CreateBarInReplay();
                                Info.u_Value.df_Value = GlobalVariableGet(def_GlobalVariableReplay);
                                Info.s_Infos.isWait = false;
                                GlobalVariableSet(def_GlobalVariableReplay, Info.u_Value.df_Value);
                        }


前バージョンと比較して大きな変更はありませんが、前バージョンとは異なり、このバージョンでは値を返さなくなりました。バーの作成がより効率的になるように変更されました。些細なことのように思えるかもしれませんが、それを実行するという単純な事実が大きな助けとなります。さて、もうひとつ細かいことですが、上の関数はpublicではなくなりました。オブジェクトクラスの外部からはアクセスできません。次の1分足バーの作成関数も同様です。

inline void CreateBarInReplay(const bool bViewMetrics = false)
                        {
#define def_Rate m_MountBar.Rate[0]

                                static ulong _mdt = 0;
                                int i;
                                
                                if (m_MountBar.bNew = (m_MountBar.memDT != macroRemoveSec(m_Ticks.Info[m_ReplayCount].time)))
                                {
                                        if (bViewMetrics)
                                        {
                                                _mdt = (_mdt > 0 ? GetTickCount64() - _mdt : _mdt);
                                                i = (int) (_mdt / 1000);
                                                Print(TimeToString(m_Ticks.Info[m_ReplayCount].time, TIME_SECONDS), " - Metrica: ", i / 60, ":", i % 60, ".", (_mdt % 1000));
                                                _mdt = GetTickCount64();
                                        }
                                        m_MountBar.memDT = macroRemoveSec(m_Ticks.Info[m_ReplayCount].time);
                                        def_Rate.real_volume = 0;
                                        def_Rate.tick_volume = 0;
                                }
                                def_Rate.close = m_Ticks.Info[m_ReplayCount].last;
                                def_Rate.open = (m_MountBar.bNew ? def_Rate.close : def_Rate.open);
                                def_Rate.high = (m_MountBar.bNew || (def_Rate.close > def_Rate.high) ? def_Rate.close : def_Rate.high);
                                def_Rate.low = (m_MountBar.bNew || (def_Rate.close < def_Rate.low) ? def_Rate.close : def_Rate.low);
                                def_Rate.real_volume += (long) m_Ticks.Info[m_ReplayCount].volume_real;
                                def_Rate.tick_volume += (m_Ticks.Info[m_ReplayCount].volume_real > 0 ? 1 : 0);
                                def_Rate.time = m_MountBar.memDT;
                                m_MountBar.bNew = false;
                                CustomRatesUpdate(def_SymbolReplay, m_MountBar.Rate, 1);
                                m_ReplayCount++;
                                
#undef def_Rate
                        }


事実上、説明は必要ありません。ここでは、1分足バーを1本ずつ作成し、チャートに送信します。まだティックを使用していません。つまり、リプレイ/シミュレーションのためにMetaTrader 5のリソースを使用することはできません。将来的には実装するつもりです。今のところは心配しなくて大丈夫です。同時に、新しいバーの作成に着手する価値があるかどうかを判断するために、必要なすべてのチェックと測定もおこないます。この部分は、あるバーとその前のバーの間の時間メトリクスを表示するだけなので、将来的には捨てても構いません。遅延の値を作成ループの中で微調整するのに役立つので、今は非常に有用です。

これでこの1分バーについての部分は終了です。このシステムは、少なくとも取引されたティックとテストされたシミュレートされたティックについては、非常に合理的な時間でそれらを提示できるようになりました。次に、ティックリプレイに関連する別の問題を扱います。ティックそのものについては、今のところ特に問題はありません。


ランダムウォークグラフの表示

これまでのところ、仕事は興味深く、楽しいほどです。しかし、今、一部の人にとっては非常に難しいかもしれないが実際におこなう必要があることに直面しています。1分足に何らかの形で存在する可能性のあるすべてのティックの開始をシミュレートすることです。以下の説明をよくお読みになることをお勧めします。物事をシンプルにするため、シミュレーションシステムの最終バージョンはここでは紹介しません。最終バージョンは後に登場します。このすべてを一度に見せるのはかなり複雑だからです。

私たちが本当に望んでいて、これから作ろうとしているのは、いわゆるランダムウォークです。このランダムウォークにはいくつかのルールがあります。通常のプログラムとは異なり、ここではシステムを完全にランダムにすることはできません。動きを指示するための数学的ルールを作る必要があります。誤解しないでください。ランダムウォークは、少なくとも短期的には、実際には完全にランダムで予測不可能な動きです。しかし、私たちは完全に予測不可能な動きを作り出すことはなく、どこから始まってどこで終わるかを知っているので、システムは完全にランダムなものではありません。ただし、バーの中にランダム性を加えることにします。

真にランダムなウォークを作りやすくするために使えるアイデアはいくつかあります。特定のケースに応じて、いくつかのアプローチが他のアプローチより優れている場合があります。経験の浅いプログラマーは、乱数生成器を使い、何らかの変換を行って値を一定の範囲に制限すれば十分だと考えるかもしれません。このアプローチは完全に間違っているわけではありませんが、いくつかの欠点があります。このような動きの結果のデータをチャートで見ると、下の画像のようなものになります。

このグラフ(はい、これが後でお見せするグラフです)は、まったくランダムな動きには見えないと思うかもしれません。これは完全に混乱しているように見えますが、実際には、時間内のポイントをジャンプすることによって達成されるランダムな動きです。このステップを達成するために、MetaTrader 5プラットフォームを使用して取得した以下のデータラインを使用します。それぞれの行が1分バーを表していることを忘れないでください。

このデータは添付ファイルとして提供されているため、ご自分で分析してください。では、なぜ上のグラフが予想外に違っているのか、その理由を理解しましょう。これを理解するためには、どのように作られたかを知る必要があります。まず始めに、サービスファイル内で2つのことを定義する必要があります。一時的に使用するもので、将来的にソースコードに登場することはありません。

#define def_TEST_SIMULATION
#ifdef def_TEST_SIMULATION
        #define def_FILE_OUT_SIMULATION "Info.csv"
#endif 
//+------------------------------------------------------------------+
#property service
#property icon "\\Images\\Market Replay\\Icon.ico"
#property copyright "Daniel Jose"
#property version   "1.13"

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

この定義によって、生成された動きのグラフを分析できるシミュレーションチェックができるようになります。一方、このファイルには、1分バー内のシミュレーションされた動きに対応するデータが含まれます。後ほど、このファイルがいつ、どこで作成されるかを見てみましょう。この定義がサービスファイルで直接定義されると、ヘッダーファイルを宣言する前に、MQHファイルでこの定義を使うことができます。それではC_Replay.Mqhファイルに移り、データを取得するために何をするのかを理解しましょう。

実際にキャプチャしてするには、シミュレーターが1分足の中でどのような動きを作ったのかを把握するために、以下の関数を使用します。

                bool LoadBarsToTicksReplay(const string szFileNameCSV)
                        {
                                int file;
                                MqlRates rate[1];
                                MqlTick tick[];
                                
                                if (OpenFileBars(file, szFileNameCSV))
                                {
                                        Print("Converting bars to ticks. Please wait...");
                                        ArrayResize(m_Ticks.Info, def_MaxSizeArray, def_MaxSizeArray);
                                        ArrayResize(tick, def_MaxSizeArray);
                                        while ((!FileIsEnding(file)) && (!_StopFlag))
                                        {
                                                FileReadBars(file, rate);
                                                Simulation(rate[0], tick);
#ifdef def_TEST_SIMULATION
        FileClose(file);
        file = FileOpen(def_FILE_OUT_SIMULATION, FILE_ANSI | FILE_WRITE);
        for (long c0 = 0; c0 < m_Ticks.nTicks; c0++)
                FileWriteString(file, StringFormat("%0.f\n", m_Ticks.Info[c0].last));
        FileClose(file);
        ArrayFree(tick);
        
        return false;
#endif
                                        }
                                        FileClose(file);
                                        ArrayFree(tick);

                                        return (!_StopFlag);
                                }
                                
                                return false;
                        }


経験の浅いプログラマーにとって興味深いのは、シミュレーターが1分間のバーの中で常に同じ種類の動作シミュレーションをおこなうかどうかという点でしょう。実際、そうではありません。それぞれのバーが個性的で、ユニークな動きをするようなやり方を作っていきたいのです。しかし、バー間の動きを常に同じにしたい、あるいは少なくとも似たようなものにしたい場合は、システムが常に特定の値からシミュレーションを開始するように設定するだけでよいのです。これは、以下のように、シミュレーター呼び出しの前に呼び出しを追加し、この呼び出しで固定値を設定することによっておこなうことができます。

// ... Code ...

                                if (OpenFileBars(file, szFileNameCSV))
                                {
                                        Print("Converting bars to ticks. Please wait...");
                                        ArrayResize(m_Ticks.Info, def_MaxSizeArray, def_MaxSizeArray);
                                        ArrayResize(tick, def_MaxSizeArray);
                                        while ((!FileIsEnding(file)) && (!_StopFlag))
                                        {
                                                FileReadBars(file, rate);
                                                srand(5);
                                                Simulation(rate[0], tick);

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

しかし、ソースコードに戻りましょう。サービスファイル内に定義があるので、実行がある時点に達すると、サービス内に特定のファイルを作成し、そのファイルにはティック配列(この場合はダミー配列)に存在するすべての要素が含まれます。これは、シミュレーション関数の内部で何が起こるかに関係なく起こります。EXCELを使って、何が起こっているかをグラフィカルにチェックすることができます。この場合、サービスからエラーメッセージを受け取ることに注意してください。チェック定義が存在する場合、このエラーメッセージは無視されるべきです。もう1つ知っておくべき重要な点は、グラフを作成するのが簡単なEXCELを使用することです。MetaTrader 5を使ってこれをおこなうこともできますが、生成される情報量を考えると、結果は混乱するでしょう。EXCELで視覚化する方が簡単です。グラフの生成には他のプログラムも使用できます。重要なのは、シミュレーターが生成したグラフを視覚化できることです。EXCELでの操作方法をご存じない方は、以下のビデオでご覧いただけます。



動きが正しく作成されているかどうかをチェックする方法が必要になるので、グラフの作成方法を知っておくことは非常に重要です。作成されるバーの動きを観察するだけでは、それが本当に適切なレベルのランダム性を持っているかどうかを判断するのに十分ではありません。もう1つ、プログラマーの間で非常に一般的なのは、シミュレーションの手順内で実行される計算を複雑にしようとしても、ランダムウォークのような真にランダムな動きをする保証はないということです。上のビデオをご覧ください。短いものですが、次の段階で大いに役立つでしょう。チャートをお見せしますが、なぜこのように見えるのかを理解する必要があります。これでローカルチェックが可能になります。


結論

物事をシンプルに保つため、そしてランダムウォークモデルの実装で私たちが目にすることになるものについて読者を混乱させないために、この時点でこの記事を終えるつもりです。次回は、異なるグラフを生成するランダムウォークモデルを実装します。また、この「ランダムウォーク」が引き起こす問題や、その背景にある考え方も見ていくことにしましょう。ただし、すでに多くの人にとって目新しく、他の人にとっては非常に複雑なものを手にしているので、添付のコードで次回まで説明しないことがわかるとはいえ、これ以上複雑にしたくはありません。

急いではいけません。まずは今日説明されたことを勉強し、理解するべきです。なぜなら、この知識とその正しい理解なくして、近い将来に何がおこなわれるかを知ることはできないからです。 

もう1つ詳細があります。サービスを構築し、グラフ分析用のデータを生成せずにリプレイ/シミュレーションシステムを動作させるには、以下のコードに示すように、testingディレクティブを無効にします。ここではサービスファイルです。

//#define def_TEST_SIMULATION // <<-- Leave this line as it is to be able to use the replay/simulation service....
#ifdef def_TEST_SIMULATION
        #define def_FILE_OUT_SIMULATION "Info.csv"
#endif 
//+------------------------------------------------------------------+
#property service
#property icon "\\Images\\Market Replay\\Icon.ico"
#property copyright "Daniel Jose"
#property version   "1.13"


これをおこなわないと、チャートだけでなく、もっと多くのデータを表示しようとすると、サービスは常にエラーを報告します。この点に注意してください。

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

添付されたファイル |
リプレイシステムの開発 - 市場シミュレーション(第9回):カスタムイベント リプレイシステムの開発 - 市場シミュレーション(第9回):カスタムイベント
ここでは、カスタムイベントがどのようにトリガーされ、指標でどのようにリプレイ/シミュレーションサービスの状態がレポートされるかを見ていきます。
ニューラルネットワークが簡単に(第49回):Soft Actor-Critic ニューラルネットワークが簡単に(第49回):Soft Actor-Critic
連続行動空間の問題を解決するための強化学習アルゴリズムについての議論を続けます。この記事では、Soft Actor-Critic (SAC)アルゴリズムについて説明します。SACの主な利点は、期待される報酬を最大化するだけでなく、行動のエントロピー(多様性)を最大化する最適な方策を見つけられることです。
リプレイシステムの開発 - 市場シミュレーション(第10回):リプレイで実データのみを使用する リプレイシステムの開発 - 市場シミュレーション(第10回):リプレイで実データのみを使用する
ここでは、リプレイシステムで、調整されているかどうかを気にすることなく、より信頼性の高いデータ(取引されたティック)を使用する方法を見ていきます。
離散ハートレー変換 離散ハートレー変換
この記事では、スペクトル分析と信号処理の方法の1つである離散ハートレー変換について説明します。信号のフィルタリング、スペクトルの分析などが可能になります。DHTの能力は離散フーリエ変換の能力に劣りません。ただし、DFTとは異なり、DHTは実数のみを使用するため、実際の実装がより便利であり、その適用結果はより視覚的です。