Squeeze auf 9 Instrumenten · Momentum neu gemessen · Fill-Annahme korrigiert · UI
Drei Messungen und eine UI-Aenderung, ausgeloest von der Frage, ob das Konzept zu ueberdenken ist. 1) SQUEEZE AUF ANDEREN INSTRUMENTEN (backtest_squeeze_multi.py): 7 von 9 halten alle vier Bedingungen, alle nachbar-robust. Die zwei Durchfaller (Gasoline, USDX) sind exakt die mit Spread >= ATR — der Test scheitert, wo er muss. ⚠ 7 sind nicht 7 unabhaengige Tests (Crude/Brent, Gold/Silber, NAS/GER40 korrelieren), und die Kompression traegt weniger als der Ausbruch selbst (Kontrolle "beliebige Box" ist ueberall positiv). 2) MOMENTUM (backtest_momentum_v2.py): bleibt verworfen. Mit Level-Order sah es nach +0,4 aus — das war Look-ahead. Eine liegende Order fuellt bei der ersten BERUEHRUNG, nicht erst wenn der Bar jenseits des Levels SCHLIESST. Ehrlich gerechnet: -0,04..-0,09 in H1. Struktureller Grund: das Momentum-Level wandert jede Bar mit C[i-N] und ist damit kein Ort, an dem eine Order liegen bleibt. 3) FILL-ANNAHME (backtest_squeeze_touchfill.py): dieselbe Frage fuer den Squeeze. Er haelt (+0,124/+0,292, PF 1,24/1,64) und die Umstellung auf ruhende Orders bleibt richtig (Market -0,158/-0,006). ABER die heute zitierten +0,456/+0,611 sind als Live-Erwartung ~0,32 R zu hoch. Die Beruehrungs-Variante trifft fast genau das original validierte Band +0,14..+0,23. ⚠ OFFEN: derselbe Test fehlt fuer den SIG-Pending-Pfad, der seit 05.08. live ist — und dort ist ein schlechteres Ergebnis zu erwarten, weil das _pend-Level wandert statt stillzustehen. 4) UI (v=143): fester Vorbehalt unter der Headline und "Score" statt nackter Prozentzahl am Ring. Statisches Markup, kein JS. Eigener Fehler dokumentiert: der erste Momentum-Entwurf pruefte nicht, ob das Level als Stop-Order auf der richtigen Marktseite liegt, und lieferte ØR +2,2 — unmoeglich, und genau daran erkennbar. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
64eef14871
commit
45bfc87153
@@ -0,0 +1,268 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Traegt der validierte Squeeze auch auf ANDEREN Instrumenten?
|
||||
|
||||
ANLASS (2026-08-06): `analyze_signal_live.py` hat gezeigt, dass die Richtungs-
|
||||
aussage der Empfehlung ein Muenzwurf ist (48–51 % ueber 1.071 Episoden). Der
|
||||
Ertrag kommt aus Einstiegspreis und Exit-Asymmetrie. Damit ist das Grundproblem
|
||||
nicht die QUALITAET des Squeeze, sondern seine **Frequenz** — 2–3×/Woche auf
|
||||
einem Instrument.
|
||||
|
||||
Darauf hat das Projekt bisher zweimal reagiert: mehr Signale auf demselben
|
||||
Instrument suchen (**23 verworfene Eingriffe**) oder auf eine feinere Zeitebene
|
||||
gehen (`backtest_squeeze_m1.py` — die Kosten erledigen es). Die dritte Achse ist
|
||||
unberuehrt: **dasselbe Signal, mehr Instrumente.** Praezedenzfall: `breakout_k=0,3`
|
||||
wurde 2026-07-09 auf Brent nachvalidiert (ØR +0,305) — die Mechanik
|
||||
Kompression→Ausbruch ist nicht oelspezifisch.
|
||||
|
||||
⚠⚠ ZWEI FALLEN, DIE HIER EINGEBAUT SIND — beide sind im Projekt schon einmal
|
||||
teuer gewesen:
|
||||
1) **Der ATR-Floor 0,06 ist ein WTI-DOLLAR-Wert.** Auf Gold (ATR ~3) waere er
|
||||
wirkungslos, auf NatGas (~0,05) wuerde er fast alles wegwerfen — und ein
|
||||
Filter, der nach einer Eigenschaft aussortiert, die mit der untersuchten
|
||||
Achse korreliert, ERZEUGT den Befund (Lehre aus `analyze_tageszeit.py` und
|
||||
dem ORB-Selektions-Artefakt). Der Floor wird deshalb **relativ** gesetzt:
|
||||
derselbe Anteil am Median-ATR wie bei WTI.
|
||||
2) **MEHRFACHVERGLEICH.** Acht Instrumente = acht Chancen auf einen Zufalls-
|
||||
treffer. Bei 5 % Fehlalarmquote ist ~0,4 davon zu erwarten. Deshalb gilt die
|
||||
Regel unten UND es wird ausgewiesen, wie viele Kandidaten bestanden haben —
|
||||
bestehen 1 von 8, ist das etwas anderes als 6 von 8.
|
||||
|
||||
VORAB FIXIERTE ENTSCHEIDUNGSREGEL (vor dem ersten Lauf festgelegt):
|
||||
Ein Instrument gilt nur dann als Kandidat, wenn
|
||||
(1) ØR > 0 UND PF > 1 in BEIDEN Haelften,
|
||||
(2) mindestens 40 Ausbrueche je Haelfte (sonst Rauschen),
|
||||
(3) die Nachbar-Parameter (Box 10/15 × k 0,1/0,2) das Vorzeichen nicht kippen,
|
||||
(4) SpotCrude als KONTROLLE seine bekannten Werte reproduziert — sonst ist die
|
||||
Pipeline defekt und nichts davon zaehlt.
|
||||
|
||||
Aufruf: python backtest_squeeze_multi.py [bars]
|
||||
"""
|
||||
import sys
|
||||
|
||||
import MetaTrader5 as mt5
|
||||
|
||||
import core.wave_rec as wr
|
||||
from backtest_cost_gate import _atr_series, kennzahlen
|
||||
from core.exit_model import LIVE, simulate
|
||||
from core.wave_rec import WaveRecommender
|
||||
|
||||
_SYMBOLE = ["SpotCrude", # Kontrolle — muss reproduzieren
|
||||
"SpotBrent", "NatGas", "Gasoline", "Copper",
|
||||
"XAUUSD", "XAGUSD", "NAS100", "GER40", "USDX"]
|
||||
_MIN_N = 40
|
||||
|
||||
|
||||
def hole(sym, n):
|
||||
if not mt5.symbol_select(sym, True):
|
||||
return None
|
||||
si = mt5.symbol_info(sym)
|
||||
b = mt5.copy_rates_from_pos(sym, mt5.TIMEFRAME_M5, 0, n)
|
||||
if b is None or len(b) < 5000 or si is None:
|
||||
return None
|
||||
return {"H": [float(x["high"]) for x in b], "L": [float(x["low"]) for x in b],
|
||||
"C": [float(x["close"]) for x in b],
|
||||
"SP": [float(x["spread"]) * si.point for x in b], "digits": si.digits}
|
||||
|
||||
|
||||
def kandidaten(d, A, floor, box=None, k=None, mult=None):
|
||||
"""`mult` = Kompressions-Schwelle. Sehr hoch gesetzt (99) entfaellt die
|
||||
Kompressions-Bedingung → Ausbruch aus einer BELIEBIGEN Box = die Kontrolle,
|
||||
die den Squeeze auf WTI ueberhaupt erst validiert hat (dort schlug er die
|
||||
beliebige Box in BEIDEN Haelften: +0,227/+0,141 gegen −0,045/+0,161)."""
|
||||
alt_n, alt_k, alt_m = wr._SQ_N, wr._SQ_K, wr._SQ_MULT
|
||||
if box is not None:
|
||||
wr._SQ_N = box
|
||||
if k is not None:
|
||||
wr._SQ_K = k
|
||||
if mult is not None:
|
||||
wr._SQ_MULT = mult
|
||||
H, L, C, SP = d["H"], d["L"], d["C"], d["SP"]
|
||||
out, last = [], None
|
||||
try:
|
||||
for i in range(wr._SQ_N + 2, len(C) - 2):
|
||||
atr = A[i]
|
||||
if not atr or atr < floor:
|
||||
continue
|
||||
w0 = i - wr._SQ_N - 2
|
||||
st = WaveRecommender._squeeze_one(H[w0:i + 1], L[w0:i + 1],
|
||||
C[w0:i + 1], atr)
|
||||
if st.get("state") != "active":
|
||||
last = None
|
||||
continue
|
||||
sig = (st["dir"], st["level"])
|
||||
if sig == last:
|
||||
continue
|
||||
last = sig
|
||||
# ⚠ Kosten: kein WTI-Ersatzwert (0,0225) fuer fremde Instrumente —
|
||||
# fehlt der Bar-Spread, wird der Median dieses Symbols genommen.
|
||||
out.append({"i": i, "d": 1 if st["dir"] == "LONG" else -1,
|
||||
"lvl": float(st["level"]), "atr": atr, "sp": SP[i]})
|
||||
finally:
|
||||
wr._SQ_N, wr._SQ_K, wr._SQ_MULT = alt_n, alt_k, alt_m
|
||||
return out
|
||||
|
||||
|
||||
def lauf(K, d, lo, hi, med_sp, slip=0.0):
|
||||
H, L, C = d["H"], d["L"], d["C"]
|
||||
rs, frei_ab = [], lo
|
||||
for kd in K:
|
||||
i = kd["i"]
|
||||
if not (lo <= i < hi) or i < frei_ab:
|
||||
continue
|
||||
sp = kd["sp"] if kd["sp"] > 0 else med_sp
|
||||
entry = kd["lvl"] + kd["d"] * slip * kd["atr"]
|
||||
r, xb = simulate(entry, kd["d"], kd["atr"], H, L, C, i + 1, LIVE,
|
||||
timestop=True, use_tp=True, ret_bar=True)
|
||||
rs.append(r - sp / kd["atr"])
|
||||
frei_ab = xb + 1
|
||||
return rs
|
||||
|
||||
|
||||
def main():
|
||||
n = int(sys.argv[1]) if len(sys.argv) > 1 else 80000
|
||||
if not mt5.initialize():
|
||||
print("MT5 nicht erreichbar"); return 1
|
||||
|
||||
daten, atr_med = {}, {}
|
||||
for s in _SYMBOLE:
|
||||
d = hole(s, n)
|
||||
if not d:
|
||||
print(f" ⚠ {s}: keine Daten / nicht handelbar — uebersprungen")
|
||||
continue
|
||||
d["A"] = _atr_series(d["H"], d["L"], d["C"])
|
||||
gute = sorted(a for a in d["A"][20:] if a > 0)
|
||||
if not gute:
|
||||
continue
|
||||
atr_med[s] = gute[len(gute) // 2]
|
||||
sps = sorted(x for x in d["SP"][20:] if x > 0)
|
||||
d["med_sp"] = sps[len(sps) // 2] if sps else 0.0
|
||||
daten[s] = d
|
||||
mt5.shutdown()
|
||||
|
||||
if "SpotCrude" not in daten:
|
||||
print("SpotCrude fehlt — ohne Kontrolle keine Aussage"); return 1
|
||||
# Relativer Floor: derselbe Anteil am Median-ATR wie live bei WTI
|
||||
rel = 0.06 / atr_med["SpotCrude"]
|
||||
|
||||
print("=" * 100)
|
||||
print(" SQUEEZE AUF MEHREREN INSTRUMENTEN — dasselbe Signal, mehr Maerkte")
|
||||
print("=" * 100)
|
||||
print(f" Bars je Symbol: {n} (M5) · Einstieg AM Level (ruhende Stop-Order) ·")
|
||||
print(f" kanonischer Exit · Echtkosten aus dem Bar-Spread · sequentiell EIN Slot")
|
||||
print(f" ATR-Floor relativ: {rel:.3f} × Median-ATR "
|
||||
f"(= 0,06 $ bei WTI, massstabsgerecht uebertragen)")
|
||||
print()
|
||||
print(f" {'Symbol':<12} {'Med ATR':>9} {'Med Spr':>9} {'Spr/ATR':>8} "
|
||||
f"{'H1 ØR':>8} {'PF':>5} {'n':>5} | {'H2 ØR':>8} {'PF':>5} {'n':>5} Urteil")
|
||||
print(" " + "-" * 96)
|
||||
|
||||
besteht = []
|
||||
for s, d in daten.items():
|
||||
floor = rel * atr_med[s]
|
||||
K = kandidaten(d, d["A"], floor)
|
||||
m = len(d["C"]) // 2
|
||||
r1 = lauf(K, d, 0, m, d["med_sp"])
|
||||
r2 = lauf(K, d, m, len(d["C"]), d["med_sp"])
|
||||
k1, k2 = kennzahlen(r1), kennzahlen(r2)
|
||||
if not k1 or not k2:
|
||||
print(f" {s:<12} — keine Ausbrueche")
|
||||
continue
|
||||
ok = (k1["oer"] > 0 and k2["oer"] > 0 and k1["pf"] > 1 and k2["pf"] > 1
|
||||
and k1["n"] >= _MIN_N and k2["n"] >= _MIN_N)
|
||||
duenn = "" if (k1["n"] >= _MIN_N and k2["n"] >= _MIN_N) else " (zu duenn)"
|
||||
print(f" {s:<12} {atr_med[s]:>9.4f} {d['med_sp']:>9.4f} "
|
||||
f"{d['med_sp']/atr_med[s]:>8.3f} "
|
||||
f"{k1['oer']:>+8.3f} {k1['pf']:>5.2f} {k1['n']:>5} | "
|
||||
f"{k2['oer']:>+8.3f} {k2['pf']:>5.2f} {k2['n']:>5} "
|
||||
f"{'✅' if ok else '❌'}{duenn}")
|
||||
if ok and s != "SpotCrude":
|
||||
besteht.append(s)
|
||||
|
||||
print()
|
||||
print("=" * 100)
|
||||
print(" NACHBAR-PARAMETER der bestandenen Kandidaten (Box × k)")
|
||||
print("=" * 100)
|
||||
if not besteht:
|
||||
print(" keiner bestanden — nichts zu pruefen")
|
||||
fest = []
|
||||
for s in besteht:
|
||||
d = daten[s]
|
||||
floor = rel * atr_med[s]
|
||||
m = len(d["C"]) // 2
|
||||
alle_ok, zeilen = True, []
|
||||
for box, kk in ((10, 0.1), (15, 0.1), (10, 0.2), (15, 0.2)):
|
||||
K = kandidaten(d, d["A"], floor, box=box, k=kk)
|
||||
a, b = (kennzahlen(lauf(K, d, 0, m, d["med_sp"])),
|
||||
kennzahlen(lauf(K, d, m, len(d["C"]), d["med_sp"])))
|
||||
if not a or not b:
|
||||
alle_ok = False
|
||||
zeilen.append(f" Box {box} k {kk}: keine Ausbrueche")
|
||||
continue
|
||||
gut = a["oer"] > 0 and b["oer"] > 0
|
||||
alle_ok &= gut
|
||||
zeilen.append(f" Box {box} k {kk}: H1 {a['oer']:+.3f} (n{a['n']}) | "
|
||||
f"H2 {b['oer']:+.3f} (n{b['n']}) {'✅' if gut else '❌'}")
|
||||
print(f" {s}:")
|
||||
for z in zeilen:
|
||||
print(z)
|
||||
print(f" → {'ROBUST' if alle_ok else 'KIPPT — verworfen'}")
|
||||
if alle_ok:
|
||||
fest.append(s)
|
||||
|
||||
print()
|
||||
print("=" * 100)
|
||||
print(" KONTROLLE — schlaegt die KOMPRESSION den Ausbruch aus BELIEBIGER Box?")
|
||||
print("=" * 100)
|
||||
print(" ⚠ Ohne diese Zeile waere der Befund nicht interpretierbar: 'Breakout +")
|
||||
print(" Trailing' hat generisch Edge. Traegt die Kompression nichts bei, misst")
|
||||
print(" man nur den Exit — und dann braucht es kein Squeeze-Modul.")
|
||||
print(f" {'Symbol':<12} {'Squeeze H1/H2':>22} {'beliebige Box H1/H2':>24} Beitrag")
|
||||
for s in fest:
|
||||
d = daten[s]; floor = rel * atr_med[s]; m = len(d["C"]) // 2
|
||||
Ks = kandidaten(d, d["A"], floor)
|
||||
Kb = kandidaten(d, d["A"], floor, mult=99.0)
|
||||
a1, a2 = (kennzahlen(lauf(Ks, d, 0, m, d["med_sp"])),
|
||||
kennzahlen(lauf(Ks, d, m, len(d["C"]), d["med_sp"])))
|
||||
b1, b2 = (kennzahlen(lauf(Kb, d, 0, m, d["med_sp"])),
|
||||
kennzahlen(lauf(Kb, d, m, len(d["C"]), d["med_sp"])))
|
||||
if not all((a1, a2, b1, b2)):
|
||||
continue
|
||||
gut = a1["oer"] > b1["oer"] and a2["oer"] > b2["oer"]
|
||||
print(f" {s:<12} {a1['oer']:>+10.3f}/{a2['oer']:>+10.3f} "
|
||||
f"{b1['oer']:>+12.3f}/{b2['oer']:>+10.3f} "
|
||||
f"{'✅ Kompression traegt' if gut else '❌ kein Beitrag'}")
|
||||
|
||||
print()
|
||||
print("=" * 100)
|
||||
print(" SLIPPAGE-HAERTETEST — die Stop-Order loest aus, fuellt aber zum Markt")
|
||||
print("=" * 100)
|
||||
for s in fest:
|
||||
d = daten[s]; floor = rel * atr_med[s]; m = len(d["C"]) // 2
|
||||
K = kandidaten(d, d["A"], floor)
|
||||
zeile = f" {s:<12}"
|
||||
for sl in (0.0, 0.10, 0.20):
|
||||
x1 = kennzahlen(lauf(K, d, 0, m, d["med_sp"], slip=sl))
|
||||
x2 = kennzahlen(lauf(K, d, m, len(d["C"]), d["med_sp"], slip=sl))
|
||||
if x1 and x2:
|
||||
zeile += f" +{sl:.2f}: {x1['oer']:+.3f}/{x2['oer']:+.3f}"
|
||||
print(zeile)
|
||||
|
||||
print()
|
||||
print("=" * 100)
|
||||
print(f" ERGEBNIS: {len(fest)} von {len(daten)-1} geprueften Fremd-Instrumenten "
|
||||
f"halten alle vier Bedingungen")
|
||||
if fest:
|
||||
print(f" Kandidaten: {', '.join(fest)}")
|
||||
print()
|
||||
print(" ⚠ MEHRFACHVERGLEICH mitlesen: bei 5 % Fehlalarmquote und "
|
||||
f"{len(daten)-1} Tests")
|
||||
print(" waere ~0,4 Zufallstreffer zu erwarten. 1 von 9 ist deshalb schwach,")
|
||||
print(" mehrere sind ein Muster. Die Nachbar-Pruefung senkt die Rate zusaetzlich.")
|
||||
print(" ⚠ NICHT gemessen: Korrelation der Instrumente untereinander (mehrere")
|
||||
print(" Slots gleichzeitig = evtl. dasselbe Risiko doppelt), Swap/Kommission")
|
||||
print(" je Symbol, Handelszeiten und Mindest-Lots. Das gehoert VOR einen Bau.")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user