English Русский 中文 Español Deutsch Português
preview
取引履歴を気にせずにチャート上で直接取引を表示する方法

取引履歴を気にせずにチャート上で直接取引を表示する方法

MetaTrader 5 | 20 11月 2024, 10:29
146 0
Artyom Trishkin
Artyom Trishkin

内容


はじめに

現代のトレーダーは、取引操作に関連する膨大なデータを分析するという課題にしばしば直面します。MetaTrader 5クライアント端末では、チャートがオープンポジションとクローズポジションのラベルで埋め尽くされ、取引履歴の表示が非効率的でわかりにくくなることがあります。特に、大量の取引を扱うトレーダーにとっては、チャートがすぐに一杯になり、取引活動を分析して情報に基づいた意思決定をおこなうことがほとんど不可能になるケースもあります。

この記事の目的は、取引履歴の認識と分析を容易にするソリューションを提供することです。クローズされたポジションと改善された取引情報を段階的に表示するメカニズムを開発し、トレーダーが個々の取引に集中し、取引操作をより深く理解できるようにすることを目指します。

以下を実装します。

  • ナビゲーションキーを使用してクローズされたポジションを1つずつ表示する機能
  • 改良された、各取引に関する詳細情報を提供するツールチップ
  • 最も重要な要素が常に表示されるようにグラフを中央に配置する機能

これらを実装するために必要なデータ構造体を詳細に検討し、MetaTrader 5での取引とポジション計算の基本原則を提案します。これにより、トレーダーは取引履歴をより効果的に管理し、得られた情報を基に情報に基づいた意思決定をおこなえるようになります。

MetaTrader 5クライアント端末では、チャート設定(F8キー)の[表示]タブで[取引履歴を表示]オプションをオンにすることで、取引履歴を表示できます。


このオプションをオンにすると、チャート上に、線で結ばれたポジションの開始および終了のラベル形式で取引履歴全体が表示されるようになります。この際、銘柄ごとに実行されたすべての取引が完全に表示されます。


取引が多数ある場合、チャートはラベルで乱雑になり、チャート上の情報が見えにくくなります。また、取引ラベルやそれを結ぶ線の上にマウスを合わせた際に表示されるツールチップも、特に有益な情報を提供しているとは言えません。


ここで紹介するソリューションは、起動時に最後にクローズされたポジションのみを表示します。残りのポジションへの移動は、カーソルキーを押して実行されます。

  • 上矢印キーを押すと、最初にクローズされたポジションが表示される
  • 下矢印キーを押すと、最後にクローズされたポジションが表示される
  • 左矢印キーを押すと、前にクローズされたポジションが表示される
  • 右矢印キーを押すと、次にクローズされたポジションが表示される

取引ラベルと接続線のツールチップは、より有用な情報を表示するよう改善されています。また、Shiftキーを押したままにすることで、現在選択されているクローズポジションに関する情報がチャートに表示されるように設計されています。


後続の各クローズポジションを表示する際は、ポジションの開始/終了ラベルとそれらを結ぶ線がチャートの中央に配置されるようにします。

開始ラベルと終了ラベルの両方が1つのチャート画面内に収まらない場合は、開始取引がチャートの左から2番目の表示バーに配置されるよう調整します。

次に、取引とポジションの計算の基本原則、ならびにここで作成するクラスの構造を確認します。

ポジションの形成過程は次の通りです。まず、取引リクエスト(注文)がサーバーに送信されます。この注文はキャンセルされるか実行されます。実行されると取引(ディール)が発生し、新たなポジションが生成されます。ポジションがない状態で取引が実行されると、取引によってポジションが新規に作成されます。一方、ポジションが既に存在する場合、その計算方式に応じて複数のオプションがあります。ネッティング計算の場合:1つの銘柄につき1つのポジションのみが可能です。したがって、新しい取引で既存ポジションが以下のように変更されます。

  1. クローズ:ロングポジションに対して、ポジション取引量と等しい量の売りが実行された場合
    買いポジション(1.0) - 売り取引(1.0) = 取引量0(ポジションのクローズ)

  2. 部分クローズ:ロングポジションに対して、ポジション取引量より少ない量の売りが実行された場合
    買いポジション(1.0) - 売り取引(0.5) = 取引量0.5
    (ポジションの部分クローズ)

  3. 取引量追加:ロングポジションに対して、新たな買いが実行された場合
    買いポジション(1.0) + 買い取引(0.5) = 取引量1.5
    (ポジション取引量の増加)

  4. 反転:ロングポジションに対してポジション取引量よりも大きい量の売りが実行された場合
    買いポジション(1.0) - 売り取引(1.5) = 売りポジション(0.5) (ポジションの反転)

ヘッジ口座タイプの場合、各取引が既存のポジションを変更するか、新規ポジションを生成します。既存ポジションを変更する(クローズまたは部分クローズ)場合、取引にポジションIDを指定する必要があります。ポジションIDは、各新しいポジションに割り当てられる一意の番号であり、ポジションの存続期間中は変更されません。

  1. 新しいポジションを建てる:別のポジションがまだ存在する間に買いまたは売りが実行された場合
    買いポジション(1.0) + 売り取引(1.0) = 独立した買いポジション(1.0)と売りポジション(1.0)
    (ポジションを建てる)、または
    買いポジション(1.0) + 買い取引(1.5) = 独立した買いポジション(1.0)と買いポジション(1.5)(ポジションを建てる)など。

  2. 既存ポジションのクローズ:既存のロングポジションのIDを指定し、それに対する売りが実行された場合
    買いポジション(1.0)(ID123) - 売り取引(1.0)(ID123) = ポジションクローズ(1.0)(ID123)(ID123ポジションのクローズ)

  3. 既存ポジションの部分クローズ:指定されたIDの既存ポジションのボリュームよりも少ないボリュームで、既存のロングポジションのIDを使用して売りが実行された場合
    買いポジション(1.0)(ID123) - 売り取引(0.5)(ID123)= 取引量0.5(ID123)(ID123ポジション部分クローズ)

注文、取引、ポジションの詳細については「MetaTrader 5の注文、ポジション、取引」をご覧ください。

各注文は、取引サーバーに送信されると、実行されるまで端末内のアクティブ注文リストに残ります。注文がトリガーされると、取引が発生し、新しいポジションを生成するか、既存のポジションを変更します。注文が実行される瞬間、その注文は履歴注文のリストに移動され、同時に取引が生成されます。この取引は履歴取引リストに登録されます。取引がポジションを生成する場合、新しいポジションが作成され、アクティブポジションのリストに配置されます。

端末には、過去の(クローズされた)ポジションのリストは存在しません。また、アクティブな取引のリストもありません。それらは実行されるとすぐに履歴として扱われ、対応するリストに配置されます。
その理由は簡単です。注文とは、口座で取引操作を実行するための指示です。注文は履行されるまで存在し、既存の注文リストに表示されます。注文が実行されると(理想的には)存在しなくなり、実行済み注文のリストに移動されます。このプロセスで取引が生成されます。取引とは、取引注文が実行されたという事実を指します。これは一度限りのイベントであり、注文が実行されると取引が発生します。それだけのことです。取引は、完了した口座イベントのリストに追加されます。
取引はポジションを生成し、そのポジションはクローズされるまで存在します。ポジションは常にアクティブポジションのリストに表示され、クローズされるとそのリストから削除されます。

端末にはクローズされたポジションのリストはありませんが、完了した取引のリストは存在します。したがって、クローズされたポジションのリストを作成するには、以前にクローズされたポジションに属していた取引からそれらを再構成する必要があります。取引リストは常に利用可能であり、各取引には関連するポジションのIDが含まれています。このため、以前口座上に存在していた各ポジションのライフヒストリーを再現するために必要な情報(ポジションIDとその取引のリスト)にすべてアクセス可能です。

上記に基づき、以下のステップに従って、既存の取引履歴リストからクローズポジションを再作成できます。

  • 必要な銘柄のすべての取引のリストを取得する
  • リストを確認して、リストから次の取引を取得する
  • 取引プロパティでポジションIDを確認する
  • 独自の履歴ポジションリストにそのようなIDのポジションが存在しない場合は、履歴ポジションオブジェクトを新規作成する
  • 以前受信したIDを使用して履歴ポジションオブジェクトを取得する
  • 現在の取引を履歴ポジションオブジェクト取引のリストに追加する

端末取引のリストのループが完了すると、各ポジションに属する取引のリストを含む、銘柄別の口座上のすべてのクローズポジションの完全なリストが得られます。

これで、各履歴ポジションには、そのポジションの変更に関係する取引のリストが表示され、ポジションが存在する間にクローズされたポジションのすべての変更に関する完全な情報を取得できるようになります。

上記のロジックを実装するには、次の3つのクラスを作成する必要があります。

  1. 取引クラス:このクラスには、過去の取引のすべてのプロパティのリスト、プロパティにアクセスするためのメソッド(設定と取得)、および取引データを表示するための追加メソッドが含まれます。
  2. ポジションクラス:このクラスには、すべての取引のリスト、それらにアクセスするためのメソッド、およびポジションとその取引に関する情報を表示するための追加メソッドが含まれます。
  3. 履歴ポジション管理クラス:このクラスには、すべての履歴ポジションのリストが含まれ、それを作成および更新するほか、ポジションとその取引のプロパティにアクセスするためのメソッド、さらにポジションとその取引に関する情報を表示するための追加メソッドも含まれます。

始めましょう。


取引クラス

取引のプロパティを取得して保存するには、注文および取引の履歴を選択する必要があります(HistorySelect())。取引履歴をループで調べ、履歴取引のリストからループインデックスを使って取引チケットを取得します(HistoryDealGetTicket())。この場合、取引はHistoryDealGetInteger()HistoryDealGetDouble()HistoryDealGetString()関数を使用してプロパティを取得するために選択されます。

取引クラスでは、取引がすでに選択されており、そのプロパティをすぐに取得できることを前提としています。取引プロパティの記録と取得に加えて、クラスを使用すると、取引を表示するチャートにグラフィカルラベルを作成できます。ラベルはキャンバスに描画されるため、定義済みのフォントシンボルセットのラベルを使用する代わりに、各タイプの取引に必要なラベルを描画できます。各取引のオブジェクトを作成する際には、取引がおこなわれた瞬間の価格のティック履歴を取得し、取引時に存在したスプレッドを計算して、取引パラメータの説明に表示することができます。

各ポジションには、その変更に参加した多くの取引が存在する可能性があるため、すべての取引は、最終的にCObjectクラスとそのCArrayObjの子孫のインスタンスへのポインタの動的配列のクラスに配置されます。
したがって、取引クラスは、標準ライブラリのCObject基底オブジェクトクラスから継承されます。

取引には、整数、実数、文字列のプロパティのセットがあります。各取引オブジェクトは、履歴ポジションクラスのオブジェクトのリストにあります。必要な取引の検索を実装するには、パラメータの列挙を記述する必要があります。この列挙の値を使用して、リスト内の取引を検索し、同じ値でリストを並べ替えることができます。検索は、取引チケットとミリ秒単位の時間というプロパティのみでおこないます。チケットを使用すると、そのような取引がリストにすでに存在するかどうかを判断できます。ミリ秒単位の時間を使用すると、取引を厳密に時系列順に並べることができます。ただし、クラスは拡張を意味するため、取引プロパティのリストにはそのすべてのプロパティが含まれます。

標準ライブラリのCArrayObjリストで検索と並べ替えを整理するために、CObjectクラスでは、値0を返すCompare()仮想メソッドが提供されます。

   //--- method of comparing the objects
   virtual int       Compare(const CObject *node,const int mode=0) const { return(0);      }

つまり、このメソッドは常に等価性を返します。CObjectから継承されたクラスの各オブジェクトでは、このメソッドを再定義して、比較対象の2つのオブジェクトのプロパティ(modeパラメータで指定)を比較するときに、メソッドが次を返すようにする必要があります。

  • -1:現在のオブジェクトプロパティの値が比較対象オブジェクトの値よりも小さい場合
  •  1:現在のオブジェクトプロパティの値が比較対象オブジェクトの値よりも大きい場合
  •  0:比較する両方のオブジェクトで指定されたプロパティの値が等しい場合

したがって、上記の説明に基づいて、今日作成された各クラスに対して、クラスオブジェクトのプロパティの列挙とそれらを比較するためのメソッドをさらに作成する必要があります。

\MQL5\Include\フォルダ内にPositionsViewer\フォルダを作成して、CDealクラスの新しいファイルDeal.mqhを含めます。


標準ライブラリのCObjectクラスが基底クラスである必要があります。


その結果、取引オブジェクトクラスファイルの次のテンプレートが得られます。

//+------------------------------------------------------------------+
//|                                                         Deal.mqh |
//|                                  Copyright 2024, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"
class CDeal : public CObject
  {
private:

public:
                     CDeal();
                    ~CDeal();
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CDeal::CDeal()
  {
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CDeal::~CDeal()
  {
  }
//+------------------------------------------------------------------+


CObjectライブラリの標準オブジェクトクラスとCCanvasクラスのファイルを、取引オブジェクトクラスの作成されたファイルにインクルードして取引オブジェクトのプロパティで並べ替えるための列挙を記述します

//+------------------------------------------------------------------+
//|                                                         Deal.mqh |
//|                                  Copyright 2024, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"

#include <Object.mqh>
#include <Canvas\Canvas.mqh>

enum ENUM_DEAL_SORT_MODE
  {
   SORT_MODE_DEAL_TICKET = 0,          // Mode of comparing/sorting by a deal ticket
   SORT_MODE_DEAL_ORDER,               // Mode of comparing/sorting by the order a deal is based on
   SORT_MODE_DEAL_TIME,                // Mode of comparing/sorting by a deal time
   SORT_MODE_DEAL_TIME_MSC,            // Mode of comparing/sorting by a deal time in milliseconds
   SORT_MODE_DEAL_TYPE,                // Mode of comparing/sorting by a deal type
   SORT_MODE_DEAL_ENTRY,               // Mode of comparing/sorting by a deal direction
   SORT_MODE_DEAL_MAGIC,               // Mode of comparing/sorting by a deal magic number
   SORT_MODE_DEAL_REASON,              // Mode of comparing/sorting by a deal reason or source
   SORT_MODE_DEAL_POSITION_ID,         // Mode of comparing/sorting by a position ID
   SORT_MODE_DEAL_VOLUME,              // Mode of comparing/sorting by a deal volume
   SORT_MODE_DEAL_PRICE,               // Mode of comparing/sorting by a deal price
   SORT_MODE_DEAL_COMMISSION,          // Mode of comparing/sorting by commission
   SORT_MODE_DEAL_SWAP,                // Mode of comparing/sorting by accumulated swap on close
   SORT_MODE_DEAL_PROFIT,              // Mode of comparing/sorting by a deal financial result
   SORT_MODE_DEAL_FEE,                 // Mode of comparing/sorting by a deal fee
   SORT_MODE_DEAL_SL,                  // Mode of comparing/sorting by Stop Loss level
   SORT_MODE_DEAL_TP,                  // Mode of comparing/sorting by Take Profit level
   SORT_MODE_DEAL_SYMBOL,              // Mode of comparing/sorting by a name of a traded symbol
   SORT_MODE_DEAL_COMMENT,             // Mode of comparing/sorting by a deal comment
   SORT_MODE_DEAL_EXTERNAL_ID,         // Mode of comparing/sorting by a deal ID in an external trading system
  };
//+------------------------------------------------------------------+
//| Deal class                                                       |
//+------------------------------------------------------------------+
class CDeal : public CObject
  {
  }

クラスが動作するために必要なすべての変数とメソッドをprivate、protected、publicセクションで宣言します。

//+------------------------------------------------------------------+
//| Deal class                                                       |
//+------------------------------------------------------------------+
class CDeal : public CObject
  {
private:
   MqlTick           m_tick;           // Deal tick structure
//--- CCanvas object
   CCanvas           m_canvas;         // Canvas
   long              m_chart_id;       // Chart ID
   int               m_width;          // Canvas width
   int               m_height;         // Canvas height
   string            m_name;           // Graphical object name
   
//--- Create a label object on the chart
   bool              CreateLabelObj(void);

//--- Draw (1) a mask, (2) Buy arrow on the canvas
   void              DrawArrowMaskBuy(const int shift_x, const int shift_y);
   void              DrawArrowBuy(const int shift_x, const int shift_y);
   
//--- Draw (1) a mask, (2) Sell arrow on the canvas
   void              DrawArrowMaskSell(const int shift_x, const int shift_y);
   void              DrawArrowSell(const int shift_x, const int shift_y);
   
//--- Draw the label appearance
   void              DrawLabelView(void);
   
//--- Get a (1) deal tick and (2) a spread of the deal minute bar
   bool              GetDealTick(const int amount=20);
   int               GetSpreadM1(void);

//--- Return time with milliseconds
   string            TimeMscToString(const long time_msc,int flags=TIME_DATE|TIME_MINUTES|TIME_SECONDS) const;
   
protected:
//--- Integer properties
   long              m_ticket;            // Deal ticket. Unique number assigned to each deal
   long              m_order;             // Deal order number
   datetime          m_time;              // Deal execution time
   long              m_time_msc;          // Deal execution time in milliseconds since 01.01.1970
   ENUM_DEAL_TYPE    m_type;              // Deal type
   ENUM_DEAL_ENTRY   m_entry;             // Deal entry - entry in, entry out, reverse
   long              m_magic;             // Magic number for a deal (see ORDER_MAGIC)
   ENUM_DEAL_REASON  m_reason;            // Deal execution reason or source
   long              m_position_id;       // The ID of the position opened, modified or closed by the deal
   
//--- Real properties
   double            m_volume;            // Deal volume
   double            m_price;             // Deal price
   double            m_commission;        // Deal commission
   double            m_swap;              // Accumulated swap when closing
   double            m_profit;            // Deal financial result
   double            m_fee;               // Fee for making a deal charged immediately after performing a deal
   double            m_sl;                // Stop Loss level
   double            m_tp;                // Take Profit level

//--- String properties
   string            m_symbol;            // Name of the symbol for which the deal is executed
   string            m_comment;           // Deal comment
   string            m_external_id;       // Deal ID in an external trading system (on the exchange)
   
//--- Additional properties
   int               m_digits;            // Symbol digits
   double            m_point;             // Symbol point
   double            m_bid;               // Bid when performing a deal
   double            m_ask;               // Ask when performing a deal
   int               m_spread;            // Spread when performing a deal
   color             m_color_arrow;       // Deal label color
   
//--- Draws an arrow corresponding to the deal type. It can be redefined in the inherited classes
   virtual void      DrawArrow(void);

public:
//--- Set the properties
//--- Integer properties
   void              SetTicket(const long ticket)              { this.m_ticket=ticket;       }  // Ticket
   void              SetOrder(const long order)                { this.m_order=order;         }  // Order
   void              SetTime(const datetime time)              { this.m_time=time;           }  // Time
   void              SetTimeMsc(const long value)              { this.m_time_msc=value;      }  // Time in milliseconds
   void              SetTypeDeal(const ENUM_DEAL_TYPE type)    { this.m_type=type;           }  // Type
   void              SetEntry(const ENUM_DEAL_ENTRY entry)     { this.m_entry=entry;         }  // Direction
   void              SetMagic(const long magic)                { this.m_magic=magic;         }  // Magic number
   void              SetReason(const ENUM_DEAL_REASON reason)  { this.m_reason=reason;       }  // Deal execution reason or source
   void              SetPositionID(const long id)              { this.m_position_id=id;      }  // Position ID
   
//--- Real properties
   void              SetVolume(const double volume)            { this.m_volume=volume;       }  // Volume
   void              SetPrice(const double price)              { this.m_price=price;         }  // Price
   void              SetCommission(const double value)         { this.m_commission=value;    }  // Commission
   void              SetSwap(const double value)               { this.m_swap=value;          }  // Accumulated swap when closing
   void              SetProfit(const double value)             { this.m_profit=value;        }  // Financial result
   void              SetFee(const double value)                { this.m_fee=value;           }  // Deal fee
   void              SetSL(const double value)                 { this.m_sl=value;            }  // Stop Loss level
   void              SetTP(const double value)                 { this.m_tp=value;            }  // Take Profit level

//--- String properties
   void              SetSymbol(const string symbol)            { this.m_symbol=symbol;       }  // Symbol name
   void              SetComment(const string comment)          { this.m_comment=comment;     }  // Comment
   void              SetExternalID(const string ext_id)        { this.m_external_id=ext_id;  }  // Deal ID in an external trading system

//--- Get the properties
//--- Integer properties
   long              Ticket(void)                        const { return(this.m_ticket);      }  // Ticket
   long              Order(void)                         const { return(this.m_order);       }  // Order
   datetime          Time(void)                          const { return(this.m_time);        }  // Time
   long              TimeMsc(void)                       const { return(this.m_time_msc);    }  // Time in milliseconds
   ENUM_DEAL_TYPE    TypeDeal(void)                      const { return(this.m_type);        }  // Type
   ENUM_DEAL_ENTRY   Entry(void)                         const { return(this.m_entry);       }  // Direction
   long              Magic(void)                         const { return(this.m_magic);       }  // Magic number
   ENUM_DEAL_REASON  Reason(void)                        const { return(this.m_reason);      }  // Deal execution reason or source
   long              PositionID(void)                    const { return(this.m_position_id); }  // Position ID
   
//--- Real properties
   double            Volume(void)                        const { return(this.m_volume);      }  // Volume
   double            Price(void)                         const { return(this.m_price);       }  // Price
   double            Commission(void)                    const { return(this.m_commission);  }  // Commission
   double            Swap(void)                          const { return(this.m_swap);        }  // Accumulated swap when closing
   double            Profit(void)                        const { return(this.m_profit);      }  // Financial result
   double            Fee(void)                           const { return(this.m_fee);         }  // Deal fee
   double            SL(void)                            const { return(this.m_sl);          }  // Stop Loss level
   double            TP(void)                            const { return(this.m_tp);          }  // Take Profit level
   
   double            Bid(void)                           const { return(this.m_bid);         }  // Bid when performing a deal
   double            Ask(void)                           const { return(this.m_ask);         }  // Ask when performing a deal
   int               Spread(void)                        const { return(this.m_spread);      }  // Spread when performing a deal
   
//--- String properties
   string            Symbol(void)                        const { return(this.m_symbol);      }  // Symbol name
   string            Comment(void)                       const { return(this.m_comment);     }  // Comment
   string            ExternalID(void)                    const { return(this.m_external_id); }  // Deal ID in an external trading system
   
//--- Set the color of the deal label
   void              SetColorArrow(const color clr);

//--- (1) Hide, (2) display the deal label on a chart
   void              HideArrow(const bool chart_redraw=false);
   void              ShowArrow(const bool chart_redraw=false);

//--- Return the description of a (1) deal type, (2) position change method and (3) deal reason
   string            TypeDescription(void)   const;
   string            EntryDescription(void)  const;
   string            ReasonDescription(void) const;
   
//--- Return (1) a short description and (2) a tooltip text of a deal
   string            Description(void);
   virtual string    Tooltip(void);

//--- Print deal properties in the journal
   void              Print(void);
   
//--- Compare two objects by the property specified in 'mode'
   virtual int       Compare(const CObject *node, const int mode=0) const;
   
//--- Constructors/destructor
                     CDeal(void) { this.m_ticket=0; }
                     CDeal(const ulong ticket);
                    ~CDeal();
  };

宣言されたメソッドについて詳しく考えてみましょう。


以下は、パラメトリックコンストラクタです。

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CDeal::CDeal(const ulong ticket)
  {
//--- Store the properties
//--- Integer properties
   this.m_ticket     =  (long)ticket;                                                     // Deal ticket
   this.m_order      =  ::HistoryDealGetInteger(ticket, DEAL_ORDER);                      // Order
   this.m_time       =  (datetime)::HistoryDealGetInteger(ticket, DEAL_TIME);             // Deal execution time
   this.m_time_msc   =  ::HistoryDealGetInteger(ticket, DEAL_TIME_MSC);                   // Deal execution time in milliseconds
   this.m_type       =  (ENUM_DEAL_TYPE)::HistoryDealGetInteger(ticket, DEAL_TYPE);       // Type
   this.m_entry      =  (ENUM_DEAL_ENTRY)::HistoryDealGetInteger(ticket, DEAL_ENTRY);     // Direction
   this.m_magic      =  ::HistoryDealGetInteger(ticket, DEAL_MAGIC);                      // Magic number
   this.m_reason     =  (ENUM_DEAL_REASON)::HistoryDealGetInteger(ticket, DEAL_REASON);   // Deal execution reason or source
   this.m_position_id=  ::HistoryDealGetInteger(ticket, DEAL_POSITION_ID);                // Position ID
   
//--- Real properties
   this.m_volume     =  ::HistoryDealGetDouble(ticket, DEAL_VOLUME);                      // Volume
   this.m_price      =  ::HistoryDealGetDouble(ticket, DEAL_PRICE);                       // Price
   this.m_commission =  ::HistoryDealGetDouble(ticket, DEAL_COMMISSION);                  // Commission
   this.m_swap       =  ::HistoryDealGetDouble(ticket, DEAL_SWAP);                        // Accumulated swap when closing
   this.m_profit     =  ::HistoryDealGetDouble(ticket, DEAL_PROFIT);                      // Financial result
   this.m_fee        =  ::HistoryDealGetDouble(ticket, DEAL_FEE);                         // Deal fee
   this.m_sl         =  ::HistoryDealGetDouble(ticket, DEAL_SL);                          // Stop Loss level
   this.m_tp         =  ::HistoryDealGetDouble(ticket, DEAL_TP);                          // Take Profit level

//--- String properties
   this.m_symbol     =  ::HistoryDealGetString(ticket, DEAL_SYMBOL);                      // Symbol name
   this.m_comment    =  ::HistoryDealGetString(ticket, DEAL_COMMENT);                     // Comment
   this.m_external_id=  ::HistoryDealGetString(ticket, DEAL_EXTERNAL_ID);                 // Deal ID in an external trading system

//--- Graphics display parameters
   this.m_chart_id   =  ::ChartID();
   this.m_digits     =  (int)::SymbolInfoInteger(this.m_symbol, SYMBOL_DIGITS);
   this.m_point      =  ::SymbolInfoDouble(this.m_symbol, SYMBOL_POINT);
   this.m_width      =  19;
   this.m_height     =  19;
   this.m_name       =  "Deal#"+(string)this.m_ticket;
   this.m_color_arrow=  (this.TypeDeal()==DEAL_TYPE_BUY ? C'3,95,172' : this.TypeDeal()==DEAL_TYPE_SELL ? C'225,68,29' : C'180,180,180');
   
//--- Parameters for calculating spread
   this.m_spread     =  0;
   this.m_bid        =  0;
   this.m_ask        =  0;
   
//--- Create a graphic label
   this.CreateLabelObj();
   
//--- If the historical tick and the Point value of the symbol were obtained
   if(this.GetDealTick() && this.m_point!=0)
     {
      //--- set the Bid and Ask price values, calculate and save the spread value
      this.m_bid=this.m_tick.bid;
      this.m_ask=this.m_tick.ask;
      this.m_spread=int((this.m_ask-this.m_bid)/this.m_point);
     }
//--- If failed to obtain a historical tick, take the spread value of the minute bar the deal took place on
   else
      this.m_spread=this.GetSpreadM1();
  }

現在の取引はすでに選択されていることに注意してください。したがって、ここでは、取引プロパティからすべてのオブジェクトプロパティをすぐに入力します。次に、チャートに取引ラベルを表示するためのキャンバスパラメータを設定し、このラベルのグラフィカルオブジェクトを作成し、取引時間によるティックを取得して、取引時に存在するスプレッドを計算します。ティックを取得できなかった場合は、取引がおこなわれた分足の平均スプレッドを取得します。

その結果、取引オブジェクトを作成すると、チケットに応じて設定された履歴取引のプロパティを持つオブジェクトと、チャート上で取引を視覚化するための作成済みの非表示のラベル、および取引時のスプレッド値がすぐに取得されます。


クラスデストラクタで、初期化解除の理由を確認します。これがチャートの時間枠の変更ではない場合は、グラフィカルラベルオブジェクトのグラフィカルリソースを破棄します。

//+------------------------------------------------------------------+
//| Destructor                                                       |
//+------------------------------------------------------------------+
CDeal::~CDeal()
  {
   if(::UninitializeReason()!=REASON_CHARTCHANGE)
      this.m_canvas.Destroy();
  }


以下は、指定されたプロパティによって2つのオブジェクトを相互に比較する仮想メソッドです。

//+------------------------------------------------------------------+
//| Compare two objects by the specified property                    |
//+------------------------------------------------------------------+
int CDeal::Compare(const CObject *node,const int mode=0) const
  {
   const CDeal * obj = node;
   switch(mode)
     {
      case SORT_MODE_DEAL_TICKET       :  return(this.Ticket() > obj.Ticket()          ?  1  :  this.Ticket() < obj.Ticket()           ? -1  :  0);
      case SORT_MODE_DEAL_ORDER        :  return(this.Order() > obj.Order()            ?  1  :  this.Order() < obj.Order()             ? -1  :  0);
      case SORT_MODE_DEAL_TIME         :  return(this.Time() > obj.Time()              ?  1  :  this.Time() < obj.Time()               ? -1  :  0);
      case SORT_MODE_DEAL_TIME_MSC     :  return(this.TimeMsc() > obj.TimeMsc()        ?  1  :  this.TimeMsc() < obj.TimeMsc()         ? -1  :  0);
      case SORT_MODE_DEAL_TYPE         :  return(this.TypeDeal() > obj.TypeDeal()      ?  1  :  this.TypeDeal() < obj.TypeDeal()       ? -1  :  0);
      case SORT_MODE_DEAL_ENTRY        :  return(this.Entry() > obj.Entry()            ?  1  :  this.Entry() < obj.Entry()             ? -1  :  0);
      case SORT_MODE_DEAL_MAGIC        :  return(this.Magic() > obj.Magic()            ?  1  :  this.Magic() < obj.Magic()             ? -1  :  0);
      case SORT_MODE_DEAL_REASON       :  return(this.Reason() > obj.Reason()          ?  1  :  this.Reason() < obj.Reason()           ? -1  :  0);
      case SORT_MODE_DEAL_POSITION_ID  :  return(this.PositionID() > obj.PositionID()  ?  1  :  this.PositionID() < obj.PositionID()   ? -1  :  0);
      case SORT_MODE_DEAL_VOLUME       :  return(this.Volume() > obj.Volume()          ?  1  :  this.Volume() < obj.Volume()           ? -1  :  0);
      case SORT_MODE_DEAL_PRICE        :  return(this.Price() > obj.Price()            ?  1  :  this.Price() < obj.Price()             ? -1  :  0);
      case SORT_MODE_DEAL_COMMISSION   :  return(this.Commission() > obj.Commission()  ?  1  :  this.Commission() < obj.Commission()   ? -1  :  0);
      case SORT_MODE_DEAL_SWAP         :  return(this.Swap() > obj.Swap()              ?  1  :  this.Swap() < obj.Swap()               ? -1  :  0);
      case SORT_MODE_DEAL_PROFIT       :  return(this.Profit() > obj.Profit()          ?  1  :  this.Profit() < obj.Profit()           ? -1  :  0);
      case SORT_MODE_DEAL_FEE          :  return(this.Fee() > obj.Fee()                ?  1  :  this.Fee() < obj.Fee()                 ? -1  :  0);
      case SORT_MODE_DEAL_SL           :  return(this.SL() > obj.SL()                  ?  1  :  this.SL() < obj.SL()                   ? -1  :  0);
      case SORT_MODE_DEAL_TP           :  return(this.TP() > obj.TP()                  ?  1  :  this.TP() < obj.TP()                   ? -1  :  0);
      case SORT_MODE_DEAL_SYMBOL       :  return(this.Symbol() > obj.Symbol()          ?  1  :  this.Symbol() < obj.Symbol()           ? -1  :  0);
      case SORT_MODE_DEAL_COMMENT      :  return(this.Comment() > obj.Comment()        ?  1  :  this.Comment() < obj.Comment()         ? -1  :  0);
      case SORT_MODE_DEAL_EXTERNAL_ID  :  return(this.ExternalID() > obj.ExternalID()  ?  1  :  this.ExternalID() < obj.ExternalID()   ? -1  :  0);
      default                             :  return(-1);
     }
  }

このメソッドは、比較対象オブジェクトへのポインタとENUM_DEAL_SORT_MODE列挙体からの比較対象プロパティの値を受け取ります。現在のオブジェクトの指定されたプロパティの値が比較対象オブジェクトのプロパティの値より大きい場合は1、小さい場合は-1、それ以外の場合は0が返されます。


以下は、取引の種類の説明を返すメソッドです。

//+------------------------------------------------------------------+
//| Return the deal type description                                 |
//+------------------------------------------------------------------+
string CDeal::TypeDescription(void) const
  {
   switch(this.m_type)
     {
      case DEAL_TYPE_BUY                     :  return "Buy";
      case DEAL_TYPE_SELL                    :  return "Sell";
      case DEAL_TYPE_BALANCE                 :  return "Balance";
      case DEAL_TYPE_CREDIT                  :  return "Credit";
      case DEAL_TYPE_CHARGE                  :  return "Additional charge";
      case DEAL_TYPE_CORRECTION              :  return "Correction";
      case DEAL_TYPE_BONUS                   :  return "Bonus";
      case DEAL_TYPE_COMMISSION              :  return "Additional commission";
      case DEAL_TYPE_COMMISSION_DAILY        :  return "Daily commission";
      case DEAL_TYPE_COMMISSION_MONTHLY      :  return "Monthly commission";
      case DEAL_TYPE_COMMISSION_AGENT_DAILY  :  return "Daily agent commission";
      case DEAL_TYPE_COMMISSION_AGENT_MONTHLY:  return "Monthly agent commission";
      case DEAL_TYPE_INTEREST                :  return "Interest rate";
      case DEAL_TYPE_BUY_CANCELED            :  return "Canceled buy deal";
      case DEAL_TYPE_SELL_CANCELED           :  return "Canceled sell deal";
      case DEAL_DIVIDEND                     :  return "Dividend operations";
      case DEAL_DIVIDEND_FRANKED             :  return "Franked (non-taxable) dividend operations";
      case DEAL_TAX                          :  return "Tax charges";
      default                                :  return "Unknown: "+(string)this.m_type;
     }
  }

すべての取引の種類の説明がここに返されます。それらのほとんどはプログラムでは使用されません。ただし、このメソッドは、クラスの拡張と継承の可能性を考慮して、すべてのタイプを返します。


以下は、ポジション変更メソッドの説明を返すメソッドです。

//+------------------------------------------------------------------+
//| Return position change method                                    |
//+------------------------------------------------------------------+
string CDeal::EntryDescription(void) const
  {
   switch(this.m_entry)
     {
      case DEAL_ENTRY_IN      :  return "Entry In";
      case DEAL_ENTRY_OUT     :  return "Entry Out";
      case DEAL_ENTRY_INOUT   :  return "Reverse";
      case DEAL_ENTRY_OUT_BY  :  return "Close a position by an opposite one";
      default                 :  return "Unknown: "+(string)this.m_entry;
     }
  }


以下は、取引理由の説明を返すメソッドです。

//+------------------------------------------------------------------+
//| Return a deal reason description                                 |
//+------------------------------------------------------------------+
string CDeal::ReasonDescription(void) const
  {
   switch(this.m_reason)
     {
      case DEAL_REASON_CLIENT          :  return "Terminal";
      case DEAL_REASON_MOBILE          :  return "Mobile";
      case DEAL_REASON_WEB             :  return "Web";
      case DEAL_REASON_EXPERT          :  return "EA";
      case DEAL_REASON_SL              :  return "SL";
      case DEAL_REASON_TP              :  return "TP";
      case DEAL_REASON_SO              :  return "SO";
      case DEAL_REASON_ROLLOVER        :  return "Rollover";
      case DEAL_REASON_VMARGIN         :  return "Var. Margin";
      case DEAL_REASON_SPLIT           :  return "Split";
      case DEAL_REASON_CORPORATE_ACTION:  return "Corp. Action";
      default                          :  return "Unknown reason "+(string)this.m_reason;
     }
  }

このメソッドでは、StopLoss、TakeProfit、StopOutによるクローズの3つの値のみを使用します。


以下は、取引の説明を返すメソッドです。

//+------------------------------------------------------------------+
//| Return deal description                                          |
//+------------------------------------------------------------------+
string CDeal::Description(void)
  {
   return(::StringFormat("Deal: %-9s %.2f %-4s #%I64d at %s", this.EntryDescription(), this.Volume(), this.TypeDescription(), this.Ticket(), this.TimeMscToString(this.TimeMsc())));
  }

StringFormat()関数を使用して、次の文字列を作成して返します。

Deal: Entry In  0.10 Buy  #1728374638 at 2023.06.12 16:51:36.838


以下は、取引ポップアップメッセージのテキストを返す仮想メソッドです。

//+------------------------------------------------------------------+
//| Returns a text of a deal pop-up message                          |
//+------------------------------------------------------------------+
string CDeal::Tooltip(void)
  {
   return(::StringFormat("Position ID #%I64d %s:\nDeal #%I64d %.2f %s %s\n%s [%.*f]\nProfit: %.2f, SL: %.*f, TP: %.*f",
                         this.PositionID(), this.Symbol(), this.Ticket(), this.Volume(), this.TypeDescription(),
                         this.EntryDescription(), this.TimeMscToString(this.TimeMsc()), this.m_digits, this.Price(),
                         this.Profit(), this.m_digits, this.SL(), this.m_digits, this.TP()));
  }

上で説明したメソッドと同様に、ここでは次のタイプのテキスト文字列が形成され、返されます。

Position ID #1752955040 EURUSD:
Deal #1728430603 0.10 Sell Entry Out
2023.06.12 17:04:20.362 [1.07590]
Profit: 15.00, SL: 1.07290, TP: 1.07590

この文字列は、後でチャート上の取引ラベルの上にマウスポインタを置いたときに、ツールチップに取引の説明を表示するために使用されます。このメソッドは継承されたクラスで再定義して、他の取引情報を出力することができます。


以下は、操作ログに取引プロパティを出力するメソッドです。

//+------------------------------------------------------------------+
//| Print deal properties in the journal                             |
//+------------------------------------------------------------------+
void CDeal::Print(void)
  {
   ::PrintFormat("  %s", this.Description());
  }

このメソッドは、すべての取引プロパティを出力するのではなく、上で説明したDescription()メソッドによって返された取引に関する最小限の情報のみを表示します。ポジションの説明を表示するときに情報を整理するために、取引の説明文字列の前に2つのスペースが追加されます。ポジションの説明ヘッダーの後にポジション取引のデータが続きます

Position EURUSD 0.10 Buy #1752955040, Magic 0
-Opened 2023.06.12 16:51:36.838 [1.07440]
-Closed 2023.06.12 17:04:20.362 [1.07590]
  Deal: Entry In  0.10 Buy  #1728374638 at 2023.06.12 16:51:36.838
  Deal: Entry Out 0.10 Sell #1728430603 at 2023.06.12 17:04:20.362

PrintFormat()関数とStringFormat()関数の詳細については、「PrintFormat()を調べてすぐ使える例を適用する」および「StringFormat():レビューと既成の例」をご覧ください。


以下は、ミリ秒単位で時間を返すメソッドです。

//+------------------------------------------------------------------+
//| Return time with milliseconds                                    |
//+------------------------------------------------------------------+
string CDeal::TimeMscToString(const long time_msc, int flags=TIME_DATE|TIME_MINUTES|TIME_SECONDS) const
  {
   return(::TimeToString(time_msc/1000, flags) + "." + ::IntegerToString(time_msc %1000, 3, '0'));
  }

ここでは、1000で割って通常の時間に変換されたミリ秒単位の時間値を含む文字列を形成します。ミリ秒単位の時間を1000で割った余りとして得られたミリ秒数が、結果の文字列に点の後に追加されます。ミリ秒の文字列は3桁の数値としてフォーマットされ、3桁より短い場合は先頭に0が追加されます。その結果、時間の表現は次のようになります。

2023.06.12 17:04:20.362

取引時のスプレッドがどのようなものであったかを理解するためには、取引時間に必要なティックをミリ秒単位で取得する必要があります。標準のCopyTicks()関数を使用して既知の時間に1つのティックをコピーするという一見些細なタスクは、指定された正確な時間にティックをコピーすることができなかったため、解決策を見つけるための小さな探求につながりました。結果として、解決策を少し探した後、必要なアルゴリズムを見つけることができました。取引時間までの「開始」から拡大し続ける時間範囲で、一定数のティックリクエストをおこなう必要があります。詳細はこちら(ロシア語)をご覧ください。


以下は、取引のティックを取得するメソッドです。

//+------------------------------------------------------------------+
//| Get the deal tick                                                |
//+------------------------------------------------------------------+
bool CDeal::GetDealTick(const int amount=20)
  {
   MqlTick ticks[];        // We will receive ticks here
   int attempts = amount;  // Number of attempts to get ticks
   int offset = 500;       // Initial time offset for an attempt
   int copied = 0;         // Number of ticks copied
   
//--- Until the tick is copied and the number of copy attempts is over
//--- we try to get a tick, doubling the initial time offset at each iteration (expand the "from_msc" time range)
   while(!::IsStopped() && (copied<=0) && (attempts--)!=0)
      copied = ::CopyTicksRange(this.m_symbol, ticks, COPY_TICKS_INFO, this.m_time_msc-(offset <<=1), this.m_time_msc);
    
//--- If the tick was successfully copied (it is the last one in the tick array), set it to the m_tick variable
   if(copied>0)
      this.m_tick=ticks[copied-1];

//--- Return the flag that the tick was copied
   return(copied>0);
  }

ティックを受け取った後、そこからAskおよびBid価格が取得され、スプレッドサイズは(Ask - Bid) / ポイントとして計算されます。

このメソッドを使用してティックを取得できなかった場合は、取引分足のスプレッドを取得する方法を使用してスプレッドの平均値を取得します。

//+------------------------------------------------------------------+
//| Gets the spread of the deal minute bar                           |
//+------------------------------------------------------------------+
int CDeal::GetSpreadM1(void)
  {
   int array[1]={};
   int bar=::iBarShift(this.m_symbol, PERIOD_M1, this.Time());
   if(bar==WRONG_VALUE)
      return 0;
   return(::CopySpread(this.m_symbol, PERIOD_M1, bar, 1, array)==1 ? array[0] : 0);
  }

ここでは、取引時間によって分足バーの開始時間を取得します。これは、CopySpread()関数を使用してバーの平均スプレッドを取得するために使用されます。バーまたはスプレッドの取得中にエラーが発生した場合、メソッドは0を返します。


以下は、チャート上にラベルオブジェクトを作成するメソッドです。

//+------------------------------------------------------------------+
//| Create a label object on the chart                               |
//+------------------------------------------------------------------+
bool CDeal::CreateLabelObj(void)
  {
//--- Create a graphical resource with a Bitmap object attached to it
   ::ResetLastError();
   if(!this.m_canvas.CreateBitmap(this.m_name, this.m_time, this.m_price, this.m_width, this.m_height, COLOR_FORMAT_ARGB_NORMALIZE))
     {
      ::PrintFormat("%s: When creating a graphic object, error %d occurred in the CreateBitmap method of the CCanvas class",__FUNCTION__, ::GetLastError());
      return false;
     }
//--- If the graphical resource is successfully created, set the Bitmap object, anchor point, price, time and tooltip text
   ::ObjectSetInteger(this.m_chart_id, this.m_name, OBJPROP_ANCHOR, ANCHOR_CENTER);
   ::ObjectSetInteger(this.m_chart_id, this.m_name, OBJPROP_TIME, this.Time());
   ::ObjectSetDouble(this.m_chart_id, this.m_name, OBJPROP_PRICE, this.Price());
   ::ObjectSetString(this.m_chart_id, this.m_name, OBJPROP_TOOLTIP, this.Tooltip());
   
//--- Hide the created object from the chart and draw its appearance on it
   this.HideArrow();
   this.DrawLabelView();
   return true;
  }

グラフィカルリソースを作成し、そのアンカーポイントを中央に設定し、取引価格と時間、およびツールチップテキストを設定します。次に、作成されたオブジェクトに、すべてのチャート期間で非表示であることを示すフラグが設定され、その外観が描画されます。これを表示するには、すべての時間枠でオブジェクトの可視性フラグを設定する必要があります。


以下は、ラベルオブジェクトの外観を描画するメソッドです。

//+------------------------------------------------------------------+
//| Draw the appearance of the label object                          |
//+------------------------------------------------------------------+
void CDeal::DrawLabelView(void)
  {
   this.m_canvas.Erase(0x00FFFFFF);
   this.DrawArrow();
   this.m_canvas.Update(true);
  }

まず、ビットマップグラフィカルオブジェクトが完全に透明な色で塗りつぶされ、次に取引の種類に対応する矢印がその上に描画され、その後、チャートが再描画されるときにキャンバスが更新されます。


以下は、取引の種類に対応する矢印を描画する仮想メソッドです。

//+------------------------------------------------------------------+
//| Draw an arrow corresponding to the deal type                     |
//+------------------------------------------------------------------+
void CDeal::DrawArrow(void)
  {
   switch(this.TypeDeal())
     {
      case DEAL_TYPE_BUY   :  this.DrawArrowBuy(5, 10);  break;
      case DEAL_TYPE_SELL  :  this.DrawArrowSell(5, 0);  break;
      default              :  break;
     }
  }

取引の種類(買いまたは売り)に応じて、対応する矢印を描画するメソッドが呼び出されます。このメソッドは仮想なので、継承されたクラスで再定義できます。例えば、子クラスでは、異なる種類の取引に対して異なるラベルを描画したり、ポジションの変更方法などを追加で考慮したりすることができます。


以下は、キャンバスに買い矢印マスクを描画するメソッドです。

//+------------------------------------------------------------------+
//| Draw Buy arrow mask on the canvas                                |
//+------------------------------------------------------------------+
void CDeal::DrawArrowMaskBuy(const int shift_x, const int shift_y)
  {
   int x[]={4+shift_x, 8+shift_x, 8+shift_x, 6+shift_x, 6+shift_x, 2+shift_x, 2+shift_x, 0+shift_x, 0+shift_x, 4+shift_x};
   int y[]={0+shift_y, 4+shift_y, 5+shift_y, 5+shift_y, 7+shift_y, 7+shift_y, 5+shift_y, 5+shift_y, 4+shift_y, 0+shift_y};
   this.m_canvas.Polygon(x, y, ::ColorToARGB(clrWhite, 220));
  }

取引ラベルとして描画された矢印には、ローソク足の暗い背景に対して目立つように、周囲に白いアウトライン(マスク)が付いています。

キャンバスに描画される図形の座標は常にキャンバスのローカル座標を基準として指定されるため、描画される図形をキャンバス内で正確に中央に配置するには、X軸とY軸に沿った破線ポイントの座標に追加されるオフセットを入力する必要があります。次に、メソッドに渡されたオフセットを考慮して、X座標とY座標の値が配列に設定され、Polygon()メソッドが呼び出されて、座標点を使用して白いアウトラインが描画されます。次に、矢印描画方法を使用して、アウトラインの内側に矢印を描画します。

以下は、キャンバス上に買い矢印を描画するメソッドです。

//+------------------------------------------------------------------+
//| Draw Buy arrow on the canvas                                     |
//+------------------------------------------------------------------+
void CDeal::DrawArrowBuy(const int shift_x, const int shift_y)
  {
   this.DrawArrowMaskBuy(shift_x, shift_y);
   int x[]={4+shift_x, 7+shift_x, 5+shift_x, 5+shift_x, 3+shift_x, 3+shift_x, 1+shift_x, 4+shift_x};
   int y[]={1+shift_y, 4+shift_y, 4+shift_y, 6+shift_y, 6+shift_y, 4+shift_y, 4+shift_y, 1+shift_y};
   this.m_canvas.Polygon(x, y, ::ColorToARGB(this.m_color_arrow));
   this.m_canvas.Fill(4+shift_x, 4+shift_y, ::ColorToARGB(this.m_color_arrow));
  }

ここでは、最初に買い矢印マスクを描画し、次に指定された座標を使用して買い矢印を描画し、その内部空間を色で塗りつぶします


以下は、同様に売り矢印を描くメソッドです。

//+------------------------------------------------------------------+
//| Draw Sell arrow mask on the canvas                               |
//+------------------------------------------------------------------+
void CDeal::DrawArrowMaskSell(const int shift_x, const int shift_y)
  {
   int x[]={4+shift_x, 0+shift_x, 0+shift_x, 2+shift_x, 2+shift_x, 6+shift_x, 6+shift_x, 8+shift_x, 8+shift_x, 4+shift_x};
   int y[]={8+shift_y, 4+shift_y, 3+shift_y, 3+shift_y, 1+shift_y, 1+shift_y, 3+shift_y, 3+shift_y, 4+shift_y, 8+shift_y};
   this.m_canvas.Polygon(x, y, ::ColorToARGB(clrWhite, 220));
  }
//+------------------------------------------------------------------+
//| Draw Sell arrow on the canvas                                    |
//+------------------------------------------------------------------+
void CDeal::DrawArrowSell(const int shift_x, const int shift_y)
  {
   this.DrawArrowMaskSell(shift_x, shift_y);
   int x[]={4+shift_x, 1+shift_x, 3+shift_x, 3+shift_x, 5+shift_x, 5+shift_x, 7+shift_x, 4+shift_x};
   int y[]={7+shift_y, 4+shift_y, 4+shift_y, 2+shift_y, 2+shift_y, 4+shift_y, 4+shift_y, 7+shift_y};
   this.m_canvas.Polygon(x, y, ::ColorToARGB(this.m_color_arrow));
   this.m_canvas.Fill(4+shift_x, 4+shift_y, ::ColorToARGB(this.m_color_arrow));
  }


以下は、チャート上で取引ラベルを非表示および表示するメソッドです。

//+------------------------------------------------------------------+
//| Hide the deal label on the chart                                 |
//+------------------------------------------------------------------+
void CDeal::HideArrow(const bool chart_redraw=false)
  {
   ::ObjectSetInteger(this.m_chart_id, this.m_name, OBJPROP_TIMEFRAMES, OBJ_NO_PERIODS);
   if(chart_redraw)
      ::ChartRedraw(this.m_chart_id);
  }
//+------------------------------------------------------------------+
//| Display the deal label on the chart                              |
//+------------------------------------------------------------------+
void CDeal::ShowArrow(const bool chart_redraw=false)
  {
   ::ObjectSetInteger(this.m_chart_id, this.m_name, OBJPROP_TIMEFRAMES, OBJ_ALL_PERIODS);
   if(chart_redraw)
      ::ChartRedraw(this.m_chart_id);
  }

チャート上のオブジェクトを非表示にするには、グラフィカルオブジェクトのOBJPROP_TIMEFRAMES可視性プロパティOBJ_NO_PERIODSに設定する必要があります。

したがって、オブジェクトを表示するには、OBJPROP_TIMEFRAMESプロパティをOBJ_ALL_PERIODSに設定する必要があります。


以下は、取引ラベルの色を設定するメソッドです。

//+------------------------------------------------------------------+
//| Set the deal label color                                         |
//+------------------------------------------------------------------+
void CDeal::SetColorArrow(const color clr)
  {
   this.m_color_arrow=clr;
   this.DrawLabelView();
  }

描画されたラベルの色を格納するm_color_arrow変数はメソッドに渡された値に設定され、グラフィカルオブジェクト全体が完全に再描画されます。したがって、チャート上の取引ラベルの色を制御プログラムから「オンザフライ」で変更することが可能です。

チケットによる過去の取引のプロパティへのアクセスを提供し、この取引に関する必要なデータを取得してチャート上でラベルを表示または非表示にできる取引クラスを作成しました。

クラスオブジェクトはポジションオブジェクト取引のリストに保存され、履歴ポジションのプロパティが表示され、取引とパラメータへのアクセスが提供され、線で接続された開始取引と終了取引が表示されます。


ポジションクラス

取引クラスが作成されたのと同じフォルダに、CPositionクラスのファイルPosition.mqhを新規作成します。

取引クラスと同様に、オブジェクトプロパティの列挙を実装して、ポジションプロパティで検索および並べ替え取引クラスファイルとポジションクラスのCArrayObjを接続します

//+------------------------------------------------------------------+
//|                                                     Position.mqh |
//|                                  Copyright 2024, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"

#include "Deal.mqh"
#include <Arrays\ArrayObj.mqh>

enum ENUM_POSITION_SORT_MODE
  {
   SORT_MODE_POSITION_TICKET = 0,      // Mode of comparing/sorting by a position ticket
   SORT_MODE_POSITION_TIME,            // Mode of comparing/sorting by position open time
   SORT_MODE_POSITION_TIME_MSC,        // Mode of comparing/sorting by position open time im milliseconds
   SORT_MODE_POSITION_TIME_UPDATE,     // Mode of comparing/sorting by position update time
   SORT_MODE_POSITION_TIME_UPDATE_MSC, // Mode of comparing/sorting by position update time im milliseconds
   SORT_MODE_POSITION_TYPE,            // Mode of comparing/sorting by position type
   SORT_MODE_POSITION_MAGIC,           // Mode of comparing/sorting by a position magic number
   SORT_MODE_POSITION_IDENTIFIER,      // Mode of comparing/sorting by a position ID
   SORT_MODE_POSITION_REASON,          // Mode of comparing/sorting by position open reason
   SORT_MODE_POSITION_VOLUME,          // Mode of comparing/sorting by a position volume
   SORT_MODE_POSITION_PRICE_OPEN,      // Mode of comparing/sorting by a position price
   SORT_MODE_POSITION_SL,              // Mode of comparing/sorting by Stop Loss level for an open position
   SORT_MODE_POSITION_TP,              // Mode of comparing/sorting by Take Profit level for an open position
   SORT_MODE_POSITION_PRICE_CURRENT,   // Mode of comparing/sorting by the current symbol price
   SORT_MODE_POSITION_SWAP,            // Mode of comparing/sorting by accumulated swap
   SORT_MODE_POSITION_PROFIT,          // Mode of comparing/sorting by the current profit
   SORT_MODE_POSITION_SYMBOL,          // Mode of comparing/sorting by a symbol a position is opened on
   SORT_MODE_POSITION_COMMENT,         // Mode of comparing/sorting by a position comment
   SORT_MODE_POSITION_EXTERNAL_ID,     // Mode of comparing/sorting by a position ID in an external system
   SORT_MODE_POSITION_TIME_CLOSE,      // Mode of comparing/sorting by a position open time
   SORT_MODE_POSITION_TIME_CLOSE_MSC,  // Mode of comparing/sorting by a position open time in milliseconds
   SORT_MODE_POSITION_PRICE_CLOSE,     // Mode of comparing/sorting by a position price
  };

//+------------------------------------------------------------------+
//| Position class                                                   |
//+------------------------------------------------------------------+
class CPosition : public CObject
  {
  }

クラスのprotectedセクションとpublicセクションに、クラスを処理するための変数とメソッドを入力します。

//+------------------------------------------------------------------+
//| Position class                                                   |
//+------------------------------------------------------------------+
class CPosition : public CObject
  {
private:

protected:
   CArrayObj         m_list_deals;        // List of position deals
   CDeal             m_temp_deal;         // Temporary deal object for searching by property in the list
   
//--- Return time with milliseconds
   string            TimeMscToString(const long time_msc,int flags=TIME_DATE|TIME_MINUTES|TIME_SECONDS) const;

//--- Integer properties
   long              m_ticket;            // Position ticket
   datetime          m_time;              // Position opening time
   long              m_time_msc;          // Position opening time in milliseconds since 01.01.1970 
   datetime          m_time_update;       // Position update time
   long              m_time_update_msc;   // Position update time in milliseconds since 01.01.1970
   ENUM_POSITION_TYPE m_type;             // Position type
   long              m_magic;             // Magic number for a position (see ORDER_MAGIC)
   long              m_identifier;        // Position ID
   ENUM_POSITION_REASON m_reason;         // Position opening reason
 
//--- Real properties
   double            m_volume;            // Position volume
   double            m_price_open;        // Position price
   double            m_sl;                // Stop Loss level for an open position
   double            m_tp;                // Take Profit level for an open position
   double            m_price_current;     // Current price by symbol
   double            m_swap;              // Accumulated swap
   double            m_profit;            // Current profit

//--- String properties
   string            m_symbol;            // A symbol the position is open for
   string            m_comment;           // Position comment
   string            m_external_id;       // Position ID in an external system (on the exchange) 

//--- Additional properties
   long              m_chart_id;          // Chart ID
   int               m_profit_pt;         // Profit in points
   int               m_digits;            // Symbol digits
   double            m_point;             // One symbol point value
   double            m_contract_size;     // Symbol trade contract size
   string            m_currency_profit;   // Symbol profit currency
   string            m_account_currency;  // Deposit currency
   
   string            m_line_name;         // Line graphical object name
   color             m_line_color;        // Connecting line color

//--- Create a line connecting open-close deals
   virtual bool      CreateLine(void);

//--- Return the pointer to (1) open and (2) close deal
   CDeal            *GetDealIn(void)   const;
   CDeal            *GetDealOut(void)  const;
   
//--- (1) Hide and (2) display deal labels on the chart
   void              HideDeals(const bool chart_redraw=false);
   void              ShowDeals(const bool chart_redraw=false);
   
//--- (1) Hide and (2) display the connecting line between the deal labels
   void              HideLine(const bool chart_redraw=false);
   void              ShowLine(const bool chart_redraw=false);

public:
//--- Set the properties
//--- Integer properties
   void              SetTicket(const long ticket)                    { this.m_ticket=ticket;          }  // Position ticket
   void              SetTime(const datetime time)                    { this.m_time=time;              }  // Position open time
   void              SetTimeMsc(const long value)                    { this.m_time_msc=value;         }  // Position open time in milliseconds 01.01.1970 
   void              SetTimeUpdate(const datetime time)              { this.m_time_update=time;       }  // Position update time
   void              SetTimeUpdateMsc(const long value)              { this.m_time_update_msc=value;  }  // Position update time in milliseconds 01.01.1970
   void              SetTypePosition(const ENUM_POSITION_TYPE type)  { this.m_type=type;              }  // Position type
   void              SetMagic(const long magic)                      { this.m_magic=magic;            }  // Magic number for a position (see ORDER_MAGIC)
   void              SetID(const long id)                            { this.m_identifier=id;          }  // Position ID
   void              SetReason(const ENUM_POSITION_REASON reason)    { this.m_reason=reason;          }  // Position opening reason
 
//--- Real properties
   void              SetVolume(const double volume)                  { this.m_volume=volume;          }  // Position volume
   void              SetPriceOpen(const double price)                { this.m_price_open=price;       }  // Position price
   void              SetSL(const double value)                       { this.m_sl=value;               }  // Stop Loss level for an open position
   void              SetTP(const double value)                       { this.m_tp=value;               }  // Take Profit level for an open position
   void              SetPriceCurrent(const double price)             { this.m_price_current=price;    }  // Current price by symbol
   void              SetSwap(const double value)                     { this.m_swap=value;             }  // Accumulated swap
   void              SetProfit(const double value)                   { this.m_profit=value;           }  // Current profit

//--- String properties
   void              SetSymbol(const string symbol)                  { this.m_symbol=symbol;          }  // Symbol a position is opened for
   void              SetComment(const string comment)                { this.m_comment=comment;        }  // Position comment
   void              SetExternalID(const string ext_id)              { this.m_external_id=ext_id;     }  // Position ID in an external system (on the exchange)


//--- Get the properties
//--- Integer properties
   long              Ticket(void)                              const { return(this.m_ticket);         }  // Position ticket
   datetime          Time(void)                                const { return(this.m_time);           }  // Position open time
   long              TimeMsc(void)                             const { return(this.m_time_msc);       }  // Position open time in milliseconds since 01.01.1970 
   datetime          TimeUpdate(void)                          const { return(this.m_time_update);    }  // Position update time
   long              TimeUpdateMsc(void)                       const { return(this.m_time_update_msc);}  // Position update time in milliseconds since 01.01.1970
   ENUM_POSITION_TYPE TypePosition(void)                       const { return(this.m_type);           }  // Position type
   long              Magic(void)                               const { return(this.m_magic);          }  // Magic number for a position (see ORDER_MAGIC)
   long              ID(void)                                  const { return(this.m_identifier);     }  // Position ID
   ENUM_POSITION_REASON Reason(void)                           const { return(this.m_reason);         }  // Position opening reason
   
//--- Real properties
   double            Volume(void)                              const { return(this.m_volume);         }  // Position volume
   double            PriceOpen(void)                           const { return(this.m_price_open);     }  // Position price
   double            SL(void)                                  const { return(this.m_sl);             }  // Stop Loss level for an open position
   double            TP(void)                                  const { return(this.m_tp);             }  // Take Profit for an open position
   double            PriceCurrent(void)                        const { return(this.m_price_current);  }  // Current price by symbol
   double            Swap(void)                                const { return(this.m_swap);           }  // Accumulated swap
   double            Profit(void)                              const { return(this.m_profit);         }  // Current profit
   
//--- String properties
   string            Symbol(void)                              const { return(this.m_symbol);         }  // A symbol position is opened on
   string            Comment(void)                             const { return(this.m_comment);        }  // Position comment
   string            ExternalID(void)                          const { return(this.m_external_id);    }  // Position ID in an external system (on the exchange)
   
//--- Additional properties
   ulong             DealIn(void)                              const;                                    // Open deal ticket
   ulong             DealOut(void)                             const;                                    // Close deal ticket
   datetime          TimeClose(void)                           const;                                    // Close time
   long              TimeCloseMsc(void)                        const;                                    // Close time in milliseconds
   int               ProfitInPoints(void)                      const;                                    // Profit in points
   double            PriceClose(void)                          const;                                    // Close price

//--- Add a deal to the list of deals, return the pointer
   CDeal            *DealAdd(const long ticket);
   
//--- Set the color of the (1) connecting line, (2) Buy and Sell deals
   void              SetLineColor(const color clr=C'225,68,29');
   void              SetDealsColor(const color clr_deal_buy=C'3,95,172', const color clr_deal_sell=C'225,68,29');

//--- Return a position type description
   string            TypeDescription(void) const;
   
//--- Return position open time and price description
   string            TimePriceCloseDescription(void);

//--- Return position close time and price description
   string            TimePriceOpenDescription(void);
   
//--- Return a brief position description
   string            Description(void);

//--- Returns a text of a position popup message
   virtual string    Tooltip(void);
 
//--- Print the properties of the position and its deals in the journal
   void              Print(void);
   
//--- (1) Hide and (2) display a graphical representation of a position on a chart
   void              Hide(const bool chart_redraw=false);
   void              Show(const bool chart_redraw=false);
   
//--- Compare two objects by the property specified in 'mode'
   virtual int       Compare(const CObject *node, const int mode=0) const;
   
//--- Constructor/destructor
                     CPosition(const long position_id, const string symbol);
                     CPosition(void)   { this.m_symbol=::Symbol();   }
                    ~CPosition();
  };

CObjectクラスとその子孫CArrayObjのインスタンスへのポインタの動的配列のクラス内の並び替えられたリストを検索するには、Search()メソッドがオブジェクトのインスタンスへのポインタを受け取り、リストの並び替えに使用されるプロパティによる比較を実行する必要があります。特定のプロパティを持つオブジェクトを常に新規作成して破棄することはパフォーマンスの面で非常にコストがかかるため、取引オブジェクトのインスタンスはクラスのprotectedセクションで宣言されます。検索を実行するには、このオブジェクトに必要なプロパティの必要な値を設定し、それを目的のインスタンスとして検索メソッドに渡すだけです。

宣言されたメソッドを詳しく見てみましょう。


以下は、パラメトリックコンストラクタです。

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CPosition::CPosition(const long position_id, const string symbol)
  {
   this.m_list_deals.Sort(SORT_MODE_DEAL_TIME_MSC);
   this.m_identifier       =  position_id;
   this.m_account_currency =  ::AccountInfoString(ACCOUNT_CURRENCY);
   this.m_symbol           =  (symbol==NULL ? ::Symbol() : symbol);
   this.m_digits           =  (int)::SymbolInfoInteger(this.m_symbol,SYMBOL_DIGITS);
   this.m_point            =  ::SymbolInfoDouble(this.m_symbol,SYMBOL_POINT);
   this.m_contract_size    =  ::SymbolInfoDouble(this.m_symbol,SYMBOL_TRADE_CONTRACT_SIZE);
   this.m_currency_profit  =  ::SymbolInfoString(this.m_symbol,SYMBOL_CURRENCY_PROFIT);
   this.m_chart_id         =  ::ChartID();
   this.m_line_name        =  "line#"+(string)this.m_identifier;
   this.m_line_color       =  C'225,68,29';
  }

コンストラクタは、ポジションIDとポジションが開かれた銘柄を受け取ります。ポジション取引のリストは、取引時間(ミリ秒単位)で並び替えるフラグを受け取ります。IDと銘柄はクラス変数に保存され、口座と銘柄のいくつかのパラメータ、チャートID、ポジションの開始と終了の取引ラベルを接続するラインのプロパティが設定されます。


クラスデストラクタで、プレフィックスがライン名であるすべてのグラフィカルオブジェクトを削除し、取引のリストをクリアします。

//+------------------------------------------------------------------+
//| Destructor                                                       |
//+------------------------------------------------------------------+
CPosition::~CPosition()
  {
   ::ObjectDelete(this.m_chart_id, this.m_line_name);
   this.m_list_deals.Clear();
  }

グラフィカルラインオブジェクトの削除は、次の理由によりプレフィックスによっておこなわれます。継承されたクラスが、開始と終了のラインだけでなく、すべてのポジション取引を接続する複数のラインを表示する場合、各ラインの名前を「line_name」+「line_number」として設定できます。この場合、すべてのラインは共通のプレフィックス「line_name」を持つため、デストラクタ内で一括して削除されます。


以下は、指定されたプロパティで2つのオブジェクトを比較するメソッドです。

//+------------------------------------------------------------------+
//| Compare two objects by the specified property                    |
//+------------------------------------------------------------------+
int CPosition::Compare(const CObject *node,const int mode=0) const
  {
   const CPosition *obj=node;
   switch(mode)
     {
      case SORT_MODE_POSITION_TICKET         :  return(this.Ticket() > obj.Ticket()                ?  1  :  this.Ticket() < obj.Ticket()                 ? -1  :  0);
      case SORT_MODE_POSITION_TIME           :  return(this.Time() > obj.Time()                    ?  1  :  this.Time() < obj.Time()                     ? -1  :  0);
      case SORT_MODE_POSITION_TIME_MSC       :  return(this.TimeMsc() > obj.TimeMsc()              ?  1  :  this.TimeMsc() < obj.TimeMsc()               ? -1  :  0);
      case SORT_MODE_POSITION_TIME_UPDATE    :  return(this.TimeUpdate() > obj.TimeUpdate()        ?  1  :  this.TimeUpdate() < obj.TimeUpdate()         ? -1  :  0);
      case SORT_MODE_POSITION_TIME_UPDATE_MSC:  return(this.TimeUpdateMsc() > obj.TimeUpdateMsc()  ?  1  :  this.TimeUpdateMsc() < obj.TimeUpdateMsc()   ? -1  :  0);
      case SORT_MODE_POSITION_TYPE           :  return(this.TypePosition() > obj.TypePosition()    ?  1  :  this.TypePosition() < obj.TypePosition()     ? -1  :  0);
      case SORT_MODE_POSITION_MAGIC          :  return(this.Magic() > obj.Magic()                  ?  1  :  this.Magic() < obj.Magic()                   ? -1  :  0);
      case SORT_MODE_POSITION_IDENTIFIER     :  return(this.ID() > obj.ID()                        ?  1  :  this.ID() < obj.ID()                         ? -1  :  0);
      case SORT_MODE_POSITION_REASON         :  return(this.Reason() > obj.Reason()                ?  1  :  this.Reason() < obj.Reason()                 ? -1  :  0);
      case SORT_MODE_POSITION_VOLUME         :  return(this.Volume() > obj.Volume()                ?  1  :  this.Volume() < obj.Volume()                 ? -1  :  0);
      case SORT_MODE_POSITION_PRICE_OPEN     :  return(this.PriceOpen() > obj.PriceOpen()          ?  1  :  this.PriceOpen() < obj.PriceOpen()           ? -1  :  0);
      case SORT_MODE_POSITION_SL             :  return(this.SL() > obj.SL()                        ?  1  :  this.SL() < obj.SL()                         ? -1  :  0);
      case SORT_MODE_POSITION_TP             :  return(this.TP() > obj.TP()                        ?  1  :  this.TP() < obj.TP()                         ? -1  :  0);
      case SORT_MODE_POSITION_PRICE_CURRENT  :  return(this.PriceCurrent() > obj.PriceCurrent()    ?  1  :  this.PriceCurrent() < obj.PriceCurrent()     ? -1  :  0);
      case SORT_MODE_POSITION_SWAP           :  return(this.Swap() > obj.Swap()                    ?  1  :  this.Swap() < obj.Swap()                     ? -1  :  0);
      case SORT_MODE_POSITION_PROFIT         :  return(this.Profit() > obj.Profit()                ?  1  :  this.Profit() < obj.Profit()                 ? -1  :  0);
      case SORT_MODE_POSITION_SYMBOL         :  return(this.Symbol() > obj.Symbol()                ?  1  :  this.Symbol() < obj.Symbol()                 ? -1  :  0);
      case SORT_MODE_POSITION_COMMENT        :  return(this.Comment() > obj.Comment()              ?  1  :  this.Comment() < obj.Comment()               ? -1  :  0);
      case SORT_MODE_POSITION_EXTERNAL_ID    :  return(this.ExternalID() > obj.ExternalID()        ?  1  :  this.ExternalID() < obj.ExternalID()         ? -1  :  0);
      case SORT_MODE_POSITION_TIME_CLOSE     :  return(this.TimeClose() > obj.TimeClose()          ?  1  :  this.TimeClose() < obj.TimeClose()           ? -1  :  0);
      case SORT_MODE_POSITION_TIME_CLOSE_MSC :  return(this.TimeCloseMsc() > obj.TimeCloseMsc()    ?  1  :  this.TimeCloseMsc() < obj.TimeCloseMsc()     ? -1  :  0);
      case SORT_MODE_POSITION_PRICE_CLOSE    :  return(this.PriceClose() > obj.PriceClose()        ?  1  :  this.PriceClose() < obj.PriceClose()         ? -1  :  0);
      default                                :  return -1;
     }
  }

取引オブジェクトクラスを開発する際にも同様のメソッドが検討されました。ここではすべてが同様です。現在のオブジェクトのプロパティ値が比較対象の値より大きい場合は1、小さい場合は-1、プロパティが等しい場合は0が返されます。


以下は、ミリ秒単位で時間を返すメソッドです。

//+------------------------------------------------------------------+
//| Return time with milliseconds                                    |
//+------------------------------------------------------------------+
string CPosition::TimeMscToString(const long time_msc, int flags=TIME_DATE|TIME_MINUTES|TIME_SECONDS) const
  {
   return(::TimeToString(time_msc/1000, flags) + "." + ::IntegerToString(time_msc %1000, 3, '0'));
  }

これは、取引クラスで考慮される取引オブジェクトクラスの同名メソッドのコピーです。


以下は、オープン取引へのポインタを返すメソッドです。

//+------------------------------------------------------------------+
//| Return the pointer to the opening deal                           |
//+------------------------------------------------------------------+
CDeal *CPosition::GetDealIn(void) const
  {
   int total=this.m_list_deals.Total();
   for(int i=0; i<total; i++)
     {
      CDeal *deal=this.m_list_deals.At(i);
      if(deal==NULL)
         continue;
      if(deal.Entry()==DEAL_ENTRY_IN)
         return deal;
     }
   return NULL;
  }

取引オブジェクトのリストで、Entry Inポジション更新メソッドを使用して取引を見つける必要があります。この取引は時間順に並べられたリストの最初にあるはずです。したがって、リストの先頭、つまりインデックス0からサイクルを開始します。

リストからインデックスによって取引を取得し、これが市場参入取引である場合は、リスト内で見つかった取引へのポインタを返します。ループの最後に、取引が見つからないためNULLを返します。


以下は、クローズ取引へのポインタを返すメソッドです。

//+------------------------------------------------------------------+
//| Return the pointer to the close deal                             |
//+------------------------------------------------------------------+
CDeal *CPosition::GetDealOut(void) const
  {
   for(int i=this.m_list_deals.Total()-1; i>=0; i--)
     {
      CDeal *deal=this.m_list_deals.At(i);
      if(deal==NULL)
         continue;
      if(deal.Entry()==DEAL_ENTRY_OUT || deal.Entry()==DEAL_ENTRY_OUT_BY)
         return deal;
     }
   return NULL;
  }

ここでは、前のメソッドと比較して、すべてが逆になります。ポジションを終了する取引はリストの一番最後にあるため、最後からループを開始します。
ポジションを終了する取引が見つかったらすぐに、その取引へのポインタを返します。それ以外の場合はNULLを返します。


以下は、履歴ポジションの追加プロパティを取得するメソッドです。

//+------------------------------------------------------------------+
//| Return the open deal ticket                                      |
//+------------------------------------------------------------------+
ulong CPosition::DealIn(void) const
  {
   CDeal *deal=this.GetDealIn();
   return(deal!=NULL ? deal.Ticket() : 0);
  }
//+------------------------------------------------------------------+
//| Return the close deal ticket                                     |
//+------------------------------------------------------------------+
ulong CPosition::DealOut(void) const
  {
   CDeal *deal=this.GetDealOut();
   return(deal!=NULL ? deal.Ticket() : 0);
  }
//+------------------------------------------------------------------+
//| Return the close time                                            |
//+------------------------------------------------------------------+
datetime CPosition::TimeClose(void) const
  {
   CDeal *deal=this.GetDealOut();
   return(deal!=NULL ? deal.Time() : 0);
  }
//+------------------------------------------------------------------+
//| Return the close time in milliseconds                            |
//+------------------------------------------------------------------+
long CPosition::TimeCloseMsc(void) const
  {
   CDeal *deal=this.GetDealOut();
   return(deal!=NULL ? deal.TimeMsc() : 0);
  }
//+------------------------------------------------------------------+
//| Return the close price                                           |
//+------------------------------------------------------------------+
double CPosition::PriceClose(void) const
  {
   CDeal *deal=this.GetDealOut();
   return(deal!=NULL ? deal.Price() : 0);
  }

すべてのメソッドでは、まず取引へのポインタを取得し、そこから対応するプロパティを取得する必要があります。ポインタが有効な場合はプロパティ値を返し、そうでない場合は0を返します。


以下は、ポイント単位の利益を返すメソッドです。

//+------------------------------------------------------------------+
//| Return a profit in points                                        |
//+------------------------------------------------------------------+
int CPosition::ProfitInPoints(void) const
  {
//--- If symbol Point has not been received previously, inform of that and return 0
   if(this.m_point==0)
     {
      ::Print("The Point() value could not be retrieved.");
      return 0;
     }
//--- Get position open and close prices
   double open =this.PriceOpen();
   double close=this.PriceClose();
   
//--- If failed to get the prices, return 0
   if(open==0 || close==0)
      return 0;
      
//--- Depending on the position type, return the calculated value of the position profit in points
   return int(this.TypePosition()==POSITION_TYPE_BUY ? (close-open)/this.m_point : (open-close)/this.m_point);
  }

ここでは、まず対応する取引から始値と終値を取得し、次にポジションの方向に応じて利益計算の結果をポイントで返します。


以下は、取引リストに取引を追加するメソッドです。

//+------------------------------------------------------------------+
//| Add a deal to the list of deals                                  |
//+------------------------------------------------------------------+
CDeal *CPosition::DealAdd(const long ticket)
  {
//--- A temporary object gets a ticket of the desired deal and the flag of sorting the list of deals by ticket
   this.m_temp_deal.SetTicket(ticket);
   this.m_list_deals.Sort(SORT_MODE_DEAL_TICKET);
   
//--- Set the result of checking if a deal with such a ticket is present in the list
   bool added=(this.m_list_deals.Search(&this.m_temp_deal)!=WRONG_VALUE);
   
//--- Set the flag of sorting by time in milliseconds for the list
   this.m_list_deals.Sort(SORT_MODE_DEAL_TIME_MSC);

//--- If a deal with such a ticket is already in the list, return NULL
   if(added)
      return NULL;
      
//--- Create a new deal object
   CDeal *deal=new CDeal(ticket);
   if(deal==NULL)
      return NULL;
   
//--- Add the created object to the list in sorting order by time in milliseconds
//--- If failed to add the deal to the list, remove the the deal object and return NULL
   if(!this.m_list_deals.InsertSort(deal))
     {
      delete deal;
      return NULL;
     }
   
//--- If this is a position closing deal, set the profit from the deal properties to the position profit value and
//--- create a connecting line between the position open-close deal labels
   if(deal.Entry()==DEAL_ENTRY_OUT || deal.Entry()==DEAL_ENTRY_OUT_BY)
     {
      this.SetProfit(deal.Profit());
      this.CreateLine();
     }
     
//--- Return the pointer to the created deal object
   return deal;   
  }

このメソッドは、選択された取引のチケットを受け取ります。まず、リスト内にそのようなチケットの取引がないことを確認します。これをおこなうには、メソッドに渡されたチケットを一時的な取引オブジェクトに割り当てます。取引リストは取引チケットの値によって並び替えられれ、そのようなチケットを含むリスト内の取引の検索が実行されます。取引を検索した直後に、ミリ秒単位の時間で並び替えるフラグがリストに返されます。そのような取引がすでにリストにある場合は、それを追加する必要はありません。メソッドはNULLを返します。同時に、取引リストの並べ替えはデフォルトでミリ秒単位の時間で設定されています。そのような取引がリストにない場合は、取引オブジェクトが新規作成され、ミリ秒単位の時間で並び替えられた順序でリストに追加されます。これがポジションクローズ取引である場合、その利益はポジションのプロパティに設定され、ポジションのオープン取引とクローズ取引の間に接続線が作成されます。その結果、新しく作成された取引オブジェクトへのポインタが返されます。


以下は、ポジションタイプの説明を返すメソッドです。

//+------------------------------------------------------------------+
//| Return a position type description                               |
//+------------------------------------------------------------------+
string CPosition::TypeDescription(void) const
  {
   return(this.m_type==POSITION_TYPE_BUY ? "Buy" : this.m_type==POSITION_TYPE_SELL ? "Sell" : "Unknown::"+(string)this.m_type);
  }

m_type変数に格納されているポジションのタイプに応じて説明を返します。変数値がポジションタイプの列挙定数と一致しない場合は、「Unknown::」+variable_valueを返します。


以下は、ポジションの終了時間と価格の説明を返すメソッドです。

//+------------------------------------------------------------------+
//| Return position close time and price description                 |
//+------------------------------------------------------------------+
string CPosition::TimePriceCloseDescription(void)
  {
   if(this.TimeCloseMsc()==0)
      return "Not closed yet";
   return(::StringFormat("Closed %s [%.*f]", this.TimeMscToString(this.TimeCloseMsc()),this.m_digits, this.PriceClose()));
  }

ポジションオブジェクトにまだ決済取引がない場合、メソッドはポジションがまだ決済されていないというメッセージを返します。それ以外の場合は、次のタイプの文字列が作成され、返されます。

Closed 2023.06.12 17:04:20.362 [1.07590]


以下は、ポジション開始時間と価格の説明を返すメソッドです。

//+------------------------------------------------------------------+
//| Return position open time and price description                  |
//+------------------------------------------------------------------+
string CPosition::TimePriceOpenDescription(void)
  {
   return(::StringFormat("Opened %s [%.*f]", this.TimeMscToString(this.TimeMsc()),this.m_digits, this.PriceOpen()));
  }

ここでは、次のタイプの文字列が作成され、返されます。

Opened 2023.06.12 16:51:36.838 [1.07440]


以下は、ポジションの簡単な説明を返すメソッドです。

//+------------------------------------------------------------------+
//| Return a brief position description                              |
//+------------------------------------------------------------------+
string CPosition::Description(void)
  {
   return(::StringFormat("Position %s %.2f %s #%I64d, Magic %I64d",
                         this.Symbol(), this.Volume(), this.TypeDescription(), this.ID(), this.Magic()));
  }

次のタイプの文字列が作成され、返されます。

Position EURUSD 0.10 Buy #1752955040, Magic 123


以下は、ポジションのポップアップメッセージのテキストを返す仮想メソッドです。

//+------------------------------------------------------------------+
//| Return a text of a position pop-up message                       |
//+------------------------------------------------------------------+
string CPosition::Tooltip(void)
  {
//--- Get the pointers to the open and close deals
   CDeal *deal_in =this.GetDealIn();
   CDeal *deal_out=this.GetDealOut();
   
//--- If no deals are received, return an empty string
   if(deal_in==NULL || deal_out==NULL)
      return NULL;
      
//--- Get commission, swap and deal fee that are common for two deals
   double commission=deal_in.Commission()+deal_out.Commission();
   double swap=deal_in.Swap()+deal_out.Swap();
   double fee=deal_in.Fee()+deal_out.Fee();
   
//--- Get the final profit of the position and the spread values when opening and closing
   double profit=deal_out.Profit();
   int    spread_in=deal_in.Spread();
   int    spread_out=deal_out.Spread();
   
//--- If the reason for closing the position is StopLoss, TakeProfit or StopOut, set the reason description in the variable
   string reason=(deal_out.Reason()==DEAL_REASON_SL || deal_out.Reason()==DEAL_REASON_TP || deal_out.Reason()==DEAL_REASON_SO ? deal_out.ReasonDescription() : "");
   
//--- Create and return the tooltip string
   return(::StringFormat("%s\nCommission %.2f, Swap %.2f, Fee %.2f\nSpread In/Out %d/%d, Profit %+.2f %s (%d points)\nResult: %s %+.2f %s",
                         this.Description(), commission, swap, fee, spread_in, spread_out, profit,this.m_currency_profit, 
                         this.ProfitInPoints(), reason, profit+commission+fee+swap, this.m_currency_profit));
  }

次のタイプの文字列が作成され、返されます。

Position EURUSD 0.10 Buy #1752955040, Magic 0
Commission 0.00, Swap 0.00, Fee 0.00
Spread In/Out 0/0, Profit +15.00 USD (150 points)
Result: TP +15.00 USD

テキストは、ポジションのオープン取引とクローズ取引を結ぶ文字列のツールチップテキストとして設定されます。このメソッドは仮想であり、わずかに異なるデータを出力する必要がある場合は、継承されたクラスでオーバーライドできます。メソッドによって返される文字列をツールチップテキストとして使用する場合、文字列の長さを考慮する必要があります。ツールチップは、制御コードを含めて約160文字に制限されています。残念ながら、経験的に発見された正確な値を指定することはできません。


以下は、チャート上に取引ラベルを表示するメソッドです。

//+------------------------------------------------------------------+
//| Display deal labels on the chart                                 |
//+------------------------------------------------------------------+
void CPosition::ShowDeals(const bool chart_redraw=false)
  {
   for(int i=this.m_list_deals.Total()-1; i>=0; i--)
     {
      CDeal *deal=this.m_list_deals.At(i);
      if(deal==NULL)
         continue;
      deal.ShowArrow();
     }
   if(chart_redraw)
      ::ChartRedraw(this.m_chart_id);
  }

取引リストをループして、各取引を取得し、受信したオブジェクトのShowArrow()メソッドを呼び出して、チャートに取引ラベルを表示します。ループの終了時に、適切なフラグが設定されている場合はチャートを再描画します。


以下は、チャート上の取引ラベルを非表示にするメソッドです。

//+------------------------------------------------------------------+
//| Hide deal labels on the chart                                    |
//+------------------------------------------------------------------+
void CPosition::HideDeals(const bool chart_redraw=false)
  {
   for(int i=this.m_list_deals.Total()-1; i>=0; i--)
     {
      CDeal *deal=this.m_list_deals.At(i);
      if(deal==NULL)
         continue;
      deal.HideArrow();
     }
   if(chart_redraw)
      ::ChartRedraw(this.m_chart_id);
  }

取引リストをループして、各取引を取得し、受信したオブジェクトのHideArrow()メソッドを呼び出して、チャートから取引ラベルを非表示にします。ループの終了時に、適切なフラグが設定されている場合はチャートを再描画します。


以下は、オープン取引とクローズ取引を結ぶ線を作成するメソッドです。

//+------------------------------------------------------------------+
//| Create a line connecting open-close deals                        |
//+------------------------------------------------------------------+
bool CPosition::CreateLine(void)
  {
//--- If the graphical line object could not be created, report this in the journal and return 'false'
   ::ResetLastError();
   if(!::ObjectCreate(this.m_chart_id, this.m_line_name, OBJ_TREND, 0, 0, 0, 0, 0))
     {
      ::Print("ObjectCreate() failed. Error ", ::GetLastError());
      return false;
     }
//--- Hide the line
   this.HideLine();

//--- Set the line to be drawn with dots, define the color and return 'true'
   ::ObjectSetInteger(this.m_chart_id, this.m_line_name, OBJPROP_STYLE, STYLE_DOT);
   ::ObjectSetInteger(this.m_chart_id, this.m_line_name, OBJPROP_COLOR, this.m_line_color);
   return true;
  }

0位置(価格は0、時間は01.01.1970 00:00:00)に価格と時間の座標を持つ線が作成されます。グラフから線を非表示にし、描画スタイルをドットとデフォルトの色に設定します。
最初は、各オブジェクトの線は非表示で作成されます。表示が必要な時点で、必要な座標と表示が次のように設定されます。


以下は、取引ラベル間の接続線を表示するメソッドです。

//+------------------------------------------------------------------+
//| Display the connecting line between deal labels                  |
//+------------------------------------------------------------------+
void CPosition::ShowLine(const bool chart_redraw=false)
  {
//--- Get the pointers to the open and close deals
   CDeal *deal_in= this.GetDealIn();
   CDeal *deal_out=this.GetDealOut();
   
//--- If no deals are received, leave
   if(deal_in==NULL || deal_out==NULL)
      return;
      
//--- Set a start and end time, a price from the deal properties and a tooltip text for the line
   ::ObjectSetInteger(this.m_chart_id, this.m_line_name, OBJPROP_TIME, 0, deal_in.Time());
   ::ObjectSetInteger(this.m_chart_id, this.m_line_name, OBJPROP_TIME, 1, deal_out.Time());
   ::ObjectSetDouble(this.m_chart_id, this.m_line_name, OBJPROP_PRICE, 0, deal_in.Price());
   ::ObjectSetDouble(this.m_chart_id, this.m_line_name, OBJPROP_PRICE, 1, deal_out.Price());
   ::ObjectSetString(this.m_chart_id, this.m_line_name, OBJPROP_TOOLTIP, this.Tooltip());
   
//--- Show the line on the chart and update it if the flag is set
   ::ObjectSetInteger(this.m_chart_id, this.m_line_name, OBJPROP_TIMEFRAMES, OBJ_ALL_PERIODS);
   if(chart_redraw)
      ::ChartRedraw(this.m_chart_id);
  }


以下は、取引ラベル間の接続線を非表示にするメソッドです。

//+------------------------------------------------------------------+
//| Hide the connecting line between the deal labels                 |
//+------------------------------------------------------------------+
void CPosition::HideLine(const bool chart_redraw=false)
  {
   ::ObjectSetInteger(this.m_chart_id, this.m_line_name, OBJPROP_TIMEFRAMES, OBJ_NO_PERIODS);
   if(chart_redraw)
      ::ChartRedraw(this.m_chart_id);
  }

オブジェクトの可視性フラグはすべてのチャート期間でリセットされ、フラグが設定されている場合はチャートが更新されます。


以下は、接続線の色を設定するメソッドです。

//+------------------------------------------------------------------+
//| Set the color of the connecting line                             |
//+------------------------------------------------------------------+
void CPosition::SetLineColor(const color clr=C'225,68,29')
  {
   if(::ObjectSetInteger(this.m_chart_id, this.m_line_name, OBJPROP_COLOR, clr))
      this.m_line_color=clr;
  }

メソッドに渡された値は、オブジェクトの色プロパティとして設定されます。すべてが成功した場合、色はm_line_color変数に設定されます。


以下は、売買取引の色を設定するメソッドです。

//+------------------------------------------------------------------+
//| Set Buy and Sell deal color                                      |
//+------------------------------------------------------------------+
void CPosition::SetDealsColor(const color clr_deal_buy=C'3,95,172', const color clr_deal_sell=C'225,68,29')
  {
//--- In the loop by the list of deals
   int total=this.m_list_deals.Total();
   for(int i=0; i<total; i++)
     {
      //--- get the next deal object
      CDeal *deal=this.m_list_deals.At(i);
      if(deal==NULL)
         continue;
      //--- In case of Buy deal type, set a color for a Buy deal for the object
      if(deal.TypeDeal()==DEAL_TYPE_BUY)
         deal.SetColorArrow(clr_deal_buy);
      //--- In case of Sell deal type, set a color for a Sell deal for the object
      if(deal.TypeDeal()==DEAL_TYPE_SELL)
         deal.SetColorArrow(clr_deal_sell);
     }
  }

メソッドは、買い取引と売り取引の色を受け取ります。ポジション取引のリストをループして、各連続取引へのポインタを取得し、取引の種類に応じて取引ラベルの色を設定します。


以下は、チャート上のポジションのグラフィック表現を表示するメソッドです。

//+------------------------------------------------------------------+
//| Display a graphical representation of a position on a chart      |
//+------------------------------------------------------------------+
void CPosition::Show(const bool chart_redraw=false)
  {
   this.ShowDeals(false);
   this.ShowLine(chart_redraw);
  }

このメソッドは、チャート上でエントリ取引とエグジット取引、およびそれらを結ぶ線を表示するprotectedメソッドを順番に呼び出します。


以下は、チャート上のポジションのグラフィック表現を非表示にするメソッドです。

//+------------------------------------------------------------------+
//| Hide a graphical representation of a position on a chart         |
//+------------------------------------------------------------------+
void CPosition::Hide(const bool chart_redraw=false)
  {
   this.HideLine(false);
   this.HideDeals(chart_redraw);
  }

このメソッドは、エントリ取引とエグジット取引、およびチャート上でそれらを結ぶ線を非表示にするprotectedメソッドを順番に呼び出します。

上記で検討した2つのメソッドはpublicで、制御プログラムからチャート上のポジションの表示を制御するために使用されます。


以下は、ポジションのプロパティと取引を操作ログに出力するメソッドです。

//+------------------------------------------------------------------+
//| Print the position properties and deals in the journal           |
//+------------------------------------------------------------------+
void CPosition::Print(void)
  {
   ::PrintFormat("%s\n-%s\n-%s", this.Description(), this.TimePriceOpenDescription(), this.TimePriceCloseDescription());
   for(int i=0; i<this.m_list_deals.Total(); i++)
     {
      CDeal *deal=this.m_list_deals.At(i);
      if(deal==NULL)
         continue;
      deal.Print();
     }
  }

まず、ポジションの簡単な説明と、ポジションの開始と終了の時間と価格を示す2行を含むヘッダーが表示されます。次に、リストのすべてのポジション取引の説明がループで印刷されます。

その結果、操作ログには次のデータが表示されます。

Position EURUSD 0.10 Sell #2523224572, Magic 0
-Opened 2024.05.31 17:06:15.134 [1.08734]
-Closed 2024.05.31 17:33:17.772 [1.08639]
  Deal: Entry In  0.10 Sell #2497852906 at 2024.05.31 17:06:15.134
  Deal: Entry Out 0.10 Buy  #2497993663 at 2024.05.31 17:33:17.772

あまり参考になりませんが、これらのクラスのPrint()メソッドはデバッグ専用に作成されました。

現在、取引クラスとポジションクラスの2つのクラスがすでに存在しています。ポジションクラスには、ポジションの存続期間中に参加した取引のリストが含まれています。クラスはシンプルで、取引に関する基本情報と、価格、ポジションの開始/終了時間、ポイントでの利益に関する追加データが含まれています。残りのメソッドは、この情報を取得してグラフまたはテキストラインに表示するために使用されます。

ここで、すべてのポジションを取引から収集し、履歴ポジションのリストに配置する一般的なクラスを作成しましょう。このクラスは、ポジションとその取引のプロパティへのアクセスを提供し、履歴ポジションのリストを更新および補足し、指定されたポジションをチャートに表示します。


履歴ポジション管理クラス

前の2つのクラスを作成した同じフォルダに、CPositionsControlクラスのファイルPositionsControl.mqhを新規作成します。

クラスはCObject標準ライブラリの基底オブジェクトから継承する必要があり、ポジションクラスファイルはCPositionsControlの新しいクラスファイルに含まれます

//+------------------------------------------------------------------+
//|                                             PositionsControl.mqh |
//|                                  Copyright 2024, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"

#include "Position.mqh"

//+------------------------------------------------------------------+
//| Class of historical positions                                    |
//+------------------------------------------------------------------+
class CPositionsControl : public CObject
  {
  }

クラスを処理するための変数とメソッドをprivate、protected、publicセクションで宣言します。

//+------------------------------------------------------------------+
//| Class of historical positions                                    |
//+------------------------------------------------------------------+
class CPositionsControl : public CObject
  {
private:
   string            m_symbol;            // The symbol the position is open for
   long              m_current_id;        // ID of the current position displayed on the chart
   bool              m_key_ctrl;          // Flag for allowing to control the chart using the keyboard 
   
//--- Return the position type by deal type
   ENUM_POSITION_TYPE PositionTypeByDeal(const CDeal *deal);

protected:
   CPosition         m_temp_pos;          // Temporary position object for searching
   CArrayObj         m_list_pos;          // List of positions
   long              m_chart_id;          // Chart ID
   
//--- Return the position object from the list by ID
   CPosition        *GetPositionObjByID(const long id);

//--- Return the flag of the market position
   bool              IsMarketPosition(const long id);
   
//--- Return the pointer to the (1) first and the (2) last open position in the list
   CPosition        *GetFirstClosedPosition(void);
   CPosition        *GetLastClosedPosition(void);

//--- Return the pointer to the (1) previous and (2) next closed position in the list
   CPosition        *GetPrevClosedPosition(CPosition *current);
   CPosition        *GetNextClosedPosition(CPosition *current);

//--- Displays a graphical representation of the specified position on a chart
   void              Show(CPosition *pos, const bool chart_redraw=false);
   
//--- Center the chart on the currently selected position
   void              CentersChartByCurrentSelected(void);

//--- Return the ID of the current selected position
   long              CurrentSelectedID(void)    const { return this.m_current_id;         }

//--- Return the selected position (1) open and (2) close time
   datetime          TimeOpenCurrentSelected(void);
   datetime          TimeCloseCurrentSelected(void);
   
//--- Hide the graphical representation of all positions on the chart except the specified one
   void              HideAllExceptOne(const long pos_id, const bool chart_redraw=false);
   
public:
//--- Return the chart (1) symbol and (2) ID
   string            Symbol(void)               const { return this.m_symbol;             }
   long              ChartID(void)              const { return this.m_chart_id;           }

//--- Create and update the list of positions. It can be redefined in the inherited classes
   virtual bool      Refresh(void);
   
//--- Return the number of positions in the list
   int               Total(void)                const { return this.m_list_pos.Total();   }

//--- (1) Hide and (2) display the graphical representation of the first position on the chart
   void              HideFirst(const bool chart_redraw=false);
   void              ShowFirst(const bool chart_redraw=false);
   
//--- (1) Hide and (2) display the graphical representation of the last position on the chart
   void              HideLast(const bool chart_redraw=false);
   void              ShowLast(const bool chart_redraw=false);
   
//--- Display a graphical representation of the (1) current, (2) previous and (3) next position
   void              ShowCurrent(const bool chart_redraw=false);
   void              ShowPrev(const bool chart_redraw=false);
   void              ShowNext(const bool chart_redraw=false);

//--- Return the description of the currently selected position
   string            CurrentSelectedDescription(void);

//--- Print the properties of all positions and their deals in the journal
   void              Print(void);

//--- Constructor/destructor
                     CPositionsControl(const string symbol=NULL);
                    ~CPositionsControl();
  };

宣言されたメソッドについて詳しく考えてみましょう。


クラスコンストラクタ

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CPositionsControl::CPositionsControl(const string symbol=NULL)
  {
   this.m_list_pos.Sort(SORT_MODE_POSITION_TIME_CLOSE_MSC);
   this.m_symbol     =  (symbol==NULL ? ::Symbol() : symbol);
   this.m_chart_id   =  ::ChartID();
   this.m_current_id =  0;
   this.m_key_ctrl   =  ::ChartGetInteger(this.m_chart_id, CHART_KEYBOARD_CONTROL);
  }

履歴ポジションのリストの終了時間(ミリ秒単位)で並び替えるフラグを設定します。コンストラクタに渡された銘柄を変数に設定します。これが空の文字列の場合、プログラムが実行されているチャートの現在の銘柄になります。チャートIDは現在のチャートIDとして設定されます。継承したクラスに異なるIDを設定することが可能になります。m_key_ctrl変数に、キーを使用してチャートを制御できるようにするフラグを設定します。プログラムの実行後、この値はチャートのプロパティに設定され直され、プログラムが起動される前の状態に戻ります。


クラスのデストラクタでは、ポジションのリストが破棄され、CHART_KEYBOARD_CONTROLチャートプロパティはプログラムの起動前に存在していた値を取得します。

//+------------------------------------------------------------------+
//| Destructor                                                       |
//+------------------------------------------------------------------+
CPositionsControl::~CPositionsControl()
  {
   this.m_list_pos.Shutdown();
   ::ChartSetInteger(this.m_chart_id, CHART_KEYBOARD_CONTROL, this.m_key_ctrl);
  }


以下は、IDによってリストからポジションオブジェクトを返すメソッドです。

//+------------------------------------------------------------------+
//| Return the position object from the list by ID                   |
//+------------------------------------------------------------------+
CPosition *CPositionsControl::GetPositionObjByID(const long id)
  {
//--- Set the position ID for the temporary object and set the flag of sorting by position ID for the list
   this.m_temp_pos.SetID(id);
   this.m_list_pos.Sort(SORT_MODE_POSITION_IDENTIFIER);
//--- Get the index of the position object with such an ID (or -1 if it is absent) from the list
//--- Use the obtained index to get the pointer to the positino object from the list (or NULL if the index value is -1)
   int index=this.m_list_pos.Search(&this.m_temp_pos);
   CPosition *pos=this.m_list_pos.At(index);
//--- Set the flag of sorting by position close time in milliseconds for the list and
//--- return the pointer to the position object (or NULL if it is absent)
   this.m_list_pos.Sort(SORT_MODE_POSITION_TIME_CLOSE_MSC);
   return pos;
  }

メソッドのロジックはコードのコメントで完全に説明されています。


以下は、市場ポジションフラグを返すメソッドです。

//+------------------------------------------------------------------+
//| Return the market position flag                                  |
//+------------------------------------------------------------------+
bool CPositionsControl::IsMarketPosition(const long id)
  {
//--- In a loop by the list of current positions in the terminal
   for(int i=::PositionsTotal()-1; i>=0; i--)
     {
      //--- get the position ticket by the loop index
      ulong ticket=::PositionGetTicket(i);
      //--- If the ticket is received, the position can be selected and its ID is equal to the one passed to the method,
      //--- this is the desired market position, return 'true'
      if(ticket!=0 && ::PositionSelectByTicket(ticket) && ::PositionGetInteger(POSITION_IDENTIFIER)==id)
         return true;
     }
//--- No such market position, return 'false'
   return false;
  }

取引リストからポジションのリストを作成するときは、現在の市場ポジションを考慮せず、それらを履歴ポジションのリストに追加しないようにすることが重要です。ポジションがまだクローズされていないことを確認するには、アクティブポジションのリストでそのポジションをIDを探す必要があります。そのようなポジションが存在する場合、リストに追加する必要はありません。このメソッドは、チケットによって市場ポジションのリストからポジションを検索し、ポジションIDがメソッドに渡された値と一致するかどうかを確認し、そのようなポジションが存在する場合(選択可能)、trueを返します。それ以外の場合はfalseを返します。


以下は、リスト内の最初のクローズポジションへのポインタを返すメソッドです。

//+------------------------------------------------------------------+
//| Return the pointer to the first closed position in the list      |
//+------------------------------------------------------------------+
CPosition *CPositionsControl::GetFirstClosedPosition(void)
  {
   this.m_list_pos.Sort(SORT_MODE_POSITION_TIME_CLOSE_MSC);
   return this.m_list_pos.At(0);
  }

ポジションのリストは、ポジションの終了時間(ミリ秒単位)で並び替えられ、リストの最初のポジション(最も古いポジション)へのポインタが返されます。


以下は、リスト内の最後のクローズポジションへのポインタを返すメソッドです。

//+------------------------------------------------------------------+
//| Return the pointer to the last closed position in the list       |
//+------------------------------------------------------------------+
CPosition *CPositionsControl::GetLastClosedPosition(void)
  {
   this.m_list_pos.Sort(SORT_MODE_POSITION_TIME_CLOSE_MSC);
   return this.m_list_pos.At(this.m_list_pos.Total()-1);
  }

ポジションのリストは、ポジションの終了時間(ミリ秒単位)で並び替えられ、リスト内の最後のポジションへのポインタが返されます。


以下は、リスト内の前のクローズポジションへのポインタを返すメソッドです。

//+------------------------------------------------------------------+
//| Return the pointer to the previous closed position in the list   |
//+------------------------------------------------------------------+
CPosition *CPositionsControl::GetPrevClosedPosition(CPosition *current)
  {
   this.m_list_pos.Sort(SORT_MODE_POSITION_TIME_CLOSE_MSC);
   int prev=this.m_list_pos.SearchLess(current);
   return this.m_list_pos.At(prev);
  }

メソッドに渡されるポインタの現在選択されているポジションに基づいて、リスト内で前のポジションが検索され、終了時間(ミリ秒単位)で並び替えられます。CArrayObjクラスのSearchLess()メソッドは、リストの並べ替えの基準となる、リスト内で最初に検出された小さい値を持つオブジェクトへのポインタを返します。この場合、リストは終了時間(ミリ秒単位)で並び替えられます。したがって、メソッドに渡された終了時間よりも短い終了時間で見つかった最初のオブジェクトが前のポジションになります。ポジションオブジェクトが見つからない場合、またはリストの最初の要素がメソッドに渡された場合(それより前の要素がない場合)、メソッドはNULLを返します。


以下は、リスト内の次のクローズポジションへのポインタを返すメソッドです。

//+------------------------------------------------------------------+
//| Return the pointer to the next closed position in the list       |
//+------------------------------------------------------------------+
CPosition *CPositionsControl::GetNextClosedPosition(CPosition *current)
  {
   this.m_list_pos.Sort(SORT_MODE_POSITION_TIME_CLOSE_MSC);
   int next=this.m_list_pos.SearchGreat(current);
   return this.m_list_pos.At(next);
  }

メソッドに渡されるポインタの現在選択されているポジションに基づいて、リスト内で次のポジションが検索され、終了時間(ミリ秒単位)で並び替えられます。CArrayObjクラスのSearchGreat()メソッドは、リストの並べ替えの基準となる、リスト内で最初に検出された、より高い値を持つオブジェクトへのポインタを返します。この場合、リストは終了時間(ミリ秒単位)で並び替えられます。したがって、メソッドに渡された終了時間よりも大きい終了時間を持つ最初のオブジェクトが次のポジションになります。ポジションオブジェクトが見つからない場合、またはリストの最後の要素がメソッドに渡された場合(次の要素がない)、メソッドはNULLを返します。


以下は、チャート上の特定のポジションのグラフィック表現を表示するメソッドです。

//+----------------------------------------------------------------------------+
//| Display a graphical representation of the specified position on the chart  |
//+----------------------------------------------------------------------------+
void CPositionsControl::Show(CPosition *pos,const bool chart_redraw=false)
  {
   if(pos!=NULL)
     {
      pos.Show(chart_redraw);
      this.m_current_id=pos.ID();
     }
  }

このメソッドは、チャート上にグラフィック表現を表示するポジションへのポインタを受け取ります。有効なオブジェクトを受信した場合は、そのShow()メソッドを呼び出して、ポジションIDをm_current_id変数に追加します。m_current_id変数内のポジションIDによって現在選択されているポジションを定義できます。ポジションのグラフィック表現がチャート上に表示されている場合、そのポジションは選択されたとみなされます。


以下は、チャートから最初のポジションのグラフィック表現を非表示にするメソッドです。

//+------------------------------------------------------------------------+
//| Hide the graphical representation of the first position from the chart |
//+------------------------------------------------------------------------+
void CPositionsControl::HideFirst(const bool chart_redraw=false)
  {
   CPosition *pos=this.GetFirstClosedPosition();
   if(pos!=NULL)
      pos.Hide(chart_redraw);
  }

リストの最初のポジションへのポインタを取得し、そのHide()メソッドを呼び出します。


以下は、チャート上の最初のポジションのグラフィック表現を表示するメソッドです。

//+---------------------------------------------------------------------------+
//| Display the graphical representation of the first position on the chart   |
//+---------------------------------------------------------------------------+
void CPositionsControl::ShowFirst(const bool chart_redraw=false)
  {
//--- Get the pointer to the first closed position
   CPosition *pos=this.GetFirstClosedPosition();
   if(pos==NULL)
      return;
//--- Hide labels of all positions except the first one by its ID
   this.HideAllExceptOne(pos.ID());
   
//--- Display the labels of the first position on the chart and
//--- center the chart by the labels of the currently selected position
   this.Show(pos,chart_redraw);
   this.CentersChartByCurrentSelected();
  }

上記のGetFirstClosedPosition()メソッドを使用して、ミリ秒単位の時間で並び替えられたリストの最初のポジションへのポインタを取得します。最初のポジションを除くすべてのポジションのラベルを非表示にし、最初のポジションを表示して、チャート上のその取引のラベルを中央に配置します。


以下は、チャート上の最後のポジションのグラフィック表現を非表示にするメソッドです。

//+------------------------------------------------------------------+
//| Hide graphical representation of the last position from the chart|
//+------------------------------------------------------------------+
void CPositionsControl::HideLast(const bool chart_redraw=false)
  {
   CPosition *pos=this.GetLastClosedPosition();
   if(pos!=NULL)
      pos.Hide(chart_redraw);
  }

リストの最後のポジションへのポインタを取得し、そのHide()メソッドを呼び出します。


以下は、チャート上の最後のポジションのグラフィック表現を表示するメソッドです。

//+-----------------------------------------------------------------------+
//| Display the graphical representation of the last position on the chart|
//+-----------------------------------------------------------------------+
void CPositionsControl::ShowLast(const bool chart_redraw=false)
  {
//--- Get the pointer to the last closed position
   CPosition *pos=this.GetLastClosedPosition();
   if(pos==NULL)
      return;
//--- Hide labels of all positions except the last one by its ID
   this.HideAllExceptOne(pos.ID(), false);
   
//--- Display the labels of the last position on the chart and
//--- center the chart by the labels of the currently selected position
   this.Show(pos,chart_redraw);
   this.CentersChartByCurrentSelected();
  }

上記のGetLastClosedPosition()メソッドを使用して、リスト内の最後のポジションへのポインタをミリ秒単位の時間で並び替えて取得します。最後のポジションを除くすべてのポジションのラベルを非表示にし、最後のポジションを表示して、チャート上の取引のラベルを中央に配置します。


以下は、チャート上の現在のポジションのグラフィック表現を表示するメソッドです。

//+--------------------------------------------------------------------------+
//| Display a graphical representation of the current position on the chart  |
//+--------------------------------------------------------------------------+
void CPositionsControl::ShowCurrent(const bool chart_redraw=false)
  {
//--- Get a pointer to the currently selected closed position
   CPosition *curr=this.GetPositionObjByID(this.CurrentSelectedID());
   if(curr==NULL)
      return;
//--- Display the labels of the current position on the chart and
//--- center the chart by the labels of the currently selected position
   this.Show(curr,chart_redraw);
   this.CentersChartByCurrentSelected();
  }

m_current_id変数にIDが設定されているポジションへのポインタを取得し、そのグラフィック表現をチャート上に表示し、チャート上の取引ラベルでチャートを中央に配置します。


以下は、チャート上の前のポジションのグラフィック表現を表示するメソッドです。

//+------------------------------------------------------------------------+
//|Display a graphical representation of the previous position on the chart|
//+------------------------------------------------------------------------+
void CPositionsControl::ShowPrev(const bool chart_redraw=false)
  {
//--- Get the pointer to the current and previous positions
   CPosition *curr=this.GetPositionObjByID(this.CurrentSelectedID());
   CPosition *prev=this.GetPrevClosedPosition(curr);
   if(curr==NULL || prev==NULL)
      return;
//--- Hide the current position, display the previous one and
//--- center the chart by the labels of the currently selected position
   curr.Hide();
   this.Show(prev,chart_redraw);
   this.CentersChartByCurrentSelected();
  }

ここで、リスト内の現在選択されているポジションと前のポジションへのポインタを取得します。現在のポジションのラベルを非表示にし、前のポジションのラベルを表示します。これらが表示されると、ポジションIDがm_current_id変数に設定され、このポジションが現在のポジションであることを示します。ラベルを使用してグラフを中央に配置します。


以下は、チャート上の次のポジションのグラフィック表現を表示するメソッドです。

//+---------------------------------------------------------------------+
//| Display a graphical representation of the next position on the chart|
//+---------------------------------------------------------------------+
void CPositionsControl::ShowNext(const bool chart_redraw=false)
  {
//--- Get the pointer to the current and next positions
   CPosition *curr=this.GetPositionObjByID(this.CurrentSelectedID());
   CPosition *next=this.GetNextClosedPosition(curr);
   if(curr==NULL || next==NULL)
      return;
//--- Hide the current position, display the next one and
//--- center the chart by the labels of the currently selected position
   curr.Hide();
   this.Show(next,chart_redraw);
   this.CentersChartByCurrentSelected();
  }

このメソッドは前のメソッドと同じですが、ここではリスト内の現在選択されているポジションと次のポジションへのポインタを取得する点が異なります。現在のポジションのラベルを非表示にして、次のポジションのアイコンを表示します。これらが表示されると、ポジションIDがm_current_id変数に設定され、このポジションが現在のポジションであることを示します。ラベルを使用してグラフを中央に配置します。


以下は、指定されたポジションを除くチャート上のすべてのポジションののグラフィック表現を非表示にするメソッドです。

//+------------------------------------------------------------------+
//| Hide the graphical representation                                |
//| of all positions except the specified one                        |
//+------------------------------------------------------------------+
void CPositionsControl::HideAllExceptOne(const long pos_id,const bool chart_redraw=false)
  {
//--- In a loop by the list of positions
   int total=this.m_list_pos.Total();
   for(int i=0; i<total; i++)
     {
      //--- get the pointer to the next position and
      CPosition *pos=this.m_list_pos.At(i);
      if(pos==NULL || pos.ID()==pos_id)
         continue;
      //--- hide the graphical representation of the position
      pos.Hide();
     }
//--- After the loop, update the chart if the flag is set
   if(chart_redraw)
      ::ChartRedraw(this.m_chart_id);
  }

このメソッドは、チャート上にラベルを残す必要があるポジションのIDを受け取ります。他のすべてのポジションのラベルは非表示になります。


以下は、現在選択されているポジションにチャートを中央揃えするメソッドです。

//+------------------------------------------------------------------+
//| Center the chart at the currently selected position              |
//+------------------------------------------------------------------+
void CPositionsControl::CentersChartByCurrentSelected(void)
  {
//--- Get the index of the first visible bar on the chart and the number of visible bars
   int bar_open=0, bar_close=0;
   int first_visible=(int)::ChartGetInteger(this.m_chart_id, CHART_FIRST_VISIBLE_BAR);
   int visible_bars =(int)::ChartGetInteger(this.m_chart_id, CHART_VISIBLE_BARS);
   
//--- Get the position opening time and use it to get the opening bar
   datetime time_open=this.TimeOpenCurrentSelected();
   if(time_open!=0)
      bar_open=::iBarShift(this.m_symbol, PERIOD_CURRENT, time_open);
   
//--- Get the position opening time and use it to get the closing bar
   datetime time_close=this.TimeCloseCurrentSelected();
   if(time_close!=0)
      bar_close=::iBarShift(this.m_symbol, PERIOD_CURRENT, time_close);
      
//--- Calculate the width of the window the deal labels are located in
   int width=bar_open-bar_close;
   
//--- Calculate the chart offset so that the window with deals is in the center of the chart
   int shift=(bar_open + visible_bars/2 - width/2);
   
//--- If the window width is greater than the chart width, the opening deal is located on the second visible bar
   if(shift-bar_open<0)
      shift=bar_open+1;
   
//--- If the deal opening bar is to the left of the first visible bar of the chart
//--- or the deal opening bar is to the right of the chart last visible bar,
//--- scroll the chart by the calculated offset
   if(bar_open>first_visible || bar_open<first_visible+visible_bars)
      ::ChartNavigate(this.m_chart_id, CHART_CURRENT_POS, first_visible-shift);
  }

メソッドのロジック全体は、コードのコメントに完全に記述されています。この方法では、銘柄チャートをシフトして、視覚的に接続線のすべてのポジション取引がチャートの中央に配置されるようにしています。すべての取引のラベルがチャートの幅内に収まらない場合は、ポジションを開く取引がチャートの左から2番目の表示バーに表示されるようにチャートがシフトされます。


以下は、現在選択されているポジションの開始時刻を返すメソッドです。

//+------------------------------------------------------------------+
//| Return the opening time of the currently selected position       |
//+------------------------------------------------------------------+
datetime CPositionsControl::TimeOpenCurrentSelected(void)
  {
   CPosition *pos=this.GetPositionObjByID(this.CurrentSelectedID());
   return(pos!=NULL ? pos.Time() : 0);
  }

m_current_id変数に設定されたIDによって、現在選択されているポジションへのポインタを取得します。ポインタが正常に受信された場合は、ポジションオープン時間を返します。それ以外の場合は0を返します。


以下は、現在選択されているポジションの終了時刻を返すメソッドです。

//+------------------------------------------------------------------+
//| Return the current selected position closing time                |
//+------------------------------------------------------------------+
datetime CPositionsControl::TimeCloseCurrentSelected(void)
  {
   CPosition *pos=this.GetPositionObjByID(this.CurrentSelectedID());
   return(pos!=NULL ? pos.TimeClose() : 0);
  }

m_current_id変数に設定されたIDによって、現在選択されているポジションへのポインタを取得します。ポインタが正常に受信された場合は、ポジションの終了時間を返します。それ以外の場合は0を返します。


以下は、取引の種類によってポジションタイプを返すメソッドです。

//+------------------------------------------------------------------+
//| Return position type by deal type                                |
//+------------------------------------------------------------------+
ENUM_POSITION_TYPE CPositionsControl::PositionTypeByDeal(const CDeal *deal)
  {
   if(deal==NULL)
      return WRONG_VALUE;
   switch(deal.TypeDeal())
     {
      case DEAL_TYPE_BUY   :  return POSITION_TYPE_BUY;
      case DEAL_TYPE_SELL  :  return POSITION_TYPE_SELL;
      default              :  return WRONG_VALUE;
     }
  }

メソッドは取引へのポインタを受け取ります。取引の種類に応じて、適切なポジションタイプを返します。


以下は、履歴ポジションのリストを作成するメソッドです。

//+------------------------------------------------------------------+
//| Create historical position list                                  |
//+------------------------------------------------------------------+
bool CPositionsControl::Refresh(void)
  {
//--- If failed to request the history of deals and orders, return 'false'
   if(!::HistorySelect(0,::TimeCurrent()))
      return false;
      
//--- Set the flag of sorting by time in milliseconds for the position list
   this.m_list_pos.Sort(SORT_MODE_POSITION_TIME_MSC);
   
//--- Declare a result variable and a pointer to the position object
   bool res=true;
   CPosition *pos=NULL;

//--- In a loop based on the number of history deals
   int total=::HistoryDealsTotal();
   for(int i=total-1; i>=0; i--)
     {
      //--- get the ticket of the next deal in the list
      ulong ticket=::HistoryDealGetTicket(i);
      
      //--- If the deal ticket is not received, or it is not a buy/sell deal, or if the deal is not for the symbol set for the class, move on
      ENUM_DEAL_TYPE deal_type=(ENUM_DEAL_TYPE)::HistoryDealGetInteger(ticket, DEAL_TYPE);
      if(ticket==0 || (deal_type!=DEAL_TYPE_BUY && deal_type!=DEAL_TYPE_SELL) || ::HistoryDealGetString(ticket, DEAL_SYMBOL)!=this.m_symbol)
         continue;
      
      //--- Get the value of the position ID from the deal 
      long pos_id=::HistoryDealGetInteger(ticket, DEAL_POSITION_ID);
      
      //--- If this is a market position, move on 
      if(this.IsMarketPosition(pos_id))
         continue;
         
      //--- Get the pointer to a position object from the list
      pos=this.GetPositionObjByID(pos_id);
      
      //--- If there is no position with this ID in the list yet 
      if(pos==NULL)
        {
         //--- Create a new position object and, if the object could not be created, add 'false' to the 'res' variable and move on
         pos=new CPosition(pos_id, this.m_symbol);
         if(pos==NULL)
           {
            res &=false;
            continue;
           }
         
         //--- If failed to add the position object to the list, add 'false' to the 'res' variable, remove the position object and move on
         if(!this.m_list_pos.InsertSort(pos))
           {
            res &=false;
            delete pos;
            continue;
           }
        }
      
      //--- If the deal object could not be added to the list of deals of the position object, add 'false' to the 'res' variable and move on
      CDeal *deal=pos.DealAdd(ticket);
      if(deal==NULL)
        {
         res &=false;
         continue;
        }
      
      //--- All is successful.
      //--- Set position properties depending on the deal type
      if(deal.Entry()==DEAL_ENTRY_IN)
        {
         pos.SetTime(deal.Time());
         pos.SetTimeMsc(deal.TimeMsc());
         ENUM_POSITION_TYPE type=this.PositionTypeByDeal(deal);
         pos.SetTypePosition(type);
         pos.SetPriceOpen(deal.Price());
         pos.SetVolume(deal.Volume());
        }
      if(deal.Entry()==DEAL_ENTRY_OUT || deal.Entry()==DEAL_ENTRY_OUT_BY)
        {
         pos.SetPriceCurrent(deal.Price());
        }
      if(deal.Entry()==DEAL_ENTRY_INOUT)
        {
         ENUM_POSITION_TYPE type=this.PositionTypeByDeal(deal);
         pos.SetTypePosition(type);
         pos.SetVolume(deal.Volume()-pos.Volume());
        }
     }
//--- Set the flag of sorting by close time in milliseconds for the position list
   this.m_list_pos.Sort(SORT_MODE_POSITION_TIME_CLOSE_MSC);
     
//--- Return the result of creating and adding a position to the list
   return res;
  }

メソッドのロジックは、コードコメントで詳しく説明されています。

過去のポジションを検索するロジックを簡単に見てみましょう。クライアント端末には現在のポジションのリストのみが含まれています。各ポジションには、履歴取引リストに独自の取引があります。各取引には、参加したポジションのIDが含まれます。このIDは、取引が属するポジションを決定するために使用できます。したがって、履歴ポジションのリストを作成するには、履歴取引のリストをループし、IDを使用して取引が属するポジションを特定する必要があります。このようなIDを持つポジションがアクティブなポジションのリストに含まれていないことを確認してください。もし含まれていれば、まだポジションがクローズされていないため、取引はスキップする必要があります。そのようなポジションが市場に存在しない場合は、そのようなIDを持つ履歴ポジションオブジェクトを新規作成し、作成されたポジションの取引リストにこの取引を追加する必要があります。ポジションオブジェクトを作成する前に、そのようなポジションが以前に作成されているかどうかを確認する必要があります。作成されている場合は、ポジションオブジェクトを作成する必要はありません。代わりに、この取引をすでに作成されたポジションのリストに追加するだけです。もちろん、そのような取引がすでにポジションリストにある場合は、追加する必要はありません。過去の取引サイクルが完了すると、結果としてすでに決済されたポジションのリストが作成され、このリスト内の各ポジションにはその取引のリストが含まれます。


以下は、ポジションとその取引のプロパティを操作ログに出力するメソッドです。

//+------------------------------------------------------------------+
//| Print the properties of positions and their deals in the journal |
//+------------------------------------------------------------------+
void CPositionsControl::Print(void)
  {
   int total=this.m_list_pos.Total();
   for(int i=0; i<total; i++)
     {
      CPosition *pos=this.m_list_pos.At(i);
      if(pos==NULL)
         continue;
      pos.Print();
     }
  }

最も早いポジション(リストの最初から)から始めて、各連続ポジションを取得し、その説明をループで操作ログに出力します。


以下は、選択された各ポジションの説明を返すメソッドです。

//+------------------------------------------------------------------+
//| Return the description of the currently selected position        |
//+------------------------------------------------------------------+
string CPositionsControl::CurrentSelectedDescription(void)
  {
   CPosition *pos=this.GetPositionObjByID(this.CurrentSelectedID());
   return(pos!=NULL ? pos.Tooltip() : NULL);
  }

m_current_id変数にIDが設定されている現在選択されているポジションへのポインタを取得し、ツールチップを表示するために作成された文字列を返します。この文字列はチャートのコメントに表示するために使用できます。チャート上にラベルが表示されているポジションのいくつかのプロパティを視覚的に表示するために、テストEAで表示してみましょう。テストEAには次の機能が含まれます。

  1. 起動すると、過去のポジションのリストの形式で取引履歴が作成されます。各ポジションにはその取引のリストが含まれます。
  2. 履歴ポジションのリストが完了すると、最後にクローズされたポジションが、線でつながれたオープンラベルとクローズラベルとしてチャートに表示されます。
  3. Ctrlキーを押しながらカーソルキーを使用して、履歴ポジションのリスト内を移動できます。
    1. 左矢印キーを押すと、チャート上の前のポジションを表示が表示される
    2. 右矢印キーを押すと、チャート上の次のポジションを表示が表示される 
    3. 上矢印キーを押すと、チャート上の最初のポジションが表示される 
    4. 下矢印キーを押すと、チャート上のさいゴのポジションが表示される
  4. Shiftキーを押したままにすると、リスト内で現在選択されているポジションを説明するコメントがチャート上に表示されます。


検証

\MQL5\Experts\PositionsViewer\で、EAファイルPositionViewer.mq5を新規作成します。

履歴ポジション管理クラスのファイルをインクルードしキーコードEAグローバル変数のマクロ置換を宣言します。

//+------------------------------------------------------------------+
//|                                               PositionViewer.mq5 |
//|                                  Copyright 2024, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"

#include <PositionsViewer\PositionsControl.mqh>

#define   KEY_LEFT   37
#define   KEY_RIGHT  39
#define   KEY_UP     38
#define   KEY_DOWN   40

//--- global variables
CPositionsControl ExtPositions;     // Historical position class instance 
bool              ExtChartScroll;   // Chart scrolling flag
bool              ExtChartHistory;  // Trading history display flag

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+

EAのOnInit()ハンドラで、次のコードを設定します。

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Save the chart auto scroll flag and disable auto scroll
   ExtChartScroll=ChartGetInteger(ChartID(), CHART_AUTOSCROLL);
   ChartSetInteger(ChartID(), CHART_AUTOSCROLL, false);
   
//--- Save the trading history display flag and disable history display
   ExtChartHistory=ChartGetInteger(ChartID(), CHART_SHOW_TRADE_HISTORY);
   ChartSetInteger(ChartID(), CHART_SHOW_TRADE_HISTORY, false);
   
//--- Create a list of closed positions and display the list creation time in the journal
   ulong start=GetTickCount64();
   Print("Reading trade history and creating a list of historical positions");
   ExtPositions.Refresh();
   ulong msec=GetTickCount64()-start;
   PrintFormat("List of historical positions created in %I64u msec", msec);
   //ExtPositions.Print();
   
//--- If this is a launch after changing the chart period, display the currently selected position
   if(UninitializeReason()==REASON_CHARTCHANGE)
      ExtPositions.ShowCurrent(true);
//--- otherwise, display the last closed position
   else
      ExtPositions.ShowLast(true);

//--- Successful
   return(INIT_SUCCEEDED);
  }

チャートの自動スクロールや取引履歴の表示のフラグは、プログラム終了時に復元するためにここに保存されます。チャートの自動スクロールと履歴表示は無効になり、現在の銘柄に存在したすべての履歴ポジションのリストが作成され、リストの作成時刻が操作ログに表示されます。「//ExtPositions.Print();」のコメントを解除すると、クローズされたポジションのリストを作成した後、作成されたリストのすべての履歴ポジションが操作ログに表示されます。チャートの時間枠の変更でない場合は、最後にクローズしたポジションのラベルがチャートに描画され、ラベルが中央になるようにチャートがスクロールされます。チャート期間の変更の場合は、リスト内で現在選択されているポジションが表示されます。


OnDeinit()ハンドラで、チャートの自動スクロールと取引履歴表示の保存された値を復元し、チャートからコメントを削除します。

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- Restore the auto scroll and trading history property initial value and remove chart comments
   ChartSetInteger(ChartID(), CHART_AUTOSCROLL, ExtChartScroll);
   ChartSetInteger(ChartID(), CHART_SHOW_TRADE_HISTORY, ExtChartHistory);
   Comment("");
  }


OnTradeTransaction()ハンドラでは、新しい取引が発生するたびに、履歴ポジション制御クラスのリストを更新するメソッドを呼び出しますポジションがクローズされている場合は、チャートにそのラベルを表示します

//+------------------------------------------------------------------+
//| TradeTransaction function                                        |
//+------------------------------------------------------------------+
void OnTradeTransaction(const MqlTradeTransaction& trans,
                        const MqlTradeRequest& request,
                        const MqlTradeResult& result)
  {
//--- In case of a transaction type, add a new deal
   if(trans.type==TRADE_TRANSACTION_DEAL_ADD)
     {
      //--- update the list of positions and their deals
      ExtPositions.Refresh();
      
      //--- Get the new deal ticket
      ulong deal_ticket=trans.deal;
      
      //--- If the ticket is not received or failed to get the method for updating the position from the deal properties, leave
      long entry;
      if(deal_ticket==0 || !HistoryDealGetInteger(deal_ticket, DEAL_ENTRY, entry))
         return;

      //--- If this is an exit deal, display the last closed position on the chart
      if(entry==DEAL_ENTRY_OUT || entry==DEAL_ENTRY_OUT_BY)
         ExtPositions.ShowLast(true);
     }
  }


イベントハンドラでは、キーの押下イベントを追跡し、Ctrlキーを押しながらカーソルキーを押下してクローズポジションのリスト内を移動することに応答します。また、Shiftキーを押すと、チャートのコメントにポジションの説明が表示されチャートのスケールを変更して現在のポジションラベルでチャートを中央に配置することもできます。

//+------------------------------------------------------------------+
//| ChartEvent function                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
//--- If the event ID is pressing a key
   if(id==CHARTEVENT_KEYDOWN)
     {
      //--- If the Ctrl key is held down
      if(TerminalInfoInteger(TERMINAL_KEYSTATE_CONTROL)<0)
        {
         //--- If the chart scrolling with keys is active, disable it
         if((bool)ChartGetInteger(0, CHART_KEYBOARD_CONTROL))
            ChartSetInteger(0, CHART_KEYBOARD_CONTROL, false);
            
         //--- If the left key is pressed, display the previous closed position 

         if(lparam==KEY_LEFT)
            ExtPositions.ShowPrev(true);
            
         //--- If the right key is pressed, display the next closed position
         if(lparam==KEY_RIGHT)
            ExtPositions.ShowNext(true);
            
         //--- If the up key is pressed, display the first closed position
         if(lparam==KEY_UP)
            ExtPositions.ShowFirst(true);
            
         //--- If the down key is pressed, display the last closed position
         if(lparam==KEY_DOWN)
            ExtPositions.ShowLast(true);
        }
      //--- If Ctrl is not pressed,
      else
        {
         //--- If the chart scrolling with keys is inactive, enable it
         if(!(bool)ChartGetInteger(0, CHART_KEYBOARD_CONTROL))
            ChartSetInteger(0, CHART_KEYBOARD_CONTROL, true);
        }
     }

//--- If the Shift key is held down, display a description of the current position in the chart comment
   if(TerminalInfoInteger(TERMINAL_KEYSTATE_SHIFT)<0)
      Comment(ExtPositions.CurrentSelectedDescription());
//--- If the Shift key is not pressed, check the comment on the chart and delete it if it is not empty
   else
     {
      if(ChartGetString(ChartID(),CHART_COMMENT)!=NULL)
         Comment("");
     }
      
//--- If the horizontal scale of the chart has changed, display the currently selected position
   static int scale=-1;
   if(id==CHARTEVENT_CHART_CHANGE)
     {
      int scale_curr=(int)ChartGetInteger(ChartID(), CHART_SCALE);
      if(scale!=scale_curr)
        {
         ExtPositions.ShowCurrent(true);
         scale=scale_curr;
        }
     }
  }


これは、取引履歴の標準表示によるクローズされたポジションの配置です。


これは、記事の冒頭に書かれていたことをよく表しています。

... チャートがオープンポジションとクローズポジションのラベルで埋め尽くされ、取引履歴の表示が非効率的でわかりにくくなることがあります。特に、大量の取引を扱うトレーダーにとっては、チャートがすぐに一杯になり、取引活動を分析して情報に基づいた意思決定をおこなうことがほとんど不可能になるケースもあります...

それでは、EAをコンパイルしてチャート上で起動してみましょう。


起動時には、標準的な方法で表示される履歴は非表示になっており、最後にクローズされたポジションのみが表示されていました。Ctrlキーを押しながらカーソルキーを押すと、ポジションの履歴を移動できます。クローズされたポジションのリストを移動しながら、ここでクローズされたポジションのすべての取引のラベルがチャートに表示された場所で、取引履歴のグラフィック表現を簡単に見ることができます。

Shiftキーも押すと、現在選択されているクローズポジションの説明がチャートのコメントに表示されます。

ここで、取引が互いに十分な距離を置いて配置されているポジションを見つけ、水平スケールを拡大したときにチャートウィンドウの1つの表示領域に収まらない場合に、チャートの中央に配置される方法を確認してみましょう。


両方の取引がチャートウィンドウに収まらない場合は、左から2番目のバーのポジションに入る取引が見えるように中央に配置されることがわかります。

Shiftキーを押すと、チャートのコメントにこのクローズポジションの説明が表示されます。


これは、取引を結ぶ線の上にマウスカーソルを合わせなくても、ポジションの説明を確認できるため便利です。Ctrl + Shiftキーを押しながらカーソルキーを使用してクローズポジションのリスト内を移動すると、チャート上に現在表示されているクローズポジションの説明を明確に確認できます。


EAを起動すると、履歴ポジションのリストの作成が開始されます。私の場合、リスト作成時の2023年からの短い履歴は次のとおりです。

PositionViewer (EURUSD,M15)     Reading trade history and creating a list of historical positions
PositionViewer (EURUSD,M15)     List of historical positions created in 6422 msec

以降のチャート期間の切り替えでは、リストを再作成する時間は不要になります。

PositionViewer (EURUSD,M1)      Reading trade history and creating a list of historical positions
PositionViewer (EURUSD,M1)      List of historical positions created in 31 msec
PositionViewer (EURUSD,M5)      Reading trade history and creating a list of historical positions
PositionViewer (EURUSD,M5)      List of historical positions created in 47 msec
PositionViewer (EURUSD,M1)      Reading trade history and creating a list of historical positions
PositionViewer (EURUSD,M1)      List of historical positions created in 31 msec


結論

作成されたプログラムによって提供される取引履歴は、アクティブで頻繁な取引の場合に便利です。チャートがクローズされたポジションのラベルでオーバーロードされることはありません。各ポジションは独立して表示され、現在のポジションと次のポジションまたは前のポジションの表示の切り替えはカーソルキーでおこなわれます。各取引ラベルのツールチップには、標準の取引履歴よりも多くの情報が含まれています。取引を結ぶ線の上にマウスを置くと、クローズされたポジションに関する情報を含むツールチップが表示されます。

この記事で作成されたクラスは、独自の開発で使用して機能を拡張し、取引履歴やクローズされたポジションの履歴に関する情報を含む、より複雑で機能的なプログラムを作成できます。

すべてのクラスファイルとテストEAは記事に添付されており、独立した学習に利用できます。MQL5.zipアーカイブには、MQL5端末ディレクトリにすぐに解凍できるような形式でファイルが含まれており、ExpertsフォルダにPositionsViewer\フォルダが新規作成され、すべてのプロジェクトファイル(PositionViewer.mq5 EAファイルと3つのクラスファイル)が含められます。EAファイルをコンパイルして使用することもできます。


MetaQuotes Ltdによってロシア語から翻訳されました。
元の記事: https://www.mql5.com/ru/articles/15026

添付されたファイル |
Deal.mqh (64.2 KB)
Position.mqh (67.96 KB)
PositionViewer.mq5 (12.39 KB)
MQL5.zip (22.77 KB)
多通貨エキスパートアドバイザーの開発(第13回):第2段階の自動化 - グループへの選択 多通貨エキスパートアドバイザーの開発(第13回):第2段階の自動化 - グループへの選択
自動最適化の第1段階はすでに実装されています。いくつかの基準に従ってさまざま銘柄と時間枠の最適化を実行し、各パスの結果に関する情報をデータベースに保存します。ここで、最初の段階で見つかったものから最適なパラメータセットのグループを選択します。
MQL5における相関分析の要素:ピアソンのカイ二乗検定による独立性と相関比 MQL5における相関分析の要素:ピアソンのカイ二乗検定による独立性と相関比
この記事では相関分析の古典的なツールについて考察します。簡潔な理論的背景と、ピアソンのカイ二乗独立性検定および相関比の実践的な実装に重点が置かれています。
MQL5で古典的な戦略を再構築する(第3回):FTSE100予想 MQL5で古典的な戦略を再構築する(第3回):FTSE100予想
この連載では、よく知られた取引戦略を再検討し、AIを使って改善できるかどうかを検証します。本日の記事では、FTSE100について調べ、指数を構成する個別銘柄の一部を使って指数の予測を試みます。
最も注目すべき人工協調探索アルゴリズムの修正(ACSm) 最も注目すべき人工協調探索アルゴリズムの修正(ACSm)
ここでは、ACSアルゴリズムの進化、つまり収束特性とアルゴリズムの効率性を向上させることを目的とした3つの変更について検討します。主要な最適化アルゴリズムの1つを変換します。行列の修正から母集団形成に関する革新的なアプローチまでをカバーします。
OSZAR »