LifeHack para traders: "amassando" ForEach com os define (#define)
— Onde está a força, irmão?
—A força, irmão, está nos define
(c) fxsaber
Você escreve em MQL4 e deseja migrar para MQL5, mas ainda não sabe por onde começar? Então, é nossa vez de ajudar você! Agora você pode trabalhar confortavelmente no editor MetaEditor MQL5 e, ao mesmo tempo, usar a notação MQL4. Deve-se dizer, porém, que ela já existia. Neste artigo eu quero apresentar uma descrição mais completa e detalhada sobre a compatibilização de funções MQL4 com as de MQL5.
Um bom programador é um programador preguiçoso
A criação de experts ou robôs de negociação implica quase sempre um monte de trabalho com ciclos. Os ciclos estão ao nosso redor em toda parte: pesquisa detalhada de ordens, operações no histórico, objetos no gráfico, símbolos na Observação do mercado, barras no buffer de indicador. Para facilitar a vida do programador, ao MetaEditor foram adicionados trechos de código ("snippets"), eles se desenrolam automaticamente num pequeno pedaço de código quando você digita os primeiros caracteres de uma função ou tarefa e pressiona o Tab. Veja como funciona um trecho de código para o ciclo for:
Não é ruim, mas não cobre todas as nossas necessidades. O exemplo mais simples pode ser quando queremos examinar todos os símbolos na Observação do mercado.
int total=SymbolsTotal(true); for(int i=0;i<total;i++) { string symbol=SymbolName(i,true); PrintFormat("%d. %s",i+1,symbol); }
Seria fantástico criar, no MetaEditor, um trecho de código próprio que começasse com fes (for_each_symbol) e se desenrolasse no seguinte bloco:
Mas, no MetaEditor, não existem "snippets" personalizados, por isso teremos de tomar um outro caminho, isto é, usaremos os define. A substituição de macros #define foi inventada por programadores inteligentes e preguiçosos que perseguiam vários objetivos. Um deles era alcançar a simplicidade de leitura e facilidade de escrita de códigos recorrentes.
Em muitas linguagens de programação, além do ciclo clássico for , existem variantes do tipo for(<typename> element:Collection) ou for each (type identifier in expression). Se fosse possível escrever código do tipo
for(ulong order_id in History) { trabalho com order_id }
, a vida do programador seria um pouco mais fácil. Na internet você vai encontrar inimigos e partidários desta abordagem. Aqui vou mostrar como fazer algo parecido com a ajuda de macros #define.
Comecemos com uma tarefa simples, isto é, obter os nomes de todos os símbolos da Observação do mercado. Vamos para o cabeçalho e escrevemos este macro:
#define ForEachSymbol(s,i) string s; int total=SymbolsTotal(true); for(int i=0;i<total;i++,s=SymbolName(i,true)) //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { //--- ForEachSymbol(symbol,index) { PrintFormat("%d. %s",index,symbol); } }
O compilador entende esta entrada e não produz erros. Executamos a depuração pressionando F5 e vemos que algo deu errado:
1. (null) 2. GBPUSD 3. USDCHF 4. USDJPY ...
O problema é que a expressão s=SymbolName(i,true) no ciclo for é avaliada após a iteração, e nossa variável s não é inicializada de maneira nenhuma na primeira iteração, quando i=0. Operador de ciclo for:
O operador for consiste em três expressões e um operador executável:
for(expressão1; expressão2; expressão3) |
A expressão1 descreve a inicialização do ciclo. A expressão2 é a verificação das condições de conclusão do ciclo. Se ela for True, será executado o operador do corpo do ciclo for. Tudo é repetido até que a expressão2 se torna false. Se ela for false, o ciclo termina e o controle passa para o próximo operador. A expressão3 é calculada após cada iteração.
Isso é facilmente resolvido. Fazemos um par de alterações:
#define ForEachSymbol(s,i) string s=SymbolName(0,true); int total=SymbolsTotal(true); for(int i=1;i<total;i++,s=SymbolName(i,true)) //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { //--- ForEachSymbol(symbol,index) { PrintFormat("%d. %s",index,symbol); // em vez de index+1, agora aparece simplesmente index } }
e obtemos o resultado desejado. Escrevemos o macro ForEachSymbol com parâmetros symbol e index, como se fosse do habitual ciclo for, e no corpo do pseudo ciclo trabalhamos com estas variáveis, como se tivessem sido declaradas e inicializadas usando o valor necessário. Assim, podemos obter as propriedades desejadas dos símbolos a partir da Observação do mercado utilizando funções do tipo SymbolInfoXXX(). Por exemplo, assim:
//+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { //--- ForEachSymbol(symbol,index) { //--- preparamos os dados double spread=SymbolInfoDouble(symbol,SYMBOL_ASK)-SymbolInfoDouble(symbol,SYMBOL_BID); double point=SymbolInfoDouble(symbol,SYMBOL_POINT); long digits=SymbolInfoInteger(symbol,SYMBOL_DIGITS); string str_spread=DoubleToString(spread/point,0); string str_point=DoubleToString(point,digits); //--- imprimimos as informações Print(index,". ",symbol," spread=",str_spread," points (", digits," digits",", point=",str_point,")"); } /* Exemplo de impressão 1. EURUSD spread=3 points (5 digits, point=0.00001) 2. USDCHF spread=8 points (5 digits, point=0.00001) 3. USDJPY spread=5 points (3 digits, point=0.001) 4. USDCAD spread=9 points (5 digits, point=0.00001) 5. AUDUSD spread=5 points (5 digits, point=0.00001) 6 NZDUSD spread=10 points (5 digits, point=0.00001) 7. USDSEK spread=150 points (5 digits, point=0.00001) */ }
Agora podemos escrever um macro desse tipo, para pesquisa detalhada de objetos gráficos no gráfico:
#define ForEachObject(name,i) string name=ObjectName(0,0); int total=ObjectsTotal(0); for(int i=1;i<=total;i++,name=ObjectName(0,i-1)) //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { //--- ForEachObject(objectname,index) { Print(index,": objectname=\"",objectname,"\", objecttype=", EnumToString((ENUM_OBJECT)ObjectGetInteger(0,objectname,OBJPROP_TYPE))); } /* Exemplo de impressão 1: objectname="H1 Arrow 61067", objecttype=OBJ_ARROW_UP 2: objectname="H1 Rectangle 31152", objecttype=OBJ_RECTANGLE 3: objectname="H1 StdDev Channel 56931", objecttype=OBJ_STDDEVCHANNEL 4: objectname="H1 Trendline 6605", objecttype=OBJ_TREND */ }
Na verdade, a linha que define o macro ForEachObject virou um pouco mais comprida, e agora não é tão simples compreender o código de substituição escrito numa única linha. Mas acontece que este problema já foi resolvido, isto é, a definição de macro pode ser dividida em linhas usando uma barra invertida '\'. Assim:
#define ForEachObject(name,i) string name=ObjectName(0,0); \ int ob_total=ObjectsTotal(0); \ for(int i=1;i<=ob_total;i++,name=ObjectName(0,i-1))
Para o compilador, todas essas três linhas aparecerão como uma linha longa, o que facilitará que o programador leia este código. Resta criar esse tipo de macros para trabalhar com entidades de negociação: ordens, posições e transações.
Comecemos com a pesquisa detalhada de ordens. Agora é adicionada apenas a função de escolha de histórico HistorySelect():
#define ForEachOrder(ticket,i) HistorySelect(0,TimeCurrent()); \ ulong ticket=OrderGetTicket(0); \ int or_total=OrdersTotal(); \ for(int i=1;i<or_total;i++,ticket=OrderGetTicket(i)) //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { //--- ForEachOrder(orderticket,index) { Print(index,": #",orderticket," ",OrderGetString(ORDER_SYMBOL)," ", EnumToString((ENUM_ORDER_TYPE)OrderGetInteger(ORDER_TYPE))); } /* Exemplo de impressão 1: #13965457 CADJPY ORDER_TYPE_SELL_LIMIT 2: #14246567 AUDNZD ORDER_TYPE_SELL_LIMIT */ }
Pode-se notar que neste macro (e nos dois anteriores) não existe tratamento de erros. Por exemplo, se HistorySelect() retornar false? Pode-se dizer que, neste caso, não podemos entrar no ciclo de todas as ordens. E, além disso, quem é que var analisar o resultado da execução de HistorySelect()? Acontece que, neste macro, não há nada proibido no que diz respeito ao método convencional de escrita de programas.
Mas a crítica mais forte do uso de #define é o fato de que o uso da substituição de macro não permite a depuração de código. Concordo com isso, porém, como fxsaber diz, "Um paciente bem estabilizado não necessita de anestesia, e um macro depurado não requer depuração."
Mais adiante, fazemos um macro para percorrer cada posição:
#define ForEachPosition(ticket,i) HistorySelect(0,TimeCurrent()); \ ulong ticket=PositionGetTicket(0); \ int po_total=PositionsTotal(); \ for(int i=1;i<=po_total;i++,ticket=PositionGetTicket(i-1)) //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { //--- ForEachPosition(positionid,index) { Print(index,": ",PositionGetString(POSITION_SYMBOL)," postionID #",positionid); } /* Exemplo de impressão 1: AUDCAD postionID #13234934 2: EURNZD postionID #13443909 3: AUDUSD postionID #14956799 4: EURUSD postionID #14878673 */
Pesquisa detalhada de transações no histórico:
#define ForEachDeal(ticket,i) HistorySelect(0,TimeCurrent()); \ ulong ticket=HistoryDealGetTicket(0); \ int total=HistoryDealsTotal(); \ for(int i=1;i<=total;i++,ticket=HistoryDealGetTicket(i-1)) //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { //--- ForEachDeal(dealticket,index) { Print(index,": deal #",dealticket,", order ticket=", HistoryDealGetInteger(dealticket,DEAL_ORDER)); } }
Pesquisa detalhada de ordens no histórico:
#define ForEachHistoryOrder(ticket,i) HistorySelect(0,TimeCurrent());\ ulong ticket=HistoryOrderGetTicket(0); \ int total=HistoryOrdersTotal(); \ for(int i=1;i<=total;i++,ticket=HistoryOrderGetTicket(i-1)) //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { //--- ForEachHistoryOrder(historyorderticket,index) { Print(index,": #",historyorderticket); } }
Coletamos todas as substituições de macros num arquivo ForEach.mqh4:
//+------------------------------------------------------------------+ //| ForEach.mqh | //| Copyright 2018, MetaQuotes Software Corp. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2018, MetaQuotes Software Corp." #property link "https://www.mql5.com" //+------------------------------------------------------------------+ //| Ciclo da pesquisa detalhada de símbolos na Observação do mercado | //+------------------------------------------------------------------+ #define ForEachSymbol(symbol,i) string symbol=SymbolName(0,true); \ int os_total=SymbolsTotal(true); \ for(int i=1;i<os_total;i++,symbol=SymbolName(i,true)) //+---------------------------------------------------------------------------+ //| Ciclo da pesquisa detalhada de objetos na janela principal do gráfico | //+---------------------------------------------------------------------------+ #define ForEachObject(name,i) string name=ObjectName(0,0); \ int ob_total=ObjectsTotal(0); \ for(int i=1;i<=ob_total;i++,name=ObjectName(0,i-1)) //+------------------------------------------------------------------+ //| Ciclo da pesquisa detalhada de ordens existentes | //+------------------------------------------------------------------+ #define ForEachOrder(ticket,i) HistorySelect(0,TimeCurrent()); \ ulong ticket=OrderGetTicket(0); \ int or_total=OrdersTotal(); \ for(int i=1;i<or_total;i++,ticket=OrderGetTicket(i)) //+------------------------------------------------------------------+ //| Ciclo da pesquisa detalhada de posições abertas | //+------------------------------------------------------------------+ #define ForEachPosition(ticket,i) HistorySelect(0,TimeCurrent()); \ ulong ticket=PositionGetTicket(0); \ int po_total=PositionsTotal(); \ for(int i=1;i<=po_total;i++,ticket=PositionGetTicket(i-1)) //+------------------------------------------------------------------+ //| Ciclo da pesquisa detalhada de transações no histórico | //+------------------------------------------------------------------+ #define ForEachDeal(ticket,i) HistorySelect(0,TimeCurrent()); \ ulong ticket=HistoryDealGetTicket(0); \ int dh_total=HistoryDealsTotal(); \ for(int i=1;i<=dh_total;i++,ticket=HistoryDealGetTicket(i-1)) //+------------------------------------------------------------------+ //| Ciclo da pesquisa detalhada de ordens no histórico | //+------------------------------------------------------------------+ #define ForEachHistoryOrder(ticket,i) HistorySelect(0,TimeCurrent());\ ulong ticket=HistoryOrderGetTicket(0); \ int oh_total=HistoryOrdersTotal(); \ for(int i=1;i<=oh_total;i++,ticket=HistoryOrderGetTicket(i-1)) //+------------------------------------------------------------------+
Repare que, para cada macro, para a variável total, tivemos de adicionar o prefixo, a fim de evitar conflitos caso decidamos usar no nosso código mais de um macro. Esta é a maior desvantagem deste macro, isto é, nós escondemos atrás dele a declaração da variável que será visível do exterior. Isso pode levar a erros dificilmente detetáveis durante compilação.
Além disso, ao usar um macro paramétrico, o compilador não dá nenhuma pista de como faz isso para as funções. Você vai ter que decorar estes seis macros se quiser usá-los. Embora não seja tão difícil, o primeiro parâmetro sempre é aquilo essencial que é procurado exaustivamente no ciclo, enquanto o segundo parâmetro é sempre o índice do ciclo que começa com 1 (um).
Por fim, adicionamos alguns macros para rastreamento no sentido inverso. Neste caso, é necessário se mover a partir do final da lista para o início. Adicionamos ao nombre do macro o sufixo Back e fazemos pequenas alterações. Veja como se vê o macro para rastreamento de símbolos na Observação do mercado.
#define ForEachSymbolBack(symbol,i) int s_start=SymbolsTotal(true)-1;\ string symbol=SymbolName(s_start,true); \ for(int i=s_start;i>=0;i--,symbol=SymbolName(i,true)) //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { //--- ForEachSymbolBack(symbol,index) { //--- preparamos os dados double spread=SymbolInfoDouble(symbol,SYMBOL_ASK)-SymbolInfoDouble(symbol,SYMBOL_BID); double point=SymbolInfoDouble(symbol,SYMBOL_POINT); long digits=SymbolInfoInteger(symbol,SYMBOL_DIGITS); string str_spread=DoubleToString(spread/point,0); string str_point=DoubleToString(point,digits); //--- imprimimos as informações Print(index,". ",symbol," spread=",str_spread," points (", digits," digits",", point=",str_point,")"); } /* Exemplo de impressão 3. USDJPY spread=5 points (3 digits, point=0.001) 2. USDCHF spread=8 points (5 digits, point=0.00001) 1. GBPUSD spread=9 points (5 digits, point=0.00001) 0. EURUSD spread=2 points (5 digits, point=0.00001) */ }
Como você pode ver, neste caso, o valor da variável index muda entre size-1 e 0. Assim conclui nossa introdução a #define e avançamos para a escrita de funções, para facilitar o acesso, em estilo MQL4.
1. Grupos de funções MQL4 descritas no artigo
Atenção: as variáveis como Point, Digits e Bar devem ser alteradas de modo independente para Point(), Digits() e Bar(Symbol(),Period())
Os grupos MQL4 AccountXXXX, MQL4 MarketInfo, MQL4 Verificação de estado, MQL4 Variáveis pré-definidas serão convertidos em MQL5. Desse modo, na pasta [date folder]\MQL5\Include\SimpleCall\ são adicionados quatro arquivos: AccountInfo.mqh, MarketInfo.mqh, Check.mqh e Predefined.mqh. No total, na pasta, haverá sete arquivos para converter funções MQL4 em MQL5: AccountInfo.mqh, Check.mqh, IndicatorsMQL4.mqh, IndicatorsMQL5.mqh, MarketInfo.mqh, Predefined.mqh e Series.mqh.
Para trabalhar, você precisará conectar por si só estes arquivos. Além disso, há uma restrição, isto é, os arquivos IndicatorsMQL4.mqh e IndicatorsMQL5.mqh não podem ser conectados ao mesmo tempo, portanto é necessário selecionar um deles. Por isso, na pasta, há dois arquivos: SimpleCallMQL4.mqh, que conecta todos os arquivos mais IndicatorsMQL4.mqh, e SimpleCallMQL5.mqh, que conecta todos os arquivos mais IndicatorsMQL5.mqh.
Exemplo de conexão com MACD Sample.mq4
Copiamos o arquivo MACD Sample.mq4 na pasta MQL5 com os EAs, por exemplo, em [data folder]\MQL5\Experts\, e alteramos a extensão do arquivo para mq5. Assim, obtemos o arquivo MACD Sample.mq5. Compilamos e, imediatamente, obtemos 59 erros e um aviso.
Agora conectamos o arquivo SimpleCallMQL4.mqh:
//+------------------------------------------------------------------+ //| MACD Sample.mq4 | //| Copyright 2005-2014, MetaQuotes Software Corp. | //| http://www.mql4.com | //+------------------------------------------------------------------+ #property copyright "2005-2014, MetaQuotes Software Corp." #property link "http://www.mql4.com" //--- #include <SimpleCall\SimpleCallMQL4.mqh> //--- input double TakeProfit =50;
Novamente, compilamos e obtemos já 39 erros e um aviso. Agora, é necessário substituir manualmente (usando Ctrl+H) Bars por Bars(Symbol(),Period()) e Point por Point(). Compilamos. Restam 35 erros, todos eles pertencem a funções de negociação. Não vamos falar sobre tais funções, neste artigo.
1.1. MQL4 AccountXXXX
A conversão de funções MQL4 AccountXXXX é realizada no arquivo [date folder]\MQL5\Include\SimpleCall\AccountInfo.mqh
Veja a tabela de correspondências entres as funções MQL4 e MQL5:
MQL4 | MQL5 AccountInfoXXXX | MQL5 CAccountInfo | Comentário: |
---|---|---|---|
AccountInfoDouble | AccountInfoDouble | CAccountInfo::InfoDouble ou CAccountInfo::double métodos | |
AccountInfoInteger | AccountInfoInteger | CAccountInfo::InfoInteger ou CAccountInfo::métodos inteiros | |
AccountInfoString | AccountInfoString | CAccountInfo::InfoString ou CAccountInfo::métodos de texto | |
AccountBalance | AccountInfoDouble(ACCOUNT_BALANCE) ou | CAccountInfo::Balance | |
AccountCredit | AccountInfoDouble(ACCOUNT_CREDIT) ou | CAccountInfo::Credit | |
AccountCompany | AccountInfoString(ACCOUNT_COMPANY) ou | CAccountInfo::Company | |
AccountCurrency | AccountInfoString(ACCOUNT_CURRENCY) ou | CAccountInfo::Currency | |
AccountEquity | AccountInfoDouble(ACCOUNT_EQUITY) ou | CAccountInfo::Equity | |
AccountFreeMargin | AccountInfoDouble(ACCOUNT_FREEMARGIN) ou | CAccountInfo::FreeMargin | |
AccountFreeMarginCheck | --- /--- | CAccountInfo::FreeMarginCheck | |
AccountFreeMarginMode | Não tem substituto | Não tem substituto | |
AccountLeverage | AccountInfoInteger(ACCOUNT_LEVERAGE) | CAccountInfo::Leverage | Em MQL4, existe o tipo int, enquanto em MQL5 - long |
AccountMargin | AccountInfoDouble(ACCOUNT_MARGIN) | CAccountInfo::Margin | |
AccountName | AccountInfoString(ACCOUNT_NAME) | CAccountInfo::Name | |
AccountNumber | AccountInfoInteger(ACCOUNT_LOGIN) | CAccountInfo::Login | Em MQL4, existe o tipo int, enquanto em MQL5 - long |
AccountProfit | AccountInfoDouble(ACCOUNT_PROFIT) | CAccountInfo::Profit | |
AccountServer | AccountInfoString(ACCOUNT_SERVER) | CAccountInfo::Server | |
AccountStopoutLevel | AccountInfoDouble(ACCOUNT_MARGIN_SO_SO) | CAccountInfo::MarginStopOut | Em MQL4, existe o tipo int, enquanto em MQL5 - double |
AccountStopoutMode | AccountInfoInteger(ACCOUNT_MARGIN_SO_MODE) | CAccountInfo::StopoutMode |
Atenção, para MQL4 AccountFreeMarginMode não existe substituto em MQL5. Use MQL4 AccountFreeMarginMode por sua própria conta e risco. No caso de MQL4 AccountFreeMarginMode, no log será impresso um aviso e será retornado NaN - "não é um número".
Para as outras funções MQL4 AccountXXXX existem substitutos, ainda por cima, em duas formas: através de AccountInfoXXXX ou através da classe de negociação CAccountInfo. Para AccountInfoDouble, AccountInfoInteger e AccountInfoString, não existem diferenças entre MQL4 e MQL5.
No cabeçalho do arquivo, encontra-se o seguinte código. Como isso funciona?
//--- #define OP_BUY ORDER_TYPE_BUY #define OP_SELL ORDER_TYPE_SELL //--- returns balance value of the current account
Para começar, demos uma vista de olhos para a ajuda do #define:
A diretiva #define pode ser utilizada para atribuir às expressões nomes mnemônicos. Existem duas formas:
#define identifier expression // forma não paramétrica #define identifier(par1,... par8) expression // forma paramétrica
A diretiva #define substitui expression por todas as ocorrências subsequentes de identifier, encontradas no texto original.
No caso do nosso código, essa descrição será assim (usamos a forma não paramétrica): diretiva #define substitui ORDER_TYPE_BUY por todas as ocorrências subsequentes OP_BUY encontradas no texto de origem. Diretiva #define substitui ORDER_TYPE_SELL por todas as ocorrências subseqüentes OP_SELL, encontradas no texto de origem. Ou seja, após conectar o arquivo [date folder]\MQL5\Include\SimpleCall\AccountInfo.mqh ao seu EA MQL5, você poderá usar nele de maneira usual os tipos MQL4 OP_BUY e OP_SELL. O compilador não gerará erros.
Assim, o primeiro grupo de funções que apresentamos em estilo MQL5 é: AccountBalance(), AccountCredit(), AccountCompany(), AccountCurrency(), AccountEquity() e AccountFreeMargin():
//--- returns balance value of the current account #define AccountBalance(void) AccountInfoDouble(ACCOUNT_BALANCE) //--- returns credit value of the current account #define AccountCredit(void) AccountInfoDouble(ACCOUNT_CREDIT) //--- returns the brokerage company name where the current account was registered #define AccountCompany(void) AccountInfoString(ACCOUNT_COMPANY) //--- returns currency name of the current account #define AccountCurrency(void) AccountInfoString(ACCOUNT_CURRENCY) //--- returns equity value of the current account #define AccountEquity(void) AccountInfoDouble(ACCOUNT_EQUITY) //--- returns free margin value of the current account #define AccountFreeMargin(void) AccountInfoDouble(ACCOUNT_MARGIN_FREE)Neste caso, nas funções MQL4 XXXX(void), usa-se a forma paramétrica #define, onde "void" serve como parâmetro. Isto é fácil de verificar, pois se você remover o "void", durante a compilação, irá obter o erro "unexpected in macro format parameter list":
No caso de MQL4 AccountFreeMarginCheck, agimos de forma diferente, quer dizer, implementamos AccountFreeMarginCheck como uma função habitual dentro da qual é usado apenas código MQL5:
//--- returns free margin that remains after the specified order has been opened //--- at the current price on the current account double AccountFreeMarginCheck(string symbol,int cmd,double volume) { double price=0.0; ENUM_ORDER_TYPE trade_operation=(ENUM_ORDER_TYPE)cmd; if(trade_operation==ORDER_TYPE_BUY) price=SymbolInfoDouble(symbol,SYMBOL_ASK); if(trade_operation==ORDER_TYPE_SELL) price=SymbolInfoDouble(symbol,SYMBOL_BID); //--- double margin_check=EMPTY_VALUE; double margin=EMPTY_VALUE; margin_check=(!OrderCalcMargin(trade_operation,symbol,volume,price,margin))?EMPTY_VALUE:margin; //--- return(AccountInfoDouble(ACCOUNT_FREEMARGIN)-margin_check); }
Como mencionado acima, para AccountFreeMarginMode não existe substituto em MQL5, por isso, se, no código, aparecer AccountFreeMarginMode, será emitido um aviso e retornado "não é um número":
//--- returns the calculation mode of free margin allowed to open orders on the current account double AccountFreeMarginMode(void) { string text="MQL4 functions \"AccountFreeMarginMode()\" has no analogs in MQL5. Returned \"NAN - not a number\""; Alert(text); Print(text); return(double("nan")); }
Para as outras funções MQL4, agimos como com as anteriores:
//--- returns leverage of the current account #define AccountLeverage(void) (int)AccountInfoInteger(ACCOUNT_LEVERAGE) //--- returns margin value of the current account #define AccountMargin(void) AccountInfoDouble(ACCOUNT_MARGIN) //--- returns the current account name #define AccountName(void) AccountInfoString(ACCOUNT_NAME) //--- returns the current account number #define AccountNumber(void) (int)AccountInfoInteger(ACCOUNT_LOGIN) //--- returns profit value of the current account #define AccountProfit(void) AccountInfoDouble(ACCOUNT_PROFIT) //--- returns the connected server name #define AccountServer(void) AccountInfoString(ACCOUNT_SERVER) //--- returns the value of the Stop Out level #define AccountStopoutLevel(void) (int)AccountInfoDouble(ACCOUNT_MARGIN_SO_SO) //--- returns the calculation mode for the Stop Out level int AccountStopoutMode(void) { ENUM_ACCOUNT_STOPOUT_MODE stopout_mode=(ENUM_ACCOUNT_STOPOUT_MODE)AccountInfoInteger(ACCOUNT_MARGIN_SO_MODE); if(stopout_mode==ACCOUNT_STOPOUT_MODE_PERCENT) return(0); return(1); }
1.2. MQL4 MarketInfo
A conversão de funções MQL4 MarketInfotXXXX é realizada no arquivo [date folder]\MQL5\Include\SimpleCall\MarketInfo.mqh
MQL4 MarketInfo tem o tipo double, mas, em MQL5, existem substitutos de MarketInfo em SymbolInfoInteger() (retorna o tipo long) e em SymbolInfoDouble() (retorna o tipo double). Neste caso, faz-se notar toda a trapalhada ao usar funções obsoletas MarketInfo: conversão de tipos. Como exemplo de conversão MQL5 SYMBOL_DIGITS:
MarketInfo(Symbol(),MODE_DIGITS) <= (double)SymbolInfoInteger(symbol,SYMBOL_DIGITS)
1.2.1 Ambiguidade com MQL4 MODE_TRADEALLOWED
Em MQL4, ele é simplesmente um sinalizador bool, enquanto em MQL5, para os símbolos, pode haver vários tipos de autorizações/proibições:
ENUM_SYMBOL_TRADE_MODE
Identificador | Descrição |
---|---|
SYMBOL_TRADE_MODE_DISABLED | Proibida negociação segundo o símbolo |
SYMBOL_TRADE_MODE_LONGONLY | Habilitadas apenas compras |
SYMBOL_TRADE_MODE_SHORTONLY | Habilitadas apenas vendas |
SYMBOL_TRADE_MODE_CLOSEONLY | Habilitadas apenas operações de fechamento de posições |
YMBOL_TRADE_MODE_FULL | Não há restrições sobre as operações de negociação |
Proponho retornar false apenas se SYMBOL_TRADE_MODE_DISABLED, enquanto para restrições parciais ou para acesso completo - true (e, ao mesmo tempo, emitir Alert e Print com um aviso de restrição parcial).
1.2.2. Ambiguidade com MQL4 MODE_SWAPTYPE
Em MQL4, MODE_SWAPTYPE retorna apenas quatro valores (método de avaliação de swaps. 0 - em pontos; 1 - em moeda base do instrumento; 2 - em porcentagem; 3 - em moeda de garantia), enquanto em MQL5 a enumeração ENUM_SYMBOL_SWAP_MODE contém nove valores que se cruzam com os valores em MQL4:
MQL4 MODE_SWAPTYPE | MQL5 ENUM_SYMBOL_SWAP_MODE |
---|---|
Sem correspondência | SYMBOL_SWAP_MODE_DISABLED |
0 - em pontos | SYMBOL_SWAP_MODE_POINTS |
1 - na moeda base do instrumento | SYMBOL_SWAP_MODE_CURRENCY_SYMBOL |
3 - na moeda da garantia | SYMBOL_SWAP_MODE_CURRENCY_MARGIN |
Sem correspondência | SYMBOL_SWAP_MODE_CURRENCY_DEPOSIT |
2 - em porcentagem | SYMBOL_SWAP_MODE_INTEREST_CURRENT |
2 - em porcentagem | SYMBOL_SWAP_MODE_INTEREST_OPEN |
Sem correspondência | SYMBOL_SWAP_MODE_REOPEN_CURRENT |
Sem correspondência | SYMBOL_SWAP_MODE_REOPEN_BID |
Em MQL5, para cálculo dos swaps em porcentagem, existem duas opções:
/*
SYMBOL_SWAP_MODE_INTEREST_CURRENT
Swaps são calculados em porcentagem anual do preço do instrumento no momento do cálculo do swap (modo bancário - 360 dias por ano)
SYMBOL_SWAP_MODE_INTEREST_OPEN
Swaps são calculados em porcentagem anual do preço de abertura da posição segundo o símbolo (modo bancário - 360 dias por ano)
*/
Vamos considerá-los como um tipo. Para o resto de variantes MQL5, que não têm análogos em MQL4, será emitido um aviso e retornado "não é um número".
1.2.3. Ambiguidade com MQL4 MODE_PROFITCALCMODE e MODE_MARGINCALCMODE
Em MQL4 MODE_PROFITCALCMODE (método de cálculo do lucro) retorna três valores: 0 - Forex; 1 - CFD; 2 - Futures, enquanto MODE_MARGINCALCMODE (método de cálculo da garantia) - quatro: 0 - Forex; 1 - CFD; 2 - Futures; 3 - CFD para índices. Em MQL5, para o instrumento, pode ser definido o método de cálculo da garantia (enumeração ENUM_SYMBOL_CALC_MODE), mas não o método para calcular o lucro. Assumo que em MQL5, o cálculo da garantia é como o de lucros, por isso, para MQL4 MODE_PROFITCALCMODE e MODE_MARGINCALCMODE, será retornado o mesmo valor com MQL5 ENUM_SYMBOL_CALC_MODE.
A enumeração MQL5 ENUM_SYMBOL_CALC_MODE contém dez maneiras. Comparamos MQL4 MODE_PROFITCALCMODE com MQL5 ENUM_SYMBOL_CALC_MODE:
MQL4 MODE_PROFITCALCMODE "Forex" <==> MQL5 SYMBOL_CALC_MODE_FOREX
MQL4 MODE_PROFITCALCMODE "CFD" <==> MQL5 SYMBOL_CALC_MODE_CFD
MQL4 MODE_PROFITCALCMODE "Futures" <==> MQL5 SYMBOL_CALC_MODE_FUTURES
Para o resto de variantes MQL5, que não têm análogos em MQL4, será emitido um aviso e retornado "não é um número":
... #define MODE_PROFITCALCMODE 1000//SYMBOL_TRADE_CALC_MODE #define MODE_MARGINCALCMODE 1001//SYMBOL_TRADE_CALC_MODE ... case MODE_PROFITCALCMODE: { ENUM_SYMBOL_CALC_MODE profit_calc_mode=(ENUM_SYMBOL_CALC_MODE)SymbolInfoInteger(symbol,SYMBOL_TRADE_CALC_MODE); switch(profit_calc_mode) { case SYMBOL_CALC_MODE_FOREX: return((double)0); case SYMBOL_CALC_MODE_FUTURES: return((double)2); case SYMBOL_CALC_MODE_CFD: return((double)1); default : { string text="MQL4 MODE_PROFITCALCMODE returned MQL5 "+EnumToString(profit_calc_mode); Alert(text); Print(text); return(double("nan")); } } } case MODE_MARGINCALCMODE: { ENUM_SYMBOL_CALC_MODE profit_calc_mode=(ENUM_SYMBOL_CALC_MODE)SymbolInfoInteger(symbol,SYMBOL_TRADE_CALC_MODE); switch(profit_calc_mode) { case SYMBOL_CALC_MODE_FOREX: return((double)0); case SYMBOL_CALC_MODE_FUTURES: return((double)2); case SYMBOL_CALC_MODE_CFD: return((double)1); default : { string text="MQL4 MODE_MARGINCALCMODE returned MQL5 "+EnumToString(profit_calc_mode); Alert(text); Print(text); return(double("nan")); } } }
1.3. MQL4 Verificação de estado
A conversão de funções MQL4 para verificação de status é realizada no arquivo [date folder]\MQL5\Include\SimpleCall\Check.mqh
Isto inclui os seguintes elementos:
- Digits
- Point
- IsConnected
- IsDemo
- IsDllsAllowed
- IsExpertEnabled
- IsLibrariesAllowed
- IsOptimization
- IsTesting
- IsTradeAllowed
- IsTradeContextBusy
- IsVisualMode
- TerminalCompany
- TerminalName
- TerminalPath
MQL4 | MQL5 | MQL5 classes | Observação |
---|---|---|---|
Digits | MQL4 permite usar simultaneamente Digits e Digits() | ||
Point | MQL4 permite usar simultaneamente Point e Point() | ||
IsConnected | TerminalInfoInteger(TERMINAL_CONNECTED) | CTerminalInfo::IsConnected | |
IsDemo | AccountInfoInteger(ACCOUNT_TRADE_MODE) | CAccountInfo::TradeMode | Retorna um dos valores da enumeração ENUM_ACCOUNT_TRADE_MODE |
IsDllsAllowed | TerminalInfoInteger(TERMINAL_DLLS_ALLOWED) e MQLInfoInteger(MQL_DLLS_ALLOWED) | CTerminalInfo::IsDLLsAllowed | TERMINAL_DLLS_ALLOWED - tem o mais alto status, e MQL_DLLS_ALLOWED pode ser ignorado |
IsExpertEnabled | TerminalInfoInteger(TERMINAL_TRADE_ALLOWED) | CTerminalInfo::IsTradeAllowed | Status do botão "Autotrading" no terminal |
IsLibrariesAllowed | MQLInfoInteger(MQL_DLLS_ALLOWED) | -/- | A verificação não faz sentido, porque, se o programa usa DLL e você não permite sua utilização (a guia "Dependência" do programa), você simplesmente não poderá executar o programa. |
IsOptimization | MQLInfoInteger(MQL_OPTIMIZATION) | -/- | Os programas MQL5 podem ter quatro modos: depuração, criação de perfil do código, testador, otimização |
IsTesting | MQLInfoInteger(MQL_TESTER) | -/- | Os programas MQL5 podem ter quatro modos: depuração, criação de perfil do código, testador, otimização |
IsTradeAllowed | MQLInfoInteger(MQL_TRADE_ALLOWED) | -/- | Estado da caixa de verificação "Ativar negociação automatizada" nas propriedades do programa |
IsTradeContextBusy | -/- | -/- | Será retornado "false" |
IsVisualMode | MQLInfoInteger(MQL_VISUAL_MODE) | Os programas MQL5 podem ter quatro modos: depuração, criação de perfil do código, testador, otimização | |
TerminalCompany | TerminalInfoString(TERMINAL_COMPANY) | CTerminalInfo::Company | |
TerminalName | TerminalInfoString(TERMINAL_NAME) | CTerminalInfo::Name | |
TerminalPath | TerminalInfoString(TERMINAL_PATH) | CTerminalInfo::Path |
Os primeiros dois pontos (Digits e Point) são completamente irrealizáveis, uma vez que MQL4 e MQL5 estão totalmente misturadas. Ou seja, em MQL4, podem-se encontrar simultaneamente Digits e Digits(), Point e Point(). Por exemplo, eu pessoalmente não sei como, através de define, Digits e Digits() podem ser reduzidos a Digits(). Os pontos restantes são encontrados exclusivamente na forma XXXX() e podem ser substituídos por análogos MQL5.
E a realização em si:
//+------------------------------------------------------------------+ //| Check.mqh | //| Copyright 2018, MetaQuotes Software Corp. | //| http://wmua.ru/slesar/ | //+------------------------------------------------------------------+ #property copyright "Copyright 2018, MetaQuotes Software Corp." #property link "http://wmua.ru/slesar/" #property version "1.003" //--- checks connection between client terminal and server #define IsConnected (bool)TerminalInfoInteger(TERMINAL_CONNECTED) //--- checks if the Expert Advisor runs on a demo account #define IsDemo (bool)(AccountInfoInteger(ACCOUNT_TRADE_MODE)==(ENUM_ACCOUNT_TRADE_MODE)ACCOUNT_TRADE_MODE_DEMO) //--- checks if the DLL function call is allowed for the Expert Advisor #define IsDllsAllowed (bool)TerminalInfoInteger(TERMINAL_DLLS_ALLOWED) //--- checks if Expert Advisors are enabled for running #define IsExpertEnabled (bool)TerminalInfoInteger(TERMINAL_TRADE_ALLOWED) //--- checks if the Expert Advisor can call library function #define IsLibrariesAllowed (bool)MQLInfoInteger(MQL_DLLS_ALLOWED) //--- checks if Expert Advisor runs in the Strategy Tester optimization mode #define IsOptimization (bool)MQLInfoInteger(MQL_OPTIMIZATION) //--- checks if the Expert Advisor runs in the testing mode #define IsTesting (bool)MQLInfoInteger(MQL_TESTER) //--- checks if the Expert Advisor is allowed to trade and trading context is not busy #define IsTradeAllowed (bool)MQLInfoInteger(MQL_TRADE_ALLOWED) //--- returns the information about trade context #define IsTradeContextBusy false //--- checks if the Expert Advisor is tested in visual mode #define IsVisualMode (bool)MQLInfoInteger(MQL_VISUAL_MODE) //--- returns the name of company owning the client terminal #define TerminalCompany TerminalInfoString(TERMINAL_COMPANY) //--- returns client terminal name #define TerminalName TerminalInfoString(TERMINAL_NAME) //--- returns the directory, from which the client terminal was launched #define TerminalPath TerminalInfoString(TERMINAL_PATH) //+------------------------------------------------------------------+
1.4. MQL4 Variáveis pré-definidas
Realização no arquivo [data folder]\MQL5\Include\SimpleCall\Predefined.mqh
MQL4 variáveis pré-definidas:
- _Digits
- _Point
- _LastError
- _Period
- _RandomSeed
- _StopFlag
- _Symbol
- _UninitReason
- Ask
- Bars
- Bid
- Close
- Digits
- High
- Low
- Open
- Point
- Time
- Volume
Variáveis pré-definidas _XXXX são convertidas em funções MQL5 usando os formulários sem parâmetros #define:
//--- the _Digits variable stores number of digits after a decimal point, #define _Digits Digits() //--- the _Point variable contains the point size of the current symbol in the quote currency #define _Point Point() //--- the _LastError variable contains code of the last error #define _LastError GetLastError() //--- the _Period variable contains the value of the timeframe of the current chart #define _Period Period() //#define _RandomSeed //--- the _StopFlag variable contains the flag of the program stop #define _StopFlag IsStopped() //--- the _Symbol variable contains the symbol name of the current chart #define _Symbol Symbol() //--- the _UninitReason variable contains the code of the program uninitialization reason #define _UninitReason UninitializeReason() //#define Bars Bars(Symbol(),Period()); //#define Digits //#define Point
A exceção é para "_RandomSeed", isto é, nesta variável, por um lado, é armazenado o estado atual do gerador de números inteiros pseudo-aleatórios e, por outro lado, em MQL5, também não há conversão em Bars (mais precisamente, Bars é deixado para substituição manual). Para Digits e Point não há solução. Como mencionado acima, no texto, podem ocorrer simultaneamente Digits e Digits(), e Point e Point().
MQL4 Ask e Bid são substituídas pelas funções personalizadas GetAsk() e GetBid():
//--- the latest known seller's price (ask price) for the current symbol #define Ask GetAsk() //--- the latest known buyer's price (offer price, bid price) of the current symbol #define Bid GetBid() ... //--- the latest known seller's price (ask price) for the current symbol double GetAsk() { MqlTick tick; SymbolInfoTick(Symbol(),tick); return(tick.ask); } //--- the latest known buyer's price (offer price, bid price) of the current symbol double GetBid() { MqlTick tick; SymbolInfoTick(Symbol(),tick); return(tick.bid); }
Claro, nós poderíamos escrever um macro para Ask mais simples:
#define Ask SymbolInfoDouble(__Symbol,SYMBOL_ASK)
Mas a nota a SymbolInfoDouble diz:
Se a função é usada para obter informações sobre o último tick, é melhor utilizar SymbolInfoTick(). É possível que, desde a conexão do terminal à conta de negociação, este símbolo não tenha tido cotações. Nesse caso, o valor consultado é indefinido.
Na maioria dos casos, basta usar a função SymbolInfoTick(). Ela permite, numa única chamada, obter os valores Ask, Bid, Last, Volume e hora de chegada do último tick.
E agora algo novo, usamos "operator"
Usaremos a palavra-chave operator, para sobrecarregar o operador de indexação []. Isto é necessário para converter, em MQL5, as matrizes e timeseries MQL4 (Open[], High[], Low[], Close[], Time[], Volume[]).
A ajuda do "operator" nos diz:
A sobrecarga de operações permite utilizar a notação operacional (entrada na forma de expressões simples) para objetos complexos - estruturas e classes.
Com base na ajuda, pode se presumir que, para sobrecarregar o operador de indexação [], é preciso criar uma classe.
Lembrando sobre #define:
A diretiva #define pode ser utilizada para atribuir às expressões nomes mnemônicos. Existem duas formas:
#define identifier expression // forma não paramétrica #define identifier(par1,... par8) expression // forma paramétrica
A diretiva #define substitui expression por todas as ocorrências subsequentes de identifier, encontradas no texto original.
o seguinte código pode ser lido como: diretiva #define substitui 159 por todas as ocorrências subseqüentes encontradas no texto de origem.
#define SeriesVolume(Volume,T) 159 //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { long a=SeriesVolume(Volume,long); }
Ou seja, o código em OnStart é convertido na forma:
long a=159;
Passo 2
Aqui dentro de #define é colocada uma classe inteira,
//+------------------------------------------------------------------+ //| Test_en.mq5 | //| Copyright 2012, CompanyName | //| http://www.companyname.net | //+------------------------------------------------------------------+ #define SeriesVolume(Volume,T) class CVolume \ { \ public: \ T operator[](const int i) const \ { \ long val[1]; \ if(CopyTickVolume(Symbol(),Period(),i,1,val)==1)\ return(val[0]); \ else \ return(-1); \ } \ }; \ CVolume Volume; //--- SeriesVolume(Volume,long) //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { Print(Volume[4]); } //+------------------------------------------------------------------+
Esta substituição pode ser apresentada da seguinte forma:
//+------------------------------------------------------------------+ //| Test_en.mq5 | //| Copyright 2012, CompanyName | //| http://www.companyname.net | //+------------------------------------------------------------------+ class CVolume { public: long operator[](const int i) const { long val[1]; if(CopyTickVolume(Symbol(),Period(),i,1,val)==1) return(val[0]); else return(-1); } }; CVolume Volume; //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { Print(Volume[4]); }
Ou seja, em OnStart, nós, na realidade, acessamos o objeto Volume da classe CVolume, o método [], em que redirecionamos o índice i. Usando o mesmo princípio, escrevemos para as séries MQL4 Open, High, Low, Close e Time.
E, finalmente, ficam as funções iXXX MQL4: iOpen, iHigh, iLow, iClose, iTime e iVolume. Para elas, implementamos o método de declaração de funções personalizadas.
Exemplo para iClose:
//--- returns Close price value for the bar of specified symbol with timeframe and shift double iClose( string symbol, // symbol ENUM_TIMEFRAMES timeframe, // timeframe int shift // shift ) { double result=0.0; //--- double val[1]; ResetLastError(); int copied=CopyClose(symbol,timeframe,shift,1,val); if(copied>0) result=val[0]; else Print(__FUNCTION__,": CopyClose error=",GetLastError()); //--- return(result); }
2. Alterações em outros arquivos
Em [data folder]\MQL5\Include\SimpleCall\IndicatorsMQL4.mqh, todos os nomes de linhas MQL4 agora estão no cabeçalho:
double NaN=double("nan"); #define MODE_MAIN 0 #define MODE_SIGNAL 1 #define MODE_PLUSDI 1 #define MODE_MINUSDI 2 #define MODE_GATORJAW 1 #define MODE_GATORTEETH 2 #define MODE_GATORLIPS 3 #define MODE_UPPER 1 #define MODE_LOWER 2 #define MODE_TENKANSEN 1 #define MODE_KIJUNSEN 2 #define MODE_SENKOUSPANA 3 #define MODE_SENKOUSPANB 4 #define MODE_CHIKOUSPAN 5
Em [data folder]\MQL5\Include\SimpleCall\Series.mqh foram retirados
#define MODE_OPEN 0 //#define MODE_LOW 1 //#define MODE_HIGH 2 #define MODE_CLOSE 3 #define MODE_VOLUME 4 //#define MODE_TIME 5
Agora eles estão em [data folder]\MQL5\Include\SimpleCall\Header.mqh :
//+------------------------------------------------------------------+ //| Header.mqh | //| Copyright 2018, MetaQuotes Software Corp. | //| http://wmua.ru/slesar/ | //+------------------------------------------------------------------+ #property copyright "Copyright 2018, MetaQuotes Software Corp." #property link "http://wmua.ru/slesar/" //--- #define MODE_LOW 10001 #define MODE_HIGH 10002 #define MODE_TIME 10005
e iClose, iHigh, iLow, iOpen, iTime e iVolume agora estão em [data folder]\MQL5\Include\SimpleCall\Predefined.mqh
Fim do artigo
No artigo anterior, vimos como escrever chamadas de indicadores em estilo MQL4 e o que isso acarreta. Acontece que, devido à simplicidade da escrita, é mais lento o trabalho dos EA, que não têm controle sobre os indicadores criados. Neste artigo, nós continuamos a procurar maneiras de simplificar a escrita de código e revisamos a substituição de macros #define.
Como resultado, vemos que, com a ajuda dos código anexados ao artigo, quase qualquer EA MQL4 pode ser obrigado a funcionar no MetaTrader 5. Só é necessário conectar os arquivos necessários que sobrecarregam ou adicionam funções necessárias e variáveis pré-definidas.
Para ser completamente compatível, não basta usar apenas funções de negociação MQL4 simplificadas. Mas este problema é resolvido, se desejado. Resumindo, mais uma vez os prós e contras desta abordagem:
Contras:
- restrição no processamento do erro de retorno ao acessar o indicador;
- queda da velocidade de teste ao acessar simultaneamente mais de um indicador;
- necessidade de especificar corretamente a linha dos indicadores dependendo da conexão de "IndicatorsMQL5.mqh" ou "IndicatorsMQL4.mqh".
- impossibilidade para depurar a substituição de macros #define;
- inexistência de dica de balão sobre os argumentos do #define paramétrico;
- potenciais conflitos de variáveis escondidas atrás de macros.
- simplicidade na escrita do código, isto é, uma linha em vez de várias;
- clareza e concisão, isto é, quanto menos código, é mais fácil de compreendê-lo;
- substituições de macros são realçadas a vermelho no editor, o que facilita a visualização de identificadores e funções personalizados;
- podem-se criar substitutos dos trechos de código.
Eu próprio considero que as técnicas mostradas no artigo são, justamente, "lifehacks", isto é, truques para superar a sobrecarga de informação, e continuo empenhado na abordagem clássica MQL5. Talvez aqueles que estão acostumados a escrever em MQL4 possam ser ajudados por estes artigos a superar a barreira psicológica na transição para a plataforma MetaTrader 5, que em todos os aspectos é muito mais conveniente.
Traduzido do russo pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/ru/articles/4332
- 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