Common European Financial Factors in Output-Gap Estimation

Evidence from 26 European Economies

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:

$$ \Delta \log X_t = 100 \left(\log X_t - \log X_{t-1}\right). $$

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

$$ z_t^{std} = \frac{z_t - \bar z}{s_z}. $$

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.

Python codeCell 1
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
Output
Transformed factors tail:
Output
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:

$$ y_{i,t} = 100 \log(Y_{i,t}), $$

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:

$$ y_{i,t} = y^*_{i,t} + g_{i,t}. $$

Potential output follows a smooth local-trend specification:

$$ y^*_{i,t} = 2y^*_{i,t-1} - y^*_{i,t-2} + \eta_{i,t}, \qquad \eta_{i,t} \sim N(0,\sigma^2_{\eta,i}). $$

The baseline output-gap equation is

$$ g_{i,t} = \beta_i g_{i,t-1} + u_{i,t}, \qquad u_{i,t} \sim N(0,\sigma^2_{u,i}). $$

The factor-augmented model adds one common European or global factor at a time:

$$ g_{i,t} = \beta_i g_{i,t-1} + \gamma_{i,z} z_{t-k} + u_{i,t}, \qquad u_{i,t} \sim N(0,\sigma^2_{u,i}), $$

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

$$ \lambda_{2,i,z} = \frac{\sigma^2_{u,i}}{\sigma^2_{\eta,i}}. $$

Equivalently, for a given value of $\lambda_{2,i,z}$, the state-space model corresponds to minimising a penalised objective of the form

$$ \sum_t \left( g_{i,t} - \beta_i g_{i,t-1} - \gamma_{i,z} z_{t-k} \right)^2 + \lambda_{2,i,z} \sum_t \left( \Delta^2 y^*_{i,t} \right)^2. $$

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.

Python codeCell 2
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
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:

$$ \pi_{i,t} = \alpha_i + \rho_i \pi_{i,t-1} + \theta_i g_{i,t-1} + \varepsilon_{i,t}. $$

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.

Python codeCell 3
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
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
Python codeCell 4
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
Figure
Notebook 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.