English Deutsch
preview
MQL5取引ツールキット(第4回):履歴管理EX5ライブラリの開発

MQL5取引ツールキット(第4回):履歴管理EX5ライブラリの開発

MetaTrader 5 | 11 4月 2025, 08:29
125 0
Wanateki Solutions LTD
Kelvin Muturi Muigua

はじめに

この魅力的な連載では、2つの包括的なEX5ライブラリを開発しました。ポジションを処理および管理するPositionsManager.ex5と、予約注文を処理するPendingOrdersManager.ex5です。これらに加えて、これらのライブラリの実装を効果的に実証するために、グラフィカルユーザーインターフェイスを備えた実用的な例も作成しました。

この記事では、完了した注文、取引、ポジション取引の履歴を取得して処理するために設計された、もう1つの重要なEX5ライブラリを紹介します。さらに、さまざまな柔軟な基準に基づいて取引システム、エキスパートアドバイザー(EA)、または特定の銘柄のパフォーマンスを評価する取引レポートを生成する分析モジュールを開発します。

この記事は、ポジション、注文、取引履歴の操作が難しいと感じる初心者のMQL5開発者向けの実用的なガイドとして役立ちます。また、取引履歴の処理を効率化し、作業を強化するライブラリを探しているMQL5プログラマーにとっても貴重なリソースとなるでしょう。

まず、MQL5プログラマー、特にMetaTrader 5で取引履歴を処理するのが初めてのプログラマーにとって、理解が難しいと感じることが多い重要な質問をいくつか取り上げます。


MQL5における取引トランザクションのライフサイクルとは

MQL5では、取引トランザクションのライフサイクルは注文の実行から始まります。注文は、直接成行注文予約注文の2つの主なタイプに分類できます。

直接成行注文のエントリー

直接成行注文は、現在の市場価格(AskまたはBid)で資産を購入または売却するためのリアルタイムのリクエストです。これらの注文を処理する方法については、ライブラリの開発中に、第1回第2回の記事で詳しく説明しました。

直接成行エントリー注文

直接成行注文は即座に実行されるため、手動取引にも自動取引戦略にも最適です。実行された注文はアクティブなポジションに移行し、一意のチケット番号と別のポジション識別子(POSITION_ID)が割り当てられます。これにより、ポジションのライフサイクルを通じて、さまざまな段階の追跡と管理がより信頼性高くおこなうことができます。

予約注文エントリー

一方、予約注文(BUYSTOPBUYLIMITSELLSTOPSELLLIMITBUYSTOPLIMITSELLSTOPLIMIT)は、指定された価格レベルに達したときにトリガーされる遅延注文です。これらの種類の注文の処理に関する詳細なガイドは、本連載第3回でPending Orders Managerライブラリを開発したときに掲載しています。

予約注文エントリー

市場価格が事前に定義された予約注文のトリガー価格と一致するまで、予約注文は非アクティブのままになります。トリガーされると、成行注文に変換されて実行され、直接成行注文と同様、一意のチケット番号ポジション識別子(POSITION_ID)を受け取ります。


ポジションのライフサイクル中にステータスがどのように変化するか

ポジションのステータスは、そのライフサイクル中にさまざまな要因によって変化する可能性があります。

  • 部分決済:ポジションの一部が決済されると、対応するエグジット(アウト)取引が取引履歴に記録されます。
  • ポジションの逆転:「closeby」取引などのポジションの反転もエグジット取引として記録されます。
  • 完全決済:ポジション全体がテイクプロフィットストップロス、またはマージンコールによるストップアウトイベントを通じて手動または自動で決済されると、最終的な決済取引が取引履歴に記録されます。

MQL5における取引操作のライフサイクルを理解することは非常に重要です。すべての取引は取引サーバーに送られる注文から始まります。これは、予約注文を出すリクエストであっても、成行注文の売買実行であっても、既存ポジションの部分決済リクエストであっても同じです。その種類に関係なく、すべての取引操作はまず注文として記録されます。

正常に実行された注文は次の段階に移行し、取引として履歴データベースに保存されます。注文や取引に利用可能なさまざまなプロパティや関数を使用して、各取引がどの注文から発生したかを追跡し、それを対応するポジションにリンクすることができます。このプロセスにより、取引のライフサイクルに関する明確で体系的な経路が作成されます。

この「パンくずリスト」アプローチにより、MQL5環境内でのすべての取引またはトランザクションの起源、進行状況、および結果を追跡できます。それにより、取引を開始した注文、実行時刻、途中で加えられた変更、最終的な取引結果(ポジション)など、詳細な監査履歴を提供します。この追跡は、透明性を高めるだけでなく、MQL5プログラマーとして取引戦略を分析し、改善すべき領域を特定し、パフォーマンスを最適化するアルゴリズムを開発する力を提供します。


MQL5におけるポジションと取引の違いは何でしょうか。

MQL5では、ポジションは現在市場で保持しているアクティブな進行中取引(ポジション)を表します。特定の銘柄買いまたは売りのポジションを反映するオープンな状態です。一方、取引は、ポジションが完全決済された、完了済みの取引を指します。 アクティブなポジションと予約注文は、MetaTrader 5のツールボックスウィンドウの[取引]タブに表示されます。

MetaTrader 5ツールボックス取引タブ

クローズ済みのポジション取引)は、注文および取引とともに、ツールボックスウィンドウの[履歴]タブに表示されます。

MetaTrader 5ツールボックスの履歴タブ

ポジションの完全な履歴にアクセスするには、プラットフォームのメニューオプションを使用して、[ポジション]メニュー項目を選択します。同じメニューオプションを使用して、注文と取引の履歴にもアクセスできます。

MetaTrader 5ツールボックス履歴タブ-ポジション履歴の選択

特にプラットフォームの標準履歴機能を使用する場合、ポジションと取引の区別は初心者のMQL5プログラマーにとって紛らわしいものです。この記事と、これから作成するライブラリの詳細なコードを読めば、MQL5でポジションと取引がどのように分類され、追跡されるかを明確に理解できるようになります。もしお急ぎですぐに使える履歴ライブラリが必要な場合は、次の記事の包括的なドキュメントに従って、プロジェクトに直接実装できます。


履歴マネージャライブラリのソースコードファイル(.mq5)を作成する

開始するには、MetaEditor IDEを開き、メニューから[新規作成]を選択してMQLウィザードにアクセスします。ウィザードで、新しいライブラリソースファイルを作成することを選択し、HistoryManager.mq5という名前を付けます。このファイルは、口座の取引履歴の管理と分析に特化したコア関数の基盤となります。新しいHistoryManager.mq5ライブラリを作成するときは、最初の記事で作成したLibraries\Toolkitフォルダに保存します。この新しいファイルをPositionsManagerおよびPendingOrdersManager EX5ライブラリと同じディレクトリに保存することで、プロジェクトの明確で一貫した組織構造を維持できます。このアプローチにより、ツールキットが拡張されるにつれて、各コンポーネントの検索と管理が容易になります。

MetaEditorナビゲータライブラリツールキットディレクトリ

新しく作成されたHistoryManager.mq5ソースファイルは次のようになります。まず、#propertyディレクティブの下にある「My function」コメントを削除します。ファイル内のcopyrightおよびlinkプロパティのディレクティブは異なる場合がありますが、コードの動作やパフォーマンスには影響しません。copyrigihtおよびlinkのディレクティブは任意の情報でカスタマイズできますが、libraryプロパティのディレクティブは変更しないでください。

//+------------------------------------------------------------------+
//|                                               HistoryManager.mq5 |
//|                          Copyright 2024, Wanateki Solutions Ltd. |
//|                                         https://www.wanateki.com |
//+------------------------------------------------------------------+
#property library
#property copyright "Copyright 2024, Wanateki Solutions Ltd."
#property link      "https://www.wanateki.com"
#property version   "1.00"
//+------------------------------------------------------------------+
//| My function                                                      |
//+------------------------------------------------------------------+
// int MyCalculator(int value,int value2) export
//   {
//    return(value+value2);
//   }
//+------------------------------------------------------------------+


データ構造体、プリプロセッサディレクティブ、グローバル変数

新しく作成したHistoryManager.mq5ライブラリソースファイルで、まず次のコンポーネントを定義します。

  • プリプロセッサディレクティブ:さまざまな種類の取引履歴の並べ替えや照会に役立ちます。
  • データ構造体:注文、取引、ポジション、予約注文の履歴データが保存されます。
  • グローバル動的構造体配列:ライブラリ内のすべての関連する履歴データが保持されます。

これらのコンポーネントをグローバルスコープで定義すると、ライブラリ全体でアクセスできるようになり、ライブラリ内のすべての異なるモジュールまたは関数で利用できるようになります。

プリプロセッサディレクティブ

履歴管理ライブラリはさまざまな種類のリクエストを処理するため、各リクエストに必要な特定の履歴データのみを取得するように設計することが重要です。このモジュール式でターゲットを絞ったアプローチにより、さまざまなユースケースに対する柔軟性を維持しながら、ライブラリのパフォーマンスが向上します。

これを実現するために、特定の種類の履歴データの識別子として機能する整数定数を定義します。これらの定数により、ライブラリは必要なデータのみをターゲットにすることができるため、リソースの消費が最小限に抑えられ、処理が高速化されます。

履歴データを5つの主要カテゴリに分類します。

  1. 注文履歴
  2. 取引履歴
  3. ポジション履歴
  4. 予約注文履歴
  5. すべての履歴データ

これらの定数を使用することで、ライブラリ内の関数は処理する履歴の種類を指定できます。メインの履歴取得関数は、要求されたデータのみを照会して返すため、時間と計算リソースが節約されます。まず、これらの整数定数をコード内の最後の#propertyディレクティブのすぐ下に配置して定義します。

#define GET_ORDERS_HISTORY_DATA 1001
#define GET_DEALS_HISTORY_DATA 1002
#define GET_POSITIONS_HISTORY_DATA 1003
#define GET_PENDING_ORDERS_HISTORY_DATA 1004
#define GET_ALL_HISTORY_DATA 1005

データ構造体

私たちのEX5ライブラリは、グローバルに宣言されたデータ構造にさまざまな履歴データを保存します。これらの構造体は、照会された際に、取引、注文、ポジション、予約注文の履歴を効率的に保持します。

//- Data structure to store deal properties
struct DealData
  {
   ulong             ticket;
   ulong             magic;
   ENUM_DEAL_ENTRY   entry;
   ENUM_DEAL_TYPE    type;
   ENUM_DEAL_REASON  reason;
   ulong             positionId;
   ulong             order;
   string            symbol;
   string            comment;
   double            volume;
   double            price;
   datetime          time;
   double            tpPrice;
   double            slPrice;
   double            commission;
   double            swap;
   double            profit;
  };

//- Data structure to store order properties
struct OrderData
  {
   datetime                timeSetup;
   datetime                timeDone;
   datetime                expirationTime;
   ulong                   ticket;
   ulong                   magic;
   ENUM_ORDER_REASON       reason;
   ENUM_ORDER_TYPE         type;
   ENUM_ORDER_TYPE_FILLING typeFilling;
   ENUM_ORDER_STATE        state;
   ENUM_ORDER_TYPE_TIME    typeTime;
   ulong                   positionId;
   ulong                   positionById;
   string                  symbol;
   string                  comment;
   double                  volumeInitial;
   double                  priceOpen;
   double                  priceStopLimit;
   double                  tpPrice;
   double                  slPrice;
  };

//- Data structure to store closed position/trade properties
struct PositionData
  {
   ENUM_POSITION_TYPE type;
   ulong              ticket;
   ENUM_ORDER_TYPE    initiatingOrderType;
   ulong              positionId;
   bool               initiatedByPendingOrder;
   ulong              openingOrderTicket;
   ulong              openingDealTicket;
   ulong              closingDealTicket;
   string             symbol;
   double             volume;
   double             openPrice;
   double             closePrice;
   datetime           openTime;
   datetime           closeTime;
   long               duration;
   double             commission;
   double             swap;
   double             profit;
   double             tpPrice;
   double             slPrice;
   int                tpPips;
   int                slPips;
   int                pipProfit;
   double             netProfit;
   ulong              magic;
   string             comment;
  };

//- Data structure to store executed or canceled pending order properties
struct PendingOrderData
  {
   string                  symbol;
   ENUM_ORDER_TYPE         type;
   ENUM_ORDER_STATE        state;
   double                  priceOpen;
   double                  tpPrice;
   double                  slPrice;
   int                     tpPips;
   int                     slPips;
   ulong                   positionId;
   ulong                   ticket;
   datetime                timeSetup;
   datetime                expirationTime;
   datetime                timeDone;
   ENUM_ORDER_TYPE_TIME    typeTime;
   ulong                   magic;
   ENUM_ORDER_REASON       reason;
   ENUM_ORDER_TYPE_FILLING typeFilling;
   string                  comment;
   double                  volumeInitial;
   double                  priceStopLimit;
  };

グローバル動的構造体配列

グローバルスコープ内の最後の宣言は、前に定義した構造体の動的データ構造体配列で構成されます。これらの配列は、ライブラリによって管理されるすべてのコアデータのプライマリストレージとして機能します。

OrderData orderInfo[];
DealData dealInfo[];
PositionData positionInfo[];
PendingOrderData pendingOrderInfo[];


GetHistoryData関数

GetHistoryData関数は、EX5ライブラリの中核として機能し、その機能のバックボーンを形成します。ライブラリ内の他のほとんどの関数は、指定された期間と履歴タイプに基づいて取引履歴を取得するためにこの関数に依存します。この関数は内部使用のみを目的としているため、エクスポート可能として定義されません。

この関数は、指定された期間と履歴タイプに対して要求された履歴データを取得するように設計されています。これはbool型関数であり、履歴が正常に取得された場合はtrueを返し、操作が失敗した場合はfalseを返します。

GetHistoryData関数は3つの入力パラメータを受け入れます。

  1. 目的の期間の開始と終了を指定する2つのdatetime変数、fromDateTimetoDateTime
  2. ファイルの先頭にある定義済み定数の1つに対応するunsigned整数型のdataToGet

これらの入力を組み合わせることで、関数は必要な履歴データを効率的に照会および処理できます。まず関数を定義しましょう。

bool GetHistoryData(datetime fromDateTime, datetime toDateTime, uint dataToGet)
  {
   return(true);

//-- Our function's code will go here

  }

この関数の最初のタスクは、指定された日付範囲が有効かどうかを確認することです。MQL5のdatetimeデータ型は、本質的にはUnixエポック形式(つまり、1970年1月1日00:00:00UTCからの経過秒数)で時間を表すlong整数型であるため、これらの値を直接比較して正確性を確認できます。また、MQL5で履歴データを要求する場合、時間はローカルマシンの時間ではなく、取引サーバーの時間に基づいていることに注意してください。

日付範囲を検証するには、fromDateTime値がtoDateTime値より小さいことを確認します。開始日を終了日以降にすることはできないため、fromDateTimetoDateTime以上の場合は期間が無効です。指定された期間が検証に失敗した場合は、falseを返して関数を終了します。

if(fromDateTime >= toDateTime)
     {
      //- Invalid time period selected
      Print("Invalid time period provided. Can't load history!");
      return(false);
     }

日付と期間が確認されると、問題が発生した場合に正確なエラーコードを確保するために、MQL5のエラーキャッシュがリセットされます。次に、if-else文内でHistorySelect関数を呼び出し、検証されたdatetime値を渡して、指定された期間の取引注文の履歴を取得します。HistorySelectブール値を返すため、処理する履歴が正常に見つかった場合はtrueを返し、エラーが発生した場合やデータの取得に失敗した場合はfalseを返します。

ResetLastError();
if(HistorySelect(fromDateTime, toDateTime)) //- History selected ok
  {
//-- Code to process the history data will go here
  }
else //- History selecting failed
  {
   Print("Selecting the history failed. Error code = ", GetLastError());
   return(false);
  }

if-else文のelse部分内に、関数を終了してブールfalseを返す前に、履歴選択に失敗したことを示すメッセージとエラーコードをログに記録するコードを追加しました。if部分では、switch文を使用して、dataToGetの値に基づいて、読み込まれた取引履歴データを処理するための適切な関数を呼び出します。

switch(dataToGet)
  {
   case GET_DEALS_HISTORY_DATA: //- Get and save only the deals history data
      SaveDealsData();
      break;

   case GET_ORDERS_HISTORY_DATA: //- Get and save only the orders history data
      SaveOrdersData();
      break;

   case GET_POSITIONS_HISTORY_DATA: //- Get and save only the positions history data
      SaveDealsData();  //- Needed to generate the positions history data
      SaveOrdersData(); //- Needed to generate the positions history data
      SavePositionsData();
      break;

   case GET_PENDING_ORDERS_HISTORY_DATA: //- Get and save only the pending orders history data
      SaveOrdersData(); //- Needed to generate the pending orders history data
      SavePendingOrdersData();
      break;

   case GET_ALL_HISTORY_DATA: //- Get and save all the history data
      SaveDealsData();
      SaveOrdersData();
      SavePositionsData();
      SavePendingOrdersData();
      break;

   default: //-- Unknown entry
      Print("-----------------------------------------------------------------------------------------");
      Print(__FUNCTION__, ": Can't fetch the historical data you need.");
      Print("*** Please specify the historical data you need in the (dataToGet) parameter.");
      break;
  }

すべてのコードセグメントが含まれた完全なGetHistoryData関数を次に示します。

bool GetHistoryData(datetime fromDateTime, datetime toDateTime, uint dataToGet)
  {
//- Check if the provided period of dates are valid
   if(fromDateTime >= toDateTime)
     {
      //- Invalid time period selected
      Print("Invalid time period provided. Can't load history!");
      return(false);
     }

//- Reset last error and get the history
   ResetLastError();
   if(HistorySelect(fromDateTime, toDateTime)) //- History selected ok
     {
      //- Get the history data
      switch(dataToGet)
        {
         case GET_DEALS_HISTORY_DATA: //- Get and save only the deals history data
            SaveDealsData();
            break;

         case GET_ORDERS_HISTORY_DATA: //- Get and save only the orders history data
            SaveOrdersData();
            break;

         case GET_POSITIONS_HISTORY_DATA: //- Get and save only the positions history data
            SaveDealsData();  //- Needed to generate the positions history data
            SaveOrdersData(); //- Needed to generate the positions history data
            SavePositionsData();
            break;

         case GET_PENDING_ORDERS_HISTORY_DATA: //- Get and save only the pending orders history data
            SaveOrdersData(); //- Needed to generate the pending orders history data
            SavePendingOrdersData();
            break;

         case GET_ALL_HISTORY_DATA: //- Get and save all the history data
            SaveDealsData();
            SaveOrdersData();
            SavePositionsData();
            SavePendingOrdersData();
            break;

         default: //-- Unknown entry
            Print("-----------------------------------------------------------------------------------------");
            Print(__FUNCTION__, ": Can't fetch the historical data you need.");
            Print("*** Please specify the historical data you need in the (dataToGet) parameter.");
            break;
        }
     }
   else
     {
      Print(__FUNCTION__, ": Selecting the history failed. Error code = ", GetLastError());
      return(false);
     }
   return(true);
  }

この段階でソースコードファイルを保存してコンパイルしようとすると、多数のコンパイルエラーと警告が発生します。これは、コード内で参照される関数の多くがまだ作成されていないためです。EX5ライブラリの開発はまだ初期段階であるため、不足している関数がすべて実装されると、EX5ライブラリファイルはエラーや警告なしでコンパイルされます。


SaveDealsData関数

SaveDealsData関数は、ライブラリ内のさまざまな関数によって要求されるさまざまな期間の取引履歴キャッシュで現在利用可能なすべての取引履歴を取得して保存する役割を担います。ライブラリ内で内部的に呼び出される(具体的にはGetHistoryData関数から呼び出される)ため、データは返されず、エクスポート可能として定義されません。この関数は、MQL5のHistoryDealGet...標準関数を利用して、さまざまな取引プロパティを取得し、それらをdealInfo動的データ構造配列に格納します。

まず、関数の定義またはシグネチャを作成します。

void SaveDealsData()
  {

//-- Our function's code will go here

  }

SaveDealsDataGetHistoryData関数内で呼び出されるため、取引履歴を処理する前にHistorySelectを再度呼び出す必要はありません。SaveDealsData関数の最初の手順は、処理する取引履歴があるかどうかを確認することです。これを実現するには、履歴キャッシュで利用可能な取引の合計数を返すHistoryDealsTotal関数を使用します。効率性を高めるために、合計履歴取引を格納するためにtotalDealsという整数を作成し、取引チケット識別子を格納するために、unsigned long型のdealTicketを作成します。

int totalDeals = HistoryDealsTotal();
ulong dealTicket;

利用可能な取引がない場合や見つからない場合(totalDeals<=0)、そのことを示すメッセージをログに記録し、不要な処理を避けるために関数を早期に終了します。

if(totalDeals > 0)
  {
//-- Code to process deal goes here
  }
else
  {
   Print(__FUNCTION__, ": No deals available to be processed, totalDeals = ", totalDeals);
  }

取引履歴が存在する場合、次の手順は取得したデータを格納する配列を準備することです。このタスクでは、dealInfo動的配列を使用し、ArrayResize関数を使用して、取引の合計数に合わせてサイズを変更することから始め、関連するすべての取引プロパティを格納するのに十分な容量があることを確認します。

ArrayResize(dealInfo, totalDeals);

次に、forループを使用して、最新の取引から逆順に取引を反復処理します。各取引について、HistoryDealGetTicket関数を使用して、取引に関連付けられた一意のチケットを取得します。チケットの取得が成功すると、さまざまな取引プロパティを取得して保存します。各プロパティを、現在のループ反復に対応するインデックスにあるdealInfo配列の対応するフィールドに格納します。

HistoryDealGetTicket関数が取引の有効なチケットを取得できなかった場合、デバッグの目的でエラーコードを含むエラーメッセージをログに記録します。これにより、プロパティ取得プロセス中に予期しない問題が発生した場合でも透明性が確保されます。

for(int x = totalDeals - 1; x >= 0; x--)
  {
   ResetLastError();
   dealTicket = HistoryDealGetTicket(x);
   if(dealTicket > 0)
     {
      //- Deal ticket selected ok, we can now save the deals properties
      dealInfo[x].ticket = dealTicket;
      dealInfo[x].entry = (ENUM_DEAL_ENTRY)HistoryDealGetInteger(dealTicket, DEAL_ENTRY);
      dealInfo[x].type = (ENUM_DEAL_TYPE)HistoryDealGetInteger(dealTicket, DEAL_TYPE);
      dealInfo[x].magic = HistoryDealGetInteger(dealTicket, DEAL_MAGIC);
      dealInfo[x].positionId = HistoryDealGetInteger(dealTicket, DEAL_POSITION_ID);
      dealInfo[x].order = HistoryDealGetInteger(dealTicket, DEAL_ORDER);
      dealInfo[x].symbol = HistoryDealGetString(dealTicket, DEAL_SYMBOL);
      dealInfo[x].comment = HistoryDealGetString(dealTicket, DEAL_COMMENT);
      dealInfo[x].volume = HistoryDealGetDouble(dealTicket, DEAL_VOLUME);
      dealInfo[x].price = HistoryDealGetDouble(dealTicket, DEAL_PRICE);
      dealInfo[x].time = (datetime)HistoryDealGetInteger(dealTicket, DEAL_TIME);
      dealInfo[x].tpPrice = HistoryDealGetDouble(dealTicket, DEAL_TP);
      dealInfo[x].slPrice = HistoryDealGetDouble(dealTicket, DEAL_SL);
      dealInfo[x].commission = HistoryDealGetDouble(dealTicket, DEAL_COMMISSION);
      dealInfo[x].swap = HistoryDealGetDouble(dealTicket, DEAL_SWAP);
      dealInfo[x].reason = (ENUM_DEAL_REASON)HistoryDealGetInteger(dealTicket, DEAL_REASON);
      dealInfo[x].profit = HistoryDealGetDouble(dealTicket, DEAL_PROFIT);
     }
   else
     {
      Print(
         __FUNCTION__, " HistoryDealGetTicket(", x, ") failed. (dealTicket = ", dealTicket,
         ") *** Error Code: ", GetLastError()
      );
     }
  }

以下に、すべてのコードセグメントが完全に含まれている完全なSaveDealsData関数を示します。

void SaveDealsData()
  {
//- Get the number of loaded history deals
   int totalDeals = HistoryDealsTotal();
   ulong dealTicket;
//-
//- Check if we have any deals to be worked on
   if(totalDeals > 0)
     {
      //- Resize the dynamic array that stores the deals
      ArrayResize(dealInfo, totalDeals);

      //- Let us loop through the deals and save them one by one
      for(int x = totalDeals - 1; x >= 0; x--)
        {
         ResetLastError();
         dealTicket = HistoryDealGetTicket(x);
         if(dealTicket > 0)
           {
            //- Deal ticket selected ok, we can now save the deals properties
            dealInfo[x].ticket = dealTicket;
            dealInfo[x].entry = (ENUM_DEAL_ENTRY)HistoryDealGetInteger(dealTicket, DEAL_ENTRY);
            dealInfo[x].type = (ENUM_DEAL_TYPE)HistoryDealGetInteger(dealTicket, DEAL_TYPE);
            dealInfo[x].magic = HistoryDealGetInteger(dealTicket, DEAL_MAGIC);
            dealInfo[x].positionId = HistoryDealGetInteger(dealTicket, DEAL_POSITION_ID);
            dealInfo[x].order = HistoryDealGetInteger(dealTicket, DEAL_ORDER);
            dealInfo[x].symbol = HistoryDealGetString(dealTicket, DEAL_SYMBOL);
            dealInfo[x].comment = HistoryDealGetString(dealTicket, DEAL_COMMENT);
            dealInfo[x].volume = HistoryDealGetDouble(dealTicket, DEAL_VOLUME);
            dealInfo[x].price = HistoryDealGetDouble(dealTicket, DEAL_PRICE);
            dealInfo[x].time = (datetime)HistoryDealGetInteger(dealTicket, DEAL_TIME);
            dealInfo[x].tpPrice = HistoryDealGetDouble(dealTicket, DEAL_TP);
            dealInfo[x].slPrice = HistoryDealGetDouble(dealTicket, DEAL_SL);
            dealInfo[x].commission = HistoryDealGetDouble(dealTicket, DEAL_COMMISSION);
            dealInfo[x].swap = HistoryDealGetDouble(dealTicket, DEAL_SWAP);
            dealInfo[x].reason = (ENUM_DEAL_REASON)HistoryDealGetInteger(dealTicket, DEAL_REASON);
            dealInfo[x].profit = HistoryDealGetDouble(dealTicket, DEAL_PROFIT);
           }
         else
           {
            Print(
               __FUNCTION__, " HistoryDealGetTicket(", x, ") failed. (dealTicket = ", dealTicket,
               ") *** Error Code: ", GetLastError()
            );
           }
        }
     }
   else
     {
      Print(__FUNCTION__, ": No deals available to be processed, totalDeals = ", totalDeals);
     }
  }


PrintDealsHistory関数

PrintDealsHistory関数は、指定された期間の取引履歴データを取得して表示するように設計されています。この関数は、特定の期間内の取引データの範囲を調べる必要がある場合に便利です。データは返されませんが、代わりに取引情報がMetaTrader5のログに出力され、確認されます。この関数は、GetHistoryData関数を使用して関連データを取得することにより、外部から呼び出してユーザーに過去の取引に関する洞察を提供することができます。

まず、PrintDealsHistory関数を定義します。この関数には、検索対象期間の開始時刻と終了時刻を表すfromDateTimetoDateTimeという2つのパラメータが必要です。この関数は、この時間枠内に実行された取引を取得します。この関数はexportとしてマークされていることに注意してください。つまり、この関数は他のプログラムやライブラリから呼び出すことができ、外部ですぐに使用できます。

void PrintDealsHistory(datetime fromDateTime, datetime toDateTime) export
  {
//-- Our function's code will go here
  }

次に、GetHistoryData関数を呼び出し、fromDateTimetoDateTime、および追加の定数GET_DEALS_HISTORY_DATAを渡します。これは、指定された開始時刻と終了時刻の間の関連する取引データを取得するように関数に指示します。この関数呼び出しにより、目的の期間の取引情報が取得され、dealInfo配列に格納されます。

GetHistoryData(fromDateTime, toDateTime, GET_DEALS_HISTORY_DATA);

取引データを取得したら、利用可能なデータがあるかどうかを確認する必要があります。ArraySize関数を使用して、dealInfo配列に保存されている取引の合計数を取得します。取引が見つからない場合(totalDeals<=0)、ユーザーに通知するメッセージをログに記録し、関数を終了します。表示する取引がない場合、関数は早期に終了し、時間を節約し、不要な操作を防ぎます。

int totalDeals = ArraySize(dealInfo);
if(totalDeals <= 0)
  {
   Print("");
   Print(__FUNCTION__, ": No deals history found for the specified period.");
   return; //-- Exit the function
  }

取引データが見つかった場合は、詳細を出力します。最初の手順は、見つかった取引の合計数と、取引が実行された日付の範囲を示す概要メッセージを出力することです。

Print("");
Print(__FUNCTION__, "-------------------------------------------------------------------------------");
Print(
   "Found a total of ", totalDeals,
   " deals executed between (", fromDateTime, ") and (", toDateTime, ")."
);

次に、forループを使用して、dealInfo配列内のすべての取引を反復処理します。各取引について、取引の銘柄、チケット番号、ポジションID、エントリータイプ、価格、ストップロス(SL)、テイクプロフィット(TP)レベル、スワップ、手数料、利益などの関連詳細を出力します。各取引の詳細は説明ラベルとともにきちんと出力されており、ユーザーは取引履歴を簡単に理解できます。

for(int r = 0; r < totalDeals; r++)
  {
   Print("---------------------------------------------------------------------------------------------------");
   Print("Deal #", (r + 1));
   Print("Symbol: ", dealInfo[r].symbol);
   Print("Time Executed: ", dealInfo[r].time);
   Print("Ticket: ", dealInfo[r].ticket);
   Print("Position ID: ", dealInfo[r].positionId);
   Print("Order Ticket: ", dealInfo[r].order);
   Print("Type: ", EnumToString(dealInfo[r].type));
   Print("Entry: ", EnumToString(dealInfo[r].entry));
   Print("Reason: ", EnumToString(dealInfo[r].reason));
   Print("Volume: ", dealInfo[r].volume);
   Print("Price: ", dealInfo[r].price);
   Print("SL Price: ", dealInfo[r].slPrice);
   Print("TP Price: ", dealInfo[r].tpPrice);
   Print("Swap: ", dealInfo[r].swap, " ", AccountInfoString(ACCOUNT_CURRENCY));
   Print("Commission: ", dealInfo[r].commission, " ", AccountInfoString(ACCOUNT_CURRENCY));
   Print("Profit: ", dealInfo[r].profit, " ", AccountInfoString(ACCOUNT_CURRENCY));
   Print("Comment: ", dealInfo[r].comment);
   Print("Magic: ", dealInfo[r].magic);
   Print("");
  }

すべてのコードセグメントが統合された完全なPrintDealsHistory関数を次に示します。

void PrintDealsHistory(datetime fromDateTime, datetime toDateTime) export
  {
//- Get and save the deals history for the specified period
   GetHistoryData(fromDateTime, toDateTime, GET_DEALS_HISTORY_DATA);
   int totalDeals = ArraySize(dealInfo);
   if(totalDeals <= 0)
     {
      Print("");
      Print(__FUNCTION__, ": No deals history found for the specified period.");
      return; //-- Exit the function
     }

   Print("");
   Print(__FUNCTION__, "-------------------------------------------------------------------------------");
   Print(
      "Found a total of ", totalDeals,
      " deals executed between (", fromDateTime, ") and (", toDateTime, ")."
   );

   for(int r = 0; r < totalDeals; r++)
     {
      Print("---------------------------------------------------------------------------------------------------");
      Print("Deal #", (r + 1));
      Print("Symbol: ", dealInfo[r].symbol);
      Print("Time Executed: ", dealInfo[r].time);
      Print("Ticket: ", dealInfo[r].ticket);
      Print("Position ID: ", dealInfo[r].positionId);
      Print("Order Ticket: ", dealInfo[r].order);
      Print("Type: ", EnumToString(dealInfo[r].type));
      Print("Entry: ", EnumToString(dealInfo[r].entry));
      Print("Reason: ", EnumToString(dealInfo[r].reason));
      Print("Volume: ", dealInfo[r].volume);
      Print("Price: ", dealInfo[r].price);
      Print("SL Price: ", dealInfo[r].slPrice);
      Print("TP Price: ", dealInfo[r].tpPrice);
      Print("Swap: ", dealInfo[r].swap, " ", AccountInfoString(ACCOUNT_CURRENCY));
      Print("Commission: ", dealInfo[r].commission, " ", AccountInfoString(ACCOUNT_CURRENCY));
      Print("Profit: ", dealInfo[r].profit, " ", AccountInfoString(ACCOUNT_CURRENCY));
      Print("Comment: ", dealInfo[r].comment);
      Print("Magic: ", dealInfo[r].magic);
      Print("");
     }
  }


SaveOrdersData関数

SaveOrdersData関数は、取引履歴キャッシュで利用可能な履歴注文データを取得して保存する役割を担います。この関数は注文を1つずつ処理し、MQL5のHistoryOrderGet...関数を使用して注文の主要なプロパティを抽出し、orderInfoと呼ばれる動的配列に格納します。この配列は、必要に応じてライブラリの他の部分でデータを分析および操作するために使用されます。この関数はデータを返さず、ライブラリ内で内部的に使用されるためエクスポート可能として定義されず、エラーを適切に処理し、デバッグの目的で問題をログに記録します。

まず関数シグネチャを定義しましょう。

void SaveOrdersData()
  {
//-- Our function's code will go here
  }

次に、利用可能な履歴注文の数を決定します。これは、キャッシュ内の履歴注文の合計数を返すHistoryOrdersTotal関数を使用して実現されます。結果はtotalOrdersHistoryという名前の変数に保存されます。さらに、各注文を処理するときにそのチケットを保持するために、unsigned long型のorderTicket変数を宣言します。

int totalOrdersHistory = HistoryOrdersTotal();
ulong orderTicket;

過去の注文がない場合(totalOrdersHistory<=0)、関数はこれを示すメッセージをログに記録し、不要な処理を避けるために早期に終了します。

if(totalOrdersHistory > 0)
  {
   //-- Code to process orders goes here
  }
else
  {
   Print(__FUNCTION__, ": No order history available to be processed, totalOrdersHistory = ", totalOrdersHistory);
   return;
  }

過去の注文が利用可能な場合は、取得したデータを格納するためのorderInfo配列を準備します。これは、ArrayResize関数を使用して配列のサイズを変更し、履歴注文の合計数と一致させることによっておこなわれます。

ArrayResize(orderInfo, totalOrdersHistory);

forループを使用して、注文を逆順(最新のものから開始)でループします。注文ごとに、まず、HistoryOrderGetTicket関数を使用して注文チケットを取得します。チケットの取得が成功した場合、HistoryOrderGet...関数を使用して注文のさまざまなプロパティを抽出し、orderInfo配列の対応するフィールドに保存します。チケットの取得に失敗した場合、関数はデバッグのためにエラーコードとともにエラーメッセージをログに記録します。

for(int x = totalOrdersHistory - 1; x >= 0; x--)
  {
   ResetLastError();
   orderTicket = HistoryOrderGetTicket(x);
   if(orderTicket > 0)
     {
      //- Order ticket selected ok, we can now save the order properties
      orderInfo[x].ticket = orderTicket;
      orderInfo[x].timeSetup = (datetime)HistoryOrderGetInteger(orderTicket, ORDER_TIME_SETUP);
      orderInfo[x].timeDone = (datetime)HistoryOrderGetInteger(orderTicket, ORDER_TIME_DONE);
      orderInfo[x].expirationTime = (datetime)HistoryOrderGetInteger(orderTicket, ORDER_TIME_EXPIRATION);
      orderInfo[x].typeTime = (ENUM_ORDER_TYPE_TIME)HistoryOrderGetInteger(orderTicket, ORDER_TYPE_TIME);
      orderInfo[x].magic = HistoryOrderGetInteger(orderTicket, ORDER_MAGIC);
      orderInfo[x].reason = (ENUM_ORDER_REASON)HistoryOrderGetInteger(orderTicket, ORDER_REASON);
      orderInfo[x].type = (ENUM_ORDER_TYPE)HistoryOrderGetInteger(orderTicket, ORDER_TYPE);
      orderInfo[x].state = (ENUM_ORDER_STATE)HistoryOrderGetInteger(orderTicket, ORDER_STATE);
      orderInfo[x].typeFilling = (ENUM_ORDER_TYPE_FILLING)HistoryOrderGetInteger(orderTicket, ORDER_TYPE_FILLING);
      orderInfo[x].positionId = HistoryOrderGetInteger(orderTicket, ORDER_POSITION_ID);
      orderInfo[x].positionById = HistoryOrderGetInteger(orderTicket, ORDER_POSITION_BY_ID);
      orderInfo[x].symbol = HistoryOrderGetString(orderTicket, ORDER_SYMBOL);
      orderInfo[x].comment = HistoryOrderGetString(orderTicket, ORDER_COMMENT);
      orderInfo[x].volumeInitial = HistoryOrderGetDouble(orderTicket, ORDER_VOLUME_INITIAL);
      orderInfo[x].priceOpen = HistoryOrderGetDouble(orderTicket, ORDER_PRICE_OPEN);
      orderInfo[x].priceStopLimit = HistoryOrderGetDouble(orderTicket, ORDER_PRICE_STOPLIMIT);
      orderInfo[x].tpPrice = HistoryOrderGetDouble(orderTicket, ORDER_TP);
      orderInfo[x].slPrice = HistoryOrderGetDouble(orderTicket, ORDER_SL);
     }
   else
     {
      Print(
         __FUNCTION__, " HistoryOrderGetTicket(", x, ") failed. (orderTicket = ", orderTicket,
         ") *** Error Code: ", GetLastError()
      );
     }
  }

すべての注文を処理した後、関数は正常に終了します。以下は、すべてのコードセグメントが含まれたSaveOrdersData関数の完全な実装です。

void SaveOrdersData()
  {
//- Get the number of loaded history orders
   int totalOrdersHistory = HistoryOrdersTotal();
   ulong orderTicket;
//-
//- Check if we have any orders in the history to be worked on
   if(totalOrdersHistory > 0)
     {
      //- Resize the dynamic array that stores the history orders
      ArrayResize(orderInfo, totalOrdersHistory);

      //- Let us loop through the order history and save them one by one
      for(int x = totalOrdersHistory - 1; x >= 0; x--)
        {
         ResetLastError();
         orderTicket = HistoryOrderGetTicket(x);
         if(orderTicket > 0)
           {
            //- Order ticket selected ok, we can now save the order properties
            orderInfo[x].ticket = orderTicket;
            orderInfo[x].timeSetup = (datetime)HistoryOrderGetInteger(orderTicket, ORDER_TIME_SETUP);
            orderInfo[x].timeDone = (datetime)HistoryOrderGetInteger(orderTicket, ORDER_TIME_DONE);
            orderInfo[x].expirationTime = (datetime)HistoryOrderGetInteger(orderTicket, ORDER_TIME_EXPIRATION);
            orderInfo[x].typeTime = (ENUM_ORDER_TYPE_TIME)HistoryOrderGetInteger(orderTicket, ORDER_TYPE_TIME);
            orderInfo[x].magic = HistoryOrderGetInteger(orderTicket, ORDER_MAGIC);
            orderInfo[x].reason = (ENUM_ORDER_REASON)HistoryOrderGetInteger(orderTicket, ORDER_REASON);
            orderInfo[x].type = (ENUM_ORDER_TYPE)HistoryOrderGetInteger(orderTicket, ORDER_TYPE);
            orderInfo[x].state = (ENUM_ORDER_STATE)HistoryOrderGetInteger(orderTicket, ORDER_STATE);
            orderInfo[x].typeFilling = (ENUM_ORDER_TYPE_FILLING)HistoryOrderGetInteger(orderTicket, ORDER_TYPE_FILLING);
            orderInfo[x].positionId = HistoryOrderGetInteger(orderTicket, ORDER_POSITION_ID);
            orderInfo[x].positionById = HistoryOrderGetInteger(orderTicket, ORDER_POSITION_BY_ID);
            orderInfo[x].symbol = HistoryOrderGetString(orderTicket, ORDER_SYMBOL);
            orderInfo[x].comment = HistoryOrderGetString(orderTicket, ORDER_COMMENT);
            orderInfo[x].volumeInitial = HistoryOrderGetDouble(orderTicket, ORDER_VOLUME_INITIAL);
            orderInfo[x].priceOpen = HistoryOrderGetDouble(orderTicket, ORDER_PRICE_OPEN);
            orderInfo[x].priceStopLimit = HistoryOrderGetDouble(orderTicket, ORDER_PRICE_STOPLIMIT);
            orderInfo[x].tpPrice = HistoryOrderGetDouble(orderTicket, ORDER_TP);
            orderInfo[x].slPrice = HistoryOrderGetDouble(orderTicket, ORDER_SL);
           }
         else
           {
            Print(
               __FUNCTION__, " HistoryOrderGetTicket(", x, ") failed. (orderTicket = ", orderTicket,
               ") *** Error Code: ", GetLastError()
            );
           }
        }
     }
   else
     {
      Print(__FUNCTION__, ": No order history available to be processed, totalOrdersHistory = ", totalOrdersHistory);
     }
  }


PrintOrdersHistory関数

PrintOrdersHistory関数は、指定された期間内の注文履歴の詳細を表示するための重要な機能を提供します。以前に保存されたデータをorderInfo配列から照会し、注文の関連する詳細をすべて出力します。この関数は、このライブラリを使用する外部モジュールまたはMQL5アプリからアクセスできるようにするために、exportとして定義されています。これはPrintDealsHistory関数と同様のアプローチに従います。以下はPrintOrdersHistory関数の完全な実装です。コードの各部分の動作をよりよく理解できるように説明コメントが付いています。

void PrintOrdersHistory(datetime fromDateTime, datetime toDateTime) export
  {
//- Get and save the orders history for the specified period
   GetHistoryData(fromDateTime, toDateTime, GET_ORDERS_HISTORY_DATA);
   int totalOrders = ArraySize(orderInfo);
   if(totalOrders <= 0)
     {
      Print("");
      Print(__FUNCTION__, ": No orders history found for the specified period.");
      return; //-- Exit the function
     }

   Print("");
   Print(__FUNCTION__, "-------------------------------------------------------------------------------");
   Print(
      "Found a total of ", totalOrders,
      " orders filled or cancelled between (", fromDateTime, ") and (", toDateTime, ")."
   );

   for(int r = 0; r < totalOrders; r++)
     {
      Print("---------------------------------------------------------------------------------------------------");
      Print("Order #", (r + 1));
      Print("Symbol: ", orderInfo[r].symbol);
      Print("Time Setup: ", orderInfo[r].timeSetup);
      Print("Type: ", EnumToString(orderInfo[r].type));
      Print("Ticket: ", orderInfo[r].ticket);
      Print("Position ID: ", orderInfo[r].positionId);
      Print("State: ", EnumToString(orderInfo[r].state));
      Print("Type Filling: ", EnumToString(orderInfo[r].typeFilling));
      Print("Type Time: ", EnumToString(orderInfo[r].typeTime));
      Print("Reason: ", EnumToString(orderInfo[r].reason));
      Print("Volume Initial: ", orderInfo[r].volumeInitial);
      Print("Price Open: ", orderInfo[r].priceOpen);
      Print("Price Stop Limit: ", orderInfo[r].priceStopLimit);
      Print("SL Price: ", orderInfo[r].slPrice);
      Print("TP Price: ", orderInfo[r].tpPrice);
      Print("Time Done: ", orderInfo[r].timeDone);
      Print("Expiration Time: ", orderInfo[r].expirationTime);
      Print("Comment: ", orderInfo[r].comment);
      Print("Magic: ", orderInfo[r].magic);
      Print("");
     }
  }


SavePositionsData関数

SavePositionsData関数は、取引と注文履歴を整理して各ポジションのライフサイクルを再構築し、利用可能なデータから情報を統合してポジション履歴を作成する上で中心的な役割を果たします。MQL5のドキュメントを見ると、履歴ポジションデータに直接アクセスするための標準関数(HistoryPositionSelectやHistoryPositionsTotalなど)がないことがわかります。したがって、ポジションIDを接続キーとして使用して、取引とその元の注文をリンクし、注文と取引データを結合するカスタム関数を作成する必要があります。

まず、取引を調べて、ポジションがクローズされたことを示すすべてのエグジット取引を特定します。そこから、対応するエントリー取引まで遡って、ポジションの開始に関する詳細を収集します。最後に、注文履歴を使用して、元の注文タイプやポジションが予約注文によって開始されたかどうかなどの追加のコンテキストでポジション履歴情報を充実させます。この段階的なプロセスにより、各ポジションのライフサイクル(開始から終了まで)が正確に再構築され、わかりやすい監査証跡が提供されます。

まず関数シグネチャを定義しましょう。この関数はEX5ライブラリコアモジュールによって内部的にのみ使用されるため、エクスポートすることはできません。

void SavePositionsData()
  {
//-- Our function's code will go here
  }

次に、すべての取引データが含まれるdealInfo配列内の取引の合計数を計算します。その後、すべてのポジション履歴データを保存し、予想されるポジション数に対応できるように準備するために使用するpositionInfo配列のサイズを変更します。

int totalDealInfo = ArraySize(dealInfo);
ArrayResize(positionInfo, totalDealInfo);
int totalPositionsFound = 0, posIndex = 0;

dealInfo配列に利用可能な取引がない場合(totalDealInfo==0)、処理するデータがないため、関数を早期に終了します。

if(totalDealInfo == 0)
  {
   return;
  }

次に、取引を逆の順序でループし(最新の取引から開始)、エグジット取引を対応するエントリ取引にマッピングできるようにします。現在の取引がエグジット取引であるかどうかは、entryプロパティを評価することによって確認します(dealInfo[x].entry==DEAL_ENTRY_OUT)。 ポジションがクローズされ、アクティブではなくなったことを確認するために、エグジット取引の検索から始めることが重要です。アクティブなポジションではなく、過去のクローズ済みポジションのみを記録します。

for(int x = totalDealInfo - 1; x >= 0; x--)
  {
   if(dealInfo[x].entry == DEAL_ENTRY_OUT)
     {
      // Process exit deal
     }
  }

エグジット取引が見つかった場合は、POSITION_IDを照合して対応するエントリー取引を検索します。エントリー取引が見つかったら、その関連情報をpositionInfo配列に保存し始めます。

for(int k = ArraySize(dealInfo) - 1; k >= 0; k--)
  {
   if(dealInfo[k].positionId == positionId)
     {
      if(dealInfo[k].entry == DEAL_ENTRY_IN)
        {
         exitDealFound = true;
         totalPositionsFound++;
         posIndex = totalPositionsFound - 1;

         // Save the entry deal data
         positionInfo[posIndex].openingDealTicket = dealInfo[k].ticket;
         positionInfo[posIndex].openTime = dealInfo[k].time;
         positionInfo[posIndex].openPrice = dealInfo[k].price;
         positionInfo[posIndex].volume = dealInfo[k].volume;
         positionInfo[posIndex].magic = dealInfo[k].magic;
         positionInfo[posIndex].comment = dealInfo[k].comment;
        }
     }
  }

エグジット取引がエントリー取引と一致したら、終値、終値時間、利益、スワップ、手数料などのエグジット取引のプロパティを保存します。また、スワップと手数料を考慮して取引期間と純利益を計算します。

if(exitDealFound)
  {
   if(dealInfo[x].type == DEAL_TYPE_BUY)
     {
      positionInfo[posIndex].type = POSITION_TYPE_SELL;
     }
   else
     {
      positionInfo[posIndex].type = POSITION_TYPE_BUY;
     }

   positionInfo[posIndex].positionId = dealInfo[x].positionId;
   positionInfo[posIndex].symbol = dealInfo[x].symbol;
   positionInfo[posIndex].profit = dealInfo[x].profit;
   positionInfo[posIndex].closingDealTicket = dealInfo[x].ticket;
   positionInfo[posIndex].closePrice = dealInfo[x].price;
   positionInfo[posIndex].closeTime = dealInfo[x].time;
   positionInfo[posIndex].swap = dealInfo[x].swap;
   positionInfo[posIndex].commission = dealInfo[x].commission;

   positionInfo[posIndex].duration = MathAbs((long)positionInfo[posIndex].closeTime -
                                     (long)positionInfo[posIndex].openTime);
   positionInfo[posIndex].netProfit = positionInfo[posIndex].profit + positionInfo[posIndex].swap -
                                      positionInfo[posIndex].commission;
  }

各ポジションについて、そのポジションが買い売りかに応じて、ストップロス(SL)レベルとテイクプロフィット(TP)レベルのピップ値を計算します。銘柄のポイント値を使用してピップ数を決定します。

if(positionInfo[posIndex].type == POSITION_TYPE_BUY)
  {
// Calculate TP and SL pip values for buy position
   if(positionInfo[posIndex].tpPrice > 0)
     {
      double symbolPoint = SymbolInfoDouble(positionInfo[posIndex].symbol, SYMBOL_POINT);
      positionInfo[posIndex].tpPips = int((positionInfo[posIndex].tpPrice -
                                           positionInfo[posIndex].openPrice) / symbolPoint);
     }
   if(positionInfo[posIndex].slPrice > 0)
     {
      double symbolPoint = SymbolInfoDouble(positionInfo[posIndex].symbol, SYMBOL_POINT);
      positionInfo[posIndex].slPips = int((positionInfo[posIndex].openPrice -
                                           positionInfo[posIndex].slPrice) / symbolPoint);
     }
// Calculate pip profit for buy position
   double symbolPoint = SymbolInfoDouble(positionInfo[posIndex].symbol, SYMBOL_POINT);
   positionInfo[posIndex].pipProfit = int((positionInfo[posIndex].closePrice -
                                           positionInfo[posIndex].openPrice) / symbolPoint);
  }
else
  {
// Calculate TP and SL pip values for sell position
   if(positionInfo[posIndex].tpPrice > 0)
     {
      double symbolPoint = SymbolInfoDouble(positionInfo[posIndex].symbol, SYMBOL_POINT);
      positionInfo[posIndex].tpPips = int((positionInfo[posIndex].openPrice -
                                           positionInfo[posIndex].tpPrice) / symbolPoint);
     }
   if(positionInfo[posIndex].slPrice > 0)
     {
      double symbolPoint = SymbolInfoDouble(positionInfo[posIndex].symbol, SYMBOL_POINT);
      positionInfo[posIndex].slPips = int((positionInfo[posIndex].slPrice -
                                           positionInfo[posIndex].openPrice) / symbolPoint);
     }
// Calculate pip profit for sell position
   double symbolPoint = SymbolInfoDouble(positionInfo[posIndex].symbol, SYMBOL_POINT);
   positionInfo[posIndex].pipProfit = int((positionInfo[posIndex].openPrice -
                                           positionInfo[posIndex].closePrice) / symbolPoint);
  }

最後に、orderInfo配列を調べて、ポジションを開始した注文を見つけます。POSITION_IDを照合し、注文がORDER_STATE_FILLED状態であることを確認します。見つかったら、開始注文のtickettypeを保存します。これにより、ポジションが予約注文によって開始されたのか、直接成行エントリーによって開始されたのかを判断するのに役立ちます。

for(int k = 0; k < ArraySize(orderInfo); k++)
  {
   if(
      orderInfo[k].positionId == positionInfo[posIndex].positionId &&
      orderInfo[k].state == ORDER_STATE_FILLED
   )
     {
      positionInfo[posIndex].openingOrderTicket = orderInfo[k].ticket;
      positionInfo[posIndex].ticket = positionInfo[posIndex].openingOrderTicket;

      //- Determine if the position was initiated by a pending order or direct market entry
      switch(orderInfo[k].type)
        {
         case ORDER_TYPE_BUY_LIMIT:
         case ORDER_TYPE_BUY_STOP:
         case ORDER_TYPE_SELL_LIMIT:
         case ORDER_TYPE_SELL_STOP:
         case ORDER_TYPE_BUY_STOP_LIMIT:
         case ORDER_TYPE_SELL_STOP_LIMIT:
            positionInfo[posIndex].initiatedByPendingOrder = true;
            positionInfo[posIndex].initiatingOrderType = orderInfo[k].type;
            break;
         default:
            positionInfo[posIndex].initiatedByPendingOrder = false;
            positionInfo[posIndex].initiatingOrderType = orderInfo[k].type;
            break;
        }

      break; //- Exit the orderInfo loop once the required data is found
     }
  }

最後に、positionInfo配列をクリーンアップするために、すべてのポジションが処理された後に、空または未使用の要素を削除するように配列のサイズを変更します。

ArrayResize(positionInfo, totalPositionsFound);

以下は、すべてのコードセグメントが含まれたSavePositionsData関数の完全な実装です。

void SavePositionsData()
  {
//- Since every transaction is recorded as a deal, we will begin by scanning the deals and link them
//- to different orders and generate the positions data using the POSITION_ID as the primary and foreign key
   int totalDealInfo = ArraySize(dealInfo);
   ArrayResize(positionInfo, totalDealInfo); //- Resize the position array to match the deals array
   int totalPositionsFound = 0, posIndex = 0;
   if(totalDealInfo == 0) //- Check if we have any deal history available for processing
     {
      return; //- No deal data to process found, we can't go on. exit the function
     }
//- Let us loop through the deals array
   for(int x = totalDealInfo - 1; x >= 0; x--)
     {
      //- First we check if it is an exit deal to close a position
      if(dealInfo[x].entry == DEAL_ENTRY_OUT)
        {
         //- We begin by saving the position id
         ulong positionId = dealInfo[x].positionId;
         bool exitDealFound = false;

         //- Now we check if we have an exit deal from this position and save it's properties
         for(int k = ArraySize(dealInfo) - 1; k >= 0; k--)
           {
            if(dealInfo[k].positionId == positionId)
              {
               if(dealInfo[k].entry == DEAL_ENTRY_IN)
                 {
                  exitDealFound = true;

                  totalPositionsFound++;
                  posIndex = totalPositionsFound - 1;

                  positionInfo[posIndex].openingDealTicket = dealInfo[k].ticket;
                  positionInfo[posIndex].openTime = dealInfo[k].time;
                  positionInfo[posIndex].openPrice = dealInfo[k].price;
                  positionInfo[posIndex].volume = dealInfo[k].volume;
                  positionInfo[posIndex].magic = dealInfo[k].magic;
                  positionInfo[posIndex].comment = dealInfo[k].comment;
                 }
              }
           }

         if(exitDealFound) //- Continue saving the exit deal data
           {
            //- Save the position type
            if(dealInfo[x].type == DEAL_TYPE_BUY)
              {
               //- If the exit deal is a buy, then the position was a sell trade
               positionInfo[posIndex].type = POSITION_TYPE_SELL;
              }
            else
              {
               //- If the exit deal is a sell, then the position was a buy trade
               positionInfo[posIndex].type = POSITION_TYPE_BUY;
              }

            positionInfo[posIndex].positionId = dealInfo[x].positionId;
            positionInfo[posIndex].symbol = dealInfo[x].symbol;
            positionInfo[posIndex].profit = dealInfo[x].profit;
            positionInfo[posIndex].closingDealTicket = dealInfo[x].ticket;
            positionInfo[posIndex].closePrice = dealInfo[x].price;
            positionInfo[posIndex].closeTime = dealInfo[x].time;
            positionInfo[posIndex].swap = dealInfo[x].swap;
            positionInfo[posIndex].commission = dealInfo[x].commission;
            positionInfo[posIndex].tpPrice = dealInfo[x].tpPrice;
            positionInfo[posIndex].tpPips = 0;
            positionInfo[posIndex].slPrice = dealInfo[x].slPrice;
            positionInfo[posIndex].slPips = 0;

            //- Calculate the trade duration in seconds
            positionInfo[posIndex].duration = MathAbs((long)positionInfo[posIndex].closeTime - (long)positionInfo[posIndex].openTime);

            //- Calculate the net profit after swap and commission
            positionInfo[posIndex].netProfit =
               positionInfo[posIndex].profit + positionInfo[posIndex].swap - positionInfo[posIndex].commission;

            //- Get pip values for the position
            if(positionInfo[posIndex].type == POSITION_TYPE_BUY) //- Buy position
              {
               //- Get sl and tp pip values
               if(positionInfo[posIndex].tpPrice > 0)
                 {
                  double symbolPoint = SymbolInfoDouble(positionInfo[posIndex].symbol, SYMBOL_POINT);
                  positionInfo[posIndex].tpPips =
                     int((positionInfo[posIndex].tpPrice - positionInfo[posIndex].openPrice) / symbolPoint);
                 }
               if(positionInfo[posIndex].slPrice > 0)
                 {
                  double symbolPoint = SymbolInfoDouble(positionInfo[posIndex].symbol, SYMBOL_POINT);
                  positionInfo[posIndex].slPips =
                     int((positionInfo[posIndex].openPrice - positionInfo[posIndex].slPrice) / symbolPoint);
                 }

               //- Get the buy profit in pip value
               double symbolPoint = SymbolInfoDouble(positionInfo[posIndex].symbol, SYMBOL_POINT);
               positionInfo[posIndex].pipProfit =
                  int((positionInfo[posIndex].closePrice - positionInfo[posIndex].openPrice) / symbolPoint);
              }
            else //- Sell position
              {
               //- Get sl and tp pip values
               if(positionInfo[posIndex].tpPrice > 0)
                 {
                  double symbolPoint = SymbolInfoDouble(positionInfo[posIndex].symbol, SYMBOL_POINT);
                  positionInfo[posIndex].tpPips =
                     int((positionInfo[posIndex].openPrice - positionInfo[posIndex].tpPrice) / symbolPoint);
                 }
               if(positionInfo[posIndex].slPrice > 0)
                 {
                  double symbolPoint = SymbolInfoDouble(positionInfo[posIndex].symbol, SYMBOL_POINT);
                  positionInfo[posIndex].slPips =
                     int((positionInfo[posIndex].slPrice - positionInfo[posIndex].openPrice) / symbolPoint);
                 }

               //- Get the sell profit in pip value
               double symbolPoint = SymbolInfoDouble(positionInfo[posIndex].symbol, SYMBOL_POINT);
               positionInfo[posIndex].pipProfit =
                  int((positionInfo[posIndex].openPrice - positionInfo[posIndex].closePrice) / symbolPoint);
              }

            //- Now we scan and get the opening order ticket in the orderInfo array
            for(int k = 0; k < ArraySize(orderInfo); k++) //- Search from the oldest to newest order
              {
               if(
                  orderInfo[k].positionId == positionInfo[posIndex].positionId &&
                  orderInfo[k].state == ORDER_STATE_FILLED
               )
                 {
                  //- Save the order ticket that intiated the position
                  positionInfo[posIndex].openingOrderTicket = orderInfo[k].ticket;
                  positionInfo[posIndex].ticket = positionInfo[posIndex].openingOrderTicket;

                  //- Determine if the position was initiated by a pending order or direct market entry
                  switch(orderInfo[k].type)
                    {
                     //- Pending order entry
                     case ORDER_TYPE_BUY_LIMIT:
                     case ORDER_TYPE_BUY_STOP:
                     case ORDER_TYPE_SELL_LIMIT:
                     case ORDER_TYPE_SELL_STOP:
                     case ORDER_TYPE_BUY_STOP_LIMIT:
                     case ORDER_TYPE_SELL_STOP_LIMIT:
                        positionInfo[posIndex].initiatedByPendingOrder = true;
                        positionInfo[posIndex].initiatingOrderType = orderInfo[k].type;
                        break;

                     //- Direct market entry
                     default:
                        positionInfo[posIndex].initiatedByPendingOrder = false;
                        positionInfo[posIndex].initiatingOrderType = orderInfo[k].type;
                        break;
                    }

                  break; //--- We have everything we need, exit the orderInfo loop
                 }
              }
           }
        }
      else //--- Position id not found
        {
         continue;//- skip to the next iteration
        }
     }
//- Resize the positionInfo array and delete all the indexes that have zero values
   ArrayResize(positionInfo, totalPositionsFound);
  }


PrintPositionsHistory関数

PrintPositionsHistory関数は、指定された時間枠内でクローズされたポジションの詳細な履歴を表示するように設計されています。これは、positionInfo配列から以前に保存されたデータにアクセスし、各ポジションの関連する詳細を出力します。この関数はエクスポート可能であり、このライブラリを利用する外部モジュールまたはMQL5アプリからアクセスできるようになります。その実装は、私たちが開発した他の出力関数と同様の構造に従います。以下に、わかりやすくするために詳細なコメントを付した完全な実装を示します。

void PrintPositionsHistory(datetime fromDateTime, datetime toDateTime) export
  {
//- Get and save the deals, orders, positions history for the specified period
   GetHistoryData(fromDateTime, toDateTime, GET_POSITIONS_HISTORY_DATA);
   int totalPositionsClosed = ArraySize(positionInfo);
   if(totalPositionsClosed <= 0)
     {
      Print("");
      Print(__FUNCTION__, ": No position history found for the specified period.");
      return; //- Exit the function
     }

   Print("");
   Print(__FUNCTION__, "-------------------------------------------------------------------------------");
   Print(
      "Found a total of ", totalPositionsClosed,
      " positions closed between (", fromDateTime, ") and (", toDateTime, ")."
   );

   for(int r = 0; r < totalPositionsClosed; r++)
     {
      Print("---------------------------------------------------------------------------------------------------");
      Print("Position #", (r + 1));
      Print("Symbol: ", positionInfo[r].symbol);
      Print("Time Open: ", positionInfo[r].openTime);
      Print("Ticket: ", positionInfo[r].ticket);
      Print("Type: ", EnumToString(positionInfo[r].type));
      Print("Volume: ", positionInfo[r].volume);
      Print("0pen Price: ", positionInfo[r].openPrice);
      Print("SL Price: ", positionInfo[r].slPrice, " (slPips: ", positionInfo[r].slPips, ")");
      Print("TP Price: ", positionInfo[r].tpPrice, " (tpPips: ", positionInfo[r].tpPips, ")");
      Print("Close Price: ", positionInfo[r].closePrice);
      Print("Close Time: ", positionInfo[r].closeTime);
      Print("Trade Duration: ", positionInfo[r].duration);
      Print("Swap: ", positionInfo[r].swap, " ", AccountInfoString(ACCOUNT_CURRENCY));
      Print("Commission: ", positionInfo[r].commission, " ", AccountInfoString(ACCOUNT_CURRENCY));
      Print("Profit: ", positionInfo[r].profit, " ", AccountInfoString(ACCOUNT_CURRENCY));
      Print("Net profit: ", DoubleToString(positionInfo[r].netProfit, 2), " ", AccountInfoString(ACCOUNT_CURRENCY));
      Print("pipProfit: ", positionInfo[r].pipProfit);
      Print("Initiating Order Type: ", EnumToString(positionInfo[r].initiatingOrderType));
      Print("Initiated By Pending Order: ", positionInfo[r].initiatedByPendingOrder);
      Print("Comment: ", positionInfo[r].comment);
      Print("Magic: ", positionInfo[r].magic);
      Print("");
     }
  }


SavePendingOrdersData関数

SavePendingOrdersData関数は、注文履歴のデータを処理し、予約注文履歴を生成して保存します。この関数は基本的に、注文履歴から予約注文をフィルタリングし、重要な詳細を保存し、テイクプロフィット(TP)レベルとストップロス(SL)レベルのピップ数などの特定の値を計算します。これは、予約注文のライフサイクルを追跡し、正確な注文履歴を生成するのに役立ち、各予約注文がどのように構成され実行されたかに関するデータをシステムに追加するのに重要な役割を果たします。

MQL5には現在、過去の予約注文データに直接アクセスするためのHistoryPendingOrderSelectHistoryPendingOrdersTotalなどの標準関数がありません。そのため、注文履歴をスキャンし、特定の履歴期間内に完了またはキャンセルされたすべての予約注文を含むデータソースを構築するカスタム関数を作成する必要があります。

まず関数シグネチャを定義しましょう。この関数はEX5ライブラリコアモジュールによって内部的にのみ使用されるため、エクスポートすることはできません。

void SavePendingOrdersData()
  {
//-- Function's code will go here
  }

次に、すべての注文の詳細を保持するorderInfo配列内の注文の合計数を計算します。最初に、注文の合計数に合わせてpendingOrderInfo配列のサイズを変更し、フィルタリングされた予約注文を保存するのに十分なスペースを確保します。

int totalOrderInfo = ArraySize(orderInfo);
ArrayResize(pendingOrderInfo, totalOrderInfo);
int totalPendingOrdersFound = 0, pendingIndex = 0;

処理する注文がない場合(totalOrderInfo==0)、処理する予約注文データがないため、すぐに関数を終了します。

if(totalOrderInfo == 0)
  {
   return;
  }

ここで、最新の注文が最初に処理されるように、注文を逆の順序でループします。ループ内では、現在の注文のタイプを評価して、それが予約注文であるかどうかを確認します。保存された注文履歴には、実行(約定)されてポジションに変換されたか、ポジションにならずにキャンセルされた予約注文(買い指値、売り指値など)が含まれます。

for(int x = totalOrderInfo - 1; x >= 0; x--)
  {
   if(
      orderInfo[x].type == ORDER_TYPE_BUY_LIMIT || orderInfo[x].type == ORDER_TYPE_BUY_STOP ||
      orderInfo[x].type == ORDER_TYPE_SELL_LIMIT || orderInfo[x].type == ORDER_TYPE_SELL_STOP ||
      orderInfo[x].type == ORDER_TYPE_BUY_STOP_LIMIT || orderInfo[x].type == ORDER_TYPE_SELL_STOP_LIMIT
   )
     {
      totalPendingOrdersFound++;
      pendingIndex = totalPendingOrdersFound - 1;

      //-- Save the pending order properties into the pendingOrderInfo array

     }

注文が予約注文の場合、そのプロパティ(タイプ、状態、ポジションID、チケット、銘柄、時間など)をpendingOrderInfo配列に保存します。

pendingOrderInfo[pendingIndex].type = orderInfo[x].type;
pendingOrderInfo[pendingIndex].state = orderInfo[x].state;
pendingOrderInfo[pendingIndex].positionId = orderInfo[x].positionId;
pendingOrderInfo[pendingIndex].ticket = orderInfo[x].ticket;
pendingOrderInfo[pendingIndex].symbol = orderInfo[x].symbol;
pendingOrderInfo[pendingIndex].timeSetup = orderInfo[x].timeSetup;
pendingOrderInfo[pendingIndex].expirationTime = orderInfo[x].expirationTime;
pendingOrderInfo[pendingIndex].timeDone = orderInfo[x].timeDone;
pendingOrderInfo[pendingIndex].typeTime = orderInfo[x].typeTime;
pendingOrderInfo[pendingIndex].priceOpen = orderInfo[x].priceOpen;
pendingOrderInfo[pendingIndex].tpPrice = orderInfo[x].tpPrice;
pendingOrderInfo[pendingIndex].slPrice = orderInfo[x].slPrice;

次に、指定されている場合はテイクプロフィット(TP)レベルとストップロス(SL)レベルの両方のピップ数を計算します。これをおこなうには、銘柄のポイント値を使用してピップの数を決定します。

if(pendingOrderInfo[pendingIndex].tpPrice > 0)
  {
   double symbolPoint = SymbolInfoDouble(pendingOrderInfo[pendingIndex].symbol, SYMBOL_POINT);
   pendingOrderInfo[pendingIndex].tpPips =
      (int)MathAbs((pendingOrderInfo[pendingIndex].tpPrice - pendingOrderInfo[pendingIndex].priceOpen) / symbolPoint);
  }
if(pendingOrderInfo[pendingIndex].slPrice > 0)
  {
   double symbolPoint = SymbolInfoDouble(pendingOrderInfo[pendingIndex].symbol, SYMBOL_POINT);
   pendingOrderInfo[pendingIndex].slPips =
      (int)MathAbs((pendingOrderInfo[pendingIndex].slPrice - pendingOrderInfo[pendingIndex].priceOpen) / symbolPoint);
  }

また、注文のマジックナンバー、理由、約定タイプ、コメント、初期数量、ストップリミット価格などの追加プロパティも保存します。

pendingOrderInfo[pendingIndex].magic = orderInfo[x].magic;
pendingOrderInfo[pendingIndex].reason = orderInfo[x].reason;
pendingOrderInfo[pendingIndex].typeFilling = orderInfo[x].typeFilling;
pendingOrderInfo[pendingIndex].comment = orderInfo[x].comment;
pendingOrderInfo[pendingIndex].volumeInitial = orderInfo[x].volumeInitial;
pendingOrderInfo[pendingIndex].priceStopLimit = orderInfo[x].priceStopLimit;

すべての注文を処理したら、pendingOrderInfo配列のサイズを変更して、空または未使用の要素を削除し、配列に関連する予約注文データのみが含まれるようにします。

ArrayResize(pendingOrderInfo, totalPendingOrdersFound);

以下は、すべてのコードセグメントが含まれたSavePendingOrdersData関数の完全な実装です。

void SavePendingOrdersData()
  {
//- Let us begin by scanning the orders and link them to different deals
   int totalOrderInfo = ArraySize(orderInfo);
   ArrayResize(pendingOrderInfo, totalOrderInfo);
   int totalPendingOrdersFound = 0, pendingIndex = 0;
   if(totalOrderInfo == 0)
     {
      return; //- No order data to process found, we can't go on. exit the function
     }

   for(int x = totalOrderInfo - 1; x >= 0; x--)
     {
      //- Check if it is a pending order and save its properties
      if(
         orderInfo[x].type == ORDER_TYPE_BUY_LIMIT || orderInfo[x].type == ORDER_TYPE_BUY_STOP ||
         orderInfo[x].type == ORDER_TYPE_SELL_LIMIT || orderInfo[x].type == ORDER_TYPE_SELL_STOP ||
         orderInfo[x].type == ORDER_TYPE_BUY_STOP_LIMIT || orderInfo[x].type == ORDER_TYPE_SELL_STOP_LIMIT
      )
        {
         totalPendingOrdersFound++;
         pendingIndex = totalPendingOrdersFound - 1;

         pendingOrderInfo[pendingIndex].type = orderInfo[x].type;
         pendingOrderInfo[pendingIndex].state = orderInfo[x].state;
         pendingOrderInfo[pendingIndex].positionId = orderInfo[x].positionId;
         pendingOrderInfo[pendingIndex].ticket = orderInfo[x].ticket;
         pendingOrderInfo[pendingIndex].symbol = orderInfo[x].symbol;
         pendingOrderInfo[pendingIndex].timeSetup = orderInfo[x].timeSetup;
         pendingOrderInfo[pendingIndex].expirationTime = orderInfo[x].expirationTime;
         pendingOrderInfo[pendingIndex].timeDone = orderInfo[x].timeDone;
         pendingOrderInfo[pendingIndex].typeTime = orderInfo[x].typeTime;
         pendingOrderInfo[pendingIndex].priceOpen = orderInfo[x].priceOpen;
         pendingOrderInfo[pendingIndex].tpPrice = orderInfo[x].tpPrice;
         pendingOrderInfo[pendingIndex].slPrice = orderInfo[x].slPrice;

         if(pendingOrderInfo[pendingIndex].tpPrice > 0)
           {
            double symbolPoint = SymbolInfoDouble(pendingOrderInfo[pendingIndex].symbol, SYMBOL_POINT);
            pendingOrderInfo[pendingIndex].tpPips =
               (int)MathAbs((pendingOrderInfo[pendingIndex].tpPrice - pendingOrderInfo[pendingIndex].priceOpen) / symbolPoint);
           }
         if(pendingOrderInfo[pendingIndex].slPrice > 0)
           {
            double symbolPoint = SymbolInfoDouble(pendingOrderInfo[pendingIndex].symbol, SYMBOL_POINT);
            pendingOrderInfo[pendingIndex].slPips =
               (int)MathAbs((pendingOrderInfo[pendingIndex].slPrice - pendingOrderInfo[pendingIndex].priceOpen) / symbolPoint);
           }

         pendingOrderInfo[pendingIndex].magic = orderInfo[x].magic;
         pendingOrderInfo[pendingIndex].reason = orderInfo[x].reason;
         pendingOrderInfo[pendingIndex].typeFilling = orderInfo[x].typeFilling;
         pendingOrderInfo[pendingIndex].comment = orderInfo[x].comment;
         pendingOrderInfo[pendingIndex].volumeInitial = orderInfo[x].volumeInitial;
         pendingOrderInfo[pendingIndex].priceStopLimit = orderInfo[x].priceStopLimit;

        }
     }
//--Resize the pendingOrderInfo array and delete all the indexes that have zero values
   ArrayResize(pendingOrderInfo, totalPendingOrdersFound);
  }


PrintPendingOrdersHistory関数

PrintPendingOrdersHistory関数は、指定された期間内に実行またはキャンセルされた予約注文の詳細な履歴を表示するように設計されています。pendingOrderInfo配列から以前に保存されたデータにアクセスし、予約注文ごとに関連する詳細を出力します。この関数はエクスポート可能なので、このEX5ライブラリを利用する外部モジュールまたはMQL5アプリからアクセスできるようになります。その実装は、私たちが開発した他の出力関数と同様の構造に従います。わかりやすくするために詳細なコメントを付けた完全な実装を以下に示します。

void PrintPendingOrdersHistory(datetime fromDateTime, datetime toDateTime) export
  {
//- Get and save the pending orders history for the specified period
   GetHistoryData(fromDateTime, toDateTime, GET_PENDING_ORDERS_HISTORY_DATA);
   int totalPendingOrders = ArraySize(pendingOrderInfo);
   if(totalPendingOrders <= 0)
     {
      Print("");
      Print(__FUNCTION__, ": No pending orders history found for the specified period.");
      return; //- Exit the function
     }

   Print("");
   Print(__FUNCTION__, "-------------------------------------------------------------------------------");
   Print(
      "Found a total of ", totalPendingOrders,
      " pending orders filled or cancelled between (", fromDateTime, ") and (", toDateTime, ")."
   );

   for(int r = 0; r < totalPendingOrders; r++)
     {
      Print("---------------------------------------------------------------------------------------------------");
      Print("Pending Order #", (r + 1));
      Print("Symbol: ", pendingOrderInfo[r].symbol);
      Print("Time Setup: ", pendingOrderInfo[r].timeSetup);
      Print("Type: ", EnumToString(pendingOrderInfo[r].type));
      Print("Ticket: ", pendingOrderInfo[r].ticket);
      Print("State: ", EnumToString(pendingOrderInfo[r].state));
      Print("Time Done: ", pendingOrderInfo[r].timeDone);
      Print("Volume Initial: ", pendingOrderInfo[r].volumeInitial);
      Print("Price Open: ", pendingOrderInfo[r].priceOpen);
      Print("SL Price: ", pendingOrderInfo[r].slPrice, " (slPips: ", pendingOrderInfo[r].slPips, ")");
      Print("TP Price: ", pendingOrderInfo[r].tpPrice, " (slPips: ", pendingOrderInfo[r].slPips, ")");
      Print("Expiration Time: ", pendingOrderInfo[r].expirationTime);
      Print("Position ID: ", pendingOrderInfo[r].positionId);
      Print("Price Stop Limit: ", pendingOrderInfo[r].priceStopLimit);
      Print("Type Filling: ", EnumToString(pendingOrderInfo[r].typeFilling));
      Print("Type Time: ", EnumToString(pendingOrderInfo[r].typeTime));
      Print("Reason: ", EnumToString(pendingOrderInfo[r].reason));
      Print("Comment: ", pendingOrderInfo[r].comment);
      Print("Magic: ", pendingOrderInfo[r].magic);
      Print("");
     }
  }


結論

この記事では、MQL5を使用して注文および取引に関する履歴データを取得する方法を解説しました。そしてこのデータを活用して、決済済みポジション予約注文の履歴を生成し、それぞれのポジションのライフサイクルを追跡できる監査証跡の作成方法を学びました。これには、ポジションの発生源、決済方法、純利益、ピップ利益、ストップロスやテイクプロフィットのピップ値、取引期間など、さまざまな有用な情報が含まれます。

さらに、History Manager EX5ライブラリの中核となる関数群を開発し、さまざまな種類の履歴データをクエリ・保存・分類できるようにしました。これらの基本的な関数は、ライブラリ内部の処理を担うエンジンの一部を構成しています。ただし、まだ完成には至っておらず、本記事で作成した関数の多くは準備段階のものであり、今後よりユーザー志向のライブラリへと発展させていく土台となります。

次回の記事では、History Manager EX5ライブラリをさらに拡張し、一般ユーザーのニーズに基づいて履歴データを並べ替え・分析できるエクスポート可能な関数を導入していきます。たとえば、直近でクローズされたポジションのプロパティ取得、最後に約定またはキャンセルされた未決注文の分析、特定の銘柄に関する直近のクローズ済みポジションの確認、当日クローズした取引の利益計算、週単位のピップ利益の算出などが可能になります。

また、MetaTrader 5ストラテジーテスターが生成するレポートに匹敵する、詳細なトレードレポートを作成するための高度な並べ替え・分析モジュールも導入します。これらのレポートでは実際の取引履歴データを基に、EAや取引戦略のパフォーマンスを可視化・分析できるようになります。さらに、銘柄やマジックナンバーといったパラメータによって、データをプログラム的にフィルタリング・ソートすることも可能になります。

スムーズな実装を実現するために、History Manager EX5ライブラリには包括的なドキュメントと、実用的なユースケースの例を用意します。これにより、ライブラリをプロジェクトに組み込み、効果的な取引分析を行う方法をわかりやすく理解できます。加えて、取引戦略の最適化やライブラリの機能を最大限に活用するための、シンプルなEAの例やステップバイステップのデモンストレーションも提供します。

この記事の最後に、HistoryManager.mq5のソースコードファイルを添付しています。最後までお読みいただきありがとうございました。あなたの取引およびMQL5プログラミングの旅が素晴らしい成功につながることを心より願っています。

MetaQuotes Ltdにより英語から翻訳されました。
元の記事: https://www.mql5.com/en/articles/16528

添付されたファイル |
HistoryManager.mq5 (33.95 KB)
Candlestick Trend Constraintモデルの構築(第9回):マルチ戦略エキスパートアドバイザー(III) Candlestick Trend Constraintモデルの構築(第9回):マルチ戦略エキスパートアドバイザー(III)
連載第3回へようこそ。今回は、日足のトレンドに沿った最適なエントリーポイントを特定する戦略として、ダイバージェンスの活用について詳しく解説します。また、トレーリングストップロスに似た、しかし独自の機能を備えたカスタム利益ロック機構もご紹介します。さらに、Trend Constraint EAを高度化し、既存の取引条件を補完する形で新たなエントリー条件を追加します。今後も、MQL5を活用したアルゴリズム開発の実践的な応用方法を深掘りし、実際に使えるテクニックや洞察を継続的にお届けしていきます。
MQL5入門(第10回):MQL5の組み込みインジケーターの操作に関する初心者向けガイド MQL5入門(第10回):MQL5の組み込みインジケーターの操作に関する初心者向けガイド
この記事では、プロジェクトベースのアプローチを使用してRSIベースのエキスパートアドバイザー(EA)を作成する方法に焦点を当て、MQL5の組み込みインジケーターの活用方法を紹介します。RSI値を取得して活用し、流動性スイープに対応し、チャートオブジェクトを使用して取引の視覚化を強化する方法を学びます。さらに、パーセンテージベースのリスク設定、リスク報酬比率の実装、利益確保のためのリスク修正など、効果的なリスク管理についても解説します。
プライスアクション分析ツールキットの開発(第4回):Analytics Forecaster EA プライスアクション分析ツールキットの開発(第4回):Analytics Forecaster EA
チャート上に表示された分析済みのメトリックを見るだけにとどまらず、Telegramとの統合によってブロードキャストを拡張するという、より広い視点へと移行しています。この機能強化により、Telegramアプリを通じて、重要な結果がモバイルデバイスに直接配信されるようになります。この記事では、この新たな取り組みを一緒に探っていきましょう。
ケリー基準とモンテカルロシミュレーションを使用したポートフォリオリスクモデル ケリー基準とモンテカルロシミュレーションを使用したポートフォリオリスクモデル
数十年にわたり、トレーダーは破産リスクを最小限に抑えつつ長期的な資産成長を最大化する手法として、ケリー基準の公式を活用してきました。しかし、単一のバックテスト結果に基づいてケリー基準を盲目的に適用することは、個人トレーダーにとって非常に危険です。というのも、実際の取引では時間の経過とともに取引優位性が薄れ、過去の実績は将来の結果を保証するものではないからです。本記事では、Pythonによるモンテカルロシミュレーションの結果を取り入れ、MetaTrader 5上で1つ以上のエキスパートアドバイザー(EA)にケリー基準を現実的に適用するためのリスク配分アプローチを紹介します。