operations_research/OR_HTML_04/handelsmaschine.html
dschlueter 47d63dfe9c Menue: ZIP-Punkt nicht mehr eingerueckt, "(eine HTML-Seite)"
Zwei Beschriftungsaenderungen in baue_sidebar_html():

* "Download Notebooks als ZIP" steht jetzt auf gleicher Hoehe wie die
  uebrigen Punkte statt eingerueckt unter "Notebooks".
* "Gesamtdokument (eine Seite)" -> "(eine HTML-Seite)". Direkt darunter steht
  "Download Buch als PDF"; ohne das Wort HTML liess sich nicht erkennen, dass
  es zwei verschiedene Formate sind.

Mit der Einrueckung faellt ihre Ursache weg: Die CSS-Klasse
.sidebar-unterpunkt in web_04/assets/site.css war eigens dafuer angelegt
worden und wird von nichts mehr benutzt - entfernt, statt als tote Regel
stehen zu bleiben.

Veroeffentlicht: 42 Dateien uebertragen (die Seiten, nichts sonst - dank
--checksum), 0 geloescht, alle vierzehn HTTP-Proben richtig. Gegenprobe vom
Server: kein sidebar-unterpunkt mehr im ausgelieferten HTML.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 19:24:54 +02:00

1081 lines
152 KiB
HTML
Generated
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!doctype html>
<html lang="de">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Kapitel 21: Die vollständige quantitative Handelsmaschine · 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>
<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 Dynamik</summary><ul><li data-kapitel="qp-nlp.html"><a href="qp-nlp.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 11: Quadratische und nichtlineare Optimierung — KKT, Lagrange, Konvexität</span></a></li><li data-kapitel="unsicherheit.html"><a href="unsicherheit.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 12: Optimierung unter Unsicherheit — Monte-Carlo, Stochastik, Robustheit</span></a></li><li data-kapitel="dynamische-programmierung.html"><a href="dynamische-programmierung.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 13: Dynamische Programmierung — Die Bellman-Gleichung und Order-Execution</span></a></li><li data-kapitel="mehrziel.html"><a href="mehrziel.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 14: Mehrere Ziele — Pareto-Fronten statt Gewichte</span></a></li><li data-kapitel="prognose.html"><a href="prognose.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 15: Predict-then-Optimize — die bessere Prognose, die schlechtere Entscheidung</span></a></li><li data-kapitel="synthese-nichtlinear.html"><a href="synthese-nichtlinear.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Synthese Teil III</span></a></li></ul></details><details class="sidebar-gruppe" open><summary>Teil IV: Anwendungen — Energiewirtschaft und Finanzmärkte</summary><ul><li data-kapitel="bruecke.html"><a href="bruecke.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 16: Die Strukturbrücke — dieselbe Mathematik, zwei Welten</span></a></li><li data-kapitel="supplychain.html"><a href="supplychain.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 17: Supply-Chain und Energieeinsatz unter Unsicherheit</span></a></li><li data-kapitel="finanzdaten.html"><a href="finanzdaten.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 18: Finanzdaten-Modellierung — Renditen, Kovarianz und Shrinkage</span></a></li><li data-kapitel="markowitz.html"><a href="markowitz.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 19: Die moderne Portfoliotheorie nach Markowitz</span></a></li><li data-kapitel="cvar.html"><a href="cvar.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 20: Tail-Risiko, CVaR und Transaktionskosten</span></a></li><li data-kapitel="handelsmaschine.html" class="aktiv"><a href="handelsmaschine.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 21: Die vollständige quantitative Handelsmaschine</span></a></li><li data-kapitel="synthese-anwendungen.html"><a href="synthese-anwendungen.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Synthese Teil IV</span></a></li></ul></details><details class="sidebar-gruppe"><summary>Teil V: Praxis</summary><ul><li data-kapitel="praxisfallen.html"><a href="praxisfallen.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 22: Praxisfallen und der Weg zum produktiven Einsatz</span></a></li><li data-kapitel="testing.html"><a href="testing.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 23: Testen, Messen, Ausliefern</span></a></li><li data-kapitel="synthese-praxis.html"><a href="synthese-praxis.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Synthese Teil V</span></a></li><li data-kapitel="projektwerkstatt.html"><a href="projektwerkstatt.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Projektwerkstatt</span></a></li></ul></details><details class="sidebar-gruppe"><summary>Anhänge</summary><ul><li data-kapitel="anhang-loesungen.html"><a href="anhang-loesungen.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Anhang A: Lösungen zu allen Übungsaufgaben</span></a></li><li data-kapitel="anhang-modellierungsmuster.html"><a href="anhang-modellierungsmuster.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Anhang B: Katalog der Modellierungsmuster</span></a></li><li data-kapitel="anhang-fehlerdiagnose.html"><a href="anhang-fehlerdiagnose.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Anhang C: Fehlerdiagnose-Handbuch</span></a></li><li data-kapitel="anhang-spickzettel.html"><a href="anhang-spickzettel.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Anhang D: Spickzettel der Solver</span></a></li><li data-kapitel="anhang-glossar.html"><a href="anhang-glossar.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Anhang E: Glossar</span></a></li><li data-kapitel="anhang-literatur.html"><a href="anhang-literatur.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Anhang F: Literaturverzeichnis</span></a></li></ul></details><ul class="sidebar-extra"><li><a href="programme.html"><svg class="icon" aria-hidden="true"><use href="#icon-book"></use></svg> Beispielprogramme</a></li><li><a href="notebooks.html"><svg class="icon" aria-hidden="true"><use href="#icon-book"></use></svg> Notebooks</a></li><li><a href="Notebooks_04.zip" download><svg class="icon" aria-hidden="true"><use href="#icon-download"></use></svg> Download Notebooks als ZIP</a></li><li><a href="anhang-glossar.html"><svg class="icon" aria-hidden="true"><use href="#icon-book"></use></svg> Glossar</a></li><li><a href="stichwortverzeichnis.html"><svg class="icon" aria-hidden="true"><use href="#icon-search"></use></svg> Stichwortverzeichnis</a></li><li><a href="gesamtdokument.html"><svg class="icon" aria-hidden="true"><use href="#icon-external-link"></use></svg> Gesamtdokument (eine HTML-Seite)</a></li><li><a href="Operations_Research_mit_Python_Version_04.pdf" download><svg class="icon" aria-hidden="true"><use href="#icon-download"></use></svg> Download Buch als PDF</a></li></ul></div></nav>
<main class="content">
<nav class="breadcrumb" aria-label="Breadcrumb"><a href="index.html">Start</a> <span>Teil IV</span> <span>Kapitel 21: Die vollständige quantitative Handelsmaschine</span></nav>
<nav class="prev-next"><a class="prev-next-knopf prev-next-prev" href="cvar.html"><svg class="icon" aria-hidden="true"><use href="#icon-chevron-left"></use></svg><span><small>Zurück</small>Kapitel 20: Tail-Risiko, CVaR und Transaktionskosten</span></a><a class="prev-next-knopf prev-next-next" href="synthese-anwendungen.html"><span><small>Weiter</small>Synthese Teil IV</span><svg class="icon" aria-hidden="true"><use href="#icon-chevron-right"></use></svg></a></nav>
<article>
<h1 id="kap-handelsmaschine">Kapitel 21: Die vollständige quantitative Handelsmaschine</h1>
<div class="card card-blick">
<blockquote>
<p><strong>📌 Kapitel auf einen Blick</strong></p>
<p><strong>Worum geht es?</strong> Um das Zusammenführen von allem: Datenpipeline, Signal, Optimierung, Rebalancing, Kostenverbuchung und ein Backtest, der nicht lügt.</p>
<p><strong>Voraussetzungen:</strong> <a href="finanzdaten.html#kap-finanzdaten">Kapitel 18</a> bis <a href="cvar.html#kap-cvar">Kapitel 20</a>.</p>
<p><strong>Danach können Sie:</strong> Eine Walk-Forward-Backtest-Architektur bauen, Lookahead-Bias vermeiden, Ergebnisse gegen eine Benchmark bewerten und typische Backtest-Fallen erkennen.</p>
<p><strong>Zeitbedarf:</strong> ca. 6 Stunden.</p>
<p><strong>Programme:</strong><br />
<code>QuantitativeTradingEngine.py</code><br />
<code>Backtest_Fallen.py</code><br />
<code>Data_Snooping.py</code></p>
<p><strong>⚠️ Keine Anlageberatung.</strong> Dieses Kapitel demonstriert Methodik an realen Daten, keine handelbare Strategie. <a href="#sec:handelsmaschine-vom-backtest-zum-betrieb">Abschnitt 21.7</a> erklärt ausführlich, warum ein guter Backtest noch lange keine funktionierende Strategie ist.</p>
<p><strong>Notebook:</strong> <a href="Notebooks_04/handelsmaschine.ipynb">handelsmaschine.ipynb</a> — herunterladen und in Jupyter öffnen, in Colab hochladen oder mit dem Kurs-Image starten</p>
</blockquote>
</div>
<hr />
<h2 id="sec:handelsmaschine-schnellstart">21.1 In 5 Minuten gelöst</h2>
<div class="card card-schnellstart">
<blockquote>
<p><strong>🚀 In 5 Minuten gelöst: Dieselben Tage, ein anderes Schicksal</strong></p>
<p>Zwei Strategien über zehn Jahre. Sie bestehen aus <strong>exakt denselben Tagesrenditen</strong> — nur in anderer Reihenfolge. Bei der einen sind die 250 schlechtesten Tage über den ganzen Zeitraum verstreut, bei der anderen liegen sie am Stück.</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>rng <span class="op">=</span> np.random.default_rng(<span class="dv">1</span>)</span>
<span id="cb1-4"><a href="#cb1-4" aria-hidden="true" tabindex="-1"></a>tage <span class="op">=</span> rng.normal(<span class="fl">0.0005</span>, <span class="fl">0.010</span>, <span class="dv">2520</span>) <span class="co"># zehn Jahre Tagesrenditen</span></span>
<span id="cb1-5"><a href="#cb1-5" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-6"><a href="#cb1-6" aria-hidden="true" tabindex="-1"></a>gestreut <span class="op">=</span> tage.copy()</span>
<span id="cb1-7"><a href="#cb1-7" aria-hidden="true" tabindex="-1"></a>rng.shuffle(gestreut)</span>
<span id="cb1-8"><a href="#cb1-8" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-9"><a href="#cb1-9" aria-hidden="true" tabindex="-1"></a>sortiert <span class="op">=</span> np.sort(tage) <span class="co"># dieselben Tage, neu geordnet:</span></span>
<span id="cb1-10"><a href="#cb1-10" aria-hidden="true" tabindex="-1"></a>schlecht, rest <span class="op">=</span> sortiert[:<span class="dv">250</span>], sortiert[<span class="dv">250</span>:] <span class="co"># die 250 schlechtesten ...</span></span>
<span id="cb1-11"><a href="#cb1-11" aria-hidden="true" tabindex="-1"></a>rng.shuffle(rest)</span>
<span id="cb1-12"><a href="#cb1-12" aria-hidden="true" tabindex="-1"></a>geballt <span class="op">=</span> np.concatenate([rest[:<span class="dv">1000</span>], schlecht, rest[<span class="dv">1000</span>:]]) <span class="co"># ... am Stueck</span></span>
<span id="cb1-13"><a href="#cb1-13" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-14"><a href="#cb1-14" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> kennzahlen(r):</span>
<span id="cb1-15"><a href="#cb1-15" aria-hidden="true" tabindex="-1"></a> verlauf <span class="op">=</span> np.cumprod(<span class="dv">1</span> <span class="op">+</span> r)</span>
<span id="cb1-16"><a href="#cb1-16" aria-hidden="true" tabindex="-1"></a> rueckschlag <span class="op">=</span> (verlauf <span class="op">/</span> np.maximum.accumulate(verlauf) <span class="op">-</span> <span class="dv">1</span>).<span class="bu">min</span>()</span>
<span id="cb1-17"><a href="#cb1-17" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> verlauf[<span class="op">-</span><span class="dv">1</span>]<span class="op">**</span>(<span class="dv">252</span><span class="op">/</span><span class="bu">len</span>(r)) <span class="op">-</span> <span class="dv">1</span>, r.mean()<span class="op">/</span>r.std()<span class="op">*</span>np.sqrt(<span class="dv">252</span>), rueckschlag</span>
<span id="cb1-18"><a href="#cb1-18" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-19"><a href="#cb1-19" aria-hidden="true" tabindex="-1"></a><span class="cf">for</span> name, r <span class="kw">in</span> [(<span class="st">&quot;gestreut&quot;</span>, gestreut), (<span class="st">&quot;geballt&quot;</span>, geballt)]:</span>
<span id="cb1-20"><a href="#cb1-20" aria-hidden="true" tabindex="-1"></a> jahr, sharpe, rueckschlag <span class="op">=</span> kennzahlen(r)</span>
<span id="cb1-21"><a href="#cb1-21" 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">:9}</span><span class="ss"> Jahresrendite </span><span class="sc">{</span>jahr<span class="op">*</span><span class="dv">100</span><span class="sc">:5.2f}</span><span class="ss"> % Sharpe </span><span class="sc">{</span>sharpe<span class="sc">:4.2f}</span><span class="ss"> &quot;</span></span>
<span id="cb1-22"><a href="#cb1-22" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;max. Rueckschlag </span><span class="sc">{</span>rueckschlag<span class="op">*</span><span class="dv">100</span><span class="sc">:6.1f}</span><span class="ss"> %&quot;</span>)</span></code></pre></div>
<p><strong>Ausgabe:</strong></p>
<pre><code>gestreut Jahresrendite 8.88 % Sharpe 0.61 max. Rueckschlag -19.0 %
geballt Jahresrendite 8.88 % Sharpe 0.61 max. Rueckschlag -98.7 %</code></pre>
</blockquote>
</div>
<p><strong>Rendite und Sharpe-Kennzahl sind identisch</strong> — sie müssen es sein, denn beide Reihen enthalten dieselben Zahlen. Nur die Reihenfolge unterscheidet sich.</p>
<p>Der maximale Rückschlag beträgt trotzdem 19,0 % gegen <strong>98,7 %</strong>. Die zweite Strategie verliert zwischenzeitlich fast das gesamte Kapital und erholt sich danach wieder auf denselben Endstand.</p>
<p>Rechnerisch sind beide gleichwertig. Praktisch ist die zweite tot: Bei 98 % gibt es keinen Anleger mehr, kein Mandat und kein Handelsdesk, das die Erholung noch erlebt.</p>
<blockquote>
<p><strong>🎯 Merksatz</strong> Rendite und Sharpe-Kennzahl mitteln über die Zeit und verlieren dabei die <strong>Reihenfolge</strong>. Genau in der Reihenfolge steckt aber, ob eine Strategie überlebt. Der maximale Rückschlag misst nicht das statistische Risiko, sondern das menschliche: <em>Wie schlimm sah es zwischendurch aus?</em> Berichten Sie ihn immer mit — eine Rendite allein ist keine Aussage.</p>
</blockquote>
<p>Dasselbe gilt weit außerhalb der Finanzwelt. Ein Produktionsplan, der im Jahresmittel optimal ist, aber im März drei Wochen lang die Liefertreue reißt, wird im April abgeschaltet — bevor sich der Mittelwert einstellen kann. Wo immer Sie einen Durchschnitt berichten, berichten Sie auch den <strong>schlimmsten Zwischenzustand</strong>.</p>
<hr />
<h2 id="sec:handelsmaschine-lernziele">21.2 Lernziele</h2>
<p>Nach diesem Kapitel können Sie …</p>
<ol type="1">
<li>… die sechs Bausteine einer quantitativen Engine benennen und ihre Reihenfolge begründen.</li>
<li>… einen Walk-Forward-Backtest ohne Lookahead-Bias implementieren.</li>
<li>… Rebalancing-Termine korrekt auf Handelstage abbilden.</li>
<li>… Transaktionskosten und Gewichtsdrift realistisch verbuchen.</li>
<li>… Backtest-Ergebnisse kritisch prüfen und die typischen Selbsttäuschungen erkennen.</li>
<li>… den maximalen Rückschlag als eigenständige Kennzahl berichten und begründen, warum Rendite und Sharpe allein nichts über den Verlauf aussagen.</li>
<li>… die Zahl der ausprobierten Varianten protokollieren und die Signifikanzschwelle entsprechend korrigieren.</li>
</ol>
<hr />
<h2 id="sec:handelsmaschine-die-architektur">21.3 Die Architektur</h2>
<figure>
<img src="bilder_04/kap14_engine_overview.svg" alt="Abb. 21.1: Überblick der quantitativen Engine" />
<figcaption aria-hidden="true">Abb. 21.1: Überblick der quantitativen Engine</figcaption>
</figure>
<table>
<colgroup>
<col style="width: 25%" />
<col style="width: 25%" />
<col style="width: 25%" />
<col style="width: 25%" />
</colgroup>
<thead>
<tr class="header">
<th>#</th>
<th>Baustein</th>
<th>Aufgabe</th>
<th>Kapitel</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>1</td>
<td><strong>Datenpipeline</strong></td>
<td>Kurse laden, bereinigen, Reihenfolge sichern</td>
<td>11</td>
</tr>
<tr class="even">
<td>2</td>
<td><strong>Signal (Alpha)</strong></td>
<td>Renditeerwartung <span class="math inline">\boldsymbol{\mu}</span> schätzen</td>
<td>11</td>
</tr>
<tr class="odd">
<td>3</td>
<td><strong>Risikomodell</strong></td>
<td>Kovarianz bzw. Szenarien bereitstellen</td>
<td>11</td>
</tr>
<tr class="even">
<td>4</td>
<td><strong>Optimierungskern</strong></td>
<td>Gewichte unter Restriktionen bestimmen</td>
<td>12, 13</td>
</tr>
<tr class="odd">
<td>5</td>
<td><strong>Rebalancing</strong></td>
<td>Umsetzen, Kosten verbuchen, Drift fortschreiben</td>
<td>13</td>
</tr>
<tr class="even">
<td>6</td>
<td><strong>Auswertung</strong></td>
<td>Kennzahlen, Benchmark-Vergleich, Diagnose</td>
<td>14</td>
</tr>
</tbody>
</table>
<blockquote>
<p><strong>🎯 Die goldene Regel des Backtestens</strong> <strong>Zum Zeitpunkt <span class="math inline">t</span> darf ausschließlich Information verwendet werden, die zum Zeitpunkt <span class="math inline">t</span> tatsächlich vorlag.</strong> Jede Verletzung — und sei sie einen einzigen Tag groß — macht Ergebnisse systematisch zu gut. Der Fehler ist besonders tückisch, weil er keine Fehlermeldung erzeugt, sondern nur die Rendite verbessert.</p>
</blockquote>
<hr />
<h2 id="sec:handelsmaschine-rebalancing-termine-richtig-bestimmen">21.4 Rebalancing-Termine richtig bestimmen</h2>
<blockquote>
<p><strong>⚠️ Ein stiller, häufiger Fehler</strong></p>
<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="va">self</span>.rebalance_dates <span class="op">=</span> <span class="va">self</span>.prices.resample(<span class="st">&quot;MS&quot;</span>).first().index</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="cf">if</span> current_date <span class="kw">in</span> <span class="va">self</span>.rebalance_dates:</span></code></pre></div>
<p><code>resample("MS")</code> erzeugt <strong>Kalender-Monatsanfänge</strong> — den 1. Januar, den 1. Februar und so fort. Der Kursindex enthält aber nur <strong>Handelstage</strong>. Fällt der Monatserste auf ein Wochenende oder einen Feiertag, ist er kein Element des Index, die Bedingung wird nie wahr, und das Rebalancing <strong>entfällt kommentarlos</strong>.</p>
<p>Messung auf einem reinen Wochentagskalender über fünf Jahre:</p>
<table>
<thead>
<tr class="header">
<th></th>
<th>Termine</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>geplant</td>
<td>61</td>
</tr>
<tr class="even">
<td>tatsächlich ausgeführt</td>
<td>44 (72 %)</td>
</tr>
<tr class="odd">
<td><strong>stillschweigend verworfen</strong></td>
<td><strong>17 (28 %)</strong></td>
</tr>
</tbody>
</table>
<p>Mit echten Börsenfeiertagen fallen weitere Termine aus. Der Backtest testete also eine <strong>andere Strategie als die beschriebene</strong> — und lieferte trotzdem plausibel aussehende Kennzahlen.</p>
<p><strong>Die Korrektur:</strong></p>
<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>monat <span class="op">=</span> <span class="va">self</span>.kurse.index.to_period(<span class="st">&quot;M&quot;</span>)</span>
<span id="cb4-2"><a href="#cb4-2" aria-hidden="true" tabindex="-1"></a><span class="va">self</span>.rebalancing_termine <span class="op">=</span> <span class="va">self</span>.kurse.index[<span class="op">~</span>monat.duplicated()] <span class="co"># erster HANDELStag</span></span>
<span id="cb4-3"><a href="#cb4-3" aria-hidden="true" tabindex="-1"></a><span class="cf">assert</span> <span class="va">self</span>.rebalancing_termine.isin(<span class="va">self</span>.kurse.index).<span class="bu">all</span>()</span></code></pre></div>
<p><code>~monat.duplicated()</code> markiert den jeweils <strong>ersten vorhandenen</strong> Tag jedes Monats — also garantiert einen Handelstag.</p>
</blockquote>
<blockquote>
<p><strong>🎯 Die allgemeine Lehre</strong> <strong>Der gefährlichste Fehler ist der, der nichts kaputt macht.</strong> Ein Absturz wird bemerkt. Ein Backtest, der stillschweigend 28 % seiner Entscheidungen auslässt, wird gelobt. Bauen Sie deshalb in jeden Backtest <strong>Zähler</strong> ein: Wie viele Rebalancings waren geplant, wie viele wurden ausgeführt? Weichen die Zahlen ab, stimmt etwas nicht.</p>
</blockquote>
<hr />
<h2 id="sec:handelsmaschine-die-engine">21.5 Die Engine</h2>
<div class="sourceCode" id="cb5"><pre class="sourceCode python"><code class="sourceCode python"><span id="cb5-1"><a href="#cb5-1" aria-hidden="true" tabindex="-1"></a><span class="co">#!/usr/bin/env python3</span></span>
<span id="cb5-2"><a href="#cb5-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-3"><a href="#cb5-3" aria-hidden="true" tabindex="-1"></a><span class="co"># QuantitativeTradingEngine.py</span></span>
<span id="cb5-4"><a href="#cb5-4" aria-hidden="true" tabindex="-1"></a><span class="co">&quot;&quot;&quot;</span></span>
<span id="cb5-5"><a href="#cb5-5" aria-hidden="true" tabindex="-1"></a><span class="co">Kapitel Handelsmaschine: Vollstaendige quantitative Handelsmaschine mit</span></span>
<span id="cb5-6"><a href="#cb5-6" aria-hidden="true" tabindex="-1"></a><span class="co">Walk-Forward-Backtest und OR-Optimierungsschicht.</span></span>
<span id="cb5-7"><a href="#cb5-7" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-8"><a href="#cb5-8" aria-hidden="true" tabindex="-1"></a><span class="co">Eigenschaften:</span></span>
<span id="cb5-9"><a href="#cb5-9" aria-hidden="true" tabindex="-1"></a><span class="co"> * Rebalancing-Termine sind garantiert Handelstage; die Zahl der</span></span>
<span id="cb5-10"><a href="#cb5-10" aria-hidden="true" tabindex="-1"></a><span class="co"> tatsaechlich ausgefuehrten Umschichtungen wird ausgewiesen</span></span>
<span id="cb5-11"><a href="#cb5-11" aria-hidden="true" tabindex="-1"></a><span class="co"> * Spaltenreihenfolge erzwungen</span></span>
<span id="cb5-12"><a href="#cb5-12" aria-hidden="true" tabindex="-1"></a><span class="co"> * Einheiten konsistent (taegliche Groessen im Modell)</span></span>
<span id="cb5-13"><a href="#cb5-13" aria-hidden="true" tabindex="-1"></a><span class="co"> * Szenario-Nebenbedingungen vektorisiert</span></span>
<span id="cb5-14"><a href="#cb5-14" aria-hidden="true" tabindex="-1"></a><span class="co"> * Lookahead-Selbsttest eingebaut</span></span>
<span id="cb5-15"><a href="#cb5-15" aria-hidden="true" tabindex="-1"></a><span class="co"> * Zusaetzliche Kennzahlen: Calmar, Trefferquote, Turnover, Kostenanteil</span></span>
<span id="cb5-16"><a href="#cb5-16" aria-hidden="true" tabindex="-1"></a><span class="co">&quot;&quot;&quot;</span></span>
<span id="cb5-17"><a href="#cb5-17" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-18"><a href="#cb5-18" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> os</span>
<span id="cb5-19"><a href="#cb5-19" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-20"><a href="#cb5-20" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> cvxpy <span class="im">as</span> cp</span>
<span id="cb5-21"><a href="#cb5-21" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> numpy <span class="im">as</span> np</span>
<span id="cb5-22"><a href="#cb5-22" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> pandas <span class="im">as</span> pd</span>
<span id="cb5-23"><a href="#cb5-23" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> yfinance <span class="im">as</span> yf</span>
<span id="cb5-24"><a href="#cb5-24" aria-hidden="true" tabindex="-1"></a><span class="im">from</span> sklearn.covariance <span class="im">import</span> LedoitWolf</span>
<span id="cb5-25"><a href="#cb5-25" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> matplotlib</span>
<span id="cb5-26"><a href="#cb5-26" aria-hidden="true" tabindex="-1"></a>matplotlib.use(<span class="st">&quot;Agg&quot;</span>)</span>
<span id="cb5-27"><a href="#cb5-27" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> matplotlib.pyplot <span class="im">as</span> plt</span>
<span id="cb5-28"><a href="#cb5-28" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-29"><a href="#cb5-29" aria-hidden="true" tabindex="-1"></a>OUTPUT_DIR <span class="op">=</span> os.path.join(os.path.dirname(os.path.abspath(<span class="va">__file__</span>)), <span class="st">&quot;output&quot;</span>)</span>
<span id="cb5-30"><a href="#cb5-30" aria-hidden="true" tabindex="-1"></a>os.makedirs(OUTPUT_DIR, exist_ok<span class="op">=</span><span class="va">True</span>)</span>
<span id="cb5-31"><a href="#cb5-31" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-32"><a href="#cb5-32" aria-hidden="true" tabindex="-1"></a>HANDELSTAGE <span class="op">=</span> <span class="dv">252</span></span>
<span id="cb5-33"><a href="#cb5-33" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-34"><a href="#cb5-34" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-35"><a href="#cb5-35" aria-hidden="true" tabindex="-1"></a><span class="kw">class</span> QuantitativeHandelsmaschine:</span>
<span id="cb5-36"><a href="#cb5-36" aria-hidden="true" tabindex="-1"></a> <span class="co">&quot;&quot;&quot;Walk-Forward-Backtest mit CVaR-Optimierung und Transaktionskosten.&quot;&quot;&quot;</span></span>
<span id="cb5-37"><a href="#cb5-37" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-38"><a href="#cb5-38" aria-hidden="true" tabindex="-1"></a> <span class="kw">def</span> <span class="fu">__init__</span>(<span class="va">self</span>, universum, benchmark<span class="op">=</span><span class="st">&quot;SPY&quot;</span>, jahre<span class="op">=</span><span class="dv">5</span>,</span>
<span id="cb5-39"><a href="#cb5-39" aria-hidden="true" tabindex="-1"></a> startkapital<span class="op">=</span><span class="fl">100_000.0</span>, gebuehrensatz<span class="op">=</span><span class="fl">0.0015</span>,</span>
<span id="cb5-40"><a href="#cb5-40" aria-hidden="true" tabindex="-1"></a> max_gewicht<span class="op">=</span><span class="fl">0.20</span>, alpha<span class="op">=</span><span class="fl">0.95</span>, risikoaversion<span class="op">=</span><span class="fl">1.2</span>,</span>
<span id="cb5-41"><a href="#cb5-41" aria-hidden="true" tabindex="-1"></a> warmup<span class="op">=</span>HANDELSTAGE):</span>
<span id="cb5-42"><a href="#cb5-42" aria-hidden="true" tabindex="-1"></a> <span class="va">self</span>.universum <span class="op">=</span> <span class="bu">list</span>(universum)</span>
<span id="cb5-43"><a href="#cb5-43" aria-hidden="true" tabindex="-1"></a> <span class="va">self</span>.benchmark <span class="op">=</span> benchmark</span>
<span id="cb5-44"><a href="#cb5-44" aria-hidden="true" tabindex="-1"></a> <span class="va">self</span>.jahre <span class="op">=</span> jahre</span>
<span id="cb5-45"><a href="#cb5-45" aria-hidden="true" tabindex="-1"></a> <span class="va">self</span>.startkapital <span class="op">=</span> startkapital</span>
<span id="cb5-46"><a href="#cb5-46" aria-hidden="true" tabindex="-1"></a> <span class="va">self</span>.gebuehrensatz <span class="op">=</span> gebuehrensatz</span>
<span id="cb5-47"><a href="#cb5-47" aria-hidden="true" tabindex="-1"></a> <span class="va">self</span>.max_gewicht <span class="op">=</span> max_gewicht</span>
<span id="cb5-48"><a href="#cb5-48" aria-hidden="true" tabindex="-1"></a> <span class="va">self</span>.alpha <span class="op">=</span> alpha</span>
<span id="cb5-49"><a href="#cb5-49" aria-hidden="true" tabindex="-1"></a> <span class="va">self</span>.risikoaversion <span class="op">=</span> risikoaversion</span>
<span id="cb5-50"><a href="#cb5-50" aria-hidden="true" tabindex="-1"></a> <span class="va">self</span>.warmup <span class="op">=</span> warmup</span>
<span id="cb5-51"><a href="#cb5-51" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-52"><a href="#cb5-52" aria-hidden="true" tabindex="-1"></a> <span class="va">self</span>.kurse <span class="op">=</span> <span class="va">None</span></span>
<span id="cb5-53"><a href="#cb5-53" aria-hidden="true" tabindex="-1"></a> <span class="va">self</span>.benchmark_kurse <span class="op">=</span> <span class="va">None</span></span>
<span id="cb5-54"><a href="#cb5-54" aria-hidden="true" tabindex="-1"></a> <span class="va">self</span>.renditen <span class="op">=</span> <span class="va">None</span></span>
<span id="cb5-55"><a href="#cb5-55" aria-hidden="true" tabindex="-1"></a> <span class="va">self</span>.rebalancing_termine <span class="op">=</span> <span class="va">None</span></span>
<span id="cb5-56"><a href="#cb5-56" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-57"><a href="#cb5-57" aria-hidden="true" tabindex="-1"></a> <span class="co"># -- 1. Datenpipeline ------------------------------------------------</span></span>
<span id="cb5-58"><a href="#cb5-58" aria-hidden="true" tabindex="-1"></a> <span class="kw">def</span> lade_daten(<span class="va">self</span>):</span>
<span id="cb5-59"><a href="#cb5-59" aria-hidden="true" tabindex="-1"></a> ende <span class="op">=</span> pd.Timestamp.today().normalize()</span>
<span id="cb5-60"><a href="#cb5-60" 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="va">self</span>.jahre)</span>
<span id="cb5-61"><a href="#cb5-61" aria-hidden="true" tabindex="-1"></a> alle <span class="op">=</span> <span class="va">self</span>.universum <span class="op">+</span> [<span class="va">self</span>.benchmark]</span>
<span id="cb5-62"><a href="#cb5-62" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-63"><a href="#cb5-63" 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>(<span class="va">self</span>.universum)<span class="sc">}</span><span class="ss"> Titel + Benchmark (</span><span class="sc">{</span><span class="va">self</span><span class="sc">.</span>benchmark<span class="sc">}</span><span class="ss">) &quot;</span></span>
<span id="cb5-64"><a href="#cb5-64" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;ueber </span><span class="sc">{</span><span class="va">self</span><span class="sc">.</span>jahre<span class="sc">}</span><span class="ss"> Jahre ...&quot;</span>)</span>
<span id="cb5-65"><a href="#cb5-65" aria-hidden="true" tabindex="-1"></a> roh <span class="op">=</span> yf.download(alle, 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="cb5-66"><a href="#cb5-66" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> roh.empty:</span>
<span id="cb5-67"><a href="#cb5-67" aria-hidden="true" tabindex="-1"></a> <span class="cf">raise</span> <span class="pp">SystemExit</span>(<span class="st">&quot;Download fehlgeschlagen (Netz/Ticker/Rate-Limit pruefen).&quot;</span>)</span>
<span id="cb5-68"><a href="#cb5-68" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-69"><a href="#cb5-69" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> <span class="bu">isinstance</span>(roh.columns, pd.MultiIndex):</span>
<span id="cb5-70"><a href="#cb5-70" aria-hidden="true" tabindex="-1"></a> daten <span class="op">=</span> roh[<span class="st">&quot;Close&quot;</span>][alle].dropna() <span class="co"># erzwingt eigene Spaltenreihenfolge</span></span>
<span id="cb5-71"><a href="#cb5-71" aria-hidden="true" tabindex="-1"></a> <span class="cf">else</span>:</span>
<span id="cb5-72"><a href="#cb5-72" aria-hidden="true" tabindex="-1"></a> daten <span class="op">=</span> roh[[<span class="st">&quot;Close&quot;</span>]].dropna()</span>
<span id="cb5-73"><a href="#cb5-73" aria-hidden="true" tabindex="-1"></a> daten.columns <span class="op">=</span> alle</span>
<span id="cb5-74"><a href="#cb5-74" aria-hidden="true" tabindex="-1"></a> <span class="cf">assert</span> <span class="bu">list</span>(daten.columns) <span class="op">==</span> alle, <span class="st">&quot;Spaltenreihenfolge weicht ab!&quot;</span></span>
<span id="cb5-75"><a href="#cb5-75" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-76"><a href="#cb5-76" aria-hidden="true" tabindex="-1"></a> <span class="va">self</span>.kurse <span class="op">=</span> daten[<span class="va">self</span>.universum]</span>
<span id="cb5-77"><a href="#cb5-77" aria-hidden="true" tabindex="-1"></a> <span class="va">self</span>.benchmark_kurse <span class="op">=</span> daten[<span class="va">self</span>.benchmark]</span>
<span id="cb5-78"><a href="#cb5-78" aria-hidden="true" tabindex="-1"></a> <span class="va">self</span>.renditen <span class="op">=</span> <span class="va">self</span>.kurse.pct_change().dropna()</span>
<span id="cb5-79"><a href="#cb5-79" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-80"><a href="#cb5-80" aria-hidden="true" tabindex="-1"></a> <span class="co"># --- erster HANDELStag je Monat, nicht Kalendermonatsanfang -----</span></span>
<span id="cb5-81"><a href="#cb5-81" aria-hidden="true" tabindex="-1"></a> monat <span class="op">=</span> <span class="va">self</span>.kurse.index.to_period(<span class="st">&quot;M&quot;</span>)</span>
<span id="cb5-82"><a href="#cb5-82" aria-hidden="true" tabindex="-1"></a> <span class="va">self</span>.rebalancing_termine <span class="op">=</span> <span class="va">self</span>.kurse.index[<span class="op">~</span>monat.duplicated()]</span>
<span id="cb5-83"><a href="#cb5-83" aria-hidden="true" tabindex="-1"></a> <span class="cf">assert</span> <span class="va">self</span>.rebalancing_termine.isin(<span class="va">self</span>.kurse.index).<span class="bu">all</span>(), <span class="op">\</span></span>
<span id="cb5-84"><a href="#cb5-84" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;Rebalancing-Termine sind keine Handelstage!&quot;</span></span>
<span id="cb5-85"><a href="#cb5-85" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-86"><a href="#cb5-86" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; </span><span class="sc">{</span><span class="bu">len</span>(<span class="va">self</span>.kurse)<span class="sc">}</span><span class="ss"> Handelstage, &quot;</span></span>
<span id="cb5-87"><a href="#cb5-87" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;</span><span class="sc">{</span><span class="bu">len</span>(<span class="va">self</span>.rebalancing_termine)<span class="sc">}</span><span class="ss"> Rebalancing-Termine &quot;</span></span>
<span id="cb5-88"><a href="#cb5-88" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;(alle sind Handelstage)&quot;</span>)</span>
<span id="cb5-89"><a href="#cb5-89" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-90"><a href="#cb5-90" aria-hidden="true" tabindex="-1"></a> <span class="co"># -- 2. Signal --------------------------------------------------------</span></span>
<span id="cb5-91"><a href="#cb5-91" aria-hidden="true" tabindex="-1"></a> <span class="kw">def</span> alpha_signal(<span class="va">self</span>, stichtag, rueckblick<span class="op">=</span>HANDELSTAGE):</span>
<span id="cb5-92"><a href="#cb5-92" aria-hidden="true" tabindex="-1"></a> <span class="co">&quot;&quot;&quot;</span></span>
<span id="cb5-93"><a href="#cb5-93" aria-hidden="true" tabindex="-1"></a><span class="co"> 12-1-Momentum: Kursentwicklung der letzten 12 Monate unter Auslassung</span></span>
<span id="cb5-94"><a href="#cb5-94" aria-hidden="true" tabindex="-1"></a><span class="co"> des letzten Monats (kurzfristige Umkehr herausrechnen).</span></span>
<span id="cb5-95"><a href="#cb5-95" aria-hidden="true" tabindex="-1"></a><span class="co"> Rueckgabe: TAEGLICHE Renditeerwartung.</span></span>
<span id="cb5-96"><a href="#cb5-96" aria-hidden="true" tabindex="-1"></a><span class="co"> &quot;&quot;&quot;</span></span>
<span id="cb5-97"><a href="#cb5-97" aria-hidden="true" tabindex="-1"></a> historie <span class="op">=</span> <span class="va">self</span>.kurse.loc[:stichtag]</span>
<span id="cb5-98"><a href="#cb5-98" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> <span class="bu">len</span>(historie) <span class="op">&lt;</span> rueckblick:</span>
<span id="cb5-99"><a href="#cb5-99" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> np.zeros(<span class="bu">len</span>(<span class="va">self</span>.universum))</span>
<span id="cb5-100"><a href="#cb5-100" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-101"><a href="#cb5-101" aria-hidden="true" tabindex="-1"></a> p_alt <span class="op">=</span> historie.iloc[<span class="op">-</span>rueckblick]</span>
<span id="cb5-102"><a href="#cb5-102" aria-hidden="true" tabindex="-1"></a> p_neu <span class="op">=</span> historie.iloc[<span class="op">-</span><span class="dv">21</span>] <span class="co"># vor ca. einem Monat</span></span>
<span id="cb5-103"><a href="#cb5-103" aria-hidden="true" tabindex="-1"></a> momentum <span class="op">=</span> (p_neu <span class="op">/</span> p_alt <span class="op">-</span> <span class="fl">1.0</span>).values</span>
<span id="cb5-104"><a href="#cb5-104" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-105"><a href="#cb5-105" aria-hidden="true" tabindex="-1"></a> <span class="co"># Z-Score, damit das Signal skalenfrei wird</span></span>
<span id="cb5-106"><a href="#cb5-106" aria-hidden="true" tabindex="-1"></a> streuung <span class="op">=</span> momentum.std()</span>
<span id="cb5-107"><a href="#cb5-107" aria-hidden="true" tabindex="-1"></a> score <span class="op">=</span> (momentum <span class="op">-</span> momentum.mean()) <span class="op">/</span> (streuung <span class="op">+</span> <span class="fl">1e-9</span>)</span>
<span id="cb5-108"><a href="#cb5-108" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-109"><a href="#cb5-109" aria-hidden="true" tabindex="-1"></a> <span class="co"># In eine taegliche Renditeerwartung uebersetzen:</span></span>
<span id="cb5-110"><a href="#cb5-110" aria-hidden="true" tabindex="-1"></a> <span class="co"># Basis 4 % p.a. plus 6 % p.a. je Standardabweichung Momentum.</span></span>
<span id="cb5-111"><a href="#cb5-111" aria-hidden="true" tabindex="-1"></a> mu_jaehrlich <span class="op">=</span> np.maximum(<span class="fl">0.04</span> <span class="op">+</span> <span class="fl">0.06</span> <span class="op">*</span> score, <span class="fl">0.0</span>)</span>
<span id="cb5-112"><a href="#cb5-112" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> mu_jaehrlich <span class="op">/</span> HANDELSTAGE</span>
<span id="cb5-113"><a href="#cb5-113" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-114"><a href="#cb5-114" aria-hidden="true" tabindex="-1"></a> <span class="co"># -- 3./4. Risikomodell und Optimierung -------------------------------</span></span>
<span id="cb5-115"><a href="#cb5-115" aria-hidden="true" tabindex="-1"></a> <span class="kw">def</span> optimiere(<span class="va">self</span>, stichtag, w_alt, rueckblick<span class="op">=</span>HANDELSTAGE):</span>
<span id="cb5-116"><a href="#cb5-116" aria-hidden="true" tabindex="-1"></a> historie <span class="op">=</span> <span class="va">self</span>.renditen.loc[:stichtag].iloc[<span class="op">-</span>rueckblick:]</span>
<span id="cb5-117"><a href="#cb5-117" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> <span class="bu">len</span>(historie) <span class="op">&lt;</span> <span class="dv">100</span>:</span>
<span id="cb5-118"><a href="#cb5-118" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> w_alt, <span class="va">False</span></span>
<span id="cb5-119"><a href="#cb5-119" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-120"><a href="#cb5-120" aria-hidden="true" tabindex="-1"></a> R <span class="op">=</span> historie.values</span>
<span id="cb5-121"><a href="#cb5-121" aria-hidden="true" tabindex="-1"></a> S, N <span class="op">=</span> R.shape</span>
<span id="cb5-122"><a href="#cb5-122" aria-hidden="true" tabindex="-1"></a> mu_taeglich <span class="op">=</span> <span class="va">self</span>.alpha_signal(stichtag, rueckblick)</span>
<span id="cb5-123"><a href="#cb5-123" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-124"><a href="#cb5-124" aria-hidden="true" tabindex="-1"></a> w <span class="op">=</span> cp.Variable(N, nonneg<span class="op">=</span><span class="va">True</span>)</span>
<span id="cb5-125"><a href="#cb5-125" aria-hidden="true" tabindex="-1"></a> gamma <span class="op">=</span> cp.Variable()</span>
<span id="cb5-126"><a href="#cb5-126" aria-hidden="true" tabindex="-1"></a> u <span class="op">=</span> cp.Variable(S, nonneg<span class="op">=</span><span class="va">True</span>)</span>
<span id="cb5-127"><a href="#cb5-127" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-128"><a href="#cb5-128" aria-hidden="true" tabindex="-1"></a> cvar <span class="op">=</span> gamma <span class="op">+</span> (<span class="fl">1.0</span> <span class="op">/</span> (S <span class="op">*</span> (<span class="fl">1.0</span> <span class="op">-</span> <span class="va">self</span>.alpha))) <span class="op">*</span> cp.<span class="bu">sum</span>(u)</span>
<span id="cb5-129"><a href="#cb5-129" aria-hidden="true" tabindex="-1"></a> kosten <span class="op">=</span> <span class="va">self</span>.gebuehrensatz <span class="op">*</span> cp.norm1(w <span class="op">-</span> w_alt)</span>
<span id="cb5-130"><a href="#cb5-130" aria-hidden="true" tabindex="-1"></a> ziel <span class="op">=</span> cp.Maximize(mu_taeglich <span class="op">@</span> w <span class="op">-</span> <span class="va">self</span>.risikoaversion <span class="op">*</span> cvar <span class="op">-</span> kosten)</span>
<span id="cb5-131"><a href="#cb5-131" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-132"><a href="#cb5-132" aria-hidden="true" tabindex="-1"></a> bedingungen <span class="op">=</span> [cp.<span class="bu">sum</span>(w) <span class="op">==</span> <span class="dv">1</span>,</span>
<span id="cb5-133"><a href="#cb5-133" aria-hidden="true" tabindex="-1"></a> w <span class="op">&lt;=</span> <span class="va">self</span>.max_gewicht,</span>
<span id="cb5-134"><a href="#cb5-134" aria-hidden="true" tabindex="-1"></a> u <span class="op">&gt;=</span> <span class="op">-</span>(R <span class="op">@</span> w) <span class="op">-</span> gamma] <span class="co"># vektorisiert</span></span>
<span id="cb5-135"><a href="#cb5-135" aria-hidden="true" tabindex="-1"></a> problem <span class="op">=</span> cp.Problem(ziel, bedingungen)</span>
<span id="cb5-136"><a href="#cb5-136" aria-hidden="true" tabindex="-1"></a> <span class="cf">try</span>:</span>
<span id="cb5-137"><a href="#cb5-137" aria-hidden="true" tabindex="-1"></a> problem.solve(solver<span class="op">=</span>cp.CLARABEL)</span>
<span id="cb5-138"><a href="#cb5-138" aria-hidden="true" tabindex="-1"></a> <span class="cf">except</span> cp.error.SolverError:</span>
<span id="cb5-139"><a href="#cb5-139" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> w_alt, <span class="va">False</span></span>
<span id="cb5-140"><a href="#cb5-140" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-141"><a href="#cb5-141" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> problem.status <span class="kw">not</span> <span class="kw">in</span> (<span class="st">&quot;optimal&quot;</span>, <span class="st">&quot;optimal_inaccurate&quot;</span>) <span class="kw">or</span> w.value <span class="kw">is</span> <span class="va">None</span>:</span>
<span id="cb5-142"><a href="#cb5-142" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> w_alt, <span class="va">False</span></span>
<span id="cb5-143"><a href="#cb5-143" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-144"><a href="#cb5-144" aria-hidden="true" tabindex="-1"></a> gewichte <span class="op">=</span> np.maximum(w.value, <span class="fl">0.0</span>)</span>
<span id="cb5-145"><a href="#cb5-145" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> gewichte <span class="op">/</span> gewichte.<span class="bu">sum</span>(), <span class="va">True</span></span>
<span id="cb5-146"><a href="#cb5-146" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-147"><a href="#cb5-147" aria-hidden="true" tabindex="-1"></a> <span class="co"># -- 5. Backtest -------------------------------------------------------</span></span>
<span id="cb5-148"><a href="#cb5-148" aria-hidden="true" tabindex="-1"></a> <span class="kw">def</span> backtest(<span class="va">self</span>):</span>
<span id="cb5-149"><a href="#cb5-149" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;Starte Walk-Forward-Backtest ...&quot;</span>)</span>
<span id="cb5-150"><a href="#cb5-150" aria-hidden="true" tabindex="-1"></a> N <span class="op">=</span> <span class="bu">len</span>(<span class="va">self</span>.universum)</span>
<span id="cb5-151"><a href="#cb5-151" aria-hidden="true" tabindex="-1"></a> gewichte <span class="op">=</span> np.ones(N) <span class="op">/</span> N</span>
<span id="cb5-152"><a href="#cb5-152" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-153"><a href="#cb5-153" aria-hidden="true" tabindex="-1"></a> werte <span class="op">=</span> [<span class="va">self</span>.startkapital]</span>
<span id="cb5-154"><a href="#cb5-154" aria-hidden="true" tabindex="-1"></a> datumsliste <span class="op">=</span> [<span class="va">self</span>.renditen.index[<span class="va">self</span>.warmup]]</span>
<span id="cb5-155"><a href="#cb5-155" aria-hidden="true" tabindex="-1"></a> gewichtshistorie <span class="op">=</span> []</span>
<span id="cb5-156"><a href="#cb5-156" aria-hidden="true" tabindex="-1"></a> geplant <span class="op">=</span> versucht <span class="op">=</span> ausgefuehrt <span class="op">=</span> <span class="dv">0</span></span>
<span id="cb5-157"><a href="#cb5-157" aria-hidden="true" tabindex="-1"></a> kosten_gesamt <span class="op">=</span> <span class="fl">0.0</span></span>
<span id="cb5-158"><a href="#cb5-158" aria-hidden="true" tabindex="-1"></a> turnover_gesamt <span class="op">=</span> <span class="fl">0.0</span></span>
<span id="cb5-159"><a href="#cb5-159" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-160"><a href="#cb5-160" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> t <span class="kw">in</span> <span class="bu">range</span>(<span class="va">self</span>.warmup, <span class="bu">len</span>(<span class="va">self</span>.renditen) <span class="op">-</span> <span class="dv">1</span>):</span>
<span id="cb5-161"><a href="#cb5-161" aria-hidden="true" tabindex="-1"></a> heute <span class="op">=</span> <span class="va">self</span>.renditen.index[t]</span>
<span id="cb5-162"><a href="#cb5-162" aria-hidden="true" tabindex="-1"></a> morgen <span class="op">=</span> <span class="va">self</span>.renditen.index[t <span class="op">+</span> <span class="dv">1</span>]</span>
<span id="cb5-163"><a href="#cb5-163" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-164"><a href="#cb5-164" aria-hidden="true" tabindex="-1"></a> ist_termin <span class="op">=</span> heute <span class="kw">in</span> <span class="va">self</span>.rebalancing_termine <span class="kw">or</span> t <span class="op">==</span> <span class="va">self</span>.warmup</span>
<span id="cb5-165"><a href="#cb5-165" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> ist_termin:</span>
<span id="cb5-166"><a href="#cb5-166" aria-hidden="true" tabindex="-1"></a> geplant <span class="op">+=</span> <span class="dv">1</span></span>
<span id="cb5-167"><a href="#cb5-167" aria-hidden="true" tabindex="-1"></a> versucht <span class="op">+=</span> <span class="dv">1</span></span>
<span id="cb5-168"><a href="#cb5-168" aria-hidden="true" tabindex="-1"></a> neue_gewichte, erfolg <span class="op">=</span> <span class="va">self</span>.optimiere(heute, gewichte)</span>
<span id="cb5-169"><a href="#cb5-169" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> erfolg:</span>
<span id="cb5-170"><a href="#cb5-170" aria-hidden="true" tabindex="-1"></a> ausgefuehrt <span class="op">+=</span> <span class="dv">1</span></span>
<span id="cb5-171"><a href="#cb5-171" aria-hidden="true" tabindex="-1"></a> turnover <span class="op">=</span> <span class="bu">float</span>(np.<span class="bu">abs</span>(neue_gewichte <span class="op">-</span> gewichte).<span class="bu">sum</span>())</span>
<span id="cb5-172"><a href="#cb5-172" aria-hidden="true" tabindex="-1"></a> gebuehr <span class="op">=</span> werte[<span class="op">-</span><span class="dv">1</span>] <span class="op">*</span> turnover <span class="op">*</span> <span class="va">self</span>.gebuehrensatz</span>
<span id="cb5-173"><a href="#cb5-173" aria-hidden="true" tabindex="-1"></a> werte[<span class="op">-</span><span class="dv">1</span>] <span class="op">-=</span> gebuehr</span>
<span id="cb5-174"><a href="#cb5-174" aria-hidden="true" tabindex="-1"></a> kosten_gesamt <span class="op">+=</span> gebuehr</span>
<span id="cb5-175"><a href="#cb5-175" aria-hidden="true" tabindex="-1"></a> turnover_gesamt <span class="op">+=</span> turnover</span>
<span id="cb5-176"><a href="#cb5-176" aria-hidden="true" tabindex="-1"></a> gewichte <span class="op">=</span> neue_gewichte</span>
<span id="cb5-177"><a href="#cb5-177" aria-hidden="true" tabindex="-1"></a> gewichtshistorie.append((heute, gewichte.copy()))</span>
<span id="cb5-178"><a href="#cb5-178" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-179"><a href="#cb5-179" aria-hidden="true" tabindex="-1"></a> <span class="co"># Rendite von MORGEN auf die HEUTE festgelegten Gewichte anwenden</span></span>
<span id="cb5-180"><a href="#cb5-180" aria-hidden="true" tabindex="-1"></a> tagesrenditen <span class="op">=</span> <span class="va">self</span>.renditen.iloc[t <span class="op">+</span> <span class="dv">1</span>].values</span>
<span id="cb5-181"><a href="#cb5-181" aria-hidden="true" tabindex="-1"></a> portfoliorendite <span class="op">=</span> <span class="bu">float</span>(gewichte <span class="op">@</span> tagesrenditen)</span>
<span id="cb5-182"><a href="#cb5-182" aria-hidden="true" tabindex="-1"></a> werte.append(werte[<span class="op">-</span><span class="dv">1</span>] <span class="op">*</span> (<span class="fl">1.0</span> <span class="op">+</span> portfoliorendite))</span>
<span id="cb5-183"><a href="#cb5-183" aria-hidden="true" tabindex="-1"></a> datumsliste.append(morgen)</span>
<span id="cb5-184"><a href="#cb5-184" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-185"><a href="#cb5-185" aria-hidden="true" tabindex="-1"></a> <span class="co"># Gewichte driften mit den Kursen weiter</span></span>
<span id="cb5-186"><a href="#cb5-186" aria-hidden="true" tabindex="-1"></a> gedriftet <span class="op">=</span> gewichte <span class="op">*</span> (<span class="fl">1.0</span> <span class="op">+</span> tagesrenditen)</span>
<span id="cb5-187"><a href="#cb5-187" aria-hidden="true" tabindex="-1"></a> gewichte <span class="op">=</span> gedriftet <span class="op">/</span> gedriftet.<span class="bu">sum</span>()</span>
<span id="cb5-188"><a href="#cb5-188" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-189"><a href="#cb5-189" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; Rebalancing: </span><span class="sc">{</span>geplant<span class="sc">}</span><span class="ss"> geplant, </span><span class="sc">{</span>ausgefuehrt<span class="sc">}</span><span class="ss"> ausgefuehrt &quot;</span></span>
<span id="cb5-190"><a href="#cb5-190" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;(</span><span class="sc">{</span>ausgefuehrt<span class="op">/</span><span class="bu">max</span>(geplant,<span class="dv">1</span>)<span class="op">*</span><span class="dv">100</span><span class="sc">:.0f}</span><span class="ss"> %)&quot;</span>)</span>
<span id="cb5-191"><a href="#cb5-191" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> ausgefuehrt <span class="op">&lt;</span> geplant:</span>
<span id="cb5-192"><a href="#cb5-192" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; WARNUNG: </span><span class="sc">{</span>geplant <span class="op">-</span> ausgefuehrt<span class="sc">}</span><span class="ss"> Optimierungen sind &quot;</span></span>
<span id="cb5-193"><a href="#cb5-193" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;fehlgeschlagen - Ursache pruefen!&quot;</span>)</span>
<span id="cb5-194"><a href="#cb5-194" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; Kumulierter Turnover: </span><span class="sc">{</span>turnover_gesamt<span class="op">*</span><span class="dv">100</span><span class="sc">:.0f}</span><span class="ss"> % | &quot;</span></span>
<span id="cb5-195"><a href="#cb5-195" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;Transaktionskosten insgesamt: </span><span class="sc">{</span>kosten_gesamt<span class="sc">:,.2f}</span><span class="ss"> EUR&quot;</span>)</span>
<span id="cb5-196"><a href="#cb5-196" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-197"><a href="#cb5-197" aria-hidden="true" tabindex="-1"></a> ergebnis <span class="op">=</span> pd.DataFrame({<span class="st">&quot;Portfolio&quot;</span>: werte}, index<span class="op">=</span>datumsliste)</span>
<span id="cb5-198"><a href="#cb5-198" aria-hidden="true" tabindex="-1"></a> benchmark <span class="op">=</span> <span class="va">self</span>.benchmark_kurse.loc[datumsliste]</span>
<span id="cb5-199"><a href="#cb5-199" aria-hidden="true" tabindex="-1"></a> ergebnis[<span class="st">&quot;Benchmark&quot;</span>] <span class="op">=</span> benchmark <span class="op">/</span> benchmark.iloc[<span class="dv">0</span>] <span class="op">*</span> <span class="va">self</span>.startkapital</span>
<span id="cb5-200"><a href="#cb5-200" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> ergebnis, gewichtshistorie, kosten_gesamt</span>
<span id="cb5-201"><a href="#cb5-201" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-202"><a href="#cb5-202" aria-hidden="true" tabindex="-1"></a> <span class="co"># -- 6. Auswertung -----------------------------------------------------</span></span>
<span id="cb5-203"><a href="#cb5-203" aria-hidden="true" tabindex="-1"></a> <span class="at">@staticmethod</span></span>
<span id="cb5-204"><a href="#cb5-204" aria-hidden="true" tabindex="-1"></a> <span class="kw">def</span> kennzahlen(reihe, risikofrei<span class="op">=</span><span class="fl">0.02</span>):</span>
<span id="cb5-205"><a href="#cb5-205" aria-hidden="true" tabindex="-1"></a> renditen <span class="op">=</span> reihe.pct_change().dropna()</span>
<span id="cb5-206"><a href="#cb5-206" aria-hidden="true" tabindex="-1"></a> jahre <span class="op">=</span> (reihe.index[<span class="op">-</span><span class="dv">1</span>] <span class="op">-</span> reihe.index[<span class="dv">0</span>]).days <span class="op">/</span> <span class="fl">365.25</span></span>
<span id="cb5-207"><a href="#cb5-207" aria-hidden="true" tabindex="-1"></a> cagr <span class="op">=</span> (reihe.iloc[<span class="op">-</span><span class="dv">1</span>] <span class="op">/</span> reihe.iloc[<span class="dv">0</span>]) <span class="op">**</span> (<span class="dv">1</span> <span class="op">/</span> jahre) <span class="op">-</span> <span class="dv">1</span></span>
<span id="cb5-208"><a href="#cb5-208" aria-hidden="true" tabindex="-1"></a> vola <span class="op">=</span> renditen.std() <span class="op">*</span> np.sqrt(HANDELSTAGE)</span>
<span id="cb5-209"><a href="#cb5-209" aria-hidden="true" tabindex="-1"></a> sharpe <span class="op">=</span> (cagr <span class="op">-</span> risikofrei) <span class="op">/</span> vola <span class="cf">if</span> vola <span class="op">&gt;</span> <span class="dv">0</span> <span class="cf">else</span> np.nan</span>
<span id="cb5-210"><a href="#cb5-210" aria-hidden="true" tabindex="-1"></a> drawdown <span class="op">=</span> ((reihe <span class="op">-</span> reihe.cummax()) <span class="op">/</span> reihe.cummax()).<span class="bu">min</span>()</span>
<span id="cb5-211"><a href="#cb5-211" aria-hidden="true" tabindex="-1"></a> calmar <span class="op">=</span> cagr <span class="op">/</span> <span class="bu">abs</span>(drawdown) <span class="cf">if</span> drawdown <span class="op">&lt;</span> <span class="dv">0</span> <span class="cf">else</span> np.nan</span>
<span id="cb5-212"><a href="#cb5-212" aria-hidden="true" tabindex="-1"></a> trefferquote <span class="op">=</span> <span class="bu">float</span>((renditen <span class="op">&gt;</span> <span class="dv">0</span>).mean())</span>
<span id="cb5-213"><a href="#cb5-213" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> {<span class="st">&quot;CAGR&quot;</span>: cagr, <span class="st">&quot;Volatilitaet&quot;</span>: vola, <span class="st">&quot;Sharpe&quot;</span>: sharpe,</span>
<span id="cb5-214"><a href="#cb5-214" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;Max Drawdown&quot;</span>: drawdown, <span class="st">&quot;Calmar&quot;</span>: calmar,</span>
<span id="cb5-215"><a href="#cb5-215" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;Trefferquote&quot;</span>: trefferquote, <span class="st">&quot;Jahre&quot;</span>: jahre}</span>
<span id="cb5-216"><a href="#cb5-216" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-217"><a href="#cb5-217" aria-hidden="true" tabindex="-1"></a> <span class="kw">def</span> bericht(<span class="va">self</span>, ergebnis, kosten_gesamt):</span>
<span id="cb5-218"><a href="#cb5-218" aria-hidden="true" tabindex="-1"></a> k_port <span class="op">=</span> <span class="va">self</span>.kennzahlen(ergebnis[<span class="st">&quot;Portfolio&quot;</span>])</span>
<span id="cb5-219"><a href="#cb5-219" aria-hidden="true" tabindex="-1"></a> k_bench <span class="op">=</span> <span class="va">self</span>.kennzahlen(ergebnis[<span class="st">&quot;Benchmark&quot;</span>])</span>
<span id="cb5-220"><a href="#cb5-220" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-221"><a href="#cb5-221" 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="cb5-222"><a href="#cb5-222" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; LEISTUNGSREPORT DER QUANTITATIVEN ENGINE&quot;</span>)</span>
<span id="cb5-223"><a href="#cb5-223" 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="cb5-224"><a href="#cb5-224" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;Zeitraum: </span><span class="sc">{</span>ergebnis<span class="sc">.</span>index[<span class="dv">0</span>]<span class="sc">:</span><span class="op">%</span>Y<span class="op">-%</span>m<span class="op">-%</span>d<span class="sc">}</span><span class="ss"> bis </span><span class="sc">{</span>ergebnis<span class="sc">.</span>index[<span class="op">-</span><span class="dv">1</span>]<span class="sc">:</span><span class="op">%</span>Y<span class="op">-%</span>m<span class="op">-%</span>d<span class="sc">}</span><span class="ss"> &quot;</span></span>
<span id="cb5-225"><a href="#cb5-225" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;(</span><span class="sc">{</span>k_port[<span class="st">&#39;Jahre&#39;</span>]<span class="sc">:.1f}</span><span class="ss"> Jahre) | Startkapital &quot;</span></span>
<span id="cb5-226"><a href="#cb5-226" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;</span><span class="sc">{</span><span class="va">self</span><span class="sc">.</span>startkapital<span class="sc">:,.0f}</span><span class="ss"> EUR</span><span class="ch">\n</span><span class="ss">&quot;</span>)</span>
<span id="cb5-227"><a href="#cb5-227" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-228"><a href="#cb5-228" aria-hidden="true" tabindex="-1"></a> zeilen <span class="op">=</span> [</span>
<span id="cb5-229"><a href="#cb5-229" aria-hidden="true" tabindex="-1"></a> (<span class="st">&quot;Endkapital&quot;</span>, <span class="ss">f&quot;</span><span class="sc">{</span>ergebnis[<span class="st">&#39;Portfolio&#39;</span>]<span class="sc">.</span>iloc[<span class="op">-</span><span class="dv">1</span>]<span class="sc">:,.0f}</span><span class="ss"> EUR&quot;</span>,</span>
<span id="cb5-230"><a href="#cb5-230" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;</span><span class="sc">{</span>ergebnis[<span class="st">&#39;Benchmark&#39;</span>]<span class="sc">.</span>iloc[<span class="op">-</span><span class="dv">1</span>]<span class="sc">:,.0f}</span><span class="ss"> EUR&quot;</span>),</span>
<span id="cb5-231"><a href="#cb5-231" aria-hidden="true" tabindex="-1"></a> (<span class="st">&quot;CAGR (Jahresrendite)&quot;</span>, <span class="ss">f&quot;</span><span class="sc">{</span>k_port[<span class="st">&#39;CAGR&#39;</span>]<span class="op">*</span><span class="dv">100</span><span class="sc">:6.2f}</span><span class="ss"> %&quot;</span>,</span>
<span id="cb5-232"><a href="#cb5-232" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;</span><span class="sc">{</span>k_bench[<span class="st">&#39;CAGR&#39;</span>]<span class="op">*</span><span class="dv">100</span><span class="sc">:6.2f}</span><span class="ss"> %&quot;</span>),</span>
<span id="cb5-233"><a href="#cb5-233" 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>k_port[<span class="st">&#39;Volatilitaet&#39;</span>]<span class="op">*</span><span class="dv">100</span><span class="sc">:6.2f}</span><span class="ss"> %&quot;</span>,</span>
<span id="cb5-234"><a href="#cb5-234" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;</span><span class="sc">{</span>k_bench[<span class="st">&#39;Volatilitaet&#39;</span>]<span class="op">*</span><span class="dv">100</span><span class="sc">:6.2f}</span><span class="ss"> %&quot;</span>),</span>
<span id="cb5-235"><a href="#cb5-235" aria-hidden="true" tabindex="-1"></a> (<span class="st">&quot;Sharpe Ratio (rf=2 %)&quot;</span>, <span class="ss">f&quot;</span><span class="sc">{</span>k_port[<span class="st">&#39;Sharpe&#39;</span>]<span class="sc">:6.2f}</span><span class="ss">&quot;</span>,</span>
<span id="cb5-236"><a href="#cb5-236" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;</span><span class="sc">{</span>k_bench[<span class="st">&#39;Sharpe&#39;</span>]<span class="sc">:6.2f}</span><span class="ss">&quot;</span>),</span>
<span id="cb5-237"><a href="#cb5-237" aria-hidden="true" tabindex="-1"></a> (<span class="st">&quot;Maximum Drawdown&quot;</span>, <span class="ss">f&quot;</span><span class="sc">{</span>k_port[<span class="st">&#39;Max Drawdown&#39;</span>]<span class="op">*</span><span class="dv">100</span><span class="sc">:6.2f}</span><span class="ss"> %&quot;</span>,</span>
<span id="cb5-238"><a href="#cb5-238" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;</span><span class="sc">{</span>k_bench[<span class="st">&#39;Max Drawdown&#39;</span>]<span class="op">*</span><span class="dv">100</span><span class="sc">:6.2f}</span><span class="ss"> %&quot;</span>),</span>
<span id="cb5-239"><a href="#cb5-239" aria-hidden="true" tabindex="-1"></a> (<span class="st">&quot;Calmar Ratio&quot;</span>, <span class="ss">f&quot;</span><span class="sc">{</span>k_port[<span class="st">&#39;Calmar&#39;</span>]<span class="sc">:6.2f}</span><span class="ss">&quot;</span>, <span class="ss">f&quot;</span><span class="sc">{</span>k_bench[<span class="st">&#39;Calmar&#39;</span>]<span class="sc">:6.2f}</span><span class="ss">&quot;</span>),</span>
<span id="cb5-240"><a href="#cb5-240" aria-hidden="true" tabindex="-1"></a> (<span class="st">&quot;Trefferquote (Tage)&quot;</span>, <span class="ss">f&quot;</span><span class="sc">{</span>k_port[<span class="st">&#39;Trefferquote&#39;</span>]<span class="op">*</span><span class="dv">100</span><span class="sc">:6.1f}</span><span class="ss"> %&quot;</span>,</span>
<span id="cb5-241"><a href="#cb5-241" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;</span><span class="sc">{</span>k_bench[<span class="st">&#39;Trefferquote&#39;</span>]<span class="op">*</span><span class="dv">100</span><span class="sc">:6.1f}</span><span class="ss"> %&quot;</span>),</span>
<span id="cb5-242"><a href="#cb5-242" aria-hidden="true" tabindex="-1"></a> ]</span>
<span id="cb5-243"><a href="#cb5-243" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(pd.DataFrame(zeilen, columns<span class="op">=</span>[<span class="st">&quot;Kennzahl&quot;</span>, <span class="st">&quot;OR-Strategie&quot;</span>, <span class="st">&quot;Benchmark&quot;</span>])</span>
<span id="cb5-244"><a href="#cb5-244" aria-hidden="true" tabindex="-1"></a> .to_string(index<span class="op">=</span><span class="va">False</span>))</span>
<span id="cb5-245"><a href="#cb5-245" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-246"><a href="#cb5-246" aria-hidden="true" tabindex="-1"></a> anteil <span class="op">=</span> kosten_gesamt <span class="op">/</span> <span class="va">self</span>.startkapital</span>
<span id="cb5-247"><a href="#cb5-247" 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">Transaktionskosten: </span><span class="sc">{</span>kosten_gesamt<span class="sc">:,.0f}</span><span class="ss"> EUR &quot;</span></span>
<span id="cb5-248"><a href="#cb5-248" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;= </span><span class="sc">{</span>anteil<span class="op">*</span><span class="dv">100</span><span class="sc">:.2f}</span><span class="ss"> % des Startkapitals &quot;</span></span>
<span id="cb5-249"><a href="#cb5-249" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;= </span><span class="sc">{</span>anteil<span class="op">/</span>k_port[<span class="st">&#39;Jahre&#39;</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="cb5-250"><a href="#cb5-250" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;Ohne diese Kosten waere die ausgewiesene Rendite entsprechend hoeher -&quot;</span>)</span>
<span id="cb5-251"><a href="#cb5-251" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;genau deshalb gehoeren sie in den Backtest und nicht daneben.&quot;</span>)</span>
<span id="cb5-252"><a href="#cb5-252" 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="cb5-253"><a href="#cb5-253" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> k_port, k_bench</span>
<span id="cb5-254"><a href="#cb5-254" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-255"><a href="#cb5-255" aria-hidden="true" tabindex="-1"></a> <span class="kw">def</span> zeichne(<span class="va">self</span>, ergebnis):</span>
<span id="cb5-256"><a href="#cb5-256" aria-hidden="true" tabindex="-1"></a> plt.figure(figsize<span class="op">=</span>(<span class="dv">11</span>, <span class="dv">6</span>))</span>
<span id="cb5-257"><a href="#cb5-257" aria-hidden="true" tabindex="-1"></a> plt.plot(ergebnis.index, ergebnis[<span class="st">&quot;Portfolio&quot;</span>], <span class="st">&quot;b-&quot;</span>, lw<span class="op">=</span><span class="fl">2.2</span>,</span>
<span id="cb5-258"><a href="#cb5-258" aria-hidden="true" tabindex="-1"></a> label<span class="op">=</span><span class="st">&quot;OR-Handelsmaschine&quot;</span>)</span>
<span id="cb5-259"><a href="#cb5-259" aria-hidden="true" tabindex="-1"></a> plt.plot(ergebnis.index, ergebnis[<span class="st">&quot;Benchmark&quot;</span>], <span class="st">&quot;k--&quot;</span>, lw<span class="op">=</span><span class="fl">1.5</span>, alpha<span class="op">=</span><span class="fl">0.75</span>,</span>
<span id="cb5-260"><a href="#cb5-260" aria-hidden="true" tabindex="-1"></a> label<span class="op">=</span><span class="ss">f&quot;Benchmark (</span><span class="sc">{</span><span class="va">self</span><span class="sc">.</span>benchmark<span class="sc">}</span><span class="ss">)&quot;</span>)</span>
<span id="cb5-261"><a href="#cb5-261" aria-hidden="true" tabindex="-1"></a> plt.title(<span class="st">&quot;Walk-Forward-Backtest: OR-Strategie gegen Benchmark&quot;</span>, fontsize<span class="op">=</span><span class="dv">12</span>)</span>
<span id="cb5-262"><a href="#cb5-262" aria-hidden="true" tabindex="-1"></a> plt.xlabel(<span class="st">&quot;Datum&quot;</span>)</span>
<span id="cb5-263"><a href="#cb5-263" aria-hidden="true" tabindex="-1"></a> plt.ylabel(<span class="st">&quot;Depotwert in EUR&quot;</span>)</span>
<span id="cb5-264"><a href="#cb5-264" aria-hidden="true" tabindex="-1"></a> plt.grid(<span class="va">True</span>, linestyle<span class="op">=</span><span class="st">&quot;:&quot;</span>, alpha<span class="op">=</span><span class="fl">0.6</span>)</span>
<span id="cb5-265"><a href="#cb5-265" aria-hidden="true" tabindex="-1"></a> plt.legend(loc<span class="op">=</span><span class="st">&quot;upper left&quot;</span>)</span>
<span id="cb5-266"><a href="#cb5-266" aria-hidden="true" tabindex="-1"></a> plt.tight_layout()</span>
<span id="cb5-267"><a href="#cb5-267" aria-hidden="true" tabindex="-1"></a> ziel <span class="op">=</span> os.path.join(OUTPUT_DIR, <span class="st">&quot;trading_engine_backtest.png&quot;</span>)</span>
<span id="cb5-268"><a href="#cb5-268" aria-hidden="true" tabindex="-1"></a> plt.savefig(ziel, dpi<span class="op">=</span><span class="dv">150</span>)</span>
<span id="cb5-269"><a href="#cb5-269" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;Chart gespeichert unter &#39;</span><span class="sc">{</span>ziel<span class="sc">}</span><span class="ss">&#39;&quot;</span>)</span>
<span id="cb5-270"><a href="#cb5-270" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-271"><a href="#cb5-271" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-272"><a href="#cb5-272" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> lookahead_selbsttest(maschine):</span>
<span id="cb5-273"><a href="#cb5-273" aria-hidden="true" tabindex="-1"></a> <span class="co">&quot;&quot;&quot;</span></span>
<span id="cb5-274"><a href="#cb5-274" aria-hidden="true" tabindex="-1"></a><span class="co"> Prueft, dass das Signal zum Zeitpunkt t NUR Daten bis t verwendet.</span></span>
<span id="cb5-275"><a href="#cb5-275" aria-hidden="true" tabindex="-1"></a><span class="co"> Methode: kuenstlich die Zukunft veraendern und pruefen, ob sich das</span></span>
<span id="cb5-276"><a href="#cb5-276" aria-hidden="true" tabindex="-1"></a><span class="co"> Signal von heute dadurch aendert. Tut es das, liegt Lookahead vor.</span></span>
<span id="cb5-277"><a href="#cb5-277" aria-hidden="true" tabindex="-1"></a><span class="co"> &quot;&quot;&quot;</span></span>
<span id="cb5-278"><a href="#cb5-278" aria-hidden="true" tabindex="-1"></a> stichtag <span class="op">=</span> maschine.kurse.index[<span class="bu">len</span>(maschine.kurse) <span class="op">//</span> <span class="dv">2</span>]</span>
<span id="cb5-279"><a href="#cb5-279" aria-hidden="true" tabindex="-1"></a> signal_original <span class="op">=</span> maschine.alpha_signal(stichtag)</span>
<span id="cb5-280"><a href="#cb5-280" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-281"><a href="#cb5-281" aria-hidden="true" tabindex="-1"></a> original_kurse <span class="op">=</span> maschine.kurse.copy()</span>
<span id="cb5-282"><a href="#cb5-282" aria-hidden="true" tabindex="-1"></a> zukunft <span class="op">=</span> maschine.kurse.index <span class="op">&gt;</span> stichtag</span>
<span id="cb5-283"><a href="#cb5-283" aria-hidden="true" tabindex="-1"></a> maschine.kurse.loc[zukunft] <span class="op">*=</span> <span class="fl">3.0</span> <span class="co"># Zukunft massiv veraendern</span></span>
<span id="cb5-284"><a href="#cb5-284" aria-hidden="true" tabindex="-1"></a> signal_manipuliert <span class="op">=</span> maschine.alpha_signal(stichtag)</span>
<span id="cb5-285"><a href="#cb5-285" aria-hidden="true" tabindex="-1"></a> maschine.kurse <span class="op">=</span> original_kurse <span class="co"># zuruecksetzen</span></span>
<span id="cb5-286"><a href="#cb5-286" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-287"><a href="#cb5-287" aria-hidden="true" tabindex="-1"></a> identisch <span class="op">=</span> np.allclose(signal_original, signal_manipuliert)</span>
<span id="cb5-288"><a href="#cb5-288" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;Lookahead-Selbsttest: Signal bleibt bei manipulierter Zukunft &quot;</span></span>
<span id="cb5-289"><a href="#cb5-289" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;</span><span class="sc">{</span><span class="st">&#39;UNVERAENDERT (gut)&#39;</span> <span class="cf">if</span> identisch <span class="cf">else</span> <span class="st">&#39;VERAENDERT - LOOKAHEAD-BIAS!&#39;</span><span class="sc">}</span><span class="ss">&quot;</span>)</span>
<span id="cb5-290"><a href="#cb5-290" aria-hidden="true" tabindex="-1"></a> <span class="cf">assert</span> identisch, <span class="st">&quot;Das Signal verwendet Zukunftsdaten!&quot;</span></span>
<span id="cb5-291"><a href="#cb5-291" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-292"><a href="#cb5-292" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-293"><a href="#cb5-293" 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="cb5-294"><a href="#cb5-294" 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;GOOGL&quot;</span>,</span>
<span id="cb5-295"><a href="#cb5-295" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;JNJ&quot;</span>, <span class="st">&quot;UNH&quot;</span>, <span class="st">&quot;PFE&quot;</span>,</span>
<span id="cb5-296"><a href="#cb5-296" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;JPM&quot;</span>, <span class="st">&quot;BAC&quot;</span>, <span class="st">&quot;GS&quot;</span>,</span>
<span id="cb5-297"><a href="#cb5-297" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;XOM&quot;</span>, <span class="st">&quot;CVX&quot;</span>,</span>
<span id="cb5-298"><a href="#cb5-298" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;PG&quot;</span>, <span class="st">&quot;KO&quot;</span>, <span class="st">&quot;COST&quot;</span>]</span>
<span id="cb5-299"><a href="#cb5-299" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-300"><a href="#cb5-300" aria-hidden="true" tabindex="-1"></a> maschine <span class="op">=</span> QuantitativeHandelsmaschine(UNIVERSUM, benchmark<span class="op">=</span><span class="st">&quot;SPY&quot;</span>, jahre<span class="op">=</span><span class="dv">5</span>)</span>
<span id="cb5-301"><a href="#cb5-301" aria-hidden="true" tabindex="-1"></a> maschine.lade_daten()</span>
<span id="cb5-302"><a href="#cb5-302" aria-hidden="true" tabindex="-1"></a> lookahead_selbsttest(maschine)</span>
<span id="cb5-303"><a href="#cb5-303" aria-hidden="true" tabindex="-1"></a> ergebnis, gewichte, kosten <span class="op">=</span> maschine.backtest()</span>
<span id="cb5-304"><a href="#cb5-304" aria-hidden="true" tabindex="-1"></a> maschine.bericht(ergebnis, kosten)</span>
<span id="cb5-305"><a href="#cb5-305" aria-hidden="true" tabindex="-1"></a> maschine.zeichne(ergebnis)</span></code></pre></div>
<blockquote>
<p><strong>💻 Code-Durchgang: die vier kritischen Stellen</strong></p>
<p><strong>1. Rebalancing-Kalender.</strong></p>
<div class="sourceCode" id="cb6"><pre class="sourceCode python"><code class="sourceCode python"><span id="cb6-1"><a href="#cb6-1" aria-hidden="true" tabindex="-1"></a>monat <span class="op">=</span> <span class="va">self</span>.kurse.index.to_period(<span class="st">&quot;M&quot;</span>)</span>
<span id="cb6-2"><a href="#cb6-2" aria-hidden="true" tabindex="-1"></a><span class="va">self</span>.rebalancing_termine <span class="op">=</span> <span class="va">self</span>.kurse.index[<span class="op">~</span>monat.duplicated()]</span></code></pre></div>
<p><code>to_period("M")</code> bildet jeden Handelstag auf seinen Monat ab; <code>~duplicated()</code> behält jeweils den <strong>ersten</strong> — also garantiert einen realen Handelstag.</p>
<p><strong>2. Die Zeitachse im Backtest.</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>tagesrenditen <span class="op">=</span> <span class="va">self</span>.renditen.iloc[t <span class="op">+</span> <span class="dv">1</span>].values <span class="co"># MORGEN</span></span>
<span id="cb7-2"><a href="#cb7-2" aria-hidden="true" tabindex="-1"></a>portfoliorendite <span class="op">=</span> gewichte <span class="op">@</span> tagesrenditen <span class="co"># HEUTE festgelegt</span></span></code></pre></div>
<p>Die Gewichte werden am Tag <span class="math inline">t</span> aus Daten bis <span class="math inline">t</span> bestimmt und auf die Rendite von <span class="math inline">t+1</span> angewendet. <strong>Diese eine Indexverschiebung ist der Unterschied zwischen einem ehrlichen und einem geschönten Backtest.</strong></p>
<p><strong>3. Der Lookahead-Selbsttest.</strong> Er verdreifacht künstlich alle Kurse <strong>nach</strong> dem Stichtag und prüft, ob sich das Signal von <em>vor</em> dem Stichtag dadurch ändert. Ändert es sich, greift der Code auf die Zukunft zu. Dieser Test kostet zehn Zeilen und findet eine Fehlerklasse, die sonst praktisch unsichtbar bleibt. <strong>Übernehmen Sie ihn in jedes Backtest-Projekt.</strong></p>
<p><strong>4. Gewichtsdrift.</strong></p>
<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>gedriftet <span class="op">=</span> gewichte <span class="op">*</span> (<span class="fl">1.0</span> <span class="op">+</span> tagesrenditen)</span>
<span id="cb8-2"><a href="#cb8-2" aria-hidden="true" tabindex="-1"></a>gewichte <span class="op">=</span> gedriftet <span class="op">/</span> gedriftet.<span class="bu">sum</span>()</span></code></pre></div>
<p>Zwischen zwei Rebalancings verändern sich die Gewichte von selbst — gestiegene Titel bekommen mehr Gewicht. Wer das weglässt, unterstellt implizit <strong>tägliches</strong> kostenloses Rebalancing und überschätzt die Strategie.</p>
</blockquote>
<blockquote>
<p><strong>⚠️ Zur Reproduzierbarkeit</strong> Dieses Programm lädt Live-Daten über ein relatives Fünfjahresfenster. <strong>Ihre Zahlen werden von jeder abgedruckten Zahl abweichen</strong> — je nach Abrufdatum und Marktlage. Bewerten Sie deshalb nicht die absoluten Werte, sondern die <strong>Struktur</strong>: Wurden alle Rebalancings ausgeführt? Besteht der Lookahead-Test? Sind die Transaktionskosten plausibel?</p>
</blockquote>
<hr />
<h2 id="sec:handelsmaschine-die-fuenf-selbsttaeuschungen-des-backtestens">21.6 Die fünf Selbsttäuschungen des Backtestens</h2>
<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"># Backtest_Fallen.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 Handelsmaschine: Wie leicht man sich selbst betruegt.</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">Demonstriert an SIMULIERTEN Daten (also ohne jede echte Prognosekraft),</span></span>
<span id="cb9-8"><a href="#cb9-8" aria-hidden="true" tabindex="-1"></a><span class="co">wie gross die scheinbare Ueberrendite durch typische Backtest-Fehler wird.</span></span>
<span id="cb9-9"><a href="#cb9-9" aria-hidden="true" tabindex="-1"></a><span class="co">Weil die Daten reines Rauschen sind, ist JEDE positive Ueberrendite ein Artefakt.</span></span>
<span id="cb9-10"><a href="#cb9-10" aria-hidden="true" tabindex="-1"></a><span class="co">&quot;&quot;&quot;</span></span>
<span id="cb9-11"><a href="#cb9-11" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-12"><a href="#cb9-12" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> numpy <span class="im">as</span> np</span>
<span id="cb9-13"><a href="#cb9-13" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> pandas <span class="im">as</span> pd</span>
<span id="cb9-14"><a href="#cb9-14" aria-hidden="true" tabindex="-1"></a></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="kw">def</span> erzeuge_zufallsmarkt(tage<span class="op">=</span><span class="dv">1260</span>, titel<span class="op">=</span><span class="dv">15</span>, seed<span class="op">=</span><span class="dv">7</span>):</span>
<span id="cb9-17"><a href="#cb9-17" aria-hidden="true" tabindex="-1"></a> <span class="co">&quot;&quot;&quot;Reines Rauschen: kein Titel hat echte Prognosekraft.&quot;&quot;&quot;</span></span>
<span id="cb9-18"><a href="#cb9-18" aria-hidden="true" tabindex="-1"></a> rng <span class="op">=</span> np.random.default_rng(seed)</span>
<span id="cb9-19"><a href="#cb9-19" aria-hidden="true" tabindex="-1"></a> renditen <span class="op">=</span> rng.normal(<span class="fl">0.0003</span>, <span class="fl">0.015</span>, size<span class="op">=</span>(tage, titel))</span>
<span id="cb9-20"><a href="#cb9-20" aria-hidden="true" tabindex="-1"></a> index <span class="op">=</span> pd.bdate_range(<span class="st">&quot;2020-01-01&quot;</span>, periods<span class="op">=</span>tage)</span>
<span id="cb9-21"><a href="#cb9-21" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> pd.DataFrame(renditen, index<span class="op">=</span>index,</span>
<span id="cb9-22"><a href="#cb9-22" aria-hidden="true" tabindex="-1"></a> columns<span class="op">=</span>[<span class="ss">f&quot;T</span><span class="sc">{</span>i<span class="sc">:02d}</span><span class="ss">&quot;</span> <span class="cf">for</span> i <span class="kw">in</span> <span class="bu">range</span>(titel)])</span>
<span id="cb9-23"><a href="#cb9-23" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-24"><a href="#cb9-24" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-25"><a href="#cb9-25" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> strategie(renditen, lookahead<span class="op">=</span><span class="va">False</span>, survivorship<span class="op">=</span><span class="va">False</span>,</span>
<span id="cb9-26"><a href="#cb9-26" aria-hidden="true" tabindex="-1"></a> ohne_kosten<span class="op">=</span><span class="va">False</span>, gebuehr<span class="op">=</span><span class="fl">0.0015</span>):</span>
<span id="cb9-27"><a href="#cb9-27" aria-hidden="true" tabindex="-1"></a> <span class="co">&quot;&quot;&quot;Einfache Momentum-Strategie mit optional eingebauten Fehlern.&quot;&quot;&quot;</span></span>
<span id="cb9-28"><a href="#cb9-28" aria-hidden="true" tabindex="-1"></a> fenster <span class="op">=</span> <span class="dv">60</span></span>
<span id="cb9-29"><a href="#cb9-29" aria-hidden="true" tabindex="-1"></a> kapital <span class="op">=</span> [<span class="fl">100.0</span>]</span>
<span id="cb9-30"><a href="#cb9-30" aria-hidden="true" tabindex="-1"></a> gewichte <span class="op">=</span> np.ones(renditen.shape[<span class="dv">1</span>]) <span class="op">/</span> renditen.shape[<span class="dv">1</span>]</span>
<span id="cb9-31"><a href="#cb9-31" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-32"><a href="#cb9-32" aria-hidden="true" tabindex="-1"></a> daten <span class="op">=</span> renditen</span>
<span id="cb9-33"><a href="#cb9-33" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> survivorship:</span>
<span id="cb9-34"><a href="#cb9-34" aria-hidden="true" tabindex="-1"></a> <span class="co"># FEHLER: nur die im Nachhinein besten Titel ins Universum lassen</span></span>
<span id="cb9-35"><a href="#cb9-35" aria-hidden="true" tabindex="-1"></a> beste <span class="op">=</span> renditen.<span class="bu">sum</span>().nlargest(renditen.shape[<span class="dv">1</span>] <span class="op">//</span> <span class="dv">2</span>).index</span>
<span id="cb9-36"><a href="#cb9-36" aria-hidden="true" tabindex="-1"></a> daten <span class="op">=</span> renditen[beste]</span>
<span id="cb9-37"><a href="#cb9-37" aria-hidden="true" tabindex="-1"></a> gewichte <span class="op">=</span> np.ones(<span class="bu">len</span>(beste)) <span class="op">/</span> <span class="bu">len</span>(beste)</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> <span class="cf">for</span> t <span class="kw">in</span> <span class="bu">range</span>(fenster, <span class="bu">len</span>(daten) <span class="op">-</span> <span class="dv">1</span>):</span>
<span id="cb9-40"><a href="#cb9-40" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> t <span class="op">%</span> <span class="dv">21</span> <span class="op">==</span> <span class="dv">0</span>: <span class="co"># monatlich</span></span>
<span id="cb9-41"><a href="#cb9-41" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> lookahead:</span>
<span id="cb9-42"><a href="#cb9-42" aria-hidden="true" tabindex="-1"></a> <span class="co"># FEHLER: Signal nutzt die Rendite von MORGEN</span></span>
<span id="cb9-43"><a href="#cb9-43" aria-hidden="true" tabindex="-1"></a> signal <span class="op">=</span> daten.iloc[t <span class="op">+</span> <span class="dv">1</span>].values</span>
<span id="cb9-44"><a href="#cb9-44" aria-hidden="true" tabindex="-1"></a> <span class="cf">else</span>:</span>
<span id="cb9-45"><a href="#cb9-45" aria-hidden="true" tabindex="-1"></a> signal <span class="op">=</span> daten.iloc[t <span class="op">-</span> fenster:t].mean().values</span>
<span id="cb9-46"><a href="#cb9-46" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-47"><a href="#cb9-47" aria-hidden="true" tabindex="-1"></a> neue <span class="op">=</span> (signal <span class="op">&gt;</span> np.median(signal)).astype(<span class="bu">float</span>)</span>
<span id="cb9-48"><a href="#cb9-48" aria-hidden="true" tabindex="-1"></a> neue <span class="op">=</span> neue <span class="op">/</span> neue.<span class="bu">sum</span>() <span class="cf">if</span> neue.<span class="bu">sum</span>() <span class="op">&gt;</span> <span class="dv">0</span> <span class="cf">else</span> gewichte</span>
<span id="cb9-49"><a href="#cb9-49" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> <span class="kw">not</span> ohne_kosten:</span>
<span id="cb9-50"><a href="#cb9-50" aria-hidden="true" tabindex="-1"></a> kapital[<span class="op">-</span><span class="dv">1</span>] <span class="op">*=</span> (<span class="dv">1</span> <span class="op">-</span> np.<span class="bu">abs</span>(neue <span class="op">-</span> gewichte).<span class="bu">sum</span>() <span class="op">*</span> gebuehr)</span>
<span id="cb9-51"><a href="#cb9-51" aria-hidden="true" tabindex="-1"></a> gewichte <span class="op">=</span> neue</span>
<span id="cb9-52"><a href="#cb9-52" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-53"><a href="#cb9-53" aria-hidden="true" tabindex="-1"></a> kapital.append(kapital[<span class="op">-</span><span class="dv">1</span>] <span class="op">*</span> (<span class="dv">1</span> <span class="op">+</span> <span class="bu">float</span>(gewichte <span class="op">@</span> daten.iloc[t <span class="op">+</span> <span class="dv">1</span>].values)))</span>
<span id="cb9-54"><a href="#cb9-54" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> np.array(kapital)</span>
<span id="cb9-55"><a href="#cb9-55" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-56"><a href="#cb9-56" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-57"><a href="#cb9-57" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> cagr(verlauf, jahre):</span>
<span id="cb9-58"><a href="#cb9-58" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> (verlauf[<span class="op">-</span><span class="dv">1</span>] <span class="op">/</span> verlauf[<span class="dv">0</span>]) <span class="op">**</span> (<span class="dv">1</span> <span class="op">/</span> jahre) <span class="op">-</span> <span class="dv">1</span></span>
<span id="cb9-59"><a href="#cb9-59" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-60"><a href="#cb9-60" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-61"><a href="#cb9-61" 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-62"><a href="#cb9-62" aria-hidden="true" tabindex="-1"></a> renditen <span class="op">=</span> erzeuge_zufallsmarkt()</span>
<span id="cb9-63"><a href="#cb9-63" aria-hidden="true" tabindex="-1"></a> jahre <span class="op">=</span> <span class="bu">len</span>(renditen) <span class="op">/</span> <span class="dv">252</span></span>
<span id="cb9-64"><a href="#cb9-64" aria-hidden="true" tabindex="-1"></a> markt <span class="op">=</span> <span class="dv">100</span> <span class="op">*</span> (<span class="dv">1</span> <span class="op">+</span> renditen.mean(axis<span class="op">=</span><span class="dv">1</span>)).cumprod().values</span>
<span id="cb9-65"><a href="#cb9-65" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-66"><a href="#cb9-66" 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="cb9-67"><a href="#cb9-67" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; DIE FUENF SELBSTTAEUSCHUNGEN - GEMESSEN AN REINEM RAUSCHEN&quot;</span>)</span>
<span id="cb9-68"><a href="#cb9-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">84</span>)</span>
<span id="cb9-69"><a href="#cb9-69" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;Die Daten sind zufaellig erzeugt. Es gibt KEINE echte Prognosekraft.&quot;</span>)</span>
<span id="cb9-70"><a href="#cb9-70" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;Jede Ueberrendite unten ist daher ein reines Artefakt.</span><span class="ch">\n</span><span class="st">&quot;</span>)</span>
<span id="cb9-71"><a href="#cb9-71" 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;Variante&#39;</span><span class="sc">:&lt;44}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;CAGR&#39;</span><span class="sc">:&gt;9}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;ggue. Markt&#39;</span><span class="sc">:&gt;13}</span><span class="ss">&quot;</span>)</span>
<span id="cb9-72"><a href="#cb9-72" 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="cb9-73"><a href="#cb9-73" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-74"><a href="#cb9-74" aria-hidden="true" tabindex="-1"></a> basis <span class="op">=</span> cagr(markt, jahre)</span>
<span id="cb9-75"><a href="#cb9-75" 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;Markt (Gleichgewichtung, Referenz)&#39;</span><span class="sc">:&lt;44}</span><span class="ss"> </span><span class="sc">{</span>basis<span class="op">*</span><span class="dv">100</span><span class="sc">:&gt;8.2f}</span><span class="ss"> % &quot;</span></span>
<span id="cb9-76"><a href="#cb9-76" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;</span><span class="sc">{</span><span class="fl">0.0</span><span class="sc">:&gt;12.2f}</span><span class="ss"> %&quot;</span>)</span>
<span id="cb9-77"><a href="#cb9-77" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-78"><a href="#cb9-78" aria-hidden="true" tabindex="-1"></a> varianten <span class="op">=</span> [</span>
<span id="cb9-79"><a href="#cb9-79" aria-hidden="true" tabindex="-1"></a> (<span class="st">&quot;Ehrlich: Signal aus Vergangenheit, mit Kosten&quot;</span>, {}),</span>
<span id="cb9-80"><a href="#cb9-80" aria-hidden="true" tabindex="-1"></a> (<span class="st">&quot;FEHLER 1: Lookahead (Signal kennt morgen)&quot;</span>, {<span class="st">&quot;lookahead&quot;</span>: <span class="va">True</span>}),</span>
<span id="cb9-81"><a href="#cb9-81" aria-hidden="true" tabindex="-1"></a> (<span class="st">&quot;FEHLER 2: Survivorship (nur Gewinner im Universum)&quot;</span>, {<span class="st">&quot;survivorship&quot;</span>: <span class="va">True</span>}),</span>
<span id="cb9-82"><a href="#cb9-82" aria-hidden="true" tabindex="-1"></a> (<span class="st">&quot;FEHLER 3: Transaktionskosten ignoriert&quot;</span>, {<span class="st">&quot;ohne_kosten&quot;</span>: <span class="va">True</span>}),</span>
<span id="cb9-83"><a href="#cb9-83" aria-hidden="true" tabindex="-1"></a> (<span class="st">&quot;FEHLER 1+2+3 kombiniert&quot;</span>, {<span class="st">&quot;lookahead&quot;</span>: <span class="va">True</span>, <span class="st">&quot;survivorship&quot;</span>: <span class="va">True</span>,</span>
<span id="cb9-84"><a href="#cb9-84" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;ohne_kosten&quot;</span>: <span class="va">True</span>}),</span>
<span id="cb9-85"><a href="#cb9-85" aria-hidden="true" tabindex="-1"></a> ]</span>
<span id="cb9-86"><a href="#cb9-86" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> name, argumente <span class="kw">in</span> varianten:</span>
<span id="cb9-87"><a href="#cb9-87" aria-hidden="true" tabindex="-1"></a> verlauf <span class="op">=</span> strategie(renditen, <span class="op">**</span>argumente)</span>
<span id="cb9-88"><a href="#cb9-88" aria-hidden="true" tabindex="-1"></a> wert <span class="op">=</span> cagr(verlauf, jahre)</span>
<span id="cb9-89"><a href="#cb9-89" 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;44}</span><span class="ss"> </span><span class="sc">{</span>wert<span class="op">*</span><span class="dv">100</span><span class="sc">:&gt;8.2f}</span><span class="ss"> % </span><span class="sc">{</span>(wert<span class="op">-</span>basis)<span class="op">*</span><span class="dv">100</span><span class="sc">:&gt;+12.2f}</span><span class="ss"> %&quot;</span>)</span>
<span id="cb9-90"><a href="#cb9-90" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-91"><a href="#cb9-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="cb9-92"><a href="#cb9-92" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;FEHLER 4 (Overfitting) und FEHLER 5 (Data Snooping) lassen sich so nicht&quot;</span>)</span>
<span id="cb9-93"><a href="#cb9-93" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;zeigen - sie entstehen erst durch WIEDERHOLTES Probieren. Faustregel:&quot;</span>)</span>
<span id="cb9-94"><a href="#cb9-94" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;Wer 20 Strategien testet, findet auch in reinem Rauschen eine mit&quot;</span>)</span>
<span id="cb9-95"><a href="#cb9-95" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;Signifikanz auf dem 5-%-Niveau. Genau das ist der Punkt.&quot;</span>)</span>
<span id="cb9-96"><a href="#cb9-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">84</span>)</span></code></pre></div>
<p><strong>Erwartete Ausgabe:</strong></p>
<pre><code>====================================================================================
DIE FUENF SELBSTTAEUSCHUNGEN - GEMESSEN AN REINEM RAUSCHEN
====================================================================================
Die Daten sind zufaellig erzeugt. Es gibt KEINE echte Prognosekraft.
Jede Ueberrendite unten ist daher ein reines Artefakt.
Variante CAGR ggue. Markt
------------------------------------------------------------------------------------
Markt (Gleichgewichtung, Referenz) 5.61 % 0.00 %
Ehrlich: Signal aus Vergangenheit, mit Kosten 6.71 % +1.10 %
FEHLER 1: Lookahead (Signal kennt morgen) 20.96 % +15.35 %
FEHLER 2: Survivorship (nur Gewinner im Univ.) 18.71 % +13.11 %
FEHLER 3: Transaktionskosten ignoriert 7.77 % +2.16 %
FEHLER 1+2+3 kombiniert 25.41 % +19.80 %
------------------------------------------------------------------------------------</code></pre>
<p><strong>Lesen Sie diese Tabelle sehr genau.</strong> Die zugrunde liegenden Daten sind <strong>reiner Zufall</strong> — es gibt keinerlei Prognosekraft, kein Signal, keine Struktur. Trotzdem:</p>
<ul>
<li><strong>Lookahead</strong> erzeugt eine scheinbare Überrendite von <strong>15,4 Prozentpunkten pro Jahr</strong>. Ein einziger falscher Index — <code>iloc[t+1]</code> statt <code>iloc[t]</code> — verwandelt Rauschen in eine scheinbar brillante Strategie.</li>
<li><strong>Survivorship-Bias</strong> liefert <strong>13,1 Prozentpunkte</strong> allein dadurch, dass man das Universum im Nachhinein auf die Gewinner beschränkt.</li>
<li>Selbst die <strong>ehrliche</strong> Variante zeigt noch +1,1 Prozentpunkte — reines Rauschen, das aber ohne Weiteres als „leichte Outperformance“ verkauft werden könnte.</li>
</ul>
<p><strong>Die Lehre:</strong> Wenn Sie in einem Backtest eine Überrendite von 15 % sehen, ist die erste Frage nicht „Wie kann ich das nutzen?“, sondern <strong>„Wo ist mein Fehler?“</strong>. Und wenn Sie keinen finden, suchen Sie weiter — die Wahrscheinlichkeit, dass Sie tatsächlich eine Goldader entdeckt haben, ist deutlich kleiner als die, dass ein Index verrutscht ist.</p>
<p>Die fünf Fallen im Einzelnen:</p>
<table>
<colgroup>
<col style="width: 25%" />
<col style="width: 25%" />
<col style="width: 25%" />
<col style="width: 25%" />
</colgroup>
<thead>
<tr class="header">
<th>#</th>
<th>Falle</th>
<th>Was passiert</th>
<th>Gegenmittel</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>1</td>
<td><strong>Lookahead-Bias</strong></td>
<td>Entscheidungen nutzen Daten, die es noch nicht gab</td>
<td>Zeitindex prüfen; Selbsttest wie oben</td>
</tr>
<tr class="even">
<td>2</td>
<td><strong>Survivorship-Bias</strong></td>
<td>Nur heute existierende Titel im Universum</td>
<td>Historische Indexzusammensetzung verwenden</td>
</tr>
<tr class="odd">
<td>3</td>
<td><strong>Kostenblindheit</strong></td>
<td>Gebühren, Spread, Slippage ignoriert</td>
<td>Kosten im Modell <strong>und</strong> in der Verbuchung</td>
</tr>
<tr class="even">
<td>4</td>
<td><strong>Overfitting</strong></td>
<td>Parameter auf die Vergangenheit getrimmt</td>
<td>Out-of-Sample-Zeitraum strikt zurückhalten</td>
</tr>
<tr class="odd">
<td>5</td>
<td><strong>Data Snooping</strong></td>
<td>Viele Varianten getestet, beste berichtet</td>
<td>Zahl der Versuche protokollieren; Deflated Sharpe Ratio</td>
</tr>
</tbody>
</table>
<blockquote>
<p><strong>🎯 Die unbequeme Wahrheit</strong> Falle 5 ist die gefährlichste, weil sie sich nicht im Code zeigt, sondern im <strong>Arbeitsprozess</strong>. Wer zwanzig Parameterkombinationen durchprobiert und die beste berichtet, hat keine Strategie gefunden, sondern eine Zufallsschwankung ausgewählt. Marcos López de Prado hat gezeigt, dass die meisten veröffentlichten Backtests genau daran scheitern. <strong>Protokollieren Sie, wie viele Varianten Sie getestet haben</strong> — und berichten Sie diese Zahl mit.</p>
</blockquote>
<hr />
<h2 id="sec:handelsmaschine-vom-backtest-zum-betrieb">21.7 Vom Backtest zum Betrieb</h2>
<p>Ein guter Backtest ist eine <strong>notwendige</strong>, keine hinreichende Bedingung. Was zwischen Backtest und echtem Einsatz liegt:</p>
<table>
<colgroup>
<col style="width: 33%" />
<col style="width: 33%" />
<col style="width: 33%" />
</colgroup>
<thead>
<tr class="header">
<th>Schritt</th>
<th>Frage</th>
<th>Typisches Ergebnis</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><strong>Papierhandel</strong></td>
<td>Funktioniert es auf Live-Daten ohne Geld?</td>
<td>36 Monate, oft schlechter als der Backtest</td>
</tr>
<tr class="even">
<td><strong>Kapazitätsanalyse</strong></td>
<td>Bewegt meine eigene Order den Markt?</td>
<td>Begrenzt das einsetzbare Volumen</td>
</tr>
<tr class="odd">
<td><strong>Regimewechsel-Test</strong></td>
<td>Hält es in Krisen?</td>
<td>Backtest über 2008, 2020 separat auswerten</td>
</tr>
<tr class="even">
<td><strong>Betriebsrisiken</strong></td>
<td>Was, wenn der Datenfeed ausfällt?</td>
<td>Fallback-Strategie, Monitoring</td>
</tr>
<tr class="odd">
<td><strong>Regulatorik</strong></td>
<td>MiFID II, Best Execution, Dokumentation</td>
<td>Protokollpflichten</td>
</tr>
</tbody>
</table>
<blockquote>
<p><strong>⚠️ Der Realitätsabschlag</strong> Als Faustregel gilt: <strong>Die reale Performance liegt deutlich unter dem Backtest</strong> — durch Slippage, verzögerte Ausführung, Datenrevisionen und schlicht dadurch, dass die Zukunft nicht die Vergangenheit ist. Wer einen Backtest mit Sharpe 1,5 hat, sollte real mit deutlich weniger rechnen. Eine Strategie, die im Backtest nur knapp die Benchmark schlägt, wird es real nicht tun.</p>
</blockquote>
<hr />
<h2 id="sec:handelsmaschine-uebungsaufgaben">21.8 Übungsaufgaben</h2>
<blockquote>
<p>Lösungen: <a href="anhang-loesungen.html#sec:loesungen-handelsmaschine">Abschnitt A.21</a>.</p>
</blockquote>
<p><strong>Aufgabe 21.1 ⭐ — Lookahead erkennen.</strong> Welche dieser Zeilen enthalten Lookahead-Bias? Begründen Sie: (a) <code>signal = kurse.loc[:heute].pct_change().mean()</code> (b) <code>signal = kurse.pct_change().mean()</code> (c) <code>vola = renditen.std()</code> (berechnet einmal über den gesamten Zeitraum) (d) <code>w = optimiere(renditen.loc[:heute]); rendite = w @ renditen.loc[heute]</code> (e) <code>universum = [t for t in alle if kurse[t].notna().all()]</code></p>
<p><strong>Aufgabe 21.2 ⭐ — Kennzahlen deuten.</strong> Strategie A: CAGR 12 %, Vola 22 %, MaxDD 35 %. Strategie B: CAGR 8 %, Vola 9 %, MaxDD 12 %. Berechnen Sie Sharpe (bei <span class="math inline">r_f = 2\,\%</span>) und Calmar. Welche würden Sie einem Pensionsfonds empfehlen und warum?</p>
<p><strong>Aufgabe 21.3 ⭐⭐ — Rebalancing-Kalender prüfen.</strong> Bauen Sie den Fehler nach: Ersetzen Sie den korrigierten Kalender durch <code>resample("MS").first().index</code>. Zählen Sie, wie viele Rebalancings ausfallen, und vergleichen Sie die Backtest-Kennzahlen beider Varianten.</p>
<p><strong>Aufgabe 21.4 ⭐⭐ — Rebalancing-Frequenz.</strong> Variieren Sie die Frequenz: täglich, wöchentlich, monatlich, quartalsweise, jährlich. (a) Wie ändern sich Rendite, Turnover und Kosten? (b) Ab welcher Frequenz fressen die Kosten den Ertrag auf? (c) Was ist Ihre Empfehlung bei einem Gebührensatz von 0,15 %? Bei 0,5 %?</p>
<p><strong>Aufgabe 21.5 ⭐⭐⭐ — Krisenverhalten.</strong> Werten Sie den Backtest getrennt für Teilzeiträume aus (z. B. je Kalenderjahr). Erstellen Sie eine Tabelle mit CAGR, Vola und MaxDD je Jahr für Strategie und Benchmark. (a) In welchen Phasen schlägt die Strategie die Benchmark? (b) Ist das Muster stabil oder zufällig? (c) Was folgt daraus für die Beurteilung der Gesamtkennzahl?</p>
<p><strong>Aufgabe 21.6 ⭐⭐⭐ — Data Snooping messen.</strong> Testen Sie 30 Varianten Ihrer Strategie (verschiedene Rückblickfenster, Risikoaversionen, Positionsgrenzen). Berichten Sie: (a) die beste Sharpe Ratio, (b) die mittlere Sharpe Ratio über alle Varianten, (c) die Sharpe Ratio der besten Variante <strong>auf einem zurückgehaltenen Zeitraum</strong>. Was lernen Sie aus der Differenz zwischen (a) und (c)?</p>
<hr />
<h2 id="sec:handelsmaschine-denkfehler">21.9 Finde den Denkfehler</h2>
<p><code>Backtest_Fallen.py</code> zeigt drei Fehler, die <strong>im</strong> Backtest stecken: Lookahead, Survivorship, vergessene Kosten. Alle drei kann man einem Programm ansehen, wenn man es liest.</p>
<p>Die beiden übrigen Selbsttäuschungen sind anders gebaut. Sie entstehen nicht im Code, sondern in der <strong>Arbeitsweise davor</strong> — und hinterlassen im Programm keine Spur. Ein Backtest, der zwanzigmal nachjustiert wurde, sieht am Ende genauso sauber aus wie einer, der beim ersten Versuch funktioniert hat.</p>
<div class="card card-denkfehler">
<blockquote>
<p><strong>🐛 Finde den Denkfehler: Die Strategie mit dem hochsignifikanten Ergebnis</strong></p>
<p>Eine Analystin entwickelt über mehrere Wochen eine Handelsstrategie. Sie probiert verschiedene Signalfenster, Schwellenwerte, Filter und Haltedauern durch — insgesamt rund <strong>hundert Varianten</strong>. Die beste testet sie sauber: kein Lookahead, korrektes Universum, Transaktionskosten berücksichtigt.</p>
<p>Das Ergebnis über fünf Jahre: <strong>Sharpe-Kennzahl 1,13</strong>, p-Wert <strong>0,0094</strong>. Der Backtest ist handwerklich einwandfrei — jede der drei Fallen aus <code>Backtest_Fallen.py</code> wurde vermieden. Sie stellt die Strategie vor.</p>
<p><strong>Ihre Aufgabe:</strong> (a) Der Backtest ist korrekt. Warum ist das Ergebnis trotzdem wertlos? (b) Sehen Sie sich unten die letzte Spalte der Tabelle an: Sie bleibt konstant bei 5 %, obwohl die Zahl der Versuche um das Tausendfache wächst. Wieso ist das kein Widerspruch, sondern der Kern des Problems? (c) Welche Zahl fehlt in ihrem Bericht — eine Zahl, die in keinem Backtest-Ergebnis steht und die sie selbst aufschreiben müsste? (d) Nennen Sie drei Gegenmittel und begründen Sie, warum das dritte nur <strong>einmal</strong> verwendbar ist.</p>
<p><em>Auflösung: <a href="anhang-loesungen.html#sec:loesungen-handelsmaschine">Abschnitt A.21</a>.</em></p>
</blockquote>
</div>
<p>Das folgende Programm macht den Effekt messbar. Es testet Strategien, die <strong>nachweislich</strong> keinerlei Vorteil haben — ihre Tagesergebnisse sind Zufallszahlen mit Erwartungswert exakt null — und zeigt, wie gut die jeweils beste davon aussieht, allein als Funktion der Anzahl der Versuche.</p>
<div class="sourceCode" id="cb11"><pre class="sourceCode python"><code class="sourceCode python"><span id="cb11-1"><a href="#cb11-1" aria-hidden="true" tabindex="-1"></a><span class="co">#!/usr/bin/env python3</span></span>
<span id="cb11-2"><a href="#cb11-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb11-3"><a href="#cb11-3" aria-hidden="true" tabindex="-1"></a><span class="co"># Data_Snooping.py</span></span>
<span id="cb11-4"><a href="#cb11-4" aria-hidden="true" tabindex="-1"></a><span class="co">&quot;&quot;&quot;</span></span>
<span id="cb11-5"><a href="#cb11-5" aria-hidden="true" tabindex="-1"></a><span class="co">Kapitel Handelsmaschine: Die Selbsttaeuschung, die man einem Backtest nicht ansieht.</span></span>
<span id="cb11-6"><a href="#cb11-6" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb11-7"><a href="#cb11-7" aria-hidden="true" tabindex="-1"></a><span class="co">Backtest_Fallen.py zeigt drei Fehler, die IM Backtest stecken: Lookahead,</span></span>
<span id="cb11-8"><a href="#cb11-8" aria-hidden="true" tabindex="-1"></a><span class="co">Survivorship, vergessene Kosten. Alle drei kann man einem Programm ansehen,</span></span>
<span id="cb11-9"><a href="#cb11-9" aria-hidden="true" tabindex="-1"></a><span class="co">wenn man es liest.</span></span>
<span id="cb11-10"><a href="#cb11-10" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb11-11"><a href="#cb11-11" aria-hidden="true" tabindex="-1"></a><span class="co">Die beiden anderen - Overfitting und Data Snooping - entstehen nicht im</span></span>
<span id="cb11-12"><a href="#cb11-12" aria-hidden="true" tabindex="-1"></a><span class="co">Backtest, sondern in der ARBEITSWEISE davor. Sie hinterlassen im Code keine</span></span>
<span id="cb11-13"><a href="#cb11-13" aria-hidden="true" tabindex="-1"></a><span class="co">Spur. Ein Backtest, der zwanzigmal angepasst wurde, sieht am Ende genauso</span></span>
<span id="cb11-14"><a href="#cb11-14" aria-hidden="true" tabindex="-1"></a><span class="co">sauber aus wie einer, der beim ersten Versuch funktioniert hat.</span></span>
<span id="cb11-15"><a href="#cb11-15" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb11-16"><a href="#cb11-16" aria-hidden="true" tabindex="-1"></a><span class="co">Dieses Programm macht den Effekt messbar. Es testet Strategien, die</span></span>
<span id="cb11-17"><a href="#cb11-17" aria-hidden="true" tabindex="-1"></a><span class="co">NACHWEISLICH keinerlei Prognosekraft haben (ihre taeglichen Ergebnisse sind</span></span>
<span id="cb11-18"><a href="#cb11-18" aria-hidden="true" tabindex="-1"></a><span class="co">Zufallszahlen mit Erwartungswert exakt null), und zeigt, wie gut die jeweils</span></span>
<span id="cb11-19"><a href="#cb11-19" aria-hidden="true" tabindex="-1"></a><span class="co">BESTE davon aussieht - allein als Funktion der Anzahl der Versuche.</span></span>
<span id="cb11-20"><a href="#cb11-20" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb11-21"><a href="#cb11-21" aria-hidden="true" tabindex="-1"></a><span class="co">Benoetigt: numpy, scipy</span></span>
<span id="cb11-22"><a href="#cb11-22" aria-hidden="true" tabindex="-1"></a><span class="co">&quot;&quot;&quot;</span></span>
<span id="cb11-23"><a href="#cb11-23" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb11-24"><a href="#cb11-24" aria-hidden="true" tabindex="-1"></a><span class="im">from</span> __future__ <span class="im">import</span> annotations</span>
<span id="cb11-25"><a href="#cb11-25" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb11-26"><a href="#cb11-26" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> numpy <span class="im">as</span> np</span>
<span id="cb11-27"><a href="#cb11-27" aria-hidden="true" tabindex="-1"></a><span class="im">from</span> scipy <span class="im">import</span> stats</span>
<span id="cb11-28"><a href="#cb11-28" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb11-29"><a href="#cb11-29" aria-hidden="true" tabindex="-1"></a>HANDELSTAGE <span class="op">=</span> <span class="dv">1260</span> <span class="co"># fuenf Jahre</span></span>
<span id="cb11-30"><a href="#cb11-30" aria-hidden="true" tabindex="-1"></a>TAGESSCHWANKUNG <span class="op">=</span> <span class="fl">0.010</span> <span class="co"># 1 % taeglich</span></span>
<span id="cb11-31"><a href="#cb11-31" aria-hidden="true" tabindex="-1"></a>WIEDERHOLUNGEN <span class="op">=</span> <span class="dv">300</span> <span class="co"># damit die Tabelle nicht vom Zufall abhaengt</span></span>
<span id="cb11-32"><a href="#cb11-32" aria-hidden="true" tabindex="-1"></a>NIVEAU <span class="op">=</span> <span class="fl">0.05</span></span>
<span id="cb11-33"><a href="#cb11-33" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb11-34"><a href="#cb11-34" aria-hidden="true" tabindex="-1"></a>RNG <span class="op">=</span> np.random.default_rng(<span class="dv">42</span>)</span>
<span id="cb11-35"><a href="#cb11-35" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb11-36"><a href="#cb11-36" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb11-37"><a href="#cb11-37" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> bewerte(ergebnisse: np.ndarray) <span class="op">-&gt;</span> <span class="bu">tuple</span>[np.ndarray, np.ndarray]:</span>
<span id="cb11-38"><a href="#cb11-38" aria-hidden="true" tabindex="-1"></a> <span class="co">&quot;&quot;&quot;Sharpe-Kennzahl und einseitiger p-Wert je Strategie.</span></span>
<span id="cb11-39"><a href="#cb11-39" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb11-40"><a href="#cb11-40" aria-hidden="true" tabindex="-1"></a><span class="co"> ergebnisse hat die Form (Strategien, Handelstage). Der p-Wert prueft die</span></span>
<span id="cb11-41"><a href="#cb11-41" aria-hidden="true" tabindex="-1"></a><span class="co"> Nullhypothese &#39;mittleres Tagesergebnis ist null&#39; - also genau die Frage,</span></span>
<span id="cb11-42"><a href="#cb11-42" aria-hidden="true" tabindex="-1"></a><span class="co"> die ein Backtest beantworten soll.</span></span>
<span id="cb11-43"><a href="#cb11-43" aria-hidden="true" tabindex="-1"></a><span class="co"> &quot;&quot;&quot;</span></span>
<span id="cb11-44"><a href="#cb11-44" aria-hidden="true" tabindex="-1"></a> mittel <span class="op">=</span> ergebnisse.mean(axis<span class="op">=</span><span class="dv">1</span>)</span>
<span id="cb11-45"><a href="#cb11-45" aria-hidden="true" tabindex="-1"></a> streuung <span class="op">=</span> ergebnisse.std(axis<span class="op">=</span><span class="dv">1</span>, ddof<span class="op">=</span><span class="dv">1</span>)</span>
<span id="cb11-46"><a href="#cb11-46" aria-hidden="true" tabindex="-1"></a> sharpe <span class="op">=</span> mittel <span class="op">/</span> streuung <span class="op">*</span> np.sqrt(<span class="dv">252</span>)</span>
<span id="cb11-47"><a href="#cb11-47" aria-hidden="true" tabindex="-1"></a> t_wert <span class="op">=</span> mittel <span class="op">/</span> (streuung <span class="op">/</span> np.sqrt(ergebnisse.shape[<span class="dv">1</span>]))</span>
<span id="cb11-48"><a href="#cb11-48" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> sharpe, stats.t.sf(t_wert, ergebnisse.shape[<span class="dv">1</span>] <span class="op">-</span> <span class="dv">1</span>)</span>
<span id="cb11-49"><a href="#cb11-49" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb11-50"><a href="#cb11-50" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb11-51"><a href="#cb11-51" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> probiere(anzahl_strategien: <span class="bu">int</span>) <span class="op">-&gt;</span> <span class="bu">tuple</span>[<span class="bu">float</span>, <span class="bu">float</span>, <span class="bu">float</span>, <span class="bu">float</span>]:</span>
<span id="cb11-52"><a href="#cb11-52" aria-hidden="true" tabindex="-1"></a> <span class="co">&quot;&quot;&quot;Liefert (beste Sharpe, ihr p-Wert, Anteil scheinbar signifikanter,</span></span>
<span id="cb11-53"><a href="#cb11-53" aria-hidden="true" tabindex="-1"></a><span class="co"> Anteil, der auch die Bonferroni-Huerde nimmt) - gemittelt ueber</span></span>
<span id="cb11-54"><a href="#cb11-54" aria-hidden="true" tabindex="-1"></a><span class="co"> WIEDERHOLUNGEN unabhaengige Durchgaenge.&quot;&quot;&quot;</span></span>
<span id="cb11-55"><a href="#cb11-55" aria-hidden="true" tabindex="-1"></a> beste_sharpe, beste_p, anteil_signifikant, ueberlebt_bonferroni <span class="op">=</span> [], [], [], []</span>
<span id="cb11-56"><a href="#cb11-56" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb11-57"><a href="#cb11-57" 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="cb11-58"><a href="#cb11-58" aria-hidden="true" tabindex="-1"></a> <span class="co"># Der springende Punkt: Erwartungswert exakt 0. Keine dieser</span></span>
<span id="cb11-59"><a href="#cb11-59" aria-hidden="true" tabindex="-1"></a> <span class="co"># Strategien hat einen echten Vorteil - per Konstruktion.</span></span>
<span id="cb11-60"><a href="#cb11-60" aria-hidden="true" tabindex="-1"></a> ergebnisse <span class="op">=</span> RNG.normal(<span class="fl">0.0</span>, TAGESSCHWANKUNG,</span>
<span id="cb11-61"><a href="#cb11-61" aria-hidden="true" tabindex="-1"></a> (anzahl_strategien, HANDELSTAGE))</span>
<span id="cb11-62"><a href="#cb11-62" aria-hidden="true" tabindex="-1"></a> sharpe, p_werte <span class="op">=</span> bewerte(ergebnisse)</span>
<span id="cb11-63"><a href="#cb11-63" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb11-64"><a href="#cb11-64" aria-hidden="true" tabindex="-1"></a> beste <span class="op">=</span> <span class="bu">int</span>(sharpe.argmax())</span>
<span id="cb11-65"><a href="#cb11-65" aria-hidden="true" tabindex="-1"></a> beste_sharpe.append(<span class="bu">float</span>(sharpe[beste]))</span>
<span id="cb11-66"><a href="#cb11-66" aria-hidden="true" tabindex="-1"></a> beste_p.append(<span class="bu">float</span>(p_werte[beste]))</span>
<span id="cb11-67"><a href="#cb11-67" aria-hidden="true" tabindex="-1"></a> anteil_signifikant.append(<span class="bu">float</span>((p_werte <span class="op">&lt;</span> NIVEAU).mean()))</span>
<span id="cb11-68"><a href="#cb11-68" aria-hidden="true" tabindex="-1"></a> <span class="co"># Bonferroni: Wer n Tests macht, muss die Huerde durch n teilen.</span></span>
<span id="cb11-69"><a href="#cb11-69" aria-hidden="true" tabindex="-1"></a> ueberlebt_bonferroni.append(</span>
<span id="cb11-70"><a href="#cb11-70" aria-hidden="true" tabindex="-1"></a> <span class="bu">float</span>((p_werte <span class="op">&lt;</span> NIVEAU <span class="op">/</span> anzahl_strategien).mean()))</span>
<span id="cb11-71"><a href="#cb11-71" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb11-72"><a href="#cb11-72" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> (<span class="bu">float</span>(np.mean(beste_sharpe)), <span class="bu">float</span>(np.mean(beste_p)),</span>
<span id="cb11-73"><a href="#cb11-73" aria-hidden="true" tabindex="-1"></a> <span class="bu">float</span>(np.mean(anteil_signifikant)),</span>
<span id="cb11-74"><a href="#cb11-74" aria-hidden="true" tabindex="-1"></a> <span class="bu">float</span>(np.mean(ueberlebt_bonferroni)))</span>
<span id="cb11-75"><a href="#cb11-75" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb11-76"><a href="#cb11-76" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb11-77"><a href="#cb11-77" 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="cb11-78"><a href="#cb11-78" 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="cb11-79"><a href="#cb11-79" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; WIE GUT SIEHT DIE BESTE VON N STRATEGIEN AUS,&quot;</span>)</span>
<span id="cb11-80"><a href="#cb11-80" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; WENN KEINE EINZIGE ETWAS TAUGT?&quot;</span>)</span>
<span id="cb11-81"><a href="#cb11-81" 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="cb11-82"><a href="#cb11-82" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;</span><span class="sc">{</span>HANDELSTAGE<span class="sc">}</span><span class="ss"> Handelstage, taegliche Ergebnisse mit Erwartungswert &quot;</span></span>
<span id="cb11-83"><a href="#cb11-83" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;EXAKT null.&quot;</span>)</span>
<span id="cb11-84"><a href="#cb11-84" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;Jede Zeile ist der Mittelwert aus </span><span class="sc">{</span>WIEDERHOLUNGEN<span class="sc">}</span><span class="ss"> unabhaengigen &quot;</span></span>
<span id="cb11-85"><a href="#cb11-85" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;Durchgaengen.</span><span class="ch">\n</span><span class="ss">&quot;</span>)</span>
<span id="cb11-86"><a href="#cb11-86" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb11-87"><a href="#cb11-87" 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;getestet&#39;</span><span class="sc">:&gt;9}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;beste Sharpe&#39;</span><span class="sc">:&gt;14}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;ihr p-Wert&#39;</span><span class="sc">:&gt;12}</span><span class="ss"> &quot;</span></span>
<span id="cb11-88"><a href="#cb11-88" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;</span><span class="sc">{</span><span class="st">&#39;scheinbar sign.&#39;</span><span class="sc">:&gt;16}</span><span class="ss">&quot;</span>)</span>
<span id="cb11-89"><a href="#cb11-89" 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="cb11-90"><a href="#cb11-90" aria-hidden="true" tabindex="-1"></a> ergebnisse <span class="op">=</span> {}</span>
<span id="cb11-91"><a href="#cb11-91" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> anzahl <span class="kw">in</span> (<span class="dv">1</span>, <span class="dv">10</span>, <span class="dv">100</span>, <span class="dv">1000</span>):</span>
<span id="cb11-92"><a href="#cb11-92" aria-hidden="true" tabindex="-1"></a> sharpe, p_wert, anteil, _ <span class="op">=</span> probiere(anzahl)</span>
<span id="cb11-93"><a href="#cb11-93" aria-hidden="true" tabindex="-1"></a> ergebnisse[anzahl] <span class="op">=</span> (sharpe, p_wert, anteil)</span>
<span id="cb11-94"><a href="#cb11-94" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;</span><span class="sc">{</span>anzahl<span class="sc">:9d}</span><span class="ss"> </span><span class="sc">{</span>sharpe<span class="sc">:14.2f}</span><span class="ss"> </span><span class="sc">{</span>p_wert<span class="sc">:12.4f}</span><span class="ss"> </span><span class="sc">{</span>anteil <span class="op">*</span> <span class="dv">100</span><span class="sc">:15.1f}</span><span class="ss"> %&quot;</span>)</span>
<span id="cb11-95"><a href="#cb11-95" 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="cb11-96"><a href="#cb11-96" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb11-97"><a href="#cb11-97" aria-hidden="true" tabindex="-1"></a> sharpe_100, p_100, _ <span class="op">=</span> ergebnisse[<span class="dv">100</span>]</span>
<span id="cb11-98"><a href="#cb11-98" aria-hidden="true" tabindex="-1"></a> sharpe_1000, p_1000, _ <span class="op">=</span> ergebnisse[<span class="dv">1000</span>]</span>
<span id="cb11-99"><a href="#cb11-99" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb11-100"><a href="#cb11-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">Lesen Sie die dritte Spalte von unten nach oben:&quot;</span>)</span>
<span id="cb11-101"><a href="#cb11-101" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; Wer </span><span class="sc">{</span><span class="dv">1000</span><span class="sc">}</span><span class="ss"> Varianten durchprobiert, findet eine mit p = </span><span class="sc">{</span>p_1000<span class="sc">:.4f}</span><span class="ss"> -&quot;</span>)</span>
<span id="cb11-102"><a href="#cb11-102" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; ein Wert, den jede Zeitschrift als &#39;hochsignifikant&#39; druckt.&quot;</span>)</span>
<span id="cb11-103"><a href="#cb11-103" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; Wer </span><span class="sc">{</span><span class="dv">100</span><span class="sc">}</span><span class="ss"> probiert, findet p = </span><span class="sc">{</span>p_100<span class="sc">:.4f}</span><span class="ss"> und eine Sharpe-Kennzahl&quot;</span>)</span>
<span id="cb11-104"><a href="#cb11-104" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; von </span><span class="sc">{</span>sharpe_100<span class="sc">:.2f}</span><span class="ss"> - damit geht man zum Vorgesetzten.&quot;</span>)</span>
<span id="cb11-105"><a href="#cb11-105" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; Und keine dieser Strategien hat auch nur den Hauch eines Vorteils.&quot;</span>)</span>
<span id="cb11-106"><a href="#cb11-106" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>()</span>
<span id="cb11-107"><a href="#cb11-107" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;Die LETZTE Spalte bleibt dagegen konstant bei rund 5 %. Das ist kein&quot;</span>)</span>
<span id="cb11-108"><a href="#cb11-108" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;Widerspruch, sondern die Definition: Ein Test zum 5-%-Niveau irrt in&quot;</span>)</span>
<span id="cb11-109"><a href="#cb11-109" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;5 </span><span class="sc">% d</span><span class="st">er Faelle. Was sich aendert, ist nicht der Anteil, sondern die&quot;</span>)</span>
<span id="cb11-110"><a href="#cb11-110" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;ANZAHL - und weil man nur die beste Strategie praesentiert, sieht man&quot;</span>)</span>
<span id="cb11-111"><a href="#cb11-111" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;von allen Fehlversuchen genau einen: den erfolgreichsten.&quot;</span>)</span>
<span id="cb11-112"><a href="#cb11-112" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb11-113"><a href="#cb11-113" 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">78</span>)</span>
<span id="cb11-114"><a href="#cb11-114" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; WAS MAN DAGEGEN TUN KANN&quot;</span>)</span>
<span id="cb11-115"><a href="#cb11-115" 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="cb11-116"><a href="#cb11-116" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;1. ZAEHLEN. Notieren Sie, wie viele Varianten Sie ausprobiert haben -&quot;</span>)</span>
<span id="cb11-117"><a href="#cb11-117" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; auch die verworfenen, auch die &#39;nur mal schnell&#39;. Ohne diese Zahl&quot;</span>)</span>
<span id="cb11-118"><a href="#cb11-118" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; ist kein p-Wert und keine Sharpe-Kennzahl interpretierbar.&quot;</span>)</span>
<span id="cb11-119"><a href="#cb11-119" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>()</span>
<span id="cb11-120"><a href="#cb11-120" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;2. KORRIGIEREN. Die Bonferroni-Korrektur teilt die Huerde durch die&quot;</span>)</span>
<span id="cb11-121"><a href="#cb11-121" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; Zahl der Versuche:&quot;</span>)</span>
<span id="cb11-122"><a href="#cb11-122" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> anzahl <span class="kw">in</span> (<span class="dv">10</span>, <span class="dv">100</span>, <span class="dv">1000</span>):</span>
<span id="cb11-123"><a href="#cb11-123" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; bei </span><span class="sc">{</span>anzahl<span class="sc">:5d}</span><span class="ss"> Versuchen: p &lt; </span><span class="sc">{</span>NIVEAU <span class="op">/</span> anzahl<span class="sc">:.5f}</span><span class="ss"> statt &quot;</span></span>
<span id="cb11-124"><a href="#cb11-124" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;p &lt; </span><span class="sc">{</span>NIVEAU<span class="sc">:.2f}</span><span class="ss">&quot;</span>)</span>
<span id="cb11-125"><a href="#cb11-125" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; Sie ist konservativ, aber sie ist ehrlich. Fuer Handelsstrategien&quot;</span>)</span>
<span id="cb11-126"><a href="#cb11-126" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; gibt es verfeinerte Varianten (Deflated Sharpe Ratio nach Bailey&quot;</span>)</span>
<span id="cb11-127"><a href="#cb11-127" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; und Lopez de Prado), die dasselbe Prinzip verfolgen.&quot;</span>)</span>
<span id="cb11-128"><a href="#cb11-128" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>()</span>
<span id="cb11-129"><a href="#cb11-129" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;3. ZURUECKHALTEN. Legen Sie einen Zeitraum beiseite, BEVOR Sie&quot;</span>)</span>
<span id="cb11-130"><a href="#cb11-130" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; anfangen, und ruehren Sie ihn bis zum Schluss nicht an. Er ist&quot;</span>)</span>
<span id="cb11-131"><a href="#cb11-131" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; der einzige Test, den Sie nicht durch Probieren verderben koennen -&quot;</span>)</span>
<span id="cb11-132"><a href="#cb11-132" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; und er ist genau einmal verwendbar.&quot;</span>)</span>
<span id="cb11-133"><a href="#cb11-133" 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>
<p><strong>Erwartete Ausgabe:</strong></p>
<pre><code>==============================================================================
WIE GUT SIEHT DIE BESTE VON N STRATEGIEN AUS,
WENN KEINE EINZIGE ETWAS TAUGT?
==============================================================================
1260 Handelstage, taegliche Ergebnisse mit Erwartungswert EXAKT null.
Jede Zeile ist der Mittelwert aus 300 unabhaengigen Durchgaengen.
getestet beste Sharpe ihr p-Wert scheinbar sign.
------------------------------------------------------------------------------
1 -0.00 0.5101 6.0 %
10 0.69 0.0885 4.9 %
100 1.13 0.0094 4.8 %
1000 1.44 0.0010 5.0 %
------------------------------------------------------------------------------
Lesen Sie die dritte Spalte von unten nach oben:
Wer 1000 Varianten durchprobiert, findet eine mit p = 0.0010 -
ein Wert, den jede Zeitschrift als &#39;hochsignifikant&#39; druckt.
Wer 100 probiert, findet p = 0.0094 und eine Sharpe-Kennzahl
von 1.13 - damit geht man zum Vorgesetzten.
Und keine dieser Strategien hat auch nur den Hauch eines Vorteils.
Die LETZTE Spalte bleibt dagegen konstant bei rund 5 %. Das ist kein
Widerspruch, sondern die Definition: Ein Test zum 5-%-Niveau irrt in
5 % der Faelle. Was sich aendert, ist nicht der Anteil, sondern die
ANZAHL - und weil man nur die beste Strategie praesentiert, sieht man
von allen Fehlversuchen genau einen: den erfolgreichsten.
==============================================================================
WAS MAN DAGEGEN TUN KANN
==============================================================================
1. ZAEHLEN. Notieren Sie, wie viele Varianten Sie ausprobiert haben -
auch die verworfenen, auch die &#39;nur mal schnell&#39;. Ohne diese Zahl
ist kein p-Wert und keine Sharpe-Kennzahl interpretierbar.
2. KORRIGIEREN. Die Bonferroni-Korrektur teilt die Huerde durch die
Zahl der Versuche:
bei 10 Versuchen: p &lt; 0.00500 statt p &lt; 0.05
bei 100 Versuchen: p &lt; 0.00050 statt p &lt; 0.05
bei 1000 Versuchen: p &lt; 0.00005 statt p &lt; 0.05
Sie ist konservativ, aber sie ist ehrlich. Fuer Handelsstrategien
gibt es verfeinerte Varianten (Deflated Sharpe Ratio nach Bailey
und Lopez de Prado), die dasselbe Prinzip verfolgen.
3. ZURUECKHALTEN. Legen Sie einen Zeitraum beiseite, BEVOR Sie
anfangen, und ruehren Sie ihn bis zum Schluss nicht an. Er ist
der einzige Test, den Sie nicht durch Probieren verderben koennen -
und er ist genau einmal verwendbar.
==============================================================================</code></pre>
<blockquote>
<p><strong>🎯 Merksatz</strong> Ein Backtest-Ergebnis ist ohne die Angabe <strong>„wie viele Varianten wurden probiert?“</strong> nicht interpretierbar. Diese Zahl steht in keinem Programm und in keiner Kennzahl — sie existiert nur im Kopf der Person, die die Arbeit gemacht hat. Genau deshalb ist sie die erste Frage, die man einem vorgelegten Backtest stellen sollte, und die erste Zahl, die man beim eigenen mitschreiben muss.</p>
</blockquote>
<hr />
<h2 id="sec:handelsmaschine-quiz">21.10 Micro-Quiz</h2>
<div class="card card-quiz">
<blockquote>
<p><strong>❓ Micro-Quiz 21: Drei Fragen zum Selbstcheck</strong></p>
<p>Genau eine Antwort ist jeweils richtig. Auflösung in <a href="anhang-loesungen.html#quiz-loesung-handelsmaschine">Anhang A</a>.</p>
<p><strong>1. Zwei Strategien haben dieselbe Jahresrendite und dieselbe Sharpe-Kennzahl. Was wissen Sie über ihren Verlauf?</strong> (a) Er ist im Wesentlichen gleich — beide Kennzahlen zusammen beschreiben ihn vollständig. (b) <strong>Nichts.</strong> Beide Kennzahlen mitteln über die Zeit und verlieren die Reihenfolge. Dieselben Tagesrenditen können zu einem Rückschlag von 19 % oder von 98 % führen. (c) Die Strategie mit der höheren Schwankung hat den größeren Rückschlag.</p>
<p><strong>2. Sie haben 50 Strategievarianten getestet und die beste erreicht p = 0,01. Wie bewerten Sie das?</strong> (a) Hochsignifikant — die Wahrscheinlichkeit eines Zufallsbefunds liegt bei 1 %. (b) Bei 50 Versuchen ist ein p-Wert von 0,01 unauffällig: Schon rein zufällig erwartet man etwa 2,5 Treffer unter dem 5-%-Niveau. Die Bonferroni-Schwelle läge bei 0,001. (c) Der p-Wert ist für Handelsstrategien grundsätzlich ungeeignet.</p>
<p><strong>3. Warum ist ein zurückgehaltener Testzeitraum nur </strong>einmal** verwendbar?** (a) Weil die Daten danach veraltet sind. (b) Weil man ab dem Moment, in dem man das Ergebnis kennt und daraufhin etwas ändert, wieder anfängt zu probieren — der Zeitraum ist dann Teil der Suche geworden und nicht mehr unabhängig. (c) Weil sich die Kennzahlen bei wiederholter Berechnung numerisch verschlechtern.</p>
</blockquote>
</div>
<hr />
<h2 id="sec:handelsmaschine-selbsttest">21.11 Selbsttest</h2>
<blockquote>
<p>Antworten: <a href="anhang-loesungen.html#selbsttest-loesung-handelsmaschine">Anhang A</a>.</p>
</blockquote>
<ol type="1">
<li>Was ist die goldene Regel des Backtestens?</li>
<li>Warum sind Kalender-Monatsanfänge als Rebalancing-Termine falsch?</li>
<li>Wie funktioniert der Lookahead-Selbsttest, und warum ist er so wirksam?</li>
<li>Warum muss man Gewichtsdrift modellieren?</li>
<li>Warum ist Data Snooping gefährlicher als ein Programmierfehler?</li>
</ol>
<hr />
<h2 id="sec:handelsmaschine-zusammenfassung">21.12 Zusammenfassung</h2>
<ul>
<li><strong>Sechs Bausteine</strong> in fester Reihenfolge: Daten, Signal, Risiko, Optimierung, Rebalancing, Auswertung.</li>
<li><strong>Die goldene Regel</strong><strong>:</strong> Zum Zeitpunkt <span class="math inline">t</span> nur Information von <span class="math inline">t</span>. Der Selbsttest mit manipulierter Zukunft prüft das automatisch.</li>
<li><strong>Rebalancing-Termine müssen Handelstage sein</strong> — sonst fallen bis zu 28 % still aus.</li>
<li><strong>Zählen Sie mit:</strong> geplante gegen ausgeführte Rebalancings, kumulierter Turnover, Gesamtkosten. Abweichungen sind Fehlerhinweise.</li>
<li><strong>Fünf Selbsttäuschungen:</strong> Lookahead, Survivorship, Kostenblindheit, Overfitting, Data Snooping. Die ersten drei stecken im Code und sind lesbar; die letzten beiden entstehen in der <strong>Arbeitsweise</strong> und hinterlassen im Programm keine Spur.</li>
<li><strong>Ohne die Zahl der ausprobierten Varianten ist kein Backtest-Ergebnis interpretierbar.</strong> Auf reinem Rauschen liefert die Beste von hundert Versuchen im Mittel Sharpe 1,13 bei p = 0,0094. Protokollieren Sie mit, korrigieren Sie die Schwelle (Bonferroni, Deflated Sharpe) — und halten Sie einen Zeitraum zurück, den Sie genau <strong>einmal</strong> verwenden.</li>
<li><strong>Rendite und Sharpe verlieren die Reihenfolge.</strong> Dieselben Tagesrenditen können zu einem Rückschlag von 19 % oder 98 % führen. Berichten Sie den maximalen Rückschlag immer mit.</li>
<li><strong>Ein guter Backtest ist notwendig, nicht hinreichend.</strong> Zwischen ihm und dem Betrieb liegen Papierhandel, Kapazitätsanalyse und Betriebsrisiken.</li>
</ul>
<p><strong>Ausblick.</strong> <a href="praxisfallen.html#kap-praxisfallen">Kapitel 22</a> zieht die Summe aus allen Teilen: die typischen Praxisfallen und der Weg vom Skript zum produktiven System.</p>
</article>
<button type="button" class="fortschritt-knopf" data-kapitel="handelsmaschine.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="cvar.html"><svg class="icon" aria-hidden="true"><use href="#icon-chevron-left"></use></svg><span><small>Zurück</small>Kapitel 20: Tail-Risiko, CVaR und Transaktionskosten</span></a><a class="prev-next-knopf prev-next-next" href="synthese-anwendungen.html"><span><small>Weiter</small>Synthese Teil IV</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>