96 lines
3.7 KiB
Python
96 lines
3.7 KiB
Python
|
|
#!/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)
|