MQL4の取引関数を体系的にまとめました。OrderSend()による成行・指値・逆指値注文、OrderClose()による決済、OrderModify()によるSL/TP変更、OrderSelect()とOrdersTotal()によるポジション管理ループ、ロット計算、トレーリングストップ、部分決済、マジックナンバーによる複数ロジック管理まで、MT4 EA開発に必要な取引処理のすべてをコード付きで解説します。
MQL4とMQL5の取引モデルの違い
MQL4の取引モデルはシンプルで直感的です。MQL5では構造体ベースの複雑なモデルに変わりましたが、MQL4では関数に引数を直接渡す方式です。
| 機能 | MQL4 | MQL5 |
|---|---|---|
| 発注 | OrderSend()に引数を直接渡す | MqlTradeRequest構造体を使用 |
| 決済 | OrderClose(ticket, lot, price, slippage) | 反対売買(CTrade.PositionClose) |
| SL/TP変更 | OrderModify(ticket, …) | MqlTradeRequest + TRADE_ACTION_SLTP |
| ポジション管理 | OrderSelect() + OrdersTotal()ループ | PositionGetTicket() + PositionsTotal() |
| 注文タイプ | OP_BUY, OP_SELL等(6種類) | ORDER_TYPE_BUY等(8種類) |
| マジックナンバー | OrderSendの引数 | MqlTradeRequest.magic |
| ヘッジング | 標準対応 | 口座設定による |
MQL4の取引関数はMQL5では全く異なる関数に置き換わっています。OrderClose()やOrderModify()はMQL5には存在しません。MQL4のコードをMQL5に移植する際は、取引部分を全面的に書き直す必要があります。
OrderSend — 新規注文
MQL4のOrderSend()は1つの関数で成行注文・指値注文・逆指値注文すべてを処理します。
関数シグネチャ
int OrderSend(
string symbol, // 通貨ペア
int cmd, // 注文タイプ(OP_BUY等)
double volume, // ロット数
double price, // 注文価格
int slippage, // 許容スリッページ(ポイント)
double stoploss, // ストップロス価格
double takeprofit, // テイクプロフィット価格
string comment = NULL, // コメント(任意)
int magic = 0, // マジックナンバー(任意)
datetime expiration = 0, // 有効期限(任意、指値注文用)
color arrow_color = clrNONE // チャート上の矢印色(任意)
);
// 戻り値: 成功時はチケット番号、失敗時は-1注文タイプ一覧
| 定数 | 値 | 説明 |
|---|---|---|
| OP_BUY | 0 | 成行買い |
| OP_SELL | 1 | 成行売り |
| OP_BUYLIMIT | 2 | 指値買い(現在値より下で待機) |
| OP_SELLLIMIT | 3 | 指値売り(現在値より上で待機) |
| OP_BUYSTOP | 4 | 逆指値買い(現在値より上で待機) |
| OP_SELLSTOP | 5 | 逆指値売り(現在値より下で待機) |
成行買い注文
int BuyOrder(double lots, int slPoints, int tpPoints)
{
double ask = Ask; // またはMarketInfo(Symbol(), MODE_ASK)
double sl = NormalizeDouble(ask - slPoints * Point, Digits);
double tp = NormalizeDouble(ask + tpPoints * Point, Digits);
int ticket = OrderSend(Symbol(), OP_BUY, lots, ask, 3, sl, tp,
"Buy Order", 12345, 0, clrBlue);
if(ticket < 0)
{
Print("買い注文失敗: ", GetLastError());
return -1;
}
Print("買い注文成功: ticket=", ticket);
return ticket;
}成行売り注文
int SellOrder(double lots, int slPoints, int tpPoints)
{
double bid = Bid;
double sl = NormalizeDouble(bid + slPoints * Point, Digits);
double tp = NormalizeDouble(bid - tpPoints * Point, Digits);
int ticket = OrderSend(Symbol(), OP_SELL, lots, bid, 3, sl, tp,
"Sell Order", 12345, 0, clrRed);
if(ticket < 0)
{
Print("売り注文失敗: ", GetLastError());
return -1;
}
Print("売り注文成功: ticket=", ticket);
return ticket;
}指値注文(Buy Limit / Sell Limit)
// Buy Limit: 現在値より下で買い待機
int BuyLimitOrder(double lots, double entryPrice, int slPoints, int tpPoints)
{
double sl = NormalizeDouble(entryPrice - slPoints * Point, Digits);
double tp = NormalizeDouble(entryPrice + tpPoints * Point, Digits);
int ticket = OrderSend(Symbol(), OP_BUYLIMIT, lots,
NormalizeDouble(entryPrice, Digits),
3, sl, tp, "BuyLimit", 12345, 0, clrBlue);
if(ticket < 0)
Print("BuyLimit失敗: ", GetLastError());
return ticket;
}
// Sell Limit: 現在値より上で売り待機
int SellLimitOrder(double lots, double entryPrice, int slPoints, int tpPoints)
{
double sl = NormalizeDouble(entryPrice + slPoints * Point, Digits);
double tp = NormalizeDouble(entryPrice - tpPoints * Point, Digits);
int ticket = OrderSend(Symbol(), OP_SELLLIMIT, lots,
NormalizeDouble(entryPrice, Digits),
3, sl, tp, "SellLimit", 12345, 0, clrRed);
if(ticket < 0)
Print("SellLimit失敗: ", GetLastError());
return ticket;
}逆指値注文(Buy Stop / Sell Stop)
// Buy Stop: 現在値より上で買い待機(ブレイクアウト狙い)
int BuyStopOrder(double lots, double entryPrice, int slPoints, int tpPoints)
{
double sl = NormalizeDouble(entryPrice - slPoints * Point, Digits);
double tp = NormalizeDouble(entryPrice + tpPoints * Point, Digits);
int ticket = OrderSend(Symbol(), OP_BUYSTOP, lots,
NormalizeDouble(entryPrice, Digits),
3, sl, tp, "BuyStop", 12345, 0, clrBlue);
if(ticket < 0)
Print("BuyStop失敗: ", GetLastError());
return ticket;
}
// Sell Stop: 現在値より下で売り待機(ブレイクダウン狙い)
int SellStopOrder(double lots, double entryPrice, int slPoints, int tpPoints)
{
double sl = NormalizeDouble(entryPrice + slPoints * Point, Digits);
double tp = NormalizeDouble(entryPrice - tpPoints * Point, Digits);
int ticket = OrderSend(Symbol(), OP_SELLSTOP, lots,
NormalizeDouble(entryPrice, Digits),
3, sl, tp, "SellStop", 12345, 0, clrRed);
if(ticket < 0)
Print("SellStop失敗: ", GetLastError());
return ticket;
}- 成行注文のpriceにはAsk(買い)/ Bid(売り)を使う。逆にするとエラー(ERR_INVALID_PRICE)
- SL/TPはNormalizeDouble()で正規化が必須
- ECN口座ではSL/TPを0にして発注後、OrderModify()で設定する必要がある場合あり
- スリッページはポイント単位(5桁ブローカーなら3=0.3pips)
- expirationを指定する場合、ブローカーが有効期限付き注文をサポートしている必要がある
OrderClose / OrderCloseBy — ポジション決済
OrderClose
bool OrderClose(
int ticket, // 注文チケット番号
double lots, // 決済ロット数
double price, // 決済価格
int slippage, // 許容スリッページ
color arrow_color = clrNONE // 矢印色
);
// 戻り値: 成功=true, 失敗=false// 買いポジションの決済
bool CloseBuyOrder(int ticket)
{
if(!OrderSelect(ticket, SELECT_BY_TICKET))
{
Print("チケット選択失敗: ", ticket);
return false;
}
// 買いポジションはBidで決済
bool result = OrderClose(ticket, OrderLots(), Bid, 3, clrBlue);
if(!result)
Print("決済失敗: ", GetLastError());
else
Print("決済成功: ticket=", ticket);
return result;
}
// 売りポジションの決済
bool CloseSellOrder(int ticket)
{
if(!OrderSelect(ticket, SELECT_BY_TICKET))
return false;
// 売りポジションはAskで決済
return OrderClose(ticket, OrderLots(), Ask, 3, clrRed);
}OrderCloseBy — 両建て決済
bool OrderCloseBy(
int ticket, // 決済する注文チケット
int opposite, // 反対ポジションのチケット
color arrow_color = clrNONE
);
// 同じ通貨ペアの反対ポジション同士を相殺決済するOrderCloseBy()はスプレッド節約に有効です。通常の決済ではスプレッドが発生しますが、両建て決済ではスプレッドが1回分で済みます。
OrderModify — SL/TP・待機注文の変更
bool OrderModify(
int ticket, // チケット番号
double price, // 新しい注文価格(成行ポジションの場合はOrderOpenPrice())
double stoploss, // 新しいSL
double takeprofit, // 新しいTP
datetime expiration, // 新しい有効期限(0=変更なし)
color arrow_color = clrNONE
);
// 戻り値: 成功=true, 失敗=false// 成行ポジションのSL/TPを変更
bool ModifySLTP(int ticket, double newSL, double newTP)
{
if(!OrderSelect(ticket, SELECT_BY_TICKET))
return false;
// 成行ポジションではpriceにOrderOpenPrice()を渡す
bool result = OrderModify(ticket, OrderOpenPrice(),
NormalizeDouble(newSL, Digits),
NormalizeDouble(newTP, Digits),
0, clrYellow);
if(!result)
Print("SL/TP変更失敗: ", GetLastError());
return result;
}
// 待機注文の価格を変更
bool ModifyPendingPrice(int ticket, double newPrice, double newSL, double newTP)
{
if(!OrderSelect(ticket, SELECT_BY_TICKET))
return false;
return OrderModify(ticket, NormalizeDouble(newPrice, Digits),
NormalizeDouble(newSL, Digits),
NormalizeDouble(newTP, Digits),
0, clrYellow);
}- 成行ポジションのpriceにはOrderOpenPrice()を渡す(変更不可のため元の値を返す)
- SL/TPを変更しない場合も、現在の値を渡す必要がある(0にするとSL/TPが削除される)
- 同じ値で再度OrderModify()を呼ぶとERR_NO_RESULT(1)エラーになる
- StopLevel(MODE_STOPLEVEL)より近い位置にSL/TPは設定できない
OrderDelete — 待機注文の削除
bool OrderDelete(
int ticket, // チケット番号
color arrow_color = clrNONE
);
// 待機注文(Limit/Stop)を削除する。成行ポジションには使用不可void DeleteAllPendingOrders(int magic)
{
for(int i = OrdersTotal() - 1; i >= 0; i--)
{
if(!OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
continue;
if(OrderSymbol() != Symbol() || OrderMagicNumber() != magic)
continue;
if(OrderType() > OP_SELL) // 2以上=待機注文
{
if(!OrderDelete(OrderTicket()))
Print("待機注文削除失敗: ", GetLastError());
}
}
}OrderSelect / OrdersTotal — ポジション管理ループ
MQL4のポジション管理で最も重要なパターンは、OrdersTotal()とOrderSelect()を使ったループです。
OrderSelect
bool OrderSelect(
int index, // インデックスまたはチケット番号
int select, // SELECT_BY_POS または SELECT_BY_TICKET
int pool = MODE_TRADES // MODE_TRADES(保有中)またはMODE_HISTORY(履歴)
);
// OrderSelect成功後に各種Order情報関数が使用可能になる保有ポジションのループ
void ScanOpenOrders(int magic)
{
int buyCount = 0, sellCount = 0;
double totalProfit = 0;
// 逆順ループ(決済時にインデックスがずれるのを防ぐ)
for(int i = OrdersTotal() - 1; i >= 0; i--)
{
if(!OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
continue;
// 通貨ペアとマジックナンバーでフィルタ
if(OrderSymbol() != Symbol())
continue;
if(OrderMagicNumber() != magic)
continue;
// ポジション情報を取得
int type = OrderType();
int ticket = OrderTicket();
double lots = OrderLots();
double profit = OrderProfit() + OrderSwap() + OrderCommission();
double openPrice = OrderOpenPrice();
totalProfit += profit;
if(type == OP_BUY)
{
buyCount++;
Print("BUY: ticket=", ticket, " lots=", lots, " profit=", profit);
}
else if(type == OP_SELL)
{
sellCount++;
Print("SELL: ticket=", ticket, " lots=", lots, " profit=", profit);
}
}
Print("合計: Buy=", buyCount, " Sell=", sellCount, " Profit=", totalProfit);
}- 逆順ループ(i–)が必須:決済するとインデックスがずれるため、順方向ループだとポジションを飛ばしてしまう
- Symbol()とMagicNumber()のフィルタは必ず入れる(他のEAや手動注文を操作しないため)
- OrderProfit()だけでなく、OrderSwap()とOrderCommission()も加算して正確な損益を計算する
Order情報取得関数一覧
| 関数 | 戻り値 | 説明 |
|---|---|---|
| OrderTicket() | int | チケット番号 |
| OrderType() | int | 注文タイプ(OP_BUY=0, OP_SELL=1等) |
| OrderSymbol() | string | 通貨ペア |
| OrderLots() | double | ロット数 |
| OrderOpenPrice() | double | 約定価格 |
| OrderClosePrice() | double | 決済価格(保有中は現在価格) |
| OrderOpenTime() | datetime | 約定時刻 |
| OrderCloseTime() | datetime | 決済時刻(保有中は0) |
| OrderStopLoss() | double | SL価格 |
| OrderTakeProfit() | double | TP価格 |
| OrderProfit() | double | 損益(スワップ・手数料除く) |
| OrderSwap() | double | スワップポイント |
| OrderCommission() | double | 手数料 |
| OrderMagicNumber() | int | マジックナンバー |
| OrderComment() | string | コメント |
| OrderExpiration() | datetime | 有効期限 |
OrderHistoryTotal — 取引履歴
void AnalyzeHistory(int magic, datetime fromDate)
{
int wins = 0, losses = 0;
double totalWin = 0, totalLoss = 0;
for(int i = OrdersHistoryTotal() - 1; i >= 0; i--)
{
if(!OrderSelect(i, SELECT_BY_POS, MODE_HISTORY))
continue;
if(OrderSymbol() != Symbol() || OrderMagicNumber() != magic)
continue;
if(OrderType() > OP_SELL) // 待機注文は除外
continue;
if(OrderCloseTime() < fromDate) // 期間フィルタ
continue;
double profit = OrderProfit() + OrderSwap() + OrderCommission();
if(profit >= 0)
{
wins++;
totalWin += profit;
}
else
{
losses++;
totalLoss += MathAbs(profit);
}
}
int total = wins + losses;
if(total > 0)
{
double winRate = (double)wins / total * 100.0;
double pf = (totalLoss > 0) ? totalWin / totalLoss : 0;
Print("勝率: ", DoubleToStr(winRate, 1), "% (",
wins, "勝", losses, "敗) PF=", DoubleToStr(pf, 2));
}
}ロット計算
リスク管理の基本は、1トレードあたりの損失を口座残高の1〜2%に抑えることです。ロット数はSL幅(ポイント)とTickValueから逆算します。固定ロットは絶対に避け、必ずリスクベースで計算しましょう。
double CalcLotSize(double riskPercent, int slPoints)
{
double balance = AccountBalance();
double riskAmount = balance * riskPercent / 100.0;
double tickValue = MarketInfo(Symbol(), MODE_TICKVALUE);
double tickSize = MarketInfo(Symbol(), MODE_TICKSIZE);
double minLot = MarketInfo(Symbol(), MODE_MINLOT);
double maxLot = MarketInfo(Symbol(), MODE_MAXLOT);
double lotStep = MarketInfo(Symbol(), MODE_LOTSTEP);
if(tickValue == 0 || slPoints == 0)
return minLot;
// 1ロットあたりのSL損失額を計算
double slMoney = slPoints * Point / tickSize * tickValue;
// リスク額からロット数を逆算
double lots = riskAmount / slMoney;
// ロットステップに丸める
lots = MathFloor(lots / lotStep) * lotStep;
// 範囲制限
if(lots < minLot) lots = minLot;
if(lots > maxLot) lots = maxLot;
return NormalizeDouble(lots, 2);
}トレーリングストップ
void TrailingStop(int magic, int trailPoints, int trailStart)
{
for(int i = OrdersTotal() - 1; i >= 0; i--)
{
if(!OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
continue;
if(OrderSymbol() != Symbol() || OrderMagicNumber() != magic)
continue;
double newSL;
double currentSL = OrderStopLoss();
int stopLevel = (int)MarketInfo(Symbol(), MODE_STOPLEVEL);
double minDist = stopLevel * Point;
if(OrderType() == OP_BUY)
{
// 含み益がtrailStart以上あるときだけ発動
if(Bid - OrderOpenPrice() < trailStart * Point)
continue;
newSL = NormalizeDouble(Bid - trailPoints * Point, Digits);
// 現在のSLより上にある場合のみ更新
if(newSL > currentSL + Point && Bid - newSL > minDist)
{
if(!OrderModify(OrderTicket(), OrderOpenPrice(), newSL,
OrderTakeProfit(), 0, clrBlue))
Print("トレーリング失敗: ", GetLastError());
}
}
else if(OrderType() == OP_SELL)
{
if(OrderOpenPrice() - Ask < trailStart * Point)
continue;
newSL = NormalizeDouble(Ask + trailPoints * Point, Digits);
if((currentSL == 0 || newSL < currentSL - Point) && newSL - Ask > minDist)
{
if(!OrderModify(OrderTicket(), OrderOpenPrice(), newSL,
OrderTakeProfit(), 0, clrRed))
Print("トレーリング失敗: ", GetLastError());
}
}
}
}部分決済
bool PartialClose(int ticket, double closePercent)
{
if(!OrderSelect(ticket, SELECT_BY_TICKET))
return false;
double totalLots = OrderLots();
double closeLots = NormalizeDouble(totalLots * closePercent / 100.0, 2);
double minLot = MarketInfo(Symbol(), MODE_MINLOT);
double lotStep = MarketInfo(Symbol(), MODE_LOTSTEP);
// ロットステップに丸める
closeLots = MathFloor(closeLots / lotStep) * lotStep;
// 最小ロット未満なら全決済
if(closeLots < minLot)
closeLots = totalLots;
double price = (OrderType() == OP_BUY) ? Bid : Ask;
bool result = OrderClose(ticket, closeLots, price, 3, clrYellow);
if(!result)
Print("部分決済失敗: ", GetLastError());
else
Print("部分決済成功: ", closeLots, " / ", totalLots, " lots");
return result;
}- 部分決済すると新しいチケット番号が発行される(残りのポジションは別チケットになる)
- closeLots が minLot 未満の場合は全決済にフォールバックする
- ブローカーによっては部分決済がサポートされていない場合がある
マジックナンバーによる複数ロジック管理
1つのEAで複数のロジックを管理する場合、マジックナンバーでポジションを識別します。
#define MAGIC_LOGIC_A 100001 // ロジックA
#define MAGIC_LOGIC_B 100002 // ロジックB
#define MAGIC_LOGIC_C 100003 // ロジックC
#define MAX_POSITIONS 3 // 全体の最大ポジション数
// 指定マジックナンバーのポジション数をカウント
int CountPositions(int magic)
{
int count = 0;
for(int i = OrdersTotal() - 1; i >= 0; i--)
{
if(!OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
continue;
if(OrderSymbol() != Symbol())
continue;
if(OrderMagicNumber() != magic)
continue;
if(OrderType() <= OP_SELL)
count++;
}
return count;
}
// 全ロジックの合計ポジション数
int CountAllPositions()
{
return CountPositions(MAGIC_LOGIC_A)
+ CountPositions(MAGIC_LOGIC_B)
+ CountPositions(MAGIC_LOGIC_C);
}
void OnTick()
{
// 最大ポジション数チェック
if(CountAllPositions() >= MAX_POSITIONS)
return;
// ロジックA: 条件チェック & エントリー
if(CountPositions(MAGIC_LOGIC_A) == 0 && LogicA_Signal())
{
OrderSend(Symbol(), OP_BUY, 0.1, Ask, 3, 0, 0,
"LogicA", MAGIC_LOGIC_A, 0, clrBlue);
}
// ロジックB: 条件チェック & エントリー
if(CountPositions(MAGIC_LOGIC_B) == 0 && LogicB_Signal())
{
OrderSend(Symbol(), OP_SELL, 0.1, Bid, 3, 0, 0,
"LogicB", MAGIC_LOGIC_B, 0, clrRed);
}
// ロジックC: 条件チェック & エントリー
if(CountPositions(MAGIC_LOGIC_C) == 0 && LogicC_Signal())
{
OrderSend(Symbol(), OP_BUY, 0.1, Ask, 3, 0, 0,
"LogicC", MAGIC_LOGIC_C, 0, clrGreen);
}
}ECN口座対応パターン
ECN口座ではOrderSend時にSL/TPを同時に設定できないブローカーがあります。その場合はStep1: SL/TPなしで発注 → Step2: OrderModifyでSL/TP設定の2段階で処理します。STP口座でも同様のエラーが出る場合があるため、汎用的にこのパターンを実装するのがベストプラクティスです。
int BuyOrderECN(double lots, int slPoints, int tpPoints, int magic)
{
// Step 1: SL/TPなしで発注
int ticket = OrderSend(Symbol(), OP_BUY, lots, Ask, 3,
0, 0, "ECN Buy", magic, 0, clrBlue);
if(ticket < 0)
{
Print("発注失敗: ", GetLastError());
return -1;
}
// Step 2: SL/TPを後から設定
if(slPoints > 0 || tpPoints > 0)
{
if(!OrderSelect(ticket, SELECT_BY_TICKET))
return ticket;
double openPrice = OrderOpenPrice();
double sl = (slPoints > 0) ? NormalizeDouble(openPrice - slPoints * Point, Digits) : 0;
double tp = (tpPoints > 0) ? NormalizeDouble(openPrice + tpPoints * Point, Digits) : 0;
// リトライ付きでSL/TP設定
for(int retry = 0; retry < 3; retry++)
{
if(OrderModify(ticket, openPrice, sl, tp, 0, clrBlue))
break;
Print("SL/TP設定リトライ: ", GetLastError());
Sleep(500);
}
}
return ticket;
}取引関数一覧
| MQL4関数 | MQL5対応 | 説明 |
|---|---|---|
| OrderSend() | OrderSend() + MqlTradeRequest | 新規注文(成行/指値/逆指値) |
| OrderClose() | CTrade::PositionClose() | ポジション決済 |
| OrderCloseBy() | CTrade::PositionCloseBy() | 両建て決済 |
| OrderModify() | OrderSend() + TRADE_ACTION_SLTP/MODIFY | SL/TP・待機注文変更 |
| OrderDelete() | CTrade::OrderDelete() | 待機注文の削除 |
| OrderSelect() | PositionSelectByTicket() / OrderGetTicket() | 注文を選択 |
| OrdersTotal() | PositionsTotal() / OrdersTotal() | 保有注文数 |
| OrdersHistoryTotal() | HistoryOrdersTotal() | 履歴注文数 |
| OrderTicket() | PositionGetTicket() | チケット番号 |
| OrderType() | PositionGetInteger(POSITION_TYPE) | 注文タイプ |
| OrderLots() | PositionGetDouble(POSITION_VOLUME) | ロット数 |
| OrderOpenPrice() | PositionGetDouble(POSITION_PRICE_OPEN) | 約定価格 |
| OrderProfit() | PositionGetDouble(POSITION_PROFIT) | 損益 |
| OrderMagicNumber() | PositionGetInteger(POSITION_MAGIC) | マジックナンバー |
| OrderStopLoss() | PositionGetDouble(POSITION_SL) | SL価格 |
| OrderTakeProfit() | PositionGetDouble(POSITION_TP) | TP価格 |
まとめ
MQL4の取引関数はMQL5と比べてシンプルで直感的です。OrderSend()に引数を直接渡すだけで注文でき、OrderClose()で決済、OrderModify()でSL/TP変更と、関数名が処理内容をそのまま表しています。
EA開発で特に重要なポイントは以下の3つです。
- ポジションループは逆順(i–)で回す — 決済時のインデックスずれを防ぐ
- Symbol()とMagicNumber()で必ずフィルタする — 他のEAや手動注文を誤操作しない
- NormalizeDouble()で価格を正規化する — 浮動小数点の誤差によるエラーを防ぐ
次のステップとして、MQLリファレンス総合ガイドから他のカテゴリも学習しましょう。インジケーター関数と組み合わせることで、実用的なEAを開発できます。





