English 日本語
preview
Erstellen eines Handelsadministrator-Panels in MQL5 (Teil XII): Integration eines Rechners für Forex-Werte

Erstellen eines Handelsadministrator-Panels in MQL5 (Teil XII): Integration eines Rechners für Forex-Werte

MetaTrader 5Beispiele |
167 10
Clemence Benjamin
Clemence Benjamin

Inhalt:


Einführung

Die heutige Diskussion konzentriert sich auf die Lösung des Problems der manuellen oder externen Berechnung von Handelswerten durch die Integration eines Forex-Rechners direkt in das Handelsmanagement-Panel - ein Unter-Panel des New Admin Panel EA.

In der Vergangenheit haben sich viele Händler auf externe Websites verlassen, um diese Berechnungen durchzuführen. Diese Tools sind unglaublich hilfreich, und den Entwicklern dieser Plattformen gebührt Anerkennung für ihre wertvollen Dienste. Auch heute noch verwenden einige Händler diese Online-Rechner - letztlich ist es eine Frage der Vorliebe.

Mit der Leistungsfähigkeit von MQL5 und seinen GUI-Funktionen haben wir nun jedoch die Möglichkeit, effizientere, integrierte Lösungen direkt im Handelsterminal zu erstellen. Mit diesem Ansatz entfällt die Notwendigkeit, zwischen verschiedenen Anwendungen zu wechseln, und der Arbeitsablauf wird verbessert, da sich alle wichtigen Tools an einem Ort befinden.

Wir danken MetaTrader 5 für die Bereitstellung einer robusten API, die einen nahtlosen Zugang zu Marktnachrichten und Datenfeeds innerhalb des Terminals ermöglicht. Obwohl es APIs von Drittanbietern für Rechner und Newsfeeds gibt, wollen wir einen eigenen Algorithmus für Rechner entwickeln, der speziell auf unser Panel zugeschnitten ist.

Dieses Projekt untergräbt nicht die bestehenden Lösungen, sondern erweitert vielmehr die Auswahlmöglichkeiten für die Wirtschaftsbeteiligten. Es fördert ein tieferes Verständnis der Möglichkeiten von MetaTrader 5 und ermutigt die Nutzer, die Plattform effektiver zu nutzen. Durch das Angebot eines vollständig integrierten Instrumentenkastens innerhalb des Terminals wollen wir eine reibungslosere, produktivere Handelserfahrung unterstützen - und damit zeigen, wie die Fortschritte in der Handelstechnologie die Branche weiterhin revolutionieren.

Einige der zu berechnenden Werte sind:

  • Positionsgröße
  • Risikobetrag
  • Pip Wert
  • Margen-Anforderung
  • Gewinn/Verlust-Schätzung
  • Swap-/Übernacht-Gebühren
  • Risiko-Ertrags-Verhältnis
  • Margen-Level
  • Spread-Kosten
  • Break-Even-Preis
  • Expected Payoff
  • Auswirkungen des Hebels usw.

Diese Berechnungen sind für Forex-Händler von entscheidender Bedeutung, da sie einen strukturierten Rahmen für das Risikomanagement, die Optimierung von Handelsvorbereitungen und die Aufrechterhaltung der Kontotragfähigkeit bieten. Die Berechnungen der Positionsgröße und der Risikohöhe stellen sicher, dass die Händler nur einen kleinen, vordefinierten Teil ihres Kapitals riskieren und so vor erheblichen Verlusten geschützt sind. Pip-Wert und Gewinn/Verlust-Schätzungen ermöglichen eine präzise Handelsplanung und helfen Händlern, realistische Ziele und Stop-Loss-Niveaus festzulegen. Die Berücksichtigung von Margen-Anforderung und Margen-Level verhindern eine übermäßige Verschuldung, die zu Nachschussforderungen oder einem Crash des Kontos führen könnte. Swap-Gebühren sind für langfristige Händler, insbesondere für diejenigen, die Carry-Trades einsetzen, von entscheidender Bedeutung, da sie sich auf die Haltekosten auswirken.

Das Risiko-Ertrags-Verhältnis dient als Richtschnur für die Handelsauswahl, um sicherzustellen, dass die potenziellen Gewinne die Risiken rechtfertigen. Zusätzliche Kennzahlen wie Spread-Kosten, Break-even-Preis, erwarteter Payoff und Leverage-Effekt verbessern die Entscheidungsfindung, indem sie die Transaktionskosten, die Durchführbarkeit der Strategie und das Gesamtrisiko berücksichtigen. Zusammen ermöglichen diese Tools den Händlern, fundierte und disziplinierte Entscheidungen zu treffen und ihre Handelsgeschäfte auf ihre finanziellen Ziele und die Marktbedingungen abzustimmen, was letztlich zu einer höheren Konsistenz und Rentabilität führt.

Im nächsten Abschnitt werde ich kurz skizzieren, wie wir die heutige Entwicklung angehen werden.


Übersicht

Seit der Einführung des modularen Aufbaus dieser Serie haben wir die Möglichkeit, uns auf einzelne Abschnitte des Programms zu konzentrieren, ohne andere Komponenten zu unterbrechen. Diese Flexibilität erlaubt es uns nun, das Trade Management Panel zu erweitern, um Platz für die Integration von Berechnungswerkzeugen zu schaffen.

Um dies zu erreichen, werden wir zusätzliche Klassen aus der MQL5-Standardbibliothek verwenden. Anstatt für jede Auftragsart einen eigenen Eingabebereich zu haben, werden wir ein Drop-Down-Menü für die Auftragsauswahl einrichten, das von einer einzigen Eingabezeile begleitet wird. Durch dieses gestraffte Layout wird Platz für unsere Rechnerkomponenten frei.

Es müssen zwar nicht alle Handelswerte angezeigt werden, aber bestimmte Schlüsselwerte sind für eine fundierte Entscheidungsfindung unerlässlich und müssen zur Verfügung gestellt werden. Einige dieser Werte müssen überhaupt nicht berechnet werden, da sie bereits über Live-Marktdaten in MQL5 zugänglich sind.

Wir beginnen mit einer detaillierten Untersuchung der wichtigsten Forex-Begriffe und -Werte, einschließlich ihrer Definitionen, Formeln und ihrer Darstellung in MQL5. Danach gehen wir in die Implementierungsphase über und beginnen mit der Anpassung des Bereichs Orders im Handelsverwaltungsbereich, um die Front-End-Schnittstelle des Rechners zu integrieren.

Änderungen im TradeManagementPanel

Erweiterung des TradeManagementPanels

In dem mit A bezeichneten Abschnitt der obigen Abbildung verwenden wir die Klasse ComboBox, um den Auftragstyp aufzulisten und auszuwählen. Der Abschnitt B wird auf ein einzeiliges Layout umgestellt, wobei das Ablaufdatum C mit einem DatePicker für eine bessere Nutzerfreundlichkeit erweitert wird.

Nach den Layout-Anpassungen werden wir sowohl die Berechnungslogik als auch die GUI-Eingabelogik integrieren, die in der Regel weniger als drei Eingabefelder pro Berechnung benötigt.

Abschließend werde ich den Testprozess und die Ergebnisse erläutern und eine Bewertung der neuen Funktionen vornehmen.


Forex Berechnungen und Formeln 

In der folgenden Tabelle sind einige gängige Forex-Begriffe aufgeführt, die in der Regel berechnet werden müssen, zusammen mit den entsprechenden Formeln und nutzerdefinierten MQL5-Funktionen, die zu ihrer Berechnung verwendet werden. Diese Beispiele sind nicht erschöpfend; als Händler müssen Sie je nach der spezifischen Strategie, die Sie umsetzen, möglicherweise zusätzliche Berechnungen durchführen. Die Formeln in der folgenden Tabelle sind das Ergebnis umfangreicher Recherchen und einer Kombination mathematischer Erkenntnisse aus verschiedenen Online-Quellen. Für weitere Studien oder Überprüfungen sollten Sie zusätzliche Informationen über Google oder andere seriöse Quellen suchen.

Forex Begriff und Beschreibung Allgemeine Formel MQL5 kodierte Formel
Positionsgröße

Berechnet die Anzahl der zu handelnden Lots auf der Grundlage des Kontostands, des Risikoprozentsatzes und des Stop-Loss und stellt sicher, dass das Risiko mit der Strategie des Händlers übereinstimmt.




double CalculatePositionSize(double accountBalance, 
   double riskPercent, double stopLossPips, 
   string symbol)
{
   if (accountBalance <= 0 || riskPercent <= 0 || 
       stopLossPips <= 0) return 0.0;
   double pipValue = CalculatePipValue(symbol, 1.0, 
       AccountCurrency());
   if (pipValue == 0) return 0.0;
   double positionSize = (accountBalance * (riskPercent / 
       100.0)) / (stopLossPips * pipValue);
   double lotStep = MarketInfo(symbol, 
       MODE_LOTSTEP);
   double minLot = MarketInfo(symbol, 
       MODE_MINLOT);
   double maxLot = MarketInfo(symbol, 
       MODE_MAXLOT);
   return NormalizeDouble(
       MathMax(minLot, MathMin(maxLot, 
       positionSize)), (int)-MathLog10(lotStep));
}
                


Risikobetrag

Beziffert den Risikobetrag eines Handels auf der Grundlage von Positionsgröße und Stop-Loss, um sicherzustellen, dass die Verluste innerhalb akzeptabler Grenzen bleiben.




double CalculateRiskAmount(double positionSize, 
   double stopLossPips, string symbol)
{
   if (positionSize <= 0 || stopLossPips <= 0) 
       return 0.0;
   double pipValue = CalculatePipValue(symbol, positionSize, 
       AccountCurrency());
   return NormalizeDouble(positionSize * stopLossPips * 
       pipValue, 2);
}
                


Pip Wert

Berechnet den monetären Wert einer Ein-Pip-Bewegung für eine bestimmte Losgröße, was für Risiko- und Gewinnberechnungen unerlässlich ist.




double CalculatePipValue(string symbol, 
   double lotSize, string accountCurrency)
{
   double tickSize = MarketInfo(symbol, 
       MODE_TICKSIZE);
   double tickValue = MarketInfo(symbol, 
       MODE_TICKVALUE);
   double pipSize = StringFind(symbol, 
       "JPY") >= 0 ? 0.01 : 0.0001;
   double conversionRate = 1.0;
   if (accountCurrency != SymbolInfoString(symbol, 
       SYMBOL_CURRENCY_PROFIT)) {
      string conversionPair = SymbolInfoString(
          symbol, SYMBOL_CURRENCY_PROFIT) + accountCurrency;
      if (SymbolSelect(conversionPair, true)) {
         conversionRate = MarketInfo(conversionPair, 
             MODE_BID);
      } else {
         Print("Warning: Conversion pair ", 
             conversionPair, " not found, using 1.0");
      }
   }
   if (tickSize == 0) return 0.0;
   return NormalizeDouble((tickValue / tickSize) * 
       pipSize * lotSize * conversionRate, 2);
}
                


Margen-Anforderung

Bestimmt die Mittel, die zur Eröffnung einer Position benötigt werden, basierend auf Losgröße, Kontraktgröße und Leverage, um eine übermäßige Fremdfinanzierung zu vermeiden.




double CalculateMarginRequirement(double lotSize, 
   string symbol)
{
   double marginRequired = MarketInfo(symbol, 
       MODE_MARGINREQUIRED);
   if (marginRequired == 0) {
      Print("Error: Margin requirement not available ", 
          symbol);
      return 0.0;
   }
   return NormalizeDouble(lotSize * marginRequired, 
       2);
}
                


Schätzung von Gewinn/Verlust

Schätzt den potenziellen Gewinn oder Verlust auf der Grundlage von Einstiegs- und Ausstiegskursen und hilft so bei der Festlegung realistischer Handelsziele.




double CalculateProfitLoss(double entryPrice, 
   double exitPrice, double lotSize, 
   string symbol)
{
   if (lotSize <= 0 || entryPrice <= 0 || 
       exitPrice <= 0) return 0.0;
   double contractSize = MarketInfo(symbol, 
       MODE_LOTSIZE);
   double conversionRate = 1.0;
   if (AccountCurrency() != SymbolInfoString(symbol, 
       SYMBOL_CURRENCY_PROFIT)) {
      string conversionPair = SymbolInfoString(
          symbol, SYMBOL_CURRENCY_PROFIT) + AccountCurrency();
      if (SymbolSelect(conversionPair, true)) {
         conversionRate = MarketInfo(conversionPair, 
             MODE_BID);
      }
   }
   double priceDiff = exitPrice - entryPrice;
   double pips = priceDiff / (StringFind(symbol, 
       "JPY") >= 0 ? 0.01 : 0.0001);
   return NormalizeDouble(pips * CalculatePipValue(symbol, 
       lotSize, AccountCurrency()), 2);
}
                


Swap-/Übernacht-Gebühren

Berechnet die Zinsen, die für das Halten von Positionen über Nacht berechnet oder verdient werden, was für langfristige Handelsgeschäfte wichtig ist.




double CalculateSwap(double lotSize, 
   string symbol, bool isBuy, 
   int days = 1)
{
   double swapLong = MarketInfo(symbol, 
       MODE_SWAPLONG);
   double swapShort = MarketInfo(symbol, 
       MODE_SWAPSHORT);
   if (swapLong == 0 && swapShort == 0) {
      Print("Error: Swap rates not available ", 
          symbol);
      return 0.0;
   }
   double swap = isBuy ? swapLong : swapShort;
   datetime currentTime = TimeCurrent();
   if (TimeDayOfWeek(currentTime) == 3) 
       days *= 3;
   double totalSwap = lotSize * swap * days;
   return NormalizeDouble(totalSwap, 2);
}
                


Risiko-Ertrags-Verhältnis

Misst den potenziellen Gewinn im Verhältnis zum potenziellen Verlust und steuert die Handelsauswahl für eine positive Erwartungshaltung.




double CalculateRiskRewardRatio(double takeProfitPips, 
   double stopLossPips)
{
   if (stopLossPips <= 0 || takeProfitPips <= 0) 
       return 0.0;
   return NormalizeDouble(takeProfitPips / stopLossPips, 
       2);
}
                


Margen-Level

Zeigt das prozentuale Verhältnis zwischen dem Eigenkapital des Kontos und der in Anspruch genommenen Marge an und überwacht den Zustand des Kontos, um Nachschussforderungen zu vermeiden.




double CalculateMarginLevel()
{
   double equity = AccountEquity();
   double margin = AccountMargin();
   if (margin == 0) return 0.0;
   return NormalizeDouble((equity / margin) * 100, 
       2);
}
                


Kosten verteilen

Berechnet die monetären Kosten der Geld-Brief-Spanne für einen Handel, was für kurzfristige Handelsstrategien entscheidend ist.




double CalculateSpreadCost(double lotSize, 
   string symbol)
{
   double spreadPips = MarketInfo(symbol, 
       MODE_SPREAD) / 10.0;
   double pipValue = CalculatePipValue(symbol, lotSize, 
       AccountCurrency());
   return NormalizeDouble(spreadPips * pipValue * lotSize, 
       2);
}
                


Hebelwirkung

Misst die effektive Hebelwirkung, die bei einem Handel eingesetzt wird, und zeigt das Risiko im Verhältnis zum Eigenkapital des Kontos auf.




double CalculateLeverageImpact(double positionSize, 
   string symbol, double accountEquity)
{
   if (positionSize <= 0 || accountEquity <= 0) 
       return 0.0;
   double contractSize = MarketInfo(symbol, 
       MODE_LOTSIZE);
   double marketPrice = MarketInfo(symbol, 
       MODE_BID);
   return NormalizeDouble((positionSize * contractSize * 
       marketPrice) / accountEquity, 2);
}
                



Im nächsten Abschnitt über die Implementierung werden wir die CComboBox der MQL5-Standardbibliothek nutzen, um die Platznutzung für die Rechner-Steuerelemente zu optimieren, die in das TradeManagementPanel integriert werden. Dieser Ansatz bietet wertvolle Lektionen in effizientem UI-Design und Kontrollmanagement. Außerdem werden wir eine Komponente des DatePicker einbauen, um die Nutzerfreundlichkeit bei der Auswahl des Ablaufdatums einer Bestellung zu verbessern.


Umsetzung

Um einen stetigen Fortschritt zu gewährleisten, werden wir unsere Entwicklung in drei Hauptphasen unterteilen:

Sobald diese Schritte abgeschlossen sind, werden wir das NewAdminPanel EA aktualisieren, um die neuen Funktionen zu unterstützen und Tests durchzuführen. Es ist wichtig, während des gesamten Prozesses genau aufzupassen, um zu vermeiden, dass kritische Details übersehen werden - insbesondere bei der Arbeit mit den Komponenten ComboBox und DatePicker.


(1) Anpassung des Abschnitts „Ausstehende Aufträge“, um Platz für neue Kontrollen zu schaffen

Jetzt extrahieren wir den Abschnitt der schwebenden Aufträge aus dem TradeManagementPanel isolieren, um ihn für eine einfachere Implementierung der Komponenten ComboBox und DatePicker zu isolieren. Außerdem werden wir eine Schaltfläche für die Bestellung hinzufügen, die gedrückt wird, sobald die Bestellung vollständig konfiguriert ist.

Deklaration der Mitgliedsvariablen für schwebende Aufträge

Diese Mitgliedsvariablen befinden sich in der Klasse CTradeManagementPanel im Abschnitt „Pending Orders“. Zunächst wird eine Kennzeichnung deklariert, das über den Steuerelementen für die ausstehenden Aufträge als Abschnittsüberschrift erscheint („Pending Orders:“).

//  Pending Orders
CLabel      m_secPendingLabel;    // “Pending Orders:” header
CLabel      m_pendingPriceHeader; // “Price:” column header
CLabel      m_pendingTPHeader;    // “TP:” column header
CLabel      m_pendingSLHeader;    // “SL:” column header
CLabel      m_pendingExpHeader;   // “Expiration:” column header

CComboBox   m_pendingOrderType;   // Combobox for “Buy Limit / Buy Stop / Sell Limit / Sell Stop”
CEdit       m_pendingPriceEdit;   // Edit box for pending‐order price
CEdit       m_pendingTPEdit;      // Edit box for pending‐order take‐profit
CEdit       m_pendingSLEdit;      // Edit box for pending‐order stop‐loss
CDatePicker m_pendingDatePicker;  // DatePicker for expiration date
CButton     m_placePendingButton; // “Place Order” button for pending orders

Unmittelbar darunter dienen vier weitere Beschriftungen als Spaltenüberschriften: „Price:“, „TP:“, „SL:“ und „Expiration:“. Unterhalb der Beschriftungen befindet sich eine ComboBox, die es dem Nutzer ermöglicht, zwischen vier Arten von ausstehenden Bestellungen zu wählen: Buy Limit, Buy Stop, Sell Limit und Sell Stop. Rechts von dieser ComboBox befinden sich drei Steuerelemente, mit denen der Nutzer den Preis des schwebenden Auftrags, den Take-Profit (TP) bzw. den Stop-Loss (SL) eingeben kann. Neben diesen Eingabefeldern befindet sich eine Datumsauswahl, die die Wahl des Ablaufdatums erleichtert. Schließlich deklarieren wir eine Schaltfläche mit der Bezeichnung Place Order, die bei Betätigung die tatsächliche Erteilung des schwebenden Auftrags mit den angegebenen Parametern auslösen wird.

Indem wir diese sechs Steuerelemente und fünf Beschriftungen in diesem Abschnitt zusammenfassen, isolieren wir alles, was für die Erstellung und Verwaltung ausstehender Aufträge erforderlich ist. Diese Trennung macht es einfacher, nur die Logik der ausstehenden Bestellungen zu erklären oder zu überarbeiten, ohne den Rest des Panels zu berühren.

Erstellen der Steuerung von schwebenden Aufträgen in Create(...)

Innerhalb der Create(...)-Methode erstellen wir den gesamten Bereich der schwebenden Aufträge unmittelbar nach dem Zeichnen einer Trennlinie unter dem Forex-Rechner. Zunächst fügen wir eine kleine vertikale Lücke ein, um sie optisch von dem darüber liegenden Rechner zu trennen. Dann wird eine Abschnittsüberschrift („Pending Orders:“) erstellt und fett gedruckt, um sie von den anderen Abschnitten zu unterscheiden.

Rechts von dieser Kopfzeile befindet sich die ComboBox zur Auswahl der Auftragsart. Nachdem wir die ComboBox hinzugefügt und die vertikale Position verschoben haben, erstellen wir vier Spaltenüberschriften: Price:, TP:, L:, and Expiration: Jede Kopfzeile wird horizontal mit gleichem Abstand so positioniert, dass sie sich über der Eingabezeile befindet.

// In CTradeManagementPanel::Create(...), after Section separator:

// 10px vertical offset before “Section 3” header
curY += 10;
if(!CreateLabelEx(m_secPendingLabel, curX, curY, DEFAULT_LABEL_HEIGHT, 
                  "SecPend", "Pending Orders:", clrNavy))
   return(false);
m_secPendingLabel.Font("Arial Bold");
m_secPendingLabel.FontSize(10);

// Create the Combobox for order types
if(!CreateComboBox(m_pendingOrderType, "PendingOrderType", 
                   curX + SECTION_LABEL_WIDTH + GAP, curY, DROPDOWN_WIDTH, EDIT_HEIGHT))
   return(false);
curY += EDIT_HEIGHT + GAP;

// Column headers: Price, TP, SL, Expiration
int headerX = curX;
if(!CreateLabelEx(m_pendingPriceHeader, headerX, curY, DEFAULT_LABEL_HEIGHT, 
                  "PendPrice", "Price:", clrBlack))
   return(false);
if(!CreateLabelEx(m_pendingTPHeader, headerX + EDIT_WIDTH + GAP, curY, DEFAULT_LABEL_HEIGHT, 
                  "PendTP", "TP:", clrBlack))
   return(false);
if(!CreateLabelEx(m_pendingSLHeader, headerX + 2 * (EDIT_WIDTH + GAP), curY, DEFAULT_LABEL_HEIGHT, 
                  "PendSL", "SL:", clrBlack))
   return(false);
if(!CreateLabelEx(m_pendingExpHeader, headerX + 3 * (EDIT_WIDTH + GAP), curY, DEFAULT_LABEL_HEIGHT, 
                  "PendExp", "Expiration:", clrBlack))
   return(false);
curY += DEFAULT_LABEL_HEIGHT + GAP;

// Pending orders inputs row:
//  • Pending Price
int inputX = curX;
if(!CreateEdit(m_pendingPriceEdit, "PendingPrice", inputX, curY, EDIT_WIDTH, EDIT_HEIGHT))
   return(false);
double ask = SymbolInfoDouble(Symbol(), SYMBOL_ASK);
m_pendingPriceEdit.Text(DoubleToString(ask, 5));

//  • Pending TP
int input2X = inputX + EDIT_WIDTH + GAP;
if(!CreateEdit(m_pendingTPEdit, "PendingTP", input2X, curY, EDIT_WIDTH, EDIT_HEIGHT))
   return(false);
m_pendingTPEdit.Text("0.00000");

//  • Pending SL
int input3X = input2X + EDIT_WIDTH + GAP;
if(!CreateEdit(m_pendingSLEdit, "PendingSL", input3X, curY, EDIT_WIDTH, EDIT_HEIGHT))
   return(false);
m_pendingSLEdit.Text("0.00000");

//  • Pending Expiration (DatePicker)
int input4X = input3X + EDIT_WIDTH + GAP;
if(!CreateDatePicker(m_pendingDatePicker, "PendingExp", 
                     input4X, curY, DATEPICKER_WIDTH + 20, EDIT_HEIGHT))
   return(false);
datetime now = TimeCurrent();
datetime endOfDay = now - (now % 86400) + 86399;
m_pendingDatePicker.Value(endOfDay);

//  • Place Order button
int buttonX = input4X + DATEPICKER_WIDTH + GAP;
if(!CreateButton(m_placePendingButton, "Place Order", 
                 buttonX + 20, curY, BUTTON_WIDTH, BUTTON_HEIGHT, clrBlue))
   return(false);
curY += BUTTON_HEIGHT + GAP * 2;

Sobald die Kopfzeilen platziert sind, verschieben wir die vertikale Position nach unten und beginnen die Eingabezeile. Als erstes kommt ein Feld für den Preis des schwebenden Auftrags, das wir sofort mit dem aktuellen Briefkurs befüllen, um dem Nutzer eine gültige Vorgabe zu geben. Rechts davon platzieren wir die TP-Bearbeitung (initialisiert auf „0.00000“) und dann die SL-Bearbeitung (ebenfalls initialisiert auf „0.00000“). Daneben wird die Datumsauswahl erstellt und standardmäßig auf „Ende des Tages“ (23:59:59) eingestellt.

Schließlich wird eine Schaltfläche „Place Order“ erstellt und neben der Datumsauswahl positioniert, damit sie die anderen Steuerelemente nicht verdeckt. Sobald jedes Steuerelement erfolgreich erstellt wurde, bewegen wir den vertikalen Cursor weiter, um darunter Platz zu schaffen. In diesen Schritten werden alle erforderlichen Steuerelemente festgelegt, die ein Nutzer benötigt, um einen schwebenden Auftrag einzurichten - Typ, Preis, TP, SL, Ablaufdatum - und dann eine Schaltfläche zu drücken, um ihn zu platzieren.

Ereignisbehandlung für schwebende Aufträge

Diese Methoden reagieren auf Nutzerinteraktionen innerhalb des Bereichs „Pending Orders“:

void CTradeManagementPanel::OnChangePendingOrderType()
{
   string selected = m_pendingOrderType.Select();
   int    index    = (int)m_pendingOrderType.Value();
   Print("OnChangePendingOrderType: Selected='", selected, "', Index=", index);

   double price = 0.0;
   if(selected == "Buy Limit" || selected == "Buy Stop")
      price = SymbolInfoDouble(Symbol(), SYMBOL_ASK);
   else
      price = SymbolInfoDouble(Symbol(), SYMBOL_BID);

   m_pendingPriceEdit.Text(DoubleToString(price, 5));
   ChartRedraw();
}

void CTradeManagementPanel::OnChangePendingDatePicker()
{
   datetime selected = m_pendingDatePicker.Value();
   Print("OnChangePendingDatePicker: Selected='", 
         TimeToString(selected, TIME_DATE|TIME_MINUTES), "'");
   ChartRedraw();
}

Bei Auswahl einer anderen Auftragsart: Immer wenn der Nutzer eine neue Auftragsart aus der ComboBox auswählt (z. B. wenn er von „Buy Limit“ zu „Sell Limit“ wechselt), lesen wir den neu ausgewählten Text und prüfen, ob er mit „Buy“ oder „Sell“ beginnt. Wenn sie mit „Buy“ beginnt, wird der aktuelle Briefkurs (Ask) abgefragt, andernfalls der aktuelle Geldkurs (Bid). Mit diesem Marktwert füllen wir dann sofort das Preisfeld editieren. Dadurch wird sichergestellt, dass der Nutzer immer einen gültigen, aktuellen Standardpreis sieht, der der von ihm gewählten Auftragsart entspricht. Zum Schluss wird die Nutzeroberfläche des Charts neu gezeichnet, sodass der neue Preis sofort erscheint.

Zur Änderung des Ablaufdatums: Immer wenn der Nutzer das Ablaufdatum in der Datumsauswahl auswählt oder ändert, rufen wir das neue Datum ab und protokollieren es zur Fehlersuche. Anschließend wird die Nutzeroberfläche des Charts neu gezeichnet, um etwaige Änderungen sofort widerzuspiegeln, wenn beispielsweise andere Teile des Panels visuell vom gewählten Ablaufdatum abhängen. In diesem Stadium erfolgt keine weitere Validierung; jedes gültige Kalenderdatum wird akzeptiert.

Indem wir diese Handler klein und fokussiert halten, stellen wir sicher, dass die ComboBox und der Date Picker mit den aktuellen Marktbedingungen synchron bleiben und verhindern, dass der Nutzer versehentlich eine Bestellung mit einem ungültigen Preis aufgibt oder unwissentlich ein abgelaufenes Datum auswählt.

Validierungshilfe für schwebende Aufträge

Bevor ein schwebender Auftrag tatsächlich an den Makler gesendet wird, überprüfen wir, ob die Eingaben des Nutzers sinnvoll sind. Diese einzige Hilfsfunktion erzwingt drei Regeln:

  • Das Volumen muss positiv sein. Wenn die Losgröße null oder negativ ist, wird ein Fehler protokolliert und der Auftrag abgelehnt.
  • Der Preis muss positiv sein. Ein nicht positiver Preis kann keinen gültigen schwebenden Auftrag bilden.

bool CTradeManagementPanel::ValidatePendingParameters(double volume, double price, string orderType)
{
   if(volume <= 0)
   {
      Print("Invalid volume for pending order");
      return(false);
   }
   if(price <= 0)
   {
      Print("Invalid price for pending order");
      return(false);
   }
   double ask = SymbolInfoDouble(Symbol(), SYMBOL_ASK);
   double bid = SymbolInfoDouble(Symbol(), SYMBOL_BID);

   if(orderType == "Buy Limit" && price >= ask)
   {
      Print("Buy Limit price must be below Ask");
      return(false);
   }
   if(orderType == "Buy Stop" && price <= ask)
   {
      Print("Buy Stop price must be above Ask");
      return(false);
   }
   if(orderType == "Sell Limit" && price <= bid)
   {
      Print("Sell Limit price must be above Bid");
      return(false);
   }
   if(orderType == "Sell Stop" && price >= bid)
   {
      Print("Sell Stop price must be below Bid");
      return(false);
   }
   return(true);
}

Überprüfung der Marktbedingungen:

Zum Beispiel:

  1. Achten Sie bei einem „Buy Limit“ darauf, dass der Limit-Preis unbedingt unter dem aktuellen Briefkurs (Ask) liegt.
  2. Wenn es sich um einen „Buy Stop“ handelt, stellen Sie sicher, dass der Stop-Preis unbedingt über dem aktuellen Briefkurs (Ask) liegt.

Wenn alle Prüfungen erfolgreich sind, gibt der Helfer „true“ zurück und zeigt damit an, dass der Auftrag fortgesetzt werden kann. Indem wir die Validierung auf diese Weise strukturieren, verhindern wir häufige Fehler - wie z. B. die Platzierung eines „Buy Limit“ über dem Markt oder eines „Sell Stop“ beim oder über dem Geldkurs (Bid) - und geben sofortige, klare Rückmeldungen, wenn die Eingaben ungültig sind.

Der Handler für die Schaltfläche „Place Pending“

void CTradeManagementPanel::OnClickPlacePending()
{
   Print("OnClickPlacePending called");
   string     orderType = m_pendingOrderType.Select();
   double     price     = StringToDouble(m_pendingPriceEdit.Text());
   double     tp        = StringToDouble(m_pendingTPEdit.Text());
   double     sl        = StringToDouble(m_pendingSLEdit.Text());
   double     volume    = StringToDouble(m_volumeEdit.Text());      // reuse market‐order volume
   datetime   expiry    = m_pendingDatePicker.Value();
   ENUM_ORDER_TYPE_TIME type_time = (expiry == 0) ? ORDER_TIME_GTC : ORDER_TIME_SPECIFIED;

   // Validate inputs
   if(!ValidatePendingParameters(volume, price, orderType))
      return;

   // Place the correct type of pending order
   if(orderType == "Buy Limit")
      m_trade.BuyLimit(volume, price, Symbol(), sl, tp, type_time, expiry, "");
   else if(orderType == "Buy Stop")
      m_trade.BuyStop(volume, price, Symbol(), sl, tp, type_time, expiry, "");
   else if(orderType == "Sell Limit")
      m_trade.SellLimit(volume, price, Symbol(), sl, tp, type_time, expiry, "");
   else if(orderType == "Sell Stop")
      m_trade.SellStop(volume, price, Symbol(), sl, tp, type_time, expiry, "");
}

Wenn der Nutzer auf die Schaltfläche „Place Order“ klickt, sammelt dieser Handler alle erforderlichen Eingaben:

  • Der in der ComboBox ausgewählte Auftragstyp.
  • Der Preis des schwebenden Auftrags aus der entsprechenden Bearbeitung.
  • Take-Profit- und Stop-Loss-Werte aus ihren Bearbeitungen.
  • Das Volumen des Handelsgeschäfts, wiederverwendet aus der Volumenbearbeitung im Abschnitt Schnellausführung.
  • Ablaufdatum aus der Datumsauswahl.

Wir entscheiden dann, ob wir GTC (Good Till Canceled) oder einen bestimmten Ablaufmodus verwenden, je nachdem, ob die gewählte Ablaufzeit gleich Null ist. Als Nächstes rufen wir unsere Validierungshilfe auf. Wenn eine Prüfung fehlschlägt, verlassen wir den Vorgang, ohne etwas zu unternehmen.

Wenn die Validierung erfolgreich ist, rufen wir genau eine der vier CTrade-Methoden auf - BuyLimit, BuyStop, SellLimit oder SellStop - und übergeben dabei Volumen, Preis, Symbol, SL, TP, Zeitmodus und Ablauf. Bei jedem Aufruf werden die Eingaben des Nutzers verwendet, sodass der Makler zum Zeitpunkt der Beendigung dieses Handlers die korrekte Anfrage für die ausstehende Bestellung erhalten hat. Wenn ein Parameter ungültig war, kehren wir einfach zurück und verlassen uns auf die protokollierten Diagnosen, um den Fehler zu melden.

OnEvent(...) Routing für schwebende Aufträge

bool CTradeManagementPanel::OnEvent(const int id, const long &lparam, 
                                    const double &dparam, const string &sparam)
{
   // 1) Forward all events to the calculator first
   if(m_calculator.OnEvent(id, lparam, dparam, sparam))
      return(true);

   // 2) Dispatch Pending‐section events
   if(id == CHARTEVENT_OBJECT_CLICK)
   {
      if(sparam == m_placePendingButton.Name())
      {
         OnClickPlacePending();
         return(true);
      }
   }
   else if(id == CHARTEVENT_OBJECT_CHANGE)
   {
      if(sparam == m_pendingOrderType.Name())
      {
         OnChangePendingOrderType();
         return(true);
      }
      else if(sparam == m_pendingDatePicker.Name())
      {
         OnChangePendingDatePicker();
         return(true);
      }
   }

   // 3) Fallback to the base class for any other events
   return CAppDialog::OnEvent(id, lparam, dparam, sparam);
}

Innerhalb der Hauptmethode OnEvent(...) von CTradeManagementPanel werden die Ereignisse für ausstehende Aufträge wie folgt weitergeleitet:

Zuerst den Rechner: Wir leiten alle Ereignisse an den eingebetteten Rechner weiter. Wenn der Rechner das Ereignis verarbeitet (z. B. wenn der Nutzer eine Pip-Wert-Eingabe ändert), hören wir dort auf.

Logik der schwebenden Aufträge:

  • Wenn es sich bei dem Ereignis um einen „Klick“ handelt und der Name des angeklickten Objekts mit der Schaltfläche für die ausstehende Bestellung übereinstimmt, wird der Handler „Place Pending“ aufgerufen.
  • Wenn es sich bei dem Ereignis um eine „Objektänderung“ handelt und der geänderte Objektname entweder mit der Combobox oder dem Datumspicker übereinstimmt, rufen wir den entsprechenden Handler auf (OnChangePendingOrderType oder OnChangePendingDatePicker).
  • Rückgriff: Jedes andere Ereignis fließt zurück zur Basis CAppDialog::OnEvent(...), sodass die Abschnitte Quick Execution und All-Ops ihre Chance bekommen, Klicks oder Bearbeitungen zu verarbeiten.

Dieses Routing stellt sicher, dass Interaktionen mit anhängigen Aufträgen sauber und isoliert gehandhabt werden, ohne andere Bereiche des Panels zu beeinträchtigen.

Testen der ComboBox- und DatePicker-Implementierung.

Angepasstes TradeManagementPanel (ComboBox und DatePicker Implementierung)


(2) Entwicklung der Steuerklasse ForexValuesCalculator

Vor den Klassendefinitionen fügen wir fünf MQL5-Standardbibliotheks-Header in das Verzeichnis „Controls“ ein. Jede dieser Klassen stellt eine GUI-Steuerklasse zur Verfügung, die wir in CForexCalculator nutzen werden:

#include <Controls\Dialog.mqh>
#include <Controls\ComboBox.mqh>
#include <Controls\Edit.mqh>
#include <Controls\Label.mqh>
#include <Controls\Button.mqh>

Dialog.mqh

Stellt die Basisklasse CAppDialog zur Verfügung, die eine Sammlung von Steuerelementen verwaltet, das Layout handhabt und Ereignisse weiterleitet. Obwohl CForexCalculator nicht direkt von CAppDialog abgeleitet ist, muss es in ein übergeordnetes Dialogfeld (z. B. CTradeManagementPanel) integriert werden, sodass das Vorhandensein von Dialog.mqh sicherstellt, dass alle Aufrufe zum Hinzufügen der Steuerelemente unseres Rechners (AddToDialog) und zur Weiterleitung von Ereignissen korrekt kompiliert werden. Ohne Dialog.mqh könnten wir dlg.Add(...) nicht aufrufen, um unsere Beschriftungen, Bearbeitungen und Schaltflächen an die übergeordnete Nutzeroberfläche anzuhängen.

ComboBox.mqh

Präsentiert die Klasse CComboBox, die wir für das Dropdown-Menü der Berechnungsoptionen verwenden. Durch die Einbindung dieser Datei können wir eine CComboBox-Instanz (m_dropdown) erzeugen und manipulieren, m_dropdown.Create(...) aufrufen, um sie zu positionieren, sie mit AddItem füllen und auf CHARTEVENT_OBJECT_CHANGE reagieren, wenn der Nutzer einen anderen Begriff auswählt. Ohne sie würde der Compiler nicht wissen, was CComboBox bedeutet.

Edit.mqh

Definiert die Klasse CEdit, die für alle numerischen und Texteingabefelder verwendet wird (z. B. Kontostand, Risikoprozentsatz, Stop-Loss, Symbol usw.). Wir erstellen dynamisch eine unterschiedliche Anzahl von CEdit-Steuerelementen innerhalb von m_inputs[], je nachdem, welcher Berechnungsterm ausgewählt ist. Jedes CEdit muss erstellt, zum Dialogfeld hinzugefügt und später durch GetInputValue oder GetInputString in CEdit* zurückgeführt werden. Wenn wir Edit.mqh weglassen würden, wäre keiner dieser Aufrufe kompilierbar.

Label.mqh

Bringt CLabel ein, das wir überall dort verwenden, wo wir statischen Text auf dem Bildschirm haben wollen: die Kennzeichnung „Calculation Option:“ (m_calcOptionLabel), jedes einzelne Eingabe-Label (für Kontostand, Risikoprozent usw.) und die Kennzeichnung „Result:“ (m_resultLabel). Jedes CLabel muss erstellt werden, damit der Nutzer weiß, was er in jedes CEdit eingeben muss. Ohne Label.mqh könnten wir den einzelnen Eingabefeldern keinen Kontext zuweisen.

Button.mqh

Liefert die Klasse CButton. Wir verwenden CButton für die Schaltfläche „Calculate“ (m_calculateButton). Durch Einfügen dieser Kopfzeile können wir m_calculateButton.Create(...) aufrufen, ihm eine Hintergrundfarbe geben, seinen Text festlegen und Klicks darauf über OnEvent erkennen. Wenn wir Button.mqh weglassen würden, würde der Compiler CButton nicht erkennen, und wir könnten nicht auf „Calculate“-Klicks reagieren.

Plan auf Projektebene für Einschlüsse

In dem größeren Projekt haben wir zwei Teile, die von diesen Steuerelementen abhängen:

ForexValuesCalculator.mqh benötigt alle Kopfzeilen der fünf Controls\*.mqh, da es einen in sich geschlossenen, wiederverwendbaren „Mini-Dialog“ zur Berechnung verschiedener Devisenwerte erstellt. Überall, wo wir CLabel, CEdit, CComboBox oder CButton verwenden, muss der entsprechende Header vorhanden sein, damit der Präprozessor von MQL5 die Klassendefinitionen finden kann.

Indem wir alle GUI-bezogenen Includes oben gruppieren, stellen wir sicher, dass jeder andere EA oder jedes Panel (z.B. TradeManagementPanel.mqh) einfach #include „ForexValuesCalculator.mqh“ einbinden kann und sofort Zugriff auf jedes benötigte GUI-Steuerelement hat, ohne dass an anderer Stelle zusätzliche Includes eingestreut werden.

Deklaration der Mitglieder 

Die Klasse CForexCalculator beginnt mit der Deklaration mehrerer Nutzeroberflächen-Steuerelemente und Datenstrukturen, die zusammen die Schnittstelle des Rechners bilden. Im oberen Bereich kann der Nutzer über eine Bezeichnung (m_calcOptionLabel) und ein Dropdown-Menü (m_dropdown) auswählen, welche Berechnung er durchführen möchte (z. B. Positionsgröße, Risikobetrag, Pip-Wert, Gewinn/Verlust oder Risk-to-Reward). Darunter befindet sich eine Schaltfläche „Calculate“ (m_calculateButton), auf die der Nutzer klickt, sobald alle Eingaben festgelegt sind. Um die Ergebnisse anzuzeigen, wird ein schreibgeschütztes Bearbeitungsfeld (m_resultField) mit einem weiteren Label (m_resultLabel) gepaart, das einen beschreibenden Text wie „Result: ...“ gefolgt von einem numerischen Wert enthält.
// Forex Calculator Class
class CForexCalculator {
private:
   CLabel      m_calcOptionLabel;   // “Calculation Option:” label
   CComboBox   m_dropdown;          // Dropdown for selecting calculation term
   CEdit       m_resultField;       // Read-only field to display result
   CLabel      m_resultLabel;       // Label preceding the result (e.g., “Result:”)
   CButton     m_calculateButton;   // “Calculate” button
   CWnd       *m_inputs[];          // Dynamically added label+edit pairs
   long        m_chart_id;          // Chart identifier
   string      m_name;              // Prefix for control names
   int         m_originX;           // X-coordinate origin for dynamic fields
   int         m_originY;           // Y-coordinate origin for dynamic fields

   InputField  m_positionSizeInputs[4];
   InputField  m_riskAmountInputs[3];
   InputField  m_pipValueInputs[3];
   InputField  m_profitLossInputs[4];
   InputField  m_riskRewardInputs[2];

   // … (other private methods follow) …
public:
   CForexCalculator();
   bool Create(const long chart, const string &name, const int subwin,
               const int x, const int y, const int w, const int h);
   bool AddToDialog(CAppDialog &dlg);
   void UpdateResult(const string term);
   double GetInputValue(const string name);
   string GetInputString(const string &name);
   CEdit* GetInputEdit(const string &name);
   string GetSelectedTerm();
   bool OnEvent(const int id, const long &lparam,
                const double &dparam, const string &sparam);
   ~CForexCalculator();
};

Alle variablen Eingabefelder - jedes besteht aus einem Label und einem Eingabefeld - werden in einem dynamischen Array (m_inputs[]) gespeichert. Hinter den Kulissen enthält die Klasse fünf Arrays mit fixierter Größe der Struktur InputField (m_positionSizeInputs, m_riskAmountInputs, m_pipValueInputs, m_profitLossInputs, m_riskRewardInputs). Jedes InputField enthält einen Namen, eine Kennzeichnung und einen numerischen Standardwert. Schließlich zeigen m_originX und m_originY an, wo der Rechnerbereich innerhalb des übergeordneten Dialogs beginnt, während m_chart_id und m_name die Kennung des Charts und ein Präfix für eindeutige Kontrollnamen speichern. In Kombination definieren diese Mitglieder sowohl das Layout des Rechners als auch die für jede Art von Devisenberechnung erforderlichen Daten.

Initialisierung statischer Standardwerte (InitInputs)

Die Methode InitInputs wird einmal bei der Erstellung des Rechnerobjekts ausgeführt. Sie füllt die fünf Arrays der InputField-Strukturen mit beschreibenden Kennzeichnungen und Rückfall-Nummern. Die Gruppe „Positionsgröße“ enthält zum Beispiel die Felder für Kontostand, Risikoprozentsatz, Stop-Loss in Pips und Symbol. Die Gruppe „Risikobetrag“ umfasst Positionsgröße, Stop-Loss in Punkten und Symbol. Jedes Array wird so eingerichtet, dass später, wenn der Nutzer einen Berechnungstyp auswählt, das entsprechende InputField-Array in dynamische Steuerelemente kopiert wird. In diesem Stadium erhält das Feld „Kontostand“ einen Platzhalter-Standardwert von 0,0 (der zur Laufzeit ersetzt wird), während Risikoprozentsätze und Pip-Werte kleine Standardwerte wie 1 % oder 20 Pips erhalten. Diese statische Initialisierung stellt sicher, dass die Eingaben jeder Berechnung mit sinnvollen Bezeichnungen und einigen numerischen Ausgangswerten erscheinen.

void InitInputs()
{
   // Position Size inputs
   m_positionSizeInputs[0].name         = "accountBalance";
   m_positionSizeInputs[0].label        = "Account Balance (" + AccountInfoString(ACCOUNT_CURRENCY) + ")";
   m_positionSizeInputs[0].defaultValue = 0.0;  // updated at runtime
   m_positionSizeInputs[1].name         = "riskPercent";
   m_positionSizeInputs[1].label        = "Risk Percentage (%)";
   m_positionSizeInputs[1].defaultValue = 1.0;
   m_positionSizeInputs[2].name         = "stopLossPips";
   m_positionSizeInputs[2].label        = "Stop Loss (Pips)";
   m_positionSizeInputs[2].defaultValue = 20.0;
   m_positionSizeInputs[3].name         = "symbol";
   m_positionSizeInputs[3].label        = "Symbol";
   m_positionSizeInputs[3].defaultValue = 0.0;

   // Risk Amount inputs
   m_riskAmountInputs[0].name = "positionSize";
   m_riskAmountInputs[0].label = "Position Size (Lots)";
   m_riskAmountInputs[0].defaultValue = 0.1;
   m_riskAmountInputs[1].name = "stopLossPips";
   m_riskAmountInputs[1].label = "Stop Loss (Pips)";
   m_riskAmountInputs[1].defaultValue = 20.0;
   m_riskAmountInputs[2].name = "symbol";
   m_riskAmountInputs[2].label = "Symbol";
   m_riskAmountInputs[2].defaultValue = 0.0;

   // Pip Value inputs
   m_pipValueInputs[0].name = "lotSize";
   m_pipValueInputs[0].label = "Lot Size";
   m_pipValueInputs[0].defaultValue = 0.1;
   m_pipValueInputs[1].name = "symbol";
   m_pipValueInputs[1].label = "Symbol";
   m_pipValueInputs[1].defaultValue = 0.0;
   m_pipValueInputs[2].name = "accountCurrency";
   m_pipValueInputs[2].label = "Account Currency";
   m_pipValueInputs[2].defaultValue = 0.0;

   // Profit/Loss inputs
   m_profitLossInputs[0].name = "entryPrice";
   m_profitLossInputs[0].label = "Entry Price";
   m_profitLossInputs[0].defaultValue = SymbolInfoDouble(_Symbol, SYMBOL_BID);
   m_profitLossInputs[1].name = "exitPrice";
   m_profitLossInputs[1].label = "Exit Price";
   m_profitLossInputs[1].defaultValue = SymbolInfoDouble(_Symbol, SYMBOL_BID) + 0.0020;
   m_profitLossInputs[2].name = "lotSize";
   m_profitLossInputs[2].label = "Lot Size";
   m_profitLossInputs[2].defaultValue = 0.1;
   m_profitLossInputs[3].name = "symbol";
   m_profitLossInputs[3].label = "Symbol";
   m_profitLossInputs[3].defaultValue = 0.0;

   // Risk-to-Reward inputs
   m_riskRewardInputs[0].name = "takeProfitPips";
   m_riskRewardInputs[0].label = "Take Profit (Pips)";
   m_riskRewardInputs[0].defaultValue = 40.0;
   m_riskRewardInputs[1].name = "stopLossPips";
   m_riskRewardInputs[1].label = "Stop Loss (Pips)";
   m_riskRewardInputs[1].defaultValue = 20.0;
}

Festlegen von Laufzeitvorgaben (SetDynamicDefaults)

Da der tatsächliche Kontostand des Nutzers nur zur Laufzeit bekannt ist, überschreibt die Methode SetDynamicDefaults m_positionSizeInputs[0].defaultValue (das Feld „Kontostand“) mit AccountInfoDouble(ACCOUNT_BALANCE). Dadurch wird sichergestellt, dass das Eingabefeld für den Kontostand mit dem tatsächlichen Kontostand des Händlers vorausgefüllt wird, wenn die Eingaben für die „Positionsgröße“ auf dem Bildschirm erscheinen. Alle anderen dynamischen Vorgaben - wie Geld-/Briefkurs oder Umrechnungskurse - werden ebenfalls aktualisiert, sobald der Rechner erstellt wird. Durch die Trennung von statischen Vorgaben und Laufzeitvorgaben bleibt die Klasse flexibel: Die Initialisierung zur Entwurfszeit erfolgt in InitInputs, während schnelle Anpassungen an marktabhängige Felder in SetDynamicDefaults erfolgen.

void SetDynamicDefaults()
{
   // Overwrite the “Account Balance” default with the real balance at runtime
   m_positionSizeInputs[0].defaultValue = AccountInfoDouble(ACCOUNT_BALANCE);
}

Helfer für die Berechnung

Unterhalb der Eingabefelder werden die einzelnen Formeln durch eine Reihe von Hilfsmethoden ausgeführt:

1. CalculatePipValue

double CalculatePipValue(const string symbol, const double lotSize, const string accountCurrency)
{
   double tickSize  = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_SIZE);
   double tickValue = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_VALUE);
   double pipSize   = (StringFind(symbol, "JPY") >= 0) ? 0.01 : 0.0001;
   double rate      = 1.0;
   string profitCcy = SymbolInfoString(symbol, SYMBOL_CURRENCY_PROFIT);
   if(accountCurrency != profitCcy)
   {
      string pair = profitCcy + accountCurrency;
      if(SymbolSelect(pair, true))
         rate = SymbolInfoDouble(pair, SYMBOL_BID);
   }
   if(tickSize == 0.0) return 0.0;
   return NormalizeDouble((tickValue / tickSize) * pipSize * lotSize * rate, 2);
}

CalculatePipValue berechnet, wie viel ein Pip in der Währung des Kontos für ein bestimmtes Symbol und eine bestimmte Losgröße wert ist. Es ruft zunächst SymbolInfoDouble auf, um SYMBOL_TRADE_TICK_SIZE und SYMBOL_TRADE_TICK_VALUE zu erhalten. Dann wählt es entweder 0,01 (für JPY-Paare) oder 0,0001 als „Pip-Größe“. Wenn sich die Gewinnwährung des Paares von der Kontowährung unterscheidet, werden die beiden Währungen miteinander verknüpft (z. B. „EURUSD“, wenn der Gewinn in EUR und das Konto in USD ist), das entsprechende Umrechnungssymbol ausgewählt und das aktuelle Gebot als Kurs ermittelt. Schließlich wird tickValue durch tickSize geteilt, mit pipSize, lotSize und rate multipliziert und das Ergebnis auf zwei Dezimalstellen gerundet zurückgegeben. Ein Rückgabewert von 0,0 bedeutet, dass die Eingaben ungültig sind (z.B. tickSize war Null).

2. CalculatePositionSize

double CalculatePositionSize(double bal, double pct, double sl, string sym)
{
   double pv = CalculatePipValue(sym, 1.0, AccountInfoString(ACCOUNT_CURRENCY));
   if(bal <= 0 || pct <= 0 || sl <= 0 || pv <= 0) return 0.0;
   double size = (bal * (pct / 100.0)) / (sl * pv);
   double step = SymbolInfoDouble(sym, SYMBOL_VOLUME_STEP);
   double minL = SymbolInfoDouble(sym, SYMBOL_VOLUME_MIN);
   double maxL = SymbolInfoDouble(sym, SYMBOL_VOLUME_MAX);
   int dp = (int)-MathLog10(step);
   return NormalizeDouble(MathMax(minL, MathMin(maxL, size)), dp);
}

Bei einem Kontostand, einem prozentualen Risiko und einem Stop-Loss in Pips liefert CalculatePositionSize die optimale Losgröße. Es ruft zunächst CalculatePipValue(sym, 1.0, AccountInfoString(ACCOUNT_CURRENCY)) auf, um den Pip-Wert für ein ganzes Los zu ermitteln. Ist eine Eingabe oder ein pv gleich Null oder negativ, wird 0 zurückgegeben.

Andernfalls verwendet es die Formel:

positionSize = (balance × (riskPercent / 100)) ÷ (stopLossPips × pipValue)

Anschließend werden die Werte SYMBOL_VOLUME_STEP, SYMBOL_VOLUME_MIN und SYMBOL_VOLUME_MAX des Instruments abgerufen, um die berechnete Losgröße zu begrenzen und zu runden. Die Rundungsdezimale dp stammen aus -MathLog10(step), wodurch sichergestellt wird, dass die zurückgegebene Größe die vom Makler erlaubten Schrittweiten einhält (z. B. 0,01, 0,1).

3. CalculateRiskAmount

Wenn der Nutzer seine Positionsgröße (ps in Lots) und Stop-Loss-Pips (sl) kennt, berechnet CalculateRiskAmount, wie viel Kapital in der Kontowährung riskiert wird. Es erhält den Pip-Wert für dieses ps über CalculatePipValue(sym, ps, ...) und multipliziert dann ps × sl × pipValue. Das Ergebnis wird auf zwei Dezimalstellen normiert. Ist eine der beiden Eingaben Null oder negativ, gibt die Funktion 0,0 zurück und signalisiert damit ungültige Eingaben.

double CalculateRiskAmount(double ps, double sl, string sym)
{
   if(ps <= 0 || sl <= 0) return 0.0;
   double pv = CalculatePipValue(sym, ps, AccountInfoString(ACCOUNT_CURRENCY));
   return NormalizeDouble(ps * sl * pv, 2);
}

4. CalculateProfitLoss

double CalculateProfitLoss(double entry, double exit, double lotSize, string sym)
{
   if(entry <= 0 || exit <= 0 || lotSize <= 0) return 0.0;
   double pipSz = (StringFind(sym, "JPY") >= 0) ? 0.01 : 0.0001;
   double diff  = (exit - entry) / pipSz;
   return NormalizeDouble(diff * CalculatePipValue(sym, lotSize, AccountInfoString(ACCOUNT_CURRENCY)), 2);
}

CalculateProfitLoss ermittelt den Netto-Gewinn/Verlust in Kontowährung für einen gegebenen Einstiegskurs, Ausstiegskurs, Losgröße und Symbol. Es berechnet die Anzahl der gewonnenen oder verlorenen Pips als (exit - entry) ÷ pipSize, wobei pipSize für JPY-Paare 0,01 und ansonsten 0,0001 beträgt. Anschließend wird die Pip-Differenz mit CalculatePipValue(sym, lotSize, accountCurrency) multipliziert, um die Pips in einen Gewinn in Kontowährung umzurechnen. Das Endergebnis wird auf zwei Dezimalstellen gerundet. Wenn eine numerische Eingabe ungültig ist, gibt die Methode 0,0 zurück.

5. CalculateRiskRewardRatio

double CalculateRiskRewardRatio(double tp, double sl)
{
   if(tp <= 0 || sl <= 0) return 0.0;
   return NormalizeDouble(tp / sl, 2);
}

Für „Risk-to-Reward“ braucht der Nutzer nur Take-Profit-Pips (tp) und Stop-Loss-Pips (sl). Sofern beide positiv sind, gibt die Funktion das Verhältnis tp / sl auf zwei Dezimalstellen gerundet zurück. Ist eine der beiden Eingaben Null oder negativ, wird 0,0 zurückgegeben, was auf ungültige Daten hinweist.

Helfer für das Layout: Hinzufügen einzelner Felder (AddField)

Die Methode AddField ist für die Erstellung einer Kennzeichnung + Bearbeitungspaares für ein InputField zuständig. Sie erhält einen Verweis auf ein InputField (das einen Namen, einen Beschriftungstext und einen Standardwert enthält) und die aktuelle vertikale Cursorposition y. Die Methode berechnet x0 = m_originX + CALC_INDENT_LEFT, damit alle Beschriftungen an einem einheitlichen linken Rand beginnen.

bool AddField(const InputField &f, int &y)
{
   int x0 = m_originX + CALC_INDENT_LEFT;

   // Create label
   CLabel *lbl = new CLabel();
   if(!lbl.Create(m_chart_id, m_name + "Lbl_" + f.name, 0,
                  x0, y, 
                  x0 + CALC_LABEL_WIDTH, y + CALC_EDIT_HEIGHT))
   {
      delete lbl;
      return false;
   }
   lbl.Text(f.label);
   ArrayResize(m_inputs, ArraySize(m_inputs) + 1);
   m_inputs[ArraySize(m_inputs) - 1] = lbl;

   // Create edit
   CEdit *edt = new CEdit();
   if(!edt.Create(m_chart_id, m_name + "Inp_" + f.name, 0,
                  x0 + CALC_EDIT_OFFSET + RESULT_BUTTON_GAP, y,
                  x0 + CALC_EDIT_OFFSET + RESULT_BUTTON_GAP + CALC_EDIT_WIDTH,
                  y + CALC_EDIT_HEIGHT))
   {
      delete edt;
      return false;
   }
   if(f.name == "symbol")
      edt.Text(_Symbol);
   else if(f.name == "accountCurrency")
      edt.Text(AccountInfoString(ACCOUNT_CURRENCY));
   else
      edt.Text(StringFormat("%.2f", f.defaultValue));

   ArrayResize(m_inputs, ArraySize(m_inputs) + 1);
   m_inputs[ArraySize(m_inputs) - 1] = edt;

   y += CALC_EDIT_HEIGHT + CALC_CONTROLS_GAP_Y;
   return true;
}

AddField akzeptiert eine InputField-Referenz (mit Name, Beschriftung, Standardwert) und die aktuelle vertikale Position y. Zunächst wird x0 = m_originX + CALC_INDENT_LEFT berechnet, um den linken Rand der Kennzeichnung zu positionieren. Ein neues CLabel mit dem Namen m_name + „Lbl_“ + f.name wird an der Position (x0, y) mit einer festen Breite/Höhe erstellt. Sein Text wird auf f.label gesetzt und zu m_inputs[] hinzugefügt.

Als Nächstes wird ein CEdit an der Stelle (x0 + CALC_EDIT_OFFSET + RESULT_BUTTON_GAP, y) erstellt, damit alle Eingabefelder einheitlich ausgerichtet sind. Wenn f.name gleich „Symbol“ ist, wird _Symbol vorausgefüllt; wenn „accountCurrency“, wird die Kontowährung vorausgefüllt; andernfalls wird f.defaultValue auf zwei Dezimalstellen formatiert. Die neue Eingabekontrolle wird an m_inputs[] angehängt. Schließlich wird y um die Kontrollhöhe plus CALC_CONTROLS_GAP_Y inkrementiert, wodurch die Position für das nächste Feld festgelegt wird. Indem jede neue Kennzeichnung+Edit in m_inputs[] injiziert wird, garantiert AddField, dass sie dem Dialog später hinzugefügt und richtig verwaltet werden.

Erstellung aller Eingaben für einen bestimmten Term (CreateInputFields)

Immer, wenn der Nutzer einen neuen Berechnungsterm auswählt (oder bei der ersten Erstellung), löscht CreateInputFields alle zuvor erstellten Steuerelemente (ArrayFree(m_inputs)) und setzt dann y direkt unter das Dropdown. Es wird geprüft, welcher Begriff gewählt wurde - „Positionsgröße“ (4 Eingaben), „Risikobetrag“ (3), „Pip-Wert“ (3), „Gewinn/Verlust“ (4) oder „Risiko/Belohnung“ (2). Für jedes InputField im entsprechenden Array ruft es AddField(...) auf. Wenn ein AddField fehlschlägt, gibt die Methode den Wert false zurück und stoppt das weitere Layout. Wenn alle Felder erfolgreich hinzugefügt wurden, wird true zurückgegeben. Das Ergebnis ist, dass zur Laufzeit nur die für die ausgewählte Berechnung relevanten Paare aus Beschriftung und Bearbeitung auf dem Bildschirm erscheinen, sauber gestapelt und mit gleichmäßigen Abständen.

bool CreateInputFields(const string term)
{
   ArrayFree(m_inputs);
   int y = m_originY + CALC_INDENT_TOP + CALC_EDIT_HEIGHT + CALC_CONTROLS_GAP_Y;

   if(term == "Position Size")
      for(int i = 0; i < 4; i++)
         if(!AddField(m_positionSizeInputs[i], y)) return false;
   else if(term == "Risk Amount")
      for(int i = 0; i < 3; i++)
         if(!AddField(m_riskAmountInputs[i], y)) return false;
   else if(term == "Pip Value")
      for(int i = 0; i < 3; i++)
         if(!AddField(m_pipValueInputs[i], y)) return false;
   else if(term == "Profit/Loss")
      for(int i = 0; i < 4; i++)
         if(!AddField(m_profitLossInputs[i], y)) return false;
   else if(term == "Risk-to-Reward")
      for(int i = 0; i < 2; i++)
         if(!AddField(m_riskRewardInputs[i], y)) return false;
   else
      return false;

   return true;
}

Panelkonstruktion (Create)

Beim Aufrufen von Create wird die Nutzeroberfläche des Rechners in einem übergeordneten Dialogfeld instanziiert. Zunächst werden die Karten-ID, ein Namenspräfix und die Ursprungskoordinaten (x, y) gespeichert. Dann:

  • Option Label

Eine statische Kennzeichnung m_calcOptionLabel wird an (x, y) mit dem Text „Calculation Option:“ erstellt. Dieser befindet sich oberhalb des Dropdowns.

  • Auswahlliste

Die CComboBox (m_dropdown) wird bei (comboX + 70, y) rechts von der Beschriftung „Berechnungsoption“ erstellt. Es wird mit den fünf Berechnungsbegriffen gefüllt. m_dropdown.Select(0) setzt „Position Size“ als Standard.

  • Schaltfläche Calculate

Ein CButton (m_calculateButton) wird in der Nähe des unteren Teils des Panel-Blocks positioniert (unter Verwendung von btnX und btnY Berechnungen). Es ist mit „Calculate“ beschriftet und mit einem stahlblauen Hintergrund und weißem Text gestaltet. Wenn Sie darauf klicken, wird UpdateResult ausgelöst.

bool Create(const long chart, const string &name, const int subwin,
            const int x, const int y, const int w, const int h)
{
   m_chart_id = chart;
   m_name     = name + "_Calc_";
   m_originX  = x;
   m_originY  = y;

   // 1) “Calculation Option:” label
   if(!m_calcOptionLabel.Create(chart, m_name + "CalcOptLbl", subwin,
                                x, y, x + CALC_LABEL_WIDTH, y + CALC_EDIT_HEIGHT))
      return false;
   m_calcOptionLabel.Text("Calculation Option:");

   // 2) Dropdown immediately to the right
   int comboX = x + CALC_LABEL_WIDTH + DROPDOWN_LABEL_GAP;
   if(!m_dropdown.Create(chart, m_name + "Dropdown", subwin,
                        comboX, y, comboX + (w - CALC_LABEL_WIDTH - DROPDOWN_LABEL_GAP), y + CALC_EDIT_HEIGHT))
      return false;
   m_dropdown.AddItem("Position Size");
   m_dropdown.AddItem("Risk Amount");
   m_dropdown.AddItem("Pip Value");
   m_dropdown.AddItem("Profit/Loss");
   m_dropdown.AddItem("Risk-to-Reward");
   m_dropdown.Select(0);

   // 3) “Calculate” button near the bottom of this panel area
   int btnX = x + w - CALC_BUTTON_WIDTH - 120;
   int btnY = y + h - CALC_BUTTON_HEIGHT + 30;
   if(!m_calculateButton.Create(chart, m_name + "CalcBtn", subwin,
                                btnX, btnY, btnX + CALC_BUTTON_WIDTH, btnY + CALC_BUTTON_HEIGHT))
      return false;
   m_calculateButton.Text("Calculate");
   m_calculateButton.ColorBackground(clrSteelBlue);
   m_calculateButton.Color(clrWhite);

   // 4) Result label and read-only field to the right of the button
   int blockX = btnX + CALC_BUTTON_WIDTH + RESULT_BUTTON_GAP;
   int lblY = btnY - 20;
   if(!m_resultLabel.Create(chart, m_name + "ResultLbl", subwin,
                            blockX, lblY, blockX + CALC_LABEL_WIDTH, lblY + CALC_EDIT_HEIGHT))
      return false;
   m_resultLabel.Text("Result:");

   int fldY = lblY + CALC_EDIT_HEIGHT + RESULT_VERTICAL_GAP;
   if(!m_resultField.Create(chart, m_name + "ResultFld", subwin,
                            blockX, fldY, blockX + CALC_EDIT_WIDTH, fldY + CALC_EDIT_HEIGHT))
      return false;
   m_resultField.ReadOnly(true);

   // 5) Populate dynamic defaults and input rows
   SetDynamicDefaults();
   string initialTerm = m_dropdown.Select();
   CreateInputFields(initialTerm);
   UpdateResult(initialTerm);

   return true;
}

Kennzeichnung von Ergebnis und Feld

Rechts von der Schaltfläche wird ein separater Block mit der Bezeichnung „Result:“ erstellt, unter dem unmittelbar ein schreibgeschütztes Feld (m_resultField) folgt. Diese Bearbeitung zeigt das numerische Ergebnis der durchgeführten Berechnung an.

Dynamische Zeilen

SetDynamicDefaults() aktualisiert den Standardkontostand. Dann wird der aktuell ausgewählte Begriff (m_dropdown.Select()) abgerufen, und CreateInputFields(term) wird aufgerufen, um die entsprechenden Kennzeichnung-Eingabe-Paare zu erzeugen. Schließlich füllt UpdateResult(term) das Ergebnisfeld mit der ursprünglichen Berechnung.

Da die Dropdown-Liste, die Schaltfläche zum Berechnen und der Ergebnisbereich zuerst angelegt wurden, erscheinen die nachfolgenden dynamischen Zeilen dazwischen, und zwar auf der Grundlage einheitlicher Offsets. Wenn ein Erstellungsaufruf fehlschlägt, gibt Create false zurück, sodass der aufrufende Code weiß, dass die Initialisierung des Rechners nicht abgeschlossen wurde.

Hinzufügen von Steuerelementen zum übergeordneten Dialog (AddToDialog)

Nach erfolgreicher Erstellung aller Steuerelemente in Create(...), ruft der übergeordnete EA oder das Panel AddToDialog auf. Diese Methode fügt jedes statische Steuerelement - m_calcOptionLabel, m_dropdown, m_calculateButton, m_resultLabel und m_resultField - in die interne Kontrollliste des Dialogs ein. Anschließend wird das dynamische Array m_inputs[] (das jedes Kennzeichnung+Edit-Paar enthält) in einer Schleife durchlaufen und ebenfalls hinzugefügt. Wenn ein Aufruf von Add(...) fehlschlägt, gibt die Methode false zurück, sodass der Aufrufer weiß, dass der Rechner nicht vollständig integriert wurde.

bool AddToDialog(CAppDialog &dlg)
{
   if(!dlg.Add(&m_calcOptionLabel)) return false;
   if(!dlg.Add(&m_dropdown))        return false;
   if(!dlg.Add(&m_calculateButton)) return false;
   if(!dlg.Add(&m_resultLabel))     return false;
   if(!dlg.Add(&m_resultField))     return false;

   for(int i = 0; i < ArraySize(m_inputs); i++)
      if(!dlg.Add(m_inputs[i])) return false;

   return true;
}

Aktualisieren der Ergebnisanzeige (UpdateResult):

void UpdateResult(const string term)
{
   double res = 0.0;
   string txt = "Result: ";

   if(term == "Position Size")
   {
      double bal = GetInputValue("accountBalance");
      double pct = GetInputValue("riskPercent");
      double sl  = GetInputValue("stopLossPips");
      string sym = GetInputString("symbol");
      if(bal > 0 && pct > 0 && sl > 0 && SymbolSelect(sym, true))
      {
         res = CalculatePositionSize(bal, pct, sl, sym);
         txt += "Position Size (lots)";
      }
      else txt += "Invalid Input";
   }
   else if(term == "Risk Amount")
   {
      double ps  = GetInputValue("positionSize");
      double slp = GetInputValue("stopLossPips");
      string sym = GetInputString("symbol");
      if(ps > 0 && slp > 0 && SymbolSelect(sym, true))
      {
         res = CalculateRiskAmount(ps, slp, sym);
         txt += "Risk Amount (" + AccountInfoString(ACCOUNT_CURRENCY) + ")";
      }
      else txt += "Invalid Input";
   }
   else if(term == "Pip Value")
   {
      double ls  = GetInputValue("lotSize");
      string sym = GetInputString("symbol");
      string cur = GetInputString("accountCurrency");
      if(ls > 0 && SymbolSelect(sym, true))
      {
         res = CalculatePipValue(sym, ls, cur);
         txt += "Pip Value (" + cur + ")";
      }
      else txt += "Invalid Input";
   }
   else if(term == "Profit/Loss")
   {
      double e   = GetInputValue("entryPrice");
      double x   = GetInputValue("exitPrice");
      double ls  = GetInputValue("lotSize");
      string sym = GetInputString("symbol");
      if(e > 0 && x > 0 && ls > 0 && SymbolSelect(sym, true))
      {
         res = CalculateProfitLoss(e, x, ls, sym);
         txt += "Profit/Loss (" + AccountInfoString(ACCOUNT_CURRENCY) + ")";
      }
      else txt += "Invalid Input";
   }
   else if(term == "Risk-to-Reward")
   {
      double tp  = GetInputValue("takeProfitPips");
      double slp = GetInputValue("stopLossPips");
      if(tp > 0 && slp > 0)
      {
         res = CalculateRiskRewardRatio(tp, slp);
         txt += "Risk-to-Reward Ratio";
      }
      else txt += "Invalid Input";
   }

   m_resultField.Text(StringFormat("%.2f", res));
   m_resultLabel.Text(txt);
}

UpdateResult liest den aktuell ausgewählten Berechnungsterm (term) und sammelt mit der entsprechenden Kombination von GetInputValue und GetInputString alle erforderlichen Eingaben. Zum Beispiel:

  • Positionsgröße: Abrufen von „accountBalance“, „riskPercent“, „stopLossPips“ und „symbol“. Wenn gültig, rufen wir CalculatePositionSize(...) auf und fügen „Position Size (lots)“ an die Kennzeichnung an.
  • Risikobetrag: Abrufen von „positionSize“, „stopLossPips“ und „symbol“. Wenn gültig, rufen wir CalculateRiskAmount(...) auf und fügen „Risk Amount (USD)“ hinzu.
  • Pip Value: Abruf von „lotSize“, „symbol“ und „accountCurrency“. Dann CalculatePipValue(...) und wir fügen „Pip Value (USD)“ hinzu.
  • Gewinn/Verlust: Abrufen von „entryPrice“, „exitPrice“, „lotSize“ und „symbol“. Dann CalculateProfitLoss(...) und „Gewinn/Verlust (USD)“ hinzufügen.
  • Risk-to-Reward: Abrufen von „takeProfitPips“ und „stopLossPips“. Dann CalculateRiskRewardRatio(...) und wir fügen „Risk-to-Reward Ratio“ hinzu.

Wenn eine Eingabe ungültig ist oder das Symbol nicht ausgewählt werden kann, setzt die Methode txt = „Result: Invalid Input“. In allen Fällen wird m_resultField.Text mit dem auf zwei Dezimalstellen formatierten numerischen Wert aktualisiert und m_resultLabel.Text(txt) aufgerufen, um den beschreibenden Text darüber anzupassen. Diese Methode garantiert, dass beim Klicken auf „Calculate“ oder beim Ändern der Dropdown-Liste sowohl die Beschriftung als auch das numerische Feld immer mit der neuesten Berechnung oder einer Fehlermeldung aktualisiert werden.

Lesen von Nutzereingaben (GetInputValue und GetInputString)

double GetInputValue(const string name)
{
   for(int i = 0; i < ArraySize(m_inputs); i++)
      if(m_inputs[i].Name() == m_name + "Inp_" + name)
         return StringToDouble(((CEdit*)m_inputs[i]).Text());
   return 0.0;
}

string GetInputString(const string &name)
{
   for(int i = 0; i < ArraySize(m_inputs); i++)
      if(m_inputs[i].Name() == m_name + "Inp_" + name)
         return ((CEdit*)m_inputs[i]).Text();
   return "";
}

Diese Hilfsmethoden abstrahieren von der Suche nach dem richtigen Eingabefeld innerhalb des dynamischen Arrays m_inputs[]. Bei einem Feldnamen wie „stopLossPips“ durchläuft GetInputValue alle m_inputs[i], prüft, ob der Name() mit m_name + „Inp_stopLossPips“ übereinstimmt, und gibt dann den numerischen Wert des Textes() zurück. In ähnlicher Weise gibt GetInputString den Rohtext (z. B. „EURUSD“) zurück, wenn Namen wie „symbol“ oder „accountCurrency“ angegeben werden. Wird keine Übereinstimmung gefunden, wird 0,0 bzw. eine leere Zeichenkette zurückgegeben, was auf eine fehlende Eingabe hinweist.

Weiterleitung von Nutzeraktionen (OnEvent)

bool OnEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
   if(id == CHARTEVENT_OBJECT_CHANGE && sparam == m_name + "Dropdown")
   {
      long idx = m_dropdown.Value();
      string term = GetSelectedTerm();
      CreateInputFields(term);
      UpdateResult(term);
      return true;
   }
   if(id == CHARTEVENT_OBJECT_CLICK && sparam == m_name + "CalcBtn")
   {
      string term = GetSelectedTerm();
      UpdateResult(term);
      return true;
   }
   return false;
}

Der Rechner verarbeitet zwei Ereignistypen:

1. Dropdown-Änderungen (CHARTEVENT_OBJECT_CHANGE)

  • Wenn sparam mit dem Namen des Dropdown-Steuerelements übereinstimmt, wird der neue Begriff über m_dropdown.Select() abgerufen.
  • Wir rufen CreateInputFields(term) auf, um alle dynamischen Label+Edit-Paare für diesen Begriff neu zu erstellen.
  • Um eine unmittelbare Vorschau zu zeigen, berechnet UpdateResult(term) die Ergebnisse anhand von Standardwerten oder vorhandenen Eingaben neu.
  • Die Rückgabe von true teilt dem übergeordneten Dialogfeld mit, dass das Ereignis konsumiert wurde.

2. Die Klicks auf die Schaltfläche „Calculate“ (CHARTEVENT_OBJECT_CLICK)

  • Wenn sparam mit m_name + „CalcBtn“ übereinstimmt, lesen wir den ausgewählten Begriff erneut und rufen UpdateResult(term) auf.
  • Dadurch kann der Nutzer alle Eingabewerte ändern (z. B. Stop-Loss-Pips anpassen) und dann auf „Calculate“ drücken, um die Ergebnisse zu aktualisieren.

Alle anderen Ereignisse geben false zurück, sodass der übergeordnete CAppDialog (oder anderer Code) sie bei Bedarf behandeln kann. Diese klare Trennung stellt sicher, dass nur relevante Interaktionen - Änderungen oder Schaltflächenklicks - Neuberechnungen oder Aktualisierungen der Nutzeroberfläche auslösen.

Aufräumen (~CForexCalculator)

~CForexCalculator()
{
   for(int i = 0; i < ArraySize(m_inputs); i++)
      delete m_inputs[i];
}

Wenn das Rechnerobjekt zerstört wird, durchläuft der Destruktor eine Schleife über m_inputs[] und löscht jedes dynamisch zugewiesene Steuerelement (Beschriftungen und Bearbeitungen). Dies verhindert Speicherlecks. Da CreateInputFields jedes Mal, wenn der Nutzer den Begriff wechselt, ArrayFree verwendet, um die alten Steuerelemente zu entfernen, müssen diese alten Steuerelemente später gelöscht werden. Die abschließende Bereinigung des Destruktors stellt sicher, dass alle von dieser Klasse erstellten Steuerelemente ordnungsgemäß freigegeben werden, wenn das gesamte Rechner-Panel geschlossen oder der EA heruntergefahren wird.


(3) Integration des Forex-Werte-Rechners in das Handelsmanagement-Panel

Die Integration des CForexCalculator in das CTradeManagementPanel beginnt einfach mit der Deklaration einer Instanz der Calculator-Klasse als eines der Mitgliedsfelder des Panels. Indem wir m_calculator zu den geschützten Mitgliedern zählen, reservieren wir effektiv einen Teil des Speichers des Panels für den internen Zustand des Rechners (Dropdown, Beschriftungen, Bearbeitungen und Schaltflächen).

CForexCalculator m_calculator;

Da der Kopf des Panels bereits den ForexValuesCalculator-Header enthält, weiß der Compiler genau, wie die Klasse CForexCalculator aufgebaut ist und welche Abhängigkeiten sie hat. In der Praxis bedeutet dies, dass wir die Steuerelemente des Rechners nicht kopieren und ihren Code nicht neu anordnen, sondern uns auf die Komposition verlassen. Das Panel kann m_calculator wie jedes andere Steuerelement behandeln, d.h. es erstellen, in der Größe anpassen, dem Dialogfeld hinzufügen und Ereignisse an es weiterleiten, ohne in seine privaten Mitglieder Einblick zu nehmen.

#include <ForexValuesCalculator.mqh>

Erstellung und Layout in der Methode Create()

Der nächste Schritt findet in der Methode Create() des Panels statt, wo wir alle vier Abschnitte nacheinander erstellen. Nach dem Anlegen von „Quick Order Execution“ und dem Zeichnen des ersten Trennzeichens gehen wir zum Abschnitt „Forex Calculator“ über, indem wir zunächst eine Abschnittskopfzeile zeichnen:

if(!CreateLabelEx(m_secCalcLabel, curX, curY, DEFAULT_LABEL_HEIGHT, 

                  "SecCalc", "Forex Values Calculator:", clrNavy))

   return(false)

m_secCalcLabel.Font("Arial Bold");

m_secCalcLabel.FontSize(10);

curY += DEFAULT_LABEL_HEIGHT + GAP;

Unmittelbar danach rufen wir die eigene Create-Methode des Rechners auf, wobei wir das aktuelle Chart, ein eindeutiges Präfix (z.B. Name + „_ForexCalc“), den Index des Unterfensters und die genaue (x, y) Position zusammen mit CALCULATOR_WIDTH und CALCULATOR_HEIGHT übergeben:

string calcName = name + "_ForexCalc";

if(!m_calculator.Create(chart, calcName, subwin, 

                        curX, curY, CALCULATOR_WIDTH, CALCULATOR_HEIGHT))

   return(false);

if(!m_calculator.AddToDialog(this))

   return(false);

curY += CALCULATOR_HEIGHT + GAP * 2;

Intern verwendet CForexCalculator::Create dasselbe Koordinatensystem, um sein Dropdown-Menü ganz oben links im Rechner-Block zu platzieren und seinen eigenen dynamischen Platz für Eingabefelder weiter unten zu reservieren. Da wir eine feste Höhe angeben, weiß die Rechnerklasse genau, wo sie die Ergebnisbeschriftung und das Ergebnisfeld am unteren Rand positionieren muss. Sobald m_calculator.Create() den Wert true zurückgibt, rufen wir sofort m_calculator.AddToDialog(this) auf, das jedes Untersteuerelement (m_dropdown, alle dynamisch erstellten CLabel/CEdit-Paare, die Schaltfläche „Berechnen“ und die Ergebnisanzeige) durchläuft und sie dem übergeordneten CAppDialog hinzufügt. Dieser Registrierungsschritt ist von entscheidender Bedeutung: Die interne Ereignisschleife des Dialogs wird nun die Steuerelemente des Rechners einbeziehen und sie in der richtigen Z-Reihenfolge darstellen.

Größenbestimmung, Positionierung und Abstände

Die Einhaltung der richtigen Abstände zwischen den Abschnitten ist der Schlüssel zur Vermeidung von visuellen Überschneidungen. Nach dem Hinzufügen des Rechners wird unser curY um die volle CALCULATOR_HEIGHT plus die doppelte Abschnittslücke vorgeschoben. Auf diese Weise beginnt das nächste Trennzeichen oder der nachfolgende Abschnitt „Pending Orders“ genau unter dem Rechnerblock, sodass keine Unklarheiten über die Kontrollgrenzen bestehen. Während dieses Layoutprozesses wird kein Steuerelement manuell relativ zu einem anderen neu positioniert; stattdessen wird durch die Abfolge des Zeichnens einer Kopfbereiches, des Erstellens des Rechners an einem bekannten Ursprung und des anschließenden Verschiebens des vertikalen Cursors gewährleistet, dass der Bereich „Calculator“ in sich geschlossen bleibt.

Da wir eindeutige Konstanten - CALCULATOR_WIDTH und CALCULATOR_HEIGHT - definiert haben, muss das Panel nicht wissen, wie viele Eingabezeilen der Rechner anzeigen wird. Die interne Logik des Rechners passt die Größe von m_inputs[] dynamisch an, verändert aber nie den gesamten reservierten Block. Wenn wir also in Zukunft weitere Eingabezeilen hinzufügen (z. B. ein Feld „Swap Rate“), schiebt der Rechner einfach sein eigenes Ergebnisfeld innerhalb dieser festen Höhe nach unten; das Panel kann von diesen Details unbeeindruckt bleiben.

Ereignisweiterleitung und Prioritätensetzung

Genauso wichtig ist das Ereignismanagement. Wenn ein Nutzer mit einem der Steuerelemente des Rechners interagiert, z. B. einen neuen Begriff aus der Dropdown-Liste auswählt oder auf die Schaltfläche „Calculate“ klickt, werden diese objektbasierten Ereignisse in CTradeManagementPanel::OnEvent(...) angezeigt. Ganz am Anfang von OnEvent leiten wir jedes Ereignis an weiter:

if(m_calculator.OnEvent(id, lparam, dparam, sparam))

{
   Print("Calculator handled event: ", sparam);

   return(true);
}

Wenn der Rechner das Ereignis erkennt (d. h. sparam passt zu einem seiner untergeordneten Steuerelementnamen wie „MyPanel_ForexCalcDropdown“ oder „MyPanel_ForexCalcCalcBtn“), gibt er nach der Verarbeitung „true“ zurück, und wir werden sofort beendet. Dieser Early-Return-Mechanismus stellt sicher, dass die Logik des Rechners für den Neuaufbau von Eingabefeldern oder die Aktualisierung der Ergebnisbeschriftung immer Vorrang hat.

Nur wenn m_calculator.OnEvent(...) den Wert false zurückgibt, werden andere panel-spezifische Ereignisse verarbeitet, z. B. Schaltflächenklicks in den Abschnitten „Quick Order“ oder „Pending Orders“. Auf diese Weise besitzt der Rechner sein eigenes Unterdialogfeld: Er kann seine dynamischen Steuerelemente hinzufügen und entfernen, auf Nutzereingaben reagieren und seine Anzeige aktualisieren, ohne die anderen Steuerelemente des Panels zu stören oder von ihnen unterbrochen zu werden.


(4) Anpassungen an das NewAdminPanel EA, um auf die neuen Aktualisierungen zu reagieren

Der Aufruf von g_tradePanel.Run() in der EA-Initialisierungs- oder Panel-Anzeigeroutine ist für das ordnungsgemäße Funktionieren aller interaktiven GUI-Elemente - insbesondere von ComboBoxen und Datepickern - absolut entscheidend. Unter der Haube übergibt Run() die Kontrolle an die Ereignisverarbeitungsschleife der Basisklasse CAppDialog, die aktiv auf Mausklicks, Tastenanschläge und andere Chartereignisse für die untergeordneten Steuerelemente des Dialogs wartet. Ohne den Aufruf von Run() existiert die CTradeManagementPanel-Instanz einfach im Speicher, wird aber nicht bei der MQL5-Laufzeit als aktiver Dialog registriert. In der Praxis bedeutet dies, dass die Auswahl einer Position aus der Combobox „Pending Order Type“ oder die Änderung des Ablaufdatums über CDatePicker nicht die erforderlichen CHARTEVENT_OBJECT_CHANGE- oder CHARTEVENT_OBJECT_ENDEDIT-Ereignisse erzeugen würde, die das Panel verarbeiten müsste.

Sobald g_tradePanel.Run() aufgerufen wird. Der Dialog tritt jedoch in seine eigene Meldungsschleife ein: Jeder Klick auf das Dropdown oder den Datepicker löst die Methode OnEvent(...) des Panels aus, die OnChangePendingOrderType() oder OnChangePendingDatePicker() überprüft und weiterleitet. Kurz gesagt, Run() verwandelt einen statischen Satz von Steuerelementen in eine reaktionsfähige, interaktive Nutzeroberfläche. Ohne sie würde die ComboBox auf ihrem Anfangswert verharren, und der Datepicker würde nie ein Ereignis auslösen, um die Logik des Preises für die ausstehende Bestellung oder die Kalenderanzeige zu aktualisieren.

void HandleTradeManagement()
{
    if(g_tradePanel)
    {
        if(g_tradePanel.IsVisible())
            g_tradePanel.Hide();
        else
            g_tradePanel.Show();
        ChartRedraw();
        return;
    }
    g_tradePanel = new CTradeManagementPanel();
    if(!g_tradePanel.Create(g_chart_id, "TradeManagementPanel", g_subwin, 310, 20, 875, 700))
    {
        delete g_tradePanel;
        g_tradePanel = NULL;
        return;
    }
    // ← This line activates the dialog’s own message loop
    g_tradePanel.Run();
    g_tradePanel.Show();

    ChartRedraw();
}

Die Verwendung von ChartRedraw()

Ebenso wichtig für die Nutzerfreundlichkeit ist die sinnvolle Verwendung von ChartRedraw() unmittelbar nach dem Ein- oder Ausblenden von Dialogen und nach der Aktualisierung visueller Elemente. Jedes Mal, wenn Sie Show() oder Hide() für ein Dialogfeld oder ein einzelnes Steuerelement wie die ComboBox, den DatePicker oder die Rechnerfelder aufrufen, muss die dem Chart zugrunde liegende Leinwand neu gezeichnet werden, damit neue Steuerelemente auf dem Bildschirm erscheinen (oder alte Steuerelemente verschwinden). In unserem EA-Code sehen Sie häufige Aufrufe von ChartRedraw() in Handlern wie HandleTradeManagement(), ToggleInterface() und innerhalb von OnEvent(...), sobald ein Ereignis verarbeitet wurde.

Jede ChartRedraw() zwingt MetaTrader 5, alle Chart-Objekte und GUI-Steuerelemente neu zu rendern, um sicherzustellen, dass Dropdown-Listen tatsächlich erweitert werden, dass DatePicker-Kalender korrekt überlagert werden und dass neu berechnete Werte in den Rechnerfeldern ohne Flackern oder Verzögerung sichtbar werden. Ohne den Aufruf von ChartRedraw() kann das Chart nach Zustandsänderungen für einen spürbaren Bruchteil einer Sekunde „abgestanden“ bleiben, was zu einem nicht reagierenden Verhalten führt: Der Nutzer könnte auf ein anderes Dropdown-Element klicken, aber bis zum nächsten Tick oder zur automatischen Aktualisierung immer noch die alte Auswahl sehen. Durch die explizite Aufforderung zum Neuzeichnen nach jeder signifikanten Änderung - sei es das Umschalten der Sichtbarkeit von Bedienfeldern, das Aktualisieren von Beschriftungen oder das Neuberechnen von Ergebnissen - garantieren wir eine durchgängig reibungslose Echtzeit-Nutzeroberfläche, bei der ComboBox-Auswahlen sofort erscheinen, DatePicker-Kalender ohne Verzögerung angezeigt werden und Rechnerausgaben sofort aktualisiert werden.

// Toggling the main interface buttons
void ToggleInterface()
{
    bool state = ObjectGetInteger(0, toggleButtonName, OBJPROP_STATE);
    ObjectSetInteger(0, toggleButtonName, OBJPROP_STATE, !state);
    UpdateButtonVisibility(!state);
    // Redraw immediately so button positions update on screen
    ChartRedraw();
}

// In the OnEvent handler, after forwarding to sub‐panels:
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
    if(id == CHARTEVENT_OBJECT_CLICK)
    {
        // ... handle panel toggles ...
        ChartRedraw();  // Ensure any Show()/Hide() calls are rendered

        // Forward to communication panel
        if(g_commPanel && g_commPanel.IsVisible())
            g_commPanel.OnEvent(id, lparam, dparam, sparam);
        ChartRedraw();  // Redraw after commPanel’s changes

        // Forward to trade panel
        if(g_tradePanel && g_tradePanel.IsVisible())
            g_tradePanel.OnEvent(id, lparam, dparam, sparam);
        ChartRedraw();  // Redraw after tradePanel’s updates (e.g., combobox or date change)

        // Forward to analytics panel
        if(g_analyticsPanel && g_analyticsPanel.IsVisible())
            g_analyticsPanel.OnEvent(id, lparam, dparam, sparam);
        ChartRedraw();  // Final redraw to reflect any analytics updates
    }
}

  • Neuzeichnen nach Sichtbarkeitsänderungen: In HandleTradeManagement() rufen wir ChartRedraw() unmittelbar nach Show() oder Hide() auf. Dadurch wird das Panel sofort ein- oder ausgeblendet, und es wird vermieden, dass das Panel versteckt oder sichtbar bleibt, bis eine externe Chartaktivität stattfindet.
  • Neu zeichnen nach Ereignis Delegation: Innerhalb von OnChartEvent(...) rufen wir, nachdem wir das Ereignis an g_tradePanel.OnEvent(...) weitergeleitet haben, erneut ChartRedraw() auf. Wenn der Nutzer mit dem Kombinationsfeld des Rechners interagiert hat, z. B. durch Auswahl von „Risikobetrag“, hat der Rechner seine Eingabefelder neu erstellt oder seine Ergebnisbeschriftung aktualisiert. Die anschließende Funktion ChartRedraw() stellt sicher, dass diese neuen Eingabefelder und Wertelabels sofort gerendert werden, um ein Flackern oder halb gezeichnete UI-Elemente zu vermeiden.
  • Reibungslose, sofortige Rückmeldung: Durch die Platzierung von ChartRedraw() an jedem Punkt - nach dem Umschalten von Schaltflächen auf der Oberfläche, nach dem Ein-/Ausblenden eines Panels und nach der Weiterleitung von Ereignissen an Unter-Panels - garantieren wir eine flüssige, reaktionsschnelle Nutzererfahrung. Die Dropdown-Listen der Comboboxen öffnen sich sofort, die Popup-Fenster der Datumsauswahl werden korrekt angezeigt, und die neu berechneten Werte im Feld „Forex-Rechner“ werden ohne spürbare Verzögerung sichtbar.

Nun können wir die neuen Funktionen im nächsten Abschnitt testen.


Tests

Folgendes wurde im MetaTrader 5 nach erfolgreicher Kompilierung ausgeführt. Das aktualisierte TradeManagementPanel enthält einen verbesserten Workflow für die Platzierung von ausstehenden Aufträgen sowie einen integrierten Forex-Werte-Rechner zur Berechnung wichtiger Forex-Kennzahlen und zur Unterstützung fundierter Handelsentscheidungen.

Testen des ForexValuesCalculator

Testen des in das TradeManagementPanel integrierten Forex-Werte-Rechners


Schlussfolgerung

Dies war eine unglaubliche, eingehende Diskussion, und ich freue mich, dass wir unser Hauptziel erreicht haben. Wir haben mehrere grundlegende Forex-Konzepte untersucht - die Berechnung von Positionsgröße, Pip-Wert, Risiko-Ertrags-Verhältnis usw. - und die zugrunde liegende Mathematik erläutert, die jeder Forex-Händler verstehen sollte. Die Umwandlung dieser Formeln in MQL5-Code stärkt die Theorie für Händler und hilft auch Entwicklern, diese Berechnungen in ihren eigenen Projekten korrekt umzusetzen.

Eine wichtige Erkenntnis aus unserer Arbeit am TradeManagementPanel war die Nutzung der Widgets aus der Standardbibliothek von MQL5 - insbesondere CComboBox und „CDatePicker“. Durch die Verwendung dieser Steuerelemente haben wir das Layout und die Zugänglichkeit der zugehörigen Eingaben verbessert und den Prozess der Festlegung eines Ablaufdatums für ausstehende Aufträge rationalisiert. Dies bedeutet eine erhebliche Zeitersparnis im Vergleich zur manuellen Eingabe von Daten und verringert die Gefahr von Nutzerfehlern.

Dabei haben wir uns auf ein modulares Design konzentriert: Wir haben den Rechner, die Steuerelemente für die ausstehende Bestellung und die Schaltflächen für die schnelle Ausführung in verschiedene Klassen aufgeteilt, die sauber zusammenarbeiten. Die Sicherstellung, dass unsere ComboBox- und DatePicker-Ereignisse innerhalb des EA korrekt reagieren, demonstriert ein robustes, wiederverwendbares Muster. Jede Komponente, die wir entwickelt haben, kann extrahiert und mit minimalem Aufwand in zukünftige Projekte integriert werden.

Auch wenn die Nutzeroberfläche jetzt solide ist, gibt es noch Raum für die Verfeinerung und Optimierung unserer Wertberechnungslogik. Ich freue mich über Ihr Feedback und Ihre Vorschläge in den Kommentaren - Ihre Ideen zur Verbesserung der bestehenden Konzepte werden von unschätzbarem Wert sein. Ich hoffe, Sie haben diese Übung als lehrreich empfunden, und ich freue mich schon auf unsere nächste Veröffentlichung. Bleiben Sie dran!


Im Folgenden finden Sie alle Dateien, die an diesem Projekt beteiligt sind:

Angehängte Datei Beschreibung
TradeManagementPanel.mqh Enthält die Hauptlogik der Handelsschnittstelle, einschließlich der Verwaltung von Marktaufträgen und schwebenden Aufträgen, Risikoberechnungen und einen integrierten Forex-Rechner. Es bietet GUI-Steuerelemente wie Dropdowns, Datepicker und Aktionsschaltflächen, die alle in einem von CAppDialog abgeleiteten Panel gekapselt sind. Sie spielt eine entscheidende Rolle bei der Abwicklung von Handelsgeschäften und interaktiven Nutzereingaben.
ForexValuesCalculator.mqh Implementiert die Hauptberechnungs-Engine, die im Handelsverwaltungspanel verwendet wird, um Handelsparameter wie Pip-Wert, Marge, Positionsgröße und Risiko-Ertrags-Verhältnis zu berechnen. 
New_Admin_Panel.mq5 Der Haupteinstiegspunkt für Expert Advisor, der alle einzelnen Module - Handelsmanagement, Kommunikation, Analyse - in einer einheitlichen grafischen Oberfläche zusammenfasst. Sie steuert die Instanziierung von Panels, die Ereignisweiterleitung, die Erstellung von Chartobjekten und die allgemeine Layoutsteuerung. Außerdem sorgt es für eine reibungslose Reaktionsfähigkeit durch häufige ChartRedraw()-Aufrufe und aktiviert die Panel-Funktionalität mit .Run().
Images.zip Eine Sammlung von Bitmap-Ressourcen, die für Schnittstellenschaltflächen und visuelle Elemente verwendet werden. Enthält Dateien wie TradeManagementPanelButton.bmp, expand.bmp, collapse.bmp und andere, die interaktives Feedback durch Schaltflächenzustände (normal/komprimiert) liefern. Diese Elemente sind für die visuelle Identität und die Nutzerfreundlichkeit der Anwendung unerlässlich.
Communications.mqh Definiert das Kommunikations-Panel, mit dem Nutzer Nachrichten über einen Telegram-Bot senden und empfangen können. Es umfasst GUI-Komponenten für die Eingabe von Anmeldeinformationen (Chat-ID, Bot-Token) und ein Eingabefeld für Nachrichten. Dieses Panel unterstützt auch zukünftige Kontaktverwaltungsfunktionen und ist mit den Steuerelementen CChartCanvas, CBmpButton und CEdit aufgebaut.
AnalyticsPanel.mqh Bietet eine chartbasierte analytische Zusammenfassung, einschließlich Signalauswertung, oder Leistungsverfolgung. Das Panel wird in den Haupt-EA integriert und über g_analyticsPanel angezeigt. Seine Struktur folgt demselben modularen CAppDialog-Ansatz, der eine isolierte Logik und erweiterbare Funktionen ermöglicht.
Telegram.mqh Erledigt das Low-Level-Networking und die JSON-Formatierung, die für die Kommunikation mit der Telegram Bot API erforderlich sind. Es enthält Funktionen zum Senden von Textnachrichten. Dieses Modul dient als Backend-Engine für das Kommunikationspanel.
Authentication.mqh Implementiert eine optionale Zwei-Faktor-Authentifizierung für das Admin-Panel, wobei Telegram als Verifizierungskanal verwendet wird. Es sendet Anmeldebestätigungen an die angegebene Chat-ID und überprüft die Eingabe des Nutzerpassworts. Dieses Modul wird in der Regel während der EA-Initialisierung aufgerufen, um die Nutzerauthentifizierung zu erzwingen und unbefugten Zugriff zu verhindern. Sie ist derzeit deaktiviert, um wiederholte Aufforderungen während häufiger Test- und Entwicklungszyklen zu vermeiden.

Bitte speichern Sie alle Header-Dateien im Verzeichnis MQL5\include und extrahieren Sie den Inhalt von Images.zip in den Ordner MQL5\Images. Kompilieren Sie dann New_Admin_Panel.mq5, um es im MetaTrader 5 Terminal auszuführen.


Zurück zum Inhalt

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

Beigefügte Dateien |
New_Admin_Panel.mq5 (17.98 KB)
Images.zip (6.17 KB)
Communications.mqh (30.32 KB)
AnalyticsPanel.mqh (32.77 KB)
Telegram.mqh (1.85 KB)
Authentication.mqh (8.92 KB)
Letzte Kommentare | Zur Diskussion im Händlerforum (10)
Clemence Benjamin
Clemence Benjamin | 21 Juni 2025 in 08:58
CapeCoddah String-Format einfacher.


Cheerio, iss deine Cornflakes und viel Spaß beim Programmieren.


Kap CoddaH

Vielen Dank, @CapeCoddah, für all Ihr Feedback und die Mühe, die Sie investiert haben - es trägt wirklich zu einer stabileren Version dieses Multipanel-Handelstools bei.

Ich weiß es wirklich zu schätzen, dass Sie sich die Zeit nehmen, die Dinge zu erforschen und herauszufinden.

Ich bin gerade dabei, die von Ihnen aufgezeigten Probleme zu überprüfen und werde auch die von Ihnen eingereichten Änderungen prüfen. Verbesserungen sind auf dem Weg.

Mit freundlichen Grüßen,

Clemence Benjamin

Clemence Benjamin
Clemence Benjamin | 21 Juni 2025 in 09:04
Oluwafemi Olabisi #:

Hallo,

Ich habe versucht, es zu installieren, aber es wurde keine Schaltfläche angezeigt, ich kann nur zwei Kontrollkästchen sehen. Ich habe die Dateien in den Ordner "Include" extrahiert, wie erwähnt, und die Bilder wurden in den Ordner "images" extrahiert

Hallo @Oluwafemi Olabisi,

Könnten Sie bitte einen Screenshot zur Verfügung stellen, damit ich Ihnen besser helfen kann?

Oluwafemi Olabisi
Oluwafemi Olabisi | 22 Juni 2025 in 18:48
Clemence Benjamin #:

Hallo @Oluwafemi Olabisi,

Könnten Sie mir bitte einen Screenshot schicken, damit ich Ihnen besser helfen kann?

Ich habe hier beigefügt, wie die Dateien in die Verzeichnisse INCLUDE und IMAGES extrahiert wurden.
CapeCoddah
CapeCoddah | 23 Juni 2025 in 12:57

Hallo Clemence,

ich habe ein paar Fragen und vielleicht können Sie einige davon lösen.

Erstens: Der Strategietester

Wenn ich meinen EA darin ausführe, werden keine Texte, Schaltflächen usw. auf der Testmaschine angezeigt. Ich habe bemerkt, dass einige Ihrer Schaltflächen angezeigt werden. Haben Sie eine Idee, was diesen Unterschied verursacht? Ich plane, Ihren EA in meinen einzubauen und zu versuchen, die Ursache für die Unterschiede zu ermitteln.

Zweitens: Wie können Sie MetaQuotes kontaktieren, um Fehler und Verbesserungsvorschläge zu übermitteln? Ich habe viel Zeit in MQL5.com verbracht und kann keinen Weg finden.

CapeCoddah
CapeCoddah | 23 Juni 2025 in 12:59
Oluwafemi Olabisi #:
Ich habe hier angehängt, wie die Dateien in die Verzeichnisse INCLUDE bzw. IMAGES extrahiert wurden.

Der EA sollte sich im Experten-Ordner und nicht im Include-Ordner befinden. Nachdem Sie ihn verschoben haben, müssen Sie den EA stoppen und neu starten, damit er im Navigator-Fenster angezeigt wird. Das ist eines der Dinge, die MQ ändern sollte. Zumindest sollten die Benutzer die Möglichkeit haben, den Ordner zu komprimieren, entweder Indicators oder EXperts, und dann die Liste während des Expand-Befehls zu aktualisieren, anstatt Terminal zu stoppen und neu zu starten und dann alle Unterverzeichnisse zu öffnen, bis Sie Ihr Ziel erreichen. Besser noch, sie sollten es automatisch tun , wenn eine neue ausführbare Datei in das Unterverzeichnis gelegt wird.

Umstellung auf MQL5 Algo Forge (Teil 3): Verwendung externer Repositories für die eigenen Projekte Umstellung auf MQL5 Algo Forge (Teil 3): Verwendung externer Repositories für die eigenen Projekte
Lassen Sie uns untersuchen, wie Sie externen Code aus einem beliebigen Repository im MQL5 Algo Forge Speicher in Ihr eigenes Projekt integrieren können. In diesem Artikel wenden wir uns endlich dieser vielversprechenden, aber auch komplexeren Aufgabe zu: wie man Bibliotheken aus Drittanbieter-Repositories innerhalb von MQL5 Algo Forge praktisch verbindet und verwendet.
Datenwissenschaft und ML (Teil 44): Forex OHLC Zeitreihenprognose mit Vektor-Autoregression (VAR) Datenwissenschaft und ML (Teil 44): Forex OHLC Zeitreihenprognose mit Vektor-Autoregression (VAR)
Entdecken Sie, wie Vektor-Autoregressions-Modelle (VAR) Forex OHLC (Open, High, Low und Close) Zeitreihendaten prognostizieren können. Dieser Artikel befasst sich mit der VAR-Implementierung, dem Modelltraining und der Echtzeitprognose in MetaTrader 5 und hilft Händlern, voneinander abhängige Währungsbewegungen zu analysieren und ihre Handelsstrategien zu verbessern.
Vom Neuling zum Experten: Animierte Nachrichtenüberschrift mit MQL5 (I) Vom Neuling zum Experten: Animierte Nachrichtenüberschrift mit MQL5 (I)
Die Zugänglichkeit von Nachrichten ist ein entscheidender Faktor beim Handel mit dem MetaTrader 5-Terminal. Obwohl zahlreiche Nachrichten-APIs verfügbar sind, stehen viele Händler vor der Herausforderung, auf diese zuzugreifen und sie effektiv in ihre Handelsumgebung zu integrieren. In dieser Diskussion wollen wir eine schlanke Lösung entwickeln, die Nachrichten direkt auf die Chart bringt – dort, wo sie am meisten gebraucht werden. Zu diesem Zweck wird ein Expert Advisor für News Headline erstellt, der Echtzeit-Nachrichten-Updates aus API-Quellen überwacht und anzeigt.
MQL5-Assistenz-Techniken, die Sie kennen sollten (Teil 70):  Verwendung der Muster von SAR und RVI mit einem Exponential-Kernel-Netzwerk MQL5-Assistenz-Techniken, die Sie kennen sollten (Teil 70): Verwendung der Muster von SAR und RVI mit einem Exponential-Kernel-Netzwerk
Wir knüpfen an unseren letzten Artikel an, in dem wir das Indikatorpaar SAR und RVI vorstellten, und überlegen, wie dieses Indikatorpaar durch maschinelles Lernen erweitert werden kann. SAR und RVI sind eine komplementäre Paarung von Trend und Momentum. Unser Ansatz des maschinellen Lernens verwendet ein neuronales Faltungsnetzwerk, das bei der Feinabstimmung der Prognosen dieses Indikatorpaares den Exponential-Kernel bei der Dimensionierung seiner Kerne und Kanäle einsetzt. Wie immer wird dies in einer nutzerdefinierten Signalklassendatei durchgeführt, die mit dem MQL5-Assistenten arbeitet, um einen Expert Advisor zusammenzustellen.