Guia prático do MQL5: Como Evitar Erros Quando Configurando/Modificando Níveis de Negociação
Introdução
Em continuação do nosso trabalho no Consultor Especialista do artigo anterior da série chamado "Guia prático: Analisando propriedades de posição no testador de estratégias do MetaTrader 5", o melhoraremos com um punhado de funções úteis, assim como aprimorar e otimizar as já existentes.
Perguntas de iniciantes sobre erros que aparecem quando configurando/modificando níveis de negociação (Parar Perdas, Obter Lucros ou ordens pendentes) estão longe de serem raras nos fóruns de programação MQL. Acredito que muitos de vocês devem já estar familiarizados com a mensagem do diário terminando com [Paradas invalidas]. Neste artigo, criaremos funções que normalizam e verificam valores de nível de negociação para exatidão antes de abrir/modificar uma posição.
O Consultor Especialista terá neste momento parâmetros externos que podem ser otimizados no Testador de Estratégias MetaTrader 5 e, em algumas formas, se parecerá com um simples sistema de transações. Nós certamente ainda termos um longo caminho para ir antes de podermos desenvolver um real sistema de transações. Mas Roma não foi construída em um dia. Então temos muito o que fazer ainda.
Otimização de código nas funções existentes será considerada enquanto o artigo se desenvolve. O painel de informação não será abordado neste ponto já que ainda precisamos olhar para algumas propriedades de posição que não podem ser obtidas usando identificadores padrão (uso do histórico de transações é necessário). Este assunto será, de qualquer forma, tratado em um dos artigos seguintes da série.
Desenvolvimento do Consultor Especialista
Vamos começar. Como de costume, começamos com a inserção de enumerações adicionais, variáveis, arranjos e funções auxiliares no início do arquivo. Precisaremos de uma função que nos permitirá facilmente obter propriedades de símbolo. A mesma abordagem simples será necessária em obter propriedades de posição.
Vimos nos artigos anteriores que a variáveis globais foram atribuídas todas propriedades de posição de uma vez na função GetPositionProperties. Agora, tentaremos fornecer a possibilidade de obter cada propriedade separadamente. Abaixo estão duas enumerações para a implementação do que está acima. As funções por si mesmas serão revisadas depois.
//--- 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 };
A enumeração ENUM_SYMBOL_PROPERTIES não contém todas as propriedades de símbolo mas elas podem ser adicionadas a qualquer momento, quando necessário. A enumeração também contém propriedades definidas pelo usuário (10, 11, 12) cálculo o qual é baseado em outras propriedades de símbolo. Há um identificador que pode ser usado para obter todas as propriedades da enumeração de uma vez, como na enumeração de propriedades de posição.
Este é seguido pelos parâmetros externos do Consultor Especialista:
//--- 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
Vamos ver mais de perto os parâmetros externos:
- NumberOfBars - este parâmetro define o número de limites de uma direção para abertura de uma posição;
- Lot - volume da posição;
- TakeProfit - nível de Obter Lucros em pontos. O valor zero significa que nenhum Obter Lucros precisa ser definido.
- StopLoss - nível de Parar Perdas em pontos. O valor zero significa que nenhum Parar Perdas precisa ser definido.
- TrailingStop - Valor do Limite Móvel em pontos. Para uma posição de COMPRA, o cálculo é baseado no limite mínimo (mínimo menos o número de pontos do parâmetro StopLoss). Para uma posição de VENDA, o cálculo é baseado no limite máximo (máximo mais o número de pontos do parâmetro StopLoss). O valor zero denota que o Limite Móvel está desligado.
- Reverse ativa/desativa a reversão de posição.
É apenas o parâmetro NumberOfBars que necessita mais clarificação. Não há motivo em configurar o valor deste parâmetro para, por exemplo, mais do que 5 já que isso é um tanto raro e poderia já ser tarde para abrir uma posição após tal movimento. Precisaremos, então, de uma variável que nos ajudará a ajustar o valor deste parâmetro:
//--- To check the value of the NumberOfBars external parameter int AllowedNumberOfBars=0;
Este parâmetro também determinará a quantidade de dados das barras que serão armazenado nos arranjos de preço. Isto será discutido em breve quando chegarmos a modificação de funções personalizadas.
Como no caso com propriedades de posição, declaramos variáveis em um escopo global para propriedades de símbolo afim de fornecer acesso de qualquer função:
//--- 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
Já que o Limite Móvel deve ser calculado baseado na alta e baixa da barra, precisaremos de arranjos para tais dados de limites:
//--- 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)
Seguiremos agora para modificar e criar funções. Temos já a função GetBarsData que copia preços de abertura e fechamento das barras para arranjos de preço. Agora, precisamos de altas e baixas. Adicionalmente, o valor obtido do parâmetro NumberOfBars deve ser ajustado. É assim que a função parecerá após a modificação:
//+------------------------------------------------------------------+ //| 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())); } }
As condições que exigem pelo menos dois limites e sempre mais por um estão ali porque usaremos apenas barras completadas que comecem com o índice [1]. De fato, ajustes podem neste caso serem tratados como desnecessários já que os dados do limite podem ser copiados começando do índice especificado no terceiro parâmetro das funções CopyOpen, CopyClose, CopyHigh e CopyLow. O limite de 5 barras também pode ser modificado (para cima/para baixo) a seu próprio critério.
A função GetTradingSignal agora tornou-se um pouco mais complexa já que a condição será gerada diferentemente dependendo do número de limites especificado no parâmetro NumberOfBars. Além disso, agora usaremos um tipo mais correto do valor retornado - tipo de ordem:
//+------------------------------------------------------------------+ //| 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); }
Vamos agora modificar a função GetPositionProperties. Nos artigos anteriores, nos permitiram obter todas as propriedades de uma só vez. Entretanto, algumas vezes você pode apenas precisar obter uma propriedade. Para fazer isto, você pode certamente usar as funções padrão oferecidas pela linguagem, entretanto isto não seria tão conveniente quanto queremos. Abaixo está o código da função GetPositionProperties modificado. Agora, quando passando um certo identificador da enumeração ENUM_POSITION_PROPERTIES, você pode ou obter uma certa propriedade de posição ou todas as propriedades de uma só vez.
//+------------------------------------------------------------------+ //| 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(); }
De forma similar, implementamos a função GetSymbolProperties para obter propriedades de símbolo:
//+------------------------------------------------------------------+ //| 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; } }
Note que algumas propriedades de símbolo podem requerer de você a obtenção de outras propriedades primeiro.
Temos uma nova função, CorrectValueBySymbolDigits. Ela retorna o valor relevante, dependendo do número de casas decimais no preço. Um inteiro ou número real pode ser passado para a função. O tipo de dado passado determina a versão da função a ser usada. Este recurso é chamado sobrecarregamento de função.
//+------------------------------------------------------------------+ //| 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; }
Nosso Consultor Especialista terá um parâmetro externo para especificar o volume (Lote) da posição de abertura. Vamos criar uma função que ajustará o lote de acordo com a especificação de símbolo - 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)); }
Procederemos para funções que são diretamente relevantes ao título do artigo. Elas são bem simples e diretas e você pode entender seus propósitos sem qualquer dificuldade usando os comentários no código.
A função CalculateTakeProfit é usada para calcular o valor de Obter Lucros:
//+------------------------------------------------------------------+ //| 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); }
A função CalculateStopLoss é usada para calcular o valor de Parar Perdas:
//+------------------------------------------------------------------+ //| 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); }
A função CalculateTrailingStop é usada para calcular o valor do Limite Móvel:
//+------------------------------------------------------------------+ //| 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); }
Agora temos todas as funções necessárias que retornam valores corretos para operações de negociação. Vamos criar uma função que verificará uma condição para modificar o Limite Móvel e modificará o mesmo, se a condição especificada é satisfeita - ModifyTrailingStop. Abaixo está o código desta função com comentários detalhados.
Por favor, preste atenção ao uso de todas as funções criadas/modificadas acima. O interruptor switch determina a condição relevante dependendo do tipo da posição atual e o resultado da condição é então armazenado na variável condition. Para modificar uma posição, usamos o método PositionModify da classe CTrade da Biblioteca Padrão.
//+------------------------------------------------------------------+ //| 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())); } } } }
Agora vamos ajustar a função TradingBlock de acordo com todas as modificações acima. Assim como na função ModifyTrailingStop, todos os valores de variáveis para uma ordem de negociação serão determinados usando o interruptor switch. Ela diminui significativamente a quantidade de código e simplifica modificações futuras já que ao invés de um ramo para dois tipos de posição, apenas um resta.
//+------------------------------------------------------------------+ //| 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; }
Também precisamos fazer outra importante correção na função SetInfoPanel mas vamos primeiro preparar algumas funções auxiliares que indicam como/onde o programa é usado atualmente:
//+------------------------------------------------------------------+ //| 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); }
A única coisa que precisamos adicionar a função SetInfoPanel é uma condição indicando ao programa que o painel de informação deve apenas ser exibido nos modos de visualização e tempo real. Se isto é ignorado, o tempo de testes será 4-5 vezes mais longo. Isto é especialmente importante ao otimizar os parâmetros.
//+------------------------------------------------------------------+ //| Setting the info panel | //|------------------------------------------------------------------+ void SetInfoPanel() { //--- Visualization or real time modes if(IsVisualMode() || IsRealtime()) { // The remaining code of the SetInfoPanel() function // ... } }
Agora precisamos fazer algumas mudanças as funções do programa principal para ser possível continuar até a otimização de parâmetro de teste do Consultor Especialista.
//+------------------------------------------------------------------+ //| 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(); }
Otimizando parâmetros e testando o Consultor Especialista
Vamos agora otimizar os parâmetros. Usaremos as configurações do Testador de Estratégia como mostradas abaixo:
Fig. 1. Configurações do Testador de Estratégia para otimização de parâmetro.
Aos parâmetros do Consultor Especialista será dada uma vasta gama de valores:
Fig. 2. Configurações do Consultor Especialista para otimização de parâmetro.
A otimização demorou cerca de 7 minutos em um processador dual-core (Intel Core 2 Duo P7350 @ 2.00GHz). Os resultados do teste do fator de recuperação máximo são como a seguir:
Fig. 3. Resultados do teste do fator máximo de recuperação.
Conclusão
Por enquanto é só. Estude, teste, otimize, experimente e se maravilhe. O código-fonte do Consultor Especialista apresentado neste artigo pode ser baixado usando o link abaixo para estudos aprofundados.
Traduzido do russo pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/ru/articles/643
- Aplicativos de negociação gratuitos
- 8 000+ sinais para cópia
- Notícias econômicas para análise dos mercados financeiros
Você concorda com a política do site e com os termos de uso