กลไก Circuit breaker สำหรับบอทเทรดและการบริหารความเสี่ยง
เรียนรู้การใช้กลไก Circuit breaker เพื่อหยุดการทำงานของบอทเทรดก่อนเกิดความเสียหายสะสม พร้อมทำความเข้าใจความถี่ในการตัดขาดทุนรายวันและการปรับขนาดสถานะตามความผันผวนของตลาดอย่างเป็นระบบ
กลไก Circuit breaker สำหรับบอทเทรด
กลไก Circuit breaker สำหรับบอทเทรดคือชุดกฎเกณฑ์ที่ใช้หยุดกลยุทธ์อัตโนมัติไม่ให้ส่งคำสั่งซื้อขายเมื่อถึงขีดจำกัดที่กำหนดไว้ กลไกนี้ทำหน้าที่เป็นชั้นการบริหารความเสี่ยงที่คั่นระหว่างกลยุทธ์กับโบรกเกอร์ โดยจะตรวจสอบทุกคำสั่งซื้อขายไม่ว่ากลยุทธ์นั้นจะเห็นด้วยหรือไม่ก็ตาม กลยุทธ์จะเป็นผู้ตัดสินใจว่าจะเทรดอะไร แต่ชั้นการบริหารความเสี่ยงจะเป็นผู้ตัดสินใจว่าจะอนุญาตให้มีการเทรดเกิดขึ้นหรือไม่
การแยกส่วนหน้าที่เช่นนี้คือหัวใจสำคัญของการออกแบบ กลยุทธ์ที่ตรวจสอบตนเองจะไม่มีการตรวจสอบที่เป็นอิสระในจังหวะที่สมมติฐานของกลยุทธ์นั้นผิดพลาด ซึ่งเป็นช่วงเวลาที่การตรวจสอบมีความสำคัญที่สุด
หน้าที่ของ Circuit breaker ในบอทเทรด
ชั้นการบริหารความเสี่ยงประกอบด้วยสี่ส่วน โดยแต่ละส่วนมีไว้เพื่อป้องกันความผิดพลาดที่เฉพาะเจาะจงและน่าเบื่อหน่าย
- Hard caps สำหรับขนาดสถานะ มูลค่าการถือครองต่อสัญลักษณ์ และอัตราการส่งคำสั่ง ซึ่งช่วยจำกัดความเสียหายจากบั๊กที่อาจส่งผลกระทบในวงกว้างหากไม่มีการควบคุม
- Drawdown circuit breaker ที่จะหยุดการส่งคำสั่งซื้อขายใหม่ทันทีเมื่อพอร์ตการลงทุนขาดทุนถึงระดับที่กำหนดในรอบการซื้อขายนั้น หรือขาดทุนถึงระดับที่กำหนดจากจุดสูงสุดของมูลค่าสินทรัพย์สุทธิ (Equity peak)
- Scaled order sizing ซึ่งกำหนดขนาดคำสั่งตามความผันผวนล่าสุดหรือตามสัดส่วนของ Kelly bet แทนการใช้จำนวนหุ้นคงที่ เพื่อรักษาความเสี่ยงต่อการเทรดให้คงที่ในขณะที่ช่วงราคาของตลาดเปลี่ยนแปลงไป
- Append-only audit log ที่บันทึกทุกการตัดสินใจ รวมถึงคำสั่งที่ชั้นการบริหารความเสี่ยงปฏิเสธไป ซึ่งเป็นหลักฐานเพียงอย่างเดียวที่ใช้แยกแยะระหว่าง "กลยุทธ์ที่ผิดพลาด" กับ "การตรวจสอบที่ไม่เคยทำงาน"
เนื้อหาทั้งหมดด้านล่างนี้คือรายละเอียดที่เป็นรูปธรรมของทั้งสี่ส่วนดังกล่าว
ขีดจำกัดการขาดทุนรายวันที่เหมาะสมสำหรับบอทเทรดคือเท่าใด
ขีดจำกัดการขาดทุนรายวันจะหยุดการส่งคำสั่งซื้อขายใหม่เมื่อผลขาดทุนของรอบการซื้อขายเกินเกณฑ์ที่กำหนด การกำหนดตัวเลขนี้คือการปรับจูน ไม่ใช่เรื่องของความชอบส่วนบุคคล หากตั้งไว้ภายในระดับความผันผวนปกติของตลาด บอทจะถูกหยุดทำงานเกือบทุกสัปดาห์ แต่หากตั้งไว้สูงเกินไป บอทก็จะไม่เคยหยุดทำงานเลย จุดเริ่มต้นในการพิจารณาคือความถี่ที่ตลาดปรับตัวลดลงในระดับที่กำหนดในแต่ละวัน
SQL เบื้องหลังตัวเลขแต่ละตัว
WITH daily AS (
SELECT toDate(toTimeZone(window_start, 'America/New_York')) AS d,
argMax(toFloat64(close), window_start) AS close_px
FROM global_markets.delayed_stocks_minute_aggs
WHERE ticker = 'SPY'
AND toDate(toTimeZone(window_start, 'America/New_York')) >= toDate('2017-12-01')
AND toDate(toTimeZone(window_start, 'America/New_York')) <= toDate('2026-07-31')
AND (toHour(toTimeZone(window_start, 'America/New_York')) * 60
+ toMinute(toTimeZone(window_start, 'America/New_York'))) BETWEEN 570 AND 959
GROUP BY d
),
with_prev AS (
SELECT d,
close_px,
any(close_px) OVER (ORDER BY d ASC ROWS BETWEEN 1 PRECEDING AND 1 PRECEDING) AS prev_close
FROM daily
)
SELECT toYear(d) AS year,
countIf(close_px / prev_close - 1 <= -0.01) AS down_1pct_days,
countIf(close_px / prev_close - 1 <= -0.02) AS down_2pct_days,
countIf(close_px / prev_close - 1 <= -0.03) AS down_3pct_days
FROM with_prev
WHERE prev_close > 0
AND d >= toDate('2018-01-01')
GROUP BY year
ORDER BY yearจำนวนรอบการซื้อขายที่ดัชนีปรับตัวลดลงหนึ่งเปอร์เซ็นต์หรือมากกว่านั้นมีจำนวน 15 ในช่วง 2019 และ 45 ในช่วง 2020 จากจำนวนวันซื้อขายประมาณสองร้อยห้าสิบวันต่อปี ส่วนจำนวนวันที่ลดลงสามเปอร์เซ็นต์หรือมากกว่านั้นเป็นอีกกรณีหนึ่ง คือ 0 ในช่วง 2019 เทียบกับ 16 ในช่วง 2020 แถวสุดท้ายของ 9 ครอบคลุมรอบการซื้อขายจนถึงวันที่ 31 กรกฎาคม 2026 เท่านั้น
เส้นกราฟมีความไม่สม่ำเสมอมากกว่าที่จะราบเรียบ และความไม่สม่ำเสมอนี้คือจุดสำคัญในการออกแบบ วันที่ตลาดปรับตัวลงหนักมักเกิดขึ้นเป็นกลุ่มก้อน บอทที่หยุดทำงานในวันแรกของกลุ่มก้อนนั้นแล้วกลับมาทำงานต่อในวันที่สอง ถือว่าไม่ได้หยุดทำงานอย่างแท้จริง
ขีดจำกัดสองประเภททำหน้าที่ต่างกัน ขีดจำกัดการขาดทุนรายวันซึ่งมักกำหนดไว้ที่สองเปอร์เซ็นต์ของมูลค่าพอร์ต (account equity) จะยุติการซื้อขายในรอบนั้น ส่วนขีดจำกัดการขาดทุนสะสม (trailing drawdown limit) ที่วัดจากจุดสูงสุดของมูลค่าพอร์ต ซึ่งมักอยู่ที่ประมาณสิบเปอร์เซ็นต์ จะยุติกลยุทธ์นั้นไว้ก่อนเพื่อรอการตรวจสอบจากมนุษย์ ขีดจำกัดแบบแรกเป็นเรื่องปกติ ส่วนแบบที่สองควรเกิดขึ้นได้ยาก และบอทที่มีเพียงขีดจำกัดแบบแรกอาจทำให้พอร์ตขาดทุนสะสมไปเรื่อย ๆ ทีละสองเปอร์เซ็นต์โดยไม่เคยถูกหยุดทำงานเลย
ความผันผวนส่งผลต่อการปรับขนาดสถานะอย่างไร
การกำหนดขนาดสถานะตามความผันผวน (Volatility targeting) จะปรับขนาดสถานะให้แปรผกผันกับค่าความผันผวนที่เกิดขึ้นจริงในช่วงที่ผ่านมา เมื่อกรอบราคาต่อวันกว้างขึ้นเป็นสองเท่า ขนาดสถานะจะลดลงเหลือประมาณครึ่งหนึ่ง เพื่อรักษาความเสี่ยงในรูปตัวเงินต่อการเทรดหนึ่งครั้งให้คงที่ ค่าความผันผวนที่เกิดขึ้นจริงในที่นี้คือค่าเบี่ยงเบนมาตรฐานของผลตอบแทนรายวันที่ปรับเป็นรายปี ซึ่งมีความผันผวนมากกว่าที่คนส่วนใหญ่คาดการณ์ไว้
SQL เบื้องหลังตัวเลขแต่ละตัว
WITH daily AS (
SELECT toDate(toTimeZone(window_start, 'America/New_York')) AS d,
argMax(toFloat64(close), window_start) AS close_px
FROM global_markets.delayed_stocks_minute_aggs
WHERE ticker = 'SPY'
AND toDate(toTimeZone(window_start, 'America/New_York')) >= toDate('2023-12-01')
AND toDate(toTimeZone(window_start, 'America/New_York')) <= toDate('2026-07-31')
AND (toHour(toTimeZone(window_start, 'America/New_York')) * 60
+ toMinute(toTimeZone(window_start, 'America/New_York'))) BETWEEN 570 AND 959
GROUP BY d
),
rets AS (
SELECT d,
close_px / any(close_px) OVER (ORDER BY d ASC ROWS BETWEEN 1 PRECEDING AND 1 PRECEDING) - 1 AS ret
FROM daily
)
SELECT formatDateTime(toStartOfMonth(d), '%Y-%m') AS month,
round(stddevSamp(ret) * sqrt(252) * 100, 1) AS realized_vol_pct,
round(least(100.0, 1200.0 / (stddevSamp(ret) * sqrt(252) * 100)), 1) AS vol_target_size_pct
FROM rets
WHERE d >= toDate('2024-01-01')
AND isFinite(ret)
GROUP BY toStartOfMonth(d)
HAVING count() >= 15
ORDER BY toStartOfMonth(d)ค่าความผันผวนที่เกิดขึ้นจริงวัดได้ 11.1% แบบปรับเป็นรายปีใน 2024-01 และ 12% ใน 2026-07 ตลอดระยะเวลา 31 เดือน คอลัมน์ที่สองแปลงค่าที่อ่านได้แต่ละค่าให้เป็นขนาดสถานะที่เป้าหมายความผันผวนระดับ 12% จะรองรับได้ โดยจำกัดไว้ที่เส้นเต็ม: 100% ใน 2024-01 เทียบกับ 99.7% ใน 2026-07 กลยุทธ์เดียวกัน ความเชื่อมั่นเท่าเดิม แต่จำนวนหุ้นที่ถือครองแตกต่างกันอย่างมาก
หลักการ Kelly criterion เข้าถึงปัญหาเดียวกันจากอีกด้านหนึ่ง โดยกำหนดขนาดสถานะจากส่วนต่างกำไร (edge) และความแปรปรวนที่คาดการณ์ไว้ แทนที่จะพิจารณาจากความผันผวนเพียงอย่างเดียว ผู้ดำเนินการเชิงระบบส่วนใหญ่ใช้เพียงเศษส่วนของหลักการนี้ เช่น ครึ่งหนึ่งหรือหนึ่งในสี่ของ Kelly เนื่องจากข้อมูลนำเข้าทั้งสองตัวเป็นเพียงการประมาณการจากกลุ่มตัวอย่างที่จำกัด การกำหนดขนาดสถานะด้วย Kelly criterion อธิบายวิธีการคำนวณดังกล่าวไว้
เหตุใดบอทเทรดจึงเข้าสถานะซ้ำหลังจากถูก Stop?
เมื่อคำสั่ง Stop ทำงาน สถานะจะถูกปิดลง เก้าสิบวินาทีต่อมาเงื่อนไขการเข้าสถานะกลับมาเป็นจริงอีกครั้ง บอทจึงเข้าสถานะเดิม รับผลขาดทุนเดิม และทำซ้ำเช่นนั้นไปเรื่อยๆ ไม่มีส่วนประกอบใดที่ทำงานผิดปกติ กลยุทธ์ทำงานตามที่เขียนไว้ คำสั่ง Stop ทำงานตามที่เขียนไว้ แต่พอร์ตการลงทุนยังคงสูญเสียเงินไปทีละรอบการเทรด
ความถี่ในการเกิดเหตุการณ์นี้มาจากเส้นทางราคาโดยตรง แผงข้อมูลนี้จะนับจำนวนครั้งต่อเซสชันที่ SPY เคลื่อนที่จากระดับที่สูงกว่าราคาเปิดเกินร้อยละ 0.1 ไปสู่ระดับที่ต่ำกว่าราคาเปิดเกินร้อยละ 0.1 หรือเคลื่อนที่กลับไปมา
SQL เบื้องหลังตัวเลขแต่ละตัว
WITH mins AS (
SELECT toDate(toTimeZone(window_start, 'America/New_York')) AS d,
window_start AS ts,
toFloat64(close) AS px
FROM global_markets.delayed_stocks_minute_aggs
WHERE ticker = 'SPY'
AND toDate(toTimeZone(window_start, 'America/New_York')) >= toDate('2025-08-01')
AND toDate(toTimeZone(window_start, 'America/New_York')) <= toDate('2026-07-31')
AND (toHour(toTimeZone(window_start, 'America/New_York')) * 60
+ toMinute(toTimeZone(window_start, 'America/New_York'))) BETWEEN 570 AND 959
),
opens AS (
SELECT d, argMin(px, ts) AS open_px
FROM mins
GROUP BY d
),
zoned AS (
SELECT m.d AS d,
m.ts AS ts,
multiIf(m.px >= o.open_px * 1.001, 1,
m.px <= o.open_px * 0.999, -1,
0) AS zone
FROM mins AS m
INNER JOIN opens AS o ON m.d = o.d
),
flips AS (
SELECT d,
zone,
any(zone) OVER (PARTITION BY d ORDER BY ts ASC ROWS BETWEEN 1 PRECEDING AND 1 PRECEDING) AS prev_zone
FROM zoned
WHERE zone != 0
),
per_day AS (
SELECT d, countIf(prev_zone != 0 AND zone != prev_zone) AS crossings
FROM flips
GROUP BY d
)
SELECT formatDateTime(toStartOfMonth(d), '%Y-%m') AS month,
round(avg(crossings), 1) AS avg_crossings_per_session,
max(crossings) AS max_crossings_in_a_session
FROM per_day
GROUP BY toStartOfMonth(d)
ORDER BY toStartOfMonth(d)SPY เคลื่อนที่ตัดผ่านกรอบดังกล่าวเฉลี่ย 0.7 ครั้งต่อเซสชันในปี 2025-08 และ 1.5 ครั้งในปี 2026-07 โดยมีหนึ่งเซสชันในปี 2026-07 ที่บันทึกการตัดผ่านได้ถึง 5 ครั้ง กฎใดก็ตามที่เปิดสถานะที่ฝั่งหนึ่งของระดับราคาและสั่ง Stop ที่อีกฝั่งหนึ่ง ย่อมมีโอกาสทำงานซ้ำได้มากเท่ากับจำนวนครั้งที่ราคาวิ่งตัดผ่านในวันเดียว
กลไกสี่ประการสามารถควบคุมปัญหานี้ได้
- การกำหนดช่วงเวลาพัก (Cooldown) หลังจากคำสั่ง Stop ทำงาน โดยวัดเป็นนาทีหรือจำนวนแท่งเทียน ซึ่งในช่วงเวลานี้จะไม่มีคำสั่งซื้อขายใหม่สำหรับสินทรัพย์นั้นผ่านชั้นการจัดการความเสี่ยง
- การจำกัดจำนวนครั้งในการเทรดต่อวันสำหรับแต่ละสินทรัพย์ ซึ่งเปลี่ยนลูปที่ไม่มีขอบเขตให้กลายเป็นลูปที่มีขอบเขตจำกัด
- การใช้สถานะหยุดทำงาน (Halt flag) แบบคงค้าง เมื่อขีดจำกัดการขาดทุนรายวันถูกกระตุ้น สถานะนี้จะคงอยู่จนกว่าจะมีผู้ดูแลระบบเข้ามาปลดล็อก
- การคงสถานะของ Flag ดังกล่าวไว้นอกหน่วยความจำของโปรเซส ระบบกำกับดูแลที่รีสตาร์ทบอทที่ล่มไปจะส่งมอบสถานะที่ว่างเปล่าให้บอท ซึ่งสถานะที่ว่างเปล่านี้คือสิ่งที่ Flag ดังกล่าวถูกสร้างขึ้นมาเพื่อป้องกัน
กลไกข้อสุดท้ายช่วยป้องกันผู้ที่ทำทุกอย่างถูกต้องแล้วแต่ยังพลาด บอทเทรดแบบ Grid ถูกออกแบบมาให้วางคำสั่งเป็นลำดับขั้น ซึ่งทำให้การจำกัดจำนวนครั้งในการเทรดเป็นส่วนประกอบสำคัญที่ช่วยรับภาระความเสี่ยง ไม่ใช่เพียงแค่ส่วนประกอบเสริมเท่านั้น
จะเกิดอะไรขึ้นเมื่อบอทเทรดด้วยข้อมูลราคาที่ไม่อัปเดต
ราคาที่หยุดอัปเดตยังคงแสดงผลเป็นตัวเลข บอทจะอ่านค่าดังกล่าว นำไปคำนวณราคาคำสั่งซื้อขาย และส่งคำสั่งนั้นเข้าสู่ตลาดที่เคลื่อนไหวไปแล้ว ความผิดพลาดนี้เกิดขึ้นอย่างเงียบเชียบ ไม่มีการแจ้งเตือน ไม่มีการบันทึกข้อผิดพลาด และผลการจับคู่คำสั่งจะดูผิดปกติก็ต่อเมื่อเหตุการณ์ผ่านไปแล้วเท่านั้น
กรณีที่วัดผลได้อย่างชัดเจนคือช่วงราคาเปิดกระโดดข้ามคืน (overnight gap) ซึ่งราคาที่ทราบค่าจะหยุดนิ่งเป็นเวลาหลายชั่วโมง ในขณะที่ราคาที่ซื้อขายได้จริงมีการเปลี่ยนแปลง
SQL เบื้องหลังตัวเลขแต่ละตัว
WITH daily AS (
SELECT ticker,
toDate(toTimeZone(window_start, 'America/New_York')) AS d,
argMin(toFloat64(open), window_start) AS open_px,
argMax(toFloat64(close), window_start) AS close_px
FROM global_markets.delayed_stocks_minute_aggs
WHERE ticker IN ('SPY', 'KO', 'MSFT', 'AAPL', 'NVDA', 'TSLA')
AND toDate(toTimeZone(window_start, 'America/New_York')) >= toDate('2024-01-01')
AND toDate(toTimeZone(window_start, 'America/New_York')) <= toDate('2026-07-31')
AND (toHour(toTimeZone(window_start, 'America/New_York')) * 60
+ toMinute(toTimeZone(window_start, 'America/New_York'))) BETWEEN 570 AND 959
GROUP BY ticker, d
),
gaps AS (
SELECT ticker,
d,
open_px,
any(close_px) OVER (PARTITION BY ticker ORDER BY d ASC ROWS BETWEEN 1 PRECEDING AND 1 PRECEDING) AS prev_close
FROM daily
)
SELECT ticker,
round(quantileDeterministic(0.5)(abs(open_px / prev_close - 1) * 100, cityHash64(ticker, d)), 2) AS median_gap_pct,
round(quantileDeterministic(0.95)(abs(open_px / prev_close - 1) * 100, cityHash64(ticker, d)), 2) AS p95_gap_pct,
round(max(abs(open_px / prev_close - 1) * 100), 2) AS max_gap_pct
FROM gaps
WHERE prev_close > 0
GROUP BY ticker
ORDER BY p95_gap_pct DESCหลักทรัพย์ที่มีส่วนต่างราคา (gap) กว้างที่สุดในเปอร์เซ็นไทล์ที่เก้าสิบห้าคือ TSLA ที่ 4.41% เทียบกับ 1% สำหรับ KO ในคืนปกติส่วนใหญ่จะมีส่วนต่างที่แคบกว่ามาก โดยมีค่ามัธยฐานของส่วนต่างอยู่ที่ 1.01% และ 0.24% ตามลำดับ ส่วนปลายของข้อมูล (tails) คือสิ่งที่ระบบบริหารความเสี่ยงถูกสร้างขึ้นมาเพื่อรับมือ โดยส่วนต่างที่ใหญ่ที่สุดเพียงรายการเดียวในวันที่ TSLA ของช่วงเวลาที่วัดผลได้อยู่ที่ 14.57% นั่นคือระยะห่างที่บอทอาจเผชิญหากตัดสินใจโดยใช้ราคาที่อ่านมาเมื่อช่วงเวลาหนึ่ง เหตุใดหุ้นจึงเกิดราคาเปิดกระโดดข้ามคืน อธิบายกลไกในส่วนนี้ไว้
วิธีการป้องกันนั้นทำได้โดยมีต้นทุนต่ำ ให้กำหนดอายุสูงสุดของราคาที่ระบบบริหารความเสี่ยงจะยอมรับในการคำนวณ โดยทั่วไปสำหรับกลยุทธ์ระหว่างวันจะอยู่ที่ระดับไม่กี่วินาที ควรแยกสัญญาณชีพ (heartbeat) ออกจากข้อมูลราคา เพื่อแยกแยะระหว่างการเชื่อมต่อที่เงียบหายไปกับการที่ตลาดไม่มีการเคลื่อนไหว และให้ถือว่าข้อมูลที่ขาดหายไปคือการหยุดชะงักของระบบมากกว่าการคงสถานะเดิม เนื่องจากบอทที่ไม่มีข้อมูลราคาจะไม่สามารถประเมินจุดออกของสถานะได้เช่นกัน
ขีดจำกัดสูงสุด (Hard cap) ใดบ้างที่ควรอยู่ในชั้นการบริหารความเสี่ยง
- มูลค่า Notional สูงสุดต่อสัญลักษณ์ (Symbol) เมื่อเทียบกับส่วนของผู้ถือหุ้นในบัญชี การกำหนดเพดานไว้ที่ร้อยละ 10 ช่วยป้องกันไม่ให้หุ้นเพียงตัวเดียวส่งผลกระทบต่อพอร์ตโดยรวมทั้งหมด
- มูลค่า Gross notional สูงสุดของสถานะเปิดทั้งหมด การกำหนดไว้ที่ร้อยละ 100 ของส่วนของผู้ถือหุ้นหมายถึงการไม่ใช้ Leverage ซึ่งเป็นการตัดสินใจที่ควรทำอย่างชัดเจน แทนที่จะปล่อยให้เป็นไปตามค่าเริ่มต้นของโบรกเกอร์
- อัตราการส่งคำสั่งซื้อขายสูงสุด ต่อนาทีและต่อวัน การส่งคำสั่งสิบรายการต่อนาทีถือว่าเพียงพอสำหรับกลยุทธ์รายย่อยส่วนใหญ่ และยังช่วยจำกัดความเสียหายหากเกิดข้อผิดพลาดในระบบ (Runaway loop) ภายในเวลาหนึ่งนาที
- ขนาดคำสั่งซื้อขายสูงสุดเมื่อเทียบกับ ปริมาณการซื้อขายเฉลี่ยต่อวัน ของสัญลักษณ์นั้น การกำหนดเพดานไว้ที่ร้อยละ 1 คือขีดจำกัดที่ป้องกันไม่ให้บอทส่งผลกระทบต่อราคาที่พยายามจะซื้อขาย โดยมีปริมาณการซื้อขายเฉลี่ยต่อวันเป็นตัวหาร
ขีดจำกัดเหล่านี้ทั้งหมดควรอยู่ในชั้นการบริหารความเสี่ยงแทนที่จะอยู่ในตัวกลยุทธ์ และทุกรายการต้องใช้ชุดคำสั่งเดียวกันทั้งในการทดสอบย้อนหลัง (Backtest) การเทรดจำลอง (Paper trading) และการเทรดจริง ขีดจำกัดที่มีอยู่เฉพาะในการเทรดจริงคือขีดจำกัดที่ไม่มีใครเคยผ่านการทดสอบมาก่อน
บันทึกการตรวจสอบ (Audit Log) ของบอทเทรดควรมีข้อมูลอะไรบ้าง
บันทึกแบบ append-only จะเขียนข้อมูลหนึ่งรายการต่อหนึ่งการตัดสินใจ โดยไม่มีการแก้ไขหรือลบข้อมูลย้อนหลัง แต่ละรายการต้องประกอบด้วย Timestamp, ราคา Quote ที่ใช้และอายุของข้อมูลนั้น, ผลการตรวจสอบ Limit check ทุกรายการพร้อมคำตัดสิน, คำสั่งซื้อขายที่ส่งออกไป และการตอบกลับจากโบรกเกอร์ คำสั่งที่ถูกปฏิเสธต้องถูกบันทึกด้วยความสำคัญเท่าเทียมกับคำสั่งที่ได้รับการจับคู่ (Filled)
เป้าหมายสำคัญคือการสร้างสถานการณ์จำลองขึ้นใหม่ (Reconstruction) หกสัปดาห์หลังจากช่วงการซื้อขายที่ย่ำแย่ คำถามที่สำคัญไม่ใช่ผลกำไรขาดทุน (P&L) แต่คือการตรวจสอบว่าเงื่อนไขใดที่ผ่านการอนุมัติโดยใช้ข้อมูลนำเข้าชุดใด หากไม่มีการบันทึกข้อมูลนำเข้าไว้ คุณจะต้องย้อนกลับไปหาค่าสถานะของบอทจากข้อมูลตลาด ซึ่งถือเป็นความผิดพลาดแบบเดียวกับ look-ahead bias ในการทำ backtesting นั่นคือการใช้ข้อมูลที่ระบบไม่ได้ถือครองอยู่ในขณะที่ตัดสินใจ
โครงการ riskguard เป็นหนึ่งในตัวอย่างการใช้งานแบบ open-source ที่แยกส่วนประกอบนี้ออกมา โดยให้ Limit check ทำงานอยู่ในคอมโพเนนต์ที่กลยุทธ์เรียกใช้ แทนที่จะกระจายตรรกะไว้ทั่วตัวกลยุทธ์ นี่เป็นเพียงหนึ่งในหลายรูปแบบการออกแบบที่ควรศึกษาทำความเข้าใจก่อนนำไปใช้จริง ไม่ว่าคุณจะเลือกใช้เครื่องมือใด ให้ระบุเวอร์ชัน (Tagged release) แทนการใช้ค่าเริ่มต้นจาก Branch เพราะ Branch อาจมีการเปลี่ยนแปลงระหว่างการทำ backtesting สองครั้ง และการที่ชั้นความเสี่ยง (Risk layer) เปลี่ยนแปลงไปโดยที่คุณไม่ทราบนั้น ถือว่าแย่ยิ่งกว่าการไม่มีระบบป้องกันเลยเสียอีก
เหตุผลที่การใช้งานครั้งแรกต้องรันบน Paper broker
ตัวเชื่อมต่อของโบรกเกอร์จะตั้งค่าเริ่มต้นเป็นระบบ Paper trading โดยการเทรดจริงจะต้องมีการตั้งค่าสถานะ (flag) ที่ชัดเจนและเจาะจงเท่านั้น ความผิดพลาดที่ระบบนี้ช่วยป้องกันได้เป็นเรื่องพื้นฐาน เช่น การคัดลอกไฟล์การตั้งค่ามาใช้ หรือตัวแปรสภาพแวดล้อม (environment variable) ที่ไม่ได้ถูกแก้ไข ซึ่งอาจส่งคำสั่งซื้อขายจริงด้วยเงินจริงโดยไม่ตั้งใจ
การรันบนระบบ Paper trading ยังสร้างข้อมูลสำคัญ นั่นคือบันทึกการตัดสินใจ (decision log) จากชั้นการจัดการความเสี่ยง (risk layer) เดียวกันบนราคาตลาดจริง ซึ่งแสดงให้เห็นว่าขีดจำกัด (limit) ใดทำงานและขีดจำกัดใดไม่ทำงาน ข้อมูลนี้เป็นหลักฐานเกี่ยวกับชั้นการจัดการความเสี่ยง ซึ่งเป็นคนละประเด็นกับว่ากลยุทธ์นั้นทำกำไรได้หรือไม่ การเทรดจำลองก่อนใช้เงินจริง ครอบคลุมถึงสิ่งที่บันทึกจากการเทรดจำลองพิสูจน์ได้และไม่ได้ ส่วน ระบบเทรด AI แบบหลายตัวแทน แสดงให้เห็นว่าเหตุใดอำนาจในการระงับการเทรด (halt authority) จึงต้องแยกออกจากตัวแทนแต่ละตัว เมื่อมีตัวแทนหลายตัวที่สามารถส่งคำสั่งซื้อขายได้
คำถามที่พบบ่อยเกี่ยวกับระบบตัดการทำงานอัตโนมัติ (Circuit Breaker) ของ Trading Bot
Circuit breaker ใน Trading bot คืออะไร?
คือเกณฑ์ในชั้นการบริหารความเสี่ยงที่ทำหน้าที่หยุดบอทไม่ให้ส่งคำสั่งซื้อขายใหม่เมื่อถึงขีดจำกัดที่กำหนดไว้ โดยส่วนใหญ่มักเป็นเกณฑ์การขาดทุนรายวันหรือการลดลงของมูลค่าพอร์ตจากจุดสูงสุด (Drawdown) ระบบนี้ทำงานแยกส่วนจากกลยุทธ์หลัก โดยจะตรวจสอบทุกคำสั่งซื้อขายและจะคงสถานะหยุดทำงานไว้จนกว่าจะมีเงื่อนไขมาปลดล็อก
ตลาดเกิดภาวะราคาลดลงสองเปอร์เซ็นต์บ่อยแค่ไหน?
SPY มีจำนวนรอบการซื้อขายที่ราคาลดลงตั้งแต่สองเปอร์เซ็นต์ขึ้นไปอยู่ที่ 5 ครั้งในปี 2019 และ 25 ในปี 2020 จากจำนวนวันซื้อขายประมาณสองร้อยห้าสิบวันต่อปี ความแตกต่างระหว่างปีที่ตลาดสงบกับปีที่ตลาดผันผวนคือเหตุผลว่าทำไมการกำหนดขีดจำกัดการขาดทุนจึงต้องอ้างอิงจากข้อมูลในอดีตแทนที่จะใช้สัญชาตญาณ
จะป้องกันไม่ให้บอทกลับเข้าเทรดหลังจากหยุดทำงานได้อย่างไร?
การกำหนดช่วงเวลาพัก (Cooldown window) หลังการหยุดทำงานแต่ละครั้ง และการจำกัดจำนวนการเทรดต่อสัญลักษณ์ในแต่ละวัน จะช่วยเปลี่ยนวงจรการทำงานที่ไม่มีขอบเขตให้มีขอบเขตที่ชัดเจน นอกจากนี้ สถานะการหยุดทำงาน (Halt flag) จะต้องถูกบันทึกและคงอยู่ภายนอกหน่วยความจำของโปรเซส เพราะหากระบบ supervisor รีสตาร์ทบอทที่ขัดข้อง บอทอาจได้รับสถานะใหม่ที่ไม่ได้ถูกหยุดทำงานหากไม่มีการบันทึกสถานะไว้
บอทจะทราบได้อย่างไรว่าข้อมูลราคาที่ได้รับนั้นล้าสมัย?
โดยการตรวจสอบอายุของราคาเสนอซื้อขาย (Quote) ทุกครั้งก่อนนำไปคำนวณราคาคำสั่งซื้อขาย และการเฝ้าติดตามสัญญาณชีพ (Heartbeat) จากแหล่งข้อมูลแยกต่างหากจากตัวข้อมูลราคาเอง ช่องว่างของราคาข้ามคืนแสดงให้เห็นถึงความเสี่ยงที่ราคาล้าสมัยปกปิดไว้ โดยการเคลื่อนไหวของราคาข้ามคืนที่เปอร์เซ็นไทล์ที่เก้าสิบห้าแตะระดับ 4.41% ในวันที่ TSLA ระหว่างเดือนมกราคม 2024 ถึงเดือนกรกฎาคม 2026
Trading bot ขนาดเล็กจำเป็นต้องมีบันทึกการตรวจสอบ (Audit log) หรือไม่?
บันทึกการจับคู่คำสั่ง (Fill log) จะบันทึกสิ่งที่เกิดขึ้นจริง ส่วนบันทึกการตัดสินใจ (Decision log) จะบันทึกสิ่งที่บอทประเมินว่าสามารถทำได้ ซึ่งเป็นวิธีเดียวที่จะแยกแยะระหว่างกลยุทธ์ที่ผิดพลาดกับการตรวจสอบความเสี่ยงที่ไม่ได้ทำงาน บันทึกแบบเพิ่มข้อมูลได้อย่างเดียว (Append-only) ที่รวมถึงรายการที่ถูกปฏิเสธ คือรูปแบบขั้นต่ำที่ใช้งานได้จริง
ตัวเลขทั้งหมดข้างต้นมาจากคิวรีที่จัดเก็บไว้ผ่านข้อมูลราคาแบบรายนาที และแต่ละส่วนสามารถเปิดดูคำสั่ง SQL เบื้องหลังได้ คุณสามารถใช้คิวรีเดียวกันนี้กับรายการสัญลักษณ์ของคุณเองได้บน Strasmore terminal