
プッシュ通知による取引の監視:MetaTrader 5サービスの例
内容
- はじめに
- プロジェクトの構造
- 取引クラス
- 履歴ポジションクラス
- 取引とポジションのプロパティで検索および並べ替えをおこなうクラス
- 履歴ポジションコレクションクラス
- 口座クラス
- 口座コレクションクラス
- 取引レポートの作成と通知の送信のためのサービスアプリ
- 結論
はじめに
金融市場で取引をおこなう場合、過去の一定期間におこなわれた取引の結果に関する情報が利用可能かどうかが重要な要素となります。
おそらく、すべてのトレーダーは、取引結果に基づいて戦略を調整するために、過去1日、1週間、1か月などの取引結果を監視する必要に少なくとも一度は直面したことがあるでしょう。MetaTrader5クライアント端末は、レポートの形で優れた統計情報を提供するため、取引結果を便利な視覚形式で評価できるようになります。このレポートは、ポートフォリオを最適化するだけでなく、リスクを軽減して取引の安定性を高める方法を理解するのに役立ちます。
戦略を分析するには、取引履歴のコンテキストメニューで[レポート]をクリックするか、[表示]メニューで[レポート]をクリックします(または、Alt+Eを押します)。
![]() | ![]() |
詳細は「新しいMetaTraderレポート:5つの主要取引指標」で参照してください。
何らかの理由でクライアント端末から提供される標準レポートが十分でない場合、MQL5言語では、レポートを生成してトレーダーのスマートフォンに送信するプログラムなど、独自のプログラムを作成する十分な機会が提供されます。これが今日議論する可能性です。
私たちのプログラムは、端末の起動時に開始し、取引口座の変更、日中の開始、レポートの作成と送信の時間を追跡する必要があります。このような目的には、サービスプログラムタイプが適しています。
MQL5リファレンスによれば、サービスはインジケーター、EA、スクリプトとは異なり、動作するためにチャートに接続する必要がないプログラムです。スクリプトと同様、サービスはトリガー以外のイベントを処理しません。サービスを起動するには、そのコードにOnStartハンドラ関数を含める必要があります。サービスはStart以外のイベントを受け入れませんが、EventChartCustomを使用してカスタムイベントをチャートへ送信できます。サービスは<端末ディレクトリ>\MQL5\Servicesに保存されます。
端末で実行されている各サービスは、独自のフローで動作します。つまり、ループされたサービスは他のプログラムの動作に影響を与えることはできません。私たちのサービスは、無限ループで動作し、指定された時間をチェックし、取引履歴全体を読み取り、クローズ済みポジションのリストを作成し、これらのリストをさまざまな基準で並べ替え、操作ログとプッシュ通知にレポートを表示し、ユーザーのスマートフォンに通知をプッシュします。また、サービスを初めて起動したときや設定を変更したときは、端末からプッシュ通知を送信できるかどうかをサービスで確認する必要があります。これを実現するには、ユーザーの応答と反応を待つメッセージウィンドウを介してユーザーとの対話を設定する必要があります。また、プッシュ通知を送信する場合、単位時間あたりの通知頻度に制限があるため、通知の送信に遅延を設定する必要があります。これらはすべて、クライアント端末で実行されている他のアプリケーションの動作に決して影響を与えません。上記のすべてに基づくと、サービスはこのようなプロジェクトを作成するための最も便利なツールです。
ここで、すべてを組み立てるために必要なコンポーネントのアイデアを形成する必要があります。
プロジェクトの構造
プログラムとそのコンポーネントを最初から最後まで見てみましょう。
- サービスアプリ:サービスの継続的な運用期間全体にわたってアクティブであったすべての口座のデータにアクセスできます。口座データからクローズ済みポジションのリストを受け取り、それらを1つの一般的なリストに結合します。設定に応じて、サービスは現在アクティブな口座からのみクローズ済みポジションのデータを使用するか、クライアント端末の現在の口座と以前に使用された各口座からクローズ済みポジションのデータを使用することができます。
取引統計は、口座リストから取得したクローズドポジションのデータに基づいて、必要な取引期間に対して作成されます。その後、プッシュ通知としてユーザーのスマートフォンに送信されます。さらに、取引統計はエキスパート端末ログに表形式で表示されます。 - 口座コレクション:コレクションには、サービスの継続的な運用中に端末が接続されていた口座のリストが含まれます。口座コレクションを使用すると、リスト内の任意の口座と、すべての口座のすべてのクローズ済みポジションにアクセスできます。リストはサービスアプリで利用でき、サービスは選択をおこない、それに基づいて統計を作成します。
- 口座オブジェクトクラス:サービスの継続的な運用中にこの口座で取引が実行されたすべてのクローズ済みポジションのリスト(コレクション)を含む、1つの口座のデータを保存します。口座のプロパティへのアクセス、この口座のクローズ済みポジションのリストの作成と更新を提供し、さまざまな選択基準によってクローズ済みポジションのリストを返します。
- 履歴ポジションコレクションクラス:ポジションオブジェクトのリストが含まれており、クローズ済みポジションのプロパティへのアクセス、ポジションのリストの作成と更新を提供します。クローズ済みポジションのリストを返します。
- ポジションオブジェクトクラス:クローズ済みポジションのプロパティを保存し、アクセスを提供します。さまざまなプロパティで2つのオブジェクトを比較する機能が含まれており、さまざまな選択基準によるポジションのリストを作成できます。このポジションの取引のリストが含まれており、それらにアクセスできます。
- 取引オブジェクトクラス:単一の取引のプロパティを保存し、アクセスを提供します。このオブジェクトには、さまざまなプロパティで2つのオブジェクトを比較する機能が含まれており、さまざまな選択基準で取引のリストを作成できます。
「取引履歴を気にせずにチャート上で直接取引を表示する方法」稿では、過去の取引リストからクローズ済みポジションを回復するという概念について説明しました。取引リストでは、取引プロパティで設定されたポジションID(PositionID)によって、各取引が特定のポジションに所属しているかどうかを判断できます。ポジションオブジェクトが作成され、見つかった取引がリストに配置されます。ここでも同じ方法を実行します。しかし、取引オブジェクトとポジションオブジェクトの構築を調整するために、各オブジェクトがプロパティを設定および取得するための同一のアクセス方法を持つ、完全に異なる、長年テストされた概念を使用します。この概念により、単一の共通キーでオブジェクトを作成し、リストに保存し、任意のオブジェクトプロパティでフィルタリングおよび並べ替え、指定されたプロパティのコンテキストで新しいリストを取得できるようになります。
このプロジェクトでクラスを構築する概念を正しく理解するには、次の記事をご覧ください。
- オブジェクトプロパティの構造「(第1回):概念、データ管理および最初の結果」
- オブジェクトリストの構造「(第2回):過去の注文と取引のコレクション」
- リスト内のオブジェクトをプロパティでフィルタリングする方法「(第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つの方法が使用されます。
- ポジション利益がゼロでない場合、スプレッドのコストは、スプレッドサイズ(ポイント)×ポジション利益(金額)/ポジション利益(ポイント)の割合で計算されます。
- ポジション利益がゼロの場合、スプレッド値は「計算されたティック値*ポイントでのスプレッドサイズ*取引量」を使用して計算されます。
以下は、すべての取引の合計手数料を設定するメソッドです。
//+------------------------------------------------------------------+ //| 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





- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索