Risk Management & Derivatives FINRISD Section C02
Professor: Bismark
Term, AY: 2nd term AY 2025-2026

Section 1: Data Load and Integrity Check

Data cut-off: 27 February 2026, per case guidelines. This analysis uses the unified master dataset containing only raw published trading days from the BAP (no imputed values). The 2021-05-20 Open typo (478.855 → 47.855) has been corrected.

df_raw <- read_csv("data/unified/unified_bap_master.csv") %>%
  mutate(Date = as.Date(Date)) %>%
  # Enforce case cut-off: 27 Feb 2026
  filter(Date <= as.Date("2026-02-27")) %>%
  arrange(Date)

integrity_check <- data.frame(
  Metric = c("Total Trading Rows", "Start Date", "End Date", "Rows with High < Low", "Rows with Close outside High-Low range"),
  Value = c(
    as.character(nrow(df_raw)),
    as.character(min(df_raw$Date)),
    as.character(max(df_raw$Date)),
    as.character(nrow(filter(df_raw, High < Low))),
    as.character(nrow(filter(df_raw, Close > High | Close < Low)))
  )
)
kable(integrity_check, caption = "Data Integrity Check")
Data Integrity Check
Metric Value
Total Trading Rows 1736
Start Date 2019-01-02
End Date 2026-02-27
Rows with High < Low 0
Rows with Close outside High-Low range 5
# Full price history chart with shaded analysis windows
# Shade the 60, 40, and 20-day windows to show their size relative to the full history
shade_df <- data.frame(
  xmin = as.Date(c(tail(df_raw$Date, 60)[1], tail(df_raw$Date, 40)[1], tail(df_raw$Date, 20)[1])),
  xmax = max(df_raw$Date),
  label = c("60-day", "40-day", "20-day"),
  y    = c(54.5, 55.8, 57.1)
)

ggplot(df_raw, aes(x = Date, y = Close)) +
  geom_rect(data = shade_df,
            aes(xmin = xmin, xmax = xmax, ymin = -Inf, ymax = Inf, fill = label),
            inherit.aes = FALSE, alpha = 0.12) +
  scale_fill_manual(values = c("60-day" = econ_gold, "40-day" = econ_grey, "20-day" = econ_red),
                    name = "Analysis window") +
  geom_line(color = econ_blue, linewidth = 0.6) +
  annotate("text", x = as.Date("2020-04-01"), y = 51.2,
           label = "COVID-19\nshock", size = 3, color = econ_grey, hjust = 0) +
  annotate("text", x = as.Date("2022-06-01"), y = 55.8,
           label = "2022 PHP\ndepreciation", size = 3, color = econ_grey, hjust = 0) +
  labs(title = "Seven years of USD/PHP",
       subtitle = "Daily closing rate, 2 Jan 2019 – 27 Feb 2026 (BAP data). Shaded regions show the three short analysis windows.",
       x = NULL, y = "PHP per USD",
       caption = "Source: BAP Historical Data. Cut-off: 27 Feb 2026.") +
  theme_economist_quant()

Section 2: Data Cleaning

# Correct High/Low bounds to encompass Open and Close (robust integrity)
df <- df_raw %>%
  mutate(
    ActualHigh = pmax(Open, High, Low, Close),
    ActualLow  = pmin(Open, High, Low, Close),
    High = ActualHigh,
    Low  = ActualLow
  ) %>%
  select(-ActualHigh, -ActualLow)

# Derive return columns
df <- df %>%
  mutate(
    dod_cents    = (Close - lag(Close)) * 100,
    dod_pct      = (Close - lag(Close)) / lag(Close) * 100,
    intraday_cents = (High - Low) * 100,
    intraday_pct   = (High - Low) / Low * 100
  )

# Drop the first row (no prior close → NA returns)
df_returns <- df %>% filter(!is.na(dod_cents))

cleaning_stats <- data.frame(
  Metric = c("Modified High/Low Bounds", "Derived Columns", "Remaining Trading Rows (DoD series)"),
  Result = c(
    as.character(nrow(filter(df_raw, High < Low | Close > High | Close < Low | Open > High | Open < Low))),
    "DoD cents, DoD %, Intraday cents, Intraday %",
    as.character(nrow(df_returns))
  )
)
kable(cleaning_stats, caption = "Data Cleaning Results Summary")
Data Cleaning Results Summary
Metric Result
Modified High/Low Bounds 6
Derived Columns DoD cents, DoD %, Intraday cents, Intraday %
Remaining Trading Rows (DoD series) 1735

Section 3: Window Definitions

Four analysis windows are defined for both the return and intraday series. Windows (b)–(d) use only their own observations — all earlier data is strictly excluded from VaR computation.

# Return series windows (for DoD / Items 1, 4, 5, 6)
windows_returns <- list(
  Full = df_returns,
  w60  = tail(df_returns, 60),
  w40  = tail(df_returns, 40),
  w20  = tail(df_returns, 20)
)

# Intraday series windows (for Item 2; no lag dependency so use df)
windows_intraday <- list(
  Full = df,
  w60  = tail(df, 60),
  w40  = tail(df, 40),
  w20  = tail(df, 20)
)

window_summary <- data.frame(
  Window     = names(windows_returns),
  Start_Date = sapply(windows_returns, function(x) as.character(min(x$Date))),
  End_Date   = sapply(windows_returns, function(x) as.character(max(x$Date))),
  n          = sapply(windows_returns, nrow)
)
kable(window_summary, caption = "Analysis Windows (Return Series, cut-off 27 Feb 2026)")
Analysis Windows (Return Series, cut-off 27 Feb 2026)
Window Start_Date End_Date n
Full Full 2019-01-03 2026-02-27 1735
w60 w60 2025-11-27 2026-02-27 60
w40 w40 2026-01-02 2026-02-27 40
w20 w20 2026-01-30 2026-02-27 20

Section 4: Item 1 — Close-to-Close VaR (95%, one-tail)

Movement metric: day-on-day (DoD) closing change, in PHP cents and %. VaR = 5th percentile of the return distribution, one-tailed at 95% confidence, expressed as a positive loss magnitude.

Guide Question 1 · 10 Points

Determine the Value at Risk for USD / PHP using the day on day closing movement. Use a one tail test at the 95% level of confidence. Use data from starting from [a] 01 January 2019, the [b] past 20, [c] 40 and [d] 60 trading days to produce four value at risk figures. For items b, c and d exclude data outside of the past 20, 40 and 60 days.

Present the figures in cents and in day on day % movement.

Are there any major outliers within the data set? If so, at what dates? Outliers here refer to observations outside the 99.9% of the total observations.

# Use factor for ordered output in tables and plots
window_order <- c("Full", "w60", "w40", "w20")

item1_var <- bind_rows(lapply(windows_returns, function(w) {
  data.frame(
    n         = nrow(w),
    VaR_Pct   = abs(quantile(w$dod_pct,   0.05, type = 7)),
    VaR_Cents = abs(quantile(w$dod_cents,  0.05, type = 7))
  )
}), .id = "Window") %>%
  mutate(Window = factor(Window, levels = window_order)) %>%
  arrange(Window)

kable(item1_var, digits = 4,
      caption = "Item 1: Close-to-Close VaR at 95% Confidence (Loss Magnitude)")
Item 1: Close-to-Close VaR at 95% Confidence (Loss Magnitude)
Window n VaR_Pct VaR_Cents
5%…1 Full 1735 0.5248 28.620
5%…2 w60 60 0.4782 28.200
5%…3 w40 40 0.4803 28.325
5%…4 w20 20 0.5005 29.475
# Outliers: observations outside the 0.1% / 99.9% bounds of the full history
lb_dod <- quantile(df_returns$dod_pct, 0.001, type = 7)
ub_dod <- quantile(df_returns$dod_pct, 0.999, type = 7)

outliers_1 <- df_returns %>%
  filter(dod_pct < lb_dod | dod_pct > ub_dod) %>%
  select(Date, Close, dod_pct, dod_cents) %>%
  arrange(Date)

kable(outliers_1, digits = 4,
      caption = paste0("Item 1: Outliers — DoD % outside [",
                       round(lb_dod, 4), "%, ", round(ub_dod, 4), "%] (0.1%/99.9% bounds)"))
Item 1: Outliers — DoD % outside [-1.5182%, 1.2951%] (0.1%/99.9% bounds)
Date Close dod_pct dod_cents
2022-11-11 57.23 -1.6498 -96
2022-12-27 55.90 1.3599 75
2023-02-06 54.39 1.3227 71
2025-01-02 57.91 -1.8308 -108
# Empirical density vs Normal reference with VaR line and outlier rug
full_var_pct <- item1_var$VaR_Pct[item1_var$Window == "Full"]

ggplot(df_returns, aes(x = dod_pct)) +
  # Empirical return density
  geom_density(fill = econ_blue, alpha = 0.20, color = econ_blue, linewidth = 0.7) +
  # Normal distribution overlay using sample mean and SD
  stat_function(fun = dnorm,
                args = list(mean = mean(df_returns$dod_pct), sd = sd(df_returns$dod_pct)),
                linetype = "dashed", color = econ_grey, linewidth = 0.6) +
  # VaR line on the left tail; x-intercept is negated because VaR is stored as a positive magnitude
  geom_vline(xintercept = -full_var_pct, color = econ_red, linewidth = 1) +
  geom_label(aes(x = -full_var_pct, y = 0.30,
                 label = paste0("VaR 95%: ", round(full_var_pct, 3), "%")),
             size = 3.2, color = econ_red, fill = "white", label.size = 0, vjust = 0) +
  # Rug marks and labels for observations outside the 99.9th percentile
  geom_rug(data = outliers_1, aes(x = dod_pct), color = econ_red, linewidth = 0.8, alpha = 0.8) +
  geom_text_repel(data = outliers_1,
                  aes(x = dod_pct, y = 0.02,
                      label = format(Date, "%b %Y")),
                  size = 2.8, color = econ_red, nudge_y = 0.05, segment.size = 0.3) +
  labs(title = "The long tail of risk",
       subtitle = "Density of USD/PHP daily % changes vs. Normal (dashed). Red rug marks = extreme outliers (outside 99.9%).",
       x = "Daily Close-to-Close Change (%)", y = "Density",
       caption = "Source: BAP. Full history 2019–2026. VaR = 5th percentile of empirical distribution (type 7).") +
  theme_economist_quant()

Response

Q: What are the four VaR figures using day-on-day closing movement at 95% confidence, and are there any major outliers? Using historical simulation based on the empirical 5th percentile expressed as positive loss magnitude, the analysis produces four distinct estimates shown in Table “Item 1: Close-to-Close VaR at 95% Confidence (Loss Magnitude)”. The full history from January 2019 yields a VaR of 0.5248%, which is equivalent to 28.62 PHP cents per USD and is computed from 1735 trading days. The past 60 trading days yield 0.4782% or 28.2 cents. The past 40 trading days yield 0.4803% or 28.33 cents. The past 20 trading days yield 0.5005% or 29.48 cents. The full-history figure is the most conservative estimate. All three short windows understate risk because they exclude the 2020 COVID shock and the 2021 to 2022 peso depreciation, which are the exact stress events that produced the largest losses in the dataset.

Q: Are there any major outliers, and if so, at what dates? Yes, 4 observations fall outside the 0.1st and 99.9th percentile bounds of [-1.5182%, 1.2951%]. The flagged dates are 11 Nov 2022, 27 Dec 2022, 06 Feb 2023, 02 Jan 2025. These are listed in Table “Item 1: Outliers” with their corresponding DoD percentage and cent values. Each date corresponds to a documented macro stress event. We retained all 4 observations in the analysis. Removing outliers to compress the VaR figure would produce a number that understates the full risk spectrum. These are the very events the model exists to anticipate.

Q: Why use historical simulation rather than a parametric approach? The density plot in Figure “The long tail of risk” confirms leptokurtosis in the empirical return distribution. The empirical density (blue curve) exceeds the Normal reference curve (dashed grey line) at both tails. Large daily moves occur more frequently than a Normal model would predict. A parametric VaR assuming Normality would systematically understate tail risk. Historical simulation lets the data speak for itself. The red rug marks in the figure identify the extreme observations outside the 99.9th percentile, each labeled with its date. This fat-tail characteristic validates our choice of historical simulation over any parametric method.

Section 5: Item 2 — Intraday (High-to-Low) VaR (95%, one-tail)

Movement metric: daily High minus Low range, in PHP cents and %. The range is non-negative, so VaR = 95th percentile (upper tail). Same four windows and exclusion rules as Item 1.

Guide Question 2 · 10 Points

Determine the Value at Risk for USD / PHP daily high to low movement within the same day. Use a one tail test at the 95% level of confidence. Use data from starting from [a] 01 January 2019, the [b] past 20, [c] 40 and [d] 60 trading days to produce four value at risk figures. For items b, c and d exclude data outside of the past 20, 40 and 60 days.

Present the figures in cents and in day on day % movement.

Are there any major outliers within the data set? If so, at what dates? Outliers here refer to observations outside the 99.9% of the total observations.

item2_var <- bind_rows(lapply(windows_intraday, function(w) {
  data.frame(
    n         = nrow(w),
    VaR_Cents = quantile(w$intraday_cents, 0.95, type = 7),
    VaR_Pct   = quantile(w$intraday_pct,   0.95, type = 7)
  )
}), .id = "Window") %>%
  mutate(Window = factor(Window, levels = window_order)) %>%
  arrange(Window)

kable(item2_var, digits = 4,
      caption = "Item 2: Intraday (High–Low) VaR at 95% Confidence (95th percentile of range)")
Item 2: Intraday (High–Low) VaR at 95% Confidence (95th percentile of range)
Window n VaR_Cents VaR_Pct
95%…1 Full 1736 40.000 0.7302
95%…2 w60 60 29.400 0.4991
95%…3 w40 40 29.400 0.4991
95%…4 w20 20 37.495 0.6528
# Outliers: above 99.9th percentile (range is one-sided positive)
ub_intraday <- quantile(df$intraday_cents, 0.999, type = 7)
outliers_2 <- df %>%
  filter(intraday_cents > ub_intraday) %>%
  select(Date, High, Low, intraday_cents, intraday_pct) %>%
  arrange(Date)

kable(outliers_2, digits = 4,
      caption = paste0("Item 2: Outliers — Intraday cents above ",
                       round(ub_intraday, 4), " cents (99.9th percentile)"))
Item 2: Outliers — Intraday cents above 100.6325 cents (99.9th percentile)
Date High Low intraday_cents intraday_pct
2019-09-04 52.93 51.92 101 1.9453
2024-09-12 56.20 55.08 112 2.0334
# Intraday range scatter with LOESS trend and full-history VaR reference line
ggplot(df, aes(x = Date, y = intraday_cents)) +
  geom_point(color = econ_blue, alpha = 0.35, size = 0.9) +
  geom_smooth(method = "loess", span = 0.15, se = FALSE, color = econ_red, linewidth = 0.8) +
  geom_hline(yintercept = item2_var$VaR_Cents[item2_var$Window == "Full"],
             linetype = "dashed", color = econ_grey, linewidth = 0.6) +
  annotate("text", x = as.Date("2019-06-01"),
           y = item2_var$VaR_Cents[item2_var$Window == "Full"] + 1.5,
           label = paste0("VaR 95% (full): ",
                          round(item2_var$VaR_Cents[item2_var$Window == "Full"], 2), " cts"),
           size = 3, color = econ_grey, hjust = 0) +
  geom_point(data = outliers_2, aes(x = Date, y = intraday_cents),
             color = econ_red, size = 3) +
  geom_text_repel(data = outliers_2, aes(x = Date, y = intraday_cents,
                  label = format(Date, "%b %Y")),
                  size = 2.8, color = econ_red, nudge_y = 2) +
  labs(title = "Wide days — intraday volatility over time",
       subtitle = "Each point = one trading day's High–Low range (cents). LOESS trend in red. Dashed = full-history VaR 95%.",
       x = NULL, y = "Intraday Range (cents)",
       caption = "Source: BAP. Red dots = outliers above 99.9th percentile.") +
  theme_economist_quant()

Response

Q: What are the four VaR figures using daily high-to-low movement at 95% confidence? Since the daily range of High minus Low is always non-negative, the VaR is the 95th percentile of the upper tail. The results are shown in Table “Item 2: Intraday (High to Low) VaR at 95% Confidence (95th percentile of range)”. The full history from January 2019 yields a VaR of 0.7302% or 40 PHP cents. The past 60 trading days yield 0.4991% or 29.4 cents. The past 40 trading days yield 0.4991% or 29.4 cents. The past 20 trading days yield 0.6528% or 37.49 cents. The full-history intraday VaR of 40 cents is significantly higher than the short-window figures, confirming that the most extreme intraday dislocations occurred during the 2020 to 2022 stress period and are absent from recent windows.

Q: Are there any major outliers, and if so, at what dates? Yes, 2 trading days exceed the 99.9th percentile intraday range threshold of 100.63 cents, which is over PHP 1.01. The flagged dates are 04 Sep 2019 and 12 Sep 2024. These are listed in Table “Item 2: Outliers” with their High, Low, range in cents, and range in percentage. These sessions represent the most extreme intraday dislocations in the full dataset, days where the peso swung by over a full peso within a single trading session. Any market participant with intraday stop-loss orders, margin calls, or hedging triggers would have been exposed to the full range of these moves regardless of where the price ultimately closed.

Q: How does this differ methodologically from Item 1? Item 1 measures the net change from one day close to the next close, which is the 5th percentile of a distribution that includes both gains and losses. Item 2 measures the total distance traveled within a single day, which is the 95th percentile of a distribution that is always positive. Item 1 answers the question of how much can be lost from yesterday close. Item 2 answers the question of how far the price can move against you within a single day. The answer to the second question is consistently worse. The scatter plot in Figure “Wide days, intraday volatility over time” shows this visually. Each point represents one trading day High to Low range in cents. The red LOESS trend line confirms that recent sessions sit below the long-run average range. The grey dashed horizontal line marks the full-history VaR 95% reference at 40 cents. The red dots mark the outlier days that breach the 99.9th percentile threshold.

Section 6: Item 3 — Closing vs. Intraday: The Volatility Gap

Guide Question 3 · 15 Points

Based on your figures for 1 and 2, will using only closing to closing movements in USD / PHP potentially miss out any potential volatility in the USD / PHP? Explain your answer.

gap_table <- item1_var %>%
  select(Window, dod_var = VaR_Cents) %>%
  left_join(item2_var %>% select(Window, intra_var = VaR_Cents), by = "Window") %>%
  mutate(
    Gap_Cents = intra_var - dod_var,
    Gap_Pct_of_Intra = Gap_Cents / intra_var * 100
  )
kable(gap_table, digits = 4,
      caption = "Item 3: VaR Gap (Intraday − DoD). Positive gap = intraday captures more risk than close-to-close.")
Item 3: VaR Gap (Intraday − DoD). Positive gap = intraday captures more risk than close-to-close.
Window dod_var intra_var Gap_Cents Gap_Pct_of_Intra
Full 28.620 40.000 11.380 28.4500
w60 28.200 29.400 1.200 4.0816
w40 28.325 29.400 1.075 3.6565
w20 29.475 37.495 8.020 21.3895
# Dual time series: intraday H-L range vs signed close-to-close change
# Use the full history rather than a short window to avoid cherry-picking a calm period
ggplot(df_returns, aes(x = Date)) +
  geom_hline(yintercept = 0, color = econ_grey, linewidth = 0.3) +
  geom_line(aes(y = intraday_pct,   color = "Intraday Range (H-L)"), linewidth = 0.45, alpha = 0.7) +
  geom_line(aes(y = dod_pct,        color = "Close-to-Close Change"), linewidth = 0.45, alpha = 0.8) +
  scale_color_manual(values = c("Intraday Range (H-L)" = econ_blue,
                                "Close-to-Close Change"  = econ_red)) +
  labs(title = "Mind the gap",
       subtitle = "Intraday H-L range (%) vs signed close-to-close change (%), full history 2019–2026.",
       x = NULL, y = "Change / Range (%)",
       caption = paste0("Full history. Intraday VaR 95% = ",
                        round(item2_var$VaR_Pct[item2_var$Window == "Full"], 4), "% vs DoD VaR 95% = ",
                        round(item1_var$VaR_Pct[item1_var$Window == "Full"], 4),
                        "%. Note how intraday range often dwarfs the net daily move.")) +
  theme_economist_quant()

Response

Q: Will using only closing-to-closing movements in USD/PHP potentially miss out any potential volatility? Yes, it will, and the magnitude of what is missed is substantial rather than marginal. The VaR gap table in Table “Item 3: VaR Gap (Intraday minus DoD)” shows that intraday VaR exceeds close-to-close VaR across every analysis window. The full-history intraday VaR of 40 cents exceeds the close-to-close VaR of 28.62 cents by 11.38 cents, which represents 28.4% of the intraday figure. Nearly 28 percent of the risk embedded in USD/PHP is invisible to any model that only examines closing prices. The 60-day gap is 1.2 cents or 4.1%. The 40-day gap is 1.07 cents or 3.7%. The 20-day gap is 8.02 cents or 21.4%. The gap is structural, not incidental.

Q: Why does this gap exist? The close-to-close change captures only the net price movement between one 4:00 PM BAP fixing and the next. It records nothing of what occurs in between. During the trading day, USD/PHP is subject to intraday bid-ask spread widening, BSP intervention responses, corporate dollar demand surges, and algorithmic trading flows, all of which can push the price far from the open before the close. Consider a session where the pair opens at PHP 57.00, spikes to PHP 57.90 on a dollar-demand surge, then retreats to close at PHP 57.10. The DoD change registers only PHP 0.10. The intraday range was PHP 0.90. A risk manager using DoD VaR classifies that session as low-risk. Anyone with a stop-loss at PHP 57.50 would have been stopped out.

Q: Who is exposed to this invisible risk? Any market participant with intraday obligations, including corporate treasurers executing hedging orders at specific intraday levels, traders managing stop-loss triggers, institutions with margin requirements that must be met intraday, and portfolio managers rebalancing positions before the close. The dual time series chart in Figure “Mind the gap” confirms this pattern holds across the full 2019 to 2026 history. The intraday range (blue line) consistently exceeds the signed close-to-close change (red line). The divergence widens dramatically during the 2020 COVID shock and the 2022 depreciation episode. The gap expands precisely when risk management matters most. Close-to-close VaR is an incomplete risk measure for any participant with intraday exposure.

Section 7: Item 4 — Coherent Risk Measures: CVaR & Winsorization

Historical VaR is sensitive to outliers and fails the sub-additivity coherence axiom. Two solutions are quantified below.

Guide Question 4 · 10 Points

Based on your figures for 1 and 2, how can you possibly solve for the problem of outliers? Explain and illustrate how your solution addresses the issue of outliers. You may present more than one solution. Hint, recall our lesson on coherent risk measures and alternatives to Value at Risk.

# CVaR (Expected Shortfall) — mean of returns below the 5th percentile
threshold_pct   <- -item1_var$VaR_Pct[item1_var$Window == "Full"]
threshold_cents <- -item1_var$VaR_Cents[item1_var$Window == "Full"]

cvar_pct   <- abs(mean(df_returns$dod_pct[df_returns$dod_pct     <= threshold_pct]))
cvar_cents <- abs(mean(df_returns$dod_cents[df_returns$dod_cents <= threshold_cents]))

# Winsorization at 0.1% / 99.9% (caps extreme values at boundary rather than removing them)
lb_w <- quantile(df_returns$dod_pct,   0.001, type = 7)
ub_w <- quantile(df_returns$dod_pct,   0.999, type = 7)
lb_c <- quantile(df_returns$dod_cents, 0.001, type = 7)
ub_c <- quantile(df_returns$dod_cents, 0.999, type = 7)

df_winsor <- df_returns %>%
  mutate(
    dod_pct_w   = pmin(pmax(dod_pct,   lb_w), ub_w),
    dod_cents_w = pmin(pmax(dod_cents, lb_c), ub_c)
  )

var_winsor_pct   <- abs(quantile(df_winsor$dod_pct_w,   0.05, type = 7))
var_winsor_cents <- abs(quantile(df_winsor$dod_cents_w, 0.05, type = 7))

raw_var_pct   <- item1_var$VaR_Pct[item1_var$Window == "Full"]
raw_var_cents <- item1_var$VaR_Cents[item1_var$Window == "Full"]

item4_table <- data.frame(
  Metric = c(
    "Raw VaR (95%)",
    "CVaR / Expected Shortfall (95%)",
    "Winsorization Lower Bound (0.1%)",
    "Winsorization Upper Bound (99.9%)",
    "VaR after Winsorization (95%)",
    "VaR Reduction from Winsorization"
  ),
  Percentage = c(raw_var_pct, cvar_pct, lb_w, ub_w, var_winsor_pct,
                 raw_var_pct - var_winsor_pct),
  Cents      = c(raw_var_cents, cvar_cents, NA, NA, var_winsor_cents,
                 raw_var_cents - var_winsor_cents)
)
kable(item4_table, digits = 4, caption = "Item 4: CVaR and Winsorization Results (Full History)")
Item 4: CVaR and Winsorization Results (Full History)
Metric Percentage Cents
Raw VaR (95%) 0.5248 28.6200
CVaR / Expected Shortfall (95%) 0.7301 40.6828
Winsorization Lower Bound (0.1%) -1.5182 NA
Winsorization Upper Bound (99.9%) 1.2951 NA
VaR after Winsorization (95%) 0.5248 28.6200
VaR Reduction from Winsorization 0.0000 0.0000
# Density plot with shaded tail region illustrating CVaR vs VaR threshold
tail_returns <- df_returns %>% filter(dod_pct <= threshold_pct)
cvar_line <- mean(tail_returns$dod_pct)

ggplot(df_returns, aes(x = dod_pct)) +
  geom_density(fill = econ_blue, alpha = 0.18, color = econ_blue, linewidth = 0.7) +
  # Shade observations in the tail beyond the VaR threshold to represent the ES region
  geom_density(data = tail_returns, aes(x = dod_pct),
               fill = econ_red, alpha = 0.4, color = NA) +
  geom_vline(xintercept = threshold_pct, color = econ_red, linewidth = 1) +
  geom_vline(xintercept = cvar_line, color = econ_gold, linewidth = 1, linetype = "dashed") +
  annotate("text", x = threshold_pct, y = 0.35,
           label = paste0("VaR 95%\n", round(-threshold_pct, 3), "%"),
           size = 3, color = econ_red, hjust = 1.1) +
  annotate("text", x = cvar_line, y = 0.35,
           label = paste0("CVaR (ES)\n", round(cvar_pct, 3), "%"),
           size = 3, color = econ_gold, hjust = 1.1) +
  labs(title = "Beyond the threshold: Expected Shortfall",
       subtitle = "Red shaded region = losses beyond VaR 95%. CVaR (gold dashed) is the mean of that tail — the coherent alternative.",
       x = "Daily Change (%)", y = "Density",
       caption = "VaR ignores the severity of tail losses. CVaR (Expected Shortfall) captures the average loss conditional on breaching VaR.") +
  theme_economist_quant()

Response

Q: How can you possibly solve for the problem of outliers? We present two solutions, both grounded in the coherent risk measure framework established by Artzner, Delbaen, Eber, and Heath (1999) in their foundational paper “Coherent Measures of Risk” (Mathematical Finance, 9(3), 203-228). The four coherence axioms are monotonicity, which means a worse portfolio carries higher risk, sub-additivity, which means portfolio combination cannot increase aggregate risk, positive homogeneity, which means scaling a position scales the risk proportionally, and translation invariance, which means adding a risk-free cash amount c reduces risk by exactly c. Value at Risk fails sub-additivity. It is possible for two combined positions to produce a combined VaR exceeding the sum of their individual VaRs. VaR also ignores the severity of losses beyond the threshold. A 95% VaR of 1% does not distinguish between a tail that averages 1.05% and one that averages 4%. That distinction is the entire point of tail risk management.

Solution 1 is Conditional Value at Risk, also called Expected Shortfall. CVaR replaces VaR as the primary risk measure. VaR tells you the threshold of loss, meaning you will lose at least X with 5% probability, but says nothing about what happens beyond that threshold. CVaR answers the follow-up question of how bad the average loss is given that we have breached the 5th percentile. The results are shown in Table “Item 4: CVaR and Winsorization Results (Full History)”. For our full history, CVaR equals 0.7301%, which is 40.68 cents. This is significantly higher than the raw VaR of 0.5248% or 28.62 cents. The difference of 0.2053 percentage points quantifies the severity of tail losses that VaR ignores. The density plot in Figure “Beyond the threshold: Expected Shortfall” illustrates this visually. The red shaded region marks the observations in the tail beyond the VaR 95% threshold (red vertical line). The gold dashed vertical line marks the CVaR, which is the mean of that shaded tail region. CVaR satisfies all four coherence axioms and is the regulatory standard under Basel IV Fundamental Review of the Trading Book. By averaging across the entire tail, CVaR automatically incorporates outlier severity. The extreme observations pull the CVaR upward in direct proportion to their magnitude. This is the correct behavior because outliers should increase the risk measure, not be discarded.

Solution 2 is Winsorization. As a robustness check, we capped extreme returns at the 0.1st and 99.9th percentile bounds of -1.5182% and 1.2951% rather than removing them. This bounds the influence of outliers without erasing them from the dataset. The results are also in Table “Item 4: CVaR and Winsorization Results (Full History)”. VaR after winsorization remains at 0.5248%, which is identical to raw VaR, producing a reduction of 0 percentage points or 0 cents. This confirms that the 5th-percentile VaR threshold sits well inside the winsorization bounds, so the extreme outliers at the 0.1% level are too far out to affect the 5th percentile. Winsorization is useful as a diagnostic tool but does not resolve the sub-additivity failure. CVaR remains the preferred primary measure.

Section 8: Item 5 — Are the Differences Statistically Significant?

Guide Question 5 · 20 Points

Are the differences between value at risk for various time periods significant? (20, 40, 60 days vs historical differences from 2019?

Discuss if it is appropriate to use only the data from past 20/40/60 days Value at Risk rather than all the way from 2019.

full_pct <- df_returns$dod_pct

item5_tests <- bind_rows(lapply(windows_returns[-1], function(w) {
  ks     <- ks.test(full_pct, w$dod_pct)
  levene <- levene_test_manual(full_pct, w$dod_pct)
  data.frame(
    n          = nrow(w),
    KS_Stat    = ks$statistic,
    KS_p       = ks$p.value,
    Levene_F   = abs(levene$statistic),
    Levene_p   = levene$p_value
  )
}), .id = "Window") %>%
  mutate(Window = factor(Window, levels = c("w60", "w40", "w20"))) %>%
  arrange(Window)

item5_desc <- bind_rows(lapply(windows_returns, function(w) {
  data.frame(n       = nrow(w),
             Mean    = mean(w$dod_pct),
             SD      = sd(w$dod_pct),
             Skew    = skewness(w$dod_pct),
             Kurt    = kurtosis(w$dod_pct))
}), .id = "Window") %>%
  mutate(Window = factor(Window, levels = window_order)) %>%
  arrange(Window)

item5_comp <- item1_var %>%
  mutate(Diff_from_Full_pp = VaR_Pct - VaR_Pct[Window == "Full"]) %>%
  arrange(Window)

kable(item5_tests, digits = 4,
      caption = "Item 5: Significance Tests (each window vs. full history). KS = distribution equality; Levene = variance equality.")
Item 5: Significance Tests (each window vs. full history). KS = distribution equality; Levene = variance equality.
Window n KS_Stat KS_p Levene_F Levene_p
D…1 w60 60 0.0971 0.6453 0.7324 0.4640
D…2 w40 40 0.1099 0.7327 0.5694 0.5691
D…3 w20 20 0.2066 0.3673 0.0647 0.9484
kable(item5_desc, digits = 4,
      caption = "Item 5: Descriptive Statistics by Window")
Item 5: Descriptive Statistics by Window
Window n Mean SD Skew Kurt
Full 1735 0.0060 0.3361 0.0457 2.1106
w60 60 -0.0332 0.2878 -0.4345 0.9081
w40 40 -0.0479 0.2894 -0.8464 0.9938
w20 20 -0.1092 0.3148 -0.9053 0.6524
kable(item5_comp, digits = 4,
      caption = "Item 5: VaR Comparison — difference from full-history baseline (percentage points)")
Item 5: VaR Comparison — difference from full-history baseline (percentage points)
Window n VaR_Pct VaR_Cents Diff_from_Full_pp
5%…1 Full 1735 0.5248 28.620 0.0000
5%…2 w60 60 0.4782 28.200 -0.0466
5%…3 w40 40 0.4803 28.325 -0.0445
5%…4 w20 20 0.5005 29.475 -0.0243
# Bar chart comparing VaR 95% magnitude across all four analysis windows
ggplot(item1_var, aes(x = Window, y = VaR_Pct,
                      fill = Window == "Full")) +
  geom_col(width = 0.55) +
  scale_fill_manual(values = c("TRUE" = econ_blue, "FALSE" = econ_grey)) +
  geom_text(aes(label = paste0(round(VaR_Pct, 3), "%")),
            vjust = 1.6, color = "white", fontface = "bold", size = 4) +
  labs(title = "Stability in numbers",
       subtitle = "VaR 95% loss magnitude (%) by analysis window. Blue = full-history baseline (2019–2026).",
       x = NULL, y = "VaR Magnitude (%)",
       caption = paste0("Windows: Full = ", format(nrow(df_returns), big.mark=","), 
                        " obs; w60 = 60 obs; w40 = 40 obs; w20 = 20 obs.")) +
  theme_economist_quant() + theme(legend.position = "none")

# Overlaid return density curves for the full history and three short windows
dist_data <- bind_rows(
  mutate(windows_returns$Full, Window = "Full (2019–2026)"),
  mutate(windows_returns$w60,  Window = "60-day"),
  mutate(windows_returns$w40,  Window = "40-day"),
  mutate(windows_returns$w20,  Window = "20-day")
) %>%
  mutate(Window = factor(Window,
                         levels = c("Full (2019–2026)", "60-day", "40-day", "20-day")))

win_colors <- c("Full (2019–2026)" = econ_blue,
                "60-day"           = econ_gold,
                "40-day"           = econ_grey,
                "20-day"           = econ_red)

ggplot(dist_data, aes(x = dod_pct, color = Window, fill = Window)) +
  geom_density(alpha = 0.12, linewidth = 0.7) +
  scale_color_manual(values = win_colors) +
  scale_fill_manual(values  = win_colors) +
  coord_cartesian(xlim = c(-2.5, 2.5)) +
  labs(title = "Same currency, different stories",
       subtitle = "Return distribution by analysis window. Short windows capture the current regime — not the full risk picture.",
       x = "Daily Close-to-Close Change (%)", y = "Density",
       caption = "KS and Levene tests assess whether differences from the full history are statistically significant.") +
  theme_economist_quant()

# Rolling 60-day VaR time series with full-history baseline as a reference line
roll_n <- 60
roll_idx <- roll_n:nrow(df_returns)

roll_var_pct <- sapply(roll_idx, function(i) {
  win <- df_returns$dod_pct[(i - roll_n + 1):i]
  abs(quantile(win, 0.05, type = 7))
})

roll_df <- data.frame(
  Date    = df_returns$Date[roll_idx],
  RollVaR = roll_var_pct
)

# Mark where the current 60-day analysis window begins
current_w60_start <- min(windows_returns$w60$Date)

ggplot(roll_df, aes(x = Date, y = RollVaR)) +
  geom_line(color = econ_blue, linewidth = 0.6) +
  geom_hline(yintercept = item1_var$VaR_Pct[item1_var$Window == "Full"],
             linetype = "dashed", color = econ_grey, linewidth = 0.6) +
  annotate("text", x = as.Date("2019-06-01"),
           y = item1_var$VaR_Pct[item1_var$Window == "Full"] + 0.02,
           label = paste0("Full-history VaR: ",
                          round(item1_var$VaR_Pct[item1_var$Window == "Full"], 3), "%"),
           size = 3, color = econ_grey, hjust = 0) +
  geom_vline(xintercept = current_w60_start, color = econ_red,
             linetype = "dotted", linewidth = 0.7) +
  annotate("text", x = current_w60_start, y = max(roll_var_pct) * 0.97,
           label = "← 60-day window starts here",
           size = 3, color = econ_red, hjust = -0.05) +
  labs(title = "VaR is not static",
       subtitle = "Rolling 60-day VaR 95% (%) through time. Flat dashed line = full-history VaR benchmark.",
       x = NULL, y = "Rolling VaR 95% (%)",
       caption = "Demonstrates regime-dependence: short-window VaR reflects the current low-volatility period, not the full risk spectrum.") +
  theme_economist_quant()

Response

Q: Are the differences between VaR for various time periods significant, comparing 20, 40, and 60 days versus the historical from 2019? The VaR figures for the 20-day, 40-day, and 60-day windows are economically distinct from the full 2019 to 2026 history, but this difference is not statistically significant. The economic dimension is clear because short windows understate tail risk. The statistical tests, however, fail to detect it. The results are shown in Table “Item 5: Significance Tests (each window vs. full history)”. We ran two formal tests. The Kolmogorov-Smirnov test evaluates distributional equality between each window and the full history. The KS statistic and p-value for the 60-day window versus the full history are 0.0971 and 0.6453. The 40-day window yields 0.1099 and 0.7327. The 20-day window yields 0.2066 and 0.3673. The Levene test evaluates variance equality. The Levene F-statistic and p-value for the 60-day window are 0.7324 and 0.464. The 40-day window yields 0.5694 and 0.5691. The 20-day window yields 0.0647 and 0.9484. All p-values are high. We fail to reject the null hypothesis in every case. Statistically, the short windows appear indistinguishable from the full history. This is a false negative. The KS test evaluates the entire cumulative distribution function, not just the tails, and is notoriously underpowered for detecting tail-specific differences in small samples of 20 to 60 observations. The Levene test faces the same limitation because it tests overall variance equality, but the critical difference lies in tail behavior, not overall dispersion.

The economic dimension is more consequential. The full 2019 to 2026 history contains four structurally distinct regimes. The first is a stable pre-crisis period through early 2020. The second is the 2020 COVID shock, which produced the most extreme daily USD/PHP swings in the dataset as global liquidity contracted. The third is the 2021 to 2022 PHP depreciation cycle, in which the peso breached PHP 59 per USD on sustained Fed rate hikes and commodity import pressure. The fourth is the 2023 to 2026 stabilization as the Fed paused. A 20-day or 60-day window captures only the fourth regime. The first three are excluded by construction. This is recency bias. The model reports safety because conditions are currently safe. It has no memory of when they were not. The descriptive statistics in Table “Item 5: Descriptive Statistics by Window” confirm this. Short windows show compressed standard deviations ranging from 0.2878 to 0.3148 versus 0.3361 for the full history, and negative skew ranging from -0.91 to -0.43, indicating recent returns have been drifting lower with reduced dispersion.

Q: Is it appropriate to use only the data from past 20, 40, or 60 days VaR rather than all the way from 2019? No, it is not appropriate. Using only short-window VaR as a standalone estimate is inadequate for standing risk limits. The maximum deviation of any window from the full-history VaR baseline is 0.0466 percentage points, as shown in Table “Item 5: VaR Comparison, difference from full-history baseline (percentage points)”. The bar chart in Figure “Stability in numbers” shows the VaR magnitude across all four windows, with the full-history baseline highlighted in blue. The distribution overlay in Figure “Same currency, different stories” shows how the short-window density curves are narrower and shifted relative to the full history. The rolling VaR chart in Figure “VaR is not static” is the most diagnostic visual. The 60-day rolling VaR behaves pro-cyclically. It spiked above 1.0% during the 2020 COVID shock, rose again during the 2022 depreciation, and has since collapsed to its current level below 0.50%. It expands during crises, which is when you already know you are in trouble, and contracts during calm periods, which is precisely when a robust risk measure should be warning you that the next shock is coming. This is the opposite of what a risk anchor should do. The full-history VaR is the appropriate baseline for standing risk limits because it prices in the full spectrum of tail events. Short-window figures are supplementary indicators of current conditions, not replacements.

Section 9: Item 6 — Portfolio Loss and Hedge Sizing (99% Confidence)

Portfolio: USD 10,000,000 cash as of 30 June 2024. VaR computed at 99% confidence (1st percentile of full DoD history); FX rate = BAP closing rate on the last trading day on or before that date.

Guide Question 6 · 20 Points

If a portfolio as of 30 June 2024 includes USD cash of 10,000,000.00, at the 99% level of confidence, how much can it possibly lose from a single day?

If we want to reduce the potential loss to PHP 20,000, would it be appropriate to reduce the amount of USD holdings? If so, what asset class should we move the amount to? Hint: recall coherent risk measures specifically translation invariance.

Will the reduction in USD holdings also potentially reduce the possible upside?

# Reference: last trading day on or before 30 June 2024
ref_row      <- df %>% filter(Date <= as.Date("2024-06-30")) %>% slice_tail(n = 1)
fx_rate      <- ref_row$Close
ref_date_used <- ref_row$Date

# VaR and upside at 99% (1st and 99th percentile of full DoD return series)
var_99_pct    <- abs(quantile(df_returns$dod_pct, 0.01, type = 7))
upside_99_pct <-    quantile(df_returns$dod_pct, 0.99, type = 7)

# Position calculations
pos_usd           <- 10000000
loss_usd          <- (var_99_pct / 100) * pos_usd
loss_php          <- loss_usd * fx_rate

target_php        <- 20000
reduced_pos_usd   <- target_php / ((var_99_pct / 100) * fx_rate)
usd_to_remove     <- pos_usd - reduced_pos_usd

upside_full_php     <- (upside_99_pct / 100) * pos_usd * fx_rate
upside_reduced_php  <- (upside_99_pct / 100) * reduced_pos_usd * fx_rate
upside_sacrificed   <- upside_full_php - upside_reduced_php

item6_table <- data.frame(
  Parameter = c(
    "Reference Date Used", "FX Rate at Reference (PHP/USD)",
    "Full-History VaR 99% (%)", "VaR 99% (cents)",
    "USD Position", "1-Day Loss @ 99% (USD)", "1-Day Loss @ 99% (PHP)",
    "Target Maximum 1-Day Loss (PHP)", "Required USD Position",
    "USD to Redeploy", "99th Percentile Upside (%)",
    "Upside on Full Position (PHP)", "Upside on Reduced Position (PHP)",
    "Upside Sacrificed (PHP)"
  ),
  Value = c(
    as.character(ref_date_used),
    round(fx_rate, 4),
    round(var_99_pct, 4),
    round(abs(quantile(df_returns$dod_cents, 0.01, type = 7)), 4),
    format(pos_usd, big.mark = ","),
    format(round(loss_usd, 2), big.mark = ","),
    format(round(loss_php, 2), big.mark = ","),
    format(target_php, big.mark = ","),
    format(round(reduced_pos_usd, 2), big.mark = ","),
    format(round(usd_to_remove, 2), big.mark = ","),
    round(upside_99_pct, 4),
    format(round(upside_full_php, 2), big.mark = ","),
    format(round(upside_reduced_php, 2), big.mark = ","),
    format(round(upside_sacrificed, 2), big.mark = ",")
  )
)
kable(item6_table, caption = "Item 6: Portfolio Loss and Hedge Sizing (FX Rate as of 30 Jun 2024, 99% Confidence)")
Item 6: Portfolio Loss and Hedge Sizing (FX Rate as of 30 Jun 2024, 99% Confidence)
Parameter Value
Reference Date Used 2024-06-28
FX Rate at Reference (PHP/USD) 58.61
Full-History VaR 99% (%) 0.7974
VaR 99% (cents) 44.764
USD Position 1e+07
1-Day Loss @ 99% (USD) 79,739.69
1-Day Loss @ 99% (PHP) 4,673,543
Target Maximum 1-Day Loss (PHP) 20,000
Required USD Position 42,794.08
USD to Redeploy 9,957,206
99th Percentile Upside (%) 0.9155
Upside on Full Position (PHP) 5,365,528
Upside on Reduced Position (PHP) 22,961.29
Upside Sacrificed (PHP) 5,342,567
# Diverging bar chart showing 1-day loss vs upside by position size
# Uses raw PHP values (not scaled to millions) with free_y facets so both
# the full-exposure bars and the PHP 20K capped bars are visible on screen.
port_viz <- data.frame(
  Category = factor(
    c("Full Exposure\n(USD 10M)", "Capped Loss\n(PHP 20K target)"),
    levels = c("Full Exposure\n(USD 10M)", "Capped Loss\n(PHP 20K target)")
  ),
  Loss   = c(loss_php, target_php),
  Upside = c(upside_full_php, upside_reduced_php)
) %>%
  pivot_longer(cols = c(Loss, Upside), names_to = "Direction", values_to = "Value") %>%
  mutate(
    SignedValue = ifelse(Direction == "Loss", -Value, Value),
    Direction   = factor(Direction, levels = c("Upside", "Loss"))
  )

ggplot(port_viz, aes(x = Direction, y = SignedValue, fill = Direction)) +
  geom_col(width = 0.5, position = "identity") +
  geom_hline(yintercept = 0, color = "#333333", linewidth = 0.4) +
  scale_fill_manual(values = c("Loss" = econ_red, "Upside" = econ_blue),
                    labels = c("1-Day Loss (99%)", "99th-Pct Upside")) +
  scale_y_continuous(labels = scales::label_comma(prefix = "PHP ")) +
  facet_wrap(~ Category, scales = "free_y") +
  labs(title = "The price of safety",
       subtitle = "Potential 1-day loss (down) vs. upside potential (up) in PHP. Each panel uses its own scale. Reference: FX rate as of 30 Jun 2024.",
       x = NULL, y = "PHP",
       caption = paste0("To cap daily loss at PHP 20,000, USD position must be reduced to USD ",
                        format(round(reduced_pos_usd, 0), big.mark = ","),
                        ". This sacrifices PHP ",
                        format(round(upside_sacrificed / 1e6, 2), big.mark = ","), "M in 99th-pct upside.")) +
  theme_economist_quant()

Response

Q: If a portfolio as of 30 June 2024 includes USD cash of 10,000,000.00, at the 99% level of confidence, how much can it possibly lose from a single day? We compute the 99% VaR, which is the 1st percentile of the full DoD return distribution. The result is 0.7974%, equivalent to 44.76 PHP cents per USD. This is a more conservative threshold than the 95% VaR of 0.5248% used in Items 1 through 5. The jump from 95% to 99% represents a 52% increase in the risk figure, reflecting the fat-tailed nature of the distribution. The FX rate used is the BAP closing rate on the last trading day on or before 30 June 2024, which is 2024-06-28 at PHP 58.61 per USD. All calculations are shown in Table “Item 6: Portfolio Loss and Hedge Sizing (FX Rate as of 30 Jun 2024, 99% Confidence)”.

The calculation proceeds as follows. The USD loss is 0.7974% multiplied by USD 10,000,000, which equals USD 79,739.69. The PHP equivalent is USD 79,739.69 multiplied by 58.61, which equals PHP 4,673,543. On 99 out of 100 trading days, the single-day loss will not exceed this figure. But on the remaining 1 day in 100, roughly once every five months, the actual loss will be worse. For a portfolio of this size, PHP 4.67 million is a material hit to the firm financial position.

Q: If we want to reduce the potential loss to PHP 20,000, would it be appropriate to reduce the amount of USD holdings, and what asset class should we move the amount to? Yes, it is appropriate. The mechanism is translation invariance, one of the four coherence axioms established by Artzner et al. (1999). Translation invariance states that adding a risk-free cash amount c in the base currency reduces the portfolio risk measure by exactly c. Formally, the risk of the portfolio minus c equals the risk of the portfolio minus c. In practical terms, if we move PHP-denominated assets into the portfolio, the FX risk declines by exactly that PHP amount.

To cap the daily loss at PHP 20,000, we solve for the USD position where the 99% VaR equals PHP 20,000. The required USD position is USD 42,794.08. The USD to be redeployed is USD 9,957,206, which is 99.6% of the original position. The appropriate asset class for the redeployed funds is PHP-denominated cash or Philippine government Treasury bills. These instruments carry zero FX exposure relative to the PHP base currency, eliminating all currency risk on the redeployed amount. This is the correct application of translation invariance because we are adding a risk-free asset in the base currency to mechanically reduce the portfolio FX risk by a deterministic amount. Moving PHP 583,591,839 into T-bills reduces FX VaR by exactly that PHP amount. The reduction is direct and deterministic.

Q: Will the reduction in USD holdings also potentially reduce the possible upside? Yes, because FX exposure is symmetric. The same position that generates downside risk when the peso strengthens generates upside gain when the peso weakens. The 99th-percentile upside of the full USD 10M position is 0.9155%, equivalent to PHP 5,365,528. After reducing the position to USD 42,794, the upside falls to PHP 22,961.29. The upside sacrificed is PHP 5,342,567, which is PHP 5.34 million. The diverging bar chart in Figure “The price of safety” makes this trade-off visually explicit. The left panel shows the full exposure with bars of PHP 4.67 million in loss and PHP 5.37 million in upside. The right panel shows the capped loss with bars of PHP 20 thousand in loss and PHP 23 thousand in upside. Each panel uses its own scale so that both the massive full-exposure bars and the tiny capped-loss bars are visible on screen. Risk reduction and return reduction are the same transaction. There is no free lunch.

Section 10: Summary Table

summary_table <- data.frame(
  Item = c(1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 3, 4, 4, 5, 5, 6, 6, 6),
  Description = c(
    "DoD VaR 95% — Full history",
    "DoD VaR 95% — 60-day window",
    "DoD VaR 95% — 40-day window",
    "DoD VaR 95% — 20-day window",
    "Outlier count (DoD, outside 0.1%/99.9%)",
    "Outlier dates (DoD)",
    "Intraday VaR 95% — Full history",
    "Intraday VaR 95% — 60-day window",
    "Intraday VaR 95% — 40-day window",
    "Intraday VaR 95% — 20-day window",
    "Outlier count (Intraday, above 99.9%)",
    "Outlier dates (Intraday)",
    "VaR gap (Intraday − DoD), full history",
    "CVaR / Expected Shortfall 95%, full",
    "VaR after Winsorization 95%",
    "KS test p-values (w60, w40, w20 vs Full)",
    "Max VaR deviation from full baseline",
    "1-day loss, USD 10M @ 99% (PHP)",
    "USD position to cap loss at PHP 20K",
    "99th-pct upside sacrificed (PHP)"
  ),
  Result = c(
    paste0(round(item1_var$VaR_Cents[item1_var$Window == "Full"], 2), " cts / ", round(item1_var$VaR_Pct[item1_var$Window == "Full"], 4), "%"),
    paste0(round(item1_var$VaR_Cents[item1_var$Window == "w60"], 2), " cts / ", round(item1_var$VaR_Pct[item1_var$Window == "w60"], 4), "%"),
    paste0(round(item1_var$VaR_Cents[item1_var$Window == "w40"], 2), " cts / ", round(item1_var$VaR_Pct[item1_var$Window == "w40"], 4), "%"),
    paste0(round(item1_var$VaR_Cents[item1_var$Window == "w20"], 2), " cts / ", round(item1_var$VaR_Pct[item1_var$Window == "w20"], 4), "%"),
    as.character(nrow(outliers_1)),
    paste(format(outliers_1$Date, "%d %b %Y"), collapse = "; "),
    paste0(round(item2_var$VaR_Cents[item2_var$Window == "Full"], 2), " cts / ", round(item2_var$VaR_Pct[item2_var$Window == "Full"], 4), "%"),
    paste0(round(item2_var$VaR_Cents[item2_var$Window == "w60"], 2), " cts / ", round(item2_var$VaR_Pct[item2_var$Window == "w60"], 4), "%"),
    paste0(round(item2_var$VaR_Cents[item2_var$Window == "w40"], 2), " cts / ", round(item2_var$VaR_Pct[item2_var$Window == "w40"], 4), "%"),
    paste0(round(item2_var$VaR_Cents[item2_var$Window == "w20"], 2), " cts / ", round(item2_var$VaR_Pct[item2_var$Window == "w20"], 4), "%"),
    as.character(nrow(outliers_2)),
    paste(format(outliers_2$Date, "%d %b %Y"), collapse = "; "),
    paste0(round(gap_table$Gap_Cents[gap_table$Window == "Full"], 2), " cts"),
    paste0(round(cvar_cents, 2), " cts / ", round(cvar_pct, 4), "%"),
    paste0(round(var_winsor_pct, 4), "% (reduction: ", round(raw_var_pct - var_winsor_pct, 4), " pp)"),
    paste(round(item5_tests$KS_p, 4), collapse = ", "),
    paste0(round(max(abs(item5_comp$Diff_from_Full_pp)), 4), " pp"),
    paste0("PHP ", format(round(loss_php, 2), big.mark = ",")),
    paste0("USD ", format(round(reduced_pos_usd, 2), big.mark = ",")),
    paste0("PHP ", format(round(upside_sacrificed, 2), big.mark = ","))
  )
)
kable(summary_table, caption = "Final Summary Table — All Items")
Final Summary Table — All Items
Item Description Result
1 DoD VaR 95% — Full history 28.62 cts / 0.5248%
1 DoD VaR 95% — 60-day window 28.2 cts / 0.4782%
1 DoD VaR 95% — 40-day window 28.33 cts / 0.4803%
1 DoD VaR 95% — 20-day window 29.48 cts / 0.5005%
1 Outlier count (DoD, outside 0.1%/99.9%) 4
1 Outlier dates (DoD) 11 Nov 2022; 27 Dec 2022; 06 Feb 2023; 02 Jan 2025
2 Intraday VaR 95% — Full history 40 cts / 0.7302%
2 Intraday VaR 95% — 60-day window 29.4 cts / 0.4991%
2 Intraday VaR 95% — 40-day window 29.4 cts / 0.4991%
2 Intraday VaR 95% — 20-day window 37.49 cts / 0.6528%
2 Outlier count (Intraday, above 99.9%) 2
2 Outlier dates (Intraday) 04 Sep 2019; 12 Sep 2024
3 VaR gap (Intraday − DoD), full history 11.38 cts
4 CVaR / Expected Shortfall 95%, full 40.68 cts / 0.7301%
4 VaR after Winsorization 95% 0.5248% (reduction: 0 pp)
5 KS test p-values (w60, w40, w20 vs Full) 0.6453, 0.7327, 0.3673
5 Max VaR deviation from full baseline 0.0466 pp
6 1-day loss, USD 10M @ 99% (PHP) PHP 4,673,543
6 USD position to cap loss at PHP 20K USD 42,794.08
6 99th-pct upside sacrificed (PHP) PHP 5,342,567

Section 10b: Export Tables as LaTeX

Section 11: Synthesis

Quantitative analysis of USD/PHP BAP data (02 Jan 2019 to 27 Feb 2026). Full analysis, evidence, and quantified figures are in the respective item sections above.

References

Artzner, Philippe & Delbaen, Freddy & Jean-Marc, Eber & Heath, David. (1999). Coherent Measures of Risk. Mathematical Finance. 9. 203 - 228. 10.1111/1467-9965.00068.