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

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

MetaTrader 5 | 19 12月 2023, 08:23
266 0
Daniel Jose
Daniel Jose

はじめに

ここまでは、前回「リプレイシステムの開発 - 市場シミュレーション(第10回):リプレイで実データのみを使用する」では、実データ(実際に取引されたチケット)のみを使用しました。これにより、情報収集について気にする必要がなくなり、動きが正確かつ簡単に作成できるようになります。取引されたチケットを1分足バーに変換するだけで、あとはMetaTrader 5プラットフォームがやってくれます。

しかし、私たちは今、別の、より困難な課題に直面しています。


計画

特に常に1分の長さであるバー(理由は後ほど説明します)をチケットに変換するので、多くの人は、この計画は簡単だと思うかもしれません。しかし、シミュレーションは一見したところよりずっと複雑です。主な問題は、1分足バーを作成するためのチケットの実際の動作を明確に理解していないことです。バーとそれに関するいくつかの情報があるだけで、バーがどのように形成されたかは知りません。難易度が最も低い1分バーを使用します。本物に非常に近い複雑な動きを作ることができれば、本物に非常に近いものを再現することができます。

通常、市場ではジグザグ型の動きが見られるため、この詳細はそれほど重要ではないように思われるかもしれません。動きの複雑さに関係なく、すべてはOHCLのポイント間でジグザグを作ることに行き着きます。バーの始点から始まり、この内側のジグザグを作るために9回以上の動きをします。常にバーの終値で終了し、次のバーで処理を繰り返します。MetaTrader 5のストラテジーテスターも同じロジックを使用しています。詳しくは、「実際ティックと生成ティック:アルゴリズム取引」を参照してください。まずはこの戦略から始めましょう。私たちの目的にとっては理想的ではありませんが、より適切なアプローチを開発するための出発点となるでしょう。

テスター戦略はリプレイ/シミュレーションシステムには最適ではないと思います。取引ストラテジーテスターでは時間の問題は最重要ではないためです。つまり、1分バーを、その長さが実際に1分であるように作成し、表現する必要はありません。それどころか、現実にはこの時間とは一致しないのだから、なおさら好都合です。もし一致したら、戦略のテストは不可能になります。各バーが異なる実際の時間を表している場合、数日、あるいは数年にわたるバーを使ってテストを実行することを想像してみてください。これは不可能な仕事です。リプレイ/シミュレーションシステムでは、私たちは異なるダイナミズムを求めています。1分間隔で1分バーを作成し、できるだけそれに近づけたいのです。


地固めをする

私たちが注目するのは、リプレイ/シミュレーションサービスのコードだけです。今は他の面を心配する必要はありません。C_Replayクラスのコードを修正し始めて、すでに開発しテストしたものをできるだけ最適化することにします。次は、クラスで最初に登場するプロシージャーです。

inline bool CheckFileIsBar(int &file, const string szFileName)
                        {
                                string  szInfo = "";
                                bool    bRet;
                                
                                for (int c0 = 0; (c0 < 9) && (!FileIsEnding(file)); c0++) szInfo += FileReadString(file);
                                if ((bRet = (szInfo == def_Header_Bar)) == false)
                                {
                                        Print("File ", szFileName, ".csv is not a file with bars.");
                                        FileClose(file);
                                }
                                
                                return bRet;
                        }



ここでの目的は、指定されたファイルがプレビューバーのファイルであるか否かを判定するテストを、バーの読み取り機能から取り除くことです。これは、バーファイルが私たちのものかどうかを判断するために同じセットを使用することが重要な場合に、コードの繰り返しを避けるために必要です。この場合、これらのバーはプレビューバーとして使用されず、取引システムで使用するための模擬チケットに変換されます。これに基づいて、もう1つの関数を紹介します。

inline void FileReadBars(int &file, MqlRates &rate[])
                        {
                                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));
                        }



指定されたファイルのバーから一行ずつデータを読み込みます。このコードを理解するのに苦労することはないでしょう。この準備段階を続けて、もう1つの関数を紹介します。

inline bool OpenFileBars(int &file, const string szFileName)
                        {
                                if ((file = FileOpen("Market Replay\\Bars\\" + szFileName + ".csv", FILE_CSV | FILE_READ | FILE_ANSI)) != INVALID_HANDLE)
                                {
                                        if (!CheckFileIsBar(file, szFileName))
                                                return false;
                                        return true;
                                }
                                Print("Falha ao acessar ", szFileName, ".csv de barras.");
                                
                                return false;
                        }



システムを完全に一元化し、バーをプレビューバーとして使用する場合と、シミュレートされてプレゼンテーション用のチケットに変換されるバーとして使用する場合の両方で、バーへの標準アクセスを提供するようになりました。そのため、従来のプレビューバーを読み込む関数も変更する必要があり、以下のようになりました。

bool LoadPrevBars(const string szFileNameCSV)
        {
                int     file,
                        iAdjust = 0;
                datetime dt = 0;
                MqlRates Rate[1];
                                
                if (OpenFileBars(file, szFileNameCSV))
                {
                        Print("Loading preview bars for Replay. Please wait....");
                        while ((!FileIsEnding(file)) && (!_StopFlag))
                        {
                                FileReadBars(file, Rate);
                                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);
                        
                        return (!_StopFlag);
                }
                m_dtPrevLoading = 0;
                        
                return false;
        }



呼び出し数は増えましたが、このダウンロード関数の仕組みは変わっていません。前の関数から、新しい点で使用する部分を抽出することで、すべてのコードがすでにテスト済みであるため、より安全性が高まります。こうすることで、新しい関数だけを心配すればよくなります。地固めができたので、構成ファイルに新しい追加要素を実装する必要があります。この関数は、チケットの観点からどのバーファイルをシミュレートすべきかを決定することを目的としています。そのためには、新しい定義を追加する必要があります。

#define def_STR_FilesBar        "[BARS]"
#define def_STR_FilesTicks      "[TICKS]"
#define def_STR_TicksToBars     "[TICKS->BARS]"
#define def_STR_BarsToTicks     "[BARS->TICKS]"



これによって簡単なテストができるようになります。これが、まさにシミュレーションに取りかかるのに必要なものです。

                bool SetSymbolReplay(const string szFileConfig)
                        {
#define macroERROR(MSG) { FileClose(file); MessageBox((MSG != "" ? MSG : StringFormat("An error occurred in line %d", iLine)), "Market Replay", MB_OK); return false; }
                                int     file,
                                        iLine;
                                string  szInfo;
                                char    iStage;
                                
                                if ((file = FileOpen("Market Replay\\" + szFileConfig, FILE_CSV | FILE_READ | FILE_ANSI)) == INVALID_HANDLE)
                                {
                                        MessageBox("Failed to open the\nconfiguration file.", "Market Replay", MB_OK);
                                        return false;
                                }
                                Print("Loading data for replay. Please wait....");
                                ArrayResize(m_Ticks.Rate, def_BarsDiary);
                                m_Ticks.nRate = -1;
                                m_Ticks.Rate[0].time = 0;
                                iStage = 0;
                                iLine = 1;
                                while ((!FileIsEnding(file)) && (!_StopFlag))
                                {
                                        switch (GetDefinition(FileReadString(file), szInfo))
                                        {
                                                case Transcription_DEFINE:
                                                        if (szInfo == def_STR_FilesBar) iStage = 1; else
                                                        if (szInfo == def_STR_FilesTicks) iStage = 2; else
                                                        if (szInfo == def_STR_TicksToBars) iStage = 3; else
                                                        if (szInfo == def_STR_BarsToTicks) iStage = 4; else
                                                                macroERROR(StringFormat("%s is not recognized in the system\nin line %d.", szInfo, iLine));
                                                        break;
                                                case Transcription_INFO:
                                                        if (szInfo != "") switch (iStage)
                                                        {
                                                                case 0:
                                                                        macroERROR(StringFormat("Command not recognized in line %d\nof the configuration file.", iLine));
                                                                        break;
                                                                case 1:
                                                                        if (!LoadPrevBars(szInfo)) macroERROR("");
                                                                        break;
                                                                case 2:
                                                                        if (!LoadTicksReplay(szInfo)) macroERROR("");
                                                                        break;
                                                                case 3:
                                                                        if (!LoadTicksReplay(szInfo, false)) macroERROR("");
                                                                        break;
                                                                case 4:
                                                                        if (!LoadBarsToTicksReplay(szInfo)) macroERROR("");
                                                                        break;
                                                        }
                                                        break;
                                        };
                                        iLine++;
                                }
                                FileClose(file);

                                return (!_StopFlag);
#undef macroERROR
                        }



コードに新しい関数を追加するのがいかに簡単かをご覧ください。このテストを追加するという事実そのものが、私たちにさらに多くの側面を分析する機会を与えてくれます。ここからは、他の状況で観察されたのと同じタイプの行動を見ることができます。この段階で定義されたファイルは、チケットに変換されるバーファイルとして扱われ、この呼び出しを使って変換されます。

繰り返しになりますが、可能な限り、不必要なコードを書くのは避けるべきです。以前にテストしたコードを再利用することが望ましいです。これこそが、これまで私たちがやってきた方法です。しかし、これはまた別の記事にするとして、近いうちに新しいトピックを立ち上げる予定です。ただし、その前に、ある本質的な点を理解することが重要です。


導入前に考えておきたいこと

変換システムを導入する前に、考慮すべき点が1つあります。バーの構成は本当に何種類存在するのでしょうか。多くの人が多くの種類があると信じていますが、実際には、すべての可能な構成をたった4つに絞り込むことができます。下図に示します。


なぜこれが私たちに関係があるのでしょうか。どれだけのオプションを実装しなければならないかを決定するためです。この4つの選択肢しかないという事実を理解していないと、選択肢を見落としたり、逆に必要以上に多くの種類のケースを作ってしまう危険性があります。もう一度強調しておきたいのは、バーを再現する完璧なシミュレートモデルを作る方法はないということです。達成できるのは、この特定のバーの形成につながった実際の動きを多かれ少なかれ正確に推定することくらいです。

2つ目のタイプについては詳細があり、画像のようにバー本体を上部にのみ配置することができます。しかし、この事実は私たちが実装するシステムには影響しません。同様に、バーが売り取引を表すか買い取引を表すかは問題ではなく、実装は同じままです。唯一のニュアンスは、最初にどの方向に進むかということです。こうすることで、必要なケースの数を最小限に抑えることができます。しかし、図に示されたケース以外に、もう1つ理解しておかなければならないことがあります。本当に最低何個のチケットを作る必要があるのでしょうか。複雑だと思う方もいらっしゃるでしょうが、リプレイ/シミュレーションシステムを導入する人、あるいはストラテジーテスターにとっては、すべてが理解できるはずです。

考えてみましょう。どのようなシステムでも、1個のチケットだけを使うのは現実的ではありません。なぜなら、これは売買取引を表すだけで、動きそのものを表すわけではないからです。したがって、この可能性は除外できます。オープニングポイントとクロージングポイントを象徴するチケットの、少なくとも2個は思いつくでしょう。これは論理的に見えますが、バーを開くのに1ティック、閉じるのに2ティックを生成するだけなので、実際の動きもありません。 

メモ:私たちはチケットだけを作ろうとしているわけではなく、実際にはバーをシミュレートする動きを作りたいのです。これらのトピックについては今後の記事でさらに掘り下げていきますが、まずは基本的なシステムを開発する必要があります。

したがって、チケットの最低数は3個となります。したがって、1分間の棒グラフは、前の図で観察された構成の一部を反映している可能性があります。ただし、少なくとも3枚のチケットが存在するということは、価格が正確に1ティック上がって1ティック下がった、あるいは3ティック上下したということではないことに注意してください。バー作成時の流動性不足により、この1ティックとは異なる動きをする可能性があります。

重要:読者はここで使われているいくつかの用語に戸惑うかもしれません。誤解を避けるために、この点を明確にしておきます。チケットという用語を使うとき、私は実際には取引イベント、つまり指定された価格で資産を売買するイベントを意味します。ティックは、取引価格に対する最小の乖離という意味です。この違いを理解するためには、次のことを考える必要があります。株式市場の1ティックは0.01ポイント、ドル先物の1ティックは0.5ポイント、指数先物の1ティックは5ポイントです。

これは、動きのシミュレーションがもはや正確な現実を反映したものではなく、むしろ理想化された動きであるため、物事を難しくする側面もありますが、多くの場合、1分足バーを使ったシミュレーションシステムは、市場で実際に起こったこと、あるいは起こっていることを正確に再現するものではないということを覚えておくために、この事実を言及することは重要です。したがって、常に最短の時間枠を使用する方が良いでしょう。この時間枠は1分なので、常にこれを使うべきです。

もしかすると、読者は、チケットを作る方法としてバーを使うことの本当の問題をまだ理解していないのかもしれません。実際の市場で、1分足バーがある価格で開くとします。流動性がないため、価格が3ティック跳ね上がり、しばらくして1ティック下がり、その最後のポジションで決済すると、最終的な状況は次のようになります。

上の画像がわかりにくくて、正しく理解できなかったかもしれません。これは以下の情報を表しています。左側では、実際の動きをティック単位で見ています。小さな横棒は各ティックを表します。丸印は、その資産が実際にチケットとチケットの間に止まった価格を表しています。緑の線は価格の急上昇を示します。あるティックでは取引がなかったが、OHCLの値を分析すると、明らかなティックスパイクが見られないケースがあることに注意してください。バーローソク足だけを使って動きをシミュレートすると、次の画像のようになります。

青い線はシミュレーションの動きを表しています。この場合、ライブ取引中に実際に何が起こったかにかかわらず、すべてのティックを調べます。モデリングは実際のデータを使うこととは違うということを常に念頭に置いておいてください。どんなに複雑なモデリングシステムでも、現実を正確に反映することはできません。


基本変換システムの実装

すでに述べたように、最初にすべきことは価格ティックの大きさを決めることです。そのためには、構成ファイルにいくつかの追加要素を含める必要があります。これらはC_Replayクラスによって認識されなければなりません。したがって、このクラスにいくつかの定義と追加コードを追加する必要があります。まず、次の行から始めましょう。

#define def_STR_FilesBar        "[BARS]"
#define def_STR_FilesTicks      "[TICKS]"
#define def_STR_TicksToBars     "[TICKS->BARS]"
#define def_STR_BarsToTicks     "[BARS->TICKS]"
#define def_STR_ConfigSymbol    "[CONFIG]"
#define def_STR_PointsPerTicks  "POINTSPERTICK"
#define def_Header_Bar          "<DATE><TIME><OPEN><HIGH><LOW><CLOSE><TICKVOL><VOL><SPREAD>"
#define def_Header_Ticks        "<DATE><TIME><BID><ASK><LAST><VOLUME><FLAGS>"
#define def_BarsDiary           540



この行は、これから処理する構成データを認識する文字列を定義します。また、これから定義していく構成の最初のものも提供します。この場合も、これらの設定を解釈して適用できるように、システムに数行のコードを追加する必要がありますが、追加は比較的簡単です。次に、何が必要かを見てみましょう。

                bool SetSymbolReplay(const string szFileConfig)
                        {
#define macroERROR(MSG) { FileClose(file); MessageBox((MSG != "" ? MSG : StringFormat("An error occurred in line %d", iLine)), "Market Replay", MB_OK); return false; }
                                int     file,
                                        iLine;
                                string  szInfo;
                                char    iStage;
                                
                                if ((file = FileOpen("Market Replay\\" + szFileConfig, FILE_CSV | FILE_READ | FILE_ANSI)) == INVALID_HANDLE)
                                {
                                        MessageBox("Failed to open the\nconfiguration file.", "Market Replay", MB_OK);
                                        return false;
                                }
                                Print("Loading data for replay. Please wait....");
                                ArrayResize(m_Ticks.Rate, def_BarsDiary);
                                m_Ticks.nRate = -1;
                                m_Ticks.Rate[0].time = 0;
                                iStage = 0;
                                iLine = 1;
                                while ((!FileIsEnding(file)) && (!_StopFlag))
                                {
                                        switch (GetDefinition(FileReadString(file), szInfo))
                                        {
                                                case Transcription_DEFINE:
                                                        if (szInfo == def_STR_FilesBar) iStage = 1; else
                                                        if (szInfo == def_STR_FilesTicks) iStage = 2; else
                                                        if (szInfo == def_STR_TicksToBars) iStage = 3; else
                                                        if (szInfo == def_STR_BarsToTicks) iStage = 4; else
                                                        if (szInfo == def_STR_ConfigSymbol) iStage = 5; else
                                                                macroERROR(StringFormat("%s is not recognized in the system\nin line %d.", szInfo, iLine));
                                                        break;
                                                case Transcription_INFO:
                                                        if (szInfo != "") switch (iStage)
                                                        {
                                                                case 0:
                                                                        macroERROR(StringFormat("Command not recognized in line %d\nof the configuration file.", iLine));
                                                                        break;
                                                                case 1:
                                                                        if (!LoadPrevBars(szInfo)) macroERROR("");
                                                                        break;
                                                                case 2:
                                                                        if (!LoadTicksReplay(szInfo)) macroERROR("");
                                                                        break;
                                                                case 3:
                                                                        if (!LoadTicksReplay(szInfo, false)) macroERROR("");
                                                                        break;
                                                                case 4:
                                                                        if (!LoadBarsToTicksReplay(szInfo)) macroERROR("");
                                                                        break;
                                                                case 5:
                                                                        if (!Configs(szInfo)) macroERROR("");
                                                                        break;
                                                        }
                                                        break;
                                        };
                                        iLine++;
                                }
                                FileClose(file);
                                
                                return (!_StopFlag);
#undef macroERROR
                        }



ここでは、次の行からのすべての情報が、分析の5段階目でシステムによって処理されることを示しています。ステップ5で情報入力がトリガーされ、キャプチャされると、プロシージャが呼び出されます。このプロシージャの説明を簡単にするため、以下では第5ステージで呼び出される手順について説明します。

inline bool Configs(const string szInfo)
                        {
                                string szRet[];
                                
                                if (StringSplit(szInfo, '=', szRet) == 2)
                                {
                                        StringTrimRight(szRet[0]);
                                        StringTrimLeft(szRet[1]);
                                        if (szRet[0] == def_STR_PointsPerTicks) m_PointsPerTick = StringToDouble(szRet[1]); else
                                        {
                                                Print("Variable >>", szRet[0], "<< not defined.");
                                                return false;
                                        }
                                        return true;
                                }
                                Print("Definition of configuration >>", szInfo, "<< is invalid.");
                                return false;
                        }



まず、内部で繰り返される変数の名前と使用される値を分離します。これは、ユーザーがリプレイ/シミュレーション設定ファイルで定義したものです。この操作の結果、2つの情報が得られます。1つ目は定義されている変数の名前で、2つ目はその値です。この値は変数によって型が異なるかもしれませんが、ユーザーには完全に透過的です。型が文字列かダブルか整数かを気にする必要はありません。タイプはコード内でここで選択されます。

このデータを使用する前に、基本情報に関係のないものはすべて削除しなければなりません。一般的にこの要素は、ユーザーが構成ファイルの読み書きを容易にするために使用できる、ある種の内部フォーマットです。理解されずに適用されたものはすべてエラーとみなされます。これによってfalseが返され、サービスが終了します。その後、もちろん、理由はMetaTrader 5プラットフォームで報告されます。

つまり、設定ファイルは以下のようになります。これは単なる設定ファイルの例であることを忘れないでください。

[Config]
PointsPerTick = 5

[Bars]
WIN$N_M1_202112060900_202112061824
WIN$N_M1_202112070900_202112071824

[ Ticks -> Bars]

[Ticks]

[ Bars -> Ticks ]
WIN$N_M1_202112080900_202112081824

#End of the configuration file...



続行する前に、システムの最終セットアップをおこなう必要があります。それは極めて重要です。バーメカニズムがシミュレートされたチケットモデルに変換されることを確認するために、システムをデバッグする必要があります。必要な変更は以下のコードで強調表示されています。

inline int Event_OnTime(void)
        {
                bool    bNew;
                int     mili, iPos;
                u_Interprocess Info;
                static MqlRates Rate[1];
                static datetime _dt = 0;
                datetime tmpDT = macroRemoveSec(m_Ticks.Info[m_ReplayCount].time);
                                
                if (m_ReplayCount >= m_Ticks.nTicks) return -1;
                if (bNew = (_dt != tmpDT))
                {
                        _dt = tmpDT;
                        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 = _dt;
                CustomRatesUpdate(def_SymbolReplay, Rate, 1);
                iPos = (int)((m_ReplayCount * def_MaxPosSlider) / m_Ticks.nTicks);
                GlobalVariableGet(def_GlobalVariableReplay, Info.u_Value.df_Value);
                if (Info.s_Infos.iPosShift != iPos)
                {
                        Info.s_Infos.iPosShift = (ushort) iPos;
                        GlobalVariableSet(def_GlobalVariableReplay, Info.u_Value.df_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);
        }



ここで興味深い点があります。チャート上でどのように動きが起こるかを見るために、視覚化をオフにすることができます。つまり、シミュレートされたチケットに存在する2番目の値を除外する関数を無効にします。この場合、模擬チケットはそれぞれ棒グラフで表されます。

これは少し変わっているようですが、ログファイルに書き込むことなく、何が起こっているのかを理解するのに役立つでしょう。このエントリでは、秒に対応する値を削除しなければなりません。もしそうしなければ、すべてのシミュレートされたチケットがバーを生成することになります。1分足があたかも本物のように見えるようにしたいのです。


いよいよ実装段階に入ります。

コードに進む前に、今回紹介する動きを見てみましょう。将来的には、もっと複雑にする方法を読者に紹介するつもりだが、まずは正しく機能することが重要です。以下に、この動きがどのように起こるかを示します。

単純に見えるかもしれませんが、ここでバーを模擬的なチケットの動きに変換します。動きは決して直線的ではなく、ジグザグのようなものなので、このような3つの揺れを持つ動きを作ることにしました。お望みなら、この数字を増やすこともできます。今後の記事では、この基本的なテクニックをもっと複雑なものに変える方法を紹介します。

さて、この動きがどうなるかがわかったところで、コードに移りましょう。次のコードは、変換システムの作成に必要な最初の関数です。

                bool LoadBarsToTicksReplay(const string szFileNameCSV)
                        {
//#define DEBUG_SERVICE_CONVERT
                                int file, max;
                                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(m_Ticks.Rate, def_BarsDiary);
                                        ArrayResize(tick, def_MaxSizeArray);
                                        while ((!FileIsEnding(file)) && (!_StopFlag))
                                        {
                                                FileReadBars(file, rate);
                                                max = SimuleBarToTicks(rate[0], tick);
                                                for (int c0 = 0; c0 <= max; c0++)
                                                {
                                                        ArrayResize(m_Ticks.Info, (m_Ticks.nTicks + 1), def_MaxSizeArray);
                                                        m_Ticks.Info[m_Ticks.nTicks++] = tick[c0];
                                                }
                                        }
                                        FileClose(file);
                                        ArrayFree(tick);
                                        
#ifdef DEBUG_SERVICE_CONVERT
        file = FileOpen("Infos.txt", FILE_ANSI | FILE_WRITE);
        for (long c0 = 0; c0 < m_Ticks.nTicks; c0++)
                FileWriteString(file, StringFormat("%s.%03d %f --> %f\n", TimeToString(m_Ticks.Info[c0].time, TIME_DATE | TIME_SECONDS), m_Ticks.Info[c0].time_msc, m_Ticks.Info[c0].last, m_Ticks.Info[c0].volume_real));
        FileClose(file);
#endif

                                        return (!_StopFlag);
                                }
                                
                                return false;
                        }



単純に見えますが、この関数はシミュレーションを作成する最初のステップです。これを見れば、システムがどのようにシミュレーションをおこなうのか、少なくとも少しは理解できるでしょう。ストラテジーテスターとは異なり、分足のシミュレーションが画面に表示されるまでに約1分かかることに注意してください。これは意図的におこなわれています。したがって、例えばストラテジーのテストが目的であれば、MetaTrader 5プラットフォームで利用可能なツールを使用し、このツール(ここで示す開発版)は手動の訓練とテストにのみ使用することをお勧めします。

将来的には存在しなくなる側面もありますが、現時点では極めて重要です。これは現在議論している定義のことで、シミュレートされたチケットデータのファイルを生成し、シミュレートされたシステムに存在する複雑さのレベルを分析することができます。作成されたファイルには、分析に必要なデータのみが含まれ、追加情報や不要な情報は含まれないことに注意することが重要です。

残りの関数は、以前から存在していたコードなので、かなり直感的に理解できます。これで、呼び出しができたので、後で詳しく見ます。チケットをシミュレートするこの呼び出しの後、データベースにチケットを保存するための小さなループが実行されます。分かりやすいし、それ以上の説明も必要ありません。

inline int SimuleBarToTicks(const MqlRates &rate, MqlTick &tick[])
                        {
                                int t0 = 0;
                                long v0, v1, v2, msc;
                                                                
                                m_Ticks.Rate[++m_Ticks.nRate] = rate;
                                Pivot(rate.open, rate.low, t0, tick);
                                Pivot(rate.low, rate.high, t0, tick);
                                Pivot(rate.high, rate.close, t0, tick, true);
                                v0 = (long)(rate.real_volume / (t0 + 1));
                                v1 = 0;
                                msc = 5;
                                v2 = ((60000 - msc) / (t0 + 1));
                                for (int c0 = 0; c0 <= t0; c0++, v1 += v0)
                                {
                                        tick[c0].volume_real = (v0 * 1.0);
                                        tick[c0].time = rate.time + (datetime)(msc / 1000);
                                        tick[c0].time_msc = msc % 1000;
                                        msc += v2;
                                }
                                tick[t0].volume_real = ((rate.real_volume - v1) * 1.0);
                                
                                return t0;
                        }



上記の特徴によって、タスクが少し難しくなり、それゆえより面白くなります。同時に、必要な構造をすべて、より組織的で管理しやすい形でまとめることができます。実際、この関数の複雑さを軽減するために、3回呼び出される別のプロシージャを作りました。実際ティックと生成ティックというドキュメントを読めば、その中でシステムが3回ではなく4回呼び出されていることに気づくでしょう。しかし、前述の通り、Pivotプロシージャに追加の呼び出しを追加することなく、このシステムの複雑さを増す方法を紹介しましょう。

前述した手順では、Pivotを3回呼び出した後、どのように分割がおこなわれたかに応じて、シミュレーションされたチケットの数が変化します。このおかげで、1分足の棒グラフデータに小さな修正を加えることができるようになり、元のデータを何らかの形で利用できるようになりました。最初のステップは、実のボリュームの単純な分割を行い、それぞれのシミュレーションされたティックが総ボリュームの何分の一かを含むようにすることです。その後、各シミュレーションのタイミングを微調整します。使用する割合を定義したら、ループに入り、各分数が適切なチケットに格納されるようにすることができます。現時点では、システムが実際に機能しなければならないという事実にこだわることにします。上記の関数はかなり改善できるので、もっと面白くできますが。時間とは異なり、オリジナルと同じであるためにはボリュームを修正しなければなりません。この詳細のため、このプロシージャでは最後の計算を行い、1分間のバーの最終的なボリュームが最初のボリュームと同じになるように補正します。

では、この記事の最後の関数を見てみましょう。この関数は、上記のコードで提供された値とパラメータに基づいてピボットポイントを作成します。重要なことは、値はあなたの興味に合うように調整することができますが、その後の機能が正しく機能するように注意する必要があるということです。

//+------------------------------------------------------------------+
#define macroCreateLeg(A, B, C) if (A < B)      {               \
                while (A < B)   {                               \
                        tick[C++].last = A;                     \
                        A += m_PointsPerTick;                   \
                                }                               \
                                                } else {        \
                while (A > B)   {                               \
                        tick[C++].last = A;                     \
                        A -= m_PointsPerTick;                   \
                                }               }
                        
inline void Pivot(const double p1, const double p2, int &t0, MqlTick &tick[], bool b0 = false)
                        {
                                double v0, v1, v2;
                                
                                v0 = (p1 > p2 ? p1 - p2 : p2 - p1);
                                v1 = p1 + (MathFloor((v0 * 0.382) / m_PointsPerTick) * m_PointsPerTick * (p1 > p2 ? -1 : 1));
                                v2 = p1 + (MathFloor((v0 * 0.618) / m_PointsPerTick) * m_PointsPerTick * (p1 > p2 ? -1 : 1));
                                v0 = p1;
                                macroCreateLeg(v0, v2, t0);
                                macroCreateLeg(v0, v1, t0);
                                macroCreateLeg(v0, p2, t0);
                                if (b0) tick[t0].last = v0;
                        }
#undef macroCreateLeg
//+------------------------------------------------------------------+



上記の特徴は、操作がシンプルであることです。その計算は奇妙に見えるかもしれませんが、ピボットポイントを作成するために使用される値を見ると、常にフィボナッチ線の1本目と3本目を使用してピボットを設定しようとしていることに気づくでしょう。まず重要なのは、ピボットが上か下かは関係ないということです。そして、プログラミングの知識があまりない人にとっては混乱するかもしれない側面が出てきます。マクロです。マクロを使う理由は、マクロを使ってピボットの一部を作成する方が簡単だからですが、そのための関数を作ることもできます。実際、もし純粋なC++を使っていたら、このマクロはまったく違うコードになっていたでしょうが、ここではMQL5をそのまま使うことで、回避策として機能します。

このマクロを使う方が、宣言されている部分の中にコードを埋め込むよりもはるかに効率的です。


結論

私たちは常に、修正や変更が必要で、私たちの行動を理解するのに何時間も費やさなければならないコードよりも、読みやすく理解しやすいコードを好むべきです。これでこの記事は終わりです。以下のビデオは、記事に添付されているものを使用して作成された、現在の開発段階での結果を示しています。

ただし、シミュレートされたチケットを使用する際に発生した問題に言及したいと思います。リプレイサービスがあるポジション以外を移動したり、検索したりするシステムのことです。この問題は、シミュレーションされたチケットを使用した場合にのみ発生します。次回は、この問題に対処修正し、その他の改良を加える予定です。



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

添付されたファイル |
Market_Replay.zip (50.42 KB)
リプレイシステムの開発 - 市場シミュレーション(第12回):シミュレーターの誕生(II) リプレイシステムの開発 - 市場シミュレーション(第12回):シミュレーターの誕生(II)
シミュレーターの開発は、見た目よりもずっと面白いものです。事態はさらに面白くなってきているため、今日は、この方向にもう少し踏み込んでみましょう。
リプレイシステムの開発 - 市場シミュレーション(第10回):リプレイで実データのみを使用する リプレイシステムの開発 - 市場シミュレーション(第10回):リプレイで実データのみを使用する
ここでは、リプレイシステムで、調整されているかどうかを気にすることなく、より信頼性の高いデータ(取引されたティック)を使用する方法を見ていきます。
リプレイシステムの開発 - 市場シミュレーション(第14回):シミュレーターの誕生(IV) リプレイシステムの開発 - 市場シミュレーション(第14回):シミュレーターの誕生(IV)
この記事ではシミュレーターの開発段階を続けます。 今回は、ランダムウォークタイプの動きを効果的に作成する方法を見ていきます。このような動きには非常に興味をそそられます。資本市場で起こるすべてのことの基礎がそれによって形成されるためです。さらに、市場分析をおこなう上で基本となるいくつかの概念についても理解を深めていきます。
リプレイシステムの開発 - 市場シミュレーション(第9回):カスタムイベント リプレイシステムの開発 - 市場シミュレーション(第9回):カスタムイベント
ここでは、カスタムイベントがどのようにトリガーされ、指標でどのようにリプレイ/シミュレーションサービスの状態がレポートされるかを見ていきます。