diff --git a/CLAUDE.md b/CLAUDE.md index 63b9c76..9ab89d5 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -9661,3 +9661,46 @@ erreichbar schlägt schön, und der Normalfall ist eine Spalte. ✅ `0 errors, 0 warnings`, `.ex5` frisch, Spiegel bitgleich. ⚠ Wer noch größer will, dreht `InpBtnBreite`/`InpBtnHoehe`/`InpBtnFont` weiter — der Spalten-Rückfall fängt zu hohe Stapel ab. + +### ⚠⚠ „Knöpfe blinken kurz groß auf und sind dann wieder klein" — der Chart trug den Indikator DREIMAL (2026-08-22, v1.50) + +User-Meldung beim Zeitebenen-Wechsel. **Nicht die Größe war das Problem.** +Beleg aus dem Terminal-Journal (`/logs/20260822.log`) — je Wechsel +**drei** identische Zeilen im selben Zehntelsekunden-Fenster: +``` +13:19:16.071 custom indicator SR_Levels (SpotCrude,M5) loaded succesfully +13:19:16.072 custom indicator SR_Levels (SpotCrude,M5) loaded succesfully +13:19:16.072 custom indicator SR_Levels (SpotCrude,M5) loaded succesfully +``` +**Auf dem Chart lagen DREI Instanzen.** Jede hat ihren **eigenen gespeicherten +Eingabesatz**: die vor v1.49 angehängten trugen noch `66/17/8`, die neue +`132/34/16`. Alle drei schrieben im Sekundentakt auf **dieselben** Objektnamen — +also wechselte die Größe permanent hin und her. + +⚠⚠ **Das ist die Falle hinter dem Symptom: doppelte Instanzen sind von außen +unsichtbar**, solange nichts schreibend kollidiert. Sie kosteten hier ausserdem +das Dreifache an CSV-Lesevorgängen und Objekt-Auf-/Abbau je Sekunde. +**➜ Beim nächsten unerklärlichen Chart-Verhalten ZUERST die Ladezeilen im +Journal zählen.** Das kostet eine Minute und hätte hier drei Hypothesen erspart. + +✅ **Fix im Code: nur EINE Instanz zeichnet die Knöpfe.** Anspruch je Chart über +eine temporäre `GlobalVariable` (`SRB_BTNOWN_`), gesetzt mit +**`GlobalVariableSetOnCondition`** — also **atomar**. Ein Check-dann-Set hätten +bei praktisch gleichzeitigem Start alle drei gewonnen. Nicht-Inhaber steigen in +`BtnDraw()` sofort aus und melden **einmalig** im Experten-Log, dass eine zweite +Instanz läuft (sichtbar machen statt still schlucken). +⚠ **`OnDeinit` räumt die Knöpfe nur noch als Inhaber weg** und gibt den Anspruch +frei — sonst hätte eine beendete Zweit-Instanz die Knöpfe der noch laufenden +ersten gelöscht. Fällt der Inhaber weg, holt sich der nächste Timer-Durchlauf +einer anderen Instanz den Anspruch. + +✅ **Zweite Absicherung: die Größe heilt sich selbst.** `BtnDraw()` legt einen +Knopf **neu an**, wenn seine gespeicherte `XSIZE`/`YSIZE`/`FONTSIZE` nicht mehr +zum Soll passt. Reines Nachsetzen der Eigenschaften (v1.49) reichte in der +Praxis nicht. + +⚠ **Der eigentliche Fix liegt beim Nutzer:** die doppelten Instanzen entfernen +(Rechtsklick auf den Chart → *Indikatorenliste* → alle `SR_Levels` bis auf eines +löschen). Der Code-Guard verhindert nur den sichtbaren Schaden, nicht die +dreifache Last. +✅ `0 errors, 0 warnings`, `.ex5` frisch, Spiegel bitgleich. diff --git a/mql5/SR_Levels.mq5 b/mql5/SR_Levels.mq5 index 38b98a4..8b0e282 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.49" +#property version "1.50" // 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 @@ -334,6 +334,43 @@ 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; } +//+------------------------------------------------------------------+ +//| ⚠⚠ NUR EINE INSTANZ DARF DIE KNOEPFE ZEICHNEN (v1.50). | +//| Real am 2026-08-22: der Chart trug SR_Levels DREIMAL (im Journal | +//| belegt: drei "loaded succesfully" je Zeitebenen-Wechsel). Jede | +//| Instanz hat ihren EIGENEN gespeicherten Eingabesatz — die vor | +//| v1.49 angehaengten trugen noch 66/17/8, die neue 132/34/16. Alle | +//| drei schrieben im Sekundentakt auf DIESELBEN Objektnamen, also | +//| wechselte die Groesse staendig hin und her: "blinken kurz gross | +//| auf und sind dann wieder klein". | +//| ⚠ Der Anspruch wird ATOMAR gesetzt (GlobalVariableSetOnCondition) | +//| — drei Instanzen starten praktisch gleichzeitig, ein | +//| Check-dann-Set haetten alle drei gewonnen. | +//| ⚠ Faellt der Inhaber weg (OnDeinit gibt frei), holt sich der | +//| naechste Timer-Durchlauf einer anderen Instanz den Anspruch. | +//+------------------------------------------------------------------+ +double g_stamp = 0.0; // eigene Kennung (nie 0) +bool g_isowner = false; +bool g_warned = false; + +string BtnOwnGV() { return "SRB_BTNOWN_" + IntegerToString(ChartID()); } + +bool BtnClaim() +{ + if(g_isowner) return true; + string gv = BtnOwnGV(); + if(!GlobalVariableCheck(gv)) GlobalVariableTemp(gv); // legt mit 0 an + g_isowner = GlobalVariableSetOnCondition(gv, g_stamp, 0.0); + if(!g_isowner && !g_warned) + { + g_warned = true; + Print("SR_Levels: weitere Instanz auf diesem Chart — die Knoepfe " + "zeichnet nur die erste. Doppelte Instanzen bitte entfernen " + "(Rechtsklick > Indikatorenliste)."); + } + return g_isowner; +} + string BtnGV(int i) { return "SRB_" + _Symbol + "_" + IntegerToString((int)_Period) + "_" + g_bkey[i]; } @@ -376,6 +413,7 @@ void BtnPaint(int i) void BtnDraw() { + if(!BtnClaim()) return; // eine andere Instanz zeichnet if(!InpShowButtons) { ObjectsDeleteAll(0, BPFX); return; } int h = MathMax(10, InpBtnHoehe); // ⚠⚠ SPALTEN-RUECKFALL. Mit der 200-%-Groesse ist der Stapel 490 px hoch; @@ -390,6 +428,14 @@ void BtnDraw() for(int i = 0; i < BTN_N; i++) { string nm = BPFX + g_bkey[i]; + // ⚠ Auch NEU ANLEGEN, wenn die Groesse nicht mehr passt: ein von einer + // frueheren Fassung erzeugter Knopf behaelt sonst seine alten Masse. + // Reines Nachsetzen der Eigenschaften reichte in der Praxis nicht. + bool falsch = (ObjectFind(0, nm) >= 0) + && ((int)ObjectGetInteger(0, nm, OBJPROP_XSIZE) != InpBtnBreite + || (int)ObjectGetInteger(0, nm, OBJPROP_YSIZE) != h + || (int)ObjectGetInteger(0, nm, OBJPROP_FONTSIZE) != InpBtnFont); + if(falsch) ObjectDelete(0, nm); if(ObjectFind(0, nm) < 0) { if(!ObjectCreate(0, nm, OBJ_BUTTON, 0, 0, 0)) continue; @@ -469,6 +515,9 @@ int LabelH(const string font, const int size) int OnInit() { if(InpApplyTheme) ApplyTheme(); + // Kennung: nie 0 (0 heisst "frei"), pro Instanz verschieden. + g_stamp = (double)(GetTickCount() % 1000000) + 1.0 + (double)(MathRand() % 997) / 1000.0; + BtnClaim(); BtnInit(); EventSetTimer(MathMax(1, InpRefreshS)); Redraw(); @@ -590,7 +639,13 @@ void OnDeinit(const int reason) EventKillTimer(); ObjectsDeleteAll(0, PFX); ObjectsDeleteAll(0, RPFX); - ObjectsDeleteAll(0, BPFX); + // ⚠ NUR der Inhaber raeumt die Knoepfe weg — sonst loescht eine + // beendete Zweit-Instanz die Knoepfe der noch laufenden ersten. + if(g_isowner) + { + ObjectsDeleteAll(0, BPFX); + GlobalVariableSet(BtnOwnGV(), 0.0); // Anspruch freigeben + } ChartRedraw(); } //+------------------------------------------------------------------+