האם מודל LLM יכול למצוא גורמי Alpha?
LLM יכול לכתוב מאה גורמי alpha בשעה. בדקו כיצד 240 גורמים אקראיים מתפקדים על פני עשר שנות מחירים אמיתיים, ואיך לבחון את המועמדים ששרדו.
מודל LLM יכול להציע גורמי alpha לאורך כל היום. תנו למודל בעל יכולות טובות מילון נתונים ומנגנון דירוג, והוא יכתוב מאה ביטויי factor סבירים עוד לפני ארוחת הצהריים. השאלה הקשה יותר נמצאת מתחת לפני השטח: איך אפשר לדעת אי פעם אם אחד מהם אמיתי, כאשר החיפוש שהפיק אותו הוא למעשה מכונה לייצור מנצחים מתוך רעש?
מהו גורם alpha?
Factor הוא כלל שהופך נתוני שוק למספר אחד עבור כל מניה ובכל תאריך. שינוי המחיר במהלך שנים עשר חודשים הוא factor. כך גם היחס בין החוב להון העצמי. Factor הופך לאסטרטגיה כאשר מדרגים לפיו יקום מניות, קונים את השכבה העליונה, מוכרים בחסר את השכבה התחתונה ומבצעים איזון מחדש לפי לוח זמנים. Alpha הוא התשואה שנותרת לאחר שמפחיתים את מה שחשיפה פשוטה לשוק הייתה מניבה ממילא.
המועמדים מקבלים ציון באמצעות יחס Sharpe: התשואה הממוצעת מחולקת בסטיית התקן של אותה תשואה, כשהתוצאה מותאמת לשנה. זהו מדד לתשואה לכל יחידת תנודתיות. יחס Sharpe ארוך-טווח, הקרוב ל־1 באסטרטגיה חיה, נחשב מכובד. כדאי לזכור זאת בפעם הבאה ש-backtest מציג 3.
כיצד מחקר גורמי factor באמצעות LLM פועל בפועל
כל פרויקט בתחום הזה מפעיל גרסה מסוימת של אותו מעגל.
- המודל כותב ביטויי factor בשפה קטנה שמנגנון הבדיקה מסוגל להעריך.
- מערכת backtesting מדרגת כל ביטוי על פני היסטוריה קבועה של מחירים ונתוני יסוד.
- ביטויים שעוברים סף ציון נשמרים. היתר נמחקים.
- הביטויים שנשמרו חוזרים להקשר של המודל כדוגמאות פתורות, והמעגל מתחיל מחדש.
מערכות מסחר מרובות-סוכנים מפצלות את המשימות האלה בין תפקידים נפרדים: אחד מציע ואחד בודק. התשתית הזאת שימושית באמת, והמיומנויות נתוני השוק שלהן זקוק סוכן AI הן אותן מיומנויות שנדרשות גם לאדם.
שום דבר במעגל הזה אינו בלתי-ישר. חיפוש הוא הדרך שבה מתבצע מחקר. הבעיה היא חשבונית, והיא מופיעה ברגע ששלב 2 מופעל יותר מכמה פעמים.
מדוע חיפוש גורמי alpha באמצעות LLM מייצר מנצחים
היסטוריית מחירים אחת. אלפי השערות זולות. כל השערה מקבלת ציון מול אותו מדגם סופי, והמדגם הזה מכיל מידה רבה של מזל. בודקים מספיק כללים, וחלקם יתאימו היטב למזל. הציון אינו יכול לומר איזה סוג התאמה התקבל, משום שכלל שהתאים לרעש וכלל שהתאים לשוק מפיקים אותו מספר.
להלן השערת האפס, שנדגמה 240 פעמים. כל "factor" להלן הוא הטלת מטבע: hash של הטיקר, החודש ומספר הניסוי מחלק 40 מניות אמריקאיות גדולות לשתי מחציות בכל חודש, והאסטרטגיה מחזיקה מחצית אחת בפוזיציית long ואת האחרת בפוזיציית short. אין בו מידע, מעצם הבנייה. כאשר מדרגים את הניסויים לפי תשואות אמיתיות מסוף חודש לסוף חודש, מינואר 2016 עד יוני 2021, מתקבלת התמונה הבאה.
ה-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, בלי יכולת להבחין בין גילוי אמיתי לבין מזל. התשואות כאן נמדדות מסגירת סוף חודש לסגירת סוף חודש; כיצד נמדדות תשואות חודשיות מסביר את החישוב הזה.
המספר החשוב הוא כמה ניסויים ביצעתם
דיווח על 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. הכותרת השתפרה בלי שאף כלל השתפר. מנגנון שמעריך עשרת אלפים ביטויים מריץ את העקומה הזאת הרחק ימינה מעבר לכל מה שמוצג כאן, והמספר שהוא מדווח הוא הפסגה שלה.
מה עושה תקופת בדיקה שלא נחשפה למנצחים
ההגנה המקובלת היא מדגם holdout: מדרגים תקופה אחת, ולאחר מכן מדרגים מחדש את השורדים בתקופה מאוחרת יותר שהחיפוש לא נגע בה. ניקח את שנים עשר הטלות המטבע הטובות ביותר בחלון האימון ונריץ את אותם כללים בדיוק על חמש השנים שלאחר מכן, מיולי 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כל זוג עמודות מייצג כלל אחד. העמודה השמאלית היא הציון שהעניק לו מקום בדוח. העמודה הימנית היא הציון של אותו כלל בחמש השנים הבאות. הניסוי המדורג במקום הראשון קיבל 1.59 בתקופת האימון ו־-0.51 לאחר מכן; הניסוי המדורג במקום השנים עשר קיבל 0.74 ולאחר מכן 0.49.
שנים עשר כללים הם מדגם קטן בפני עצמו. אם ממיינים את כל 240 הניסויים לחמישונים לפי ציון האימון, ואז מחשבים את ציון ה-holdout הממוצע של כל חמישון, מתקבלת תמונה נקייה יותר.
ה-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 בתחתית — סולם רחב ומסודר לחלוטין, דבר שמובטח מכיוון שהקבוצות נחתכו לפי אותו ציון עצמו. בתקופת ה-holdout, שני הקצוות הללו מציגים ממוצעים של 0.01 ו־0.13. הסולם משתטח. מדגם holdout הוא החלק היחיד בצינור העבודה שלא עבר אופטימיזציה מולו, ולכן כדאי להשתמש בו בזהירות.
הגנות שבאמת עובדות
מדגם holdout שמשתמשים בו פעם אחת. כל עיון בו הופך אותו לנתוני אימון. בדיקת walk-forward, שבה החלון נע קדימה וכל ציון מתקבל מנתונים שלאחר ההתאמה, היא הגרסה ששורדת שימוש חוזר.
תיקון לריבוי בדיקות. יחס Sharpe מנוכה, שהוצג על ידי Bailey ו־López de Prado בשנת 2014, מפחית יחס Sharpe שנצפה לפי מספר הניסויים שבוצעו, אורך המדגם, מידת הא-סימטריה של התשואות ועובי הזנבות שלהן. כאשר מזינים לו מספר ניסויים אמיתי, יחס Sharpe שיווקי מתוך חיפוש של עשרת אלפים ביטויים מתאפס לעיתים קרובות.
נתיב ביקורת המכסה כל ביטוי שנוסה, כולל אלה שנזרקו. זהו המרכיב הנושא את רוב המשקל, ולכן "ניתן לביקורת" היא המילה המעניינת בתיאור של פרויקט מחקר factor. החישוב המנוכה זקוק למספר הניסויים. צינור עבודה שמתעד רק את המנצחים השמיד את הקלט לתיקון של עצמו. טיוטות שנדחו, סריקות פרמטרים שננטשו, הפעלות מחדש של החוקר וכל גרסה מוקדמת יותר של קוד הדירוג — כולם נספרים במספר הזה.
בדיקות עלויות והטיית הצצה קדימה לפני שמאמינים לציון. Factor שדורג לפי נתון יסוד שנושא את התאריך שבו ספק הנתונים טען אותו, במקום את התאריך שבו השוק יכול היה לראות אותו, נראה מצוין ב-backtest אך נסחר גרוע.
כיצד לקרוא את המילה "ניתן לביקורת"
מאגרים חדשים בתחום הזה מופיעים כמעט מדי שבוע, ופרויקט עם כמה עשרות כוכבים הוא אב-טיפוס ולא רקורד ביצועים. גם מספר הכוכבים משתנה מהר יותר מהקוד, ולכן הדף הזה מדרג את התבנית ולא פרויקט מסוים. אלה הדברים שכדאי לבדוק תחילה בכל פרויקט שמגיע לידיכם.
- האם הוא מתעד כל מועמד, כולל הביטוי והציון שלו, עם חותמת זמן, או רק את הביטויים שנשמרו?
- האם מנגנון הבדיקה אוכף את ה-holdout, או שהדבר תלוי במשמעת העצמית של החוקר?
- האם כל ציון מדווח כולל לצדו את מספר הניסויים?
- עבור איזה שוק הוא נבנה? ספרייה שהותאמה למניות A סיניות יורשת מגבלות מחיר יומיות ואיסור למכור מניות שנרכשו באותו סשן. התנהגות factor תחת כללים אלה אינה עוברת למניות אמריקאיות.
- האם אפשר להריץ אותו מחדש ולשחזר את המספרים? יש לנעול את ה-commit המדויק שקראתם, משום שפרויקט בשלב הזה עשוי לשכתב את קוד הדירוג שלו בין סוף שבוע אחד למשנהו.
שום דבר מזה אינו הופך LLM לחסר תועלת במחקר factor. יצירת השערות היא צוואר בקבוק אמיתי, ומודלים טובים בכך. מה שמשתנה הוא המקום שבו מוטל הנטל: על החשבונאות של מספר ההשערות שנשרפו. לפני שכל דבר פוגש ספר פקודות חי, מסחר בנייר הוא המקום שבו המרחק בין backtest לבין עסקה שבוצעה נעשה גלוי.
שאלות נפוצות על גורמי alpha של LLM
האם LLM יכול למצוא גורמי alpha?
הוא יכול להציע אותם באלפים, אך הצעה אינה ממצא. הטענה נוצרת בשלב הדירוג, וציון שנשלף מחיפוש רחב נושא בעיית בחירה שהציון עצמו אינו מסוגל לראות. יש לבחון את משמעת ה-holdout ואת מספר הניסויים המתועד לפני שבוחנים את הביטוי עצמו.
מהו יחס Sharpe מנוכה?
זהו תיקון שהופך יחס Sharpe שנצפה להסתברות שחיפוש בגודל כזה היה מפיק אותו גם בלי יתרון אמיתי. Bailey ו־López de Prado פרסמו אותו בשנת 2014. הקלט המרכזי שלו הוא מספר הניסויים — בדיוק המספר שצינור עבודה מחקרי שאינו ניתן לביקורת אינו מסוגל לספק.
כמה backtests הם יותר מדי?
אין סף קבוע, אלא תיקון שחייבים להחיל. backtest אחד עם ציון 1.0 ועשרת אלפים backtests שהטוב שבהם קיבל 1.0 הם טענות שונות על העולם. הטלות המטבע לעיל הגיעו ל־1.59 לאורך 240 ניסויים, בלי שהיה מידע כלשהו בנתונים.
מדוע גורמי factor שפורסמו נחלשים לאחר פרסומם?
מחקרים אקדמיים עקבו אחר דעיכתן של אנומליות שפורסמו לאורך השנים שלאחר הופעתן. צפיפות מסחר היא מנגנון אחד שהוצע להסבר התופעה, ותוצאה מקורית שהתאימה יתר על המידה למדגם שלה היא מנגנון אחר; שניהם יוצרים צורה דומה בתרשים. השערת השוק היעיל מסבירה את הראשון, והניסויים לעיל מדגימים את השני.
כל פאנל כאן הוא שאילתה שמורה על מחירי סוף חודש אמיתיים, כאשר שאילתת ה-SQL פתוחה מתחתיו. העתיקו אחת, הגדילו את מספר הניסויים, וצפו במספר הטוב ביותר עולה בטרמינל Strasmore.