Invalid stops(10016/130)を完全解決 — MT5/MT4でSL・TPが設定できない原因と直し方
目次
- Invalid stops とは(10016 と 130 の違い)
- ① TRADE_RETCODE_INVALID_STOPS = 10016(MT5 / `OrderSend()` のリターンコード)
- ② ERR_INVALID_STOPS = 130(MT4 / `GetLastError()`)
- まず30秒でできる切り分け
- 原因と対処(6パターン)
- ① SL/TPが現在価格に近すぎる(ストップレベル未満)
- ② SL/TPの向きが逆(BUY/SELLの取り違え)
- ③ フリーズレベル内での注文・ポジション変更
- ④ 価格とポイント(距離)の混同
- ⑤ 成行実行ブローカーでエントリー時のSL/TPが通らない
- ⑥ 銘柄固有の癖(ゴールド・指数はストップレベルが大きい)
- ブローカーによる違い(注意点)
- MQL5でこのエラーを防ぐコード(EA開発者向け)
- 発注前にSL/TPを検証・クランプする
- retcode 10016 を個別にハンドリングする
- トレール修正はフリーズレベルを見てからにする
- 優先度チェックリスト
- まとめ
- FAQ
- Q: SL/TPの計算は合っているはずなのに Invalid stops が出ます。なぜ?
- Q: 10016 と 130 はどう違いますか?
- Q: 普段は出ないのに、指標発表のときだけ Invalid stops になります。
- Q: SL/TPなしなら通るのに、SL/TP付きだと拒否されます。
- Q: EURUSDでは動くEAが、ゴールドだと Invalid stops を連発します。
Invalid stops(10016/130)を完全解決
EAを動かしていて、Expertタブに Invalid stops や OrderSend error 130 が出ると「SLの値がおかしい? でも計算は合っているはず…」と混乱しがちですが、このエラーの大半はSL/TPの計算ミスではなく「ブローカーが決めた最小距離ルール」への違反です。値そのものは正しくても、現在価格に近すぎる・向きが逆・修正禁止ゾーンに入っている、のどれかで拒否されます。
この記事は、MT5/MT4でEAを使う人と、MQL5でEAを書く人の両方に向けて、Invalid stops の正体・6つの原因・30秒の切り分け・コード側での恒久防止までを一本にまとめた決定版です。エラーコード全般の一覧は MQL5 / MT5 エラーコード対処法の総合ガイド を参照してください。
この記事は2026年7月時点のMT5(build 4xxx系)を前提にしています。ストップレベルの具体値はブローカー・銘柄ごとに異なります。
Invalid stops とは(10016 と 130 の違い)
「不正なストップ」を表す値は、MT5とMT4(世代)で2種類あります。どちらのプラットフォーム・どの段階で出たかを区別すると、原因究明が一気に早くなります。
① TRADE_RETCODE_INVALID_STOPS = 10016(MT5 / OrderSend() のリターンコード)
MQL5の OrderSend() の結果は MqlTradeResult.retcode に入ります。リクエスト内のSL/TP(またはペンディング注文の価格との関係)がサーバーのルールに合わないと、10016(TRADE_RETCODE_INVALID_STOPS) で拒否されます。これは取引サーバー側からの拒否通知です。
意味: リクエスト内のストップ(SL/TP)が不正(Invalid stops in the request)
定数: TRADE_RETCODE_INVALID_STOPS
値 : 10016
// ログで確認できる典型的な出力例
2026.07.07 09:15:32.441 EA_NAME XAUUSD,M5: OrderSend error: retcode=10016 (invalid stops)
② ERR_INVALID_STOPS = 130(MT4 / GetLastError())
MT4(MQL4)世代では、OrderSend() / OrderModify() が失敗した後の GetLastError() が 130(ERR_INVALID_STOPS) を返します。Expertタブの OrderSend error 130 はこれです。MT4版EAを併用している場合はこちらが出ます。
意味: 不正なストップ(invalid stops)
定数: ERR_INVALID_STOPS
値 : 130
実務上の使い分け:
| プラットフォーム | 取得元 | 値 | 出る場面 |
|---|---|---|---|
| MT5 | MqlTradeResult.retcode | 10016 (TRADE_RETCODE_INVALID_STOPS) | OrderSend / PositionModify がサーバーに拒否された |
| MT5 | CTrade.ResultRetcode() | 10016 | CTrade 経由の発注・修正拒否 |
| MT4 | GetLastError() | 130 (ERR_INVALID_STOPS) | OrderSend / OrderModify 失敗後 |
番号は違いますが意味も原因もほぼ同じで、対処は共通です。なお、MT5でよく混同される **10015(TRADE_RETCODE_INVALID_PRICE)は「注文価格そのものが不正」**で別物です。10016はあくまで「SL/TP(ストップ)の位置」の問題です。
まず30秒でできる切り分け
MT5の「気配値 → シンボル右クリック → 仕様」を開き、次の2項目を見てください。
ストップレベル (Stops level) : SL/TP を現在価格から離すべき最小距離(ポイント)
フリーズレベル (Freeze level) : 発動が近い注文の変更・取消が禁止される距離(ポイント)
そのうえで、エラーが出た瞬間のログに 送ろうとしたSL/TPの値と現在のBid/Ask を並べて確認します。
- SLやTPが現在価格からストップレベル未満の距離 → ほぼこれが原因(原因①)。
- BUYなのにSLがBidより上/TPがBidより下(SELLなら逆) → 向きの取り違え(原因②)。
- 保有ポジションの修正だけ失敗する → フリーズレベルか、SL/TP後付け仕様(原因③・⑤)。
- SLの値が「50」など明らかに価格でない数値 → 価格とポイントの混同(原因④)。
コードから確認するなら1行です。
Print("StopsLevel=", SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL),
" FreezeLevel=", SymbolInfoInteger(_Symbol, SYMBOL_TRADE_FREEZE_LEVEL));
原因と対処(6パターン)
① SL/TPが現在価格に近すぎる(ストップレベル未満)
症状: SLをタイトに置くスキャルピング系EA・トレール幅の小さいEAで頻発。手動でも「価格のすぐ近く」にSLを置こうとすると発注ボタンが押せない/拒否される。
原因: ブローカーは銘柄ごとに SYMBOL_TRADE_STOPS_LEVEL(ポイント単位の最小ストップ距離)を設定しており、現在価格からこの距離未満のSL/TP・ペンディング価格はサーバーが一律拒否します。検証の基準価格は、BUYポジションのSL/TPならBid、SELLなら**Askです。スプレッドが広がるとBidとAskの間隔が開くため、平常時は通る距離でも指標時・早朝に突然拒否されることがあります。
対処:
- 仕様ウィンドウでストップレベルを確認し、EAのSL/TP・トレール幅がそれ以上になるよう設定を広げる
- EA側では発注前にクランプする(後述のコード)
- どうしてもタイトなSLが必要なら、ストップレベルの小さいブローカー・口座タイプを検討する
② SL/TPの向きが逆(BUY/SELLの取り違え)
症状: 特定の方向(買いだけ/売りだけ)で必ず Invalid stops になる。自作EAの初回テストで最も多いパターン。
原因: ルールは単純で、BUYのSLは現在価格(Bid)より下・TPは上、SELLのSLは現在価格(Ask)より上・TPは下です。BUY用の計算式をSELLにコピーして符号を直し忘れる、price - sl と price + sl を取り違える、といった典型的なミスで、サーバーは即座に10016/130を返します。
対処:
- エラー時に
PrintFormat("type=%s sl=%.5f tp=%.5f bid=%.5f ask=%.5f", ...)で実値を出し、向きを目視確認する - SL/TP計算をBUY/SELLで共通関数化し、符号の分岐を1箇所にまとめる(コピペ分岐をなくす)
③ フリーズレベル内での注文・ポジション変更
症状: 新規は問題ないのに、TP直前・SL直前・ペンディング発動直前の変更/取消だけが拒否される。
原因: SYMBOL_TRADE_FREEZE_LEVEL が設定されている銘柄では、発動価格(TP/SL/ペンディングのトリガー価格)に現在価格が一定距離まで近づくと、その注文の変更・取消が凍結されます。約定処理と変更リクエストの競合を防ぐためのサーバー仕様で、EAのトレールが「TP間際にもう一段更新しようとして拒否される」形で顕在化します。
対処:
- 変更前に
SYMBOL_TRADE_FREEZE_LEVELを読み、発動価格までの距離がフリーズレベル以下ならその回の変更をスキップする - トレールの更新間隔・更新幅を広げ、発動間際の無駄な修正リクエストを減らす
- 拒否されても致命的ではない(発動間際=もうすぐ約定する)ので、エラーを握りつぶしてログだけ残す設計でよい
④ 価格とポイント(距離)の混同
症状: SLに 50 や 0.0050 のような「距離のつもりの数値」がそのまま入っている。ログに出る sl=50.00000 が明らかに価格でない。
原因: MqlTradeRequest.sl / .tp に入れるのは絶対価格です(「エントリーから50ポイント下」ではない)。距離で管理しているEAは entry ± distance * _Point に変換してから渡す必要があります。逆にMT4時代の一部関数感覚で絶対価格を渡すべき所に距離を渡すと、価格として成立せず10016/130になります。
さらにpipsとポイントの混同も定番です。5桁ブローカー(例: USDJPYが3桁・EURUSDが5桁表示)では 1 pip = 10ポイント。「SL 50」がpips想定かポイント想定かで距離が10倍ずれ、ストップレベル未満に潰れて拒否されることがあります。
対処:
- SL/TPは必ず
NormalizeDouble(price ± dist * _Point, _Digits)の形で組み立てる - 入力パラメーターの単位(pips / points)をコメントで明示し、内部で
_Point換算を1箇所に集約する
⑤ 成行実行ブローカーでエントリー時のSL/TPが通らない
症状: SL/TPなしなら約定するのに、SL/TP付きの新規注文だけ Invalid stops。特にECN/成行実行(Market Execution)系の口座で発生。
原因: 成行実行では「リクエスト時の価格」と「実際の約定価格」がずれるため、一部のサーバーは新規注文リクエスト内のSL/TPを受け付けず、約定後にポジション修正で設定する運用を要求します(MT4時代のECN口座でerror 130が多発した古典的な仕様で、MT5でも同様の挙動をするサーバーが残っています)。
対処:
- まずSL/TPなしで発注 → 約定確認後に
PositionModify()(CTradeならtrade.PositionModify())でSL/TPを付ける2段構えに変える - この方式では「発注は成功したがSL設定に失敗した」瞬間が生まれるため、SL設定が通るまでリトライし、規定回数失敗したら即決済する保護を必ず入れる(SLなし放置が最悪の結果)
- 実行方式は
SymbolInfoInteger(_Symbol, SYMBOL_TRADE_EXEMODE)で確認できる
⑥ 銘柄固有の癖(ゴールド・指数はストップレベルが大きい)
症状: 同じEAがEURUSDでは動くのに、XAUUSD(ゴールド)や株価指数CFDに載せた途端 Invalid stops を連発する。
原因: ストップレベルは銘柄ごとに別設定で、ゴールド・指数・エキゾチック通貨はメジャー通貨よりも大きく取られているのが普通です。メジャーFX用に調整したタイトなSL/トレール幅をそのまま流用すると、銘柄側の最小距離に届かず拒否されます。桁数も銘柄で違う(ゴールドは2〜3桁など)ため、_Digits を決め打ちしたコードも壊れます。
対処:
- 銘柄を変えたら必ず仕様ウィンドウでストップレベル・桁数を確認する
- SL/TP幅を固定ポイントでなくATR等のボラティリティ基準にすると、銘柄をまたいでも破綻しにくい
- コードは
_Point/_Digits/SYMBOL_TRADE_STOPS_LEVELを常に動的取得する(ハードコード禁止)
ブローカーによる違い(注意点)
ストップレベル・フリーズレベルはブローカーと銘柄の組み合わせごとに全く違います。同じEA・同じ設定でも、ブローカーAでは一度も出ないエラーがブローカーBでは毎日出る、が普通に起きます。
さらに注意すべきは、ストップレベルが「0」と表示されるブローカーです。0は「制限なし」ではなく「動的に判定する」ことが多く、平常時はどんなに近いSLも通るのに、指標発表時や早朝などスプレッドが広がった瞬間だけ拒否されます。「たまにしか出ない Invalid stops」の正体はたいていこれです。
確認は必ず実際の口座で行ってください。
long stops = SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL); // ポイント
long freeze = SymbolInfoInteger(_Symbol, SYMBOL_TRADE_FREEZE_LEVEL); // ポイント
具体的な数値は各ブローカーの口座タイプ・銘柄仕様に依存するため、この記事では挙げません。自分の口座で上のコードを実行した値が唯一の正解です。
MQL5でこのエラーを防ぐコード(EA開発者向け)
正しい設計は「エラーが出たら直す」ではなく、発注前にSL/TPをブローカーの最小距離にクランプして、そもそも10016を発生させないことです。
発注前にSL/TPを検証・クランプする
// SL/TP をストップレベル以上の距離にクランプしてから発注する
// 戻り値 false = 向きが逆(設計ミス)なので発注しない
bool ClampStops(ENUM_ORDER_TYPE type, double &sl, double &tp)
{
double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
long stopsPt = SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL);
double spread = SymbolInfoDouble(_Symbol, SYMBOL_ASK)
- SymbolInfoDouble(_Symbol, SYMBOL_BID);
// ストップレベル + スプレッド分の余裕(stops=0 の動的判定ブローカー対策)
double minDist = stopsPt * point + spread;
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
if(type == ORDER_TYPE_BUY)
{
// BUY の SL/TP は Bid 基準で検証される
if(sl > 0 && sl >= bid) return false; // 向きが逆
if(tp > 0 && tp <= bid) return false;
if(sl > 0 && (bid - sl) < minDist) sl = bid - minDist;
if(tp > 0 && (tp - bid) < minDist) tp = bid + minDist;
}
else if(type == ORDER_TYPE_SELL)
{
// SELL の SL/TP は Ask 基準で検証される
if(sl > 0 && sl <= ask) return false; // 向きが逆
if(tp > 0 && tp >= ask) return false;
if(sl > 0 && (sl - ask) < minDist) sl = ask + minDist;
if(tp > 0 && (ask - tp) < minDist) tp = ask - minDist;
}
sl = NormalizeDouble(sl, digits);
tp = NormalizeDouble(tp, digits);
return true;
}
ポイントは3つです。
SYMBOL_TRADE_STOPS_LEVELとSYMBOL_POINTを毎回動的取得する(銘柄・ブローカー非依存になる)- スプレッド分の余裕を上乗せする(ストップレベル0の動的判定ブローカーでも通りやすくなる)
- 最後に必ず
NormalizeDouble(価格, _Digits)で桁を揃える(余分な小数桁も拒否の原因になる)
retcode 10016 を個別にハンドリングする
拒否されたときに「何を送って、そのとき距離がいくつだったか」をログに残すと、原因①〜⑥のどれかが一目で分かります。
MqlTradeRequest req; MqlTradeResult res;
ZeroMemory(req); ZeroMemory(res);
// ... req を組み立て(sl/tp は ClampStops 済み) ...
if(!OrderSend(req, res) || res.retcode != TRADE_RETCODE_DONE)
{
if(res.retcode == TRADE_RETCODE_INVALID_STOPS) // 10016
PrintFormat("Invalid stops: sl=%s tp=%s bid=%s stopsLevel=%d",
DoubleToString(req.sl, _Digits),
DoubleToString(req.tp, _Digits),
DoubleToString(SymbolInfoDouble(_Symbol, SYMBOL_BID), _Digits),
(int)SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL));
else
PrintFormat("OrderSend failed: retcode=%d, lastError=%d", res.retcode, GetLastError());
}
トレール修正はフリーズレベルを見てからにする
// ポジション修正の前に、発動価格がフリーズゾーンに入っていないか確認
bool CanModify(double triggerPrice)
{
long freeze = SymbolInfoInteger(_Symbol, SYMBOL_TRADE_FREEZE_LEVEL);
if(freeze <= 0) return true;
double dist = MathAbs(SymbolInfoDouble(_Symbol, SYMBOL_BID) - triggerPrice);
return (dist > freeze * SymbolInfoDouble(_Symbol, SYMBOL_POINT));
}
MT4版EAの場合は MarketInfo(Symbol(), MODE_STOPLEVEL) / MODE_FREEZELEVEL で同じ値が取れます。考え方は完全に同じです。
FXEA365が配布しているEAは、この発注前のSL/TP検証・ストップレベルのクランプ・銘柄非依存の動的取得を標準実装しているため、ブローカーや銘柄を変えても Invalid stops で止まらない設計になっています。
優先度チェックリスト
| 優先 | 確認 | 対処 |
|---|---|---|
| 🚨 まず | SL/TPと現在価格の距離 < ストップレベルか | SL/TP幅を広げる/クランプ実装 |
| 🚨 まず | BUY/SELLでSL/TPの向きが逆でないか | ログに実値を出して目視確認 |
| ⚠️ 次 | 修正だけ失敗 → フリーズレベル内か | 発動間際の修正をスキップ |
| ⚠️ 次 | sl/tp に距離(ポイント)を入れていないか | 絶対価格 price ± dist*_Point に変換 |
| ✅ 確認 | 成行実行でSL/TP後付けが必要な口座か | 発注→PositionModify の2段構え |
| 🛠 開発 | 銘柄仕様を動的取得しているか | 上記 ClampStops を実装 |
まとめ
Invalid stopsは MT5 = 10016(TRADE_RETCODE_INVALID_STOPS)、MT4 = 130(ERR_INVALID_STOPS)。番号は違うが意味・対処は共通。- 原因の大半は計算ミスではなく、ストップレベル未満・向きの逆転・フリーズレベル・価格とポイントの混同・SL/TP後付け仕様・銘柄固有の癖の6つ。
- 開発者は「発注前に
SYMBOL_TRADE_STOPS_LEVELを読んでクランプ+NormalizeDouble」で恒久対策できる。ストップレベル0のブローカーもスプレッド分の余裕で吸収する。
エラーコード全般は MQL5 / MT5 エラーコード対処法の総合ガイド を参照してください。同じく発注が拒否される定番エラー「資金不足」については ERR_NO_MONEY(134/10019)の解説記事 にまとめています。
FAQ
Q: SL/TPの計算は合っているはずなのに Invalid stops が出ます。なぜ?
値の正しさではなく現在価格からの距離で拒否されている可能性が高いです。ブローカーの最小ストップ距離(ストップレベル)未満のSL/TPは、値が正確でも一律拒否されます。仕様ウィンドウか SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL) で最小距離を確認してください。
Q: 10016 と 130 はどう違いますか?
10016(TRADE_RETCODE_INVALID_STOPS)はMT5の OrderSend() / PositionModify() の結果コード、130(ERR_INVALID_STOPS)はMT4の GetLastError() が返す値です。プラットフォームが違うだけで、意味(SL/TPの位置が不正)と対処は同じです。
Q: 普段は出ないのに、指標発表のときだけ Invalid stops になります。
スプレッド拡大が原因です。BUYのSL/TPはBid基準・SELLはAsk基準で検証されるため、スプレッドが広がった瞬間だけ距離が足りなくなります。ストップレベルが0のブローカーでも、急変時だけ動的に拒否する挙動があります。SL/TP距離にスプレッド分の余裕を上乗せしてください(本文のコード参照)。
Q: SL/TPなしなら通るのに、SL/TP付きだと拒否されます。
成行実行(Market Execution)系の口座で、新規注文リクエスト内のSL/TPを受け付けないサーバーの可能性があります。SL/TPなしで発注し、約定後に PositionModify() で設定する2段構えに変更してください。ただしSL設定に失敗した場合のリトライと、失敗し続けた場合の即決済保護を必ず入れてください。
Q: EURUSDでは動くEAが、ゴールドだと Invalid stops を連発します。
ゴールド・株価指数はストップレベルがメジャー通貨より大きく設定されているのが普通で、桁数も異なります。SL/トレール幅を銘柄仕様に合わせて広げるか、ATR基準のボラティリティ連動幅に変更してください。
関連記事
2026-07-07
自動売買が無効(AutoTrading disabled)でEAが動かない — MT5の10027/10026を完全解決
2026-07-07
Unsupported filling mode(MT5エラー10030)を完全解決 — 別ブローカーに移した日にEAが発注できなくなる理由と直し方
2026-07-07
Market closed(10018 / エラー132)を完全解決 — MT5「市場が閉鎖」でEAが発注できない原因と直し方
2026-07-07
Off quotes / リクオート(MT5/MQL5)を完全解決 — 10021・10004 で注文が拒否される原因と直し方
📧 5日間メール講座(無料)
FX自動売買の本質、バックテストの正しい見方、ブローカー選びのコツを毎日1通お届けします。
※ プライバシーは厳守。配信解除はいつでも可能です。