99 lines
3.8 KiB
Python
99 lines
3.8 KiB
Python
|
|
#!/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)
|