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. 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. Initial data
audit
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)
| 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)
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)
| 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. Which variables are
most interesting?
The variables naturally fall into four groups.
4.1 Core financial
development variables
These should be the main variables for the firm-year analysis:
- Total assets (
fa_total_assets) —
captures firm scale and balance-sheet growth.
- Net income (
net_income) — captures
profitability in absolute terms.
- Dividend per share
(
dividend_per_share) — captures shareholder
distribution.
- Debt-to-assets (
debt_to_assets) —
captures balance-sheet leverage.
- 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 Market
variables
From the monthly dataset, the most useful variables are:
- Market capitalization (
market_cap) —
firm size from the market’s perspective.
- Price (
price) — share-price
development.
- Total return index
(
total_return_index) — preferred for measuring
investor performance.
- 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 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)
| 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. 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. Firm development:
one firm at a time
A useful first visualisation is to plot the main variables for every
firm.
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 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 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 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. 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)))
)
| 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. 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)
)
)
| 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. 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))))
| 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. 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 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 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 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 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 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. Variables to keep
for the main analysis
The recommended first specification is:
| 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. 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. Panel A: Monthly
HP + monthly macro → market response
============================================================
Research
Question:
How is the
stock-market performance of Swiss listed
real-estate companies
associated with changes in the
macroeconomic
environment?
The analysis is
descriptive and exploratory.
It does not make
causal claims.
============================================================
13.1 Prepare monthly
panel
============================================================
panel_a <- hp |> mutate( date = as.Date(date), year =
lubridate::year(date), month = lubridate::month(date) ) |>
arrange(ticker, date)
============================================================
13.2 Check the monthly
dataset
============================================================
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” )
============================================================
13.3 Macro variable
availability
============================================================
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” )
============================================================
13.4 Prepare
interest-rate changes
============================================================
The research question
focuses on changes in the
macroeconomic
environment. Therefore, in addition to
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()
============================================================
13.5 Interest rates
and monthly returns
============================================================
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” )
============================================================
13.6 Changes in
interest rates and monthly returns
============================================================
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” )
============================================================
13.7 Long-term
interest rates and monthly returns
============================================================
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” )
============================================================
13.8 Inflation and
monthly returns
============================================================
Rename the main CPI
inflation variable to make the
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” )
============================================================
13.9 Unemployment and
monthly returns
============================================================
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” )
============================================================
13.10 Job vacancies
and monthly returns
============================================================
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” )
============================================================
13.11 Differences
across REOCs
============================================================
ALLN is treated
separately because it represents an
aggregate series
rather than an individual REOC.
panel_a_firms <- panel_a |> filter(ticker != “ALLN”)
Relationship between
SNB policy rate and returns
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” )
============================================================
13.12 Interest-rate
regimes
============================================================
We classify months as
low-rate or high-rate according
============================================================
13.13 Returns across
interest-rate regimes
============================================================
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” )
============================================================
13.14 Interest-rate
regimes by REOC
============================================================
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” )
============================================================
13.15 Correlation
analysis
============================================================
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” )
============================================================
13.16 Average returns
by interest-rate regime
============================================================
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” )
============================================================
13.17 Summary
statistics by macro environment
============================================================
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”
)
============================================================
13.18
Interpretation
============================================================
The analysis above is
descriptive.
The scatterplots show
whether monthly REOC returns tend to
move together with
interest rates, inflation, unemployment
and other
macroeconomic indicators.
The firm-level plots
show whether these relationships appear
similar across REOCs
or differ substantially between firms.
The regime analysis
compares REOC returns during relatively
low-rate and
high-rate periods.
These results
describe associations and should not be
interpreted as
evidence of a causal effect of macroeconomic
variables on REOC
returns.