リプレイシステムの開発(第31回):エキスパートアドバイザープロジェクト - C_Mouseクラス(V)
はじめに
前回の記事「リプレイシステムの開発(第28回):エキスパートアドバイザープロジェクト - C_Mouseクラス(IV)」では、新しいリソースやモデルをテストするために、クラスシステムをどのように変更、追加、または自分のスタイルに合わせることができるかを見てきました。こうすることで、メインのコードに依存することを避け、常に信頼性、安定性、堅牢性を保つことができます。新しいリソースは、作成したモデルが完全に適合した後に、メインのシステムにプッシュされるからです。リプレイ/シミュレーションシステムの開発における主な課題であり、おそらくこの仕事を難しくしているのは、リアル口座で取引するときに使用するシステムに、同一ではないにしても、可能な限り近い仕組みを作ることです。リプレイやシミュレーションをおこなうためにシステムを作っても、リアル口座が使用されたときに同じリソースが使えないのでは意味がありません。
C_Mouseクラスのシステムと、以前の記事で紹介した分析クラスを見ると、デモ口座であれリアル口座であれ、ライブマーケットで使用する場合、タイマーは常に次のバーがいつ始まるかを教えてくれることに気づくでしょう。しかし、レプリケーション/シミュレーションシステムを使用する場合、それは当てになりません。ここで分かることがあります。一見すると、このような対称性の破れは特に意味がないように思えるかもしれません。しかし、無駄なものを是正も除去もせずに溜め込んでおくと、本当に解決しなければならない問題の解決を妨げるだけの、まったく役に立たないガラクタの山ができてしまいます。リプレイ/シミュレーションの終了まで残り時間を表示できるタイマーが必要です。これは一見、シンプルで迅速な解決策に見えるかもしれません。多くの人は、取引サーバーが使用しているのと同じシステムを適応して使用しようとするだけです。しかし、この解決策を考えるとき、多くの人が考慮しないことがあります。リプレイでは、そしてシミュレーションではなおさら、時計の動きは異なるということです。これにはいくつかの理由があります。
- リプレイは常に過去を指します。このように、プラットフォームやコンピューター上の時計は、決して時間を示すのに十分なものではありません。
- リプレイ/シミュレーション中に、時間を早送りしたり、一時停止したり、後退させたりすることができます。後者のケースはもはや不可能であり、以前の記事でお話ししたようなさまざまな理由から、かなり前にそうなっています。早送りも一時停止もできます。そのため、取引サーバーで使用されているタイミングはもはや適切ではありません。
リプレイ/シミュレーションシステムでタイマーを設定することがいかに複雑か、そして実際に何を扱っているかをご理解いただくために、図01をご覧ください。
図01:実際の市場におけるタイマー
この図01は、チャートに新しいバーが表示されるタイミングを示すタイマーの動作を示しています。これはとても短い図です。時々、OnTimeイベントがあります。Updateイベントがトリガーされ、タイマーの値が更新されます。こうすることで、新しいバーが表示されるまでの残り時間を提示することができます。しかし、Update関数がどのような値が表示されるかを知るためには、GetBarTime関数にバーが表示されるまでの時間を要求します。GetBarTimeは、サーバー上で実行されるのではなく、サーバー上でローカル時間を固定するTimeCurrentを使用します。したがって、最後のバーでサーバーがトリガーされてからどれだけの時間が経過したかを知ることができ、この値に基づいて、新しいバーがトリガーされるまでの残り時間を計算することができます。システムが一時停止したかどうか、または一定の時間が経過したかどうかを心配する必要がないため、これはこの全体のストーリーの中で最も単純な部分です。これは、データが取引サーバーから直接送られてくる資産を使用する場合に起こります。リプレイ/シミュレーションシステムを扱うと、物事はもっと複雑になります。大きな問題は、私たちがリプレイ/シミュレーションモードで作業をしていることではありません。問題は、リプレイやシミュレーションでTimeCurrentを呼び出すのを回避する方法を考え出すことです。なぜなら、問題全体が生じるのはこの瞬間だからです。ただし、これは最小限の変更でおこなわなければなりません。TimeCurrentの呼び出しシステムをバイパスしたいだけです。しかし、サーバーと作業する際には、図01のようなシステムにしたいのです。
幸いなことに、MetaTrader 5を使用することで、最小限の手間で、すでに実装されているコードの修正や追加を大幅に減らすことができます。この記事でお話しするのは、まさにこのことです。
計画
これをどうやるかという計画は、おそらく最も簡単な部分でしょう。リプレイ/シミュレーションサービスによって計算された時間値をシステムに送るだけです。これは簡単な部分です。このためにグローバル端末変数を使用するだけです。これまで連載で、これらの変数の使い方を見てきました。ある記事で、ユーザーが何をしたいかをサービスに知らせるコントロールを紹介しました。多くの人は、これらの変数を使用するとdoubleデータしか転送できないと思っています。しかし、彼らは1つの事実を忘れています。バイナリは単なるバイナリであり、いかなる場合も0と1以外の情報を表すことがないということです。そのため、これらのグローバル端末変数を通じて、あらゆる情報を渡すことができます。もちろん、後で情報を再構成できるようにビットを論理的に並べることができればの話ですが。幸いなことに、DateTime型は実際にはulong値です。つまり、DateTime値には8バイトの情報が必要で、年、月、日、時、分、秒を含む完全な日付と時刻が表現されます。TimeCurrentの呼び出しは8バイトで同じ値を使い、同じ値を返すので、個の呼び出しをバイパスするのに必要なのはこれだけです。Double型はプラットフォーム内の情報転送にちょうど64ビットを使用するので、これが解決策となります。
ただし、そう単純な話ではありません。このシステムがどのように導入されるかを説明すれば明らかになるであろう、いくつかの問題があります。サービスは非常にシンプルで簡単に構築できますが、タイマーで表現できる情報をクラスに提供することを目的とした、必要な修正に関連する若干の複雑さがあります。最後に、図02のような結果が得られます。
図02:汎用タイマー
この汎用タイマーは、それに応じて反応することができ、完全に透明であるため、ユーザーはデモ/ライブ口座でおこなうのと非常に近いリプレイ/シミュレーション体験をすることができます。これは、このようなシステムを作る目的です。つまり、システムの使い方をゼロから学び直すことなく、同じ経験をし、ユーザーが正確に行動する方法を知ることができるようにするためです。
実装
この部分がこのシステムで最も興味深いところです。この時点で、TimeCurrentの呼び出しをバイパスする方法を見てみましょう。ユーザーは、自分がリプレイを使用しているのか、それともサーバーとやりとりしているのかを知るべきではありません。システムの実装を開始するには、(これまでのトピックから明らかなように)新しいグローバル端末変数を追加する必要があります。同時に、double変数を介してDateTime情報を渡す適切な手段が必要です。これには、Interprocess.mqhヘッダーファイルを使用します。また、次のようなことも加えています。
#property copyright "Daniel Jose" //+------------------------------------------------------------------+ #define def_SymbolReplay "RePlay" #define def_GlobalVariableReplay def_SymbolReplay + " Infos" #define def_GlobalVariableIdGraphics def_SymbolReplay + " ID" #define def_GlobalVariableServerTime def_SymbolReplay + " Time" #define def_MaxPosSlider 400 #define def_ShortName "Market " + def_SymbolReplay //+------------------------------------------------------------------+ union u_Interprocess { union u_0 { double df_Value; // Value of the terminal global variable... ulong IdGraphic; // Contains the Graph ID of the asset... }u_Value; struct st_0 { bool isPlay; // Indicates whether we are in Play or Pause mode... bool isWait; // Tells the user to wait... ushort iPosShift; // Value between 0 and 400... }s_Infos; datetime ServerTime; }; //+------------------------------------------------------------------+
ここでは、通信に使用するグローバル端末変数の名前を設定します。次に、datetime形式のデータにアクセスするための変数を定義します。その後、C_Replayクラスに行き、クラスのデストラクタに以下を追加します。
~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); GlobalVariableDel(def_GlobalVariableServerTime); Print("Finished replay service..."); }
この行を追加することで、リプレイ/シミュレーションサービスを終了する際に、タイマーに時間値を渡すグローバル端末変数を確実に削除することができます。ここで、このグローバル端末変数が適切なタイミングで作成されるようにする必要があります。ループは異なるタイミングで起動される可能性があるため、ループの実行中に同じグローバル変数を作成しないでください。システムが有効になるとすぐに、グローバル端末変数の内容にアクセスできるようになるはずです。ある意味では、コントロール指標に似た作成システムを使用することも考えました。ただし、現時点ではEAがチャート上に存在するかどうかはわからないので、少なくとも現時点では、サービスによって作成されたタイマーの責任を端末変数に負わせることにしましょう。こうすることで、少なくとも自分たちの行動を最低限適切にコントロールできるようになります。おそらく、状況は将来変わるでしょう。以下のコードは、その作成場所を示しています。
bool ViewReplay(ENUM_TIMEFRAMES arg1) { #define macroError(A) { Print(A); return false; } u_Interprocess info; if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE) == 0) macroError("Asset configuration is not complete, it remains to declare the size of the ticket."); if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_VALUE) == 0) macroError("Asset configuration is not complete, need to declare the ticket value."); if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_VOLUME_STEP) == 0) macroError("Asset configuration not complete, need to declare the minimum volume."); 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("Waiting for [Market Replay] indicator permission to start replay ..."); info.ServerTime = m_Ticks.Info[m_ReplayCount].time; CreateGlobalVariable(def_GlobalVariableServerTime, info.u_Value.df_Value); info.u_Value.IdGraphic = m_IdReplay = ChartOpen(def_SymbolReplay, arg1); ChartApplyTemplate(m_IdReplay, "Market Replay.tpl"); CreateGlobalVariable(def_GlobalVariableIdGraphics, info.u_Value.df_Value); while ((!GlobalVariableCheck(def_GlobalVariableReplay)) && (!_StopFlag) && (ChartSymbol(m_IdReplay) != "")) Sleep(750); return ((!_StopFlag) && (ChartSymbol(m_IdReplay) != "")); #undef macroError }
このごちゃごちゃした行の真ん中に、変数の値を初期化するポイントがあります。これにより、システムがすでに使用する値を持っていることが保証され、リプレイ/シミュレーションが実際に同期しているかどうかを確認することができます。この直後に、グローバル端末変数を作成して初期化する呼び出しがあります。この初期化コードを以下に示します。
void CreateGlobalVariable(const string szName, const double value) { GlobalVariableDel(szName); GlobalVariableTemp(szName); GlobalVariableSet(szName, value); }
作成と初期化のコードは非常にシンプルです。プラットフォームが終了し、グローバル端末変数の保存がリクエストされた場合、リプレイ/シミュレーションシステムで使用される変数は保存されません。このような値が保存され、後で検索されることは避けたいです。これでタイマーの読み取りについてみることができます。しかし、現時点でこれをおこなったとしても、そこに含まれる価値は常に同じであり、実利をもたらすとは限りません。リプレイ/シミュレーションサービスが一時停止したように表示されます。しかし、タイマーが作動しているとき、つまりプレイモードのときに動いてほしいのです。そこで、TimeCurrent関数の要件をモデル化します。この関数では、サーバーにある対応する時刻と日付のデータを取得します。そのためには、グローバル変数の値が約1秒ごとに変化する必要があります。このプロセスにはタイマーをセットするのが正しいでしょうが、このようなことはできないので、グローバル変数の値の変更を実装する別の手段が必要です。
時間を決定するためには、C_Replayクラスにいくつかの追加を実装する必要があります。それらは以下のコードで見ることができます。
bool LoopEventOnTime(const bool bViewBuider) { u_Interprocess Info; int iPos, iTest, iCount; if (!m_Infos.bInit) ViewInfos(); 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); Info.ServerTime = m_Ticks.Info[m_ReplayCount].time; GlobalVariableSet(def_GlobalVariableServerTime, Info.u_Value.df_Value); iPos = iCount = 0; while ((m_ReplayCount < m_Ticks.nTicks) && (!_StopFlag)) { iPos += (int)(m_ReplayCount < (m_Ticks.nTicks - 1) ? m_Ticks.Info[m_ReplayCount + 1].time_msc - m_Ticks.Info[m_ReplayCount].time_msc : 0); CreateBarInReplay(true); while ((iPos > 200) && (!_StopFlag)) { if (ChartSymbol(m_IdReplay) == "") return false; 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(195); iPos -= 200; iCount++; if (iCount > 4) { iCount = 0; GlobalVariableGet(def_GlobalVariableServerTime, Info.u_Value.df_Value); Info.ServerTime += 1; GlobalVariableSet(def_GlobalVariableServerTime, Info.u_Value.df_Value); } } } return (m_ReplayCount == m_Ticks.nTicks); }
新しく追加された変数は、新しいバーが始まる時間をおおよそ予測するのに役立つでしょう。しかし、時間を進めることができるので、「サーバー」から指定された値をリセットする必要があります。これは、関数が新しい観測点を指す瞬間におこなわれます。しばらくの間、一時停止モードのままであっても、その価値が適切なものであることを確信しなければなりません。本当の問題は、タイマーをセットしようとしたときです。どんな価値でも獲得できるわけではありません。システムは、コンピュータの時計に表示される実時間より進んでいる場合もあれば、遅れている場合もあります。そのため、この早い段階でタイミングを図っています。バージェネレーターにはタイマーが内蔵されているのです。それを参考にします。およそ195ミリ秒ごとにティックを生成し、5単位のカウントに近づけます。ゼロから始めたので、カウンターの値が4より大きいかどうかを確認します。このとき、時間を1単位、つまり1秒増加させ、この値をグローバル端末変数に入れ、システムの他の部分がそれを使用できるようにします。そしてループが繰り返されます。
これにより、新しいバーがいつ現れるかを知ることができます。そう、これはまさにその通りなのですが、時期によって若干のバリエーションは可能です。そして、それが累積されるにつれて、時間はずれていきます。必要なのは、時間を許容範囲内で同期させることであって、完璧を目指すことではありません。ほとんどの場合、かなり近いところまで行けます。そのために、バーとタイマーの同期性を高めるために、上述の関数を少し変更します。これらの変更点を以下に示します。
//... Sleep(195); iPos -= 200; iCount++; if (iCount > 4) { iCount = 0; GlobalVariableGet(def_GlobalVariableServerTime, Info.u_Value.df_Value); Info.ServerTime += 1; Info.ServerTime = ((Info.ServerTime + 1) < m_Ticks.Info[m_ReplayCount].time ? Info.ServerTime : m_Ticks.Info[m_ReplayCount].time); GlobalVariableSet(def_GlobalVariableServerTime, Info.u_Value.df_Value); } //...
無茶だと思うかもしれません。ある意味、これはちょっと無茶な行動だと私も思います。しかし、この特定の行を追加することで、許容できるレベルで同期を維持できるようになります。比較的短時間、できれば1秒以内に取引が成立する程度に流動性の高い商品であれば、かなり同期化されたシステムになります。完璧に近いシステムかもしれません。しかしこれは、その商品が実際に十分な流動性を持っている場合にのみ達成されます。どうなっているのでしょうか。少し考えてみましょう。約195ミリ秒のループを5回繰り返すごとに、タイマーを更新するコードを実行します。従って、更新レートは約975ミリ秒となり、各ループで25ミリ秒のミスがあることになります。しかし実際には、この値は一定ではありません。少し大きくなることもあれば、少し小さくなることもあります。この違いを克服するために、システムを強制的にスローダウンさせるために、新しいsleepコマンドを使用して同期を設定しようとしてはなりません。一見したところ、これでうまくいくはずですが、時間が経つにつれて、こうした微小な違いが大きくなり、システム全体が同期しなくなります。この問題を解決するために、何か違うことをします。時間を知る代わりに、バーそのものを使用して同期を確保するのです。CreateBarInReplay関数が実行されると、常に現在のティックを指します。このティックの時間値をグローバル変数の時間値と比較することで、場合によっては1より大きな値(この場合は1秒)を得ることができます。この値がより低い場合、つまりInfo.ServerTime変数が累積25ミリ秒だけ時間的に遅れている場合、現在のティックタイム値がその差を修正するために使用され、タイマーは完璧な値に非常に近くなります。しかし、説明の冒頭ですでに報告したように、このメカニズムは、使用する金融商品に十分な流動性があれば、自動的にシステムを調整します。取引が長時間停止し、1つの取引と別の取引の間に5~10分が経過すると、タイミングシステムの精度が低下します。これは、1秒ごとに平均25ミリ秒遅れるからです(これは平均値であり、正確な値ではありません)。
これで次の部分に進むことができます。これはC_Study.mqhヘッダーファイルにあり、ここでシステムにデータを報告させ、チャートに新しいバーが表示されるタイミングを正しく推定できるようにします。
C_Studyクラスの適応
修正を開始するには、まず以下のように変更する必要があります。
void Update(void) { switch (m_Info.Status) { case eCloseMarket: m_Info.szInfo = "Closed Market"; break; case eAuction : m_Info.szInfo = "Auction"; break; case eInReplay : case eInTrading : m_Info.szInfo = TimeToString(GetBarTime(), TIME_SECONDS); break; case eInReplay : m_Info.szInfo = "In Replay"; break; default : m_Info.szInfo = "ERROR"; } Draw(); }
現物市場でシステムを使用するときと同じステータスと機能を持つために、消されたラインを取り除き、より高いレベルに移動させます。このようにして、物事を平準化します。つまり、リプレイ/シミュレーション中と、デモ/リアル口座の両方で、同じ動作をすることになります。これで、GetBarTime関数のコードに加えられた最初の修正を見ることができます。
const datetime GetBarTime(void) { datetime dt; u_Interprocess info; if (m_Info.Status == eInReplay) { if (!GlobalVariableGet(def_GlobalVariableServerTime, info.u_Value.df_Value)) return ULONG_MAX; dt = info.ServerTime; }else dt = TimeCurrent(); if (m_Info.Rate.time <= dt) m_Info.Rate.time = iTime(GetInfoTerminal().szSymbol, PERIOD_CURRENT, 0) + PeriodSeconds(); return m_Info.Rate.time - dt; }
分析システムでマジックが起こるのはここからです。この関数の古いバージョンでは、この呼び出しはタイマーをセットしていました。しかし、リプレイ/シミュレーションシステムで動かすには適していません。そこで、この呼び出しをバイパスするメソッドを作り、システムがどこで使われても同じ概念と情報を持つようにします。そこで、リプレイで使用されるチャートにコードが表示されている期間中に使用される余分なものを追加します。これは特別なことではありません。グローバル変数に報告された値を、あたかもトレードサーバーから送られてきたかのように使用するのです。ここで実際にシステムをバイパスします。しかし、取引回数が少ないチャート時間帯では、バーを正しく構築するのに十分でないという問題があります。これは、使用するデータに日中取引が含まれている場合に特に当てはまります(一部の資産では非常に一般的なケースです)。失敗に終わるでしょう。この失敗は、提示された情報と表示された情報の間にギャップとして現れます。サービスが更新されなくなったからではありません。指標には何の情報も表示されず、何が起きているのかわからないまま、暗闇に取り残されます。たとえその商品に取引がなくても、いくつかのFOREX取引ペアでは、バーが開いたときに取引が1つしか発生しないことはよくあることです。これは、添付ファイルに見られるように、その日の最初にバーが開いた時点と実際に取引が発生した時点との間にギャップがあります。この段階では、何が起きているのかについての情報は一切見えません。これは、システムが期待するポイント以外で取引がおこなわれているか、使用されている資産がオークションに参加しているかのどちらかであるため、何らかの方法で修正する必要があります。本当に、可能な限り現実に近づける必要があります。
これらの問題を解決するには、クラスコードに2つの変更を加えます。今すぐやります。最初の変更は以下の通りです。
void Update(void) { datetime dt; switch (m_Info.Status) { case eCloseMarket: m_Info.szInfo = "Closed Market"; break; case eInReplay : case eInTrading : dt = GetBarTime(); if (dt < ULONG_MAX) { m_Info.szInfo = TimeToString(dt, TIME_SECONDS); break; } case eAuction : m_Info.szInfo = "Auction"; break; default : m_Info.szInfo = "ERROR"; } Draw(); }
上に示したUpdateコードは、奇妙で複雑な外見とは裏腹に、見た目よりもずっとシンプルで便利なものです。次のようなシナリオがあります。リプレイシステム、あるいは実際のマーケットで、GetBarTime関数からULONG_MAX値を受け取った場合、オークションに関するメッセージを表示します。値がこのULONG_MAXより小さい場合(通常の状況では常にそうである)、タイマーの値が表示されます。
この情報に基づいてGetBarTime関数に戻り、Update関数に必要なデータを生成して正しいデータをプロットし、ユーザーが状況を把握できるようにします。新しいGetBarTime関数のコードは以下のようになります。
const datetime GetBarTime(void) { datetime dt; u_Interprocess info; int i0 = PeriodSeconds(); if (m_Info.Status == eInReplay) { if (!GlobalVariableGet(def_GlobalVariableServerTime, info.u_Value.df_Value)) return ULONG_MAX; dt = info.ServerTime; if (dt == ULONG_MAX) return ULONG_MAX; }else dt = TimeCurrent(); if (m_Info.Rate.time <= dt) m_Info.Rate.time = (datetime)(((ulong) dt / i0) * i0)) + i0; return m_Info.Rate.time - dt; }
この素晴らしいコードによって、少なくとも今のところは問題は完全に解決しています。ここで何が起こっているのか見てみましょう。TimeCurrent関数を使用する現物市場の場合は、何も変わりません。これは序盤の話です。しかし、リプレイシステムになると、状況は非常に奇妙に変化します。したがって、リプレイやシミュレーションのデータで何が起こっているかにかかわらず、システムがどのように起こっていることを表現しているのかを理解することにご注意ください。サービスがグローバル端末変数にULONG_MAX値を配置する場合、またはこの変数が見つからない場合、GetBarTime関数はULONG_MAX値を返す必要があります。この後、Updateメソッドがオークションモードであることを教えてくれます。タイマーを進めることはできません。ここからが面白いところで、2つ目の問題を解決してくれます。取引サーバーに接続された金融商品でシステムを使用する場合、常に同期しているのとは異なり、リプレイ/シミュレーションモードを使用する場合は、物事が制御不能になり、かなり異常な状況に遭遇する可能性があります。この問題を解決するために、現物市場と私たちが開発しているシステムの両方で機能するこの計算を使用します。この計算では、現在のバーの開始時刻を知る必要があった従来の方法を置き換えます。こうして、リプレイ/シミュレーションを使用して両方の問題を解決することができました。
しかし、C_Replayクラスに戻り、資産がいつオークションされたかをシステムが表示できるようにする必要があります。この部分は、グローバル端末変数にULONG_MAX値をセットするだけなので、比較的簡単です。他の問題が目の前にあるため、これは比較的簡単に説明されていることに注意してください。これが実際にどうなるかを見てみましょう。
C_Replayクラスを通信システムに適応させる
C_Replayクラスで最初にすることは、以下のコードを変更することです。
bool ViewReplay(ENUM_TIMEFRAMES arg1) { #define macroError(A) { Print(A); return false; } u_Interprocess info; if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE) == 0) macroError("Asset configuration is not complete, it remains to declare the size of the ticket."); if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_VALUE) == 0) macroError("Asset configuration is not complete, need to declare the ticket value."); if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_VOLUME_STEP) == 0) macroError("Asset configuration not complete, need to declare the minimum volume."); 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("Waiting for [Market Replay] indicator permission to start replay ..."); info.ServerTime = ULONG_MAX; info.ServerTime = m_Ticks.Info[m_ReplayCount].time; CreateGlobalVariable(def_GlobalVariableServerTime, info.u_Value.df_Value); info.u_Value.IdGraphic = m_IdReplay = ChartOpen(def_SymbolReplay, arg1); ChartApplyTemplate(m_IdReplay, "Market Replay.tpl"); CreateGlobalVariable(def_GlobalVariableIdGraphics, info.u_Value.df_Value); while ((!GlobalVariableCheck(def_GlobalVariableReplay)) && (!_StopFlag) && (ChartSymbol(m_IdReplay) != "")) Sleep(750); return ((!_StopFlag) && (ChartSymbol(m_IdReplay) != "")); #undef macroError }
一行を削除し、別の行に置き換えていることにご注目ください。この変更により、リプレイ/シミュレーションサービスが開始され、資産がチャート上で実行されている場合、オークションに関するメッセージが表示されます。システムが正常に通信していることを示します。簡単なのはここまでで、難しい部分に移ります。すでにサービスが開始されている場合、その資産がオークションされたかどうかを調べる必要があります。資産がオークションされる理由はさまざまで、そのすべてがまったく予期せぬものである可能性があります。もっと大きな変動があったかもしれないし、オーダーブックが暴落して完全に帳消しになったかもしれません。資産がオークションに出された理由は重要ではありません。重要なのは、オークションされたときにどうなるかです。オークションがどのように実施されるかを決定する具体的なルールはありますが、私たちにとって最も重要なルールは、資産がオークションされる最低時間はどれくらいであるかということです。まさにここがポイントです。この時間が1分未満であれば、リプレイシミュレーションシステムがそれを検知することなく、実市場の資産が取引を開始したり終了したりすることができます。というより、この変動はバーの時間を定義するのに使用できる最短時間内に常に発生するため、気づくことはできません。
ある資産がオークションされたことを判断するためにリプレイ/シミュレーションシステムで使用できる最も単純なメカニズムは、あるバーと別のバーの時間差を確認することです。この差が1分を超えた場合、その資産がオークションプロセスに入ったばかりであることをユーザーに通知し、全期間にわたってオークションを中断しなければなりません。このような仕組みは将来的に役に立つでしょう。今のところ、その開発と実施のみを扱い、他の問題は別の機会に譲ります。この問題を解決する方法を見てみましょう。これは、以下のコードで確認できます。
bool LoopEventOnTime(const bool bViewBuider) { u_Interprocess Info; int iPos, iTest, iCount; if (!m_Infos.bInit) ViewInfos(); 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); Info.ServerTime = m_Ticks.Info[m_ReplayCount].time; GlobalVariableSet(def_GlobalVariableServerTime, Info.u_Value.df_Value); iPos = iCount = 0; while ((m_ReplayCount < m_Ticks.nTicks) && (!_StopFlag)) { iPos += (int)(m_ReplayCount < (m_Ticks.nTicks - 1) ? m_Ticks.Info[m_ReplayCount + 1].time_msc - m_Ticks.Info[m_ReplayCount].time_msc : 0); CreateBarInReplay(true); while ((iPos > 200) && (!_StopFlag)) { if (ChartSymbol(m_IdReplay) == "") return false; 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(195); iPos -= 200; iCount++; if (iCount > 4) { iCount = 0; GlobalVariableGet(def_GlobalVariableServerTime, Info.u_Value.df_Value); if ((m_Ticks.Info[m_ReplayCount].time - m_Ticks.Info[m_ReplayCount - 1].time) > 60) Info.ServerTime = ULONG_MAX; else { Info.ServerTime += 1; Info.ServerTime = ((Info.ServerTime + 1) < m_Ticks.Info[m_ReplayCount].time ? Info.ServerTime : m_Ticks.Info[m_ReplayCount].time); }; GlobalVariableSet(def_GlobalVariableServerTime, Info.u_Value.df_Value); } } } return (m_ReplayCount == m_Ticks.nTicks); }
ティックと次のティックの時差を確認しています。この差が60秒より大きい場合、つまり最短のバー作成時間より大きい場合、これは「オークション呼び出し」であると報告し、リプレイ/シミュレーションシステム全体がオークションを表示します。時間差が60秒以下であれば、資産がまだアクティブであることを意味し、この記事で説明したようにタイマーを開始する必要があります。これで現在のステージは終了しました。
結論
今日は、新しいバーの出現を示すタイマーを、完全に実用的で、堅牢で、信頼性が高く、効果的な方法で追加する方法について見ました。不可能と思われる瞬間を何度か経験しましたが、実際には不可能なことなどありません。克服するのはもう少し難しいかもしれませんが、問題を解決するための適切な方法はいつでも見つけられます。ここでの要点は、それが必要とされるすべての状況において、常に適用可能な解決策を生み出すよう努めるべきであるということを示すことです。そのようなモデルを適用するために、まったく異なるものを使用しなければならないのであれば、ユーザーのために、あるいは(可能性を学ぶために)私たち自身のために何かをプログラムすることに意味がありません。これでは完全にやる気がなくなります。だから、いつも忘れないでほしいのです。もし何かをする必要があるのなら、それは正しくおこなわれる必要があります。理論的にはすべてがうまくいくが、実際にはまったく異なる挙動を示すようなことがあってはなりません。
MetaQuotes Ltdによりポルトガル語から翻訳されました。
元の記事: https://www.mql5.com/pt/articles/11378
- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索