Modelling Exchange Rate Volatility in Namibia

Comparative Evidence from NAD/USD, NAD/EUR and NAD/GBP Using GARCH Models

Author

Jo-Brown Tjatindi

Published

August 10, 2026

1 Introduction

1.1 Background

Exchange rate movements affect import costs, export competitiveness, inflation, investment decisions and financial risk. Because the Namibia dollar is pegged to the South African rand, external movements in the rand are transmitted directly to the Namibia dollar.

This study compares volatility in three external exchange rates:

  • NAD/USD
  • NAD/EUR
  • NAD/GBP

The exchange rates are expressed as Namibia dollars per unit of foreign currency. Therefore, an increase represents depreciation of the Namibia dollar, while a decrease represents appreciation.

1.2 Problem Statement

Exchange rates often display periods of calm followed by periods of intense movement. This phenomenon, known as volatility clustering, cannot be adequately represented by models that assume constant variance.

GARCH-family models allow conditional variance to change over time and are therefore suitable for measuring and forecasting exchange-rate risk.

1.3 Main Objective

To model, compare and forecast the volatility of NAD/USD, NAD/EUR and NAD/GBP exchange rates using GARCH family models.

1.4 Specific Objectives

  1. Examine the historical behaviour of the three exchange rates.
  2. Calculate and compare exchange rate returns.
  3. Test for volatility clustering and ARCH effects.
  4. Estimate symmetric and asymmetric GARCH models.
  5. Compare model performance using information criteria and residual diagnostics.
  6. measure volatility persistence and shock half-life.
  7. Produce short-term volatility forecasts.

1.5 Questions

  1. Do the three exchange rates exhibit volatility clustering?
  2. Which exchange rate is most volatile?
  3. How persistent are exchange-rate shocks?
  4. Do positive and negative exchange-rate shocks have asymmetric effects?
  5. Which GARCH specification provides the best fit?
  6. How accurately can future volatility be forecast?

2 Data and Methodology

2.1 Data Description

The study uses daily exchange-rate observations for:

  • NAD/USD
  • NAD/EUR
  • NAD/GBP

The raw dataset should contain:

Variable Description
Date Trading date
NAD_USD Namibia dollars per US dollar
NAD_EUR Namibia dollars per euro
NAD_GBP Namibia dollars per British pound

2.2 Return Calculation

Exchange rate returns are calculated as:

[ r_t=100]

where (P_t) is the exchange rate at time (t).

A positive return means the foreign currency became more expensive in Namibia dollar terms.

2.3 Models

The following models will be compared:

  1. Standard GARCH(1,1)Generalized Autoregressive Conditional Heteroskedasticity

  2. EGARCH(1,1)Exponential Generalized Autoregressive Conditional Heteroskedasticity

  3. GJR‑GARCH(1,1)Glosten–Jagannathan–Runkle Generalized Autoregressive Conditional Heteroskedasticity

Student-(t) innovations will be considered because financial returns often exhibit heavy tails.

3 Setup

Code
library(readxl)
library(dplyr)
library(tidyr)
library(tibble)
library(lubridate)
library(ggplot2)
library(scales)
library(e1071)
library(tseries)
library(strucchange)
library(FinTS)
library(reshape2)
library(rugarch)
library(knitr)

options(scipen=999)

4 Data Import and Preparation

4.1 Objective

Prepare and validate the exchange rate dataset.

4.2 Questions

  • Are all dates properly formatted?
  • Are observations missing?
  • Are the three exchange rates aligned on the same dates?
Code
setwd("C:/Users/tjati/OneDrive/Documents/R programming")
exchange_data<-read_excel("Exchange_rates.xlsx")

exchange_data<-exchange_data%>%
mutate(Date=as.Date(Date))%>%
arrange(Date)


report <- data.frame(
  Variable = names(exchange_data),
  Class    = sapply(exchange_data, class),
  NA_Count = colSums(is.na(exchange_data)))

top_data <- head(exchange_data)

knitr::kable(
report,
caption="Data Structure")
Data Structure
Variable Class NA_Count
Date Date Date 0
NAD_USD NAD_USD numeric 0
NAD_EUR NAD_EUR numeric 0
NAD_GBP NAD_GBP numeric 0
Code
knitr::kable(
top_data,
caption="The dataset")
The dataset
Date NAD_USD NAD_EUR NAD_GBP
2010-01-01 7.3903 10.5862 11.9360
2010-01-04 7.2855 10.5007 11.7190
2010-01-05 7.3172 10.5117 11.7017
2010-01-06 7.3280 10.5585 11.7382
2010-01-07 7.4318 10.6178 11.8418
2010-01-08 7.3539 10.5974 11.7835

4.3 Calculated Log Returns

Code
exchange_returns<-exchange_data%>%
mutate(
USD_Return=100*(log(NAD_USD)-lag(log(NAD_USD))),
EUR_Return=100*(log(NAD_EUR)-lag(log(NAD_EUR))),
GBP_Return=100*(log(NAD_GBP)-lag(log(NAD_GBP))))%>%
filter(if_all(c(USD_Return,EUR_Return,GBP_Return),~!is.na(.x)))

head1 <- head(exchange_returns)
knitr::kable(
head1,
caption="Exchange Returns")
Exchange Returns
Date NAD_USD NAD_EUR NAD_GBP USD_Return EUR_Return GBP_Return
2010-01-04 7.2855 10.5007 11.7190 -1.4282258 -0.8109345 -1.8347587
2010-01-05 7.3172 10.5117 11.7017 0.4341670 0.1047001 -0.1477326
2010-01-06 7.3280 10.5585 11.7382 0.1474886 0.4442300 0.3114350
2010-01-07 7.4318 10.6178 11.8418 1.4065463 0.5600615 0.8787164
2010-01-08 7.3539 10.5974 11.7835 -1.0537306 -0.1923150 -0.4935397
2010-01-11 7.3768 10.7066 11.8875 0.3109155 1.0251686 0.8787180

5 Descriptive Analysis

5.1 Descriptive Statistics

5.1.1 Objective

Compare the statistical properties of the three return series.

5.1.2 Questions

  • Which currency has the highest average volatility?
  • Are the returns skewed?
  • Do the returns exhibit excess kurtosis?
Code
descriptive_table <- function(df, digits = 2) {
  num_df <- df[, sapply(df, is.numeric), drop = FALSE]
  describe_col <- function(x) {
    x_clean <- x[!is.na(x)]
    if (length(x_clean) < 3) return(rep(NA, 15))
    q <- quantile(x_clean, probs = c(0.25, 0.5, 0.75))
    c(Count = length(x_clean),
      Mean = round(mean(x_clean), digits),
      Median = round(median(x_clean), digits),
      Std_Dev = round(sd(x_clean), digits),
      Variance = round(var(x_clean), digits),
      Minimum = round(min(x_clean), digits),
      `25%` = round(q[1], digits),
      `50%` = round(q[2], digits),
      `75%` = round(q[3], digits),
      Maximum = round(max(x_clean), digits),
      Range = round(max(x_clean) - min(x_clean), digits),
      Missing = sum(is.na(x)),
      IQR = round(IQR(x_clean), digits),
      CV_Percent = round((sd(x_clean) / mean(x_clean)) * 100, digits),
      Skewness = round(e1071::skewness(x_clean, type = 2), digits),
      Kurtosis = round(e1071::kurtosis(x_clean, type = 2), digits))
  }
  as.data.frame(t(sapply(num_df, describe_col)), row.names = names(num_df))
}


exchange_data1 <- exchange_returns %>%
  mutate(Year = year(Date),
         Month = month(Date))


filtered_data <- exchange_data1 %>%
  filter(Year == 2026, Month == 5) # Filter for the year and month you want

summarised_table <- descriptive_table(filtered_data) %>%
  rownames_to_column("Variable") %>%
  mutate(Year = 2026, Month = 5)

knitr::kable(
summarised_table,
caption="Summary Statistics")
Summary Statistics
Variable Count Mean Median Std_Dev Variance Minimum 25%.25% 50%.50% 75%.75% Maximum Range Missing IQR CV_Percent Skewness Kurtosis Year Month
NAD_USD 20 16.47 16.45 0.16 0.02 16.23 16.38 16.45 16.54 16.81 0.58 0 0.16 0.94 0.51 -0.15 2026 5
NAD_EUR 20 19.23 19.26 0.19 0.04 18.91 19.12 19.26 19.36 19.65 0.74 0 0.24 1.00 0.09 -0.23 2026 5
NAD_GBP 20 22.21 22.22 0.23 0.05 21.82 22.07 22.22 22.35 22.74 0.92 0 0.27 1.02 0.38 0.47 2026 5
USD_Return 20 -0.15 -0.03 0.75 0.56 -1.61 -0.70 -0.03 0.44 1.28 2.89 0 1.14 -504.44 -0.22 -0.55 2026 5
EUR_Return 20 -0.17 0.02 0.56 0.32 -1.17 -0.67 0.02 0.18 0.85 2.02 0 0.85 -321.97 -0.43 -0.73 2026 5
GBP_Return 20 -0.19 -0.10 0.52 0.27 -1.31 -0.58 -0.10 0.15 0.71 2.02 0 0.72 -271.89 -0.46 -0.26 2026 5
Year 20 2026.00 2026.00 0.00 0.00 2026.00 2026.00 2026.00 2026.00 2026.00 0.00 0 0.00 0.00 NaN NaN 2026 5
Month 20 5.00 5.00 0.00 0.00 5.00 5.00 5.00 5.00 5.00 0.00 0 0.00 0.00 NaN NaN 2026 5

5.1.3 Descriptive Statistics (May 2026)

  • The Namibian Dollar exchange rate averaged N$16.47/USD, N$19.23/EUR, and N$22.21/GBP, with coefficients of variation below 1.1%, indicating a highly stable foreign exchange market.

  • The British Pound recorded the highest average exchange rate, followed by the Euro and the US Dollar, reflecting their relative strength against the Namibian Dollar during the month.

  • Daily exchange rate returns averaged -0.15% (USD), -0.17% (EUR), and -0.19% (GBP), suggesting a slight overall appreciation of the Namibian Dollar.

  • The US Dollar exhibited the highest return volatility, while the Euro and British Pound experienced comparatively smaller day to day fluctuations.

  • Skewness and kurtosis values were close to zero for both exchange rates and returns, indicating approximately symmetric distributions with no evidence of extreme exchange rate movements.

  • No missing observations were recorded, confirming that the dataset is complete and suitable for further econometric and financial analysis.

Overall, the descriptive statistics indicate a stable foreign exchange market in May 2026, characterised by low exchange rate volatility, modest daily return fluctuations, and consistent movements across the three major currencies.

5.2 Exchange-Rate Levels

5.2.1 Objective

Examine long-term movements in the three exchange rates.

5.2.2 Questions

  • Which foreign currency is most expensive in Namibia dollar terms?
  • When did major appreciation or depreciation episodes occur?
Code
exchange_levels_long<-exchange_data%>%
pivot_longer(
cols=c(NAD_USD,NAD_EUR,NAD_GBP),
names_to="Currency",
values_to="Exchange_Rate")

ggplot(exchange_levels_long,aes(Date,Exchange_Rate,colour=Currency))+
geom_line(linewidth=.7)+
facet_wrap(~Currency,ncol=1,scales="free_y")+
scale_color_manual(values = 
                     c("NAD_EUR" = "#296960",
                       "NAD_GBP" = "#B22222",
                       "NAD_USD" = "#EAB200")) +
labs(
     title="External Exchange Rates of the Namibia Dollar",
     subtitle="Namibia dollars per unit of foreign currency",
     x=NULL,
     y="Exchange rate",
     colour=NULL,
     caption="Source: Cirrus")+
  theme_minimal(base_size = 11) +
  theme(
    plot.title = element_text(face = "bold", size = 14, hjust = 0.5),
    plot.subtitle = element_text(size = 11, hjust = 0.5, margin = margin(b = 10)),
    axis.title.x = element_blank(),
    axis.title.y = element_text(face = "bold", size = 9),
    axis.text.x = element_text(hjust = 1, size = 9),
    axis.text.y = element_text(hjust = 1, size = 9),
    legend.position = "none",
    legend.text = element_text(size = 9),
    legend.title = element_blank(),
    panel.background = element_rect(fill = "grey96", color = "grey70", linewidth = 0.6),
    panel.grid.major.y = element_line(color = "white", linewidth = 0.7),
    panel.grid.minor.y = element_line(color = "white", linewidth = 0.05),
    panel.grid.major.x = element_blank(),
    panel.grid.minor = element_blank())

5.2.3 Exchange Rate Trend

  • The Namibian Dollar depreciated against the Euro, British Pound, and US Dollar over the long term, as all three exchange rate series exhibit an upward trend.

  • The British Pound consistently recorded the highest exchange rate, followed by the Euro and the US Dollar, reflecting the relative strength of these currencies throughout the periods.

  • A sharp depreciation of the Namibian Dollar occurred between 2015 and 2016, particularly against the British Pound and US Dollar, suggesting strict exchange rate pressures during this period.

  • The exchange rates declined between 2017 and 2019, indicating a temporary appreciation or recovery of the Namibian Dollar before resuming an upward trend.

  • During 2020-2021, exchange rates experienced another noticeable increase, reflecting increased volatility associated with the COVID-19 pandemic and global financial uncertainty.

  • From 2022 onwards, exchange rates remained relatively high with moderate fluctuations, suggesting that the Namibian Dollar stabilised at a weaker level against the three major currencies.

Overall, the figure indicates a long-term depreciation of the Namibian Dollar, interrupted by short periods of appreciation, with exchange rates becoming relatively stable at elevated levels in recent years.

5.3 Exchange-Rate Returns

5.3.1 Objective

Examine short-run exchange rate movements.

5.3.2 Questions

  • Are returns centred around zero?
  • Are there clusters of unusually large movements?
Code
returns_long<-exchange_returns%>%
dplyr::select(Date,USD_Return,EUR_Return,GBP_Return)%>%
pivot_longer(
cols=-Date,
names_to="Currency",
values_to="Return")

ggplot(returns_long,aes(Date,Return,colour=Currency))+
geom_line(linewidth=.45)+
geom_hline(yintercept=0,linetype="dashed",colour="grey50")+
facet_wrap(~Currency,ncol=1,scales="free_y")+
scale_color_manual(values = 
                     c("EUR_Return" = "#296960",
                       "GBP_Return" = "#B22222",
                       "USD_Return" = "#EAB200")) +
labs(
title="Daily Exchange Rate Returns",
subtitle="Log returns expressed as percentages",
x=NULL,
y="Return (%)",
colour=NULL,
caption="Source: Author's calculations")+
theme_minimal(base_size = 11) +
theme(
    plot.title = element_text(face = "bold", size = 14, hjust = 0.5),
    plot.subtitle = element_text(size = 11, hjust = 0.5, margin = margin(b = 10)),
    axis.title.x = element_blank(),
    axis.title.y = element_text(face = "bold", size = 9),
    axis.text.x = element_text(hjust = 1, size = 9),
    axis.text.y = element_text(hjust = 1, size = 9),
    legend.position = "none",
    legend.text = element_text(size = 9),
    legend.title = element_blank(),
    panel.background = element_rect(fill = "grey96", color = "grey70", linewidth = 0.6),
    panel.grid.major.y = element_line(color = "white", linewidth = 0.7),
    panel.grid.minor.y = element_line(color = "white", linewidth = 0.05),
    panel.grid.major.x = element_blank(),
    panel.grid.minor = element_blank())

5.3.3 Daily Exchange Rate Returns

  • Daily exchange rate returns fluctuated around zero for the Euro, British Pound, and US Dollar, indicating that positive and negative movements largely offset each other over time.

  • Most daily returns were relatively small, suggesting that exchange rate changes were generally small under normal market conditions.

  • Periods of increased volatility are evident between 2015 and 2021, where larger positive and negative return spikes occurred, reflecting increased uncertainty in the foreign exchange market.

  • The British Pound exhibited the largest return swings, followed by the US Dollar, while the **Euro displayed comparatively lower day to day volatility.

  • Despite occasional sharp fluctuations, volatility clustered during specific periods rather than remaining persistently high, indicating that episodes of market uncertainty were temporary.

  • After 2022, return volatility appears to be moderate, with fewer extreme movements and returns becoming more concentrated around zero, suggesting improved exchange rate stability.

Overall, the return series exhibit the typical characteristics oftime series, with mean reverting behaviour around zero, volatility clustering, and occasional large shocks associated with periods of economic and financial uncertainty.

5.4 Rolling Volatility

The 30‑day rolling standard deviation of daily returns measures how volatile a financial asset’s price has been over the most recent 30 trading days. It’s calculated by taking the standard deviation of daily percentage returns within a moving 30‑day window, then updating that value each day as new data comes in and old data drops out. This rolling approach smooths short‑term noise and reveals evolving patterns of market stability, higher values mean greater variability in returns (more risk), while lower values indicate steadier price movements.

5.4.1 Objective

Compare how volatility changes through time.

5.4.2 Questions

  • Which currency experiences the largest volatility spikes?
  • Do volatility episodes occur at similar times?
Code
library(zoo)

rolling_volatility<-exchange_returns%>%
mutate(
USD_Volatility=rollapply(USD_Return,30,sd,fill=NA,align="right"),
EUR_Volatility=rollapply(EUR_Return,30,sd,fill=NA,align="right"),
GBP_Volatility=rollapply(GBP_Return,30,sd,fill=NA,align="right"))%>%
dplyr::select(Date,USD_Volatility,EUR_Volatility,GBP_Volatility)%>%
pivot_longer(
cols=-Date,
names_to="Currency",
values_to="Volatility")

ggplot(rolling_volatility,aes(Date,Volatility,colour=Currency))+
geom_line(linewidth=.65,na.rm=TRUE)+
facet_wrap(~Currency,ncol=1,scales="free_y")+
scale_color_manual(values = 
                     c("EUR_Volatility" = "#296960",
                       "GBP_Volatility" = "#B22222",
                       "USD_Volatility" = "#EAB200")) +
labs(
title="Thirty-Day Rolling Exchange Rate Volatility",
subtitle="Rolling standard deviation of daily returns",
x=NULL,
y="Volatility",
colour=NULL,
caption="Source: Author's calculations")+
theme_minimal(base_size = 11) +
theme(
    plot.title = element_text(face = "bold", size = 14, hjust = 0.5),
    plot.subtitle = element_text(size = 11, hjust = 0.5, margin = margin(b = 10)),
    axis.title.x = element_blank(),
    axis.title.y = element_text(face = "bold", size = 9),
    axis.text.x = element_text(hjust = 1, size = 9),
    axis.text.y = element_text(hjust = 1, size = 9),
    legend.position = "none",
    legend.text = element_text(size = 9),
    legend.title = element_blank(),
    panel.background = element_rect(fill = "grey96", color = "grey70", linewidth = 0.6),
    panel.grid.major.y = element_line(color = "white", linewidth = 0.7),
    panel.grid.minor.y = element_line(color = "white", linewidth = 0.05),
    panel.grid.major.x = element_blank(),
    panel.grid.minor = element_blank())

5.4.3 Thirty-Day Rolling Exchange Rate Volatility

  • Rolling volatility varied over time for the Euro, British Pound, and US Dollar, indicating that exchange rate risk was not constant but changed in response to market conditions.

  • The highest volatility occurred between 2015 and 2021, reflecting periods of increased uncertainty and larger exchange rate fluctuations.

  • The US Dollar recorded the highest volatility peaks, followed by the British Pound, while the Euro generally exhibited the lowest and most stable volatility throughout the sample period.

  • Volatility clustering is clearly evident, where periods of high volatility were followed by further high volatility before gradually returning to lower levels, a common characteristic of financial markets.

  • From 2022 onwards, volatility generally declined, although occasional spikes remained, indicating that exchange rate uncertainty reduced but did not disappear completely.

  • The three currencies displayed similar volatility patterns, suggesting that they were influenced by common domestic and international economic events.

Overall, the rolling volatility analysis indicates that exchange rate risk was highest during periods of global economic and financial uncertainty, while more recent years have been characterized by relatively lower and more stable exchange rate volatility.

5.5 Return Correlations

5.5.1 Objective

Measure co-movement across currencies.

5.5.2 Research Question

Do the three external exchange rates experience similar daily shocks?

Code
return_correlations<-cor(
exchange_returns[,c("USD_Return","EUR_Return","GBP_Return")],
use="complete.obs")

return1 <- round(return_correlations,3)

knitr::kable(
return1,
caption="Currency Returns Correlation")
Currency Returns Correlation
USD_Return EUR_Return GBP_Return
USD_Return 1.000 0.830 0.812
EUR_Return 0.830 1.000 0.851
GBP_Return 0.812 0.851 1.000
Code
return_melt <- melt(return1)


ggplot(return_melt, aes(x=Var1, y=Var2, fill=value)) +
  geom_tile() +
  geom_text(aes(label=value), color = "white", size = 3.5) +
  scale_fill_gradient2(low="#EAB200", high="#C04F15", mid = "white",
                       midpoint=0, limit=c(-1,1), space="Lab",
                       name="Correlation") +
  labs(title="Currency Returns Correlation Heatmap",
       x="", y="") +
theme_minimal(base_size = 11) +
theme(
    plot.title = element_text(face = "bold", size = 14, hjust = 0.5),
    plot.subtitle = element_text(size = 11, hjust = 0.5, margin = margin(b = 10)),
    axis.title.x = element_blank(),
    axis.title.y = element_text(face = "bold", size = 9),
    axis.text.x = element_text(hjust = 1, size = 9),
    axis.text.y = element_text(hjust = 1, size = 9),
    legend.position = "right",
    legend.text = element_text(size = 9),
    legend.title = element_blank(),
    panel.grid.major.y = element_line(color = "white", linewidth = 0.7),
    panel.grid.minor.y = element_line(color = "white", linewidth = 0.05),
    panel.grid.major.x = element_blank(),
    panel.grid.minor = element_blank())

5.5.3 Currency Returns Correlation

  • All three currency returns are strongly and positively correlated, indicating that movements in the US Dollar, Euro, and British Pound generally occurred in the same direction against the Namibian Dollar.
  • The strongest correlation is between the Euro and British Pound returns (0.851), suggesting that these two currencies exhibited the most similar exchange rate behaviour.
  • The US Dollar and Euro also displayed a strong positive correlation (0.830), indicating that shocks affecting one currency were often accompanied by similar movements in the other.
  • The US Dollar and British Pound recorded the lowest, but still strong, correlation (0.812), showing that although their movements were slightly less aligned, they remained highly interconnected.
  • The consistently high positive correlations (above 0.80) suggest that the three exchange rates were influenced by common regional and global economic factors rather than independent currency-specific events.
  • Overall, the correlation analysis indicates strong co-movement among the major currencies, implying limited diversification benefits when analysing or managing exchange rate risk using only these three currency pairs.

6 Preliminary Statistical Tests

6.1 Stationarity Tests

6.1.1 Objective

Confirm that exchange rate returns are stationary.

6.1.2 Questions

  • Are exchange rate levels non-stationary?
  • Are the return series stationary?
Code
stationarity_table<-bind_rows(
       data.frame(
                  Currency="USD",
                  ADF_Level=adf.test(exchange_data$NAD_USD)$p.value,
                  ADF_Return=adf.test(exchange_returns$USD_Return)$p.value),
       data.frame(
                  Currency="EUR",
                  ADF_Level=adf.test(exchange_data$NAD_EUR)$p.value,
                  ADF_Return=adf.test(exchange_returns$EUR_Return)$p.value),
       data.frame(
                  Currency="GBP",
                  ADF_Level=adf.test(exchange_data$NAD_GBP)$p.value,
                  ADF_Return=adf.test(exchange_returns$GBP_Return)$p.value)) %>%
       mutate(across(where(is.numeric),~round(.x,4)))

knitr::kable(
stationarity_table,
caption="Stationarity Test Table")
Stationarity Test Table
Currency ADF_Level ADF_Return
USD 0.4345 0.01
EUR 0.1128 0.01
GBP 0.3575 0.01

6.1.3 Stationarity Test

  • The Augmented Dickey-Fuller (ADF) test results indicate that all three exchange rate series (USD, EUR, and GBP) are non-stationary in levels, as the p-values (USD = 0.4345, EUR = 0.1128, GBP = 0.3575) are greater than the 5% significance level.

  • After first differencing (daily returns), all currencies become stationary, with ADF p-values of 0.01, confirming rejection of the unit root hypothesis.

  • These findings suggest that the exchange rates follow an integrated process of order one, I(1), meaning shocks to exchange rate levels have persistent effects, while changes (returns) fluctuate around a stable mean.

  • The results justify the use of exchange rate returns rather than exchange rate levels for subsequent time-series modelling and volatility analysis.

Overall, the stationarity tests confirm that the exchange rate series require first differencing before econometric analysis, ensuring that the assumptions underlying time-series models are satisfied.

6.2 Normality Tests

6.2.1 Objective

Test whether returns follow a normal distribution.

Code
normality_table<-tibble(
           Currency=c("NAD/USD","NAD/EUR","NAD/GBP"),
           JB_Statistic=c(
                          jarque.bera.test(exchange_returns$USD_Return)$statistic,
                          jarque.bera.test(exchange_returns$EUR_Return)$statistic,
                          jarque.bera.test(exchange_returns$GBP_Return)$statistic),
               P_Value=c(
                         jarque.bera.test(exchange_returns$USD_Return)$p.value,
                         jarque.bera.test(exchange_returns$EUR_Return)$p.value,
                         jarque.bera.test(exchange_returns$GBP_Return)$p.value)) %>%
                  mutate(
                         across(where(is.numeric),~round(.x,4)),
                         Decision=if_else(P_Value<.05,"Non-normal returns","Normal returns"))

knitr::kable(
normality_table,
caption="Normality Test Table")
Normality Test Table
Currency JB_Statistic P_Value Decision
NAD/USD 478.890 0 Non-normal returns
NAD/EUR 1003.294 0 Non-normal returns
NAD/GBP 1024.840 0 Non-normal returns

6.2.2 Normality Test

  • The Jarque-Bera (JB) test results reject the null hypothesis of normality for the NAD/USD, NAD/EUR, and NAD/GBP return series, as all p-values are 0.000, which is below the 5% significance level.

  • The British Pound (JB = 1024.840) recorded the largest Jarque-Bera statistic, followed closely by the Euro (JB = 1003.294), indicating the strongest departure from a normal distribution.

  • The US Dollar also exhibited a large Jarque-Bera statistic (478.890), confirming that its return distribution is likewise non-normal.

  • The non-normality of the return series suggests the presence of fat tails and/or extreme exchange rate movements, which are common characteristics of financial market data.

  • These findings indicate that large exchange rate changes occur more frequently than would be expected under a normal distribution, highlighting the importance of using econometric models that account for non-normality and time-varying volatility.

Overall, the normality tests confirm that the daily exchange rate returns are non-normally distributed, consistent with the stylised facts of financial time series and supporting the use of volatility models such as GARCH.

6.3 Autocorrelation of Squared Returns and ARCH Effects

6.3.1 Objective

To examine whether exchange-rate returns exhibit volatility clustering by analysing the autocorrelation structure of squared returns before estimating GARCH models. Determine whether conditional heteroskedasticity is present.

6.3.2 Research Questions

  • Do squared exchange rate returns exhibit significant autocorrelation?

  • Is there evidence of volatility clustering in the Namibia dollar against the US dollar, euro and British pound?

  • Does the autocorrelation, ARCH effects structure justify the application of GARCH type volatility models?

Code
library(forecast)

#---------------------------------------------------------
# Ljung-Box and ARCH-LM Tests 
#---------------------------------------------------------
pre_garch_test <- function(returns, currency){

  lb <- Box.test(
    returns^2,
    lag = 20,
    type = "Ljung-Box")

  arch <- ArchTest(
    returns,
    lags = 12)

  tibble(
    Currency = currency,

    Ljung_Box_Statistic = round(lb$statistic, 2),
    Ljung_Box_p = round(lb$p.value, 4),
    Ljung_Box_Decision = ifelse(
      lb$p.value > .05,
      "No volatility clustering",
      "Volatility clustering"),

    ARCH_LM_Statistic = round(arch$statistic, 2),
    ARCH_LM_p = round(arch$p.value, 4),
    ARCH_LM_Decision = ifelse(
      arch$p.value > .05,
      "No ARCH effects",
      "ARCH effects present"))
}

pre_garch_table <- bind_rows(
  pre_garch_test(exchange_returns$USD_Return, "NAD/USD"),
  pre_garch_test(exchange_returns$EUR_Return, "NAD/EUR"),
  pre_garch_test(exchange_returns$GBP_Return, "NAD/GBP"))

knitr::kable(
  pre_garch_table,
  caption = "Pre-Estimation Ljung-Box and ARCH-LM Tests")
Pre-Estimation Ljung-Box and ARCH-LM Tests
Currency Ljung_Box_Statistic Ljung_Box_p Ljung_Box_Decision ARCH_LM_Statistic ARCH_LM_p ARCH_LM_Decision
NAD/USD 515.92 0 Volatility clustering 247.98 0 ARCH effects present
NAD/EUR 609.17 0 Volatility clustering 273.42 0 ARCH effects present
NAD/GBP 527.58 0 Volatility clustering 233.49 0 ARCH effects present
Code
acf_data<-bind_rows(
  tibble(
    Lag=0:30,
    ACF=as.numeric(acf(
      exchange_returns$USD_Return^2,
      lag.max=30,
      plot=FALSE,
      na.action=na.pass)$acf),
    Currency="NAD/USD"),
  tibble(
    Lag=0:30,
    ACF=as.numeric(acf(
      exchange_returns$EUR_Return^2,
      lag.max=30,
      plot=FALSE,
      na.action=na.pass)$acf),
    Currency="NAD/EUR"),
  tibble(
    Lag=0:30,
    ACF=as.numeric(acf(
      exchange_returns$GBP_Return^2,
      lag.max=30,
      plot=FALSE,
      na.action=na.pass)$acf),
    Currency="NAD/GBP"))%>%
filter(Lag>0)

confidence_limit<-1.96/sqrt(nrow(exchange_returns))




ggplot(acf_data,aes(x=Lag,y=ACF,fill=Currency))+
geom_col(width=.7)+
geom_hline(yintercept=0,colour="grey35",linewidth=.5)+
geom_hline(yintercept=c(-confidence_limit,confidence_limit),
           linetype="dashed",
           colour="red",
           linewidth=.6)+
facet_wrap(~Currency,ncol=1,scales="free_y")+
scale_fill_manual(values=c(
  "NAD/USD"="#EAB200",
  "NAD/EUR"="#296960",
  "NAD/GBP"="#B22222"))+
scale_x_continuous(breaks=seq(0,30,5))+
labs(
  title="Autocorrelation of Squared Exchange Rate Returns",
  subtitle="Dashed lines represent approximate 95% confidence limits",
  x="Lag (Trading Days)",
  y="Autocorrelation",
  fill=NULL,
  caption="Source: Author's calculations")+
theme_minimal(base_size=11)+
theme(
      plot.title=element_text(face="bold",size=14,hjust=.5),
      plot.subtitle=element_text(size=11,hjust=.5,margin=margin(b=10)),
      strip.text=element_text(face="bold",size=10),
      axis.title.x=element_text(face="bold",size=9),
      axis.title.y=element_text(face="bold",size=9),
      axis.text.x=element_text(size=8),
      axis.text.y=element_text(size=9),
      legend.position="none",
      panel.background=element_rect(fill="grey96",colour="grey70",linewidth=.6),
      panel.grid.major.y=element_line(colour="white",linewidth=.7),
      panel.grid.minor.y=element_line(colour="white",linewidth=.05),
      panel.grid.major.x=element_blank(),
      panel.grid.minor.x=element_blank())

6.3.3 Pre-Estimation Ljung-Box and ARCH-LM Test

  • The Ljung-Box test rejects the null hypothesis of no autocorrelation for the squared returns of all three currencies, as the p-values are 0.000, confirming the presence of volatility clustering.

  • The NAD/EUR exchange rate recorded the highest Ljung-Box statistic (609.17), followed by NAD/GBP (527.58) and NAD/USD (515.92), indicating persistent dependence in exchange rate volatility.

  • The ARCH-LM test also rejects the null hypothesis of no ARCH effects for all three currencies, with p-values of 0.000, confirming significant time-varying volatility.

  • The NAD/EUR series exhibited the strongest ARCH effects (ARCH-LM = 273.42), followed by NAD/USD (247.98) and NAD/GBP (233.49).

  • The autocorrelation plots of squared returns further support these findings, as several autocorrelation coefficients remain above the 95% confidence limits, indicating persistent volatility over multiple trading days.

  • The presence of volatility clustering and ARCH effects suggests that periods of high exchange rate volatility are followed by further high volatility, while calm periods are followed by continued stability.

Overall, the diagnostic tests confirm that the exchange rate returns exhibit conditional heteroskedasticity, supporting the use of ARCH/GARCH-family models to model and forecast exchange rate volatility.

7 GARCH Model Estimation

7.1 Model Specifications

Code
sgarch_spec<-ugarchspec(
variance.model=list(model="sGARCH",garchOrder=c(1,1)),
mean.model=list(armaOrder=c(0,0),include.mean=TRUE),
distribution.model="std")

egarch_spec<-ugarchspec(
variance.model=list(model="eGARCH",garchOrder=c(1,1)),
mean.model=list(armaOrder=c(0,0),include.mean=TRUE),
distribution.model="std")

gjr_spec<-ugarchspec(
variance.model=list(model="gjrGARCH",garchOrder=c(1,1)),
mean.model=list(armaOrder=c(0,0),include.mean=TRUE),
distribution.model="std")

7.2 Fit Models

Code
fit_currency_models<-function(return_series){
list(
sGARCH=ugarchfit(sgarch_spec,data=return_series,solver="hybrid"),
EGARCH=ugarchfit(egarch_spec,data=return_series,solver="hybrid"),
GJR_GARCH=ugarchfit(gjr_spec,data=return_series,solver="hybrid"))
}

usd_models<-fit_currency_models(exchange_returns$USD_Return)
eur_models<-fit_currency_models(exchange_returns$EUR_Return)
gbp_models<-fit_currency_models(exchange_returns$GBP_Return)

8 Model Comparison

8.1 Information Criteria

8.1.1 Objective

Identify the best-fitting volatility model for each currency.

Code
extract_information_criteria<-function(model_list,currency){
bind_rows(lapply(names(model_list),function(model_name){
criteria<-infocriteria(model_list[[model_name]])
tibble(
Currency=currency,
Model=model_name,
AIC=criteria[1],
BIC=criteria[2],
Shibata=criteria[3],
Hannan_Quinn=criteria[4])
}))
}

model_comparison<-bind_rows(
extract_information_criteria(usd_models,"NAD/USD"),
extract_information_criteria(eur_models,"NAD/EUR"),
extract_information_criteria(gbp_models,"NAD/GBP"))%>%
mutate(across(where(is.numeric),~round(.x,4)))

knitr::kable(
model_comparison,
caption="Model Comparison Table")
Model Comparison Table
Currency Model AIC BIC Shibata Hannan_Quinn
NAD/USD sGARCH 2.6238 2.6314 2.6238 2.6265
NAD/USD EGARCH 2.6188 2.6279 2.6188 2.6220
NAD/USD GJR_GARCH 2.6164 2.6255 2.6164 2.6196
NAD/EUR sGARCH 2.3976 2.4051 2.3976 2.4003
NAD/EUR EGARCH 2.3957 2.4047 2.3957 2.3989
NAD/EUR GJR_GARCH 2.3950 2.4041 2.3950 2.3982
NAD/GBP sGARCH 2.4368 2.4444 2.4368 2.4395
NAD/GBP EGARCH 2.4372 2.4462 2.4372 2.4404
NAD/GBP GJR_GARCH 2.4350 2.4440 2.4350 2.4382

8.1.2 GARCH Model Comparison

  • The GJR-GARCH model produced the lowest AIC, BIC, Shibata, and Hannan-Quinn information criteria for all three exchange rates, indicating that it provides the best overall fit among the competing volatility models.

  • For NAD/USD, the GJR-GARCH model (AIC = 2.6164) outperformed both the EGARCH (2.6188) and standard GARCH (2.6238) models, suggesting that it captures exchange rate volatility more effectively.

  • For NAD/EUR, the GJR-GARCH model (AIC = 2.3950) also achieved the best performance, although the differences relative to the EGARCH and standard GARCH models were relatively small.

  • For NAD/GBP, the GJR-GARCH model (AIC = 2.4350) recorded the lowest information criteria, making it the preferred specification for modelling Pound exchange rate volatility.

  • The consistently lower information criteria across all currencies indicate that accounting for asymmetric volatility effects improves model performance, suggesting that positive and negative exchange rate shocks do not affect volatility equally.

Overall, the model comparison results identify the GJR-GARCH model as the most appropriate specification for analyzing and forecasting exchange rate volatility in the Namibian Dollar against the US Dollar, Euro, and British Pound.

9 Parameter Estimates

9.1 Objective

Compare volatility persistence and asymmetric effects.

Code
extract_coefficients<-function(model,currency){

                      as.data.frame(model@fit$matcoef)%>%
                      rownames_to_column("Parameter")%>%
                      mutate(Currency=currency, .before=1)
}

coefficient_table<-bind_rows(
extract_coefficients(usd_models$GJR_GARCH,"NAD/USD"),
extract_coefficients(eur_models$GJR_GARCH,"NAD/EUR"),
extract_coefficients(gbp_models$GJR_GARCH,"NAD/GBP"))

knitr::kable(
coefficient_table,
caption="Coefficient Table")
Coefficient Table
Currency Parameter Estimate Std. Error t value Pr(>|t|)
NAD/USD mu 0.0138798 0.0132958 1.0439247 0.2965202
NAD/USD omega 0.0087427 0.0018111 4.8271557 0.0000014
NAD/USD alpha1 0.0580153 0.0051028 11.3693307 0.0000000
NAD/USD beta1 0.9576823 0.0018528 516.8911909 0.0000000
NAD/USD gamma1 -0.0550892 0.0086702 -6.3538433 0.0000000
NAD/USD shape 13.5457168 2.4282597 5.5783642 0.0000000
NAD/EUR mu -0.0051436 0.0115698 -0.4445729 0.6566285
NAD/EUR omega 0.0172645 0.0069921 2.4691396 0.0135438
NAD/EUR alpha1 0.0752819 0.0166272 4.5276294 0.0000060
NAD/EUR beta1 0.9218326 0.0214309 43.0142006 0.0000000
NAD/EUR gamma1 -0.0457361 0.0135503 -3.3752867 0.0007374
NAD/EUR shape 7.6157319 0.8192295 9.2962129 0.0000000
NAD/GBP mu 0.0032888 0.0118027 0.2786470 0.7805157
NAD/GBP omega 0.0091623 0.0036520 2.5088354 0.0121130
NAD/GBP alpha1 0.0596923 0.0112822 5.2908389 0.0000001
NAD/GBP beta1 0.9438435 0.0125991 74.9135649 0.0000000
NAD/GBP gamma1 -0.0330851 0.0105503 -3.1359521 0.0017130
NAD/GBP shape 8.2520008 0.9782437 8.4355268 0.0000000

9.1.1 GJR-GARCH Coefficient

  • The mean equation (μ) is statistically insignificant for all three exchange rates (p > 0.05), indicating that average daily returns are not significantly different from zero.

  • The variance constant (ω) is positive and statistically significant across all currencies, confirming the presence of a persistent baseline level of exchange rate volatility.

  • The ARCH coefficients (α₁) are positive and highly significant for all exchange rates, indicating that recent exchange rate shocks have a significant short-run impact on current volatility.

  • The GARCH coefficients (β₁) are positive, highly significant, and close to one (USD = 0.958, EUR = 0.922, GBP = 0.944), demonstrating strong volatility persistence, where periods of high volatility tend to be followed by further high volatility.

  • The asymmetry coefficients (γ₁) are negative and statistically significant for all currencies, confirming the presence of asymmetric (leverage) effects, where positive and negative exchange rate shocks influence volatility differently.

  • The shape parameters are positive and highly significant, indicating that the return distributions are fat-tailed, implying that extreme exchange rate movements occur more frequently than predicted under a normal distribution.

Overall, the GJR-GARCH estimates indicate that exchange rate volatility is persistent, responds strongly to new market information, exhibits asymmetric behavour, and is characterized by heavy tailed return distributions, making the GJR-GARCH model well suited for modelling the volatility of the Namibian Dollar against the US Dollar, Euro, and British Pound.

10 Volatility Persistence

10.1 Objective

Measure how long exchange-rate volatility shocks persist.

Code
extract_persistence<-function(model,currency){
                  p<-rugarch::persistence(model)
tibble(
Currency=currency,
Model="GJR-GARCH",
Persistence=p,
Half_Life=ifelse(p>0&p<1,log(.5)/log(p),NA_real_))
}

persistence_table<-bind_rows(
extract_persistence(usd_models$GJR_GARCH,"NAD/USD"),
extract_persistence(eur_models$GJR_GARCH,"NAD/EUR"),
extract_persistence(gbp_models$GJR_GARCH,"NAD/GBP"))%>%
mutate(
Persistence=round(Persistence,4),
Half_Life=round(Half_Life,2),
Interpretation=case_when(
Persistence>=1~"Non-mean-reverting or explosive",
Persistence>=.99~"Extremely persistent",
Persistence>=.95~"Highly persistent",
Persistence>=.80~"Moderately persistent",
TRUE~"Low persistence"))

knitr::kable(
persistence_table,
caption="Persistence Table")
Persistence Table
Currency Model Persistence Half_Life Interpretation
NAD/USD GJR-GARCH 0.9882 58.16 Highly persistent
NAD/EUR GJR-GARCH 0.9742 26.57 Highly persistent
NAD/GBP GJR-GARCH 0.9870 52.94 Highly persistent

10.1.1 Persistence and Half Life Visual

Code
ggplot(persistence_table,aes(x = reorder(Currency, Persistence),y = Persistence,
       colour = Currency)) +
  geom_segment(aes(xend = Currency,y = .95,yend = Persistence),linewidth = 2,
               show.legend = FALSE) +
  geom_point(size = 5,show.legend = FALSE) +
  geom_text(aes(label = round(Persistence, 4)),nudge_y = .0015,fontface = "bold",
            size = 3.5) +
  geom_hline(yintercept = .95,linetype = "dashed",colour = "grey40") +
  annotate("text",x = 3.25,y = .9505,label = "High persistence threshold",hjust = 1,
           size = 3,colour = "grey35") +
  scale_colour_manual(values = c(
    "NAD/USD" = "#EAB200",
    "NAD/EUR" = "#296960",
    "NAD/GBP" = "#B22222")) +
  scale_y_continuous(limits = c(.95, .99),breaks = seq(.95, .99, .01),
    labels = scales::label_number(accuracy = .01)) +
  labs(
    title = "Volatility Persistence Across Exchange Rates",
    subtitle = "Persistence estimates from the selected GJR-GARCH models",
    x = NULL,
    y = "Persistence",
    caption = "Source: Author's GJR-GARCH estimates") +
  theme_minimal(base_size = 11) +
  theme(
      plot.title = element_text(face = "bold", size = 14, hjust = .5),
      plot.subtitle = element_text(size = 11, hjust = .5, margin = margin(b = 10)),
      axis.title.y = element_text(face = "bold", size = 9),
      axis.text.x = element_text(face = "bold", size = 9),
      axis.text.y = element_text(size = 9),
      panel.background = element_rect(fill = "grey96", colour = "grey70", linewidth = .6),
      panel.grid.major.y = element_line(colour = "white", linewidth = .7),
      panel.grid.minor.y = element_line(colour = "white", linewidth = .05),
      panel.grid.major.x = element_blank(),
      panel.grid.minor.x = element_blank(),
      legend.position = "none")

Code
ggplot(persistence_table,aes(x=Currency,y=Half_Life,fill=Currency))+
geom_col(width=.65)+
geom_text(aes(label=paste0(round(Half_Life,1)," days")),
          vjust=-.5,
          fontface="bold",
          size=3.5)+
scale_fill_manual(values=c(
  "NAD/USD"="#EAB200",
  "NAD/EUR"="#296960",
  "NAD/GBP"="#B22222"))+
scale_y_continuous(
  limits=c(0,max(persistence_table$Half_Life)+8),
  breaks=scales::pretty_breaks(n=6),
  expand=expansion(mult=c(0,.05)))+
labs(
  title="Half-Life of Exchange Rate Volatility Shocks",
  subtitle="Trading days required for half of a volatility shock to dissipate",
  x=NULL,
  y="Trading Days",
  fill=NULL,
  caption="Source: Author's GJR-GARCH estimates")+
theme_minimal(base_size=11)+
theme(
      plot.title=element_text(face="bold",size=14,hjust=.5),
      plot.subtitle=element_text(size=11,hjust=.5,margin=margin(b=10)),
      axis.title.y=element_text(face="bold",size=9),
      axis.text.x=element_text(face="bold",size=9),
      axis.text.y=element_text(size=9),
      legend.position="none",
      panel.background=element_rect(fill="grey96",colour="grey70",linewidth=.6),
      panel.grid.major.y=element_line(colour="white",linewidth=.7),
      panel.grid.minor.y=element_line(colour="white",linewidth=.05),
      panel.grid.major.x=element_blank(),
      panel.grid.minor.x=element_blank())

10.1.2 Volatility Persistence and Half-Life

  • The GJR-GARCH model indicates that exchange rate volatility is highly persistent for all three currencies, with persistence values exceeding 0.97, implying that volatility shocks dissipate only gradually over time.

  • The NAD/USD exchange rate exhibits the highest persistence (0.9882), followed closely by NAD/GBP (0.9870) and NAD/EUR (0.9742), suggesting that volatility shocks are strongest and most enduring in the US Dollar market.

  • The estimated half-life of volatility shocks is approximately 58.2 trading days for NAD/USD, 52.9 trading days for NAD/GBP, and 26.6 trading days for NAD/EUR, indicating the time required for half of a volatility shock to dissipate.

  • The US Dollar and British Pound require nearly two months for volatility shocks toreduce by half, whereas the Euro returns to normal volatility conditionsmore quickly, requiring less than one month.

  • The high persistence values indicate that periods of elevated exchange rate volatility are likely to be followed by further periods of high volatility, reflecting strong dependence in volatility over time.

Overall, the persistence analysis confirms that exchange rate volatility in Namibia is highly persistent, with the US Dollar and British Pound exhibiting the most prolonged volatility dynamics, supporting the use of GJR-GARCH models for volatility forecasting.

11 Conditional Volatility

11.1 Objective

Compare estimated volatility over time.

Code
best_usd<-usd_models$GJR_GARCH
best_eur<-eur_models$GJR_GARCH
best_gbp<-gbp_models$GJR_GARCH

conditional_volatility<-tibble(
Date=exchange_returns$Date,
USD_Volatility=as.numeric(sigma(best_usd)),
EUR_Volatility=as.numeric(sigma(best_eur)),
GBP_Volatility=as.numeric(sigma(best_gbp)))%>%
pivot_longer(
cols=-Date,
names_to="Currency",
values_to="Conditional_Volatility")

ggplot(conditional_volatility,aes(Date,Conditional_Volatility,colour=Currency))+
geom_line(linewidth=.65)+
facet_wrap(~Currency,ncol=1,scales="free_y")+
scale_color_manual(values = 
                     c("EUR_Volatility" = "#296960",
                       "GBP_Volatility" = "#B22222",
                       "USD_Volatility" = "#EAB200")) +
labs(
title="Estimated Conditional Exchange Rate Volatility",
subtitle="Volatility estimates from selected GJR-GARCH model",
x=NULL,
y="Conditional volatility",
colour=NULL,
caption="Source: Author's GJR-GARCH estimates")+
theme_minimal(base_size = 11) +
theme(
    plot.title = element_text(face = "bold", size = 14, hjust = 0.5),
    plot.subtitle = element_text(size = 11, hjust = 0.5, margin = margin(b = 10)),
    axis.title.x = element_blank(),
    axis.title.y = element_text(face = "bold", size = 9),
    axis.text.x = element_text(hjust = 1, size = 9),
    axis.text.y = element_text(hjust = 1, size = 9),
    legend.position = "none",
    legend.text = element_text(size = 9),
    legend.title = element_blank(),
    panel.background = element_rect(fill = "grey96", color = "grey70", linewidth = 0.6),
    panel.grid.major.y = element_line(color = "white", linewidth = 0.7),
    panel.grid.minor.y = element_line(color = "white", linewidth = 0.05),
    panel.grid.major.x = element_blank(),
    panel.grid.minor = element_blank())

11.1.1 Estimated Conditional Exchange Rate Volatility

  • The estimated conditional volatility from the GJR-GARCH model varies over time for all three exchange rates, confirming that exchange rate risk is dynamic rather than constant.

  • Pronounced volatility spikes are observed during 2015-2021, indicating periods of heightened uncertainty and increased exchange rate fluctuations.

  • The NAD/USD exchange rate exhibits the largest and most persistent volatility spikes, followed closely by NAD/GBP, while NAD/EUR generally displays comparatively lower volatility throughout the sample period.

  • The estimated volatility shows clear volatility clustering, where periods of high volatility are followed by further high volatility before gradually reverting to lower levels.

  • From 2022 onwards, conditional volatility generally declines, although occasional spikes remain, suggesting improved exchange rate stability alongside intermittent market shocks.

  • The similar patterns across the three currencies indicate that their volatility was influenced by common domestic and international economic events, resulting in synchronized periods of heightened exchange rate uncertainty.

Overall, the GJR-GARCH model successfully captures the time-varying nature of exchange rate volatility, confirming that volatility is persistent, clustered, and responsive to major economic and financial events.

Code
forecast_horizon <- 120

usd_returns <- exchange_returns$USD_Return
eur_returns <- exchange_returns$EUR_Return
gbp_returns <- exchange_returns$GBP_Return

test_dates <- tail(exchange_returns$Date,forecast_horizon)


usd_spec <- getspec(usd_models$GJR_GARCH)
eur_spec <- getspec(eur_models$GJR_GARCH)
gbp_spec <- getspec(gbp_models$GJR_GARCH)



usd_test_fit <- ugarchfit(
  spec=usd_spec,
  data=usd_returns,
  out.sample=forecast_horizon,
  solver="hybrid")

eur_test_fit <- ugarchfit(
  spec=eur_spec,
  data=eur_returns,
  out.sample=forecast_horizon,
  solver="hybrid")

gbp_test_fit <- ugarchfit(
  spec=gbp_spec,
  data=gbp_returns,
  out.sample=forecast_horizon,
  solver="hybrid")



usd_test_forecast <- ugarchforecast(
  usd_test_fit,
  n.ahead=1,
  n.roll=forecast_horizon-1)

eur_test_forecast <- ugarchforecast(
  eur_test_fit,
  n.ahead=1,
  n.roll=forecast_horizon-1)

gbp_test_forecast <- ugarchforecast(
  gbp_test_fit,
  n.ahead=1,
  n.roll=forecast_horizon-1)



usd_forecast_sigma <- as.numeric(sigma(usd_test_forecast))
eur_forecast_sigma <- as.numeric(sigma(eur_test_forecast))
gbp_forecast_sigma <- as.numeric(sigma(gbp_test_forecast))

usd_forecast_variance <- usd_forecast_sigma^2
eur_forecast_variance <- eur_forecast_sigma^2
gbp_forecast_variance <- gbp_forecast_sigma^2


usd_actual_variance <- tail(usd_returns,forecast_horizon)^2
eur_actual_variance <- tail(eur_returns,forecast_horizon)^2
gbp_actual_variance <- tail(gbp_returns,forecast_horizon)^2



volatility_forecast_data <- bind_rows(
  tibble(
    Date=test_dates,
    Currency="NAD/USD",
    Actual_Return=tail(usd_returns,forecast_horizon),
    Actual_Variance=usd_actual_variance,
    Forecast_Volatility=usd_forecast_sigma,
    Forecast_Variance=usd_forecast_variance),

  tibble(
    Date=test_dates,
    Currency="NAD/EUR",
    Actual_Return=tail(eur_returns,forecast_horizon),
    Actual_Variance=eur_actual_variance,
    Forecast_Volatility=eur_forecast_sigma,
    Forecast_Variance=eur_forecast_variance),

  tibble(
    Date=test_dates,
    Currency="NAD/GBP",
    Actual_Return=tail(gbp_returns,forecast_horizon),
    Actual_Variance=gbp_actual_variance,
    Forecast_Volatility=gbp_forecast_sigma,
    Forecast_Variance=gbp_forecast_variance)) %>%
  mutate(
    Error=Actual_Variance-Forecast_Variance,
    Absolute_Error=abs(Error),
    Squared_Error=Error^2)


knitr::kable(
  head(volatility_forecast_data),
  caption="Out-of-Sample GJR-GARCH Forecast Accuracy for the Final 30 Trading Days")
Out-of-Sample GJR-GARCH Forecast Accuracy for the Final 30 Trading Days
Date Currency Actual_Return Actual_Variance Forecast_Volatility Forecast_Variance Error Absolute_Error Squared_Error
2026-02-09 NAD/USD -0.9539402 0.9100018 0.6599540 0.4355393 0.4744625 0.4744625 0.2251147
2026-02-10 NAD/USD 0.4292081 0.1842196 0.6547002 0.4286323 -0.2444127 0.2444127 0.0597376
2026-02-11 NAD/USD -0.5035482 0.2535608 0.6547802 0.4287372 -0.1751764 0.1751764 0.0306868
2026-02-12 NAD/USD 0.5004128 0.2504130 0.6482048 0.4201694 -0.1697564 0.1697564 0.0288172
2026-02-13 NAD/USD -0.0062711 0.0000393 0.6512490 0.4241252 -0.4240859 0.4240859 0.1798489
2026-02-16 NAD/USD 0.1710596 0.0292614 0.6441794 0.4149671 -0.3857057 0.3857057 0.1487689
Code
forecast_accuracy <- volatility_forecast_data %>%
  group_by(Currency) %>%
  summarise(
    MAE=mean(Absolute_Error,na.rm=TRUE),
    RMSE=sqrt(mean(Squared_Error,na.rm=TRUE)),
    QLIKE=mean(
      log(pmax(Forecast_Variance,1e-8))+
      Actual_Variance/pmax(Forecast_Variance,1e-8),
      na.rm=TRUE),
    Mean_Actual_Variance=mean(Actual_Variance,na.rm=TRUE),
    Mean_Forecast_Variance=mean(Forecast_Variance,na.rm=TRUE),
    Bias=mean(Forecast_Variance-Actual_Variance,na.rm=TRUE),
    .groups="drop") %>%
  mutate(across(where(is.numeric),~round(.x,4)))


knitr::kable(
  forecast_accuracy,
  caption="Out-of-Sample GJR-GARCH Forecast Accuracy for the Final 30 Trading Days")
Out-of-Sample GJR-GARCH Forecast Accuracy for the Final 30 Trading Days
Currency MAE RMSE QLIKE Mean_Actual_Variance Mean_Forecast_Variance Bias
NAD/EUR 0.5341 0.8705 0.1553 0.4220 0.5191 0.0971
NAD/GBP 0.5095 0.8818 0.1481 0.4153 0.4874 0.0721
NAD/USD 0.8094 1.4180 0.7686 0.7840 0.7696 -0.0145
Code
forecast_plot_data <- volatility_forecast_data %>%
  group_by(Currency) %>%
  arrange(Date,.by_group=TRUE) %>%
  mutate(
    Realised_Variance_MA5=zoo::rollapply(
      Actual_Variance,
      width=5,
      FUN=mean,
      fill=NA,
      align="right")) %>%
  ungroup()



ggplot(forecast_plot_data,aes(x=Date))+
  geom_line(aes(y=Actual_Variance),
            colour="grey50",
            linewidth=.5,
            alpha=.7)+
  geom_line(aes(y=Realised_Variance_MA5,
                colour="Smoothed Realised Variance"),
            linewidth=.9) +
  geom_line(aes(y=Forecast_Variance,
                colour="Forecast Variance"),
            linewidth=.9,
            linetype="dashed")+
  facet_wrap(~Currency,ncol=1,scales="free_y")+
  scale_colour_manual(values=c(
    "Smoothed Realised Variance"="#1F4E79",
    "Forecast Variance"="#CB7A09"))+
  labs(
    title="Out-of-Sample Exchange Rate Variance Forecasts",
    subtitle="GJR-GARCH forecasts compared with smoothed realised variance",
    x=NULL,
    y="Variance",
    colour=NULL,
    caption="Source: Author's GJR-GARCH estimates")+
  theme_minimal(base_size=11)+
  theme(
    plot.title=element_text(face="bold",size=14,hjust=.5),
    plot.subtitle=element_text(size=11,hjust=.5,margin=margin(b=10)),
    strip.text=element_text(face="bold",size=10),
    axis.title.y=element_text(face="bold",size=9),
    axis.text.x=element_text(angle=45,hjust=1,size=8),
    axis.text.y=element_text(size=9),
    legend.position="top",
    panel.background=element_rect(fill="grey96",colour="grey70",linewidth=.6),
    panel.grid.major.y=element_line(colour="white",linewidth=.7),
    panel.grid.minor.y=element_line(colour="white",linewidth=.05),
    panel.grid.major.x=element_blank(),
    panel.grid.minor.x=element_blank())

11.1.2 Out-of-Sample Volatility Forecasts

  • Ability to capture normal exchange rate risk: The forecasts generally follow the underlying movements in realized variance, suggesting that the GJR-GARCH models are useful for identifying whether exchange-rate risk is rising, falling, or remaining relatively stable. This is particularly relevant financial institutions exposed to foreign currency movements.

  • The Mean Absolute Error (MAE) measures the typical size of the model’s forecasting mistake, regardless of whether volatility was over or underestimated. The lower errors for NAD/GBP and NAD/EUR therefore suggest that their expected daily risk levels were generally estimated more accurately than for NAD/USD.

  • RMSE places greater weight on large forecasting mistakes, making it particularly informative for exchange rate risk management. The higher RMSE for NAD/USD indicates that the model had greater difficulty anticipating unusually large movements in dollar related volatility.

  • QLIKE evaluates how well the model predicts the underlying variance process and penalises substantial discrepancies between forecast and realized variance. The lower values for NAD/GBP and NAD/EUR indicate comparatively better volatility forecasts, while the higher value for NAD/USD reinforces the finding that dollar volatility was more difficult to predict.

  • Extreme movements remain difficult to predict: The graph shows that the forecasts capture broad volatility patterns but smooth over some sharp realized spikes. Therefore, the GJR-GARCH models appear more effective at estimating the general evolution and persistence of exchange-rate risk than predicting the exact magnitude of sudden market shocks.

Overall, the results suggest that GJR-GARCH can provide useful short-term information about Namibia’s exchange rate risk environment. However, users of the forecasts should recognise that unexpected currency shocks can exceed predicted volatility, particularly for NAD/USD, meaning volatility forecasts should complement rather than replace broader foreign exchange risk measures.

12 Residual Diagnostics

12.1 Objective

Determine whether the selected models adequately capture serial dependence and ARCH effects.

12.2 Questions

  • Are standardized residuals serially uncorrelated?
  • Are ARCH effects still present?
  • Are squared residuals independent?
Code
diagnose_garch<-function(model,currency){

residuals<-residuals(model,standardize=TRUE)

lb<-Box.test(
residuals,
lag=20,
type="Ljung-Box")

arch<-ArchTest(
residuals,
lags=12)
tibble(
Currency=currency,
Ljung_Box_Statistic=round(lb$statistic,2),
Ljung_Box_p=round(lb$p.value,4),
Ljung_Box_Decision=ifelse(
lb$p.value>.05,
"No autocorrelation",
"Autocorrelation remains"),

ARCH_LM_Statistic=round(arch$statistic,2),
ARCH_LM_p=round(arch$p.value,4),
ARCH_Decision=ifelse(
arch$p.value>.05,
"No remaining ARCH effects",
"Remaining ARCH effects"))
}

diagnostic_table<-bind_rows(

diagnose_garch(
usd_models$GJR_GARCH,
"NAD/USD"),

diagnose_garch(
eur_models$GJR_GARCH,
"NAD/EUR"),

diagnose_garch(
gbp_models$GJR_GARCH,
"NAD/GBP"))

knitr::kable(
diagnostic_table,
caption="Diagnostic Summary")
Diagnostic Summary
Currency Ljung_Box_Statistic Ljung_Box_p Ljung_Box_Decision ARCH_LM_Statistic ARCH_LM_p ARCH_Decision
NAD/USD 24.91 0.2050 No autocorrelation 24.44 0.0177 Remaining ARCH effects
NAD/EUR 18.56 0.5507 No autocorrelation 14.96 0.2435 No remaining ARCH effects
NAD/GBP 14.98 0.7774 No autocorrelation 23.32 0.0251 Remaining ARCH effects

12.2.1 Post-Estimation Diagnostic

  • The post-estimation diagnostics essentially ask one important question: after the GJR-GARCH model has done its job, is there still a meaningful volatility pattern left unexplained?
  • For all three currencies, the Ljung-Box test finds no remaining autocorrelation. In simple terms, the model has successfully captured the systematic patterns in the exchange-rate movements, leaving residuals that are largely unpredictable from their past values.
  • NAD/EUR provides the cleanest result. There is no remaining autocorrelation and no significant ARCH effect. This means the GJR-GARCH model has captured most of the volatility behaviour in the Euro exchange rate, leaving little systematic volatility behind.
  • The story is slightly different for NAD/USD and NAD/GBP. Although their autocorrelation has been removed, the ARCH-LM tests remain significant. This means some volatility clustering is still hiding in the residuals. The model explains much of the volatility, but not everything.
  • Economically, this suggests that USD and GBP exchange-rate risk may be more complex. Their volatility could contain additional dynamics or market shocks that a single GJR-GARCH specification does not fully capture.
  • Compared with the strong ARCH effects found before modelling, however, the post-estimation results represent a clear improvement. The GJR-GARCH model has therefore absorbed a substantial amount of the volatility structure present in the original returns.

Overall, the diagnostics tell a positive but not perfect story: GJR-GARCH performs very well for NAD/EUR and reasonably well for NAD/USD and NAD/GBP, although the latter two retain some unexplained volatility that should be acknowledged as a model limitation.

12.3 10. News Impact Curves

12.3.1 Objective

Assess whether positive and negative exchange-rate shocks have asymmetric effects on future exchange-rate volatility.

12.3.2 Research Question

  • Do positive and negative exchange-rate shocks affect volatility differently?
Code
#--------------------------------------------------------
# News Impact Curves
#--------------------------------------------------------

make_news_df <- function(ni, label){
  data.frame(
    shock = ni$zx,
    variance = ni$zy,
    Currency = label)
}

usd_news <- make_news_df(newsimpact(usd_models$GJR_GARCH), "USD")
eur_news <- make_news_df(newsimpact(eur_models$GJR_GARCH), "EUR")
gbp_news <- make_news_df(newsimpact(gbp_models$GJR_GARCH), "GBP")

news_df <- bind_rows(usd_news, eur_news, gbp_news)



end_labels <- news_df %>%
  group_by(Currency) %>%
  slice_max(shock, n = 1, with_ties = FALSE) %>%
  ungroup() %>%
  mutate(
    Label = paste0(number(variance, accuracy = 0.001)),
    Label_Y = case_when(
      Currency == "USD" ~ variance + 0.0015,
      Currency == "GBP" ~ variance,
      Currency == "EUR" ~ variance - 0.0015))

shock_min <- min(news_df$shock)
shock_max <- max(news_df$shock)

variance_min <- min(news_df$variance)
variance_max <- max(news_df$variance)

ggplot(news_df, aes(x = shock, y = variance, colour = Currency)) +
  geom_line(linewidth = 1) +
  geom_vline(xintercept = 0,linetype = "dashed",colour = "grey40",linewidth = 0.6) +
  geom_text(data = end_labels,
            aes(y = Label_Y, label = Label),
            hjust = -0.08,
            fontface = "bold",
            size = 3.3,
            show.legend = FALSE) +
  annotate("text",x = 0.08,y = 0.704,
           label = "NAD depreciation shocks\nproduce larger increases\nin volatility",hjust = 0,fontface = "italic",colour = "grey20",size = 3.4) +
  annotate("segment",x = 0.13,xend = 0.23,y = variance_max - 0.007,
           yend = variance_max - 0.005,arrow = arrow(length = unit(0.15, "cm")),
           colour = "grey30") +
  annotate("text",x = 0,y = variance_min - 0.006,label = "No shock",
           colour = "grey35",size = 3) +
  scale_colour_manual(values = c("USD" = "#1F4E79",
                                 "EUR" = "#B22222",
                                 "GBP" = "#2E8B57")) +
  scale_x_continuous(limits = c(shock_min, shock_max * 1.18),
                     breaks = pretty_breaks(5),
                     expand = expansion(mult = c(0.01, 0.01))) +
  scale_y_continuous(limits = c(variance_min - 0.008,
                                variance_max + 0.005),
                     labels = label_number(accuracy = 0.001)) +
  labs(title = "News Impact Curves under the GJR-GARCH Model",
       subtitle = "Asymmetric response of conditional volatility to exchange rate shocks",
       x = "Standardised Shock",
       y = "Conditional Variance",
       caption = "Source: Author's GJR-GARCH estimates") +
  theme_minimal(base_size = 11) +
  theme(
      plot.title = element_text(face = "bold", size = 14, hjust = .5),
      plot.subtitle = element_text(size = 11, hjust = .5, margin = margin(b = 12)),
      axis.title.x = element_text(face = "bold", size = 10),
      axis.title.y = element_text(face = "bold", size = 10),
      axis.text = element_text(size = 9),
      legend.position = "none",
      plot.margin = margin(10, 70, 10, 10),
      panel.background = element_rect(fill = "grey96", colour = "grey70", linewidth = .6),
      panel.grid.major.y = element_line(colour = "white", linewidth = .7),
      panel.grid.minor.y = element_line(colour = "white", linewidth = .2),
      panel.grid.major.x = element_blank(),
      panel.grid.minor.x = element_blank())

12.3.3 News Impact Curve

  • The News Impact Curves tell a simple story: the foreign exchange market reacts differently depending on whether the Namibian Dollar is strengthening or weakening. A shock of zero represents a normal trading day with no unexpected currency movement.

  • When the NAD depreciates, the curves rise more strongly. This shows that NAD depreciation creates greater uncertainty and therefore a larger increase in expected exchange-rate volatility.

  • In contrast, when the NAD appreciates, volatility also responds, but the effect is generally smaller. Therefore, the market appears to be more sensitive to a weakening Namibia Dollar than to an equivalent strengthening.

  • This asymmetric response is economically important. For example, a sudden depreciation can increase uncertainty around the cost of imports, foreign payments and other foreign currency activities, making exchange rate risk more important.

  • The curves also show that the currencies operate at different underlying levels of conditional variance, with the upper series around 0.721, the middle around 0.679, and the lower around 0.642 at the illustrated positive shock.

Overall, the News Impact Curves show that not all exchange rate shocks are equal: depreciation of the Namibia Dollar tends to generate a stronger volatility response than appreciation. This asymmetry helps explain why the GJR-GARCH model performed better than a standard symmetric GARCH model.

12.4 11. Value-at-Risk (VaR) and Backtesting

12.4.1 Objective

Estimate downside exchange rate risk and evaluate whether the selected GARCH model accurately predicts extreme exchange rate movements.

12.4.2 Research Questions

  • What is the maximum expected exchange rate loss at the 95% and 99% confidence levels?

  • Does the selected GARCH model adequately capture extreme market movements?

Code
#---------------------------------------------------------
#  Compute VaR for each currency
#---------------------------------------------------------

# USD
usd_sigma <- as.numeric(sigma(usd_models$GJR_GARCH))
usd_mu <- as.numeric(fitted(usd_models$GJR_GARCH))

usd_var95 <- usd_mu + qdist(
  "std", p=.05, mu=0, sigma=usd_sigma,
  shape=coef(usd_models$GJR_GARCH)["shape"])

usd_var99 <- usd_mu + qdist(
  "std", p=.01, mu=0, sigma=usd_sigma,
  shape=coef(usd_models$GJR_GARCH)["shape"])

# EUR
eur_sigma <- as.numeric(sigma(eur_models$GJR_GARCH))
eur_mu <- as.numeric(fitted(eur_models$GJR_GARCH))

eur_var95 <- eur_mu + qdist(
  "std", p=.05, mu=0, sigma=eur_sigma,
  shape=coef(eur_models$GJR_GARCH)["shape"])

eur_var99 <- eur_mu + qdist(
  "std", p=.01, mu=0, sigma=eur_sigma,
  shape=coef(eur_models$GJR_GARCH)["shape"])

# GBP
gbp_sigma <- as.numeric(sigma(gbp_models$GJR_GARCH))
gbp_mu <- as.numeric(fitted(gbp_models$GJR_GARCH))

gbp_var95 <- gbp_mu + qdist(
  "std", p=.05, mu=0, sigma=gbp_sigma,
  shape=coef(gbp_models$GJR_GARCH)["shape"])

gbp_var99 <- gbp_mu + qdist(
  "std", p=.01, mu=0, sigma=gbp_sigma,
  shape=coef(gbp_models$GJR_GARCH)["shape"])

#---------------------------------------------------------
#  Backtesting
#---------------------------------------------------------

usd_back1 <- VaRTest(alpha = .05, actual = exchange_returns$USD_Return, VaR = usd_var95)
usd_back2 <- VaRTest(alpha = .01, actual = exchange_returns$USD_Return, VaR = usd_var99)

eur_back1 <- VaRTest(alpha = .05, actual = exchange_returns$EUR_Return, VaR = eur_var95)
eur_back2 <- VaRTest(alpha = .01, actual = exchange_returns$EUR_Return, VaR = eur_var99)

gbp_back1 <- VaRTest(alpha = .05, actual = exchange_returns$GBP_Return, VaR = gbp_var95)
gbp_back2 <- VaRTest(alpha = .01, actual = exchange_returns$GBP_Return, VaR = gbp_var99)

#---------------------------------------------------------
#  Summary Table
#---------------------------------------------------------

var_table <- tibble(
  Currency = c("NAD/USD", "NAD/EUR", "NAD/GBP"),
  VaR95 = c(mean(usd_var95, na.rm = TRUE),
            mean(eur_var95, na.rm = TRUE),
            mean(gbp_var95, na.rm = TRUE)),
  VaR99 = c(mean(usd_var99, na.rm = TRUE),
            mean(eur_var99, na.rm = TRUE),
            mean(gbp_var99, na.rm = TRUE)),
  Kupiec_p = c(usd_back1$LikelihoodRatio$Kupiec[2],
               eur_back1$LikelihoodRatio$Kupiec[2],
               gbp_back1$LikelihoodRatio$Kupiec[2]),
  Christoffersen_p = c(usd_back2$ConditionalCoverage$Christoffersen[2],
                       eur_back2$ConditionalCoverage$Christoffersen[2],
                       gbp_back2$ConditionalCoverage$Christoffersen[2]))


knitr::kable(var_table, caption = "Value-at-Risk Backtesting Results")
Value-at-Risk Backtesting Results
Currency VaR95 VaR99
NAD/USD -1.482118 -2.219486
NAD/EUR -1.345766 -2.104865
NAD/GBP -1.364980 -2.120909

12.4.3 Value-at-Risk Backtesting Visual

Code
usd_plot <- tibble(
  Date=exchange_returns$Date,
  Return=as.numeric(exchange_returns$USD_Return),
  VaR95=as.numeric(usd_var95),
  Currency="NAD/USD")

eur_plot <- tibble(
  Date=exchange_returns$Date,
  Return=as.numeric(exchange_returns$EUR_Return),
  VaR95=as.numeric(eur_var95),
  Currency="NAD/EUR")

gbp_plot <- tibble(
  Date=exchange_returns$Date,
  Return=as.numeric(exchange_returns$GBP_Return),
  VaR95=as.numeric(gbp_var95),
  Currency="NAD/GBP")

var_plot <- bind_rows(usd_plot,eur_plot,gbp_plot) %>%
  mutate(Violation=Return<VaR95)

var_violations <- var_plot %>%
  filter(Violation) %>%
  mutate(
    Breach_Size = Return - VaR95,
    Date = as.Date(Date)) %>%
  dplyr::select(Currency, Date, Return, VaR95, Breach_Size) %>%
  arrange(Currency, Date)

var_violations1 <- head(var_violations, 15)


knitr::kable(
  var_violations1,
  digits = 4,
  caption = "Dates and Magnitudes of 95% Value-at-Risk Violations")
Dates and Magnitudes of 95% Value-at-Risk Violations
Currency Date Return VaR95 Breach_Size
NAD/EUR 2010-02-01 -1.2622 -1.1308 -0.1314
NAD/EUR 2010-02-11 -1.7998 -1.1658 -0.6340
NAD/EUR 2010-02-17 -1.6781 -1.1730 -0.5051
NAD/EUR 2010-03-01 -1.4828 -1.1773 -0.3055
NAD/EUR 2010-03-04 -1.2500 -1.1819 -0.0681
NAD/EUR 2010-04-20 -1.1994 -1.0916 -0.1077
NAD/EUR 2010-04-26 -1.0809 -1.0550 -0.0260
NAD/EUR 2010-05-10 -1.9398 -1.0247 -0.9151
NAD/EUR 2010-05-12 -1.1740 -1.1281 -0.0459
NAD/EUR 2010-05-26 -2.1527 -1.9149 -0.2378
NAD/EUR 2010-06-28 -1.4342 -1.3313 -0.1029
NAD/EUR 2010-07-20 -1.6813 -1.6187 -0.0626
NAD/EUR 2010-11-05 -1.6740 -1.0366 -0.6374
NAD/EUR 2010-11-29 -1.4871 -1.0087 -0.4784
NAD/EUR 2010-11-30 -1.1392 -1.0732 -0.0661
Code
ggplot(var_plot,aes(x=Date))+
  geom_line(aes(y=Return),colour="#1F4E79",linewidth=.6)+
  geom_line(aes(y=VaR95),colour="#B22222",linetype="dashed",linewidth=.8)+
  geom_point(
    data=var_plot %>% filter(Violation),
    aes(y=Return),
    colour="red",
    size=1.8)+
  facet_wrap(~Currency,ncol=1,scales="free_y")+
  labs(
    title="Value-at-Risk Backtesting Across Exchange Rates",
    subtitle="Red points indicate returns below the estimated 95% VaR threshold",
    x=NULL,
    y="Daily Log Return (%)",
    caption="Source: Author's GJR-GARCH estimates")+
  theme_minimal(base_size=11)+
  theme(
    plot.title=element_text(face="bold",size=14,hjust=.5),
    plot.subtitle=element_text(size=11,hjust=.5,margin=margin(b=12)),
    strip.text=element_text(face="bold",size=10),
    axis.title.y=element_text(face="bold",size=9),
    axis.text.x=element_text(size=8),
    axis.text.y=element_text(size=9),
    legend.position="none",
    panel.background=element_rect(fill="grey96",colour="grey70",linewidth=.6),
    panel.grid.major.y=element_line(colour="white",linewidth=.7),
    panel.grid.minor.y=element_line(colour="white",linewidth=.05),
    panel.grid.major.x=element_blank(),
    panel.grid.minor.x=element_blank())

12.4.4 Value-at-Risk Backtesting

  • The Value-at-Risk (VaR) analysis tells us how large a daily exchange rate loss could become under normal and more extreme market conditions. The 95% VaR represents a loss threshold expected to be exceeded only about 5% of the time, while the 99% VaR represents a more severe threshold expected to be exceeded only about 1% of the time.

  • For NAD/USD, the 95% VaR of -1.48% means that on about 95% of trading days, the daily adverse return would be expected to remain within approximately 1.48%. Under the stricter 99% level, the corresponding threshold increases to about 2.22%.

  • NAD/USD has the largest potential downside risk of the three currencies, with both the highest 95% and 99% VaR magnitudes. This is consistent with the earlier results showing relatively high USD volatility and persistent volatility shocks.

  • NAD/EUR has the lowest estimated downside risk, with losses of approximately 1.35% at 95% VaR and 2.10% at 99% VaR, while NAD/GBP lies between the Euro and US Dollar.

  • The red points in the graph represent VaR exceedances, days when actual losses were greater than the estimated 95% risk threshold. Their concentration during more turbulent periods shows how exchange-rate risk increases when markets become unstable.

Overall, the VaR results indicate that NAD/USD carries the greatest downside exchange rate risk, followed by NAD/GBP and NAD/EUR, reinforcing the importance of accounting for changing volatility when managing foreign exchange exposure.

12.5 12. Structural Break Analysis

12.5.1 Objective

Identify significant structural changes in exchange rate volatility over the sample period.

12.5.2 Research Questions

  • Did exchange rate volatility experience significant structural changes?

  • Which periods correspond to different volatility regimes?

Code
volatility_data <- tibble(
  Date = exchange_returns$Date,
  `NAD/USD` = as.numeric(sigma(usd_models$GJR_GARCH)),
  `NAD/EUR` = as.numeric(sigma(eur_models$GJR_GARCH)),
  `NAD/GBP` = as.numeric(sigma(gbp_models$GJR_GARCH))) %>%
  pivot_longer(
    cols = -Date,
    names_to = "Currency",
    values_to = "Volatility")

estimate_breaks <- function(data, currency_name){

  currency_data <- data %>% filter(Currency == currency_name) %>% arrange(Date)

  bp_full <- breakpoints(
    Volatility ~ 1,
    data = currency_data,
    h = .15,
    breaks = 5)

  bp_bic <- BIC(bp_full)

  bic_table <- tibble(
    Currency = currency_name,
    Number_of_Breaks = 0:(length(bp_bic) - 1),
    BIC = as.numeric(bp_bic))

  optimal_breaks <- bic_table$Number_of_Breaks[
    which.min(bic_table$BIC)]

  bp_model <- breakpoints(
    Volatility ~ 1,
    data = currency_data,
    h = .15,
    breaks = optimal_breaks)

  break_indices <- bp_model$breakpoints
  break_indices <- break_indices[!is.na(break_indices)]

  if(length(break_indices) > 0){
    break_dates <- currency_data$Date[break_indices]

    break_table <- tibble(
      Currency = currency_name,
      Break_Number = seq_along(break_dates),
      Break_Date = break_dates)
  }else{
    break_table <- tibble(
      Currency = character(),
      Break_Number = integer(),
      Break_Date = as.Date(character()))
  }

  currency_data$Regime <- breakfactor(bp_model)

  regime_table <- currency_data %>%
    group_by(Currency, Regime) %>%
    summarise(
      Start = min(Date),
      End = max(Date),
      Observations = n(),
      Mean_Volatility = mean(Volatility, na.rm = TRUE),
      Median_Volatility = median(Volatility, na.rm = TRUE),
      Maximum_Volatility = max(Volatility, na.rm = TRUE),
      SD = sd(Volatility, na.rm = TRUE),
      .groups = "drop")

  list(
    data = currency_data,
    bic = bic_table,
    breaks = break_table,
    regimes = regime_table,
    optimal_breaks = optimal_breaks)
}

usd_breaks <- estimate_breaks(volatility_data, "NAD/USD")
eur_breaks <- estimate_breaks(volatility_data, "NAD/EUR")
gbp_breaks <- estimate_breaks(volatility_data, "NAD/GBP")

combined_break_data <- bind_rows(
  usd_breaks$data,
  eur_breaks$data,
  gbp_breaks$data)

combined_bic_table <- bind_rows(
  usd_breaks$bic,
  eur_breaks$bic,
  gbp_breaks$bic) %>%
  group_by(Currency) %>%
  mutate(
    BIC = round(BIC, 2),
    Selected = if_else(BIC == min(BIC), "Selected", "")) %>%
  ungroup()

combined_break_dates <- bind_rows(
  usd_breaks$breaks,
  eur_breaks$breaks,
  gbp_breaks$breaks)

combined_regime_summary <- bind_rows(
  usd_breaks$regimes,
  eur_breaks$regimes,
  gbp_breaks$regimes) %>%
  mutate(
    across(
      c(Mean_Volatility, Median_Volatility, Maximum_Volatility, SD),
      ~round(.x, 4)))

optimal_breaks_table <- tibble(
  Currency = c("NAD/USD", "NAD/EUR", "NAD/GBP"),
  Optimal_Breaks = c(
    usd_breaks$optimal_breaks,
    eur_breaks$optimal_breaks,
    gbp_breaks$optimal_breaks))

knitr::kable(
  optimal_breaks_table,
  caption = "Optimal Number of Structural Breaks by Exchange Rate")
Optimal Number of Structural Breaks by Exchange Rate
Currency Optimal_Breaks
NAD/USD 0
NAD/EUR 0
NAD/GBP 0
Code
knitr::kable(
  combined_break_dates,
  caption = "Estimated Structural Break Dates by Exchange Rate")
Estimated Structural Break Dates by Exchange Rate
Currency Break_Number Break_Date
NAD/USD 1 2023-10-26
NAD/EUR 1 2021-03-26
NAD/GBP 1 2023-10-16
Code
knitr::kable(
  combined_regime_summary,
  caption = "Conditional Volatility Regimes Across Exchange Rates")
Conditional Volatility Regimes Across Exchange Rates
Currency Regime Start End Observations Mean_Volatility Median_Volatility Maximum_Volatility SD
NAD/USD segment1 2010-01-04 2023-10-26 3511 0.9458 0.9098 2.0557 0.2025
NAD/USD segment2 2023-10-27 2026-07-31 689 0.7751 0.7688 1.1785 0.1200
NAD/EUR segment1 2010-01-04 2021-03-26 2863 0.8734 0.8193 1.9038 0.2120
NAD/EUR segment2 2021-03-29 2026-07-31 1337 0.7499 0.7318 1.3933 0.1230
NAD/GBP segment1 2010-01-04 2023-10-16 3503 0.8822 0.8398 1.9263 0.1973
NAD/GBP segment2 2023-10-17 2026-07-31 697 0.6805 0.6686 1.0071 0.1035
Code
ggplot(combined_break_data,
       aes(x = Date, y = Volatility, colour = Regime)) +
  geom_line(linewidth = .7) +
  geom_vline(
    data = combined_break_dates,
    aes(xintercept = Break_Date),
    linetype = "dashed",
    colour = "red",
    linewidth = .7,
    inherit.aes = FALSE) +
  facet_wrap(~Currency, ncol = 1, scales = "free_y") +
  scale_colour_manual(values = c(
    "segment1" = "#1F4E79",
    "segment2" = "#B22222",
    "segment3" = "#2E8B57",
    "segment4" = "#CB7A09",
    "segment5" = "#6A5ACD",
    "segment6" = "#708090")) +
  labs(
    title = "Structural Breaks in Exchange Rate Conditional Volatility",
    subtitle = "Bai-Perron break regimes estimated separately for each currency",
    x = NULL,
    y = "Conditional Volatility",
    colour = NULL,
    caption = "Source: Author's GJR-GARCH estimates") +
  theme_minimal(base_size = 11) +
  theme(
    plot.title = element_text(face = "bold", size = 14, hjust = .5),
    plot.subtitle = element_text(size = 11, hjust = .5, margin = margin(b = 12)),
    strip.text = element_text(face = "bold", size = 10),
    axis.title.y = element_text(face = "bold", size = 9),
    axis.text.x = element_text(size = 8),
    axis.text.y = element_text(size = 9),
    legend.position = "top",
    legend.text = element_text(size = 9),
    panel.background = element_rect(fill = "grey96", colour = "grey70", linewidth = .6),
    panel.grid.major.y = element_line(colour = "white", linewidth = .7),
    panel.grid.minor.y = element_line(colour = "white", linewidth = .05),
    panel.grid.major.x = element_blank(),
    panel.grid.minor.x = element_blank())

12.5.3 Structural Breaks in Exchange Rate Volatility

  • The structural break analysis identifies a clear shift from relatively high volatility regimes to lower and more stable volatility regimes for all three exchange rates.
  • For NAD/EUR, the structural break occurred on 26 March 2021. Before the break, mean volatility was 0.8734 and median volatility was 0.8193. After the break, these declined to 0.7499 and 0.7318, respectively, indicating a noticeable moderation in Euro related exchange rate risk.
  • For NAD/GBP, the break occurred on 16 October 2023. Mean volatility declined substantially from 0.8822 before the break to 0.6805 afterwards, while median volatility fell from 0.8398 to 0.6686. This represents the largest reduction among the three currencies.
  • For NAD/USD, the break occurred on 26 October 2023. Mean volatility decreased from 0.9458 to 0.7751, while the median declined from 0.9098 to 0.7688, showing that USD-related exchange rate risk also shifted into a calmer regime.
  • The decline in both the mean and median is important because it shows that the change was not simply caused by a few extreme observations. Rather, the typical level of exchange rate volatility itself became lower after the structural breaks.
  • The simultaneous decline in the standard deviation of volatility also shows that volatility became more stable and less dispersed after each break, particularly for NAD/GBP and NAD/USD.

Overall, the structural break analysis tells a consistent story: exchange rate volatility has moderated over time. NAD/EUR entered a lower volatility regime in March 2021, while NAD/GBP and NAD/USD experienced similar transitions in October 2023, with both average and typical volatility remaining lower thereafter.

12.6 13. Rolling Volatility Analysis

12.6.1 Objective

Examine how exchange rate volatility evolves over time using rolling window estimates.

12.6.2 Research Questions

  • How has exchange rate volatility changed over time?

  • Which periods experienced relatively high or low market uncertainty?

12.7 Volatility Forecasting

12.7.1 Objective

Forecast exchange rate risk over the next ten trading days.

Code
forecast_horizon<-10

usd_forecast<-ugarchforecast(
usd_models$GJR_GARCH,
n.ahead=forecast_horizon)

eur_forecast<-ugarchforecast(
eur_models$GJR_GARCH,
n.ahead=forecast_horizon)

gbp_forecast<-ugarchforecast(
gbp_models$GJR_GARCH,
n.ahead=forecast_horizon)

volatility_forecast_table<-tibble(
Horizon=1:forecast_horizon,
USD_Volatility=round(as.numeric(sigma(usd_forecast)),6),
EUR_Volatility=round(as.numeric(sigma(eur_forecast)),6),
GBP_Volatility=round(as.numeric(sigma(gbp_forecast)),6))


knitr::kable(
volatility_forecast_table,
caption="Diagnostic Summary")
Diagnostic Summary
Horizon USD_Volatility EUR_Volatility GBP_Volatility
1 0.864527 0.752853 0.725295
2 0.864462 0.754623 0.726892
3 0.864398 0.756343 0.728466
4 0.864335 0.758015 0.730015
5 0.864273 0.759640 0.731541
6 0.864211 0.761220 0.733045
7 0.864150 0.762757 0.734525
8 0.864090 0.764251 0.735984
9 0.864030 0.765703 0.737421
10 0.863971 0.767116 0.738836

12.7.2 Future Volatility Forecast Visual

Code
volatility_forecast_long<-volatility_forecast_table%>%
pivot_longer(
cols=-Horizon,
names_to="Currency",
values_to="Forecast_Volatility")%>%
mutate(
Currency=dplyr::recode(
Currency,
EUR_Volatility="NAD/EUR",
GBP_Volatility="NAD/GBP",
USD_Volatility="NAD/USD"))

end_labels<-volatility_forecast_long%>%
group_by(Currency)%>%
filter(Horizon==max(Horizon))%>%
ungroup()

ggplot(volatility_forecast_long,aes(Horizon, Forecast_Volatility, 
                                    colour = Currency)) +
  geom_line(linewidth = 0.5) +
  geom_point(size = 1.2) +
  geom_text(data = end_labels,
            aes(label = paste0(round(Forecast_Volatility, 3))),
            hjust = -0.08,
            fontface = "bold",
            size = 3.2,
            show.legend = FALSE) +
  scale_colour_manual(values = c("NAD/EUR" = "#296960","NAD/GBP" = "#B22222",
                                 "NAD/USD" = "#EAB200")) +
  scale_x_continuous(breaks = 1:10,limits = c(1, 11),
    expand = expansion(mult = c(.01, .02))) +
  scale_y_continuous(
    breaks = scales::pretty_breaks(n = 6),
    labels = scales::label_number(accuracy = .01),
    expand = expansion(mult = c(.08, .12))) +
  labs(
    title = "Ten-Day Exchange Rate Volatility Forecast",
    subtitle = "Conditional standard deviation from selected GJR-GARCH model",
    x = "Trading day forecast horizon",
    y = "Forecast volatility (%)",
    colour = NULL,
    caption = "Source: Author's GJR-GARCH estimates") +
  theme_minimal(base_size = 11) +
  theme(
    plot.title = element_text(face = "bold", size = 14, hjust = .5),
    plot.subtitle = element_text(size = 11, hjust = .5, margin = margin(b = 12)),
    axis.title.x = element_text(face = "bold", size = 9),
    axis.title.y = element_text(face = "bold", size = 9),
    axis.text.x = element_text(size = 9),
    axis.text.y = element_text(size = 9),
    legend.position = "top",
    legend.text = element_text(size = 9),
    panel.background = element_rect(fill = "grey96", colour = "grey70", linewidth = .6),
    panel.grid.major.y = element_line(colour = "white", linewidth = .7),
    panel.grid.minor.y = element_line(colour = "white", linewidth = .05),
    panel.grid.major.x = element_blank(),
    panel.grid.minor.x = element_blank())

12.7.3 Ten-Day Exchange-Rate Volatility Forecast

  • The 10-day GJR-GARCH forecast provides a forward looking picture of exchange rate risk, showing how volatile the NAD is expected to be against the three currencies over the next ten trading days.

  • NAD/USD is expected to remain the most volatile exchange rate, with forecast volatility remaining almost unchanged at approximately 0.864%. This suggests that USD related exchange rate risk is expected to remain elevated and persistent in the short term.

  • NAD/EUR volatility is forecast to increase slightly, from approximately 0.753% on day 1 to 0.767% by day 10. The gradual increase suggests a modest rise in expected uncertainty rather than a sudden volatility shock.

  • NAD/GBP shows a similar gradual increase, rising from approximately 0.725% to 0.739% over the forecast horizon. Despite this increase, it remains the least volatile of the three exchange rates.

  • The relatively smooth forecast paths indicate that no major surge in volatility is anticipated over the next ten trading days. Instead, the models expect current volatility conditions to persist and adjust gradually.

Overall, the forecast points to a relatively stable short-term foreign exchange environment, but with clear differences in risk: NAD/USD remains the primary source of volatility, followed by NAD/EUR and NAD/GBP.

13 Discussion

13.1 Main Findings

  • NAD/USD showed the highest overall volatility among the three exchange rates. This was evident from the larger return fluctuations, higher conditional volatility, and the highest short-term volatility forecast. The result suggests that movements in the US Dollar represent the greatest source of foreign exchange uncertainty for the Namibian Dollar.

  • Volatility clustering was present in all three currencies. The pre-estimation Ljung-Box and ARCH-LM tests strongly rejected the absence of volatility dependence. NAD/EUR produced the largest Ljung-Box statistic (609.17) and ARCH-LM statistic (273.42), indicating particularly strong clustering of volatility shocks.

  • Volatility persistence was very high across all currencies. The estimated persistence values were 0.9882 for NAD/USD, 0.9870 for NAD/GBP, and 0.9742 for NAD/EUR. Since these values are close to one, volatility shocks tend to disappear slowly rather than immediately.

  • The persistence results were also reflected in the estimated half-lives. A volatility shock required approximately 58.2 trading days for NAD/USD, 52.9 days for NAD/GBP, and 26.6 days for NAD/EUR to reduce by half. USD and GBP shocks therefore remained in the market considerably longer.

  • Asymmetric GARCH models generally performed better than the standard symmetric GARCH model. Based on AIC, BIC, Shibata and Hannan-Quinn criteria, the GJR-GARCH model provided the best overall fit for NAD/USD, NAD/EUR and NAD/GBP.

  • The significant asymmetry parameters indicate that positive and negative exchange rate shocks did not have equal effects on future volatility. The News Impact Curves showed that shocks associated with NAD depreciation generated stronger increases in conditional volatility than comparable appreciation shocks.

  • The preferred model was therefore GJR-GARCH for all three currencies, showing that incorporating both volatility persistence and asymmetric responses to exchange rate shocks improves the modelling of Namibia’s foreign exchange risk.

  • Post-estimation diagnostics showed that the models successfully removed residual autocorrelation for all currencies. However, remaining ARCH effects were detected for NAD/USD and NAD/GBP, while NAD/EUR showed no significant remaining ARCH effects. The model therefore fitted NAD/EUR particularly well, while some additional volatility dynamics remained unexplained for USD and GBP.

13.2 Economic Interpretation

  • Namibia operates under the Common Monetary Area (CMA), with the Namibia Dollar maintained at parity with the South African Rand. Consequently, movements of the NAD against major international currencies largely reflect movements of the Rand against those currencies. Namibia is therefore exposed not only to domestic developments but also to South African and international financial market conditions.

  • The periods of elevated volatility observed in the study are consistent with an exchange rate exposed to global financial uncertainty, commodity price movements, changes in international interest rates and episodes of risk aversion. Such shocks can rapidly influence currencies of small open economies such as Namibia.

  • The relatively high volatility of NAD/USD is economically important because the US Dollar plays a major role in global trade, commodity pricing and international financial transactions. Changes in global USD conditions can therefore transmit strongly into the Namibian Dollar.

  • Exchange rate depreciation creates import price risk. When the NAD weakens, Namibia requires more domestic currency to purchase the same amount of foreign goods. This can increase the local cost of imported fuel, machinery, vehicles, equipment, intermediate inputs and consumer goods, potentially contributing to inflationary pressures.

  • Namibian businesses with foreign currency obligations are consequently exposed to exchange rate risk. For example, an importer expecting to make a future USD payment may face a substantially higher Namibia Dollar cost if the currency depreciates before payment is made.

  • The persistence results strengthen this concern. Since volatility shocks, particularly for NAD/USD and NAD/GBP, can remain elevated for several weeks, businesses should not assume that exchange rate uncertainty disappears immediately after a major market disturbance.

  • The Value-at-Risk analysis reinforces this risk management perspective. NAD/USD recorded the largest downside risk, with a 95% VaR of approximately -1.48% and a 99% VaR of approximately -2.22%, compared with smaller estimated losses for NAD/EUR and NAD/GBP.

  • Structural break analysis also indicates that exchange rate risk is not constant through time. NAD/EUR shifted into a lower volatility regime after 26 March 2021, while NAD/GBP and NAD/USD entered lower volatility regimes after 16 October 2023 and 26 October 2023, respectively. The decline in both mean and median volatility after these breaks suggests a genuine moderation in typical market volatility.

  • From a economics perspective, these findings support the use of foreign exchange hedging and scenario analysis when managing international payments and foreign currency liabilities.

  • From a policy perspective, monitoring exchange rate volatility remains important because sustained depreciation and volatility can transmit into import costs, inflation and broader financial conditions. Since the NAD is linked to the South African Rand, developments in South Africa and international markets remain particularly relevant for Namibia’s exchange rate environment.

14 Conclusion

  1. Main descriptive findings.
    The Namibia Dollar showed a long-run depreciation against the US Dollar, Euro and British Pound between 2010 and 2026, although this trend was interrupted by periods of appreciation and relative stability. Daily returns generally fluctuated around zero, while periods of unusually large movements were concentrated in specific episodes. The three currency returns were also strongly positively correlated, with correlations exceeding 0.80, indicating substantial co-movement.

  2. Evidence of ARCH effects.
    The return series were stationary while the exchange rate levels were non-stationary. Jarque-Bera tests further showed that returns were non-normally distributed. Significant Ljung-Box statistics for squared returns and ARCH-LM tests confirmed the presence of volatility clustering and conditional heteroskedasticity, providing strong justification for the application of GARCH family models.

  3. Best model for each currency.
    Comparison of the standard GARCH, EGARCH and GJR-GARCH specifications showed that the GJR-GARCH model was preferred for NAD/USD, NAD/EUR and NAD/GBP. Its superior information criteria indicate that explicitly allowing exchange rate shocks to have asymmetric volatility effects improved model performance.

  4. Volatility persistence.
    Exchange rate volatility was found to be highly persistent. Persistence was highest for NAD/USD (0.9882), followed by NAD/GBP (0.9870) and NAD/EUR (0.9742). Consequently, volatility shocks dissipated slowly, particularly for USD and GBP, demonstrating that periods of exchange rate uncertainty can continue well beyond the initial shock.

  5. Forecast implications.
    Out-of-sample results showed that the GJR-GARCH models were capable of capturing the general evolution of realised volatility, although extreme volatility spikes were more difficult to forecast. NAD/GBP and NAD/EUR produced comparatively better forecast accuracy, while NAD/USD recorded larger forecast errors. The 10-day forecast nevertheless suggests a relatively stable short-term environment, with NAD/USD remaining the most volatile, followed by NAD/EUR and NAD/GBP.

  6. Study limitations.
    The analysis is limited to NAD/USD, NAD/EUR and NAD/GBP and therefore does not represent all of Namibia’s foreign exchange exposures. The models are univariate and do not explicitly incorporate macroeconomic variables such as interest rates, commodity prices, inflation, global risk indicators or South African financial conditions. The final 30-trading-day out-of-sample period is also relatively short. In addition, remaining ARCH effects in the NAD/USD and NAD/GBP models indicate that the selected GJR-GARCH specifications do not capture every aspect of their volatility dynamics.

  7. Recommendations for future research.
    Future studies could extend the analysis using multivariate GARCH models to examine volatility transmission and spillovers between currencies. Macroeconomic and financial variables such as South African interest rates, commodity prices, oil prices, the US Dollar Index and global riskmeasures could also be incorporated. Longer forecasting windows and alternative models such as GARCH-X, APARCH, stochastic volatility or regime switching models could be compared with the GJR-GARCH results. Particular attention could also be given to explaining the identified 2021 and 2023 structural breaks and assessing whether the lower volatility regimes remain persistent over time.