Deployment-Drift: Stufe 1 (Trail-mult) + Stufe 2 (Entscheidungs-Telemetrie)
Behebt die strukturelle Schwachstelle, die heute dreimal zugeschlagen hat:
Komponenten werden unter anderen Bedingungen BETRIEBEN als VALIDIERT.
Track B schuetzt gegen Overfitting, nicht gegen Deployment-Drift.
STUFE 1 (core/trailing.py): _MULT_BY_TF durchgehend 1,5 (vorher M15 2,0 /
M30 2,5 / H1 3,0) und _MULT_BREAKOUT_ADD 0,5 -> 0,0. Die Staffelung war nie
gemessen; backtest_trailing.py validiert nur 1,5 und die breiteren Werte
sind dort messbar schlechter (80k Bars, 2 Halbjahre):
mult 1,5 H1 SigmaR -249 · H2 +1252 · Worst -1,50
mult 2,0 H1 SigmaR -412 · H2 +1187 · Worst -2,00
mult 2,5 H1 SigmaR -1367 · H2 +1450 · Worst -2,50
Auf M30 fuhr der Bot also 5,5x schlechteres H1 und einen 67 % groesseren
Einzelverlust; bei Breakouts waren es 3,0 - nie getestet. Relevant, weil die
TF-Heuristik oft auf M15/M30 landet (31.07.: 19 von 33 Phasen).
STUFE 2a (wave_rec/history/engine): jede Empfehlung loggt einen
maschinenlesbaren block_reason - deadband, dead_hour, eia, htf_counter,
stretch, min_conf, breakout_pending, entry_room, no_data, stale. DB migriert
(Backup angelegt). Ohne den war nicht feststellbar, WELCHES der Gates die
93 % WARTEN erzeugt (Backtest-Erwartung ~43 %).
STUFE 2b (analyze_divergence.py, NEU): haelt vier Dinge gegen die
Backtest-Erwartung - A Signal-Verteilung, B Gate-Anteile inkl.
"im Backtest modelliert?", C TF-Wechsel-Rate, D0 Modell-Kalibrierung,
D Merkmals-Drift gegen _PB_MU/_SD.
LEHRE AUS DEM BAU SELBST: die Merkmals-Drift (D) haette den realen Fehler
NICHT gefangen - mom3 lag nur 0,50 Sigma unter mu, bei Gewicht 1,43 wurden
daraus aber -0,71 im Logit (23 % statt 41 % vorhergesagt). Deshalb ist D0
der schaerfere Test und die D-Schwelle auf 0,5 Sigma gesetzt.
Erster Lauf nach dem Fix bestaetigt das Nachtraining live:
D0 = 27,0 % vorhergesagt vs 28,0 % real (vorher 23 vs 41).
Stufe 3 (geteilter Exit-Kern) bleibt offen, spaeter und einzeln.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
0208101707
commit
97617cd81d
+5
-1
@@ -1366,7 +1366,11 @@ class TradingEngine:
|
||||
reversal=("rev" if "REV" in (rec.get("setup") or "") else None),
|
||||
ai_sentiment=adv.get("bias"), ai_confidence=adv.get("confidence"),
|
||||
setup=rec.get("setup"), regime=None, rsi=s.get("rsi_m15"),
|
||||
news_score=_news)
|
||||
news_score=_news,
|
||||
# Stufe-2-Telemetrie (2026-07-31): WELCHES Gate blockt gerade?
|
||||
# Ohne das war live nicht feststellbar, warum der Bot zu 93 %
|
||||
# WARTEN sagt (Backtest-Erwartung ~43 %).
|
||||
block=rec.get("block"))
|
||||
except Exception as e:
|
||||
log.warning(f"log_recommendation: {e}")
|
||||
|
||||
|
||||
+12
-5
@@ -85,7 +85,13 @@ SCHEMA = [
|
||||
setup TEXT,
|
||||
regime TEXT,
|
||||
rsi REAL,
|
||||
news_score REAL
|
||||
news_score REAL,
|
||||
-- Maschinenlesbarer Grund für WARTEN (Stufe-2-Telemetrie 2026-07-31):
|
||||
-- deadband · dead_hour · eia · htf_counter · stretch · min_conf ·
|
||||
-- breakout_pending · entry_room · no_data · stale. NULL = kein Block.
|
||||
-- Ohne den war live nicht feststellbar, WELCHES Gate die 93 % WARTEN
|
||||
-- erzeugt (Backtest-Erwartung ~43 %).
|
||||
block_reason TEXT
|
||||
)
|
||||
""",
|
||||
"""
|
||||
@@ -340,7 +346,8 @@ class HistoryLogger:
|
||||
setup: str | None = None,
|
||||
regime: str | None = None,
|
||||
rsi: float | None = None,
|
||||
news_score: float | None = None):
|
||||
news_score: float | None = None,
|
||||
block: str | None = None):
|
||||
"""
|
||||
Empfehlungs-Logging mit Throttling: nur jede N Sekunden, um die DB
|
||||
nicht mit identischen Snapshots zu fluten.
|
||||
@@ -355,13 +362,13 @@ class HistoryLogger:
|
||||
(timestamp, signal, score, conf_pct,
|
||||
angle_m5, angle_m15, angle_m30, angle_h1,
|
||||
reversal, ai_sentiment, ai_confidence,
|
||||
setup, regime, rsi, news_score)
|
||||
VALUES (?,?,?,?, ?,?,?,?, ?,?,?, ?,?,?,?)
|
||||
setup, regime, rsi, news_score, block_reason)
|
||||
VALUES (?,?,?,?, ?,?,?,?, ?,?,?, ?,?,?,?,?)
|
||||
""", (now, signal, score, conf_pct,
|
||||
angles.get("M5"), angles.get("M15"),
|
||||
angles.get("M30"), angles.get("H1"),
|
||||
reversal, ai_sentiment, ai_confidence,
|
||||
setup, regime, rsi, news_score))
|
||||
setup, regime, rsi, news_score, block))
|
||||
conn.commit()
|
||||
|
||||
def log_signal(self, *, signal_type: str, direction: str,
|
||||
|
||||
+21
-6
@@ -107,16 +107,31 @@ _TF_LABELS = {
|
||||
mt5.TIMEFRAME_H1: "H1",
|
||||
}
|
||||
|
||||
# Trail-Multiplikator je Timeframe: niedrige TF (Scalp) → enger Stop,
|
||||
# hohe TF (Trend) → mehr Puffer. Ersetzt die alte 2.5–4.5-Logik.
|
||||
# Trail-Multiplikator je Timeframe.
|
||||
# ⚠⚠ ANGEGLICHEN AUF DEN GEMESSENEN WERT 1,5 (2026-07-31, Stufe 1 der
|
||||
# Deployment-Drift-Behebung). VORHER: M15 2,0 · M30 2,5 · H1 3,0 (+0,5 bei
|
||||
# Breakouts) — diese Staffelung war **nie gemessen**. `backtest_trailing.py`
|
||||
# validiert ausschließlich 1,5, und die breiteren Werte sind dort MESSBAR
|
||||
# SCHLECHTER (80k Bars, 2 Halbjahre, Signal Trend+Reversal):
|
||||
# mult 1,5 (start 0,3): H1 ΣR −249 · H2 +1252 · Worst −1,50
|
||||
# mult 2,0 (start 0,3): H1 ΣR −412 · H2 +1187 · Worst −2,00
|
||||
# mult 2,5 (start 0,3): H1 ΣR −1367 · H2 +1450 · Worst −2,50
|
||||
# Auf M30 fuhr der Bot also 5,5× schlechteres H1 und einen 67 % größeren
|
||||
# Einzelverlust; mit `_MULT_BREAKOUT_ADD` waren es bei Breakouts sogar 3,0 —
|
||||
# ein Wert, den nie jemand getestet hat. Real relevant, weil die TF-Heuristik
|
||||
# häufig auf M15/M30 landet (31.07.: 19 von 33 Phasen).
|
||||
# ⚠ Falls die Staffelung je zurück soll: erst `backtest_trailing.py` auf
|
||||
# M15-/M30-BASIS-Signalen laufen lassen — bisher misst er nur M5.
|
||||
_MULT_BY_TF = {
|
||||
mt5.TIMEFRAME_M1: 1.5,
|
||||
mt5.TIMEFRAME_M5: 1.5,
|
||||
mt5.TIMEFRAME_M15: 2.0,
|
||||
mt5.TIMEFRAME_M30: 2.5,
|
||||
mt5.TIMEFRAME_H1: 3.0,
|
||||
mt5.TIMEFRAME_M15: 1.5,
|
||||
mt5.TIMEFRAME_M30: 1.5,
|
||||
mt5.TIMEFRAME_H1: 1.5,
|
||||
}
|
||||
_MULT_BREAKOUT_ADD = 0.5 # Breakouts brauchen etwas mehr Luft (gedeckelt)
|
||||
# 0,0 statt 0,5: der Aufschlag hob Breakout-Trades auf mind. 2,0 — gemessen
|
||||
# schlechter als 1,5 (s. o.) und für Breakouts nie separat validiert.
|
||||
_MULT_BREAKOUT_ADD = 0.0
|
||||
|
||||
_PHASE_RANK = {"Init": 0, "Trail": 1, "Lock": 2}
|
||||
|
||||
|
||||
+16
-5
@@ -564,7 +564,7 @@ class WaveRecommender:
|
||||
snap["breakout"] = {"pending": True, "dir": sig,
|
||||
"level": round(p["level"], 3), "need": round(need, 3)}
|
||||
return {"signal": "WARTEN", "conf_pct": 0, "score": 0.0, "setup": "WAVE",
|
||||
"regime": None, "rsi": None,
|
||||
"regime": None, "rsi": None, "block": "breakout_pending",
|
||||
"reasons": [f"warte auf Breakout (+{k:.1f}×ATR {sig}, noch {need:.2f})"]}
|
||||
|
||||
def _room_gate(self, rec, cur):
|
||||
@@ -594,7 +594,7 @@ class WaveRecommender:
|
||||
if dist >= x:
|
||||
return rec
|
||||
return {"signal": "WARTEN", "conf_pct": 0, "score": 0.0, "setup": "WAVE",
|
||||
"regime": None, "rsi": None,
|
||||
"regime": None, "rsi": None, "block": "entry_room",
|
||||
"reasons": [f"kein Raum: {'Widerstand' if d > 0 else 'Support'} "
|
||||
f"{lvl:.2f} nur {dist:.2f}×ATR entfernt (Gate {x:.1f}) — "
|
||||
f"Ertrag gedeckelt, Kosten fressen den Edge"]}
|
||||
@@ -673,11 +673,16 @@ class WaveRecommender:
|
||||
"dir": "LONG" if stretch < 0 else "SHORT"}
|
||||
else:
|
||||
snap["bounce"] = {"state": None, "dir": None}
|
||||
wait = {"signal": "WARTEN", "conf_pct": 0, "score": 0.0,
|
||||
# `block` = maschinenlesbarer Grund, WARUM WARTEN (Stufe-2-Telemetrie
|
||||
# 2026-07-31). Die `reasons` sind für Menschen, `block` fuer die Statistik:
|
||||
# ohne ihn war live nicht feststellbar, welches der Gates die 93 % WARTEN
|
||||
# erzeugt (Backtest-Erwartung ~43 %).
|
||||
wait = {"signal": "WARTEN", "conf_pct": 0, "score": 0.0, "block": None,
|
||||
"setup": "WAVE", "regime": None, "rsi": None, "reasons": []}
|
||||
|
||||
# Totband: kein klarer Trend → kein Trade (Chop)
|
||||
if abs(sep) < _TREND_DEADBAND:
|
||||
wait["block"] = "deadband"
|
||||
wait["reasons"] = [f"kein klarer Trend ({tf_lbl}, EMA-Abstand {sep:+.2f}×ATR)"]
|
||||
return wait, snap
|
||||
|
||||
@@ -685,6 +690,7 @@ class WaveRecommender:
|
||||
# Kostenfalle + 12/16 Uhr. Leer = AUS, User-Vorgabe 2026-07-22 trotz Messung).
|
||||
if hour is not None and hour in self._dead_hours:
|
||||
why = "Nacht-Spread frisst den Edge" if hour <= 7 else "gemessen negativer Edge"
|
||||
wait["block"] = "dead_hour"
|
||||
wait["reasons"] = [f"Zeit-Gate {hour}:00 Uhr — {why}, kein Trade"]
|
||||
return wait, snap
|
||||
|
||||
@@ -695,6 +701,7 @@ class WaveRecommender:
|
||||
if hour is not None:
|
||||
_now_b = datetime.now(_BERLIN)
|
||||
if _now_b.weekday() == 2 and (15, 30) <= (_now_b.hour, _now_b.minute) < (16, 30):
|
||||
wait["block"] = "eia"
|
||||
wait["reasons"] = ["EIA-Blackout Mi 15:30–16:30 — Lagerdaten-Vorlauf, "
|
||||
"gemessen negativ, kein Trade"]
|
||||
return wait, snap
|
||||
@@ -721,15 +728,18 @@ class WaveRecommender:
|
||||
# Higher-TF-Gegen-Trend-Filter: kein Short im M30-Aufwärtstrend (und
|
||||
# umgekehrt). Per Backtest belegt (Ø-Edge ×2). Nur reguläre Trendsignale.
|
||||
if htf_trend != 0 and htf_trend != d_sig:
|
||||
wait["block"] = "htf_counter"
|
||||
wait["reasons"] = [
|
||||
f"gegen {_HTF_LABEL}-Trend "
|
||||
f"({'auf' if htf_trend > 0 else 'ab'}) — kein Gegen-Trade"]
|
||||
return wait, snap
|
||||
# Anti-Überdehnung: nicht weit weg von der EMA hinterherkaufen/-shorten
|
||||
if sig == "LONG" and stretch > _STRETCH_MAX:
|
||||
wait["block"] = "stretch"
|
||||
wait["reasons"] = [f"überdehnt: {stretch:+.1f}×ATR über EMA — kein Spät-Long"]
|
||||
return wait, snap
|
||||
if sig == "SHORT" and stretch < -_STRETCH_MAX:
|
||||
wait["block"] = "stretch"
|
||||
wait["reasons"] = [f"überdehnt: {stretch:+.1f}×ATR unter EMA — kein Spät-Short"]
|
||||
return wait, snap
|
||||
|
||||
@@ -830,6 +840,7 @@ class WaveRecommender:
|
||||
# Mindest-Konfidenz-Gate: schwache Setups (zu viele Strafen gestapelt)
|
||||
# tragen negativen Edge (gemessen Band 40–54 %) → kein Trade.
|
||||
if conf < _MIN_CONF:
|
||||
wait["block"] = "min_conf"
|
||||
wait["reasons"] = [f"Konfidenz {conf}% < {_MIN_CONF}% — Setup zu schwach"] + reasons[:2]
|
||||
return wait, snap
|
||||
|
||||
@@ -848,11 +859,11 @@ class WaveRecommender:
|
||||
rec, ts, err = self._rec, self._ts, self._error
|
||||
if rec is None:
|
||||
return {"signal": "WARTEN", "conf_pct": 0, "score": 0.0,
|
||||
"setup": "WAVE", "regime": None, "rsi": None,
|
||||
"setup": "WAVE", "regime": None, "rsi": None, "block": "no_data",
|
||||
"reasons": [err or "keine Wellen-Daten"]}
|
||||
if not ts or time.time() - ts > _STALE_S:
|
||||
return {"signal": "WARTEN", "conf_pct": 0, "score": 0.0,
|
||||
"setup": "WAVE", "regime": None, "rsi": None,
|
||||
"setup": "WAVE", "regime": None, "rsi": None, "block": "stale",
|
||||
"reasons": ["Wellen-Daten veraltet"]}
|
||||
return dict(rec)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user