Richtungs-Pfeile im MT5 statt des Kegels (v1.32) + Handbuch-Muster ergaenzt
PFEILE (User: "anstatt des Trichters einen Pfeil, der die wahrscheinlichste
Kursentwicklung anzeigt"). Randbedingung: die FREIE Richtungsfrage ist
gemessen ein Muenzwurf (analyze_reversal.py, AUC 0,499-0,509 oos, in-sample
nur 0,52). Ein frei schwebender Prognose-Pfeil waere unbelegt - und
gefaehrlicher als der Kegel, weil er ueberzeugender aussieht. Deshalb DREI
Pfeile mit jeweils eigener, benannter Beleglage:
L blau naechstes Level, aus dem kalibrierten P(break)
(AUC 0,65 oos, live 27 % vorhergesagt vs 28 % real)
S neongruen Squeeze-Ausbruch - das EINZIGE validierte Richtungssignal
K grau Konsens der Module - NICHT kalibriert, heisst deshalb
"Konsens" und nicht "Prognose"
L hat ein TOTBAND 45-55 %: dort waagerechter Pfeil "unentschieden", statt
aus einem Muenzwurf eine Richtung zu zeichnen.
MQL5 v1.32: OBJ_ARROWED_LINE + Label an der Spitze. Label heisst ALBL_* und
NICHT T* - sonst zieht RepositionLabels() es an den rechten Rand und loest
es von der Pfeilspitze.
Kegel bleibt im Code und in der Dashboard-Kachel; der Bot exportiert ihn nur
nicht mehr ins Chart ([trading] export_cone=false).
CHARTMUSTER: alle Handbuch-Muster ergaenzt - Dreifach-Top/-Boden, Flagge,
Wimpel, Rechteck, Steigender/Fallender Keil. ALLE mit measured=False, sie
tragen die Verdict-Stimme NICHT: das Gewicht 1,0 stammt aus einem
Kontrolltest, der nur die alten Typen abdeckte. engine._verdict waehlt jetzt
nur Muster mit measured=True.
Bug beim Bau gefunden: der fallende Keil feuerte in 20.000 Bars KEIN
EINZIGES MAL - die Bedingung stand auf dh > dl, bei einem fallenden Keil
faellt aber die OBERE Linie schneller (sonst konvergiert nichts). Beim
Spiegelbild (steigender Keil) stimmte es.
backtest_patterns_v2.py (NEU) misst die neuen Typen - mit dem ECHTEN
Detektor (core.patterns._detect statt einer Nachbildung) und dem
kanonischen Exit, plus Kontrollgruppe "generischer Swing-Bruch".
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
365a0584e5
commit
81614a3128
+85
-9
@@ -368,6 +368,8 @@ class TradingEngine:
|
||||
self._mql5_files_dir = None # <terminal>\MQL5\Files (lazy, für den Indikator)
|
||||
self._export_trade_marks = self.cfg["trading"].get("export_trade_marks", "false").lower() == "true"
|
||||
self._export_mql5 = self.cfg["trading"].get("export_mql5_levels", "true").lower() == "true"
|
||||
# Kegel im MT5-Chart: per Default AUS, seit die Pfeile ihn ersetzen (2026-07-31).
|
||||
self._export_cone = self.cfg["trading"].get("export_cone", "false").lower() == "true"
|
||||
self._draw_last = {"r": None, "s": None} # letzte gezeichnete S/R (Hysterese)
|
||||
try: # WHT-Quellensteuer (Broker behält % je GEWINN ein) → Netto-Anzeige
|
||||
self._wht_pct = max(0.0, float(self.cfg["trading"].get("wht_pct", "0") or 0))
|
||||
@@ -2271,17 +2273,80 @@ class TradingEngine:
|
||||
lines.append(f"PX;{_bid}")
|
||||
except Exception:
|
||||
pass
|
||||
# WAHRSCHEINLICHKEITS-KEGEL: CN;<minuten>;<lo>;<hi> je Horizont (30/60/120),
|
||||
# aufsteigend — der Indikator verbindet sie vom aktuellen Bar aus zu zwei
|
||||
# sich öffnenden Pfaden. ⚠ Streuung, NICHT Richtung: der Kegel ist um den
|
||||
# aktuellen Kurs zentriert und sagt nur, wie weit der Kurs plausibel läuft.
|
||||
# Out-of-sample kalibriert (`analyze_cone.py`), 80 %-Band trifft real ~76 %.
|
||||
_atr5 = 0.0
|
||||
try:
|
||||
_atr5 = float(((self.wave.snapshot() or {}).get("pb_feats") or {})
|
||||
.get("atr") or 0.0)
|
||||
_cn = _cone_build(_bid, _atr5, bands=(80,))
|
||||
for lv in (_cn or {}).get("levels", []):
|
||||
lines.append(f"CN;{lv['minutes']};{lv['lo']};{lv['hi']}")
|
||||
except Exception:
|
||||
pass
|
||||
# ── WAHRSCHEINLICHKEITS-KEGEL (CN) — im Chart per Default AUS
|
||||
# (User 2026-07-31: „anstatt des Trichters hätte ich gerne einen Pfeil").
|
||||
# Die Dashboard-Kachel „Erwartete Spanne" bleibt; `[trading] export_cone=true`
|
||||
# holt den Kegel in den Chart zurück.
|
||||
if self._export_cone:
|
||||
try:
|
||||
_cn = _cone_build(_bid, _atr5, bands=(80,))
|
||||
for lv in (_cn or {}).get("levels", []):
|
||||
lines.append(f"CN;{lv['minutes']};{lv['lo']};{lv['hi']}")
|
||||
except Exception:
|
||||
pass
|
||||
# ── PFEILE: AR;<art>;<von>;<nach>;<text> (art L | S | K) ──────────────
|
||||
# ⚠ EINORDNUNG — die freie Frage „wohin läuft der Kurs?" ist im Projekt
|
||||
# GEMESSEN ein Münzwurf (`analyze_reversal.py`: AUC 0,499–0,509 out-of-
|
||||
# sample, in-sample nur 0,52). Deshalb zeigt KEINER dieser Pfeile eine
|
||||
# freie Kursprognose, sondern jeweils eine Aussage mit eigener Beleglage:
|
||||
# L = am nächsten LEVEL, aus dem kalibrierten P(break) (AUC 0,65 oos,
|
||||
# live kalibriert 27 % vorhergesagt vs 28 % real) — BEDINGTE Aussage
|
||||
# an einem realen Objekt, die einzige belegte Richtungsangabe.
|
||||
# S = SQUEEZE-Ausbruch — das einzige 2-Stichproben-validierte
|
||||
# Richtungssignal (ØR +0,14…+0,23). Erscheint nur bei `active`.
|
||||
# K = KONSENS der Verdict-Module — ⚠ NICHT kalibriert
|
||||
# (`analyze_verdict_calibration.py`: in H2 sogar invertiert), heißt
|
||||
# deshalb „Konsens" und nicht „Prognose".
|
||||
try:
|
||||
_dl = dl
|
||||
_r = (_dl.get("res") or [None])[0]
|
||||
_s = (_dl.get("sup") or [None])[0]
|
||||
if _bid and _atr5 > 0:
|
||||
# (L) nächstes Level = das nähere von beiden
|
||||
cand = [(abs(x - _bid), x, 1 if x > _bid else -1)
|
||||
for x in (_r, _s) if x is not None]
|
||||
if cand:
|
||||
_d0, _lv0, _dd = min(cand)
|
||||
_p = _pb_for(_lv0, _dd) # in Prozent (0–100) oder None
|
||||
# ⚠ TOTBAND 45–55 %: dort ist P(break) ein Münzwurf. Einen
|
||||
# Pfeil zu zeichnen würde daraus eine Richtungsaussage machen —
|
||||
# genau der Fehler, den das Projekt heute mehrfach vermieden hat
|
||||
# (freie Richtung = AUC 0,5). Stattdessen ein FLACHER Pfeil mit
|
||||
# „unentschieden".
|
||||
if _p is not None:
|
||||
if 45 <= _p <= 55:
|
||||
to = _bid # waagerecht = keine Aussage
|
||||
txt = f"{_p}% — unentschieden"
|
||||
elif _p > 55:
|
||||
to = _lv0 + _dd * 0.5 * _atr5
|
||||
txt = f"{_p}% Durchbruch"
|
||||
else:
|
||||
to = _bid - _dd * 1.0 * _atr5
|
||||
txt = f"{100 - _p}% Abprall"
|
||||
lines.append(f"AR;L;{round(_bid, 3)};{round(to, 3)};{txt}")
|
||||
# (S) Squeeze — nur wenn aktiv
|
||||
_sq = (self.wave.snapshot() or {}).get("squeeze") or {}
|
||||
if _sq.get("state") == "active" and _sq.get("dir") and _sq.get("level"):
|
||||
_sd = 1 if _sq["dir"] == "LONG" else -1
|
||||
lines.append(f"AR;S;{round(float(_sq['level']), 3)};"
|
||||
f"{round(float(_sq['level']) + _sd * 2.0 * _atr5, 3)};"
|
||||
f"Squeeze {_sq['dir']}")
|
||||
# (K) Konsens — Länge proportional zum Bias-Betrag (max 2×ATR)
|
||||
_vd = self._verdict(self.data.snapshot(), self.wave.signal(),
|
||||
self.wave.snapshot(), None,
|
||||
self.agent.snapshot(), self.elliott.snapshot(),
|
||||
self.patterns.snapshot())
|
||||
_b = float(_vd.get("bias") or 0.0)
|
||||
if abs(_b) >= 0.2:
|
||||
lines.append(f"AR;K;{round(_bid, 3)};"
|
||||
f"{round(_bid + (1 if _b > 0 else -1) * min(abs(_b), 1.0) * 2.0 * _atr5, 3)};"
|
||||
f"Konsens {_b:+.2f}")
|
||||
except Exception:
|
||||
pass
|
||||
# G/V der offenen Position, direkt unter dem Kurs (User-Wunsch 2026-07-30):
|
||||
@@ -2441,7 +2506,18 @@ class TradingEngine:
|
||||
pv, pdet, pw = 0, "—", 0.0
|
||||
try:
|
||||
_pats = (snap_parts_patterns or {}).get("patterns") or []
|
||||
_p0 = next((m for m in _pats if m.get("dir") in ("bull", "bear")), None)
|
||||
# ⚠ NUR GEMESSENE Muster-Typen stimmen mit (Fix 2026-07-31): das Gewicht
|
||||
# 1,0 stammt aus dem Kontrolltest (`backtest_patterns.py` gegen einen
|
||||
# generischen Swing-Bruch), der nur Doppeltop/-boden, Tasse/inv. Tasse,
|
||||
# SKS/inv. SKS und auf-/absteigendes Dreieck abdeckte. Die am selben Tag
|
||||
# aus dem Handbuch ergänzten Typen (Flagge, Wimpel, Rechteck, Keil,
|
||||
# Dreifach) sind REINE ANZEIGE — sonst würde ein validiertes Gewicht
|
||||
# still mit unvalidierten Eingaben gefüttert (genau der Deployment-Drift-
|
||||
# Fehler, der heute fünfmal aufgefallen ist). `measured` kommt aus
|
||||
# `core/patterns.py`; fehlt das Feld (Alt-Snapshot), gilt es als gemessen.
|
||||
_p0 = next((m for m in _pats
|
||||
if m.get("dir") in ("bull", "bear") and m.get("measured", True)),
|
||||
None)
|
||||
if _p0:
|
||||
pv = 1 if _p0["dir"] == "bull" else -1
|
||||
_conf = _p0.get("status") == "confirmed"
|
||||
|
||||
Reference in New Issue
Block a user