English Русский 中文 Español 日本語 Português
preview
Verständnis von Programmierparadigmen (Teil 1): Ein verfahrenstechnischer Ansatz für die Entwicklung eines Price Action Expert Advisors

Verständnis von Programmierparadigmen (Teil 1): Ein verfahrenstechnischer Ansatz für die Entwicklung eines Price Action Expert Advisors

MetaTrader 5Beispiele | 16 Februar 2024, 16:04
317 0
Kelvin Muturi Muigua
Kelvin Muturi Muigua

Einführung

In der Welt der Softwareentwicklung sind Programmierparadigmen die Leitbilder für das Schreiben und Organisieren von Code. Ähnlich wie bei der Wahl verschiedener Routen, um ein Ziel zu erreichen, gibt es verschiedene Programmieransätze oder Paradigmen für die Bewältigung von Aufgaben mit MQL5.

In zwei Artikeln werden wir die grundlegenden Programmierparadigmen erforschen, die für die Erstellung von Handelswerkzeugen mit MQL5 erforderlich sind. Mein Ziel ist es, effektive und bewährte Methoden zu vermitteln, die mit kurzem und effizientem Code großartige Ergebnisse erzielen. Ich erkläre jeden Programmierstil und demonstriere ihn durch die Erstellung eines voll funktionsfähigen Expert Advisors.

Arten von Programmierparadigmen

Es gibt drei wichtige Programmierparadigmen, die jeder MQL5-Entwickler oder Programmierer kennen sollte:

  1. Prozedurale Programmierung: Dieser Artikel wird sich mit diesem Paradigma befassen.
  2. Funktionale Programmierung: Dieses Paradigma wird auch in diesem Artikel behandelt, da es der prozeduralen Programmierung sehr ähnlich ist.
  3. Objektorientierte Programmierung (OOP): Dieses Paradigma wird im nächsten Artikel erörtert.
Jedes Paradigma hat seine eigenen, einzigartigen Regeln und Eigenschaften, die darauf ausgelegt sind, spezifische Probleme zu lösen und die effektive Entwicklung verschiedener Handelswerkzeuge mit MQL5 zu gestalten.


Verstehen der prozeduralen Programmierung

Die prozedurale Programmierung ist ein systematischer, schrittweiser Ansatz zum Schreiben von Code. Dabei wird jedes Problem in eine Abfolge von präzisen Anweisungen zerlegt, ähnlich wie bei einem Rezept. Programmierer geben dem Computer einen klaren Weg vor und leiten ihn Zeile für Zeile an, um das gewünschte Ergebnis zu erzielen.

Egal, ob Sie neu in der Programmierung sind oder einfach nur neugierig auf die Organisation von Code sind, die prozedurale Programmierung bietet einen einfachen und intuitiven Einstieg in die Welt der Programmierung.

Haupteigenschaften der prozeduralen Programmierung


Hier sind die wichtigsten Eigenschaften, die die prozedurale Programmierung kennzeichnen:

  1. Funktionen:
    Das Herzstück der prozeduralen Programmierung sind Funktionen. Dabei handelt es sich um Sätze von Anweisungen, die zur Ausführung einer bestimmten Aufgabe zusammengefasst werden. Funktionen kapseln die Funktionalität und fördern die Modularität und die Wiederverwendung von Code.
  2. Top-Down-Design:
    Bei der prozeduralen Programmierung wird häufig ein Top-Down-Design-Ansatz verwendet. Die Entwickler zerlegen ein Problem in kleinere, besser zu bewältigende Teilaufgaben. Jede Teilaufgabe wird einzeln gelöst und trägt zur Gesamtlösung bei.
  3. Imperativer Stil:
    Der imperative oder befehlsartige Charakter der prozeduralen Programmierung betont die expliziten Anweisungen, die den Zustand eines Programms verändern. Die Entwickler legen durch eine Reihe von Verfahrensbefehlen fest, wie das Programm eine Aufgabe erfüllen soll.
  4. Variablen und Daten:
    Prozeduren oder Funktionen in der prozeduralen Programmierung manipulieren Variablen und Daten. Diese Variablen können Werte enthalten, die sich während der Ausführung des Programms ändern. Zustandsänderungen sind ein grundlegender Aspekt der prozeduralen Programmierung.
  5. Sequentielle Ausführung:
    Die Ausführung des Programms folgt einem sequentiellen Ablauf. Die Anweisungen werden nacheinander ausgeführt, und Kontrollstrukturen wie Schleifen und Konditionale steuern den Programmfluss.
  6. Modularität:
    Die prozedurale Programmierung fördert die Modularität, indem sie den Code in Prozeduren oder Funktionen organisiert. Jedes Modul befasst sich mit einem bestimmten Aspekt der Programmfunktionalität, wodurch die Codeorganisation und die Wartbarkeit verbessert werden.
  7. Wiederverwendbarkeit:
    Die Wiederverwendbarkeit von Code ist ein wesentlicher Vorteil der prozeduralen Programmierung. Sobald eine Funktion geschrieben und getestet ist, kann sie überall dort eingesetzt werden, wo diese spezifische Funktionalität im Programm benötigt wird. Dadurch wird Redundanz reduziert und die Effizienz gesteigert.
  8. Lesbarkeit:
    Prozeduraler Code ist in der Regel besser lesbar, insbesondere für diejenigen, die an ein schrittweises Vorgehen gewöhnt sind. Der lineare Ablauf der Ausführung macht es einfach, der Logik des Programms zu folgen.

Verstehen der funktionalen Programmierung

Bei der funktionalen Programmierung dreht sich alles um das Konzept der Funktionen als Bürger erster Klasse und der Unveränderlichkeit. Sie ist der prozeduralen Programmierung ähnlich, mit Ausnahme des Hauptprinzips, wie sie Daten behandelt und Aufgaben ausführt.

Im Gegensatz zur prozeduralen Programmierung, bei der die Daten ihr Aussehen und ihre Rolle während der Ausführung des Programms ändern können, bevorzugt die funktionale Programmierung eine stabilere Umgebung. Einmal erstellte Daten bleiben so, wie sie sind. Diese Verpflichtung zur Unveränderlichkeit sorgt für ein gewisses Maß an Vorhersehbarkeit und hilft, unerwartete Nebeneffekte in Ihrem Code zu vermeiden.

Haupteigenschaften der funktionalen Programmierung


Hier sind einige wichtige Merkmale der funktionalen Programmierung:

  1. Unveränderlichkeit:
    In der funktionalen Programmierung ist die Unveränderlichkeit ein Grundprinzip. Einmal erstellte Daten bleiben unverändert. Anstatt bestehende Daten zu ändern, werden neue Daten mit den gewünschten Änderungen erstellt. Dies gewährleistet Vorhersehbarkeit und hilft, unbeabsichtigte Nebenwirkungen zu vermeiden.
  2. Funktionen als Bürger erster Klasse:
    Funktionen werden als Bürger erster Klasse behandelt, d. h. sie können Variablen zugewiesen, als Argumente an andere Funktionen übergeben und als Ergebnis von anderen Funktionen zurückgegeben werden. Diese Flexibilität ermöglicht die Erstellung von Funktionen höherer Ordnung und fördert einen modularen und ausdrucksstarken Kodierungsstil.
  3. Deklarativer Stil:
    Die funktionale Programmierung bevorzugt einen deklarativen Programmierstil, bei dem der Schwerpunkt darauf liegt, was das Programm erreichen soll, und nicht darauf, wie es erreicht werden soll. Dies trägt zu einem prägnanteren und besser lesbaren Code bei.
  4. Vermeidung eines veränderlichen Zustands:
    In der funktionalen Programmierung wird ein veränderlicher Zustand minimiert oder eliminiert. Daten werden als unveränderlich behandelt, und Funktionen vermeiden es, den externen Zustand zu verändern. Diese Eigenschaft vereinfacht die Überlegungen über das Verhalten von Funktionen.
  5. Rekursion und Funktionen höherer Ordnung:
    Rekursion und Funktionen höherer Ordnung, bei denen Funktionen andere Funktionen als Argumente annehmen oder als Ergebnis zurückgeben, werden in der funktionalen Programmierung häufig verwendet. Dies führt zu mehr modularem und wiederverwendbarem Code.


Der verfahrenstechnische Ansatz zur Entwicklung eines Price Action EA

Nachdem wir nun das Wesen des prozeduralen und des funktionalen Programmierparadigmas kennengelernt haben, wollen wir die Theorie anhand eines praktischen Beispiels in die Praxis umsetzen: die Erstellung eines preisaktionsbasierten Expert Advisors. Zunächst werde ich einen Einblick in die Handelsstrategie geben, die wir automatisieren wollen. Später werden wir durch die verschiedenen Komponenten des Codes navigieren, ihre Funktionen enträtseln und herausfinden, wie sie nahtlos zusammenarbeiten.


Preisaktionsstrategie mit EMA-Indikator


Unsere Handelsstrategie stützt sich auf einen einzigen Indikator, den exponentiellen gleitenden Durchschnitt (EMA). Dieser Indikator wird häufig in der technischen Analyse verwendet und hilft bei der Bestimmung der Marktrichtung auf der Grundlage des von Ihnen gewählten Handelssystems. Sie können den gleitenden Durchschnitt leicht als Standardindikator in MQL5 finden, sodass er sich leicht in unseren Code einfügen lässt.


Kauferöffnung:
Wir eröffnen eine Kaufposition, wenn die zuletzt geschlossene Kerze eine Kaufkerze ist und sowohl ihr Tiefst- als auch ihr Höchstkurs über dem exponentiellen gleitenden Durchschnitt (EMA) liegen.

Preis-Aktion EMA-Strategie Kaufsignal


Verkaufseröffnung:
Wir eröffnen eine Verkaufsposition, wenn die zuletzt geschlossene Kerze eine Verkaufskerze ist und sowohl ihr Tiefst- als auch ihr Höchstkurs unter dem exponentiellen gleitenden Durchschnitt (EMA) liegen.

Preis-Aktion EMA-Strategie Verkaufssignal


Ausstieg:
Wir schließen automatisch alle offenen Positionen und realisieren den damit verbundenen Gewinn oder Verlust, wenn der vom Nutzer angegebene prozentuale Gewinn oder Verlust für das Konto erreicht ist, oder wir verwenden die traditionellen Stop-Loss- oder Take-Profit-Orders.


Einstellungen Zustand
Kauferöffnung Wenn die zuletzt geschlossene Kerze eine Kaufkerze ist (Schlusskurs > Eröffnungskurs) und sowohl der Tiefst- als auch der Höchstkurs über dem Exponential Moving Average (EMA) liegen.
Verkaufseröffnung Wenn die zuletzt geschlossene Kerze eine Verkaufskerze ist (Schlusskurs < Eröffnungskurs) und sowohl der Tiefst- als auch der Höchstkurs unter dem Exponential Moving Average (EMA) liegen.
Ausstieg  Wir schließen alle offenen Positionen und realisieren Gewinn oder Verlust, wenn der vom Nutzer festgelegte Prozentsatz erreicht wird oder wenn die Stop-Loss- oder Take-Profit-Order ausgelöst wird.


Kodierung und Implementierung der Handelsstrategie

Nachdem wir nun unsere Handelsregeln und unseren Plan aufgestellt haben, können wir unsere Handelsstrategie zum Leben erwecken, indem wir unseren MQL5-Code in der MetaEditor IDE schreiben. Befolgen Sie diese Schritte, um sicherzustellen, dass wir mit einer sauberen Expertenberater-Vorlage beginnen, die nur die erforderlichen Pflichtfunktionen enthält:


Schritt 1: Öffnen Sie die MetaEditor IDE und starten Sie den „MQL-Assistenten“ über die Schaltfläche „Neu“ im Menü.

MQL5-Assistent neuer Expertenberater


Schritt 2: Wählen Sie die Option „Expert Advisor (Vorlage)“ und klicken Sie auf „Weiter“.

MQL5-Assistent neuer Expertenberater


Schritt 3:  Geben Sie im Abschnitt „Allgemeine Eigenschaften“ den Namen des Expertenberaters ein und klicken Sie auf „Weiter“.

MQL5-Assistent neuer Expertenberater


Schritt 4:  Vergewissern Sie sich, dass im Abschnitt „Ereignisbehandlung“ (Event handlers) keine Optionen ausgewählt sind. Heben Sie die Markierung von Optionen auf, wenn sie ausgewählt sind, und klicken Sie auf „Weiter“.

MQL5-Assistent neuer Expertenberater


Schritt 5: Vergewissern Sie sich, dass im Abschnitt „Tester Event Handlers“ keine Optionen ausgewählt sind. Deaktivieren Sie alle Optionen, wenn sie ausgewählt sind, und klicken Sie dann auf „Fertigstellen“ (Finish), um unsere MQL5-Expertenberater-Vorlage zu erstellen.

MQL5-Assistent neuer Expertenberater


Wir haben jetzt eine saubere MQL5 Expert Advisor-Vorlage mit nur den obligatorischen Funktionen (OnInit, OnDeinit und OnTick). Denken Sie daran, die neue Datei zu speichern, bevor Sie fortfahren.

So sieht unser neu generierter Expertenberater-Code aus:

//+------------------------------------------------------------------+
//|                                               PriceActionEMA.mq5 |
//|                                  Copyright 2023, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2023, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---

//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---

  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---

  }
//+------------------------------------------------------------------+


Zuerst schreiben wir eine kurze Beschreibung des EA, deklarieren und initialisieren die Nutzereingabevariablen und deklarieren dann alle globalen Variablen. Wir platzieren diesen Code direkt vor der Funktion OnInit().

#property description "A price action EA to demonstrate how to "
#property description "implement the procedural programming paradigm."

//--User input variables
input long magicNumber = 101;//Magic Number (Set 0 [Zero] to disable

input group ""
input ENUM_TIMEFRAMES tradingTimeframe = PERIOD_H1;//Trading Timeframe
input int emaPeriod = 20;//Moving Average Period
input int emaShift = 0;//Moving Average Shift

input group ""
input bool enableTrading = true;//Enable Trading
input bool enableAlerts = false;//Enable Alerts

input group ""
input double accountPercentageProfitTarget = 10.0;//Account Percentage (%) Profit Target
input double accountPercentageLossTarget = 10.0;//Account Percentage (%) Loss Target

input group ""
input int maxPositions = 3;//Max Positions (Max open positions in one direction)
input int tp = 5000;//TP (Take Profit Points/Pips [Zero (0) to diasable])
input int sl = 10000;//SL (Stop Loss Points/Pips [Zero (0) to diasable])


Wie wir bereits gelernt haben, „Der Kern der prozeduralen Programmierung sind Funktionen. Dabei handelt es sich um Sätze von Anweisungen, die zur Ausführung einer bestimmten Aufgabe zusammengefasst werden. Funktionen kapseln Funktionalität und fördern so Modularität und Wiederverwendung von Code.“ Wir werden dies umsetzen, indem wir unsere eigenen nutzerdefinierten Funktionen erstellen.

Die Funktion GetInit:

Diese Funktion ist verantwortlich für die Initialisierung aller globalen Variablen und die Durchführung anderer Aufgaben, wenn der EA geladen oder initialisiert wird.

int GetInit() //Function to initialize the robot and all the variables
  {
   int returnVal = 1;
//create the iMA indicator
   emaHandle = iMA(Symbol(), tradingTimeframe, emaPeriod, emaShift, MODE_EMA, PRICE_CLOSE);
   if(emaHandle < 0)
     {
      Print("Error creating emaHandle = ", INVALID_HANDLE);
      Print("Handle creation: Runtime error = ", GetLastError());
      //force program termination if the handle is not properly set
      return(-1);
     }
   ArraySetAsSeries(movingAverage, true);

//reset the count for positions
   totalOpenBuyPositions = 0;
   totalOpenSellPositions = 0;
   buyPositionsProfit = 0.0;
   sellPositionsProfit = 0.0;
   buyPositionsVol = 0.0;
   sellPositionsVol = 0.0;

   closedCandleTime = iTime(_Symbol, tradingTimeframe, 1);

   startingCapital = AccountInfoDouble(ACCOUNT_EQUITY);//used to calculate the account percentage profit

   if(enableAlerts)
     {
      Alert(MQLInfoString(MQL_PROGRAM_NAME), " has just been LOADED in the ", Symbol(), " ", EnumToString(Period()), " period chart.");
     }

//structure our comment string
   commentString = "\n\n" +
                   "Account No: " + IntegerToString(AccountInfoInteger(ACCOUNT_LOGIN)) +
                   "\nAccount Type: " + EnumToString((ENUM_ACCOUNT_TRADE_MODE)AccountInfoInteger(ACCOUNT_TRADE_MODE)) +
                   "\nAccount Leverage: " + IntegerToString(AccountInfoInteger(ACCOUNT_LEVERAGE)) +
                   "\n-----------------------------------------------------------------------------------------------------";
   return(returnVal);
  }


Die Funktion GetDeinit:

Diese Funktion ist für das Freigeben des belegten Speichers, das Löschen von Diagrammkommentaren und andere Deinitialisierungsaufgaben zuständig, bevor der EA beendet wird.

void GetDeinit()  //De-initialize the robot on shutdown and clean everything up
  {
   IndicatorRelease(emaHandle); //delete the moving average handle and deallocate the memory spaces occupied
   ArrayFree(movingAverage); //free the dynamic arrays containing the moving average buffer data

   if(enableAlerts)
     {
      Alert(MQLInfoString(MQL_PROGRAM_NAME), " has just been REMOVED from the ", Symbol(), " ", EnumToString(Period()), " period chart.");
     }
//delete and clear all chart displayed messages
   Comment("");
  }

Die Funktion GetEma:

Diese Funktion ruft den Wert des exponentiellen gleitenden Durchschnitts ab und bestimmt die Marktrichtung, indem sie den EMA-Wert mit den Eröffnungs-, Schluss-, Höchst- und Tiefstkursen der kürzlich geschlossenen Kerze vergleicht. Sie speichert den Wert der Marktrichtung in globalen Variablen zur weiteren Verarbeitung durch andere Funktionen.

void GetEma()
  {
//Get moving average direction
   if(CopyBuffer(emaHandle, 0, 0, 100, movingAverage) <= 0)
     {
      return;
     }
   movingAverageTrend = "FLAT";
   buyOk = false;
   sellOk = false;
   if(movingAverage[1] > iHigh(_Symbol, tradingTimeframe, 1) && movingAverage[1] > iLow(_Symbol, tradingTimeframe, 1))
     {
      movingAverageTrend = "SELL/SHORT";
      if(iClose(_Symbol, tradingTimeframe, 1) < iOpen(_Symbol, tradingTimeframe, 1))
        {
         sellOk = true;
         buyOk = false;
        }
     }
   if(movingAverage[1] < iHigh(_Symbol, tradingTimeframe, 1) && movingAverage[1] < iLow(_Symbol, tradingTimeframe, 1))
     {
      movingAverageTrend = "BUY/LONG";
      if(iClose(_Symbol, tradingTimeframe, 1) > iOpen(_Symbol, tradingTimeframe, 1))
        {
         buyOk = true;
         sellOk = false;
        }
     }
  }


Die Funktion GetPositionsData:

Diese Funktion durchsucht alle offenen Positionen und speichert deren Eigenschaften, wie z.B. Gewinnbetrag, Gesamtzahl der geöffneten Positionen, Gesamtzahl der geöffneten Kauf- und Verkaufspositionen und das Gesamtvolumen/Lot jeder Positionsart. Es schließt Daten für Positionen aus, die nicht von unserem EA eröffnet wurden oder die nicht mit der magischen Zahl des EA übereinstimmen.

void GetPositionsData()
  {
//get the total number of all open positions and their status
   if(PositionsTotal() > 0)
     {
      //variables for storing position properties values
      ulong positionTicket;
      long positionMagic, positionType;
      string positionSymbol;
      int totalPositions = PositionsTotal();

      //reset the count
      totalOpenBuyPositions = 0;
      totalOpenSellPositions = 0;
      buyPositionsProfit = 0.0;
      sellPositionsProfit = 0.0;
      buyPositionsVol = 0.0;
      sellPositionsVol = 0.0;

      //scan all the open positions
      for(int x = totalPositions - 1; x >= 0; x--)
        {
         positionTicket = PositionGetTicket(x);//gain access to other position properties by selecting the ticket
         positionMagic = PositionGetInteger(POSITION_MAGIC);
         positionSymbol = PositionGetString(POSITION_SYMBOL);
         positionType = PositionGetInteger(POSITION_TYPE);

         if(positionMagic == magicNumber && positionSymbol == _Symbol)
           {
            if(positionType == POSITION_TYPE_BUY)
              {
               ++totalOpenBuyPositions;
               buyPositionsProfit += PositionGetDouble(POSITION_PROFIT);
               buyPositionsVol += PositionGetDouble(POSITION_VOLUME);
              }
            if(positionType == POSITION_TYPE_SELL)
              {
               ++totalOpenSellPositions;
               sellPositionsProfit += PositionGetDouble(POSITION_PROFIT);
               sellPositionsVol += PositionGetDouble(POSITION_VOLUME);
              }
           }
        }
      //Get and save the account percentage profit
      accountPercentageProfit = ((buyPositionsProfit + sellPositionsProfit) * 100) / startingCapital;
     }
   else  //if no positions are open then the account percentage profit should be zero
     {
      startingCapital = AccountInfoDouble(ACCOUNT_EQUITY);
      accountPercentageProfit = 0.0;

      //reset position counters too
      totalOpenBuyPositions = 0;
      totalOpenSellPositions = 0;
     }
  }

Die Funktion TradingIsAllowed:

Diese Funktion prüft, ob der Nutzer, das Terminal und der Broker dem EA die Erlaubnis zum Handel gegeben haben.

bool TradingIsAllowed()
  {
//check if trading is enabled
   if(enableTrading &&
      MQLInfoInteger(MQL_TRADE_ALLOWED) && TerminalInfoInteger(TERMINAL_TRADE_ALLOWED) &&
      AccountInfoInteger(ACCOUNT_TRADE_ALLOWED) && AccountInfoInteger(ACCOUNT_TRADE_EXPERT)
     )
     {
      tradingStatus = "\n-----------------------------------------------------------------------------------------" +
                      "\nTRADING IS FULLY ENABLED! *** SCANNING FOR ENTRY ***";
      return(true);
     }
   else  //trading is disabled
     {
      tradingStatus = "\n-----------------------------------------------------------------------------------------" +
                      "\nTRADING IS NOT FULLY ENABLED! *** GIVE EA PERMISSION TO TRADE ***";
      return(false);
     }
  }

Die Funktion TradeNow:

Verantwortlich für die Eröffnung neuer Positionen, wenn alle erforderlichen Prüfungen und Signale darauf hindeuten, dass eine neue Handelsinitialisierung erfolgen kann.

void TradeNow()
  {
//Detect new candle formation and open a new position
   if(closedCandleTime != iTime(_Symbol, tradingTimeframe, 1))  //-- New candle found
     {
      //use the candle time as the position comment to prevent opening dublicate trades on one candle
      string positionComment = IntegerToString(iTime(_Symbol, tradingTimeframe, 1));

      //open a buy position
      if(buyOk && totalOpenBuyPositions < maxPositions)
        {
         //Use the positionComment string to check if we had already have a position open on this candle
         if(!PositionFound(_Symbol, POSITION_TYPE_BUY, positionComment)) //no position has been openend on this candle, open a buy position now
           {
            BuySellPosition(POSITION_TYPE_BUY, positionComment);
           }
        }

      //open a sell position
      if(sellOk && totalOpenSellPositions < maxPositions)
        {
         //Use the positionComment string to check if we had already have a position open on this candle
         if(!PositionFound(_Symbol, POSITION_TYPE_SELL, positionComment)) //no position has been openend on this candle, open a sell position now
           {
            BuySellPosition(POSITION_TYPE_SELL, positionComment);
           }
        }

      //reset closedCandleTime value to prevent new entry orders from opening before a new candle is formed
      closedCandleTime = iTime(_Symbol, tradingTimeframe, 1);
     }
  }

Die Funktion ManageProfitAndLoss:

Diese Funktion prüft, ob die vom Nutzer eingegebenen Gewinn- und Verlustschwellen erreicht sind. Wenn die Bedingungen erfüllt sind, werden alle Gewinne oder Verluste durch Schließung aller offenen Positionen liquidiert.

void ManageProfitAndLoss()
  {
//if the account percentage profit or loss target is hit, delete all positions
   double lossLevel = -accountPercentageLossTarget;
   if(
      (accountPercentageProfit >= accountPercentageProfitTarget || accountPercentageProfit <= lossLevel) ||
      ((totalOpenBuyPositions >= maxPositions || totalOpenSellPositions >= maxPositions) && accountPercentageProfit > 0)
   )
     {
      //delete all open positions
      if(PositionsTotal() > 0)
        {
         //variables for storing position properties values
         ulong positionTicket;
         long positionMagic, positionType;
         string positionSymbol;
         int totalPositions = PositionsTotal();

         //scan all the open positions
         for(int x = totalPositions - 1; x >= 0; x--)
           {
            positionTicket = PositionGetTicket(x);//gain access to other position properties by selecting the ticket
            positionMagic = PositionGetInteger(POSITION_MAGIC);
            positionSymbol = PositionGetString(POSITION_SYMBOL);
            positionType = PositionGetInteger(POSITION_TYPE);
            int positionDigits= (int)SymbolInfoInteger(positionSymbol, SYMBOL_DIGITS);
            double positionVolume = PositionGetDouble(POSITION_VOLUME);
            ENUM_POSITION_TYPE positionType = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);

            if(positionMagic == magicNumber && positionSymbol == _Symbol)
              {
               //print the position details
               Print("*********************************************************************");
               PrintFormat(
                  "#%I64u %s  %s  %.2f  %s [%I64d]",
                  positionTicket, positionSymbol, EnumToString(positionType), positionVolume,
                  DoubleToString(PositionGetDouble(POSITION_PRICE_OPEN), positionDigits), positionMagic
               );

               //reset the the tradeRequest and tradeResult values by zeroing them
               ZeroMemory(tradeRequest);
               ZeroMemory(tradeResult);
               //set the operation parameters
               tradeRequest.action = TRADE_ACTION_DEAL;//type of trade operation
               tradeRequest.position = positionTicket;//ticket of the position
               tradeRequest.symbol = positionSymbol;//symbol
               tradeRequest.volume = positionVolume;//volume of the position
               tradeRequest.deviation = SymbolInfoInteger(_Symbol, SYMBOL_SPREAD);//allowed deviation from the price
               tradeRequest.magic = magicNumber;//MagicNumber of the position

               //set the price and order type depending on the position type
               if(positionType == POSITION_TYPE_BUY)
                 {
                  tradeRequest.price = SymbolInfoDouble(positionSymbol, SYMBOL_BID);
                  tradeRequest.type = ORDER_TYPE_SELL;
                 }
               else
                 {
                  tradeRequest.price = SymbolInfoDouble(positionSymbol, SYMBOL_ASK);
                  tradeRequest.type = ORDER_TYPE_BUY;
                 }

               //print the position close details
               PrintFormat("Close #%I64d %s %s", positionTicket, positionSymbol, EnumToString(positionType));
               //send the tradeRequest
               if(OrderSend(tradeRequest, tradeResult)) //trade tradeRequest success, position has been closed
                 {
                  if(enableAlerts)
                    {
                     Alert(
                        _Symbol + " PROFIT LIQUIDATION: Just successfully closed POSITION (#" +
                        IntegerToString(positionTicket) + "). Check the EA journal for more details."
                     );
                    }
                  PrintFormat("Just successfully closed position: #%I64d %s %s", positionTicket, positionSymbol, EnumToString(positionType));
                  PrintFormat("retcode=%u  deal=%I64u  order=%I64u", tradeResult.retcode, tradeResult.deal, tradeResult.order);
                 }
               else  //trade tradeRequest failed
                 {
                  //print the information about the operation
                  if(enableAlerts)
                    {
                     Alert(
                        _Symbol + " ERROR ** PROFIT LIQUIDATION: closing POSITION (#" +
                        IntegerToString(positionTicket) + "). Check the EA journal for more details."
                     );
                    }
                  PrintFormat("Position clossing failed: #%I64d %s %s", positionTicket, positionSymbol, EnumToString(positionType));
                  PrintFormat("OrderSend error %d", GetLastError());//print the error code
                 }
              }
           }
        }
     }
  }

Die Funktion PrintOnChart:

Formatiert und zeigt den EA-Status im Chart an, sodass der Nutzer eine visuelle Textdarstellung des Kontos und des EA-Status erhält.

void PrintOnChart()
  {
//update account status strings and display them on the chart
   accountStatus = "\nAccount Balance: " + DoubleToString(AccountInfoDouble(ACCOUNT_BALANCE), 2) + accountCurrency +
                   "\nAccount Equity: " + DoubleToString(AccountInfoDouble(ACCOUNT_EQUITY), 2) + accountCurrency +
                   "\nAccount Profit: " + DoubleToString(AccountInfoDouble(ACCOUNT_PROFIT), 2) + accountCurrency +
                   "\nAccount Percentage Profit: " + DoubleToString(accountPercentageProfit, 2) + "%" +
                   "\n-----------------------------------------------------------------------------------------" +
                   "\nTotal Buy Positions Open: " + IntegerToString(totalOpenBuyPositions) +
                   "        Total Vol/Lots: " + DoubleToString(buyPositionsVol, 2) +
                   "        Profit: " + DoubleToString(buyPositionsProfit, 2) + accountCurrency +
                   "\nTotal Sell Positions Open: " + IntegerToString(totalOpenSellPositions) +
                   "        Total Vol/Lots: " + DoubleToString(sellPositionsVol, 2) +
                   "        Profit: " + DoubleToString(sellPositionsProfit, 2) + accountCurrency +
                   "\nPositionsTotal(): " + IntegerToString(PositionsTotal()) +
                   "\n-----------------------------------------------------------------------------------------" +
                   "\nJust Closed Candle:     Open: " + DoubleToString(iOpen(_Symbol, tradingTimeframe, 1), _Digits) +
                   "     Close: " + DoubleToString(iClose(_Symbol, tradingTimeframe, 1), _Digits) +
                   "     High: " + DoubleToString(iHigh(_Symbol, tradingTimeframe, 1), _Digits) +
                   "     Low: " + DoubleToString(iLow(_Symbol, tradingTimeframe, 1), _Digits) +
                   "\n-----------------------------------------------------------------------------------------" +
                   "\nMovingAverage (EMA): " + DoubleToString(movingAverage[1], _Digits) +
                   "     movingAverageTrend = " + movingAverageTrend +
                   "\nsellOk: " + IntegerToString(sellOk) +
                   "\nbuyOk: " + IntegerToString(buyOk);

//show comments on the chart
   Comment(commentString + accountStatus + tradingStatus);
  }


Die Funktion BuySellPosition:

Diese Funktion eröffnet neue Kauf- und Verkaufspositionen.

bool BuySellPosition(int positionType, string positionComment)
  {
//reset the the tradeRequest and tradeResult values by zeroing them
   ZeroMemory(tradeRequest);
   ZeroMemory(tradeResult);
//initialize the parameters to open a position
   tradeRequest.action = TRADE_ACTION_DEAL;
   tradeRequest.symbol = Symbol();
   tradeRequest.deviation = SymbolInfoInteger(_Symbol, SYMBOL_SPREAD);
   tradeRequest.magic = magicNumber;
   tradeRequest.comment = positionComment;
   double volumeLot = NormalizeDouble(((SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN) * AccountInfoDouble(ACCOUNT_EQUITY)) / 10000), 2);

   if(positionType == POSITION_TYPE_BUY)
     {
      if(sellPositionsVol > volumeLot && AccountInfoDouble(ACCOUNT_MARGIN_LEVEL) > 200)
        {
         volumeLot = NormalizeDouble((sellPositionsVol + volumeLot), 2);
        }
      if(volumeLot < 0.01)
        {
         volumeLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
        }
      if(volumeLot > SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX))
        {
         volumeLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
        }
      tradeRequest.volume = NormalizeDouble(volumeLot, 2);
      tradeRequest.type = ORDER_TYPE_BUY;
      tradeRequest.price = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
      if(tp > 0)
        {
         tradeRequest.tp = NormalizeDouble(tradeRequest.price + (tp * _Point), _Digits);
        }
      if(sl > 0)
        {
         tradeRequest.sl = NormalizeDouble(tradeRequest.price - (sl * _Point), _Digits);
        }
      if(OrderSend(tradeRequest, tradeResult)) //successfully openend the position
        {
         if(enableAlerts)
           {
            Alert(_Symbol, " Successfully openend BUY POSITION #", tradeResult.order, ", Price: ", tradeResult.price);
           }
         PrintFormat("retcode=%u  deal=%I64u  order=%I64u", tradeResult.retcode, tradeResult.deal, tradeResult.order);
         return(true);
        }
      else
        {
         if(enableAlerts)
           {
            Alert(_Symbol, " ERROR opening a BUY POSITION at: ", SymbolInfoDouble(_Symbol, SYMBOL_ASK));
           }
         PrintFormat("ERROR: Opening a BUY POSITION: ErrorCode = %d",GetLastError());//OrderSend failed, output the error code
         return(false);
        }
     }

   if(positionType == POSITION_TYPE_SELL)
     {
      if(buyPositionsVol > volumeLot && AccountInfoDouble(ACCOUNT_MARGIN_LEVEL) > 200)
        {
         volumeLot = NormalizeDouble((buyPositionsVol + volumeLot), 2);
        }
      if(volumeLot < 0.01)
        {
         volumeLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
        }
      if(volumeLot > SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX))
        {
         volumeLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
        }
      tradeRequest.volume = NormalizeDouble(volumeLot, 2);
      tradeRequest.type = ORDER_TYPE_SELL;
      tradeRequest.price = SymbolInfoDouble(_Symbol, SYMBOL_BID);
      if(tp > 0)
        {
         tradeRequest.tp = NormalizeDouble(tradeRequest.price - (tp * _Point), _Digits);
        }
      if(sl > 0)
        {
         tradeRequest.sl = NormalizeDouble(tradeRequest.price + (sl * _Point), _Digits);
        }
      if(OrderSend(tradeRequest, tradeResult)) //successfully openend the position
        {
         if(enableAlerts)
           {
            Alert(_Symbol, " Successfully openend SELL POSITION #", tradeResult.order, ", Price: ", tradeResult.price);
           }
         PrintFormat("retcode=%u  deal=%I64u  order=%I64u", tradeResult.retcode, tradeResult.deal, tradeResult.order);
         return(true);
        }
      else
        {
         if(enableAlerts)
           {
            Alert(_Symbol, " ERROR opening a SELL POSITION at: ", SymbolInfoDouble(_Symbol, SYMBOL_ASK));
           }
         PrintFormat("ERROR: Opening a SELL POSITION: ErrorCode = %d",GetLastError());//OrderSend failed, output the error code
         return(false);
        }
     }
   return(false);
  }

Die Funktion PositionFound:

Diese Funktion prüft, ob eine bestimmte Position existiert, damit der EA nicht mehrere doppelte Positionen auf einer Kerze öffnet.

bool PositionFound(string symbol, int positionType, string positionComment)
  {
   if(PositionsTotal() > 0)
     {
      ulong positionTicket;
      int totalPositions = PositionsTotal();
      //scan all the open positions
      for(int x = totalPositions - 1; x >= 0; x--)
        {
         positionTicket = PositionGetTicket(x);//gain access to other position properties by selecting the ticket
         if(
            PositionGetInteger(POSITION_MAGIC) == magicNumber && PositionGetString(POSITION_SYMBOL) == symbol &&
            PositionGetInteger(POSITION_TYPE) == positionType && PositionGetString(POSITION_COMMENT) == positionComment
         )
           {
            return(true);//a similar position exists, don't open another position on this candle
            break;
           }
        }
     }
   return(false);
  }


Nachdem wir nun unsere nutzerdefinierten Funktionen definiert haben, können wir sie aufrufen, um die vorgesehenen Aufgaben zu erfüllen.

  • Platzieren der Funktion GetInit in der Funktion OnInit und ihr Aufruf: 

int OnInit()
  {
//---
   if(GetInit() <= 0)
     {
      return(INIT_FAILED);
     }
//---
   return(INIT_SUCCEEDED);
  }

  • Platzieren der Funktion GetDeinit in der Funktion OnDeinit und ihr Aufruf: 

void OnDeinit(const int reason)
  {
//---
   GetDeinit();
  }

  • Platzieren der folgenden Funktionen in der Funktion OnTick und rufen ihr Aufruf in der richtigen Reihenfolge. Da einige Funktionen globale Variablen ändern, auf die sich andere Funktionen verlassen, um wichtige Entscheidungen zu treffen, werden wir sicherstellen, dass sie zuerst aufgerufen werden, um zu gewährleisten, dass die Daten verarbeitet und aktualisiert werden, bevor andere Funktionen darauf zugreifen können. 

void OnTick()
  {
//---
   GetEma();
   GetPositionsData();
   if(TradingIsAllowed())
     {
      TradeNow();
      ManageProfitAndLoss();
     }
   PrintOnChart();
  }

Wenn der EA-Code fertig ist, speichern und kompilieren Sie ihn. So können Sie direkt vom Handelsterminal aus auf Ihren EA zugreifen. Der vollständige Code ist am Ende des Artikels beigefügt.


Testen unseres EA im Strategy Tester

Es ist von entscheidender Bedeutung, dass unser EA nach unserem Plan arbeitet. Dies können wir erreichen, indem wir sie entweder auf einen aktiven Symbolchart laden und in einem Demokonto handeln oder indem wir den Strategietester für eine umfassende Bewertung nutzen. Sie können die Strategie zwar auf einem Demokonto testen, aber zunächst werden wir den Strategietester verwenden, um ihre Leistung zu bewerten.

Hier sind die Einstellungen, die wir im Strategietester anwenden werden:

  • Makler: MT5 Metaquotes-Demokonto (wird automatisch bei der MT5-Installation erstellt)

  • Symbol: EURUSD

  • Testzeitraum (Datum): 1 Jahr (November 2022 bis November 2023)

  • Modellierung: Jeder Tick anhand realer Ticks

  • Einlage: $10.000 USD

  • Hebel: 1:100

PriceActionEA-Strategie-Tester-Einstellungen


PriceActionEA-Strategie-Tester-Einstellungen


Bei der Überprüfung unserer Backtesting-Ergebnisse hat unser EA nicht nur einen Gewinn erzielt, sondern auch einen bemerkenswert niedrigen Drawdown beibehalten. Diese Strategie ist vielversprechend und kann weiter modifiziert und optimiert werden, um noch bessere Ergebnisse zu erzielen, insbesondere wenn sie auf mehrere Symbole gleichzeitig angewendet wird.

PriceActionEMA-Testergebnisse

PriceActionEMA-Testergebnisse

PriceActionEMA-Testergebnisse


Schlussfolgerung

Der prozedurale Code des EA, den wir oben erstellt haben, ist selbst für MQL5-Anfänger leicht zu verstehen. Diese Einfachheit ergibt sich aus der klaren und direkten Natur der prozeduralen Programmierung, insbesondere bei der Verwendung von Funktionen zur Organisation des Codes auf der Grundlage spezifischer Aufgaben und globaler Variablen zur Übergabe geänderter Daten an verschiedene Funktionen.

Ein Nachteil des prozeduralen Codes ist jedoch, dass er sich mit zunehmender Komplexität des EA deutlich ausdehnt und sich daher vor allem für weniger komplexe Projekte eignet. In Fällen, in denen Ihr Projekt sehr komplex ist, erweist sich die objektorientierte Programmierung als vorteilhafter als die prozedurale Programmierung.

In unserem nächsten Artikel werden wir Sie in die objektorientierte Programmierung einführen und unseren kürzlich erstellten prozeduralen Price Action EA-Code in objektorientierten Code umwandeln. Dies wird einen deutlichen Vergleich zwischen diesen Paradigmen ermöglichen und ein klareres Verständnis der Unterschiede bieten.

Vielen Dank, dass Sie sich die Zeit genommen haben, diesen Artikel zu lesen. Ich wünsche Ihnen viel Erfolg bei der Entwicklung von MQL5 und bei Ihren Handelsbestrebungen.

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

Beigefügte Dateien |
PriceActionEMA.mq5 (23.33 KB)
Fertige Vorlagen für die Aufnahme von Indikatoren in Expert Advisors (Teil 3): Trendindikatoren Fertige Vorlagen für die Aufnahme von Indikatoren in Expert Advisors (Teil 3): Trendindikatoren
In diesem Referenzartikel werden wir uns mit Standardindikatoren aus der Kategorie Trendindikatoren beschäftigen. Wir werden gebrauchsfertige Vorlagen für die Verwendung von Indikatoren in EAs erstellen - Deklaration und Einstellung von Parametern, Initialisierung und Deinitialisierung von Indikatoren sowie Empfang von Daten und Signalen aus Indikatorpuffern in EAs.
Datenwissenschaft und maschinelles Lernen (Teil 17): Geld von Bäumen? Die Kunst und Wissenschaft der Random Forests im Devisenhandel Datenwissenschaft und maschinelles Lernen (Teil 17): Geld von Bäumen? Die Kunst und Wissenschaft der Random Forests im Devisenhandel
Entdecken Sie die Geheimnisse der algorithmischen Alchemie, während wir Sie durch die Mischung aus Kunstfertigkeit und Präzision bei der Entschlüsselung von Finanzlandschaften führen. Entdecken Sie, wie Random Forests Daten in Vorhersagefähigkeiten umwandeln und eine einzigartige Perspektive für die Navigation auf dem komplexen Terrain der Aktienmärkte bieten. Begleiten Sie uns auf dieser Reise in das Herz der Finanzmagie, wo wir die Rolle von Random Forests bei der Gestaltung des Marktgeschehens entmystifizieren und die Türen zu lukrativen Gelegenheiten aufschließen
Neuronale Netze leicht gemacht (Teil 58): Decision Transformer (DT) Neuronale Netze leicht gemacht (Teil 58): Decision Transformer (DT)
Wir setzen das Studium der Methoden des Reinforcement Learning bzw. des Verstärkungslernens fort. In diesem Artikel werde ich mich auf einen etwas anderen Algorithmus konzentrieren, der die Politik des Agenten im Paradigma der Konstruktion einer Sequenz von Aktionen betrachtet.
Algorithmen zur Optimierung mit Populationen: Shuffled Frog-Leaping Algorithmus (SFL) Algorithmen zur Optimierung mit Populationen: Shuffled Frog-Leaping Algorithmus (SFL)
Der Artikel enthält eine detaillierte Beschreibung des Shuffled-Frog-Leaping-Algorithmus (SFL) und seiner Fähigkeiten bei der Lösung von Optimierungsproblemen. Der SFL-Algorithmus ist vom Verhalten der Frösche in ihrer natürlichen Umgebung inspiriert und bietet einen neuen Ansatz zur Funktionsoptimierung. Der SFL-Algorithmus ist ein effizientes und flexibles Werkzeug, das eine Vielzahl von Datentypen verarbeiten und optimale Lösungen erzielen kann.