English 日本語
preview
Entwicklung des Price Action Analysis Toolkit (Teil 42): Interaktive Chart-Prüfung mit Schaltflächenlogik und statistischen Ebenen

Entwicklung des Price Action Analysis Toolkit (Teil 42): Interaktive Chart-Prüfung mit Schaltflächenlogik und statistischen Ebenen

MetaTrader 5Beispiele |
50 0
Christian Benjamin
Christian Benjamin

Inhalt



Einführung

Willkommen zum nächsten Teil der Serie Entwicklung eines Price Action Analysis Toolkit. Unser Ziel ist es, die Preisaktionsanalyse zu automatisieren und sie für Händler, die sich eher auf die Preisstruktur als auf Blackbox-Indikatoren verlassen, intuitiv und zugänglich zu machen. In diesem Artikel erweitern wir die bisherige Arbeit, in der statistische Werte wie Mittelwert, Standardabweichung, Median und andere Verteilungswerte eingeführt wurden, die aus den typischen Preisen von Kerzen berechnet werden, und zeigen, wie diese Werte auf natürliche Weise wichtigen Marktreferenzniveaus wie Unterstützung, Widerstand und Umkehrpunkten zugeordnet werden können.

Anstatt Parameter fest zu kodieren und Eingaben manuell einzustellen, führen wir jetzt einen interaktiveren Ansatz ein: das „Statistical Dashboard“. Dieses Dashboard platziert Kontrollschaltflächen und bearbeitbare Felder direkt auf dem Chart, sodass die Nutzer bei Bedarf statistische Werte berechnen und visualisieren können. Obwohl viele Funktionen der vorherigen Implementierung beibehalten wurden, stellt dieses Design einen ausgefeilteren, nutzerzentrierten Ansatz für die On-Chart-Analyse dar.

In diesem Artikel werden wir:

  • Die Logik und Motivation erläutern, die hinter dem Statistical Dashboard stehen.
  • Durch die Highlights der MQL5-Implementierung gehen.
  • Beispiele für Ergebnisse und Nutzungsmuster angeben.
  • Die Schlussfolgerungen und die nächsten Schritte zusammenfassen.


Verstehen des Konzepts

Aus Preisdaten berechnete statistische Werte wie Mittelwert, Median, Perzentile und dichtebasierte Modi erfassen den zentralen Trend und die Verteilungsform der jüngsten Preisentwicklung. Diese Werte fallen oft mit wichtigen Reaktionsbereichen des Marktes zusammen: Zonen, in denen sich die Liquidität konzentriert, in die der Preis tendenziell zurückkehrt, oder in denen Ausbrüche und Umkehrungen ihren Ursprung haben. Die Behandlung dieser Statistiken als Referenzwerte unterstützt sowohl diskretionäre als auch systematische Ansätze. Sie sind visuell leicht zu interpretieren und für die automatische Überwachung geeignet. Mehr über Werte wie Mittelwert, Standardabweichung und Median erfahren Sie in dem vorangegangenen Artikel, in dem ich sie ausführlich erläutert habe.

Die Logik dieses Tools basiert auf Schaltflächen, die die Marktanalyse vereinfachen. Das Dashboard wandelt die Eingaben des Entwicklers in eine leichtgewichtige, mit Charts versehene Nutzeroberfläche um, die es den Händlern ermöglicht,:

  • Einen Datums-/Zeitbereich (auf dem Chart oder getippt) zu wählen.
  • Die Verteilungsstatistiken über den ausgewählten Bereich zu berechnen
  • Die Linien für Mittelwert, Standardabweichungsbereiche, Perzentile, Median und Modi zu rendern.
  • Von den Referenzpegel für die Live-Überwachung (Erkennung von Berührungen, Ausbrüchen und Umkehrungen) Snapshots zu machen.
  • Die Snapshot-Daten in CSV zu exportieren.
  • Das Dashboard schnell in einen sauberen Zustand zurückzusetzen.

Dank dieser UX auf dem Chart wird das Rätselraten über die zu verwendenden Einstellungen überflüssig und die Analyseabläufe werden beschleunigt. Es macht das Tool auch sicherer und freundlicher für den Live-Handel: Anstatt den Code ständig zu ändern, können die Händler die Ergebnisse in Sekundenschnelle berechnen lassen und überprüfen.

Flussdiagramm

Das Diagramm zeigt das einfache Interaktionsmodell des Dashboards: Ein einziger Schaltflächenklick führt zu einer von drei klaren Aktionen (Zurücksetzen, Berechnen, Umschalten). Jede Aktion führt deterministische Operationen durch (Daten löschen, Statistiken berechnen und zeichnen oder die Sichtbarkeit umschalten), aktualisiert das Chart sofort und versetzt den EA anschließend in einen Ruhezustand. Durch diesen Arbeitsablauf auf dem Chart entfällt die Notwendigkeit, den Code für Routineanalysen zu bearbeiten, die Entscheidungsfindung wird beschleunigt, und das Risiko, veraltete Objekte oder globale Variable im Terminal zu hinterlassen, wird verringert.


Umsetzung

Wir beginnen damit, die grundlegenden Metadaten für unseren Indikator festzulegen. Wir fügen Header-Kommentare ein, die den Dateinamen, den Autor und das Copyright angeben, um das Skript zu identifizieren und die Eigentumsverhältnisse zu kennzeichnen. In den plattformspezifischen Direktiven setzen wir Eigenschaften wie #property copyright, #property version und #property strict. Diese Direktiven stellen sicher, dass MetaTrader unser Skript korrekt erkennt, strenge Syntaxregeln für eine sicherere Kodierung durchsetzt und eine wichtige Versionierung für die Wartung bietet. Diese Ersteinrichtung ist von entscheidender Bedeutung, da sie sicherstellt, dass sich unser Code nahtlos in den MetaTrader integriert und die Best Practices befolgt werden.

//+------------------------------------------------------------------+
//|                                         Statistical Dashboard.mq5|
//|                               Copyright 2025, Christian Benjamin.|
//|                           https://www.mql5.com/en/users/lynnchris|
//+------------------------------------------------------------------+
#property copyright "https://www.mql5.com/en/users/lynnchris"
#property version   "1.36"
#property strict

Als Nächstes binden wir externe Bibliotheken ein, um die Fähigkeiten unseres Skripts zu erweitern. Die Datei ArrayObj.mqh bietet erweiterte Funktionen zur Verwaltung von Arrays. Auf diese Weise können wir dynamische Sammlungen von Objekten und Daten effizient handhaben – ein wichtiger Aspekt beim Umgang mit mehreren Ebenen, Signalen und UI-Komponenten. Durch diesen modularen Ansatz bleibt unser Code sauber und skalierbar, insbesondere wenn wir Funktionen wie grafische Objekte oder globale Variablen hinzufügen.

// Include array utility library
#include <Arrays/ArrayObj.mqh>

Dann definieren wir Nutzereingabeparameter, mit denen Händler das Verhalten des Indikators anpassen können. Zu diesen Eingabevariablen gehören Rückblickzeiträume, Signalschwellenwerte, visuelle Präferenzen und Kontrollkippschalter. Lookback bestimmt zum Beispiel, wie viele Balken wir auf einmal analysieren, während ZScoreSignalEnter die Empfindlichkeit gegenüber Marktabweichungen steuert. Durch die Offenlegung dieser Parameter können Händler den Indikator an verschiedene Märkte, Zeitrahmen und persönliche Strategien anpassen, was unsere Implementierung flexibel und nutzerfreundlich macht.

// === Inputs ===
input int    Lookback               = 1000;
input bool   ExcludeCurrent         = true;
input bool   UseWeightedByVol       = true;
input int    ModeBins               = 30;
input int    KDEGridPoints          = 100;
input double KDEBandwidthFactor     = 1.0;
input int    RefreshEveryXTicks     = 1;
input double ZScoreSignalEnter      = 2.0;
input double ZScoreSignalExit       = 0.8;
input bool   AllowLongSignals       = true;
input bool   AllowShortSignals      = true;
input bool   SendAlertOnSignal      = false;
input bool   PlaySoundOnSignal      = false;
input string SoundFileOnSignal      = "alert.wav";
input bool   SendPushOnSignal       = false;
input ENUM_TIMEFRAMES TF           = PERIOD_CURRENT;
input int    TimerIntervalSeconds   = 60;
input int    CleanupIntervalSeconds = 3600;
input bool     AutoSnapshotLevels   = false;
input datetime InputRefStart        = 0;
input datetime InputRefEnd          = 0;
input int      MonitorBars          = 20;
input double   TouchTolerancePips   = 3.0;
input double   BreakoutPips         = 5.0;
input double   ReversalPips         = 5.0;
input bool     UseCloseForConfirm   = true;
input bool     UseATRforThresholds  = true;
input double   ATRMultiplier        = 0.5;
input int      ATRperiod            = 14;
input bool     ClearSnapshotOnStart = false;
input int LabelOffset_Mean_Sec   = 0;
input int LabelOffset_Median_Sec = -60;
input int LabelOffset_ModeB_Sec  = -120;
input int LabelOffset_ModeK_Sec  = -180;
input int LabelOffset_Pct_Sec    = -240;
input bool DebugMode = false;
input string InputRefStartStr = "";
input string InputRefEndStr   = "";

Im weiteren Verlauf richten wir interne Zustandsvariablen ein, um den aktuellen Status des Indikators zu verfolgen. Variablen wie awaitingSetStart und refStartChart enthalten Informationen über Nutzerinteraktionen, z. B. ob sie Referenzpunkte setzen, und speichern Referenzzeitstempel. Variablen wie currentSignal geben Aufschluss darüber, ob das System derzeit einen Kauf, Verkauf oder eine neutrale Haltung signalisiert. Diese internen Variablen sind von entscheidender Bedeutung, da sie es unserem Skript ermöglichen, den Kontext über Ticks und Nutzerbefehle hinweg beizubehalten und so ein konsistentes und reaktionsschnelles Verhalten zu gewährleisten.

// Internal variables to track state
bool awaitingSetStart = false;
bool awaitingSetEnd = false;
datetime refStartChart = 0;
datetime refEndChart = 0;
int currentSignal = 0; // 1 for long, -1 for short, 0 for neutral

Zur Handhabung mehrerer Referenzebenen definieren wir einen strukturierten Datentyp namens RefLevel. Diese Struktur enthält Details wie den Namen des Levels, den Preis, ob es berührt wurde, die Anzahl der Berührungen, die höchsten und niedrigsten beobachteten Preise und andere Werte. Anschließend erstellen wir ein Array refLevels[], um mehrere solcher Instanzen zu speichern, sodass wir mehrere Ebenen gleichzeitig überwachen und analysieren können. Die Verwendung eines strukturierten Ansatzes wie diesem sorgt für Klarheit und Skalierbarkeit – wenn Händler weitere Ebenen hinzufügen, kann unser Code diese systematisch und effizient verarbeiten.

struct RefLevel
{
  string name;
  double price;
  bool touched;
  datetime touchTime;
  int touchCount;
  double highest;
  double lowest;
  double avgTouchVol;
  int recentTouches;
  double persistence;
  int result; // -1, 0, 1
  datetime resolvedTime;
};
RefLevel refLevels[]; // Array to hold multiple levels

In unserer Implementierung deklarieren wir zahlreiche Funktionen, um unsere Logik zu modularisieren. So gibt es beispielsweise Funktionen zum Erstellen von Schaltflächen, Kennzeichnungen und grafischen Objekten (CreateButton(), CreateHLine_Pro(), DrawArrowAt()), zum Exportieren von Daten (ExportSnapshotCSV()) und zur Durchführung statistischer Berechnungen (ComputeLevelScore(), Median(), Variance()). Dieser modulare Aufbau ist von entscheidender Bedeutung, da er die Verwaltung der Nutzeroberfläche von den Analyseroutinen trennt und so das Verständnis, die Fehlersuche und die Erweiterung unseres Codes erleichtert. Außerdem können wir so Codeschnipsel in verschiedenen Teilen des Skripts wiederverwenden.

// Forward declarations
void CreateToolbar();
void DeleteToolbar();
void CreateButton(string name,int corner,int xdist,int ydist,int xsize,int ysize,string text);
void CreateButtonStatLabel(string labName,int corner,int xdist,int ydist,string text);
void CreateEditField(string name,int corner,int xdist,int ydist,int xsize,int ysize,string text);
void ExportSnapshotCSV();
void CreateHLine_Pro(string name,double price,double score,string friendlyLabel);
double ComputeLevelScore(int touchCount,double avgTouchVolume,int recentTouches,double persistenceBars,datetime lastTouchTime);
void DrawArrowAt(string name, datetime when, double price, bool isBuy);
void CreatePanel();
void CreateOrUpdateLineText(string name, datetime t, double price, string text);
void RemoveOldObjects(int ageSec);
void ClearSnapshot();
void ClearSnapshotVisuals();
void SnapshotReferenceLevels(double mean_val,double p25,double p75,double median_val,double mode_b,double mode_k,double stddev);
void MonitorReferenceLevels(const MqlRates &rates[], int copied);
double pipToPointMultiplier();
void DeleteObjectIfExists(string name);
void RemoveHistogramObjects();
void SetObjTimestamp(string name);
datetime GetObjTimestamp(string name);
void CleanupMetaForObject(string name);
void CleanupAllMetaGlobals();
double Mean(const double &a[], int n);
double WeightedMean(const double &a[], const double &w[], int n);
double WeightedMeanFromRates(const MqlRates &rates[], int copied);
double Variance(const double &a[], int n, bool sample);
double Median(const double &a[], int n);
double Percentile(const double &a[], int n, double q);
double ModeBinned(const double &a[], int n, int bins);
double ModeKDE(const double &a[], int n, int gridPts, double bwFactor);
double ArrayMin(const double &a[], int n);
double ArrayMax(const double &a[], int n);
bool ComputeStatsFromGlobals(double &mean,double &stddev,double &median,double &modeb,double &modek,double &p25,double &p75,double &zscore);

Anschließend entwickeln wir Nutzfunktionen, die gängige Aufgaben vereinfachen. So werden beispielsweise mit UpdateLabelText() die Kennzeichnungen der Nutzeroberfläche dynamisch aktualisiert, mit TrimString() werden die vom Nutzer eingegebenen Zeichenfolgen bereinigt und mit pipToPointMultiplier() werden Pip-Einheiten in plattformspezifische Punkte für präzise Berechnungen umgerechnet. Diese Hilfsprogramme verbessern die Robustheit, verhindern redundanten Code und sorgen für eine konsistente Handhabung von Daten – Eigenschaften, die für einen professionellen Indikator unerlässlich sind.

// Example: update label text
void UpdateLabelText(string name, string text)
{
  if(ObjectFind(0, name) >= 0)
    ObjectSetString(0, name, OBJPROP_TEXT, text);
  else
    CreateButtonStatLabel(name, 0, 0, 0, text);
}

// Example: trim strings
string TrimString(string s)
{
  // Implementation omitted for brevity
}

Im nächsten Schritt implementieren wir eine umfassende Reset-Routine namens ResetAll(). Diese Funktion löscht Snapshots, entfernt alle grafischen Objekte und die zugehörigen Metadaten, setzt interne Variablen zurück und baut die Nutzeroberfläche neu auf. Diese Funktion ist von entscheidender Bedeutung, da sie es dem Nutzer ermöglicht, die Analyse sauber neu zu starten, ohne den MetaTrader neu zu starten, insbesondere nach einer Änderung der Parameter oder dem Auftreten unerwarteter Zustände. Dies verbessert die Nutzerfreundlichkeit und bewahrt die Integrität der laufenden Analysen.

void ResetAll()
{
  // Clear snapshots, delete objects, reset globals, rebuild UI
  ClearSnapshot();
  RemoveExistingEAObjects();
  CleanupAllMetaGlobals();
  RemoveHistogramObjects();
  DeleteToolbar();
  
  // Reset internal variables
  currentSignal = 0;
  refStartChart = 0;
  refEndChart = 0;
  refSnapshotTaken = false;
  snapshotTakenTime = 0;
  
  // Recreate UI
  CreatePanel();
  CreateToolbar();
}

In der Funktion OnInit() initialisieren wir den Indikator, wenn er geladen wird. Hier generieren wir eindeutige Bezeichner auf der Grundlage des Symbols und des Zeitrahmens, erstellen visuelle Panels und Symbolleisten, richten Zeitgeber für regelmäßige Bereinigungen ein und initialisieren Referenzpunkte auf der Grundlage von Nutzereingaben oder Chart-Anmerkungen. In dieser Einrichtungsphase wird sichergestellt, dass alle erforderlichen Ressourcen – Nutzeroberflächenkomponenten, globale Variablen und interne Zustände – korrekt konfiguriert sind, bevor die Echtzeitdatenverarbeitung beginnt. Eine ordnungsgemäße Initialisierung ist von grundlegender Bedeutung, um Fehler zu vermeiden und einen reibungslosen Betrieb zu gewährleisten.

int OnInit()
{
  // Set base strings for global variables
  S_base = StringFormat("CSTATS_%s_%d", _Symbol, (int)TF);
  CreatePanel();
  if(ClearSnapshotOnStart) ClearSnapshot();
  EventSetTimer(TimerIntervalSeconds);
  CreateToolbar();
  // Additional setup...
  return INIT_SUCCEEDED;
}

Dementsprechend übernimmt OnDeinit() die Bereinigung, wenn der Indikator entfernt wird. Es beendet Zeitgeber, löscht grafische Objekte, löscht globale Variablen und setzt die Umgebung zurück. Dieser Schritt ist von entscheidender Bedeutung, da er Ressourcenverluste verhindert, Unordnung im Chart vermeidet und sicherstellt, dass nachfolgende Indikatoren oder Skripte ohne Störungen funktionieren. Sie erhält die allgemeine Gesundheit und Leistungsfähigkeit des Handelsumfelds aufrecht.

void OnDeinit(const int reason)
{
  EventKillTimer();
  // Delete graphical objects
  DeleteObjectIfExists(S_mean);
  DeleteObjectIfExists(S_panel);
  // Clear global variables
  CleanupAllMetaGlobals();
}

Anschließend implementieren wir mit OnTimer() einen Timer-Handler, der in bestimmten Intervallen ausgeführt wird. Sie entfernt veraltete grafische Objekte und abgelaufene Snapshots auf der Grundlage ihres Alters, um das Chartübersichtlich zu halten und sicherzustellen, dass die angezeigten Daten relevant bleiben. Diese regelmäßige Bereinigung sorgt für Übersichtlichkeit und Leistung, insbesondere bei langen Handelssitzungen mit kontinuierlichem Datenfluss.

void OnTimer()
{
  // Periodic cleanup of old objects
  RemoveOldObjects(CleanupIntervalSeconds);
  // Clear expired snapshots
  if(refSnapshotTaken && snapshotTakenTime > 0 && (TimeCurrent() - snapshotTakenTime) >= CleanupIntervalSeconds)
  {
    ClearSnapshot();
  }
}

Der Kern unserer Echtzeit-Analyse befindet sich in OnTick(). Jedes Mal, wenn ein Markttick aufritt, prüft diese Funktion, ob der Indikator pausiert oder ob er die Verarbeitung auf der Grundlage der Aktualisierungsraten überspringen soll. Anschließend werden aktuelle Marktdaten über die Funktion GetRatesForSelection() abgefragt, die historische Kurse innerhalb nutzerdefinierter Bereiche oder Rückblickzeiträume abruft. Anhand dieser Daten berechnen wir statistische Werte – Mittelwert, Median, Modus und Standardabweichung – mit Funktionen wie ComputeStatsFromRates(). Diese Werte bilden die Grundlage für die Ermittlung von Marktregimen, Abweichungen und potenziellen Signalen.

Anschließend berechnen wir einen Z-Score, der angibt, wie weit der letzte Kurs vom Mittelwert abweicht, und der als Auslöser für Signale dient. Auf der Grundlage von Schwellenwerten aktualisieren wir den aktuellen Signalstatus und markieren die Signale visuell mit Pfeilen, um den Händlern unmittelbare visuelle Anhaltspunkte über die Marktbedingungen zu geben.

void OnTick()
{
  if(GlobalVariableCheck(S_base + "_PAUSED") && GlobalVariableGet(S_base + "_PAUSED") == 1.0)
    return; // Paused
  
  // Throttle refresh rate
  tick_count++;
  if(tick_count < RefreshEveryXTicks) return;
  tick_count = 0;
  
  // Gather data
  MqlRates rates[]; int copied=0;
  if(!GetRatesForSelection(rates, copied))
    return;
  
  // Compute stats
  double mean_val, stddev, median_val, mode_b, mode_k, p25, p75;
  if(!ComputeStatsFromRates(rates, copied, mean_val, stddev, median_val, mode_b, mode_k, p25, p75))
    return;
  
  // Calculate z-score
  double latest = (rates[0].high + rates[0].low + rates[0].close) / 3.0;
  double zscore = (stddev > 0) ? (latest - mean_val) / stddev : 0;
  
  // Store global variables
  GlobalVariableSet(S_base + "_mean", mean_val);
  GlobalVariableSet(S_base + "_zscore", zscore);
  
  // Generate signals
  int newSignal = 0;
  if(zscore >= ZScoreSignalEnter && AllowLongSignals)
    newSignal = 1;
  else if(zscore <= -ZScoreSignalEnter && AllowShortSignals)
    newSignal = -1;
  
  // Update visual signals
  if(newSignal != currentSignal)
  {
    if(newSignal == 1)
      DrawArrowAt(S_arrow_long, iTime(_Symbol, TF, 0), latest, true);
    else if(newSignal == -1)
      DrawArrowAt(S_arrow_short, iTime(_Symbol, TF, 0), latest, false);
    currentSignal = newSignal;
  }
}

Um die Datenanalyse zu erleichtern, entwickeln wir Funktionen wie GetRatesForSelection() und ComputeStatsFromRates(). Im ersten Fall werden die relevanten Marktdaten unter Berücksichtigung der vom Nutzer angegebenen Datumsbereiche oder Rückblickzeiträume abgerufen, um sicherzustellen, dass sich die Analyse auf den Bereich konzentriert, der für den Händler von Interesse ist. Der zweite führt statistische Berechnungen durch – Mittelwert, Median, Modi – und verwendet robuste Algorithmen wie Arraysortierung und Perzentilberechnung. Diese Funktionen bilden das Rückgrat der analytischen Fähigkeiten des Indikators, indem sie die rohen Marktdaten in aussagekräftige Erkenntnisse für die Entscheidungsfindung umwandeln.

bool GetRatesForSelection(MqlRates &rates[], int &copied)
{
  // Fetch data based on date range or lookback
  if(UseDateRangeOnChart && refStartChart > 0 && refEndChart > 0)
  {
    int shiftStart = iBarShift(_Symbol, TF, refEndChart, false);
    int shiftEnd = iBarShift(_Symbol, TF, refStartChart, false);
    int startShift = MathMin(shiftStart, shiftEnd);
    int endShift = MathMax(shiftStart, shiftEnd);
    int count = endShift - startShift + 1;
    ArrayResize(rates, count);
    copied = CopyRates(_Symbol, TF, startShift, count, rates);
    return copied > 0;
  }
  else
  {
    int startShift = ExcludeCurrent ? 1 : 0;
    int needed = Lookback;
    ArrayResize(rates, needed);
    copied = CopyRates(_Symbol, TF, startShift, needed, rates);
    return copied > 0;
  }
}

Interaktivität ist ein wichtiger Aspekt; wir entwickeln Funktionen zur Verwaltung von Nutzereingaben und -interaktionen. CreateButton(), CreateEditField() und CreateToolbar() erzeugen beispielsweise UI-Steuerelemente im Chart, mit denen Händler Parameter anpassen oder Aktionen auslösen können. Die Funktion OnChartEvent() verarbeitet Nutzerklicks, Tastenbetätigungen und Objektänderungen und aktualisiert interne Variablen oder Referenzpunkte entsprechend. Dieses Design macht den Indikator äußerst anpassungsfähig und ermöglicht es Händlern, die Analyseparameter im Handumdrehen anzupassen, was in dynamischen Handelsumgebungen unerlässlich ist.

bool ComputeStatsFromRates(const MqlRates &rates[], int copied, double &mean, double &stddev, double &median, double &modeb, double &modek, double &p25, double &p75)
{
  // Extract data
  double vals[]; ArrayResize(vals, copied);
  for(int i=0; i<copied; i++)
  {
    double tp = (rates[i].high + rates[i].low + rates[i].close) / 3.0;
    vals[i] = tp;
  }
  // Compute measures
  mean = Mean(vals, copied);
  stddev = MathSqrt(Variance(vals, copied, true));
  median = Median(vals, copied);
  p25 = Percentile(vals, copied, 0.25);
  p75 = Percentile(vals, copied, 0.75);
  modeb = ModeBinned(vals, copied, ModeBins);
  modek = ModeKDE(vals, copied, KDEGridPoints, KDEBandwidthFactor);
  return true;
}

Wenn Sie auf die Schaltfläche „Mean“ (Mittelwert) klicken, erkennt die Funktion OnChartEvent() diese Aktion über das Ereignis CHARTEVENT_OBJECT_CLICK. Der Code stellt dann fest, dass es sich bei dem angeklickten Objekt tatsächlich um die Schaltfläche „Mean“ handelt, indem er ihren Namen überprüft. Nach der Bestätigung fährt der EA fort, den relevanten Marktdatenbereich durch den Aufruf von GetRatesForSelection() abzufragen. Wenn dieser Datenabruf fehlschlägt, d. h. keine Daten verfügbar sind oder ein Fehler auftritt, aktualisiert das Script die entsprechende Kennzeichnung, um den Nutzer darüber zu informieren, dass keine Daten gefunden wurden, und beendet sich dann.

void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
   if(id != CHARTEVENT_OBJECT_CLICK)
      return;

   string objName = sparam;

   if(objName == S_base + "_BTN_SHOWMEAN")
   {
      // Step 1: Sample data for the selected range
      MqlRates rates[];
      int copied=0;
      if(!GetRatesForSelection(rates, copied) || copied <= 0)
      {
         // Failure: No data available
         UpdateLabelText(S_base + "_LBL_BTN_MEAN", "No data for selection");
         return;
      }

      // Step 2: Compute statistics (mean, stddev, etc.)
      double mean_val, stddev, median_val, mode_b, mode_k, p25, p75;
      if(!ComputeStatsFromRates(rates, copied, mean_val, stddev, median_val, mode_b, mode_k, p25, p75))
      {
         // Failure: Calculation failed
         UpdateLabelText(S_base + "_LBL_BTN_MEAN", "Compute failed");
         return;
      }

      // Step 3: Draw horizontal line at mean
      CreateHLine_Pro(S_mean, mean_val, 0.85, "Mean");

      // Step 4: Update label with computed mean
      string rangeTxt = "";
      if(UseDateRangeOnChart && refStartChart > 0 && refEndChart > 0)
         rangeTxt = StringFormat("%s -> %s", TimeToString(refStartChart, TIME_DATE|TIME_MINUTES), TimeToString(refEndChart, TIME_DATE|TIME_MINUTES));
      else
         rangeTxt = StringFormat("Lookback %d bars", copied);

      CreateOrUpdateLineText(S_mean + "_TXT", iTime(_Symbol, TF, 0), mean_val, StringFormat("Mean: %s | %s | N=%d", DoubleToString(mean_val, _Digits), rangeTxt, copied));
      UpdateLabelText(S_base + "_LBL_BTN_MEAN", "Mean: " + DoubleToString(mean_val, _Digits));

      // End of process for button press
      return;
   }

   // Similar structure applies for other buttons like Std, Mode, Draw Levels, etc.
}

Wenn der Datenabruf erfolgreich ist, fährt der EA mit der Berechnung des Mittelwerts fort, indem er die abgetasteten Daten an ComputeStatsFromRates() übergibt. Sollte die Berechnung aus irgendeinem Grund fehlschlagen, wird die Kennzeichnung aktualisiert, um den Fehler anzuzeigen, und der Vorgang wird beendet. Andernfalls zeichnet der EA nach erfolgreicher Berechnung mit CreateHLine_Pro() eine horizontale Linie am berechneten Mittelwert, wobei er ihr Aussehen und ihre Kennzeichnung der Übersichtlichkeit halber anpasst. Gleichzeitig aktualisiert das Skript die statistische Kennzeichnung neben der Schaltfläche mit dem neuen Mittelwert und gibt so ein unmittelbares visuelles Feedback. Nach Beendigung dieser Schritte ist der Prozess abgeschlossen und bereit für die nächste Nutzerinteraktion. Dieser Ablauf stellt sicher, dass jeder Tastendruck eine Abfolge von Datenerfassung, Analyse und visuellen Aktualisierungen auslöst, wodurch das Tool interaktiv und informativ wird.

Logik der Schaltfläche

Visuelle Hilfen sind für eine schnelle Interpretation von zentraler Bedeutung; Wir implementieren Funktionen wie CreateHLine_Pro(), DrawArrowAt() und CreateOrUpdateLineText(). Diese Funktionen zeichnen horizontale Linien, die statistische Niveaus wie Mittelwert oder Median anzeigen, zeichnen Pfeile, die Ausbrüche oder Umkehrungen signalisieren, und zeigen Textanmerkungen mit detaillierten Informationen an.

void CreateHLine_Pro(string name, double price, double score, string friendlyLabel)
{
  int width = 1 + (int)MathRound(score * 3.0);
  color col = clrDodgerBlue;
  // Determine color/style based on label
  if(ObjectFind(0, name) >= 0)
  {
    ObjectSetDouble(0, name, OBJPROP_PRICE, price);
    ObjectSetInteger(0, name, OBJPROP_COLOR, col);
    ObjectSetInteger(0, name, OBJPROP_WIDTH, width);
    ObjectSetInteger(0, name, OBJPROP_STYLE, STYLE_SOLID);
  }
  else
  {
    ObjectCreate(0, name, OBJ_HLINE, 0, 0, price);
    ObjectSetDouble(0, name, OBJPROP_PRICE, price);
    ObjectSetInteger(0, name, OBJPROP_COLOR, col);
    ObjectSetInteger(0, name, OBJPROP_WIDTH, width);
    ObjectSetInteger(0, name, OBJPROP_STYLE, STYLE_SOLID);
  }
  // Add label
  CreateOrUpdateLineText(name + "_TXT", iTime(_Symbol, TF, 0), price, friendlyLabel);
}

Diese visuellen Marker helfen Händlern, kritische Niveaus oder Signale sofort zu erkennen, ohne sich durch Zahlen zu wühlen, und verbessern so die Entscheidungsgeschwindigkeit und -genauigkeit.

void DrawArrowAt(string name, datetime when, double price, bool isBuy)
{
  if(ObjectFind(0, name) >= 0)
    ObjectDelete(0, name);
  color col = isBuy ? clrLime : clrMaroon;
  int arrowCode = isBuy ? 233 : 234; // Up or down arrow
  ObjectCreate(0, name, OBJ_ARROW, 0, when, price);
  ObjectSetInteger(0, name, OBJPROP_ARROWCODE, arrowCode);
  ObjectSetInteger(0, name, OBJPROP_COLOR, col);
  ObjectSetInteger(0, name, OBJPROP_WIDTH, 1);
}

Die Überwachung der Marktinteraktionen mit den Referenzwerten wird von MonitorReferenceLevels() übernommen. Diese Funktion verfolgt, wie der Kurs mit vordefinierten Niveaus interagiert, und erkennt Berührungen, Ausbrüche und Umkehrungen. Es aktualisiert die Anzahl der Berührungen, die höchsten und niedrigsten beobachteten Preise und die Persistenzwerte, um die Signifikanz jeder Ebene zu bewerten. Wenn bestimmte Kriterien erfüllt sind – wie z. B. das mehrfache Berühren eines Niveaus oder das Durchbrechen von Schwellenwerten – löst die Funktion das Niveau auf und löst visuelle oder Alarm-Benachrichtigungen aus, was den Händlern hilft, Wendepunkte des Marktes zu erfassen.

void MonitorReferenceLevels(const MqlRates &rates[], int copied)
{
  // For each level, check touch, breakout, or reversal conditions
  for(int i=0; i<ArraySize(refLevels); i++)
  {
    RefLevel &L = refLevels[i];
    // Touch detection
    if(!L.touched)
    {
      if(rates[0].high >= L.price - touchTol && rates[0].low <= L.price + touchTol)
      {
        L.touched = true;
        L.touchTime = rates[0].time;
        L.touchCount++;
        // Update visual
        CreateOrUpdateLineText(...);
      }
    }
    else
    {
      // Check for breakout or reversal
      if(rates[0].high >= L.price + breakoutThreshold)
        L.result = 1; // Breakout
      else if(rates[0].low <= L.price - reversalThreshold)
        L.result = -1; // Reversal
      // Draw outcome
      DrawOutcome(L, L.result == 1);
    }
  }
}

Der Indikator unterstützt auch die Erfassung von Momentaufnahmen des aktuellen Marktzustands durch SnapshotReferenceLevels(). Diese Funktion zeichnet die aktuellen Werte der Ebenen auf, berechnet die Punktzahl auf der Grundlage der Berührungsaktivität und speichert sie zur späteren Verwendung.

void SnapshotReferenceLevels(double mean_val, double p25, double p75, double median_val, double mode_b, double mode_k, double stddev)
{
  // Store snapshot data
  snapshot_mean = mean_val;
  snapshot_p25 = p25;
  snapshot_p75 = p75;
  snapshot_median = median_val;
  snapshot_modeb = mode_b;
  snapshot_modek = mode_k;
  // Visualize snapshot
  ClearSnapshotVisuals();
  // Create lines
  CreateHLine_Pro(...);
  CreateOrUpdateLineText(...);
  refSnapshotTaken = true;
  snapshotTakenTime = TimeCurrent();
}

Die Snapshot-Daten können dann über ExportSnapshotCSV() in CSV-Dateien exportiert werden, sodass Händler historische Niveaus analysieren, verschiedene Marktregime vergleichen oder Daten extern austauschen können. Diese Fähigkeit vertieft die Marktanalyse und ermöglicht eine Offline-Überprüfung und strategische Planung.

void ExportSnapshotCSV()
{
  // Save snapshot data to CSV file
  string filename = StringFormat("CSTATS_SNAPSHOT_%s_%d.csv", _Symbol, (int)TimeCurrent());
  int handle = FileOpen(filename, FILE_WRITE | FILE_CSV);
  // Write headers and data
  FileWrite(handle, "symbol", _Symbol);
  // ...
  FileClose(handle);
}

In unserer gesamten Implementierung verfolgen wir einen einheitlichen Ansatz für die Verwaltung von grafischen Objekten und Metadaten. Funktionen wie DeleteObjectIfExists(), SetObjTimestamp() und CleanupMetaForObject() stellen sicher, dass Objekte korrekt erstellt, aktualisiert und gelöscht werden, um Unordnung zu vermeiden und Datenintegrität zu gewährleisten. Durch eine ordnungsgemäße Zeitstempelverwaltung können wir nachvollziehen, wann Objekte zuletzt geändert oder erstellt wurden, was die Bereinigung veralteter Darstellungen und die Aufrechterhaltung eines präzisen Chart-Overlays erleichtert.

void CreatePanel()
  {
   if(ObjectFind(0, S_panel) >= 0)
      ObjectDelete(0, S_panel);
   if(!ObjectCreate(0, S_panel, OBJ_LABEL, 0, 0, 0))
     {
      if(DebugMode)
         Print("CreatePanel: ObjectCreate failed: ", GetLastError());
      return;
     }
   ObjectSetInteger(0, S_panel, OBJPROP_CORNER, CORNER_LEFT_UPPER);
   ObjectSetInteger(0, S_panel, OBJPROP_XDISTANCE, 6);
   ObjectSetInteger(0, S_panel, OBJPROP_YDISTANCE, 24);
   ObjectSetString(0, S_panel, OBJPROP_TEXT, "Statistical — Dashboard");
   ObjectSetInteger(0, S_panel, OBJPROP_FONTSIZE, 11);
   ObjectSetInteger(0, S_panel, OBJPROP_SELECTABLE, false);
#ifdef __MQL5__
   ObjectSetInteger(0, S_panel, OBJPROP_BACK, false);
#endif
   SetObjTimestamp(S_panel);
  }



Ergebnisse

In diesem Abschnitt wird dargestellt, was der EA in der Praxis produziert und wie diese Ergebnisse zu lesen sind. Das folgende Chart zeigt, wie der EA nach dem Anhängen an das Chart in aller Ruhe arbeitet. Auf der linken Seite sehen Sie das Bedienfeld und die Symbolleiste des statistischen Dashboards mit den Schaltflächen „Mean“, „Std“, „Mode“, „"Draw Levels“, „Snapshot“, „Apply Dates“ und „Reset All“. Neben jeder Schaltfläche befinden sich eine einen Abstand haltende Objekt, das mit den berechneten Statistiken aktualisiert werden, sobald Sie sie drücken. In der oberen rechten Ecke befindet sich ein Steuerelement, mit dem Sie nutzerdefinierte Datumsbereiche oder Bezugspunkte eingeben können. Derzeit sind noch keine statistischen Niveaus berechnet worden, sodass das Chart nur einige Terminalprimitive und einige übrig gebliebene horizontale Linien aus früheren Läufen zeigt.

Wenn Sie auf Schaltflächen wie „Mean“ oder „Draw Levels“ (Ebenen zeichnen) klicken, nimmt der EA eine Stichprobe aus dem ausgewählten Bereich, unabhängig davon, ob er auf dem Rückblickzeitraum oder bestimmten Chart-Daten basiert, und berechnet Werte wie Mittelwert, Standardabweichung, Perzentile, Median und Modi. Er zeichnet dann die entsprechenden horizontalen Linien und Kennzeichnungen, füllt die Statistikfelder aus, und wenn Sie sich für einen Schnappschuss entscheiden, beginnt er mit der Überwachung dieser Niveaus auf Berührungen, Persistenz und mögliche Signale wie Ausbrüche oder Umkehrungen.

Ergebnis 1

Im Folgenden zeige ich Ihnen, wie Sie den Periodenbereich festlegen, d. h. das Anfangs- und Enddatum für Ihre Analyse bestimmen.

Einstellung Zeitbereich

Lassen Sie uns nun die Gesamtleistung betrachten.

Der EA ist mit dem eingestellten Datumsbereich gestartet (Start: 2025.08.14 11:00 – Ende: 2025.09.23 04:00) und die beiden Zeitstempel in der Grafik. Jede Schaltfläche auf dem Dashboard funktioniert sofort: Wenn Sie auf Mean, Std., Mode, Median, P75/P25 usw. drücken, wird die Kennzahl für das ausgewählte Intervall berechnet und als horizontale Linie mit einer Text-Kennzeichnung auf dem Chart dargestellt. Mit der Funktion „Ebenen zeichnen“ wird eine Gruppe ausgewählter Ebenen auf einmal gezeichnet, mit der Funktion „Ebenen entfernen“ werden sie gelöscht, und mit der Funktion „Snapshot/Save Snapshot“ wird die aktuelle Einstellung für später gespeichert.


Schlussfolgerung

Die Stärke dieses Tools liegt in seiner knopfgesteuerten Logik, die das Testen und Analysieren von Charts schnell und interaktiv macht. Mit einem einzigen Klick werden statistische Werte wie Mittelwert, Standardabweichung, Modus, Median und Perzentile sofort berechnet und im Chart als beschriftete horizontale Linien angezeigt. Dies macht manuelle Berechnungen überflüssig und ermöglicht schnelle Vergleiche, das Ziehen oder Entfernen von Füllständen während der Prüfung. Die Möglichkeit, Bereiche anzuwenden oder zurückzusetzen, Schnappschüsse zu speichern und Levels direkt über das Dashboard zu kontrollieren, macht es zu einem leistungsstarken Assistenten, um zu untersuchen, wie der Preis auf verschiedene statistische Zonen reagiert, was sowohl die Forschung als auch die Echtzeitanalyse rationalisiert.

Unterstützung und Widerstand

Das zusätzliche Diagramm unterstreicht diese Schlussfolgerung, indem es zeigt, wie die berechneten Werte mit dem tatsächlichen Marktverhalten übereinstimmen. Die lila gestrichelte Linie (25. Perzentil) und die gelbe durchgezogene Linie (Mittelwert plus Abweichung) zeigen, dass diese statistischen Ergebnisse durchweg als echte Unterstützungs- und Widerstandszonen fungieren. Die Kursreaktionen um diese Niveaus herum sind ein klarer Beweis dafür, dass das Tool wichtige Abprall- und Retracement-Bereiche definiert und zuverlässige Punkte für die Antizipation von Ausbrüchen und Umkehrungen hervorhebt.

Dieses Tool soll bei der Analyse des Preisniveaus durch statistische Berechnungen helfen. Es führt keine Geschäfte in Ihrem Namen aus – die Entscheidung bleibt bei Ihnen. Betrachten Sie ihn als einen Level-Helfer EA, der am besten neben Ihrer eigenen Handelsstrategie funktioniert. Ziel ist es, Klarheit darüber zu schaffen, wie der Preis mit statistisch abgeleiteten Niveaus interagiert, um einen besseren Kontext für Ausbrüche, Umkehrungen und Rückschritte zu bieten. Ich freue mich darauf, in Zukunft weitere Tools zu entwickeln, die die Preisaktionsanalyse vertiefen und fundierte Handelsentscheidungen unterstützen.

Übersetzt aus dem Englischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/en/articles/19697

Beigefügte Dateien |
Aufbau eines Handelssystems (Teil 4): Wie zufällige Ausstiege die Handelserwartung beeinflussen Aufbau eines Handelssystems (Teil 4): Wie zufällige Ausstiege die Handelserwartung beeinflussen
Viele Händler haben diese Erfahrung gemacht, sie halten sich oft an ihre Einstiegskriterien, aber sie haben Probleme mit dem Handelsmanagement. Selbst bei den richtigen Setups können emotionale Entscheidungen – wie z. B. panische Ausstiege vor Erreichen des Take-Profit- oder Stop-Loss-Niveaus – zu einer fallenden Kapitalkurve führen. Wie können Händler dieses Problem lösen und ihre Ergebnisse verbessern? Dieser Artikel geht auf diese Fragen ein, indem er zufällige Gewinnraten untersucht und anhand von Monte-Carlo-Simulationen aufzeigt, wie Händler ihre Strategien verfeinern können, indem sie bei angemessenen Niveaus Gewinne mitnehmen, bevor das ursprüngliche Ziel erreicht ist.
Automatisieren von Handelsstrategien in MQL5 (Teil 33): Erstellung des Preisaktions-Systems des harmonischen Musters Shark Automatisieren von Handelsstrategien in MQL5 (Teil 33): Erstellung des Preisaktions-Systems des harmonischen Musters Shark
In diesem Artikel entwickeln wir das System des Shark-Musters in MQL5, das steigende und fallende harmonische Shark-Muster unter Verwendung von Umkehrpunkten und Fibonacci-Ratios identifiziert und Handelsgeschäfte mit anpassbaren Einstiegs-, Stop-Loss- und Take-Profit-Levels basierend auf vom Nutzer ausgewählten Optionen ausführt. Wir verbessern den Einblick des Händlers mit visuellem Feedback durch Chart-Objekte wie Dreiecke, Trendlinien und Kennzeichnungen, um die X-A-B-C-D-Musterstruktur klar darzustellen.
MQL5-Assistenten-Techniken, die Sie kennen sollten (Teil 80): Verwendung von Ichimoku-Muster und des ADX-Wilder mit TD3 Reinforcement Learning MQL5-Assistenten-Techniken, die Sie kennen sollten (Teil 80): Verwendung von Ichimoku-Muster und des ADX-Wilder mit TD3 Reinforcement Learning
Dieser Artikel schließt an Teil 74 an, in dem wir die Paarung von Ichimoku und ADX im Rahmen des überwachten Lernens untersuchten, und verlagert den Schwerpunkt auf das Bestärkende Lernen. Ichimoku und ADX bilden eine komplementäre Kombination von Unterstützungs-/Widerstandskartierung und Trendstärkemessung. In dieser Folge wird gezeigt, wie der Twin Delayed Deep Deterministic Policy Gradient (TD3) Algorithmus mit diesem Indikatorensatz verwendet werden kann. Wie bei früheren Teilen der Serie erfolgt die Implementierung in einer nutzerdefinierten Signalklasse, die für die Integration mit dem MQL5-Assistenten entwickelt wurde, was eine problemlose Zusammenstellung von Expert Advisors ermöglicht.
Wie man ein zyklusbasiertes Handelssystem aufbaut und optimiert (Detrended Price Oscillator – DPO) Wie man ein zyklusbasiertes Handelssystem aufbaut und optimiert (Detrended Price Oscillator – DPO)
Dieser Artikel erklärt, wie man ein Handelssystem mit dem Detrended Price Oscillator (DPO) in MQL5 entwickelt und optimiert. Er umreißt die Kernlogik des Indikators und zeigt, wie er kurzfristige Zyklen erkennt, indem er langfristige Trends herausfiltert. Anhand einer Reihe von Schritt-für-Schritt-Beispielen und einfachen Strategien lernen die Leser, wie man den Code erstellt, Ein- und Ausstiegssignale definiert und Backtests durchführt. Schließlich werden praktische Optimierungsmethoden vorgestellt, um die Leistung zu verbessern und das System an die sich ändernden Marktbedingungen anzupassen.