//+------------------------------------------------------------------+ //| SR_Levels.mq5 | //| Zeichnet Support-/Resistance-Linien + Zonen (Ranges), die der | //| Python-Bot (Oil Trading Server) nach MQL5\Files\sr_levels.csv | //| schreibt. Das Python-MetaTrader5-Paket kann selbst KEINE Chart- | //| Objekte erzeugen — daher diese Datei-Bridge. | //| | //| Installation: | //| 1) Diese Datei nach \MQL5\Indicators\ kopieren. | //| 2) In MetaEditor öffnen und mit F7 kompilieren. | //| 3) Im Navigator auf den WTI-Chart (SpotCrude) ziehen. | //| 4) Der Bot muss laufen (schreibt die CSV alle ~5 s). | //+------------------------------------------------------------------+ #property copyright "Oil Trading Bot" #property version "1.12" // 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 // Linien im Chart, die nicht aus Pivots geschätzt sind, sondern tatsächlich // ruhende Orders zeigen (dick gestrichelt + Größen-Label). Reine Anzeige. // v1.11: Chartmuster-Linien (jetzt entfernt). // ⚠ nach dem Update den Indikator NEU mit F7 kompilieren. #property indicator_chart_window #property indicator_plots 0 input string InpFile = "sr_levels.csv"; // Datei in MQL5\Files input int InpRefreshS = 2; // Aktualisierung (Sekunden) input color InpResColor = clrDodgerBlue; // Widerstand (blau kräftig) input color InpSupColor = clrDeepSkyBlue; // Support (blau hell) input bool InpShowRange = false; // dynamische Handels-Range zeichnen (aus per User-Wunsch) input int InpRangeBars = 50; // Range = Hoch/Tief der letzten N Kerzen input color InpRangeColor = clrGoldenrod; // Range-Farbe input bool InpRangeFill = false; // Range füllen (aus = nur Umriss) input bool InpShowChannel = true; // Regressionskanal (M30) zeichnen 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) const string PFX = "SRB_"; // Präfix S/R-Linien (aus Datei) const string RPFX = "SRR_"; // Präfix dynamische Range (aus Chart) string g_last = "\x01"; // letzter Datei-Inhalt (gegen Flackern) bool g_range_drawn = false; // hat DIESE Instanz die Range gezeichnet? //+------------------------------------------------------------------+ int OnInit() { EventSetTimer(MathMax(1, InpRefreshS)); Redraw(); DrawRange(); return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ void OnDeinit(const int reason) { EventKillTimer(); ObjectsDeleteAll(0, PFX); ObjectsDeleteAll(0, RPFX); ChartRedraw(); } //+------------------------------------------------------------------+ void OnTimer() { Redraw(); DrawRange(); RepositionLabels(); } //+------------------------------------------------------------------+ //| Zeitpunkt der horizontalen Chart-Mitte (sichtbarer Bereich) | //+------------------------------------------------------------------+ datetime CenterTime() { long first = ChartGetInteger(0, CHART_FIRST_VISIBLE_BAR); long width = ChartGetInteger(0, CHART_WIDTH_IN_BARS); int mid = (int)MathMax(0, first - width / 2); return iTime(_Symbol, PERIOD_CURRENT, mid); } //+------------------------------------------------------------------+ //| Hält die S/R-Beschriftungen beim Scrollen/Zoomen mittig | //+------------------------------------------------------------------+ void RepositionLabels() { datetime ct = CenterTime(); int total = ObjectsTotal(0, 0, OBJ_TEXT); for(int i = 0; i < total; i++) { string nm = ObjectName(0, i, 0, OBJ_TEXT); if(StringFind(nm, PFX + "T") == 0) { double pr = ObjectGetDouble(0, nm, OBJPROP_PRICE); ObjectMove(0, nm, 0, ct, pr); } } ChartRedraw(); } //+------------------------------------------------------------------+ //| Dynamische Handels-Range = Hoch/Tief der letzten N Kerzen des | //| AKTUELLEN Charts (TF-genau). In-place via ObjectMove → kein | //| Flackern, passt sich an M1/M5/…/H1 automatisch an. | //+------------------------------------------------------------------+ void DrawRange() { string nm = RPFX + "BOX"; if(!InpShowRange) { // Nur löschen, was DIESE Instanz gezeichnet hat — sonst kämpfen zwei // Instanzen (eine an/eine aus) um die Box → Blinken im 2-s-Takt. if(g_range_drawn) { ObjectDelete(0, nm); g_range_drawn = false; ChartRedraw(); } return; } int total = Bars(_Symbol, PERIOD_CURRENT); if(total < 5) return; int n = InpRangeBars; if(n > total - 1) n = total - 1; int hi_i = iHighest(_Symbol, PERIOD_CURRENT, MODE_HIGH, n, 0); int lo_i = iLowest (_Symbol, PERIOD_CURRENT, MODE_LOW, n, 0); if(hi_i < 0 || lo_i < 0) return; double hi = iHigh(_Symbol, PERIOD_CURRENT, hi_i); double lo = iLow (_Symbol, PERIOD_CURRENT, lo_i); datetime t0 = iTime(_Symbol, PERIOD_CURRENT, n - 1); datetime t1 = iTime(_Symbol, PERIOD_CURRENT, 0) + PeriodSeconds(); if(ObjectFind(0, nm) < 0) { ObjectCreate(0, nm, OBJ_RECTANGLE, 0, t0, lo, t1, hi); ObjectSetInteger(0, nm, OBJPROP_COLOR, InpRangeColor); ObjectSetInteger(0, nm, OBJPROP_STYLE, STYLE_SOLID); ObjectSetInteger(0, nm, OBJPROP_WIDTH, 1); ObjectSetInteger(0, nm, OBJPROP_FILL, InpRangeFill); ObjectSetInteger(0, nm, OBJPROP_BACK, true); ObjectSetInteger(0, nm, OBJPROP_SELECTABLE, false); ObjectSetString (0, nm, OBJPROP_TEXT, "Range " + IntegerToString(n)); } else { ObjectMove(0, nm, 0, t0, lo); // linke untere Ecke ObjectMove(0, nm, 1, t1, hi); // rechte obere Ecke } g_range_drawn = true; ChartRedraw(); } //+------------------------------------------------------------------+ //| Pflicht-Funktion jedes Indikators — hier No-op (wir zeichnen | //| über OnTimer aus der Datei, nicht aus den Kursreihen). | //+------------------------------------------------------------------+ int OnCalculate(const int rates_total, const int prev_calculated, const datetime &time[], const double &open[], const double &high[], const double &low[], const double &close[], const long &tick_volume[], const long &volume[], const int &spread[]) { return(rates_total); } //+------------------------------------------------------------------+ void Redraw() { int h = FileOpen(InpFile, FILE_READ|FILE_TXT|FILE_ANSI|FILE_SHARE_READ|FILE_SHARE_WRITE); if(h == INVALID_HANDLE) return; // Datei (noch) nicht da → nächster Tick // Ganze Datei in einen String lesen und mit dem letzten Stand vergleichen — // nur bei ÄNDERUNG neu zeichnen (sonst flackert der delete/recreate alle 2 s). string content = ""; while(!FileIsEnding(h)) content += FileReadString(h) + "\n"; FileClose(h); if(content == g_last) return; // nichts geändert → nicht anfassen g_last = content; ObjectsDeleteAll(0, PFX); string rows[]; int nr = StringSplit(content, '\n', rows); int idx = 0; for(int r = 0; r < nr; r++) { string line = rows[r]; if(StringLen(line) < 2) continue; string p[]; int k = StringSplit(line, ';', p); if(k < 2) continue; string typ = p[0]; // 1. Zeile: SYM; — nur auf dem passenden Chart zeichnen if(typ == "SYM") { if(p[1] != _Symbol) { ObjectsDeleteAll(0, PFX); ChartRedraw(); return; } continue; } // Horizontale S/R-Linien + sichtbares Text-Label an der Linie if(typ == "R" || typ == "S") { double price = StringToDouble(p[1]); if(price <= 0) continue; string lbl = (typ=="R" ? "Widerstand " : "Unterstützung ") + DoubleToString(price, (int)_Digits); color col = (typ=="R" ? InpResColor : InpSupColor); string nm = PFX + typ + "_" + IntegerToString(idx); if(ObjectCreate(0, nm, OBJ_HLINE, 0, 0, price)) { ObjectSetInteger(0, nm, OBJPROP_COLOR, col); ObjectSetInteger(0, nm, OBJPROP_STYLE, STYLE_DASH); ObjectSetInteger(0, nm, OBJPROP_WIDTH, 1); ObjectSetInteger(0, nm, OBJPROP_BACK, true); ObjectSetInteger(0, nm, OBJPROP_SELECTABLE, false); ObjectSetString (0, nm, OBJPROP_TEXT, lbl); // Tooltip } // Beschriftung als eigenes Text-Objekt: horizontal ZENTRIERT im sicht- // baren Chart, direkt über der Linie (bleibt via RepositionLabels() // auch beim Scrollen/Zoomen zentriert) string tn = PFX + "T" + typ + "_" + IntegerToString(idx); if(ObjectCreate(0, tn, OBJ_TEXT, 0, CenterTime(), price)) { ObjectSetString (0, tn, OBJPROP_TEXT, lbl); ObjectSetInteger(0, tn, OBJPROP_COLOR, col); ObjectSetInteger(0, tn, OBJPROP_FONTSIZE, 8); ObjectSetInteger(0, tn, OBJPROP_ANCHOR, ANCHOR_LOWER); // zentriert über dem Punkt ObjectSetInteger(0, tn, OBJPROP_BACK, false); ObjectSetInteger(0, tn, OBJPROP_SELECTABLE, false); } idx++; } // Regressionskanal: CH;;t1;p1;t2;p2 → diagonale Trendlinie (OBJ_TREND), // nach rechts verlängert. Anker in Broker-Zeit (= Server-Zeit im Terminal). if(typ == "CH") { if(!InpShowChannel) continue; if(k < 6) continue; string kind = p[1]; datetime t1 = (datetime)StringToInteger(p[2]); double pr1 = StringToDouble(p[3]); datetime t2 = (datetime)StringToInteger(p[4]); double pr2 = StringToDouble(p[5]); if(pr1 <= 0 || pr2 <= 0 || t1 <= 0 || t2 <= 0) continue; string nm = PFX + "CH_" + kind; if(ObjectCreate(0, nm, OBJ_TREND, 0, t1, pr1, t2, pr2)) { ObjectSetInteger(0, nm, OBJPROP_COLOR, InpChanColor); ObjectSetInteger(0, nm, OBJPROP_STYLE, (kind == "M" ? STYLE_DOT : STYLE_DASH)); ObjectSetInteger(0, nm, OBJPROP_WIDTH, 1); ObjectSetInteger(0, nm, OBJPROP_RAY_RIGHT, true); // nach rechts verlängern ObjectSetInteger(0, nm, OBJPROP_RAY_LEFT, false); ObjectSetInteger(0, nm, OBJPROP_BACK, true); ObjectSetInteger(0, nm, OBJPROP_SELECTABLE, false); ObjectSetString (0, nm, OBJPROP_TEXT, "Kanal " + kind); } continue; } // ECHTE Liquiditäts-Wände: LQ;;preis;size (B = Nachfrage/Bid-Wand, // A = Angebot/Ask-Wand). Quelle = Hyperliquid-L2-Orderbuch, größtes Level je // Seite, im Bot BASIS-KORRIGIERT auf CFD-Niveau (HL notiert ~0,45 $ tiefer). // ⚠ Das ist die EINZIGE Linie im Chart, die nicht aus Pivots geschätzt ist, // sondern tatsächlich ruhende Orders zeigt. Dick + gestrichelt, damit sie sich // klar von den dünnen S/R-Linien abhebt. REINE ANZEIGE (kein Signal). // (Chartmuster-Linien „PT;…" wurden 2026-07-30 auf User-Wunsch entfernt.) if(typ == "LQ") { if(k < 3) continue; if(!InpShowWalls) continue; string side = p[1]; double lpx = StringToDouble(p[2]); double lsz = (k > 3) ? StringToDouble(p[3]) : 0.0; if(lpx <= 0) continue; bool isBid = (side == "B"); color lcol = isBid ? InpWallBidColor : InpWallAskColor; string nm = PFX + "LQ_" + side; if(ObjectCreate(0, nm, OBJ_HLINE, 0, 0, lpx)) { ObjectSetInteger(0, nm, OBJPROP_COLOR, lcol); ObjectSetInteger(0, nm, OBJPROP_STYLE, STYLE_DASH); ObjectSetInteger(0, nm, OBJPROP_WIDTH, 2); ObjectSetInteger(0, nm, OBJPROP_BACK, true); ObjectSetInteger(0, nm, OBJPROP_SELECTABLE, false); } else ObjectMove(0, nm, 0, 0, lpx); string llbl = (isBid ? "Liquidität NACHFRAGE " : "Liquidität ANGEBOT ") + DoubleToString(lsz, 0); string ltn = PFX + "TLQ_" + side; if(ObjectCreate(0, ltn, OBJ_TEXT, 0, CenterTime(), lpx)) { ObjectSetString (0, ltn, OBJPROP_TEXT, llbl); ObjectSetInteger(0, ltn, OBJPROP_COLOR, lcol); ObjectSetInteger(0, ltn, OBJPROP_FONTSIZE, 9); ObjectSetInteger(0, ltn, OBJPROP_ANCHOR, ANCHOR_LOWER); ObjectSetInteger(0, ltn, OBJPROP_SELECTABLE, false); } else { ObjectMove(0, ltn, 0, CenterTime(), lpx); ObjectSetString(0, ltn, OBJPROP_TEXT, llbl); } continue; } // ("Z;"-Zonen-Zeilen schreibt der Bot nicht mehr — die dynamische Handels- // Range zeichnet DrawRange() selbst aus den Chart-Kerzen.) } ChartRedraw(); } //+------------------------------------------------------------------+