MQL5-Handelswerkzeuge (Teil 21): Hinzufügen eines Cyberpunk-Theme zu Regressionsdiagrammen
Einführung
Sie verlassen sich bei der Analyse von Symbolpaaren im Handel auf Standard-Regressionsdiagramme, doch diese wirken bei längeren Sitzungen statisch und wenig ansprechend, was es erschwert, den Fokus auf Korrelationen zu behalten oder Trends inmitten eintöniger Darstellungen zu erkennen – insbesondere in Umgebungen mit schwachem Licht, in denen Blendungen die Lesbarkeit beeinträchtigen. Einfachen Darstellungen fehlen dynamische Elemente, um wichtige Datenpunkte hervorzuheben oder das Interesse aufrechtzuerhalten, was dazu führt, dass in volatilen Märkten wichtige Erkenntnisse übersehen werden. Dieser Artikel richtet sich an Entwickler der Sprache MetaQuotes Language 5 (MQL5) sowie an algorithmische Händler, die ihre Charting-Tools um eindrucksvolle Themes ergänzen möchten, um die Visualisierung zu verbessern.
In unserem vorherigen Artikel (Teil 20) haben wir in MQL5 ein Canvas-basiertes Diagrammtool für statistische Korrelations- und lineare Regressionsanalysen zwischen zwei Symbolen erstellt, das verschoben und in der Größe angepasst werden kann. In Teil 21 erweitern wir dieses Tool um einen Cyberpunk-Themenmodus mit Neon-Effekten, Animationen, holografischen Rahmen und futuristischen Elementen. Dieses System mit zwei Themenmodi umfasst eine Umschaltfunktion per Schaltfläche, dynamische Hintergründe mit Sternen und Neon-Grafiken für eine fesselnde Analyse von Symbolpaaren im Handel. Wir werden Folgendes behandeln:
- Cyberpunk-Thema für eine verbesserte Visualisierung der Regression
- Implementierung in MQL5
- Backtests
- Schlussfolgerung
Am Ende werden Sie ein thematisches Regressionsdiagramm haben, das Standard- und Cyberpunk-Stile miteinander verbindet – legen wir los!
Cyberpunk-Thema für eine verbesserte Visualisierung der Regression
Der Cyberpunk-Themenmodus verbessert die Visualisierung von Regressionsanalysen durch die Einbindung von Neonlicht, animierten Impulsen, holografischen Rahmen und dynamischen Rastern und verwandelt Standarddiagramme in eindrucksvolle, futuristische Darstellungen, die fesselnd wirken und zugleich die analytische Genauigkeit bewahren. Es nutzt Farbmischungen, Alpha-Compositing und zeitgesteuerte Animationen, um Effekte wie leuchtende Linien, funkelnde Hintergründe und Panels im Glassmorphism-Stil zu erzeugen – ideal für den Handel im Nachtmodus oder zur individuellen Gestaltung.
Dieser Themenmodus lässt sich über eine Schaltfläche umschalten und ermöglicht sowohl den Cyberpunk- als auch den Standardmodus für eine vielseitige Analyse von Symbolpaaren. Wir werden eine Themenumschaltung mit Animationen über einen Timer hinzufügen, Cyberpunk-Elemente wie Neonpunkte und leuchtende Rahmen mithilfe von Überblendungen darstellen und gleichzeitig Standardfunktionen für die Dual-Mode-Kompatibilität anpassen. Kurz gesagt: Hier ist eine visuelle Darstellung unserer Ziele.

Implementierung in MQL5
Um das Programm in MQL5 zu verbessern, müssen wir neue globale Variablen und Eingabeparameter hinzufügen, um es an die neuen Verbesserungen und Themen anzupassen, die wir einführen werden.
Erweiterung von Eingaben und globalen Variablen für die Themenverwaltung
Um die neuen Cyberpunk-Funktionen zu unterstützen, führen wir Eingabeparameter zur Steuerung der Leuchtintensität sowie globale Variablen für Farben, Flags und Animations-Tracking ein.
//+------------------------------------------------------------------+ //| Canvas Graphing PART 2 - Statistical Regression (Cyberpunk).mq5 | //| Copyright 2026, Allan Munene Mutiiria. | //| https://t.me/Forex_Algo_Trader | //+------------------------------------------------------------------+ #property copyright "Copyright 2026, Allan Munene Mutiiria." #property link "https://t.me/Forex_Algo_Trader" #property version "1.00" #property strict #property description "Dual Theme Regression: Standard & Cyberpunk Modes" //+------------------------------------------------------------------+ //| Inputs | //+------------------------------------------------------------------+ input group "=== CYBERPUNK MODE SETTINGS ===" input double glowIntensity = 0.8; // Glow Intensity (0-1) //+------------------------------------------------------------------+ //| Global Variables | //+------------------------------------------------------------------+ bool isCyberpunkThemeEnabled = false; // Initialize cyberpunk theme flag int themeToggleButtonX = 0; // Initialize theme button X int themeToggleButtonY = 0; // Initialize theme button Y const int THEME_TOGGLE_BUTTON_SIZE = 25; // Set theme toggle button size const color CYBER_PRIMARY_COLOR = C'0,191,255'; // Set cyber primary color const color CYBER_SECONDARY_COLOR = C'255,0,255'; // Set cyber secondary color const color CYBER_DATA_POINTS_COLOR = C'255,50,150'; // Set cyber data points color const color CYBER_REGRESSION_COLOR = C'0,150,255'; // Set cyber regression color int currentAnimationFrame = 0; // Initialize animation frame
Zunächst fügen wir eine neue Eingabegruppe === CYBERPUNK-MODUS-EINSTELLUNGEN === hinzu, mit dem Parameter glowIntensity vom Typ double im Bereich von 0 bis 1, dessen Standardwert 0,8 ist, um die Intensität der Leuchteffekte im Cyberpunk-Themenmodus zu steuern. Als Nächstes deklarieren wir globale Variablen für die Themenverwaltung: isCyberpunkThemeEnabled, initialisiert auf false, als Flag zum Umschalten zwischen den Modi; themeToggleButtonX und themeToggleButtonY werden auf 0 gesetzt, um die Positionierung der Schaltflächen festzulegen; die Konstante THEME_TOGGLE_BUTTON_SIZE wird auf 25 Pixel festgelegt, um die Abmessungen zu definieren. Wir definieren Cyberpunk-spezifische Farben mithilfe der Notation C'...': "CYBER_PRIMARY_COLOR" als leuchtendes Cyan C'0,191,255', "CYBER_SECONDARY_COLOR" als Magenta C'255,0,255', "CYBER_DATA_POINTS_COLOR" als Rosa C'255,50,150' und "CYBER_REGRESSION_COLOR" als Blau C'0,150,255', wodurch lebendige, thematisch bezogene Farbpaletten entstehen.
Sie können jede andere Farbkombination wählen, die Ihnen gefällt; wir haben diese Option lediglich zur Veranschaulichung willkürlich ausgewählt. Abschließend setzen wir currentAnimationFrame auf 0, um den Fortschritt der Animation bei dynamischen Effekten zu verfolgen. Als Nächstes werden wir eine Hilfsfunktion erstellen, um Farben im Cyberpunk-Modus sanft ineinander übergehen zu lassen, damit die Darstellung nicht flach wirkt.
Implementierung eines Dienstprogramms zur Farbmischung
Um dynamische Effekte wie Leuchteffekte und Farbverläufe zu ermöglichen, definieren wir eine Funktion zur Interpolation zwischen zwei Farben.
//+------------------------------------------------------------------+ //| Blend colors | //+------------------------------------------------------------------+ color BlendColors(color color1, color color2, double ratio) { //--- Extract color1 red uchar r1 = (uchar)((color1 >> 16) & 0xFF); //--- Extract color1 green uchar g1 = (uchar)((color1 >> 8) & 0xFF); //--- Extract color1 blue uchar b1 = (uchar)(color1 & 0xFF); //--- Extract color2 red uchar r2 = (uchar)((color2 >> 16) & 0xFF); //--- Extract color2 green uchar g2 = (uchar)((color2 >> 8) & 0xFF); //--- Extract color2 blue uchar b2 = (uchar)(color2 & 0xFF); //--- Blend red uchar r = (uchar)(r1 * (1.0 - ratio) + r2 * ratio); //--- Blend green uchar g = (uchar)(g1 * (1.0 - ratio) + g2 * ratio); //--- Blend blue uchar b = (uchar)(b1 * (1.0 - ratio) + b2 * ratio); //--- Return blended color return (r << 16) | (g << 8) | b; }
Hier implementieren wir die Funktion BlendColors, um sanfte Farbübergänge für Cyberpunk-Effekte zu erzeugen. Dabei extrahieren wir mithilfe von Bitverschiebungen die RGB-Komponenten aus zwei Eingabefarben, mischen dann jeden Kanal, indem wir den ersten mit (1 – ratio) und den zweiten mit ratio gewichten, und kombinieren diese schließlich zu einem neuen Farbwert für dynamische Farbverläufe oder Animationen. Stellen Sie sich das wie einen magischen Farbmischer vor. Man gibt zwei Farben ein und sagt: Mische sie zu diesem Anteil (Verhältnis etwa 0,5 für halb und halb), und schon entsteht eine neue Farbe. Im Cyber-Modus werden wir damit sanfte, leuchtende Effekte erzeugen, beispielsweise indem wir Blau und Rosa mischen, um violettes Licht zu erzeugen. Ohne diese Funktion würden die Farben flach wirken; mit ihr sieht alles harmonischer und professioneller aus. Als Nächstes fügen wir in der Kopfzeile eine Schaltfläche zum Wechseln des Themes hinzu, damit wir problemlos zwischen den Themes wechseln können.
Schaltfläche zum Umschalten des Themes hinzufügen
Um einen Moduswechsel zu ermöglichen, zeigen wir in der Kopfzeile eine Schaltfläche an und zentrieren die Regressionsgerade, um ein ausgewogenes Erscheinungsbild zu erzielen.
//--- Adjust line drawing for centering in drawRegressionPlot //--- Draw line mainCanvas.LineAA(lineStartScreenX, lineStartScreenY + w - regressionLineWidth/2, lineEndScreenX, lineEndScreenY + w - regressionLineWidth/2, argbLine); //+------------------------------------------------------------------+ //| Draw theme toggle button | //+------------------------------------------------------------------+ void drawThemeToggleButton() { //--- Compute button X themeToggleButtonX = currentWidthPixels - THEME_TOGGLE_BUTTON_SIZE - 8; //--- Compute button Y themeToggleButtonY = (HEADER_BAR_HEIGHT - THEME_TOGGLE_BUTTON_SIZE) / 2; //--- Declare button color color buttonColor; //--- Check cyber mode if (isCyberpunkThemeEnabled) { //--- Set cyber color buttonColor = isHoveringThemeButton ? LightenColor(CYBER_PRIMARY_COLOR, 0.3) : CYBER_PRIMARY_COLOR; } else { //--- Set standard color buttonColor = isHoveringThemeButton ? LightenColor(themeColor, 0.3) : themeColor; } //--- Get button ARGB uint argbButton = ColorToARGB(buttonColor, 200); //--- Get border ARGB uint argbBorder = ColorToARGB(isCyberpunkThemeEnabled ? CYBER_SECONDARY_COLOR : themeColor, 255); //--- Fill button mainCanvas.FillRectangle(themeToggleButtonX, themeToggleButtonY, themeToggleButtonX + THEME_TOGGLE_BUTTON_SIZE, themeToggleButtonY + THEME_TOGGLE_BUTTON_SIZE, argbButton); //--- Draw border mainCanvas.Rectangle(themeToggleButtonX, themeToggleButtonY, themeToggleButtonX + THEME_TOGGLE_BUTTON_SIZE, themeToggleButtonY + THEME_TOGGLE_BUTTON_SIZE, argbBorder); //--- Set icon font mainCanvas.FontSet("Arial Black", 16); //--- Get text ARGB uint argbText = ColorToARGB(clrWhite, 255); //--- Set icon text string iconText = isCyberpunkThemeEnabled ? "◆" : "●"; //--- Draw icon mainCanvas.TextOut(themeToggleButtonX + THEME_TOGGLE_BUTTON_SIZE/2, themeToggleButtonY + 3, iconText, argbText, TA_CENTER); }
Zunächst passen wir die Funktion drawRegressionPlot so an, dass die Regressionsgerade vertikal zentriert ist. Dies geschieht durch Anpassung der Y-Positionen in der Schleife für die Breite. Ziehe die Hälfte der Linienbreite regressionLineWidth/2 sowohl von der Start- als auch von der Endposition der Y-Koordinate auf dem Bildschirm ab. Dadurch wird beim Zeichnen mit LineAA eine gleichmäßige Strichstärke entlang der berechneten Linie gewährleistet. Früher sah die dicke Linie vielleicht etwas dezentriert aus, wie eine schiefe Straße. Wenn man die Hälfte der Breite abzieht, wird es perfekt zentriert. Es ist nur eine kleine Korrektur, aber dadurch sieht die Linie ordentlich und professionell aus – so, als würde man ein Bild an der Wand gerade hängen.
Als Nächstes implementieren wir die Funktion drawThemeToggleButton. Diese Funktion blendet in der Kopfzeile eine Umschaltfläche zum Wechseln zwischen den Modi ein. Sie positioniert die Schaltfläche rechts in der Kopfzeile mit einem kleinen Randabstand und wählt eine Farbe entsprechend dem Theme und dem Hover-Zustand aus. Für die Dynamik wird LightenColor verwendet. Die Funktion füllt ein Rechteck mit halbtransparentem ARGB, zeichnet einen Rahmen in der themenspezifischen Farbe und legt die Schriftart Arial Black in fetter Schrift fest. Anschließend wird mit der Methode TextOut ein quadratisches Symbol für Cyberpunk bzw. ein kreisförmiges Symbol für Standard in weißem ARGB zentriert. Sie könnten wie bisher Symbole aus dem Wingdings-Zeichensatz verwenden. Wir haben uns jedoch dafür entschieden, um einen schlichten, technisch anmutenden Look zu erzielen. Als Nächstes rendern wir die Cyberpunk-Visualisierung. Beginnen wir mit der Überlagerung der Hauptzeichenfläche.
Zeichnen von Cyberpunk-Hintergrund und Rahmen
Um die Grundlage für das Thema zu schaffen, gestalten wir sternenbesetzte Farbverläufe und animierte holografische Rahmen.
//+------------------------------------------------------------------+ //| Draw cyberpunk background | //+------------------------------------------------------------------+ void drawCyberpunkBackground() { //--- Set top color color topColor = C'10,10,25'; //--- Set bottom color color bottomColor = C'20,5,30'; //--- Compute alpha uchar alphaChannel = (uchar)(255 * backgroundOpacityLevel); //--- Loop over rows for (int y = HEADER_BAR_HEIGHT; y < currentHeightPixels; y++) { //--- Compute factor double factor = (double)(y - HEADER_BAR_HEIGHT) / (currentHeightPixels - HEADER_BAR_HEIGHT); //--- Blend row color color rowColor = BlendColors(topColor, bottomColor, factor); //--- Get ARGB uint argbColor = ColorToARGB(rowColor, alphaChannel); //--- Loop over columns for (int x = 0; x < currentWidthPixels; x++) { //--- Check for star if (MathRand() % 800 == 0) { //--- Compute brightness uchar brightness = (uchar)(100 + MathRand() % 155); //--- Get star color uint starColor = ColorToARGB(C'255,255,255', brightness); //--- Blend star blendPixelSet(mainCanvas, x, y, starColor); } else { //--- Blend normal blendPixelSet(mainCanvas, x, y, argbColor); } } } } //+------------------------------------------------------------------+ //| Draw holographic border | //+------------------------------------------------------------------+ void drawHolographicBorder() { //--- Compute phase double phase = (currentAnimationFrame % 360) * M_PI / 180.0; //--- Compute glow factor double glowFactor = (MathSin(phase) + 1.0) * 0.5; //--- Blend border color color borderColor = BlendColors(CYBER_PRIMARY_COLOR, CYBER_SECONDARY_COLOR, glowFactor); //--- Loop for glow layers for (int i = 0; i < 3; i++) { //--- Compute alpha uchar alpha = (uchar)(80 * glowIntensity * (3 - i) / 3.0); //--- Get glow color uint glowColor = ColorToARGB(borderColor, alpha); //--- Draw top glow for (int x = i; x < currentWidthPixels - i; x++) { //--- Blend pixel blendPixelSet(mainCanvas, x, i, glowColor); } //--- Draw bottom glow for (int x = i; x < currentWidthPixels - i; x++) { //--- Blend pixel blendPixelSet(mainCanvas, x, currentHeightPixels - 1 - i, glowColor); } //--- Draw left glow for (int y = i; y < currentHeightPixels - i; y++) { //--- Blend pixel blendPixelSet(mainCanvas, i, y, glowColor); } //--- Draw right glow for (int y = i; y < currentHeightPixels - i; y++) { //--- Blend pixel blendPixelSet(mainCanvas, currentWidthPixels - 1 - i, y, glowColor); } } //--- Get border ARGB uint argbBorder = ColorToARGB(borderColor, 255); //--- Draw outer border mainCanvas.Rectangle(0, 0, currentWidthPixels - 1, currentHeightPixels - 1, argbBorder); //--- Draw inner border mainCanvas.Rectangle(1, 1, currentWidthPixels - 2, currentHeightPixels - 2, argbBorder); //--- Draw corner accents drawCornerAccents(borderColor); } //+------------------------------------------------------------------+ //| Draw corner accents | //+------------------------------------------------------------------+ void drawCornerAccents(color accentColor) { //--- Get accent ARGB uint argbAccent = ColorToARGB(accentColor, 255); //--- Set accent size int accentSize = 15; //--- Loop for top-left for (int i = 0; i < 3; i++) { //--- Draw horizontal mainCanvas.Line(5, 5 + i, accentSize, 5 + i, argbAccent); //--- Draw vertical mainCanvas.Line(5 + i, 5, 5 + i, accentSize, argbAccent); } //--- Loop for top-right for (int i = 0; i < 3; i++) { //--- Draw horizontal mainCanvas.Line(currentWidthPixels - accentSize - 1, 5 + i, currentWidthPixels - 6, 5 + i, argbAccent); //--- Draw vertical mainCanvas.Line(currentWidthPixels - 6 - i, 5, currentWidthPixels - 6 - i, accentSize, argbAccent); } //--- Loop for bottom-left for (int i = 0; i < 3; i++) { //--- Draw horizontal mainCanvas.Line(5, currentHeightPixels - 6 - i, accentSize, currentHeightPixels - 6 - i, argbAccent); //--- Draw vertical mainCanvas.Line(5 + i, currentHeightPixels - accentSize - 1, 5 + i, currentHeightPixels - 6, argbAccent); } //--- Loop for bottom-right for (int i = 0; i < 3; i++) { //--- Draw horizontal mainCanvas.Line(currentWidthPixels - accentSize - 1, currentHeightPixels - 6 - i, currentWidthPixels - 6, currentHeightPixels - 6 - i, argbAccent); //--- Draw vertical mainCanvas.Line(currentWidthPixels - 6 - i, currentHeightPixels - accentSize - 1, currentWidthPixels - 6 - i, currentHeightPixels - 6, argbAccent); } }
Für den Cyberpunk-Rahmen implementieren wir die Funktion drawCyberpunkBackground, um einen dunklen, sternenübersäten Farbverlauf für das Cyberpunk-Thema zu erstellen, wobei wir den oberen Bereich auf ein tiefes Blau-Schwarz (C'10,10,25') und den unteren Bereich auf ein Violett-Schwarz (C'20,5,30') setzen, berechnen den Alpha-Wert anhand der Deckkraft, durchlaufen dann die Zeilen in einer Schleife, um die Farben mit BlendColors zu mischen, und fügen zufällig weiße Sterne über MathRand% 800 == 0 mit unterschiedlicher Helligkeit hinzu, wobei alle Pixel mit blendPixelSet überblendet werden, um einen kosmischen Effekt zu erzeugen.
Als Nächstes erstellen wir drawHolographicBorder für animierte, leuchtende Rahmen, berechnen dabei die sinusbasierte Phase aus currentAnimationFrame für die Pulsation, mischen Primär- und Sekundär-Cyberfarben für einen dynamischen Rand und schichten dann Leuchteffekte mit abnehmender Deckkraft in Schleifen für oben/unten/links/rechts, zeichnen die Hauptrahmen mit Rectangle und rufen drawCornerAccents für zusätzliche Akzente auf. In drawCornerAccents wandeln wir die Akzentfarbe in ARGB um, legen die Größe auf 15 fest und zeichnen in einer dreimaligen Schleife mit der Funktion Line kurze horizontale und vertikale Linien an jeder Ecke (oben links, oben rechts, unten links, unten rechts), wodurch klammerartige Akzente für einen technischen Rahmen entstehen. Nun müssen wir eine futuristische Kopfzeile zeichnen.
Zeichnen einer futuristischen Kopfzeile
Als krönenden Abschluss rendern wir eine Kopfzeile mit Farbverlauf, leuchtendem Text und Animationen.
//+------------------------------------------------------------------+ //| Draw futuristic header | //+------------------------------------------------------------------+ void drawFuturisticHeader() { //--- Compute phase double phase = (currentAnimationFrame % 360) * M_PI / 180.0; //--- Compute anim factor double animFactor = (MathSin(phase) + 1.0) * 0.5; //--- Loop over header rows for (int y = 0; y < HEADER_BAR_HEIGHT; y++) { //--- Compute grad factor double gradFactor = (double)y / HEADER_BAR_HEIGHT; //--- Declare header color color headerColor; //--- Check dragging if (isDraggingCanvas) { //--- Blend dragged headerColor = BlendColors(DarkenColor(CYBER_PRIMARY_COLOR, 0.7), DarkenColor(CYBER_SECONDARY_COLOR, 0.7), gradFactor); } //--- Check hovering else if (isHoveringHeader) { //--- Blend hovered headerColor = BlendColors(DarkenColor(CYBER_PRIMARY_COLOR, 0.5), DarkenColor(CYBER_SECONDARY_COLOR, 0.5), gradFactor); } else { //--- Blend normal headerColor = BlendColors(DarkenColor(CYBER_PRIMARY_COLOR, 0.8), DarkenColor(CYBER_SECONDARY_COLOR, 0.8), gradFactor); } //--- Get header ARGB uint argbHeader = ColorToARGB(headerColor, 255); //--- Loop over columns for (int x = 0; x < currentWidthPixels; x++) { //--- Set pixel mainCanvas.PixelSet(x, y, argbHeader); } } //--- Blend border color borderColor = BlendColors(CYBER_PRIMARY_COLOR, CYBER_SECONDARY_COLOR, animFactor); //--- Get border ARGB uint argbBorder = ColorToARGB(borderColor, 255); //--- Draw outer mainCanvas.Rectangle(0, 0, currentWidthPixels - 1, HEADER_BAR_HEIGHT, argbBorder); //--- Draw inner mainCanvas.Rectangle(1, 1, currentWidthPixels - 2, HEADER_BAR_HEIGHT - 1, argbBorder); //--- Set title font mainCanvas.FontSet("Arial Black", titleFontSize); //--- Format title string titleText = StringFormat("◆ %s vs %s - Linear Regression ◆", secondarySymbol, primarySymbol); //--- Get glow ARGB uint glowColor = ColorToARGB(CYBER_PRIMARY_COLOR, (uchar)(120 * glowIntensity)); //--- Loop for glow Y for (int offsetY = -1; offsetY <= 1; offsetY++) { //--- Loop for glow X for (int offsetX = -1; offsetX <= 1; offsetX++) { //--- Skip center if (offsetX == 0 && offsetY == 0) continue; //--- Draw glow text mainCanvas.TextOut(currentWidthPixels / 2 + offsetX, (HEADER_BAR_HEIGHT - titleFontSize) / 2 + offsetY, titleText, glowColor, TA_CENTER); } } //--- Set text color uint textColor = ColorToARGB(C'255,105,180', 255); //--- Draw title mainCanvas.TextOut(currentWidthPixels / 2, (HEADER_BAR_HEIGHT - titleFontSize) / 2, titleText, textColor, TA_CENTER); }
Hier implementieren wir die Funktion drawFuturisticHeader, um eine animierte Kopfzeile mit Farbverlauf für das Cyberpunk-Thema darzustellen. Dazu berechnen wir zunächst eine Phase aus currentAnimationFrame unter Verwendung einer Sinusfunktion für pulsierende Effekte sowie einen Animationsfaktor, der auf den Bereich zwischen 0 und 1 normiert ist. Anschließend durchlaufen wir die Zeilen der Kopfzeile in einer Schleife, um einen vertikalen Gradientenfaktor zu berechnen, mischen die je nach Status (Ziehen, Hover oder Standard) abgedunkelten primären und sekundären Cyber-Farben mit BlendColors und DarkenColor, konvertieren sie in ARGB und setzen jedes Pixel zeilenweise mit PixelSet für eine dynamische Schattierung.
Um einen Rahmen hinzuzufügen, mischen wir Primär- und Sekundärfarben mit dem Animationsfaktor und zeichnen mit der Funktion Rectangle äußere und innere Rechtecke in den gemischten ARGB-Farben. Wir haben die Schriftart Arial Black mit FontSet, formatieren den Titel mit Symbolen und Paarnamen mithilfe von StringFormat, erzeugen dann einen Glüheffekt, indem wir versetzte Texte in einem 3x3-Raster (die Mitte wird übersprungen) mit Cyber-Primärfarben in ARGB bei geringer Deckkraft zeichnen, und überlagern den Haupttitel in leuchtendem Pink (ARGB) zentriert mit der Methode TextOut. Als Nächstes müssen wir das Cyberpunk-Regressionsdiagramm zeichnen, doch zunächst benötigen wir einige Hilfsfunktionen zum Darstellen des Rasters und der Datenpunkte.
Zeichnen von Cyberpunk-Rastern und Neonpunkten
Um die Darstellung mehrschichtiger zu gestalten, fügen wir animierte Raster und leuchtende Datenmarkierungen hinzu.
//+------------------------------------------------------------------+ //| Draw cyberpunk grid | //+------------------------------------------------------------------+ void drawCyberpunkGrid(int startX, int startY, int width, int height) { //--- Set grid spacing int gridSpacing = 40; //--- Compute phase double phase = (currentAnimationFrame % 120) * M_PI / 60.0; //--- Compute alpha uchar gridAlpha = (uchar)(30 + 15 * MathSin(phase)); //--- Set grid color color gridColor = CYBER_PRIMARY_COLOR; //--- Get grid ARGB uint argbGrid = ColorToARGB(gridColor, gridAlpha); //--- Loop vertical lines for (int x = startX; x < startX + width; x += gridSpacing) { //--- Loop over height for (int y = startY; y < startY + height; y++) { //--- Blend grid pixel blendPixelSet(mainCanvas, x, y, argbGrid); } } //--- Loop horizontal lines for (int y = startY; y < startY + height; y += gridSpacing) { //--- Loop over width for (int x = startX; x < startX + width; x++) { //--- Blend grid pixel blendPixelSet(mainCanvas, x, y, argbGrid); } } } //+------------------------------------------------------------------+ //| Draw neon point | //+------------------------------------------------------------------+ void drawNeonPoint(int centerX, int centerY, int radius, color pointColor) { //--- Loop for glow for (int glowRadius = radius + 4; glowRadius > radius; glowRadius--) { //--- Compute alpha uchar glowAlpha = (uchar)(80 * glowIntensity * (radius + 4 - glowRadius) / 4.0); //--- Get glow color uint glowColor = ColorToARGB(pointColor, glowAlpha); //--- Draw glow circle drawCirclePoint(centerX, centerY, glowRadius, glowColor); } //--- Get point ARGB uint argbPoint = ColorToARGB(pointColor, 255); //--- Draw point circle drawCirclePoint(centerX, centerY, radius, argbPoint); //--- Get center ARGB uint argbCenter = ColorToARGB(C'255,255,255', 200); //--- Draw center circle drawCirclePoint(centerX, centerY, radius - 1, argbCenter); }
Wir implementieren die Funktion drawCyberpunkGrid, um im Cyberpunk-Modus eine animierte Rasterüberlagerung für den Diagrammbereich zu erstellen. Dabei legen wir den Abstand auf 40 Pixel fest, berechnen anhand von currentAnimationFrame eine Phase für eine sinusförmige Pulsation und variieren die Deckkraft zwischen 30 und 45, um einen dezenten Schimmer zu erzeugen. Als Nächstes verwenden wir die primäre Cyberpunk-Farbe, konvertieren sie in ARGB mit dynamischem Alpha-Kanal und zeichnen dann in einer Schleife vertikale Linien in regelmäßigen Abständen, indem wir mit blendPixelSet Pixel über die gesamte Höhe mischen. Auf ähnliche Weise zeichnen wir horizontale Linien über die gesamte Breite und fügen so einen futuristischen, matrixartigen Hintergrund hinzu, ohne die Daten zu überladen. Dieses Raster trägt durch Überblendungen mit geringer Deckkraft dazu bei, das thematische Eintauchen in die Welt zu verstärken, und sorgt dafür, dass es sich nahtlos in andere Elemente einfügt. Sie können es aber auch weglassen, wenn Sie es nicht wollen.
Um Neon-Effekte hinzuzufügen, erstellen wir die Funktion drawNeonPoint für Datenpunkte. Dabei durchlaufen wir in einer Schleife vom Radius +4 bis hinunter zum Radius +1, um konzentrische Leuchtkreise mit abnehmender Deckkraft zu zeichnen, die mit glowIntensity skaliert werden. Dabei verwenden wir drawCirclePoint (unter der Annahme einer Basis-Kreiszeichnungsfunktion) in der Punktfarbe ARGB. Anschließend zeichnen wir den Mittelpunkt mit vollem Radius und voller Deckkraft, gefolgt von einem kleineren inneren Kreis in halbtransparentem Weiß C'255,255,255' mit einer Deckkraft von 200 als Highlight. So entsteht eine pulsierende Sci-Fi-Visualisierung, die sich bei dunklen Themes besonders gut abhebt. Auch hier lässt sich die Farbauswahl anpassen. Wir haben für die Demonstration lediglich beliebige Werte verwendet. Mit diesen Hilfsfunktionen können wir nun die Cyberpunk-Diagramme darstellen.
Darstellung der Cyberpunk-Regression
Um die Kernanalyse darzustellen, passen wir das Diagramm mit Rastern, Achsen, Skalenstrichen, Linien und Punkten im Cyberpunk-Stil an.
//+------------------------------------------------------------------+ //| Draw regression plot cyberpunk | //+------------------------------------------------------------------+ void drawRegressionPlotCyberpunk() { //--- Return if no data if (!dataLoadedSuccessfully) return; //--- Set plot left int plotAreaLeft = 60; //--- Set plot right int plotAreaRight = currentWidthPixels - 40; //--- Set plot top int plotAreaTop = HEADER_BAR_HEIGHT + 10; //--- Set plot bottom int plotAreaBottom = currentHeightPixels - 50; //--- Set draw left int drawAreaLeft = plotAreaLeft + plotPadding; //--- Set draw right int drawAreaRight = plotAreaRight - plotPadding; //--- Set draw top int drawAreaTop = plotAreaTop + plotPadding; //--- Set draw bottom int drawAreaBottom = plotAreaBottom - plotPadding; //--- Compute plot width int plotWidth = drawAreaRight - drawAreaLeft; //--- Compute plot height int plotHeight = drawAreaBottom - drawAreaTop; //--- Return if invalid if (plotWidth <= 0 || plotHeight <= 0) return; //--- Init min X double minX = primaryClosePrices[0]; //--- Init max X double maxX = primaryClosePrices[0]; //--- Init min Y double minY = secondaryClosePrices[0]; //--- Init max Y double maxY = secondaryClosePrices[0]; //--- Get data points int dataPoints = ArraySize(primaryClosePrices); //--- Loop over points for (int i = 1; i < dataPoints; i++) { //--- Update min X if (primaryClosePrices[i] < minX) minX = primaryClosePrices[i]; //--- Update max X if (primaryClosePrices[i] > maxX) maxX = primaryClosePrices[i]; //--- Update min Y if (secondaryClosePrices[i] < minY) minY = secondaryClosePrices[i]; //--- Update max Y if (secondaryClosePrices[i] > maxY) maxY = secondaryClosePrices[i]; } //--- Compute range X double rangeX = maxX - minX; //--- Compute range Y double rangeY = maxY - minY; //--- Set min range X if (rangeX == 0) rangeX = 1; //--- Set min range Y if (rangeY == 0) rangeY = 1; //--- Draw grid drawCyberpunkGrid(drawAreaLeft, drawAreaTop, plotWidth, plotHeight); //--- Set axis color color axisColor = CYBER_PRIMARY_COLOR; //--- Get axis ARGB uint argbAxis = ColorToARGB(axisColor, 255); //--- Get glow ARGB uint glowColor = ColorToARGB(axisColor, (uchar)(60 * glowIntensity)); //--- Loop for glow for (int g = 1; g <= 2; g++) { //--- Draw Y glow for (int y = plotAreaTop; y <= plotAreaBottom; y++) { //--- Blend left glow blendPixelSet(mainCanvas, plotAreaLeft - g - 1, y, glowColor); //--- Blend right glow blendPixelSet(mainCanvas, plotAreaLeft + g + 1, y, glowColor); } //--- Draw X glow for (int x = plotAreaLeft; x <= plotAreaRight; x++) { //--- Blend bottom glow blendPixelSet(mainCanvas, x, plotAreaBottom + g + 1, glowColor); //--- Blend top glow blendPixelSet(mainCanvas, x, plotAreaBottom - g - 1, glowColor); } } //--- Loop for thick Y-axis for (int thick = 0; thick < 2; thick++) { //--- Draw Y-axis line mainCanvas.Line(plotAreaLeft - thick, plotAreaTop, plotAreaLeft - thick, plotAreaBottom, argbAxis); } //--- Loop for thick X-axis for (int thick = 0; thick < 2; thick++) { //--- Draw X-axis line mainCanvas.Line(plotAreaLeft, plotAreaBottom + thick, plotAreaRight, plotAreaBottom + thick, argbAxis); } //--- Set tick font mainCanvas.FontSet("Consolas", axisLabelFontSize); //--- Get tick label ARGB uint argbTickLabel = ColorToARGB(CYBER_PRIMARY_COLOR, 255); //--- Declare Y ticks double yTickValues[]; //--- Compute Y ticks int numYTicks = calculateOptimalTicks(minY, maxY, plotHeight, yTickValues); //--- Loop over Y ticks for (int i = 0; i < numYTicks; i++) { //--- Get Y value double yValue = yTickValues[i]; //--- Skip out of range if (yValue < minY || yValue > maxY) continue; //--- Compute Y pos int yPos = drawAreaBottom - (int)((yValue - minY) / rangeY * plotHeight); //--- Draw tick mainCanvas.Line(plotAreaLeft - 5, yPos, plotAreaLeft, yPos, argbAxis); //--- Format label string yLabel = formatTickLabel(yValue, rangeY); //--- Draw label mainCanvas.TextOut(plotAreaLeft - 8, yPos - axisLabelFontSize/2, yLabel, argbTickLabel, TA_RIGHT); } //--- Declare X ticks double xTickValues[]; //--- Compute X ticks int numXTicks = calculateOptimalTicks(minX, maxX, plotWidth, xTickValues); //--- Loop over X ticks for (int i = 0; i < numXTicks; i++) { //--- Get X value double xValue = xTickValues[i]; //--- Skip out of range if (xValue < minX || xValue > maxX) continue; //--- Compute X pos int xPos = drawAreaLeft + (int)((xValue - minX) / rangeX * plotWidth); //--- Draw tick mainCanvas.Line(xPos, plotAreaBottom, xPos, plotAreaBottom + 5, argbAxis); //--- Format label string xLabel = formatTickLabel(xValue, rangeX); //--- Draw label mainCanvas.TextOut(xPos, plotAreaBottom + 7, xLabel, argbTickLabel, TA_CENTER); } //--- Compute start Y double lineStartY = regressionIntercept + regressionSlope * minX; //--- Compute end Y double lineEndY = regressionIntercept + regressionSlope * maxX; //--- Set start screen X int lineStartScreenX = drawAreaLeft; //--- Compute start screen Y int lineStartScreenY = drawAreaBottom - (int)((lineStartY - minY) / rangeY * plotHeight); //--- Set end screen X int lineEndScreenX = drawAreaRight; //--- Compute end screen Y int lineEndScreenY = drawAreaBottom - (int)((lineEndY - minY) / rangeY * plotHeight); //--- Get line ARGB uint argbLine = ColorToARGB(CYBER_REGRESSION_COLOR, 255); //--- Loop for width for (int w = 0; w < regressionLineWidth; w++) { //--- Draw line mainCanvas.LineAA(lineStartScreenX, lineStartScreenY + w - regressionLineWidth/2, lineEndScreenX, lineEndScreenY + w - regressionLineWidth/2, argbLine); } //--- Compute pulse phase double pulsePhase = (currentAnimationFrame % 60) * M_PI / 30.0; //--- Compute pulse factor double pulseFactor = (MathSin(pulsePhase) + 1.0) * 0.3 + 0.7; //--- Compute point size int effectivePointSize = (int)(dataPointSize * pulseFactor); //--- Loop over points for (int i = 0; i < dataPoints; i++) { //--- Compute screen X int screenX = drawAreaLeft + (int)((primaryClosePrices[i] - minX) / rangeX * plotWidth); //--- Compute screen Y int screenY = drawAreaBottom - (int)((secondaryClosePrices[i] - minY) / rangeY * plotHeight); //--- Draw neon point drawNeonPoint(screenX, screenY, effectivePointSize, CYBER_DATA_POINTS_COLOR); } //--- Set axis label font mainCanvas.FontSet("Arial Bold", labelFontSize); //--- Get axis label ARGB uint argbAxisLabel = ColorToARGB(CYBER_SECONDARY_COLOR, 255); //--- Set X label string xAxisLabel = "► " + primarySymbol + " (X-AXIS)"; //--- Draw X label mainCanvas.TextOut(currentWidthPixels / 2, currentHeightPixels - 20, xAxisLabel, argbAxisLabel, TA_CENTER); //--- Set Y label string yAxisLabel = "► " + secondarySymbol + " (Y-AXIS)"; //--- Set vertical angle mainCanvas.FontAngleSet(900); //--- Draw Y label mainCanvas.TextOut(12, currentHeightPixels / 2, yAxisLabel, argbAxisLabel, TA_CENTER); //--- Reset angle mainCanvas.FontAngleSet(0); }
Hier implementieren wir die Funktion drawRegressionPlotCyberpunk, um die Regression im Cyberpunk-Modus zu visualisieren. Dabei wird zunächst geprüft, ob Daten vorhanden sind; sind keine vorhanden, wird die Funktion beendet. Anschließend werden die Plot- und Zeichenbereiche mit Abständen festgelegt, die Breiten und Höhen berechnet und die Funktion beendet, falls die Eingabe ungültig ist. Als Nächstes ermitteln wir die Minimal- und Maximalwerte für X (primär) und Y (sekundär) durch Iteration der Preise, passen die Nullbereiche auf 1 an, rufen drawCyberpunkGrid für das Hintergrundraster auf, legen die Achsen auf Cyber-Primär-ARGB fest, fügen mit blendPixelSet Glühebenen um die Y- und X-Achsen für mehr Tiefe hinzu und zeichnen in Schleifen mit Line verdickte Linien. Zum Beschriften legen wir die Schriftart Consolas mit der Primärfarbe Cyber ARGB fest, berechnen die Y-Ticks und zeichnen in einer Schleife kurze Linien sowie rechtsbündig formatierte Beschriftungen mithilfe von formatTickLabel; analog dazu erfolgt dies für die X-Achse mit zentrierten Beschriftungen am unteren Rand.
Für die Linie berechnen wir die Start- und End-Y-Koordinaten anhand von Regressionsparametern, ordnen diese den Bildschirmpositionen zu, verwenden den ARGB-Wert der Regressionslinie und zeichnen in einer Schleife Anti-Aliasing-Segmente mit LineAA, die durch Anpassung der Halbbreite zentriert werden. Um die Punkte hervorzuheben, berechnen wir aus currentAnimationFrame eine pulsierende Phase mit Sinus, einem Skalierungsfaktor von 0,7–1,0, durchlaufen die Datenpunkte, um ihre Bildschirmpositionen zu berechnen, und rendern sie mit drawNeonPoint in der Farbe der Datenpunkte, um einen animierten Leuchteffekt zu erzielen. Abschließend haben wir für die Beschriftungen die fette Schriftart Arial Bold mit Cyber-Sekundär-ARGB festgelegt, fest codierte Pfeil-Präfixe für den besonderen Effekt hinzugefügt – auch dies haben wir der Einfachheit halber getan –, X unten zentriert, Y mit FontAngleSet um 900 gedreht, um eine vertikale Ausrichtung zu erzielen, und den Winkel zurückgesetzt, womit die thematische Darstellung abgeschlossen ist. Bei der Kompilierung erhalten wir dann etwa folgendes Ergebnis.

Nachdem das erledigt ist, können wir die Bedienfelder und die Legende für dieses Cyberpunk-Thema hinzufügen.
//+------------------------------------------------------------------+ //| Draw glass morphism stats panel | //+------------------------------------------------------------------+ void drawGlassMorphismStatsPanel() { //--- Set panel X int panelX = statsPanelX; //--- Set panel Y int panelY = HEADER_BAR_HEIGHT + statsPanelY; //--- Set panel width int panelWidth = statsPanelWidth; //--- Set panel height int panelHeight = statsPanelHeight; //--- Compute bg color color panelBgColor = DarkenColor(CYBER_PRIMARY_COLOR, 0.85); //--- Set alpha uchar bgAlpha = 120; //--- Get panel bg ARGB uint argbPanelBg = ColorToARGB(panelBgColor, bgAlpha); //--- Loop over rows for (int y = panelY; y <= panelY + panelHeight; y++) { //--- Loop over columns for (int x = panelX; x <= panelX + panelWidth; x++) { //--- Blend bg pixel blendPixelSet(mainCanvas, x, y, argbPanelBg); } } //--- Blend border color borderColor = BlendColors(CYBER_PRIMARY_COLOR, CYBER_SECONDARY_COLOR, 0.5); //--- Get border ARGB uint argbBorder = ColorToARGB(borderColor, 255); //--- Get glow ARGB uint glowColor = ColorToARGB(borderColor, (uchar)(60 * glowIntensity)); //--- Loop for glow for (int g = 1; g <= 2; g++) { //--- Draw top glow for (int x = panelX - g; x <= panelX + panelWidth + g; x++) { //--- Blend pixel blendPixelSet(mainCanvas, x, panelY - g, glowColor); } //--- Draw bottom glow for (int x = panelX - g; x <= panelX + panelWidth + g; x++) { //--- Blend pixel blendPixelSet(mainCanvas, x, panelY + panelHeight + g, glowColor); } //--- Draw left glow for (int y = panelY - g; y <= panelY + panelHeight + g; y++) { //--- Blend pixel blendPixelSet(mainCanvas, panelX - g, y, glowColor); } //--- Draw right glow for (int y = panelY - g; y <= panelY + panelHeight + g; y++) { //--- Blend pixel blendPixelSet(mainCanvas, panelX + panelWidth + g, y, glowColor); } } //--- Draw top border for (int x = panelX; x <= panelX + panelWidth; x++) { //--- Blend border pixel blendPixelSet(mainCanvas, x, panelY, argbBorder); //--- Blend bottom border blendPixelSet(mainCanvas, x, panelY + panelHeight, argbBorder); } //--- Draw left and right borders for (int y = panelY; y <= panelY + panelHeight; y++) { //--- Blend left blendPixelSet(mainCanvas, panelX, y, argbBorder); //--- Blend right blendPixelSet(mainCanvas, panelX + panelWidth, y, argbBorder); } //--- Set stats font mainCanvas.FontSet("Consolas", panelFontSize); //--- Get text ARGB uint argbText = ColorToARGB(CYBER_PRIMARY_COLOR, 255); //--- Set text Y int textY = panelY + 8; //--- Set line spacing int lineSpacing = panelFontSize; //--- Format equation string equationText = StringFormat("Y = %.3f + %.3f * X", regressionIntercept, regressionSlope); //--- Draw equation mainCanvas.TextOut(panelX + 8, textY, equationText, argbText, TA_LEFT); //--- Update Y textY += lineSpacing; //--- Format correlation string correlationText = StringFormat("Correlation: %.4f", correlationCoefficient); //--- Draw correlation mainCanvas.TextOut(panelX + 8, textY, correlationText, argbText, TA_LEFT); //--- Update Y textY += lineSpacing; //--- Format R-squared string rSquaredText = StringFormat("R-Squared: %.4f", rSquared); //--- Draw R-squared mainCanvas.TextOut(panelX + 8, textY, rSquaredText, argbText, TA_LEFT); //--- Update Y textY += lineSpacing; //--- Format points string dataPointsText = StringFormat("Points: %d", ArraySize(primaryClosePrices)); //--- Draw points mainCanvas.TextOut(panelX + 8, textY, dataPointsText, argbText, TA_LEFT); } //+------------------------------------------------------------------+ //| Draw glass morphism legend | //+------------------------------------------------------------------+ void drawGlassMorphismLegend() { //--- Set legend X int legendX = statsPanelX; //--- Set legend Y int legendY = HEADER_BAR_HEIGHT + statsPanelY + statsPanelHeight; //--- Set legend width int legendWidth = statsPanelWidth; //--- Set legend height int legendHeightThis = legendHeight; //--- Compute bg color color legendBgColor = DarkenColor(CYBER_SECONDARY_COLOR, 0.85); //--- Set alpha uchar bgAlpha = 120; //--- Get legend bg ARGB uint argbLegendBg = ColorToARGB(legendBgColor, bgAlpha); //--- Loop over rows for (int y = legendY; y <= legendY + legendHeightThis; y++) { //--- Loop over columns for (int x = legendX; x <= legendX + legendWidth; x++) { //--- Blend bg pixel blendPixelSet(mainCanvas, x, y, argbLegendBg); } } //--- Blend border color borderColor = BlendColors(CYBER_SECONDARY_COLOR, CYBER_PRIMARY_COLOR, 0.5); //--- Get border ARGB uint argbBorder = ColorToARGB(borderColor, 255); //--- Get glow ARGB uint glowColor = ColorToARGB(borderColor, (uchar)(60 * glowIntensity)); //--- Loop for glow for (int g = 1; g <= 2; g++) { //--- Draw top and bottom glow for (int x = legendX - g; x <= legendX + legendWidth + g; x++) { //--- Blend top blendPixelSet(mainCanvas, x, legendY - g, glowColor); //--- Blend bottom blendPixelSet(mainCanvas, x, legendY + legendHeightThis + g, glowColor); } //--- Draw left and right glow for (int y = legendY - g; y <= legendY + legendHeightThis + g; y++) { //--- Blend left blendPixelSet(mainCanvas, legendX - g, y, glowColor); //--- Blend right blendPixelSet(mainCanvas, legendX + legendWidth + g, y, glowColor); } } //--- Draw top and bottom borders for (int x = legendX; x <= legendX + legendWidth; x++) { //--- Blend top blendPixelSet(mainCanvas, x, legendY, argbBorder); //--- Blend bottom blendPixelSet(mainCanvas, x, legendY + legendHeightThis, argbBorder); } //--- Draw left and right borders for (int y = legendY; y <= legendY + legendHeightThis; y++) { //--- Blend left blendPixelSet(mainCanvas, legendX, y, argbBorder); //--- Blend right blendPixelSet(mainCanvas, legendX + legendWidth, y, argbBorder); } //--- Set legend font mainCanvas.FontSet("Consolas", panelFontSize); //--- Get text ARGB uint argbText = ColorToARGB(CYBER_SECONDARY_COLOR, 255); //--- Set item Y int itemY = legendY + 10; //--- Set line spacing int lineSpacing = panelFontSize; //--- Draw neon point drawNeonPoint(legendX + 12, itemY, dataPointSize, CYBER_DATA_POINTS_COLOR); //--- Draw data label mainCanvas.TextOut(legendX + 22, itemY - 4, "Data Points", argbText, TA_LEFT); //--- Update Y itemY += lineSpacing; //--- Get line color uint argbLineColor = ColorToARGB(CYBER_REGRESSION_COLOR, 255); //--- Loop to draw line for (int i = 0; i < 15; i++) { //--- Blend line pixel blendPixelSet(mainCanvas, legendX + 7 + i, itemY, argbLineColor); //--- Blend below pixel blendPixelSet(mainCanvas, legendX + 7 + i, itemY + 1, argbLineColor); } //--- Draw line label mainCanvas.TextOut(legendX + 27, itemY - 4, "Regression Line", argbText, TA_LEFT); }
Zunächst implementieren wir die Funktion drawGlassMorphismStatsPanel, um ein halbtransparentes, leuchtendes Statistik-Overlay für das Cyberpunk-Thema zu erstellen. Dabei positionieren wir es anhand der Eingaben mit einem Versatz zur Kopfzeile, verdunkeln die Cyber-Primärfarbe mit DarkenColor für den Hintergrund, legen eine niedrige Deckkraft von 120 fest und den Bereich Pixel für Pixel mithilfe von blendPixelSet überblenden, um einen Milchglaseffekt zu erzielen. Als Nächstes mischen wir die Rahmenfarben aus den Cyber-Primär- und -Sekundärfarben mit BlendColors, bereiten ARGB vor, fügen äußere Leuchteffekte hinzu, indem wir Schleifen um die Rahmen des Panels mit abnehmender Intensität erweitern, und zeichnen dann die oberen, unteren, linken und rechten Rahmen durch das Überblenden von Linien. Wir legen mit FontSet die Schriftart Consolas fest, verwenden Cyber Primary ARGB für den Text, initialisieren Y mit Abständen und Zwischenräumen entsprechend der Schriftgröße, formatieren und zeichnen die Gleichung, die Korrelation, den R-Quadrat-Wert und die Anzahl der Punkte mit StringFormat und TextOut linksbündig und aktualisieren Y in jeder Zeile, um eine vertikale Stapelung zu erzielen.
Ähnlich verfahren wir bei drawGlassMorphismLegend: Wir positionieren die Statistiken mit passender Breite darunter, verdunkeln die Cyber-Sekundärfarbe für den Hintergrund, mischen die Füllfarbe, wenden eine identische Randüberblendung an und fügen eine Leuchteffekterweiterung hinzu. Wir zeichnen die oberen, unteren, linken und rechten Rahmen, legen die Schriftart Consolas mit sekundärem ARGB-Text in der Farbe Cyber fest, zeichnen mithilfe von drawNeonPoint ein Neonpunkt-Symbol an der angepassten Position in der Farbe Cyber, fügen die Beschriftung Datenpunkte hinzu und aktualisieren Y; anschließend fügen wir ein kurzes horizontales Liniensegment (15 Pixel breit, 2 Pixel hoch) in der Farbe Cyber-Regression ein und beschriften es mit Regressionsgerade als visuelle Erläuterung. Für die Neon-Anzeige zur Größenanpassung verfolgen wir den folgenden Ansatz.
Zeichnen eines Neon-Indikators für die Größenänderung
Um die Größenänderung zu erleichtern, zeigen wir hervorgehobene Griffpunkte mit einem Leuchteffekt an.
//+------------------------------------------------------------------+ //| Draw neon resize indicator | //+------------------------------------------------------------------+ void drawNeonResizeIndicator() { //--- Blend indicator color color indicatorColor = BlendColors(CYBER_PRIMARY_COLOR, CYBER_SECONDARY_COLOR, 0.5); //--- Get indicator ARGB uint argbIndicator = ColorToARGB(indicatorColor, 200); //--- Get glow ARGB uint glowColor = ColorToARGB(indicatorColor, (uchar)(100 * glowIntensity)); //--- Check corner if (hoverResizeMode == RESIZE_CORNER || activeResizeMode == RESIZE_CORNER) { //--- Compute corner X int cornerX = currentWidthPixels - resizeGripSize; //--- Compute corner Y int cornerY = currentHeightPixels - resizeGripSize; //--- Loop for glow for (int g = 1; g <= 3; g++) { //--- Loop Y glow for (int y = cornerY - g; y < currentHeightPixels + g; y++) { //--- Loop X glow for (int x = cornerX - g; x < currentWidthPixels + g; x++) { //--- Blend glow blendPixelSet(mainCanvas, x, y, glowColor); } } } //--- Fill corner mainCanvas.FillRectangle(cornerX, cornerY, currentWidthPixels - 1, currentHeightPixels - 1, argbIndicator); //--- Set line color uint lineColor = ColorToARGB(C'255,255,255', 255); //--- Loop for lines for (int i = 0; i < 4; i++) { //--- Compute offset int offset = i * 3; //--- Draw diagonal mainCanvas.Line(cornerX + offset, currentHeightPixels - 1, currentWidthPixels - 1, cornerY + offset, lineColor); } } //--- Check right if (hoverResizeMode == RESIZE_RIGHT_EDGE || activeResizeMode == RESIZE_RIGHT_EDGE) { //--- Compute indicator Y int indicatorY = currentHeightPixels / 2 - 15; //--- Loop for glow for (int g = 1; g <= 3; g++) { //--- Fill glow mainCanvas.FillRectangle(currentWidthPixels - 3 - g, indicatorY - g, currentWidthPixels - 1 + g, indicatorY + 30 + g, glowColor); } //--- Fill indicator mainCanvas.FillRectangle(currentWidthPixels - 3, indicatorY, currentWidthPixels - 1, indicatorY + 30, argbIndicator); } //--- Check bottom if (hoverResizeMode == RESIZE_BOTTOM_EDGE || activeResizeMode == RESIZE_BOTTOM_EDGE) { //--- Compute indicator X int indicatorX = currentWidthPixels / 2 - 15; //--- Loop for glow for (int g = 1; g <= 3; g++) { //--- Fill glow mainCanvas.FillRectangle(indicatorX - g, currentHeightPixels - 3 - g, indicatorX + 30 + g, currentHeightPixels - 1 + g, glowColor); } //--- Fill indicator mainCanvas.FillRectangle(indicatorX, currentHeightPixels - 3, indicatorX + 30, currentHeightPixels - 1, argbIndicator); } }
Wir implementieren die Funktion drawNeonResizeIndicator, um im Cyberpunk-Modus visuelle Hinweise für die Größenänderung zu geben. Dazu mischen wir zunächst die Cyber-Primär- und -Sekundärfarben mit BlendColors für den Indikator, wandeln das Ergebnis in ARGB mit einem Alpha-Wert von 200 um und erstellen eine Leuchtvariante mit einem auf das 100-Fache skalierten Alpha-Wert. Für die Größenänderung der Ecken (beim Hover oder im aktiven Zustand) berechnen wir die Position unten rechts anhand der Griffgröße, fügen durch eine Schleife ein mehrschichtiges Leuchten hinzu, um die erweiterten Bereiche um die Ecke herum mit blendPixelSet auszufüllen, füllen dann das Griffquadrat mit FillRectangle aus und zeichnen mit Line vier versetzte diagonale Linien in reinem Weiß (ARGB), um einen Streifeneffekt zu erzielen.
Als Nächstes zentrieren wir für den rechten Rand einen vertikalen Indikator Y, fügen einen Leuchteffekt hinzu, indem wir in Schleifen leicht verbreiterte und versetzte Rechtecke füllen, gefolgt von der Hauptfüllung. Verfahren Sie ebenso mit der unteren Kante: Setzen Sie die horizontale Markierung X in die Mitte, fügen Sie Leuchtfüllungen hinzu und vervollständigen Sie das Ganze mit dem Hauptmarkierungsrechteck. Dieser Neon-Themenmodus mit Leuchteffekten sorgt für mehr Interaktivität und lässt die Bereiche zur Größenanpassung im dunklen Cyberpunk-Thema durch mehrschichtige Überblendungen, die für Tiefe sorgen, besonders hervorstechen. Wir können unsere Diagramme nun mithilfe der folgenden Logik einheitlich aufrufen und erstellen.
//+------------------------------------------------------------------+ //| Render visualization | //+------------------------------------------------------------------+ void renderVisualization() { //--- Check cyberpunk mode if (isCyberpunkThemeEnabled) { //--- Render cyberpunk renderCyberpunkTheme(); } else { //--- Render standard renderStandardTheme(); } } //+------------------------------------------------------------------+ //| Render standard theme | //+------------------------------------------------------------------+ void renderStandardTheme() { //--- Erase canvas mainCanvas.Erase(0); //--- Check background fill if (enableBackgroundFill) { //--- Draw gradient drawGradientBackground(); } //--- Draw border drawCanvasBorder(); //--- Draw header drawHeaderBar(); //--- Draw toggle button drawThemeToggleButton(); //--- Draw plot drawRegressionPlot(); //--- Check show stats if (showStatistics) { //--- Draw stats panel drawStatisticsPanel(); //--- Draw legend drawLegend(); } //--- Check resize hover if (isHoveringResizeZone && enableResizing) { //--- Draw indicator drawResizeIndicator(); } //--- Update canvas mainCanvas.Update(); } //+------------------------------------------------------------------+ //| Render cyberpunk theme | //+------------------------------------------------------------------+ void renderCyberpunkTheme() { //--- Erase canvas mainCanvas.Erase(0); //--- Draw cyber background drawCyberpunkBackground(); //--- Draw holographic border drawHolographicBorder(); //--- Draw futuristic header drawFuturisticHeader(); //--- Draw toggle button drawThemeToggleButton(); //--- Draw cyber plot drawRegressionPlotCyberpunk(); //--- Check show stats if (showStatistics) { //--- Draw glass stats drawGlassMorphismStatsPanel(); //--- Draw glass legend drawGlassMorphismLegend(); } //--- Check resize hover if (isHoveringResizeZone && enableResizing) { //--- Draw neon indicator drawNeonResizeIndicator(); } //--- Update canvas mainCanvas.Update(); }
Hier implementieren wir die Funktion renderVisualization, um das Diagramm in Abhängigkeit vom Theme-Modus darzustellen. Dabei prüfen wir isCyberpunkThemeEnabled, um für futuristische Effekte renderCyberpunkTheme oder für die klassische Darstellung renderStandardTheme aufzurufen, was einen nahtlosen Wechsel ohne überflüssigen Code ermöglicht. In renderStandardTheme löschen wir die Zeichenfläche mit Erase, zeichnen optional einen Farbverlaufshintergrund, sofern dieser aktiviert ist, fügen Rahmen, die Kopfzeile, die Schaltfläche zum Umschalten des Themes, das Hauptdiagramm zur Regression hinzu und, falls showStatistics auf true gesetzt ist, das Statistikfeld und die Legende; bei Mauszeiger-Überfahrt wird die Größe angepasst und die Funktion aktiviert, ein Indikator hinzugefügt und anschließend mit der Methode Update aktualisiert.
Ebenso löscht renderCyberpunkTheme alle Elemente, zeichnet einen Cyber-Hintergrund, einen holografischen Rahmen, eine futuristische Kopfzeile, eine Umschalt-Schaltfläche, ein Cyber-Diagramm, ein Statistikpanel im Glassmorphism-Stil und eine Legende (sofern angezeigt) sowie gegebenenfalls eine Neon-Größenanpassungsanzeige und führt anschließend eine Aktualisierung durch, wodurch durch spezielle Funktionen thematische Konsistenz gewährleistet wird. Dadurch wird die Visualisierung nun je nach ausgewähltem Themenmodus bedingt dargestellt, allerdings müssen wir noch einen Timer für die Animationen einrichten. Animationen basieren auf einer Timer-Einstellung, die die Bilder in regelmäßigen Abständen aktualisiert, wodurch die Effekte im Cyberpunk-Modus flüssig bleiben.
Timer für Animationen einrichten
//+------------------------------------------------------------------+ //| Initialize expert | //+------------------------------------------------------------------+ int OnInit() { //--- Render visualization renderVisualization(); //--- Enable mouse events ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true); //--- Redraw chart ChartRedraw(); //--- Set timer for animations EventSetMillisecondTimer(50); //--- Return success return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Deinitialize expert | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- Kill timer EventKillTimer(); //--- Destroy canvas mainCanvas.Destroy(); //--- Redraw chart ChartRedraw(); } //+------------------------------------------------------------------+ //| Handle timer | //+------------------------------------------------------------------+ void OnTimer() { //--- Check cyberpunk enabled if (isCyberpunkThemeEnabled) { //--- Increment frame currentAnimationFrame++; //--- Reset frame if (currentAnimationFrame > 360) currentAnimationFrame = 0; //--- Render renderVisualization(); //--- Redraw chart ChartRedraw(); } }
Wir beginnen in OnInit damit, die bisherige Logik um die Unterstützung von Animationen zu erweitern, rufen renderVisualization für die anfängliche Darstellung auf, aktivieren Mausbewegungsereignisse, zeichnen das Diagramm neu und setzen einen 50-Millisekunden-Timer mithilfe von EventSetMillisecondTimer, um regelmäßige Aktualisierungen zu steuern, bevor wir INIT_SUCCEEDED zurückgeben. Anschließend beenden wir in OnDeinit den Timer mit EventKillTimer, um Animationen zu stoppen, die Canvas zu löschen und das Diagramm zur Bereinigung neu zu zeichnen.
Zur Steuerung der Animationen implementieren wir OnTimer: Dabei prüfen wir, ob der Cyberpunk-Modus aktiviert ist, erhöhen den Wert von currentAnimationFrame und setzen ihn zur Wiederholung auf 360 zurück. Anschließend rendern wir die Visualisierung mit renderVisualization neu und zeichnen das Diagramm neu, um framebasierte Änderungen wie Impulse oder Leuchteffekte anzuwenden. Das ist alles. Denken Sie abschließend daran, dass wir in der Kopfzeile eine zusätzliche Schaltfläche hinzugefügt haben, die wir beim Überfahren mit der Maus erkennen müssen. Wir werden die Logik entsprechend erweitern, um sie in den Ereignis-Handler des Diagramms einzubinden.
Umgang mit dem Themenumschalter bei Ereignissen
Um auf Klicks zu reagieren, erweitern wir den Ereignis-Handler für die Schaltflächenerkennung.
//+------------------------------------------------------------------+ //| Handle chart event | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { //--- Check mouse move if (id == CHARTEVENT_MOUSE_MOVE) { //--- Set mouse X int mouseX = (int)lparam; //--- Set mouse Y int mouseY = (int)dparam; //--- Set mouse state int mouseState = (int)sparam; //--- Store prev canvas hover bool previousHoverState = isHoveringCanvas; //--- Store prev header hover bool previousHeaderHoverState = isHoveringHeader; //--- Store prev resize hover bool previousResizeHoverState = isHoveringResizeZone; //--- Store prev theme hover bool previousThemeButtonHover = isHoveringThemeButton; //--- Update canvas hover isHoveringCanvas = (mouseX >= currentPositionX && mouseX <= currentPositionX + currentWidthPixels && mouseY >= currentPositionY && mouseY <= currentPositionY + currentHeightPixels); //--- Update header hover isHoveringHeader = isMouseOverHeaderBar(mouseX, mouseY); //--- Update theme hover isHoveringThemeButton = isMouseOverThemeButton(mouseX, mouseY); //--- Update resize hover isHoveringResizeZone = isMouseInResizeZone(mouseX, mouseY, hoverResizeMode); //--- Check if redraw needed bool needRedraw = (previousHoverState != isHoveringCanvas || previousHeaderHoverState != isHoveringHeader || previousResizeHoverState != isHoveringResizeZone || previousThemeButtonHover != isHoveringThemeButton); //--- Check button press if (mouseState == 1 && previousMouseButtonState == 0) { //--- Check theme button if (isHoveringThemeButton) { //--- Toggle theme isCyberpunkThemeEnabled = !isCyberpunkThemeEnabled; //--- Render renderVisualization(); //--- Redraw chart ChartRedraw(); } //--- Check drag start else if (enableDragging && isHoveringHeader && !isHoveringResizeZone) { //--- Set dragging isDraggingCanvas = true; //--- Set start X dragStartX = mouseX; //--- Set start Y dragStartY = mouseY; //--- Set canvas X canvasStartX = currentPositionX; //--- Set canvas Y canvasStartY = currentPositionY; //--- Disable scroll ChartSetInteger(0, CHART_MOUSE_SCROLL, false); //--- Set redraw needRedraw = true; } //--- Check resize start else if (isHoveringResizeZone) { //--- Set resizing isResizingCanvas = true; //--- Set active mode activeResizeMode = hoverResizeMode; //--- Set start X resizeStartX = mouseX; //--- Set start Y resizeStartY = mouseY; //--- Set initial width resizeInitialWidth = currentWidthPixels; //--- Set initial height resizeInitialHeight = currentHeightPixels; //--- Disable scroll ChartSetInteger(0, CHART_MOUSE_SCROLL, false); //--- Set redraw needRedraw = true; } } //--- Check drag else if (mouseState == 1 && previousMouseButtonState == 1) { //--- Handle drag if (isDraggingCanvas) { //--- Handle drag handleCanvasDrag(mouseX, mouseY); } //--- Handle resize else if (isResizingCanvas) { //--- Handle resize handleCanvasResize(mouseX, mouseY); } } //--- Check button release else if (mouseState == 0 && previousMouseButtonState == 1) { //--- Check active if (isDraggingCanvas || isResizingCanvas) { //--- Reset dragging isDraggingCanvas = false; //--- Reset resizing isResizingCanvas = false; //--- Reset mode activeResizeMode = NO_RESIZE; //--- Enable scroll ChartSetInteger(0, CHART_MOUSE_SCROLL, true); //--- Set redraw needRedraw = true; } } //--- Check redraw if (needRedraw) { //--- Render renderVisualization(); //--- Redraw chart ChartRedraw(); } //--- Update last X lastMouseX = mouseX; //--- Update last Y lastMouseY = mouseY; //--- Update prev state previousMouseButtonState = mouseState; } }
Um das Umschalten der Themenmodus-Schaltfläche zu handhaben, erweitern wir die Ereignisbehandlung von OnChartEvent, um diese Funktion neben den bestehenden Interaktionen zu integrieren. Dabei überprüfen wir zunächst CHARTEVENT_MOUSE_MOVE, extrahieren die Mauskoordinaten und den Mausstatus, speichern frühere Hover-Zustände einschließlich der neuen Themen-Schaltfläche und aktualisieren die Flags für die Zeichenfläche sowie die Kopfzeile mit isMouseOverHeaderBar, die Themen-Schaltfläche über isMouseOverThemeButton und den Größenänderungsbereich über isMouseInResizeZone. Anschließend wird ermittelt, ob aufgrund einer Änderung des Mauszeigers eine Neuzeichnung erforderlich ist.
Beim Herunterdrücken der Maustaste (Zustand 1, zuvor 0) prüfen wir zunächst, ob der Mauszeiger über der Themen-Schaltfläche schwebt, und schalten isCyberpunkThemeEnabled um, führen mit renderVisualization eine Neu-Rendering durch und zeichnen das Diagramm neu; andernfalls wird das Ziehen (sofern aktiviert) und das Bewegen des Mauszeigers über die Kopfzeile ohne Größenänderung durch Setzen von Flags, Erfassen von Startpunkten, Deaktivieren des Scrollens und Markieren der Neuzeichnung behandelt; oder die Größenänderung wird initiiert, wenn sich der Mauszeiger in der Zone befindet, indem der Modus und die Anfangswerte gesetzt sowie das Scrollen deaktiviert werden. Solange diese Funktion aufgerufen ist (Zustand 1, prev 1), rufen wir handleCanvasDrag für das Ziehen oder handleCanvasResize für die Größenänderung auf. Beim Loslassen (Zustand 0, vorher 1) werden Flags und Modus zurückgesetzt, das Scrollen aktiviert und das Flag Neuzeichnen gesetzt. Falls eine Neuzeichnung erforderlich ist, rufen Sie die Funktionen renderVisualization und ChartRedraw auf. Abschließend aktualisieren wir die letzten Mauspositionen und den vorherigen Zustand, um die Kontinuität zu gewährleisten. Wir haben die spezifischen Änderungen zur Verdeutlichung hervorgehoben. Nach dem Kompilieren erhalten wir folgendes Ergebnis:

Aus der Visualisierung geht hervor, dass wir das Regressionsdiagramm durch die Einbindung eines Cyberpunk-Themas aufgewertet und damit unsere Ziele erreicht haben. Nun bleibt nur noch, die Funktionsfähigkeit des Systems zu testen, was im folgenden Abschnitt behandelt wird.
Backtests
Wir haben die Tests durchgeführt, und hier ist die kompilierte Visualisierung in einem einzigen Graphics Interchange Format (GIF) Bitmap-Bildformat.

Während der Tests konnte ohne Verzögerung nahtlos zwischen den Themenmodi umgeschaltet werden und ohne Verzögerung zwischen den Modi. Neon-Leuchteffekte heben Datenpunkte hervor, um Ausreißer besser erkennen zu können, und Animationen pulsierten synchron mit den Bildaktualisierungen, wodurch eine flüssige Leistung gewährleistet wurde.
Schlussfolgerung
Zusammenfassend lässt sich sagen, dass wir das Charting-Tool Regression in MQL5 um einen Cyberpunk-Modus mit Neon-Leuchteffekten, Animationen und holografischen Effekten erweitert haben, um eine eindrucksvolle Visualisierung zu ermöglichen. Wir haben die Umschaltung zwischen Themenmodi, dynamische Hintergründe mit Sternen, leuchtende Rahmen sowie Neonpunkte und -linien integriert und dabei die Kompatibilität mit dem Standardmodus gewahrt. Dieses System mit zwei Themenmodi wertet die Analyse von Symbolpaaren durch eine futuristische Ästhetik auf und unterstützt Echtzeitaktualisierungen sowie Interaktionen für aufschlussreiche Handelserkenntnisse. Nach der Lektüre dieses Artikels werden Sie in der Lage sein:
- Themen in Diagrammen umschalten, um die Sichtbarkeit an unterschiedliche Lichtverhältnisse anzupassen
- Verwenden Sie Neonmarkierungen, um Abweichungen von Korrelationen schnell zu erkennen
- Verwenden Sie pulsierende Linien als visuelle Hinweise auf die Stärke des Trends bei Pair-Trades
In den folgenden Teilen werden wir uns mit der grafischen Darstellung von statistischen Verteilungsmodellen sowie deren Darstellung in Balkendiagrammen befassen. Bleiben Sie dran!
Übersetzt aus dem Englischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/en/articles/21306
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.
Die Übertragung der Trading-Signale in einem universalen Expert Advisor.
Vom Anfänger zum Experten: Automatisierung von Intraday-Strategien
Eine alternative Log-datei mit der Verwendung der HTML und CSS
Automatisierung des Indikators Market Memory Zones: Bereiche, zu denen der Kurs voraussichtlich zurückkehrt
- 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.