เขียน EA ตรวจจับแท่งเทียน Bearish Engulfing — โค้ด MQL4 และ MQL5 ที่คอมไพล์ผ่านจริง

$ ¥ Bearish Engulfing // เงื่อนไขที่ 1-4 if(open[1] > close[1] && open[2] < close[2] && open[2] > close[1] && close[2] < open[1]) return(true); เขียน EA ตรวจจับแท่งเทียน โค้ด MQL4 · MQL5 ที่ใช้ได้จริง โดย คนเล่น Forex บั๊กในโค้ดตัวอย่างเดิม 3 จุด คอมไพล์ไม่ผ่าน 2 · ยิงครั้งเดียว 1 เงื่อนไขที่ตรวจได้จริง 6 ข้อ แปลงจากนิยามเป็นโค้ดทีละบรรทัด ฉบับตรวจโค้ดจริง
เขียน EA

เขียน EA ตรวจจับ Candlestick Pattern — แปลงนิยาม Bearish Engulfing เป็นโค้ด MQL4 และ MQL5 ทีละบรรทัด

หน้านี้พาทำทีละขั้นจากนิยามภาษาคน → เงื่อนไขที่วัดได้ → โค้ดที่คอมไพล์ผ่านจริง โดยใช้ Bearish Engulfing เป็นตัวอย่าง พร้อมตรวจ 3 บั๊กที่พบซ้ำในโค้ดตัวอย่างแบบเก่าที่ลอยอยู่ตามอินเทอร์เน็ต — สองข้อทำให้คอมไพล์ไม่ผ่าน และอีกข้อทำให้ EA ยิงสัญญาณได้ครั้งเดียวตลอดอายุการรัน

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


ขั้นที่ 1 — แปลงนิยามให้เป็นเงื่อนไขที่ตัดสินได้

Bearish Engulfing คือสัญญาณกลับตัวจากขาขึ้นเป็นขาลง ที่ตัวแท่งของแท่งลงกลืนตัวแท่งของแท่งขึ้นก่อนหน้าไว้ทั้งหมด · ถ้ายังไม่คุ้นกับรูปแบบนี้ อ่านความหมายฝั่งการอ่านกราฟได้ที่ Engulfing, Morning Star, Hammer คืออะไร ก่อน แล้วค่อยกลับมาที่ฝั่งโค้ด

แผนภาพ Bearish Engulfing พร้อมหมายเลขแท่ง 0 1 2 3 ตามการนับ shift ของ MetaTrader
ตัวเลขในภาพคือ shift ที่โค้ดใช้อ้างถึงแท่ง — 0 = แท่งที่กำลังวิ่งอยู่ (ยังไม่ปิด) · 1 = แท่งที่ปิดล่าสุด · 2 และ 3 คือแท่งก่อนหน้า ⇒ โค้ดจะไม่แตะแท่ง 0 เลย เพราะราคาปิดของมันยังเปลี่ยนได้
⚠️
จุดที่ต้องตัดสินให้ชัดตั้งแต่ต้น — เราจะเช็ก "การกลืน" ที่ ตัวแท่ง (open กับ close) ไม่ใช่ที่ไส้ (high กับ low) เพราะไส้เทียนขึ้นกับความผันผวนชั่วขณะและเปลี่ยนคำตอบได้ทั้งที่รูปแบบเหมือนเดิม ⇒ ถ้าใครใช้นิยามที่กลืนถึงไส้ด้วย ต้องเปลี่ยนโค้ดเป็น iHigh / iLow แทน และจะได้สัญญาณน้อยลงมาก

เมื่อกำหนดชัดแล้ว นิยามจะแตกออกเป็น 6 เงื่อนไขที่ตรวจได้จากราคา open และ close ล้วน ๆ

#เงื่อนไขภาษาคนเขียนเป็นเงื่อนไขใช้ยืนยันอะไร
1แท่ง 1 ต้องเป็นแท่งลงopen[1] > close[1]แท่งที่จะมากลืนต้องเป็นฝั่งขาย
2แท่ง 2 ต้องเป็นแท่งขึ้นopen[2] < close[2]แท่งที่ถูกกลืนต้องเป็นฝั่งซื้อ
3ตัวแท่ง 1 คลุมด้านล่างของแท่ง 2open[2] > close[1]ปิดของแท่งลงต่ำกว่าเปิดของแท่งขึ้น
4ตัวแท่ง 1 คลุมด้านบนของแท่ง 2close[2] < open[1]เปิดของแท่งลงสูงกว่าปิดของแท่งขึ้น
5แท่ง 3 ต้องเป็นแท่งขึ้นopen[3] < close[3]ยืนยันว่าก่อนหน้านี้เป็นขาขึ้นจริง
6แท่ง 3 ตัวยาวกว่าแท่ง 2body[3] > body[2]ขาขึ้นกำลังอ่อนแรงลงอยู่แล้ว

ข้อ 3 กับ 4 ต้องเป็นจริงพร้อมกัน ถึงจะเรียกว่ากลืน — ขาดข้อใดข้อหนึ่งคือแค่แท่งลงที่ยาวกว่าเดิมเท่านั้น และนั่นคือจุดที่คนเขียนเงื่อนไขพลาดบ่อยที่สุด


ขั้นที่ 2 — ลองตรวจด้วยตัวเลขจริงก่อนเขียนโค้ด

ก่อนแตะโปรแกรม ให้เอาตัวเลขสมมติมาไล่ทีละเงื่อนไขด้วยมือก่อน ถ้าไล่ด้วยมือไม่ได้ แปลว่าเงื่อนไขยังไม่ชัดพอจะเขียนเป็นโค้ด

ไล่ตรวจทีละเงื่อนไขจากราคาจริง วาดตามสัดส่วน: 1 จุด = 8 พิกเซล 1.102001.101001.10000 แท่ง 3แท่ง 2แท่ง 1 O 1.10000O 1.10100O 1.10200 C 1.10120C 1.10180C 1.10060 1. แท่ง 1 เป็นแท่งลง 2. แท่ง 2 เป็นแท่งขึ้น 3. คลุมด้านล่าง 4. คลุมด้านบน 5. แท่ง 3 เป็นแท่งขึ้น 6. แท่ง 3 ยาวกว่าแท่ง 2 ✓ ✓ ✓ ✓ ✓ ✓ ครบ 6 ข้อ = Bearish Engulfing ตัวแท่งของแท่ง 1 คือ 14.0 จุด · ของแท่ง 2 คือ 8.0 จุด · ของแท่ง 3 คือ 12.0 จุด — ทุกช่องคำนวณจากราคาในภาพได้ตรง ๆ
ไล่ตามลำดับ ข้อ 3 กับ 4 คือคู่ที่ตัดสินว่า "กลืน" จริงหรือไม่ — กรอบส้มคือตัวแท่งของแท่ง 1 ที่ต้องคลุมตัวแท่งของแท่ง 2 ให้ครบทั้งบนและล่าง

ทีนี้ลองเปลี่ยนตัวเลขแค่ช่องเดียว — ให้ราคาเปิดของแท่ง 1 เป็น 1.10170 แทน 1.10200 (ต่ำกว่าราคาปิดของแท่ง 2 ที่ 1.10180 อยู่ 1 จุด) จะเห็นว่ารูปแบบยังดูคล้ายเดิมมากบนกราฟ แต่ตกเงื่อนไขทันที

เงื่อนไขชุด A · open[1] = 1.10200ชุด B · open[1] = 1.10170
1. open[1] > close[1]ผ่านผ่าน
2. open[2] < close[2]ผ่านผ่าน
3. open[2] > close[1]ผ่านผ่าน
4. close[2] < open[1]ผ่านไม่ผ่าน — 1.10180 ไม่น้อยกว่า 1.10170
5. open[3] < close[3]ผ่านผ่าน
6. body[3] > body[2]ผ่าน — 12.0 > 8.0ผ่าน — 12.0 > 8.0
สรุปเป็น Bearish Engulfingไม่ใช่ (ตกข้อเดียว)
ระยะห่างระหว่าง "ใช่" กับ "ไม่ใช่" ในตารางนี้คือ 3 จุด — นี่คือเหตุผลที่การเขียนเงื่อนไขให้ชัดสำคัญกว่าการเลือกภาษาโปรแกรม เพราะ เงื่อนไขที่คลุมเครือ 1 ข้อ เปลี่ยนจำนวนสัญญาณทั้งระบบ

ขั้นที่ 3 — โค้ด MQL4 ที่คอมไพล์ผ่าน

โครงมีสามส่วนคือ ตัวช่วยแปลงหน่วยจุด · ฟังก์ชันตรวจแพทเทิร์น · และตัวคุมให้ตรวจแค่ครั้งเดียวต่อแท่ง

// ===== MQL4 : ตรวจจับ Bearish Engulfing ===== input ENUM_TIMEFRAMES InpTF = PERIOD_M5; // กราฟที่ใช้ตรวจ input double InpMinBodyPips = 10.0; // ตัวแท่งขั้นต่ำ (จุด) datetime g_lastBarTime = 0; // เวลาเปิดของแท่งที่ตรวจไปแล้ว // 1 จุด (pip) มีค่ากี่ point — บัญชี 5 หลักและทอง 3 หลักต้องคูณ 10 double PipSize() { int d = (int)MarketInfo(_Symbol, MODE_DIGITS); return((d == 3 || d == 5) ? _Point * 10.0 : _Point); } double BodyPips(int shift) { double body = MathAbs(iClose(_Symbol, InpTF, shift) - iOpen(_Symbol, InpTF, shift)); return(body / PipSize()); } bool IsBearishEngulfing() { double o1 = iOpen (_Symbol, InpTF, 1), c1 = iClose(_Symbol, InpTF, 1); double o2 = iOpen (_Symbol, InpTF, 2), c2 = iClose(_Symbol, InpTF, 2); double o3 = iOpen (_Symbol, InpTF, 3), c3 = iClose(_Symbol, InpTF, 3); if(!(o1 > c1)) return(false); // 1. แท่ง 1 เป็นแท่งลง if(!(o2 < c2)) return(false); // 2. แท่ง 2 เป็นแท่งขึ้น if(!(o2 > c1)) return(false); // 3. คลุมด้านล่าง if(!(c2 < o1)) return(false); // 4. คลุมด้านบน if(!(o3 < c3)) return(false); // 5. แท่ง 3 เป็นแท่งขึ้น if(!((c3 - o3) > (c2 - o2))) return(false); // 6. แท่ง 3 ยาวกว่าแท่ง 2 if(BodyPips(1) < InpMinBodyPips) return(false); // 7. ตัวแท่งต้องใหญ่พอ return(true); } void OnTick() { datetime t = iTime(_Symbol, InpTF, 0); if(t == g_lastBarTime) return; // ยังเป็นแท่งเดิม ไม่ต้องตรวจซ้ำ g_lastBarTime = t; // จำแท่งใหม่ไว้ if(IsBearishEngulfing()) Print("พบ Bearish Engulfing ที่แท่งปิด ", TimeToString(iTime(_Symbol, InpTF, 1))); }
ทำไมต้องมี PipSize() — บัญชี 5 หลัก (EURUSD 1.10000) มี _Point เท่ากับ 0.00001 ⇒ 1 จุดเท่ากับ 10 point ส่วนบัญชี 4 หลักมี 1 จุดเท่ากับ 1 point พอดี · ทองคำที่ตั้งราคา 3 หลักก็เข้าเคสคูณสิบเหมือนกัน ⇒ ถ้าหารด้วย _Point ตรง ๆ ค่าตัวแท่งจะคลาดไป 10 เท่าบนบางสัญลักษณ์ และตัวกรองขนาดแท่งจะไม่ทำงานเลย

ขั้นที่ 4 — โค้ด MQL5 ต่างจาก MQL4 ตรงไหน

เรื่องMQL4MQL5
ฟังก์ชันที่ทำงานทุก tickOnTick() (ของเก่าใช้ start())OnTick() เท่านั้น
อ่านราคาแท่งย้อนหลังiOpen / iClose / iTimeCopyRates ลงอาร์เรย์ MqlRates
ทิศทางของ indexเป็น series อยู่แล้ว (0 = แท่งปัจจุบัน)ต้องเรียก ArraySetAsSeries เอง
เช็กว่าข้อมูลพอไหมดูค่า Barsดูค่าที่ CopyRates คืนกลับมา
จำนวนหลักของราคาMarketInfo(_Symbol, MODE_DIGITS)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS)
// ===== MQL5 : ตรวจจับ Bearish Engulfing ===== input ENUM_TIMEFRAMES InpTF = PERIOD_M5; input double InpMinBodyPips = 10.0; datetime g_lastBarTime = 0; double PipSize() { int d = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS); return((d == 3 || d == 5) ? _Point * 10.0 : _Point); } bool IsBearishEngulfing() { MqlRates r[]; ArraySetAsSeries(r, true); // ให้ index 0 = แท่งปัจจุบัน if(CopyRates(_Symbol, InpTF, 0, 4, r) < 4) return(false); // ข้อมูลยังไม่พอ 4 แท่ง if(!(r[1].open > r[1].close)) return(false); // 1 if(!(r[2].open < r[2].close)) return(false); // 2 if(!(r[2].open > r[1].close)) return(false); // 3 if(!(r[2].close < r[1].open)) return(false); // 4 if(!(r[3].open < r[3].close)) return(false); // 5 if(!((r[3].close - r[3].open) > (r[2].close - r[2].open))) return(false); // 6 if(MathAbs(r[1].close - r[1].open) / PipSize() < InpMinBodyPips) return(false); // 7 return(true); } void OnTick() { datetime t[]; if(CopyTime(_Symbol, InpTF, 0, 1, t) < 1) return; if(t[0] == g_lastBarTime) return; g_lastBarTime = t[0]; if(IsBearishEngulfing()) Print("พบ Bearish Engulfing"); }

3 บั๊กในโค้ดตัวอย่างแบบเก่า — ตรวจแล้วทั้งสามข้อ

โค้ดตัวอย่างรุ่นเก่าที่ลอยอยู่ตามเว็บไทย (รวมถึงเวอร์ชันเดิมของหน้านี้เอง) มีปัญหาสามข้อที่ทำให้เอาไปใช้ต่อไม่ได้จริง

1

วงเล็บไม่ครบในเงื่อนไขข้อ 5

บรรทัด if(!((iOpen(...) < iClose(...))) เปิดวงเล็บ 1 อันเกินมา ⇒ นับแล้วเหลือค้าง 1 วงเล็บ เทียบกับข้อ 3 ที่เขียนถูกและปิดครบพอดี ⇒ คอมไพล์ไม่ผ่าน ตั้งแต่บรรทัดนี้

2

บล็อกคอมเมนต์ไม่ปิด

bool OpenOrder(int cmd) { /* … ครับ } เปิด /* แล้วไม่มี */ ⇒ คอมเมนต์กลืนวงเล็บปีกกาที่ปิดฟังก์ชันและทุกอย่างที่เหลือของไฟล์ ⇒ คอมไพล์ไม่ผ่านอีกจุด และฟังก์ชันก็ไม่ได้คืนค่า bool ตามที่ประกาศไว้

3

ตัวคุม "แท่งใหม่" ยิงได้ครั้งเดียว

โค้ดเดิมเก็บ pbl_Bars = Bars + 1 ตอนเริ่ม แล้วตรวจว่า pbl_Bars == Bars ก่อนจะ pbl_Bars-- ⇒ พอ Bars เพิ่มขึ้นเรื่อย ๆ แต่ตัวนับถูกลดลง ทั้งสองค่าไม่มีวันเท่ากันอีกเลย ⇒ EA ตรวจแค่ครั้งเดียวแล้วเงียบตลอดไป

แท่งใหม่ 8 แท่ง — ตัวคุมสองแบบให้ผลต่างกันคนละเรื่อง โค้ดเดิม นับ Bars ขึ้น ตัวแปรลง แก้แล้ว จำเวลาเปิดแท่ง 1234 5678 แท่งใหม่ลำดับที่ 1 8 วงกลมเขียว = ตรวจแพทเทิร์นจริง · วงกลมเทา = ข้ามไปเฉย ๆ · ตัวเลขขวาสุดคือจำนวนครั้งที่ตรวจใน 8 แท่ง
จำลองตามตรรกะของโค้ดทั้งสองแบบตรง ๆ — แบบเดิมตรวจได้ครั้งเดียวแล้วเงียบ ส่วนแบบที่จำเวลาเปิดแท่งตรวจครบทุกแท่ง
เรื่อง "หนึ่งไม้ต่อหนึ่งแท่ง" มีรายละเอียดมากกว่านี้อีกมาก ทั้งวิธีตรวจว่า EA ของคุณมีอาการยิงซ้ำหรือไม่ และตัวกรองอีก 3 แบบพร้อมโค้ดทั้งสองภาษา ⇒ อ่านต่อได้ที่ EA เปิดออร์เดอร์ซ้ำในแท่งเดียว หน้านี้จะไม่เขียนซ้ำ

ตรวจเจอแพทเทิร์นแล้วยังไงต่อ — สองอย่างที่โค้ดนี้ยังไม่ได้ทำ

อย่างแรก: ต้องกรองขนาดตัวแท่ง เพราะ Bearish Engulfing บนแท่งจิ๋วเกิดได้ตลอดเวลาและไม่มีความหมาย ตารางนี้คือเหตุผลที่โค้ดข้างบนมีเงื่อนไขข้อ 7 (InpMinBodyPips) ติดมาด้วย

ตัวแท่งของแท่งที่กลืนสเปรดกินกี่ % ของตัวแท่งตัวแท่งเป็นกี่เท่าของสเปรดใช้ได้ไหม
3 จุด50.0%2.0 เท่าไม่ควรใช้
5 จุด30.0%3.3 เท่ายังแพงมาก
10 จุด15.0%6.7 เท่าเริ่มพอไหว
14 จุด10.7%9.3 เท่าใช้ได้
25 จุด6.0%16.7 เท่าดี

คิดที่สเปรด 1.5 จุด ⇒ ตัวแท่ง 3 จุดหมายความว่าครึ่งหนึ่งของแท่งที่คุณเห็นเป็นต้นทุน ไม่ใช่การเคลื่อนไหวของราคา · กฎง่าย ๆ คือตั้ง InpMinBodyPips ให้ตัวแท่งเป็นอย่างน้อย 8–10 เท่าของสเปรดปกติของคู่นั้น

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

⚠️
อย่าต่อโค้ดส่งคำสั่งเข้าไปแล้วรันบนบัญชีจริงทันที — ทดสอบบน บัญชีทดลอง (demo) ให้เห็นพฤติกรรมจริงก่อนเสมอ และดูใน Journal ว่ามันตรวจเจอแท่งละครั้งจริงตามที่ตั้งใจหรือไม่ · EA ที่ตรรกะถูกแต่ยิงผิดจังหวะสร้างความเสียหายได้เร็วกว่าการเทรดมือมาก

คำถามที่พบบ่อย

  • ทำไมไม่ตรวจแท่ง 0 — แท่ง 0 คือแท่งที่ยังวิ่งอยู่ ราคาปิดของมันเปลี่ยนได้ตลอดจนกว่าจะหมดเวลาแท่ง ⇒ ถ้าตรวจแท่ง 0 สัญญาณจะโผล่แล้วหายไประหว่างแท่ง ซึ่งเป็นคนละเรื่องกับสัญญาณที่ยืนยันแล้ว · หลักการเดียวกับเรื่องเบรกหลอก คือรอราคาปิด
  • ใช้กับ Bullish Engulfing ต้องแก้อะไร — กลับเครื่องหมายเปรียบเทียบทั้ง 6 ข้อ (มากกว่าเป็นน้อยกว่า) และสลับบทบาทแท่ง 1 กับแท่ง 2 ให้แท่ง 1 เป็นแท่งขึ้น ⇒ โครงฟังก์ชันเหมือนเดิมทั้งหมด
  • ทำไมต้องมีเงื่อนไขข้อ 5 และ 6 ด้วย — สองข้อนี้ยืนยันว่าก่อนหน้านั้นเป็นขาขึ้นจริง เพราะสัญญาณกลับตัวจะมีความหมายก็ต่อเมื่อมีอะไรให้กลับ ⇒ ถ้าตัดออกจะได้สัญญาณเยอะขึ้นมาก แต่ส่วนที่เพิ่มมาคือแท่งที่เกิดกลางกรอบแคบซึ่งไม่ได้กลับตัวอะไร
  • โค้ดนี้ใช้กับกราฟไหนได้บ้าง — ใช้ได้ทุกสัญลักษณ์และทุกไทม์เฟรม เพราะอ่านค่าจาก _Symbol และ InpTF ⇒ แต่ค่า InpMinBodyPips ต้องตั้งใหม่ทุกครั้งที่เปลี่ยนสัญลักษณ์ เพราะขนาดแท่งปกติของแต่ละตัวไม่เท่ากันเลย
  • ยังใช้ start() แทน OnTick() ได้ไหม — MT4 รุ่นใหม่ยังคอมไพล์ผ่านเพื่อความเข้ากันได้ แต่ MQL5 ไม่รู้จัก start() ⇒ เขียนเป็น OnTick() ตั้งแต่แรกจะย้ายไป MT5 ได้โดยแก้แค่ส่วนอ่านราคา

สรุป

ลำดับที่ใช้ได้จริงคือ นิยาม → เงื่อนไขที่ตัดสินได้ → ไล่ด้วยตัวเลขจริง → ค่อยเขียนโค้ด ถ้าข้ามขั้นที่สามไป จะเขียนโค้ดที่คอมไพล์ผ่านแต่ตรวจคนละอย่างกับที่ตั้งใจ และหาสาเหตุไม่เจอ เพราะโปรแกรมไม่ได้ผิด — นิยามต่างหากที่ยังไม่ชัด

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

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

ความคิดเห็น

Related stories