リプレイシステムの開発 - 市場シミュレーション(第16回):新しいクラスシステム
はじめに
前回の「リプレイシステムの開発 - 市場シミュレーション(第15回):シミュレーターの誕生(V) - ランダムウォーク」の誕生により、無作為にデータを取得する方法を開発しました。これは、完全に適切な結果を保証するために行われました。しかし、このシステムは確かに注文をもっともらしくシミュレートすることができるとしても、シミュレーターからだけでなく、リプレイシステム自体からも、ある情報が欠けています。実際、私たちが実施しなければならないことの中には、特にシステム構築の観点から見て、かなり複雑なものもあります。よって、少なくともより良いモデルを手に入れ、次のステップで迷わないようにするために、いくつかの変更を加える必要があります。
『ランダムウォーク』を難しいと感じたとしたら、それは、私たちがシミュレーション/リプレイ体験を最高のものにするために開発すべきものを見ていないからにほかなりません。最も困難な側面のひとつは、シミュレーションやリプレイに使用されるアセットについて、少なくとも基本的なことを知らなければならないことです。今の段階では、いくつかの問題を自動的に解決することについては心配していません。
前回の記事を注意深く読めば、EXCELのようなプログラムを使えば、可能性のある市場シナリオを作成できることに気づくでしょう。しかし、他のアセットからの情報を利用し、それらを組み合わせてより複雑なシミュレーションをおこなうこともできます。このため、資産そのものに関する欠落した情報を探すような完全自動化システムを作るのは難しくなります。もうひとつ小さな問題があります。そこで、ここではメンテナンスや改良を容易にするために、C_Replay.mqhヘッダーファイルの内容を切り取って配布することにします。私は大きなクラスで仕事をするのは好きではありません。その理由は、多くの場合、コードが完全に最適化されていない可能性があるからです。したがって、作業を簡単にするために、両方の変更を加えます。つまり、C_Replayクラスの内容を複数のクラスに分散し、同時に最も関連性の高い部分を実装します。
新しいクラスシステムによるサービスの実装
以前の記事で取り上げたコードは、概してほとんど変更されていませんが、この新しい実装には新しいモデルが含まれています。このモデルは拡張が容易であり、また要素が単純な方法で接続されているため、プログラミングの知識が豊富でない人にも理解しやすいです。長くて読み疲れるような関数はありません。少し奇妙に思えるような細部もありますが、奇妙なことに、それがコードをより安全で安定した、構造的に健全なものにしています。これらの詳細のひとつは、C++やCのレガシー言語で使われているのとは異なるポインタの使い方です。しかし、動作はこれらの言語と非常に似ています。
ポインタを使うことで、他の方法では不可能だったことが可能になります。C++で導入された機能の多くはMQL5では利用できませんが、ポインタを最も単純な形で使用するという単純な事実により、少なくともプログラミングと構文に関しては、より柔軟で楽しい方法でモデリングを実装することができます。
したがって、現在のバージョンの新しいサービスファイルは次のようになります。
#property service #property icon "\\Images\\Market Replay\\Icon.ico" #property copyright "Daniel Jose" #property version "1.16" #property description "Replay-simulation system for MT5." #property description "It is independent from the Market Replay." #property description "For details see the article:" #property link "https://www.mql5.com/ru/articles/11095" //+------------------------------------------------------------------+ #define def_Dependence "\\Indicators\\Market Replay.ex5" #resource def_Dependence //+------------------------------------------------------------------+ #include <Market Replay\C_Replay.mqh> //+------------------------------------------------------------------+ input string user00 = "Config.txt"; //"Replay" config file. input ENUM_TIMEFRAMES user01 = PERIOD_M1; //Initial timeframe for the chart. input bool user02 = true; //Visual bar construction. input bool user03 = true; //Visualize creation metrics. //+------------------------------------------------------------------+ void OnStart() { C_Replay *pReplay; pReplay = new C_Replay(user00); if (pReplay.ViewReplay(user01)) { Print("Permission received. The replay service can now be used..."); while (pReplay.LoopEventOnTime(user02, user03)); } delete pReplay; } //+------------------------------------------------------------------+
大きな変化はないように見えるかもしれませんが、実際はすべてがまったく違います。エンドユーザーにとっては、以前のバージョンと同じで、まったく同じ動作をします。しかし、プラットフォームや特にOSによって、生成される実行ファイルは見た目も動作も異なります。クラスを変数としてではなく、メモリへのポインタとして使っているからです。よって、システム全体の挙動がまったく違います。注意深くプログラムすれば、クラスは変数としてのみ使われるクラスよりもさらに安全で安定したものになります。両者は同じ動作を示しますが、メモリ使用量の点で異なります。
さて、クラスはポインタとして使われるので、あるものは除外し、他のものを使い始めます。まず第一に、クラスは常に明示的に立ち上げられ、閉じられます。これには newとdelete演算子を使います。new演算子を使ってクラスを作成する場合、必ずクラスコンストラクタを呼び出さなければなりません。これらは決して値を返さないので、戻り値を直接確認することはできません。それはまた別の機会にしましょう。delete演算子を使っても同じことが起こり、クラスのデストラクタが呼び出されます。クラスのコンストラクタと同様、デストラクタは値を返しません。しかしコンストラクタと違って、デストラクタは引数を受け取りません。
new演算子を使ってクラスを作成し、delete演算子を使ってクラスを破棄します。これが私たちが実際にやらなければならない唯一の仕事になるでしょう。OSは、プログラムが特定のメモリ領域で実行できるように十分なメモリを割り当て、プログラムが存在する限り可能な限り安全な状態にします。しかし、C++/Cでポインタを使い慣れた人にとっては、ここに危険が潜んでいます。表記法です。C++やC言語では、ポインタに言及するときはいつも、非常に特殊な記法を使います。通常は矢印 (->)を使います。C++/Cプログラマーにとって、これはポインタが使われることを意味します。しかし、別の記法を使うこともできます。私のコードでポインタにアクセスするときに見ることができます。
加えて、もちろん変数名が使われますが、これは通常、文字pか、ptrのような組み合わせで始まります(ただし、これは厳密なルールではないので、こだわる必要はありません)。MQL5では、上のコードに示されている表記法と下のコードに示されている表記法の両方が使えますが、個人的には、ポインタを使うコードは、実際に正しい宣言を使っている方が読みやすいと思います。従って、C++/C言語の知識のおかげで、コードでは以下のような表記になります。
void OnStart() { C_Replay *pReplay; pReplay = new C_Replay(user00); if ((*pReplay).ViewReplay(user01)) { Print("Permission received. The replay service can now be used..."); while ((*pReplay).LoopEventOnTime(user02, user03)); } delete pReplay; }
実際には、コードを書くことに関連した余分な仕事があります。しかし、長年の経験を持つC++/Cプログラマーとしては、上の図のようなコードを見ると、ポインタを指していることが理解しやすくなります。そして、MQL5はC++/Cと同じようにこれを理解しているので、この表記法を使うことに問題はないと思います。上図のような表記のコードを見ても、単なるポインタなので心配する必要はありません。
新しいクラスシステムを探求し続けることができます。これらの変化だけが起こったと考える読者はかなり楽観的です。このような変更を加えるということは、クラスが特定の時間に生成され、破棄されることを明示的に保証するということであり、コードにさらにいくつかの変更を加える必要があります。コンストラクタとデストラクタは値を返しません。しかし、クラスが正しく作成されているかどうかは、どうにかして知らなければなりません。
この方法を理解するには、C_Replayクラスのブラックボックスの中を見る必要があります。これはヘッダーファイルC_Replay.mqhにあります。その内部構造を下の画像に示します。
図01:リプレイクラスの接続システム
図01は、リプレイ/シミュレーションサービスによって目的の作業を実行するために、クラスがどのように互いに接続されているかを示しています。緑色の矢印はクラスがインポートされていることを示し、そのためクラスの内部メンバーの一部はpublicになります。赤い矢印は、データはインポートされますが、シニアクラスでは表示されなくなることを示しています。C_FilesBarsクラスは緩やかなクラスです。実際に他のクラスから継承されることはありませんが、そのメソッドは他のクラスで使用されます。
このような接続がどのように作られるのかを本当に理解するには、内部で何が起こっているのかを見る必要があります。そのためには、それぞれのクラスがどのように作られ、対応するファイル内にどのように配置されているかを調べる必要があります。これらのファイルは常にクラスと同じ名前を持ちます。これは必要ではありませんが、コードがいくつか変更されたので、まだ良い習慣です。これについての詳細は省きますが、数分後にどのような変化が起きたのか、そしてその理由を見てみましょう。これは、プログラミングを始めようとしている人にとって非常に役立ちます。関数型コードがあり、それを変更して別のもの(同時に必要なもの)を生成し、コードの操作性を維持しながら、同じ状態を維持すると学習がはるかに簡単になるためです。
どのような構成になっているのかを見てみましょう。
ルーズなクラス:C_FileBars
このクラスは非常にシンプルで、ファイルに存在するバーを読み取るために必要なすべてを含んでいます。それほど大きなクラスではありません。そのコード全体を以下に示します。
#property copyright "Daniel Jose" //+------------------------------------------------------------------+ #include "Interprocess.mqh" //+------------------------------------------------------------------+ #define def_BarsDiary 1440 //+------------------------------------------------------------------+ class C_FileBars { private : int m_file; string m_szFileName; //+------------------------------------------------------------------+ inline void CheckFileIsBar(void) { string szInfo = ""; for (int c0 = 0; (c0 < 9) && (!FileIsEnding(m_file)); c0++) szInfo += FileReadString(m_file); if (szInfo != "<DATE><TIME><OPEN><HIGH><LOW><CLOSE><TICKVOL><VOL><SPREAD>") { Print("Файл ", m_szFileName, ".csv не является файлом баров."); FileClose(m_file); m_file = INVALID_HANDLE; } } //+------------------------------------------------------------------+ public : //+------------------------------------------------------------------+ C_FileBars(const string szFileNameCSV) :m_szFileName(szFileNameCSV) { if ((m_file = FileOpen("Market Replay\\Bars\\" + m_szFileName + ".csv", FILE_CSV | FILE_READ | FILE_ANSI)) == INVALID_HANDLE) Print("Could not access ", m_szFileName, ".csv with bars."); else CheckFileIsBar(); } //+------------------------------------------------------------------+ ~C_FileBars() { if (m_file != INVALID_HANDLE) FileClose(m_file); } //+------------------------------------------------------------------+ bool ReadBar(MqlRates &rate[]) { if (m_file == INVALID_HANDLE) return false; if (FileIsEnding(m_file)) return false; rate[0].time = StringToTime(FileReadString(m_file) + " " + FileReadString(m_file)); rate[0].open = StringToDouble(FileReadString(m_file)); rate[0].high = StringToDouble(FileReadString(m_file)); rate[0].low = StringToDouble(FileReadString(m_file)); rate[0].close = StringToDouble(FileReadString(m_file)); rate[0].tick_volume = StringToInteger(FileReadString(m_file)); rate[0].real_volume = StringToInteger(FileReadString(m_file)); rate[0].spread = (int) StringToInteger(FileReadString(m_file)); return true; } //+------------------------------------------------------------------+ datetime LoadPreView(const string szFileNameCSV) { int iAdjust = 0; datetime dt = 0; MqlRates Rate[1]; Print("Loading bars for Replay. Please wait...."); while (ReadBar(Rate) && (!_StopFlag)) { iAdjust = ((dt != 0) && (iAdjust == 0) ? (int)(Rate[0].time - dt) : iAdjust); dt = (dt == 0 ? Rate[0].time : dt); CustomRatesUpdate(def_SymbolReplay, Rate, 1); } return ((_StopFlag) || (m_file == INVALID_HANDLE) ? 0 : Rate[0].time + iAdjust); } //+------------------------------------------------------------------+ };
このクラスにはあまり意味がないと思うかもしれませんが、パネルファイルを開いたり、読み込んだり、カスタムリソースを構築したり、閉じたりするのに必要なものがすべて含まれています。これらはすべて、非常に具体的な手順で実装されているので、ここでの変更は重要ではありません。バーファイルを読みたい場合は、ここでおこなうことができます。
このクラスは、演算子NEWとDELETEが継続的に使用するように設計されています。そのため、メモリに残るのはその仕事をするのに十分な時間だけです。上記の演算子を使わずにこのクラスを使うことは、できるだけ避けるべきです。そうでなければ、安定性に問題が生じるかもしれません。本当にそうなるわけではありません。ただし、演算子を使うために設計されたからといって、他の手段で使うのに適しているわけではありません。
このクラスには2つのグローバル変数とprivate変数があります。これらはクラスのコンストラクタで正確に初期化され、指定されたファイルがバーファイルであるかどうかを開いてチェックします。ただし、コンストラクタはいかなる型の値も返さないことを忘れないでください。このため、ここでは何も返す方法がまったくありませんが、ファイルを無効なファイルとしてマークすることで、ファイルが期待を満たしていないことを示すことができます。閉じられた後に明らかになることもあります。読み込もうとするとエラーとなり、それに応じて報告されます。そして、その戻り値は呼び出し側で処理することができます。その結果、このクラスは、あたかもすでにバーを含むファイルであるかのように動作します。あるいは、すでにファイルの内容を含んでいる大きなオブジェクトのようにです。ここでの呼び出し側は、この大きなオブジェクトの中身を読むことになります。しかし、デストラクタが呼ばれると、ファイルは閉じられ、呼び出し元のプログラムによってクラスは破棄されます。
この種のシミュレーションは、安全性や安定性に欠けるように思えるかもしれませんが、私を信じて頂きたいと思います。C++に存在する他の演算子にもアクセスできれば、さらに面白くなるでしょう。もちろん、そのためにはコードを正しく書く必要があり、そうでなければすべてが台無しになってしまいます。しかし、MQL5はC++ではないので、この言語の能力を勉強して最大限に活用しましょう。こうすることで、言語が可能にする限界に非常に近いものを使用するシステムを手に入れることができます。
ディープなクラス:C_FileTicks
次に見ていくのは、C_FileTicksクラスです。public要素、private要素、その中間に位置する要素があるので、C_FileBarsクラスよりもはるかに複雑です。これらには特別な名前があります。PROTECTEDです。protectedという用語は、クラス間の継承に関しては特別な意味を持ちます。C++の場合、少なくとも学び始めは、すべてがかなり複雑です。これは、C++に存在するいくつかの演算子によるものです。幸いなことに、MQL5はもっとシンプルな方法でこの問題を解決しています。そのため、protectedと宣言された要素がどのように継承されるのか、そして継承の仕方によってアクセスできるのかできないのかを理解するのがずっと簡単になります。下の表を参照してください。
基本クラスでの定義 | 基本クラス継承型 | 派生クラス内でのアクセス | 派生クラスの呼び出しによるアクセス |
---|---|---|---|
private | public | アクセスが却下される | 基本クラスのデータまたはプロシージャにアクセスできない |
public | public | アクセスが許可される | 基本クラスのデータまたはプロシージャにアクセスできる |
protected | public | アクセスが許可される | 基本クラスのデータまたはプロシージャにアクセスできない |
private | private | アクセスが却下される | 基本クラスのデータまたはプロシージャにアクセスできない |
public | private | アクセスが許可される | 基本クラスのデータまたはプロシージャにアクセスできない |
protected | private | アクセスが許可される | 基本クラスのデータまたはプロシージャにアクセスできない |
private | protected | アクセスが却下される | 基本クラスのデータまたはプロシージャにアクセスできない |
public | protected | アクセスが許可される | 基本クラスのデータまたはプロシージャにアクセスできない |
protected | protected | アクセスが許可される | 基本クラスのデータまたはプロシージャにアクセスできない |
クラス要素および関数へのアクセスレベルの表
クラス内のデータやプロシージャーにアクセスできるのは、継承やアクセス定義システムを使う場合だけです。そして、その時にすべてが公表されます。それ以外の場合は、クラス継承レベルでは多かれ少なかれ可能ですが、基本クラス内部にあるプロシージャーやデータへのアクセスは不可能です。これはアクセスには依存しません。
重要:もし何かを「protected」と宣言し、クラス継承を使わずにそのデータやプロシージャーに直接アクセスしようとすると、そのようなデータやプロシージャーにはアクセスできなくなります。なぜなら、継承を使わなければ、protectedと宣言されたデータやプロシージャーはprivateとみなされ、アクセスできなくなるからです。
これはかなり複雑に思えるかと思います。しかし、実際にはもっと簡単です。しかし、このメカニズムがどのように機能するのかを本当に理解するには、実際に何度か実験してみる必要があるでしょう。ただし、これはC++よりもMQL5の方がはるかに簡単です。クラス継承の過程で、protected、場合によってはprivateと宣言されたデータや手続きへのアクセスレベルを変更する方法があるからです。本当に無茶です。しかし、MQL5ではすべてがスムーズに機能します。
これに基づいて、ようやくC_FileTicksクラスを見ることができます。これは理論的にはより複雑であるにもかかわらず、コードは比較的単純です。クラス内部の最初の要素を見て、その宣言から始めましょう。
#property copyright "Daniel Jose" //+------------------------------------------------------------------+ #include "C_FileBars.mqh" //+------------------------------------------------------------------+ #define def_MaxSizeArray 16777216 // 16 Mbytes of positions //+------------------------------------------------------------------+ #define macroRemoveSec(A) (A - (A % 60)) //+------------------------------------------------------------------+ class C_FileTicks { protected: struct st00 { MqlTick Info[]; MqlRates Rate[]; int nTicks, nRate; }m_Ticks; double m_PointsPerTick;
見てくれ、とても簡単ですが、この構造体と変数にはアクセスレベルテーブルに従ってアクセスすることができるので注意が必要です。これが終われば、クラスでいくつかのprivateプロシージャーをおこなうことになります。これらはクラスの外からはアクセスできず、公開手続きをサポートする役割を果たします。以下のように2つあります。
public : //+------------------------------------------------------------------+ bool BarsToTicks(const string szFileNameCSV) { C_FileBars *pFileBars; int iMem = m_Ticks.nTicks; MqlRates rate[1]; MqlTick local[]; pFileBars = new C_FileBars(szFileNameCSV); ArrayResize(local, def_MaxSizeArray); Print("Converting bars to ticks. Please wait..."); while ((*pFileBars).ReadBar(rate) && (!_StopFlag)) Simulation(rate[0], local); ArrayFree(local); delete pFileBars; return ((!_StopFlag) && (iMem != m_Ticks.nTicks)); } //+------------------------------------------------------------------+ datetime LoadTicks(const string szFileNameCSV, const bool ToReplay = true) { int MemNRates, MemNTicks; datetime dtRet = TimeCurrent(); MqlRates RatesLocal[]; MemNRates = (m_Ticks.nRate < 0 ? 0 : m_Ticks.nRate); MemNTicks = m_Ticks.nTicks; if (!Open(szFileNameCSV)) return 0; if (!ReadAllsTicks()) return 0; if (!ToReplay) { ArrayResize(RatesLocal, (m_Ticks.nRate - MemNRates)); ArrayCopy(RatesLocal, m_Ticks.Rate, 0, 0); CustomRatesUpdate(def_SymbolReplay, RatesLocal, (m_Ticks.nRate - MemNRates)); dtRet = m_Ticks.Rate[m_Ticks.nRate].time; m_Ticks.nRate = (MemNRates == 0 ? -1 : MemNRates); m_Ticks.nTicks = MemNTicks; ArrayFree(RatesLocal); } return dtRet; }; //+------------------------------------------------------------------+
privateプロシージャーはすべて、当連載の過去の記事ですでに説明分析されているものです。プロシージャーはこれまで説明してきたこととほとんど変わらないので、ここでは詳しく説明しません。しかし、この2つのpublic手続きに関して、注目すべきことがあります。これはBarsToTicksにあります。前のトピックで、C_FileBarsクラスは実際には継承されないルーズなクラスであると述べました。これは今でも変わりません。しかし、ある特定の場面で使用され、かなり特殊な方法でアクセスされるべきものであることも述べました。
その瞬間のひとつがここにあります。まず、非常に特殊な方法でクラスを宣言します。ここで、バーの値を取得したいファイル名を指定してクラスコンストラクタを呼び出します。この呼び出しは値を返さないことを覚えておいてほしい。NEW演算子を使用して、クラス専用に予約されたメモリ領域を確保します。MetaTrader 5はこのクラスがどこに配置されるかを実際には制御しないので、このスペースにクラスが格納されます。この情報を持っているのはオペレーティングシステムだけです。
しかしもっと便利なのは、NEW演算子から値を取得し、この値を「ポインタ」として、クラスを直接参照できることです(注:ポインタという言葉が引用符で囲まれているのは、実際にはポインタではないからです。これは、あたかもそのクラスが別の変数または定数であるかのように、そのクラスを参照できる単なる変数です。将来的には、これを使ってデータにアクセスするだけで計算は実行できない定数クラスを作る方法を紹介しましょう。非常に特殊な使い方なので、別の機会に譲ります)。この「ポインタ」が手に入れば、クラスで仕事をすることができます。したがって、データを読み込んで使用する前に、何らかの検証をおこなう必要があります。幸いなことに、読み出しの試行中に、呼び出しが正しく完了したかどうかを確認できるので、これは必要ありません。つまり、データが読み込まれたかどうかをここで確認することができます。
何らかの理由で読み取りが失敗した場合、WHILEループはそのまま閉じられます。したがって、これ以上の確認は必要ありません。データを読み込もうとする試みそのものが、読み込みが成功したかどうかを確認する役割を果たします。こうすることで、C_FileBarsクラスの外でも操作することができますが、クラスを明示的に終了させ、そのクラスが入っていたメモリをOSに戻す必要があります。これには、DELETE演算子でデストラクタを呼び出します。これにより、クラスが適切に削除され、参照されなくなったことが確認されます。
これを怠ると、データに一貫性がなくなったり、プログラムに不具合が生じたりする可能性があります。しかし、上記の手順を踏めば、そのクラスがいつ、どこで、どのように使われているかを正確に知ることができます。これは、モデリングが非常に複雑になりがちないくつかのシナリオで役立ちます。
1つのクラスで複数の機能を持つ:C_ConfigService
このクラスはとても興味深いものです。これは、C_FileTicksクラスとC_Replayクラスの橋渡しをするクラスであるにもかかわらず、どうにかして、すべてが期待通りに保たれ、構成システムの改善や変更が、本当に見えるべき場所にのみ反映されるようにしています。このクラスはそれほど広範で複雑なものではなく、かなり単純なコードを持つ中級クラスです。リプレイ/シミュレーションサービスのセットアップに関連するすべてをこのクラスに置くということです。基本的に、その目的は、設定ファイルを読み込んでその内容をサービスに適用し、ユーザーによって設定されたとおりに動作するようにすることです。
このクラスがおこなうべきことは、モデリングのためのティックの読み取りと作成、リプレイアセットへのバーの適用、そして場合によってはリプレイアセットの変数の調整です。したがって、この資産は実物資産に非常に近い動きをします。以下は、現在の開発段階におけるクラスの全コードです。
#property copyright "Daniel Jose" //+------------------------------------------------------------------+ #include "C_FileBars.mqh" #include "C_FileTicks.mqh" //+------------------------------------------------------------------+ class C_ConfigService : protected C_FileTicks { protected: //+------------------------------------------------------------------+ datetime m_dtPrevLoading; //+------------------------------------------------------------------+ private : //+------------------------------------------------------------------+ enum eTranscriptionDefine {Transcription_INFO, Transcription_DEFINE}; //+------------------------------------------------------------------+ 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; } //+------------------------------------------------------------------+ inline bool Configs(const string szInfo) { const string szList[] = { "POINTSPERTICK" }; 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_PointsPerTick = StringToDouble(szRet[1]); return true; } Print("Variable >>", szRet[0], "<< undefined."); }else Print("Configuration >>", szInfo, "<< invalid."); return false; } //+------------------------------------------------------------------+ inline void FirstBarNULL(void) { MqlRates rate[1]; rate[0].close = rate[0].open = rate[0].high = rate[0].low = m_Ticks.Info[0].last; rate[0].tick_volume = 0; rate[0].real_volume = 0; rate[0].time = m_Ticks.Info[0].time - 60; CustomRatesUpdate(def_SymbolReplay, rate, 1); } //+------------------------------------------------------------------+ inline bool WhatDefine(const string szArg, char &cStage) { const string szList[] = { "[BARS]", "[TICKS]", "[TICKS->BARS]", "[BARS->TICKS]", "[CONFIG]" }; cStage = 1; for (char c0 = 0; c0 < ArraySize(szList); c0++, cStage++) if (szList[c0] == szArg) return true; return false; } //+------------------------------------------------------------------+ public : //+------------------------------------------------------------------+ bool SetSymbolReplay(const string szFileConfig) { int file, iLine; char cError, cStage; string szInfo; bool bBarPrev; C_FileBars *pFileBars; 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; bBarPrev = false; iLine = 1; cError = cStage = 0; 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: pFileBars = new C_FileBars(szInfo); if ((m_dtPrevLoading = (*pFileBars).LoadPreView(szInfo)) == 0) cError = 3; else bBarPrev = true; delete pFileBars; break; case 2: if (LoadTicks(szInfo) == 0) cError = 4; break; case 3: if ((m_dtPrevLoading = LoadTicks(szInfo, false)) == 0) cError = 5; else bBarPrev = true; break; case 4: if (!BarsToTicks(szInfo)) cError = 6; break; case 5: if (!Configs(szInfo)) cError = 7; break; } break; }; iLine += (cError > 0 ? 0 : 1); } FileClose(file); switch(cError) { case 0: if (m_Ticks.nTicks <= 0) { Print("No ticks to use. Closing the service..."); cError = -1; }else if (!bBarPrev) FirstBarNULL(); break; case 1 : Print("Command in line ", iLine, " cannot be recognized by the system..."); break; case 2 : Print("The system did not expect the content of the line ", iLine); break; default : Print("Error in line ", iLine); } return (cError == 0 ? !_StopFlag : false); } //+------------------------------------------------------------------+ };
ここで、この継承表を使い始めます。これらの項目はすべてC_FileTicksクラスから継承されています。したがって、実際にはC_ConfigServiceクラス自体の機能を拡張していることになります。しかし、これだけではありません。よく見ると、バーからデータを読み込む必要がある非常に特殊な状況があります。そのためには、C_FileBarsクラスを使う必要があります。そこで、C_FileTicksクラスと同じメソッドを使い、バーファイルからデータを読み込んでティックに変換する必要がありました。そこで述べた説明がここでも当てはまります。
いわば、このクラスは構成ファイルに含まれるデータの翻訳を担当します。今、私たちがしなければならないのは、正しい状態を指し示すように、いや、むしろ引き起こすように、物事を正しいポイントで定義することです。これは、値が正しく入力されているか、データが正しく読み込まれているかを確認するためにおこないます。これは2カ所でおこなわれます。
まず最初に、どの状態、より正確にはどのキーをキャプチャまたは調整しているかを示します。それほど複雑なものではありませんが、ここにあるのは、構成ファイルの次の数行で作業する内容のキーとなるもののリストです。ここでは、このリストが一定の論理的順序に従わなければならないという事実に注意を払う必要があります。そうでなければ、値の翻訳に問題が生じます。データ要素がどの位置にあるべきかを知るには、単にSetSymbolReplay関数を見て、ここで各値が具体的に何をするのかを見てください。
2つ目の場所は、リプレイ/シミュレーション設定ファイルに含まれる値を、サービス内で使用される定数にデコードする役割を担っています。ここでは前とほとんど同じことをしますが、今回は配列に含まれる各値がクラス内の変数名を示します。つまり、必要なのは、その変数が持つ名前を構成ファイルのリストに追加することだけです。そして、リスト内の位置を呼び出しに加えます。このようにして、目的の変数の値を変更します。私の言っていることが理解できなくても、心配なさらないでください。すぐに、新しい変数を追加する方法の実例を紹介しましょう。その前に、この時点でいくつかの追加事項を定義しておく必要があります。
すべてがとても美しく見えますが、まだ最後のクラスを見ていません。
Cクラス_リプレー - 何も理解できない...どこにあるの?
これは、リプレー/シミュレーションサービスが実際に扱う唯一のクラスです。このクラスはライブラリと考える必要がありますが、そのライブラリの唯一の機能は、リプレイやシミュレーションの代わりに、現物市場やデモ口座とやりとりした場合に起こるであろう動作と同様の動作を促進することです。つまり、MetaTrader 5プラットフォームが、あたかも実際のサーバーから送られてくるかのように、すべてのリプレイシミュレーションを実行できるように、このクラス内だけで実装すればよいのです。
しかし、クラスのコードをよく見て何かを探し始めると、変数や構造体、呼び出される関数はどこにあるのかがわかりません。どこにもないのです。このような考え方は、最初の段階で起こり得ます。それは、まだクラス間の継承にあまり慣れていないことを意味しています。心配せずに、落ち着いてコードを勉強すれば、すぐにこの継承の仕組みが理解できるようになるでしょう。今はじめるべきです。もうすぐもっと複雑なものをお見せするので、皆さんを混乱させるかもしれません。その1つがポリモルフィズムです。これは非常に便利なものですが、継承の仕組みに関連する問題を理解していない人にとっては、多くの混乱を引き起こすものでもあります。よって、このコードをきちんと勉強することをお勧めします。
とりあえず、ポリモーフィズムの話題は将来に残しておいて、以下のコードをご覧ください。
#property copyright "Daniel Jose" //+------------------------------------------------------------------+ #include "C_ConfigService.mqh" //+------------------------------------------------------------------+ class C_Replay : private C_ConfigService { private : int m_ReplayCount; long m_IdReplay; struct st01 { MqlRates Rate[1]; bool bNew; datetime memDT; int delay; }m_MountBar; //+------------------------------------------------------------------+ void AdjustPositionToReplay(const bool bViewBuider) { u_Interprocess Info; MqlRates Rate[def_BarsDiary]; int iPos, nCount; Info.u_Value.df_Value = GlobalVariableGet(def_GlobalVariableReplay); if (Info.s_Infos.iPosShift == (int)((m_ReplayCount * def_MaxPosSlider * 1.0) / m_Ticks.nTicks)) return; iPos = (int)(m_Ticks.nTicks * ((Info.s_Infos.iPosShift * 1.0) / (def_MaxPosSlider + 1))); Rate[0].time = macroRemoveSec(m_Ticks.Info[iPos].time); if (iPos < m_ReplayCount) { CustomRatesDelete(def_SymbolReplay, Rate[0].time, LONG_MAX); if ((m_dtPrevLoading == 0) && (iPos == 0)) { m_ReplayCount = 0; Rate[m_ReplayCount].close = Rate[m_ReplayCount].open = Rate[m_ReplayCount].high = Rate[m_ReplayCount].low = m_Ticks.Info[iPos].last; Rate[m_ReplayCount].tick_volume = Rate[m_ReplayCount].real_volume = 0; CustomRatesUpdate(def_SymbolReplay, Rate, 1); }else { for(Rate[0].time -= 60; (m_ReplayCount > 0) && (Rate[0].time <= macroRemoveSec(m_Ticks.Info[m_ReplayCount].time)); m_ReplayCount--); m_ReplayCount++; } }else if (iPos > m_ReplayCount) { if (bViewBuider) { Info.s_Infos.isWait = true; GlobalVariableSet(def_GlobalVariableReplay, Info.u_Value.df_Value); }else { for(; Rate[0].time > m_Ticks.Info[m_ReplayCount].time; m_ReplayCount++); for (nCount = 0; m_Ticks.Rate[nCount].time < macroRemoveSec(m_Ticks.Info[iPos].time); nCount++); CustomRatesUpdate(def_SymbolReplay, m_Ticks.Rate, nCount); } } for (iPos = (iPos > 0 ? iPos - 1 : 0); (m_ReplayCount < iPos) && (!_StopFlag);) CreateBarInReplay(); Info.u_Value.df_Value = GlobalVariableGet(def_GlobalVariableReplay); Info.s_Infos.isWait = false; GlobalVariableSet(def_GlobalVariableReplay, Info.u_Value.df_Value); } //+------------------------------------------------------------------+ inline void CreateBarInReplay(const bool bViewMetrics = false) { #define def_Rate m_MountBar.Rate[0] static ulong _mdt = 0; int i; if (m_MountBar.bNew = (m_MountBar.memDT != macroRemoveSec(m_Ticks.Info[m_ReplayCount].time))) { if (bViewMetrics) { _mdt = (_mdt > 0 ? GetTickCount64() - _mdt : _mdt); i = (int) (_mdt / 1000); Print(TimeToString(m_Ticks.Info[m_ReplayCount].time, TIME_SECONDS), " - Metrica: ", i / 60, ":", i % 60, ".", (_mdt % 1000)); _mdt = GetTickCount64(); } m_MountBar.memDT = macroRemoveSec(m_Ticks.Info[m_ReplayCount].time); def_Rate.real_volume = 0; def_Rate.tick_volume = 0; } def_Rate.close = m_Ticks.Info[m_ReplayCount].last; def_Rate.open = (m_MountBar.bNew ? def_Rate.close : def_Rate.open); def_Rate.high = (m_MountBar.bNew || (def_Rate.close > def_Rate.high) ? def_Rate.close : def_Rate.high); def_Rate.low = (m_MountBar.bNew || (def_Rate.close < def_Rate.low) ? def_Rate.close : def_Rate.low); def_Rate.real_volume += (long) m_Ticks.Info[m_ReplayCount].volume_real; def_Rate.tick_volume += (m_Ticks.Info[m_ReplayCount].volume_real > 0 ? 1 : 0); def_Rate.time = m_MountBar.memDT; m_MountBar.bNew = false; CustomRatesUpdate(def_SymbolReplay, m_MountBar.Rate, 1); m_ReplayCount++; #undef def_Rate } //+------------------------------------------------------------------+ public : //+------------------------------------------------------------------+ C_Replay(const string szFileConfig) { m_ReplayCount = 0; m_dtPrevLoading = 0; m_Ticks.nTicks = 0; m_PointsPerTick = 0; Print("************** Market Replay Service **************"); srand(GetTickCount()); GlobalVariableDel(def_GlobalVariableReplay); SymbolSelect(def_SymbolReplay, false); CustomSymbolDelete(def_SymbolReplay); CustomSymbolCreate(def_SymbolReplay, StringFormat("Custom\\%s", def_SymbolReplay), _Symbol); CustomRatesDelete(def_SymbolReplay, 0, LONG_MAX); CustomTicksDelete(def_SymbolReplay, 0, LONG_MAX); SymbolSelect(def_SymbolReplay, true); m_IdReplay = (SetSymbolReplay(szFileConfig) ? 0 : -1); } //+------------------------------------------------------------------+ ~C_Replay() { 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); Print("Replay service completed..."); } //+------------------------------------------------------------------+ bool ViewReplay(ENUM_TIMEFRAMES arg1) { u_Interprocess info; if (m_IdReplay == -1) return false; if ((m_IdReplay = ChartFirst()) > 0) do { if (ChartSymbol(m_IdReplay) == def_SymbolReplay) { ChartClose(m_IdReplay); ChartRedraw(); } }while ((m_IdReplay = ChartNext(m_IdReplay)) > 0); Print("Wait for permission from [Market Replay] indicator to start replay ..."); info.u_Value.IdGraphic = m_IdReplay = ChartOpen(def_SymbolReplay, arg1); ChartApplyTemplate(m_IdReplay, "Market Replay.tpl"); ChartRedraw(m_IdReplay); GlobalVariableDel(def_GlobalVariableIdGraphics); GlobalVariableTemp(def_GlobalVariableIdGraphics); GlobalVariableSet(def_GlobalVariableIdGraphics, info.u_Value.df_Value); while ((!GlobalVariableCheck(def_GlobalVariableReplay)) && (!_StopFlag) && (ChartSymbol(m_IdReplay) != "")) Sleep(750); return ((!_StopFlag) && (ChartSymbol(m_IdReplay) != "")); } //+------------------------------------------------------------------+ bool LoopEventOnTime(const bool bViewBuider, const bool bViewMetrics) { u_Interprocess Info; int iPos, iTest; iTest = 0; while ((iTest == 0) && (!_StopFlag)) { iTest = (ChartSymbol(m_IdReplay) != "" ? iTest : -1); iTest = (GlobalVariableGet(def_GlobalVariableReplay, Info.u_Value.df_Value) ? iTest : -1); iTest = (iTest == 0 ? (Info.s_Infos.isPlay ? 1 : iTest) : iTest); if (iTest == 0) Sleep(100); } if ((iTest < 0) || (_StopFlag)) return false; AdjustPositionToReplay(bViewBuider); m_MountBar.delay = 0; while ((m_ReplayCount < m_Ticks.nTicks) && (!_StopFlag)) { CreateBarInReplay(bViewMetrics); iPos = (int)(m_ReplayCount < m_Ticks.nTicks ? m_Ticks.Info[m_ReplayCount].time_msc - m_Ticks.Info[m_ReplayCount - 1].time_msc : 0); m_MountBar.delay += (iPos < 0 ? iPos + 1000 : iPos); if (m_MountBar.delay > 400) { if (ChartSymbol(m_IdReplay) == "") break; GlobalVariableGet(def_GlobalVariableReplay, Info.u_Value.df_Value); if (!Info.s_Infos.isPlay) return true; Info.s_Infos.iPosShift = (ushort)((m_ReplayCount * def_MaxPosSlider) / m_Ticks.nTicks); GlobalVariableSet(def_GlobalVariableReplay, Info.u_Value.df_Value); Sleep(m_MountBar.delay - 20); m_MountBar.delay = 0; } } return (m_ReplayCount == m_Ticks.nTicks); } //+------------------------------------------------------------------+ }; //+------------------------------------------------------------------+ #undef macroRemoveSec #undef def_SymbolReplay //+------------------------------------------------------------------+
ご覧の通り、これといって変わったところはありませんが、以前のバージョンよりはるかに大きくなっています。それでも、以前実施したことはすべてできました。ただし、ここで強調しておきたいのは、C_Replayクラスには、実際にはすべてのポイントが含まれていないということです。これらのポイントは継承されます。これは、C_Replayクラスの外部からはアクセスできないようにするためです。そのために、私たちは物事を私的に継承します。このようにして、私たちは継承された情報の完全性を保証します。コンストラクタとデストラクタは考慮されていないので、このクラスには実際に外部からアクセスできる関数は2つしかありません。
しかし、クラスのコンストラクタとデストラクタの話をする前に、クラスの外からアクセスできる2つの関数を見てみましょう。ある時点で、どちらか片方だけを残すことにしましたが、現実的な理由から両方の機能を残しました。この方がシンプルです。以前の記事でLoopEventOnTime関数について考察しました。そして、ここでは何の変更も加えられていないのよって、追加の説明をする意味はありません。私たちはそれをスキップして、変化を遂げたものに集中することができます。ViewReplayです。
ViewReplay関数の変更点はただ1つ、チェックです。ここでは、クラスコンストラクタがクラスの初期化に成功したかどうかをチェックします。失敗した場合、この関数はリプレイサービスを終了させるべき値を返します。以前の記事と比べ、修正したのはこれだけです。
最終的な検討事項
すべての変更点を考慮し、新しい資料を勉強し、また添付のコードを以前の記事で紹介した他のコードと比較することをお勧めします。次回は、まったく異なる、そしてかなり興味深いトピックを始める。またお会いしましょう。
MetaQuotes Ltdによりポルトガル語から翻訳されました。
元の記事: https://www.mql5.com/pt/articles/11095
- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索