diff --git a/CLAUDE.md b/CLAUDE.md index d271e84..af29c9e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -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___`) 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. diff --git a/mql5/SR_Levels.mq5 b/mql5/SR_Levels.mq5 index 6e6dce4..9866273 100644 --- a/mql5/SR_Levels.mq5 +++ b/mql5/SR_Levels.mq5 @@ -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]);