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:
- Indikatoren erzeugt und ausliest
- Signale (Crossover und Volatilitätsfilter) erkennt
- Risikomanagement per Stop-Loss, Take-Profit und Lot-Berechnung umsetzt
- Orders platziert und überwacht
- 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
- 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.
- iMA() / iATR() liefern in
- 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.
- 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)
undSYMBOL_TRADE_TICK_SIZE
mit ein, um den Geldwert pro Punkt korrekt zu berücksichtigen.
- Stop-Loss wird auf 1,5 × ATR festgelegt:
- 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.
- Mit
- 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.
- Sobald eine Position offen ist und
- Aufräumen in OnDeinit()
- Alle erzeugten Indikator-Handles (
fastHandle
,slowHandle
,atrHandle
) werden mitIndicatorRelease(handle)
wieder freigegeben, um Ressourcen korrekt zu bereinigen.
- Alle erzeugten Indikator-Handles (
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
}
//+------------------------------------------------------------------+