MQL5で成行注文を送るとき、
前回の記事ではMqlTradeRequest構造体にactionやsymbol、
type_fillingまで1つずつフィールドを埋めてからOrderSend(request, result)を呼ぶ、
という手順を解説しました。
これは公式の書き方として間違いではありませんが、
毎回同じ初期化コードを書くのは正直面倒ですし、
フィールドを1つ埋め忘れただけで発注が拒否される、というミスも起きがちです。
実はMQL5の標準ライブラリには、この構造体を毎回自分で組み立てなくても、
Buy()やSell()のようなメソッドを1行呼ぶだけで発注できるCTradeクラスが最初から用意されています。
この記事では、CTradeクラスを使って発注・決済を簡潔に書く方法を、
生のOrderSend()との対比で解説します。
CTradeクラスとは何か
CTradeは、MQL5をインストールするとInclude\Trade\Trade.mqhに標準で同梱されているクラスです。
中身は結局のところMqlTradeRequest/MqlTradeResultを組み立ててOrderSend()を呼んでいるだけなのですが、
その組み立て部分をクラスの内部に隠蔽し、
開発者からはBuy()/Sell()/PositionClose()のようなメソッド呼び出しだけで済むようにしたものです。
“`#include #includeでヘッダーを読み込み、
CTrade型の変数(インスタンス)を1つ用意するだけで準備が完了します。
構造体を毎回={}で初期化する手間がなくなる点が、
まず生のOrderSend()との一番の違いです。
発注前に一度だけ設定する項目
CTradeインスタンスには、発注のたびに指定しなくてよいように、
あらかじめ設定しておけるプロパティがいくつかあります。
OnInit()など、EAの初期化処理の中で1回呼んでおくのが定石です。
void OnInit()
{
trade.SetExpertMagicNumber(123456); //マジックナンバー
trade.SetDeviationInPoints(10); //許容スリッページ(ポイント単位)
trade.SetTypeFilling(ORDER_FILLING_FOK); //注文執行方式
trade.SetTypeFillingBySymbol(_Symbol); //銘柄の対応状況から執行方式を自動選択(推奨)
}
生のOrderSend()ではrequest.magicやrequest.type_fillingを毎回のリクエストに書いていましたが、
CTradeではこれらを一度セットしておけば、
以降のBuy()/Sell()呼び出しすべてに自動的に適用されます。
特にSetTypeFillingBySymbol()は、
その銘柄・ブローカーが対応している執行方式を自動判定してくれるメソッドで、
前回の記事で扱った「retcode 10030(Unsupported filling mode)」のようなミスを未然に防ぎやすくなります。
主要メソッド: Buy() / Sell() / PositionClose()
発注・決済に使う主要メソッドは以下のとおりです。
CTradeの主なメソッド bool Buy(double volume, string symbol=NULL, double price=0, double sl=0, double tp=0, string comment=NULL) bool Sell(double volume, string symbol=NULL, double price=0, double sl=0, double tp=0, string comment=NULL) bool PositionClose(string symbol, ulong deviation=ULONG_MAX) bool PositionClose(ulong ticket, ulong deviation=ULONG_MAX) bool PositionModify(string symbol, double sl, double tp) bool OrderSend(const MqlTradeRequest &request, MqlTradeResult &result) //生のリクエストも渡せる
Buy()/Sell()はvolume(ロット数)だけが必須引数で、
symbolを省略すると現在のチャートの通貨ペアが使われ、
priceを省略(0のまま)すると現在の成行価格で自動発注されます。
生のOrderSend()ではrequest.price = SymbolInfoDouble(_Symbol, SYMBOL_ASK)のように自分で価格を取得する必要がありましたが、
CTradeではその処理も内部で行われます。
//成行買い、0.01ロット、SL/TPは価格から100ポイント分
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
double sl = ask - 100 * _Point;
double tp = ask + 100 * _Point;
if(!trade.Buy(0.01, _Symbol, 0, sl, tp))
{
Print("Buy()が失敗しました。retcode=", trade.ResultRetcode());
return;
}
生のOrderSend()ではMqlTradeRequest・MqlTradeResultという2つの構造体を用意し、
action/symbol/volume/type/price/sl/tp/deviation/magic/type_fillingと10個近いフィールドを埋めてから呼び出す必要がありました。
CTradeのBuy()は、それと同じ処理を1行にまとめています。
決済も同様に、ポジションを保有している通貨ペア名かチケット番号を渡すだけです。
//保有中のUSDJPYポジションを決済
if(!trade.PositionClose(_Symbol))
{
Print("PositionClose()が失敗しました。retcode=", trade.ResultRetcode());
}
前回の記事(position-select.md)で扱った「決済はtypeを反対売買にしてpositionフィールドにチケットを指定する」という手順も、
CTradeではPositionClose()の呼び出し1行に置き換わります。
結果の確認: ResultRetcode()を必ずチェックする
Buy()/Sell()がtrueを返しても、
生のOrderSend()と同じく「サーバーに送信できた」ことを保証するだけで、
実際に約定したかどうかは別途確認が必要です。
CTradeでは、直前の取引結果をResultRetcode()やResultRetcodeDescription()で取得できます。
if(!trade.Buy(0.01))
{
PrintFormat("Buy()自体が失敗しました。retcode=%d (%s)",
trade.ResultRetcode(), trade.ResultRetcodeDescription());
return;
}
if(trade.ResultRetcode() != TRADE_RETCODE_DONE)
{
PrintFormat("注文は拒否されました。retcode=%d (%s)",
trade.ResultRetcode(), trade.ResultRetcodeDescription());
return;
}
PrintFormat("成行買い成立: order=%d deal=%d price=%.5f",
(int)trade.ResultOrder(), (int)trade.ResultDeal(), trade.ResultPrice());
生のOrderSend()ではresult.retcodeやresult.commentのようにMqlTradeResult構造体のフィールドを直接参照していましたが、
CTradeではResultRetcode()/ResultRetcodeDescription()/ResultOrder()/ResultDeal()/ResultPrice()といったメソッド呼び出しに置き換わります。
ResultRetcodeDescription()はretcodeの数値だけでなく人間が読める説明文字列も返してくれるため、
ログを見たときに原因を把握しやすくなる点は生の構造体にはない利点です。
CTradeを使うと安全になる理由、生OrderSendで陥りがちなミス
生のOrderSend()を直接使う場合、
MqlTradeRequestを={}で0初期化し忘れる、
actionフィールドの指定漏れ、
type_fillingにブローカー非対応の値を指定してしまう、
といった「構造体を手組みするからこそ起きるミス」が発生します。
特に決済処理では、request.positionにチケット番号を指定し忘れたままTRADE_ACTION_DEALを送ってしまい、
決済のつもりが新規の逆ポジションを建ててしまう、という事故が起きやすいポイントです。
CTradeのPositionClose()は、
内部で該当ポジションのチケットを自動的に解決してから決済リクエストを組み立てるため、
この種の「決済のつもりが新規発注になっていた」というミスが構造的に起きません。
もちろんCTradeは万能ではなく、
複雑な条件分岐(部分決済の数量計算、複数の予約注文を組み合わせた両建て戦略など)を書く場合は、
結局内部で何が起きているかを理解しておく必要があります。
その意味で、前回の記事で解説した生のOrderSend()とMqlTradeRequest構造体の知識は無駄になりません。
「まずCTradeで書き、CTradeのメソッドでは表現できない特殊なリクエストが必要になったときだけ生のOrderSend()に降りる」という使い分けが実務的です。
実際に動くコード全体
ここまでの内容をまとめた、成行買い注文を1回送信し、保有ポジションがあれば決済する、
実際にMetaEditorでコンパイルできるスクリプトです。
実際に注文を送信・決済するコードのため、動作確認は必ずデモ口座で行ってください。
//+------------------------------------------------------------------+
//| CTradeDemo.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_confirm
#include <Trade\Trade.mqh>
input double Lots = 0.01;//ロット数
input int SlPoints = 100;//損切り幅(ポイント)
input int TpPoints = 100;//利確幅(ポイント)
input int Deviation = 10;//許容スリッページ(ポイント)
input long MagicNumber = 123456;//マジックナンバー
input bool CloseAfter = false;//trueにすると発注直後に同じ銘柄のポジションを決済する
CTrade trade;
//+------------------------------------------------------------------+
//| Script program start function |
//+------------------------------------------------------------------+
void OnStart()
{
trade.SetExpertMagicNumber(MagicNumber);
trade.SetDeviationInPoints(Deviation);
trade.SetTypeFillingBySymbol(_Symbol);//銘柄が対応する執行方式を自動選択
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
double sl = ask - SlPoints * _Point;
double tp = ask + TpPoints * _Point;
if(!trade.Buy(Lots, _Symbol, 0, sl, tp))
{
PrintFormat("Buy()自体が失敗しました。retcode=%d (%s)",
trade.ResultRetcode(), trade.ResultRetcodeDescription());
return;
}
if(trade.ResultRetcode() != TRADE_RETCODE_DONE)
{
PrintFormat("注文は拒否されました。retcode=%d (%s)",
trade.ResultRetcode(), trade.ResultRetcodeDescription());
return;
}
PrintFormat("成行買い成立: order=%d deal=%d price=%.5f",
(int)trade.ResultOrder(), (int)trade.ResultDeal(), trade.ResultPrice());
if(CloseAfter)
{
if(!trade.PositionClose(_Symbol))
{
PrintFormat("PositionClose()が失敗しました。retcode=%d (%s)",
trade.ResultRetcode(), trade.ResultRetcodeDescription());
return;
}
Print("ポジションを決済しました。");
}
}
//+------------------------------------------------------------------+
MetaEditorで実際に動かす手順
- MetaTrader 5の「ツール」→「MetaQuotes Language Editor」でMetaEditorを開く
- 「ファイル」→「新規作成」→「スクリプト」を選び、名前を「CTradeDemo」にして作成する(中身は空のテンプレートで構いません)
- テンプレートの中身を全て削除し、上のコードを丸ごと貼り付ける
- F7キー(またはコンパイルボタン)でコンパイルし、「0 error(s), 0 warning(s)」になることを確認する(`#include
`はMT5に標準同梱のため、追加のダウンロードは不要です) - MT5でデモ口座を選択していることを確認したうえで、ナビゲーターパネルからスクリプトをチャートへドラッグ&ドロップする(入力パラメーターの確認画面が出るので、決済まで試したい場合は
CloseAfterをtrueにしてOKを押す) - 「表示」→「ツールボックス」の「取引」タブに新規ポジションが表示されれば成功です
よくあるエラーと対処
- コンパイルエラー「’Trade.mqh’ file not found」:
#includeのパス区切りが正しいか確認してください。`#include`のように、Windows形式のバックスラッシュを使う必要があります(スラッシュでも動く場合がありますが、公式サンプルに合わせてバックスラッシュを推奨します) - retcodeが「10030(Unsupported filling mode)」:
SetTypeFillingBySymbol()を呼ばずにSetTypeFilling()で固定の執行方式を指定していると起きやすいミスです。特別な理由がなければSetTypeFillingBySymbol(_Symbol)を使ってください PositionClose()がfalseを返すのに保有ポジションはある: ヘッジ口座で同じ通貨ペアに複数ポジションがある場合、PositionClose(string symbol)はどれか一方しか決済できないことがあります。前回の記事(position-select.md)で解説したPositionGetTicket()でチケットを特定し、PositionClose(ulong ticket)のチケット版を使ってくださいtrade.ResultRetcode()が常に0を返す:Buy()/Sell()を一度も呼んでいない状態でResultRetcode()だけ呼んでも意味のある値は返りません。必ず発注メソッドの直後に確認してください
【MQL5入門】OrderSendの構造体を毎回書くのは冗長?CTradeクラスのBuy()/Sell()で発注を1行にする方法:まとめ
MQL5のCTradeクラスを使うと、
生のOrderSend()で毎回必要だったMqlTradeRequest/MqlTradeResult構造体の組み立てが、
Buy()/Sell()/PositionClose()のメソッド1行に置き換わります。
SetExpertMagicNumber()やSetTypeFillingBySymbol()で発注前の共通設定をまとめて済ませられる点、
ResultRetcodeDescription()で人間が読めるエラー説明を取得できる点も、
生の構造体にはない実務上のメリットです。
一方で、CTradeのメソッドで表現しきれない特殊な発注(複雑な部分決済・両建て戦略など)では、
結局は前回の記事で扱った生のOrderSend()とMqlTradeRequest構造体の知識が必要になります。
まずはCTradeで書き始め、必要になったときだけ生のOrderSend()に降りる、
という使い分けを意識してください。
コメント