ทำให้ EA หยุดเองเมื่อขาดทุนถึงลิมิตต่อวัน — Daily Loss Limit ที่ใช้ได้จริง (2026)

Somkiat (ThaiMQL)14 นาที
ทำให้ EA หยุดเองเมื่อขาดทุนถึงลิมิตต่อวัน — Daily Loss Limit ที่ใช้ได้จริง (2026)

มีเทรดเดอร์คนหนึ่งทักมาหาผมตอนเจ็ดโมงเช้า ข้อความแรกคือภาพหน้าจอแดชบอร์ดของ prop firm ขึ้นตัวแดงคำเดียว: Daily Loss Limit Breached

เขาสอบ challenge มาสองรอบแล้ว รอบนี้ผ่านครึ่งทางมาด้วยดี EA ที่ใช้ก็ "มี daily loss limit" — เขาย้ำคำนี้กับผมหลายรอบ เพราะตอนซื้อมา คนขายบอกว่ามี และในหน้า input ของ EA ก็มีช่องให้กรอกจริงๆ

คืนที่พัง ราคาวิ่งแรงช่วงตีสองตามเวลาไทย ไม้ที่ลอยอยู่ติดลบลึกลงเรื่อยๆ EA ไม่ได้ทำอะไรเลยจนกว่าแท่ง H1 ใหม่จะเปิด พอมันตื่นมาเช็ก ตัวเลขก็เลยเส้นที่ prop firm ขีดไว้ไปแล้ว ไม่มีปุ่มย้อนเวลา บัญชีจบตรงนั้น ค่าสอบหายไปอีกหนึ่งรอบ — ทั้งที่ ถ้า EA ตัดตัวเองเร็วกว่านั้นสักสองนาที บัญชีจะยังอยู่

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

บทความนี้จะพาทำ circuit breaker หรือเบรกเกอร์ตัดวงจรให้ EA: ขาดทุนถึงลิมิตของวัน → ปิดไม้ทั้งหมด → หยุดเทรดจนกว่าจะขึ้นวันใหม่ โดยเจาะจุดที่คนพลาดจริงๆ ทีละจุด ตั้งแต่เรื่องที่ฟังดูง่ายที่สุดอย่าง "วันใหม่เริ่มตอนไหน" (คำตอบไม่ใช่เที่ยงคืนบ้านเรา) ไปจนถึงกับดักในโค้ดที่ทำให้ปิดไม้ไม่ครบแบบเงียบๆ

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


ทำไม EA ส่วนใหญ่ "มี daily loss limit" แต่คุมไม่อยู่จริง

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

ชั้นที่ 1: เช็กลิมิตเฉพาะตอนเปิดแท่งใหม่ ไม่ได้เช็กทุก tick

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

ลองนึกภาพ EA ที่รันบน H1 แล้วเช็กลิมิตแค่ตอนแท่งเปิด: ระหว่าง 60 นาทีในแท่งนั้น ราคาจะวิ่งไปไหนก็ได้ ข่าวแรงออกตอนนาทีที่ 5 ไม้ที่ลอยอยู่ติดลบทะลุลิมิตตอนนาทีที่ 12 — EA จะรู้ตัวตอนนาทีที่ 60 ซึ่งความเสียหายจริงอาจเลยลิมิตไปแล้วเท่าตัว

ลิมิตขาดทุนไม่ใช่สัญญาณเทรด มันคือระบบความปลอดภัย และระบบความปลอดภัยต้องทำงาน ทุก tick เหมือนเบรกรถที่ต้องพร้อมทุกวินาที ไม่ใช่เช็กทุกต้นชั่วโมง การเช็กแบบนี้แทบไม่กินเครื่องเลย (เดี๋ยวเห็นในโค้ดว่ามันคือการบวกเลขไม่กี่ค่า) ดังนั้นข้ออ้างเรื่อง performance ฟังไม่ขึ้น

ชั้นที่ 2: นับแต่ขาดทุนที่ "ปิดแล้ว" ไม่นับไม้ที่ยังลอยอยู่

อันนี้เจ็บกว่าชั้นแรก เพราะมัน ดูเหมือนทำงานถูก — EA คำนวณผลรวมของไม้ที่ปิดไปแล้ววันนี้ (realized P&L) เทียบกับลิมิต ตัวเลขใน log สวยงาม ทุกอย่างดูปกติ

แต่ prop firm แทบทุกเจ้าวัด daily loss จาก equity ไม่ใช่จากไม้ที่ปิดแล้ว แปลว่าไม้ที่ยังเปิดอยู่และกำลังติดลบ (floating loss) ถูกนับเต็มๆ ในสายตาของเขา ขณะที่ EA ของคุณไม่เห็นมันเลย

สถานการณ์คลาสสิก: วันนี้ยังไม่ปิดไม้ขาดทุนสักไม้ realized = 0 EA รายงานว่า "ยังห่างลิมิตอีกเยอะ" — แต่มีไม้ลอยติดลบอยู่ 4% ของพอร์ต บวก swap ที่โดนหักไปเมื่อคืนอีกนิด equity จริงเลยเส้นไปแล้ว EA ที่นับแต่ realized จะยิงไม้ใหม่เพิ่มด้วยซ้ำ เพราะในบัญชีของมัน วันนี้ยังขาวสะอาด

ระบบสาย Grid และ Martingale คือกลุ่มที่ตายด้วยข้อนี้บ่อยที่สุด เพราะธรรมชาติของมันคือถือไม้ติดลบหลายไม้พร้อมกันโดยไม่ปิด — realized ดูสวยตลอด จน equity พังในครั้งเดียว

ชั้นที่ 3: ตรวจเจอแล้ว แต่ไม่มี "ประตู" กันยิงออเดอร์ใหม่

ชั้นสุดท้ายคือชั้นที่คนเขียนโค้ดเองพลาดบ่อย: EA ตรวจเจอว่าเกินลิมิต ปิดไม้เรียบร้อย print ข้อความสวยงามว่า "Daily limit reached!" — แล้วบรรทัดถัดมา ตรรกะเทรดปกติของมันก็เจอสัญญาณเข้า และยิงออเดอร์ใหม่ทันที

เพราะการ "ตรวจเจอ" กับการ "ห้ามเทรด" เป็นคนละเรื่องกัน ถ้าไม่มีสถานะกลาง (ผมจะเรียกว่า HALTED) ที่ทุกจุดในโค้ดต้องเช็กก่อนส่งออเดอร์ การตรวจเจอก็เป็นแค่การพิมพ์ log หนึ่งบรรทัด ไม้ใหม่จะถูกเปิด ขาดทุนต่อ แล้ววนกลับมาปิด-เปิดใหม่ไปเรื่อยๆ ทั้งวัน ซึ่งแย่กว่าไม่มีลิมิตเสียอีก เพราะเสียทั้งเงินทั้ง spread ทุกรอบที่วน

สรุปสามชั้นเป็นตารางให้เห็นภาพ:

จุดพลาด อาการที่เห็น ผลลัพธ์จริง
เช็กเฉพาะแท่งใหม่ log ปกติดี ไม่มี error รู้ตัวช้าสุด 1 แท่งเต็มๆ — ขาดทุนเลยลิมิตไปไกลแล้ว
นับแต่ realized ตัวเลขใน log ยังไม่เกินลิมิต equity จริงเกินไปแล้วเพราะ floating + swap ไม่ถูกนับ
ไม่มีประตู HALTED ปิดไม้แล้ว print แจ้งเตือนด้วย เปิดไม้ใหม่ในวินาทีถัดมา วนขาดทุนซ้ำทั้งวัน

เบรกเกอร์ที่ใช้ได้จริงต้องอุดครบทั้งสามชั้น: เช็กทุก tick · นับ P&L รวมทั้ง realized และ floating · มีสถานะ HALTED ที่ปิดประตูจริง — สามหัวข้อถัดไปคือการลงมือทำทีละชั้น แต่ก่อนถึงโค้ด ต้องเคลียร์คำถามที่ฟังดูโง่ที่สุดแต่ทำเงินหายมากที่สุดก่อน: "วันนี้" ของ EA เริ่มตอนไหน


เส้นแบ่งวันคือเที่ยงคืน "เวลาเซิร์ฟเวอร์" — จุดที่คนไทยพลาดมากที่สุด

ถ้าให้เลือกจุดเดียวจากบทความนี้ไปตรวจ EA ที่คุณใช้อยู่ ผมให้ตรวจจุดนี้ก่อนเลย เพราะมันเป็นจุดที่พลาดกันเยอะที่สุด และพลาดแบบไม่รู้ตัวนานเป็นเดือน

คำว่า "ขาดทุนต่อวัน" ต้องมีนิยามว่า "วัน" เริ่มและจบตรงไหน สมองคนไทยจะตอบอัตโนมัติว่าเที่ยงคืนบ้านเรา — แต่ EA ไม่ได้อยู่บ้านเรา มันอยู่กับเซิร์ฟเวอร์ของโบรก และนาฬิกาเดียวที่มันเห็นผ่าน TimeCurrent() คือ เวลาเซิร์ฟเวอร์โบรก

โบรกส่วนใหญ่ในตลาด forex ตั้งเวลาเซิร์ฟเวอร์เป็น GMT+2 หรือ GMT+3 (เพื่อให้แท่ง daily ตรงกับรอบตลาดนิวยอร์กปิด) ขณะที่ไทยอยู่ GMT+7 ลองเทียบดู:

เวลาเซิร์ฟเวอร์โบรก เที่ยงคืนเซิร์ฟเวอร์ = เวลาไทย แปลว่า "วันใหม่" ของ EA เริ่มตอน
GMT+2 ตี 5 เช้าบ้านเรา 05:00 น. ตามเวลาไทย
GMT+3 (ช่วง DST) ตี 4 เช้าบ้านเรา 04:00 น. ตามเวลาไทย

แผนภาพเทียบเส้นเวลาไทยกับเวลาเซิร์ฟเวอร์โบรก GMT+2/GMT+3 แสดงว่าเที่ยงคืนเซิร์ฟเวอร์ตรงกับตี 4 ถึงตี 5 ตามเวลาไทย และช่วงตี 3 ถึงตี 5 ของไทยคือรอยต่อคาบเกี่ยวสองวันเทรดในสายตาของ EA

ผลที่ตามมาในชีวิตจริงมีสองเรื่องที่ต้องรู้:

เรื่องแรก — ลิมิตไม่รีเซ็ตตอนเที่ยงคืนบ้านเรา สมมติคุณขาดทุนหนักตอน 4 ทุ่มคืนวันจันทร์ เบรกเกอร์ตัดแล้วหยุดเทรด คุณเข้านอนคิดว่า "พรุ่งนี้เริ่มใหม่" — แต่ตี 1 ของบ้านเราคืนนั้นยังเป็น "วันจันทร์" ของเซิร์ฟเวอร์อยู่เลย EA จะไม่ปลดล็อกจนกว่าจะถึงตี 4-5 เช้าวันอังคาร ถ้าคุณเขียนโค้ดให้รีเซ็ตตามเวลาไทย EA จะกลับมาเทรดตอนเที่ยงคืนทั้งที่โควตาขาดทุนของ "วันเซิร์ฟเวอร์" นั้นหมดไปแล้ว — เท่ากับเปิดโอกาสขาดทุนสองเด้งในวันเดียวของ prop firm

เรื่องที่สอง — ช่วงตี 3 ถึงตี 5 คือรอยต่ออันตราย ถ้าคุณ (หรือ EA) เทรดช่วงนั้น คุณกำลังคาบเกี่ยวสองวันเทรดในสายตาของระบบ ไม้ที่เปิดตอนตี 3 อาจไปปิดตอนตี 5 ซึ่งข้ามเส้นแบ่งวันไปแล้ว — ขาดทุนของไม้นั้นจะไปตกอยู่ "วันใหม่" ทั้งที่ความรู้สึกเราคือคืนเดียวกัน ยิ่งใครเทรดช่วงตลาดอเมริกาปิด-เอเชียเปิด ต้องเข้าใจรอยต่อนี้ให้ชัดก่อนตั้งลิมิต

เช็กเวลาเซิร์ฟเวอร์ของโบรกตัวเองยังไง

ง่ายที่สุดคือให้ EA พิมพ์เทียบสองค่าออกมาดูกัน:

// เทียบเวลาเซิร์ฟเวอร์กับเวลาเครื่องเรา — รันครั้งเดียวก็รู้ offset
void PrintServerOffset()
  {
   Print("เวลาเซิร์ฟเวอร์โบรก : ", TimeToString(TimeCurrent(), TIME_DATE|TIME_MINUTES));
   Print("เวลาเครื่องเรา (ไทย): ", TimeToString(TimeLocal(),   TIME_DATE|TIME_MINUTES));
   // ผลต่างสองค่านี้คือ offset — โบรก GMT+2 จะห่างจากไทยราว 5 ชั่วโมง
  }

TimeCurrent() คือเวลาเซิร์ฟเวอร์ ณ tick ล่าสุด ส่วน TimeLocal() คือเวลาเครื่องที่รัน MT5 (ถ้าเครื่องคุณตั้งเวลาไทย ก็คือเวลาไทย) ผลต่างของสองค่านี้บอก offset ทันที ไม่ต้องไปถาม support โบรก

และพอรู้แล้วว่า "วัน" ของเซิร์ฟเวอร์คืออะไร การหา "เที่ยงคืนของวันนี้" ในโค้ดก็สั้นมาก:

// หาเที่ยงคืนของ "วันนี้" ตามเวลาเซิร์ฟเวอร์
datetime DayStart()
  {
   datetime now = TimeCurrent();
   return now - (now % 86400);   // ตัดเศษชั่วโมง/นาที/วินาทีทิ้ง เหลือ 00:00 ของวัน
  }

ค่านี้คือจุดตั้งต้นของทุกอย่างในบทความนี้ — ใช้เป็นขอบล่างตอนดึงประวัติเทรด และใช้คำนวณเวลาปลดล็อกของเบรกเกอร์

คำเตือนสองข้อก่อนไปต่อ

หนึ่ง — offset ของโบรกไม่ได้นิ่งตลอดปี โบรกจำนวนมากขยับเวลาเซิร์ฟเวอร์ตาม Daylight Saving Time ของฝั่งยุโรป/อเมริกา คือสลับระหว่าง GMT+2 กับ GMT+3 ปีละสองครั้ง (ราวปลายมีนาคมและปลายตุลาคม) แปลว่า "ตี 5" ที่คุณจำไว้ อาจกลายเป็น "ตี 4" ในอีกครึ่งปี ถ้าโค้ดของคุณอิง TimeCurrent() ตรงๆ แบบข้างบน ตัวโค้ดจะปรับตามเองอัตโนมัติ — แต่ ความเข้าใจของคุณ ต้องปรับตามด้วย โดยเฉพาะถ้าคุณตั้งเวลาห้ามเทรดช่วงรอยต่อวันไว้

สอง — สำคัญมากสำหรับสาย prop firm: "วัน" ของ prop firm อาจไม่ใช่ "วัน" ของโบรก prop firm บางเจ้านับ daily loss ตาม timezone ของบริษัทตัวเอง เช่น ตามเวลายุโรปกลาง หรือรีเซ็ตตอนห้าโมงเย็นเวลานิวยอร์ก ซึ่งอาจไม่ตรงกับเที่ยงคืนเซิร์ฟเวอร์ของโบรกที่บัญชีคุณรันอยู่ ถ้าสองนาฬิกานี้ไม่ตรงกัน คุณจะเจอสถานการณ์พิลึก: EA คุมลิมิตถูกต้องทุกอย่างตามวันของโบรก แต่ prop firm ตัดสินคุณด้วยวันของเขา — คุมถูกวิธี แต่ผิดวัน ก่อนใช้งานจริงต้องเข้าไปอ่านนิยาม "daily" ในกฎของเจ้าที่คุณสอบ แล้วเทียบกับเวลาเซิร์ฟเวอร์บัญชีให้ตรงกัน ถ้าไม่ตรง ทางแก้ที่ปลอดภัยคือขยับเส้นแบ่งวันในโค้ดให้ตามนิยามของ prop firm (เลื่อน DayStart() ด้วย offset คงที่) หรืออย่างน้อยตั้งลิมิตภายในให้เผื่อมากขึ้น


คำนวณผลขาดทุนของวันให้ถูก — realized บวก floating บวก swap

ได้เส้นแบ่งวันแล้ว ต่อไปคือคำถามที่สอง: "วันนี้ขาดทุนไปเท่าไหร่แล้ว" — ตัวเลขนี้ต้องประกอบจากสองก้อนเสมอ

ก้อนแรก: realized — กำไร/ขาดทุนของไม้ที่ปิดไปแล้วตั้งแต่เที่ยงคืนเซิร์ฟเวอร์ ก้อนที่สอง: floating — กำไร/ขาดทุนของไม้ที่ยังเปิดค้างอยู่ ณ วินาทีนี้

ขาดก้อนใดก้อนหนึ่ง ตัวเลขที่ได้คือคนละเรื่องกับ equity ที่ prop firm มองอยู่

ก้อนแรก: อ่าน realized จากประวัติ deal

ใน MT5 การอ่านผลของไม้ที่ปิดแล้วทำผ่านประวัติ deal: เลือกช่วงเวลาด้วย HistorySelect() แล้ววนอ่านทีละ deal

// ผลกำไร/ขาดทุนที่ "ปิดแล้ว" ของวันนี้ (ตามวันเซิร์ฟเวอร์)
double RealizedToday()
  {
   double total = 0.0;
   HistorySelect(DayStart(), TimeCurrent());
   for(int i = 0; i < HistoryDealsTotal(); i++)
     {
      ulong ticket = HistoryDealGetTicket(i);
      long  entry  = HistoryDealGetInteger(ticket, DEAL_ENTRY);
      if(entry != DEAL_ENTRY_OUT && entry != DEAL_ENTRY_INOUT)
         continue;   // นับเฉพาะ deal ขาออก — deal ขาเข้าไม่มี P&L ของตัวเอง
      total += HistoryDealGetDouble(ticket, DEAL_PROFIT)
             + HistoryDealGetDouble(ticket, DEAL_SWAP)
             + HistoryDealGetDouble(ticket, DEAL_COMMISSION);
     }
   return total;
  }

จุดที่คนพลาดในโค้ดสั้นๆ นี้มีสองจุด:

จุดแรก — ต้องกรองเฉพาะ DEAL_ENTRY_OUT กับ DEAL_ENTRY_INOUT ใน MT5 การเปิดไม้หนึ่งครั้งและปิดหนึ่งครั้งจะสร้าง deal สองรายการ: deal ขาเข้า (IN) กับ deal ขาออก (OUT) — กำไร/ขาดทุนทั้งหมดถูกบันทึกไว้ที่ deal ขาออกเท่านั้น deal ขาเข้ามีกำไรเป็นศูนย์เสมอเพราะมันยังไม่มีอะไรให้วัด การวนบวกทุก deal โดยไม่กรองจึงได้แต่ศูนย์มารกการคำนวณเปล่าๆ และที่แย่กว่านั้นคือใน history ยังมี deal ประเภทอื่นปนอยู่ เช่น รายการฝาก/ถอนเงินหรือ balance adjustment ซึ่งถ้าเผลอนับเข้าไป ตัวเลข "ขาดทุนวันนี้" จะเพี้ยนทันทีที่มีการเติมเงิน ส่วน DEAL_ENTRY_INOUT คือกรณีไม้ reverse (ปิดฝั่งหนึ่งแล้วเปิดสวนในคำสั่งเดียว) ซึ่งมี P&L ของขาที่ถูกปิดติดมาด้วย ต้องนับเสมอ

จุดที่สอง — ต้องบวก swap และ commission เข้าไปด้วย เพราะ prop firm วัดจาก equity ซึ่งโดนหักสองค่านี้ไปแล้วจริงๆ EA ที่นับแต่ DEAL_PROFIT เปล่าๆ จะรายงานขาดทุนน้อยกว่าความจริงเสมอ ระบบที่เทรดถี่โดน commission กิน ระบบที่ถือข้ามคืนโดน swap กิน — ไม่มีระบบไหนรอดทั้งคู่

ก้อนที่สอง: อ่าน floating จากไม้ที่เปิดอยู่

// ผลกำไร/ขาดทุน "ลอยตัว" ของไม้ที่ยังเปิดอยู่ทั้งหมด
double FloatingNow()
  {
   double total = 0.0;
   for(int i = 0; i < PositionsTotal(); i++)
     {
      if(PositionGetTicket(i) == 0) continue;   // เลือกไม้เข้า context ไปพร้อมกัน
      total += PositionGetDouble(POSITION_PROFIT)
             + PositionGetDouble(POSITION_SWAP); // swap คือต้นทุนที่เกิดขึ้นแล้วจริง
     }
   return total;
  }

สังเกตว่าผมบวก POSITION_SWAP เข้าไปด้วย — หลายคนตัดออกเพราะคิดว่า "ยังไม่ปิดไม้ ยังไม่จ่ายจริง" ซึ่งเข้าใจผิดครับ swap ถูกคิดเข้าบัญชีทุกคืนที่ถือข้าม ไม่ว่าไม้จะปิดหรือยัง มันคือต้นทุนที่ เกิดขึ้นแล้ว และสะท้อนอยู่ใน equity แล้ว ไม้ grid ที่ถือมาสองสัปดาห์อาจมี swap สะสมติดลบมากกว่า floating loss ของตัวไม้เองด้วยซ้ำ การไม่นับ swap คือการประเมินความเสี่ยงต่ำกว่าจริง ในจังหวะที่ต้องการความแม่นยำมากที่สุด — คือจังหวะที่กำลังจะชนลิมิต

รวมสองก้อน = ตัวเลขที่เอาไปเทียบลิมิต

บรรทัดเดียวจบ: double pnlToday = RealizedToday() + FloatingNow();

ค่านี้ติดลบเมื่อวันนี้ขาดทุนสุทธิ และคือตัวเลขที่ใกล้เคียงมุมมอง equity ของ prop firm ที่สุดเท่าที่ EA คำนวณเองได้ เทียบให้เห็นชัดๆ ว่าแต่ละวิธีนับต่างกันแค่ไหน:

วิธีนับ นับอะไรบ้าง ตรงกับที่ prop firm เห็นไหม
realized อย่างเดียว เฉพาะไม้ที่ปิดแล้ว ไม่ตรง — พลาด floating ทั้งก้อน อันตรายที่สุด
realized + floating (ไม่รวม swap/commission) ไม้ปิดแล้ว + ไม้ลอย เกือบตรง แต่รายงานขาดทุนน้อยกว่าจริงเสมอ
realized + floating + swap + commission ทุกอย่างที่กระทบ equity ใกล้เคียงที่สุด — ใช้แบบนี้

มีเกร็ดหนึ่งที่ควรรู้: บาง prop firm นิยาม daily loss จาก "balance หรือ equity ณ จุดเริ่มวัน" ที่ต่างกัน บางเจ้าใช้ balance ตอนเที่ยงคืน บางเจ้าใช้ equity ตอนเที่ยงคืน (ซึ่งรวม floating ของไม้ที่ค้างข้ามวันมา) ตัวเลขฐานต่างกันทำให้ระยะถึงลิมิตต่างกันได้พอสมควรถ้ามีไม้ค้างข้ามคืนเยอะ — อ่านกฎของเจ้าที่ใช้ให้ละเอียด แล้วปรับสูตรฐานให้ตรง โครงโค้ดข้างบนรองรับทั้งสองแบบ แค่เปลี่ยนว่าเก็บค่าฐานอะไรไว้ตอนเปิดวัน


ประตูที่ปิดจริง — สถานะ HALTED และลำดับตอนเบรกเกอร์ตัด

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

ลำดับตอนตัดต้องเป็นแบบนี้เสมอ:

  1. ปิดไม้ที่เปิดอยู่ทั้งหมด — หยุดเลือดก่อน ไม่มีอะไรสำคัญกว่านี้
  2. ยกเลิก pending order ทั้งหมด — ไม่งั้น limit/stop order ที่แขวนไว้จะกลายเป็นไม้ใหม่หลังเบรกเกอร์ตัด ซึ่งเท่ากับเบรกแล้วรถยังไหลต่อ
  3. ตั้งสถานะ HALTED พร้อมเวลาปลดล็อก — บันทึกว่าห้ามเทรดจนถึงเมื่อไหร่
  4. ปลดล็อกอัตโนมัติ เมื่อถึงเที่ยงคืนเซิร์ฟเวอร์ถัดไป — ไม่ต้องพึ่งคนมากดอะไร

แผนภาพลำดับการทำงานของ circuit breaker ใน EA: เช็ก P&L รวมทุก tick เมื่อถึงลิมิตให้ปิดไม้ทั้งหมด ยกเลิก pending ตั้งสถานะ HALTED แล้วปลดล็อกอัตโนมัติตอนเที่ยงคืนเซิร์ฟเวอร์ถัดไป โดยทุกจุดที่จะส่งออเดอร์ต้องผ่านประตูเช็กสถานะก่อน

แปลงเป็นโค้ดหลักใน OnTick():

input double InpDailyLossLimit = 500.0;   // ลิมิตขาดทุนต่อวัน (สกุลเงินบัญชี)

bool     g_halted      = false;
datetime g_haltedUntil = 0;

void OnTick()
  {
   // 1) เช็กลิมิตทุก tick — ก่อนตรรกะอื่นทั้งหมด
   double pnlToday = RealizedToday() + FloatingNow();
   if(!g_halted && pnlToday <= -InpDailyLossLimit)
     {
      CloseAllPositions();                    // หยุดเลือดก่อน
      CancelAllPending();                     // กัน pending กลายเป็นไม้ใหม่
      g_halted      = true;
      g_haltedUntil = DayStart() + 86400;     // เที่ยงคืนเซิร์ฟเวอร์ถัดไป
      Print("CIRCUIT BREAKER: หยุดเทรดถึง ", TimeToString(g_haltedUntil));
      return;
     }

   // 2) ประตู — ห้ามผ่านถ้ายัง HALTED
   if(!CanTrade()) return;

   // 3) ตรรกะเทรดปกติของ EA อยู่ใต้บรรทัดนี้เท่านั้น
  }

// ประตูเดียวที่ทุกจุดในโค้ดต้องผ่านก่อนส่งออเดอร์
bool CanTrade()
  {
   if(g_halted && TimeCurrent() >= g_haltedUntil)
     {
      g_halted = false;                       // ขึ้นวันใหม่แล้ว ปลดล็อกอัตโนมัติ
      Print("Circuit breaker: ปลดล็อก เริ่มวันเทรดใหม่");
     }
   return !g_halted;
  }

จุดที่ผมขอเน้นเป็นตัวหนา: ทุกจุดในโค้ดที่มีการเรียก OrderSend() หรือฟังก์ชันที่ห่อมัน ต้องเรียก CanTrade() ก่อนเสมอ ไม่ใช่แค่จุดเดียวต้นทาง OnTick() — เพราะ EA จริงมักมีทางยิงออเดอร์มากกว่าหนึ่งทาง: จุดเปิดไม้ตามสัญญาณ จุดเปิดไม้แก้ (recovery/grid) จุดตั้ง pending ล่วงหน้า บางตัวมีตรรกะใน OnTimer() หรือ OnTradeTransaction() ด้วย ถ้ามีทางไหนหลุดไม่ผ่านประตูแม้แต่ทางเดียว สถานะ HALTED ก็ไม่มีความหมาย เหมือนล็อกประตูหน้าบ้านแน่นหนาแต่ประตูหลังแง้มไว้

ถ้ากลัวพลาด วิธีตรวจที่ตรงไปตรงมาที่สุดคือกด Ctrl+F ค้นทุกจุดที่มี OrderSend และ trade. (ถ้าใช้คลาส CTrade) ในโปรเจกต์ แล้วไล่ดูว่าเหนือบรรทัดนั้นมีการเช็ก CanTrade() ครบทุกจุดหรือยัง — งานสิบนาทีที่คุ้มค่ามาก

อีกเรื่องที่มองข้ามกันคือ EA รีสตาร์ทแล้วสถานะหาย — ตัวแปร g_halted อยู่ในหน่วยความจำ ถ้า VPS รีบูตหรือคุณปิด-เปิดเทอร์มินัลกลางวัน ตัวแปรจะกลับเป็น false และ EA จะเทรดต่อทั้งที่วันนี้ชนลิมิตไปแล้ว ทางแก้ที่ง่ายและพอเพียงคือให้ OnInit() คำนวณ P&L ของวันใหม่ทันทีจาก history (ฟังก์ชัน RealizedToday() ที่มีอยู่แล้วนั่นแหละ) ถ้าพบว่าเกินลิมิตอยู่แล้ว ก็ตั้ง HALTED กลับคืนตั้งแต่ยังไม่ทันเทรด — ข้อดีของการคำนวณจาก history แทนการจำสถานะไว้ในไฟล์คือมันสร้างความจริงขึ้นใหม่ได้เสมอไม่ว่าจะรีสตาร์ทกี่รอบ


กับดักโค้ดสองอันที่ทำให้ "ปิดไม้ไม่ครบแบบเงียบๆ"

มาถึงส่วนที่ผมเจอในงานแก้ EA บ่อยที่สุด สองกับดักนี้ร้ายตรงที่มันไม่ error ไม่ crash ไม่ฟ้องอะไรเลย — EA ดูเหมือนทำงานปกติ แต่ตอนเบรกเกอร์ตัดจริง ไม้ปิดไม่ครบ และคุณจะรู้ตัวก็ตอนที่ไม้ที่เหลือค้างลากบัญชีทะลุลิมิตไปแล้ว

กับดักที่ 1: วนลูปปิดไม้ต้องวนถอยหลังเท่านั้น

ดูโค้ดปิดไม้ทั้งหมดที่ถูกต้องก่อน:

#include <Trade/Trade.mqh>
CTrade trade;

void CloseAllPositions()
  {
   // ต้องวนถอยหลังจากตัวสุดท้ายลงมา 0 เสมอ
   for(int i = PositionsTotal() - 1; i >= 0; i--)
     {
      ulong ticket = PositionGetTicket(i);
      if(ticket == 0) continue;
      if(!trade.PositionClose(ticket))
         Print("ปิดไม้ ", ticket, " ไม่สำเร็จ: ", trade.ResultRetcodeDescription());
     }
  }

void CancelAllPending()
  {
   for(int i = OrdersTotal() - 1; i >= 0; i--)   // ถอยหลังด้วยเหตุผลเดียวกัน
     {
      ulong ticket = OrderGetTicket(i);
      if(ticket == 0) continue;
      if(!trade.OrderDelete(ticket))
         Print("ยกเลิก pending ", ticket, " ไม่สำเร็จ: ", trade.ResultRetcodeDescription());
     }
  }

ทำไมต้องถอยหลัง? เพราะรายการ position ใน MT5 เป็นเหมือนแถวที่ขยับชิดกันตลอดเวลา สมมติมีไม้ 5 ไม้ที่ index 0-4 แล้วคุณวนเดินหน้า: ปิดไม้ index 0 สำเร็จ → ไม้ที่เหลือทั้งหมดเลื่อนลงมาหนึ่งช่อง ไม้ที่เคยอยู่ index 1 ตอนนี้กลายเป็น index 0 — แต่ลูปของคุณเดินไป index 1 แล้ว เท่ากับ ข้ามไม้นั้นไปเฉยๆ ผลคือวนจบรอบเดียวจะปิดได้แค่ไม้เว้นไม้ เหลือค้างประมาณครึ่งหนึ่ง

ที่อันตรายคือมันเงียบสนิท — ลูปทำงานจบตามปกติ ไม่มี error สักบรรทัด log ก็ดูสะอาด และในการทดสอบทั่วไปที่มีไม้เดียวสองไม้ บั๊กนี้แทบไม่แสดงอาการ มันจะโผล่ตอนที่มีไม้เยอะๆ ซึ่งก็คือตอนที่ grid กำลังบานหรือตลาดกำลังผันผวน — จังหวะที่คุณต้องการให้ปิดครบที่สุดพอดี

การวนถอยหลังแก้ปัญหานี้หมดจด: ปิดไม้ index 4 → ไม้ index 0-3 ไม่กระทบ → ลดมาปิด index 3 → ไล่ลงไปเรื่อยๆ ไม่มีตัวไหนเลื่อนหนี สังเกตว่าในโค้ดข้างบนผมวนถอยหลังทั้ง CloseAllPositions() และ CancelAllPending() — รายการ pending order ก็ขยับชิดแบบเดียวกับ position เป๊ะ พลาดได้ด้วยกลไกเดียวกัน

กับดักที่ 2: filling mode ต้องเลือกตามที่ symbol รองรับจริง

ตอนส่งคำสั่งปิดไม้ MT5 ต้องระบุ "โหมดการเติมออเดอร์" (filling mode) ไปด้วย ซึ่งมีสามแบบหลัก: FOK (Fill or Kill — ได้ครบทั้งจำนวนหรือไม่เอาเลย), IOC (Immediate or Cancel — ได้เท่าไหร่เอาเท่านั้น ที่เหลือยกเลิก), และ Return (ค้างรอในระบบ)

ปัญหาคือ โบรกแต่ละเจ้า แต่ละ symbol รองรับไม่เหมือนกัน และถ้าคุณส่งโหมดที่ symbol นั้นไม่รองรับ เทอร์มินัลจะตีกลับคำสั่งทันที (error 4756 / retcode 10030 = TRADE_RETCODE_INVALID_FILL) ตั้งแต่ยังไม่ทันออกจากเครื่อง — คำสั่งปิดไม้ไม่ถึงเซิร์ฟเวอร์ด้วยซ้ำ

ลองนึกภาพ: เบรกเกอร์ตรวจเจอว่าชนลิมิต สั่งปิดไม้ทั้งหมด แต่ทุกคำสั่งโดนตีกลับเพราะ filling mode ผิด — ไม้ทุกไม้ยังลอยอยู่ครบ ขาดทุนวิ่งต่อ ในขณะที่ log เต็มไปด้วย error ที่ไม่มีใครนั่งดูตอนตีสอง นี่คือสาเหตุที่ EA ที่ก๊อปโค้ดจากเน็ตมาแล้ว "ใช้ได้กับโบรกเก่า แต่พังกับโบรกใหม่" — เพราะ hard-code โหมดไว้ตายตัว

วิธีที่ถูกคืออ่านจาก symbol ว่ารองรับอะไร แล้วเลือกตามลำดับ:

// เลือก filling mode ตามที่ symbol รองรับจริง — FOK ก่อน แล้ว IOC แล้วค่อย Return
ENUM_ORDER_TYPE_FILLING PickFilling(string symbol)
  {
   long modes = SymbolInfoInteger(symbol, SYMBOL_FILLING_MODE);
   if((modes & SYMBOL_FILLING_FOK) != 0) return ORDER_FILLING_FOK;
   if((modes & SYMBOL_FILLING_IOC) != 0) return ORDER_FILLING_IOC;
   return ORDER_FILLING_RETURN;   // ไม่ประกาศ FOK/IOC = ใช้ Return
  }

// ใช้กับ CTrade ก่อนส่งคำสั่ง:
trade.SetTypeFilling(PickFilling(_Symbol));

ค่าที่อ่านได้จาก SYMBOL_FILLING_MODE เป็น bitmask คือ symbol หนึ่งอาจรองรับหลายโหมดพร้อมกัน โค้ดข้างบนไล่เช็กทีละ bit ตามลำดับความเข้มงวด ถ้าใช้คลาส CTrade ให้เซ็ตด้วย SetTypeFilling() ครั้งเดียวตอน OnInit() ก็ได้ แต่อย่าลืมว่าถ้า EA เทรดหลาย symbol ต้องเลือกใหม่ต่อ symbol

สองกับดักนี้เจอคู่กันบ่อย และมักเจอในโค้ดที่ backtest ผ่านสวยงามด้วย — เพราะใน Strategy Tester ทุกคำสั่ง execute สมบูรณ์แบบตลอด เงื่อนไขที่ทำให้มันพังมีเฉพาะบนเซิร์ฟเวอร์จริง ซึ่งเป็นธีมเดียวกับที่ผมเขียนไว้ในบทความ วิธี Backtest EA ใน MT5 ว่ามีบั๊กทั้งกลุ่มที่ backtest ไม่มีทางเจอ


ตั้งลิมิตเท่าไหร่ดี — โดยเฉพาะถ้าเทรด prop firm

โค้ดถูกหมดแล้ว เหลือคำถามเชิงนโยบาย: ตัวเลขลิมิตควรเป็นเท่าไหร่

สำหรับภาพรวมของวงการ prop firm ส่วนใหญ่ตั้ง daily loss ไว้ราว 4–5% ของพอร์ต และ max drawdown รวมราว 8–10% — ย้ำว่านี่คือ "ภาพรวม" ไม่ใช่ตัวเลขของเจ้าใดเจ้าหนึ่ง แต่ละเจ้ามีนิยาม วิธีวัด และตัวเลขต่างกันในรายละเอียด บางเจ้าวัดจาก balance บางเจ้าวัดจาก equity บางเจ้ามี trailing drawdown ที่ขยับตามกำไร ก่อนตั้งค่าอะไร ให้เปิดกฎของเจ้าที่คุณใช้อ่านเองหนึ่งรอบเสมอ อย่าเชื่อตัวเลขจากบทความไหนรวมถึงบทความนี้

ประเด็นที่สำคัญกว่าตัวเลขของ prop firm คือ: ลิมิตภายใน EA ต้องแคบกว่าลิมิตของ prop firm เสมอ — ห้ามตั้งเท่ากันเด็ดขาด ด้วยเหตุผลสามข้อ:

  1. Slippage ตอนปิด — ราคาที่ EA เห็นตอนตัดสินใจ กับราคาที่ปิดได้จริง ไม่ใช่ราคาเดียวกัน โดยเฉพาะตอนตลาดวิ่งแรงซึ่งเป็นจังหวะที่เบรกเกอร์มักทำงานพอดี
  2. ช่องว่างระหว่าง tick — ต่อให้เช็กทุก tick ราคาก็กระโดดข้าม tick ได้ ช่วงข่าวแรง ราคากระโดดทีละหลายจุดเป็นเรื่องปกติ
  3. ความต่างของนาฬิกาและวิธีวัด — ตามที่คุยไปในเรื่องเส้นแบ่งวันและนิยาม daily ของแต่ละเจ้า

แนวที่ผมใช้เวลาทำโมดูลนี้ให้ลูกค้าสาย prop firm คือเผื่อ buffer ไว้ราว 20–30% ของลิมิตจริง ตัวอย่างการคิด (สมมติพอร์ต 100,000 หน่วย และ prop firm ให้ daily loss 5%):

รายการ ค่า หมายเหตุ
ลิมิตของ prop firm 5,000 (5%) เส้นตายจริง — แตะแล้วบัญชีจบ
buffer เผื่อ slippage/ช่องว่าง tick 1,000–1,500 ราว 20–30% ของลิมิต
ลิมิตที่ตั้งใน EA 3,500–4,000 (3.5–4%) เบรกเกอร์ตัดที่เส้นนี้

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

อีกมุมที่ควรคิดคือความสัมพันธ์ระหว่างลิมิตกับขนาดไม้: ถ้า EA ของคุณเสี่ยงต่อไม้ 1% ลิมิต 4% แปลว่ารับขาดทุนติดกันได้ 4 ไม้ก่อนหยุด — ตัวเลขนี้สมเหตุสมผลไหมกับสถิติขาดทุนติดกันของระบบคุณ? ถ้าระบบขาดทุนติดกัน 6-7 ไม้เป็นเรื่องปกติ (ดูได้จากผล backtest) ลิมิต 4% จะทำให้เบรกเกอร์ตัดบ่อยเกินไปจนระบบไม่ได้เดินตามแผน ทางแก้ไม่ใช่ขยายลิมิต แต่คือ ลดขนาดไม้ ให้ชุดขาดทุนปกติของระบบอยู่ในลิมิตได้ — เรื่องนี้ต้องดูจากข้อมูล backtest ที่เชื่อถือได้ ซึ่งผมเขียนวิธีทำไว้ละเอียดในบทความ backtest


ต่อยอดจาก daily loss limit ธรรมดา

โครงที่มีถึงตรงนี้คือเวอร์ชันพื้นฐานที่ควรมีในทุก EA แต่พอมันทำงานนิ่งแล้ว มีทิศทางต่อยอดอีกหลายทางที่ใช้โครงเดียวกันเกือบทั้งหมด ผมเล่าสั้นๆ พอให้เห็นภาพว่าอะไรทำได้บ้าง:

ลิมิตรายสัปดาห์ — เปลี่ยนขอบล่างของ HistorySelect() จากเที่ยงคืนวันนี้เป็นเที่ยงคืนวันจันทร์ของสัปดาห์ ที่เหลือใช้โครงเดิมทั้งหมด เหมาะกับคนที่กลัวอาการ "ขาดทุนวันละนิดจนครบโควตาทั้งสัปดาห์ในสามวัน"

หยุดเมื่อขาดทุนติดกัน N ไม้ — วนอ่าน deal ล่าสุดย้อนหลัง นับว่าขาดทุนติดกันมากี่ไม้ ถ้าถึงเกณฑ์ (เช่น 4 ไม้) ให้พักเทรดชั่วคราว อันนี้จับอาการ "ระบบไม่เข้ากับสภาพตลาดวันนี้" ได้เร็วกว่าลิมิตที่เป็นจำนวนเงิน เพราะบางวันระบบแพ้ติดๆ กันด้วยไม้เล็กๆ ที่ยังไม่ชนลิมิตเงินแต่ส่งสัญญาณชัดแล้ว

แจ้งเตือนเข้ามือถือตอนเบรกเกอร์ตัด — เพิ่มบรรทัดเดียวในลำดับการตัด: SendNotification("Circuit breaker ตัดแล้ว: ขาดทุนวันนี้ถึงลิมิต") ข้อความจะเด้งเข้าแอป MT5 บนมือถือทันที (ต้องผูก MetaQuotes ID ในเทอร์มินัลก่อน ตั้งได้ที่ Tools → Options → Notifications) เรื่องนี้สำคัญกว่าที่คิด — เบรกเกอร์ที่ตัดเงียบๆ ตอนตีสองแล้วคุณมาเห็นตอนเก้าโมงเช้า กับเบรกเกอร์ที่ปลุกคุณทันที ให้ความรู้สึกควบคุมต่างกันมาก และบางครั้งการรู้เร็วช่วยให้ตัดสินใจเรื่องอื่นทัน เช่น EA ตัวอื่นที่ยังรันอยู่

Drawdown จาก peak equity แทนรายวัน — ลิมิตรายวันรีเซ็ตทุกเช้า แต่ max drawdown ของ prop firm ส่วนใหญ่ไม่รีเซ็ต มันวัดจากจุดสูงสุดที่บัญชีเคยไปถึง โครงเดียวกันปรับได้: เก็บค่า equity สูงสุดไว้ (อัปเดตทุก tick) แล้วเทียบ equity ปัจจุบันกับค่านั้น ถ้าห่างเกินเกณฑ์ก็ตัดด้วยลำดับเดียวกัน — ต่างกันแค่เบรกเกอร์ตัวนี้ไม่ปลดล็อกเองตอนเที่ยงคืน ต้องให้คนมาตัดสินใจ เพราะการชน max drawdown คือสัญญาณว่าต้องทบทวนระบบ ไม่ใช่แค่พักหนึ่งคืน

EA หลายตัวแชร์สถานะ HALTED ร่วมกัน — ถ้าคุณรัน EA หลายตัวบนเทอร์มินัลเดียวกัน (คนละชาร์ต คนละคู่เงิน) แต่ทุกตัวอยู่บนบัญชี prop firm เดียวกัน ลิมิตรายวันเป็นของบัญชีรวม ไม่ใช่ของ EA แต่ละตัว — EA ตัวหนึ่งตัดตัวเองแล้ว แต่อีกสามตัวยังเทรดต่อ ก็จบเหมือนเดิม ทางแก้ใน MQL5 คือ Global Variable ของเทอร์มินัล ซึ่งเป็นตัวแปรกลางที่ทุก EA ในเทอร์มินัลเดียวกันอ่าน/เขียนร่วมกันได้:

#define HALT_FLAG "DAILY_HALT_UNTIL"   // ชื่อตัวแปรกลาง ใช้ตรงกันทุก EA

// ตอนเบรกเกอร์ของตัวไหนตัด — ประกาศให้ทั้งเทอร์มินัลรู้
GlobalVariableSet(HALT_FLAG, (double)(DayStart() + 86400));

// ทุก EA เพิ่มเงื่อนไขนี้เข้าไปในประตู CanTrade() ของตัวเอง
bool HaltedGlobally()
  {
   if(!GlobalVariableCheck(HALT_FLAG)) return false;
   return TimeCurrent() < (datetime)GlobalVariableGet(HALT_FLAG);
  }

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


ข้อจำกัดที่ผมต้องบอกตรงๆ

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

หนึ่ง — คุมได้เฉพาะ EA ที่ฝังโมดูลนี้เท่านั้น EA ตัวอื่นบนชาร์ตอื่นที่ไม่มีโมดูล (หรือมีแต่ไม่เช็กตัวแปรกลางร่วมกัน) ยิงออเดอร์ได้ตามปกติ เบรกเกอร์ไม่ใช่ผนังกั้นทั้งบัญชี มันคือวินัยของ EA แต่ละตัว

สอง — หยุดมือคนไม่ได้ ไม่มีกลไกใน MQL5 ที่ห้ามเจ้าของบัญชีกดเปิดไม้เองจากหน้าจอหรือมือถือ Global Variable ช่วยประสานได้เฉพาะระหว่าง EA ที่ยอมเช็กด้วยกันเท่านั้น ถ้าปัญหาจริงของคุณคือมือตัวเองแอบเทรดหลังชนลิมิต (ซึ่งเป็นปัญหาคลาสสิกของคนสอบ challenge) เครื่องมือทางโค้ดช่วยไม่ได้ — อันนั้นต้องแก้ที่พฤติกรรม เช่น เอารหัสผ่านบัญชีเทรดออกจากมือถือช่วงสอบ

สาม — การตัดในตลาดผันผวนไม่ได้สวยงามเสมอ จังหวะที่เบรกเกอร์ทำงานมักเป็นจังหวะที่ตลาดกำลังแรง คำสั่งปิดอาจโดน slippage หรือเจอ partial fill คือปิดได้บางส่วน เหลือเศษไม้ค้างไปถึง tick ถัดไปให้ตามปิดซ้ำ โค้ดที่ดีต้องเผื่อการวนปิดซ้ำจนกว่าจะเกลี้ยง ไม่ใช่สั่งรอบเดียวแล้วถือว่าจบ

สี่ — ตัวเลขตอนตัดสินใจไม่ใช่ตัวเลขตอนปิดได้จริง P&L ที่เบรกเกอร์ใช้ตัดสินใจคือราคา ณ tick นั้น แต่กว่าคำสั่งจะเดินทางถึงเซิร์ฟเวอร์และ execute เสร็จ ราคาขยับไปแล้ว ในตลาดปกติสองค่านี้ต่างกันน้อยมาก แต่ในตลาดเร็ว — ซึ่งเป็นตอนที่เบรกเกอร์ทำงานพอดี — ความต่างมีนัยสำคัญ นี่คือเหตุผลหลักของ buffer 20–30% ที่คุยไปในหัวข้อก่อน

ห้า — race condition กับ pending order มีหน้าต่างเวลาแคบๆ ที่ pending order ซึ่งราคากำลังวิ่งเข้าใส่อยู่แล้วฝั่งเซิร์ฟเวอร์ อาจ trigger กลายเป็นไม้จริงก่อนคำสั่ง CancelAllPending() ของเราไปถึง โอกาสเกิดต่ำแต่ไม่ใช่ศูนย์ โดยเฉพาะช่วงข่าว ทางบรรเทาคือให้ลูปเช็กหลังตัด (tick ถัดๆ ไป) คอยดูว่ามีไม้ใหม่โผล่มาหลังสถานะ HALTED หรือไม่ ถ้ามีก็ปิดทิ้งทันที

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


เช็กลิสต์ก่อนปล่อยเบรกเกอร์ใช้งานจริง

รวมทุกอย่างในบทความเป็นรายการติ๊ก — ไล่ให้ครบก่อนเอาไปแขวนกับบัญชีที่มีเดิมพันจริง โดยเฉพาะบัญชี challenge:

เรื่องเวลา

  • รู้ offset ระหว่างเวลาเซิร์ฟเวอร์โบรกกับเวลาไทย (พิมพ์เทียบ TimeCurrent() กับ TimeLocal() มาดูแล้ว)
  • เส้นแบ่งวันในโค้ดอิงเที่ยงคืนเซิร์ฟเวอร์ ไม่ใช่เวลาไทยหรือเวลาเครื่อง
  • อ่านนิยาม "daily" ของ prop firm ที่ใช้แล้ว และเทียบกับเวลาเซิร์ฟเวอร์บัญชีว่าตรงกันหรือต้องเลื่อน offset
  • รู้ว่าโบรกตัวเองขยับเวลาตาม DST หรือไม่

เรื่องตัวเลข

  • P&L ของวันรวมครบ: realized + floating + swap + commission
  • อ่าน history โดยกรองเฉพาะ DEAL_ENTRY_OUT และ DEAL_ENTRY_INOUT
  • รู้ว่า prop firm วัดฐานจาก balance หรือ equity ณ จุดเริ่มวัน และตั้งสูตรฐานให้ตรง
  • ลิมิตใน EA แคบกว่าลิมิตของ prop firm อย่างน้อย 20%
  • ลิมิตสอดคล้องกับขนาดไม้และสถิติขาดทุนติดกันของระบบ (ไม่ตัดถี่จนระบบเดินไม่ได้)

เรื่องโค้ด

  • เช็กลิมิตทุก tick ใน OnTick() ก่อนตรรกะอื่นทั้งหมด
  • ลำดับตอนตัดครบ: ปิดไม้ → ยกเลิก pending → ตั้ง HALTED → ปลดล็อกเองวันถัดไป
  • ค้นทุกจุดที่มี OrderSend / trade. แล้วยืนยันว่าทุกจุดผ่าน CanTrade() ก่อน
  • ลูปปิดไม้และยกเลิก pending วนถอยหลังทั้งคู่
  • filling mode อ่านจาก SYMBOL_FILLING_MODE ไม่ได้ hard-code
  • OnInit() คำนวณ P&L วันนี้ใหม่จาก history — รีสตาร์ทแล้วสถานะ HALTED ไม่หาย
  • ถ้ารันหลาย EA บนบัญชีเดียว: ทุกตัวเช็ก Global Variable กลางตัวเดียวกัน

เรื่องทดสอบ

  • รันใน Strategy Tester แล้วเห็นเบรกเกอร์ตัดและปลดล็อกตรงเวลาที่ควร ไม่มีออเดอร์หลุดระหว่าง HALTED
  • ทดลองบน Demo โดยตั้งลิมิตต่ำๆ ให้มันตัดจริงต่อหน้าอย่างน้อยหนึ่งครั้ง — อย่าให้ครั้งแรกที่เบรกเกอร์ทำงานคือวันที่เงินจริงแขวนอยู่
  • เปิด SendNotification() แล้วทดสอบว่าแจ้งเตือนเข้ามือถือจริง

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


คำถามที่พบบ่อยเรื่อง Daily Loss Limit ใน EA

Q1: Daily Loss Limit ใน EA คืออะไร ต่างจาก Stop Loss ยังไง?

Stop Loss คุมความเสียหายของ "ไม้เดียว" ส่วน Daily Loss Limit คุมความเสียหายรวมของ "ทั้งวัน" — ต่อให้ทุกไม้มี SL เรียบร้อย ระบบก็ยังขาดทุนติดกันหลายไม้จนยอดรวมของวันบานได้ Daily Loss Limit คือเบรกเกอร์อีกชั้นที่บอกว่าวันนี้พอแล้ว: ปิดไม้ทั้งหมด ยกเลิก pending และห้ามเปิดไม้ใหม่จนกว่าจะขึ้นวันเทรดใหม่ สองอย่างนี้ต้องมีคู่กัน ไม่ใช่เลือกอย่างใดอย่างหนึ่ง

Q2: ทำไมต้องเช็กลิมิตทุก tick เช็กตอนเปิดแท่งใหม่ไม่พอหรือ?

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

Q3: ลิมิตรายวันรีเซ็ตตอนไหน เที่ยงคืนบ้านเราใช่ไหม?

ไม่ใช่ครับ และนี่คือจุดที่คนไทยพลาดมากที่สุด — เส้นแบ่งวันของ EA คือเที่ยงคืน "เวลาเซิร์ฟเวอร์โบรก" ซึ่งส่วนใหญ่ตั้ง GMT+2 หรือ GMT+3 ขณะที่ไทยคือ GMT+7 แปลว่าวันใหม่ของ EA เริ่มราวตี 4 ถึงตี 5 ตามเวลาไทย ถ้าขาดทุนหนักตอน 4 ทุ่ม ลิมิตจะยังไม่รีเซ็ตตอนเที่ยงคืนบ้านเรา ต้องรอถึงเช้ามืด และต้องระวังเพิ่มว่าโบรกบางเจ้าเปลี่ยน offset ตาม DST ปีละสองครั้ง ส่วน prop firm บางเจ้าก็นับวันด้วย timezone ของตัวเองซึ่งอาจไม่ตรงกับเซิร์ฟเวอร์โบรกอีกชั้น — ต้องเช็กให้ตรงกันทั้งสามนาฬิกา

Q4: ต้องนับกำไร/ขาดทุนอะไรบ้างในลิมิตรายวัน?

ต้องนับให้ครบสี่ส่วน: กำไร/ขาดทุนของไม้ที่ปิดแล้ววันนี้ (realized) กำไร/ขาดทุนลอยตัวของไม้ที่ยังเปิดอยู่ (floating) swap และ commission — เพราะ prop firm วัดจาก equity ซึ่งสะท้อนทั้งสี่ส่วนแล้ว EA ที่นับแต่ realized จะรายงานว่ายังไม่เกินลิมิตทั้งที่ equity จริงเกินไปแล้ว ฝั่งโค้ด MQL5 ตอนอ่าน history ต้องกรองเฉพาะ deal ประเภท DEAL_ENTRY_OUT และ DEAL_ENTRY_INOUT เพราะกำไร/ขาดทุนถูกบันทึกที่ deal ขาออกเท่านั้น

Q5: EA มี daily loss limit แล้ว รับประกันว่าจะไม่โดน prop firm ตัดใช่ไหม?

ไม่ใช่ครับ เบรกเกอร์ลดความเสี่ยงได้มากแต่รับประกันไม่ได้ เพราะมีช่องว่างที่โค้ดคุมไม่ถึง: slippage ตอนปิดไม้ในตลาดเร็ว partial fill ที่ทำให้เหลือเศษไม้ค้าง pending order ที่ trigger ก่อนคำสั่งยกเลิกไปถึง EA ตัวอื่นที่ไม่ได้ฝังโมดูล และมือของเราเองที่กดเทรดเพิ่ม วิธีรับมือคือตั้งลิมิตภายใน EA ให้แคบกว่าลิมิตของ prop firm ราว 20–30% เพื่อให้ช่องว่างพวกนี้ยังอยู่ในพื้นที่ปลอดภัย

Q6: prop firm ให้ daily loss 5% ควรตั้งลิมิตใน EA เท่าไหร่?

แนวที่ผมใช้คือ 3.5–4% คือเผื่อ buffer ราว 20–30% จากเส้นตายจริง เพื่อรองรับ slippage ราคากระโดดข้าม tick และความคลาดเคลื่อนของนาฬิกา/วิธีวัดระหว่าง EA กับ prop firm และก่อนตั้งต้องอ่านกฎของเจ้าที่ใช้เองเสมอว่าเขาวัดจาก balance หรือ equity และรีเซ็ตวันตอนไหน เพราะแต่ละเจ้านิยามไม่เหมือนกัน ตัวเลข 4–5% เป็นเพียงภาพรวมของวงการ ไม่ใช่กฎของเจ้าใดเจ้าหนึ่ง

Q7: รัน EA หลายตัวบนบัญชีเดียว จะให้หยุดพร้อมกันทั้งหมดได้ไหม?

ได้ ถ้าทุกตัวอยู่บนเทอร์มินัล MT5 เดียวกันและทุกตัวฝังโมดูลที่เช็กตัวแปรกลางร่วมกัน — ใช้ Global Variable ของเทอร์มินัล (GlobalVariableSet/GlobalVariableGet) เป็นธงกลาง ตัวไหนชนลิมิตก่อนก็เซ็ตธงพร้อมเวลาปลดล็อก ตัวอื่นเห็นธงแล้วหยุดตามในทิกถัดไป ข้อจำกัดคือมันคุมได้เฉพาะ EA ที่ยอมเช็กธงด้วยกันเท่านั้น EA ที่ไม่มีโมดูลหรือรันอยู่คนละเทอร์มินัลจะไม่รับรู้อะไรเลย

Q8: ทดสอบ circuit breaker ใน Strategy Tester ได้ไหม?

ได้และควรทำ — รัน backtest ด้วยโหมด Every tick based on real ticks แล้วดู log ว่าเบรกเกอร์ตัดตรงจังหวะที่ควรตัด ปลดล็อกตรงเที่ยงคืนเซิร์ฟเวอร์จริง และไม่มีออเดอร์ใหม่โผล่ระหว่าง HALTED แต่อย่าลืมว่า Strategy Tester จำลองการ execute แบบสมบูรณ์แบบ — slippage จริง partial fill และ filling mode ที่โบรกตีกลับ เป็นเงื่อนไขที่มีเฉพาะบนเซิร์ฟเวอร์จริง จึงต้องรันบนบัญชี Demo ต่ออีกช่วงหนึ่งเสมอ วิธีตั้งค่า tester ให้ผลเชื่อถือได้ผมเขียนไว้ละเอียดในบทความ วิธี Backtest EA ใน MT5


สรุป

  1. EA ส่วนใหญ่คุม daily loss ไม่อยู่เพราะพลาดสามชั้น — เช็กแค่ตอนแท่งใหม่ นับแต่ realized และไม่มีประตูกันยิงออเดอร์หลังตรวจเจอ ต้องอุดครบทั้งสามถึงเรียกว่ามีเบรกเกอร์จริง
  2. เส้นแบ่งวันคือเที่ยงคืนเวลาเซิร์ฟเวอร์ ไม่ใช่เที่ยงคืนไทย — วันใหม่ของ EA เริ่มราวตี 4-5 บ้านเรา โบรกเปลี่ยน offset ตาม DST ได้ และ prop firm อาจนับวันด้วยนาฬิกาของตัวเองอีกชั้น
  3. P&L ของวันต้องรวม realized + floating + swap + commission — เพราะ prop firm วัดจาก equity ขาดตัวไหนไปคือประเมินความเสี่ยงต่ำกว่าจริง
  4. ลำดับตอนตัดตายตัว: ปิดไม้ → ยกเลิก pending → ตั้ง HALTED → ปลดล็อกเองวันถัดไป และทุกจุดที่ส่งออเดอร์ต้องผ่านประตูเช็กสถานะก่อน ไม่งั้น HALTED เป็นแค่ log
  5. ลูปปิดไม้ต้องวนถอยหลัง และ filling mode ต้องอ่านจาก symbol — สองกับดักเงียบที่ทำให้ปิดไม้ไม่ครบในจังหวะคอขาดบาดตาย
  6. ลิมิตใน EA ต้องแคบกว่าลิมิต prop firm ราว 20–30% — เผื่อ slippage ราคากระโดด และความคลาดเคลื่อนของนาฬิกา
  7. เบรกเกอร์คือชั้นป้องกันชั้นสุดท้าย ไม่ใช่ชั้นเดียว — มันหยุด EA ตัวอื่นไม่ได้ หยุดมือคนไม่ได้ และตัดกลางตลาดผันผวนอาจไม่เกลี้ยงในครั้งเดียว รู้ขอบเขตของมันแล้วใช้ร่วมกับขนาดไม้ที่เหมาะสมเสมอ

ถ้าจะให้เลือกข้อเดียวไปตรวจ EA ของคุณคืนนี้ — ข้อ 2 ครับ เปิดดูว่าโค้ดรีเซ็ตวันด้วยนาฬิกาไหน เพราะมันคือจุดที่ผิดกันเยอะที่สุดและตรวจง่ายที่สุด


อยากได้เบรกเกอร์แบบนี้ใน EA ที่ใช้อยู่? ส่งมาให้ดูได้

ถ้าคุณมี EA ที่รักอยู่แล้วแต่ไม่แน่ใจว่าระบบจำกัดขาดทุนของมันอุดครบทั้งสามชั้นหรือเปล่า — หรืออยากฝังโมดูล circuit breaker แบบในบทความนี้เข้าไปโดยไม่กระทบตรรกะเดิม — ส่งไฟล์ EA มาให้ผมดูได้เลย ประเมินราคาฟรี ตอบกลับใน 1 วัน

รับทั้งงานเขียน EA ใหม่, แก้ไข EA เดิม, และเพิ่มโมดูลบริหารความเสี่ยงให้ EA ที่มีอยู่แล้ว — เริ่มต้น 500 บาท รับใบเสนอราคาภายใน 24 ชม.

📱 Line: @lafanoom 📞 โทร: 085-937-7580 💬 Facebook: facebook.com/thaimql 📧 Email: Lafanoom@gmail.com

🕒 เวลาทำการ: ทุกวัน (10:00 - 20:00 น.)


แหล่งอ้างอิง — บทความนี้อ้างอิงงานเขียนบน MQL5.com ขอบคุณผู้เขียนครับ:


อ่านต่อ: วิธี Backtest EA ใน MT5 ให้แม่น · รับเขียน EA ทุกสิ่งที่ต้องรู้ · Grid Trading คืออะไร · ราคาเขียน EA เท่าไหร่


บทความนี้เขียนจากประสบการณ์รับเขียนและแก้ EA มา 15 ปี โค้ดตัวอย่างทั้งหมดเป็นโครงสำหรับศึกษา ต้องปรับและทดสอบกับบัญชีและโบรกของคุณเองก่อนใช้จริง กฎของ prop firm แต่ละเจ้าต่างกันในรายละเอียด — อ่านกฎของเจ้าที่คุณใช้เองเสมอ และทดสอบทุกอย่างบน Demo ก่อนใช้เงินจริงครับ