kleine Änderungen
This commit is contained in:
parent
0f9c815b86
commit
dc088fa214
47 changed files with 183868 additions and 573 deletions
169
bilder_04/erzeuge_var_vs_cvar.py
Normal file
169
bilder_04/erzeuge_var_vs_cvar.py
Normal file
|
|
@ -0,0 +1,169 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
# erzeuge_var_vs_cvar.py
|
||||
"""
|
||||
Erzeugt das Subadditivitaets-Bild zum Kapitel CVaR:
|
||||
|
||||
bilder_04/kap_cvar_subadditivitaet.svg
|
||||
|
||||
ERGAENZT kap_cvar_var_vergleich.svg, ersetzt es nicht: Jenes zeigt, WAS die
|
||||
beiden Kennzahlen sind - eine Schwelle und ein Mittelwert. Dieses zeigt, warum
|
||||
der Unterschied zaehlt.
|
||||
|
||||
Gerechnet wird das Gegenbeispiel aus Teil 2 von VaR_CVaR_Demo.py: zwei
|
||||
unabhaengige Anleihen, je 100 EUR Nominal, Ausfallwahrscheinlichkeit 4 %,
|
||||
sonst 2 EUR Kupon. Einzeln meldet der VaR fuer jede Anleihe einen GEWINN von
|
||||
2 EUR - denn 4 % liegen unter der 5-%-Schwelle, der Ausfall faellt also aus der
|
||||
Betrachtung heraus. Im Portfolio steigt die Wahrscheinlichkeit mindestens eines
|
||||
Ausfalls auf 7,8 % und damit UEBER die Schwelle; der VaR springt auf 98 EUR.
|
||||
|
||||
Das ist die Aussage des Bildes: Der VaR behauptet, Streuung habe das Risiko um
|
||||
102 EUR ERHOEHT. Der CVaR, der den ganzen Rand mittelt statt ihn abzuschneiden,
|
||||
kommt auf 101,33 EUR gegenueber 159,24 EUR - Streuung senkt das Risiko, wie es
|
||||
sein muss.
|
||||
|
||||
Die Ziehungsreihenfolge des Buchprogramms wird nachgespielt: default_rng(2026),
|
||||
und Teil 1 des Programms zieht nichts, also sind die beiden Anleihen die ersten
|
||||
Ziehungen. Deshalb stehen im Bild genau die Zahlen der abgedruckten Tabelle;
|
||||
pruefe_gegen_buch() bricht ab, wenn nicht.
|
||||
|
||||
Aufruf (aus dem Repository-Wurzelverzeichnis):
|
||||
python3 bilder_04/erzeuge_var_vs_cvar.py
|
||||
|
||||
Benoetigt: numpy, matplotlib
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
import numpy as np
|
||||
|
||||
HIER = os.path.dirname(os.path.abspath(__file__))
|
||||
BASIS = os.path.dirname(HIER)
|
||||
sys.path.insert(0, HIER)
|
||||
sys.path.insert(0, os.path.join(
|
||||
BASIS, "Operations_Research_mit_Python_Version_04_Programme"))
|
||||
|
||||
from stil_04 import FARBEN, speichere # noqa: E402
|
||||
import matplotlib.pyplot as plt # noqa: E402
|
||||
from VaR_CVaR_Demo import cvar_rockafellar, var_quantil # noqa: E402
|
||||
|
||||
# Instanz wortgleich zu Teil 2 von VaR_CVaR_Demo.py.
|
||||
SAAT = 2026
|
||||
ZIEHUNGEN = 2_000_000
|
||||
AUSFALLWAHRSCHEINLICHKEIT = 0.04
|
||||
VERLUST_BEI_AUSFALL = 100.0
|
||||
KUPON = 2.0
|
||||
|
||||
# Die im Kapitel abgedruckte Tabelle; nur zum Gegenpruefen.
|
||||
TABELLE_IM_BUCH = {
|
||||
"Anleihe A allein": (-2.00, 79.59),
|
||||
"Anleihe B allein": (-2.00, 79.66),
|
||||
"Portfolio A+B": (98.00, 101.33),
|
||||
"Summe der Einzelwerte": (-4.00, 159.24),
|
||||
}
|
||||
|
||||
|
||||
def rechne() -> dict[str, tuple[float, float]]:
|
||||
"""Dieselben Ziehungen wie im Buchprogramm - siehe Modulkommentar."""
|
||||
rng = np.random.default_rng(SAAT)
|
||||
verlust_a = np.where(rng.random(ZIEHUNGEN) < AUSFALLWAHRSCHEINLICHKEIT,
|
||||
VERLUST_BEI_AUSFALL, -KUPON)
|
||||
verlust_b = np.where(rng.random(ZIEHUNGEN) < AUSFALLWAHRSCHEINLICHKEIT,
|
||||
VERLUST_BEI_AUSFALL, -KUPON)
|
||||
|
||||
werte = {}
|
||||
for name, v in (("Anleihe A allein", verlust_a),
|
||||
("Anleihe B allein", verlust_b),
|
||||
("Portfolio A+B", verlust_a + verlust_b)):
|
||||
werte[name] = (var_quantil(v), cvar_rockafellar(v))
|
||||
werte["Summe der Einzelwerte"] = tuple(
|
||||
werte["Anleihe A allein"][i] + werte["Anleihe B allein"][i]
|
||||
for i in (0, 1))
|
||||
return werte
|
||||
|
||||
|
||||
def pruefe_gegen_buch(werte) -> None:
|
||||
"""Die Tafel muss dieselben Zahlen zeigen wie die Tabelle im Kapitel."""
|
||||
for name, (soll_var, soll_cvar) in TABELLE_IM_BUCH.items():
|
||||
ist_var, ist_cvar = werte[name]
|
||||
if abs(ist_var - soll_var) > 0.005 or abs(ist_cvar - soll_cvar) > 0.005:
|
||||
raise SystemExit(
|
||||
f"Subadditivitaets-Bild: '{name}' ergibt VaR {ist_var:.2f} / "
|
||||
f"CVaR {ist_cvar:.2f}, das Kapitel druckt {soll_var:.2f} / "
|
||||
f"{soll_cvar:.2f} ab.")
|
||||
# Die Aussage selbst - nicht nur die Zahlen.
|
||||
if not werte["Portfolio A+B"][0] > werte["Summe der Einzelwerte"][0]:
|
||||
raise SystemExit("Subadditivitaets-Bild: Der VaR verletzt die "
|
||||
"Subadditivitaet hier nicht - dann zeigt die Tafel "
|
||||
"das Gegenteil ihrer Überschrift.")
|
||||
if not werte["Portfolio A+B"][1] <= werte["Summe der Einzelwerte"][1]:
|
||||
raise SystemExit("Subadditivitaets-Bild: Der CVaR ist hier nicht "
|
||||
"subadditiv. Das widerspricht dem Kapiteltext.")
|
||||
|
||||
|
||||
def euro(wert: float) -> str:
|
||||
return f"{wert:,.2f} €".replace(",", "@").replace(".", ",").replace("@", ".")
|
||||
|
||||
|
||||
def zeichne(werte) -> None:
|
||||
figur, achsen = plt.subplots(1, 2, figsize=(10.2, 4.4))
|
||||
namen = ["Anleihe A allein", "Anleihe B allein",
|
||||
"Summe der Einzelwerte", "Portfolio A+B"]
|
||||
y = np.arange(len(namen))[::-1]
|
||||
|
||||
for achse, index, titel, urteil, farbe in (
|
||||
(achsen[0], 0, "VaR 95 %",
|
||||
"Portfolio teurer als die Summe —\nSubadditivität verletzt",
|
||||
FARBEN["fehler"]),
|
||||
(achsen[1], 1, "CVaR 95 %",
|
||||
"Portfolio billiger als die Summe —\nsubadditiv, also kohärent",
|
||||
FARBEN["gut"])):
|
||||
wert = [werte[n][index] for n in namen]
|
||||
farben = [FARBEN["gedaempft"], FARBEN["gedaempft"],
|
||||
FARBEN["zweit"], farbe]
|
||||
achse.barh(y, wert, height=0.6, color=farben, zorder=3)
|
||||
# Balken muessen bei null beginnen, sonst luegt ihre Laenge. Der Rand
|
||||
# links richtet sich nach der Spannweite, damit die Zahl an einem
|
||||
# negativen Balken nicht an der Achsenbeschriftung klebt.
|
||||
spanne = max(wert) - min(0.0, min(wert))
|
||||
achse.set_xlim(min(0.0, min(wert)) - 0.20 * spanne, max(wert) * 1.22)
|
||||
for stelle, w in enumerate(wert):
|
||||
abstand = 0.015 * spanne
|
||||
achse.text(w + (abstand if w >= 0 else -abstand), y[stelle],
|
||||
euro(w), va="center", ha="left" if w >= 0 else "right",
|
||||
fontsize=9, color=FARBEN["text"], zorder=4)
|
||||
achse.axvline(0, color=FARBEN["text"], linewidth=1.0, zorder=2)
|
||||
achse.set_yticks(y)
|
||||
achse.set_yticklabels(namen if index == 0 else [], fontsize=9,
|
||||
color=FARBEN["text"])
|
||||
achse.set_title(titel, fontsize=11, color=FARBEN["text"], pad=34)
|
||||
achse.set_xlabel("Verlust in € (negativ = Gewinn)")
|
||||
achse.grid(axis="y", visible=False)
|
||||
# Das Urteil ueber der Achse, nicht in ihr - sonst stoesst es an die
|
||||
# Zahl am obersten Balken.
|
||||
achse.text(0.5, 1.008, urteil, transform=achse.transAxes,
|
||||
fontsize=8.5, color=farbe, ha="center", va="bottom",
|
||||
fontweight="bold")
|
||||
|
||||
figur.suptitle("Zwei unabhängige Anleihen, je 4 % Ausfallrisiko — "
|
||||
"und zwei Urteile über dieselbe Diversifikation",
|
||||
fontsize=11, color=FARBEN["text"], y=1.02)
|
||||
speichere(figur, "kap_cvar_subadditivitaet")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
werte = rechne()
|
||||
print(f" {'':<24} {'VaR 95%':>10} {'CVaR 95%':>10}")
|
||||
for name in ("Anleihe A allein", "Anleihe B allein", "Portfolio A+B",
|
||||
"Summe der Einzelwerte"):
|
||||
print(f" {name:<24} {werte[name][0]:>10.2f} {werte[name][1]:>10.2f}")
|
||||
pruefe_gegen_buch(werte)
|
||||
print(" stimmt mit der abgedruckten Tabelle überein")
|
||||
print(f" VaR: Portfolio {werte['Portfolio A+B'][0]:.2f} > Summe "
|
||||
f"{werte['Summe der Einzelwerte'][0]:.2f} — verletzt")
|
||||
print(f" CVaR: Portfolio {werte['Portfolio A+B'][1]:.2f} ≤ Summe "
|
||||
f"{werte['Summe der Einzelwerte'][1]:.2f} — kohärent")
|
||||
zeichne(werte)
|
||||
Loading…
Reference in a new issue