Files
AH-Oil-Trader/mql5/SR_Levels.mq5
T
Axel HocksandClaude Opus 4.8 06b37a1474 Liquiditaets-Waende: Richtungs-Pfeil + pinke Liquiditaets-Trendlinie (v1.13)
User-Wunsch: "Kennzeiche die Liquiditaetslinie mit einem Pfeil in welche richtung
die linie den Kurs treibt. Zeichne mir eine Trendlnie in pink im mt5 auf basis
der liquiditaet ein".

(a) Pfeil je Wand in Wirkrichtung (Lesart "die Wand haelt": Nachfrage stuetzt hoch,
    Angebot deckelt runter); groessere Wand dicker + "<<". CSV-Format erweitert:
    LQ;<B|A>;preis;size;<U|D>;<1|0>.
    WICHTIG: Pfeilrichtung ist INTERPRETATION, nicht gemessen -- die Gegenthese
    ("Liquidity Grab", Kurs laeuft ZUR Wand) ist genauso verbreitet. Klaert
    analyze_bookflow.py Frage 3.

(b) Pinke Liquiditaets-Trendlinie LT;t1;p1;t2;p2;<U|D>: Regression ueber den
    VOLUMENGEWICHTETEN Wand-Schwerpunkt der letzten 60 min. Zeigt, wohin das
    Gewicht der RUHENDEN Orders wandert -- nicht der gehandelte Kurs.
    Zeiten im Bot in Broker-Zeit umgerechnet, Preise basis-korrigiert.

HL /api/walls liefert jetzt arrow_bid/arrow_ask/dominant/trend.
Verifiziert: LQ;B;84.182;2441;U;1 / LQ;A;84.223;2441;D;0 /
LT;1785427175;84.135;1785430664;83.978;D in sr_levels.csv. Indikator kompiliert.

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

384 lines
18 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.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
// 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)
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)
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);
// 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))
{
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);
}
// 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;<U|D> (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-
// Range zeichnet DrawRange() selbst aus den Chart-Kerzen.)
}
ChartRedraw();
}
//+------------------------------------------------------------------+