Version 04 als eigenes Repository
Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
commit
b7af2f1d9a
468 changed files with 262141 additions and 0 deletions
96
OR_HTML_04/programme/Backtest_Fallen.py
Normal file
96
OR_HTML_04/programme/Backtest_Fallen.py
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
# Backtest_Fallen.py
|
||||
"""
|
||||
Kapitel Handelsmaschine: Wie leicht man sich selbst betruegt.
|
||||
|
||||
Demonstriert an SIMULIERTEN Daten (also ohne jede echte Prognosekraft),
|
||||
wie gross die scheinbare Ueberrendite durch typische Backtest-Fehler wird.
|
||||
Weil die Daten reines Rauschen sind, ist JEDE positive Ueberrendite ein Artefakt.
|
||||
"""
|
||||
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
|
||||
|
||||
def erzeuge_zufallsmarkt(tage=1260, titel=15, seed=7):
|
||||
"""Reines Rauschen: kein Titel hat echte Prognosekraft."""
|
||||
rng = np.random.default_rng(seed)
|
||||
renditen = rng.normal(0.0003, 0.015, size=(tage, titel))
|
||||
index = pd.bdate_range("2020-01-01", periods=tage)
|
||||
return pd.DataFrame(renditen, index=index,
|
||||
columns=[f"T{i:02d}" for i in range(titel)])
|
||||
|
||||
|
||||
def strategie(renditen, lookahead=False, survivorship=False,
|
||||
ohne_kosten=False, gebuehr=0.0015):
|
||||
"""Einfache Momentum-Strategie mit optional eingebauten Fehlern."""
|
||||
fenster = 60
|
||||
kapital = [100.0]
|
||||
gewichte = np.ones(renditen.shape[1]) / renditen.shape[1]
|
||||
|
||||
daten = renditen
|
||||
if survivorship:
|
||||
# FEHLER: nur die im Nachhinein besten Titel ins Universum lassen
|
||||
beste = renditen.sum().nlargest(renditen.shape[1] // 2).index
|
||||
daten = renditen[beste]
|
||||
gewichte = np.ones(len(beste)) / len(beste)
|
||||
|
||||
for t in range(fenster, len(daten) - 1):
|
||||
if t % 21 == 0: # monatlich
|
||||
if lookahead:
|
||||
# FEHLER: Signal nutzt die Rendite von MORGEN
|
||||
signal = daten.iloc[t + 1].values
|
||||
else:
|
||||
signal = daten.iloc[t - fenster:t].mean().values
|
||||
|
||||
neue = (signal > np.median(signal)).astype(float)
|
||||
neue = neue / neue.sum() if neue.sum() > 0 else gewichte
|
||||
if not ohne_kosten:
|
||||
kapital[-1] *= (1 - np.abs(neue - gewichte).sum() * gebuehr)
|
||||
gewichte = neue
|
||||
|
||||
kapital.append(kapital[-1] * (1 + float(gewichte @ daten.iloc[t + 1].values)))
|
||||
return np.array(kapital)
|
||||
|
||||
|
||||
def cagr(verlauf, jahre):
|
||||
return (verlauf[-1] / verlauf[0]) ** (1 / jahre) - 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
renditen = erzeuge_zufallsmarkt()
|
||||
jahre = len(renditen) / 252
|
||||
markt = 100 * (1 + renditen.mean(axis=1)).cumprod().values
|
||||
|
||||
print("=" * 84)
|
||||
print(" DIE FUENF SELBSTTAEUSCHUNGEN - GEMESSEN AN REINEM RAUSCHEN")
|
||||
print("=" * 84)
|
||||
print("Die Daten sind zufaellig erzeugt. Es gibt KEINE echte Prognosekraft.")
|
||||
print("Jede Ueberrendite unten ist daher ein reines Artefakt.\n")
|
||||
print(f"{'Variante':<44} {'CAGR':>9} {'ggue. Markt':>13}")
|
||||
print("-" * 84)
|
||||
|
||||
basis = cagr(markt, jahre)
|
||||
print(f"{'Markt (Gleichgewichtung, Referenz)':<44} {basis*100:>8.2f} % "
|
||||
f"{0.0:>12.2f} %")
|
||||
|
||||
varianten = [
|
||||
("Ehrlich: Signal aus Vergangenheit, mit Kosten", {}),
|
||||
("FEHLER 1: Lookahead (Signal kennt morgen)", {"lookahead": True}),
|
||||
("FEHLER 2: Survivorship (nur Gewinner im Universum)", {"survivorship": True}),
|
||||
("FEHLER 3: Transaktionskosten ignoriert", {"ohne_kosten": True}),
|
||||
("FEHLER 1+2+3 kombiniert", {"lookahead": True, "survivorship": True,
|
||||
"ohne_kosten": True}),
|
||||
]
|
||||
for name, argumente in varianten:
|
||||
verlauf = strategie(renditen, **argumente)
|
||||
wert = cagr(verlauf, jahre)
|
||||
print(f"{name:<44} {wert*100:>8.2f} % {(wert-basis)*100:>+12.2f} %")
|
||||
|
||||
print("-" * 84)
|
||||
print("FEHLER 4 (Overfitting) und FEHLER 5 (Data Snooping) lassen sich so nicht")
|
||||
print("zeigen - sie entstehen erst durch WIEDERHOLTES Probieren. Faustregel:")
|
||||
print("Wer 20 Strategien testet, findet auch in reinem Rauschen eine mit")
|
||||
print("Signifikanz auf dem 5-%-Niveau. Genau das ist der Punkt.")
|
||||
print("=" * 84)
|
||||
Loading…
Reference in a new issue