1+ # Value at Risk (VaR) and Expected Shortfall (ES) Calculator
2+ # Implements multiple VaR calculation methods and Expected Shortfall
3+ # Features: Historical, Parametric, and Monte Carlo VaR/ES calculations
4+
5+ library(R6 )
6+
7+ # ' RiskMetrics Class
8+ # ' @description R6 class for calculating Value at Risk and Expected Shortfall
9+ # ' @details Implements multiple methods for VaR and ES calculation:
10+ # ' - Historical simulation (non-parametric)
11+ # ' - Parametric (variance-covariance)
12+ # ' - Monte Carlo simulation
13+ # ' Time complexity varies by method, documented per method
14+ RiskMetrics <- R6Class(
15+ " RiskMetrics" ,
16+
17+ public = list (
18+ # ' @description Initialize risk calculator
19+ # ' @param returns Historical returns data
20+ # ' @param confidence_level Confidence level for VaR/ES (default 0.95)
21+ # ' @param time_horizon Time horizon in days (default 1)
22+ initialize = function (returns = NULL , confidence_level = 0.95 , time_horizon = 1 ) {
23+ private $ validate_parameters(confidence_level , time_horizon )
24+
25+ self $ returns <- returns
26+ self $ confidence_level <- confidence_level
27+ self $ time_horizon <- time_horizon
28+
29+ if (! is.null(returns )) {
30+ private $ fit_distribution()
31+ }
32+ },
33+
34+ # ' @description Calculate Historical VaR
35+ # ' @param portfolio_value Current portfolio value
36+ # ' @param method Calculation method ('historical', 'parametric', or 'monte_carlo')
37+ # ' @param n_simulations Number of simulations for Monte Carlo method
38+ calculate_var = function (portfolio_value , method = " historical" , n_simulations = 10000 ) {
39+ if (is.null(self $ returns )) {
40+ stop(" No returns data available. Please initialize with returns data." )
41+ }
42+
43+ method <- match.arg(method , c(" historical" , " parametric" , " monte_carlo" ))
44+
45+ var_value <- switch (method ,
46+ " historical" = private $ calculate_historical_var(portfolio_value ),
47+ " parametric" = private $ calculate_parametric_var(portfolio_value ),
48+ " monte_carlo" = private $ calculate_monte_carlo_var(portfolio_value , n_simulations )
49+ )
50+
51+ # Scale VaR to time horizon
52+ var_value * sqrt(self $ time_horizon )
53+ },
54+
55+ # ' @description Calculate Expected Shortfall (Conditional VaR)
56+ # ' @param portfolio_value Current portfolio value
57+ # ' @param method Calculation method ('historical', 'parametric', or 'monte_carlo')
58+ # ' @param n_simulations Number of simulations for Monte Carlo method
59+ calculate_es = function (portfolio_value , method = " historical" , n_simulations = 10000 ) {
60+ if (is.null(self $ returns )) {
61+ stop(" No returns data available. Please initialize with returns data." )
62+ }
63+
64+ method <- match.arg(method , c(" historical" , " parametric" , " monte_carlo" ))
65+
66+ es_value <- switch (method ,
67+ " historical" = private $ calculate_historical_es(portfolio_value ),
68+ " parametric" = private $ calculate_parametric_es(portfolio_value ),
69+ " monte_carlo" = private $ calculate_monte_carlo_es(portfolio_value , n_simulations )
70+ )
71+
72+ # Scale ES to time horizon
73+ es_value * sqrt(self $ time_horizon )
74+ },
75+
76+ # ' @description Generate risk report with multiple metrics
77+ # ' @param portfolio_value Current portfolio value
78+ # ' @param include_methods Which methods to include in report
79+ generate_risk_report = function (portfolio_value ,
80+ include_methods = c(" historical" , " parametric" , " monte_carlo" )) {
81+ results <- list ()
82+
83+ for (method in include_methods ) {
84+ results [[method ]] <- list (
85+ var = self $ calculate_var(portfolio_value , method ),
86+ es = self $ calculate_es(portfolio_value , method )
87+ )
88+ }
89+
90+ # Add distribution statistics
91+ results $ statistics <- list (
92+ mean_return = mean(self $ returns ),
93+ volatility = sd(self $ returns ),
94+ skewness = private $ calculate_skewness(),
95+ kurtosis = private $ calculate_kurtosis()
96+ )
97+
98+ return (results )
99+ },
100+
101+ # ' @description Update returns data and recalculate distribution parameters
102+ # ' @param new_returns New returns data to use
103+ update_returns = function (new_returns ) {
104+ self $ returns <- new_returns
105+ private $ fit_distribution()
106+ invisible (self )
107+ },
108+
109+ # Public fields
110+ returns = NULL ,
111+ confidence_level = NULL ,
112+ time_horizon = NULL
113+ ),
114+
115+ private = list (
116+ # Distribution parameters
117+ mean_return = NULL ,
118+ volatility = NULL ,
119+
120+ # ' @description Fit distribution to returns data
121+ fit_distribution = function () {
122+ private $ mean_return <- mean(self $ returns )
123+ private $ volatility <- sd(self $ returns )
124+ },
125+
126+ # ' @description Calculate Historical VaR
127+ calculate_historical_var = function (portfolio_value ) {
128+ sorted_returns <- sort(self $ returns )
129+ index <- floor((1 - self $ confidence_level ) * length(sorted_returns ))
130+ - sorted_returns [index ] * portfolio_value
131+ },
132+
133+ # ' @description Calculate Parametric VaR
134+ calculate_parametric_var = function (portfolio_value ) {
135+ z_score <- stats :: qnorm(self $ confidence_level )
136+ portfolio_value * (z_score * private $ volatility - private $ mean_return )
137+ },
138+
139+ # ' @description Calculate Monte Carlo VaR
140+ calculate_monte_carlo_var = function (portfolio_value , n_simulations ) {
141+ simulated_returns <- stats :: rnorm(
142+ n_simulations ,
143+ mean = private $ mean_return ,
144+ sd = private $ volatility
145+ )
146+ sorted_returns <- sort(simulated_returns )
147+ index <- floor((1 - self $ confidence_level ) * n_simulations )
148+ - sorted_returns [index ] * portfolio_value
149+ },
150+
151+ # ' @description Calculate Historical Expected Shortfall
152+ calculate_historical_es = function (portfolio_value ) {
153+ sorted_returns <- sort(self $ returns )
154+ var_index <- floor((1 - self $ confidence_level ) * length(sorted_returns ))
155+ tail_returns <- sorted_returns [1 : var_index ]
156+ - mean(tail_returns ) * portfolio_value
157+ },
158+
159+ # ' @description Calculate Parametric Expected Shortfall
160+ calculate_parametric_es = function (portfolio_value ) {
161+ z_score <- stats :: qnorm(self $ confidence_level )
162+ phi_z <- stats :: dnorm(z_score )
163+ lambda <- phi_z / (1 - self $ confidence_level )
164+ portfolio_value * (lambda * private $ volatility - private $ mean_return )
165+ },
166+
167+ # ' @description Calculate Monte Carlo Expected Shortfall
168+ calculate_monte_carlo_es = function (portfolio_value , n_simulations ) {
169+ simulated_returns <- stats :: rnorm(
170+ n_simulations ,
171+ mean = private $ mean_return ,
172+ sd = private $ volatility
173+ )
174+ sorted_returns <- sort(simulated_returns )
175+ var_index <- floor((1 - self $ confidence_level ) * n_simulations )
176+ tail_returns <- sorted_returns [1 : var_index ]
177+ - mean(tail_returns ) * portfolio_value
178+ },
179+
180+ # ' @description Calculate distribution skewness
181+ calculate_skewness = function () {
182+ r <- self $ returns
183+ n <- length(r )
184+ m3 <- sum((r - mean(r ))^ 3 ) / n
185+ s3 <- sd(r )^ 3
186+ m3 / s3
187+ },
188+
189+ # ' @description Calculate distribution kurtosis
190+ calculate_kurtosis = function () {
191+ r <- self $ returns
192+ n <- length(r )
193+ m4 <- sum((r - mean(r ))^ 4 ) / n
194+ s4 <- sd(r )^ 4
195+ m4 / s4 - 3 # Excess kurtosis (normal = 0)
196+ },
197+
198+ # ' @description Validate input parameters
199+ validate_parameters = function (confidence_level , time_horizon ) {
200+ if (confidence_level < = 0 || confidence_level > = 1 ) {
201+ stop(" Confidence level must be between 0 and 1" )
202+ }
203+ if (time_horizon < = 0 ) {
204+ stop(" Time horizon must be positive" )
205+ }
206+ }
207+ )
208+ )
209+
210+ # Demonstration
211+ demonstrate_risk_metrics <- function () {
212+ cat(" === Value at Risk and Expected Shortfall Demo ===\n\n " )
213+
214+ # Generate sample returns data
215+ set.seed(42 )
216+ n_days <- 1000
217+ returns <- rnorm(n_days , mean = 0.0001 , sd = 0.01 )
218+
219+ # Initialize calculator
220+ risk_calc <- RiskMetrics $ new(
221+ returns = returns ,
222+ confidence_level = 0.95 ,
223+ time_horizon = 1
224+ )
225+
226+ # Portfolio parameters
227+ portfolio_value <- 1000000 # $1 million portfolio
228+
229+ cat(" Portfolio Parameters:\n " )
230+ cat(sprintf(" Value: $%d\n " , portfolio_value ))
231+ cat(sprintf(" Confidence Level: %.1f%%\n " , risk_calc $ confidence_level * 100 ))
232+ cat(sprintf(" Time Horizon: %d day(s)\n\n " , risk_calc $ time_horizon ))
233+
234+ # Calculate VaR using different methods
235+ methods <- c(" historical" , " parametric" , " monte_carlo" )
236+
237+ cat(" Value at Risk (VaR) Results:\n " )
238+ for (method in methods ) {
239+ var_value <- risk_calc $ calculate_var(portfolio_value , method )
240+ cat(sprintf(" %s VaR: $%.2f\n " , tools :: toTitleCase(method ), var_value ))
241+ }
242+ cat(" \n " )
243+
244+ cat(" Expected Shortfall (ES) Results:\n " )
245+ for (method in methods ) {
246+ es_value <- risk_calc $ calculate_es(portfolio_value , method )
247+ cat(sprintf(" %s ES: $%.2f\n " , tools :: toTitleCase(method ), es_value ))
248+ }
249+ cat(" \n " )
250+
251+ # Generate and display comprehensive risk report
252+ cat(" Comprehensive Risk Report:\n " )
253+ report <- risk_calc $ generate_risk_report(portfolio_value )
254+
255+ cat(" \n Distribution Statistics:\n " )
256+ cat(sprintf(" Mean Return: %.6f\n " , report $ statistics $ mean_return ))
257+ cat(sprintf(" Volatility: %.6f\n " , report $ statistics $ volatility ))
258+ cat(sprintf(" Skewness: %.6f\n " , report $ statistics $ skewness ))
259+ cat(sprintf(" Excess Kurtosis: %.6f\n " , report $ statistics $ kurtosis ))
260+
261+ cat(" \n === Demo Complete ===\n " )
262+ }
263+
264+ # Run demonstration if not in interactive mode
265+ if (! interactive()) {
266+ demonstrate_risk_metrics()
267+ }
0 commit comments