
リプレイシステムの開発 - 市場シミュレーション(第21回):FOREX (II)
はじめに
前回の「リプレイシステムの開発 - 市場シミュレーション(第20回):FOREX (I)」稿では、リプレイ/シミュレーションシステムの組み立てというか、適合を始めました。これは、例えばFOREXのような市場データを使用できるようにするためであり、少なくともこの市場のリプレイを作成できるようにするためです。
この可能性を発展させるためには、システムに多くの変更と調整を加える必要がありましたが、この記事ではFOREX市場だけを考えているわけではありません。最後に取引された価格を見るだけでなく、特定のタイプの資産や市場でよく見られるBIDベースの表示システムも使用できるようになったため、考えられる市場の種類を幅広くカバーできるようになったことがわかります。
ただし、これですべてではありません。また、ティックデータが一定期間、ある資産の取引がなかったことを示す場合に、リプレイ/シミュレーションシステムが「ロック」されるのを防ぐ方法も導入しました。システムは時限モードになっており、一定時間が経過すると解除され、再び使用できるようになっていました。現在のところ、そのような問題には遭遇していませんが、新たな課題や問題はまだ残っており、その解決と、リプレイ/シミュレーションシステムのさらなる改良が待たれます。
構成ファイルの問題解決
構成ファイルをより使いやすくすることから始めましょう。少し作業が難しくなっていることにお気づきかもしれません。リプレイまたはシミュレーションを設定しようとするときに、サービスの開始時にエラーが発生したことを示すメッセージが表示されず、チャートが完全に空白のままでコントロール指標にアクセスできずにユーザーが動揺することは望ましくありません。
その結果、リプレイ/シミュレーションサービスを使用しようとすると、図に示すような状況が発生する可能性があります。01:
図01:ブートシーケンスの失敗
このエラーは、誤用や操作方法の誤解によるものではありません。カスタム資産に配置されていたすべてのデータが完全に削除されたためだと思われます。これは、資産の構成が変更された結果、あるいは資産に存在するデータが破壊された結果、発生する可能性があります。
しかし、いずれにせよ、1分足バーを読み込んだ後にティック取引を読み込むと、この障害はより頻繁に発生します。この場合、システムは価格の表示方法を変更する必要があることを理解し、この変更をおこなうことで、前回の記事で示したように、すべてのバーが削除されます。
この問題を解決するためには、まず、前のバーを読み込む前にティックの読み込みを宣言しなければなりません。これによって問題は解決されますが、同時にユーザーは構成ファイルの構造に従わざるを得なくなります。これは個人的にはあまり意味がありません。というのも、構成ファイルに書かれていることを分析し実行する役割を担うプログラムを設計することで、特定の構文が守られている限り、ユーザーがどのような順序で宣言しても構わないようにすることができるからです。
基本的に、この問題を解決する方法は1つしかありません。このパスの作り方によって、若干のバリエーションは生まれるが、基本的にはいつも同じです。しかし、これはプログラミングの観点からの話です。要するに、構成ファイル全体を読み込んで、必要な要素に必要な順番でアクセスし、すべてが完璧に調和するようにする必要があるのです。
このテクニックのバリエーションとして、FileSeek関数とFileTell関数を使用して、必要な順序で読み込めるようにする方法があります。ファイルに一度だけアクセスし、目的の要素にメモリから直接アクセスすることもできるし、ファイルを断片的に読み込んで、すでにメモリにある場合と同じ作業をおこなうこともできます。しかし、別の選択肢もあります。
個人的には、図01のような結果にならないよう、メモリに完全に読み込んでから部分的に読み込む方が好きです。そこで次のテクニックを使用します。構成ファイルをメモリに読み込んでからデータを読み込み、資産がリプレイやシミュレーションで使用できるようにします。
プログラミングが構成ファイルのシーケンスをオーバーライドする機能を作るには、まず変数のセットを作らなければなりません。でも、すべてをゼロからプログラムするつもりはありません。その代わりに、MQL5にすでに含まれている関数を利用することにしましょう。
より正確には、標準的なMQL5ライブラリを使用します。使用するのは、このライブラリで紹介されているもののほんの一部です。このアプローチの大きな利点は、含まれている機能がすでに徹底的にテストされていることです。将来のある時点でプログラムに改良を加えた場合、それがどのようなものであれ、システムは自動的にそれを受け入れます。こうすることで、テストや最適化の段階で必要な時間が減るため、プログラミングプロセスが非常に簡単になります。
では、必要なものを見てみましょう。これは以下のコードに示されています。
#include "C_FileBars.mqh" #include "C_FileTicks.mqh" //+------------------------------------------------------------------+ #include <Arrays\ArrayString.mqh> //+------------------------------------------------------------------+ class C_ConfigService : protected C_FileTicks { // ... Internal class code .... }
ここではMQL5で提供されているインクルードファイルを使用します。ここでの目的にはこれで十分です。
この後、クラスのprivateグローバル変数が必要になります。
private : //+------------------------------------------------------------------+ enum eTranscriptionDefine {Transcription_INFO, Transcription_DEFINE}; string m_szPath; struct st001 { CArrayString *pTicksToReplay, *pBarsToTicks, *pTicksToBars, *pBarsToPrev; }m_GlPrivate;
構造体の中にあるこれらの変数は、実際には使用するクラスへのポインタです。ポインタの操作は変数の操作とは異なることに注意することが重要です。なぜなら、ポインタはメモリ上の場所、より正確にはアドレスを指す構造体であるのに対し、変数は値のみを含むからです。
この話全体で重要なのは、デフォルトで、多くのプログラマー(特にプログラミングを学び始めたばかりのプログラマー)が楽なように、MQL5では実際にはC/C++とまったく同じポインタの概念は使わないということです。C/C++でプログラミングをしたことのある方なら、ポインタがいかに便利で、同時に危険で混乱を生じるものであるかをご存じでしょう。しかしMQL5では、開発者はポインタの使用に伴う混乱や危険性の多くを排除しようとしています。実用的な目的のため、実際にはCArrayStringクラスへのアクセスにポインタを使用することに注意すべきです。
CArrayStringクラスのメソッドの詳細については、そのドキュメントを参照してください。CArrayStringをクリックして、すでに利用可能なものを見てみましょう。ここでの目的にはかなり適しています。
オブジェクトへのアクセスにはポインタを使用するので、まずポインタを初期化する必要があります。
bool SetSymbolReplay(const string szFileConfig) { // ... Internal code ... m_GlPrivate.pTicksToReplay = m_GlPrivate.pTicksToBars = m_GlPrivate.pBarsToTicks = m_GlPrivate.pBarsToPrev = NULL; // ... The rest of the code ... }
おそらくプログラマーがポインタを使用するときに特にC/C++で犯す最大の間違いは、ポインタデータが初期化される前に使用しようとすることでしょう。ポインタの危険な点は、初期化される前に使用可能なメモリ領域を指すことがないことです。慣れない記憶領域で読もうとする、特に書こうとすることは、ほとんどの場合、重要な部分に書いているか、間違った情報を読んでいることを意味します。この場合、システムはしばしばクラッシュし、さまざまな問題を引き起こします。したがって、プログラムでポインタを使用する際には十分ご注意ください。
すべての注意事項を念頭に置いて、リプレイ/シミュレーションシステムの設定機能でどのように入るかを見てみましょう。関数全体を以下のコードに示します。
bool SetSymbolReplay(const string szFileConfig) { #define macroFileName ((m_szPath != NULL ? m_szPath + "\\" : "") + szInfo) int file, iLine; char cError, cStage; string szInfo; bool bBarsPrev; if ((file = FileOpen("Market Replay\\" + szFileConfig, FILE_CSV | FILE_READ | FILE_ANSI)) == INVALID_HANDLE) { Print("Failed to open the configuration file [", szFileConfig, "]. Closing the service..."); return false; } Print("Loading ticks for replay. Please wait...."); ArrayResize(m_Ticks.Rate, def_BarsDiary); m_Ticks.nRate = -1; m_Ticks.Rate[0].time = 0; iLine = 1; cError = cStage = 0; bBarsPrev = false; m_GlPrivate.pTicksToReplay = m_GlPrivate.pTicksToBars = m_GlPrivate.pBarsToTicks = m_GlPrivate.pBarsToPrev = NULL; while ((!FileIsEnding(file)) && (!_StopFlag) && (cError == 0)) { switch (GetDefinition(FileReadString(file), szInfo)) { case Transcription_DEFINE: cError = (WhatDefine(szInfo, cStage) ? 0 : 1); break; case Transcription_INFO: if (szInfo != "") switch (cStage) { case 0: cError = 2; break; case 1: if (m_GlPrivate.pBarsToPrev == NULL) m_GlPrivate.pBarsToPrev = new CArrayString(); (*m_GlPrivate.pBarsToPrev).Add(macroFileName); pFileBars = new C_FileBars(macroFileName); if ((m_dtPrevLoading = (*pFileBars).LoadPreView()) == 0) cError = 3; else bBarsPrev = true; delete pFileBars; break; case 2: if (m_GlPrivate.pTicksToReplay == NULL) m_GlPrivate.pTicksToReplay = new CArrayString(); (*m_GlPrivate.pTicksToReplay).Add(macroFileName); if (LoadTicks(macroFileName) == 0) cError = 4; break; case 3: if (m_GlPrivate.pTicksToBars == NULL) m_GlPrivate.pTicksToBars = new CArrayString(); (*m_GlPrivate.pTicksToBars).Add(macroFileName); if ((m_dtPrevLoading = LoadTicks(macroFileName, false)) == 0) cError = 5; else bBarsPrev = true; break; case 4: if (m_GlPrivate.pBarsToTicks == NULL) m_GlPrivate.pBarsToTicks = new CArrayString(); (*m_GlPrivate.pBarsToTicks).Add(macroFileName); if (!BarsToTicks(macroFileName)) cError = 6; break; case 5: if (!Configs(szInfo)) cError = 7; break; } break; }; iLine += (cError > 0 ? 0 : 1); } FileClose(file); Cmd_TicksToReplay(cError); Cmd_BarsToTicks(cError); bBarsPrev = (Cmd_TicksToBars(cError) ? true : bBarsPrev); bBarsPrev = (Cmd_BarsToPrev(cError) ? true : bBarsPrev); switch(cError) { case 0: if (m_Ticks.nTicks <= 0) { Print("No ticks to use. Closing the service..."); cError = -1; }else if (!bBarsPrev) FirstBarNULL(); break; case 1 : Print("Command in line ", iLine, " could not be recognized by the system..."); break; case 2 : Print("The contents of the line are unexpected for the system ", iLine); break; default : Print("Error occurred while accessing one of the specified files..."); } return (cError == 0 ? !_StopFlag : false); #undef macroFileName }
前のコードで消された部分は、ここではもう必要ないので削除しました。別の場所に移されました。しかし、新しい場所を見る前に、この段階でコードがどのように機能するかにご注目ください。
常に異なるポインタにアクセスしているにもかかわらず、ここではかなり繰り返しの多いコードになっているのがおわかりかと思います。
このコードは、new演算子がクラスが存在するメモリ領域を作成することを目的としています。同時に、この演算子はクラスを初期化します。初期化コンストラクタがないため、クラスは生成され、デフォルト値で初期化されます。
クラスが初期化されると、ポインタ値はNULLではなくなります。ポインタは最初の要素しか参照しないので、ここで配列の話をしていなければ、リストのように見えるでしょう。ポインタがメモリ上の正しい場所を指すようになったので、クラスメソッドの1つを使用して文字列を追加することができます。
これはほとんど透過的におこなわれます。私たちは、追加した情報がクラスのどこでどのように整理されているかを知る必要はありません。本当に重要なのは、メソッドを呼び出すたびにクラスがデータを保存してくれることです。
構成ファイルに記載されるファイルを参照したことはありません。リアルタイムでおこなわれるのは、基本的な操作情報の設定だけです。ヒストグラムであれティックデータであれ、データの読み込みは後でおこないます。
システムが構成ファイルの読み込みを終えたら、次の手順に進みます。さて、このシステムを追加したり使用したりする際に、ぜひとも考えていただきたいことがあります。これから説明することは、ご自分の特定のシステムにとって重要かもしれないので、細心の注意を払ってください。私たちにとって重要なことではありません。
ここには4つの関数があり、その順番が最終的な結果に影響します。とりあえず、それぞれの関数のコードについては気にしないでください。ここでは、コード内での出現順序を考慮する必要があります。出現順序に応じて、システムは何らかの方法でグラフを表示するためです。複雑かもしれませんが、状況を理解しましょう。使用するデータベースの種類によって、どの順番が最も適切かを知る必要があります。
コードで使用されている順序で、システムは以下のアクションを実行します。まず、実際のデータのティックを読み取ります。次に、モデリングプロセスを使用してデータをティックに変換します。次に、ティックからバーへの最初の変換に固執してバーを作成し、最後に1分足を読み込みます。
データは上記の順番通りに読み込まれます。この順番を変えたければ、これらの関数が呼ばれる順番を変えなければなりません。しかし、これからモデル化するティックを読む前に、以前のバーを読むべきではありません。これは、リプレイで使用するためにティックを読み取ったり、テスターで使用するためにバーを読み取ったりすると、チャート上のすべてのものが削除されてしまうためです。これは、前回の記事で述べた詳細と関連しています。使用または読み込みの順序が、構成ファイルではなく、システム内で決定されるという事実は、ティックの前にバーを宣言することを可能にし、同時にシステムが起こりうる問題に対処することを可能にします。
ただし、問題はそれほど単純ではありません。そうすることで、イベントの順番というか、読む順番をシステムに決めさせるのです。しかし、すべてのデータをメモリに読み込んでから解釈するというやり方では、読み込みに失敗した行を正確に報告することは難しいです。アクセスできなかったファイルデータの名前が残っています。
エラーが発生した正確な行をシステムが報告できないというこの不都合を解決するためには、コードに小さくてごく簡単な追加をしなければなりません。覚えておいてください。システムの機能は低下させたくありません。その代わり、できるだけ多くのケースをカバーできるよう、増やし、広げていきたいのです。
この解決策を導入する前に、私たちがなぜこの決断を下したのかを理解していただきたいと思います。
この定義の問題を解決するためには、読み込みの段階で、ファイル名とそれが宣言された行の両方を保存しなければなりません。この解決策は実にシンプルです。必要なのは、CArrayIntクラスを使用して別の変数セットを宣言することだけです。このクラスは、それぞれのファイル名に対応する文字列を格納します。
標準ライブラリを使用するので、一見するとかなりいい解決策のように思えますが、独自の解決策を開発する場合に必要な変数よりも多くの変数を追加しなければならないので、少々高くつきます。標準ライブラリで使用されているクラスと同じ原理を使用しますが、同時に大量のデータを扱うことができます。
標準ライブラリにある、すでにテストされ実装された解決策を使用するのですむので、このやり方はプロジェクトを複雑にしていると思われるかもしれません。しかし、標準ライブラリで利用可能なこれらのメソッドをすべて必要とするわけではないので、ここではあまり意味がありません。必要なのは2つだけです。このように、導入コストは追加人件費を補うものです。つまり、プロジェクトに新しいクラスが出現したわけです。C_Arrayです。
その全コードを以下に示します。
#property copyright "Daniel Jose" //+------------------------------------------------------------------+ class C_Array { private : //+------------------------------------------------------------------+ string m_Info[]; int m_nLine[]; int m_maxIndex; //+------------------------------------------------------------------+ public : C_Array() :m_maxIndex(0) {}; //+------------------------------------------------------------------+ ~C_Array() { if (m_maxIndex > 0) { ArrayResize(m_nLine, 0); ArrayResize(m_Info, 0); } }; //+------------------------------------------------------------------+ bool Add(const string Info, const int nLine) { m_maxIndex++; ArrayResize(m_Info, m_maxIndex); ArrayResize(m_nLine, m_maxIndex); m_Info[m_maxIndex - 1] = Info; m_nLine[m_maxIndex - 1] = nLine; return true; } //+------------------------------------------------------------------+ string At(const int Index, int &nLine) const { if (Index >= m_maxIndex) { nLine = -1; return ""; } nLine = m_nLine[Index]; return m_Info[Index]; } //+------------------------------------------------------------------+ }; //+------------------------------------------------------------------+
シンプルでコンパクトでありながら、私たちが必要とする目的に完璧に合致していることにご注目ください。このクラスを使用すると、リプレイやシミュレーションに必要な情報を含むファイルの行と名前の両方を保存できるようになります。これらのデータはすべて、構成ファイルで宣言されます。したがって、このクラスをプロジェクトに追加することで、同じレベルの機能を維持し、むしろ向上させることができます。今後は、FOREX市場と同様の市場からのデータをリプレイすることもできます。
リプレイ/シミュレーションのセットアップを担当するクラスのコードに、いくつかの変更を加える必要があります。これらの変更は以下のコードから始まります。
#property copyright "Daniel Jose" //+------------------------------------------------------------------+ #include "C_FileBars.mqh" #include "C_FileTicks.mqh" #include "C_Array.mqh" //+------------------------------------------------------------------+ class C_ConfigService : protected C_FileTicks { protected: //+------------------------------------------------------------------+ datetime m_dtPrevLoading; int m_ReplayCount; //+------------------------------------------------------------------+ inline void FirstBarNULL(void) { MqlRates rate[1]; for(int c0 = 0; m_Ticks.Info[c0].volume_real == 0; c0++) rate[0].close = m_Ticks.Info[c0].last; rate[0].open = rate[0].high = rate[0].low = rate[0].close; rate[0].tick_volume = 0; rate[0].real_volume = 0; rate[0].time = m_Ticks.Info[0].time - 60; CustomRatesUpdate(def_SymbolReplay, rate); m_ReplayCount = 0; } //+------------------------------------------------------------------+ private : //+------------------------------------------------------------------+ enum eTranscriptionDefine {Transcription_INFO, Transcription_DEFINE}; string m_szPath; struct st001 { C_Array *pTicksToReplay, *pBarsToTicks, *pTicksToBars, *pBarsToPrev; int Line; }m_GlPrivate;
これらの項目だけをファイルに追加する必要がありました。また、構成機能の修正も必要です。次のようになります。
bool SetSymbolReplay(const string szFileConfig) { #define macroFileName ((m_szPath != NULL ? m_szPath + "\\" : "") + szInfo) int file; char cError, cStage; string szInfo; bool bBarsPrev; if ((file = FileOpen("Market Replay\\" + szFileConfig, FILE_CSV | FILE_READ | FILE_ANSI)) == INVALID_HANDLE) { Print("Failed to open configuration file [", szFileConfig, "]. Closing the service..."); return false; } Print("Loading ticks for replay. Please wait...."); ArrayResize(m_Ticks.Rate, def_BarsDiary); m_Ticks.nRate = -1; m_Ticks.Rate[0].time = 0; cError = cStage = 0; bBarsPrev = false; m_GlPrivate.Line = 1; m_GlPrivate.pTicksToReplay = m_GlPrivate.pTicksToBars = m_GlPrivate.pBarsToTicks = m_GlPrivate.pBarsToPrev = NULL; while ((!FileIsEnding(file)) && (!_StopFlag) && (cError == 0)) { switch (GetDefinition(FileReadString(file), szInfo)) { case Transcription_DEFINE: cError = (WhatDefine(szInfo, cStage) ? 0 : 1); break; case Transcription_INFO: if (szInfo != "") switch (cStage) { case 0: cError = 2; break; case 1: if (m_GlPrivate.pBarsToPrev == NULL) m_GlPrivate.pBarsToPrev = new C_Array(); (*m_GlPrivate.pBarsToPrev).Add(macroFileName, m_GlPrivate.Line); break; case 2: if (m_GlPrivate.pTicksToReplay == NULL) m_GlPrivate.pTicksToReplay = new C_Array(); (*m_GlPrivate.pTicksToReplay).Add(macroFileName, m_GlPrivate.Line); break; case 3: if (m_GlPrivate.pTicksToBars == NULL) m_GlPrivate.pTicksToBars = new C_Array(); (*m_GlPrivate.pTicksToBars).Add(macroFileName, m_GlPrivate.Line); break; case 4: if (m_GlPrivate.pBarsToTicks == NULL) m_GlPrivate.pBarsToTicks = new C_Array(); (*m_GlPrivate.pBarsToTicks).Add(macroFileName, m_GlPrivate.Line); break; case 5: if (!Configs(szInfo)) cError = 7; break; } break; }; m_GlPrivate.Line += (cError > 0 ? 0 : 1); } FileClose(file); Cmd_TicksToReplay(cError); Cmd_BarsToTicks(cError); bBarsPrev = (Cmd_TicksToBars(cError) ? true : bBarsPrev); bBarsPrev = (Cmd_BarsToPrev(cError) ? true : bBarsPrev); switch(cError) { case 0: if (m_Ticks.nTicks <= 0) { Print("No ticks to use. Closing the service..."); cError = -1; }else if (!bBarsPrev) FirstBarNULL(); break; case 1 : Print("Command in line ", m_GlPrivate.Line, " could not be recognized by the system..."); break; case 2 : Print("The contents of the line is unexpected for the system: ", m_GlPrivate.Line); break; default : Print("Access error occurred in the files indicated in line: ", m_GlPrivate.Line); } return (cError == 0 ? !_StopFlag : false); #undef macroFileName }
これで、エラーが発生したことと、どの行でエラーが発生したかをユーザーに伝えることができることがわかります。ご覧の通り、新しい要素が加わっただけで、関数は以前とほとんど変わらないため、これにかかる費用は実質ゼロです。標準ライブラリを使った場合にどれだけの作業が必要になるかを考えれば、これは非常に良いことです。どうか正しく理解してください。標準ライブラリを使用するなと言っているのではなく、独自のソリューションを作る必要がある場合もあることを示しただけです。その場合、コストが作業を上回るからです。
ここで、システムに追加された新しい関数、つまり上に示した4つの関数を見てみましょう。どれも非常にシンプルで、そのコードを以下に示しますが、どれも同じ原理で動くので、面倒にならないようにすぐに説明しましょう。
inline void Cmd_TicksToReplay(char &cError) { string szInfo; if (m_GlPrivate.pTicksToReplay != NULL) { for (int c0 = 0; (c0 < INT_MAX) && (cError == 0); c0++) { if ((szInfo = (*m_GlPrivate.pTicksToReplay).At(c0, m_GlPrivate.Line)) == "") break; if (LoadTicks(szInfo) == 0) cError = 4; } delete m_GlPrivate.pTicksToReplay; } } //+------------------------------------------------------------------+ inline void Cmd_BarsToTicks(char &cError) { string szInfo; if (m_GlPrivate.pBarsToTicks != NULL) { for (int c0 = 0; (c0 < INT_MAX) && (cError == 0); c0++) { if ((szInfo = (*m_GlPrivate.pBarsToTicks).At(c0, m_GlPrivate.Line)) == "") break; if (!BarsToTicks(szInfo)) cError = 6; } delete m_GlPrivate.pBarsToTicks; } } //+------------------------------------------------------------------+ inline bool Cmd_TicksToBars(char &cError) { bool bBarsPrev = false; string szInfo; if (m_GlPrivate.pTicksToBars != NULL) { for (int c0 = 0; (c0 < INT_MAX) && (cError == 0); c0++) { if ((szInfo = (*m_GlPrivate.pTicksToBars).At(c0, m_GlPrivate.Line)) == "") break; if ((m_dtPrevLoading = LoadTicks(szInfo, false)) == 0) cError = 5; else bBarsPrev = true; } delete m_GlPrivate.pTicksToBars; } return bBarsPrev; } //+------------------------------------------------------------------+ inline bool Cmd_BarsToPrev(char &cError) { bool bBarsPrev = false; string szInfo; C_FileBars *pFileBars; if (m_GlPrivate.pBarsToPrev != NULL) { for (int c0 = 0; (c0 < INT_MAX) && (cError == 0); c0++) { if ((szInfo = (*m_GlPrivate.pBarsToPrev).At(c0, m_GlPrivate.Line)) == "") break; pFileBars = new C_FileBars(szInfo); if ((m_dtPrevLoading = (*pFileBars).LoadPreView()) == 0) cError = 3; else bBarsPrev = true; delete pFileBars; } delete m_GlPrivate.pBarsToPrev; } return bBarsPrev; } //+------------------------------------------------------------------+
でも、ちょっと待ってください。上の4つの関数をよく見ると、繰り返しのコードがたくさんあります。繰り返しのコードがたくさんある場合、どうすればいいのでしょうか。私たちは、コードの再利用を可能にするために、機能をより基本的なレベルにまで下げようとしています。これは私の愚かさ、不注意に聞こえるかもしれませんが、この連載では、プログラムがゼロからどのように作られるかを紹介するだけでなく、優れたプログラマーがメンテナンス作業を減らしてプログラムを作成することで、どのようにプログラムを改善しているかを紹介しています。
重要なのは、プログラミング経験が何年多いかということではなく、たとえプログラミングの世界に足を踏み入れたばかりであっても、多くの場合、関数を改善したりコードを削減したりできるため、開発が容易かつ高速になることを理解できるという事実です。再利用性を高めるためにコードを最適化することは、最初は時間の無駄に思えるかもしれません。しかし、何かを改善する必要があるときに、複数の場所で繰り返すことが少なくなるため、開発全体を通して役に立つでしょう。プログラミングをするとき、ご自分にこう問いかけてみてください。このタスクの実行に使われるコードの量を減らすことはできるでしょうか。
これはまさに以下の関数がおこなうことです。つまり、システムを改良する必要がある場合、4つの関数を変更するのではなく、1つの関数を変更すればよいのです。
inline bool CMD_Array(char &cError, eWhatExec e1) { bool bBarsPrev = false; string szInfo; C_FileBars *pFileBars; C_Array *ptr = NULL; switch (e1) { case eTickReplay: ptr = m_GlPrivate.pTicksToReplay; break; case eTickToBar : ptr = m_GlPrivate.pTicksToBars; break; case eBarToTick : ptr = m_GlPrivate.pBarsToTicks; break; case eBarPrev : ptr = m_GlPrivate.pBarsToPrev; break; } if (ptr != NULL) { for (int c0 = 0; (c0 < INT_MAX) && (cError == 0); c0++) { if ((szInfo = ptr.At(c0, m_GlPrivate.Line)) == "") break; switch (e1) { case eTickReplay: if (LoadTicks(szInfo) == 0) cError = 4; break; case eTickToBar : if ((m_dtPrevLoading = LoadTicks(szInfo, false)) == 0) cError = 5; else bBarsPrev = true; break; case eBarToTick : if (!BarsToTicks(szInfo)) cError = 6; break; case eBarPrev : pFileBars = new C_FileBars(szInfo); if ((m_dtPrevLoading = (*pFileBars).LoadPreView()) == 0) cError = 3; else bBarsPrev = true; delete pFileBars; break; } } delete ptr; } return bBarsPrev; }
非常に重要なのは、この関数は同じ関数の中にありながら、実際には2つのセグメントに分かれているということです。最初のセグメントでは、構成ファイルの読み込み時に格納されたデータにアクセスするためのポインタを初期化しています。このセグメントは非常にシンプルで、理解するのに苦労する人はいないと思います。この後、2つ目のセグメントに移り、ユーザー指定のファイルの内容を読み込みます。ここで、それぞれのポインタは、メモリに格納された内容を読み出し、その仕事をします。最後にポインタは破棄され、使用されたメモリは解放されます。
しかし、設定関数に戻る前に、このシステムでできることがさらにあることをお見せしたいと思います。このトピックの冒頭で、「読み込みがどうなるかを考えながらプログラムをコンパイルする必要がある」と言いましたが、記事を書く過程で考えた結果、常にプログラムを再コンパイルする必要がないように範囲を広げることは可能だと判断しました。
アイデアは、まずシミュレーションされたデータを読み込み、必要に応じて過去のバーを読み込むことです。リプレイ/シミュレーションで使用されるティックも、以前のバーも、タイプTickまたはタイプBarのファイルから取得することができますが、何らかの方法でそれを示す必要があります。そこで、ユーザーが定義できる変数を使用することを提案します。物事をシンプルにし、長い目で見て実行可能なものにするために、具体的に説明しましょう。そのために、次の表を使用してみましょう。
値 | 読み込みモード |
---|---|
1 | ティック/ティックモード |
2 | ティック/バーモード |
3 | バー/ティックモード |
4 | バー/バーモード |
表01:内部読み取りモデルのデータ
上の表は、読み取りがどのようにおこなわれるかを示しています。リプレイやシミュレーションで使用されるデータを読み込むことから始め、次にチャート上の前データとして使用される値を読み込みます。そして、もしユーザーが1の値を報告すれば、システムは次のように動作します。実ティックのファイル - ティックに変換されたバーのファイル - 以前のデータに変換されたティックのファイル - 以前のバーのファイル。これがオプション1ですが、何らかの理由で、同じデータベースを使用しながら異なるタイプの分析をおこなうようにシステムを変更したいとします。この場合、例えば値4を報告することができ、システムは同じデータベースを使用しますが、読み取りは以下の順序でおこなわれるため、結果は若干異なります。ティックに変換されたバーのファイル - 実ティックのファイル - 以前のバーのファイル - 以前のデータに変換されたティックのファイル...これを試してみると、平均値などの指標は、同じデータベースを使用していても、あるモードから別のモードへと小さな変化を発生させることがわかるでしょう。
この実装には最小限のプログラミングしか必要ないので、このようなリソースを提供することは理にかなっていると思います。
では、このようなシステムをどのように導入するか見てみましょう。まず、作業を続けられるように、グローバルだがprivateな変数をクラスに追加します。
class C_ConfigService : protected C_FileTicks { protected: //+------------------------------------------------------------------+ datetime m_dtPrevLoading; int m_ReplayCount, m_ModelLoading;
この変数にはモデルが格納されます。ユーザーが構成ファイルで定義していない場合は、初期値を設定します。
C_ConfigService() :m_szPath(NULL), m_ModelLoading(1) { }
つまり、デフォルトでは常にティック/ティックモードを使用します。この後、ユーザーに使用する値を指定する機会を与える必要があります。これも簡単なことです。
inline bool Configs(const string szInfo) { const string szList[] = { "PATH", "POINTSPERTICK", "VALUEPERPOINTS", "VOLUMEMINIMAL", "LOADMODEL" }; string szRet[]; char cWho; if (StringSplit(szInfo, '=', szRet) == 2) { StringTrimRight(szRet[0]); StringTrimLeft(szRet[1]); for (cWho = 0; cWho < ArraySize(szList); cWho++) if (szList[cWho] == szRet[0]) break; switch (cWho) { case 0: m_szPath = szRet[1]; return true; case 1: CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE, StringToDouble(szRet[1])); return true; case 2: CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_VALUE, StringToDouble(szRet[1])); return true; case 3: CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_VOLUME_STEP, StringToDouble(szRet[1])); return true; case 4: m_ModelLoading = StringInit(szRet[1]); m_ModelLoading = ((m_ModelLoading < 1) && (m_ModelLoading > 4) ? 1 : m_ModelLoading); return true; } Print("Variable >>", szRet[0], "<< undefined."); }else Print("Set-up configuration >>", szInfo, "<< invalid."); return false; }
ここでは、表01に示すように、ユーザーが変数を操作する際に使用する名前を指定し、また使用する値を定義することができます。ここでちょっとしたポイントがあります。その値が、システムが期待する範囲内であることを確認する必要があります。ユーザーが期待される値以外の値を指定した場合、エラーは発生しませんが、システムはデフォルト値を使用し始めます。
これで、最終的な読み込みと設定の関数を見ることができます。以下をご覧ください。
bool SetSymbolReplay(const string szFileConfig) { #define macroFileName ((m_szPath != NULL ? m_szPath + "\\" : "") + szInfo) int file; char cError, cStage; string szInfo; bool bBarsPrev; if ((file = FileOpen("Market Replay\\" + szFileConfig, FILE_CSV | FILE_READ | FILE_ANSI)) == INVALID_HANDLE) { Print("Failed to open configuration file [", szFileConfig, "]. Closing the service..."); return false; } Print("Loading ticks for replay. Please wait...."); ArrayResize(m_Ticks.Rate, def_BarsDiary); m_Ticks.nRate = -1; m_Ticks.Rate[0].time = 0; cError = cStage = 0; bBarsPrev = false; m_GlPrivate.Line = 1; m_GlPrivate.pTicksToReplay = m_GlPrivate.pTicksToBars = m_GlPrivate.pBarsToTicks = m_GlPrivate.pBarsToPrev = NULL; while ((!FileIsEnding(file)) && (!_StopFlag) && (cError == 0)) { switch (GetDefinition(FileReadString(file), szInfo)) { case Transcription_DEFINE: cError = (WhatDefine(szInfo, cStage) ? 0 : 1); break; case Transcription_INFO: if (szInfo != "") switch (cStage) { case 0: cError = 2; break; case 1: if (m_GlPrivate.pBarsToPrev == NULL) m_GlPrivate.pBarsToPrev = new C_Array(); (*m_GlPrivate.pBarsToPrev).Add(macroFileName, m_GlPrivate.Line); break; case 2: if (m_GlPrivate.pTicksToReplay == NULL) m_GlPrivate.pTicksToReplay = new C_Array(); (*m_GlPrivate.pTicksToReplay).Add(macroFileName, m_GlPrivate.Line); break; case 3: if (m_GlPrivate.pTicksToBars == NULL) m_GlPrivate.pTicksToBars = new C_Array(); (*m_GlPrivate.pTicksToBars).Add(macroFileName, m_GlPrivate.Line); break; case 4: if (m_GlPrivate.pBarsToTicks == NULL) m_GlPrivate.pBarsToTicks = new C_Array(); (*m_GlPrivate.pBarsToTicks).Add(macroFileName, m_GlPrivate.Line); break; case 5: if (!Configs(szInfo)) cError = 7; break; } break; }; m_GlPrivate.Line += (cError > 0 ? 0 : 1); } FileClose(file); CMD_Array(cError, (m_ModelLoading <= 2 ? eTickReplay : eBarToTick)); CMD_Array(cError, (m_ModelLoading <= 2 ? eBarToTick : eTickReplay)); bBarsPrev = (CMD_Array(cError, ((m_ModelLoading & 1) == 1 ? eTickToBar : eBarPrev)) ? true : bBarsPrev); bBarsPrev = (CMD_Array(cError, ((m_ModelLoading & 1) == 1 ? eBarPrev : eTickToBar)) ? true : bBarsPrev); switch(cError) { case 0: if (m_Ticks.nTicks <= 0) { Print("No ticks to use. Closing the service..."); cError = -1; }else if (!bBarsPrev) FirstBarNULL(); break; case 1 : Print("Command in line ", m_GlPrivate.Line, " could not be recognized by the system..."); break; case 2 : Print("The contents of the line is unexpected for the system: ", m_GlPrivate.Line); break; default : Print("Access error occurred in the files indicated in line: ", m_GlPrivate.Line); } return (cError == 0 ? !_StopFlag : false); #undef macroFileName }
これで、どのモデルを読み込みに使用するかを選択できる小さなシステムができました。しかし、リプレイやシミュレーションで使用するデータを読み込むことから始め、その後に前のバーとして使用するデータを読み込むことを忘れないでください。
最終的な検討事項
これで、構成ファイルに関するこの段階の作業は終了です。これで(少なくとも現時点では)、ユーザーはこの初期段階で必要なものをすべて指定できるようになります。
この記事にはかなり時間がかかりましたが、その価値はあったと思います。これで、しばらくは設定ファイルの問題を心配する必要がなくなります。どのように作られたとしても、常に期待通りに機能します。
株式市場はすでにかなり進んだ段階にあるため、次回はリプレイシミュレーションシステムをさらに発展させ、FOREX市場や同様の市場をカバーする予定です。このシステムが株式市場と同じように機能するように、私はFOREX市場に焦点を当てます。
MetaQuotes Ltdによりポルトガル語から翻訳されました。
元の記事: https://www.mql5.com/pt/articles/11153





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