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. 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 steigende Volatilität zeigt. 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 mit Chance/Risiko von 1:2: TP-Abstand = 2 × SL-Abstand In CalculateLot() wird ermittelt, wie viele Lots so platziert werden können, dass beim Erreichen des SL maximal RiskPercent % des Kontos verloren gehen. Dabei fließen SymbolInfoDouble(..., SYMBOL_TRADE_TICK_VALUE) und SYMBOL_TRADE_TICK_SIZE ein, um den Geldwert pro Punkt korrekt zu berücksichtigen. 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. Trailing-Stop (optional) Sobald eine Position offen ist und UseTrailingStop == true, verschiebt der EA bei jedem Tick den Stop-Loss um 1 × ATR hinter den aktuellen Marktpreis: Long: neuer SL = Bid – ATR Short: neuer SL = Ask + ATR Ermöglicht eine dynamische Absicherung bereits erzielter Gewinne. Aufräumen in OnDeinit() Alle erzeugten Indikator-Handles (fastHandle, slowHandle, atrHandle) werden mit IndicatorRelease(handle) 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 } //+------------------------------------------------------------------+