diff --git a/CLAUDE.md b/CLAUDE.md index 75dd433..9b88d6a 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1202,7 +1202,7 @@ dort bereits nachvalidiert, ØR +0,305.) **Eine** Oberfläche: 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.15** — `LQ`-Handler zeichnet seit v1.14 **nur noch + **`mql5/SR_Levels.mq5`** (**v1.18** — `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 @@ -1417,6 +1417,29 @@ dort bereits nachvalidiert, ØR +0,305.) **Eine** Oberfläche: wie Squeeze vs. beliebige Box); ohne die ist „Muster wirkt" nicht belegt. Muster bleiben **reine Anzeige**. Ziel%-Spalte = der ehrliche Muster-Check (13–38 % = Muster sagen den Kursverlauf kaum voraus). +- **MQL5-Chart-Erweiterungen 2026-07-30 (Indikator v1.16–v1.18, alles REINE ANZEIGE):** + · **Trade-Marker** `TR;;` — die Kerze der **Eröffnung wird + HELLGRÜN**, die des **Close DUNKELGRÜN** umrandet (OBJ_RECTANGLE über Bar-Spanne + + High/Low, `iBarShift` → sitzt auf jeder Chart-TF korrekt). Quelle: offene Position + (`trader.open_time`) + die letzten `_TRADE_MARKS`=6 geschlossenen Trades. + · **Liquiditätszonen** `EQ;;lo;hi;` — **Equal Highs / Equal Lows** + aus den M30-Pivots (zwei Pivots innerhalb `_EQ_TOL_ATR`=0,15×ATR, 2–40 Bars + auseinander) als **goldenes Band** + gestrichelte Kante, nach rechts verlängert + (Darstellung nach User-Referenzbild). Berechnung in `wave_rec` (`_liq_zones`, + Snapshot `liq_zones`), Mindesthöhe `_EQ_MIN_H_ATR`=0,10×ATR. ⚠ **Als SIGNAL + gemessen und verworfen** (`backtest_liquidity_sweep.py`) — nur Kontext. + · **Aktueller Kurs** `PX;bid` — fett oben rechts (OBJ_LABEL, CORNER_RIGHT_UPPER, + Arial Black, `InpShowPrice`/`InpPriceSize`/`InpPriceColor`). + · ⚠ **Zwei Fallen beim Pfeil-Bug** („Pfeile werden nicht angezeigt", v1.17): + (1) **`ANCHOR_LEFT` ist für `OBJ_ARROW` UNGÜLTIG** — erlaubt sind nur `ANCHOR_TOP`/ + `ANCHOR_BOTTOM`; (2) die Pfeile sitzen rechts NEBEN der letzten Kerze → ohne + **`CHART_SHIFT=true`** gibt es dort keinen sichtbaren Bereich. Beides im Theme/ + Handler behoben. + · ⚠ **Blink-Fix in `core/hl_walls.py`** („Pfeile blinken nur manchmal auf"): bei + einem Fehlschlag (HTTP-Timeout, HL-Server kurz busy) wurde der Cache auf `None` + gesetzt → `LQ`-Zeilen fehlten in der CSV → der Indikator löschte die Pfeile beim + Redraw. Jetzt wird der **letzte gute Stand `_STALE_GRACE_S`=120 s weitergereicht**; + erst danach verschwinden die Pfeile (echter Ausfall). - **Kurslücken-Kachel** (`#card-gaps`): offene D1-Gaps des laufenden Jahres (`gaps.GapAnalyzer`, Refresh ~10 min im Trend-Loop unter `mt5_lock`). Je Gap: Richtung, Zone, Fill-Level, Abstand zum Kurs. Die **Fill-Level fließen als diff --git a/core/engine.py b/core/engine.py index 9d1fbca..375b53e 100644 --- a/core/engine.py +++ b/core/engine.py @@ -75,6 +75,8 @@ _SQUEEZE_REENTRY_COOLDOWN_S = 180.0 # nach JEDEM Close eines Trades (Notfall/SL # gegen die Open→Instant-Close-Kaskade bei zu engem Stop # (real 2026-07-27 15:39: −3-€-Stop killte Entry #1 in 16 s, # Squeeze noch aktiv → sofortiger Re-Entry; 2026-07-28) +_TRADE_MARKS = 6 # so viele letzte Trades im MQL5-Chart markieren + # (Kerze umranden: Eröffnung hellgrün, Close dunkelgrün) _STARTUP_GRACE_S = 60.0 # nach Bot-Start schließt der Bot 60 s lang KEINEN # laufenden Trade server-seitig (S/R-Close/Notfall/ # Time-Stop/Reverse) — die Daten (Preis/ATR/P(break)/ @@ -1984,6 +1986,45 @@ class TradingEngine: f"{'U' if tr.get('dir')=='up' else 'D'}") except Exception: pass + # Liquiditätszonen (Equal Highs/Lows, M30): EQ;;lo;hi; + # → goldenes Band ab dem ersten Pivot, nach rechts verlängert. + # ⚠ REINE ANZEIGE (als Signal gemessen verworfen, s. backtest_liquidity_sweep). + try: + zs = (self.wave.snapshot() or {}).get("liq_zones") or {} + boff_z = self.trader._broker_offset_s() + for key, tag in (("eqh", "H"), ("eql", "L")): + z = zs.get(key) + if z and z.get("lo") and z.get("hi"): + # `t` ist ROHE Broker-Zeit aus copy_rates → NICHT nochmal + # umrechnen (anders als die HL-Trendlinie, die lokal tickt). + lines.append(f"EQ;{tag};{z['lo']};{z['hi']};{int(z['t'])}") + except Exception: + pass + # Aktueller Kurs für die fette Anzeige oben rechts (User-Wunsch): PX;bid + try: + _bid = (self.data.snapshot() or {}).get("bid") + if _bid: + lines.append(f"PX;{_bid}") + except Exception: + pass + # Trade-Marker: TR;; → der Indikator umrandet die Kerze, + # in der eröffnet (hellgrün) bzw. geschlossen (dunkelgrün) wurde + # (User-Vorgabe 2026-07-30). Offene Position + die letzten Trades des Tages. + try: + boff = self.trader._broker_offset_s() + marks: list[tuple[str, int]] = [] + ps = self.trader.snapshot() + if ps.get("ticket") and ps.get("open_time"): + marks.append(("O", int(float(ps["open_time"]) + boff))) + for t in (self.history.last_closed_trades(_TRADE_MARKS) or []): + if t.get("entry_time"): + marks.append(("O", int(float(t["entry_time"]) + boff))) + if t.get("exit_time"): + marks.append(("C", int(float(t["exit_time"]) + boff))) + for kind, ts in marks[:2 * _TRADE_MARKS + 1]: + lines.append(f"TR;{kind};{ts}") + except Exception: + pass # Zonen NICHT mehr aus der (statischen) [zones]-Config — die dynamische # Handels-Range zeichnet der Indikator selbst aus den Chart-Kerzen (TF-genau). path = os.path.join(self._mql5_files_dir, "sr_levels.csv") diff --git a/core/hl_walls.py b/core/hl_walls.py index afcd766..2a7c005 100644 --- a/core/hl_walls.py +++ b/core/hl_walls.py @@ -34,6 +34,8 @@ log = get_logger("hlwalls") _TIMEOUT_S = 2.0 # der MT5-Loop darf nicht am HTTP hängen _TTL_S = 10.0 # Cache — die Wände wandern langsamer als der Export-Takt (~5 s) _MAX_AGE_S = 60.0 # älter = HL-Feed steht → nicht mehr zeichnen +_STALE_GRACE_S = 120.0 # nach einem Fehlschlag den letzten guten Stand so lange + # weiterreichen (Blink-Fix — s. Kommentar in `walls()`) class HLWalls: @@ -85,6 +87,20 @@ class HLWalls: f"Liquiditätslinien (HL-Dashboard auf 8001 läuft nicht?)") self._fail_logged = True with self._lock: + if data is not None: + self._last_good = data + self._last_good_ts = now + elif (getattr(self, "_last_good", None) is not None + and now - getattr(self, "_last_good_ts", 0.0) < _STALE_GRACE_S): + # ⚠ BLINK-FIX (2026-07-30, User „die Liquidationspfeile blinken nur + # manchmal auf"): ein einzelner Fehlschlag (HTTP-Timeout, HL-Server + # kurz busy, Snapshot minimal zu alt) darf die Pfeile NICHT löschen. + # Ohne den letzten guten Wert fehlen die LQ-Zeilen in der CSV → der + # Indikator löscht beim Redraw alle Objekte und zeichnet sie nicht neu + # → Pfeil weg, beim nächsten Erfolg wieder da = sichtbares Blinken. + # Innerhalb der Grace wird der letzte gute Stand weitergereicht; + # erst danach verschwinden die Pfeile wirklich (echter Ausfall). + data = self._last_good self._cache = data self._cache_ts = now return data diff --git a/core/wave_rec.py b/core/wave_rec.py index 32ed0d3..c8fb73e 100644 --- a/core/wave_rec.py +++ b/core/wave_rec.py @@ -80,6 +80,8 @@ _SQ_REFRESH_S = 5.0 # eigener, schneller M5-Fetch NUR für den Squeeze (entkoppe # raus (z. B. Short im M30-Aufwärtstrend). Nur für Basis-TF < M30 aktiv. _HTF = mt5.TIMEFRAME_M30 _HTF_LABEL = "M30" +_EQ_TOL_ATR = 0.15 # „gleich" für Equal Highs/Lows: beide Pivots innerhalb x×ATR +_EQ_MIN_H_ATR = 0.10 # Mindest-Bandhöhe der Liquiditätszone (sonst unsichtbar dünn) _HTF_DEADBAND = 0.15 # |EMA12−EMA50| < x×ATR(M30) → HTF gilt als neutral # Multi-TF-Konfluenz (nur Konfidenz/Anzeige): wenn M30 UND H1 die Richtung @@ -296,6 +298,10 @@ class WaveRecommender: if _lbl == "M5": self._m5_hl = (_h, _l) elif _lbl == "M30": + # Bar-ZEITEN mit ablegen — die Liquiditätszonen (Equal + # Highs/Lows) brauchen den Zeitpunkt der beiden Pivots, + # um im Chart als Band gezeichnet werden zu können. + self._m30_t = [int(b["time"]) for b in _tb] # M30-H/L für die S/R-LEVEL (Chart + P(break)-Auto-Close). # Gemessen 2026-07-30 (`backtest_level_tf.py`): M30-Level # schlagen M5 in BEIDEN Hälften (R-Ertrag ~2×, Level 1,95 @@ -398,6 +404,35 @@ class WaveRecommender: pl30 = [round(_ls[j], 3) for j in range(_K, len(_ls) - _K) if _ls[j] == min(_ls[j - _K:j + _K + 1])] self._pb_levels30 = {"ph": ph30, "pl": pl30} + # ── Liquiditätszonen: EQUAL HIGHS / EQUAL LOWS (SMC, User-Wunsch + # 2026-07-30 „zeichne mir die Liquiditätszonen wie im Bild ein"). + # Zwei ~gleiche M30-Pivots (innerhalb _EQ_TOL×ATR, 2–40 Bars + # auseinander) = Stop-Cluster. ⚠ REINE ANZEIGE — als SIGNAL gemessen + # und verworfen (`backtest_liquidity_sweep.py`: Equal Highs/Lows + # fallen in fast allen Varianten durch, der SMC-Exit sogar überall). + _a30 = turn_data.get("M30", (None, None))[1] if turn_data else None + _t30 = getattr(self, "_m30_t", None) + if _a30 and _a30 > 0 and _t30 and len(_t30) >= len(_hs): + _tol = _EQ_TOL_ATR * _a30 + _toff = len(_t30) - len(_hs) # Index-Versatz Fenster→Voll-Liste + def _equal(vals, want_high): + # jüngstes Paar zuerst suchen (rückwärts) + idx = [j for j in range(_K, len(vals) - _K) + if ((vals[j] == max(vals[j - _K:j + _K + 1])) if want_high + else (vals[j] == min(vals[j - _K:j + _K + 1])))] + for a in range(len(idx) - 1, 0, -1): + for b in range(a - 1, -1, -1): + gap = idx[a] - idx[b] + if gap < 2: continue + if gap > 40: break + if abs(vals[idx[a]] - vals[idx[b]]) <= _tol: + lo = min(vals[idx[a]], vals[idx[b]]) + hi = max(vals[idx[a]], vals[idx[b]]) + pad = max(0.0, (_EQ_MIN_H_ATR * _a30 - (hi - lo)) / 2) + return {"lo": round(lo - pad, 3), "hi": round(hi + pad, 3), + "t": int(_t30[idx[b] + _toff])} + return None + self._liq_zones = {"eqh": _equal(_hs, True), "eql": _equal(_ls, False)} # Multi-TF-Bounce: stärksten Status über M1/M5/M15/M30 wählen (H1 ist NUR # für die Ampel im turn_data, NICHT im gemessen-neutralen Bounce → skip). cands = [] @@ -420,6 +455,8 @@ class WaveRecommender: # M30-Level = Quelle für Chart-Linien UND P(break)-Auto-Close (gemessen besser); # `pb_levels` (M5) bleibt für das Entry-Raum-Gate. snap["pb_levels30"] = dict(self._pb_levels30) if getattr(self, "_pb_levels30", None) else None + # Liquiditätszonen (Equal Highs/Lows, M30) — REINE ANZEIGE (Signal verworfen) + snap["liq_zones"] = dict(self._liq_zones) if getattr(self, "_liq_zones", None) else None snap["squeeze"] = dict(self._squeeze) if getattr(self, "_squeeze", None) else \ {"state": None, "dir": None, "level": None, "box_atr": None} # ── Entry-Raum-Gate (gemessen `backtest_entryroom.py`, beide Hälften diff --git a/mql5/SR_Levels.mq5 b/mql5/SR_Levels.mq5 index cbd500e..c7e6da5 100644 --- a/mql5/SR_Levels.mq5 +++ b/mql5/SR_Levels.mq5 @@ -12,7 +12,21 @@ //| 4) Der Bot muss laufen (schreibt die CSV alle ~5 s). | //+------------------------------------------------------------------+ #property copyright "Oil Trading Bot" -#property version "1.15" +#property version "1.18" +// v1.18 (2026-07-30): NEU (User-Wuensche) — (a) LIQUIDITAETSZONEN: Equal Highs / +// Equal Lows aus den M30-Pivots als goldenes Band + gestrichelte Kante, +// nach rechts verlaengert (EQ;;lo;hi;zeit). REINE ANZEIGE, als Signal +// gemessen verworfen. (b) AKTUELLER KURS oben rechts, fett (PX;bid). +// v1.17 (2026-07-30): FIX „Liquiditaets-Pfeile werden nicht angezeigt" — zwei +// Ursachen: (1) ANCHOR_LEFT ist fuer OBJ_ARROW UNGUELTIG (nur ANCHOR_TOP/ +// ANCHOR_BOTTOM), (2) die Pfeile sitzen rechts NEBEN der letzten Kerze — +// ohne CHART_SHIFT gibt es dort keinen sichtbaren Bereich. Beides behoben. +// NEU: TRADE-MARKER (TR;;zeit) — die Kerze der Eroeffnung wird HELLGRUEN, +// die des Close DUNKELGRUEN umrandet (InpShowTrades/InpTradeOpenColor/ +// InpTradeCloseColor). +// v1.16 (2026-07-30): Beschriftung der Liquiditäts-Trendlinie per Default AUS +// (User: „die beschreibung der liquiditätslinie kann ausgeblendet werden") — +// Text steht jetzt im Tooltip der Linie; `InpLiqTrendLabel=true` holt sie zurück. // v1.15 (2026-07-30): CHART-THEME (User: „Farben im MT5 wie im Bild") — setzt beim // Start Hintergrund/Gitter/Kerzenfarben auf die Dashboard-Palette // (Bull #3B82F6, Bear #EF4444, BG #0D1117, Gitter #10151C dezent). @@ -56,6 +70,15 @@ input color InpWallBidColor = C'245,158,11'; // Liquidität Nachfrage (Bid) input color InpWallAskColor = C'245,158,11'; // Liquidität Angebot (Ask) — Gold input bool InpShowLiqTrend = true; // Liquiditäts-Trendlinie zeichnen input color InpLiqTrendColor = clrMagenta; // Liquiditäts-Trend (pink) +input bool InpLiqTrendLabel = false; // Beschriftung der Trendlinie (aus per User-Wunsch) +input bool InpShowTrades = true; // Trade-Kerzen umranden (Eroeffnung/Close) +input color InpTradeOpenColor = C'134,239,172'; // Eroeffnung — hellgruen +input color InpTradeCloseColor = C'21,128,61'; // Close — dunkelgruen +input bool InpShowLiqZones = true; // Liquiditaetszonen (Equal Highs/Lows) +input color InpLiqZoneColor = C'146,94,10'; // Zonen-Band (gedaempftes Gold) +input bool InpShowPrice = true; // aktuellen Kurs oben rechts anzeigen +input int InpPriceSize = 20; // Schriftgroesse des Kurses +input color InpPriceColor = C'230,237,243'; // Kurs-Farbe // ── Chart-THEME (User-Vorgabe 2026-07-30 „Farben im MT5 wie im Bild") ──────── // Setzt beim Start die Chart-Eigenschaften auf die Dashboard-Palette, damit MT5 // und die PWA gleich aussehen. Farben in MQL5-Notation C'R,G,B'. @@ -107,6 +130,10 @@ void ApplyTheme() ChartSetInteger(0, CHART_COLOR_ASK, InpThemeBear); ChartSetInteger(0, CHART_COLOR_STOP_LEVEL, C'239,68,68'); ChartSetInteger(0, CHART_SHOW_PERIOD_SEP, false); + // ⚠ CHART_SHIFT MUSS an sein: die Liquiditäts-Pfeile sitzen rechts NEBEN der + // letzten Kerze. Ohne Shift gibt es dort keinen sichtbaren Bereich → die Pfeile + // wären unsichtbar (das war der zweite Grund für „Pfeile werden nicht angezeigt"). + ChartSetInteger(0, CHART_SHIFT, true); ChartRedraw(); } //+------------------------------------------------------------------+ @@ -354,7 +381,10 @@ void Redraw() ObjectSetInteger(0, an, OBJPROP_ARROWCODE, (dirc == "U" ? 233 : 234)); ObjectSetInteger(0, an, OBJPROP_COLOR, lcol); ObjectSetInteger(0, an, OBJPROP_WIDTH, (isDom ? 4 : 2)); - ObjectSetInteger(0, an, OBJPROP_ANCHOR, ANCHOR_LEFT); + // ⚠ KEIN ANCHOR_LEFT: OBJ_ARROW unterstützt NUR ANCHOR_TOP/ANCHOR_BOTTOM + // — ein ungültiger Anker war (neben dem fehlenden Chart-Shift) der Grund, + // warum die Pfeile nicht erschienen (Fix 2026-07-30). + ObjectSetInteger(0, an, OBJPROP_ANCHOR, ANCHOR_TOP); ObjectSetInteger(0, an, OBJPROP_SELECTABLE, false); } else @@ -373,6 +403,116 @@ void Redraw() ObjectDelete(0, PFX + "TLQ_" + side); continue; } + // LIQUIDITÄTSZONE: EQ;;lo;hi; → goldenes Band über die + // Equal Highs / Equal Lows (M30), ab dem ersten Pivot nach rechts verlängert, + // plus gestrichelte Kante — Darstellung wie im Referenzbild des Users. + // ⚠ REINE ANZEIGE: als Signal gemessen und verworfen (backtest_liquidity_sweep.py). + if(typ == "EQ") + { + if(k < 5) continue; + if(!InpShowLiqZones) continue; + string zs = p[1]; + double zlo = StringToDouble(p[2]), zhi = StringToDouble(p[3]); + datetime zt = (datetime)StringToInteger(p[4]); + if(zlo <= 0 || zhi <= 0 || zt <= 0) continue; + datetime zend = iTime(_Symbol, PERIOD_CURRENT, 0) + (datetime)(PeriodSeconds() * 3); + string zn = PFX + "EQZ_" + zs; + if(ObjectCreate(0, zn, OBJ_RECTANGLE, 0, zt, zlo, zend, zhi)) + { + ObjectSetInteger(0, zn, OBJPROP_COLOR, InpLiqZoneColor); + ObjectSetInteger(0, zn, OBJPROP_STYLE, STYLE_SOLID); + ObjectSetInteger(0, zn, OBJPROP_WIDTH, 1); + ObjectSetInteger(0, zn, OBJPROP_FILL, true); + ObjectSetInteger(0, zn, OBJPROP_BACK, true); + ObjectSetInteger(0, zn, OBJPROP_SELECTABLE, false); + ObjectSetString (0, zn, OBJPROP_TOOLTIP, + (zs == "H" ? "Equal Highs - Sell-Side Liquidity" + : "Equal Lows - Buy-Side Liquidity")); + } + else + { + ObjectMove(0, zn, 0, zt, zlo); + ObjectMove(0, zn, 1, zend, zhi); + } + // gestrichelte Kante auf Höhe des Levels (wie im Bild) + string zl = PFX + "EQL_" + zs; + double edge = (zs == "H" ? zhi : zlo); + if(ObjectCreate(0, zl, OBJ_TREND, 0, zt, edge, zend, edge)) + { + ObjectSetInteger(0, zl, OBJPROP_COLOR, InpLiqZoneColor); + ObjectSetInteger(0, zl, OBJPROP_STYLE, STYLE_DASH); + ObjectSetInteger(0, zl, OBJPROP_WIDTH, 1); + ObjectSetInteger(0, zl, OBJPROP_RAY_RIGHT, true); + ObjectSetInteger(0, zl, OBJPROP_BACK, true); + ObjectSetInteger(0, zl, OBJPROP_SELECTABLE, false); + } + else + { + ObjectMove(0, zl, 0, zt, edge); + ObjectMove(0, zl, 1, zend, edge); + } + continue; + } + // AKTUELLER KURS oben rechts, fett (User-Wunsch): PX;bid + if(typ == "PX") + { + if(k < 2) continue; + if(!InpShowPrice) continue; + double pxv = StringToDouble(p[1]); + if(pxv <= 0) continue; + string pn = PFX + "PRICE"; + if(ObjectFind(0, pn) < 0) + { + ObjectCreate(0, pn, OBJ_LABEL, 0, 0, 0); + ObjectSetInteger(0, pn, OBJPROP_CORNER, CORNER_RIGHT_UPPER); + ObjectSetInteger(0, pn, OBJPROP_XDISTANCE, 12); + ObjectSetInteger(0, pn, OBJPROP_YDISTANCE, 14); + ObjectSetInteger(0, pn, OBJPROP_ANCHOR, ANCHOR_RIGHT_UPPER); + ObjectSetString (0, pn, OBJPROP_FONT, "Arial Black"); + ObjectSetInteger(0, pn, OBJPROP_FONTSIZE, InpPriceSize); + ObjectSetInteger(0, pn, OBJPROP_SELECTABLE, false); + } + ObjectSetString (0, pn, OBJPROP_TEXT, DoubleToString(pxv, (int)_Digits)); + ObjectSetInteger(0, pn, OBJPROP_COLOR, InpPriceColor); + continue; + } + // TRADE-MARKER: TR;; → die Kerze, in der eröffnet bzw. + // geschlossen wurde, wird umrandet (User-Vorgabe 2026-07-30): + // O = Eröffnung → HELLGRÜN · C = Close → DUNKELGRÜN + // Umrandung = OBJ_RECTANGLE über die Bar-Spanne (Zeit) und High/Low (Preis), + // ungefüllt. Die Zeit wird auf den Bar-Beginn der aktuellen Chart-TF gerundet, + // damit die Box exakt auf einer Kerze sitzt — egal auf welcher TF man schaut. + if(typ == "TR") + { + if(k < 3) continue; + if(!InpShowTrades) continue; + string tkind = p[1]; + datetime tts = (datetime)StringToInteger(p[2]); + if(tts <= 0) continue; + int bi = iBarShift(_Symbol, PERIOD_CURRENT, tts, false); + if(bi < 0) continue; // Zeit liegt außerhalb der History + datetime bt = iTime(_Symbol, PERIOD_CURRENT, bi); + double bh = iHigh(_Symbol, PERIOD_CURRENT, bi); + double bl = iLow (_Symbol, PERIOD_CURRENT, bi); + if(bh <= 0 || bl <= 0) continue; + double pad = (bh - bl) * 0.12; // etwas Luft um die Kerze + if(pad <= 0) pad = _Point * 10; + string tn2 = PFX + "TRD_" + tkind + "_" + IntegerToString((int)tts); + if(ObjectCreate(0, tn2, OBJ_RECTANGLE, 0, bt, bl - pad, + bt + PeriodSeconds(), bh + pad)) + { + ObjectSetInteger(0, tn2, OBJPROP_COLOR, + (tkind == "O" ? InpTradeOpenColor : InpTradeCloseColor)); + ObjectSetInteger(0, tn2, OBJPROP_STYLE, STYLE_SOLID); + ObjectSetInteger(0, tn2, OBJPROP_WIDTH, 2); + ObjectSetInteger(0, tn2, OBJPROP_FILL, false); + ObjectSetInteger(0, tn2, OBJPROP_BACK, false); + ObjectSetInteger(0, tn2, OBJPROP_SELECTABLE, false); + ObjectSetString (0, tn2, OBJPROP_TOOLTIP, + (tkind == "O" ? "Trade EROEFFNET" : "Trade GESCHLOSSEN")); + } + continue; + } // Liquiditäts-TRENDLINIE: LT;t1;p1;t2;p2; (pink, nach rechts verlängert). // Regression über den VOLUMENGEWICHTETEN Schwerpunkt der beiden Wände über die // letzten ~60 min: zeigt, wohin das Gewicht der RUHENDEN Orders wandert — @@ -404,21 +544,30 @@ void Redraw() ObjectMove(0, nm, 0, t1, q1); ObjectMove(0, nm, 1, t2, q2); } + // Beschriftung der Liquiditäts-Trendlinie: auf User-Wunsch (2026-07-30) + // per Default AUS — der Chart soll sauber bleiben. Die Richtung liest man + // an der Linie selbst ab; Details stehen im Tooltip der Linie. string tl = PFX + "TLIQTREND"; - string tt = "Liquiditäts-Trend " + (td == "U" ? CharToString(94) : CharToString(118)); - if(ObjectCreate(0, tl, OBJ_TEXT, 0, t2, q2)) + string tt = "Liquiditaets-Trend " + (td == "U" ? CharToString(94) : CharToString(118)); + ObjectSetString(0, nm, OBJPROP_TOOLTIP, tt); + if(InpLiqTrendLabel) { - ObjectSetString (0, tl, OBJPROP_TEXT, tt); - ObjectSetInteger(0, tl, OBJPROP_COLOR, InpLiqTrendColor); - ObjectSetInteger(0, tl, OBJPROP_FONTSIZE, 9); - ObjectSetInteger(0, tl, OBJPROP_ANCHOR, ANCHOR_LOWER); - ObjectSetInteger(0, tl, OBJPROP_SELECTABLE, false); + if(ObjectCreate(0, tl, OBJ_TEXT, 0, t2, q2)) + { + ObjectSetString (0, tl, OBJPROP_TEXT, tt); + ObjectSetInteger(0, tl, OBJPROP_COLOR, InpLiqTrendColor); + ObjectSetInteger(0, tl, OBJPROP_FONTSIZE, 9); + ObjectSetInteger(0, tl, OBJPROP_ANCHOR, ANCHOR_LOWER); + ObjectSetInteger(0, tl, OBJPROP_SELECTABLE, false); + } + else + { + ObjectMove(0, tl, 0, t2, q2); + ObjectSetString(0, tl, OBJPROP_TEXT, tt); + } } else - { - ObjectMove(0, tl, 0, t2, q2); - ObjectSetString(0, tl, OBJPROP_TEXT, tt); - } + ObjectDelete(0, tl); // evtl. Alt-Label aus v1.13–1.15 entfernen continue; } // ("Z;"-Zonen-Zeilen schreibt der Bot nicht mehr — die dynamische Handels-