איך מחשבים Value at Risk: שלוש שיטות מרכזיות
מדריך לחישוב Value at Risk בשיטות היסטורית, פרמטרית ומונטה קרלו עבור SPY. הסבר על אופן המדידה ועל ה-Expected Shortfall שנותר מחוץ לחישוב ה-VaR הסטנדרטי.
ערך בסיכון (VaR)
מדד ה-Value at Risk, או VaR, מחושב על ידי קביעת טווח זמן, הגדרת רמת סמך, ולאחר מכן חילוץ אחוזון מסדרת תשואות. ערך VaR יומי של שני אחוזים ברמת סמך של תשעים ותשעה אחוזים משמעו שההפסד יישאר נמוך משני אחוזים בתשעים ותשעה מתוך כל מאה ימי מסחר, ויחרוג ממנו באחד הנותר. שלוש שיטות סטנדרטיות משיבות על שאלה זו בהתבסס על נתוני קלט זהים, והן מציגות פערים גדולים יותר ממה שרוב האנשים מצפים.
מה באמת מודד ערך בסיכון (VaR)
מדד ה-VaR הוא קוונטיל בהתפלגות הפסדים. אם נסדר את כל התשואות היומיות במדגם מהגרועה ביותר לטובה ביותר, ונתקדם אחוז אחד מהקצה השלילי, התשואה שבה נעצור – כשהיא מוצגת כהפסד חיובי – היא ה-VaR ההיסטורי ליום אחד ברמת סמך של 99%. שום דבר במבנה הזה אינו מבטיח תרחיש קיצון. המדד מסמן רק את הגבול של האזור שהאומדן מפסיק לתאר.
זהו המאפיין שקוראים נוטים להחמיץ בתדירות הגבוהה ביותר. VaR של 99% קובע כי אחוז אחד מהימים הגרועים ביותר נמצא מעבר לסף, אך הוא אינו מפרט עד כמה רחוק. ירידה מקסימלית מהשיא (Maximum drawdown) עונה על שאלה אחרת: ההפסד מהשיא לשפל שתיק השקעות חווה בפועל. שני המדדים עשויים לדרג את אותו זוג תיקים בסדר הפוך.
כל נתון להלן מתבסס על סדרה אחת: שערי הסגירה היומיים של SPY מתחילת שנת 2010 ועד סוף שנת 2025, שהומרו לשינויי אחוזים בין סגירה לסגירה. חלון הזמן קבוע ולא נע, וכל פאנל בונה מחדש את הסדרה מאותם תאריכים, כך שהמספרים אינם משתנים בין הרצות.
ה-SQL המדויק מאחורי כל מספר
WITH
px AS
(
SELECT date, max(toFloat64(close)) AS close_px
FROM global_markets.stocks_daily_aggs
WHERE ticker = 'SPY'
AND date >= '2009-12-01'
AND date < '2026-01-01'
GROUP BY date
),
rets AS
(
SELECT date, 100 * (close_px / prev_close - 1) AS ret_pct
FROM
(
SELECT date, close_px,
lagInFrame(close_px, 1) OVER (ORDER BY date ROWS BETWEEN 1 PRECEDING AND CURRENT ROW) AS prev_close
FROM px
)
WHERE date >= '2010-01-01' AND prev_close > 0
)
SELECT
toString(toYear(date)) AS year,
count() AS sessions,
round(avg(ret_pct), 3) AS mean_return_pct,
round(stddevSamp(ret_pct), 2) AS daily_sigma_pct
FROM rets
GROUP BY year
ORDER BY yearהסדרה משתרעת על פני 16 שנים קלנדריות, עם 252 ימי מסחר בשנת 2010 בלבד. שני מאפיינים שלה חשובים לכל מה שבא בהמשך. התשואה היומית הממוצעת כמעט חסרת חשיבות בטווח זמן זה: 0.054% בשנת 2010, לעומת סטיית תקן יומית, סיגמא, של 1.13%. יתרה מכך, הסיגמא אינה קבועה. בשנת 2020 היא עמדה על 2.11% ליום, ובשנת 2017 על 0.43%. סיגמא אחת אינה יכולה לתאר את שתיהן.
כיצד מחושב ערך בסיכון (VaR), שלוש דרכים
VaR היסטורי: קריאת האחוזון מתוך נתונים קיימים
ממיינים את התשואות בפועל ובוחרים את האחוזון המתאים. השיטה אינה מניחה התפלגות כלשהי, וזהו יתרונה המרכזי. מה שהשיטה כן מניחה הוא שהמדגם כבר כולל את סוג היום שהאומדן נועד לכסות. אם דוחפים את רמת הסמך רחוק מספיק, רק ימי המסחר הגרועים ביותר בתוך כל חלון הזמן יכתיבו את התוצאה.
VaR פרמטרי: ממוצע פחות z כפול סטיית תקן
מסכמים את הסדרה באמצעות הממוצע וסטיית התקן שלה, ומניחים שהתשואות מתפלגות נורמלית. ה-VaR הוא z כפול סטיית התקן פחות הממוצע, כאשר z הוא הקוונטיל הנורמלי הסטנדרטי: 1.645 ברמת סמך של 95%, 2.326 ב-99%, ו-3.090 ב-99.9%. החישוב מיידי, אך ההנחה נכשלת בכיוון מסוים. תשואות יומיות של מניות מתרכזות בצפיפות רבה יותר סביב הממוצע מאשר עקומת התפלגות נורמלית, ומגיעות רחוק יותר בקצוות. סטיית התקן כאן היא המכנה שמופיע גם ב-מדד שארפ, והיא מביאה עמה את אותו עיוורון לנתונים בעלי זנבות עבים (fat-tailed).
VaR מונטה קרלו: סימולציה עם "זרע" קבוע
מייצרים מדגם סינתטי גדול מתוך תהליך מוגדר וקוראים את האחוזון מתוך התוצאות. הלוח להלן משתמש ב-40,000 דגימות נורמליות סטנדרטיות שנבנו באמצעות טרנספורמציית Box-Muller מתוך רצף אחיד בעל "זרע" (seed) קבוע, ומותאמות לממוצע ולסטיית התקן של הסדרה. הזרע מוטמע ב-SQL, כך שהדגימות זהות בכל הרצה מחדש. סימולציה מעניקה גמישות, תלות במסלול ומתאם בין נכסים מרובים, אך לא בהכרח ריאליזם: אם מזינים לה התפלגות נורמלית, היא מחזירה את התוצאה הפרמטרית בתוספת רעש דגימה. לעומת זאת, דגימה מחדש (resampling) של התשואות שנצפו – הטכניקה שמאחורי רווחי סמך בשיטת ה-bootstrapping – שומרת על הזנבות האמיתיים בתוך החישוב.
ה-SQL המדויק מאחורי כל מספר
WITH
px AS
(
SELECT date, max(toFloat64(close)) AS close_px
FROM global_markets.stocks_daily_aggs
WHERE ticker = 'SPY'
AND date >= '2009-12-01'
AND date < '2026-01-01'
GROUP BY date
),
rets AS
(
SELECT date, 100 * (close_px / prev_close - 1) AS ret_pct
FROM
(
SELECT date, close_px,
lagInFrame(close_px, 1) OVER (ORDER BY date ROWS BETWEEN 1 PRECEDING AND CURRENT ROW) AS prev_close
FROM px
)
WHERE date >= '2010-01-01' AND prev_close > 0
),
emp AS
(
SELECT
avg(ret_pct) AS mu,
stddevSamp(ret_pct) AS sd,
quantileExact(0.100)(ret_pct) AS h90,
quantileExact(0.050)(ret_pct) AS h95,
quantileExact(0.025)(ret_pct) AS h975,
quantileExact(0.010)(ret_pct) AS h99,
quantileExact(0.005)(ret_pct) AS h995,
quantileExact(0.001)(ret_pct) AS h999
FROM rets
),
draws AS
(
SELECT
quantileExact(0.100)(z) AS z90,
quantileExact(0.050)(z) AS z95,
quantileExact(0.025)(z) AS z975,
quantileExact(0.010)(z) AS z99,
quantileExact(0.005)(z) AS z995,
quantileExact(0.001)(z) AS z999
FROM
(
SELECT sqrt(-2 * log(u1)) * cos(2 * pi() * u2) AS z
FROM
(
SELECT
(cityHash64('var-seed-u1', i) % 999999937 + 1) / 999999938.0 AS u1,
(cityHash64('var-seed-u2', i) % 999999937 + 1) / 999999938.0 AS u2
FROM (SELECT arrayJoin(range(40000)) AS i)
)
)
)
SELECT
tupleElement(lvl, 1) AS confidence,
round(-1 * tupleElement(lvl, 2), 2) AS historical_var_pct,
round(tupleElement(lvl, 3) * sd - mu, 2) AS parametric_var_pct,
round(-1 * (mu + sd * tupleElement(lvl, 4)), 2) AS monte_carlo_var_pct,
round(-1 * tupleElement(lvl, 2) - (tupleElement(lvl, 3) * sd - mu), 2) AS method_spread
FROM
(
SELECT
mu,
sd,
arrayJoin([
('90.0%', h90, 1.281552, z90, 1),
('95.0%', h95, 1.644854, z95, 2),
('97.5%', h975, 1.959964, z975, 3),
('99.0%', h99, 2.326348, z99, 4),
('99.5%', h995, 2.575829, z995, 5),
('99.9%', h999, 3.090232, z999, 6)
]) AS lvl
FROM emp
CROSS JOIN draws
)
ORDER BY tupleElement(lvl, 5)ברמת 95.0% שלוש השיטות מניבות תוצאות קרובות מאוד: 1.66% בשיטה ההיסטורית, 1.74% בפרמטרית, ו-1.71% בסימולציה. ברמת 99.9% הדרכים נפרדות: 5.85% בשיטה ההיסטורית לעומת 3.31% בפרמטרית, פער של 2.54 נקודות אחוז על אותם נתונים בדיוק. העמודה הסימולטיבית ניצבת לצד העמודה הפרמטרית בכל רמה, וזהו הלקח המרכזי ולא פגם בשיטה. סימולציה משחזרת את ההתפלגות שהוזנה לה.
קריאת הלוח לאורך עמודה מראה כיצד כל עלייה ברמת הסמך מרחיבה את הסף. קריאת הלוח לרוחב שורה מראה כי בחירת השיטה כמעט אינה מורגשת במרכז ההתפלגות, אך היא דומיננטית בקצוות. מגבלת VaR שמוצגת ללא ציון השיטה וחלון הזמן שלה אינה מספר שמישהו אחר יכול לשחזר.
מה VaR לא אומר לכם: ה-Expected Shortfall
ה-Expected Shortfall, המכונה גם Conditional VaR, מחשב את ממוצע ההפסדים בימים שבהם נחצה רף ה-VaR. בעוד שה-VaR מסמן את הנקודה שבה מתחיל הזנב של התפלגות ההפסדים, ה-Expected Shortfall מודד את מה שנמצא בתוכו.
ה-SQL המדויק מאחורי כל מספר
WITH
px AS
(
SELECT date, max(toFloat64(close)) AS close_px
FROM global_markets.stocks_daily_aggs
WHERE ticker = 'SPY'
AND date >= '2009-12-01'
AND date < '2026-01-01'
GROUP BY date
),
rets AS
(
SELECT date, 100 * (close_px / prev_close - 1) AS ret_pct
FROM
(
SELECT date, close_px,
lagInFrame(close_px, 1) OVER (ORDER BY date ROWS BETWEEN 1 PRECEDING AND CURRENT ROW) AS prev_close
FROM px
)
WHERE date >= '2010-01-01' AND prev_close > 0
),
qs AS
(
SELECT
quantileExact(0.100)(ret_pct) AS q90,
quantileExact(0.050)(ret_pct) AS q95,
quantileExact(0.025)(ret_pct) AS q975,
quantileExact(0.010)(ret_pct) AS q99,
quantileExact(0.005)(ret_pct) AS q995,
quantileExact(0.001)(ret_pct) AS q999
FROM rets
),
tails AS
(
SELECT
any(q90) AS var90,
any(q95) AS var95,
any(q975) AS var975,
any(q99) AS var99,
any(q995) AS var995,
any(q999) AS var999,
avgIf(ret_pct, ret_pct <= q90) AS es90,
avgIf(ret_pct, ret_pct <= q95) AS es95,
avgIf(ret_pct, ret_pct <= q975) AS es975,
avgIf(ret_pct, ret_pct <= q99) AS es99,
avgIf(ret_pct, ret_pct <= q995) AS es995,
avgIf(ret_pct, ret_pct <= q999) AS es999
FROM rets
CROSS JOIN qs
HAVING countIf(ret_pct <= q999) > 0
)
SELECT
tupleElement(lvl, 1) AS confidence,
round(-1 * tupleElement(lvl, 2), 2) AS historical_var_pct,
round(-1 * tupleElement(lvl, 3), 2) AS expected_shortfall_pct,
round(tupleElement(lvl, 3) / tupleElement(lvl, 2), 2) AS es_to_var_ratio
FROM
(
SELECT
arrayJoin([
('90.0%', var90, es90, 1),
('95.0%', var95, es95, 2),
('97.5%', var975, es975, 3),
('99.0%', var99, es99, 4),
('99.5%', var995, es995, 5),
('99.9%', var999, es999, 6)
]) AS lvl
FROM tails
)
ORDER BY tupleElement(lvl, 4)ב-99.0%, ה-VaR של סדרה זו עומד על 3.09% וה-Expected Shortfall עומד על 4.43%, או פי 1.44 מהרף. תחת התפלגות נורמלית, יחס זה היה עומד על כ-1.15 באותה רמת מובהקות. גם ב-99.9%, שבו הרף כבר הגיע ל-5.85%, יום החריגה הממוצע מסתכם ב-8.14%. מגבלה המבוססת על VaR בלבד מתייחסת לכל חריגה כאל אירוע זהה, ועמודת היחס מודדת עד כמה הנחה זו אינה נכונה.
האם מדד VaR יומי ברמת מובהקות של 99% זהה עבור מוסד פיננסי ועבור סוחר?
לא, והפער נובע בעיקר מאופק הזמן. סוחר שסוגר פוזיציות לפני סוף יום המסחר חשוף לסיכון למשך שעות בלבד, ולכן סף של יום מסחר אחד תואם את תקופת ההחזקה שלו, אף שהתנודתיות התוך-יומית עשויה לחרוג משמעותית מהנתון הנמדד בין סגירה לסגירה. מוסד פיננסי המממן התחייבויות ארוכות טווח מחזיק בפוזיציות במשך שנים, החשיפה שלו נפרסת על פני רבעונים, ונתון ה-VaR היומי שלו משמש ככלי לניהול הון ובקרה ולא כתיאור מדויק של הסיכון הממשי שהוא נושא. כללי הלימות ההון של בנקים התבססו במשך שנים על VaR יומי של 99%, אך מסגרת סיכוני השוק של באזל עברה מאוחר יותר למדד של Expected Shortfall ברמת 97.5%.
הגשר המקובל בין אופקי זמן שונים הוא כיול באמצעות שורש ריבועי של הזמן: מכפילים את הנתון היומי בשורש הריבועי של מספר ימי המסחר באופק הנמדד. צעד זה מניח שהתשואות הן בלתי תלויות ובעלות סטיית תקן קבועה, אך עמודת סטיית התקן השנתית לעיל כבר מדגימה כיצד הנחה זו אינה מתקיימת במציאות.
ה-SQL המדויק מאחורי כל מספר
WITH
px AS
(
SELECT date, max(toFloat64(close)) AS close_px
FROM global_markets.stocks_daily_aggs
WHERE ticker = 'SPY'
AND date >= '2009-11-01'
AND date < '2026-01-01'
GROUP BY date
),
multi AS
(
SELECT
date,
100 * (close_px / p1 - 1) AS r1,
100 * (close_px / p5 - 1) AS r5,
100 * (close_px / p10 - 1) AS r10,
100 * (close_px / p20 - 1) AS r20
FROM
(
SELECT
date,
close_px,
lagInFrame(close_px, 1) OVER (ORDER BY date ROWS BETWEEN 20 PRECEDING AND CURRENT ROW) AS p1,
lagInFrame(close_px, 5) OVER (ORDER BY date ROWS BETWEEN 20 PRECEDING AND CURRENT ROW) AS p5,
lagInFrame(close_px, 10) OVER (ORDER BY date ROWS BETWEEN 20 PRECEDING AND CURRENT ROW) AS p10,
lagInFrame(close_px, 20) OVER (ORDER BY date ROWS BETWEEN 20 PRECEDING AND CURRENT ROW) AS p20
FROM px
)
WHERE date >= '2010-01-01' AND p20 > 0
),
q AS
(
SELECT
quantileExact(0.01)(r1) AS q1,
quantileExact(0.01)(r5) AS q5,
quantileExact(0.01)(r10) AS q10,
quantileExact(0.01)(r20) AS q20
FROM multi
)
SELECT
tupleElement(h, 1) AS horizon,
round(-1 * tupleElement(h, 3), 2) AS actual_var_pct,
round(-1 * q1 * sqrt(tupleElement(h, 2)), 2) AS sqrt_scaled_var_pct,
round(tupleElement(h, 3) / (q1 * sqrt(tupleElement(h, 2))), 2) AS actual_to_scaled_ratio
FROM
(
SELECT
q1,
arrayJoin([
('1 session', 1.0, q1, 1),
('5 sessions', 5.0, q5, 2),
('10 sessions', 10.0, q10, 3),
('20 sessions', 20.0, q20, 4)
]) AS h
FROM q
)
ORDER BY tupleElement(h, 4)השורה הראשונה היא בדיקת זהות: ב-1 session הנתון המכויל שווה לנתון הנמדד, יחס של 1. בטווח של 20 sessions השניים נפרדים, ולא בכיוון שבו נהוג להזהיר מפני קיצורי דרך: כיול הנתון היומי מניב 13.8%, בעוד שהתשואות שנמדדו בפועל על פני אופק זה מציגות 10.7%, יחס של 0.78. בטווח זה, הנתון המכויל גבוה מהזנב הנמדד של ריבוי ימים. שני דפוסים פועלים כאן בכיוונים מנוגדים. התפלגות בעלת זנבות עבים ביום בודד הופכת ל"דקה" יותר ככל שמצרפים תשואות, והקוונטיל של 99% המצטבר מתרחב לאט יותר מהשורש הריבועי של אופק הזמן. מנגד, התקבצות תנודתיות (volatility clustering) פועלת בכיוון ההפוך, ומקבצת ימים סוערים בתוך חלון זמן אחד. בסדרה זו, הדפוס הראשון הוא הדומיננטי, אך אין ערובה לכך שזה יהיה המצב בנכס אחר או בחלון זמן אחר; זו בדיוק הנקודה: המכפיל הוא הנחה, לא מדידה. התקבצות זו היא גם המאפיין שעליו נשענת אסטרטגיית volatility targeting בעת התאמת גודל הפוזיציות.
מגבלה נוספת, נאמרת בבירור: כל האמור לעיל מתייחס ל-VaR של נכס בודד בקרן מדד רחבה. תיק המורכב מעשרה נכסים מתואמים נושא סיכון ריכוזיות ש-VaR תיקי מטפל בו באמצעות אומדן מתאם, והמתאמים נוטים לזנק בדיוק ברגע שבו האומדן הופך לקריטי.
הערות מתודולוגיות ומוסכמות
- אחוזונים נגזרים מקוונטיל מדויק ולא מאומדן דגימה, וסימולציות מונטה קרלו מבוססות על רצף אקראי אחיד עם "זרע" (seed) קבוע המוזן ל-SQL, כך שכל נתון כאן מחושב מחדש לאותו ערך.
- התשואות מבוססות על שינויי מחיר בין סגירה לסגירה ללא השקעה חוזרת של דיבידנדים, המוסכמה המקובלת ל-VaR יומי.
- לוח אופקי הזמן משתמש בחלונות חופפים: תשואות של עשרים ימי מסחר עוקבים חולקות תשעה-עשר ימים משותפים, ולכן הזנב שלהן נשען על מספר קטן בהרבה של תצפיות בלתי תלויות ממה שמרמז מספר השורות.
שאלות נפוצות
מהו מדד VaR יומי ברמת סמך של 99%?
זהו שיעור ההפסד שיום מסחר אחד מתוך מאה ימי מסחר גרועים צפוי לעבור, בחישוב של יום מסחר בודד. בסדרה שלעיל, האומדן ההיסטורי הוא 3.09%. הנתון מציין סף בלבד ואינו מספק מידע על גודל ההפסדים מעבר לסף זה.
איזו שיטת חישוב VaR היא המדויקת ביותר?
אף אחת משלוש השיטות אינה מדויקת באופן מופשט, שכן כל אחת מהן עונה על השאלה תחת הנחות שונות. VaR היסטורי נאמן למדגם שניתן לו ואינו מתייחס לכל מה שהמדגם החמיץ. VaR פרמטרי הוא זול ונוטה להמעיט בערך הזנבות של מניות. מודל Monte Carlo טוב רק כטיב ההתפלגות המוזנת לתוכו.
מהו ההבדל בין VaR לבין Expected Shortfall?
VaR הוא סף ברמת סמך נבחרת. Expected Shortfall מחשב את ממוצע ההפסדים בימים שבהם הסף נפרץ. בסדרה זו, ה-Expected Shortfall ברמת סמך של 99% עומד על פי 1.44 מה-VaR, לעומת יחס של 1.15 בקירוב תחת התפלגות נורמלית.
האם ניתן להמיר VaR יומי ל-VaR ל-10 ימים?
הכפלה בשורש ריבועי של 10 היא קיצור הדרך המקובל, והיא מניחה שהתשואות בלתי תלויות ובעלות תנודתיות קבועה. לוח האופק מודד את הפער: לאורך 20 ימי מסחר, הנתון המחושב לפי הקיצור עמד על 13.8% לעומת 10.7% במדידה בפועל.
כל לוח לעיל כולל את שאילתת ה-SQL שיצרה אותו. כדי להריץ את אותם שלושה חישובים על Ticker או טווח זמן אחר, יש לבקש זאת באנגלית פשוטה במסוף Strasmore.