แก้ปัญหา Invalid Stops (10016/130) — SL บน MT5
สารบัญ
- Invalid Stops คืออะไร (ความแตกต่างระหว่าง 10016 กับ 130)
- ① TRADE_RETCODE_INVALID_STOPS = 10016 (รหัสคืนค่าของ `OrderSend()` บน MT5)
- ② ERR_INVALID_STOPS = 130 (`GetLastError()` บน MT4)
- วิธีตรวจสอบเบื้องต้นใน 30 วินาที
- สาเหตุและวิธีแก้ไข (6 รูปแบบ)
- ① SL/TP อยู่ใกล้ราคาปัจจุบันเกินไป (น้อยกว่า Stop Level)
- ② ทิศทางของ SL/TP กลับด้าน (สลับ BUY/SELL)
- ③ การแก้ไขคำสั่ง/โพซิชันภายในโซน Freeze Level
- ④ ความสับสนระหว่างราคากับพอยท์ (ระยะห่าง)
- ⑤ บัญชีที่ Execution แบบ Market ไม่ยอมรับ SL/TP ตอนเปิดออร์เดอร์
- ⑥ ลักษณะเฉพาะของสัญลักษณ์ (ทองคำและดัชนีมี Stop Level กว้างกว่า)
- ความแตกต่างระหว่างโบรกเกอร์ (ข้อควรระวัง)
- โค้ดป้องกันข้อผิดพลาดนี้ใน MQL5 (สำหรับผู้พัฒนา EA)
- ตรวจสอบและ Clamp ค่า SL/TP ก่อนส่งคำสั่ง
- จัดการ retcode 10016 แยกเฉพาะ
- ตรวจสอบ Freeze Level ก่อนแก้ไข Trail
- ตารางตรวจสอบตามลำดับความสำคัญ
- สรุป
- คำถามที่พบบ่อย (FAQ)
- Q: คำนวณ SL/TP ถูกต้องแล้ว แต่ยังเจอ Invalid stops เพราะอะไร?
- Q: 10016 กับ 130 ต่างกันอย่างไร?
- Q: ปกติไม่เจอปัญหา แต่พอมีการประกาศข่าวสำคัญกลับเจอ Invalid stops?
- Q: ไม่มี SL/TP แล้วส่งคำสั่งผ่าน แต่พอใส่ SL/TP กลับถูกปฏิเสธ?
- Q: EA ที่ทำงานปกติบน EURUSD กลับเจอ Invalid stops ซ้ำๆ บนทองคำ?
แก้ปัญหา Invalid Stops (10016/130) แบบครบวงจร
เวลารัน EA แล้วเห็น Invalid stops หรือ OrderSend error 130 ขึ้นในแท็บ Expert หลายคนมักสับสนว่า "ค่า SL ผิดหรือเปล่า? แต่คำนวณถูกแล้วนี่..." ทว่าความจริงแล้ว ข้อผิดพลาดนี้ส่วนใหญ่ไม่ได้เกิดจากการคำนวณ SL/TP ผิด แต่เป็นการละเมิด "กฎระยะห่างขั้นต่ำที่โบรกเกอร์กำหนด" แม้ค่าที่ส่งไปจะถูกต้อง แต่ถ้าอยู่ใกล้ราคาปัจจุบันเกินไป ทิศทางกลับด้าน หรืออยู่ในโซนห้ามแก้ไข ก็จะถูกปฏิเสธทันที
บทความนี้เขียนขึ้นสำหรับทั้งผู้ใช้ EA บน MT5/MT4 และผู้พัฒนา EA ด้วย MQL5 โดยรวบรวมเนื้อหาตั้งแต่ ตัวตนที่แท้จริงของ Invalid stops, สาเหตุทั้ง 6 ข้อ, วิธีตรวจสอบใน 30 วินาที ไปจนถึงการป้องกันถาวรในโค้ด ไว้ในบทความเดียว สำหรับรายการข้อผิดพลาดทั้งหมด สามารถดูได้ที่ คู่มือรวมวิธีแก้ปัญหารหัสข้อผิดพลาด MQL5 / MT5
บทความนี้อ้างอิงจาก MT5 (build 4xxx) ณ เดือนกรกฎาคม 2026 ค่า Stop Level ที่แท้จริงจะแตกต่างกันไปตามโบรกเกอร์และสัญลักษณ์การเทรด
Invalid Stops คืออะไร (ความแตกต่างระหว่าง 10016 กับ 130)
ค่าที่แสดงถึง "Stop ที่ไม่ถูกต้อง" มี 2 แบบตาม MT5 และ MT4 (คนละเจเนอเรชัน) การแยกให้ชัดว่าเกิดขึ้นบนแพลตฟอร์มไหนและในขั้นตอนใดจะช่วยให้หาสาเหตุได้เร็วขึ้นมาก
① TRADE_RETCODE_INVALID_STOPS = 10016 (รหัสคืนค่าของ OrderSend() บน MT5)
ผลลัพธ์ของ OrderSend() ใน MQL5 จะถูกเก็บไว้ใน MqlTradeResult.retcode หาก SL/TP ในคำสั่ง (หรือความสัมพันธ์กับราคาของ pending order) ไม่ตรงตามกฎของเซิร์ฟเวอร์ คำสั่งจะถูกปฏิเสธด้วยรหัส 10016 (TRADE_RETCODE_INVALID_STOPS) ซึ่งเป็น การแจ้งปฏิเสธจากฝั่งเทรดเซิร์ฟเวอร์
ความหมาย: Stop (SL/TP) ในคำสั่งไม่ถูกต้อง (Invalid stops in the request)
ค่าคงที่ : TRADE_RETCODE_INVALID_STOPS
ค่า : 10016
// ตัวอย่าง output ที่พบได้บ่อยในล็อก
2026.07.07 09:15:32.441 EA_NAME XAUUSD,M5: OrderSend error: retcode=10016 (invalid stops)
② ERR_INVALID_STOPS = 130 (GetLastError() บน MT4)
บนเจเนอเรชัน MT4 (MQL4) เมื่อ OrderSend() / OrderModify() ล้มเหลว ค่า GetLastError() จะคืนค่า 130 (ERR_INVALID_STOPS) ข้อความ OrderSend error 130 ที่เห็นในแท็บ Expert ก็คือค่านี้เอง หากใช้งาน EA เวอร์ชัน MT4 ควบคู่กันก็จะเจอค่านี้
ความหมาย: Stop ไม่ถูกต้อง (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 (Stop)" เท่านั้น
วิธีตรวจสอบเบื้องต้นใน 30 วินาที
เปิดเมนู "Market Watch → คลิกขวาที่สัญลักษณ์ → Specification" บน MT5 แล้วดู 2 รายการนี้
Stops level : ระยะห่างขั้นต่ำ (หน่วยพอยท์) ที่ SL/TP ต้องอยู่ห่างจากราคาปัจจุบัน
Freeze level : ระยะห่าง (หน่วยพอยท์) ที่ห้ามแก้ไข/ยกเลิกคำสั่งที่ใกล้จะทำงาน
จากนั้นตรวจสอบล็อกในจังหวะที่เกิดข้อผิดพลาด โดยเทียบ ค่า SL/TP ที่พยายามส่งกับ Bid/Ask ปัจจุบัน
- SL หรือ TP อยู่ห่างจากราคาปัจจุบันน้อยกว่า Stop Level → เกือบแน่นอนว่าเป็นสาเหตุนี้ (สาเหตุ ①)
- เป็น BUY แต่ SL อยู่สูงกว่า Bid หรือ TP อยู่ต่ำกว่า Bid (ถ้าเป็น SELL ก็กลับกัน) → ทิศทางสลับกัน (สาเหตุ ②)
- การแก้ไขโพซิชันที่ถืออยู่ล้มเหลวเท่านั้น → อาจเป็น Freeze Level หรือข้อกำหนดตั้ง SL/TP ภายหลัง (สาเหตุ ③ / ⑤)
- ค่า SL เป็นตัวเลขที่เห็นชัดว่าไม่ใช่ราคา เช่น "50" → ความสับสนระหว่างราคากับพอยท์ (สาเหตุ ④)
หากต้องการตรวจสอบผ่านโค้ด เพียงบรรทัดเดียว
Print("StopsLevel=", SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL),
" FreezeLevel=", SymbolInfoInteger(_Symbol, SYMBOL_TRADE_FREEZE_LEVEL));
สาเหตุและวิธีแก้ไข (6 รูปแบบ)
① SL/TP อยู่ใกล้ราคาปัจจุบันเกินไป (น้อยกว่า Stop Level)
อาการ: พบบ่อยใน EA แนวสแกลป์ที่วาง SL แบบชิดราคา หรือ EA ที่มี Trail แคบ แม้แต่การเทรดด้วยมือ หากพยายามวาง SL "ใกล้ราคามาก" ปุ่มส่งคำสั่งอาจกดไม่ได้หรือถูกปฏิเสธ
สาเหตุ: โบรกเกอร์กำหนดค่า SYMBOL_TRADE_STOPS_LEVEL (ระยะ Stop ขั้นต่ำ หน่วยพอยท์) แยกตามแต่ละสัญลักษณ์ และ SL/TP หรือราคา pending order ที่อยู่ห่างจากราคาปัจจุบันน้อยกว่าระยะนี้จะถูกเซิร์ฟเวอร์ปฏิเสธทั้งหมด ราคาอ้างอิงที่ใช้ตรวจสอบคือ Bid สำหรับ SL/TP ของโพซิชัน BUY และ Ask สำหรับ SELL เมื่อสเปรดขยายตัว ระยะห่างระหว่าง Bid กับ Ask จะกว้างขึ้น ทำให้บางครั้งระยะที่ผ่านได้ในภาวะปกติอาจถูกปฏิเสธทันทีในช่วงข่าวสำคัญหรือช่วงเช้าตรู่
วิธีแก้ไข:
- ตรวจสอบ Stop Level จากหน้าต่าง Specification แล้วปรับ SL/TP หรือระยะ Trail ของ EA ให้กว้างกว่านั้น
- ทำการ Clamp ค่าฝั่ง EA ก่อนส่งคำสั่ง (ดูโค้ดด้านล่าง)
- หากจำเป็นต้องใช้ SL แบบชิดราคาจริงๆ ควรพิจารณาโบรกเกอร์หรือประเภทบัญชีที่มี Stop Level ต่ำ
② ทิศทางของ SL/TP กลับด้าน (สลับ BUY/SELL)
อาการ: เกิด Invalid stops ทุกครั้งเมื่อเทรดในทิศทางใดทิศทางหนึ่ง (ซื้ออย่างเดียว หรือขายอย่างเดียว) เป็นรูปแบบที่พบบ่อยที่สุดในการทดสอบ EA ที่เขียนเองครั้งแรก
สาเหตุ: กฎเรียบง่ายคือ SL ของ BUY ต้องต่ำกว่าราคาปัจจุบัน (Bid) และ TP ต้องสูงกว่า ส่วน SL ของ SELL ต้องสูงกว่าราคาปัจจุบัน (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 ไว้ในฟังก์ชันเดียวกัน โดยแยกเครื่องหมายไว้จุดเดียว (ไม่ให้เกิดการแยกโค้ดด้วยการคัดลอกวาง)
③ การแก้ไขคำสั่ง/โพซิชันภายในโซน Freeze Level
อาการ: คำสั่งใหม่ไม่มีปัญหา แต่ การแก้ไข/ยกเลิกคำสั่งที่ใกล้จะถึง TP, ใกล้ถึง SL หรือใกล้ถึงจุดทำงานของ pending order เท่านั้นที่ถูกปฏิเสธ
สาเหตุ: สำหรับสัญลักษณ์ที่มีการตั้งค่า SYMBOL_TRADE_FREEZE_LEVEL เมื่อ ราคาปัจจุบันเข้าใกล้ราคาที่จะทำงาน (ราคาทริกเกอร์ของ TP/SL/pending) ในระยะหนึ่ง การแก้ไข/ยกเลิกคำสั่งนั้นจะถูกล็อก นี่เป็นข้อกำหนดของเซิร์ฟเวอร์เพื่อป้องกันความขัดแย้งระหว่างกระบวนการ Execution กับคำขอแก้ไข ปัญหานี้มักปรากฏในรูปแบบที่ Trail ของ EA "พยายามอัปเดตอีกขั้นตอนตอนใกล้ถึง TP แต่ถูกปฏิเสธ"
วิธีแก้ไข:
- ก่อนแก้ไขคำสั่ง ให้อ่านค่า
SYMBOL_TRADE_FREEZE_LEVELและหากระยะห่างถึงราคาทริกเกอร์น้อยกว่าหรือเท่ากับ Freeze Level ให้ ข้ามการแก้ไขในรอบนั้น - ขยายช่วงเวลา/ระยะการอัปเดต Trail เพื่อลดคำขอแก้ไขที่ไม่จำเป็นในช่วงใกล้ทำงาน
- แม้ถูกปฏิเสธก็ไม่ร้ายแรง (ใกล้ทำงาน = กำลังจะ Execute อยู่แล้ว) จึงออกแบบให้ดักข้อผิดพลาดแล้วบันทึกล็อกไว้เฉยๆ ได้
④ ความสับสนระหว่างราคากับพอยท์ (ระยะห่าง)
อาการ: ค่า 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 เท่า จนต่ำกว่า Stop Level และถูกปฏิเสธ
วิธีแก้ไข:
- คำนวณ SL/TP ด้วยรูปแบบ
NormalizeDouble(price ± dist * _Point, _Digits)เสมอ - ระบุหน่วยของพารามิเตอร์อินพุต (pips / points) ไว้ในคอมเมนต์ให้ชัดเจน และรวมการแปลงหน่วย
_Pointไว้ในจุดเดียวภายในโค้ด
⑤ บัญชีที่ Execution แบบ Market ไม่ยอมรับ SL/TP ตอนเปิดออร์เดอร์
อาการ: คำสั่งใหม่จะสำเร็จก็ต่อเมื่อไม่มี SL/TP แต่คำสั่งที่มี SL/TP แนบไปด้วยจะเกิด Invalid stops โดยเฉพาะบัญชีประเภท ECN/Market Execution
สาเหตุ: ในระบบ Market Execution "ราคาตอนส่งคำสั่ง" กับ "ราคาที่ Execute จริง" อาจไม่ตรงกัน ทำให้ เซิร์ฟเวอร์บางแห่งไม่รับ SL/TP ที่แนบไปในคำสั่งเปิดใหม่ และกำหนดให้ตั้งค่าผ่านการแก้ไขโพซิชันภายหลังจาก Execute แล้วเท่านั้น (เป็นข้อกำหนดคลาสสิกที่ทำให้บัญชี ECN ยุค MT4 เจอ error 130 บ่อยครั้ง และยังมีเซิร์ฟเวอร์บน MT5 บางแห่งที่มีพฤติกรรมเดียวกันหลงเหลืออยู่)
วิธีแก้ไข:
- เปลี่ยนมาใช้รูปแบบ 2 ขั้นตอน คือ ส่งคำสั่งโดยไม่มี SL/TP ก่อน → หลังยืนยัน Execute แล้วจึงใช้
PositionModify()(หรือtrade.PositionModify()ถ้าใช้ CTrade) เพื่อตั้ง SL/TP - วิธีนี้จะเกิดช่วงเวลาที่ "ส่งคำสั่งสำเร็จแต่ตั้ง SL ไม่สำเร็จ" ดังนั้นต้องมีระบบป้องกัน ลองซ้ำจนกว่าตั้ง SL สำเร็จ และหากล้มเหลวครบจำนวนครั้งที่กำหนดให้ปิดออร์เดอร์ทันที (การปล่อยไว้โดยไม่มี SL คือผลลัพธ์ที่แย่ที่สุด)
- สามารถตรวจสอบรูปแบบ Execution ได้ด้วย
SymbolInfoInteger(_Symbol, SYMBOL_TRADE_EXEMODE)
⑥ ลักษณะเฉพาะของสัญลักษณ์ (ทองคำและดัชนีมี Stop Level กว้างกว่า)
อาการ: EA ตัวเดียวกันทำงานปกติบน EURUSD แต่พอนำไปใช้กับ XAUUSD (ทองคำ) หรือ CFD ดัชนีหุ้น กลับเกิด Invalid stops ซ้ำๆ
สาเหตุ: Stop Level ถูกตั้งค่าแยกกันในแต่ละสัญลักษณ์ และโดยทั่วไป ทองคำ ดัชนี และสกุลเงินแปลกใหม่ (Exotic) มักมี Stop Level กว้างกว่าคู่เงินหลัก หากนำระยะ SL/Trail ที่ปรับไว้แคบสำหรับ Forex คู่หลักมาใช้ตรงๆ จะไม่ถึงระยะขั้นต่ำของสัญลักษณ์นั้นและถูกปฏิเสธ นอกจากนี้จำนวนหลักทศนิยมก็แตกต่างกันตามสัญลักษณ์ (เช่น ทองคำมี 2-3 หลัก) ดังนั้นโค้ดที่กำหนดค่า _Digits ตายตัวก็จะพังเช่นกัน
วิธีแก้ไข:
- เมื่อเปลี่ยนสัญลักษณ์ ให้ตรวจสอบ Stop Level และจำนวนหลักจากหน้าต่าง Specification ทุกครั้ง
- กำหนดระยะ SL/TP ด้วย เกณฑ์ความผันผวน เช่น ATR แทนการใช้พอยท์ตายตัว จะช่วยลดปัญหาเมื่อใช้งานข้ามสัญลักษณ์
- ในโค้ดควรดึงค่า
_Point/_Digits/SYMBOL_TRADE_STOPS_LEVELแบบไดนามิกเสมอ (ห้าม Hardcode)
ความแตกต่างระหว่างโบรกเกอร์ (ข้อควรระวัง)
Stop Level และ Freeze Level แตกต่างกันโดยสิ้นเชิงตามแต่ละคู่ของโบรกเกอร์และสัญลักษณ์ แม้จะใช้ EA และการตั้งค่าเดียวกัน แต่ข้อผิดพลาดที่ไม่เคยเกิดขึ้นเลยกับโบรกเกอร์ A อาจเกิดขึ้นทุกวันกับโบรกเกอร์ B ก็เป็นเรื่องปกติ
สิ่งที่ต้องระวังเพิ่มเติมคือ โบรกเกอร์ที่แสดง Stop Level เป็น "0" ค่า 0 มักไม่ได้หมายถึง "ไม่มีข้อจำกัด" แต่หมายถึง "ตัดสินแบบไดนามิก" ในหลายกรณี ทำให้ SL ที่วางใกล้แค่ไหนก็ผ่านได้ในภาวะปกติ แต่ จะถูกปฏิเสธเฉพาะช่วงที่สเปรดขยายตัว เช่น ตอนประกาศข่าวสำคัญหรือช่วงเช้าตรู่ ซึ่งมักเป็นต้นตอของ "Invalid stops ที่นานๆ เกิดที"
การตรวจสอบควรทำกับบัญชีจริงเสมอ
long stops = SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL); // หน่วยพอยท์
long freeze = SymbolInfoInteger(_Symbol, SYMBOL_TRADE_FREEZE_LEVEL); // หน่วยพอยท์
ตัวเลขที่แท้จริงขึ้นอยู่กับประเภทบัญชีและสเปคของสัญลักษณ์ในแต่ละโบรกเกอร์ บทความนี้จึงไม่ได้ระบุตัวเลขที่ชัดเจน ค่าที่ได้จากการรันโค้ดข้างต้นในบัญชีของคุณเองคือคำตอบที่ถูกต้องเพียงหนึ่งเดียว
โค้ดป้องกันข้อผิดพลาดนี้ใน MQL5 (สำหรับผู้พัฒนา EA)
การออกแบบที่ถูกต้องไม่ใช่ "แก้เมื่อเกิดข้อผิดพลาด" แต่คือ Clamp ค่า SL/TP ให้อยู่ในระยะขั้นต่ำของโบรกเกอร์ก่อนส่งคำสั่ง เพื่อไม่ให้เกิด 10016 ตั้งแต่แรก
ตรวจสอบและ Clamp ค่า SL/TP ก่อนส่งคำสั่ง
// Clamp ค่า SL/TP ให้อยู่ในระยะที่มากกว่าหรือเท่ากับ Stop Level ก่อนส่งคำสั่ง
// คืนค่า 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);
// เผื่อระยะเพิ่มเติมเท่ากับสเปรด นอกเหนือจาก Stop Level (รองรับโบรกเกอร์ที่ stops=0 และตัดสินแบบไดนามิก)
double minDist = stopsPt * point + spread;
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
if(type == ORDER_TYPE_BUY)
{
// SL/TP ของ BUY จะถูกตรวจสอบโดยอ้างอิง 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)
{
// SL/TP ของ SELL จะถูกตรวจสอบโดยอ้างอิง 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แบบไดนามิกทุกครั้ง (ไม่ผูกกับสัญลักษณ์หรือโบรกเกอร์ใดโบรกเกอร์หนึ่ง) - เผื่อระยะเพิ่มเติมเท่ากับสเปรด (ช่วยให้ผ่านได้ง่ายขึ้นแม้กับโบรกเกอร์ที่ Stop Level เป็น 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());
}
ตรวจสอบ Freeze Level ก่อนแก้ไข Trail
// ก่อนแก้ไขโพซิชัน ให้ตรวจสอบว่าราคาทริกเกอร์อยู่ในโซน Freeze หรือไม่
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));
}
สำหรับ EA เวอร์ชัน MT4 สามารถดึงค่าเดียวกันได้ด้วย MarketInfo(Symbol(), MODE_STOPLEVEL) / MODE_FREEZELEVEL แนวคิดเหมือนกันทุกประการ
EA ที่ FXEA365 แจกจ่ายนั้นมี การตรวจสอบ SL/TP ก่อนส่งคำสั่ง การ Clamp ตาม Stop Level และการดึงค่าแบบไดนามิกที่ไม่ผูกกับสัญลักษณ์ เป็นมาตรฐานอยู่แล้ว จึงออกแบบมาไม่ให้หยุดทำงานเพราะ Invalid stops แม้เปลี่ยนโบรกเกอร์หรือสัญลักษณ์
ตารางตรวจสอบตามลำดับความสำคัญ
| ลำดับ | ตรวจสอบ | วิธีแก้ไข |
|---|---|---|
| 🚨 อันดับแรก | ระยะห่างระหว่าง SL/TP กับราคาปัจจุบัน < Stop Level หรือไม่ | ขยายระยะ SL/TP / ใช้โค้ด Clamp |
| 🚨 อันดับแรก | ทิศทางของ SL/TP สลับกันระหว่าง BUY/SELL หรือไม่ | แสดงค่าจริงในล็อกแล้วตรวจสอบด้วยตา |
| ⚠️ ถัดไป | เฉพาะการแก้ไขล้มเหลว → อยู่ในโซน Freeze Level หรือไม่ | ข้ามการแก้ไขในช่วงใกล้ทำงาน |
| ⚠️ ถัดไป | ใส่ระยะทาง (พอยท์) ลงใน sl/tp โดยตรงหรือไม่ | แปลงเป็นราคาสัมบูรณ์ price ± dist*_Point |
| ✅ ตรวจสอบ | เป็นบัญชี Market Execution ที่ต้องตั้ง SL/TP ภายหลังหรือไม่ | ใช้รูปแบบ 2 ขั้นตอน ส่งคำสั่ง → PositionModify |
| 🛠 พัฒนา | ดึงค่าสเปคของสัญลักษณ์แบบไดนามิกหรือไม่ | นำ ClampStops ด้านบนไปใช้งาน |
สรุป
Invalid stopsคือ MT5 = 10016 (TRADE_RETCODE_INVALID_STOPS), MT4 = 130 (ERR_INVALID_STOPS) ตัวเลขต่างกันแต่ความหมายและวิธีแก้ไขเหมือนกัน- สาเหตุส่วนใหญ่ไม่ใช่ความผิดพลาดในการคำนวณ แต่มาจาก 6 สาเหตุนี้ ต่ำกว่า Stop Level, ทิศทางกลับด้าน, Freeze Level, ความสับสนระหว่างราคากับพอยท์, ข้อกำหนดตั้ง SL/TP ภายหลัง และลักษณะเฉพาะของสัญลักษณ์
- ผู้พัฒนาสามารถป้องกันถาวรได้ด้วยการ "อ่านค่า
SYMBOL_TRADE_STOPS_LEVELแล้ว Clamp พร้อมใช้NormalizeDoubleก่อนส่งคำสั่ง" แม้กับโบรกเกอร์ที่ Stop Level เป็น 0 ก็สามารถรองรับได้ด้วยการเผื่อระยะเท่ากับสเปรด
สำหรับรหัสข้อผิดพลาดทั้งหมด สามารถดูได้ที่ คู่มือรวมวิธีแก้ปัญหารหัสข้อผิดพลาด MQL5 / MT5 ส่วนข้อผิดพลาดยอดนิยมอีกแบบที่ทำให้คำสั่งถูกปฏิเสธเช่นกันคือ "เงินทุนไม่เพียงพอ" ซึ่งสรุปไว้ใน บทความอธิบาย ERR_NO_MONEY (134/10019)
คำถามที่พบบ่อย (FAQ)
Q: คำนวณ SL/TP ถูกต้องแล้ว แต่ยังเจอ Invalid stops เพราะอะไร?
มีความเป็นไปได้สูงว่าไม่ได้ถูกปฏิเสธเพราะค่าผิด แต่เป็นเพราะ ระยะห่างจากราคาปัจจุบัน SL/TP ที่มีระยะห่างน้อยกว่าระยะ Stop ขั้นต่ำของโบรกเกอร์ (Stop Level) จะถูกปฏิเสธทั้งหมดแม้ค่าจะถูกต้องก็ตาม กรุณาตรวจสอบระยะขั้นต่ำได้จากหน้าต่าง Specification หรือ SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL)
Q: 10016 กับ 130 ต่างกันอย่างไร?
10016 (TRADE_RETCODE_INVALID_STOPS) คือรหัสผลลัพธ์จาก OrderSend() / PositionModify() บน MT5 ส่วน 130 (ERR_INVALID_STOPS) คือค่าที่ GetLastError() คืนค่าบน MT4 เป็นเพียงความแตกต่างของแพลตฟอร์ม ส่วนความหมาย (ตำแหน่ง SL/TP ไม่ถูกต้อง) และวิธีแก้ไขเหมือนกัน
Q: ปกติไม่เจอปัญหา แต่พอมีการประกาศข่าวสำคัญกลับเจอ Invalid stops?
สาเหตุคือสเปรดที่ขยายตัว เนื่องจาก SL/TP ของ BUY จะถูกตรวจสอบโดยอ้างอิง Bid ส่วน SELL อ้างอิง Ask เมื่อสเปรดขยายตัวในจังหวะนั้น ระยะห่างที่มีอยู่อาจไม่เพียงพอ แม้แต่โบรกเกอร์ที่ Stop Level เป็น 0 ก็มีพฤติกรรมปฏิเสธแบบไดนามิกเฉพาะช่วงที่ราคาผันผวนรุนแรง ควรเผื่อระยะ SL/TP เพิ่มเท่ากับสเปรด (ดูโค้ดในเนื้อหา)
Q: ไม่มี SL/TP แล้วส่งคำสั่งผ่าน แต่พอใส่ SL/TP กลับถูกปฏิเสธ?
มีความเป็นไปได้ว่าเป็นบัญชีประเภท Market Execution ที่เซิร์ฟเวอร์ไม่รับ SL/TP ที่แนบไปในคำสั่งเปิดใหม่ กรุณาเปลี่ยนเป็นรูปแบบ 2 ขั้นตอน คือส่งคำสั่งโดยไม่มี SL/TP ก่อน แล้วตั้งค่าด้วย PositionModify() หลังจาก Execute สำเร็จ แต่ต้องมีระบบลองซ้ำเมื่อตั้ง SL ไม่สำเร็จ และมีมาตรการปิดออร์เดอร์ทันทีหากล้มเหลวต่อเนื่องด้วย
Q: EA ที่ทำงานปกติบน EURUSD กลับเจอ Invalid stops ซ้ำๆ บนทองคำ?
โดยทั่วไปทองคำและดัชนีหุ้นมี Stop Level กว้างกว่าคู่เงินหลัก และจำนวนหลักก็แตกต่างกันด้วย ควรขยายระยะ SL/Trail ให้สอดคล้องกับสเปคของสัญลักษณ์นั้น หรือเปลี่ยนไปใช้ระยะที่อิงตามความผันผวน เช่น ATR
บทความที่เกี่ยวข้อง
📧 แจ้งเตือนก่อนขึ้นราคา + คอร์สอีเมลฟรี 5 วัน
EA ทั้งหมดจำหน่ายในราคาเปิดตัวและจะปรับขึ้นเป็นขั้นตามยอดขาย รับการแจ้งเตือนก่อนการขึ้นราคาแต่ละครั้ง พร้อมอีเมลรายวันเกี่ยวกับการเทรดอัตโนมัติ การอ่านแบ็กเทสต์ และการเลือกโบรกเกอร์
* ปกป้องความเป็นส่วนตัวอย่างเคร่งครัด คุณสามารถยกเลิกการสมัครได้ตลอดเวลา