Manuale MQL5: Come evitare errori durante l'impostazione/modifica dei livelli di trading
Introduzione
In continuazione del nostro lavoro sull'Expert Advisor dal precedente articolo della serie chiamato "Manuale MQL5: Analizzando le proprietà della posizione nel tester di strategia MetaTrader 5", lo miglioreremo con un sacco di funzioni utili, oltre a migliorare e ottimizzare quelle esistenti.
Le domande dei principianti sugli errori che si verificano durante l'impostazione/modifica dei livelli di trading (Stop Loss, Take Profit e ordini in sospeso) sono tutt'altro che rare nei forum di programmazione MQL. Credo che molti di voi debbano già avere familiarità con il messaggio del diario che termina con [Arresti non validi]. In questo articolo, creeremo funzioni che normalizzano e controllano la correttezza dei valori del livello di trading prima di aprire/modificare una posizione.
L'Expert Advisor questa volta avrà parametri esterni che possono essere ottimizzati nel MetaTrader 5 Strategy Tester e in qualche modo assomiglierà a un semplice sistema di trading. Abbiamo sicuramente ancora molta strada da fare prima di poter sviluppare un vero sistema di trading. Ma Roma non è stata costruita in un giorno. Quindi abbiamo ancora molto da fare.
L'ottimizzazione del codice nelle funzioni esistenti sarà considerata man mano che l'articolo si sviluppa. Il pannello delle informazioni non verrà trattato a questo punto poiché dobbiamo ancora dare un'occhiata ad alcune proprietà di posizione che non possono essere ottenute utilizzando identificatori standard (è richiesto l'uso della cronologia delle offerte). Questo argomento sarà comunque trattato in uno dei seguenti articoli della serie.
Sviluppo di consulenti esperti
Cominciamo. Come al solito, iniziamo inserendo enumerazioni aggiuntive, variabili, array e funzioni ausiliarie all'inizio del file. Avremo bisogno di una funzione che ci permetta di ottenere facilmente le proprietà dei simboli. Lo stesso semplice approccio sarà necessario per ottenere le proprietà di posizione.
Abbiamo visto negli articoli precedenti che alle variabili globali sono state assegnate tutte le proprietà di posizione contemporaneamente nella funzione GetPositionProperties. Questa volta, cercheremo di fornire la possibilità di ottenere ciascuna proprietà separatamente. Di seguito sono riportate due enumerazioni per l'implementazione di quanto sopra. Le funzioni stesse saranno riviste un po' più tardi.
//--- Enumeration of position properties enum ENUM_POSITION_PROPERTIES { P_SYMBOL = 0, P_MAGIC = 1, P_COMMENT = 2, P_SWAP = 3, P_COMMISSION = 4, P_PRICE_OPEN = 5, P_PRICE_CURRENT = 6, P_PROFIT = 7, P_VOLUME = 8, P_SL = 9, P_TP = 10, P_TIME = 11, P_ID = 12, P_TYPE = 13, P_ALL = 14 }; //--- Enumeration of symbol properties enum ENUM_SYMBOL_PROPERTIES { S_DIGITS = 0, S_SPREAD = 1, S_STOPSLEVEL = 2, S_POINT = 3, S_ASK = 4, S_BID = 5, S_VOLUME_MIN = 6, S_VOLUME_MAX = 7, S_VOLUME_LIMIT = 8, S_VOLUME_STEP = 9, S_FILTER = 10, S_UP_LEVEL = 11, S_DOWN_LEVEL = 12, S_ALL = 13 };
L'enumerazione ENUM_SYMBOL_PROPERTIES non contiene tutte le proprietà dei simboli ma possono essere aggiunte in qualsiasi momento, se necessario. L'enumerazione contiene anche proprietà definite dall'utente (10, 11, 12) il cui calcolo si basa su altre proprietà del simbolo. Esiste un identificatore che può essere utilizzato per ottenere tutte le proprietà dall'enumerazione contemporaneamente, come nell'enumerazione delle proprietà di posizione.
Seguono i parametri esterni dell'Expert Advisor:
//--- External parameters of the Expert Advisor input int NumberOfBars=2; // Number of Bullish/Bearish bars for a Buy/Sell input double Lot =0.1; // Lot input double StopLoss =50; // Stop Loss input double TakeProfit =100; // Take Profit input double TrailingStop=10; // Trailing Stop input bool Reverse =true; // Position reverse
Diamo un'occhiata più da vicino ai parametri esterni:
- NumberOfBars - questo parametro imposta il numero di barre a una direzione per l'apertura di una posizione;
- Lot - Volume della posizione
- TakeProfit - Livello Take Profit in punti. Il valore zero significa che non è necessario impostare alcun Take Profit.
- StopLoss - Livello di Stop Loss in punti. Il valore zero significa che non è necessario impostare alcuno Stop Loss.
- TrailingStop - Valore Trailing Stop in punti. Per una posizione BUY, il calcolo si basa sul minimo della barra (minimo meno il numero di punti dal parametro StopLoss). Per una posizione SELL, il calcolo si basa sul massimo della barra (massimo più il numero di punti dal parametro StopLoss). Il valore zero indica che il Trailing Stop è disattivato.
- Reverse abilita/disabilita la posizione di inversione.
È solo il parametro NumberOfBars che necessita di ulteriori chiarimenti. Non ha senso impostare questo valore di parametro, ad esempio, su più di 5 poiché è piuttosto raro e sarebbe già tardi per aprire una posizione dopo tale movimento. Avremo quindi bisogno di una variabile che ci aiuti a regolare il valore di questo parametro:
//--- To check the value of the NumberOfBars external parameter int AllowedNumberOfBars=0;
Questo parametro determinerà anche la quantità di dati della barra che verranno archiviati negli array dei prezzi. Questo sarà discusso tra un po' quando arriveremo a modificare le funzioni personalizzate.
Come nel caso delle proprietà di posizione, dichiariamo variabili nell'ambito globale per le proprietà dei simboli al fine di fornire l'accesso da qualsiasi funzione:
//--- Symbol properties int sym_digits=0; // Number of decimal places int sym_spread=0; // Spread in points int sym_stops_level=0; // Stops level double sym_point=0.0; // Point value double sym_ask=0.0; // Ask price double sym_bid=0.0; // Bid price double sym_volume_min=0.0; // Minimum volume for a deal double sym_volume_max=0.0; // Maximum volume for a deal double sym_volume_limit=0.0; // Maximum permissible volume for a position and orders in one direction double sym_volume_step=0.0; // Minimum volume change step for a deal double sym_offset=0.0; // Offset from the maximum possible price for a transaction double sym_up_level=0.0; // Upper Stop level price double sym_down_level=0.0; // Lower Stop level price
Poiché il Trailing Stop deve essere calcolato in base al massimo e al minimo della barra, avremo bisogno di array per tali dati della barra:
//--- Price data arrays double close_price[]; // Close (closing prices of the bar) double open_price[]; // Open (opening prices of the bar) double high_price[]; // High (bar's highs) double low_price[]; // Low (bar's lows)
Passiamo ora alla modifica e alla creazione delle funzioni. Abbiamo già la funzione GetBarsData che copia i prezzi di apertura e chiusura delle barre in array di prezzi. Ora, abbiamo anche bisogno di alti e bassi. Inoltre, il valore ottenuto dal parametro NumberOfBars dovrebbe essere modificato. Ecco come appare la funzione dopo la modifica:
//+------------------------------------------------------------------+ //| Getting bar values | //+------------------------------------------------------------------+ void GetBarsData() { //--- Adjust the number of bars for the position opening condition if(NumberOfBars<=1) AllowedNumberOfBars=2; // At least two bars are required if(NumberOfBars>=5) AllowedNumberOfBars=5; // but no more than 5 else AllowedNumberOfBars=NumberOfBars+1; // and always more by one //--- Reverse the indexing order (... 3 2 1 0) ArraySetAsSeries(close_price,true); ArraySetAsSeries(open_price,true); ArraySetAsSeries(high_price,true); ArraySetAsSeries(low_price,true); //--- Get the closing price of the bar // If the number of the obtained values is less than requested, print the relevant message if(CopyClose(_Symbol,Period(),0,AllowedNumberOfBars,close_price)<AllowedNumberOfBars) { Print("Failed to copy the values (" +_Symbol+", "+TimeframeToString(Period())+") to the Close price array! " "Error "+IntegerToString(GetLastError())+": "+ErrorDescription(GetLastError())); } //--- Get the opening price of the bar // If the number of the obtained values is less than requested, print the relevant message if(CopyOpen(_Symbol,Period(),0,AllowedNumberOfBars,open_price)<AllowedNumberOfBars) { Print("Failed to copy the values (" +_Symbol+", "+TimeframeToString(Period())+") to the Open price array! " "Error "+IntegerToString(GetLastError())+": "+ErrorDescription(GetLastError())); } //--- Get the bar's high // If the number of the obtained values is less than requested, print the relevant message if(CopyHigh(_Symbol,Period(),0,AllowedNumberOfBars,high_price)<AllowedNumberOfBars) { Print("Failed to copy the values (" +_Symbol+", "+TimeframeToString(Period())+") to the High price array! " "Error "+IntegerToString(GetLastError())+": "+ErrorDescription(GetLastError())); } //--- Get the bar's low // If the number of the obtained values is less than requested, print the relevant message if(CopyLow(_Symbol,Period(),0,AllowedNumberOfBars,low_price)<AllowedNumberOfBars) { Print("Failed to copy the values (" +_Symbol+", "+TimeframeToString(Period())+") to the Low price array! " "Error "+IntegerToString(GetLastError())+": "+ErrorDescription(GetLastError())); } }
Le condizioni che richiedono almeno due battute e sempre più di una ci sono perché andremo solo per battute completate che iniziano con indice [1]. Infatti, le regolazioni in questo caso possono essere considerate non necessarie in quanto i dati della barra possono essere copiati a partire dall'indice specificato nel terzo parametro delle funzioni CopyOpen, CopyClose, CopyHigh e CopyLow. Il limite di 5 barre può anche essere modificato (alto/basso) a propria discrezione.
La funzione GetTradingSignal è ora diventata un po' più complessa poiché la condizione verrà generata in modo diverso a seconda del numero di barre specificato nel parametro NumberOfBars. Inoltre, ora utilizziamo un tipo più corretto del valore restituito - tipo di ordine:
//+------------------------------------------------------------------+ //| Determining trading signals | //+------------------------------------------------------------------+ ENUM_ORDER_TYPE GetTradingSignal() { //--- A Buy signal (ORDER_TYPE_BUY) : if(AllowedNumberOfBars==2 && close_price[1]>open_price[1]) return(ORDER_TYPE_BUY); if(AllowedNumberOfBars==3 && close_price[1]>open_price[1] && close_price[2]>open_price[2]) return(ORDER_TYPE_BUY); if(AllowedNumberOfBars==4 && close_price[1]>open_price[1] && close_price[2]>open_price[2] && close_price[3]>open_price[3]) return(ORDER_TYPE_BUY); if(AllowedNumberOfBars==5 && close_price[1]>open_price[1] && close_price[2]>open_price[2] && close_price[3]>open_price[3] && close_price[4]>open_price[4]) return(ORDER_TYPE_BUY); if(AllowedNumberOfBars>=6 && close_price[1]>open_price[1] && close_price[2]>open_price[2] && close_price[3]>open_price[3] && close_price[4]>open_price[4] && close_price[5]>open_price[5]) return(ORDER_TYPE_BUY); //--- A Sell signal (ORDER_TYPE_SELL) : if(AllowedNumberOfBars==2 && close_price[1]<open_price[1]) return(ORDER_TYPE_SELL); if(AllowedNumberOfBars==3 && close_price[1]<open_price[1] && close_price[2]<open_price[2]) return(ORDER_TYPE_SELL); if(AllowedNumberOfBars==4 && close_price[1]<open_price[1] && close_price[2]<open_price[2] && close_price[3]<open_price[3]) return(ORDER_TYPE_SELL); if(AllowedNumberOfBars==5 && close_price[1]<open_price[1] && close_price[2]<open_price[2] && close_price[3]<open_price[3] && close_price[4]<open_price[4]) return(ORDER_TYPE_SELL); if(AllowedNumberOfBars>=6 && close_price[1]<open_price[1] && close_price[2]<open_price[2] && close_price[3]<open_price[3] && close_price[4]<open_price[4] && close_price[5]<open_price[5]) return(ORDER_TYPE_SELL); //--- No signal (WRONG_VALUE): return(WRONG_VALUE); }
Modifichiamo ora la funzione GetPositionProperties. Negli articoli precedenti, ci ha permesso di ottenere tutte le proprietà contemporaneamente. Tuttavia, a volte potrebbe essere necessario ottenere solo una proprietà. Per fare ciò, puoi certamente utilizzare le funzioni standard offerte dal linguaggio, ma questo non sarebbe conveniente come vorremmo. Di seguito è riportato il codice della funzione GetPositionProperties modificato. Ora, quando si passa un determinato identificatore dall'enumerazione ENUM_POSITION_PROPERTIES, è possibile ottenere una determinata proprietà di posizione singola o tutte le proprietà contemporaneamente.
//+------------------------------------------------------------------+ //| Getting position properties | //+------------------------------------------------------------------+ void GetPositionProperties(ENUM_POSITION_PROPERTIES position_property) { //--- Check if there is an open position pos_open=PositionSelect(_Symbol); //--- If an open position exists, get its properties if(pos_open) { switch(position_property) { case P_SYMBOL : pos_symbol=PositionGetString(POSITION_SYMBOL); break; case P_MAGIC : pos_magic=PositionGetInteger(POSITION_MAGIC); break; case P_COMMENT : pos_comment=PositionGetString(POSITION_COMMENT); break; case P_SWAP : pos_swap=PositionGetDouble(POSITION_SWAP); break; case P_COMMISSION : pos_commission=PositionGetDouble(POSITION_COMMISSION); break; case P_PRICE_OPEN : pos_price=PositionGetDouble(POSITION_PRICE_OPEN); break; case P_PRICE_CURRENT : pos_cprice=PositionGetDouble(POSITION_PRICE_CURRENT); break; case P_PROFIT : pos_profit=PositionGetDouble(POSITION_PROFIT); break; case P_VOLUME : pos_volume=PositionGetDouble(POSITION_VOLUME); break; case P_SL : pos_sl=PositionGetDouble(POSITION_SL); break; case P_TP : pos_tp=PositionGetDouble(POSITION_TP); break; case P_TIME : pos_time=(datetime)PositionGetInteger(POSITION_TIME); break; case P_ID : pos_id=PositionGetInteger(POSITION_IDENTIFIER); break; case P_TYPE : pos_type=(ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); break; case P_ALL : pos_symbol=PositionGetString(POSITION_SYMBOL); pos_magic=PositionGetInteger(POSITION_MAGIC); pos_comment=PositionGetString(POSITION_COMMENT); pos_swap=PositionGetDouble(POSITION_SWAP); pos_commission=PositionGetDouble(POSITION_COMMISSION); pos_price=PositionGetDouble(POSITION_PRICE_OPEN); pos_cprice=PositionGetDouble(POSITION_PRICE_CURRENT); pos_profit=PositionGetDouble(POSITION_PROFIT); pos_volume=PositionGetDouble(POSITION_VOLUME); pos_sl=PositionGetDouble(POSITION_SL); pos_tp=PositionGetDouble(POSITION_TP); pos_time=(datetime)PositionGetInteger(POSITION_TIME); pos_id=PositionGetInteger(POSITION_IDENTIFIER); pos_type=(ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); break; default: Print("The passed position property is not listed in the enumeration!"); return; } } //--- If there is no open position, zero out variables for position properties else ZeroPositionProperties(); }
Allo stesso modo, implementiamo la funzione GetSymbolProperties per ottenere le proprietà dei simboli:
//+------------------------------------------------------------------+ //| Getting symbol properties | //+------------------------------------------------------------------+ void GetSymbolProperties(ENUM_SYMBOL_PROPERTIES symbol_property) { int lot_offset=1; // Number of points for the offset from the Stops level //--- switch(symbol_property) { case S_DIGITS : sym_digits=(int)SymbolInfoInteger(_Symbol,SYMBOL_DIGITS); break; case S_SPREAD : sym_spread=(int)SymbolInfoInteger(_Symbol,SYMBOL_SPREAD); break; case S_STOPSLEVEL : sym_stops_level=(int)SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL); break; case S_POINT : sym_point=SymbolInfoDouble(_Symbol,SYMBOL_POINT); break; //--- case S_ASK : sym_digits=(int)SymbolInfoInteger(_Symbol,SYMBOL_DIGITS); sym_ask=NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),sym_digits); break; case S_BID : sym_digits=(int)SymbolInfoInteger(_Symbol,SYMBOL_DIGITS); sym_bid=NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),sym_digits); break; //--- case S_VOLUME_MIN : sym_volume_min=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MIN); break; case S_VOLUME_MAX : sym_volume_max=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MAX); break; case S_VOLUME_LIMIT : sym_volume_limit=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_LIMIT); break; case S_VOLUME_STEP : sym_volume_step=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_STEP); break; //--- case S_FILTER : sym_digits=(int)SymbolInfoInteger(_Symbol,SYMBOL_DIGITS); sym_point=SymbolInfoDouble(_Symbol,SYMBOL_POINT); sym_offset=NormalizeDouble(CorrectValueBySymbolDigits(lot_offset*sym_point),sym_digits); break; //--- case S_UP_LEVEL : sym_digits=(int)SymbolInfoInteger(_Symbol,SYMBOL_DIGITS); sym_stops_level=(int)SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL); sym_point=SymbolInfoDouble(_Symbol,SYMBOL_POINT); sym_ask=NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),sym_digits); sym_up_level=NormalizeDouble(sym_ask+sym_stops_level*sym_point,sym_digits); break; //--- case S_DOWN_LEVEL : sym_digits=(int)SymbolInfoInteger(_Symbol,SYMBOL_DIGITS); sym_stops_level=(int)SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL); sym_point=SymbolInfoDouble(_Symbol,SYMBOL_POINT); sym_bid=NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),sym_digits); sym_down_level=NormalizeDouble(sym_bid-sym_stops_level*sym_point,sym_digits); break; //--- case S_ALL : sym_digits=(int)SymbolInfoInteger(_Symbol,SYMBOL_DIGITS); sym_spread=(int)SymbolInfoInteger(_Symbol,SYMBOL_SPREAD); sym_stops_level=(int)SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL); sym_point=SymbolInfoDouble(_Symbol,SYMBOL_POINT); sym_ask=NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),sym_digits); sym_bid=NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),sym_digits); sym_volume_min=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MIN); sym_volume_max=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MAX); sym_volume_limit=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_LIMIT); sym_volume_step=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_STEP); sym_offset=NormalizeDouble(CorrectValueBySymbolDigits(lot_offset*sym_point),sym_digits); sym_up_level=NormalizeDouble(sym_ask+sym_stops_level*sym_point,sym_digits); sym_down_level=NormalizeDouble(sym_bid-sym_stops_level*sym_point,sym_digits); break; //--- default: Print("The passed symbol property is not listed in the enumeration!"); return; } }
Tieni presente che alcune proprietà dei simboli potrebbero richiedere di ottenere prima altre proprietà.
Abbiamo una nuova funzione, CorrectValueBySymbolDigits. Restituisce il valore rilevante, a seconda del numero di cifre decimali nel prezzo. Alla funzione può essere passato un numero intero o un numero reale. Il tipo dei dati passati determina la versione della funzione da utilizzare. Questa funzione è chiamata sovraccarico della funzione.
//+------------------------------------------------------------------+ //| Adjusting the value based on the number of digits in the price (int)| //+------------------------------------------------------------------+ int CorrectValueBySymbolDigits(int value) { return (sym_digits==3 || sym_digits==5) ? value*=10 : value; } //+------------------------------------------------------------------+ //| Adjusting the value based on the number of digits in the price (double)| //+------------------------------------------------------------------+ double CorrectValueBySymbolDigits(double value) { return (sym_digits==3 || sym_digits==5) ? value*=10 : value; }
Il nostro Expert Advisor avrà un parametro esterno per specificare il volume (Lot) della posizione di apertura. Creiamo una funzione che regolerà il lotto in base alla specifica del simbolo - CalculateLot:
//+------------------------------------------------------------------+ //| Calculating position lot | //+------------------------------------------------------------------+ double CalculateLot(double lot) { //--- To adjust as per the step double corrected_lot=0.0; //--- GetSymbolProperties(S_VOLUME_MIN); // Get the minimum possible lot GetSymbolProperties(S_VOLUME_MAX); // Get the maximum possible lot GetSymbolProperties(S_VOLUME_STEP); // Get the lot increase/decrease step //--- Adjust as per the lot step corrected_lot=MathRound(lot/sym_volume_step)*sym_volume_step; //--- If less than the minimum, return the minimum if(corrected_lot<sym_volume_min) return(NormalizeDouble(sym_volume_min,2)); //--- If greater than the maximum, return the maximum if(corrected_lot>sym_volume_max) return(NormalizeDouble(sym_volume_max,2)); //--- return(NormalizeDouble(corrected_lot,2)); }
Passiamo ora alle funzioni direttamente attinenti al titolo dell'articolo. Sono abbastanza semplici e diretti e puoi capire il loro scopo senza alcuna difficoltà usando i commenti nel codice.
La funzione CalculateTakeProfit viene utilizzata per calcolare il valore Take Profit:
//+------------------------------------------------------------------+ //| Calculating the Take Profit value | //+------------------------------------------------------------------+ double CalculateTakeProfit(ENUM_ORDER_TYPE order_type) { //--- If Take Profit is required if(TakeProfit>0) { //--- For the calculated Take Profit value double tp=0.0; //--- If you need to calculate the value for a SELL position if(order_type==ORDER_TYPE_SELL) { //--- Calculate the level tp=NormalizeDouble(sym_bid-CorrectValueBySymbolDigits(TakeProfit*sym_point),sym_digits); //--- Return the calculated value if it is lower than the lower limit of the Stops level // If the value is higher or equal, return the adjusted value return(tp<sym_down_level ? tp : sym_down_level-sym_offset); } //--- If you need to calculate the value for a BUY position if(order_type==ORDER_TYPE_BUY) { //--- Calculate the level tp=NormalizeDouble(sym_ask+CorrectValueBySymbolDigits(TakeProfit*sym_point),sym_digits); //--- Return the calculated value if it is higher that the upper limit of the Stops level // If the value is lower or equal, return the adjusted value return(tp>sym_up_level ? tp : sym_up_level+sym_offset); } } //--- return(0.0); }
La funzione CalculateStopLoss viene utilizzata per calcolare il valore di Stop Loss:
//+------------------------------------------------------------------+ //| Calculating the Stop Loss value | //+------------------------------------------------------------------+ double CalculateStopLoss(ENUM_ORDER_TYPE order_type) { //--- If Stop Loss is required if(StopLoss>0) { //--- For the calculated Stop Loss value double sl=0.0; //--- If you need to calculate the value for a BUY position if(order_type==ORDER_TYPE_BUY) { // Calculate the level sl=NormalizeDouble(sym_ask-CorrectValueBySymbolDigits(StopLoss*sym_point),sym_digits); //--- Return the calculated value if it is lower that the lower limit of the Stops level // If the value is higher or equal, return the adjusted value return(sl<sym_down_level ? sl : sym_down_level-sym_offset); } //--- If you need to calculate the value for a SELL position if(order_type==ORDER_TYPE_SELL) { //--- Calculate the level sl=NormalizeDouble(sym_bid+CorrectValueBySymbolDigits(StopLoss*sym_point),sym_digits); //--- Return the calculated value if it is higher than the upper limit of the Stops level // If the value is lower or equal, return the adjusted value return(sl>sym_up_level ? sl : sym_up_level+sym_offset); } } //--- return(0.0); }
La funzione CalculateTrailingStop viene utilizzata per calcolare il valore del Trailing Stop:
//+------------------------------------------------------------------+ //| Calculating the Trailing Stop value | //+------------------------------------------------------------------+ double CalculateTrailingStop(ENUM_POSITION_TYPE position_type) { //--- Variables for calculations double level =0.0; double buy_point =low_price[1]; // The Low value for a Buy double sell_point =high_price[1]; // The High value for a Sell //--- Calculate the level for a BUY position if(position_type==POSITION_TYPE_BUY) { //--- Bar's low minus the specified number of points level=NormalizeDouble(buy_point-CorrectValueBySymbolDigits(StopLoss*sym_point),sym_digits); //--- If the calculated level is lower than the lower limit of the Stops level, // the calculation is complete, return the current value of the level if(level<sym_down_level) return(level); //--- If it is not lower, try to calculate based on the bid price else { level=NormalizeDouble(sym_bid-CorrectValueBySymbolDigits(StopLoss*sym_point),sym_digits); //--- If the calculated level is lower than the limit, return the current value of the level // otherwise set the nearest possible value return(level<sym_down_level ? level : sym_down_level-sym_offset); } } //--- Calculate the level for a SELL position if(position_type==POSITION_TYPE_SELL) { // Bar's high plus the specified number of points level=NormalizeDouble(sell_point+CorrectValueBySymbolDigits(StopLoss*sym_point),sym_digits); //--- If the calculated level is higher than the upper limit of the Stops level, // the calculation is complete, return the current value of the level if(level>sym_up_level) return(level); //--- If it is not higher, try to calculate based on the ask price else { level=NormalizeDouble(sym_ask+CorrectValueBySymbolDigits(StopLoss*sym_point),sym_digits); //--- If the calculated level is higher than the limit, return the current value of the level // Otherwise set the nearest possible value return(level>sym_up_level ? level : sym_up_level+sym_offset); } } //--- return(0.0); }
Ora abbiamo tutte le funzioni necessarie che restituiscono valori corretti per le operazioni di trading. Creiamo una funzione che verificherà una condizione per la modifica del Trailing Stop e modificherà la stessa, se la condizione specificata è soddisfatta - ModifyTrailingStop. Di seguito è riportato il codice di questa funzione con commenti dettagliati.
Si prega di prestare attenzione all'utilizzo di tutte le funzioni create/modificate sopra. L'interruttore determina la condizione rilevante in base al tipo della posizione corrente e il risultato della condizione viene quindi memorizzato nella variabile di condizione. Per modificare una posizione, utilizziamo il metodo PositionModify dalla classCTrade della Standard Library.
//+------------------------------------------------------------------+ //| Modifying the Trailing Stop level | //+------------------------------------------------------------------+ void ModifyTrailingStop() { //--- If the Trailing Stop and Stop Loss are set if(TrailingStop>0 && StopLoss>0) { double new_sl=0.0; // For calculating the new Stop Loss level bool condition=false; // For checking the modification condition //--- Get the flag of presence/absence of the position pos_open=PositionSelect(_Symbol); //--- If the position exists if(pos_open) { //--- Get the symbol properties GetSymbolProperties(S_ALL); //--- Get the position properties GetPositionProperties(P_ALL); //--- Get the Stop Loss level new_sl=CalculateTrailingStop(pos_type); //--- Depending on the position type, check the relevant condition for the Trailing Stop modification switch(pos_type) { case POSITION_TYPE_BUY : //--- If the new Stop Loss value is higher // than the current value plus the set step condition=new_sl>pos_sl+CorrectValueBySymbolDigits(TrailingStop*sym_point); break; case POSITION_TYPE_SELL : //--- If the new Stop Loss value is lower // than the current value minus the set step condition=new_sl<pos_sl-CorrectValueBySymbolDigits(TrailingStop*sym_point); break; } //--- If there is a Stop Loss, compare the values before modification if(pos_sl>0) { //--- If the condition for the order modification is met, i.e. the new value is lower/higher // than the current one, modify the Trailing Stop of the position if(condition) { if(!trade.PositionModify(_Symbol,new_sl,pos_tp)) Print("Error modifying the position: ",GetLastError()," - ",ErrorDescription(GetLastError())); } } //--- If there is no Stop Loss, simply set it if(pos_sl==0) { if(!trade.PositionModify(_Symbol,new_sl,pos_tp)) Print("Error modifying the position: ",GetLastError()," - ",ErrorDescription(GetLastError())); } } } }
Ora regoliamo la funzione TradingBlock in base a tutte le modifiche di cui sopra. Proprio come nella funzione ModifyTrailingStop, tutti i valori delle variabili per un ordine di trading saranno determinati utilizzando l'interruttore. Riduce notevolmente la quantità di codice e semplifica ulteriori modifiche poiché invece di un ramo per due tipi di posizione, ne rimane solo uno.
//+------------------------------------------------------------------+ //| Trading block | //+------------------------------------------------------------------+ void TradingBlock() { ENUM_ORDER_TYPE signal=WRONG_VALUE; // Variable for getting a signal string comment="hello :)"; // Position comment double tp=0.0; // Take Profit double sl=0.0; // Stop Loss double lot=0.0; // Volume for position calculation in case of reverse position double position_open_price=0.0; // Position opening price ENUM_ORDER_TYPE order_type=WRONG_VALUE; // Order type for opening a position ENUM_POSITION_TYPE opposite_position_type=WRONG_VALUE; // Opposite position type //--- Get a signal signal=GetTradingSignal(); //--- If there is no signal, exit if(signal==WRONG_VALUE) return; //--- Find out if there is a position pos_open=PositionSelect(_Symbol); //--- Get all symbol properties GetSymbolProperties(S_ALL); //--- Determine values for trade variables switch(signal) { //--- Assign values to variables for a BUY case ORDER_TYPE_BUY : position_open_price=sym_ask; order_type=ORDER_TYPE_BUY; opposite_position_type=POSITION_TYPE_SELL; break; //--- Assign values to variables for a SELL case ORDER_TYPE_SELL : position_open_price=sym_bid; order_type=ORDER_TYPE_SELL; opposite_position_type=POSITION_TYPE_BUY; break; } //--- Calculate the Take Profit and Stop Loss levels sl=CalculateStopLoss(order_type); tp=CalculateTakeProfit(order_type); //--- If there is no position if(!pos_open) { //--- Adjust the volume lot=CalculateLot(Lot); //--- Open a position // If the position failed to open, print the relevant message if(!trade.PositionOpen(_Symbol,order_type,lot,position_open_price,sl,tp,comment)) { Print("Error opening the position: ",GetLastError()," - ",ErrorDescription(GetLastError())); } } //--- If there is a position else { //--- Get the position type GetPositionProperties(P_TYPE); //--- If the position is opposite to the signal and the position reverse is enabled if(pos_type==opposite_position_type && Reverse) { //--- Get the position volume GetPositionProperties(P_VOLUME); //--- Adjust the volume lot=pos_volume+CalculateLot(Lot); //--- Open the position. If the position failed to open, print the relevant message if(!trade.PositionOpen(_Symbol,order_type,lot,position_open_price,sl,tp,comment)) { Print("Error opening the position: ",GetLastError()," - ",ErrorDescription(GetLastError())); } } } //--- return; }
Dobbiamo anche fare un'altra importante correzione nella funzione SetInfoPanel ma prepariamo prima alcune funzioni ausiliarie che indicano come/dove viene attualmente utilizzato il programma:
//+------------------------------------------------------------------+ //| Returning the testing flag | //+------------------------------------------------------------------+ bool IsTester() { return(MQL5InfoInteger(MQL5_TESTER)); } //+------------------------------------------------------------------+ //| Returning the optimization flag | //+------------------------------------------------------------------+ bool IsOptimization() { return(MQL5InfoInteger(MQL5_OPTIMIZATION)); } //+------------------------------------------------------------------+ //| Returning the visual testing mode flag | //+------------------------------------------------------------------+ bool IsVisualMode() { return(MQL5InfoInteger(MQL5_VISUAL_MODE)); } //+------------------------------------------------------------------+ //| Returning the flag for real time mode outside the Strategy Tester| //| if all conditions are met | //+------------------------------------------------------------------+ bool IsRealtime() { if(!IsTester() && !IsOptimization() && !IsVisualMode()) return(true); else return(false); }
L'unica cosa che dobbiamo aggiungere alla funzione SetInfoPanel è una condizione che indica al programma che il pannello informativo deve essere visualizzato solo nelle modalità di visualizzazione e in tempo reale. Se questo viene ignorato, il tempo di test sarà 4-5 volte più lungo. Ciò è particolarmente importante quando si ottimizzano i parametri.
//+------------------------------------------------------------------+ //| Setting the info panel | //|------------------------------------------------------------------+ void SetInfoPanel() { //--- Visualization or real time modes if(IsVisualMode() || IsRealtime()) { // The remaining code of the SetInfoPanel() function // ... } }
Ora, abbiamo solo bisogno di apportare alcune modifiche alle principali funzioni del programma per poter procedere all'ottimizzazione dei parametri e al test di Expert Advisor.
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Initialize the new bar CheckNewBar(); //--- Get the properties and set the panel GetPositionProperties(P_ALL); //--- Set the info panel SetInfoPanel(); //--- return(0); }
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- If the bar is not new, exit if(!CheckNewBar()) return; //--- If there is a new bar else { GetBarsData(); // Get bar data TradingBlock(); // Check the conditions and trade ModifyTrailingStop(); // Modify the Trailing Stop level } //--- Get the properties and update the values on the panel GetPositionProperties(P_ALL); //--- Update the info panel SetInfoPanel(); }
//+------------------------------------------------------------------+ //| Trade event | //+------------------------------------------------------------------+ void OnTrade() { //--- Get position properties and update the values on the panel GetPositionProperties(P_ALL); //--- Update the info panel SetInfoPanel(); }
Ottimizzazione dei parametri e test Expert Advisor
Ora ottimizziamo i parametri. Effettueremo le impostazioni di Strategy Tester come mostrato di seguito:
Fig. 1. Impostazioni di Strategy Tester per l'ottimizzazione dei parametri.
Ai parametri dell'Expert Advisor verrà assegnata una vasta gamma di valori:
Fig. 2. Impostazioni Expert Advisor per l'ottimizzazione dei parametri.
L'ottimizzazione ha richiesto circa 7 minuti su un processore dual-core (Intel Core2 Duo P7350 @ 2.00GHz). I risultati del test del fattore di recupero massimo sono i seguenti:
Fig. 3. Risultati del test del fattore di recupero massimo.
Conclusione
Questo è tutto per ora. Studia, testa, ottimizza, sperimenta e stupisci. Il codice sorgente dell'Expert Advisor presente nell'articolo può essere scaricato utilizzando il collegamento sottostante per ulteriori approfondimenti.
Tradotto dal russo da MetaQuotes Ltd.
Articolo originale: https://www.mql5.com/ru/articles/643
- App di trading gratuite
- Oltre 8.000 segnali per il copy trading
- Notizie economiche per esplorare i mercati finanziari
Accetti la politica del sito e le condizioni d’uso