English Русский Español Deutsch Português
preview
リプレイシステムの開発(第29回):エキスパートアドバイザープロジェクト - C_Mouseクラス(III)

リプレイシステムの開発(第29回):エキスパートアドバイザープロジェクト - C_Mouseクラス(III)

MetaTrader 5テスター | 2 4月 2024, 11:14
59 0
Daniel Jose
Daniel Jose

はじめに

前回の「リプレイシステムの開発(第28回):エキスパートアドバイザープロジェクト - C_Mouseクラス(II)」稿では、より読みやすいコードを作成する方法について見てきました。このモデルは、プログラムをより理解しやすくするためには非常に興味深いものですが、このアプローチを使ったプログラミングには時間がかかることにお気づきでしょう。これはプログラミングが難しいからではありません。まったく逆です。難しいのは、プログラムを読みやすくするこの方法には限界があることです。そのような制限の1つが、あらゆるプログラミング言語の構文です。構文は特定の形式と構造を持つように設計されていますが、定義の使用は有用である一方で、別の意味で私たちを制限します。しかし、私の視点からは、これを実際のコードで示すのが適切だと思います。

元の構文を維持しますが、私たちが示す方法でコードを探求したいのであれば、必要と思われる定義を自由に作成し、ご自分にとって理解しやすいようにコードを適応させてください。これはかなり興味深いテクニックを学ぶのに役立つでしょう。こうして私は他の言語のプログラミングを学んだのです。これは労働集約的なプロセスですが、ある言語では別の言語よりも実装しやすいメソッドやアルゴリズムさえあるため、便利なプロセスです。しかし、原語のコードを読むことができれば、他の人にはできないことができます。それは翻訳作業だと考えることができます。あなたは2つの異なる世界の間の通訳なのです。成功するためには、いつも同じサイン、記号、用語を使用してコミュニケーションをとっている人よりも、私たちの理解の幅を広げなければなりません。

視野を広げ、既成概念にとらわれないことです。

さて、この記事を書くことになったきっかけに話を移しましょう。ここでは、クラスを変更することなく、また継承システムを使用することなく、能力に関係なく、制御された安全で信頼性の高い方法でシステムの機能を拡張する方法を見ていきます。作業は初めは単純に見えるかもしれませんが、毎回同じメソッドを構築したときに得られるものをはるかに超えて、物事がどのように機能するかを深く理解できるようになります。

今日の記事では、金融商品分析システムを拡張する方法について見てみましょう。C_MouseクラスとC_Terminalクラスから継承したものを使用して、別の分析ニッチを作ることにしましょう。C_Mouseクラスの内容を使用する新しいクラスを作成しますが、C_Mouseクラスを直接継承することはしません。この新しいクラスが最終的なコードに追加されるかどうかは、私たちの目標次第です。ただし、それとは関係なく、以前に作成されテストされたコードの完全性を侵害することなく、独自の訓練モデルを作成する方法を学びます。これがこの記事の本当の目的です。


拡大のための舞台作り

C_Mouseクラスを継承するのではなく、C_Mouseクラスの機能を拡張したり、むしろ変更したりするクラスのプログラミングを始める前に、元のC_Mouseクラスの詳細をいくつか調整する必要があります。何か問題があるからではなく、いくつかの追加と、あらゆる拡張を容易にするための小さな変更が必要だからです。こうしておけば、クラスの機能を変更しても、何か問題が起きれば、問題なく元のクラスに戻せばいいだけなので、非常に実用的です。変化は少なく、シンプルですが重要なものです。まず、C_Mouseクラスのコードに新しい変数を追加します。

class C_Mouse : public C_Terminal
{
   protected:
      enum eEventsMouse {ev_HideMouse, ev_ShowMouse};
      enum eBtnMouse {eKeyNull = 0x00, eClickLeft = 0x01, eClickRight = 0x02, eSHIFT_Press = 0x04, eCTRL_Press = 0x08, eClickMiddle = 0x10};
      struct st_Mouse
      {
         struct st00
         {
            int     X,
                    Y;
            double  Price;
            datetime dt;
         }Position;
         uint    ButtonStatus;
         bool    ExecStudy;
      };

この変数によって、継承やポリモーフィズムを必要とせずに、C_Mouseクラスを拡張したり、動作を変更したりすることができます。これらは最も一般的な方法ですが、ここでは別のアプローチを取ります。実際、これから紹介する方法を使用すれば、どのクラスでもこの戦略を適用することができます。ここで重要なのは、すべてが、クラスのソースコードを一行も変更することなくおこなわれるということです。機能を拡張できるようにすることを目的とした前述の変更を実装する前に、C_Mouseクラスに簡単なコードを1行追加する必要があります。簡単なものです。

inline void CreateObjectBase(const string szName, const ENUM_OBJECT obj, const color cor)
   {
      ObjectCreate(GetInfoTerminal().ID, szName, obj, 0, 0, 0);
      ObjectSetString(GetInfoTerminal().ID, szName, OBJPROP_TOOLTIP, "\n");
      ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_BACK, false);
      ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_COLOR, cor);
      ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_ZORDER, -1);
   }

この特別な行は、マウスの価格線がフォアグラウンドラインであることが判明しても、クリックしようとしているオブジェクトに重なった場合にイベントを受け取らないことを保証します。価格線は、マウスカーソルのフォーカスにオブジェクトがない場合にのみクリックイベントを捕捉します。このコード行を追加すると、オブジェクトがクリックされた場合でも分析の生成がブロックされないことに注意することが重要です。このコード行では、オブジェクトが直接クリックを受け取ることはできませんが、CHARTEVENT_MOUSE_MOVEイベントの起動がアクティブになってC_Mouseクラスによってキャプチャされるのは妨げられません。

重要な注意事項以前は、まさにこのコード行がなかったために、私はある問題を抱えていていました。「チャートをより面白くする:背景の追加」稿に、当時は解決できなかった欠陥があります。いくらやっても問題は解決しませんでした。チャート上に存在するオブジェクトにアクセスできないエラーは、チャートに背景を挿入するために使用されたオブジェクトにハイライト線を追加するだけで解決できました。もっと早くこのアドバイスを共有することもできましたが、実際に記事を読んでくれた人たちに何とか報いたかったのです。これで、記事にある問題を解決する方法がわかったでしょう。

次に、以下の関数に少し変更を加えます。

virtual void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
      {
         int w = 0;
         static double memPrice = 0;
                                
         C_Terminal::DispatchMessage(id, lparam, dparam, sparam);
         switch (id)
         {
            case (CHARTEVENT_CUSTOM + ev_HideMouse):
               ObjectSetInteger(GetInfoTerminal().ID, def_NameObjectLineH, OBJPROP_COLOR, clrNONE);
               break;
            case (CHARTEVENT_CUSTOM + ev_ShowMouse):
               ObjectSetInteger(GetInfoTerminal().ID, def_NameObjectLineH, OBJPROP_COLOR, m_Info.corLineH);
               break;
            case CHARTEVENT_MOUSE_MOVE:
               ChartXYToTimePrice(GetInfoTerminal().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);
               ObjectMove(GetInfoTerminal().ID, def_NameObjectLineH, 0, 0, m_Info.Data.Position.Price = AdjustPrice(m_Info.Data.Position.Price));
               m_Info.Data.Position.dt = AdjustTime(m_Info.Data.Position.dt);
               ChartTimePriceToXY(GetInfoTerminal().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) ObjectMove(GetInfoTerminal().ID, def_NameObjectLineV, 0, m_Info.Data.Position.dt, 0);
               m_Info.Data.ButtonStatus = (uint) sparam;
               if (CheckClick(eClickMiddle) && ((color)ObjectGetInteger(GetInfoTerminal().ID, def_NameObjectLineH, OBJPROP_COLOR) != clrNONE)) CreateStudy();
               if (CheckClick(eClickLeft) && (m_Info.Study == eStudyCreate))
               {
                  ChartSetInteger(GetInfoTerminal().ID, CHART_MOUSE_SCROLL, false);
                  ObjectMove(GetInfoTerminal().ID, def_NameObjectLineT, 0, m_Info.Data.Position.dt, memPrice = m_Info.Data.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 (sparam == def_NameObjectLineH) CreateLineH();
               break;
         }
      }

これらの変更は、特にC_Mouseクラスに利益をもたらすものではなく、むしろC_Mouseクラスに基づいて構築されるプログラム全体に利益をもたらします。以前の記事と現在の記事のコードの違いに気づかないかもしれませんが、それは変更の微妙さと特異性のためです。実際、コード上は何も変わっていません。これらの変更は、ユーザビリティやカスタマイズオプションの面でさまざまな利点をもたらします。その違いに気づくのは容易ではないかもしれませんが、コードに追加した3行は大いに役立っています。それぞれの仕事ぶりを見てみましょう。

  1. この行は、時間値を調整し、画面座標(XとY)だけでなく、金融商品座標(価格と時間)を使用して、チャート上の任意のオブジェクトを実際に使用できるようにします。私はこの質問の答えをずっと探していました。資産座標で作業する方が、画面座標で作業するよりもずっと面白いということも考えてみてください。この自由度の高さは明らかです。呼び出しをおこなっていることもわかりますが、実用的な理由からC_Terminalクラスになっています。
  2. 追加された呼び出しはChartTimePriceToXYで、価格座標を画面座標に変換します。
  3. そして最後のポイントは、まさにこれでした。C_Mouseクラスが学習モードであるかどうかを示します。混乱を避けるために、構文にご注意ください

これらはすべて、C_Mouseクラスに加えられた変更です。しかし、すでに述べたように、C_Terminalクラスには新しい関数が追加されました。この関数が何であるかを理解するために、この関数を見てみましょう。追加された機能を以下に示します。

inline datetime AdjustTime(const datetime arg) const
   {
      int nSeconds= PeriodSeconds();
      datetime dt = iTime(m_Infos.szSymbol, PERIOD_CURRENT, 0);
                                
      return (dt < arg ? ((datetime)(arg / nSeconds) * nSeconds) : iTime(m_Infos.szSymbol, PERIOD_CURRENT, Bars(m_Infos.szSymbol, PERIOD_CURRENT, arg, dt)));
   }

この機能がとても奇妙でわかりにくいと感じても、心配はいりません。最初は混乱し、無意味に思えるかもしれませんが、実際には素晴らしく興味深いものを提供してくれます。この関数のマジックを理解するためには、型変換について1つ、そしていくつかの追加の詳細について、理解しておく必要があります。まず、型変換について見てみましょう。

この関数が呼ばれると、パラメータとして日付と時刻の値を受け取ります。この値を日付と時刻としてだけでなく、ulong値として考えることが重要です。これが第一のポイントです。datetimeはulong値なので、変数内の日時情報はかなり特殊な方法で圧縮されます。私たちが関心があるのは最下位ビット(LSB)で、秒、分、時、日、月、年の値が最下位ビットから最上位ビットの順に並べられています。

nSecondsには、チャートで使用される期間の秒単位の値が含まれます。これは、この情報を提供するPeriodSeconds関数によって決定されます。ここでdt変数には、チャート上に存在する最後のバーの作成値が格納されます。この事実は非常に重要で、dtの値が呼び出しの値より小さければ、どの時点にいるかがわかるからです。この場合、未来です。したがって、画面座標(XとY)に対する時間の位置は、未来のバーがどこにあるか、あるいはどこに作られるかを示しています。現時点では、iTime関数を使用しても、この時点までのチャートがまだ作成されていないため、その位置を知ることはできません。しかし、その場合でも、特に未来に関連するチャート分析をおこなう場合は、それが実際にどこになるのかを知る必要があります。

画面座標(XとY)を調べるには、datetimeulong値であることを利用します。この値を秒数nSecondsで割ると、2倍の値が得られます。次に重要なポイントがあります。この端数値にnSecondsを掛けると、元の値が得られます。例えば、10を3で割って、割った結果に3を掛けると、また10になります。しかし、型変換をする場合(ここが重要)、double値をulongに変換するか、もっといいのはdatetime型の値に変換します。ここに端数はありません。したがって、この値にnSecondsを掛けると、すでに補正された未来の値が得られます。ここが一番面白いところです。ただし、問題があります。

この問題は、過去、特に連続的な系列、つまりギャップがない系列を調べるときに現れます。この方法は、主に特定の時間や日に取引される資産に典型的な、そのようなギャップがある資産の過去の分析には適していません。これは、特定の時間帯にのみバーが形成され、その時間帯以外では市場がクローズする株式市場の商品を指します。このような状況に対処するため、指定された値がどの列にあるのかを判断するために、少し変わったセットアップを使用します。これは、現在のバーと指定した時刻のバーの間のバー数を計算することで、正確な時点を捕捉するためのオフセットとして使用できる値が得られます。こうすることで、過去に何が起こったかに関係なく調整することもできるようになります。

この機能はまったく必要ないと思うかもしれません。なぜわざわざ開発するのでしょうか。しかし、この機能を使用すれば、銘柄座標(価格と時間)を画面座標(XとY)に変換することができます。したがって、画面座標やリソースオブジェクトに限らず、あらゆる種類のグラフィカルオブジェクトを使用することができます。あるタイプの座標を別のタイプに変換することができるようになりますが、そのためにChartXYToTimePrice(画面座標を資産座標に変換)とChartTimePriceToXY(資産座標を画面座標に変換)の呼び出しを使用します。分析の種類によっては、可能な限り正確な情報を得る必要があります。特定のバーを何かの指標として使用したい場合、この変換が必要になります。さらに、これは非常に興味深い情報を与えてくれます。


C_Studysクラスを作成する

C_Mouseクラスを改良した後は、分析のためのまったく新しいフレームワークを作るためのクラスを作ることに集中しましょう。この新しいクラスを作るのに、継承やポリモーフィズムは使用しません。その代わりに、価格線に新しいオブジェクトを追加します。それがこの記事でやろうとしていることです。次回は、分析結果を変更する方法について見るつもりです。しかし、C_Mouseクラスのコードを変更することなく、これらすべてをおこないます。実際には、継承やポリモーフィズムを使用すれば、もっと簡単に実現できるでしょう。新しいコードに欠陥があったとしても、大きな混乱を招くことなく柔軟性を確保できるため、同じ結果を得られる方法は他にもあります。

このアプローチでは、問題のあるコードを削除し、バグを修正し、以前にテストしたコードに変更を加えることなく、再び導入することができます。覚えておいてほしいのは、バグ修正によって既存のクラス構造との互換性が失われ、継承やポリモーフィズムを使用して新たな機能を追加することが難しくなることがよくあるということです。したがって、これらの代替テクニックをマスターすることは、特に、既存のコードを大幅にリストラすることなく、すでに完成しテスト済みのプログラムに新しい機能を実装したい場合には重要です。

ただし、関係するクラス間に独自の階層構造を持つ複数の異なるファイルを作成することも可能です。このファイルやファイルが親クラスの階層を統合することはないので、問題はありません。こうすることで、必要に応じてさまざまな変更や改良が可能な独立したヒエラルキーを確立することができます。この柔軟性は、メインの開発階層とは直接関係なく、ほとんど独立したプロジェクトのように機能することに由来します。

まずはシンプルなシステムから始め、将来的にはより複雑なアプローチを模索していきます。ファイルはこのように実行されます。

#include "..\C_Mouse.mqh"
#include "..\..\..\Service Graphics\Support\Interprocess.mqh"
//+------------------------------------------------------------------+
#define def_ExpansionPrefix "Expansion1_"
#define def_ExpansionBtn1 def_ExpansionPrefix + "B1"
#define def_ExpansionBtn2 def_ExpansionPrefix + "B2"
#define def_ExpansionBtn3 def_ExpansionPrefix + "B3"
//+------------------------------------------------------------------+
#define def_InfoTerminal (*mouse).GetInfoTerminal()
#define def_InfoMousePos (*mouse).GetInfoMouse().Position
//+------------------------------------------------------------------+

このコードの紹介から、このプロセスが集中的なものであることが理解できます。このファイルの異なる場所に2つのヘッダーファイルが含まれていることにご注意ください。この部分についてはすでに述べました。もう少し後で、使用するオブジェクトのいくつかを紹介します。これは、さらに目的のオブジェクトにアクセスする際の混乱を避けるために重要です。また、最も強力で、同時に最も危険なリソースの1つであるポインタによく似たものを使用するので、コーディングを容易にするために、一種のエイリアスを定義しました。しかし、私たちが計画しているプログラム方法のおかげで、この資源に関連するリスクはかなり管理しやすくなり、非常に興味深い使い方ができるようになるでしょう。

このような定義(エイリアス)は、特定のものにアクセスしたいが、コードを書いている最中に何かタイプミスをするリスクを冒したくない場合によく使用されます。いつもとても興味深い情報源です。

次に、以下のクラスコードが来ます。

class C_Studys
{
   protected:
   private :
//+------------------------------------------------------------------+
      enum eStatusMarket {eCloseMarket, eAuction, eInTrading, eInReplay};
//+------------------------------------------------------------------+
      C_Mouse *mouse;
//+------------------------------------------------------------------+
      struct st00
      {
         eStatusMarket   Status;
         MqlRates        Rate;
         string          szInfo;
         color           corP,
                         corN;
         int             HeightText;
      }m_Info;

この段階では、クラスのprivate変数だけに注目します。C_Mouseクラスへのポインタのような役割を果たしています。ポインタはプログラミングにおいて最も強力なリソースの1つですが、使用時に問題が発生する可能性があるため、特別な注意が必要です。したがって、MQL5ではポインタがC/C++のような特性を持っていないとしても、慎重に使用することが非常に重要です。いずれにせよ、使用する際には注意が必要です。ポインタを甘く見てはいけません。.残りのコードはまだ特別なものではありません。

最初のクラス関数を以下に示します。

const datetime GetBarTime(void)
   {
      datetime dt = TimeCurrent();
                                
      if (m_Info.Rate.time <= dt)
         m_Info.Rate.time = iTime(def_InfoTerminal.szSymbol, PERIOD_CURRENT, 0) + PeriodSeconds();

      return m_Info.Rate.time - dt;
   }

これは、新しいバーが表示されるまでの残り時間を計算する非常にシンプルな関数です。計算自体はその特定のポイントでのみおこなわれるが、テストがバーのタイムリミットに達したことを検出するたびに、必要な読み取りと調整をおこない、計算を次のバーに進めます。したがって、このiTime関数の呼び出しは、各バーが作成されるたびに1回だけ発生し、各インタラクションやメソッド呼び出しでは発生しません。次の関数は、標準的な方法でオブジェクトを作成します。

inline void CreateObjectBase(const string szName, const ENUM_OBJECT obj)
   {
      ObjectCreate(def_InfoTerminal.ID, szName, obj, 0, 0, 0);
      ObjectSetString(def_InfoTerminal.ID, szName, OBJPROP_TOOLTIP, "\n");
      ObjectSetInteger(def_InfoTerminal.ID, szName, OBJPROP_BACK, false);
      ObjectSetInteger(def_InfoTerminal.ID, szName, OBJPROP_ZORDER, -1);
   }

C_Mouseにある関数と本質的に同じプロセスを踏んでいるのだから、この2つを組み合わせて、より一般的な関数にすることもすぐに考えられるでしょう。この直後、テストにとって興味深いかもしれないプロシージャを示します。

int CreateBTNInfo(const string szExample, int x, string szName, color backColor, string szFontName, int FontSize)
   {
      int w;
                                
      CreateObjectBase(szName, OBJ_BUTTON);
      TextGetSize(szExample, w, m_Info.HeightText);
      m_Info.HeightText += 5;
      w += 5;
      ObjectSetInteger(def_InfoTerminal.ID, szName, OBJPROP_STATE, true);
      ObjectSetInteger(def_InfoTerminal.ID, szName, OBJPROP_BORDER_COLOR, clrBlack);
      ObjectSetInteger(def_InfoTerminal.ID, szName, OBJPROP_COLOR, clrBlack);
      ObjectSetInteger(def_InfoTerminal.ID, szName, OBJPROP_BGCOLOR, backColor);
      ObjectSetString(def_InfoTerminal.ID, szName, OBJPROP_FONT, szFontName);
      ObjectSetInteger(def_InfoTerminal.ID, szName, OBJPROP_FONTSIZE, FontSize);
      ObjectSetInteger(def_InfoTerminal.ID, szName, OBJPROP_CORNER, CORNER_LEFT_UPPER); 
      ObjectSetInteger(def_InfoTerminal.ID, szName, OBJPROP_XSIZE, w); 
      ObjectSetInteger(def_InfoTerminal.ID, szName, OBJPROP_YSIZE, m_Info.HeightText);
      ObjectSetInteger(def_InfoTerminal.ID, szName, OBJPROP_XDISTANCE, x);
                                
      return w;
   }

この方法では、使用するフォントのサイズや種類、オブジェクトの内部に印刷されるテキストの長さに応じて、オブジェクトのサイズを調整することができます。これは、TextGetSize関数を使用することによって達成され、提供された情報に基づいて、テキストのサイズの推定値を与えます。しかし、オブジェクトの中でテキストをより良く見せるために、そのサイズを少し大きくします。これにより、テキストとオブジェクトの境界の間に小さなスペースができます。

次に、チャートに情報を表示する関数があります。

void Draw(void)
   {
      double v1;
                                
      ObjectSetInteger(def_InfoTerminal.ID, def_ExpansionBtn1, OBJPROP_YDISTANCE, def_InfoMousePos.Y <= 0 ? UINT_MAX : def_InfoMousePos.Y - m_Info.HeightText);
      ObjectSetInteger(def_InfoTerminal.ID, def_ExpansionBtn2, OBJPROP_YDISTANCE, def_InfoMousePos.Y <= 0 ? UINT_MAX : def_InfoMousePos.Y - 1);
      ObjectSetInteger(def_InfoTerminal.ID, def_ExpansionBtn3, OBJPROP_YDISTANCE, def_InfoMousePos.Y <= 0 ? UINT_MAX : def_InfoMousePos.Y - 1);
      ObjectSetString(def_InfoTerminal.ID, def_ExpansionBtn1, OBJPROP_TEXT, m_Info.szInfo);
      v1 = NormalizeDouble(100.0 - ((m_Info.Rate.close / def_InfoMousePos.Price) * 100.0), 2);
      ObjectSetInteger(def_InfoTerminal.ID, def_ExpansionBtn2, OBJPROP_BGCOLOR, (v1 < 0 ? m_Info.corN : m_Info.corP));
      ObjectSetString(def_InfoTerminal.ID, def_ExpansionBtn2, OBJPROP_TEXT, (string)MathAbs(v1) + "%");
      v1 = NormalizeDouble(100.0 - ((m_Info.Rate.close / iClose(def_InfoTerminal.szSymbol, PERIOD_D1, 0)) * 100.0), 2);
      ObjectSetInteger(def_InfoTerminal.ID, def_ExpansionBtn3, OBJPROP_BGCOLOR, (v1 < 0 ? m_Info.corN : m_Info.corP));
      ObjectSetString(def_InfoTerminal.ID, def_ExpansionBtn3, OBJPROP_TEXT, (string)MathAbs(v1) + "%");
   }

これはまさに、常に存在し、価格線に付随する研究のタイプを生成します。そうすれば、ある物事を簡単に理解できるようになります。情報の種類や表示される内容は、あなたが何を必要としているかによって異なります。ここではデモンストレーションのため、3種類の情報を用意します。では、何が提供されるのか見てみましょう。

プログラムがプラットフォームや取引サーバーとどのように相互作用するかによって、異なる情報を受け取ることができます。次のような情報を受け取っています。

  • チャートに次のバーが表示されるまでの残り時間
  • 市場が閉鎖されているという情報
  • リプレイを扱っているという情報
  • 資産がオークションされているという情報
  • そして(極めてまれなケースだが)エラーメッセージが表示されます。

私たちがやっていることは珍しいことではないことにご注意ください。これは、これから使用するテクニックのデモンストレーションにすぎません。次にデスタクターです。

~C_Studys()
   {
      ObjectsDeleteAll(def_InfoTerminal.ID, def_ExpansionPrefix);
   }

上記のコードの目的は、クラスが作成した要素を削除したいことをプラットフォームに示すことです。なぜなら、そのようなオブジェクトが削除されても再作成するつもりはないからです。

ここで次の機能が登場します。

void Update(void)
   {
      switch (m_Info.Status)
      {
         case eCloseMarket: m_Info.szInfo = "Closed Market";                         break;
         case eAuction   : m_Info.szInfo = "Auction";                                break;
         case eInTrading : m_Info.szInfo = TimeToString(GetBarTime(), TIME_SECONDS); break;
         case eInReplay  : m_Info.szInfo = "In Replay";                              break;
         default         : m_Info.szInfo = "ERROR";
      }
      Draw();
   }

この機能で重要なのは、単独では機能しないということです。よく似た名前の関数がもう1つあります。

void Update(const MqlBookInfo &book[])
   {
      m_Info.Status = (ArraySize(book) == 0 ? eCloseMarket : (def_InfoTerminal.szSymbol == def_SymbolReplay ? eInReplay : eInTrading));
      for (int c0 = 0; (c0 < ArraySize(book)) && (m_Info.Status != eAuction); c0++)
         if ((book[c0].type == BOOK_TYPE_BUY_MARKET) || (book[c0].type == BOOK_TYPE_SELL_MARKET)) m_Info.Status = eAuction;
      this.Update();
   }

なぜUpdateという名前の関数が2つあるのでしょうか。可能でしょうか。はい、同じ名前の関数を宣言することができます。この現象は過負荷として知られています。名前は同じですが、コンパイラーにとっては両関数の名前は異なります。これはパラメータのせいです。このようにして、関数やプロシージャをオーバーロードすることができるが、唯一のルールは、パラメータが異なっていなければならないということです。つ目の関数では、パラメータを必要としないメソッドを使用して1つ目の関数を呼び出していることを忘れてはなりません。このやり方は、オーバーロードされるメソッドをプログラミングするときに、デバッグを容易にするものを作りたいときによく使用されます。

ある資産がオークションされているかどうかをどうやって調べるのでしょうか。価格情報は通常、オーダーブックで提供されます。しかし、資産のオーダーブックがこれらの特定の値のいずれかを示すことがあり、これが起こると、資産がオークションされたことを意味します。自動EAに追加する興味深いリソースになるかもしれないので、ご注目ください。このことについては、別の連載ですでに触れました。 自動で動くEAを作る(第14回):自動化 (VI).しかし、資産がオークションされているかどうかを理解する方法については詳しく説明しませんでした。私たちの目標は、自動EAを使った取引プロセスのすべての側面を詳細にカバーすることではありません。

以下の関数は、いくつかのプラットフォームイベントに応答するために使用されます。

virtual void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
   {               
      switch (id)
      {
         case CHARTEVENT_MOUSE_MOVE:
            Draw();
            break;
      }
   }

オブジェクトはマウスに従わなければならず、マウス解析はC_Mouseクラスによって処理されるので、シンプルですが効果的です。ここでも、継承やポリモーフィズムを使わずに、C_Mouseクラスの動作を変更しています。つまり、マウスの位置を調整したり修正したりするのは、C_Mouseクラスの仕事なのです。データのみを使用します。

ほとんどすべてのコードを見てきました。しかし、マジックが実際に起こる場所に目を向ける必要があります。EAのコードから始めましょう。下に完全なコードがあります。

//+------------------------------------------------------------------+
#include <Market Replay\System EA\Auxiliar\C_Mouse.mqh>
#include <Market Replay\System EA\Auxiliar\Study\C_Studys.mqh>
//+------------------------------------------------------------------+
input group "Mouse";
input color     user00 = clrBlack;      //Price Line
input color     user01 = clrPaleGreen;  //Positive Study
input color     user02 = clrLightCoral; //Negative Study
//+------------------------------------------------------------------+
C_Mouse *mouse = NULL;
C_Studys *extra = NULL;
//+------------------------------------------------------------------+
int OnInit()
{
   mouse = new C_Mouse(user00, user01, user02);
   extra = new C_Studys(mouse, user01, user02);
                
   OnBookEvent(_Symbol);
   EventSetMillisecondTimer(500);

   return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
   MarketBookRelease(_Symbol);
   EventKillTimer();
        
   delete extra;
   delete mouse;
}
//+------------------------------------------------------------------+
void OnTick() {}
//+------------------------------------------------------------------+
void OnTimer()
{
   (*extra).Update();
}
//+------------------------------------------------------------------+
void OnBookEvent(const string &symbol)
{

       MqlBookInfo book[];
   

       if (mouse.GetInfoTerminal().szSymbol == def_SymbolReplay) ArrayResize(book, 1, 0); else
   {
      if (symbol != (*mouse).GetInfoTerminal().szSymbol) return;
      MarketBookGet((*mouse).GetInfoTerminal().szSymbol, book);
   }
   (*extra).Update(book);
}
//+------------------------------------------------------------------+
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
    (*mouse).DispatchMessage(id, lparam, dparam, sparam);
    (*extra).DispatchMessage(id, lparam, dparam, sparam);
        
    ChartRedraw();
}
//+------------------------------------------------------------------+

このEAでは、同じツールを使用してリアル資産とシミュレーション資産の両方を取引することができます。これは、この種の検証のおかげです。現在、そして現段階では、EAがチャート上で何を認識するかによって、異なる情報を得ることができるでしょう。

クラスのコンストラクタには、C_Mouseクラスの初期化時に生成されたポインタを渡します。こうすることで、C_Studysクラスは、その内容を使用するためにC_Mouseクラスを直接継承する必要がなくなり、C_Studysの内部でC_Mouseを初期化する必要がなくなります。このモデリング技法は、継承やポリモーフィズムに頼らずに情報を交換したり、要素と相互作用したりしたい場合に便利です。C_Mouseクラスと何らかの関係があるC_Studysクラスの機能を削除したり、適応させたり、新しい機能を作成したりする必要がある場合、C_Mouseクラスを変更することなく、簡単におこなうことができます。

このモデルの大きな利点は、コンポーネントの並行開発が可能なことです。C_Studysクラスが最終的なプロジェクトの一部でない場合は、そこからコードを削除するだけでよくなります。このC_Studysは、最終的なコードのメインクラスシステムから独立した並列クラスとなります。

では、この情報伝達がどのように実装されているかを理解するために、コンストラクタのコードを分析してみましょう。

C_Studys(C_Mouse *arg, color corP, color corN)
   {
#define def_FontName "Lucida Console"
#define def_FontSize 10
      int x;
                                        
      mouse = arg;
      ZeroMemory(m_Info);
      m_Info.Status = eCloseMarket;
      m_Info.Rate.close = iClose(def_InfoTerminal.szSymbol, PERIOD_D1, ((def_InfoTerminal.szSymbol == def_SymbolReplay) || (macroGetDate(TimeCurrent()) != macroGetDate(iTime(def_InfoTerminal.szSymbol, PERIOD_D1, 0))) ? 0 : 1));
      m_Info.corP = corP;
      m_Info.corN = corN;
      TextSetFont(def_FontName, -10 * def_FontSize, FW_NORMAL);
      CreateBTNInfo("Closed Market", 2, def_ExpansionBtn1, clrPaleTurquoise, def_FontName, def_FontSize);
      x = CreateBTNInfo("99.99%", 2, def_ExpansionBtn2, clrNONE, def_FontName, def_FontSize);
      CreateBTNInfo("99.99%", x + 5, def_ExpansionBtn3, clrNONE, def_FontName, def_FontSize);
      Draw();
#undef def_FontSize
#undef def_FontName
   }

ご覧の通り、このコードにはちょっと変わったパラメータがあります。クラスへのポインタです。これは、私たちが設計すべきシステムを形成するために、要素がどのように組み合わされているかを示しています。しかし、一見奇妙に思えるかもしれませんが、もう1つ興味深い機能があります。TextSetFont関数です。 表示する情報の種類に応じて、オブジェクトのサイズを調整することは非常に重要です。ここでは因数分解をおこなっていることにご注意ください。なぜ負数を使用するのでしょうか。念のため、ドキュメントに記載されている説明を見てみましょう。

フォントサイズは、正または負の値を使用して決定されます。この事実が、テキストサイズのオペレーティングシステム設定(サイズスケール)への依存を決定します。

  • サ イ ズ が正の場合、論理フ ォ ン ト を物理フ ォ ン ト と し て表示す る 際に、 それはデバ イ ス の物理単位 (ピ ク セル) へ変換 さ れます。サイズは、使用可能なフォントの銘柄セルの高さに対応します。TextOut()関数を使用して表示されるテキストと、OBJ_LABEL(「テキストラベル」)グラフィカルオブジェクトを使用して表示されるテキストを共有する場合には、これは推奨されません。
  • サイズが負の場合、それは論理ポイントの10分の1で設定されていると仮定され(値-350は35論理ポイントに等しい)、10で割られます。結果の値は、デバイスの物理単位(ピクセル)に変換され、使用可能なフォントからの文字の高さの絶対値に対応します。オブジェクトプロパティで定義されたフォントサイズに-10を掛けて、スクリーンのテキストサイズをOBJ_LABELオブジェクトサイズに近づけます。

 そのため、因数分解はこの方法でおこなわれます。ここでこのパラメータを設定しておかないと、オブジェクト生成関数で使われるTextGetSize関数の使用に問題が生じます。これは、使用されているフォントやその寸法が、私たちの意図するものと正確に一致しないために起こります。


結論

添付のアプリを必ずテストしてください。リプレイ/シミュレーションモードと、市場で稼動している口座(デモまたはリアル)の両方で実験するようお勧めします、

幅広い理解を得るためにです。ただし、メインクラスのコードは一行も変更しません。お約束します。

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

添付されたファイル |
Files_-_FUTUROS.zip (11397.51 KB)
Files_-_FOREX.zip (3743.96 KB)
Files_-_BOLSA.zip (1358.24 KB)
リプレイシステムの開発(第30回):エキスパートアドバイザープロジェクト - C_Mouseクラス(IV) リプレイシステムの開発(第30回):エキスパートアドバイザープロジェクト - C_Mouseクラス(IV)
今日は、プログラマーとしての職業生活のさまざまな段階で非常に役立つテクニックを学びます。多くの場合、制限されているのはプラットフォーム自体ではなく、制限について話す人の知識です。この記事では、常識と創造性があれば、クレイジーなプログラムなどを作成することなく、MetaTrader 5 プラットフォームをより面白くて多用途にし、シンプルでありながら安全で信頼性の高いコードを作成できることを説明します。創造力を駆使して、ソース コードを1行も削除したり追加したりすることなく、既存のコードを変更します。
リプレイシステムの開発(第28回):エキスパートアドバイザープロジェクト-C_Mouseクラス(II) リプレイシステムの開発(第28回):エキスパートアドバイザープロジェクト-C_Mouseクラス(II)
人々が初めてコンピューティングが可能なシステムを作り始めたとき、すべてには、プロジェクトを熟知しているエンジニアの参加が必要でした。コンピュータ技術の黎明期、プログラミング用の端末すらなかった時代の話です。それが発展し、より多くの人々が何かを創造できることに興味を持つようになると、新しいアイデアやプログラミングの方法が現れ、以前のようなコネクタの位置を変えるスタイルに取って変わりました。最初の端末が登場したのはこの時です。
母集団最適化アルゴリズム:Stochastic Diffusion Search (SDS) 母集団最適化アルゴリズム:Stochastic Diffusion Search (SDS)
この記事では、ランダムウォークの原理に基づく非常に強力で効率的な最適化アルゴリズムである確SDS(Stochastic Diffusion Search、確率的拡散探索)について説明します。このアルゴリズムは、複雑な多次元空間で最適解を求めることができ、収束速度が速く、局所極値を避けることができるのが特徴です。
リプレイシステムの開発(第27回):エキスパートアドバイザープロジェクト-C_Mouseクラス(I) リプレイシステムの開発(第27回):エキスパートアドバイザープロジェクト-C_Mouseクラス(I)
この記事では、C_Mouseクラスを実装します。このクラスは、最高水準でプログラミングする能力を提供します。しかし、高水準や低水準のプログラミング言語について語ることは、コードに卑猥な言葉や専門用語を含めることではありません。逆です。高水準プログラミング、低水準プログラミングというのは、他のプログラマーが理解しやすいか、しにくいかという意味です。