Konfidenzintervalle (scipy-Bootstrap) — und sie korrigieren den Befund vom 06.08.
backtest_cost_gate.ci95(): 95-%-Bootstrap-KI des Mittelwerts (Perzentil, 2000 Resamples, random_state fest -> reproduzierbar, ab n>=20). kennzahlen() liefert es als neuen Schluessel `ci` — rein additiv, alle 11 Aufrufer lesen weiter nur n/wr/oer/sr/pf, keine dokumentierte Zahl verschiebt sich (geprueft). Sofortiger Ertrag: der erste Lauf relativiert das `3 von 7 tragen` vom 06.08. Auf der entscheidenden Spalte C (Beruehrungs-Fill): KONTROLLE Squeeze H1 [+0,037…+0,229] H2 [+0,225…+0,366] PDH / PDL H1 [-0,032…+0,544]⚠ H2 [+0,155…+0,764] Equal H/L H1 [+0,001…+0,425] H2 [-0,035…+0,356]⚠ Asian Range H1 [-0,067…+0,357]⚠ H2 [-0,060…+0,332]⚠ Nur der Squeeze hat in BEIDEN Haelften ein Intervall klar ueber null. Bei Asian Range enthalten beide die Null — als Ueberlebender war das zu stark formuliert. Formulierung auf `1 gesichert, 2 unklar` korrigiert. Zwei verschiedene Fragen, beide noetig: die 2-Stichproben-Regel prueft die Stabilitaet des VORZEICHENS ueber Regime, das KI die Unterscheidbarkeit vom RAUSCHEN innerhalb einer Stichprobe. Hier war Ersteres erfuellt, Letzteres nicht. Ein KI, das die Null enthaelt, heisst nicht widerlegt, sondern nicht belegt — und zwei gleichgerichtete Haelften sind zusammen mehr Evidenz als jede fuer sich. Grenze dokumentiert: der Bootstrap unterstellt unabhaengige Ziehungen. Fuer die sequentielle Sim mit EINEM Slot passt das; fuer ueberlappende Fenster (analyze_hl_funding.py) ist er zu optimistisch — dort braeuchte es einen Block-Bootstrap. ci95 ist dafuer ausdruecklich NICHT gedacht. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
e69907edbe
commit
79f3e8efe1
+53
-4
@@ -165,21 +165,70 @@ def sequentiell(K, H, L, C, pred, lo, hi):
|
||||
return rs
|
||||
|
||||
|
||||
_BOOT_N = 2000 # Resamples — fuer ein 95-%-KI reichlich und schnell genug
|
||||
_BOOT_MIN = 20 # darunter ist ein KI ohnehin nur ein Achselzucken
|
||||
|
||||
|
||||
def ci95(rs):
|
||||
"""95-%-Konfidenzintervall des MITTELWERTS (Bootstrap, Perzentil-Methode).
|
||||
|
||||
⚠⚠ WOZU. Das Projekt entscheidet ueber Punktschaetzungen plus die
|
||||
2-Stichproben-Regel. Ob ein ØR von +0,12 bei n=30 ueberhaupt von null zu
|
||||
unterscheiden ist, musste bisher jedes Mal im Kopf abgeschaetzt werden — am
|
||||
2026-08-06 gleich mehrfach (Buckets mit n=15–30, „Standardfehler ≈0,026",
|
||||
die Slippage-Tabelle). Mit einem KI steht es einfach da, und ein Ergebnis,
|
||||
dessen Intervall die Null enthaelt, ist als solches erkennbar.
|
||||
|
||||
⚠⚠ GRENZE, die man kennen MUSS: der Bootstrap unterstellt UNABHAENGIGE
|
||||
Ziehungen. Fuer die sequentielle Sim mit EINEM Positions-Slot stimmt das
|
||||
naeherungsweise — dort beginnt jeder Trade erst nach dem Exit des vorigen.
|
||||
Fuer UEBERLAPPENDE Beobachtungen ist er ZU OPTIMISTISCH: stuendliche Werte
|
||||
mit 24-h-Horizont teilen 23 von 24 Stunden, das effektive n ist ~n/24. Genau
|
||||
daran ist `analyze_hl_funding.py` am 06.08. einmal aufgelaufen (dort wird
|
||||
entueberlappt statt gebootstrappt). Fuer solche Faelle braeuchte es einen
|
||||
BLOCK-Bootstrap — dieses `ci95` ist dafuer NICHT gedacht.
|
||||
|
||||
`random_state` fest, damit derselbe Lauf dieselbe Zahl liefert.
|
||||
Rueckgabe (lo, hi) oder None (zu wenige Werte / scipy fehlt).
|
||||
"""
|
||||
if not rs or len(rs) < _BOOT_MIN:
|
||||
return None
|
||||
try:
|
||||
import numpy as np
|
||||
from scipy.stats import bootstrap
|
||||
r = bootstrap((np.asarray(rs, dtype=float),), np.mean,
|
||||
n_resamples=_BOOT_N, confidence_level=0.95,
|
||||
method="percentile", random_state=0)
|
||||
return (float(r.confidence_interval.low),
|
||||
float(r.confidence_interval.high))
|
||||
except Exception:
|
||||
return None # nie den Backtest daran scheitern lassen
|
||||
|
||||
|
||||
def kennzahlen(rs):
|
||||
if not rs:
|
||||
return None
|
||||
n = len(rs); s = sum(rs)
|
||||
g = sum(x for x in rs if x > 0); v = -sum(x for x in rs if x < 0)
|
||||
# ⚠ `ci` ist rein ADDITIV — alle 11 bestehenden Aufrufer lesen nur n/wr/oer/
|
||||
# sr/pf und bleiben unveraendert. Keine dokumentierte Zahl verschiebt sich.
|
||||
return {"n": n, "wr": 100.0 * sum(1 for x in rs if x > 0) / n,
|
||||
"oer": s / n, "sr": s, "pf": (g / v) if v > 0 else float("inf")}
|
||||
"oer": s / n, "sr": s, "pf": (g / v) if v > 0 else float("inf"),
|
||||
"ci": ci95(rs)}
|
||||
|
||||
|
||||
def zeile(lbl, k1, k2):
|
||||
def zeile(lbl, k1, k2, ci=False):
|
||||
"""`ci=True` haengt das 95-%-KI des ØR an — und markiert mit `~`, wenn es die
|
||||
Null enthaelt (dann ist das Vorzeichen nicht gesichert)."""
|
||||
def f(k):
|
||||
if not k:
|
||||
return f"{'—':>32}"
|
||||
return (f"{k['n']:>5} {k['wr']:>5.1f}% {k['oer']:>+7.3f} "
|
||||
f"{k['pf']:>5.2f} {k['sr']:>+8.1f}")
|
||||
s = (f"{k['n']:>5} {k['wr']:>5.1f}% {k['oer']:>+7.3f} "
|
||||
f"{k['pf']:>5.2f} {k['sr']:>+8.1f}")
|
||||
if ci and k.get("ci"):
|
||||
lo, hi = k["ci"]
|
||||
s += f" [{lo:+.3f}…{hi:+.3f}]{'~' if lo <= 0 <= hi else ' '}"
|
||||
return s
|
||||
print(f" {lbl:<26}{f(k1)} |{f(k2)}")
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user