English Русский Español Deutsch Português
preview
リプレイシステムの開発(第35回):調整(I)

リプレイシステムの開発(第35回):調整(I)

MetaTrader 5 | 16 5月 2024, 16:46
65 0
Daniel Jose
Daniel Jose

はじめに

前回の「リプレイシステムの開発(第34回):発注システム(III)」稿で 、このシステムにはいくつかの奇妙で謎めいたバグがあると述べました。これらのバグや障害は、システム内の何らかの相互作用によって引き起こされました。このような失敗をなくすために原因を突き止めようと試みましたが、すべて失敗に終わりました。例えば、C/C++でポインタや再帰を使用すると、プログラムがクラッシュしてしまいます。最初のステップは、これらのメカニズムを検証することです。しかしMQL5では、これはC/C++と同じようには起こりません。いくつかの小さな編集を施した後、欠点の1つを解決することができました。この解決策はエレガントには見えませんが、失敗の1つを完全に消し去ることができました。

ただし、システム操作に影響を及ぼすエラーを完全に排除するためには、まだもう少し根本的なコードの変更が必要です。このシステムには以前からある種の相互作用がなかったため、これらはおそらく長い間存在したでしょう。こうした相互作用が起こり始めると、こうしたエラーが目立つようになりました。

既存の障害はシステムの動作に悪影響を及ぼすものではありませんが、本当に正しい動作を可能にするものではありません。これらすべてによって、プログラムの体験が非常に不快になり、受け入れがたくなります。これらの欠点のうち、最初のものは非常に簡単に修正できるので、まずそれから始めましょう。


サービスビジー表示の解消

これらの欠点のうち、最初の欠点は最も修正しやすいものです。注文中にCTRLSHIFTを押すと、サービスがビジー状態であることが表示されるということです。これは、システムが正常に機能しているにもかかわらず、サービスが他のタスクを実行していることを示しています。この仕事はタイムシフトです。つまり、リプレイ/シミュレーターチャートで何らかの分析をおこなう前に、バーが作成されています。このエラーは極端に有害なものではありませんが、リプレイ/シミュレーターの使用経験をかなり不愉快なものにしています。

サービスビジーを表示しない方がいいと考える方もいらっしゃるかもしれません。時間をスクロールするときのバー作成の表示を使用するのは久しぶりなので。ただし、それでは問題は解決しません。これでは問題が背景に追いやられ、目立たなくなるだけですが、解決策は実際には非常にシンプルで効果的です。さらに、将来、バーの作成を見ることができる可視化システムに戻ることができるように、いくつかのものを保存することができます。以前は可能だったので、リプレイ/シミュレーションサービスを無効にしました。この問題を解決するためには、いくつかの変更を加える必要があります。次のコードから始めましょう。

int OnInit()
{
#define macro_INIT_FAILED { ChartIndicatorDelete(m_id, 0, def_ShortName); return INIT_FAILED; }
        u_Interprocess Info;
        ulong ul = 1;

        m_id = ChartID();
        ul <<= def_BitShift;
        IndicatorSetString(INDICATOR_SHORTNAME, def_ShortName);
        if ((_Symbol != def_SymbolReplay) || (!GlobalVariableCheck(def_GlobalVariableIdGraphics))) macro_INIT_FAILED;
        Info.u_Value.df_Value = GlobalVariableGet(def_GlobalVariableIdGraphics);
        if (Info.u_Value.IdGraphic != m_id) macro_INIT_FAILED;
        if ((Info.u_Value.IdGraphic >> def_BitShift) == 1) macro_INIT_FAILED;
        IndicatorSetString(INDICATOR_SHORTNAME, def_ShortName + "Device");
        Info.u_Value.IdGraphic |= ul;
        GlobalVariableSet(def_GlobalVariableIdGraphics, Info.u_Value.df_Value); 
        if (GlobalVariableCheck(def_GlobalVariableReplay)) Info.u_Value.df_Value = GlobalVariableGet(def_GlobalVariableReplay); else Info.u_Value.df_Value = 0;
        EventChartCustom(m_id, C_Controls::ev_WaitOff, 0, Info.u_Value.df_Value, "");
        EventChartCustom(m_id, C_Controls::ev_WaitOff, 1, Info.u_Value.df_Value, "");
        Control.Init(Info.s_Infos.isPlay);
        
        return INIT_SUCCEEDED;
        
#undef macro_INIT_FAILED
}

最初の修正は、指標ファイルに対しておこないます。正確な修正箇所は上記の通りです。消された部分は削除され、新しいコードに置き換えられました。本当に必要な変更はこれだけなので、よくご覧ください。これは、EventChartCustom関数に渡す必要があるlparamパラメータの値に関するものです。奇妙なことに、このような単純な変化がすでに実を結んでいます。

同じファイルの別の関数で、よく似たことをします。

int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
{
  static bool bWait = false;
  u_Interprocess Info;
        
  Info.u_Value.df_Value = GlobalVariableGet(def_GlobalVariableReplay);
  if (!bWait)
  {
     if (Info.s_Infos.isWait)
     {
        EventChartCustom(m_id, C_Controls::ev_WaitOn, 0, 0, "");
        EventChartCustom(m_id, C_Controls::ev_WaitOn, 1, 0, "");
        bWait = true;
     }
  }else if (!Info.s_Infos.isWait)
  {
     EventChartCustom(m_id, C_Controls::ev_WaitOff, 0, Info.u_Value.df_Value, "");
     EventChartCustom(m_id, C_Controls::ev_WaitOff, 1, Info.u_Value.df_Value, "");
     bWait = false;
  }
        
  return rates_total;
}

これは上記のOnInitコードと似ています。消された部分は新しいコードに置き換えられています。唯一の違いは、やはりlparamパラメータの値です。 というわけで、今回もEventChartCustom関数に関する修正です。

なぜこのようなことが起こるのか、そして本当に効果があるのでしょうか。実際、ここでこのような変更を加えても問題は解決しません。この問題を本当に解決するには、C_Controlクラスに入って、メッセージ処理関数に小さな確認を追加する必要があります。以下の新しいコードをご確認ください。

void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
{
   u_Interprocess Info;
   static int six = -1, sps;
   int x, y, px1, px2;
                                
   switch (id)
   {
      case (CHARTEVENT_CUSTOM + C_Controls::ev_WaitOn):
         if (lparam == 0) break;
         m_bWait = true;
         CreateBtnPlayPause(true);
         break;
      case (CHARTEVENT_CUSTOM + C_Controls::ev_WaitOff):
         if (lparam == 0) break;
         m_bWait = false;
         Info.u_Value.df_Value = dparam;
         CreateBtnPlayPause(Info.s_Infos.isPlay);
         break;
//... The rest of the code ...
  }
}

ここでは、問題を理解するために本当に必要な断片だけを示します。両方のカスタムイベントに小さな確認が加えられていることにご注意ください。ユーザーインタラクションがプログラム内のカスタムイベントを発生させることで、これらの確認はカスタムイベントの発生のみを許可するようになります。これらのイベントは、コントロール指標のコード内で正確にトリガーされます。それ以外の理由であれば、ハンドラに届く値は期待したものとは異なります。この場合、lparamパラメータの値が考慮されます。このようなことは、MetaTrader 5プラットフォームでかなり興味深いものの使い方を説明するときに、将来また登場することになるでしょう。この件については、この誤りの理由を詳しく説明することにしましょう。しかし、今のところ、その理由はまだ奇妙です。

でも、待ってください。それでは意味がありません。実際、私も最初はその理由がわかりませんでしたが、どういうわけか、開発中の発注システムを使用するためにSHIFTCTRLを押すと、プラットフォームは2つ前のイベントをトリガーするイベントを発生させます。具体的にはCHARTEVENT_CUSTOM + C_Controls::ev_WaitOnです。これにより、サービスビジー画像がチャートに表示されます。SHIFTまたはCTRLを離すとすぐに CHARTEVENT_CUSTOM + C_Controls::ev_WaitOffイベントが発生し、これですべてが元に戻ります。

この問題が解決すれば、次の問題に移ることができます。しかし、2つ目の問題を解決するのはかなり難しいです。混乱を避けるため、別のトピックで検討する必要があります。では、実際の状況を理解するために次のセクションに進みましょう。


C_Mouseクラスをより深く再利用する

次の欠陥は、分析をするときに起こります。このようなことが起こると、ユーザーが実際にそれに気づかないまま、システムがポジション指標を動かしてしまう可能性があります。この問題を解決するには、もっと深いコード編集が必要です。ユーザーが実際にスライダーオブジェクトを動かそうとしているかどうかを確認する確認を実行すれば十分だと思うかもしれません。このスライダーオブジェクトは、コントロール指標の不可欠な部分であり、リプレイやシミュレーションがどの段階にあるのかをユーザーに知らせる役割を果たします。しかし、単純な確認だけではこの欠陥を解決することはできません。単純な検証を超えなければなりません。実際、最初のうちはもっと複雑なことをすることになりますが、その方がより決定的な方法で問題を解決できるし、後で他のこともできるようになります。

EAで元々使用されているC_Mouseクラスをコントロール指標でも使用しますが、これは見かけほど簡単ではありません。コードのいくつかの箇所を大幅に変更する必要があります。これは、C_Mouseクラスがコントロール指標とEAの両方で完全に調和して動作するようにするためです。すべてが競合なく機能する必要があります。そうすることで、また別のことができるようになります。

まず、C_Mouseクラスとその変更点を見てみましょう。変更点を説明するために、このクラスのコードに実際に何が追加されたかを見てみる必要があります。まずはprivateグローバル変数についてです。その変更点を以下に示します。

struct st_Mem
{
   bool     CrossHair,
            IsFull;
   datetime dt;
}m_Mem;

追加されたこの変数は、クラス内での確認に使用されます。その優先順位は、クラスがどのように実行されるかを正確に定義することです。変数を宣言したら、適切な方法でそれを開始する必要があります。これについては、クラスのコンストラクタを使用します。ここで最初の複雑な問題が発生します。システムをFULLモードで使用するかどうかをどうやって知るかということです。些細でありながら重要なのは、FULLモードを使用する場合、実際にはグラフィカルオブジェクトを使用するということです。これらのオブジェクトはEAで使用されます。EAでそのクラスを使用しない場合、そのようなオブジェクトは作成されるべきではありません。しかし、EAを使用しているのか、それとも他の何かを使用しているのか、どのようにクラスに伝えることができるのでしょうか。方法はいくつかあります。必要なのは、クラスのコンストラクタを通しておこなわれるものです。こうすることで、その後の多くの追加確認や、クラスの誤った使用に伴うリスクを回避することができます。そこで、コンストラクタを変更します。以下のようになります。

C_Mouse(C_Terminal *arg, color corH = clrNONE, color corP = clrNONE, color corN = clrNONE)
{
   Terminal = arg;
   if (CheckPointer(Terminal) == POINTER_INVALID) SetUserError(C_Terminal::ERR_PointerInvalid);
   if (_LastError != ERR_SUCCESS) return;
   m_Mem.CrossHair = (bool)ChartGetInteger(def_InfoTerminal.ID, CHART_CROSSHAIR_TOOL);
   ChartSetInteger(def_InfoTerminal.ID, CHART_EVENT_MOUSE_MOVE, true);
   ChartSetInteger(def_InfoTerminal.ID, CHART_CROSSHAIR_TOOL, false);
   ZeroMemory(m_Info);
   m_Info.corLineH  = corH;
   m_Info.corTrendP = corP;
   m_Info.corTrendN = corN;
   m_Info.Study = eStudyNull;
   if (m_Mem.IsFull = (corP != clrNONE) && (corH != clrNONE) && (corN != clrNONE))
      def_AcessTerminal.CreateObjectGraphics(def_NameObjectLineH, OBJ_HLINE, m_Info.corLineH);
}

ここで何が起きているかに注目してください。コンストラクタの宣言で、色データをデフォルト値で定義しているため、コンストラクタをオーバーロードすることができます。このオーバーロードは、同じC_Mouseクラスを使用するために他のコンストラクタコードを作る必要がないことを意味します。これは、コントロール指標での使用か、EAでの使用かを区別するために必要です。もしそのようなオーバーロードが存在しなければ、FULLモードで使用するかどうかをクラスに伝えるために、別のコンストラクタを作らなければなりません。コンストラクタコードのこの時点で、変数の値を定義します。同時に、その価値を初めて使用することになります。FULLモードであれば、チャート上で使用する価格ラインを作成します。そうでなければ、この行は作成されません。

デストラクタのコードも少し変更されました。次がコードです。

~C_Mouse()
{
   if (CheckPointer(Terminal) == POINTER_INVALID) return;
   ChartSetInteger(def_InfoTerminal.ID, CHART_EVENT_OBJECT_DELETE, 0, false);
   ChartSetInteger(def_InfoTerminal.ID, CHART_EVENT_MOUSE_MOVE, false);
   ChartSetInteger(def_InfoTerminal.ID, CHART_CROSSHAIR_TOOL, m_Mem.CrossHair);
   ObjectsDeleteAll(def_InfoTerminal.ID, def_MousePrefixName);
}

C_Terminalクラスへのポインタが正しく初期化されなかったためにクラス構築に失敗した場合、デストラクタでそのポインタを使用することはできません。無効なポインタの使用を避けるため、この確認はクラスのデストラクタでおこないます。これは初期コーディングの中で最も簡単な部分でした。では、古いクラスのコードをざっと見て、どのような変更が必要かを確認しましょう。

void CreateStudy(void)
{
   if (m_Mem.IsFull)
   {
      def_AcessTerminal.CreateObjectGraphics(def_NameObjectLineV, OBJ_VLINE, m_Info.corLineH);
      def_AcessTerminal.CreateObjectGraphics(def_NameObjectLineT, OBJ_TREND, m_Info.corLineH);
      ObjectSetInteger(def_InfoTerminal.ID, def_NameObjectLineT, OBJPROP_WIDTH, 2);
      CreateObjectInfo(0, 0, def_NameObjectStudy);
   }
   m_Info.Study = eStudyCreate;
}
//+------------------------------------------------------------------+
void ExecuteStudy(const double memPrice)
{
   double v1 = GetInfoMouse().Position.Price - memPrice;
   int w, h;
                                
   if (!CheckClick(eClickLeft))
   {
      m_Info.Study = eStudyNull;
      ChartSetInteger(def_InfoTerminal.ID, CHART_MOUSE_SCROLL, true);
      if (m_Mem.IsFull) ObjectsDeleteAll(def_InfoTerminal.ID, def_MousePrefixName + "T");
   }else if (m_Mem.IsFull)
   {
      string sz1 = StringFormat(" %." + (string)def_InfoTerminal.nDigits + "f [ %d ] %02.02f%% ",
      MathAbs(v1), Bars(def_InfoTerminal.szSymbol, PERIOD_CURRENT, m_Mem.dt, GetInfoMouse().Position.dt) - 1, MathAbs((v1/ memPrice) * 100.0)));
      GetDimensionText(sz1, w, h);
      ObjectSetString(def_InfoTerminal.ID, def_NameObjectStudy, OBJPROP_TEXT, sz1);                                                                                                                           
      ObjectSetInteger(def_InfoTerminal.ID, def_NameObjectStudy, OBJPROP_BGCOLOR, (v1 < 0 ? m_Info.corTrendN : m_Info.corTrendP));
      ObjectSetInteger(def_InfoTerminal.ID, def_NameObjectStudy, OBJPROP_XSIZE, w);
      ObjectSetInteger(def_InfoTerminal.ID, def_NameObjectStudy, OBJPROP_YSIZE, h);
      ObjectSetInteger(def_InfoTerminal.ID, def_NameObjectStudy, OBJPROP_XDISTANCE, GetInfoMouse().Position.X - w);
      ObjectSetInteger(def_InfoTerminal.ID, def_NameObjectStudy, OBJPROP_YDISTANCE, GetInfoMouse().Position.Y - (v1 < 0 ? 1 : h));                            
      ObjectMove(def_InfoTerminal.ID, def_NameObjectLineT, 1, GetInfoMouse().Position.dt, GetInfoMouse().Position.Price);
      ObjectSetInteger(def_InfoTerminal.ID, def_NameObjectLineT, OBJPROP_COLOR, (memPrice > GetInfoMouse().Position.Price ? m_Info.corTrendN : m_Info.corTrendP));
   }
   m_Info.Data.ButtonStatus = eKeyNull;
}

この2つのメソッドを非常にシンプルで簡単なものに改良しました。オブジェクトの作成と操作に、ちょっとした確認が加わります。このような操作は、FULLモードでクラスを使用する場合にのみおこなわれます。しかし、C_Mouseクラスには別のメソッドもあります。このメソッドを通じてクラスと対話することになるため、これは非常に重要です。そのコードは以下にあります。

virtual void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
{
   int w = 0;
   static double memPrice = 0;
                                
   switch (id)
   {
      case (CHARTEVENT_CUSTOM + ev_HideMouse):
         if (m_Mem.IsFull) ObjectSetInteger(def_InfoTerminal.ID, def_NameObjectLineH, OBJPROP_COLOR, clrNONE);
         break;
      case (CHARTEVENT_CUSTOM + ev_ShowMouse):
         if (m_Mem.IsFull) ObjectSetInteger(def_InfoTerminal.ID, def_NameObjectLineH, OBJPROP_COLOR, m_Info.corLineH);
         break;
      case CHARTEVENT_MOUSE_MOVE:
         ChartXYToTimePrice(def_InfoTerminal.ID, m_Info.Data.Position.X = (int)lparam, m_Info.Data.Position.Y = (int)dparam, w, m_Info.Data.Position.dt, m_Info.Data.Position.Price);
         if (m_Mem.IsFull) ObjectMove(def_InfoTerminal.ID, def_NameObjectLineH, 0, 0, m_Info.Data.Position.Price = def_AcessTerminal.AdjustPrice(m_Info.Data.Position.Price));
         m_Info.Data.Position.dt = def_AcessTerminal.AdjustTime(m_Info.Data.Position.dt);
         ChartTimePriceToXY(def_InfoTerminal.ID, w, m_Info.Data.Position.dt, m_Info.Data.Position.Price, m_Info.Data.Position.X, m_Info.Data.Position.Y);
         if ((m_Info.Study != eStudyNull) && (m_Mem.IsFull)) ObjectMove(def_InfoTerminal.ID, def_NameObjectLineV, 0, m_Info.Data.Position.dt, 0);
         m_Info.Data.ButtonStatus = (uint) sparam;
         if (CheckClick(eClickMiddle))
            if ((!m_Mem.IsFull) || ((color)ObjectGetInteger(def_InfoTerminal.ID, def_NameObjectLineH, OBJPROP_COLOR) != clrNONE)) CreateStudy();
               if (CheckClick(eClickLeft) && (m_Info.Study == eStudyCreate))
               {
                  ChartSetInteger(def_InfoTerminal.ID, CHART_MOUSE_SCROLL, false);
                  if (m_Mem.IsFull) ObjectMove(def_InfoTerminal.ID, def_NameObjectLineT, 0, m_Mem.dt = GetInfoMouse().Position.dt, memPrice = GetInfoMouse().Position.Price);
                  m_Info.Study = eStudyExecute;
               }
         if (m_Info.Study == eStudyExecute) ExecuteStudy(memPrice);
         m_Info.Data.ExecStudy = m_Info.Study == eStudyExecute;
         break;
      case CHARTEVENT_OBJECT_DELETE:
         if ((m_Mem.IsFull) && (sparam == def_NameObjectLineH)) def_AcessTerminal.CreateObjectGraphics(def_NameObjectLineH, OBJ_HLINE, m_Info.corLineH);
         break;
   }
}

この方法もまた、以前の方法と同様、改良が加えられています。実際には、チャート上のオブジェクトを操作する必要があるかどうかの確認を加えただけです。場合によってはオブジェクトがチャート上に存在しない場合があり、存在する場合には同じ方法で操作する必要があるため、このような確認は必要ないと思われるかもしれません。これは、MetaTrader 5プラットフォームが重複オブジェクトを作成しないためです。しかし、次のことを考えてみてください。オブジェクトに対してこのような操作をおこなうとき、ただ一度だけおこなうのではありません。EAとコントロール指標が同じクラスを使用しており、両者が同時に使用していることを知らないため、MetaTrader 5プラットフォームはオブジェクトハンドラを2回呼び出さざるを得ません。これはさほど問題ではありませんが、システムがマーケットリプレイやシミュレーションのために使用されていることを忘れてはなりません。このプロジェクトが始まって以来、私たちの主な目標は、シミュレーションや再生を実際の市場で起こっていることにできるだけ近づけることだと何度も繰り返してきました。ただバーを作るのではなく、それを作るのにかかる時間が重要なのです。1回の呼び出しで済むのにMetaTrader 5がオブジェクトに2回アクセスすれば、どのようなプロセッサやハードウェアを使用していても、貴重な時間が浪費されます。このような呼び出しのためにコードが重複し、ミリ秒、あるいはナノ秒のロスが出始めると、やがて秒単位のロスが出たり、バーの作成が大幅に遅れたりするようになります。ですから、MetaTrader 5がチャート上にオブジェクトを表示するために余分な作業をする必要がないようにすべきです。

C_Mouseクラスのコードの修正は完了したので、EAのコードについては心配する必要はありません。ディレクトリ構造は変わりましたが、これはインクルードのコードにしか影響しないので、こだわる必要はないと思います。EAのコードに実装上の変更がないのは、ささやかな救いですが、これは一時的なものです。リプレイ/シミュレーションをコントロールするために使用されるコントロール指標のコードには、詳細かつ冷静な説明に値する、より深い変更が加えられるからです。では、次の話題に移りましょう。


コントロール指標の変更

コントロール指標にこの変更を加えるには、まずこの指標が使用するクラスから着手しましょう。C_Controlクラスのことです。最初のコード修正を見てみましょう。

#include "..\Auxiliar\C_Terminal.mqh"
#include "..\Auxiliar\C_Mouse.mqh"
//+------------------------------------------------------------------+
#define def_AcessTerminal (*Terminal)
#define def_InfoTerminal def_AcessTerminal.GetInfoTerminal()
//+------------------------------------------------------------------+
class C_Controls : protected C_Mouse
{
        protected:
                enum EventCustom {ev_WaitOn, ev_WaitOff};
        private :
//+------------------------------------------------------------------+
                string  m_szBtnPlay;
                bool    m_bWait;
                struct st_00
                {
                        string  szBtnLeft,
                                szBtnRight,
                                szBtnPin,
                                szBarSlider,
                                szBarSliderBlock;
                        int     posPinSlider,
                                posY,
                                Minimal;
                }m_Slider;
                C_Terminal *Terminal;

このコードには、C_TerminalクラスとC_Mouseクラスの2つのファイルをインクルードする呼び出しがあることがすぐにわかります。しかし、なぜC_Mouseクラスを継承したC_Studyクラスを使用しないのでしょうか。コントロール指標に分析をさせたり、管理させたりしないからです。これは、少なくとも実装の現段階では、EAの仕事であり、コントロール指標の仕事ではありません。そこで、C_Mouseクラスを使用することにします。C_ControlクラスはC_Mouseクラスを継承していることに注意してください。この継承にもかかわらず、コントロール指標は、少なくとも直接的にはその恩恵を受けることはありません。このため、このような継承はprivateにすることもできますが、私は通常、この種の継承はprotectedとして扱います。これらの宣言の最後の注意点は、C_Terminalクラスが使用するポインタです。

:C_Controlクラスのソースコードには、チャートウィンドウのインデックスにアクセスするためのprivateグローバル変数がありました。このインデックスはこのバージョンではもう存在しません。これまではC_Controlクラスの内部インデックスを使用しておこなっていた作業を、C_Terminalクラスを使用しておこなうからです。

この詳細のため、インデックスが参照されていた箇所はすべて削除されています。本当に必要なところでは、参照を、C_Terminalクラスを通してインデックスにアクセスできる定義に置き換えました。クラスコンストラクタのコードを見てみましょう。以下をご覧ください。

C_Controls(C_Terminal *arg)
          :C_Mouse(arg),
           m_bWait(false)
   {
      if (CheckPointer(Terminal = arg) == POINTER_INVALID) SetUserError(C_Terminal::ERR_PointerInvalid);
      m_szBtnPlay             = NULL;
      m_Slider.szBarSlider    = NULL;
      m_Slider.szBtnPin       = NULL;
      m_Slider.szBtnLeft      = NULL;
      m_Slider.szBtnRight     = NULL;
   }

コンストラクタは、C_Terminalクラスへのポインタを1つのパラメータとして受け取ります。このパラメータはC_Mouseクラスに渡されます。ただし、C_Mouseは FULLモードでは使用されません.。その場合、C_Mouseクラスのオブジェクトは生成されません。これは、コードの再利用をサポートするものでしかないからです。いずれにせよ、ここではこのポインタが有効かどうかを確認します。これは、無効または未知のメモリ位置を指すものを使用しないために重要です。また、クラスのデストラクタもありますが、これも少し修正しました。

~C_Controls()
{
   if (CheckPointer(Terminal) == POINTER_INVALID) return;
   ChartSetInteger(def_InfoTerminal.ID, CHART_EVENT_OBJECT_DELETE, false);
   ObjectsDeleteAll(def_InfoTerminal.ID, def_PrefixObjectName);
}

この確認は、コンストラクタが失敗してもデストラクタがそれを知らずに無効なポインタを使おうとするのを防ぎます。この確認をおこなうことで、デストラクタもコンストラクタが失敗した理由を知っていることを保証します。デストラクタが呼ばれるのは、単にC_Controlsクラスの使用を終了するためかもしれないからです。現在システムを使用しているので、いくつかの要素はこのクラスの外で初期化されます。したがって、ソースコードに追加の変更を加えることができます。

void Init(const bool state)
{
   if (m_szBtnPlay != NULL) return;
   ChartSetInteger(def_InfoTerminal.ID, CHART_EVENT_MOUSE_MOVE, true);
   ChartSetInteger(def_InfoTerminal.ID, CHART_EVENT_OBJECT_DELETE, true);
   CreateBtnPlayPause(state);
   GlobalVariableTemp(def_GlobalVariableReplay);
   if (!state) CreteCtrlSlider();
   ChartRedraw();
}

上記の行は、他の箇所ですでに実装されているため、削除しました。これは、C_Mouseクラスがマウスの動きをトリガーし、C_TerminalクラスがMetaTrader 5プラットフォームにオブジェクトがチャートから削除されたことを通知するためです。このような理由から、これらの行がコードに残っていると、状況によっては奇妙な動作が見られる可能性があるため、削除されました。決してコードを重複させてはいけないことを忘れないでください。コードが正しく実行されなくなったり、長期にわたる保守が非常に困難になったりするため、コードの重複はエラーとみなされます。

次に変更する必要があるのは、クラスメッセージハンドラです。以下をご覧ください。

void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
{
   u_Interprocess Info;
   static int six = -1, sps;
   int x, y, px1, px2;
                                
   C_Mouse::DispatchMessage(id, lparam, dparam, sparam);
   switch (id)
   {
      case (CHARTEVENT_CUSTOM + C_Controls::ev_WaitOn):
         if (lparam == 0) break;
         m_bWait = true;
         CreateBtnPlayPause(true);
         break;
      case (CHARTEVENT_CUSTOM + C_Controls::ev_WaitOff):
         if (lparam == 0) break;
         m_bWait = false;
         Info.u_Value.df_Value = dparam;
         CreateBtnPlayPause(Info.s_Infos.isPlay);
         break;
      case CHARTEVENT_OBJECT_DELETE:
         if (StringSubstr(sparam, 0, StringLen(def_PrefixObjectName)) == def_PrefixObjectName)
         {
            if (StringSubstr(sparam, 0, StringLen(def_NameObjectsSlider)) == def_NameObjectsSlider)
            {
               RemoveCtrlSlider();
               CreteCtrlSlider();
            }else
            {
               Info.u_Value.df_Value = GlobalVariableGet(def_GlobalVariableReplay);
               CreateBtnPlayPause(Info.s_Infos.isPlay);
            }
            ChartRedraw();
         }
         break;
      case CHARTEVENT_OBJECT_CLICK:
         if (m_bWait) break;
         if (sparam == m_szBtnPlay)
         {
            Info.s_Infos.isPlay = (bool) ObjectGetInteger(def_InfoTerminal.ID, m_szBtnPlay, OBJPROP_STATE);
            if (!Info.s_Infos.isPlay) CreteCtrlSlider(); else
            {
               RemoveCtrlSlider();
               m_Slider.szBtnPin = NULL;
            }
            Info.s_Infos.iPosShift = (ushort) m_Slider.posPinSlider;
            GlobalVariableSet(def_GlobalVariableReplay, Info.u_Value.df_Value);
            ChartRedraw();
         }else   if (sparam == m_Slider.szBtnLeft) PositionPinSlider(m_Slider.posPinSlider - 1);
         else if (sparam == m_Slider.szBtnRight) PositionPinSlider(m_Slider.posPinSlider + 1);
         break;
      case CHARTEVENT_MOUSE_MOVE:
         if (GetInfoMouse().ExecStudy) return;
         if ((CheckClick(C_Mouse::eClickLeft)) && (m_Slider.szBtnPin != NULL))
         {
            x = GetInfoMouse().Position.X;
            y = GetInfoMouse().Position.Y;
            px1 = m_Slider.posPinSlider + def_PosXObjects + 86;
            px2 = m_Slider.posPinSlider + def_PosXObjects + 114;
            if ((y >= (m_Slider.posY - 14)) && (y <= (m_Slider.posY + 14)) && (x >= px1) && (x <= px2) && (six == -1))
            {
               six = x;
               sps = m_Slider.posPinSlider;
               ChartSetInteger(def_InfoTerminal.ID, CHART_MOUSE_SCROLL, false);
            }
            if (six > 0) PositionPinSlider(sps + x - six);
         }else if (six > 0)
         {
            six = -1;
            ChartSetInteger(def_InfoTerminal.ID, CHART_MOUSE_SCROLL, true);
         }
         break;
   }
}

このコードは、以前と比べるといくつかの変更が加えられています。ご覧のものが最終バージョンです。そうすれば、ここで何が起こっているのかを説明しやすくなるでしょう。コントロール指標のためには、マウスの動作を知る必要があります。これにはCHARTEVENT_MOUSE_MOVEイベントだけでは不十分です。C_Mouseクラスに仕事をさせる必要があります。EAは指標と同様にC_Mouseクラスを使用していますが、マウスが何をしているかについての情報は交換しません。

このような交換を実装することを考えました。可能ですが、そのためにはいくつかの問題があります。

  1. MetaTrader 5プラットフォームの機能を使用してEAとコントロール指標間でマウスデータを交換した場合、リプレイ/シミュレーションシステムの外部でEAを使用する際に問題が発生する可能性があります。また、コントロール指標にマウスの動きを理解させるのも難しいかもしれません。
  2. また、DLLを介して共有メモリを使用することも考えられますが、この場合、プラットフォームやリプレイ/シミュレーションサービスへの依存関係が生じます。率直に言って、現時点ではそのような依存関係を作ることには興味がありません。したがって、MetaTrader 5がC_Mouseクラスを管理する必要がないように共有メモリを使用することには、もはや意味がありません。

これらの理由はあまり説得力がないように思えるかもしれませんが、私はMQL5言語とMetaTrader 5プラットフォームをフルに活用して、リプレイ/シミュレーションシステムを構築したいのです。多くの人がプラットフォームや言語で可能だと考えている以上のことができることが示されるでしょう。したがって、マウスに関連するデータも更新するC_Mouseクラスが必要になります。ここから呼び出します。CHARTEVENT_MOUSE_MOVE を介してマウスイベントを処理するので、この特定のイベントに進むことができます。

最初にすることは、ユーザーがチャート上で分析をおこなっているかどうかを確認することです。この場合、C_Controlクラスのマウスイベント処理に関連するものはすべて無視します。これからは、マウスイベントをローカルで処理することはありません。その代わりに、C_Mouseクラスに何が起こったかを尋ね、ユーザーの望みを叶えるために適切な判断と行動を取ります。次に、ユーザーがマウスの左ボタンを押したかどうか、そしてチャート上にスライダーオブジェクトがあるかどうかを確認します(一時停止モード)。trueの場合、クリックが発生した位置を確認します。スライダーオブジェクトにあった場合は、左ボタンが押されている限り、それに応じて反応します。こうすることで、ユーザーは以前と同じように、スライダーオブジェクトを左右にドラッグすることができます。

つまり、C_Controlクラスが、ユーザーが解析をおこなっているかどうかを知る方法を追加するだけでよかったのです。似たような要素はもっと違うやり方もあるでしょうが、冒頭で述べたように、今はそれほど明白ではないにせよ、これにはいくつかの利点があります。


結論

この記事では、もともとEAで使用するために開発されたクラスやメソッドを、別のプログラム(この場合は指標)で使用する方法について説明しました。ここでおこなわれたすべての作業によって、リプレイ/シミュレーションシステムを使用する際に、より良い体験ができるようになります。したがって、ある技術を開発したり、特定の作業方法をテストするには、このシーケンスで開発されたシステムを使用できます。もう1つ、現在解決しつつある問題があります。それについては次回にお話しします。というのも、状況を改善するためには、変更と追加が必要だからです。そして、この記事で紹介されているすべての資料と同様に、プログラミングを学び始めたばかりの人にとっては、すでに非常に難しいものとなっている可能性があります。理由もなく難易度を上げるつもりはありません。

添付ファイルにシステムコード一式があります。また、記事の最後には3組のファイルが用意されているので、さまざまなシナリオでシステムをテストすることができます。定期的にテストすることで、それがどのように開発されたかを少しずつ知ることができます。


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

添付されたファイル |
Files_-_BOLSA.zip (1358.24 KB)
Files_-_FOREX.zip (3743.96 KB)
Files_-_FUTUROS.zip (11397.51 KB)
Developing a Replay System (Part 36): Making Adjustments (II) Developing a Replay System (Part 36): Making Adjustments (II)
One of the things that can make our lives as programmers difficult is assumptions. In this article, I will show you how dangerous it is to make assumptions: both in MQL5 programming, where you assume that the type will have a certain value, and in MetaTrader 5, where you assume that different servers work the same.
リプレイシステムの開発(第34回):発注システム (III) リプレイシステムの開発(第34回):発注システム (III)
今回は、構築の第一段階を完成させます。この部分はかなり短時間で終わりますが、前回までに説明しなかった詳細をカバーします。多くの方が理解していない点をいくつか説明します。なぜShiftキーやCtrlキーを押さなければならないかご存じでしょうか。
リプレイシステムの開発(第37回):道を切り開く(I) リプレイシステムの開発(第37回):道を切り開く(I)
今回は、もっと前にやりたかったことをようやく始めます。確固たる地盤がないため、この部分を公に発表する自信がありませんでした。今、私にはその根拠があります。この記事の内容を理解することにできるだけ集中することをお勧めします。単に読むだけではなくて、という意味です。ここで強調しておきたいのは、この記事を理解できなければ、それに続く記事の内容を理解することはできないということです。
リプレイシステムの開発(第33回):発注システム(II) リプレイシステムの開発(第33回):発注システム(II)
今日も発注システムの開発を続けます。ご覧のように、他の記事ですでに紹介したものを大量に再利用することになります。とはいえ、この記事にはささやかなご褒美があります。まず、デモ口座からでもリアル口座からでも、取引サーバーで使えるシステムを開発します。MetaTrader 5プラットフォームを幅広く活用し、当初から必要なサポートをすべて提供します。