เริ่มต้นเขียน EA MQL4 — โครงไฟล์ ฟังก์ชันหลัก Bid/Ask และ 5 บั๊กที่มือใหม่เจอซ้ำ

$ £ MyExpert.mq4 int OnInit() { return(INIT_SUCCEEDED); } void OnTick() { if(NewBar()) CheckSignal(); } void OnDeinit(const int r) { ObjectsDeleteAll(); } เริ่มต้นเขียน EA MQL4 โครงไฟล์ · Bid/Ask · บั๊กที่เจอซ้ำ โดย คนเล่น Forex ตั้ง TP ผิดข้างสเปรด 6 จุด ไกลกว่าที่ตั้งใจ = 2 เท่าของสเปรด บั๊กที่มือใหม่เจอซ้ำ 5 ข้อ พร้อมโค้ดที่แก้แล้วทุกข้อ ฉบับตรวจโค้ดจริง
เขียน EA

เริ่มต้นเขียน EA (MQL4) — โครงไฟล์ ฟังก์ชันที่ต้องมี เรื่อง Bid/Ask ที่ทำให้ตั้ง TP ผิด และ 5 บั๊กที่เจอซ้ำที่สุด

หน้านี้รวมพื้นฐานการเขียน EA ทั้งกองไว้ที่เดียว ตั้งแต่ EA เขียนด้วยภาษาอะไร โครงไฟล์มีฟังก์ชันอะไรบ้างและทำงานตอนไหน ไปจนถึงเรื่อง Bid กับ Ask ที่ทำให้หลายคนตั้งจุดทำกำไรพลาดไป 2 เท่าของสเปรด โดยไม่รู้ตัว — พร้อมโค้ดที่แก้แล้วทุกจุด

ถ้ายังไม่รู้ว่า EA คืออะไรและใช้ทำอะไรได้บ้าง อ่านภาพรวมที่ EA (Expert Advisor) คืออะไร ก่อน · ส่วนหน้านี้เริ่มจากสมมติว่าคุณจะลงมือเขียนเองแล้ว


EA เขียนด้วยภาษาอะไร และเขียนที่ไหน

EA ของ MetaTrader เขียนด้วยภาษา MQL (MetaQuotes Language) — MT4 ใช้ MQL4 ส่วน MT5 ใช้ MQL5 ทั้งคู่หน้าตาคล้ายภาษา C มาก และเขียนได้จากโปรแกรมเทรดที่ติดตั้งอยู่แล้ว ไม่ต้องลงอะไรเพิ่ม

  • เปิดตัวแก้ไขโค้ด — ในโปรแกรมเทรด ไปที่เมนู Tools → MetaQuotes Language Editor หรือกด F4
  • สร้างไฟล์ใหม่ — File → New → Expert Advisor แล้วตั้งชื่อ ระบบจะสร้างโครงไฟล์ว่าง ๆ ให้พร้อมฟังก์ชันหลักครบ
  • คอมไพล์ — File → Compile ⇒ ถ้าไม่มี error จะได้ไฟล์ .ex4 / .ex5 ที่เอาไปลากใส่ชาร์ตได้
    ⚠️ ปุ่มลัดไม่เหมือนกันทุกรุ่น — MetaEditor รุ่นเก่าเป็น F5 รุ่นใหม่เป็น F7 ⇒ ให้ดูตัวเลขที่เมนูของเครื่องตัวเองเป็นหลัก
  • ดูผลลัพธ์ตอนรัน — ข้อความจาก Print() จะไปโผล่ที่แท็บ Experts ส่วน error ตอนส่งคำสั่งจะอยู่ที่แท็บ Journal
เมนู Tools ของ MT4 ที่มีรายการ MetaQuotes Language Editor พร้อมปุ่มลัด F4
เปิดตัวแก้ไขโค้ดจากในโปรแกรมเทรดได้เลย — Tools → MetaQuotes Language Editor หรือกด F4
เมนู File ของ MetaEditor ที่มีรายการ Compile พร้อมปุ่มลัด F5
เมนู File → Compile ใน MetaEditor — ภาพนี้เป็นรุ่นที่ผูกไว้กับ F5 ส่วนรุ่นใหม่จะเป็น F7 ⇒ นี่คือเหตุผลที่ให้ดูจากเมนู ไม่ใช่จำตัวเลขจากบทความ
ไฟล์ไปอยู่ไหน — โค้ดต้นฉบับอยู่ในโฟลเดอร์ MQL4/Experts (หรือ MQL5/Experts) ของ data folder ⇒ เปิดจากเมนู File → Open Data Folder ในโปรแกรมเทรด · อย่าไปหาในโฟลเดอร์ที่ติดตั้งโปรแกรม เพราะคนละที่กัน และเป็นสาเหตุที่หลายคนคอมไพล์แล้วไม่เห็น EA โผล่ในหน้าต่าง Navigator

โครงไฟล์ EA — ฟังก์ชันที่ต้องมี และทำงานตอนไหน

ไฟล์ EA ที่สร้างใหม่จะมีฟังก์ชันหลักมาให้สามตัว แต่ละตัวถูกเรียกโดยโปรแกรมเทรดเอง เราไม่ได้เรียกมันเอง

ฟังก์ชันถูกเรียกตอนไหนใช้ทำอะไรชื่อเก่าที่เลิกใช้แล้ว
OnInit()ตอนลาก EA ลงชาร์ต หรือเปลี่ยนค่า inputตั้งค่าเริ่มต้น · ตรวจว่าค่าที่ผู้ใช้กรอกมาใช้ได้ไหมinit()
OnTick()ทุกครั้งที่มีราคาใหม่เข้ามาที่ที่ตรรกะการเทรดทั้งหมดอยู่start()
OnDeinit()ตอนถอด EA ออก ปิดชาร์ต หรือปิดโปรแกรมเก็บกวาดสิ่งที่วาดไว้บนชาร์ตdeinit()
⚠️
OnTick() ถูกเรียกบ่อยกว่าที่คิดมาก — ในช่วงตลาดคึกคัก ราคาใหม่เข้ามาได้หลายครั้งต่อวินาที ⇒ ถ้าเขียนคำสั่งเปิดออเดอร์ไว้ตรง ๆ ใน OnTick() โดยไม่มีตัวคุม EA จะเปิดออเดอร์รัวจนพอร์ตพังก่อนที่คุณจะทันกดหยุด · วิธีคุมและตัวกรอง 4 แบบพร้อมโค้ดอยู่ที่ EA เปิดออร์เดอร์ซ้ำในแท่งเดียว
//+------------------------------------------------------------------+ //| โครงไฟล์ EA ที่ใช้เป็นจุดเริ่มต้นได้เลย (MQL4) | //+------------------------------------------------------------------+ input int InpMagic = 20260903; // เลขประจำตัว EA ใช้แยกไม้ของเราออกจากไม้อื่น input double InpLots = 0.01; // ขนาดล็อต datetime g_lastBarTime = 0; // เวลาเปิดของแท่งที่ประมวลผลไปแล้ว int OnInit() { if(InpLots <= 0.0) { Print("ค่า InpLots ต้องมากกว่า 0"); return(INIT_PARAMETERS_INCORRECT); // บอกให้โปรแกรมหยุด ไม่ใช่รันต่อทั้งที่ค่าผิด } return(INIT_SUCCEEDED); } bool IsNewBar() { datetime t = iTime(_Symbol, _Period, 0); if(t == g_lastBarTime) return(false); g_lastBarTime = t; return(true); } void OnTick() { if(!IsNewBar()) return; // ทำงานแท่งละครั้ง ไม่ใช่ทุก tick // ตรรกะการเทรดของคุณอยู่ตรงนี้ } void OnDeinit(const int reason) { ObjectsDeleteAll(0, "myEA_"); // ลบเฉพาะของที่ EA ตัวนี้วาดไว้ }

Bid กับ Ask — เรื่องที่ทำให้ตั้ง TP ผิดไป 2 เท่าของสเปรด

ก่อนเขียนคำสั่งแรก ต้องจำสองบรรทัดนี้ให้แม่น เพราะทุกอย่างที่เหลือต่อยอดจากมัน

ประเภทออเดอร์เปิดที่ราคาปิดที่ราคาTP และ SL เทียบกับราคาไหน
BUYAsk (แพงกว่า)BidBid — TP ทำงานเมื่อ Bid ขึ้นไปถึง
SELLBid (ถูกกว่า)AskAsk — TP ทำงานเมื่อ Ask ลงมาถึง

ผลที่ตามมาคือ ถ้าคุณคิดจากราคาที่เห็นตอน "ซื้อ" แล้วเอาไปตั้ง TP ตรง ๆ มันจะเพี้ยนไปเท่ากับสเปรด — และถ้าปรับผิดข้าง จะเพี้ยนเป็นสองเท่า ลองดูตัวอย่าง GBP/USD ที่สเปรด 3 จุด

ตั้ง TP ให้ออเดอร์ BUY — สามวิธี ผลไม่เท่ากัน GBP/USD · สเปรด 3 จุด · อยากออกแถว ๆ 1.6150 · วาดตามสัดส่วน 1 จุด = 18 พิกเซล 1.6150 เป้าที่คิดไว้ TP ที่ตั้ง อยากให้ Bid แตะ 1.6150 ⇒ ตั้ง TP = 1.6150 ตรง ๆ บวกสเปรด ⇒ TP = 1.6153 — ออกช้าไป 6 จุด อยากให้ Ask แตะ 1.6150 ⇒ ตั้ง TP = 1.6147 (ลบสเปรด) ถูกต้อง — TP ของ BUY เทียบกับ Bid อยู่แล้ว ไม่ต้องปรับอะไร ผิดข้าง — ต้องรอ Bid ถึง 1.6153 คือ Ask ถึง 1.6156 = ห่างจากที่ตั้งใจ 2 เท่าของสเปรด ถูกต้อง — ตอน Ask = 1.6150 ราคา Bid จะอยู่ที่ 1.6147 พอดี สูงกว่าเป้า 3 จุด ต่ำกว่าเป้า 3 จุด
เส้นแดงคือระดับ TP ที่ตั้งจริง — วิธีกลางบวกสเปรดผิดข้าง ทำให้ออเดอร์ปิดช้ากว่าที่ตั้งใจ 6 จุด ทั้งที่ตั้งใจจะออกที่ระดับเดียวกัน
คุณอยากให้ออกตอนไหนต้องตั้ง TP เท่าไรเหตุผล
อยากให้ Bid (ราคาบนกราฟ MT4) แตะ 1.61501.6150TP ของ BUY เทียบกับ Bid อยู่แล้ว ⇒ ไม่ต้องปรับ
อยากให้ Ask แตะ 1.61501.6147 (ลบสเปรด)ตอน Ask = 1.6150 ราคา Bid อยู่ที่ 1.6147
ถ้าบวกสเปรดแทน (TP = 1.6153) ⇒ ต้องรอ Bid ถึง 1.6153 = Ask ถึง 1.6156 ⇒ ห่างจากที่ตั้งใจ 6 จุด หรือ 2 เท่าของสเปรด

ฝั่ง SELL กลับด้านทุกอย่าง — TP เทียบกับ Ask ⇒ ถ้าอยากให้ Bid บนกราฟลงมาแตะ 1.5850 ต้องตั้ง TP = 1.5853 คือบวกสเปรด · เรื่องสเปรดที่ไม่คงที่และถ่างได้ในบางช่วงเวลา อ่านต่อที่ Spread ถ่าง คืออะไร


ฟังก์ชันเปิดออเดอร์ — และกับดักของการวนส่งซ้ำ

การแยกคำสั่งเปิดออเดอร์ออกเป็นฟังก์ชันของตัวเองเป็นเรื่องที่ควรทำ เพราะมันคือจุดที่อันตรายที่สุดของทั้งโปรแกรม แต่ตัวอย่างเก่าจำนวนมากใส่ลูป "ส่งซ้ำจนกว่าจะสำเร็จ" ไว้ ซึ่งเป็นการเพิ่มความเสี่ยงแบบทวีคูณ

⚠️
ทำไมลูปส่งซ้ำถึงอันตราย — ถ้าคำสั่งแรกสำเร็จจริงแต่ตอบกลับช้า โค้ดจะคิดว่าล้มเหลวแล้วส่งใหม่ ⇒ วนได้ 5 ครั้งก็เปิดได้ถึง 5 ไม้ = ความเสี่ยง 5 เท่าของที่วางแผนไว้ · และถ้าเขียนแบบไม่จำกัดจำนวนครั้ง (while(ticket < 0) เฉย ๆ) ก็คือลูปที่ยิงคำสั่งไม่หยุดในช่วงที่ตลาดผันผวน
//+------------------------------------------------------------------+ //| เปิดออเดอร์แบบมีเพดานความคลาดและตรวจซ้ำก่อนยิงใหม่ (MQL4) | //+------------------------------------------------------------------+ input int InpSlippage = 3; // ยอมให้ราคาคลาดได้กี่จุด — 0 = ไม่ยอมเลย // นับไม้ของ EA ตัวนี้บนสัญลักษณ์นี้ ใช้กันการเปิดซ้ำ int CountMyOrders() { int n = 0; for(int i = OrdersTotal() - 1; i >= 0; i--) { if(!OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) continue; if(OrderSymbol() == _Symbol && OrderMagicNumber() == InpMagic) n++; } return(n); } bool OpenOrder(int cmd, double lots) { if(CountMyOrders() > 0) { Print("มีไม้ของ EA ตัวนี้อยู่แล้ว ไม่เปิดซ้ำ"); return(false); // กันเปิดซ้ำก่อนเป็นอันดับแรก } double price = (cmd == OP_BUY) ? Ask : Bid; int ticket = OrderSend(_Symbol, cmd, lots, price, InpSlippage, 0.0, 0.0, "myEA_", InpMagic, 0, clrNONE); if(ticket < 0) { int err = GetLastError(); Print("เปิดออเดอร์ไม่สำเร็จ error = ", err); return(false); // ไม่วนส่งซ้ำ — ปล่อยให้แท่งถัดไปตัดสินใจใหม่ } return(true); }

สาระของโค้ดนี้คือ ตรวจก่อนยิง ไม่ใช่ยิงแล้วค่อยแก้ — CountMyOrders() ใช้ OrderMagicNumber() แยกไม้ของ EA ตัวนี้ออกจากไม้ที่เปิดด้วยมือหรือจาก EA ตัวอื่นบนบัญชีเดียวกัน ⇒ ถ้าไม่มีเลขนี้ EA จะไปนับไม้ของคนอื่นเป็นของตัวเอง และตัดสินใจผิดทั้งกระดาน

ค่า InpSlippage คืออะไร — คือจำนวนจุดที่คุณยอมให้ราคาคลาดจากที่ขอ ตั้ง 0 แปลว่าไม่ยอมเลย ⇒ ในช่วงข่าวจะเปิดไม่ติดบ่อย ส่วนตั้งกว้างแปลว่ายอมเข้าที่ราคาแย่ลงได้ ⇒ กินอัตราส่วนเสี่ยงต่อผลตอบแทนของแผน · รายละเอียดและตัวเลขว่ากินไปเท่าไร อยู่ที่ Requote คืออะไร

แสดงค่าบนชาร์ตเพื่อดีบัก — วิธีดูว่า EA คิดอะไรอยู่

เวลา EA ทำงานไม่ตรงที่คิด สิ่งที่ช่วยได้มากที่สุดคือให้มันบอกออกมาว่ากำลังเห็นค่าอะไร วิธีที่เบาที่สุดคือวาดข้อความไว้มุมชาร์ต

ชาร์ต GBPUSD ที่มีข้อความ Order Ticket 349 Profit: 75 แสดงอยู่มุมซ้ายบน จาก EA
ข้อความมุมซ้ายบนคือสิ่งที่ EA เขียนออกมาเอง — เห็นได้ทันทีว่ามันกำลังมองไม้ไหนอยู่และคิดกำไรได้เท่าไร โดยไม่ต้องไล่อ่าน Journal
//+------------------------------------------------------------------+ //| แสดงกำไร/ขาดทุนรวมของไม้ EA ตัวนี้ ไว้มุมซ้ายบนของชาร์ต (MQL4) | //+------------------------------------------------------------------+ void ShowStatus() { double profit = 0.0; int count = 0; for(int i = OrdersTotal() - 1; i >= 0; i--) { if(!OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) continue; if(OrderSymbol() != _Symbol || OrderMagicNumber() != InpMagic) continue; profit += OrderProfit() + OrderSwap() + OrderCommission(); count++; } string name = "myEA_status"; if(ObjectFind(0, name) < 0) // สร้างครั้งเดียวพอ ไม่ต้องสร้างใหม่ทุก tick { ObjectCreate(0, name, OBJ_LABEL, 0, 0, 0); ObjectSetInteger(0, name, OBJPROP_CORNER, CORNER_LEFT_UPPER); ObjectSetInteger(0, name, OBJPROP_XDISTANCE, 10); ObjectSetInteger(0, name, OBJPROP_YDISTANCE, 20); } ObjectSetString(0, name, OBJPROP_TEXT, StringFormat("ไม้ %d กำไรสุทธิ %.2f", count, profit)); ObjectSetInteger(0, name, OBJPROP_COLOR, (profit >= 0) ? clrLime : clrRed); }
สามจุดที่ตัวอย่างเก่ามักทำพลาดในฟังก์ชันแบบนี้ — (1) ประกาศเป็น int แล้วมีทางออกที่ไม่ได้ return ค่า (2) return ทันทีที่เจอไม้แรก ⇒ ได้กำไรของไม้เดียวไม่ใช่ทั้งหมด (3) นับ OrderProfit() เพียว ๆ โดยไม่บวก OrderSwap() กับ OrderCommission() ⇒ ตัวเลขที่เห็นบนชาร์ตไม่ตรงกับยอดที่หักจริงในบัญชี

5 บั๊กที่มือใหม่เจอซ้ำที่สุด

1

เปิดออเดอร์ใน OnTick โดยไม่มีตัวคุม

OnTick() ถูกเรียกทุกครั้งที่ราคาขยับ ⇒ เงื่อนไขที่เป็นจริงอยู่ 10 วินาทีอาจเปิดออเดอร์ไปหลายสิบไม้ · แก้ด้วยตัวคุมแท่ง (IsNewBar()) และการนับไม้ที่มีอยู่ก่อนเปิด

2

ไม่ใส่ Magic Number

ไม่มีเลขประจำตัว EA ⇒ OrdersTotal() จะนับไม้ของทุกคนบนบัญชีนั้นรวมกัน · EA สองตัวบนบัญชีเดียวกันจะปิดไม้ของกันและกันโดยไม่ตั้งใจ

3

ตั้ง TP/SL ผิดข้างสเปรด

คิดจากราคาที่เห็นตอนซื้อแล้วเอาไปตั้งตรง ๆ ⇒ พลาดไปเท่ากับสเปรด และถ้าปรับผิดข้างจะพลาดเป็น 2 เท่า ตามตารางข้างบน

4

วนส่งคำสั่งซ้ำจนกว่าจะสำเร็จ

ในช่วงตลาดผันผวน คำสั่งที่ตอบกลับช้าอาจสำเร็จไปแล้ว ⇒ วน 5 ครั้งได้ 5 ไม้ · แก้ด้วยการตรวจไม้ที่มีอยู่ก่อนยิงใหม่เสมอ

5

ไม่เก็บกวาดใน OnDeinit

ป้ายและเส้นที่ EA วาดไว้ค้างบนชาร์ตหลังถอด EA ออก ⇒ รอบหน้าที่ลากลงมาใหม่จะเห็นค่าเก่าปนกับค่าใหม่และวินิจฉัยผิด


ก่อนปล่อย EA ลงบัญชีจริง

เขียนเสร็จแล้วยังไม่จบ ลำดับที่ควรทำคือทดสอบสามชั้น และแต่ละชั้นตอบคนละคำถาม

  1. Strategy Tester — ตอบว่า "โค้ดทำงานตามที่เขียนไหม"
    ดูว่ามันเปิดปิดไม้ตรงเงื่อนไขหรือเปล่า ไม่ใช่ดูว่ากำไรเท่าไร ⇒ ผลกำไรจากการทดสอบย้อนหลังไม่ใช่คำสัญญาว่าจะได้แบบนั้นจริง เพราะข้อมูลย้อนหลังไม่มีสเปรดถ่าง ไม่มีราคาคลาด และไม่มีคำสั่งที่ส่งไม่ติด
  2. บัญชีทดลอง (demo) อย่างน้อย 1 เดือน — ตอบว่า "ทำงานได้ในสภาพจริงไหม"
    ตรงนี้จะเจอของที่ผลทดสอบย้อนหลังไม่มีทางเจอ ทั้งสเปรดที่ถ่างตอนข่าว ราคาคลาด และการที่เซิร์ฟเวอร์ตอบกลับช้า ⇒ ให้ดู Journal ทุกวัน ไม่ใช่ดูแค่ยอดเงิน
  3. บัญชีจริงด้วยขนาดเล็กที่สุด — ตอบว่า "เรารับพฤติกรรมของมันได้ไหม"
    ขึ้นบัญชีจริงด้วยล็อตต่ำสุดก่อนเสมอ และมีเงื่อนไขปิดสวิตช์ที่เขียนไว้ล่วงหน้าว่าจะหยุดเมื่อไร ⇒ ตัวเลขที่ใช้ตัดสินคืออัตราชนะที่ทำให้เสมอตัวของแผน ไม่ใช่ความรู้สึก

อ่านต่อ

บทความนี้เป็นเนื้อหาให้ความรู้ด้านการเขียนโปรแกรมเท่านั้น ไม่ใช่คำแนะนำการลงทุน โค้ดตัวอย่างมีไว้เพื่อการศึกษาและควรทดสอบบนบัญชีทดลองก่อนเสมอ ตัวเลขราคาและสเปรดทั้งหมดเป็นตัวอย่างสมมติเพื่อให้ผู้อ่านคำนวณตามได้ ไม่ใช่ผลตอบแทนที่รับรอง การเทรดมีความเสี่ยงที่จะสูญเสียเงินลงทุน ผู้อ่านควรศึกษาและตัดสินใจด้วยตนเอง

ความคิดเห็น

แสดงความคิดเห็น

Related stories