Beide offenen Vorschlaege umgesetzt: Trail 1,0 + Legacy-Recheck
VORSCHLAG 1 - Trail-Multiplikator 1,5 -> 1,0 (core/exit_model.py LIVE.mult),
live verifiziert ("AKTIVIERT ... mult=1.0x").
Voller Sweep mit FIXEM SL 2,0 (backtest_trailmult.py, 8 Werte 0,5-3,0):
enger ist MONOTON besser. Wave-Signal H1/H2 SigmaR:
0,5 +1258/+2418 · 0,8 +852/+1668 · 1,0 +80/+1360
1,5 -220/+1305 · 2,0 -394/+1198 · 2,5 -1072/+1327
Optimum am RAND = Warnsignal, deshalb Gegentest auf SQUEEZE-Entries mit
Echtkosten (--squeeze): das Setup lebt von Laeufern, muesste also
dagegenhalten - tut es NICHT (1,0: H1 -0,202/H2 +0,010 · 1,5:
-0,219/-0,032). Deshalb 1,0 (besser als 1,5 in beiden Haelften auf BEIDEN
Signalmengen), aber NICHT 0,5: Randwert, und die Sim modelliert keine
Exit-Slippage - ein engerer Trail loest viel haeufiger aus und ist davon
staerker betroffen (real bis 0,75xATR ueber den Stop).
VORSCHLAG 2 - backtest_legacy_recheck.py (NEU): die beiden Legacy-Befunde
nachgerechnet, die live ECHTE Gates steuern.
EIA-Blackout BESTAETIGT: auch mit echtem Exit + Echtkosten in BEIDEN
Haelften schlechter als der Rest (-0,097 / -0,119). Gate ist gedeckt.
DEAD-HOURS reproduzieren NICHT als selektiver Befund: 19 von 22 Stunden
sind in beiden Haelften negativ, keine robust positive Stunde. Die alte
Auswahl (0-7, 12, 16) ist damit nicht mehr gestuetzt. Praktisch folgenlos
(dead_hours ist leer), aber eine Reaktivierung auf der alten Begruendung
waere nicht gedeckt.
Einschraenkung selbst benannt: der Recheck handelt die UNGEGATETE
EMA-Richtung (ohne min_conf, Breakout-Bestaetigung, HTF-Filter, Raum-Gate).
Fuer den Vergleich neutral, die absoluten Werte sind nicht das Live-Signal.
Konsequenz fuer den TF-Churn-Fix: "WARTEN Richtung 43 %" ist eine
Haeufigkeits-, keine Ertragsgroesse - mehr Signale sind nur dann besser,
wenn die freigegebenen Setups auch tragen.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
4c1a68a7e1
commit
bb17ecff0a
@@ -1982,8 +1982,19 @@ konstant; (d) es gab **keine Telemetrie über die Entscheidung selbst**.
|
|||||||
| 3,0 | −1088 | +1397 | −2,00 |
|
| 3,0 | −1088 | +1397 | −2,00 |
|
||||||
**Der Worst-Case ist bei JEDEM Multiplikator identisch −2,00** (der SL deckelt).
|
**Der Worst-Case ist bei JEDEM Multiplikator identisch −2,00** (der SL deckelt).
|
||||||
**Die Entscheidung bleibt richtig:** 1,5 schlägt 2,0 UND 2,5 in beiden Hälften.
|
**Die Entscheidung bleibt richtig:** 1,5 schlägt 2,0 UND 2,5 in beiden Hälften.
|
||||||
⚠ **NEU und offen:** **Trail 1,0 schlägt 1,5 in BEIDEN Hälften** (−73/+1346 vs
|
**➜ TRAIL AUF 1,0 GESETZT (User-Vorgabe „setze alle Vorschläge um", 2026-07-31),
|
||||||
−325/+1294) — eigener Vorschlag, bewusst NICHT ungefragt umgesetzt.
|
in `core/exit_model.py` (`LIVE.mult`), live verifiziert (`AKTIVIERT … mult=1.0x`).**
|
||||||
|
Voller Sweep (`backtest_trailmult.py`, fixer SL 2,0, 8 Werte 0,5–3,0): **enger ist
|
||||||
|
MONOTON besser** — Wave-Signal H1/H2 ΣR: 0,5 **+1258/+2418** · 0,8 +852/+1668 ·
|
||||||
|
1,0 +80/+1360 · 1,5 −220/+1305 · 2,0 −394/+1198 · 2,5 −1072/+1327.
|
||||||
|
⚠ **Optimum am RAND des Suchbereichs = Warnsignal** („misst man besser oder nur
|
||||||
|
weniger?"). **Gegentest auf SQUEEZE-Entries mit Echtkosten** (`--squeeze`) — das
|
||||||
|
Setup lebt von LÄUFERN, müsste also dagegenhalten: **tut es nicht**, auch dort ist
|
||||||
|
enger monoton besser (1,0: H1 −0,202/H2 +0,010 · 1,5: −0,219/−0,032). Deshalb der
|
||||||
|
Schritt auf 1,0 — besser als 1,5 in beiden Hälften auf **beiden** Signalmengen —
|
||||||
|
aber **nicht** auf 0,5 (Randwert). ⚠ Zweiter Grund gegen 0,5: die Sim modelliert
|
||||||
|
**keine Exit-Slippage**; ein engerer Trail löst viel häufiger aus und ist stärker
|
||||||
|
betroffen (real gemessen bis 0,75×ATR über den Stop hinaus).
|
||||||
- **Stufe 2 — ERLEDIGT:** **Entscheidungs-Telemetrie.** (a) Jede Empfehlung loggt
|
- **Stufe 2 — ERLEDIGT:** **Entscheidungs-Telemetrie.** (a) Jede Empfehlung loggt
|
||||||
jetzt einen maschinenlesbaren **`block_reason`** (`recommendations.block_reason`,
|
jetzt einen maschinenlesbaren **`block_reason`** (`recommendations.block_reason`,
|
||||||
DB migriert mit Backup): `deadband · dead_hour · eia · htf_counter · stretch ·
|
DB migriert mit Backup): `deadband · dead_hour · eia · htf_counter · stretch ·
|
||||||
@@ -2028,6 +2039,25 @@ konstant; (d) es gab **keine Telemetrie über die Entscheidung selbst**.
|
|||||||
**Migrations-Regel (in `exit_model.py` festgehalten):** ein Backtest darf nur auf
|
**Migrations-Regel (in `exit_model.py` festgehalten):** ein Backtest darf nur auf
|
||||||
`simulate()` umgestellt werden, wenn er danach DIESELBEN Zahlen liefert — sonst ist
|
`simulate()` umgestellt werden, wenn er danach DIESELBEN Zahlen liefert — sonst ist
|
||||||
es keine Refaktorierung, sondern eine unbemerkte Änderung der Messgrundlage.
|
es keine Refaktorierung, sondern eine unbemerkte Änderung der Messgrundlage.
|
||||||
|
**LEGACY-RECHECK (`backtest_legacy_recheck.py`, 2026-07-31): die beiden Befunde
|
||||||
|
nachgerechnet, die live ECHTE Gates steuern.**
|
||||||
|
· ✅ **EIA-Blackout BESTÄTIGT** — auch mit dem echten Exit + Echtkosten ist das
|
||||||
|
Fenster Mi 15:30–16:30 in BEIDEN Hälften schlechter als der Rest (ØR-Differenz
|
||||||
|
−0,097 / −0,119). Das aktive Gate ist gedeckt.
|
||||||
|
· ❌ **DEAD-HOURS reproduzieren NICHT als selektiver Befund** — mit dem echten Exit
|
||||||
|
sind **19 von 22 Stunden in BEIDEN Hälften negativ**, es gibt **keine** robust
|
||||||
|
positive Stunde. Die alte Analyse hatte 0–7 sowie 12 und 16 als *die* schlechten
|
||||||
|
Stunden herausgehoben; wenn fast alles negativ ist, ist diese Auswahl nicht mehr
|
||||||
|
gestützt. (Praktisch folgenlos, weil `dead_hours` ohnehin leer ist — aber eine
|
||||||
|
Reaktivierung auf der alten Begründung wäre nicht gedeckt.)
|
||||||
|
⚠ **Einschränkung des Rechecks (selbst benannt):** er handelt die **ungegatete**
|
||||||
|
EMA-Richtung — ohne Mindest-Konfidenz, Breakout-Bestätigung, HTF-Filter und
|
||||||
|
Raum-Gate. Für den VERGLEICH (Fenster gegen Rest, Stunde gegen Stunde) ist das
|
||||||
|
neutral, die absoluten Werte sind aber NICHT das Live-Signal.
|
||||||
|
⚠ **Konsequenz für die Erfolgskontrolle des TF-Churn-Fixes:** „WARTEN-Anteil
|
||||||
|
Richtung 43 % bewegen" ist als Zielgröße mit Vorsicht zu lesen — die 43 % stammen
|
||||||
|
aus `backtest_dist.py` (Signal-HÄUFIGKEIT), sie sind kein Ertragsversprechen. Mehr
|
||||||
|
Signale sind nur dann besser, wenn die freigegebenen Setups auch tragen.
|
||||||
|
|
||||||
⚠ **Erwartung: die drei Fälle sind nicht vollständig.** Sie wurden bei gezielter Suche
|
⚠ **Erwartung: die drei Fälle sind nicht vollständig.** Sie wurden bei gezielter Suche
|
||||||
in ~20 min gefunden; die Trefferquote spricht für weitere. `analyze_divergence.py`
|
in ~20 min gefunden; die Trefferquote spricht für weitere. `analyze_divergence.py`
|
||||||
|
|||||||
@@ -0,0 +1,165 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Legacy-Befunde mit dem ECHTEN Exit gegenprüfen (2026-07-31, Stufe-3-Folgearbeit).
|
||||||
|
|
||||||
|
BEFUND BEIM STUFE-3-REFACTOR: sechs Backtests nutzen einen **stark vereinfachten
|
||||||
|
Exit** (`_TPTRAIL=0.5`, KEIN Breakeven, KEIN Lock, KEIN Time-Stop, `_MAXH=240`):
|
||||||
|
`backtest_hourly`, `_hourly_split`, `_bounce`, `_events`, `_deadhour`, `_chopgate`.
|
||||||
|
Auf ihnen ruhen aber Befunde, die live **echte Gates** steuern:
|
||||||
|
|
||||||
|
* **EIA-Blackout** (Mi 15:30–16:30) — AKTIV, blockt Trades (`backtest_events.py`)
|
||||||
|
* **Dead-Hours** (0–7, 12, 16) — aktuell aus, aber jederzeit reaktivierbar
|
||||||
|
(`backtest_hourly_split.py`)
|
||||||
|
|
||||||
|
Wenn diese Befunde nur unter dem vereinfachten Exit gelten, gaten wir live auf einer
|
||||||
|
Grundlage, die es nicht mehr gibt. Genau das ist Deployment-Drift.
|
||||||
|
|
||||||
|
DIESES SKRIPT rechnet beide Aussagen mit `core/exit_model.simulate()` neu — dem
|
||||||
|
Exit, den das Live-System wirklich fährt (Phasen, Breakeven 1,3, Time-Stop,
|
||||||
|
`max_hold` 200) — plus ECHTKOSTEN (Bar-Spread/ATR) und dem 2-Hälften-Protokoll.
|
||||||
|
|
||||||
|
⚠ Es geht NICHT darum, die alten Zahlen zu reproduzieren (das kann es nicht — anderer
|
||||||
|
Exit), sondern um die FRAGE: hält die SCHLUSSFOLGERUNG? Also: ist das EIA-Fenster
|
||||||
|
auch mit dem echten Exit in beiden Hälften schlechter als der Rest, und sind die
|
||||||
|
Dead-Hours es auch?
|
||||||
|
"""
|
||||||
|
import sys
|
||||||
|
from datetime import datetime, timezone, timedelta
|
||||||
|
|
||||||
|
import MetaTrader5 as mt5
|
||||||
|
|
||||||
|
from core.analysis import calc_trend_angle
|
||||||
|
from core.exit_model import LIVE, simulate
|
||||||
|
from core.wave_rec import (_EMA_FAST, _EMA_SLOW, _N_BARS, _ANGLE_LR, _ANGLE_DEAD,
|
||||||
|
_REVERSAL_STRETCH, _STRETCH_MAX)
|
||||||
|
|
||||||
|
_ATRMIN = 0.12
|
||||||
|
_BROKER_OFF_H = 3 # Brokerzeit = UTC+3
|
||||||
|
_BERLIN_OFF_H = 2 # Sommerzeit (die History liegt im Sommerhalbjahr)
|
||||||
|
|
||||||
|
|
||||||
|
def _ema_series(v, p):
|
||||||
|
k = 2.0 / (p + 1); o = []; e = v[0]
|
||||||
|
for i, x in enumerate(v):
|
||||||
|
e = x if i == 0 else x * k + e * (1 - k)
|
||||||
|
o.append(e)
|
||||||
|
return o
|
||||||
|
|
||||||
|
|
||||||
|
def _atr_series(H, L, C, p=14):
|
||||||
|
t = [0.0]
|
||||||
|
for i in range(1, len(C)):
|
||||||
|
t.append(max(H[i] - L[i], abs(H[i] - C[i - 1]), abs(L[i] - C[i - 1])))
|
||||||
|
out = []
|
||||||
|
for i in range(len(C)):
|
||||||
|
if not i:
|
||||||
|
out.append(None); continue
|
||||||
|
w = t[max(1, i - p + 1):i + 1]
|
||||||
|
out.append(sum(w) / max(1, len(w)))
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
def _berlin(ts_broker: int) -> datetime:
|
||||||
|
"""Broker-Epoch (UTC+3) → Berliner Wanduhr."""
|
||||||
|
return datetime.fromtimestamp(ts_broker, timezone.utc) \
|
||||||
|
- timedelta(hours=_BROKER_OFF_H) + timedelta(hours=_BERLIN_OFF_H)
|
||||||
|
|
||||||
|
|
||||||
|
def trades(T, H, L, C, EF, ES, AT, SP, lo, hi, step=2):
|
||||||
|
"""→ [(berlin_dt, R_netto)] mit dem ECHTEN Exit + Echtkosten."""
|
||||||
|
TH = _REVERSAL_STRETCH; out = []
|
||||||
|
for i in range(max(lo, _N_BARS), min(hi, len(C) - LIVE.max_hold - 1), step):
|
||||||
|
atr = AT[i]
|
||||||
|
if not atr or atr <= 0:
|
||||||
|
continue
|
||||||
|
atr = max(atr, _ATRMIN); es = ES[i]; ef = EF[i]
|
||||||
|
stretch = (C[i] - es) / atr
|
||||||
|
ang = calc_trend_angle(C[i - _ANGLE_LR - 2:i], _ANGLE_LR); ad = ang - 90.0
|
||||||
|
d = 0
|
||||||
|
if stretch <= -TH and ad >= _ANGLE_DEAD: d = 1
|
||||||
|
elif stretch >= TH and ad <= -_ANGLE_DEAD: d = -1
|
||||||
|
elif abs(stretch) < _STRETCH_MAX: d = 1 if ef > es else -1 if ef < es else 0
|
||||||
|
if not d:
|
||||||
|
continue
|
||||||
|
cost = (SP[i] if SP[i] > 0 else 0.0225) / atr
|
||||||
|
R = simulate(C[i], d, atr, H, L, C, i + 1, LIVE) - cost
|
||||||
|
out.append((_berlin(int(T[i])), R))
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
def stat(rs):
|
||||||
|
if not rs:
|
||||||
|
return "n=0"
|
||||||
|
n = len(rs); s = sum(rs)
|
||||||
|
g = sum(x for x in rs if x > 0); l = -sum(x for x in rs if x < 0)
|
||||||
|
return (f"n={n:>6} ØR={s/n:+.3f} PF={(g/l if l > 0 else 9.99):>4.2f} ΣR={s:+.0f}")
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
n = int(sys.argv[1]) if len(sys.argv) > 1 else 80000
|
||||||
|
mt5.initialize(); sym = None
|
||||||
|
for c in ("SpotCrude", "USOIL", "WTI", "XTIUSD"):
|
||||||
|
if mt5.symbol_info(c):
|
||||||
|
sym = c; break
|
||||||
|
bars = mt5.copy_rates_from_pos(sym, mt5.TIMEFRAME_M5, 0, n)
|
||||||
|
si = mt5.symbol_info(sym); point = si.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]
|
||||||
|
EF = _ema_series(C, _EMA_FAST); ES = _ema_series(C, _EMA_SLOW); AT = _atr_series(H, L, C)
|
||||||
|
mid = len(C) // 2
|
||||||
|
|
||||||
|
print("=" * 90)
|
||||||
|
print(f" LEGACY-RECHECK mit dem ECHTEN Exit — {sym} M5 ({len(C)} Bars)")
|
||||||
|
print(f" Exit: core/exit_model.py (trail {LIVE.mult} · BE {LIVE.be} · "
|
||||||
|
f"Time-Stop {LIVE.timestop_bars}) · Echtkosten")
|
||||||
|
print(" Alt gemessen wurde mit einem Exit OHNE Breakeven/Lock/Time-Stop.")
|
||||||
|
print("=" * 90)
|
||||||
|
|
||||||
|
halves = [("H1", _N_BARS, mid), ("H2", mid, len(C))]
|
||||||
|
data = {lbl: trades(T, H, L, C, EF, ES, AT, SP, a, b) for lbl, a, b in halves}
|
||||||
|
for lbl, _, _ in halves:
|
||||||
|
print(f" {lbl}: {len(data[lbl])} Trades")
|
||||||
|
|
||||||
|
# ── 1) EIA-Blackout: Mi 15:30–16:30 Berlin (LIVE AKTIVES GATE)
|
||||||
|
print("\n" + "=" * 90)
|
||||||
|
print(" 1) EIA-BLACKOUT (Mi 15:30–16:30 Berlin) — blockt live echte Trades")
|
||||||
|
print("=" * 90)
|
||||||
|
for lbl, _, _ in halves:
|
||||||
|
inw = [r for t, r in data[lbl]
|
||||||
|
if t.weekday() == 2 and (15, 30) <= (t.hour, t.minute) < (16, 30)]
|
||||||
|
out = [r for t, r in data[lbl]
|
||||||
|
if not (t.weekday() == 2 and (15, 30) <= (t.hour, t.minute) < (16, 30))]
|
||||||
|
print(f" {lbl} im Fenster : {stat(inw)}")
|
||||||
|
print(f" {lbl} ausserhalb : {stat(out)}")
|
||||||
|
if inw and out:
|
||||||
|
delta = sum(inw) / len(inw) - sum(out) / len(out)
|
||||||
|
print(f" → Differenz ØR {delta:+.3f} "
|
||||||
|
f"{'Fenster schlechter (Gate bestätigt)' if delta < 0 else '⚠ Fenster BESSER — Gate nicht gedeckt'}")
|
||||||
|
|
||||||
|
# ── 2) Dead-Hours: Netto-R je Berliner Stunde
|
||||||
|
print("\n" + "=" * 90)
|
||||||
|
print(" 2) STUNDEN-PROFIL (Dead-Hours-Grundlage) — Netto-ØR je Berliner Stunde")
|
||||||
|
print("=" * 90)
|
||||||
|
print(f" {'Std':<5}{'H1 n':>7}{'H1 ØR':>9}{'H2 n':>7}{'H2 ØR':>9} Urteil")
|
||||||
|
robust_neg, robust_pos = [], []
|
||||||
|
for h in range(24):
|
||||||
|
a = [r for t, r in data["H1"] if t.hour == h]
|
||||||
|
b = [r for t, r in data["H2"] if t.hour == h]
|
||||||
|
if len(a) < 30 or len(b) < 30:
|
||||||
|
continue
|
||||||
|
ma, mb = sum(a) / len(a), sum(b) / len(b)
|
||||||
|
verdict = ""
|
||||||
|
if ma < 0 and mb < 0:
|
||||||
|
verdict = "beidseitig NEGATIV"; robust_neg.append(h)
|
||||||
|
elif ma > 0 and mb > 0:
|
||||||
|
verdict = "beidseitig positiv"; robust_pos.append(h)
|
||||||
|
print(f" {h:<5}{len(a):>7}{ma:>+9.3f}{len(b):>7}{mb:>+9.3f} {verdict}")
|
||||||
|
print(f"\n Robust NEGATIV: {robust_neg or '—'}")
|
||||||
|
print(f" Robust POSITIV: {robust_pos or '—'}")
|
||||||
|
print(" ⚠ Alt (vereinfachter Exit) waren 0–7 sowie 12 und 16 die Kandidaten.")
|
||||||
|
print(" Deckt sich das? Falls nicht, ruht das Dead-Hours-Gate auf dem alten Exit.")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
+49
-5
@@ -81,17 +81,50 @@ def signals(H, L, C, ES, EF, AT, lo, hi, step):
|
|||||||
return out
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
def squeeze_signals(H, L, C, AT, lo, hi):
|
||||||
|
"""Nur SQUEEZE-Ausbrüche — der EINZIGE Setup mit belegtem Edge, und er lebt von
|
||||||
|
LÄUFERN (43 % Treffer, Ertrag im Tail). Gegentest zum Wave-Signal: ein enger
|
||||||
|
Trail, der dort gewinnt, muss hier verlieren, wenn er nur „früher aussteigen"
|
||||||
|
belohnt. Regel exakt wie live (`wave._squeeze_one`)."""
|
||||||
|
from core.wave_rec import _SQ_N, _SQ_MULT, _SQ_K
|
||||||
|
_W = 24; _COOL = 12
|
||||||
|
out = []
|
||||||
|
i = max(lo, _N_BARS, _SQ_N + 15)
|
||||||
|
while i < min(hi, len(C) - LIVE.max_hold - 1):
|
||||||
|
atr = AT[i]
|
||||||
|
if not atr or atr < 0.06:
|
||||||
|
i += 1; continue
|
||||||
|
boxHi = max(H[i - _SQ_N:i]); boxLo = min(L[i - _SQ_N:i])
|
||||||
|
if (boxHi - boxLo) > _SQ_MULT * atr:
|
||||||
|
i += 1; continue
|
||||||
|
hit = None
|
||||||
|
for j in range(i, min(i + _W, len(C) - LIVE.max_hold - 1)):
|
||||||
|
up = boxHi + _SQ_K * atr; dn = boxLo - _SQ_K * atr
|
||||||
|
if H[j] >= up: hit = (j, 1, up); break
|
||||||
|
if L[j] <= dn: hit = (j, -1, dn); break
|
||||||
|
if hit is None:
|
||||||
|
i += 1; continue
|
||||||
|
j, d, lvl = hit
|
||||||
|
out.append((j, d, atr, lvl))
|
||||||
|
i = j + _COOL
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
n = int(sys.argv[1]) if len(sys.argv) > 1 else 80000
|
squeeze = "--squeeze" in sys.argv
|
||||||
step = int(sys.argv[2]) if len(sys.argv) > 2 else 2
|
args = [a for a in sys.argv[1:] if not a.startswith("--")]
|
||||||
|
n = int(args[0]) if args else 80000
|
||||||
|
step = int(args[1]) if len(args) > 1 else 2
|
||||||
mt5.initialize(); sym = None
|
mt5.initialize(); sym = None
|
||||||
for c in ("SpotCrude", "USOIL", "WTI", "XTIUSD"):
|
for c in ("SpotCrude", "USOIL", "WTI", "XTIUSD"):
|
||||||
if mt5.symbol_info(c):
|
if mt5.symbol_info(c):
|
||||||
sym = c; break
|
sym = c; break
|
||||||
bars = mt5.copy_rates_from_pos(sym, mt5.TIMEFRAME_M5, 0, n)
|
bars = mt5.copy_rates_from_pos(sym, mt5.TIMEFRAME_M5, 0, n)
|
||||||
|
si = mt5.symbol_info(sym); point = si.point
|
||||||
mt5.shutdown()
|
mt5.shutdown()
|
||||||
H = [float(b["high"]) for b in bars]; L = [float(b["low"]) 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]
|
C = [float(b["close"]) for b in bars]
|
||||||
|
SP = [float(b["spread"]) * point for b in bars]
|
||||||
EF = _ema_series(C, _EMA_FAST); ES = _ema_series(C, _EMA_SLOW); AT = _atr_series(H, L, C)
|
EF = _ema_series(C, _EMA_FAST); ES = _ema_series(C, _EMA_SLOW); AT = _atr_series(H, L, C)
|
||||||
mid = len(C) // 2
|
mid = len(C) // 2
|
||||||
halves = [("H1 (alt)", _N_BARS, mid), ("H2 (neu)", mid, len(C))]
|
halves = [("H1 (alt)", _N_BARS, mid), ("H2 (neu)", mid, len(C))]
|
||||||
@@ -99,16 +132,27 @@ def main():
|
|||||||
print(f" TRAIL-MULTIPLIKATOR — {sym} M5 ({len(C)} Bars)")
|
print(f" TRAIL-MULTIPLIKATOR — {sym} M5 ({len(C)} Bars)")
|
||||||
print(f" Initial-SL FEST {LIVE.sl_atr}×ATR (wie live) · nur der Trail variiert")
|
print(f" Initial-SL FEST {LIVE.sl_atr}×ATR (wie live) · nur der Trail variiert")
|
||||||
print(f" Exit aus core/exit_model.py · Time-Stop {LIVE.timestop_bars} Bars · BE {LIVE.be}")
|
print(f" Exit aus core/exit_model.py · Time-Stop {LIVE.timestop_bars} Bars · BE {LIVE.be}")
|
||||||
|
if squeeze:
|
||||||
|
print(" Signal-Set: NUR SQUEEZE-Ausbrueche (laeuferabhaengig) · ECHTKOSTEN")
|
||||||
print("=" * 92)
|
print("=" * 92)
|
||||||
SIG = {lbl: signals(H, L, C, ES, EF, AT, a, b, step) for lbl, a, b in halves}
|
if squeeze:
|
||||||
|
SIG = {lbl: [(i, d, atr) for (i, d, atr, _lv)
|
||||||
|
in squeeze_signals(H, L, C, AT, a, b)] for lbl, a, b in halves}
|
||||||
|
else:
|
||||||
|
SIG = {lbl: signals(H, L, C, ES, EF, AT, a, b, step) for lbl, a, b in halves}
|
||||||
for lbl, _, _ in halves:
|
for lbl, _, _ in halves:
|
||||||
print(f" {lbl}: {len(SIG[lbl])} Signale")
|
print(f" {lbl}: {len(SIG[lbl])} Signale")
|
||||||
for mult in (1.0, 1.5, 2.0, 2.5, 3.0):
|
# ⚠ Auch die kleinen Werte prüfen: läuft es nach unten monoton weiter besser,
|
||||||
|
# liegt das „Optimum" am Rand des getesteten Bereichs = Warnsignal (dann misst
|
||||||
|
# man womöglich nur „macht weniger" statt „macht es besser").
|
||||||
|
for mult in (0.5, 0.75, 1.0, 1.25, 1.5, 2.0, 2.5, 3.0):
|
||||||
tag = " ← live (nach Stufe 1)" if abs(mult - 1.5) < 1e-9 else ""
|
tag = " ← live (nach Stufe 1)" if abs(mult - 1.5) < 1e-9 else ""
|
||||||
print(f"\nTrail {mult:.1f}×ATR{tag}")
|
print(f"\nTrail {mult:.1f}×ATR{tag}")
|
||||||
for lbl, _, _ in halves:
|
for lbl, _, _ in halves:
|
||||||
p = LIVE.with_(mult=mult)
|
p = LIVE.with_(mult=mult)
|
||||||
Rs = [simulate(C[i], d, atr, H, L, C, i + 1, p) for (i, d, atr) in SIG[lbl]]
|
Rs = [simulate(C[i], d, atr, H, L, C, i + 1, p)
|
||||||
|
- ((SP[i] if SP[i] > 0 else 0.0225) / atr if squeeze else 0.0)
|
||||||
|
for (i, d, atr) in SIG[lbl]]
|
||||||
print(f" {lbl} {st(Rs)}")
|
print(f" {lbl} {st(Rs)}")
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+14
-2
@@ -41,7 +41,7 @@ class ExitParams:
|
|||||||
"""Alle Größen in ×ATR, Zeiten in Bars der jeweiligen Basis-TF."""
|
"""Alle Größen in ×ATR, Zeiten in Bars der jeweiligen Basis-TF."""
|
||||||
sl_atr: float = 2.0 # Initial-SL (Live-Band 1,8–2,2 → Ziel 2,0)
|
sl_atr: float = 2.0 # Initial-SL (Live-Band 1,8–2,2 → Ziel 2,0)
|
||||||
trail_start: float = 0.3 # Phase Init→Trail
|
trail_start: float = 0.3 # Phase Init→Trail
|
||||||
mult: float = 1.5 # Trail-Abstand HW ∓ mult×ATR
|
mult: float = 1.0 # Trail-Abstand HW ∓ mult×ATR (1,5 → 1,0 am 2026-07-31)
|
||||||
be: float = 1.3 # Breakeven-Boden (Entry) ab diesem Profit
|
be: float = 1.3 # Breakeven-Boden (Entry) ab diesem Profit
|
||||||
lock_start: float = 3.5 # Phase Trail→Lock
|
lock_start: float = 3.5 # Phase Trail→Lock
|
||||||
lock_scale: float = 0.6 # mult×scale in der Lock-Phase
|
lock_scale: float = 0.6 # mult×scale in der Lock-Phase
|
||||||
@@ -57,7 +57,19 @@ class ExitParams:
|
|||||||
# ── DIE Live-Parameter. `core/trailing.py` importiert sie von hier. ───────────
|
# ── DIE Live-Parameter. `core/trailing.py` importiert sie von hier. ───────────
|
||||||
# Werte = der jeweils GEMESSENE Stand:
|
# Werte = der jeweils GEMESSENE Stand:
|
||||||
# sl 2,0 `backtest_exit.py` (Band 1,8–2,2, Ø-R +32 % ggü. 1,2–1,5)
|
# sl 2,0 `backtest_exit.py` (Band 1,8–2,2, Ø-R +32 % ggü. 1,2–1,5)
|
||||||
# trail 1,5 `backtest_trailing.py` (2,0/2,5 messbar schlechter, s. Stufe 1)
|
# trail 1,0 `backtest_trailmult.py` (2026-07-31) — SAUBER gemessen mit FIXEM
|
||||||
|
# SL 2,0 (der alte `backtest_trailing.py` koppelte den SL an mult und
|
||||||
|
# vermengte damit zwei Änderungen). Enger ist MONOTON besser, und zwar
|
||||||
|
# auf BEIDEN Signalmengen und in BEIDEN Hälften:
|
||||||
|
# Wave-Signal 1,0: H1 +80/H2 +1360 · 1,5: −220/+1305 · 2,0: −394/+1198
|
||||||
|
# Squeeze(Kost) 1,0: H1 −0,202/H2 +0,010 · 1,5: −0,219/−0,032
|
||||||
|
# Der Squeeze ist der Gegentest: er lebt von LÄUFERN — wäre „enger" nur
|
||||||
|
# „macht weniger", müsste er dagegenhalten. Tut er nicht.
|
||||||
|
# ⚠ 0,5 wäre in der Messung NOCH besser (Wave H1 +1258/H2 +2418), liegt
|
||||||
|
# aber am RAND des Suchbereichs → bewusst nicht genommen. Zusätzlicher
|
||||||
|
# Grund: die Sim modelliert **keine Exit-Slippage**; ein engerer Trail
|
||||||
|
# löst deutlich häufiger aus und ist damit stärker davon betroffen
|
||||||
|
# (real gemessen: bis 0,75 ATR über den Stop hinaus).
|
||||||
# start 0,3 `backtest_trailing.py` (0,6/1,0 schlechter)
|
# start 0,3 `backtest_trailing.py` (0,6/1,0 schlechter)
|
||||||
# be 1,3 `backtest_exit.py be` (0,6 → 21 % Breakeven-Scratches)
|
# be 1,3 `backtest_exit.py be` (0,6 → 21 % Breakeven-Scratches)
|
||||||
# timestop 24 `backtest_timestop.py` (120 min; 30/60 min kippten)
|
# timestop 24 `backtest_timestop.py` (120 min; 30/60 min kippten)
|
||||||
|
|||||||
Reference in New Issue
Block a user