Charts-Tab komplett entfernt (User-Vorgabe)

Entfernt: Tab-Button, #view-charts, der gesamte app.js-Chart-Block
(Kartenaufbau, renderChart, loadCharts, 20-s-Refresh, Resize-Handler), die
Chart-CSS-Regeln, die vendorte Bibliothek
web/lightweight-charts.standalone.production.js (164 KB) samt script-Tag - und
die Backend-Kette dahinter: /api/bars und engine.get_bars (60 Zeilen). Beide
existierten ausschliesslich fuer dieses Tab; geprueft, dass es keinen anderen
Aufrufer gibt.

Nicht betroffen (sahen nur aehnlich aus): die Chartmuster-Karte (Verdict-Gewicht
0,25, im Tab "SIG Live"), der MQL5-Indikator samt sr_levels.csv-Export (eigener
Pfad ueber _write_levels_file) und die Analyse-Module structure/patterns/cone -
die speisen den Snapshot, nicht das Chart. Der MT5-Chart bleibt voll bedient.

Ein veralteter Kommentar in trader.py, der auf engine.get_bars verwies, wurde
auf core/gaps.py umgehaengt.

Verifiziert am laufenden Server: /api/bars -> 404, Bibliothek -> 404,
data-view="charts" und id="view-charts" nicht mehr im ausgelieferten HTML,
6 Tabs zu 6 Views paarig, keine verwaisten JS-Referenzen, Klammern-Balance ok,
keine Fehler im Log. v=142.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Axel Hocks
2026-08-05 23:27:24 +02:00
co-authored by Claude Opus 5
parent 6ff3c15b1d
commit a1a5d560b9
8 changed files with 24 additions and 394 deletions
+20 -160
View File
@@ -2213,7 +2213,7 @@ dort bereits nachvalidiert, ØR +0,305.) **Eine** Oberfläche:
`pnl>0`-Gate (`history`) noch vom 5-Min-Cooldown betroffen.
## Mobile-UI (web/) & Alarme
- Aufbau: **Tab-Navigation ganz oben** (sticky: Dashboard/**SIG Live**/**SIG Anzeige**/Logs/Statistik/**Charts**/News),
- Aufbau: **Tab-Navigation ganz oben** (sticky: Dashboard/**SIG Live**/**SIG Anzeige**/Logs/Statistik/News),
darunter Header.
- **Design-System (`style.css`, Pass 2026-07-15, KEIN Framework — Tailwind bewusst
verworfen wegen WireGuard/Offline + kein Node):** Tokens in `:root` — Flächen-Leiter
@@ -2246,164 +2246,24 @@ dort bereits nachvalidiert, ØR +0,305.) **Eine** Oberfläche:
der Zeile. Titel „aktueller Trade läuft"; Richtung/Lots/Einsatz = Anzeige-Boxen
(`.tb-val`), Rest editierbar — alle im selben `.tb-field`-Look. Position-Kachel hält
nur noch `cur-near`-Warnung + `pos-srhint`. (`.emg*`/`.sr-toggle`-CSS entfernt.)
- **Charts-Tab** (`#view-charts`): **M1/M5/M15/M30/H1 untereinander**, je ein
Candlestick-Chart mit **EMA12 (blau) + EMA50 (amber)**-Overlay und Trend-Header
(▲/▼/→ nach EMA12-vs-EMA50 mit Totband). Framework **TradingView Lightweight
Charts v4.2.3 — lokal vendored** (`web/lightweight-charts.standalone.production.js`,
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`. **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
volle Breite extrapoliert). Frontend zeichnet 3 LineSeries (oben/unten gestrichelt,
Mitte gepunktet, grau). Dieselbe Kanal-Definition wie die Marktstruktur-Kachel —
REINE Anzeige (Signalvariante gemessen verworfen, s. `backtest_structure.py`).
**S/R-Overlay & Dashboard-Kachel = EINE Quelle** (`_draw_levels`; die Position-Kachel
`sr-res`/`sr-sup` speist jetzt via `_dash_sr` daraus statt aus dem Misch-Set
`_sr_levels` — vorher wichen Chart und Kachel ab, User-Feedback 2026-07-15). `_sr_levels`
bleibt intern für Strategie/Konfidenz. **„Klebrige" Hysterese** in `_draw_levels`
(`_draw_last`, verschärft 2026-07-21): die gezeichnete Linie **bleibt stehen, bis der
Kurs sie WIRKLICH durchbricht** (bid > R+0,1×ATR bzw. bid < S0,1×ATR) — ein neuer,
näherer M5-Pivot löst KEINEN Wechsel mehr aus (das war die Zitter-Quelle: auf M5
bilden sich laufend neue Pivots, die alte 0,25×ATR-Regel sprang darauf an). Ausnahme:
ist die alte Linie >1,5×ATR weit weg UND ein deutlich näheres Level existiert, wird
refresht (sonst bliebe sie irrelevant hängen). Gilt für PWA-Chart UND MQL5-CSV (eine
Quelle).
**Level-Auswahl nach Kursposition statt Entstehungstyp (Fix 2026-07-23, User-Frage
„Widerstand/Support-Retest von der falschen Seite"):** vorher kam Widerstand NUR aus
`ph` (Swing-Hochs) und Support NUR aus `pl` (Swing-Tiefs) — ein gebrochenes Level blieb
für IMMER im falschen Topf hängen. 2 von 4 Retest-Varianten hatten dadurch NIE eine
Durchbruchwahrscheinlichkeit: (1) Widerstand von unten ✓ funktionierte, (2) **Support-
Retest von unten** (Kurs unter einem gebrochenen Support, nähert sich von unten wieder)
✗ kein Hinweis, (3) Support von oben ✓ funktionierte, (4) **Widerstand-Retest von oben**
(Kurs über einem gebrochenen Widerstand, nähert sich von oben wieder) ✗ kein Hinweis.
Fix: `ph`/`pl` bleiben GETRENNT geclustert (unverändert granular — ein erster Versuch,
beide Rohpools VOR dem Clustern zusammenzulegen, erzeugte in dichten Zonen Ketten-Mega-
Cluster durch die verdoppelte Punktdichte, >1×ATR breit, verworfen), aber NACH dem
Clustern werden beide fertigen Linien-Listen zusammengeführt und die Wahl erfolgt rein
per `min/max` relativ zu `bid` — nicht mehr nach Topf-Zugehörigkeit. Verifiziert: Fall 1
bei Live-Daten (bid 90,951) und ein dichter Kontrollfall liefern exakt dieselben Level
wie vorher (keine Regression); Fälle 2+4 (synthetische Retest-Szenarien) finden jetzt
das nahe, vorher übersehene Level statt eines weit entfernten Ersatzlevels. Betrifft
`_draw_levels` direkt und damit ALLE Konsumenten (PWA-Chart, MQL5-CSV, Dashboard-
Kachel, `_sr_close_hint`/Auto-Close) — eine Quelle, ein Fix.
**⚠ LEVEL-TF M5→M30 umgestellt (2026-07-30, gemessen `backtest_level_tf.py`):**
Auslöser war die User-Beobachtung „die S/R-Linien liegen oft sehr nahe beieinander" —
quantifiziert stimmte das: **71 % der M5-Level lagen näher als 1×ATR** (Median-Abstand
nur 0,61×ATR). Gemessen über 80k M5-Bars in 2 Halbjahren (Trades bleiben M5, nur die
LEVEL-Quelle variiert; Live-Exit + Echtkosten). **M30 gewinnt in BEIDEN Hälften:**
Abstand **1,95×ATR** (nur 28 % eng) · AUC out-of-sample **0,721** (M15 0,725, M5 0,710)
· R-Ertrag der P(break)-Close-Regel **rund doppelt** (+12.424/+13.047 vs +6.698/+7.749
bei P<0,55). M15 lag dazwischen; M30 gewählt wegen des klar besten R-Ertrags und der
weitesten Level. **Das P(break)-Modell wurde dafür NACHTRAINIERT** (`_PB_MU/_SD/_W`
in `engine.py`): out-of-sample validiert (H1→H2), dann auf allen 80k Bars final
gefittet — 34.771 Touches, Basisrate 39,4 %, Kalibrierung trifft (pred/real 23/25 ·
38/36 · 58/59 · 86/87). Die **Merkmale bleiben M5** (mom6/mom3 auf M5-Bars), nur die
Level sind M30. Quelle live: `wave.pb_levels30` (M30-H/L aus dem 15-s-Turn-Fetch,
k=3, letzte 50 Bars = trainingsgleich zum 300-M5-Lookback), Fallback `pb_levels` (M5)
beim Kaltstart. ⚠ **NICHT mitverändert: das Entry-Raum-Gate** (`wave._room_gate`) nutzt
weiter die M5-`pb_levels``entry_room_atr=0.6` ist darauf kalibriert, eine Umstellung
dort wäre ungemessen. Alte M5-Modellwerte stehen als Kommentar über `_PB_MU` (Rückweg).
Real nach der Umstellung: R/S 84,617/83,722 = 2,9×ATR Abstand (vorher 0,28×ATR).
**S/R-Overlay:** `get_bars` liefert `levels {res, sup}` aus **`engine._draw_levels`**
= echte Kursstruktur (rohe M30-Pivots aus `wave.pb_levels30`, geclustert ~0,5×ATR,
**nur die NÄCHSTE je Seite = 2 Linien total, deckungsgleich mit Dashboard
sr-res/sr-sup** — mehr Linien tauchten beim Kurslauf laufend auf/ab, User-Feedback
2026-07-14; identisch zur Auto-Close-Logik). ⚠ Bugfix 2026-07-14: vorher
kamen die Linien aus `_sr_levels` (stale [zones]-Config + Tageslevel + Gaps) —
gezeichnet wurden Uralt-Level (81,24/82,64) statt aktueller Struktur. Farben
**blau** (User-Wunsch): Widerstand #1f6feb, Support #58a6ff. **Beschriftung**
(2026-07-14): PWA-Achsenlabel „Widerstand"/„Unterstützung" (+Nummer ab 2);
MQL5 zusätzlich sichtbares OBJ_TEXT-Label je Linie („Widerstand 78.65",
~8 Kerzen vor dem rechten Rand, in Linienfarbe).
**Kein MT5-Terminal-Zeichnen aus Python:** das `MetaTrader5`-Paket hat KEINE
`object_create`/`OBJ_HLINE`/`chart_*`-Funktionen (nur MQL5). S/R-Linien daher im
PWA-Chart. **Für das Desktop-Terminal: Datei-Bridge**`engine._write_levels_file`
(im `_run_analysis`, ~5 s, `[trading] export_mql5_levels=true`) schreibt atomar
`<terminal>\MQL5\Files\sr_levels.csv` (`SYM;…` / `R;preis` / `S;preis` / **`CH;<U|M|L>;
t1;p1;t2;p2`** = Regressionskanal-Anker / **`LQ;<B|A>;preis;size;<U|D>;<1|0>`** = ECHTE
Liquiditäts-Wand + Pfeilrichtung + Dominanz (⚠ seit MQL5 v1.14 zeichnet der
Indikator davon NUR noch den PFEIL, rechtsbündig am aktuellen Rand — Linie und
Text-Label sind auf User-Wunsch raus, Details stehen im Tooltip) / **`LT;t1;p1;t2;p2;<U|D>`** =
Liquiditäts-Trendlinie, s. u.); Quelle **`_draw_levels`** (S/R) +
**`structure.channel_line`** (Kanal) + **`self.hl_walls`** (Liquidität),
Indikator-Farben blau DodgerBlue/DeepSkyBlue, Wände grün/orangerot.
**Chartmuster-Linien (`PT;…`) 2026-07-30 auf User-Wunsch ENTFERNT** („entferne die
chartmuster linien aus dem mt5" — Chart war zu voll); die Muster-Kachel + das
PWA-Chart-Overlay bleiben unverändert, nur der MT5-Export/Indikator-Handler ist raus.
**ECHTE Liquiditäts-Wände im MT5-Chart (User-Wunsch 2026-07-30 „zeichne mir die
liquiditätslinien in den mt5 ein"):** `core/hl_walls.py` holt vom **HL-Dashboard**
(`GET http://127.0.0.1:8001/api/walls`, `[trading] export_hl_walls=true`) das
**größte L2-Orderbuch-Level je Seite** — die einzigen Linien im Chart, die NICHT aus
Pivots geschätzt sind, sondern **tatsächlich ruhende Orders** zeigen (Pepperstone =
Market Maker, liefert kein Buch). ⚠ **Basis-Korrektur zwingend:** HL `xyz:CL` notiert
**~0,45 $ unter** dem CFD (anderer Kontrakt/Contango) — die Umrechnung passiert
serverseitig im HL-Projekt (Median über 60 Snapshots), hier kommt schon
`bid_mt5`/`ask_mt5` an; fehlt die Basis, wird NICHT gezeichnet. ⚠ **`127.0.0.1`, nicht
`localhost`** (IPv6-Auflösung → 2066 ms statt 1946 ms, lief in den 2-s-Timeout) und
der HL-Endpoint liest **nur seine DB** (live-`fetch_l2_book` dauerte ~2 s = Timeout).
**Pfeil + Liquiditäts-Trendlinie (User-Wunsch 2026-07-30, Indikator v1.13):**
(a) Jede Wand bekommt einen **Pfeil in ihrer Wirkrichtung** (Wingdings 233/234) —
Lesart **„die Wand HÄLT"**: Nachfrage-Wand stützt von unten (▲), Angebots-Wand
deckelt (▼); die **größere** der beiden wird dicker gezeichnet + „<<" im Label
(`dominant` aus dem Endpoint). ⚠ **Die Pfeilrichtung ist INTERPRETATION, nicht
gemessen** — die Gegenthese („Liquidity Grab": der Kurs läuft ZUR Wand, um sie
abzuräumen) ist genauso verbreitet. Welche stimmt, beantwortet `analyze_bookflow.py`
(Frage 3) nach genug Daten; bis dahin ist der Pfeil eine Lese-Hilfe, kein Signal.
(b) **Pinke Liquiditäts-Trendlinie** (`OBJ_TREND`, nach rechts verlängert,
`InpShowLiqTrend`/`InpLiqTrendColor`): lineare Regression über den **volumen-
gewichteten Wand-Schwerpunkt** `(bid_px·bid_sz + ask_px·ask_sz)/(bid_sz+ask_sz)`
der letzten **60 min** (≥20 Punkte nötig). ⚠ Sie zeigt etwas ANDERES als eine
Kurs-Trendlinie: **wohin das Gewicht der RUHENDEN Orders wandert**, nicht wohin der
gehandelte Preis läuft — steigt sie, ziehen die Käufer ihre großen Gebote hoch.
Zeiten werden im Bot in **Broker-Zeit** umgerechnet (`trader._broker_offset_s()`),
weil MT5-Charts die brauchen; Preise sind bereits basis-korrigiert.
Fail-safe: HL-Server aus → keine Linien, kein Fehler (Log 1×, dann still), Cache 10 s,
Daten älter als 60 s werden verworfen. **REINE ANZEIGE** — ob HL-Buch-Signale auf den
CFD übertragbar sind, wird gerade gemessen (`analyze_bookflow.py` im HL-Projekt,
Datensammlung seit 2026-07-30, Urteil in ~2 Wochen). Der Indikator
**`mql5/SR_Levels.mq5`** (**v1.29** — `LQ`-Handler zeichnet seit v1.14 **nur noch
den PFEIL**, rechtsbündig am aktuellen Rand (in `RepositionLabels()` nachgeführt,
bleibt beim Scrollen rechts; Details im **Tooltip**) — Linie und Text-Label sind
auf User-Wunsch raus („Chart zu voll"). **v1.15 = CHART-THEME** (User „Farben im
MT5 wie im Bild"): `ApplyTheme()` setzt beim Start Hintergrund/Gitter/Kerzenfarben
auf die Dashboard-Palette — Bull **#3B82F6**, Bear **#EF4444**, BG **#0D1117**,
Gitter **#10151C** (dezent), Text #8B949E; Liquiditäts-Pfeile **Gold #F59E0B**
(= „Liquiditätszone" der Legende). ⚠ Das ändert das Chart **dauerhaft** (bleibt
auch nach Entfernen des Indikators) → `InpApplyTheme=false` schaltet es ab, alle
Farben sind einzeln als Inputs überschreibbar. Schalter: `InpShowWalls`/
`InpWallBidColor`/`InpWallAskColor`/`InpShowLiqTrend`/`InpLiqTrendColor`;
PT-Handler entfernt; in `MQL5\Indicators\`, **braucht
`OnCalculate` als No-op** sonst Compile-Error, F7-kompilieren, auf den Chart ziehen)
liest die S/R-Linien per Timer (OBJ_HLINE) UND den **M30-Regressionskanal als 3
diagonale Trendlinien** (OBJ_TREND, nach rechts verlängert, oben/unten gestrichelt/
Mitte gepunktet grau; `InpShowChannel`/`InpChanColor`, Default an) — Anker in
Broker-Zeit (= Server-Zeit im Terminal), zeichnet **nur bei Datei-Änderung neu**
(Inhalts-Cache `g_last` gegen Flackern). Der Kanal ist dieselbe M30-Definition wie
PWA-Overlay + Marktstruktur-Kachel; ⚠ nach dem Update den Indikator **neu F7-
kompilieren** (v1.10). Die dynamische Handels-Range (`DrawRange`, Hoch/Tief
der letzten `InpRangeBars` Kerzen) ist **per Default AUS** (`InpShowRange=false`,
User-Wunsch 2026-07-14) — bei Bedarf über die Indikator-Eingaben aktivierbar. (Die
statischen `[zones]`-Config werden NICHT mehr in die CSV geschrieben — waren stale
WTI-Level, füllten den Chart.)
**Architektur bewusst: Python = Gehirn (Analyse/Strategie/PWA/News/LLM/Reports),
MQL5 = nur dünne Zeichen-Schicht.** Kein Full-Rewrite in MQL5 (PWA/Backtests/
Integrationen wären dort nicht/kaum machbar). **Sym-Zeile:** Symbol links · rechts `hdr-clock`
(**Srv = Broker-Wallclock** aus `market.server_time` via `getUTC*`, **Lok** =
Browser) + **Session** (`#session`; zeigt 2122 Uhr lokal „🕘 PRIME-TIME" —
gemessen die stärksten Stunden — und bei Headline-Burst „📰 NEWS-STURM"/„viele
News" aus `news.storm_state()` → Snapshot `news_storm` {level: normal/elevated/
storm, fresh, base_per_h}; Headline-Rate letzte Stunde vs Basisrate, Basis-Fenster
min. 6 h verankert. Beides reine Anzeige) + Verbindungspunkt. Darunter Balance ·
**Tag P/L** · Equity. **Kein** Bid/Ask/Spread (Kurs steht in der Position-Kachel,
färbt sich nahe S/R). **Tag P/L** (`day-pl`) = realisiert heute
(`stats_overview("today")`) + offene Position; grün/rot
(`day_pl{realized,open,total}` im Snapshot).
- ⚠⚠ **CHARTS-TAB KOMPLETT ENTFERNT (User-Vorgabe 2026-08-05, v=142).** Weg sind:
Tab-Button, `#view-charts`, der gesamte `app.js`-Chart-Block (Kartenaufbau,
`renderChart`, `loadCharts`, 20-s-Refresh, Resize-Handler), die Chart-CSS-Regeln,
die **vendorte Bibliothek** `web/lightweight-charts.standalone.production.js`
(164 KB) samt `<script>`-Tag — und die Backend-Kette dahinter: **`/api/bars`**
und **`engine.get_bars`** (60 Zeilen). Beide existierten ausschliesslich für
dieses Tab (geprüft: kein anderer Aufrufer).
**Nicht betroffen** — sie sahen nur ähnlich aus: die **Chartmuster**-Karte
(`#card-patterns`, Verdict-Gewicht 0,25, jetzt im Tab „SIG Live"), der
**MQL5-Indikator** samt `sr_levels.csv`-Export (eigener Pfad über
`_write_levels_file`, unabhängig von `get_bars`) und alle Analyse-Module
(`structure.py`, `patterns.py`, `cone.py`) — die speisen den Snapshot, nicht das
Chart. Der MT5-Chart bleibt also vollständig bedient.
Verifiziert am laufenden Server: `/api/bars`**404**, Bibliothek → **404**,
`data-view="charts"` und `id="view-charts"` nicht mehr im ausgelieferten HTML,
6 Tabs zu 6 Views paarig, keine verwaisten JS-Referenzen, Klammern-Balance ok.
Zurückholen: alles liegt in der Git-Historie (Commit davor).
- **Gesamtempfehlung** (`#card-verdict`, ganz oben): grafische Aggregation aller
Module — Konfidenz-Ring (conic-gradient = `conf_pct`), Bias-Balken mit Nadel
(SHORT↔LONG) und Modul-Chips (Welle/M30/H1/KI/Elliott/**Squeeze**, je grün/rot/grau; **TU
@@ -3272,7 +3132,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=141** (in `web/index.html` hochzählen, siehe Workflows).
- Asset-Version aktuell **v=142** (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`
-60
View File
@@ -757,66 +757,6 @@ class TradingEngine:
self._save_emergency_state() # Handeingabe neustart-fest (Fix 2026-07-17)
return self._takeprofit
def get_bars(self, tf_label: str, n: int = 90) -> dict:
"""OHLC-Bars + EMA12/50 + Trend je Timeframe für den Charts-Tab. Läuft unter
`mt5_lock`. Trend = EMA12 vs EMA50 mit Totband (_TREND_DEADBAND×ATR)."""
import MetaTrader5 as mt5
from core.mt5_utils import mt5_lock
from core.wave_rec import _ema_series, _atr, _EMA_FAST, _EMA_SLOW, _TREND_DEADBAND
tf_map = {"M1": mt5.TIMEFRAME_M1, "M5": mt5.TIMEFRAME_M5, "M15": mt5.TIMEFRAME_M15,
"M30": mt5.TIMEFRAME_M30, "H1": mt5.TIMEFRAME_H1}
tf = tf_map.get(tf_label); sym = self.data.symbol
if tf is None or not sym:
return {"tf": tf_label, "bars": [], "error": "kein TF/Symbol"}
n = max(30, min(int(n), 200))
try:
with mt5_lock(timeout=5) as got:
if not got:
return {"tf": tf_label, "bars": [], "error": "busy"}
rates = mt5.copy_rates_from_pos(sym, tf, 0, n + _EMA_SLOW)
except Exception as e:
return {"tf": tf_label, "bars": [], "error": str(e)}
if rates is None or len(rates) == 0:
return {"tf": tf_label, "bars": [], "error": "keine Daten"}
closes = [float(b["close"]) for b in rates]
highs = [float(b["high"]) for b in rates]
lows = [float(b["low"]) for b in rates]
e12 = _ema_series(closes, _EMA_FAST); e50 = _ema_series(closes, _EMA_SLOW)
atr = _atr(highs, lows, closes) or 0.0
d = e12[-1] - e50[-1]
dead = _TREND_DEADBAND * atr if atr else 0.0
trend = "flat" if abs(d) <= dead else ("up" if d > 0 else "down")
k = min(n, len(rates))
bars = [{"t": int(b["time"]), "o": float(b["open"]), "h": float(b["high"]),
"l": float(b["low"]), "c": float(b["close"])} for b in rates[-k:]]
# S/R-Linien fürs Chart-Overlay: echte Pivot-Struktur (`_draw_levels`,
# identisch zum Auto-Close-System) — NICHT mehr die stale [zones]-Config.
try:
dl = self._draw_levels()
levels = {"res": dl.get("res", []), "sup": dl.get("sup", []), "zones": []}
except Exception:
levels = {"res": [], "sup": [], "zones": []}
# 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, "patterns": patterns,
"broker_utc_offset": 3 * 3600}
def _in_startup_grace(self) -> bool:
"""True in den ersten `_startup_grace_s` s nach Bot-Start — dann schließt der
Bot keinen laufenden Trade selbst (Daten noch nicht stabil nach Neustart)."""
+1 -1
View File
@@ -52,7 +52,7 @@ def _retcode_msg(res) -> str:
# ── Broker-Zeitzone ───────────────────────────────────────────────────────────
# Pepperstone liefert Broker-Zeit UTC+3 (ganzjährig) — dieselbe Konstante nutzt
# `engine.get_bars` bereits für die Chart-Umrechnung (`broker_utc_offset`).
# `core/gaps.py` nutzt dieselbe Konstante fuer die Datums-Zuordnung.
_BROKER_OFF_DEFAULT_S = 3 * 3600
# Nur ein FRISCHER Tick darf den Offset (neu) bestimmen. Begründung s.
# `_broker_offset_s` — die Schwelle liegt unter dem halben Rundungsraster
-6
View File
@@ -214,12 +214,6 @@ def news():
}))
@app.get("/api/bars")
def bars(tf: str = "M5", n: int = 90):
"""OHLC-Bars + EMA12/50 + Trend je Timeframe (Charts-Tab). Read-only, kein Token."""
return JSONResponse(content=_json_safe(app.state.engine.get_bars(tf, n)))
@app.get("/api/squeeze_monitor")
def squeeze_monitor():
"""Auto-Squeeze-B4-Monitor: Live-Trades (echt) + Mechanik (Entry-Edge isoliert)
-137
View File
@@ -1423,7 +1423,6 @@ function showView(name) {
if (name === "logs") loadLogs();
if (name === "stats") loadStats();
if (name === "news") loadNews();
if (name === "charts") loadCharts();
}
// Nur echte View-Tabs registrieren — NICHT den Mute-Button (der trägt .tab nur
// fürs Styling; ohne [data-view]-Filter überschrieb das hier seinen onclick und
@@ -1572,146 +1571,10 @@ async function loadNews() {
} catch (e) { bEl.innerHTML = '<div class="down">Netzwerkfehler</div>'; }
}
// ── Charts (Lightweight Charts): M1/M5/M15/M30/H1 untereinander mit Trend ──
const CHART_TFS = ["M1", "M5", "M15", "M30", "H1"];
const _charts = {}; // tf → {chart, candle, s12, s50}
const _TREND_TXT = { up: ["▲ Aufwärts", "up"], down: ["▼ Abwärts", "down"], flat: ["→ Seitwärts", "flat"] };
function _ensureChartCard(tf) {
if (document.getElementById("chart-" + tf)) return;
const card = document.createElement("div");
card.className = "chart-card"; card.id = "chart-" + tf;
card.innerHTML =
`<div class="chart-head"><span class="chart-tf">${tf}</span>` +
`<span class="chart-trend" id="ctr-${tf}">—</span>` +
`<span class="chart-last" id="clast-${tf}">—</span></div>` +
`<div class="chart-cv" id="ccv-${tf}"></div>`;
$("charts-body").appendChild(card);
}
function _makeChart(tf) {
const el = document.getElementById("ccv-" + tf);
// Explizite Größe statt autoSize: autoSize hängt an ResizeObserver und lieferte
// auf Mobil-Browsern teils 0×0-Charts (leerer Tab). clientWidth ist gesetzt,
// weil loadCharts erst nach dem Einblenden des Tabs läuft.
const chart = LightweightCharts.createChart(el, {
width: el.clientWidth || 320, height: 200,
layout: { background: { color: "#0d1117" }, textColor: "#8b949e", fontSize: 15 },
grid: { vertLines: { color: "#10151c" }, horzLines: { color: "#10151c" } },
timeScale: { borderColor: "#30363d", timeVisible: true, secondsVisible: false },
rightPriceScale: { borderColor: "#30363d" },
crosshair: { mode: LightweightCharts.CrosshairMode.Normal },
handleScale: { axisPressedMouseMove: false },
});
const candle = chart.addCandlestickSeries({
upColor: "#3fb950", downColor: "#f85149", borderVisible: false,
wickUpColor: "#3fb950", wickDownColor: "#f85149",
});
const line = (color) => chart.addLineSeries(
{ color, lineWidth: 1, priceLineVisible: false, lastValueVisible: false, crosshairMarkerVisible: false });
const s12 = line("#58a6ff"), s50 = line("#d29922"); // EMA12 blau, EMA50 amber
// Regressionskanal (nur M30 bekommt Daten): oben/unten gestrichelt, Mitte gepunktet
const chLine = (color, style) => chart.addLineSeries(
{ color, lineWidth: 1, lineStyle: style, priceLineVisible: false,
lastValueVisible: false, crosshairMarkerVisible: false });
const chU = chLine("#8b949e", 2), chM = chLine("#8b949e66", 1), chL = chLine("#8b949e", 2);
_charts[tf] = { chart, candle, s12, s50, chU, chM, chL };
return _charts[tf];
}
function renderChart(tf, d) {
const off = d.broker_utc_offset || 0;
const B = d.bars || [];
if (d.error || !B.length) {
const tr = document.getElementById("ctr-" + tf);
if (tr) { tr.textContent = d.error ? "⚠ " + d.error : "keine Daten"; tr.className = "chart-trend flat"; }
return;
}
const candles = B.map(b => ({ time: b.t - off, open: b.o, high: b.h, low: b.l, close: b.c }));
const em = (arr) => (arr || []).map((v, i) => (B[i] ? { time: B[i].t - off, value: v } : null)).filter(Boolean);
const c = _charts[tf] || _makeChart(tf);
c.candle.setData(candles);
c.s12.setData(em(d.ema12));
c.s50.setData(em(d.ema50));
// Regressionskanal (M30) — 3 Linien deckungsgleich mit den Bars; sonst leeren
if (c.chU) {
const ch = d.channel;
if (ch && ch.upper) {
c.chU.setData(em(ch.upper)); c.chM.setData(em(ch.mid)); c.chL.setData(em(ch.lower));
} else {
c.chU.setData([]); c.chM.setData([]); c.chL.setData([]);
}
}
// S/R-Linien + Zonen (Ranges) als horizontale Preislinien einzeichnen
(c.lines || []).forEach(pl => { try { c.candle.removePriceLine(pl); } catch (e) {} });
c.lines = [];
const addLine = (p, color, style, title) => {
if (p == null) return;
c.lines.push(c.candle.createPriceLine({ price: p, color, lineWidth: 1,
lineStyle: style, axisLabelVisible: true, title: title || "" }));
};
const LV = d.levels || {};
// Verständliche Labels: nächstes Level ausgeschrieben, weitere nummeriert
(LV.res || []).forEach((p, i) =>
addLine(p, "#1f6feb", 2, i === 0 ? "Widerstand" : `Widerstand ${i + 1}`));
(LV.sup || []).forEach((p, i) =>
addLine(p, "#58a6ff", 2, i === 0 ? "Unterstützung" : `Unterstützung ${i + 1}`));
(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"];
tr.textContent = txt; tr.className = "chart-trend " + cls;
document.getElementById("clast-" + tf).textContent = candles[candles.length - 1].close.toFixed(3);
}
async function loadCharts() {
if (typeof LightweightCharts === "undefined") {
$("charts-body").innerHTML = '<div class="dim" style="padding:14px">Chart-Framework nicht geladen (Seite neu laden).</div>';
return;
}
for (const tf of CHART_TFS) {
_ensureChartCard(tf);
try {
const d = await (await fetch(`api/bars?tf=${tf}&n=100`)).json();
renderChart(tf, d);
// Breite nachziehen (Rotation/Resize seit Erstellung)
const c = _charts[tf], el = document.getElementById("ccv-" + tf);
if (c && el && el.clientWidth > 0) c.chart.applyOptions({ width: el.clientWidth });
} catch (e) {
// Fehler SICHTBAR machen statt stumm schlucken (war: leere Kacheln ohne Grund)
const tr = document.getElementById("ctr-" + tf);
if (tr) { tr.textContent = "⚠ " + (e && e.message ? e.message : "Fehler"); tr.className = "chart-trend flat"; }
}
}
}
window.addEventListener("resize", () => {
for (const tf of CHART_TFS) {
const c = _charts[tf], el = document.getElementById("ccv-" + tf);
if (c && el && el.clientWidth > 0) c.chart.applyOptions({ width: el.clientWidth });
}
});
$("logs-reload").onclick = loadLogs;
$("stats-reload").onclick = loadStats;
$("news-reload").onclick = loadNews;
$("charts-reload").onclick = loadCharts;
// Auto-Refresh nur solange der Charts-Tab sichtbar ist (schont den mt5_lock)
setInterval(() => { if (!$("view-charts").classList.contains("hidden")) loadCharts(); }, 20000);
// Sofort einmal per REST laden, dann auf den Live-Stream umschalten
fetch("api/snapshot").then(r => r.json()).then(_applySnapshot).catch(() => {});
+2 -12
View File
@@ -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=141">
<link rel="stylesheet" href="style.css?v=142">
</head>
<body>
<!-- Modul-Menü (ganz oben) -->
@@ -20,7 +20,6 @@
<button class="tab" data-view="siganzeige">SIG Anzeige</button>
<button class="tab" data-view="logs">Logs</button>
<button class="tab" data-view="stats">Statistik</button>
<button class="tab" data-view="charts">Charts</button>
<button class="tab" data-view="news">News</button>
<button class="tab mute-tab" id="mute-btn" title="Töne an/aus">🔊</button>
</nav>
@@ -262,14 +261,6 @@
</section>
</section>
<!-- Charts: M1/M5/M15/M30/H1 untereinander mit Trend (Lightweight Charts) -->
<section id="view-charts" class="view hidden">
<div class="view-head">
<h2>Charts</h2>
<button class="reload" id="charts-reload"></button>
</div>
<div id="charts-body"></div>
</section>
<!-- News -->
<section id="view-news" class="view hidden">
@@ -321,7 +312,6 @@
</div>
<div id="toast" class="toast hidden"></div>
<script src="lightweight-charts.standalone.production.js?v=114"></script>
<script src="app.js?v=141"></script>
<script src="app.js?v=142"></script>
</body>
</html>
File diff suppressed because one or more lines are too long
+1 -11
View File
@@ -26,7 +26,7 @@ body{
-webkit-font-smoothing:antialiased; text-rendering:optimizeLegibility;
}
/* Zahlen-lastige Werte in Mono (aligned, Trading-Terminal-Optik) */
#hdr-price,#hdr-pnl,.px span,.tb-val,.tb-field input,.chart-last,
#hdr-price,#hdr-pnl,.px span,.tb-val,.tb-field input,
.hdr-clock,.gap-zone,.trade .p{font-family:var(--mono)}
/* ── Header (scrollt unter den Tabs) ────── */
@@ -342,16 +342,6 @@ main{padding-bottom:74px} /* Platz für die fixe Leiste */
.stx-c b {font-size:18px;color:var(--text-bright);margin-left:3px}
/* Charts-Tab: M1/M5/M15/M30/H1 untereinander (Lightweight Charts) */
#charts-body {display:flex;flex-direction:column;gap:12px;padding:10px}
.chart-card {background:var(--surface-2);border:1px solid var(--border);border-radius:10px;overflow:hidden}
.chart-head {display:flex;align-items:center;gap:10px;padding:8px 12px;border-bottom:1px solid var(--line)}
.chart-tf {font-weight:800;font-size:19px;color:var(--text-bright);letter-spacing:.5px}
.chart-trend {font-weight:700;font-size:17px}
.chart-trend.up {color:#3fb950}
.chart-trend.down {color:#f85149}
.chart-trend.flat {color:var(--dim)}
.chart-last {margin-left:auto;font-variant-numeric:tabular-nums;color:var(--text-bright);font-size:18px}
.chart-cv {height:200px;width:100%}
/* Kurslücken (Gaps): offene Fill-Magnete */
.gaps-list {display:flex;flex-direction:column;gap:6px}