เปิดสถานะ Long ที่ 84,120 จุดตัดขาดทุนที่ 84,036 เป้าทำกำไรที่ 84,271 แท่งของนาทีถัดมาเข้ามา: ราคาเปิด 84,118 จุดสูงสุด 84,290 จุดต่ำสุด 84,010 ราคาปิด 84,240
ระดับทั้งสองอยู่ภายในแท่งนั้น จุดตัดขาดทุนถูกแตะและเป้าทำกำไรก็ถูกแตะ แต่ตัวเลข OHLC ทั้ง 4 ค่าไม่มีข้อมูลเลยว่าจุดไหนเกิดก่อน แบ็กเทสต์ของคุณก็ยังให้ผลลัพธ์ออกมาได้สักตัวเลข ระหว่างทางในลูปมีบรรทัดหนึ่งตัดสินใจไปแล้ว ซึ่งน่าจะเป็นบรรทัดที่ตอนเขียนคุณไม่ได้คิดว่าเป็นสมมติฐานในการจำลอง
นี่คือแหล่งที่มาที่ใหญ่ที่สุดเพียงอย่างเดียวของผลตอบแทนปลอมที่ผมพบในกลยุทธ์ระยะสั้น มากกว่าค่าธรรมเนียมและ slippage เพราะมันไม่ได้ดูเหมือนสมมติฐาน แต่มันดูเหมือนแค่การต่อระบบเข้าด้วยกัน
วิธีผิดแบบที่หนึ่ง: ปล่อยให้ if-chain ตัดสินใจ
รูปแบบที่พบกันทั่วไป:
if bar.high >= target:
exit(target, "tp")
elif bar.low <= stop:
exit(stop, "sl")
ไม่มีใครเลือกให้ "เป้าทำกำไรถูกตัดสินก่อนจุดตัดขาดทุน" แค่มีการเขียนเงื่อนไขตรวจเป้าทำกำไรก่อน เพราะเป็นกรณีที่ทำให้ผลลัพธ์ดีและเป็นสิ่งที่คุณกำลังคิดถึงอยู่ สลับลำดับสอง branch แล้วเส้นกราฟทุนก็เปลี่ยน แค่นี้ก็น่าจะบอกได้แล้วว่า P&L ของกลยุทธ์ส่วนหนึ่งขึ้นอยู่กับโปรแกรมแก้ไขโค้ดของคุณ
ผมลองใช้กลยุทธ์ scalper แบบกลับสู่ค่าเฉลี่ยที่ธรรมดาจงใจ บนสัญญา perp ของ BTCUSDT ข้อมูลแท่ง 1 นาทีเป็นเวลา 3 เดือน มี 4,812 เทรด จุดตัดขาดทุน 0.10% และเป้าทำกำไร 0.18% จากราคาเข้า สัญญาณเดิม ค่าธรรมเนียมเดิม เปลี่ยนแค่ธรรมเนียมตัดสินกรณีระดับชนกัน
เทรด 1 ใน 8 รายการเป็นตัวกำหนดผลลัพธ์ทั้งหมด นี่คือคณิตศาสตร์ของคู่จุดตัดขาดทุนและเป้าทำกำไรที่วางไว้ใกล้กัน: เทรดที่คลุมเครือคือเทรดที่ราคาวิ่งไปทั้งสองทาง ซึ่งเป็นกรณีที่น่าสนใจส่วนใหญ่ และแต่ละรายการมีมูลค่าเท่ากับระยะห่างจากจุดตัดขาดทุนถึงเป้าทำกำไรทั้งหมด ขึ้นอยู่กับการโยนเหรียญ 12.6% ของเทรด × ระยะห่าง 0.28% เท่ากับ 3.5% ของมูลค่าซื้อขายรวมต่อหนึ่งหน่วยของตัวอย่าง ซึ่งมากกว่า edge จริงของกลยุทธ์อย่างเทียบกันไม่ได้
อัตรานี้ขึ้นอยู่กับขนาดแท่งเมื่อเทียบกับระยะห่างระหว่างระดับของคุณ และแย่ลงอย่างรวดเร็วเมื่อใช้แท่งที่ยาวขึ้น จุดตัดขาดทุนและเป้าทำกำไรเดิม สัญญาณเดิม แต่เปลี่ยนความถี่ของแท่ง:
| ช่วงเวลาแท่ง | เทรดที่มีทั้ง 2 ระดับอยู่ในแท่งเดียว | Sharpe (เป้าทำกำไรก่อน) |
|---|---|---|
| 1s | 0.3% | 0.91 |
| 1m | 12.6% | 2.31 |
| 5m | 34% | 3.60 |
| 15m | 49% | 4.42 |
| 1h | 71% | 5.88 |
ลองดูว่าตารางนี้กำลังบอกอะไรจริง ๆ แท่งที่หยาบขึ้นทำให้แบ็กเทสต์ดีขึ้น สัญชาตญาณของนักวิจัยทุกคนบอกว่าแท่งรายชั่วโมงเป็นทางเลือกที่ระมัดระวัง เพราะมี noise น้อยกว่าและ overfit กับโครงสร้างจุลภาคของตลาดน้อยกว่า แต่เมื่อใช้ธรรมเนียมตัดสินภายในแท่งที่เข้าข้างคุณ แท่งที่หยาบขึ้นก็เป็นแค่กล่องที่ใหญ่ขึ้น ซึ่งเปิดโอกาสให้สมมติว่าคุณโชคดี ในแท่ง 1h เทรด 7 ใน 10 รายการขึ้นอยู่กับธรรมเนียมล้วน ๆ แบ็กเทสต์นั้นไม่ได้ทดสอบกลยุทธ์ แต่กำลังทดสอบลำดับของคำสั่ง `if` 2 คำสั่งซ้ำ 3,400 ครั้ง
วิธีผิดแบบที่สอง: สมมติว่าคำสั่งตัดขาดทุนจับคู่ที่ราคาตัดขาดทุน
สมมติว่าคุณแก้ลำดับแล้ว จุดตัดขาดทุนถูกตัดสินก่อน คุณบันทึกขาดทุนเท่ากับ 0.10% พอดีบวกค่าธรรมเนียม taker แล้วรู้สึกว่าทำอย่างรอบคอบ แต่ก็ยังมีข้อผิดพลาดอีก 2 อย่าง
ข้อแรก จุดตัดขาดทุนเป็นเงื่อนไขกระตุ้น ไม่ใช่การจับคู่คำสั่ง บน Binance USDⓈ-M คำสั่ง STOP_MARKET จะกลายเป็นคำสั่ง market ทันทีที่ตรงตามเงื่อนไขกระตุ้น จากนั้นจึงกินสภาพคล่องที่มีอยู่ใน order book ในนาทีที่เงียบสงบ อาจเกิด slippage แค่ 1 หรือ 2 tick แต่นาทีที่กระตุ้นจุดตัดขาดทุนของคุณจริง ๆ ซึ่งเป็นนาทีที่แท่งมีตัวแท่ง 40 จุดและมีการล้างสถานะต่อเนื่องอยู่ด้านล่าง order book จะบางพอดีที่ฝั่งซึ่งคุณกำลังข้ามไป ในตัวอย่างของผม เมื่อนำการกระตุ้นจุดตัดขาดทุนไปเทียบกับข้อมูล tick ค่ามัธยฐานของราคาจับคู่แย่กว่าราคากระตุ้น 1.4 bps และเปอร์เซ็นไทล์ที่ 95 อยู่ที่ 11 bps สำหรับจุดตัดขาดทุน 10 bps ส่วนหางของการกระจายทำให้คุณเสียความเสี่ยงเพิ่มอีก 1 ใน 10 จากที่คิดว่ากำหนดไว้
ข้อที่สองละเอียดกว่าและเฉพาะกับ perp: ราคาใดเป็นตัวกระตุ้น Binance ตั้งค่าเริ่มต้นให้คำสั่งตัดขาดทุนใช้ mark price และ mark price คำนวณจากดัชนีบวก basis ที่ปรับให้เรียบ ไม่ได้ใช้ราคาซื้อขายล่าสุดบน venue นั้น ชุดข้อมูล OHLC ของคุณเป็นราคาซื้อขายล่าสุด ซึ่งเป็นคนละชุดข้อมูลกัน และจะแตกต่างกันมากที่สุดในช่วงเหตุการณ์ที่กระตุ้นจุดตัดขาดทุนพอดี
| ราคาซื้อขายล่าสุด (klines ของคุณ) | Mark price (ค่าเริ่มต้นสำหรับการกระตุ้น) | |
|---|---|---|
| แหล่งข้อมูล | การซื้อขายบน venue นี้ | ดัชนีจากหลาย venue + basis |
| ลักษณะของไส้เทียน | แสดงการเคลื่อนไหวเต็มช่วง | ถูกลดความผันผวนลงอย่างมาก |
| ความแตกต่างที่พบโดยทั่วไป | 1–3 bps ในช่วงสงบ, 20–35 bps ในนาทีที่เกิดการล้างสถานะ | |
| ผลต่อแบ็กเทสต์ | จุดตัดขาดทุนที่ถูกกระตุ้นทั้งที่ไม่ควรถูกกระตุ้น และในทางกลับกัน | |
ดังนั้น ไส้เทียน 25 bps ในข้อมูลราคาซื้อขายล่าสุดทำให้แบ็กเทสต์ปิดสถานะขาดทุน ทั้งที่ mark price แบบ live ไม่เคยเข้าใกล้จุดกระตุ้นในระยะ 10 bps ด้วยซ้ำ หรืออาจเกิดกลับกันในวันที่ดัชนีขยับแต่ venue ของคุณตามไม่ทัน ถ้าคุณตั้ง workingType เป็น CONTRACT_PRICE อย่างน้อยก็ทำให้พฤติกรรม live สอดคล้องกับข้อมูลของคุณ ซึ่งโดยทั่วไปเป็นทางเลือกที่เหมาะสำหรับนักวิจัย เพราะการจำลองการกระตุ้นด้วย mark price อย่างถูกต้องหมายถึงต้องนำข้อมูลอีกชุดผ่าน fill engine ทั้งระบบ
กรณีที่ผมจำได้แม่นที่สุดคือ มีคนในทีมเรา "ปรับปรุง" กลยุทธ์โดยขยับ take-profit จาก 0.18% เป็น 0.21% Sharpe เพิ่มจาก 2.3 เป็น 3.1 ไม่มี edge ใหม่เกิดขึ้น เป้าทำกำไรแค่ขยับออกไปพ้นส่วนหนาแน่นของการกระจายไส้เทียน 1 นาที ทำให้มีเทรดน้อยลงที่ตกอยู่ในกลุ่มคลุมเครือ ซึ่งโค้ดกำลังให้ชนะอย่างเงียบ ๆ พวกเขาปรับค่า tie-breaker ให้เหมาะที่สุด
วิธีผิดแบบที่สาม: สมมติให้แย่ที่สุดเสมอแล้วเรียกว่าระมัดระวัง
วิธีแก้ตามสัญชาตญาณคือมองในแง่ร้าย หากแตะทั้ง 2 ระดับ ให้เลือกจุดตัดขาดทุน จบ ไม่มองโลกในแง่ดีอีกแล้ว พร้อมปล่อยใช้งาน
เมื่อก่อนผมก็ทำแบบนี้ ดีกว่าทางเลือกอื่น แต่ก็ยังผิด ด้วยเหตุผล 2 ข้อ
มันทำให้กลยุทธ์ที่ใช้ได้ถูกปัดทิ้ง การตัดสินแบบมองแง่ร้ายใน 12.6% ของเทรดทำให้กลยุทธ์นี้เสีย Sharpe ไป 2.1 จุด เมื่อเทียบกับค่า 0.94 ที่ได้จากการตัดสินด้วย tick ถ้าค่าจริงคือ 0.94 แต่ธรรมเนียมของคุณรายงาน 0.18 คุณก็ทิ้งแนวคิดนั้นแล้วไปทำงานกับสิ่งที่แย่กว่า การระมัดระวังที่คลาดไป 2 จุด Sharpe ไม่ใช่ความระมัดระวัง แต่เป็น noise ที่วางท่าเหมือนมีหลักการ
ที่แย่กว่านั้นคือมันทำให้การหาค่าที่เหมาะสมผิดเพี้ยน ป้อนการกวาดพารามิเตอร์ด้วย tie-break แบบมองแง่ร้าย แล้ว optimizer จะเรียนรู้ให้หลีกเลี่ยงความคลุมเครือ เพราะตอนนี้ความคลุมเครือกลายเป็นโทษล้วน ๆ มันจะขยับไปหาจุดตัดขาดทุนที่กว้างและเป้าทำกำไรที่ใกล้ หรือไปหาแท่งที่ช้าลงซึ่งระดับทั้ง 2 แทบไม่เกิดร่วมกัน แล้วนำเสนอพารามิเตอร์ที่ถูกเลือกให้เข้ากับธรรมเนียมการจับคู่คำสั่งของคุณ ไม่ใช่กับตลาด เป็นความล้มเหลวแบบเดียวกับกรณีมองแง่ดี ต่างกันแค่ทิศทาง และใน tearsheet ก็มองไม่เห็นพอกัน
กฎคร่าว ๆ ที่เราใช้เป็นอย่างแรก: ถ้า stop_distance + target_distance เล็กกว่าช่วงเปอร์เซ็นไทล์ที่ 75 ของช่วงเวลาแท่ง สมมติฐานภายในแท่งของคุณจะมีผลต่อ P&L มากกว่าสัญญาณ คำนวณตัวเลขทั้งสอง ใช้แค่ 4 บรรทัด และช่วยยุติการทบทวนกลยุทธ์มาแล้วมากกว่าการตรวจสอบอย่างอื่นข้อใดข้อหนึ่ง
สิ่งที่ได้ผลจริง
เส้นทางราคาภายในแท่งคือข้อมูล ไปหามา หรือไม่ก็จำกัดขอบเขตของส่วนที่หาไม่ได้
- ตัดสินด้วยชุดข้อมูลที่ละเอียดที่สุดเท่าที่มี Binance aggTrades สำหรับนาทีที่เกี่ยวข้องมีข้อมูลเพียงไม่กี่ร้อยแถว และตอบได้ชัดเจนเลยว่าระดับใดถูกแตะก่อน รวมถึงการกวาดคำสั่งจับคู่ที่ราคาใด คุณไม่จำเป็นต้องใช้ข้อมูล tick กับแบ็กเทสต์ทั้งหมด ใช้เฉพาะแท่งที่คลุมเครือก็พอ ในตัวอย่างของผมมี 606 นาที จากทั้งหมด 129,600 นาที ดาวน์โหลดข้อมูลเล็กน้อย ไม่ใช่โครงการวางระบบโครงสร้างพื้นฐาน
- ถ้าไม่มีข้อมูล tick ให้ลดช่วงเวลาลง 1 หรือ 2 ระดับเพื่อใช้ตัดสินเท่านั้น ใช้สัญญาณจาก 15m แล้วตัดสินการออกด้วยแท่ง 1s หรือ 1m ความคลุมเครือลดจาก 49% เหลือเพียงเศษเสี้ยวของเปอร์เซ็นต์ และส่วนที่เหลือก็น้อยพอที่จะละไว้ได้โดยไม่ปิดบัง
- รายงานช่วงผลลัพธ์ไว้เสมอ รันแบ็กเทสต์ทุกชุด 2 ครั้ง ใช้การตัดสินแบบมองแง่ดีและมองแง่ร้าย แล้วพิมพ์ค่า Sharpe ทั้ง 2 ค่าคู่กับค่าที่ได้จากการตัดสินจริง ส่วนต่างนี้คือความไม่แน่นอนภายในแท่ง และควรอยู่ใน tearsheet ข้างช่วงความเชื่อมั่นที่คุณจะแสดงให้ค่า Sharpe เอง เมื่อช่วงอยู่ระหว่าง 0.2–2.3 ข้อสรุปใด ๆ ภายในช่วงนั้นก็เชื่อถือไม่ได้
- ติดตามอัตราความคลุมเครือเป็นตัวชี้วัดหลัก เราแสดงค่านี้ไว้ด้านบนของการ์ดกลยุทธ์ทุกใบ ข้างจำนวนเทรดและ turnover หากอัตราสูงกว่า 5% โดยประมาณ แสดงว่าสิ่งที่กำลังทดสอบคือ logic การออกสถานะ ไม่ใช่ logic การเข้า
- จำลองเงื่อนไขกระตุ้นแยกจากการจับคู่คำสั่ง ใช้ชุดราคาที่ venue ใช้จริงในการกระตุ้น แล้วจับคู่คำสั่งที่ราคากระตุ้นบวกค่า slippage ที่สุ่มจากการกระจายซึ่งปรับเทียบกับข้อมูลเทป ไม่ใช่ที่ราคากระตุ้น
หุ้นก็มีปัญหาเดียวกัน แค่ปรากฏในรูปแบบที่ต่างออกไป จุดตัดขาดทุนที่ 62.00 สำหรับหุ้นที่เปิดกระโดดลงมาอยู่ที่ 58.40 ในชั่วข้ามคืน ไม่ได้จับคู่ที่ 62.00 แต่จับคู่ที่ราคาต่ำกว่าราคาเปิด และแบ็กเทสต์ข้อมูลแท่งรายวันที่คิด slippage เป็น −$0.00 เมื่อราคาเปิดกระโดดข้ามจุดตัดขาดทุน ก็พร้อมจะบอกคุณว่าการครอบด้วยคำสั่ง stop-loss ช่วยลด drawdown ทั้งที่ไม่จริง มันแค่ไม่เคยถูกทดสอบในวันที่สำคัญ การหยุดซื้อขายก็ทำให้เกิดปัญหาเช่นกัน: คำสั่งของคุณจะถูกจับคู่จริงตอนประมูลเปิดซื้อขายอีกครั้ง ที่ราคาซึ่งไม่ปรากฏในจุดต่ำสุดของแท่ง
เรื่องเหล่านี้ไม่ใช่เรื่องแปลกใหม่ แค่ต้องยอมรับว่าแท่งเป็นข้อมูลสรุป และกลยุทธ์ที่ใช้จุดตัดขาดทุนกับเป้าทำกำไรคือการเดิมพันกับลำดับเหตุการณ์ที่ข้อมูลสรุปตัดทิ้งไป เมื่อ engine สำหรับ paper trading รันกลยุทธ์นี้กับข้อมูลเทปจริงในที่สุด เทปก็มีคำตอบเรื่องลำดับเหตุการณ์ และไม่เคยสนเลยว่าคุณพิมพ์ branch ไหนของคำสั่ง if ก่อน
← บทความทั้งหมด

