#!/usr/bin/env python3 # Schaetzrauschen_Demo.py """ Kapitel Finanzdaten: Der Error-Maximizer-Effekt im Experiment. Aufbau: Wir KENNEN die wahre Kovarianzmatrix, weil wir die Daten selbst erzeugen. Dann schaetzen wir sie aus endlich vielen Beobachtungen und vergleichen, wie gut das daraus optimierte Portfolio in der WAHREN Welt abschneidet - mit und ohne Shrinkage. Das ist der einzige saubere Weg, Schaetzfehler zu messen: Man braucht eine Wahrheit zum Vergleich, und die gibt es nur in der Simulation. """ import numpy as np from sklearn.covariance import LedoitWolf def erzeuge_wahre_kovarianz(n, rng): """Ein-Faktor-Modell: gemeinsamer Marktfaktor plus titelspezifisches Rauschen.""" beta = rng.uniform(0.6, 1.4, size=n) # Marktsensitivitaeten var_markt = 0.04 # Marktvarianz p.a. var_spezifisch = rng.uniform(0.01, 0.09, size=n) # idiosynkratische Varianz return np.outer(beta, beta) * var_markt + np.diag(var_spezifisch) def minimum_varianz_gewichte(sigma): """Analytische GMV-Loesung: w = Sigma^-1 1 / (1' Sigma^-1 1). Ohne Restriktionen.""" eins = np.ones(len(sigma)) try: loesung = np.linalg.solve(sigma, eins) except np.linalg.LinAlgError: loesung = np.linalg.pinv(sigma) @ eins # falls singulaer return loesung / loesung.sum() def portfoliovolatilitaet(w, sigma): return float(np.sqrt(w @ sigma @ w)) if __name__ == "__main__": rng = np.random.default_rng(2026) N = 40 # Anzahl Titel WIEDERHOLUNGEN = 200 sigma_wahr = erzeuge_wahre_kovarianz(N, rng) w_ideal = minimum_varianz_gewichte(sigma_wahr) vola_ideal = portfoliovolatilitaet(w_ideal, sigma_wahr) print("=" * 92) print(" ERROR-MAXIMIZER: WIE TEUER IST SCHAETZRAUSCHEN?") print("=" * 92) print(f"Aufbau: {N} Titel, wahre Kovarianz bekannt (Ein-Faktor-Modell).") print(f"Bestmoegliche Volatilitaet bei perfektem Wissen: " f"{vola_ideal*100:.2f} % p.a.\n") print(f"{'T (Tage)':>9} | {'T/N':>5} | {'Stichprobe':>22} | {'Ledoit-Wolf':>22} | " f"{'Shrink':>7}") print(f"{'':>9} | {'':>5} | {'Vola Aufschlag':>22} | " f"{'Vola Aufschlag':>22} | {'delta':>7}") print("-" * 92) for T in [60, 120, 252, 504, 1260]: vola_stichprobe, vola_lw, deltas = [], [], [] for _ in range(WIEDERHOLUNGEN): # Daten aus der WAHREN Verteilung ziehen (taeglich) daten = rng.multivariate_normal(np.zeros(N), sigma_wahr / 252, size=T) # (a) Stichproben-Kovarianz s_stich = np.cov(daten, rowvar=False, ddof=1) * 252 w_stich = minimum_varianz_gewichte(s_stich) # (b) Ledoit-Wolf-Shrinkage lw = LedoitWolf().fit(daten) s_lw = lw.covariance_ * 252 w_lw = minimum_varianz_gewichte(s_lw) deltas.append(lw.shrinkage_) # Bewertung IMMER mit der wahren Kovarianz - das ist der Punkt vola_stichprobe.append(portfoliovolatilitaet(w_stich, sigma_wahr)) vola_lw.append(portfoliovolatilitaet(w_lw, sigma_wahr)) m_stich, m_lw = np.mean(vola_stichprobe), np.mean(vola_lw) print(f"{T:>9} | {T/N:>5.1f} | {m_stich*100:>8.2f} % " f"{(m_stich/vola_ideal-1)*100:>+9.1f} % | " f"{m_lw*100:>8.2f} % {(m_lw/vola_ideal-1)*100:>+9.1f} % | " f"{np.mean(deltas):>7.3f}") print("-" * 92) print("'Aufschlag' = wie viel mehr Risiko das Portfolio in der WAHREN Welt traegt,") print("verglichen mit dem Portfolio bei perfektem Wissen.") print("Lesart: Je kleiner T/N, desto teurer das Schaetzrauschen - und desto mehr") print("schrumpft Ledoit-Wolf (delta steigt).") print("=" * 92)