history-select-mql5.mdで、
MQL5の約定履歴を取得する、
`HistorySelect()`+`HistoryDealGetDouble()`の、
使い方を解説しました。
ところがこのコードだけでは、「指値注文を出したのに、
価格に届かないまま有効期限が切れてキャンセルされた」
というケースを一切拾えません。
Dealは「実際に約定した」記録であり、
注文したのに一度も約定しなかった指値・逆指値注文はDealとしては存在しないからです。
この記事では、約定の有無に関わらず「注文(Order)」
そのものの履歴を扱う`HistoryOrderGetDouble()`/
`HistoryOrderGetInteger()`/
`HistoryOrderGetString()`を使い、
出した指値注文のうち何割が実際に約定し、
何割がキャンセル・失効したのかを集計するスクリプトを解説します。
DealとOrderは別物: 「約定した記録」と「注文した記録」
MQL5では、あなたが出した1つの指値・逆指値注文は、まず「Order(注文)」
として記録されます。
その注文が実際に価格に届いて約定すると、初めて「Deal(約定)」
という別の記録が追加で作られます。
Deal(約定): 実際に売買が成立した時点で作られる記録。
キャンセルや失効で終わった注文にはDealが存在しない
つまり「指値がどれだけ刺さらずに終わったか」を調べたい場合、
Dealだけを見ていては絶対に分かりません。
Order単位の履歴を扱う`HistoryOrderGetDouble()`系の関数が、
必要になるのはこのためです。
HistoryOrderGetDouble/Integer/Stringの書式
`HistorySelect()`で対象期間を指定したあと、
次の3つの関数で注文の詳細を取得します。
double HistoryOrderGetDouble(ulong ticket_number, ENUM_ORDER_PROPERTY_DOUBLE property_id); long HistoryOrderGetInteger(ulong ticket_number, ENUM_ORDER_PROPERTY_INTEGER property_id); string HistoryOrderGetString(ulong ticket_number, ENUM_ORDER_PROPERTY_STRING property_id);
`HistoryDealGetDouble()`と同じく、
値の型(double/long/string)ごとに呼び出す関数を変える設計です。
対象となる注文のチケット番号(`ticket_number`)は、
`HistoryOrdersTotal()`で件数を取得し、
`HistoryOrderGetTicket(index)`で1件ずつ取り出します。
int HistoryOrdersTotal(); ulong HistoryOrderGetTicket(int index);
`HistoryDealsTotal()`/
`HistoryDealGetTicket()`と関数名が非常に似ているため、
`Deal`と`Order`を書き間違えるコピペミスに注意してください。
(コンパイルは通ってしまい、意図と違う集計結果になります)
注文の状態を判定するORDER_STATE
注文が最終的にどうなったかは、
`HistoryOrderGetInteger(ticket, ORDER_STATE)`
で取得できる`ENUM_ORDER_STATE`で判定します。
- ORDER_STATE_FILLED: 約定して完了した(Dealが存在する)
- ORDER_STATE_CANCELED: ユーザーまたはEAがキャンセルした
- ORDER_STATE_EXPIRED: 有効期限(ORDER_TIME_EXPIRATION)に達して自動的に失効した
- ORDER_STATE_PARTIAL: 一部数量のみ約定した
- ORDER_STATE_REJECTED: サーバー側に拒否された
「指値が刺さらなかった」注文は、
このうち`ORDER_STATE_CANCELED`と
`ORDER_STATE_EXPIRED`の2つを数えれば集計できます。
実際に動くコード全体
指定期間の指値・逆指値注文を集計し、約定率とキャンセル・失効の内訳を表示する、
実際にMetaEditorでコンパイルできるスクリプトです。
//+------------------------------------------------------------------+
//| PendingOrderStatsDemo.mq5 |
//| Copyright 2026, FX-EA System Project Creator |
//| https://creator.fx-ea-system-project.com/ |
//+------------------------------------------------------------------+
#property copyright "Copyright 2026, FX-EA System Project Creator"
#property link "https://creator.fx-ea-system-project.com/"
#property version "1.00"
#property script_show_inputs
input int LookbackDays = 30;//集計対象にする過去の日数
//+------------------------------------------------------------------+
//| 指値・逆指値注文(ORDER_TYPE_LIMIT/STOP系)かどうかを判定する |
//+------------------------------------------------------------------+
bool IsPendingOrderType(ENUM_ORDER_TYPE type)
{
return(type==ORDER_TYPE_BUY_LIMIT || type==ORDER_TYPE_SELL_LIMIT ||
type==ORDER_TYPE_BUY_STOP || type==ORDER_TYPE_SELL_STOP);
}
//+------------------------------------------------------------------+
//| Script program start function |
//+------------------------------------------------------------------+
void OnStart()
{
datetime toDate = TimeCurrent();
datetime fromDate = toDate - LookbackDays*86400;
if(!HistorySelect(fromDate, toDate))
{
Print("履歴の取得に失敗しました。エラーコード=", GetLastError());
return;
}
int total = 0;
int filled = 0;
int canceled = 0;
int expired = 0;
int other = 0;
int ordersTotal = HistoryOrdersTotal();
for(int i=0; i<ordersTotal; i++)
{
ulong ticket = HistoryOrderGetTicket(i);
if(ticket==0) continue;
ENUM_ORDER_TYPE type = (ENUM_ORDER_TYPE)HistoryOrderGetInteger(ticket, ORDER_TYPE);
if(!IsPendingOrderType(type)) continue;
total++;
ENUM_ORDER_STATE state = (ENUM_ORDER_STATE)HistoryOrderGetInteger(ticket, ORDER_STATE);
switch(state)
{
case ORDER_STATE_FILLED: filled++; break;
case ORDER_STATE_CANCELED: canceled++; break;
case ORDER_STATE_EXPIRED: expired++; break;
default: other++; break;
}
}
if(total==0)
{
PrintFormat("過去%d日間に指値・逆指値注文はありませんでした。", LookbackDays);
return;
}
PrintFormat("過去%d日間の指値・逆指値注文: 合計%d件", LookbackDays, total);
PrintFormat("約定: %d件(%.1f%%) キャンセル: %d件(%.1f%%) 失効: %d件(%.1f%%) その他: %d件",
filled, filled *100.0/total,
canceled, canceled*100.0/total,
expired, expired *100.0/total,
other);
}
//+------------------------------------------------------------------+
MetaEditorで実際に動かす手順
- MetaTrader 5の「ツール」→「MetaQuotes Language Editor」でMetaEditorを開く
- 「ファイル」→「新規作成」→「スクリプト」を選び、名前を「PendingOrderStatsDemo」にして作成する(中身は空のテンプレートで構いません)
- テンプレートの中身を全て削除し、上のコードを丸ごと貼り付ける
- F7キー(またはコンパイルボタン)でコンパイルし、「0 error(s), 0 warning(s)」になることを確認する
- チャートへドラッグ&ドロップし、`LookbackDays`を指定して「エキスパート」タブに集計結果が表示されることを確認する
コピペしてコンパイルが通らない・想定と違う結果になるときに確認すること
- `HistoryDealGetTicket()`と`HistoryOrderGetTicket()`を混同していないか(どちらも引数・戻り値の形が似ているため、DealのつもりでOrderの関数を呼んでいるコピペミスが起きやすい)
- 成行注文(`ORDER_TYPE_BUY`/`ORDER_TYPE_SELL`)まで含めて集計していないか(成行は基本的に即約定するため、指値・逆指値の分析に混ぜると約定率が不自然に高く見える。このサンプルは`IsPendingOrderType()`で意図的に除外している)
- `HistorySelect()`の戻り値(`bool`)を確認せずに`HistoryOrdersTotal()`を呼んでいないか(失敗時は0件として扱われ、エラーに気づきにくい)
- 集計結果が0件になる場合、`LookbackDays`の範囲にそもそも指値・逆指値注文を出していない可能性がある。デモ口座で意図的に成行から遠い価格の指値を出し、キャンセルしてから再実行すると動作確認しやすい
あわせて読みたい: 【MQL5入門】MarketInfo()は存在しない?SymbolInfoDouble/Integer/Stringでロット刻み・最小ストップ距離を取得する
まとめ
MQL5の履歴には「注文(Order)」と「約定(Deal)」
という2つの別々の記録があり、
指値・逆指値注文がキャンセル・失効した場合はDealが存在しません。
`HistoryOrderGetDouble()`/
`HistoryOrderGetInteger()`/
`HistoryOrderGetString()`を使えば、
約定の有無に関わらず注文そのものの履歴を追え、指値が刺さらなかった割合のような、
Dealだけでは分からない分析ができるようになります。
コメント