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:
co-authored by
Claude Opus 4.8
parent
21d855b163
commit
ae6bbb2f35
@@ -932,7 +932,10 @@ dort bereits nachvalidiert, ØR +0,305.) **Eine** Oberfläche:
|
||||
KEIN CDN wegen WireGuard/Offline). Daten via `GET /api/bars?tf=&n=` →
|
||||
`engine.get_bars` (OHLC+EMA+Trend unter `mt5_lock`). `app.js`: `loadCharts`/
|
||||
`renderChart`, Charts nur bei sichtbarem Tab geladen + Auto-Refresh 20 s
|
||||
(schont den Lock). Broker-Zeit (UTC+3) → UTC via `broker_utc_offset`.
|
||||
(schont den Lock). Broker-Zeit (UTC+3) → UTC via `broker_utc_offset`. **Gitternetz
|
||||
dezent** (2026-07-27, User „dunkler/unauffälliger": `grid`-Farbe `#161b22`→`#10151c`,
|
||||
näher am Hintergrund `#0d1117`). **Chartmuster-Overlay (nur M30)** — s. Chartmuster-
|
||||
Kachel: die 2 stärksten M30-Muster als Trigger-/Ziel-Preislinien.
|
||||
**Regressionskanal-Overlay (nur M30, 2026-07-20):** `get_bars` liefert bei M30
|
||||
`channel {dir, mid[], upper[], lower[]}` (Arrays deckungsgleich mit den Bars,
|
||||
`structure.channel_series` — Regression über 60 abgeschl. Bars, linear über die
|
||||
@@ -1134,7 +1137,16 @@ dort bereits nachvalidiert, ØR +0,305.) **Eine** Oberfläche:
|
||||
Dreieck.** Je Muster: Richtung (bull/bear/neutral), Nackenlinie/Trigger, Measured-
|
||||
Move-Ziel, Status (bildet sich / bestätigt / ungültig→raus), Güte (Extrem-Symmetrie),
|
||||
Bars-her. Refresh ~30 s im Trend-Loop unter `mt5_lock`; Snapshot `patterns
|
||||
{patterns[], atr, cur}`; Frontend zeigt Karte + Disclaimer „kein Signal". ⚠ **Signal-
|
||||
{patterns[], atr, cur}`. **Nur AKTIVE, kursnahe Muster** (2026-07-27, `_detect`-Filter):
|
||||
abgearbeitete Muster (Ziel bereits erreicht) UND zu weit entfernte (`_MAX_DIST_ATR=5,0`,
|
||||
Trigger >5×ATR vom Kurs) werden ausgeblendet — sonst zeigte die Kachel alte, nicht mehr
|
||||
handelbare Muster. **Grafische Darstellung (2026-07-27, User „grafisch darstellen"):**
|
||||
(a) Muster-Karte zeigt je Item eine **kompakte SVG-Skizze** der Form (`_patternSvg`,
|
||||
viewBox 56×32, farbcodiert grün/rot/grau) — Form auf einen Blick; (b) im **Charts-Tab
|
||||
(M30)** werden die **2 stärksten Muster als Preislinien** gezeichnet: Trigger = kräftige
|
||||
Linie (grün bull/rot bear, Label „▶ bestätigt"/„◌ bildet sich"), Ziel = gepunktete Linie
|
||||
gleicher Farbe. Dazu liefert `get_bars` bei M30 jetzt `patterns` (aus `patterns.snapshot`).
|
||||
Frontend zeigt Karte + Disclaimer „kein Signal". ⚠ **Signal-
|
||||
Einfluss ERST nach Backtest** (`backtest_patterns.py`, noch zu bauen) — die
|
||||
Muster-KLASSE ist bereits 2× als Signal verworfen (`backtest_doubletop.py` oben +
|
||||
`backtest_structure.py`), also **hohe Beweislast**; die Kachel macht das Muster nur
|
||||
@@ -1249,7 +1261,7 @@ dort bereits nachvalidiert, ØR +0,305.) **Eine** Oberfläche:
|
||||
Signal → **Hinweis-Dialog „trotzdem eröffnen?"** (überschreibbar). CLOSE bei
|
||||
**negativem** P&L → Sicherheitsabfrage; im Plus/Breakeven direkt. Sonst keine
|
||||
Token-Dialoge (`require_token=false`). Audio braucht 1× Nutzer-Tap.
|
||||
- Asset-Version aktuell **v=116** (in `web/index.html` hochzählen, siehe Workflows).
|
||||
- Asset-Version aktuell **v=117** (in `web/index.html` hochzählen, siehe Workflows).
|
||||
Schriftgrößen 2026-07-24 global **+4px** (2× je +2px auf User-Wunsch; Body-Basis
|
||||
14→18px). ⚠ Betrifft in `style.css` sowohl `font-size:Npx` (71×) ALS AUCH die
|
||||
`font:<weight> Npx/…`-**Shorthand** (3×: `.ms-chip`/`.ms-bos-lbl`/`.sqm-badge` —
|
||||
|
||||
+7
-1
@@ -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
@@ -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
|
||||
|
||||
+37
-2
@@ -225,6 +225,26 @@ function renderCircuitBreaker(cb) {
|
||||
}
|
||||
|
||||
// Chartmuster (visuell) — reine Anzeige. Status: bildet sich / bestätigt / (ungültig raus).
|
||||
// Kompakte SVG-Skizze der Muster-Form (viewBox 60×32). Rein schematisch, damit die
|
||||
// Form auf einen Blick erkennbar ist; Farbe = Richtung (grün bull / rot bear / grau).
|
||||
function _patternSvg(type, col) {
|
||||
const P = {
|
||||
double_top: "M2,26 L14,8 L22,16 L34,8 L46,26",
|
||||
double_bottom: "M2,6 L14,24 L22,16 L34,24 L46,6",
|
||||
hs: "M2,24 L12,14 L18,20 L28,4 L38,20 L44,14 L54,24",
|
||||
inv_hs: "M2,8 L12,18 L18,12 L28,28 L38,12 L44,18 L54,8",
|
||||
cup: "M2,6 C2,30 44,30 44,6 M44,6 L54,12",
|
||||
inv_cup: "M2,26 C2,2 44,2 44,26 M44,26 L54,20",
|
||||
tri_asc: "M2,26 L52,26 M2,4 L52,22",
|
||||
tri_desc: "M2,6 L52,6 M2,28 L52,10",
|
||||
tri_sym: "M2,4 L52,16 M2,28 L52,16",
|
||||
};
|
||||
const d = P[type] || "M2,16 L54,16";
|
||||
return `<svg class="pt-svg" viewBox="0 0 56 32" width="56" height="32" aria-hidden="true">
|
||||
<path d="${d}" fill="none" stroke="${col}" stroke-width="2"
|
||||
stroke-linejoin="round" stroke-linecap="round"/></svg>`;
|
||||
}
|
||||
|
||||
function renderPatterns(pt) {
|
||||
const el = $("pt-list");
|
||||
if (!el) return;
|
||||
@@ -235,6 +255,7 @@ function renderPatterns(pt) {
|
||||
}
|
||||
el.innerHTML = items.map(m => {
|
||||
const dirCls = m.dir === "bull" ? "up" : m.dir === "bear" ? "down" : "dim";
|
||||
const col = m.dir === "bull" ? "#3fb950" : m.dir === "bear" ? "#f85149" : "#8b949e";
|
||||
const arr = m.dir === "bull" ? "▲" : m.dir === "bear" ? "▼" : "◆";
|
||||
const st = m.status === "confirmed"
|
||||
? `<span class="pt-badge conf">bestätigt</span>`
|
||||
@@ -243,7 +264,8 @@ function renderPatterns(pt) {
|
||||
? `Ziel <b>${fmt(m.target, 2)}</b>` : `Ausbruch offen`;
|
||||
const q = Math.round((m.quality || 0) * 100);
|
||||
return `<div class="pt-item ${dirCls}">
|
||||
<div class="pt-head"><span class="pt-name">${arr} ${m.name}</span>${st}
|
||||
<div class="pt-head">${_patternSvg(m.type, col)}
|
||||
<span class="pt-name">${arr} ${m.name}</span>${st}
|
||||
<span class="pt-q">Güte ${q}%</span></div>
|
||||
<div class="pt-det">Trigger <b>${fmt(m.trigger, 2)}</b> · ${tgt}
|
||||
· Höhe ${m.height_atr}×ATR · vor ${m.bars_ago} Bars</div>
|
||||
@@ -1161,7 +1183,7 @@ function _makeChart(tf) {
|
||||
const chart = LightweightCharts.createChart(el, {
|
||||
width: el.clientWidth || 320, height: 200,
|
||||
layout: { background: { color: "#0d1117" }, textColor: "#8b949e", fontSize: 15 },
|
||||
grid: { vertLines: { color: "#161b22" }, horzLines: { color: "#161b22" } },
|
||||
grid: { vertLines: { color: "#10151c" }, horzLines: { color: "#10151c" } },
|
||||
timeScale: { borderColor: "#30363d", timeVisible: true, secondsVisible: false },
|
||||
rightPriceScale: { borderColor: "#30363d" },
|
||||
crosshair: { mode: LightweightCharts.CrosshairMode.Normal },
|
||||
@@ -1223,6 +1245,19 @@ function renderChart(tf, d) {
|
||||
(LV.zones || []).forEach(z => {
|
||||
addLine(z.hi, "#1f6feb", 1, "Zone"); addLine(z.lo, "#1f6feb", 1, "");
|
||||
});
|
||||
// Chartmuster-Overlay (nur M30): Trigger = kräftige Linie (grün bull / rot bear),
|
||||
// Ziel = gepunktete Linie gleicher Farbe. Nur die 2 stärksten, sonst wird's voll.
|
||||
(d.patterns || []).slice(0, 2).forEach(p => {
|
||||
const bull = p.dir === "bull";
|
||||
const col = bull ? "#3fb950" : (p.dir === "bear" ? "#f85149" : "#8b949e");
|
||||
c.lines.push(c.candle.createPriceLine({
|
||||
price: p.trigger, color: col, lineWidth: 2, lineStyle: 0,
|
||||
axisLabelVisible: true, title: (p.status === "confirmed" ? "▶ " : "◌ ") + p.name }));
|
||||
if (p.target != null)
|
||||
c.lines.push(c.candle.createPriceLine({
|
||||
price: p.target, color: col, lineWidth: 1, lineStyle: 1,
|
||||
axisLabelVisible: true, title: "Ziel" }));
|
||||
});
|
||||
c.chart.timeScale().fitContent();
|
||||
const tr = document.getElementById("ctr-" + tf);
|
||||
const [txt, cls] = _TREND_TXT[d.trend] || ["—", "flat"];
|
||||
|
||||
+2
-2
@@ -6,7 +6,7 @@
|
||||
<meta name="theme-color" content="#0d1117">
|
||||
<title>Oil · MT5</title>
|
||||
<link rel="manifest" href="manifest.json">
|
||||
<link rel="stylesheet" href="style.css?v=116">
|
||||
<link rel="stylesheet" href="style.css?v=117">
|
||||
</head>
|
||||
<body>
|
||||
<!-- Modul-Menü (ganz oben) -->
|
||||
@@ -226,6 +226,6 @@
|
||||
<div id="toast" class="toast hidden"></div>
|
||||
|
||||
<script src="lightweight-charts.standalone.production.js?v=114"></script>
|
||||
<script src="app.js?v=116"></script>
|
||||
<script src="app.js?v=117"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -147,6 +147,8 @@ main{display:grid;grid-template-columns:1fr 1fr;gap:11px;padding:11px}
|
||||
border-radius:var(--r-sm);padding:9px 12px;margin-bottom:8px}
|
||||
.pt-item.up{border-left-color:var(--up)} .pt-item.down{border-left-color:var(--down)}
|
||||
.pt-head{display:flex;align-items:center;gap:8px;flex-wrap:wrap}
|
||||
.pt-svg{flex:0 0 auto;background:var(--bg);border:1px solid var(--border);
|
||||
border-radius:4px;padding:1px 2px}
|
||||
.pt-name{font-weight:700;font-size:15px}
|
||||
.pt-item.up .pt-name{color:var(--up)} .pt-item.down .pt-name{color:var(--down)}
|
||||
.pt-badge{font-size:11px;font-weight:700;border-radius:4px;padding:1px 7px}
|
||||
|
||||
Reference in New Issue
Block a user