MQL4で書いていた「保有中と決済済みの注文を両方数えて勝率を出す」
処理を、そのままMQL5でやろうとして`OrderSelect(i, SELECT_BY_POS, MODE_HISTORY)`をMetaEditorに打ち込んだら、
そんな関数は存在しないというエラーが出た——MQL4からMQL5に移った人が最初に驚くポイントの一つです。
MQL5では約定履歴の取得方法そのものが別の設計に変わっています。
この記事では、MQL5で過去の約定履歴を取得する`HistorySelect()`と`HistoryDealGetDouble()`/`HistoryDealGetInteger()`の使い方を、
実際に勝率と純損益を集計して表示するスクリプトで解説します。
あわせて、MQL5特有の「発注と決済が別々の記録になる」ことで初心者がほぼ必ず踏む二重集計の罠も扱います。
MQL4とMQL5で履歴取得の設計がまったく違う
MQL4では、決済済みの注文は`OrderSelect(位置, SELECT_BY_POS, MODE_HISTORY)`のように、
保有中の注文と同じ`OrderSelect`系関数の第3引数を`MODE_HISTORY`に切り替えるだけで参照できました(詳しくはOrder系関数の記事を参照)。
MQL5にはこの`MODE_HISTORY`という概念自体がありません。
代わりに、次の2ステップに分かれた設計になっています。
②HistoryDealsTotal()/HistoryDealGetTicket()でチケット番号を取得し、HistoryDealGetDouble()等でそのチケットの詳細を取得する
`HistorySelect()`を呼ばずにいきなり`HistoryDealGetDouble()`を呼んでも、
コンパイルエラーにも実行時エラーにもならず、静かに0が返ってくるだけなので気づきにくい落とし穴です。
必ず`HistorySelect()`を先に呼び、戻り値の`bool`で成功しているか確認してください。
HistorySelect関数の書式
bool HistorySelect( datetime from_date, //取得したい履歴の開始日時 datetime to_date //取得したい履歴の終了日時 );
指定した期間内の決済済み注文(deal)と発注済みの注文(order)を、
内部のキャッシュに読み込みます。
戻り値は成功したかどうかの`bool`です。
この後に呼ぶ`HistoryDealsTotal()`や`HistoryDealGetXxx()`系の関数は、
すべてこの`HistorySelect()`で選択した範囲のデータを対象に動きます。
deal単位でデータを取得する3つの関数
MQL5では、約定の記録1件を「deal(ディール)」と呼びます。
dealのチケット番号さえ分かれば、そのdealのあらゆる情報を次の3つの関数で取得できます。
double HistoryDealGetDouble(ulong ticket_number, ENUM_DEAL_PROPERTY_DOUBLE property);//数値(利益・スワップ等) long HistoryDealGetInteger(ulong ticket_number, ENUM_DEAL_PROPERTY_INTEGER property);//種別・時刻・マジックナンバー等 string HistoryDealGetString(ulong ticket_number, ENUM_DEAL_PROPERTY_STRING property);//通貨ペア・コメント等
この記事でよく使うプロパティは以下の通りです。
DEAL_PROFIT(double) — そのdeal単体の損益DEAL_SWAP(double) — そのdealのスワップDEAL_COMMISSION(double) — そのdealの手数料DEAL_ENTRY(long、実体はENUM_DEAL_ENTRY) — 新規(DEAL_ENTRY_IN)か決済(DEAL_ENTRY_OUT)かの区分DEAL_TYPE(long、実体はENUM_DEAL_TYPE) — 買い(DEAL_TYPE_BUY)・売り(DEAL_TYPE_SELL)・入出金(DEAL_TYPE_BALANCE)等の種別
最大の罠: 発注と決済は別々のdealとして記録される
MQL4の「1つの注文=1行」という感覚のままMQL5の履歴を数えると、
必ずつまずくポイントがあります。
MQL5では、ポジションを新規に持った瞬間の「発注」と、それを閉じた瞬間の「決済」
が、それぞれ独立した1件のdealとして記録されます。
つまり、1回の取引(エントリーから決済まで)は最低でも2件のdealになります。
→決済する(決済deal、DEAL_ENTRY_OUT、DEAL_PROFITに実際の損益)
このため、`HistoryDealsTotal()`で数えた件数をそのまま「取引回数」
として扱うと2倍になり、`DEAL_PROFIT`を全dealについて単純合計しても(発注deal側は通常0なので合計自体は狂いませんが)件数のズレに気づけないまま「勝率」
を計算すると、発注deal(損益0)まで母数に含めてしまい正しい勝率が出ません。
**決済(`DEAL_ENTRY_OUT`)側のdealだけを数える**のが正しいやり方です。
さらに、入金・出金などの資金操作も`DEAL_TYPE_BALANCE`という種別のdealとして同じ履歴に混ざって記録されます。
取引の集計をするときは、`DEAL_TYPE`が`DEAL_TYPE_BUY`または`DEAL_TYPE_SELL`であることも合わせて確認し、
資金操作を除外する必要があります。
実際に動くコード全体
直近N日間の決済済み取引だけを正しく絞り込んで、決済件数・勝ち数・勝率・純損益(利益+スワップ+手数料の合計)を計算して表示するスクリプトです。
//+------------------------------------------------------------------+
//| HistorySelectWinRate.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;//さかのぼって集計する日数
//+------------------------------------------------------------------+
//| Script program start function |
//+------------------------------------------------------------------+
void OnStart()
{
datetime to = TimeCurrent();
datetime from = to - LookbackDays * 24 * 60 * 60;
if(!HistorySelect(from, to))
{
Print("HistorySelectに失敗しました。エラーコード=", GetLastError());
return;
}
int totalDeals = HistoryDealsTotal();
int closedCount = 0;
int winCount = 0;
double netProfit = 0;
for(int i = 0; i < totalDeals; i++)
{
ulong ticket = HistoryDealGetTicket(i);
if(ticket == 0)
continue;
//入出金(DEAL_TYPE_BALANCE)等の売買以外のdealを除外
ENUM_DEAL_TYPE dealType = (ENUM_DEAL_TYPE)HistoryDealGetInteger(ticket, DEAL_TYPE);
if(dealType != DEAL_TYPE_BUY && dealType != DEAL_TYPE_SELL)
continue;
//発注(IN)側のdealは損益が確定していないため、決済(OUT)側だけを集計する
ENUM_DEAL_ENTRY entry = (ENUM_DEAL_ENTRY)HistoryDealGetInteger(ticket, DEAL_ENTRY);
if(entry != DEAL_ENTRY_OUT)
continue;
double profit = HistoryDealGetDouble(ticket, DEAL_PROFIT);
double swap = HistoryDealGetDouble(ticket, DEAL_SWAP);
double commission = HistoryDealGetDouble(ticket, DEAL_COMMISSION);
double dealNet = profit + swap + commission;
netProfit += dealNet;
closedCount++;
if(dealNet > 0)
winCount++;
}
double winRate = (closedCount > 0) ? (double)winCount / closedCount * 100.0 : 0.0;
Print("直近", LookbackDays, "日: 決済件数=", closedCount,
" 勝ち=", winCount,
" 勝率=", DoubleToString(winRate, 1), "%",
" 純損益=", DoubleToString(netProfit, 2));
}
//+------------------------------------------------------------------+
MetaEditorで実際に動かす手順
- MT5の「ツール」→「MetaQuotes Language Editor」でMetaEditorを開く
- 「ファイル」→「新規作成」→「スクリプト」を選び、名前を「HistorySelectWinRate」にして作成する(中身は空のテンプレートで構いません)
- テンプレートの中身を全て削除し、上のコードを丸ごと貼り付ける
- F7キー(またはコンパイルボタン)でコンパイルし、「0 error(s), 0 warning(s)」になることを確認する
- MT5の「ナビゲーター」パネルからスクリプトを探し、チャートへドラッグ&ドロップする(入力ダイアログが出るのでLookbackDaysを確認して「OK」)
- MT5下部の「エキスパート」タブに「直近30日: 決済件数=… 勝率=…% 純損益=…」のようなログが表示されれば成功です
コピペしてコンパイルが通らない・値がおかしいときに確認すること
- 拡張子が「.mq5」になっているか(このコードはMQL5専用で、MQL4環境ではコンパイルできません)
- `HistorySelect()`の戻り値を確認せずに`HistoryDealGetDouble()`を呼んでいないか(選択に失敗していると全て0が返り、エラーが出ないまま気づきにくい)
- 決済件数が想定より多い場合、`DEAL_ENTRY_OUT`の絞り込みが抜けていて発注(IN)側のdealまで数えていないか
- 純損益が想定とズレる場合、`DEAL_TYPE_BALANCE`(入出金)を除外できているか
まとめ
MQL5の履歴取得は、MQL4の`OrderSelect(MODE_HISTORY)`とは別物の`HistorySelect()`+`HistoryDealGetXxx()`という設計です。
最大の注意点は、1回の取引が「発注」と「決済」という2つの別々のdealとして記録されることで、
`DEAL_ENTRY_OUT`(決済側)と`DEAL_TYPE`(売買のみ)で正しく絞り込まないと、
件数も損益も二重・過大に集計してしまいます。
この記事のコードをベースに期間や通貨ペアの絞り込みを足せば、
実運用中のEAの成績集計にもそのまま応用できます。
コメント