library(tidyverse)
library(lubridate)
library(scales)
library(ggrepel)
library(tidyquant)
library(jsonlite)
theme_set(theme_minimal())
invisible(Sys.setlocale("LC_TIME", "en_US.UTF-8"))
Both traded on the SIX Swiss Exchange.
library(familyoffice)
world_usd <- fo_get("IE00B4L5Y983", from = as.Date("2015-01-01"), currency = "USD")
world_chf_hedged <- fo_get("IE00B8BVCK12", from = as.Date("2015-01-01"))
world_etfs <- bind_rows(world_usd |> transmute(etf = "iShares Core MSCI World USD", date, close = close, currency = "USD"),
world_chf_hedged |> transmute(etf = "iShares MSCI World CHF Hedged", date, close = close, currency = "CHF"))
world_etfs |> saveRDS("data/currencyhedging/world_etfs.RDS")
usd <- tq_get(c("USDCHF=X"), from = as.Date("2015-01-01"))
usd <- usd |>
transmute(currency = str_remove(symbol, "CHF=X"), date, rate = close)
usd |> saveRDS("data/currencyhedging/usd.RDS")
world_etfs <- readRDS("data/currencyhedging/world_etfs.RDS")
usd <- readRDS("data/currencyhedging/usd.RDS")
world_etfs |>
group_by(etf) |>
mutate(p = close / first(close)-1) |>
ggplot(aes(date, p, color = etf)) +
geom_line() +
scale_y_continuous(labels = scales::percent, breaks = 0:50/5) +
scale_color_manual(values = c("blue", "red")) +
labs(title = "Performance of MSCI World ETFs",
subtitle = "Total Return (each in its own currency)",
x = NULL,
y = "Performance",
color = NULL) +
theme(legend.position = "bottom")
Now both in CHF. Should the hedged ETF outperform? After all USD-denominated index has outperformed due to the USD gaining against the CHF (around +8% over the past 10 years).
world_etfs_fx <- world_etfs |>
left_join(usd, by = c("date", "currency")) |>
group_by(etf) |>
mutate(rate = na.locf0(rate)) |>
ungroup() |>
mutate(rate = ifelse(currency == "CHF", 1, rate)) |>
mutate(close_chf = close * rate)
world_etfs_fx |>
group_by(etf) |>
mutate(p_chf = close_chf / first(close_chf)-1) |>
ggplot(aes(date, p_chf, color = etf)) +
geom_line() +
scale_y_continuous(labels = scales::percent, breaks = 0:50/5) +
scale_color_manual(values = c("blue", "red")) +
labs(title = "Performance of ETFs",
subtitle = "MSCI World ETFs (converted to CHF)",
x = NULL,
y = "Performance",
color = NULL) +
theme(legend.position = "bottom")
Actually, the hedged ETF still underperformed. Let’s have a look at annual returns.
annual_comp <- world_etfs_fx |>
group_by(etf) |>
mutate(r_chf = close_chf/lag(close_chf)-1,
r = close/lag(close)-1,
fx = rate/lag(rate)-1) |>
drop_na() |>
filter(year(date) %in% 2015:2024) |>
group_by(etf, year = year(date)) |>
summarize(r_chf = sum(r_chf),
r = sum(r),
fx = sum(fx)) |>
pivot_wider(names_from = etf, values_from = c(r_chf, r, fx)) |>
select(-c(`r_chf_iShares MSCI World CHF Hedged`, `fx_iShares MSCI World CHF Hedged`, `r_iShares Core MSCI World USD`)) |>
mutate(excess_r_hedged = `r_iShares MSCI World CHF Hedged` - `r_chf_iShares Core MSCI World USD`) |>
rename(chg_fx = `fx_iShares Core MSCI World USD`)
annual_comp |>
# all as percent and kable table
mutate(year = as.character(year)) |>
mutate_if(is.numeric, percent, accuracy = 0.1) |>
knitr::kable()
year | r_chf_iShares Core MSCI World USD | r_iShares MSCI World CHF Hedged | chg_fx | excess_r_hedged |
---|---|---|---|---|
2015 | 4.3% | 4.7% | 0.7% | 0.3% |
2016 | 10.0% | 6.8% | 2.8% | -3.3% |
2017 | 16.8% | 15.2% | -3.6% | -1.5% |
2018 | -7.7% | -10.7% | 1.0% | -2.9% |
2019 | 24.4% | 22.6% | -1.2% | -1.8% |
2020 | 9.4% | 14.6% | -9.4% | 5.3% |
2021 | 24.5% | 21.8% | 3.5% | -2.7% |
2022 | -17.2% | -19.1% | 1.5% | -1.9% |
2023 | 13.7% | 17.3% | -8.7% | 3.7% |
2024 | 25.1% | 16.3% | 7.0% | -8.8% |
annual_comp |>
ggplot(aes(x = chg_fx, y = excess_r_hedged)) +
geom_hline(yintercept = 0, linetype = 2, color = "red") +
geom_vline(xintercept = 0, linetype = 2, color = "red") +
geom_point() +
geom_text_repel(aes(label = year), box.padding = 0.5) +
labs(title = "Annual Performance of MSCI World ETFs",
subtitle = "Excess Return of Hedged ETF vs. Unhedged ETF",
x = "Change in USD/CHF Exchange Rate",
y = "Excess Return",
caption = "Source: Yahoo Finance") +
scale_x_continuous(labels = scales::percent, breaks = seq(-0.1, 0.1, 0.02)) +
scale_y_continuous(labels = scales::percent, breaks = seq(-0.1, 0.1, 0.02)) +
theme(legend.position = "bottom")
As expected, the hedged ETF outperforms when the USD loses against the CHF. However, the hedged ETF underperforms when the USD gains against the CHF. The hedged ETF is a bet on the USD losing against the CHF.
But the hedge is far from efficient: only in 2020 and in 2023 the hedged ETF outperformed the unhedged ETF. In both years considerably less than the USD gained against the CHF (2020: USD +9.5%, hedging excess +5%, 2023: USD +9%, hedging excess +3.5%). The currency hedging comes at a price.
Let’s focus on a more brutal currency pair: JPY/CHF with two ETFs tracking the Nikkei 400:
Both are traded on the SIX.
nikkei_jpy <- fo_get("LU1681039050", from = as.Date("2015-01-01"), currency = "JPY")
nikkei_chf_hedged <- fo_get("LU1681047665", from = as.Date("2015-01-01"))
nikkei_etfs <- bind_rows(nikkei_jpy |> transmute(etf = "Amundi JPX Nikkei 400 JPY", date, close = close, currency = "JPY"),
nikkei_chf_hedged |> transmute(etf = "Amundi ETF JPX Nikkei 400 CHF Hedged", date, close = close, currency = "CHF"))
nikkei_etfs |> saveRDS("data/currencyhedging/nikkei_etfs.RDS")
jpy <- tq_get(c("JPYCHF=X"), from = as.Date("2015-01-01"))
jpy <- jpy |>
transmute(currency = str_remove(symbol, "CHF=X"), date, rate = close)
jpy |> saveRDS("data/currencyhedging/jpy.RDS")
nikkei_etfs <- readRDS("data/currencyhedging/nikkei_etfs.RDS")
jpy <- readRDS("data/currencyhedging/jpy.RDS")
Since 2018-03-22.
The Yen has lost against the CHF. The hedged ETF should outperform.
jpy |>
ggplot(aes(date, rate)) +
geom_vline(xintercept = d_min, linetype = 2, color = "red") +
geom_line() +
familyoffice::geom_drawdowns(downs = 1, text_up = 0.0002) +
labs(title = "JPY/CHF Exchange Rate",
subtitle = "JPY losing against CHF",
x = NULL,
y = "Exchange Rate") +
theme(legend.position = "bottom")
nikkei_etfs_fx <- nikkei_etfs |>
left_join(jpy, by = c("date", "currency")) |>
group_by(etf) |>
mutate(rate = na.locf0(rate)) |>
ungroup() |>
mutate(rate = ifelse(currency == "CHF", 1, rate)) |>
mutate(close_chf = close * rate)
nikkei_etfs_fx |>
group_by(etf) |>
mutate(p_chf = close_chf / first(close_chf)-1) |>
ggplot(aes(date, p_chf, color = etf)) +
geom_line() +
scale_y_continuous(labels = scales::percent, breaks = 0:50/5) +
scale_color_manual(values = c("blue", "red")) +
labs(title = "Performance of ETFs",
subtitle = "Nikkei 400 ETFs (converted to CHF)",
x = NULL,
y = "Performance",
color = NULL) +
theme(legend.position = "bottom")
Since early 2018 (when the ETFs were launched) the hedged ETF outperformed the unhedged ETF. The unhedged ETF is up just short of 20% (when converted to CHF), while the CHF-hedged ETF is up almost 80%.
The ETF in JPY is up 83%, so not much more than the hedged ETF is in CHF.
This makes seem that the hedged ETF was quite efficient: it delivered the Japanese return but in Swiss francs at a minimal cost (just about 5 percentage points over almost 6 years). How is this possible?
The reason must lie in the interest rate differential. Both currencies (the JPY and the CHF) saw low rates, reducing the cost of the hedge.
rate_us <- tq_get("IR3TIB01USM156N", get = 'economic.data', from = as.Date("2015-01-01"))
rate_ch <- tq_get("IR3TIB01CHM156N", get = 'economic.data', from = as.Date("2015-01-01"))
rate_jp <- tq_get("IR3TIB01JPM156N", get = 'economic.data', from = as.Date("2015-01-01"))
rates <- bind_rows(rate_us |> transmute(currency = "USD", date, rate = price/100),
rate_ch |> transmute(currency = "CHF", date, rate = price/100),
rate_jp |> transmute(currency = "JPY", date, rate = price/100))
rates |> saveRDS("data/currencyhedging/rates.RDS")
rates <- readRDS("data/currencyhedging/rates.RDS")
rates |>
ggplot(aes(date, rate, color = currency)) +
geom_line() +
scale_y_continuous(labels = scales::percent, breaks = seq(0, 0.1, 0.02)) +
labs(title = "Interest Rates",
subtitle = "3-Month or 90-Day Rates and Yields: Interbank Rates",
x = NULL,
y = "Rate") +
theme(legend.position = "bottom")
So expecting the Swiss franc to remain strong, that means other currencies will lose versus the CHF, should a Swiss investor chose hedged foreign ETFs?
The thing is that one will not make money if the appreciation is expected. The interest differential is the key to the cost of the hedge. The hedged ETFs are a bet on the interest rate differential between the two currencies. The cost of the hedge is the difference between the two interest rates.
eurchf <- "
Kontraktdatum Eröffnung Hoch Tief Geld Vol. Geldkurs Briefkurs Brief Vol. +/- Letzter Preis Letzter Trade Tägl. Abrechnungspreis Gehandelte Kontrakte Open Interest
17.02.2025 0,93704 0,93704 0,93704 - - - - 0,41% 0,93704 16:20 14.01. 0,93704 - -
17.03.2025 0,9356 0,93678 0,9356 - - - - 0,48% 0,93628 21:23 14.01. 0,93556 15 2.001
14.04.2025 0,93392 0,93392 0,93392 - - - - 0,40% 0,93392 16:20 14.01. 0,93392 - -
19.05.2025 0,93186 0,93186 0,93186 - - - - 0,40% 0,93186 16:20 14.01. 0,93186 - -
16.06.2025 0,93032 0,93032 0,93032 - - - - 0,42% 0,93032 16:20 14.01. 0,93032 - -
14.07.2025 0,92859 0,92859 0,92859 - - - - 0,40% 0,92859 16:20 14.01. 0,92859 - -
18.08.2025 0,92662 0,92662 0,92662 - - - - 0,40% 0,92662 16:20 14.01. 0,92662 - -
15.09.2025 0,92507 0,92507 0,92507 - - - - 0,40% 0,92507 16:20 14.01. 0,92507 - -
13.10.2025 0,92356 0,92356 0,92356 - - - - 0,41% 0,92356 16:20 14.01. 0,92356 - -
17.11.2025 0,92163 0,92163 0,92163 - - - - 0,41% 0,92163 16:20 14.01. 0,92163 - -
15.12.2025 0,92011 0,92011 0,92011 - - - - 0,41% 0,92011 16:20 14.01. 0,92011 - -
19.01.2026 0,91821 0,91821 0,91821 - - - - 0,41% 0,91821 16:20 14.01. 0,91821 - -
16.02.2026 0,91665 0,91665 0,91665 - - - - 0,40% 0,91665 16:20 14.01. 0,91665 - -
16.03.2026 0,9151 0,9151 0,9151 - - - - 0,40% 0,9151 16:20 14.01. 0,9151 - -
13.04.2026 0,91356 0,91356 0,91356 - - - - - 0,91356 16:20 14.01. 0,91356 - -
15.06.2026 0,91011 0,91011 0,91011 - - - - 0,40% 0,91011 16:20 14.01. 0,91011 - -
14.09.2026 0,90529 0,90529 0,90529 - - - - 0,40% 0,90529 16:20 14.01. 0,90529 - -
14.12.2026 0,90064 0,90064 0,90064 - - - - 0,39% 0,90064 16:20 14.01. 0,90064 - -
14.06.2027 0,89164 0,89164 0,89164 - - - - 0,40% 0,89164 16:20 14.01. 0,89164 - -
13.12.2027 0,88308 0,88308 0,88308 - - - - 0,41% 0,88308 16:20 14.01. 0,88308"
eurchf <- read_table2(eurchf, col_types = "cccccccccccc") |>
transmute(fdate = dmy(`Kontraktdatum`),
price = `Eröffnung` |> str_replace(",", ".") |> as.numeric())
eurchf |>
ggplot(aes(fdate, price)) +
geom_line() +
geom_point() +
labs(title = "EUR/CHF Futures",
subtitle = "Forward exchange rate",
x = NULL,
y = "Price") +
theme(legend.position = "bottom")
EUR/CHF is expected to be at 0.88308 in December 2027. The current spot rate is 0.94. That means that over three years, the EUR is expected to lose against the CHF by -6.1%.
The hedging will not make money if the appreciation is expected. So if the EUR/CHF loses more than the expected (the forward rate), the hedge will make money (but with some threshold as the hedging also comes at a cost).
usdchf <- "
Date Open High Low Bid Vol Bid Ask Ask Vol +/- Last Last traded D. Settle Volume OI adj
17/02/2025 0.91283 0.91283 0.91283 - - - - -0.03% 0.91283 16:20 14/01 0.91283 - -
17/03/2025 0.90998 0.90776 0.90776 21 0.90656 0.90668 21 -0.28% 0.90776 17:28 14/01 0.90998 21 9
14/04/2025 0.90701 0.90701 0.90701 - - - - -0.10% 0.90701 16:20 14/01 0.90701 - -
19/05/2025 0.90384 0.90384 0.90384 - - - - -0.05% 0.90384 16:20 14/01 0.90384 - -
16/06/2025 0.90038 0.90038 0.90038 - - - - -0.03% 0.90038 16:20 14/01 0.90038 - 1
14/07/2025 0.89774 0.89774 0.89774 - - - - -0.06% 0.89774 16:20 14/01 0.89774 - -
18/08/2025 0.89411 0.89411 0.89411 - - - - -0.05% 0.89411 16:20 14/01 0.89411 - -
15/09/2025 0.89121 0.89121 0.89121 - - - - -0.05% 0.89121 16:20 14/01 0.89121 - -
13/10/2025 0.88832 0.88832 0.88832 - - - - -0.04% 0.88832 16:20 14/01 0.88832 - -
17/11/2025 0.88467 0.88467 0.88467 - - - - -0.05% 0.88467 16:20 14/01 0.88467 - -
15/12/2025 0.88175 0.88175 0.88175 - - - - -0.05% 0.88175 16:20 14/01 0.88175 - 850
19/01/2026 0.8781 0.8781 0.8781 - - - - -0.05% 0.8781 16:20 14/01 0.8781 - -
16/02/2026 0.87524 0.87524 0.87524 - - - - -0.05% 0.87524 16:20 14/01 0.87524 - -
16/03/2026 0.8724 0.8724 0.8724 - - - - -0.05% 0.8724 16:20 14/01 0.8724 - -
13/04/2026 0.86956 0.86956 0.86956 - - - - - 0.86956 16:20 14/01 0.86956 - -
15/06/2026 0.8632 0.8632 0.8632 - - - - -0.05% 0.8632 16:20 14/01 0.8632 - -
14/09/2026 0.85418 0.85418 0.85418 - - - - -0.06% 0.85418 16:20 14/01 0.85418 - -
14/12/2026 0.84532 0.84532 0.84532 - - - - -0.06% 0.84532 16:20 14/01 0.84532 - -
14/06/2027 0.82838 0.82838 0.82838 - - - - -0.05% 0.82838 16:20 14/01 0.82838 - -
13/12/2027 0.81217 0.81217 0.81217 - - - - -0.04% 0.81217 16:20 14/01 0.81217 - -"
usdchf <- read_table2(usdchf, col_types = "cccccccccccc") |>
transmute(fdate = dmy(`Date`),
price = `Open` |> str_replace(",", ".") |> as.numeric())
jpychf <- "
Date Open High Low Bid Vol Bid Ask Ask Vol +/- Last Last traded D. Settle Volume OI adj
17/02/2025 161.355 161.355 161.355 - - - - 0.81% 161.355 16:20 14/01 161.355 - -
17/03/2025 160.832 161.80 160.832 21 161.881 161.899 21 1.29% 161.80 16:39 14/01 161.044 6 6,168
14/04/2025 160.756 160.756 160.756 - - - - 0.77% 160.756 16:20 14/01 160.756 - -
19/05/2025 160.468 160.468 160.468 - - - - 0.81% 160.468 16:20 14/01 160.468 - -
16/06/2025 160.169 160.169 160.169 - - - - 0.82% 160.169 16:20 14/01 160.169 - -
14/07/2025 159.965 159.965 159.965 - - - - 0.81% 159.965 16:20 14/01 159.965 - -
18/08/2025 159.669 159.669 159.669 - - - - 0.81% 159.669 16:20 14/01 159.669 - -
15/09/2025 159.446 159.446 159.446 - - - - 0.80% 159.446 16:20 14/01 159.446 - -
13/10/2025 159.234 159.234 159.234 - - - - 0.80% 159.234 16:20 14/01 159.234 - -
17/11/2025 158.956 158.956 158.956 - - - - 0.80% 158.956 16:20 14/01 158.956 - -
15/12/2025 158.742 158.742 158.742 - - - - 0.80% 158.742 16:20 14/01 158.742 - -
19/01/2026 158.481 158.481 158.481 - - - - 0.80% 158.481 16:20 14/01 158.481 - -
16/02/2026 158.256 158.256 158.256 - - - - 0.80% 158.256 16:20 14/01 158.256 - -
16/03/2026 158.035 158.035 158.035 - - - - 0.80% 158.035 16:20 14/01 158.035 - -
13/04/2026 157.817 157.817 157.817 - - - - - 157.817 16:20 14/01 157.817 - -
15/06/2026 157.341 157.341 157.341 - - - - 0.78% 157.341 16:20 14/01 157.341 - -
14/09/2026 156.666 156.666 156.666 - - - - 0.78% 156.666 16:20 14/01 156.666 - -
14/12/2026 156.012 156.012 156.012 - - - - 0.77% 156.012 16:20 14/01 156.012 - -
14/06/2027 154.706 154.706 154.706 - - - - 0.76% 154.706 16:20 14/01 154.706 - -
13/12/2027 153.456 153.456 153.456 - - - - 0.74% 153.456 16:20 14/01 153.456 - -
"
jpychf <- read_table2(jpychf, col_types = "cccccccccccc") |>
transmute(fdate = dmy(`Date`),
price = `Open` |> str_replace(",", ".") |> as.numeric())
gbpchf <- "
Date Open High Low Bid Vol Bid Ask Ask Vol +/- Last Last traded D. Settle Volume OI adj
17/02/2025 1.10878 1.10878 1.10878 - - - - 0.09% 1.10878 16:20 14/01 1.10878 - -
17/03/2025 1.10548 1.10548 1.10548 10 1.10503 1.1053 10 0.06% 1.10548 16:20 14/01 1.10548 - -
14/04/2025 1.10198 1.10198 1.10198 - - - - 0.07% 1.10198 16:20 14/01 1.10198 - -
19/05/2025 1.09731 1.09731 1.09731 - - - - 0.07% 1.09731 16:20 14/01 1.09731 - -
16/06/2025 1.09327 1.09327 1.09327 10 1.09277 1.09307 10 0.05% 1.09327 16:20 14/01 1.09327 - -
14/07/2025 1.08956 1.08956 1.08956 - - - - 0.06% 1.08956 16:20 14/01 1.08956 - -
18/08/2025 1.08497 1.08497 1.08497 - - - - 0.06% 1.08497 16:20 14/01 1.08497 - -
15/09/2025 1.08132 1.08132 1.08132 - - - - 0.06% 1.08132 16:20 14/01 1.08132 - -
13/10/2025 1.07768 1.07768 1.07768 - - - - 0.06% 1.07768 16:20 14/01 1.07768 - -
17/11/2025 1.07313 1.07313 1.07313 - - - - 0.07% 1.07313 16:20 14/01 1.07313 - -
15/12/2025 1.0695 1.0695 1.0695 - - - - 0.07% 1.0695 16:20 14/01 1.0695 - -
19/01/2026 1.06497 1.06497 1.06497 - - - - 0.07% 1.06497 16:20 14/01 1.06497 - -
16/02/2026 1.06135 1.06135 1.06135 - - - - 0.07% 1.06135 16:20 14/01 1.06135 - -
16/03/2026 1.05773 1.05773 1.05773 - - - - 0.07% 1.05773 16:20 14/01 1.05773 - -
13/04/2026 1.05413 1.05413 1.05413 - - - - - 1.05413 16:20 14/01 1.05413 - -
15/06/2026 1.04607 1.04607 1.04607 - - - - 0.07% 1.04607 16:20 14/01 1.04607 - -
14/09/2026 1.03461 1.03461 1.03461 - - - - 0.08% 1.03461 16:20 14/01 1.03461 - -
14/12/2026 1.02336 1.02336 1.02336 - - - - 0.09% 1.02336 16:20 14/01 1.02336 - -
14/06/2027 1.00188 1.00188 1.00188 - - - - 0.10% 1.00188 16:20 14/01 1.00188 - -
13/12/2027 0.98135 0.98135 0.98135 - - - - 0.12% 0.98135 16:20 14/01 0.98135 - -
"
gbpchf <- read_table2(gbpchf, col_types = "cccccccccccc") |>
transmute(fdate = dmy(`Date`),
price = `Open` |> str_replace(",", ".") |> as.numeric())
forwardrates <- bind_rows(eurchf |> rename(rate = price) |> mutate(currency = "EUR/CHF"),
usdchf |> rename(rate = price) |> mutate(currency = "USD/CHF"),
jpychf |> rename(rate = price) |> mutate(currency = "JPY/CHF"),
gbpchf |> rename(rate = price) |> mutate(currency = "GBP/CHF"))
forwardrates |>
group_by(currency) |>
mutate(fchg = rate/first(rate)-1) |>
ggplot(aes(fdate, fchg, color = currency)) +
geom_line() +
scale_y_continuous(labels = scales::percent, breaks = seq(-0.1, 0.1, 0.02)) +
scale_color_manual(values = c("blue", "red", "green", "purple")) +
labs(title = "Forward Exchange Rates",
subtitle = "Change in Forward Rates",
x = NULL,
y = "Change") +
theme(legend.position = "bottom")
Versus the CHF
Swiss short term interest is at 0.5%.
So if you buy a CHF-hedged US equity market ETF, you are betting on the USD losing against the CHF by more than the market expects.
Same logic for the other currencies.
Hedged stock ETFs are a bet on the other currency depreciating more than the market expects.