MQL5 v1.48: Schaltflaechen links zum Ein-/Ausblenden der Objektgruppen

User-Wunsch. 13 OBJ_BUTTON am linken Chartrand, Klick ueber OnChartEvent:
S/R, Zonen, Kanal, Liq, LiqTr, EqH/L, Box, Kegel, Pfeile, Muster, Meldung,
Kurs, Trades. Gruen = an, grau = aus.

EIGENER PRAEFIX BPFX="SRBTN_": Redraw() ruft bei jedem Datei-Update
ObjectsDeleteAll(0, PFX) - unter PFX waeren die Knoepfe sofort wieder weg.
Nachgerechnet statt angenommen: "SRBTN_" beginnt NICHT mit "SRB_" (viertes
Zeichen T gegen _), die Praefix-Loeschung greift also nicht.

g_last wird beim Klick zurueckgesetzt: Redraw() steigt bei unveraendertem
Dateiinhalt sofort wieder aus, ohne den Reset waere das Umschalten erst beim
naechsten CSV-Update sichtbar und der Knopf saehe tot aus.

Voreinstellung UNTEN links, weil oben links der Meldungs-Stapel (MS;) steht.
InpBtnOben=true stellt um; die Stapelrichtung dreht mit, damit der erste Knopf
immer oben steht (bei CORNER_LEFT_LOWER waechst YDISTANCE nach oben).

Zustand je Symbol UND Periode in einer GlobalVariable - ueberlebt Neustart und
Zeitebenen-Wechsel und hat Vorrang vor der Eingabe. Die InpShow*-Eingaben
bleiben die Voreinstellung; Vis(i) ist danach die einzige Quelle fuer alle
Zeichen-Zweige.

OBJPROP_STATE bleibt immer false, sonst bliebe der Knopf optisch gedrueckt
haengen. BtnDraw() laeuft auch im Timer (Fenstergroesse, von Hand geloeschte
Objekte).

Neu ist, dass die S/R-Linien ueberhaupt abschaltbar sind - sie waren als
einzige Gruppe fest verdrahtet.

0 errors 0 warnings, Spiegel bitgleich, statisch geprueft: 13 Vis()-Guards,
genau ein OnChartEvent, keine Praefix-Kollision. Das Klick-Verhalten selbst ist
NICHT verifiziert - dafuer braucht es einen echten Klick im Chart.
This commit is contained in:
Axel Hocks
2026-08-21 16:04:10 +02:00
parent 72c2aedc1a
commit 911d5220b9
2 changed files with 228 additions and 16 deletions
+64
View File
@@ -9564,3 +9564,67 @@ stimmen; der Dreiecks-Apex landet korrekt in der Zukunft.
CSV-Zeile
`PT;M5;Doppeltop;bear;87.384;86.814;dreieck;1787327400;87.954;1787328000;87.384;1787330100;87.914`.
Abschaltbar wie bisher `InpShowMuster=false`; Füllfarbe `InpMusterFill`.
## ✅ SCHALTFLÄCHEN LINKS IM MT5-CHART (2026-08-21, v1.48)
User-Wunsch: die einzelnen Objektgruppen im Chart von Hand ein- und ausblenden.
**13 Knöpfe** (`OBJ_BUTTON`) am linken Rand, Klick über `OnChartEvent`:
`S/R · Zonen · Kanal · Liq · LiqTr · EqH/L · Box · Kegel · Pfeile · Muster ·
Meldung · Kurs · Trades`
Grün = an (mit `*`), grau = aus. Eingaben: `InpShowButtons` ·
`InpBtnOben` (Voreinstellung **unten** links) · `InpBtnX/Y` · `InpBtnBreite/Hoehe` ·
`InpBtnFont` · `InpBtnAnFarbe`/`InpBtnAusFarbe`.
⚠⚠ **EIGENER PRÄFIX — sonst wären die Knöpfe sofort wieder weg.** `Redraw()`
ruft bei jedem Datei-Update `ObjectsDeleteAll(0, PFX)`. Die Knöpfe liegen
deshalb unter **`BPFX = "SRBTN_"`** und werden nur in `OnDeinit` aufgeräumt.
✅ Geprüft, dass das wirklich trennt: `"SRBTN_"` beginnt **nicht** mit `"SRB_"`
(viertes Zeichen `T` gegen `_`) — die Präfix-Löschung greift also nicht. Das ist
knapp und war der erste Punkt, den ich nachgerechnet statt angenommen habe.
⚠⚠ **`g_last` MUSS beim Klick zurückgesetzt werden.** `Redraw()` steigt bei
unverändertem Dateiinhalt sofort wieder aus (`if(content == g_last) return;`) —
ohne den Reset würde ein Umschalten erst beim nächsten CSV-Update sichtbar, bis
zu Sekunden später. Es sähe aus, als reagiere der Knopf nicht.
**Voreinstellung UNTEN links**, weil oben links der Meldungs-Stapel (`MS;`)
steht — dort wären die Knöpfe eine Überdeckung. `InpBtnOben=true` stellt um; die
Stapelrichtung dreht dabei mit, damit der erste Knopf **immer oben** steht (bei
`CORNER_LEFT_LOWER` wächst `YDISTANCE` nach oben, die Liste liefe sonst verkehrt
herum).
**Der Zustand überlebt Neustart und Zeitebenen-Wechsel**: er liegt je Symbol
UND Periode in einer `GlobalVariable` (`SRB_<sym>_<period>_<key>`) und hat
Vorrang vor der Eingabe. Ohne das wäre jeder Chart-Wechsel ein Zurücksetzen von
dreizehn Knöpfen.
⚠ Die `InpShow*`-Eingaben bleiben die **Voreinstellung**; der Knopf ist danach
die Autorität. `Vis(i)` ist die **einzige** Quelle für alle Zeichen-Zweige — ein
zweiter Pfad wäre genau die Divergenz, die in diesem Projekt mehrfach
zugeschlagen hat.
**`OBJPROP_STATE` bleibt immer `false`**: sonst bliebe der Knopf optisch
„gedrückt" hängen und arbeitete gegen die Hintergrundfarbe.
**`BtnDraw()` läuft auch im Timer** — das Chart-Fenster kann seine Größe
ändern, und ein von Hand gelöschtes Objekt baut sich so wieder auf.
✅ Neu ist, dass die **S/R-Linien überhaupt abschaltbar** sind — sie waren als
einzige Gruppe fest verdrahtet.
✅ Kompiliert ins richtige Terminal, `0 errors, 0 warnings`, Spiegel
`mql5/SR_Levels.mq5` bitgleich. Statisch geprüft: 13 `Vis()`-Guards, genau ein
`OnChartEvent`, keine Präfix-Kollision.
**NICHT verifiziert ist das Klick-Verhalten selbst** — dafür braucht es einen
echten Klick im Chart. Compile und Verdrahtung sind belegt, die Wirkung nicht.
⚠⚠ **BEIM SCHREIBEN DIESES ABSCHNITTS EIN NEUER FALL DER SCHREIB-FALLE — und
die Größenprüfung hat ihn NICHT gefangen.** Der Doku-Patch wandelte `\n` nach
`\r\n` auf dem **gesamten** Dokument statt nur auf dem angehängten Block. Die
bereits vorhandenen `\r\n` wurden dadurch zu **`\r\r\n`** — 9.567 Zeilen,
+9,5 KB. Die Datei wuchs also, die Prüfung `neu > alt` war zufrieden.
✅ Aufgefallen nur, weil die Zunahme (12,5 KB) nicht zum geschriebenen Text
(~3 KB) passte; repariert durch `\r\r\n``\r\n`, kein Inhalt verloren.
**➜ Regel: die Zeilenenden-Umwandlung IMMER nur auf den neuen Text anwenden,
nie auf das eingelesene Dokument.** Und: **eine Größenprüfung belegt nur, dass
nichts abgeschnitten wurde — nicht, dass der Inhalt stimmt** (dieselbe Klasse
wie „eine LEERE Datei besteht `node --check`"). Wo es billig ist, gehört die
erwartete Zunahme mitgeprüft, nicht nur ihr Vorzeichen.
+164 -16
View File
@@ -12,7 +12,7 @@
//| 4) Der Bot muss laufen (schreibt die CSV alle ~5 s). |
//+------------------------------------------------------------------+
#property copyright "Oil Trading Bot"
#property version "1.47"
#property version "1.48"
// v1.42 (2026-08-19): Y-Stapel oben rechts GEMESSEN statt geschaetzt. Der
// Faktor 1,8 aus v1.41 war nur die naechste Schaetzung; jetzt liefert
// TextGetSize die TATSAECHLICHE Pixelhoehe (LabelH). Ausserdem wird Y bei
@@ -283,6 +283,146 @@ input color InpMusterFill = C'205,205,205';// Fuellung der Musterform
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)
//+------------------------------------------------------------------+
//| SCHALTFLAECHEN links (User-Vorgabe 2026-08-21): jede Objektgruppe |
//| von Hand ein-/ausblenden. |
//| ⚠⚠ EIGENER PRAEFIX. `Redraw()` ruft `ObjectsDeleteAll(0, PFX)` — |
//| laegen die Knoepfe unter PFX, waeren sie bei jedem Datei-Update |
//| weg. Sie leben deshalb unter BPFX und werden NUR in OnDeinit |
//| aufgeraeumt. |
//| ⚠ Der Zustand ueberlebt Neustart und Timeframe-Wechsel: er liegt |
//| je Symbol UND Periode in einer GlobalVariable. Ohne das waere |
//| jedes Umschalten des Charts ein Zuruecksetzen — und der Nutzer |
//| muesste zwoelf Knoepfe neu stellen. |
//+------------------------------------------------------------------+
input bool InpShowButtons = true; // Schaltflaechen links einblenden
// ⚠ Standard ist UNTEN links: oben links stapelt der Indikator die
// Meldungen (MS;) — dort waeren die Knoepfe eine Ueberdeckung.
input bool InpBtnOben = false; // Knoepfe oben statt unten links
input int InpBtnX = 4; // Abstand vom linken Rand (px)
input int InpBtnY = 22; // Abstand vom Rand (px)
input int InpBtnBreite = 66; // Knopfbreite (px)
input int InpBtnHoehe = 17; // Knopfhoehe (px)
input int InpBtnFont = 8; // Schriftgroesse
input color InpBtnAnFarbe = C'58,116,84'; // Hintergrund AN
input color InpBtnAusFarbe = C'64,64,68'; // Hintergrund AUS
#define BTN_N 13
#define B_SR 0
#define B_ZO 1
#define B_KA 2
#define B_LQ 3
#define B_LT 4
#define B_EQ 5
#define B_CO 6
#define B_CN 7
#define B_AR 8
#define B_PT 9
#define B_MS 10
#define B_PX 11
#define B_TR 12
const string BPFX = "SRBTN_";
string g_bkey[BTN_N];
string g_blbl[BTN_N];
bool g_bvis[BTN_N];
//| Sichtbar? Einzige Quelle fuer alle Zeichen-Zweige.
bool Vis(int i) { return (i >= 0 && i < BTN_N) ? g_bvis[i] : true; }
string BtnGV(int i) { return "SRB_" + _Symbol + "_"
+ IntegerToString((int)_Period) + "_" + g_bkey[i]; }
void BtnInit()
{
// ⚠ Element-weise, NICHT als Array-Initialisierer: `input`-Variablen sind
// in MQL5 keine Konstanten, ein `bool x[N] = {InpA, InpB}` kompiliert nicht.
g_bkey[B_SR] = "SR"; g_blbl[B_SR] = "S/R"; g_bvis[B_SR] = true;
g_bkey[B_ZO] = "ZO"; g_blbl[B_ZO] = "Zonen"; g_bvis[B_ZO] = InpShowZonen;
g_bkey[B_KA] = "KA"; g_blbl[B_KA] = "Kanal"; g_bvis[B_KA] = InpShowChannel;
g_bkey[B_LQ] = "LQ"; g_blbl[B_LQ] = "Liq"; g_bvis[B_LQ] = InpShowWalls;
g_bkey[B_LT] = "LT"; g_blbl[B_LT] = "LiqTr"; g_bvis[B_LT] = InpShowLiqTrend;
g_bkey[B_EQ] = "EQ"; g_blbl[B_EQ] = "EqH/L"; g_bvis[B_EQ] = InpShowLiqZones;
g_bkey[B_CO] = "CO"; g_blbl[B_CO] = "Box"; g_bvis[B_CO] = InpShowConsol;
g_bkey[B_CN] = "CN"; g_blbl[B_CN] = "Kegel"; g_bvis[B_CN] = InpShowCone;
g_bkey[B_AR] = "AR"; g_blbl[B_AR] = "Pfeile"; g_bvis[B_AR] = InpShowArrows;
g_bkey[B_PT] = "PT"; g_blbl[B_PT] = "Muster"; g_bvis[B_PT] = InpShowMuster;
g_bkey[B_MS] = "MS"; g_blbl[B_MS] = "Meldung"; g_bvis[B_MS] = InpShowMeldungen;
g_bkey[B_PX] = "PX"; g_blbl[B_PX] = "Kurs"; g_bvis[B_PX] = InpShowPrice;
g_bkey[B_TR] = "TR"; g_blbl[B_TR] = "Trades"; g_bvis[B_TR] = InpShowTrades;
// Gemerkter Zustand hat Vorrang vor der Eingabe.
for(int i = 0; i < BTN_N; i++)
{
string gv = BtnGV(i);
if(GlobalVariableCheck(gv)) g_bvis[i] = (GlobalVariableGet(gv) > 0.5);
}
}
void BtnPaint(int i)
{
string nm = BPFX + g_bkey[i];
ObjectSetInteger(0, nm, OBJPROP_BGCOLOR,
g_bvis[i] ? InpBtnAnFarbe : InpBtnAusFarbe);
ObjectSetString(0, nm, OBJPROP_TEXT,
(g_bvis[i] ? "* " : " ") + g_blbl[i]);
// ⚠ STATE bewusst immer FALSE: sonst bliebe der Knopf optisch
// "gedrueckt" haengen und wuerde gegen die Hintergrundfarbe arbeiten.
ObjectSetInteger(0, nm, OBJPROP_STATE, false);
}
void BtnDraw()
{
if(!InpShowButtons) { ObjectsDeleteAll(0, BPFX); return; }
int h = MathMax(10, InpBtnHoehe);
for(int i = 0; i < BTN_N; i++)
{
string nm = BPFX + g_bkey[i];
if(ObjectFind(0, nm) < 0)
{
if(!ObjectCreate(0, nm, OBJ_BUTTON, 0, 0, 0)) continue;
ObjectSetInteger(0, nm, OBJPROP_CORNER,
InpBtnOben ? CORNER_LEFT_UPPER : CORNER_LEFT_LOWER);
ObjectSetInteger(0, nm, OBJPROP_XSIZE, InpBtnBreite);
ObjectSetInteger(0, nm, OBJPROP_YSIZE, h);
ObjectSetInteger(0, nm, OBJPROP_FONTSIZE, InpBtnFont);
ObjectSetInteger(0, nm, OBJPROP_COLOR, clrWhite);
ObjectSetInteger(0, nm, OBJPROP_BORDER_COLOR, C'90,90,96');
ObjectSetInteger(0, nm, OBJPROP_SELECTABLE, false);
ObjectSetInteger(0, nm, OBJPROP_ZORDER, 100);
}
ObjectSetInteger(0, nm, OBJPROP_XDISTANCE, InpBtnX);
// Stapelrichtung so, dass der ERSTE Knopf immer OBEN steht — bei
// CORNER_LEFT_LOWER waechst YDISTANCE nach oben, die Liste liefe
// sonst verkehrt herum.
int rang = InpBtnOben ? i : (BTN_N - 1 - i);
ObjectSetInteger(0, nm, OBJPROP_YDISTANCE, InpBtnY + rang * (h + 2));
BtnPaint(i);
}
}
//| Klick auf eine Schaltflaeche → Gruppe umschalten.
//| ⚠⚠ `g_last` MUSS zurueckgesetzt werden: `Redraw()` steigt sonst bei
//| unveraendertem Dateiinhalt sofort wieder aus, und das Umschalten
//| wuerde erst beim naechsten Datei-Update sichtbar (bis zu Sekunden
//| spaeter — es saehe aus, als reagiere der Knopf nicht).
void OnChartEvent(const int id, const long &lparam,
const double &dparam, const string &sparam)
{
if(id != CHARTEVENT_OBJECT_CLICK) return;
if(StringFind(sparam, BPFX) != 0) return;
for(int i = 0; i < BTN_N; i++)
{
if(sparam != BPFX + g_bkey[i]) continue;
g_bvis[i] = !g_bvis[i];
GlobalVariableSet(BtnGV(i), g_bvis[i] ? 1.0 : 0.0);
BtnPaint(i);
g_last = "\x01"; // vollen Neuaufbau erzwingen
Redraw(); DrawRange(); RepositionLabels();
ChartRedraw();
return;
}
}
// Kegel: Verkettung der Horizonte (30->60->120). Die CSV liefert sie aufsteigend;
// jeder Abschnitt startet dort, wo der vorherige endete -> ein sich oeffnender
// Faecher statt drei unverbundener Geraden. Wird je Redraw zurueckgesetzt.
@@ -314,9 +454,11 @@ int LabelH(const string font, const int size)
int OnInit()
{
if(InpApplyTheme) ApplyTheme();
BtnInit();
EventSetTimer(MathMax(1, InpRefreshS));
Redraw();
DrawRange();
BtnDraw();
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
@@ -433,10 +575,13 @@ void OnDeinit(const int reason)
EventKillTimer();
ObjectsDeleteAll(0, PFX);
ObjectsDeleteAll(0, RPFX);
ObjectsDeleteAll(0, BPFX);
ChartRedraw();
}
//+------------------------------------------------------------------+
void OnTimer() { Redraw(); DrawRange(); RepositionLabels(); }
// ⚠ `BtnDraw()` auch im Timer: das Chart-Fenster kann seine Groesse aendern,
// und ein Nutzer kann ein Objekt von Hand loeschen — dann baut es sich neu auf.
void OnTimer() { Redraw(); DrawRange(); RepositionLabels(); BtnDraw(); }
//+------------------------------------------------------------------+
//| Zeitpunkt der horizontalen Chart-Mitte (sichtbarer Bereich) |
//+------------------------------------------------------------------+
@@ -619,6 +764,9 @@ void Redraw()
// Horizontale S/R-Linien + sichtbares Text-Label an der Linie
if(typ == "R" || typ == "S")
{
// ⚠ Neu 2026-08-21: die S/R-Linien waren als einzige Gruppe nicht
// abschaltbar. Der Knopf ist die einzige Quelle dafuer.
if(!Vis(B_SR)) continue;
double price = StringToDouble(p[1]);
if(price <= 0) continue;
// Feld 3 (optional) = P(Durchbruch) in % aus dem kalibrierten Modell des
@@ -677,7 +825,7 @@ void Redraw()
// nach rechts verlängert. Anker in Broker-Zeit (= Server-Zeit im Terminal).
if(typ == "CH")
{
if(!InpShowChannel) continue;
if(!Vis(B_KA)) continue;
if(k < 6) continue;
string kind = p[1];
datetime t1 = (datetime)StringToInteger(p[2]);
@@ -709,7 +857,7 @@ void Redraw()
if(typ == "LQ")
{
if(k < 3) continue;
if(!InpShowWalls) continue;
if(!Vis(B_LQ)) continue;
string side = p[1];
double lpx = StringToDouble(p[2]);
double lsz = (k > 3) ? StringToDouble(p[3]) : 0.0;
@@ -760,7 +908,7 @@ void Redraw()
if(typ == "EQ")
{
if(k < 5) continue;
if(!InpShowLiqZones) continue;
if(!Vis(B_EQ)) continue;
string zs = p[1];
double zlo = StringToDouble(p[2]), zhi = StringToDouble(p[3]);
datetime zt = (datetime)StringToInteger(p[4]);
@@ -815,7 +963,7 @@ void Redraw()
if(typ == "CO")
{
if(k < 3) continue;
if(!InpShowConsol) continue;
if(!Vis(B_CO)) continue;
double clo = StringToDouble(p[1]), chi = StringToDouble(p[2]);
bool tight = (k > 3 && p[3] == "1");
if(clo <= 0 || chi <= clo) continue;
@@ -865,7 +1013,7 @@ void Redraw()
if(typ == "CN")
{
if(k < 4) continue;
if(!InpShowCone) continue;
if(!Vis(B_CN)) continue;
int mins = (int)StringToInteger(p[1]);
double clo = StringToDouble(p[2]);
double chi = StringToDouble(p[3]);
@@ -916,7 +1064,7 @@ void Redraw()
if(typ == "AR")
{
if(k < 5) continue;
if(!InpShowArrows) continue;
if(!Vis(B_AR)) continue;
string art = p[1];
if(art == "K" && !InpShowConsensus) continue; // Konsens-Pfeil optional
double a_from = StringToDouble(p[2]);
@@ -966,7 +1114,7 @@ void Redraw()
if(typ == "HL")
{
if(k < 6) continue;
if(!InpShowHL) continue;
if(!InpShowHL || !Vis(B_PX)) continue;
double hlp = StringToDouble(p[1]);
if(hlp <= 0) continue;
int hage = (int)StringToInteger(p[2]);
@@ -1006,7 +1154,7 @@ void Redraw()
// Kegel-Startpunkt IMMER merken - auch wenn das Kurs-Label aus ist
// (sonst verliert der Faecher seinen Anker).
g_conePx = pxv;
if(!InpShowPrice) continue;
if(!Vis(B_PX)) continue;
string pn = PFX + "PRICE";
if(ObjectFind(0, pn) < 0)
{
@@ -1050,7 +1198,7 @@ void Redraw()
// dem noch nichts gebrochen ist.
if(typ == "PT")
{
if(!InpShowMuster || k < 6) continue;
if(!Vis(B_PT) || k < 6) continue;
// ⚠⚠ `_Period`, NICHT `PERIOD_CURRENT`. PERIOD_CURRENT ist der Wert 0 —
// MT5-Funktionen wie iTime() deuten das als "dieser Chart", ein
// `switch` trifft damit aber KEINEN case und TFName() gab
@@ -1149,7 +1297,7 @@ void Redraw()
}
if(typ == "RZ" || typ == "SZ")
{
if(!InpShowZonen || k < 3) continue;
if(!Vis(B_ZO) || k < 3) continue;
double zlo = StringToDouble(p[1]), zhi = StringToDouble(p[2]);
if(zlo <= 0 || zhi <= 0) continue;
string zn = PFX + "ZONE_" + typ;
@@ -1190,7 +1338,7 @@ void Redraw()
// stehen — genau der Fehler, den die HL-Pfeile schon einmal hatten).
if(typ == "MS")
{
if(!InpShowMeldungen || k < 2) continue;
if(!Vis(B_MS) || k < 2) continue;
string mn = PFX + "MSG_" + IntegerToString(msg_idx);
if(ObjectFind(0, mn) < 0)
{
@@ -1242,7 +1390,7 @@ void Redraw()
if(typ == "PL")
{
if(k < 2) continue;
if(!InpShowPnL) continue;
if(!InpShowPnL || !Vis(B_PX)) continue;
double plv = StringToDouble(p[1]);
string cur = (k > 2) ? p[2] : "";
string qn = PFX + "PNL";
@@ -1276,7 +1424,7 @@ void Redraw()
if(typ == "TR")
{
if(k < 3) continue;
if(!InpShowTrades) continue;
if(!Vis(B_TR)) continue;
string tkind = p[1];
datetime tts = (datetime)StringToInteger(p[2]);
if(tts <= 0) continue;
@@ -1312,7 +1460,7 @@ void Redraw()
if(typ == "LT")
{
if(k < 5) continue;
if(!InpShowLiqTrend) continue;
if(!Vis(B_LT)) continue;
datetime t1 = (datetime)StringToInteger(p[1]);
double q1 = StringToDouble(p[2]);
datetime t2 = (datetime)StringToInteger(p[3]);