Signal
Quant และ research
ระบบวิจัยที่ไม่หลอกตัวเอง: การ ingest แบบ read-only การจำแนกสภาวะตลาด Market Memory และการ calibrate นี่คือกระดูกสันหลังของ XCAP
SG-01 Ingest แบบ read-only และ edge ที่ไม่ต้องใช้ key
บทเรียน สร้าง ingest edge แบบ read-only ที่ดึงข้อมูลสาธารณะจากโลกภายนอกลงสู่ดิสก์โดยไม่เปิดเผย credential แม้แต่ตัวเดียว และมีโครงสร้างที่ทำให้ไม่สามารถส่งคำสั่งซื้อขายหรือเคลื่อนย้ายเงินได้เลย
Ingest แบบ read-only และ edge ที่ไม่ต้องใช้ key
บทเรียนสร้าง ingest edge แบบ read-only ที่ดึงข้อมูลสาธารณะจากโลกภายนอกลงสู่ดิสก์โดยไม่เปิดเผย credential แม้แต่ตัวเดียว และมีโครงสร้างที่ทำให้ไม่สามารถส่งคำสั่งซื้อขายหรือเคลื่อนย้ายเงินได้เลย
ระบบ research ที่แตะเครือข่ายในทุกจุด คือระบบที่สามารถทำให้ key รั่วไหล สั่ง trade โดยอุบัติเหตุ และปนเปื้อนผลลัพธ์ด้วยข้อมูลที่ไม่สามารถทำซ้ำได้ keyless edge คือเส้นพรมแดนที่โลกภายนอกเข้ามาได้โดยไม่สามารถสร้างความเสียหาย
บทเรียน
สัญชาตญาณหลักของ XCAP นั้นโหดร้ายและขัดกับความเข้าใจของผู้ที่มาจากสาย SaaS อย่างยิ่ง: ในระบบวิจัยการซื้อขาย เครือข่ายไม่ใช่ความสะดวก แต่คือพื้นผิวการโจมตี (attack surface) ที่ใหญ่ที่สุดที่คุณมี ทุก endpoint ที่ต้องใช้ key คือ credential ที่อาจรั่วไหลได้ ทุกการเรียกแบบ write คือ trade ที่อาจถูกสั่งโดย bug ด้วยเหตุนี้ XCAP จึงยุบการเปิดรับเครือข่ายทั้งหมดลงเหลือเพียงจุดเดียว: ingest edge แบบ read-only, keyless และ GET-only ส่วนที่เหลือของระบบเป็น deterministic จากดิสก์ หากคุณเข้าใจสิ่งนี้ คุณก็เข้าใจว่าทำไม `binance_public.py` จึงร้องขอแค่ klines (แท่งเทียน OHLCV ในอดีต) ผ่าน HTTP GET โดยไม่มี API key ไม่ลงนามอะไรเลย และไม่เคยแตะ endpoint ของบัญชีหรือคำสั่งซื้อขายเลย
ในเชิงรูปธรรม รูปแบบคือ: source ที่เป็นนามธรรม (ใน XCAP คือลำดับชั้น `OHLCSource`) ที่มีสอง implementation `LocalCSVSource` อ่านไฟล์ CSV ที่อยู่บนดิสก์อยู่แล้ว — ไม่มีเครือข่ายเลย เป็นกรณีโดยปริยาย และเป็นตัวที่ loop อัตโนมัติทุกตัวใช้ และ `HttpOHLCSource` (รองรับโดย `binance_public.py`) ซึ่งจะทำงานก็ต่อเมื่อ caller ส่ง `allow_network=True` อย่างชัดเจน หรือเรียกคำสั่ง `data-fetch` ความไม่สมมาตรนี้เป็นความตั้งใจ: เส้นทางที่ปลอดภัยคือค่าโดยปริยาย และเส้นทางที่มีเครือข่ายต้องระบุตัวเลือกอย่างชัดเจนในทุกครั้งที่เรียก ไม่ใช่ flag global ที่อยู่ในสภาพแวดล้อม ไม่ใช่ environment variable ที่ค้างเปิดทิ้งไว้
endpoint ของ Binance ที่คุณใช้คือ endpoint ตลาดสาธารณะ: `GET https://api.binance.com/api/v3/klines?symbol=BTCUSDT&interval=1d&limit=1000` ซึ่งคืนค่าเป็น array ซ้อน array: `[openTime, open, high, low, close, volume, closeTime, ...]` จงสังเกตสิ่งที่ไม่มีอยู่ตรงนี้: ไม่มี header `X-MBX-APIKEY` ไม่มี query param สำหรับลายเซ็น HMAC ไม่มี timestamp ที่ลงนาม endpoint นี้โดยกายภาพไม่สามารถอ่านยอดเงินคงเหลือหรือวางคำสั่งซื้อขายของคุณได้ เพราะ API บัญชีของ Binance อยู่อีกเส้นทางหนึ่งและต้องการลายเซ็น ความปลอดภัยไม่ใช่คำสัญญาของคุณว่าจะประพฤติดี แต่เป็นคุณสมบัติของ endpoint ที่คุณเลือกเอง
เสาหลักที่สองคือการ normalize ที่ขอบ (edge) ข้อมูลจากโลกจริงมาแบบสกปรก: timestamp เป็นมิลลิวินาที ตัวเลขมาเป็น string แท่งเทียนซ้ำกัน มีช่องว่าง edge ของคุณต้อง normalize ให้เป็นรูปแบบ canonical เดียวก่อนที่ downstream อะไรจะแตะมัน ใน XCAP ไฟล์ CSV ของ `data/real` เป็นตัวพิมพ์เล็ก (`open,high,low,close,volume`) timestamp ถูก parse เป็นวันที่ และ `LocalCSVSource` อ่านมันด้วยวิธีเดิมเสมอ วินัยนี้คือสิ่งที่ทำให้ระบบสามารถทำซ้ำได้: classifier ของ regime (SG-02) และ Market Memory (SG-03) อ่านรูปแบบที่เสถียรจากดิสก์ ไม่ใช่จาก HTTP response ที่ผันผวนและเปลี่ยนไปในแต่ละรอบการรัน
การตรวจสอบคือบทสรุป XCAP มีคำสั่ง `check` (รันด้วย `PYTHONPATH=src python3 -m xcap.control check`) ที่ยืนยันเป็น invariant ว่า connector เป็น keyless, read-only ไม่สามารถซื้อขายได้ และไม่สามารถเคลื่อนย้ายเงินได้ มันไม่ใช่ test ที่ผ่านครั้งเดียว แต่เป็นการ assert ที่รันทุกครั้งที่ `check` ดังนั้นหากมีใครเพิ่ม key หรือ method แบบ write เข้าไป `check` ก็จะกลายเป็นสีแดง นี่คือวิธีที่คุณเปลี่ยนเจตนาในการออกแบบให้กลายเป็นคุณสมบัติที่ระบบปกป้องตัวเอง กฎทอง: ความปลอดภัยที่ขึ้นอยู่กับว่าคุณจะจดจำบางอย่างได้ ไม่ใช่ความปลอดภัย แต่ความปลอดภัยที่ test สีแดงบังคับให้คุณเคารพ คือความปลอดภัยจริง
รายละเอียดเชิงปฏิบัติที่สำคัญ: dashboard ของ XCAP bind เฉพาะ `127.0.0.1` ไม่เคยเป็น `0.0.0.0` ingest edge คือพื้นผิวขาออกเพียงพื้นผิวเดียว ส่วนพื้นผิวขาเข้า (dashboard) ไม่ฟังบนเครือข่าย และ FileVault (AES-256) เข้ารหัสดิสก์ขณะพักนิ่ง ห่วงโซ่ทั้งหมด — ดิสก์ที่เข้ารหัส, ingest แบบ keyless read-only, dashboard ที่เป็น local-only, Capital Gate ที่ปิดอยู่ — คือสิ่งที่ทำให้สามารถมีระบบ trading-research บนแล็ปท็อปได้โดยไม่กลายเป็นระเบิดเวลา
แบบฝึกหัด
จงสร้าง data source ที่มีสอง backend สร้าง `ohlc_source.py` ที่มี class ฐาน `OHLCSource` (method `get(symbol, interval, limit) -> list[Bar]`) implement `LocalCSVSource` (อ่านจาก `./data/{symbol}.csv` ไม่มีเครือข่าย) และ `HttpOHLCSource` (GET ไปยัง `api.binance.com/api/v3/klines` โดยไม่มี API key ใด ๆ) constructor ของ `HttpOHLCSource` ต้องรับ `allow_network: bool = False` และโยน `RuntimeError('network not allowed')` หากเรียก `get()` ขณะ `allow_network=False` จง normalize ทั้งสองให้เป็นโครงสร้างเดียวกัน `Bar(time, open, high, low, close, volume)` ด้วย float และวันที่ที่ parse แล้ว เขียน `check_connector()` ที่ตรวจสอบด้วย introspection ว่า class HTTP ไม่มี method หรือ attribute ใดที่ชื่อมีคำว่า 'order', 'trade', 'sign', 'key', 'secret' หรือ 'withdraw' และให้ fail หากพบ
สิ่งที่ส่งมอบ
`ohlc_source.py` ที่มี `LocalCSVSource` + `HttpOHLCSource(allow_network=False)` เป็นค่าโดยปริยาย, normalize เป็น `Bar` แบบ canonical และฟังก์ชัน `check_connector()` ที่ยืนยันสภาพ keyless/read-only ด้วย introspection และคืนรายงาน PASS/FAIL
ข้อคิดสำคัญ
เส้นทางที่ปลอดภัยต้องเป็นค่าโดยปริยาย และเส้นทางที่อันตรายต้องบังคับให้ระบุตัวเลือกอย่างชัดเจนในทุกครั้งที่เรียก — ไม่ใช่ flag global `allow_network=True` ที่ส่งผ่าน argument ในทุกการใช้งานนั้นปลอดภัย ส่วน environment variable `ALLOW_NETWORK=1` ที่ค้างเปิดทิ้งไว้คือการรั่วไหลที่รอวันเกิดขึ้น
ข้อผิดพลาดที่ควรเลี่ยง
- ×ใช้ endpoint ที่ต้องการ API key 'เพราะให้ข้อมูลมากกว่า' — ในวินาทีที่คุณลงนามคำขอ edge ของคุณก็ไม่ได้ไร้ความสามารถในการซื้อขายเชิงโครงสร้างอีกต่อไป และหลักประกันทั้งหมดก็พังลง
- ×วางการควบคุมเครือข่ายไว้ใน environment variable แบบ global แทนที่จะเป็น argument ที่ระบุชัดต่อการเรียก มันค้างเปิดข้ามรอบการรัน และ loop อัตโนมัติก็สืบทอดเครือข่ายมาโดยไม่ตั้งใจ
- ×ไม่ normalize ที่ขอบ: ปล่อยให้ timestamp เป็นมิลลิวินาทีและตัวเลขแบบ string แพร่ลงไป downstream ทำลายความสามารถในการทำซ้ำระหว่าง source แบบ local และแบบ HTTP
- ×bind dashboard ไปที่ `0.0.0.0` เพื่อ 'ดูจากมือถือ' — คุณกำลังเปิดเผยงานวิจัยทั้งหมดของคุณสู่เครือข่าย จงใช้ `127.0.0.1` และ SSH tunnel หากคุณต้องการจริง ๆ
- ×ตรวจสอบคุณสมบัติ keyless ด้วยมือเพียงครั้งเดียวแทนที่จะเข้ารหัสมันเป็น assertion ใน `check` ที่รันเสมอ หลักประกันจะถูกกัดกร่อนตั้งแต่การ refactor ครั้งแรกที่ไม่มีใครตรวจสอบ
SG-02 ตัวจำแนกสภาวะของราคา (regime)
บทเรียน สร้าง classifier ของ regime ราคาที่บริสุทธิ์และ deterministic ซึ่งติดป้ายสถานะของตลาด (แนวโน้ม, ความผันผวน, พฤติกรรม, ความเครียด) โดยไม่เคยกำหนดขนาดหรือซื้อขายตำแหน่งใด ๆ เลย
ตัวจำแนกสภาวะของราคา (regime)
บทเรียนสร้าง classifier ของ regime ราคาที่บริสุทธิ์และ deterministic ซึ่งติดป้ายสถานะของตลาด (แนวโน้ม, ความผันผวน, พฤติกรรม, ความเครียด) โดยไม่เคยกำหนดขนาดหรือซื้อขายตำแหน่งใด ๆ เลย
การตัดสินใจโดยไม่รู้ว่าคุณอยู่ใน regime ใดคือการซื้อขายแบบหลับตา: กลยุทธ์ที่ชนะในแนวโน้มจะเลือดไหลไม่หยุดในตลาด sideways ป้าย regime คือเงื่อนไขเบื้องต้นที่ซื่อสัตย์ของทุกการตัดสินใจ และการแยกมันออกจากการ execute ช่วยป้องกันไม่ให้อคติแบบ 'อยากซื้อขาย' มาปนเปื้อนการวินิจฉัย
บทเรียน
ความผิดพลาดเชิงแนวคิดที่ XCAP แก้ไขในการ audit เดือนมิถุนายน 2026 นั้นละเอียดอ่อนและมีค่าดั่งทอง: การสับสนระหว่างสุขภาพเชิงปฏิบัติการ (operational health) กับ regime ของราคา `market_state.py` วัดสุขภาพของระบบ (ฉันมีข้อมูลใหม่หรือไม่? flag ตั้งถูกหรือเปล่า?) ส่วน `regime.py` วัดสิ่งที่แตกต่างโดยสิ้นเชิง: ตลาดเองอยู่ในสถานะใด? ทั้งสองเป็นแกนที่ตั้งฉากกัน (orthogonal) และการนำมาปนกันจะสร้าง classifier ที่ไม่รู้ว่าตัวเองกำลังบอกอะไร วินัยข้อแรกคือ: classifier ของ regime พูดถึงเฉพาะราคาเท่านั้น ไม่เคยพูดถึงสถานะของ infrastructure ของคุณ
การออกแบบของ XCAP จำแนกตามแกนอิสระสี่แกน Trend (ขึ้น ลง หรือออกข้าง? — ผ่านความชันของค่าเฉลี่ยหรือผลตอบแทนสะสมในหน้าต่างหนึ่ง) Volatility (สงบหรือปั่นป่วน? — ผ่าน standard deviation ของผลตอบแทน ที่ normalize แล้ว) Behavior (มีความต่อเนื่อง/เป็นแนวโน้ม หรือ mean-reverting? — ผ่าน autocorrelation ของผลตอบแทน) และ Stress (มี drawdown กำลังก่อตัวหรือไม่?) แต่ละแกนคำนวณอย่างบริสุทธิ์: input เดียวกัน output เดียวกัน ไม่มีสถานะซ่อนเร้น ไม่มีความสุ่ม จากนั้น cascade ของลำดับความสำคัญที่มี document ก็จะ derive `Regime` หลักเพียงตัวเดียวจากแกนทั้งสี่
insight ที่คมที่สุดของ XCAP อยู่ที่วิธีนิยาม Panic และนี่คือจุดที่เกือบทุกคนพลาด สิ่งล่อใจคือการนิยาม panic ว่าเป็น 'ความผันผวนสูง' (อัตราส่วน stdev สูง) ซึ่งไม่ถูกต้อง crash แบบโมโนโทน — ราคาที่ตกลงเกือบเป็นเส้นตรง — มีความผันผวน 'ภายใน' ต่ำ เพราะแต่ละแท่งเทียนคล้ายแท่งก่อนหน้า หากคุณนิยาม panic ด้วย stdev คุณจะพลาด crash ที่อันตรายที่สุดพอดี นิยามที่ถูกต้องของ XCAP คือ: panic คือ drawdown ขนาดใหญ่ที่กำลังก่อตัวอย่างรวดเร็ว วัดด้วยผลตอบแทนล่าสุดที่ติดลบรุนแรงและเฉียบคม ไม่ใช่ด้วยการกระจายตัว นี่คือสิ่งที่แยกแยะ classifier ที่คิดโดยคนที่เคยมองดู crash จริงออกจากคนที่ลอกสูตรมาจากตำรา
ความบริสุทธิ์ไม่ใช่ความเอาแต่ใจเชิงสุนทรียะ แต่เป็นรากฐานของความเชื่อมั่น `regime.py` เป็น pure function: `classify(bars) -> Regime` มันไม่อ่านจากดิสก์ภายในตัวเอง ไม่เรียกเครือข่าย ไม่กลายพันธุ์อะไรที่เป็น global ไม่มีสาขาที่สุ่ม นี่หมายความว่าคุณสามารถรันมันพันครั้งบนหน้าต่างเดียวกันแล้วได้ป้ายเดียวกัน คุณสามารถ test มันด้วย fixture ที่ deterministic และคุณสามารถ audit ได้อย่างแม่นยำว่าทำไมมันถึงบอกว่า 'Panic' ในวันที่หนึ่ง ๆ classifier ที่มีสถานะซ่อนเร้นคือ classifier ที่คุณไม่สามารถไว้ใจให้สร้าง Market Memory ทับลงไปได้
กฎที่สำคัญที่สุด และเป็นกฎที่เชื่อมโยงกับทั้ง constellation: classifier เป็นแบบ label-only มันไม่เคยกำหนดขนาดหรือซื้อขาย คืนค่าเป็นป้ายและไม่มีอะไรอื่น เหตุผลคือสถาปัตยกรรมแห่งความซื่อสัตย์: หาก module เดียวกันที่วินิจฉัย regime ยังเป็นตัวตัดสินขนาดของตำแหน่งด้วย คุณก็มีแรงจูงใจให้การวินิจฉัยมาสนับสนุนการซื้อขายที่คุณอยากทำอยู่แล้ว การแยกการวินิจฉัยออกจากการกระทำคือสิ่งที่ทำให้ใน SG-03 สามารถวัดได้อย่างซื่อสัตย์ว่า regime มีคุณค่าในการทำนายหรือไม่ — เพราะป้ายถูกสร้างขึ้นโดยไม่รู้ว่าคุณจะเอามันไปทำอะไร
เพื่อ implement ให้ดี จง document cascade ของลำดับความสำคัญเป็นโค้ดที่อ่านง่าย ไม่ใช่ if ที่พันกันยุ่งเหยิง เช่น: หาก Stress สั่ง Panic แล้ว Panic ชนะเหนือสิ่งอื่นใด หากไม่ และ Volatility สูงพร้อม Trend แรง ก็เป็น Trending-Volatile หาก Behavior เป็น mean-reverting และ Trend ออกข้าง ก็เป็น Range เป็นต้น แต่ละสาขาต้องมี comment ที่อธิบายเหตุผลเชิงตลาด ไม่ใช่แค่ค่า threshold comment นั้นคือสิ่งที่ผู้ตรวจสอบ — หรือตัวคุณเองในอีกหกเดือน — ต้องการเพื่อจะไว้ใจป้ายนั้น
แบบฝึกหัด
เขียน `regime.py` ที่มี pure function `classify(bars: list[Bar]) -> Regime` คำนวณสี่สัญญาณย่อย: `trend` (เครื่องหมายและขนาดของผลตอบแทนในหน้าต่าง), `vol` (stdev ของผลตอบแทนรายวัน), `behavior` (autocorrelation lag-1 ของผลตอบแทน), `stress` (drawdown สูงสุดล่าสุด และความรวดเร็ว = ผลตอบแทนของ N วันล่าสุด) implement cascade ลำดับความสำคัญที่ Panic = drawdown ขนาดใหญ่ + ผลตอบแทนล่าสุดติดลบรุนแรง (ไม่ใช่ stdev สูง) คืนค่า `Regime` ที่มีป้ายหลักและแกนทั้งสี่ที่เปิดเผยออกมา จงทดสอบด้วย fixture สังเคราะห์สามตัว: แนวโน้มขาขึ้นที่สะอาด, crash แบบโมโนโทน (ต้องให้ Panic แม้ vol ภายในจะต่ำ) และตลาด sideways ที่มีสัญญาณรบกวน จงตรวจสอบว่าฟังก์ชันบริสุทธิ์โดยรัน `classify` สองครั้งบน input เดียวกันแล้วยืนยันว่าเท่ากัน
สิ่งที่ส่งมอบ
`regime.py` ที่มี `classify(bars) -> Regime` ที่บริสุทธิ์และ deterministic, สี่แกน (trend/vol/behavior/stress), cascade ลำดับความสำคัญที่มี document และ test fixture สามตัวที่ crash แบบโมโนโทนถูกจำแนกเป็น Panic อย่างถูกต้อง
ข้อคิดสำคัญ
Panic ไม่ใช่ความผันผวนสูง — crash แบบโมโนโทนมีความผันผวนภายในต่ำเพราะแต่ละแท่งเทียนเลียนแบบแท่งก่อนหน้า จงนิยาม panic ด้วยความเร็วของ drawdown (ผลตอบแทนล่าสุดที่เฉียบคม) ไม่ใช่ด้วยการกระจายตัว มิฉะนั้นคุณจะพลาด regime ที่อันตรายที่สุดพอดี
ข้อผิดพลาดที่ควรเลี่ยง
- ×ปนระหว่าง regime ราคากับสุขภาพเชิงปฏิบัติการใน module เดียวกัน ทั้งสองเป็นแกนที่ตั้งฉากกัน และการรวมเข้าด้วยกันจะสร้างป้ายที่ไม่มีความหมายเป็นรูปธรรม
- ×นิยาม Panic ด้วยอัตราส่วน stdev สูง: คุณจะพลาด crash แบบโมโนโทน ซึ่งเป็นสิ่งที่สำคัญที่สุดพอดี
- ×ใส่สาขาที่สุ่มหรือการอ่านดิสก์เข้าไปใน classifier ทำลายความบริสุทธิ์ของมันและทำให้ไม่สามารถ audit ได้ว่าทำไมจึงติดป้ายให้วันหนึ่ง ๆ
- ×ปล่อยให้ classifier กำหนดขนาดหรือเสนอแนะการซื้อขาย ในวินาทีที่การวินิจฉัยและการกระทำใช้ module ร่วมกัน การวินิจฉัยก็เริ่มสนับสนุนการซื้อขายที่คุณอยากทำอยู่แล้ว
- ×ซ่อน cascade ลำดับความสำคัญไว้ใน if โดยไม่ comment เหตุผลเชิงตลาด ไม่มีใครสามารถไว้ใจหรือตรวจสอบค่า threshold ได้ในอีกหกเดือน
SG-03 Market Memory: forecast และ calibration
บทเรียน สร้าง Market Memory: ledger ที่ล็อกคำทำนายที่พิสูจน์เท็จได้ (falsifiable) ก่อนที่ผลลัพธ์จะเกิดขึ้น แล้วแก้ไขมันเทียบกับผลตอบแทนที่เกิดขึ้นจริง และให้คะแนน calibration ของตัวเอง (hit-rate, Brier, calibration ตามความมั่นใจและตาม regime)
Market Memory: forecast และ calibration
บทเรียนสร้าง Market Memory: ledger ที่ล็อกคำทำนายที่พิสูจน์เท็จได้ (falsifiable) ก่อนที่ผลลัพธ์จะเกิดขึ้น แล้วแก้ไขมันเทียบกับผลตอบแทนที่เกิดขึ้นจริง และให้คะแนน calibration ของตัวเอง (hit-rate, Brier, calibration ตามความมั่นใจและตาม regime)
หากไม่มีบันทึกคำทำนายที่ล็อกก่อนผลลัพธ์ ระบบ research ก็ไม่มีทางรู้อย่างซื่อสัตย์ว่าตัวเองรู้อะไรจริงหรือไม่ มันคือความแตกต่างระหว่าง 'ฉันคิดว่ามันได้ผล' กับ 'นี่คือประวัติ out-of-sample ของฉันที่ให้คะแนนแล้ว' มันคือแหล่งหลักฐานเดียวของ edge ที่แท้จริง
บทเรียน
การค้นพบหลักของการ audit ใน XCAP นั้นทำลายล้าง: ทุก module ก่อนหน้านี้เป็นได้แค่การคำนวณแบบ stateless หรือเป็นตัวสะสมแบบ passive ไม่มีอะไรเลยที่บันทึกคำทำนายที่พิสูจน์เท็จได้ก่อนผลลัพธ์ แล้วให้คะแนน calibration ของตัวเอง loop ที่ขาดหายไปนี้คือสิ่งที่ทำให้ระบบ research หลอกได้ยาก และเป็นแหล่งหลักฐานเดียวที่ซื่อสัตย์ของ edge Market Memory (`market_memory.py`) คือ loop นั้น หากคุณไม่สร้าง module นี้ ทุกอย่างที่เหลือก็เป็นเพียงละคร: การคำนวณที่สวยงามแต่ไม่เคยถูกเผชิญหน้ากับความเป็นจริง
หัวใจคือ `advance_market_memory(universe, state_dir, horizon_days=5, ...)` และลำดับของมันศักดิ์สิทธิ์ตามลำดับนี้: สังเกตวันที่ใหม่ → จำแนก regime (โดยใช้ `regime.py` ของ SG-02) → บันทึก forecast ที่ถูกล็อก (baseline ที่ derive จาก regime พร้อมความน่าจะเป็นและ horizon ของมัน) → แก้ไข forecast ที่ครบกำหนดแล้วเทียบกับผลตอบแทน forward ที่เกิดจริง → ให้คะแนน คำว่าล็อกคือคำสำคัญ: เมื่อคุณบันทึกแล้วว่า 'ในวันที่ T ฉันทำนาย prob_up=0.62 ที่ 5 วัน' บันทึกนั้นเปลี่ยนแปลงไม่ได้ คุณกลับไปแตะมันไม่ได้เมื่อเห็นผลลัพธ์ ความเปลี่ยนแปลงไม่ได้นี้คือสิ่งที่เปลี่ยน ledger ให้กลายเป็นหลักฐาน ไม่ใช่การหาเหตุผลย้อนหลัง
metric ไม่ได้มีแค่ตัวเดียว แต่เป็นแผงควบคุมที่ซื่อสัตย์ Hit-rate (คำทำนายเชิงทิศทางถูกกี่ส่วน?) Brier score (ความน่าจะเป็นถูก calibrate ดีแค่ไหน? — ลงโทษทั้งความมั่นใจเกินและความมั่นใจน้อยเกิน) calibration ตาม bucket ของความมั่นใจ (เมื่อคุณบอก 70% คุณถูก 70% ของเวลาหรือเปล่า?) และอัญมณี: hit-rate ตาม regime ตัวสุดท้ายนี้คือตัวที่ให้ข้อมูลจริง ๆ มันบอกคุณว่าคำทำนายของคุณมีคุณค่าในแนวโน้มแต่เป็นขยะในตลาด sideways หรือคุณถูกแค่ในตลาดสงบเท่านั้น hit-rate รวมที่ 55% อาจซ่อน 70% ในแนวโน้มและ 40% ใน panic — และการแยกย่อยนี้คือสิ่งที่บอกว่า edge อยู่ตรงไหน ถ้ามี
คุณสมบัติต้านการปลอมแปลงเป็นโดยโครงสร้าง (เราจะเจาะลึกใน SG-04 แต่ที่นี่มันเป็นเชิงโครงสร้างแล้ว): มีเพียง forecast ที่ horizon ครบกำหนดด้วยข้อมูลที่มีอยู่หลังวันที่ของ forecast เท่านั้นที่ถูกแก้ไข มันเป็น out-of-sample โดยการออกแบบ คุณไม่เคยสามารถ 'ทำนาย' วันที่ที่คุณรู้ผลลัพธ์อยู่แล้ว เพราะ forecast ถูกล็อกที่ T และแก้ไขก็ต่อเมื่อถึง T+horizon พร้อมข้อมูลจริง state dir ถูก gitignore — ประวัติศาสตร์ไม่ถูก version เพื่อไม่ให้มีสิ่งล่อใจในการแก้ไขด้วยมือและแต่งหน้าผลลัพธ์
คุณสมบัติความถูกต้องสามประการที่ XCAP บังคับใช้ และที่คุณต้องทำซ้ำ: สะสม (ledger เติบโตข้ามรอบการรัน ไม่เคย reset), idempotent (รัน tick สองครั้งบนวันที่เดียวกันไม่ทำให้ forecast ซ้ำหรือถูกแก้ไขสองครั้ง) และ Gate-closed (ไม่เคยซื้อขาย) นี่คือ invariant ตระกูลเดียวกับ capital invariant ของ SG-05: สถานะของระบบคือผลรวมของเหตุการณ์จริง ไม่ใช่การ replay หรือ baseline ใหม่ CLI `market-memory-tick` ทำหน้าที่ orchestrate สิ่งนี้และอ่านไฟล์ CSV รูปแบบ `data/real` (ตัวพิมพ์เล็ก) ผ่าน `LocalCSVSource` — ไม่มีเครือข่าย ทุกอย่าง deterministic จากดิสก์
จุดหนึ่งที่แยกวิศวกรออกจากมือสมัครเล่น: baseline ที่คุณบันทึกต้องซื่อสัตย์และถ่อมตน XCAP บันทึก 'regime_baseline' — ความน่าจะเป็นที่ derive จาก regime ไม่ใช่ model ที่ซับซ้อนซึ่งคุณ optimize ไปแล้วโดยมองอดีต ทำไมต้องเริ่มต้นอย่างถ่อมตน? เพราะ baseline คือไม้บรรทัดวัดของคุณ หาก forecaster ที่ซับซ้อนเอาชนะ regime baseline แบบ out-of-sample ไม่ได้ คุณก็ไม่มี edge คุณมี overfitting Market Memory มีอยู่เพื่อหักล้างความคิดของคุณ ไม่ใช่เพื่อยืนยันมัน และบันทึก forecast ที่ดีคือหลักฐาน ไม่เคยเป็นข้อพิสูจน์ — ด้วยเหตุนี้ Capital Gate จึงยังปิดอยู่ไม่ว่า hit-rate จะดูดีแค่ไหนก็ตาม
แบบฝึกหัด
จงสร้าง `market_memory.py` ด้วย ledger แบบ JSON-lines append-only implement `advance(universe, state_dir, horizon_days=5)` ที่: (1) อ่านวันที่ใหม่ (2) สำหรับแต่ละวันที่ จำแนก regime ด้วย `regime.py` ของคุณ (3) บันทึก forecast ที่ถูกล็อก `{date, symbol, regime, prob_up, horizon, resolved: false}` (4) แก้ไข forecast ที่ครบกำหนด (ที่มีแท่งเทียนที่ date+horizon) โดยคำนวณผลตอบแทน forward จริงและทำเครื่องหมาย `hit` (5) เขียน metric: hit-rate รวม, Brier และการแยกย่อย hit-rate ตาม regime จงทำให้ tick เป็น idempotent: หากมี forecast สำหรับ (date, symbol) อยู่แล้ว อย่าทำซ้ำ หากถูกแก้ไขแล้ว อย่าแก้ไขซ้ำ จงสาธิตการสะสมโดยรัน tick บนช่วงวันที่ที่ต่อเนื่องกันสองช่วงและแสดงว่า ledger เติบโตขึ้นโดยไม่ reset
สิ่งที่ส่งมอบ
`market_memory.py` + ledger แบบ append-only ใน `state/` (gitignore) ที่มี forecast ถูกล็อกก่อนผลลัพธ์, การแก้ไขแบบ out-of-sample, metric hit-rate/Brier/calibration-ตาม-regime และ CLI `market-memory-tick` ที่ idempotent และสะสม
ข้อคิดสำคัญ
metric ที่ให้ข้อมูลจริง ๆ คือ hit-rate ตาม regime ไม่ใช่ค่ารวม ค่ารวม 55% อาจซ่อน 70% ในแนวโน้มและ 40% ใน panic — และการแยกย่อยนั้นคือตัวเดียวที่บอกคุณว่า edge ที่แท้จริงและใช้ประโยชน์ได้อยู่ตรงไหน ถ้ามี
ข้อผิดพลาดที่ควรเลี่ยง
- ×บันทึกคำทำนายและแก้ไขมันในรอบการรันเดียวกันโดยมองผลลัพธ์ไปแล้ว นั่นไม่ใช่ forecast แต่เป็นการปรับย้อนหลัง และทำลายหลักฐานของ edge ทั้งหมด
- ×อนุญาตให้ ledger reset ระหว่างรอบการรัน คุณสูญเสียประวัติศาสตร์ที่เป็นทรัพย์สินพอดี และทำลายคุณสมบัติการสะสม
- ×ทำให้ tick ไม่ idempotent: รันสองครั้งทำให้ forecast ซ้ำหรือแก้ไขซ้ำ ทำให้ metric พองตัวหรือเสียหาย
- ×รายงานแค่ hit-rate รวมและซ่อนการแยกย่อยตาม regime ซึ่งเป็นที่ที่ข้อมูลที่นำไปปฏิบัติได้อาศัยอยู่
- ×version state dir ใน git ซึ่งเปิดประตูสู่การแก้ไขประวัติศาสตร์ด้วยมือและแต่งหน้า calibration จงเก็บมันไว้ใน gitignore
SG-04 ป้องกันการกุข้อมูลตั้งแต่การออกแบบ
บทเรียน ออกแบบระบบให้มีโครงสร้างที่ไม่สามารถปั้นข้อมูลหรือ edge ขึ้นมาได้: null controls, การวัดนัยสำคัญพร้อมการแก้ไขสำหรับการเปรียบเทียบหลายครั้ง, การประเมินแบบ out-of-sample และ edge_demonstrated ที่เป็น False เสมอโดยปริยาย
ป้องกันการกุข้อมูลตั้งแต่การออกแบบ
บทเรียนออกแบบระบบให้มีโครงสร้างที่ไม่สามารถปั้นข้อมูลหรือ edge ขึ้นมาได้: null controls, การวัดนัยสำคัญพร้อมการแก้ไขสำหรับการเปรียบเทียบหลายครั้ง, การประเมินแบบ out-of-sample และ edge_demonstrated ที่เป็น False เสมอโดยปริยาย
วิธีที่แพงที่สุดในการหลอกตัวเองใน research คือการสร้างตัวเลขที่ดูเหมือน edge แต่จริง ๆ เป็นสัญญาณรบกวน, drift หรือ data-snooping การต้านการปลอมแปลงไม่ใช่การตรวจสอบขั้นสุดท้าย แต่เป็นคุณสมบัติเชิงสถาปัตยกรรมที่ทำให้ forecaster ประกาศชัยชนะไม่ได้หากยังไม่เอาชนะความสุ่มและ drift ด้วยนัยสำคัญ
บทเรียน
คำถามที่นิยาม module นี้คือ: คุณจะสร้างระบบที่ 'ไม่สามารถ' โกหกคุณเรื่อง edge ได้อย่างไร? คำตอบของ XCAP ไม่ใช่ 'จงระมัดระวัง' แต่คือการแนะนำคู่ต่อสู้เชิงโครงสร้าง — null controls — ที่ทุกการอ้าง edge ต้องแข่งขันด้วย Hypothesis Lab (`hypothesis_lab.py`) มี registry ของ forecaster จริง (regime_baseline, trend_follow, momentum_follow, mean_revert) และ null control โดยเจตนาสองตัว: `always_up` (null ของ DRIFT — ตลาดขึ้นโดยเฉลี่ย forecaster ของคุณแค่จับสิ่งนั้นหรือเปล่า?) และ `coin_flip` (null ของความสุ่ม — seeded และ deterministic คุณเอาชนะการโยนเหรียญได้ไหม?) forecaster ที่เอาชนะ null ทั้งสองไม่ได้ ไม่มี edge มันมีแค่ภาพลวงตา
เสาหลักที่สองคือนัยสำคัญพร้อมการแก้ไขสำหรับการเปรียบเทียบหลายครั้ง หากคุณทดสอบ forecaster ยี่สิบตัว บางตัวจะดูดีด้วยความสุ่มล้วน ๆ — นั่นคือปัญหา data-snooping XCAP จัดการมันด้วย threshold z ที่แก้ไขด้วย Bonferroni ซึ่ง scale ตาม K (จำนวน forecaster ที่ประเมิน): ยิ่งคุณทดสอบ hypothesis มากเท่าไร มาตรฐานที่แต่ละตัวต้องผ่านก็ยิ่งสูง `evaluate_forecasters(universe, names, horizon_days, min_samples, alpha)` เปรียบเทียบ forecaster จริงแต่ละตัวกับ null ทั้งสอง (Δความสุ่มและΔdrift) และมอบ `candidate_edge` ก็ต่อเมื่อมันเอาชนะความสุ่มและ drift และผ่าน threshold ที่แก้ไขแล้ว และมีอย่างน้อย `min_samples` observation เงื่อนไขทั้งสี่ต้องเป็นจริงพร้อมกัน การเอาออกข้อใดข้อหนึ่งเปิดประตูสู่การปลอมแปลง
รายละเอียดที่พิสูจน์ว่าระบบซื่อสัตย์และไม่ถูกจัดฉาก: `edge_demonstrated` เป็น False เสมอ lab สามารถมอบ `candidate_edge` — 'สิ่งนี้ควรค่าแก่การวิจัยเพิ่ม' — แต่ไม่เคยประกาศ edge ที่พิสูจน์แล้ว เพราะไม่มี backtest ใด ไม่ว่าจะดีแค่ไหน ที่พิสูจน์ edge ในอนาคตได้ นี่คือการตัดสินใจในการออกแบบที่ปกป้องคุณจากความมองโลกในแง่ดีของตัวเอง: ระบบไม่มีเส้นทางโค้ดใดที่บอกว่า 'ใช่ คุณมี edge จง trade' เลย Capital Gate ยังปิดอยู่โดยโครงสร้าง ไม่ใช่ด้วยวินัยของคุณ
การตรวจสอบว่า scoreboard ไม่ถูกจัดฉากนั้นสง่างามและคุณต้องทำซ้ำ: XCAP ตรวจสอบบนแนวโน้มสังเคราะห์ว่า `trend_follow` ชนะแบบขาดลอย (ตรวจจับสัญญาณจริง), `mean_revert` ติดลบอย่างมีนัยสำคัญ (ตรวจจับ anti-edge — การเดิมพันสวนแนวโน้มย่อมขาดทุน) และ `coin_flip` ลงเอยที่ราว 50% (ความสุ่มไม่ได้รับรางวัล) หาก coin_flip ของคุณออกมาที่ hit-rate 60% pipeline ของคุณพังหรือมี leakage null ไม่ใช่แค่คู่ต่อสู้: มันคือ probe สำหรับ calibrate ระบบการวัดของคุณเอง coin_flip ที่ไม่ให้ราว 50% คือระบบที่กำลังโกหกตัวเอง และคุณต้องหยุดทุกอย่างจนกว่าจะเข้าใจว่าทำไม
รากฐานที่ใช้ร่วมกันป้องกันกลโกงการย้ายเสาประตู: `scoring.py` คือแหล่งความจริงเดียวสำหรับ primitive (prob_up, is_hit, brier, conf_bucket, hit_rate_z) ทั้ง Market Memory (SG-03) และ Hypothesis Lab ให้คะแนนด้วยโค้ดเดียวกัน หากคุณมี implementation ของ 'hit' สองตัว คุณก็สามารถ — รู้ตัวหรือไม่ก็ตาม — ใช้ตัวที่ใจกว้างกว่าให้กับ forecaster ตัวโปรดของคุณ primitive ที่ใช้ร่วมกันเพียงตัวเดียวปิดประตูนั้น นี่คือการต้านการปลอมแปลงโดยโครงสร้าง: คุณเลือกกฎที่เข้าทางคุณไม่ได้เพราะมีกฎเพียงข้อเดียว
ทุกการประเมินเป็น out-of-sample โดยกลไกเดียวกับ SG-03: แท่งเทียนแต่ละแท่งถูกจำแนกครั้งเดียว forecast ถูกสร้างโดยไม่เห็นอนาคต และถูกแก้ไขเทียบกับผลตอบแทน forward จริง doc `docs/HYPOTHESIS_LAB.md` และ invariant 4 ตัวของ `check` เข้ารหัสหลักประกันเหล่านี้ บทเรียนเชิงลึก ซึ่งเป็นทีสิสของทั้ง constellation Signal: ข้อจำกัดที่กำหนด (binding constraint) สำหรับการใช้ทุนจริงคือ edge ที่ 'พิสูจน์แล้ว' และเครื่องมือเหล่านี้วัด edge อย่างซื่อสัตย์ (null controls + นัยสำคัญ) แทนที่จะอ้างมัน ระบบที่สามารถออกได้แค่ `candidate_edge` และไม่เคย `edge_demonstrated` คือระบบที่เคารพในสิ่งที่ตัวเองไม่รู้
แบบฝึกหัด
จงขยาย Hypothesis Lab ของคุณด้วย `forecasters.py` (registry ที่มีอย่างน้อย `trend_follow`, `mean_revert` และ null สองตัว: `always_up` สำหรับ drift และ `coin_flip` แบบ seeded สำหรับความสุ่ม) และ `evaluate_forecasters(names, horizon_days, min_samples, alpha)` สำหรับ forecaster จริงแต่ละตัว คำนวณ hit-rate แบบ out-of-sample และ delta เทียบกับ null ทั้งสอง implement threshold z พร้อมการแก้ไข Bonferroni ที่ scale ตาม K = จำนวน forecaster มอบ `candidate_edge=True` ก็ต่อเมื่อชนะความสุ่มและ drift และผ่าน threshold และมี >= min_samples ตั้ง `edge_demonstrated=False` เสมอ จง validate pipeline ของคุณบนอนุกรมสังเคราะห์ที่มีแนวโน้ม: `trend_follow` ต้องออกมาบวกอย่างมีนัยสำคัญ, `mean_revert` ลบอย่างมีนัยสำคัญ และ `coin_flip` ต้องลงเอยที่ราว 50% (ถ้าไม่ การวัดของคุณมี leakage)
สิ่งที่ส่งมอบ
`forecasters.py` + `hypothesis_lab.py` ที่มี null control สองตัว (ความสุ่มและ drift), threshold z แบบ Bonferroni ที่ scale ตาม K, gate สี่เงื่อนไขสำหรับ `candidate_edge`, `edge_demonstrated` เป็น False เสมอ และการ validate ที่ coin_flip ออกมาราว 50% เพื่อพิสูจน์ว่า scoreboard ไม่ถูกจัดฉาก
ข้อคิดสำคัญ
null controls ไม่ใช่แค่คู่ต่อสู้ที่คุณแข่งขันด้วย — มันคือ probe สำหรับ calibrate ระบบการวัดของคุณเอง หาก coin_flip ของคุณไม่ออกมาราว 50% คุณมี leakage หรือ bug และคุณต้องหยุดทุกอย่างจนกว่าจะเข้าใจว่าทำไมก่อนที่จะเชื่อตัวเลขอื่นใด
ข้อผิดพลาดที่ควรเลี่ยง
- ×เปรียบเทียบ forecaster กับความสุ่มเพียงอย่างเดียวและลืม drift 'edge' หลายตัวเป็นเพียงอคติขาขึ้นพื้นฐานของตลาดในคราบปลอมตัว
- ×ไม่แก้ไขสำหรับการเปรียบเทียบหลายครั้ง: ทดสอบ forecaster 20 ตัวแล้วหนึ่งตัวจะดูมีนัยสำคัญด้วยความสุ่มล้วน ๆ (data-snooping)
- ×อนุญาตให้มีเส้นทางโค้ดที่ประกาศ `edge_demonstrated=True` ไม่มี backtest ใดพิสูจน์ edge ในอนาคต และ flag นั้นชวนให้เปิด Capital Gate ก่อนเวลาอันควร
- ×มี implementation ของ 'hit' หรือ 'brier' สองตัวแทนที่จะเป็น primitive ที่ใช้ร่วมกันเพียงตัวเดียว มันทำให้คุณเลือกกฎที่ใจกว้างกว่าให้ forecaster ตัวโปรดของคุณ
- ×เพิกเฉยว่า coin_flip ออกมา 60%: นั่นไม่ใช่โชค แต่คือ leakage ใน pipeline ของคุณ และผลลัพธ์อื่นใดจาก lab ก็เป็นขยะจนกว่าจะแก้ไขมัน
SG-05 Capital invariant
บทเรียน implement capital invariant: ledger ที่ทุนสะสมข้ามเซสชัน เคลื่อนที่เฉพาะเมื่อมีการปิดสถานะจริง (P&L ที่เกิดขึ้นจริง) และไม่เคย reset — เพื่อให้ตัวเลขบนหน้าจอสะท้อนผลลัพธ์ที่ปิดแล้วเสมอ ไม่ใช่ replay หรือกำไรบนกระดาษ
Capital invariant
บทเรียนimplement capital invariant: ledger ที่ทุนสะสมข้ามเซสชัน เคลื่อนที่เฉพาะเมื่อมีการปิดสถานะจริง (P&L ที่เกิดขึ้นจริง) และไม่เคย reset — เพื่อให้ตัวเลขบนหน้าจอสะท้อนผลลัพธ์ที่ปิดแล้วเสมอ ไม่ใช่ replay หรือกำไรบนกระดาษ
ยอดคงเหลือที่พองด้วย P&L ที่ยังไม่เกิดขึ้นจริงหรือที่ reset ระหว่างรอบการรัน คือยอดคงเหลือที่โกหก ความถูกต้องของทุนคือเงื่อนไขที่หากปราศจากมัน ก็ไม่มีตัวเลขอื่นใดในระบบที่ควรค่าแก่ความเชื่อมั่น: หากการบัญชีโกหกได้ ทุกอย่างที่เหลือก็เช่นกัน
บทเรียน
module นี้ถือกำเนิดจากความผิดพลาดจริงและเจ็บปวดใน XCAP และนั่นคือเหตุผลที่มันสอนได้ดีนัก autorun ที่ตั้งเวลาไว้กำลัง reset ledger กลับไปที่ยอดเริ่มต้น 500 อย่างเงียบ ๆ และเล่นซ้ำ fixture เดิมที่เหมือนกันทุก 5 นาที ระบบแสดงกิจกรรม ตัวเลขเปลี่ยนแปลง ดูเหมือนมีชีวิต — แต่เป็นเท็จโดยสิ้นเชิง กฎสามข้อถูกละเมิดพร้อมกัน และเป็นสามข้อที่นิยาม capital invariant จงจำกฎเหล่านี้เพราะมันคือแก่นแกน: สะสม, เฉพาะที่เกิดขึ้นจริง (realized-only) และการเข้าใหม่ (re-entry) สานต่อเซสชันก่อนหน้า
กฎข้อ 1 — สะสม ทุนไม่ reset ระหว่างรอบการรันหรือเซสชัน หากคุณจบเซสชันเมื่อวานที่ 547.30 วันนี้คุณเริ่มที่ 547.30 baseline ใหม่ทุกการเริ่มต้นไม่ใช่การบัญชี แต่คือ demo ในคราบปลอมตัว ledger ถูกโหลดจากดิสก์เมื่อเริ่ม และถูก persist เมื่อจบ สถานะดำรงอยู่ระหว่างการ execute
กฎข้อ 2 — เฉพาะที่เกิดขึ้นจริง (realized-only) ยอดคงเหลือเคลื่อนที่ก็ต่อเมื่อสถานะถูกปิด/ขายเท่านั้น P&L ที่ยังไม่เกิดขึ้นจริง (mark-to-market มูลค่าลอยตัวของสถานะที่เปิดอยู่) ไม่เคยทำให้ยอดคงเหลือพอง invariant ที่แม่นยำ ซึ่งคุณต้องสามารถยืนยันได้ทุกเมื่อ: `current_balance == starting_balance + realized_pnl` สถานะที่เปิดอยู่พร้อมกำไรลอยตัว +200 ไม่เปลี่ยนยอดคงเหลือ มันเปลี่ยนก็ต่อเมื่อคุณปิดมันและ +200 นั้นกลายเป็น realized สิ่งนี้ป้องกันกับดักทางจิตวิทยาที่เก่าแก่ที่สุดในการซื้อขาย: การนับกำไรก่อนเก็บเข้ากระเป๋า
กฎข้อ 3 — การเข้าใหม่สานต่อ เมื่อ restart ทุนคือผลลัพธ์สะสมของการซื้อขายในเซสชันก่อนหน้า ไม่ใช่เส้นฐานใหม่ ระบบต้องสามารถปิดและเปิดได้พันครั้งและทุนยังคงเป็นผลรวมที่ซื่อสัตย์ของการปิดสถานะจริงทั้งหมดที่เกิดขึ้น นี่คือสิ่งเดียวกับคุณสมบัติการสะสมของ Market Memory (SG-03): สถานะคือผลรวมของเหตุการณ์จริง ไปในทิศทางเดียว ไม่มีการ replay
บทเรียนเชิงสถาปัตยกรรมคือความแตกต่างระหว่างสองคำสั่งที่ XCAP จงใจแยกออกจากกัน autorun ที่ตั้งเวลาไว้รัน `sim-autopilot-tick`: โหลด ledger ที่มีอยู่ หมุน regime สังเคราะห์ และสะสมทับสิ่งก่อนหน้า ยังมี `sim-run-agent` ด้วย ซึ่ง reset และเล่นซ้ำ fixture — แต่ตัวนั้นเก็บไว้เฉพาะสำหรับ demo แบบ deterministic เท่านั้น และไม่เคยควรเป็นสิ่งที่ autorun รัน bug คือการชี้ scheduler ไปยังคำสั่งที่ผิดพอดี บทเรียน: คำสั่งที่ reset กับคำสั่งที่สะสมไม่ควรสับสนกันได้เลย จงตั้งชื่อให้ตัวที่อันตรายตะโกนบอกสิ่งที่มันทำ
วิธีหุ้มเกราะสิ่งนี้ไม่ให้พังอีก: invariant ถูก LOCKED โดย `tests/test_sim_autopilot_tick.py` หากคุณแตะ ledger หรือ autorun test เหล่านั้นต้องยังเขียวอยู่ มันไม่ใช่ document แต่คือกุญแจที่ execute ได้ กฎเชิงปฏิบัติที่ผู้ใช้กำหนดอย่างหนักแน่น: หากคุณแตะการบัญชี คุณต้องรัน test นั้น และหากมันกลายเป็นสีแดง คุณได้ทำลายความถูกต้องของทุน และคุณต้องหยุด นี่คือปรัชญาเดียวกับ Capital Gate และ `check` ของ SG-01: invariant ที่สำคัญจริง ๆ ถูกเข้ารหัสเป็น test ที่ปฏิเสธจะให้คุณทำลายมัน การบัญชีของ XCAP ไม่ได้น่าเชื่อถือเพราะผู้เขียนระมัดระวัง แต่น่าเชื่อถือเพราะ test สีแดงห้ามไม่ให้เขาประมาท
แบบฝึกหัด
จง implement `ledger.py` ด้วยยอดคงเหลือที่ persist ใน `state/ledger.json` ฟังก์ชัน: `load()` (อ่านยอดสะสม หรือ starting_balance เฉพาะการเริ่มต้นครั้งแรกสุดเท่านั้น), `open_position(symbol, entry, size)`, `mark(symbol, price)` (คำนวณ P&L ที่ยังไม่เกิดขึ้นจริงแต่ไม่แตะยอดคงเหลือ) และ `close_position(symbol, exit)` (realize P&L และเฉพาะตอนนั้นจึงปรับยอดคงเหลือ แล้ว persist) จงรักษา invariant `current_balance == starting_balance + sum(realized_pnl)` และยืนยันมันหลังทุกการดำเนินการ เขียน `test_capital_invariant.py` ที่: (1) เปิดสถานะที่กำไร เรียก `mark` ด้วยราคาที่สูงกว่า และตรวจสอบว่ายอดคงเหลือไม่เปลี่ยน (2) ปิดมันและตรวจสอบว่าตอนนี้มันขึ้นพอดีตามค่า P&L ที่ realize แล้ว (3) จำลองการ restart (load หลัง persist) และตรวจสอบว่ายอดคงเหลือสานต่อ ไม่ reset
สิ่งที่ส่งมอบ
`ledger.py` ที่มียอดคงเหลือสะสมที่ persist + `test_capital_invariant.py` ที่ทดสอบกฎสามข้อ: mark ไม่ขยับยอดคงเหลือ, close ขยับมันด้วยค่า P&L ที่ realize แล้วอย่างแม่นยำ และการ restart สานต่อยอดสะสมแทนที่จะ reset
ข้อคิดสำคัญ
invariant `current_balance == starting_balance + realized_pnl` คือบรรทัดเดียวที่เมื่อยืนยันหลังทุกการดำเนินการ จะทำให้เป็นไปไม่ได้เชิงโครงสร้างที่ P&L บนกระดาษหรือการ replay จะพองยอดคงเหลือของคุณ เมื่อการบัญชีเป็นสมการที่ตรวจสอบได้ ไม่ใช่ความเชื่อ มันก็เลิกโกหกคุณได้
ข้อผิดพลาดที่ควรเลี่ยง
- ×reset ledger กลับไปที่ starting_balance ในทุกการเริ่ม autorun — bug ดั้งเดิมของ XCAP มันแสดงกิจกรรมปลอมและลบประวัติศาสตร์จริงทั้งหมด
- ×ปล่อยให้ P&L ที่ยังไม่เกิดขึ้นจริง (mark-to-market) แตะยอดคงเหลือ คุณนับกำไรที่ยังไม่ได้เก็บเข้ากระเป๋า และตัวเลขบนหน้าจอก็โกหก
- ×สับสนระหว่างคำสั่งที่สะสม (`sim-autopilot-tick`) กับตัวที่ reset-แล้ว-เล่นซ้ำ (`sim-run-agent`) จงตั้งชื่อตัวที่อันตรายเพื่อให้เป็นไปไม่ได้ที่จะชี้ scheduler ไปหามันโดยผิดพลาด
- ×แตะ ledger หรือ autorun โดยไม่รัน `test_capital_invariant.py` หลังจากนั้น invariant จะถูกกัดกร่อนตั้งแต่การ refactor ครั้งแรกที่ไม่มีใครตรวจสอบ
- ×ปฏิบัติต่อ invariant เป็น comment ในโค้ดแทนที่จะเป็น assertion ที่ execute หลังทุกการดำเนินการ comment ไม่ป้องกันคุณจากการทำลายมัน แต่ assert ป้องกัน
SG-06 Autopilot ticks และ loop ที่ซื่อตรง
บทเรียน สร้าง autopilot loop ที่สะสมการเรียนรู้ที่แท้จริงระหว่าง tick — deterministic จากดิสก์, idempotent, Gate-ปิด — แทนที่จะสร้างสัญญาณรบกวนหรือกิจกรรมปลอม
Autopilot ticks และ loop ที่ซื่อตรง
บทเรียนสร้าง autopilot loop ที่สะสมการเรียนรู้ที่แท้จริงระหว่าง tick — deterministic จากดิสก์, idempotent, Gate-ปิด — แทนที่จะสร้างสัญญาณรบกวนหรือกิจกรรมปลอม
loop อัตโนมัติที่ออกแบบไม่ดีนั้นอันตราย: มันรันเอง โดยไม่มีใครเฝ้าดู และสามารถ reset สถานะ, ทำซ้ำบันทึก หรือสั่งการกระทำจริงได้ ความแตกต่างระหว่าง loop ที่เรียนรู้กับ loop ที่แค่เคลื่อนไหวคือความแตกต่างทั้งหมดระหว่าง research กับละครอัตโนมัติ
บทเรียน
autopilot tick คือโค้ดที่รันโดยไม่มีมนุษย์เฝ้าดู และนั่นคือเหตุผลพอดีที่มันต้องเป็นโค้ดที่มีวินัยมากที่สุดในระบบ ความผิดพลาดที่ XCAP ประสบกับตัวเองสอนทุกอย่าง: tick ที่ตั้งเวลาไว้ซึ่งทุก 5 นาทีจะ reset ledger และเล่นซ้ำ fixture ที่เหมือนกัน มองจากภายนอกระบบดูเหมือนมีชีวิต — ตัวเลขเคลื่อนไหว กิจกรรมต่อเนื่อง แต่ภายในมันคือคำโกหกใน loop คำถามที่นิยาม tick ที่ดี: loop นี้ เมื่อรันพันครั้ง 'สะสม' อะไรที่แท้จริง หรือแค่สร้างภาพลักษณ์ของกิจกรรม?
คุณสมบัติ 1 — deterministic จากดิสก์ tick อ่าน input ของมันจากดิสก์ (CSV `data/real` ผ่าน `LocalCSVSource`) ไม่ใช่จากเครือข่าย loop อัตโนมัติของ XCAP ไม่เคยเปิดเครือข่าย — `allow_network` คงเป็น False เสมอ นี่คือความต่อเนื่องโดยตรงจาก SG-01: เครือข่ายเป็น opt-in อย่างชัดเจนต่อการเรียกของมนุษย์ (คำสั่ง `data-fetch`) ไม่เคยเป็นสิ่งที่ loop อัตโนมัติเปิดเอง tick ที่แตะเครือข่ายโดยไม่มีการดูแลคือ tick ที่สามารถรั่ว, fail แบบไม่สามารถทำซ้ำได้ หรือค้างรอ socket ได้ deterministic จากดิสก์หมายความว่าสถานะเดียวกันบนดิสก์ให้ผลลัพธ์เดียวกัน กล้าหาญและทำซ้ำได้
คุณสมบัติ 2 — idempotent การรัน tick สองครั้งบนวันที่เดียวกันต้องไม่ทำให้ forecast ซ้ำ ไม่แก้ไขสิ่งที่แก้ไขแล้วซ้ำ และไม่นับการปิดสถานะสองครั้ง สิ่งนี้สำคัญมากใน loop เพราะ scheduler fail, retry, ทับซ้อน หาก tick ของคุณไม่ idempotent การ retry จะทำให้สถานะเสียหายอย่างเงียบ ๆ วิธีทำให้สำเร็จ: ทุกเหตุการณ์ (forecast หนึ่งตัว, การปิดสถานะหนึ่งครั้ง) มี key ธรรมชาติ (วันที่+สัญลักษณ์) และ tick ตรวจสอบการมีอยู่ก่อนเขียน `advance_market_memory` และ `sim-autopilot-tick` เป็น idempotent ด้วยเหตุผลนี้
คุณสมบัติ 3 — สะสม ไม่ reset tick โหลดสถานะก่อนหน้าและสร้าง 'ทับ' มัน `sim-autopilot-tick` โหลด ledger หมุน regime สังเคราะห์ และสะสม — เป็นกฎเดียวกันกับ capital invariant ของ SG-05 และ Market Memory ของ SG-03 พอดี นี่คือ invariant เดียวกันที่ปรากฏเป็นครั้งที่สามใน constellation และไม่ใช่เรื่องบังเอิญ: ความซื่อสัตย์ของระบบอัตโนมัติ 'คือ' การปฏิเสธที่จะ reset ตัวเอง loop ที่เริ่มจากศูนย์ทุกครั้งไม่เรียนรู้อะไร loop ที่สะสมคือสิ่งเดียวที่สามารถสร้างประวัติ calibration ที่มีค่าได้
คุณสมบัติ 4 — Gate-ปิดโดยโครงสร้าง tick ไม่เคยซื้อขายทุนจริง Capital Gate (`capital/broker/live_trading/automation` เป็น False ทั้งหมด) ถูกตรวจสอบโดย `python3 -m xcap.control check` autopilot คือจุดที่ระบบ trading ฆ่าเจ้าของมันพอดี: มันรันเอง และหากมันมีความสามารถซื้อขายจริง bug ตอนตีสามก็ทำให้บัญชีหมดเกลี้ยง ด้วยเหตุนี้ใน XCAP autopilot จึงเป็น sim-only โดยไม่มีข้อยกเว้น และ `check` ยืนยันมันในทุกการรัน ระบบอัตโนมัติและความสามารถซื้อขายจริงคือสองสิ่งที่ไม่เคยควรอยู่ร่วมกันได้โดยปราศจากการตัดสินใจของมนุษย์ที่จงใจอย่างที่สุด
วิธีดำเนินการและหุ้มเกราะทั้งหมดนี้: tick รันผ่าน scheduler (ใน XCAP คือ task ที่ตั้งเวลาไว้ซึ่งเรียก `sim-autopilot-tick` คำสั่งที่สะสม — ไม่เคย `sim-run-agent` ตัวที่ reset สำหรับ demo) พฤติกรรมถูก LOCKED โดย `tests/test_sim_autopilot_tick.py`: หากคุณแตะ autorun test เหล่านั้นยังเขียวอยู่ มิฉะนั้นคุณได้ทำลายความถูกต้องของ loop และทุกการรันจบด้วย `check` ที่ยืนยัน invariant อีกครั้ง (keyless, Gate-ปิด, การสะสม) หลักการสุดท้ายที่ผนึก constellation ทั้งหมด: loop ที่ซื่อสัตย์คือ loop ที่หลักประกันไม่ขึ้นอยู่กับว่ามีใครเฝ้าดู เพราะมันถูกเข้ารหัสเป็น test และ invariant ที่ loop เองตรวจสอบในทุกรอบ จงสร้าง loop ที่ปฏิเสธจะโกหกแม้ในยามที่ไม่มีใครมองดู — นั่นคือระบบอัตโนมัติเดียวที่ระบบ research ไว้ใจได้
แบบฝึกหัด
จงสร้าง `autopilot_tick.py` ด้วยฟังก์ชัน `tick(state_dir)` ที่ในแต่ละการเรียก: (1) โหลดสถานะก่อนหน้าจากดิสก์ (ledger + ledger ของ forecast), (2) อ่านแท่งเทียนใหม่ผ่าน `LocalCSVSource` พร้อม `allow_network=False`, (3) advance Market Memory (ล็อก forecast ใหม่, แก้ไขตัวที่ครบกำหนด) แบบสะสม, (4) รัน `check()` ยืนยัน Gate-ปิดและ keyless, (5) persist จงทำให้ idempotent: รัน `tick` สองครั้งติดกันบนสถานะเดียวกันและตรวจสอบว่าครั้งที่สองไม่เพิ่มหรือเปลี่ยนแปลงอะไร เขียน `test_autopilot_tick.py` ที่ทดสอบ: idempotency (tick ซ้ำ = สถานะเหมือนเดิม), การสะสม (tick สองครั้งบนช่วงต่างกันทำให้สถานะเติบโต) และว่า `check` fail หากมีใครตั้ง `allow_network=True` หรือเปิด Gate จงตั้งเวลา tick ด้วย task แบบเกิดซ้ำที่เรียกเฉพาะคำสั่งที่สะสมเท่านั้น
สิ่งที่ส่งมอบ
`autopilot_tick.py` (deterministic จากดิสก์, idempotent, สะสม, Gate-ปิด) + `test_autopilot_tick.py` ที่ lock idempotency และการสะสม และ scheduler ที่ชี้ไปยังคำสั่งที่สะสม — ไม่เคยชี้ไปยังตัวที่ reset
ข้อคิดสำคัญ
invariant การสะสม (ไม่ reset) ปรากฏสามครั้งใน Signal — ทุน, Market Memory และ autopilot — และไม่ใช่เรื่องบังเอิญ: ความซื่อสัตย์ของระบบอัตโนมัติคือการปฏิเสธที่จะ reset ตัวเอง loop ที่เริ่มจากศูนย์ทุกรอบไม่เรียนรู้ มันแค่แสร้งว่ามีชีวิต
ข้อผิดพลาดที่ควรเลี่ยง
- ×ชี้ scheduler ไปยังคำสั่งที่ reset-แล้ว-เล่นซ้ำ (`sim-run-agent`) แทนที่จะเป็นตัวที่สะสม (`sim-autopilot-tick`) — bug ที่ปลอมแปลงกิจกรรมของ XCAP พอดี
- ×ปล่อยให้ loop เปิดเครือข่าย (`allow_network=True`) tick อัตโนมัติที่แตะเครือข่ายสามารถรั่ว, ค้าง หรือ fail แบบไม่สามารถทำซ้ำได้โดยไม่มีใครเฝ้าดู
- ×ทำให้ tick ไม่ idempotent: เมื่อ scheduler retry หรือทับซ้อน มันจะทำให้ forecast ซ้ำและทำให้สถานะเสียหายอย่างเงียบ ๆ
- ×ให้ autopilot มีความสามารถซื้อขายจริงใด ๆ โดยปราศจากการตัดสินใจของมนุษย์ที่จงใจ ระบบอัตโนมัติ + ความสามารถ trade จริงคือวิธีที่ loop ฆ่าบัญชีตอนตีสาม
- ×พึ่งพาการเฝ้าดูของมนุษย์แทนที่จะเข้ารหัสหลักประกันเป็น test และ invariant ที่ loop เองตรวจสอบด้วย `check` ในทุกรอบ
กลุ่มดาวถัดไป
Brand & Surface
แบรนด์และพื้นผิว