มีคำถามหนึ่งที่ backtest ทุกตัวหลบเลี่ยง: ถ้าโค้ดชุดเดียวกันนี้เจอตลาดจริงแบบเรียลไทม์ มันจะทำแบบเดิมไหม? คุณตอบคำถามนี้ด้วยการ backtest เพิ่มไม่ได้ ทางเดียวคือรันกลยุทธ์กับข้อมูลสด ด้วยกลไกการส่งคำสั่งจริง แล้วตรวจทีละเทรดว่าสองโลกนี้ตรงกันไหม เราตรวจแบบนี้ทุกวันโดยอัตโนมัติ และมันคือเครื่องมือจับบั๊กที่ให้ผลคุ้มค่าที่สุดในแพลตฟอร์มนี้
วันแรก: สร้างกระจกสะท้อน
กลยุทธ์ที่ผ่านการโปรโมตทุกตัวจะรันอยู่ใน node สดของตัวเองแบบแยกขาด กินสตรีมจริงจากตลาด: quote สดสำหรับการจับคู่คำสั่ง สเปรดจริง ชั่วโมงเทรดจริง ไม่มี state ร่วมกันระหว่างกลยุทธ์ (บทเรียนนี้เราเจ็บมาแล้ว — node ที่ใช้ร่วมกันทำให้การเรียกดูสถานะโพซิชันของกลยุทธ์หนึ่งรั่วไปปนกับอีกตัว) ทุกการเข้าออกถูกบันทึกพร้อมค่าสัญญาณที่ก่อให้เกิดมันขึ้น
ทุกเช้า: รีเพลย์แล้วเทียบ
งานประจำวันจะหยิบหน้าต่างเวลา paper ของแต่ละกลยุทธ์ รันโค้ดชุดเดียวกันเป๊ะผ่านเอนจิน backtest บนช่วงเวลาเดียวกันด้วยข้อมูลจาก catalog แล้วจับคู่การเข้าออกแบบหนึ่งต่อหนึ่ง: ฝั่งเดียวกัน timestamp อยู่ในเกณฑ์คลาดเคลื่อนที่ยอมรับได้ ขนาดเท่ากัน ผลลัพธ์ไม่ใช่ค่าสหสัมพันธ์หรือความรู้สึก แต่เป็นตาราง: จับคู่ได้, มีเฉพาะฝั่ง paper, มีเฉพาะฝั่งรีเพลย์ กลยุทธ์ที่สุขภาพดีจะอยู่ที่ จับคู่ได้ 54 จาก 55 ส่วนตัวที่มีปัญหาจะบอกคุณตรง ๆ ว่าเทรดไหนแตกต่าง และแตกต่างตอนไหน
อะไรพังบ้าง เรียงตามลำดับที่มันพัง
- การเข้าออกช่วง warmup node สดป้อนข้อมูลย้อนหลังเข้าไปเพื่อ warm up อินดิเคเตอร์ — แล้วกลยุทธ์ที่มี bar handler เขียนเองก็เทรดบนข้อมูลนั้นอย่างมีความสุข ส่วน backtest ไม่เคยเห็นการเข้าซื้อผีพวกนี้เลย ทางแก้อยู่ที่เลเยอร์ส่งคำสั่ง ไม่ใช่แก้ที่ตัวกลยุทธ์ เพราะไม่งั้นทุกกลยุทธ์ก็จะสืบทอดบั๊กนี้ต่อไป
- warmup แบบหลายขาที่ไล่ทีละตัว กลยุทธ์ cross-sectional ตัวหนึ่ง warm up ทีละอินสตรูเมนต์ ทำให้ทุกขามองเห็นขาพี่น้องเป็นค่าว่าง และตัว anchor ไม่เคยถูกตั้งค่า พอรันจริงมันไม่เทรดเลย แต่พอรีเพลย์กลับเทรดปกติ จับคู่ได้ศูนย์เทรด — และมีแต่การเทียบเท่านั้นที่พูดออกมาดัง ๆ
- ลูปแครชจากความละเอียดของ tick tick จาก aggTrade สดเข้ามาด้วยความละเอียดที่ต่างจากที่อินสตรูเมนต์ใน sandbox คาดไว้ node เลยแครชวนซ้ำที่ assertion ซึ่ง backtest ไม่มีทางไปกระตุ้นได้เลย เพราะแท่งใน catalog ถูกนอร์มัลไลซ์มาแล้ว
- อาการหลงลืมหลังรีสตาร์ท การรีสตาร์ตโปรเซสทำให้ตรรกะเข้าออร์เดอร์ยิงซ้ำบนโพซิชันที่มีอยู่แล้ว — สำหรับกลยุทธ์ที่ทำ rebalance การรีสตาร์ทหนึ่งครั้งก็กลายเป็นการ rebalance ไปเงียบ ๆ ในการเทรดจริงนี่ไม่ใช่เชิงอรรถเรื่องพาริตี้ แต่มันคือธงแดงที่มีเงินติดอยู่ด้วย
สังเกตแพตเทิร์นดู: ไม่มีสักข้อที่เป็นบั๊กของสัญญาณ ทั้งหมดเป็นบั๊กที่ขอบเขตของการส่งคำสั่ง — warmup, state, ความละเอียด, การรีสตาร์ท ซึ่งเป็นความล้มเหลวประเภทที่ backtest มองไม่เห็นโดยโครงสร้าง เพราะในโลกของ backtest เอนจิน backtest คือ ขอบเขตของการส่งคำสั่งเสียเอง
ความไม่สมมาตรที่เราจงใจเก็บไว้
สองโลกนี้ตั้งใจให้ไม่เหมือนกันเป๊ะ ฝั่ง backtest คิดค่าครึ่งสเปรดตามแบบจำลองบวกกับ market impact แบบรากที่สองเข้าไปใน PnL ส่วนฝั่ง paper จ่ายสเปรดจริงในราคาที่จับคู่ได้ และไม่มีโมเดล impact การเอาสเปรดจำลองไปใส่ฝั่ง paper (หรือเอาสเปรดจริงไปใส่ฝั่ง backtest) จะกลายเป็นการนับซ้ำ พาริตี้หมายถึง ตัดสินใจเหมือนกัน จับคู่คำสั่งได้ภายในเกณฑ์คลาดเคลื่อน — ไม่ใช่ PnL ตรงกันทุกบิต การรู้ว่าความต่างไหนมีเหตุผลรองรับและความต่างไหนคือบั๊ก คือหัวใจของวินัยข้อนี้
backtest คือสมมติฐาน paper trading คือการทดลอง ส่วนพาริตี้จากการรีเพลย์คือสมุดบันทึกแล็บที่คอยจับได้ว่าคุณกำลังหลอกตัวเอง
ถ้าคุณรันกลยุทธ์อยู่แล้วไม่ได้เทียบพฤติกรรมสดกับพฤติกรรมที่รีเพลย์อย่างเป็นกิจวัตร แสดงว่ามีบั๊กอยู่ประเภทหนึ่งที่คุณไม่เคยเห็นเลย เราเจอราวหนึ่งตัวทุกสองสัปดาห์ และทุกตัวล้วนมองไม่เห็นจากภายใน backtest
← บทความทั้งหมด
