Version 04 als eigenes Repository

Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python"
(Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im
uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch
Version_03 (eingefroren) verwaltet und Version_04/ ignoriert.

Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit
der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen
Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist.

Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise,
325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren,
25 Notebooks, PDF mit 715 Seiten.

Zusaetzlich in diesem Commit:

* pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api,
  figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt
  unveraendert daneben bestehen. ortools steht in der Grundausstattung,
  highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der
  schlanken Installation gar nicht erst auftreten.

* Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py
  importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo
  steht in requirements.txt, wird aber von keinem Programm importiert,
  sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in
  [empfehlungen]).

* NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es
  ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden
  Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen.

* PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den
  tatsaechlichen aus erzeuge_titelseite.py korrigiert.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
dschlueter 2026-09-08 01:20:09 +02:00
commit b7af2f1d9a
468 changed files with 262141 additions and 0 deletions

View file

@ -0,0 +1,96 @@
#!/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)