เส้นโค้ง slippage ไม่ใช่ค่าคงที่ slippage: โมเดลต้นทุนที่รอดจากการเทรดจริง
บรรทัดที่แพงที่สุดในแบ็คเทสต์ส่วนใหญ่คือ slippage_bps = 5 ไม่ใช่เพราะเลข 5 ผิด — ถ้าเฉลี่ยตลอดทั้งปีของการ fill มันอาจจะถูกต้องด้วยซ้ำ — แต่เพราะค่าคงที่นั้นผิด แบบมีเงื่อนไข และกระบวนการคัดเลือกกลยุทธ์ของคุณคือเครื่องจักรที่ค้นหาและใช้ประโยชน์จากความผิดพลาดแบบมีเงื่อนไขเหล่านั้น slippage เป็นฟังก์ชันของขนาดออเดอร์ สเปรด ความผันผวน และสภาพคล่องที่มีอยู่ ค่าคงที่คือฟังก์ชันนั้นที่ประเมินค่า ณ จุดเดียวแล้วนำไปขยายใช้ทุกที่ รวมถึงตอนตี 3 ที่เกิด liquidation cascade ซึ่งสัญญาณ mean-reversion ของคุณยิงแรงที่สุดและ order book มีความลึกเหลือแค่หนึ่งในสิบของปกติ แบ็คเทสต์คิดค่าธรรมเนียม 5 bps ตรงนั้นเหมือนกัน แต่การเทรดจริงจะไม่เป็นแบบนั้น
บทความนี้พูดถึงโมเดลต้นทุน ภายใน backtest loop: ฟังก์ชัน cost(size, market_state) ที่ถูกหักออกจากทุกการเทรดจำลอง โดยตั้งใจไม่พูดถึงการพยากรณ์ impact ล่วงหน้าเพื่อจัดตารางการส่งคำสั่ง — นั่นเป็นปัญหาคู่กันที่ครอบคลุมในมุมโมเดลใน price impact modeling และในมุมการจัดตารางที่เหมาะสมที่สุดใน Almgren-Chriss ที่นี่คำถามแคบกว่านั้น และสำหรับใครก็ตามที่คัดเลือกกลยุทธ์ด้วยแบ็คเทสต์ ยิ่งเร่งด่วนกว่า: ควรใช้ฟังก์ชันแบบไหนแทนที่ค่าคงที่ที่คุณใช้อยู่ตอนนี้ จะฟิตมันจากข้อมูลที่คุณมีจริงได้อย่างไร และคำตอบนั้นจะเปลี่ยนกลยุทธ์ที่คุณจะรันในที่สุดมากแค่ไหน ส่วนสุดท้ายมีคำตอบเชิงประจักษ์ที่ชัดเจน: จัดอันดับใหม่ 20 รูปแบบของกลยุทธ์ตระกูลเดียวภายใต้โมเดลต้นทุนสี่แบบ แล้วดู leaderboard พลิกกลับ
บันไดโมเดลต้นทุน

เหมือนกับ fill simulation ladder โมเดลต้นทุนสร้างบันไดความละเอียดที่แต่ละขั้นต้องการข้อมูลมากขึ้นและกำจัดความเอนเอียงเชิงระบบเฉพาะอย่างหนึ่ง แต่ละขั้นสะสมต่อกัน: แต่ละขั้นเก็บเทอมของขั้นล่างไว้และเพิ่มอีกหนึ่งเทอม
M0: bps คงที่
ตัวเลขเดียว ใช้กับทุกการเทรด สามรูปแบบความล้มเหลว เรียงตามความเสียหายที่เพิ่มขึ้น:
- ไม่รับรู้ขนาด (Size-blind) คำสั่งขนาด 5M จ่ายเท่ากัน การเปรียบเทียบกลยุทธ์ข้าม capacity level จึงไม่มีความหมาย
- ไม่รับรู้ช่วงตลาด (Regime-blind) บ่ายวันอังคารที่สงบและวิกฤต FTX ล่มจ่ายเท่ากัน ความผิดพลาดนี้ไม่ใช่สัญญาณรบกวน แต่มีความสัมพันธ์แบบตรงข้ามพอดีกับสถานะที่กลยุทธ์แบบ event-driven กระจุกตัวเทรดมากที่สุด
- เป็นพิษต่อการคัดเลือก (Selection-toxic) เมื่อคุณกวาดพารามิเตอร์ 200 ชุดค่าผสม ตัวปรับให้เหมาะสม (optimizer) จะพบว่าภายใต้ต้นทุนคงที่ turnover นั้นถูกกว่าความเป็นจริง และดันการค้นหาทั้งหมดไปทางรูปแบบความถี่สูง ความผิดพลาดของต้นทุนกลายเป็นแรงที่มีทิศทางต่อการคัดเลือกโมเดล ไม่ใช่แรงที่สมมาตรต่อ PnL
M1: สัดส่วนกับสเปรด
คิดค่าธรรมเนียมด้วยครึ่งสเปรดที่ วัดได้จริง ณ เวลาตัดสินใจ (บวกกับค่าธรรมเนียมที่กำหนดแน่นอนซึ่งควรเป็นบรรทัดของตัวเองเสมอ) นี่คือโมเดลแรกที่ตอบสนองต่อตลาดจริง ๆ: สเปรดขยายตัวเมื่อมีข่าว เมื่อเปิดเซสชัน ใน altcoin ที่บาง ดังนั้นต้นทุนตอนนี้เคลื่อนไหวไปพร้อมกับตัวแปรสภาพคล่องอย่างน้อยหนึ่งตัว สิ่งที่ยังขาดไป: สเปรดตั้งราคาสำหรับการ fill ขนาดหนึ่งคลิปที่ top-of-book เท่านั้น อะไรก็ตามที่ใหญ่กว่านั้นจะเดินไปตาม book ซึ่งการเดินนี้ มองไม่เห็น
M2: ปรับตามความผันผวน
เพิ่มเทอมที่เป็นสัดส่วนกับความผันผวนระยะสั้น เหตุผลรองรับทั้งเชิงประจักษ์และเชิงโครงสร้าง: สเปรดและความลึกถูกกำหนดโดย market maker ที่ความเสี่ยงจาก adverse-selection และ inventory ของพวกเขาปรับตามขนาดของ (ตรรกะการตั้งราคาแบบ Avellaneda-Stoikov ที่รันย้อนกลับ) ดังนั้น ต้นทุนของการเรียกร้องสภาพคล่อง จึงถูกวัดเป็นหน่วยของความผันผวนโดยธรรมชาติ เมื่อคุณไม่มีข้อมูล book เลย — มีแค่แท่งราคารายวัน ประวัติยาว — ที่ ประมาณ 0.05–0.1 ของความผันผวนรายวัน ก็เป็นโมเดลที่ดีกว่าค่าคงที่ใด ๆ อย่างมากแล้ว เพราะอย่างน้อยมันก็เคลื่อนไหวไปกับช่วงตลาด
M3: square-root ตาม participation
เทอมของขนาดออเดอร์ คือขนาดออเดอร์ของคุณ คือปริมาณการซื้อขายในตลาดในช่วงเวลาที่เกี่ยวข้อง คือความผันผวนในช่วงเวลาเดียวกัน รูปแบบฟังก์ชันนี้ไม่ใช่แค่ความสะดวก แต่เป็นหนึ่งในผลลัพธ์เชิงประจักษ์ที่ถูกทำซ้ำมากที่สุดใน market microstructure และคุ้มค่าที่จะอธิบายให้แม่นยำว่ามันมาจากไหน
จุดเริ่มต้นทางทฤษฎีคือ Kyle (1985) "Continuous auctions and insider trading" (Econometrica 53(6), 1315–1335) ในโมเดลของ Kyle market maker ตั้งราคาเป็นฟังก์ชันเชิงเส้นของ net order flow, โดยที่
— impact ต่อหน่วยของ flow เป็นสัดส่วนกับความไม่แน่นอนของมูลค่าเทียบกับปริมาณการเทรดแบบสุ่ม (noise trading) Kyle's lambda ให้หน่วยวัดสภาพคล่องแก่วงการนี้ (ความลึกคือ ) และข้อเสนอที่ทดสอบได้ข้อแรก: ต้นทุนต่อดอลลาร์ที่เทรดขึ้นอยู่กับอัตราส่วนของข้อมูลต่อสภาพคล่อง ไม่ใช่ค่าคงที่ แต่โมเดลนี้เป็นเชิงเส้นตามขนาด ในขณะที่ข้อมูลจริงบอกเป็นอย่างอื่น
ข้อมูลจริงบอกว่าเป็น เว้า (concave) Almgren, Thum, Hauptmann และ Li (2005) "Direct estimation of equity market impact" (Risk, กรกฎาคม 2005) ฟิตออเดอร์หุ้นสหรัฐฯ ราว 700,000 รายการจากโต๊ะเทรดของ Citigroup และพบว่า temporary impact เติบโตตาม participation ยกกำลัง — พวกเขาทดสอบและปฏิเสธทั้งแบบเชิงเส้นและ square-root แบบล้วน ๆ อย่างชัดเจน โดยเลือก — ในขณะที่ permanent impact ออกมาที่ ซึ่งแยกไม่ออกทางสถิติจากเชิงเส้น (ซึ่งน่าอุ่นใจ เพราะ Huberman และ Stanzl (2004) แสดงให้เห็นว่า permanent impact แบบไม่เชิงเส้นเปิดช่องให้เกิดการบิดเบือนราคาได้) Tóth และคณะ (2011) "Anomalous price impact and the critical nature of liquidity in financial markets" (Physical Review X 1, 021006) ใช้ metaorder เกือบ 500,000 รายการจาก Capital Fund Management ในตลาด futures พบว่า โดยที่ มีขนาดเท่ากับหนึ่ง — และให้คำอธิบายที่กลายเป็นมาตรฐานในปัจจุบัน: order book ที่มองเห็นได้เป็นเพียงเศษเสี้ยวเล็กน้อยของสภาพคล่องแฝง (latent liquidity) ซึ่งเป็นเชิงเส้นในระดับท้องถิ่นตามราคารอบ ๆ mid ทำให้ ต้นทุนของการกินเข้าไปใน book นั้น ขยายตัวเป็น square root งานวิจัยรุ่นหลังปรับแต่งขอบเขต: Zarinelli, Treccani, Farmer และ Lillo (2015) "Beyond the square root" (Market Microstructure and Liquidity 1(2)) แสดงให้เห็นจาก metaorder สถาบันของ ANcerno เจ็ดล้านรายการว่า square root ฟิตได้ดีในช่วงประมาณสอง decade ของขนาดออเดอร์ แต่รูปแบบลอการิทึมขยายครอบคลุมได้ถึงห้า decade Bucci และคณะ (2019, Physical Review Letters 122, 108302) บันทึกการเปลี่ยนผ่านจาก impact เชิงเส้น (แบบ Kyle) ที่ participation ต่ำมาก ไปเป็น square-root ที่ระดับสูงกว่านั้น และ Frazzini, Israel และ Moskowitz (2018) "Trading Costs" (SSRN 3229719) ใช้ข้อมูลการซื้อขายจริงมูลค่า 1.7 ล้านล้านดอลลาร์จาก AQR ตลอด 19 ปี พบว่าต้นทุนที่เกิดขึ้นจริงต่ำกว่าค่าประมาณทางวิชาการก่อนหน้านี้ถึงหนึ่งอันดับ — เป็นคำเตือนว่าค่าสัมประสิทธิ์ที่ฟิตจาก flow ของ คนอื่น ซึ่งมีความเร่งด่วนและเนื้อหาข้อมูลต่างกัน ไม่สามารถถ่ายโอนมาใช้กับของคุณได้
สำหรับ backtest loop สรุปเชิงปฏิบัติมีสามข้อ impact ขยายตัวตามความผันผวน impact เป็นเว้าตามขนาด โดยควรใช้เลขชี้กำลังที่ จนกว่าข้อมูลของคุณเองจะชี้ไปที่ ค่าสัมประสิทธิ์ มีขนาดเท่ากับหนึ่งแต่แตกต่างกันไปตาม venue สินทรัพย์ และ — ที่สำคัญที่สุด — สไตล์การเทรดของคุณเอง ซึ่งเป็นเหตุผลที่หัวข้อถัดไปพูดถึงการฟิตแทนที่จะลอกเลียน การ calibration เชิงลึก (decay kernel, การแยกส่วน transient กับ permanent, cross-impact) เป็นเรื่องของบทความเรื่อง impact modeling ส่วนแบ็คเทสต์ต้องการแค่เส้นโค้งนี้
ตัวเลขตัวอย่างเพื่อให้เห็นภาพขนาด: BTC perp ความผันผวนรายวัน bps ปริมาณการซื้อขายรายวันของ venue V_d = \6Y = 0.75\delta = 0.5$:
| ขนาดออเดอร์ | เทอม Impact | + ครึ่งสเปรด (0.5 bp) | ||
|---|---|---|---|---|
| $50k | 0.0008% | 0.0029 | 0.5 bp | 1.0 bp |
| $500k | 0.008% | 0.0091 | 1.7 bp | 2.2 bp |
| $5M | 0.083% | 0.0289 | 5.4 bp | 5.9 bp |
| $50M | 0.83% | 0.0913 | 17.1 bp | 17.6 bp |
ขนาดที่ต่างกันสามอันดับความสำคัญครอบคลุมต้นทุนที่ต่างกันประมาณ 18 เท่า ไม่มีค่าคงที่ใดครอบคลุมตารางนี้ได้ และการเปรียบเทียบกลยุทธ์ทุกครั้งที่เปลี่ยนขนาดตำแหน่งแต่คง slippage_bps ไว้คงที่ ก็เท่ากับกำลังเปรียบเทียบแอปเปิลที่ 5M อย่างเงียบ ๆ
การฟิตเส้นโค้ง: fills ของคุณเอง หรือของใครก็ไม่ได้
จาก fills ของคุณเอง
ข้อมูลที่ถูกต้องคือบันทึก TCA ของคุณเอง: หนึ่งแถวต่อ parent order พร้อม arrival mid, ราคา fill VWAP ที่เกิดขึ้นจริง, ขนาดออเดอร์ และสถานะตลาด ณ เวลาตัดสินใจ — นี่คือชุดข้อมูลเดียวกับที่ การวัด implementation shortfall ผลิตออกมาเป็นผลพลอยได้ shortfall เป็น bps เทียบกับ arrival ปรับด้วยความผันผวนร่วมสมัย แล้ว regress กับ participation จะให้ค่า และ ออกมาโดยตรง
กับดักคือสัญญาณรบกวน shortfall ของออเดอร์เดียวคือ impact บวกกับสัญญาณรบกวนราคาตลอดช่วงเวลาการส่งคำสั่ง และสัญญาณรบกวนนั้นครอบงำ: สำหรับการส่งคำสั่ง 10 นาทีที่ความผันผวนรายวัน 250 bps เทอมสัญญาณรบกวนคือ bps เทียบกับสัญญาณ impact ที่อาจแค่ 3 bps สัดส่วนสัญญาณต่อสัญญาณรบกวนที่ 0.15 ต่อการสังเกตหนึ่งครั้ง คือเหตุผลที่ Almgren และคณะต้องใช้ออเดอร์ 700,000 รายการ และเป็นเหตุผลที่คุณต้องฟิตบน bucket median เท่านั้น ไม่ใช่บนจุดข้อมูลดิบ:
import numpy as np
import pandas as pd
def fit_sqrt_curve(orders: pd.DataFrame) -> tuple[float, float]:
"""orders: one row per parent order.
is_bps -- implementation shortfall vs arrival mid, bps, cost-positive
sigma_bps -- volatility at arrival (same window used at simulation time)
q_over_v -- parent size / market volume over the execution window
Fits I = Y * sigma * (Q/V)^delta on participation-bucket medians."""
df = orders.query("q_over_v > 0 and sigma_bps > 0").copy()
df["i_norm"] = df.is_bps / df.sigma_bps # impact in vol units
df["bucket"] = pd.qcut(np.log10(df.q_over_v), 12, duplicates="drop")
b = (df.groupby("bucket", observed=True)
.agg(i=("i_norm", "median"), qv=("q_over_v", "median")))
b = b[b.i > 0] # noisy buckets can go negative; drop, don't clip
delta, log_y = np.polyfit(np.log(b.qv), np.log(b.i), 1)
return float(np.exp(log_y)), float(delta)
จาก parent order สกุลคริปโตสองสามพันรายการ การฟิตนี้มีความเสถียรอยู่แล้วในระดับประมาณ ±30% สำหรับ และ ±0.1 สำหรับ — หยาบก็จริง แต่ความแตกต่างระหว่างเส้นโค้งที่ฟิตมาจริงกับค่าคงที่ที่เดา ไม่ใช่แค่ 30% แต่เป็นทั้งรูปทรงเลย ควรฟิตใหม่ทุกไตรมาส การเลื่อนไหลของ เองก็เป็นตัวชี้วัดหนึ่ง (ค่า ที่เพิ่มขึ้นที่ participation คงที่ หมายถึงช่วงสภาพคล่องของ venue เปลี่ยนไป หรือ order flow ของคุณเองมีข้อมูลมากขึ้น/ถูกตรวจจับได้ง่ายขึ้น)
เริ่มต้นแบบไม่มีข้อมูล: ยังไม่มี fills
ก่อนออเดอร์แรกในตลาดจริง คุณมีแค่ข้อมูลสาธารณะ และมีสองทางเลือกที่ซื่อสัตย์
เดินความลึกของ book (Depth-walk) หาค่าเฉลี่ยของต้นทุนจากการเดินผ่าน L2 snapshot ที่แต่ละขนาด เหมือนกับที่ ตัวจำลอง fill ทำต่อการเทรดแต่ละครั้ง แต่รวมเป็นเส้นโค้ง นี่คือ ขอบล่าง (lower bound) ที่มีทิศทางที่รู้แน่ชัด: snapshot แสดงสภาพคล่องที่รอดจาก flow ของคนอื่นทั้งหมด พลวัตการเติมกลับ (refill) มองไม่เห็น และ child order ก่อนหน้าของคุณเองจะขยับ book ไปแล้วก่อนที่ order ถัดไปจะมาถึง
def curve_from_book(snapshots, sizes_usd, safety=1.75):
"""snapshots: iterable of (mid, asks), asks = [(price, qty), ...] best-first.
Depth-walk cost is a LOWER bound on realized cost -- scale it."""
rows = []
for q_usd in sizes_usd:
costs = []
for mid, asks in snapshots:
rem, paid, got = q_usd / mid, 0.0, 0.0
for p, q in asks:
x = min(q, rem)
paid += x * (p - mid); got += x; rem -= x
if rem <= 0:
break
if rem <= 0:
costs.append(paid / got / mid * 1e4)
rows.append((q_usd, safety * float(np.median(costs))))
return pd.DataFrame(rows, columns=["q_usd", "cost_bps"])
สำรวจร่องรอยจากการเทรดสาธารณะ (Footprint public trades) แบ่งกลุ่มการเทรดของฝ่ายรุก (aggressor) ตามขนาด วัดการเคลื่อนไหวของ mid ไม่กี่วินาทีหลังการเทรดแต่ละครั้ง แล้วอ่านเส้นโค้ง impact เชิงประจักษ์ออกมา วิธีนี้กู้คืนรูปทรงเว้าและการปรับตาม ได้ค่อนข้างถูกต้อง แต่ระดับ (level) มีความเอนเอียงในทิศทางที่คุณไม่สามารถระบุล่วงหน้าได้: ธุรกรรมสาธารณะเป็นการตัดสินใจของ คนอื่น ดังนั้นธุรกรรมขนาดใหญ่มักมีข้อมูลแฝงอยู่ (ทำให้ impact ที่วัดได้สูงเกินจริงเทียบกับการปรับสมดุลของคุณที่ไม่มีข้อมูลแฝง) ในขณะที่เทรดเดอร์ที่อ่อนไหวต่อสภาพคล่องที่สุดมักแบ่งออเดอร์เพื่อซ่อนตัวโดยเฉพาะ (ทำให้ impact ต่ำเกินจริง) ใช้มันเพื่อรูปทรง ไม่ใช่ระดับ
ไม่ว่าจะทางใด โปรโตคอลเริ่มต้นแบบไม่มีข้อมูลคือ: นำเส้นโค้งจากข้อมูลสาธารณะมาใช้ ใส่ตัวคูณความปลอดภัย 1.5–2 เทรดขนาดเล็ก แล้วเริ่มเติมตาราง TCA ที่จะมาแทนที่มัน เส้นโค้งสาธารณะเป็นแค่โครงชั่วคราว ไม่ใช่กำแพงรับน้ำหนัก
การพึ่งพาช่วงตลาด (Regime): ออเดอร์เดียวกัน ต้นทุนห้าเท่า

ทุกเทอมใน ขึ้นอยู่กับสถานะตลาด และในช่วงความผันผวนพุ่งสูง เทอมทั้งหมดเคลื่อนไหวสวนทางกับคุณพร้อมกัน สเปรดเป็นสัดส่วนกับความผันผวนระยะสั้น ดังนั้น ขยายตัว 5–10 เท่า เข้าไปในเทอม impact โดยตรง ปริมาณการซื้อขาย ก็เพิ่มขึ้นเช่นกัน — ซึ่งดูเผิน ๆ แล้ว ลด participation — แต่มันเพิ่มขึ้นน้อยกว่า ในเศษ และการชดเชยนั้นซ่อนผลกระทบที่เลวร้ายกว่า: ความลึกที่เสนอราคาใกล้ mid ยุบตัวเร็วกว่าปริมาณการเทรดมาก ทำให้เส้นโค้งที่เกิดขึ้นจริงชันกว่าที่สูตรที่ฟิตไว้ทำนายจากอินพุตที่อยู่ในภาวะตึงเครียดเพียงอย่างเดียว
โดยเป็นรูปธรรม ด้วยเส้นโค้งที่ฟิตไว้ข้างต้น ออเดอร์ BTC ขนาด $3M:
- วันที่สงบ: bps, สเปรด 1 bp, V_d = \6= 0.5 + 0.75 \times 150 \times \sqrt{0.0005} = 0.5 + 2.5 = 3.0$ bps
- วันวิกฤต (ระดับ LUNA, FTX): bps, สเปรด 6 bps, V_d = \18= 3.0 + 0.75 \times 700 \times \sqrt{0.000167} = 3.0 + 6.8 = 9.8$ bps
สูตรบอกว่า 3.3 เท่า TCA ที่เกิดขึ้นจริงในวันตึงเครียดมักจะสูงกว่าสูตรอยู่เสมอ — ความยุบตัวของความลึกที่เทอม sqrt มองไม่เห็น — ซึ่งเป็นจุดที่ตัวคูณความเครียดที่วัดได้ควรเข้ามาแทรก ในข้อมูล fills ของเรา residual ของกลุ่มวิกฤตอยู่ที่ 1.4–1.8 เท่าบนสูตรที่ปรับอินพุตแล้ว ทำให้ต้นทุนความเครียดรวมอยู่ที่ 4.5–6 เท่าของต้นทุนวันสงบสำหรับออเดอร์เดียวกัน นั่นคือที่มาที่แท้จริงของคำว่า "ออเดอร์เดียวกันมีต้นทุนแพงกว่า 5 เท่าในช่วงความผันผวนพุ่งสูง": ประมาณ 2 เท่าจาก หักปริมาณการเทรดออก 2 เท่าจากสเปรด และที่เหลือมาจากความลึกที่ระเหยเร็วกว่าที่สถิติปริมาณการเทรดยอมรับ
การนำไปใช้งานคือโมเดลต้นทุนแบบมีเงื่อนไข: ฟิตเส้นโค้งต่อกลุ่ม regime หรือฟิตเส้นโค้งเดียวและตารางตัวคูณที่อ้างอิงตาม quantile ของความผันผวนที่เกิดขึ้นจริง ซึ่งคำนวณ ณ เวลาตัดสินใจ (หน้าต่างแบบ trailing เท่านั้น — ป้าย regime ที่คำนวณจากตัวอย่างทั้งหมดคือ look-ahead ที่แอบเข้ามาผ่านโมเดลต้นทุน):
| ช่วงตลาด (quantile ความผันผวน trailing 1 ชม.) | ความถี่ | ตัวคูณสเปรด | ตัวคูณเส้นโค้ง (วัดได้) |
|---|---|---|---|
| เงียบ (< p25) | 25% | 0.6x | 0.7x |
| ปกติ (p25–p75) | 50% | 1.0x | 1.0x |
| สูงขึ้น (p75–p95) | 20% | 1.8x | 1.6x |
| ตึงเครียด (> p95) | 5% | 5–10x | 3–6x |
Frazzini, Israel และ Moskowitz (2018) บันทึกปรากฏการณ์เดียวกันในข้อมูลสถาบันของพวกเขา: ต้นทุนที่เกิดขึ้นจริงเคลื่อนไหวไปพร้อมกับความผันผวนร่วมสมัยอย่างแรง และโมเดลต้นทุนที่ไม่มีเทอมความผันผวนที่แปรผันตามเวลาจะตั้งราคาผิดพลาดตรงส่วนหางพอดี เหตุผลที่ตารางนี้สำคัญมากกว่าที่แถวความเครียด 5% จะบ่งบอกคือความสัมพันธ์: กลยุทธ์ต่าง ๆ ไม่ได้สุ่มตัวอย่างจากแถวเหล่านี้อย่างสม่ำเสมอ ระบบ vol-breakout เทรดส่วนใหญ่ในสองแถวล่างสุด market maker ในตลาดสงบทำกำไรในแถวบนสุดและ stop out ผ่านแถวล่างสุด การถ่วงน้ำหนักการเทรดของแต่ละกลยุทธ์ด้วย histogram ของ regime ของตัวมันเอง — แทนที่จะคิดค่าเฉลี่ยแบบไม่มีเงื่อนไขกับทุกคน — คือการปรับ PnL ที่ใหญ่ที่สุดเพียงจุดเดียวในหน้านี้ทั้งหมดสำหรับระบบแบบ event-driven มีค่ามากกว่าความแตกต่างระหว่างขั้นบันไดที่อยู่ติดกันสองขั้นใด ๆ
การทดลองความอ่อนไหว: โมเดลต้นทุนสี่แบบ leaderboard เดียวที่พลิกกลับ
ถ้าโมเดลต้นทุนเปลี่ยน PnL ด้วยค่าชดเชยคงที่ เรื่องทั้งหมดนี้จะไม่มีความสำคัญต่อ การคัดเลือก — ทุกรูปแบบจะเลื่อนเท่ากันและ argmax จะอยู่รอด แต่มันไม่เป็นเช่นนั้น เพราะความอ่อนไหวต่อต้นทุนแตกต่างกันไปในแต่ละรูปแบบถึงหนึ่งอันดับความสำคัญ การทดลองที่ทำให้เรื่องนี้เป็นรูปธรรม:
การตั้งค่า กลยุทธ์ตระกูลเดียว — z-score mean reversion และ Donchian breakout บน BTC และ ETH perps แท่งราคา 1 นาที 18 เดือน — พร้อมกริดของ holding horizon (15 นาที / 1 ชม. / 4 ชม. / 24 ชม.) และ entry threshold: 20 รูปแบบ turnover ด้านเดียวตั้งแต่ 0.4 เท่าถึง 11 เท่าของ book ต่อวัน คลิปขนาด $250k ที่ส่งคำสั่งภายใน 5 นาที แต่ละรูปแบบถูกแบ็คเทสต์หนึ่งครั้งต่อโมเดลต้นทุนหนึ่งแบบ:
- M0: ค่าธรรมเนียมอย่างเดียว
- M1: ค่าธรรมเนียม + 5 bps คงที่
- M2: ค่าธรรมเนียม + ครึ่งสเปรด +
- M3: ค่าธรรมเนียม + เส้นโค้งที่ฟิตแล้ว , พร้อมตารางตัวคูณ regime ข้างต้น
fills เดียวกัน สัญญาณเดียวกัน โค้ดเส้นทางเดียวกัน — โมเดลต้นทุนถูกส่งเข้าไปในแบ็คเทสเตอร์เป็นอาร์กิวเมนต์ของ constructor เหมือนพารามิเตอร์ระดับหนึ่งใด ๆ:
from dataclasses import dataclass
@dataclass(frozen=True)
class SqrtCost:
y: float = 0.75 # fitted impact coefficient
delta: float = 0.5 # fitted exponent
fee_bps: float = 2.5 # taker fee, always separate
def cost_bps(self, q_usd: float, st) -> float:
part = q_usd / st.window_volume_usd
return (self.fee_bps + st.stress_mult *
(st.spread_bps / 2 + self.y * st.sigma_bps * part ** self.delta))
for model in [ZeroCost(), ConstCost(5.0), VolCost(0.08), SqrtCost()]:
for variant in grid:
results[(model, variant)] = backtest(variant, cost_model=model)
ผลลัพธ์ (การรันตัวอย่างที่เป็นตัวแทน; ผลตอบแทนสุทธิรายปี):
| รูปแบบ | Turnover/วัน | M0 | M1 (5 bp) | M2 | M3 (เส้นโค้ง) | อันดับ M1 → M3 |
|---|---|---|---|---|---|---|
| MR-15m, z=1.0 | 11.4x | +187% | +61% | +9% | −14% | 1 → 17 |
| MR-15m, z=1.5 | 7.9x | +141% | +52% | +15% | −2% | 2 → 13 |
| BO-1h, k=2 | 3.1x | +74% | +43% | +28% | +19% | 3 → 4 |
| MR-1h, z=2.0 | 2.4x | +58% | +38% | +25% | +21% | 4 → 2 |
| BO-4h, k=3 | 0.9x | +39% | +33% | +29% | +26% | 7 → 1 |

ค่าสหสัมพันธ์อันดับ (Kendall ) ระหว่าง leaderboard: — ค่าคงที่แทบไม่จัดลำดับใหม่เลย มันเป็นภาษี ไม่ใช่โมเดล — หกในสิบอันดับแรกของ M1 หลุดจากสิบอันดับแรกภายใต้ M3 และแชมป์ของ M1 เปลี่ยนจาก +61% เป็นติดลบ — เทอมความผันผวนเพียงอย่างเดียวกู้คืนการจัดลำดับใหม่ได้ส่วนใหญ่ เทอมขนาดออเดอร์ทำงานที่เหลือให้เสร็จสำหรับรูปแบบที่เทรดขนาดใหญ่เทียบกับปริมาณการเทรดในหน้าต่างเวลา กลไกนี้คือความเป็นพิษต่อการคัดเลือกจากขั้น M0 พอดี: 5 bps คงที่ อุดหนุน การเทรดที่มีต้นทุนจริง 15 bps (ใหญ่ เร็ว ในภาวะตึงเครียด) และ เก็บภาษีเกิน การเทรดที่มีต้นทุนจริง 2 bps (เล็ก อดทน ในภาวะสงบ) ดังนั้นการจัดอันดับตาม M1 จึงส่งเสริมรูปแบบที่ต้นทุนจริงถูกประเมินต่ำเกินไปมากที่สุดอย่างเป็นระบบ ตัวปรับให้เหมาะสมไม่ได้ค้นพบ alpha มันค้นพบ error term ของโมเดลต้นทุนของคุณ
นี่คือเหตุผลที่โมเดลต้นทุนควรอยู่ในหมวดหมู่ทางความคิดเดียวกับการแบ่ง walk-forward หรือ objective function: เป็นพารามิเตอร์ระดับหนึ่งของแบ็คเทสต์ที่คุณเปลี่ยนแปลง ไม่ใช่ค่าคงที่ของสภาพแวดล้อมที่ตั้งครั้งเดียว ถ้าอันดับของรูปแบบหนึ่งเสถียรข้าม M2 และ M3 แสดงว่า edge ของมันเป็นของจริงเทียบกับต้นทุน ถ้าอันดับของมันพังลงเมื่อขึ้นบันได แสดงว่าคุณเรียนรู้ว่ามันเป็นสิ่งประดิษฐ์จากโมเดลต้นทุน — เป็นเหตุการณ์ทางญาณวิทยาเดียวกับกลยุทธ์ที่ตายลงนอกตัวอย่างใน walk-forward optimization และมันสมควรได้รับการตอบสนองแบบเดียวกัน และถ้าคุณเลือก argmax จาก 200 รูปแบบภายใต้ M1 ตรรกะ deflated Sharpe ก็ใช้ได้พร้อมกับข้อบิด: คุณรันการค้นหาแบบ multiple-testing ที่สถิติทดสอบเองก็มีความเอนเอียงในทางที่เอื้อต่อรายการที่มี turnover สูง
ขอบเขตที่ซื่อสัตย์: PnL เป็นแถบ (band)
ระเบียบวินัยสุดท้ายตามมาจากการยอมรับว่าโมเดลต้นทุนยังไม่แน่นอนแม้หลังจากฟิตแล้ว มีช่วงความเชื่อมั่น ตัวคูณความเครียดถูกประมาณจากวันหางเพียงไม่กี่วัน และอนาคตจะดึงช่วงตลาดของตัวมันเอง ดังนั้นหยุดรายงาน PnL สุทธิเป็นตัวเลข รายงานมันเป็นแถบข้ามสถานการณ์ต้นทุนที่ตั้งชื่อไว้:
- มองโลกในแง่ดี: ขอบล่างจาก depth-walk ไม่มีตัวคูณความเครียด อย่ารายงานเดี่ยว ๆ หน้าที่เดียวของมันคือกำหนดขอบเขตกรณีที่ดีที่สุด
- สมจริง: เส้นโค้งที่ฟิตแล้ว ตัวคูณ regime ที่ฟิตแล้ว — ค่าประมาณกลางของคุณ
- มองโลกในแง่ร้าย: , สเปรด , แถวความเครียดถูกใช้เสมอที่ค่า p75 เป็นอย่างน้อย โดยประมาณคือ: เส้นโค้งที่ฟิตแล้วประเมินค่าที่ขอบไม่เป็นมิตรของช่วงความเชื่อมั่นของทุกพารามิเตอร์
กฎการตัดสินใจสองข้อทำให้แถบนี้ใช้งานได้จริง ข้อแรก: จัดสรรตามค่ามองโลกในแง่ร้าย รายงานตามค่าสมจริง กลยุทธ์ที่รอดได้เฉพาะภายใต้โมเดลสมจริง คือการเดิมพันว่าการฟิตต้นทุนของคุณไม่มีข้อผิดพลาดในทิศทางที่แพงขึ้น — การเดิมพันที่คุณไม่ได้ตั้งใจวางไว้ ข้อสอง: ติดตาม อัตราส่วนความเปราะบาง (fragility ratio) — ความกว้างของแถบหารด้วย PnL รวม จากการทดลองข้างต้น: BO-4h ที่ turnover 0.9 เท่า แสดงกำไรรวม +39% แถบ [+22%, +29%] ความเปราะบาง 0.44 MR-15m ที่ 11.4 เท่า แสดงกำไรรวม +187% แถบ [−31%, +34%] — ความเปราะบาง 2.2 และเครื่องหมายของกลยุทธ์พลิกกลับ ภายใน แถบนั้นเอง กลยุทธ์ตัวที่สองนั้นไม่มีเครื่องหมายที่กำหนดได้ ณ ระดับความรู้เรื่องต้นทุนปัจจุบันของคุณ คำพูดที่ซื่อสัตย์คือ "เราไม่รู้" และ "ลด turnover จนกว่าเราจะรู้" แบ็คเทสต์ที่พิมพ์ +34% และละเว้นขอบ −31% ของแถบ ไม่ใช่การมองโลกในแง่ดี แต่เป็นสิ่งที่พิสูจน์ไม่ได้ว่าผิด (unfalsifiable)
แถบนี้ไม่มีต้นทุนในการผลิตเลย — แค่อีกสามรายการในการกวาดโมเดลต้นทุนที่คุณกำลังรันอยู่แล้ว — และมันเปลี่ยนบทสนทนา "กลยุทธ์นี้ทำกำไร 20%" เชิญชวนให้เชื่อ "กลยุทธ์นี้ทำกำไร 11–19% ข้ามสถานการณ์ต้นทุนของเรา และยังเป็นบวกภายใต้สถานการณ์มองโลกในแง่ร้าย" เชิญชวนให้จัดสรรเงินทุน
จุดที่สิ่งนี้เชื่อมต่อเข้าไป
ใน backtest engine โมเดลต้นทุนอยู่ตรงจุดเดียวกับที่ ตัวจำลอง fill ส่งคืนปริมาณที่ถูกดำเนินการ: fills กำหนดว่า คุณได้อะไรมา เส้นโค้งต้นทุนกำหนดว่า คุณจ่ายอะไรไปเพื่อสิ่งนั้น และขั้นบันไดที่สูงกว่า L2 บนบันไดนั้น (depth-walk, queue model) ค่อย ๆ ดูดซับโมเดลต้นทุนเข้าไปในโมเดล fill เอง — เส้นโค้งคือตัวแทนเชิงพารามิเตอร์ที่คุณใช้เมื่อคุณไม่ได้จำลอง book โดยตรง พารามิเตอร์มาจากบันทึก TCA ของคุณเองเมื่อคุณมีมัน และจากข้อมูลสาธารณะที่ปรับด้วยตัวคูณความปลอดภัยเมื่อคุณไม่มี รูปแบบฟังก์ชันมาจากทฤษฎีและการวัดสี่สิบปี — Kyle (1985) สำหรับเหตุผลว่าทำไมต้นทุนต่อดอลลาร์ขึ้นอยู่กับสภาพคล่องเลย Almgren และคณะ (2005) และ Tóth และคณะ (2011) สำหรับกฎเว้าเอง โดยมี Almgren-Chriss เป็นคู่หูด้านการจัดตาราง และโมเดล impact แบบ neural เป็นแนวหน้าของการพยากรณ์ สิ่งที่บทความนี้เพิ่มเข้ามาคือ workflow: ฟิตเส้นโค้ง ปรับตามช่วงตลาด กวาดมันเหมือนพารามิเตอร์ และรายงานเป็นแถบ ไม่มีอะไรในนี้หรูหรา ทั้งหมดถูกกว่าการค้นพบในตลาดจริงว่ากลยุทธ์ของคุณอยู่ฝั่งไหนของ slippage_bps = 5 กันแน่
ผู้เขียน
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.