#!/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)