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>
99 lines
3.8 KiB
Python
Generated
99 lines
3.8 KiB
Python
Generated
#!/usr/bin/env python3
|
|
|
|
# Robuste_Optimierung.py
|
|
"""
|
|
Kapitel Unsicherheit: Robuste Portfolio-Optimierung.
|
|
|
|
Vergleicht drei Haltungen zur Unsicherheit:
|
|
(a) nominal - vertraut den Punktschaetzungen blind
|
|
(b) robust - sichert gegen Box-Unsicherheit ab (Worst Case)
|
|
(c) stochastisch - optimiert den Erwartungswert ueber Szenarien
|
|
|
|
und misst den "Preis der Robustheit": Wie viel Ertrag kostet die Absicherung
|
|
im Normalfall - und wie viel Verlust erspart sie im Ernstfall?
|
|
"""
|
|
|
|
import cvxpy as cp
|
|
import numpy as np
|
|
|
|
ASSETS = ["Aktien Welt", "Anleihen", "Rohstoffe", "Immobilien"]
|
|
MU_SCHAETZUNG = np.array([0.085, 0.030, 0.055, 0.060]) # Punktschaetzung
|
|
UNSICHERHEIT = np.array([0.040, 0.008, 0.045, 0.025]) # +/- delta je Titel
|
|
VOLA = np.array([0.17, 0.05, 0.22, 0.12])
|
|
KORR = np.array([
|
|
[1.00, -0.15, 0.35, 0.55],
|
|
[-0.15, 1.00, -0.05, 0.10],
|
|
[0.35, -0.05, 1.00, 0.25],
|
|
[0.55, 0.10, 0.25, 1.00],
|
|
])
|
|
SIGMA = np.diag(VOLA) @ KORR @ np.diag(VOLA)
|
|
LAMBDA = 4.0 # Risikoaversion
|
|
N = len(ASSETS)
|
|
|
|
|
|
def optimiere(mu_effektiv):
|
|
"""Standard-Mean-Variance mit vorgegebenem Renditevektor."""
|
|
w = cp.Variable(N, nonneg=True)
|
|
ziel = cp.Maximize(mu_effektiv @ w - 0.5 * LAMBDA * cp.quad_form(w, SIGMA))
|
|
problem = cp.Problem(ziel, [cp.sum(w) == 1])
|
|
problem.solve()
|
|
return w.value
|
|
|
|
|
|
def kennzahlen(w, mu):
|
|
ertrag = float(mu @ w)
|
|
risiko = float(np.sqrt(w @ SIGMA @ w))
|
|
return ertrag, risiko
|
|
|
|
|
|
if __name__ == "__main__":
|
|
print("=" * 88)
|
|
print(" ROBUSTE vs. NOMINALE PORTFOLIO-OPTIMIERUNG")
|
|
print("=" * 88)
|
|
print(f"{'Asset':<14} {'Erw. Rendite':>14} {'Unsicherheit':>14} "
|
|
f"{'Worst Case':>12} {'Volatilitaet':>13}")
|
|
print("-" * 88)
|
|
for i, name in enumerate(ASSETS):
|
|
print(f"{name:<14} {MU_SCHAETZUNG[i]*100:>13.1f} % "
|
|
f"{'+/- ' + format(UNSICHERHEIT[i]*100, '.1f') + ' %':>14} "
|
|
f"{(MU_SCHAETZUNG[i]-UNSICHERHEIT[i])*100:>11.1f} % "
|
|
f"{VOLA[i]*100:>12.1f} %")
|
|
|
|
# (a) nominal: vertraut den Schaetzungen
|
|
w_nominal = optimiere(MU_SCHAETZUNG)
|
|
# (b) robust: rechnet mit dem Worst Case der Box-Unsicherheitsmenge
|
|
w_robust = optimiere(MU_SCHAETZUNG - UNSICHERHEIT)
|
|
|
|
print("\n" + "-" * 88)
|
|
print(f"{'':<14} {'nominal':>22} {'robust':>22}")
|
|
print("-" * 88)
|
|
for i, name in enumerate(ASSETS):
|
|
print(f"{name:<14} {w_nominal[i]*100:>21.1f} % {w_robust[i]*100:>21.1f} %")
|
|
|
|
# --- Bewertung in beiden Welten --------------------------------------
|
|
mu_worst = MU_SCHAETZUNG - UNSICHERHEIT
|
|
e_nom_gut, r_nom = kennzahlen(w_nominal, MU_SCHAETZUNG)
|
|
e_rob_gut, r_rob = kennzahlen(w_robust, MU_SCHAETZUNG)
|
|
e_nom_schlecht, _ = kennzahlen(w_nominal, mu_worst)
|
|
e_rob_schlecht, _ = kennzahlen(w_robust, mu_worst)
|
|
|
|
print("\n" + "-" * 88)
|
|
print(f"{'Bewertung':<34} {'nominales Portfolio':>22} {'robustes Portfolio':>22}")
|
|
print("-" * 88)
|
|
print(f"{'Ertrag, wenn Schaetzung stimmt':<34} {e_nom_gut*100:>21.2f} % "
|
|
f"{e_rob_gut*100:>21.2f} %")
|
|
print(f"{'Ertrag im Worst Case':<34} {e_nom_schlecht*100:>21.2f} % "
|
|
f"{e_rob_schlecht*100:>21.2f} %")
|
|
print(f"{'Volatilitaet':<34} {r_nom*100:>21.2f} % {r_rob*100:>21.2f} %")
|
|
|
|
print("\n" + "-" * 88)
|
|
print(f"Preis der Robustheit (Ertragsverzicht im Normalfall): "
|
|
f"{(e_nom_gut - e_rob_gut)*100:+.2f} Prozentpunkte")
|
|
print(f"Nutzen der Robustheit (Vorteil im Worst Case): "
|
|
f"{(e_rob_schlecht - e_nom_schlecht)*100:+.2f} Prozentpunkte")
|
|
verhaeltnis = ((e_rob_schlecht - e_nom_schlecht)
|
|
/ max(e_nom_gut - e_rob_gut, 1e-9))
|
|
print(f"Verhaeltnis Nutzen/Preis: {verhaeltnis:.2f}")
|
|
print(" -> Werte > 1 bedeuten: Die Absicherung bringt im Ernstfall mehr,")
|
|
print(" als sie im Normalfall kostet.")
|
|
print("=" * 88)
|