Look-Ahead Bias: ความผิดพลาดเพียงหนึ่งแท่งเทียนสร้าง Sharpe 15 จาก Noise ล้วน ๆ ได้อย่างไร
เป็นส่วนหนึ่งของซีรีส์ "Backtests Without Illusions"
📄 บทความนี้ขยายกลายเป็นเปเปอร์วิจัย การรั่วไหลแบบ look-ahead ที่แนบเนียนสามแบบถูกนำมาทดสอบอย่างมีการควบคุมเทียบกับ ground truth ที่รู้ค่าจริง (ประวัติศาสตร์จำลอง 4,000 ชุด) อ่านเปเปอร์ฉบับเต็มออนไลน์ (เวอร์ชันอินเทอร์แอกทีฟ + PDF) ได้ที่ lookahead.marketmaker.cc โค้ดและข้อมูลอยู่ที่ github.com/suenot/lookahead-inflation
ไม่กี่สัปดาห์ก่อน benchmark การค้นหาพารามิเตอร์ของเรากำลังโกหกเรา และเราเกือบไม่ทันสังเกต
เอนจิ้นดูสะอาดหมดจด logic แบบ closed-bar, การแบ่ง rolling walk-forward ที่ซื่อสัตย์, การค้นหาแบบ Sobol/QMC บน parameter space, และ test window ที่กันไว้ต่างหาก การค้นหาพบ configuration ที่ดูดีบน in-sample ปัญหาเดียวคือ: บน out-of-sample แทบทุกอย่างติดลบ เราสันนิษฐานว่ากลยุทธ์อ่อนแอเฉย ๆ
จากนั้นเราก็เจอบรรทัดหนึ่ง สัญญาณถูกตัดสินใจที่ close ของแท่ง i แต่ fill กลับถูกบันทึกบนแท่ง i เดียวกัน แทนที่จะเป็น open ของแท่งถัดไป ความผิดพลาด off-by-one หนึ่งจุดใน execution index เราย้าย fill ไปที่ open[i+1] — ราคาเดียวที่คุณจะซื้อขายได้จริงหลังจากเห็น close ของแท่ง i — แล้วผลลัพธ์ out-of-sample ก็ พลิกเครื่องหมาย การค้นหาแบบ Sobol เปลี่ยนจากขาดทุนเป็นกำไร ไม่มีอะไรเกี่ยวกับกลยุทธ์เปลี่ยนแปลงเลยแม้แต่น้อย เราแค่หยุดเทรดในอดีตเท่านั้นเอง
นั่นคือ look-ahead bias และส่วนที่น่ากังวลคือความผิดพลาดนั้น เล็ก แค่ไหน แต่ผลลัพธ์กลับ ใหญ่ ขนาดไหน บทความนี้คือการตรวจสอบตัวเองแบบมีการควบคุม: เราสร้างตัวจำลองที่รู้ ground truth อยู่แล้วโดยการออกแบบ ฉีดการรั่วไหลที่แนบเนียนทีละแบบ และวัดว่าแต่ละแบบเพิ่มผลลัพธ์ backtest มากแค่ไหนอย่างแม่นยำ ประเด็นหลัก: แม้ ไม่มี edge จริงเลยแม้แต่น้อย การ fill บนแท่งเดียวกันก็สร้าง Sharpe ต่อปีที่ +14.8 จาก noise ล้วน ๆ ได้
Look-Ahead Bias คืออะไรกันแน่

Look-ahead bias คือจุดใดก็ตามใน pipeline ของคุณที่การตัดสินใจหรือการวัดค่าใช้ข้อมูลที่ในเวลาจริงจะยังไม่มีอยู่ ณ ตอนที่ถูกนำไปใช้ ตัวอย่างในตำราเรียนมักหยาบ — เช่นใช้กำไรทั้งปีของหุ้นตัวหนึ่งในเดือนมกราคม หรือใช้ตัวเลขที่ถูก restate แต่ยังไม่ถูกเผยแพร่ สิ่งเหล่านี้สังเกตเห็นได้ง่าย แต่สิ่งที่รอดจาก code review มักแนบเนียน และซ่อนอยู่ในสามจุด:
- Execution — คุณตัดสินใจบนแท่ง
iและ fill บนแท่งi(หรือใช้ high/low ของแท่งiสำหรับ stop บนแท่งเดียวกับที่สร้างสัญญาณ) คุณซื้อขายที่ราคาซึ่งมีความสัมพันธ์กับสิ่งที่กระตุ้นให้คุณเทรด - Normalization — คุณทำ z-score, min-max หรือ scale feature ด้วยสถิติที่คำนวณจาก ทั้งซีรีส์ รวมถึงอนาคตด้วย scaler "รู้" test set
- Indicators / features — คุณ smooth หรือ filter ด้วย window ที่อยู่ตรงกลาง (หรือแอบมองไปข้างหน้าด้วยวิธีอื่น) ทำให้ค่าที่แท่ง
iมีชิ้นส่วนของแท่งi+1ปนอยู่แล้ว
ทั้งสามแบบเป็นรูปแบบของสิ่งที่วรรณกรรม machine learning เรียกว่า leakage: การปนเปื้อนของ training/evaluation ด้วยข้อมูลจากอนาคตของ target (Kaufman et al., 2012; Kapoor & Narayanan, 2023) ในโลกการเงิน ตำราหลักคือ Advances in Financial Machine Learning ของ López de Prado (2018) — purged cross-validation, embargoing, อันตรายของการ backtest วินัยแบบ point-in-time นั้นย้อนกลับไปได้อย่างน้อยถึง Fama & French (1992) ที่จงใจ lag ข้อมูลบัญชีไว้หกเดือนเพื่อให้ตัวแปรเป็นที่รู้ก่อนที่ผลตอบแทนซึ่งมันอธิบายจะเกิดขึ้น
คำถามที่บทความนี้ตอบคือคำถามเชิง ปริมาณ: ไม่ใช่ "leakage เลวร้ายหรือไม่" (ทุกคนเห็นตรงกันอยู่แล้ว) แต่คือ "แต่ละรูปแบบให้ Sharpe point เพิ่มขึ้นกี่จุด และแบบไหนอันตรายกว่ากัน?" หากไม่มีตัวเลข คุณก็ไม่สามารถให้เหตุผลกับมันได้ คุณบอกไม่ได้ว่าการเพิ่มขึ้น +0.3 เป็นแค่ noise หรือการเพิ่มขึ้น +14 คือหลักฐานมัดตัว
ตัวจำลองที่รู้ ground truth

การจะวัดการเพิ่มขึ้น (inflation) ได้ คุณต้องรู้ความจริงก่อน ข้อมูลจริงไม่เคยบอกความจริงกับคุณ — มันให้แค่ realization เดียวและไม่มี oracle ดังนั้นเราจึงสร้างตลาดสังเคราะห์ที่ เรา เป็นผู้กำหนด edge เอง
กระบวนการสร้างข้อมูล (data-generating process) เป็นแบบ causal อย่างเคร่งครัดและไม่ explosive:
ในที่นี้ คือ latent drift ที่คงอยู่ต่อเนื่องแบบ exogenous (เป็น AR(1) ที่มี ) และผลตอบแทนของแท่ง มี drift เล็ก ๆ คือ ซึ่ง รู้ล่วงหน้าหนึ่งแท่ง เนื่องจาก ไม่ขึ้นกับผลตอบแทนในอดีต จึงไม่มี feedback และไม่มีอะไร explode พารามิเตอร์ คือ dial ที่กำหนดว่ามี edge จริงมากแค่ไหน:
- — null: ไม่มี edge เลยแม้แต่น้อย backtest Sharpe ที่เป็นบวกใด ๆ ล้วนเป็น artifact 100%
- — edge จริงที่เทรดได้: กฎ momentum แบบซื่อสัตย์ทำกำไรได้จริง
กลยุทธ์นี้เรียบง่ายโดยตั้งใจ — กฎ momentum แบบ sign ฟีเจอร์คือผลรวมของผลตอบแทนย้อนหลัง แท่ง ( แท่ง) และ position คือเครื่องหมาย (sign) ของมัน:
csum = np.concatenate(([0.0], np.cumsum(r))) # csum[k] = sum r[0..k-1]
mom = np.full(n, np.nan)
tt = np.arange(L - 1, n)
mom[tt] = csum[tt + 1] - csum[tt - L + 1]
signal = np.sign(mom) # position for the next bar
ฟีเจอร์ momentum นี้เป็นเครื่องมือที่สมบูรณ์แบบสำหรับศึกษาการรั่วไหลแบบ same-bar เพราะมันมีคุณสมบัติที่ indicator จริงหลายตัวมีเหมือนกัน: มันมีแท่งปัจจุบันปนอยู่โดยกลไก mom[t] มี r[t] รวมอยู่ด้วย ดังนั้นถ้าคุณบันทึก r[t] เป็นการเทรดของคุณ คุณก็กำลังเดิมพันบางส่วนกับปริมาณที่ อยู่ในสัญญาณของคุณเองอยู่แล้ว นั่นคือการรั่วไหล ในรูปแบบที่จับต้องได้
การตั้งค่า: (volatility ต่อแท่ง 1%), ค่าธรรมเนียมทางเดียว 0.00045 (round-trip 0.09% ตรงกับเอนจิ้นของเรา), Sharpe ปรับเป็นรายปีด้วย (แท่งรายชั่วโมง), ประวัติศาสตร์อิสระ 4,000 ชุด ชุดละ 4,000 แท่ง ทุกอย่างถูก seed และเป็น deterministic
Pipeline ที่ซื่อสัตย์ (ตัวเดียวที่เทรดได้จริง)
ตัดสินใจที่ close ของแท่ง t รับผลตอบแทนของแท่ง ถัดไป และจ่ายค่าธรรมเนียมเมื่อ position เปลี่ยน:
def sharpe(sig, ret_booked):
dpos = np.abs(np.diff(np.concatenate(([0.0], sig))))
pnl = sig * ret_booked - FEE_ONEWAY * dpos
return pnl.mean() / pnl.std() * np.sqrt(8760)
honest = sharpe(signal[idx], r[idx + 1]) # earn r[t+1]: tradable
การรั่วไหลสามแบบ แต่ละแบบคือการเปลี่ยนแปลงเล็ก ๆ ที่แม่นยำ
same_bar = sharpe(signal[idx], r[idx])
z_full = (mom - mom[valid].mean()) / mom[valid].std()
norm_full = sharpe(np.sign(z_full[idx]), r[idx + 1])
z_sm = (mom[:-2] + mom[1:-1] + mom[2:]) / 3.0 # uses t-1, t, t+1
indicator = sharpe(np.sign(z_sm[idx]), r[idx + 1])
การรั่วไหลแต่ละแบบห่างจาก pipeline ที่ซื่อสัตย์แค่บรรทัดเดียว นั่นคือประเด็นทั้งหมด: มันไม่ใช่ความผิดพลาดแปลกประหลาด แต่เป็นสิ่งที่ผ่าน review ไปได้
ผลลัพธ์: ขนาดของการรั่วไหลแต่ละแบบ

จากการรันครอบคลุม 4,000 seed นี่คือ Sharpe ต่อปีที่แต่ละ pipeline รายงาน ทั้งภายใต้ null (ไม่มี edge) และภายใต้ edge จริง ( ปรับให้ Sharpe ที่ซื่อสัตย์อยู่ที่ +1.57 ซึ่งน่าเชื่อถือ):
| Pipeline | Null (ไม่มี edge) | Edge จริง |
|---|---|---|
| Honest (ความจริงแท้) | −0.74 | +1.57 |
| Same-bar fill | +14.79 | +15.85 |
| Indicator peek (1 แท่ง) | +4.76 | +6.62 |
| Whole-series normalization | −0.84 | +1.46 |
ช่วงความเชื่อมั่น 95% ข้าม seed อยู่ที่ ±0.05 หรือแคบกว่านั้นในทุกช่อง; paired t-test บนค่าการเพิ่มขึ้นมีนัยสำคัญอย่างมหาศาลในจุดที่ effect เป็นจริง (t > 400, p ≈ 0)
อ่าน คอลัมน์ null ก่อน เพราะมันคือการทดลองที่สะอาดที่สุดเท่าที่จะเป็นไปได้: ไม่มี edge เลย ดังนั้น pipeline ที่ซื่อสัตย์จึงขาดทุนอย่างถูกต้อง (−0.74 คือแรงฉุดจากการจ่ายค่าธรรมเนียมเพื่อเทรด noise) ทีนี้มาดูว่าการรั่วไหลทำอะไรกับความว่างเปล่าเดียวกันนี้:
- Same-bar fill: −0.74 → +14.79 กลยุทธ์ที่ไม่มีพลังพยากรณ์เลยแม้แต่น้อย เทรด noise แบบสุ่ม กลับรายงาน Sharpe ต่อปีเกือบ 15 นี่ไม่ใช่ bias ที่แนบเนียน แต่คือการปั้นแต่งขึ้นมา กลไกก็คือสิ่งที่เราสร้างไว้ตั้งแต่แรก: ฟีเจอร์ momentum มี
r[t]ปนอยู่ ดังนั้นการบันทึกr[t]จึงเท่ากับการเดิมพันกับสัญญาณของตัวเอง - Indicator peek: −0.74 → +4.76 การปล่อยให้ smoother มองเห็นล่วงหน้าไปหนึ่งแท่งสร้าง Sharpe ใกล้ 5 ขึ้นมาจาก noise เพราะค่าที่ผ่านการ smooth แล้วที่
tตอนนี้มีความสัมพันธ์กับr[t+1]ที่คุณกำลังจะได้รับ - Whole-series normalization: −0.74 → −0.84 แทบไม่มีการเพิ่มขึ้นเลย นี่คือข้อค้นพบที่ซื่อสัตย์และไม่คาดคิด (รายละเอียดเพิ่มเติมด้านล่าง)
คอลัมน์ edge ส่งสารที่อันตรายกว่านั้น เมื่อมี edge จริงอยู่ (honest +1.57) การรั่วไหลไม่ได้แค่บวกค่าคงที่เข้าไปเท่านั้น — มันดัน Sharpe ที่วัดได้ ขึ้นไปถึง +15.85 และ +6.62 ซึ่งสูงกว่า +1.57 ที่คุณเทรดได้จริงมาก ดังนั้นตัวเลขที่วัดได้ แยกความสามารถออกจากการรั่วไหลไม่ได้ ตัวเลข +6 ที่มาจากการรั่วไหลกับ +6 ที่ซื่อสัตย์ดูเหมือนกันทุกประการบนรายงาน คุณจะรู้ว่าได้ตัวไหนมาก็ต่อเมื่อคุณ deploy เงินทุนไปแล้วเท่านั้น
การรั่วไหลคือ gradient ไม่ใช่สวิตช์

ข้อโต้แย้งที่เป็นธรรมชาติ: "การบันทึกแท่งสัญญาณ ทั้งแท่ง เป็นความผิดพลาดสุดโต่งที่ไม่สมจริง" ดังนั้นเราจึงกวาด dose — สัดส่วน ของแท่งสัญญาณที่ถูกจับโดยการรั่วไหล ตั้งแต่ 0 (ซื่อสัตย์) จนถึง 1 (same-bar เต็มรูปแบบ):
| สัดส่วนที่จับได้ | Sharpe (Null) | Sharpe (Edge) |
|---|---|---|
| 0.00 (ซื่อสัตย์) | −0.74 | +1.57 |
| 0.25 | +3.90 | +6.41 |
| 0.50 | +9.86 | +12.20 |
| 1.00 (รั่วไหลเต็มรูปแบบ) | +14.79 | +15.85 |
การจับเพียง หนึ่งในสี่ ของแท่งสัญญาณก็พาให้กลยุทธ์ที่ไม่มี edge เปลี่ยนจาก −0.74 ไปเป็น +3.90 ได้แล้ว คุณไม่จำเป็นต้องมี off-by-one เต็มรูปแบบเพื่อถูกหลอก แค่ fill ที่เอื้อประโยชน์เกินไปเล็กน้อย — slippage ที่มองโลกในแง่ดีนิดหน่อยบนแท่งสัญญาณ, stop ภายในแท่งที่ตรวจสอบกับแท่งที่กระตุ้นมันเอง — ก็เพียงพอที่จะผ่านเกณฑ์ "deployable" ส่วนใหญ่ไปได้ การเพิ่มขึ้นนี้ราบเรียบและเป็น monotone ตามสัดส่วนของปัจจุบันที่คุณปล่อยให้ตัวเองเทรด
สิ่งนี้ทำให้กลยุทธ์ที่ขาดทุนเข้าสู่ production บ่อยแค่ไหน?
ตัวเลขที่ควรทำให้ practitioner กังวลคือ false-deployment rate: การรั่วไหลทำให้ configuration ที่ขาดทุนจริง ๆ ผ่านเกณฑ์ที่คุณจะใช้ไฟเขียวให้มันบ่อยแค่ไหน โดยใช้ "Sharpe ต่อปี ≥ 1.0" เป็นเกณฑ์การ deploy ภายใต้ null:
- Same-bar fill: 68% ของกลยุทธ์ที่ไม่มี edge ดูเหมือน deploy ได้ และ ขาดทุนจริง สอง configuration จากสามที่เป็น pure noise ล้วน ๆ จะผ่าน gate ที่ Sharpe-≥-1 ได้และขาดทุนเมื่อเทรดจริง (rate นี้ถูกนิยามอย่างชัดเจนในที่นี้เพราะการรั่วไหลอยู่ที่ execution ล้วน ๆ — คู่เทียบที่ซื่อสัตย์คือสัญญาณเดียวกันที่มี fill ซื่อสัตย์)
- Indicator peek: มันดันแทบทุก configuration ที่ไม่มี edge ให้ผ่านเกณฑ์ deploy ด้วยเช่นกัน (99.9% ผ่าน Sharpe ≥ 1) — มันจะโบกมือให้ noise เข้าสู่ production ได้โดยตรง
- Whole-series normalization: 12% ผ่านเกณฑ์ — ซึ่งก็คือ base rate ของ noise เอง แทบไม่มี premium จากการรั่วไหลเพิ่มขึ้นมาเลย
อนุกรมวิธาน และวิธีตรวจจับแต่ละแบบ
การรั่วไหลทั้งสามแบบไม่ได้อันตรายเท่ากัน และความแตกต่างนี้ก็ให้บทเรียนที่ดี
1. Execution leakage (ตัวที่แพงที่สุด)

อาการ: ราคา fill มีความสัมพันธ์กับสัญญาณเพราะมันมาจากแท่งเดียวกัน ขนาด: มหาศาล (+15 จาก noise ที่ dose เต็ม, +3.9 ที่ dose หนึ่งในสี่) ทำไมมันแย่ที่สุด: สัญญาณของคุณแทบจะโดยนิยามแล้วถูกสร้างจากการเคลื่อนไหวราคาล่าสุด ดังนั้นผลตอบแทนของแท่งสัญญาณจึงเป็นสิ่งที่ feature ของคุณมีความสัมพันธ์ด้วยมากที่สุดพอดี การบันทึกมันก็เหมือนกับการแอบดูเฉลย
การตรวจจับ — one-bar shift test นี่คือการวินิจฉัยที่มีค่าที่สุดเพียงหนึ่งเดียวในบทความนี้ นำ backtest ของคุณมา shift ทุก fill ให้ช้าลงหนึ่งแท่ง (ตัดสินใจที่ i, fill ที่ open[i+1]) ถ้าผลลัพธ์แทบไม่ขยับ แสดงว่า execution ของคุณซื่อสัตย์ ถ้าผลลัพธ์ ล่มหรือพลิกเครื่องหมาย แสดงว่าคุณกำลังเทรดในอดีต นี่คือสิ่งที่เกิดขึ้นกับการค้นหาแบบ Sobol ของเราพอดี: shift fill แล้ว OOS ที่ "ทำกำไร" กลับกลายเป็นขาดทุน — หรือพูดให้ถูกต้องกว่านั้นคือ ความสัมพันธ์ ที่แท้จริง ปรากฏขึ้นเมื่อการรั่วไหลถูกกำจัดออกไป
entry_price = open_[i + 1] # NOT close[i], NOT open[i]
2. Indicator / feature leakage (ตัวที่เงียบที่สุด)
อาการ: indicator ที่แท่ง i ขึ้นอยู่กับข้อมูลจาก i+1 หรือหลังจากนั้น — moving average แบบ centered, filter ที่ไม่มี causal delay, label แบบ peak/trough ที่ต้องใช้แท่งในอนาคตมายืนยัน, transform สไตล์ Heikin-Ashi ที่ป้อนแท่งเทียนในอนาคตเข้าไป ขนาด: ใหญ่ (+4.8 จาก noise) ทำไมมันซ่อนตัวได้: การรั่วไหลถูกฝังอยู่ในการเรียกไลบรารี scipy.signal.filtfilt เป็น zero-phase — และ zero-phase หมายถึง non-causal feature แบบ "แท่งนี้คือ local maximum" ไม่มีทางรู้ได้จนกว่าแท่งถัดไปจะปรากฏขึ้น
การตรวจจับ: สำหรับ indicator ทุกตัว ให้ถามว่า index สูงสุดที่มันอ่านคืออะไร? ถ้าการคำนวณค่าที่ t แตะ t+1 เมื่อไหร่ก็ตาม นั่นคือ non-causal คำนวณ indicator บน window แบบ expanding/rolling ที่เป็น causal แล้วตรวจสอบว่าค่าที่แท่ง t เหมือนกันไม่ว่าจะมีแท่งหลัง t อยู่ใน array หรือไม่ (การ implement HMA/ADX ของเราผ่านการทดสอบนี้: ทุก output ที่ t อ่านเฉพาะ input ที่ ≤ t เท่านั้น)
3. Normalization leakage (ตัวที่ขึ้นอยู่กับ channel)
อาการ: scaler (StandardScaler, min-max, global z-score) ถูก fit บน dataset ทั้งหมด รวมถึง test set ด้วย คำเตือนหลักในวรรณกรรม ML พูดถึงเรื่องนี้อย่างชัดเจน — Elements of Statistical Learning §7.10.2 ของ Hastie, Tibshirani & Friedman ("the wrong and right way to do cross-validation") และ common-pitfalls guide ของ scikit-learn เอง: "ค่าเฉลี่ยควรเป็นค่าเฉลี่ยของ train subset ไม่ใช่ค่าเฉลี่ยของข้อมูลทั้งหมด"
ขนาดในการทดสอบของเรา: ≈ ศูนย์ (−0.74 → −0.84) นี่คือผลลัพธ์ที่น่าประหลาดใจและซื่อสัตย์ และคุ้มค่าที่จะทำความเข้าใจมากกว่าท่องจำ
ทำไมมันถึงไม่เพิ่มค่า? เพราะกลยุทธ์ของเราใช้ feature ผ่าน sign เท่านั้น (threshold ที่ศูนย์) การ scale ด้วย standard deviation ไม่เคยเปลี่ยน sign และการ center ด้วยค่าเฉลี่ยทั้งซีรีส์ก็แค่ขยับจุดตัดศูนย์เล็กน้อยเท่านั้น ดังนั้นการทำ standardization ทั้งซีรีส์บนกฎแบบ sign ล้วน ๆ จึงแทบไม่เป็นอันตรายเลย
อย่าสรุปเกินไปจากตรงนี้ Normalization leakage นั้นขึ้นอยู่กับ channel ทันทีที่กลยุทธ์ของคุณใช้ ขนาด (magnitude) ของ feature — position sizing ที่เป็นสัดส่วนกับ z-score, entry threshold ที่ไม่ใช่ศูนย์ซึ่งเลือกจากการดู distribution ที่ถูก scale แล้ว, neural net ที่รับ input แบบ standardized — scaler ที่รู้อนาคตจะเริ่มมีผลกระทบ และยิ่งมีผลมากขึ้นเมื่อสถิติทั้งซีรีส์ต่างจากสถิติแบบ causal มากขึ้น ผลลัพธ์ของเราไม่ได้แปลว่า "normalization leakage ปลอดภัย" แต่หมายความว่า "ขนาดของ leakage ขึ้นอยู่กับ channel ที่ปริมาณที่รั่วไหลเข้าสู่การตัดสินใจ และคุณควรวัดมันแทนที่จะสันนิษฐานเอาเอง" กฎแบบ sign เป็นกรณีเดียวที่การรั่วไหลแบบนี้มีต้นทุนต่ำ
จุดเชื่อมโยงของบทความนี้
Look-ahead bias คือจุดเชื่อมแรกในห่วงโซ่ที่ซีรีส์นี้กำลังบันทึกไว้:
- มันทำให้ input ของการ validation เสียหาย backtest ที่รั่วไหลจะแล่นผ่าน walk-forward split ไปได้อย่างสบาย ๆ และดูเหมือน plateau กว้าง ๆ แทนที่จะเป็นยอดแหลม overfit — เพราะการรั่วไหลสม่ำเสมอในทุก fold cross-validation จึงจับมันไม่ได้ Leakage คือ failure mode ที่อยู่ ต้นน้ำ ของ overfitting และไม่ว่า validation ปลายน้ำจะซื่อสัตย์แค่ไหนก็ช่วยคุณไม่ได้
- มันมีปฏิสัมพันธ์กับ parameter search: การค้นหานับพัน trial บนข้อมูลที่รั่วไหลจะพบ configuration ที่ขูดรีดการรั่วไหลนั้นได้มากที่สุด "ผู้ชนะ" ก็คือตัวการที่เลวร้ายที่สุด
- มันคือเหตุผลที่ backtest-live parity ไม่ตรงกัน การรั่วไหลคือคำอธิบายที่ชัดเจนที่สุดสำหรับช่องว่าง 30–50% ระหว่าง backtest กับบอท เพราะการเทรดจริงคือที่เดียวโดยกลไกที่คุณแอบดูไม่ได้
วินัยที่จับความผิดพลาดทั้งหมดนี้ได้คือวินัยเดียวกับที่วรรณกรรมวิชาการเรียกร้องมาหลายปี: ปฏิบัติต่อ backtest เหมือนการทดลองทางสถิติที่มีขอบเขตข้อมูลอย่างเคร่งครัด Bailey, Borwein, López de Prado & Zhu แสดงให้เห็นว่า overfitting สร้างผลงานปลอมได้ง่ายแค่ไหน (2014); backtesting protocol ของ Arnott, Harvey & Markowitz (2019) กำหนดสุขอนามัยนี้เป็นระเบียบ Look-ahead bias คือขอบเขตพื้นฐานที่สุดในบรรดาทั้งหมด — ขอบเขตใน เวลา — และเป็นสิ่งที่ละเมิดโดยไม่ตั้งใจได้ง่ายที่สุด
สรุปประเด็นสำคัญ

- Look-ahead bias มีขนาดใหญ่มากเชิงปริมาณแต่มองไม่เห็นเชิงคุณภาพ ความผิดพลาดของ execution แค่หนึ่งแท่งเปลี่ยน Sharpe จาก −0.74 (noise ล้วน ๆ ที่ขาดทุนอย่างถูกต้อง) ไปเป็น +14.79 ความผิดพลาดคือหนึ่งบรรทัด แต่ผลลัพธ์คือ track record ที่ถูกปั้นแต่งขึ้นมา
- มันคือ gradient การจับแม้แต่ 25% ของแท่งสัญญาณก็ให้ +3.90 จากความว่างเปล่า คุณไม่จำเป็นต้องมีบั๊กที่โจ่งแจ้ง — แค่มองโลกในแง่ดีกับ fill ของคุณเกินไปนิดหน่อยก็เพียงพอแล้ว
- ตัวเลขที่วัดได้แยกความสามารถออกจากการรั่วไหลไม่ได้ เมื่อมี edge จริงอยู่ การรั่วไหลจะเพิ่มค่าในรายงานไปไกลเกินความจริงที่เทรดได้ การป้องกันเดียวคือ กระบวนการ ไม่ใช่ metric
- One-bar shift test คือการวินิจฉัยที่เร็วที่สุดของคุณ ขยับทุก fill ให้ช้าลงหนึ่งแท่ง ถ้าประสิทธิภาพล่ม แสดงว่าคุณกำลังเทรดในอดีต
- ขนาดของ leakage ขึ้นอยู่กับ channel Execution และ indicator peek นั้นร้ายแรงมาก ส่วน whole-series normalization บนกฎแบบ sign แทบไม่มีต้นทุนเลย วัดการรั่วไหลผ่าน channel ที่มันเข้ามาจริง ๆ — อย่าสันนิษฐานเอาเอง
การศึกษาแบบควบคุมฉบับเต็ม — การรั่วไหลทั้งสามแบบ, dose sweep, การวิเคราะห์ false-deployment, วิธีการอย่างเป็นทางการ และตัวเลขทุกตัวที่ reproduce ได้จาก script แบบ deterministic เพียงตัวเดียว — อยู่ในเปเปอร์คู่กันที่ lookahead.marketmaker.cc พร้อมโค้ดและข้อมูลที่ github.com/suenot/lookahead-inflation
กลยุทธ์ในการทดลอง null ของเราไม่มี edge เลยแม้แต่น้อย แต่มันก็ยังแสดง Sharpe 15 ออกมา ถ้า backtest ของคุณดูดีเกินไป สิ่งแรกที่ควรสงสัยไม่ใช่ความอัจฉริยะของคุณ — แต่คือนาฬิกาของคุณ
ผู้เขียน
Trading-systems engineer
Trading-systems engineer building bots since 2017: cross-exchange arbitrage (connected up to 30 venues), cointegration-based pairs arbitrage across spot and futures, scalping, news and sentiment-driven strategies, trend algorithms, and portfolio management and balancing algorithms. Also builds sub-millisecond order execution, big-data warehouses, backtesting engines, AI agents, and trading interfaces (incl. open-source profitmaker.cc). Stack: JS/TS, Python, Rust/Zig/Go, DevOps, backend, frontend, architecture.