6  Value at Risk

The previous chapter focused on how to allocate capital across assets. The complementary risk-management task is to estimate how large a short-run market loss could be once a portfolio exists. Value at Risk (VaR) provides a compact answer by combining a time horizon, a confidence level, portfolio weights, and a loss distribution.

If \(L\) is the portfolio loss over the chosen horizon and \(\alpha\) is the confidence level, VaR is the loss threshold such that losses are below or equal to it with probability \(\alpha\).

\[ P(L \leq VaR_\alpha)=\alpha. \]

Equivalently, when losses are sorted from low to high, VaR is the corresponding loss quantile.

\[ VaR_\alpha = Q_\alpha(L), \]

where \(Q_\alpha(L)\) is the \(\alpha\) quantile of the loss distribution.

This chapter develops and extends the example called Investment in Four Stock Indices in Chapters 22 and 23 of Hull (2015). In this book, the example is used as an integrative market-risk exercise within the broader Financial Modeling with R path. It combines several ideas already used in the book: data preparation, currency adjustment, returns, portfolio weights, optimization, scenario generation, quantiles, and realized-outcome evaluation. The database used in this example dataR.txt is available here.

The working question is specific: calculate VaR for a portfolio using a one-day time horizon, a 99% confidence level, and 501 days of data, from Monday August 7, 2006 to Thursday September 25, 2008. The chapter follows four steps: express all index positions in US dollars, build one-day loss scenarios, estimate VaR with historical and model-building methods, and evaluate how portfolio weights change the loss distribution.

6.1 Prepare the data

The data set contains the index levels and exchange rates needed to express all positions from the perspective of a US investor.

Code
df <- read.table("dataR.txt", sep = "", header = TRUE)

The first rows show the raw structure of the file before currency adjustment.

Code
head(df)
      DJIA FTSE100 USDGBP   CAC40 EURUSD   Nikkei YENUSD
1 11219.38  5828.8 1.9098 4956.34 0.7776 15154.06 115.00
2 11173.59  5818.1 1.9072 4967.95 0.7789 15464.66 115.08
3 11076.18  5860.5 1.9086 5025.15 0.7762 15656.59 115.17
4 11124.37  5823.4 1.8918 4976.64 0.7828 15630.91 115.41
5 11088.02  5820.1 1.8970 4985.52 0.7833 15565.02 116.07
6 11097.87  5870.9 1.8923 5046.93 0.7847 15857.11 116.45

These are four international stock indices: Dow Jones Industrial Average (DJIA) in the US, the FTSE 100 in the UK, the CAC 40 in France, and the Nikkei 225 in Japan together with the corresponding exchange rates.

Because the example uses the perspective of a US investor, the values of the FTSE 100, CAC 40, and Nikkei 225 must be measured in US dollars. The adjusted US dollar equivalents of the stock indices follow these conversions.

\(FTSE_{USD}=FTSE_{GBP}\times\frac{USD}{GBP}\).

\(CAC_{USD}=\frac{CAC_{EUR}}{\frac{EUR}{USD}}\).

\(Nikkei_{USD}=\frac{Nikkei_{JPY}}{\frac{JPY}{USD}}\).

Code
df.Adj <- df %>%
  mutate(Day = c(0:501)) %>%
  mutate(AFTSE100 = FTSE100 * USDGBP) %>%
  mutate(ACAC40 = CAC40 / EURUSD) %>%
  mutate(ANikkei = Nikkei / YENUSD)

The following table shows the US dollar equivalent of the stock indices used for historical simulation.

Code
df.Adj %>%
  select(Day, DJIA, AFTSE100, ACAC40, ANikkei) %>%
  slice(1, 2, 3, 4, 500, 501)
  Day     DJIA  AFTSE100   ACAC40  ANikkei
1   0 11219.38 11131.842 6373.894 131.7744
2   1 11173.59 11096.280 6378.162 134.3818
3   2 11076.18 11185.350 6474.040 135.9433
4   3 11124.37 11016.708 6357.486 135.4381
5 499 10825.17  9438.580 6033.935 114.2604
6 500 11022.06  9599.898 6200.396 112.8221

The VaR estimation uses 501 observations because the sample counts from day zero through day 500. The A before the index name stands for adjusted. The figure shows the common dollar-based scale that will be used for scenario construction.

Code
df.Adj %>%
  mutate(ANikkei = ANikkei*100) %>%
  mutate(obs = c(1:502)) %>%
  select(obs, DJIA, AFTSE100, ACAC40, ANikkei) %>%
  filter(obs < 502) %>%
  gather(IX, Adj, DJIA, AFTSE100, ACAC40, ANikkei) %>%
  mutate(IX = as.factor(IX)) %>%
  ggplot(aes(x = obs, y = Adj, col = IX)) +
  geom_line(size = 1) +
  labs(y = "US dollar equivalent of stock indices", x = "") +
  theme(legend.position = "bottom")
Figure 6.1: Adjusted index values, Nikkei = Nikkei \(\times\) 100.

The time series starts on Monday August 7, 2006 and ends on Thursday September 25, 2008. These are 501 past daily observations. The plot below also shows the value of Friday September 26, 2008 as a dot. This is observation 502, stored as tomorrow. Observation 502 is excluded from the VaR estimation because it represents the future value being evaluated.

Code
tomorrow <- df.Adj %>%
  mutate(ANikkei = ANikkei*100) %>%
  mutate(obs = c(1:502)) %>%
  select(obs, DJIA, AFTSE100, ACAC40, ANikkei) %>%
  filter(obs == 502) %>%
  gather(IX, Adj, DJIA, AFTSE100, ACAC40, ANikkei) %>%
  mutate(IX = as.factor(IX))

df.Adj %>%
  mutate(ANikkei = ANikkei*100) %>%
  mutate(obs = c(1:502)) %>%
  select(obs, DJIA, AFTSE100, ACAC40, ANikkei) %>%
  filter(obs > 400) %>%
  filter(obs < 502) %>%
  gather(IX, Adj, DJIA, AFTSE100, ACAC40, ANikkei) %>%
  mutate(IX = as.factor(IX)) %>%
  ggplot(aes(x = obs, y = Adj, col = IX)) +
  geom_line(size = 1) +
  geom_point(aes(obs, Adj), size = 2, alpha = 0.4, data = tomorrow) +
  labs(y = "US dollar equivalent of stock indices", x = "") +
  theme(legend.position = "bottom")
Figure 6.2: Adjusted index values, Nikkei = Nikkei \(\times\) 100, zoom view.

The stock index values for Friday September 26, 2008 provide the realized values used later for evaluation.

Code
tomorrow
  obs       IX       Adj
1 502     DJIA 11143.130
2 502 AFTSE100  9379.123
3 502   ACAC40  6081.478
4 502  ANikkei 11216.788

6.2 Historical simulation

The historical simulation method requires scenarios for the possible stock-index values on Friday September 26, 2008. The calculation constructs 500 scenarios from 501 past observations. The method assumes that the index value on Friday September 26, 2008 can be represented by the value observed on Thursday September 25, 2008 multiplied by one historical percentage change. The first percentage change goes from Monday August 7, 2006 to Tuesday August 8, 2006. The second goes from Tuesday August 8, 2006 to Wednesday August 9, 2006, and the sequence continues through the historical window.

For index \(j\) and historical scenario \(k\), the generated value is defined by applying a past daily relative change to the last observed value.

\[ S_{j,k}^{scenario}=S_{j,T}\frac{S_{j,k}}{S_{j,k-1}}, \qquad k=1,\dots,500. \]

The portfolio value and loss for scenario \(k\) combine those scenario index levels with the dollar allocations.

\[ p_k=\sum_{j=1}^{4} a_j \frac{S_{j,k}^{scenario}}{S_{j,T}}, \qquad l_k=10{,}000-p_k, \]

where \(a_j\) is the dollar allocation to index \(j\) in thousands of US dollars.

The adjusted values provide the reference levels for the scenario ratios.

Code
df.Adj %>%
  select(Day, DJIA, AFTSE100, ACAC40, ANikkei) %>%
  slice(1, 2, 3, 4, 500, 501)
  Day     DJIA  AFTSE100   ACAC40  ANikkei
1   0 11219.38 11131.842 6373.894 131.7744
2   1 11173.59 11096.280 6378.162 134.3818
3   2 11076.18 11185.350 6474.040 135.9433
4   3 11124.37 11016.708 6357.486 135.4381
5 499 10825.17  9438.580 6033.935 114.2604
6 500 11022.06  9599.898 6200.396 112.8221

The first three Dow Jones scenarios \(SDJIA\) illustrate the calculation.

\(SDJIA_1 = 11022.06 \times \frac{11173.59}{11219.38} \rightarrow SDJIA_1 = 10977.08\).

\(SDJIA_2 = 11022.06 \times \frac{11076.18}{11173.59} \rightarrow SDJIA_2 = 10925.97\)

\(SDJIA_3 = 11022.06 \times \frac{11124.37}{11076.18} \rightarrow SDJIA_3 = 11070.01\)

The first three possible Dow Jones values according to the historical approach for September 26, 2008 are 10977.08, 10925.97, and 11070.01. The actual value, 11143.130, is reserved for evaluation, so it does not enter the VaR estimate.

The generated scenario table gives the first simulated values and the last historical rows in the scenario window.

Code
df.Adj.S <- df.Adj %>%
  mutate(obs = c(0:501)) %>%
  mutate(SDJIA = DJIA[501] * (DJIA/lag(DJIA))) %>%
  mutate(SFTSE100 = AFTSE100[501] * (AFTSE100/lag(AFTSE100))) %>%
  mutate(SCAC40 = ACAC40[501] * (ACAC40/lag(ACAC40))) %>%
  mutate(SNikkei = ANikkei[501] * (ANikkei/lag(ANikkei)))

df.Adj.S %>%
  select(obs, SDJIA, SFTSE100, SCAC40, SNikkei) %>%
  slice(2, 3, 4, 500, 501)
  obs    SDJIA SFTSE100   SCAC40  SNikkei
1   1 10977.08 9569.230 6204.547 115.0545
2   2 10925.97 9676.957 6293.603 114.1331
3   3 11070.01 9455.160 6088.768 112.4028
4 499 10831.43 9383.488 6051.936 113.8498
5 500 11222.53 9763.974 6371.450 111.4019

The violin plot shows the generated scenarios together with the actual values for September 26, 2008. The dot is useful for evaluation because it shows where the realized next-day value landed relative to the historical scenario range.

Code
levels(tomorrow$IX) <- list(SCAC40 = "ACAC40", SDJIA = "DJIA",
                         SFTSE100 = "AFTSE100", SNikkei = "ANikkei")

df.Adj.S %>%
  mutate(SNikkei = SNikkei*100) %>%
  mutate(obs = c(1:502)) %>%
  select(obs, SDJIA, SFTSE100, SCAC40, SNikkei) %>%
  filter(obs < 502) %>%
  gather(IX, S, SDJIA, SFTSE100, SCAC40, SNikkei) %>%
  mutate(IX = as.factor(IX)) %>%
  ggplot(aes(x = IX, fill = IX)) +
  geom_violin(aes(IX, S), draw_quantiles = 0.5) +
  geom_point(aes(IX, Adj), size = 3,
             data = tomorrow, show.legend = FALSE) +
  labs(y = "Scenarios generated for September 26, 2008", x = "") +
  theme(legend.position = "none")

According to the example, an investor in the United States owns, on September 25, 2008, a portfolio worth $10 million consisting of 40% in the DJIA, 30% in the FTSE, 10% in the CAC, and 20% in the Nikkei. The calculations below express portfolio values and losses in thousands of US dollars. The corresponding 500 scenarios for the possible portfolio value \(p\) on September 26, 2008 use the same allocation formula.

\(p_1 = 4000 \frac{SDJIA_1}{DJIA_{sept.25.2008}} + 3000 \frac{SFTSE_1}{FTSE_{sept.25.2008}} + 1000 \frac{SCAC_1}{CAC_{sept.25.2008}} + 2000 \frac{SNikkei_1}{Nikkei_{sept.25.2008}}\),

\(p_1 = 4000 \frac{10977.08}{11022.06} + 3000 \frac{9569.230}{9599.898} + 1000 \frac{6204.547}{6200.396} + 2000 \frac{115.0545}{112.8221}\),

\(p_1 = 10{,}014.334\) thousand dollars.

\(p_2 = 4000 \frac{SDJIA_2}{DJIA_{sept.25.2008}} + 3000 \frac{SFTSE_2}{FTSE_{sept.25.2008}} + 1000 \frac{SCAC_2}{CAC_{sept.25.2008}} + 2000 \frac{SNikkei_2}{Nikkei_{sept.25.2008}}\),

\(p_2 = 4000 \frac{10925.97}{11022.06} + 3000 \frac{9676.957}{9599.898} + 1000 \frac{6293.603}{6200.396} + 2000 \frac{114.1331}{112.8221}\),

\(p_2 = 10{,}027.481\) thousand dollars.

The same scenario formula is applied to the remaining historical changes.

Code
df.Adj.S <- df.Adj.S %>%
  mutate(p = (4000 * SDJIA / DJIA[501]) + # Investment portfolio value
             (3000 * SFTSE100 / AFTSE100[501]) +
             (1000 * SCAC40 / ACAC40[501]) +
             (2000 * SNikkei / ANikkei[501])) %>%
  mutate(l = 10000 - p) # Losses l

df.Adj.S %>%
  select(obs, SDJIA, SFTSE100, SCAC40, SNikkei, p, l) %>%
  slice(2, 3, 4, 500, 501)
  obs    SDJIA SFTSE100   SCAC40  SNikkei         p          l
1   1 10977.08 9569.230 6204.547 115.0545 10014.334  -14.33385
2   2 10925.97 9676.957 6293.603 114.1331 10027.481  -27.48131
3   3 11070.01 9455.160 6088.768 112.4028  9946.736   53.26406
4 499 10831.43 9383.488 6051.936 113.8498  9857.465  142.53549
5 500 11222.53 9763.974 6371.450 111.4019 10126.439 -126.43897

The variable \(l\) represents portfolio losses, with \(l = 10{,}000 - p\) in thousands of US dollars. A negative loss represents a portfolio gain. The values reproduce the numerical structure of the historical-simulation example in Hull (2015).

The scenario-loss plot preserves the historical ordering of the 500 daily changes. Positive bars are losses and negative bars are gains, so the picture gives a quick sense of the downside tail before selecting a VaR threshold.

Code
l <- df.Adj.S %>%
  mutate(obs = c(0:501)) %>%
  select(obs, l) %>%
  filter(obs > 0 & obs < 501) %>%
  select(l) %>%
  unlist()

l.tomorrow <- df.Adj.S %>%
  mutate(obs = c(0:501)) %>%
  select(obs, l) %>%
  filter(obs == 501) %>%
  select(l) %>%
  unlist()
Code
plot(l, type = "h", lwd = 2,
     xlab = "500 scenarios (sorted by historical date)",
     ylab = "Portfolio gains (-) and losses (+)",
     col = ifelse(l < 0, "blue", "red"))
points(501, l.tomorrow, pch = 19, cex = 2)
abline(0, 0)
abline(v = 0)
lines(seq(0, max(l, na.rm = TRUE), length.out = 500), lty = 2)
lines(seq(0, min(l, na.rm = TRUE), length.out = 500), lty = 2)
abline(h = 253.385, lwd = 2, col = "green")
legend("bottomleft", legend = c("Gains", "Losses", "Historical VaR: 253.385"),
       col = c("blue", "red", "green"), lty = 1, bg = "white", lwd = 2)
Figure 6.3: Scenario losses for the September 25, 2008 portfolio.

The histogram compresses the same scenarios into a distribution of losses. Most scenarios are clustered around moderate gains or losses, while VaR is read from the right tail.

Code
hist(l, 15, xlab = "Portfolio losses (+) and gains (-)", main = NULL)
points(l.tomorrow, 1, pch = 19, cex = 2)
abline(v = 253.385, col = "red", lwd = 2)
legend("topleft", legend = c("Historical VaR: 253.385"), col = "red",
       bg = "white", lwd = 2)
Figure 6.4: Histogram of scenario losses between September 25 and 26, 2008.

The point marks the realized loss, and the red line marks the historical VaR estimate. The density plot smooths the same information and makes the shape of the empirical loss distribution easier to compare with the normal model used later.

Code
densi <- density(l, na.rm = TRUE)
plot(densi, main = "", xlab = "Portfolio losses (+) and gains (-)")
polygon(densi, col = "grey", border = "black")
points(l.tomorrow, 0, pch = 19, cex = 2)
abline(v = 253.385, col = "red", lwd = 2)
legend("topleft", legend = c("Historical VaR: 253.385"), col = "red",
       bg = "white", lwd = 2)
Figure 6.5: Density plot of scenario losses between September 25 and 26, 2008.

The same loss distribution can be displayed with axes that match the original textbook presentation.

Code
hist(l, 15, axes = F, lty = "blank", col = "grey", main = "",
     xlab = "Portfolio losses (+) and gains (-)")
axis(1, pos = 0)
axis(2, pos = 0, las = 1)
Figure 6.6: Historical loss distribution with Hull-style axes.

Ranking the 500 scenario losses from highest to lowest makes the tail selection explicit. At the 99% confidence level with 500 scenarios, 1% of the sample corresponds to five observations. The fifth-largest loss is therefore the discrete historical VaR used in the textbook example.

Code
Table22.4 <- data.frame(scenario.number = order(l, decreasing = TRUE),
                        `loss in positive values` =
                          l[order(l, decreasing = TRUE)])
head(Table22.4, 15)
     scenario.number loss.in.positive.values
l494             494                477.8410
l339             339                345.4351
l349             349                282.2038
l329             329                277.0413
l487             487                253.3850
l227             227                217.9740
l131             131                202.2555
l238             238                201.3892
l473             473                191.2691
l306             306                191.0497
l477             477                185.1269
l495             495                184.4496
l376             376                182.7072
l237             237                180.1048
l365             365                172.2237

The last values show the largest gains in the ranked loss table.

Code
tail(Table22.4)
     scenario.number loss.in.positive.values
l395             395               -224.5146
l489             489               -284.9247
l341             341               -307.9301
l377             377               -316.4893
l379             379               -333.0218
l497             497               -555.7954

One of the highest gains happens in scenario 497, almost at the end of the historical window. The largest gains remain outside the VaR tail calculation and still help show that the historical window contains both downside and upside shocks.

Code
VaR.hist <- 253.3850
Results <- data.frame(VaR.method = "Historical simulation, original weights",
                      VaR = VaR.hist)
Results
                               VaR.method     VaR
1 Historical simulation, original weights 253.385

The original weights are 40% in the DJIA, 30% in the FTSE, 10% in the CAC, and 20% in the Nikkei. They are treated as a benchmark allocation because they are assigned exogenously in the example. The ranked-loss barplot makes the 1% tail visible and separates the VaR estimate from the realized next-day outcome.

Code
barplot(l[order(l, decreasing = TRUE)],
        names.arg = order(l, decreasing = TRUE), ylim = c(0, 500),
        xlim = c(0, 20), las = 2, cex.names = 1, ylab = "Portfolio loss ($000s)",
        col = "red", xlab = "Scenario number (ranked)")
abline(h = 253.385, lwd = 3)
abline(h = 0, lwd = 3)
abline(h = l.tomorrow, lwd = 3, col = "green")
legend("topright", legend = c("Historical VaR: 253.385",
       "Realized loss"), col = c("black", "green"),
       bg = "white", lwd = 2)
Figure 6.7: Ranked scenario losses with 1 percent historical VaR and realized loss.

The top 1% can also be explored with the quantile() function. R’s default quantile estimator interpolates between order statistics, so its 99% value differs from Hull’s fifth-worst-scenario historical VaR. The difference comes from a numerical convention for estimating sample quantiles.

Code
VaR.hist.quantile <- quantile(l, 0.99)
VaR.hist.quantile
     99% 
218.3281 

Hull’s discrete historical VaR is reproduced by taking the fifth largest loss directly.

Code
VaR.hist.discrete <- sort(l, decreasing = TRUE)[5]
VaR.hist.discrete
   l487 
253.385 

The default quantile() function at the 99% level gives the interpolated estimate.

Code
quantile(l, 0.99)
     99% 
218.3281 

The quantile curve shows how the VaR estimate changes with the confidence level.

Code
plot(seq(from = 0.902, to = 1, by = 0.002),
     quantile(l, seq(from = 0.902, to = 1, by = 0.002)), cex = 1.5,
     xlab = "Confidence level (vertical line at 99%)",
     ylab = "Portfolio VaR")
abline(v = 0.99, lty = 2, lwd = 2)
abline(h = quantile(l, 0.99), col = "blue", lty = 2, lwd = 2)
abline(h = Table22.4[5, 2], col = "red", lty = 2, lwd = 2)
legend("topleft", legend = c("99% level", "Historical VaR: 253.385",
                             "99% quantile VaR: 218.3281"),
       col = c("black", "red", "blue"), lty = 2, bg = "white", lwd = 2)
Figure 6.8: VaR estimates across confidence levels.
Code
ci <- seq(0, 1, length.out = 500)
ind <- c(1:500)
tail(data.frame("quantile function" = quantile(l, ci),
                "scenario number" = order(l),
                "sort losses" = sort(l)))
             quantile.function scenario.number sort.losses
98.9979960%           217.9740             227    217.9740
99.1983968%           253.3850             487    253.3850
99.3987976%           277.0413             329    277.0413
99.5991984%           282.2038             349    282.2038
99.7995992%           345.4351             339    345.4351
100.0000000%          477.8410             494    477.8410

The results table stores the historical VaR estimates.

Code
Results.updated <- Results %>%
  add_row(VaR.method =
            "Historical approximate quantile(), original weights",
          VaR = VaR.hist.quantile) %>%
  arrange(VaR.method)
Results.updated
                                           VaR.method      VaR
1 Historical approximate quantile(), original weights 218.3281
2             Historical simulation, original weights 253.3850

6.3 Model building approach

The model-building approach assumes normality for portfolio losses. The historical loss distribution can deviate from that assumption, especially in the tails. Its main advantage is simplicity: once the covariance matrix and portfolio weights are defined, VaR follows from the normal quantile. The method is also useful as a benchmark against historical simulation because both methods use the same portfolio and horizon.

The adjusted indices are first transformed into simple returns.

\[ R_{j,t}=\frac{S_{j,t}-S_{j,t-1}}{S_{j,t-1}}. \]

Code
R <- df.Adj %>%
  select(Day, DJIA, AFTSE100, ACAC40, ANikkei) %>%
  mutate(RDJIA = (DJIA-lag(DJIA))/lag(DJIA)) %>%
  mutate(RFTSE100 = (AFTSE100-lag(AFTSE100))/lag(AFTSE100)) %>%
  mutate(RCAC40 = (ACAC40-lag(ACAC40))/lag(ACAC40)) %>%
  mutate(Nikkei = (ANikkei-lag(ANikkei))/lag(ANikkei)) %>%
  select(RDJIA, RFTSE100, RCAC40, Nikkei) %>%
  slice(-1)

R.tomorrow <- R %>%
  slice(501)

R <- R %>%
  slice(-501)

The correlation matrix on September 25, 2008 is calculated by giving equal weight to the last 500 daily returns.

Code
Rcor <- cor(R)
Table23.5 <- Rcor
Table23.5
               RDJIA  RFTSE100    RCAC40      Nikkei
RDJIA     1.00000000 0.4891059 0.4957096 -0.06189921
RFTSE100  0.48910594 1.0000000 0.9181083  0.20094221
RCAC40    0.49570963 0.9181083 1.0000000  0.21095096
Nikkei   -0.06189921 0.2009422 0.2109510  1.00000000

The covariance matrix on September 25, 2008 is calculated by giving equal weight to the last 500 daily returns.

Code
Rcov <- cov(R)
Table23.6 <- Rcov
Table23.6
                 RDJIA     RFTSE100       RCAC40        Nikkei
RDJIA     1.229524e-04 7.696591e-05 7.682514e-05 -9.493488e-06
RFTSE100  7.696591e-05 2.013973e-04 1.821076e-04  3.944302e-05
RCAC40    7.682514e-05 1.821076e-04 1.953506e-04  4.078130e-05
Nikkei   -9.493488e-06 3.944302e-05 4.078130e-05  1.913129e-04

This covariance matrix is close to the one in Hull (2015). Minor differences arise because the calculations here do not use rounded intermediate values.

With dollar allocations \(\alpha=(4000,3000,1000,2000)'\), the variance of portfolio losses in thousands of dollars follows the quadratic portfolio-risk expression.

\[ \sigma_p^2=\alpha'\Sigma\alpha. \]

Code
Rsd <- apply(R, 2, sd)
alpha <- c(4000, 3000, 1000, 2000)
(Pvar <- t(alpha) %*% Rcov %*% alpha) # equation in page 506 (chapter 22)
         [,1]
[1,] 8779.392

Hull reports 8,761.833 for this variance. The value calculated here is slightly different because the code keeps more intermediate precision. Taking the square root gives the portfolio standard deviation.

Code
(Psd <- Pvar^0.5)
         [,1]
[1,] 93.69841

The one-day 99% VaR under the normal model multiplies the portfolio standard deviation by the 99% normal quantile.

\[ VaR_{0.99}=z_{0.99}\sigma_p. \]

Code
(VaR.ew <- qnorm(0.99, 0, 1) * Psd)
         [,1]
[1,] 217.9751

Hull reports $217,757. In the units used here, that is 217.757 in thousands of dollars, compared with 253.385 from the historical simulation approach. The model-building estimate is lower in this case because the normal approximation smooths the empirical tail.

Code
Results.updated <- Results.updated %>%
  add_row(VaR.method =
            "Model building equal observation weights, original weights",
          VaR = VaR.ew) %>%
  arrange(VaR.method)
Results.updated
                                                  VaR.method      VaR
1        Historical approximate quantile(), original weights 218.3281
2                    Historical simulation, original weights 253.3850
3 Model building equal observation weights, original weights 217.9751

The equal-observation-weight covariance estimate can be compared with the exponentially weighted moving average method (EWMA).

With decay parameter \(\lambda\) and demeaned return vector \(u_t=r_t-\bar r\), the EWMA covariance update gives more weight to recent squared return innovations.

\[ \Sigma_t=\lambda\Sigma_{t-1}+(1-\lambda)u_{t-1}u_{t-1}'. \]

Code
# Model building example -- EWMA
covEWMA <-
  function(factors, lambda = 0.96, return.cor = FALSE) {
    factor.names  = colnames(factors)
    t.factor      = nrow(factors)
    k.factor      = ncol(factors)
    factors       = as.matrix(factors)
    t.names       = rownames(factors)
    factor.means  = colMeans(factors)

    cov.f.ewma = array(,c(t.factor, k.factor, k.factor))
    cov.f = var(factors)  # unconditional variance as EWMA at time = 0
    FF = (factors[1, ] - factor.means) %*% t(factors[1,] - factor.means)
    cov.f.ewma[1,,] = (1 - lambda) * FF  + lambda * cov.f
    for (i in 2:t.factor) {
    FF = (factors[i, ] - factor.means) %*% t(factors[i, ] - factor.means)
    cov.f.ewma[i,,] = (1 - lambda) * FF  + lambda * cov.f.ewma[(i-1),,]
    }
    dimnames(cov.f.ewma) = list(t.names, factor.names, factor.names)

    if(return.cor) {
      cor.f.ewma = cov.f.ewma
      for (i in 1:dim(cor.f.ewma)[1]) {
        cor.f.ewma[i, , ] = cov2cor(cov.f.ewma[i, ,])
      }
      return(cor.f.ewma)
    } else {
      return(cov.f.ewma)
    }
  }

The covariance matrix on September 25, 2008 is also calculated using the EWMA method with \(\lambda=0.94\). EWMA gives more weight to recent observations, so it responds more strongly when volatility rises near the end of the sample.

Code
Rdf <- as.data.frame(R)
C <- covEWMA(Rdf, lambda = 0.94)[500,,]
Table23.7 <- C
Table23.7
                 RDJIA     RFTSE100       RCAC40        Nikkei
RDJIA     0.0004801472 0.0004300030 0.0004257243 -0.0000398876
RFTSE100  0.0004300030 0.0010311334 0.0009630006  0.0002090981
RCAC40    0.0004257243 0.0009630006 0.0009535706  0.0001681154
Nikkei   -0.0000398876 0.0002090981 0.0001681154  0.0002534478

The variance of portfolio losses again follows the quadratic form \(\alpha'\Sigma\alpha\).

Code
(Pvar_EWMA <- t(alpha) %*% C %*% alpha)
         [,1]
[1,] 40977.22

Hull reports 40,995.765 for this value. Taking the square root gives the EWMA portfolio standard deviation.

Code
(Psd_EWMA <- Pvar_EWMA^0.5)
         [,1]
[1,] 202.4283

Hull reports 202.474 for this value. The one-day 99% VaR is therefore calculated from the same normal-quantile expression used above.

Code
(VaR.ewma <- qnorm(0.99, 0, 1) * Psd_EWMA)
         [,1]
[1,] 470.9187

Hull reports $471,025, or 471.025 in thousands of dollars. This value is more than twice the VaR estimated with equal observation weights, which reflects the higher volatility near the end of the sample.

Code
Results.updated <- Results.updated %>%
  add_row(VaR.method = "Model building EWMA, original weights",
          VaR = VaR.ewma) %>%
  arrange(VaR.method)
Results.updated
                                                  VaR.method      VaR
1        Historical approximate quantile(), original weights 218.3281
2                    Historical simulation, original weights 253.3850
3                      Model building EWMA, original weights 470.9187
4 Model building equal observation weights, original weights 217.9751

The table compares daily volatilities under equal observation weights and EWMA.

Code
Table23.8 <- data.frame(Rsd * 100, diag(C)^0.5 * 100)
colnames(Table23.8) <- c("Equal obs. weights", "EWMA")
rownames(Table23.8) <- c("DJIA", "FTSE 100", "CAC 40", "Nikkei 225")
t(Table23.8)
                       DJIA FTSE 100   CAC 40 Nikkei 225
Equal obs. weights 1.108839 1.419145 1.397679   1.383159
EWMA               2.191226 3.211127 3.087994   1.592004

The estimated daily standard deviations are much higher when EWMA is used than when data receive equal observation weights. This happens because volatilities were much higher during the period immediately preceding September 25, 2008, than during the rest of the 500 days covered by the data.

6.4 Optimal allocation

VaR depends on the loss distribution, and the loss distribution depends on the weights. The current benchmark portfolio weights provide the reference allocation.

Code
W_benchmark <- alpha / 10000
W_benchmarkt <- data.frame(W_benchmark)
rownames(W_benchmarkt) <- c("DJIA", "FTSE 100", "CAC 40", "Nikkei 225")
t(W_benchmarkt)
            DJIA FTSE 100 CAC 40 Nikkei 225
W_benchmark  0.4      0.3    0.1        0.2

The following calculation estimates three comparison portfolios: the benchmark allocation, a minimum-variance allocation, and an allocation that targets the expected return of the CAC 40. Returns are expressed in percent for the mean-variance exercise; the later VaR calculations continue to use dollar allocations and the unscaled covariance matrices.

The minimum-variance allocation solves a constrained variance minimization problem.

\[ \min_w w'\Sigma w \quad \text{subject to} \quad \mathbf{1}'w=1. \]

The CAC 40 target-return allocation adds a return constraint to the same variance minimization problem. This analytical Markowitz calculation is unconstrained, so a target-return solution can include negative weights. In portfolio language, a negative weight is a short position.

\[ \min_w w'\Sigma w \quad \text{subject to} \quad \mathbf{1}'w=1,\quad w'\mu=\mu_{CAC40}. \]

Code
R <- R * 100
R.tomorrow <- R.tomorrow * 100
stocks<- c("DJIA", "FTSE100", "CAC40", "Nikkei")
sigma <- var(R)
sd <- diag(sigma)^0.5
E <- colMeans(R)
E.tomorrow <- colMeans(R.tomorrow)
ones <- abs(E / E)
a <- c(t(ones) %*% solve(sigma) %*% ones)
b <- c(t(ones) %*% solve(sigma) %*% E)
c <- c(t(E) %*% solve(sigma) %*% E)
d <- c(a * c - (b^2))
g <- c(solve(sigma) %*% (c * ones - b * E) / d)
h <- c(solve(sigma) %*% (a * E - b * ones) / d)
ER <- seq(from = -0.3, to = 0.3, by = 0.0001)
S <- ER
W <- matrix(0, nrow = length(ER), ncol = length(stocks))
for(i in 1:length(ER)){
  W[i,] <- g + h * ER[i]
  ER[i] <- W[i,] %*% E
  S[i] <- (t(W[i,]) %*% sigma %*% W[i, ])^0.5
  }
W_min <- (solve(sigma) %*% ones) / a
R_min <- c(t(W_min) %*% E)
R_min.realized <- c(t(W_min) %*% E.tomorrow)
S_min <- c(t(W_min) %*% sigma %*% W_min)^0.5
# Allocation targeting the CAC40 expected return with lower estimated variance.
W.cac40 <- g + h * E[3]
R_cac40 <- c(t(W.cac40) %*% E)
R_cac40.realized <- c(t(W.cac40) %*% E.tomorrow)
S_cac40 <- c(t(W.cac40) %*% sigma %*% W.cac40)^0.5
R_benchmark.realized = c(t(W_benchmark) %*% E.tomorrow)

There are now four portfolio-weight alternatives, two of which are optimized. The risk-management comparison asks whether optimized weights reduce VaR under the same data and horizon.

Code
W <- data.frame(W_benchmark, W_min, W.cac40, c(0.25, 0.25, 0.25, 0.25))
rownames(W) <- c("DJIA", "FTSE 100", "CAC 40", "Nikkei 225")
colnames(W) <- c("Benchmark weights",
                 "minimum variance", "CAC40 target return",
                 "equal 1/N")
t(W)
                         DJIA    FTSE 100     CAC 40 Nikkei 225
Benchmark weights   0.4000000  0.30000000 0.10000000  0.2000000
minimum variance    0.5592378  0.03793305 0.02279301  0.3800361
CAC40 target return 0.6075003 -0.40341894 0.45978629  0.3361323
equal 1/N           0.2500000  0.25000000 0.25000000  0.2500000

The portfolio weights remain constant in this worked example. A dynamic implementation could rebalance weights as new data arrive. The following resource shows the time evolution of minimum-variance weights when five US industry portfolios are used as investment assets.

Code
embed_url("https://youtu.be/paH1-bDPsA4")

A similar exercise with 10 US industry-sorted portfolios extends the same idea.

Code
embed_url("https://youtu.be/DyjQbKm-NdE")

The comparison now returns to the four-index portfolio.

Code
W_benchmark <- alpha / 10000
R_benchmark <- c(t(W_benchmark) %*% E)
S_benchmark <- c(t(W_benchmark) %*% sigma %*% W_benchmark)^0.5

plot(S, ER, type = "l", lwd = 3, xlim = c(0.7, 1.6),
     ylim = c(-0.022, 0.005),
     ylab = "Expected return", xlab = "Standard deviation")
points(diag(sigma)^0.5, E)
abline(0, R_benchmark / S_benchmark, lty = 3, col = "blue")
abline(0, R_min / S_min, lty = 3, col = "red")
points(S_min, R_min, col = "red", pch = 19, cex = 2)
abline(0, 0, lty = 3)
text(S_min, R_min, "Minimum
     variance", pos = 2, cex = 0.8)
text(sd, E, stocks, adj = -0.5, cex = 0.8)
points(S_benchmark, R_benchmark, col = "blue", pch = 19, cex = 2)
text(S_benchmark, R_benchmark, "Benchmark
     allocation", adj = -0.3, cex = 0.8, pos = 1)
legend("bottomleft",
       legend = c("DJ=0.4,   FTSE=0.3,     CAC=0.1,     Nikkei=0.2",
                            "DJ=0.56, FTSE=0.038, CAC=0.022, Nikkei=0.38"),
       col = c("blue", "red"), pch = 19, bg = "white", cex = 1)
Figure 6.9: Original allocation and minimum-variance alternative in mean-variance space.

The figure suggests that the minimum-variance alternative reduces estimated portfolio risk relative to the original allocation. In the estimated mean-variance space, the minimum-variance portfolio has higher expected return and lower estimated risk. The statement is in-sample and based on estimated means and covariances. The realized outcome can then be evaluated with the actual returns for September 26, 2008.

Code
(R_min.realized <- c(t(W_min) %*% E.tomorrow))
[1] 0.2629549
Code
(R_benchmark.realized = c(t(W_benchmark) %*% E.tomorrow))
[1] -0.5583249

The comparison suggests that the original allocation would have produced a realized return of -0.5583249% on Friday September 26, 2008. The minimum-variance allocation would have produced a portfolio gain of 0.2629549%. The example shows why portfolio weights matter before VaR is computed: the loss distribution changes when the allocation changes.

Code
W_benchmark <- alpha / 10000
R_benchmark <- c(t(W_benchmark) %*% E)
S_benchmark <- c(t(W_benchmark) %*% sigma %*% W_benchmark)^0.5

W_1N <- c(0.25,0.25,0.25,0.25)
R_1N <- c(t(W_1N) %*% E)
S_1N <- c(t(W_1N) %*% sigma %*% W_1N)^0.5

plot(S, ER, type = "l", lwd = 3, xlim = c(0.7, 1.6),
     ylim = c(-0.022, 0.005),
     ylab = "Expected return", xlab = "Standard deviation")
points(diag(sigma)^0.5, E)
abline(0, R_benchmark / S_benchmark, lty = 3, col = "blue")
abline(0, R_min / S_min, lty = 3, col = "red")
abline(0, R_1N / S_1N, lty = 3, col = "purple")
points(S_min, R_min, col = "red", pch = 19, cex = 2)
points(S_1N, R_1N, col = "purple", pch = 19, cex = 2)
abline(0, 0, lty = 3)
text(S_min, R_min, "Minimum
     variance", pos = 2, cex = 0.8)
text(sd, E, stocks, adj = -0.5, cex = 0.8)
points(S_benchmark, R_benchmark, col = "blue", pch = 19, cex = 2)
text(S_benchmark, R_benchmark, "Benchmark
     allocation", adj = -0.3, cex = 0.8, pos = 1)
text(S_1N, R_1N, "1/N", adj = -0.3, cex = 0.8, pos = 3)
legend("bottomleft",
       legend =
         c("Bench.: DJ=0.4,   FTSE=0.3,     CAC=0.1,     Nikkei=0.2",
"Minvar: DJ=0.56, FTSE=0.038, CAC=0.022, Nikkei=0.38",
"1/N:      DJ=0.25,  FTSE=0.25,  CAC=0.25,  Nikkei=0.25"),
       col = c("blue","red", "purple"), pch = 19,
bg = "white", cex = 0.9)
Figure 6.10: Original allocation, minimum-variance alternative, and 1/N benchmark.

The efficient frontier also changes when the investable universe changes. The following resource shows the US efficient frontier using five industry-sorted portfolios as investment assets.

Code
embed_url("https://youtu.be/Fq4UlUCneyI")

Increasing the number of investment assets can expand the mean-variance frontier when the added assets bring useful return, risk, or correlation characteristics. The following resource compares 10 versus 12 US industry-sorted portfolios.

Code
embed_url("https://youtu.be/_GnJRviyYJI")

The mean and standard deviation estimates can also be used to simulate 10,000 possible one-day returns for Friday September 26, 2008. The histograms below are model-based checks of where the realized return falls relative to the fitted normal distribution.

Code
embed_url("https://youtu.be/P1Ky6-a8DE0")

The first simulated distribution corresponds to the minimum-variance portfolio.

Code
set.seed(1)
M <- rnorm(10000, R_min, S_min)
hist(M, 100, xlim = c(-4, 4), ylim = c(0, 450), main = "")
abline(v = R_min.realized, col = "blue", lwd = 3)
abline(v = quantile(M, 0.025), lty = 2)
abline(v = quantile(M, 0.975), lty = 2)
Figure 6.11: Minimum-variance allocation and realized outcome.

The blue line falls on the gain side of the simulated distribution. For this particular day, the minimum-variance allocation produced a positive realized return under the observed market moves.

The second simulated distribution corresponds to the original allocation.

Code
set.seed(1)
H = rnorm(10000, R_benchmark, S_benchmark)
hist(H, 100, xlim = c(-4, 4), ylim = c(0, 450), main = "")
abline(v = R_benchmark.realized, col = "blue", lwd = 3)
abline(v = quantile(H, 0.025), lty = 2)
abline(v = quantile(H, 0.975), lty = 2)
Figure 6.12: Original allocation and realized outcome.

The blue line for the original allocation falls on the loss side. Comparing this histogram with the previous one shows that changing the weights changes both the center of the fitted distribution and the realized portfolio return.

The portfolio that targets the CAC 40 expected return is evaluated in the same way.

Code
set.seed(1)
Ca <- rnorm(10000, R_cac40, S_cac40)
hist(Ca, 100, xlim = c(-4, 4), ylim = c(0, 450), main = "")
abline(v = R_cac40.realized, col = "blue", lwd = 3)
abline(v = quantile(Ca, 0.025), lty = 2)
abline(v = quantile(Ca, 0.975), lty = 2)
Figure 6.13: CAC40 target-return allocation and realized outcome.

The CAC 40 target-return allocation also produces a positive realized return in this one-day evaluation. Because this allocation uses a negative FTSE weight, its apparent improvement has to be read together with short-selling feasibility and implementation constraints.

The portfolio that targets the CAC 40 expected return can also be located in mean-variance space.

Code
plot(S, ER, type = "l", lwd = 3, xlim = c(0.7, 1.6),
     ylim = c(-0.022, 0.007), ylab = "Expected return",
     xlab = "Standard deviation")
points(diag(sigma)^0.5, E)
abline(0, 0, lty = 3)
points(S_cac40, R_cac40, col = "red", pch = 19, cex = 2)
points(0.68, R_cac40.realized, col = "red", pch = 15, cex = 2)
text(S_cac40, R_cac40, "CAC40
     expected return", pos = 2, cex = 0.8)
text(sd,E,stocks,adj=-0.5,cex=0.8)
W_benchmark = alpha / 10000
R_benchmark = c(t(W_benchmark) %*% E)
S_benchmark = c(t(W_benchmark) %*% sigma %*% W_benchmark)^0.5
points(S_benchmark, R_benchmark, col = "blue", pch = 19, cex = 2)
points(0.68, R_benchmark.realized, col = "blue", pch = 15, cex = 2)
abline(0,R_benchmark / S_benchmark, lty = 3, col = "blue")
abline(0,R_cac40 / S_cac40, lty = 3, col = "red")
abline(0,E[3] / sd[3], lty = 3)
abline(v = S_cac40, lty = 3)
text(S_benchmark, R_benchmark, "Benchmark
     allocation", adj = -0.3, cex = 0.8, pos = 1)
legend("bottomleft",
       legend = c("DJ=0.4,     FTSE=0.3,      CAC=0.1,     Nikkei=0.2",
                          "DJ=0.607, FTSE=-0.403, CAC=0.459, Nikkei=0.336"), col = c("blue", "red"),
       pch = 19, bg = "white", cex = 0.9)
Figure 6.14: CAC40 target-return allocation in mean-variance space.

The realized returns on Friday September 26, 2008 are used to compare the three allocations.

Code
(R_min.realized)
[1] 0.2629549
Code
(R_benchmark.realized)
[1] -0.5583249
Code
(R_cac40.realized)
[1] 0.5183212

The original portfolio allocation produces a loss on the realized day. It would lead to a return of -0.558%, equivalent to a loss of 0.558%. The minimum-variance allocation would lead to a positive return of 0.2629%, and the portfolio that targets the CAC 40 expected return leads to a realized return of 0.518%. The last case uses a short FTSE position, so it requires additional feasibility checks before being interpreted as an implementable allocation.

6.5 Model building approach with optimal weights

The two optimized portfolios are evaluated with the model-building VaR approach. This keeps the covariance estimate fixed and changes only the portfolio weights, which isolates the effect of allocation on VaR. For any weight vector \(w\), the dollar allocation is \(\alpha(w)=10{,}000w\) in thousands of dollars and the normal VaR is

\[ VaR_{0.99}(w)=z_{0.99}\sqrt{\alpha(w)'\Sigma\alpha(w)}. \]

Code
# Model-building approach with equal observation weights.
alpha2 <- c(W_min * 10000)
alpha3 <- c(W.cac40 * 10000)
# Minimum-variance optimal portfolio.
Pvar2 <- t(alpha2) %*% Rcov %*% alpha2
Psd2 <- Pvar2^0.5
VaR.ew.optimal <- qnorm(0.99, 0, 1) * Psd2
# CAC 40 target-return optimal portfolio.
Pvar3 <- t(alpha3) %*% Rcov %*% alpha3
Psd3 <- Pvar3^0.5
VaR.ew.optimal.cac <- qnorm(0.99, 0, 1) * Psd3

The results table includes the model-building VaR estimates with optimal weights.

Code
Results.updated <- Results.updated %>%
  add_row(VaR.method =
        c("Model building equal observation weights, minimum variance weights",
          "Model building equal observation weights, targeting CAC40"),
          VaR = c(VaR.ew.optimal, VaR.ew.optimal.cac)) %>%
  arrange(VaR.method)
Results.updated
                                                          VaR.method      VaR
1                Historical approximate quantile(), original weights 218.3281
2                            Historical simulation, original weights 253.3850
3                              Model building EWMA, original weights 470.9187
4 Model building equal observation weights, minimum variance weights 194.3892
5         Model building equal observation weights, original weights 217.9751
6          Model building equal observation weights, targeting CAC40 203.7821

The second calculation uses the EWMA covariance matrix.

Code
Pvar2_EWMA <- t(alpha2) %*% C %*% alpha2
Psd2_EWMA <- Pvar2_EWMA^0.5
VaR.ewma.optimal <- qnorm(0.99, 0, 1) * Psd2_EWMA
VaR.ewma.optimal
         [,1]
[1,] 338.3195
Code
Pvar3_EWMA <- t(alpha3) %*% C %*% alpha3
Psd3_EWMA <- Pvar3_EWMA^0.5
VaR.ewma.optimal.cac <- qnorm(0.99, 0, 1) * Psd3_EWMA
VaR.ewma.optimal.cac
         [,1]
[1,] 348.1888

The results table includes the EWMA VaR estimates with optimal weights.

Code
Results.updated <- Results.updated %>%
  add_row(VaR.method =
            c("Model building EWMA, minimum variance weights",
              "Model building EWMA, targeting CAC40"),
          VaR = c(VaR.ewma.optimal, VaR.ewma.optimal.cac)) %>%
  arrange(VaR.method)
Results.updated
                                                          VaR.method      VaR
1                Historical approximate quantile(), original weights 218.3281
2                            Historical simulation, original weights 253.3850
3                      Model building EWMA, minimum variance weights 338.3195
4                              Model building EWMA, original weights 470.9187
5                               Model building EWMA, targeting CAC40 348.1888
6 Model building equal observation weights, minimum variance weights 194.3892
7         Model building equal observation weights, original weights 217.9751
8          Model building equal observation weights, targeting CAC40 203.7821

6.6 Historical approach with optimal weights

The final comparison returns to historical simulation and applies the same scenario returns to the optimized weights. This keeps the market scenarios fixed and changes only the portfolio exposure. For 500 scenarios, the empirical 99% VaR is again the fifth-worst loss.

\[ VaR_{0.99}^{hist}(w)=l_{(5)}(w), \]

where losses are sorted from largest to smallest.

Code
df.Adj.S <- df.Adj.S %>%
  mutate(p2 = (alpha2[1] * SDJIA / DJIA[501]) +
             (alpha2[2] * SFTSE100 / AFTSE100[501]) +
             (alpha2[3] * SCAC40 / ACAC40[501]) +
             (alpha2[4] * SNikkei / ANikkei[501])) %>%
  mutate(l2 = 10000 - p2) %>% # Losses l
  mutate(p3 = (alpha3[1] * SDJIA / DJIA[501]) +
             (alpha3[2] * SFTSE100 / AFTSE100[501]) +
             (alpha3[3] * SCAC40 / ACAC40[501]) +
             (alpha3[4] * SNikkei / ANikkei[501])) %>%
  mutate(l3 = 10000 - p3) # Losses l

The new portfolio losses are calculated under the optimized weights.

Code
l2 <- df.Adj.S %>%
  filter(obs > 0 & obs < 501) %>%
  select(l2) %>%
  unlist()

l2.tomorrow <- df.Adj.S %>%
  filter(obs == 501) %>%
  select(l2) %>%
  unlist()

l3 <- df.Adj.S %>%
  filter(obs > 0 & obs < 501) %>%
  select(l3) %>%
  unlist()

l3.tomorrow <- df.Adj.S %>%
  filter(obs == 501) %>%
  select(l3) %>%
  unlist()

The benchmark allocation provides the reference distribution.

Code
plot(l, type = "h", ylim = c(-600, 600), lwd = 2,
     xlab = "500 scenarios (sorted by historical date)",
ylab = "Portfolio gains (-) and losses (+)",
col = ifelse(l < 0, "blue", "red"))
abline(0, 0)
abline(v = 0)
abline(h = max(l), lty = 2)
abline(h = min(l), lty = 2)
Figure 6.15: Losses with the original weights DJ=0.4, FTSE=0.3, CAC=0.1, Nikkei=0.2.

The benchmark distribution contains several large downside scenarios, and the fifth-worst loss is the historical 99% VaR for the original allocation.

The one-day 99% value at risk can be estimated as the fifth-worst loss.

Code
sort(l)[496]
   l487 
253.385 

The historical approach is then applied with minimum-variance weights.

Code
plot(l2, type = "h", ylim = c(-600, 600), lwd = 2,
     xlab = "500 scenarios (sorted by historical date)",
     ylab = "Portfolio gains (-) and losses (+)",
     col = ifelse(l2 < 0, "blue", "red"))
abline(0, 0)
abline(v = 0)
abline(h = max(l2), lty = 2)
abline(h = min(l2), lty = 2)
Figure 6.16: Losses with minimum-variance weights DJ=0.56, FTSE=0.038, CAC=0.022, Nikkei=0.38.

The range of losses and gains is reduced under the minimum-variance weights. The one-day 99% Value at Risk can be estimated as the fifth-worst loss.

Code
sort(l2)[496]
   l2487 
213.6898 

The results table is updated with this case.

Code
Results.updated <- Results.updated %>%
  add_row(VaR.method = "Historical simulation, minimum variance weights",
          VaR = sort(l2)[496]) %>%
  arrange(VaR.method)
Results.updated
                                                          VaR.method      VaR
1                Historical approximate quantile(), original weights 218.3281
2                    Historical simulation, minimum variance weights 213.6898
3                            Historical simulation, original weights 253.3850
4                      Model building EWMA, minimum variance weights 338.3195
5                              Model building EWMA, original weights 470.9187
6                               Model building EWMA, targeting CAC40 348.1888
7 Model building equal observation weights, minimum variance weights 194.3892
8         Model building equal observation weights, original weights 217.9751
9          Model building equal observation weights, targeting CAC40 203.7821

The final case is the portfolio targeting the CAC 40 expected return.

Code
plot(l3, type = "h", ylim = c(-600, 600), lwd = 2,
     xlab = "500 scenarios (sorted by historical date)",
     ylab = "Portfolio gains (-) and losses (+)",
     col = ifelse(l3 < 0, "blue", "red"))
abline(0, 0)
abline(v = 0)
abline(h = max(l3), lty = 2)
abline(h = min(l3), lty = 2)
Figure 6.17: Losses with CAC40 target-return weights DJ=0.607, FTSE=-0.403, CAC=0.459, Nikkei=0.336.

The CAC 40 target-return allocation also compresses much of the loss range while still allowing larger gains in some scenarios. The one-day 99% Value at Risk can be estimated as the fifth-worst loss. Because this portfolio includes a short FTSE position, its risk reduction is linked to an exposure that may be constrained in practice.

Code
sort(l3)[496]
   l3494 
248.8909 

The results table is updated again.

Code
Results.updated <- Results.updated %>%
  add_row(VaR.method = "Historical simulation, targeting CAC40",
          VaR = sort(l3)[496]) %>%
  arrange(VaR.method)
Results.updated
                                                           VaR.method      VaR
1                 Historical approximate quantile(), original weights 218.3281
2                     Historical simulation, minimum variance weights 213.6898
3                             Historical simulation, original weights 253.3850
4                              Historical simulation, targeting CAC40 248.8909
5                       Model building EWMA, minimum variance weights 338.3195
6                               Model building EWMA, original weights 470.9187
7                                Model building EWMA, targeting CAC40 348.1888
8  Model building equal observation weights, minimum variance weights 194.3892
9          Model building equal observation weights, original weights 217.9751
10          Model building equal observation weights, targeting CAC40 203.7821

The three historical approaches are compared side by side.

Code
par(mfrow=c(1, 3), oma = c(0, 0, 2, 0))
par(pty = "s")
plot(l, type = "h", ylim = c(-600, 600), lwd = 2, xlab = "",
     main = "Benchmark allocation",
     ylab = "Portfolio gains (-) and losses (+)",
     col = ifelse(l < 0, "blue", "red"))
abline(0, 0)
abline(v = 0)
abline(h = max(l), lty = 2)
abline(h = min(l), lty = 2)
plot(l2, type = "h", ylim = c(-600, 600), lwd = 2,
     main = "Minimum variance",
     xlab = "500 scenarios sorted by historical date", ylab = "",
     col = ifelse(l2 < 0, "blue", "red"))
abline(0, 0)
abline(v = 0)
abline(h = max(l2), lty = 2)
abline(h = min(l2), lty = 2)
plot(l3, type = "h", ylim = c(-600, 600), lwd = 2, xlab = "",
     main = "CAC40 target return", ylab = "",
     col = ifelse(l3 < 0, "blue", "red"))
abline(0, 0)
abline(v = 0)
abline(h = max(l3), lty = 2)
abline(h = min(l3), lty = 2)
Figure 6.18: Historical losses under original, minimum-variance, and CAC40-target weights.

The side-by-side view makes the effect of allocation visible. The optimized portfolios reduce the height of the worst loss bars in this historical window, while the CAC 40 target-return case changes the distribution partly through the short FTSE exposure.

The quantiles provide a compact comparison of the three loss distributions.

Code
lq <- quantile(l, c(0.25, 0.5, 0.75))
lq2 <- quantile(l2, c(0.25, 0.5, 0.75))
lq3 <- quantile(l3, c(0.25, 0.5, 0.75))
ll <- data.frame(lq, lq2, lq3)
colours <- c("red", "orange", "blue")
Code
barplot(as.matrix(t(ll)), beside = TRUE, col = colours,
        ylim = c(-60, 60), xlab = "Quantiles",
        ylab = "Portfolio gains (-) and Losses (+)")
legend("topleft", c("Benchmark","Minimum variance",
                    "CAC40 target return"), cex = 1.3, bty = "n",
       fill = colours)
Figure 6.19: Loss quantiles under alternative portfolio weights.

The quantile comparison summarizes the same message without the full time ordering of scenarios: changing weights shifts the empirical loss distribution, and VaR changes because it is a tail quantile of that distribution.

The chapter’s main value is integration. The VaR number is the final summary, after the analyst prepares international index data, converts foreign values into US dollars, defines portfolio weights, generates loss scenarios, estimates volatility and covariance inputs, compares allocation choices, and reads the realized outcome. Within the Financial Modeling with R path, VaR turns data, returns, allocation, and market-risk assumptions into a single loss statement that can be audited and recomputed.