Vom Neuling zum Experten: Hilfsprogramm zur Parametersteuerung
Inhalt:
Einführung
Heute bauen wir weiter auf dem Fundament auf, das wir in unserem letzten Artikel gelegt haben. Wenn Sie uns gefolgt sind, werden Sie sich daran erinnern, dass wir einen Indikator entwickelt haben, mit dem sich Zeiträume mit höherem Zeitrahmen (Higher-Timeframe, HTF) direkt auf Charts mit niedrigerem Zeitrahmen darstellen lassen. Dieses Konzept hat sich als leistungsfähiges Analysewerkzeug erwiesen, das die komplizierten Kursbewegungen, die in den größeren Balken der höheren Zeitrahmen verborgen sind, aufzeigt.
Solche Details sind für einen Händler von unschätzbarem Wert. Was beispielsweise auf einem höheren Zeitrahmen als einfacher Docht erscheint, kann bei einer Untersuchung auf einem niedrigeren Zeitrahmen deutliche Muster, Unterstützungs- und Widerstandsniveaus oder sogar Orderblöcke offenbaren. Das Verständnis dieser internen Marktstruktur ermöglicht es den Händlern, das künftige Preisverhalten besser vorherzusehen und ihre Strategien zu verfeinern. In der Abbildung 1 unten zeigt die H1-Kerzenperiode A auf der linken Seite ein Widerstandsniveau innerhalb ihres Aufwärtsdochts – den Ablehnungsbereich – der später von der Periode B auf der rechten Seite getestet und respektiert wurde. Diese Wechselwirkung verdeutlicht, wie vergangene Dochtzonen als aussagekräftige Bezugspunkte für künftige Kursreaktionen dienen können.

Abb. 1. Einblicke in die H1-Periode bei M1 mit dem Marker Periods Synchronizer
Eine Herausforderung bei der Entwicklung komplexer Tools ist jedoch das Fehlen von intuitiven, leicht zugänglichen Bedienelementen. Bisher haben wir uns bei der Anpassung von Parametern auf die Registerkarte „Eingaben“ eines EA oder Indikators verlassen – ein Prozess, der schnell mühsam werden kann, wenn mehrere Werte eingestellt oder mit visuellen Einstellungen experimentiert werden.
In diesem Projekt stellen wir uns dieser Herausforderung, indem wir ein Echtzeit-Steuerungsprogramm entwickeln – einen Expert Advisor (EA), der statische Eingabeparameter in interaktive On-Chart-Steuerungen umwandelt. Dieses „Market Period Synchronizer Control Utility“ erweitert das frühere Indikatorenkonzept zu einem dynamischen Dashboard mit fortschrittlichen Funktionen, das sofortiges Feedback und einen effizienteren analytischen Arbeitsablauf bietet.
Die wichtigsten Vorteile des neuen Hilfsprogramms
- Sofortiger Parameterzugriff – Passen Sie die Tasteneinstellungen direkt im Chart an, ohne den Eigenschaftsdialog zu öffnen.
- Visuelle Aktualisierungen in Echtzeit – Sehen Sie sofortige Änderungen an Objekten, Farben und Zeitrahmen, wenn Sie Steuerelemente ändern.
- Beschleunigte Analyse – Eliminieren Sie sich wiederholende Schritte des Speicherns und erneuten Ladens und straffen Sie Ihren Arbeitsablauf.
- Moderne visuelle Schnittstelle – Verwendet CCanvas für glatte, halbtransparente und visuell ansprechende Panels.
- Multi-Timeframe-Synchronisation – Problemlose Anzeige und Kontrolle von Haupt- und Nebenzeitstrukturen.
- Interaktive Schieberegler – schnelles und einfaches Einstellen der Werte wie Breite und Aktualisierungsintervalle.
- Umschalter – Aktivieren oder Deaktivieren der Funktionen sofort mit einem einzigen Klick.
Im Folgenden wird veranschaulicht, was wir am Ende dieser Diskussion erreichen wollen. Danach tauchen wir in die Implementierungsphase ein, in der wir die einzelnen Entwicklungsschritte aufschlüsseln, die grundlegende Codestruktur erläutern und mit der Bereitstellung und den Leistungstests abschließen.

Abb. 2. Ein Screenshot der Funktionen der Parametersteuerung.
Umsetzung
Wir werden einen modularen Expert Advisor implementieren, der statische Eingaben in ein On-Chart-Dashboard umwandelt. Der EA (1) liest HTF-Balken (Zeit, Öffnen, Schließen), (2) zeichnet HTF-Hintergrundgrafiken (vertikale Linien, optionale Körperfüllungen, horizontale Öffnungs-/Schließungslinien) und (3) stellt Laufzeitsteuerungen über eine halbtransparente Leinwand (canvas) und Chart-Objekte (Schaltflächen, Beschriftungen, vertikale Schieberegler, Pop-up-TF-Dropdowns) bereit. Die Nutzeroberfläche aktualisiert Laufzeit-Zustandsvariablen (veränderbare Kopien von Eingaben), und RefreshLines() verwendet diese Werte, um Chart-Objekte sofort zu aktualisieren. Das Design legt den Schwerpunkt auf Übersichtlichkeit (UI-Container + Beschriftungen), Reaktionsfähigkeit (Schieberegler werden sofort aktualisiert) und Störungsfreiheit (HTF-Objekte werden mit OBJPROP_BACK=true erstellt, damit die UI anklickbar bleibt).
Wichtige Hinweise zur Umsetzung, die der Leser nicht verpassen sollte:
- Eingaben werden nur als Standardwerte behandelt – das Dashboard ändert Laufzeitkopien (mit dem Präfix g_), damit die Interaktionen auf der Nutzeroberfläche bestehen bleiben, während der EA ausgeführt wird.
- Schieberegler sind vertikal und mit zwei Chart-Schaltflächen implementiert: eine Spur (visuell) und ein Knopf (auswählbar) – durch Ziehen des Knopfes wird der zugrunde liegende Wert in Echtzeit aktualisiert.
- HTF-Formen werden im Hintergrund gezeichnet (OBJPROP_BACK = true), um zu verhindern, dass Mausereignisse von der Nutzeroberfläche gestohlen werden.
- OnTick() prüft nur Zeitänderungen (über iTime), um unnötige Neuaufbauten zu vermeiden.
1) Kopfzeile, Includes und Nutzereingaben – Zweck und Verhalten
Wir beginnen damit, Canvas.mqh einzubinden und alle Eingabeparameter zu deklarieren. Die Zeile #include <Canvas/Canvas.mqh> zieht eine kleine UI-Helferklasse (CCanvas) ein, mit der wir eine halbtransparente Hintergrund-Bitmap für unser Dashboard zeichnen. Wir tun dies, weil Chart-Objekte allein (Schaltflächen/Beschriftungen) auf verschiedenen Chart-Themen klobig aussehen; die Leinwand bietet einen einzigen, konsistenten Container, den wir einmal gestalten und wiederverwenden können. Beachten Sie, dass Canvas.mqh im Include-Pfad vorhanden sein muss – andernfalls lässt sich der EA nicht kompilieren. Wir verwenden auch #property strict, damit der Compiler moderne MQL-Typ- und Signaturregeln durchsetzt.
Der Eingabeblock ist bewusst in drei praktische Kategorien gegliedert, damit die Nutzer sofort verstehen, wo sie suchen müssen und wie das Tool mit Handelskonzepten zusammenpasst. Die erste Kategorie – Hauptzeitrahmen und visuelle Darstellung – enthält den Zeitrahmen, der die markierten Hauptbalken (z. B. H1) definiert, eine Rückblickstiefe (wie viele HTF-Balken wir zeichnen), eine Standardfarbe und eine Standardbreite für die vertikalen Haupttrennlinien sowie ein Aktualisierungsintervall in Sekunden. Diese Einstellungen legen die primäre Struktur fest: Haupttakte bilden den Rahmen, in dem alles andere (Füllungen, offene/geschlossene Horizontale und Nebentakte) kontextualisiert wird. Der Rückblick ist besonders wichtig, da er steuert, wie viele Hauptperiodenobjekte erstellt werden können. Ein großer Rückblick kann viele Objekte erstellen und die Charts verlangsamen, daher empfehlen wir vernünftige Standardwerte (200) und Maximalwerte in einem Produktions-Build.
Die zweite Kategorie befasst sich mit den Öffnungs-/Schließungsmarkierungen und den Körperfüllungen. Es gibt Umschaltmöglichkeiten und Farben für die horizontalen Markierungen zum Öffnen und Schließen (nützlich, um zu sehen, wo jeder HTF-Balken im Verhältnis zum unteren Zeitrahmen geöffnet und geschlossen wurde), Breite und Linienstil für diese Horizontalen, einen Offset in den aktuellen HTF-Balken, um zu steuern, wie weit die Horizontale reicht, sowie Boolesche Werte und Farben für die Füllung des Körpers (Bulle/Bär). Diese sind optional, aber aussagekräftig: Die Füllungen bieten einen schnellen Überblick über Auf- und Abwärts-Balken höherer Zeitrahmen, während die Eröffnungs-/Schlusslinien uns helfen zu erkennen, wo die innere Balkenstruktur eines HTF-Balkens oder der Dochte Unterstützung/Widerstand geschaffen hat. Im Code speichern wir diese als Eingabevorgaben (unveränderlich) und kopieren sie in Laufzeitvariablen, damit das Dashboard sie live umschalten kann.
Die dritte Kategorie umfasst kleinere Zeiträume. Es gibt zwei Minor-TF-Optionen (Minor1, Minor2), jeweils mit einem Kippschalter, einer Zeitrahmenauswahl, einer Farbe und einer Breite. Der Zweck besteht darin, die Zwischenstruktur innerhalb des HTF sichtbar zu machen: Bei einem H1-Dur kann beispielsweise ein M15-Moll die internen Unterteilungen zeigen. Der EA zeichnet nur dann kleine Vertikalen, wenn die kleine Zeit genau zwischen zwei aufeinanderfolgende große Zeiten fällt (oder innerhalb des aktuellen großen Balkens) – dieses Verhalten entspricht der ursprünglichen Logik des Indikators. Durch die Aktivierung von zwei separaten Nebenschichten können wir verschachtelte Strukturen (z. B. H1 H1 major, M30 minor, M15 micro-minor) gleichzeitig sehen.
Ein entscheidender Architekturpunkt und ein häufiger Fallstrick: Eingabeparameter in MQL sind Kompilierzeitkonstanten für die Laufzeit – sie können vom EA während der Ausführung nicht neu zugewiesen werden. Um ein wirklich interaktives Dashboard zu bieten, kopieren wir daher jede Eingabe in eine entsprechende g_ (global mutable) Variable während OnInit. Unser UI-Code aktualisiert die g_-Variablen (z. B. g_WidthMajor oder g_ShowFill) und RefreshLines() liest diese g_-Werte, um die Chart-Objekte sofort zu aktualisieren. Diese Trennung vermeidet Verwirrung und ermöglicht es uns, Eingaben als sichere Standardwerte zu behandeln und gleichzeitig die volle Kontrolle über die Laufzeit zu gewährleisten. Das bedeutet auch, dass wir eine explizite Speicher-/Ladelogik hinzufügen müssen, wenn der Nutzer die Einstellungen über mehrere Sitzungen hinweg beibehalten möchte – eine zukünftige Erweiterung.
Schließlich verwenden wir aggressiv Namenskonventionen und Präfixe, damit das Programm Chart-Objekte deterministisch verwalten, aktualisieren und Müll sammeln kann. Präfixe wie HTF_MAJ_, HTF_MIN1_ und MPS_UI_ ermöglichen es uns, nur die von uns erstellten Objekte zu suchen, zu aktualisieren oder zu löschen, ohne dass andere Zeichnungen, die der Nutzer im Chart hat, betroffen sind. Dies ist ein kleines, aber wichtiges technisches Detail: Ohne eine einheitliche Namensgebung ist es leicht möglich, nicht verwandte Objekte versehentlich zu löschen oder zu überschreiben. Wir führen auch ein internes tf_list[]-Array (die Menge der zulässigen Zeiträume), damit die Dropdowns und die Zykluslogik konsistent und lokalisiert sind.
#include <Canvas/Canvas.mqh> // Canvas helper library (expects Canvas.mqh to be present) // --------------------------- USER INPUTS --------------------------- // Major timeframe + lookback + default visuals input ENUM_TIMEFRAMES InpHigherTF = PERIOD_H1; // Major higher timeframe input int InpLookback = 200; // Lookback (bars) input color InpColorMajor = clrRed; // Major line color input int InpWidthMajor = 2; // Major line width input int InpRefreshSec = 5; // Refresh interval (seconds) // Open/Close marker settings input bool InpShowOpenClose = true; // show open/close markers input color InpColorOpen = clrGreen; input color InpColorClose = clrLime; input int InpWidthOC = 1; input ENUM_LINE_STYLE InpStyleOC = STYLE_DASH; input int InpHorizOffsetBars= 3; // Body fill for majors input bool InpShowFill = true; input color InpFillBull = clrLime; input color InpFillBear = clrPink; // Minor periods input bool InpShowMinor1 = false; input ENUM_TIMEFRAMES InpMinor1TF = PERIOD_M30; input color InpColorMin1 = clrOrange; input int InpWidthMin1 = 1; input bool InpShowMinor2 = false; input ENUM_TIMEFRAMES InpMinor2TF = PERIOD_M15; input color InpColorMin2 = clrYellow; input int InpWidthMin2 = 1;
2) Globale und Laufzeitkopien – warum getrennte Eingaben und Laufzeitstatus?
Wir deklarieren Arrays und g_-Variablen für veränderbare Laufzeitzustände, Farbpaletten, Schieberegler-Infrastruktur und UI-Namensstrings. Diese Trennung ist der Schlüssel: UI-Operationen schreiben in g_*-Werte; RefreshLines() liest sie. Wir bereiten auch Arrays vor, die Schieberegler-Metadaten enthalten, damit jeder Schieberegler generisch erstellt/aktualisiert werden kann.
// --------------------------- GLOBALS ------------------------------- enum SliderIndex { SLIDER_MAJ_WIDTH = 0, SLIDER_REFRESH = 1 }; const int SLIDER_COUNT = 2; int Y_OFFSET = 50; // top offset for UI container // runtime (mutable) copies of inputs (dashboard will change these) ENUM_TIMEFRAMES g_HigherTF; int g_Lookback; color g_ColorMajor; int g_WidthMajor; int g_RefreshSec; // ... (other g_ variables for toggles & minors) // slider infrastructure (vertical sliders) string g_slider_track_names[]; string g_slider_knob_names[]; int g_slider_min[]; int g_slider_max[]; int g_slider_left_x[]; int g_slider_top_y[]; int g_vslider_height_px = 110; int g_vslider_width_px = 14; int g_slider_knob_w = 12; bool g_slider_drag = false; int g_current_slider = -1;
3) TF-Hilfsprogramme – konsistente Beschriftungen und Zyklen
TFToString() zentralisiert die Zeitrahmenbeschriftung für Schaltflächen und Objektnamen (damit Dropdowns und Beschriftungen immer übereinstimmen). FindNextTFIndex() implementiert einen einfachen Zyklus, um zum nächsten Zeitrahmen in tf_list zu gelangen – nützlich für schnelle Schaltflächenwechsel.
string TFToString(ENUM_TIMEFRAMES tf) { switch(tf) { case PERIOD_M1: return "M1"; case PERIOD_M5: return "M5"; case PERIOD_M15: return "M15"; case PERIOD_M30: return "M30"; case PERIOD_H1: return "H1"; case PERIOD_H4: return "H4"; case PERIOD_D1: return "D1"; case PERIOD_W1: return "W1"; case PERIOD_MN1: return "MN"; } return IntegerToString((int)tf); } int FindNextTFIndex(ENUM_TIMEFRAMES current) { int n = ArraySize(tf_list); for(int i=0;i<n;i++) if(tf_list[i] == current) return (i+1)%n; return 0; }
4) OnInit() – Vorbereitung des Laufzeitstatus, der UI-Namen, der Leinwand und der ersten Zeichnung
OnInit ist der Orchestrierungsschritt: Kopieren von Eingabewerten in g_*, Berechnen der letzten Taktzeiten für die Änderungserkennung, Erstellen von UI-Namensstrings, Vorbereiten von Schieberegler-Arrays, Erstellen des Canvas-Hintergrunds, Erstellen von UI-Widgets (Schaltflächen, Beschriftungen, Farbschaltflächen), Erstellen der vertikalen Schieberegler und Beschriftungen und anschließendes Starten des Timers. Beachten Sie, dass wir am Ende RefreshLines() aufrufen, damit das Chart sofort mit HTF-Objekten aufgefüllt wird.
Nach dem Kompilieren und Anhängen des EA erwarten wir ein halbtransparentes UI-Panel mit Steuerelementen; HTF-Linien und Füllungen erscheinen im Chart und spiegeln die Eingabevorgaben wider.
int OnInit() { main_chart_id = ChartID(); // copy inputs -> runtime g_HigherTF = InpHigherTF; g_Lookback = MathMax(10, InpLookback); g_ColorMajor = InpColorMajor; g_WidthMajor = MathMax(1, InpWidthMajor); g_RefreshSec = MathMax(1, InpRefreshSec); // ... (copy the rest of Inp->g_ variables) // initialize last bar times g_last_major_time = iTime(_Symbol, g_HigherTF, 0); if(g_ShowMinor1) g_last_minor1_time = iTime(_Symbol, g_Minor1TF, 0); if(g_ShowMinor2) g_last_minor2_time = iTime(_Symbol, g_Minor2TF, 0); // UI prefix and object names UI_PREFIX = StringFormat("MPS_UI_%d_", main_chart_id); lbl_title = UI_PREFIX + "LBL_TITLE"; btn_major_tf = UI_PREFIX + "BTN_MAJ_TF"; // ... (initialize other UI name strings) // prepare slider arrays ArrayResize(g_slider_track_names, SLIDER_COUNT); ArrayResize(g_slider_knob_names, SLIDER_COUNT); ArrayResize(g_slider_min, SLIDER_COUNT); ArrayResize(g_slider_max, SLIDER_COUNT); ArrayResize(g_slider_left_x, SLIDER_COUNT); ArrayResize(g_slider_top_y, SLIDER_COUNT); for(int i=0;i<SLIDER_COUNT;i++) { g_slider_track_names[i] = UI_PREFIX + StringFormat("SL_TRK_%d", i); g_slider_knob_names[i] = UI_PREFIX + StringFormat("SL_KNB_%d", i); } // background area coords and create UI g_bg_y = Y_OFFSET - 6; g_bg_h = 250; CreateUIBackground(); CreateLabel(lbl_title, 12, 4 + Y_OFFSET, "Market Period Synchronizer Control Utility", 12); ObjectSetInteger(main_chart_id, lbl_title, OBJPROP_COLOR, XRGB(230,230,230)); // create many UI buttons/labels and sliders... CreateButton(btn_major_tf, 12, 34 + Y_OFFSET, 70, 24, TFToString(g_HigherTF)); CreateLabel(lbl_major_tf, 92, 36 + Y_OFFSET, "Major TF"); // ... more buttons and color swatches // Major width slider (vertical) g_slider_left_x[SLIDER_MAJ_WIDTH] = 190; g_slider_top_y[SLIDER_MAJ_WIDTH] = slider_base_top; g_slider_min[SLIDER_MAJ_WIDTH] = 1; g_slider_max[SLIDER_MAJ_WIDTH] = 10; CreateVerticalSliderAt(SLIDER_MAJ_WIDTH, g_slider_left_x[SLIDER_MAJ_WIDTH], g_slider_top_y[SLIDER_MAJ_WIDTH], g_slider_track_names[SLIDER_MAJ_WIDTH], g_slider_knob_names[SLIDER_MAJ_WIDTH], g_WidthMajor, g_slider_min[SLIDER_MAJ_WIDTH], g_slider_max[SLIDER_MAJ_WIDTH], g_vslider_height_px); // start events & timer ChartSetInteger(main_chart_id, CHART_EVENT_MOUSE_MOVE, true); EventSetTimer(g_RefreshSec); // initial drawing of HTF objects RefreshLines(); return INIT_SUCCEEDED; }
5) Erstellung von Hintergründen – visuelle Gruppierung und Klickverhalten
CreateUIBackground() verwendet den CCanvas-Helper, um ein Bitmap-Label zu erstellen, das als dunkles, halbtransparentes Panel hinter UI-Elementen fungiert. Wichtige Design-Entscheidung: wir setzen den Canvas OBJPROP_BACK = false und OBJPROP_SELECTABLE = false, sodass die Leinwand zwar sichtbar ist, aber keine Klicks abfängt – davor gezeichnete Schaltflächen erhalten weiterhin Mausereignisse.
Eine polierte dunkle Panel verbessert die Lesbarkeit von Symbolen und Chart-Themen.
void CreateUIBackground()
{
g_bg_name = UI_PREFIX + "BG";
ObjectDelete(main_chart_id, g_bg_name);
bool ok = g_bgCanvas.CreateBitmapLabel(main_chart_id, 0, g_bg_name, g_bg_x, g_bg_y, g_bg_w, g_bg_h, COLOR_FORMAT_ARGB_RAW);
if(!ok) { PrintFormat("CreateUIBackground: CreateBitmapLabel failed err=%d", GetLastError()); return; }
uint dark_grey = ARGB(180, 30, 30, 30);
uint border_col = XRGB(80, 80, 80);
uint top_strip = ARGB(210, 24, 24, 24);
g_bgCanvas.FillRectangle(0, 0, g_bg_w - 1, g_bg_h - 1, dark_grey);
g_bgCanvas.Rectangle(0, 0, g_bg_w - 1, g_bg_h - 1, border_col);
g_bgCanvas.FillRectangle(0, 0, g_bg_w - 1, 28, top_strip);
g_bgCanvas.Update(true);
ObjectSetInteger(main_chart_id, g_bg_name, OBJPROP_BACK, false);
ObjectSetInteger(main_chart_id, g_bg_name, OBJPROP_SELECTABLE, false);
ObjectSetInteger(main_chart_id, g_bg_name, OBJPROP_HIDDEN, false);
}
6) Hilfsmittel für die UI-Erstellung – einheitliche Gestaltung von Schaltflächen und Etiketten
CreateButton, CreateLabel und CreateColorButton zentralisieren die UI-Erstellung, sodass Layout und Styling konsistent bleiben. Schaltflächen werden als auswählbar angelegt und im Vordergrund gezeichnet (OBJPROP_BACK=false), die Beschriftungen sind nicht auswählbar. Dies gewährleistet ein vorhersehbares Ereignismodell und ein einheitliches Aussehen.
void CreateButton(string name,int x,int y,int w,int h,string text) { if(StringLen(name) == 0) return; ObjectDelete(main_chart_id, name); if(!ObjectCreate(main_chart_id, name, OBJ_BUTTON, 0, 0, 0)) { PrintFormat("CreateButton: failed to create %s err=%d", name, GetLastError()); return; } ObjectSetInteger(main_chart_id, name, OBJPROP_BACK, false); // draw in front ObjectSetInteger(main_chart_id, name, OBJPROP_CORNER, CORNER_LEFT_UPPER); ObjectSetInteger(main_chart_id, name, OBJPROP_XDISTANCE, x); ObjectSetInteger(main_chart_id, name, OBJPROP_YDISTANCE, y); ObjectSetInteger(main_chart_id, name, OBJPROP_XSIZE, w); ObjectSetInteger(main_chart_id, name, OBJPROP_YSIZE, h); ObjectSetString(main_chart_id, name, OBJPROP_TEXT, text); ObjectSetInteger(main_chart_id, name, OBJPROP_FONTSIZE, 10); ObjectSetInteger(main_chart_id, name, OBJPROP_SELECTABLE, true); ObjectSetInteger(main_chart_id, name, OBJPROP_HIDDEN, false); } void CreateLabel(string name,int x,int y,string text, int fontsize=9) { if(StringLen(name) == 0) return; ObjectDelete(main_chart_id, name); if(!ObjectCreate(main_chart_id, name, OBJ_LABEL, 0, 0, 0)) { PrintFormat("CreateLabel: failed to create %s err=%d", name, GetLastError()); return; } ObjectSetInteger(main_chart_id, name, OBJPROP_BACK, false); ObjectSetInteger(main_chart_id, name, OBJPROP_CORNER, CORNER_LEFT_UPPER); ObjectSetInteger(main_chart_id, name, OBJPROP_XDISTANCE, x); ObjectSetInteger(main_chart_id, name, OBJPROP_YDISTANCE, y); ObjectSetString(main_chart_id, name, OBJPROP_TEXT, text); ObjectSetInteger(main_chart_id, name, OBJPROP_FONTSIZE, fontsize); ObjectSetInteger(main_chart_id, name, OBJPROP_SELECTABLE, false); ObjectSetInteger(main_chart_id, name, OBJPROP_HIDDEN, false); } void CreateColorButton(string name, int x, int y, int w, int h, color col) { CreateButton(name, x, y, w, h, ""); ObjectSetInteger(main_chart_id, name, OBJPROP_BGCOLOR, col); ObjectSetInteger(main_chart_id, name, OBJPROP_FONTSIZE, 8); }
7) Vertikale Schieberegler – Architektur und Ziehen in Echtzeit
Vertikale Schieberegler bestehen aus einer nicht auswählbaren Spur (visuell) und einem auswählbaren Knopf (Taste). CreateVerticalSliderAt() berechnet die Position des Reglers aus dem Wertebereich, speichert die Metadaten des Reglers in Arrays und platziert den Regler. Die Ziehlogik (die in OnChartEvent behandelt wird) verwendet CHARTEVENT_MOUSE_MOVE, um den Knopf Y zu setzen, ein Verhältnis zu berechnen und es auf den Wertebereich zurückzuführen. Wenn sich ein Schieberegler ändert, aktualisiert der Code die entsprechenden g_-Variablen (z. B. g_WidthMajor, g_RefreshSec) und ruft sofort RefreshLines() auf.
Die Y-Koordinate wird für den Wert Mapping-Top = Maximalwert invertiert.
void CreateVerticalSliderAt(int id, int base_x, int base_y, string track_name, string knob_name, int current_value, int min_val, int max_val, int track_height) { // track ObjectDelete(main_chart_id, track_name); if(!ObjectCreate(main_chart_id, track_name, OBJ_BUTTON, 0, 0, 0)) { PrintFormat("CreateVerticalSliderAt: failed track %s err=%d", track_name, GetLastError()); } ObjectSetInteger(main_chart_id, track_name, OBJPROP_BACK, false); ObjectSetInteger(main_chart_id, track_name, OBJPROP_CORNER, CORNER_LEFT_UPPER); ObjectSetInteger(main_chart_id, track_name, OBJPROP_XDISTANCE, base_x); ObjectSetInteger(main_chart_id, track_name, OBJPROP_YDISTANCE, base_y); ObjectSetInteger(main_chart_id, track_name, OBJPROP_XSIZE, g_vslider_width_px); ObjectSetInteger(main_chart_id, track_name, OBJPROP_YSIZE, track_height); ObjectSetString(main_chart_id, track_name, OBJPROP_TEXT, ""); ObjectSetInteger(main_chart_id, track_name, OBJPROP_SELECTABLE, false); ObjectSetInteger(main_chart_id, track_name, OBJPROP_HIDDEN, false); // knob ObjectDelete(main_chart_id, knob_name); if(!ObjectCreate(main_chart_id, knob_name, OBJ_BUTTON, 0, 0, 0)) { PrintFormat("CreateVerticalSliderAt: failed knob %s err=%d", knob_name, GetLastError()); } ObjectSetInteger(main_chart_id, knob_name, OBJPROP_BACK, false); ObjectSetInteger(main_chart_id, knob_name, OBJPROP_CORNER, CORNER_LEFT_UPPER); double ratio = 0.0; if(max_val > min_val) ratio = double(current_value - min_val) / double(max_val - min_val); int knob_y = base_y + (int)MathRound((1.0 - ratio) * (track_height - g_slider_knob_w)); int knob_x = base_x - (g_slider_knob_w/2) + (g_vslider_width_px/2); ObjectSetInteger(main_chart_id, knob_name, OBJPROP_XDISTANCE, knob_x); ObjectSetInteger(main_chart_id, knob_name, OBJPROP_YDISTANCE, knob_y); ObjectSetInteger(main_chart_id, knob_name, OBJPROP_XSIZE, g_slider_knob_w); ObjectSetInteger(main_chart_id, knob_name, OBJPROP_YSIZE, g_slider_knob_w); ObjectSetString(main_chart_id, knob_name, OBJPROP_TEXT, ""); ObjectSetInteger(main_chart_id, knob_name, OBJPROP_SELECTABLE, true); ObjectSetInteger(main_chart_id, knob_name, OBJPROP_HIDDEN, false); ObjectSetInteger(main_chart_id, knob_name, OBJPROP_FONTSIZE, 1); // store slider params g_slider_min[id] = min_val; g_slider_max[id] = max_val; g_slider_left_x[id] = base_x; g_slider_top_y[id] = base_y; }
8) Schieberegler-Beschriftung aktualisieren – UI synchron halten
UpdateLabelsAfterSliderChange() aktualisiert die Textbeschriftungen unter den Schiebereglern, wenn sich der Wert ändert, damit die visuellen Informationen konsistent bleiben.
void UpdateLabelsAfterSliderChange() { if(ObjectFind(main_chart_id, lbl_major_width) >= 0) ObjectSetString(main_chart_id, lbl_major_width, OBJPROP_TEXT, StringFormat("Maj W:%d", g_WidthMajor)); if(ObjectFind(main_chart_id, lbl_refresh_label) >= 0) ObjectSetString(main_chart_id, lbl_refresh_label, OBJPROP_TEXT, StringFormat("Refresh:%ds", g_RefreshSec)); }
9) TF-Dropdown-Logik – kleine, zuverlässige Popup-Listen
ShowTFDropdownFor() erzeugt einen Stapel kleiner Schaltflächen unmittelbar unter der gewünschten TF-Schaltfläche (major / minor1 / minor2). HideTFDropdown() entfernt sie. Die Auswahlliste verwendet ein zusammengesetztes Präfix, damit Auswahlereignisse in OnChartEvent leicht geparst werden können.
Dies ist absichtlich einfach und robust – die Verwendung von Chart-Schaltflächen vermeidet die Erstellung einer vollständigen nutzerdefinierten Combobox-Klasse und bietet gleichzeitig das erwartete Dropdown-Verhalten.
void ShowTFDropdownFor(int target) { if(g_tf_dropdown_visible && g_tf_dropdown_target == target) { HideTFDropdown(); return; } if(g_tf_dropdown_visible) HideTFDropdown(); string target_btn = (target == 0 ? btn_major_tf : (target == 1 ? btn_minor1_tf : btn_minor2_tf)); if(ObjectFind(main_chart_id, target_btn) < 0) return; int bx = (int)ObjectGetInteger(main_chart_id, target_btn, OBJPROP_XDISTANCE); int by = (int)ObjectGetInteger(main_chart_id, target_btn, OBJPROP_YDISTANCE); int bh = (int)ObjectGetInteger(main_chart_id, target_btn, OBJPROP_YSIZE); int base_x = bx; int base_y = by + bh + 4; int w = 60; int h = 20; for(int i=0;i<ArraySize(tf_list);i++) { string oname = TF_DROPDOWN_PREFIX + IntegerToString(target) + "_" + IntegerToString(i); CreateButton(oname, base_x, base_y + i*(h+2), w, h, TFToString(tf_list[i])); } g_tf_dropdown_visible = true; g_tf_dropdown_target = target; } void HideTFDropdown() { if(!g_tf_dropdown_visible) return; for(int i=0; i<ArraySize(tf_list); i++) { string oname = TF_DROPDOWN_PREFIX + IntegerToString(g_tf_dropdown_target) + "_" + IntegerToString(i); ObjectDelete(main_chart_id, oname); } g_tf_dropdown_visible = false; g_tf_dropdown_target = -1; }
10) OnChartEvent() – Ereignisbehandlung und Handhabung des Ziehens
Dies ist das Interaktionsgehirn. Es behandelt:
- Starten und Stoppen des Ziehens mit dem Drehknopf (ein Klick auf den Drehknopf beginnt das Ziehen; jeder weitere Klick auf das Objekt beendet es).
- Mausbewegung beim Ziehen: Berechnung der Pixelposition des Drehknopfes, Zuordnung zu einem numerischen Wert, Aktualisierung der Variablen g_ und Aufruf von RefreshLines(), wenn sich die Position geändert hat.
- Tastenklicks: Umschalttasten, Rückblick +/- Tasten, Farbauswahl, TF-Dropdown anzeigen/ausblenden.
- TF-Dropdown-Auswahl: Analysiert den Namen der angeklickten Schaltfläche und wendet den gewählten Zeitrahmen entsprechend auf Haupt- und Nebenzeit an, dann wird neu gezeichnet.
- Design-Entscheidungen: Das Anhalten des Ziehens bei jedem Objektklick ist ein einfaches plattformübergreifendes Muster, das die Notwendigkeit vermeidet, globale Ereignisse wie Maustaste-Hoch zu erkennen (die in MQL5 nicht immer als dediziertes Chart-Ereignis verfügbar sind).
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { // stop dragging if an object click happens if(id == CHARTEVENT_OBJECT_CLICK && g_slider_drag) { g_slider_drag = false; g_current_slider = -1; return; } // dragging motion: use mouse Y from dparam if(id == CHARTEVENT_MOUSE_MOVE && g_slider_drag && g_current_slider >= 0) { int my = (int)dparam; int s = g_current_slider; string track_name = g_slider_track_names[s]; string knob_name = g_slider_knob_names[s]; int track_top = (int)ObjectGetInteger(main_chart_id, track_name, OBJPROP_YDISTANCE); int track_h = (int)ObjectGetInteger(main_chart_id, track_name, OBJPROP_YSIZE); int track_bottom = track_top + track_h - g_slider_knob_w; int new_knob_y = my; if(new_knob_y < track_top) new_knob_y = track_top; if(new_knob_y > track_bottom) new_knob_y = track_bottom; ObjectSetInteger(main_chart_id, knob_name, OBJPROP_YDISTANCE, new_knob_y); double ratio = 1.0 - double(new_knob_y - track_top) / double(track_h - g_slider_knob_w); int new_val = g_slider_min[s] + (int)MathRound(ratio * (g_slider_max[s] - g_slider_min[s])); if(new_val < g_slider_min[s]) new_val = g_slider_min[s]; if(new_val > g_slider_max[s]) new_val = g_slider_max[s]; bool changed = false; if(s == SLIDER_MAJ_WIDTH) { if(g_WidthMajor != new_val) { g_WidthMajor = new_val; changed = true; } } else if(s == SLIDER_REFRESH) { if(g_RefreshSec != new_val) { g_RefreshSec = new_val; EventSetTimer(g_RefreshSec); changed = true; } } if(changed) { UpdateLabelsAfterSliderChange(); RefreshLines(); } return; } // TF dropdown option clicked if(StringFind(sparam, TF_DROPDOWN_PREFIX) == 0 && id == CHARTEVENT_OBJECT_CLICK) { string rest = StringSubstr(sparam, StringLen(TF_DROPDOWN_PREFIX)); int sep = StringFind(rest, "_"); if(sep >= 0) { int target = (int)StringToInteger(StringSubstr(rest, 0, sep)); int idx = (int)StringToInteger(StringSubstr(rest, sep+1)); if(idx >= 0 && idx < ArraySize(tf_list)) { ENUM_TIMEFRAMES chosen = tf_list[idx]; if(target == 0) { g_HigherTF = chosen; g_last_major_time = iTime(_Symbol, g_HigherTF, 0); ObjectSetString(main_chart_id, btn_major_tf, OBJPROP_TEXT, TFToString(g_HigherTF)); } else if(target == 1) { g_Minor1TF = chosen; if(g_ShowMinor1) g_last_minor1_time = iTime(_Symbol, g_Minor1TF, 0); ObjectSetString(main_chart_id, btn_minor1_tf, OBJPROP_TEXT, TFToString(g_Minor1TF)); } else if(target == 2) { g_Minor2TF = chosen; if(g_ShowMinor2) g_last_minor2_time = iTime(_Symbol, g_Minor2TF, 0); ObjectSetString(main_chart_id, btn_minor2_tf, OBJPROP_TEXT, TFToString(g_Minor2TF)); } HideTFDropdown(); RefreshLines(); } } return; } // If dropdown visible and user clicked elsewhere => hide if(g_tf_dropdown_visible && id == CHARTEVENT_OBJECT_CLICK && StringFind(sparam, TF_DROPDOWN_PREFIX) != 0) { HideTFDropdown(); } // Handle other button clicks & knob starts... if(id == CHARTEVENT_OBJECT_CLICK) { string obj = sparam; if(obj == btn_major_tf) { ShowTFDropdownFor(0); return; } if(obj == btn_lookback_minus) { g_Lookback = MathMax(10, g_Lookback - 10); ObjectSetString(main_chart_id, lbl_lookback, OBJPROP_TEXT, StringFormat("Lookback:%d", g_Lookback)); RefreshLines(); return; } if(obj == btn_lookback_plus) { g_Lookback += 10; ObjectSetString(main_chart_id, lbl_lookback, OBJPROP_TEXT, StringFormat("Lookback:%d", g_Lookback)); RefreshLines(); return; } if(obj == btn_toggle_openclose) { g_ShowOpenClose = !g_ShowOpenClose; ObjectSetString(main_chart_id, btn_toggle_openclose, OBJPROP_TEXT, g_ShowOpenClose ? "Open/Close: ON" : "Open/Close: OFF"); RefreshLines(); return; } if(obj == btn_toggle_fill) { g_ShowFill = !g_ShowFill; ObjectSetString(main_chart_id, btn_toggle_fill, OBJPROP_TEXT, g_ShowFill ? "Fill: ON" : "Fill: OFF"); RefreshLines(); return; } // major colors if(obj == btn_major_col1) { g_ColorMajor = major_colors[0]; RefreshLines(); return; } // ... other colors // minors toggles / tf dropdown if(obj == btn_minor1_toggle) { g_ShowMinor1 = !g_ShowMinor1; if(g_ShowMinor1) g_last_minor1_time = iTime(_Symbol, g_Minor1TF, 0); ObjectSetString(main_chart_id, btn_minor1_toggle, OBJPROP_TEXT, g_ShowMinor1 ? "Min1: ON" : "Min1: OFF"); RefreshLines(); return; } if(obj == btn_minor1_tf) { ShowTFDropdownFor(1); return; } if(obj == btn_minor2_toggle) { g_ShowMinor2 = !g_ShowMinor2; if(g_ShowMinor2) g_last_minor2_time = iTime(_Symbol, g_Minor2TF, 0); ObjectSetString(main_chart_id, btn_minor2_toggle, OBJPROP_TEXT, g_ShowMinor2 ? "Min2: ON" : "Min2: OFF"); RefreshLines(); return; } if(obj == btn_minor2_tf) { ShowTFDropdownFor(2); return; } if(obj == btn_clear_all) { DeleteAllHTFLines(); return; } // slider knob clicked -> begin dragging for(int s=0; s<SLIDER_COUNT; s++) { if(obj == g_slider_knob_names[s]) { g_current_slider = s; g_slider_drag = true; return; } } } }
11) OnTick() und OnTimer() – effiziente Aktualisierungsauslöser
OnTick() prüft die neueste iTime für die konfigurierten höheren und niedrigeren Zeitrahmen und setzt need_refresh nur dann, wenn ein neuer Balken erscheint – dies verhindert unnötige Objektveränderungen. OnTimer() ruft einfach RefreshLines() im Intervall g_RefreshSec auf (das der Schieberegler live ändern kann).
Schnelle, aber nicht verschwenderische Aktualisierungen; UI-gesteuerte Änderungen (z. B. Umschalten der Füllung) rufen RefreshLines() sofort auf, während periodische Überprüfungen Fälle behandeln, in denen neue HTF-Balken erscheinen.
void OnTimer() { RefreshLines(); } void OnTick() { bool need_refresh = false; datetime curr; curr = iTime(_Symbol, g_HigherTF, 0); if(curr != g_last_major_time && curr != 0) { g_last_major_time = curr; need_refresh = true; } if(g_ShowMinor1) { curr = iTime(_Symbol, g_Minor1TF, 0); if(curr != g_last_minor1_time && curr != 0) { g_last_minor1_time = curr; need_refresh = true; } } if(g_ShowMinor2) { curr = iTime(_Symbol, g_Minor2TF, 0); if(curr != g_last_minor2_time && curr != 0) { g_last_minor2_time = curr; need_refresh = true; } } if(need_refresh) RefreshLines(); }
12) RefreshLines() – Kernzeichnung & Garbage Collection
Dies ist die Hauptroutine. Sie:
- Kopiert HTF-Zeiten, Öffnungen und Schließungen für den gewünschten Rückblick.
- Kehrt Arrays in aufsteigender Reihenfolge für einfache Intervallvergleiche um.
- Erstellt oder aktualisiert für jeden wichtigen Zeitpunkt eine vertikale Linie, ein optionales Füllrechteck, horizontale Linien zum Öffnen/Schließen und Beschriftungen.
- Für Minors ruft sie DrawMinorsBetweenIntervals() auf, das die Vertikalen von der Minors nur dann zeichnet, wenn sie genau zwischen aufeinanderfolgende Graden von Majors fallen (plus aktuelles laufendes Intervall).
- Erstellt keepNames[], das alle gewünschten HTF-Objekte auflistet; am Ende durchläuft sie alle Chart-Objekte und löscht alle HTF-Objekte, die nicht in keepNames[] enthalten sind (Garbage Collection).
Bei jedem Durchlauf werden die Eigenschaften vorhandener Objekte (Farbe, Breite usw.) aktualisiert, sodass Änderungen an der Nutzeroberfläche sofort wirksam werden, ohne dass alles gelöscht oder neu erstellt werden muss.
void RefreshLines() { datetime major_times[]; ArrayFree(major_times); double major_opens[]; ArrayFree(major_opens); double major_closes[]; ArrayFree(major_closes); int copiedMaj = CopyTime(_Symbol, g_HigherTF, 0, g_Lookback, major_times); if(copiedMaj <= 0) { PrintFormat("RefreshLines: CopyTime majors returned %d for %s", copiedMaj, TFToString(g_HigherTF)); return; } if(CopyOpen(_Symbol, g_HigherTF, 0, copiedMaj, major_opens) != copiedMaj) { Print("RefreshLines: CopyOpen failed"); return; } if(CopyClose(_Symbol, g_HigherTF, 0, copiedMaj, major_closes) != copiedMaj) { Print("RefreshLines: CopyClose failed"); return; } // reverse to ascending order (oldest first) int n = copiedMaj; datetime sorted_times[]; ArrayResize(sorted_times, n); double sorted_opens[]; ArrayResize(sorted_opens, n); double sorted_closes[]; ArrayResize(sorted_closes, n); for(int k = 0; k < n; k++) { sorted_times[k] = major_times[n - 1 - k]; sorted_opens[k] = major_opens[n - 1 - k]; sorted_closes[k] = major_closes[n - 1 - k]; } string keepNames[]; ArrayResize(keepNames, 0); // create/update major objects for(int i = 0; i < n; ++i) { datetime t = sorted_times[i]; double p_open = sorted_opens[i]; double p_close = sorted_closes[i]; // Major vertical string name = PREFIX_MAJ + TFToString(g_HigherTF) + "_" + IntegerToString((int)t); if(ObjectFind(0, name) == -1) { double dummy_price = 0.0; if(!ObjectCreate(0, name, OBJ_VLINE, 0, t, dummy_price)) PrintFormat("Failed to create major %s error %d", name, GetLastError()); } // update props each pass so UI changes apply immediately ObjectSetInteger(0, name, OBJPROP_COLOR, g_ColorMajor); ObjectSetInteger(0, name, OBJPROP_WIDTH, g_WidthMajor); ObjectSetInteger(0, name, OBJPROP_STYLE, STYLE_SOLID); ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false); ObjectSetInteger(0, name, OBJPROP_HIDDEN, false); ObjectSetInteger(0, name, OBJPROP_BACK, true); // draw in background int sz = ArraySize(keepNames); ArrayResize(keepNames, sz + 1); keepNames[sz] = name; // Optional fill / open-close code follows (omitted for brevity) } if(n >= 2) { if(g_ShowMinor1) DrawMinorsBetweenIntervals(PREFIX_MIN1, g_Minor1TF, g_ColorMin1, g_WidthMin1, sorted_times, keepNames); if(g_ShowMinor2) DrawMinorsBetweenIntervals(PREFIX_MIN2, g_Minor2TF, g_ColorMin2, g_WidthMin2, sorted_times, keepNames); } // cleanup old HTF objects not in keepNames int total = ObjectsTotal(0); for(int idx = total - 1; idx >= 0; --idx) { string oname = ObjectName(0, idx); if(StringFind(oname, "HTF_") != -1) { bool found = false; for(int k=0;k<ArraySize(keepNames);k++) if(oname == keepNames[k]) { found = true; break; } if(!found) ObjectDelete(0, oname); } } UpdateLabelsAfterSliderChange(); ChartRedraw(main_chart_id); }
13) DrawMinorsBetweenIntervals() – präzise Minor-Platzierungslogik
Diese Funktion berechnet die Anzahl der anzufordernden Nebenbalken (basierend auf der Zeitspanne zwischen dem ersten Hauptzeitpunkt und dem aktuellen Zeitpunkt), kopiert die Nebenzeitpunkte, kehrt die aufsteigende Reihenfolge um und platziert vertikale Nebenlinien nur dann, wenn ein Nebenzeitpunkt genau zwischen aufeinanderfolgende Hauptzeitpunkte fällt – dies spiegelt das Verhalten des Indikators wider. Es werden auch Nebenzeiten behandelt, die in das aktuelle Hauptintervall fallen (nach der letzten Hauptzeit).
Design Detail: wir fügen eine Marge (+20) zu approx_bars, um robust gegen Ausrichtung zu sein; wir fordern auch mindestens g_Lookback Balken.
void DrawMinorsBetweenIntervals(const string prefix, const ENUM_TIMEFRAMES minorTF, const color c, const int width, const datetime &major_times[], string &keepNames[]) { datetime current_minor_time = iTime(_Symbol, minorTF, 0); if(current_minor_time == 0) return; int time_span = (int)(current_minor_time - major_times[0]); int minor_sec = PeriodSeconds(minorTF); int approx_bars = (time_span / minor_sec) + 20; if(approx_bars < g_Lookback) approx_bars = g_Lookback; datetime minor_times[]; ArrayFree(minor_times); int copiedMin = CopyTime(_Symbol, minorTF, 0, approx_bars, minor_times); PrintFormat("DrawMinorsBetweenIntervals: TF=%s copiedMin=%d majors=%d", TFToString(minorTF), copiedMin, ArraySize(major_times)); if(copiedMin <= 0) return; datetime sorted_minor_times[]; ArrayResize(sorted_minor_times, copiedMin); for(int k = 0; k < copiedMin; k++) sorted_minor_times[k] = minor_times[copiedMin - 1 - k]; int num_maj = ArraySize(major_times); for(int m = 0; m < copiedMin; ++m) { datetime mt = sorted_minor_times[m]; bool equals_major = false; for(int kk = 0; kk < num_maj; ++kk) if(major_times[kk] == mt) { equals_major = true; break; } if(equals_major) continue; bool placed = false; for(int j = 0; j < num_maj - 1; ++j) { if( major_times[j] < mt && mt < major_times[j+1] ) { string name = prefix + TFToString(minorTF) + "_" + IntegerToString((int)mt); if(ObjectFind(0, name) == -1) { double dummy_price = 0.0; if(!ObjectCreate(0, name, OBJ_VLINE, 0, mt, dummy_price)) PrintFormat("Failed to create minor %s error %d", name, GetLastError()); else { ObjectSetInteger(0, name, OBJPROP_COLOR, c); ObjectSetInteger(0, name, OBJPROP_WIDTH, width); ObjectSetInteger(0, name, OBJPROP_STYLE, STYLE_DOT); ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false); ObjectSetInteger(0, name, OBJPROP_HIDDEN, false); ObjectSetInteger(0, name, OBJPROP_BACK, true); } } int sz = ArraySize(keepNames); ArrayResize(keepNames, sz + 1); keepNames[sz] = name; placed = true; break; } } if(!placed && mt > major_times[num_maj - 1]) { // create a minor in the ongoing major interval string name = prefix + TFToString(minorTF) + "_" + IntegerToString((int)mt); if(ObjectFind(0, name) == -1) { double dummy_price = 0.0; if(!ObjectCreate(0, name, OBJ_VLINE, 0, mt, dummy_price)) PrintFormat("Failed to create minor (current) %s error %d", name, GetLastError()); else { ObjectSetInteger(0, name, OBJPROP_COLOR, c); ObjectSetInteger(0, name, OBJPROP_WIDTH, width); ObjectSetInteger(0, name, OBJPROP_STYLE, STYLE_DOT); ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false); ObjectSetInteger(0, name, OBJPROP_HIDDEN, false); ObjectSetInteger(0, name, OBJPROP_BACK, true); } } int sz = ArraySize(keepNames); ArrayResize(keepNames, sz + 1); keepNames[sz] = name; placed = true; } } ChartRedraw(main_chart_id); }
14) Löschen und Aufräumen – DeleteAllHTFLines() und OnDeinit()
DeleteAllHTFLines() entfernt nur HTF-Objekte; OnDeinit() entfernt UI-Objekte und Slider-Komponenten, blendet Dropdowns aus und zerstört den Canvas. Wir löschen HTF-Objekte absichtlich nicht standardmäßig beim Deinitieren (außer wenn HTF löschen gedrückt wird), sodass der Nutzer seine Zeichnungen behalten kann, wenn er das möchte.
void DeleteAllHTFLines() { int total = ObjectsTotal(0); for(int idx = total - 1; idx >= 0; --idx) { string oname = ObjectName(0, idx); if(StringFind(oname, "HTF_") != -1) ObjectDelete(0, oname); } } void OnDeinit(const int reason) { EventKillTimer(); string names[] = { lbl_title, btn_major_tf, lbl_major_tf, btn_lookback_minus, lbl_lookback, btn_lookback_plus, btn_toggle_openclose, btn_toggle_fill, btn_major_col1, btn_major_col2, btn_major_col3, btn_major_col4, btn_minor1_toggle, btn_minor1_tf, btn_minor2_toggle, btn_minor2_tf, btn_clear_all, lbl_major_width, lbl_refresh_label }; for(int i=0;i<ArraySize(names);i++) if(StringLen(names[i])>0) ObjectDelete(main_chart_id, names[i]); for(int s=0; s<SLIDER_COUNT; s++) { if(StringLen(g_slider_track_names[s])>0) ObjectDelete(main_chart_id, g_slider_track_names[s]); if(StringLen(g_slider_knob_names[s])>0) ObjectDelete(main_chart_id, g_slider_knob_names[s]); } if(g_tf_dropdown_visible) HideTFDropdown(); // destroy canvas g_bgCanvas.Destroy(); }
Tests
Nach erfolgreicher Zusammenstellung war es an der Zeit, den Expert Advisor in einem Live-Chart des MetaTrader 5 zu testen. Ein wichtiger Hinweis ist, dass die CCanvas-Klasse Teil der MQL5-Standardbibliothek ist, sodass es wichtig ist, den Include-Pfad korrekt zu definieren, um Kompilierungsfehler zu vermeiden. Sobald der EA an das Chart angehängt war, wurde er reibungslos initialisiert, und das Kontroll-Dashboard erschien wie erwartet.
Die Schnittstelle wurde sauber gerendert, wobei alle Schalter, Schieberegler und Beschriftungen korrekt auf dem Leinwandhintergrund ausgerichtet waren. Während des Tests reagierte jedes Steuerelement in Echtzeit – die Schieberegler passten visuelle Parameter wie Breite und Aktualisierungsrate dynamisch an, während die Schalter die Sichtbarkeit und Füllfunktionen sofort umschalteten. Das Ergebnis war eine äußerst reaktionsschnelle, interaktive Erfahrung, die das Konzept der Echtzeit-Parametersteuerung zum Leben erweckte.
Das folgende Bild veranschaulicht den erfolgreichen Einsatz und die aktiven Kontrollprozesse des EA, die die Stabilität und die Präzision des Entwurfs in der Ausführung bestätigen.

Abb. 3. Testen der Steuerelemente in einem Live-Chart
Ein besonders interessantes Ergebnis dieses Projekts war die Beobachtung des Echtzeit-Körperfüllungseffekts von Kerzen mit höherem Zeitrahmen, die sich direkt auf dem Chart mit niedrigerem Zeitrahmen bilden. Im Gegensatz zur früheren Version, die eine Neuinitialisierung des Indikators erforderte, um die Darstellung zu aktualisieren, können Händler mit dieser Implementierung die dynamische Entwicklung von Strukturen auf höheren Zeitskalen beobachten. Es bietet einen klaren und kontinuierlichen Überblick darüber, wie jede Bewegung in einem niedrigeren Zeitrahmen zur Bildung eines Balkens in einem höheren Zeitrahmen beiträgt, was einen tieferen Einblick in die sich entwickelnde Marktstruktur und -dynamik ermöglicht.
Schlussfolgerung
Diese Erkundung war sowohl ein technischer als auch ein kreativer Meilenstein auf unserer weiteren Reise mit der Sprache MQL5. Was als einfache Neugierde zur Verbesserung der Interaktion mit Charts begann, entwickelte sich zu einem vollwertigen System, das die Art und Weise, wie wir an die Abstimmung von Eingaben und die Visualisierung herangehen, neu definiert. Durch diese Entwicklung haben wir gelernt, dass MQL5 nicht nur eine Sprache für die Handelsautomatisierung ist, sondern auch eine Leinwand für das Design von Nutzererfahrungen – eine, die uns befähigt, statische Eingabeparameter in dynamische, reaktionsfähige Echtzeit-Steuerungsumgebungen zu verwandeln.
Durch die Nutzung von Objektmanipulation und grafischen Schnittstellen durch Expert Advisors haben wir gezeigt, dass es durchaus möglich ist, visuelle und logische Komponenten eines Systems direkt vom Chart aus zu steuern. Dieser Durchbruch überbrückt die Lücke zwischen Analyse und Interaktion und bietet Händlern und Entwicklern eine neue Dimension von Geschwindigkeit, Präzision und Kreativität. Mit der Echtzeitsteuerung wird das Testen mehrerer Einstellungen, Zeitrahmen und Visualisierungsmodi fließend – so können Nutzer schneller Entscheidungen treffen und gleichzeitig den Kontext beibehalten.
Das Market Periods Synchronizer Control Utility geht noch einen Schritt weiter, indem es Händlern die Möglichkeit gibt, zu beobachten, wie das Verhalten in kleineren Zeitrahmen die Strukturen in höheren Zeitrahmen beeinflusst. Sie verleiht der Analyse mit mehreren Zeitrahmen mehr Tiefe und ermöglicht es den Nutzern, den Herzschlag des Marktes über verschiedene Skalen hinweg zu untersuchen und besser zu verstehen, wie kleinere Preisschwankungen zur Gesamtform der großen Kerzen beitragen. Im Wesentlichen fördert es einen wissenschaftlicheren Ansatz bei der Interpretation von Preisaktionen – nicht nur was passiert ist, sondern warum und wie es sich gebildet hat.
Dies ist jedoch erst der Anfang. Die hier untersuchten Ideen können weit über den Rahmen dieses Hilfsprogramms hinaus erweitert werden. Künftige Verbesserungen könnten nutzerdefinierte Chart-Steuerungen, Synchronisierung mit mehreren Symbolen, Datenexport für Analysen oder KI-gesteuerte Visualisierungsoptimierung umfassen. Die Schönheit von MQL5 liegt in seiner Offenheit – es belohnt Experimente, Kreativität und die Bereitschaft, das Unkonventionelle zu erforschen.
Experimentieren Sie ruhig mit dieser Idee in Ihren eigenen Projekten. Passen Sie es an, modifizieren Sie es, integrieren Sie es in Ihre bestehenden Tools und entdecken Sie, welche neuen Möglichkeiten es für Ihre Handelssysteme eröffnen könnte. Ihr Feedback, Ihre Ideen und Beiträge sind von unschätzbarem Wert. Bitte teilen Sie uns Ihre Gedanken und Vorschläge in den unten stehenden Kommentaren mit. Gemeinsam können wir die MQL5-Gemeinschaft zu einem lebendigeren, innovativeren und kollaborativeren Raum für Lernen und Wachstum machen.
Abschließend finden Sie unten eine Übersichtstabelle mit den wichtigsten Erkenntnissen aus dieser Untersuchung sowie die angehängten Quelldateien, die Sie herunterladen, studieren und erweitern können. Denken Sie daran: Der beste Weg, etwas zu meistern, ist, darauf aufzubauen. Experimentieren Sie mutig, studieren Sie gründlich, und lassen Sie sich von Ihrer Arbeit inspirieren.
Wichtige Lektionen
| Lektion | Beschreibung |
|---|---|
| 1. Kontrolle in Echtzeit ist möglich. | Durch strukturierte Ereignisbehandlung und Objektaktualisierungen ist es möglich, Schnittstellen zu erstellen, die sofort auf Nutzereingaben reagieren und sofortiges Feedback und visuelle Aktualisierungen im Chart ermöglichen. |
| 2. CCanvas setzt visuelle Kreativität frei | Die Canvas-Klasse bietet die Möglichkeit, professionelle und visuell ansprechende Dashboards zu entwerfen. Sie ermöglicht Hintergrundmalerei, Transparenz und Überlagerung, um die Nutzerinteraktion zu verbessern. |
| 3. Die Reaktionsfähigkeit der Nutzeroberfläche hängt von der Objektverwaltung ab. | Die ordnungsgemäße Handhabung der Erstellung, Aktualisierung und Löschung von Chart-Objekten gewährleistet eine reibungslose Leistung und verhindert Unordnung oder Verzögerungen bei Echtzeitaktualisierungen. |
| 4. Dynamische Parameter verbessern das Experimentieren: | So können Händler Einstellungen wie Zeitrahmen, Farben und Breiten anpassen, ohne das Eigenschaftsfenster erneut öffnen zu müssen, was die Effizienz von Experimenten und Analysen erhöht. |
| 5. Multi-Timeframe-Synchronisation sorgt für mehr Tiefgang. | Die Kombination von Daten mit höherem und niedrigerem Zeitrahmen hilft Händlern, die interne Marktstruktur zu verstehen, Mikrobewegungen zu erkennen und kleinere Trends mit größeren Formationen zu verbinden. |
| 6. Ereignisgesteuertes Design ist der Schlüssel zu interaktiven Tools. | Der Aufbau interaktiver Systeme erfordert ein ausgeprägtes Verständnis für die Handhabung von Chart-Ereignissen. Für ein intuitives Steuerungsverhalten muss jede Nutzeraktion einer bestimmten Programmreaktion zugeordnet werden. |
| 7. Objektüberlagerung verbessert die Nutzerfreundlichkeit. | Durch die sorgfältige Einstellung von Hintergrund- und Vordergrundeigenschaften kann sichergestellt werden, dass die Schnittstelle reaktionsfähig bleibt und Objekte niemals wichtige Nutzerinteraktionen blockieren. |
| 8. Modularer Code erhöht die Wartungsfreundlichkeit. | Durch die Unterteilung des Projekts in logische Abschnitte, wie z. B. die Erstellung der Nutzeroberfläche, die Ereignisbehandlung und die Zeichenfunktionen, lässt sich das System leichter erweitern, debuggen und in zukünftigen Projekten wiederverwenden. |
| 9. Testen und Debuggen von Visualisierungswerkzeugen. | Visuelle Echtzeittests deckten Probleme wie nicht aktualisierte Objekte und Überschneidungskonflikte auf und vermittelten uns systematische Debugging-Methoden unter Verwendung von Protokollen und Ereignisverfolgung. |
| 10. Optimieren der Aktualisierungs- und Zeichnungsintervalle. | Wir haben gelernt, wie man durch die Anpassung von Aktualisierungszeitpunkten und Redraw-Strategien die Leistung und Reaktionsfähigkeit verbessern kann, insbesondere bei Echtzeit-Chart-Aktualisierungen. |
| 11. Visuelles Feedback verbessert das analytische Selbstvertrauen. | Visuelle Anpassungen in Echtzeit helfen Händlern, Ursache und Wirkung sofort zu erkennen, und stärken das Vertrauen in die Genauigkeit und Interpretation der Chart-Daten. |
| 12. Experimentieren treibt die Innovation voran. | Dieses Projekt beweist, dass die Erforschung unkonventioneller Ansätze in MQL5 zu neuen Tools und Ideen führt. Kreativität in Verbindung mit technischem Verständnis bringt Fortschritt für die gesamte Gemeinschaft. |
Anlagen
| Dateiname | Version | Beschreibung |
|---|---|---|
| MarketPeriodsSynchronizer_EA.mq5 | 1.00 | Dieser Expert Advisor bietet ein Echtzeit-Kontroll-Dashboard für die Synchronisierung und Visualisierung mehrerer Zeitrahmenperioden direkt im Chart. Er erweitert die Funktionalität des ursprünglichen Market Periods Synchronizer-Indikators durch die Integration interaktiver Steuerelemente, Schieberegler und Schalter für die sofortige Anpassung von Parametern ohne erneutes Öffnen der Eingabeeinstellungen. |
Übersetzt aus dem Englischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/en/articles/19918
Warnung: Alle Rechte sind von MetaQuotes Ltd. vorbehalten. Kopieren oder Vervielfältigen untersagt.
Dieser Artikel wurde von einem Nutzer der Website verfasst und gibt dessen persönliche Meinung wieder. MetaQuotes Ltd übernimmt keine Verantwortung für die Richtigkeit der dargestellten Informationen oder für Folgen, die sich aus der Anwendung der beschriebenen Lösungen, Strategien oder Empfehlungen ergeben.
Selbstoptimierende Expert Advisors in MQL5 (Teil 16): Überwachte lineare Systemidentifikation
Einführung in MQL5 (Teil 24): Erstellen eines EAs, der mit Chart-Objekten handelt
Eine alternative Log-datei mit der Verwendung der HTML und CSS
Die Grenzen des maschinellen Lernens überwinden (Teil 6): Effektive Speichervalidierung
- Freie Handelsapplikationen
- Über 8.000 Signale zum Kopieren
- Wirtschaftsnachrichten für die Lage an den Finanzmärkte
Sie stimmen der Website-Richtlinie und den Nutzungsbedingungen zu.