เขียน EA ตรวจจับ Candlestick Pattern — แปลงนิยาม Bearish Engulfing เป็นโค้ด MQL4 และ MQL5 ทีละบรรทัด
หน้านี้พาทำทีละขั้นจากนิยามภาษาคน → เงื่อนไขที่วัดได้ → โค้ดที่คอมไพล์ผ่านจริง โดยใช้ Bearish Engulfing เป็นตัวอย่าง พร้อมตรวจ 3 บั๊กที่พบซ้ำในโค้ดตัวอย่างแบบเก่าที่ลอยอยู่ตามอินเทอร์เน็ต — สองข้อทำให้คอมไพล์ไม่ผ่าน และอีกข้อทำให้ EA ยิงสัญญาณได้ครั้งเดียวตลอดอายุการรัน
สิ่งที่ทำให้การเขียน EA ตรวจแพทเทิร์นยากไม่ใช่ภาษาโปรแกรม แต่คือการเปลี่ยนคำอธิบายที่คลุมเครือให้เป็นเงื่อนไขที่คอมพิวเตอร์ตัดสินได้ คำว่า "แท่งแดงครอบแท่งเขียว" ตัดสินไม่ได้จนกว่าจะระบุว่าครอบตัวแท่งหรือครอบไส้ และครอบทั้งสองด้านหรือด้านเดียว บทความนี้จึงเริ่มจากตรงนั้นก่อน
ขั้นที่ 1 — แปลงนิยามให้เป็นเงื่อนไขที่ตัดสินได้
Bearish Engulfing คือสัญญาณกลับตัวจากขาขึ้นเป็นขาลง ที่ตัวแท่งของแท่งลงกลืนตัวแท่งของแท่งขึ้นก่อนหน้าไว้ทั้งหมด · ถ้ายังไม่คุ้นกับรูปแบบนี้ อ่านความหมายฝั่งการอ่านกราฟได้ที่ Engulfing, Morning Star, Hammer คืออะไร ก่อน แล้วค่อยกลับมาที่ฝั่งโค้ด
iHigh / iLow แทน และจะได้สัญญาณน้อยลงมากเมื่อกำหนดชัดแล้ว นิยามจะแตกออกเป็น 6 เงื่อนไขที่ตรวจได้จากราคา open และ close ล้วน ๆ
| # | เงื่อนไขภาษาคน | เขียนเป็นเงื่อนไข | ใช้ยืนยันอะไร |
|---|---|---|---|
| 1 | แท่ง 1 ต้องเป็นแท่งลง | open[1] > close[1] | แท่งที่จะมากลืนต้องเป็นฝั่งขาย |
| 2 | แท่ง 2 ต้องเป็นแท่งขึ้น | open[2] < close[2] | แท่งที่ถูกกลืนต้องเป็นฝั่งซื้อ |
| 3 | ตัวแท่ง 1 คลุมด้านล่างของแท่ง 2 | open[2] > close[1] | ปิดของแท่งลงต่ำกว่าเปิดของแท่งขึ้น |
| 4 | ตัวแท่ง 1 คลุมด้านบนของแท่ง 2 | close[2] < open[1] | เปิดของแท่งลงสูงกว่าปิดของแท่งขึ้น |
| 5 | แท่ง 3 ต้องเป็นแท่งขึ้น | open[3] < close[3] | ยืนยันว่าก่อนหน้านี้เป็นขาขึ้นจริง |
| 6 | แท่ง 3 ตัวยาวกว่าแท่ง 2 | body[3] > body[2] | ขาขึ้นกำลังอ่อนแรงลงอยู่แล้ว |
ข้อ 3 กับ 4 ต้องเป็นจริงพร้อมกัน ถึงจะเรียกว่ากลืน — ขาดข้อใดข้อหนึ่งคือแค่แท่งลงที่ยาวกว่าเดิมเท่านั้น และนั่นคือจุดที่คนเขียนเงื่อนไขพลาดบ่อยที่สุด
ขั้นที่ 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 — โค้ด 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 ตรงไหน
| เรื่อง | MQL4 | MQL5 |
|---|---|---|
| ฟังก์ชันที่ทำงานทุก tick | OnTick() (ของเก่าใช้ start()) | OnTick() เท่านั้น |
| อ่านราคาแท่งย้อนหลัง | iOpen / iClose / iTime | CopyRates ลงอาร์เรย์ 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 บั๊กในโค้ดตัวอย่างแบบเก่า — ตรวจแล้วทั้งสามข้อ
โค้ดตัวอย่างรุ่นเก่าที่ลอยอยู่ตามเว็บไทย (รวมถึงเวอร์ชันเดิมของหน้านี้เอง) มีปัญหาสามข้อที่ทำให้เอาไปใช้ต่อไม่ได้จริง
วงเล็บไม่ครบในเงื่อนไขข้อ 5
บรรทัด if(!((iOpen(...) < iClose(...))) เปิดวงเล็บ 1 อันเกินมา ⇒ นับแล้วเหลือค้าง 1 วงเล็บ เทียบกับข้อ 3 ที่เขียนถูกและปิดครบพอดี ⇒ คอมไพล์ไม่ผ่าน ตั้งแต่บรรทัดนี้
บล็อกคอมเมนต์ไม่ปิด
bool OpenOrder(int cmd) { /* … ครับ } เปิด /* แล้วไม่มี */ ⇒ คอมเมนต์กลืนวงเล็บปีกกาที่ปิดฟังก์ชันและทุกอย่างที่เหลือของไฟล์ ⇒ คอมไพล์ไม่ผ่านอีกจุด และฟังก์ชันก็ไม่ได้คืนค่า bool ตามที่ประกาศไว้
ตัวคุม "แท่งใหม่" ยิงได้ครั้งเดียว
โค้ดเดิมเก็บ pbl_Bars = Bars + 1 ตอนเริ่ม แล้วตรวจว่า pbl_Bars == Bars ก่อนจะ pbl_Bars-- ⇒ พอ Bars เพิ่มขึ้นเรื่อย ๆ แต่ตัวนับถูกลดลง ทั้งสองค่าไม่มีวันเท่ากันอีกเลย ⇒ 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 เท่าของสเปรดปกติของคู่นั้น
อย่างที่สอง: ตรวจเจอแพทเทิร์นไม่เท่ากับมีความได้เปรียบ — โค้ดนี้บอกได้แค่ว่า "รูปแบบนี้เกิดขึ้นแล้ว" ไม่ได้บอกว่าเข้าไม้แล้วจะกำไร ⇒ ก่อนเอาไปต่อกับคำสั่งซื้อขาย ต้องมีจุดสตอป เป้าหมาย และอัตราชนะที่ทำให้เสมอตัวของแผนนั้นก่อน ไม่ใช่เข้าเพราะเจอรูปแบบเฉย ๆ
คำถามที่พบบ่อย
- ทำไมไม่ตรวจแท่ง 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
บทความนี้เป็นเนื้อหาให้ความรู้ด้านการเขียนโปรแกรมเท่านั้น ไม่ใช่คำแนะนำการลงทุน โค้ดตัวอย่างมีไว้เพื่อการศึกษาและควรทดสอบบนบัญชีทดลองก่อนเสมอ ตัวเลขราคาและสเปรดทั้งหมดเป็นตัวอย่างสมมติเพื่อให้ผู้อ่านคำนวณตามได้ ไม่ใช่ผลตอบแทนที่รับรอง การเทรดมีความเสี่ยงที่จะสูญเสียเงินลงทุน ผู้อ่านควรศึกษาและตัดสินใจด้วยตนเอง
ความคิดเห็น
แสดงความคิดเห็น