Strasmore Research
Deep Dives · Matt ConnorBy Matt Connor ·

הטיית הסתכלות קדימה בקטסט

הטיית הסתכלות קדימה (Look-ahead bias) היא דליפת מידע עתידי לבדיקה לאחור. שלושה מנגנונים: החלטות על אותו בר, הישרדות יקום, ונתונים מתוקנים. איך לזהות ולמדוד.

הטיית הסתכלות קדימה (Look-ahead bias) היא שימוש במידע בתוך בדיקה לאחור (בקטסט) שאיש לא יכול היה להחזיק בו ברגע שבו בוצעה העסקה המדומה. עקומת ההון מטפסת, הסטטיסטיקות נראות מצוינות, ואף אחת מהן לא שורדת מגע עם פקודה חיה. דף זה מגדיר את ההטיה, עובר על שלושה מנגנונים שמייצרים אותה, ומודד כל אחד מהם על נתוני שוק אמיתיים.

מהי הטיית הסתכלות קדימה

בקטסט הוא טענה לגבי החלטה: בזמן זה, עם מידע זה, הכלל היה עושה זאת. הטענה תקפה רק כאשר כל קלט שהכלל קורא כבר היה קיים, בצורתו הסופית, לפני אותו חותם זמן. הטיית הסתכלות קדימה היא כל הפרה של תנאי זה, והיא שקטה. שום דבר בגיליון אלקטרוני או בסקריפט לא מתלונן כאשר שורה מחזירה ערך שמוטבע מאוחר יותר מההחלטה שהוא מזין.

שלוש הפרות מכסות את רוב מה שמופיע בפועל. החלטה שקוראת בר מחיר שהיא גם סוחרת בתוכו. יקום המורכב מהשמות שעדיין רשומים היום. שדה נתונים שתוקן לאחר התאריך שהוא נושא.

מנגנון ראשון: החלטה על בר שאתה גם סוחר

התחל עם כלל שנשמע לא מזיק. קנה בחוזק, כלומר במועדים שבהם הקרן מסיימת מעל למחיר הפתיחה שלה. מקודד ברשלנות, הבדיקה נכנסת בפתיחה של אותו מועד ויוצאת בסגירה של אותו מועד, בעוד שהתנאי שהיא מסננת עליו הוא מחיר הסגירה של אותו בר. איש אינו יודע מחיר סגירה בפעמון הפתיחה.

הפאנל שלהלן מריץ את שתי הגרסאות על SPY, עוקב S&P 500, על פני עשר שנים קלנדריות. העמודה הראשונה היא הבלתי אפשרית. השנייה מעכבת את אותו תנאי במועד בודד, שהוא המוקדם ביותר שחשבון ממומן יכול היה לפעול על פיו.

שאילתההחלטה באותו מול השהיה של מושב אחד: SPY, רווח ממוצע למושב, 2016-2025
ה-SQL המדויק מאחורי כל מספר
WITH bars AS (
    SELECT date,
           toFloat64(open) AS o,
           toFloat64(close) AS c,
           toFloat64(close) > toFloat64(open) AS up_day
    FROM global_markets.stocks_daily_aggs
    WHERE ticker = 'SPY'
      AND date >= toDate('2015-12-01')
      AND date <= toDate('2025-12-31')
),
lagged AS (
    SELECT date, o, c, up_day,
           any(up_day) OVER (ORDER BY date ROWS BETWEEN 1 PRECEDING AND 1 PRECEDING) AS prior_up
    FROM bars
)
SELECT toYear(date) AS year,
       countIf(up_day) AS signal_days,
       round(100 * avgIf(c / o - 1, up_day), 3) AS same_bar_avg_pct,
       round(100 * avgIf(c / o - 1, prior_up), 3) AS lagged_avg_pct,
       round(100 * (avgIf(c / o - 1, up_day) - avgIf(c / o - 1, prior_up)), 3) AS gap_pp
FROM lagged
WHERE date >= toDate('2016-01-01')
GROUP BY year
ORDER BY year
Run this yourself

ההפרדה אינה עדינה. ב-2016 הגרסה על אותו בר הניבה בממוצע 0.447% למועד כשיר בעוד שהגרסה המושהית הניבה בממוצע -0.008%, פער של 0.455 נקודות אחוז ליום. ב-2025 השניים קראו 0.636% ו--0.03%. עם בערך 140 מועדים כשירים בשנה, פער של חצי נקודת אחוז למועד מצטבר לעקומת הון שאף חשבון חי לא הדפיס מעולם.

כל היתרון הוא הגדרתי. בחירת מועדים שנסגרו מעל הפתיחה שלהם, ולאחר מכן מדידת פתיחה-עד-סגירה באותם מועדים, מודדת כמות שמובטחת חיובית. הכלל גילה את הסינון העצמי שלו.

וריאציות של זה מסתתרות בנתונים יומיים כל הזמן: ממוצע נע שכולל את הסגירה של היום ומשמש למסחר היום, אות המחושב על מחירים שכבר הותאמו לפיצול שעדיין לא התרחש, עצירה תוך-יומית המוצבת בגובה מועד שעדיין לא הודפס. הצורה המשותפת היא חותם זמן החלטה שנמצא מוקדם יותר מאחד מהתשומות שלו עצמו.

מנגנון שני: יקום הנגזר מהשורדים של היום

המנגנון השני לעולם לא נוגע בכלל. הוא יושב ברשימת השמות שהכלל רשאי לראות.

משוך רשימת סימולים מספק נתונים נוכחי, הרץ אותה עשר שנים אחורה, והמדגם שותק מוציא כל חברה שפשטה רגל, נרכשה, או נמחקה מהמסחר בדרך. הכלל מעולם לא הוצע למפסידנים. הפאנל מודד את גודל ההשמטה הזו: כל שם הרשום בארה"ב שנסחר לפחות 200 מועדים מעל $1 בשנה קלנדרית נתונה, חלקה של קבוצה זו שעדיין מדפיס מחירים ביולי 2026, והתשואה החציונית של כל הקבוצה לשנה קלנדרית לצד התשואה החציונית של השורדים בלבד.

שאילתההישרדות ביקום: שמות הנסחרים בכל שנה, חלקם עדיין רשומים ביולי 2026, ותשואה חציונית
ה-SQL המדויק מאחורי כל מספר
WITH recent AS (
    SELECT DISTINCT ticker
    FROM global_markets.stocks_daily_aggs
    WHERE date >= toDate('2026-06-15')
      AND date <= toDate('2026-07-28')
),
per_name AS (
    SELECT toYear(date) AS year,
           ticker,
           argMin(toFloat64(close), date) AS first_close,
           argMax(toFloat64(close), date) AS last_close,
           count() AS sessions
    FROM global_markets.stocks_daily_aggs
    WHERE date >= toDate('2015-01-01')
      AND date <= toDate('2024-12-31')
      AND close > 1
    GROUP BY year, ticker
    HAVING sessions >= 200
)
SELECT year,
       uniqExact(ticker) AS names_trading,
       round(100 * uniqExactIf(ticker, ticker IN (SELECT ticker FROM recent)) / uniqExact(ticker), 1) AS still_listed_pct,
       round(100 * quantileDeterministic(0.5)(last_close / first_close - 1, cityHash64(ticker)), 2) AS median_return_all_pct,
       round(100 * quantileDeterministicIf(0.5)(last_close / first_close - 1, cityHash64(ticker), ticker IN (SELECT ticker FROM recent)), 2) AS median_return_survivors_pct,
       round(100 * (quantileDeterministicIf(0.5)(last_close / first_close - 1, cityHash64(ticker), ticker IN (SELECT ticker FROM recent))
                    - quantileDeterministic(0.5)(last_close / first_close - 1, cityHash64(ticker))), 2) AS survivor_gap_pp
FROM per_name
GROUP BY year
ORDER BY year
Run this yourself

מתוך 7063 השמות שעברו את הרף ב-2015, 52.5% עדיין נסחרו ביולי 2026. עבור קבוצת 2024 הנתון הוא 87.5%, בעיקר עניין של זמן שחלף: לשם מ-2024 היו שנתיים להיעלם ולשם מ-2015 היו אחת-עשרה.

עמודות התשואה נושאות את ההטיה עצמה. ב-2017 השם החציוני השיב 7.51% בעוד שהשורד החציוני השיב 11.32%, פער של 3.81 נקודות אחוז שניתן בחינם. הפער חיובי בכל אחת מ-10 השנים המצוירות. אסטרטגיה שנבדקה על שורדים יורשת אותו לפני קבלת החלטה אחת, וכללים שמסננים לחולשה יורשים יותר מהחציון, מכיוון שהשמות שנעלמו יושבים באופן לא פרופורציונלי בדלי הזה.

התיקון הוא יקום נקודתי בזמן: רשימת השמות כפי שעמדה בכל תאריך איזון מחדש, כולל מחיקות, כאשר עמדה שנמחקה נסגרת בהדפסה האחרונה שלה במקום להישמט מהרשומה.

מנגנון שלישי: נתונים שתוקנו לאחר חותם התאריך שלהם

מחירים מוטבעים פעם אחת. נתונים פונדמנטליים לא. נתון רבעוני נושא את תאריך סיום הרבעון, מתוקן על ידי דיווח מאוחר יותר, ומסד נתונים שאוגר רק את הגרסה הנוכחית ישרת בשמחה את הנתון המתוקן מול התאריך המקורי. כלל שמסנן על נתון זה קורא תיקון שלקח חודשים להגיע.

אותו דפוס עובר דרך רשימות חברות מדד שנבנו מחדש למרכיבים נוכחיים, היסטוריות אומדני אנליסטים שנכתבו מחדש עם הקונצנזוס הסופי, והתאמות לפעולות תאגיד שהוחלו אחורה על פני סדרת מחירים שלמה. בכל מקרה הרשומה המאוחסנת היא תמונת מצב בהווה הנושאת תאריך עבר.

שני נוהלים מכילים זאת. השתמש במקור ששומר בצירי זמן, כלומר הערך כפי שהיה ידוע בכל תאריך ולא הערך כפי שהוא ידוע כעת. היכן שבצירי זמן אינם זמינים, החל פיגור דיווח נדיב מספיק כדי לכסות את האמיתי, והתייחס לאורכו של פיגור זה כפרמטר ששווה לבדוק ולא כפרט ששווה לנחש.

איך נראית העקומה כשהעתיד דולף פנימה

להטיית הסתכלות קדימה יש חתימה: ביצועים שיושבים הרבה מעל למה שהשוק הבסיסי הציע. נקודת ייחוס שימושית היא תקרת הראייה לאחור, הצמיחה שתחזית מושלמת של יום אחד קדימה הייתה מייצרת על מכשיר בודד.

שאילתהתקרת הרטרוספקטיבה: קנייה והחזקה של SPY, אותה שנה ללא ימי העלייה הגדולים ביותר, וראיית הנולד המושלמת ליום אחד
ה-SQL המדויק מאחורי כל מספר
WITH bars AS (
    SELECT date,
           toFloat64(close) AS c,
           any(toFloat64(close)) OVER (ORDER BY date ROWS BETWEEN 1 PRECEDING AND 1 PRECEDING) AS prev_c
    FROM global_markets.stocks_daily_aggs
    WHERE ticker = 'SPY'
      AND date >= toDate('2015-12-01')
      AND date <= toDate('2025-12-31')
),
rets AS (
    SELECT toYear(date) AS year,
           c / prev_c - 1 AS r
    FROM bars
    WHERE date >= toDate('2016-01-01')
      AND prev_c > 0
)
SELECT year,
       count() AS sessions,
       round(100 * (exp(sum(log(1 + r))) - 1), 1) AS buy_and_hold_pct,
       round(100 * (exp(sumIf(log(1 + r), r < 0.02)) - 1), 1) AS without_big_up_days_pct,
       round(exp(sum(log(1 + abs(r)))), 1) AS hindsight_ceiling_multiple
FROM rets
GROUP BY year
ORDER BY year
Run this yourself

התקרה עצומה. ב-2020, ראייה מושלמת של יום אחד קדימה על SPY הכפילה הון פי 27.7 על פני 253 מועדים, לעומת 16.2% עבור החזקה פשוטה של הקרן. בשנה הרגועה ביותר מבין העשר, 2017, אותה תחזית מושלמת השיבה פי 2.2 מההון.

העמודה האמצעית מראה עד כמה מרוכזת תקרה זו. הפשט את המועדים שעלו ביותר מ-2%, ו-2020 הופך מ-16.2% ל--44.9%. דליפה ששווה רק לקומץ מועדים כאלה בשנה מזיזה בקטסט רחוק מאוד. עקומה עם משיכות רדודות, אחוזי הצלחה גבוהים, והימים הטובים ביותר שלה מרוכזים במועדים האלימים ביותר של השוק, זכאית לביקורת.

כיול חשוב יותר מכל סף בודד. כלל הטוען ל-40% חלקים בשנה במשך עשור טוען לחלק גדול מתקרת הראייה לאחור, והשאלה הכנה היא איזה קלט אמר לו מה עומד לקרות.

כיצד לבדוק בקטסט להטיית הסתכלות קדימה

  • עכב הכל בבר אחד. הזז כל אות תקופה מלאה אחת מאוחר יותר והרץ מחדש. יתרונות אמיתיים מתדרדרים מעט. דליפות קורסות, לעתים קרובות לכלום.
  • הפרע את העתיד. קח את הנתונים לאחר כל חותם זמן החלטה, החלף אותם ברעש או בערבוב מחדש, והרץ מחדש את לוגיקת ההחלטה. כל החלטה צריכה לצאת זהה. כל החלטה שזזה קראה משהו שלא יכלה לדעת.
  • התייחס לחותמי זמן כנתונים. עבור כל קלט, אחסן את הרגע שבו ערך הפך זמין לצד הרגע שהוא מתאר. צירוף שלעולם לא משווה בין שתי עמודות אלו לא יכול לאכוף את הכלל.
  • בנה מחדש את היקום נכון לתאריך. כלול שמות שנמחקו ונרכשו, סגור אותם בהדפסה האחרונה שלהם, והרץ את אותו כלל על שני היקומים כדי למדוד את תרומת ההישרדות.
  • מדוד מול התקרה. שים את תשואת האסטרטגיה לצד נתון קנה והחזק ונתון ראייה מושלמת לאותו חלון. מרחק מהתקרה הוא הקשר שיחס שארפ לבדו אינו נותן.

קביעת גודל הפוזיציה נמצאת במורד הזרם של כל זה. אחוז הצלחה מנופח מזין גודל הימור מנופח, וזה המקום שבו קביעת גודל פוזיציה לפי קריטריון קלי הופך טעות חשבונאית למשיכה. אותו משמעת חלה לפני שההון האמיתי זז: מסחר בנייר לפני כסף אמיתי בודק מכניקת פקודות, וערימות אוטומטיות נושאות את אותו סיכון במהירות מכונה, מכוסה ב-מערכות מסחר בבינה מלאכותית מרובת סוכנים. השפעות ששורדות בדיקות נקודתיות קפדניות, כגון אנומליית התנודתיות הנמוכה, מעניינות בדיוק מסיבה זו.

שאלות נפוצות

מהי הטיית הסתכלות קדימה בבקטסט?

זהו שימוש בנתונים בעסקה מדומה שלא היו זמינים כאשר העסקה הייתה ממוקמת. צורות נפוצות הן החלטה על בר מחיר שהעסקה גם מבצעת בתוכו, סינון יקום של חברות שעדיין רשומות היום, וקריאת נתון פיננסי שתוקן לאחר התאריך המצורף אליו.

כיצד ניתן לזהות הטיית הסתכלות קדימה?

עכב כל אות בתקופה מלאה אחת והרץ מחדש: דליפה בדרך כלל קורסת בעוד שיתרון אמיתי מתדרדר בעדינות. מבחן חזק יותר מחליף את כל הנתונים לאחר כל חותם זמן החלטה ברעש ומאשר שכל החלטה יוצאת ללא שינוי.

האם הטיית הישרדות זהה להטיית הסתכלות קדימה?

הן נפרדות אך הן נוסעות יחד. הטיית הישרדות היא מדגם הבנוי מהשמות שהחזיקו מעמד, וידיעה אילו שמות החזיקו מעמד היא בעצמה מידע מהעתיד, ולכן יקום של שורדים בלבד הוא דליפת הסתכלות קדימה הממוקמת בנתונים ולא בכלל.

מדוע בקטסטים עם הטיית הסתכלות קדימה נראים כל כך טוב?

הדליפה בוחרת תוצאות במקום לחזות אותן. בפאנל למעלה, סינון מועדים על מחיר הסגירה שלהם עצמו הפיק רווח מועד ממוצע של כ-0.455 נקודות אחוז מעל הגרסה המושהית ב-2016, יתרון שהוא הגדרתי ולא מושג.

מהם נתונים נקודתיים בזמן?

נתונים נקודתיים בזמן אוגרים כל ערך כפי שהיה ידוע בכל תאריך היסטורי, כולל הנתון המקורי וכל תיקון מאוחר יותר, לצד יקום ניירות הערך כפי שעמד באותו יום. זוהי ההתייחסות שבקטסט צריך כדי להראות שהתשומות שלו היו קיימות לפני ההחלטות שלו.


כל נתון לעיל הוא שאילתה מאוחסנת ומגוונת על פני ברים יומיים אמיתיים. הרחב את ה-SQL של כל פאנל, או הרץ את מבחן הפיגור על הכלל שלך בטרמינל סטרסמור.

#backtesting#look-ahead bias#survivorship bias#quant#research process