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,156 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Fuellt die ruhende Squeeze-Order wirklich so, wie der Backtest unterstellt?
|
||||
|
||||
ANLASS (2026-08-06): Bei `backtest_momentum_v2.py` ist aufgefallen, dass ein
|
||||
"Einstieg am Level" zwei sehr verschiedene Dinge bedeuten kann:
|
||||
(a) einsteigen, WO das Signal ausgeloest hat — also dort, wo der Bar-CLOSE das
|
||||
Level ueberschritten hat, und zwar zum Level-Preis;
|
||||
(b) einsteigen, WANN die liegende Order gefuellt haette — bei der ERSTEN
|
||||
Beruehrung des Levels, unabhaengig davon, wo der Bar schliesst.
|
||||
Live gilt (b). Der Unterschied ist kein Detail: beim Momentum lag er bei **0,45 R**
|
||||
und hat aus +0,40 ein −0,06 gemacht — die Variante (a) konditioniert auf den
|
||||
Bar-Schluss, den eine liegende Order nicht kennen kann. Look-ahead.
|
||||
|
||||
⚠ DIESELBE FRAGE MUSS FUER DEN SQUEEZE BEANTWORTET WERDEN, denn `squeeze_pending_
|
||||
entry` laeuft seit 05.08. mit ECHTEM Geld, und die Erwartung dahinter (+0,456/
|
||||
+0,611 aus `backtest_squeeze_entry.py`) stammt aus Variante (a).
|
||||
|
||||
GRUND ZUR HOFFNUNG — aber eben nur Hoffnung, bis gemessen: die Squeeze-Box steht
|
||||
ueber viele Bars still, das Level ist also ein echter, stabiler Ort fuer eine
|
||||
Order. Das Momentum-Level wandert dagegen mit `C[i−N]` jede Bar mit. Wenn der
|
||||
Unterschied klein ist, war die Hoffnung berechtigt; ist er gross, ist die
|
||||
Live-Erwartung zu hoch angesetzt und gehoert korrigiert.
|
||||
|
||||
Aufruf: python backtest_squeeze_touchfill.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
|
||||
|
||||
_ATRMIN = 0.06
|
||||
|
||||
|
||||
def boxen(H, L, C, A):
|
||||
"""Je Bar die AKTUELLE Box-Grenze + Ausbruchs-Level — auch ohne Ausbruch.
|
||||
|
||||
Das ist der Zustand, in dem die ruhende Order liegt (`armed`): die Box steht,
|
||||
die beiden Stop-Level stehen. Ob der Kurs sie beruehrt, entscheidet der Markt.
|
||||
"""
|
||||
out = []
|
||||
for i in range(wr._SQ_N + 2, len(C) - 2):
|
||||
atr = A[i - 1] # am Ende der Vorbar bekannt
|
||||
if not atr or atr < _ATRMIN:
|
||||
out.append(None); continue
|
||||
hb = H[i - wr._SQ_N - 1:i - 1]; lb = L[i - wr._SQ_N - 1:i - 1]
|
||||
if not hb or not lb:
|
||||
out.append(None); continue
|
||||
hi, lo = max(hb), min(lb)
|
||||
if (hi - lo) / atr > wr._SQ_MULT: # keine Kompression → keine Order
|
||||
out.append(None); continue
|
||||
out.append((i, hi + wr._SQ_K * atr, lo - wr._SQ_K * atr, atr))
|
||||
return out
|
||||
|
||||
|
||||
def lauf_close(H, L, C, SP, A, lo, hi):
|
||||
"""(a) Projekt-Standard: Einstieg dort, wo `_squeeze_one` `active` meldet
|
||||
(Bar-CLOSE hat das Level gebrochen), zum Level-Preis."""
|
||||
rs, frei_ab, last = [], lo, None
|
||||
for i in range(wr._SQ_N + 2, len(C) - 2):
|
||||
if not (lo <= i < hi) or i < frei_ab:
|
||||
continue
|
||||
atr = A[i]
|
||||
if not atr or atr < _ATRMIN:
|
||||
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
|
||||
d = 1 if st["dir"] == "LONG" else -1
|
||||
r, xb = simulate(float(st["level"]), d, atr, H, L, C, i + 1, LIVE,
|
||||
timestop=True, use_tp=True, ret_bar=True)
|
||||
rs.append(r - (SP[i] if SP[i] > 0 else 0.0225) / atr)
|
||||
frei_ab = xb + 1
|
||||
return rs
|
||||
|
||||
|
||||
def lauf_touch(H, L, C, SP, A, B, lo, hi):
|
||||
"""(b) Realitaet: die Order liegt an beiden Box-Grenzen und fuellt bei der
|
||||
ERSTEN Beruehrung — auch wenn der Bar wieder in die Box zurueckfaellt."""
|
||||
rs, frei_ab = [], lo
|
||||
for eintrag in B:
|
||||
if eintrag is None:
|
||||
continue
|
||||
i, lvl_hi, lvl_lo, atr = eintrag
|
||||
if not (lo <= i < hi) or i < frei_ab:
|
||||
continue
|
||||
d = 0
|
||||
if H[i] >= lvl_hi:
|
||||
d, lvl = 1, lvl_hi
|
||||
elif L[i] <= lvl_lo:
|
||||
d, lvl = -1, lvl_lo
|
||||
if not d:
|
||||
continue
|
||||
r, xb = simulate(lvl, d, atr, H, L, C, i + 1, LIVE,
|
||||
timestop=True, use_tp=True, ret_bar=True)
|
||||
rs.append(r - (SP[i] if SP[i] > 0 else 0.0225) / 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
|
||||
sym = next((c for c in ("SpotCrude", "USOIL", "WTI", "XTIUSD")
|
||||
if mt5.symbol_info(c)), None)
|
||||
b = mt5.copy_rates_from_pos(sym, mt5.TIMEFRAME_M5, 0, n)
|
||||
point = mt5.symbol_info(sym).point
|
||||
mt5.shutdown()
|
||||
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"]) * point for x in b]
|
||||
A = _atr_series(H, L, C)
|
||||
B = boxen(H, L, C, A)
|
||||
m = len(C) // 2
|
||||
|
||||
print("=" * 92)
|
||||
print(f" SQUEEZE — Fill bei BAR-CLOSE-Ausbruch gegen Fill bei BERUEHRUNG ({sym})")
|
||||
print("=" * 92)
|
||||
print(f" {len(C)} M5-Bars · kanonischer Exit · Echtkosten · sequentiell EIN Slot")
|
||||
print()
|
||||
a1 = kennzahlen(lauf_close(H, L, C, SP, A, 0, m))
|
||||
a2 = kennzahlen(lauf_close(H, L, C, SP, A, m, len(C)))
|
||||
b1 = kennzahlen(lauf_touch(H, L, C, SP, A, B, 0, m))
|
||||
b2 = kennzahlen(lauf_touch(H, L, C, SP, A, B, m, len(C)))
|
||||
for lbl, x1, x2 in (("(a) Close bricht Level (Backtest-Standard)", a1, a2),
|
||||
("(b) Order fuellt bei Beruehrung (LIVE)", b1, b2)):
|
||||
if not x1 or not x2:
|
||||
print(f" {lbl:<44} — keine Trades"); continue
|
||||
print(f" {lbl:<44} H1 ØR {x1['oer']:+.3f} PF {x1['pf']:.2f} n{x1['n']:<5} | "
|
||||
f"H2 ØR {x2['oer']:+.3f} PF {x2['pf']:.2f} n{x2['n']:<5}")
|
||||
if all((a1, a2, b1, b2)):
|
||||
print()
|
||||
print(f" Look-ahead-Aufschlag: H1 {a1['oer']-b1['oer']:+.3f} · "
|
||||
f"H2 {a2['oer']-b2['oer']:+.3f} R je Trade")
|
||||
print(f" Zusaetzliche Fills durch Beruehrung: H1 {b1['n']-a1['n']:+d} · "
|
||||
f"H2 {b2['n']-a2['n']:+d}")
|
||||
gut = b1["oer"] > 0 and b2["oer"] > 0 and b1["pf"] > 1 and b2["pf"] > 1
|
||||
print()
|
||||
print(f" → Der Squeeze haelt der realistischen Fill-Annahme "
|
||||
f"{'STAND ✅' if gut else 'NICHT stand ❌'}")
|
||||
print(" ⚠ Zum Vergleich Momentum (`backtest_momentum_v2.py`): dort betrug")
|
||||
print(" der Aufschlag ~0,45 R und drehte +0,40 auf −0,06.")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user