Project overview
This project asks whether common European and global financial factors can improve country-level output-gap estimates relative to a standard same-sample baseline model. The analysis evaluates whether variables such as the Eurostoxx50 return, CISS financial stress, Brent oil prices, euro-area interest rates, unemployment and exchange-rate movements contain useful information for decomposing real GDP into potential output and the cyclical output gap across European economies.
Download and transformation of common factors
This cell downloads and prepares the common European and global factors used in the
state-space output-gap models. The aim is to construct a single quarterly dataset,
common_factors, in which all variables are transformed into a stationary or directly
usable form for the empirical analysis.
The daily financial-market variables are downloaded from Yahoo Finance. The EUR/USD exchange rate and the Eurostoxx50 index are first converted to quarter-end values. They are then transformed into quarterly log changes:
The resulting variables are eurusd_dlog and eurstock_logreturn. For the Eurostoxx50
index, this transformation is interpreted as a quarterly stock-market log return.
The CISS indicator is downloaded from the ECB Data Portal. The daily CISS series is
converted to quarterly frequency by taking both the quarterly mean and the quarterly
maximum. These are then standardized, producing ciss_std and ciss_max_std.
The main specification uses the quarterly mean version, ciss_std.
The remaining common factors are downloaded from FRED. Monthly series are converted to quarterly frequency by taking the quarterly average. Brent oil prices and the euro-area share price index are transformed into quarterly log changes. For the euro-area share price index this can be interpreted as a quarterly log return, while for Brent oil it is interpreted more generally as a quarterly log price change. The euro-area three-month interbank rate enters as a simple quarterly change.
The euro-area M3 growth rate is included in two forms: as a standardized level and as a quarterly change. Standardization is defined as
The euro-area unemployment rate is downloaded separately from FRED. Monthly unemployment observations are averaged within each quarter. The code stores the level of the unemployment rate, its quarterly change in percentage points, and its standardized level.
The unemployment series ends earlier than some of the financial-market variables, so
missing values appear in the most recent quarters. This is not treated as an error. Later
model estimations should drop missing observations separately for each factor, instead of
dropping all rows with any missing value in the full common_factors dataset.
The cell does not save any files. It creates the object common_factor_data, which contains
the transformed factors, raw quarterly series, daily Yahoo Finance series, monthly FRED
series, the daily ECB CISS series, and the main settings used in the download. For
convenience, the transformed factor dataset is also stored directly as common_factors.
Show code
# ============================================================
# CELL 1 — Download and transform common European/global factors
# ============================================================
# No files are saved.
#
# Output objects:
# common_factor_data["factors"] -> transformed factors, ready for modelling
# common_factor_data["raw_quarterly"] -> raw quarterly series
# common_factor_data["daily"] -> daily Yahoo series
# common_factor_data["monthly"] -> monthly FRED series
# common_factor_data["ecb"] -> ECB CISS daily series
#
# Convenience object:
# common_factors = common_factor_data["factors"]
import warnings
warnings.filterwarnings("ignore")
import io
import contextlib
import numpy as np
import pandas as pd
import requests
import yfinance as yf
from io import StringIO
from IPython.display import display
# ============================================================
# 0. Settings
# ============================================================
DOWNLOAD_START_DATE = "1994-01-01"
FINAL_START_DATE = "1995-01-01"
FINAL_START_QUARTER = "1995Q1"
END_DATE = None
EURUSD_TICKER = "EURUSD=X"
EUROSTOXX_TICKER = "^STOXX50E"
FRED_SERIES = {
"brent_oil": "MCOILBRENTEU",
"ea_3m_interbank_rate": "IR3TIB01EZQ156N",
"ea_share_price_index": "SPASTT01EZQ661N",
"ea_reer": "RBXMBIS",
"ea_m3_yoy_growth": "EA19MABMM301GYSAQ",
}
UNEMPLOYMENT_SERIES_ID = "LRHUTTTTEZM156S"
CISS_FLOW = "CISS"
CISS_KEYS_TO_TRY = [
"D.U2.Z0Z.4F.EC.SS_CIN.IDX",
"D.U2.Z0Z.4F.EC.SS_CI.IDX",
]
# ============================================================
# 1. Helpers
# ============================================================
def standardize(s):
s = pd.Series(s, dtype=float)
sd = s.std()
if not np.isfinite(sd) or sd == 0:
return s - s.mean()
return (s - s.mean()) / sd
def dlog100(s):
s = pd.Series(s, dtype=float)
return 100.0 * np.log(s).diff()
def trim_start(df, start_quarter=FINAL_START_QUARTER):
start = pd.Period(start_quarter, freq="Q")
return df.loc[df.index >= start].copy()
def to_quarter_period_index(dates):
return pd.to_datetime(dates).to_period("Q")
def quarterly_resample_last(s):
try:
out = s.resample("QE").last()
except Exception:
out = s.resample("Q").last()
out.index = out.index.to_period("Q")
return out
def quarterly_resample_mean(s):
try:
out = s.resample("QE").mean()
except Exception:
out = s.resample("Q").mean()
out.index = out.index.to_period("Q")
return out
def quarterly_resample_max(s):
try:
out = s.resample("QE").max()
except Exception:
out = s.resample("Q").max()
out.index = out.index.to_period("Q")
return out
def download_yahoo_series(ticker, start, end=None, value_name="value"):
df = yf.download(
ticker,
start=start,
end=end,
progress=False,
auto_adjust=False
)
if df.empty:
raise ValueError(f"Could not download Yahoo Finance data: {ticker}")
if isinstance(df.columns, pd.MultiIndex):
df.columns = df.columns.get_level_values(0)
if "Adj Close" in df.columns:
s = df["Adj Close"].copy()
elif "Close" in df.columns:
s = df["Close"].copy()
else:
raise ValueError(f"No Close or Adj Close column for ticker: {ticker}")
s.index = pd.to_datetime(s.index).tz_localize(None)
s = pd.to_numeric(s, errors="coerce").dropna()
s.name = value_name
return s
def make_quarterly_log_change_from_daily(daily_series, level_name, dlog_name):
q_level = quarterly_resample_last(daily_series)
q_dlog = 100.0 * np.log(q_level).diff()
out = pd.DataFrame({
level_name: q_level,
dlog_name: q_dlog,
dlog_name.replace("_dlog", "_pct_change"): 100.0 * q_level.pct_change(),
})
out.index.name = "quarter"
return out
def download_fred_series(series_id):
url = f"https://fred.stlouisfed.org/graph/fredgraph.csv?id={series_id}"
df = pd.read_csv(url)
if "observation_date" in df.columns:
date_col = "observation_date"
elif "DATE" in df.columns:
date_col = "DATE"
else:
date_col = df.columns[0]
if series_id in df.columns:
value_col = series_id
else:
value_col = [c for c in df.columns if c != date_col][0]
df = df.rename(columns={date_col: "date", value_col: "value"})
df["date"] = pd.to_datetime(df["date"], errors="coerce")
df["value"] = pd.to_numeric(df["value"], errors="coerce")
df = df.dropna(subset=["date", "value"])
df = df[df["date"] >= pd.Timestamp(DOWNLOAD_START_DATE)]
df = df.sort_values("date")
if df.empty:
raise ValueError(f"No numeric observations downloaded for FRED series {series_id}")
return df
def monthly_to_quarterly_mean(monthly_df, name):
df = monthly_df.copy()
df = df.set_index("date").sort_index()
try:
q = df["value"].resample("QE").mean()
except Exception:
q = df["value"].resample("Q").mean()
q.index = q.index.to_period("Q")
q.name = name
return q
def monthly_to_quarterly_unemployment(monthly_df):
df = monthly_df.copy()
df = df[df["date"] >= pd.Timestamp(FINAL_START_DATE)]
df = df.set_index("date").sort_index()
try:
q_rate = df["value"].resample("QE").mean()
except Exception:
q_rate = df["value"].resample("Q").mean()
q_rate.index = q_rate.index.to_period("Q")
out = pd.DataFrame(index=q_rate.index)
out["ea_unemployment_rate"] = q_rate
out["ea_unemployment_change"] = q_rate.diff()
out["ea_unemployment_std"] = standardize(q_rate)
out = out.loc[out.index >= pd.Period(FINAL_START_QUARTER, freq="Q")]
return out
def ecb_download_csv(flow, key, start_date=DOWNLOAD_START_DATE):
url = (
f"https://data-api.ecb.europa.eu/service/data/{flow}/{key}"
f"?startPeriod={start_date}&format=csvdata"
)
r = requests.get(url, timeout=60)
r.raise_for_status()
df = pd.read_csv(StringIO(r.text))
if "TIME_PERIOD" not in df.columns or "OBS_VALUE" not in df.columns:
raise ValueError(f"Unexpected ECB CSV format. Columns: {df.columns.tolist()}")
df["TIME_PERIOD"] = pd.to_datetime(df["TIME_PERIOD"], errors="coerce")
df["OBS_VALUE"] = pd.to_numeric(df["OBS_VALUE"], errors="coerce")
s = (
df.dropna(subset=["TIME_PERIOD", "OBS_VALUE"])
.set_index("TIME_PERIOD")["OBS_VALUE"]
.sort_index()
)
s.name = "ciss"
return s
def download_ciss():
last_error = None
for key in CISS_KEYS_TO_TRY:
try:
s = ecb_download_csv(CISS_FLOW, key)
q_mean = quarterly_resample_mean(s)
q_max = quarterly_resample_max(s)
q_mean.name = "ciss_mean"
q_max.name = "ciss_max"
return s, q_mean, q_max
except Exception as e:
last_error = e
raise RuntimeError(f"Could not download CISS. Last error: {last_error}")
# ============================================================
# 2. Main download and transformation
# ============================================================
with contextlib.redirect_stdout(io.StringIO()), contextlib.redirect_stderr(io.StringIO()):
# ----------------------------
# Yahoo Finance
# ----------------------------
eurusd_daily = download_yahoo_series(
ticker=EURUSD_TICKER,
start=DOWNLOAD_START_DATE,
end=END_DATE,
value_name="eurusd"
)
eurostoxx_daily = download_yahoo_series(
ticker=EUROSTOXX_TICKER,
start=DOWNLOAD_START_DATE,
end=END_DATE,
value_name="eurostoxx50"
)
eurusd_q = make_quarterly_log_change_from_daily(
daily_series=eurusd_daily,
level_name="eurusd",
dlog_name="eurusd_dlog"
)
eurostoxx_q = make_quarterly_log_change_from_daily(
daily_series=eurostoxx_daily,
level_name="eurostoxx50",
dlog_name="eurostoxx50_dlog"
)
# ----------------------------
# FRED monthly/quarterly series
# ----------------------------
fred_quarterly = {}
fred_monthly = {}
for name, sid in FRED_SERIES.items():
monthly_df = download_fred_series(sid)
fred_monthly[name] = monthly_df
fred_quarterly[name] = monthly_to_quarterly_mean(monthly_df, name)
unemployment_monthly = download_fred_series(UNEMPLOYMENT_SERIES_ID)
unemployment_q = monthly_to_quarterly_unemployment(unemployment_monthly)
# ----------------------------
# ECB CISS
# ----------------------------
ciss_daily, ciss_mean_q, ciss_max_q = download_ciss()
# ----------------------------
# Raw quarterly dataset
# ----------------------------
raw_q = pd.concat(
[
eurusd_q,
eurostoxx_q,
ciss_mean_q,
ciss_max_q,
fred_quarterly["brent_oil"],
fred_quarterly["ea_3m_interbank_rate"],
fred_quarterly["ea_share_price_index"],
fred_quarterly["ea_reer"],
fred_quarterly["ea_m3_yoy_growth"],
unemployment_q,
],
axis=1
).sort_index()
raw_q = trim_start(raw_q, FINAL_START_QUARTER)
# ----------------------------
# Transformed modelling factors
# ----------------------------
factors = pd.DataFrame(index=raw_q.index)
factors["eurusd_dlog"] = raw_q["eurusd_dlog"]
factors["eurstock_logreturn"] = raw_q["eurostoxx50_dlog"]
factors["eurostoxx50_dlog"] = raw_q["eurostoxx50_dlog"]
factors["ciss_std"] = standardize(raw_q["ciss_mean"])
factors["ciss_max_std"] = standardize(raw_q["ciss_max"])
factors["brent_dlog"] = dlog100(raw_q["brent_oil"])
factors["ea_3m_rate_change"] = raw_q["ea_3m_interbank_rate"].diff()
factors["ea_share_price_dlog"] = dlog100(raw_q["ea_share_price_index"])
factors["ea_reer_dlog"] = dlog100(raw_q["ea_reer"])
factors["ea_m3_yoy_std"] = standardize(raw_q["ea_m3_yoy_growth"])
factors["ea_m3_yoy_change"] = raw_q["ea_m3_yoy_growth"].diff()
factors["ea_unemployment_rate"] = raw_q["ea_unemployment_rate"]
factors["ea_unemployment_change"] = raw_q["ea_unemployment_change"]
factors["ea_unemployment_std"] = raw_q["ea_unemployment_std"]
factors = trim_start(factors, FINAL_START_QUARTER)
# ----------------------------
# Final object
# ----------------------------
common_factor_data = {
"factors": factors,
"raw_quarterly": raw_q,
"daily": {
"eurusd": eurusd_daily,
"eurostoxx50": eurostoxx_daily,
},
"monthly": {
**fred_monthly,
"unemployment": unemployment_monthly,
},
"ecb": {
"ciss_daily": ciss_daily,
},
"settings": {
"download_start_date": DOWNLOAD_START_DATE,
"final_start_date": FINAL_START_DATE,
"final_start_quarter": FINAL_START_QUARTER,
"eurusd_ticker": EURUSD_TICKER,
"eurostoxx_ticker": EUROSTOXX_TICKER,
"fred_series": FRED_SERIES,
"unemployment_series_id": UNEMPLOYMENT_SERIES_ID,
"ciss_flow": CISS_FLOW,
"ciss_keys_to_try": CISS_KEYS_TO_TRY,
}
}
common_factors = common_factor_data["factors"]
print("\nTransformed factors tail:")
display(common_factors.tail(15))
Show output
Transformed factors tail:
| eurusd_dlog | eurstock_logreturn | eurostoxx50_dlog | ciss_std | ciss_max_std | brent_dlog | ea_3m_rate_change | ea_share_price_dlog | ea_reer_dlog | ea_m3_yoy_std | ea_m3_yoy_change | ea_unemployment_rate | ea_unemployment_change | ea_unemployment_std | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 2022Q4 | 8.117534 | 13.389829 | 13.389829 | 2.009865 | 2.263206 | -12.863555 | 1.291383 | 2.463118 | 3.186632 | -0.110213 | -1.304632 | 6.666667 | -0.033333 | -1.923726 |
| 2023Q1 | 2.262018 | 12.878813 | 12.878813 | 0.266883 | 0.615778 | -8.705586 | 0.859803 | 10.453107 | -0.138516 | -0.736604 | -1.886522 | 6.700000 | 0.033333 | -1.900375 |
| 2023Q2 | -0.336384 | 1.928880 | 1.928880 | 0.039889 | 0.377466 | -3.582635 | 0.724507 | 2.071042 | 2.259681 | -1.346762 | -1.837633 | NaN | NaN | NaN |
| 2023Q3 | -2.852007 | -5.236471 | -5.236471 | -0.369446 | -0.558516 | 10.123198 | 0.420954 | -0.801922 | 1.362352 | -1.937444 | -1.778974 | NaN | NaN | NaN |
| 2023Q4 | 4.677002 | 7.984399 | 7.984399 | -0.230911 | -0.365771 | -3.447470 | 0.180069 | -0.760614 | -0.907266 | -1.937640 | -0.000591 | NaN | NaN | NaN |
| 2024Q1 | -2.504115 | 11.710730 | 11.710730 | -0.609479 | -0.552092 | -0.863694 | -0.033841 | 8.556794 | -0.448476 | -1.569978 | 1.107299 | NaN | NaN | NaN |
| 2024Q2 | -0.799894 | -3.797019 | -3.797019 | -0.685772 | -0.692129 | 1.960496 | -0.115443 | 4.614102 | 0.924425 | -1.194776 | 1.130007 | NaN | NaN | NaN |
| 2024Q3 | 4.217978 | 2.151389 | 2.151389 | -0.344047 | -0.086988 | -5.841935 | -0.252795 | -2.355632 | -0.111399 | -0.731259 | 1.395991 | NaN | NaN | NaN |
| 2024Q4 | -7.081789 | -2.658191 | -2.658191 | -0.609851 | -0.724708 | -6.774716 | -0.558890 | 0.678820 | -1.406382 | -0.396157 | 1.009237 | NaN | NaN | NaN |
| 2025Q1 | 3.936098 | 7.497535 | 7.497535 | -0.753033 | -0.895394 | 1.595496 | -0.439651 | 7.591123 | -1.157010 | -0.308906 | 0.262775 | NaN | NaN | NaN |
| 2025Q2 | 8.017440 | 1.039661 | 1.039661 | -0.434249 | -0.125919 | -10.866844 | -0.449145 | 0.587169 | 4.250496 | -0.384138 | -0.226578 | NaN | NaN | NaN |
| 2025Q3 | 0.034016 | 4.186258 | 4.186258 | -0.702536 | -0.816706 | 1.401756 | -0.096119 | 3.974684 | 1.007064 | -0.679497 | -0.889541 | NaN | NaN | NaN |
| 2025Q4 | 0.135006 | 4.702544 | 4.702544 | -0.661042 | -0.725134 | -8.059063 | 0.029051 | 4.631801 | -0.434936 | -0.738001 | -0.176199 | NaN | NaN | NaN |
| 2026Q1 | -2.475972 | -3.985944 | -3.985944 | -0.646409 | -0.768841 | 23.157397 | NaN | NaN | -1.011476 | NaN | NaN | NaN | NaN | NaN |
| 2026Q2 | 0.586180 | 8.470472 | 8.470472 | -0.787866 | -0.861377 | 33.581004 | NaN | NaN | 0.979421 | NaN | NaN | NaN | NaN | NaN |
Common factors and transformations
| Variable | Source | Transformation | Lag | Effective start |
|---|---|---|---|---|
| EUR/USD exchange rate | Yahoo Finance, EURUSD=X |
Quarter-end value, $100\Delta\log$ | 2 | 2004Q3 |
| Euro-area share price index | FRED, SPASTT01EZQ661N |
Quarterly log return, $100\Delta\log$ | 2 | 1995Q3 |
| Eurostoxx50 index | Yahoo Finance, ^STOXX50E |
Quarter-end value, $100\Delta\log$ | 0 | 2007Q2 |
| ECB CISS | ECB Data Portal | Quarterly mean, standardised | 0 | 1995Q1 |
| Brent oil price | FRED, MCOILBRENTEU |
Quarterly mean, $100\Delta\log$ | 0 | 1995Q1 |
| Euro-area 3-month interbank rate | FRED, IR3TIB01EZQ156N |
Quarterly change | 0 | 1995Q1 |
| Euro-area unemployment rate | FRED, LRHUTTTTEZM156S |
Quarterly mean, standardised | 0 | 1995Q1--2023Q1 |
Note. The reported lags were selected in a preliminary specification-testing step. Variables expressed as prices or indices are transformed into quarterly log changes, while the interest-rate variable enters as a simple quarterly change. Standardised variables are demeaned and divided by their sample standard deviation.
Estimation of country-level factor-augmented output-gap models
This cell estimates the Borio-style state-space output-gap models for the country sample. The quarterly real GDP series were downloaded from Eurostat. For each country, real GDP is transformed into logarithms and multiplied by 100:
where $Y_{i,t}$ denotes seasonally and calendar adjusted real GDP. The observed output series is decomposed into latent potential output and an output gap:
Potential output follows a smooth local-trend specification:
The baseline output-gap equation is
The factor-augmented model adds one common European or global factor at a time:
where $z_{t-k}$ is the selected common factor entering with lag $k$. The common factors
are not read from external files in this cell. Instead, they are taken from the previously
created common_factors object.
The relative smoothness of potential output is governed by the variance ratio
Equivalently, for a given value of $\lambda_{2,i,z}$, the state-space model corresponds to minimising a penalised objective of the form
For the baseline model, the term $\gamma_{i,z}z_{t-k}$ is omitted. The first term measures the unexplained part of the cyclical equation, while the second term penalises changes in the growth rate of potential output. A larger value of $\lambda_{2,i,z}$ imposes a stronger smoothness penalty on potential output, so more short-run variation is assigned to the output gap. A smaller value allows potential output to move more flexibly with observed GDP.
The smoothing parameters $\lambda_{2,i,z}$ were optimized in a preliminary step for each country, model type and factor specification. In this cell, those previously selected values are fixed and hard-coded into the pipeline. This avoids repeating the computationally expensive lambda-search step and ensures that the reported results are reproducible.
The factor lags were also selected in a preliminary specification-testing step. For each country and factor, the cell estimates two models on the same matched sample: the same-sample baseline model and the corresponding factor-augmented model. The final table reports the estimated persistence parameter $\beta_i$, the factor coefficient $\gamma_{i,z}$, likelihood-based model-comparison statistics, and the improvement relative to the same-sample baseline.
Show code
# ============================================================
# CELL 2 — Estimate selected factor models with fixed lambda2
# ============================================================
# Assumption:
# The previous cell has already created `common_factors`
# or `common_factor_data["factors"]`.
#
# This cell:
# - reads country GDP data from Excel, as before;
# - uses common factors from the existing notebook object, not from Excel;
# - uses hard-coded lambda2 values selected in a previous optimization step;
# - estimates the baseline-same-sample and factor models with fixed lambda2;
# - shows a progress bar during estimation;
# - prints only the FINAL FACTOR RESULTS table.
#
# Output objects:
# summary_df
# factor_results
# baseline_results
# outputs_long
# failed_df
# eurostoxx_potential_output
# eurostoxx_potential_output_long
import warnings
warnings.filterwarnings("ignore")
import re
from pathlib import Path
import numpy as np
import pandas as pd
from scipy.optimize import minimize
from scipy.stats import gamma as gamma_dist
from scipy.stats import norm, chi2
from statsmodels.tsa.filters.hp_filter import hpfilter
from statsmodels.tsa.statespace.mlemodel import MLEModel
try:
from tqdm.auto import tqdm
except Exception:
tqdm = None
# ============================================================
# 0. SETTINGS
# ============================================================
GDP_EXCEL = "real_sa_gdp_complete_from_1995_all_available_countries.xlsx"
START_QUARTER = "1995Q1"
END_QUARTER = "2025Q4"
COUNTRIES = None
USE_PRIORS = True
STANDARDIZE_FACTORS = True
BETA_UPPER = 2
BETA_PRIOR_MEAN = 0.70
BETA_PRIOR_SD = 0.30
HP_LAMBDA = 1600
MIN_OBS = 25
# This is the factor whose potential-output estimate is stored together
# with the corresponding same-sample baseline potential output.
EUROSTOXX_FACTOR_ID = "eurstock_logreturn"
# ============================================================
# 1. FINAL FACTOR-LAG CHOICES
# ============================================================
FACTOR_SPECS = [
{
"factor_id": "eurusd_dlog",
"lag": 2,
"description": "EUR/USD quarterly exchange-rate log change",
"aliases": [
"eurusd_dlog",
"eur_usd_dlog",
"eurusd_log_change",
"eurusd_change",
"eur_usd_change",
],
},
{
"factor_id": "ea_share_price_dlog",
"lag": 2,
"description": "Euro area share price index quarterly log return",
"aliases": [
"ea_share_price_dlog",
],
},
{
"factor_id": "eurstock_logreturn",
"lag": 0,
"description": "European stock index quarterly log return",
"aliases": [
"eurstock_logreturn",
"eurstock_dlog",
"eurostoxx50_dlog",
"eurostoxx_dlog",
"eu_stock_dlog",
"europe_stock_dlog",
],
},
{
"factor_id": "ea_unemployment_std",
"lag": 0,
"description": "Euro area unemployment rate, standardized",
"aliases": [
"ea_unemployment_std",
"ea19_unemployment_std",
"ea20_unemployment_std",
"euro_area_unemployment_std",
],
},
{
"factor_id": "ciss_std",
"lag": 0,
"description": "ECB CISS, standardized",
"aliases": [
"ciss_std",
],
},
{
"factor_id": "brent_dlog",
"lag": 0,
"description": "Brent oil price quarterly log change",
"aliases": [
"brent_dlog",
],
},
{
"factor_id": "ea_3m_rate_change",
"lag": 0,
"description": "Euro area 3M rate quarterly change",
"aliases": [
"ea_3m_rate_change",
],
},
]
# ============================================================
# 2. QUARTER HANDLING
# ============================================================
def parse_quarter(x):
if isinstance(x, pd.Period):
return x.asfreq("Q")
if isinstance(x, pd.Timestamp):
return x.to_period("Q")
s = str(x).strip()
m = re.match(r"^(\d{4})-?Q([1-4])$", s)
if m:
return pd.Period(f"{m.group(1)}Q{m.group(2)}", freq="Q")
try:
return pd.Timestamp(s).to_period("Q")
except Exception:
raise ValueError(f"Nem értelmezhető negyedév: {x}")
def make_period_index(df):
df = df.copy()
possible_qcols = [
c for c in df.columns
if str(c).lower() in ["quarter", "date", "time", "period"]
]
qcol = possible_qcols[0] if len(possible_qcols) > 0 else df.columns[0]
df = df.rename(columns={qcol: "quarter"})
df["quarter"] = df["quarter"].apply(parse_quarter)
df = df.set_index("quarter").sort_index()
for col in df.columns:
df[col] = pd.to_numeric(df[col], errors="coerce")
return df
def ensure_quarter_index(df):
df = df.copy()
if isinstance(df.index, pd.PeriodIndex):
df.index = df.index.asfreq("Q")
elif isinstance(df.index, pd.DatetimeIndex):
df.index = df.index.to_period("Q")
elif "quarter" in df.columns:
df["quarter"] = df["quarter"].apply(parse_quarter)
df = df.set_index("quarter")
else:
df.index = [parse_quarter(x) for x in df.index]
df = df.sort_index()
for col in df.columns:
df[col] = pd.to_numeric(df[col], errors="coerce")
return df
def cut_sample(df):
return df.loc[
(df.index >= pd.Period(START_QUARTER, freq="Q")) &
(df.index <= pd.Period(END_QUARTER, freq="Q"))
].copy()
def read_quarterly_excel(path, sheet_candidates=None):
path = Path(path)
if not path.exists():
raise FileNotFoundError(f"Missing file: {path}")
xls = pd.ExcelFile(path)
if sheet_candidates is None:
sheet_candidates = []
chosen_sheet = None
for sh in sheet_candidates:
if sh in xls.sheet_names:
chosen_sheet = sh
break
if chosen_sheet is None:
chosen_sheet = xls.sheet_names[0]
df = pd.read_excel(path, sheet_name=chosen_sheet)
df = make_period_index(df)
df = cut_sample(df)
return df
def get_factors_from_existing_object():
if "common_factors" in globals():
factors = common_factors.copy()
elif "common_factor_data" in globals() and "factors" in common_factor_data:
factors = common_factor_data["factors"].copy()
else:
raise NameError(
"Nem találom a `common_factors` objektumot. "
"Előbb futtasd le a közös faktorokat letöltő cellát."
)
factors = ensure_quarter_index(factors)
factors = cut_sample(factors)
return factors
def find_column(df, aliases):
lower_map = {str(c).lower(): c for c in df.columns}
for a in aliases:
if a.lower() in lower_map:
return lower_map[a.lower()]
raise KeyError(
f"Egyik alias sem található: {aliases}. "
f"Elérhető oszlopok: {df.columns.tolist()}"
)
# ============================================================
# 3. FIXED LAMBDA2 VALUES
# ============================================================
# These values were selected in a previous lambda2 optimization step
# and are hard-coded here to avoid re-reading the Excel file during estimation.
FIXED_LAMBDA2 = {
('Belgium', 'baseline_same_sample', 'eurusd_dlog', 2): 507.173411693,
('Belgium', 'factor', 'eurusd_dlog', 2): 492.486777172,
('Belgium', 'baseline_same_sample', 'ea_share_price_dlog', 2): 854.241235121,
('Belgium', 'factor', 'ea_share_price_dlog', 2): 946.087001125,
('Belgium', 'baseline_same_sample', 'eurstock_logreturn', 0): 457.108029957,
('Belgium', 'factor', 'eurstock_logreturn', 0): 314.667555053,
('Belgium', 'baseline_same_sample', 'ea_unemployment_std', 0): 889.252366917,
('Belgium', 'factor', 'ea_unemployment_std', 0): 845.370674413,
('Belgium', 'baseline_same_sample', 'ciss_std', 0): 867.755081688,
('Belgium', 'factor', 'ciss_std', 0): 895.008842094,
('Belgium', 'baseline_same_sample', 'brent_dlog', 0): 867.755081688,
('Belgium', 'factor', 'brent_dlog', 0): 904.54127239,
('Belgium', 'baseline_same_sample', 'ea_3m_rate_change', 0): 867.755081688,
('Belgium', 'factor', 'ea_3m_rate_change', 0): 1046.53445263,
('Czechia', 'baseline_same_sample', 'eurusd_dlog', 2): 44.8477218791,
('Czechia', 'factor', 'eurusd_dlog', 2): 50.9261239781,
('Czechia', 'baseline_same_sample', 'ea_share_price_dlog', 2): 63.8108970858,
('Czechia', 'factor', 'ea_share_price_dlog', 2): 61.519245693,
('Czechia', 'baseline_same_sample', 'eurstock_logreturn', 0): 121.378424293,
('Czechia', 'factor', 'eurstock_logreturn', 0): 68.8625627266,
('Czechia', 'baseline_same_sample', 'ea_unemployment_std', 0): 58.5208433557,
('Czechia', 'factor', 'ea_unemployment_std', 0): 11.7047855624,
('Czechia', 'baseline_same_sample', 'ciss_std', 0): 57.4456587304,
('Czechia', 'factor', 'ciss_std', 0): 31.5871667558,
('Czechia', 'baseline_same_sample', 'brent_dlog', 0): 57.4456587304,
('Czechia', 'factor', 'brent_dlog', 0): 55.2683152809,
('Czechia', 'baseline_same_sample', 'ea_3m_rate_change', 0): 57.4456587304,
('Czechia', 'factor', 'ea_3m_rate_change', 0): 56.0026127449,
('Denmark', 'baseline_same_sample', 'eurusd_dlog', 2): 109.819715976,
('Denmark', 'factor', 'eurusd_dlog', 2): 111.558576352,
('Denmark', 'baseline_same_sample', 'ea_share_price_dlog', 2): 153.852456025,
('Denmark', 'factor', 'ea_share_price_dlog', 2): 163.017744936,
('Denmark', 'baseline_same_sample', 'eurstock_logreturn', 0): 255.99078417,
('Denmark', 'factor', 'eurstock_logreturn', 0): 162.224307989,
('Denmark', 'baseline_same_sample', 'ea_unemployment_std', 0): 195.397378471,
('Denmark', 'factor', 'ea_unemployment_std', 0): 90.933086269,
('Denmark', 'baseline_same_sample', 'ciss_std', 0): 191.879335637,
('Denmark', 'factor', 'ciss_std', 0): 140.792885273,
('Denmark', 'baseline_same_sample', 'brent_dlog', 0): 191.879335637,
('Denmark', 'factor', 'brent_dlog', 0): 205.536413104,
('Denmark', 'baseline_same_sample', 'ea_3m_rate_change', 0): 191.879335637,
('Denmark', 'factor', 'ea_3m_rate_change', 0): 221.619796202,
('Germany', 'baseline_same_sample', 'eurusd_dlog', 2): 202.813089788,
('Germany', 'factor', 'eurusd_dlog', 2): 208.806203016,
('Germany', 'baseline_same_sample', 'ea_share_price_dlog', 2): 247.626263816,
('Germany', 'factor', 'ea_share_price_dlog', 2): 224.185858665,
('Germany', 'baseline_same_sample', 'eurstock_logreturn', 0): 254.830770689,
('Germany', 'factor', 'eurstock_logreturn', 0): 111.596794376,
('Germany', 'baseline_same_sample', 'ea_unemployment_std', 0): 244.256734416,
('Germany', 'factor', 'ea_unemployment_std', 0): 173.608462663,
('Germany', 'baseline_same_sample', 'ciss_std', 0): 256.202100153,
('Germany', 'factor', 'ciss_std', 0): 223.091068625,
('Germany', 'baseline_same_sample', 'brent_dlog', 0): 256.202100153,
('Germany', 'factor', 'brent_dlog', 0): 278.237664892,
('Germany', 'baseline_same_sample', 'ea_3m_rate_change', 0): 256.202100153,
('Germany', 'factor', 'ea_3m_rate_change', 0): 312.18151094,
('Estonia', 'baseline_same_sample', 'eurusd_dlog', 2): 72.5117050858,
('Estonia', 'factor', 'eurusd_dlog', 2): 69.5136216988,
('Estonia', 'baseline_same_sample', 'ea_share_price_dlog', 2): 48.3376837094,
('Estonia', 'factor', 'ea_share_price_dlog', 2): 59.5766089548,
('Estonia', 'baseline_same_sample', 'eurstock_logreturn', 0): 59.3250678735,
('Estonia', 'factor', 'eurstock_logreturn', 0): 33.8852968149,
('Estonia', 'baseline_same_sample', 'ea_unemployment_std', 0): 64.6979267929,
('Estonia', 'factor', 'ea_unemployment_std', 0): 12.9402673457,
('Estonia', 'baseline_same_sample', 'ciss_std', 0): 63.0514032078,
('Estonia', 'factor', 'ciss_std', 0): 35.2605209738,
('Estonia', 'baseline_same_sample', 'brent_dlog', 0): 63.0514032078,
('Estonia', 'factor', 'brent_dlog', 0): 61.9124566502,
('Estonia', 'baseline_same_sample', 'ea_3m_rate_change', 0): 63.0514032078,
('Estonia', 'factor', 'ea_3m_rate_change', 0): 69.6290211243,
('Ireland', 'baseline_same_sample', 'eurusd_dlog', 2): 85.8884321046,
('Ireland', 'factor', 'eurusd_dlog', 2): 83.8172238123,
('Ireland', 'baseline_same_sample', 'ea_share_price_dlog', 2): 85.359077027,
('Ireland', 'factor', 'ea_share_price_dlog', 2): 90.1806828354,
('Ireland', 'baseline_same_sample', 'eurstock_logreturn', 0): 156.934341982,
('Ireland', 'factor', 'eurstock_logreturn', 0): 141.640056959,
('Ireland', 'baseline_same_sample', 'ea_unemployment_std', 0): 119.813859295,
('Ireland', 'factor', 'ea_unemployment_std', 0): 52.7198939955,
('Ireland', 'baseline_same_sample', 'ciss_std', 0): 109.089146513,
('Ireland', 'factor', 'ciss_std', 0): 76.8779015035,
('Ireland', 'baseline_same_sample', 'brent_dlog', 0): 109.089146513,
('Ireland', 'factor', 'brent_dlog', 0): 87.3254816969,
('Ireland', 'baseline_same_sample', 'ea_3m_rate_change', 0): 109.089146513,
('Ireland', 'factor', 'ea_3m_rate_change', 0): 79.3171187853,
('Greece', 'baseline_same_sample', 'eurusd_dlog', 2): 52.6842105729,
('Greece', 'factor', 'eurusd_dlog', 2): 52.5925002065,
('Greece', 'baseline_same_sample', 'ea_share_price_dlog', 2): 57.1777332339,
('Greece', 'factor', 'ea_share_price_dlog', 2): 57.6479431878,
('Greece', 'baseline_same_sample', 'eurstock_logreturn', 0): 108.167027035,
('Greece', 'factor', 'eurstock_logreturn', 0): 54.836020821,
('Greece', 'baseline_same_sample', 'ea_unemployment_std', 0): 58.1639617645,
('Greece', 'factor', 'ea_unemployment_std', 0): 49.3883562494,
('Greece', 'baseline_same_sample', 'ciss_std', 0): 56.7496187441,
('Greece', 'factor', 'ciss_std', 0): 28.5508229156,
('Greece', 'baseline_same_sample', 'brent_dlog', 0): 56.7496187441,
('Greece', 'factor', 'brent_dlog', 0): 56.7574022754,
('Greece', 'baseline_same_sample', 'ea_3m_rate_change', 0): 56.7496187441,
('Greece', 'factor', 'ea_3m_rate_change', 0): 56.7032552071,
('Spain', 'baseline_same_sample', 'eurusd_dlog', 2): 146.471524172,
('Spain', 'factor', 'eurusd_dlog', 2): 142.572595005,
('Spain', 'baseline_same_sample', 'ea_share_price_dlog', 2): 217.890005146,
('Spain', 'factor', 'ea_share_price_dlog', 2): 207.930703635,
('Spain', 'baseline_same_sample', 'eurstock_logreturn', 0): 239.460126109,
('Spain', 'factor', 'eurstock_logreturn', 0): 151.056918536,
('Spain', 'baseline_same_sample', 'ea_unemployment_std', 0): 246.057503934,
('Spain', 'factor', 'ea_unemployment_std', 0): 216.577462158,
('Spain', 'baseline_same_sample', 'ciss_std', 0): 217.559265102,
('Spain', 'factor', 'ciss_std', 0): 202.430956814,
('Spain', 'baseline_same_sample', 'brent_dlog', 0): 217.559265102,
('Spain', 'factor', 'brent_dlog', 0): 223.927414367,
('Spain', 'baseline_same_sample', 'ea_3m_rate_change', 0): 217.559265102,
('Spain', 'factor', 'ea_3m_rate_change', 0): 223.323842357,
('France', 'baseline_same_sample', 'eurusd_dlog', 2): 381.952894897,
('France', 'factor', 'eurusd_dlog', 2): 375.519272565,
('France', 'baseline_same_sample', 'ea_share_price_dlog', 2): 1235.33298636,
('France', 'factor', 'ea_share_price_dlog', 2): 1306.79208404,
('France', 'baseline_same_sample', 'eurstock_logreturn', 0): 400.641160069,
('France', 'factor', 'eurstock_logreturn', 0): 285.687838468,
('France', 'baseline_same_sample', 'ea_unemployment_std', 0): 1469.43471432,
('France', 'factor', 'ea_unemployment_std', 0): 1375.03886927,
('France', 'baseline_same_sample', 'ciss_std', 0): 1408.74028895,
('France', 'factor', 'ciss_std', 0): 1458.67463425,
('France', 'baseline_same_sample', 'brent_dlog', 0): 1408.74028895,
('France', 'factor', 'brent_dlog', 0): 1377.45336846,
('France', 'baseline_same_sample', 'ea_3m_rate_change', 0): 1408.74028895,
('France', 'factor', 'ea_3m_rate_change', 0): 1638.06142747,
('Croatia', 'baseline_same_sample', 'eurusd_dlog', 2): 73.8852350533,
('Croatia', 'factor', 'eurusd_dlog', 2): 74.8886524676,
('Croatia', 'baseline_same_sample', 'ea_share_price_dlog', 2): 54.0968650362,
('Croatia', 'factor', 'ea_share_price_dlog', 2): 43.7788636589,
('Croatia', 'baseline_same_sample', 'eurstock_logreturn', 0): 339.011974431,
('Croatia', 'factor', 'eurstock_logreturn', 0): 103.557484297,
('Croatia', 'baseline_same_sample', 'ea_unemployment_std', 0): 111.724239101,
('Croatia', 'factor', 'ea_unemployment_std', 0): 119.949963786,
('Croatia', 'baseline_same_sample', 'ciss_std', 0): 108.100108766,
('Croatia', 'factor', 'ciss_std', 0): 83.9602807023,
('Croatia', 'baseline_same_sample', 'brent_dlog', 0): 108.100108766,
('Croatia', 'factor', 'brent_dlog', 0): 107.910435762,
('Croatia', 'baseline_same_sample', 'ea_3m_rate_change', 0): 108.100108766,
('Croatia', 'factor', 'ea_3m_rate_change', 0): 107.294051006,
('Latvia', 'baseline_same_sample', 'eurusd_dlog', 2): 57.5961974791,
('Latvia', 'factor', 'eurusd_dlog', 2): 71.042274873,
('Latvia', 'baseline_same_sample', 'ea_share_price_dlog', 2): 56.7549369041,
('Latvia', 'factor', 'ea_share_price_dlog', 2): 52.5573690913,
('Latvia', 'baseline_same_sample', 'eurstock_logreturn', 0): 54.3662184788,
('Latvia', 'factor', 'eurstock_logreturn', 0): 18.0133847833,
('Latvia', 'baseline_same_sample', 'ea_unemployment_std', 0): 59.5395323805,
('Latvia', 'factor', 'ea_unemployment_std', 0): 11.9085341027,
('Latvia', 'baseline_same_sample', 'ciss_std', 0): 59.2492977948,
('Latvia', 'factor', 'ciss_std', 0): 26.0196989788,
('Latvia', 'baseline_same_sample', 'brent_dlog', 0): 59.2492977948,
('Latvia', 'factor', 'brent_dlog', 0): 57.8448875487,
('Latvia', 'baseline_same_sample', 'ea_3m_rate_change', 0): 59.2492977948,
('Latvia', 'factor', 'ea_3m_rate_change', 0): 59.1388494777,
('Lithuania', 'baseline_same_sample', 'eurusd_dlog', 2): 54.2999699375,
('Lithuania', 'factor', 'eurusd_dlog', 2): 73.0257910544,
('Lithuania', 'baseline_same_sample', 'ea_share_price_dlog', 2): 48.2578439768,
('Lithuania', 'factor', 'ea_share_price_dlog', 2): 55.4439564297,
('Lithuania', 'baseline_same_sample', 'eurstock_logreturn', 0): 73.0273133694,
('Lithuania', 'factor', 'eurstock_logreturn', 0): 58.3012266568,
('Lithuania', 'baseline_same_sample', 'ea_unemployment_std', 0): 55.6024389953,
('Lithuania', 'factor', 'ea_unemployment_std', 0): 11.1210739347,
('Lithuania', 'baseline_same_sample', 'ciss_std', 0): 50.7613671611,
('Lithuania', 'factor', 'ciss_std', 0): 19.1312314329,
('Lithuania', 'baseline_same_sample', 'brent_dlog', 0): 50.7613671611,
('Lithuania', 'factor', 'brent_dlog', 0): 34.8352759488,
('Lithuania', 'baseline_same_sample', 'ea_3m_rate_change', 0): 50.7613671611,
('Lithuania', 'factor', 'ea_3m_rate_change', 0): 60.3844352596,
('Luxembourg', 'baseline_same_sample', 'eurusd_dlog', 2): 153.643175797,
('Luxembourg', 'factor', 'eurusd_dlog', 2): 155.451654628,
('Luxembourg', 'baseline_same_sample', 'ea_share_price_dlog', 2): 170.850149487,
('Luxembourg', 'factor', 'ea_share_price_dlog', 2): 165.021749641,
('Luxembourg', 'baseline_same_sample', 'eurstock_logreturn', 0): 385.526870607,
('Luxembourg', 'factor', 'eurstock_logreturn', 0): 239.229919947,
('Luxembourg', 'baseline_same_sample', 'ea_unemployment_std', 0): 233.129101907,
('Luxembourg', 'factor', 'ea_unemployment_std', 0): 77.1723322689,
('Luxembourg', 'baseline_same_sample', 'ciss_std', 0): 242.868489641,
('Luxembourg', 'factor', 'ciss_std', 0): 244.213445216,
('Luxembourg', 'baseline_same_sample', 'brent_dlog', 0): 242.868489641,
('Luxembourg', 'factor', 'brent_dlog', 0): 211.2535277,
('Luxembourg', 'baseline_same_sample', 'ea_3m_rate_change', 0): 242.868489641,
('Luxembourg', 'factor', 'ea_3m_rate_change', 0): 252.584135294,
('Hungary', 'baseline_same_sample', 'eurusd_dlog', 2): 146.340758195,
('Hungary', 'factor', 'eurusd_dlog', 2): 145.344024047,
('Hungary', 'baseline_same_sample', 'ea_share_price_dlog', 2): 259.149824686,
('Hungary', 'factor', 'ea_share_price_dlog', 2): 278.935212749,
('Hungary', 'baseline_same_sample', 'eurstock_logreturn', 0): 349.181592272,
('Hungary', 'factor', 'eurstock_logreturn', 0): 154.16360338,
('Hungary', 'baseline_same_sample', 'ea_unemployment_std', 0): 246.434443623,
('Hungary', 'factor', 'ea_unemployment_std', 0): 187.861601635,
('Hungary', 'baseline_same_sample', 'ciss_std', 0): 235.936561261,
('Hungary', 'factor', 'ciss_std', 0): 177.669539869,
('Hungary', 'baseline_same_sample', 'brent_dlog', 0): 235.936561261,
('Hungary', 'factor', 'brent_dlog', 0): 246.520462996,
('Hungary', 'baseline_same_sample', 'ea_3m_rate_change', 0): 235.936561261,
('Hungary', 'factor', 'ea_3m_rate_change', 0): 257.347919851,
('Austria', 'baseline_same_sample', 'eurusd_dlog', 2): 303.618275521,
('Austria', 'factor', 'eurusd_dlog', 2): 289.289735276,
('Austria', 'baseline_same_sample', 'ea_share_price_dlog', 2): 688.401573283,
('Austria', 'factor', 'ea_share_price_dlog', 2): 662.501443745,
('Austria', 'baseline_same_sample', 'eurstock_logreturn', 0): 315.066576389,
('Austria', 'factor', 'eurstock_logreturn', 0): 237.382631201,
('Austria', 'baseline_same_sample', 'ea_unemployment_std', 0): 548.975431394,
('Austria', 'factor', 'ea_unemployment_std', 0): 547.78804898,
('Austria', 'baseline_same_sample', 'ciss_std', 0): 693.974908714,
('Austria', 'factor', 'ciss_std', 0): 620.075469927,
('Austria', 'baseline_same_sample', 'brent_dlog', 0): 693.974908714,
('Austria', 'factor', 'brent_dlog', 0): 704.105009776,
('Austria', 'baseline_same_sample', 'ea_3m_rate_change', 0): 693.974908714,
('Austria', 'factor', 'ea_3m_rate_change', 0): 801.707003556,
('Poland', 'baseline_same_sample', 'eurusd_dlog', 2): 79.5714895728,
('Poland', 'factor', 'eurusd_dlog', 2): 69.8617475624,
('Poland', 'baseline_same_sample', 'ea_share_price_dlog', 2): 99.5425674437,
('Poland', 'factor', 'ea_share_price_dlog', 2): 107.385849744,
('Poland', 'baseline_same_sample', 'eurstock_logreturn', 0): 294.022626121,
('Poland', 'factor', 'eurstock_logreturn', 0): 60.901963806,
('Poland', 'baseline_same_sample', 'ea_unemployment_std', 0): 52.2652526835,
('Poland', 'factor', 'ea_unemployment_std', 0): 43.7388314118,
('Poland', 'baseline_same_sample', 'ciss_std', 0): 52.036437017,
('Poland', 'factor', 'ciss_std', 0): 37.3274586767,
('Poland', 'baseline_same_sample', 'brent_dlog', 0): 52.036437017,
('Poland', 'factor', 'brent_dlog', 0): 56.2414993864,
('Poland', 'baseline_same_sample', 'ea_3m_rate_change', 0): 52.036437017,
('Poland', 'factor', 'ea_3m_rate_change', 0): 47.9863509301,
('Portugal', 'baseline_same_sample', 'eurusd_dlog', 2): 220.430508004,
('Portugal', 'factor', 'eurusd_dlog', 2): 211.72186705,
('Portugal', 'baseline_same_sample', 'ea_share_price_dlog', 2): 235.083112024,
('Portugal', 'factor', 'ea_share_price_dlog', 2): 215.796345833,
('Portugal', 'baseline_same_sample', 'eurstock_logreturn', 0): 313.471876132,
('Portugal', 'factor', 'eurstock_logreturn', 0): 244.502052552,
('Portugal', 'baseline_same_sample', 'ea_unemployment_std', 0): 276.52134625,
('Portugal', 'factor', 'ea_unemployment_std', 0): 279.94402073,
('Portugal', 'baseline_same_sample', 'ciss_std', 0): 261.086490493,
('Portugal', 'factor', 'ciss_std', 0): 253.184508746,
('Portugal', 'baseline_same_sample', 'brent_dlog', 0): 261.086490493,
('Portugal', 'factor', 'brent_dlog', 0): 267.056141181,
('Portugal', 'baseline_same_sample', 'ea_3m_rate_change', 0): 261.086490493,
('Portugal', 'factor', 'ea_3m_rate_change', 0): 282.326760531,
('Romania', 'baseline_same_sample', 'eurusd_dlog', 2): 87.6373693978,
('Romania', 'factor', 'eurusd_dlog', 2): 89.4644296549,
('Romania', 'baseline_same_sample', 'ea_share_price_dlog', 2): 181.381374551,
('Romania', 'factor', 'ea_share_price_dlog', 2): 187.771656431,
('Romania', 'baseline_same_sample', 'eurstock_logreturn', 0): 123.351471252,
('Romania', 'factor', 'eurstock_logreturn', 0): 75.5436874814,
('Romania', 'baseline_same_sample', 'ea_unemployment_std', 0): 187.922419454,
('Romania', 'factor', 'ea_unemployment_std', 0): 71.4629524282,
('Romania', 'baseline_same_sample', 'ciss_std', 0): 187.272285097,
('Romania', 'factor', 'ciss_std', 0): 163.809816369,
('Romania', 'baseline_same_sample', 'brent_dlog', 0): 187.272285097,
('Romania', 'factor', 'brent_dlog', 0): 153.251011702,
('Romania', 'baseline_same_sample', 'ea_3m_rate_change', 0): 187.272285097,
('Romania', 'factor', 'ea_3m_rate_change', 0): 165.578664006,
('Slovakia', 'baseline_same_sample', 'eurusd_dlog', 2): 60.3332720135,
('Slovakia', 'factor', 'eurusd_dlog', 2): 66.9892095147,
('Slovakia', 'baseline_same_sample', 'ea_share_price_dlog', 2): 79.84749151,
('Slovakia', 'factor', 'ea_share_price_dlog', 2): 80.3614187785,
('Slovakia', 'baseline_same_sample', 'eurstock_logreturn', 0): 1429.32240245,
('Slovakia', 'factor', 'eurstock_logreturn', 0): 684.096395112,
('Slovakia', 'baseline_same_sample', 'ea_unemployment_std', 0): 84.3351934719,
('Slovakia', 'factor', 'ea_unemployment_std', 0): 78.5895941101,
('Slovakia', 'baseline_same_sample', 'ciss_std', 0): 83.2956772296,
('Slovakia', 'factor', 'ciss_std', 0): 78.8474119006,
('Slovakia', 'baseline_same_sample', 'brent_dlog', 0): 83.2956772296,
('Slovakia', 'factor', 'brent_dlog', 0): 82.6350418891,
('Slovakia', 'baseline_same_sample', 'ea_3m_rate_change', 0): 83.2956772296,
('Slovakia', 'factor', 'ea_3m_rate_change', 0): 87.3514461595,
('Finland', 'baseline_same_sample', 'eurusd_dlog', 2): 80.1804970156,
('Finland', 'factor', 'eurusd_dlog', 2): 80.6167013575,
('Finland', 'baseline_same_sample', 'ea_share_price_dlog', 2): 54.6621519046,
('Finland', 'factor', 'ea_share_price_dlog', 2): 60.2943976203,
('Finland', 'baseline_same_sample', 'eurstock_logreturn', 0): 160.029597158,
('Finland', 'factor', 'eurstock_logreturn', 0): 47.3315194147,
('Finland', 'baseline_same_sample', 'ea_unemployment_std', 0): 56.3097984012,
('Finland', 'factor', 'ea_unemployment_std', 0): 11.2633937616,
('Finland', 'baseline_same_sample', 'ciss_std', 0): 53.3654030645,
('Finland', 'factor', 'ciss_std', 0): 29.4094866617,
('Finland', 'baseline_same_sample', 'brent_dlog', 0): 53.3654030645,
('Finland', 'factor', 'brent_dlog', 0): 67.6557893015,
('Finland', 'baseline_same_sample', 'ea_3m_rate_change', 0): 53.3654030645,
('Finland', 'factor', 'ea_3m_rate_change', 0): 92.1501416174,
('Sweden', 'baseline_same_sample', 'eurusd_dlog', 2): 155.1146483,
('Sweden', 'factor', 'eurusd_dlog', 2): 154.718536121,
('Sweden', 'baseline_same_sample', 'ea_share_price_dlog', 2): 188.914089989,
('Sweden', 'factor', 'ea_share_price_dlog', 2): 248.05716248,
('Sweden', 'baseline_same_sample', 'eurstock_logreturn', 0): 272.44595378,
('Sweden', 'factor', 'eurstock_logreturn', 0): 86.8728918285,
('Sweden', 'baseline_same_sample', 'ea_unemployment_std', 0): 269.987920981,
('Sweden', 'factor', 'ea_unemployment_std', 0): 72.8068376913,
('Sweden', 'baseline_same_sample', 'ciss_std', 0): 272.156640014,
('Sweden', 'factor', 'ciss_std', 0): 316.447402016,
('Sweden', 'baseline_same_sample', 'brent_dlog', 0): 272.156640014,
('Sweden', 'factor', 'brent_dlog', 0): 240.197345262,
('Sweden', 'baseline_same_sample', 'ea_3m_rate_change', 0): 272.156640014,
('Sweden', 'factor', 'ea_3m_rate_change', 0): 304.414084505,
('Iceland', 'baseline_same_sample', 'eurusd_dlog', 2): 86.9017375221,
('Iceland', 'factor', 'eurusd_dlog', 2): 87.8076379036,
('Iceland', 'baseline_same_sample', 'ea_share_price_dlog', 2): 156.238626888,
('Iceland', 'factor', 'ea_share_price_dlog', 2): 150.861126588,
('Iceland', 'baseline_same_sample', 'eurstock_logreturn', 0): 197.059175067,
('Iceland', 'factor', 'eurstock_logreturn', 0): 169.27180828,
('Iceland', 'baseline_same_sample', 'ea_unemployment_std', 0): 150.172852243,
('Iceland', 'factor', 'ea_unemployment_std', 0): 165.479088998,
('Iceland', 'baseline_same_sample', 'ciss_std', 0): 153.490087927,
('Iceland', 'factor', 'ciss_std', 0): 100.588899071,
('Iceland', 'baseline_same_sample', 'brent_dlog', 0): 153.490087927,
('Iceland', 'factor', 'brent_dlog', 0): 148.982052877,
('Iceland', 'baseline_same_sample', 'ea_3m_rate_change', 0): 153.490087927,
('Iceland', 'factor', 'ea_3m_rate_change', 0): 165.207919677,
('Norway', 'baseline_same_sample', 'eurusd_dlog', 2): 99.2681284441,
('Norway', 'factor', 'eurusd_dlog', 2): 95.0290546232,
('Norway', 'baseline_same_sample', 'ea_share_price_dlog', 2): 84.5933817038,
('Norway', 'factor', 'ea_share_price_dlog', 2): 92.9505827776,
('Norway', 'baseline_same_sample', 'eurstock_logreturn', 0): 292.753963586,
('Norway', 'factor', 'eurstock_logreturn', 0): 192.072006107,
('Norway', 'baseline_same_sample', 'ea_unemployment_std', 0): 68.3575022266,
('Norway', 'factor', 'ea_unemployment_std', 0): 49.541923961,
('Norway', 'baseline_same_sample', 'ciss_std', 0): 72.7884401877,
('Norway', 'factor', 'ciss_std', 0): 76.3213150771,
('Norway', 'baseline_same_sample', 'brent_dlog', 0): 72.7884401877,
('Norway', 'factor', 'brent_dlog', 0): 66.8526850819,
('Norway', 'baseline_same_sample', 'ea_3m_rate_change', 0): 72.7884401877,
('Norway', 'factor', 'ea_3m_rate_change', 0): 81.5107240897,
('Switzerland', 'baseline_same_sample', 'eurusd_dlog', 2): 79.3979581788,
('Switzerland', 'factor', 'eurusd_dlog', 2): 97.7938752074,
('Switzerland', 'baseline_same_sample', 'ea_share_price_dlog', 2): 179.836926262,
('Switzerland', 'factor', 'ea_share_price_dlog', 2): 157.438317146,
('Switzerland', 'baseline_same_sample', 'eurstock_logreturn', 0): 945.408564516,
('Switzerland', 'factor', 'eurstock_logreturn', 0): 640.691899752,
('Switzerland', 'baseline_same_sample', 'ea_unemployment_std', 0): 168.323173776,
('Switzerland', 'factor', 'ea_unemployment_std', 0): 152.625474143,
('Switzerland', 'baseline_same_sample', 'ciss_std', 0): 180.311066577,
('Switzerland', 'factor', 'ciss_std', 0): 172.950864264,
('Switzerland', 'baseline_same_sample', 'brent_dlog', 0): 180.311066577,
('Switzerland', 'factor', 'brent_dlog', 0): 177.497828233,
('Switzerland', 'baseline_same_sample', 'ea_3m_rate_change', 0): 180.311066577,
('Switzerland', 'factor', 'ea_3m_rate_change', 0): 186.793088539,
('Serbia', 'baseline_same_sample', 'eurusd_dlog', 2): 86.8446404726,
('Serbia', 'factor', 'eurusd_dlog', 2): 90.1619998022,
('Serbia', 'baseline_same_sample', 'ea_share_price_dlog', 2): 199.834552548,
('Serbia', 'factor', 'ea_share_price_dlog', 2): 201.5219784,
('Serbia', 'baseline_same_sample', 'eurstock_logreturn', 0): 226.522950413,
('Serbia', 'factor', 'eurstock_logreturn', 0): 144.02255649,
('Serbia', 'baseline_same_sample', 'ea_unemployment_std', 0): 292.277799652,
('Serbia', 'factor', 'ea_unemployment_std', 0): 274.457570337,
('Serbia', 'baseline_same_sample', 'ciss_std', 0): 292.766928907,
('Serbia', 'factor', 'ciss_std', 0): 312.796180818,
('Serbia', 'baseline_same_sample', 'brent_dlog', 0): 292.766928907,
('Serbia', 'factor', 'brent_dlog', 0): 280.751075397,
('Serbia', 'baseline_same_sample', 'ea_3m_rate_change', 0): 292.766928907,
('Serbia', 'factor', 'ea_3m_rate_change', 0): 292.639402413,
('Türkiye', 'baseline_same_sample', 'eurusd_dlog', 2): 131.728103997,
('Türkiye', 'factor', 'eurusd_dlog', 2): 132.085152496,
('Türkiye', 'baseline_same_sample', 'ea_share_price_dlog', 2): 88.5304472859,
('Türkiye', 'factor', 'ea_share_price_dlog', 2): 94.4914082755,
('Türkiye', 'baseline_same_sample', 'eurstock_logreturn', 0): 486.2356832,
('Türkiye', 'factor', 'eurstock_logreturn', 0): 262.419485925,
('Türkiye', 'baseline_same_sample', 'ea_unemployment_std', 0): 79.6893068034,
('Türkiye', 'factor', 'ea_unemployment_std', 0): 30.6663054082,
('Türkiye', 'baseline_same_sample', 'ciss_std', 0): 80.4575090559,
('Türkiye', 'factor', 'ciss_std', 0): 78.1869155856,
('Türkiye', 'baseline_same_sample', 'brent_dlog', 0): 80.4575090559,
('Türkiye', 'factor', 'brent_dlog', 0): 81.4840196773,
('Türkiye', 'baseline_same_sample', 'ea_3m_rate_change', 0): 80.4575090559,
('Türkiye', 'factor', 'ea_3m_rate_change', 0): 87.1369857944,
}
def get_fixed_lambda(country, model_type, factor_id, lag):
key = (str(country), str(model_type), str(factor_id), int(lag))
if key not in FIXED_LAMBDA2:
raise KeyError(
f"No hard-coded lambda2 found for "
f"country={country}, model_type={model_type}, factor_id={factor_id}, lag={lag}"
)
return float(FIXED_LAMBDA2[key])
# ============================================================
# 4. STATE-SPACE MODELS
# ============================================================
class BorioBaselineModel(MLEModel):
"""
y_t = potential_t + gap_t
potential_t = 2 potential_{t-1} - potential_{t-2} + eta_t
gap_t = beta gap_{t-1} + u_t
"""
param_names = ["beta"]
def __init__(self, y, init_state, init_state_cov, sigma2_eta, sigma2_gap):
self.sigma2_eta = float(sigma2_eta)
self.sigma2_gap = float(sigma2_gap)
super().__init__(
endog=np.asarray(y, dtype=float),
k_states=3,
k_posdef=2,
initialization="known",
initial_state=init_state,
initial_state_cov=init_state_cov,
loglikelihood_burn=2,
)
self.ssm["design"] = np.array([[1.0, 0.0, 1.0]])
self.ssm["obs_cov"] = np.zeros((1, 1))
self.ssm["selection"] = np.array([
[1.0, 0.0],
[0.0, 0.0],
[0.0, 1.0],
])
self.ssm["state_cov"] = np.diag([
self.sigma2_eta,
self.sigma2_gap,
])
self.ssm["transition"] = np.zeros((3, 3))
self.ssm["state_intercept"] = np.zeros((3, self.nobs))
def update(self, params, transformed=True, **kwargs):
params = super().update(params, transformed=transformed, **kwargs)
beta = params[0]
dtype = np.result_type(params)
self.ssm["transition"] = np.array([
[2.0, -1.0, 0.0],
[1.0, 0.0, 0.0],
[0.0, 0.0, beta],
], dtype=dtype)
self.ssm["state_intercept"] = np.zeros((3, self.nobs), dtype=dtype)
class BorioFactorModel(MLEModel):
"""
y_t = potential_t + gap_t
potential_t = 2 potential_{t-1} - potential_{t-2} + eta_t
gap_t = beta gap_{t-1} + gamma x_{t-lag} + u_t
"""
param_names = ["beta", "gamma"]
def __init__(self, y, x, init_state, init_state_cov, sigma2_eta, sigma2_gap):
self.x = np.asarray(x, dtype=float)
self.sigma2_eta = float(sigma2_eta)
self.sigma2_gap = float(sigma2_gap)
super().__init__(
endog=np.asarray(y, dtype=float),
k_states=3,
k_posdef=2,
initialization="known",
initial_state=init_state,
initial_state_cov=init_state_cov,
loglikelihood_burn=2,
)
self.ssm["design"] = np.array([[1.0, 0.0, 1.0]])
self.ssm["obs_cov"] = np.zeros((1, 1))
self.ssm["selection"] = np.array([
[1.0, 0.0],
[0.0, 0.0],
[0.0, 1.0],
])
self.ssm["state_cov"] = np.diag([
self.sigma2_eta,
self.sigma2_gap,
])
self.ssm["transition"] = np.zeros((3, 3))
self.ssm["state_intercept"] = np.zeros((3, self.nobs))
def update(self, params, transformed=True, **kwargs):
params = super().update(params, transformed=transformed, **kwargs)
beta, gamma = params
dtype = np.result_type(params)
self.ssm["transition"] = np.array([
[2.0, -1.0, 0.0],
[1.0, 0.0, 0.0],
[0.0, 0.0, beta],
], dtype=dtype)
c = np.zeros((3, self.nobs), dtype=dtype)
c[2, :] = gamma * self.x
self.ssm["state_intercept"] = c
# ============================================================
# 5. HELPERS
# ============================================================
def second_diff(series):
return series.diff().diff()
def gamma_logpdf_from_mean_sd(x, mean, sd):
if x <= 0:
return -np.inf
shape = (mean / sd) ** 2
scale = (sd ** 2) / mean
return gamma_dist.logpdf(x, a=shape, scale=scale)
def standardize_series(s):
s = pd.Series(s).astype(float)
mu = s.mean()
sd = s.std()
if not np.isfinite(sd) or sd <= 0:
return s - mu
return (s - mu) / sd
def significance_stars(p):
if p is None or not np.isfinite(p):
return ""
if p < 0.01:
return "***"
if p < 0.05:
return "**"
if p < 0.10:
return "*"
return ""
def safe_z_p(params, se):
params = np.asarray(params, dtype=float)
se = np.asarray(se, dtype=float)
z = np.full_like(params, np.nan, dtype=float)
p = np.full_like(params, np.nan, dtype=float)
valid = np.isfinite(se) & (se > 0)
z[valid] = params[valid] / se[valid]
p[valid] = 2.0 * (1.0 - norm.cdf(np.abs(z[valid])))
return z, p
def finite_diff_hessian(f, x, rel_step=1e-5):
x = np.asarray(x, dtype=float)
n = len(x)
h = rel_step * np.maximum(np.abs(x), 1.0)
H = np.zeros((n, n))
fx = f(x)
for i in range(n):
ei = np.zeros(n)
ei[i] = h[i]
H[i, i] = (
f(x + ei)
- 2.0 * fx
+ f(x - ei)
) / (h[i] ** 2)
for j in range(i + 1, n):
ej = np.zeros(n)
ej[j] = h[j]
H[i, j] = H[j, i] = (
f(x + ei + ej)
- f(x + ei - ej)
- f(x - ei + ej)
+ f(x - ei - ej)
) / (4.0 * h[i] * h[j])
return H
def safe_standard_errors(neg_obj, params):
try:
H = finite_diff_hessian(neg_obj, params)
if not np.all(np.isfinite(H)):
return np.full(len(params), np.nan)
cov = np.linalg.pinv(H)
diag = np.diag(cov)
return np.sqrt(np.where(diag > 0, diag, np.nan))
except Exception:
return np.full(len(params), np.nan)
# ============================================================
# 6. DATA CONSTRUCTION
# ============================================================
def make_country_factor_data(gdp_df, factors_df, country, factor_col=None, lag=0):
data = pd.DataFrame(index=gdp_df.index)
data["real_gdp"] = gdp_df[country]
data["y"] = 100.0 * np.log(data["real_gdp"])
if factor_col is not None:
data["factor_raw"] = factors_df[factor_col].shift(lag)
if STANDARDIZE_FACTORS:
data["factor"] = standardize_series(data["factor_raw"])
else:
data["factor"] = data["factor_raw"]
data = data.replace([np.inf, -np.inf], np.nan).dropna()
return data
# ============================================================
# 7. MODEL CONSTRUCTION
# ============================================================
def make_model_for_lambda(data, lambda2, model_type):
hp_gap, hp_trend = hpfilter(data["y"], lamb=HP_LAMBDA)
sigma2_gap = hp_gap.diff().var()
sigma2_eta = sigma2_gap / lambda2
if not np.isfinite(sigma2_gap) or sigma2_gap <= 0:
sigma2_gap = 1.0
if not np.isfinite(sigma2_eta) or sigma2_eta <= 0:
sigma2_eta = 1e-4
init_state = np.array([
data["y"].iloc[0],
data["y"].iloc[0],
0.0,
])
init_state_cov = np.diag([
sigma2_eta,
sigma2_eta,
sigma2_gap,
])
if model_type in ["baseline", "baseline_same_sample"]:
model = BorioBaselineModel(
y=data["y"].to_numpy(),
init_state=init_state,
init_state_cov=init_state_cov,
sigma2_eta=sigma2_eta,
sigma2_gap=sigma2_gap,
)
elif model_type == "factor":
model = BorioFactorModel(
y=data["y"].to_numpy(),
x=data["factor"].to_numpy(),
init_state=init_state,
init_state_cov=init_state_cov,
sigma2_eta=sigma2_eta,
sigma2_gap=sigma2_gap,
)
else:
raise ValueError("model_type must be baseline, baseline_same_sample or factor")
denom = second_diff(hp_trend).var()
if np.isfinite(denom) and denom > 0:
target_ratio = hp_gap.var() / denom
else:
target_ratio = np.nan
hp_info = {
"hp_gap": hp_gap,
"hp_trend": hp_trend,
"sigma2_gap": sigma2_gap,
"sigma2_eta": sigma2_eta,
"target_variance_ratio": target_ratio,
}
return model, hp_info
# ============================================================
# 8. ESTIMATION GIVEN FIXED LAMBDA2
# ============================================================
def estimate_given_lambda(data, lambda2, model_type):
model, hp_info = make_model_for_lambda(data, lambda2, model_type)
def logprior(params):
params = np.asarray(params, dtype=float)
beta = params[0]
if beta <= 0 or beta >= BETA_UPPER:
return -np.inf
if not USE_PRIORS:
return 0.0
lp = gamma_logpdf_from_mean_sd(
beta,
mean=BETA_PRIOR_MEAN,
sd=BETA_PRIOR_SD,
)
return lp
def negative_log_posterior(params):
params = np.asarray(params, dtype=float)
beta = params[0]
if beta <= 0 or beta >= BETA_UPPER:
return 1e100
ll = model.loglike(params, transformed=True)
lp = logprior(params)
val = -(ll + lp)
if not np.isfinite(val):
return 1e100
return float(np.real(val))
beta_starts = [
0.50,
0.70,
0.85,
min(0.93, BETA_UPPER - 1e-4),
]
if model_type in ["baseline", "baseline_same_sample"]:
start_grid = [np.array([b]) for b in beta_starts]
bounds = [(1e-8, BETA_UPPER - 1e-8)]
else:
gamma_starts = [-0.5, 0.0, 0.5]
start_grid = [
np.array([b, g])
for b in beta_starts
for g in gamma_starts
]
bounds = [
(1e-8, BETA_UPPER - 1e-8),
(None, None),
]
best_opt = None
for sp in start_grid:
opt = minimize(
negative_log_posterior,
sp,
method="L-BFGS-B",
bounds=bounds,
options={
"maxiter": 2000,
"ftol": 1e-9,
"gtol": 1e-5,
},
)
if best_opt is None or opt.fun < best_opt.fun:
best_opt = opt
params = best_opt.x
smooth_res = model.smooth(params, transformed=True)
states = pd.DataFrame(
smooth_res.smoothed_state.T,
index=data.index,
columns=["potential", "potential_lag", "gap_state"],
)
output_gap = data["y"] - states["potential"]
denom = second_diff(states["potential"]).var()
if np.isfinite(denom) and denom > 0:
achieved_ratio = output_gap.var() / denom
else:
achieved_ratio = np.nan
loglik = float(model.loglike(params, transformed=True))
return {
"model": model,
"opt": best_opt,
"params": params,
"negative_log_posterior": negative_log_posterior,
"loglik": loglik,
"hp_info": hp_info,
"states": states,
"output_gap": output_gap,
"achieved_variance_ratio": achieved_ratio,
}
# ============================================================
# 9. RUN MODEL WITH PREVIOUS LAMBDA2
# ============================================================
def run_model(data, country, model_type, factor_id=None, factor_description=None, lag=None, lambda2=None):
if len(data) < MIN_OBS:
raise ValueError(f"Túl kevés megfigyelés: n={len(data)}")
if lambda2 is None or not np.isfinite(lambda2) or lambda2 <= 0:
raise ValueError(f"Invalid fixed lambda2: {lambda2}")
res = estimate_given_lambda(data, lambda2, model_type)
params = res["params"]
se = safe_standard_errors(res["negative_log_posterior"], params)
z, p = safe_z_p(params, se)
beta = params[0]
if model_type == "factor":
gamma = params[1]
gamma_se = se[1]
gamma_z = z[1]
gamma_p = p[1]
gamma_signif = significance_stars(gamma_p)
else:
gamma = np.nan
gamma_se = np.nan
gamma_z = np.nan
gamma_p = np.nan
gamma_signif = ""
nobs = len(data)
k = len(params)
loglik = res["loglik"]
aic = -2.0 * loglik + 2.0 * k
bic = -2.0 * loglik + np.log(nobs) * k
target = res["hp_info"]["target_variance_ratio"]
achieved = res["achieved_variance_ratio"]
if np.isfinite(target) and np.isfinite(achieved) and target > 0 and achieved > 0:
abs_log_ratio_error = abs(np.log(achieved / target))
else:
abs_log_ratio_error = np.nan
summary = {
"country": country,
"model_type": model_type,
"factor_id": factor_id,
"factor_description": factor_description,
"lag": lag,
"sample_start": str(data.index.min()),
"sample_end": str(data.index.max()),
"n_obs": nobs,
"lambda2_opt": lambda2,
"lambda2_used": lambda2,
"lambda_source": "hardcoded_previous_optimization",
"lambda_error": np.nan,
"target_variance_ratio": target,
"achieved_variance_ratio": achieved,
"abs_log_ratio_error": abs_log_ratio_error,
"beta": beta,
"beta_se": se[0],
"beta_z": z[0],
"beta_p": p[0],
"beta_signif": significance_stars(p[0]),
"beta_boundary_upper": abs(beta - BETA_UPPER) <= 1e-5,
"gamma": gamma,
"gamma_se": gamma_se,
"gamma_z": gamma_z,
"gamma_p": gamma_p,
"gamma_signif": gamma_signif,
"loglik": loglik,
"aic": aic,
"bic": bic,
"n_params": k,
"converged": res["opt"].success,
"optimizer_message": str(res["opt"].message),
"sigma2_gap": res["hp_info"]["sigma2_gap"],
"sigma2_eta": res["hp_info"]["sigma2_eta"],
}
output = data.copy()
output["potential"] = res["states"]["potential"]
output["output_gap"] = res["output_gap"]
output["gap_state"] = res["states"]["gap_state"]
output["country"] = country
output["model_type"] = model_type
output["factor_id"] = factor_id
output["lag"] = lag
output["lambda2_opt"] = lambda2
output["lambda2_used"] = lambda2
output["beta"] = beta
output["gamma"] = gamma
return summary, output
# ============================================================
# 10. BASELINE COMPARISON
# ============================================================
def add_baseline_comparison(summary_df):
summary_df = summary_df.copy()
for c in [
"baseline_loglik_same_sample",
"baseline_aic_same_sample",
"baseline_bic_same_sample",
"delta_loglik_vs_baseline",
"delta_aic_vs_baseline",
"delta_bic_vs_baseline",
"lr_stat_vs_baseline",
"lr_p_vs_baseline",
]:
summary_df[c] = np.nan
summary_df["lr_signif_vs_baseline"] = ""
for idx, row in summary_df.iterrows():
if row["model_type"] != "factor":
continue
mask = (
(summary_df["country"] == row["country"]) &
(summary_df["model_type"] == "baseline_same_sample") &
(summary_df["factor_id"] == row["factor_id"]) &
(summary_df["lag"] == row["lag"])
)
if mask.sum() != 1:
continue
base = summary_df.loc[mask].iloc[0]
d_ll = row["loglik"] - base["loglik"]
d_aic = row["aic"] - base["aic"]
d_bic = row["bic"] - base["bic"]
lr_stat = 2.0 * d_ll
lr_p = chi2.sf(lr_stat, df=1) if np.isfinite(lr_stat) and lr_stat >= 0 else np.nan
summary_df.loc[idx, "baseline_loglik_same_sample"] = base["loglik"]
summary_df.loc[idx, "baseline_aic_same_sample"] = base["aic"]
summary_df.loc[idx, "baseline_bic_same_sample"] = base["bic"]
summary_df.loc[idx, "delta_loglik_vs_baseline"] = d_ll
summary_df.loc[idx, "delta_aic_vs_baseline"] = d_aic
summary_df.loc[idx, "delta_bic_vs_baseline"] = d_bic
summary_df.loc[idx, "lr_stat_vs_baseline"] = lr_stat
summary_df.loc[idx, "lr_p_vs_baseline"] = lr_p
summary_df.loc[idx, "lr_signif_vs_baseline"] = significance_stars(lr_p)
return summary_df
# ============================================================
# 11. EUROSTOXX POTENTIAL-OUTPUT OBJECT
# ============================================================
def make_eurostoxx_potential_country_object(
country,
factor_col,
lag,
base_summary,
factor_summary,
base_output,
factor_output,
):
"""
Build one country-level comparison table containing:
- same-sample baseline potential output;
- Eurostoxx-augmented potential output;
- the corresponding output gaps and key parameter values.
The two series are estimated on exactly the same sample.
"""
out = pd.DataFrame(index=base_output.index.copy())
out.index.name = "quarter"
out["country"] = country
out["factor_id"] = EUROSTOXX_FACTOR_ID
out["actual_factor_column"] = factor_col
out["lag"] = lag
out["real_gdp"] = base_output["real_gdp"]
out["y"] = base_output["y"]
if "factor_raw" in factor_output.columns:
out["eurostoxx_raw"] = factor_output["factor_raw"]
if "factor" in factor_output.columns:
out["eurostoxx_factor"] = factor_output["factor"]
out["potential_same_sample_baseline"] = base_output["potential"]
out["output_gap_same_sample_baseline"] = base_output["output_gap"]
out["gap_state_same_sample_baseline"] = base_output["gap_state"]
out["potential_eurostoxx"] = factor_output["potential"]
out["output_gap_eurostoxx"] = factor_output["output_gap"]
out["gap_state_eurostoxx"] = factor_output["gap_state"]
out["lambda2_same_sample_baseline"] = base_summary["lambda2_used"]
out["lambda2_eurostoxx"] = factor_summary["lambda2_used"]
out["beta_same_sample_baseline"] = base_summary["beta"]
out["beta_eurostoxx"] = factor_summary["beta"]
out["gamma_eurostoxx"] = factor_summary["gamma"]
out["sample_start"] = base_summary["sample_start"]
out["sample_end"] = base_summary["sample_end"]
out["n_obs"] = base_summary["n_obs"]
return out
def finalize_eurostoxx_potential_object(eurostoxx_potential_output):
by_country = eurostoxx_potential_output.get("by_country", {})
if len(by_country) == 0:
eurostoxx_potential_output["long"] = pd.DataFrame()
eurostoxx_potential_output["summary"] = pd.DataFrame()
return eurostoxx_potential_output
long_df = pd.concat(
[df.copy() for df in by_country.values()],
axis=0,
).reset_index()
long_df["quarter"] = long_df["quarter"].astype(str)
summary_df_obj = pd.DataFrame(eurostoxx_potential_output.get("summaries", []))
eurostoxx_potential_output["long"] = long_df
eurostoxx_potential_output["summary"] = summary_df_obj
return eurostoxx_potential_output
# ============================================================
# 12. MAIN
# ============================================================
gdp_df = read_quarterly_excel(GDP_EXCEL)
if COUNTRIES is None:
countries = gdp_df.columns.tolist()
else:
countries = COUNTRIES
factors_df = get_factors_from_existing_object()
all_summaries = []
all_outputs = []
failed_runs = []
eurostoxx_potential_output = {
"factor_id": EUROSTOXX_FACTOR_ID,
"description": "Eurostoxx-augmented and same-sample baseline potential output by country",
"by_country": {},
"summaries": [],
}
total_runs = len(countries) * len(FACTOR_SPECS)
progress_bar = tqdm(total=total_runs, desc="Estimating models", unit="spec") if tqdm is not None else None
for country in countries:
if country not in gdp_df.columns:
failed_runs.append({
"country": country,
"factor_id": None,
"error": "Country column missing from GDP file",
})
if progress_bar is not None:
progress_bar.update(len(FACTOR_SPECS))
continue
for fs in FACTOR_SPECS:
factor_id = fs["factor_id"]
lag = fs["lag"]
factor_description = fs["description"]
try:
factor_col = find_column(factors_df, fs["aliases"])
data_factor = make_country_factor_data(
gdp_df=gdp_df,
factors_df=factors_df,
country=country,
factor_col=factor_col,
lag=lag,
)
data_baseline_same = data_factor.drop(
columns=[c for c in ["factor_raw", "factor"] if c in data_factor.columns]
).copy()
baseline_lambda2 = get_fixed_lambda(
country=country,
model_type="baseline_same_sample",
factor_id=factor_id,
lag=lag,
)
factor_lambda2 = get_fixed_lambda(
country=country,
model_type="factor",
factor_id=factor_id,
lag=lag,
)
base_summary, base_output = run_model(
data=data_baseline_same,
country=country,
model_type="baseline_same_sample",
factor_id=factor_id,
factor_description=f"Same-sample baseline for {factor_id}, lag {lag}",
lag=lag,
lambda2=baseline_lambda2,
)
factor_summary, factor_output = run_model(
data=data_factor,
country=country,
model_type="factor",
factor_id=factor_id,
factor_description=factor_description,
lag=lag,
lambda2=factor_lambda2,
)
base_summary["actual_factor_column"] = factor_col
factor_summary["actual_factor_column"] = factor_col
base_output["actual_factor_column"] = factor_col
factor_output["actual_factor_column"] = factor_col
all_summaries.append(base_summary)
all_summaries.append(factor_summary)
all_outputs.append(base_output)
all_outputs.append(factor_output)
if factor_id == EUROSTOXX_FACTOR_ID:
eurostoxx_country_df = make_eurostoxx_potential_country_object(
country=country,
factor_col=factor_col,
lag=lag,
base_summary=base_summary,
factor_summary=factor_summary,
base_output=base_output,
factor_output=factor_output,
)
eurostoxx_potential_output["by_country"][country] = eurostoxx_country_df
eurostoxx_potential_output["summaries"].append({
"country": country,
"factor_id": factor_id,
"actual_factor_column": factor_col,
"lag": lag,
"sample_start": base_summary["sample_start"],
"sample_end": base_summary["sample_end"],
"n_obs": base_summary["n_obs"],
"lambda2_same_sample_baseline": base_summary["lambda2_used"],
"lambda2_eurostoxx": factor_summary["lambda2_used"],
"beta_same_sample_baseline": base_summary["beta"],
"beta_eurostoxx": factor_summary["beta"],
"gamma_eurostoxx": factor_summary["gamma"],
"aic_same_sample_baseline": base_summary["aic"],
"aic_eurostoxx": factor_summary["aic"],
"bic_same_sample_baseline": base_summary["bic"],
"bic_eurostoxx": factor_summary["bic"],
})
if progress_bar is not None:
progress_bar.update(1)
except Exception as e:
failed_runs.append({
"country": country,
"factor_id": factor_id,
"lag": lag,
"error": str(e),
})
if progress_bar is not None:
progress_bar.update(1)
if progress_bar is not None:
progress_bar.close()
summary_df = pd.DataFrame(all_summaries)
if not summary_df.empty:
summary_df = add_baseline_comparison(summary_df)
factor_results = (
summary_df[summary_df["model_type"] == "factor"].copy()
if not summary_df.empty else pd.DataFrame()
)
baseline_results = (
summary_df[summary_df["model_type"] == "baseline_same_sample"].copy()
if not summary_df.empty else pd.DataFrame()
)
failed_df = pd.DataFrame(failed_runs)
if not factor_results.empty:
factor_results_sorted = factor_results.sort_values(
["country", "delta_aic_vs_baseline", "gamma_p"],
ascending=[True, True, True],
)
else:
factor_results_sorted = pd.DataFrame()
if len(all_outputs) > 0:
outputs_long = pd.concat(all_outputs, axis=0).reset_index()
outputs_long = outputs_long.rename(columns={"index": "quarter"})
outputs_long["quarter"] = outputs_long["quarter"].astype(str)
else:
outputs_long = pd.DataFrame()
eurostoxx_potential_output = finalize_eurostoxx_potential_object(eurostoxx_potential_output)
eurostoxx_potential_output_long = eurostoxx_potential_output["long"]
print("\n" + "#" * 100)
print("FINAL FACTOR RESULTS")
print("#" * 100)
display_cols = [
"country",
"factor_id",
"actual_factor_column",
"lag",
"sample_start",
"sample_end",
"n_obs",
"lambda2_opt",
"lambda2_used",
"target_variance_ratio",
"achieved_variance_ratio",
"abs_log_ratio_error",
"beta",
"beta_se",
"beta_p",
"beta_signif",
"gamma",
"gamma_se",
"gamma_p",
"gamma_signif",
"loglik",
"aic",
"bic",
"delta_aic_vs_baseline",
"delta_bic_vs_baseline",
"lr_p_vs_baseline",
"lr_signif_vs_baseline",
"converged",
]
if not factor_results.empty:
cols = [c for c in display_cols if c in factor_results.columns]
print(factor_results[cols].round(6).to_string(index=False))
else:
print("No successful factor results.")
Show output
####################################################################################################
FINAL FACTOR RESULTS
####################################################################################################
country factor_id actual_factor_column lag sample_start sample_end n_obs lambda2_opt lambda2_used target_variance_ratio achieved_variance_ratio abs_log_ratio_error beta beta_se beta_p beta_signif gamma gamma_se gamma_p gamma_signif loglik aic bic delta_aic_vs_baseline delta_bic_vs_baseline lr_p_vs_baseline lr_signif_vs_baseline converged
Belgium eurusd_dlog eurusd_dlog 2 2004Q3 2025Q4 86 492.486777 492.486777 24304.638889 2.430543e+04 0.000032 0.665044 0.124310 0.000000 *** 0.204741 0.204210 0.316053 -169.952443 343.904886 348.813581 1.007981 3.462328 0.319249 True
Belgium ea_share_price_dlog ea_share_price_dlog 2 1995Q4 2025Q4 121 946.087001 946.087001 18678.491178 4.900164e+06 5.569651 1.009223 0.009636 0.000000 *** 0.142274 0.143542 0.321603 -225.172129 454.344259 459.935840 0.916425 3.712216 0.297899 True
Belgium eurstock_logreturn eurstock_logreturn 0 2007Q2 2025Q4 75 314.667555 314.667555 37701.576861 3.770236e+04 0.000021 0.652352 0.134270 0.000001 *** 0.737037 0.237082 0.001879 *** -146.006404 296.012807 300.647783 -6.515536 -4.198048 0.003521 *** True
Belgium ea_unemployment_std ea_unemployment_std 0 1995Q1 2023Q1 113 845.370674 845.370674 17184.405316 1.234022e+04 0.331138 0.678374 0.111695 0.000000 *** 0.049661 0.241729 0.837228 -214.801815 433.603630 439.058406 1.483403 4.210791 0.472297 True
Belgium ciss_std ciss_std 0 1995Q1 2025Q4 124 895.008842 895.008842 18988.164470 1.340650e+04 0.348076 0.678833 0.107477 0.000000 *** -0.141072 0.158433 0.373241 -230.455103 464.910205 470.550768 1.541144 4.361425 0.498159 True
Belgium brent_dlog brent_dlog 0 1995Q2 2025Q4 123 904.541272 904.541272 18972.716753 4.530753e+06 5.475641 1.009089 0.009566 0.000000 *** -0.026393 0.141239 0.851766 -228.398780 460.797560 466.421928 1.924334 4.736518 0.783259 True
Belgium ea_3m_rate_change ea_3m_rate_change 0 1995Q2 2025Q4 123 1046.534453 1046.534453 18972.716753 6.214379e+06 5.791619 1.009015 0.009066 0.000000 *** 0.012921 0.142336 0.927671 -228.346410 460.692821 466.317190 1.819595 4.631779 0.671025 True
Czechia eurusd_dlog eurusd_dlog 2 2004Q3 2025Q4 86 50.926124 50.926124 4488.955011 4.489152e+03 0.000044 1.008466 0.064913 0.000000 *** 0.149968 0.170843 0.380046 -160.818391 325.636781 330.545476 1.073470 3.527817 0.335766 True
Czechia ea_share_price_dlog ea_share_price_dlog 2 1995Q4 2025Q4 121 61.519246 61.519246 2986.226838 2.892348e+03 0.031942 0.908510 0.090891 0.000000 *** 0.204393 0.125176 0.102501 -206.698795 417.397590 422.989171 -0.548943 2.246848 0.110368 True
Czechia eurstock_logreturn eurstock_logreturn 0 2007Q2 2025Q4 75 68.862563 68.862563 6073.259167 6.073559e+03 0.000049 1.019379 0.030143 0.000000 *** 0.700964 0.192673 0.000275 *** -135.564477 275.128954 279.763930 -8.663547 -6.346059 0.001093 *** True
Czechia ea_unemployment_std ea_unemployment_std 0 1995Q1 2023Q1 113 11.704786 11.704786 3186.019899 2.694371e+04 2.134978 1.021906 0.012162 0.000000 *** 0.592022 0.356468 0.096754 * -200.077344 404.154688 409.609463 4.703426 7.430814 NaN True
Czechia ciss_std ciss_std 0 1995Q1 2025Q4 124 31.587167 31.587167 3189.290810 3.177354e+03 0.003750 0.939357 0.096112 0.000000 *** -0.328103 0.153286 0.032318 ** -211.452806 426.905612 432.546176 -1.122163 1.698118 0.077234 * True
Czechia brent_dlog brent_dlog 0 1995Q2 2025Q4 123 55.268315 55.268315 3080.884602 2.659917e+03 0.146922 0.912153 0.087638 0.000000 *** 0.183895 0.121410 0.129857 -210.366730 424.733459 430.357828 -0.137578 2.674606 0.143729 True
Czechia ea_3m_rate_change ea_3m_rate_change 0 1995Q2 2025Q4 123 56.002613 56.002613 3080.884602 2.666566e+03 0.144425 0.902217 0.088358 0.000000 *** 0.183620 0.129329 0.155670 -210.517841 425.035682 430.660051 0.164645 2.976829 0.175496 True
Denmark eurusd_dlog eurusd_dlog 2 2004Q3 2025Q4 86 111.558576 111.558576 8029.455236 8.029455e+03 0.000000 0.815666 0.128904 0.000000 *** 0.159774 0.168572 0.343228 -156.426229 316.852458 321.761152 1.118983 3.573330 0.347923 True
Denmark ea_share_price_dlog ea_share_price_dlog 2 1995Q4 2025Q4 121 163.017745 163.017745 6134.765262 6.260619e+03 0.020307 0.770179 0.110376 0.000000 *** 0.317058 0.130113 0.014819 ** -207.529831 419.059663 424.651244 -3.556766 -0.760975 0.018409 ** True
Denmark eurstock_logreturn eurstock_logreturn 0 2007Q2 2025Q4 75 162.224308 162.224308 10937.700611 1.093787e+04 0.000015 0.777639 0.153600 0.000000 *** 0.431633 0.193612 0.025789 ** -133.379434 270.758867 275.393843 -2.486761 -0.169273 0.034158 ** True
Denmark ea_unemployment_std ea_unemployment_std 0 1995Q1 2023Q1 113 90.933086 90.933086 5914.668689 3.039965e+03 0.665589 0.806594 0.155866 0.000000 *** 0.231404 0.290926 0.426377 -195.788502 395.577005 401.031780 1.227973 3.955361 0.379591 True
Denmark ciss_std ciss_std 0 1995Q1 2025Q4 124 140.792885 140.792885 6145.921878 6.132591e+03 0.002171 0.751319 0.117255 0.000000 *** -0.348630 0.150545 0.020570 ** -210.553603 425.107206 430.747769 -3.649327 -0.829046 0.017462 ** True
Denmark brent_dlog brent_dlog 0 1995Q2 2025Q4 123 205.536413 205.536413 6082.831053 7.583697e+03 0.220531 0.815450 0.125152 0.000000 *** 0.227980 0.123642 0.065203 * -211.115981 426.231963 431.856331 0.146873 2.959058 0.173420 True
Denmark ea_3m_rate_change ea_3m_rate_change 0 1995Q2 2025Q4 123 221.619796 221.619796 6082.831053 7.633289e+03 0.227049 0.808543 0.128289 0.000000 *** 0.147153 0.135069 0.275948 -212.331173 428.662346 434.286715 2.577257 5.389441 NaN True
Germany eurusd_dlog eurusd_dlog 2 2004Q3 2025Q4 86 208.806203 208.806203 17476.186145 1.747603e+04 0.000009 0.788044 0.104517 0.000000 *** 0.156922 0.181805 0.388063 -161.187598 326.375196 331.283890 1.269713 3.724061 0.392790 True
Germany ea_share_price_dlog ea_share_price_dlog 2 1995Q4 2025Q4 121 224.185859 224.185859 16200.720444 1.644452e+04 0.014936 0.789517 0.091690 0.000000 *** 0.219011 0.132312 0.097872 * -211.464573 426.929147 432.520728 -0.488216 2.307574 0.114702 True
Germany eurstock_logreturn eurstock_logreturn 0 2007Q2 2025Q4 75 111.596794 111.596794 12834.508545 1.283454e+04 0.000003 0.871862 0.151157 0.000000 *** 0.737418 0.213430 0.000550 *** -138.053136 280.106272 284.741248 -8.298831 -5.981343 0.001331 *** True
Germany ea_unemployment_std ea_unemployment_std 0 1995Q1 2023Q1 113 173.608463 173.608463 16185.168087 1.618487e+04 0.000019 0.842830 0.122272 0.000000 *** 0.208161 0.268627 0.438394 -201.993456 407.986913 413.441688 2.068332 4.795720 NaN True
Germany ciss_std ciss_std 0 1995Q1 2025Q4 124 223.091069 223.091069 16367.802670 1.633704e+04 0.001881 0.770049 0.091662 0.000000 *** -0.310125 0.151373 0.040487 ** -214.481032 432.962064 438.602628 -1.919427 0.900855 0.047731 ** True
Germany brent_dlog brent_dlog 0 1995Q2 2025Q4 123 278.237665 278.237665 16375.252738 1.691250e+04 0.032282 0.768758 0.088553 0.000000 *** 0.258332 0.129755 0.046490 ** -213.071707 430.143413 435.767782 -2.007688 0.804496 0.045293 ** True
Germany ea_3m_rate_change ea_3m_rate_change 0 1995Q2 2025Q4 123 312.181511 312.181511 16375.252738 1.716624e+04 0.047174 0.716025 0.091157 0.000000 *** 0.313900 0.144012 0.029281 ** -212.668550 429.337100 434.961468 -2.814001 -0.001817 0.028229 ** True
Estonia eurusd_dlog eurusd_dlog 2 2004Q3 2025Q4 86 69.513622 69.513622 6587.985041 6.588020e+03 0.000005 1.007023 0.035079 0.000000 *** -0.045786 0.223716 0.837836 -183.241772 370.483544 375.392238 1.890191 4.344538 0.740361 True
Estonia ea_share_price_dlog ea_share_price_dlog 2 1995Q4 2025Q4 121 59.576609 59.576609 4210.947410 4.591713e+03 0.086566 1.003530 0.029043 0.000000 *** 0.218884 0.172995 0.205777 -249.423973 502.847947 508.439528 1.494048 4.289838 0.476896 True
Estonia eurstock_logreturn eurstock_logreturn 0 2007Q2 2025Q4 75 33.885297 33.885297 4263.956111 4.264043e+03 0.000020 1.018894 0.036668 0.000000 *** 0.747766 0.237415 0.001635 *** -153.630825 311.261650 315.896627 -8.146553 -5.829065 0.001446 *** True
Estonia ea_unemployment_std ea_unemployment_std 0 1995Q1 2023Q1 113 12.940267 12.940267 4402.225867 3.317464e+04 2.019675 1.022675 0.009258 0.000000 *** 1.255081 0.473728 0.008064 *** -230.762565 465.525131 470.979906 -10.387750 -7.660362 0.000432 *** True
Estonia ciss_std ciss_std 0 1995Q1 2025Q4 124 35.260521 35.260521 4225.504005 4.206405e+03 0.004530 0.923894 0.056598 0.000000 *** -0.726018 0.223111 0.001138 *** -248.467537 500.935074 506.575637 -10.716558 -7.896276 0.000362 *** True
Estonia brent_dlog brent_dlog 0 1995Q2 2025Q4 123 61.912457 61.912457 4219.447348 4.881797e+03 0.145809 1.001618 0.035030 0.000000 *** 0.367893 0.166265 0.026919 ** -250.810195 505.620389 511.244758 -2.968397 -0.156213 0.025815 ** True
Estonia ea_3m_rate_change ea_3m_rate_change 0 1995Q2 2025Q4 123 69.629021 69.629021 4219.447348 4.819685e+03 0.133004 0.987303 0.071274 0.000000 *** 0.197342 0.209013 0.345089 -253.077949 510.155897 515.780266 1.567111 4.379295 0.510575 True
Ireland eurusd_dlog eurusd_dlog 2 2004Q3 2025Q4 86 83.817224 83.817224 4175.899095 4.176119e+03 0.000053 0.860568 0.126525 0.000000 *** 0.170395 0.396140 0.667095 -229.532102 463.064204 467.972898 1.846293 4.300640 0.695018 True
Ireland ea_share_price_dlog ea_share_price_dlog 2 1995Q4 2025Q4 121 90.180683 90.180683 3215.060230 1.937532e+04 1.796154 1.006674 0.031809 0.000000 *** 0.155893 0.298962 0.602055 -313.715866 631.431732 637.023313 1.649632 4.445422 0.553905 True
Ireland eurstock_logreturn eurstock_logreturn 0 2007Q2 2025Q4 75 141.640057 141.640057 4015.582084 4.015541e+03 0.000010 0.804131 0.190247 0.000024 *** 0.376015 0.447818 0.401099 -202.298573 408.597146 413.232122 1.175567 3.493056 0.363887 True
Ireland ea_unemployment_std ea_unemployment_std 0 1995Q1 2023Q1 113 52.719894 52.719894 2787.567881 2.869454e+05 4.634122 1.020633 0.008731 0.000000 *** 1.092710 0.516567 0.034402 ** -289.201772 582.403544 587.858319 -2.664796 0.062591 0.030787 ** True
Ireland ciss_std ciss_std 0 1995Q1 2025Q4 124 76.877902 76.877902 3241.521700 2.836650e+03 0.133419 0.837220 0.218467 0.000127 *** -0.539667 0.381989 0.157720 -319.556938 643.113876 648.754439 -1.230098 1.590183 0.072296 * True
Ireland brent_dlog brent_dlog 0 1995Q2 2025Q4 123 87.325482 87.325482 3224.938448 2.186940e+04 1.914174 1.008598 0.024044 0.000000 *** -0.426408 0.290326 0.141908 -317.623385 639.246769 644.871138 0.104924 2.917108 0.168630 True
Ireland ea_3m_rate_change ea_3m_rate_change 0 1995Q2 2025Q4 123 79.317119 79.317119 3224.938448 2.918095e+04 2.202602 1.010046 0.019577 0.000000 *** -0.412690 0.301594 0.171198 -317.841530 639.683061 645.307430 0.541215 3.353400 0.227124 True
Greece eurusd_dlog eurusd_dlog 2 2004Q3 2025Q4 86 52.592500 52.592500 2540.057635 2.540102e+03 0.000017 0.940435 0.096217 0.000000 *** 0.021613 0.248678 0.930742 -192.527292 389.054584 393.963279 1.997791 4.452138 0.962513 True
Greece ea_share_price_dlog ea_share_price_dlog 2 1995Q4 2025Q4 121 57.647943 57.647943 1855.713888 1.852851e+03 0.001544 0.925949 0.130763 0.000000 *** -0.076821 0.181116 0.671452 -254.462049 512.924098 518.515679 1.831896 4.627687 0.681802 True
Greece eurstock_logreturn eurstock_logreturn 0 2007Q2 2025Q4 75 54.836021 54.836021 6569.824465 6.569908e+03 0.000013 1.013293 0.034095 0.000000 *** 1.129343 0.277292 0.000046 *** -161.550355 327.100710 331.735687 -14.216276 -11.898788 0.000057 *** True
Greece ea_unemployment_std ea_unemployment_std 0 1995Q1 2023Q1 113 49.388356 49.388356 1856.922199 1.292187e+03 0.362585 0.899455 0.139933 0.000000 *** -0.027982 0.403099 0.944657 -241.019930 486.039860 491.494636 1.990985 4.718373 0.924358 True
Greece ciss_std ciss_std 0 1995Q1 2025Q4 124 28.550823 28.550823 1842.294311 1.825580e+03 0.009114 0.970768 0.209501 0.000004 *** -0.278439 0.263770 0.291144 -258.881242 521.762483 527.403046 1.003621 3.823902 0.318188 True
Greece brent_dlog brent_dlog 0 1995Q2 2025Q4 123 56.757402 56.757402 1845.934734 1.812675e+03 0.018182 0.913035 0.125607 0.000000 *** 0.227336 0.174657 0.193048 -256.961900 517.923800 523.548169 0.367275 3.179460 0.201327 True
Greece ea_3m_rate_change ea_3m_rate_change 0 1995Q2 2025Q4 123 56.703255 56.703255 1845.934734 1.816852e+03 0.015881 0.927601 0.138470 0.000000 *** -0.056127 0.183040 0.759120 -257.719902 519.439803 525.064172 1.883278 4.695463 0.732618 True
Spain eurusd_dlog eurusd_dlog 2 2004Q3 2025Q4 86 142.572595 142.572595 6698.880096 6.698791e+03 0.000013 0.771904 0.145899 0.000000 *** 0.251486 0.312418 0.420839 -208.679790 421.359579 426.268274 1.406270 3.860618 0.440981 True
Spain ea_share_price_dlog ea_share_price_dlog 2 1995Q4 2025Q4 121 207.930704 207.930704 5586.814740 5.586981e+03 0.000030 0.760275 0.111702 0.000000 *** 0.180227 0.222328 0.417575 -275.845175 555.690349 561.281930 1.261457 4.057248 0.390128 True
Spain eurstock_logreturn eurstock_logreturn 0 2007Q2 2025Q4 75 151.056919 151.056919 11085.221533 1.108537e+04 0.000014 0.814075 0.161933 0.000000 *** 1.120224 0.377532 0.003005 *** -180.542466 365.084932 369.719908 -6.300641 -3.983153 0.003963 *** True
Spain ea_unemployment_std ea_unemployment_std 0 1995Q1 2023Q1 113 216.577462 216.577462 6414.790025 6.414720e+03 0.000011 0.687082 0.129363 0.000000 *** -0.438069 0.562630 0.436210 -260.747933 525.495866 530.950642 1.401318 4.128705 0.439081 True
Spain ciss_std ciss_std 0 1995Q1 2025Q4 124 202.430957 202.430957 5573.464294 5.573835e+03 0.000067 0.752307 0.112210 0.000000 *** -0.159053 0.252518 0.528782 -281.032910 566.065819 571.706383 1.525654 4.345936 0.490995 True
Spain brent_dlog brent_dlog 0 1995Q2 2025Q4 123 223.927414 223.927414 5582.996567 5.560949e+03 0.003957 0.744180 0.111171 0.000000 *** 0.093048 0.220076 0.672442 -279.527802 563.055603 568.679972 1.889398 4.701582 0.739459 True
Spain ea_3m_rate_change ea_3m_rate_change 0 1995Q2 2025Q4 123 223.323842 223.323842 5582.996567 5.563583e+03 0.003483 0.741354 0.110763 0.000000 *** 0.173758 0.225480 0.440935 -279.321801 562.643603 568.267972 1.477397 4.289582 0.469734 True
France eurusd_dlog eurusd_dlog 2 2004Q3 2025Q4 86 375.519273 375.519273 27509.610544 2.751037e+04 0.000028 0.640595 0.120270 0.000000 *** 0.206480 0.250434 0.409663 -186.410249 376.820499 381.729193 1.368248 3.822595 0.426713 True
France ea_share_price_dlog ea_share_price_dlog 2 1995Q4 2025Q4 121 1306.792084 1306.792084 20076.527982 6.409730e+06 5.766021 1.007642 0.011988 0.000000 *** 0.205171 0.174273 0.239077 -247.593552 499.187104 504.778685 0.554913 3.350703 0.229318 True
France eurstock_logreturn eurstock_logreturn 0 2007Q2 2025Q4 75 285.687838 285.687838 34248.056022 3.424754e+04 0.000015 0.640521 0.132311 0.000001 *** 0.853211 0.288080 0.003059 *** -161.727561 327.455122 332.090098 -5.655350 -3.337862 0.005660 *** True
France ea_unemployment_std ea_unemployment_std 0 1995Q1 2023Q1 113 1375.038869 1375.038869 21483.013801 1.882334e+04 0.132165 0.666694 0.117894 0.000000 *** -0.013104 0.283990 0.963197 -235.789318 475.578637 481.033412 1.338684 4.066072 0.416096 True
France ciss_std ciss_std 0 1995Q1 2025Q4 124 1458.674634 1458.674634 19961.667764 1.996205e+04 0.000019 0.661517 0.109384 0.000000 *** -0.191083 0.190465 0.315742 -253.505951 511.011903 516.652466 1.430305 4.250586 0.450380 True
France brent_dlog brent_dlog 0 1995Q2 2025Q4 123 1377.453368 1377.453368 20164.993348 6.360469e+06 5.753909 1.007398 0.012065 0.000000 *** -0.101299 0.171528 0.554809 -251.282308 506.564615 512.188984 1.671362 4.483546 0.566462 True
France ea_3m_rate_change ea_3m_rate_change 0 1995Q2 2025Q4 123 1638.061427 1638.061427 20164.993348 8.640837e+06 6.060307 1.006649 0.012129 0.000000 *** 0.111175 0.172671 0.519669 -251.173614 506.347227 511.971596 1.453974 4.266158 0.459946 True
Croatia eurusd_dlog eurusd_dlog 2 2004Q3 2025Q4 86 74.888652 74.888652 5516.581771 5.516619e+03 0.000007 0.901021 0.120988 0.000000 *** 0.222504 0.262939 0.397430 -194.934121 393.868242 398.776937 1.328345 3.782692 0.412475 True
Croatia ea_share_price_dlog ea_share_price_dlog 2 1995Q4 2025Q4 121 43.778864 43.778864 3990.186699 1.173296e+03 1.224022 0.820889 0.120582 0.000000 *** -0.061588 0.208666 0.767879 -265.756767 535.513534 541.105115 2.745435 5.541225 NaN True
Croatia eurstock_logreturn eurstock_logreturn 0 2007Q2 2025Q4 75 103.557484 103.557484 16909.284171 1.690863e+04 0.000039 1.009951 0.049550 0.000000 *** 1.025374 0.291728 0.000440 *** -166.007600 336.015200 340.650176 -7.799508 -5.482020 0.001746 *** True
Croatia ea_unemployment_std ea_unemployment_std 0 1995Q1 2023Q1 113 119.949964 119.949964 4291.409931 4.291421e+03 0.000003 0.846807 0.105046 0.000000 *** 0.185765 0.394922 0.638080 -256.314277 516.628555 522.083330 1.714788 4.442176 0.593305 True
Croatia ciss_std ciss_std 0 1995Q1 2025Q4 124 83.960281 83.960281 4200.770663 4.193509e+03 0.001730 0.816570 0.114590 0.000000 *** -0.563710 0.252881 0.025804 ** -274.124138 552.248275 557.888838 -2.672587 0.147695 0.030648 ** True
Croatia brent_dlog brent_dlog 0 1995Q2 2025Q4 123 107.910436 107.910436 4207.631640 4.882947e+03 0.148849 0.847530 0.105090 0.000000 *** 0.379888 0.204696 0.063473 * -272.405314 548.810628 554.434997 -1.375920 1.436265 0.066156 * True
Croatia ea_3m_rate_change ea_3m_rate_change 0 1995Q2 2025Q4 123 107.294051 107.294051 4207.631640 4.818187e+03 0.135498 0.857404 0.103862 0.000000 *** 0.099467 0.212080 0.639065 -273.995893 551.991787 557.616155 1.805239 4.617423 0.658983 True
Latvia eurusd_dlog eurusd_dlog 2 2004Q3 2025Q4 86 71.042275 71.042275 5174.547243 5.174598e+03 0.000010 1.000040 0.051598 0.000000 *** 0.586502 0.260064 0.024119 ** -194.843883 393.687767 398.596461 -2.601687 -0.147340 0.031941 ** True
Latvia ea_share_price_dlog ea_share_price_dlog 2 1995Q4 2025Q4 121 52.557369 52.557369 3198.270291 3.421016e+03 0.067328 0.968207 0.085156 0.000000 *** 0.394371 0.222044 0.075717 * -275.857932 555.715865 561.307446 -1.172540 1.623250 0.074886 * True
Latvia eurstock_logreturn eurstock_logreturn 0 2007Q2 2025Q4 75 18.013385 18.013385 3514.075141 3.514066e+03 0.000003 1.024641 0.022968 0.000000 *** 1.117149 0.278616 0.000061 *** -161.783057 327.566113 332.201089 -13.290410 -10.972922 0.000092 *** True
Latvia ea_unemployment_std ea_unemployment_std 0 1995Q1 2023Q1 113 11.908534 11.908534 3159.662415 1.412326e+04 1.497358 1.022088 0.013801 0.000000 *** 0.918968 0.621412 0.139183 -261.459520 526.919039 532.373815 0.302565 3.029953 0.192624 True
Latvia ciss_std ciss_std 0 1995Q1 2025Q4 124 26.019699 26.019699 3189.809701 3.163142e+03 0.008395 0.950736 0.067420 0.000000 *** -0.746476 0.273744 0.006393 *** -278.836427 561.672854 567.313417 -6.194422 -3.374141 0.004202 *** True
Latvia brent_dlog brent_dlog 0 1995Q2 2025Q4 123 57.844888 57.844888 3191.843932 3.280255e+03 0.027322 0.979164 0.071076 0.000000 *** 0.126933 0.210801 0.547076 -280.821879 565.643757 571.268126 1.587186 4.399371 0.520545 True
Latvia ea_3m_rate_change ea_3m_rate_change 0 1995Q2 2025Q4 123 59.138849 59.138849 3191.843932 3.202144e+03 0.003222 0.926974 0.061099 0.000000 *** 0.577193 0.228802 0.011647 ** -278.044320 560.088640 565.713009 -3.967931 -1.155747 0.014568 ** True
Lithuania eurusd_dlog eurusd_dlog 2 2004Q3 2025Q4 86 73.025791 73.025791 6520.898586 6.520966e+03 0.000010 0.989866 0.078149 0.000000 *** 0.302289 0.225744 0.180545 -180.541921 365.083841 369.992536 0.487384 2.941731 0.218740 True
Lithuania ea_share_price_dlog ea_share_price_dlog 2 1995Q4 2025Q4 121 55.443956 55.443956 3963.062177 3.211653e+03 0.210231 0.998165 0.077096 0.000000 *** 0.101931 0.171079 0.551302 -245.788436 495.576873 501.168454 1.965112 4.760903 0.851831 True
Lithuania eurstock_logreturn eurstock_logreturn 0 2007Q2 2025Q4 75 58.301227 58.301227 4713.418707 4.713329e+03 0.000019 1.015050 0.042391 0.000000 *** 0.840702 0.237203 0.000394 *** -150.791322 305.582645 310.217621 -10.271949 -7.954461 0.000460 *** True
Lithuania ea_unemployment_std ea_unemployment_std 0 1995Q1 2023Q1 113 11.121074 11.121074 4001.676864 1.441099e+04 1.281278 1.020900 0.013246 0.000000 *** 0.747224 0.481223 0.120480 -231.232118 466.464236 471.919011 -0.915211 1.812177 0.087748 * True
Lithuania ciss_std ciss_std 0 1995Q1 2025Q4 124 19.131231 19.131231 3983.020168 3.956634e+03 0.006647 0.926535 0.068709 0.000000 *** -0.931651 0.225840 0.000037 *** -241.779126 487.558252 493.198816 -15.684410 -12.864128 0.000026 *** True
Lithuania brent_dlog brent_dlog 0 1995Q2 2025Q4 123 34.835276 34.835276 3978.873722 2.573680e+03 0.435662 1.007316 0.051874 0.000000 *** 0.413468 0.164515 0.011962 ** -245.955485 495.910970 501.535339 -4.999279 -2.187094 0.008154 *** True
Lithuania ea_3m_rate_change ea_3m_rate_change 0 1995Q2 2025Q4 123 60.384435 60.384435 3978.873722 3.681628e+03 0.077644 0.950015 0.065784 0.000000 *** 0.413203 0.174585 0.017944 ** -247.067522 498.135043 503.759412 -2.775205 0.036979 0.028872 ** True
Luxembourg eurusd_dlog eurusd_dlog 2 2004Q3 2025Q4 86 155.451655 155.451655 9661.441722 9.661611e+03 0.000018 0.839840 0.133268 0.000000 *** 0.044670 0.215056 0.835452 -176.710906 357.421811 362.330506 1.972174 4.426522 0.867520 True
Luxembourg ea_share_price_dlog ea_share_price_dlog 2 1995Q4 2025Q4 121 165.021750 165.021750 9144.544447 1.099768e+05 2.487112 1.007909 0.014372 0.000000 *** 0.046307 0.163822 0.777431 -243.314368 490.628735 496.220316 1.938856 4.734647 0.804697 True
Luxembourg eurstock_logreturn eurstock_logreturn 0 2007Q2 2025Q4 75 239.229920 239.229920 6949.853882 6.949745e+03 0.000016 0.669330 0.141507 0.000002 *** 0.887829 0.214593 0.000035 *** -138.682668 281.365336 286.000312 -14.994644 -12.677156 0.000037 *** True
Luxembourg ea_unemployment_std ea_unemployment_std 0 1995Q1 2023Q1 113 77.172332 77.172332 9575.610528 1.700830e+03 1.728103 0.786837 0.154590 0.000000 *** 0.165379 0.429910 0.700472 -227.053753 458.107507 463.562282 -0.294116 2.433272 0.129865 True
Luxembourg ciss_std ciss_std 0 1995Q1 2025Q4 124 244.213445 244.213445 9122.592231 5.549619e+03 0.497025 0.802923 0.122497 0.000000 *** -0.184936 0.183067 0.312394 -248.260993 500.521985 506.162548 1.017682 3.837963 0.321627 True
Luxembourg brent_dlog brent_dlog 0 1995Q2 2025Q4 123 211.253528 211.253528 9363.170218 1.323706e+05 2.648821 1.007508 0.014381 0.000000 *** -0.084169 0.161076 0.601292 -246.245054 496.490108 502.114476 1.739804 4.551988 0.609986 True
Luxembourg ea_3m_rate_change ea_3m_rate_change 0 1995Q2 2025Q4 123 252.584135 252.584135 9363.170218 2.036146e+05 3.079445 1.007423 0.012908 0.000000 *** -0.031739 0.163768 0.846327 -246.356142 496.712284 502.336653 1.961981 4.774165 0.845404 True
Hungary eurusd_dlog eurusd_dlog 2 2004Q3 2025Q4 86 145.344024 145.344024 5667.404141 5.667395e+03 0.000002 0.745847 0.144285 0.000000 *** 0.269732 0.259148 0.297949 -192.320626 388.641252 393.549947 0.966223 3.420570 0.309273 True
Hungary ea_share_price_dlog ea_share_price_dlog 2 1995Q4 2025Q4 121 278.935213 278.935213 3251.410376 3.083491e+03 0.053027 0.684313 0.127813 0.000000 *** 0.316524 0.186910 0.090368 * -254.303905 512.607810 518.199391 -0.289628 2.506162 0.130241 True
Hungary eurstock_logreturn eurstock_logreturn 0 2007Q2 2025Q4 75 154.163603 154.163603 5558.488573 5.558520e+03 0.000006 0.777276 0.202398 0.000123 *** 0.850759 0.331860 0.010359 ** -166.598655 337.197309 341.832286 -4.971795 -2.654307 0.008280 *** True
Hungary ea_unemployment_std ea_unemployment_std 0 1995Q1 2023Q1 113 187.861602 187.861602 3331.198331 3.331143e+03 0.000017 0.613641 0.133449 0.000004 *** -0.456059 0.486030 0.348072 -240.209636 484.419272 489.874048 0.363489 3.090877 0.200805 True
Hungary ciss_std ciss_std 0 1995Q1 2025Q4 124 177.669540 177.669540 3123.496951 3.121156e+03 0.000750 0.661739 0.123348 0.000000 *** -0.524427 0.223527 0.018969 ** -256.466936 516.933871 522.574434 -4.582928 -1.762647 0.010296 ** True
Hungary brent_dlog brent_dlog 0 1995Q2 2025Q4 123 246.520463 246.520463 3173.882146 2.967647e+03 0.067186 0.675293 0.127639 0.000000 *** 0.299566 0.181600 0.099026 * -256.974740 517.949480 523.573849 -0.417351 2.394833 0.119998 True
Hungary ea_3m_rate_change ea_3m_rate_change 0 1995Q2 2025Q4 123 257.347920 257.347920 3173.882146 3.012799e+03 0.052086 0.645326 0.122999 0.000000 *** 0.315683 0.196718 0.108549 -257.191333 518.382666 524.007035 0.015835 2.828019 0.158952 True
Austria eurusd_dlog eurusd_dlog 2 2004Q3 2025Q4 86 289.289735 289.289735 24360.585229 2.435994e+04 0.000027 0.743364 0.114926 0.000000 *** 0.201492 0.234411 0.390029 -182.695818 369.391636 374.300330 1.345257 3.799604 0.418422 True
Austria ea_share_price_dlog ea_share_price_dlog 2 1995Q4 2025Q4 121 662.501444 662.501444 20678.003198 2.086608e+04 0.009054 0.754057 0.096996 0.000000 *** 0.320171 0.165487 0.053025 * -239.631415 483.262831 488.854412 -1.868910 0.926881 0.049188 ** True
Austria eurstock_logreturn eurstock_logreturn 0 2007Q2 2025Q4 75 237.382631 237.382631 29479.864626 2.948069e+04 0.000028 0.747419 0.130075 0.000000 *** 0.711130 0.276290 0.010057 ** -158.931149 321.862297 326.497274 -3.933544 -1.616056 0.014855 ** True
Austria ea_unemployment_std ea_unemployment_std 0 1995Q1 2023Q1 113 547.788049 547.788049 16696.636947 1.669659e+04 0.000003 0.743796 0.109679 0.000000 *** -0.018553 0.293727 0.949637 -228.595495 461.190990 466.645766 1.997486 4.724874 0.960010 True
Austria ciss_std ciss_std 0 1995Q1 2025Q4 124 620.075470 620.075470 20626.722274 2.062151e+04 0.000253 0.768317 0.101167 0.000000 *** -0.149723 0.178562 0.401754 -246.089767 496.179533 501.820097 0.765139 3.585420 0.266464 True
Austria brent_dlog brent_dlog 0 1995Q2 2025Q4 123 704.105010 704.105010 20631.758883 2.051879e+04 0.005491 0.741662 0.097619 0.000000 *** 0.131335 0.164580 0.424871 -244.408564 492.817127 498.441496 1.474276 4.286460 0.468410 True
Austria ea_3m_rate_change ea_3m_rate_change 0 1995Q2 2025Q4 123 801.707004 801.707004 20631.758883 2.059464e+04 0.001801 0.717672 0.099575 0.000000 *** 0.213217 0.175912 0.225486 -244.366631 492.733262 498.357630 1.390410 4.202594 0.434942 True
Poland eurusd_dlog eurusd_dlog 2 2004Q3 2025Q4 86 69.861748 69.861748 9655.006382 2.647943e+04 1.008892 1.013632 0.026877 0.000000 *** 0.260510 0.172958 0.132014 -160.755875 325.511750 330.420444 -0.054621 2.399726 0.151745 True
Poland ea_share_price_dlog ea_share_price_dlog 2 1995Q4 2025Q4 121 107.385850 107.385850 7721.445824 1.318806e+05 2.837896 1.010905 0.014721 0.000000 *** 0.122129 0.139878 0.382600 -222.911711 449.823422 455.415003 -0.100716 2.695075 0.147230 True
Poland eurstock_logreturn eurstock_logreturn 0 2007Q2 2025Q4 75 60.901964 60.901964 16568.718911 1.656921e+04 0.000030 1.020447 0.023044 0.000000 *** 0.660211 0.189189 0.000484 *** -135.396090 274.792179 279.427156 -10.320971 -8.003483 0.000448 *** True
Poland ea_unemployment_std ea_unemployment_std 0 1995Q1 2023Q1 113 43.738831 43.738831 7040.249321 2.782153e+03 0.928418 0.907354 1.869685 0.627465 0.274344 0.319108 0.389943 -213.353886 430.707773 436.162549 2.350318 5.077705 NaN True
Poland ciss_std ciss_std 0 1995Q1 2025Q4 124 37.327459 37.327459 7049.331293 9.218461e+02 2.034310 0.757436 0.167325 0.000006 *** -0.261997 0.187491 0.162297 -229.960117 463.920235 469.560798 0.868699 3.688980 0.287498 True
Poland brent_dlog brent_dlog 0 1995Q2 2025Q4 123 56.241499 56.241499 7317.559046 1.627433e+03 1.503273 0.795656 0.201647 0.000080 *** 0.108578 0.144246 0.451612 -227.743213 459.486426 465.110794 1.488766 4.300950 0.474605 True
Poland ea_3m_rate_change ea_3m_rate_change 0 1995Q2 2025Q4 123 47.986351 47.986351 7317.559046 1.300206e+03 1.727754 0.798343 0.198442 0.000057 *** -0.026180 0.151900 0.863163 -228.048347 460.096693 465.721062 2.099033 4.911218 NaN True
Portugal eurusd_dlog eurusd_dlog 2 2004Q3 2025Q4 86 211.721867 211.721867 9016.450460 9.016347e+03 0.000011 0.692815 0.127435 0.000000 *** 0.218679 0.282937 0.439588 -198.179097 400.358194 405.266888 1.491841 3.946188 0.475937 True
Portugal ea_share_price_dlog ea_share_price_dlog 2 1995Q4 2025Q4 121 215.796346 215.796346 6683.355803 7.252727e+03 0.081757 0.782744 0.126383 0.000000 *** 0.299507 0.202849 0.139810 -264.983629 533.967257 539.558838 -0.346554 2.449236 0.125560 True
Portugal eurstock_logreturn eurstock_logreturn 0 2007Q2 2025Q4 75 244.502053 244.502053 15560.071030 1.556056e+04 0.000031 0.766312 0.140950 0.000000 *** 0.991218 0.332247 0.002851 *** -172.085580 348.171160 352.806136 -6.716767 -4.399279 0.003153 *** True
Portugal ea_unemployment_std ea_unemployment_std 0 1995Q1 2023Q1 113 279.944021 279.944021 7050.565018 7.050600e+03 0.000005 0.751431 0.142703 0.000000 *** -0.104969 0.427978 0.806249 -251.809709 507.619419 513.074194 2.036876 4.764264 NaN True
Portugal ciss_std ciss_std 0 1995Q1 2025Q4 124 253.184509 253.184509 6700.581640 6.699728e+03 0.000127 0.766186 0.125249 0.000000 *** -0.092798 0.227350 0.683147 -270.956697 545.913394 551.553957 1.725057 4.545339 0.600035 True
Portugal brent_dlog brent_dlog 0 1995Q2 2025Q4 123 267.056141 267.056141 6689.967281 6.960719e+03 0.039674 0.760091 0.121094 0.000000 *** 0.067382 0.201037 0.737496 -269.328610 542.657219 548.281588 1.978834 4.791018 0.884327 True
Portugal ea_3m_rate_change ea_3m_rate_change 0 1995Q2 2025Q4 123 282.326761 282.326761 6689.967281 6.956385e+03 0.039051 0.749684 0.121528 0.000000 *** 0.190815 0.207858 0.358617 -269.072712 542.145424 547.769792 1.467038 4.279222 0.465364 True
Romania eurusd_dlog eurusd_dlog 2 2004Q3 2025Q4 86 89.464430 89.464430 5397.153498 5.397197e+03 0.000008 0.889664 0.147225 0.000000 *** 0.100729 0.273586 0.712739 -197.306549 398.613099 403.521793 1.893467 4.347814 0.744126 True
Romania ea_share_price_dlog ea_share_price_dlog 2 1995Q4 2025Q4 121 187.771656 187.771656 3213.780895 8.557503e+03 0.979360 0.821424 0.107326 0.000000 *** -0.257978 0.251596 0.305190 -281.566728 567.133456 572.725037 0.887345 3.683135 0.291506 True
Romania eurstock_logreturn eurstock_logreturn 0 2007Q2 2025Q4 75 75.543687 75.543687 7873.717861 7.873941e+03 0.000028 1.019549 0.031882 0.000000 *** 1.242851 0.302247 0.000039 *** -166.357655 336.715310 341.350286 -12.248779 -9.931291 0.000160 *** True
Romania ea_unemployment_std ea_unemployment_std 0 1995Q1 2023Q1 113 71.462952 71.462952 3220.818067 3.220824e+03 0.000002 0.587952 0.122678 0.000002 *** -1.268525 0.822247 0.122891 -275.928933 555.857865 561.312641 0.162292 2.889680 0.175220 True
Romania ciss_std ciss_std 0 1995Q1 2025Q4 124 163.809816 163.809816 3136.186513 3.133458e+03 0.000870 0.734570 0.162660 0.000006 *** -0.435683 0.300057 0.146501 -298.098029 600.196058 605.836621 -0.343236 2.477046 0.125828 True
Romania brent_dlog brent_dlog 0 1995Q2 2025Q4 123 153.251012 153.251012 3185.534510 3.135449e+03 0.015848 0.730832 0.144940 0.000000 *** 0.671776 0.251593 0.007583 *** -293.152836 590.305671 595.930040 -5.349124 -2.536940 0.006710 *** True
Romania ea_3m_rate_change ea_3m_rate_change 0 1995Q2 2025Q4 123 165.578664 165.578664 3185.534510 3.187687e+03 0.000675 0.720804 0.142754 0.000000 *** 0.253198 0.264747 0.338881 -296.291127 596.582254 602.206623 0.927459 3.739643 0.300372 True
Slovakia eurusd_dlog eurusd_dlog 2 2004Q3 2025Q4 86 66.989210 66.989210 7691.374193 7.690775e+03 0.000078 1.005879 0.049630 0.000000 *** 0.176893 0.228800 0.439441 -185.086379 374.172758 379.081452 1.218713 3.673060 0.376748 True
Slovakia ea_share_price_dlog ea_share_price_dlog 2 1995Q4 2025Q4 121 80.361419 80.361419 4213.262705 3.685309e+03 0.133883 0.823813 0.116285 0.000000 *** 0.143832 0.188812 0.446194 -253.356352 510.712704 516.304285 1.502320 4.298110 0.480521 True
Slovakia eurstock_logreturn eurstock_logreturn 0 2007Q2 2025Q4 75 684.096395 684.096395 26965.059597 1.860385e+04 0.371174 0.793340 0.156775 0.000000 *** 0.683755 0.279241 0.014341 ** -157.214862 318.429724 323.064701 -6.216756 -3.899268 0.004151 *** True
Slovakia ea_unemployment_std ea_unemployment_std 0 1995Q1 2023Q1 113 78.589594 78.589594 4170.971772 4.170879e+03 0.000022 0.868483 0.115397 0.000000 *** 0.306093 0.360578 0.395941 -240.339949 484.679898 490.134674 1.378680 4.106068 0.430557 True
Slovakia ciss_std ciss_std 0 1995Q1 2025Q4 124 78.847412 78.847412 4192.773263 4.161450e+03 0.007499 0.808610 0.160586 0.000000 *** -0.586570 0.231928 0.011435 ** -255.460914 514.921828 520.562392 -4.808581 -1.988299 0.009072 *** True
Slovakia brent_dlog brent_dlog 0 1995Q2 2025Q4 123 82.635042 82.635042 4231.427856 3.825850e+03 0.100759 0.850931 0.122217 0.000000 *** 0.280017 0.176483 0.112591 -256.054423 516.108846 521.733215 -0.490176 2.322008 0.114559 True
Slovakia ea_3m_rate_change ea_3m_rate_change 0 1995Q2 2025Q4 123 87.351446 87.351446 4231.427856 3.802748e+03 0.106816 0.829627 0.127666 0.000000 *** 0.315237 0.187410 0.092554 * -255.881290 515.762579 521.386948 -0.836443 1.975741 0.092148 * True
Finland eurusd_dlog eurusd_dlog 2 2004Q3 2025Q4 86 80.616701 80.616701 8071.730937 8.071927e+03 0.000024 0.933765 0.096268 0.000000 *** 0.085080 0.166116 0.608529 -154.164863 312.329727 317.238421 1.806853 4.261200 0.660310 True
Finland ea_share_price_dlog ea_share_price_dlog 2 1995Q4 2025Q4 121 60.294398 60.294398 6220.369376 8.284308e+03 0.286534 1.005690 0.028140 0.000000 *** 0.289264 0.122464 0.018175 ** -204.861176 413.722353 419.313934 -3.606416 -0.810626 0.017895 ** True
Finland eurstock_logreturn eurstock_logreturn 0 2007Q2 2025Q4 75 47.331519 47.331519 10105.013378 1.010516e+04 0.000014 1.023285 0.023773 0.000000 *** 0.741486 0.176639 0.000027 *** -127.148515 258.297030 262.932006 -11.754390 -9.436902 0.000208 *** True
Finland ea_unemployment_std ea_unemployment_std 0 1995Q1 2023Q1 113 11.263394 11.263394 6382.697562 3.637026e+04 1.740161 1.022149 0.011241 0.000000 *** 0.670177 0.354940 0.059007 * -197.545856 399.091713 404.546488 3.921010 6.648397 NaN True
Finland ciss_std ciss_std 0 1995Q1 2025Q4 124 29.409487 29.409487 6349.533017 2.287940e+03 1.020730 0.896633 0.257395 0.000495 *** -0.440016 0.166118 0.008077 *** -208.411031 420.822062 426.462625 -4.196414 -1.376133 0.012801 ** True
Finland brent_dlog brent_dlog 0 1995Q2 2025Q4 123 67.655789 67.655789 6354.478584 2.601953e+03 0.892898 0.894249 0.114803 0.000000 *** 0.286306 0.121148 0.018114 ** -208.425386 420.850771 426.475140 -2.619144 0.193041 0.031617 ** True
Finland ea_3m_rate_change ea_3m_rate_change 0 1995Q2 2025Q4 123 92.150142 92.150142 6354.478584 2.537646e+03 0.917923 0.809849 0.102811 0.000000 *** 0.350598 0.142470 0.013860 ** -208.867043 421.734087 427.358456 -1.735828 1.076357 0.053257 * True
Sweden eurusd_dlog eurusd_dlog 2 2004Q3 2025Q4 86 154.718536 154.718536 16677.085945 1.667716e+04 0.000004 0.865306 0.104500 0.000000 *** -0.029101 0.173331 0.866668 -159.467072 322.934144 327.842839 1.965573 4.419920 0.852801 True
Sweden ea_share_price_dlog ea_share_price_dlog 2 1995Q4 2025Q4 121 248.057162 248.057162 12795.290559 7.856721e+03 0.487708 0.793433 0.101668 0.000000 *** 0.260467 0.133000 0.050182 * -209.840530 423.681061 429.272642 -0.719190 2.076600 0.099148 * True
Sweden eurstock_logreturn eurstock_logreturn 0 2007Q2 2025Q4 75 86.872892 86.872892 14412.234730 1.475313e+04 0.023378 0.950983 0.205788 0.000004 *** 0.814254 0.212576 0.000128 *** -133.644985 271.289971 275.924947 -11.357634 -9.040145 0.000257 *** True
Sweden ea_unemployment_std ea_unemployment_std 0 1995Q1 2023Q1 113 72.806838 72.806838 12523.991228 5.892015e+03 0.754048 0.893714 0.144865 0.000000 *** 0.400335 0.246200 0.103937 -198.283962 400.567923 406.022699 -0.889698 1.837690 0.089148 * True
Sweden ciss_std ciss_std 0 1995Q1 2025Q4 124 316.447402 316.447402 12534.562445 1.252160e+04 0.001034 0.798371 0.099629 0.000000 *** -0.363783 0.144114 0.011594 ** -212.714232 429.428463 435.069027 -3.632199 -0.811918 0.017633 ** True
Sweden brent_dlog brent_dlog 0 1995Q2 2025Q4 123 240.197345 240.197345 12691.586722 2.396281e+05 2.938149 1.008772 0.014455 0.000000 *** -0.037626 0.123253 0.760156 -213.464188 430.928376 436.552745 2.023128 4.835313 NaN True
Sweden ea_3m_rate_change ea_3m_rate_change 0 1995Q2 2025Q4 123 304.414085 304.414085 12691.586722 4.323936e+05 3.528397 1.008915 0.012240 0.000000 *** -0.017056 0.125348 0.891765 -213.398548 430.797097 436.421465 1.891849 4.704033 0.742259 True
Iceland eurusd_dlog eurusd_dlog 2 2004Q3 2025Q4 86 87.807638 87.807638 6186.098485 6.186335e+03 0.000038 0.873998 0.124553 0.000000 *** 0.154186 0.331518 0.641866 -214.540575 433.081149 437.989844 1.793658 4.248005 0.649650 True
Iceland ea_share_price_dlog ea_share_price_dlog 2 1995Q4 2025Q4 121 150.861127 150.861127 5469.226371 6.307372e+03 0.142582 0.803044 0.112177 0.000000 *** 0.175187 0.276353 0.526129 -301.393987 606.787973 612.379554 1.647187 4.442977 0.552525 True
Iceland eurstock_logreturn eurstock_logreturn 0 2007Q2 2025Q4 75 169.271808 169.271808 6553.010484 6.552942e+03 0.000010 0.760624 0.151589 0.000001 *** 0.385865 0.369771 0.296705 -185.957326 375.914652 380.549628 0.982945 3.300433 0.313219 True
Iceland ea_unemployment_std ea_unemployment_std 0 1995Q1 2023Q1 113 165.479089 165.479089 5095.997711 5.096167e+03 0.000033 0.754349 0.193247 0.000095 *** -0.287292 0.844486 0.733707 -282.996123 569.992246 575.447021 1.984632 4.712020 0.901340 True
Iceland ciss_std ciss_std 0 1995Q1 2025Q4 124 100.588899 100.588899 5482.043135 5.452731e+03 0.005361 0.811108 0.141422 0.000000 *** -0.581813 0.328439 0.076486 * -306.728123 617.456245 623.096809 -0.620131 2.200150 0.105516 True
Iceland brent_dlog brent_dlog 0 1995Q2 2025Q4 123 148.982053 148.982053 5482.165638 5.231313e+03 0.046838 0.784797 0.118153 0.000000 *** 0.326277 0.267608 0.222754 -305.177618 614.355235 619.979604 0.536050 3.348234 0.226303 True
Iceland ea_3m_rate_change ea_3m_rate_change 0 1995Q2 2025Q4 123 165.207920 165.207920 5482.165638 5.254381e+03 0.042438 0.728033 0.120764 0.000000 *** 0.591889 0.290796 0.041809 ** -303.878710 611.757421 617.381790 -2.061765 0.750420 0.043865 ** True
Norway eurusd_dlog eurusd_dlog 2 2004Q3 2025Q4 86 95.029055 95.029055 10944.116883 1.094420e+04 0.000007 0.911655 0.131304 0.000000 *** 0.145771 0.127240 0.251946 -134.827377 273.654754 278.563448 0.771167 3.225514 0.267634 True
Norway ea_share_price_dlog ea_share_price_dlog 2 1995Q4 2025Q4 121 92.950583 92.950583 9348.048026 8.010985e+04 2.148231 1.009822 0.018741 0.000000 *** 0.066136 0.109213 0.544798 -192.280143 388.560286 394.151867 1.344954 4.140745 0.418314 True
Norway eurstock_logreturn eurstock_logreturn 0 2007Q2 2025Q4 75 192.072006 192.072006 12486.612758 1.248652e+04 0.000007 0.737263 0.134073 0.000000 *** 0.313583 0.146101 0.031845 ** -114.252887 232.505774 237.140750 -1.868937 0.448551 0.049188 ** True
Norway ea_unemployment_std ea_unemployment_std 0 1995Q1 2023Q1 113 49.541924 49.541924 7063.492690 3.197238e+03 0.792652 0.849942 0.192250 0.000010 *** 0.279120 0.243867 0.252392 -182.506107 369.012214 374.466990 0.639223 3.366611 0.243403 True
Norway ciss_std ciss_std 0 1995Q1 2025Q4 124 76.321315 76.321315 8337.802675 4.448796e+03 0.628166 0.854264 0.207401 0.000038 *** -0.169988 0.129837 0.190453 -198.245870 400.491741 406.132304 0.213022 3.033303 0.181295 True
Norway brent_dlog brent_dlog 0 1995Q2 2025Q4 123 66.852685 66.852685 8665.695724 4.936680e+04 1.739906 1.010147 0.020138 0.000000 *** 0.048954 0.106485 0.645709 -196.557951 397.115903 402.740271 2.067696 4.879880 NaN True
Norway ea_3m_rate_change ea_3m_rate_change 0 1995Q2 2025Q4 123 81.510724 81.510724 8665.695724 6.366088e+04 1.994198 1.009831 0.018991 0.000000 *** 0.072697 0.109918 0.508372 -196.134635 396.269270 401.893639 1.221063 4.033247 0.377466 True
Switzerland eurusd_dlog eurusd_dlog 2 2004Q3 2025Q4 86 97.793875 97.793875 13895.666987 1.157020e+04 0.183144 0.962797 0.612856 0.116183 0.189350 0.149774 0.206142 -136.412866 276.825732 281.734427 -0.044796 2.409551 0.152727 True
Switzerland ea_share_price_dlog ea_share_price_dlog 2 1995Q4 2025Q4 121 157.438317 157.438317 11932.616486 1.229173e+04 0.029652 0.849733 0.083043 0.000000 *** 0.221753 0.097704 0.023230 ** -175.704775 355.409549 361.001130 -2.930958 -0.135168 0.026380 ** True
Switzerland eurstock_logreturn eurstock_logreturn 0 2007Q2 2025Q4 75 640.691900 640.691900 27378.175285 2.737820e+04 0.000001 0.813926 0.121229 0.000000 *** 0.419249 0.151136 0.005538 *** -115.976533 235.953066 240.588042 -7.046140 -4.728651 0.002633 *** True
Switzerland ea_unemployment_std ea_unemployment_std 0 1995Q1 2023Q1 113 152.625474 152.625474 11202.614769 1.120277e+04 0.000014 0.875068 0.087306 0.000000 *** 0.142444 0.168489 0.397877 -168.660590 341.321180 346.775956 1.348371 4.075759 0.419531 True
Switzerland ciss_std ciss_std 0 1995Q1 2025Q4 124 172.950864 172.950864 12232.626544 1.222290e+04 0.000796 0.860783 0.088835 0.000000 *** -0.151913 0.106871 0.155186 -180.127135 364.254269 369.894832 0.024495 2.844777 0.159865 True
Switzerland brent_dlog brent_dlog 0 1995Q2 2025Q4 123 177.497828 177.497828 12217.297381 1.223487e+04 0.001438 0.845582 0.084043 0.000000 *** 0.159141 0.095097 0.094237 * -178.764244 361.528487 367.152856 -0.710588 2.101596 0.099684 * True
Switzerland ea_3m_rate_change ea_3m_rate_change 0 1995Q2 2025Q4 123 186.793089 186.793089 12217.297381 1.230680e+04 0.007299 0.811408 0.086706 0.000000 *** 0.160304 0.104910 0.126508 -179.044266 362.088532 367.712901 -0.150544 2.661641 0.142519 True
Serbia eurusd_dlog eurusd_dlog 2 2004Q3 2025Q4 86 90.162000 90.162000 3008.105047 1.088590e+03 1.016427 0.649107 0.197304 0.001002 *** 0.348070 0.222885 0.118368 -179.405828 362.811656 367.720351 -0.295856 2.158491 0.129720 True
Serbia ea_share_price_dlog ea_share_price_dlog 2 1995Q4 2025Q4 121 201.521978 201.521978 6453.034078 1.633456e+04 0.928733 0.859482 0.108187 0.000000 *** -0.044380 0.330180 0.893078 -315.260099 634.520198 640.111779 1.950322 4.746113 0.823625 True
Serbia eurstock_logreturn eurstock_logreturn 0 2007Q2 2025Q4 75 144.022556 144.022556 7866.850022 7.866832e+03 0.000002 0.822445 0.211737 0.000103 *** 0.691388 0.255180 0.006740 *** -148.056387 300.112775 304.747751 -5.633235 -3.315747 0.005730 *** True
Serbia ea_unemployment_std ea_unemployment_std 0 1995Q1 2023Q1 113 274.457570 274.457570 6586.380990 6.586320e+03 0.000009 0.626727 0.120630 0.000000 *** -0.133453 0.677232 0.843784 -300.048167 604.096334 609.551110 1.990650 4.718038 0.922969 True
Serbia ciss_std ciss_std 0 1995Q1 2025Q4 124 312.796181 312.796181 6594.834564 6.593823e+03 0.000153 0.622345 0.113547 0.000000 *** -0.343996 0.380939 0.366515 -323.818128 651.636256 657.276819 1.177480 3.997762 0.364445 True
Serbia brent_dlog brent_dlog 0 1995Q2 2025Q4 123 280.751075 280.751075 6610.713700 6.753842e+03 0.021420 0.634591 0.108130 0.000000 *** 0.220080 0.319865 0.491427 -319.661357 643.322715 648.947084 1.578474 4.390659 0.516177 True
Serbia ea_3m_rate_change ea_3m_rate_change 0 1995Q2 2025Q4 123 292.639402 292.639402 6610.713700 6.726135e+03 0.017309 0.621062 0.106908 0.000000 *** 0.321544 0.329414 0.329010 -319.399275 642.798551 648.422919 1.054310 3.866494 0.330819 True
Türkiye eurusd_dlog eurusd_dlog 2 2004Q3 2025Q4 86 132.085152 132.085152 10584.401199 1.058458e+04 0.000017 0.872682 0.110257 0.000000 *** 0.012254 0.307642 0.968226 -206.679544 417.359087 422.267782 2.001711 4.456059 NaN True
Türkiye ea_share_price_dlog ea_share_price_dlog 2 1995Q4 2025Q4 121 94.491408 94.491408 6915.557602 7.078487e+03 0.023287 0.824981 0.091621 0.000000 *** 0.390664 0.272674 0.151939 -285.000104 574.000209 579.591790 0.110804 2.906595 0.169293 True
Türkiye eurstock_logreturn eurstock_logreturn 0 2007Q2 2025Q4 75 262.419486 262.419486 9841.767430 9.841706e+03 0.000006 0.785767 0.118031 0.000000 *** 1.148707 0.334048 0.000584 *** -174.913675 353.827349 358.462326 -11.275170 -8.957682 0.000269 *** True
Türkiye ea_unemployment_std ea_unemployment_std 0 1995Q1 2023Q1 113 30.666305 30.666305 6556.710113 6.556744e+03 0.000005 0.835834 0.087697 0.000000 *** 1.622840 0.677695 0.016636 ** -269.042244 542.084488 547.539264 -0.895727 1.831661 0.088815 * True
Türkiye ciss_std ciss_std 0 1995Q1 2025Q4 124 78.186916 78.186916 6640.039099 6.626628e+03 0.002022 0.792279 0.100784 0.000000 *** -0.761854 0.340945 0.025448 ** -290.427583 584.855166 590.495729 -2.560873 0.259409 0.032710 ** True
Türkiye brent_dlog brent_dlog 0 1995Q2 2025Q4 123 81.484020 81.484020 6792.465651 6.565161e+03 0.034037 0.896388 0.088510 0.000000 *** 0.138484 0.239290 0.562773 -290.221920 584.443840 590.068208 1.686992 4.499176 0.575840 True
Türkiye ea_3m_rate_change ea_3m_rate_change 0 1995Q2 2025Q4 123 87.136986 87.136986 6792.465651 6.650482e+03 0.021125 0.870947 0.097697 0.000000 *** 0.217861 0.280937 0.438057 -290.053523 584.107045 589.731414 1.350197 4.162382 0.420183 True
Results
| Common factor | $\Delta BIC<0$ | $\Delta AIC<0$ | $p<0.01$ | $p<0.05$ | $p<0.10$ |
|---|---|---|---|---|---|
| Eurostoxx50 return | 23/26 | 24/26 | 21/26 | 24/26 | 24/26 |
| CISS financial stress | 8/26 | 15/26 | 5/26 | 11/26 | 13/26 |
| Brent oil price change | 4/26 | 11/26 | 2/26 | 5/26 | 8/26 |
| Euro-area share price index | 3/26 | 11/26 | 0/26 | 4/26 | 5/26 |
| Euro-area 3-month rate change | 2/26 | 8/26 | 0/26 | 4/26 | 6/26 |
| Euro-area unemployment rate | 1/26 | 9/26 | 1/26 | 3/26 | 5/26 |
| EUR/USD exchange-rate change | 1/26 | 4/26 | 0/26 | 1/26 | 1/26 |
Note. The table reports the number of countries, out of 26, for which the factor-augmented model improves upon the corresponding same-sample baseline model. AIC and BIC improvements are defined as $\Delta AIC<0$ and $\Delta BIC<0$, respectively. The significance columns refer to the likelihood-ratio test comparing the factor-augmented model to the baseline model.
| Country | Eurostoxx50 $\beta$ | Eurostoxx50 $\gamma$ | Eurostoxx50 $\Delta AIC$ | Brent $\beta$ | Brent $\gamma$ | Brent $\Delta AIC$ | CISS $\beta$ | CISS $\gamma$ | CISS $\Delta AIC$ |
|---|---|---|---|---|---|---|---|---|---|
| Belgium | 0.652 | 0.737 | -6.515 | 0.671 | 0.103 | 1.973 | 0.679 | -0.142 | 1.536 |
| Czechia | 1.019 | 0.701 | -8.663 | 0.929 | 0.184 | -0.164 | 0.940 | -0.328 | -1.129 |
| Denmark | 0.778 | 0.432 | -2.487 | 0.776 | 0.235 | -1.430 | 0.752 | -0.349 | -3.668 |
| Germany | 0.872 | 0.737 | -8.299 | 0.770 | 0.257 | -2.005 | 0.770 | -0.311 | -1.934 |
| Estonia | 1.019 | 0.748 | -8.146 | 0.995 | 0.366 | -2.887 | 0.924 | -0.726 | -10.739 |
| Ireland | 0.804 | 0.376 | 1.175 | 0.793 | -0.310 | 0.122 | 0.838 | -0.541 | -1.244 |
| Greece | 1.013 | 1.129 | -14.216 | 0.914 | 0.228 | 0.334 | 0.971 | -0.279 | 1.004 |
| Spain | 0.814 | 1.120 | -6.301 | 0.742 | 0.095 | 1.877 | 0.752 | -0.159 | 1.527 |
| France | 0.641 | 0.853 | -5.655 | 0.668 | 0.050 | 1.696 | 0.662 | -0.191 | 1.427 |
| Croatia | 1.010 | 1.025 | -7.800 | 0.828 | 0.372 | -1.276 | 0.817 | -0.563 | -2.670 |
| Latvia | 1.025 | 1.117 | -13.291 | 0.976 | 0.122 | 1.609 | 0.951 | -0.749 | -6.239 |
| Lithuania | 1.015 | 0.841 | -10.272 | 1.009 | 0.411 | -4.965 | 0.927 | -0.931 | -15.677 |
| Luxembourg | 0.669 | 0.888 | -14.995 | 0.776 | 0.061 | 1.121 | 0.803 | -0.186 | 1.006 |
| Hungary | 0.777 | 0.851 | -4.972 | 0.685 | 0.296 | -0.413 | 0.662 | -0.524 | -4.572 |
| Austria | 0.747 | 0.711 | -3.934 | 0.758 | 0.127 | 1.522 | 0.768 | -0.150 | 0.762 |
| Poland | 1.020 | 0.660 | -10.321 | 0.834 | 0.109 | 1.656 | 1.011 | -0.281 | -0.517 |
| Portugal | 0.766 | 0.991 | -6.717 | 0.758 | 0.069 | 1.981 | 0.766 | -0.093 | 1.725 |
| Romania | 1.020 | 1.243 | -12.249 | 0.740 | 0.667 | -5.477 | 0.735 | -0.434 | -0.323 |
| Slovakia | 0.793 | 0.684 | -7.838 | 0.866 | 0.279 | -0.529 | 0.810 | -0.586 | -4.800 |
| Finland | 1.023 | 0.741 | -11.754 | 1.001 | 0.257 | -2.813 | 0.993 | -0.430 | -4.800 |
| Sweden | 0.951 | 0.814 | -11.358 | 0.819 | 0.049 | 1.487 | 0.798 | -0.365 | -3.663 |
| Iceland | 0.761 | 0.386 | 0.983 | 0.792 | 0.327 | 0.496 | 0.812 | -0.584 | -0.637 |
| Norway | 0.737 | 0.314 | -1.869 | 0.815 | 0.042 | 1.906 | 0.855 | -0.170 | 0.202 |
| Switzerland | 0.814 | 0.419 | -7.046 | 0.843 | 0.160 | -0.791 | 0.861 | -0.152 | 0.018 |
| Serbia | 0.822 | 0.691 | -5.633 | 0.636 | 0.196 | 1.628 | 0.622 | -0.343 | 1.180 |
| Türkiye | 0.786 | 1.149 | -11.276 | 0.902 | 0.142 | 1.666 | 0.792 | -0.765 | -2.602 |
Note. $\Delta AIC$ is defined as the AIC of the factor-augmented model minus the AIC of the corresponding same-sample baseline model. Negative values therefore indicate an improvement relative to the baseline. The reported $\beta$ and $\gamma$ values are taken directly from the one-factor state-space estimations.
Overall, the results show that the Eurostoxx50 return is the only broadly robust common factor in the country sample. It improves model fit in almost all countries and is usually statistically significant, indicating that European equity-market movements contain systematic information about national output gaps. CISS and Brent oil prices also improve some country-level specifications, but their effects are much more selective and less stable across criteria. The country-level results confirm this pattern: Eurostoxx50 coefficients are consistently positive and associated with negative $\Delta AIC$ values in nearly all cases. At the same time, some specifications have estimated $\beta$ values above 0.95, which is problematic because the output gap then becomes close to a unit-root process. This is economically difficult to interpret, since the output gap should represent a temporary cyclical deviation from potential output, not a near-permanent component. Therefore, while the Eurostoxx50 return provides the clearest and most general signal, the results still need to be evaluated together with persistence diagnostics and the plausibility of the estimated potential-output paths.
Validation
This cell tests whether the Eurostoxx50-augmented output gap performs better than the same-sample baseline gap in an external validation exercise. The idea is that a credible output-gap estimate should contain information about future inflationary pressure.
The code uses the output-gap objects already created in the notebook. The only external data downloaded here is inflation: monthly all-items HICP indices are downloaded from Eurostat and converted to quarterly inflation.
For each country, the same Phillips-curve regression is estimated twice: once with the baseline gap and once with the Eurostoxx50 gap:
The two specifications are compared using $R^2$, adjusted $R^2$, AIC, BIC, RMSE, and the significance of the output-gap coefficient. The cell prints only the final summary table, showing in how many countries the Eurostoxx50 gap performs better according to each criterion.
Show code
# ============================================================
# CELL — Phillips-curve validation using existing gap objects
# ============================================================
# Assumptions:
# - The Eurostoxx and same-sample baseline output gaps already exist in memory,
# preferably as `eurostoxx_potential_output_long` or
# `eurostoxx_potential_output["long"]`.
# - The code does NOT read the previous output-gap Excel file.
# - If quarterly HICP has already been downloaded into `hicp`, it is reused.
# Otherwise HICP is downloaded from Eurostat.
#
# Printed output:
# - only SUMMARY
#
# Output objects:
# pc_gaps
# hicp
# failed_hicp
# pc_reg_results
# pc_failed_regressions
# pc_comparison
# pc_summary
# summary
import warnings
warnings.filterwarnings("ignore")
import time
import numpy as np
import pandas as pd
import requests
import statsmodels.api as sm
from scipy.stats import binomtest
# ============================================================
# 0. SETTINGS
# ============================================================
EUROSTOXX_FACTOR_ID = "eurstock_logreturn"
# HICP: Eurostat monthly HICP index, all-items, 2015=100.
EUROSTAT_DATASET = "prc_hicp_midx"
HICP_UNIT = "I15"
HICP_COICOP = "CP00"
# Inflation definition:
# "yoy" = 100 * (log HICP_q - log HICP_{q-4})
# "qoq_ann" = 400 * (log HICP_q - log HICP_{q-1})
INFLATION_DEFINITION = "yoy"
# Phillips curve:
# pi_t = alpha + rho*pi_{t-1} + theta*gap_{t-1} + error_t
GAP_LAG = 1
PI_LAG = 1
# HAC standard errors.
HAC_MAXLAGS = 4
MIN_REG_OBS = 20
# None = all countries available in the existing output-gap object.
COUNTRIES = None
# ============================================================
# 1. COUNTRY NAME TO EUROSTAT GEO CODE
# ============================================================
COUNTRY_TO_GEO = {
"Austria": "AT",
"Belgium": "BE",
"Croatia": "HR",
"Czechia": "CZ",
"Denmark": "DK",
"Estonia": "EE",
"Finland": "FI",
"France": "FR",
"Germany": "DE",
"Greece": "EL",
"Hungary": "HU",
"Iceland": "IS",
"Ireland": "IE",
"Latvia": "LV",
"Lithuania": "LT",
"Luxembourg": "LU",
"Norway": "NO",
"Poland": "PL",
"Portugal": "PT",
"Romania": "RO",
"Serbia": "RS",
"Slovakia": "SK",
"Spain": "ES",
"Sweden": "SE",
"Switzerland": "CH",
"Türkiye": "TR",
"Turkey": "TR",
}
# ============================================================
# 2. BASIC HELPERS
# ============================================================
def parse_quarter(x):
if isinstance(x, pd.Period):
return x.asfreq("Q")
if isinstance(x, pd.Timestamp):
return x.to_period("Q")
return pd.Period(str(x), freq="Q")
def safe_star(p):
if p is None or not np.isfinite(p):
return ""
if p < 0.01:
return "***"
if p < 0.05:
return "**"
if p < 0.10:
return "*"
return ""
def compute_inflation_from_hicp_index(q):
q = q.copy().sort_values(["country", "quarter"])
if INFLATION_DEFINITION == "yoy":
q["pi"] = q.groupby("country")["hicp_index_q"].transform(
lambda s: 100.0 * (np.log(s) - np.log(s.shift(4)))
)
elif INFLATION_DEFINITION == "qoq_ann":
q["pi"] = q.groupby("country")["hicp_index_q"].transform(
lambda s: 400.0 * (np.log(s) - np.log(s.shift(1)))
)
else:
raise ValueError("INFLATION_DEFINITION must be 'yoy' or 'qoq_ann'.")
return q
# ============================================================
# 3. EXISTING OUTPUT-GAP OBJECTS
# ============================================================
def gaps_from_eurostoxx_potential_long(obj):
d = obj.copy()
needed = {
"country",
"quarter",
"output_gap_same_sample_baseline",
"output_gap_eurostoxx",
}
missing = needed - set(d.columns)
if missing:
raise ValueError(f"Missing columns from Eurostoxx potential object: {missing}")
d["quarter"] = d["quarter"].apply(parse_quarter)
optional_base = [
"real_gdp",
"y",
"potential_same_sample_baseline",
"lambda2_same_sample_baseline",
"beta_same_sample_baseline",
]
optional_euro = [
"real_gdp",
"y",
"potential_eurostoxx",
"lambda2_eurostoxx",
"beta_eurostoxx",
"gamma_eurostoxx",
]
base_cols = [c for c in ["country", "quarter", "output_gap_same_sample_baseline"] + optional_base if c in d.columns]
euro_cols = [c for c in ["country", "quarter", "output_gap_eurostoxx"] + optional_euro if c in d.columns]
base = d[base_cols].copy()
base = base.rename(columns={
"output_gap_same_sample_baseline": "output_gap",
"potential_same_sample_baseline": "potential",
"lambda2_same_sample_baseline": "lambda2_used",
"beta_same_sample_baseline": "beta",
})
base["model_type"] = "baseline_same_sample"
base["factor_id"] = EUROSTOXX_FACTOR_ID
euro = d[euro_cols].copy()
euro = euro.rename(columns={
"output_gap_eurostoxx": "output_gap",
"potential_eurostoxx": "potential",
"lambda2_eurostoxx": "lambda2_used",
"beta_eurostoxx": "beta",
"gamma_eurostoxx": "gamma",
})
euro["model_type"] = "factor"
euro["factor_id"] = EUROSTOXX_FACTOR_ID
gaps = pd.concat([base, euro], ignore_index=True, sort=False)
gaps = gaps.dropna(subset=["country", "quarter", "model_type", "factor_id", "output_gap"])
return gaps
def gaps_from_outputs_long(obj):
d = obj.copy()
needed = {"country", "quarter", "model_type", "factor_id", "output_gap"}
missing = needed - set(d.columns)
if missing:
raise ValueError(f"Missing columns from outputs_long: {missing}")
d = d[d["factor_id"].astype(str).eq(EUROSTOXX_FACTOR_ID)].copy()
d = d[d["model_type"].isin(["baseline_same_sample", "factor"])].copy()
d["quarter"] = d["quarter"].apply(parse_quarter)
keep = [
"country",
"quarter",
"model_type",
"factor_id",
"output_gap",
]
optional = [
"real_gdp",
"y",
"potential",
"beta",
"gamma",
"lambda2_opt",
"lambda2_used",
]
keep += [c for c in optional if c in d.columns]
return d[keep].copy()
def get_output_gaps_from_existing_objects():
# Preferred object from the previous Eurostoxx-specific potential-output cell.
if "eurostoxx_potential_output_long" in globals():
return gaps_from_eurostoxx_potential_long(globals()["eurostoxx_potential_output_long"])
if "eurostoxx_potential_output" in globals():
obj = globals()["eurostoxx_potential_output"]
if isinstance(obj, dict):
if "long" in obj and isinstance(obj["long"], pd.DataFrame) and not obj["long"].empty:
return gaps_from_eurostoxx_potential_long(obj["long"])
if "by_country" in obj and isinstance(obj["by_country"], dict) and len(obj["by_country"]) > 0:
long_obj = pd.concat([v.copy() for v in obj["by_country"].values()], axis=0).reset_index()
return gaps_from_eurostoxx_potential_long(long_obj)
# Fallback: full long output object from the factor-estimation cell.
if "outputs_long" in globals():
return gaps_from_outputs_long(globals()["outputs_long"])
if "all_outputs" in globals() and len(globals()["all_outputs"]) > 0:
d = pd.concat([x.copy() for x in globals()["all_outputs"]], axis=0).reset_index()
d = d.rename(columns={"index": "quarter"})
return gaps_from_outputs_long(d)
raise NameError(
"Nem találok meglévő output-gap objektumot. "
"Futtasd előbb azt a cellát, amely létrehozza az "
"`eurostoxx_potential_output_long` vagy `outputs_long` objektumot."
)
# ============================================================
# 4. EUROSTAT JSON-STAT DOWNLOAD AND PARSING
# ============================================================
def flatten_jsonstat_values(js):
dim_ids = js["id"]
sizes = js["size"]
dims = js["dimension"]
pos_to_label = {}
for dim in dim_ids:
idx = dims[dim]["category"]["index"]
pos_to_label[dim] = {int(pos): label for label, pos in idx.items()}
values = js.get("value", {})
if isinstance(values, list):
value_items = [(i, v) for i, v in enumerate(values) if v is not None]
else:
value_items = [(int(i), v) for i, v in values.items() if v is not None]
rows = []
for flat_idx, val in value_items:
remaining = int(flat_idx)
coords = []
for size in reversed(sizes):
coords.append(remaining % size)
remaining //= size
coords = list(reversed(coords))
row = {}
for dim, pos in zip(dim_ids, coords):
row[dim] = pos_to_label[dim].get(pos)
row["value"] = val
rows.append(row)
return pd.DataFrame(rows)
def download_hicp_country(country, geo, sleep_sec=0.2):
url = f"https://ec.europa.eu/eurostat/api/dissemination/statistics/1.0/data/{EUROSTAT_DATASET}"
params = {
"format": "JSON",
"lang": "en",
"freq": "M",
"unit": HICP_UNIT,
"coicop": HICP_COICOP,
"geo": geo,
}
r = requests.get(url, params=params, timeout=60)
r.raise_for_status()
js = r.json()
d = flatten_jsonstat_values(js)
if d.empty:
raise ValueError(f"No HICP data returned for {country} ({geo}).")
if "time" not in d.columns:
possible = [c for c in d.columns if c.lower() in ["time", "time_period"]]
if possible:
d = d.rename(columns={possible[0]: "time"})
else:
raise ValueError(f"No time dimension found for {country} ({geo}). Columns: {d.columns.tolist()}")
d = d[["time", "value"]].copy()
d["hicp_index_m"] = pd.to_numeric(d["value"], errors="coerce")
d = d.dropna(subset=["hicp_index_m"])
d["month"] = pd.PeriodIndex(d["time"].astype(str), freq="M")
d["quarter"] = d["month"].dt.asfreq("Q")
q = (
d.groupby("quarter", as_index=False)["hicp_index_m"]
.mean()
.rename(columns={"hicp_index_m": "hicp_index_q"})
)
q = q.sort_values("quarter")
q["country"] = country
q["geo"] = geo
q = compute_inflation_from_hicp_index(q)
time.sleep(sleep_sec)
return q
def download_hicp_all(countries):
all_rows = []
failed = []
for country in countries:
geo = COUNTRY_TO_GEO.get(country)
if geo is None:
failed.append({"country": country, "geo": None, "error": "Missing geo code"})
continue
try:
q = download_hicp_country(country, geo)
all_rows.append(q)
except Exception as e:
failed.append({"country": country, "geo": geo, "error": str(e)})
hicp_out = pd.concat(all_rows, ignore_index=True) if all_rows else pd.DataFrame()
failed_hicp_out = pd.DataFrame(failed)
return hicp_out, failed_hicp_out
def get_hicp_from_existing_or_download(countries):
if "hicp" in globals() and isinstance(globals()["hicp"], pd.DataFrame) and not globals()["hicp"].empty:
h = globals()["hicp"].copy()
if "quarter" not in h.columns:
raise ValueError("Existing `hicp` object has no `quarter` column.")
if "country" not in h.columns:
raise ValueError("Existing `hicp` object has no `country` column.")
h["quarter"] = h["quarter"].apply(parse_quarter)
if "pi" not in h.columns:
if "hicp_index_q" not in h.columns:
raise ValueError("Existing `hicp` object has neither `pi` nor `hicp_index_q`.")
h = compute_inflation_from_hicp_index(h)
h = h[h["country"].isin(countries)].copy()
failed = pd.DataFrame()
return h, failed
return download_hicp_all(countries)
# ============================================================
# 5. PHILLIPS CURVE REGRESSION
# ============================================================
def run_phillips_regression(country, gap_model_name, d):
d = d.copy().sort_values("quarter")
d["pi_lag"] = d["pi"].shift(PI_LAG)
d["gap_lag"] = d["output_gap"].shift(GAP_LAG)
reg = d.dropna(subset=["pi", "pi_lag", "gap_lag"]).copy()
if len(reg) < MIN_REG_OBS:
return None
y = reg["pi"]
X = sm.add_constant(reg[["pi_lag", "gap_lag"]])
res = sm.OLS(y, X).fit(cov_type="HAC", cov_kwds={"maxlags": HAC_MAXLAGS})
fitted = res.predict(X)
rmse = float(np.sqrt(np.mean((y - fitted) ** 2)))
return {
"country": country,
"gap_model": gap_model_name,
"sample_start": str(reg["quarter"].min()),
"sample_end": str(reg["quarter"].max()),
"n_obs": int(len(reg)),
"inflation_definition": INFLATION_DEFINITION,
"pi_lag": PI_LAG,
"gap_lag": GAP_LAG,
"hac_maxlags": HAC_MAXLAGS,
"alpha": res.params.get("const", np.nan),
"alpha_se": res.bse.get("const", np.nan),
"alpha_p": res.pvalues.get("const", np.nan),
"rho_pi_lag": res.params.get("pi_lag", np.nan),
"rho_pi_lag_se": res.bse.get("pi_lag", np.nan),
"rho_pi_lag_p": res.pvalues.get("pi_lag", np.nan),
"theta_gap_lag": res.params.get("gap_lag", np.nan),
"theta_gap_lag_se": res.bse.get("gap_lag", np.nan),
"theta_gap_lag_p": res.pvalues.get("gap_lag", np.nan),
"theta_gap_lag_signif": safe_star(res.pvalues.get("gap_lag", np.nan)),
"r2": res.rsquared,
"adj_r2": res.rsquared_adj,
"aic": res.aic,
"bic": res.bic,
"rmse": rmse,
}
def run_all_regressions(gaps, hicp_data):
model_map = {
"baseline_same_sample": "baseline",
"factor": "eurostoxx",
}
rows = []
failed = []
countries = sorted(gaps["country"].dropna().unique())
for country in countries:
h = hicp_data[hicp_data["country"] == country][["quarter", "hicp_index_q", "pi"]].copy()
if h.empty:
failed.append({"country": country, "model_type": None, "error": "No HICP data"})
continue
for model_type, gap_model_name in model_map.items():
g = gaps[
(gaps["country"] == country) &
(gaps["model_type"] == model_type)
].copy()
if g.empty:
failed.append({"country": country, "model_type": model_type, "error": "No gap data"})
continue
merged = pd.merge(
h,
g,
on="quarter",
how="inner",
validate="one_to_many",
)
merged = merged.drop_duplicates(subset=["quarter", "model_type"])
try:
out = run_phillips_regression(country, gap_model_name, merged)
if out is None:
failed.append({
"country": country,
"model_type": model_type,
"error": f"Too few observations after merging: {len(merged)}",
})
continue
rows.append(out)
except Exception as e:
failed.append({"country": country, "model_type": model_type, "error": str(e)})
reg_results = pd.DataFrame(rows)
failed_reg = pd.DataFrame(failed)
return reg_results, failed_reg
# ============================================================
# 6. BASELINE VS EUROSTOXX COMPARISON
# ============================================================
def compare_baseline_eurostoxx(reg_results):
base = reg_results[reg_results["gap_model"] == "baseline"].copy().set_index("country")
euro = reg_results[reg_results["gap_model"] == "eurostoxx"].copy().set_index("country")
common = sorted(set(base.index).intersection(set(euro.index)))
rows = []
for country in common:
b = base.loc[country]
e = euro.loc[country]
rows.append({
"country": country,
"baseline_n_obs": b["n_obs"],
"eurostoxx_n_obs": e["n_obs"],
"baseline_theta": b["theta_gap_lag"],
"baseline_theta_p": b["theta_gap_lag_p"],
"baseline_theta_signif": b["theta_gap_lag_signif"],
"eurostoxx_theta": e["theta_gap_lag"],
"eurostoxx_theta_p": e["theta_gap_lag_p"],
"eurostoxx_theta_signif": e["theta_gap_lag_signif"],
"baseline_r2": b["r2"],
"eurostoxx_r2": e["r2"],
"delta_r2_euro_minus_base": e["r2"] - b["r2"],
"baseline_adj_r2": b["adj_r2"],
"eurostoxx_adj_r2": e["adj_r2"],
"delta_adj_r2_euro_minus_base": e["adj_r2"] - b["adj_r2"],
"baseline_aic": b["aic"],
"eurostoxx_aic": e["aic"],
"delta_aic_euro_minus_base": e["aic"] - b["aic"],
"baseline_bic": b["bic"],
"eurostoxx_bic": e["bic"],
"delta_bic_euro_minus_base": e["bic"] - b["bic"],
"baseline_rmse": b["rmse"],
"eurostoxx_rmse": e["rmse"],
"delta_rmse_euro_minus_base": e["rmse"] - b["rmse"],
"euro_higher_r2": e["r2"] > b["r2"],
"euro_higher_adj_r2": e["adj_r2"] > b["adj_r2"],
"euro_lower_aic": e["aic"] < b["aic"],
"euro_lower_bic": e["bic"] < b["bic"],
"euro_lower_rmse": e["rmse"] < b["rmse"],
"baseline_gap_significant_5pct": b["theta_gap_lag_p"] < 0.05,
"eurostoxx_gap_significant_5pct": e["theta_gap_lag_p"] < 0.05,
"euro_theta_p_lower": e["theta_gap_lag_p"] < b["theta_gap_lag_p"],
})
return pd.DataFrame(rows)
def make_summary(comparison):
rows = []
checks = [
("euro_higher_r2", "Eurostoxx gap has higher R2"),
("euro_higher_adj_r2", "Eurostoxx gap has higher adjusted R2"),
("euro_lower_aic", "Eurostoxx gap has lower AIC"),
("euro_lower_bic", "Eurostoxx gap has lower BIC"),
("euro_lower_rmse", "Eurostoxx gap has lower RMSE"),
("baseline_gap_significant_5pct", "Baseline gap coefficient significant at 5%"),
("eurostoxx_gap_significant_5pct", "Eurostoxx gap coefficient significant at 5%"),
("euro_theta_p_lower", "Eurostoxx gap has lower theta p-value"),
]
for col, label in checks:
valid = comparison[col].dropna()
n_valid = len(valid)
k = int(valid.sum()) if n_valid > 0 else 0
share = k / n_valid if n_valid > 0 else np.nan
if n_valid > 0:
pval = binomtest(k, n_valid, p=0.5, alternative="greater").pvalue
else:
pval = np.nan
rows.append({
"criterion": label,
"count": k,
"out_of": n_valid,
"share": share,
"binomial_p_value_against_50pct": pval,
})
return pd.DataFrame(rows)
# ============================================================
# 7. MAIN
# ============================================================
pc_gaps = get_output_gaps_from_existing_objects()
if COUNTRIES is not None:
pc_gaps = pc_gaps[pc_gaps["country"].isin(COUNTRIES)].copy()
countries = sorted(pc_gaps["country"].dropna().unique())
hicp, failed_hicp = get_hicp_from_existing_or_download(countries)
pc_reg_results, pc_failed_regressions = run_all_regressions(pc_gaps, hicp)
pc_comparison = compare_baseline_eurostoxx(pc_reg_results) if not pc_reg_results.empty else pd.DataFrame()
pc_summary = make_summary(pc_comparison) if not pc_comparison.empty else pd.DataFrame()
# Compatibility alias.
summary = pc_summary
print("\n" + "#" * 100)
print("SUMMARY")
print("#" * 100)
if not pc_summary.empty:
print(pc_summary.to_string(index=False))
else:
print("No summary results.")
Show output
####################################################################################################
SUMMARY
####################################################################################################
criterion count out_of share binomial_p_value_against_50pct
Eurostoxx gap has higher R2 12 26 0.461538 0.721401
Eurostoxx gap has higher adjusted R2 12 26 0.461538 0.721401
Eurostoxx gap has lower AIC 12 26 0.461538 0.721401
Eurostoxx gap has lower BIC 12 26 0.461538 0.721401
Eurostoxx gap has lower RMSE 12 26 0.461538 0.721401
Baseline gap coefficient significant at 5% 5 26 0.192308 0.999733
Eurostoxx gap coefficient significant at 5% 5 26 0.192308 0.999733
Eurostoxx gap has lower theta p-value 12 26 0.461538 0.721401
Show code
import math
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
# ============================================================
# SETTINGS
# ============================================================
N_COLS = 3
FIG_WIDTH = 11.69
FIG_HEIGHT = 14
PLOT_ACTUAL_Y = True
# ============================================================
# LOAD FROM EXISTING OBJECTS
# ============================================================
def get_eurostoxx_potential_plot_data():
if "eurostoxx_potential_output_long" in globals():
d = eurostoxx_potential_output_long.copy()
elif (
"eurostoxx_potential_output" in globals()
and isinstance(eurostoxx_potential_output, dict)
and "long" in eurostoxx_potential_output
):
d = eurostoxx_potential_output["long"].copy()
elif "outputs_long" in globals():
tmp = outputs_long.copy()
needed = {"country", "quarter", "model_type", "factor_id", "potential"}
missing = needed - set(tmp.columns)
if missing:
raise ValueError(f"`outputs_long` missing columns: {missing}")
tmp = tmp[
(tmp["factor_id"].astype(str) == "eurstock_logreturn") &
(tmp["model_type"].isin(["baseline_same_sample", "factor"]))
].copy()
base = (
tmp[tmp["model_type"] == "baseline_same_sample"]
[["country", "quarter", "potential"]]
.rename(columns={"potential": "potential_same_sample_baseline"})
)
euro = (
tmp[tmp["model_type"] == "factor"]
[["country", "quarter", "potential"]]
.rename(columns={"potential": "potential_eurostoxx"})
)
d = pd.merge(base, euro, on=["country", "quarter"], how="inner")
if "y" in tmp.columns:
y_df = (
tmp[["country", "quarter", "y"]]
.drop_duplicates(subset=["country", "quarter"])
.rename(columns={"y": "actual_log_gdp"})
)
d = pd.merge(d, y_df, on=["country", "quarter"], how="left")
else:
raise NameError(
"Nem találom az ábrához szükséges objektumot. "
"Előbb fusson le az Eurostoxx potential outputot létrehozó cella."
)
needed = {
"country",
"quarter",
"potential_same_sample_baseline",
"potential_eurostoxx",
}
missing = needed - set(d.columns)
if missing:
raise ValueError(f"Missing columns from plotting object: {missing}")
d = d.copy()
d["quarter"] = pd.PeriodIndex(d["quarter"].astype(str), freq="Q")
if "actual_log_gdp" not in d.columns:
if {
"potential_same_sample_baseline",
"output_gap_same_sample_baseline",
}.issubset(d.columns):
d["actual_log_gdp"] = (
d["potential_same_sample_baseline"]
+ d["output_gap_same_sample_baseline"]
)
elif {
"potential_eurostoxx",
"output_gap_eurostoxx",
}.issubset(d.columns):
d["actual_log_gdp"] = (
d["potential_eurostoxx"]
+ d["output_gap_eurostoxx"]
)
return d
df = get_eurostoxx_potential_plot_data()
countries = sorted(df["country"].dropna().unique())
n_countries = len(countries)
n_rows = math.ceil(n_countries / N_COLS)
# ============================================================
# PLOT
# ============================================================
fig, axes = plt.subplots(
n_rows,
N_COLS,
figsize=(FIG_WIDTH, FIG_HEIGHT),
sharex=True,
squeeze=False
)
axes = axes.flatten()
legend_handles = None
legend_labels = None
for i, country in enumerate(countries):
ax = axes[i]
d = df[df["country"] == country].copy()
d = d.sort_values("quarter")
x = d["quarter"].dt.to_timestamp(how="end")
if PLOT_ACTUAL_Y and "actual_log_gdp" in d.columns:
ax.plot(
x,
d["actual_log_gdp"],
linestyle="--",
linewidth=0.8,
label="Actual log GDP"
)
ax.plot(
x,
d["potential_same_sample_baseline"],
linewidth=1.1,
label="Baseline potential"
)
ax.plot(
x,
d["potential_eurostoxx"],
linewidth=1.1,
label="Eurostoxx potential"
)
ax.set_title(country, fontsize=10.5, fontweight="bold", pad=2)
ax.tick_params(axis="y", labelsize=6)
ax.tick_params(axis="x", labelsize=7)
ax.grid(True, alpha=0.25, linewidth=0.4)
ax.margins(x=0.01)
if legend_handles is None:
legend_handles, legend_labels = ax.get_legend_handles_labels()
ax.tick_params(axis="x", which="both", labelbottom=False)
# ============================================================
# X labels only on the last used subplot in each column
# ============================================================
last_used_in_col = {}
for idx in range(n_countries):
col = idx % N_COLS
last_used_in_col[col] = idx
for col, idx in last_used_in_col.items():
axes[idx].tick_params(axis="x", which="both", labelbottom=True)
for j in range(n_countries, len(axes)):
axes[j].axis("off")
# ============================================================
# FINAL LAYOUT
# ============================================================
fig.legend(
legend_handles,
legend_labels,
loc="upper center",
ncol=3,
fontsize=8,
frameon=False,
bbox_to_anchor=(0.5, 0.995)
)
fig.supxlabel("Year", fontsize=9, y=0.015)
fig.supylabel("100 × log real GDP", fontsize=9, x=0.005)
fig.tight_layout(rect=[0.015, 0.035, 0.995, 0.955], h_pad=0.25, w_pad=0.35)
plt.show()
Show figure
The validation results give a more cautious picture than the likelihood-based comparisons above. The Eurostoxx50-augmented model clearly improves in-sample fit in most countries, but this does not automatically mean that the resulting output gaps are better in external validation exercises. In the Phillips-curve regressions, the Eurostoxx50 gap does not systematically improve the inflation equation, and the real-time revision exercise also shows mixed evidence. The country-level potential-output plots help explain this. In several countries, especially Croatia, Estonia, Finland, Greece, Latvia, Lithuania and Poland, the Eurostoxx50 specification produces very persistent cyclical components and potential-output paths that are difficult to interpret economically. These cases suggest that the factor sometimes improves statistical fit by shifting too much variation into the output gap. Excluding these problematic countries, and also Ireland and Iceland where the Eurostoxx50 coefficient is weak, the Eurostoxx50 estimates look more informative overall. Still, the main conclusion remains cautious: the Eurostoxx50 return contains useful common European financial-cycle information, but it does not always produce more plausible or better externally validated output-gap estimates.