Schritt-für-Schritt: Eigenen EA in MQL5 schreiben

Selbst-Programmiert Expert Advisor „Moving Average Crossover“

Einführung

Dieser Expert Advisor (EA) „MA_Cross“ für MetaTrader 5 automatisiert das Erkennen und Handeln eines Trendfolge-Setups auf Basis zweier exponentieller gleitender Durchschnitte (EMA) und der Average True Range (ATR).

Er veranschaulicht, wie man in MQL5:

  1. Indikatoren erzeugt und ausliest
  2. Signale (Crossover und Volatilitätsfilter) erkennt
  3. Risikomanagement per Stop-Loss, Take-Profit und Lot-Berechnung umsetzt
  4. Orders platziert und überwacht
  5. Trailing-Stop dynamisch anpasst

Ziel ist es, zu zeigen, wie man in MQL5 sauber mit Indikator-Handles arbeitet und einen kompletten, lauffähigen EA erstellt.


Übersicht des Codes

Bibliotheken und Handelsobjekt

#include <Trade\Trade.mqh>   // CTrade-Klasse für Orderfunktionen einbinden
CTrade trade;               // Handelsobjekt zum Senden und Anpassen von Orders

Trade.mqh stellt die Klasse CTrade zur Verfügung, mit der Marktorders (Buy/Sell) sowie Anpassungen an bestehenden Positionen komfortabel umgesetzt werden können.


Eingabeparameter

input int    FastEMAPeriod   = 10;            // Periode für schnellen EMA
input int    SlowEMAPeriod   = 50;            // Periode für langsamen EMA
input int    ATRPeriod       = 14;            // Periode für ATR
input double RiskPercent     = 1.0;           // Max. Risiko pro Trade in % des Kontos
input double RewardRiskRatio = 2.0;           // Verhältnis TP zu SL (z.B. 1:2)
input bool   UseTrailingStop = true;          // Trailing-Stop aktivieren?
input string CommentText     = "EMA_Cross_ATR";// Kommentar für alle Orders

Diese Inputs erlauben es, alle wichtigen Parameter (EMA-Perioden, ATR, Risiko, R-R-Ratio, Trailing-Stop) direkt im Tester oder in den EA-Eigenschaften anzupassen.


Globale Handles für Indikatoren

int fastHandle;    // Handle für schnellen EMA (FastEMAPeriod)
int slowHandle;    // Handle für langsamen EMA (SlowEMAPeriod)
int atrHandle;     // Handle für ATR (ATRPeriod)

Indikatoren werden in MQL5 über Handles angelegt. Diese Integer-Variablen speichern die Referenzen und werden später zum Auslesen der Werte via CopyBuffer() benutzt.


OnInit()

int OnInit()
  {
   // EMA-Handles erzeugen
   fastHandle = iMA(_Symbol, PERIOD_CURRENT, FastEMAPeriod, 0, MODE_EMA, PRICE_CLOSE);
   slowHandle = iMA(_Symbol, PERIOD_CURRENT, SlowEMAPeriod, 0, MODE_EMA, PRICE_CLOSE);
   // ATR-Handle erzeugen
   atrHandle  = iATR(_Symbol, PERIOD_CURRENT, ATRPeriod);

   // Prüfen, ob alle Handles gültig sind
   if(fastHandle == INVALID_HANDLE
      || slowHandle == INVALID_HANDLE
      || atrHandle  == INVALID_HANDLE)
     {
      Print("Fehler beim Erstellen der Indikator-Handles");
      return(INIT_FAILED);
     }

   return(INIT_SUCCEEDED);
  }
  • iMA(…) und iATR(…) liefern die Handles.
  • Bei INVALID_HANDLE schlägt die Initialisierung fehl.

CalculateLot()

double CalculateLot(bool isBuy, double stopLossPrice)
  {
   // Entry-Preis (Ask bei Buy, Bid bei Sell)
   double entryPrice = isBuy
                       ? SymbolInfoDouble(_Symbol, SYMBOL_ASK)
                       : SymbolInfoDouble(_Symbol, SYMBOL_BID);
   // Abstand Entry ↔ Stop-Loss in Punkten
   double distance   = MathAbs(entryPrice - stopLossPrice);
   // Betrag, den wir maximal riskieren dürfen
   double riskAmount = AccountInfoDouble(ACCOUNT_BALANCE) * RiskPercent / 100.0;
   // Tick-Wert und Tick-Größe für das Symbol
   double tickValue  = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE);
   double tickSize   = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
   double pointValue = tickValue / tickSize;
   // Roh-Lot-Berechnung: Risiko / (Abstand × Punktwert)
   double lots       = riskAmount / (distance * pointValue);
   // Auf erlaubte Volumen-Schritte runden
   double step       = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
   double minLot     = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
   double maxLot     = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
   lots = MathFloor(lots / step) * step;
   if(lots < minLot) lots = minLot;
   if(lots > maxLot) lots = maxLot;
   return(lots);
  }

Diese Funktion errechnet die Lot-Size so, dass beim Erreichen des Stop-Loss der maximale Risiko-Betrag nicht überschritten wird.


OnTick()

void OnTick()
  {
   // 1) Aktuelle Bid-/Ask-Preise
   double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
   double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);

   // 2) Puffer für die letzten 2 Werte jedes Indikators
   double fastBuf[2], slowBuf[2], atrBuf[2];

   // 3) Indikator-Werte kopieren (aktueller = Index 0, vorheriger = Index 1)
   if(CopyBuffer(fastHandle, 0, 0, 2, fastBuf) <= 0 ||
      CopyBuffer(slowHandle, 0, 0, 2, slowBuf) <= 0 ||
      CopyBuffer(atrHandle,  0, 0, 2, atrBuf)  <= 0)
     {
      Print("Fehler beim Kopieren der Indikator-Daten");
      return;
     }

   // 4) Zuordnung der Pufferdaten
   double fastEMA    = fastBuf[0], fastEMAold = fastBuf[1];
   double slowEMA    = slowBuf[0], slowEMAold = slowBuf[1];
   double atrNow     = atrBuf[0],  atrOld     = atrBuf[1];

   // 5) Prüfen, ob bereits eine Position offen ist
   bool positionExists = PositionSelect(_Symbol);

   // — Einstieg nur, wenn keine Position offen —
   if(!positionExists)
     {
      // Long-Signal: EMA10 kreuzt EMA50 nach oben + ATR steigend
      bool longSignal  = (fastEMAold < slowEMAold)
                         && (fastEMA > slowEMA)
                         && (atrNow > atrOld);
      // Short-Signal: EMA10 kreuzt EMA50 nach unten
      bool shortSignal = (fastEMAold > slowEMAold)
                         && (fastEMA < slowEMA);

      // Long-Entry
      if(longSignal)
        {
         double sl  = ask - 1.5 * atrNow;
         double tp  = ask + RewardRiskRatio * (ask - sl);
         double lot = CalculateLot(true, sl);
         if(trade.Buy(lot, NULL, ask, sl, tp, CommentText))
            Print("Long eröffnet bei ", ask, " SL=", sl, " TP=", tp);
        }
      // Short-Entry
      else if(shortSignal)
        {
         double sl  = bid + 1.5 * atrNow;
         double tp  = bid - RewardRiskRatio * (sl - bid);
         double lot = CalculateLot(false, sl);
         if(trade.Sell(lot, NULL, bid, sl, tp, CommentText))
            Print("Short eröffnet bei ", bid, " SL=", sl, " TP=", tp);
        }
     }
   // — Trailing-Stop, falls Position offen und aktiviert —
   else if(UseTrailingStop)
     {
      ulong ticket = PositionGetInteger(POSITION_TICKET);
      ENUM_POSITION_TYPE type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
      double newSL = (type == POSITION_TYPE_BUY)
                     ? bid - atrNow
                     : ask + atrNow;
      trade.PositionModify(ticket, newSL, 0);
     }
  }

In OnTick() werden die Indikatoren ausgewertet, Signale erkannt, Orders gesendet und bei Bedarf der Stop-Loss dynamisch nachgezogen.


OnDeinit()

void OnDeinit(const int reason)
  {
   if(fastHandle != INVALID_HANDLE) IndicatorRelease(fastHandle);
   if(slowHandle != INVALID_HANDLE) IndicatorRelease(slowHandle);
   if(atrHandle  != INVALID_HANDLE) IndicatorRelease(atrHandle);
  }

Beim Entfernen des EAs werden alle Indikator-Handles freigegeben, um Speicherlecks und andere Probleme zu vermeiden.


Erklärung der wichtigsten Schritte

  1. Handle-Erzeugung und Werte-Auslesung
    • iMA() / iATR() liefern in OnInit() jeweils einen Handle, eine numerische Referenz auf den jeweiligen Indikator.
    • In OnTick() ruft CopyBuffer(handle, 0, shift, count, buffer) die aktuellen und vorherigen Werte in ein lokales Array.
    • Vorteil: Effizienter als wiederholte Direktaufrufe und ermöglicht den Zugriff auf historische Balken.
  2. Crossover-Erkennung
    bool longSignal = (fastEMAold < slowEMAold) && (fastEMA > slowEMA) && (atrNow > atrOld); bool shortSignal = (fastEMAold > slowEMAold) && (fastEMA < slowEMA);
    • fastEMAold und slowEMAold sind die EMA-Werte des vorherigen Balkens, fastEMA und slowEMA jene des aktuellen.
    • Ein Long-Signal entsteht, wenn der schnelle EMA den langsamen EMA von unten nach oben kreuzt und die ATR zeigt steigende Volatilität.
    • Ein Short-Signal erfolgt beim Kreuzen von oben nach unten, ohne ATR-Filter.
  3. Risikomanagement & Lot-Berechnung
    • Stop-Loss wird auf 1,5 × ATR festgelegt:
      • Long: SL = Ask – 1.5 × ATR
      • Short: SL = Bid + 1.5 × ATR
    • Take-Profit so, dass das Risiko-Ertrags-Verhältnis 1:2 beträgt:
      • TP-Abstand = 2 × SL-Abstand
    • In CalculateLot() wird ermittelt, wie viele Lots so platziert werden können, dass beim Erreichen des Stop-Loss maximal RiskPercent % des Kontoguthabens verloren gehen.
    • Dabei fließen SymbolInfoDouble(..., SYMBOL_TRADE_TICK_VALUE) und SYMBOL_TRADE_TICK_SIZE mit ein, um den Geldwert pro Punkt korrekt zu berücksichtigen.
  4. Order-Platzierung
    • Mit trade.Buy(lot, NULL, price, sl, tp, CommentText) bzw.
      trade.Sell(...) wird eine Marktorder mit definiertem SL und TP ausgelöst.
    • CTrade kümmert sich intern um Fehlerbehandlung, Slippage und Rückgabe des Order-Tickets.
  5. Trailing-Stop (optional)
    • Sobald eine Position offen ist und UseTrailingStop == true, verschiebt der EA bei jedem Tick den Stop-Loss auf 1 × ATR hinter den aktuellen Marktpreis:
      • Long: neuer SL = Bid – ATR
      • Short: neuer SL = Ask + ATR
    • Ermöglicht eine dynamische Absicherung bereits erzielter Gewinne.
  6. Aufräumen in OnDeinit()
    • Alle erzeugten Indikator-Handles (fastHandle, slowHandle, atrHandle) werden mit IndicatorRelease(handle) wieder freigegeben, um Ressourcen korrekt zu bereinigen.

Durch diese klar strukturierten Schritte lernst du, wie man in MQL5:

  • Indikatoren performant anlegt und ausliest
  • Signale präzise detektiert
  • Verantwortungsvolles Risikomanagement implementiert
  • Marktorders professionell verwaltet
  • Dynamische Stop-Loss-Strategien umsetzt

Zusammenfassung

Dieser EA demonstriert einen vollständigen Workflow in MQL5:

  • Saubere Trennung zwischen Initialisierung (OnInit), Hauptlogik (OnTick) und Aufräumen (OnDeinit).
  • Handle-basierte Indikatornutzung mit CopyBuffer().
  • Einfache Trendfolge-Strategie auf Basis zweier EMAs plus Volatilitätsfilter.
  • Sorgfältiges Risikomanagement mittels Stop-Loss, Take-Profit und dynamischer Lot-Berechnung.
  • Optionale Automatisierung eines Trailing-Stops.

Mit diesem kommentierten Beispiel erhältst du eine solide Grundlage, um eigene Strategien in MQL5 zu entwickeln und zu verstehen, wie man automatisierte Handelssysteme professionell umsetzt. Viel Erfolg bei deinen nächsten Schritten in der MQL5-Programmierung!


Quellcode MA_Cross.mq5

//+------------------------------------------------------------------+
//|                                                   MA_Cross.mq5   |
//|        EMA Crossover + ATR-Filter für MetaTrader 5 (MQL5)        |
//+------------------------------------------------------------------+
#include <Trade\Trade.mqh>                                  // CTrade-Klasse für Orderfunktionen einbinden
CTrade trade;                                              // Handelsobjekt

//—— Eingabeparameter —————————————————————————————————————————————
input int    FastEMAPeriod    = 10;                       // Periode für schnellen EMA
input int    SlowEMAPeriod    = 50;                       // Periode für langsamen EMA
input int    ATRPeriod        = 14;                       // Periode für ATR
input double RiskPercent      = 1.0;                      // Max. Risiko pro Trade in % (z.B. 1 = 1 %)
input double RewardRiskRatio  = 2.0;                      // Risiko-Ertrag Verhältnis (z.B. 1:2)
input bool   UseTrailingStop  = true;                     // Trailing-Stop aktivieren?
input string CommentText      = "EMA_Cross_ATR";          // Kommentartext für Orders

//—— Globale Handles für Indikatoren —————————————————————————————————
int fastHandle;                                           // Handle für schnellen EMA
int slowHandle;                                           // Handle für langsamen EMA
int atrHandle;                                            // Handle für ATR

//+------------------------------------------------------------------+
//| OnInit: Initialisierung beim Laden des EAs                      |
//+------------------------------------------------------------------+
int OnInit()
  {
   // EMA-Handles mit Symbol, Timeframe, Periode, Shift, Methode, Preisquelle erzeugen
   fastHandle = iMA(_Symbol, PERIOD_CURRENT, FastEMAPeriod, 0, MODE_EMA, PRICE_CLOSE);
   slowHandle = iMA(_Symbol, PERIOD_CURRENT, SlowEMAPeriod, 0, MODE_EMA, PRICE_CLOSE);
   // ATR-Handle mit Symbol, Timeframe, Periode erzeugen
   atrHandle  = iATR(_Symbol, PERIOD_CURRENT, ATRPeriod);

   // Prüfen, ob Handles gültig sind
   if(fastHandle==INVALID_HANDLE || slowHandle==INVALID_HANDLE || atrHandle==INVALID_HANDLE)
     {
      Print("Fehler beim Erstellen der Indikator-Handles");
      return(INIT_FAILED);                               // Init abbrechen bei Fehler
     }
   return(INIT_SUCCEEDED);                                // Alles ok
  }

//+------------------------------------------------------------------+
//| CalculateLot: Berechnet Lotgröße basierend auf SL-Abstand       |
//+------------------------------------------------------------------+
double CalculateLot(bool isBuy, double stopLossPrice)
  {
   // 1) Entry-Preis je nach Buy/Sell ermitteln
   double entryPrice = isBuy
                       ? SymbolInfoDouble(_Symbol, SYMBOL_ASK)
                       : SymbolInfoDouble(_Symbol, SYMBOL_BID);
   // 2) Abstand SL ↔ Entry
   double distance   = MathAbs(entryPrice - stopLossPrice);
   // 3) Risikobetrag (Konto × Risiko%)
   double riskAmount = AccountInfoDouble(ACCOUNT_BALANCE) * RiskPercent / 100.0;
   // 4) Punktwert für Symbol (TickValue/TickSize)
   double tickValue  = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE);
   double tickSize   = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
   double pointValue = tickValue / tickSize;
   // 5) Roh-Lot = Risiko / (Abstand × Punktwert)
   double lots       = riskAmount / (distance * pointValue);
   // 6) Auf minimale Lot-Schritte runden
   double step       = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
   double minLot     = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
   double maxLot     = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
   lots = MathFloor(lots / step) * step;
   if(lots < minLot) lots = minLot;
   if(lots > maxLot) lots = maxLot;
   return(lots);                                          // Fertige Lotgröße zurückgeben
  }

//+------------------------------------------------------------------+
//| OnTick: Hauptlogik bei jedem Tick                               |
//+------------------------------------------------------------------+
void OnTick()
  {
   // 1) Aktuelle Bid/Ask-Preise holen
   double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
   double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);

   // 2) Puffer für aktuelle und vorige Indikator-Werte
   double fastBuf[2], slowBuf[2], atrBuf[2];
   // 3) Daten aus den Handles kopieren
   if(CopyBuffer(fastHandle, 0, 0, 2, fastBuf) <= 0 ||
      CopyBuffer(slowHandle, 0, 0, 2, slowBuf) <= 0 ||
      CopyBuffer(atrHandle,  0, 0, 2, atrBuf)  <= 0)
     {
      Print("Fehler beim Kopieren der Indikator-Daten");
      return;                                             // Abbruch bei Fehler
     }

   // 4) Werte zuordnen: [0] = aktuell, [1] = vorher
   double fastEMA    = fastBuf[0];
   double fastEMAold = fastBuf[1];
   double slowEMA    = slowBuf[0];
   double slowEMAold = slowBuf[1];
   double atrNow     = atrBuf[0];
   double atrOld     = atrBuf[1];

   // 5) Prüfen, ob aktuell eine Position offen ist
   bool positionExists = PositionSelect(_Symbol);

   // —— Einstieg wenn keine Position offen —————————————————————————————
   if(!positionExists)
     {
      // Long, wenn EMA-Cross up + ATR steigend
      bool longSignal  = (fastEMAold < slowEMAold) && (fastEMA > slowEMA) && (atrNow > atrOld);
      // Short, wenn EMA-Cross down
      bool shortSignal = (fastEMAold > slowEMAold) && (fastEMA < slowEMA);

      if(longSignal)  // Long-Einstieg
        {
         double sl  = ask - 1.5 * atrNow;                                         // SL = Entry –1.5×ATR
         double tp  = ask + RewardRiskRatio * (ask - sl);                        // TP = Entry +2×Risiko
         double lot = CalculateLot(true, sl);                                    // Lot berechnen
         if(trade.Buy(lot, NULL, ask, sl, tp, CommentText))                      // Buy-Order senden
            Print("Long eröffnet bei ", ask, " SL=", sl, " TP=", tp);            
        }
      else if(shortSignal) // Short-Einstieg
        {
         double sl  = bid + 1.5 * atrNow;                                         // SL = Entry +1.5×ATR
         double tp  = bid - RewardRiskRatio * (sl - bid);                        // TP = Entry –2×Risiko
         double lot = CalculateLot(false, sl);                                   // Lot berechnen
         if(trade.Sell(lot, NULL, bid, sl, tp, CommentText))                     // Sell-Order senden
            Print("Short eröffnet bei ", bid, " SL=", sl, " TP=", tp);
        }
     }
   // —— Trailing-Stop wenn Position offen ———————————————————————————————
   else if(UseTrailingStop)
     {
      ulong ticket = PositionGetInteger(POSITION_TICKET);                        // Ticket der Position
      ENUM_POSITION_TYPE type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
      double newSL;
      if(type == POSITION_TYPE_BUY)                                             // Bei Long
         newSL = bid - atrNow;                                                   // SL = Bid –1×ATR
      else                                                                       // Bei Short
         newSL = ask + atrNow;                                                   // SL = Ask +1×ATR

      trade.PositionModify(ticket, newSL, 0);                                   // SL anpassen
     }
  }

//+------------------------------------------------------------------+
//| OnDeinit: Handles freigeben                                      |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   if(fastHandle != INVALID_HANDLE)  IndicatorRelease(fastHandle);             // Fast EMA freigeben
   if(slowHandle != INVALID_HANDLE)  IndicatorRelease(slowHandle);             // Slow EMA freigeben
   if(atrHandle  != INVALID_HANDLE)  IndicatorRelease(atrHandle);              // ATR freigeben
  }
//+------------------------------------------------------------------+
Nach oben scrollen