1 1. Objective

This document develops a first exploratory framework for analysing Swiss real-estate operating companies (REOCs) at the firm-year level.

The main question is:

How does each REOC develop over time, and which financial and market variables are most informative for describing that development?

The analysis uses the two cleaned input files:

  • swiss_reoc_yearly_FA.csv: annual fundamental/accounting and valuation variables.
  • swiss_reoc_monthly_HP.csv: monthly market variables and returns.

The first version deliberately focuses on descriptive analysis. It does not yet make causal claims.

2 2. Packages and data

library(tidyverse)
library(janitor)
library(lubridate)
library(knitr)
library(scales)

theme_set(theme_minimal())

fa <- read_csv("../Dataset 1 excel/swiss_reoc_yearly_FA.csv", show_col_types = FALSE) |>
  clean_names()


hp <- read_csv("../Dataset 1 excel/swiss_reoc_monthly_HP.csv", show_col_types = FALSE) |>
  clean_names()

fa <- fa |>
  mutate(year = as.integer(year))

hp <- hp |>
  mutate(
    date = as.Date(date),
    year = year(date)
  )

3 3. Initial data audit

3.1 3.1 Dataset structure

data_overview <- tibble(
  dataset = c("Annual fundamentals", "Monthly market data"),
  rows = c(nrow(fa), nrow(hp)),
  firms = c(n_distinct(fa$ticker), n_distinct(hp$ticker)),
  first_date_or_year = c(min(fa$year, na.rm = TRUE), min(hp$date, na.rm = TRUE)),
  last_date_or_year = c(max(fa$year, na.rm = TRUE), max(hp$date, na.rm = TRUE))
)

kable(data_overview)
dataset rows firms first_date_or_year last_date_or_year
Annual fundamentals 214 11 2001 2027
Monthly market data 2224 11 10987 20573

The annual dataset contains one observation per firm-year, while the monthly dataset contains one observation per firm-month.

duplicates <- tibble(
  annual_duplicate_ticker_year = sum(duplicated(fa[c("ticker", "year")])),
  monthly_duplicate_ticker_date = sum(duplicated(hp[c("ticker", "date")]))
)

kable(duplicates)
annual_duplicate_ticker_year monthly_duplicate_ticker_date
0 0

3.2 3.2 Firm coverage

firm_coverage <- fa |>
  group_by(ticker) |>
  summarise(
    first_year = min(year, na.rm = TRUE),
    last_year = max(year, na.rm = TRUE),
    n_years = n(),
    .groups = "drop"
  ) |>
  arrange(first_year, ticker)

kable(firm_coverage)
ticker first_year last_year n_years
ALLN 2001 2027 27
ISN 2001 2027 27
PSPN 2001 2027 27
SPSN 2001 2027 27
MOBN 2004 2027 24
ZUGEST 2011 2027 17
ZUGN 2011 2027 17
HIAG 2012 2027 16
IREN 2013 2027 15
EPIC 2019 2027 9
CHAM 2020 2027 8

This matters because the panel is unbalanced: some REOCs are observed for much longer than others. Therefore, changes in the aggregate sample can reflect both genuine firm development and the entry of additional firms.

4 4. Which variables are most interesting?

The variables naturally fall into four groups.

4.1 4.1 Core financial development variables

These should be the main variables for the firm-year analysis:

  1. Total assets (fa_total_assets) — captures firm scale and balance-sheet growth.
  2. Net income (net_income) — captures profitability in absolute terms.
  3. Dividend per share (dividend_per_share) — captures shareholder distribution.
  4. Debt-to-assets (debt_to_assets) — captures balance-sheet leverage.
  5. Net debt / EBITDA (net_debt_ebitda) — captures debt burden relative to operating earnings.

These variables together give a useful picture of growth, profitability, shareholder distributions and financial risk.

4.2 4.2 Market variables

From the monthly dataset, the most useful variables are:

  1. Market capitalization (market_cap) — firm size from the market’s perspective.
  2. Price (price) — share-price development.
  3. Total return index (total_return_index) — preferred for measuring investor performance.
  4. Monthly return (monthly_return) — used to construct annual returns.

The total return index is especially important because it captures a broader investor return measure than price alone.

4.3 4.3 Secondary valuation/profitability variables

The annual file also contains:

  • dividend_yield
  • pe_ratio
  • profit_margin

These are potentially interesting, but they should not be treated as core variables in the first analysis because their availability is much more limited in the supplied data. We therefore report their coverage first and use them only where observations are sufficiently available.

key_vars <- c(
  "fa_total_assets", "net_income", "dividend_per_share",
  "debt_to_assets", "net_debt_ebitda",
  "dividend_yield", "pe_ratio", "profit_margin"
)

missingness <- fa |>
  summarise(across(all_of(key_vars), ~ mean(is.na(.)))) |>
  pivot_longer(everything(), names_to = "variable", values_to = "missing_share") |>
  mutate(
    available_share = 1 - missing_share,
    missing_share = scales::percent(missing_share, accuracy = 0.1),
    available_share = scales::percent(available_share, accuracy = 0.1)
  ) |>
  arrange(desc(as.numeric(gsub("%", "", missing_share))))

kable(missingness)
variable missing_share available_share
dividend_yield 88.3% 11.7%
pe_ratio 86.4% 13.6%
profit_margin 84.6% 15.4%
net_income 13.6% 86.4%
net_debt_ebitda 12.1% 87.9%
fa_total_assets 10.3% 89.7%
dividend_per_share 10.3% 89.7%
debt_to_assets 10.3% 89.7%

Interpretation: the first analysis should concentrate on assets, net income, dividends, leverage and net debt/EBITDA. Valuation variables such as P/E and dividend yield should be treated as secondary because they are sparse in this extract.

5 5. Annual firm-year dataset

The core analytical object is one row per firm-year.

We add year-on-year changes for the main variables.

firm_year <- fa |>
  select(
    ticker, year,
    fa_total_assets,
    net_income,
    dividend_per_share,
    debt_to_assets,
    net_debt_ebitda,
    dividend_yield,
    pe_ratio,
    profit_margin
  ) |>
  arrange(ticker, year) |>
  group_by(ticker) |>
  mutate(
    assets_yoy = fa_total_assets / lag(fa_total_assets) - 1,
    net_income_yoy = net_income / lag(net_income) - 1,
    dividend_yoy = dividend_per_share / lag(dividend_per_share) - 1,
    debt_to_assets_change = debt_to_assets - lag(debt_to_assets),
    net_debt_ebitda_change = net_debt_ebitda - lag(net_debt_ebitda)
  ) |>
  ungroup()

The year-on-year variables are useful because levels answer “how large/profitable is the firm?”, while changes answer “how quickly is the firm changing?”

6 6. Firm development: one firm at a time

A useful first visualisation is to plot the main variables for every firm.

6.1 6.1 Total assets

ggplot(
  firm_year |> filter(year <= 2025),
  aes(x = year, y = fa_total_assets, group = ticker)
) +
  geom_line(na.rm = TRUE) +
  facet_wrap(~ ticker, scales = "free_y") +
  labs(
    title = "Development of total assets by REOC",
    x = NULL,
    y = "Total assets"
  )

This is the main measure of firm scale and growth. Because firms differ substantially in size, scales = "free_y" is used to make within-firm trajectories visible.

6.2 6.2 Net income

ggplot(
  firm_year |> filter(year <= 2025),
  aes(x = year, y = net_income, group = ticker)
) +
  geom_line(na.rm = TRUE) +
  facet_wrap(~ ticker, scales = "free_y") +
  labs(
    title = "Development of net income by REOC",
    x = NULL,
    y = "Net income"
  )

Net income should be interpreted together with assets. A firm can grow its asset base without generating proportionally stronger earnings.

6.3 6.3 Dividend per share

ggplot(
  firm_year |> filter(year <= 2025),
  aes(x = year, y = dividend_per_share, group = ticker)
) +
  geom_line(na.rm = TRUE) +
  facet_wrap(~ ticker, scales = "free_y") +
  labs(
    title = "Development of dividend per share by REOC",
    x = NULL,
    y = "Dividend per share"
  )

Dividend per share is useful for identifying firms with stable, growing or volatile shareholder distributions.

6.4 6.4 Leverage

ggplot(
  firm_year |> filter(year <= 2025),
  aes(x = year, y = debt_to_assets, group = ticker)
) +
  geom_line(na.rm = TRUE) +
  facet_wrap(~ ticker, scales = "free_y") +
  labs(
    title = "Debt-to-assets by REOC",
    x = NULL,
    y = "Debt-to-assets"
  )

Leverage should be considered jointly with growth and profitability. Increasing leverage can support expansion, but persistent increases can also indicate greater financial risk.

7 7. Firm-year growth rates

For comparing firms of different sizes, growth rates are often more informative than levels.

growth_summary <- firm_year |>
  filter(year >= 2005, year <= 2025) |>
  group_by(ticker) |>
  summarise(
    avg_asset_growth = mean(assets_yoy, na.rm = TRUE),
    median_asset_growth = median(assets_yoy, na.rm = TRUE),
    avg_income_growth = mean(net_income_yoy, na.rm = TRUE),
    median_income_growth = median(net_income_yoy, na.rm = TRUE),
    avg_dividend_growth = mean(dividend_yoy, na.rm = TRUE),
    .groups = "drop"
  )

kable(
  growth_summary |>
    mutate(across(where(is.numeric), ~ round(.x, 3)))
)
ticker avg_asset_growth median_asset_growth avg_income_growth median_income_growth avg_dividend_growth
ALLN 0.085 0.070 0.138 0.023 0.025
CHAM 0.469 0.221 1.548 0.083 NaN
EPIC 0.074 0.066 0.345 0.097 0.774
HIAG 0.084 0.066 0.138 0.115 Inf
IREN 0.111 0.120 0.101 0.030 0.047
ISN 0.046 0.032 0.093 0.045 0.086
MOBN 0.118 0.086 0.212 0.156 Inf
PSPN 0.068 0.069 0.062 0.038 Inf
SPSN 0.118 0.071 0.131 0.053 Inf
ZUGEST 0.078 0.073 0.112 0.066 Inf
ZUGN 0.078 0.073 0.112 0.066 Inf

Large year-on-year changes should be inspected individually rather than automatically interpreted as structural growth. They can arise from acquisitions, disposals, restructurings, one-off gains/losses or changes in the underlying accounting data.

8 8. Long-run firm development

A compact way to describe each firm is to compare its first and last available historical observation.

For comparability, the initial version uses 2001–2025 as the historical window. The 2026–2027 observations in the annual file are not treated as ordinary historical observations because coverage is incomplete and the monthly market dataset currently ends in April 2026.

long_run <- firm_year |>
  filter(year <= 2025) |>
  group_by(ticker) |>
  summarise(
    first_year = min(year[!is.na(fa_total_assets)]),
    last_year = max(year[!is.na(fa_total_assets)]),
    assets_start = first(fa_total_assets[!is.na(fa_total_assets)]),
    assets_end = last(fa_total_assets[!is.na(fa_total_assets)]),
    net_income_start = first(net_income[!is.na(net_income)]),
    net_income_end = last(net_income[!is.na(net_income)]),
    dividend_start = first(dividend_per_share[!is.na(dividend_per_share)]),
    dividend_end = last(dividend_per_share[!is.na(dividend_per_share)]),
    .groups = "drop"
  ) |>
  mutate(
    asset_cagr = (assets_end / assets_start)^(1 / (last_year - first_year)) - 1,
    net_income_cagr = (net_income_end / net_income_start)^(1 / (last_year - first_year)) - 1,
    dividend_cagr = if_else(
      dividend_start > 0 & dividend_end > 0,
      (dividend_end / dividend_start)^(1 / (last_year - first_year)) - 1,
      NA_real_
    )
  )

kable(
  long_run |>
    mutate(
      asset_cagr = scales::percent(asset_cagr, accuracy = 0.1),
      net_income_cagr = scales::percent(net_income_cagr, accuracy = 0.1),
      dividend_cagr = scales::percent(dividend_cagr, accuracy = 0.1)
    )
)
ticker first_year last_year assets_start assets_end net_income_start net_income_end dividend_start dividend_end asset_cagr net_income_cagr dividend_cagr
ALLN 2001 2025 907.4115 6441.653 96.5834 466.1904 2.6886 3.7364 8.5% 6.8% 1.4%
CHAM 2020 2025 355.0206 1904.658 19.1340 150.0616 0.0000 0.0000 39.9% 51.0% NA
EPIC 2019 2025 1208.5584 1848.726 54.7842 70.0257 0.7193 3.4161 7.3% 4.2% 29.6%
HIAG 2012 2025 817.8782 2276.056 36.2856 177.8420 0.0000 3.9499 8.2% 13.0% NA
IREN 2013 2025 724.7767 2454.255 48.9348 85.2006 2.1562 3.2026 10.7% 4.7% 3.4%
ISN 2001 2025 823.8530 1941.161 59.4277 96.0137 2.5830 6.4052 3.6% 2.0% 3.9%
MOBN 2004 2025 498.1389 4768.024 18.5444 205.9725 0.0000 10.9422 11.4% 12.1% NA
PSPN 2001 2025 1621.3952 10929.104 105.5660 376.5285 4.2757 4.2167 8.3% 5.4% -0.1%
SPSN 2001 2025 1178.3410 15780.759 72.6389 592.4642 0.0000 3.7364 11.4% 9.1% NA
ZUGEST 2011 2025 730.1436 2048.200 40.3714 97.6811 0.0000 196.1159 7.6% 6.5% NA
ZUGN 2011 2025 730.1436 2048.200 40.3714 97.6811 0.0000 196.1159 7.6% 6.5% NA

The long-run table is a descriptive ranking, not a performance league table. Firms have different starting dates and business histories.

9 9. Cross-sectional development by year

The next question is how the typical REOC changed over time.

Because the panel is unbalanced, it is useful to show both:

  • the number of firms available in each year;
  • the median value across firms.

Medians are preferred to means for the first pass because the firms differ considerably in size.

annual_summary <- firm_year |>
  filter(year <= 2025) |>
  group_by(year) |>
  summarise(
    n_firms = n_distinct(ticker),
    median_assets = median(fa_total_assets, na.rm = TRUE),
    median_net_income = median(net_income, na.rm = TRUE),
    median_dividend = median(dividend_per_share, na.rm = TRUE),
    median_debt_to_assets = median(debt_to_assets, na.rm = TRUE),
    median_net_debt_ebitda = median(net_debt_ebitda, na.rm = TRUE),
    .groups = "drop"
  )

kable(annual_summary |> mutate(across(where(is.numeric), ~ round(.x, 2))))
year n_firms median_assets median_net_income median_dividend median_debt_to_assets median_net_debt_ebitda
2001 4 1042.88 NA NA 47.32 8.07
2002 4 1302.40 NA NA 48.07 10.53
2003 4 1258.81 84.61 NA 46.57 11.09
2004 5 1212.85 84.91 1.34 50.04 10.53
2005 5 1459.32 110.04 1.29 47.48 10.00
2006 5 1506.35 112.53 0.00 38.60 10.28
2007 5 1612.49 117.35 0.00 43.87 9.09
2008 5 1918.20 128.68 2.52 45.35 8.55
2009 5 2075.22 193.71 2.65 44.59 8.36
2010 5 2578.73 199.87 2.90 42.87 7.18
2011 7 2036.85 82.20 2.93 42.50 5.69
2012 8 1568.25 70.95 1.49 41.00 6.45
2013 9 1058.34 66.29 2.78 40.44 6.30
2014 9 1101.81 75.59 2.88 39.25 7.79
2015 9 1293.22 97.37 3.37 38.54 7.33
2016 9 1302.16 145.57 3.30 38.43 6.88
2017 9 1215.02 92.98 3.42 37.84 8.71
2018 9 1420.64 152.25 3.29 40.50 8.61
2019 10 1525.06 108.24 3.19 42.58 7.79
2020 11 1502.78 131.17 3.32 36.39 10.11
2021 11 1754.36 128.96 3.24 35.62 6.54
2022 11 1814.86 97.22 3.49 36.86 9.30
2023 11 1945.97 140.75 3.60 37.32 17.79
2024 11 2170.25 131.45 3.67 35.90 7.93
2025 11 2276.06 150.06 3.95 34.62 6.04
ggplot(annual_summary, aes(year, median_assets)) +
  geom_line() +
  geom_point() +
  labs(
    title = "Median total assets across REOCs",
    x = NULL,
    y = "Median total assets"
  )

10 10. Connecting fundamentals to market development

The monthly file allows us to construct annual market measures.

For each firm-year we calculate:

  • annual total return;
  • year-end market capitalization;
  • average monthly return.

The annual total return is calculated from the total return index and is therefore preferable to simply using the change in price.

hp_annual <- hp |>
  group_by(ticker, year) |>
  arrange(date, .by_group = TRUE) |>
  summarise(
    year_end_date = max(date, na.rm = TRUE),
    year_end_market_cap = market_cap[which.max(date)],
    year_end_tri = total_return_index[which.max(date)],
    .groups = "drop"
  ) |>
  arrange(ticker, year) |>
  group_by(ticker) |>
  mutate(
    annual_total_return = year_end_tri / lag(year_end_tri) - 1
  ) |>
  ungroup()

The fundamental and market datasets can then be combined at the firm-year level.

firm_year_market <- firm_year |>
  left_join(
    hp_annual |>
      select(ticker, year, year_end_market_cap, annual_total_return),
    by = c("ticker", "year")
  )

10.1 10.1 Annual total return by firm

Market performance provides a complementary perspective to the accounting variables. The following figure shows how annual total returns developed for each REOC.

ggplot(
  firm_year_market |> filter(year <= 2025),
  aes(x = year, y = annual_total_return, group = ticker)
) +
  geom_line(na.rm = TRUE) +
  geom_point(na.rm = TRUE) +
  facet_wrap(~ ticker, scales = "free_y") +
  scale_y_continuous(labels = scales::percent) +
  labs(
    title = "Annual total return by REOC",
    x = "Year",
    y = "Annual total return"
  )

This plot allows us to compare the market performance of individual REOCs over time. It should be interpreted alongside the fundamental variables rather than on its own.

10.2 10.2 Assets and market capitalisation

ggplot(
  firm_year_market |> filter(year <= 2025),
  aes(x = fa_total_assets, y = year_end_market_cap)
) +
  geom_point(na.rm = TRUE) +
  facet_wrap(~ year, scales = "free") +
  labs(
    title = "Total assets and year-end market capitalisation",
    x = "Total assets",
    y = "Year-end market capitalisation"
  )

This is a descriptive relationship only. A stronger analysis would need to account for firm-specific effects, market conditions and timing.

10.3 10.3 Profitability and subsequent market return

For a later stage, it is more interesting to relate information known in year t to market performance in year t+1.

lagged_analysis <- firm_year_market |>
  arrange(ticker, year) |>
  group_by(ticker) |>
  mutate(
    next_year_return = lead(annual_total_return),
    next_year_market_cap = lead(year_end_market_cap)
  ) |>
  ungroup()

10.4 10.4 Net income growth and subsequent return

A more informative relationship is between the change in fundamentals in year t and market performance in year t+1.

ggplot(
  lagged_analysis |>
    filter(year <= 2024, is.finite(net_income_yoy), is.finite(next_year_return)),
  aes(x = net_income_yoy, y = next_year_return)
) +
  geom_point(na.rm = TRUE) +
  geom_smooth(method = "lm", se = FALSE, na.rm = TRUE) +
  scale_x_continuous(labels = scales::percent) +
  scale_y_continuous(labels = scales::percent) +
  labs(
    title = "Net income growth and subsequent annual return",
    x = "Net income growth in year t",
    y = "Annual total return in year t+1"
  )
## `geom_smooth()` using formula = 'y ~ x'

This is an exploratory relationship and does not by itself establish causality.

10.5 10.5 Leverage change and subsequent return

The same idea can be applied to leverage.

ggplot(
  lagged_analysis |>
    filter(year <= 2024, is.finite(debt_to_assets_change), is.finite(next_year_return)),
  aes(x = debt_to_assets_change, y = next_year_return)
) +
  geom_point(na.rm = TRUE) +
  geom_smooth(method = "lm", se = FALSE, na.rm = TRUE) +
  scale_y_continuous(labels = scales::percent) +
  labs(
    title = "Change in leverage and subsequent annual return",
    x = "Change in debt-to-assets in year t",
    y = "Annual total return in year t+1"
  )
## `geom_smooth()` using formula = 'y ~ x'

Again, this is descriptive and exploratory. A regression analysis would be needed to investigate whether the relationship remains after controlling for firm and year effects.

This creates a cleaner basis for future predictive tests, because the fundamental variable is measured before the subsequent return.

11 11. Variables to keep for the main analysis

The recommended first specification is:

Role Variable Why it matters
Firm size fa_total_assets Balance-sheet scale and growth
Profitability net_income Absolute earnings
Shareholder distribution dividend_per_share Dividend policy
Leverage debt_to_assets Balance-sheet risk
Debt burden net_debt_ebitda Debt relative to earnings
Market size year_end_market_cap Market valuation/size
Market performance annual_total_return Investor return
Market price price Price development
Secondary profit_margin Relative profitability, but sparse
Secondary pe_ratio Valuation, but sparse
Secondary dividend_yield Income valuation, but sparse

12 12. Important data-quality observations

The annual file contains observations for 2026 and 2027, but the coverage of the core accounting variables is incomplete in those years. The monthly market file currently runs only through April 2026.

Therefore, this first historical analysis uses 2001–2025 for the main long-run comparisons.

The annual panel is also unbalanced because firms enter the dataset at different dates. This means that aggregate yearly statistics should always be accompanied by the number of firms observed.

Missing values should not automatically be converted to zero. In particular, a missing dividend is not necessarily equivalent to a zero dividend.

13 ============================================================

14 13. Panel A: Monthly HP + monthly macro → market response

15 ============================================================

16 Research Question:

17

18 How is the stock-market performance of Swiss listed

19 real-estate companies associated with changes in the

20 macroeconomic environment?

21

22 The analysis is descriptive and exploratory.

23 It does not make causal claims.

24 ============================================================

25 13.1 Prepare monthly panel

26 ============================================================

panel_a <- hp |> mutate( date = as.Date(date), year = lubridate::year(date), month = lubridate::month(date) ) |> arrange(ticker, date)

27 ============================================================

28 13.2 Check the monthly dataset

29 ============================================================

panel_a_overview <- tibble( observations = nrow(panel_a), firms = n_distinct(panel_a\(ticker), first_date = min(panel_a\)date, na.rm = TRUE), last_date = max(panel_a$date, na.rm = TRUE) )

kable( panel_a_overview, caption = “Overview of the monthly market and macroeconomic dataset” )

30 ============================================================

31 13.3 Macro variable availability

32 ============================================================

33 Main macroeconomic variables used in Panel A

macro_vars <- c( “snb_policy_rate”, “libor_3m_chf”, “confederation_5y”, “confederation_10y”, “snb_core_inflation_trimmed_mean1”, “sfso_core_inflation_12”, “sfso_core_inflation_23”, “sfso_inflation_according_to_the_national_consumer_price_index”, “jobless_rate_sa”, “job_vacancies_sa”, “gdp” )

macro_availability <- panel_a |> summarise( across( all_of(macro_vars), ~ mean(!is.na(.)) ) ) |> pivot_longer( cols = everything(), names_to = “variable”, values_to = “available_share” ) |> mutate( available_share = scales::percent( available_share, accuracy = 0.1 ) )

kable( macro_availability, caption = “Availability of macroeconomic variables” )

34 ============================================================

35 13.4 Prepare interest-rate changes

36 ============================================================

37 The research question focuses on changes in the

38 macroeconomic environment. Therefore, in addition to

39 interest-rate levels, we calculate monthly changes.

panel_a <- panel_a |> arrange(ticker, date) |> group_by(ticker) |> mutate( policy_rate_change = snb_policy_rate - lag(snb_policy_rate),

libor_3m_change =
  libor_3m_chf - lag(libor_3m_chf),

confederation_10y_change =
  confederation_10y - lag(confederation_10y)

) |> ungroup()

40 ============================================================

41 13.5 Interest rates and monthly returns

42 ============================================================

43 13.5.1 SNB policy rate level

ggplot( panel_a |> filter( !is.na(monthly_return), !is.na(snb_policy_rate) ), aes( x = snb_policy_rate, y = monthly_return ) ) + geom_point(alpha = 0.4) + geom_smooth( method = “lm”, se = TRUE ) + scale_y_continuous( labels = scales::percent ) + labs( title = “SNB policy rate and monthly REOC returns”, x = “SNB policy rate (%)”, y = “Monthly total return” )

44 ============================================================

45 13.6 Changes in interest rates and monthly returns

46 ============================================================

ggplot( panel_a |> filter( !is.na(monthly_return), !is.na(policy_rate_change) ), aes( x = policy_rate_change, y = monthly_return ) ) + geom_point(alpha = 0.4) + geom_smooth( method = “lm”, se = TRUE ) + scale_y_continuous( labels = scales::percent ) + labs( title = “Changes in the SNB policy rate and monthly REOC returns”, x = “Monthly change in SNB policy rate (percentage points)”, y = “Monthly total return” )

47 ============================================================

48 13.7 Long-term interest rates and monthly returns

49 ============================================================

ggplot( panel_a |> filter( !is.na(monthly_return), !is.na(confederation_10y) ), aes( x = confederation_10y, y = monthly_return ) ) + geom_point(alpha = 0.4) + geom_smooth( method = “lm”, se = TRUE ) + scale_y_continuous( labels = scales::percent ) + labs( title = “10-year Confederation yield and monthly REOC returns”, x = “10-year Confederation yield (%)”, y = “Monthly total return” )

50 ============================================================

51 13.8 Inflation and monthly returns

52 ============================================================

53 Rename the main CPI inflation variable to make the

54 following analysis easier to read.

panel_a <- panel_a |> rename( inflation = sfso_inflation_according_to_the_national_consumer_price_index )

ggplot( panel_a |> filter( !is.na(monthly_return), !is.na(inflation) ), aes( x = inflation, y = monthly_return ) ) + geom_point(alpha = 0.4) + geom_smooth( method = “lm”, se = TRUE ) + scale_y_continuous( labels = scales::percent ) + labs( title = “Inflation and monthly REOC returns”, x = “Inflation (%)”, y = “Monthly total return” )

55 ============================================================

56 13.9 Unemployment and monthly returns

57 ============================================================

ggplot( panel_a |> filter( !is.na(monthly_return), !is.na(jobless_rate_sa) ), aes( x = jobless_rate_sa, y = monthly_return ) ) + geom_point(alpha = 0.4) + geom_smooth( method = “lm”, se = TRUE ) + scale_y_continuous( labels = scales::percent ) + labs( title = “Unemployment and monthly REOC returns”, x = “Seasonally adjusted unemployment rate (%)”, y = “Monthly total return” )

58 ============================================================

59 13.10 Job vacancies and monthly returns

60 ============================================================

ggplot( panel_a |> filter( !is.na(monthly_return), !is.na(job_vacancies_sa) ), aes( x = job_vacancies_sa, y = monthly_return ) ) + geom_point(alpha = 0.4) + geom_smooth( method = “lm”, se = TRUE ) + scale_y_continuous( labels = scales::percent ) + labs( title = “Job vacancies and monthly REOC returns”, x = “Seasonally adjusted job vacancies”, y = “Monthly total return” )

61 ============================================================

62 13.11 Differences across REOCs

63 ============================================================

64 ALLN is treated separately because it represents an

65 aggregate series rather than an individual REOC.

panel_a_firms <- panel_a |> filter(ticker != “ALLN”)

66 Relationship between SNB policy rate and returns

67 for each individual REOC.

ggplot( panel_a_firms |> filter( !is.na(monthly_return), !is.na(snb_policy_rate) ), aes( x = snb_policy_rate, y = monthly_return ) ) + geom_point(alpha = 0.35) + geom_smooth( method = “lm”, se = FALSE ) + facet_wrap(~ ticker) + scale_y_continuous( labels = scales::percent ) + labs( title = “SNB policy rate and monthly returns by REOC”, x = “SNB policy rate (%)”, y = “Monthly total return” )

68 ============================================================

69 13.12 Interest-rate regimes

70 ============================================================

71 We classify months as low-rate or high-rate according

72 to the median SNB policy rate in the sample.

rate_threshold <- median( panel_a$snb_policy_rate, na.rm = TRUE )

panel_a <- panel_a |> mutate( rate_regime = case_when( snb_policy_rate <= rate_threshold ~ “Low-rate regime”, snb_policy_rate > rate_threshold ~ “High-rate regime”, TRUE ~ NA_character_ ) )

73 ============================================================

74 13.13 Returns across interest-rate regimes

75 ============================================================

ggplot( panel_a_firms |> filter( !is.na(monthly_return), !is.na(rate_regime) ), aes( x = rate_regime, y = monthly_return ) ) + geom_boxplot() + scale_y_continuous( labels = scales::percent ) + labs( title = “REOC monthly returns across interest-rate regimes”, x = NULL, y = “Monthly total return” )

76 ============================================================

77 13.14 Interest-rate regimes by REOC

78 ============================================================

ggplot( panel_a_firms |> filter( !is.na(monthly_return), !is.na(rate_regime) ), aes( x = rate_regime, y = monthly_return ) ) + geom_boxplot() + facet_wrap(~ ticker) + scale_y_continuous( labels = scales::percent ) + labs( title = “Monthly REOC returns across interest-rate regimes”, x = NULL, y = “Monthly total return” )

79 ============================================================

80 13.15 Correlation analysis

81 ============================================================

correlation_data <- panel_a |> select( monthly_return, snb_policy_rate, policy_rate_change, libor_3m_chf, confederation_10y, inflation, jobless_rate_sa, job_vacancies_sa )

correlation_matrix <- cor( correlation_data, use = “pairwise.complete.obs” )

kable( round(correlation_matrix, 3), caption = “Correlation between monthly REOC returns and macroeconomic variables” )

82 ============================================================

83 13.16 Average returns by interest-rate regime

84 ============================================================

regime_summary <- panel_a_firms |> filter( !is.na(monthly_return), !is.na(rate_regime) ) |> group_by(rate_regime) |> summarise( n_observations = n(), mean_return = mean( monthly_return, na.rm = TRUE ), median_return = median( monthly_return, na.rm = TRUE ), sd_return = sd( monthly_return, na.rm = TRUE ), .groups = “drop” ) |> mutate( mean_return = scales::percent( mean_return, accuracy = 0.1 ), median_return = scales::percent( median_return, accuracy = 0.1 ), sd_return = scales::percent( sd_return, accuracy = 0.1 ) )

kable( regime_summary, caption = “REOC monthly returns by interest-rate regime” )

85 ============================================================

86 13.17 Summary statistics by macro environment

87 ============================================================

macro_return_summary <- panel_a |> summarise( observations = sum(!is.na(monthly_return)),

mean_monthly_return =
  mean(monthly_return, na.rm = TRUE),

median_monthly_return =
  median(monthly_return, na.rm = TRUE),

sd_monthly_return =
  sd(monthly_return, na.rm = TRUE),

mean_policy_rate =
  mean(snb_policy_rate, na.rm = TRUE),

mean_inflation =
  mean(inflation, na.rm = TRUE),

mean_unemployment =
  mean(jobless_rate_sa, na.rm = TRUE)

)

kable( macro_return_summary, digits = 3, caption = “Summary statistics for monthly REOC returns and the macroeconomic environment” )

88 ============================================================

89 13.18 Interpretation

90 ============================================================

91 The analysis above is descriptive.

92

93 The scatterplots show whether monthly REOC returns tend to

94 move together with interest rates, inflation, unemployment

95 and other macroeconomic indicators.

96

97 The firm-level plots show whether these relationships appear

98 similar across REOCs or differ substantially between firms.

99

100 The regime analysis compares REOC returns during relatively

101 low-rate and high-rate periods.

102

103 These results describe associations and should not be

104 interpreted as evidence of a causal effect of macroeconomic

105 variables on REOC returns.