MQL5で「注文が成立したら履歴を記録したい」と考えたとき、

OnTrade()だけでは何が起きたのかを細かく区別できません。

そこで使うのが`OnTradeTransaction()`です。

ただし、このイベントは1回の注文につき1回だけ呼ばれるとは限りません。

注文の受付、約定の追加、注文の履歴移動など、サーバー上の状態変化ごとに呼ばれます。

この記事では、OnTradeTransactionの3つの引数と、

二重処理を避けながら「新しい約定だけ」を検知する方法を解説します。

OnTradeTransactionは取引の細かな変化を受け取るイベント

OnTradeTransactionはEAで取引トランザクションが発生したときに、

自動で呼ばれるイベントハンドラです。

自分のEAが送った注文だけでなく、手動売買や別EA、

サーバー側の処理による変化も対象になります。

そのため「この関数が呼ばれた=自分の注文が新規約定した」と、

決めつけてはいけません。

実務では、最初に`trans.type`を確認し、必要な種類だけを処理します。

OnTradeTransactionの書式と3つの引数

void OnTradeTransaction(
   const MqlTradeTransaction &trans,
   const MqlTradeRequest     &request,
   const MqlTradeResult      &result
);

`trans`には、今回発生したトランザクションの種類、銘柄、

注文・約定・ポジションのチケットなどが入ります。

`request`と`result`は取引リクエストと実行結果です。

ただし、この2つに有効な値が入るのは主に、

`TRADE_TRANSACTION_REQUEST`の場合です。

約定追加を処理するときは、`trans.deal`を使って、

履歴から詳細を取得する設計が分かりやすくなります。

1回の注文で複数回呼ばれる理由

成行注文を1回送っても、内部では「リクエスト受付」

「注文追加」

「約定追加」

「注文を履歴へ移動」といった複数の変化が起こります。

さらに取引所や流動性によっては、

1注文が複数の約定に分かれる部分約定もあります。

したがって、イベントが呼ばれるたびに損益集計や通知を行うと、

同じ取引を何度も数える事故が起きます。

新しい約定を処理したい場合は、

`TRADE_TRANSACTION_DEAL_ADD`だけを通すのが第一段階です。

新しい約定だけを検知するEAコード

次のコードは、約定が履歴に追加されたときだけ、

銘柄・売買方向・数量・価格をログへ出します。

#property strict

void OnTick()
{
   // 売買ロジックはここに記述
}

void OnTradeTransaction(
   const MqlTradeTransaction &trans,
   const MqlTradeRequest     &request,
   const MqlTradeResult      &result)
{
   if(trans.type != TRADE_TRANSACTION_DEAL_ADD)
      return;

   ulong deal_ticket = trans.deal;
   if(deal_ticket == 0 || !HistoryDealSelect(deal_ticket))
   {
      Print("約定履歴を選択できません: ", deal_ticket);
      return;
   }

   string symbol = HistoryDealGetString(deal_ticket, DEAL_SYMBOL);
   long deal_type = HistoryDealGetInteger(deal_ticket, DEAL_TYPE);
   long entry_type = HistoryDealGetInteger(deal_ticket, DEAL_ENTRY);
   double volume = HistoryDealGetDouble(deal_ticket, DEAL_VOLUME);
   double price = HistoryDealGetDouble(deal_ticket, DEAL_PRICE);

   PrintFormat("約定追加 ticket=%I64u symbol=%s type=%s entry=%s volume=%.2f price=%.*f",
               deal_ticket,
               symbol,
               EnumToString((ENUM_DEAL_TYPE)deal_type),
               EnumToString((ENUM_DEAL_ENTRY)entry_type),
               volume,
               (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS),
               price);
}

`DEAL_ENTRY_IN`は新規エントリー、

`DEAL_ENTRY_OUT`は決済側の約定を表します。

「新規ポジションだけ通知する」なら、

`entry_type != DEAL_ENTRY_IN`の場合に、

returnする条件を追加してください。

MetaEditorでの確認手順

MT5のMetaEditorで新しいEAを作り、上のコードを貼り付けてコンパイルします。

ストラテジーテスターまたはデモ口座のチャートへEAを適用し、注文を実行します。

「エキスパート」ログに`約定追加`が表示され、

注文受付など別のイベントでは表示されないことを確認してください。

実口座へ入れる前に、部分決済と反対売買でも、

`DEAL_ENTRY`が期待どおりになるかをデモ環境で確認します。

よくあるエラーと設計上の注意

requestとresultを毎回読む

requestとresultは、すべてのトランザクション種別で、

同じ意味を持つわけではありません。

発注結果を読む処理と、約定履歴を読む処理を分けてください。

イベント内で重い処理をする

トランザクションは短時間に連続して届きます。

イベント内で長い通信や大量計算を行わず、

必要な情報だけ記録して後段で処理する設計が安全です。

銘柄やマジックナンバーを絞らない

口座全体の取引が届くため、自分のEAだけを対象にする場合は、

`DEAL_MAGIC`と`DEAL_SYMBOL`も確認します。

まとめ

OnTradeTransactionは、取引結果を細かく追跡できる、

MQL5らしいイベントです。

一方で「1回の発注につき1回」と考えると、

通知や集計を重複させる原因になります。

まず`trans.type`で目的の変化だけに絞り、

約定処理では`trans.deal`を履歴から読み直す構成にしてください。

関連記事として、発注側のCTradeクラスと、

約定履歴を期間指定で読むHistorySelectもあわせて確認すると、

発注から履歴管理までを一続きで理解できます。