Value at Risk কীভাবে হিসাব করবেন: 3টি পদ্ধতি
একই SPY return series-এ historical, parametric ও Monte Carlo VaR হিসাব দেখুন, সঙ্গে expected shortfall কেন VaR-এর বাদ পড়া ক্ষতি প্রকাশ করে তা জানুন।
Value at risk বা VaR নির্ণয় করতে প্রথমে একটি সময়সীমা এবং একটি confidence level নির্ধারণ করা হয়। এরপর return series থেকে সংশ্লিষ্ট percentile বের করা হয়। 1-day 99% VaR 2% হলে তার অর্থ, প্রতি 100টি ট্রেডিং সেশনের মধ্যে 99টিতে ক্ষতি 2%-এর নিচে থাকবে এবং 100তম সেশনে তা 2% ছাড়িয়ে যাবে। একই input ব্যবহার করে এই প্রশ্নের উত্তর দেওয়ার জন্য তিনটি standard পদ্ধতি রয়েছে। অধিকাংশ মানুষ যতটা আশা করেন, পদ্ধতিগুলোর ফলাফলের মধ্যে তার চেয়েও বেশি পার্থক্য দেখা যায়।
ঝুঁকিতে প্রকৃতপক্ষে কী পরিমাপ করে VaR
VaR হলো ক্ষতির বণ্টনের একটি quantile। একটি নমুনায় প্রতিটি দৈনিক return খারাপ থেকে ভালো ক্রমে সাজান। এরপর খারাপ দিক থেকে 1% পথ এগিয়ে যে return-এ পৌঁছাবেন, সেটিকে positive loss হিসেবে লিখলে সেটিই 1-day 99% historical VaR। এই নির্মাণের কোথাও worst-case ফলাফলের নিশ্চয়তা নেই। এটি কেবল সেই অঞ্চলের সীমানা নির্দেশ করে, যার পর থেকে estimate আর তথ্য দিতে পারে না।
পাঠকেরা সবচেয়ে বেশি যে বৈশিষ্ট্যটি ভুলভাবে প্রয়োগ করেন, সেটি হলো এটাই। 99% VaR জানায় যে সবচেয়ে খারাপ 1% দিন threshold-এর ওপারে রয়েছে। কিন্তু কতটা ওপারে, সে বিষয়ে এটি নীরব। সর্বোচ্চ পতন ভিন্ন প্রশ্নের উত্তর দেয়: একটি portfolio বাস্তবে যে peak থেকে trough পর্যন্ত ক্ষতির মধ্য দিয়ে গেছে। একই দুইটি portfolio-কে এই দুই মাপকাঠি বিপরীত ক্রমেও স্থান দিতে পারে।
নিচের প্রতিটি figure একটি মাত্র series-এর ওপর ভিত্তি করে তৈরি: 2010 সালের শুরু থেকে 2025 সালের শেষ পর্যন্ত SPY-এর দৈনিক close, যা close-to-close percent change-এ রূপান্তরিত হয়েছে। সময়সীমাটি rolling নয়, নির্দিষ্ট। প্রতিটি panel একই তারিখগুলো থেকে series পুনর্গঠন করে। তাই বিভিন্ন run-এ সংখ্যাগুলো বদলে যায় না।
প্রতিটি সংখ্যার পেছনের সঠিক 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এই series-এ 16 calendar year এবং শুধু 2010-এ 252টি session রয়েছে। পরবর্তী সব বিশ্লেষণের জন্য এর দুটি বৈশিষ্ট্য গুরুত্বপূর্ণ। এই সময়সীমায় গড় session প্রায় অপ্রাসঙ্গিক: 2010-এ 0.054%, যেখানে দৈনিক standard deviation, sigma, হলো 1.13%। আর sigma কোনো ধ্রুবক নয়। 2020-এ এটি দৈনিক 2.11% ছিল, আর 2017-এ 0.43%। একটি sigma দিয়ে দুটিকে বর্ণনা করা যায় না।
ভ্যালু অ্যাট রিস্ক কীভাবে হিসাব করা হয়: 3টি পদ্ধতি
Historical VaR: যা ঘটেছে, তার পার্সেন্টাইল পড়ুন
প্রকৃত রিটার্নগুলো সাজিয়ে পার্সেন্টাইল নির্ধারণ করুন। কোনো distribution ধরে নেওয়া হয় না—এটাই এই পদ্ধতির প্রধান সুবিধা। তবে পদ্ধতিটি ধরে নেয়, যে সময়সীমার নমুনা নেওয়া হয়েছে তাতে অনুমানটি যে ধরনের দিন কভার করবে, সেই ধরনের দিন ইতিমধ্যেই রয়েছে। Confidence level যত দূরের দিকে নেওয়া হয়, পুরো সময়সীমার সবচেয়ে খারাপ কয়েকটি ট্রেডিং সেশনই ফল নির্ধারণ করে।
Parametric VaR: mean minus z times sigma
সিরিজটিকে mean এবং sigma দিয়ে সংক্ষেপ করুন। এরপর ধরে নিন, রিটার্ন normal distribution অনুসরণ করে। VaR হলো z times sigma minus mean, যেখানে z হলো standard normal quantile: 95%-এ 1.645, 99%-এ 2.326 এবং 99.9%-এ 3.090। হিসাবটি তাৎক্ষণিক, তবে অনুমানটি একটি নির্দিষ্ট দিক থেকে ব্যর্থ হয়। Daily equity returns normal curve-এর মধ্যভাগে বেশি ঘনভাবে জমা হয়, আবার চরম প্রান্তে অনেক দূর পর্যন্ত বিস্তৃত হয়। এখানে sigma-ই denominator, যা Sharpe ratio-তেও ব্যবহৃত হয়। তাই fat-tailed data-র ক্ষেত্রে এটিও একই blind spot তৈরি করে।
Monte Carlo VaR: seed নির্দিষ্ট রেখে simulation
একটি অনুমানভিত্তিক process থেকে বড় synthetic sample তৈরি করুন এবং সেই draws-এর percentile পড়ুন। নিচের panel-এ hash-seeded uniform sequence থেকে Box-Muller transform ব্যবহার করে তৈরি 40,000টি standard normal draw নেওয়া হয়েছে। এরপর সেগুলোকে সিরিজের mean এবং sigma অনুযায়ী scale করা হয়েছে। Seed-টি SQL-এ রাখা আছে, তাই প্রতিবার rerun করলেও draw একই থাকে। Simulation flexibility, path dependence এবং multi-asset correlation দেয়, কিন্তু realism দেয় না। এতে normal distribution দিলে sampling noise-সহ parametric ফলই ফিরে আসে। এর পরিবর্তে observed returns resample করলে, bootstrapped confidence intervals-এর পেছনে থাকা কৌশলটি প্রকৃত tail-কে হিসাবের মধ্যে রাখে।
প্রতিটি সংখ্যার পেছনের সঠিক 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% স্তরে 3টি পদ্ধতির ফল একে অপরের 1 percentage point-এর মধ্যে থাকে: 1.66% historical, 1.74% parametric, 1.71% simulated। 99.9%-এ পৌঁছালে পার্থক্য দেখা যায়: 5.85% historical বনাম 3.31% parametric; একই data-তে ব্যবধান 2.54 percentage points। প্রতিটি স্তরেই simulated column parametric column-এর পাশে থাকে। এটিই মূল শিক্ষা, ত্রুটি নয়। কোনো simulation-কে যে distribution দেওয়া হয়, সে সেটিই পুনরুৎপাদন করে।
একটি column ধরে panel-টি পড়লে দেখা যায়, confidence প্রতিটি ধাপে বাড়ার সঙ্গে threshold প্রশস্ত হয়। একটি row ধরে আড়াআড়িভাবে পড়লে বোঝা যায়, distribution-এর মধ্যভাগে method choice-এর প্রভাব সামান্য, কিন্তু tail-এ সেটিই প্রধান হয়ে ওঠে। কোনো VaR limit-এর সঙ্গে তার method এবং window উল্লেখ না থাকলে, অন্য কেউ সেই সংখ্যাটি পুনরুৎপাদন করতে পারবে না।
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%, অর্থাৎ threshold-এর 1.44 গুণ। একই স্তরে normal distribution হলে এই অনুপাত প্রায় 1.15 হওয়ার কথা। এমনকি 99.9%-এ, যখন threshold ইতিমধ্যে 5.85%-এ পৌঁছেছে, তখনও breach হওয়া দিনের গড় ক্ষতি 8.14%। শুধু VaR-এর ভিত্তিতে নির্ধারিত কোনো limit প্রতিটি breach-কে একই ঘটনা হিসেবে বিবেচনা করে। ratio column দেখায়, এই ধারণা বাস্তবে কতটা ভুল।
1-day 99% VaR কি কোনো প্রতিষ্ঠান এবং একজন ট্রেডারের জন্য একই অর্থ বহন করে?
না। এই অমিলের প্রধান কারণ হলো time horizon। রাতের জন্য পজিশন ধরে না রাখা একজন ট্রেডার কয়েক ঘণ্টার ঝুঁকি বহন করেন। তাই একটি trading session-এর threshold তার holding period-এর সঙ্গে মোটামুটি সামঞ্জস্যপূর্ণ। তবে intraday price path close-to-close হিসাবের তুলনায় অনেক বেশি দূর যেতে পারে। দীর্ঘমেয়াদি liability-এর অর্থায়নকারী একটি প্রতিষ্ঠান বছরের পর বছর পজিশন ধরে রাখে। তার exposure কয়েক quarter জুড়ে থাকে। তাই তার 1-day 99% figure বহন করা ঝুঁকির সরাসরি বিবরণ নয়; এটি মূলত capital এবং monitoring-এর একটি পরিমাপক। বহু বছর ধরে ব্যাংকের capital rules 1-day 99% VaR-এর ওপর ভিত্তি করে তৈরি ছিল। পরে Basel market risk framework এই পরিমাপককে 97.5% expected shortfall-এ পরিবর্তন করে।
বিভিন্ন time horizon-এর মধ্যে প্রচলিত সংযোগ হলো square root of time অনুযায়ী scaling। অর্থাৎ একটি session-এর figure-কে horizon-এ থাকা session সংখ্যার square root দিয়ে গুণ করতে হয়। এই পদ্ধতি ধরে নেয় যে returns পরস্পর independent এবং sigma স্থির। ওপরের year-by-year sigma column-ই দেখায়, এই অনুমানটি দ্বিতীয় অর্ধে কতটা ব্যর্থ হয়।
প্রতিটি সংখ্যার পেছনের সঠিক 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)প্রথম row-টি একটি identity check। 1 session-এ scaled figure এবং measured figure সমান, ratio 1। 20 sessions-এ গিয়ে দুটি figure আলাদা হয়ে যায়। সাধারণত যে দিকের সতর্কতা দেওয়া হয়, অমিলটি সেই দিকে নয়। এক session-এর number scaling করলে পাওয়া যায় 13.8%। ওই horizon-এ সরাসরি মাপা returns-এর figure হলো 10.7%। Ratio 0.78। এই window-তে scaled figure measured multi-session tail-এর চেয়ে বেশি।
এখানে দুটি pattern পরস্পরের বিপরীতে কাজ করছে। একটি session-এর fat-tailed distribution থেকে returns একত্র করলে tail কিছুটা পাতলা হয়। ফলে aggregated 99% quantile square root of time-এর তুলনায় ধীরে বাড়ে। Volatility clustering বিপরীত দিকে কাজ করে। একটি window-এর মধ্যে এটি একাধিক অস্থির session একসঙ্গে জমা করে। এই series-এ প্রথম pattern-টির প্রভাব বেশি। তবে অন্য কোনো window বা অন্য কোনো asset-এ কোনো দিকই নিশ্চিত নয়। এটাই মূল কথা: multiplier একটি assumption, measurement নয়। volatility targeting পজিশনের আকার পুনর্নির্ধারণের সময় এই clustering-এর বৈশিষ্ট্যের ওপরও নির্ভর করে।
আরও একটি সীমাবদ্ধতা স্পষ্টভাবে বলা দরকার। ওপরের সব হিসাব একটি broad index fund-এর single-asset VaR। দশটি correlated name নিয়ে গঠিত একটি book-এ concentration risk থাকে। Portfolio VaR correlation estimate ব্যবহার করে এই ঝুঁকি বিবেচনায় নেয়। কিন্তু estimate-টির সবচেয়ে বেশি প্রয়োজনের সময়ই correlations সবচেয়ে বেশি পরিবর্তিত হয়।
পদ্ধতি ও প্রচলিত নিয়মের নোট
- Percentile হিসাব sampled estimate নয়, exact quantile থেকে নেওয়া হয়েছে। Monte Carlo draws SQL-এ লেখা hash-seeded uniform sequence থেকে এসেছে। তাই এখানে প্রতিটি figure একই value-তে পুনরায় হিসাব করা যায়।
- Returns হলো dividend reinvestment ছাড়া close-to-close price change। এটি one-session VaR-এর প্রচলিত convention।
- Horizon panel-এ overlapping windows ব্যবহার করা হয়েছে। পরপর 20-session returns-এর মধ্যে 19 দিন অভিন্ন থাকে। তাই row count যতটা বোঝায়, বাস্তবে তার তুলনায় অনেক কম independent observation-এর ওপর tail নির্ভর করছে।
প্রায়শই জিজ্ঞাসিত প্রশ্ন
1 দিনের 99% VaR কী?
একটি সেশনের জন্য পরিমাপ করা এমন ক্ষতির মাত্রা, যা 100টি ট্রেডিং দিনের মধ্যে সবচেয়ে খারাপ 1 দিনে অতিক্রান্ত হয়। উপরের সিরিজে ঐতিহাসিক অনুমান হলো 3.09%। এই সংখ্যা একটি সীমা নির্দেশ করে; এর বেশি ক্ষতির পরিমাণ সম্পর্কে এটি কিছু বলে না।
কোন VaR গণনা-পদ্ধতি সবচেয়ে নির্ভুল?
বিমূর্তভাবে দেখলে তিনটির কোনোটিই নির্ভুল নয়, কারণ প্রতিটি পদ্ধতি ভিন্ন অনুমানের ভিত্তিতে প্রশ্নটির উত্তর দেয়। Historical VaR যে নমুনা দেওয়া হয়, তার প্রতি বিশ্বস্ত থাকে; তবে নমুনায় যা নেই, সে বিষয়ে কিছু বলে না। Parametric VaR কম খরচে হিসাব করা যায়, কিন্তু equity tail risk সাধারণত কম দেখায়। Monte Carlo ফলাফল যে distribution ব্যবহার করা হয়, তার মানের ওপরই নির্ভর করে।
VaR এবং expected shortfall-এর মধ্যে পার্থক্য কী?
নির্বাচিত confidence level-এ VaR হলো ক্ষতির সীমা। Expected shortfall সেই সীমা অতিক্রম করা দিনগুলোর ক্ষতির গড় নির্ণয় করে। এই সিরিজে 99% level-এ expected shortfall, VaR-এর 1.44 গুণ; normal distribution-এর ক্ষেত্রে তা প্রায় 1.15 গুণ।
1 দিনের VaR-কে কি 10 দিনের VaR-এ রূপান্তর করা যায়?
বর্গমূল 10 দিয়ে গুণ করা প্রচলিত shortcut। এতে ধরে নেওয়া হয় যে returns পরস্পর independent এবং volatility অপরিবর্তিত। Horizon panel-টি ব্যবধানটি দেখায়: 20টি সেশনে scaled figure ছিল 13.8%, যেখানে measured figure ছিল 10.7%।
উপরের প্রতিটি panel তৈরি করা SQL বহন করে। ভিন্ন ticker বা সময়সীমায় একই তিনটি গণনা চালাতে Strasmore terminal-এ plain English-এ অনুরোধটি লিখুন।