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
@@ -5220,6 +5220,78 @@ nicht führbar — und Schritt 4 ist der EINZIGE belastbare Nachweis, dass neuer
|
|||||||
Python-Code läuft. Jetzt mit **Punktpfaden**; ein Feld mit Wert `None` zählt als
|
Python-Code läuft. Jetzt mit **Punktpfaden**; ein Feld mit Wert `None` zählt als
|
||||||
vorhanden (geprüft wird der Schlüssel, nicht der Inhalt).
|
vorhanden (geprüft wird der Schlüssel, nicht der Inhalt).
|
||||||
|
|
||||||
|
### ⚠⚠ GATE ③b — DIE KARTE PRÜFTE „LEVEL NAH", NIE „RAUM DAHINTER" (2026-08-10)
|
||||||
|
|
||||||
|
**Anlass: Trade #49671601, −87,03 €** (User: „SL geschlossen trotz LONG-Empfehlung").
|
||||||
|
⚠ **Die Prämisse stimmte nicht — es gab keine LONG-Empfehlung.** 138 Zeilen
|
||||||
|
zwischen Ein- und Ausstieg, **100 % WARTEN**: `entry_room` 76 % · `breakout_pending`
|
||||||
|
19 % · `min_conf` 5 %, **kein einziges durchgelassenes Richtungssignal**. Gesehen
|
||||||
|
wurde `verboten: SHORT` — ein **Veto**.
|
||||||
|
⚠⚠ Damit ist es der **dritte** Fall derselben Umwandlung in dieser Karte: am 08.08.
|
||||||
|
machte der **Code** aus dem Verbot eine Richtung (13 LONG-Episoden an einem
|
||||||
|
fallenden Tag), am 10.08. früh die **Geometrie** (Gate ④), jetzt die **Lesart**.
|
||||||
|
Wenn eine Anzeige dreimal zur selben Fehldeutung einlädt, liegt es an der Anzeige.
|
||||||
|
|
||||||
|
⚠⚠ **DER EIGENTLICHE BEFUND: zwei Bauteile lesen DIESELBE Tatsache mit
|
||||||
|
umgekehrtem Vorzeichen.**
|
||||||
|
| Bauteil | bei „Level sehr nah" | Beleg |
|
||||||
|
|---|---|---|
|
||||||
|
| **`entry_room`** (Signal-Motor) | **WARTEN** — Ertrag am Level gedeckelt | ØR −0,119/−0,067 · **PF 0,42/0,66** |
|
||||||
|
| **Gate ③** (M15-Karte) | **GRÜN** — Level in Reichweite | Ausführungs-Edge der ruhenden Order |
|
||||||
|
✅ **Beide sind gemessen und beide haben recht — über VERSCHIEDENE Trades:**
|
||||||
|
`entry_room` misst den **Markt**-Einstieg mit dem Level als *Hindernis voraus*,
|
||||||
|
die Karte die **ruhende Order AM** Level. Der Unterschied ist real.
|
||||||
|
**Die Karte zeigte nur die grüne Hälfte** — die Block-Grund-Zeile war beim
|
||||||
|
Entfernen der Gesamtempfehlung mit verschwunden.
|
||||||
|
|
||||||
|
**Der Trade war beim Einstieg entschieden:** Widerstand **0,07×ATR = 0,008 $**
|
||||||
|
voraus, Stop **0,560 $ = 4,7×ATR_M5** zurück (CRV ~1:67), **MFE +0,016 $** — nie
|
||||||
|
im Plus. `ctx_atr_m5 = 0,12` = **der ATR-Floor**, der Markt stand still; der Stop
|
||||||
|
kam trotzdem aus ATR_M15 (bei manuellen Trades Absicht, M5 nur für Auto-Squeeze
|
||||||
|
seit 05.08.). −87 € = `0,568 $ × 1,77 Lots × ~87,6 €` — reine Geometrie, kein
|
||||||
|
Ausreißer. ⚠ Stunde 6 „vor DE-Open": autonom hätte der Bot dort nicht eröffnet.
|
||||||
|
|
||||||
|
**Ist es ein Muster? Gemessen (77 Trades mit `ctx_*` seit 04.08., je Lot):**
|
||||||
|
| Block beim Einstieg | n | WR | EUR/Lot |
|
||||||
|
|---|---|---|---|
|
||||||
|
| **`entry_room`** | **29** | 55 % | **−11,45** |
|
||||||
|
| `min_conf` | 16 | 44 % | −16,08 |
|
||||||
|
| `htf_counter` | 10 | 80 % | +22,83 |
|
||||||
|
| `breakout_pending` | 9 | 89 % | +17,68 |
|
||||||
|
|
||||||
|
Direkt auf der Raum-Achse (Abstand zum Level in der **eigenen** Trade-Richtung):
|
||||||
|
| | n | WR | EUR/Lot | 95-%-KI |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| Raum < 0,6 (geblockt) | 24 | **62 %** | −9,91 | [−29,4 … +6,6] ⚠ |
|
||||||
|
| Raum ≥ 0,6 (frei) | 52 | **62 %** | +3,24 | [−5,8 … +13,5] ⚠ |
|
||||||
|
⚠⚠ **Beide Intervalle enthalten die Null — mit n=76 ist das NICHT belegt**, nur
|
||||||
|
konsistent mit dem über 80k Bars gemessenen Gate. ✅ Die schärfste Zeile ist die
|
||||||
|
**identische Trefferquote 62 % gegen 62 %**: die Richtungswahl war gleich gut, der
|
||||||
|
Unterschied liegt **allein in der Asymmetrie** (Gewinn am Level gedeckelt, Verlust
|
||||||
|
läuft bis zum Stop) — die dokumentierte Kern-Leckage an neuer Stelle.
|
||||||
|
|
||||||
|
✅ **GEBAUT: Zeile ③b** (`#m15-motor`, v=160) — zeigt den aktuellen `block_reason`
|
||||||
|
im Klartext **plus den Raum je Richtung** („③b Motor blockt: gegen den M30-Trend ·
|
||||||
|
Raum ▲ 0,17×ATR ⚠ / ▼ 0,07×ATR ⚠ (Gate 0,6)"). Beide Richtungen, weil die Karte
|
||||||
|
bewusst keine vorgibt.
|
||||||
|
⚠⚠ **Das Status-Banner bleibt BEWUSST unverändert.** Es auf 🟡 zu ziehen wäre eine
|
||||||
|
Strategie-Änderung an einer gemessenen Gate-Kette — und die einzige Live-Zahl dafür
|
||||||
|
(−9,91 gegen +3,24 €/Lot) hat ein KI, das die Null enthält. **Sichtbar machen,
|
||||||
|
nicht bevormunden**; die Order-Buttons bleiben ohnehin bedienbar (Übersteuerungen
|
||||||
|
liefen gemessen 68 % WR).
|
||||||
|
✅ **`wave_rec.room_info(cur)` ist EINE Implementierung für Gate UND Anzeige** —
|
||||||
|
`_room_gate` rechnet jetzt darüber. Ein Nachbau wäre die Divergenz-Falle, die im
|
||||||
|
Projekt dreimal zugeschlagen hat (`sim_run`/`_ema_series` in ≥6 Skripten · der
|
||||||
|
nachgebaute Level-Cluster · das fünfte Exit-Modell).
|
||||||
|
⚠ Quelle ist trainingsgleich **M5**, nicht die M30-Level der Karte — `entry_room_atr
|
||||||
|
=0,6` ist auf M5 kalibriert (die drei divergierenden Level-Quellen sind Absicht).
|
||||||
|
✅ **Bitgleichheit bewiesen** (`tests/test_room_info.py`, 6 Tests): die alte Fassung
|
||||||
|
liegt wörtlich eingefroren im Test und läuft über **2.000 Zufallslagen × 3 Signale**
|
||||||
|
gegen die neue — identisch, und das Gate feuerte dabei >300× (ein Test, in dem es
|
||||||
|
nie feuert, belegt nichts). ⚠ Der gefährlichste Fall hat einen eigenen Test:
|
||||||
|
`frei` MUSS auf dem **ungerundeten** Abstand entschieden werden — bei 0,5951 würde
|
||||||
|
die gerundete 0,60 den Fall fälschlich durchlassen.
|
||||||
|
|
||||||
## ⚠⚠ EXIT-ABSTAND AUS M15-ATR = FÄLLT DURCH (2026-08-08)
|
## ⚠⚠ EXIT-ABSTAND AUS M15-ATR = FÄLLT DURCH (2026-08-08)
|
||||||
|
|
||||||
Zweiter Teil der M15-Idee: M5-Einstieg behalten, nur den EXIT gröber führen.
|
Zweiter Teil der M15-Idee: M5-Einstieg behalten, nur den EXIT gröber führen.
|
||||||
|
|||||||
+35
-2
@@ -3879,7 +3879,7 @@ class TradingEngine:
|
|||||||
# NICHT, `ruff F821` schon (4 Befunde, darunter `_m15` selbst) — genau
|
# NICHT, `ruff F821` schon (4 Befunde, darunter `_m15` selbst) — genau
|
||||||
# die Fehlerklasse, fuer die der Linter am 07.08. eingebaut wurde.
|
# die Fehlerklasse, fuer die der Linter am 07.08. eingebaut wurde.
|
||||||
_m15 = self._m15_setup(market, wave_snap, _cone,
|
_m15 = self._m15_setup(market, wave_snap, _cone,
|
||||||
self._alignment_cached())
|
self._alignment_cached(), wave_signal)
|
||||||
self._log_m15_state(_m15, market)
|
self._log_m15_state(_m15, market)
|
||||||
self._log_cone_check(_cone)
|
self._log_cone_check(_cone)
|
||||||
# Für den KI-Copiloten festhalten (2026-08-02) — er bekam bis dahin nur
|
# Für den KI-Copiloten festhalten (2026-08-02) — er bekam bis dahin nur
|
||||||
@@ -4281,7 +4281,8 @@ class TradingEngine:
|
|||||||
except Exception:
|
except Exception:
|
||||||
return None
|
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.
|
"""SETUP-BEREITSCHAFT auf M15 — der Ersatz für die entfernte Gesamtempfehlung.
|
||||||
|
|
||||||
⚠⚠ BEWUSST KEINE RICHTUNGS-EMPFEHLUNG. Gemessen (2026-08-08) trägt auf
|
⚠⚠ BEWUSST KEINE RICHTUNGS-EMPFEHLUNG. Gemessen (2026-08-08) trägt auf
|
||||||
@@ -4360,6 +4361,38 @@ class TradingEngine:
|
|||||||
"p_break": round(100 * p)})
|
"p_break": round(100 * p)})
|
||||||
out["zonen"] = zonen
|
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 %)
|
# ── 4) REICHWEITE — der Kegel (kalibriert: 80 %-Band trifft 77 %)
|
||||||
lv = (cone or {}).get("levels") or []
|
lv = (cone or {}).get("levels") or []
|
||||||
b80 = [x for x in lv if x.get("band") == 80]
|
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",
|
"regime": None, "rsi": None, "block": "breakout_pending",
|
||||||
"reasons": [f"warte auf Breakout (+{k:.1f}×ATR {sig}, noch {need:.2f})"]}
|
"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):
|
def _room_gate(self, rec, cur):
|
||||||
"""Entry-Raum-Gate: Signal → WARTEN, wenn das Gegenlevel < X×ATR_M5 entfernt
|
"""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
|
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-
|
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
|
x = self._entry_room_atr
|
||||||
sig = rec.get("signal")
|
sig = rec.get("signal")
|
||||||
if x <= 0 or sig == "WARTEN":
|
if x <= 0 or sig == "WARTEN":
|
||||||
return rec
|
return rec
|
||||||
lv = getattr(self, "_pb_levels", None) or {}
|
info = self.room_info(cur)
|
||||||
pf = getattr(self, "_pb_feats", None) or {}
|
if not info.get("atr_m5"):
|
||||||
atr5 = pf.get("atr")
|
|
||||||
if not atr5:
|
|
||||||
return rec
|
return rec
|
||||||
d = 1 if sig == "LONG" else -1
|
d = 1 if sig == "LONG" else -1
|
||||||
if d > 0:
|
seite = info["long"] if d > 0 else info["short"]
|
||||||
cands = [p for p in (lv.get("ph") or []) if p > cur]
|
if seite is None or seite["level"] is None or seite["frei"]:
|
||||||
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:
|
|
||||||
return rec
|
return rec
|
||||||
|
lvl, dist = seite["level"], seite["dist_atr"]
|
||||||
return {"signal": "WARTEN", "conf_pct": 0, "score": 0.0, "setup": "WAVE",
|
return {"signal": "WARTEN", "conf_pct": 0, "score": 0.0, "setup": "WAVE",
|
||||||
"regime": None, "rsi": None, "block": "entry_room",
|
"regime": None, "rsi": None, "block": "entry_room",
|
||||||
"reasons": [f"kein Raum: {'Widerstand' if d > 0 else 'Support'} "
|
"reasons": [f"kein Raum: {'Widerstand' if d > 0 else 'Support'} "
|
||||||
|
|||||||
@@ -0,0 +1,159 @@
|
|||||||
|
"""`room_info` darf das entry_room-Gate NICHT veraendert haben.
|
||||||
|
|
||||||
|
ANLASS (2026-08-10): der Raum zum Gegenlevel lag bisher nur als Fliesstext in
|
||||||
|
`reasons` und war nicht auswertbar. Fuer die Anzeige in der M15-Karte wurde er
|
||||||
|
herausgezogen — als EINE Implementierung fuer Gate und Anzeige, nicht als zweite
|
||||||
|
Kopie (die Divergenz-Falle, die im Projekt dreimal zugeschlagen hat).
|
||||||
|
|
||||||
|
⚠⚠ MIGRATIONS-REGEL: eine Refaktorierung ist nur dann eine, wenn das Verhalten
|
||||||
|
bitgenau gleich bleibt. Die ALTE Fassung liegt dafuer woertlich eingefroren hier
|
||||||
|
im Test (aus `git show`) und laeuft gegen die neue.
|
||||||
|
|
||||||
|
⚠ Der gefaehrlichste Fall ist die RUNDUNG: `dist_atr` wird auf 2 Stellen
|
||||||
|
gerundet ausgewiesen. Wuerde `frei` auf dem gerundeten Wert entschieden, kippte
|
||||||
|
ein Grenzfall (0,5951 → 0,60 → faelschlich frei). Genau dafuer gibt es
|
||||||
|
`test_grenzfall_rundung` — ohne den waere der Fehler unsichtbar, weil er nur
|
||||||
|
etwa jeden hundertsten Fall trifft.
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import random
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from core.wave_rec import WaveRecommender
|
||||||
|
|
||||||
|
|
||||||
|
class _Stub:
|
||||||
|
"""Minimal-Recommender: nur die drei Felder, die beide Methoden lesen."""
|
||||||
|
|
||||||
|
def __init__(self, x, ph, pl, atr):
|
||||||
|
self._entry_room_atr = x
|
||||||
|
self._pb_levels = {"ph": ph, "pl": pl}
|
||||||
|
self._pb_feats = {"atr": atr}
|
||||||
|
|
||||||
|
room_info = WaveRecommender.room_info
|
||||||
|
_room_gate = WaveRecommender._room_gate
|
||||||
|
|
||||||
|
|
||||||
|
# ── die ALTE Fassung, woertlich eingefroren (Stand vor dem Umbau) ─────────
|
||||||
|
def _alt_room_gate(s, rec, cur):
|
||||||
|
x = s._entry_room_atr
|
||||||
|
sig = rec.get("signal")
|
||||||
|
if x <= 0 or sig == "WARTEN":
|
||||||
|
return rec
|
||||||
|
lv = s._pb_levels or {}
|
||||||
|
pf = s._pb_feats or {}
|
||||||
|
atr5 = pf.get("atr")
|
||||||
|
if not atr5:
|
||||||
|
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:
|
||||||
|
return rec
|
||||||
|
dist = (lvl - cur) * d / atr5
|
||||||
|
if dist >= x:
|
||||||
|
return rec
|
||||||
|
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'} "
|
||||||
|
f"{lvl:.2f} nur {dist:.2f}×ATR entfernt (Gate {x:.1f}) — "
|
||||||
|
f"Ertrag gedeckelt, Kosten fressen den Edge"]}
|
||||||
|
|
||||||
|
|
||||||
|
def _rec(sig):
|
||||||
|
return {"signal": sig, "conf_pct": 70, "score": 1.0, "setup": "WAVE",
|
||||||
|
"regime": None, "rsi": None, "reasons": ["Test"]}
|
||||||
|
|
||||||
|
|
||||||
|
def test_bitgleich_ueber_zufallsfaelle():
|
||||||
|
"""2.000 Zufallslagen — alte und neue Fassung muessen IDENTISCH entscheiden.
|
||||||
|
|
||||||
|
⚠ Die Level werden eng um den Kurs gestreut, damit die Gate-Grenze wirklich
|
||||||
|
oft getroffen wird. Liegen alle Level weit weg, blockt nie etwas und der
|
||||||
|
Test belegt nichts (dieselbe Falle wie bei `test_squeeze_scan`, wo erst die
|
||||||
|
Vola-Wechsel die Bedingungen durchlaufen liessen)."""
|
||||||
|
r = random.Random(4711)
|
||||||
|
getroffen = 0
|
||||||
|
for _ in range(2000):
|
||||||
|
cur = 79.0 + r.uniform(-2, 2)
|
||||||
|
atr = r.choice([0.06, 0.12, 0.2, 0.35])
|
||||||
|
x = r.choice([0.0, 0.3, 0.6, 1.0])
|
||||||
|
ph = [round(cur + r.uniform(-1.0, 1.0), 3) for _ in range(r.randint(0, 5))]
|
||||||
|
pl = [round(cur + r.uniform(-1.0, 1.0), 3) for _ in range(r.randint(0, 5))]
|
||||||
|
s = _Stub(x, ph, pl, atr)
|
||||||
|
for sig in ("LONG", "SHORT", "WARTEN"):
|
||||||
|
neu = s._room_gate(_rec(sig), cur)
|
||||||
|
alt = _alt_room_gate(s, _rec(sig), cur)
|
||||||
|
assert neu == alt, f"Abweichung bei {sig} cur={cur} atr={atr} x={x}"
|
||||||
|
if neu.get("block") == "entry_room":
|
||||||
|
getroffen += 1
|
||||||
|
# Ein Test, in dem das Gate nie feuert, belegt nichts.
|
||||||
|
assert getroffen > 300, f"Gate feuerte nur {getroffen}x — Testdaten zu lasch"
|
||||||
|
|
||||||
|
|
||||||
|
def test_grenzfall_rundung():
|
||||||
|
"""dist = 0,5951 liegt UNTER dem Gate 0,6 — gerundet waere es 0,60.
|
||||||
|
|
||||||
|
Wuerde `frei` auf dem gerundeten Wert entschieden, liesse das Gate diesen
|
||||||
|
Fall faelschlich durch. Das ist der einzige Weg, wie der Umbau das Verhalten
|
||||||
|
haette veraendern koennen."""
|
||||||
|
cur, atr, x = 79.0, 0.1, 0.6
|
||||||
|
lvl = cur + 0.05951 # dist = 0,5951
|
||||||
|
s = _Stub(x, [round(lvl, 5)], [], atr)
|
||||||
|
info = s.room_info(cur)
|
||||||
|
assert info["long"]["dist_atr"] == 0.6, "Anzeige rundet wie erwartet"
|
||||||
|
assert info["long"]["frei"] is False, "…aber die ENTSCHEIDUNG nutzt den Rohwert"
|
||||||
|
assert s._room_gate(_rec("LONG"), cur)["block"] == "entry_room"
|
||||||
|
|
||||||
|
|
||||||
|
# ── die neue Anzeige-Faehigkeit ──────────────────────────────────────────
|
||||||
|
def test_beide_richtungen_werden_geliefert():
|
||||||
|
"""Die Karte gibt keine Richtung vor → sie braucht den Raum fuer beide."""
|
||||||
|
cur = 79.0
|
||||||
|
s = _Stub(0.6, [79.30], [78.20], 0.2)
|
||||||
|
info = s.room_info(cur)
|
||||||
|
assert info["long"]["level"] == 79.30
|
||||||
|
assert info["long"]["dist_atr"] == 1.5 and info["long"]["frei"] is True
|
||||||
|
assert info["short"]["level"] == 78.20
|
||||||
|
assert info["short"]["dist_atr"] == 4.0 and info["short"]["frei"] is True
|
||||||
|
assert info["gate"] == 0.6 and info["atr_m5"] == 0.2
|
||||||
|
|
||||||
|
|
||||||
|
def test_der_reale_fall_vom_10_08():
|
||||||
|
"""Trade #49671601: BUY, Widerstand praktisch AUF dem Kurs.
|
||||||
|
|
||||||
|
ctx_dist_res_atr war −0,07 bei ATR_M5 0,12 (dem Floor) — der Widerstand lag
|
||||||
|
rund 0,008 $ voraus, der Stop 0,560 $ zurueck. Genau diese Lage muss die
|
||||||
|
Karte kuenftig ANZEIGEN."""
|
||||||
|
cur, atr = 79.065, 0.12
|
||||||
|
s = _Stub(0.6, [79.073], [78.99], atr)
|
||||||
|
info = s.room_info(cur)
|
||||||
|
assert info["long"]["frei"] is False # kein Raum nach oben
|
||||||
|
assert info["long"]["dist_atr"] < 0.1
|
||||||
|
assert s._room_gate(_rec("LONG"), cur)["block"] == "entry_room"
|
||||||
|
|
||||||
|
|
||||||
|
def test_freie_bahn_ohne_level():
|
||||||
|
"""Kein Level voraus = bestes gemessenes Segment → frei, nicht geblockt."""
|
||||||
|
s = _Stub(0.6, [], [], 0.2)
|
||||||
|
info = s.room_info(79.0)
|
||||||
|
assert info["long"] == {"level": None, "dist_atr": None, "frei": True}
|
||||||
|
assert s._room_gate(_rec("LONG"), 79.0).get("block") is None
|
||||||
|
|
||||||
|
|
||||||
|
def test_ohne_atr_kein_absturz():
|
||||||
|
"""Kaltstart: keine pb_feats → fail-open, kein Gate, keine Exception."""
|
||||||
|
s = _Stub(0.6, [79.3], [78.2], None)
|
||||||
|
info = s.room_info(79.0)
|
||||||
|
assert info["atr_m5"] is None and info["long"] is None
|
||||||
|
assert s._room_gate(_rec("LONG"), 79.0).get("block") is None
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(pytest.main([__file__, "-v"]))
|
||||||
+50
@@ -256,6 +256,56 @@ function renderM15(s) {
|
|||||||
? "④ erwartete Spanne · " + r.map(x => `${x.minutes}′ ${f(x.lo, 2)}–${f(x.hi, 2)}`).join(" · ")
|
? "④ erwartete Spanne · " + r.map(x => `${x.minutes}′ ${f(x.lo, 2)}–${f(x.hi, 2)}`).join(" · ")
|
||||||
: "④ erwartete Spanne —";
|
: "④ erwartete Spanne —";
|
||||||
}
|
}
|
||||||
|
// ── ③b RAUM + Motor-Zustand ──────────────────────────────────────────
|
||||||
|
// ⚠⚠ ANLASS: Trade #49671601 (10.08., −87 €). Die Karte stand auf GRÜN,
|
||||||
|
// während der Signal-Motor zu 76 % mit `entry_room` blockte — der Widerstand
|
||||||
|
// lag 0,07×ATR voraus, der Stop 4,7×ATR zurück. Die Karte prüfte, ob ein
|
||||||
|
// Level NAH ist, nie ob RAUM dahinter ist. Genau das steht jetzt hier.
|
||||||
|
// ⚠ Reine Anzeige: keine Richtung, kein Blockieren der Buttons.
|
||||||
|
const mo = s.motor || {};
|
||||||
|
const rm = mo.raum || {};
|
||||||
|
const moEl = $("m15-motor");
|
||||||
|
const seite = (o, lbl) => {
|
||||||
|
if (!o) return null;
|
||||||
|
if (o.level == null) return `${lbl} frei`;
|
||||||
|
return `${lbl} ${o.dist_atr}×ATR${o.frei ? "" : " ⚠"}`;
|
||||||
|
};
|
||||||
|
if (mo.block) {
|
||||||
|
// Der Motor blockt — das ist die Information, die gefehlt hat.
|
||||||
|
const gr = {
|
||||||
|
entry_room: "kein Raum zum Gegenlevel",
|
||||||
|
breakout_pending: "Bestätigung läuft noch",
|
||||||
|
min_conf: `Konfidenz ${mo.conf_pct ?? "?"} % zu schwach`,
|
||||||
|
htf_counter: "gegen den M30-Trend",
|
||||||
|
stretch: "überdehnt",
|
||||||
|
deadband: "Totband — kein Trend",
|
||||||
|
eia: "EIA-Blackout",
|
||||||
|
dead_hour: "gesperrte Stunde",
|
||||||
|
stale: "Daten veraltet",
|
||||||
|
no_data: "keine Daten"
|
||||||
|
}[mo.block] || mo.block;
|
||||||
|
moEl.className = "m15-item " + (mo.block === "entry_room" ? "warn" : "");
|
||||||
|
moEl.textContent = `③b Motor blockt: ${gr}`;
|
||||||
|
} else if (mo.signal) {
|
||||||
|
moEl.className = "m15-item ok";
|
||||||
|
moEl.textContent = `③b Motor frei · ${mo.signal}`;
|
||||||
|
} else {
|
||||||
|
moEl.className = "m15-item";
|
||||||
|
moEl.textContent = "③b Motor —";
|
||||||
|
}
|
||||||
|
const teile = [seite(rm.long, "▲"), seite(rm.short, "▼")].filter(Boolean);
|
||||||
|
if (teile.length) {
|
||||||
|
moEl.textContent += ` · Raum ${teile.join(" / ")}`
|
||||||
|
+ (rm.gate ? ` (Gate ${rm.gate})` : "");
|
||||||
|
}
|
||||||
|
moEl.title = "Was der Signal-Motor GERADE blockt, plus der Raum zum nächsten "
|
||||||
|
+ "Gegenlevel je Richtung (M5 — dieselbe Rechnung wie das Gate selbst, kein "
|
||||||
|
+ "Nachbau). Unter 0,6×ATR ist der Ertrag am Level gedeckelt: gemessen "
|
||||||
|
+ "ØR −0,119/−0,067 bei PF 0,42/0,66 — Trefferquote hoch und trotzdem "
|
||||||
|
+ "Verlust. ⚠ Gate ③ liest dieselbe Nähe als GUT (ruhende Order AM Level), "
|
||||||
|
+ "entry_room als SCHLECHT (Markt-Einstieg mit dem Level als Hindernis). "
|
||||||
|
+ "Beides stimmt — für verschiedene Trades. Blockt nichts.";
|
||||||
|
|
||||||
rEl.title = "Reine Geometrie auf dem Kegel: liegt das Ziel im 80-%-Band des "
|
rEl.title = "Reine Geometrie auf dem Kegel: liegt das Ziel im 80-%-Band des "
|
||||||
+ "Horizonts? Das Ziel wird NUR in der von ② erlaubten Richtung gebildet — "
|
+ "Horizonts? Das Ziel wird NUR in der von ② erlaubten Richtung gebildet — "
|
||||||
+ "ein rein geometrisches Ziel zeigte am 10.08. auf die verbotene Seite. "
|
+ "ein rein geometrisches Ziel zeigte am 10.08. auf die verbotene Seite. "
|
||||||
|
|||||||
+9
-2
@@ -6,7 +6,7 @@
|
|||||||
<meta name="theme-color" content="#0d1117">
|
<meta name="theme-color" content="#0d1117">
|
||||||
<title>Oil · MT5</title>
|
<title>Oil · MT5</title>
|
||||||
<link rel="manifest" href="manifest.json">
|
<link rel="manifest" href="manifest.json">
|
||||||
<link rel="stylesheet" href="style.css?v=159">
|
<link rel="stylesheet" href="style.css?v=160">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<!-- Modul-Menü (ganz oben) -->
|
<!-- Modul-Menü (ganz oben) -->
|
||||||
@@ -109,6 +109,13 @@
|
|||||||
<div class="m15-item" id="m15-kosten" title="Spread geteilt durch ATR_M15. Auf M15 gemessen 0,104 gegen 0,193 auf M5 - hier ist die groebere Zeitebene echt im Vorteil. Schwellen 0,20 / 0,32 aus backtest_realcosts.py.">—</div>
|
<div class="m15-item" id="m15-kosten" title="Spread geteilt durch ATR_M15. Auf M15 gemessen 0,104 gegen 0,193 auf M5 - hier ist die groebere Zeitebene echt im Vorteil. Schwellen 0,20 / 0,32 aus backtest_realcosts.py.">—</div>
|
||||||
<div class="m15-item" id="m15-zone" title="Naechstes STEHENDES Level (M30-Pivots, dieselbe Quelle wie Chart und S/R-Close) mit Abstand und Durchbruchwahrscheinlichkeit. ⚠ P(break) ist live derzeit rund 7 Pp zu niedrig kalibriert.">—</div>
|
<div class="m15-item" id="m15-zone" title="Naechstes STEHENDES Level (M30-Pivots, dieselbe Quelle wie Chart und S/R-Close) mit Abstand und Durchbruchwahrscheinlichkeit. ⚠ P(break) ist live derzeit rund 7 Pp zu niedrig kalibriert.">—</div>
|
||||||
<div class="m15-item" id="m15-reich" title="Erwartete Spanne aus core/cone.py - out-of-sample geprueft, die 80-%-Baender treffen real 77 %. Sagt WIE WEIT, nicht wohin.">—</div>
|
<div class="m15-item" id="m15-reich" title="Erwartete Spanne aus core/cone.py - out-of-sample geprueft, die 80-%-Baender treffen real 77 %. Sagt WIE WEIT, nicht wohin.">—</div>
|
||||||
|
<!-- ③b RAUM + MOTOR-ZUSTAND (2026-08-10, nach Trade #49671601 / -87 EUR).
|
||||||
|
Die Karte zeigte GRUEN, waehrend der Signal-Motor zu 76 % mit
|
||||||
|
entry_room blockte: dieselbe Tatsache ("ein Level ist nah") wird von
|
||||||
|
Gate ③ als GUT und von entry_room als SCHLECHT gelesen - beide
|
||||||
|
gemessen, aber ueber verschiedene Trades. Diese Zeile schliesst die
|
||||||
|
Luecke. REINE ANZEIGE, blockt nichts. -->
|
||||||
|
<div class="m15-item" id="m15-motor" title="Was der Signal-Motor GERADE blockt, plus der Raum zum naechsten Gegenlevel je Richtung (M5, dieselbe Rechnung wie das Gate selbst). Unter 0,6xATR ist der Ertrag am Level gedeckelt - gemessen ØR -0,119/-0,067 bei PF 0,42/0,66, also Trefferquote hoch und trotzdem Verlust. ⚠ Gate ③ liest dieselbe Naehe als GUT (ruhende Order AM Level), entry_room als SCHLECHT (Markt-Einstieg mit dem Level als Hindernis). Beides stimmt - fuer verschiedene Trades.">—</div>
|
||||||
<!-- ⑥ SELBSTMESSUNG: die Karte zeigt die Trefferquote ihrer EIGENEN
|
<!-- ⑥ SELBSTMESSUNG: die Karte zeigt die Trefferquote ihrer EIGENEN
|
||||||
Lesart. ⚠ Jeder Ausfall, der in diesem Projekt je aufgedeckt wurde,
|
Lesart. ⚠ Jeder Ausfall, der in diesem Projekt je aufgedeckt wurde,
|
||||||
kam aus einer Rueckkopplung — pbreak_predictions entlarvte den
|
kam aus einer Rueckkopplung — pbreak_predictions entlarvte den
|
||||||
@@ -347,6 +354,6 @@
|
|||||||
</div>
|
</div>
|
||||||
<div id="toast" class="toast hidden"></div>
|
<div id="toast" class="toast hidden"></div>
|
||||||
|
|
||||||
<script src="app.js?v=159"></script>
|
<script src="app.js?v=160"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
Reference in New Issue
Block a user