English Русский 中文 Español Deutsch Português
preview
プッシュ通知による取引の監視:MetaTrader 5サービスの例

プッシュ通知による取引の監視:MetaTrader 5サービスの例

MetaTrader 5 | 4 3月 2025, 12:12
93 0
Artyom Trishkin
Artyom Trishkin

内容


はじめに

金融市場で取引をおこなう場合、過去の一定期間におこなわれた取引の結果に関する情報が利用可能かどうかが重要な要素となります。

おそらく、すべてのトレーダーは、取引結果に基づいて戦略を調整するために、過去1日、1週間、1か月などの取引結果を監視する必要に少なくとも一度は直面したことがあるでしょう。MetaTrader5クライアント端末は、レポートの形で優れた統計情報を提供するため、取引結果を便利な視覚形式で評価できるようになります。このレポートは、ポートフォリオを最適化するだけでなく、リスクを軽減して取引の安定性を高める方法を理解するのに役立ちます。

戦略を分析するには、取引履歴のコンテキストメニューで[レポート]をクリックするか、[表示]メニューで[レポート]をクリックします(または、Alt+Eを押します)。




詳細は「新しいMetaTraderレポート:5つの主要取引指標」で参照してください。

何らかの理由でクライアント端末から提供される標準レポートが十分でない場合、MQL5言語では、レポートを生成してトレーダーのスマートフォンに送信するプログラムなど、独自のプログラムを作成する十分な機会が提供されます。これが今日議論する可能性です。

私たちのプログラムは、端末の起動時に開始し、取引口座の変更、日中の開始、レポートの作成と送信の時間を追跡する必要があります。このような目的には、サービスプログラムタイプが適しています。

MQL5リファレンスによれば、サービスはインジケーター、EA、スクリプトとは異なり、動作するためにチャートに接続する必要がないプログラムです。スクリプトと同様、サービスはトリガー以外のイベントを処理しません。サービスを起動するには、そのコードにOnStartハンドラ関数を含める必要があります。サービスはStart以外のイベントを受け入れませんが、EventChartCustomを使用してカスタムイベントをチャートへ送信できます。サービスは<端末ディレクトリ>\MQL5\Servicesに保存されます。

端末で実行されている各サービスは、独自のフローで動作します。つまり、ループされたサービスは他のプログラムの動作に影響を与えることはできません。私たちのサービスは、無限ループで動作し、指定された時間をチェックし、取引履歴全体を読み取り、クローズ済みポジションのリストを作成し、これらのリストをさまざまな基準で並べ替え、操作ログとプッシュ通知にレポートを表示し、ユーザーのスマートフォンに通知をプッシュします。また、サービスを初めて起動したときや設定を変更したときは、端末からプッシュ通知を送信できるかどうかをサービスで確認する必要があります。これを実現するには、ユーザーの応答と反応を待つメッセージウィンドウを介してユーザーとの対話を設定する必要があります。また、プッシュ通知を送信する場合、単位時間あたりの通知頻度に制限があるため、通知の送信に遅延を設定する必要があります。これらはすべて、クライアント端末で実行されている他のアプリケーションの動作に決して影響を与えません。上記のすべてに基づくと、サービスはこのようなプロジェクトを作成するための最も便利なツールです。

ここで、すべてを組み立てるために必要なコンポーネントのアイデアを形成する必要があります。


プロジェクトの構造

プログラムとそのコンポーネントを最初から最後まで見てみましょう。

  • サービスアプリ:サービスの継続的な運用期間全体にわたってアクティブであったすべての口座のデータにアクセスできます。口座データからクローズ済みポジションのリストを受け取り、それらを1つの一般的なリストに結合します。設定に応じて、サービスは現在アクティブな口座からのみクローズ済みポジションのデータを使用するか、クライアント端末の現在の口座と以前に使用された各口座からクローズ済みポジションのデータを使用することができます。
    取引統計は、口座リストから取得したクローズドポジションのデータに基づいて、必要な取引期間に対して作成されます。その後、プッシュ通知としてユーザーのスマートフォンに送信されます。さらに、取引統計はエキスパート端末ログに表形式で表示されます。
  • 口座コレクション:コレクションには、サービスの継続的な運用中に端末が接続されていた口座のリストが含まれます。口座コレクションを使用すると、リスト内の任意の口座と、すべての口座のすべてのクローズ済みポジションにアクセスできます。リストはサービスアプリで利用でき、サービスは選択をおこない、それに基づいて統計を作成します。
  • 口座オブジェクトクラス:サービスの継続的な運用中にこの口座で取引が実行されたすべてのクローズ済みポジションのリスト(コレクション)を含む、1つの口座のデータを保存します。口座のプロパティへのアクセス、この口座のクローズ済みポジションのリストの作成と更新を提供し、さまざまな選択基準によってクローズ済みポジションのリストを返します。
  • 履歴ポジションコレクションクラス:ポジションオブジェクトのリストが含まれており、クローズ済みポジションのプロパティへのアクセス、ポジションのリストの作成と更新を提供します。クローズ済みポジションのリストを返します。
  • ポジションオブジェクトクラス:クローズ済みポジションのプロパティを保存し、アクセスを提供します。さまざまなプロパティで2つのオブジェクトを比較する機能が含まれており、さまざまな選択基準によるポジションのリストを作成できます。このポジションの取引のリストが含まれており、それらにアクセスできます。
  • 取引オブジェクトクラス:単一の取引のプロパティを保存し、アクセスを提供します。このオブジェクトには、さまざまなプロパティで2つのオブジェクトを比較する機能が含まれており、さまざまな選択基準で取引のリストを作成できます。


取引履歴を気にせずにチャート上で直接取引を表示する方法」稿では、過去の取引リストからクローズ済みポジションを回復するという概念について説明しました。取引リストでは、取引プロパティで設定されたポジションID(PositionID)によって、各取引が特定のポジションに所属しているかどうかを判断できます。ポジションオブジェクトが作成され、見つかった取引がリストに配置されます。ここでも同じ方法を実行します。しかし、取引オブジェクトとポジションオブジェクトの構築を調整するために、各オブジェクトがプロパティを設定および取得するための同一のアクセス方法を持つ、完全に異なる、長年テストされた概念を使用します。この概念により、単一の共通キーでオブジェクトを作成し、リストに保存し、任意のオブジェクトプロパティでフィルタリングおよび並べ替え、指定されたプロパティのコンテキストで新しいリストを取得できるようになります。

このプロジェクトでクラスを構築する概念を正しく理解するには、次の記事をご覧ください。

  1. オブジェクトプロパティの構造「(第1回):概念、データ管理および最初の結果」 
  2. オブジェクトリストの構造「(第2回):過去の注文と取引のコレクション
  3. リスト内のオブジェクトをプロパティでフィルタリングする方法「(第3回):市場注文とポジションの収集、検索、並べ替え

本質的には、この3つの記事では、MQL5内の任意のオブジェクトのデータベースを作成し、それらをデータベースに保存して、必要なプロパティと値を取得する可能性について説明しています。これはまさにこのプロジェクトに必要な機能であり、このため、記事で説明されている概念に従ってオブジェクトとそのコレクションを構築することが決定されました。ここでのみ、protectedコンストラクタを持つ抽象オブジェクトクラスを作成せず、クラスでサポートされていないオブジェクトプロパティを定義せずに、少し簡単に実行されます。すべてがよりシンプルになります。各オブジェクトには独自のプロパティリストがあり、3つの配列に格納されて、書き込みと取得が可能になります。これらのオブジェクトはすべてリストに保存され、指定されたプロパティに従って必要なオブジェクトのみの新しいリストを取得できるようになります。

つまり、プロジェクトで作成された各オブジェクトには、MQL5のあらゆるオブジェクトやエンティティと同様に、独自のプロパティセットが存在します。MQL5にのみ、プロパティを取得するための標準関数があり、プロジェクトオブジェクトの場合、これらは各オブジェクトのクラスに直接設定された整数、実数、文字列のプロパティを取得するためのメソッドになります。さらに、これらすべてのオブジェクトは、リストに格納されます。このリストは、標準ライブラリのCObjectオブジェクトへのポインタの動的配列です。標準ライブラリクラスを使用すると、最小限のコストで複雑なプロジェクトを作成できます。この場合、これは、取引がおこなわれたすべての口座のクローズ済みポジションのデータベースを意味し、必要なプロパティによって並べ替えられ選択されたオブジェクトのリストを取得する機能を備えています。

    ポジションは、建てた瞬間(In dealを実行)からクローズした瞬間(Out/OutBuy dealを実行)までのみ存在します。つまり、市場オブジェクトとしてのみ存在するオブジェクトです。対照的に、取引は単に注文(取引注文)を実行した事実であるため、あらゆる取引は単なる履歴オブジェクトにすぎません。したがって、クライアント端末では、履歴リストにポジションはなく、現在の市場ポジションのリストにのみ存在します。

    したがって、すでに閉じられた市場ポジションを再現するには、過去の取引から以前に存在していたポジションを「組み立てる」必要があります。幸いなことに、各取引には、その取引が関与したポジションIDが含まれています。過去の取引のリストを調べ、リストから次の取引を取得し、ポジションIDをチェックして、ポジションオブジェクトを作成する必要があります。作成された取引オブジェクトを新しい履歴ポジションに追加します。これをさらに実行していきます。その間、引き続き作業する取引とポジションオブジェクトのクラスを作成しましょう。


    取引クラス

    <端末ディレクトリ>\MQL5\Services\に、新しいAccountReporter\フォルダを作成して、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"
    
    #include <Object.mqh>
    
    //+------------------------------------------------------------------+
    //| Deal class                                                       |
    //+------------------------------------------------------------------+
    class CDeal : public CObject
      {
      }
    
    


    次に、整数、実数、文字列の取引プロパティの列挙を追加し、private、protected、publicのセクションで、クラスメンバー変数と取引プロパティを処理するためのメソッドを宣言します。

    //+------------------------------------------------------------------+
    //|                                                         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>
    
    //--- Enumeration of integer deal properties
    enum ENUM_DEAL_PROPERTY_INT
      {
       DEAL_PROP_TICKET = 0,               // Deal ticket
       DEAL_PROP_ORDER,                    // Deal order number
       DEAL_PROP_TIME,                     // Deal execution time
       DEAL_PROP_TIME_MSC,                 // Deal execution time in milliseconds
       DEAL_PROP_TYPE,                     // Deal type
       DEAL_PROP_ENTRY,                    // Deal direction
       DEAL_PROP_MAGIC,                    // Deal magic number
       DEAL_PROP_REASON,                   // Deal execution reason or source
       DEAL_PROP_POSITION_ID,              // Position ID
       DEAL_PROP_SPREAD,                   // Spread when performing a deal
      };
      
    //--- Enumeration of real deal properties
    enum ENUM_DEAL_PROPERTY_DBL
      {
       DEAL_PROP_VOLUME = DEAL_PROP_SPREAD+1,// Deal volume
       DEAL_PROP_PRICE,                    // Deal price
       DEAL_PROP_COMMISSION,               // Commission
       DEAL_PROP_SWAP,                     // Accumulated swap when closing
       DEAL_PROP_PROFIT,                   // Deal financial result
       DEAL_PROP_FEE,                      // Deal fee
       DEAL_PROP_SL,                       // Stop Loss level
       DEAL_PROP_TP,                       // Take Profit level
      };
      
    //--- Enumeration of string deal properties
    enum ENUM_DEAL_PROPERTY_STR
      {
       DEAL_PROP_SYMBOL = DEAL_PROP_TP+1,  // Symbol the deal is executed for
       DEAL_PROP_COMMENT,                  // Deal comment
       DEAL_PROP_EXTERNAL_ID,              // Deal ID in an external trading system
      };
      
    //+------------------------------------------------------------------+
    //| Deal class                                                       |
    //+------------------------------------------------------------------+
    class CDeal : public CObject
      {
    private:
       MqlTick           m_tick;                                      // Deal tick structure
       long              m_lprop[DEAL_PROP_SPREAD+1];                 // Array for storing integer properties
       double            m_dprop[DEAL_PROP_TP-DEAL_PROP_SPREAD];      // Array for storing real properties
       string            m_sprop[DEAL_PROP_EXTERNAL_ID-DEAL_PROP_TP]; // Array for storing string properties
    
    //--- Return the index of the array the deal's (1) double and (2) string properties are located at
       int               IndexProp(ENUM_DEAL_PROPERTY_DBL property)   const { return(int)property-DEAL_PROP_SPREAD-1; }
       int               IndexProp(ENUM_DEAL_PROPERTY_STR property)   const { return(int)property-DEAL_PROP_TP-1;     }
       
    //--- 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:
    //--- 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
       
    public:
    //--- Set the properties
    //--- Set deal's (1) integer, (2) real and (3) string properties
       void              SetProperty(ENUM_DEAL_PROPERTY_INT property,long   value){ this.m_lprop[property]=value;                 }
       void              SetProperty(ENUM_DEAL_PROPERTY_DBL property,double value){ this.m_dprop[this.IndexProp(property)]=value; }
       void              SetProperty(ENUM_DEAL_PROPERTY_STR property,string value){ this.m_sprop[this.IndexProp(property)]=value; }
    
    //--- Integer properties
       void              SetTicket(const long ticket)              { this.SetProperty(DEAL_PROP_TICKET, ticket);                  }  // Ticket
       void              SetOrder(const long order)                { this.SetProperty(DEAL_PROP_ORDER, order);                    }  // Order
       void              SetTime(const datetime time)              { this.SetProperty(DEAL_PROP_TIME, time);                      }  // Time
       void              SetTimeMsc(const long value)              { this.SetProperty(DEAL_PROP_TIME_MSC, value);                 }  // Time in milliseconds
       void              SetTypeDeal(const ENUM_DEAL_TYPE type)    { this.SetProperty(DEAL_PROP_TYPE, type);                      }  // Type
       void              SetEntry(const ENUM_DEAL_ENTRY entry)     { this.SetProperty(DEAL_PROP_ENTRY, entry);                    }  // Direction
       void              SetMagic(const long magic)                { this.SetProperty(DEAL_PROP_MAGIC, magic);                    }  // Magic number
       void              SetReason(const ENUM_DEAL_REASON reason)  { this.SetProperty(DEAL_PROP_REASON, reason);                  }  // Deal execution reason or source
       void              SetPositionID(const long id)              { this.SetProperty(DEAL_PROP_POSITION_ID, id);                 }  // Position ID
    
    //--- Real properties
       void              SetVolume(const double volume)            { this.SetProperty(DEAL_PROP_VOLUME, volume);                  }  // Volume
       void              SetPrice(const double price)              { this.SetProperty(DEAL_PROP_PRICE, price);                    }  // Price
       void              SetCommission(const double value)         { this.SetProperty(DEAL_PROP_COMMISSION, value);               }  // Commission
       void              SetSwap(const double value)               { this.SetProperty(DEAL_PROP_SWAP, value);                     }  // Accumulated swap when closing
       void              SetProfit(const double value)             { this.SetProperty(DEAL_PROP_PROFIT, value);                   }  // Financial result
       void              SetFee(const double value)                { this.SetProperty(DEAL_PROP_FEE, value);                      }  // Deal fee
       void              SetSL(const double value)                 { this.SetProperty(DEAL_PROP_SL, value);                       }  // Stop Loss level
       void              SetTP(const double value)                 { this.SetProperty(DEAL_PROP_TP, value);                       }  // Take Profit level
    
    //--- String properties
       void              SetSymbol(const string symbol)            { this.SetProperty(DEAL_PROP_SYMBOL,symbol);                   }  // Symbol name
       void              SetComment(const string comment)          { this.SetProperty(DEAL_PROP_COMMENT,comment);                 }  // Comment
       void              SetExternalID(const string ext_id)        { this.SetProperty(DEAL_PROP_EXTERNAL_ID,ext_id);              }  // Deal ID in an external trading system
    
    //--- Get the properties
    //--- Return deal’s (1) integer, (2) real and (3) string property from the properties array
       long              GetProperty(ENUM_DEAL_PROPERTY_INT property) const { return this.m_lprop[property];                      }
       double            GetProperty(ENUM_DEAL_PROPERTY_DBL property) const { return this.m_dprop[this.IndexProp(property)];      }
       string            GetProperty(ENUM_DEAL_PROPERTY_STR property) const { return this.m_sprop[this.IndexProp(property)];      }
    
    //--- Integer properties
       long              Ticket(void)                        const { return this.GetProperty(DEAL_PROP_TICKET);                   }  // Ticket
       long              Order(void)                         const { return this.GetProperty(DEAL_PROP_ORDER);                    }  // Order
       datetime          Time(void)                          const { return (datetime)this.GetProperty(DEAL_PROP_TIME);           }  // Time
       long              TimeMsc(void)                       const { return this.GetProperty(DEAL_PROP_TIME_MSC);                 }  // Time in milliseconds
       ENUM_DEAL_TYPE    TypeDeal(void)                      const { return (ENUM_DEAL_TYPE)this.GetProperty(DEAL_PROP_TYPE);     }  // Type
       ENUM_DEAL_ENTRY   Entry(void)                         const { return (ENUM_DEAL_ENTRY)this.GetProperty(DEAL_PROP_ENTRY);   }  // Direction
       long              Magic(void)                         const { return this.GetProperty(DEAL_PROP_MAGIC);                    }  // Magic number
       ENUM_DEAL_REASON  Reason(void)                        const { return (ENUM_DEAL_REASON)this.GetProperty(DEAL_PROP_REASON); }  // Deal execution reason or source
       long              PositionID(void)                    const { return this.GetProperty(DEAL_PROP_POSITION_ID);              }  // Position ID
       
    //--- Real properties
       double            Volume(void)                        const { return this.GetProperty(DEAL_PROP_VOLUME);                   }  // Volume
       double            Price(void)                         const { return this.GetProperty(DEAL_PROP_PRICE);                    }  // Price
       double            Commission(void)                    const { return this.GetProperty(DEAL_PROP_COMMISSION);               }  // Commission
       double            Swap(void)                          const { return this.GetProperty(DEAL_PROP_SWAP);                     }  // Accumulated swap when closing
       double            Profit(void)                        const { return this.GetProperty(DEAL_PROP_PROFIT);                   }  // Financial result
       double            Fee(void)                           const { return this.GetProperty(DEAL_PROP_FEE);                      }  // Deal fee
       double            SL(void)                            const { return this.GetProperty(DEAL_PROP_SL);                       }  // Stop Loss level
       double            TP(void)                            const { return this.GetProperty(DEAL_PROP_TP);                       }  // Take Profit level
       
    //--- String properties
       string            Symbol(void)                        const { return this.GetProperty(DEAL_PROP_SYMBOL);                   }  // Symbol name
       string            Comment(void)                       const { return this.GetProperty(DEAL_PROP_COMMENT);                  }  // Comment
       string            ExternalID(void)                    const { return this.GetProperty(DEAL_PROP_EXTERNAL_ID);              }  // Deal ID in an external trading system
       
    //--- Additional properties
       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 (int)this.GetProperty(DEAL_PROP_SPREAD);              }  // Spread when performing a deal
       
    //--- 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 deal description
       string            Description(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){}
                         CDeal(const ulong ticket);
                        ~CDeal();
      };
    
    

    クラスメソッドの実装を見てみましょう。

    クラスコンストラクタでは、取引がすでに選択されており、そのプロパティを取得できることを考慮に入れます。

    //+------------------------------------------------------------------+
    //| Constructor                                                      |
    //+------------------------------------------------------------------+
    CDeal::CDeal(const ulong ticket)
      {
    //--- Store the properties
    //--- Integer properties
       this.SetTicket((long)ticket);                                                    // Deal ticket
       this.SetOrder(::HistoryDealGetInteger(ticket, DEAL_ORDER));                      // Order
       this.SetTime((datetime)::HistoryDealGetInteger(ticket, DEAL_TIME));              // Deal execution time
       this.SetTimeMsc(::HistoryDealGetInteger(ticket, DEAL_TIME_MSC));                 // Deal execution time in milliseconds
       this.SetTypeDeal((ENUM_DEAL_TYPE)::HistoryDealGetInteger(ticket, DEAL_TYPE));    // Type
       this.SetEntry((ENUM_DEAL_ENTRY)::HistoryDealGetInteger(ticket, DEAL_ENTRY));     // Direction
       this.SetMagic(::HistoryDealGetInteger(ticket, DEAL_MAGIC));                      // Magic number
       this.SetReason((ENUM_DEAL_REASON)::HistoryDealGetInteger(ticket, DEAL_REASON));  // Deal execution reason or source
       this.SetPositionID(::HistoryDealGetInteger(ticket, DEAL_POSITION_ID));           // Position ID
       
    //--- Real properties
       this.SetVolume(::HistoryDealGetDouble(ticket, DEAL_VOLUME));                     // Volume
       this.SetPrice(::HistoryDealGetDouble(ticket, DEAL_PRICE));                       // Price
       this.SetCommission(::HistoryDealGetDouble(ticket, DEAL_COMMISSION));             // Commission
       this.SetSwap(::HistoryDealGetDouble(ticket, DEAL_SWAP));                         // Accumulated swap when closing
       this.SetProfit(::HistoryDealGetDouble(ticket, DEAL_PROFIT));                     // Financial result
       this.SetFee(::HistoryDealGetDouble(ticket, DEAL_FEE));                           // Deal fee
       this.SetSL(::HistoryDealGetDouble(ticket, DEAL_SL));                             // Stop Loss level
       this.SetTP(::HistoryDealGetDouble(ticket, DEAL_TP));                             // Take Profit level
    
    //--- String properties
       this.SetSymbol(::HistoryDealGetString(ticket, DEAL_SYMBOL));                     // Symbol name
       this.SetComment(::HistoryDealGetString(ticket, DEAL_COMMENT));                   // Comment
       this.SetExternalID(::HistoryDealGetString(ticket, DEAL_EXTERNAL_ID));            // Deal ID in an external trading system
    
    //--- Additional parameters
       this.m_digits = (int)::SymbolInfoInteger(this.Symbol(), SYMBOL_DIGITS);
       this.m_point  = ::SymbolInfoDouble(this.Symbol(), SYMBOL_POINT);
       
    //--- Parameters for calculating spread
       this.m_bid = 0;
       this.m_ask = 0;
       this.SetProperty(DEAL_PROP_SPREAD, 0);
       
    //--- 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;
          int  spread=(int)::fabs((this.m_ask-this.m_bid)/this.m_point);
          this.SetProperty(DEAL_PROP_SPREAD, spread);
         }
    //--- If failed to obtain a historical tick, take the spread value of the minute bar the deal took place on
       else
          this.SetProperty(DEAL_PROP_SPREAD, this.GetSpreadM1());
      }
    
    

    計算を実行して取引情報を表示するには、取引プロパティ、および取引が実行された銘柄の数字とポイントをクラスプロパティ配列に保存します。次に、取引時の履歴ティックを取得します。この方法により、取引時のBid価格とAsk価格にアクセスでき、スプレッドを計算できるようになります。


    以下は、指定されたプロパティで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 DEAL_PROP_TICKET      :  return(this.Ticket() > obj.Ticket()          ?  1  :  this.Ticket() < obj.Ticket()           ? -1  :  0);
          case DEAL_PROP_ORDER       :  return(this.Order() > obj.Order()            ?  1  :  this.Order() < obj.Order()             ? -1  :  0);
          case DEAL_PROP_TIME        :  return(this.Time() > obj.Time()              ?  1  :  this.Time() < obj.Time()               ? -1  :  0);
          case DEAL_PROP_TIME_MSC    :  return(this.TimeMsc() > obj.TimeMsc()        ?  1  :  this.TimeMsc() < obj.TimeMsc()         ? -1  :  0);
          case DEAL_PROP_TYPE        :  return(this.TypeDeal() > obj.TypeDeal()      ?  1  :  this.TypeDeal() < obj.TypeDeal()       ? -1  :  0);
          case DEAL_PROP_ENTRY       :  return(this.Entry() > obj.Entry()            ?  1  :  this.Entry() < obj.Entry()             ? -1  :  0);
          case DEAL_PROP_MAGIC       :  return(this.Magic() > obj.Magic()            ?  1  :  this.Magic() < obj.Magic()             ? -1  :  0);
          case DEAL_PROP_REASON      :  return(this.Reason() > obj.Reason()          ?  1  :  this.Reason() < obj.Reason()           ? -1  :  0);
          case DEAL_PROP_POSITION_ID :  return(this.PositionID() > obj.PositionID()  ?  1  :  this.PositionID() < obj.PositionID()   ? -1  :  0);
          case DEAL_PROP_SPREAD      :  return(this.Spread() > obj.Spread()          ?  1  :  this.Spread() < obj.Spread()           ? -1  :  0);
          case DEAL_PROP_VOLUME      :  return(this.Volume() > obj.Volume()          ?  1  :  this.Volume() < obj.Volume()           ? -1  :  0);
          case DEAL_PROP_PRICE       :  return(this.Price() > obj.Price()            ?  1  :  this.Price() < obj.Price()             ? -1  :  0);
          case DEAL_PROP_COMMISSION  :  return(this.Commission() > obj.Commission()  ?  1  :  this.Commission() < obj.Commission()   ? -1  :  0);
          case DEAL_PROP_SWAP        :  return(this.Swap() > obj.Swap()              ?  1  :  this.Swap() < obj.Swap()               ? -1  :  0);
          case DEAL_PROP_PROFIT      :  return(this.Profit() > obj.Profit()          ?  1  :  this.Profit() < obj.Profit()           ? -1  :  0);
          case DEAL_PROP_FEE         :  return(this.Fee() > obj.Fee()                ?  1  :  this.Fee() < obj.Fee()                 ? -1  :  0);
          case DEAL_PROP_SL          :  return(this.SL() > obj.SL()                  ?  1  :  this.SL() < obj.SL()                   ? -1  :  0);
          case DEAL_PROP_TP          :  return(this.TP() > obj.TP()                  ?  1  :  this.TP() < obj.TP()                   ? -1  :  0);
          case DEAL_PROP_SYMBOL      :  return(this.Symbol() > obj.Symbol()          ?  1  :  this.Symbol() < obj.Symbol()           ? -1  :  0);
          case DEAL_PROP_COMMENT     :  return(this.Comment() > obj.Comment()        ?  1  :  this.Comment() < obj.Comment()         ? -1  :  0);
          case DEAL_PROP_EXTERNAL_ID :  return(this.ExternalID() > obj.ExternalID()  ?  1  :  this.ExternalID() < obj.ExternalID()   ? -1  :  0);
          default                    :  return(-1);
         }
      }
    
    

    これは、CObject親クラス内の同じ名前のメソッドをオーバーライドする仮想メソッドです。比較モード(取引オブジェクトのプロパティの1つ)に応じて、これらのプロパティは現在のオブジェクトと、ポインタによってメソッドに渡されたオブジェクトに対して比較されます。現在のオブジェクトプロパティの値が比較対象オブジェクトの値を超える場合、このメソッドは1を返します。少ない場合は-1になります。値が等しい場合は0となります。


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

    //+------------------------------------------------------------------+
    //| Return the deal type description                                 |
    //+------------------------------------------------------------------+
    string CDeal::TypeDescription(void) const
      {
       switch(this.TypeDeal())
         {
          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.TypeDeal();
         }
      }
    
    

    取引の種類に応じて、そのテキストの説明が返されます。このプロジェクトでは、すべてのタイプの取引を使用するのではなく、ポジション(買いまたは売り)に関連する取引のみを使用するため、このメソッドは冗長です。


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

    //+------------------------------------------------------------------+
    //| Return position change method                                    |
    //+------------------------------------------------------------------+
    string CDeal::EntryDescription(void) const
      {
       switch(this.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.Entry();
         }
      }
    
    


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

    //+------------------------------------------------------------------+
    //| Return a deal reason description                                 |
    //+------------------------------------------------------------------+
    string CDeal::ReasonDescription(void) const
      {
       switch(this.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.Reason();
         }
      }
    
    


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

    //+------------------------------------------------------------------+
    //| 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())));
      }
    
    


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

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


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

    //+------------------------------------------------------------------+
    //| 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'));
      }
    
    

    テキストの説明を返してログに記録するすべてのメソッドは、取引を説明することを目的としています。このプロジェクトでは実際には必要ありませんが、拡張と改善については常に覚えておく必要があります。このようなメソッドがここに存在するのはそのためです。

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

    //+------------------------------------------------------------------+
    //| Get the deal tick                                                |
    //| https://www.mql5.com/ru/forum/42122/page47#comment_37205238      |
    //+------------------------------------------------------------------+
    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.Symbol(), ticks, COPY_TICKS_INFO, this.TimeMsc()-(offset <<=1), this.TimeMsc());
        
    //--- 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.Symbol(), PERIOD_M1, this.Time());
       if(bar==WRONG_VALUE)
          return 0;
       return(::CopySpread(this.Symbol(), PERIOD_M1, bar, 1, array)==1 ? array[0] : 0);
      }
    
    

    取引クラスの準備ができました。クラスオブジェクトは、履歴ポジションクラスの取引リストに保存され、そこから必要な取引へのポインタを取得してそのデータを処理できるようになります。


    履歴ポジションクラス

    \MQL5\Services\AccountReporter\で、CPositionクラスの新しいファイルPosition.mqhを作成します。

    クラスは標準ライブラリのCObject基本オブジェクトクラスから継承する必要があります。

    //+------------------------------------------------------------------+
    //|                                                     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"
      
    //+------------------------------------------------------------------+
    //| Position class                                                   |
    //+------------------------------------------------------------------+
    class CPosition : public CObject
      {
      }
    
    


    ポジションクラスにはこのポジションのリストが含まれるため、作成されたファイルに取引クラスファイルCObjectオブジェクトへのポインタの動的配列のクラスファイルを含める必要があります。

    //+------------------------------------------------------------------+
    //|                                                     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>
    
    //+------------------------------------------------------------------+
    //| Position class                                                   |
    //+------------------------------------------------------------------+
    class CPosition : public CObject
      {
      }
    
    


    ここで、整数、実数、文字列の取引プロパティの列挙を追加し、private、protected、publicのセクションで、クラスメンバー変数とポジションプロパティを処理するためのメソッドを宣言します。

    //+------------------------------------------------------------------+
    //|                                                     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>
    
    //--- Enumeration of integer position properties
    enum ENUM_POSITION_PROPERTY_INT
      {
       POSITION_PROP_TICKET = 0,        // Position ticket
       POSITION_PROP_TIME,              // Position open time
       POSITION_PROP_TIME_MSC,          // Position open time in milliseconds
       POSITION_PROP_TIME_UPDATE,       // Position change time
       POSITION_PROP_TIME_UPDATE_MSC,   // Position change time in milliseconds
       POSITION_PROP_TYPE,              // Position type
       POSITION_PROP_MAGIC,             // Position magic number
       POSITION_PROP_IDENTIFIER,        // Position ID
       POSITION_PROP_REASON,            // Position open reason
       POSITION_PROP_ACCOUNT_LOGIN,     // Account number
       POSITION_PROP_TIME_CLOSE,        // Position close time
       POSITION_PROP_TIME_CLOSE_MSC,    // Position close time in milliseconds
      };
      
    //--- Enumeration of real position properties
    enum ENUM_POSITION_PROPERTY_DBL
      {
       POSITION_PROP_VOLUME = POSITION_PROP_TIME_CLOSE_MSC+1,// Position volume
       POSITION_PROP_PRICE_OPEN,        // Position price
       POSITION_PROP_SL,                // Stop Loss for open position
       POSITION_PROP_TP,                // Take Profit for open position
       POSITION_PROP_PRICE_CURRENT,     // Symbol current price
       POSITION_PROP_SWAP,              // Accumulated swap
       POSITION_PROP_PROFIT,            // Current profit
       POSITION_PROP_CONTRACT_SIZE,     // Symbol trade contract size
       POSITION_PROP_PRICE_CLOSE,       // Position close price
       POSITION_PROP_COMMISSIONS,       // Accumulated commission
       POSITION_PROP_FEE,               // Accumulated payment for deals
      };
    
    //--- Enumeration of string position properties
    enum ENUM_POSITION_PROPERTY_STR
      {
       POSITION_PROP_SYMBOL = POSITION_PROP_FEE+1,// A symbol the position is open for
       POSITION_PROP_COMMENT,           // Comment to a position
       POSITION_PROP_EXTERNAL_ID,       // Position ID in the external system
       POSITION_PROP_CURRENCY_PROFIT,   // Position symbol profit currency
       POSITION_PROP_ACCOUNT_CURRENCY,  // Account deposit currency
       POSITION_PROP_ACCOUNT_SERVER,    // Server name
      };
      
    //+------------------------------------------------------------------+
    //| Position class                                                   |
    //+------------------------------------------------------------------+
    class CPosition : public CObject
      {
    private:
       long              m_lprop[POSITION_PROP_TIME_CLOSE_MSC+1];                    // Array for storing integer properties
       double            m_dprop[POSITION_PROP_FEE-POSITION_PROP_TIME_CLOSE_MSC];    // Array for storing real properties
       string            m_sprop[POSITION_PROP_ACCOUNT_SERVER-POSITION_PROP_FEE];    // Array for storing string properties
    
    //--- Return the index of the array the order's (1) double and (2) string properties are located at
       int               IndexProp(ENUM_POSITION_PROPERTY_DBL property)   const { return(int)property-POSITION_PROP_TIME_CLOSE_MSC-1;}
       int               IndexProp(ENUM_POSITION_PROPERTY_STR property)   const { return(int)property-POSITION_PROP_FEE-1;           }
       
    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;
    
    //--- Additional properties
       int               m_profit_pt;         // Profit in points
       int               m_digits;            // Symbol digits
       double            m_point;             // One symbol point value
       double            m_tick_value;        // Calculated tick value
       
    //--- Return the pointer to (1) open and (2) close deal
       CDeal            *GetDealIn(void)   const;
       CDeal            *GetDealOut(void)  const;
       
    public:
    //--- Return the list of deals
       CArrayObj        *GetListDeals(void)                              { return(&this.m_list_deals);                                  }
    
    //--- Set the properties
    //--- Set (1) integer, (2) real and (3) string properties
       void              SetProperty(ENUM_POSITION_PROPERTY_INT property,long   value)  { this.m_lprop[property]=value;                 }
       void              SetProperty(ENUM_POSITION_PROPERTY_DBL property,double value)  { this.m_dprop[this.IndexProp(property)]=value; }
       void              SetProperty(ENUM_POSITION_PROPERTY_STR property,string value)  { this.m_sprop[this.IndexProp(property)]=value; }
    
    //--- Integer properties
       void              SetTicket(const long ticket)                    { this.SetProperty(POSITION_PROP_TICKET, ticket);              }  // Position ticket
       void              SetTime(const datetime time)                    { this.SetProperty(POSITION_PROP_TIME, time);                  }  // Position open time
       void              SetTimeMsc(const long value)                    { this.SetProperty(POSITION_PROP_TIME_MSC, value);             }  // Position open time in milliseconds since 01.01.1970 
       void              SetTimeUpdate(const datetime time)              { this.SetProperty(POSITION_PROP_TIME_UPDATE, time);           }  // Position update time
       void              SetTimeUpdateMsc(const long value)              { this.SetProperty(POSITION_PROP_TIME_UPDATE_MSC, value);      }  // Position update time in milliseconds since 01.01.1970
       void              SetTypePosition(const ENUM_POSITION_TYPE type)  { this.SetProperty(POSITION_PROP_TYPE, type);                  }  // Position type
       void              SetMagic(const long magic)                      { this.SetProperty(POSITION_PROP_MAGIC, magic);                }  // Magic number for a position (see ORDER_MAGIC)
       void              SetID(const long id)                            { this.SetProperty(POSITION_PROP_IDENTIFIER, id);              }  // Position ID
       void              SetReason(const ENUM_POSITION_REASON reason)    { this.SetProperty(POSITION_PROP_REASON, reason);              }  // Position open reason
       void              SetTimeClose(const datetime time)               { this.SetProperty(POSITION_PROP_TIME_CLOSE, time);            }  // Close time
       void              SetTimeCloseMsc(const long value)               { this.SetProperty(POSITION_PROP_TIME_CLOSE_MSC, value);       }  // Close time in milliseconds
       void              SetAccountLogin(const long login)               { this.SetProperty(POSITION_PROP_ACCOUNT_LOGIN, login);        }  // Acount number
       
    //--- Real properties
       void              SetVolume(const double volume)                  { this.SetProperty(POSITION_PROP_VOLUME, volume);              }  // Position volume
       void              SetPriceOpen(const double price)                { this.SetProperty(POSITION_PROP_PRICE_OPEN, price);           }  // Position price
       void              SetSL(const double value)                       { this.SetProperty(POSITION_PROP_SL, value);                   }  // Stop Loss level for an open position
       void              SetTP(const double value)                       { this.SetProperty(POSITION_PROP_TP, value);                   }  // Take Profit level for an open position
       void              SetPriceCurrent(const double price)             { this.SetProperty(POSITION_PROP_PRICE_CURRENT, price);        }  // Current price by symbol
       void              SetSwap(const double value)                     { this.SetProperty(POSITION_PROP_SWAP, value);                 }  // Accumulated swap
       void              SetProfit(const double value)                   { this.SetProperty(POSITION_PROP_PROFIT, value);               }  // Current profit
       void              SetPriceClose(const double price)               { this.SetProperty(POSITION_PROP_PRICE_CLOSE, price);          }  // Close price
       void              SetContractSize(const double value)             { this.SetProperty(POSITION_PROP_CONTRACT_SIZE, value);        }  // Symbol trading contract size
       void              SetCommissions(void);                                                                                             // Total commission of all deals
       void              SetFee(void);                                                                                                     // Total deal fee
       
    //--- String properties
       void              SetSymbol(const string symbol)                  { this.SetProperty(POSITION_PROP_SYMBOL, symbol);              }  // Symbol a position is opened for
       void              SetComment(const string comment)                { this.SetProperty(POSITION_PROP_COMMENT, comment);            }  // Position comment
       void              SetExternalID(const string ext_id)              { this.SetProperty(POSITION_PROP_EXTERNAL_ID, ext_id);         }  // Position ID in an external system (on the exchange)
       void              SetAccountServer(const string server)           { this.SetProperty(POSITION_PROP_ACCOUNT_SERVER, server);      }  // Server name
       void              SetAccountCurrency(const string currency)       { this.SetProperty(POSITION_PROP_ACCOUNT_CURRENCY, currency);  }  // Account deposit currency
       void              SetCurrencyProfit(const string currency)        { this.SetProperty(POSITION_PROP_CURRENCY_PROFIT, currency);   }  // Profit currency of the position symbol
       
    //--- Get the properties
    //--- Return (1) integer, (2) real and (3) string property from the properties array
       long              GetProperty(ENUM_POSITION_PROPERTY_INT property) const { return this.m_lprop[property];                        }
       double            GetProperty(ENUM_POSITION_PROPERTY_DBL property) const { return this.m_dprop[this.IndexProp(property)];        }
       string            GetProperty(ENUM_POSITION_PROPERTY_STR property) const { return this.m_sprop[this.IndexProp(property)];        }
    
    //--- Integer properties
       long              Ticket(void)                              const { return this.GetProperty(POSITION_PROP_TICKET);               }  // Position ticket
       datetime          Time(void)                                const { return (datetime)this.GetProperty(POSITION_PROP_TIME);       }  // Position open time
       long              TimeMsc(void)                             const { return this.GetProperty(POSITION_PROP_TIME_MSC);             }  // Position open time in milliseconds since 01.01.1970 
       datetime          TimeUpdate(void)                          const { return (datetime)this.GetProperty(POSITION_PROP_TIME_UPDATE);}  // Position change time
       long              TimeUpdateMsc(void)                       const { return this.GetProperty(POSITION_PROP_TIME_UPDATE_MSC);      }  // Position update time in milliseconds since 01.01.1970
       ENUM_POSITION_TYPE TypePosition(void)                       const { return (ENUM_POSITION_TYPE)this.GetProperty(POSITION_PROP_TYPE);}// Position type
       long              Magic(void)                               const { return this.GetProperty(POSITION_PROP_MAGIC);                }  // Magic number for a position (see ORDER_MAGIC)
       long              ID(void)                                  const { return this.GetProperty(POSITION_PROP_IDENTIFIER);           }  // Position ID
       ENUM_POSITION_REASON Reason(void)                           const { return (ENUM_POSITION_REASON)this.GetProperty(POSITION_PROP_REASON);}// Position opening reason
       datetime          TimeClose(void)                           const { return (datetime)this.GetProperty(POSITION_PROP_TIME_CLOSE); }  // Close time
       long              TimeCloseMsc(void)                        const { return this.GetProperty(POSITION_PROP_TIME_CLOSE_MSC);       }  // Close time in milliseconds
       long              AccountLogin(void)                        const { return this.GetProperty(POSITION_PROP_ACCOUNT_LOGIN);        }  // Login
       
    //--- Real properties
       double            Volume(void)                              const { return this.GetProperty(POSITION_PROP_VOLUME);               }  // Position volume
       double            PriceOpen(void)                           const { return this.GetProperty(POSITION_PROP_PRICE_OPEN);           }  // Position price
       double            SL(void)                                  const { return this.GetProperty(POSITION_PROP_SL);                   }  // Stop Loss level for an open position
       double            TP(void)                                  const { return this.GetProperty(POSITION_PROP_TP);                   }  // Take Profit level for an open position
       double            PriceCurrent(void)                        const { return this.GetProperty(POSITION_PROP_PRICE_CURRENT);        }  // Current price by symbol
       double            Swap(void)                                const { return this.GetProperty(POSITION_PROP_SWAP);                 }  // Accumulated swap
       double            Profit(void)                              const { return this.GetProperty(POSITION_PROP_PROFIT);               }  // Current profit
       double            ContractSize(void)                        const { return this.GetProperty(POSITION_PROP_CONTRACT_SIZE);        }  // Symbol trading contract size
       double            PriceClose(void)                          const { return this.GetProperty(POSITION_PROP_PRICE_CLOSE);          }  // Close price
       double            Commissions(void)                         const { return this.GetProperty(POSITION_PROP_COMMISSIONS);          }  // Total commission of all deals
       double            Fee(void)                                 const { return this.GetProperty(POSITION_PROP_FEE);                  }  // Total deal fee
       
    //--- String properties
       string            Symbol(void)                              const { return this.GetProperty(POSITION_PROP_SYMBOL);               }  // A symbol position is opened on
       string            Comment(void)                             const { return this.GetProperty(POSITION_PROP_COMMENT);              }  // Position comment
       string            ExternalID(void)                          const { return this.GetProperty(POSITION_PROP_EXTERNAL_ID);          }  // Position ID in an external system (on the exchange)
       string            AccountServer(void)                       const { return this.GetProperty(POSITION_PROP_ACCOUNT_SERVER);       }  // Server name
       string            AccountCurrency(void)                     const { return this.GetProperty(POSITION_PROP_ACCOUNT_CURRENCY);     }  // Account deposit currency
       string            CurrencyProfit(void)                      const { return this.GetProperty(POSITION_PROP_CURRENCY_PROFIT);      }  // Profit currency of the position symbol
       
       
    //--- Additional properties
       ulong             DealIn(void)                              const;                                                                  // Open deal ticket
       ulong             DealOut(void)                             const;                                                                  // Close deal ticket
       int               ProfitInPoints(void)                      const;                                                                  // Profit in points
       int               SpreadIn(void)                            const;                                                                  // Spread when opening
       int               SpreadOut(void)                           const;                                                                  // Spread when closing
       double            SpreadOutCost(void)                       const;                                                                  // Spread cost when closing
       double            PriceOutAsk(void)                         const;                                                                  // Ask price when closing
       double            PriceOutBid(void)                         const;                                                                  // Bid price when closing
       
    //--- Add a deal to the list of deals, return the pointer
       CDeal            *DealAdd(const long ticket);
       
    //--- 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 position description
       string            Description(void);
    
    //--- Print the properties of the position and its deals 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;
       
    //--- Constructor/destructor
                         CPosition(const long position_id, const string symbol);
                         CPosition(void){}
                        ~CPosition();
      };
    
    

    クラスメソッドの実装を見てみましょう。

    クラスコンストラクタのメソッドに渡されるパラメータからポジションIDと銘柄を設定し、口座と銘柄のデータを書き込みます。

    //+------------------------------------------------------------------+
    //| Constructor                                                      |
    //+------------------------------------------------------------------+
    CPosition::CPosition(const long position_id, const string symbol)
      {
       this.m_list_deals.Sort(DEAL_PROP_TIME_MSC);
       this.SetID(position_id);
       this.SetSymbol(symbol);
       this.SetAccountLogin(::AccountInfoInteger(ACCOUNT_LOGIN));
       this.SetAccountServer(::AccountInfoString(ACCOUNT_SERVER));
       this.SetAccountCurrency(::AccountInfoString(ACCOUNT_CURRENCY));
       this.SetCurrencyProfit(::SymbolInfoString(this.Symbol(),SYMBOL_CURRENCY_PROFIT));
       this.SetContractSize(::SymbolInfoDouble(this.Symbol(),SYMBOL_TRADE_CONTRACT_SIZE));
       this.m_digits     = (int)::SymbolInfoInteger(this.Symbol(),SYMBOL_DIGITS);
       this.m_point      = ::SymbolInfoDouble(this.Symbol(),SYMBOL_POINT);
       this.m_tick_value = ::SymbolInfoDouble(this.Symbol(), SYMBOL_TRADE_TICK_VALUE);
      }
    
    


    クラスのデストラクタで、ポジション取引のリストをクリアします。

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


    以下は、指定されたプロパティで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 POSITION_PROP_TICKET           :  return(this.Ticket() > obj.Ticket()                   ?  1  :  this.Ticket() < obj.Ticket()                    ? -1  :  0);
          case POSITION_PROP_TIME             :  return(this.Time() > obj.Time()                       ?  1  :  this.Time() < obj.Time()                        ? -1  :  0);
          case POSITION_PROP_TIME_MSC         :  return(this.TimeMsc() > obj.TimeMsc()                 ?  1  :  this.TimeMsc() < obj.TimeMsc()                  ? -1  :  0);
          case POSITION_PROP_TIME_UPDATE      :  return(this.TimeUpdate() > obj.TimeUpdate()           ?  1  :  this.TimeUpdate() < obj.TimeUpdate()            ? -1  :  0);
          case POSITION_PROP_TIME_UPDATE_MSC  :  return(this.TimeUpdateMsc() > obj.TimeUpdateMsc()     ?  1  :  this.TimeUpdateMsc() < obj.TimeUpdateMsc()      ? -1  :  0);
          case POSITION_PROP_TYPE             :  return(this.TypePosition() > obj.TypePosition()       ?  1  :  this.TypePosition() < obj.TypePosition()        ? -1  :  0);
          case POSITION_PROP_MAGIC            :  return(this.Magic() > obj.Magic()                     ?  1  :  this.Magic() < obj.Magic()                      ? -1  :  0);
          case POSITION_PROP_IDENTIFIER       :  return(this.ID() > obj.ID()                           ?  1  :  this.ID() < obj.ID()                            ? -1  :  0);
          case POSITION_PROP_REASON           :  return(this.Reason() > obj.Reason()                   ?  1  :  this.Reason() < obj.Reason()                    ? -1  :  0);
          case POSITION_PROP_ACCOUNT_LOGIN    :  return(this.AccountLogin() > obj.AccountLogin()       ?  1  :  this.AccountLogin() < obj.AccountLogin()        ? -1  :  0);
          case POSITION_PROP_TIME_CLOSE       :  return(this.TimeClose() > obj.TimeClose()             ?  1  :  this.TimeClose() < obj.TimeClose()              ? -1  :  0);
          case POSITION_PROP_TIME_CLOSE_MSC   :  return(this.TimeCloseMsc() > obj.TimeCloseMsc()       ?  1  :  this.TimeCloseMsc() < obj.TimeCloseMsc()        ? -1  :  0);
          case POSITION_PROP_VOLUME           :  return(this.Volume() > obj.Volume()                   ?  1  :  this.Volume() < obj.Volume()                    ? -1  :  0);
          case POSITION_PROP_PRICE_OPEN       :  return(this.PriceOpen() > obj.PriceOpen()             ?  1  :  this.PriceOpen() < obj.PriceOpen()              ? -1  :  0);
          case POSITION_PROP_SL               :  return(this.SL() > obj.SL()                           ?  1  :  this.SL() < obj.SL()                            ? -1  :  0);
          case POSITION_PROP_TP               :  return(this.TP() > obj.TP()                           ?  1  :  this.TP() < obj.TP()                            ? -1  :  0);
          case POSITION_PROP_PRICE_CURRENT    :  return(this.PriceCurrent() > obj.PriceCurrent()       ?  1  :  this.PriceCurrent() < obj.PriceCurrent()        ? -1  :  0);
          case POSITION_PROP_SWAP             :  return(this.Swap() > obj.Swap()                       ?  1  :  this.Swap() < obj.Swap()                        ? -1  :  0);
          case POSITION_PROP_PROFIT           :  return(this.Profit() > obj.Profit()                   ?  1  :  this.Profit() < obj.Profit()                    ? -1  :  0);
          case POSITION_PROP_CONTRACT_SIZE    :  return(this.ContractSize() > obj.ContractSize()       ?  1  :  this.ContractSize() < obj.ContractSize()        ? -1  :  0);
          case POSITION_PROP_PRICE_CLOSE      :  return(this.PriceClose() > obj.PriceClose()           ?  1  :  this.PriceClose() < obj.PriceClose()            ? -1  :  0);
          case POSITION_PROP_COMMISSIONS      :  return(this.Commissions() > obj.Commissions()         ?  1  :  this.Commissions() < obj.Commissions()          ? -1  :  0);
          case POSITION_PROP_FEE              :  return(this.Fee() > obj.Fee()                         ?  1  :  this.Fee() < obj.Fee()                          ? -1  :  0);
          case POSITION_PROP_SYMBOL           :  return(this.Symbol() > obj.Symbol()                   ?  1  :  this.Symbol() < obj.Symbol()                    ? -1  :  0);
          case POSITION_PROP_COMMENT          :  return(this.Comment() > obj.Comment()                 ?  1  :  this.Comment() < obj.Comment()                  ? -1  :  0);
          case POSITION_PROP_EXTERNAL_ID      :  return(this.ExternalID() > obj.ExternalID()           ?  1  :  this.ExternalID() < obj.ExternalID()            ? -1  :  0);
          case POSITION_PROP_CURRENCY_PROFIT  :  return(this.CurrencyProfit() > obj.CurrencyProfit()   ?  1  :  this.CurrencyProfit() < obj.CurrencyProfit()    ? -1  :  0);
          case POSITION_PROP_ACCOUNT_CURRENCY :  return(this.AccountCurrency() > obj.AccountCurrency() ?  1  :  this.AccountCurrency() < obj.AccountCurrency()  ? -1  :  0);
          case POSITION_PROP_ACCOUNT_SERVER   :  return(this.AccountServer() > obj.AccountServer()     ?  1  :  this.AccountServer() < obj.AccountServer()      ? -1  :  0);
          default                             :  return -1;
         }
      }
    
    

    これは、CObject親クラス内の同じ名前のメソッドをオーバーライドする仮想メソッドです。比較モード(ポジションオブジェクトのプロパティの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;
      }
    
    

    ポジション取引のリストを介したループで、DEAL_ENTRY_IN(市場参入)ポジション変更メソッドを使用して取引を探し、見つかった取引へのポインタを返します。


    以下は、市場決済取引へのポインタを返すメソッドです。

    //+------------------------------------------------------------------+
    //| 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;
      }
    
    

    ポジション取引のリストを介したループでは、DEAL_ENTRY_OUT(市場終了)またはDEAL_ENTRY_OUT_BYクローズバイ)ポジション変更メソッドを使用して取引を探し、見つかった取引へのポインタを返します。


    以下は、市場エントリー取引のチケットを返すメソッドです。

    //+------------------------------------------------------------------+
    //| 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 spread when opening                                       |
    //+------------------------------------------------------------------+
    int CPosition::SpreadIn(void) const
      {
       CDeal *deal=this.GetDealIn();
       return(deal!=NULL ? deal.Spread() : 0);
      }
    
    

    市場エントリー取引へのポインタを取得し、取引に設定されたスプレッドを返します。


    以下は、クローズ時にスプレッドを返すメソッドです。

    //+------------------------------------------------------------------+
    //| Return spread when closing                                       |
    //+------------------------------------------------------------------+
    int CPosition::SpreadOut(void) const
      {
       CDeal *deal=this.GetDealOut();
       return(deal!=NULL ? deal.Spread() : 0);
      }
    
    

    市場決済取引へのポインタを取得し、取引で設定されたスプレッドを返します。


    以下は、クローズ時にAsk価格を返すメソッドです。

    //+------------------------------------------------------------------+
    //| Return Ask price when closing                                    |
    //+------------------------------------------------------------------+
    double CPosition::PriceOutAsk(void) const
      {
       CDeal *deal=this.GetDealOut();
       return(deal!=NULL ? deal.Ask() : 0);
      }
    
    

    市場決済取引へのポインタを取得し、取引で設定された売り価格の値を返します。


    以下は、クローズ時にBid価格を返すメソッドです。

    //+------------------------------------------------------------------+
    //| Return the Bid price when closing                                |
    //+------------------------------------------------------------------+
    double CPosition::PriceOutBid(void) const
      {
       CDeal *deal=this.GetDealOut();
       return(deal!=NULL ? deal.Bid() : 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)::round(this.TypePosition()==POSITION_TYPE_BUY ? (close-open)/this.m_point : (open-close)/this.m_point);
      }
    
    


    以下は、クローズ時にスプレッドを返すメソッドです。

    //+------------------------------------------------------------------+
    //| Return the spread value when closing                             |
    //+------------------------------------------------------------------+
    double CPosition::SpreadOutCost(void) const
      {
    //--- Get close deal
       CDeal *deal=this.GetDealOut();
       if(deal==NULL)
          return 0;
    
    //--- Get position profit and position profit in points
       double profit=this.Profit();
       int profit_pt=this.ProfitInPoints();
       
    //--- If the profit is zero, return the spread value using the TickValue * Spread * Lots equation
       if(profit==0)
          return(this.m_tick_value * deal.Spread() * deal.Volume());
    
    //--- Calculate and return the spread value (proportion)
       return(profit_pt>0 ? deal.Spread() * ::fabs(profit / profit_pt) : 0);
      }
    
    

    このメソッドでは、スプレッド値を計算するために2つの方法が使用されます。

    1. ポジション利益がゼロでない場合、スプレッドのコストは、スプレッドサイズ(ポイント)×ポジション利益(金額)/ポジション利益(ポイント)の割合で計算されます。
    2. ポジション利益がゼロの場合、スプレッド値は「計算されたティック値*ポイントでのスプレッドサイズ*取引量」を使用して計算されます。


    以下は、すべての取引の合計手数料を設定するメソッドです。

    //+------------------------------------------------------------------+
    //| Set the total commission for all deals                           |
    //+------------------------------------------------------------------+
    void CPosition::SetCommissions(void)
      {
       double res=0;
       int total=this.m_list_deals.Total();
       for(int i=0; i<total; i++)
         {
          CDeal *deal=this.m_list_deals.At(i);
          res+=(deal!=NULL ? deal.Commission() : 0);
         }
       this.SetProperty(POSITION_PROP_COMMISSIONS, res);
      }
    
    

    ポジションの全期間にかかる手数料を決定するには、ポジション内のすべての取引の手数料を合計する必要があります。ポジション取引のリストをループして、各取引の手数料を結果の値に追加します。この値は最終的にメソッドから返されます。


    以下は、総取引手数料を設定するメソッドです。

    //+------------------------------------------------------------------+
    //| Sets the total deal fee                                          |
    //+------------------------------------------------------------------+
    void CPosition::SetFee(void)
      {
       double res=0;
       int total=this.m_list_deals.Total();
       for(int i=0; i<total; i++)
         {
          CDeal *deal=this.m_list_deals.At(i);
          res+=(deal!=NULL ? deal.Fee() : 0);
         }
       this.SetProperty(POSITION_PROP_FEE, res);
      }
    
    

    ここではすべてが前の方法とまったく同じで、各ポジション取引の手数料の合計を返します。

    これらのメソッドは両方とも、ポジションの取引がすべてリストされているときに呼び出す必要があります。そうしないと、結果が不完全になります。


    以下は、ポジション取引のリストに取引を追加するメソッドです。

    //+------------------------------------------------------------------+
    //| 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(DEAL_PROP_TICKET);
       
    //--- Set the result of checking if a deal with such a ticket is present in the list
       bool exist=(this.m_list_deals.Search(&this.m_temp_deal)!=WRONG_VALUE);
       
    //--- Return sorting by time in milliseconds for the list
       this.m_list_deals.Sort(DEAL_PROP_TIME_MSC);
    
    //--- If a deal with such a ticket is already in the list, return NULL
       if(exist)
          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
       if(deal.Entry()==DEAL_ENTRY_OUT || deal.Entry()==DEAL_ENTRY_OUT_BY)
         {
          this.SetProfit(deal.Profit());
          this.SetSwap(deal.Swap());
         }
         
    //--- Return the pointer to the created deal object
       return deal;   
      }
    
    

    メソッドのロジックはコードのコメントで完全に説明されています。このメソッドは、現在選択されている取引のチケットを受け取ります。リスト内にそのようなチケットを含む取引がまだない場合は、新しい取引オブジェクトが作成され、ポジション取引のリストに追加されます。


    以下は、いくつかのポジションプロパティの説明を返すメソッドです。

    //+------------------------------------------------------------------+
    //| Return a position type description                               |
    //+------------------------------------------------------------------+
    string CPosition::TypeDescription(void) const
      {
       return(this.TypePosition()==POSITION_TYPE_BUY ? "Buy" : this.TypePosition()==POSITION_TYPE_SELL ? "Sell" : "Unknown::"+(string)this.TypePosition());
      }
    //+------------------------------------------------------------------+
    //| 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()));
      }
    //+------------------------------------------------------------------+
    //| 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()));
      }
    //+------------------------------------------------------------------+
    //| Return a brief position description                              |
    //+------------------------------------------------------------------+
    string CPosition::Description(void)
      {
       return(::StringFormat("%I64d (%s): %s %.2f %s #%I64d, Magic %I64d", this.AccountLogin(), this.AccountServer(),
                             this.Symbol(), this.Volume(), this.TypeDescription(), this.ID(), this.Magic()));
      }
    
    

    これらのメソッドは、たとえば、操作ログに役職の説明を表示するために使用されます。

    Printメソッドを使用すると、操作ログにポジションの説明を表示できます。

    //+------------------------------------------------------------------+
    //| 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();
         }
      }
    
    

    まず、ポジションの説明を含むヘッダーが印刷されます。次に、すべてのポジション取引をループし、Print()メソッドを使用して各取引の説明が印刷されます。

    履歴ポジションクラスの準備が完了しました。次に、取引とポジションをプロパティ別に選択、検索、並べ替えるための静的クラスを作成しましょう。


    取引とポジションのプロパティで検索および並べ替えをおこなうクラス

    このクラスは「MetaTraderプログラムを簡単かつ迅速に開発するためのライブラリ(第3部):成行注文と取引のコレクション、検索と並び替え」稿で徹底的に検討されました(検索配置セクション)。

    \MQL5\Services\AccountReporter\で、CSelectクラスの新しいファイルSelect.mqhを作成します。

    //+------------------------------------------------------------------+
    //|                                                       Select.mqh |
    //|                        Copyright 2024, MetaQuotes Software Corp. |
    //|                             https://mql5.com/ja/users/artmedia70 |
    //+------------------------------------------------------------------+
    #property copyright "Copyright 2024, MetaQuotes Software Corp."
    #property link      "https://mql5.com/ja/users/artmedia70"
    #property version   "1.00"
    
    //+------------------------------------------------------------------+
    //| Class for sorting objects meeting the criterion                  |
    //+------------------------------------------------------------------+
    class CSelect
      {
      }
    


    比較モードの列挙を設定し、取引クラスとポジションクラスのファイルをインクルードしストレージリストを宣言します

    //+------------------------------------------------------------------+
    //|                                                       Select.mqh |
    //|                        Copyright 2024, MetaQuotes Software Corp. |
    //|                             https://mql5.com/ja/users/artmedia70 |
    //+------------------------------------------------------------------+
    #property copyright "Copyright 2024, MetaQuotes Software Corp."
    #property link      "https://mql5.com/ja/users/artmedia70"
    #property version   "1.00"
    
    enum ENUM_COMPARER_TYPE
      {
       EQUAL,                                                   // Equal
       MORE,                                                    // More
       LESS,                                                    // Less
       NO_EQUAL,                                                // Not equal
       EQUAL_OR_MORE,                                           // Equal or more
       EQUAL_OR_LESS                                            // Equal or less
      };
    
    //+------------------------------------------------------------------+
    //| Include files                                                    |
    //+------------------------------------------------------------------+
    #include "Deal.mqh"
    #include "Position.mqh"
    
    //+------------------------------------------------------------------+
    //| Storage list                                                     |
    //+------------------------------------------------------------------+
    CArrayObj   ListStorage; // Storage object for storing sorted collection lists
    //+------------------------------------------------------------------+
    //| Class for sorting objects meeting the criterion                  |
    //+------------------------------------------------------------------+
    class CSelect
      {
      }
    
    


    オブジェクトを選択し、検索条件を満たすリストを作成するためのすべてのメソッドを記述します。

    //+------------------------------------------------------------------+
    //|                                                       Select.mqh |
    //|                        Copyright 2024, MetaQuotes Software Corp. |
    //|                             https://mql5.com/ja/users/artmedia70 |
    //+------------------------------------------------------------------+
    #property copyright "Copyright 2024, MetaQuotes Software Corp."
    #property link      "https://mql5.com/ja/users/artmedia70"
    #property version   "1.00"
    
    enum ENUM_COMPARER_TYPE                                     // Comparison modes
      {
       EQUAL,                                                   // Equal
       MORE,                                                    // More
       LESS,                                                    // Less
       NO_EQUAL,                                                // Not equal
       EQUAL_OR_MORE,                                           // Equal or more
       EQUAL_OR_LESS                                            // Equal or less
      };
    
    //+------------------------------------------------------------------+
    //| Include files                                                    |
    //+------------------------------------------------------------------+
    #include "Deal.mqh"
    #include "Position.mqh"
    
    //+------------------------------------------------------------------+
    //| Storage list                                                     |
    //+------------------------------------------------------------------+
    CArrayObj   ListStorage; // Storage object for storing sorted collection lists
    //+------------------------------------------------------------------+
    //| Class for sorting objects meeting the criterion                  |
    //+------------------------------------------------------------------+
    class CSelect
      {
    private:
       //--- Method for comparing two values
       template<typename T>
       static bool       CompareValues(T value1,T value2,ENUM_COMPARER_TYPE mode);
    public:
    //+------------------------------------------------------------------+
    //| Deal handling methods                                            |
    //+------------------------------------------------------------------+
       //--- Return the list of deals with one out of (1) integer, (2) real and (3) string properties meeting a specified criterion
       static CArrayObj *ByDealProperty(CArrayObj *list_source,ENUM_DEAL_PROPERTY_INT property,long value,ENUM_COMPARER_TYPE mode);
       static CArrayObj *ByDealProperty(CArrayObj *list_source,ENUM_DEAL_PROPERTY_DBL property,double value,ENUM_COMPARER_TYPE mode);
       static CArrayObj *ByDealProperty(CArrayObj *list_source,ENUM_DEAL_PROPERTY_STR property,string value,ENUM_COMPARER_TYPE mode);
       
       //--- Return the deal index with the maximum value of the (1) integer, (2) real and (3) string properties
       static int        FindDealMax(CArrayObj *list_source,ENUM_DEAL_PROPERTY_INT property);
       static int        FindDealMax(CArrayObj *list_source,ENUM_DEAL_PROPERTY_DBL property);
       static int        FindDealMax(CArrayObj *list_source,ENUM_DEAL_PROPERTY_STR property);
       
       //--- Return the deal index with the minimum value of the (1) integer, (2) real and (3) string properties
       static int        FindDealMin(CArrayObj *list_source,ENUM_DEAL_PROPERTY_INT property);
       static int        FindDealMin(CArrayObj *list_source,ENUM_DEAL_PROPERTY_DBL property);
       static int        FindDealMin(CArrayObj *list_source,ENUM_DEAL_PROPERTY_STR property);
       
    //+------------------------------------------------------------------+
    //| Position handling methods                                        |
    //+------------------------------------------------------------------+
       //--- Return the list of positions with one out of (1) integer, (2) real and (3) string properties meeting a specified criterion
       static CArrayObj *ByPositionProperty(CArrayObj *list_source,ENUM_POSITION_PROPERTY_INT property,long value,ENUM_COMPARER_TYPE mode);
       static CArrayObj *ByPositionProperty(CArrayObj *list_source,ENUM_POSITION_PROPERTY_DBL property,double value,ENUM_COMPARER_TYPE mode);
       static CArrayObj *ByPositionProperty(CArrayObj *list_source,ENUM_POSITION_PROPERTY_STR property,string value,ENUM_COMPARER_TYPE mode);
       
       //--- Return the position index with the maximum value of the (1) integer, (2) real and (3) string properties
       static int        FindPositionMax(CArrayObj *list_source,ENUM_POSITION_PROPERTY_INT property);
       static int        FindPositionMax(CArrayObj *list_source,ENUM_POSITION_PROPERTY_DBL property);
       static int        FindPositionMax(CArrayObj *list_source,ENUM_POSITION_PROPERTY_STR property);
       
       //--- Return the position index with the minimum value of the (1) integer, (2) real and (3) string properties
       static int        FindPositionMin(CArrayObj *list_source,ENUM_POSITION_PROPERTY_INT property);
       static int        FindPositionMin(CArrayObj *list_source,ENUM_POSITION_PROPERTY_DBL property);
       static int        FindPositionMin(CArrayObj *list_source,ENUM_POSITION_PROPERTY_STR property);
      };
    //+------------------------------------------------------------------+
    //| Method for comparing two values                                  |
    //+------------------------------------------------------------------+
    template<typename T>
    bool CSelect::CompareValues(T value1,T value2,ENUM_COMPARER_TYPE mode)
      {
       switch(mode)
         {
          case EQUAL           :  return(value1==value2   ?  true  :  false);
          case NO_EQUAL        :  return(value1!=value2   ?  true  :  false);
          case MORE            :  return(value1>value2    ?  true  :  false);
          case LESS            :  return(value1<value2    ?  true  :  false);
          case EQUAL_OR_MORE   :  return(value1>=value2   ?  true  :  false);
          case EQUAL_OR_LESS   :  return(value1<=value2   ?  true  :  false);
          default              :  return false;
         }
      }
    //+------------------------------------------------------------------+
    //| Deal list handling methods                                       |
    //+------------------------------------------------------------------+
    //+------------------------------------------------------------------+
    //| Return the list of deals with one integer                        |
    //| property meeting the specified criterion                         |
    //+------------------------------------------------------------------+
    CArrayObj *CSelect::ByDealProperty(CArrayObj *list_source,ENUM_DEAL_PROPERTY_INT property,long value,ENUM_COMPARER_TYPE mode)
      {
       if(list_source==NULL)
          return NULL;
       CArrayObj *list=new CArrayObj();
       if(list==NULL)
          return NULL;
       list.FreeMode(false);
       if(!ListStorage.Add(list))
         {
          delete list;
          return NULL;
         }
       int total=list_source.Total();
       for(int i=0; i<total; i++)
         {
          CDeal *obj=list_source.At(i);
          long obj_prop=obj.GetProperty(property);
          if(CompareValues(obj_prop, value, mode))
             list.Add(obj);
         }
       return list;
      }
    //+------------------------------------------------------------------+
    //| Return the list of deals with one real                           |
    //| property meeting the specified criterion                         |
    //+------------------------------------------------------------------+
    CArrayObj *CSelect::ByDealProperty(CArrayObj *list_source,ENUM_DEAL_PROPERTY_DBL property,double value,ENUM_COMPARER_TYPE mode)
      {
       if(list_source==NULL)
          return NULL;
       CArrayObj *list=new CArrayObj();
       if(list==NULL)
          return NULL;
       list.FreeMode(false);
       if(!ListStorage.Add(list))
         {
          delete list;
          return NULL;
         }
       for(int i=0; i<list_source.Total(); i++)
         {
          CDeal *obj=list_source.At(i);
          double obj_prop=obj.GetProperty(property);
          if(CompareValues(obj_prop,value,mode))
             list.Add(obj);
         }
       return list;
      }
    //+------------------------------------------------------------------+
    //| Return the list of deals with one string                         |
    //| property meeting the specified criterion                         |
    //+------------------------------------------------------------------+
    CArrayObj *CSelect::ByDealProperty(CArrayObj *list_source,ENUM_DEAL_PROPERTY_STR property,string value,ENUM_COMPARER_TYPE mode)
      {
       if(list_source==NULL)
          return NULL;
       CArrayObj *list=new CArrayObj();
       if(list==NULL)
          return NULL;
       list.FreeMode(false);
       if(!ListStorage.Add(list))
         {
          delete list;
          return NULL;
         }
       for(int i=0; i<list_source.Total(); i++)
         {
          CDeal *obj=list_source.At(i);
          string obj_prop=obj.GetProperty(property);
          if(CompareValues(obj_prop,value,mode))
             list.Add(obj);
         }
       return list;
      }
    //+------------------------------------------------------------------+
    //| Return the deal index in the list                                |
    //| with the maximum integer property value                          |
    //+------------------------------------------------------------------+
    int CSelect::FindDealMax(CArrayObj *list_source,ENUM_DEAL_PROPERTY_INT property)
      {
       if(list_source==NULL)
          return WRONG_VALUE;
       int index=0;
       CDeal *max_obj=NULL;
       int total=list_source.Total();
       if(total==0)
          return WRONG_VALUE;
       for(int i=1; i<total; i++)
         {
          CDeal *obj=list_source.At(i);
          long obj1_prop=obj.GetProperty(property);
          max_obj=list_source.At(index);
          long obj2_prop=max_obj.GetProperty(property);
          if(CompareValues(obj1_prop,obj2_prop,MORE))
             index=i;
         }
       return index;
      }
    //+------------------------------------------------------------------+
    //| Return the deal index in the list                                |
    //| with the maximum real property value                             |
    //+------------------------------------------------------------------+
    int CSelect::FindDealMax(CArrayObj *list_source,ENUM_DEAL_PROPERTY_DBL property)
      {
       if(list_source==NULL)
          return WRONG_VALUE;
       int index=0;
       CDeal *max_obj=NULL;
       int total=list_source.Total();
       if(total==0)
          return WRONG_VALUE;
       for(int i=1; i<total; i++)
         {
          CDeal *obj=list_source.At(i);
          double obj1_prop=obj.GetProperty(property);
          max_obj=list_source.At(index);
          double obj2_prop=max_obj.GetProperty(property);
          if(CompareValues(obj1_prop,obj2_prop,MORE))
             index=i;
         }
       return index;
      }
    //+------------------------------------------------------------------+
    //| Return the deal index in the list                                |
    //| with the maximum string property value                           |
    //+------------------------------------------------------------------+
    int CSelect::FindDealMax(CArrayObj *list_source,ENUM_DEAL_PROPERTY_STR property)
      {
       if(list_source==NULL)
          return WRONG_VALUE;
       int index=0;
       CDeal *max_obj=NULL;
       int total=list_source.Total();
       if(total==0) return WRONG_VALUE;
       for(int i=1; i<total; i++)
         {
          CDeal *obj=list_source.At(i);
          string obj1_prop=obj.GetProperty(property);
          max_obj=list_source.At(index);
          string obj2_prop=max_obj.GetProperty(property);
          if(CompareValues(obj1_prop,obj2_prop,MORE))
             index=i;
         }
       return index;
      }
    //+------------------------------------------------------------------+
    //| Return the deal index in the list                                |
    //| with the minimum integer property value                          |
    //+------------------------------------------------------------------+
    int CSelect::FindDealMin(CArrayObj* list_source,ENUM_DEAL_PROPERTY_INT property)
      {
       int index=0;
       CDeal *min_obj=NULL;
       int total=list_source.Total();
       if(total==0)
          return WRONG_VALUE;
       for(int i=1; i<total; i++)
         {
          CDeal *obj=list_source.At(i);
          long obj1_prop=obj.GetProperty(property);
          min_obj=list_source.At(index);
          long obj2_prop=min_obj.GetProperty(property);
          if(CompareValues(obj1_prop,obj2_prop,LESS))
             index=i;
         }
       return index;
      }
    //+------------------------------------------------------------------+
    //| Return the deal index in the list                                |
    //| with the minimum real property value                             |
    //+------------------------------------------------------------------+
    int CSelect::FindDealMin(CArrayObj* list_source,ENUM_DEAL_PROPERTY_DBL property)
      {
       int index=0;
       CDeal *min_obj=NULL;
       int total=list_source.Total();
       if(total==0)
          return WRONG_VALUE;
       for(int i=1; i<total; i++)
         {
          CDeal *obj=list_source.At(i);
          double obj1_prop=obj.GetProperty(property);
          min_obj=list_source.At(index);
          double obj2_prop=min_obj.GetProperty(property);
          if(CompareValues(obj1_prop,obj2_prop,LESS))
             index=i;
         }
       return index;
      }
    //+------------------------------------------------------------------+
    //| Return the deal index in the list                                |
    //| with the minimum string property value                           |
    //+------------------------------------------------------------------+
    int CSelect::FindDealMin(CArrayObj* list_source,ENUM_DEAL_PROPERTY_STR property)
      {
       int index=0;
       CDeal *min_obj=NULL;
       int total=list_source.Total();
       if(total==0)
          return WRONG_VALUE;
       for(int i=1; i<total; i++)
         {
          CDeal *obj=list_source.At(i);
          string obj1_prop=obj.GetProperty(property);
          min_obj=list_source.At(index);
          string obj2_prop=min_obj.GetProperty(property);
          if(CompareValues(obj1_prop,obj2_prop,LESS))
             index=i;
         }
       return index;
      }
    //+------------------------------------------------------------------+
    //| Position list handling method                                    |
    //+------------------------------------------------------------------+
    //+------------------------------------------------------------------+
    //| Return the list of positions with one integer                    |
    //| property meeting the specified criterion                         |
    //+------------------------------------------------------------------+
    CArrayObj *CSelect::ByPositionProperty(CArrayObj *list_source,ENUM_POSITION_PROPERTY_INT property,long value,ENUM_COMPARER_TYPE mode)
      {
       if(list_source==NULL)
          return NULL;
       CArrayObj *list=new CArrayObj();
       if(list==NULL)
          return NULL;
       list.FreeMode(false);
       if(!ListStorage.Add(list))
         {
          delete list;
          return NULL;
         }
       int total=list_source.Total();
       for(int i=0; i<total; i++)
         {
          CPosition *obj=list_source.At(i);
          long obj_prop=obj.GetProperty(property);
          if(CompareValues(obj_prop, value, mode))
             list.Add(obj);
         }
       return list;
      }
    //+------------------------------------------------------------------+
    //| Return the list of positions with one real                       |
    //| property meeting the specified criterion                         |
    //+------------------------------------------------------------------+
    CArrayObj *CSelect::ByPositionProperty(CArrayObj *list_source,ENUM_POSITION_PROPERTY_DBL property,double value,ENUM_COMPARER_TYPE mode)
      {
       if(list_source==NULL)
          return NULL;
       CArrayObj *list=new CArrayObj();
       if(list==NULL)
          return NULL;
       list.FreeMode(false);
       if(!ListStorage.Add(list))
         {
          delete list;
          return NULL;
         }
       for(int i=0; i<list_source.Total(); i++)
         {
          CPosition *obj=list_source.At(i);
          double obj_prop=obj.GetProperty(property);
          if(CompareValues(obj_prop,value,mode))
             list.Add(obj);
         }
       return list;
      }
    //+------------------------------------------------------------------+
    //| Return the list of positions with one string                     |
    //| property meeting the specified criterion                         |
    //+------------------------------------------------------------------+
    CArrayObj *CSelect::ByPositionProperty(CArrayObj *list_source,ENUM_POSITION_PROPERTY_STR property,string value,ENUM_COMPARER_TYPE mode)
      {
       if(list_source==NULL)
          return NULL;
       CArrayObj *list=new CArrayObj();
       if(list==NULL)
          return NULL;
       list.FreeMode(false);
       if(!ListStorage.Add(list))
         {
          delete list;
          return NULL;
         }
       for(int i=0; i<list_source.Total(); i++)
         {
          CPosition *obj=list_source.At(i);
          string obj_prop=obj.GetProperty(property);
          if(CompareValues(obj_prop,value,mode))
             list.Add(obj);
         }
       return list;
      }
    //+------------------------------------------------------------------+
    //| Return the position index in the list                            |
    //| with the maximum integer property value                          |
    //+------------------------------------------------------------------+
    int CSelect::FindPositionMax(CArrayObj *list_source,ENUM_POSITION_PROPERTY_INT property)
      {
       if(list_source==NULL)
          return WRONG_VALUE;
       int index=0;
       CPosition *max_obj=NULL;
       int total=list_source.Total();
       if(total==0)
          return WRONG_VALUE;
       for(int i=1; i<total; i++)
         {
          CPosition *obj=list_source.At(i);
          long obj1_prop=obj.GetProperty(property);
          max_obj=list_source.At(index);
          long obj2_prop=max_obj.GetProperty(property);
          if(CompareValues(obj1_prop,obj2_prop,MORE))
             index=i;
         }
       return index;
      }
    //+------------------------------------------------------------------+
    //| Return the position index in the list                            |
    //| with the maximum real property value                             |
    //+------------------------------------------------------------------+
    int CSelect::FindPositionMax(CArrayObj *list_source,ENUM_POSITION_PROPERTY_DBL property)
      {
       if(list_source==NULL)
          return WRONG_VALUE;
       int index=0;
       CPosition *max_obj=NULL;
       int total=list_source.Total();
       if(total==0)
          return WRONG_VALUE;
       for(int i=1; i<total; i++)
         {
          CPosition *obj=list_source.At(i);
          double obj1_prop=obj.GetProperty(property);
          max_obj=list_source.At(index);
          double obj2_prop=max_obj.GetProperty(property);
          if(CompareValues(obj1_prop,obj2_prop,MORE))
             index=i;
         }
       return index;
      }
    //+------------------------------------------------------------------+
    //| Return the position index in the list                            |
    //| with the maximum string property value                           |
    //+------------------------------------------------------------------+
    int CSelect::FindPositionMax(CArrayObj *list_source,ENUM_POSITION_PROPERTY_STR property)
      {
       if(list_source==NULL)
          return WRONG_VALUE;
       int index=0;
       CPosition *max_obj=NULL;
       int total=list_source.Total();
       if(total==0) return WRONG_VALUE;
       for(int i=1; i<total; i++)
         {
          CPosition *obj=list_source.At(i);
          string obj1_prop=obj.GetProperty(property);
          max_obj=list_source.At(index);
          string obj2_prop=max_obj.GetProperty(property);
          if(CompareValues(obj1_prop,obj2_prop,MORE))
             index=i;
         }
       return index;
      }
    //+------------------------------------------------------------------+
    //| Return the position index in the list                            |
    //| with the minimum integer property value                          |
    //+------------------------------------------------------------------+
    int CSelect::FindPositionMin(CArrayObj* list_source,ENUM_POSITION_PROPERTY_INT property)
      {
       int index=0;
       CPosition *min_obj=NULL;
       int total=list_source.Total();
       if(total==0)
          return WRONG_VALUE;
       for(int i=1; i<total; i++)
         {
          CPosition *obj=list_source.At(i);
          long obj1_prop=obj.GetProperty(property);
          min_obj=list_source.At(index);
          long obj2_prop=min_obj.GetProperty(property);
          if(CompareValues(obj1_prop,obj2_prop,LESS))
             index=i;
         }
       return index;
      }
    //+------------------------------------------------------------------+
    //| Return the position index in the list                            |
    //| with the minimum real property value                             |
    //+------------------------------------------------------------------+
    int CSelect::FindPositionMin(CArrayObj* list_source,ENUM_POSITION_PROPERTY_DBL property)
      {
       int index=0;
       CPosition *min_obj=NULL;
       int total=list_source.Total();
       if(total==0)
          return WRONG_VALUE;
       for(int i=1; i<total; i++)
         {
          CPosition *obj=list_source.At(i);
          double obj1_prop=obj.GetProperty(property);
          min_obj=list_source.At(index);
          double obj2_prop=min_obj.GetProperty(property);
          if(CompareValues(obj1_prop,obj2_prop,LESS))
             index=i;
         }
       return index;
      }
    //+------------------------------------------------------------------+
    //| Return the position index in the list                            |
    //| with the minimum string property value                           |
    //+------------------------------------------------------------------+
    int CSelect::FindPositionMin(CArrayObj* list_source,ENUM_POSITION_PROPERTY_STR property)
      {
       int index=0;
       CPosition *min_obj=NULL;
       int total=list_source.Total();
       if(total==0)
          return WRONG_VALUE;
       for(int i=1; i<total; i++)
         {
          CPosition *obj=list_source.At(i);
          string obj1_prop=obj.GetProperty(property);
          min_obj=list_source.At(index);
          string obj2_prop=min_obj.GetProperty(property);
          if(CompareValues(obj1_prop,obj2_prop,LESS))
             index=i;
         }
       return index;
      }
    
    

    上記記事の「検索の整理」セクションを参照してください。

    これで、履歴ポジションのリストを処理するクラスを作成する準備が整いました。


    履歴ポジションコレクションクラス

    \MQL5\Services\AccountReporter\端末フォルダに、CPositionsControlクラスの新しいファイルPositionsControl.mqhを作成します。
    クラスは標準ライブラリのCObject基底オブジェクトから継承する必要があり、履歴ポジションクラスと検索およびフィルタクラスファイルは作成されるファイルに含まれる必要があります。

    //+------------------------------------------------------------------+
    //|                                             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"
    #include "Select.mqh"
    
    //+------------------------------------------------------------------+
    //| Collection class of historical positions                         |
    //+------------------------------------------------------------------+
    class CPositionsControl : public CObject
      {
      }
    


    private、protected、publicのクラスメソッドを宣言しましょう。

    //+------------------------------------------------------------------+
    //| Collection class of historical positions                         |
    //+------------------------------------------------------------------+
    class CPositionsControl : public CObject
      {
    private:
    //--- Return (1) position type and (2) reason for opening by deal type
       ENUM_POSITION_TYPE PositionTypeByDeal(const CDeal *deal);
       ENUM_POSITION_REASON PositionReasonByDeal(const CDeal *deal);
    
    protected:
       CPosition         m_temp_pos;          // Temporary position object for searching
       CArrayObj         m_list_pos;          // List of positions
       
    //--- 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);
       
    public:
    //--- Create and update the list of positions. It can be redefined in the inherited classes
       virtual bool      Refresh(void);
       
    //--- Return (1) the list, (2) number of positions in the list
       CArrayObj        *GetPositionsList(void)           { return &this.m_list_pos;          }
       int               PositionsTotal(void)       const { return this.m_list_pos.Total();   }
    
    //--- Print the properties of all positions and their deals in the journal
       void              Print(void);
    
    //--- Constructor/destructor
                         CPositionsControl(void);
                        ~CPositionsControl();
      };
    
    


    宣言されたメソッドの実装を考えてみましょう。

    クラスコンストラクタで、履歴ポジションのリストのクローズ時間(ミリ秒単位)による並び替えフラグを設定します。

    //+------------------------------------------------------------------+
    //| Constructor                                                      |
    //+------------------------------------------------------------------+
    CPositionsControl::CPositionsControl(void)
      {
       this.m_list_pos.Sort(POSITION_PROP_TIME_CLOSE_MSC);
      }
    
    


    クラスデストラクタで、履歴ポジションのリストを破棄します。

    //+------------------------------------------------------------------+
    //| Destructor                                                       |
    //+------------------------------------------------------------------+
    CPositionsControl::~CPositionsControl()
      {
       this.m_list_pos.Shutdown();
      }
    
    


    以下は、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(POSITION_PROP_IDENTIFIER);
       
    //--- Get the index of the position object with the specified 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);
       
    //--- Return 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(POSITION_PROP_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;
      }
    
    


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

    //+------------------------------------------------------------------+
    //| 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;
         }
      }
    
    

    取引の種類に応じて、対応するポジションタイプを返します。


    以下は、取引の種類別にポジションを建てた理由を返すメソッドです。

    //+------------------------------------------------------------------+
    //| Returns the reason for opening a position by deal type           |
    //+------------------------------------------------------------------+
    ENUM_POSITION_REASON CPositionsControl::PositionReasonByDeal(const CDeal *deal)
      {
       if(deal==NULL)
          return WRONG_VALUE;
       switch(deal.Reason())
         {
          case DEAL_REASON_CLIENT :  return POSITION_REASON_CLIENT;
          case DEAL_REASON_MOBILE :  return POSITION_REASON_MOBILE;
          case DEAL_REASON_WEB    :  return POSITION_REASON_WEB;
          case DEAL_REASON_EXPERT :  return POSITION_REASON_EXPERT;
          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(POSITION_PROP_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, 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))
             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
             string pos_symbol=HistoryDealGetString(ticket, DEAL_SYMBOL);
             pos=new CPosition(pos_id, pos_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.SetTicket(deal.Order());
             pos.SetMagic(deal.Magic());
             pos.SetTime(deal.Time());
             pos.SetTimeMsc(deal.TimeMsc());
             ENUM_POSITION_TYPE type=this.PositionTypeByDeal(deal);
             pos.SetTypePosition(type);
             ENUM_POSITION_REASON reason=this.PositionReasonByDeal(deal);
             pos.SetReason(reason);
             pos.SetPriceOpen(deal.Price());
             pos.SetVolume(deal.Volume());
            }
          if(deal.Entry()==DEAL_ENTRY_OUT || deal.Entry()==DEAL_ENTRY_OUT_BY)
            {
             pos.SetPriceCurrent(deal.Price());
             pos.SetPriceClose(deal.Price());
             pos.SetTimeClose(deal.Time());
             pos.SetTimeCloseMsc(deal.TimeMsc());
            }
          if(deal.Entry()==DEAL_ENTRY_INOUT)
            {
             ENUM_POSITION_TYPE type=this.PositionTypeByDeal(deal);
             pos.SetTypePosition(type);
             pos.SetVolume(deal.Volume()-pos.Volume());
            }
         }
         
    //--- All historical positions are created and the corresponding deals are added to the deal lists of the position objects
    //--- Set the flag of sorting by close time in milliseconds for the position list
       this.m_list_pos.Sort(POSITION_PROP_TIME_CLOSE_MSC);
    
    //--- In the loop through the created list of closed positions, we set the Commissions and Fee values for each position
       for(int i=0; i<this.m_list_pos.Total(); i++)
         {
          CPosition *pos=this.m_list_pos.At(i);
          if(pos==NULL)
             continue;
          pos.SetCommissions();
          pos.SetFee();
         }
    
    //--- Return the result of creating and adding a position to the list
       return res;
      }
    
    

    端末内の取引リストをループして、次の取引を受信し、そのポジションIDを確認します。これが市場のポジションである場合、取引をスキップします。そのようなポジションが履歴ポジションのリストにまだない場合は、新しいポジションオブジェクトを作成し、それを履歴ポジションのリストに配置します。履歴ポジションオブジェクトに選択した取引のチケットを持つ取引がまだない場合は、その取引をポジションオブジェクト取引のリストに追加します。各ポジションの履歴ポジションオブジェクトを作成するループの最後に、すべてのポジション取引に共通の手数料と取引手数料を設定します。このメソッドは仮想であるため、ポジションのリストを1日に1回以上更新する必要がある場合、継承されたクラスでより最適なロジックを作成できます。


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

    //+------------------------------------------------------------------+
    //| 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();
         }
      }
    
    

    作成された履歴ポジションのリストを制御する必要がある場合、このメソッドを使用すると、各ポジションをその取引とともに操作ログに表示できます。

    サービスアプリは、継続的なサービス操作中に接続されたすべての口座を「記憶」します。つまり、端末の再起動がなく、異なる口座と取引サーバーへの接続があった場合、プログラムはこれらの口座を記憶し、すべてのクローズ済みポジションのリストを保存します。接続された各口座に存在していたクローズ済みのポジションの取引レポートが表示されます。または、設定で現在の口座からのレポートのみを表示する必要がある場合、クローズ済みポジションのリストは現在の口座のログインとサーバーによって並べ替えられます。

    上記に基づいて、この口座で取引されたクローズ済みポジションのリストの管理クラスを保存する口座クラスが必要であることがわかります。サービスアプリでは、必要な口座を受け取り、それを使用してクローズ済みポジションのリストを取得します。


    口座クラス

    \MQL5\Services\AccountReporter\で、CAccountクラスの新しいファイルAccount.mqhを作成します。
    クラスは標準ライブラリのCObjectベースオブジェクトから継承する必要があり、履歴ポジションコレクションクラスファイルは作成されたファイルに含まれる必要があります。

    //+------------------------------------------------------------------+
    //|                                                      Account.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 "PositionsControl.mqh"
    
    //+------------------------------------------------------------------+
    //| Account class                                                    |
    //+------------------------------------------------------------------+
    class CAccount : public CObject
      {
      }
    


    クラスのprotectedセクションで、履歴ポジションの制御オブジェクト(口座のクローズ済みポジションのリストのクラス)と整数、実数、文字列のプロパティのリストを宣言します。

    //+------------------------------------------------------------------+
    //| Account class                                                    |
    //+------------------------------------------------------------------+
    class CAccount : public CObject
      {
    private:
       
    protected:
       CPositionsControl m_positions;                  // Historical positions control object
    //--- account integer properties
       long              m_login;                    //   Account number
       ENUM_ACCOUNT_TRADE_MODE m_trade_mode;         //   Trading account type
       long              m_leverage;                 //   Leverage
       int               m_limit_orders;             //   Maximum allowed number of active pending orders
       ENUM_ACCOUNT_STOPOUT_MODE m_margin_so_mode;   //   Mode of setting the minimum available margin level
       bool              m_trade_allowed;            //   Trading permission of the current account
       bool              m_trade_expert;             //   Trading permission of an EA
       ENUM_ACCOUNT_MARGIN_MODE m_margin_mode;       //   Margin calculation mode
       int               m_currency_digits;          //   Number of digits for an account currency necessary for accurate display of trading results
       bool              m_fifo_close;               //   The flag indicating that positions can be closed only by the FIFO rule
       bool              m_hedge_allowed;            //   Allowed opposite positions on a single symbol
        
    //--- account real properties
       double            m_balance;                  //   Account balance in a deposit currency
       double            m_credit;                   //   Credit in a deposit currency
       double            m_profit;                   //   Current profit on an account in the account currency
       double            m_equity;                   //   Equity on an account in the deposit currency
       double            m_margin;                   //   Reserved margin on an account in a deposit currency
       double            m_margin_free;              //   Free funds available for opening a position in a deposit currency
       double            m_margin_level;             //   Margin level on an account in %
       double            m_margin_so_call;           //   Margin Call level
       double            m_margin_so_so;             //   Stop Out level
       double            m_margin_initial;           //   Funds reserved on an account to ensure a guarantee amount for all pending orders 
       double            m_margin_maintenance;       //   Funds reserved on an account to ensure a minimum amount for all open positions
       double            m_assets;                   //   Current assets on an account
       double            m_liabilities;              //   Current liabilities on an account
       double            m_commission_blocked;       //   Current sum of blocked commissions on an account
      
    //--- account string properties
       string            m_name;                     //   Client name
       string            m_server;                   //   Trade server name
       string            m_currency;                 //   Deposit currency
       string            m_company;                  //   Name of a company serving account
       
    public:
    
    


    publicセクションで、リストを処理するメソッド口座オブジェクトのプロパティを設定および返すメソッドなどを設定します。

    public:
    //--- Return the (1) control object, (2) the list of historical positions, (3) number of positions
       CPositionsControl*GetPositionsCtrlObj(void)        { return &this.m_positions;                  }
       CArrayObj        *GetPositionsList(void)           { return this.m_positions.GetPositionsList();}
       int               PositionsTotal(void)             { return this.m_positions.PositionsTotal();  }
    
    //--- Return the list of positions by (1) integer, (2) real and (3) string property
       CArrayObj        *GetPositionsList(ENUM_POSITION_PROPERTY_INT property, long   value, ENUM_COMPARER_TYPE mode)
                           { return CSelect::ByPositionProperty(this.GetPositionsList(), property, value, mode); }
       
       CArrayObj        *GetPositionsList(ENUM_POSITION_PROPERTY_DBL property, double value, ENUM_COMPARER_TYPE mode)
                           { return CSelect::ByPositionProperty(this.GetPositionsList(), property, value, mode); }
       
       CArrayObj        *GetPositionsList(ENUM_POSITION_PROPERTY_STR property, string value, ENUM_COMPARER_TYPE mode)
                           { return CSelect::ByPositionProperty(this.GetPositionsList(), property, value, mode); }
    
    //--- (1) Update and (2) print the list of closed positions in the journal
       bool              PositionsRefresh(void)           { return this.m_positions.Refresh();}
       void              PositionsPrint(void)             { this.m_positions.Print();         }
       
    //--- set (1) login and (2) server
       void              SetLogin(const long login)       { this.m_login=login;               }
       void              SetServer(const string server)   { this.m_server=server;             }
       
    //--- return integer account properties
       long              Login(void)                const { return this.m_login;              }  //   Account number
       ENUM_ACCOUNT_TRADE_MODE TradeMode(void)      const { return this.m_trade_mode;         }  //   Trading account type
       long              Leverage(void)             const { return this.m_leverage;           }  //   Provided leverage
       int               LimitOrders(void)          const { return this.m_limit_orders;       }  //   Maximum allowed number of active pending orders
       ENUM_ACCOUNT_STOPOUT_MODE MarginSoMode(void) const { return this.m_margin_so_mode;     }  //   Mode of setting the minimum available margin level
       bool              TradeAllowed(void)         const { return this.m_trade_allowed;      }  //   Trading permission of the current account
       bool              TradeExpert(void)          const { return this.m_trade_expert;       }  //   Trading permission for EA
       ENUM_ACCOUNT_MARGIN_MODE MarginMode(void)    const { return this.m_margin_mode;        }  //   Margin calculation mode
       int               CurrencyDigits(void)       const { return this.m_currency_digits;    }  //   Number of digits for an account currency necessary for accurate display of trading results
       bool              FIFOClose(void)            const { return this.m_fifo_close;         }  //   The flag indicating that positions can be closed only by the FIFO rule
       bool              HedgeAllowed(void)         const { return this.m_hedge_allowed;      }  //   Allowed opposite positions on a single symbol
        
    //--- return real account properties
       double            Balance(void)              const { return this.m_balance;            }  //   Account balance in a deposit currency
       double            Credit(void)               const { return this.m_credit;             }  //   Credit in deposit currency
       double            Profit(void)               const { return this.m_profit;             }  //   Current profit on an account in the account currency
       double            Equity(void)               const { return this.m_equity;             }  //   Available equity in the deposit currency
       double            Margin(void)               const { return this.m_margin;             }  //   The amount of reserved collateral funds on the account in the deposit currency
       double            MarginFree(void)           const { return this.m_margin_free;        }  //   Free funds available for opening a position in a deposit currency
       double            MarginLevel(void)          const { return this.m_margin_level;       }  //   Margin level on an account in %
       double            MarginSoCall(void)         const { return this.m_margin_so_call;     }  //   Margin Call level
       double            MarginSoSo(void)           const { return this.m_margin_so_so;       }  //   Stop Out level
       double            MarginInitial(void)        const { return this.m_margin_initial;     }  //   Funds reserved on an account to ensure a guarantee amount for all pending orders 
       double            MarginMaintenance(void)    const { return this.m_margin_maintenance; }  //   Funds reserved on an account to ensure the minimum amount for all open positions 
       double            Assets(void)               const { return this.m_assets;             }  //   Current assets on an account
       double            Liabilities(void)          const { return this.m_liabilities;        }  //   Current amount of liabilities on the account
       double            CommissionBlocked(void)    const { return this.m_commission_blocked; }  //   Current sum of blocked commissions on an account
      
    //--- return account string properties
       string            Name(void)                 const { return this.m_name;               }  //   Client name
       string            Server(void)               const { return this.m_server;             }  //   Trade server name
       string            Currency(void)             const { return this.m_currency;           }  //   Deposit currency
       string            Company(void)              const { return this.m_company;            }  //   Name of the company servicing the account
       
    //--- return (1) account description, (2) trading account type and (3) margin calculation mode
       string            Description(void)          const;
       string            TradeModeDescription(void) const;
       string            MarginModeDescription(void)const;
       
    //--- virtual method for comparing two objects
       virtual int       Compare(const CObject *node,const int mode=0) const;
    
    //--- Display the account description in the journal
       void              Print(void)                      { ::Print(this.Description());      }
    
    //--- constructors/destructor
                         CAccount(void){}
                         CAccount(const long login, const string server_name);
                        ~CAccount() {}
      };
    
    


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

    クラスコンストラクタで、現在の口座のすべてのプロパティをオブジェクトに設定します。

    //+------------------------------------------------------------------+
    //| Constructor                                                      |
    //+------------------------------------------------------------------+
    CAccount::CAccount(const long login, const string server_name)
      {
       this.m_login=login;
       this.m_server=server_name;
       
    //--- set account integer properties
       this.m_trade_mode          = (ENUM_ACCOUNT_TRADE_MODE)::AccountInfoInteger(ACCOUNT_TRADE_MODE);    //   Trading account type
       this.m_leverage            = ::AccountInfoInteger(ACCOUNT_LEVERAGE);                               //   Leverage
       this.m_limit_orders        = (int)::AccountInfoInteger(ACCOUNT_LIMIT_ORDERS);                      //   Maximum allowed number of active pending orders
       this.m_margin_so_mode      = (ENUM_ACCOUNT_STOPOUT_MODE)AccountInfoInteger(ACCOUNT_MARGIN_SO_MODE);//   Mode of setting the minimum available margin level
       this.m_trade_allowed       = ::AccountInfoInteger(ACCOUNT_TRADE_ALLOWED);                          //   Trading permission of the current account
       this.m_trade_expert        = ::AccountInfoInteger(ACCOUNT_TRADE_EXPERT);                           //   Trading permission of an EA
       this.m_margin_mode         = (ENUM_ACCOUNT_MARGIN_MODE)::AccountInfoInteger(ACCOUNT_MARGIN_MODE);  //   Margin calculation mode
       this.m_currency_digits     = (int)::AccountInfoInteger(ACCOUNT_CURRENCY_DIGITS);                   //   Number of digits for an account currency necessary for accurate display of trading results
       this.m_fifo_close          = ::AccountInfoInteger(ACCOUNT_FIFO_CLOSE);                             //   The flag indicating that positions can be closed only by the FIFO rule
       this.m_hedge_allowed       = ::AccountInfoInteger(ACCOUNT_HEDGE_ALLOWED);                          //   Allowed opposite positions on a single symbol
        
    //--- set account real properties
       this.m_balance             = ::AccountInfoDouble(ACCOUNT_BALANCE);                                 //   Account balance in a deposit currency
       this.m_credit              = ::AccountInfoDouble(ACCOUNT_CREDIT);                                  //   Credit in a deposit currency
       this.m_profit              = ::AccountInfoDouble(ACCOUNT_PROFIT);                                  //   Current profit on an account in the account currency
       this.m_equity              = ::AccountInfoDouble(ACCOUNT_EQUITY);                                  //   Equity on an account in the deposit currency
       this.m_margin              = ::AccountInfoDouble(ACCOUNT_MARGIN);                                  //   Reserved margin on an account in a deposit currency
       this.m_margin_free         = ::AccountInfoDouble(ACCOUNT_MARGIN_FREE);                             //   Free funds available for opening a position in a deposit currency
       this.m_margin_level        = ::AccountInfoDouble(ACCOUNT_MARGIN_LEVEL);                            //   Margin level on an account in %
       this.m_margin_so_call      = ::AccountInfoDouble(ACCOUNT_MARGIN_SO_CALL);                          //   Margin Call level
       this.m_margin_so_so        = ::AccountInfoDouble(ACCOUNT_MARGIN_SO_SO);                            //   Stop Out level
       this.m_margin_initial      = ::AccountInfoDouble(ACCOUNT_MARGIN_INITIAL);                          //   Funds reserved on an account to ensure a guarantee amount for all pending orders 
       this.m_margin_maintenance  = ::AccountInfoDouble(ACCOUNT_MARGIN_MAINTENANCE);                      //   Funds reserved on an account to ensure a minimum amount for all open positions
       this.m_assets              = ::AccountInfoDouble(ACCOUNT_ASSETS);                                  //   Current assets on an account
       this.m_liabilities         = ::AccountInfoDouble(ACCOUNT_LIABILITIES);                             //   Current liabilities on an account
       this.m_commission_blocked  = ::AccountInfoDouble(ACCOUNT_COMMISSION_BLOCKED);                      //   Current sum of blocked commissions on an account
      
    //--- set account string properties
       this.m_name                = ::AccountInfoString(ACCOUNT_NAME);                                    //   Client name
       this.m_currency            = ::AccountInfoString(ACCOUNT_CURRENCY);                                //   Deposit currency
       this.m_company             = ::AccountInfoString(ACCOUNT_COMPANY);                                 //   Name of a company serving account
      }
    
    


    以下は、2つのオブジェクトを比較するメソッドです。

    //+------------------------------------------------------------------+
    //| Method for comparing two objects                                 |
    //+------------------------------------------------------------------+
    int CAccount::Compare(const CObject *node,const int mode=0) const
      {
       const CAccount *obj=node;
       return(this.Login()>obj.Login()   ? 1 : this.Login()<obj.Login()   ? -1 :
              this.Server()>obj.Server() ? 1 : this.Server()<obj.Server() ? -1 : 0);
      }
    
    

    このメソッドは、ログインとサーバー名の2つのプロパティのみを使用して2つの口座オブジェクトを比較します。比較する2つのオブジェクトのログインが等しい場合は、サーバー名の等価性がチェックされます。サーバーも同じであれば、2つのオブジェクトは等しくなります。それ以外の場合は、2つのオブジェクト間で比較されるプロパティの値が大きいか小さいかに応じて、1または-1が返されます。


    以下は、いくつかの口座オブジェクトプロパティの説明を返すメソッドです。

    //+------------------------------------------------------------------+
    //| Return the description of the trading account type               |
    //+------------------------------------------------------------------+
    string CAccount::TradeModeDescription(void) const
      {
       string mode=::StringSubstr(::EnumToString(this.TradeMode()), 19);
       if(mode.Lower())
          mode.SetChar(0, ushort(mode.GetChar(0)-32));
       return mode;
      }
    //+------------------------------------------------------------------+
    //| Return the description of the margin calculation mode            |
    //+------------------------------------------------------------------+
    string CAccount::MarginModeDescription(void) const
      {
       string mode=::StringSubstr(::EnumToString(this.MarginMode()), 20);
       ::StringReplace(mode, "RETAIL_", "");
       if(mode.Lower())
          mode.SetChar(0, ushort(mode.GetChar(0)-32));
       return mode;
      }
    
    

    これらのメソッドは、Descriptionメソッドで口座の説明を作成するために使用されます。

    //+------------------------------------------------------------------+
    //| Return the account description                                   |
    //+------------------------------------------------------------------+
    string CAccount::Description(void) const
      {
       return(::StringFormat("%I64d: %s (%s, %s, %.2f %s, %s)",
                             this.Login(), this.Name(), this.Company(), this.TradeModeDescription(),
                             this.Balance(), this.Currency(), this.MarginModeDescription()));
      }
    
    

    このメソッドは次の文字列を返します。

    68008618: Artem (MetaQuotes Ltd., Demo, 10779.50 USD, Hedging)
    
    

    この文字列は、クラスのPrint()メソッドを使用してログに出力できます。

    ここで、サービスアプリの操作中に接続されたすべての口座のリストを保存するクラスを作成する必要があります。


    口座コレクションクラス

    \MT5\MQL5\Services\AccountReporter\端末フォルダに、CAccountsクラスの新しいファイルAccounts.mqhを作成します。
    クラスは標準ライブラリのCObject基底オブジェクトから継承する必要があり、口座クラスファイルは作成されたファイルに含まれる必要があります

    //+------------------------------------------------------------------+
    //|                                                     Accounts.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 "Account.mqh"
    
    //+------------------------------------------------------------------+
    //| Account collection class                                         |
    //+------------------------------------------------------------------+
    class CAccounts : public CObject
      {
      }
    


    クラス操作のメソッドをprivate、protected、publicセクションで宣言します。

    //+------------------------------------------------------------------+
    //| Account collection class                                         |
    //+------------------------------------------------------------------+
    class CAccounts : public CObject
      {
    private:
       CArrayObj         m_list;           // List of account objects
       CAccount          m_tmp;            // Temporary account object for searching
       
    protected:
    //--- Create a new account object and add it to the list
       CAccount         *Add(const long login, const string server);
       
    public:
    //--- Create a new account object
       bool              Create(const long login, const string server);
       
    //--- Return the pointer to the specified account object by (1) login and server, (2) index in the list
       CAccount         *Get(const long login, const string server);
       CAccount         *Get(const int index)                         const { return this.m_list.At(index);  }
    
    //--- Combine the lists of account positions and return the combined one
       CArrayObj        *GetCommonPositionsList(void);
    
    //--- Return the list of positions for the specified account
       CArrayObj        *GetAccountPositionsList(const long login, const string server);
    
    //--- Return the number of stored accounts
       int               Total(void)                                  const { return this.m_list.Total();    }
       
    //--- Update the lists of positions of the specified account
       bool              PositionsRefresh(const long login, const string server);
    
    //--- Constructor/destructor
                         CAccounts();
                        ~CAccounts();
      };
    
    


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

    クラスコンストラクタで、口座のリストに並び替えられたリストフラグを設定します。

    //+------------------------------------------------------------------+
    //| Constructor                                                      |
    //+------------------------------------------------------------------+
    CAccounts::CAccounts()
      {
       this.m_list.Sort();
      }
    
    


    クラスデストラクタで口座のリストをクリアします。

    //+------------------------------------------------------------------+
    //| Destructor                                                       |
    //+------------------------------------------------------------------+
    CAccounts::~CAccounts()
      {
       this.m_list.Clear();
      }
    
    


    以下は、新しい口座オブジェクトを作成し、それをリストに追加するprotectedメソッドです。

    //+------------------------------------------------------------------+
    //| Create a new account object and add it to the list               |
    //+------------------------------------------------------------------+
    CAccount *CAccounts::Add(const long login,const string server)
      {
    //--- Create a new account object
       CAccount *account=new CAccount(login, server);
       if(account==NULL)
          return NULL;
    //--- If the created object is not added to the list, remove it and return NULL
       if(!this.m_list.Add(account))
         {
          delete account;
          return NULL;
         }
    //--- Return the pointer to a created object
       return account;
      }
    
    

    これはprotectedメソッドであり、新しい口座オブジェクトを作成するpublicメソッドの一部として機能します。

    //+------------------------------------------------------------------+
    //| Create a new account object                                      |
    //+------------------------------------------------------------------+
    bool CAccounts::Create(const long login,const string server)
      {
    //--- Set login and server to the temporary account object
       this.m_tmp.SetLogin(login);
       this.m_tmp.SetServer(server);
       
    //--- Set the sorted list flag for the account object list
    //--- and get the object index having the same login and server as the ones the temporary object has
       this.m_list.Sort();
       int index=this.m_list.Search(&this.m_tmp);
       
    //--- Return the flag of an object being successfully added to the list (Add method operation result) or 'false' if the object is already in the list
       return(index==WRONG_VALUE ? this.Add(login, server)!=NULL : false);
      }
    
    


    以下は、指定された口座オブジェクトへのポインタを返すメソッドです。

    //+------------------------------------------------------------------+
    //| Return the pointer to the specified account object               |
    //+------------------------------------------------------------------+
    CAccount *CAccounts::Get(const long login,const string server)
      {
    //--- Set login and server to the temporary account object
       this.m_tmp.SetLogin(login);
       this.m_tmp.SetServer(server);
       
    //--- Set the sorted list flag for the account object list
    //--- and get the object index having the same login and server as the ones the temporary object has
       this.m_list.Sort();
       int index=this.m_list.Search(&this.m_tmp);
    
    //--- Return the pointer to the object in the list by index or NULL if the index is -1
       return this.m_list.At(index);
      }
    
    


    以下は、指定された口座のポジションリストを更新するメソッドです。

    //+------------------------------------------------------------------+
    //| Update the lists of positions of the specified account           |
    //+------------------------------------------------------------------+
    bool CAccounts::PositionsRefresh(const long login, const string server)
      {
    //--- Get the pointer to the account object with the specified login and server
       CAccount *account=this.Get(login, server);
       if(account==NULL)
          return false;
    
    //--- If the received object is not the current account,
       if(account.Login()!=::AccountInfoInteger(ACCOUNT_LOGIN) || account.Server()!=::AccountInfoString(ACCOUNT_SERVER))
         {
          //--- inform that updating data of the non-current account will result in incorrect data and return 'false'
          ::Print("Error. Updating the list of positions for a non-current account will result in incorrect data.");
          return false;
         }
    
    //--- Return the result of updating the current account data
       return account.PositionsRefresh();
      }
    
    


    以下は、口座ポジションのリストを結合し、結合されたリストを返すメソッドです。

    //+--------------------------------------------------------------------+
    //| Combine the lists of account positions and return the combined one |
    //+--------------------------------------------------------------------+
    CArrayObj *CAccounts::GetCommonPositionsList(void)
      {
    //--- Create a new list and reset the flag of managing memory
       CArrayObj *list=new CArrayObj();
       if(list==NULL)
          return NULL;
       list.FreeMode(false);
    
    //--- In the loop through the list of accounts,
       int total=this.m_list.Total();
       for(int i=0; i<total; i++)
         {
          //--- get another account object
          CAccount *account=this.m_list.At(i);
          if(account==NULL)
             continue;
          
          //--- Get the list of closed account positions
          CArrayObj *src=account.GetPositionsList();
          if(src==NULL)
             continue;
    
          //--- If this is the first account in the list,
          if(i==0)
            {
             //--- copy the elements from the account positions list to the new list
             if(!list.AssignArray(src))
               {
                delete list;
                return NULL;
               }
            }
          //--- If this is not the first account in the list,
          else
            {
             //--- add elements from the account position list to the end of the new list
             if(!list.AddArray(src))
                continue;
            }
         }
       
    //--- Send a new list to the storage
       if(!ListStorage.Add(list))
         {
          delete list;
          return NULL;
         }
         
    //--- Return the pointer to the created and filled list
       return list;
      }
    
    


    以下は、指定された口座のポジションのリストを返すメソッドです。

    //+------------------------------------------------------------------+
    //| Return the list of positions for the specified account           |
    //+------------------------------------------------------------------+
    CArrayObj *CAccounts::GetAccountPositionsList(const long login,const string server)
      {
       CAccount *account=this.Get(login, server);
       return(account!=NULL ? account.GetPositionsList() : NULL);
      }
    
    

    ログインとサーバーによって口座オブジェクトへのポインタを取得し、その履歴ポジションのリストへのポインタを返します。口座オブジェクトの取得に失敗した場合はNULLを返します。

    このクラスのすべてのメソッドはコメントで詳細に説明されています。まだ不明な点がある場合は、記事のディスカッションで質問することができます。

    サービスアプリの基礎となるすべてのクラスが準備完了です。プログラム自体の実装を始めましょう。


    取引レポートの作成と通知の送信のためのサービスアプリ

    プログラムがどのように動作するかを決めましょう。

    サービスが開始されると、クライアント端末にMetaQuotesIDが存在するかどうか、およびスマートフォンにプッシュ通知を送信する許可があるかどうかが確認されます。

    これらの設定は、[通知]タブの[ツール]-[オプション]メニューにあります。


    MetaQuotes IDフィールドに値がない場合、または[プッシュ通知を有効にするチェックボックスがオンになっていない場合、これらのパラメータを設定するように求めるウィンドウが表示されます。これらのパラメータを設定しない場合は、MQIDが存在しない、またはスマートフォンへの通知の送信が許可されていない、すべてのメッセージが操作ログにのみ保存されるという警告が表示されます。すべてのパラメータを設定すると、レポートはスマートフォンとエキスパート端末操作ログの両方に送信されます。メインループでは、プログラムは端末内の通知送信設定の状態を常にチェックします。したがって、サービスの起動時に通知を送信する許可が設定されていない場合は、サービスプログラムの起動後にいつでも有効にすることができます。サービスプログラムは変更を認識し、対応するフラグを有効にします。

    サービス設定では、レポートを作成するために必要なメッセージ送信パラメータと期間を選択できます。

    • 一般的なレポートパラメータ
      • レポートに使用する口座(すべてまたは現在の口座)
      • 銘柄別にレポートを作成するかどうか(はい/いいえ):最初にレポートが作成され、次に取引に関係する銘柄ごとに個別のレポートが作成されます。
      • マジックナンバー別にレポートを作成するかどうか(はい/いいえ):レポートが作成され、その後、取引で使用されるマジックナンバーごとに個別のレポートが作成されます。
      • 手数料をレポートに含めるかどうか(はい/いいえ):有効にすると、手数料、スワップ、取引手数料のコストが、すべてのコストの合計額に加えて個別に表示されます。
      • ポジションをクローズするときにスプレッドで発生する可能性のある損失をレポートに含めるかどうか(はい/いいえ):有効にすると、クローズ時に発生する可能性のあるすべてのスプレッド費用の合計が個別に表示されます。
    • 日報設定
      • 過去24時間のレポートを送信するかどうか(はい/いいえ):指定された期間のレポートにも適用されます。有効にすると、過去24時間および構成可能な取引時間間隔(日数、月数、年数)のレポートが、指定された時間に毎日送信されます。
      • レポート送信時間(時間)(デフォルト8)
      • レポート送信時間(分)(デフォルト0)
    • カスタム期間の日次レポート設定
      • 指定した日数のレポートを送信するかどうか(はい/いいえ):有効にすると、指定した日数のレポートが毎日上記の時間に作成されます。レポートの日数は、現在の日付から指定した日数を引いて計算されます。
      • 指定された日数レポートの日数(デフォルト7)
      • 指定した月数のレポートを送信するかどうか(はい/いいえ):有効にすると、指定した月数のレポートが毎日上記の時間に作成されます。レポートの月数は、現在の日付から指定した月数を減算して計算されます。
      • 指定した月数のレポートの月数(デフォルト3)
      • 指定した年数のレポートを送信するかどうか(はい/いいえ):有効にすると、指定した年数のレポートが毎日上記の時間に作成されます。レポートの年数は、現在の日付から指定した年数を引いて計算されます。
      • 指定された年数のレポートの年数(デフォルト2)
    • その他の期間の週次レポート設定
      • 週次レポートを送信する曜日(デフォルトは土曜日):指定された日になると、以下の設定で指定されたレポートが作成され、送信されます。
      • レポート送信時間(時間)(デフォルト8)
      • レポート送信時間(分)(デフォルト0)
      • 現在の週の初めからレポートを送信するかどうか(はい/いいえ):有効にすると、現在の週の初めからのレポートが毎週指定された日に作成されます。
      • 現在の月の初めからレポートを送信するかどうか(はい/いいえ):有効にすると、現在の月の初めからのレポートが毎週指定された日に作成されます。
      • 現在の年の初めからレポートを送信するかどうか(はい/いいえ):有効にすると、現在の年の初めからのレポートが指定された日に毎週作成されます。
      • 取引期間全体のレポートを送信するかどうか(はい/いいえ):有効にすると、指定された日に毎週取引期間全体のレポートが作成されます。


    これらの設定は、レポートを作成するために関心のある取引期間のほとんどをカバーするのに十分です。

    \MQL5\Services\AccountReporter\端末フォルダに、サービスアプリの新しいファイルReporter.mq5を作成します。



    プログラムが動作するために必要なマクロ置換を入力し、外部ファイルを接続し、列挙、入力、およびグローバル変数を記述しましょう。

    //+------------------------------------------------------------------+
    //|                                                     Reporter.mq5 |
    //|                                  Copyright 2024, MetaQuotes Ltd. |
    //|                                             https://www.mql5.com |
    //+------------------------------------------------------------------+
    #property service
    #property copyright "Copyright 2024, MetaQuotes Ltd."
    #property link      "https://www.mql5.com"
    #property version   "1.00"
    
    #define   COUNTER_DELAY       1000                                   // Counter delay in milliseconds during the working loop
    #define   REFRESH_ATTEMPTS    5                                      // Number of attempts to obtain correct account data
    #define   REFRESH_DELAY       500                                    // Delay in milliseconds before next attempt to get data
    #define   TABLE_COLUMN_W      10                                     // Width of the statistics table column for displaying in the journal
    
    #include <Arrays\ArrayString.mqh>                                    // Dynamic array of string variables for a symbol list object
    #include <Arrays\ArrayLong.mqh>                                      // Dynamic array of long type variables for the magic number list object
    #include <Tools\DateTime.mqh>                                        // Expand the MqlDateTime structure
    #include "Accounts.mqh"                                              // Collection class of account objects
    
    //+------------------------------------------------------------------+
    //| Enumerations                                                     |
    //+------------------------------------------------------------------+
    enum ENUM_USED_ACCOUNTS                                              // Enumerate used accounts in statistics
      {
       USED_ACCOUNT_CURRENT,                                             // Current Account only
       USED_ACCOUNTS_ALL,                                                // All used accounts
      };
    
    enum ENUM_REPORT_RANGE                                               // Enumerate statistics ranges
      {
       REPORT_RANGE_DAILY,                                               // Day
       REPORT_RANGE_WEEK_BEGIN,                                          // Since the beginning of the week
       REPORT_RANGE_MONTH_BEGIN,                                         // Since the beginning of the month
       REPORT_RANGE_YEAR_BEGIN,                                          // Since the beginning of the year
       REPORT_RANGE_NUM_DAYS,                                            // Number of days
       REPORT_RANGE_NUM_MONTHS,                                          // Number of months
       REPORT_RANGE_NUM_YEARS,                                           // Number of years
       REPORT_RANGE_ALL,                                                 // Entire period
      };
      
    enum ENUM_REPORT_BY                                                  // Enumerate statistics filters
      {
       REPORT_BY_RANGE,                                                  // Date range
       REPORT_BY_SYMBOLS,                                                // By symbols
       REPORT_BY_MAGICS,                                                 // By magic numbers
      };
      
    //+------------------------------------------------------------------+
    //| Inputs                                                           |
    //+------------------------------------------------------------------+
    input group                "============== Report options =============="
    input ENUM_USED_ACCOUNTS   InpUsedAccounts      =  USED_ACCOUNT_CURRENT;// Accounts included in statistics
    input bool                 InpReportBySymbols   =  true;             // Reports by Symbol
    input bool                 InpReportByMagics    =  true;             // Reports by Magics
    input bool                 InpCommissionsInclude=  true;             // Including Comissions
    input bool                 InpSpreadInclude     =  true;             // Including Spread
    
    input group                "========== Daily reports for daily periods =========="
    input bool                 InpSendDReport       =  true;             // Send daily report (per day and specified periods)
    input uint                 InpSendDReportHour   =  8;                // Hour of sending the report (Local time)
    input uint                 InpSendDReportMin    =  0;                // Minutes of sending the report (Local time)
    
    input group                "========= Daily reports for specified periods ========="
    input bool                 InpSendSReportDays   =  true;             // Send a report for the specified num days
    input uint                 InpSendSReportDaysN  =  7;                // Number of days to report for the specified number of days
    input bool                 InpSendSReportMonths =  true;             // Send a report for the specified num months
    input uint                 InpSendSReportMonthsN=  3;                // Number of months to report for the specified number of months
    input bool                 InpSendSReportYears  =  true;             // Send a report for the specified num years
    input uint                 InpSendSReportYearN  =  2;                // Number of years to report for the specified number of years
    
    input group                "======== Weekly reports for all other periods ========"
    input ENUM_DAY_OF_WEEK     InpSendWReportDayWeek=  SATURDAY;         // Day of sending the reports (Local time)
    input uint                 InpSendWReportHour   =  8;                // Hour of sending the reports (Local time)
    input uint                 InpSendWReportMin    =  0;                // Minutes of sending the reports (Local time)
    input bool                 InpSendWReport       =  true;             // Send a report for the current week
    input bool                 InpSendMReport       =  false;            // Send a report for the current month
    input bool                 InpSendYReport       =  false;            // Send a report for the current year
    input bool                 InpSendAReport       =  false;            // Send a report for the entire trading period
    
    //+------------------------------------------------------------------+
    //| Global variables                                                 |
    //+------------------------------------------------------------------+
    CAccounts      ExtAccounts;                                          // Account management object
    long           ExtLogin;                                             // Current account login
    string         ExtServer;                                            // Current account server
    bool           ExtNotify;                                            // Push notifications enabling flag
    
    //+------------------------------------------------------------------+
    //| Service program start function                                   |
    //+------------------------------------------------------------------+
    void OnStart()
      {
      }
    
    


    \MQL5\Include\Tools\DateTime.mqhファイルが含まれていることがわかります。これは標準のMqlDateTimeから継承された構造体です。

    //+------------------------------------------------------------------+
    //|                                                     DateTime.mqh |
    //|                             Copyright 2000-2024, MetaQuotes Ltd. |
    //|                                             https://www.mql5.com |
    //+------------------------------------------------------------------+
    //+------------------------------------------------------------------+
    //| Structure CDateTime.                                             |
    //| Purpose: Working with dates and time.                            |
    //|         Extends the MqlDateTime structure.                       |
    //+------------------------------------------------------------------+
    struct CDateTime : public MqlDateTime
      {
       //--- additional information
       string            MonthName(const int num) const;
       string            ShortMonthName(const int num) const;
       string            DayName(const int num) const;
       string            ShortDayName(const int num) const;
       string            MonthName(void)               const { return(MonthName(mon));            }
       string            ShortMonthName(void)          const { return(ShortMonthName(mon));       }
       string            DayName(void)                 const { return(DayName(day_of_week));      }
       string            ShortDayName(void)            const { return(ShortDayName(day_of_week)); }
       int               DaysInMonth(void) const;
       //--- data access
       datetime          DateTime(void)                      { return(StructToTime(this));        }
       void              DateTime(const datetime value)      { TimeToStruct(value,this);          }
       void              DateTime(const MqlDateTime& value)  { this=value;                        }
       void              Date(const datetime value);
       void              Date(const MqlDateTime &value);
       void              Time(const datetime value);
       void              Time(const MqlDateTime &value);
       //--- settings
       void              Sec(const int value);
       void              Min(const int value);
       void              Hour(const int value);
       void              Day(const int value);
       void              Mon(const int value);
       void              Year(const int value);
       //--- increments
       void              SecDec(int delta=1);
       void              SecInc(int delta=1);
       void              MinDec(int delta=1);
       void              MinInc(int delta=1);
       void              HourDec(int delta=1);
       void              HourInc(int delta=1);
       void              DayDec(int delta=1);
       void              DayInc(int delta=1);
       void              MonDec(int delta=1);
       void              MonInc(int delta=1);
       void              YearDec(int delta=1);
       void              YearInc(int delta=1);
       //--- check
       void              DayCheck(void);
      };
    
    

    この構造体には、日付と時刻を操作するための既成のメソッドが含まれています。統計期間の開始時間を計算する必要があります。まさにここで、現在の日付から日数、週数、月数、年数を減算したときに取得される日付の有効性を個別に計算しないようにするために、構造メソッドが役立ちます。ここでは、すべての計算は誤った値を修正して実行されます。たとえば、現在の日付から減算した日数がその月の日数より多い場合は、閏年を考慮しながら月と日を計算して結果の日付を調整する必要があります。しかし、特定の構造の日数、月数、年数を減らす方法を採用して、正しい最終日を即座に取得する方が簡単です。

    サービスアプリ自体は無限ループで動作する必要があります。ループ内で、約1秒の遅延を設定します。待機期間が終了したら、すべてのチェックと計算を手配する必要があります。ループの本体全体は、わかりやすく理解しやすいように、タイトル付きのブロックに分割されています。プログラム本体自体を見てみましょう。

    //+------------------------------------------------------------------+
    //| Service program start function                                   |
    //+------------------------------------------------------------------+
    void OnStart()
      {
    //---
       CArrayObj  *PositionsList  =  NULL;          // List of closed account positions
       long        account_prev   =  0;             // Previous login
       double      balance_prev   =  EMPTY_VALUE;   // Previous balance
       bool        Sent           =  false;         // Flag of sent report for non-daily periods
       int         day_of_year_prev= WRONG_VALUE;   // The previous day number of the year
       
    //--- Create lists of symbols and magic numbers traded in history and a list of messages for Push notifications
       CArrayString  *SymbolsList =  new CArrayString();
       CArrayLong    *MagicsList  =  new CArrayLong();
       CArrayString  *MessageList =  new CArrayString();
       if(SymbolsList==NULL || MagicsList==NULL || MessageList==NULL)
         {
          Print("Failed to create list CArrayObj");
          return;
         }
         
    //--- Check for the presence of MetaQuotes ID and permission to send notifications to it
       ExtNotify=CheckMQID();
       if(ExtNotify)
          Print(MQLInfoString(MQL_PROGRAM_NAME)+"-Service notifications OK");
    
    //--- The main loop
       int count=0;
       while(!IsStopped())
         {
          //+------------------------------------------------------------------+
          //| Delay in the loop                                                |
          //+------------------------------------------------------------------+
          //--- Increase the loop counter. If the counter has not exceeded the specified value, repeat
          Sleep(16);
          count+=10;
          if(count<COUNTER_DELAY)
             continue;
          //--- Waiting completed. Reset the loop counter
          count=0;
          
          //+------------------------------------------------------------------+
          //| Check notification settings                                      |
          //+------------------------------------------------------------------+
          //--- If the notification flag is not set, we check the notification settings in the terminal and, if activated, we report this
          if(!ExtNotify && TerminalInfoInteger(TERMINAL_MQID) && TerminalInfoInteger(TERMINAL_NOTIFICATIONS_ENABLED))
            {
             Print("Now MetaQuotes ID is specified and sending notifications is allowed");
             SendNotification("Now MetaQuotes ID is specified and sending notifications is allowed");
             ExtNotify=true;
            }
          //--- If the notification flag is set, but the terminal does not have permission for them, we report this
          if(ExtNotify && (!TerminalInfoInteger(TERMINAL_MQID) || !TerminalInfoInteger(TERMINAL_NOTIFICATIONS_ENABLED)))
            {
             string caption=MQLInfoString(MQL_PROGRAM_NAME);
             string message="The terminal has a limitation on sending notifications. Please check your notification settings";
             MessageBox(message, caption, MB_OK|MB_ICONWARNING);
             ExtNotify=false;
            }
          
          //+------------------------------------------------------------------+
          //| Change account                                                   |
          //+------------------------------------------------------------------+
          //--- If the current login is not equal to the previous one
          if(AccountInfoInteger(ACCOUNT_LOGIN)!=account_prev)
            {
             //--- if we failed to wait for the account data to be updated, repeat on the next loop iteration
             if(!DataUpdateWait(balance_prev))
                continue;
             
             //--- Received new account data
             //--- Save the current login and balance as previous ones for the next check
             account_prev=AccountInfoInteger(ACCOUNT_LOGIN);
             balance_prev=AccountInfoDouble(ACCOUNT_BALANCE);
             
             //--- Reset the sent message flag and call the account change handler
             Sent=false;
             AccountChangeHandler();
            }
          
          //+------------------------------------------------------------------+
          //| Daily reports                                                    |
          //+------------------------------------------------------------------+
          //--- Fill the structure with data about local time and date
          MqlDateTime tm={};
          TimeLocal(tm);
          
          //--- Clear the list of messages sent to MQID
          MessageList.Clear();
          
          //--- If the current day number in the year is not equal to the previous one, it is the beginning of a new day
          if(tm.day_of_year!=day_of_year_prev)
            {
             //--- If hours/minutes have reached the specified values for sending statistics
             if(tm.hour>=(int)InpSendDReportHour && tm.min>=(int)InpSendDReportMin)
               {
                //--- If sending daily statistics is allowed
                if(InpSendDReport)
                  {
                   //--- update the lists of closed positions for the day on the current account
                   ExtAccounts.PositionsRefresh(ExtLogin, ExtServer);
                   //--- if the settings are set to receive statistics from all accounts -
                   //--- get a list of closed positions of all accounts that were active when the service was running
                   if(InpUsedAccounts==USED_ACCOUNTS_ALL)
                      PositionsList=ExtAccounts.GetCommonPositionsList();
                   //--- otherwise, get the list of closed positions of the current account only
                   else
                      PositionsList=ExtAccounts.GetAccountPositionsList(ExtLogin, ExtServer);
                   
                   //--- Create messages about trading statistics for a daily time range,
                   //--- print the generated messages to the log and send them to MQID
                   SendReport(REPORT_RANGE_DAILY, 0, PositionsList, SymbolsList, MagicsList, MessageList);
                   
                   //--- If the settings allow sending trading statistics for the specified number of days,
                   //--- Create messages about trade statistics for the number of days in InpSendSReportDaysN,
                   //--- print the created messages to the log and add them to the list for sending to MQID
                   if(InpSendSReportDays)
                      SendReport(REPORT_RANGE_NUM_DAYS, InpSendSReportDaysN, PositionsList, SymbolsList, MagicsList, MessageList);
                   
                   //--- If the settings allow sending trading statistics for the specified number of months,
                   //--- Create messages about trade statistics for the number of months in InpSendSReportMonthsN,
                   //--- print the created messages to the log and add them to the list for sending to MQID
                   if(InpSendSReportMonths)
                      SendReport(REPORT_RANGE_NUM_MONTHS, InpSendSReportMonthsN, PositionsList, SymbolsList, MagicsList, MessageList);
                   
                   //--- If the settings allow sending trading statistics for the specified number of years,
                   //--- Create messages about trade statistics for the number of years in InpSendSReportYearN,
                   //--- print the created messages to the log and add them to the list for sending to MQID
                   if(InpSendSReportYears)
                      SendReport(REPORT_RANGE_NUM_YEARS, InpSendSReportYearN, PositionsList, SymbolsList, MagicsList, MessageList);
                  }
    
                //--- Set the current day as the previous one for subsequent verification
                day_of_year_prev=tm.day_of_year;
               }
            }
            
          //+------------------------------------------------------------------+
          //| Weekly reports                                                   |
          //+------------------------------------------------------------------+
          //--- If the day of the week is equal to the one set in the settings,
          if(tm.day_of_week==InpSendWReportDayWeek)
            {
             //--- if the message has not been sent yet and it is time to send messages
             if(!Sent && tm.hour>=(int)InpSendWReportHour && tm.min>=(int)InpSendWReportMin)
               {
                //--- update the lists of closed positions on the current account 
                ExtAccounts.PositionsRefresh(ExtLogin, ExtServer);
                
                //--- if the settings are set to receive statistics from all accounts -
                //--- get a list of closed positions of all accounts that were active when the service was running
                if(InpUsedAccounts==USED_ACCOUNTS_ALL)
                   PositionsList=ExtAccounts.GetCommonPositionsList();
                //--- otherwise, get the list of closed positions of the current account only
                else
                   PositionsList=ExtAccounts.GetAccountPositionsList(ExtLogin, ExtServer);
                
                //--- If the settings allow sending trading statistics for a week,
                //--- Create messages about trading statistics from the beginning of the current week,
                //--- print the created messages to the log and add them to the list for sending to MQID
                if(InpSendWReport)
                   SendReport(REPORT_RANGE_WEEK_BEGIN, 0, PositionsList, SymbolsList, MagicsList, MessageList);
                
                //--- If the settings allow sending trading statistics for a month,
                //--- Create messages about trading statistics from the beginning of the current month,
                //--- print the created messages to the log and add them to the list for sending to MQID
                if(InpSendMReport)
                   SendReport(REPORT_RANGE_MONTH_BEGIN, 0, PositionsList, SymbolsList, MagicsList, MessageList);
                
                //--- If the settings allow sending trading statistics for a year,
                //--- Create messages about trading statistics from the beginning of the current year,
                //--- print the created messages to the log and add them to the list for sending to MQID
                if(InpSendYReport)
                   SendReport(REPORT_RANGE_YEAR_BEGIN, 0, PositionsList, SymbolsList, MagicsList, MessageList);
                
                //--- If the settings allow sending trading statistics for the entire period,
                //--- Create messages about trading statistics from the start of the epoch (01.01.1970 00:00),
                //--- print the created messages to the log and add them to the list for sending to MQID
                if(InpSendAReport)
                   SendReport(REPORT_RANGE_ALL, 0, PositionsList, SymbolsList, MagicsList, MessageList);
                
                //--- Set the flag that all messages with statistics are sent to the journal
                Sent=true;
               }
            }
          //--- If the day of the week specified in the settings for sending statistics has not yet arrived, reset the flag of sent messages
          else
             Sent=false;
    
          //--- If the list of messages to send to MQID is not empty, call the function for sending notifications to a smartphone
          if(MessageList.Total()>0)
             SendMessage(MessageList);
         }
         
       //+------------------------------------------------------------------+
       //| Service shutdown                                                 |
       //+------------------------------------------------------------------+
       //--- Clear and delete lists of messages, symbols and magic numbers
       if(MessageList!=NULL)
         {
          MessageList.Clear();
          delete MessageList;
         }
       if(SymbolsList!=NULL)
         {
          SymbolsList.Clear();
          delete SymbolsList;
         }
       if(MagicsList!=NULL)
         {
          MagicsList.Clear();
          delete MagicsList;
         }
      }
    
    

    ご覧のとおり、サービスが起動されると、スマートフォンに通知を送信するための端末の権限の存在がチェックされます。CheckMQID()関数が呼び出され、各設定がチェックされ、クライアント端末設定で必要なパラメータを有効にするように要求されます。

    //+------------------------------------------------------------------+
    //| Check for the presence of MetaQuotes ID                          |
    //| and permission to send notifications to the mobile terminal      |
    //+------------------------------------------------------------------+
    bool CheckMQID(void)
      {
       string caption=MQLInfoString(MQL_PROGRAM_NAME); // Message box header
       string message=caption+"-Service OK";           // Message box text
       int    mb_id=IDOK;                              // MessageBox() return code
       
    //--- If MQID is not installed in the terminal settings, we will make a request to install it with explanations on the procedure
       if(!TerminalInfoInteger(TERMINAL_MQID))
         {
          message="The client terminal does not have a MetaQuotes ID for sending Push notifications.\n"+
                  "1. Install the mobile version of the MetaTrader 5 terminal from the App Store or Google Play.\n"+
                  "2. Go to the \"Messages\" section of your mobile terminal.\n"+
                  "3. Click \"MQID\".\n"+
                  "4. In the client terminal, in the \"Tools - Settings\" menu, in the \"Notifications\" tab, in the MetaQuotes ID field, enter the received code.";
          mb_id=MessageBox(message, caption, MB_RETRYCANCEL|MB_ICONWARNING);
         }
         
    //--- If the Cancel button is pressed, inform about the refusal to use Push notifications
       if(mb_id==IDCANCEL)
         {
          message="You refused to enter your MetaQuotes ID. The service will send notifications to the “Experts” tab of the terminal";
          MessageBox(message, caption, MB_OK|MB_ICONINFORMATION);
         }
         
    //--- If the Retry button is pressed, 
       else
         {
          //--- If the terminal has MetaQuotes ID installed for sending Push notifications
          if(TerminalInfoInteger(TERMINAL_MQID))
            {
             //--- if the terminal does not have permission to send notifications to a smartphone,
             if(!TerminalInfoInteger(TERMINAL_NOTIFICATIONS_ENABLED))
               {
                //--- show the message asking for permission to send notifications in the settings
                message="Please enable sending Push notifications in the terminal settings in the \"Notifications\" tab in the \"Tools - Settings\" menu.";
                mb_id=MessageBox(message, caption, MB_RETRYCANCEL|MB_ICONEXCLAMATION);
                
                //--- If the Cancel button is pressed in response to the message,
                if(mb_id==IDCANCEL)
                  {
                   //--- inform about the refusal to send notifications to a smartphone
                   string message="You have opted out of sending Push notifications. The service will send notifications to the “Experts” tab of the terminal.";
                   MessageBox(message, caption, MB_OK|MB_ICONINFORMATION);
                  }
                //--- If the Retry button is pressed in response to the message (this is expected to be done after enabling permission in the settings),
                //--- but there is still no permission to send notifications in the terminal,
                if(mb_id==IDRETRY && !TerminalInfoInteger(TERMINAL_NOTIFICATIONS_ENABLED))
                  {
                   //--- inform that the user has refused to send notifications to a smartphone, and messages will only be in the journal
                   string message="You have not allowed push notifications. The service will send notifications to the “Experts” tab of the terminal.";
                   MessageBox(message, caption, MB_OK|MB_ICONINFORMATION);
                  }
               }
            }
          //--- If the terminal has MetaQuotes ID installed for sending Push notifications,
          else
            {
             //--- inform that the terminal does not have MetaQuotes ID installed to send notifications to a smartphone, and messages will only be sent to the journal
             string message="You have not set your MetaQuotes ID. The service will send notifications to the “Experts” tab of the terminal";
             MessageBox(message, caption, MB_OK|MB_ICONINFORMATION);
            }
         }
    //--- Return the flag that MetaQuotes ID is set in the terminal and sending notifications is allowed
       return(TerminalInfoInteger(TERMINAL_MQID) && TerminalInfoInteger(TERMINAL_NOTIFICATIONS_ENABLED));
      }
    
    


    上記の関数が実行されると、ループが開始され、プログラム内で通知を送信する許可のフラグと、端末内でのこれらの許可の設定が制御されます。

          //+------------------------------------------------------------------+
          //| Check notification settings                                      |
          //+------------------------------------------------------------------+
          //--- If the notification flag is not set, we check the notification settings in the terminal and, if activated, we report this
          if(!ExtNotify && TerminalInfoInteger(TERMINAL_MQID) && TerminalInfoInteger(TERMINAL_NOTIFICATIONS_ENABLED))
            {
             Print("Now MetaQuotes ID is specified and sending notifications is allowed");
             SendNotification("Now MetaQuotes ID is specified and sending notifications is allowed");
             ExtNotify=true;
            }
          //--- If the notification flag is set, but the terminal does not have permission for them, we report this
          if(ExtNotify && (!TerminalInfoInteger(TERMINAL_MQID) || !TerminalInfoInteger(TERMINAL_NOTIFICATIONS_ENABLED)))
            {
             string caption=MQLInfoString(MQL_PROGRAM_NAME);
             string message="The terminal has a limitation on sending notifications. Please check your notification settings";
             MessageBox(message, caption, MB_OK|MB_ICONWARNING);
             ExtNotify=false;
            }
    
    

    端末で何かが変更された場合、サービスは必要な警告を発行します。有効になっていて無効になった場合、サービスは通知の送信に制限があることを報告します。逆に、無効になっているが、ユーザーが設定で権限を有効にした場合、サービスはすべてが正常になったことを報告し、これに関する通知をスマートフォンに送信します。

    ループの次の部分では、口座の変更がチェックされます。

          //+------------------------------------------------------------------+
          //| Change account                                                   |
          //+------------------------------------------------------------------+
          //--- If the current login is not equal to the previous one
          if(AccountInfoInteger(ACCOUNT_LOGIN)!=account_prev)
            {
             //--- if we failed to wait for the account data to be updated, repeat on the next loop iteration
             if(!DataUpdateWait(balance_prev))
                continue;
             
             //--- Received new account data
             //--- Save the current login and balance as previous ones for the next check
             account_prev=AccountInfoInteger(ACCOUNT_LOGIN);
             balance_prev=AccountInfoDouble(ACCOUNT_BALANCE);
             
             //--- Reset the sent message flag and call the account change handler
             Sent=false;
             AccountChangeHandler();
            }
    
    

    ログインが変更され、以前に記憶したものと異なるようになったらすぐに、現在の口座の読み込みを待機する関数を呼び出します。

    //+------------------------------------------------------------------+
    //| Waiting for account data update                                  |
    //+------------------------------------------------------------------+
    bool DataUpdateWait(double &balance_prev)
      {
       int attempts=0;   // Number of attempts
     
    //--- Until the program stop flag is disabled and until the number of attempts is less than the number set in REFRESH_ATTEMPTS
       while(!IsStopped() && attempts<REFRESH_ATTEMPTS)
         {
          //--- If the balance of the current account differs from the balance of the previously saved balance value,
          //--- we assume that we were able to obtain the account data, return 'true'
          if(NormalizeDouble(AccountInfoDouble(ACCOUNT_BALANCE)-balance_prev, 8)!=0)
             return true;
          
          //--- Wait half a second for the next attempt, increase the number of attempts and
          //--- log a message about waiting for data to be received and the number of attempts
          Sleep(500);
          attempts++;
          PrintFormat("%s::%s: Waiting for account information to update. Attempt %d", MQLInfoString(MQL_PROGRAM_NAME),__FUNCTION__, attempts);
         }
         
    //--- If failed to obtain the new account data after all attempts,
    //--- report this to the log, write an empty value to the "previous balance" and return 'false'
       PrintFormat("%s::%s: Could not wait for updated account data... Try again", MQLInfoString(MQL_PROGRAM_NAME),__FUNCTION__);
       balance_prev=EMPTY_VALUE;
       return false;
      }
    
    

    この関数は、端末キャッシュから口座残高データが受信されなくなるまで待機します。結局のところ、新しい口座の残高は以前の口座の残高とは異なる可能性があります。この関数は、以前の口座の記憶された残高と新しい口座の残高の差を取得するために、指定された回数試行します。失敗した場合(または残高がまだ等しい場合)、関数は最終的にEMPTY_VALUEを以前の残高に設定し、ループの次の反復で、この新しい値と比較して新しい口座の現在のデータを受信しているかどうかを確認します。この新しい値は、おそらく口座残高にはもう存在しません。

    次に、日付と時刻のチェックがおこなわれ、日次レポートと週次レポートが作成されます。

          //+------------------------------------------------------------------+
          //| Daily reports                                                    |
          //+------------------------------------------------------------------+
          //--- Fill the structure with data about local time and date
          MqlDateTime tm={};
          TimeLocal(tm);
          
          //--- Clear the list of messages sent to MQID
          MessageList.Clear();
          
          //--- If the current day number in the year is not equal to the previous one, it is the beginning of a new day
          if(tm.day_of_year!=day_of_year_prev)
            {
             //--- If hours/minutes have reached the specified values for sending statistics
             if(tm.hour>=(int)InpSendDReportHour && tm.min>=(int)InpSendDReportMin)
               {
                //--- If sending daily statistics is allowed
                if(InpSendDReport)
                  {
                   //--- update the lists of closed positions for the day on the current account
                   ExtAccounts.PositionsRefresh(ExtLogin, ExtServer);
                   //--- if the settings are set to receive statistics from all accounts -
                   //--- get a list of closed positions of all accounts that were active when the service was running
                   if(InpUsedAccounts==USED_ACCOUNTS_ALL)
                      PositionsList=ExtAccounts.GetCommonPositionsList();
                   //--- otherwise, get the list of closed positions of the current account only
                   else
                      PositionsList=ExtAccounts.GetAccountPositionsList(ExtLogin, ExtServer);
                   
                   //--- Create messages about trading statistics for a daily time range,
                   //--- print the generated messages to the log and send them to MQID
                   SendReport(REPORT_RANGE_DAILY, 0, PositionsList, SymbolsList, MagicsList, MessageList);
                   
                   //--- If the settings allow sending trading statistics for the specified number of days,
                   //--- Create messages about trade statistics for the number of days in InpSendSReportDaysN,
                   //--- print the created messages to the log and add them to the list for sending to MQID
                   if(InpSendSReportDays)
                      SendReport(REPORT_RANGE_NUM_DAYS, InpSendSReportDaysN, PositionsList, SymbolsList, MagicsList, MessageList);
                   
                   //--- If the settings allow sending trading statistics for the specified number of months,
                   //--- Create messages about trade statistics for the number of months in InpSendSReportMonthsN,
                   //--- print the created messages to the log and add them to the list for sending to MQID
                   if(InpSendSReportMonths)
                      SendReport(REPORT_RANGE_NUM_MONTHS, InpSendSReportMonthsN, PositionsList, SymbolsList, MagicsList, MessageList);
                   
                   //--- If the settings allow sending trading statistics for the specified number of years,
                   //--- Create messages about trade statistics for the number of years in InpSendSReportYearN,
                   //--- print the created messages to the log and add them to the list for sending to MQID
                   if(InpSendSReportYears)
                      SendReport(REPORT_RANGE_NUM_YEARS, InpSendSReportYearN, PositionsList, SymbolsList, MagicsList, MessageList);
                  }
    
                //--- Set the current day as the previous one for subsequent verification
                day_of_year_prev=tm.day_of_year;
               }
            }
            
          //+------------------------------------------------------------------+
          //| Weekly reports                                                   |
          //+------------------------------------------------------------------+
          //--- If the day of the week is equal to the one set in the settings,
          if(tm.day_of_week==InpSendWReportDayWeek)
            {
             //--- if the message has not been sent yet and it is time to send messages
             if(!Sent && tm.hour>=(int)InpSendWReportHour && tm.min>=(int)InpSendWReportMin)
               {
                //--- update the lists of closed positions on the current account 
                ExtAccounts.PositionsRefresh(ExtLogin, ExtServer);
                
                //--- if the settings are set to receive statistics from all accounts -
                //--- get a list of closed positions of all accounts that were active when the service was running
                if(InpUsedAccounts==USED_ACCOUNTS_ALL)
                   PositionsList=ExtAccounts.GetCommonPositionsList();
                //--- otherwise, get the list of closed positions of the current account only
                else
                   PositionsList=ExtAccounts.GetAccountPositionsList(ExtLogin, ExtServer);
                
                //--- If the settings allow sending trading statistics for a week,
                //--- Create messages about trading statistics from the beginning of the current week,
                //--- print the created messages to the log and add them to the list for sending to MQID
                if(InpSendWReport)
                   SendReport(REPORT_RANGE_WEEK_BEGIN, 0, PositionsList, SymbolsList, MagicsList, MessageList);
                
                //--- If the settings allow sending trading statistics for a month,
                //--- Create messages about trading statistics from the beginning of the current month,
                //--- print the created messages to the log and add them to the list for sending to MQID
                if(InpSendMReport)
                   SendReport(REPORT_RANGE_MONTH_BEGIN, 0, PositionsList, SymbolsList, MagicsList, MessageList);
                
                //--- If the settings allow sending trading statistics for a year,
                //--- Create messages about trading statistics from the beginning of the current year,
                //--- print the created messages to the log and add them to the list for sending to MQID
                if(InpSendYReport)
                   SendReport(REPORT_RANGE_YEAR_BEGIN, 0, PositionsList, SymbolsList, MagicsList, MessageList);
                
                //--- If the settings allow sending trading statistics for the entire period,
                //--- Create messages about trading statistics from the start of the epoch (01.01.1970 00:00),
                //--- print the created messages to the log and add them to the list for sending to MQID
                if(InpSendAReport)
                   SendReport(REPORT_RANGE_ALL, 0, PositionsList, SymbolsList, MagicsList, MessageList);
                
                //--- Set the flag that all messages with statistics are sent to the journal
                Sent=true;
               }
            }
          //--- If the day of the week specified in the settings for sending statistics has not yet arrived, reset the flag of sent messages
          else
             Sent=false;
    
          //--- If the list of messages to send to MQID is not empty, call the function for sending notifications to a smartphone
          if(MessageList.Total()>0)
             SendMessage(MessageList);
    
    

    ここではすべてのロジックがコードにコメントされています。ループ内でメッセージを作成した直後にスマートフォンにメッセージを送信することはできないことに注意してください。このようなメッセージは多数存在する可能性があるため(設定でどのレポートが選択されているかによって異なります)、プッシュ通知には厳しい制限が設定されています(1秒あたり2件以下、1分あたり10件以下)。したがって、作成されたすべてのメッセージは、標準ライブラリのCArrayStringリストに設定されます。すべてのレポートが作成された後、この配列が空でない場合は、スマートフォンに通知を送信する関数を呼び出します。この関数では、設定された制限に違反しないように、必要なすべての送信遅延が調整されます。

    サービスアプリを操作するために使用するすべての機能を見てみましょう。

    以下は、指定された範囲の統計情報のリストを返す関数です。

    //+------------------------------------------------------------------+
    //| Return a list with the specified statistics range                |
    //+------------------------------------------------------------------+
    CArrayObj *GetListDataRange(ENUM_REPORT_RANGE range, CArrayObj *list, datetime &time_start, const int num_periods)
      {
    //--- Current date
       CDateTime current={};
       current.Date(TimeLocal());
       
    //--- Period start date
       CDateTime begin_range=current;
       
    //--- Set the period start time to 00:00:00
       begin_range.Hour(0);
       begin_range.Min(0);
       begin_range.Sec(0);
       
    //--- Adjust the start date of the period depending on the specified period of required statistics
       switch(range)
         {
          //--- Day
          case REPORT_RANGE_DAILY       :  // decrease Day by 1
            begin_range.DayDec(1);       
            break;
            
          //--- Since the beginning of the week
          case REPORT_RANGE_WEEK_BEGIN  :  // decrease Day by (number of days passed in the week)-1
            begin_range.DayDec(begin_range.day_of_week==SUNDAY ? 6 : begin_range.day_of_week-1);
            break;
            
          //--- Since the beginning of the month
          case REPORT_RANGE_MONTH_BEGIN :  // set the first day of the month as Day
            begin_range.Day(1);
            break;
            
          //--- Since the beginning of the year
          case REPORT_RANGE_YEAR_BEGIN  :  // set Month to the first month of the year, and Day to the first day of the month
            begin_range.Mon(1);
            begin_range.Day(1);
            break;
            
          //--- Number of days
          case REPORT_RANGE_NUM_DAYS    :  // Decrease Day by the specified number of days
            begin_range.DayDec(fabs(num_periods));
            break;
            
          //--- Number of months
          case REPORT_RANGE_NUM_MONTHS  :  // Decrease Month by the specified number of months
            begin_range.MonDec(fabs(num_periods));
            break;
            
          //--- Number of years
          case REPORT_RANGE_NUM_YEARS   :  // Decrease Year by the specified number of years
            begin_range.YearDec(fabs(num_periods));
            break;
            
          //---REPORT_RANGE_ALL Entire period
          default                       :  // Set the date to 1970.01.01
            begin_range.Year(1970);
            begin_range.Mon(1);
            begin_range.Day(1);
            break;
         }
         
    //--- Write the start date of the period and return the pointer to the list of positions,
    //--- the opening time of which is greater than or equal to the start time of the requested period
       time_start=begin_range.DateTime();
       return CSelect::ByPositionProperty(list,POSITION_PROP_TIME,time_start,EQUAL_OR_MORE);
      }
    
    

    この関数は、処理する統計の範囲(毎日、週の初め、月、年、指定された日数、月数、年数、または全取引期間)の指示と、期間の開始日で並べ替える必要があるクローズ済みポジションのリストを受け取ります。次に、受信した統計の範囲に応じて、必要な範囲の開始日を調整し計算された日付の初めからのクローズ済みポジションのリストを取得して返します


    以下は、口座変更ハンドラ関数です。

    //+------------------------------------------------------------------+
    //| Account change handler                                           |
    //+------------------------------------------------------------------+
    void AccountChangeHandler(void)
      {
    //--- Set the current account login and server
       long   login  = AccountInfoInteger(ACCOUNT_LOGIN);
       string server = AccountInfoString(ACCOUNT_SERVER);
       
    //--- Get the pointer to the account object based on the current account data
       CAccount *account = ExtAccounts.Get(login, server);
       
    //--- If the object is empty, create a new account object and get a pointer to it
       if(account==NULL && ExtAccounts.Create(login, server))
          account=ExtAccounts.Get(login, server);
       
    //--- If the account object is eventually not received, report this and leave
       if(account==NULL)
         {
          PrintFormat("Error getting access to account object: %I64d (%s)", login, server);
          return;
         }
       
    //--- Set the current login and server values from the account object data
       ExtLogin =account.Login();
       ExtServer=account.Server();
       
    //--- Display the account data in the journal and display a message about the start of creating the list of closed positions
       account.Print();
       Print("Beginning to create a list of closed positions...");
    
    //--- Create the list of closed positions and report the number of created positions and the time spent in the journal upon completion
       ulong start=GetTickCount();
       ExtAccounts.PositionsRefresh(ExtLogin, ExtServer);
       PrintFormat("A list of %d positions was created in %I64u ms", account.PositionsTotal(), GetTickCount()-start);
      }
    
    

    ハンドラでは、以前に使用されたことがない場合は新しい口座オブジェクトが作成され、以前に接続されていた場合は以前に作成された口座へのポインタが取得されます。その後、口座のクローズ済みポジションのリストが作成されます。操作ログには、履歴ポジションのリストの作成の開始、完了、およびそれに費やされたミリ秒数に関するメッセージが表示されます。


    以下は、指定された時間範囲の統計を作成する関数です。

    //+------------------------------------------------------------------+
    //| Create statistics for the specified time range                   |
    //+------------------------------------------------------------------+
    void SendReport(ENUM_REPORT_RANGE range, int num_periods, CArrayObj *list_common, CArrayString *list_symbols, CArrayLong *list_magics, CArrayString *list_msg)
      {
       string array_msg[2] = {NULL, NULL};    // Array of messages (0) for displaying in the journal and (1) for sending to a smartphone
       datetime time_start = 0;               // Here we will store the start time of the statistics period
       CArrayObj *list_tmp = NULL;            // Temporary list for sorting by symbols and magic number
       
    //--- Get a list of positions for the 'range' period
       CArrayObj *list_range=GetListDataRange(range, list_common, time_start, num_periods);
       if(list_range==NULL)
          return;
          
    //--- If the list of positions is empty, report to the journal that there were no transactions for the given period of time
       if(list_range.Total()==0)
         {
          PrintFormat("\"%s\" no trades",ReportRangeDescription(range, num_periods));
          return;
         }
    
    //--- Create the lists of symbols and magic numbers of positions in the received list of closed positions for a period of time, while resetting them beforehand
       list_symbols.Clear();
       list_magics.Clear();
       CreateSymbolMagicLists(list_range, list_symbols, list_magics);
       
    //--- Create statistics on closed positions for the specified period,
    //--- print the generated statistics from array_msg[0] in the journal and
    //--- set the string from array_msg[1] to the list of messages for push notifications
       if(CreateStatisticsMessage(range, num_periods, REPORT_BY_RANGE, MQLInfoString(MQL_PROGRAM_NAME),time_start, list_range, list_symbols, list_magics, 0, array_msg))
         {
          Print(StatisticsRangeTitle(range, num_periods, REPORT_BY_RANGE, time_start));       // Statistics title
          Print(StatisticsTableHeader("Symbols ", InpCommissionsInclude, InpSpreadInclude));  // Table header 
          Print(array_msg[0]);                                                                // Statistics for a period of time
          Print("");                                                                          // String indentation
          list_msg.Add(array_msg[1]);                                                         // Save the message for Push notifications to the list for later sending
         }
       
    //--- If statistics are allowed separately by symbols
       if(InpReportBySymbols)
         {
          //--- Display the statistics and table headers to the journal
          Print(StatisticsRangeTitle(range, num_periods, REPORT_BY_SYMBOLS, time_start));
          Print(StatisticsTableHeader("Symbol ", InpCommissionsInclude, InpSpreadInclude));
          
          //--- In the loop by the list of symbols,
          for(int i=0; i<list_symbols.Total(); i++)
            {
             //--- get the name of the next symbol
             string symbol=list_symbols.At(i);
             if(symbol=="")
                continue;
             //--- sort out the list of positions leaving only positions with the received symbol
             list_tmp=CSelect::ByPositionProperty(list_range, POSITION_PROP_SYMBOL, symbol, EQUAL);
             
             //--- Create statistics on closed positions for the specified period by the current list symbol,
             //--- print the generated statistics from array_msg[0] and
             //--- set the string from array_msg[1] to the list of messages for push notifications
             if(CreateStatisticsMessage(range, num_periods, REPORT_BY_SYMBOLS, MQLInfoString(MQL_PROGRAM_NAME), time_start, list_tmp, list_symbols, list_magics, i, array_msg))
               {
                Print(array_msg[0]);
                list_msg.Add(array_msg[1]);
               }
            }
          //--- After the loop has completed for all symbols, display the separator line to the journal
          Print("");
         }
       
    //--- If statistics are allowed separately by magic numbers
       if(InpReportByMagics)
         {
          //--- Display the statistics and table headers to the journal
          Print(StatisticsRangeTitle(range, num_periods, REPORT_BY_MAGICS, time_start));
          Print(StatisticsTableHeader("Magic ", InpCommissionsInclude, InpSpreadInclude));
          
          //--- In the loop by the list of magic numbers,
          for(int i=0; i<list_magics.Total(); i++)
            {
             //--- get the next magic number
             long magic=list_magics.At(i);
             if(magic==LONG_MAX)
                continue;
             //--- sort out the list of positions leaving only positions with the received magic number
             list_tmp=CSelect::ByPositionProperty(list_range, POSITION_PROP_MAGIC, magic, EQUAL);
             
             //--- Create statistics on closed positions for the specified period by the current list magic number,
             //--- print the generated statistics from array_msg[0] and
             //--- set the string from array_msg[1] to the list of messages for push notifications
             if(CreateStatisticsMessage(range, num_periods, REPORT_BY_MAGICS, MQLInfoString(MQL_PROGRAM_NAME), time_start, list_tmp, list_symbols, list_magics, i, array_msg))
               {
                Print(array_msg[0]);
                list_msg.Add(array_msg[1]);
               }
            }
          //--- After the loop has completed for all magic numbers, display the separator line to the journal
          Print("");
         }
      }
    
    

    この関数は、指定された取引期間の統計を作成する関数を呼び出し、操作ログにテーブルヘッダーと統計を表示します。プッシュ通知のメッセージは、メソッドに渡されるメッセージのリストへのポインタに設定されます。統計に銘柄とマジックナンバーによるレポートが含まれている場合、メイン統計が操作ログに送信された後、銘柄とマジックナンバーによる統計テーブルのタイトルとヘッダーが表示されます。その後に、表形式で記号とマジックナンバーによるレポートが続きます。


    以下は、テーブルヘッダー行を作成して返す関数です。

    //+------------------------------------------------------------------+
    //| Create and return the table header row                           |
    //+------------------------------------------------------------------+
    string StatisticsTableHeader(const string first, const bool commissions, const bool spreads)
      {
    //--- Declare and initialize the table column headers
       string h_trades="Trades ";
       string h_long="Long ";
       string h_short="Short ";
       string h_profit="Profit ";
       string h_max="Max ";
       string h_min="Min ";
       string h_avg="Avg ";
       string h_costs="Costs ";
    //--- table columns disabled in the settings
       string h_commiss=(commissions ? "Commiss " : "");
       string h_swap=(commissions    ? "Swap "    : "");
       string h_fee=(commissions     ? "Fee "     : "");
       string h_spread=(spreads      ? "Spread "  : "");
    //--- width of table columns
       int w=TABLE_COLUMN_W;
       int c=(commissions ? TABLE_COLUMN_W : 0);
       
    //--- Table column separators that can be disabled in the settings
       string sep1=(commissions ? "|" : "");
       string sep2=(spreads ? "|" : "");
       
    //--- Create a table header row
       return StringFormat("|%*s|%*s|%*s|%*s|%*s|%*s|%*s|%*s|%*s|%*s%s%*s%s%*s%s%*s%s",
                           w,first,
                           w,h_trades,
                           w,h_long,
                           w,h_short,
                           w,h_profit,
                           w,h_max,
                           w,h_min,
                           w,h_avg,
                           w,h_costs,
                           c,h_commiss,sep1,
                           c,h_swap,sep1,
                           c,h_fee,sep1,
                           w,h_spread,sep2);
                           
      }
    
    

    この関数は次の行を作成します。

    |  Symbols |   Trades |     Long |    Short |   Profit |      Max |      Min |      Avg |    Costs |  Commiss |     Swap |      Fee |   Spread |
    
    

    最後の4つの列-統計で手数料、スワップ、取引手数料、スプレッド値の使用が許可されているかどうかに応じて表示が異なります。

    異なるテーブルには異なるヘッダーが必要なため、ヘッダーの最初の列には、パラメータで関数に渡される名前が含まれます。

    テキストメッセージの書式設定の詳細については、「PrintFormat()の学習と既成の例の適用」稿および「StringFormat():レビューと既成例」稿を参照してください。


    以下は、要求された統計期間の説明ヘッダーを返す関数です。

    //+------------------------------------------------------------------+
    //| Return the description header of the requested statistics period |
    //+------------------------------------------------------------------+
    string StatisticsRangeTitle(const ENUM_REPORT_RANGE range, const int num_periods, const ENUM_REPORT_BY report_by, const datetime time_start, const string symbol=NULL, const long magic=LONG_MAX)
      {
       string report_by_str=
         (
          report_by==REPORT_BY_SYMBOLS  ?  (symbol==NULL     ?  "by symbols "  :  "by "+symbol+" ") :
          report_by==REPORT_BY_MAGICS   ?  (magic==LONG_MAX  ?  "by magics "   :  "by magic #"+(string)magic+" ") : ""
         );
       return StringFormat("Report %sfor the period \"%s\" from %s", report_by_str,ReportRangeDescription(range, num_periods), TimeToString(time_start, TIME_DATE));
      }
    
    

    統計範囲と統計フィルター(銘柄、マジックナンバー、日付)に応じて、次のタイプの文字列が作成され、返されます。

    Report for the period "3 months" from 2024.04.23 00:00
    
    

    または

    Report by symbols for the period "3 months" from 2024.04.23 00:00
    
    

    または

    Report by magics for the period "3 months" from 2024.04.23 00:00
    
    

    などです。

    以下は、統計情報を含むメッセージテキストを返す関数です。

    //+------------------------------------------------------------------+
    //| Return a message text with statistics                            |
    //+------------------------------------------------------------------+
    bool CreateStatisticsMessage(const ENUM_REPORT_RANGE range, const int num_periods, const ENUM_REPORT_BY report_by, const string header, const datetime time_start,
                                 CArrayObj *list, CArrayString *list_symbols, CArrayLong *list_magics, const int index, string &array_msg[])
      {
    //--- Get a symbol and a magic number by index from the passed lists
       string   symbol = list_symbols.At(index);
       long     magic  = list_magics.At(index);
    //--- If the passed lists are empty, or no data was received from them, return 'false'
       if(list==NULL || list.Total()==0 || (report_by==REPORT_BY_SYMBOLS && symbol=="") || (report_by==REPORT_BY_MAGICS && magic==LONG_MAX))
          return false;
       
       CPosition  *pos_min  =  NULL;          // Pointer to the position with the minimum property value
       CPosition  *pos_max  =  NULL;          // Pointer to the position with the maximum property value
       CArrayObj  *list_tmp =  NULL;          // Pointer to a temporary list for sorting by properties
       int         index_min=  WRONG_VALUE;   // Index of the position in the list with the minimum property value
       int         index_max=  WRONG_VALUE;   // Index of the position in the list with the maximum property value
       
    //--- Get the sum of the position properties from the list of positions
       double profit=PropertyValuesSum(list, POSITION_PROP_PROFIT);            // Total profit of positions in the list
       double commissions=PropertyValuesSum(list,POSITION_PROP_COMMISSIONS);   // Total commission of positions in the list
       double swap=PropertyValuesSum(list, POSITION_PROP_SWAP);                // General swap of positions in the list
       double fee=PropertyValuesSum(list, POSITION_PROP_FEE);                  // Total deal fee in the list 
       double costs=commissions+swap+fee;                                      // All commissions
       double spreads=PositionsCloseSpreadCostSum(list);                       // Total spread costs for all items in the list
       
    //--- Define text descriptions of all received values
       string s_0=(report_by==REPORT_BY_SYMBOLS ? symbol : report_by==REPORT_BY_MAGICS ? (string)magic : (string)list_symbols.Total())+" ";
       string s_trades=StringFormat("%d ", list.Total());
       string s_profit=StringFormat("%+.2f ", profit);
       string s_costs=StringFormat("%.2f ",costs);
       string s_commiss=(InpCommissionsInclude ? StringFormat("%.2f ",commissions) : "");
       string s_swap=(InpCommissionsInclude ? StringFormat("%.2f ",swap) : "");
       string s_fee=(InpCommissionsInclude ? StringFormat("%.2f ",fee) : "");
       string s_spread=(InpSpreadInclude ? StringFormat("%.2f ",spreads) : "");
       
    //--- Get the list of only long positions and create a description of their quantity
       list_tmp=CSelect::ByPositionProperty(list, POSITION_PROP_TYPE, POSITION_TYPE_BUY, EQUAL);
       string s_long=(list_tmp!=NULL ? (string)list_tmp.Total() : "0")+" ";
       
    //--- Get the list of only short positions and create a description of their quantity
       list_tmp=CSelect::ByPositionProperty(list, POSITION_PROP_TYPE, POSITION_TYPE_SELL, EQUAL);
       string s_short=(list_tmp!=NULL ? (string)list_tmp.Total() : "0")+" ";
       
    //--- Get the index of the position in the list with the maximum profit and create a description of the received value
       index_max=CSelect::FindPositionMax(list, POSITION_PROP_PROFIT);
       pos_max=list.At(index_max);
       double profit_max=(pos_max!=NULL ? pos_max.Profit() : EMPTY_VALUE);
       string s_max=(profit_max!=EMPTY_VALUE ? StringFormat("%+.2f ",profit_max) : "No trades ");
       
    //--- Get the index of the position in the list with the minimum profit and create a description of the received value
       index_min=CSelect::FindPositionMin(list, POSITION_PROP_PROFIT);
       pos_min=list.At(index_min);
       double profit_min=(pos_min!=NULL ? pos_min.Profit() : EMPTY_VALUE);
       string s_min=(profit_min!=EMPTY_VALUE ? StringFormat("%+.2f ",profit_min) : "No trades ");
       
    //--- Create a description of the average profit value of all positions in the list
       string s_avg=StringFormat("%.2f ", PropertyAverageValue(list, POSITION_PROP_PROFIT));
       
    //--- Table column width
       int w=TABLE_COLUMN_W;
       int c=(InpCommissionsInclude ? TABLE_COLUMN_W : 0);
    
    //--- Separators for table columns that can be disabled in the settings
       string sep1=(InpCommissionsInclude ? "|" : "");
       string sep2=(InpSpreadInclude ? "|" : "");
       
    //--- For displaying in the journal, create a string with table columns featuring the values obtained above
       array_msg[0]=StringFormat("|%*s|%*s|%*s|%*s|%*s|%*s|%*s|%*s|%*s|%*s%s%*s%s%*s%s%*s%s",
                                 w,s_0,
                                 w,s_trades,
                                 w,s_long,
                                 w,s_short,
                                 w,s_profit,
                                 w,s_max,
                                 w,s_min,
                                 w,s_avg,
                                 w,s_costs,
                                 c,s_commiss,sep1,
                                 c,s_swap,sep1,
                                 c,s_fee,sep1,
                                 w,s_spread,sep2);
       
    //--- For sending MQID notifications, create a string with table columns featuring the values obtained above
       array_msg[1]=StringFormat("%s:\nTrades: %s Long: %s Short: %s\nProfit: %s Max: %s Min: %s Avg: %s\n%s%s%s%s%s",
                                 StatisticsRangeTitle(range, num_periods, report_by, time_start, (report_by==REPORT_BY_SYMBOLS ? symbol : NULL), (report_by==REPORT_BY_MAGICS ? magic : LONG_MAX)),
                                 s_trades,
                                 s_long,
                                 s_short,
                                 s_profit,
                                 s_max,
                                 s_min,
                                 s_avg,
                                 (costs!=0 ? "Costs: "+s_costs : ""),
                                 (InpCommissionsInclude && commissions!=0 ? " Commiss: "+s_commiss : ""),
                                 (InpCommissionsInclude && swap!=0        ? " Swap: "+s_swap       : ""),
                                 (InpCommissionsInclude && fee!=0         ? " Fee: "+s_fee         : ""),
                                 (InpSpreadInclude      && spreads!=0     ? " Spreads: "+s_spread  : ""));
    //--- All is successful
       return true;
      }
    
    

    この関数は、前に実装したCSelectクラスを使用して、リストを並び替え、クローズされたポジションのインデックスを検索します。受信したリストからレポートにデータを表示するためのテキストが作成されます。
    レポートテキストは、関数の最後に2つのコピー(操作ログの表形式での表示用と、プッシュ通知用の通常の行用)で作成されます


    以下は、渡されたリストからマジックナンバーとポジション銘柄のリストを埋める関数です。

    //+--------------------------------------------------------------------------+
    //| Fill the lists of magic numbers and position symbols from the passed list|
    //+--------------------------------------------------------------------------+
    void CreateSymbolMagicLists(CArrayObj *list, CArrayString *list_symbols, CArrayLong *list_magics)
      {
    //--- If an invalid pointer to a list of positions is passed, or the list is empty, leave
       if(list==NULL || list.Total()==0)
          return;
       
       int index=WRONG_VALUE;  // Index of the necessary symbol or magic number in the list
    
    //--- In a loop by the list of positions
       for(int i=0; i<list.Total(); i++)
         {
          //--- get the pointer to the next position 
          CPosition *pos=list.At(i);
          if(pos==NULL)
             continue;
       
          //--- Get the position symbol
          string symbol=pos.Symbol();
       
          //--- Set the sorted list flag for the symbol list and get the symbol index in the symbol list
          list_symbols.Sort();
          index=list_symbols.Search(symbol);
          
          //--- If there is no such symbol in the list, add it
          if(index==WRONG_VALUE)
             list_symbols.Add(symbol);
       
          //--- Get the position magic number
          long magic=pos.Magic();
          
          //--- Set the sorted list flag for the magic number list and get the magic number index in the list of magic numbers
          list_magics.Sort();
          index=list_magics.Search(magic);
          
          //--- If there is no such magic number in the list, add it
          if(index==WRONG_VALUE)
             list_magics.Add(magic);
         }
      }
    
    

    当初、口座での取引にどのような銘柄とマジックナンバーが使用されたかはわかりません。銘柄とマジックナンバーによるレポートを受け取るには、すべてのクローズポジションの完全なリストでクローズポジションのすべての銘柄とすべてのマジックナンバーを見つけて、対応するリストに書き留める必要があります。すべてのクローズ済みポジションの完全なリストと、銘柄およびマジックナンバーリストへのポインタがこの関数に渡されます。見つかったすべての銘柄とマジックナンバーは、対応するリストに記録されます。関数操作が完了すると、銘柄とマジックナンバーの2つの入力済みリストが作成されます。これを使用して、銘柄とマジックナンバーに関するレポートを個別にコンパイルできます。

    リスト内のすべてのポジションの任意の整数または実数プロパティの値の合計を取得するには、ループ内でこのプロパティの値を追加する必要があります。これは何故必要なのでしょうか。たとえば、合計スプレッドの値、または合計利益または損失を取得します。リスト内のすべてのポジションの指定されたプロパティの値を合計できる関数を記述しましょう。

    以下は、リスト内のすべての項目の指定された整数プロパティの値の合計を返す関数です。

    //+------------------------------------------------------------------+
    //| Return the sum of the values of the specified                    |
    //| integer property of all positions in the list                    |
    //+------------------------------------------------------------------+
    long PropertyValuesSum(CArrayObj *list, const ENUM_POSITION_PROPERTY_INT property)
      {
       long res=0;
       int total=list.Total();
       for(int i=0; i<total; i++)
         {
          CPosition *pos=list.At(i);
          res+=(pos!=NULL ? pos.GetProperty(property) : 0);
         }
       return res;
      }
    
    

    関数にポインタが渡されるリストに沿ったループで、ループインデックスによってオブジェクトから指定されたプロパティの値を取得し、結果の値に追加します。その結果、ループの最後には、関数に渡されたリスト内のすべてのポジションの指定されたプロパティの値の合計が得られます。

    以下は、リスト内のすべての項目の指定された実数プロパティの値の合計を返す関数です。

    //+------------------------------------------------------------------+
    //| Return the sum of the values of the specified                    |
    //| real property of all positions in the list                       |
    //+------------------------------------------------------------------+
    double PropertyValuesSum(CArrayObj *list, const ENUM_POSITION_PROPERTY_DBL property)
      {
       double res=0;
       int total=list.Total();
       for(int i=0; i<total; i++)
         {
          CPosition *pos=list.At(i);
          res+=(pos!=NULL ? pos.GetProperty(property) : 0);
         }
       return res;
      }
    
    


    同じ原理を使用して、指定されたプロパティの平均値を返す関数を作成します。

    以下は、リスト内のすべてのポジションの指定された整数プロパティの平均値を返す関数です。

    //+------------------------------------------------------------------+
    //| Return the average value of the specified                        |
    //| integer property of all positions in the list                    |
    //+------------------------------------------------------------------+
    double PropertyAverageValue(CArrayObj *list, const ENUM_POSITION_PROPERTY_INT property)
      {
       long res=0;
       int total=list.Total();
       for(int i=0; i<total; i++)
         {
          CPosition *pos=list.At(i);
          res+=(pos!=NULL ? pos.GetProperty(property) : 0);
         }
       return(total>0 ? (double)res/(double)total : 0);
      }
    
    


    以下は、リスト内のすべてのポジションの指定された材料特性の平均値を返す関数です。

    //+------------------------------------------------------------------+
    //| Return the average value of the specified                        |
    //| real property of all positions in the list                       |
    //+------------------------------------------------------------------+
    double PropertyAverageValue(CArrayObj *list, const ENUM_POSITION_PROPERTY_DBL property)
      {
       double res=0;
       int total=list.Total();
       for(int i=0; i<total; i++)
         {
          CPosition *pos=list.At(i);
          res+=(pos!=NULL ? pos.GetProperty(property) : 0);
         }
       return(total>0 ? res/(double)total : 0);
      }
    
    


    以下は、リスト内のすべてのポジションをクローズする取引のスプレッドコストの合計を返す関数です。

    //+------------------------------------------------------------------+
    //| Returns the sum of the spread costs                              |
    //| of deals closing all positions in the list                       |
    //+------------------------------------------------------------------+
    double PositionsCloseSpreadCostSum(CArrayObj *list)
      {
       double res=0;
       if(list==NULL)
          return 0;
       int total=list.Total();
       for(int i=0; i<total; i++)
         {
          CPosition *pos=list.At(i);
          res+=(pos!=NULL ? pos.SpreadOutCost() : 0);
         }
       return res;
      }
    
    

    ポジションには「スプレッドコスト」プロパティがないため、上記の関数をここで使用することはできません。したがって、ここでは、ポジションをクローズするときにスプレッドのコストを計算して返すポジションオブジェクトのメソッドを直接使用します。リスト内のすべてのポジションの取得された値をすべて最終結果に追加し、取得された値を返します。


    以下は、レポート期間の説明を返す関数です。

    //+------------------------------------------------------------------+
    //| Return the report period description                             |
    //+------------------------------------------------------------------+
    string ReportRangeDescription(ENUM_REPORT_RANGE range, const int num_period)
      {
       switch(range)
         {
          //--- Day
          case REPORT_RANGE_DAILY       : return("Daily");
          //--- Since the beginning of the week
          case REPORT_RANGE_WEEK_BEGIN  : return("Weekly");
          //--- Since the beginning of the month
          case REPORT_RANGE_MONTH_BEGIN : return("Month-to-date");
          //--- Since the beginning of the year
          case REPORT_RANGE_YEAR_BEGIN  : return("Year-to-date");
          //--- Number of days
          case REPORT_RANGE_NUM_DAYS    : return StringFormat("%d days", num_period);
          //--- Number of months
          case REPORT_RANGE_NUM_MONTHS  : return StringFormat("%d months", num_period);
          //--- Number of years
          case REPORT_RANGE_NUM_YEARS   : return StringFormat("%d years", num_period);
          //--- Entire period
          case REPORT_RANGE_ALL         : return("Entire period");
          //--- any other
          default                       : return("Unknown period: "+(string)range);
         }
      }
    
    

    渡されたレポート期間の値と日数/月数/年数に応じて、説明文字列が作成され、返されます。


    サービスプログラムのすべての機能と、プログラム自体(メインループ)について説明しました。コンパイルしてサービスを起動してみましょう。コンパイル後、プログラムはNavigator端末ウィンドウのサービスセクションに配置されます。

    サービスを右クリックし、「サービスの追加」を選択します。


    プログラム設定ウィンドウが開きます。



    サービスが開始されると、以下の内容を含む日次レポートが作成されます。

    • 3か月間の一般レポートと、銘柄とマジックナンバーに関する3か月間のレポート
    • 2年間の一般レポートと、銘柄とマジックナンバーに関する2年間のレポート
    Reporter        -Service notifications OK
    Reporter        68008618: Artem (MetaQuotes Ltd., Demo, 10779.50 USD, Hedging)
    Reporter        Beginning to create a list of closed positions...
    Reporter        A list of 155 positions was created in 8828 ms
    Reporter        "Daily" no trades
    Reporter        "7 days" no trades
    Reporter        Report for the period "3 months" from 2024.04.23 00:00
    Reporter        |  Symbols |   Trades |     Long |    Short |   Profit |      Max |      Min |      Avg |    Costs |  Commiss |     Swap |      Fee |   Spread |
    Reporter        |        2 |       77 |       17 |       60 |  +247.00 |   +36.70 |    -0.40 |     3.20 |     0.00 |     0.00 |     0.00 |     0.00 |     5.10 |
    Reporter        
    Reporter        Report by symbols for the period "3 months" from 2024.04.23 00:00
    Reporter        |   Symbol |   Trades |     Long |    Short |   Profit |      Max |      Min |      Avg |    Costs |  Commiss |     Swap |      Fee |   Spread |
    Reporter        |   EURUSD |       73 |       17 |       56 |  +241.40 |   +36.70 |    -0.40 |     3.30 |     0.00 |     0.00 |     0.00 |     0.00 |     4.30 |
    Reporter        |   GBPUSD |        4 |        0 |        4 |    +5.60 |    +2.20 |    +0.10 |     1.40 |     0.00 |     0.00 |     0.00 |     0.00 |     0.80 |
    Reporter        
    Reporter        Report by magics for the period "3 months" from 2024.04.23 00:00
    Reporter        |    Magic |   Trades |     Long |    Short |   Profit |      Max |      Min |      Avg |    Costs |  Commiss |     Swap |      Fee |   Spread |
    Reporter        |        0 |       75 |       15 |       60 |  +246.60 |   +36.70 |    -0.40 |     3.28 |     0.00 |     0.00 |     0.00 |     0.00 |     4.90 |
    Reporter        | 10879099 |        1 |        1 |        0 |    +0.40 |    +0.40 |    +0.40 |     0.40 |     0.00 |     0.00 |     0.00 |     0.00 |     0.10 |
    Reporter        | 27394171 |        1 |        1 |        0 |    +0.00 |    +0.00 |    +0.00 |     0.00 |     0.00 |     0.00 |     0.00 |     0.00 |     0.10 |
    Reporter        
    Reporter        Report for the period "2 years" from 2022.07.23 00:00
    Reporter        |  Symbols |   Trades |     Long |    Short |   Profit |      Max |      Min |      Avg |    Costs |  Commiss |     Swap |      Fee |   Spread |
    Reporter        |        2 |      155 |       35 |      120 |  +779.50 |  +145.00 |   -22.80 |     5.03 |     0.00 |     0.00 |     0.00 |     0.00 |    15.38 |
    Reporter        
    Reporter        Report by symbols for the period "2 years" from 2022.07.23 00:00
    Reporter        |   Symbol |   Trades |     Long |    Short |   Profit |      Max |      Min |      Avg |    Costs |  Commiss |     Swap |      Fee |   Spread |
    Reporter        |   EURUSD |      138 |       30 |      108 |  +612.40 |   +36.70 |   -22.80 |     4.43 |     0.00 |     0.00 |     0.00 |     0.00 |     6.90 |
    Reporter        |   GBPUSD |       17 |        5 |       12 |  +167.10 |  +145.00 |    -7.20 |     9.83 |     0.00 |     0.00 |     0.00 |     0.00 |     8.48 |
    Reporter        
    Reporter        Report by magics for the period "2 years" from 2022.07.23 00:00
    Reporter        |    Magic |   Trades |     Long |    Short |   Profit |      Max |      Min |      Avg |    Costs |  Commiss |     Swap |      Fee |   Spread |
    Reporter        |        0 |      131 |       31 |      100 |  +569.10 |   +36.70 |    -8.50 |     4.34 |     0.00 |     0.00 |     0.00 |     0.00 |     8.18 |
    Reporter        |        1 |        2 |        0 |        2 |    +2.80 |    +1.80 |    +1.00 |     1.40 |     0.00 |     0.00 |     0.00 |     0.00 |     1.80 |
    Reporter        |      123 |        2 |        0 |        2 |    +0.80 |    +0.40 |    +0.40 |     0.40 |     0.00 |     0.00 |     0.00 |     0.00 |     0.10 |
    Reporter        |     1024 |        2 |        1 |        1 |    +0.10 |    +0.10 |    +0.00 |     0.05 |     0.00 |     0.00 |     0.00 |     0.00 |     0.00 |
    Reporter        |   140578 |        1 |        0 |        1 |  +145.00 |  +145.00 |  +145.00 |   145.00 |     0.00 |     0.00 |     0.00 |     0.00 |     4.00 |
    Reporter        |  1114235 |        1 |        0 |        1 |    +2.30 |    +2.30 |    +2.30 |     2.30 |     0.00 |     0.00 |     0.00 |     0.00 |     0.10 |
    Reporter        |  1769595 |        1 |        0 |        1 |   +15.00 |   +15.00 |   +15.00 |    15.00 |     0.00 |     0.00 |     0.00 |     0.00 |     0.10 |
    Reporter        |  1835131 |        1 |        0 |        1 |    +3.60 |    +3.60 |    +3.60 |     3.60 |     0.00 |     0.00 |     0.00 |     0.00 |     0.10 |
    Reporter        |  2031739 |        1 |        0 |        1 |   +15.00 |   +15.00 |   +15.00 |    15.00 |     0.00 |     0.00 |     0.00 |     0.00 |     0.10 |
    Reporter        |  2293883 |        1 |        0 |        1 |    +1.40 |    +1.40 |    +1.40 |     1.40 |     0.00 |     0.00 |     0.00 |     0.00 |     0.10 |
    Reporter        |  2949243 |        1 |        0 |        1 |   -15.00 |   -15.00 |   -15.00 |   -15.00 |     0.00 |     0.00 |     0.00 |     0.00 |     0.00 |
    Reporter        | 10879099 |        1 |        1 |        0 |    +0.40 |    +0.40 |    +0.40 |     0.40 |     0.00 |     0.00 |     0.00 |     0.00 |     0.10 |
    Reporter        | 12517499 |        1 |        1 |        0 |   +15.00 |   +15.00 |   +15.00 |    15.00 |     0.00 |     0.00 |     0.00 |     0.00 |     0.00 |
    Reporter        | 12976251 |        1 |        0 |        1 |    +2.90 |    +2.90 |    +2.90 |     2.90 |     0.00 |     0.00 |     0.00 |     0.00 |     0.10 |
    Reporter        | 13566075 |        1 |        0 |        1 |   +15.00 |   +15.00 |   +15.00 |    15.00 |     0.00 |     0.00 |     0.00 |     0.00 |     0.10 |
    Reporter        | 13959291 |        1 |        0 |        1 |   +15.10 |   +15.10 |   +15.10 |    15.10 |     0.00 |     0.00 |     0.00 |     0.00 |     0.10 |
    Reporter        | 15728763 |        1 |        0 |        1 |   +11.70 |   +11.70 |   +11.70 |    11.70 |     0.00 |     0.00 |     0.00 |     0.00 |     0.10 |
    Reporter        | 16121979 |        1 |        0 |        1 |   +15.00 |   +15.00 |   +15.00 |    15.00 |     0.00 |     0.00 |     0.00 |     0.00 |     0.10 |
    Reporter        | 16318587 |        1 |        0 |        1 |   -15.00 |   -15.00 |   -15.00 |   -15.00 |     0.00 |     0.00 |     0.00 |     0.00 |     0.00 |
    Reporter        | 16580731 |        1 |        0 |        1 |    +2.10 |    +2.10 |    +2.10 |     2.10 |     0.00 |     0.00 |     0.00 |     0.00 |     0.10 |
    Reporter        | 21299323 |        1 |        0 |        1 |   -22.80 |   -22.80 |   -22.80 |   -22.80 |     0.00 |     0.00 |     0.00 |     0.00 |     0.00 |
    Reporter        | 27394171 |        1 |        1 |        0 |    +0.00 |    +0.00 |    +0.00 |     0.00 |     0.00 |     0.00 |     0.00 |     0.00 |     0.10 |
    Reporter        
    Reporter        Beginning of sending 31 notifications to MQID
    Reporter        10 out of 31 messages sent.
    Reporter        No more than 10 messages per minute! Message limit has been reached. Wait 55 seconds until a minute is up.
    Reporter        20 out of 31 messages sent.
    Reporter        No more than 10 messages per minute! Message limit has been reached. Wait 55 seconds until a minute is up.
    Reporter        30 out of 31 messages sent.
    Reporter        No more than 10 messages per minute! Message limit has been reached. Wait 55 seconds until a minute is up.
    Reporter        Sending 31 notifications completed
    
    

    操作ログにレポートを送信した後、サービスはスマートフォンにレポートの送信を開始します。31件のメッセージが4つのバッチで送信されました(1分あたり10件のメッセージ)。

    前日およびレポート受信日の7日前までに取引がなかったため、サービスは適切なメッセージを提供します

    設定で銘柄とマジックナンバーによるレポートを無効にし、手数料とスプレッドを無効にし、指定された日数のレポートを禁止し、現在の週、月、年の日次レポートを許可すると、


    統計は次のようになります。

    Reporter        -Service notifications OK
    Reporter        68008618: Artem (MetaQuotes Ltd., Demo, 10779.50 USD, Hedging)
    Reporter        Beginning to create a list of closed positions...
    Reporter        A list of 155 positions was created in 8515 ms
    Reporter        "Daily" no trades
    Reporter        "Weekly" no trades
    Reporter        Report for the period "Month-to-date" from 2024.07.01 00:00
    Reporter        |  Symbols |   Trades |     Long |    Short |   Profit |      Max |      Min |      Avg |    Costs |          
    Reporter        |        2 |       22 |        3 |       19 |   +46.00 |    +5.80 |    -0.30 |     2.09 |     0.00 |          
    Reporter        
    Reporter        Report for the period "Year-to-date" from 2024.01.01 00:00
    Reporter        |  Symbols |   Trades |     Long |    Short |   Profit |      Max |      Min |      Avg |    Costs |          
    Reporter        |        2 |      107 |       31 |       76 |  +264.00 |   +36.70 |    -7.20 |     2.47 |     0.00 |          
    Reporter        
    Reporter        Report for the period "Entire period" from 1970.01.01 00:00
    Reporter        |  Symbols |   Trades |     Long |    Short |   Profit |      Max |      Min |      Avg |    Costs |          
    Reporter        |        2 |      155 |       35 |      120 |  +779.50 |  +145.00 |   -22.80 |     5.03 |     0.00 |          
    Reporter        
    Reporter        Beginning of sending 3 notifications to MQID
    Reporter        Sending 3 notifications completed
    
    

    上記のレポートテーブルはすべて、端末の[エキスパート]タブに表示されます。

    レポートは、若干異なる形式でスマートフォンに送信されます。


    ここでは、レポート行のスペースを節約するために、ゼロ手数料値はレポートに表示されません(設定で許可されているかどうかに関係なく)。レポート行の長さは255文字を超えることはできません。


    結論

    サービスアプリの開発を例に、さまざまなデータを保存し、さまざまな基準に従ってデータのリストを取得する可能性について検討しました。この概念により、オブジェクトのリストに異なるデータセットを作成し、指定されたプロパティに基づいて必要なオブジェクトへのポインタを取得し、さらに必要なプロパティで並び替えられたオブジェクトのリストを作成することが可能です。これにより、データをデータベース形式で保存し、必要な情報を取得することができます。受け取った情報は、例えば取引レポートの形式で提示し、操作ログに渡したり、MetaQuotes IDを通じてユーザーのスマートフォンに通知として送信したりすることができます。

    今日紹介したプログラムをさらに改良することで、レポートを拡張し、表、グラフ、図の形式で、ユーザーが要求する形式に合わせて別のチャートに表示することも可能です。これらすべてはMQL5言語によって実現できます。

    すべてのプロジェクトファイルとアーカイブをMQL5端末のフォルダに解凍し、Reporter.mq5ファイルを事前にコンパイルすることで、プログラムをすぐに使用することができます。


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

    添付されたファイル |
    Deal.mqh (49.73 KB)
    Position.mqh (67.53 KB)
    Select.mqh (43.22 KB)
    Account.mqh (32.94 KB)
    Accounts.mqh (15.89 KB)
    Reporter.mq5 (101.21 KB)
    MQL5.zip (36.5 KB)
    初級から中級へ:変数(II) 初級から中級へ:変数(II)
    今日は、static変数の取り扱いについて学びます。このメカニズムを使用する際に守らなければならないいくつかの推奨事項があるため、この問題は初心者やある程度の経験を持つプログラマーにとってしばしば混乱を招きます。ここで提示される資料は教育目的のみに使用されます。いかなる状況においても、提示された概念を学習し習得する以外の目的でアプリケーションを閲覧することは避けてください。
    取引におけるニューラルネットワーク:Adam-mini最適化によるメモリ消費量の削減 取引におけるニューラルネットワーク:Adam-mini最適化によるメモリ消費量の削減
    モデルの訓練と収束プロセスの効率を向上させるためのアプローチの1つが、最適化手法の改良です。Adam-miniは、従来のAdamアルゴリズムを改良し、より効率的な適応型最適化を実現することを目的とした手法です。
    多通貨エキスパートアドバイザーの開発(第16回):異なるクォート履歴がテスト結果に与える影響 多通貨エキスパートアドバイザーの開発(第16回):異なるクォート履歴がテスト結果に与える影響
    開発中のエキスパートアドバイザー(EA)は、さまざまなブローカーとの取引で良好な結果を示すことが期待されていますが、現時点では、MetaQuotesデモ口座からのクォートを使用してテストを実行しています。テストや最適化に使用したクォートとは異なる価格データを持つ取引口座でも、EAが正しく機能する準備が整っているのかを確認してみましょう。
    初級から中級へ:変数(I) 初級から中級へ:変数(I)
    多くの初心者プログラマーは、自分のコードが期待どおりに動作しない理由を理解するのに苦労します。コードを正しく機能させるためには、さまざまな要素が関わります。ただ関数や操作を組み合わせるだけでは、コードが適切に動作するとは限りません。今日は、単にコードをコピー&ペーストするのではなく、実際に正しくコードを書く方法を学んでみましょう。ここで提供される資料は教育目的のみに使用されるべきです。いかなる状況においても、提示された概念を学習し習得する以外の目的でアプリケーションを閲覧することは避けてください。