Files
AH-Oil-Trader/backtest_conf_gated.py
T
Axel HocksandClaude Opus 5 f15a36138f Konfidenz-Schwelle und M15-Selektivitaet gemessen: keine Aenderung
(2) conf_pct trennt im Markt-Modus NICHT: der Bandverlauf ist nicht
gleichgerichtet (H1 bestes Band 0-44, schlechtestes 55-64), und die Schwelle
bewegt nichts -- >=0 liefert H1 -0,189, >=55 liefert -0,197. 11,4 % Signalmenge
fuer 0,017 R. Mit ruhender Order trennt die obere Haelfte: >=75 schlaegt die
Live-Schwelle im OeR beidhaelftig (-0,001/+0,147 gegen -0,070/+0,081), aber die
Summe faellt (+82,8 gegen +146,1). Regel verlangte beides -> nicht geaendert,
Regel NICHT nachtraeglich gelockert. 27. verworfener Signal-Eingriff.

Dritte unabhaengige Bestaetigung des Ausfuehrungs-Hebels am selben Tag:
Level gegen Markt +0,127/+0,096 R, groessengleich zu backtest_entryroom_level.

(3) M15-Karte steht 37,8 % der ZEIT gruen (in Wechseln gerechnet waeren es 49 %
-- gruene Phasen sind kuerzer). Handlungsrelevanter Zustand entkoppelt 54,1 % /
51,5 %, beide KI enthalten die 50 %. 205 von 350 unabhaengigen Faellen, also in
gut zwei Wochen entscheidbar. Die Hysterese vom 12.08. wirkt (53 -> 54,1 %).
Gate 3b ins Banner zu ziehen ist durch den Entry-Raum-Lauf schon beantwortet:
diese Dimension trennt nicht.

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

157 lines
6.3 KiB
Python

"""Trennt `conf_pct` ueberhaupt? Und ist 55 die richtige Schwelle? (2026-08-19)
FRAGE (User, Punkt (2) der Verbesserungsliste). `min_conf` blockt live **11,4 %**
aller Empfehlungen. Die Schwelle 55 stammt aus `backtest_conf.py`: Band 40-54 %
gemessen negativ (-0,025), >= 55 % positiv (+0,058).
WARUM DAS NEU ZU MESSEN IST -- drei Gruende, jeder fuer sich hinreichend:
1. `backtest_conf.py` misst `_build` ALLEIN, ohne Breakout-Bestaetigung und
ohne Entry-Raum-Gate. Genau die beiden sind live die groessten Blocker
(dokumentiert 2026-08-05: 77,9 % gerichtet allein vs. 19,5 % im vollen
Stack). Es ist also eine andere Population.
2. Es nutzt ein aelteres Exit-Modell, nicht `exit_model.LIVE`.
3. `analyze_verdict_calibration.py` hat `conf_pct` als **nicht kalibriert**
ausgewiesen und in H2 sogar INVERTIERT (85-100 % -> 40 % Trefferquote).
Ob es wenigstens im ERTRAG trennt, ist damit offen.
AUFBAU. Voller Live-Gate-Stack, aber `_MIN_CONF` auf 0 gesetzt, damit auch die
heute geblockten Signale sichtbar werden; danach nach Konfidenz-Baendern
aufgeteilt. Kanonischer Exit, Echtkosten, 2 Halbjahre, 95-%-KI je Zelle.
Zusaetzlich beide Ausfuehrungs-Achsen (Markt / ruhende Order am Level), weil
`backtest_entryroom_level.py` am selben Tag gezeigt hat, dass die Achse
beidhaelftig +0,09 bis +0,11 R traegt.
REGEL VORAB FIXIERT:
- Die Schwelle wird nur verschoben, wenn ein anderer Schnitt in BEIDEN
Haelften mehr OeR UND mehr Summe liefert und die Nachbarbaender mithalten.
- Zusaetzliche Aussage: trennt conf ueberhaupt? Das verlangt einen in beiden
Haelften GLEICHGERICHTETEN Verlauf ueber die Baender -- ein Band, das in H1
steigt und in H2 faellt, ist Rauschen.
GRENZE: kein sequentieller Ein-Slot-Lauf (verglichen werden Populationen).
Exit-Slippage ist nicht modelliert.
Aufruf: python backtest_conf_gated.py [bars]
"""
import sys
import MetaTrader5 as mt5
import core.wave_rec as WR
from backtest_breakout_gated import _PIV_K, _TU, _atr_series, _htf_sign
from backtest_cost_gate import kennzahlen
from backtest_entryroom_level import _GATE, _K, handeln, sammle
from core.analysis import calc_trend_angle
from core.exit_model import LIVE
from core.wave_rec import (WaveRecommender, _ANGLE_LR, _EMA_FAST, _EMA_SLOW,
_ema_series)
BAENDER = [(0, 45), (45, 55), (55, 65), (65, 75), (75, 101)]
def zeig(lbl, k1, k2):
def f(k):
if not k:
return " --"
ci = k.get("ci")
m = ""
if ci:
m = (" [{:+.3f} .. {:+.3f}]".format(ci[0], ci[1])
+ ("" if ci[0] * ci[1] > 0 else " ~0"))
return ("n={:>4} WR={:>3.0f}% OeR={:+.3f} SR={:+8.1f} PF={:.2f}{}"
.format(k["n"], k["wr"], k["oer"], k["sr"], k["pf"], m))
print(" {:<16} H1 {}".format(lbl, f(k1)))
print(" {:<16} H2 {}".format("", f(k2)))
def main():
n = int(sys.argv[1]) if len(sys.argv) > 1 else 80000
mt5.initialize()
sym = next((c for c in ("SpotCrude", "USOIL", "WTI", "XTIUSD")
if mt5.symbol_info(c)), None)
bars = mt5.copy_rates_from_pos(sym, mt5.TIMEFRAME_M5, 0, n)
point = mt5.symbol_info(sym).point
mt5.shutdown()
H = [float(b["high"]) for b in bars]; L = [float(b["low"]) for b in bars]
C = [float(b["close"]) for b in bars]
SP = [float(b["spread"]) * point for b in bars]
A = _atr_series(H, L, C)
EF = _ema_series(C, _EMA_FAST); ES = _ema_series(C, _EMA_SLOW)
N = len(C); mid = N // 2; ende = N - LIVE.max_hold - 1
print("Vorberechnung ...", flush=True)
AD = [90.0] * N
for i in range(_ANGLE_LR + 2, N):
AD[i] = calc_trend_angle(C[i - (_ANGLE_LR + 1):i + 1], _ANGLE_LR)
HTF = [0] * N; H1 = [0] * N
for i in range(N):
if A[i]:
HTF[i] = _htf_sign(C, i, 6, A[i])
H1[i] = _htf_sign(C, i, 12, A[i])
PH = [j for j in range(_PIV_K, N - _PIV_K)
if H[j] == max(H[j - _PIV_K:j + _PIV_K + 1])]
PL = [j for j in range(_PIV_K, N - _PIV_K)
if L[j] == min(L[j - _PIV_K:j + _PIV_K + 1])]
w = WaveRecommender(_TU(), mt5.TIMEFRAME_M5)
w.set_reversal_enabled(False)
w.set_entry_room(_GATE)
w.set_dead_hours([])
w.set_breakout_k(_K)
# Gate oeffnen, damit die heute geblockten Baender ueberhaupt entstehen.
# ⚠ Modul-Konstante, wirkt prozessweit -- deshalb hier und nirgends sonst.
alt = WR._MIN_CONF
WR._MIN_CONF = 0
print("=" * 100)
print(" KONFIDENZ-SCHWELLE im vollen Gate-Stack -- {} M5 ({} Bars)".format(sym, N))
print(" _MIN_CONF {} -> 0 fuer die Messung ({} Bars pro Haelfte)".format(alt, mid))
print("=" * 100)
try:
s1 = sammle(w, H, L, C, A, EF, ES, AD, HTF, H1, PH, PL, 0, mid)
s2 = sammle(w, H, L, C, A, EF, ES, AD, HTF, H1, PH, PL, mid, ende)
finally:
WR._MIN_CONF = alt
print(" Signale ohne Konfidenz-Gate: H1 {} H2 {}".format(len(s1), len(s2)))
for modus, titel in (("markt", "A) MARKT am Bar-Close (= wie die Sim von 2026-07-16)"),
("level", "B) ruhende Order AM Level, Fill bei BERUEHRUNG")):
print("")
print(" " + titel)
print(" " + "-" * 96)
for lo, hi in BAENDER:
def rs(sigs):
out = []
for s in sigs:
c = s.get("conf")
if c is None or not (lo <= c < hi):
continue
r = handeln(s, H, L, C, SP, ende, modus, 0.0, 12)
if r is not None:
out.append(r)
return out
zeig("conf {}-{}".format(lo, hi - 1), kennzahlen(rs(s1)), kennzahlen(rs(s2)))
print("")
# Kumulativ: was liefert die Schwelle X, wenn man alles >= X handelt?
print(" Schwellen-Vergleich (alles >= X gehandelt)")
for x in (0, 45, 55, 65, 75):
def rs2(sigs):
out = []
for s in sigs:
c = s.get("conf")
if c is None or c < x:
continue
r = handeln(s, H, L, C, SP, ende, modus, 0.0, 12)
if r is not None:
out.append(r)
return out
tag = " <- LIVE" if x == alt else ""
zeig(">= {}{}".format(x, tag), kennzahlen(rs2(s1)), kennzahlen(rs2(s2)))
print("")
print("=" * 100)
if __name__ == "__main__":
main()