diff --git a/CLAUDE.md b/CLAUDE.md index f076ea4..b593ad6 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1106,8 +1106,9 @@ dort bereits nachvalidiert, ØR +0,305.) **Eine** Oberfläche: 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 `\MQL5\Files\sr_levels.csv` (`SYM;…` / `R;preis` / `S;preis` / **`CH;; - t1;p1;t2;p2`** = Regressionskanal-Anker / **`LQ;;preis;size`** = ECHTE - Liquiditäts-Wand, s. u.); Quelle **`_draw_levels`** (S/R) + + t1;p1;t2;p2`** = Regressionskanal-Anker / **`LQ;;preis;size;;<1|0>`** = ECHTE + Liquiditäts-Wand + Pfeilrichtung + Dominanz / **`LT;t1;p1;t2;p2;`** = + 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 @@ -1124,6 +1125,22 @@ dort bereits nachvalidiert, ØR +0,305.) **Eine** Oberfläche: `bid_mt5`/`ask_mt5` an; fehlt die Basis, wird NICHT gezeichnet. ⚠ **`127.0.0.1`, nicht `localhost`** (IPv6-Auflösung → 2066 ms statt 19–46 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, diff --git a/core/engine.py b/core/engine.py index 08b5ee0..58deeec 100644 --- a/core/engine.py +++ b/core/engine.py @@ -1940,11 +1940,25 @@ class TradingEngine: # basis-korrigiert auf CFD-Niveau) — Format LQ;;preis;size. # Anders als R/S NICHT aus Pivots geschätzt, sondern tatsächlich ruhende # Orders. REINE ANZEIGE (Übertragbarkeit auf den CFD wird gerade gemessen). + # Format: LQ;;preis;size;;<1|0> (Pfeil-Richtung = Wirkrichtung + # der Wand nach der Lesart „sie hält"; letztes Feld 1 = die GRÖSSERE der + # beiden Wände). Zusätzlich LT;t1;p1;t2;p2; = Liquiditäts-Trendlinie + # (Regression über den volumengewichteten Wand-Schwerpunkt) — Zeiten + # werden hier in BROKER-Zeit umgerechnet, weil MT5-Charts die brauchen. try: w = self.hl_walls.walls() if w: - lines.append(f"LQ;B;{w['bid']};{w['bid_sz']:.0f}") - lines.append(f"LQ;A;{w['ask']};{w['ask_sz']:.0f}") + dom = w.get("dominant") + ab = "U" if (w.get("arrow_bid") == "up") else "D" + aa = "D" if (w.get("arrow_ask") == "down") else "U" + lines.append(f"LQ;B;{w['bid']};{w['bid_sz']:.0f};{ab};{1 if dom=='B' else 0}") + lines.append(f"LQ;A;{w['ask']};{w['ask_sz']:.0f};{aa};{1 if dom=='A' else 0}") + tr = w.get("trend") + if tr and tr.get("t1") and tr.get("t2"): + boff = self.trader._broker_offset_s() + lines.append(f"LT;{int(tr['t1'])+boff};{tr['p1']};" + f"{int(tr['t2'])+boff};{tr['p2']};" + f"{'U' if tr.get('dir')=='up' else 'D'}") except Exception: pass # Zonen NICHT mehr aus der (statischen) [zones]-Config — die dynamische diff --git a/core/hl_walls.py b/core/hl_walls.py index 681d32a..afcd766 100644 --- a/core/hl_walls.py +++ b/core/hl_walls.py @@ -67,7 +67,17 @@ class HLWalls: data = {"bid": float(d["bid_mt5"]), "ask": float(d["ask_mt5"]), "bid_sz": float(d.get("bid_sz") or 0), "ask_sz": float(d.get("ask_sz") or 0), - "basis": d.get("basis"), "imb5": d.get("imb5")} + "basis": d.get("basis"), "imb5": d.get("imb5"), + # Wirkrichtung je Wand (Pfeil im Chart) + größere Wand. + # ⚠ Lesart „die Wand hält" — INTERPRETATION, nicht gemessen. + "arrow_bid": d.get("arrow_bid") or "up", + "arrow_ask": d.get("arrow_ask") or "down", + "dominant": d.get("dominant"), + # Liquiditäts-Trendlinie (Regression über den volumen- + # gewichteten Wand-Schwerpunkt): {t1,p1,t2,p2,dir,slope_per_h} + # — Zeiten sind LOKALE Epoch, der Aufrufer rechnet sie in + # Broker-Zeit um (MT5-Charts brauchen Broker-Zeit). + "trend": d.get("trend")} self._fail_logged = False except Exception as e: if not self._fail_logged: # nur 1× melden, nicht jeden Tick diff --git a/mql5/SR_Levels.mq5 b/mql5/SR_Levels.mq5 index 32f81c4..92f2bdf 100644 --- a/mql5/SR_Levels.mq5 +++ b/mql5/SR_Levels.mq5 @@ -12,7 +12,14 @@ //| 4) Der Bot muss laufen (schreibt die CSV alle ~5 s). | //+------------------------------------------------------------------+ #property copyright "Oil Trading Bot" -#property version "1.12" +#property version "1.13" +// v1.13 (2026-07-30): Liquiditäts-Wände bekommen einen PFEIL in ihrer Wirkrichtung +// (Lesart „die Wand hält" — Nachfrage stützt hoch, Angebot deckelt runter; +// die GRÖSSERE Wand wird dicker gezeichnet + "<<"). NEU: pinke +// LIQUIDITÄTS-TRENDLINIE (LT;…) = Regression über den volumengewichteten +// Wand-Schwerpunkt der letzten ~60 min — zeigt, wohin das Gewicht der +// RUHENDEN Orders wandert (nicht der gehandelte Kurs). +// ⚠ Pfeilrichtung ist INTERPRETATION, nicht gemessen (s. analyze_bookflow.py). // v1.12 (2026-07-30): Chartmuster-Linien (PT;…) ENTFERNT (User-Wunsch, Chart war zu // voll — Kachel/PWA-Anzeige bleibt). NEU: ECHTE Liquiditäts-Wände (LQ;…) aus // dem Hyperliquid-L2-Orderbuch, basis-korrigiert auf CFD-Niveau — die einzigen @@ -36,6 +43,8 @@ input color InpChanColor = clrYellow; // Kanal-Farbe input bool InpShowWalls = true; // echte Liquiditäts-Wände (Hyperliquid-Orderbuch) input color InpWallBidColor = clrMediumSpringGreen; // Nachfrage-Wand (Bid, Käufer) input color InpWallAskColor = clrOrangeRed; // Angebots-Wand (Ask, Verkäufer) +input bool InpShowLiqTrend = true; // Liquiditäts-Trendlinie zeichnen +input color InpLiqTrendColor = clrMagenta; // Liquiditäts-Trend (pink) const string PFX = "SRB_"; // Präfix S/R-Linien (aus Datei) const string RPFX = "SRR_"; // Präfix dynamische Range (aus Chart) @@ -279,8 +288,16 @@ void Redraw() } else ObjectMove(0, nm, 0, 0, lpx); - string llbl = (isBid ? "Liquidität NACHFRAGE " : "Liquidität ANGEBOT ") - + DoubleToString(lsz, 0); + // Pfeil = Wirkrichtung der Wand (Feld 4: U/D). Lesart „die Wand HÄLT": + // Nachfrage-Wand stützt von unten (hoch), Angebots-Wand deckelt (runter). + // Feld 5 = 1 bei der GRÖSSEREN der beiden Wände → dickere Linie + "<<". + string dirc = (k > 4) ? p[4] : (isBid ? "U" : "D"); + bool isDom = (k > 5 && p[5] == "1"); + string arrow = (dirc == "U" ? CharToString(94) : CharToString(118)); // ^ / v + if(isDom) + ObjectSetInteger(0, nm, OBJPROP_WIDTH, 3); + string llbl = arrow + " " + (isBid ? "Liquidität NACHFRAGE " : "Liquidität ANGEBOT ") + + DoubleToString(lsz, 0) + (isDom ? " <<" : ""); string ltn = PFX + "TLQ_" + side; if(ObjectCreate(0, ltn, OBJ_TEXT, 0, CenterTime(), lpx)) { @@ -295,6 +312,67 @@ void Redraw() ObjectMove(0, ltn, 0, CenterTime(), lpx); ObjectSetString(0, ltn, OBJPROP_TEXT, llbl); } + // Echtes Pfeil-Objekt am rechten Rand (Wingdings 233 hoch / 234 runter) — + // deutlicher sichtbar als das Textzeichen im Label. + string an = PFX + "ALQ_" + side; + datetime atime = CenterTime() + (datetime)(PeriodSeconds() * 6); + if(ObjectCreate(0, an, OBJ_ARROW, 0, atime, lpx)) + { + ObjectSetInteger(0, an, OBJPROP_ARROWCODE, (dirc == "U" ? 233 : 234)); + ObjectSetInteger(0, an, OBJPROP_COLOR, lcol); + ObjectSetInteger(0, an, OBJPROP_WIDTH, (isDom ? 3 : 2)); + ObjectSetInteger(0, an, OBJPROP_SELECTABLE, false); + } + else + ObjectMove(0, an, 0, atime, lpx); + 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 — + // NICHT wohin der gehandelte Kurs läuft. Steigt sie, ziehen die Käufer ihre + // großen Gebote hoch. Zeiten kommen bereits in Broker-Zeit aus dem Bot. + if(typ == "LT") + { + if(k < 5) continue; + if(!InpShowLiqTrend) continue; + datetime t1 = (datetime)StringToInteger(p[1]); + double q1 = StringToDouble(p[2]); + datetime t2 = (datetime)StringToInteger(p[3]); + double q2 = StringToDouble(p[4]); + string td = (k > 5) ? p[5] : ""; + if(t1 <= 0 || t2 <= 0 || q1 <= 0 || q2 <= 0) continue; + string nm = PFX + "LIQTREND"; + if(ObjectCreate(0, nm, OBJ_TREND, 0, t1, q1, t2, q2)) + { + ObjectSetInteger(0, nm, OBJPROP_COLOR, InpLiqTrendColor); + ObjectSetInteger(0, nm, OBJPROP_STYLE, STYLE_SOLID); + ObjectSetInteger(0, nm, OBJPROP_WIDTH, 2); + ObjectSetInteger(0, nm, OBJPROP_RAY_RIGHT, true); + ObjectSetInteger(0, nm, OBJPROP_RAY_LEFT, false); + ObjectSetInteger(0, nm, OBJPROP_BACK, true); + ObjectSetInteger(0, nm, OBJPROP_SELECTABLE, false); + } + else + { + ObjectMove(0, nm, 0, t1, q1); + ObjectMove(0, nm, 1, t2, q2); + } + string tl = PFX + "TLIQTREND"; + string tt = "Liquiditäts-Trend " + (td == "U" ? CharToString(94) : CharToString(118)); + 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); + } continue; } // ("Z;"-Zonen-Zeilen schreibt der Bot nicht mehr — die dynamische Handels-