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
@@ -41,6 +41,19 @@ _MAX_LOOKBACK = 9 # nur die letzten N Swings betrachten (aktuelle Muster)
|
||||
_MAX_DIST_ATR = 5.0 # Trigger max. so weit vom AKTUELLEN Kurs (sonst nicht mehr
|
||||
# aktionabel — alte, weit weg gelaufene Muster ausblenden)
|
||||
|
||||
# Typen, die im Kontrolltest (`backtest_patterns.py` gegen einen generischen
|
||||
# Swing-Bruch, 2026-07-30) tatsaechlich gemessen wurden. NUR sie tragen die
|
||||
# Verdict-Stimme; die 2026-07-31 aus dem Handbuch ergaenzten Typen (Flagge,
|
||||
# Wimpel, Rechteck, Keil, Dreifach) sind REINE ANZEIGE, bis sie gemessen sind.
|
||||
_MEASURED = {"double_top", "double_bottom", "hs", "inv_hs",
|
||||
"cup", "inv_cup", "tri_asc", "tri_desc"}
|
||||
|
||||
# Fortsetzungsmuster brauchen eine „Stange" (impulsiver Vorlauf) — ab wann gilt
|
||||
# ein Swing als Stange, und wie kurz muss die Konsolidierung dagegen sein.
|
||||
_POLE_MIN_ATR = 2.0 # Vorlauf mindestens so gross
|
||||
_POLE_MAX_BARS = 20 # ... und in hoechstens so vielen Bars (impulsiv)
|
||||
_FLAG_MAX_FRAC = 0.5 # Konsolidierung hoechstens so gross wie die Stange
|
||||
|
||||
|
||||
def _atr(highs, lows, closes, p=14):
|
||||
trs = []
|
||||
@@ -102,6 +115,13 @@ def _detect(piv, cur, atr, last_idx):
|
||||
"target": round(target, 3), "height_atr": round(h / atr, 1),
|
||||
"status": _status(dir_, trigger, cur, extreme),
|
||||
"bars_ago": last_idx - ix[-1], "note": note,
|
||||
# ⚠ `measured` = war dieser Typ im Kontrolltest (`backtest_patterns.py`
|
||||
# + generischer Breakout, 2026-07-30)? NUR gemessene Typen dürfen die
|
||||
# Verdict-Stimme tragen — sonst würde ein validiertes Gewicht still mit
|
||||
# unvalidierten Eingaben gefüttert (der Deployment-Drift-Fehler des
|
||||
# Projekts). Gemessen wurden: Doppeltop/-boden, Tasse/inv. Tasse,
|
||||
# auf-/absteigendes Dreieck, SKS/inv. SKS.
|
||||
"measured": typ in _MEASURED,
|
||||
})
|
||||
|
||||
# über alle 3er/5er-Fenster am aktuellen Rand
|
||||
@@ -156,6 +176,90 @@ def _detect(piv, cur, atr, last_idx):
|
||||
add("cup", "Tasse+Henkel", "bull", q, max(rim_l, rim_r), bot,
|
||||
f"abgerundeter Boden {bot:.2f}, Rand ~{rim_r:.2f} (Henkel-Oberkante)")
|
||||
|
||||
# ══ HANDBUCH-MUSTER (ergänzt 2026-07-31, User-Vorgabe „alle Muster aus dem
|
||||
# Handbuch") — ALLE REINE ANZEIGE (`measured=False`), sie tragen die
|
||||
# Verdict-Stimme NICHT, weil der Kontrolltest sie nicht abdeckt. ═══════════
|
||||
for s2 in range(len(P) - 2):
|
||||
w = list(zip(kd[s2:], pr[s2:], ix[s2:]))
|
||||
|
||||
# ── Dreifach-Top / -Boden: drei ~gleiche Extreme (H,L,H,L,H bzw. umgekehrt).
|
||||
# Abgrenzung zur SKS: dort ist das MITTLERE Extrem deutlich weiter draussen.
|
||||
if len(w) >= 5 and [x[0] for x in w[:5]] == ["H", "L", "H", "L", "H"]:
|
||||
h1, t1, h2, t2, h3 = (w[i][1] for i in range(5))
|
||||
q = _sim(h1, h2, atr) * _sim(h2, h3, atr)
|
||||
if q > 0.30 and h2 <= max(h1, h3) + 0.3 * atr: # kein Kopf → kein SKS
|
||||
neck = min(t1, t2)
|
||||
add("triple_top", "Dreifach-Top", "bear", q, neck, max(h1, h2, h3),
|
||||
f"drei Hochs {h1:.2f}/{h2:.2f}/{h3:.2f}, Nacken {neck:.2f}")
|
||||
if len(w) >= 5 and [x[0] for x in w[:5]] == ["L", "H", "L", "H", "L"]:
|
||||
l1, t1, l2, t2, l3 = (w[i][1] for i in range(5))
|
||||
q = _sim(l1, l2, atr) * _sim(l2, l3, atr)
|
||||
if q > 0.30 and l2 >= min(l1, l3) - 0.3 * atr:
|
||||
neck = max(t1, t2)
|
||||
add("triple_bottom", "Dreifach-Boden", "bull", q, neck, min(l1, l2, l3),
|
||||
f"drei Tiefs {l1:.2f}/{l2:.2f}/{l3:.2f}, Nacken {neck:.2f}")
|
||||
|
||||
# ── Flagge / Wimpel (Fortsetzung): impulsive STANGE, danach eine kleine
|
||||
# Konsolidierung GEGEN die Stangenrichtung. Flagge = paralleler Kanal,
|
||||
# Wimpel = konvergierend. Trigger = Ausbruch in Stangenrichtung.
|
||||
if len(w) >= 4:
|
||||
pole_h = abs(w[1][1] - w[0][1])
|
||||
pole_bars = w[1][2] - w[0][2]
|
||||
if (pole_h >= _POLE_MIN_ATR * atr and 0 < pole_bars <= _POLE_MAX_BARS):
|
||||
up = w[1][1] > w[0][1] # Stange nach oben?
|
||||
cons = [x[1] for x in w[1:5]]
|
||||
rng = max(cons) - min(cons)
|
||||
if rng <= _FLAG_MAX_FRAC * pole_h and len(cons) >= 3:
|
||||
# konvergierend? (Schwankung nimmt ab → Wimpel)
|
||||
d1 = abs(cons[1] - cons[0]); d2 = abs(cons[-1] - cons[-2])
|
||||
pennant = d2 < d1 * 0.7
|
||||
trig = max(cons) if up else min(cons)
|
||||
ext = min(cons) if up else max(cons)
|
||||
typ = "pennant" if pennant else "flag"
|
||||
nm = ("Wimpel" if pennant else "Flagge") + (" (bullisch)" if up else " (bärisch)")
|
||||
add(typ, nm, "bull" if up else "bear", 0.55, trig, ext,
|
||||
f"Stange {pole_h/atr:.1f}×ATR in {pole_bars} Bars, "
|
||||
f"{'konvergierende' if pennant else 'flache'} Konsolidierung")
|
||||
|
||||
# ── Rechteck (Range): zwei ~flache Hochs UND zwei ~flache Tiefs. Das Handbuch
|
||||
# führt es als Fortsetzung — die Ausbruchsrichtung ist aber offen, deshalb
|
||||
# NEUTRAL (wie das symmetrische Dreieck), nur als Kontext.
|
||||
hh = [(x[2], x[1]) for x in zip(kd, pr, ix) if x[0] == "H"][-2:]
|
||||
ll = [(x[2], x[1]) for x in zip(kd, pr, ix) if x[0] == "L"][-2:]
|
||||
if len(hh) == 2 and len(ll) == 2:
|
||||
top = (hh[0][1] + hh[1][1]) / 2.0
|
||||
bot = (ll[0][1] + ll[1][1]) / 2.0
|
||||
if (abs(hh[1][1] - hh[0][1]) <= _NECK_TOL_ATR * atr
|
||||
and abs(ll[1][1] - ll[0][1]) <= _NECK_TOL_ATR * atr
|
||||
and (top - bot) >= _MIN_HEIGHT_ATR * atr):
|
||||
out.append({
|
||||
"type": "rectangle", "name": "Rechteck (Range)", "dir": "neutral",
|
||||
"quality": round(_sim(hh[0][1], hh[1][1], atr)
|
||||
* _sim(ll[0][1], ll[1][1], atr), 2),
|
||||
"trigger": round(top, 3), "target": None,
|
||||
"height_atr": round((top - bot) / atr, 1), "status": "forming",
|
||||
"bars_ago": last_idx - ix[-1], "measured": False,
|
||||
"note": f"Range {bot:.2f}–{top:.2f} — Ausbruchsrichtung offen",
|
||||
})
|
||||
# ── Keil: beide Linien in DIESELBE Richtung geneigt und konvergierend.
|
||||
# Steigender Keil = bärisch, fallender Keil = bullisch (Handbuch).
|
||||
dh = hh[1][1] - hh[0][1]
|
||||
dl = ll[1][1] - ll[0][1]
|
||||
conv = abs(hh[1][1] - ll[1][1]) < abs(hh[0][1] - ll[0][1]) - 0.3 * atr
|
||||
if conv and dh > 0.3 * atr and dl > 0.3 * atr and dl > dh:
|
||||
add("wedge_rise", "Steigender Keil", "bear", 0.5,
|
||||
min(ll[0][1], ll[1][1]), max(hh[0][1], hh[1][1]),
|
||||
"beide Linien steigen, Tiefs schneller — Erschöpfung nach oben")
|
||||
# ⚠ Bedingung korrigiert (2026-07-31): stand `dh > dl` — bei einem FALLENDEN
|
||||
# Keil fällt aber die OBERE Linie schneller (sonst konvergiert nichts), also
|
||||
# `dh < dl`. Mit der alten Fassung war die Bedingung in sich widersprüchlich
|
||||
# (`conv` verlangt Konvergenz) → der Typ feuerte in 20.000 Bars KEIN EINZIGES
|
||||
# MAL. Beim Spiegelbild (steigender Keil, `dl > dh`) stimmte es.
|
||||
if conv and dh < -0.3 * atr and dl < -0.3 * atr and dh < dl:
|
||||
add("wedge_fall", "Fallender Keil", "bull", 0.5,
|
||||
max(hh[0][1], hh[1][1]), min(ll[0][1], ll[1][1]),
|
||||
"beide Linien fallen, Hochs schneller — Erschöpfung nach unten")
|
||||
|
||||
# ── Dreiecke (Fortsetzung): letzte 2 Hochs + 2 Tiefs, Steigungen prüfen ──
|
||||
highs = [(x[2], x[1]) for x in zip(kd, pr, ix) if x[0] == "H"][-2:]
|
||||
lows = [(x[2], x[1]) for x in zip(kd, pr, ix) if x[0] == "L"][-2:]
|
||||
@@ -183,6 +287,7 @@ def _detect(piv, cur, atr, last_idx):
|
||||
"target": None, "height_atr": round(wide / atr, 1),
|
||||
"status": "forming", "bars_ago": last_idx - ix[-1],
|
||||
"note": f"konvergierend (Hochs fallen, Tiefs steigen) — Ausbruchsrichtung offen",
|
||||
"measured": False, # neutral, ohne Richtung → nie stimmberechtigt
|
||||
})
|
||||
|
||||
# Duplikate (gleicher Typ+Trigger) zusammenfassen, beste Qualität behalten
|
||||
|
||||
Reference in New Issue
Block a user