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

リプレイシステムの開発—市場シミュレーション(第5回):プレビューの追加

MetaTrader 5 | 26 9月 2023, 10:13
254 0
Daniel Jose
Daniel Jose

はじめに

以前の「リプレイシステムの開発—市場シミュレーション(第2回):最初の実験(II)」では、現実的で利用しやすい方法で市場リプレイシステムを実装する方法を開発することに成功しました。この方法では、各1分足バーは1分以内に作成されます。まだ微調整が必要ですが、それについては後ほどお話しします。このステップは、市場リプレイをシミュレートできるシステムを考える上で非常に重要です。

このようなことはできましたが、私たちのリプレイシステムは、あるオペレーションシステムでは、探求し実行するには十分ではないようです。これでは、(数分、数時間、数日にわたる)過去のデータを事前に分析することができません。

事前分析とは、システムがあるポイントからバーを作成する瞬間より前のバーを持つ可能性を意味します。これら以前のバーがなければ、どの指標からも信頼できる情報を得ることはできません。

このように考えることができます。私たちは、特定の日に実行されたすべての取引ティックのファイルを持っています。しかし、このファイルの内容だけでは、どの指標からも本当に有益な情報を得ることはできません。例えば、JOE DI NAPOLIシステムで使用されている3期間の移動平均線を使用したとしても、少なくとも3本のバーが作成されるまではシグナルが発生しません。その後、移動平均がチャートに表示されます。実用化の観点からは、今日までこのシステムはまったく役に立たず、操作不能でした。

5分という時間枠の中で調査をおこなう状況を想像してみましょう。3期間移動平均線がチャートに表示されるまで15分待つ必要があります。有用なシグナルが現れるまで、あと数分かかるでしょう。つまり、システムをアップデートする必要があり、この記事の目的は、このアップデートの方法について論じることです。


詳細について

実装部分は非常にシンプルで、比較的短時間でできます。しかし、コードを一行でも書く前に、もうひとつ考えておかなければならないことがあります。

奇妙なことですが、考慮すべき問題は、リプレイで使用するチャートの期間と、各指標が有用な情報を生成するために必要な最小期間です。これらの質問は非常に重要であり、適切な回答が求められます。標準的なモデルはなく、市場リプレイを使ってどのように分析をおこなうかによります。

しかし、これらの質問に正しく答えることができれば、リプレイだけに限定されることはないでしょう。市場シミュレーターを作ることもできます。これははるかにより興味深いことです。しかし、リプレイと同じ動作原理に従います。リプレイとシミュレーターの唯一の違いは、スタディの作成に使用されるデータソース(TICKSまたはBARS)です。

リプレイの場合、データは現実のものであり、シミュレーターの場合、データは数学的モデルに基づいて、あるいは純粋に任意に、何らかの方法で合成されたものです。シミュレーターの場合、非常に興味深いことが起こります。多くの企業や経験豊富なトレーダーでさえ、数学的手法を使ってストレステストと呼ばれる特殊な研究をおこない、様々なシナリオの下で起こりうる極端な動きを予測するために市場の動きをシミュレートしています。しかし、この疑問については、まだ説明されていない部分があるので、後で考えることにします。

でも、将来的に、このリプレイシステムがもっと進んだ段階で、そのようなデータを「作る」方法を説明するので心配しないでください。しかし、今は、実際の市場データ、つまりリプレイの使い方に焦点を当てましょう。

リプレイに必要な前のバーの数を決定するには、かなり単純な計算をおこなう必要があります。


ここで、Nは必要なバーの数、Pは平均または指標が有用な情報を提供し始めるために使用するまたは必要とする最大の期間、Tはチャート期間の分数です。それを明確にするために、以下の例を見てみましょう。

例1 

200周期の移動平均線と5分足の時間枠をチャートに使用するとします。チャートに必要なバーの最小値はなんでしょうか。

n = ( 200 * 5 ) + 5

答えは1005になります。つまり、リプレイが始まる前に、200の平均を算出するためには、少なくとも1005本の1分足バーが必要です。つまり、使用するチャートの時間枠が5分以下です。したがって、平均値はリプレイの一番最初に表示されます。

例2 

調査には、21周期の移動平均、50周期の移動平均、14周期に設定したRSI、日中のVWAPを使いたいと思います。5分と15分の時間枠で調査する予定です。ただし、エントリやエグジット取引を確認するために、時折30分足の時間枠を確認します。リプレイを開始する前に最低限必要なバーは何本でしょうか。

経験の浅い人にとっては、この状況は複雑に見えるかもしれませんが、実際は非常にシンプルです。バーの最小数を計算する方法を考えてみましょう。まず、移動平均や指標で使われる最大の期間を見つける必要があります。この場合、VWAPは時間に依存しない指標であり、気にする必要がないため、値は50です。これが最初のポイントです。では、時間枠を確認してみましょう。この場合、使用できる3つの中で最も長い 30を使用する必要があります。たとえ30分足チャートが一度しか使用されないとしても、それは最小値として考慮されるべきです。すると、計算は次のようになります。

n = ( 50 * 30 ) + 30

つまり、実際のリプレイを開始する前に、少なくとも1530本の1分足バーが必要です。

これは多いように見えますが、実際にはこの値は、より極端なケースで必要とされる値にはまだほど遠いものです。しかし、調査や分析に必要だと多くの人が考えるよりも、はるかに大量のデータを常に持っているべきだという考え方があります。

さて、もうひとつ疑問があります。通常、B3(ブラジル証券取引所)では、取引セッション(取引期間)はいくつかの段階に分かれておこなわれ、資産クラスによっては異なる時間帯でおこなわれます。先物市場の取引は通常午前9時に開始し、午後6時に終了しますが、時期によってはこの時間が午後6時30分まで延長されます。株式市場は午前10時から午後6時まで開いています。その他の市場や資産には、それぞれ固有の営業時間があります。したがって、入札の前に、これらのパラメータをチェックする必要があります。その理由は、B3には取引ウィンドウがあるため、計算上の最小バー数を得るためには、数日間のデータをアップロードする必要があるということです。

重要な注意事項ここではバーの数を数えていますが、バーはティックの取引によって生成されること、つまり必要なデータ量がはるかに大きくなることを忘れてはなりません。混乱を避けるため、私は説明を簡単にするためにバーの本数を使うことを好みます。

そのため、別の種類の計算が必要になります。これによって、キャプチャする最低日数が決定されます。以下の式を使用します。


ここで、Nは日数、Fは取引ウィンドウが閉じる1時間、Kは取引ウィンドウが始まる1時間、Mはこのウィンドウの端数の分数です。わかりやすくするために、次の例を考えてみましょう。

ドル先物の取引が午前9時に開始され、午後6時半に終了しました。その日に1分足のバーは何本あったでしょうか。

n = (( 18 - 9 ) * 60 ) + 30

これは1分足のバー570本分に相当します。

通常、市場は午後6時に取引ウィンドウを閉じるため、この値は1分足で約540本となります。しかし、この数字は正確ではありません。なぜなら、日中、オークションのために取引が停止することがあるからです。これは物事を少し複雑にしますが、いずれにせよ、先物には最大の取引窓口があるため、1日の取引で最大570本の1分足バーを持つことになります。

これを知っていれば、必要なバーの数を、資産がレプリケーションで使用する日次バーの数で割ることができます。この1日の量は平均です。移動平均線と指標がチャート上に正しく表示されるために必要な最低購入日数が表示されます。


実装

私がここで意図しているのは、自分自身で解決策を構築することを学ぶ意欲を高めることです。繰り返しに聞こえるかもしれませんが、システムを少し変えるだけで、ずいぶん変わるものです。コーディングモデルを使わなければならないので、コードをできるだけ速く保つようにしましょう。以下は最初の変更点です。

#property service
#property copyright "Daniel Jose"
#property version   "1.00"
//+------------------------------------------------------------------+
#include <Market Replay\C_Replay.mqh>
//+------------------------------------------------------------------+
input string    user00 = "WIN$N_M1_202108030900_202108051754";  //File with bars ( Prev. )
input string    user01 = "WINQ21_202108060900_202108061759";    //File with ticks ( Replay )
//+------------------------------------------------------------------+
C_Replay        Replay;

どのファイルに以前のデータが含まれているかを指定する行を追加しました。将来的には変更するつもりですが、今のところはこのままです。コーディングの過程で混乱してほしくないからです。コードは徐々に変更されるので、そこから学ぶことができます。

1つのファイルしか指定できないということは、複数の日を使用する必要がある場合、MetaTrader 5に複数の日が組み込まれたファイルを強制的に生成させる必要があるということです。複雑そうに聞こえますが、実はよりシンプルで簡単です。


上の図では日付けが異なりますのでご注意ください。こうして、必要なバーをすべて固定します。合計1605本の1分足バーがキャプチャされていることを忘れてはなりません。これにより、広い動作範囲が得られます。計算のある例では、その数は少なかったです。

でも、なぜ1分足を使う必要があるのでしょうか。なぜ5分足や15分足のバーが使えないのでしょうか。

なぜなら、1分足はどの時間枠でも使えるからです。MetaTrader 5プラットフォームは異なる時間枠のバーを作成するので、調整の心配がなく非常に便利です。例えば、15分足でデータベースを使用する場合、他の時間枠を簡単に使用することはできません。これは、1分足バーを使用した場合にMetaTrader 5プラットフォームが提供するサポートに頼ることができないからです。このため、できれば常に1分足を使うべきです。

別の時間枠を使うことは可能ですが、ここではそれについては触れません。このことは、シミュレーションに関わる問題を扱うときにわかるでしょう。

しかし、複数の日をキャプチャしたくない場合でも、日を分けてランドをキャプチャし、ファイルを結合することができます。しかし、まだ絶望することはありません。こんなことをしなくてもいいように、別の解決策があります。ただし、この記事ではそれについて話すつもりはありません。コードの次のステップは以下の通りです。

void OnStart()
{
        ulong t1;
        int delay = 3;
        long id;
        u_Interprocess Info;
        bool bTest = false;
        
        Replay.InitSymbolReplay();
        Replay.LoadPrevBars(user00);
        if (!Replay.LoadTicksReplay(user01)) return;
        Print("Aguardando permissão para iniciar replay ...");

サービスが使用する変数をいくつか宣言します。その後、リプレイで使用する銘柄を決定するための呼び出しを作成します。この呼び出しが本当に最初のものであることが重要です。それ以外はすべて、作成された銘柄の内部で直接おこなわれるからです。

次に前のバーを読み込みます。ここには論理的な順序がありますが、前のバーを読み込むというこの機能には、興味深い詳細があります。とりあえず、続けてシステムがどのように起動するかを見てみましょう。

前の値があればそれを読み込んだ後、取引されたティックを読み込みます。この瞬間、リプレイの準備が本当に整います。すべてが正常であれば、システムがスタンバイ状態にあるというメッセージがターミナルに表示されるはずです。

        id = Replay.ViewReplay();
        while (!GlobalVariableCheck(def_GlobalVariableReplay)) Sleep(750);
        Print("Permission granted. Replay service can now be used...");
        t1 = GetTickCount64();

システムはこの状態で、リプレイテンプレートの読み込みを待ちます。リプレイアセットチャートも開いて表示します。そうなると、待機ループは終了します。

この場合、グローバルターミナル変数が作成され、ターミナルサービスとコントロール指標間のリンクが提供されるからです。この指標については以前に作成し、説明しました。「リプレイシステムの開発 - 市場シミュレーション(第3回):設定を調整する(I)」稿と「リプレイシステムの開発 - 市場シミュレーション(第4回):設定を調整する(II)」稿をご覧ください。

コントロール指標が読み込みされるとすぐにメッセージが表示されます。これでユーザーは、システム内で[再生]を押すことができるようになります。最後に、マシンのティック数を取得し、時間の経過を確認する方法を作成します。

その後、以前の記事で詳しく説明したループに入ります。しかし、これは導入の最初の部分に過ぎません。私たちにはまだ、特別な注意を払うべき細部があります。


新しいC_Replayクラス

奇妙なことに、リプレイのメンテナンスを担当するクラスに内部的な変更を加えなければなりませんでした。新しい変更点については、その内容や理由を理解してもらえるよう、徐々に詳しく説明していくつもりです。まずは変数から。変数は変更されました。

int      m_ReplayCount;
datetime m_dtPrevLoading;
long     m_IdReplay;
struct st00
{
        MqlTick Info[];
        int     nTicks;
}m_Ticks;

この新しい変数のセットは、ここでの仕事には十分でしょう。上のコードを見ると、セットが変わっていることに気づくでしょう。その結果、他のコンポーネントも変化しました。新しいリプレイ銘柄の初期化関数ができた。

void InitSymbolReplay(void)
{
        SymbolSelect(def_SymbolReplay, false);
        CustomSymbolDelete(def_SymbolReplay);
        CustomSymbolCreate(def_SymbolReplay, StringFormat("Custom\\%s", def_SymbolReplay), _Symbol);
        SymbolSelect(def_SymbolReplay, true);
}

気配値ウィンドウにリプレイ銘柄がある場合は、それを削除することから始めましょう。これは、この銘柄がオープンチャートの銘柄でない場合にのみ起こります。

そしてそれを削除し、カスタム銘柄として作成します。しかし、なぜこのような仕事をするのでしょうか。後でその理由がわかるでしょう。カスタム銘柄を作成したら、その銘柄を気配値ウィンドウに表示し、チャート上に配置します。この初期化機能は将来的に変更されるでしょうが、今のところはこれで十分です。変更を加える前に、まずシステムを機能させなければならないことを忘れないでください。

次に変更された機能は、取引されたティックの読み込みです。

#define macroRemoveSec(A) (A - (A % 60))
                bool LoadTicksReplay(const string szFileNameCSV)
                        {
                                int     file,
                                        old;
                                string  szInfo;
                                MqlTick tick;
                                
                                if ((file = FileOpen("Market Replay\\Ticks\\" + szFileNameCSV + ".csv", FILE_CSV | FILE_READ | FILE_ANSI)) != INVALID_HANDLE)
                                {
                                        Print("Loading replay ticks. Wait...");
                                        ArrayResize(m_Ticks.Info, def_MaxSizeArray);
                                        old = m_Ticks.nTicks = 0;
                                        for (int c0 = 0; c0 < 7; c0++) FileReadString(file);
                                        while ((!FileIsEnding(file)) && (m_Ticks.nTicks < def_MaxSizeArray))
                                        {
                                                szInfo = FileReadString(file) + " " + FileReadString(file);
                                                tick.time = macroRemoveSec(StringToTime(StringSubstr(szInfo, 0, 19)));
                                                tick.time_msc = (int)StringToInteger(StringSubstr(szInfo, 20, 3));
                                                tick.bid = StringToDouble(FileReadString(file));
                                                tick.ask = StringToDouble(FileReadString(file));
                                                tick.last = StringToDouble(FileReadString(file));
                                                tick.volume_real = StringToDouble(FileReadString(file));
                                                tick.flags = (uchar)StringToInteger(FileReadString(file));
                                                if ((m_Ticks.Info[old].last == tick.last) && (m_Ticks.Info[old].time == tick.time) && (m_Ticks.Info[old].time_msc == tick.time_msc))
                                                        m_Ticks.Info[old].volume_real += tick.volume_real;
                                                else
                                                {
                                                        m_Ticks.Info[m_Ticks.nTicks] = tick;
                                                        m_Ticks.nTicks += (tick.volume_real > 0.0 ? 1 : 0);
                                                        old = (m_Ticks.nTicks > 0 ? m_Ticks.nTicks - 1 : old);
                                                }
                                        }
                                }else
                                {
                                        Print("Tick file ", szFileNameCSV,".csv not found...");
                                        return false;
                                }
                                return true;
                        };
#undef macroRemoveSec

まず、完了した取引のティックを含むファイルを開いてみます。この段階には注意が必要で、ここではまだ何のチェックもしていません。したがって、正しいファイルを指定する際には注意が必要です。

ファイルはMQL5ディレクトリの指定されたフォルダに配置する必要があります。デフォルトはCSVなので、拡張子を指定する必要はありません。ファイルが見つかれば、読み込みを続行します。ファイルが見つからない場合、リプレイサービスは開始されず、失敗の理由を示すメッセージがメッセージボックスに表示されます。

取引されたティックファイルが見つかったらどうなるか見てみましょう。この場合、まずファイルの最初の行の内容をスキップします。その後、情報を読み込むループに入ります。最初にすることは、ティックが取引された日付と時刻をキャプチャし、データを一時的な場所に保存することです。そして、それぞれの値を正しい順番でキャプチャします。何も指定する必要はなく、CSVファイル形式があれば十分です。MQL5は正しく読み取るはずです。

前回の取引価格と取引時間が今回の取引価格と等しい場合、今回の取引量は前回の取引量に加算されます。つまり、これが起こるためには、両方のチェックが真でなければなりません。

お気づきのように、私はフラグの問題は気にしていません。他の条件が同じであれば、少なくとも今のところ、価格が動いていなかったため、りプレイには影響しません。これで、チェック条件が失敗した場合、別のティックを読んでいることがわかります。この場合、ティック行列に追加します。ティックがBIDまたはASKの調整である場合、その中にボリュームはありません。したがって、新しいポジションを追加することはなく、新しいティックが読み込まれると、このポジションは上書きされます。しかし、ある程度のボリュームがあれば、ポジションは上がります。

ファイルが終了するか、ティックリミットに達するまで、このループを続けます。ただし、まだテストはしていないので気をつけてください。システムはまだ非常にシンプルで、使用されるデータ量も大きくないため、多少の欠陥は無視できます。しかし、時間が経てば経つほど、この機能におけるチェックの量は増え、これまではそれほどコストがかからなかった問題を回避することができるようになります。

次の関数のコードを以下に示します。

bool LoadPrevBars(const string szFileNameCSV)
{
        int     file,
                iAdjust = 0;
        datetime dt = 0;
        MqlRates Rate[1];
                                
        if ((file = FileOpen("Market Replay\\Bars\\" + szFileNameCSV + ".csv", FILE_CSV | FILE_READ | FILE_ANSI)) != INVALID_HANDLE)
        {
                for (int c0 = 0; c0 < 9; c0++) FileReadString(file);
                Print("Loading preview bars to Replay. Wait ....");
                while (!FileIsEnding(file))
                {
                        Rate[0].time = StringToTime(FileReadString(file) + " " + FileReadString(file));
                        Rate[0].open = StringToDouble(FileReadString(file));
                        Rate[0].high = StringToDouble(FileReadString(file));
                        Rate[0].low = StringToDouble(FileReadString(file));
                        Rate[0].close = StringToDouble(FileReadString(file));
                        Rate[0].tick_volume = StringToInteger(FileReadString(file));
                        Rate[0].real_volume = StringToInteger(FileReadString(file));
                        Rate[0].spread = (int) StringToInteger(FileReadString(file));
                        iAdjust = ((dt != 0) && (iAdjust == 0) ? (int)(Rate[0].time - dt) : iAdjust);
                        dt = (dt == 0 ? Rate[0].time : dt);
                        CustomRatesUpdate(def_SymbolReplay, Rate, 1);
                }
                m_dtPrevLoading = Rate[0].time + iAdjust;
                FileClose(file);
        }else
        {
                Print("Failed to access the previous bars data file.");
                m_dtPrevLoading = 0;                                    
                return false;
        }
        return true;
}

上記のコードは、指定されたファイルによって必要とされる、または所有されるすべての過去のバーをリプレイにアップロードします。しかし、以前の機能では、後で使用するために情報を保存する必要がありましたが、ここでは違う方法で保存します。私たちは情報を読み、すぐにそれを銘柄に加えます。説明に従って、その仕組みを理解しましょう。

まず、バーでファイルを開いてみます。ティックで使ったのと同じコマンドがあることにご注意ください。しかし、場所は異なり、ファイルの内容も異なるが、情報にアクセスする方法はまったく同じです。ファイルを開くのに成功したら、まず最初の行をスキップします。

今度は、ファイルの最終行で終了するループに入ります。このファイルのサイズやデータ量に制限はありません。その結果、すべてのデータが読み込まれます。リードデータはOHCLモデルに従っているので、一時的な構造体に配置します。リプレイがどの時点で始まり、前のバーがどこで終わるのか、どうやって知ることができるのでしょうか。

この関数の外では、複雑になります。しかしここでは、前のバーがどこで終わり、どこでリプレイが始まるかを正確に示しています。まさにこの時点で、私たちはこの一時的なポジションを保存します。後に使用するためにです。ここで、バーの行を読み取るごとに、リプレイ銘柄に含まれるバーを即座に更新していることに注目してください。こうしておけば、後で余計な仕事が増えることはありません。こうして、前のバーが読み込まれたら、指標を追加することができます。

次の関数に移りましょう。とてもシンプルですが、とても重要な関数です。

long ViewReplay(void)
{
        m_IdReplay = ChartOpen(def_SymbolReplay, PERIOD_M1);                            
        ChartApplyTemplate(m_IdReplay, "Market Replay.tpl");
        ChartRedraw(m_IdReplay);
        return m_IdReplay;
}

標準の1分足時間枠でリプレイ銘柄のチャートを開きます。常に変更する必要がないように、別の標準時間枠を使うこともできます。

その後、リプレイ指標を含むテンプレートを読み込みます。指標がテンプレートになければ、サービスをプレイすることはできません。別のテンプレートを使用することも可能ですが、その場合はリプレイを開始するために、銘柄チャートにリプレイ指標を手動で追加しなければなりません。もし手動でやるのがお望みなら、それはそれで構いませんが、指標と一緒にテンプレートを使用することで、チャートの更新を強制的におこない、チャートインデックスを返して利用可能かどうかをチェックするため、システムにすぐにアクセスできるようになります。

システムをスタートさせるので、オフにする必要もあります。このために、あと1つの関数があります。

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

ここで重要なのは、行動を起こす順番が非常に重要だということです。順番を変えれば、期待通りにいかないかもしれません。したがって、まず銘柄チャートを閉じ、気配値ウィンドウから削除し、カスタム銘柄のリストから削除し、最後にターミナルからグローバル変数を削除します。その後、リプレイサービスは終了します。

また、以下の関数も一部変更されています。

inline int Event_OnTime(void)
{
        bool    bNew;
        int     mili, iPos;
        u_Interprocess Info;
        static MqlRates Rate[1];
        static datetime _dt = 0;
                                
        if (m_ReplayCount >= m_Ticks.nTicks) return -1;
        if (bNew = (_dt != m_Ticks.Info[m_ReplayCount].time))
        {
                _dt = m_Ticks.Info[m_ReplayCount].time;
                Rate[0].real_volume = 0;
                Rate[0].tick_volume = 0;
        }
        mili = (int) m_Ticks.Info[m_ReplayCount].time_msc;
        do
        {
                while (mili == m_Ticks.Info[m_ReplayCount].time_msc)
                {
                        Rate[0].close = m_Ticks.Info[m_ReplayCount].last;
                        Rate[0].open = (bNew ? Rate[0].close : Rate[0].open);
                        Rate[0].high = (bNew || (Rate[0].close > Rate[0].high) ? Rate[0].close : Rate[0].high);
                        Rate[0].low = (bNew || (Rate[0].close < Rate[0].low) ? Rate[0].close : Rate[0].low);
                        Rate[0].real_volume += (long) m_Ticks.Info[m_ReplayCount].volume_real;
                        bNew = false;
                        m_ReplayCount++;
                }
                mili++;
        }while (mili == m_Ticks.Info[m_ReplayCount].time_msc);
        Rate[0].time = m_Ticks.Info[m_ReplayCount].time;
        CustomRatesUpdate(def_SymbolReplay, Rate, 1);
        iPos = (int)((m_ReplayCount * def_MaxPosSlider) / m_Ticks.nTicks);
        GlobalVariableGet(def_GlobalVariableReplay, Info.Value);
        if (Info.s_Infos.iPosShift != iPos)
        {
                Info.s_Infos.iPosShift = iPos;
                GlobalVariableSet(def_GlobalVariableReplay, Info.Value);
        }
        return (int)(m_Ticks.Info[m_ReplayCount].time_msc < mili ? m_Ticks.Info[m_ReplayCount].time_msc + (1000 - mili) : m_Ticks.Info[m_ReplayCount].time_msc - mili);
}

上記の関数は1分足バーを作成し、リプレイ銘柄に追加しますが、しかし、ここで注目すべきことがあります。なぜリプレイには表示されるものとされないものがあるのでしょうか。

ティックボリュームが常にゼロであることにお気づきでしょうか。なぜでしょうか。これを理解するには、「実際のティックと生成されたティック」のドキュメントをチェックする必要があります。説明を読んでも理解できない、または、なぜティックの量が常にゼロなのかを理解できない場合、重要な事実を無視していることになります。なので、なぜこの値が常にゼロになるのかを理解してみましょう。

取引されたティックのデータを読み込むときは、実際のティックを読み込みます。例えば、注文の数量が10に等しい場合、10ティックが使用されたのではなく、最低必要ロット数である10の実数量で取引がおこなわれたことを意味します。1ティックの取引量を生成することは不可能です。しかし、ティックボリュームがある状況が1つあります。それは、注文が開かれ、閉じられた結果、2ティックのボリュームが発生した場合です。しかし実際には、最小ティック量は3です。実際の取引ティックを使用しているため、提示された値に調整を加える必要があります。現時点では、まだこの計算をリプレイシステムに追加していないので、実際に表示されるのは取引された実際の値だけです。

一見同じように見えるが、ティックボリュームが何回動いたかを示すのに対し、実際のボリュームは実際に何回取引がおこなわれたかを示すので、これには注意が必要です。

今のところ、これが非常に複雑で理解するのが難しいように思われることは理解していますが、次の記事で外国為替市場に入り、この市場でリプレイとシミュレーションをおこなうう方法について説明すると、すべてがより明確になります。したがって、今すぐすべてを完全に理解しようとしてはいけません。時間が経つにつれて、理解できるようになります。

この記事で取り上げる最後の関数を以下に示します。

int AdjustPositionReplay()
{
        u_Interprocess Info;
        MqlRates Rate[1];
        int iPos = (int)((m_ReplayCount * def_MaxPosSlider * 1.0) / m_Ticks.nTicks);
                                
        Info.Value = GlobalVariableGet(def_GlobalVariableReplay);
        if (Info.s_Infos.iPosShift == iPos) return 0;
        iPos = (int)(m_Ticks.nTicks * ((Info.s_Infos.iPosShift * 1.0) / def_MaxPosSlider));
        if (iPos < m_ReplayCount)
        {
                CustomRatesDelete(def_SymbolReplay, m_dtPrevLoading, LONG_MAX);
                m_ReplayCount = 0;
                if (m_dtPrevLoading == 0)
                {
                        Rate[0].close = Rate[0].open = Rate[0].high = Rate[0].low = m_Ticks.Info[m_ReplayCount].last;
                        Rate[0].tick_volume = 0;
                        Rate[0].time = m_Ticks.Info[m_ReplayCount].time - 60;
                        CustomRatesUpdate(def_SymbolReplay, Rate, 1);
                }
        };
        for (iPos = (iPos > 0 ? iPos - 1 : 0); m_ReplayCount < iPos; m_ReplayCount++) Event_OnTime();
        return Event_OnTime();
}

この関数では、1つだけ前の関数と異なる点があり、この点によって売買のティックの挿入を開始することができます。したがって、このポイントより上はすべて実際の取引ティックのリプレイとみなされ、このポイントより下は以前のバーの値となります。もしこの時点でゼロに等しい値しか知らせなければ、リプレイ銘柄に記録された情報はすべて削除され、1分足データのバーを読み直して前のバーの値を取得しなければならなくなります。それは冗長で、より困難な仕事です。そのため、閾値のポイントを示すだけで、MetaTrader 5は実際のティックによって作成されたバーを削除し、物事がより簡単になります。


結論

関数内のその他すべてについては、「リプレイシステムの開発 — 市場シミュレーション(第4回):設定の調整(II)」稿ですでに説明しているので、ここでは詳しく触れません。

下のビデオで、このシステムが実際に動いているところを見ることができます。ビデオでは、リプレイシステムにさまざまな指標を追加できることがわかります。




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

添付されたファイル |
Market_Replay.zip (6102.2 KB)
リプレイシステムの開発—市場シミュレーション(第6回):最初の改善(I) リプレイシステムの開発—市場シミュレーション(第6回):最初の改善(I)
この記事では、システム全体の安定化を開始します。安定化がなければ次のステップに進むことができない可能性があります。
MQL5のプログラム構造について学ぶ必要があるすべて MQL5のプログラム構造について学ぶ必要があるすべて
どのようなプログラミング言語でも、プログラムには特定の構造があります。この記事では、MetaTrader 5で実行可能なMQL5取引システムや取引ツールを作成する際に非常に役立つMQL5プログラム構造のすべての部分のプログラミングの基礎を理解することにより、MQL5プログラム構造の重要な部分を学びます。
MQL5ストラテジーテスターを理解し、効果的に活用する MQL5ストラテジーテスターを理解し、効果的に活用する
MQL5のプログラマーや開発者は、重要で貴重なツールをマスターする必要があります。ストラテジーテスターはこれらのツールのうちの1つです。この記事は、MQL5のストラテジーテスターを理解し、使用するための実践的なガイドです。
DoEasy - コントロール(第32部):水平スクロールバー、マウスホイールスクロール DoEasy - コントロール(第32部):水平スクロールバー、マウスホイールスクロール
この記事では、水平スクロールバーオブジェクト機能の開発を完成します。また、スクロールバーのスライダーを動かしたり、マウスホイールを回転させたりしてコンテナの内容をスクロールできるようにするほか、MQL5の新しい注文実行ポリシーや新しいランタイムエラーコードを考慮したライブラリへの追加もおこないます。