Auto-Flip-Close gebaut (User-Wunsch) - trotz erneut negativer Messung
User: "schliesse den trade automatisch beim close signal". Dreht die
Empfehlung gegen die offene Position und steht sie >=0,5xATR im Plus ->
close(reason="flip_close").
VORHER NEU GEMESSEN, weil das alte Urteil (backtest_flipclose.py,
16.07.) auf einem VIERTEN Exit-Modell fusste: Trail fest 1,5 (live jetzt
1,0), keine Lock-Phase, kein Time-Stop, kein Initial-TP, _MAXH 288 statt
200, Phasen ueber den Bar-Close statt ueber das High-Water.
backtest_flipclose2.py (80k M5, 2 Halbjahre, Echtkosten, Exit aus
core/exit_model.py) bestaetigt das alte Urteil - deutlicher:
Basis ohne Flip H1 SigmaR -537 · H2 -140
Flip ab 0,0xATR Delta H1 -126 · H2 -221
Flip ab 0,3xATR Delta H1 -77 · H2 -216
Flip ab 0,5xATR Delta H1 -92 · H2 -160 <- mildeste, Default
Flip auch im Minus Delta H1 -205 · H2 -364
JEDE Variante ist in BEIDEN Haelften schlechter. Trefferquote steigt
39 -> 45 %, Ertrag faellt = Gewinner-Kappen (die nachlaufende EMA dreht
oft mitten im Pullback).
Gebaut wurde die mildeste Variante (0,5xATR Mindestgewinn), abschaltbar
ueber [trading] auto_flip_close.
Fuer die Neumessung bekam exit_model.simulate() einen stop_when-Hook
(+ ret_bar), damit die Phasen-Mechanik nicht zum fuenften Mal kopiert
werden musste. Rueckwaertskompatibilitaet verifiziert: ohne Hook bitgenau
identisch (die zunaechst gemeldeten Abweichungen kamen allein aus
LIVE.mult 1,5 -> 1,0, gegengeprueft mit mult=1.5 -> identisch).
Mit 10 synthetischen Szenarien getestet: beide Richtungen, aus, unter
Schwelle, gleichgerichtet, WARTEN, im Minus, flat, Startup-Schonfrist,
Ticket-Dedup - alle korrekt.
B4/B5-Pflicht: Live-Ertrag gegen die Erwartung halten, bei Drift
auto_flip_close=false.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
f1cc3775f9
commit
fea91fddb3
+23
-5
@@ -88,7 +88,9 @@ def simulate(entry: float, d: int, atr: float,
|
||||
H: list, L: list, C: list, j0: int,
|
||||
p: ExitParams = LIVE,
|
||||
timestop: bool = True,
|
||||
use_tp: bool = True) -> float:
|
||||
use_tp: bool = True,
|
||||
stop_when=None,
|
||||
ret_bar: bool = False):
|
||||
"""Kanonische Exit-Simulation → Ergebnis in R (×ATR), Kosten NICHT enthalten.
|
||||
|
||||
`d` = +1 LONG / −1 SHORT · `j0` = erster Bar NACH dem Einstieg.
|
||||
@@ -105,6 +107,14 @@ def simulate(entry: float, d: int, atr: float,
|
||||
(`timestop=True, use_tp=True`) ist also die einzige live-treue Variante. Die
|
||||
Flags erlauben es, ein bestehendes Skript BITGENAU zu migrieren und dabei
|
||||
sichtbar zu machen, welchen Ausschnitt es misst, statt ihn zu verstecken.
|
||||
|
||||
`stop_when(j, profit_atr)` — optionaler Rueckruf fuer einen ZUSAETZLICHEN Exit
|
||||
(z. B. Flip-Close: „Signal dreht"). Gibt er True zurueck, wird zum Close von Bar
|
||||
`j` geschlossen. Ohne ihn ist das Ergebnis bitgenau wie zuvor. Damit laesst sich
|
||||
eine Exit-Idee messen, OHNE die Phasen-Mechanik zu kopieren — genau diese
|
||||
Kopiererei hat im Projekt VIER divergierende Exit-Modelle erzeugt.
|
||||
`ret_bar=True` liefert `(R, exit_bar)` statt nur `R` (fuer sequentielle Sims,
|
||||
die nach dem Exit weiterlaufen muessen).
|
||||
"""
|
||||
if atr <= 0:
|
||||
return 0.0
|
||||
@@ -113,21 +123,29 @@ def simulate(entry: float, d: int, atr: float,
|
||||
hw = entry
|
||||
rank = 0
|
||||
end = min(j0 + p.max_hold, len(C) - 1)
|
||||
def _out(px, bar):
|
||||
r = (px - entry) * d / atr
|
||||
return (r, bar) if ret_bar else r
|
||||
|
||||
for j in range(j0, end + 1):
|
||||
hi, lo = H[j], L[j]
|
||||
if (lo <= sl) if d > 0 else (hi >= sl):
|
||||
return (sl - entry) * d / atr
|
||||
return _out(sl, j)
|
||||
if tp is not None and ((hi >= tp) if d > 0 else (lo <= tp)):
|
||||
return (tp - entry) * d / atr
|
||||
return _out(tp, j)
|
||||
hw = max(hw, hi) if d > 0 else min(hw, lo)
|
||||
profit = (hw - entry) * d
|
||||
# Zusaetzlicher Exit (Flip-Close o. AE.) — NACH SL/TP, denn die haben im
|
||||
# selben Bar Vorrang (pessimistisch), und VOR der Trail-Nachfuehrung.
|
||||
if stop_when is not None and stop_when(j, (C[j] - entry) * d / atr):
|
||||
return _out(C[j], j)
|
||||
ph = 0 if profit < p.trail_start * atr else (
|
||||
1 if profit < p.lock_start * atr else 2)
|
||||
if ph < rank:
|
||||
ph = rank
|
||||
rank = ph
|
||||
if timestop and rank == 0 and (j - j0) >= p.timestop_bars:
|
||||
return (C[j] - entry) * d / atr
|
||||
return _out(C[j], j)
|
||||
if ph == 0:
|
||||
continue
|
||||
m = p.mult if ph == 1 else max(p.lock_min, p.mult * p.lock_scale)
|
||||
@@ -135,4 +153,4 @@ def simulate(entry: float, d: int, atr: float,
|
||||
if profit >= p.be * atr or ph == 2:
|
||||
cand = max(cand, entry) if d > 0 else min(cand, entry)
|
||||
sl = max(sl, cand) if d > 0 else min(sl, cand)
|
||||
return (C[end] - entry) * d / atr
|
||||
return _out(C[end], end)
|
||||
|
||||
Reference in New Issue
Block a user