首頁 > 部落格 > 徹底解決 Invalid stops(10016/130)— MT5/MT4的SL設定錯誤

MT5MQL5錯誤問題排解EA

徹底解決 Invalid stops(10016/130)— MT5/MT4的SL設定錯誤

發布日: 2026-07-07閱讀時間:約 2 分鐘
本文為發布之日的資訊。EA的績效數值(PF、DD、年化)會隨實盤運行與重新驗證而變動,最新數值請在各EA頁面確認。 查看最新EA績效

徹底解決 Invalid stops(10016/130)

在運行EA時,若Expert頁籤出現 Invalid stopsOrderSend 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

實務上的區分:

平台取得來源數值出現場合
MT5MqlTradeResult.retcode10016(TRADE_RETCODE_INVALID_STOPS)OrderSend / PositionModify 被伺服器拒絕
MT5CTrade.ResultRetcode()10016透過CTrade下單、修改被拒絕
MT4GetLastError()130(ERR_INVALID_STOPS)OrderSend / OrderModify 失敗後

雖然代碼不同,但意義與成因幾乎相同,對策也是共通的。順帶一提,MT5中常被混淆的 10015(TRADE_RETCODE_INVALID_PRICE)指的是「訂單價格本身不合法」,是另一回事。10016純粹是「SL/TP(止損)位置」的問題。


先花30秒排查

打開MT5的「報價 → 右鍵點擊商品 → 規格」,查看以下兩項:

止損位 (Stops level)   : SL/TP 應與當前價格保持的最小距離(點數)
凍結位 (Freeze level)  : 即將觸發的訂單,禁止修改/取消的距離(點數)

接著,在錯誤發生當下的日誌中,比對打算送出的SL/TP數值與當時的Bid/Ask

  • SL或TP與當前價格的距離小於止損位 → 這幾乎就是原因所在(原因①)。
  • BUY時SL卻高於Bid/TP低於Bid(SELL則相反) → 方向搞反了(原因②)。
  • 只有修改持倉時失敗 → 可能是凍結位限制,或SL/TP需後補的規格(原因③・⑤)。
  • SL的數值是「50」這類明顯不像價格的數字 → 價格與點數混淆(原因④)。

若要從程式碼中確認,一行即可:

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的間隔會拉開,因此平時能通過的距離,在指標公布時或清晨時段可能突然被拒絕。

對策:

  1. 在規格視窗確認止損位,將EA的SL/TP、移動止損幅度設定得更寬
  2. 在EA端於下單前先做限幅處理(見後述程式碼)
  3. 若確實需要極緊的SL,可考慮止損位較小的券商或帳戶類型

② SL/TP方向相反(BUY/SELL搞混)

症狀: 特定方向(僅買或僅賣)必定出現 Invalid stops。這是自製EA首次測試時最常見的模式。

原因: 規則很單純,BUY的SL須低於當前價格(Bid),TP須高於;SELL的SL須高於當前價格(Ask),TP須低於。將BUY用的計算式複製給SELL卻忘了改正負號、把 price - slprice + sl 搞混,都是典型錯誤,伺服器會立即返回10016/130。

對策:

  1. 出錯時用 PrintFormat("type=%s sl=%.5f tp=%.5f bid=%.5f ask=%.5f", ...) 印出實際數值,目視確認方向
  2. 將SL/TP計算改為BUY/SELL共用函式,把正負號的分支統一收攏在一處(消除複製貼上分支)

③ 在凍結位範圍內修改訂單或持倉

症狀: 新開倉沒問題,但僅在觸及TP前、SL前、掛單即將觸發前的修改/取消操作遭拒絕。

原因: 對設有 SYMBOL_TRADE_FREEZE_LEVEL 的商品,當觸發價格(TP/SL/掛單的觸發價)與當前價格接近到一定距離時,該訂單的修改與取消會被凍結。這是為防止成交處理與修改請求發生衝突的伺服器規格,常表現為EA的移動止損「想在接近TP時再更新一段,卻被拒絕」。

對策:

  1. 修改前先讀取 SYMBOL_TRADE_FREEZE_LEVEL,若距觸發價的距離小於等於凍結位,該次修改直接跳過
  2. 拉長移動止損的更新間隔與更新幅度,減少接近觸發前的無謂修改請求
  3. 被拒絕並非致命問題(接近觸發=即將成交),因此可設計成吞下錯誤、僅留日誌記錄即可

④ 價格與點數(距離)混淆

症狀: SL被直接填入「50」或「0.0050」這類「本意是距離」的數值。日誌中出現的 sl=50.00000 明顯不是價格。

原因: MqlTradeRequest.sl / .tp 要填入的是絕對價格(並非「距進場價下方50點」)。以距離管理的EA,必須先換算成 entry ± distance * _Point 後再傳入。反過來,若延續MT4時代某些函式的用法,在應填絕對價格的地方傳入距離,價格便無法成立,導致10016/130。

此外,pips與點數(point)的混淆也是常見問題。5位報價的券商(例如USDJPY顯示3位小數、EURUSD顯示5位小數)中,1 pip = 10點。「SL 50」究竟是以pips還是point為單位,會使距離相差10倍,可能因此落在止損位以下而遭拒絕。

對策:

  1. SL/TP務必以 NormalizeDouble(price ± dist * _Point, _Digits) 的形式組合
  2. 在輸入參數的單位(pips / points)上以註解明確標示,並將 _Point 換算邏輯統一收攏在一處

⑤ 市價執行券商無法在進場時同時設定SL/TP

症狀: 不帶SL/TP時能成交,但帶SL/TP的新單就出現Invalid stops。特別常見於ECN/市價執行(Market Execution)類型的帳戶。

原因: 市價執行下,「請求時的價格」與「實際成交價格」會有落差,因此部分伺服器不接受新單請求內附帶SL/TP,要求成交後再透過修改持倉來設定(這是MT4時代ECN帳戶頻繁出現error 130的經典規格,MT5中仍有部分伺服器保留相同行為)。

對策:

  1. 改為先以不帶SL/TP的方式下單 → 確認成交後再用 PositionModify()(若用CTrade則為 trade.PositionModify())補上SL/TP的兩段式流程
  2. 此方式會出現「下單成功但SL設定失敗」的瞬間,因此務必加入SL設定失敗時重試,達到規定次數仍失敗則立即平倉的保護機制(放任無SL部位是最壞的結果)
  3. 可用 SymbolInfoInteger(_Symbol, SYMBOL_TRADE_EXEMODE) 確認執行方式

⑥ 商品特性差異(黃金、指數的止損位較大)

症狀: 同一個EA在EURUSD上運作正常,一換到XAUUSD(黃金)或股指CFD就頻繁出現Invalid stops。

原因: 止損位是依商品個別設定的,黃金、指數、非主流貨幣對通常設定得比主要貨幣對更大。若直接沿用為主要外匯調校的緊縮SL/移動止損幅度,便會達不到該商品的最小距離而遭拒絕。位數也因商品而異(例如黃金常為2〜3位小數),若程式碼中硬編碼 _Digits,同樣會出錯。

對策:

  1. 更換商品時務必在規格視窗確認止損位與位數
  2. 將SL/TP幅度改為以ATR等波動性基準設定,而非固定點數,可提升跨商品的穩健性
  3. 程式碼中應始終動態取得 _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;
}

重點有三:

  1. 每次都動態取得 SYMBOL_TRADE_STOPS_LEVELSYMBOL_POINT(不受商品、券商限制)
  2. 加上點差的緩衝空間(即使是止損位為0的動態判定券商,也更容易通過)
  3. 最後務必以 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的兩段式流程
🛠 開發是否動態取得商品規格實作上述的 ClampStops

總結

  • Invalid stopsMT5為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)解說文章


常見問題

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() 設定的兩段式流程。但務必加入SL設定失敗時的重試機制,以及持續失敗時立即平倉的保護措施。

Q: 在EURUSD上正常運作的EA,換到黃金就頻繁出現 Invalid stops。

黃金、股指的止損位通常比主要貨幣對設定得更大,位數也不同。請依商品規格拉寬SL/移動止損幅度,或改為採用ATR基準的波動連動幅度。

📧 漲價預告 + 免費5天郵件課程

所有EA目前均為首發價,並將隨銷量階梯式上調。在每次漲價前收到通知,另有每日一封郵件:自動交易本質、正確解讀回測、券商選擇技巧。

※ 嚴格保護隱私。可隨時取消訂閱。

留言與提問