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

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

MetaTrader 5テスター | 28 7月 2023, 11:11
255 0
Daniel Jose
Daniel Jose

はじめに

前回の「リプレイシステムの開発—市場シミュレーション(第1回):最初の実験(I)」稿では、適切な市場シミュレーションを生成するために実行時間の短いイベントシステムを作成しようとすると、いくつかの制限があることがわかりました。この方法では10ミリ秒未満を達成するのは不可能であることが明らかになりました。多くの場合、この時間は非常に短いです。ただし、記事に添付されているファイルを調べると、10ミリ秒が十分に短い時間ではないことがわかります。1ミリ秒または2ミリ秒といった望ましい時間を達成できる他の方法はあるでしょうか。

数ミリ秒という時間の使用に関連したことを少しでも考える前に、これは簡単な作業ではないことを思い出すことが重要です。実際のところ、オペレーティングシステム自体が提供するタイマーではこれらの値に到達することはできません。したがって、これは巨大ではないにしても、大きな問題です。この記事では、この質問に答え、オペレーティングシステムによって設定された時間の制限を超えて問題を解決する方法を示します。最新のプロセッサは1秒あたり数十億回の計算を実行できると多くの方が考えていることは承知しています。ただし、プロセッサが計算を実行することと、コンピュータ内のすべてのプロセスが必要なタスクを処理できるかどうかは別の問題です。なお、ここでは外部コードやDLLを使用せずに、MQL5のみを使用しようとしています。純粋なMQL5のみです。


計画

これを検証するには、方法論にいくつかの変更を加える必要があります。このプロセスがうまくいけば、リプレイ作成システムを再びいじる必要はなくなります。実際のティック値またはシミュレートされた値を使用して調査や訓練を実施するのに役立つ、他の質問に焦点を当てられるようになります。1分足の組み立て方は変わりません。これがこの記事の主な焦点になります。

最大限汎用的な方法を使用するつもりですが、私が見つけた最良の方法は、クライアント/サーバーのようなシステムを使用することです。これはすでに、「一からの取引エキスパートアドバイザーの開発(第16部):Web上のデータにアクセスする(II)」で説明しました。 その記事では、MetaTrader 5内で情報を送信する3つの方法を示しました。ここでは、これらのメソッドの1つであるSERVICEを使用します。MarketReplayはMetaTrader 5サービスになります。

すべてをゼロから作成するのだと思われていているかもしれませんが、しません。基本的に、システムはすでに実行されていますが、希望の1分間に達しません。システムをサービスに変えることでこの問題が解決するのではないかと問われるかもしれません:。 実際、システムをサービスに置き換えるだけでは問題は解決しません。しかし、最初から1分足の作成をEAシステムの残りの部分から分離すると、EA自体が足の作成の実行にわずかな遅延を引き起こすため、その後の作業が少なくなります。その理由については後ほど説明します。

サービスを利用する理由がお分かりになったでしょうか。これは、上で説明した他の方法よりもはるかに実用的です。EAとサービスの間でメッセージを交換する方法についての「一からの取引エキスパートアドバイザーの開発(第17部):Web上のデータにアクセスする(III)」稿で説明したのと同じ方法で制御できます。ただし、ここではこの制御の生成には焦点を当てず、サービスがチャート上に配置されるバーを生成することだけを望みます。より興味深くするために、EAとサービスを使用するだけでなく、プラットフォームをより創造的な方法で使用するつもりです。

念のために言っておきますが、時間を短縮するための最後の試みでは、次の結果が得られました。

これが最高の時間でした。ここで今回は早速潰していきます。 ただし、これらの値やここで示したテストに完全に執着しないでください。リプレイ/シミュレーターシステムの作成に関連するこの連載は、すでにかなり進んだ段階にあり、システムを実際に期待どおりに動作させるためにいくつかの概念を数回変更しました。現時点ではすべてが適切であるように見えますが、奥底ではタイミングテストに関連するいくつかの間違いを犯しました。このような初期のシステムでは、このようなエラーや誤解に気づくのは簡単ではありません。この連載を読み進めていくと、このタイミング関連の問題ははるかに複雑であり、リプレイ/シミュレーターシステムに没入できるように、CPUとMetaTrader 5プラットフォームがチャート上に一定量のデータを提供するだけではなく、はるかに多くのことが関係していることに気づくでしょう。

したがって、ここに示されているすべてを文字通りに受け取らないでください。ここでおこなうことは単純でも簡単でもないため、この連載に従ってください。


実装

システムの基盤を作成することから始めましょう。これらには次のものが含まれます。

  1. 1分足作成サービス
  2. サービスを開始するスクリプト
  3. シミュレーション用EA(後述)


市場リプレイサービスの定義

サービスを適切に操作するには、C_Replayクラスを更新する必要があります。ただし、これらの変更は非常に小さいため、詳細については説明しません。基本的に、これらはリターンコードです。ただし、追加の実装があるため、別途注意すべき点が1つあります。コードは次のとおりです。

#define macroGetMin(A)  (int)((A - (A - ((A % 3600) - (A % 60)))) / 60)
                int Event_OnTime(void)
                        {
                                bool isNew;
                                int mili;
                                static datetime _dt = 0;
                                
                                if (m_ReplayCount >= m_ArrayCount) return -1;
                                if (m_dt == 0)
                                {
                                        m_Rate[0].close = m_Rate[0].open =  m_Rate[0].high = m_Rate[0].low = m_ArrayInfoTicks[m_ReplayCount].Last;
                                        m_Rate[0].tick_volume = 0;
                                        m_Rate[0].time = m_ArrayInfoTicks[m_ReplayCount].dt - 60;
                                        CustomRatesUpdate(def_SymbolReplay, m_Rate, 1);
                                        _dt = TimeLocal();
                                }
                                isNew = m_dt != m_ArrayInfoTicks[m_ReplayCount].dt;
                                m_dt = (isNew ? m_ArrayInfoTicks[m_ReplayCount].dt : m_dt);
                                mili = m_ArrayInfoTicks[m_ReplayCount].milisec;
                                while (mili == m_ArrayInfoTicks[m_ReplayCount].milisec)
                                {
                                        m_Rate[0].close = m_ArrayInfoTicks[m_ReplayCount].Last;
                                        m_Rate[0].open = (isNew ? m_Rate[0].close : m_Rate[0].open);
                                        m_Rate[0].high = (isNew || (m_Rate[0].close > m_Rate[0].high) ? m_Rate[0].close : m_Rate[0].high);
                                        m_Rate[0].low = (isNew || (m_Rate[0].close < m_Rate[0].low) ? m_Rate[0].close : m_Rate[0].low);
                                        m_Rate[0].tick_volume = (isNew ? m_ArrayInfoTicks[m_ReplayCount].Vol : m_Rate[0].tick_volume + m_ArrayInfoTicks[m_ReplayCount].Vol);
                                        isNew = false;
                                        m_ReplayCount++;
                                }
                                m_Rate[0].time = m_dt;
                                CustomRatesUpdate(def_SymbolReplay, m_Rate, 1);
                                mili = (m_ArrayInfoTicks[m_ReplayCount].milisec < mili ? m_ArrayInfoTicks[m_ReplayCount].milisec + (1000 - mili) : m_ArrayInfoTicks[m_ReplayCount].milisec - mili);
                                if ((macroGetMin(m_dt) == 1) && (_dt > 0))
                                {
                                        Print("Elapsed time: ", TimeToString(TimeLocal() - _dt, TIME_SECONDS));
                                        _dt = 0;
                                }                               
                                return (mili < 0 ? 0 : mili);
                        };
#undef macroGetMin

強調表示の部分がC_Replayクラスのソースコードに追加されています。ここでおこなうのは遅延時間を定義することです。つまり、ラインで取得した値をミリ秒単位で正確に使用します。この時間はいくつかの変数に依存するため、正確ではありませんが、できるだけ1ミリ秒に近づけるように努めます。

これらの変更を念頭に置いて、以下のサービスコードを見てみましょう。

#property service
#property copyright "Daniel Jose"
#property version   "1.00"
//+------------------------------------------------------------------+
#include <Market Replay\C_Replay.mqh>
//+------------------------------------------------------------------+
input string    user01 = "WINZ21_202110220900_202110221759"; //File with ticks
//+------------------------------------------------------------------+
C_Replay Replay;
//+------------------------------------------------------------------+
void OnStart()
{
        ulong t1;
        int delay = 3;
        
        if (!Replay.CreateSymbolReplay(user01)) return;
        Print("Waiting for permission to start replay ...");
        GlobalVariableTemp(def_GlobalVariable01);
        while (!GlobalVariableCheck(def_SymbolReplay)) Sleep(750);
        Print("Replay service started ...");
        t1 = GetTickCount64();
        while (GlobalVariableCheck(def_SymbolReplay))
        {
                if ((GetTickCount64() - t1) >= (uint)(delay))
                {
                        if ((delay = Replay.Event_OnTime()) < 0) break;
                        t1 = GetTickCount64();
                }
        }
        GlobalVariableDel(def_GlobalVariable01);
        Print("Replay service finished ...");
}
//+------------------------------------------------------------------+

上記のコードはバーの作成を担当します。このコードをここに配置することで、リプレイシステムが独立して機能するようになります。MetaTrader 5プラットフォームの動作は、リプレイシステムにほとんど影響を与えず、影響も受けません。したがって、制御システム、リプレイの分析、シミュレーションに関連する他の作業をおこなうことができます。しかし、これは後でおこなわれます。

ここで興味深いことがわかります。強調表示された部分がGetTickCount64関数を提供していることに注目してください。 これにより、前の記事で見たものと同等のシステムが提供されますが、利点が1つあります。ここでは、解像度が1ミリ秒まで低下します。この精度は正確ではなく近似値ですが、近似レベルは実際の市場の動きに非常に近いものになります。これは使用するハードウェアには依存しません。結局のところ、より高い精度を保証するループを作成することもできますが、今回は使用するハードウェアに依存するため、非常に手間がかかります。

次におこなうのは次のスクリプトです。完全なコードは次のとおりです。

#property copyright "Daniel Jose"
#property version   "1.00"
//+------------------------------------------------------------------+
#include <Market Replay\C_Replay.mqh>
//+------------------------------------------------------------------+
C_Replay Replay;
//+------------------------------------------------------------------+
void OnStart()
{
        Print("Waiting for the Replay System ...");
        while((!GlobalVariableCheck(def_GlobalVariable01)) && (!IsStopped())) Sleep(500);
        if (IsStopped()) return;
        Replay.ViewReplay();
        GlobalVariableTemp(def_SymbolReplay);
        while ((!IsStopped()) && (GlobalVariableCheck(def_GlobalVariable01))) Sleep(500);
        GlobalVariableDel(def_SymbolReplay);
        Print("Replay Script finished...");
        Replay.CloseReplay();
}
//+------------------------------------------------------------------+

ご覧のとおり、どちらのコードも非常に単純です。ただし、プラットフォームでサポートされているグローバル変数を介して相互に通信します。したがって、次のスキームがあります。

これらのスキームはプラットフォーム自体によって維持されます。スクリプトが閉じると、サービスは停止します。サービスが停止すると、再生システムの実行に使用している銘柄はデータの受信を停止します。これにより、非常にシンプルで持続性の高いものになります。(プラットフォームとハードウェアの両方における)改善は、全体的なパフォーマンスに自動的に反映されます。これは奇跡ではありません。これはすべて、サービスプロセスによって実行される各操作中に発生するわずかな待ち時間によって実現されます。実際にシステム全体に影響を与えるのはこれだけであり、今後開発するスクリプトやWAについて心配する必要はありません。改善はサービスにのみ影響します。

システムをテストする手間を省くために、以下の画像で結果をプレビューできます。したがって、読者の皆さんは、チャート上の結果を確認するのに1分も待つ必要はありません。

ご覧のとおり、結果は理想に非常に近くなっています。余分な9秒は、システム設定を使用して簡単に削除できます。理想的には、この時間は1分未満である必要があります。これにより、システムに遅延を追加するだけで済むため、調整が容易になります。レイテンシを減らすよりも増やす方が簡単です。しかし、システム時間を短縮できないと思われる場合は、これを詳しく見てみましょう。

サービス中のシステムに遅延が発生する箇所があります。実際に遅延が発生するこの点は、以下のコードで強調表示されています。しかし、この行をコメントにするとどうなるでしょうか。システムはどうなりますか。

        t1 = GetTickCount64();
        while (GlobalVariableCheck(def_SymbolReplay))
        {
// ...  COMMENT ...  if ((GetTickCount64() - t1) >= (uint)(delay))
                {
                        if ((delay = Replay.Event_OnTime()) < 0) break;
                        t1 = GetTickCount64();
                }
        }
        GlobalVariableDel(def_GlobalVariable01);

強調表示された行は実行されなくなります。この場合、システムをローカルでテストする必要がなくなり、再び1分間待つ必要がなくなります。実行結果は以下のビデオに示されています。全体をご覧になってもいいですし、最終結果が表示される部分のみをご覧になっても大丈夫です。ご自由にお選びください。



つまり、最大の課題は遅延を適切に発生させることです。ただし、バーが作成されるまでの1分間のわずかな時間のずれは、実際には問題ではありません。リアル口座であっても情報伝達に遅延があるため、正確な時刻はわかりません。この遅延は非常に小さいですが、依然として存在します。


最大速度..本当に?

ここで、システムを1分以内に動作させるための最後の試みをおこないます。

ミリ秒の値を見ると、ある行と別の行の間にわずか1ミリ秒の違いがあることに気づくことがあります。しかし、ここではすべてを同じ秒内に処理します。したがって、コードに小さな変更を加えます。その中にループを追加します。これはシステム全体に非常に大きな違いをもたらす可能性があります。

変更点を以下に示します。

#define macroGetMin(A)  (int)((A - (A - ((A % 3600) - (A % 60)))) / 60)
inline int Event_OnTime(void)
                        {
                                bool isNew;
                                int mili;
                                static datetime _dt = 0;
                                
                                if (m_ReplayCount >= m_ArrayCount) return -1;
                                if (m_dt == 0)
                                {
                                        m_Rate[0].close = m_Rate[0].open =  m_Rate[0].high = m_Rate[0].low = m_ArrayInfoTicks[m_ReplayCount].Last;
                                        m_Rate[0].tick_volume = 0;
                                        m_Rate[0].time = m_ArrayInfoTicks[m_ReplayCount].dt - 60;
                                        CustomRatesUpdate(def_SymbolReplay, m_Rate, 1);
                                        _dt = TimeLocal();
                                }
                                isNew = m_dt != m_ArrayInfoTicks[m_ReplayCount].dt;
                                m_dt = (isNew ? m_ArrayInfoTicks[m_ReplayCount].dt : m_dt);
                                mili = m_ArrayInfoTicks[m_ReplayCount].milisec;
                                do
                                {
                                        while (mili == m_ArrayInfoTicks[m_ReplayCount].milisec)
                                        {
                                                m_Rate[0].close = m_ArrayInfoTicks[m_ReplayCount].Last;
                                                m_Rate[0].open = (isNew ? m_Rate[0].close : m_Rate[0].open);
                                                m_Rate[0].high = (isNew || (m_Rate[0].close > m_Rate[0].high) ? m_Rate[0].close : m_Rate[0].high);
                                                m_Rate[0].low = (isNew || (m_Rate[0].close < m_Rate[0].low) ? m_Rate[0].close : m_Rate[0].low);
                                                m_Rate[0].tick_volume = (isNew ? m_ArrayInfoTicks[m_ReplayCount].Vol : m_Rate[0].tick_volume + m_ArrayInfoTicks[m_ReplayCount].Vol);
                                                isNew = false;
                                                m_ReplayCount++;
                                        }
                                        mili++;
                                }while (mili == m_ArrayInfoTicks[m_ReplayCount].milisec);
                                m_Rate[0].time = m_dt;
                                CustomRatesUpdate(def_SymbolReplay, m_Rate, 1);
                                mili = (m_ArrayInfoTicks[m_ReplayCount].milisec < mili ? m_ArrayInfoTicks[m_ReplayCount].milisec + (1000 - mili) : m_ArrayInfoTicks[m_ReplayCount].milisec - mili);
                                if ((macroGetMin(m_dt) == 1) && (_dt > 0))
                                {
                                        Print("Elapsed time: ", TimeToString(TimeLocal() - _dt, TIME_SECONDS));
                                        _dt = 0;
                                }                               
                                return (mili < 0 ? 0 : mili);
                        };
#undef macroGetMin

お気づきかと思いますが、この1ミリ秒テストを実行する外側のループができました。この1ミリ秒を活用できるようにシステム内で適切な調整をおこなうのは非常に難しいため、この1ミリ秒を考慮に入れないほうがよいでしょう。

変更は1つだけです。結果は以下のビデオでご覧いただけます。



さらに速くしたい方は、結果をご覧ください。

それで十分だと思います。この時間の下に1分足が作成されました。完璧な時刻に到達するように調整し、システムに遅延を追加することができます。しかし、ここでのアイディアは模擬学習ができるようなシステムをつくるということですから、それはやりません。訓練や練習にはほぼ1分で大丈夫です。正確である必要はありません。


結論

これで、作成中のリプレイシステムの基本ができたので、次のポイントに進むことができます。MQL5言語に存在する設定と関数を使用するだけですべてが解決されていることがわかります。これは、MQL5言語が実際には多くの方が考えているよりもはるかに多くのことができることを証明しています。

ただし、私たちの取り組みはまだ始まったばかりです。やるべきことはまだたくさんあります。

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

添付されたファイル |
Replay.zip (10746.69 KB)
MQL5の圏論(第8回):モノイド MQL5の圏論(第8回):モノイド
MQL5における圏論の実装についての連載を続けます。今回は、ルールと単位元を含むことで、圏論を他のデータ分類法と一線を画す始域(集合)としてモノイドを紹介します。
リプレイシステムの開発—市場シミュレーション(第1回):最初の実験(I) リプレイシステムの開発—市場シミュレーション(第1回):最初の実験(I)
市場がしまっているときに研究したり、市場の状況をシミュレーションしたりできるシステムを作成してはどうでしょうか。ここで、このトピックを扱う新しい連載を開始します。
リプレイシステムの開発—市場シミュレーション(第3回):設定の調整(I) リプレイシステムの開発—市場シミュレーション(第3回):設定の調整(I)
まずは現状を明らかにすることから始めましょう。今やらなければ、すぐに問題になります。
MQL5:あなたもこの言語の達人になれます MQL5:あなたもこの言語の達人になれます
この記事は自己インタビューのようなもので、私がどのようにMQL5言語への第一歩を踏み出したかをお話しします。どうすれば優れたMQL5プログラマーになれるかをお見せして、この偉業を達成するために必要なベースについて説明します。唯一の前提条件は学ぶ意欲です。