Chartmuster grafisch: SVG-Skizze in Karte + Trigger/Ziel-Linien im PWA-Chart

- patterns.py: nur AKTIVE, kursnahe Muster (abgearbeitete/Ziel erreicht raus,
  _MAX_DIST_ATR=5.0 Distanz-Filter)
- get_bars liefert bei M30 die Muster (patterns.snapshot) fürs Chart-Overlay
- app.js: _patternSvg-Skizze je Muster-Karte + 2 stärkste Muster als
  Trigger-/Ziel-Preislinien im M30-Chart; Gitternetz dezenter (#10151c)
- style.css: .pt-svg; index.html v=116->117; CLAUDE.md aktualisiert

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Axel Hocks
2026-07-27 19:13:47 +02:00
co-authored by Claude Opus 4.8
parent 21d855b163
commit ae6bbb2f35
6 changed files with 78 additions and 10 deletions
+7 -1
View File
@@ -586,16 +586,22 @@ class TradingEngine:
# Regressionskanal — nur M30 (dort ist die Marktstruktur berechnet), als
# Linien-Arrays deckungsgleich mit den Bars (Overlay im Charts-Tab).
channel = None
patterns = []
if tf_label == "M30":
try:
from core.structure import channel_series
channel = channel_series(closes, atr, k)
except Exception:
channel = None
# Visuelle Chartmuster (M30) fürs Overlay — Trigger-/Ziel-Preislinien.
try:
patterns = (self.patterns.snapshot() or {}).get("patterns") or []
except Exception:
patterns = []
return {"tf": tf_label, "symbol": sym, "trend": trend, "bars": bars,
"ema12": [round(x, 3) for x in e12[-k:]],
"ema50": [round(x, 3) for x in e50[-k:]],
"levels": levels, "channel": channel,
"levels": levels, "channel": channel, "patterns": patterns,
"broker_utc_offset": 3 * 3600}
def _in_startup_grace(self) -> bool:
+15 -2
View File
@@ -38,6 +38,8 @@ _MIN_HEIGHT_ATR = 1.2 # Muster-Höhe muss ≥ so viel ATR sein (sonst Rausche
_TOL_ATR = 0.7 # zwei "gleiche" Extrema dürfen so weit auseinander (×ATR)
_NECK_TOL_ATR = 0.9 # Nackenlinie "flach": zwei Tröge/Hochs so weit (×ATR)
_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)
def _atr(highs, lows, closes, p=14):
@@ -189,8 +191,19 @@ def _detect(piv, cur, atr, last_idx):
key = (m["type"], round(m["trigger"], 2))
if key not in best or m["quality"] > best[key]["quality"]:
best[key] = m
# ungültige raus, dann nach Status (confirmed/forming) + Qualität + Recency
res = [m for m in best.values() if m["status"] != "invalidated"]
def played_out(m):
"""Ziel bereits erreicht → Muster abgearbeitet, nicht mehr zeigen."""
t = m.get("target")
if t is None:
return False
return (cur <= t) if m["dir"] == "bear" else (cur >= t)
# raus: ungültig · abgearbeitet (Ziel erreicht) · zu weit vom Kurs (nicht mehr
# aktionabel — nur AKTIVE, kursnahe Muster zeigen).
res = [m for m in best.values()
if m["status"] != "invalidated"
and not played_out(m)
and abs(m["trigger"] - cur) <= _MAX_DIST_ATR * atr]
rank = {"confirmed": 2, "forming": 1}
res.sort(key=lambda m: (rank.get(m["status"], 0), m["quality"], -m["bars_ago"]), reverse=True)
return res