Verständnis von Programmierparadigmen (Teil 1): Ein verfahrenstechnischer Ansatz für die Entwicklung eines Price Action Expert Advisors
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:
- Prozedurale Programmierung: Dieser Artikel wird sich mit diesem Paradigma befassen.
- Funktionale Programmierung: Dieses Paradigma wird auch in diesem Artikel behandelt, da es der prozeduralen Programmierung sehr ähnlich ist.
- Objektorientierte Programmierung (OOP): Dieses Paradigma wird im nächsten Artikel erörtert.
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:
- 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. - 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. - 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. - 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. - 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. - 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. - 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. - 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:
- 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. - 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. - 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. - 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. - 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.
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.
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ü.
Schritt 2: Wählen Sie die Option „Expert Advisor (Vorlage)“ und klicken Sie auf „Weiter“.
Schritt 3: Geben Sie im Abschnitt „Allgemeine Eigenschaften“ den Namen des Expertenberaters ein und klicken Sie auf „Weiter“.
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“.
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.
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
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.
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
- Freie Handelsapplikationen
- Über 8.000 Signale zum Kopieren
- Wirtschaftsnachrichten für die Lage an den Finanzmärkte
Sie stimmen der Website-Richtlinie und den Nutzungsbedingungen zu.