อัตราการชนะ 87% เป็นบั๊กหนึ่งบรรทัดในสคริปต์ตรวจสอบของฉัน ── การหยุดขาดทุนที่มองเห็นหกแท่งข้างหน้าในอนาคต
สวัสดีครับ ผม lab.aiueo ที่สร้างอินดิเคเตอร์ MT4/MT5 และขายบน GogoJungle
ผมจะเขียนต่อเนื่องถึงสิ่งที่เกิดขึ้นในฝั่งผู้สร้างอินดิเคเตอร์ ครั้งนี้เป็นเรื่องที่เขียนยากที่สุด
อัตราชนะ 87%、PF 2.21
เดือนสิงหาคม 2026 ผมสร้างเครื่องหมายสัญญาสำหรับทองคำโดยเฉพาะ ชื่อ GD-Signal
หลังจากสร้าเสร็จ ผมทดสอบผลกับข้อมูลย้อนหลัง ชุดกรอบเวลา 1 ชั่วโมงของทองคำ ตั้งแต่มกราคม 2011 ถึงสิงหาคม 2026 เป็นระยะเวลา 15.6 ปี มีแท่งเทียนทั้งหมด 91,586 แท่ง
ผลลัพธ์เป็นอย่างนี้
- จำนวนการเทรด: 423 ครั้ง
- อัตราชนะ: 87.0%
- PF (กำไรรวม÷ขาดทุนรวม): 2.21
- การแพ้ที่ยาวนานที่สุด: 3 ไม้
เมื่อเขียนตามความจริง ผมดีใจมาก
จากการทดสอบนี้ ผมนำ PF มาใส่เป็นหลักฐานบนหน้าผลิตภัณฑ์และบทความแนะนำ เพื่อเริ่มการขาย
ไม่กี่วันต่อมา จำนวนตัวเลขทั้งหมดกลับกลายเป็นโมฆะ
สคริปต์ Python ที่ใช้ในการตรวจสอบมีบัค บรรทัดหนึ่งใน 466 บรรทัดที่กำหนดช่วงจุดหยุดขาดทุน
หลังแก้ไข ผมรันข้อมูลเดิมซ้ำ ผลลัพธ์เป็นดังนี้
| กรอบเวลา (15.6 ปี) | ก่อนแก้ | หลังแก้ |
|---|---|---|
| 1 ชั่วโมง | 423 ครั้ง อัตราชนะ 87.0% PF 2.21 | 544 ครั้ง อัตราชนะ 46.3% PF 1.07 |
| 4 ชั่วโมง | 201 ครั้ง อัตราชนะ 81.6% PF 1.62 | 248 ครั้ง อัตราชนะ 40.3% PF 0.68 |
อัตราชนะลดลงครึ่งหนึ่ง PF ในกรอบเวลา 1 ชั่วโมงสูงกว่าเล็กน้อยเกิน 1 และกรอบเวลา 4 ชั่วโมงต่ำกว่า 1
ผมหยุดขายและถอดบทความออก เพราะไม่มีผู้ซื้อ ไม่ได้มีผลกระทบ
แต่หากขายไปจริง จะเป็นการซื้อด้วยตัวเลขที่ไม่มีหลักฐาน
อัตราชนะ 87% แต่กำไรน้อยเกินไป
เมื่อย้อนกลับมาดู ตัวเลขบอกสัญญาณอยู่แล้ว
เป็นกำไรต่อไม้ของ 1 ชั่วโมงก่อนแก้
- ค่าเฉลี่ยชนะ: +4.99 ดอลลาร์
- ค่าเฉลี่ยแพ้: −15.10 ดอลลาร์
เมื่อชนะ ไล่รางวัลเล็ก แต่เมื่อแพ้ จะแพ้ถึง 3 เท่า แม้จะมีอัตราชนะ 87% ก็ตาม ต่อการแพ้ 1 ครั้ง จะทำให้ชนะ 3 ไม้หายไป
“อัตราชนะสูงผิดปกติ แต่กำไรน้อย” เป็นลายนิ้วมือของบัคนี่
เหตุผลจะอธิบายละเอียดในภายหลัง สรุปคือ ตอนกำหนดจุดหยุดขาดทุน มองไปที่เวลาอนาคตที่ยังมาไม่ถึง 6 แท่ง
หากวางหยุดขาดทุนต่ำกว่าจุดต่ำสุดในอนาคต จะไม่ถูกหยุดขาดทุนไปสักระยะ อัตราชนะจะสูงขึ้นเป็นเรื่องธรรมดา
เหตุผลที่นำเรื่องนี้มาเป็นเรื่องแรก
การเขียนถึงความล้มเหลวของตนเองจริงๆ ไม่ง่าย
อย่างไรก็ตาม ผมมีเหตุผลสองข้อให้เริ่มด้วย
ข้อหนึ่ง เพื่อเก็บกระบวนการสงสัยไว้ก่อนที่จะดีใจ เมื่อผลทดสอบแสดงอัตราชนะสูง
ข้อสอง หากคุณเป็นคนที่ตรวจสอบกฎการซื้อขายด้วย Python หรือ Excel ก็มีโอกาสตกหลุมเดิม
เมื่อย้ายตรรกะจาก MQL4 (ภาษา MT4) ไปยัง Python ลำดับราคาจะกลับด้าน สคริปต์นี้มีแค่จุดเดียวที่ลืมกลับด้าน ทั้งที่ย้ายแล้วและตรวจทานทีละบรรทัด กลับผ่านจุดนั้นเพียงจุดเดียว
สคริปต์นี้ผมร่วมทำกับ AI (Claude Code) ฝ่าย AI ที่รับผิดชอบส่วนที่เป็นตัวเลข ผมเป็นผู้เผยแพร่ตัวเลขเหล่านี้
จะไม่มีสมการหรือสูตรในบทความ แต่อธิบายด้วยลำดับราคาของแท่งเทียน
บรรทัดบัคหนึ่งบรรทัด
ส่วนการคำนวณหยุดขาดทุนในสคริปต์ทดสอบทองคำ
ก่อนแก้
we = min(i + SWING_LEN + 1, n-1)
sl = (l[i:we+1].min() - buf) if is_buy else (h[i:we+1].max() + buf)
หลังแก้
ws = max(i - SWING_LEN - 1, 0)
sl = (l[ws:i+1].min() - buf) if is_buy else (h[ws:i+1].max() + buf)
i คือหมายเลขแท่งที่สัญญาออก และ SWING_LEN คือ 5 ค่า l คือชุดต่ำสุด และ h คือชุดสูงสุด
ถ้าซื้อ ให้วางหยุดขาดทุนเล็กๆ ใต้จุดต่ำสุดของช่วงนี้
ขอบเขตต่างกันเท่านั้น
- ก่อนแก้: จาก i ถึง i+6 (แท่งสัญญาและอีก 6 แท่งถัดไป)
- หลังแก้: จาก i-6 ถึง i (แท่งสัญญาและ 6 ก่อนหน้า)
ก่อนและหลังกลับกันโดยสมบูรณ์
ทิศทางลำดับราคาเปลี่ยนไปเป็นตรงกันข้าม
จะอธิบายเหตุผลสั้นๆ ว่าเกิดขึ้นได้อย่างไร
ตรรกะเดิมเขียนไว้เป็นอินดิเคเตอร์ MT4 ใน MQL4 ช่วงหยุดขาดทุนในแง่ความหมายคือดังนี้
low[evalBar ~ evalBar+SwingLength+1]
ใน MQL4 ลำดับราคาจะ0 คือแท่งล่าสุดยิ่งหมายเลขใหญ่ย้อนไปมากขึ้นอยู่ในอดีต
ดังนั้น "evalBar จาก +6" ก็คือ มองย้อนหลัง 6 ไทม์เฟรมจากแท่งสัญญา
ในทางตรงกันข้าม เมื่ออ่านข้อมูลราคาใน Python ปกติจะเป็น0 คือแท่งที่เก่าที่สุดหมายเลขใหญ่ขึ้นจะไปสู่อนาคต
เมื่อเรียงลำดับจะได้แบบนี้
MQL4 (0คือรีล่าสุด) … [8][7][6][5][4][3][2][1][0] ← ปลายขวาคือปัจจุบัน
Python(0คือเก่าสุด) [0][1][2][3][4][5][6][7][8] … ← ไปทางขวาคืออนาคต
ถึง "+6" เดียวกัน ใน MQL4 จะเป็นอดีต ใน Python จะเป็นอนาคต
เพราะฉะนั้นเมื่อย้าย ต้องกลับทิศทางหมายเลขทั้งหมด ในสคริปต์นี้จึงกลับทิศทางที่อื่นๆ แล้ว เพียงบรรทัดเดียวที่ยังคงเขียนเป็นแบบเดิม
หากมองห้าหกช่วงอนาคต จะเกิดอะไรขึ้น
นี่คือส่วนที่สำคัญที่สุด
การเข้าออเดอร์คือจุดเริ่มต้น จุดเริ่มต้นคือตารับแท่งถัดไปหลังแท่งสัญญา หยุดขาดทุนถูกวางต่ำลงไปกว่าสมมุติที่แท่งสัญญาเกิดขึ้น และอีก 6 ไทม์เฟรมถัดไป
นั่นคือตั้งแต่เข้าออเดอร์ไปในช่วง 6 ไทม์เฟรม จะไม่มีการถูกหยุดขาดทุนเพราะต่ำกว่าจุดต่ำสุดของทั้ง 6 ไทม์เฟรมเดิมจึงมีหยุดขาดทุนอยู่ตลอด (ถ้าขายสูงขึ้น จะอยู่เหนือจุดสูงสุดของอนาคต ซึ่งก็คือทิศทางตรงกันข้าม)
- ถ้าเป็นกรอบ 1 ชั่วโมง จะมี 6 ชั่วโมง
- ถ้าเป็นกรอบ 4 ชั่วโมง จะมี 24 ชั่วโมง
ช่วงเวลานี้จะไม่แพ้
ในทางตรงกันข้าม เป้าหมายกำไรใกล้เคียงกับภูเขา ( swing ) ก่อนหน้า 6 ไทม์เฟรม เพราะ 6 ไทม์เฟรมก็เพียงพอที่จะถึง
ผลลัพธ์เลยเป็นดังนี้
- ชนะคือชนะเล็กๆ ที่ไปถึงเป้าหมายใกล้
- แพ้คือหลังจาก 6 ไทม์เฟรมผ่านไป แล้วหยุดขาดทุนที่วางไว้ไกลจะลงมาถึง ผู้ชนะหนึ่งครั้งมีมูลค่ามาก
อัตราชนะ 87%、เฉลี่ยกำไรชนะ 4.99 ดอลลาร์、เฉลี่ยขาดทุน 15.10 ดอลลาร์ รูปแบบตัวเลขสามารถอธิบายตรงนี้ได้
「ถ้าวางหยุดขาดทุนใต้จุดต่ำสุดอนาคต จะทำให้ได้อัตราชนะสูงขึ้น」
ไม่ใช่ว่าเทคนิคดีเยี่ยม แต่เป็นแค่การวางหยุดขาดทุนโดยดูอนาคต
เหตุผลที่รีทูกับดูทีละบรรทัดไปผ่าน
สคริปต์นี้ผมสร้างโดยวางโค้ด MQL4 ทางด้านข้าง เพื่อให้ตรวจสอบทีละฟังก์ชันว่า “ทำสิ่งเดียวกันกับต้นฉบับหรือไม่”
บรรทัดนี้ไม่พบด้วยวิธีตรวจสอบแบบนั้น
- รหัสต้นฉบับ: จาก evalBar ไป SwingLength+1 ไทม์เฟรม
- Python: จาก i ไป SWING_LEN+1 ไทม์เฟรม
เมื่อเปรียบเทียบดู เห็นว่าเห็นด้วยอย่างแน่นอน “ว่าเหมือนกันหรือไม่” ตรวจสอบนี้จะถือว่าบรรทัดนี้ถูกต้อง
ปัญหาคือ ถึงแม้เขียนเหมือนกัน แต่ความหมายกลับกัน
สิ่งที่ควรดูไม่ใช่ “เหมือนกันหรือไม่” แต่ “บรรทัดนี้มาจากแท่งสัญญาในอดีตมากกว่าหรือไม่”
ถ้าคำถามที่ตรวจสอบต่างกัน ก็ไม่พบแม้ดูบรรทัดเดิมซ้ำๆ
จริงๆ แล้วการตรวจสอบผ่านไปแล้ว
จุดที่เห็นเป็นครั้งแรก
เห็นหลังจากออกตัวเลขไม่กี่วัน
นำตรรกะโลจิกชุดเดียวกันมาสร้าง EA (โปรแกรมซื้อขายอัตโนมัติ) และรันบน MT4 Strategy Tester ซึ่งใช้โค้ด MQL4 ต้นฉบับให้ทำงานบน MT4
อัตราชนะของ EA นั้นไม่สอดคล้องกับอัตราชนะที่ได้จาก Python แบบสิ้นเชิง
บอกตามตรง ตอนแรกผมบันทึกว่า “ทำซ้ำไม่ได้” แล้ววางไว้ข้างๆ เนื่องจากเงื่อนไขเล็กน้อยใน tester กับ Python ต่างกัน คิดว่ามันเป็นเหตุผลที่ควรยอมรับ
แต่ก็ยังสงสัย จัดการคำนวณเข้าออเดอร์ หยุดขาดทุน และทำกำไรทีละส่วน และสุดท้ายพบว่าบรรทัดที่หยุดขาดทุนหันไปหันหน้าไปทางอนาคต
วิธีการยืนยันตัวเลขที่แก้แล้วว่าเป็น “ถูกต้อง”
เมื่อแก้บัคแล้ว เรื่องต่อมาคือ ตรวจสอบว่าเลขที่แก้ถูกต้องหรือไม่ บางทีอาจใส่ข้อผิดพลาดอื่นเข้าไปโดยไม่ตั้งใจ
หลักฐานมีสองแนวทางที่สอดคล้องกัน
- อัตราชนะของ EA จริงบนฮาร์ดแวร์ MT4 ที่ใช้งานจริง
- อัตราชนะจาก Python ที่รันด้วยคู่สกุลเงินเดียวกันและช่วงเวลาเดียวกัน
ทั้งสองแนวทางตรงกันภายใน 1 จุด
ภาษาและสภาพแวดล้อมที่ต่างกันสองแบบบรรลุข้อเท็จจริงเดียวกัน จึงเริ่มเชื่อถือเลขที่แก้แล้วมากขึ้น
ตัวเลขทองคำ (อัตราชนะ 46.3% PF 1.07) ก็ได้มาจากสคริปต์ที่ปรับแก้แบบเดียวกัน
สิ่งที่ทำเสมอก่อนออกตัวเลข
หลังเหตุการณ์นี้ ผมตัดสินใจเพิ่มเติมว่า ก่อนนำตัวเลขไปลงบนหน้าผลิตภัณฑ์หรือบทความ จะต้องตรวจสอบ 4 ข้อที่เกี่ยวข้องกับบัคครั้งนี้โดยตรง
1. ตรวจดูอนาคตว่าไม่ได้มองไปข้างหน้าเพียงลำพัง และตรวจสอบทีละหมายเลข
เป็นงานแยกต่างหากนอกเหนือจากการทบทวนโค้ด
สรุปโค้ดที่อ่านลำดับราคามาทั้งหมด แล้วถามไปทีละข้อว่า “หมายเลขนี้เป็นอดีตกว่าตัวสัญญาหรือไม่” หากหมายเลขใหญ่กว่าขั้น i ปรากฏขึ้นจะหยุดทันที
ไม่ใช่ “เหมือนต้นฉบับ” อย่างเดียว แต่ “เป็นอดีต” ด้วย การเปลี่ยนประเด็นคำถามทำให้บรรทัดนี้ตรวจพบได้ทันที
2. หากอัตราชนะเกิน 70% ให้สงสัยบัคก่อน
การสงสัยว่าวิธีนี้อาจดีมากเป็นเรื่องสุดท้าย
โดยเฉพาะรูปแบบที่ “อัตราชนะเกิน 80% และกำไรชนะเฉลี่ยใกล้เคียงกับ 1 ใน 3 ของขาดทุนเฉลี่ย” มักเห็นในกรณีที่อ้างถึงอนาคต เกิดบ่อยเมื่อมีการตรวจสอบก่อนหยุดขาดทุนและการทำกำไร
3. ก่อนเปิดเผยตัวเลข ต้องเทียบกับ MT4/MT5 จริงก่อน
ตัวเลข Python อย่างเดียวจะไม่เผยแพร่ ต้องให้การทดสอบด้วยโค้ด MQL ที่ทำงานบน tester และอัตราชนะสอดคล้องกันก่อน
4. สคริปต์ต้องคงอยู่พร้อมกับตัวเลข
จริงๆ สคริปต์นี้เคยอยู่ในโฟลเดอร์ชั่วคราวและเกือบหายไป เพราะตัวเลขปรากฏอยู่ในเอกสารแต่ไม่รู้ว่าออกจากโค้ดไหน
ตอนนี้ ผมเก็บเวอร์ชันก่อนแก้และหลังแก้ไว้ในที่เดียวกับซอร์สอินジ และเก็บตารางตัวเลขทั้งก่อน/หลังไว้ประกอบคำอธิบาย
ต่อไปผมทำอะไร
GD-Signal ยังหยุดขายอยู่
รหัสตัวอินจิยังไม่มีบัค บัคอยู่ที่โค้ดสำหรับวัดผลลัพธ์
หลังจากนั้น ผมสร้างเครื่องมือทองคำชื่อ GD-Deck ซึ่งตอนออกแบบ กำไรและอัตราชนะไม่เปิดเผยเลย และหน้าผลิตภัณฑ์ก็ไม่ระบุ
หากไม่มีโค้ดวัดผล ก็ไม่มีบัคในโค้ดวัดผลที่จะทำลายผลิตภัณฑ์
หน้าขาย GD-Deck คลิกดูที่นี่
สรุป
- อัตราชนะ 87% และ PF 2.21 เกิดจากบัคบรรทัดเดียวของสคริปต์ทดสอบ เมื่อแก้แล้ว อัตราชนะลดเหลือ 46.3% PF 1.07
- สาเหตุคือ ลำดับทิศทางระหว่าง MQL4 (0 คือปัจจุบัน) กับ Python (0 คืออดีต) ผิดด้านเดียว แต่ช่วงหยุดขาดทุนถูกแก้ไขด้านทิศทางไม่ครบ
- หากวางหยุดขาดทุนใต้จุดต่ำสุดในอนาคต 6 ไทม์เฟรม จะไม่แพ้ในช่วง 6 ไทม์เฟรม ดังนั้นจึงอัตราชนะสูงแต่กำไรเล็ก
- การตรวจสอบว่า “เหมือนต้นฉบับหรือไม่” ไม่พบบัคนี้ ต้องตั้งคำถามว่า “หมายเลขนี้เป็นอดีตหรือไม่” แยกต่างหาก
- ตัวเลขจะไม่เปิดเผยจนกว่าจะตรงกับ EA จริงบนฮาร์ดแวร์ และสคริปต์จะคงอยู่พร้อมกับตัวเลข
“เมื่อประเมินเครื่องมือของตนเอง ควรให้ความสำคัญกับโค้ดของผู้ประเมินมากกว่า”
ตั้งแต่สิงหาคมเป็นต้นมา ผมเชื่อเช่นนั้นมาตลอด
หลายคนที่ได้รับประโยชน์จากตัวเลขการทดสอบหรือถูกหลอก อาจต้องการบทความนี้เป็นสัญญาณให้ตั้งคำถามก่อนที่จะแสดงความยินดี
บทความนี้เป็นบันทึกการพัฒนาและการทดสอบส่วนตัว เนื้อหาอัตราชนะ PF ฯลฯ เป็นข้อมูลเพื่ออธิบายข้อผิดพลาด ไม่ใช่ผลการดำเนินงานของผลิตภัณฑ์ใดๆ และไม่รับประกันประสิทธิภาพของกลยุทธ์หรือผลิตภัณฑ์ใดๆ ผู้ใช้งานควรตัดสินใจลงทุนด้วยตนเอง
ขอบคุณที่อ่านจนจบ
ครั้งหน้าจะพูดถึงการตรวจสอบก่อนยุติ MT4 ของ OANDA และสิ่งที่ผู้ที่ใช้งานอินจิ้นควรตรวจสอบก่อนย้ายไปใช้งาน