#!/usr/bin/env python3 # erzeuge_var_cvar.py """ Erzeugt das VaR/CVaR-Diagramm zum Kapitel CVaR: bilder_04/kap_cvar_var_vergleich.svg statisch, fuer das PDF bilder_04/kap_cvar_var_vergleich.png Rasterfassung Gezeigt wird die Verteilung simulierter Tagesrenditen mit beiden Kennzahlen: Der VaR ist die SCHWELLE, unter die nur 5 % der Tage fallen; der CVaR ist der MITTELWERT genau dieser 5 %. Der Abstand zwischen beiden Linien ist die Information, die der VaR wegwirft. Zur Instanz: Die Renditen stammen aus einer t-Verteilung mit drei Freiheitsgraden, auf Standardabweichung 1 normiert und dann auf eine realistische Tagesvolatilitaet skaliert - dasselbe Fat-Tail-Modell, mit dem Teil 1 von VaR_CVaR_Demo.py rechnet. Die beiden Kennzahlen werden mit den Funktionen aus demselben Programm bestimmt, insbesondere der CVaR ueber Rockafellar-Uryasev statt ueber den naheliegenden, aber falschen Mittelwert aller Werte jenseits des VaR. Die Zahlen, die das Skript ausgibt, stehen so auch im Kapiteltext. Wer die Instanz aendert, muss beides nachziehen - deshalb druckt das Skript sie. Aufruf (aus dem Repository-Wurzelverzeichnis): python3 bilder_04/erzeuge_var_cvar.py Benoetigt: numpy, scipy, matplotlib """ from __future__ import annotations import os import matplotlib matplotlib.use("Agg") import matplotlib.pyplot as plt import numpy as np from scipy import stats # Reproduzierbare SVG-Ausgabe (siehe erzeuge_titelseite.py) plt.rcParams["svg.hashsalt"] = "or-mit-python-v04" BASIS = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) BILDER = os.path.join(BASIS, "bilder_04") INDIGO, AMBER, ROT = "#4338ca", "#b45309", "#be123c" TAGE = 20_000 # simulierte Handelstage FREIHEITSGRADE = 3 # Fat-Tail-Modell des Kapitels TAGESVOLA = 0.014 # 1,4 % Tagesschwankung - realistisch fuer ein Aktienportfolio TAGESDRIFT = 0.0003 # leicht positiver Erwartungswert ALPHA = 0.95 # --- Kennzahlen: wortgleich zu VaR_CVaR_Demo.py ----------------------------- def var_quantil(verluste: np.ndarray, alpha: float = ALPHA) -> float: """VaR = Quantil der Verlustverteilung (Verluste positiv, Gewinne negativ).""" return float(np.quantile(verluste, alpha)) def cvar_rockafellar(verluste: np.ndarray, alpha: float = ALPHA) -> float: """CVaR ueber min_gamma { gamma + 1/(1-alpha) * E[max(Verlust - gamma, 0)] }. Der naheliegende Weg "Mittelwert aller Werte >= VaR" ist falsch, sobald die Verteilung Atome hat. Hier hat sie keine, aber die Formel bleibt dieselbe wie im Buchprogramm - und nur so ist der Wert im Bild derselbe wie dort. """ kandidaten = np.unique(verluste) return float(min(g + np.mean(np.maximum(verluste - g, 0.0)) / (1.0 - alpha) for g in kandidaten)) def renditen(saat: int = 2026) -> np.ndarray: """Tagesrenditen aus dem Fat-Tail-Modell des Kapitels.""" rng = np.random.default_rng(saat) verteilung = stats.t(df=FREIHEITSGRADE) roh = verteilung.rvs(size=TAGE, random_state=rng) / verteilung.std() return TAGESDRIFT + TAGESVOLA * roh AUSSCHNITT = 8.0 # Prozent - der sichtbare Bereich der x-Achse def zeichne(werte: np.ndarray, var: float, cvar: float) -> None: """Zeichnet die Verteilung mit beiden Kennzahlen. Die x-Achse ist bewusst auf +/- 8 % beschnitten. Eine t-Verteilung mit drei Freiheitsgraden hat keine endliche Woelbung: Einzelne Tage liegen bei +70 %, und ungeschnitten quetscht sich die gesamte Verteilung zu einem Strich zusammen. Wie viele Tage ausserhalb liegen, steht im Bild - sie sind nicht verschwiegen, sondern gerade der Punkt. """ figur, achse = plt.subplots(figsize=(8.0, 4.6)) prozent = werte * 100 sichtbar = prozent[np.abs(prozent) <= AUSSCHNITT] achse.hist(sichtbar, bins=90, color=INDIGO, alpha=0.75, edgecolor="white", linewidth=0.3) achse.set_xlim(-AUSSCHNITT, AUSSCHNITT) achse.axvspan(-AUSSCHNITT, -var * 100, color=ROT, alpha=0.08, zorder=0) achse.axvline(-var * 100, color=AMBER, linewidth=2.0, linestyle="--") achse.axvline(-cvar * 100, color=ROT, linewidth=2.0) def komma(wert: float, stellen: int = 2) -> str: return f"{wert:.{stellen}f}".replace(".", ",") # Platz oberhalb der Verteilung schaffen, damit keine Beschriftung in den # Balken liegt - die Aussage steht sonst buchstaeblich im Weg. hoehe = achse.get_ylim()[1] achse.set_ylim(0, hoehe * 1.30) achse.annotate(f"VaR 95 % = {komma(var * 100)} %\ndie Schwelle", (-var * 100, hoehe * 1.24), textcoords="offset points", xytext=(8, 0), fontsize=9, color=AMBER, va="top") achse.annotate(f"CVaR 95 % = {komma(cvar * 100)} %\nder Mittelwert dahinter", (-cvar * 100, hoehe * 1.24), textcoords="offset points", xytext=(-8, 0), fontsize=9, color=ROT, va="top", ha="right") anteil = float(np.mean(werte <= -var)) achse.annotate(f"die schlechtesten\n{anteil * 100:.0f} % der Tage", ((-AUSSCHNITT - var * 100) / 2, hoehe * 0.55), fontsize=8.5, color="#475569", ha="center") draussen = int(np.sum(np.abs(prozent) > AUSSCHNITT)) schlimmster = float(prozent.min()) achse.annotate(f"{draussen} Tage liegen außerhalb dieses Ausschnitts,\n" f"der schlechteste bei {komma(schlimmster, 1)} %.\n" f"Genau sie zählt der VaR nicht mit.", xy=(0.985, 0.80), xycoords="axes fraction", fontsize=8.5, color="#475569", ha="right", va="top") achse.set_xlabel("Tagesrendite des Portfolios (%)") achse.set_ylabel("Häufigkeit (von 20 000 Tagen)") achse.set_title("Der VaR ist eine Schwelle, der CVaR ein Mittelwert", fontsize=11) achse.grid(axis="y", linestyle=":", alpha=0.45) for rand in ("top", "right"): achse.spines[rand].set_visible(False) figur.tight_layout() os.makedirs(BILDER, exist_ok=True) for endung in ("svg", "png"): pfad = os.path.join(BILDER, f"kap_cvar_var_vergleich.{endung}") figur.savefig(pfad, format=endung, dpi=160, metadata={"Date": None} if endung == "svg" else None) print(f"geschrieben: {pfad}") plt.close(figur) if __name__ == "__main__": werte = renditen() verluste = -werte var = var_quantil(verluste) cvar = cvar_rockafellar(verluste) schlimmster = float(verluste.max()) print(f"{TAGE} simulierte Tage, t(df={FREIHEITSGRADE}), " f"Tagesvola {TAGESVOLA * 100:.1f} %") print(f"VaR 95 % = {var * 100:.2f} % (Schwelle)") print(f"CVaR 95 % = {cvar * 100:.2f} % (Mittel der schlechtesten 5 %)") print(f"schlimmster Einzeltag: {schlimmster * 100:.2f} %") print(f"Verhaeltnis CVaR/VaR: {cvar / var:.2f}") zeichne(werte, var, cvar)