M15-Karte: Gate 3b zeigt Motor-Block + RAUM je Richtung
Anlass Trade #49671601 (-87,03 EUR). Die Praemisse "SL trotz LONG- Empfehlung" stimmte nicht: 138 Zeilen zwischen Ein- und Ausstieg, 100 % WARTEN (entry_room 76 %, breakout_pending 19 %, min_conf 5 %). Gesehen wurde "verboten: SHORT" - ein Veto. Dritter Fall derselben Veto->Richtung-Umwandlung in dieser Karte (Code 08.08., Geometrie 10.08. frueh, jetzt die Lesart). Der eigentliche Befund: entry_room und Gate 3 lesen DIESELBE Tatsache ("ein Level ist nah") mit umgekehrtem Vorzeichen. Beide gemessen, beide richtig - ueber verschiedene Trades (Markt-Einstieg mit dem Level als Hindernis vs. ruhende Order AM Level). Die Karte zeigte nur die gruene Haelfte; die Block-Grund-Zeile war mit der Gesamtempfehlung verschwunden. Gebaut: Zeile 3b (#m15-motor) mit block_reason im Klartext + Raum je Richtung. Reine Anzeige. Das Status-Banner bleibt bewusst unveraendert - es auf gelb zu ziehen waere eine Strategie-Aenderung, und die einzige Live-Zahl dafuer (-9,91 vs +3,24 EUR/Lot, n=76) hat ein KI, das die Null enthaelt. - wave_rec.room_info(): EINE Implementierung fuer Gate und Anzeige, _room_gate rechnet jetzt darueber (kein Nachbau) - tests/test_room_info.py: Bitgleichheit ueber 2.000 Zufallslagen x 3 Signale, Gate feuerte >300x; Grenzfall-Test fuer die Rundung - v=160 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
5474c2be61
commit
47c596ed70
+35
-2
@@ -3879,7 +3879,7 @@ class TradingEngine:
|
||||
# NICHT, `ruff F821` schon (4 Befunde, darunter `_m15` selbst) — genau
|
||||
# die Fehlerklasse, fuer die der Linter am 07.08. eingebaut wurde.
|
||||
_m15 = self._m15_setup(market, wave_snap, _cone,
|
||||
self._alignment_cached())
|
||||
self._alignment_cached(), wave_signal)
|
||||
self._log_m15_state(_m15, market)
|
||||
self._log_cone_check(_cone)
|
||||
# Für den KI-Copiloten festhalten (2026-08-02) — er bekam bis dahin nur
|
||||
@@ -4281,7 +4281,8 @@ class TradingEngine:
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
def _m15_setup(self, market, wave_snap, cone, alignment) -> dict | None:
|
||||
def _m15_setup(self, market, wave_snap, cone, alignment,
|
||||
wave_signal=None) -> dict | None:
|
||||
"""SETUP-BEREITSCHAFT auf M15 — der Ersatz für die entfernte Gesamtempfehlung.
|
||||
|
||||
⚠⚠ BEWUSST KEINE RICHTUNGS-EMPFEHLUNG. Gemessen (2026-08-08) trägt auf
|
||||
@@ -4360,6 +4361,38 @@ class TradingEngine:
|
||||
"p_break": round(100 * p)})
|
||||
out["zonen"] = zonen
|
||||
|
||||
# ── 3b) MOTOR-ZUSTAND + RAUM ─────────────────────────────────
|
||||
# ⚠⚠ ANLASS 2026-08-10 (Trade #49671601, −87 €): die Karte zeigte
|
||||
# GRÜN, während der Signal-Motor zu 76 % mit `entry_room` blockte.
|
||||
# Das ist kein Widerspruch im Motor, sondern eine LÜCKE in der
|
||||
# Karte: dieselbe geometrische Tatsache — „ein Level ist sehr nah"
|
||||
# — wird von beiden Seiten mit UMGEKEHRTEM Vorzeichen gelesen:
|
||||
# · Gate ③ (Karte): Level in Reichweite → man kann eine ruhende
|
||||
# Order dort platzieren → GUT
|
||||
# · entry_room (Motor): das Ziel-Level ist zu nah → Ertrag
|
||||
# gedeckelt, Kosten fressen ihn → SCHLECHT
|
||||
# (gemessen ØR −0,119/−0,067 · PF 0,42/0,66)
|
||||
# Beide sind gemessen und beide haben recht — über VERSCHIEDENE
|
||||
# Trades (Markt-Einstieg mit dem Level als Hindernis gegen ruhende
|
||||
# Order AM Level). Die Karte verschwieg nur die zweite Hälfte.
|
||||
# ⚠ REINE ANZEIGE. Es wird nichts geblockt und keine Richtung
|
||||
# ausgegeben — der Raum kommt für BEIDE Richtungen, weil der Mensch
|
||||
# in jede handeln kann. Hartes Blockieren ist im Projekt verworfen
|
||||
# (Übersteuerungen des Users liefen gemessen 68 % WR).
|
||||
# ⚠ `room_info` ist DIESELBE Funktion, die das Gate entscheidet —
|
||||
# kein Nachbau (die Divergenz-Falle, dreimal dokumentiert).
|
||||
wsig = wave_signal or {}
|
||||
try:
|
||||
raum = self.wave.room_info(bid)
|
||||
except Exception:
|
||||
raum = None
|
||||
out["motor"] = {
|
||||
"signal": wsig.get("signal"),
|
||||
"block": wsig.get("block"),
|
||||
"grund": (wsig.get("reasons") or [None])[0],
|
||||
"conf_pct": wsig.get("conf_pct"),
|
||||
"raum": raum}
|
||||
|
||||
# ── 4) REICHWEITE — der Kegel (kalibriert: 80 %-Band trifft 77 %)
|
||||
lv = (cone or {}).get("levels") or []
|
||||
b80 = [x for x in lv if x.get("band") == 80]
|
||||
|
||||
+50
-15
@@ -612,32 +612,67 @@ class WaveRecommender:
|
||||
"regime": None, "rsi": None, "block": "breakout_pending",
|
||||
"reasons": [f"warte auf Breakout (+{k:.1f}×ATR {sig}, noch {need:.2f})"]}
|
||||
|
||||
def room_info(self, cur):
|
||||
"""Abstand zum Gegenlevel JE RICHTUNG — die Zahl hinter dem entry_room-Gate.
|
||||
|
||||
⚠⚠ Warum das hier steht und nicht anderswo nachgebaut wird: die Zahl lag
|
||||
bisher NUR als Fließtext in `reasons` und war damit nicht auswertbar. Eine
|
||||
zweite Berechnung in Engine oder Frontend wäre genau die Divergenz, die im
|
||||
Projekt schon dreimal zugeschlagen hat (`sim_run`/`_ema_series` als Kopie in
|
||||
≥6 Skripten · der nachgebaute Level-Cluster · das fünfte Exit-Modell).
|
||||
**Eine Implementierung, zwei Nutzer:** `_room_gate` (Entscheidung) und
|
||||
`engine._m15_setup` (Anzeige).
|
||||
|
||||
⚠ BEIDE Richtungen, weil die Karte bewusst keine Richtung vorgibt — der
|
||||
Mensch kann in jede handeln und braucht den Raum für die, die er wählt.
|
||||
⚠ Quelle ist trainingsgleich **M5** (`_pb_levels`/`_pb_feats`), NICHT die
|
||||
M30-Level der Karte: `entry_room_atr=0.6` ist auf M5 kalibriert. Die drei
|
||||
divergierenden Level-Quellen sind Absicht, aber eine Stolperfalle.
|
||||
"""
|
||||
lv = getattr(self, "_pb_levels", None) or {}
|
||||
pf = getattr(self, "_pb_feats", None) or {}
|
||||
atr5 = pf.get("atr")
|
||||
x = self._entry_room_atr
|
||||
out = {"gate": x, "atr_m5": atr5, "long": None, "short": None}
|
||||
if not atr5 or not cur:
|
||||
return out
|
||||
for name, d in (("long", 1), ("short", -1)):
|
||||
if d > 0:
|
||||
cands = [p for p in (lv.get("ph") or []) if p > cur]
|
||||
lvl = min(cands) if cands else None
|
||||
else:
|
||||
cands = [p for p in (lv.get("pl") or []) if p < cur]
|
||||
lvl = max(cands) if cands else None
|
||||
if lvl is None: # freie Bahn (bestes gemessenes Segment)
|
||||
out[name] = {"level": None, "dist_atr": None, "frei": True}
|
||||
continue
|
||||
dist = (lvl - cur) * d / atr5
|
||||
# ⚠ `frei` MUSS auf dem UNGERUNDETEN Wert entschieden werden — sonst
|
||||
# kippt ein Grenzfall (0,5951 → gerundet 0,60 → fälschlich frei)
|
||||
# und das Gate verhielte sich anders als vor dem Umbau.
|
||||
out[name] = {"level": round(lvl, 3), "dist_atr": round(dist, 2),
|
||||
"frei": bool(dist >= x)}
|
||||
return out
|
||||
|
||||
def _room_gate(self, rec, cur):
|
||||
"""Entry-Raum-Gate: Signal → WARTEN, wenn das Gegenlevel < X×ATR_M5 entfernt
|
||||
ist (gemessen `backtest_entryroom.py`: Raum <0,6×ATR in BEIDEN Hälften klar
|
||||
negativ — 79–82 % WR, aber PF<1 = Klein-Close-Falle). Level/ATR trainings-
|
||||
gleich aus `_pb_levels`/`_pb_feats` (M5). Fail-open: ohne Daten kein Gate."""
|
||||
gleich aus `_pb_levels`/`_pb_feats` (M5). Fail-open: ohne Daten kein Gate.
|
||||
⚠ Rechnet seit 2026-08-10 über `room_info` — Verhalten unverändert
|
||||
(bitgenau geprüft in `tests/test_room_info.py`)."""
|
||||
x = self._entry_room_atr
|
||||
sig = rec.get("signal")
|
||||
if x <= 0 or sig == "WARTEN":
|
||||
return rec
|
||||
lv = getattr(self, "_pb_levels", None) or {}
|
||||
pf = getattr(self, "_pb_feats", None) or {}
|
||||
atr5 = pf.get("atr")
|
||||
if not atr5:
|
||||
info = self.room_info(cur)
|
||||
if not info.get("atr_m5"):
|
||||
return rec
|
||||
d = 1 if sig == "LONG" else -1
|
||||
if d > 0:
|
||||
cands = [p for p in (lv.get("ph") or []) if p > cur]
|
||||
lvl = min(cands) if cands else None
|
||||
else:
|
||||
cands = [p for p in (lv.get("pl") or []) if p < cur]
|
||||
lvl = max(cands) if cands else None
|
||||
if lvl is None: # freie Bahn (bestes gemessenes Segment)
|
||||
return rec
|
||||
dist = (lvl - cur) * d / atr5
|
||||
if dist >= x:
|
||||
seite = info["long"] if d > 0 else info["short"]
|
||||
if seite is None or seite["level"] is None or seite["frei"]:
|
||||
return rec
|
||||
lvl, dist = seite["level"], seite["dist_atr"]
|
||||
return {"signal": "WARTEN", "conf_pct": 0, "score": 0.0, "setup": "WAVE",
|
||||
"regime": None, "rsi": None, "block": "entry_room",
|
||||
"reasons": [f"kein Raum: {'Widerstand' if d > 0 else 'Support'} "
|
||||
|
||||
Reference in New Issue
Block a user