English Русский 中文 Español Deutsch Português
リプレイシステムの開発 - 市場シミュレーション(第10回):リプレイで実データのみを使用する

リプレイシステムの開発 - 市場シミュレーション(第10回):リプレイで実データのみを使用する

MetaTrader 5 | 19 12月 2023, 08:22
211 0
Daniel Jose
Daniel Jose

はじめに

前回の「リプレイシステムの開発 - 市場シミュレーション(第9回):カスタムイベント」では、カスタムイベントのトリガー方法について見ました。また、指標とサービスとの相互作用という観点からも、非常に興味深いシステムを導入しました。この記事では、取引されたティックを維持することの重要性を強調したいと思います。まだこの習慣を試したことがないなら、毎日実践することを真剣に考えるべきです。詳細な調査が必要な資産のすべての取引値を記録します。

失われた情報を回復するための奇跡的な解決策を探すのは無意味です。一度失われた情報は、どんな方法を使っても取り戻すことは不可能です。特定の資産を深く掘り下げて理解するのが目的なら、後回しにしないことです。取引されたティックは貴重なので、できるだけ早く安全な場所に保管するべきです。時間が経つにつれ、バーデータが取引されたティックの値と一致しないことがあることがわかるようになります。この食い違いが、特に金融市場にあまり詳しくない人にとっては、理解を難しくしています。

例えば、1分足バーでは実際の操作を反映しないため、特定のイベントが数値に影響することがあります。決算発表、資産のグループ化や分割、資産の満期(先物の場合)、債券の発行など、さまざまなイベントがあります。これらはすべて、1分足バーの現在値を歪め、その時間または期間で取引されている現実と矛盾させる可能性のある状況の例です。

これは、こうしたイベントが発生すると、資産の価格が一定の形で調整されるためです。どんなに頑張っても、1分足を取引ティックに等しくする調整はできません。だからティックは貴重なのです。

メモ:差を足せば同じ値になると言う人もいるかもしれません。ただし、問題ではこのことではありません。ジレンマは、市場が価格を一定の範囲にあると認識すれば、一定の反応を示すということです。もし、私たちがそれを別に感じれば、反応は違ってきます。

したがって、私たちのリプレイ/シミュレーションシステムでは、取引されたティックファイルのみを使用するように適応を実装する必要があります。本連載の当初から行っていることではありますが、取引されたティックに含まれるデータを実際に使用することは、リプレイを作成する以外にはまだありません。ただし、この状況はすぐに変わるでしょう。また、これらのファイルや、それらに含まれるデータをプレビューバーの作成に使用できるようにします。これは、1分間のプレビューバーデータのファイルのみを使用する現在の方法を置き換えるものです。


コード開発

ほとんどのコードはすでに実装されているので、この面ではあまり手を加える必要はないでしょう。しかし、リプレイ/シミュレーション時に使用される構成ファイルについて考える必要があります。このファイルを使い始めると、それが私たちにとって極めて重要であることが明らかになりました。1つはリプレイ/シミュレーションの作成に使用される取引ティックをリストする構造、もう1つは動きのプレビューとして使用されるバーを示す構造で、これはシミュレーションには含まれず、資産に追加されるだけです。

これにより、情報を正しく分離し、修正することができますが、今度は小さな問題に直面します。ティックファイルがバー構造に含まれている場合、システムはエラー警告を発行します。この方法を維持するのは、構成ファイルの作成時のタイプミスによって、リプレイやシミュレーションが制御不能になる危険性を避けるためです。1分足のバーファイルを取引されたティックファイルのように追加しようとしても同じことが起こります。

このジレンマの一部を解決し、ティックファイルをプレビューバーとして使用することで、システムがこれをエラーと認識しないようにするにはどうすればいいのでしょうか。ここでは、いくつかの可能性のある解決策のうちの1つを提案します。最初のステップは、C_Replay.mqhヘッダーファイルに新しい定義を追加することです。

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

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


この定義は、1つ以上のティックファイルがバーファイルとして扱われるべき場合を決定するのに役立ちます。そうすることで、次のステップに進みやすくなります。しかし、さらに細かいことがあります。この定義の追加にはとどまりません。また、他の定義と同様に、ユーザーが変更できるようにします。しかし、よりエレガントな方法でです。これによって、ユーザーにとってすべてがより明確になり、理解しやすくなる可能性があります。いずれにせよ、システムはすべてを正しく解釈し続けるので、大きな変化はないでしょう。

したがって、もしユーザーが構成ファイルに次のような定義を入力することに決めたとします。

[ TICKS -> BARS ]



これは有効な定義として理解されるべきです。同時に、ユーザーが理解しやすいようにパラメータを少し拡張しました。というのも、データをグループ化せず、論理的に分けることを好む人もいるからです。それは完全に許容できます。ユーザーが提供された定義をわずかに「変更」できるような柔軟性を提供するためには、サービスコードにいくつかの詳細を追加する必要があります。これによってコードがより明確になり、将来的な機能拡張をできるだけ簡単におこなえるようになります。そのために、列挙型を作成します。しかし、以下に見るように、詳細があります。

class C_Replay
{
        private :
                enum eTranscriptionDefine {Transcription_FAILED, Transcription_INFO, Transcription_DEFINE};
                int      m_ReplayCount;
                datetime m_dtPrevLoading;

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


この列挙子はC_Replayクラスのprivateであり、クラスの外部からはアクセスできません。private宣言ブロックに置くことでこれを可能にする一方で、構成ファイルの複雑さを簡単に増大させることもできます。しかし、この詳細については、今回の記事ではカバーしきれないため、本連載の今後の記事で取り上げる予定です。

それが終われば、次に実装する項目に集中できます。この項目は実際には、構成ファイルに含まれるデータの分類を定義するための関数です。このプロセスの仕組みを見てみましょう。ここでは次のコードを使用します。

inline eTranscriptionDefine GetDefinition(const string &In, string &Out)
{
        string szInfo;
        
        szInfo = In;
        Out = "";
        StringToUpper(szInfo);
        StringTrimLeft(szInfo);
        StringTrimRight(szInfo);
        if (StringSubstr(szInfo, 0, 1) != "[")
        {
                Out = szInfo;
                return Transcription_INFO;
        }
        for (int c0 = 0; c0 < StringLen(szInfo); c0++)
                if (StringGetCharacter(szInfo, c0) > ' ')
                        StringAdd(Out, StringSubstr(szInfo, c0, 1));                                    
                
        return Transcription_DEFINE;
}



ここでは、構成ファイルからデータを予備変換するプロセス全体を一元化することを意図しています。その後の作業がどうなるかはともかく、上記の関数は、受信したデータが特定のパターンに一致するかどうかの事前チェックをすべておこないます。その前にまずすべきことは、すべての文字を大文字に変換することです。この後、後続のステップで不要な要素をすべて削除します。これが完了したら、シーケンスの最初の文字を確認します。それが「[」と異なる場合、シーケンスが定義ではなく何らかの情報を表しています。

この特別なケースでは、前に実行された処理の結果を返すだけです。そうでない場合は、これらの定義を探し始めることになります。たとえ形式が違っていても、内容は適切で正しいかもしれません。読み取りの際、値がスペースより小さい文字は無視しなければなりません。したがって、たとえ[B A R S]と書いても、システムは [BARS]として解釈します。この場合、入力の書き方に多少の違いがあっても、内容が期待に合致していれば、それに対応する結果が得られます。

これで、構成ファイルの内容を読み込んで構成をおこなう新しいシステムができました。

bool SetSymbolReplay(const string szFileConfig)
{
        int     file;
        string  szInfo;
        bool    isBars = true;
                                
        if ((file = FileOpen("Market Replay\\" + szFileConfig, FILE_CSV | FILE_READ | FILE_ANSI)) == INVALID_HANDLE)
        {
                MessageBox("Failed to open\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;
        while ((!FileIsEnding(file)) && (!_StopFlag)) switch (GetDefinition(FileReadString(file), szInfo))
        {
		case Transcription_DEFINE:
			if (szInfo == def_STR_FilesBar) isBars = true; else
                        if (szInfo == def_STR_FilesTicks) isBars = false;
                        break;
		case Transcription_INFO:
			if (szInfo != "") if (!(isBars ? LoadPrevBars(szInfo) : LoadTicksReplay(szInfo)))
			{
				if (!_StopFlag)
					MessageBox(StringFormat("File %s from %s\ncould not be loaded.", szInfo, (isBars ? def_STR_FilesBar : def_STR_FilesTicks), "Market Replay", MB_OK));
				FileClose(file);
				return false;
			}
			break;
	}
        FileClose(file);

        return (!_StopFlag);
}



しかし、興奮しすぎるのはよくありません。ここで変わったのは、以前からあった構造だけで、動作方法は変わっていません。取引されたティックを含むファイルを1分足のプレビューバーとして使用することはできません。上記のコードを設定する必要があります。

よく見れば、私たちは以前は不可能だったことを可能にする能力を身につけています。考えてみてください。データは、構成ファイルから読み込まれたデータの前処理を一元管理するプロシージャーに送られます。上記のコードで使われているデータは、すでに「クリーン」なものです。つまり、構成ファイルにコメントを入れることができるということでしょうか。答えははいです。今ならこれができます。あとはコメントの形式、つまり1行か複数行かを決めるだけです。まずは一行コメントから始めましょう。プロシージャは単純明快です。

inline eTranscriptionDefine GetDefinition(const string &In, string &Out)
{
        string szInfo;
                                
        szInfo = In;
        Out = "";
        StringToUpper(szInfo);
        StringTrimLeft(szInfo);
        StringTrimRight(szInfo);
        if (StringSubstr(szInfo, 0, 1) == "#") return Transcription_INFO;
        if (StringSubstr(szInfo, 0, 1) != "[")
        {
                Out = szInfo;
                return Transcription_INFO;
        }
        for (int c0 = 0; c0 < StringLen(szInfo); c0++)
                if (StringGetCharacter(szInfo, c0) > ' ')
                        StringAdd(Out, StringSubstr(szInfo, c0, 1));                                    
                                
        return Transcription_DEFINE;
}



指定した文字が行頭にある場合、コメントとして扱われ、その内容はすべて無視されます。これで、構成ファイルにコメントを挿入できるようになりました。面白いとおもいませんか。コード行を追加するだけで、コメントをサポートするようになりました。しかし、本題に戻りましょう。コードでは、取引されたファイルを1分足バーとしてまだ考慮していません。そのためには、いくつかの変更を加える必要があります。このような変更をおこなう前に、システムが以前と同じように機能することを確認する必要があります。コードは次のようになります。

bool SetSymbolReplay(const string szFileConfig)
        {
#define macroERROR(MSG) { FileClose(file); if (MSG != "") MessageBox(MSG, "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\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
                                                macroERROR(StringFormat("%s is not recognized in system\nin line %d.", szInfo, iLine));
                                        break;
                                case Transcription_INFO:
                                        if (szInfo != "") switch (iStage)
                                        {
                                                case 0:
                                                        macroERROR(StringFormat("Couldn't recognize command in line %d\nof the configuration file.", iLine));
                                                        break;
                                                case 1:
                                                        if (!LoadPrevBars(szInfo)) macroERROR(StringFormat("This line is declared in line %d", iLine));
                                                        break;
                                                case 2:
                                                        if (!LoadTicksReplay(szInfo)) macroERROR(StringFormat("This line is declared in line %d", iLine));
                                                        break;
                                                case 3:
                                                        break;
                                        }
                                        break;
                        };
                        iLine++;
                }
                FileClose(file);
                return (!_StopFlag);
#undef macroERROR
        }



このプロシージャを呼び出すと、まず、使用するパラメータが初期化されます。これは、システムがエラーの発生箇所を正確に報告するために必要なことです。このため、さまざまな状況で使用される一般的なエラーメッセージを生成するマクロを用意しています。これで私たちのシステムは段階的に機能することになります。したがって、次の行で何を処理するかを明確に定義しなければなりません。このステップをスキップするとエラーとみなされます。この場合、最初の誤差は常にゼロに等しくなります。なぜなら、手順を初期化するときに、分析のゼロ段階にいることを示したからです。 

この構造化は一見複雑に見えますが、私たちが望むあらゆる側面を素早く効率的に拡大することを可能にします。追加が以前のコードに影響することはほとんどありません。この方法を使えば、次のような内部フォーマットを持つ構成ファイルを使うことができます。

#First set initial bars, I think 3 is enough ....
[Bars]
WIN$N_M1_202108020900_202108021754
WIN$N_M1_202108030900_202108031754
WIN$N_M1_202108040900_202108041754

#I have a tick file but I will use it for pre-bars ... thus we have 4 files with bars
[ Ticks -> Bars]
WINQ21_202108050900_202108051759

#Now use the file of traded ticks to run replay ...
[Ticks]
WINQ21_202108060900_202108061759

#End of the configuration file...



これで何が起きているのかを明確にし、より効率的なフォーマットを使うことができます。ただし、私たちが望んでいることはまだ実現していません。コードを少し変えるだけで、物事がより面白くなることを示しただけです。そして、このような変化は、読者が考えているほど難しいものではありません。

それでは続けましょう。取引されたティックファイルをプレビューバーとして使用するために必要な変更をおこないます。そして、読者が複雑なコードを考え始める前に、必要なコードはすでにリプレイ/シミュレーションサービスに用意されていることをお知らせしておきます。ただ、複雑さの中に隠れているだけです。さて、このコードを抽出して投稿する必要があります。一歩間違えれば、既存のシステム全体を危険にさらすことになりかねないからです。これを理解するために、前回の記事で紹介した次のコードを見てみましょう。

bool LoadTicksReplay(const string szFileNameCSV)
{
        int     file,
                old;
        string  szInfo = "";
        MqlTick tick;
        MqlRates rate;
                        
        if ((file = FileOpen("Market Replay\\Ticks\\" + szFileNameCSV + ".csv", FILE_CSV | FILE_READ | FILE_ANSI)) != INVALID_HANDLE)
        {
                ArrayResize(m_Ticks.Info, def_MaxSizeArray, def_MaxSizeArray);
                ArrayResize(m_Ticks.Rate, def_BarsDiary, def_BarsDiary);
                old = m_Ticks.nTicks;
                for (int c0 = 0; c0 < 7; c0++) szInfo += FileReadString(file);
                if (szInfo != def_Header_Ticks)
                {
                        Print("File ", szFileNameCSV, ".csv is not a file with traded ticks.");
                        return false;
                }
                Print("Loading ticks for replay. Wait...");
                while ((!FileIsEnding(file)) && (m_Ticks.nTicks < (INT_MAX - 2)) && (!_StopFlag))
                {
                        ArrayResize(m_Ticks.Info, (m_Ticks.nTicks + 1), 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;
                                if (tick.volume_real > 0.0)
                                {
                                        m_Ticks.nRate += (BuiderBar1Min(rate, tick) ? 1 : 0);
                                        rate.spread = m_Ticks.nTicks;
                                        m_Ticks.Rate[m_Ticks.nRate] = rate;
                                        m_Ticks.nTicks++;
                                }
                                old = (m_Ticks.nTicks > 0 ? m_Ticks.nTicks - 1 : old);
                        }
                }
                if ((!FileIsEnding(file)) && (!_StopFlag))
                {
                        Print("Too much data in the tick file.\nCannot continue...");
                        return false;
                }
        }else
        {
                Print("Tick file ", szFileNameCSV,".csv not found...");
                return false;
        }
        return (!_StopFlag);
};



このコードは、取引されたティックを読み込んで保存し、後でリプレイシミュレーションとして使用することを意図していますが、重要なポイントがあります。ある時点で、取引されたティックデータだけを使って1分足バーと同等のものを作成することになります。

さて、考えてみましょう。これこそ、私たちがやりたいことではないでしょうか。取引されたティックのファイルを読み込んで1分足のバーを作成し、リプレイ/シミュレーションに使用する資産に保存したいのです。ただし、取引されたティックとしてではなく、プレビューバーとして解釈されるデータとして保存します。したがって、これらのティックを単に保存する代わりに、前述のコードに少し変更を加えると、取引されたティックを読み取る際に作成されたバーを、プレビューバーとしてリプレイ/シミュレーションで分析するための資産に挿入するように見せることができます。

この作業では、ある重要な点を考慮する必要があります。この点については、実装について説明するときに取り上げることにします。なぜなら、実際にコードがビルドされ、動作しているのを見て初めて理解できるからです。まず、取引されたティックをバーに変換するコードを下で見てみましょう。

bool SetSymbolReplay(const string szFileConfig)
        {
#define macroERROR(MSG) { FileClose(file); MessageBox((MSG != "" ? MSG : StringFormat("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\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
                                                macroERROR(StringFormat("%s is not recognized in system\nin line %d.", szInfo, iLine));
                                        break;
                                case Transcription_INFO:
                                        if (szInfo != "") switch (iStage)
                                        {
                                                case 0:
                                                        macroERROR(StringFormat("Couldn't recognize command 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;
                                        }
                                        break;
                        };
                        iLine++;
                }
                FileClose(file);
                        return (!_StopFlag);
#undef macroERROR
        }



報告の標準化を図るため、エラー報告システムに若干の修正を加えましたが、それが今回の目的ではありません。取引されたティックから1分足バーを生成する呼び出しには、とても興味があります。この呼び出しは、パラメータが追加されただけで、先ほどの呼び出しと同じであることに注意してください。この単純な追加パラメータの詳細が鍵であり、以下のコードに見られるように、関数全体を書き直す手間を省くことができます。

取引されたティックファイルからデータを処理し、1分足バーに変換するために必要なロジックは、すべてオリジナルのリプレイ/シミュレーションサービス機能にすでに存在しています。本当に必要なのは、全体的なパフォーマンスを損なうことなく、これらのバーを予備的なものとして適応させることです。なぜなら、必要な修正をおこなわないと、リプレイ/シミュレーションサービスを使用する際にエラーが発生する可能性があるからです。では、取引されたティックを読み取るコードに加えられた変更を見てみましょう。したがって、システムはこれらのティックをバーとして使用することができます。

bool LoadTicksReplay(const string szFileNameCSV, const bool ToReplay = true)
{
        int     file,
                old,
                MemNRates,
                MemNTicks;
        string  szInfo = "";
        MqlTick tick;
        MqlRates rate,
                RatesLocal[];
                                
        MemNRates = (m_Ticks.nRate < 0 ? 0 : m_Ticks.nRate);
        MemNTicks = m_Ticks.nTicks;
        if ((file = FileOpen("Market Replay\\Ticks\\" + szFileNameCSV + ".csv", FILE_CSV | FILE_READ | FILE_ANSI)) != INVALID_HANDLE)
        {
                ArrayResize(m_Ticks.Info, def_MaxSizeArray, def_MaxSizeArray);
                ArrayResize(m_Ticks.Rate, def_BarsDiary, def_BarsDiary);
                old = m_Ticks.nTicks;
                for (int c0 = 0; c0 < 7; c0++) szInfo += FileReadString(file);
                if (szInfo != def_Header_Ticks)
                {
                        Print("File", szFileNameCSV, ".csv is not a file with traded ticks.");
                        return false;
                }
                Print("Loading ticks for replay. Wait...");
                while ((!FileIsEnding(file)) && (m_Ticks.nTicks < (INT_MAX - 2)) && (!_StopFlag))
                {
                        ArrayResize(m_Ticks.Info, (m_Ticks.nTicks + 1), 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;
                                if (tick.volume_real > 0.0)
                                {
                                        m_Ticks.nRate += (BuiderBar1Min(rate, tick) ? 1 : 0);
                                        rate.spread = (ToReplay ? m_Ticks.nTicks : 0);
                                        m_Ticks.Rate[m_Ticks.nRate] = rate;
                                        m_Ticks.nTicks++;
                                }
                                old = (m_Ticks.nTicks > 0 ? m_Ticks.nTicks - 1 : old);
                        }
                }
                if ((!FileIsEnding(file)) && (!_StopFlag))
                {
                        Print("Too much data in the tick file.\nCannot continue...");
                        FileClose(file);
                        return false;
                }
                FileClose(file);
        }else
        {
                Print("Tick file ", szFileNameCSV,".csv not found...");
                return false;
        }
        if ((!ToReplay) && (!_StopFlag))
        {
                ArrayResize(RatesLocal, (m_Ticks.nRate - MemNRates));
                ArrayCopy(RatesLocal, m_Ticks.Rate, 0, 0);
                CustomRatesUpdate(def_SymbolReplay, RatesLocal, (m_Ticks.nRate - MemNRates));
                m_dtPrevLoading = m_Ticks.Rate[m_Ticks.nRate].time;
                m_Ticks.nRate = (MemNRates == 0 ? -1 : MemNRates);
                m_Ticks.nTicks = MemNTicks;
                ArrayFree(RatesLocal);
        }
        return (!_StopFlag);
};



まず、いくつかの変数を追加する必要があります。これらのローカル変数は重要な情報を一時的に保存し、必要に応じて後でシステムを元の状態に戻すことができるようにします。なお、すべてのコードはオリジナルと同じままです。1分足が表示されるたびに追加することもできますが、別の方法をとりました。当然、特定の場所に変更を加える必要がありました。まさに、データがバーとみなされるかどうかを評価する場所にです。データを棒グラフとして使用する場合は、一貫性のある論理的な表示を保証しなければなりません。

また、本来の処置に支障をきたすことなく読影がおこなわれるよう配慮しています。取引されたティックは読み込まれ、1分足に変換されるが、そこに変更はありません。しかし、ファイルが完全に読み込まれ、すべてが期待通りに進むと、新たな段階に入ります。そして、ここで本当の変革が起こります。ティックファイルがプレビューバーシステムとして使用されるように割り当てられており、ユーザーが読み込み中にリプレイ/シミュレーションサービスの終了を要求しなかった場合、trueの状態を発見したことになります。そして、データが正しく使用されるように具体的な対策を講じ、システムを元の状態に戻します。こうして、プレイ段階での問題や異常事態を回避します。

ここでの最初のステップは、1分足のバーを一時的に保存するためのメモリを確保することです。そうしたら、ファイルを読むときに作られたバーをこの一時的なスペースに移動させます。このアクションは、次のステップであるリプレイ/シミュレーション資産にバーを挿入し、その成功を確実にするために重要です。この前のアクションがなければ、資産内の1分足バーをプレビューバーと解釈されるように正しく配置することは難しいでしょう。もし、作成時にバーを追加する直接的な方法を選択していれば、このロジックはすべて必要なかったことに注目してください。しかし、まずファイルを読み込んでからバーを保存するという選択した方法では、正しい動作を保証するためにこれらの操作が必要になります。

このようにデータを分散して転送することで、転送のためのループを作る必要がなくなり、プロセスが簡素化されます。翻訳が完了したら、バーの限界位置の値を修正し、プロシージャの最初に保存した値に戻します。そうすれば、システムは何も変わっていないかのように機能することになります。システムには、棒グラフの読み取りがティックチャートからではなく、1分足の棒グラフファイルから直接おこなわれたかのように見えます。バーが期待通りに表示され、ようやく一時的なメモリ領域が解放されます。

こうして、最小限の努力で深刻な問題を克服することができました。しかし、提案された解決策は、ソースコードの変更が最も少なくて済みそうではありますが、唯一可能な解決策ではありません。


すべてのリプレイグラフィックの削除

閉鎖時のシステムには興味深い点があります。これは通常、コントロール指標が表示されているチャートを閉じるときに起こります。実際、問題とは言えませんが、ちょっとした不便さはあります。多くの人は、同じ資産の複数のチャートを同時に開くことを好みます。これは普通のことであり、理解できるし、状況によっては役に立つこともあります。しかし、こう考えてください。リプレイ/シミュレーションサービスが、使用された資産の痕跡を削除しようとすると、単に失敗します。理由は簡単で、リプレイ資産のある別のチャートが開いているからです。

そのため、システムはこれらの痕跡を取り除くことができません。気配値表示ウィンドウには、意味のない資産が1つ残ります。手動で除去することもできますが、これは私たちが必要とするものではありません。リプレイ/シミュレーションサービスは、痕跡を完全に自動的に削除するべきです。これを解決するには、コードを少し変更する必要があります。何が追加されるのかを本当に理解するために、以下のソースコードを見てみましょう。

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



このコードを呼び出すと、サービスによって開かれたチャートだけが閉じられます。リプレイ資産を参照する他のチャートが開かれていなければ、気配値表示ウィンドウから削除できます。しかし、すでに述べたように、ユーザーはマーケットリプレイ資産を使って他のチャートを開くことができます。このような状況で、気配値表示ウィンドウから資産を削除しようとすると、失敗します。この問題を解決するには、サービスの終了方法を変更する必要があります。より複雑で、より効果的な解決策となるよう、対応するラインを調整する必要があります。関連コードを以下に示します。

void CloseReplay(void)
{                       
        ArrayFree(m_Ticks.Info);
        ArrayFree(m_Ticks.Rate);
        m_IdReplay = ChartFirst();
        do
        {
                if (ChartSymbol(m_IdReplay) == def_SymbolReplay)
                        ChartClose(m_IdReplay);
        }while ((m_IdReplay = ChartNext(m_IdReplay)) > 0);
        for (int c0 = 0; (c0 < 2) && (!SymbolSelect(def_SymbolReplay, false)); c0++);
        CustomRatesDelete(def_SymbolReplay, 0, LONG_MAX);
        CustomTicksDelete(def_SymbolReplay, 0, LONG_MAX);
        CustomSymbolDelete(def_SymbolReplay);
        GlobalVariableDel(def_GlobalVariableReplay);
        GlobalVariableDel(def_GlobalVariableIdGraphics);
}



このコードは奇妙に見えるかもしれませんが、その機能は非常に単純です。まず、最初のチャートのIDを取得します。必ずしも最初に開くとは限らないことに注意する必要があります。次にループを開始します。このループでは、チャートが参照している資産を確認します。リプレイ資産であれば、グラフを閉じます。ループを完了させるか否かを決定するために、私たちはプラットフォームに対し、リストの次のチャートのIDを教えてもらいます。リストに他のチャートがなければ、0より小さい値が返され、ループは終了します。そうでなければ、ループは再び実行されます。これにより、資産がリプレイとして使用されているウィンドウは、数に関係なくすべて閉じられます。次に、気配値表示ウィンドウからリプレイ資産を削除する試みを2回おこないます。試行回数が2回あるのは、リプレイ/シミュレーションサービスのウィンドウしか開いていない場合、資産を削除するには1回の試行で十分だからです。しかし、ユーザーが他のウィンドウを開いている場合、再試行が必要になることがあります。

通常は1回挑戦すれば十分です。気配値表示ウィンドウから資産を削除できなければ、カスタム銘柄も削除できません。しかし、それとは関係なく、ユーザーリソースに存在するコンテンツは、リプレイ/シミュレーションサービス以外では役に立たないため、削除します。何も残してはいけません。カスタム資産を完全に削除できなくても、その中には何もないので大きな問題にはならないでしょう。しかし、この処置の目的は、プラットフォームから完全に取り除くことです。


結論

下のビデオでは、この記事で紹介した作業の結果を見ることができます。まだ見えていない部分もあるかもしれませんが、ビデオを見ることで、これらすべての記事におけるリプレイ/シミュレーションシステムの進歩を明確に理解することができるでしょう。ビデオを見て、最初と今の変化を比べてみてください。



次回は、本当に必要な機能がまだいくつか残っているので、システムの開発を続けます。


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

添付されたファイル |
Market_Replay.zip (13061.7 KB)
リプレイシステムの開発 - 市場シミュレーション(第11回):シミュレーターの誕生(I) リプレイシステムの開発 - 市場シミュレーション(第11回):シミュレーターの誕生(I)
バーを形成するデータを使うためには、リプレイをやめてシミュレーターの開発に着手しなければなりません。難易度が最も低い1分バーを使用します。
リプレイシステムの開発 - 市場シミュレーション(第9回):カスタムイベント リプレイシステムの開発 - 市場シミュレーション(第9回):カスタムイベント
ここでは、カスタムイベントがどのようにトリガーされ、指標でどのようにリプレイ/シミュレーションサービスの状態がレポートされるかを見ていきます。
リプレイシステムの開発 - 市場シミュレーション(第12回):シミュレーターの誕生(II) リプレイシステムの開発 - 市場シミュレーション(第12回):シミュレーターの誕生(II)
シミュレーターの開発は、見た目よりもずっと面白いものです。事態はさらに面白くなってきているため、今日は、この方向にもう少し踏み込んでみましょう。
リプレイシステムの開発 - 市場シミュレーション(第13回):シミュレーターの誕生(III) リプレイシステムの開発 - 市場シミュレーション(第13回):シミュレーターの誕生(III)
ここでは、次回以降の仕事に関連するいくつかの要素を簡略化します。シミュレーターが生成するランダム性を視覚化する方法も説明しましょう。