Conformal Prediction สำหรับการกำหนดขนาดสถานะแบบตระหนักความเสี่ยง
ทุกสูตรการกำหนดขนาดสถานะ (position sizing) ต้องการการประมาณค่าความไม่แน่นอน เกณฑ์ Kelly ต้องการความน่าจะเป็นในการชนะและอัตราส่วนผลตอบแทน (ดู เกณฑ์ Kelly สำหรับกลยุทธ์) การหาค่าเหมาะสมแบบ mean-variance ต้องการเมทริกซ์ความแปรปรวนร่วม (covariance matrix) VaR ต้องการการแจกแจงของผลตอบแทน ทั้งหมดนี้ต้องการสมมติฐานเกี่ยวกับกระบวนการสร้างข้อมูล (data-generating process) ซึ่งเป็นสมมติฐานที่ตลาดการเงินมักจะละเมิดอยู่เสมอ
Conformal prediction นำเสนอสิ่งที่แตกต่างออกไป: ช่วงการพยากรณ์ที่มีการรับประกัน coverage แบบ finite-sample โดยไม่ต้องมีสมมติฐานเกี่ยวกับการแจกแจงแบบพารามิเตอร์ใด ๆ หากคุณขอ coverage 90% คุณก็จะได้ coverage อย่างน้อย 90% ไม่ว่าผลตอบแทนจะเป็นแบบ Gaussian, fat-tailed, เบ้ (skewed) หรือ heteroskedastic ก็ตาม ข้อกำหนดเพียงอย่างเดียวคือ exchangeability (หรือเงื่อนไขที่อ่อนกว่านั้น ดังที่เราจะได้เห็น)
โพสต์นี้ครอบคลุมทฤษฎี รูปแบบหลัก ๆ และการนำไปใช้งานจริงสำหรับการกำหนดขนาดสถานะใน Python
แนวคิดหลัก: Nonconformity Scores

Conformal prediction ทำงานโดยการวัดว่าการสังเกตใหม่นั้น "แปลก" เพียงใดเมื่อเทียบกับข้อมูลในอดีต ความแปลกนี้ถูกวัดปริมาณด้วย nonconformity score ซึ่งเป็นฟังก์ชันใด ๆ ที่วัดว่าจุดข้อมูลหนึ่งสอดคล้องกับรูปแบบที่เห็นในข้อมูลส่วนที่เหลือได้แย่เพียงใด
สำหรับการถดถอย (regression — การพยากรณ์ค่าต่อเนื่องอย่างเช่นผลตอบแทน) nonconformity score ที่ง่ายที่สุดคือ residual แบบสัมบูรณ์:
โดยที่ คือตัวพยากรณ์แบบจุด (point predictor) ใด ๆ ก็ได้ (linear regression, random forest, neural network — ไม่สำคัญ) และ คือจุดข้อมูลหนึ่ง
ข้อมูลเชิงลึกที่สำคัญ: หากจุดข้อมูล มีคุณสมบัติ exchangeable แล้ว อันดับ (rank) ของ ในบรรดา จะมีการแจกแจงแบบสม่ำเสมอ (uniform) บน นี่เป็นข้อเท็จจริงเชิงการจัดหมู่ (combinatorial) ล้วน ๆ — ไม่ต้องการสมมติฐานใด ๆ เกี่ยวกับการแจกแจงของ หรือ
จากความสม่ำเสมอของอันดับนี้ เราสามารถสร้างช่วงการพยากรณ์ที่มี coverage แบบ finite-sample ได้
Split Conformal Prediction

Split conformal prediction (Papadopoulos et al., 2002; Lei et al., 2018) เป็นรูปแบบที่ใช้งานได้จริงที่สุด อัลกอริทึมนั้นเรียบง่าย:
ขั้นที่ 1. แบ่งข้อมูลออกเป็น training set และ calibration set
ขั้นที่ 2. ฟิตโมเดลใด ๆ บน
ขั้นที่ 3. คำนวณ nonconformity scores บน calibration set:
ขั้นที่ 4. สำหรับระดับ miscoverage ที่ต้องการ ให้กำหนด เป็น empirical quantile ที่ ของ กล่าวอย่างเป็นรูปธรรม นี่คือ residual ที่เล็กเป็นอันดับที่ (และ เมื่อใดก็ตามที่ กล่าวคือสำหรับ ที่เล็กมาก)
ขั้นที่ 5. ช่วงการพยากรณ์สำหรับจุดใหม่ คือ:
การรับประกัน Coverage
ภายใต้ exchangeability ของ calibration data และจุดทดสอบใหม่:
นี่คือการรับประกันแบบ finite-sample — ไม่ใช่การประมาณค่าเชิง asymptotic มันเป็นจริงสำหรับโมเดล ใด ๆ การแจกแจงของข้อมูลแบบใดก็ได้ และขนาดตัวอย่าง เท่าใดก็ได้ หาก เป็นตัวพยากรณ์ที่ห่วยแตก ช่วงก็จะกว้างขึ้นเท่านั้น การรับประกัน coverage ยังคงเป็นจริงอยู่
นอกจากนี้ยังมีขอบเขตบน (upper bound) เมื่อ scores ไม่มีค่าซ้ำกัน (no ties): ดังนั้น coverage จึงไม่ได้อนุรักษนิยมจนสิ้นเปลือง
เหตุใดสิ่งนี้จึงสำคัญต่อการเทรด
ช่วงการพยากรณ์แบบดั้งเดิม เช่นจาก linear regression สมมติว่ามี error แบบ Gaussian ช่วงแบบ Gaussian ที่ปรับเทียบบนข้อมูลส่วนใหญ่อาจประเมินส่วนหาง (tails) ผิดอย่างร้ายแรงเมื่อ residual จริงเป็นแบบ heavy-tailed (เช่น Student- ที่มี degrees of freedom เพียงไม่กี่ค่า): มวลตรงกลางจะบางกว่าแบบ Gaussian ดังนั้นช่วง Gaussian ที่ตรงกับความแปรปรวน (variance-matched) จะ over-cover ใกล้จุดกึ่งกลางแต่ under-cover ในส่วนหาง ส่วนช่วงที่ฟิตกับส่วนหาง (tail-fit) ก็จะทำตรงข้าม ประเด็นไม่ได้อยู่ที่ตัวเลขมหัศจรรย์เพียงตัวเดียว — แต่อยู่ที่ว่า coverage ที่เกิดขึ้นจริงของช่วงแบบพารามิเตอร์นั้นขึ้นอยู่กับสมมติฐานเกี่ยวกับการแจกแจงที่คุณยังไม่ได้ตรวจสอบ
ช่วงการพยากรณ์แบบ conformal หลีกเลี่ยงปัญหานี้ได้ พวกมันขยายกว้างขึ้นโดยอัตโนมัติเมื่อโมเดลไม่แน่ใจ และรักษา marginal coverage ไว้ได้ไม่ว่าการแจกแจงของ error จริงจะเป็นอย่างไร สำหรับเทรดเดอร์ สิ่งนี้หมายความว่า:
- หากคุณกำหนดขนาดสถานะแบบแปรผกผันกับความกว้างของช่วง คุณก็จะ ลดความเสี่ยง (exposure) โดยอัตโนมัติ เมื่อโมเดลไม่แน่ใจ
- การรับประกัน coverage หมายความว่าการประมาณความเสี่ยงของคุณนั้น ซื่อสัตย์ — หากคุณบอกว่า "90% ของผลตอบแทนที่เกิดขึ้นจริงจะอยู่ภายในช่วงนี้" ข้อความนั้นจะถูกต้องในเชิงสถิติ (โดย marginal ภายใต้ exchangeability)
Full Conformal และ Jackknife+
Split conformal นั้นเรียบง่ายแต่สิ้นเปลืองข้อมูล: calibration set ไม่สามารถนำไปใช้ในการฝึก (training) ได้ มีสองทางเลือกที่จัดการเรื่องนี้
Full Conformal Prediction
Full conformal prediction (Vovk et al., 2005) ใช้ข้อมูลทั้งหมดทั้งในการฝึกและการปรับเทียบ สำหรับค่าผู้เข้าชิง (candidate value) แต่ละค่าของ :
- เพิ่มชุดข้อมูลด้วย
- ฟิตโมเดลใหม่บนชุดข้อมูลที่เพิ่มแล้ว
- คำนวณ nonconformity scores ทั้งหมด
- รวม เข้าในเซตการพยากรณ์ หาก score สำหรับ ไม่สุดโต่งเกินไป
เซตการพยากรณ์คือ:
โดยที่ คือ nonconformity scores ที่คำนวณด้วยชุดข้อมูลที่เพิ่มแล้ว
Full conformal ให้ช่วงที่แคบที่สุด แต่มีต้นทุนการคำนวณที่สูงเกินไปสำหรับโมเดลส่วนใหญ่ — คุณต้องฟิตโมเดลใหม่สำหรับทุกค่าผู้เข้าชิง บนตาราง (grid) สำหรับการพยากรณ์ผลตอบแทน นี่อาจหมายถึงการฟิตใหม่หลายพันครั้งต่อการพยากรณ์หนึ่งครั้ง
Jackknife+ (Barber et al., 2021)
Jackknife+ สร้างความสมดุล มันใช้ residual แบบ leave-one-out (LOO) แต่คำนึงถึงความแปรปรวนในโมเดลที่ฟิตได้ข้าม LOO folds ต่าง ๆ
ให้ แทนโมเดลที่ฝึกบนข้อมูลทั้งหมดยกเว้นจุด นิยาม LOO nonconformity score ด้วย residual แบบสัมบูรณ์ค่าเดียว:
จากนั้นช่วงการพยากรณ์แบบ jackknife+ จะถูกสร้างจากการพยากรณ์ LOO ที่จุดทดสอบ ขยายกว้างด้วย residual เหล่านี้:
ในที่นี้ แทนค่าที่เล็กเป็นอันดับที่ ของเซต และ แทนค่าที่เล็กเป็นอันดับที่ ขอบเขตล่าง (lower bound) ลบ residual ออกจากการพยากรณ์ LOO แต่ละค่า ส่วนขอบเขตบน (upper bound) บวก มันเข้าไป ความไม่สมมาตรนั้นคือหัวใจสำคัญทั้งหมด — การยุบขอบเขตทั้งสองให้เหลือ จะทำให้ขอบเขตล่างอยู่เหนือการพยากรณ์ ซึ่งเป็นสิ่งที่ผิด
Jackknife+ ให้การรับประกัน coverage อย่างน้อย (อ่อนกว่า ของ split conformal เล็กน้อย) แต่มันใช้ข้อมูลทั้งหมดทั้งในการฝึกและการปรับเทียบ ในทางปฏิบัติ coverage มักจะใกล้เคียง
สำหรับโมเดลการเทรดที่ฝึกบนข้อมูลจำกัด (เช่น โมเดลเฉพาะ regime ที่มีการสังเกตเพียงไม่กี่ร้อยตัว) jackknife+ มักเป็นตัวเลือกที่ดีที่สุด — มันไม่ต้องสละข้อมูลที่หายากไปเพื่อการปรับเทียบ ต้นทุนคือการฟิตโมเดลใหม่ ครั้ง
ปัญหากับอนุกรมเวลาทางการเงิน: Non-Exchangeability

การรับประกัน conformal มาตรฐานต้องการ exchangeability: การแจกแจงร่วม (joint distribution) ของ ไม่เปลี่ยนแปลงภายใต้การสับเปลี่ยน (permutations) สำหรับข้อมูลแบบ i.i.d. คุณสมบัตินี้เป็นจริงโดยปริยาย
อนุกรมเวลาทางการเงินไม่มีคุณสมบัติ exchangeable ผลตอบแทนแสดง:
- Volatility clustering: ช่วงที่ความผันผวนสูงตามมาด้วยช่วงที่ความผันผวนสูง (GARCH effects)
- Momentum และ mean reversion: สหสัมพันธ์ในตัวเอง (autocorrelation) ในผลตอบแทนหรือผลตอบแทนยกกำลังสอง
- การเปลี่ยน Regime: การแจกแจงเลื่อนไปตามเวลา (ตลาดขาขึ้น vs. ตลาดขาลง)
หากคุณใช้ split conformal กับอนุกรมเวลาอย่างไร้เดียงสา — โดยใช้การแบ่ง calibration แบบสุ่ม — คุณจะละเมิดโครงสร้างเชิงเวลา calibration scores จากปี 2017 ที่สงบจะไม่สะท้อนความไม่แน่นอนของปี 2020 ที่ผันผวน การรับประกัน coverage ของคุณจะพังทลาย
Adaptive Conformal Inference (ACI)

Gibbs และ Candes (2021, NeurIPS) ได้นำเสนอ Adaptive Conformal Inference (ACI) เพื่อจัดการกับ distribution shift และข้อมูลที่ไม่มีคุณสมบัติ exchangeable แนวคิดนี้สง่างาม: แทนที่จะใช้ระดับ coverage ที่ตายตัว ให้ปรับ ระดับ miscoverage เป้าหมาย แบบออนไลน์โดยอิงว่าช่วงล่าสุดครอบคลุมผลลัพธ์จริงหรือไม่ และคำนวณ quantile ใหม่จากการแจกแจงของ score ในแต่ละขั้น
อัลกอริทึม ACI
ACI ไม่ได้ ขยับความกว้างของช่วงโดยตรง มันรักษาพารามิเตอร์ miscoverage แบบปรับตัว และคำนวณ conformal quantile ใหม่จากค่านั้น ในแต่ละขั้นเวลา :
- คำนวณเกณฑ์ conformal เป็น empirical -quantile ของเซต residual ปัจจุบัน (calibration scores บวกกับ realized scores ที่มีจนถึงตอนนี้):
- สังเกต features สร้างช่วง
- สังเกตค่าจริง และคำนวณตัวบ่งชี้ error
- อัปเดต ระดับ (ไม่ใช่ความกว้าง):
โดยที่ คือขนาดก้าว (step size) และ คือ miscoverage เป้าหมาย หากช่วงพลาด () จะหดลง ซึ่งผลัก quantile ถัดไปให้สูงขึ้นและขยายช่วงให้กว้างขึ้น หากครอบคลุมได้ จะโตขึ้นและช่วงจะแคบลง สิ่งสำคัญคือ ในที่นี้อยู่ใน หน่วยความน่าจะเป็น — มันขยับระดับใน ไม่ใช่เกณฑ์ในหน่วยผลตอบแทนดิบ — ดังนั้น ค่าเดียวกันจึงทำงานได้อย่างสมเหตุสมผลไม่ว่า residual จะอยู่ในระดับ หรือไม่ก็ตาม
การรับประกัน Coverage สำหรับ ACI
ACI ให้การรับประกัน coverage ระยะยาวที่ไม่ขึ้นกับโมเดลการแจกแจง:
เนื่องจาก ถูก clip ไว้ที่ ตัวเศษจึงมีขอบเขตด้วย คูณค่าคงที่ ดังนั้นด้านขวามือจึงเป็น และความถี่ของ empirical miscoverage ลู่เข้าสู่ ข้อความที่ชัดเจน: ACI รับประกันว่าความถี่ของ empirical miscoverage ระยะยาวลู่เข้าสู่ สำหรับลำดับใด ๆ ก็ตาม (รวมถึงลำดับที่เป็นปฏิปักษ์ — adversarial) ตราบใดที่ระดับที่ปรับแล้วยังอยู่ในขอบเขต — ซึ่ง clip บังคับให้เป็นเช่นนั้น มันเป็นการรับประกันเกี่ยวกับ ความถี่ ของ coverage ไม่ใช่ ความให้สารสนเทศ (informativeness) ของช่วง: ภายใต้ลำดับที่เป็นปฏิปักษ์อย่างแท้จริง ช่วงอาจขยายกว้างจนไร้สารสนเทศในขณะที่ยังคงบรรลุเป้าหมาย coverage
Dynamically-Tuned ACI (DtACI)
Gibbs และ Candes (2024, JMLR) ได้ปรับปรุง ACI ด้วย การปรับจูนแบบไดนามิก (dynamic tuning) ของขนาดก้าว แทนที่จะตรึง ไว้ พวกเขารักษาเซตผู้เข้าชิง และรวมพวกมันผ่านกฎการรวมผู้เชี่ยวชาญ (expert-aggregation rule) โดยให้น้ำหนัก ที่ coverage ล่าสุดใกล้เคียงกับเป้าหมายมากที่สุด
สิ่งนี้แก้ปัญหาเชิงปฏิบัติ: ที่ใหญ่จะปรับตัวอย่างรวดเร็วต่อการเปลี่ยน regime แต่สร้างความกว้างของช่วงที่ผันผวน ส่วน ที่เล็กจะเสถียรแต่ปรับตัวช้า DtACI แลกเปลี่ยนสิ่งเหล่านี้โดยอัตโนมัติ
เหตุใดสิ่งนี้จึงสำคัญต่อการเทรด
ลองพิจารณากลยุทธ์ market-making ที่ใช้โมเดลพยากรณ์ผลตอบแทน ในช่วงตลาดสงบ ช่วง conformal จะแคบ — โมเดลมั่นใจ และคุณสามารถถือสถานะที่ใหญ่ขึ้นได้ เมื่อความผันผวนพุ่งขึ้น (ฤดูประกาศผลกำไร, การประกาศของ FOMC, ความตื่นตระหนกทางภูมิรัฐศาสตร์) ระดับ ACI จะปรับตัวและช่วงจะขยายกว้างขึ้นภายในไม่กี่ขั้นเวลา การกำหนดขนาดสถานะของคุณจะหดลงเพื่อตอบสนอง โดยไม่ต้องมี โมเดลความผันผวนหรือตรรกะการตรวจจับ regime อย่างชัดแจ้งเลย
นี่คือการวัดปริมาณความไม่แน่นอนในฐานะสัญญาณชั้นหนึ่ง (first-class signal) ไม่ใช่สิ่งที่คิดทีหลัง
การกำหนดขนาดสถานะด้วยช่วง Conformal

ตอนนี้เรามาเชื่อมโยง conformal prediction กับการกำหนดขนาดสถานะอย่างเป็นรูปธรรม ตัวแปรสำคัญคือ ครึ่งความกว้างของช่วงการพยากรณ์ (prediction interval half-width) เทียบกับกรณี residual สัมบูรณ์แบบสมมาตร เมื่อมีช่วงสมมาตร ความกว้างเต็มคือ เพื่อให้สูตรและโค้ดสอดคล้องกัน เราจะวัดทุกอย่างเทียบกับ ความกว้างเต็ม ตลอดทั้งบทความ
การกำหนดขนาดแบบ Inverse-Width
วิธีที่ง่ายที่สุด: กำหนดขนาดแบบแปรผกผันกับความกว้างของช่วง
โดยที่ คือค่าคงที่ปรับสเกล (scaling constant) ที่ปรับเทียบให้เข้ากับงบความเสี่ยง (risk budget) ของคุณ เมื่อโมเดลมั่นใจ (ช่วงแคบ) คุณถือสถานะที่ใหญ่ขึ้น เมื่อไม่แน่ใจ (ช่วงกว้าง) คุณถือสถานะที่เล็กลง
นี่คล้ายกับ volatility targeting () แต่มีความแตกต่างที่สำคัญ: ความกว้างของช่วง conformal เป็นมาตรวัดความไม่แน่นอนแบบ distribution-free ไม่ใช่การประมาณความผันผวนแบบพารามิเตอร์ มันจับความไม่แน่นอนเชิงพยากรณ์ภายใต้การรับประกัน coverage ไม่ใช่เพียงแค่ความแปรปรวนของผลตอบแทน
การกำหนดขนาดแบบ Edge-Ratio และตัวกรอง No-Trade
การกำหนดขนาดแบบ inverse-width ล้วน ๆ ละเลยความแรงของสัญญาณเอง การปรับแต่งที่เป็นธรรมชาติคือการปรับสเกลด้วย edge ratio — การพยากรณ์แบบจุดเทียบกับความกว้างของช่วง:
นี่คืออะนาล็อกแบบ conformal ของอัตราส่วนสัญญาณต่อสัญญาณรบกวน (signal-to-noise ratio): ผลตอบแทนคาดหวังหารด้วยมาตรวัดความไม่แน่นอนแบบ distribution-free เราใช้มันทั้งในการกำหนดขนาดและในตัวกรอง no-trade
ตัวกรองนี้มีหลักการ หากช่วงคร่อมศูนย์ (straddles zero)
แล้วช่วงการพยากรณ์ จะรวมทั้งผลตอบแทนบวกและลบ — ผลตอบแทนที่เกิดขึ้นจริงสามารถมีเครื่องหมายตรงข้ามกับการพยากรณ์ของคุณได้อย่างน่าเชื่อ นิยาม เกณฑ์ edge ขั้นต่ำ (minimum edge threshold) และเทรดเฉพาะเมื่อ ข้อสังเกต: เนื้อหาเชิงเรขาคณิตของ " ใหญ่พอที่ช่วงจะไม่คร่อมศูนย์อีกต่อไป" คือ พอดี (เนื่องจากช่วงจะพ้นศูนย์เมื่อ ) เลือก บนสเกลจริงของ ผ่าน backtesting สำหรับ residual ของผลตอบแทนรายวัน มักจะต่ำกว่า มาก ดังนั้น ที่เล็กจิ๋วอาจยอมรับการเทรดเกือบทั้งหมด ส่วนค่าที่ใหญ่อาจไม่ยอมรับเลย ปรับเทียบมันให้เข้ากับข้อมูลของคุณ
เกี่ยวกับ "Conformal Kelly"
มันชวนให้อยากเอาช่วง conformal ไปติดเข้ากับ Kelly fraction แต่ เป็นเศษส่วนที่สมบูรณ์และมีขอบเขตอยู่แล้ว ซึ่งได้มาจากความน่าจะเป็นในการชนะ และอัตราส่วนผลตอบแทน การคูณมันด้วยอัตราส่วนที่ไม่มีขอบเขตอย่าง ไม่มีเหตุผลรองรับเชิงทฤษฎีการตัดสินใจ (decision-theoretic) เลย — มันสามารถเกิน 1 หรือพลิกเครื่องหมายได้อย่างเป็นอิสระจาก และมันนับ edge ที่ เข้ารหัสไว้แล้วซ้ำสองครั้ง ดังนั้นเราจึง ไม่ นำเสนอตัวคูณ "conformal Kelly"
หากคุณต้องการขับเคลื่อน Kelly จากช่วง คุณต้องหา และ จากมันจริง ๆ ซึ่งต้องการสมมติฐานที่ชัดเจนเกี่ยวกับการแจกแจง ภายใน ช่วง (ช่วง conformal จงใจไม่บอกอะไรเกี่ยวกับสิ่งนั้น — ดู Limitations) ตัวอย่างเช่น ภายใต้รูปร่างภายในช่วงที่สมมติไว้ คุณสามารถประมาณ และอัตราส่วนผลตอบแทนจากเรขาคณิตของช่วงได้ — แต่ต้องประกาศสมมติฐานนั้นออกมาอย่างดังฟังชัด เพราะมันนำเอาความผูกพันแบบพารามิเตอร์ที่ conformal prediction ตั้งใจจะหลีกเลี่ยงกลับเข้ามาพอดี
ทางเลือกที่ซื่อสัตย์และพึ่งพาสมมติฐานน้อยคือการใช้ edge ratio เป็น fractional-Kelly shrinkage: เพิ่มขนาดเมื่อผลตอบแทนคาดหวังใหญ่เมื่อเทียบกับช่วง ลดขนาดเมื่อมันเล็ก และใช้สิ่งนี้บนเพดานแข็ง (hard cap) — อย่างชัดเจนในฐานะ heuristic ไม่ใช่ในฐานะ "Kelly fraction"
การนำไปใช้งานใน Python
นี่คือการนำไปใช้งานจริง เราแสดงทั้งเส้นทาง split/prefit และเส้นทางเชิงเวลา (EnbPI) เนื่องจากประเด็นทั้งหมดของโพสต์นี้คือข้อมูลทางการเงินไม่มีคุณสมบัติ exchangeable
การตั้งค่าและการเตรียมข้อมูล
import numpy as np
import pandas as pd
from sklearn.ensemble import GradientBoostingRegressor
from sklearn.model_selection import KFold
from mapie.regression import MapieRegressor, MapieTimeSeriesRegressor
from mapie.subsample import BlockBootstrap
def prepare_features(prices: pd.Series, lookback: int = 20) -> pd.DataFrame:
"""Create features from a price series."""
df = pd.DataFrame()
returns = prices.pct_change()
for lag in range(1, lookback + 1):
df[f"ret_lag_{lag}"] = returns.shift(lag)
for window in [5, 10, 20]:
df[f"ret_mean_{window}"] = returns.rolling(window).mean().shift(1)
df[f"ret_std_{window}"] = returns.rolling(window).std().shift(1)
df[f"ret_skew_{window}"] = returns.rolling(window).skew().shift(1)
df["target"] = returns.shift(-1)
return df.dropna()
Split Conformal ด้วย MAPIE (Prefit)
สำหรับ split/prefit conformal นั้น cv="prefit" ต้องการ method="base" (ตัวประมาณ split-conformal แบบ naive) ตัวเลือก method="plus" คือตัวประมาณ CV+/jackknife+ และไม่เข้ากันกับ cv="prefit" — มันต้องการอ็อบเจกต์ cross-validation แทน เราใช้การจับคู่ที่ถูกต้องในที่นี้ และทำให้การกำหนดขนาดเป็นมาตรฐานบนความกว้างเต็ม
def split_conformal_sizing(
prices: pd.Series,
alpha: float = 0.1,
k: float = 1e-3, # scaling constant, in width units
max_position: float = 1.0,
min_edge: float = 0.05, # threshold on |pred| / width
) -> pd.DataFrame:
"""
Position sizing using split (prefit) conformal prediction intervals.
Sizing rule (consistent with the prose):
edge_t = |pred_t| / width_t
size_t = clip(k / width_t, 0, max_position) # inverse-width
size_t = 0 if edge_t < min_edge # no-trade filter
"""
df = prepare_features(prices)
X = df.drop(columns=["target"])
y = df["target"]
n_train = int(len(X) * 0.6)
n_cal = int(len(X) * 0.2)
X_train, y_train = X.iloc[:n_train], y.iloc[:n_train]
X_cal, y_cal = X.iloc[n_train:n_train + n_cal], y.iloc[n_train:n_train + n_cal]
X_test, y_test = X.iloc[n_train + n_cal:], y.iloc[n_train + n_cal:]
base_model = GradientBoostingRegressor(
n_estimators=200, max_depth=3, learning_rate=0.05, random_state=42,
)
base_model.fit(X_train, y_train)
mapie = MapieRegressor(estimator=base_model, cv="prefit", method="base")
mapie.fit(X_cal, y_cal)
y_pred, y_intervals = mapie.predict(X_test, alpha=alpha)
lower = y_intervals[:, 0, 0]
upper = y_intervals[:, 1, 0]
width = upper - lower
raw_size = k / np.where(width > 0, width, np.inf)
position_size = np.clip(raw_size, 0.0, max_position)
edge_ratio = np.abs(y_pred) / np.where(width > 0, width, np.inf)
position_size = np.where(edge_ratio < min_edge, 0.0, position_size)
position_size = position_size * np.sign(y_pred)
return pd.DataFrame({
"prediction": y_pred,
"lower": lower,
"upper": upper,
"width": width,
"edge_ratio": edge_ratio,
"position_size": position_size,
"actual": y_test.values,
}, index=X_test.index)
Time-Series Conformal ด้วย EnbPI
เนื่องจากผลตอบแทนไม่มีคุณสมบัติ exchangeable การแบ่งแบบ random/prefit ข้างต้นจึงเป็นเพียงเส้นฐาน (baseline) เท่านั้น MapieTimeSeriesRegressor ของ MAPIE ที่ใช้ method="enbpi" (Xu & Xie, 2021) ใช้ block bootstrap และการอัปเดต residual ที่ออกแบบมาสำหรับความขึ้นต่อกันเชิงเวลา (temporal dependence) นี่คือเครื่องมือที่เข้ากันได้กับข้อโต้แย้งของโพสต์นี้เอง
def enbpi_sizing(
prices: pd.Series,
alpha: float = 0.1,
k: float = 1e-3,
max_position: float = 1.0,
min_edge: float = 0.05,
) -> pd.DataFrame:
"""Position sizing with EnbPI (block-bootstrap, time-series conformal)."""
df = prepare_features(prices)
X = df.drop(columns=["target"])
y = df["target"]
n_train = int(len(X) * 0.7)
X_train, y_train = X.iloc[:n_train], y.iloc[:n_train]
X_test, y_test = X.iloc[n_train:], y.iloc[n_train:]
base_model = GradientBoostingRegressor(
n_estimators=200, max_depth=3, learning_rate=0.05, random_state=42,
)
cv = BlockBootstrap(n_resamplings=30, length=20, overlapping=False, random_state=42)
mapie_ts = MapieTimeSeriesRegressor(base_model, method="enbpi", cv=cv, agg_function="mean")
mapie_ts.fit(X_train, y_train)
y_pred, y_intervals = mapie_ts.predict(X_test, alpha=alpha, ensemble=True)
lower = y_intervals[:, 0, 0]
upper = y_intervals[:, 1, 0]
width = upper - lower
raw_size = k / np.where(width > 0, width, np.inf)
position_size = np.clip(raw_size, 0.0, max_position)
edge_ratio = np.abs(y_pred) / np.where(width > 0, width, np.inf)
position_size = np.where(edge_ratio < min_edge, 0.0, position_size)
position_size = position_size * np.sign(y_pred)
return pd.DataFrame({
"prediction": y_pred, "lower": lower, "upper": upper,
"width": width, "edge_ratio": edge_ratio,
"position_size": position_size, "actual": y_test.values,
}, index=X_test.index)
Adaptive Conformal Inference (ออนไลน์)
สำหรับการเทรดสด เราจะนำ ACI จริงมาใช้งานจากศูนย์: รักษา ระดับ miscoverage อัปเดตมันแบบบวก (additively) และคำนวณ quantile ใหม่จากเซต residual ในแต่ละขั้น มีรายละเอียดแบบ finite-sample สองอย่างที่สำคัญ:
- ใช้ order statistic ไม่ใช่ quantile แบบประมาณค่าระหว่าง (interpolated)
np.quantileทำ interpolation โดยปริยาย ซึ่งอาจตกลงต่ำกว่าอันดับที่กำหนดเล็กน้อยและทำให้ undercover ให้ส่งmethod="higher"(เทียบเท่ากับ"inverted_cdf") - เมื่ออันดับที่กำหนดเกิน ( เล็ก, coverage เป้าหมายสูง) เกณฑ์ที่ถูกต้องคือ (ช่วง = ทั้งเส้นจำนวนจริง) ไม่ใช่การ clamp ไปที่ residual ที่ใหญ่ที่สุด การ clamp จะทำลายการรับประกัน อย่างเงียบ ๆ
class AdaptiveConformalSizer:
"""
Online position sizing with Adaptive Conformal Inference (Gibbs & Candes,
2021). Updates the miscoverage LEVEL alpha_t and re-derives the quantile
from the residual set each step -- gamma is in probability units.
"""
def __init__(self, base_model, alpha=0.1, gamma=0.02,
max_position=1.0, min_edge=0.05, k=1e-3):
self.base_model = base_model
self.alpha_target = alpha # target miscoverage
self.alpha_t = alpha # adaptive miscoverage level
self.gamma = gamma # step size, in [0, 1] units
self.max_position = max_position
self.min_edge = min_edge
self.k = k
self.residuals = []
self.q_hat = np.inf
self.coverage_history = []
@staticmethod
def _conformal_quantile(residuals, alpha_t):
"""(1 - alpha_t) conformal quantile via the order statistic."""
n = len(residuals)
if n == 0:
return np.inf
rank = int(np.ceil((1.0 - alpha_t) * (n + 1)))
if rank > n: # required order statistic does not exist
return np.inf # -> interval is the whole line
level = rank / n
return float(np.quantile(residuals, min(level, 1.0), method="higher"))
def calibrate(self, X_cal, y_cal):
preds = self.base_model.predict(X_cal)
self.residuals = list(np.abs(np.asarray(y_cal) - preds))
self.q_hat = self._conformal_quantile(self.residuals, self.alpha_t)
def predict_and_size(self, X_t) -> dict:
mu_hat = self.base_model.predict(np.asarray(X_t).reshape(1, -1))[0]
lower, upper = mu_hat - self.q_hat, mu_hat + self.q_hat
width = upper - lower # = 2 * q_hat
edge_ratio = abs(mu_hat) / width if np.isfinite(width) and width > 0 else 0.0
if edge_ratio < self.min_edge:
size = 0.0
else:
size = min(self.k / width, self.max_position) if width > 0 else 0.0
return {
"prediction": mu_hat, "lower": lower, "upper": upper,
"width": width, "edge_ratio": edge_ratio,
"position_size": size * np.sign(mu_hat),
"alpha_t": self.alpha_t, "q_hat": self.q_hat,
}
def update(self, X_t, y_t: float):
"""ACI update: adapt the LEVEL, then re-derive the quantile."""
mu_hat = self.base_model.predict(np.asarray(X_t).reshape(1, -1))[0]
residual = abs(y_t - mu_hat)
covered = int(residual <= self.q_hat)
err_t = 1 - covered
self.alpha_t = float(np.clip(
self.alpha_t + self.gamma * (self.alpha_target - err_t), 0.0, 1.0
))
self.residuals.append(residual)
self.q_hat = self._conformal_quantile(self.residuals, self.alpha_t)
self.coverage_history.append(covered)
@property
def running_coverage(self) -> float:
if not self.coverage_history:
return float("nan")
return float(np.mean(self.coverage_history))
นำมาประกอบกัน: ลูป Backtest
def backtest_aci_sizing(prices: pd.Series, alpha=0.1, gamma=0.02) -> pd.DataFrame:
"""Backtest position sizing with Adaptive Conformal Inference."""
df = prepare_features(prices)
X = df.drop(columns=["target"]).values
y = df["target"].values
index = df.index
n_train = int(len(X) * 0.5)
n_cal = int(len(X) * 0.2)
X_train, y_train = X[:n_train], y[:n_train]
X_cal, y_cal = X[n_train:n_train + n_cal], y[n_train:n_train + n_cal]
X_test, y_test = X[n_train + n_cal:], y[n_train + n_cal:]
test_index = index[n_train + n_cal:]
model = GradientBoostingRegressor(
n_estimators=200, max_depth=3, learning_rate=0.05, random_state=42,
)
model.fit(X_train, y_train)
sizer = AdaptiveConformalSizer(base_model=model, alpha=alpha, gamma=gamma)
sizer.calibrate(X_cal, y_cal)
records = []
for i in range(len(X_test)):
result = sizer.predict_and_size(X_test[i])
result["actual"] = y_test[i]
result["pnl"] = result["position_size"] * y_test[i]
records.append(result)
sizer.update(X_test[i], y_test[i]) # online residual + level update
results = pd.DataFrame(records, index=test_index)
results["cumulative_pnl"] = results["pnl"].cumsum()
results["running_coverage"] = (
((results["lower"] <= results["actual"]) &
(results["actual"] <= results["upper"])).expanding().mean()
)
return results
การประเมินผลลัพธ์
def evaluate(results: pd.DataFrame, alpha: float = 0.1):
"""Print evaluation metrics for conformal position sizing."""
covered = ((results["actual"] >= results["lower"]) &
(results["actual"] <= results["upper"]))
print(f"Target coverage: {1 - alpha:.1%}")
print(f"Empirical coverage: {covered.mean():.1%}")
print(f"Mean interval width: {results['width'].mean():.6f}")
print(f"Median position size: {results['position_size'].abs().median():.4f}")
print(f"Fraction no-trade: {(results['position_size'] == 0).mean():.1%}")
print(f"Total PnL (bps): {results['pnl'].sum() * 10000:.1f}")
sd = results['pnl'].std()
sharpe = results['pnl'].mean() / sd * np.sqrt(252) if sd > 0 else float('nan')
print(f"Sharpe (annualized): {sharpe:.2f}")
ข้อพิจารณาเชิงปฏิบัติ
การเลือก Nonconformity Score
Residual แบบสัมบูรณ์ เป็นค่าเริ่มต้น แต่มันสมมติว่าช่วงการพยากรณ์ควรสมมาตรรอบการพยากรณ์แบบจุด สำหรับผลตอบแทนทางการเงิน ช่วงแบบไม่สมมาตร (asymmetric intervals) มักสมเหตุสมผลกว่า:
- Conformalized Quantile Regression (CQR): ฟิตตัวถดถอย quantile ที่ระดับ และ แล้วทำ conformalize (Romano et al., 2019) ช่วงจะปรับรูปร่างให้เข้ากับการแจกแจงเฉพาะที่ — กว้างขึ้นทางด้านขาลง (downside) ในช่วง drawdown และกว้างขึ้นทางด้านขาขึ้น (upside) ในช่วง rally (เมื่อใช้ CQR ช่วงจะไม่สมมาตรอีกต่อไป ดังนั้น จึงเป็นความกว้างจริงแบบบนลบล่าง — ใช้ เป็นตัวส่วนทุกที่ต่อไป)
- Normalized scores: โดยที่ คือการประมาณความผันผวนเฉพาะที่ (local volatility) สิ่งนี้สร้างช่วงที่ปรับตัวแบบมีเงื่อนไข (conditionally adaptive) — แคบลงในช่วง regime ความผันผวนต่ำ และกว้างขึ้นในช่วงความผันผวนสูง — ในขณะที่ยังคงรักษา marginal coverage
ขนาดของ Calibration Set
การรับประกัน coverage ของ split conformal เป็นจริงสำหรับขนาด calibration set ใด ๆ แต่ความกว้างของช่วงจะลดลงเมื่อ เพิ่มขึ้น สำหรับ ที่เล็กมาก order statistic ที่กำหนดอาจไม่มีอยู่ ซึ่งในกรณีนี้เกณฑ์ที่ซื่อสัตย์คือ (ช่วงที่ไร้สารสนเทศแต่ถูกต้อง) — การ clamp ไปที่ residual ที่ใหญ่ที่สุดจะทำให้การรับประกันเป็นโมฆะอย่างเงียบ ๆ สำหรับการเทรดในทางปฏิบัติ:
- จุดปรับเทียบให้ช่วงที่แคบพอสมควร
- เป็นที่นิยมสำหรับการประมาณ quantile ที่เสถียร
- เมื่อใช้ ACI calibration set มีไว้สำหรับการกำหนดค่าเริ่มต้นเท่านั้น การอัปเดตระดับแบบออนไลน์จะจัดการส่วนที่เหลือ
ความถี่ในการเทรนใหม่
โมเดลฐาน อาจล้าสมัยได้ มีสองแนวทาง:
- เทรนใหม่เป็นระยะ (เช่น รายเดือน) และปรับเทียบ conformal quantile ใหม่
- ใช้ ACI และให้ระดับแบบปรับตัวชดเชยความล้าสมัยของโมเดล — ช่วงจะขยายกว้างขึ้นโดยอัตโนมัติเมื่อ residual ของโมเดลโตขึ้น
ทางเลือกที่ 2 นั้นง่ายกว่าและมีประสิทธิภาพอย่างน่าประหลาดใจ ชั้น conformal ทำหน้าที่เป็นตาข่ายนิรภัย (safety net): แม้ว่าโมเดลจะเสื่อมลง ความถี่ coverage ระยะยาวของ ACI ก็ยังคงลู่เข้าสู่เป้าหมาย
ต้นทุนการทำธุรกรรม
ช่วง conformal มีปฏิสัมพันธ์กับต้นทุนการทำธุรกรรม (transaction costs) ในแบบที่มีประโยชน์ เมื่อช่วงกว้าง (ความไม่แน่นอนสูง) สถานะจะเล็ก ดังนั้น turnover จึงต่ำ เมื่อช่วงแคบลง (โมเดลมั่นใจ) สถานะจะโต — แต่โมเดลมีแนวโน้มจะถูกมากกว่า ดังนั้น turnover จึงคุ้มค่าที่จะจ่าย
คุณยังสามารถรวมต้นทุนการทำธุรกรรมเข้าไปในตัวกรอง no-trade โดยตรงได้:
สิ่งนี้รับประกันว่าคุณเทรดเฉพาะเมื่อ edge สุทธิที่คาดหวังเกินเกณฑ์ที่ปรับสเกลด้วยความกว้าง conformal — โดยใช้ตัวส่วน เดียวกันกับทุกที่
การเปรียบเทียบกับวิธีดั้งเดิม
| คุณสมบัติ | Gaussian CI | Bootstrap CI | Conformal PI |
|---|---|---|---|
| สมมติฐานการแจกแจง | Normal errors | i.i.d. + asymptotic | ไม่มี (exchangeability) |
| การรับประกันแบบ finite-sample | ไม่ | ไม่ | ใช่ |
| ใช้ได้กับโมเดลใด ๆ | ไม่ | ใช่ | ใช่ |
| ปรับตัวต่อ heteroskedasticity | ไม่ | บางส่วน | ด้วย CQR / normalized scores |
| จัดการ distribution shift | ไม่ | ไม่ | รูปแบบ ACI / EnbPI |
| ต้นทุนการคำนวณ | ต่ำ | ปานกลาง | Split: ต่ำ; jackknife+: refits; full: สูงเกินไป |
Bootstrap เป็น "asymptotic" เฉพาะใน การรับประกัน ของมันเท่านั้น มันยังคง สมมติ ข้อมูลแบบ i.i.d./exchangeable และความเรียบ (smoothness) ดังนั้นช่องสมมติฐานการแจกแจงของมันจึงไม่ใช่ "ปลอดสมมติฐาน" และคอลัมน์ "conformal" เดียวซ่อนต้นทุนที่แตกต่างกันมากในแต่ละรูปแบบ ซึ่งแถวต้นทุนได้สะกดออกมาอย่างชัดเจนแล้ว
ข้อจำกัด
Conformal prediction ไม่ใช่เวทมนตร์ ข้อจำกัดสำคัญสำหรับการเทรด:
-
Marginal ไม่ใช่ conditional coverage การรับประกันคือ แบบ marginal — เฉลี่ยข้ามความสุ่มทั้งใน และ สำหรับค่าเงื่อนไขเฉพาะ coverage เฉพาะที่อาจสูงหรือต่ำกว่า Conformalized quantile regression แก้ปัญหานี้ได้บางส่วน
-
Exchangeability เป็นข้อกำหนดที่แท้จริง สำหรับ split conformal calibration data และจุดทดสอบต้องมีคุณสมบัติ exchangeable ข้อมูลทางการเงินไม่มี ACI และ EnbPI ผ่อนปรนสิ่งนี้ให้เป็นการรับประกันระยะยาว แต่ coverage ในระยะสั้นอาจเบี่ยงเบนได้
-
ความกว้างของช่วงไม่ใช่ความหนาแน่นของความน่าจะเป็น (probability density) ช่วง conformal บอกคุณว่า จะตกอยู่ที่ใดด้วยความน่าจะเป็น แต่มันไม่ได้บอกอะไรเกี่ยวกับการแจกแจง ภายใน ช่วงเลย มันไม่ใช่สิ่งทดแทนการแจกแจงเชิงพยากรณ์เต็มรูปแบบ — ซึ่งนั่นคือเหตุผลที่การหา และ ของ Kelly จากมันต้องการสมมติฐานเพิ่มเติมที่ชัดเจน
-
ขยะเข้า กว้างออก (Garbage in, wider out) โมเดลฐานที่แย่จะสร้างช่วงที่กว้าง Conformal prediction รับประกัน coverage แต่ไม่รับประกันความมีประโยชน์ หากโมเดลของคุณไม่มีพลังพยากรณ์ ช่วงจะกว้างเสียจนตัวกำหนดขนาดสถานะไม่เคยเทรดเลย
สรุป
Conformal prediction มอบกรอบงานที่เข้มงวดและ distribution-free สำหรับการวัดปริมาณความไม่แน่นอน ที่เข้ากันได้อย่างเป็นธรรมชาติกับการกำหนดขนาดสถานะ:
- Split conformal สำหรับการปรับเทียบแบบ static, offline ที่มี coverage แบบ finite-sample
- Jackknife+ เมื่อ calibration data หายากและคุณต้องการใช้การสังเกตทั้งหมด (โดยมีต้นทุนการฟิตใหม่ ครั้ง)
- Adaptive conformal inference / EnbPI สำหรับการเทรดออนไลน์กับตลาดที่ไม่นิ่ง (non-stationary)
- การกำหนดขนาดสถานะ ผ่านความกว้างช่วงแบบผกผัน (inverse interval width) และตัวกรอง no-trade แบบ edge-ratio — และหากคุณจะไปถึง Kelly ก็ต่อเมื่อหา และ มาอย่างซื่อสัตย์เท่านั้น ไม่ใช่ผ่านตัวคูณที่ไม่มีเหตุผลรองรับ
ข้อได้เปรียบสำคัญเหนือทางเลือกแบบพารามิเตอร์: คุณไม่จำเป็นต้องระบุหรือตรวจสอบสมมติฐานการแจกแจงแบบพารามิเตอร์เลย ช่วงต่าง ๆ ซื่อสัตย์โดยการสร้าง (by construction) (แบบ marginal ภายใต้ exchangeability) สำหรับเทรดเดอร์เชิงระบบ สิ่งนี้หมายถึงแหล่งความเสี่ยงของโมเดล (model risk) ที่ลดลงไปหนึ่งแหล่ง — และในธุรกิจที่ความเสี่ยงของโมเดลเป็นเรื่องคอขาดบาดตาย นั่นเป็นเรื่องสำคัญ
เอกสารอ้างอิง:
- Papadopoulos, H., Proedrou, K., Vovk, V., Gammerman, A. (2002). Inductive confidence machines for regression. ECML.
- Vovk, V., Gammerman, A., Shafer, G. (2005). Algorithmic Learning in a Random World. Springer.
- Lei, J., G'Sell, M., Rinaldo, A., Tibshirani, R.J., Wasserman, L. (2018). Distribution-free predictive inference for regression. JASA.
- Xu, C., Xie, Y. (2021). Conformal prediction interval for dynamic time-series (EnbPI). ICML.
- Barber, R.F., Candes, E.J., Ramdas, A., Tibshirani, R.J. (2021). Predictive inference with the jackknife+. Annals of Statistics.
- Gibbs, I., Candes, E.J. (2021). Adaptive conformal inference under distribution shift. NeurIPS.
- Gibbs, I., Candes, E.J. (2024). Conformal inference for online prediction with arbitrary distribution shifts. JMLR.
- Romano, Y., Patterson, E., Candes, E.J. (2019). Conformalized quantile regression. NeurIPS.
- Cordier, T. et al. (2022). MAPIE: an open-source library for distribution-free uncertainty quantification. arXiv:2207.12274.
ผู้เขียน
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.