English 日本語
preview
Entwicklung eines nutzerdefinierten Indikators für die Kontoperformance-Matrix

Entwicklung eines nutzerdefinierten Indikators für die Kontoperformance-Matrix

MetaTrader 5Beispiele |
101 0
Hlomohang John Borotho
Hlomohang John Borotho

Einführung

Viele Händler haben oft mit übermäßigem Handel, mangelnder Disziplin und emotionalen Entscheidungen zu kämpfen, vor allem, wenn sie nach den strengen Regeln von Prop-Firmen handeln oder ihr eigenes Kapital verwalten. Die Versuchung, den Verlusten hinterherzulaufen, die Losgröße nach einer Pechsträhne zu erhöhen oder die Tageslimits zu ignorieren, führt häufig dazu, dass die Drawdown-Regeln verletzt und die Konten vorzeitig aufgelöst werden. Selbst erfahrene Händler können in die Falle tappen, wenn sie die täglichen Risikogrenzen nicht einhalten, was zu einer inkonsistenten Leistung führt und dazu, dass sie keine langfristige Rentabilität erreichen oder finanzierte Handelsherausforderungen bestehen.

Der Indikator der Performance-Matrix des Kontos bietet eine praktische Lösung, indem er als eingebauter Risikomanager und Disziplin-Coach fungiert. Da die Händler ihren eigenen täglichen Risikoprozentsatz innerhalb der Grenzen der Regeln für den maximalen Drawdown festlegen können, werden Kontokapital, täglicher Gewinn/Verlust und Drawdowns automatisch in Echtzeit verfolgt. Wird der tägliche Risikoschwellenwert erreicht, schließt der Indikator alle Positionen und sperrt den Handel bis zum Beginn der nächsten Sitzung, wodurch die Möglichkeit eines Wiedergutmachungs-Handels oder des emotionalen Overtradings ausgeschlossen wird. Diese Struktur hilft den Händlern, Beständigkeit aufzubauen, ihr Kapital zu schützen und ihre Chancen zu erhöhen, die Herausforderungen der Prop-Firma zu bestehen oder ihre persönlichen Konten stetig zu vergrößern.



Planung und Logik

1. Konservative Einstellung:

  • Account Balance = $10,000
  • Max Overall Drawdown (6%) = $600
  • Max Daily Drawdown (8%) = $800
  • Daily Risk (1%) = $100

Der Handel kann nur $100 pro Tag verlieren. Damit haben sie 6 Verlusttage, bevor sie den maximalen Drawdown erreichen, was innerhalb der Sicherheitsgrenzen der Prop-Firmen liegt.

2. Moderate Einstellung:

  • Account Balance = $10,000
  • Max Overall Drawdown (6%) = $600
  • Max Dailydown (8%) = $800
  • Daily Risk (2%) = $200

Der Händler riskiert 200 Dollar pro Tag. Sie haben jetzt etwa 3 Tage Zeit, bevor der maximale Drawdown erreicht wird. Dies ist aggressiver, aber mit weniger Spielraum.

3. Aggressive Einstellung (nicht empfehlenswert):

  • Account Balance = $10,000
  • Max Overall Drawdown (6%) = $600
  • Max Dailydown (8%) = $800
  • Daily Risk (5%) = $500

Ein schlechter Tag könnte 500 $ vernichten, sodass nur sehr wenig Spielraum unter dem maximalen Drawdown von 600 $ bleibt. Wenn das Risiko nicht kontrolliert wird, ist das Scheitern fast garantiert.

Merkmale des Indikators:

  • Das Dashboard zeigt die Leistungskennzahlen des Kontos
  • Visueller Risikostatus des Handels
  • Gewinn/Verlust (P/L), der auf dem Chart für geöffnete Positionen angezeigt wird


Logik des Risikomanagements


Die ersten Schritte

//+------------------------------------------------------------------+
//|                                                   Acc_Matrix.mq5 |
//|                        GIT under Copyright 2025, MetaQuotes Ltd. |
//|                     https://www.mql5.com/en/users/johnhlomohang/ |
//+------------------------------------------------------------------+
#property copyright "GIT under Copyright 2025, MetaQuotes Ltd."
#property link      "https://www.mql5.com/en/users/johnhlomohang/"
#property version   "1.00"
#property indicator_chart_window
#property indicator_buffers 2
#property indicator_plots   2
#property indicator_type1   DRAW_LINE
#property indicator_color1  clrDodgerBlue
#property indicator_style1  STYLE_SOLID
#property indicator_width1  1
#property indicator_type2   DRAW_LINE
#property indicator_color2  clrCrimson
#property indicator_style2  STYLE_SOLID
#property indicator_width2  1
#include <Trade\Trade.mqh>

//+------------------------------------------------------------------+
//| Input variables                                                  |
//+------------------------------------------------------------------+
input bool     ShowDashboard = true;           // Show performance dashboard
input bool     ShowProfit_Loss = true;         // Show Profit and Loss on the chart
input color    DashboardBGColor = clrWhiteSmoke; // Dashboard background color
input color    TextColor = clrBlack;           // Text color
input int      DashboardX = 20;                // Dashboard X position
input int      DashboardY = 20;                // Dashboard Y position
input int      DashboardWidth = 280;           // Dashboard width
input int      FontSize = 10;                  // Font size

// Performance tracking mode
input bool     TrackFromIndicatorStart = true; // Track from indicator start (true) or overall (false)

// Risk management parameters
input double   DailyRiskPercent = 1.0;         // Daily risk percentage
input double   MaxDailyDrawdownPercent = 10.0; // Max daily drawdown percentage
input double   MaxOverallDrawdownPercent = 8.0; // Max overall drawdown percentage
input bool     EnableRiskManagement = true;    // Enable risk management
input ulong    MagicNumber = 123456;           // Magic number for position identification

Wir beginnen mit dem Abschnitt über die Eingabevariablen, der es den Händlern ermöglicht, die Darstellung und das Verhalten des Indikators anzupassen. Sie können umschalten, ob ein Dashboard angezeigt werden soll, Gewinn oder Verlust direkt im Chart anzeigen und visuelle Eigenschaften wie Hintergrundfarbe, Textfarbe, Positionierung und Schriftgröße anpassen. Dank dieser Optionen können Händler die visuelle Schnittstelle flexibel an ihre persönlichen Vorlieben anpassen, während die wichtigsten Kontodaten in Echtzeit sichtbar bleiben.

Vor allem aber machen die Parameter des Risikomanagements den Indikator zu mehr als nur einem visuellen Instrument. Händler können ihren täglichen Risikoprozentsatz, den maximalen täglichen Drawdown-Prozentsatz und den maximalen Gesamtdrawdown-Prozentsatz festlegen, um sicherzustellen, dass das System Disziplin erzwingt und rücksichtsloses Handeln verhindert. Durch die Aktivierung des Risikomanagements kann der Indikator die Handelsgeschäfte überwachen (über die magische Zahl zur Identifizierung) und bei Überschreitung von Limits Warnungen senden. Diese Funktion hilft den Händlern, die Konsistenz zu wahren, übermäßiges Handeln zu vermeiden und sich an die Regeln der Prop-Firma oder die persönlichen Strategien für das Kontowachstum anzupassen.

//+------------------------------------------------------------------+
//| Indicator buffers                                                |
//+------------------------------------------------------------------+
double         BidBuffer[];
double         AskBuffer[];

//+------------------------------------------------------------------+
//| Global variables                                                 |
//+------------------------------------------------------------------+
double         currentProfit;
double         balance;
double         equity;
double         margin;
double         freeMargin;
double         marginLevel;
int            totalOrders;
double         totalProfit;
double         dailyProfit;
datetime       lastTradeTime;
int            winCount;
int            lossCount;
double         maxDrawdown;
double         maxDrawdownPercent;

// Risk management variables
double         initialBalance;
double         dailyHighEquity;
double         dailyLowEquity;
double         dailyStartEquity;
bool           tradingEnabled = true;
string         riskStatus = "Trading Enabled";
color          riskStatusColor = clrGreen;

// Performance tracking variables
int            totalClosedTrades;
int            closedWinCount;
int            closedLossCount;
double         totalGains;
double         totalLosses;
double         largestWin;
double         largestLoss;
double         averageWin;
double         averageLoss;
double         profitFactor;

// Track the last time history was updated
datetime       lastHistoryUpdate;
bool           firstUpdate = true;

In diesem Abschnitt des Codes werden die globalen Variablen definiert, die der Indikator verwendet, um die Performance, das Risikomanagement und den Handelsverlauf über mehrere Sitzungen hinweg zu verfolgen. Die erste Gruppe von Variablen befasst sich mit den Leistungskennzahlen des Kontos wie laufender Gewinn, Saldo, Kapital, Marge, freie Marge und Margenhöhe. Außerdem werden die Anzahl der offenen Aufträge, der Gesamtgewinn, der Tagesgewinn, der Zeitpunkt des letzten Handels sowie die Anzahl der Handelsgeschäfte mit Gewinn und Verlust erfasst. Darüber hinaus überwacht es den maximalen Drawdown sowohl in absoluten Werten als auch in Prozent, was für die Bewertung des Risikos entscheidend ist.

Die zweite Gruppe von Variablen ist dem Risikomanagement gewidmet. Hier speichert der Code den Anfangssaldo, wenn die Verfolgung beginnt, sowie den Tageshöchst- und -tiefststand und den Startwert. Diese Werte helfen dabei, die täglichen Leistungsschwankungen zu messen und durch die Erkennung von Überschreitungen der Tages- oder Gesamtlimits für Disziplin zu sorgen. Das Flag tradingEnabled fungiert als Hauptschalter, der bei Überschreitung von Risikoschwellen Warnungen sendet. Ein visuelles Element ist durch den Text und Farbe in riskStatus und die riskStatusColor enthalten, die dem Händler eine klare Rückmeldung auf dem Chart geben, z. B. die Anzeige „Trading Enabled“ in grün oder „Warning“ in rot.

Die letzte Gruppe enthält Performance-Tracking-Variablen für die Analyse von abgeschlossenen Handelsgeschäften. Dazu gehören die Gesamtzahl der abgeschlossenen Handelsgeschäfte, wie viele davon Gewinne oder Verluste waren, die kumulierten Gewinne und Verluste sowie der größte Gewinn und der größte Verlust. Außerdem werden Durchschnittswerte für Gewinne und Verluste sowie der Gewinnfaktor berechnet, der eine wichtige Leistungskennzahl für den Vergleich von Gewinnen und Verlusten darstellt. Um genaue Aktualisierungen zu gewährleisten, verfolgt das System, wann der Verlauf zuletzt aktualisiert wurde und ob es sich um die erste Aktualisierung seit der Initialisierung handelt. Insgesamt geben diese Variablen den Händlern einen umfassenden Überblick über ihre Handelsleistung, um die Konsistenz zu verbessern.

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
{

   IndicatorSetString(INDICATOR_SHORTNAME, "ACCOUNT PERFORMANCE MATRIX");
   IndicatorSetInteger(INDICATOR_DIGITS, _Digits);
   
   // Initialize variables
   currentProfit = 0.0;
   balance = AccountInfoDouble(ACCOUNT_BALANCE);
   equity = AccountInfoDouble(ACCOUNT_EQUITY);
   
   // Set initial balance based on tracking mode
   if(TrackFromIndicatorStart)
   {
      initialBalance = balance;
   }
   else
   {
      // For overall tracking, use the actual account balance
      initialBalance = AccountInfoDouble(ACCOUNT_BALANCE);
   }
   
   dailyStartEquity = equity;
   dailyHighEquity = equity;
   dailyLowEquity = equity;
   lastHistoryUpdate = TimeCurrent();
   
   // Load historical trade data based on tracking mode
   LoadHistory();
   
   // Create dashboard objects if enabled
   if(ShowDashboard)
   {
      CreateDashboard();
   }
   
   // Set timer to update every second
   EventSetTimer(1);
   
   return(INIT_SUCCEEDED);
}

Die Funktion OnInit() initialisiert den nutzerdefinierten Indikator, indem sie seinen Anzeigenamen und seine Genauigkeit festlegt und alle Variablen zur Leistungsverfolgung für die Verwendung vorbereitet. Es setzt den aktuellen Gewinn zurück, ruft den Kontostand und das Kapital ab und bestimmt den Anfangssaldo, je nachdem, ob der Nutzer die Performance ab dem Zeitpunkt, an dem der Indikator angebracht wird, oder über die gesamte Kontohistorie verfolgen möchte. Es legt auch den Startwert für den Tag fest und zeichnet die Höchst- und Tiefstwerte des Kapitalstands auf, um die Performance innerhalb eine Tages zu messen. Historische Handelsdaten werden mit der Funktion LoadHistory() geladen, um den Kontext für Berechnungen zu liefern, und wenn die Dashboard-Option aktiviert ist, wird das visuelle Performance-Panel mit CreateDashboard() erstellt. Schließlich aktiviert die Funktion ein Timer-Ereignis mit Aktualisierungen im Sekundentakt, um sicherzustellen, dass alle Kontokennzahlen und Dashboard-Anzeigen mit der Echtzeit-Handelsaktivität synchronisiert bleiben.

//+------------------------------------------------------------------+
//| Custom indicator deinitialization function                       |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
   // Delete all objects created by the indicator
   ObjectsDeleteAll(0, 0, -1);
   EventKillTimer();
}

//+------------------------------------------------------------------+
//| Timer function                                                   |
//+------------------------------------------------------------------+
void OnTimer()
{
   // Update account information
   UpdateAccountInfo();
   
   // Update historical data periodically to capture new closed trades
   if(TimeCurrent() - lastHistoryUpdate >= 5 || firstUpdate) // Update every 5 seconds or on first run
   {
      LoadHistory();
      lastHistoryUpdate = TimeCurrent();
      firstUpdate = false;
   }
   
   // Update dashboard if enabled
   if(ShowDashboard)
   {
      UpdateDashboard();
   }
   
   // Check risk management rules
   if(EnableRiskManagement)
   {
      CheckRiskManagement();
   }
}

//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
{
   // Update bid and ask buffers
   if(ShowProfit_Loss)
   {
      UpdatePriceBuffers(rates_total, BidBuffer, AskBuffer, time);
   }
   
   return(rates_total);
}

//+------------------------------------------------------------------+
//| Update account information function                              |
//+------------------------------------------------------------------+
void UpdateAccountInfo()
{
   balance = AccountInfoDouble(ACCOUNT_BALANCE);
   equity = AccountInfoDouble(ACCOUNT_EQUITY);
   margin = AccountInfoDouble(ACCOUNT_MARGIN);
   freeMargin = AccountInfoDouble(ACCOUNT_MARGIN_FREE);
   marginLevel = AccountInfoDouble(ACCOUNT_MARGIN_LEVEL);
   
   // Update daily high/low equity
   if(equity > dailyHighEquity) dailyHighEquity = equity;
   if(equity < dailyLowEquity) dailyLowEquity = equity;
   
   CalculateProfitMetrics();
   CalculateTradeMetrics();
}

Die Funktion OnDeinit() sorgt für ein sauberes Herunterfahren des Indikators, indem sie alle von ihm erstellten Chart-Objekte löscht und das Timer-Ereignis beendet, um Hintergrundaktualisierungen zu stoppen. Dadurch wird verhindert, dass das Chart unübersichtlich wird, nachdem der Indikator entfernt wurde, und es wird sichergestellt, dass keine unnötigen Prozesse aktiv bleiben. Die Funktion OnTimer(), die jede Sekunde ausgeführt wird, ist für die Funktionalität des Indikators von zentraler Bedeutung: Sie aktualisiert kontinuierlich die Kontoinformationen, aktualisiert die historischen Handelsdaten alle fünf Sekunden (oder beim ersten Durchlauf) und verwaltet das Performance-Dashboard, falls aktiviert. Außerdem werden die Regeln für das Risikomanagement überprüft und durchgesetzt, sodass der Händler bei Erreichen von Risikoschwellen in Echtzeit gewarnt wird.

In der Zwischenzeit integriert die Funktion OnCalculate() den Indikator in die Chartaktualisierung und stellt sicher, dass bei aktivierter Gewinn-/Verlustanzeige die Bid- und Ask-Linien durch Preispuffer aktualisiert werden. Die Funktion UpdateAccountInfo() ruft Live-Kontodetails wie Saldo, Kapital, Marge und freie Marge ab und zeichnet gleichzeitig den täglichen Höchst- und Tiefstwert des Kapitals auf, um die Performance genau zu verfolgen. Anschließend ruft es unterstützende Funktionen wie CalculateProfitMetrics() und CalculateTradeMetrics() auf, um die Leistung weiter zu analysieren. Zusammen bilden diese Funktionen ein sich selbst aktualisierendes, risikobewusstes Dashboard, das Händlern einen vollständigen Überblick über ihren Kontostatus gibt und gleichzeitig aktiv für Disziplin sorgt.

//+------------------------------------------------------------------+
//| Update account information function                              |
//+------------------------------------------------------------------+
void UpdateAccountInfo()
{
   balance = AccountInfoDouble(ACCOUNT_BALANCE);
   equity = AccountInfoDouble(ACCOUNT_EQUITY);
   margin = AccountInfoDouble(ACCOUNT_MARGIN);
   freeMargin = AccountInfoDouble(ACCOUNT_MARGIN_FREE);
   marginLevel = AccountInfoDouble(ACCOUNT_MARGIN_LEVEL);
   
   // Update daily high/low equity
   if(equity > dailyHighEquity) dailyHighEquity = equity;
   if(equity < dailyLowEquity) dailyLowEquity = equity;
   
   CalculateProfitMetrics();
   CalculateTradeMetrics();
}

//+------------------------------------------------------------------+
//| Update price buffers with Bid and Ask values                     |
//+------------------------------------------------------------------+
void UpdatePriceBuffers(int rates_total, double &bidBuffer[], double &askBuffer[], const datetime &time[])
{
   double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
   double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
   
   // Calculate current profit for open positions
   currentProfit = 0.0;
   for(int i = PositionsTotal() - 1; i >= 0; i--)
   {
      if(PositionGetSymbol(i) == _Symbol)
      {
         currentProfit += PositionGetDouble(POSITION_PROFIT);
      }
   }
   
   // Fill buffers with current bid and ask values
   if(ShowProfit_Loss && rates_total > 0)
   {
      for(int i = 0; i < rates_total; i++)
      {
         bidBuffer[i] = bid;
         askBuffer[i] = ask;
      }
      
      // Calculate position for labels (2 bars to the right of current candle)
      int currentBar = rates_total - 1;
      datetime labelTime = (currentBar + 2 < rates_total) ? time[currentBar + 2] : time[currentBar] + PeriodSeconds() * 2;
      
      // Add profit/loss label with color coding
      string plLabelText;
      color plColor;
      if(currentProfit >= 0)
      {
         plLabelText = "Profit: " + DoubleToString(currentProfit, 2);
         plColor = clrBlue;
      }
      else
      {
         plLabelText = "Loss: " + DoubleToString(currentProfit, 2);
         plColor = clrRed;
      }
      
      CreateOrUpdateLabel("PLLabel", plLabelText, labelTime, (bid + ask) / 2, plColor, ANCHOR_RIGHT);
   }
}

Die Funktion UpdateAccountInfo() ist für den Abruf von Live-Kontodaten und die Verfolgung wichtiger Leistungskennzahlen zuständig. Es bezieht den aktuellen Saldo, das Kapital, die Marge, die freie Marge und die Margenhöhe vom Handelskonto. Darüber hinaus überwacht es die Tagesschwankungen des Kapitalstands, indem es dessen Höchst- und Tiefstwerte aktualisiert, sobald ein neuer Extremwert erreicht wird. Sobald die rohen Kontodaten aktualisiert sind, ruft die Funktion CalculateProfitMetrics() und CalculateTradeMetrics() auf, die tiefergehende Analysen wie Gewinnberechnungen, die Verfolgung von Handelsgewinnen und -verlusten und die Leistungsbewertung durchführen. Dadurch wird sichergestellt, dass das Dashboard immer die aktuellsten Kontobedingungen widerspiegelt.

Die Funktion UpdatePriceBuffers() konzentriert sich auf die Visualisierung von Preisen und Gewinnen/Verlusten. Zunächst werden die aktuellen Geld- und Briefkurse, Bid und Ask, abgerufen, dann werden die offenen Positionen durchlaufen, um den aktuellen Gewinn für das aktive Symbol zu berechnen. Wenn die Gewinn/Verlust-Visualisierung aktiviert ist, werden die Puffer des Indikators mit den letzten Bid- und Ask-Werten des Charts gefüllt und ein Etikett erstellt, das leicht rechts vom aktuellen Balken positioniert ist. Dieses Etikett zeigt entweder einen Gewinn (blau) oder einen Verlust (rot) an, je nach dem aktuellen Handelsergebnis. Durch die Kombination der Verfolgung der Kontoperformance mit der Visualisierung von Kursen und Gewinnen in Echtzeit bietet die Funktion Händlern sowohl numerisches als auch grafisches Feedback, das das Situationsbewusstsein und die Entscheidungsfindung verbessert.

//+------------------------------------------------------------------+
//| Create or update text label                                      |
//+------------------------------------------------------------------+
void CreateOrUpdateLabel(string name, string text, datetime time, double price, color clr, ENUM_ANCHOR_POINT anchor)
{
   if(ObjectFind(0, name) < 0)
   {
      ObjectCreate(0, name, OBJ_TEXT, 0, time, price);
      ObjectSetString(0, name, OBJPROP_TEXT, text);
      ObjectSetInteger(0, name, OBJPROP_COLOR, clr);
      ObjectSetInteger(0, name, OBJPROP_ANCHOR, anchor);
      ObjectSetInteger(0, name, OBJPROP_FONTSIZE, FontSize);
   }
   else
   {
      ObjectMove(0, name, 0, time, price);
      ObjectSetString(0, name, OBJPROP_TEXT, text);
      ObjectSetInteger(0, name, OBJPROP_COLOR, clr);
   }
}

//+------------------------------------------------------------------+
//| Calculate profit metrics                                         |
//+------------------------------------------------------------------+
void CalculateProfitMetrics()
{
   totalProfit = equity - initialBalance;
   
   // Calculate max drawdown - FIXED: Prevent division by zero
   double drawdown = dailyHighEquity - equity;
   if(drawdown > maxDrawdown)
   {
      maxDrawdown = drawdown;
      // Avoid division by zero - use a small epsilon if dailyHighEquity is zero
      double denominator = (dailyHighEquity == 0) ? 0.000001 : dailyHighEquity;
      maxDrawdownPercent = (drawdown / denominator) * 100;
   }
}

Die Funktion UpdateAccountInfo() ist für den Abruf von Live-Kontodaten und die Verfolgung wichtiger Leistungskennzahlen zuständig. Es bezieht den aktuellen Saldo, das Kapital, die Marge, die freie Marge und die Margenhöhe vom Handelskonto. Darüber hinaus überwacht es die Tagesschwankungen des Kapitalstands, indem es dessen Höchst- und Tiefstwerte aktualisiert, sobald ein neuer Extremwert erreicht wird. Sobald die rohen Kontodaten aktualisiert sind, ruft die Funktion CalculateProfitMetrics() und CalculateTradeMetrics() auf, die tiefer gehende Analysen wie Gewinnberechnungen, die Verfolgung von Handelsgewinnen und -verlusten und die Leistungsbewertung durchführen. Dadurch wird sichergestellt, dass das Dashboard immer die aktuellsten Kontobedingungen widerspiegelt.

Die Funktion UpdatePriceBuffers() konzentriert sich auf die Visualisierung von Preisen und Gewinnen/Verlusten. Zunächst werden die aktuellen Geld- und Briefkurse, Bid und Ask, abgerufen, dann werden die offenen Positionen durchlaufen, um den aktuellen Gewinn für das aktive Symbol zu berechnen. Wenn die Gewinn/Verlust-Visualisierung aktiviert ist, werden die Puffer des Indikators mit den letzten Bid- und Ask-Werten des Charts gefüllt und ein Etikett erstellt, das leicht rechts vom aktuellen Balken positioniert ist. Dieses Etikett zeigt entweder einen Gewinn (blau) oder einen Verlust (rot) an, je nach dem aktuellen Handelsergebnis. Durch die Kombination der Verfolgung der Kontoperformance mit der Visualisierung von Kursen und Gewinnen in Echtzeit bietet die Funktion Händlern sowohl numerisches als auch grafisches Feedback, das das Situationsbewusstsein und die Entscheidungsfindung verbessert.

//+------------------------------------------------------------------+
//| Calculate trade metrics                                          |
//+------------------------------------------------------------------+
void CalculateTradeMetrics()
{
   totalOrders = PositionsTotal();
   winCount = 0;
   lossCount = 0;
   
   // Count winning and losing positions
   for(int i = PositionsTotal() - 1; i >= 0; i--)
   {
      ulong ticket = PositionGetTicket(i);
      if(PositionSelectByTicket(ticket))
      {
         double profit = PositionGetDouble(POSITION_PROFIT);
         if(profit > 0) winCount++;
         else if(profit < 0) lossCount++;
      }
   }
}

//+------------------------------------------------------------------+
//| Load historical trade data                                       |
//+------------------------------------------------------------------+
void LoadHistory()
{
   datetime startDate = 0;
   if(TrackFromIndicatorStart)
   {
      // Only load history from when the indicator was started
      startDate = TimeCurrent() - 86400; // Load from 24 hours ago to ensure we capture all recent trades
   }
   
   HistorySelect(startDate, TimeCurrent());
   int totalHistory = HistoryDealsTotal();
   
   // Reset counters
   int newClosedTrades = 0;
   int newClosedWinCount = 0;
   int newClosedLossCount = 0;
   double newTotalGains = 0;
   double newTotalLosses = 0;
   double newLargestWin = 0;
   double newLargestLoss = 0;
   
   for(int i = 0; i < totalHistory; i++)
   {
      ulong ticket = HistoryDealGetTicket(i);
      if(ticket > 0)
      {
         // Check if this is a closing deal (not a deposit/withdrawal or opening trade)
         long dealType = HistoryDealGetInteger(ticket, DEAL_TYPE);
         if(dealType == DEAL_TYPE_BUY || dealType == DEAL_TYPE_SELL)
         {
            double profit = HistoryDealGetDouble(ticket, DEAL_PROFIT);
            if(profit != 0)
            {
               newClosedTrades++;
               if(profit > 0)
               {
                  newClosedWinCount++;
                  newTotalGains += profit;
                  if(profit > newLargestWin) newLargestWin = profit;
               }
               else
               {
                  newClosedLossCount++;
                  newTotalLosses += MathAbs(profit);
                  if(profit < newLargestLoss) newLargestLoss = profit;
               }
            }
         }
      }
   }
   
   // Update the global variables
   totalClosedTrades = newClosedTrades;
   closedWinCount = newClosedWinCount;
   closedLossCount = newClosedLossCount;
   totalGains = newTotalGains;
   totalLosses = newTotalLosses;
   largestWin = newLargestWin;
   largestLoss = newLargestLoss;
   
   // Calculate averages and profit factor
   averageWin = (closedWinCount > 0) ? totalGains / closedWinCount : 0;
   averageLoss = (closedLossCount > 0) ? totalLosses / closedLossCount : 0;
   profitFactor = (totalLosses > 0) ? totalGains / totalLosses : (totalGains > 0) ? 1000 : 0;
}

Die Funktion CalcculateTradeMetrics() liefert einen Live-Schnappschuss der offenen Handelspositionen, indem sie zählt, wie viele derzeit im Gewinn und wie viele im Verlust sind. Zunächst wird die Gesamtzahl der offenen Positionen abgefragt, dann werden die einzelnen Positionen in einer Schleife durchlaufen und ihr Gewinn- oder Verlustwert überprüft. Ist die Position gewinnbringend, wird der Gewinnzähler erhöht, ist sie verlustbringend, wird der Verlustzähler erhöht. Mit dieser Funktion erhalten Händler sofortiges Feedback über die Qualität ihrer offenen Handelsgeschäfte und können so in Echtzeit beurteilen, ob ihre Positionen mit ihrem Handelsplan übereinstimmen.

Die Funktion LoadHistory() vertieft die historische Performance, indem sie abgeschlossene Trades innerhalb eines bestimmten Zeitrahmens analysiert. Wenn das Tracking beim Start des Indikators eingestellt ist, wird die Historie der letzten 24 Stunden geladen, um sicherzustellen, dass die jüngsten Handelsgeschäfte berücksichtigt werden. Es durchsucht dann alle historischen Handelsgeschäfte und filtert nur Kauf- oder Verkaufsabschlüsse heraus, während Einzahlungen, Abhebungen oder Eingänge ignoriert werden. Für jeden abgeschlossenen Handel wird der Gewinn oder Verlust aufgezeichnet, die Zählungen für Gewinne und Verluste aktualisiert, die Gesamtgewinne und -verluste summiert und der größte Einzelgewinn oder -verlust verfolgt. Schließlich werden Durchschnittswerte und der Gewinnfaktor berechnet, eine wichtige Kennzahl zum Vergleich von Gewinnen und Verlusten. Diese umfassende Aufschlüsselung der Performance ermöglicht es Händlern, Muster in ihrem Handelsverhalten zu erkennen, die Rentabilität zu bewerten und im Laufe der Zeit ein diszipliniertes Risikomanagement zu betreiben.

//+------------------------------------------------------------------+
//| Check risk management rules                                      |
//+------------------------------------------------------------------+
void CheckRiskManagement()
{
   // Check if it's a new day (reset daily equity)
   MqlDateTime today, lastCheck;
   TimeToStruct(TimeCurrent(), today);
   TimeToStruct(TimeCurrent(), lastCheck);
   
   static int lastDay = -1;
   if(today.day != lastDay)
   {
      dailyStartEquity = equity;
      dailyHighEquity = equity;
      dailyLowEquity = equity;
      lastDay = today.day;
      
      // Re-enable trading at the start of a new day
      if(!tradingEnabled)
      {
         tradingEnabled = true;
         riskStatus = "Trading Enabled";
         riskStatusColor = clrGreen;
      }
   }
   
   // Calculate daily drawdown percentage - FIXED: Prevent division by zero
   double dailyDrawdownPercent = 0;
   if(dailyHighEquity > 0) 
   {
      dailyDrawdownPercent = (dailyHighEquity - equity) / dailyHighEquity * 100;
   }
   
   // Calculate overall drawdown percentage - FIXED: Prevent division by zero
   double overallDrawdownPercent = 0;
   if(initialBalance > 0) 
   {
      overallDrawdownPercent = (initialBalance - equity) / initialBalance * 100;
   }
   
   double dailyRiskEquity = dailyStartEquity * (1 - DailyRiskPercent / 100);
   
   // Check if we've hit risk limits
   if(tradingEnabled)
   {
      if(equity <= dailyRiskEquity)
      {
         riskStatus = "Daily Risk Limit Reached";
         riskStatusColor = clrRed;
         tradingEnabled = false;
         Alert("Daily Risk Limit Reached, Consider Closing Open Positions!!!");
         
      }
      else if(dailyDrawdownPercent >= MaxDailyDrawdownPercent)
      {
         riskStatus = "Max Daily Drawdown Reached";
         riskStatusColor = clrRed;
         tradingEnabled = false;
         Alert("Max Daily Drawdown Reached!!!");
         
      }
      else if(overallDrawdownPercent >= MaxOverallDrawdownPercent)
      {
         riskStatus = "Max Overall Drawdown Reached";
         riskStatusColor = clrRed;
         tradingEnabled = false;
         Alert("Max Overall Drawdown Reached!!!");
         
      }
   }
}

//+------------------------------------------------------------------+
//| Create dashboard function                                        |
//+------------------------------------------------------------------+
void CreateDashboard()
{
   // Create background rectangle
   ObjectCreate(0, "DashboardBG", OBJ_RECTANGLE_LABEL, 0, 0, 0);
   ObjectSetInteger(0, "DashboardBG", OBJPROP_XDISTANCE, DashboardX);
   ObjectSetInteger(0, "DashboardBG", OBJPROP_YDISTANCE, DashboardY);
   ObjectSetInteger(0, "DashboardBG", OBJPROP_XSIZE, DashboardWidth);
   ObjectSetInteger(0, "DashboardBG", OBJPROP_YSIZE, 320);
   ObjectSetInteger(0, "DashboardBG", OBJPROP_BGCOLOR, DashboardBGColor);
   ObjectSetInteger(0, "DashboardBG", OBJPROP_BORDER_TYPE, BORDER_FLAT);
   ObjectSetInteger(0, "DashboardBG", OBJPROP_BORDER_COLOR, clrGray);
   ObjectSetInteger(0, "DashboardBG", OBJPROP_BACK, true);
   ObjectSetInteger(0, "DashboardBG", OBJPROP_SELECTABLE, false);
   ObjectSetInteger(0, "DashboardBG", OBJPROP_SELECTED, false);
   ObjectSetInteger(0, "DashboardBG", OBJPROP_HIDDEN, true);
   ObjectSetInteger(0, "DashboardBG", OBJPROP_ZORDER, 0);
   
   // Create title
   ObjectCreate(0, "DashboardTitle", OBJ_LABEL, 0, 0, 0);
   ObjectSetInteger(0, "DashboardTitle", OBJPROP_XDISTANCE, DashboardX + 10);
   ObjectSetInteger(0, "DashboardTitle", OBJPROP_YDISTANCE, DashboardY + 10);
   ObjectSetString(0, "DashboardTitle", OBJPROP_TEXT, "ACCOUNT PERFORMANCE MATRIX");
   ObjectSetInteger(0, "DashboardTitle", OBJPROP_COLOR, TextColor);
   ObjectSetInteger(0, "DashboardTitle", OBJPROP_FONTSIZE, FontSize + 2);
   
   // Create tracking mode indicator
   string trackingMode = TrackFromIndicatorStart ? "From Indicator Start" : "Overall Account";
   ObjectCreate(0, "TrackingModeLabel", OBJ_LABEL, 0, 0, 0);
   ObjectSetInteger(0, "TrackingModeLabel", OBJPROP_XDISTANCE, DashboardX + 10);
   ObjectSetInteger(0, "TrackingModeLabel", OBJPROP_YDISTANCE, DashboardY + 30);
   ObjectSetString(0, "TrackingModeLabel", OBJPROP_TEXT, "Tracking: " + trackingMode);
   ObjectSetInteger(0, "TrackingModeLabel", OBJPROP_COLOR, clrDarkBlue);
   ObjectSetInteger(0, "TrackingModeLabel", OBJPROP_FONTSIZE, FontSize);
   
   // Create information labels
   CreateDashboardLabel("RiskStatusLabel", "Trading Status: ", 50, DashboardX, DashboardY);
   CreateDashboardLabel("BalanceLabel", "Balance: ", 70, DashboardX, DashboardY);
   CreateDashboardLabel("EquityLabel", "Equity: ", 90, DashboardX, DashboardY);
   CreateDashboardLabel("DailyProfitLabel", "Daily P/L: ", 110, DashboardX, DashboardY);
   CreateDashboardLabel("TotalProfitLabel", "Total P/L: ", 130, DashboardX, DashboardY);
   CreateDashboardLabel("PositionsLabel", "Open Positions: ", 150, DashboardX, DashboardY);
   CreateDashboardLabel("WinRateLabel", "Win Rate: ", 170, DashboardX, DashboardY);
   CreateDashboardLabel("DailyRiskLabel", "Daily Risk: ", 190, DashboardX, DashboardY);
   CreateDashboardLabel("DailyDrawdownLabel", "Daily Drawdown: ", 210, DashboardX, DashboardY);
   CreateDashboardLabel("TotalDrawdownLabel", "Total Drawdown: ", 230, DashboardX, DashboardY);
   CreateDashboardLabel("ProfitFactorLabel", "Profit Factor: ", 250, DashboardX, DashboardY);
   CreateDashboardLabel("TradesLabel", "Total Trades: ", 270, DashboardX, DashboardY);
   CreateDashboardLabel("AvgWinLossLabel", "Avg Win/Loss: ", 290, DashboardX, DashboardY);
}

Die Funktion CheckRiskManagement() ist der Durchsetzungsmechanismus, der sicherstellt, dass die Händler ihren Risikoplan einhalten und die Kontolimits nicht überschreiten. Anschließend werden die täglichen und gesamten Drawdown-Prozentsätze berechnet, wobei die Division durch Null sorgfältig gehandhabt wird, um Fehler zu vermeiden. Die Funktion vergleicht das Kapital mit dem vordefinierten täglichen Risikolimit, dem maximalen täglichen Drawdown und dem maximalen Gesamtdrawdown des Händlers. Wird einer dieser Schwellenwerte überschritten, färbt sich die Statusmeldung rot, und es werden Warnmeldungen ausgelöst, um den Händler zu warnen. Dieser Ansatz trägt nicht nur dazu bei, katastrophale Verluste zu verhindern, sondern sorgt auch für Disziplin, indem er die Versuchung beseitigt, nach Erreichen der Limits weiter zu handeln.

Die Funktion CreateDashboard() erstellt die Bildschirmoberfläche, die alle verfolgten Metriken in einem organisierten und visuell klaren Panel anzeigt. Es beginnt mit der Erstellung eines Hintergrundrechtecks, das mit nutzerdefinierten Farben, Größen und Rahmen gestaltet wird und als Container für alle Leistungsdaten dient. Anschließend werden ein Titel und eine Kennzeichnung für den Verfolgungsmodus hinzugefügt, um anzugeben, ob das Dashboard die Ergebnisse ab dem Start des Indikators oder den gesamten Kontoverlauf überwacht. Darunter befinden sich in strukturierten Zeilen eine Reihe von beschrifteten Feldern wie Saldo, Kapital, Tages- und Gesamtgewinn/-verlust, Gewinnrate, Drawdowns, Gewinnfaktor und durchschnittlicher Gewinn/Verlust. Dieses visuelle Dashboard ermöglicht es Händlern, sowohl den Zustand ihres Kontos als auch ihren Risikostatus sofort zu beurteilen, ohne sich mit mehreren Berichten auseinandersetzen zu müssen, was es zu einem leistungsstarken Instrument macht, um Klarheit und Disziplin beim Handel zu bewahren.

//+------------------------------------------------------------------+
//| Create dashboard label helper function                           |
//+------------------------------------------------------------------+
void CreateDashboardLabel(string name, string text, int yOffset, int x, int y)
{
   ObjectCreate(0, name, OBJ_LABEL, 0, 0, 0);
   ObjectSetInteger(0, name, OBJPROP_XDISTANCE, x + 10);
   ObjectSetInteger(0, name, OBJPROP_YDISTANCE, y + yOffset);
   ObjectSetString(0, name, OBJPROP_TEXT, text);
   ObjectSetInteger(0, name, OBJPROP_COLOR, TextColor);
   ObjectSetInteger(0, name, OBJPROP_FONTSIZE, FontSize);
}

//+------------------------------------------------------------------+
//| Update dashboard function                                        |
//+------------------------------------------------------------------+
void UpdateDashboard()
{
   // Update risk status with color coding
   ObjectSetString(0, "RiskStatusLabel", OBJPROP_TEXT, "Trading Status: " + riskStatus);
   ObjectSetInteger(0, "RiskStatusLabel", OBJPROP_COLOR, riskStatusColor);
   
   // Update values for all dashboard labels
   UpdateDashboardLabel("BalanceLabel", "Balance: " + DoubleToString(balance, 2));
   UpdateDashboardLabel("EquityLabel", "Equity: " + DoubleToString(equity, 2));
   
   // Color code profit values
   double dailyPL = equity - dailyStartEquity;
   string dailyPLText = "Daily P/L: ";
   if(dailyPL >= 0) dailyPLText += "+" + DoubleToString(dailyPL, 2);
   else dailyPLText += DoubleToString(dailyPL, 2);
   UpdateDashboardLabel("DailyProfitLabel", dailyPLText);
   
   string totalPLText = "Total P/L: ";
   if(totalProfit >= 0) totalPLText += "+" + DoubleToString(totalProfit, 2);
   else totalPLText += DoubleToString(totalProfit, 2);
   UpdateDashboardLabel("TotalProfitLabel", totalPLText);
   
   UpdateDashboardLabel("PositionsLabel", "Open Positions: " + IntegerToString(totalOrders));
   
   // Calculate and display win rate from closed trades
   int totalTrades = closedWinCount + closedLossCount;
   string winRateText = "Win Rate: ";
   if(totalTrades > 0) 
   {
      double winRate = (double)closedWinCount / totalTrades * 100;
      winRateText += DoubleToString(winRate, 1) + "% (" + IntegerToString(closedWinCount) + 
                    "/" + IntegerToString(totalTrades) + ")";
   }
   else winRateText += "N/A";
   UpdateDashboardLabel("WinRateLabel", winRateText);
   
   // Risk metrics
   double dailyRiskEquity = dailyStartEquity * (1 - DailyRiskPercent / 100);
   
   // Calculate drawdown percentages with zero division protection
   double dailyDrawdownPercent = 0;
   if(dailyHighEquity > 0) 
   {
      dailyDrawdownPercent = (dailyHighEquity - equity) / dailyHighEquity * 100;
   }
   
   double overallDrawdownPercent = 0;
   if(initialBalance > 0) 
   {
      overallDrawdownPercent = (initialBalance - equity) / initialBalance * 100;
   }
   
   UpdateDashboardLabel("DailyRiskLabel", "Daily Risk: " + DoubleToString(DailyRiskPercent, 1) + 
                       "% (" + DoubleToString(dailyRiskEquity, 2) + ")");
   
   string dailyDrawdownText = "Daily Drawdown: " + DoubleToString(dailyDrawdownPercent, 1) + "%";
   UpdateDashboardLabel("DailyDrawdownLabel", dailyDrawdownText);
   
   string totalDrawdownText = "Total Drawdown: " + DoubleToString(overallDrawdownPercent, 1) + "%";
   UpdateDashboardLabel("TotalDrawdownLabel", totalDrawdownText);
   
   // Performance metrics
   UpdateDashboardLabel("ProfitFactorLabel", "Profit Factor: " + DoubleToString(profitFactor, 2));
   UpdateDashboardLabel("TradesLabel", "Total Trades: " + IntegerToString(totalClosedTrades));
   UpdateDashboardLabel("AvgWinLossLabel", "Avg Win/Loss: " + DoubleToString(averageWin, 2) + 
                       "/" + DoubleToString(MathAbs(averageLoss), 2));
}

//+------------------------------------------------------------------+
//| Update dashboard label helper function                           |
//+------------------------------------------------------------------+
void UpdateDashboardLabel(string name, string text)
{
   ObjectSetString(0, name, OBJPROP_TEXT, text);
}

Die Funktion CreateDashboardLabel() dient als Hilfsfunktion, um schnell beschriftete Felder auf dem Dashboard zu erzeugen. Jedes Kennzeichen wird mit einem bestimmten Namen, Text und Positionsversatz erstellt, und es wird ein einheitliches Styling wie Schriftgröße und -farbe angewendet. Dadurch wird sichergestellt, dass alle Metriken auf dem Dashboard deutlich sichtbar und einheitlich formatiert sind, was den Händlern einen organisierten Überblick über die Kontoperformance und den Risikostatus bietet.

Die Funktion UpdateDashboard() aktualisiert das Dashboard kontinuierlich mit aktuellen Konto- und Leistungsdaten. Es aktualisiert alle wichtigen Kennzahlen, einschließlich Saldo, Kapital, täglicher und gesamter Gewinn/Verlust, offene Positionen, Gewinnrate, Drawdowns, tägliches Risiko, Gewinnfaktor, Gesamthandel und durchschnittlicher Gewinn/Verlust. Die Funktion kodiert außerdem wichtige Informationen wie Gewinn/Verlust und Handelsstatus farblich, um sofortiges visuelles Feedback zu geben. Durch die ständige Darstellung der Kontobedingungen in Echtzeit hilft es den Händlern, fundierte Entscheidungen zu treffen, stärkt die Disziplin und verhindert übermäßiges Handeln, indem es deutlich anzeigt, wann die Risikolimits erreicht sind.

Indikator-Demo:



Schlussfolgerung

Zusammenfassend lässt sich sagen, dass wir einen nutzerdefinierten Account Performance Matrix-Indikator entwickelt haben, der sowohl die Echtzeit- als auch die historische Handelsentwicklung verfolgt. Der Indikator überwacht wichtige Kontokennzahlen wie Saldo, Kapital, Marge, offene Positionen, Tages- und Gesamtgewinn/-verlust, Gewinnrate, Drawdowns, Gewinnfaktor und durchschnittlicher Gewinn/Verlust. Es enthält ein dynamisches Dashboard mit visuellen Kennzeichnungen und farbkodierten Warnungen, um Händlern einen klaren und organisierten Überblick über ihre Kontoperformance zu geben. Risikomanagementregeln sind integriert, um tägliche Risikolimits und maximale Drawdowns durchzusetzen, und wenn die Limits erreicht werden, wird der Händler über die aktuelle Kontoperformance informiert.

Zusammenfassend lässt sich sagen, dass dieser Indikator den Händlern hilft, Disziplin zu bewahren, übermäßiges Handeln zu vermeiden und fundiertere Entscheidungen zu treffen, indem er einen transparenten Überblick über den Zustand des Kontos und die Risikoexposition bietet. Indem sie klar aufzeigt, wann Risikogrenzen erreicht oder überschritten werden, fördert sie ein konsistentes Handelsverhalten und unterstützt nachhaltiges Wachstum. Unabhängig davon, ob ein Händler daran arbeitet, eine Herausforderung für eine Prop-Firma zu bestehen oder ein persönliches Handelskonto zu verwalten, bietet dieses Tool eine strukturierte Anleitung zum Risikomanagement, zur Optimierung von Handelsentscheidungen und letztlich zur Steigerung der langfristigen Rentabilität.

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

Beigefügte Dateien |
Acc_Matrix.mq5 (25.52 KB)
Aufbau eines professionellen Handelssystems mit Heikin Ashi (Teil 2): Entwicklung eines EA Aufbau eines professionellen Handelssystems mit Heikin Ashi (Teil 2): Entwicklung eines EA
Dieser Artikel erklärt, wie man einen professionellen Heikin Ashi-basierten Expert Advisor (EA) in MQL5 entwickelt. Sie werden lernen, wie man Eingabeparameter, Enumerationen, Indikatoren und globale Variablen einrichtet und die zentrale Handelslogik implementiert. Sie können auch einen Backtest mit Gold durchführen, um Ihre Arbeit zu überprüfen.
Entwicklung von Handelsstrategien mit den Oszillatoren Parafrac und Parafrac V2: Einzeleintritt Performance Insights Entwicklung von Handelsstrategien mit den Oszillatoren Parafrac und Parafrac V2: Einzeleintritt Performance Insights
In diesem Artikel werden der ParaFrac Oscillator und sein V2-Modell als Handelsinstrumente vorgestellt. Es werden drei Handelsstrategien vorgestellt, die mit Hilfe dieser Indikatoren entwickelt wurden. Jede Strategie wurde getestet und optimiert, um ihre Stärken und Schwächen zu ermitteln. Die vergleichende Analyse zeigte die Leistungsunterschiede zwischen dem Original und dem V2-Modell auf.
Vom Neuling zum Experten: Animierte Nachrichtenüberschrift mit MQL5 (XI) – Korrelation im Nachrichtenhandel Vom Neuling zum Experten: Animierte Nachrichtenüberschrift mit MQL5 (XI) – Korrelation im Nachrichtenhandel
In diesem Beitrag werden wir untersuchen, wie das Konzept der Finanzkorrelation angewendet werden kann, um die Entscheidungseffizienz beim Handel mit mehreren Symbolen während der Ankündigung wichtiger wirtschaftlicher Ereignisse zu verbessern. Der Schwerpunkt liegt dabei auf der Bewältigung des erhöhten Risikos, das durch die erhöhte Volatilität bei der Veröffentlichung von Nachrichten entsteht.
Der MQL5 Standard Library Explorer (Teil 1): Einführung in CTrade, CiMA, und CiATR Der MQL5 Standard Library Explorer (Teil 1): Einführung in CTrade, CiMA, und CiATR
Die MQL5-Standardbibliothek spielt eine wichtige Rolle bei der Entwicklung von Handelsalgorithmen für MetaTrader 5. In dieser Diskussionsreihe wollen wir seine Anwendung beherrschen, um die Erstellung effizienter Handelswerkzeuge für MetaTrader 5 zu vereinfachen. Zu diesen Tools gehören nutzerdefinierte Expert Advisors, Indikatoren und andere Hilfsmittel. Wir beginnen heute mit der Entwicklung eines trendfolgenden Expert Advisors unter Verwendung der Klassen CTrade, CiMA und CiATR. Dies ist ein besonders wichtiges Thema für alle – egal, ob Sie Anfänger oder erfahrener Entwickler sind. Nehmen Sie an dieser Diskussion teil und erfahren Sie mehr.