English Русский 中文 Español Deutsch Português
preview
リプレイシステムの開発—市場シミュレーション(第1回):最初の実験(I)

リプレイシステムの開発—市場シミュレーション(第1回):最初の実験(I)

MetaTrader 5テスター | 28 7月 2023, 10:48
343 0
Daniel Jose
Daniel Jose

はじめに

一からの取引エキスパートアドバイザーの開発」の連載を書いているときに、MQL5プログラミングで実行できる以上のことが可能であることを実感する瞬間に何度か遭遇しました。そのような瞬間の1つは、グラフィックTimes&Tradeシステムを開発したときでした。その記事では、これまでに構築したものを超えることができるのではないかと考えました。

初心者トレーダーからの最も一般的な苦情の1つは、MetaTrader 5プラットフォームに特定の機能が欠如していることです。これらの機能の中に、私の意見では理にかなっている機能が1つあります。それは、市場シミュレーションまたはリプレイシステムです。新しい市場参加者にとって、資産のテスト、検証、さらには研究を可能にする何らかのメカニズムやツールがあるとよいでしょう。そのようなツールの1つは、リプレイおよびシミュレーションシステムです。

MetaTrader 5の標準インストールパッケージにはこの機能は含まれていません。したがって、このような調査をどのように実施するかは各ユーザーの判断に任されています。ただし、MetaTrader 5では、このプラットフォームが非常に機能的であるため、非常に多くのタスクに対するソリューションを見つけることができます。ただし、実際にその可能性を最大限に活用するには、優れたプログラミング経験が必要です。必ずしもMQL5プログラミングを意味するわけではなく、プログラミング全般を意味します。

この分野での経験があまりない場合は、基本だけを守ることになります。(優れたトレーダーになるという点で)市場で成長するためのより適切な手段やより良い方法がなくなるでしょう。したがって、十分なレベルのプログラミング知識がない限り、MetaTrader 5が提供するすべてを実際に使用することはできません。経験豊富なプログラマであっても、MetaTrader 5用の特定の種類のプログラムやアプリケーションの作成にはまだ興味がないかもしれません。

実際のところ、初心者向けに実用的なシステムを作成できる人はほとんどいません。市場リプレイシステムを作成するための無料の提案もいくつかあります。しかし、私の意見では、それらはMQL5が提供する機能を実際には活用していません。多くの場合、クローズドコードの外部DLLを使用する必要があります。それは良い考えではないと思います。ソースやそのようなDLLに存在するコンテンツが実際には分からないため、システム全体が危険にさらされることになるため、さらにそうです。

この連載に記事がいくつ含まれるかはわかりませんが、動作するリプレイの開発に関するものになります。このリプレイを実装するコードを作成する方法を説明します。ただし、これですべてではありません。また、どんなに奇妙で珍しい市場状況であっても、あらゆる市場状況をシミュレートできるシステムを開発する予定です。

興味深い事実は、取引量について話すとき、実際には多くの人が実際に何を言っているのかを理解していないということです。この種のことに関する研究をおこなう実際的な方法がないためです。しかし、この連載で説明する概念を理解していれば、MetaTrader 5を定量分析システムに変えることができるでしょう。したがって、可能性は私がここで実際に明らかにするものをはるかに超えて広がるでしょう。

繰り返しすぎて疲れないようにするために、システムをリプレイのように扱います。正しい用語はリプレイ/シミュレーションですが、過去の動きを分析するだけでなく、自分の動きを開発して研究することもできるためです。したがって、このシステムを単なる市場のリプレイとして扱うのではなく、市場シミュレーター、あるいは市場の「ゲーム」としても扱ってください。これには多くのゲームプログラミングも含まれるためです。ゲームに関わるこの種のプログラミングは、場合によっては必要になります。ただし、システムの開発と強化を進めながら、これを段階的に確認していきます。


計画

まず、ここで何を扱っているのかを理解する必要があります。奇妙に思えるかもしれませんが、リプレイ/シミュレーションシステムを使用するときに何を達成したいのか、本当にわかっているでしょうか。

市場リプレイの作成に関しては、かなり難しい問題がいくつかあります。そのうちの1つは、おそらく主要なものは、資産とそれに関する情報の存続期間です。これを理解できない場合は、次のことを理解することが重要です。取引システムは、すべての資産に対して実行された取引ごとに、すべての情報を1つずつ記録します。しかし、それらがどれだけのデータを表しているかがわかるでしょうか。すべての資産を整理して分類するのにどれくらいの時間がかかるか考えたことがありますか。

一般的な資産の中には、毎日の移動で約80MBのデータが含まれる場合があります。これは場合によっては上下します。これは1日に1つのアセットのみに適用されます。次に、同じ資産を1か月、1年、10年間保管しなければならないことを考えてみましょう。あるいは、永遠に。保存してから取得する必要がある膨大な量のデータについて考えてみましょう。単にディスクに保存しただけでは、すぐに何も見つけられなくなるからです。これをよく表す言葉があります。

スペースが大きければ大きいほど、混乱も大きくなります

作業を簡単にするために、しばらくすると、データが必要最小限の情報を含む1分足に圧縮され、何らかの調査ができるようになります。しかし、そのバーが実際に作成されると、それを構築したティックは消え、アクセスできなくなります。その後、実際の市場リプレイをおこなうことはできなくなります。この瞬間から、あるのはシミュレーターです。実際の動きにはもはやアクセスできないため、もっともらしい市場の動きに基づいてそれをシミュレートする何らかの方法を作成する必要があります。

理解するには、以下の図を参照してください。

            

上記のシーケンスは、時間の経過とともにデータがどのように失われるかを示しています。左側の画像は、実際のティック値を示しています。データが圧縮されると、画像が中央に表示されます。これに基づいて、左の値を取得することはできません。そんなことは不可能です。しかし、市場が通常どのように動くかについての知識に基づいて、市場の動きをシミュレーションする、右の画像のようなものを作成することはできます。ただし、元の画像とはまったく異なります。

リプレイを操作するときは、このことに留意してください。元のデータがなければ、本格的な研究はできません。実行できるのは統計的な研究のみであり、実際の動きに近い場合もあれば、そうでない場合もあります。これを常に覚えておいてください。このシーケンスを通じて、これをおこなう方法をさらに検討していきます。しかし、これは少しずつ起こるでしょう。

それでは、本当に難しい部分に移りましょう。リプレイシステムを実装します。


実装

この部分は簡単そうに見えますが、ハードウェアの問題や制限、ソフトウェア部分の問題などが絡んでおり、非常に複雑です。したがって、少なくとも最も基本的で機能的で受け入れられるものを作成するように努める必要があります。基礎が弱いと、より複雑なことをしようとしても役に立ちません。

奇妙なことに、私たちの主かつ最大の問題は時間です。時間は克服すべき大きな問題です。

添付ファイルには、過去の期間のあらゆる資産のティックの少なくとも2REALセットを(この最初の段階で)常に残しておきます。データが紛失したため、このデータを取得することはできず、ダウンロードすることもできません。これは、あらゆる詳細を研究するのに役立ちます。ただし、独自のREALティックベースを作成することもできます。


独自のデータベースを作成する

幸いなことに、MetaTrader 5にはこれをおこなう手段があります。これは非常に簡単ですが、着実に実行する必要があります。そうしないと、値が失われ、このタスクを完了できなくなります。

これをおこなうには、MetaTrader 5を開き、デフォルトのショートカットキー(CTRL+U)を押します。画面が開きます。ここで資産とデータ収集の開始日と終了日を指定し、ボタンを押してデータを要求し、数分待ちます。サーバーは必要なデータをすべて返します。その後、このデータは非常に貴重なので、エクスポートして慎重に保管してください。

以下はキャプチャに使用する画面です。

これをおこなうプログラムを作成することもできますが、手動でおこなう方が良いと思います。盲目的に信用できないものもあります。何が起こっているのかを実際に確認する必要があります。そうしないと、使用しているものに適切な自信が持てなくなります。

これは間違えなく、これから学習するシステム全体の中で最も簡単な部分です。この時点から、事態はさらに複雑になります。


最初のリプレイテスト

これは簡単な作業だろうと思う方もいらっしゃるかもしれませんが、すぐにこの考えを否定します。なぜMetaTrader 5ストラテジーテスターを使用してリプレイを実行しないのかと、疑問に思う方もいらっしゃるかもしれません。その理由は、市場で取引しているかのようにリプレイすることができないためです。テスターを介してリプレイするには制限と困難があるため、実際に市場で取引しているかのように、リプレイに完全に没入することはできません。

大きな課題に直面するでしょうが、この長い旅の最初の一歩を踏み出さなければなりません。非常に単純な実装から始めましょう。このためには、バー(ローソク足)を作成するデータフローを生成するOnTimeイベントが必要です。このイベントはEAと指標に提供されていますが、障害が発生するとリプレイシステム以上の危険が及ぶため、この場合は指標を使用しないでください。次のようにコードを開始します。

#property copyright "Daniel Jose"
#property icon "Resources\\App.ico"
#property description "Expert Advisor - Market Replay"
//+------------------------------------------------------------------+
int OnInit()
{
        EventSetTimer(60);
        return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
        EventKillTimer();
}
//+------------------------------------------------------------------+
void OnTick()
{
}
//+------------------------------------------------------------------+
void OnTimer()
{
}
//+------------------------------------------------------------------+

ただし、強調表示されたコードはここでの目的には適していません。この場合、使用できる最小期間は1秒であり、非常に長い時間であるためです。市場イベントははるかに短い時間枠で発生するため、ミリ秒まで下げる必要があり、そのためには別の関数(EventSetMilliSecondTimer)を使用する必要があります。しかし、ここで問題が発生します。


EventSetMillisecondTimer関数の制限事項

ドキュメントを見てみましょう。

「リアルタイムモードで動作している場合、ハードウェアの制限により、タイマーイベントは10~16ミリ秒に1回しか生成されません。」

これは問題ではないかもしれませんが、実際に何が起こるかを確認するにはさまざまなチェックを実行する必要があります。そこで、結果を確認するための簡単なコードを作成してみましょう。

以下のEAコードから始めましょう。

#property copyright "Daniel Jose"
#property icon "Resources\\App.ico"
#property description "Expert Advisor - Market Replay"
//+------------------------------------------------------------------+
#include "Include\\C_Replay.mqh"
//+------------------------------------------------------------------+
input string user01 = "WINZ21_202110220900_202110221759";       //Tick archive
//+------------------------------------------------------------------+
C_Replay        Replay;
//+------------------------------------------------------------------+
int OnInit()
{
        Replay.CreateSymbolReplay(user01);
        EventSetMillisecondTimer(20);
        
        return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
        EventKillTimer();
}
//+------------------------------------------------------------------+
void OnTick() {}
//+------------------------------------------------------------------+
void OnTimer()
{
        Replay.Event_OnTime();
}
//+------------------------------------------------------------------+

EAコード内の強調表示された行で示されているように、OnTimeイベントは約20ミリ秒ごとに発生することに注意してください。速すぎると思うかもしれませんが、本当にそうなのでしょうか。チェックしてみましょう。ドキュメントには、10~16ミリ秒を下回ることはできないと記載されていることに注意してください。したがって、この時間内にはイベントが生成されないため、値を1ミリ秒に設定しても意味がありません。

EAコードには外部リンクが2つしかないことに注意してください。次に、これらのコードが実装されているクラスを見てみましょう。

#property copyright "Daniel Jose"
//+------------------------------------------------------------------+
#define def_MaxSizeArray 134217727 // 128 Mbytes of positions
#define def_SymbolReplay "Replay"
//+------------------------------------------------------------------+
class C_Replay
{
};

上記の強調表示された点で示されているように、クラスには128MBの定義があることに注意することが重要です。これは、すべてのティックのデータを含むファイルがこのサイズを超えてはいけないことを意味します。必要に応じてこのサイズを増やすことができますが、個人的にはこの値で問題ありませんでした。

次の行では、リプレイとして使用されるアセットの名前を指定します。アセットにREPLAYという名前を付けるのは、とても独創的ですね。😂さて、クラスの勉強を続けましょう。次に説明するコードを以下に示します。

void CreateSymbolReplay(const string FileTicksCSV)
{
        SymbolSelect(def_SymbolReplay, false);
        CustomSymbolDelete(def_SymbolReplay);
        CustomSymbolCreate(def_SymbolReplay, StringFormat("Custom\\Replay\\%s", def_SymbolReplay), _Symbol);
        CustomRatesDelete(def_SymbolReplay, 0, LONG_MAX);
        CustomTicksDelete(def_SymbolReplay, 0, LONG_MAX);
        SymbolSelect(def_SymbolReplay, true);
        m_IdReplay = ChartOpen(def_SymbolReplay, PERIOD_M1);
        LoadFile(FileTicksCSV);
        Print("Running speed test.");
}

強調表示されている2行は、非常に興味深いことをおこなっています。ご存じない方のために説明すると、CustomSymbolCreate関数はカスタム銘柄を作成します。この場合、いくつかのことを調整できますが、これは単なるテストなので、今は説明しません。ChartOpenはカスタム銘柄のチャートを開きます。この場合はリプレイになります。これはすべて非常に素晴らしいことですが、ファイルからリプレイを読み込む必要があり、これは次の関数によっておこなわれます。

#define macroRemoveSec(A) (A - (A % 60))
                void LoadFile(const string szFileName)
                        {
                                int file;
                                string szInfo;
                                double last;
                                long    vol;
                                uchar flag;
                                
                                if ((file = FileOpen("Market Replay\\Ticks\\" + szFileName + ".csv", FILE_CSV | FILE_READ | FILE_ANSI)) != INVALID_HANDLE)
                                {
                                        ArrayResize(m_ArrayInfoTicks, def_MaxSizeArray);
                                        m_ArrayCount = 0;
                                        last = 0;
                                        vol = 0;
                                        for (int c0 = 0; c0 < 7; c0++) FileReadString(file);
                                        Print("Loading data for Replay.\nPlease wait ....");
                                        while ((!FileIsEnding(file)) && (m_ArrayCount < def_MaxSizeArray))
                                        {
                                                szInfo = FileReadString(file);
                                                szInfo += " " + FileReadString(file);                                           
                                                m_ArrayInfoTicks[m_ArrayCount].dt = macroRemoveSec(StringToTime(StringSubstr(szInfo, 0, 19)));
                                                m_ArrayInfoTicks[m_ArrayCount].milisec = (int)StringToInteger(StringSubstr(szInfo, 20, 3));
                                                m_ArrayInfoTicks[m_ArrayCount].Bid = StringToDouble(FileReadString(file));
                                                m_ArrayInfoTicks[m_ArrayCount].Ask = StringToDouble(FileReadString(file));
                                                m_ArrayInfoTicks[m_ArrayCount].Last = StringToDouble(FileReadString(file));
                                                m_ArrayInfoTicks[m_ArrayCount].Vol = StringToInteger(FileReadString(file));
                                                flag = m_ArrayInfoTicks[m_ArrayCount].flag = (uchar)StringToInteger(FileReadString(file));
                                                if (((flag & TICK_FLAG_ASK) == TICK_FLAG_ASK) || ((flag & TICK_FLAG_BID) == TICK_FLAG_BID)) continue;
                                                m_ArrayCount++;
                                        }
                                        FileClose(file);
                                        Print("Loading completed.\nReplay started.");
                                        return;
                                }
                                Print("Failed to access tick data file.");
                                ExpertRemove();
                        };
#undef macroRemoveSec

この関数は、すべてのティックデータを行ごとに読み込みます。ファイルが存在しないかアクセスできない場合、ExpertRemoveはEAを閉じます。

すべてのデータは、さらなる処理を高速化するために一時的にメモリに保存されます。これは、システムメモリよりも遅いディスクドライブを使用している可能性があるためです。したがって、最初からすべてのデータが存在することを確認する価値があります。

しかし、上記のコードには、FileReadString関数という興味深いものがあります。区切り文字が見つかるまでデータを読み取ります。興味深いのは、記事の冒頭で説明したように、MetaTrader 5によって生成され、CSV形式で保存されたティックファイルのバイナリデータを確認すると、次の結果が得られることです。


黄色の領域はファイルヘッダーで、その後の内部構造の構成を示します。緑色の領域は最初のデータ行を表します。次に、この形式に存在する青い点(区切り文字)を見てみましょう。0D、0Aは改行、09はタブ(TABキー)を表します。FileReadString関数を使用する場合、テストのためにデータを蓄積する必要はありません。関数自体がそれをおこないます。私たちがしなければならないのは、データを必要な型に変換することだけです。次のコード部分を見てみましょう。

if (((flag & TICK_FLAG_ASK) == TICK_FLAG_ASK) || ((flag & TICK_FLAG_BID) == TICK_FLAG_BID)) continue;

このコードにより、データ行列に不要なデータが表示されるのを防ぎますが、なぜこれらの値をフィルターで除外するのでしょうか。リプレイには向かないからです。これらの値を使用したい場合は、そのまま渡すことができますが、後でバーを作成するときにフィルターする必要があります。したがって、ここでそれらをフィルターすることを好みます。

以下に、テストシステムの最後のルーチンを示します。

#define macroGetMin(A)  (int)((A - (A - ((A % 3600) - (A % 60)))) / 60)
                void Event_OnTime(void)
                        {
                                bool isNew;
                                static datetime _dt = 0;
                                
                                if (m_ReplayCount >= m_ArrayCount) return;
                                if (m_dt == 0)
                                {
                                        m_Rate[0].close = m_Rate[0].open =  m_Rate[0].high = m_Rate[0].low = m_ArrayInfoTicks[m_ReplayCount].Last;
                                        m_Rate[0].tick_volume = 0;
                                        _dt = TimeLocal();
                                }
                                isNew = m_dt != m_ArrayInfoTicks[m_ReplayCount].dt;
                                m_dt = (isNew ? m_ArrayInfoTicks[m_ReplayCount].dt : m_dt);
                                m_Rate[0].close = m_ArrayInfoTicks[m_ReplayCount].Last;
                                m_Rate[0].open = (isNew ? m_Rate[0].close : m_Rate[0].open);
                                m_Rate[0].high = (isNew || (m_Rate[0].close > m_Rate[0].high) ? m_Rate[0].close : m_Rate[0].high);
                                m_Rate[0].low = (isNew || (m_Rate[0].close < m_Rate[0].low) ? m_Rate[0].close : m_Rate[0].low);
                                m_Rate[0].tick_volume = (isNew ? m_ArrayInfoTicks[m_ReplayCount].Vol : m_Rate[0].tick_volume + m_ArrayInfoTicks[m_ReplayCount].Vol);
                                m_Rate[0].time = m_dt;
                                CustomRatesUpdate(def_SymbolReplay, m_Rate, 1);
                                m_ReplayCount++;
                                if ((macroGetMin(m_dt) == 1) && (_dt > 0))
                                {
                                        Print(TimeToString(_dt, TIME_DATE | TIME_SECONDS), " ---- ", TimeToString(TimeLocal(), TIME_DATE | TIME_SECONDS));
                                        _dt = 0;
                                }
                        };
#undef macroGetMin

このコードは、1分の期間のバーを作成します。これは、他のチャート期間を作成するためのプラットフォームの最小要件です。強調表示された部分はコード自体の一部ではありませんが、1分足の分析に役立ちます。本当にこの期間内に作成されたかどうかを確認する必要があります。作成に1分を大幅に超える時間がかかる場合は、何らかの対応をしなければならないためです。1分以内に作成された場合は、システムがすぐに実行可能であることを示している可能性があります。

このシステムを実行すると、次のビデオに示す結果が得られます。



タイミングが予想よりもはるかに長いと感じるかもしれませんが、コードにいくつかの改善を加えることができ、おそらくそれが変化をもたらすでしょう。


コードの改善

大幅な遅延にもかかわらず、状況を少し改善し、システムのパフォーマンスを期待に少し近づけることができるかもしれません。しかし、私は奇跡をあまり信じていません。EventSetMillisecondTimer関数の制限はわかっており、問題はMQL5によるものではなく、ハードウェアの制限です。ただし、システムを助けることができるかどうか見てみましょう。

データを見ると、システムが動かず、価格が動かない瞬間がいくつかあることがわかります。あるいは、本があらゆる攻撃を吸収し、価格が動かないことが起こった可能性もあります。これは下の画像で確認できます。

2つの異なる条件があることに注意してください。そのうちの1つは、時間と価格が変更されていないことです。これは、データが間違っていることを示すものではありませんが、ミリ秒の測定に違いが生じるのに十分な時間が経過していないことを示しています。また、価格は動かず、時間がわずか1ミリ秒だけ動いた別のタイプのイベントもあります。どちらの場合も、情報を組み合わせると、バーの作成時間の違いは1分になる可能性があります。これにより、作成関数への追加の呼び出しが回避され、ナノ秒ごとに節約されることが長期的には大きな違いを生む可能性があります。すべてが積み重なり、少しずつ多くのことが達成されます。

違いがあるかどうかを確認するには、生成される情報量を確認する必要があります。これは統計上の話なので正確ではありません。小さな間違いは許容されます。しかし、ビデオで確認できる時間は、現実に近いシミュレーションとしてはまったく受け入れられません。

これを確認するために、コードに最初の変更を加えます。

#define macroRemoveSec(A) (A - (A % 60))
                void LoadFile(const string szFileName)
                        {

// ... Internal code ...
                                        FileClose(file);
                                        Print("Loading completed.\n", m_ArrayCount, " movement positions were generated.\nStarting Replay.");
                                        return;
                                }
                                Print("Failed to access tick data file.");
                                ExpertRemove();
                        };
#undef macroRemoveSec

指定された追加パーツで対応します。最初の起動を見て、何が起こるかを見てみましょう。これらすべては、以下の画像で確認できます。

これで、変更が役立つかどうかを確認できるパラメータがいくつかできました。これを使用すると、1分のデータを生成するのにほぼ3分かかったことがわかります。言い換えれば、このシステムは決して受け入れられるものではありません。

したがって、コードに小さな改善を加えます。

#define macroRemoveSec(A) (A - (A % 60))
                void LoadFile(const string szFileName)
                        {
                                int file;
                                string szInfo;
                                double last;
                                long    vol;
                                uchar flag;
                                
                                
                                if ((file = FileOpen("Market Replay\\Ticks\\" + szFileName + ".csv", FILE_CSV | FILE_READ | FILE_ANSI)) != INVALID_HANDLE)
                                {
                                        ArrayResize(m_ArrayInfoTicks, def_MaxSizeArray);
                                        m_ArrayCount = 0;
                                        last = 0;
                                        vol = 0;
                                        for (int c0 = 0; c0 < 7; c0++) FileReadString(file);
                                        Print("Loading data to Replay.\nPlease wait ....");
                                        while ((!FileIsEnding(file)) && (m_ArrayCount < def_MaxSizeArray))
                                        {
                                                szInfo = FileReadString(file);
                                                szInfo += " " + FileReadString(file);                                           
                                                m_ArrayInfoTicks[m_ArrayCount].dt = macroRemoveSec(StringToTime(StringSubstr(szInfo, 0, 19)));
                                                m_ArrayInfoTicks[m_ArrayCount].milisec = (int)StringToInteger(StringSubstr(szInfo, 20, 3));
                                                m_ArrayInfoTicks[m_ArrayCount].Bid = StringToDouble(FileReadString(file));
                                                m_ArrayInfoTicks[m_ArrayCount].Ask = StringToDouble(FileReadString(file));
                                                m_ArrayInfoTicks[m_ArrayCount].Last = StringToDouble(FileReadString(file));
                                                m_ArrayInfoTicks[m_ArrayCount].Vol = vol + StringToInteger(FileReadString(file));
                                                flag = m_ArrayInfoTicks[m_ArrayCount].flag = (uchar)StringToInteger(FileReadString(file));
                                                if (((flag & TICK_FLAG_ASK) == TICK_FLAG_ASK) || ((flag & TICK_FLAG_BID) == TICK_FLAG_BID)) continue;
                                                if (m_ArrayInfoTicks[m_ArrayCount].Last != last)
                                                {
                                                        last = m_ArrayInfoTicks[m_ArrayCount].Last;
                                                        vol = 0;
                                                        m_ArrayCount++;
                                                }else
                                                        vol += m_ArrayInfoTicks[m_ArrayCount].Vol;
                                        }
                                        FileClose(file);
                                        Print("Loading complete.\n", m_ArrayCount, " movement positions were generated.\nStarting Replay.");
                                        return;
                                }
                                Print("Failed to access tick data file.");
                                ExpertRemove();
                        };
#undef macroRemoveSec

以下の図に示すように、強調表示された太線を追加すると、結果が大幅に改善されます。


ここではシステムのパフォーマンスが向上しました。大したことではないように思えるかもしれませんが、それでも初期の変更が決定的な役割を果たすことを示しています。1分バーを生成するのにかかるおよその時間は2分29秒に達しました。言い換えれば、システムは全体的に改善されています。それは喜ばしいことのように聞こえますが、問題を複雑にしているものがあります。EventSetMillisecondTimer関数によって生成されるイベント間の時間を短縮することはできないため、別のアプローチを検討する必要があります。

ただし、以下に示すように、システムには小さな改善が加えられています。

                void Event_OnTime(void)
                        {
                                bool isNew;
                                static datetime _dt = 0;
                                
                                if (m_ReplayCount >= m_ArrayCount) return;
                                if (m_dt == 0)
                                {
                                        m_Rate[0].close = m_Rate[0].open =  m_Rate[0].high = m_Rate[0].low = m_ArrayInfoTicks[m_ReplayCount].Last;
                                        m_Rate[0].tick_volume = 0;
                                        m_Rate[0].time = m_ArrayInfoTicks[m_ReplayCount].dt - 60;
                                        CustomRatesUpdate(def_SymbolReplay, m_Rate, 1);
                                        _dt = TimeLocal();
                                }

// ... Internal code ....

                        }

強調表示された線が何をおこなうかはチャート上で確認できます。これらがないと、最初の小節が常に切り取られてしまい、正しく読むことが困難になります。しかし、これらの2行を追加すると、視覚的な表現がはるかに良くなり、生成されたバーを適切に確認できるようになります。これは最初のバーから起こります。とてもシンプルなことですが、最終的には大きな違いを生みます。

しかし、バーの表示と作成に適したシステムを作成するという最初の質問に戻りましょう。仮に時間を短縮できたとしても、十分な体制が整いません。実際、アプローチを変更する必要があります。これが、EAがリプレイシステムを作成する最適な方法ではない理由です。それでも、皆さんにとって興味深いかもしれないもう1つのことを紹介したいと思います。OnTimeイベントの生成に可能な限り短い時間を使用した場合、1分足の作成を実際にどの程度削減または改善できるでしょうか。同じ1分以内に値が変化しない場合、データをさらにティックスコープに圧縮するとどうなるでしょうか。何らかの違いがもたらされるでしょうか。


極限まで行く

これをおこなうには、コードに最後の変更を加える必要があります。以下に示します。

#define macroRemoveSec(A) (A - (A % 60))
#define macroGetMin(A)  (int)((A - (A - ((A % 3600) - (A % 60)))) / 60)
                void LoadFile(const string szFileName)
                        {
                                int file;
                                string szInfo;
                                double last;
                                long    vol;
                                uchar flag;
                                datetime mem_dt = 0;
                                
                                if ((file = FileOpen("Market Replay\\Ticks\\" + szFileName + ".csv", FILE_CSV | FILE_READ | FILE_ANSI)) != INVALID_HANDLE)
                                {
                                        ArrayResize(m_ArrayInfoTicks, def_MaxSizeArray);
                                        m_ArrayCount = 0;
                                        last = 0;
                                        vol = 0;
                                        for (int c0 = 0; c0 < 7; c0++) FileReadString(file);
                                        Print("Loading data to Replay.\nPlease wait ....");
                                        while ((!FileIsEnding(file)) && (m_ArrayCount < def_MaxSizeArray))
                                        {
                                                szInfo = FileReadString(file);
                                                szInfo += " " + FileReadString(file);                                           
                                                m_ArrayInfoTicks[m_ArrayCount].dt = macroRemoveSec(StringToTime(StringSubstr(szInfo, 0, 19)));
                                                m_ArrayInfoTicks[m_ArrayCount].milisec = (int)StringToInteger(StringSubstr(szInfo, 20, 3));
                                                m_ArrayInfoTicks[m_ArrayCount].Bid = StringToDouble(FileReadString(file));
                                                m_ArrayInfoTicks[m_ArrayCount].Ask = StringToDouble(FileReadString(file));
                                                m_ArrayInfoTicks[m_ArrayCount].Last = StringToDouble(FileReadString(file));
                                                m_ArrayInfoTicks[m_ArrayCount].Vol = vol + StringToInteger(FileReadString(file));
                                                flag = m_ArrayInfoTicks[m_ArrayCount].flag = (uchar)StringToInteger(FileReadString(file));
                                                if (((flag & TICK_FLAG_ASK) == TICK_FLAG_ASK) || ((flag & TICK_FLAG_BID) == TICK_FLAG_BID)) continue;
                                                if ((mem_dt == macroGetMin(m_ArrayInfoTicks[m_ArrayCount].dt)) && (last == m_ArrayInfoTicks[m_ArrayCount].Last)) vol += m_ArrayInfoTicks[m_ArrayCount].Vol; else
                                                {
                                                        mem_dt = macroGetMin(m_ArrayInfoTicks[m_ArrayCount].dt);
                                                        last = m_ArrayInfoTicks[m_ArrayCount].Last;
                                                        vol = 0;
                                                        m_ArrayCount++;
                                                }
                                        }
                                        FileClose(file);
                                        Print("Upload completed.\n", m_ArrayCount, " movement positions were generated.\nStarting Replay.");
                                        return;
                                }
                                Print("Failed to access tick data file.");
                                ExpertRemove();
                        };
#undef macroRemoveSec
#undef macroGetMin

強調表示されたコードは、以前に存在したが気付かなかった小さな問題を修正します。価格が変わらないがバーからバーに移行した場合、新しいバーを作成するのに時間がかかりました。しかし、本当の問題は、始値が元のチャートに示されていたものと異なっていたことです。これは修正されました。他のすべてのパラメータが同じであるかミリ秒単位で小さな違いがある場合、保存されるポジションは1つだけになります。

その後、EventSetMillisecondTimerを20に設定してシステムをテストできます。結果は以下のとおりです。

 

この場合、結果は20ミリ秒のイベントで2分34秒でした。次に、EventSetMillisecondTimerの値を10(ドキュメントで指定されている最小値)に変更しましょう。以下が結果です。

 

この場合、結果は10ミリ秒のイベントで1分56秒でした。結果は改善されましたが、必要なものにはまだ程遠いです。そして、この記事で採用した方法を使用して時間イベントをさらに短縮する方法はありません。これは不可能であることがドキュメント自体に示されているためです。そうしないと、次のステップに進むことができる十分な安定性が得られません。


結論

この記事では、リプレイ/シミュレーションシステムを作成したい方向けに基本原則を説明しました。これらの原則はシステム全体の基礎となっていますが、プログラミング経験がないと、MetaTrader 5プラットフォームがどのように機能するかを理解するのは困難な作業になる可能性があります。これらの原則が実際にどのように適用されているかを見ることは、プログラミングの学習を始める大きな動機となるでしょう。なぜなら、物事はどのように機能するかを見て初めて面白くなるからです。コードを見ているだけではモチベーションが上がりません。

何ができるかを理解し、それがどのように機能するかを理解すると、すべてが変わります。まるで魔法の扉が開いて、可能性に満ちたまったく新しい未知の世界が現れるかのようです。この連載を通してこれがどのように起こるのかを見ていきます。記事を作成しながら開発していきますので、しばらくお待ちください。進歩がないように見えても、必ず進歩があります。そして知識は多すぎるということはありません。それによって私たちの幸福度が下がる可能性もありますが、決して害を及ぼすことはありません。

添付ファイルには、ここで説明した2つのバージョンが含まれています。また、実際のティックファイルが2つあるので、ご自分のハードウェア上でシステムがどのように動作するかを実験して確認できます。結果は私が示したものとそれほど変わりませんが、コンピューターが特定の問題をどのように処理し、非常に創造的な方法で解決するかを確認するのは非常に興味深い場合があります。

次の記事では、より適切なシステムを実現するためにいくつかの変更を加えます。また、プログラミングの世界への旅を始めたばかりの方にとっても役立つ、別の興味深い解決法についても見ていきます。ということで、仕事は始まったばかりです。


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

添付されたファイル |
Replay.zip (10910.23 KB)
リプレイシステムの開発—市場シミュレーション(第2回):最初の実験(II) リプレイシステムの開発—市場シミュレーション(第2回):最初の実験(II)
今回は、1分という目標を達成するために、別の方法を試してみましょう。ただし、このタスクは思っているほど単純ではありません。
MQL5:あなたもこの言語の達人になれます MQL5:あなたもこの言語の達人になれます
この記事は自己インタビューのようなもので、私がどのようにMQL5言語への第一歩を踏み出したかをお話しします。どうすれば優れたMQL5プログラマーになれるかをお見せして、この偉業を達成するために必要なベースについて説明します。唯一の前提条件は学ぶ意欲です。
MQL5の圏論(第8回):モノイド MQL5の圏論(第8回):モノイド
MQL5における圏論の実装についての連載を続けます。今回は、ルールと単位元を含むことで、圏論を他のデータ分類法と一線を画す始域(集合)としてモノイドを紹介します。
モスクワ取引所(MOEX)におけるストップ注文を利用した取引所グリッド取引の自動化 モスクワ取引所(MOEX)におけるストップ注文を利用した取引所グリッド取引の自動化
本稿では、MQL5エキスパートアドバイザー(EA)に実装されたストップ指値注文に基づくグリッド取引についてモスクワ取引所(MOEX)で考察します。市場で取引する場合、最も単純な戦略の1つは、市場価格を「キャッチ」するように設計された注文のグリッドです。