Files
AH-Oil-Trader/mql5/SR_Levels.mq5
T
Axel HocksandClaude Opus 4.8 7f2285ddb3 MT5-Chart: echte Liquiditaets-Waende statt Chartmuster-Linien (Indikator v1.12)
User-Wunsch: "zeichne mir die liquiditaetslinien in den mt5 ein. entferne die
chartmuster linien aus dem mt5".

- core/hl_walls.py: holt GET /api/walls vom HL-Dashboard (groesstes L2-Level je
  Seite, basis-korrigiert auf CFD-Niveau). Fail-safe: HL aus -> keine Linien, kein
  Fehler; Cache 10s, Daten >60s alt werden verworfen, Log nur 1x.
- engine._write_levels_file: PT-Zeilen raus, LQ;<B|A>;preis;size rein.
- SR_Levels.mq5 v1.12: PT-Handler entfernt, LQ-Handler (dick gestrichelt +
  Groessen-Label), InpShowWalls/InpWallBidColor/InpWallAskColor. Kompiliert.
- config: export_hl_walls=true, hl_walls_url.

Zwei Fallen dokumentiert: (1) Basis-Differenz HL vs CFD ~0,45$ -- ungerechnet laegen
die Linien falsch. (2) 127.0.0.1 statt localhost (IPv6 -> 2066ms statt 19-46ms, lief
in den 2s-Timeout).

Verifiziert: LQ-Zeilen erscheinen in sr_levels.csv (B 83.619/3120, A 83.665/1721),
plausibel zwischen R 83.721 und S 83.516.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-30 15:16:19 +02:00

306 lines
14 KiB
Plaintext

//+------------------------------------------------------------------+
//| 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 <Terminal>\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;<symbol> — 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;<U|M|L>;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;<B|A>;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();
}
//+------------------------------------------------------------------+