English Русский Español Deutsch Português
preview
リプレイシステムの開発 - 市場シミュレーション(第25回):次の段階への準備

リプレイシステムの開発 - 市場シミュレーション(第25回):次の段階への準備

MetaTrader 5テスター | 30 3月 2024, 15:06
76 0
Daniel Jose
Daniel Jose

はじめに

前回の「リプレイシステムの開発 - 市場シミュレーション(第24回):FOREX (V)」稿では、一見異なるように見える2つの宇宙を、いかに調和させて統合できるかを示しました。一方にはBidベースのチャートがあり、もう一方にはLastベースのチャートがあります。私たちの目的は、理想的には1分間のチャート時間を表すバーだけをカウントすることによって、起こりそうな値動きをシミュレーションする、あるいはより正確に生成する方法を作成することでした。これは非常に興味深い挑戦でした。提示された解決策は効果的ではありますが、この特定の目標を達成する唯一の方法ではありません。しかし、その解決策が有効であることが判明したので、この段階は完了したと考えています。特定のモデルを解くことができなくなるまではです。そうなったら、未解決のモデルをカバーできるように、提案された解決策を再度改良します。

まだ修正していないところがあります。しかし実際には、これは変更を加えるということではなく、まだ実装するつもりの要素に大きく干渉する可能性のある機能を削除するということです。その1つが過去に戻る能力です。コントロールを使用して過去に戻るという問題は、システムから取り除かなければならないものです。この機能は長期的には非現実的であることが判明しており、まだ問題は起きていませんが、新機能を実装していくうちに必ず問題が起きるでしょう。コントロールを使用して過去に戻るというアイデアは面白いと思われるかもしれません。私も面白い概念だとは思いますが、実際にはそれほど機能的ではありません。多くの場面で時間を遡る能力は、それが生み出す問題に対処する際に頭痛の種となります。

この機能を修正するのは特に難しいことではありません。コントロール表示のテストと確認を追加する必要があるため、少し面倒なだけです。システムから別の要素を取り除くことも考えています。この記事でこの決断を下します。このコントロール表示の変更とともに、サービスが効果的に機能するために改善が必要なその他の問題にも焦点を当てます。この記事で紹介されている展開は、非常に有益であることが約束されていますので、ぜひついてきてください。今日は、プログラミングやシステム開発の学習に役立つこと間違いなしの、興味深い概念をたくさん取り上げます。この記事の最初のトピックから始めましょう。


コントロール指標の使用制限

まずは、ユーザーが過去に戻ることができないように、コントロール指標にいくつかの制限を導入することから始めましょう。「過去に戻る」というのは、ある程度進むと、コントロール指標を使用して前のポジションに戻ることができなくなるという意味です。操作を元に戻すには、リプレイ/シミュレーションサービスを終了し、プロセスを最初からやり直す必要があります。この制限は大変に思えるかもしれませんが、私を信じてください。この方法は、将来、ゴーバック機能を使おうとしたときに起こるかもしれない多くの問題を防ぐことができます。

この制限を実装するのは難しくありませんが、システムに特定のテストを追加する必要があるため、多少の労力が必要となります。これらのテストは、指標の他の機能と衝突を起こさないように注意して使用し、指標が効果的に機能するようにしなければなりません。この作業をいくつかの手順に分け、効率的に変更を実施しやすくします。


はじめに:微調整ボタンのオン/オフ

この段階での作業は比較的簡単です。これは、コントロールパネルの端にある微調整ボタンへのアクセスを有効または無効にすることを含みます。これらのボタンは下の画像に示されています。

図01

図01:微調整ボタン

これらのボタンにより、希望の前進速度を簡単に微調整できます。ある一定の時間を正確に進めたり戻したりすることができます。しかし、ユーザーが過去に戻ることを防ぐためには、これらのボタンの存在を必要に応じて隠したり、表示したりすることが重要です。この手順をよりよく理解するために、こう考えてみてください。システムがアイテムを1つも進めていないのに、なぜ左のボタンをアクティブにしておくのでしょうか。あるいは、システムが前進の上限に達したとき、なぜ右のボタンが必要なのでしょうか。最後に生成されたチケットがあるとします。なぜ右ボタンが必要なのでしょうか。取っておく必要はないということでしょうか。したがって、この段階の目的は、設定された限界を超えて前進または後退することが不可能であることをユーザーに知らせることです。

この作業は簡単でシンプルです。これ以上の移動が不可能な限界に達したら、ボタン表示を無効にすべきです。ただし、少し異なるアプローチを採用するので、結果がより興味深いものになると思います。まず、多くのコードを書く必要はなく、小さな変更を加えるだけです。最初の手順では、コントロール指標リソースとして、ボタンが無効になっているときのビットマップを含めます。これは次のようにおこなわれます。

#define def_ButtonPlay          "Images\\Market Replay\\Play.bmp"
#define def_ButtonPause         "Images\\Market Replay\\Pause.bmp"
#define def_ButtonLeft          "Images\\Market Replay\\Left.bmp"
#define def_ButtonLeftBlock     "Images\\Market Replay\\Left_Block.bmp"
#define def_ButtonRight         "Images\\Market Replay\\Right.bmp"
#define def_ButtonRightBlock    "Images\\Market Replay\\Right_Block.bmp"
#define def_ButtonPin           "Images\\Market Replay\\Pin.bmp"
#define def_ButtonWait          "Images\\Market Replay\\Wait.bmp"
#resource "\\" + def_ButtonPlay
#resource "\\" + def_ButtonPause
#resource "\\" + def_ButtonLeft
#resource "\\" + def_ButtonLeftBlock
#resource "\\" + def_ButtonRight
#resource "\\" + def_ButtonRightBlock
#resource "\\" + def_ButtonPin
#resource "\\" + def_ButtonWait

これらの行は、コントロール指標の中にビットマップを追加するもので、限界で無効になっているボタンを象徴しています。これにより、インターフェイスがより魅力的になり、ユーザーに提供したいものとより一致した外観のボタンを作成できるようになります。自由に変更してください。この手順が完了したら、これらの値を参照する必要があります。コードはほぼできているので、あとはこれらのリソースを参照するだけです。これは以下のコードでおこなわれます。

void CreteCtrlSlider(void)
   {
      u_Interprocess Info;
                                
      m_Slider.szBarSlider = def_NameObjectsSlider + " Bar";
      m_Slider.szBtnLeft   = def_NameObjectsSlider + " BtnL";
      m_Slider.szBtnRight  = def_NameObjectsSlider + " BtnR";
      m_Slider.szBtnPin    = def_NameObjectsSlider + " BtnP";
      m_Slider.posY = 40;
      CreteBarSlider(82, 436);
      CreateObjectBitMap(52, 25, m_Slider.szBtnLeft, def_ButtonLeft, def_ButtonLeftBlock);
      CreateObjectBitMap(516, 25, m_Slider.szBtnRight, def_ButtonRight, def_ButtonRightBlock);
      CreateObjectBitMap(def_MinPosXPin, m_Slider.posY, m_Slider.szBtnPin, def_ButtonPin);
      ObjectSetInteger(m_id, m_Slider.szBtnPin, OBJPROP_ANCHOR, ANCHOR_CENTER);
      if (GlobalVariableCheck(def_GlobalVariableReplay)) Info.u_Value.df_Value = GlobalVariableGet(def_GlobalVariableReplay); else Info.u_Value.df_Value = 0;
      PositionPinSlider(Info.s_Infos.iPosShift);

これらの参照を挿入することで、ボタンを担当するオブジェクトが、望ましい結果を得るための処理をおこなうことができます。今のところ、リソースリンク以外は何も追加しておらず、システムは期待された機能を実行できることにご注意ください。ただし、調整限界に達したときにボタンを変更するには、もう少しコードを追加する必要があります。これは非常にシンプルで簡単な作業なので心配はいりません。必要なコードは以下の通りです。

inline void PositionPinSlider(int p, const int minimal = 0)
   {
      m_Slider.posPinSlider = (p < 0 ? 0 : (p > def_MaxPosSlider ? def_MaxPosSlider : p));
      ObjectSetInteger(m_id, m_Slider.szBtnPin, OBJPROP_XDISTANCE, m_Slider.posPinSlider + def_MinPosXPin);
      ObjectSetInteger(m_id, m_Slider.szBtnLeft, OBJPROP_STATE, m_Slider.posPinSlider != minimal);
      ObjectSetInteger(m_id, m_Slider.szBtnRight, OBJPROP_STATE, m_Slider.posPinSlider < def_MaxPosSlider);
      ChartRedraw();
   }

呼び出しに新しいパラメータを導入しましたが、最初は標準モードでシステムを使用するので、このパラメータは値ゼロから始まります。つまり、現時点では変更の必要はないということです。その後、特定の状況下で限界を試すことができます。コントロールの左にあるボタンを有効または無効にするには、1つの計算を使用します。コントロールの右隅にあるボタンをオフにするには、別の計算を適用します。右クリックの場合、計算はスライダーが上限に達したかどうかだけを考慮します。しかし、左のボタンは異なる動作をし、最初はゼロの値のみに基づきます。コントロール指標のコードをコンパイルし、リプレイ/シミュレーションサービスを実行すると、以下のアニメーションで示される動作が見られます。

アニメーション01

アニメーション01:ボタンのオン/オフシステムのデモンストレーション

このソリューションは理解も実装も非常に簡単で、私たちが本当に必要としていたものを開発するための素晴らしい出発点でした。次に少し難しいタスクに直面しますが、これはユーザーが何が起こっているかを理解するために必要なことです。この問題については、次のトピックで詳しく検討します。


制限の変更をユーザーに通知

スライダーがリプレイ/シミュレーションサービスによって指定された最小ポイントに達したときに、左ボタンのオン/オフを切り替えるだけで、プロセスは非常にシンプルになります。しかし、ユーザーはコントロールを後方、つまりゼロ点に移動させることができないため、混乱する可能性があります。理解を深めるために、下のアニメーションをご覧ください。

アニメーション02

アニメーション02:なぜゼロにできないのか?

アニメーション02は、左ボタンが移動不可能であることを示しているにもかかわらず、スライダーがゼロに達しない場合にユーザーが経験する混乱を明確に示しています。この状況は、現在の表示が十分に明確でないことを示しています。そのため、スライダーを一定以上動かすことができない制限や限界についての通知を改善する必要があります。この表示がどのように実装されるかを詳しく説明する前に、ゼロ点に到達する前にコントロールをロックするためにどのような方法が使用されるのか気になられているかもしれません。好奇心は素晴らしいものです。複雑なソフトウエアのトリックに頼らず、ただ停止点を特定しただけです。でも、どこででしょうか。場所は以下の通りです。

inline void PositionPinSlider(int p, const int minimal = 0)
   {
      m_Slider.posPinSlider = (p < minimal ? minimal : (p > def_MaxPosSlider ? def_MaxPosSlider : p));
      ObjectSetInteger(m_id, m_Slider.szBtnPin, OBJPROP_XDISTANCE, m_Slider.posPinSlider + def_MinPosXPin);
      ObjectSetInteger(m_id, m_Slider.szBtnLeft, OBJPROP_STATE, m_Slider.posPinSlider != minimal);
      ObjectSetInteger(m_id, m_Slider.szBtnRight, OBJPROP_STATE, m_Slider.posPinSlider < def_MaxPosSlider);
      ChartRedraw();
   }

ここで何がおこなわれたのかを不思議に思われているに違いません。 ご心配なく。微妙ですが重要な注意点があります。変数minimalはゼロに設定されています。この値を例えば100や80に変えたらどうなるでしょうか。この時点で値を確認すると、左隅のボタンが無効になります。ただし、ユーザーがスライダーを左クリックまたは左へドラッグした場合、システムが値を下げるのを防ぐことはできません。その通りです。しかし、今スライダーをminimal変数によって正確に定義された位置に設定します。これではっきりしたでしょうか。ユーザーがいくらスライダーを動かそうとしても、左ボタンを押そうとしても、指定されたポイントが最小値として設定された値を下回ることはありません。

面白いとおもわれませんか。可能な最小値を決定するのはリプレイ/シミュレーションサービスの仕事であり、リプレイ/シミュレーションが進むにつれて、この値が自動的に調整されます。しかし、サービス側が使用可能な最小値を変更していなければ、ユーザーはこの点を変更することができます。これは複雑に見えるかもしれませんが、思ったより簡単です。それについては後で説明しましょう。とりあえず、アニメーション02で提起された問題に注目しましょう。これは、左の制限に関してユーザーに明確な指示がないことを示しています。これにはいくつかの方法があり、美学的に奇妙に思えるものもあれば、少し風変わりなものもあります。中間的な解決策を選ぶこともできます。ウォール通知はどうでしょうか。私の意見では、これは面白いビジュアル面を提供できるので、賢い選択だと思います。グラフィックアーティストの読者は、ここで紹介したものよりももっと良い結果が得られるかもしれません。以下のコードを使用します。

inline void CreteBarSlider(int x, int size)
   {
      ObjectCreate(m_id, m_Slider.szBarSlider, OBJ_RECTANGLE_LABEL, 0, 0, 0);
      ObjectSetInteger(m_id, m_Slider.szBarSlider, OBJPROP_XDISTANCE, x);
      ObjectSetInteger(m_id, m_Slider.szBarSlider, OBJPROP_YDISTANCE, m_Slider.posY - 4);
      ObjectSetInteger(m_id, m_Slider.szBarSlider, OBJPROP_XSIZE, size);
      ObjectSetInteger(m_id, m_Slider.szBarSlider, OBJPROP_YSIZE, 9);
      ObjectSetInteger(m_id, m_Slider.szBarSlider, OBJPROP_BGCOLOR, clrLightSkyBlue);
      ObjectSetInteger(m_id, m_Slider.szBarSlider, OBJPROP_BORDER_COLOR, clrBlack);
      ObjectSetInteger(m_id, m_Slider.szBarSlider, OBJPROP_WIDTH, 3);
      ObjectSetInteger(m_id, m_Slider.szBarSlider, OBJPROP_BORDER_TYPE, BORDER_FLAT);
//---
      ObjectCreate(m_id, m_Slider.szBarSliderBlock, OBJ_RECTANGLE_LABEL, 0, 0, 0);
      ObjectSetInteger(m_id, m_Slider.szBarSliderBlock, OBJPROP_XDISTANCE, x);
      ObjectSetInteger(m_id, m_Slider.szBarSliderBlock, OBJPROP_YDISTANCE, m_Slider.posY - 9);
      ObjectSetInteger(m_id, m_Slider.szBarSliderBlock, OBJPROP_YSIZE, 19);
      ObjectSetInteger(m_id, m_Slider.szBarSliderBlock, OBJPROP_BGCOLOR, clrRosyBrown);
      ObjectSetInteger(m_id, m_Slider.szBarSliderBlock, OBJPROP_BORDER_TYPE, BORDER_RAISED);
   }

緑で強調表示された線は、この下限表示を作成するコードを示しています。そう、このためにオブジェクトを使用するのです。お望みであれば、ビットマップを使用すると、より視覚的に魅力的な結果を得ることができます。読者の多くはプログラミングの初心者かと思いますので、コードはシンプルにしたいと思います。こうして、よりアクセスしやすいコードにすることで、すべてがどのように実装されたかを理解しやすくなります。ビットマップやテクスチャパターンを追加するのは簡単で、特にDirectXプログラミングを使用している場合は、かなり面白い結果が得られます。そう、MQL5ではそれが可能です。ですが、それはまた別の機会にしましょう。とりあえず、シンプルで機能的なものにしましょう。その結果が下のアニメーション03です。

アニメーション03

アニメーション03:これで左の限界ができました。

左の限界表示パネルの導入により、ユーザーはリプレイ/シミュレーションに戻れない理由を理解しやすくなりました。しかし、上記のコードでは、左限界バーのサイズをどのように変更するかが指定されていないことにお気づきでしょうか。このサイズを定義するコードを以下に示します。

inline void PositionPinSlider(int p, const int minimal = 0)
   {
      m_Slider.posPinSlider = (p < minimal ? minimal : (p > def_MaxPosSlider ? def_MaxPosSlider : p));
      ObjectSetInteger(m_id, m_Slider.szBtnPin, OBJPROP_XDISTANCE, m_Slider.posPinSlider + def_MinPosXPin);
      ObjectSetInteger(m_id, m_Slider.szBtnLeft, OBJPROP_STATE, m_Slider.posPinSlider != minimal);
      ObjectSetInteger(m_id, m_Slider.szBtnRight, OBJPROP_STATE, m_Slider.posPinSlider < def_MaxPosSlider);
      ObjectSetInteger(m_id, m_Slider.szBarSliderBlock, OBJPROP_XSIZE, minimal + 2);
      ChartRedraw();
   }

指標バーのサイズはminimal変数によって決定されます。リプレイ/モデリングサービスがデータを更新すると、バーもそれに応じて調整されます。さて、次の手順は、この限界がリプレイ/シミュレーションサービスによって正しく更新されるようにすることです。次は、このことについて取り上げます。


リプレイ/シミュレーションサービスと話す

コントロールプロンプトが表示された時点で、ユーザーが時間を遡ることができないようにシステムのコアが設定されているので、どの時点でユーザーが時間を遡ることができなくなったかをコントロール指標に伝えるために、リプレイ/シミュレーションサービスが必要です。この作業は、すでにやったことに比べれば比較的簡単です。主なことは、一時停止時のリプレイ/シミュレーションサービスの現在位置を確認することです。この部分は簡単です。必要な機能を実装する方法を見てみましょう。最初に、コードを少し変更する必要があります。

class C_Controls
{
   private :
//+------------------------------------------------------------------+
      string  m_szBtnPlay;
      long    m_id;
      bool    m_bWait;
      struct st_00
      {
         string  szBtnLeft,
                 szBtnRight,
                 szBtnPin,
                 szBarSlider,
                 szBarSliderBlock;
         int     posPinSlider,
                 posY,
                 Minimal;
      }m_Slider;
//+------------------------------------------------------------------+
      void CreteCtrlSlider(void)
         {
            u_Interprocess Info;
                                
            m_Slider.szBarSlider      = def_NameObjectsSlider + " Bar";
            m_Slider.szBarSliderBlock = def_NameObjectsSlider + " Bar Block";
            m_Slider.szBtnLeft        = def_NameObjectsSlider + " BtnL";
            m_Slider.szBtnRight       = def_NameObjectsSlider + " BtnR";
            m_Slider.szBtnPin         = def_NameObjectsSlider + " BtnP";
            m_Slider.posY = 40;
            CreteBarSlider(82, 436);
            CreateObjectBitMap(52, 25, m_Slider.szBtnLeft, def_ButtonLeft, def_ButtonLeftBlock);
            CreateObjectBitMap(516, 25, m_Slider.szBtnRight, def_ButtonRight, def_ButtonRightBlock);
            CreateObjectBitMap(def_MinPosXPin, m_Slider.posY, m_Slider.szBtnPin, def_ButtonPin);
            ObjectSetInteger(m_id, m_Slider.szBtnPin, OBJPROP_ANCHOR, ANCHOR_CENTER);
            if (GlobalVariableCheck(def_GlobalVariableReplay)) Info.u_Value.df_Value = GlobalVariableGet(def_GlobalVariableReplay); else Info.u_Value.df_Value = 0;
            m_Slider.Minimal = Info.s_Infos.iPosShift;
            PositionPinSlider(Info.s_Infos.iPosShift);
         }
//+------------------------------------------------------------------+
inline void PositionPinSlider(int p, const int minimal = 0)
         {
            m_Slider.posPinSlider = (p < minimal ? minimal : (p > def_MaxPosSlider ? def_MaxPosSlider : p));
            m_Slider.posPinSlider = (p < m_Slider.Minimal ? m_Slider.Minimal : (p > def_MaxPosSlider ? def_MaxPosSlider : p));
            ObjectSetInteger(m_id, m_Slider.szBtnPin, OBJPROP_XDISTANCE, m_Slider.posPinSlider + def_MinPosXPin);
            ObjectSetInteger(m_id, m_Slider.szBtnLeft, OBJPROP_STATE, m_Slider.posPinSlider != minimal);
            ObjectSetInteger(m_id, m_Slider.szBtnLeft, OBJPROP_STATE, m_Slider.posPinSlider != m_Slider.Minimal);
            ObjectSetInteger(m_id, m_Slider.szBtnRight, OBJPROP_STATE, m_Slider.posPinSlider < def_MaxPosSlider);
            ObjectSetInteger(m_id, m_Slider.szBarSliderBlock, OBJPROP_XSIZE, minimal + 2);
            ObjectSetInteger(m_id, m_Slider.szBarSliderBlock, OBJPROP_XSIZE, m_Slider.Minimal + 2);
            ChartRedraw();
         }
//+------------------------------------------------------------------+

限界バーが正しく作成され、設定され、コントロールボタンも正しく設定されるようにするには、このコードで十分です。そのためには、変数を関数呼び出しから移動させ、構造体の中に置く必要がありました。将来、適切な場所でアクセスできるように、コードの特定の場所で初期化されます。なぜこの方法を選んだのでしょうか。コードの他の部分の調整を避けるためにです。リプレイ/シミュレーションサービスが中断されるたびに、CreateCtrlSlider関数が呼び出されます。いくつかのオブジェクトが破棄されても、この関数の呼び出しは発生するので、作成ロジック全体がシンプルになります。

さて、コントロール指標の問題は解決したので、次はリプレイ/シミュレーションサービスのコードに注目し、いくつかの変更を加えましょう。これらの変更の多くは、本質的にはより美的なものですが、より複雑な問題に取り組む前に、システムが円滑に稼動していることを確認することが重要です。


リプレイ/シミュレーションサービスにおける美的問題の解決

私たちが解決しなければならない最初の問題は、単に美的な問題ではなく、技術的な問題です。これは、リプレイ/シミュレーションサービスが、リプレイ開始前に未来の位置に移動するよう要求された場合に発生します。つまり、サービスを開始したばかりのときに、リプレイボタンを押す代わりにチャートを数ポジション進め、その後にリプレイボタンを押すと、チャートが正しく表示されないという問題が発生します。これを解決するには、システムに「偽プレイ」をさせ、スライダーが示す位置に移動させる必要があります。必要なコードの修正を以下に示します。

void AdjustPositionToReplay(const bool bViewBuider)
   {
      u_Interprocess Info;
      MqlRates       Rate[def_BarsDiary];
      int            iPos, nCount;
                                
      Info.u_Value.df_Value = GlobalVariableGet(def_GlobalVariableReplay);
      if ((m_ReplayCount == 0) && (m_Ticks.ModePlot == PRICE_EXCHANGE))
         for (; m_Ticks.Info[m_ReplayCount].volume_real == 0; m_ReplayCount++);
      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);
         CustomTicksDelete(def_SymbolReplay, m_Ticks.Info[iPos].time_msc, LONG_MAX);
         if ((m_dtPrevLoading == 0) && (iPos == 0)) FirstBarNULL(); 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)
      {
      CreateBarInReplay(true);
      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++);
         nCount = CustomRatesUpdate(def_SymbolReplay, m_Ticks.Rate, nCount);
      }
      for (iPos = (iPos > 0 ? iPos - 1 : 0); (m_ReplayCount < iPos) && (!_StopFlag);) CreateBarInReplay(false);
      CustomTicksAdd(def_SymbolReplay, m_Ticks.Info, m_ReplayCount);
      Info.u_Value.df_Value = GlobalVariableGet(def_GlobalVariableReplay);
      Info.s_Infos.isWait = false;
      GlobalVariableSet(def_GlobalVariableReplay, Info.u_Value.df_Value);
   }

このコードの呼び出しは、必要とされる「偽プレイ」を作り出すために非常に重要です。この呼び出しがないと、チャートの作図エラーが表示されます。さらに、気配値表示ウィンドウに欠落したティックを追加するコードを追加し、よりリアルで興味深いリプレイを提供します。消された行を見ればわかるように、コードには他にも変更が加えられています。これらの確認により、同じ移動ポジションにいる場合はシステムに入ることができません。これは、ユーザーが過去に戻ることを認めないという私たちの決定の直接的な結果であり、この機能に関連するコードは削除することができます。

この欠点を修正したので、長い間存在していた美的問題に焦点を当てましょう。ただし、今、リプレイ/シミュレーションサービスのユーザー体験をより楽しいものにすることで、この問題に対処する機会を得たのです。この審美的な問題は、チャート上で以前のバーを表すファイルを選択したときに発生します。リプレイ/シミュレーションサービスを通じてチャートを開くと、初期状態では価格線が表示されません。これはシステムの機能性には影響しませんが、美的観点からは、価格線のないチャートを観察するのは不便です。この部分を修正または対処するには、いくつかの変更が必要です。その最初の変更点を以下に示します。

bool LoopEventOnTime(const bool bViewBuider)
   {
      u_Interprocess Info;
      int iPos, iTest;
                                
      if (!m_Infos.bInit) ViewInfos();
      if (!m_Infos.bInit)
      {
         ChartSetInteger(m_IdReplay, CHART_SHOW_ASK_LINE, m_Ticks.ModePlot == PRICE_FOREX);
         ChartSetInteger(m_IdReplay, CHART_SHOW_BID_LINE, m_Ticks.ModePlot == PRICE_FOREX);
         ChartSetInteger(m_IdReplay, CHART_SHOW_LAST_LINE, m_Ticks.ModePlot == PRICE_EXCHANGE);
         m_Infos.PointsPerTick = SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE);
         m_MountBar.Rate[0].time = 0;
         m_Infos.bInit = true;
         ChartRedraw(m_IdReplay);
      }
      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);
      iPos = 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;
         }
      }                               
      return (m_ReplayCount == m_Ticks.nTicks);
   }                               

コードの消された部分を削除し、強調表示された新しい行を追加します。この呼び出しのコードをここに含めることもできますが、将来的には、このコードは別の関数に移される可能性が高いです。したがって、将来的な移植を容易にするために、必要なコードを別の場所に集めることを好みます。

リプレイ/シミュレーションサービスによってチャートが開かれたときに、価格線がすぐに表示されないという美的問題を解決するには、以下のコードが必要です。

void ViewInfos(void)
   {
      MqlRates Rate[1];
                                
      ChartSetInteger(m_IdReplay, CHART_SHOW_ASK_LINE, m_Ticks.ModePlot == PRICE_FOREX);
      ChartSetInteger(m_IdReplay, CHART_SHOW_BID_LINE, m_Ticks.ModePlot == PRICE_FOREX);
      ChartSetInteger(m_IdReplay, CHART_SHOW_LAST_LINE, m_Ticks.ModePlot == PRICE_EXCHANGE);
      m_Infos.PointsPerTick = SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE);
      m_MountBar.Rate[0].time = 0;
      m_Infos.bInit = true;
      CopyRates(def_SymbolReplay, PERIOD_M1, 0, 1, Rate);
      if ((m_ReplayCount == 0) && (m_Ticks.ModePlot == PRICE_EXCHANGE))

         for (; m_Ticks.Info[m_ReplayCount].volume_real == 0; m_ReplayCount++);
      if (Rate[0].close > 0)
      {
         if (m_Ticks.ModePlot == PRICE_EXCHANGE) m_Infos.tick[0].last = Rate[0].close; else
         {
            m_Infos.tick[0].bid = Rate[0].close;
            m_Infos.tick[0].ask = Rate[0].close + (Rate[0].spread * m_Infos.PointsPerTick);
         }                                       
         m_Infos.tick[0].time = Rate[0].time;
         m_Infos.tick[0].time_msc = Rate[0].time * 1000;
      }else
         m_Infos.tick[0] = m_Ticks.Info[m_ReplayCount];
      CustomTicksAdd(def_SymbolReplay, m_Infos.tick);
      ChartRedraw(m_IdReplay);
   }

これらのコード行は、前の関数では消されていました。しかし、必要な追加ラインには注意を払う必要があります。リプレイ/シミュレーションサービスによってチャートに表示された最後のバーを、指標を操作するための一般的な機能を使用して特定します。バーを捕捉できた場合、つまり終値がゼロより大きい場合、使用する構築モードに応じて特別なティックを設定します。終値がゼロの場合は、読み込みまたはシミュレーションされたティックのリストから最初の有効なティックを使用します。有効なティックを検索する関数は、まさに前述の2行にあります。Bidモードでは、最初のティックがすでに有効であるため、この関数はLastプロットモードで作業する場合に特に有用です。最終的に、この特別に作成されたティックが気配値表示ウィンドウに表示され、サービスがMetaTrader 5プラットフォームにチャートを開くように命令するとすぐに、チャートに価格線が表示されます。

リプレイ/モデリングに問題があり、やや信頼性に欠けるものの、提示されるデータセットより前のバーを指し示すことができないため、さらに別の修正が必要です。この場合、最初のバーの構造を切断することができます。この問題を最終的に解決するには、後で紹介するセット全体の前にあるバーを指定する必要があります。このコード変更により、1分から1日、さらには1週間まで、さまざまな時間間隔のチャートでシステムが正しく機能するようになります。まあ、1ヶ月は多すぎるかもしれません。

inline void FirstBarNULL(void)
   {
      MqlRates rate[1];
      int c0 = 0;
                                
      for(; (m_Ticks.ModePlot == PRICE_EXCHANGE) && (m_Ticks.Info[c0].volume_real == 0); c0++);
      rate[0].close = (m_Ticks.ModePlot == PRICE_EXCHANGE ? m_Ticks.Info[c0].last : m_Ticks.Info[c0].bid);
      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 = macroRemoveSec(m_Ticks.Info[c0].time) - 86400;
      CustomRatesUpdate(def_SymbolReplay, rate);
      m_ReplayCount = 0;
   }

最初の手順は、有効なティックを見つけることです。特にチャートシステムが最終価格を使用する場合です。これが完了したら、ティックシリーズの最初の有効価格を使用して前のバーを作成します。これはリプレイまたはシミュレーションで使用されます。ここで重要なのは、1日分の値を分単位で差し引くことによって補正された、時間における位置の表示です。これにより、前のバーがチャートに正しく表示され、日足チャートでも完全に見えるように配置されます。このシステムは、FOREX市場のデータと株式市場の両方に有効です。


結論

提供された添付ファイルを使用して、リプレイ/シミュレーションサービスの現在の実装をテストできます。基本的なシステムはできていますが、まだ使用していない機能もあるため、より効果的な訓練モードに適応させるためには、さらなる変更や調整が必要になります。この時点で、リプレイ/シミュレーションシステムは完成したと考えています。次回の記事では、このシステムをさらに改良し、新たなステージを切り開く方法について考えてみたいと思います。

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

添付されたファイル |
Files_-_BOLSA.zip (1358.24 KB)
Files_-_FOREX.zip (3743.96 KB)
Files_-_FUTUROS.zip (11397.51 KB)
リプレイシステムの開発(第26回):エキスパートアドバイザープロジェクト-C_Terminalクラス リプレイシステムの開発(第26回):エキスパートアドバイザープロジェクト-C_Terminalクラス
これで、リプレイ/シミュレーションシステムで使用するEAの作成を開始できます。ただし、行き当たりばったりの解決策ではなく、何か改善策が必要です。にもかかわらず、最初の複雑さに怯んではなりません。どこかで始めることが重要で、そうでなければ、その課題を克服しようともせずに、その難しさを反芻してしまうことになります。それこそがプログラミングの醍醐味であり、学習、テスト、徹底的な研究を通じて障害を克服することです。
リプレイシステムの開発 - 市場シミュレーション(第24回):FOREX (V) リプレイシステムの開発 - 市場シミュレーション(第24回):FOREX (V)
本日は、Last価格に基づくシミュレーションを妨げていた制限を取り除き、このタイプのシミュレーションに特化した新しいエントリポイントをご紹介します。操作の仕組みはすべて、FOREX市場の原理に基づいています。この手順の主な違いは、BidシミュレーションとLastシミュレーションの分離です。ただし、時間をランダム化し、C_Replayクラスに適合するように調整するために使用された方法は、両方のシミュレーションで同じままであることに注意することが重要です。これは良いことです。特にティック間の処理時間に関して、一方のモードを変更すれば、もう一方のモードも自動的に改善されるからです。
リプレイシステムの開発(第27回):エキスパートアドバイザープロジェクト-C_Mouseクラス(I) リプレイシステムの開発(第27回):エキスパートアドバイザープロジェクト-C_Mouseクラス(I)
この記事では、C_Mouseクラスを実装します。このクラスは、最高水準でプログラミングする能力を提供します。しかし、高水準や低水準のプログラミング言語について語ることは、コードに卑猥な言葉や専門用語を含めることではありません。逆です。高水準プログラミング、低水準プログラミングというのは、他のプログラマーが理解しやすいか、しにくいかという意味です。
リプレイシステムの開発 - 市場シミュレーション(第23回)FOREX (IV) リプレイシステムの開発 - 市場シミュレーション(第23回)FOREX (IV)
これで、ティックをバーに変換したのと同じ時点で作成がおこなわれます。こうすることで、変換プロセス中に問題が発生した場合、すぐにエラーに気づくことができます。これは、早送り中にチャート上に1分足を配置するコードと同じコードが、通常のパフォーマンス中に足を配置する位置決めシステムにも使用されるためです。言い換えれば、このタスクを担当するコードは他の場所には複製されません。このようにして、メンテナンスと改善の両方においてはるかに優れたシステムが得られます。