LLM ค้นหาปัจจัยแอลฟาได้จริงหรือไม่
LLM เขียนปัจจัยแอลฟาได้เป็นร้อยรายการในหนึ่งชั่วโมง แต่ 240 ปัจจัยที่เสี่ยงเหมือนโยนเหรียญให้ผลอย่างไรเมื่อทดสอบกับราคาจริงนานสิบปี และควรทดสอบตัวที่ผ่านอย่างไร
LLM สามารถเสนอปัจจัยแอลฟาได้ตลอดทั้งวัน เพียงป้อนพจนานุกรมข้อมูลและชุดทดสอบการให้คะแนนให้โมเดลที่มีความสามารถ โมเดลดังกล่าวก็จะเขียนนิพจน์ปัจจัยที่ดูสมเหตุสมผลได้เป็นร้อยรายการก่อนมื้อกลางวัน คำถามที่ยากกว่านั้นอยู่เบื้องหลังว่า จะรู้ได้อย่างไรว่ารายการใดรายการหนึ่งเป็นของจริง ในเมื่อกระบวนการค้นหาที่ใช้สร้างมันขึ้นมาเป็นเครื่องจักรผลิตผู้ชนะจากสัญญาณรบกวน
ปัจจัยแอลฟาคืออะไร
ปัจจัย คือกฎที่แปลงข้อมูลตลาดให้เป็นตัวเลขหนึ่งตัวสำหรับหุ้นทุกตัวในทุกวัน การเปลี่ยนแปลงของราคาย้อนหลัง 12 เดือนเป็นปัจจัยประเภทหนึ่ง เช่นเดียวกับอัตราส่วนหนี้สินต่อส่วนของผู้ถือหุ้น ปัจจัยจะกลายเป็นกลยุทธ์เมื่อคุณจัดอันดับหุ้นในจักรวาลตามปัจจัยนั้น ซื้อกลุ่มบนสุด ขายกลุ่มล่างสุด และปรับพอร์ตตามกำหนดเวลา Alpha คือผลตอบแทนส่วนที่เหลือหลังหักผลตอบแทนที่การลงทุนตามตลาดทั่วไปจะให้มาอยู่แล้ว
ผู้สมัครแต่ละรายการจะได้รับการให้คะแนนด้วย Sharpe ratio ซึ่งคำนวณจากผลตอบแทนเฉลี่ยหารด้วยส่วนเบี่ยงเบนมาตรฐานของผลตอบแทนนั้น แล้วปรับเป็นรายปี นี่คือผลตอบแทนต่อความผันผวนหนึ่งหน่วย Sharpe ระยะยาวใกล้ 1 สำหรับกลยุทธ์ที่ใช้เงินจริงถือว่าอยู่ในระดับน่าพอใจ จึงควรระลึกไว้เมื่อเห็น backtest รายงานค่า 3
การวิจัยปัจจัยด้วย LLM ทำงานอย่างไร
ทุกโครงการในด้านนี้ใช้กระบวนการวนซ้ำรูปแบบเดียวกันโดยประมาณ
- โมเดลเขียนนิพจน์ปัจจัยในภาษาขนาดเล็กที่ชุดทดสอบสามารถประเมินผลได้
- ระบบ backtest ให้คะแนนนิพจน์แต่ละรายการโดยใช้ประวัติราคากับข้อมูลพื้นฐานในช่วงเวลาที่กำหนด
- เก็บนิพจน์ที่ได้คะแนนสูงกว่าเกณฑ์ และตัดรายการที่เหลือทิ้ง
- นำนิพจน์ที่ผ่านกลับเข้าไปในบริบทของโมเดลในฐานะตัวอย่างที่ทำเสร็จแล้ว แล้วเริ่มวนรอบใหม่
ระบบซื้อขายแบบหลายตัวแทน แบ่งงานเหล่านี้ไปยังบทบาทแยกกัน โดยมีบทบาทหนึ่งเสนอแนวคิดและอีกบทบาทหนึ่งทดสอบ ระบบเชื่อมต่อมีประโยชน์จริง และ ทักษะข้อมูลตลาดที่เอเจนต์ AI ต้องมี ก็เป็นทักษะเดียวกับที่มนุษย์ต้องใช้
กระบวนการนี้ไม่มีสิ่งใดไม่ซื่อสัตย์ การค้นหาเป็นวิธีที่ทำให้การวิจัยเกิดขึ้น ปัญหาอยู่ที่คณิตศาสตร์ และจะปรากฏทันทีที่ขั้นตอนที่ 2 ทำซ้ำมากกว่าสองสามครั้ง
เหตุใดการค้นหาปัจจัยแอลฟาด้วย LLM จึงผลิตผู้ชนะจากสัญญาณรบกวน
ประวัติราคาชุดเดียว สมมติฐานราคาถูกหลายพันรายการ สมมติฐานทุกข้อได้รับการให้คะแนนจากกลุ่มตัวอย่างจำกัดชุดเดียวกัน และกลุ่มตัวอย่างนั้นมีความบังเอิญอยู่มาก ทดสอบกฎมากพอ แล้วบางกฎจะปรับเข้ากับความบังเอิญได้อย่างใกล้ชิด คะแนนไม่สามารถบอกได้ว่าคุณได้การปรับแบบใด เพราะกฎที่จับสัญญาณรบกวนและกฎที่จับการเคลื่อนไหวของตลาดจะให้ตัวเลขเดียวกัน
สมมติฐานว่างต่อไปนี้สุ่มทั้งหมด 240 ครั้ง แต่ละ “ปัจจัย” ด้านล่างเป็นการโยนเหรียญ: แฮชของ ticker เดือน และหมายเลขการทดลองจะแบ่งหุ้นสหรัฐฯ ขนาดใหญ่ 40 ตัวออกเป็นสองส่วนในแต่ละเดือน จากนั้นกลยุทธ์จะถือส่วนหนึ่งเป็นสถานะ long และอีกส่วนเป็นสถานะ short การทดลองนี้ไม่มีข้อมูลอยู่ภายในโดยการออกแบบ เมื่อนำไปให้คะแนนด้วยผลตอบแทนจริง ณ สิ้นเดือนตั้งแต่ มกราคม 2016 ถึง มิถุนายน 2021 การทดลองทั้ง 240 รายการกระจายตัวดังนี้
SQL เบื้องหลังตัวเลขแต่ละตัว
WITH month_end AS (
SELECT ticker,
toStartOfMonth(toDate(toTimeZone(window_start, 'America/New_York'))) AS month_start,
argMax(toFloat64(close), window_start) AS close_px
FROM global_markets.delayed_stocks_minute_aggs
WHERE ticker IN ('AAPL','ADBE','AMZN','BA','CAT','COST','CRM','CSCO','CVX','DE',
'DUK','GE','GOOGL','HD','HON','IBM','INTC','JNJ','JPM','KO',
'LMT','MCD','MMM','MRK','MSFT','NKE','NVDA','ORCL','PEP','PFE',
'PG','QCOM','SO','T','TGT','TXN','UNP','VZ','WMT','XOM')
AND toDate(toTimeZone(window_start, 'America/New_York')) >= toDate('2015-12-01')
AND toDate(toTimeZone(window_start, 'America/New_York')) <= toDate('2021-06-30')
AND toDayOfMonth(toTimeZone(window_start, 'America/New_York')) >= 22
AND (toHour(toTimeZone(window_start, 'America/New_York')) * 60
+ toMinute(toTimeZone(window_start, 'America/New_York'))) BETWEEN 570 AND 959
GROUP BY ticker, month_start
),
lagged AS (
SELECT ticker,
month_start,
close_px,
lagInFrame(close_px) OVER (PARTITION BY ticker ORDER BY month_start
ROWS BETWEEN 1 PRECEDING AND CURRENT ROW) AS prev_px
FROM month_end
),
monthly_return AS (
SELECT ticker, month_start, close_px / prev_px - 1 AS ret
FROM lagged
WHERE prev_px > 0
AND month_start >= toDate('2016-01-01')
),
trial AS (
SELECT arrayJoin(range(1, 241)) AS n
),
factor_month AS (
SELECT t.n AS trial_id,
m.month_start AS month_start,
avgIf(m.ret, bitAnd(cityHash64(m.ticker, toString(m.month_start), t.n), 1) = 1)
- avgIf(m.ret, bitAnd(cityHash64(m.ticker, toString(m.month_start), t.n), 1) = 0) AS long_short_ret
FROM monthly_return AS m
CROSS JOIN trial AS t
GROUP BY trial_id, month_start
HAVING countIf(bitAnd(cityHash64(m.ticker, toString(m.month_start), t.n), 1) = 1) > 0
AND countIf(bitAnd(cityHash64(m.ticker, toString(m.month_start), t.n), 1) = 0) > 0
),
scored AS (
SELECT trial_id,
avg(long_short_ret) / stddevSamp(long_short_ret) * sqrt(12) AS sharpe
FROM factor_month
GROUP BY trial_id
HAVING stddevSamp(long_short_ret) > 0
)
SELECT multiIf(sharpe < -1.2, 'below -1.2',
sharpe < -0.8, '-1.2 to -0.8',
sharpe < -0.4, '-0.8 to -0.4',
sharpe < 0.0, '-0.4 to 0.0',
sharpe < 0.4, '0.0 to 0.4',
sharpe < 0.8, '0.4 to 0.8',
sharpe < 1.2, '0.8 to 1.2',
'1.2 and above') AS sharpe_bucket,
count() AS factor_count,
round(100 * count() / 240, 1) AS share_pct
FROM scored
GROUP BY sharpe_bucket
ORDER BY min(sharpe)ส่วนต่างคือประเด็นสำคัญ ไม่มีสิ่งใดในกราฟนั้นที่ใช้พยากรณ์ได้ แต่ 1 การทดลองกลับอยู่ในกลุ่มคะแนนสูงสุด (1.2 and above) หรือคิดเป็น 0.4% ของการค้นหา และ 1 อยู่ในกลุ่มคะแนนต่ำสุด (below -1.2) นักวิจัยที่ทำการทดลองซึ่งโชคดีเพียงครั้งเดียวแล้วหยุด จะมีทั้งกราฟและ Sharpe ratio แต่ไม่มีทางแยกได้ว่าสิ่งเหล่านี้เป็นการค้นพบจริงหรือไม่ ผลตอบแทนในที่นี้วัดจากราคาปิดสิ้นเดือนหนึ่งถึงราคาปิดสิ้นเดือนถัดไป ส่วน วิธีวัดผลตอบแทนรายเดือน อธิบายการคำนวณดังกล่าว
ตัวเลขสำคัญคือจำนวนครั้งที่คุณทดลอง
ผล backtest ที่รายงานเพียงลำพังขาดตัวส่วน การทดลอง 240 ครั้งเดียวกัน หากอ่านในฐานะการค้นหาที่ขยายกว้างขึ้น จะเห็นคะแนนสูงสุดบนกระดานเทียบกับค่าเฉลี่ยของทุกสิ่งที่ทดลองมาจนถึงแต่ละขั้นตอน
SQL เบื้องหลังตัวเลขแต่ละตัว
WITH month_end AS (
SELECT ticker,
toStartOfMonth(toDate(toTimeZone(window_start, 'America/New_York'))) AS month_start,
argMax(toFloat64(close), window_start) AS close_px
FROM global_markets.delayed_stocks_minute_aggs
WHERE ticker IN ('AAPL','ADBE','AMZN','BA','CAT','COST','CRM','CSCO','CVX','DE',
'DUK','GE','GOOGL','HD','HON','IBM','INTC','JNJ','JPM','KO',
'LMT','MCD','MMM','MRK','MSFT','NKE','NVDA','ORCL','PEP','PFE',
'PG','QCOM','SO','T','TGT','TXN','UNP','VZ','WMT','XOM')
AND toDate(toTimeZone(window_start, 'America/New_York')) >= toDate('2015-12-01')
AND toDate(toTimeZone(window_start, 'America/New_York')) <= toDate('2021-06-30')
AND toDayOfMonth(toTimeZone(window_start, 'America/New_York')) >= 22
AND (toHour(toTimeZone(window_start, 'America/New_York')) * 60
+ toMinute(toTimeZone(window_start, 'America/New_York'))) BETWEEN 570 AND 959
GROUP BY ticker, month_start
),
lagged AS (
SELECT ticker,
month_start,
close_px,
lagInFrame(close_px) OVER (PARTITION BY ticker ORDER BY month_start
ROWS BETWEEN 1 PRECEDING AND CURRENT ROW) AS prev_px
FROM month_end
),
monthly_return AS (
SELECT ticker, month_start, close_px / prev_px - 1 AS ret
FROM lagged
WHERE prev_px > 0
AND month_start >= toDate('2016-01-01')
),
trial AS (
SELECT arrayJoin(range(1, 241)) AS n
),
factor_month AS (
SELECT t.n AS trial_id,
m.month_start AS month_start,
avgIf(m.ret, bitAnd(cityHash64(m.ticker, toString(m.month_start), t.n), 1) = 1)
- avgIf(m.ret, bitAnd(cityHash64(m.ticker, toString(m.month_start), t.n), 1) = 0) AS long_short_ret
FROM monthly_return AS m
CROSS JOIN trial AS t
GROUP BY trial_id, month_start
HAVING countIf(bitAnd(cityHash64(m.ticker, toString(m.month_start), t.n), 1) = 1) > 0
AND countIf(bitAnd(cityHash64(m.ticker, toString(m.month_start), t.n), 1) = 0) > 0
),
scored AS (
SELECT trial_id,
avg(long_short_ret) / stddevSamp(long_short_ret) * sqrt(12) AS sharpe
FROM factor_month
GROUP BY trial_id
HAVING stddevSamp(long_short_ret) > 0
),
ladder AS (
SELECT arrayJoin([1, 2, 5, 10, 25, 50, 100, 160, 240]) AS n
)
SELECT l.n AS factors_tried,
round(max(s.sharpe), 2) AS best_sharpe,
round(avg(s.sharpe), 2) AS average_sharpe
FROM ladder AS l
CROSS JOIN scored AS s
WHERE s.trial_id <= l.n
GROUP BY factors_tried
ORDER BY factors_triedค่าสูงสุดสะสมทำได้เพียงเพิ่มขึ้น ซึ่งเป็นกับดักโดยตรง กฎแรกที่ทดสอบได้คะแนน 0.44 หลังจากทดลองไปแล้ว 240 ครั้ง คะแนนที่ดีที่สุดบนกระดานอยู่ที่ 1.59 ขณะที่ค่าเฉลี่ยของทั้งหมดอยู่ที่ 0.01 หัวข้อข่าวดูดีขึ้นโดยที่ไม่มีกฎใดดีขึ้นเลย ชุดทดสอบที่ประเมินนิพจน์ 10,000 รายการกำลังสร้างเส้นโค้งนี้ไปไกลทางขวามากกว่าที่แสดงในที่นี้ และตัวเลขที่รายงานก็คือจุดสูงสุดของเส้นโค้งนั้น
ช่วงเวลาที่กันไว้ทดสอบส่งผลต่อผู้ชนะอย่างไร
แนวป้องกันมาตรฐานคือ ชุดข้อมูลกันไว้ทดสอบ: ให้คะแนนในช่วงเวลาหนึ่ง แล้วให้คะแนนผู้รอดชีวิตซ้ำในช่วงเวลาหลังจากนั้น ซึ่งการค้นหาไม่เคยแตะต้อง นำการโยนเหรียญ 12 รายการที่ดีที่สุดจากช่วงฝึกอบรมไปใช้กับกฎเดียวกันตลอด 5 ปีถัดมา ตั้งแต่ กรกฎาคม 2021 ถึง มิถุนายน 2026
SQL เบื้องหลังตัวเลขแต่ละตัว
WITH month_end AS (
SELECT ticker,
toStartOfMonth(toDate(toTimeZone(window_start, 'America/New_York'))) AS month_start,
argMax(toFloat64(close), window_start) AS close_px
FROM global_markets.delayed_stocks_minute_aggs
WHERE ticker IN ('AAPL','ADBE','AMZN','BA','CAT','COST','CRM','CSCO','CVX','DE',
'DUK','GE','GOOGL','HD','HON','IBM','INTC','JNJ','JPM','KO',
'LMT','MCD','MMM','MRK','MSFT','NKE','NVDA','ORCL','PEP','PFE',
'PG','QCOM','SO','T','TGT','TXN','UNP','VZ','WMT','XOM')
AND toDate(toTimeZone(window_start, 'America/New_York')) >= toDate('2015-12-01')
AND toDate(toTimeZone(window_start, 'America/New_York')) <= toDate('2026-06-30')
AND toDayOfMonth(toTimeZone(window_start, 'America/New_York')) >= 22
AND (toHour(toTimeZone(window_start, 'America/New_York')) * 60
+ toMinute(toTimeZone(window_start, 'America/New_York'))) BETWEEN 570 AND 959
GROUP BY ticker, month_start
),
lagged AS (
SELECT ticker,
month_start,
close_px,
lagInFrame(close_px) OVER (PARTITION BY ticker ORDER BY month_start
ROWS BETWEEN 1 PRECEDING AND CURRENT ROW) AS prev_px
FROM month_end
),
monthly_return AS (
SELECT ticker, month_start, close_px / prev_px - 1 AS ret
FROM lagged
WHERE prev_px > 0
AND month_start >= toDate('2016-01-01')
),
trial AS (
SELECT arrayJoin(range(1, 241)) AS n
),
factor_month AS (
SELECT t.n AS trial_id,
m.month_start AS month_start,
avgIf(m.ret, bitAnd(cityHash64(m.ticker, toString(m.month_start), t.n), 1) = 1)
- avgIf(m.ret, bitAnd(cityHash64(m.ticker, toString(m.month_start), t.n), 1) = 0) AS long_short_ret
FROM monthly_return AS m
CROSS JOIN trial AS t
GROUP BY trial_id, month_start
HAVING countIf(bitAnd(cityHash64(m.ticker, toString(m.month_start), t.n), 1) = 1) > 0
AND countIf(bitAnd(cityHash64(m.ticker, toString(m.month_start), t.n), 1) = 0) > 0
),
scored AS (
SELECT trial_id,
avgIf(long_short_ret, month_start < toDate('2021-07-01'))
/ stddevSampIf(long_short_ret, month_start < toDate('2021-07-01')) * sqrt(12) AS in_sample_sharpe,
avgIf(long_short_ret, month_start >= toDate('2021-07-01'))
/ stddevSampIf(long_short_ret, month_start >= toDate('2021-07-01')) * sqrt(12) AS out_of_sample_sharpe
FROM factor_month
GROUP BY trial_id
HAVING countIf(month_start < toDate('2021-07-01')) >= 24
AND countIf(month_start >= toDate('2021-07-01')) >= 24
)
SELECT concat('trial ', toString(trial_id)) AS factor_label,
round(in_sample_sharpe, 2) AS in_sample_sharpe,
round(out_of_sample_sharpe, 2) AS out_of_sample_sharpe
FROM scored
ORDER BY in_sample_sharpe DESC
LIMIT 12แท่งกราฟแต่ละคู่แทนกฎหนึ่งรายการ แท่งซ้ายคือคะแนนที่ทำให้กฎนั้นมีสิทธิ์อยู่ในรายงาน แท่งขวาคือคะแนนของกฎเดียวกันในช่วง 5 ปีถัดมา การทดลองที่อยู่ในอันดับหนึ่งได้คะแนน 1.59 ในช่วงฝึกอบรม และ -0.51 ในช่วงหลังจากนั้น ส่วนการทดลองอันดับที่ 12 ได้ 0.74 แล้วลดลงมาอยู่ที่ 0.49
กฎ 12 รายการก็เป็นกลุ่มตัวอย่างขนาดเล็กในตัวเอง การจัดการทดลองทั้ง 240 รายการเป็น 5 กลุ่มตามคะแนนช่วงฝึกอบรม แล้วหาคะแนนช่วงกันไว้ทดสอบเฉลี่ยของแต่ละกลุ่ม จะให้ภาพที่ชัดเจนกว่า
SQL เบื้องหลังตัวเลขแต่ละตัว
WITH month_end AS (
SELECT ticker,
toStartOfMonth(toDate(toTimeZone(window_start, 'America/New_York'))) AS month_start,
argMax(toFloat64(close), window_start) AS close_px
FROM global_markets.delayed_stocks_minute_aggs
WHERE ticker IN ('AAPL','ADBE','AMZN','BA','CAT','COST','CRM','CSCO','CVX','DE',
'DUK','GE','GOOGL','HD','HON','IBM','INTC','JNJ','JPM','KO',
'LMT','MCD','MMM','MRK','MSFT','NKE','NVDA','ORCL','PEP','PFE',
'PG','QCOM','SO','T','TGT','TXN','UNP','VZ','WMT','XOM')
AND toDate(toTimeZone(window_start, 'America/New_York')) >= toDate('2015-12-01')
AND toDate(toTimeZone(window_start, 'America/New_York')) <= toDate('2026-06-30')
AND toDayOfMonth(toTimeZone(window_start, 'America/New_York')) >= 22
AND (toHour(toTimeZone(window_start, 'America/New_York')) * 60
+ toMinute(toTimeZone(window_start, 'America/New_York'))) BETWEEN 570 AND 959
GROUP BY ticker, month_start
),
lagged AS (
SELECT ticker,
month_start,
close_px,
lagInFrame(close_px) OVER (PARTITION BY ticker ORDER BY month_start
ROWS BETWEEN 1 PRECEDING AND CURRENT ROW) AS prev_px
FROM month_end
),
monthly_return AS (
SELECT ticker, month_start, close_px / prev_px - 1 AS ret
FROM lagged
WHERE prev_px > 0
AND month_start >= toDate('2016-01-01')
),
trial AS (
SELECT arrayJoin(range(1, 241)) AS n
),
factor_month AS (
SELECT t.n AS trial_id,
m.month_start AS month_start,
avgIf(m.ret, bitAnd(cityHash64(m.ticker, toString(m.month_start), t.n), 1) = 1)
- avgIf(m.ret, bitAnd(cityHash64(m.ticker, toString(m.month_start), t.n), 1) = 0) AS long_short_ret
FROM monthly_return AS m
CROSS JOIN trial AS t
GROUP BY trial_id, month_start
HAVING countIf(bitAnd(cityHash64(m.ticker, toString(m.month_start), t.n), 1) = 1) > 0
AND countIf(bitAnd(cityHash64(m.ticker, toString(m.month_start), t.n), 1) = 0) > 0
),
scored AS (
SELECT trial_id,
avgIf(long_short_ret, month_start < toDate('2021-07-01'))
/ stddevSampIf(long_short_ret, month_start < toDate('2021-07-01')) * sqrt(12) AS in_sample_sharpe,
avgIf(long_short_ret, month_start >= toDate('2021-07-01'))
/ stddevSampIf(long_short_ret, month_start >= toDate('2021-07-01')) * sqrt(12) AS out_of_sample_sharpe
FROM factor_month
GROUP BY trial_id
HAVING countIf(month_start < toDate('2021-07-01')) >= 24
AND countIf(month_start >= toDate('2021-07-01')) >= 24
),
ranked AS (
SELECT trial_id,
in_sample_sharpe,
out_of_sample_sharpe,
row_number() OVER (ORDER BY in_sample_sharpe DESC) AS in_sample_rank
FROM scored
)
SELECT multiIf(in_sample_rank <= 48, 'best fifth in training',
in_sample_rank <= 96, 'second fifth',
in_sample_rank <= 144, 'middle fifth',
in_sample_rank <= 192, 'fourth fifth',
'worst fifth in training') AS training_group,
round(avg(in_sample_sharpe), 2) AS avg_in_sample_sharpe,
round(avg(out_of_sample_sharpe), 2) AS avg_out_of_sample_sharpe
FROM ranked
GROUP BY training_group
ORDER BY min(in_sample_rank)ในช่วงฝึกอบรม กลุ่มต่าง ๆ เรียงจาก 0.66 ที่กลุ่มบนสุดถึง -0.63 ที่กลุ่มล่างสุด เป็นลำดับที่กว้างและเป็นระเบียบอย่างสมบูรณ์ ซึ่งรับประกันได้เพราะแบ่งกลุ่มด้วยคะแนนนั้นเอง เมื่อดูช่วงกันไว้ทดสอบ ปลายทั้งสองด้านเดียวกันมีค่าเฉลี่ย 0.01 และ 0.13 ตามลำดับ ลำดับดังกล่าวแบนราบลง ช่วงกันไว้ทดสอบเป็นส่วนเดียวของกระบวนการที่ไม่ได้ถูกปรับให้เหมาะกับข้อมูลมาก่อน นั่นคือเหตุผลที่ควรใช้ช่วงดังกล่าวอย่างรอบคอบ
แนวป้องกันที่ใช้ได้จริง
ชุดข้อมูลกันไว้ทดสอบที่ใช้เพียงครั้งเดียว ทุกครั้งที่เปิดดูข้อมูล จะเปลี่ยนข้อมูลนั้นให้กลายเป็นข้อมูลฝึกอบรม การทดสอบแบบเดินหน้า ซึ่งหน้าต่างข้อมูลจะเลื่อนไปเรื่อย ๆ และคะแนนแต่ละชุดมาจากข้อมูลหลังช่วงที่ใช้ฟิตโมเดล คือรูปแบบที่รองรับการใช้งานซ้ำได้
การปรับค่าจากการทดสอบหลายครั้ง Deflated Sharpe ratio ซึ่ง Bailey และ López de Prado นำเสนอในปี 2014 จะลดทอน Sharpe ที่สังเกตได้ตามจำนวนครั้งที่ทดลอง ความยาวของกลุ่มตัวอย่าง ความเบ้ของผลตอบแทน และความหนาของหางการกระจาย หากป้อนจำนวนครั้งที่ทดลองอย่างซื่อตรง Sharpe ที่โดดเด่นจากการค้นหานิพจน์ 10,000 รายการมักลดลงจนแทบไม่มีความหมาย
เส้นทางการตรวจสอบที่ครอบคลุมนิพจน์ทุกตัวที่เคยทดลอง รวมถึงรายการที่ถูกทิ้ง ข้อนี้เป็นองค์ประกอบหลัก และเป็นเหตุผลที่คำว่า “ตรวจสอบได้” มีความสำคัญเมื่ออธิบายโครงการวิจัยปัจจัย การลดทอนค่าต้องใช้จำนวนครั้งที่ทดลอง หากกระบวนการบันทึกเฉพาะผู้ชนะ ก็ได้ทำลายข้อมูลนำเข้าของการปรับแก้ของตนเองแล้ว ร่างที่ถูกทิ้ง การกวาดหาพารามิเตอร์ที่ยกเลิก การเริ่มต้นการวิจัยใหม่ของนักวิจัย และโค้ดให้คะแนนทุกเวอร์ชันก่อนหน้า ล้วนต้องนับรวมในตัวเลขดังกล่าว
ตรวจสอบต้นทุนและ อคติจากการมองไปข้างหน้า ก่อนเชื่อคะแนน ปัจจัยที่จัดอันดับด้วยข้อมูลพื้นฐานซึ่งประทับวันที่ที่ผู้ให้บริการนำข้อมูลเข้า แทนวันที่ตลาดสามารถมองเห็นข้อมูลนั้น จะให้ผล backtest ที่สวยงาม แต่ซื้อขายจริงได้แย่
การอ่านคำว่า “ตรวจสอบได้”
คลังโค้ดใหม่ในด้านนี้เกิดขึ้นเกือบทุกสัปดาห์ และโครงการที่มีดาวอยู่ไม่กี่สิบดวงเป็นเพียงต้นแบบ ไม่ใช่ประวัติผลงาน จำนวนดาวยังเปลี่ยนเร็วกว่าตัวโค้ด นั่นคือเหตุผลที่หน้านี้ประเมินรูปแบบการทำงานแทนที่จะประเมินโครงการใดโครงการหนึ่ง ต่อไปนี้คือสิ่งที่ควรเปิดดูเป็นอันดับแรกในโครงการใดก็ตามที่อยู่ตรงหน้า
- โครงการบันทึกผู้สมัครทุกตัวพร้อมนิพจน์และคะแนน รวมถึงเวลา หรือบันทึกเฉพาะรายการที่ผ่าน?
- ชุดทดสอบบังคับใช้ช่วงกันไว้ทดสอบเอง หรืออาศัยวินัยของนักวิจัย?
- คะแนนที่รายงานมีจำนวนครั้งที่ทดลองกำกับไว้ด้วยหรือไม่?
- โครงการสร้างขึ้นสำหรับตลาดใด? ไลบรารีที่ปรับให้เหมาะกับหุ้น A ของจีนจะมีข้อจำกัดราคาประจำวันและข้อห้ามขายหุ้นที่ซื้อในช่วงเวลาเดียวกันติดมาด้วย และพฤติกรรมของปัจจัยภายใต้กฎเหล่านั้นไม่สามารถนำไปใช้กับหุ้นสหรัฐฯ ได้โดยตรง
- คุณสามารถรันซ้ำและสร้างตัวเลขเดิมได้หรือไม่? ระบุ commit ที่แน่นอนที่คุณอ่านไว้ เพราะโครงการในระยะนี้อาจเขียนโค้ดให้คะแนนใหม่ระหว่างสุดสัปดาห์
ไม่มีข้อใดทำให้ LLM ไร้ประโยชน์ในการวิจัยปัจจัย การสร้างสมมติฐานเป็นคอขวดที่มีอยู่จริง และโมเดลทำเรื่องนี้ได้ดี สิ่งที่เปลี่ยนไปคือภาระตกอยู่ที่การทำบัญชีว่ามีการใช้สมมติฐานไปกี่รายการ ก่อนที่สิ่งใดจะพบกับสมุดคำสั่งซื้อขายจริง การซื้อขายจำลอง คือขั้นตอนที่ทำให้เห็นช่องว่างระหว่าง backtest กับการจับคู่คำสั่ง
คำถามที่พบบ่อยเกี่ยวกับปัจจัยแอลฟาของ LLM
LLM สามารถค้นหาปัจจัยแอลฟาได้หรือไม่
โมเดลสามารถเสนอได้เป็นพันรายการ แต่ข้อเสนอไม่ใช่การค้นพบ ข้ออ้างดังกล่าวเกิดขึ้นในขั้นตอนให้คะแนน และคะแนนจากการค้นหาที่กว้างย่อมมีปัญหาการคัดเลือกที่ตัวคะแนนเองมองไม่เห็น ควรประเมินวินัยในการใช้ช่วงกันไว้ทดสอบและจำนวนครั้งที่บันทึกไว้ก่อนพิจารณานิพจน์
Deflated Sharpe ratio คืออะไร
เป็นการปรับแก้ที่แปลง Sharpe ratio ที่สังเกตได้ให้เป็นความน่าจะเป็นว่า การค้นหาที่มีขนาดดังกล่าวจะสร้างผลลัพธ์นี้ได้แม้ไม่มีความได้เปรียบที่แท้จริง Bailey และ López de Prado เผยแพร่วิธีนี้ในปี 2014 ตัวแปรนำเข้าหลักคือจำนวนครั้งที่ทดลอง ซึ่งเป็นตัวเลขที่กระบวนการวิจัยที่ตรวจสอบไม่ได้ไม่สามารถจัดหาได้
Backtest กี่ครั้งจึงถือว่ามากเกินไป
ไม่มีเกณฑ์ตายตัว มีเพียงการปรับแก้ที่ต้องนำมาใช้ backtest หนึ่งครั้งที่ได้คะแนน 1.0 กับ backtest 10,000 ครั้งซึ่งครั้งที่ดีที่สุดได้ 1.0 เป็นข้อกล่าวอ้างต่อโลกคนละแบบกัน การโยนเหรียญด้านบนได้ 1.59 จากการทดลอง 240 ครั้ง ทั้งที่ไม่มีข้อมูลอยู่ในข้อมูลเลย
เหตุใดปัจจัยที่เผยแพร่แล้วจึงอ่อนแอลงหลังการเผยแพร่
งานวิชาการติดตามการเสื่อมลงของความผิดปกติที่เผยแพร่แล้วในช่วงหลายปีหลังปรากฏผลครั้งแรก การที่ผู้ลงทุนเข้ามาแออัดในกลยุทธ์เดียวกันเป็นกลไกหนึ่งที่ถูกเสนอ ส่วนผลลัพธ์ดั้งเดิมที่ปรับเข้ากับกลุ่มตัวอย่างของตนเองมากเกินไปก็เป็นอีกกลไกหนึ่ง ทั้งสองกรณีให้รูปทรงเดียวกันบนกราฟ สมมติฐานตลาดมีประสิทธิภาพ อธิบายกลไกแรก ส่วนการทดลองด้านบนแสดงให้เห็นกลไกที่สอง
แผงข้อมูลทุกแผงในที่นี้เป็น query ที่จัดเก็บไว้จากราคาจริง ณ สิ้นเดือน โดยมี SQL เปิดให้ตรวจสอบอยู่ด้านล่าง คัดลอกไปหนึ่งรายการ เพิ่มจำนวนครั้งที่ทดลอง แล้วดูตัวเลขที่ดีที่สุดไต่สูงขึ้นบนเทอร์มินัล Strasmore