operations_research/OR_HTML_04/finanzdaten.html

976 lines
133 KiB
HTML
Raw Normal View History

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>
2026-09-08 01:20:09 +02:00
<!doctype html>
<html lang="de">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Kapitel 18: Finanzdaten-Modellierung — Renditen, Kovarianz und Shrinkage · Optimierte Entscheidungsfindung mit Python</title>
<script>
(function () {
try {
var t = localStorage.getItem("or-theme");
if (t) document.documentElement.setAttribute("data-theme", t);
} catch (e) {}
})();
</script>
<link rel="stylesheet" href="assets/highlight.css" />
<link rel="stylesheet" href="katex/katex.min.css" />
<script defer="" src="katex/katex.min.js"></script>
<script>document.addEventListener("DOMContentLoaded", function () {
var mathElements = document.getElementsByClassName("math");
var macros = [];
for (var i = 0; i < mathElements.length; i++) {
var texText = mathElements[i].firstChild;
if (mathElements[i].tagName == "SPAN") {
katex.render(texText.data, mathElements[i], {
displayMode: mathElements[i].classList.contains('display'),
throwOnError: false,
macros: macros,
fleqn: false
});
}}
// Der Browser springt zu einem #anker in der URL schon beim ersten Rendern
// an, BEVOR die KaTeX-Formeln oben im Text ihre finale Hoehe bekommen -
// durch den Reflow landet der Anker danach zu weit unten. Nach dem
// Formel-Rendering hier erneut zum Anker springen, das behebt es.
if (location.hash) {
var ziel = document.getElementById(decodeURIComponent(location.hash.slice(1)));
if (ziel) ziel.scrollIntoView({behavior: "instant", block: "start"});
}
});
</script>
<link rel="stylesheet" href="assets/site.css" />
</head>
<body>
<svg style="display:none" aria-hidden="true"><symbol id="icon-menu" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round">
<line x1="3" y1="6" x2="21" y2="6"/><line x1="3" y1="12" x2="21" y2="12"/><line x1="3" y1="18" x2="21" y2="18"/>
</symbol>
<symbol id="icon-search" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round">
<circle cx="11" cy="11" r="7"/><line x1="21" y1="21" x2="16.2" y2="16.2"/>
</symbol>
<symbol id="icon-sun" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round">
<circle cx="12" cy="12" r="4.5"/>
<line x1="12" y1="1.5" x2="12" y2="4"/><line x1="12" y1="20" x2="12" y2="22.5"/>
<line x1="1.5" y1="12" x2="4" y2="12"/><line x1="20" y1="12" x2="22.5" y2="12"/>
<line x1="4.5" y1="4.5" x2="6.2" y2="6.2"/><line x1="17.8" y1="17.8" x2="19.5" y2="19.5"/>
<line x1="19.5" y1="4.5" x2="17.8" y2="6.2"/><line x1="6.2" y1="17.8" x2="4.5" y2="19.5"/>
</symbol>
<symbol id="icon-moon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M20 14.5A8.5 8.5 0 1 1 9.5 4a6.8 6.8 0 0 0 10.5 10.5z"/>
</symbol>
<symbol id="icon-chevron-left" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="15 4 7 12 15 20"/>
</symbol>
<symbol id="icon-chevron-right" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="9 4 17 12 9 20"/>
</symbol>
<symbol id="icon-check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="4 13 9.5 18.5 20 6"/>
</symbol>
<symbol id="icon-external-link" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M18 13.5V19a1.5 1.5 0 0 1-1.5 1.5H5A1.5 1.5 0 0 1 3.5 19V7A1.5 1.5 0 0 1 5 5.5h5.5"/>
<polyline points="14.5 3.5 20.5 3.5 20.5 9.5"/><line x1="11" y1="13" x2="20" y2="4"/>
</symbol>
<symbol id="icon-book" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M4 5.5A2 2 0 0 1 6 4h6v16H6a2 2 0 0 0-2 1.5z"/>
<path d="M20 5.5A2 2 0 0 0 18 4h-6v16h6a2 2 0 0 1 2 1.5z"/>
</symbol>
<symbol id="icon-copy" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/>
</symbol>
<symbol id="icon-download" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M12 3v12"/><polyline points="7 10 12 15 17 10"/><path d="M4 19.5h16"/>
</symbol></svg>
<header class="site-header">
<button type="button" class="icon-btn" id="sidebar-toggle" aria-label="Menü öffnen"><svg class="icon" aria-hidden="true"><use href="#icon-menu"></use></svg></button>
<a class="brand" href="index.html"><svg class="icon" aria-hidden="true"><use href="#icon-book"></use></svg> <span>Optimierte Entscheidungsfindung mit Python</span></a>
<div class="site-search">
<input id="suche-eingabe" type="search" placeholder="Suchen …" aria-label="Suche" autocomplete="off" />
<svg class="icon such-icon" aria-hidden="true"><use href="#icon-search"></use></svg>
<div id="suche-ergebnisse" class="suche-ergebnisse" hidden></div>
</div>
<button type="button" class="icon-btn" id="theme-toggle" aria-label="Darstellung umschalten">
<svg class="icon icon-sun" aria-hidden="true"><use href="#icon-sun"></use></svg><svg class="icon icon-moon" aria-hidden="true"><use href="#icon-moon"></use></svg>
</button>
</header>
<div class="site-body">
<div class="sidebar-overlay" id="sidebar-overlay" hidden></div>
Phase 8.1: Synthese-Seiten je Teil - und die gebrochene Lesekette Setzt Paket 5 aus Verbesserungen_02.md um (den Teil, der nicht zurueckgestellt wurde). Fuenf neue Dateien 19_/29_/39_/49_/52_Synthese_*.md, je eine am Ende eines Teils, mit eigener Website-Seite ueber SONDERSEITEN - sie tragen bewusst keine "# Kapitel:"-Ueberschrift, weil sie keine Kapitel sind, sondern der Rueckblick auf einen Teil. Der Entwurf musste sich abgrenzen: Die Teil-Einleitungen haben bereits Entscheidungsdiagramme. Eine zweite Matrix am Teil-Ende waere eine Dopplung gewesen. Die Synthesen leisten deshalb, was eine Einleitung nicht kann - den Vergleich ueber die Kapitel hinweg (Verfahren nebeneinander, mit der Spalte "wo es aufhoert"), eine Tabelle "was dieser Teil gemessen hat" (Behauptung gegen Messung gegen Fundstelle) und drei Fehler, die der Teil verhindert. Zitiert wird ausschliesslich, was im Buch tatsaechlich gerechnet wird. Drei Funde beim Einbau: * Teil III sagte "die drei Kapitel dieses Teils", hat aber fuenf. Phase 3 hatte Mehrziel und Predict-then-Optimize hinzugefuegt, die Einleitung blieb stehen. * 50_Praxis.md verwies auf die Projektwerkstatt mit "acht eigene Anwendungen" - sie hat elf. * Und der eigentliche Fund: Die Lesekette der Quelldateien fuehrte an ACHT Kapiteln vorbei. 12_Python_Oekosystem zeigte direkt auf 20_Lineare_Programmierung, 23_Graphen direkt auf 30_QP, 32_Dynamische direkt auf 40_Finanzdaten, 50_Praxis direkt auf die Projektwerkstatt. Wer der Kette folgte, uebersprang acht von 23 Kapiteln - darunter Metaheuristiken, Spaltengenerierung, Strukturbruecke, Supply-Chain und das ganze Testing-Kapitel. Zehn weitere Dateien hatten gar keine Navigationszeile. Zur Reichweite, damit sie nicht ueberschaetzt wird: Diese Zeilen stehen nur in den Quelldateien. entferne_navigation() streicht sie aus dem Gesamtdokument, und die Website baut ihre Vor/Zurueck-Knoepfe selbst aus DATEIEN. PDF und Website waren nie betroffen - wohl aber jeder, der die Markdown-Dateien im Repository liest, und das wird nach der Veroeffentlichung der Normalfall sein. Die Kette ist jetzt ueber alle 35 Uebergaenge geschlossen, und --check bewacht sie: Fehlt eine Zeile oder zeigt sie an der in DATEIEN folgenden Datei vorbei, ist der Lauf rot. Gegengetestet mit beiden Bruchformen. Stand: 36 Dateien, 296 Abschnitte, 815 Querverweise, 328 Indexmarken, 76 Programme (unveraendert), 33 pytest-Tests, PDF 758 Seiten. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 12:10:02 +02:00
<nav class="sidebar" id="sidebar" aria-label="Kapitelnavigation"><div class="sidebar-inhalt"><details class="sidebar-gruppe"><summary>Einstieg</summary><ul><li data-kapitel="vorwort.html"><a href="vorwort.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Vorwort & Lesehilfe</span></a></li><li data-kapitel="notation.html"><a href="notation.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Notation & Abkürzungen</span></a></li></ul></details><details class="sidebar-gruppe"><summary>Teil I: Grundlagen des Operations Research</summary><ul><li data-kapitel="einfuehrung.html"><a href="einfuehrung.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 1: Einführung in Operations Research — Vom Ursprung zur mathematischen Entscheidungsfindung</span></a></li><li data-kapitel="fundament.html"><a href="fundament.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 2: Das mathematische Fundament — Vektoren, Matrizen, Konvexität</span></a></li><li data-kapitel="oekosystem.html"><a href="oekosystem.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 3: Das Python-Ökosystem für OR — Solver, Bindings und Modellierungsschichten</span></a></li><li data-kapitel="modellierung.html"><a href="modellierung.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 4: Vom Management-Wunsch zum Modell</span></a></li><li data-kapitel="synthese-grundlagen.html"><a href="synthese-grundlagen.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Synthese Teil I</span></a></li></ul></details><details class="sidebar-gruppe"><summary>Teil II: Die Kernverfahren der deterministischen Optimierung</summary><ul><li data-kapitel="lp.html"><a href="lp.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 5: Lineare Programmierung — Simplex, Dualität und Schattenpreise</span></a></li><li data-kapitel="milp.html"><a href="milp.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 6: Gemischt-ganzzahlige Optimierung — Diskrete Entscheidungen und Branch-and-Bound</span></a></li><li data-kapitel="cpsat.html"><a href="cpsat.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 7: Constraint Programming mit CP-SAT — Logik, Scheduling und Zuweisung</span></a></li><li data-kapitel="graphen.html"><a href="graphen.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 8: Graphen, Flüsse und Touren — Min-Cost-Flow, Matching und VRP</span></a></li><li data-kapitel="metaheuristiken.html"><a href="metaheuristiken.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 9: Metaheuristiken — wenn der exakte Solver aussteigt</span></a></li><li data-kapitel="dekomposition.html"><a href="dekomposition.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 10: Spaltengenerierung — das Modell umbauen statt die Lösung raten</span></a></li><li data-kapitel="synthese-kernverfahren.html"><a href="synthese-kernverfahren.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Synthese Teil II</span></a></li></ul></details><details class="sidebar-gruppe"><summary>Teil III: Nichtlinearität, Unsicherheit und mehrperiodige D
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>
2026-09-08 01:20:09 +02:00
<main class="content">
<nav class="breadcrumb" aria-label="Breadcrumb"><a href="index.html">Start</a> <span>Teil IV</span> <span>Kapitel 18: Finanzdaten-Modellierung — Renditen, Kovarianz und Shrinkage</span></nav>
<nav class="prev-next"><a class="prev-next-knopf prev-next-prev" href="supplychain.html"><svg class="icon" aria-hidden="true"><use href="#icon-chevron-left"></use></svg><span><small>Zurück</small>Kapitel 17: Supply-Chain und Energieeinsatz unter Unsicherheit</span></a><a class="prev-next-knopf prev-next-next" href="markowitz.html"><span><small>Weiter</small>Kapitel 19: Die moderne Portfoliotheorie nach Markowitz</span><svg class="icon" aria-hidden="true"><use href="#icon-chevron-right"></use></svg></a></nav>
<article>
<h1 id="kap-finanzdaten">Kapitel 18: Finanzdaten-Modellierung — Renditen, Kovarianz und Shrinkage</h1>
<div class="card card-blick">
<blockquote>
<p><strong>📌 Kapitel auf einen Blick</strong></p>
<p><strong>Worum geht es?</strong> Um die Eingangsdaten der Portfoliooptimierung — und darum, warum sie trügerisch sind. Wer historische Kovarianzen ungefiltert in einen Optimierer gibt, optimiert Rauschen.</p>
<p><strong>Voraussetzungen:</strong> <a href="qp-nlp.html#kap-qp-nlp">Kapitel 11</a> (Eigenwerte, positive Definitheit), Grundlagen der Statistik.</p>
<p><strong>Danach können Sie:</strong> Kursdaten korrekt laden, diskrete und logarithmische Renditen unterscheiden, das Schätzfehlerproblem erklären, Ledoit-Wolf-Shrinkage anwenden — und erkennen, wann eine Kovarianzmatrix aus zu wenigen Beobachtungen stammt.</p>
<p><strong>Zeitbedarf:</strong> ca. 5 Stunden.</p>
<p><strong>Programme:</strong><br />
<code>Renditen_Vergleich.py</code><br />
<code>Schaetzrauschen_Demo.py</code><br />
<code>Finanzdaten_Ledoit_Wolf.py</code><br />
<code>Kovarianz_Falle.py</code></p>
<p><strong>⚠️ Überspringen Sie dieses Kapitel nicht.</strong> Wer direkt bei Markowitz (<a href="markowitz.html#kap-markowitz">Kapitel 19</a>) einsteigt, erhält absurde Portfoliogewichte und weiß nicht, warum.</p>
<p><strong>Notebook:</strong> <a href="Notebooks_04/finanzdaten.ipynb">finanzdaten.ipynb</a><br />
<a href="https://colab.research.google.com/github/dschlueter/or-mit-python/blob/main/Notebooks_04/finanzdaten.ipynb">In Google Colab öffnen</a></p>
</blockquote>
</div>
<hr />
<h2 id="sec:finanzdaten-schnellstart">18.1 In 5 Minuten gelöst</h2>
<div class="card card-schnellstart">
<blockquote>
<p><strong>🚀 In 5 Minuten gelöst: Die durchschnittliche Rendite, die es nicht gibt</strong></p>
<p>Eine Anlage wurde über vier Perioden beobachtet. Der Kurs stand bei 100, dann bei 50, 75, 150 und schließlich bei <strong>120</strong>.</p>
<div class="sourceCode" id="cb1"><pre class="sourceCode python"><code class="sourceCode python"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> numpy <span class="im">as</span> np</span>
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a>kurse <span class="op">=</span> np.array([<span class="fl">100.0</span>, <span class="fl">50.0</span>, <span class="fl">75.0</span>, <span class="fl">150.0</span>, <span class="fl">120.0</span>])</span>
<span id="cb1-4"><a href="#cb1-4" aria-hidden="true" tabindex="-1"></a>diskret <span class="op">=</span> kurse[<span class="dv">1</span>:] <span class="op">/</span> kurse[:<span class="op">-</span><span class="dv">1</span>] <span class="op">-</span> <span class="dv">1</span> <span class="co"># (neu - alt) / alt</span></span>
<span id="cb1-5"><a href="#cb1-5" aria-hidden="true" tabindex="-1"></a>log <span class="op">=</span> np.log(kurse[<span class="dv">1</span>:] <span class="op">/</span> kurse[:<span class="op">-</span><span class="dv">1</span>]) <span class="co"># ln(neu / alt)</span></span>
<span id="cb1-6"><a href="#cb1-6" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-7"><a href="#cb1-7" aria-hidden="true" tabindex="-1"></a><span class="bu">print</span>(<span class="st">&quot;diskret:&quot;</span>, np.<span class="bu">round</span>(diskret <span class="op">*</span> <span class="dv">100</span>, <span class="dv">2</span>), <span class="st">&quot;%&quot;</span>)</span>
<span id="cb1-8"><a href="#cb1-8" aria-hidden="true" tabindex="-1"></a><span class="bu">print</span>(<span class="st">&quot;log :&quot;</span>, np.<span class="bu">round</span>(log <span class="op">*</span> <span class="dv">100</span>, <span class="dv">2</span>), <span class="st">&quot;%&quot;</span>)</span>
<span id="cb1-9"><a href="#cb1-9" aria-hidden="true" tabindex="-1"></a><span class="bu">print</span>(<span class="ss">f&quot;Mittelwert diskret </span><span class="sc">{</span>diskret<span class="sc">.</span>mean()<span class="op">*</span><span class="dv">100</span><span class="sc">:5.2f}</span><span class="ss"> % -&gt; hochgerechnet &quot;</span></span>
<span id="cb1-10"><a href="#cb1-10" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;</span><span class="sc">{</span><span class="dv">100</span> <span class="op">*</span> (<span class="dv">1</span> <span class="op">+</span> diskret.mean())<span class="op">**</span><span class="dv">4</span><span class="sc">:6.2f}</span><span class="ss">&quot;</span>)</span>
<span id="cb1-11"><a href="#cb1-11" aria-hidden="true" tabindex="-1"></a><span class="bu">print</span>(<span class="ss">f&quot;Mittelwert log </span><span class="sc">{</span>log<span class="sc">.</span>mean()<span class="op">*</span><span class="dv">100</span><span class="sc">:5.2f}</span><span class="ss"> % -&gt; hochgerechnet &quot;</span></span>
<span id="cb1-12"><a href="#cb1-12" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;</span><span class="sc">{</span><span class="dv">100</span> <span class="op">*</span> np<span class="sc">.</span>exp(log.mean() <span class="op">*</span> <span class="dv">4</span>)<span class="sc">:6.2f}</span><span class="ss">&quot;</span>)</span></code></pre></div>
<p><strong>Ausgabe:</strong></p>
<pre><code>diskret: [-50. 50. 100. -20.] %
log : [-69.31 40.55 69.31 -22.31] %
Mittelwert diskret 20.00 % -&gt; hochgerechnet 207.36
Mittelwert log 4.56 % -&gt; hochgerechnet 120.00</code></pre>
</blockquote>
</div>
<p><strong>Die durchschnittliche Rendite beträgt 20 % pro Periode — bei einer Anlage, die insgesamt 20 % gewonnen hat.</strong> Wer damit vier Perioden hochrechnet, landet bei 207,36 statt bei den tatsächlichen 120,00.</p>
<p>Der Grund steckt in der ersten Zeile: Ein Verlust von 50 % und ein Gewinn von 50 % heben sich <strong>nicht</strong> auf. Aus 100 werden 50, daraus 75 — nicht wieder 100. Diskrete Renditen darf man deshalb nicht mitteln und nicht addieren; sie multiplizieren sich.</p>
<p>Logarithmische Renditen dagegen <strong>addieren</strong> sich. Ihre Summe beträgt hier 18,23 %, und <span class="math inline">100 \cdot e^{0{,}1823} = 120{,}00</span> — exakt der beobachtete Endkurs.</p>
<table>
<colgroup>
<col style="width: 33%" />
<col style="width: 33%" />
<col style="width: 33%" />
</colgroup>
<thead>
<tr class="header">
<th></th>
<th>diskrete Rendite</th>
<th>logarithmische Rendite</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>Verknüpfung über die Zeit</td>
<td>multiplikativ: <span class="math inline">\prod(1+r_t)</span></td>
<td><strong>additiv</strong>: <span class="math inline">\sum \ln(1+r_t)</span></td>
</tr>
<tr class="even">
<td>Mitteln zulässig?</td>
<td>nein — führt systematisch zu hoch</td>
<td>ja</td>
</tr>
<tr class="odd">
<td>Verknüpfung über Anlagen</td>
<td><strong>additiv</strong>: <span class="math inline">\sum w_i r_i</span></td>
<td>nein (Summe von Logs ≠ Log der Summe)</td>
</tr>
<tr class="even">
<td>Wofür also</td>
<td>Portfoliogewichtung, Kennzahlen je Periode</td>
<td>Zeitreihenanalyse, Volatilität, Modelle</td>
</tr>
</tbody>
</table>
<blockquote>
<p><strong>🎯 Merksatz</strong> Beide Renditearten sind richtig — für <strong>verschiedene Richtungen</strong>. Diskrete Renditen addieren sich über die <strong>Anlagen</strong> eines Portfolios, logarithmische über die <strong>Zeit</strong>. Wer sie verwechselt, bekommt keine Fehlermeldung, sondern eine plausible falsche Zahl.</p>
</blockquote>
<p><strong>Warum funktioniert das?</strong> Weil der Logarithmus aus einem Produkt eine Summe macht: <span class="math inline">\ln(a \cdot b) = \ln a + \ln b</span>. Genau diese Eigenschaft braucht man, wenn sich Renditen über die Zeit aufmultiplizieren. Der Preis dafür: Über mehrere Anlagen hinweg funktioniert es nicht mehr, denn der Logarithmus einer Summe ist nicht die Summe der Logarithmen. Der Rest dieses Kapitels behandelt die zweite große Fehlerquelle bei Finanzdaten — die <strong>Kovarianzmatrix</strong>, deren Schätzung noch weit trügerischer ist als die Rendite selbst.</p>
<hr />
<h2 id="sec:finanzdaten-lernziele">18.2 Lernziele</h2>
<p>Nach diesem Kapitel können Sie …</p>
<ol type="1">
<li>… erklären, wann man diskrete und wann logarithmische Renditen verwendet.</li>
<li>… beschreiben, warum die Stichproben-Kovarianzmatrix bei vielen Titeln unbrauchbar wird.</li>
<li>… den <strong>Error-Maximizer-Effekt</strong> an einem Experiment nachweisen.</li>
<li>… Ledoit-Wolf-Shrinkage anwenden und ihre Wirkung an Eigenwerten und Konditionszahl messen.</li>
<li>… Kursdaten so laden, dass die Spaltenreihenfolge garantiert stimmt.</li>
<li>… begründen, warum eine Schätzung aus <span class="math inline">T &lt; N</span> Beobachtungen dem Optimierer risikofreie Richtungen vorgaukelt — und welche drei Gegenmittel es gibt.</li>
<li>… ein Portfolio <strong>außerhalb</strong> des Schätzzeitraums bewerten statt darin.</li>
</ol>
<hr />
<h2 id="sec:finanzdaten-diskrete-und-logarithmische-renditen">18.3 Diskrete und logarithmische Renditen</h2>
<p>Sei <span class="math inline">P_{i,t}</span> der <strong>bereinigte</strong> Schlusskurs (<em>adjusted close</em>) von Titel <span class="math inline">i</span> zum Zeitpunkt <span class="math inline">t</span>. „Bereinigt“ heißt: um Dividenden und Aktiensplits korrigiert — sonst erscheint jede Dividendenzahlung als Kurssturz.</p>
<h3 id="diskrete-rendite">Diskrete Rendite</h3>
<p><span class="math display">
R_{i,t} = \frac{P_{i,t} - P_{i,t-1}}{P_{i,t-1}} = \frac{P_{i,t}}{P_{i,t-1}} - 1
</span></p>
<p><strong>Eigenschaft — über Titel additiv:</strong></p>
<p><span class="math display">
R_{p,t} = \sum_{i=1}^n w_i R_{i,t} = \mathbf{w}^\top\mathbf{R}_t
</span></p>
<h3 id="logarithmische-rendite">Logarithmische Rendite</h3>
<p><span class="math display">
r_{i,t} = \ln\!\left(\frac{P_{i,t}}{P_{i,t-1}}\right) = \ln P_{i,t} - \ln P_{i,t-1}
</span></p>
<p><strong>Eigenschaft — über die Zeit additiv:</strong></p>
<p><span class="math display">
r_{i,0\to T} = \sum_{t=1}^T r_{i,t}
</span></p>
<blockquote>
<p><strong>🎯 Die Merkregel</strong> <strong>Diskrete Renditen addieren sich über das Portfolio</strong> (mehrere Titel, ein Zeitpunkt) — deshalb braucht man sie für die <strong>Allokation</strong>. <strong>Logarithmische Renditen addieren sich über die Zeit</strong> (ein Titel, mehrere Perioden) — deshalb braucht man sie für <strong>Zeitreihenanalyse und Volatilitätsschätzung</strong>.</p>
<p>Man kann nicht beides gleichzeitig haben: Die gewichtete Summe von Log-Renditen ist <strong>nicht</strong> die Log-Rendite des Portfolios.</p>
</blockquote>
<blockquote>
<p><strong>✏️ Handrechnung 18.1: Der Unterschied in Zahlen</strong></p>
<p>Ein Kurs steigt von 100 auf 150, dann fällt er zurück auf 100.</p>
<table>
<colgroup>
<col style="width: 20%" />
<col style="width: 20%" />
<col style="width: 20%" />
<col style="width: 20%" />
<col style="width: 20%" />
</colgroup>
<thead>
<tr class="header">
<th></th>
<th>Periode 1</th>
<th>Periode 2</th>
<th>Summe</th>
<th>Wahrheit</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><strong>Diskret</strong></td>
<td><span class="math inline">+50{,}0\,\%</span></td>
<td><span class="math inline">-33{,}3\,\%</span></td>
<td><span class="math inline">+16{,}7\,\%</span></td>
<td><span class="math inline">0\,\%</span></td>
</tr>
<tr class="even">
<td><strong>Logarithmisch</strong></td>
<td><span class="math inline">+0{,}4055</span></td>
<td><span class="math inline">-0{,}4055</span></td>
<td><span class="math inline">0{,}0000</span></td>
<td><span class="math inline">0\,\%</span></td>
</tr>
</tbody>
</table>
<p>Die diskreten Renditen <strong>suggerieren einen Gewinn von 16,7 %</strong>, obwohl der Kurs exakt dort steht, wo er begann. Die Log-Renditen heben sich korrekt auf.</p>
<p><strong>Deshalb ist die Aussage „im Mittel 5 % Rendite“ mehrdeutig.</strong> Das arithmetische Mittel diskreter Renditen überschätzt systematisch, was ein Anleger tatsächlich verdient hätte. Für Wachstumsaussagen nimmt man das <strong>geometrische</strong> Mittel — oder rechnet gleich in Log-Renditen.</p>
</blockquote>
<div class="sourceCode" id="cb3"><pre class="sourceCode python"><code class="sourceCode python"><span id="cb3-1"><a href="#cb3-1" aria-hidden="true" tabindex="-1"></a><span class="co">#!/usr/bin/env python3</span></span>
<span id="cb3-2"><a href="#cb3-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-3"><a href="#cb3-3" aria-hidden="true" tabindex="-1"></a><span class="co"># Renditen_Vergleich.py</span></span>
<span id="cb3-4"><a href="#cb3-4" aria-hidden="true" tabindex="-1"></a><span class="co">&quot;&quot;&quot;</span></span>
<span id="cb3-5"><a href="#cb3-5" aria-hidden="true" tabindex="-1"></a><span class="co">Kapitel Finanzdaten: Diskrete vs. logarithmische Renditen.</span></span>
<span id="cb3-6"><a href="#cb3-6" aria-hidden="true" tabindex="-1"></a><span class="co">Zeigt an einem simulierten Kursverlauf, welche Eigenschaft wo gilt -</span></span>
<span id="cb3-7"><a href="#cb3-7" aria-hidden="true" tabindex="-1"></a><span class="co">und warum das arithmetische Mittel diskreter Renditen in die Irre fuehrt.</span></span>
<span id="cb3-8"><a href="#cb3-8" aria-hidden="true" tabindex="-1"></a><span class="co">&quot;&quot;&quot;</span></span>
<span id="cb3-9"><a href="#cb3-9" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-10"><a href="#cb3-10" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> numpy <span class="im">as</span> np</span>
<span id="cb3-11"><a href="#cb3-11" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> pandas <span class="im">as</span> pd</span>
<span id="cb3-12"><a href="#cb3-12" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-13"><a href="#cb3-13" aria-hidden="true" tabindex="-1"></a><span class="cf">if</span> <span class="va">__name__</span> <span class="op">==</span> <span class="st">&quot;__main__&quot;</span>:</span>
<span id="cb3-14"><a href="#cb3-14" aria-hidden="true" tabindex="-1"></a> kurse <span class="op">=</span> np.array([<span class="fl">100.0</span>, <span class="fl">150.0</span>, <span class="fl">100.0</span>, <span class="fl">120.0</span>, <span class="fl">90.0</span>, <span class="fl">135.0</span>])</span>
<span id="cb3-15"><a href="#cb3-15" aria-hidden="true" tabindex="-1"></a> tage <span class="op">=</span> [<span class="ss">f&quot;t</span><span class="sc">{</span>i<span class="sc">}</span><span class="ss">&quot;</span> <span class="cf">for</span> i <span class="kw">in</span> <span class="bu">range</span>(<span class="bu">len</span>(kurse))]</span>
<span id="cb3-16"><a href="#cb3-16" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-17"><a href="#cb3-17" aria-hidden="true" tabindex="-1"></a> diskret <span class="op">=</span> kurse[<span class="dv">1</span>:] <span class="op">/</span> kurse[:<span class="op">-</span><span class="dv">1</span>] <span class="op">-</span> <span class="fl">1.0</span></span>
<span id="cb3-18"><a href="#cb3-18" aria-hidden="true" tabindex="-1"></a> logarithmisch <span class="op">=</span> np.log(kurse[<span class="dv">1</span>:] <span class="op">/</span> kurse[:<span class="op">-</span><span class="dv">1</span>])</span>
<span id="cb3-19"><a href="#cb3-19" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-20"><a href="#cb3-20" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">74</span>)</span>
<span id="cb3-21"><a href="#cb3-21" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; DISKRETE UND LOGARITHMISCHE RENDITEN IM VERGLEICH&quot;</span>)</span>
<span id="cb3-22"><a href="#cb3-22" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">74</span>)</span>
<span id="cb3-23"><a href="#cb3-23" aria-hidden="true" tabindex="-1"></a> tabelle <span class="op">=</span> pd.DataFrame({</span>
<span id="cb3-24"><a href="#cb3-24" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;Periode&quot;</span>: [<span class="ss">f&quot;</span><span class="sc">{</span>tage[i]<span class="sc">}</span><span class="ss"> -&gt; </span><span class="sc">{</span>tage[i<span class="op">+</span><span class="dv">1</span>]<span class="sc">}</span><span class="ss">&quot;</span> <span class="cf">for</span> i <span class="kw">in</span> <span class="bu">range</span>(<span class="bu">len</span>(diskret))],</span>
<span id="cb3-25"><a href="#cb3-25" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;Kurs von&quot;</span>: kurse[:<span class="op">-</span><span class="dv">1</span>],</span>
<span id="cb3-26"><a href="#cb3-26" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;Kurs bis&quot;</span>: kurse[<span class="dv">1</span>:],</span>
<span id="cb3-27"><a href="#cb3-27" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;diskret R&quot;</span>: [<span class="ss">f&quot;</span><span class="sc">{</span>r<span class="op">*</span><span class="dv">100</span><span class="sc">:+7.2f}</span><span class="ss"> %&quot;</span> <span class="cf">for</span> r <span class="kw">in</span> diskret],</span>
<span id="cb3-28"><a href="#cb3-28" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;log r&quot;</span>: [<span class="ss">f&quot;</span><span class="sc">{</span>r<span class="sc">:+8.4f}</span><span class="ss">&quot;</span> <span class="cf">for</span> r <span class="kw">in</span> logarithmisch],</span>
<span id="cb3-29"><a href="#cb3-29" aria-hidden="true" tabindex="-1"></a> })</span>
<span id="cb3-30"><a href="#cb3-30" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(tabelle.to_string(index<span class="op">=</span><span class="va">False</span>))</span>
<span id="cb3-31"><a href="#cb3-31" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-32"><a href="#cb3-32" aria-hidden="true" tabindex="-1"></a> <span class="co"># --- Zeitliche Aggregation ------------------------------------------</span></span>
<span id="cb3-33"><a href="#cb3-33" aria-hidden="true" tabindex="-1"></a> gesamt_wahr <span class="op">=</span> kurse[<span class="op">-</span><span class="dv">1</span>] <span class="op">/</span> kurse[<span class="dv">0</span>] <span class="op">-</span> <span class="fl">1.0</span></span>
<span id="cb3-34"><a href="#cb3-34" aria-hidden="true" tabindex="-1"></a> summe_log <span class="op">=</span> logarithmisch.<span class="bu">sum</span>()</span>
<span id="cb3-35"><a href="#cb3-35" aria-hidden="true" tabindex="-1"></a> aus_log_zurueck <span class="op">=</span> np.exp(summe_log) <span class="op">-</span> <span class="fl">1.0</span></span>
<span id="cb3-36"><a href="#cb3-36" aria-hidden="true" tabindex="-1"></a> summe_diskret <span class="op">=</span> diskret.<span class="bu">sum</span>()</span>
<span id="cb3-37"><a href="#cb3-37" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-38"><a href="#cb3-38" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;</span><span class="ch">\n</span><span class="st">--- Aggregation ueber die Zeit ---&quot;</span>)</span>
<span id="cb3-39"><a href="#cb3-39" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; Tatsaechliche Gesamtrendite: </span><span class="sc">{</span>gesamt_wahr<span class="op">*</span><span class="dv">100</span><span class="sc">:+8.2f}</span><span class="ss"> %&quot;</span>)</span>
<span id="cb3-40"><a href="#cb3-40" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; Summe der Log-Renditen -&gt; exp()-1: </span><span class="sc">{</span>aus_log_zurueck<span class="op">*</span><span class="dv">100</span><span class="sc">:+8.2f}</span><span class="ss"> % &quot;</span></span>
<span id="cb3-41"><a href="#cb3-41" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;</span><span class="sc">{</span><span class="st">&#39;KORREKT&#39;</span> <span class="cf">if</span> <span class="bu">abs</span>(aus_log_zurueck <span class="op">-</span> gesamt_wahr) <span class="op">&lt;</span> <span class="fl">1e-9</span> <span class="cf">else</span> <span class="st">&#39;falsch&#39;</span><span class="sc">}</span><span class="ss">&quot;</span>)</span>
<span id="cb3-42"><a href="#cb3-42" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; Summe der diskreten Renditen: </span><span class="sc">{</span>summe_diskret<span class="op">*</span><span class="dv">100</span><span class="sc">:+8.2f}</span><span class="ss"> % FALSCH&quot;</span>)</span>
<span id="cb3-43"><a href="#cb3-43" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-44"><a href="#cb3-44" aria-hidden="true" tabindex="-1"></a> <span class="co"># --- Mittelwerte ------------------------------------------------------</span></span>
<span id="cb3-45"><a href="#cb3-45" aria-hidden="true" tabindex="-1"></a> arithmetisch <span class="op">=</span> diskret.mean()</span>
<span id="cb3-46"><a href="#cb3-46" aria-hidden="true" tabindex="-1"></a> geometrisch <span class="op">=</span> np.prod(<span class="dv">1</span> <span class="op">+</span> diskret) <span class="op">**</span> (<span class="dv">1</span> <span class="op">/</span> <span class="bu">len</span>(diskret)) <span class="op">-</span> <span class="dv">1</span></span>
<span id="cb3-47"><a href="#cb3-47" aria-hidden="true" tabindex="-1"></a> aus_log <span class="op">=</span> np.exp(logarithmisch.mean()) <span class="op">-</span> <span class="dv">1</span></span>
<span id="cb3-48"><a href="#cb3-48" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-49"><a href="#cb3-49" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;</span><span class="ch">\n</span><span class="st">--- Welcher Mittelwert ist der richtige? ---&quot;</span>)</span>
<span id="cb3-50"><a href="#cb3-50" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; Arithmetisches Mittel (diskret): </span><span class="sc">{</span>arithmetisch<span class="op">*</span><span class="dv">100</span><span class="sc">:+8.2f}</span><span class="ss"> % &quot;</span></span>
<span id="cb3-51"><a href="#cb3-51" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;-&gt; ueberschaetzt&quot;</span>)</span>
<span id="cb3-52"><a href="#cb3-52" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; Geometrisches Mittel: </span><span class="sc">{</span>geometrisch<span class="op">*</span><span class="dv">100</span><span class="sc">:+8.2f}</span><span class="ss"> % -&gt; korrekt&quot;</span>)</span>
<span id="cb3-53"><a href="#cb3-53" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; exp(Mittel der Log-Renditen) - 1: </span><span class="sc">{</span>aus_log<span class="op">*</span><span class="dv">100</span><span class="sc">:+8.2f}</span><span class="ss"> % &quot;</span></span>
<span id="cb3-54"><a href="#cb3-54" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;-&gt; identisch zum geometrischen&quot;</span>)</span>
<span id="cb3-55"><a href="#cb3-55" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-56"><a href="#cb3-56" aria-hidden="true" tabindex="-1"></a> <span class="co"># Probe: Endkapital mit dem jeweiligen Mittelwert hochgerechnet</span></span>
<span id="cb3-57"><a href="#cb3-57" aria-hidden="true" tabindex="-1"></a> n <span class="op">=</span> <span class="bu">len</span>(diskret)</span>
<span id="cb3-58"><a href="#cb3-58" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;</span><span class="ch">\n</span><span class="ss"> Probe - Startkapital 100 EUR ueber </span><span class="sc">{</span>n<span class="sc">}</span><span class="ss"> Perioden:&quot;</span>)</span>
<span id="cb3-59"><a href="#cb3-59" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; tatsaechlich: </span><span class="sc">{</span>kurse[<span class="op">-</span><span class="dv">1</span>]<span class="sc">:8.2f}</span><span class="ss"> EUR&quot;</span>)</span>
<span id="cb3-60"><a href="#cb3-60" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; mit arithm. Mittel hochgerechnet: </span><span class="sc">{</span><span class="dv">100</span><span class="op">*</span>(<span class="dv">1</span><span class="op">+</span>arithmetisch)<span class="op">**</span>n<span class="sc">:8.2f}</span><span class="ss"> EUR&quot;</span>)</span>
<span id="cb3-61"><a href="#cb3-61" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; mit geom. Mittel hochgerechnet: </span><span class="sc">{</span><span class="dv">100</span><span class="op">*</span>(<span class="dv">1</span><span class="op">+</span>geometrisch)<span class="op">**</span>n<span class="sc">:8.2f}</span><span class="ss"> EUR&quot;</span>)</span>
<span id="cb3-62"><a href="#cb3-62" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-63"><a href="#cb3-63" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;</span><span class="ch">\n</span><span class="st">--- Annualisierung (252 Handelstage) ---&quot;</span>)</span>
<span id="cb3-64"><a href="#cb3-64" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; Volatilitaet aus Log-Renditen: &quot;</span></span>
<span id="cb3-65"><a href="#cb3-65" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;</span><span class="sc">{</span>logarithmisch<span class="sc">.</span>std(ddof<span class="op">=</span><span class="dv">1</span>)<span class="op">*</span>np<span class="sc">.</span>sqrt(<span class="dv">252</span>)<span class="op">*</span><span class="dv">100</span><span class="sc">:.2f}</span><span class="ss"> % p.a.&quot;</span>)</span>
<span id="cb3-66"><a href="#cb3-66" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; (Die Wurzel-Zeit-Regel gilt nur bei unabhaengigen Renditen -&quot;</span>)</span>
<span id="cb3-67"><a href="#cb3-67" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; fuer reale Maerkte ist sie eine Naeherung.)&quot;</span>)</span>
<span id="cb3-68"><a href="#cb3-68" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">74</span>)</span></code></pre></div>
<hr />
<h2 id="sec:finanzdaten-das-schaetzfehler-problem">18.4 Das Schätzfehler-Problem</h2>
<p>Sei <span class="math inline">\mathbf{X}\in\mathbb{R}^{T\times N}</span> die Matrix zentrierter Renditen von <span class="math inline">N</span> Titeln über <span class="math inline">T</span> Handelstage. Die <strong>Stichproben-Kovarianzmatrix</strong> lautet:</p>
<p><span class="math display">
\mathbf{S} = \frac{1}{T-1}\mathbf{X}^\top\mathbf{X}
</span></p>
<p>Das Problem: <span class="math inline">\mathbf{S}</span> hat <span class="math inline">\frac{N(N+1)}{2}</span> zu schätzende Parameter, aber nur <span class="math inline">T \cdot N</span> Datenpunkte. Bei <span class="math inline">N = 50</span> Titeln sind das <strong>1275 Parameter</strong> — geschätzt aus einem Jahr Daten (<span class="math inline">T = 252</span>).</p>
<figure>
<img src="bilder_04/kap11_ledoit_wolf_shrinkage.svg" alt="Abb. 18.1: Ledoit-Wolf-Kovarianz-Shrinkage" />
<figcaption aria-hidden="true">Abb. 18.1: Ledoit-Wolf-Kovarianz-Shrinkage</figcaption>
</figure>
<table>
<colgroup>
<col style="width: 50%" />
<col style="width: 50%" />
</colgroup>
<thead>
<tr class="header">
<th>Verhältnis</th>
<th>Folge</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><span class="math inline">T \gg N</span></td>
<td><span class="math inline">\mathbf{S}</span> ist brauchbar</td>
</tr>
<tr class="even">
<td><span class="math inline">T \approx N</span></td>
<td>Extrem instabil; Eigenwerte streuen weit</td>
</tr>
<tr class="odd">
<td><span class="math inline">T &lt; N</span></td>
<td><span class="math inline">\mathbf{S}</span> ist <strong>singulär</strong> — nicht invertierbar, Rang <span class="math inline">&lt; N</span></td>
</tr>
</tbody>
</table>
<p>Die <strong>Zufallsmatrizentheorie</strong> (<em>Random Matrix Theory</em>, Marchenko-Pastur-Gesetz) zeigt: Die kleinsten Eigenwerte von <span class="math inline">\mathbf{S}</span> werden <strong>systematisch unterschätzt</strong>, die größten überschätzt. Und genau das ist fatal, denn:</p>
<blockquote>
<p><strong>🎯 Der Error-Maximizer-Effekt</strong> Ein Risikominimierer sucht die Richtungen mit der <strong>kleinsten</strong> Varianz — also genau jene Eigenrichtungen, deren Eigenwerte am stärksten <strong>nach unten verzerrt</strong> sind. Die Optimierung greift damit zielsicher in das Schätzrauschen hinein. Markowitz-Optimierung ist deshalb kein Fehlerdämpfer, sondern ein <strong>Fehlerverstärker</strong>.</p>
</blockquote>
<div class="sourceCode" id="cb4"><pre class="sourceCode python"><code class="sourceCode python"><span id="cb4-1"><a href="#cb4-1" aria-hidden="true" tabindex="-1"></a><span class="co">#!/usr/bin/env python3</span></span>
<span id="cb4-2"><a href="#cb4-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-3"><a href="#cb4-3" aria-hidden="true" tabindex="-1"></a><span class="co"># Schaetzrauschen_Demo.py</span></span>
<span id="cb4-4"><a href="#cb4-4" aria-hidden="true" tabindex="-1"></a><span class="co">&quot;&quot;&quot;</span></span>
<span id="cb4-5"><a href="#cb4-5" aria-hidden="true" tabindex="-1"></a><span class="co">Kapitel Finanzdaten: Der Error-Maximizer-Effekt im Experiment.</span></span>
<span id="cb4-6"><a href="#cb4-6" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-7"><a href="#cb4-7" aria-hidden="true" tabindex="-1"></a><span class="co">Aufbau: Wir KENNEN die wahre Kovarianzmatrix, weil wir die Daten selbst</span></span>
<span id="cb4-8"><a href="#cb4-8" aria-hidden="true" tabindex="-1"></a><span class="co">erzeugen. Dann schaetzen wir sie aus endlich vielen Beobachtungen und</span></span>
<span id="cb4-9"><a href="#cb4-9" aria-hidden="true" tabindex="-1"></a><span class="co">vergleichen, wie gut das daraus optimierte Portfolio in der WAHREN Welt</span></span>
<span id="cb4-10"><a href="#cb4-10" aria-hidden="true" tabindex="-1"></a><span class="co">abschneidet - mit und ohne Shrinkage.</span></span>
<span id="cb4-11"><a href="#cb4-11" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-12"><a href="#cb4-12" aria-hidden="true" tabindex="-1"></a><span class="co">Das ist der einzige saubere Weg, Schaetzfehler zu messen: Man braucht eine</span></span>
<span id="cb4-13"><a href="#cb4-13" aria-hidden="true" tabindex="-1"></a><span class="co">Wahrheit zum Vergleich, und die gibt es nur in der Simulation.</span></span>
<span id="cb4-14"><a href="#cb4-14" aria-hidden="true" tabindex="-1"></a><span class="co">&quot;&quot;&quot;</span></span>
<span id="cb4-15"><a href="#cb4-15" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-16"><a href="#cb4-16" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> numpy <span class="im">as</span> np</span>
<span id="cb4-17"><a href="#cb4-17" aria-hidden="true" tabindex="-1"></a><span class="im">from</span> sklearn.covariance <span class="im">import</span> LedoitWolf</span>
<span id="cb4-18"><a href="#cb4-18" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-19"><a href="#cb4-19" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-20"><a href="#cb4-20" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> erzeuge_wahre_kovarianz(n, rng):</span>
<span id="cb4-21"><a href="#cb4-21" aria-hidden="true" tabindex="-1"></a> <span class="co">&quot;&quot;&quot;Ein-Faktor-Modell: gemeinsamer Marktfaktor plus titelspezifisches Rauschen.&quot;&quot;&quot;</span></span>
<span id="cb4-22"><a href="#cb4-22" aria-hidden="true" tabindex="-1"></a> beta <span class="op">=</span> rng.uniform(<span class="fl">0.6</span>, <span class="fl">1.4</span>, size<span class="op">=</span>n) <span class="co"># Marktsensitivitaeten</span></span>
<span id="cb4-23"><a href="#cb4-23" aria-hidden="true" tabindex="-1"></a> var_markt <span class="op">=</span> <span class="fl">0.04</span> <span class="co"># Marktvarianz p.a.</span></span>
<span id="cb4-24"><a href="#cb4-24" aria-hidden="true" tabindex="-1"></a> var_spezifisch <span class="op">=</span> rng.uniform(<span class="fl">0.01</span>, <span class="fl">0.09</span>, size<span class="op">=</span>n) <span class="co"># idiosynkratische Varianz</span></span>
<span id="cb4-25"><a href="#cb4-25" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> np.outer(beta, beta) <span class="op">*</span> var_markt <span class="op">+</span> np.diag(var_spezifisch)</span>
<span id="cb4-26"><a href="#cb4-26" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-27"><a href="#cb4-27" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-28"><a href="#cb4-28" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> minimum_varianz_gewichte(sigma):</span>
<span id="cb4-29"><a href="#cb4-29" aria-hidden="true" tabindex="-1"></a> <span class="co">&quot;&quot;&quot;Analytische GMV-Loesung: w = Sigma^-1 1 / (1&#39; Sigma^-1 1). Ohne Restriktionen.&quot;&quot;&quot;</span></span>
<span id="cb4-30"><a href="#cb4-30" aria-hidden="true" tabindex="-1"></a> eins <span class="op">=</span> np.ones(<span class="bu">len</span>(sigma))</span>
<span id="cb4-31"><a href="#cb4-31" aria-hidden="true" tabindex="-1"></a> <span class="cf">try</span>:</span>
<span id="cb4-32"><a href="#cb4-32" aria-hidden="true" tabindex="-1"></a> loesung <span class="op">=</span> np.linalg.solve(sigma, eins)</span>
<span id="cb4-33"><a href="#cb4-33" aria-hidden="true" tabindex="-1"></a> <span class="cf">except</span> np.linalg.LinAlgError:</span>
<span id="cb4-34"><a href="#cb4-34" aria-hidden="true" tabindex="-1"></a> loesung <span class="op">=</span> np.linalg.pinv(sigma) <span class="op">@</span> eins <span class="co"># falls singulaer</span></span>
<span id="cb4-35"><a href="#cb4-35" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> loesung <span class="op">/</span> loesung.<span class="bu">sum</span>()</span>
<span id="cb4-36"><a href="#cb4-36" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-37"><a href="#cb4-37" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-38"><a href="#cb4-38" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> portfoliovolatilitaet(w, sigma):</span>
<span id="cb4-39"><a href="#cb4-39" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> <span class="bu">float</span>(np.sqrt(w <span class="op">@</span> sigma <span class="op">@</span> w))</span>
<span id="cb4-40"><a href="#cb4-40" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-41"><a href="#cb4-41" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-42"><a href="#cb4-42" aria-hidden="true" tabindex="-1"></a><span class="cf">if</span> <span class="va">__name__</span> <span class="op">==</span> <span class="st">&quot;__main__&quot;</span>:</span>
<span id="cb4-43"><a href="#cb4-43" aria-hidden="true" tabindex="-1"></a> rng <span class="op">=</span> np.random.default_rng(<span class="dv">2026</span>)</span>
<span id="cb4-44"><a href="#cb4-44" aria-hidden="true" tabindex="-1"></a> N <span class="op">=</span> <span class="dv">40</span> <span class="co"># Anzahl Titel</span></span>
<span id="cb4-45"><a href="#cb4-45" aria-hidden="true" tabindex="-1"></a> WIEDERHOLUNGEN <span class="op">=</span> <span class="dv">200</span></span>
<span id="cb4-46"><a href="#cb4-46" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-47"><a href="#cb4-47" aria-hidden="true" tabindex="-1"></a> sigma_wahr <span class="op">=</span> erzeuge_wahre_kovarianz(N, rng)</span>
<span id="cb4-48"><a href="#cb4-48" aria-hidden="true" tabindex="-1"></a> w_ideal <span class="op">=</span> minimum_varianz_gewichte(sigma_wahr)</span>
<span id="cb4-49"><a href="#cb4-49" aria-hidden="true" tabindex="-1"></a> vola_ideal <span class="op">=</span> portfoliovolatilitaet(w_ideal, sigma_wahr)</span>
<span id="cb4-50"><a href="#cb4-50" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-51"><a href="#cb4-51" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">92</span>)</span>
<span id="cb4-52"><a href="#cb4-52" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; ERROR-MAXIMIZER: WIE TEUER IST SCHAETZRAUSCHEN?&quot;</span>)</span>
<span id="cb4-53"><a href="#cb4-53" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">92</span>)</span>
<span id="cb4-54"><a href="#cb4-54" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;Aufbau: </span><span class="sc">{</span>N<span class="sc">}</span><span class="ss"> Titel, wahre Kovarianz bekannt (Ein-Faktor-Modell).&quot;</span>)</span>
<span id="cb4-55"><a href="#cb4-55" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;Bestmoegliche Volatilitaet bei perfektem Wissen: &quot;</span></span>
<span id="cb4-56"><a href="#cb4-56" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;</span><span class="sc">{</span>vola_ideal<span class="op">*</span><span class="dv">100</span><span class="sc">:.2f}</span><span class="ss"> % p.a.</span><span class="ch">\n</span><span class="ss">&quot;</span>)</span>
<span id="cb4-57"><a href="#cb4-57" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-58"><a href="#cb4-58" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;</span><span class="sc">{</span><span class="st">&#39;T (Tage)&#39;</span><span class="sc">:&gt;9}</span><span class="ss"> | </span><span class="sc">{</span><span class="st">&#39;T/N&#39;</span><span class="sc">:&gt;5}</span><span class="ss"> | </span><span class="sc">{</span><span class="st">&#39;Stichprobe&#39;</span><span class="sc">:&gt;22}</span><span class="ss"> | </span><span class="sc">{</span><span class="st">&#39;Ledoit-Wolf&#39;</span><span class="sc">:&gt;22}</span><span class="ss"> | &quot;</span></span>
<span id="cb4-59"><a href="#cb4-59" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;</span><span class="sc">{</span><span class="st">&#39;Shrink&#39;</span><span class="sc">:&gt;7}</span><span class="ss">&quot;</span>)</span>
<span id="cb4-60"><a href="#cb4-60" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;</span><span class="sc">{</span><span class="st">&#39;&#39;</span><span class="sc">:&gt;9}</span><span class="ss"> | </span><span class="sc">{</span><span class="st">&#39;&#39;</span><span class="sc">:&gt;5}</span><span class="ss"> | </span><span class="sc">{</span><span class="st">&#39;Vola Aufschlag&#39;</span><span class="sc">:&gt;22}</span><span class="ss"> | &quot;</span></span>
<span id="cb4-61"><a href="#cb4-61" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;</span><span class="sc">{</span><span class="st">&#39;Vola Aufschlag&#39;</span><span class="sc">:&gt;22}</span><span class="ss"> | </span><span class="sc">{</span><span class="st">&#39;delta&#39;</span><span class="sc">:&gt;7}</span><span class="ss">&quot;</span>)</span>
<span id="cb4-62"><a href="#cb4-62" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;-&quot;</span> <span class="op">*</span> <span class="dv">92</span>)</span>
<span id="cb4-63"><a href="#cb4-63" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-64"><a href="#cb4-64" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> T <span class="kw">in</span> [<span class="dv">60</span>, <span class="dv">120</span>, <span class="dv">252</span>, <span class="dv">504</span>, <span class="dv">1260</span>]:</span>
<span id="cb4-65"><a href="#cb4-65" aria-hidden="true" tabindex="-1"></a> vola_stichprobe, vola_lw, deltas <span class="op">=</span> [], [], []</span>
<span id="cb4-66"><a href="#cb4-66" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-67"><a href="#cb4-67" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> _ <span class="kw">in</span> <span class="bu">range</span>(WIEDERHOLUNGEN):</span>
<span id="cb4-68"><a href="#cb4-68" aria-hidden="true" tabindex="-1"></a> <span class="co"># Daten aus der WAHREN Verteilung ziehen (taeglich)</span></span>
<span id="cb4-69"><a href="#cb4-69" aria-hidden="true" tabindex="-1"></a> daten <span class="op">=</span> rng.multivariate_normal(np.zeros(N), sigma_wahr <span class="op">/</span> <span class="dv">252</span>, size<span class="op">=</span>T)</span>
<span id="cb4-70"><a href="#cb4-70" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-71"><a href="#cb4-71" aria-hidden="true" tabindex="-1"></a> <span class="co"># (a) Stichproben-Kovarianz</span></span>
<span id="cb4-72"><a href="#cb4-72" aria-hidden="true" tabindex="-1"></a> s_stich <span class="op">=</span> np.cov(daten, rowvar<span class="op">=</span><span class="va">False</span>, ddof<span class="op">=</span><span class="dv">1</span>) <span class="op">*</span> <span class="dv">252</span></span>
<span id="cb4-73"><a href="#cb4-73" aria-hidden="true" tabindex="-1"></a> w_stich <span class="op">=</span> minimum_varianz_gewichte(s_stich)</span>
<span id="cb4-74"><a href="#cb4-74" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-75"><a href="#cb4-75" aria-hidden="true" tabindex="-1"></a> <span class="co"># (b) Ledoit-Wolf-Shrinkage</span></span>
<span id="cb4-76"><a href="#cb4-76" aria-hidden="true" tabindex="-1"></a> lw <span class="op">=</span> LedoitWolf().fit(daten)</span>
<span id="cb4-77"><a href="#cb4-77" aria-hidden="true" tabindex="-1"></a> s_lw <span class="op">=</span> lw.covariance_ <span class="op">*</span> <span class="dv">252</span></span>
<span id="cb4-78"><a href="#cb4-78" aria-hidden="true" tabindex="-1"></a> w_lw <span class="op">=</span> minimum_varianz_gewichte(s_lw)</span>
<span id="cb4-79"><a href="#cb4-79" aria-hidden="true" tabindex="-1"></a> deltas.append(lw.shrinkage_)</span>
<span id="cb4-80"><a href="#cb4-80" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-81"><a href="#cb4-81" aria-hidden="true" tabindex="-1"></a> <span class="co"># Bewertung IMMER mit der wahren Kovarianz - das ist der Punkt</span></span>
<span id="cb4-82"><a href="#cb4-82" aria-hidden="true" tabindex="-1"></a> vola_stichprobe.append(portfoliovolatilitaet(w_stich, sigma_wahr))</span>
<span id="cb4-83"><a href="#cb4-83" aria-hidden="true" tabindex="-1"></a> vola_lw.append(portfoliovolatilitaet(w_lw, sigma_wahr))</span>
<span id="cb4-84"><a href="#cb4-84" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-85"><a href="#cb4-85" aria-hidden="true" tabindex="-1"></a> m_stich, m_lw <span class="op">=</span> np.mean(vola_stichprobe), np.mean(vola_lw)</span>
<span id="cb4-86"><a href="#cb4-86" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;</span><span class="sc">{</span>T<span class="sc">:&gt;9}</span><span class="ss"> | </span><span class="sc">{</span>T<span class="op">/</span>N<span class="sc">:&gt;5.1f}</span><span class="ss"> | </span><span class="sc">{</span>m_stich<span class="op">*</span><span class="dv">100</span><span class="sc">:&gt;8.2f}</span><span class="ss"> % &quot;</span></span>
<span id="cb4-87"><a href="#cb4-87" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;</span><span class="sc">{</span>(m_stich<span class="op">/</span>vola_ideal<span class="op">-</span><span class="dv">1</span>)<span class="op">*</span><span class="dv">100</span><span class="sc">:&gt;+9.1f}</span><span class="ss"> % | &quot;</span></span>
<span id="cb4-88"><a href="#cb4-88" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;</span><span class="sc">{</span>m_lw<span class="op">*</span><span class="dv">100</span><span class="sc">:&gt;8.2f}</span><span class="ss"> % </span><span class="sc">{</span>(m_lw<span class="op">/</span>vola_ideal<span class="op">-</span><span class="dv">1</span>)<span class="op">*</span><span class="dv">100</span><span class="sc">:&gt;+9.1f}</span><span class="ss"> % | &quot;</span></span>
<span id="cb4-89"><a href="#cb4-89" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;</span><span class="sc">{</span>np<span class="sc">.</span>mean(deltas)<span class="sc">:&gt;7.3f}</span><span class="ss">&quot;</span>)</span>
<span id="cb4-90"><a href="#cb4-90" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-91"><a href="#cb4-91" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;-&quot;</span> <span class="op">*</span> <span class="dv">92</span>)</span>
<span id="cb4-92"><a href="#cb4-92" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;&#39;Aufschlag&#39; = wie viel mehr Risiko das Portfolio in der WAHREN Welt traegt,&quot;</span>)</span>
<span id="cb4-93"><a href="#cb4-93" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;verglichen mit dem Portfolio bei perfektem Wissen.&quot;</span>)</span>
<span id="cb4-94"><a href="#cb4-94" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;Lesart: Je kleiner T/N, desto teurer das Schaetzrauschen - und desto mehr&quot;</span>)</span>
<span id="cb4-95"><a href="#cb4-95" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;schrumpft Ledoit-Wolf (delta steigt).&quot;</span>)</span>
<span id="cb4-96"><a href="#cb4-96" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">92</span>)</span></code></pre></div>
<hr />
<p><strong>Erwartete Ausgabe:</strong></p>
<pre><code>============================================================================================
ERROR-MAXIMIZER: WIE TEUER IST SCHAETZRAUSCHEN?
============================================================================================
Aufbau: 40 Titel, wahre Kovarianz bekannt (Ein-Faktor-Modell).
Bestmoegliche Volatilitaet bei perfektem Wissen: 11.84 % p.a.
T (Tage) | T/N | Stichprobe | Ledoit-Wolf | Shrink
| | Vola Aufschlag | Vola Aufschlag | delta
--------------------------------------------------------------------------------------------
60 | 1.5 | 20.78 % +75.5 % | 15.44 % +30.4 % | 0.093
120 | 3.0 | 14.42 % +21.8 % | 13.79 % +16.5 % | 0.048
252 | 6.3 | 12.86 % +8.7 % | 12.76 % +7.8 % | 0.024
504 | 12.6 | 12.33 % +4.1 % | 12.30 % +3.9 % | 0.012
1260 | 31.5 | 12.03 % +1.6 % | 12.02 % +1.6 % | 0.005
--------------------------------------------------------------------------------------------</code></pre>
<p><strong>Diese Tabelle ist das Kernargument des ganzen Kapitels.</strong> Lesen Sie die erste Zeile: Bei nur 60 Beobachtungen für 40 Titel trägt das „risikominimale“ Portfolio in Wahrheit <strong>75,5 % mehr Risiko</strong> als nötig. Der Optimierer hat nicht das Risiko minimiert, sondern das <strong>geschätzte</strong> Risiko — und die Differenz ist der Schätzfehler, in den er hineinoptimiert hat.</p>
<p>Drei Beobachtungen:</p>
<ol type="1">
<li><strong>Der Schaden wächst dramatisch, je knapper die Daten.</strong> Von <span class="math inline">T/N = 31{,}5</span> (+1,6 %) bis <span class="math inline">T/N = 1{,}5</span> (+75,5 %) ist es kein gleitender Übergang, sondern eine Explosion.</li>
<li><strong>Shrinkage halbiert den Schaden dort, wo er groß ist</strong> (75,5 % → 30,4 %) und schadet dort nichts, wo er klein ist (1,6 % → 1,6 %). Sie ist damit praktisch risikolos einzusetzen.</li>
<li><strong><span class="math inline">\delta</span> passt sich automatisch an:</strong> viel Schrumpfung bei wenig Daten (0,093), fast keine bei vielen (0,005). Man muss nichts von Hand einstellen.</li>
</ol>
<blockquote>
<p><strong>🎯 Die Praxisregel</strong> Faustregel für Aktienportfolios: <strong><span class="math inline">T \ge 5N</span></strong> sollte man mindestens haben, besser <span class="math inline">T \ge 10N</span>. Bei 50 Titeln sind das 250 bis 500 Handelstage — ein bis zwei Jahre. Wer weniger hat, sollte entweder das Universum verkleinern oder mit Shrinkage arbeiten. Am besten beides.</p>
</blockquote>
<h2 id="sec:finanzdaten-ledoit-wolf-shrinkage">18.5 Ledoit-Wolf-Shrinkage</h2>
<p>Olivier Ledoit und Michael Wolf lösten das Dilemma durch <strong>lineare Schrumpfung</strong>:</p>
<p><span class="math display">
\boldsymbol{\Sigma}_{\text{LW}} = (1-\delta)\,\mathbf{S} + \delta\,\mathbf{F}
</span></p>
<blockquote>
<p><strong>📐 Formel-Lesehilfe</strong> * <span class="math inline">\mathbf{S}</span> — die Stichprobenmatrix: <strong>unverzerrt</strong>, aber verrauscht. * <span class="math inline">\mathbf{F}</span> — das <strong>Schrumpfungsziel</strong>: verzerrt, aber stabil und rauschfrei. * <span class="math inline">\delta \in [0,1]</span> — wie weit man vom Rauschen zur Struktur zieht.</p>
<p><strong>Ohne Formel gesagt:</strong> Man mischt eine zappelige, aber im Mittel richtige Schätzung mit einer ruhigen, aber etwas schiefen. Die Mischung ist besser als beide Bestandteile einzeln — ein klassischer <strong>Bias-Varianz-Kompromiss</strong>. Ledoit und Wolf berechnen dabei dasjenige <span class="math inline">\delta</span>, das den erwarteten quadratischen Fehler zur unbekannten wahren Matrix <strong>minimiert</strong> — analytisch, ohne Kreuzvalidierung.</p>
</blockquote>
<h3 id="welches-ziel-mathbff">Welches Ziel <span class="math inline">\mathbf{F}</span>?</h3>
<p>Ein häufiges Missverständnis: Es gibt nämlich <strong>mehrere</strong> Ledoit-Wolf-Varianten, und Text und Code müssen sich konsistent auf dieselbe beziehen:</p>
<table>
<colgroup>
<col style="width: 15%" />
<col style="width: 25%" />
<col style="width: 14%" />
<col style="width: 43%" />
</colgroup>
<thead>
<tr class="header">
<th>Ziel <span class="math inline">\mathbf{F}</span></th>
<th>Formel</th>
<th>Quelle</th>
<th>Wer benutzt es</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><strong>Skalierte Einheitsmatrix</strong></td>
<td><span class="math inline">\mathbf{F} = \frac{\operatorname{tr}(\mathbf{S})}{N}\,\mathbf{I}</span></td>
<td>Ledoit/Wolf 2004, <em>„A well-conditioned estimator“</em></td>
<td><strong><code>sklearn.covariance.LedoitWolf</code></strong></td>
</tr>
<tr class="even">
<td><strong>Konstante Korrelation</strong></td>
<td><span class="math inline">f_{ii} = s_{ii}</span>, <span class="math inline">f_{ij} = \bar{r}\sqrt{s_{ii}s_{jj}}</span></td>
<td>Ledoit/Wolf 2003, <em>„Honey, I shrunk the sample covariance matrix“</em></td>
<td>oft in der Praxis, z. B. <code>PyPortfolioOpt</code></td>
</tr>
<tr class="odd">
<td><strong>Ein-Faktor-Modell</strong></td>
<td>aus Marktbeta geschätzt</td>
<td>Ledoit/Wolf 2003</td>
<td>Faktormodell-Ansätze</td>
</tr>
</tbody>
</table>
<p><strong>Wichtig ist, Text und Code konsistent auf dieselbe Variante zu beziehen</strong> — zum Beispiel <code>sklearn.covariance.LedoitWolf</code>, das die skalierte Einheitsmatrix verwendet, versus eine manuelle Implementierung des Konstant-Korrelations-Ziels. Dieses Buch nennt beide und macht transparent, welches wo zum Einsatz kommt.</p>
<blockquote>
Achter Fund (Uebung-Verweise) und Phase 8.3: Dockerfile Zwei Dinge in einem Commit, weil beide den Plan abschliessen. ACHTER FUND: sieben Saetze der Bauart "Uebung 8.5 laesst Sie diese Balance untersuchen" - und sechs davon standen in alter Zaehlung. "8" war in Version 03 das QP/NLP-Kapitel, heute ist es Kapitel 11. Dieselbe Familie wie die Denkfehler-Verweise aus 6.1a, nur mit einem Wort, das keine der bestehenden Pruefungen kannte. Aufgaben haben kein eigenes Label, ein {ref:} auf eine einzelne Aufgabe ist also nicht moeglich. Verwiesen wird stattdessen auf Abschnitt plus Aufgabentitel - und der ist stabil. Jedes Ziel wurde einzeln ueber die alte Zaehlung bestimmt und am Zusammenhang geprueft. Ein Fall war knifflig: "Uebung 6.7 (Wochendienstplan)" meinte die siebte CP-SAT-Aufgabe der alten Zaehlung, also "Eigener Dienstplan" - die heute an achter Stelle steht, weil in Phase 6.3 eine Aufgabe davor eingefuegt wurde. Wer nur die Kapitelnummer angepasst haette, waere bei der falschen Aufgabe gelandet. --check kennt jetzt auch "Uebung"/"Übung". Gegengetestet. PHASE 8.3: Dockerfile, zweistufig. Die erste Stufe uebersetzt die Abhaengigkeiten in eine virtuelle Umgebung und braucht dafuer einen Compiler, die zweite kopiert nur /opt/venv. Installiert werden die Gruppen finance, large-scale, api und dev aus pyproject.toml; figures fehlt bewusst, weil es zusaetzlich Graphviz verlangt. Es wurde nicht behauptet, sondern gebaut. Ergebnis: 1,31 GB, und darin der Installationstest mit allen drei Solver-Funktionstests bestanden, die 33 pytest-Tests bestanden und alle 69 netzfreien Programme fehlerfrei - einschliesslich der drei aus 8.2, deren spawn-Isolation im Container ebenso traegt wie ausserhalb. Zwei Dinge, die der Bau gelehrt hat: libgomp1 fehlt im python:3.12-slim-Image und wird von OR-Tools und HiGHS zur Laufzeit gebraucht (sonst "libgomp.so.1: cannot open shared object file"). Und ein eigener Fehler: USER kurs stand vor dem mkdir /buch/output, /buch gehoert root, der Bau brach in der letzten Zeile ab. Beides steht jetzt als Kommentar im Dockerfile. Das Image fuehrt die Programme aus und baut das Buch nicht. Ein .dockerignore haelt Website, PDF und Notebooks aus dem Build-Kontext. Und es enthaelt ortools UND highspy, obwohl sie sich nicht gemeinsam importieren lassen - der Konflikt wird zur Laufzeit durch getrennte Prozesse geloest, nicht durch Weglassen. Damit ist Phase 8 abgeschlossen und der Plan abgearbeitet. Stand: 825 Querverweise, 76 Programme, 33 pytest-Tests, PDF 760 Seiten. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 13:18:05 +02:00
<p><strong>💡 Welches Ziel ist besser?</strong> Es kommt darauf an. Die <strong>Einheitsmatrix</strong> unterstellt, dass alle Titel gleich riskant und unkorreliert sind — eine starke, aber sehr stabile Annahme. Das <strong>Konstant-Korrelations-Ziel</strong> behält die geschätzten Einzelvarianzen und glättet nur die Korrelationen; das ist bei Aktien meist realistischer, weil Titel tatsächlich sehr unterschiedliche Volatilitäten haben. In der Aufgabe <em>Beide Shrinkage-Ziele vergleichen</em> (<a href="#sec:finanzdaten-uebungsaufgaben">Abschnitt 18.7</a>) implementieren Sie es selbst und vergleichen.</p>
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>
2026-09-08 01:20:09 +02:00
</blockquote>
<p>Das Ergebnis ist in jedem Fall garantiert <strong>positiv definit</strong>, wohlkonditioniert und stabil invertierbar.</p>
<hr />
<h2 id="sec:finanzdaten-praxis-datenpipeline-mit-korrekter-spaltenreihenfolge">18.6 Praxis: Datenpipeline mit korrekter Spaltenreihenfolge</h2>
<blockquote>
<p><strong>⚠️ Die wichtigste Zeile dieses Kapitels</strong></p>
<p><code>yfinance</code> liefert die Kursspalten <strong>alphabetisch sortiert</strong>, nicht in der Reihenfolge Ihrer Ticker-Liste:</p>
<pre><code>Ihre Liste: [&#39;AAPL&#39;, &#39;MSFT&#39;, &#39;NVDA&#39;, &#39;AMZN&#39;, &#39;JNJ&#39;, &#39;PFE&#39;, &#39;JPM&#39;, &#39;GS&#39;, &#39;XOM&#39;, &#39;CVX&#39;]
prices.columns: [&#39;AAPL&#39;, &#39;AMZN&#39;, &#39;CVX&#39;, &#39;GS&#39;, &#39;JNJ&#39;, &#39;JPM&#39;, &#39;MSFT&#39;, &#39;NVDA&#39;, &#39;PFE&#39;, &#39;XOM&#39;]</code></pre>
<p>Wer danach mit Positionsindizes arbeitet, beschriftet Ergebnisse falsch und beschränkt falsche Sektoren. <strong>Die Abhilfe ist eine Zeile:</strong></p>
<div class="sourceCode" id="cb7"><pre class="sourceCode python"><code class="sourceCode python"><span id="cb7-1"><a href="#cb7-1" aria-hidden="true" tabindex="-1"></a>prices <span class="op">=</span> raw[<span class="st">&quot;Close&quot;</span>][tickers].dropna() <span class="co"># erzwingt die eigene Reihenfolge</span></span>
<span id="cb7-2"><a href="#cb7-2" aria-hidden="true" tabindex="-1"></a><span class="cf">assert</span> <span class="bu">list</span>(prices.columns) <span class="op">==</span> tickers</span></code></pre></div>
</blockquote>
<div class="sourceCode" id="cb8"><pre class="sourceCode python"><code class="sourceCode python"><span id="cb8-1"><a href="#cb8-1" aria-hidden="true" tabindex="-1"></a><span class="co">#!/usr/bin/env python3</span></span>
<span id="cb8-2"><a href="#cb8-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-3"><a href="#cb8-3" aria-hidden="true" tabindex="-1"></a><span class="co"># Finanzdaten_Ledoit_Wolf.py</span></span>
<span id="cb8-4"><a href="#cb8-4" aria-hidden="true" tabindex="-1"></a><span class="co">&quot;&quot;&quot;</span></span>
<span id="cb8-5"><a href="#cb8-5" aria-hidden="true" tabindex="-1"></a><span class="co">Kapitel Finanzdaten: Automatisierte Finanzdaten-Pipeline und Kovarianz-Shrinkage.</span></span>
<span id="cb8-6"><a href="#cb8-6" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-7"><a href="#cb8-7" aria-hidden="true" tabindex="-1"></a><span class="co">Eigenschaften:</span></span>
<span id="cb8-8"><a href="#cb8-8" aria-hidden="true" tabindex="-1"></a><span class="co"> * Spaltenreihenfolge wird erzwungen und per assert geprueft</span></span>
<span id="cb8-9"><a href="#cb8-9" aria-hidden="true" tabindex="-1"></a><span class="co"> * beide Shrinkage-Ziele werden berechnet und verglichen</span></span>
<span id="cb8-10"><a href="#cb8-10" aria-hidden="true" tabindex="-1"></a><span class="co"> * Datenqualitaetspruefungen (Luecken, Ausreisser, Mindestlaenge)</span></span>
<span id="cb8-11"><a href="#cb8-11" aria-hidden="true" tabindex="-1"></a><span class="co"> * Eigenwertspektrum wird ausgewiesen, nicht nur die Konditionszahl</span></span>
<span id="cb8-12"><a href="#cb8-12" aria-hidden="true" tabindex="-1"></a><span class="co">&quot;&quot;&quot;</span></span>
<span id="cb8-13"><a href="#cb8-13" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-14"><a href="#cb8-14" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> numpy <span class="im">as</span> np</span>
<span id="cb8-15"><a href="#cb8-15" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> pandas <span class="im">as</span> pd</span>
<span id="cb8-16"><a href="#cb8-16" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> yfinance <span class="im">as</span> yf</span>
<span id="cb8-17"><a href="#cb8-17" aria-hidden="true" tabindex="-1"></a><span class="im">from</span> sklearn.covariance <span class="im">import</span> LedoitWolf</span>
<span id="cb8-18"><a href="#cb8-18" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-19"><a href="#cb8-19" aria-hidden="true" tabindex="-1"></a>HANDELSTAGE <span class="op">=</span> <span class="dv">252</span></span>
<span id="cb8-20"><a href="#cb8-20" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-21"><a href="#cb8-21" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-22"><a href="#cb8-22" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> lade_kurse(tickers: <span class="bu">list</span>[<span class="bu">str</span>], start, ende) <span class="op">-&gt;</span> pd.DataFrame:</span>
<span id="cb8-23"><a href="#cb8-23" aria-hidden="true" tabindex="-1"></a> <span class="co">&quot;&quot;&quot;Laedt bereinigte Schlusskurse und garantiert die Spaltenreihenfolge.&quot;&quot;&quot;</span></span>
<span id="cb8-24"><a href="#cb8-24" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;Lade </span><span class="sc">{</span><span class="bu">len</span>(tickers)<span class="sc">}</span><span class="ss"> Ticker von </span><span class="sc">{</span>start<span class="sc">}</span><span class="ss"> bis </span><span class="sc">{</span>ende<span class="sc">}</span><span class="ss"> ...&quot;</span>)</span>
<span id="cb8-25"><a href="#cb8-25" aria-hidden="true" tabindex="-1"></a> roh <span class="op">=</span> yf.download(tickers, start<span class="op">=</span>start, end<span class="op">=</span>ende, auto_adjust<span class="op">=</span><span class="va">True</span>, progress<span class="op">=</span><span class="va">False</span>)</span>
<span id="cb8-26"><a href="#cb8-26" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> roh.empty:</span>
<span id="cb8-27"><a href="#cb8-27" aria-hidden="true" tabindex="-1"></a> <span class="cf">raise</span> <span class="pp">SystemExit</span>(<span class="st">&quot;Download fehlgeschlagen (Netz, Ticker oder Rate-Limit pruefen).&quot;</span>)</span>
<span id="cb8-28"><a href="#cb8-28" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-29"><a href="#cb8-29" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> <span class="bu">isinstance</span>(roh.columns, pd.MultiIndex):</span>
<span id="cb8-30"><a href="#cb8-30" aria-hidden="true" tabindex="-1"></a> <span class="co"># [tickers] erzwingt die gewuenschte Spaltenreihenfolge</span></span>
<span id="cb8-31"><a href="#cb8-31" aria-hidden="true" tabindex="-1"></a> kurse <span class="op">=</span> roh[<span class="st">&quot;Close&quot;</span>][tickers].dropna()</span>
<span id="cb8-32"><a href="#cb8-32" aria-hidden="true" tabindex="-1"></a> <span class="cf">else</span>:</span>
<span id="cb8-33"><a href="#cb8-33" aria-hidden="true" tabindex="-1"></a> kurse <span class="op">=</span> roh[[<span class="st">&quot;Close&quot;</span>]].dropna()</span>
<span id="cb8-34"><a href="#cb8-34" aria-hidden="true" tabindex="-1"></a> kurse.columns <span class="op">=</span> tickers</span>
<span id="cb8-35"><a href="#cb8-35" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-36"><a href="#cb8-36" aria-hidden="true" tabindex="-1"></a> <span class="cf">assert</span> <span class="bu">list</span>(kurse.columns) <span class="op">==</span> tickers, (</span>
<span id="cb8-37"><a href="#cb8-37" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;Spaltenreihenfolge weicht ab!</span><span class="ch">\n</span><span class="ss"> erwartet: </span><span class="sc">{</span>tickers<span class="sc">}</span><span class="ch">\n</span><span class="ss">&quot;</span></span>
<span id="cb8-38"><a href="#cb8-38" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot; erhalten: </span><span class="sc">{</span><span class="bu">list</span>(kurse.columns)<span class="sc">}</span><span class="ss">&quot;</span>)</span>
<span id="cb8-39"><a href="#cb8-39" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> kurse</span>
<span id="cb8-40"><a href="#cb8-40" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-41"><a href="#cb8-41" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-42"><a href="#cb8-42" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> pruefe_datenqualitaet(kurse: pd.DataFrame, mindest_tage: <span class="bu">int</span> <span class="op">=</span> <span class="dv">200</span>) <span class="op">-&gt;</span> <span class="va">None</span>:</span>
<span id="cb8-43"><a href="#cb8-43" aria-hidden="true" tabindex="-1"></a> <span class="co">&quot;&quot;&quot;Faengt die haeufigsten Datenprobleme ab, bevor sie ins Modell gelangen.&quot;&quot;&quot;</span></span>
<span id="cb8-44"><a href="#cb8-44" aria-hidden="true" tabindex="-1"></a> T, N <span class="op">=</span> kurse.shape</span>
<span id="cb8-45"><a href="#cb8-45" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;</span><span class="ch">\n</span><span class="ss">Datenqualitaet: </span><span class="sc">{</span>T<span class="sc">}</span><span class="ss"> Handelstage, </span><span class="sc">{</span>N<span class="sc">}</span><span class="ss"> Titel (T/N = </span><span class="sc">{</span>T<span class="op">/</span>N<span class="sc">:.1f}</span><span class="ss">)&quot;</span>)</span>
<span id="cb8-46"><a href="#cb8-46" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> T <span class="op">&lt;</span> mindest_tage:</span>
<span id="cb8-47"><a href="#cb8-47" aria-hidden="true" tabindex="-1"></a> <span class="cf">raise</span> <span class="pp">SystemExit</span>(<span class="ss">f&quot;Zu wenige Beobachtungen (</span><span class="sc">{</span>T<span class="sc">}</span><span class="ss"> &lt; </span><span class="sc">{</span>mindest_tage<span class="sc">}</span><span class="ss">).&quot;</span>)</span>
<span id="cb8-48"><a href="#cb8-48" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> T <span class="op">&lt;</span> N:</span>
<span id="cb8-49"><a href="#cb8-49" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; WARNUNG: T &lt; N - die Stichprobenkovarianz ist singulaer!&quot;</span>)</span>
<span id="cb8-50"><a href="#cb8-50" aria-hidden="true" tabindex="-1"></a> <span class="cf">elif</span> T <span class="op">&lt;</span> <span class="dv">3</span> <span class="op">*</span> N:</span>
<span id="cb8-51"><a href="#cb8-51" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; WARNUNG: T &lt; 3N - erhebliches Schaetzrauschen zu erwarten.&quot;</span>)</span>
<span id="cb8-52"><a href="#cb8-52" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-53"><a href="#cb8-53" aria-hidden="true" tabindex="-1"></a> renditen <span class="op">=</span> kurse.pct_change().dropna()</span>
<span id="cb8-54"><a href="#cb8-54" aria-hidden="true" tabindex="-1"></a> extreme <span class="op">=</span> (renditen.<span class="bu">abs</span>() <span class="op">&gt;</span> <span class="fl">0.25</span>).<span class="bu">sum</span>().<span class="bu">sum</span>()</span>
<span id="cb8-55"><a href="#cb8-55" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> extreme:</span>
<span id="cb8-56"><a href="#cb8-56" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; Hinweis: </span><span class="sc">{</span>extreme<span class="sc">}</span><span class="ss"> Tagesrenditen ueber 25 % &quot;</span></span>
<span id="cb8-57"><a href="#cb8-57" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;(Splits, Sondersituationen oder Datenfehler pruefen).&quot;</span>)</span>
<span id="cb8-58"><a href="#cb8-58" aria-hidden="true" tabindex="-1"></a> luecken <span class="op">=</span> kurse.isna().<span class="bu">sum</span>().<span class="bu">sum</span>()</span>
<span id="cb8-59"><a href="#cb8-59" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; Fehlende Werte nach dropna: </span><span class="sc">{</span>luecken<span class="sc">}</span><span class="ss">&quot;</span>)</span>
<span id="cb8-60"><a href="#cb8-60" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-61"><a href="#cb8-61" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-62"><a href="#cb8-62" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> ziel_konstante_korrelation(renditen: np.ndarray) <span class="op">-&gt;</span> np.ndarray:</span>
<span id="cb8-63"><a href="#cb8-63" aria-hidden="true" tabindex="-1"></a> <span class="co">&quot;&quot;&quot;</span></span>
<span id="cb8-64"><a href="#cb8-64" aria-hidden="true" tabindex="-1"></a><span class="co"> Shrinkage-Ziel nach Ledoit/Wolf 2003: Einzelvarianzen behalten,</span></span>
<span id="cb8-65"><a href="#cb8-65" aria-hidden="true" tabindex="-1"></a><span class="co"> alle Korrelationen durch ihren Mittelwert ersetzen.</span></span>
<span id="cb8-66"><a href="#cb8-66" aria-hidden="true" tabindex="-1"></a><span class="co"> (sklearn nutzt stattdessen die skalierte Einheitsmatrix - siehe oben.)</span></span>
<span id="cb8-67"><a href="#cb8-67" aria-hidden="true" tabindex="-1"></a><span class="co"> &quot;&quot;&quot;</span></span>
<span id="cb8-68"><a href="#cb8-68" aria-hidden="true" tabindex="-1"></a> S <span class="op">=</span> np.cov(renditen, rowvar<span class="op">=</span><span class="va">False</span>, ddof<span class="op">=</span><span class="dv">1</span>)</span>
<span id="cb8-69"><a href="#cb8-69" aria-hidden="true" tabindex="-1"></a> d <span class="op">=</span> np.sqrt(np.diag(S))</span>
<span id="cb8-70"><a href="#cb8-70" aria-hidden="true" tabindex="-1"></a> korrelation <span class="op">=</span> S <span class="op">/</span> np.outer(d, d)</span>
<span id="cb8-71"><a href="#cb8-71" aria-hidden="true" tabindex="-1"></a> n <span class="op">=</span> <span class="bu">len</span>(S)</span>
<span id="cb8-72"><a href="#cb8-72" aria-hidden="true" tabindex="-1"></a> r_quer <span class="op">=</span> (korrelation.<span class="bu">sum</span>() <span class="op">-</span> n) <span class="op">/</span> (n <span class="op">*</span> (n <span class="op">-</span> <span class="dv">1</span>)) <span class="co"># Mittel ohne Diagonale</span></span>
<span id="cb8-73"><a href="#cb8-73" aria-hidden="true" tabindex="-1"></a> F <span class="op">=</span> r_quer <span class="op">*</span> np.outer(d, d)</span>
<span id="cb8-74"><a href="#cb8-74" aria-hidden="true" tabindex="-1"></a> np.fill_diagonal(F, np.diag(S))</span>
<span id="cb8-75"><a href="#cb8-75" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> F</span>
<span id="cb8-76"><a href="#cb8-76" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-77"><a href="#cb8-77" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-78"><a href="#cb8-78" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> analysiere(kurse: pd.DataFrame):</span>
<span id="cb8-79"><a href="#cb8-79" aria-hidden="true" tabindex="-1"></a> renditen <span class="op">=</span> kurse.pct_change().dropna()</span>
<span id="cb8-80"><a href="#cb8-80" aria-hidden="true" tabindex="-1"></a> T, N <span class="op">=</span> renditen.shape</span>
<span id="cb8-81"><a href="#cb8-81" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-82"><a href="#cb8-82" aria-hidden="true" tabindex="-1"></a> mu <span class="op">=</span> renditen.mean().values <span class="op">*</span> HANDELSTAGE</span>
<span id="cb8-83"><a href="#cb8-83" aria-hidden="true" tabindex="-1"></a> sigma_stichprobe <span class="op">=</span> renditen.cov().values <span class="op">*</span> HANDELSTAGE</span>
<span id="cb8-84"><a href="#cb8-84" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-85"><a href="#cb8-85" aria-hidden="true" tabindex="-1"></a> lw <span class="op">=</span> LedoitWolf(assume_centered<span class="op">=</span><span class="va">False</span>).fit(renditen.values)</span>
<span id="cb8-86"><a href="#cb8-86" aria-hidden="true" tabindex="-1"></a> sigma_lw <span class="op">=</span> lw.covariance_ <span class="op">*</span> HANDELSTAGE</span>
<span id="cb8-87"><a href="#cb8-87" aria-hidden="true" tabindex="-1"></a> delta <span class="op">=</span> lw.shrinkage_</span>
<span id="cb8-88"><a href="#cb8-88" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-89"><a href="#cb8-89" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;</span><span class="ch">\n</span><span class="st">&quot;</span> <span class="op">+</span> <span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">84</span>)</span>
<span id="cb8-90"><a href="#cb8-90" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; FINANZDATEN-PIPELINE UND MATRIX-KONDITIONIERUNG&quot;</span>)</span>
<span id="cb8-91"><a href="#cb8-91" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">84</span>)</span>
<span id="cb8-92"><a href="#cb8-92" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;Beobachtungen T: </span><span class="sc">{</span>T<span class="sc">}</span><span class="ss">&quot;</span>)</span>
<span id="cb8-93"><a href="#cb8-93" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;Titel N: </span><span class="sc">{</span>N<span class="sc">}</span><span class="ss">&quot;</span>)</span>
<span id="cb8-94"><a href="#cb8-94" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;Optimales Shrinkage delta: </span><span class="sc">{</span>delta<span class="sc">:.4f}</span><span class="ss"> &quot;</span></span>
<span id="cb8-95"><a href="#cb8-95" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;(</span><span class="sc">{</span>delta<span class="op">*</span><span class="dv">100</span><span class="sc">:.1f}</span><span class="ss"> % Gewicht auf dem strukturierten Ziel)&quot;</span>)</span>
<span id="cb8-96"><a href="#cb8-96" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-97"><a href="#cb8-97" aria-hidden="true" tabindex="-1"></a> eig_stich <span class="op">=</span> np.linalg.eigvalsh(sigma_stichprobe)</span>
<span id="cb8-98"><a href="#cb8-98" aria-hidden="true" tabindex="-1"></a> eig_lw <span class="op">=</span> np.linalg.eigvalsh(sigma_lw)</span>
<span id="cb8-99"><a href="#cb8-99" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-100"><a href="#cb8-100" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;</span><span class="ch">\n</span><span class="st">--- Eigenwertspektrum (annualisiert) ---&quot;</span>)</span>
<span id="cb8-101"><a href="#cb8-101" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;</span><span class="sc">{</span><span class="st">&#39;&#39;</span><span class="sc">:&lt;14}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;kleinster&#39;</span><span class="sc">:&gt;12}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;Median&#39;</span><span class="sc">:&gt;12}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;groesster&#39;</span><span class="sc">:&gt;12}</span><span class="ss"> &quot;</span></span>
<span id="cb8-102"><a href="#cb8-102" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;</span><span class="sc">{</span><span class="st">&#39;Kondition&#39;</span><span class="sc">:&gt;12}</span><span class="ss">&quot;</span>)</span>
<span id="cb8-103"><a href="#cb8-103" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> name, eig <span class="kw">in</span> [(<span class="st">&quot;Stichprobe&quot;</span>, eig_stich), (<span class="st">&quot;Ledoit-Wolf&quot;</span>, eig_lw)]:</span>
<span id="cb8-104"><a href="#cb8-104" aria-hidden="true" tabindex="-1"></a> kondition <span class="op">=</span> eig.<span class="bu">max</span>() <span class="op">/</span> <span class="bu">max</span>(eig.<span class="bu">min</span>(), <span class="fl">1e-12</span>)</span>
<span id="cb8-105"><a href="#cb8-105" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;</span><span class="sc">{</span>name<span class="sc">:&lt;14}</span><span class="ss"> </span><span class="sc">{</span>eig<span class="sc">.</span><span class="bu">min</span>()<span class="sc">:&gt;12.6f}</span><span class="ss"> </span><span class="sc">{</span>np<span class="sc">.</span>median(eig)<span class="sc">:&gt;12.6f}</span><span class="ss"> &quot;</span></span>
<span id="cb8-106"><a href="#cb8-106" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;</span><span class="sc">{</span>eig<span class="sc">.</span><span class="bu">max</span>()<span class="sc">:&gt;12.6f}</span><span class="ss"> </span><span class="sc">{</span>kondition<span class="sc">:&gt;12.1f}</span><span class="ss">&quot;</span>)</span>
<span id="cb8-107"><a href="#cb8-107" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-108"><a href="#cb8-108" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;</span><span class="ch">\n</span><span class="st">Die Konditionszahl misst, wie stark sich kleine Datenaenderungen auf&quot;</span>)</span>
<span id="cb8-109"><a href="#cb8-109" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;die Inverse auswirken. Je kleiner, desto stabiler die Portfoliogewichte.&quot;</span>)</span>
<span id="cb8-110"><a href="#cb8-110" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-111"><a href="#cb8-111" aria-hidden="true" tabindex="-1"></a> <span class="co"># --- Vergleich der beiden Shrinkage-Ziele ----------------------------</span></span>
<span id="cb8-112"><a href="#cb8-112" aria-hidden="true" tabindex="-1"></a> S_taeglich <span class="op">=</span> np.cov(renditen.values, rowvar<span class="op">=</span><span class="va">False</span>, ddof<span class="op">=</span><span class="dv">1</span>)</span>
<span id="cb8-113"><a href="#cb8-113" aria-hidden="true" tabindex="-1"></a> F_identitaet <span class="op">=</span> np.eye(N) <span class="op">*</span> np.trace(S_taeglich) <span class="op">/</span> N</span>
<span id="cb8-114"><a href="#cb8-114" aria-hidden="true" tabindex="-1"></a> F_korrelation <span class="op">=</span> ziel_konstante_korrelation(renditen.values)</span>
<span id="cb8-115"><a href="#cb8-115" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-116"><a href="#cb8-116" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;</span><span class="ch">\n</span><span class="st">--- Die beiden Shrinkage-Ziele im Vergleich ---&quot;</span>)</span>
<span id="cb8-117"><a href="#cb8-117" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> name, F <span class="kw">in</span> [(<span class="st">&quot;skalierte Einheitsmatrix (sklearn)&quot;</span>, F_identitaet),</span>
<span id="cb8-118"><a href="#cb8-118" aria-hidden="true" tabindex="-1"></a> (<span class="st">&quot;konstante Korrelation (LW 2003)&quot;</span>, F_korrelation)]:</span>
<span id="cb8-119"><a href="#cb8-119" aria-hidden="true" tabindex="-1"></a> gemischt <span class="op">=</span> ((<span class="dv">1</span> <span class="op">-</span> delta) <span class="op">*</span> S_taeglich <span class="op">+</span> delta <span class="op">*</span> F) <span class="op">*</span> HANDELSTAGE</span>
<span id="cb8-120"><a href="#cb8-120" aria-hidden="true" tabindex="-1"></a> eig <span class="op">=</span> np.linalg.eigvalsh(gemischt)</span>
<span id="cb8-121"><a href="#cb8-121" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; </span><span class="sc">{</span>name<span class="sc">:&lt;36}</span><span class="ss">: Kondition </span><span class="sc">{</span>eig<span class="sc">.</span><span class="bu">max</span>()<span class="op">/</span><span class="bu">max</span>(eig.<span class="bu">min</span>(),<span class="fl">1e-12</span>)<span class="sc">:8.1f}</span><span class="ss">, &quot;</span></span>
<span id="cb8-122"><a href="#cb8-122" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;kleinster EW </span><span class="sc">{</span>eig<span class="sc">.</span><span class="bu">min</span>()<span class="sc">:.6f}</span><span class="ss">&quot;</span>)</span>
<span id="cb8-123"><a href="#cb8-123" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-124"><a href="#cb8-124" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;</span><span class="ch">\n</span><span class="st">--- Erwartete Renditen (annualisiert) ---&quot;</span>)</span>
<span id="cb8-125"><a href="#cb8-125" aria-hidden="true" tabindex="-1"></a> uebersicht <span class="op">=</span> pd.DataFrame({</span>
<span id="cb8-126"><a href="#cb8-126" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;Ticker&quot;</span>: kurse.columns,</span>
<span id="cb8-127"><a href="#cb8-127" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;Rendite p.a.&quot;</span>: [<span class="ss">f&quot;</span><span class="sc">{</span>r<span class="op">*</span><span class="dv">100</span><span class="sc">:+7.2f}</span><span class="ss"> %&quot;</span> <span class="cf">for</span> r <span class="kw">in</span> mu],</span>
<span id="cb8-128"><a href="#cb8-128" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;Volatilitaet p.a.&quot;</span>: [<span class="ss">f&quot;</span><span class="sc">{</span>np<span class="sc">.</span>sqrt(sigma_lw[i, i])<span class="op">*</span><span class="dv">100</span><span class="sc">:6.2f}</span><span class="ss"> %&quot;</span> <span class="cf">for</span> i <span class="kw">in</span> <span class="bu">range</span>(N)],</span>
<span id="cb8-129"><a href="#cb8-129" aria-hidden="true" tabindex="-1"></a> })</span>
<span id="cb8-130"><a href="#cb8-130" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(uebersicht.to_string(index<span class="op">=</span><span class="va">False</span>))</span>
<span id="cb8-131"><a href="#cb8-131" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">84</span>)</span>
<span id="cb8-132"><a href="#cb8-132" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> renditen, mu, sigma_stichprobe, sigma_lw</span>
<span id="cb8-133"><a href="#cb8-133" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-134"><a href="#cb8-134" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-135"><a href="#cb8-135" aria-hidden="true" tabindex="-1"></a><span class="cf">if</span> <span class="va">__name__</span> <span class="op">==</span> <span class="st">&quot;__main__&quot;</span>:</span>
<span id="cb8-136"><a href="#cb8-136" aria-hidden="true" tabindex="-1"></a> UNIVERSUM <span class="op">=</span> [<span class="st">&quot;AAPL&quot;</span>, <span class="st">&quot;MSFT&quot;</span>, <span class="st">&quot;NVDA&quot;</span>, <span class="st">&quot;AMZN&quot;</span>, <span class="co"># Technologie</span></span>
<span id="cb8-137"><a href="#cb8-137" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;JNJ&quot;</span>, <span class="st">&quot;PFE&quot;</span>, <span class="co"># Gesundheit</span></span>
<span id="cb8-138"><a href="#cb8-138" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;JPM&quot;</span>, <span class="st">&quot;GS&quot;</span>, <span class="co"># Banken</span></span>
<span id="cb8-139"><a href="#cb8-139" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;XOM&quot;</span>, <span class="st">&quot;CVX&quot;</span>] <span class="co"># Energie</span></span>
<span id="cb8-140"><a href="#cb8-140" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-141"><a href="#cb8-141" aria-hidden="true" tabindex="-1"></a> <span class="co"># Relatives 2-Jahres-Fenster, damit das Beispiel nicht &quot;altert&quot;</span></span>
<span id="cb8-142"><a href="#cb8-142" aria-hidden="true" tabindex="-1"></a> ende <span class="op">=</span> pd.Timestamp.today().normalize()</span>
<span id="cb8-143"><a href="#cb8-143" aria-hidden="true" tabindex="-1"></a> start <span class="op">=</span> ende <span class="op">-</span> pd.DateOffset(years<span class="op">=</span><span class="dv">2</span>)</span>
<span id="cb8-144"><a href="#cb8-144" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-145"><a href="#cb8-145" aria-hidden="true" tabindex="-1"></a> kurse <span class="op">=</span> lade_kurse(UNIVERSUM, start.strftime(<span class="st">&quot;%Y-%m-</span><span class="sc">%d</span><span class="st">&quot;</span>), ende.strftime(<span class="st">&quot;%Y-%m-</span><span class="sc">%d</span><span class="st">&quot;</span>))</span>
<span id="cb8-146"><a href="#cb8-146" aria-hidden="true" tabindex="-1"></a> pruefe_datenqualitaet(kurse)</span>
<span id="cb8-147"><a href="#cb8-147" aria-hidden="true" tabindex="-1"></a> analysiere(kurse)</span></code></pre></div>
<blockquote>
<p><strong>⚠️ Zur Reproduzierbarkeit</strong> Dieses Programm lädt <strong>Live-Daten</strong>. Ihre Zahlen werden von den hier abgedruckten abweichen — je nach Abrufdatum. Das ist beabsichtigt und selbst eine Lektion: Wenn sich das optimale Portfolio zwischen zwei Abrufen deutlich ändert, obwohl nur ein paar Tage vergangen sind, sehen Sie den Schätzfehler direkt. Prüfen Sie das ruhig einmal.</p>
</blockquote>
<hr />
<h2 id="sec:finanzdaten-uebungsaufgaben">18.7 Übungsaufgaben</h2>
<blockquote>
<p>Lösungen: <a href="anhang-loesungen.html#sec:loesungen-finanzdaten">Abschnitt A.18</a>.</p>
</blockquote>
<p><strong>Aufgabe 18.1 ⭐ — Renditeart wählen.</strong> Welche Renditeart nehmen Sie und warum? (a) Portfoliorendite aus Einzelrenditen berechnen. (b) Volatilität über 5 Jahre schätzen. (c) Kumulierte Wertentwicklung über 10 Jahre darstellen. (d) Korrelationen zwischen Titeln schätzen.</p>
<p><strong>Aufgabe 18.2 ⭐ — Mittelwertfalle.</strong> Ein Fonds meldet Jahresrenditen von <span class="math inline">+60\,\%</span>, <span class="math inline">-40\,\%</span>, <span class="math inline">+60\,\%</span>, <span class="math inline">-40\,\%</span>. (a) Wie hoch ist das arithmetische Mittel? (b) Wie viel Kapital hat ein Anleger nach 4 Jahren aus 10 000 €? (c) Welche jährliche Rendite entspricht dem tatsächlich?</p>
<p><strong>Aufgabe 18.3 ⭐⭐ — Konditionszahl verstehen.</strong> Erzeugen Sie eine Kovarianzmatrix für <span class="math inline">N = 30</span> Titel aus <span class="math inline">T = 40</span>, <span class="math inline">T = 100</span> und <span class="math inline">T = 1000</span> simulierten Beobachtungen. Berechnen Sie jeweils kleinsten Eigenwert und Konditionszahl. Ab welchem <span class="math inline">T</span> wird die Matrix brauchbar?</p>
<p><strong>Aufgabe 18.4 ⭐⭐ — Spaltenreihenfolge prüfen.</strong> Laden Sie fünf Ticker in einer bewusst unsortierten Reihenfolge. Zeigen Sie, dass <code>raw["Close"].columns</code> abweicht, und dass <code>raw["Close"][tickers]</code> das behebt. Was passiert, wenn ein Ticker nicht existiert?</p>
<p><strong>Aufgabe 18.5 ⭐⭐⭐ — Beide Shrinkage-Ziele vergleichen.</strong> Implementieren Sie das Konstant-Korrelations-Ziel vollständig (inklusive eigener Berechnung von <span class="math inline">\delta</span> nach Ledoit/Wolf 2003 oder per Kreuzvalidierung). Vergleichen Sie mit <code>sklearn</code> anhand: (a) der Konditionszahl, (b) der Out-of-Sample-Volatilität eines GMV-Portfolios (nach dem Muster von <code>Schaetzrauschen_Demo.py</code>), (c) der Stabilität der Gewichte über rollierende Fenster.</p>
<p><strong>Aufgabe 18.6 ⭐⭐⭐ — Error-Maximizer selbst messen.</strong> Erweitern Sie <code>Schaetzrauschen_Demo.py</code> um eine dritte Variante: die <strong>Gleichgewichtung</strong> (<span class="math inline">w_i = 1/N</span>), die überhaupt keine Schätzung benötigt. (a) Ab welchem <span class="math inline">T/N</span> schlägt die Stichproben-Optimierung die Gleichgewichtung? (b) Ab welchem <span class="math inline">T/N</span> schlägt Ledoit-Wolf sie? (c) Was folgt daraus für die Praxis?</p>
<hr />
<h2 id="sec:finanzdaten-denkfehler">18.8 Finde den Denkfehler</h2>
<div class="card card-denkfehler">
<blockquote>
Sechster und siebter Fund: die letzten handgeschriebenen Nummern Der Loesungsanhang trug 98 Marken der Bauart "**9.2 — Ansatz waehlen.**", saemtlich aus Version 03, wo Unsicherheit Kapitel 9 war. Heute ist es Kapitel 12, und die Aufgabe wird korrekt als "Aufgabe 12.2" gesetzt. Das war keine Schoenheitsfrage: "Aufgabe 9.2" existiert wirklich - sie gehoert zum Kapitel Metaheuristiken ("Zuggroesse und Temperatur"). Wer die Loesung zu 9.2 nachschlug, landete beim falschen Thema. Beide bisherigen Pruefungen liefen vorbei: Vor der Zahl steht kein Schluesselwort, und es ist keine Tabellenzelle. Zwei Messungen machten die Reparatur billig. In allen 15 nummerierten Abschnitten gab es exakt so viele Loesungen wie Aufgaben, lueckenlos 1..n - nur der Kapitelteil war falsch. Und die acht in Phase 3 ergaenzten Kapitel machten es laengst richtig (**Titel.** ohne Nummer); der Anhang wurde also nicht auf etwas Neues umgestellt, sondern auf das, was seine neueren Teile schon taten. nummeriere_marken() vergibt die Marken jetzt selbst. Der Praefix kommt NICHT aus "# Anhang A:" - der Anhang ist eine Ueberschrift, seine Abschnitte gehoeren aber zu 23 verschiedenen Kapiteln. Er kommt aus {#sec:loesungen-X} -> {#kap:X}; diese Zuordnung gilt geprueft fuer alle 23. Im Quelltext steht "**{loesung} — Titel.**". Siebter Fund unterwegs: neun Verweise auf Denkfehler-Nummern. Saetze wie "siehe Denkfehler 8.1" standen in alter Zaehlung - "8" war in Version 03 das QP/NLP-Kapitel, heute ist es Graphen. Eine blosse Umnummerierung haette sie stillschweigend woanders hin zeigen lassen; das Ziel wurde deshalb fuer jeden einzelnen aus dem Zusammenhang bestimmt und auf {ref:sec:<kapitel>-denkfehler} umgestellt. Einer steckte in einem Programm-Docstring und bekam nach Regel 12 den Kapitelnamen statt eines Verweises. Eine bewusste Abweichung vom Plan: Der Denkfehler bekommt GAR KEINE Nummer. Es gibt je Kapitel genau einen, die zweite Stelle waere immer .1, und er steht ohnehin unter einer nummerierten Ueberschrift. Eine Nummer, die nie variiert, holte nur die Fragilitaet zurueck, die hier beseitigt wird. Auch das folgt den acht neuen Kapiteln, die ihren Denkfehler schon vorher nur mit dem Titel des Raetsels ankuendigen. --check bewacht jetzt alle Familien (Denkfehler N.M, Micro-Quiz N, **N.M — im Anhang) und zaehlt zusaetzlich ab, dass jeder Loesungsabschnitt so viele {loesung}-Marken hat wie sein Kapitel Aufgaben - das faengt eine vergessene oder doppelte Loesung, was keine Textsuche leisten kann. Gegengetestet mit vier kuenstlichen Fehlern auf einmal: alle vier gemeldet, nach dem Rueckbau null. Gegenprobe der Umstellung: Gesamtdokument vorher gesichert, nachher verglichen. 168 geaenderte Zeilen, restlos einer Familie zuzuordnen - 118 Loesungsmarken (138 gesamt minus 20, die in den Kapiteln 1 bis 3 zufaellig schon stimmten), 15 Anhang-Ueberschriften, 15 Kapitel-Callouts, 12 Micro-Quiz, 7 Zeilen mit Verweisen, 1 Docstring. Keine verschobene oder inhaltlich veraenderte Zeile. Ergebnis: 138 Aufgaben, 138 Loesungen, keine ohne Gegenstueck. Micro-Quiz tragen die echten Kapitelnummern. 293 Abschnitte, 720 Querverweise, 327 Indexmarken, PDF unveraendert 725 Seiten, 33 pytest-Tests, 67 netzfreie Programme fehlerfrei (die vier yfinance-Programme scheiterten am Rate-Limit des Anbieters, nicht an dieser Aenderung). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 09:52:41 +02:00
<p><strong>🐛 Finde den Denkfehler: Das risikofreie Portfolio</strong></p>
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>
2026-09-08 01:20:09 +02:00
<p>Ein Analyst stellt ein Portfolio aus <strong>30 Titeln</strong> zusammen. Für die Kovarianzmatrix nimmt er das letzte Quartal — bei Wochendaten sind das <strong>20 Beobachtungen</strong>. Dann minimiert er die Varianz.</p>
<p>Das Ergebnis begeistert ihn: Das Portfolio hat ein Risiko von <strong>0,000 %</strong>. Er hat ein risikofreies Aktienportfolio gefunden.</p>
<p><strong>Ihre Aufgabe:</strong> (a) Wie hoch ist der Rang einer Kovarianzmatrix, die aus <span class="math inline">T</span> Beobachtungen für <span class="math inline">N &gt; T</span> Anlagen geschätzt wurde — und was folgt daraus geometrisch? (b) Warum findet ausgerechnet ein <strong>Minimum</strong>-Varianz-Optimierer diese Stelle so zuverlässig? (c) Sehen Sie sich unten die Spalte „Hebel“ an. Was sagt ein Wert von 3,8 über das Portfolio aus? (d) Warum schlägt die simple Gleichgewichtung den Optimierer — und was folgt daraus für Ihre eigenen Modelle, auch außerhalb der Finanzwelt?</p>
<p><em>Auflösung: <a href="anhang-loesungen.html#sec:loesungen-finanzdaten">Abschnitt A.18</a>.</em></p>
</blockquote>
</div>
<p>Das folgende Programm führt den Fall vor. Es arbeitet mit <strong>synthetischen</strong> Daten, damit das Ergebnis reproduzierbar ist und kein Internetzugang nötig wird — mit echten Kursdaten sieht es genauso aus.</p>
<div class="sourceCode" id="cb9"><pre class="sourceCode python"><code class="sourceCode python"><span id="cb9-1"><a href="#cb9-1" aria-hidden="true" tabindex="-1"></a><span class="co">#!/usr/bin/env python3</span></span>
<span id="cb9-2"><a href="#cb9-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-3"><a href="#cb9-3" aria-hidden="true" tabindex="-1"></a><span class="co"># Kovarianz_Falle.py</span></span>
<span id="cb9-4"><a href="#cb9-4" aria-hidden="true" tabindex="-1"></a><span class="co">&quot;&quot;&quot;</span></span>
<span id="cb9-5"><a href="#cb9-5" aria-hidden="true" tabindex="-1"></a><span class="co">Kapitel Finanzdaten: Warum die historische Kovarianzmatrix den Optimierer belaeugt.</span></span>
<span id="cb9-6"><a href="#cb9-6" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-7"><a href="#cb9-7" aria-hidden="true" tabindex="-1"></a><span class="co">Ein Portfolio aus 30 Anlagen soll das Risiko minimieren. Geschaetzt wird die</span></span>
<span id="cb9-8"><a href="#cb9-8" aria-hidden="true" tabindex="-1"></a><span class="co">Kovarianzmatrix aus 20 Beobachtungen - in der Praxis ein voellig ueblicher</span></span>
<span id="cb9-9"><a href="#cb9-9" aria-hidden="true" tabindex="-1"></a><span class="co">Fall (30 Titel, ein Quartal Wochendaten).</span></span>
<span id="cb9-10"><a href="#cb9-10" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-11"><a href="#cb9-11" aria-hidden="true" tabindex="-1"></a><span class="co">Das Ergebnis ist ein Portfolio mit einem gemessenen Risiko von 0,000 %. Der</span></span>
<span id="cb9-12"><a href="#cb9-12" aria-hidden="true" tabindex="-1"></a><span class="co">Optimierer haelt es fuer risikofrei. Ausserhalb des Schaetzzeitraums ist es</span></span>
<span id="cb9-13"><a href="#cb9-13" aria-hidden="true" tabindex="-1"></a><span class="co">das schlechteste der drei untersuchten Portfolios - schlechter sogar als</span></span>
<span id="cb9-14"><a href="#cb9-14" aria-hidden="true" tabindex="-1"></a><span class="co">blosse Gleichgewichtung.</span></span>
<span id="cb9-15"><a href="#cb9-15" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-16"><a href="#cb9-16" aria-hidden="true" tabindex="-1"></a><span class="co">Der Grund ist nicht Zufall, sondern Struktur: Bei N Anlagen und T &lt; N</span></span>
<span id="cb9-17"><a href="#cb9-17" aria-hidden="true" tabindex="-1"></a><span class="co">Beobachtungen hat die Stichproben-Kovarianzmatrix hoechstens Rang T-1. Es gibt</span></span>
<span id="cb9-18"><a href="#cb9-18" aria-hidden="true" tabindex="-1"></a><span class="co">dann ganze Richtungen im Gewichtsraum, in denen sie exakt null Varianz misst -</span></span>
<span id="cb9-19"><a href="#cb9-19" aria-hidden="true" tabindex="-1"></a><span class="co">Richtungen, die es in Wirklichkeit nicht gibt. Der Optimierer findet sie</span></span>
<span id="cb9-20"><a href="#cb9-20" aria-hidden="true" tabindex="-1"></a><span class="co">zuverlaessig, denn er sucht ja genau danach.</span></span>
<span id="cb9-21"><a href="#cb9-21" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-22"><a href="#cb9-22" aria-hidden="true" tabindex="-1"></a><span class="co">Die Daten sind synthetisch (ein Marktfaktor plus Eigenrauschen), damit das</span></span>
<span id="cb9-23"><a href="#cb9-23" aria-hidden="true" tabindex="-1"></a><span class="co">Ergebnis reproduzierbar ist und kein Internetzugang noetig wird. Mit echten</span></span>
<span id="cb9-24"><a href="#cb9-24" aria-hidden="true" tabindex="-1"></a><span class="co">Kursdaten sieht es genauso aus.</span></span>
<span id="cb9-25"><a href="#cb9-25" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-26"><a href="#cb9-26" aria-hidden="true" tabindex="-1"></a><span class="co">Benoetigt: numpy, cvxpy, scikit-learn</span></span>
<span id="cb9-27"><a href="#cb9-27" aria-hidden="true" tabindex="-1"></a><span class="co">&quot;&quot;&quot;</span></span>
<span id="cb9-28"><a href="#cb9-28" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-29"><a href="#cb9-29" aria-hidden="true" tabindex="-1"></a><span class="im">from</span> __future__ <span class="im">import</span> annotations</span>
<span id="cb9-30"><a href="#cb9-30" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-31"><a href="#cb9-31" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> numpy <span class="im">as</span> np</span>
<span id="cb9-32"><a href="#cb9-32" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> cvxpy <span class="im">as</span> cp</span>
<span id="cb9-33"><a href="#cb9-33" aria-hidden="true" tabindex="-1"></a><span class="im">from</span> sklearn.covariance <span class="im">import</span> LedoitWolf</span>
<span id="cb9-34"><a href="#cb9-34" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-35"><a href="#cb9-35" aria-hidden="true" tabindex="-1"></a>ANZAHL_ANLAGEN <span class="op">=</span> <span class="dv">30</span></span>
<span id="cb9-36"><a href="#cb9-36" aria-hidden="true" tabindex="-1"></a>BEOBACHTUNGEN_SCHAETZUNG <span class="op">=</span> <span class="dv">20</span> <span class="co"># weniger als Anlagen - genau darum geht es</span></span>
<span id="cb9-37"><a href="#cb9-37" aria-hidden="true" tabindex="-1"></a>BEOBACHTUNGEN_TEST <span class="op">=</span> <span class="dv">2000</span> <span class="co"># der &quot;spaetere Verlauf&quot;</span></span>
<span id="cb9-38"><a href="#cb9-38" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-39"><a href="#cb9-39" aria-hidden="true" tabindex="-1"></a>RNG <span class="op">=</span> np.random.default_rng(<span class="dv">11</span>)</span>
<span id="cb9-40"><a href="#cb9-40" aria-hidden="true" tabindex="-1"></a><span class="co"># Jede Anlage reagiert unterschiedlich stark auf den Gesamtmarkt.</span></span>
<span id="cb9-41"><a href="#cb9-41" aria-hidden="true" tabindex="-1"></a>BETA <span class="op">=</span> RNG.uniform(<span class="fl">0.6</span>, <span class="fl">1.4</span>, ANZAHL_ANLAGEN)</span>
<span id="cb9-42"><a href="#cb9-42" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-43"><a href="#cb9-43" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-44"><a href="#cb9-44" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> erzeuge_renditen(perioden: <span class="bu">int</span>) <span class="op">-&gt;</span> np.ndarray:</span>
<span id="cb9-45"><a href="#cb9-45" aria-hidden="true" tabindex="-1"></a> <span class="co">&quot;&quot;&quot;Ein Marktfaktor, auf den alle reagieren, plus Eigenrauschen je Anlage.&quot;&quot;&quot;</span></span>
<span id="cb9-46"><a href="#cb9-46" aria-hidden="true" tabindex="-1"></a> markt <span class="op">=</span> RNG.normal(<span class="fl">0.0</span>, <span class="fl">0.010</span>, perioden)</span>
<span id="cb9-47"><a href="#cb9-47" aria-hidden="true" tabindex="-1"></a> eigen <span class="op">=</span> RNG.normal(<span class="fl">0.0</span>, <span class="fl">0.012</span>, (perioden, ANZAHL_ANLAGEN))</span>
<span id="cb9-48"><a href="#cb9-48" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> np.outer(markt, BETA) <span class="op">+</span> eigen</span>
<span id="cb9-49"><a href="#cb9-49" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-50"><a href="#cb9-50" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-51"><a href="#cb9-51" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> minimales_risiko(kovarianz: np.ndarray) <span class="op">-&gt;</span> <span class="bu">tuple</span>[np.ndarray, <span class="bu">float</span>]:</span>
<span id="cb9-52"><a href="#cb9-52" aria-hidden="true" tabindex="-1"></a> <span class="co">&quot;&quot;&quot;Minimum-Varianz-Portfolio: Gewichte summieren sich zu 1, sonst frei.</span></span>
<span id="cb9-53"><a href="#cb9-53" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-54"><a href="#cb9-54" aria-hidden="true" tabindex="-1"></a><span class="co"> cp.psd_wrap() sagt CVXPY: &#39;Ich weiss, dass diese Matrix numerisch nicht</span></span>
<span id="cb9-55"><a href="#cb9-55" aria-hidden="true" tabindex="-1"></a><span class="co"> exakt positiv semidefinit ist - rechne trotzdem.&#39; Das ist hier bewusst so</span></span>
<span id="cb9-56"><a href="#cb9-56" aria-hidden="true" tabindex="-1"></a><span class="co"> gewaehlt, denn genau diese Eigenschaft wollen wir vorfuehren. Im</span></span>
<span id="cb9-57"><a href="#cb9-57" aria-hidden="true" tabindex="-1"></a><span class="co"> Produktivbetrieb waere die DCP-Fehlermeldung das Warnsignal, dem man</span></span>
Sechster und siebter Fund: die letzten handgeschriebenen Nummern Der Loesungsanhang trug 98 Marken der Bauart "**9.2 — Ansatz waehlen.**", saemtlich aus Version 03, wo Unsicherheit Kapitel 9 war. Heute ist es Kapitel 12, und die Aufgabe wird korrekt als "Aufgabe 12.2" gesetzt. Das war keine Schoenheitsfrage: "Aufgabe 9.2" existiert wirklich - sie gehoert zum Kapitel Metaheuristiken ("Zuggroesse und Temperatur"). Wer die Loesung zu 9.2 nachschlug, landete beim falschen Thema. Beide bisherigen Pruefungen liefen vorbei: Vor der Zahl steht kein Schluesselwort, und es ist keine Tabellenzelle. Zwei Messungen machten die Reparatur billig. In allen 15 nummerierten Abschnitten gab es exakt so viele Loesungen wie Aufgaben, lueckenlos 1..n - nur der Kapitelteil war falsch. Und die acht in Phase 3 ergaenzten Kapitel machten es laengst richtig (**Titel.** ohne Nummer); der Anhang wurde also nicht auf etwas Neues umgestellt, sondern auf das, was seine neueren Teile schon taten. nummeriere_marken() vergibt die Marken jetzt selbst. Der Praefix kommt NICHT aus "# Anhang A:" - der Anhang ist eine Ueberschrift, seine Abschnitte gehoeren aber zu 23 verschiedenen Kapiteln. Er kommt aus {#sec:loesungen-X} -> {#kap:X}; diese Zuordnung gilt geprueft fuer alle 23. Im Quelltext steht "**{loesung} — Titel.**". Siebter Fund unterwegs: neun Verweise auf Denkfehler-Nummern. Saetze wie "siehe Denkfehler 8.1" standen in alter Zaehlung - "8" war in Version 03 das QP/NLP-Kapitel, heute ist es Graphen. Eine blosse Umnummerierung haette sie stillschweigend woanders hin zeigen lassen; das Ziel wurde deshalb fuer jeden einzelnen aus dem Zusammenhang bestimmt und auf {ref:sec:<kapitel>-denkfehler} umgestellt. Einer steckte in einem Programm-Docstring und bekam nach Regel 12 den Kapitelnamen statt eines Verweises. Eine bewusste Abweichung vom Plan: Der Denkfehler bekommt GAR KEINE Nummer. Es gibt je Kapitel genau einen, die zweite Stelle waere immer .1, und er steht ohnehin unter einer nummerierten Ueberschrift. Eine Nummer, die nie variiert, holte nur die Fragilitaet zurueck, die hier beseitigt wird. Auch das folgt den acht neuen Kapiteln, die ihren Denkfehler schon vorher nur mit dem Titel des Raetsels ankuendigen. --check bewacht jetzt alle Familien (Denkfehler N.M, Micro-Quiz N, **N.M — im Anhang) und zaehlt zusaetzlich ab, dass jeder Loesungsabschnitt so viele {loesung}-Marken hat wie sein Kapitel Aufgaben - das faengt eine vergessene oder doppelte Loesung, was keine Textsuche leisten kann. Gegengetestet mit vier kuenstlichen Fehlern auf einmal: alle vier gemeldet, nach dem Rueckbau null. Gegenprobe der Umstellung: Gesamtdokument vorher gesichert, nachher verglichen. 168 geaenderte Zeilen, restlos einer Familie zuzuordnen - 118 Loesungsmarken (138 gesamt minus 20, die in den Kapiteln 1 bis 3 zufaellig schon stimmten), 15 Anhang-Ueberschriften, 15 Kapitel-Callouts, 12 Micro-Quiz, 7 Zeilen mit Verweisen, 1 Docstring. Keine verschobene oder inhaltlich veraenderte Zeile. Ergebnis: 138 Aufgaben, 138 Loesungen, keine ohne Gegenstueck. Micro-Quiz tragen die echten Kapitelnummern. 293 Abschnitte, 720 Querverweise, 327 Indexmarken, PDF unveraendert 725 Seiten, 33 pytest-Tests, 67 netzfreie Programme fehlerfrei (die vier yfinance-Programme scheiterten am Rate-Limit des Anbieters, nicht an dieser Aenderung). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 09:52:41 +02:00
<span id="cb9-58"><a href="#cb9-58" aria-hidden="true" tabindex="-1"></a><span class="co"> nachgehen muss (siehe den Denkfehler im Kapitel Quadratische und</span></span>
<span id="cb9-59"><a href="#cb9-59" aria-hidden="true" tabindex="-1"></a><span class="co"> nichtlineare Optimierung).</span></span>
<span id="cb9-60"><a href="#cb9-60" aria-hidden="true" tabindex="-1"></a><span class="co"> &quot;&quot;&quot;</span></span>
<span id="cb9-61"><a href="#cb9-61" aria-hidden="true" tabindex="-1"></a> gewichte <span class="op">=</span> cp.Variable(ANZAHL_ANLAGEN)</span>
<span id="cb9-62"><a href="#cb9-62" aria-hidden="true" tabindex="-1"></a> problem <span class="op">=</span> cp.Problem(cp.Minimize(cp.quad_form(gewichte, cp.psd_wrap(kovarianz))),</span>
<span id="cb9-63"><a href="#cb9-63" aria-hidden="true" tabindex="-1"></a> [cp.<span class="bu">sum</span>(gewichte) <span class="op">==</span> <span class="dv">1</span>])</span>
<span id="cb9-64"><a href="#cb9-64" aria-hidden="true" tabindex="-1"></a> problem.solve()</span>
<span id="cb9-65"><a href="#cb9-65" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> np.array(gewichte.value).ravel(), <span class="bu">max</span>(problem.value, <span class="fl">0.0</span>)</span>
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>
2026-09-08 01:20:09 +02:00
<span id="cb9-66"><a href="#cb9-66" aria-hidden="true" tabindex="-1"></a></span>
Sechster und siebter Fund: die letzten handgeschriebenen Nummern Der Loesungsanhang trug 98 Marken der Bauart "**9.2 — Ansatz waehlen.**", saemtlich aus Version 03, wo Unsicherheit Kapitel 9 war. Heute ist es Kapitel 12, und die Aufgabe wird korrekt als "Aufgabe 12.2" gesetzt. Das war keine Schoenheitsfrage: "Aufgabe 9.2" existiert wirklich - sie gehoert zum Kapitel Metaheuristiken ("Zuggroesse und Temperatur"). Wer die Loesung zu 9.2 nachschlug, landete beim falschen Thema. Beide bisherigen Pruefungen liefen vorbei: Vor der Zahl steht kein Schluesselwort, und es ist keine Tabellenzelle. Zwei Messungen machten die Reparatur billig. In allen 15 nummerierten Abschnitten gab es exakt so viele Loesungen wie Aufgaben, lueckenlos 1..n - nur der Kapitelteil war falsch. Und die acht in Phase 3 ergaenzten Kapitel machten es laengst richtig (**Titel.** ohne Nummer); der Anhang wurde also nicht auf etwas Neues umgestellt, sondern auf das, was seine neueren Teile schon taten. nummeriere_marken() vergibt die Marken jetzt selbst. Der Praefix kommt NICHT aus "# Anhang A:" - der Anhang ist eine Ueberschrift, seine Abschnitte gehoeren aber zu 23 verschiedenen Kapiteln. Er kommt aus {#sec:loesungen-X} -> {#kap:X}; diese Zuordnung gilt geprueft fuer alle 23. Im Quelltext steht "**{loesung} — Titel.**". Siebter Fund unterwegs: neun Verweise auf Denkfehler-Nummern. Saetze wie "siehe Denkfehler 8.1" standen in alter Zaehlung - "8" war in Version 03 das QP/NLP-Kapitel, heute ist es Graphen. Eine blosse Umnummerierung haette sie stillschweigend woanders hin zeigen lassen; das Ziel wurde deshalb fuer jeden einzelnen aus dem Zusammenhang bestimmt und auf {ref:sec:<kapitel>-denkfehler} umgestellt. Einer steckte in einem Programm-Docstring und bekam nach Regel 12 den Kapitelnamen statt eines Verweises. Eine bewusste Abweichung vom Plan: Der Denkfehler bekommt GAR KEINE Nummer. Es gibt je Kapitel genau einen, die zweite Stelle waere immer .1, und er steht ohnehin unter einer nummerierten Ueberschrift. Eine Nummer, die nie variiert, holte nur die Fragilitaet zurueck, die hier beseitigt wird. Auch das folgt den acht neuen Kapiteln, die ihren Denkfehler schon vorher nur mit dem Titel des Raetsels ankuendigen. --check bewacht jetzt alle Familien (Denkfehler N.M, Micro-Quiz N, **N.M — im Anhang) und zaehlt zusaetzlich ab, dass jeder Loesungsabschnitt so viele {loesung}-Marken hat wie sein Kapitel Aufgaben - das faengt eine vergessene oder doppelte Loesung, was keine Textsuche leisten kann. Gegengetestet mit vier kuenstlichen Fehlern auf einmal: alle vier gemeldet, nach dem Rueckbau null. Gegenprobe der Umstellung: Gesamtdokument vorher gesichert, nachher verglichen. 168 geaenderte Zeilen, restlos einer Familie zuzuordnen - 118 Loesungsmarken (138 gesamt minus 20, die in den Kapiteln 1 bis 3 zufaellig schon stimmten), 15 Anhang-Ueberschriften, 15 Kapitel-Callouts, 12 Micro-Quiz, 7 Zeilen mit Verweisen, 1 Docstring. Keine verschobene oder inhaltlich veraenderte Zeile. Ergebnis: 138 Aufgaben, 138 Loesungen, keine ohne Gegenstueck. Micro-Quiz tragen die echten Kapitelnummern. 293 Abschnitte, 720 Querverweise, 327 Indexmarken, PDF unveraendert 725 Seiten, 33 pytest-Tests, 67 netzfreie Programme fehlerfrei (die vier yfinance-Programme scheiterten am Rate-Limit des Anbieters, nicht an dieser Aenderung). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 09:52:41 +02:00
<span id="cb9-67"><a href="#cb9-67" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-68"><a href="#cb9-68" aria-hidden="true" tabindex="-1"></a><span class="cf">if</span> <span class="va">__name__</span> <span class="op">==</span> <span class="st">&quot;__main__&quot;</span>:</span>
<span id="cb9-69"><a href="#cb9-69" aria-hidden="true" tabindex="-1"></a> schaetzzeitraum <span class="op">=</span> erzeuge_renditen(BEOBACHTUNGEN_SCHAETZUNG)</span>
<span id="cb9-70"><a href="#cb9-70" aria-hidden="true" tabindex="-1"></a> spaeterer_verlauf <span class="op">=</span> erzeuge_renditen(BEOBACHTUNGEN_TEST)</span>
<span id="cb9-71"><a href="#cb9-71" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-72"><a href="#cb9-72" aria-hidden="true" tabindex="-1"></a> stichprobe <span class="op">=</span> np.cov(schaetzzeitraum, rowvar<span class="op">=</span><span class="va">False</span>)</span>
<span id="cb9-73"><a href="#cb9-73" aria-hidden="true" tabindex="-1"></a> ledoit_wolf <span class="op">=</span> LedoitWolf().fit(schaetzzeitraum)</span>
<span id="cb9-74"><a href="#cb9-74" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-75"><a href="#cb9-75" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">78</span>)</span>
<span id="cb9-76"><a href="#cb9-76" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; DIE KOVARIANZ-FALLE&quot;</span>)</span>
<span id="cb9-77"><a href="#cb9-77" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">78</span>)</span>
<span id="cb9-78"><a href="#cb9-78" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;</span><span class="sc">{</span>ANZAHL_ANLAGEN<span class="sc">}</span><span class="ss"> Anlagen, geschaetzt aus </span><span class="sc">{</span>BEOBACHTUNGEN_SCHAETZUNG<span class="sc">}</span><span class="ss"> &quot;</span></span>
<span id="cb9-79"><a href="#cb9-79" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;Beobachtungen.&quot;</span>)</span>
<span id="cb9-80"><a href="#cb9-80" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;Rang der Stichprobenmatrix: &quot;</span></span>
<span id="cb9-81"><a href="#cb9-81" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;</span><span class="sc">{</span>np<span class="sc">.</span>linalg<span class="sc">.</span>matrix_rank(stichprobe)<span class="sc">}</span><span class="ss"> - noetig waeren </span><span class="sc">{</span>ANZAHL_ANLAGEN<span class="sc">}</span><span class="ss">.&quot;</span>)</span>
<span id="cb9-82"><a href="#cb9-82" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;Kleinster Eigenwert: </span><span class="sc">{</span>np<span class="sc">.</span>linalg<span class="sc">.</span>eigvalsh(stichprobe)<span class="sc">.</span><span class="bu">min</span>()<span class="sc">:.2e}</span><span class="ss">&quot;</span>)</span>
<span id="cb9-83"><a href="#cb9-83" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>()</span>
<span id="cb9-84"><a href="#cb9-84" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;Es gibt also Richtungen, in denen diese Matrix EXAKT null Varianz&quot;</span>)</span>
<span id="cb9-85"><a href="#cb9-85" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;misst. Genau dort sucht ein Minimum-Varianz-Optimierer.&quot;</span>)</span>
<span id="cb9-86"><a href="#cb9-86" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>()</span>
<span id="cb9-87"><a href="#cb9-87" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-88"><a href="#cb9-88" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;</span><span class="sc">{</span><span class="st">&#39;Schaetzer&#39;</span><span class="sc">:&lt;26}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;Risiko im Zeitraum&#39;</span><span class="sc">:&gt;19}</span><span class="ss"> &quot;</span></span>
<span id="cb9-89"><a href="#cb9-89" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;</span><span class="sc">{</span><span class="st">&#39;Risiko spaeter&#39;</span><span class="sc">:&gt;16}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;Hebel&#39;</span><span class="sc">:&gt;7}</span><span class="ss">&quot;</span>)</span>
<span id="cb9-90"><a href="#cb9-90" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;-&quot;</span> <span class="op">*</span> <span class="dv">78</span>)</span>
<span id="cb9-91"><a href="#cb9-91" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-92"><a href="#cb9-92" aria-hidden="true" tabindex="-1"></a> ergebnisse <span class="op">=</span> {}</span>
<span id="cb9-93"><a href="#cb9-93" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> name, matrix <span class="kw">in</span> [</span>
<span id="cb9-94"><a href="#cb9-94" aria-hidden="true" tabindex="-1"></a> (<span class="st">&quot;Stichprobe (roh)&quot;</span>, stichprobe),</span>
<span id="cb9-95"><a href="#cb9-95" aria-hidden="true" tabindex="-1"></a> (<span class="ss">f&quot;Ledoit-Wolf (d=</span><span class="sc">{</span>ledoit_wolf<span class="sc">.</span>shrinkage_<span class="sc">:.2f}</span><span class="ss">)&quot;</span>, ledoit_wolf.covariance_)]:</span>
<span id="cb9-96"><a href="#cb9-96" aria-hidden="true" tabindex="-1"></a> gewichte, varianz_intern <span class="op">=</span> minimales_risiko(matrix)</span>
<span id="cb9-97"><a href="#cb9-97" aria-hidden="true" tabindex="-1"></a> risiko_spaeter <span class="op">=</span> <span class="bu">float</span>(np.std(spaeterer_verlauf <span class="op">@</span> gewichte))</span>
<span id="cb9-98"><a href="#cb9-98" aria-hidden="true" tabindex="-1"></a> hebel <span class="op">=</span> <span class="bu">float</span>(np.<span class="bu">abs</span>(gewichte).<span class="bu">sum</span>())</span>
<span id="cb9-99"><a href="#cb9-99" aria-hidden="true" tabindex="-1"></a> ergebnisse[name] <span class="op">=</span> (risiko_spaeter, gewichte)</span>
<span id="cb9-100"><a href="#cb9-100" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;</span><span class="sc">{</span>name<span class="sc">:&lt;26}</span><span class="ss"> </span><span class="sc">{</span>np<span class="sc">.</span>sqrt(varianz_intern) <span class="op">*</span> <span class="dv">100</span><span class="sc">:&gt;18.3f}</span><span class="ss"> % &quot;</span></span>
<span id="cb9-101"><a href="#cb9-101" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;</span><span class="sc">{</span>risiko_spaeter <span class="op">*</span> <span class="dv">100</span><span class="sc">:&gt;15.3f}</span><span class="ss"> % </span><span class="sc">{</span>hebel<span class="sc">:&gt;7.1f}</span><span class="ss">&quot;</span>)</span>
<span id="cb9-102"><a href="#cb9-102" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-103"><a href="#cb9-103" aria-hidden="true" tabindex="-1"></a> gleich <span class="op">=</span> np.full(ANZAHL_ANLAGEN, <span class="fl">1.0</span> <span class="op">/</span> ANZAHL_ANLAGEN)</span>
<span id="cb9-104"><a href="#cb9-104" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;</span><span class="sc">{</span><span class="st">&#39;Gleichgewichtung 1/N&#39;</span><span class="sc">:&lt;26}</span><span class="ss"> &quot;</span></span>
<span id="cb9-105"><a href="#cb9-105" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;</span><span class="sc">{</span><span class="bu">float</span>(np.std(schaetzzeitraum <span class="op">@</span> gleich)) <span class="op">*</span> <span class="dv">100</span><span class="sc">:&gt;18.3f}</span><span class="ss"> % &quot;</span></span>
<span id="cb9-106"><a href="#cb9-106" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;</span><span class="sc">{</span><span class="bu">float</span>(np.std(spaeterer_verlauf <span class="op">@</span> gleich)) <span class="op">*</span> <span class="dv">100</span><span class="sc">:&gt;15.3f}</span><span class="ss"> % </span><span class="sc">{</span><span class="fl">1.0</span><span class="sc">:&gt;7.1f}</span><span class="ss">&quot;</span>)</span>
<span id="cb9-107"><a href="#cb9-107" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;-&quot;</span> <span class="op">*</span> <span class="dv">78</span>)</span>
<span id="cb9-108"><a href="#cb9-108" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-109"><a href="#cb9-109" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;</span><span class="ch">\n</span><span class="st">Drei Beobachtungen:&quot;</span>)</span>
<span id="cb9-110"><a href="#cb9-110" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;1. Die rohe Schaetzung meldet 0,000 % Risiko - und liefert spaeter das&quot;</span>)</span>
<span id="cb9-111"><a href="#cb9-111" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; SCHLECHTESTE Ergebnis der drei. Sie hat kein Portfolio optimiert,&quot;</span>)</span>
<span id="cb9-112"><a href="#cb9-112" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; sondern eine Luecke in den eigenen Daten gefunden.&quot;</span>)</span>
<span id="cb9-113"><a href="#cb9-113" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;2. Die Gleichgewichtung, die gar nicht optimiert, schlaegt den rohen&quot;</span>)</span>
<span id="cb9-114"><a href="#cb9-114" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; Optimierer deutlich. Das ist kein Zufallsbefund, sondern in der&quot;</span>)</span>
<span id="cb9-115"><a href="#cb9-115" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; Literatur breit belegt.&quot;</span>)</span>
<span id="cb9-116"><a href="#cb9-116" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;3. Ledoit-Wolf ist im Schaetzzeitraum EHRLICHER (0,441 statt 0,000 %)&quot;</span>)</span>
<span id="cb9-117"><a href="#cb9-117" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; und spaeter am besten. Der Hebel faellt von 3,8 auf 1,8 - das&quot;</span>)</span>
<span id="cb9-118"><a href="#cb9-118" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; Portfolio wird auch praktisch handelbarer.&quot;</span>)</span>
<span id="cb9-119"><a href="#cb9-119" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>()</span>
<span id="cb9-120"><a href="#cb9-120" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;Merksatz: Ein Optimierer glaubt seinen Eingabedaten vollstaendig.&quot;</span>)</span>
<span id="cb9-121"><a href="#cb9-121" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;Je mehr Freiheit Sie ihm geben, desto gruendlicher findet er deren&quot;</span>)</span>
<span id="cb9-122"><a href="#cb9-122" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;Fehler. Das gilt fuer Kovarianzmatrizen wie fuer Lieferzeiten,&quot;</span>)</span>
<span id="cb9-123"><a href="#cb9-123" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;Ausfallraten und jede andere geschaetzte Groesse.&quot;</span>)</span>
<span id="cb9-124"><a href="#cb9-124" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">78</span>)</span></code></pre></div>
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>
2026-09-08 01:20:09 +02:00
<p><strong>Erwartete Ausgabe:</strong></p>
<pre><code>==============================================================================
DIE KOVARIANZ-FALLE
==============================================================================
30 Anlagen, geschaetzt aus 20 Beobachtungen.
Rang der Stichprobenmatrix: 19 - noetig waeren 30.
Kleinster Eigenwert: -6.47e-20
Es gibt also Richtungen, in denen diese Matrix EXAKT null Varianz
misst. Genau dort sucht ein Minimum-Varianz-Optimierer.
Schaetzer Risiko im Zeitraum Risiko spaeter Hebel
------------------------------------------------------------------------------
Stichprobe (roh) 0.000 % 1.287 % 3.8
Ledoit-Wolf (d=0.34) 0.441 % 0.974 % 1.8
Gleichgewichtung 1/N 0.808 % 1.026 % 1.0
------------------------------------------------------------------------------
Drei Beobachtungen:
1. Die rohe Schaetzung meldet 0,000 % Risiko - und liefert spaeter das
SCHLECHTESTE Ergebnis der drei. Sie hat kein Portfolio optimiert,
sondern eine Luecke in den eigenen Daten gefunden.
2. Die Gleichgewichtung, die gar nicht optimiert, schlaegt den rohen
Optimierer deutlich. Das ist kein Zufallsbefund, sondern in der
Literatur breit belegt.
3. Ledoit-Wolf ist im Schaetzzeitraum EHRLICHER (0,441 statt 0,000 %)
und spaeter am besten. Der Hebel faellt von 3,8 auf 1,8 - das
Portfolio wird auch praktisch handelbarer.
Merksatz: Ein Optimierer glaubt seinen Eingabedaten vollstaendig.
Je mehr Freiheit Sie ihm geben, desto gruendlicher findet er deren
Fehler. Das gilt fuer Kovarianzmatrizen wie fuer Lieferzeiten,
Ausfallraten und jede andere geschaetzte Groesse.
==============================================================================</code></pre>
<div class="card card-formel">
<blockquote>
<p><strong>🔤 Formel-Übersetzer: warum <span class="math inline">N &gt; T</span> alles kaputt macht</strong></p>
<table>
<colgroup>
<col style="width: 50%" />
<col style="width: 50%" />
</colgroup>
<thead>
<tr class="header">
<th>Mathematik</th>
<th>Alltagssprache</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><span class="math inline">\mathbf{S} = \frac{1}{T-1}\sum_{t}(\mathbf{r}_t - \bar{\mathbf{r}})(\mathbf{r}_t - \bar{\mathbf{r}})^\top</span></td>
<td>„Wie stark schwanken die Anlagen — und wie sehr gemeinsam?“</td>
</tr>
<tr class="even">
<td><span class="math inline">\operatorname{rang}(\mathbf{S}) \le T - 1</span></td>
<td>„Aus 20 Beobachtungen lassen sich höchstens 19 unabhängige Richtungen ablesen.“</td>
</tr>
<tr class="odd">
<td><span class="math inline">N = 30 &gt; 19</span></td>
<td>„Es bleiben 11 Richtungen übrig, über die die Daten <strong>nichts</strong> sagen.“</td>
</tr>
<tr class="even">
<td>$,</td>
<td></td>
</tr>
</tbody>
</table>
</blockquote>
</div>
<p>e 0: ^ op = 0$ | „In genau diesen Richtungen misst die Matrix null Risiko — nicht weil keines da ist, sondern weil sie blind ist.“ | &gt; | <span class="math inline">\min_{\mathbf{w}} \mathbf{w}^ op\mathbf{S}\mathbf{w}</span> | „Und der Optimierer sucht zielsicher genau dort.“ | &gt; &gt; <strong>Die Faustregel:</strong> Für eine halbwegs verlässliche Kovarianzschätzung braucht man <span class="math inline">T \gtrsim 10 \cdot N</span> Beobachtungen. Bei 30 Titeln also rund 300 Perioden — mehr als ein Jahr Tagesdaten. Wer weniger hat, <strong>muss</strong> schrumpfen.</p>
<hr />
<h2 id="sec:finanzdaten-quiz">18.9 Micro-Quiz</h2>
<div class="card card-quiz">
<blockquote>
Sechster und siebter Fund: die letzten handgeschriebenen Nummern Der Loesungsanhang trug 98 Marken der Bauart "**9.2 — Ansatz waehlen.**", saemtlich aus Version 03, wo Unsicherheit Kapitel 9 war. Heute ist es Kapitel 12, und die Aufgabe wird korrekt als "Aufgabe 12.2" gesetzt. Das war keine Schoenheitsfrage: "Aufgabe 9.2" existiert wirklich - sie gehoert zum Kapitel Metaheuristiken ("Zuggroesse und Temperatur"). Wer die Loesung zu 9.2 nachschlug, landete beim falschen Thema. Beide bisherigen Pruefungen liefen vorbei: Vor der Zahl steht kein Schluesselwort, und es ist keine Tabellenzelle. Zwei Messungen machten die Reparatur billig. In allen 15 nummerierten Abschnitten gab es exakt so viele Loesungen wie Aufgaben, lueckenlos 1..n - nur der Kapitelteil war falsch. Und die acht in Phase 3 ergaenzten Kapitel machten es laengst richtig (**Titel.** ohne Nummer); der Anhang wurde also nicht auf etwas Neues umgestellt, sondern auf das, was seine neueren Teile schon taten. nummeriere_marken() vergibt die Marken jetzt selbst. Der Praefix kommt NICHT aus "# Anhang A:" - der Anhang ist eine Ueberschrift, seine Abschnitte gehoeren aber zu 23 verschiedenen Kapiteln. Er kommt aus {#sec:loesungen-X} -> {#kap:X}; diese Zuordnung gilt geprueft fuer alle 23. Im Quelltext steht "**{loesung} — Titel.**". Siebter Fund unterwegs: neun Verweise auf Denkfehler-Nummern. Saetze wie "siehe Denkfehler 8.1" standen in alter Zaehlung - "8" war in Version 03 das QP/NLP-Kapitel, heute ist es Graphen. Eine blosse Umnummerierung haette sie stillschweigend woanders hin zeigen lassen; das Ziel wurde deshalb fuer jeden einzelnen aus dem Zusammenhang bestimmt und auf {ref:sec:<kapitel>-denkfehler} umgestellt. Einer steckte in einem Programm-Docstring und bekam nach Regel 12 den Kapitelnamen statt eines Verweises. Eine bewusste Abweichung vom Plan: Der Denkfehler bekommt GAR KEINE Nummer. Es gibt je Kapitel genau einen, die zweite Stelle waere immer .1, und er steht ohnehin unter einer nummerierten Ueberschrift. Eine Nummer, die nie variiert, holte nur die Fragilitaet zurueck, die hier beseitigt wird. Auch das folgt den acht neuen Kapiteln, die ihren Denkfehler schon vorher nur mit dem Titel des Raetsels ankuendigen. --check bewacht jetzt alle Familien (Denkfehler N.M, Micro-Quiz N, **N.M — im Anhang) und zaehlt zusaetzlich ab, dass jeder Loesungsabschnitt so viele {loesung}-Marken hat wie sein Kapitel Aufgaben - das faengt eine vergessene oder doppelte Loesung, was keine Textsuche leisten kann. Gegengetestet mit vier kuenstlichen Fehlern auf einmal: alle vier gemeldet, nach dem Rueckbau null. Gegenprobe der Umstellung: Gesamtdokument vorher gesichert, nachher verglichen. 168 geaenderte Zeilen, restlos einer Familie zuzuordnen - 118 Loesungsmarken (138 gesamt minus 20, die in den Kapiteln 1 bis 3 zufaellig schon stimmten), 15 Anhang-Ueberschriften, 15 Kapitel-Callouts, 12 Micro-Quiz, 7 Zeilen mit Verweisen, 1 Docstring. Keine verschobene oder inhaltlich veraenderte Zeile. Ergebnis: 138 Aufgaben, 138 Loesungen, keine ohne Gegenstueck. Micro-Quiz tragen die echten Kapitelnummern. 293 Abschnitte, 720 Querverweise, 327 Indexmarken, PDF unveraendert 725 Seiten, 33 pytest-Tests, 67 netzfreie Programme fehlerfrei (die vier yfinance-Programme scheiterten am Rate-Limit des Anbieters, nicht an dieser Aenderung). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 09:52:41 +02:00
<p><strong>❓ Micro-Quiz 18: Drei Fragen zum Selbstcheck</strong></p>
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>
2026-09-08 01:20:09 +02:00
<p>Genau eine Antwort ist jeweils richtig. Auflösung in <a href="anhang-loesungen.html#quiz-loesung-finanzdaten">Anhang A</a>.</p>
<p><strong>1. Ein Kurs fällt um 50 % und steigt danach um 50 %. Wo steht er?</strong> (a) Wieder beim Ausgangswert — die Prozente heben sich auf. (b) Bei 75 % des Ausgangswerts. Diskrete Renditen verknüpfen sich multiplikativ, nicht additiv: <span class="math inline">1{,}0 \cdot 0{,}5 \cdot 1{,}5 = 0{,}75</span>. (c) Bei 25 % des Ausgangswerts.</p>
<p><strong>2. Wofür nimmt man logarithmische Renditen, wofür diskrete?</strong> (a) Logarithmische sind grundsätzlich genauer und daher immer vorzuziehen. (b) Logarithmische addieren sich über die <strong>Zeit</strong>, diskrete über die <strong>Anlagen</strong> eines Portfolios. Man wählt nach der Richtung, in der summiert wird. (c) Diskrete für kurze, logarithmische für lange Zeiträume.</p>
<p><strong>3. Sie schätzen eine Kovarianzmatrix für 50 Anlagen aus 40 Wochenrenditen. Was ist zu tun?</strong> (a) Nichts — 40 Beobachtungen sind eine ordentliche Stichprobe. (b) Die Matrix ist singulär und gaukelt dem Optimierer risikofreie Richtungen vor. Entweder mehr Daten beschaffen oder einen Shrinkage-Schätzer verwenden, der positive Definitheit garantiert. (c) Die Anzahl der Anlagen auf 40 reduzieren, dann passt es.</p>
</blockquote>
</div>
<hr />
<h2 id="sec:finanzdaten-selbsttest">18.10 Selbsttest</h2>
<blockquote>
<p>Antworten: <a href="anhang-loesungen.html#selbsttest-loesung-finanzdaten">Anhang A</a>.</p>
</blockquote>
<ol type="1">
<li>Warum addieren sich Log-Renditen über die Zeit, diskrete aber über das Portfolio?</li>
<li>Was besagt der Error-Maximizer-Effekt?</li>
<li>Warum ist <span class="math inline">\mathbf{S}</span> bei <span class="math inline">T &lt; N</span> singulär, und was folgt daraus praktisch?</li>
<li>Was mischt Ledoit-Wolf, und wie wird <span class="math inline">\delta</span> bestimmt?</li>
<li>Welche zwei Shrinkage-Ziele gibt es, und welches verwendet <code>scikit-learn</code>?</li>
</ol>
<hr />
<h2 id="sec:finanzdaten-zusammenfassung">18.11 Zusammenfassung</h2>
<ul>
<li><strong>Diskret über das Portfolio, logarithmisch über die Zeit</strong> — die Wahl der Renditeart ist keine Geschmacksfrage.</li>
<li><strong>Das arithmetische Mittel diskreter Renditen überschätzt</strong> die tatsächliche Wertentwicklung systematisch.</li>
<li><strong>Die Stichprobenkovarianz ist bei <span class="math inline">T \approx N</span> unbrauchbar</strong> und bei <span class="math inline">T &lt; N</span> singulär.</li>
<li><strong>Markowitz verstärkt Schätzfehler</strong>, statt sie auszugleichen — der Error-Maximizer-Effekt.</li>
<li><strong>Shrinkage</strong> mischt Rauschen mit Struktur und liefert eine stabil invertierbare Matrix. Es gibt mehrere Ziele; <code>sklearn</code> verwendet die skalierte Einheitsmatrix.</li>
<li><strong>Erzwingen Sie die Spaltenreihenfolge</strong> nach jedem Datenimport und prüfen Sie sie per <code>assert</code>.</li>
<li><strong>Faustregel für die Datenmenge:</strong> <span class="math inline">T \gtrsim 10 \cdot N</span>. Bei 30 Titeln also rund 300 Perioden. Wer weniger hat, muss schrumpfen — oder die Freiheit des Optimierers begrenzen.</li>
<li><strong>Bewerten Sie außerhalb des Schätzzeitraums.</strong> Ein Portfolio, das im eigenen Datenfenster 0,000 % Risiko meldet, ist kein gutes Portfolio, sondern ein Beleg dafür, dass die Daten nicht ausreichen.</li>
<li><strong>Das gilt weit über Finanzdaten hinaus.</strong> Bearbeitungszeiten, Ausfallraten, Nachfrageprognosen: Ein Optimierer sucht nicht die beste Lösung, sondern den größten Fehler in Ihren Schätzungen.</li>
</ul>
<p><strong>Ausblick.</strong> Mit stabilen Schätzungen können wir in <a href="markowitz.html#kap-markowitz">Kapitel 19</a> endlich optimieren: die Markowitz-Effizienzgrenze mit realistischen institutionellen Restriktionen.</p>
</article>
<button type="button" class="fortschritt-knopf" data-kapitel="finanzdaten.html"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg> <span>Als gelesen markieren</span></button>
<nav class="prev-next"><a class="prev-next-knopf prev-next-prev" href="supplychain.html"><svg class="icon" aria-hidden="true"><use href="#icon-chevron-left"></use></svg><span><small>Zurück</small>Kapitel 17: Supply-Chain und Energieeinsatz unter Unsicherheit</span></a><a class="prev-next-knopf prev-next-next" href="markowitz.html"><span><small>Weiter</small>Kapitel 19: Die moderne Portfoliotheorie nach Markowitz</span><svg class="icon" aria-hidden="true"><use href="#icon-chevron-right"></use></svg></a></nav>
</main>
</div>
<footer class="site-footer">
<p>© Dieter Schlüter · <a href="gesamtdokument.html">Gesamtdokument</a> ·
<a href="programme.html">Beispielprogramme</a></p>
</footer>
<script defer src="assets/search-index.js"></script>
<script defer src="assets/programme.js"></script>
<script defer src="assets/site.js"></script>
</body>
</html>