
リプレイシステムの開発—市場シミュレーション(第6回):最初の改善(I)
はじめに
この連載では、私たちの市場リプレイシステムがより定義された形になる前に物事が実際にどのように始まるのかを読者に示すために、その作成過程とともに紹介しています。このプロセスの結果、ゆっくりと注意深く進むことができ、多くのものを作成、削除、追加、変更することができるようになります。その理由は、市場リプレイシステムが記事の公開と同時に作成されているということです。したがって、記事の公開前に、モデリングおよび調整段階にあるシステムが安定して機能することを確認するために、いくつかのテストが実行されています。
それでは本題に入りましょう。前回の「リプレイシステムの開発 — 市場シミュレーション(第5回):プレビューの追加」では,プレビューパネルを読み込むシステムを作成しました。このシステムは機能していますが、まだいくつかの問題があります。最も差し迫った問題は、より使いやすいデータベースを取得するには、既存のバーファイルを作成する必要があることです。このファイルには、多くの場合、数日分のデータが含まれており、データを挿入することができません。
たとえば、月曜日から金曜日までの1週間のデータを含むプレビューファイルを作成した場合、同じデータベースを使用して木曜日のリプレイを作成することはできません。これには、新しいデータベースを作成する必要があります。そして、よく考えてみると恐ろしいことです。
この不便さに加えて、適切なデータベースを使用していることを確認するための適切なテストが完全に欠如しているなど、他の問題もあります。このため、バーファイルを誤って実行された取引のティックデータであるかのように使用したり、その逆をおこなったりする可能性があります。この場合、システムに重大な障害が発生し、正常に機能しなくなります。この記事の一部として、その他の小さな変更も加えます。本題に入りましょう。
何が起こっているのかを理解するために、各ポイントを確認していきます。
改善の実装
実装する必要がある最初の変更は、サービスファイルに2つの新しい行を追加することです。それらを以下に示します。
#define def_Dependence "\\Indicators\\Market Replay.ex5" #resource def_Dependence
なぜこれをおこなう必要があるのでしょうか。単純な事実として、システムはモジュールで構成されており、リプレイシステムを使用するときに何らかの方法でモジュールの存在を保証する必要があります。最も重要なモジュールはまさにこの指標です。指標は、実行される内容に対する特定の制御を担当するためです。
これが最良の方法ではないことは認めます。おそらく将来、MetaTrader 5プラットフォームとMQL5言語を開発する人は、コンパイルディレクティブなどを追加して、ファイルがコンパイルされていること、またはファイルが存在することを実際に確認できるようにするでしょう。しかし、他に解決策がないので、この方法で解決します。
サービスが他のものに依存していることを示すディレクティブを設定します。次に、これをリソースとしてサービスに追加します。重要な点が1つあります。この要素をリソースに変換しても、必ずしもリソースとして使用できるわけではありません。このケースは特別です。
これらの2つの単純な行により、リプレイで使用するためにテンプレート内に存在する指標が、リプレイサービスのコンパイル時に実際にコンパイルされることが保証されます。これを忘れて、テンプレートが指標を読み込んだときに指標が見つからずに障害が発生し、サービスの制御に使用される指標がチャート上に見つからないときに初めて気づくことになります。
これを回避するために、指標がサービスと一緒にコンパイルされていることをすでに確認しています。ただし、カスタムテンプレートを使用して制御指標を手動で追加する場合は、サービスコードの上の2行を削除できます。このような指標がなくても、コードやサービスの動作には影響しません。
注:コンパイルを強制したとしても、実際にコンパイルがおこなわれるのは、指標実行可能ファイルが存在しない場合のみです。指標が変更された場合、サービスのみをコンパイルしても指標はビルドされません。
これはMetaEditorのプロジェクトモードを使用すれば解決するだろうとおっしゃられるかもしれませんが、このモードでは、MAKEファイルを使用してコンパイルを制御するC/C++などの言語と同じように作業することはできません。BATCHファイル経由で実行することもできますが、コードをコンパイルするためだけにMetaEditorを終了する必要があります。
続けます。次の2つの新しい行ができました。
input string user00 = "Config.txt"; //Replay configuration file input ENUM_TIMEFRAMES user01 = PERIOD_M5; //Initial timeframe
リプレイサービスにとって非常に役立つものを次に示します。この行は実際には、リプレイ銘柄の設定が含まれるファイルの名前です。これらの設定には、現時点では、以前のバーを生成するためにどのファイルが使用されるか、および取引されたティックを保存するためにどのファイルが使用されるかが含まれます。
複数のファイルを同時に使用できるようになりました。また、私は、多くのトレーダーが市場取引時に特定の時間枠を使用し、同時にチャートを全画面モードで使用することを好むことも知っています。この行を使用すると、最初から使用する時間枠を設定できます。これは非常にシンプルで、しかも使用するためにこれらの設定をすべて保存できるため、非常に便利です。ここにさらに何かを追加することもできますが、今のところはこれで十分です。
次に、さらにいくつかの点を理解する必要があります。これは、以下のコードに示されています。
void OnStart() { ulong t1; int delay = 3; long id = 0; u_Interprocess Info; bool bTest = false; Replay.InitSymbolReplay(); if (!Replay.SetSymbolReplay(user00)) { Finish(); return; } Print("Wait for permission from [Market Replay] indicator to start replay..."); id = Replay.ViewReplay(user01); while ((!GlobalVariableCheck(def_GlobalVariableReplay)) && (!_StopFlag) && (ChartSymbol(id) != "")) Sleep(750); if ((_StopFlag) || (ChartSymbol(id) == "")) { Finish(); return; } Print("Permission received. Now you can use the replay service..."); t1 = GetTickCount64(); while ((ChartSymbol(id) != "") && (GlobalVariableGet(def_GlobalVariableReplay, Info.Value)) && (!_StopFlag)) { if (!Info.s_Infos.isPlay) { if (!bTest) bTest = true; }else { if (bTest) { delay = ((delay = Replay.AdjustPositionReplay()) == 0 ? 3 : delay); bTest = false; t1 = GetTickCount64(); }else if ((GetTickCount64() - t1) >= (uint)(delay)) { if ((delay = Replay.Event_OnTime()) < 0) break; t1 = GetTickCount64(); } } } Finish(); }
当初、私たちはすべての準備が整うのをただ待っていましたが、これからはすべてが実際に機能することを確認していきます。それはテストを通じておこなわれます。リプレイの実行に使用されるファイルが実際にこれに適しているかどうかをテストしてみましょう。ファイルに含まれるデータを読み取る関数の戻りを確認します。障害が発生した場合、何が起こったかを通知するメッセージがMetaTrader 5ツールボックスに表示されますが、使用するのに十分なデータがないため、リプレイサービスは単に終了します。
データが正しく読み込まれた場合は、ツールボックスに対応するメッセージが表示され、続行できます。次に、リプレイ銘柄チャートを開き、次のステップに進むための許可を待ちます。ただし、待機中にユーザーがサービスを終了したり、リプレイ銘柄チャートを閉じたりする場合があります。この問題が発生した場合は、リプレイサービスを停止する必要があります。すべてが正しく動作している場合は、リプレイループに入ると同時に、ユーザーがチャートを閉じたりサービスを終了したりしないようにします。これが起こった場合、リプレイも閉じなければならないためです。
すべてが機能することを想像するだけでなく、実際にそれが適切に機能することを確認します。以前のバージョンのシステムでこの種のテストが実行されなかったことが奇妙に思えるかもしれませんが、他にも問題があったため、何らかの理由でリプレイが閉じられたり終了したりするたびに、舞台裏で何かが残っていたのです。しかしここで、すべてが正しく動作し、不必要な要素がないことを確認します。
最終的に、サービスファイル内には次のコードがまだ残っています。
void Finish(void) { Replay.CloseReplay(); Print("Replay service completed..."); }
理解するのは難しくありません。ここでは、単にリプレイを完了し、ツールボックスを介してユーザーに通知します。このようにして、C_Replayクラスを実装するファイルに移動し、すべてが正しく動作していることを確認するための追加のチェックも実行します。
C_Replayクラスには大きな変更はありません。リプレイサービスが可能な限り安定して信頼できるよう、このように構築されています。したがって、これまでの作業を台無しにしないように、徐々に変更を加えていきます。
最初に目を引くのは、以下に示す行です。
#define def_STR_FilesBar "[BARS]" #define def_STR_FilesTicks "[TICKS]" #define def_Header_Bar "<DATE><TIME><OPEN><HIGH><LOW><CLOSE><TICKVOL><VOL><SPREAD>" #define def_Header_Ticks "<DATE><TIME><BID><ASK><LAST><VOLUME><FLAGS>"
重要ではないように見えるかもしれませんが、これらの4行は必要なテストを実行するため、非常に興味深いものです。これら2つの定義は構成ファイルで使用されますが、これについては後ほど説明します。これには、まさに、前のバーとして使用されるバーを含むファイルの最初の行のデータが含まれています。この定義には、まさに、取引されたティックを含むファイルの最初の行の内容が含まれています。
でも、ちょっと待ってください。これらの定義は、ファイルヘッダーにある定義とまったく同じではありません。タブがありません。そうです。実際、元のファイルにはタブが存在しません。ただし、データの読み取り方法に関連する細かい点があります。
詳細に入る前に、開発の現在の段階でリプレイサービス構成ファイルがどのように見えるかを見てみましょう。ファイルの例を以下に示します。
[Bars]
WIN$N_M1_202108020900_202108021754
WIN$N_M1_202108030900_202108031754
WIN$N_M1_202108040900_202108041754
[Ticks]
WINQ21_202108050900_202108051759
WINQ21_202108060900_202108061759
システムを大文字と小文字を区別するように設定していないため、このようなタイプにすることができる[Bars]として定義された行は、後続のすべての行が前のバーとして使用されることを示します。3つの異なるものが指定された順序で読み込まれます。順番を間違えて配置すると必要なリプレイが得られなくなるので注意してください。これらのファイルに存在するすべてのバーが、リプレイに使用される銘柄に1つずつ追加されます。ファイルまたはバーの数に関係なく、これを変更する指示があるまで、すべてのファイルが前のバーとして追加されます。
[Ticks]行の場合、これにより、後続のすべての行に、リプレイに使用される取引されたティックが含まれる、または含まれる必要があることがリプレイサービスに通知されます。バーの場合と同様、ここでも同じ警告が当てはまります。ファイルを正しい順序で配置するように注意してください。そうしないと、リプレイが予期したものと異なるようになります。すべてのファイルは常に最初から最後まで読み取られます。このようにして、バーとティックを組み合わせることができます。
ただし、現時点では若干の制限があります。おそらく、これは実際には制限ではありません。取引されたティックを追加し、それらをリプレイし、さらにバーが表示されるのを観察し、それが別のリプレイ呼び出しで使用されるのを観察することは意味がないからです。バーの前にティックを付けても、リプレイシステムには何の違いもありません。取引されるバーが常に最初に表示され、その後にのみ取引されるティックが存在します。
重要:上の例では、これが可能であるという事実は考慮されていませんでした。物事をより良い方法で整理したい場合は、ディレクトリツリーを使用して物事を分離し、より適切な方法で整理することができます。これは、クラスコードに追加の変更を加えることなく実行できます。必要なのは、クラスファイルに存在する構造内の特定のロジックに注意深く従うことだけです。わかりやすくするために、ディレクトリツリーを使用して銘柄、月、年ごとに項目を区切る方法の例を見てみましょう。
それを理解するために、以下の画像を見てみましょう。
MARKETREPLAYディレクトリがROOTとして指定されていることに注意してください。これは、このディレクトリ内で使用する必要があるベースです。物事を銘柄、年、月に分割することで整理できます。各月には、その特定の月に起こったことに対応するファイルが含まれます。システムの作成方法により、コードを変更せずに上記のような構造化を使用できます。構成ファイルでこれを指定するだけで、特定のデータにアクセスできます。
このファイルには任意の名前を付けることができますが、その内容のみが構造化されています。名前は自由に選択できます。
素晴らしいです。2020年6月16日のミニドルのティックファイルなどのファイルを呼び出す必要がある場合は、構成ファイルで次の行を使用します。
[Ticks] Mini_Dolar_Futuro\2020\06-Junho\WDO_16062020
システムはこのファイルを正確に読み取るように指示されます。もちろん、これは仕事を組織化する方法の一例にすぎません。
しかし、なぜこれが起こり、可能になるのかを理解するには、この構成ファイルの読み取りを担当する関数を確認する必要があります。見てみましょう。
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 read the configuration file.", "Market Replay", MB_OK); return false; } Print("Loading data for replay.\nPlease wait...."); while ((!FileIsEnding(file)) && (!_StopFlag)) { szInfo = FileReadString(file); StringToUpper(szInfo); if (szInfo == def_STR_FilesBar) isBars = true; else if (szInfo == def_STR_FilesTicks) isBars = false; else if (szInfo != "") if (!(isBars ? LoadPrevBars(szInfo) : LoadTicksReplay(szInfo))) { if (!_StopFlag) MessageBox(StringFormat("File %s of %s\ncould not be loaded.", szInfo, (isBars ? def_STR_FilesBar : def_STR_FilesTicks), "Market Replay", MB_OK)); FileClose(file); return false; } } FileClose(file); return (!_StopFlag); }
まず、構成ファイルを読み取ろうとします。このファイルは特定の場所にある必要があります。この場所は、少なくともシステムのコンパイル後は変更できません。ファイルを開くことができない場合は、エラーメッセージが表示され、関数が終了します。ファイルを開くことができる場合は、ファイルの読み取りを開始します。ただし、MetaTrader 5ユーザーがリプレイサービスの停止を要求したかどうかを継続的に確認することに注意してください。
これが発生した場合、つまりユーザーがサービスを停止した場合、関数は失敗したかのように閉じられます。一行ずつ読んで、読み取ったすべての文字を対応する大文字に変換することで、分析が容易になります。次に、適切な関数を解析して呼び出し、構成スクリプトで指定されたファイルからデータを読み取ります。何らかの理由でこれらのファイルのいずれかの読み取りが失敗した場合、ユーザーにはエラーメッセージが表示され、関数は失敗します。構成ファイル全体が読み取られると、関数は単に終了します。ユーザーが停止を要求すると、すべて問題ないことを示す応答を受け取ります。
構成ファイルがどのように読み取られるかを確認したので、次にデータの読み取りを担当する関数を見て、要求されたファイルが適切ではないという警告が表示される理由を理解しましょう。つまり、取引ティックを含むファイルの代わりにバーを含むファイルを使用しようとした場合、またはその逆の場合、システムはエラーを報告します。これがどのように起こるかを見てみましょう。
バーの読み取りと読み込みを担当する最も単純な関数から始めましょう。これを担当するコードを以下に示します。
bool LoadPrevBars(const string szFileNameCSV) { int file, iAdjust = 0; datetime dt = 0; MqlRates Rate[1]; string szInfo = ""; if ((file = FileOpen("Market Replay\\Bars\\" + szFileNameCSV + ".csv", FILE_CSV | FILE_READ | FILE_ANSI)) != INVALID_HANDLE) { for (int c0 = 0; c0 < 9; c0++) szInfo += FileReadString(file); if (szInfo != def_Header_Bar) { Print("Файл ", szFileNameCSV, ".csv это не файл предыдущих баров."); return false; } Print("Loading bars for the replay. Please wait...."); while ((!FileIsEnding(file)) && (!_StopFlag)) { 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("Could not access the bars data file."); m_dtPrevLoading = 0; return false; } return (!_StopFlag); }
一見すると、このコードは以前の「リプレイシステムの開発(第5回)」稿で提供したコードとあまり変わりません。しかし相違点もあり、それらは一般的かつ構造的な観点から見て非常に重要です。
最初の違いは、読み取られているファイルのヘッダーデータをインターセプトするようになったことです。このヘッダーは、バー読み取り関数によって決定され予期される値と比較されます。このヘッダーが予期したものと異なる場合、エラーがスローされ、関数は終了します。しかし、これが予期されたヘッダーである場合、ループに移ります。
以前は、このループの出力は、読み取られるファイルの終わりによってのみ制御されていました。ユーザーが何らかの理由でサービスを停止した場合、サービスは閉じられませんでした。この問題は修正され、ファイルの1つの読み取り中にユーザーがリプレイシステムを終了すると、ループが閉じられ、システムが失敗したことを示すエラーがスローされるようになりました。ただし、これはすべてをよりスムーズに、突然ではない結論に導くための単なる形式的なものです。
データの読み取り方法に変更が加えられていないため、関数の残りの部分は同じように実行され続けます。
次に、取引されたティックを読み取る関数を見てみましょう。この関数は変更され、バー読み取り関数よりもさらに興味深いものになっています。以下がそのコードです。
#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) { ArrayResize(m_Ticks.Info, def_MaxSizeArray, def_MaxSizeArray); 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 replay ticks. Please 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; m_Ticks.nTicks += (tick.volume_real > 0.0 ? 1 : 0); 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); }; #undef macroRemoveSec
前回の記事より前は、取引されたティックを読み取るこの関数には制限がありました。次のdefine行です。
#define def_MaxSizeArray 134217727 // 128 Mbytes of positions
この行はまだ存在しますが、少なくとも部分的に制限を削除しました。複数の取引ティックデータベースを処理できるリプレイシステムを作成すると便利だからです。こうすれば、システムに2日以上追加して、より大規模な繰り返しテストをおこなうことができます。さらに、非常に特殊なケースとして、128MBを超えるポジションを含むファイルを処理する必要がある場合があります。このようなことはまれですが、起こる可能性はあります。今後は、メモリ使用量を最適化するために、上記のこの定義により小さい値を使用できるようになります。
しかし、ちょっと待ってください。「より小さい」と言ったでしょうか。言いました。新しい定義を見ると、次のコードがあります。
#define def_MaxSizeArray 16777216 // 16 Mbytes of positions
読者の皆さんは頭がおかしい、これはシステムにダメージを与えるだろう...と思われるかもしれませんが、そうではありません。取引されたティックの通常の読み取りを見ると、以前は存在しなかった興味深い2行があります。これらは、最大2の32乗のデータポジションの読み取りと保存を可能にする役割を果たします。これは、ループの開始時のチェックによって保証されます。
最初のポジションを失わないように、何らかの理由でテストが失敗しないように2を減算します。外部ループを追加してメモリ容量を増やすこともできますが、個人的にはそうする理由がありません。2ギガバイトのポジションで十分でないならば、何が十分なのかわかりません。ここで、2行を使用して定義値を減らすと最適化がどのように向上するかを理解しましょう。これを担当するコード部分を詳しく見てみましょう。
// ... Previous code .... if ((file = FileOpen("Market Replay\\Ticks\\" + szFileNameCSV + ".csv", FILE_CSV | FILE_READ | FILE_ANSI)) != INVALID_HANDLE) { ArrayResize(m_Ticks.Info, def_MaxSizeArray, def_MaxSizeArray); 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 replay ticks. Please 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); // ... The rest of the code...
初めてメモリを割り当てるときは、指定されたサイズ全体に予約値を加えたものを割り当てます。この予約値は安全策となります。その後、読み取りループに入ると、実際に必要な場合に限り、一連の再配置がおこなわれます。
ここで、この2番目の割り当てでは、すでに読み取られたティックカウンタの現在の値に1を加えた値を使用することに注意してください。システムをテストしたときに、この呼び出しが値0で実行され、ランタイムエラーが発生したことに気付きました。以前はメモリがより高い値で割り当てられていたため、これはおかしいと思われるかもしれません。ここで重要なのは、ArrayResize関数のドキュメントには、配列のサイズを再定義することが記載されているということです。
この2番目の呼び出しを使用すると、関数は配列をゼロにリセットします。これは、関数が最初に呼び出されたときの変数の現在の値です。値を増加してませんでした。この理由についてはここでは説明しませんが、MQL5で動的割り当てを使用する場合は注意が必要です。コードが正しく見えても、システムがそれを間違った方法で解釈する場合があるためです。
注意すべきもう1つの詳細は、なぜテストでUINT_MAXではなくINT_MAXを使用するのかということです。実際、理想的なオプションはUINT_MAXを使用することで、4GBの割り当て領域が得られますが、ArrayResize関数はINTシステム、つまり符号付き整数で動作します。
また、32ビットのlong型を使用して4ギガバイトを割り当てたい場合でも、符号によりデータ長の1ビットが常に失われます。したがって、31ビットを使用することになり、ArrayResize関数を使用して割り当て可能な2ギガバイトのスペースが保証されます。4GB以上の割り当てを保証する共有スキームを使用してこの制限を回避することもできますが、そうする理由がわかりません。2GBのデータがあれば十分です。
説明が終わったので、コードに戻りましょう。取引されたティックを読み取る関数をまだ見ていません。ファイル内のデータが実際に取引されたティックであるかどうかを確認するには、ファイルヘッダーにある値を読み取り、保存します。この後、ヘッダーが、取引されたティックファイル内でシステムが見つけることを期待しているヘッダーと一致するかどうかを確認できます。そうしないと、システムはエラーを生成し、シャットダウンします。
バーを読み取る場合と同様に、ユーザーがシステムのシャットダウンを要求したかどうかを確認します。これにより、よりスムーズでクリーンな出力が得られます。リプレイサービスを閉じたり終了したりした場合、ユーザーは混乱を引き起こすようなエラーメッセージが表示されることを望まないからです。
ここで実行したすべてのテストに加えて、さらにいくつか実行する必要があります。実際、これらの操作は必要ありませんが、すべてをプラットフォームに任せたくないのです。いくつかのことが実際に実装されていることを確認したいので、コードに新しい行を追加します。
void CloseReplay(void) { ArrayFree(m_Ticks.Info); ChartClose(m_IdReplay); SymbolSelect(def_SymbolReplay, false); CustomSymbolDelete(def_SymbolReplay); GlobalVariableDel(def_GlobalVariableReplay); }
この呼び出しはまったく重要ではないと思われるかもしれません。ただし、プログラミングのベストプラクティスの1つは、作成したものをすべてクリーンアップするか、割り当てたすべてのメモリを明示的に再利用することです。そして、それがまさに私たちが今のところおこなっていることです。ティックの読み込み中に割り当てられたメモリがオペレーティングシステムに返されることを保証します。これは通常、プラットフォームを閉じるか、チャート上のプログラムを終了するときにおこなわれます。ただし、プラットフォームがすでにそれをおこなっている場合でも、これが完了していることを確認することは良いことです。確信できなければなりません。
障害が発生し、リソースがオペレーティングシステムに返されない場合、そのリソースを再度使用しようとしても使用できなくなる可能性があります。これはプラットフォームやオペレーティングシステムのエラーによるものではなく、プログラミング時の忘れによるものです。
結論
以下のビデオでは、現在の開発段階でシステムがどのように動作するかをご覧いただけます。以前のバーは8月4日に終了することに注意してください。リプレイの初日は、8月5日の最初のティックから始まります。ただし、リプレイを8月6日まで進めてから、8月5日の初めに戻ることができます。以前のバージョンのリプレイシステムではこれは不可能でしたが、今ではその機会が与えられています。
よく見ると、システムにエラーがあることがわかります。このエラーは次の記事で修正し、市場リプレイをさらに改善し、より安定して直感的に使用できるようにします。
添付ファイルには、ビデオで使用されているソースコードとファイルが含まれています。これらを使用して、構成ファイルの作成をより深く理解し、実践してください。構成ファイルは時間の経過とともに積極的に変更され、その機能が向上するため、今からこの段階の検討を開始することが重要です。したがって、私たちはこれを今すぐ最初から理解する必要があります。
MetaQuotes Ltdによりポルトガル語から翻訳されました。
元の記事: https://www.mql5.com/pt/articles/10768





- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索