M15-Karte: Bias war ein Richtungsgeber - korrigiert zum Veto (v=155)
Tagestest an 07.08. (test_m15_karte_tag.py) deckt einen strukturellen Fehler in meiner eigenen Karte auf. Rohzahlen: 92 M15-Bars, davon 46 (50 %) auf GRUEN. Entkoppelt auf zusammenhaengende Bloecke bleiben 13 Setup-Episoden - und ALLE 13 waren LONG, an einem Tag, der von 78,399 auf 77,383 FIEL (-1,016 $). Trefferquote 0 %, OeR -0,753, SumR -9,79. Ursache ist nicht Pech, sondern ein Denkfehler: gemessen ist "gegen den M30-Trend zu handeln halbiert den Edge" - eine VERBOTS-Aussage. Ich hatte daraus "M30+H1 einig -> Bias LONG" gemacht, also einen Richtungsgeber. Exakt der Fehler, wegen dem die Gesamtempfehlung entfernt wurde (Richtung dort 48-51 % Treffer). Die nachlaufende EMA zeigt im Trendtag stur in die alte Richtung - dokumentiert seit analyze_signal_live.py. Korrigiert: das Feld liefert nur noch, welche Richtung VERBOTEN ist (bias.verboten); bias.richtung ist dauerhaft None. Das Banner nennt keine Richtung mehr: "BEDINGUNGEN OK @ 77,098 - Order ANS LEVEL, nicht zum Markt - SHORT vermeiden" Zwei weitere Befunde aus demselben Test, offen dokumentiert: - Das Kosten-Gate blockte 0 von 92 Bars. Auf M15 liegt Spread/ATR bei ~0,10, die Schwelle bei 0,32 - das Gate ist dort faktisch dekorativ. - Die strengere Bias-Variante (M30 UND H1) unterscheidet sich kaum von der laxen (46 gegen 47 gruene Bars) - die Zusatzstrenge kostet nichts, bringt aber auch nichts. Auch ein Fehler in meinem TEST, dokumentiert: der erste Lauf zaehlte 46 fortlaufende Bars als 46 Trades und meldete 7 % Trefferquote - eine Bewegung 46-mal gezaehlt. Erst die Entkopplung auf Episoden macht die Zahl lesbar. WARNUNG zur Beweiskraft: EIN Tag ist kein Edge-Nachweis. Belastbar ist hier nicht die Trefferquote, sondern der MECHANISMUS - und der ist aus zwei Halbjahren dokumentiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
e5adfc9757
commit
30830d76b1
+20
-4
@@ -4034,12 +4034,28 @@ class TradingEngine:
|
|||||||
atr15 = (ws.get("tf_atr") or {}).get("M15")
|
atr15 = (ws.get("tf_atr") or {}).get("M15")
|
||||||
out: dict = {"atr_m15": round(atr15, 4) if atr15 else None}
|
out: dict = {"atr_m15": round(atr15, 4) if atr15 else None}
|
||||||
|
|
||||||
# ── 1) BIAS — der einzige belegte Filter (M30-Gegen-Trend: Edge ×2)
|
# ── 1) VETO statt BIAS ────────────────────────────────────────
|
||||||
|
# ⚠⚠ KORREKTUR 2026-08-08, nach dem Tagestest an 07.08.: der erste
|
||||||
|
# Entwurf machte aus „M30+H1 einig" eine RICHTUNG („Bias LONG") und
|
||||||
|
# erzeugte damit 13 LONG-Episoden an einem Tag, der 1,02 $ FIEL —
|
||||||
|
# Trefferquote 0 %, ØR −0,753. Der Fehler ist strukturell, nicht
|
||||||
|
# Pech: gemessen ist **„gegen den M30-Trend halbiert den Edge"**,
|
||||||
|
# also eine VERBOTS-Aussage. Daraus einen Richtungsgeber zu machen
|
||||||
|
# ist exakt der Fehler, wegen dem die Gesamtempfehlung entfernt
|
||||||
|
# wurde (Richtung dort 48–51 % Treffer = Münzwurf).
|
||||||
|
# Die nachlaufende EMA zeigt im Trendtag stur in die alte Richtung —
|
||||||
|
# dokumentiert seit `analyze_signal_live.py` (06.08.).
|
||||||
|
# Jetzt liefert das Feld nur noch, welche Richtung VERBOTEN ist.
|
||||||
h30 = ws.get("htf_trend"); h1 = ws.get("h1_trend")
|
h30 = ws.get("htf_trend"); h1 = ws.get("h1_trend")
|
||||||
einig = (h30 and h1 and h30 == h1)
|
verboten = None
|
||||||
|
if h30:
|
||||||
|
verboten = "SHORT" if h30 == "LONG" or h30 == 1 else "LONG"
|
||||||
out["bias"] = {"m30": h30, "h1": h1,
|
out["bias"] = {"m30": h30, "h1": h1,
|
||||||
"richtung": h30 if einig else None,
|
"verboten": verboten,
|
||||||
"einig": bool(einig)}
|
"einig": bool(h30 and h1 and h30 == h1),
|
||||||
|
# ⚠ `richtung` bleibt None — bewusst KEINE
|
||||||
|
# Richtungsaussage mehr (s. o.).
|
||||||
|
"richtung": None}
|
||||||
|
|
||||||
# ── 2) KOSTEN auf M15 — hier gewinnt M15 wirklich (gemessen 0,104
|
# ── 2) KOSTEN auf M15 — hier gewinnt M15 wirklich (gemessen 0,104
|
||||||
# gegen M5 0,193). Schwellen wie beim M5-Chip (0,20 / 0,32).
|
# gegen M5 0,193). Schwellen wie beim M5-Chip (0,20 / 0,32).
|
||||||
|
|||||||
@@ -0,0 +1,193 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Plausibilitaets-Test der M15-Karte an EINEM Handelstag.
|
||||||
|
|
||||||
|
⚠⚠ WAS DAS IST UND WAS NICHT. Ein Tag ist KEIN Edge-Nachweis — die
|
||||||
|
2-Stichproben-Regel gilt unveraendert, und im Projekt sind Einzeltag-Anekdoten
|
||||||
|
schon zweimal als Beleg missbraucht worden (15-Minuten-Regel 31.07.,
|
||||||
|
Nacht-Guard 05.08.). Geprueft wird deshalb nur die MECHANIK:
|
||||||
|
· Wie oft steht die Karte auf gruen / gelb / rot?
|
||||||
|
· Sind die Gates zu streng (nie gruen) oder zu lasch (dauernd gruen)?
|
||||||
|
· WELCHES Gate blockt wie oft — dieselbe Telemetrie-Frage wie bei
|
||||||
|
`block_reason`, die den 93-%-WARTEN-Befund erst erklaerbar gemacht hat.
|
||||||
|
· Und als Kontext, nicht als Beleg: was waere nach einem gruenen Status
|
||||||
|
passiert?
|
||||||
|
|
||||||
|
⚠ ZWEI SCHWELLEN SIND GESETZT, NICHT GEMESSEN — und der Test soll das sichtbar
|
||||||
|
machen, nicht verstecken:
|
||||||
|
(a) „Level in Reichweite" = 0,5×ATR_M15 — von mir gewaehlt.
|
||||||
|
(b) „Bias einig" verlangt M30 UND H1 in derselben Richtung. Gemessen ist nur
|
||||||
|
der M30-GEGEN-Trend-Filter (Edge ×2); H1 ist im Projekt Konfluenz-BONUS,
|
||||||
|
kein Pflicht-Gate. Die Und-Verknuepfung ist strenger als alles Belegte.
|
||||||
|
Beide Varianten werden deshalb mitgerechnet, damit der Preis der Strenge
|
||||||
|
sichtbar wird.
|
||||||
|
|
||||||
|
Aufruf: python test_m15_karte_tag.py [YYYY-MM-DD] (Default 2026-08-07)
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import sys
|
||||||
|
from datetime import datetime, timedelta, timezone
|
||||||
|
from zoneinfo import ZoneInfo
|
||||||
|
|
||||||
|
import MetaTrader5 as mt5
|
||||||
|
|
||||||
|
from core.exit_model import LIVE, simulate
|
||||||
|
from core.sr_levels import halten, linien, naechste
|
||||||
|
|
||||||
|
_BERLIN = ZoneInfo("Europe/Berlin")
|
||||||
|
_BROKER = timezone(timedelta(hours=3))
|
||||||
|
_HTF_DEAD = 0.15 # Totband wie _HTF_DEADBAND
|
||||||
|
_PIV_K = 3
|
||||||
|
_LOOK = 50 # M30-Pivot-Fenster wie live
|
||||||
|
|
||||||
|
|
||||||
|
def _berlin(ts):
|
||||||
|
return datetime.fromtimestamp(ts, timezone.utc).replace(
|
||||||
|
tzinfo=_BROKER).astimezone(_BERLIN)
|
||||||
|
|
||||||
|
|
||||||
|
def ema(v, p):
|
||||||
|
k = 2 / (p + 1); out = [v[0]]
|
||||||
|
for x in v[1:]:
|
||||||
|
out.append(out[-1] + k * (x - out[-1]))
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
def atr_series(H, L, C, p=14):
|
||||||
|
tr = [0.0]
|
||||||
|
for i in range(1, len(C)):
|
||||||
|
tr.append(max(H[i] - L[i], abs(H[i] - C[i - 1]), abs(L[i] - C[i - 1])))
|
||||||
|
out = [None]
|
||||||
|
for i in range(1, len(C)):
|
||||||
|
seg = tr[max(1, i - p + 1):i + 1]
|
||||||
|
out.append(sum(seg) / len(seg))
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
def htf_sign(H, L, C, step):
|
||||||
|
"""Trend-Vorzeichen der hoeheren TF je M15-Index — LOOK-AHEAD-frei."""
|
||||||
|
hh, hl, hc = [], [], []
|
||||||
|
for s in range(0, len(C) - step + 1, step):
|
||||||
|
hh.append(max(H[s:s + step])); hl.append(min(L[s:s + step]))
|
||||||
|
hc.append(C[s + step - 1])
|
||||||
|
if len(hc) < 55:
|
||||||
|
return [0] * len(C)
|
||||||
|
ef, es = ema(hc, 12), ema(hc, 50)
|
||||||
|
ha = atr_series(hh, hl, hc)
|
||||||
|
sg = []
|
||||||
|
for j in range(len(hc)):
|
||||||
|
a = ha[j]
|
||||||
|
if j < 52 or not a:
|
||||||
|
sg.append(0); continue
|
||||||
|
d = ef[j] - es[j]
|
||||||
|
sg.append(0 if abs(d) < _HTF_DEAD * a else (1 if d > 0 else -1))
|
||||||
|
out = [0] * len(C)
|
||||||
|
for i in range(len(C)):
|
||||||
|
j = i // step - 1
|
||||||
|
out[i] = sg[j] if 0 <= j < len(sg) else 0
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
def m30_levels(H, L, C):
|
||||||
|
"""Rohe M30-Pivots je M15-Index (2 M15-Bars = 1 M30-Bar), kausal."""
|
||||||
|
st = 2
|
||||||
|
mh = [max(H[s:s + st]) for s in range(0, len(C) - st + 1, st)]
|
||||||
|
ml = [min(L[s:s + st]) for s in range(0, len(C) - st + 1, st)]
|
||||||
|
out = [None] * len(C)
|
||||||
|
for i in range(len(C)):
|
||||||
|
j = i // st - 1
|
||||||
|
if j < _PIV_K + 5:
|
||||||
|
continue
|
||||||
|
a, b = max(0, j - _LOOK), j + 1
|
||||||
|
hs, ls = mh[a:b], ml[a:b]
|
||||||
|
ph = [round(hs[x], 3) for x in range(_PIV_K, len(hs) - _PIV_K)
|
||||||
|
if hs[x] == max(hs[x - _PIV_K:x + _PIV_K + 1])]
|
||||||
|
pl = [round(ls[x], 3) for x in range(_PIV_K, len(ls) - _PIV_K)
|
||||||
|
if ls[x] == min(ls[x - _PIV_K:x + _PIV_K + 1])]
|
||||||
|
out[i] = (ph, pl)
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
tag = sys.argv[1] if len(sys.argv) > 1 else "2026-08-07"
|
||||||
|
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_M15, 0, 4000)
|
||||||
|
point = mt5.symbol_info(sym).point
|
||||||
|
mt5.shutdown()
|
||||||
|
T = [int(b["time"]) for b in bars]
|
||||||
|
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)
|
||||||
|
S30, S1H = htf_sign(H, L, C, 2), htf_sign(H, L, C, 4)
|
||||||
|
LV = m30_levels(H, L, C)
|
||||||
|
|
||||||
|
idx = [i for i, t in enumerate(T) if _berlin(t).strftime("%Y-%m-%d") == tag]
|
||||||
|
if not idx:
|
||||||
|
print(f"keine M15-Bars fuer {tag}"); return 1
|
||||||
|
|
||||||
|
print("=" * 92)
|
||||||
|
print(f" M15-KARTE — Mechanik-Test an {tag} ({sym}, {len(idx)} M15-Bars)")
|
||||||
|
print(" ⚠ EIN Tag = kein Edge-Nachweis. Geprueft wird nur, ob die Gates")
|
||||||
|
print(" plausibel greifen (zu streng / zu lasch) und welches blockt.")
|
||||||
|
print("=" * 92)
|
||||||
|
|
||||||
|
zaehler = {"gruen": 0, "gelb": 0, "rot_bias": 0, "rot_kosten": 0, "kein_level": 0}
|
||||||
|
zaehler_lax = dict(zaehler) # Variante: nur „nicht gegen M30"
|
||||||
|
gruene = []
|
||||||
|
prev_r = prev_s = None
|
||||||
|
for i in idx:
|
||||||
|
a = A[i]
|
||||||
|
if not a or not LV[i]:
|
||||||
|
continue
|
||||||
|
ph, pl = LV[i]
|
||||||
|
lv = linien(ph, pl, a)
|
||||||
|
r, s = naechste(lv, C[i])
|
||||||
|
r = halten(r, prev_r, C[i], a, True); s = halten(s, prev_s, C[i], a, False)
|
||||||
|
prev_r, prev_s = r, s
|
||||||
|
kr = SP[i] / a
|
||||||
|
kOk = kr < 0.32
|
||||||
|
gaps = [abs(x - C[i]) / a for x in (r, s) if x is not None]
|
||||||
|
nah = [g for g in gaps if g <= 0.5]
|
||||||
|
|
||||||
|
for name, bOk, d in (("streng", S30[i] != 0 and S30[i] == S1H[i], S30[i]),
|
||||||
|
("lax", S30[i] != 0, S30[i])):
|
||||||
|
z = zaehler if name == "streng" else zaehler_lax
|
||||||
|
if not bOk: z["rot_bias"] += 1
|
||||||
|
elif not kOk: z["rot_kosten"] += 1
|
||||||
|
elif not gaps: z["kein_level"] += 1
|
||||||
|
elif not nah: z["gelb"] += 1
|
||||||
|
else:
|
||||||
|
z["gruen"] += 1
|
||||||
|
if name == "streng":
|
||||||
|
lvl = min((x for x in (r, s) if x is not None),
|
||||||
|
key=lambda x: abs(x - C[i]))
|
||||||
|
gruene.append((i, d, lvl, a, kr))
|
||||||
|
|
||||||
|
n = len(idx)
|
||||||
|
print(f"\n {'Status':<16}{'STRENG (M30 UND H1)':>22}{'LAX (nur nicht gegen M30)':>28}")
|
||||||
|
for k, lbl in (("gruen", "🟢 SETUP BEREIT"), ("gelb", "🟡 warten auf Level"),
|
||||||
|
("rot_bias", "🔴 Bias"), ("rot_kosten", "🔴 Kosten"),
|
||||||
|
("kein_level", "kein Level")):
|
||||||
|
print(f" {lbl:<16}{zaehler[k]:>10} ({100*zaehler[k]/n:>4.0f} %)"
|
||||||
|
f"{zaehler_lax[k]:>16} ({100*zaehler_lax[k]/n:>4.0f} %)")
|
||||||
|
|
||||||
|
print(f"\n ── Was folgte auf 🟢? (KONTEXT, kein Beleg — n={len(gruene)}) ──")
|
||||||
|
if not gruene:
|
||||||
|
print(" kein einziges gruenes Signal an diesem Tag.")
|
||||||
|
else:
|
||||||
|
Rs = []
|
||||||
|
for i, d, lvl, a, kr in gruene:
|
||||||
|
R = simulate(lvl, d, a, H, L, C, i + 1, LIVE)
|
||||||
|
Rs.append(R - kr)
|
||||||
|
w = sum(1 for x in Rs if x > 0)
|
||||||
|
print(f" Trefferquote {100*w/len(Rs):.0f} % ØR {sum(Rs)/len(Rs):+.3f} "
|
||||||
|
f"ΣR {sum(Rs):+.2f}")
|
||||||
|
print(f" Zeiten: " + ", ".join(f"{_berlin(T[i]):%H:%M}" for i, *_ in gruene[:12]))
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
+17
-10
@@ -159,12 +159,16 @@ function renderM15(s) {
|
|||||||
const f = (v, d = 2) => (v == null ? "—" : (+v).toFixed(d));
|
const f = (v, d = 2) => (v == null ? "—" : (+v).toFixed(d));
|
||||||
|
|
||||||
// ── Gate 1: HTF-Bias (der einzige belegte Filter, Edge ×2) ───────────
|
// ── Gate 1: HTF-Bias (der einzige belegte Filter, Edge ×2) ───────────
|
||||||
|
// ⚠⚠ VETO, KEINE RICHTUNG (Korrektur 2026-08-08). Der erste Entwurf zeigte
|
||||||
|
// „Bias ▲ LONG" und erzeugte am 07.08. dreizehn LONG-Episoden an einem Tag,
|
||||||
|
// der 1,02 $ FIEL (Trefferquote 0 %). Gemessen ist nur: GEGEN den M30-Trend
|
||||||
|
// zu handeln halbiert den Edge — eine Verbots-, keine Richtungsaussage.
|
||||||
const b = s.bias || {};
|
const b = s.bias || {};
|
||||||
const bOk = !!b.einig;
|
const bOk = !!b.verboten; // Gate offen, sobald ein HTF steht
|
||||||
$("m15-bias").className = "m15-item " + (bOk ? "ok" : "fail");
|
$("m15-bias").className = "m15-item " + (bOk ? "ok" : "");
|
||||||
$("m15-bias").textContent = bOk
|
$("m15-bias").textContent = bOk
|
||||||
? `① Bias ${b.richtung === "LONG" ? "▲ LONG" : "▼ SHORT"} · M30+H1 einig`
|
? `① Veto: ${b.verboten} vermeiden (HTF ${b.m30}${b.einig ? " + H1 einig" : ""})`
|
||||||
: `① Bias unklar · M30 ${b.m30 || "—"} / H1 ${b.h1 || "—"}`;
|
: `① kein Veto — HTF flach (M30 ${b.m30 ?? "—"} / H1 ${b.h1 ?? "—"})`;
|
||||||
|
|
||||||
// ── Gate 2: Kosten auf M15 ───────────────────────────────────────────
|
// ── Gate 2: Kosten auf M15 ───────────────────────────────────────────
|
||||||
const k = s.kosten || {};
|
const k = s.kosten || {};
|
||||||
@@ -224,15 +228,18 @@ function renderM15(s) {
|
|||||||
: "⑤ Veto: noch zu wenige Trades";
|
: "⑤ Veto: noch zu wenige Trades";
|
||||||
|
|
||||||
// ── Status: die Kette wird der Reihe nach ausgewertet ────────────────
|
// ── Status: die Kette wird der Reihe nach ausgewertet ────────────────
|
||||||
|
// ⚠ Das Banner nennt AUSDRUECKLICH keine Richtung. Es sagt, ob die
|
||||||
|
// BEDINGUNGEN fuer eine Order am Level stehen — und was verboten ist.
|
||||||
let cls, txt;
|
let cls, txt;
|
||||||
if (!bOk) { cls = "rot"; txt = "🔴 KEIN TRADE · Bias unklar (M30/H1 uneinig)"; }
|
if (!kOk && k.ratio != null) { cls = "rot"; txt = "🔴 KEIN TRADE · Kosten zu hoch"; }
|
||||||
else if (!kOk && k.ratio != null) { cls = "rot"; txt = "🔴 KEIN TRADE · Kosten zu hoch"; }
|
else if (!zOk) {
|
||||||
else if (!zOk) { cls = "gelb"; txt = "🟡 WARTEN AUF LEVEL · Bias und Kosten passen"; }
|
cls = "gelb";
|
||||||
else {
|
txt = "🟡 WARTEN AUF LEVEL · Kosten passen, Kurs noch zu weit";
|
||||||
|
} else {
|
||||||
const nn = nah[0];
|
const nn = nah[0];
|
||||||
cls = "gruen";
|
cls = "gruen";
|
||||||
txt = `🟢 SETUP BEREIT @ ${f(nn.level, 3)} · ${b.richtung === "LONG" ? "▲" : "▼"} `
|
txt = `🟢 BEDINGUNGEN OK @ ${f(nn.level, 3)} · Order ANS LEVEL, nicht zum Markt`
|
||||||
+ `${b.richtung} · Order ANS LEVEL legen, nicht zum Markt`;
|
+ (b.verboten ? ` · ⛔ ${b.verboten} vermeiden` : "");
|
||||||
}
|
}
|
||||||
st.className = "m15-status " + cls;
|
st.className = "m15-status " + cls;
|
||||||
st.textContent = txt;
|
st.textContent = txt;
|
||||||
|
|||||||
+2
-2
@@ -6,7 +6,7 @@
|
|||||||
<meta name="theme-color" content="#0d1117">
|
<meta name="theme-color" content="#0d1117">
|
||||||
<title>Oil · MT5</title>
|
<title>Oil · MT5</title>
|
||||||
<link rel="manifest" href="manifest.json">
|
<link rel="manifest" href="manifest.json">
|
||||||
<link rel="stylesheet" href="style.css?v=154">
|
<link rel="stylesheet" href="style.css?v=155">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<!-- Modul-Menü (ganz oben) -->
|
<!-- Modul-Menü (ganz oben) -->
|
||||||
@@ -340,6 +340,6 @@
|
|||||||
</div>
|
</div>
|
||||||
<div id="toast" class="toast hidden"></div>
|
<div id="toast" class="toast hidden"></div>
|
||||||
|
|
||||||
<script src="app.js?v=154"></script>
|
<script src="app.js?v=155"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
Reference in New Issue
Block a user