
Arbitragem triangular
Descrição da ideia
A arbitragem triangular é um tópico que com frequência invejável é abordado nos fóruns de discussão na web. De que se trata?
A palavra "arbitragem" neste âmbito implica uma certa neutralidade no mercado. "Triangular" se refere ao fato de o portfólio ser composto por três instrumentos.
Tomemos o exemplo mais popular, isto é, o triângulo "euro - libra - dólar. Ele é descrito em pares de moedas assim: EURUSD + GBPUSD + EURGBP. A neutralidade exigida consiste em tentar comprar e vender ao mesmo tempo os mesmos instrumentos, ganhando lucro.
Isto tem a seguinte aparência. Apresentamos qualquer par deste exemplo por meio dos dois restantes:
EURUSD=GBPUSD*EURGBP~,
ou GBPUSD=EURUSD/EURGBP,
ou EURGBP=EURUSD/GBPUSD.
Todas estas variantes são idênticas, no entanto abaixo vamos dar uma olhada na escolha de qualquer uma delas. Por ora, vamos nos concentrar na primeira variante.
Agora tentemos nos aperceber dos preços bid e ask. O processo será como se segue:
- Compramos EURUSD, ou seja, usamos o preço ask. No saldo, temos mais euros e menos dólares.
- Expressamos o EURUSD por meio dos outros dois pares.
- GBPUSD: aqui não há euro, mas dólar sim (devemos vender os dólares). Para vender dólares em GBPUSD, é preciso comprar este par. Então, usamos ask. Ao comprar, temos no saldo mais libras e menos dólares.
- EURGBP: necessitamos comprar euros, enquanto vendemos a libra, que não precisamos. Compramos EURGBP, usamos ask. No saldo, temos mais euros e menos libras. Bem, está tudo certo por aqui.
No total, temos: (ask) EURUSD = (ask) GBPUSD * (ask) EURGBP. Nós obtivemos a igualdade necessária. Agora, para ganhar nela, temos que comprar um lado e vender o outro. Aqui há duas possibilidades:
- Comprar EURUSD mais barato do que podemos vendê-lo, mas expresso de uma forma diferente: (ask) EURUSD < (bid) GBPUSD * (bid) EURGBP
- Vender EURUSD mais caro do que podemos comprá-lo, mas expresso de uma forma diferente: (bid) EURUSD > (ask) GBPUSD * (ask) EURGBP
Só resta uma coisa pequena, isto é, encontrar uma tal situação e fazer dinheiro com isso.
Chamo a atenção para a hipótese de formar o triângulo de uma outra maneira, movendo todos os três pares em uma direção e comparando a 1. Todas as variantes são idênticas, mas a acima mostrada, na minha opinião, é mais facilmente compreendida e explicada.
E mais uma observação importante: acompanhando esta situação podemos descobrir o momento de compra e venda simultânea. Neste caso, o lucro será imediato, mas esses momentos são muitíssimos raros.
Com um pouco mais de frequência costuma acontecer que podemos comprar um lado mais barato do que o outro, mas não se pode vender com lucro. Então, aguardamos que esse desequilíbrio desapareça. Estamos seguros estando no trade, porque a nossa posição é quase zero, ou seja, estamos fora do mercado. Porém, você tem que entender de onde vem esse quase. Para uma ideal regularização dos volumes de negociação, é necessária uma exatidão que não está à nossa disposição. Lembro-lhe que geralmente os volumes de negociação são arredondados para 2 casas decimais, e, para nossa estratégia, isso é um arredondamento sem precisão.
Já aprofundamos a teoria, é o momento de programar o robô. O expert advisor é escrito em um estilo processual, portanto, é claro para os programadores novatos e aqueles que por qualquer motivo não gostam da programação orientada a objetos
Breve descrição do robô
Primeiro, criamos todos os triângulos possíveis, organizamo-los corretamente e obtemos todos os dados necessários para cada par de moedas.
Todas essas informações são armazenadas na matriz de estruturas MxThree. Cada triângulo tem um campo status. Seu valor inicial = 0. Se for necessário abrir o triângulo, ao status será atribuído o valor = 1. Depois de confirmar que o triângulo foi completamente aberto, o seu status é alterado para 2. Se o triângulo não se abrir completamente ou já estiver na hora de ser fechado, o status é alterado para 3. Uma vez que o triângulo fecha com sucesso, o status retorna para a posição 0.
O robô grava a abertura e fechamento dos triângulos em um arquivo de log para validar a correção da ação e restaurar o histórico. Nome do arquivo de log: Three Point Arbitrage Control YYYY.DD.MM.csv.
Para testar, carregue no Testador todos os pares de moedas necessários. Para fazer isso, antes de iniciar o Testador, é necessário executar o robô no modo Create file with symbols. Si não estiver este arquivo, o robô desencadeia o teste com base no testador padrão EUR+GBP+USD.
Variáveis utilizadas
O código de qualquer robô, quando eu trabalho, começa com a inclusão do arquivo de cabeçalho. Ele lista todos os incodes, bibliotecas, etc. Este robô não é excepção: imediatamente após o bloco da descrição segue a linha #include "head.mqh" etc.:
#include <Trade\Trade.mqh> #include <Trade\SymbolInfo.mqh> #include <Trade\TerminalInfo.mqh> #include "var.mqh" #include "fnWarning.mqh" #include "fnSetThree.mqh" #include "fnSmbCheck.mqh" #include "fnChangeThree.mqh" #include "fnSmbLoad.mqh" #include "fnCalcDelta.mqh" #include "fnMagicGet.mqh" #include "fnOpenCheck.mqh" #include "fnCalcPL.mqh" #include "fnCreateFileSymbols.mqh" #include "fnControlFile.mqh" #include "fnCloseThree.mqh" #include "fnCloseCheck.mqh" #include "fnCmnt.mqh" #include "fnRestart.mqh" #include "fnOpen.mqh"
Agora, esta lista não é totalmente clara para o leitor, mas o artigo está escrito no modo de acompanhamento de código, de forma que a estrutura do programa não é perturbada. Enquanto você lê tudo se torna claro. Por uma questão de comodidade, todas as funções, classes, unidades de código estão organizados em arquivos individuais. No meu caso, cada arquivo incluído, com exceção da biblioteca padrão, também começa com a linha #include "head.mqh". Isso permite usar IntelliSense nos arquivos incluídos e não manter em mente os nomes de todas as entidades necessárias.
Em seguida, conectamos o arquivo para o Testador. Em nenhum outro lugar pode-se fazer isso, portanto declaramo-lo aqui. Esta linha é necessária para carregar os símbolos no testador multimoeda:
#property tester_file FILENAME
Em seguida, descrevemos as variáveis utilizadas no programa. Sua descrição também está contida em um arquivo separado var.mqh:
// macros #define DEVIATION 3 // Derrapagem máxima possível #define FILENAME "Three Point Arbitrage.csv" // Aqui é armazenado o símbolo de trabalho #define FILELOG "Three Point Arbitrage Control " // Parte do nome do arquivo de log #define FILEOPENWRITE(nm) FileOpen(nm,FILE_UNICODE|FILE_WRITE|FILE_SHARE_READ|FILE_CSV) // Abertura do arquivo para gravação #define FILEOPENREAD(nm) FileOpen(nm,FILE_UNICODE|FILE_READ|FILE_SHARE_READ|FILE_CSV) // Abertura do arquivo para leitura #define CF 1.2 // Coeficiente crescente para a margem #define MAGIC 200 // Faixa de magics usados #define MAXTIMEWAIT 3 // Tempo máximo de espera em segundos após a abertura do triângulo // estrutura para o par de moedas struct stSmb { string name; // Par de moedas int digits; // Número de casa decimais na cotação uchar digits_lot; // Número de casa decimais no lote, para arredondamento int Rpoint; // 1/point, a fim de, nas fórmulas, multiplicar por esse valor, em vez de dividir double dev; // Derrapagem possível. Convertemos diretamente no número de pontos double lot; // Volume de negociação para o par de moedas double lot_min; // Volume mínimo double lot_max; // Volume máximo double lot_step; // Incremento do lote double contract; // Tamanho do contrato double price; // Preço de abertura do preço no triângulo. Necessário para compensação ulong tkt; // Bilhete da ordem que abriu o negócio. É necessário por conveniência nas contas de cobertura MqlTick tick; // Preços atuais do par double tv; // Custo atual do tick double mrg; // Margem atual necessário para abertura double sppoint; // Spread em pontos inteiros double spcost; // Spread em dinheiro para o lote atual a ser aberto stSmb(){price=0;tkt=0;mrg=0;} }; // Estrutura para o triângulo struct stThree { stSmb smb1; stSmb smb2; stSmb smb3; double lot_min; // Volume mínimo para todo o triângulo double lot_max; // Volume máximo para todo o triângulo ulong magic; // Número mágico do triângulo uchar status; // Status do triângulo 0 - não se usa. 1 - enviado para abertura. 2 - aberto com sucesso. 3- enviado para fechamento double pl; // Lucro do triângulo datetime timeopen; // Hora de envio do triângulo para abertura double PLBuy; // Quanto você pode ganhar se comprar um triângulo double PLSell; // Quanto você pode ganhar se vender triângulo double spread; // Custo total de todos os três spreads (com a comissão!) stThree(){status=0;magic=0;} }; // Modo de trabalho do expert advisor enum enMode { STANDART_MODE = 0, /*Symbols from Market Watch*/ // Modo de trabalho padrão. Símbolos da Observação do mercado USE_FILE = 1, /*Symbols from file*/ // Usar o arquivo de símbolos CREATE_FILE = 2, /*Create file with symbols*/ // Criar arquivo para teste ou para trabalho //END_ADN_CLOSE = 3, /*Not open, wait profit, close & exit*/ // Fechar todos os negócios e terminar o trabalho //CLOSE_ONLY = 4 /*Not open, not wait profit, close & exit*/ }; stThree MxThree[]; // Matriz principal, onde se armazenam os triângulos de trabalho e todos os dados adicionais necessário CTrade ctrade; // Classe CTrade da biblioteca padrão CSymbolInfo csmb; // Classe CSymbolInfo da biblioteca padrão CTerminalInfo cterm; // Classe CTerminalInfo da biblioteca padrão int glAccountsType=0; // Tipo de conta: cobertura ou compensação int glFileLog=0; // Identificador do arquivo de log // Parâmetros de entrada sinput enMode inMode= 0; // Modo de trabalho input double inProfit= 0; // Comissão input double inLot= 1; // Volume de negociação input ushort inMaxThree= 0; // Triângulos abertos sinput ulong inMagic= 300; // Número mágico do expert advisor sinput string inCmnt= "R "; // Comentários
Primeiro vão os define, eles são simples e têm muitos comentários. Eu acho que não haverá problemas para compreendê-los.
Em seguida, vão duas estruturas stSmb e stThree. Sua lógica é a seguinte: qualquer triângulo consiste em três pares de moedas. Portanto, descrevendo um deles e usando-o três vezes temos um triângulo. stSmb é a estrutura que descreve o par de moedas e suas especificações: possíveis volumes de negociação, variáveis _Digits e _Point, preços atuais na abertura e outros. Na estrutura stThree, stSmb é usado três vezes, ele é nosso triângulo. Além disso, aqui são adicionadas certas propriedades pertencentes ao triângulo: o lucro atual, magic, tempo de abertura e assim por diante. Em seguida, temos os modos de trabalho, sobre os quais falaremos mais tarde, e as variáveis de entrada. As variáveis de entrada também são descritas nos comentários, porém, concentraremo-nos em duas delas:
No parâmetro inMaxThree é armazenado o número máximo de triângulos abertos simultaneamente. Quando é 0, ele não se usa. Por exemplo, se o parâmetro definido como 2, não mais de 2 triângulos podem ser abertos simultaneamente.
Parâmetro inProfit contém o tamanho da configurações, se existir.
Configuração inicial
Bem, os arquivos incluídos e variáveis usadas estão descritos. A seguir, abordamos o bloco OnInint().
Antes de iniciar o expert advisor, é necessário verificar se os parâmetros inseridos são corretos e obter os dados iniciais, onde necessário. Se tudo for bem-sucedido, começamos a trabalhar. Nos experts, eu procuro definir o mínimo de configurações de entrada, e este robô não é a exceção.
Somente um dos 6 parâmetros de entrada pode fazer com que o expert advisor não seja capaz de trabalhar, nomeadamente o volume de negociação. Não podemos abrir um negócio com volume negativo. Todas as outras configurações para saber se o trabalho é correto não influenciam. A verificação é realizada na primeira função do bloco OnInit().
Vejamos seu código.
void fnWarning(int &accounttype, double lot, int &fh) { // Verificamos se o posicionamento do volume da ordem é correto; não podemos negociar com um volume negativo if (lot<0) { Alert("Trade volume < 0"); ExpertRemove(); } // Se o volume de negociação = 0, antecipamos que o robô irá automaticamente utilizar o mínimo volume possível. if (lot==0) Alert("Always use the same minimum trading volume");
Como o robô é escrito em um estilo processual, é necessário implementar algumas variáveis globais. Uma delas é o identificador do arquivo de log. O nome consiste na parte e data fixas do robô. Isso é feito para facilitar o controle, a fim de evitar a busca em um só arquivo onde começa no log um certo ponto de partida. Observe que o nome muda a cada inicialização, e o último arquivo com o mesmo nome será excluído se existir.
Durante o trabalho, o expert advisor utiliza dois arquivos, isto é: o arquivo com os triângulos encontrados (cria-se apenas durante a seleção adequada do usuário) e o arquivo de log onde se grava o tempo de abertura e fechamento do triângulo, os preços de abertura e certas informações adicionais para facilitar o controle. O arquivo de log sempre é realizado.
// Criamos o arquivo de log, somente se não tiver sido selecionado o modo de criação do arquivo dos triângulos, uma vez que, neste caso, é irrelevante. if(inMode!=CREATE_FILE) { string name=FILELOG+TimeToString(TimeCurrent(),TIME_DATE)+".csv"; FileDelete(name); fh=FILEOPENWRITE(name); if (fh==INVALID_HANDLE) Alert("The log file is not created"); } // Na esmagadora maioria, o tamanho do contrato para os pares de moedas que possuem as corretoras é de = 100 000, mas às vezes há exceções. // Elas são tão raras que é mais fácil, uma vez, no início, verificar este valor, e se não é igual a 100 000, então, fazer saber sobre isso, // para que o usuário tome sua própria decisão. Mais tarde, ele continua trabalhando sem descrever mais momentos em que // no triângulo podem se encontrar pares com diferentes tamanhos de contrato. for(int i=SymbolsTotal(true)-1;i>=0;i--) { string name=SymbolName(i,true); // a função para verificar a disponibilidade do comércio também é usada na composição dos triângulos. // Aqui examinamo-la em mais detalhes if(!fnSmbCheck(name)) continue; double cs=SymbolInfoDouble(name,SYMBOL_TRADE_CONTRACT_SIZE); if(cs!=100000) Alert("Attention: "+name+", contract size = "+DoubleToString(cs,0)); } // Obtemos o tipo de conta com cobertura ou compensação accounttype=(int)AccountInfoInteger(ACCOUNT_MARGIN_MODE); }
Composição dos triângulos
Para elaborar triângulos, precisamos examinar os seguintes aspetos:
- Qual a fonte dos triângulos, quer dizer, vindos da janela "Observação do mercado" ou a partir de um arquivo previamente preparado.
- Estamos no testador ou não? Se for assim, será necessário carregar os símbolos na Observação do mercado. Não faz sentido carregar tudo quanto possível, uma vez que um computador de desktop simplesmente não é capaz de lidar com tal carga. Vamos procurar o arquivo previamente preparado contendo os símbolos para o teste. Se ele não existir, testaremos a estratégia no triângulo padrão: EUR+USD+GBP.
- Para simplificar o código, impomos uma restrição, isto é, todos os símbolos no triângulo devem ter o mesmo tamanho de contrato.
- Não esqueçamos que os triângulos podem ser formados apenas a partir de pares de moedas.
A primeira função necessária é a composição dos triângulos a partir da Observação do mercado.
void fnGetThreeFromMarketWatch(stThree &MxSmb[]) { // Obtemos o número total de símbolos int total=SymbolsTotal(true); // Variáveis para comparar o tamanho dos contratos double cs1=0,cs2=0; // No primeiro ciclo tomamos o primeiro símbolo da lista for(int i=0;i<total-2 && !IsStopped();i++) {//1 string sm1=SymbolName(i,true); // Verificamos se o símbolo tem algum tipo de restrição if(!fnSmbCheck(sm1)) continue; // Obtemos o tamanho do contrato e imediatamente normalizamo-lo, uma vez que vamos comparar esse valor if (!SymbolInfoDouble(sm1,SYMBOL_TRADE_CONTRACT_SIZE,cs1)) continue; cs1=NormalizeDouble(cs1,0); // Obtemos a moeda base e a moeda do lucro, pois realizamos a comparação com base nelas, e não pelo nome do par string sm1base=SymbolInfoString(sm1,SYMBOL_CURRENCY_BASE); string sm1prft=SymbolInfoString(sm1,SYMBOL_CURRENCY_PROFIT); // No segundo ciclo tomamos o seguinte símbolo da lista for(int j=i+1;j<total-1 && !IsStopped();j++) {//2 string sm2=SymbolName(j,true); if(!fnSmbCheck(sm2)) continue; if (!SymbolInfoDouble(sm2,SYMBOL_TRADE_CONTRACT_SIZE,cs2)) continue; cs2=NormalizeDouble(cs2,0); string sm2base=SymbolInfoString(sm2,SYMBOL_CURRENCY_BASE); string sm2prft=SymbolInfoString(sm2,SYMBOL_CURRENCY_PROFIT); // No primeiro e o segundo par deve haver uma ocorrência em qualquer uma das moedas. // Se não for assim, não podemos formar um triângulo a partir deles. // Neste caso, não faz sentido verificar se há paridade absoluta, porque, por exemplo, a partir de // eurusd e eurusd.xxx, seja como for, não se pode formar um triângulo. if(sm1base==sm2base || sm1base==sm2prft || sm1prft==sm2base || sm1prft==sm2prft); else continue; // Os tamanhos dos contratos devem ser idênticos if (cs1!=cs2) continue; // No terceiro ciclo, procuramos o último símbolo para o triângulo for(int k=j+1;k<total && !IsStopped();k++) {//3 string sm3=SymbolName(k,true); if(!fnSmbCheck(sm3)) continue; if (!SymbolInfoDouble(sm3,SYMBOL_TRADE_CONTRACT_SIZE,cs1)) continue; cs1=NormalizeDouble(cs1,0); string sm3base=SymbolInfoString(sm3,SYMBOL_CURRENCY_BASE); string sm3prft=SymbolInfoString(sm3,SYMBOL_CURRENCY_PROFIT); // Sabemos que o primeiro e segundo símbolo tem uma moeda comum. Para compor formar o triângulo, é necessário encontrar // um tal terceiro par de moedas, onde uma moeda é a mesma no primeiro par e outra que é igual // no segundo par. Se não houver coincidência, o par não servirá. if(sm3base==sm1base || sm3base==sm1prft || sm3base==sm2base || sm3base==sm2prft);else continue; if(sm3prft==sm1base || sm3prft==sm1prft || sm3prft==sm2base || sm3prft==sm2prft);else continue; if (cs1!=cs2) continue; // Se nós alcançamos esta etapa, já todas as verificações estão feitas, e a partir destes três pares é possível formar um triângulo // Gravamo-lo em nossa matriz int cnt=ArraySize(MxSmb); ArrayResize(MxSmb,cnt+1); MxSmb[cnt].smb1.name=sm1; MxSmb[cnt].smb2.name=sm2; MxSmb[cnt].smb3.name=sm3; break; }//3 }//2 }//1 }
A segunda função necessária é a leitura dos triângulos a partir do arquivo
void fnGetThreeFromFile(stThree &MxSmb[]) { // Se o arquivo com os símbolos não for encontrado, imprimimos uma mensagem sobre isto e concluímos o trabalho int fh=FileOpen(FILENAME,FILE_UNICODE|FILE_READ|FILE_SHARE_READ|FILE_CSV); if(fh==INVALID_HANDLE) { Print("File with symbols not read!"); ExpertRemove(); } // Deslocamos o carro para o início do arquivo FileSeek(fh,0,SEEK_SET); // Ignoramos o cabeçalho (a primeira linha do arquivo) while(!FileIsLineEnding(fh)) FileReadString(fh); while(!FileIsEnding(fh) && !IsStopped()) { // Obtemos os três símbolo do triângulo. Realizamos uma verificação básica quando a disponibilidade dos dados // o robô pode formar automaticamente um arquivo com triângulos. Se de repente o usuário // o ter mudado sozinho e de maneira incorreta, por isso assumimos que ele fez isso deliberadamente string smb1=FileReadString(fh); string smb2=FileReadString(fh); string smb3=FileReadString(fh); // Se os dados dos símbolos estão disponíveis, após ir até o fim da linha, gavamo-los em nossa matriz de triângulos if (!csmb.Name(smb1) || !csmb.Name(smb2) || !csmb.Name(smb3)) {while(!FileIsLineEnding(fh)) FileReadString(fh);continue;} int cnt=ArraySize(MxSmb); ArrayResize(MxSmb,cnt+1); MxSmb[cnt].smb1.name=smb1; MxSmb[cnt].smb2.name=smb2; MxSmb[cnt].smb3.name=smb3; while(!FileIsLineEnding(fh)) FileReadString(fh); } }
A última função, que é necessária nesta seção, é um encapsulamento das duas funções anteriores. Ela responde pela seleção da fonte dos triângulos dependendo das configurações de entrada do robô. Além disso, verificamos nele onde é executado o robô. Se for no Testador, independentemente da escolha do usuário, carregamos os triângulos a partir do arquivo. Se não houver arquivo, carregamos o triângulo padrão EURUSD+GBPUSD+EURGBP.
void fnSetThree(stThree &MxSmb[],enMode mode) { // Redefinimos nossa matriz de triângulos ArrayFree(MxSmb); // Conferimos se estamos no testador ou não if((bool)MQLInfoInteger(MQL_TESTER)) { // Ser for assim, procuramos o arquivo dos símbolos e iniciamos o download de símbolos desde o arquivo if(FileIsExist(FILENAME)) fnGetThreeFromFile(MxSmb); // Se o arquivo não for encontrado, percorremos todos os símbolos disponíveis e encontre entre eles o triângulo padrão EURUSD+GBPUSD+EURGBP else{ char cnt=0; for(int i=SymbolsTotal(false)-1;i>=0;i--) { string smb=SymbolName(i,false); if ((SymbolInfoString(smb,SYMBOL_CURRENCY_BASE)=="EUR" && SymbolInfoString(smb,SYMBOL_CURRENCY_PROFIT)=="GBP") || (SymbolInfoString(smb,SYMBOL_CURRENCY_BASE)=="EUR" && SymbolInfoString(smb,SYMBOL_CURRENCY_PROFIT)=="USD") || (SymbolInfoString(smb,SYMBOL_CURRENCY_BASE)=="GBP" && SymbolInfoString(smb,SYMBOL_CURRENCY_PROFIT)=="USD")) { if (SymbolSelect(smb,true)) cnt++; } else SymbolSelect(smb,false); if (cnt>=3) break; } // Depois do download do triângulo padrão na Observação do mercado, executamos a composição do triângulo fnGetThreeFromMarketWatch(MxSmb); } return; } // Se não estamos no testador, conferimos qual modo de trabalho escolheu o usuário: // tomar os símbolos a partir da Observação do mercado ou desde o arquivo if(mode==STANDART_MODE || mode==CREATE_FILE) fnGetThreeFromMarketWatch(MxSmb); if(mode==USE_FILE) fnGetThreeFromFile(MxSmb); }
Aqui usamos uma função auxiliar, nomeadamente, fnSmbCheck(). Nela é verificado se existe restrição para trabalhar com o símbolo. Se houver, ignoramo-la. Este é seu código.
bool fnSmbCheck(string smb) { // O triângulo pode ser formado apenas de pares de moedas if(SymbolInfoInteger(smb,SYMBOL_TRADE_CALC_MODE)!=SYMBOL_CALC_MODE_FOREX) return(false); // Se existirem restrições de comércio, então pulamos este símbolo if(SymbolInfoInteger(smb,SYMBOL_TRADE_MODE)!=SYMBOL_TRADE_MODE_FULL) return(false); // Se houver uma data de início ou fim do contrato, também pulamos, já que este parâmetro não é usado na moeda if(SymbolInfoInteger(smb,SYMBOL_START_TIME)!=0)return(false); if(SymbolInfoInteger(smb,SYMBOL_EXPIRATION_TIME)!=0) return(false); // Disponibilidade para tipos de ordens. Embora o robô opere apenas ordens a mercado, não devia haver restrições int som=(int)SymbolInfoInteger(smb,SYMBOL_ORDER_MODE); if((SYMBOL_ORDER_MARKET&som)==SYMBOL_ORDER_MARKET); else return(false); if((SYMBOL_ORDER_LIMIT&som)==SYMBOL_ORDER_LIMIT); else return(false); if((SYMBOL_ORDER_STOP&som)==SYMBOL_ORDER_STOP); else return(false); if((SYMBOL_ORDER_STOP_LIMIT&som)==SYMBOL_ORDER_STOP_LIMIT); else return(false); if((SYMBOL_ORDER_SL&som)==SYMBOL_ORDER_SL); else return(false); if((SYMBOL_ORDER_TP&som)==SYMBOL_ORDER_TP); else return(false); // Verifica-se se a biblioteca padrão tem disponibilidade de dados if(!csmb.Name(smb)) return(false); // A validação abaixo só é necessária, no trabalho real, porque às vezes por alguma razão SymbolInfoTick funciona e os preços, por assim dizer, // são recebidos, porém, na verdade, Ask ou Bid=0. // No testador, desligamos, uma vez que os preços podem aparecer mais tarde. if(!(bool)MQLInfoInteger(MQL_TESTER)) { MqlTick tk; if(!SymbolInfoTick(smb,tk)) return(false); if(tk.ask<=0 || tk.bid<=0) return(false); } return(true); }
Bem, os triângulos estão formados. As funções para sua composição estão no arquivo incluído fnSetThree.mqh. A função para verificar a restrição do símbolo se encontra no arquivo separado fnSmbCheck.mqh.
Nós compomos todos os triângulos possíveis. Os pares contidos neles podem se encontrar aleatoriamente, o que causa inúmeros incômodos, uma vez que precisamos definir como expressar um par de moedas por meio de outras. Providenciando pôr ordem, no exemplo euro-dólar-libra estudamos todas as possíveis variantes de disposição:
№ | símbolo 1 | símbolo 2 | símbolo 3 | |
---|---|---|---|---|
1 | EURUSD = | GBPUSD | х | EURGBP |
2 | EURUSD = | EURGBP | х | GBPUSD |
3 | GBPUSD = | EURUSD | / | EURGBP |
4 | GBPUSD = | EURGBP | 0 | EURUSD |
5 | EURGBP = | EURUSD | / | GBPUSD |
6 | EURGBP = | GBPUSD | 0 | EURUSD |
'x' = multiplicar , '/' = dividir. '0' = ação impossível
Na tabela acima, pode se ver que o triângulo pode ser composto de 6 maneiras, mas duas delas - linhas 4 e 6 - não permitem expressar o primeiro símbolo por meio dos dois restantes. Então, essas variantes caem. As restantes 4 são idênticas. Acima de tudo, o que tem importância para nos é a velocidade. A operação de divisão é mais lenta do que a multiplicação, por isso pomos de lado as variantes 3 e 5. Restam duas variantes: linhas 1 e 2.
Devido a uma cômoda visualização, concentramo-nos na variante da lina 2. Assim, não teremos de inserir caixas de edição adicionais no robô para o primeiro, segundo e terceiro símbolos, na verdade, isso é possível, uma vez que negociamos não apenas um, mas sim todos os possíveis.
A comodidade depende de nossa escolha, e uma vez que operamos arbitragem, esta estratégia pressupõe a posição neutra, portanto devemos comprar e vender o mesmo. Exemplo: Buy 0.7 do lote EURUSD e Sell 0.7 do lote EURGBP - nós compramos e vendemos 70000€. Ou seja, temos uma posição, apesar do fato de que estamos fora do mercado, uma vez que durante a compra e venda figurava um volume, mas expresso em dinheiro diferente. Precisamos ajustar ir isso realizando um negócio em GBPUSD. Em outras palavras, de vez sabemos que os símbolos 1 e 2 devem ter sempre o mesmo volume, mas uma direção diferente. Além disso, sabe-se que o volume do terceiro par é igual ao preço do segundo par.
Função que coloca pares no triângulo corretamente:void fnChangeThree(stThree &MxSmb[]) { int count=0; for(int i=ArraySize(MxSmb)-1;i>=0;i--) {//for // Primeiro, definimos o que está em terceiro lugar. // Este par é uma moeda base que não conincide com as outras duas moedas string sm1base="",sm2base="",sm3base=""; // Se, de repente, por alguma razão não conseguimos obter a moeda base, então não usamos esse triângulo if(!SymbolInfoString(MxSmb[i].smb1.name,SYMBOL_CURRENCY_BASE,sm1base) || !SymbolInfoString(MxSmb[i].smb2.name,SYMBOL_CURRENCY_BASE,sm2base) || !SymbolInfoString(MxSmb[i].smb3.name,SYMBOL_CURRENCY_BASE,sm3base)) {MxSmb[i].smb1.name="";continue;} // Se, na moeda base, 1 e 2 símbolos coincidem, em seguida, pulamos esta etapa. Se não, alteramos o lugar dos par if(sm1base!=sm2base) { if(sm1base==sm3base) { string temp=MxSmb[i].smb2.name; MxSmb[i].smb2.name=MxSmb[i].smb3.name; MxSmb[i].smb3.name=temp; } if(sm2base==sm3base) { string temp=MxSmb[i].smb1.name; MxSmb[i].smb1.name=MxSmb[i].smb3.name; MxSmb[i].smb3.name=temp; } } // Agora definimos o primeiro e segundo lugar. // No segundo lugar o par cuja moeda de lucro coincide com a base da terceira moeda. // Neste caso, usamos sempre multiplicação. sm3base=SymbolInfoString(MxSmb[i].smb3.name,SYMBOL_CURRENCY_BASE); string sm2prft=SymbolInfoString(MxSmb[i].smb2.name,SYMBOL_CURRENCY_PROFIT); // Alteramos os lugares do primeiro e segundo par. if(sm3base!=sm2prft) { string temp=MxSmb[i].smb1.name; MxSmb[i].smb1.name=MxSmb[i].smb2.name; MxSmb[i].smb2.name=temp; } // Imprimimos uma mensagem sobre o triângulo processado. Print("Use triangle: "+MxSmb[i].smb1.name+" + "+MxSmb[i].smb2.name+" + "+MxSmb[i].smb3.name); count++; }// // Relatamos sobre o número total de triângulos no trabalho. Print("All used triangles: "+(string)count); }
A função é inteiramente localizada em um arquivo separado fnChangeThree.mqh.
E a última etapa necessário para completar a preparação de triângulos: carregamos Imediatamente todos os dados dos pares usados, para não gastar tempo chamando-os. Nós precisamos o seguinte:
- volumes máximo e mínimo de negociação para cada símbolo;
- número de caracteres no preço e volume para o arredondamento;
- variável Point e Ticksize. Eu nunca deparei com uma situação em que os pares de moedas fossem diferentes, mas, mesmo assim, obtemos todos os dados e utilizamo-los nos lugares certos.
void fnSmbLoad(double lot,stThree &MxSmb[]) { // Macros simples para imprimir #define prnt(nm) {nm="";Print("NOT CORRECT LOAD: "+nm);continue;} // Percorremos no ciclo todos os triângulos formados. Neste caso, vamos ter um gasto excessivo de tempo para solicitar várias vezes os dados do mesmo // símbolo, mas, como esta operação só ocorre quando o robô é baixado, pode-se atuar dessa maneira para reduzir o código. // Para obter os dados, usamos a biblioteca padrão. for(int i=ArraySize(MxSmb)-1;i>=0;i--) { // Após transferir o símbolo para a classe CSymbolInfo, inicializamos a coleta de todos os dados que precisamos // e ao mesmo tempo e verificamos sua disponibilidade. Se algo está errado, marcamos o triângulo como inoperante. if (!csmb.Name(MxSmb[i].smb1.name)) prnt(MxSmb[i].smb1.name); // Obtemos o número de dígitos de cada símbolo MxSmb[i].smb1.digits=csmb.Digits(); //Convertemos a derrapagem de pontos inteiros em decimais. Mais tarde vamos precisar deste formato para realizar os cálculos MxSmb[i].smb1.dev=csmb.TickSize()*DEVIATION; // Para transformar as cotações em pontos, geralmente temos que dividir o preço pelo valor _Point. // O melhor é representar este valor como 1/Point, e então substituímos a divisão e multiplicamos. // Aqui não se verifica se csmb.Point() é igual a 0, porque ela não pode ser igual a 0, mas se por algum motivo // não for obtido o parâmetro, este triângulo será eliminado pela linha if (!csmb.Name(MxSmb[i].smb1.name)). MxSmb[i].smb1.Rpoint=int(NormalizeDouble(1/csmb.Point(),0)); // Arredondamos o lote, uma vez que há muitos caracteres. MxSmb[i].smb1.digits_lot=csup.NumberCount(csmb.LotsStep()); // As restrições de volume são normalizadas imediatamente MxSmb[i].smb1.lot_min=NormalizeDouble(csmb.LotsMin(),MxSmb[i].smb1.digits_lot); MxSmb[i].smb1.lot_max=NormalizeDouble(csmb.LotsMax(),MxSmb[i].smb1.digits_lot); MxSmb[i].smb1.lot_step=NormalizeDouble(csmb.LotsStep(),MxSmb[i].smb1.digits_lot); //Tamanho do contrato MxSmb[i].smb1.contract=csmb.ContractSize(); // O mesmo que acima, mas levado para o símbolo 2 if (!csmb.Name(MxSmb[i].smb2.name)) prnt(MxSmb[i].smb2.name); MxSmb[i].smb2.digits=csmb.Digits(); MxSmb[i].smb2.dev=csmb.TickSize()*DEVIATION; MxSmb[i].smb2.Rpoint=int(NormalizeDouble(1/csmb.Point(),0)); MxSmb[i].smb2.digits_lot=csup.NumberCount(csmb.LotsStep()); MxSmb[i].smb2.lot_min=NormalizeDouble(csmb.LotsMin(),MxSmb[i].smb2.digits_lot); MxSmb[i].smb2.lot_max=NormalizeDouble(csmb.LotsMax(),MxSmb[i].smb2.digits_lot); MxSmb[i].smb2.lot_step=NormalizeDouble(csmb.LotsStep(),MxSmb[i].smb2.digits_lot); MxSmb[i].smb2.contract=csmb.ContractSize(); // O mesmo que acima, mas levado para o símbolo 3 if (!csmb.Name(MxSmb[i].smb3.name)) prnt(MxSmb[i].smb3.name); MxSmb[i].smb3.digits=csmb.Digits(); MxSmb[i].smb3.dev=csmb.TickSize()*DEVIATION; MxSmb[i].smb3.Rpoint=int(NormalizeDouble(1/csmb.Point(),0)); MxSmb[i].smb3.digits_lot=csup.NumberCount(csmb.LotsStep()); MxSmb[i].smb3.lot_min=NormalizeDouble(csmb.LotsMin(),MxSmb[i].smb3.digits_lot); MxSmb[i].smb3.lot_max=NormalizeDouble(csmb.LotsMax(),MxSmb[i].smb3.digits_lot); MxSmb[i].smb3.lot_step=NormalizeDouble(csmb.LotsStep(),MxSmb[i].smb3.digits_lot); MxSmb[i].smb3.contract=csmb.ContractSize(); // Ajustamos o volume de negociação. Aqui há restrições para cada par de moedas e para todo o triângulo. // Restrições para o par gravadas aqui: MxSmb[i].smbN.lotN // Restrições para o triângulo gravadas aqui: MxSmb[i].lotN // Selecionamos - de todos os valores mínimos - o valor máximo. Neste caso, arredondamos com base no valor maior. // Todo este bloco de código é feito apenas se em termos de volume acontecer que: 0,01 + 0,01 + 0,1. // Neste caso, o menor volume de negociação possível será definido como 0,1 e arredondado para 1 casa decimal. double lt=MathMax(MxSmb[i].smb1.lot_min,MathMax(MxSmb[i].smb2.lot_min,MxSmb[i].smb3.lot_min)); MxSmb[i].lot_min=NormalizeDouble(lt,(int)MathMax(MxSmb[i].smb1.digits_lot,MathMax(MxSmb[i].smb2.digits_lot,MxSmb[i].smb3.digits_lot))); // Também tomamos - de todos os valores máximos - o valor mínimo, e imediatamente arredondamos. lt=MathMin(MxSmb[i].smb1.lot_max,MathMin(MxSmb[i].smb2.lot_max,MxSmb[i].smb3.lot_max)); MxSmb[i].lot_max=NormalizeDouble(lt,(int)MathMax(MxSmb[i].smb1.digits_lot,MathMax(MxSmb[i].smb2.digits_lot,MxSmb[i].smb3.digits_lot))); // Se, nos parâmetros de entrada, o volume de negociação é definido como 0, usamos a menor volume possível, no entanto, não tomamos nenhum mínimo para cada par, // em vez disso, tomamos o mínimo para todos. if (lot==0) { MxSmb[i].smb1.lot=MxSmb[i].lot_min; MxSmb[i].smb2.lot=MxSmb[i].lot_min; MxSmb[i].smb3.lot=MxSmb[i].lot_min; } else { // Se é necessário ajustar o volume, nos pares 1 e 2 ele é conhecido, e o volume do terceiro par será calculado diretamente na entrada. MxSmb[i].smb1.lot=lot; MxSmb[i].smb2.lot=lot; // Se o volume de negociação de entrada não se enquadrar nas restrições atuais, não vamos usar o triângulo durante o trabalho. // Notificamos sobre isto usando um alerta if (lot<MxSmb[i].smb1.lot_min || lot>MxSmb[i].smb1.lot_max || lot<MxSmb[i].smb2.lot_min || lot>MxSmb[i].smb2.lot_max) { MxSmb[i].smb1.name=""; Alert("Triangle: "+MxSmb[i].smb1.name+" "+MxSmb[i].smb2.name+" "+MxSmb[i].smb3.name+" - not correct the trading volume"); continue; } } } }
A função é localizada em um arquivo separado fnSmbLoad.mqh
Nesta seção sobre a formação de triângulos podemos considerar que o tópico está fechado. Seguimos em frente.
Modo de trabalho do expert advisor
Ao executar o robô, podemos selecionar um dos modos disponíveis:- Symbols from Market Watch.
- Symbols from file.
- Create file with symbols.
Modo "Symbols from Market Watch" assume que nós iniciamos o robô no símbolo atual e formamos triângulos de trabalho a partir da janela Observação do mercado. Este é o modo básico de funcionamento, e não necessita de tratamento adicional.
Modo "Symbols from file" se distingue do primeiro pela fonte dos triângulos, isto é, a partir do arquivo previamente preparado.
Modo "Create file with symbols" cria oportunamente o arquivo com triângulos, que, mais para frente, vamos usar quer no segundo modo de trabalho ou no testador. Este modo implica apenas a composição dos triângulos, após a qual o trabalho do expert advisor é concluído.
Descrevemos esta lógica:
if(inMode==CREATE_FILE) { // Removemos o arquivo, se ele existir. FileDelete(FILENAME); int fh=FILEOPENWRITE(FILENAME); if (fh==INVALID_HANDLE) { Alert("File with symbols not created"); ExpertRemove(); } // Escrevemos os triângulos e algumas informações adicionais no arquivo fnCreateFileSymbols(MxThree,fh); Print("File with symbols created"); // Fechamos o arquivo e completamos o trabalho do Expert Advisor FileClose(fh); ExpertRemove(); }
A função de armazenamento de dados é simples e não requer comentários específicos:
void fnCreateFileSymbols(stThree &MxSmb[], int filehandle) { // Definimos os cabeçalhos no arquivo FileWrite(filehandle,"Symbol 1","Symbol 2","Symbol 3","Contract Size 1","Contract Size 2","Contract Size 3", "Lot min 1","Lot min 2","Lot min 3","Lot max 1","Lot max 2","Lot max 3","Lot step 1","Lot step 2","Lot step 3", "Common min lot","Common max lot","Digits 1","Digits 2","Digits 3"); // Preenchemos o arquivo de cabeçalho em conformidade com os cabeçalhos mencionados acima for(int i=ArraySize(MxSmb)-1;i>=0;i--) { FileWrite(filehandle,MxSmb[i].smb1.name,MxSmb[i].smb2.name,MxSmb[i].smb3.name, MxSmb[i].smb1.contract,MxSmb[i].smb2.contract,MxSmb[i].smb3.contract, MxSmb[i].smb1.lot_min,MxSmb[i].smb2.lot_min,MxSmb[i].smb3.lot_min, MxSmb[i].smb1.lot_max,MxSmb[i].smb2.lot_max,MxSmb[i].smb3.lot_max, MxSmb[i].smb1.lot_step,MxSmb[i].smb2.lot_step,MxSmb[i].smb3.lot_step, MxSmb[i].lot_min,MxSmb[i].lot_max, MxSmb[i].smb1.digits,MxSmb[i].smb2.digits,MxSmb[i].smb3.digits); } FileWrite(filehandle,""); // Deixamos uma linha em branco depois de todos os símbolos // Depois de completar o trabalho, transferimos todos os dados para o disco, por segurança FileFlush(filehandle); }
Além dos triângulos, gravamos informações adicionais: o volume de negociação permitido, o tamanho do contrato, o número de caracteres nas cotações. Precisamos esses dados a partir do arquivo apenas para um controle visual das propriedades dos símbolos.
Essa função está disponível no arquivo separado fnCreateFileSymbols.mqh
Reinicio do robô
Temos quase completado a configuração de inicialização do expert advisor. Nós ainda temos que responder a outra pergunta: como lidar com a recuperação de pane? Se ocorrer uma perda de Internet curta, não há problema. O robô vai continuar a funcionar corretamente após a reconexão à rede. Mas se tivermos que reiniciar o robô, então precisamos encontrar suas posições e continuar a trabalhar com els.
Função que resolve os problemas com a reinicialização do robô:
void fnRestart(stThree &MxSmb[],ulong magic,int accounttype) { string smb1,smb2,smb3; long tkt1,tkt2,tkt3; ulong mg; uchar count=0; //Contador de triângulos restaurados switch(accounttype) { // Com cobertura, restaurar a posição é simples, basta percorrer por todas as posições abertas, e mediante o magic encontrar as próprias e // formar com elas triângulos. // Com a cobertura é mais difícil, porque é necessário acessar seu próprio banco de dados, em que armazenada a posição aberta pelo robô. // Algoritmo para buscar posições e recuperação sem caminhos difíceis e // otimização. Mas, como em esta etapa é necessária raramente, pode-se ignorar o desempenho para // redução do código. case ACCOUNT_MARGIN_MODE_RETAIL_HEDGING: // Percorremos todas as posições abertas e vemos a ocorrência pelo magic. // Lembramos o magic da primeira posição encontrada, uma vez que com base nele vamos buscas os dois restantes. for(int i=PositionsTotal()-1;i>=2;i--) {//for i smb1=PositionGetSymbol(i); mg=PositionGetInteger(POSITION_MAGIC); if (mg<magic || mg>(magic+MAGIC)) continue; // Lembramos o bilhete para que mais tarde seja mais simples acessar essa posição. tkt1=PositionGetInteger(POSITION_TICKET); // Buscamos a segunda posição com esse mesmo magic. for(int j=i-1;j>=1;j--) {//for j smb2=PositionGetSymbol(j); if (mg!=PositionGetInteger(POSITION_MAGIC)) continue; tkt2=PositionGetInteger(POSITION_TICKET); // Buscamos a última posição. for(int k=j-1;k>=0;k--) {//for k smb3=PositionGetSymbol(k); if (mg!=PositionGetInteger(POSITION_MAGIC)) continue; tkt3=PositionGetInteger(POSITION_TICKET); // Se chegamos até esta linha, significa que há um triângulo aberto. Os dados sobre ele já estão carregados. O robô contará tudo o resto com a próximo tick. for(int m=ArraySize(MxSmb)-1;m>=0;m--) {//for m // Percorremos a matriz de triângulos, pulando todos os abertos. if (MxSmb[m].status!=0) continue; // A pesquisa detalhada é feita no cabeçalho. À primeira vista, pode parecer que, ao acontecer isto, podemos algumas vezes // consultar o mesmo par de moedas. No entanto, não é assim, porque nos ciclos de pesquisa detalhada // depois de encontrar o seguinte par de moedas, continuamos a procurar desde o próximo par e não desde o inícioif ( (MxSmb[m].smb1.name==smb1 || MxSmb[m].smb1.name==smb2 || MxSmb[m].smb1.name==smb3) && (MxSmb[m].smb2.name==smb1 || MxSmb[m].smb2.name==smb2 || MxSmb[m].smb2.name==smb3) && (MxSmb[m].smb3.name==smb1 || MxSmb[m].smb3.name==smb2 || MxSmb[m].smb3.name==smb3)); else continue; // Encontramos este triângulo, e atribuímos o estatuto adequado MxSmb[m].status=2; MxSmb[m].magic=magic; MxSmb[m].pl=0; // Organizamos os bilhetes na sequência correta. Triângulo novamente em funcionamento. if (MxSmb[m].smb1.name==smb1) MxSmb[m].smb1.tkt=tkt1; if (MxSmb[m].smb1.name==smb2) MxSmb[m].smb1.tkt=tkt2; if (MxSmb[m].smb1.name==smb3) MxSmb[m].smb1.tkt=tkt3; if (MxSmb[m].smb2.name==smb1) MxSmb[m].smb2.tkt=tkt1; if (MxSmb[m].smb2.name==smb2) MxSmb[m].smb2.tkt=tkt2; if (MxSmb[m].smb2.name==smb3) MxSmb[m].smb2.tkt=tkt3; if (MxSmb[m].smb3.name==smb1) MxSmb[m].smb3.tkt=tkt1; if (MxSmb[m].smb3.name==smb2) MxSmb[m].smb3.tkt=tkt2; if (MxSmb[m].smb3.name==smb3) MxSmb[m].smb3.tkt=tkt3; count++; break; }//for m }//for k }//for j }//for i break; default: break; } if (count>0) Print("Restore "+(string)count+" triangles"); }
Como antes, essa função está em um arquivo separado: fnRestart.mqh
Últimas etapas:
ctrade.SetDeviationInPoints(DEVIATION); ctrade.SetTypeFilling(ORDER_FILLING_FOK); ctrade.SetAsyncMode(true); ctrade.LogLevel(LOG_LEVEL_NO); EventSetTimer(1);
Preste atenção ao modo assíncrono de envio de ordens. A estratégia envolve uma ação mais rápida, portanto usamos este modo de posicionamento. Haverá dificuldades: precisamos de código extra para acompanhar se a posição é aberta com sucesso. Considere tudo isso abaixo.
Por aí fica concluído o bloco OnInit(), podemos avançar para o corpo do robô.
OnTick
Primeiro, vemos se, nas configurações, temos restrições no número máximo de triângulos abertos possível. O fato de houver indica que atingimos o spread definido, e neste tick, pode-se ignorar parte substancial do código:
ushort OpenThree=0; // Número de triângulos abertos for(int j=ArraySize(MxThree)-1;j>=0;j--) if (MxThree[j].status!=0) OpenThree++; // Contamos os que não estão fechados também
A verificação é simples. Nós declaramos a variável local para contagem de triângulos abertos e percorremos nossa matriz principal. Se o status do triângulo não for 0, ele está funcionando.
Após a contagem de triângulos abertos, se a restrição permitir, começamos a examinar todos os triângulos restantes e acompanhar seu status. A função fnCalcDelta() é responsável por isso:
if (inMaxThree==0 || (inMaxThree>0 && inMaxThree>OpenThree)) fnCalcDelta(MxThree,inProfit,inCmnt,inMagic,inLot,inMaxThree,OpenThree); // Calculamos a divergência e imediatamente abrimos
Examinamos seu código:
void fnCalcDelta(stThree &MxSmb[],double prft, string cmnt, ulong magic,double lot, ushort lcMaxThree, ushort &lcOpenThree) { double temp=0; string cmnt_pos=""; for(int i=ArraySize(MxSmb)-1;i>=0;i--) {//for i // Se o triângulo está trabalhando, então ignoramo-lo if(MxSmb[i].status!=0) continue; // Mais uma vez, vamos verificar a disponibilidade de todos os três pares: se pelo menos um deles não estiver disponível, // então não fará sentido calcular todo o triângulo if (!fnSmbCheck(MxSmb[i].smb1.name)) continue; if (!fnSmbCheck(MxSmb[i].smb2.name)) continue; //e se de repente tivermos fechado a negociação em algum par if (!fnSmbCheck(MxSmb[i].smb3.name)) continue; // Calculamos o número de triângulos abertos no início de cada tick, // eles podem ser abertos dentro do tick. Por isso, constantemente acompanhamos seu número if (lcMaxThree>0) {if (lcMaxThree>lcOpenThree); else continue;} // Em seguida, obtemos todos os dados necessários para os cálculos. // Obtemos o valor de tick para cada par. if(!SymbolInfoDouble(MxSmb[i].smb1.name,SYMBOL_TRADE_TICK_VALUE,MxSmb[i].smb1.tv)) continue; if(!SymbolInfoDouble(MxSmb[i].smb2.name,SYMBOL_TRADE_TICK_VALUE,MxSmb[i].smb2.tv)) continue; if(!SymbolInfoDouble(MxSmb[i].smb3.name,SYMBOL_TRADE_TICK_VALUE,MxSmb[i].smb3.tv)) continue; // Obtemos os preços atuais. if(!SymbolInfoTick(MxSmb[i].smb1.name,MxSmb[i].smb1.tick)) continue; if(!SymbolInfoTick(MxSmb[i].smb2.name,MxSmb[i].smb2.tick)) continue; if(!SymbolInfoTick(MxSmb[i].smb3.name,MxSmb[i].smb3.tick)) continue; // Verificamos se Ask ou Bid são iguais a 0. if(MxSmb[i].smb1.tick.ask<=0 || MxSmb[i].smb1.tick.bid<=0 || MxSmb[i].smb2.tick.ask<=0 || MxSmb[i].smb2.tick.bid<=0 || MxSmb[i].smb3.tick.ask<=0 || MxSmb[i].smb3.tick.bid<=0) continue; // Calculamos o volume para o terceiro par. O volume dos dois primeiros pares é conhecido, ele é o mesmo e é fixo. // O volume do terceiro par sempre muda. Porém, ele é calculado só se, nas variáveis de início, o valor do lote é igual a 0. // Quando o lote é zero em todos os lugares, será utilizado o volume mínimo // A lógica de cálculo de volume é simples. Lembramos nossa variação de triângulo: EURUSD=EURGBP*GBPUSD. Quantidade de libras compradas ou vendidas // depende das cotações do EURGBP, e no terceiro par, esta terceira moeda está em primeiro lugar. Isso nós liberta da parte dos cálculos, // tomando como volume o preço do segundo par. Eu tomei a média entre o Ask e o Bid. // Não esquecemos a alteração do volume de negociação de entrada. if (lot>0) MxSmb[i].smb3.lot=NormalizeDouble((MxSmb[i].smb2.tick.ask+MxSmb[i].smb2.tick.bid)/2*MxSmb[i].smb1.lot,MxSmb[i].smb3.digits_lot); // Se o volume calculado estiver fora dos limites admissíveis, informamos o usuário sobre isso. // Marcamos esse triângulo como não funcional if (MxSmb[i].smb3.lot<MxSmb[i].smb3.lot_min || MxSmb[i].smb3.lot>MxSmb[i].smb3.lot_max) { Alert("The calculated lot for ",MxSmb[i].smb3.name," is out of range. Min/Max/Calc: ", DoubleToString(MxSmb[i].smb3.lot_min,MxSmb[i].smb3.digits_lot),"/", DoubleToString(MxSmb[i].smb3.lot_max,MxSmb[i].smb3.digits_lot),"/", DoubleToString(MxSmb[i].smb3.lot,MxSmb[i].smb3.digits_lot)); Alert("Triangle: "+MxSmb[i].smb1.name+" "+MxSmb[i].smb2.name+" "+MxSmb[i].smb3.name+" - DISABLED"); MxSmb[i].smb1.name=""; continue; } // Calculamos nossas despesas, ou seja, spread+comissão. pr = spread em pontos inteiros. // O spread não nós permite ganhar com essa estratégia, por isso é preciso calculá-lo. // Pode-se tomar de vez o spread em pontos, em vez de utilizar a diferença de preços multiplicada pelo ponto inverso. MxSmb[i].smb1.sppoint=NormalizeDouble(MxSmb[i].smb1.tick.ask-MxSmb[i].smb1.tick.bid,MxSmb[i].smb1.digits)*MxSmb[i].smb1.Rpoint; MxSmb[i].smb2.sppoint=NormalizeDouble(MxSmb[i].smb2.tick.ask-MxSmb[i].smb2.tick.bid,MxSmb[i].smb2.digits)*MxSmb[i].smb2.Rpoint; MxSmb[i].smb3.sppoint=NormalizeDouble(MxSmb[i].smb3.tick.ask-MxSmb[i].smb3.tick.bid,MxSmb[i].smb3.digits)*MxSmb[i].smb3.Rpoint; if (MxSmb[i].smb1.sppoint<=0 || MxSmb[i].smb2.sppoint<=0 || MxSmb[i].smb3.sppoint<=0) continue; // Agora calculamos o spread na moeda do depósito. // Na moeda, o custo de 1 tick é sempre igual ao parâmetro SYMBOL_TRADE_TICK_VALUE. // Além disso, não nós esquecemos dos volumes de negociação MxSmb[i].smb1.spcost=MxSmb[i].smb1.sppoint*MxSmb[i].smb1.tv*MxSmb[i].smb1.lot; MxSmb[i].smb2.spcost=MxSmb[i].smb2.sppoint*MxSmb[i].smb2.tv*MxSmb[i].smb2.lot; MxSmb[i].smb3.spcost=MxSmb[i].smb3.sppoint*MxSmb[i].smb3.tv*MxSmb[i].smb3.lot; // Bem, essas são nossas despesas para o volume de negociação definido acrescentando a comissão estabelecida pelo usuário MxSmb[i].spread=MxSmb[i].smb1.spcost+MxSmb[i].smb2.spcost+MxSmb[i].smb3.spcost+prft; // Podemos monitorar quando o Ask < Bid, embora seja uma situação muito rara, // ela pode ser acompanhada separadamente. Além disso, a arbitragem, espalhada no tempo, também lida com essa situação. // Assim, a permanência na posição é livre de riscos, e por isso: nós compramos eurusd, // e de vez vendemos através de eurgbp e gbpusd. // Ou seja, nós vimos ask eurusd< bid eurgbp * bid gbpusd. Essas situações costumam ser muitas, mas, para entrar no mercado com sucesso, isso é pouco. // Calculamos também os custos do spread. É necessário entrar não só quando Ask < Bid, mas também quando a diferença entre // o custo do spread. // Concordamos em que a compra significa comprar o primeiro símbolo e vender os dois restantes, // enquanto a venda é vender o primeiro par e comprar os dois restantes. temp=MxSmb[i].smb1.tv*MxSmb[i].smb1.Rpoint*MxSmb[i].smb1.lot; // Examinemos a fórmula de cálculo. // 1. Entre parênteses, em cada preço, a derrapagem é ajustada para o pior lado: MxSmb[i].smb2.tick.bid-MxSmb[i].smb2.dev // 2. Como mostrado, na fórmula acima , bid eurgbp * bid gbpusd, são os preços do segundo e terceiro símbolo que multiplicamos: // (MxSmb[i].smb2.tick.bid-MxSmb[i].smb2.dev)*(MxSmb[i].smb3.tick.bid-MxSmb[i].smb3.dev) // 3. A seguir, calculamos a diferença entre o Ask e o Bid // 4. Obtivemos a diferença em pontos que é necessário converter em pontos, isto é, multiplicar // custo do ponto e volume de negociação. Para fazer isso, tomamos os valores do primeiro par. // Se quisermos construir um triângulo movendo todos os pares na mesma direção e comparando com 1, os cálculos serão maiores. MxSmb[i].PLBuy=((MxSmb[i].smb2.tick.bid-MxSmb[i].smb2.dev)*(MxSmb[i].smb3.tick.bid-MxSmb[i].smb3.dev)-(MxSmb[i].smb1.tick.ask+MxSmb[i].smb1.dev))*temp; MxSmb[i].PLSell=((MxSmb[i].smb1.tick.bid-MxSmb[i].smb1.dev)-(MxSmb[i].smb2.tick.ask+MxSmb[i].smb2.dev)*(MxSmb[i].smb3.tick.ask+MxSmb[i].smb3.dev))*temp; // Obtivemos o cálculo do montante que podemos ganhar ou perder, se compramos ou vendemos o triângulo. // Resta compará-lo com o custo para decidir se entramos em uma transação. Ajustamos para dois 2 caracteres. MxSmb[i].PLBuy= NormalizeDouble(MxSmb[i].PLBuy,2); MxSmb[i].PLSell= NormalizeDouble(MxSmb[i].PLSell,2); MxSmb[i].spread= NormalizeDouble(MxSmb[i].spread,2); // Se houver um lucro potencial, devemos verificar se há suficiente dinheiro para a abertura. if (MxSmb[i].PLBuy>MxSmb[i].spread || MxSmb[i].PLSell>MxSmb[i].spread) { // Eu só considerei a margem inteira para compra. Como ela, seja como for, é maior do que para venda, pode-se não levar em conta a direção do negócio. // Observemos o coeficiente crescente. Não se deve abrir o triângulo quando a margem é ligeiramente suficiente. O coeficiente crescente por padrão é = 20% if(OrderCalcMargin(ORDER_TYPE_BUY,MxSmb[i].smb1.name,MxSmb[i].smb1.lot,MxSmb[i].smb1.tick.ask,MxSmb[i].smb1.mrg)) if(OrderCalcMargin(ORDER_TYPE_BUY,MxSmb[i].smb2.name,MxSmb[i].smb2.lot,MxSmb[i].smb2.tick.ask,MxSmb[i].smb2.mrg)) if(OrderCalcMargin(ORDER_TYPE_BUY,MxSmb[i].smb3.name,MxSmb[i].smb3.lot,MxSmb[i].smb3.tick.ask,MxSmb[i].smb3.mrg)) if(AccountInfoDouble(ACCOUNT_MARGIN_FREE)>((MxSmb[i].smb1.mrg+MxSmb[i].smb2.mrg+MxSmb[i].smb3.mrg)*CF)) //verificamos a margem livre { // Estamos quase prontos para abrir, resta encontrar um magic livre em nossa gama. // O magic inicial é definido nos parâmetros da entrada na variável inMagic, por padrão é 300. // O intervalo de magics é definido em MAGIC, por padrão é 200. MxSmb[i].magic=fnMagicGet(MxSmb,magic); if (MxSmb[i].magic<=0) { // Se retornar 0, todos os magics estão ocupados. Enviamos uma mensagem sobre isto e imprimimos. Print("Free magic ended\nNew triangles will not open"); break; } // Definimos o magic encontrado para o robô ctrade.SetExpertMagicNumber(MxSmb[i].magic); // Criamos o comentário para o triângulo cmnt_pos=cmnt+(string)MxSmb[i].magic+" Open"; // Abrimos, memorizando o tempo de envio do triângulo para a abertura. // Isso é para não estar esperando. // Por padrão, no define MAXTIMEWAIT é definido o tempo de espera antes da abertura, isto é, 3 segundos. // Se durante este tempo nós não abrimos, enviamos para fechamento o que teve tempo para se abrir. MxSmb[i].timeopen=TimeCurrent(); if (MxSmb[i].PLBuy>MxSmb[i].spread) fnOpen(MxSmb,i,cmnt_pos,true,lcOpenThree); if (MxSmb[i].PLSell>MxSmb[i].spread) fnOpen(MxSmb,i,cmnt_pos,false,lcOpenThree); // Imprimimos a mensagem sobre a abertura do triângulo. if (MxSmb[i].status==1) Print("Open triangle: "+MxSmb[i].smb1.name+" + "+MxSmb[i].smb2.name+" + "+MxSmb[i].smb3.name+" magic: "+(string)MxSmb[i].magic); } } }//for i }
Eu minuciosamente provi a função de comentários e explicações. Espero que o leitor não fique com dúvidas sobre ela. Por baixo dos panos, restam dois momentos: o mecanismo de seleção do magic disponível e abertura direta do triângulo.
Assim escolhemos o magic disponível:
ulong fnMagicGet(stThree &MxSmb[],ulong magic) { int mxsize=ArraySize(MxSmb); bool find; // Pode-se percorrer todos os triângulos abertos em nossa matriz. // Mas eu escolhi outra opção, isto é, percorrer o intervalo do magic, // e disparar o já escolhido na matriz. for(ulong i=magic;i<magic+MAGIC;i++) { find=false; // Magic em i. Verifique se ele é atribuído a algum triângulo dos abertos. for(int j=0;j<mxsize;j++) if (MxSmb[j].status>0 && MxSmb[j].magic==i) { find=true; break; } // Se o magic não é usado, saímos do ciclo, sem esperar pela sua conclusão. if (!find) return(i); } return(0); }
Assim abrimos o triângulo:
bool fnOpen(stThree &MxSmb[],int i,string cmnt,bool side, ushort &opt) { // Sinalizador de abertura de primeira ordem. bool openflag=false; // Se não tivermos permissão para negociar, não operamos. if (!cterm.IsTradeAllowed()) return(false); if (!cterm.IsConnected()) return(false); switch(side) { case true: // Se, após enviar a ordem para abertura, for retornado false, não fará sentido enviar para abertura os 2 pares restantes. // Melhor tentemos novamente no próximo tick. O robô também não se ocupa de acabar de abrir o triângulo. // Se, após enviar ordens, algo não se abrir, // então após o tempo especificado no define MAXTIMEWAIT, fechamos o triângulo que não é aberto. if(ctrade.Buy(MxSmb[i].smb1.lot,MxSmb[i].smb1.name,0,0,0,cmnt)) { openflag=true; MxSmb[i].status=1; opt++; // Em seguida, a lógica é a mesma, se não conseguimos abrir, o triângulo deverá ser fechado. if(ctrade.Sell(MxSmb[i].smb2.lot,MxSmb[i].smb2.name,0,0,0,cmnt)) ctrade.Sell(MxSmb[i].smb3.lot,MxSmb[i].smb3.name,0,0,0,cmnt); } break; case false: if(ctrade.Sell(MxSmb[i].smb1.lot,MxSmb[i].smb1.name,0,0,0,cmnt)) { openflag=true; MxSmb[i].status=1; opt++; if(ctrade.Buy(MxSmb[i].smb2.lot,MxSmb[i].smb2.name,0,0,0,cmnt)) ctrade.Buy(MxSmb[i].smb3.lot,MxSmb[i].smb3.name,0,0,0,cmnt); } break; } return(openflag); }
Geralmente, as funções acima estão localizadas nos arquivos separados fnCalcDelta.mqh, fnMagicGet.mqh e fnOpen.mqh.
Então, nós encontramos o triângulo necessário e enviamo-lo para a abertura. No MetaTrader 4 ou em contas de cobertura no MetaTrader 5, isso na verdade marca o fim do trabalho do expert advisor. Mas ainda temos de rastrear o sucesso da abertura do triângulo. Para fazer isso, eu não uso os eventos OnTrade e OnTradeTransaction, porque, em seu caso, não há nenhuma garantia de receber um bom resultado. Em vez deles, eu verifico o número de posições existentes.
Consideremos a função de controle de abertura da posição:
void fnOpenCheck(stThree &MxSmb[], int accounttype, int fh) { uchar cnt=0; // Contador de posições abertas no triângulo ulong tkt=0; // Tick atual string smb=""; // Símbolo atual // Verificamos nossa matriz de triângulos for(int i=ArraySize(MxSmb)-1;i>=0;i--) { // Analisamos apenas os triângulos com status 1, ou seja, enviados para abertura if(MxSmb[i].status!=1) continue; if ((TimeCurrent()-MxSmb[i].timeopen)>MAXTIMEWAIT) { // Se exceder o tempo previsto para a abertura, rotularemos o triângulo como um pronto para ser fechado MxSmb[i].status=3; Print("Not correct open: "+MxSmb[i].smb1.name+" + "+MxSmb[i].smb2.name+" + "+MxSmb[i].smb3.name); continue; } cnt=0; switch(accounttype) { case ACCOUNT_MARGIN_MODE_RETAIL_HEDGING: // Verificamos todas as posições abertas. Esta verificação é feita para cada triângulo. for(int j=PositionsTotal()-1;j>=0;j--) if (PositionSelectByTicket(PositionGetTicket(j))) if (PositionGetInteger(POSITION_MAGIC)==MxSmb[i].magic) { // Obtemos o símbolo e bilhete da posição em consideração.tkt=PositionGetInteger(POSITION_TICKET); smb=PositionGetString(POSITION_SYMBOL); // Verificamos se, no triângulo examinado, está a posição atual entre as que precisamos. // Se houver, então podemos aumentar o contador e memorizamos o bilhete e preço de abertura. if (smb==MxSmb[i].smb1.name){ cnt++; MxSmb[i].smb1.tkt=tkt; MxSmb[i].smb1.price=PositionGetDouble(POSITION_PRICE_OPEN);} else if (smb==MxSmb[i].smb2.name){ cnt++; MxSmb[i].smb2.tkt=tkt; MxSmb[i].smb2.price=PositionGetDouble(POSITION_PRICE_OPEN);} else if (smb==MxSmb[i].smb3.name){ cnt++; MxSmb[i].smb3.tkt=tkt; MxSmb[i].smb3.price=PositionGetDouble(POSITION_PRICE_OPEN);} // Se encontrarmos as três posições necessárias, nosso triângulo será aberto com sucesso. Mudamos seu status para 2 (aberta). // Gravamos os dados sobre a abertura no arquivo de log if (cnt==3) { MxSmb[i].status=2; fnControlFile(MxSmb,i,fh); break; } } break; default: break; } } }
A a função de gravação no arquivo de log usada é simples:
void fnControlFile(stThree &MxSmb[],int i, int fh) { FileWrite(fh,"============"); FileWrite(fh,"Open:",MxSmb[i].smb1.name,MxSmb[i].smb2.name,MxSmb[i].smb3.name); FileWrite(fh,"Tiket:",MxSmb[i].smb1.tkt,MxSmb[i].smb2.tkt,MxSmb[i].smb3.tkt); FileWrite(fh,"Lot",DoubleToString(MxSmb[i].smb1.lot,MxSmb[i].smb1.digits_lot),DoubleToString(MxSmb[i].smb2.lot,MxSmb[i].smb2.digits_lot),DoubleToString(MxSmb[i].smb3.lot,MxSmb[i].smb3.digits_lot)); FileWrite(fh,"Margin",DoubleToString(MxSmb[i].smb1.mrg,2),DoubleToString(MxSmb[i].smb2.mrg,2),DoubleToString(MxSmb[i].smb3.mrg,2)); FileWrite(fh,"Ask",DoubleToString(MxSmb[i].smb1.tick.ask,MxSmb[i].smb1.digits),DoubleToString(MxSmb[i].smb2.tick.ask,MxSmb[i].smb2.digits),DoubleToString(MxSmb[i].smb3.tick.ask,MxSmb[i].smb3.digits)); FileWrite(fh,"Bid",DoubleToString(MxSmb[i].smb1.tick.bid,MxSmb[i].smb1.digits),DoubleToString(MxSmb[i].smb2.tick.bid,MxSmb[i].smb2.digits),DoubleToString(MxSmb[i].smb3.tick.bid,MxSmb[i].smb3.digits)); FileWrite(fh,"Price open",DoubleToString(MxSmb[i].smb1.price,MxSmb[i].smb1.digits),DoubleToString(MxSmb[i].smb2.price,MxSmb[i].smb2.digits),DoubleToString(MxSmb[i].smb3.price,MxSmb[i].smb3.digits)); FileWrite(fh,"Tick value",DoubleToString(MxSmb[i].smb1.tv,MxSmb[i].smb1.digits),DoubleToString(MxSmb[i].smb2.tv,MxSmb[i].smb2.digits),DoubleToString(MxSmb[i].smb3.tv,MxSmb[i].smb3.digits)); FileWrite(fh,"Spread point",DoubleToString(MxSmb[i].smb1.sppoint,0),DoubleToString(MxSmb[i].smb2.sppoint,0),DoubleToString(MxSmb[i].smb3.sppoint,0)); FileWrite(fh,"Spread $",DoubleToString(MxSmb[i].smb1.spcost,3),DoubleToString(MxSmb[i].smb2.spcost,3),DoubleToString(MxSmb[i].smb3.spcost,3)); FileWrite(fh,"Spread all",DoubleToString(MxSmb[i].spread,3)); FileWrite(fh,"PL Buy",DoubleToString(MxSmb[i].PLBuy,3)); FileWrite(fh,"PL Sell",DoubleToString(MxSmb[i].PLSell,3)); FileWrite(fh,"Magic",string(MxSmb[i].magic)); FileWrite(fh,"Time open",TimeToString(MxSmb[i].timeopen,TIME_DATE|TIME_SECONDS)); FileWrite(fh,"Time current",TimeToString(TimeCurrent(),TIME_DATE|TIME_SECONDS)); FileFlush(fh); }
Bem, encontramos o triângulo para entrar, abrimos com sucesso a posição com base nele. Agora temos que calcular o quanto ganhamos graças a ele.
void fnCalcPL(stThree &MxSmb[], int accounttype, double prft) { // Novamente revemos nossa matriz de triângulos. // A velocidade de abertura e fechamento são pontos extremamente importantes desta estratégia. // Então, assim que encontrarmos o triângulo para fechamento, realizamos o fechamento imediatamente. bool flag=cterm.IsTradeAllowed() & cterm.IsConnected(); for(int i=ArraySize(MxSmb)-1;i>=0;i--) {//for // Estamos apenas interessado no triângulos com o status 2 ou 3. // Estado 3 - fechar o triângulo - pode ser obtido se o triângulo não é completamente aberto if(MxSmb[i].status==2 || MxSmb[i].status==3); else continue; // Calculamos ganhou o triângulo if (MxSmb[i].status==2) { MxSmb[i].pl=0; // Redefinimos o lucro switch(accounttype) {//switch case ACCOUNT_MARGIN_MODE_RETAIL_HEDGING: if (PositionSelectByTicket(MxSmb[i].smb1.tkt)) MxSmb[i].pl=PositionGetDouble(POSITION_PROFIT); if (PositionSelectByTicket(MxSmb[i].smb2.tkt)) MxSmb[i].pl+=PositionGetDouble(POSITION_PROFIT); if (PositionSelectByTicket(MxSmb[i].smb3.tkt)) MxSmb[i].pl+=PositionGetDouble(POSITION_PROFIT); break; default: break; }//switch // Redondeamos para 2 caracteres MxSmb[i].pl=NormalizeDouble(MxSmb[i].pl,2); // No encerramento deixamo-nós estar um pouco mais. Eu costumo usar a seguinte lógica: // situação de arbitragem não é normal e não deve ocorrer, ou seja, quando ela surge podemos esperar que volte para o // status quando não havia arbitragem. Será que vamos ser capazes de ganhar? Em outras palavras, não podemos dizer se // continua ou não a obtenção do lucro. Então eu prefiro fechar a posição imediatamente após cobertos a comissão e o spread. // O cálculo na arbitragem triangular é sobre os pontos, aqui não tem que contar com grandes movimentos. // No entanto, na variável "Comissão", nos parâmetros de entrada, você pode aguardar a chegada do lucro desejado. // Assim, se nós ganhamos mais do que gastamos, atribuímos à posição o status de "enviar para o fechamento". if (flag && MxSmb[i].pl>prft) MxSmb[i].status=3; } // Encerramento do triângulo somente se permitido o comércio. if (flag && MxSmb[i].status==3) fnCloseThree(MxSmb,accounttype,i); }//for }
Uma função simples responde pelo fechamento do triângulo:
void fnCloseThree(stThree &MxSmb[], int accounttype, int i) { // Antes de encerrar, é necessário verificar a disponibilidade de todos os pares no triângulo. // Quebrar o triângulo é muito errado e perigoso, e se você trabalhar na conta de compensação, // mais tarde será gerada confusão nas posições. if(fnSmbCheck(MxSmb[i].smb1.name)) if(fnSmbCheck(MxSmb[i].smb2.name)) if(fnSmbCheck(MxSmb[i].smb3.name)) // Se tudo estiver disponível, fechamos todas as 3 posições usando a biblioteca padrão. // Após o fechamento, é necessário verificar o sucesso da ação. switch(accounttype) { case ACCOUNT_MARGIN_MODE_RETAIL_HEDGING: ctrade.PositionClose(MxSmb[i].smb1.tkt); ctrade.PositionClose(MxSmb[i].smb2.tkt); ctrade.PositionClose(MxSmb[i].smb3.tkt); break; default: break; } }
Praticamente, temos chegado quase ao fim, resta verificar o sucesso do fechamento e imprimir qualquer coisa na tela. Quando o robô não escreve nada, é provável que não este funcionando.
Aqui está nossa verificação do sucesso do fechamento. A propósito, para abertura e fechamento pode ser implementada uma única função, simplesmente mudando a direção do negócio, mas não gosto muito desta opção, porque há ainda umas pequenas diferenças processuais entre a abertura e o fechamento.
Verificamos o sucesso do fechamento:
void fnCloseCheck(stThree &MxSmb[], int accounttype,int fh) { // Percorremos a matriz de triângulos. for(int i=ArraySize(MxSmb)-1;i>=0;i--) { // Estamos interessados em apenas aqueles cujo status = 3, ou seja, já fechados ou enviados para fechamento. if(MxSmb[i].status!=3) continue; switch(accounttype) { case ACCOUNT_MARGIN_MODE_RETAIL_HEDGING: // O fato de não conseguir identificar um único par de todo o triângulo indica que encerramos com sucesso. Retornamos o status para 0 if (!PositionSelectByTicket(MxSmb[i].smb1.tkt)) if (!PositionSelectByTicket(MxSmb[i].smb2.tkt)) if (!PositionSelectByTicket(MxSmb[i].smb3.tkt)) { // Significa que encerramos com sucesso MxSmb[i].status=0; Print("Close triangle: "+MxSmb[i].smb1.name+" + "+MxSmb[i].smb2.name+" + "+MxSmb[i].smb3.name+" magic: "+(string)MxSmb[i].magic+" P/L: "+DoubleToString(MxSmb[i].pl,2)); if (fh!=INVALID_HANDLE) { FileWrite(fh,"============"); FileWrite(fh,"Close:",MxSmb[i].smb1.name,MxSmb[i].smb2.name,MxSmb[i].smb3.name); FileWrite(fh,"Lot",DoubleToString(MxSmb[i].smb1.lot,MxSmb[i].smb1.digits_lot),DoubleToString(MxSmb[i].smb2.lot,MxSmb[i].smb2.digits_lot),DoubleToString(MxSmb[i].smb3.lot,MxSmb[i].smb3.digits_lot)); FileWrite(fh,"Tiket",string(MxSmb[i].smb1.tkt),string(MxSmb[i].smb2.tkt),string(MxSmb[i].smb3.tkt)); FileWrite(fh,"Magic",string(MxSmb[i].magic)); FileWrite(fh,"Profit",DoubleToString(MxSmb[i].pl,3)); FileWrite(fh,"Time current",TimeToString(TimeCurrent(),TIME_DATE|TIME_SECONDS)); FileFlush(fh); } } break; } } }
E, finalmente, imprimimos algo na forma de comentários na tela. Isso é um tipo de "cosmético" para acompanhamento visual. Exibimos o seguinte:
- Número de triângulos acompanhados
- Triângulos abertos
- os 5 triângulos mais mais próximo da abertura
- Triângulos abertos, se houver
Aqui está o código desta função:
void fnCmnt(stThree &MxSmb[], ushort lcOpenThree) { int total=ArraySize(MxSmb); string line="=============================\n"; string txt=line+MQLInfoString(MQL_PROGRAM_NAME)+": ON\n"; txt=txt+"Total triangles: "+(string)total+"\n"; txt=txt+"Open triangles: "+(string)lcOpenThree+"\n"+line; // Número máximo de triângulos que é exibido na tela short max=5; max=(short)MathMin(total,max); // Exibir os próximos 5 para abertura short index[]; // Matriz de índices ArrayResize(index,max); ArrayInitialize(index,-1); // Não é usado short cnt=0,num=0; while(cnt<max && num<total) // Para começar, tomamos os primeiros max dos índices fechados dos triângulos { if(MxSmb[num].status!=0) {num++;continue;} index[cnt]=num; num++;cnt++; } // Somente faz sentido classificar e buscar se há mais elementos do que é possível exibir na tela. if (total>max) for(short i=max;i<total;i++) { // Os triângulos abertos são mostrados abaixo. if(MxSmb[i].status!=0) continue; for(short j=0;j<max;j++) { if (MxSmb[i].PLBuy>MxSmb[index[j]].PLBuy) {index[j]=i;break;} if (MxSmb[i].PLSell>MxSmb[index[j]].PLSell) {index[j]=i;break;} } } // Exibimos os triângulos mais próximos da abertura. bool flag=true; for(short i=0;i<max;i++) { cnt=index[i]; if (cnt<0) continue; if (flag) { txt=txt+"Smb1 Smb2 Smb3 P/L Buy P/L Sell Spread\n"; flag=false; } txt=txt+MxSmb[cnt].smb1.name+" + "+MxSmb[cnt].smb2.name+" + "+MxSmb[cnt].smb3.name+":"; txt=txt+" "+DoubleToString(MxSmb[cnt].PLBuy,2)+" "+DoubleToString(MxSmb[cnt].PLSell,2)+" "+DoubleToString(MxSmb[cnt].spread,2)+"\n"; } // Exibimos os triângulos abertos. txt=txt+line+"\n"; for(int i=total-1;i>=0;i--) if (MxSmb[i].status==2) { txt=txt+MxSmb[i].smb1.name+"+"+MxSmb[i].smb2.name+"+"+MxSmb[i].smb3.name+" P/L: "+DoubleToString(MxSmb[i].pl,2); txt=txt+" Time open: "+TimeToString(MxSmb[i].timeopen,TIME_DATE|TIME_MINUTES|TIME_SECONDS); txt=txt+"\n"; } Comment(txt); }
Teste
Pode-se, por curiosidade, realizar o teste no modo de simulação de ticks, comparar com o teste em ticks reais. É possível ir ainda mais longe, ou seja, comparar os resultados de teste em ticks reais com o trabalho real, e concluir que o multitestador está muito longe da realidade.
Os resultados de trabalho mostram que se pode contar com 3-4 negócios por semana, em média. Na maioria das vezes, a posição é aberta durante a noite e, como regra, no triângulo há moedas menos líquidas, como TRY, NOK, SEK e outras similares. O lucro do robô depende do volume negociado, e como os trades ocorrem com pouca frequência, este expert advisor pode facilmente manipular grandes quantidades, operando em paralelo com outros robôs.
O risco do robô pode ser calculado facilmente: 3 spreads * número de triângulos abertos.
Para a preparação dos pares de moedas que podem ser usados para trabalhar, eu recomendo, em primeiro lugar, abrir todos os símbolos e, em seguida, ocultar aqueles cuja negociação é proibida e que não são pares de moedas. Esta operação pode ser realizada mais rapidamente usando o script: https://www.mql5.com/pt/market/product/25256
Lembre que o histórico no testador não é baixado a partir do servidor da corretora, uma vez que ele tem que ser pré-carregado no terminal do cliente. Por isso, isso deve ser feito quer por conta própria antes do iniciar o teste ou re-utilizando o script acima.
Perspetivas de desenvolvimento
Será que pode ser aprimorado o desempenho? Claro que é possível. Para fazer isso, você precisa fazer seu agregador de liquidez, mas a desvantagem dessa abordagem é a necessidade de abrir contas em várias corretoras.
Também pode-se acelerar os resultados do teste. Para fazer isso, existem duas maneiras que se podem combinar. O primeiro passo é realizar o cálculo discreto, constantemente rastreando apenas os triângulos onde a probabilidade de entrada é muito alta. A segunda maneira consiste em usar OpenCL, o que é muito razoável para este robô.
Arquivos usados no artigo
№ | Nome do arquivo | Descrição |
---|---|---|
1 | var.mqh | Descritas todas as variáveis, defines e inputs. |
2 | fnWarning.mqh | Verificação das condições iniciais para o funcionamento correto do Expert Advisor: variáveis de entrada, configurações, ambiente. |
3 | fnSetThree.mqh | Formamos triângulos de pares de moedas. Aqui é selecionada a fonte dos pares, isto é, Observação do mercado ou um arquivo previamente preparado. |
4 | fnSmbCheck.mqh | Função para verificar se o símbolo está disponível para negociação e outras restrições. NB: Sessões de negociação e cotação não são verificadas no robô |
5 | fnChangeThree.mqh | Alteramos a localização dos pares de moedas no triângulo, de modo que sejam construídos seguindo o mesmo princípio. |
6 | fnSmbLoad.mqh | Transferimos diferentes dados de símbolos, preços, pontos, restrições de volumes e assim por diante. |
7 | fnCalcDelta.mqh | Calculamos todas as separações no triângulo, aqui consideramos todo os gastos adicionais: spread, comissão, derrapagem. |
8 | fnMagicGet.mqh | Procuramos o magic que pode ser usado para o triângulo atual |
9 | fnOpenCheck.mqh | Verifique se o triângulo foi aberto com sucesso |
10 | fnCalcPL.mqh | Calculamos o lucro/prejuízo do triângulo |
11 | fnCreateFileSymbols.mqh | A função que cria um arquivo com triângulos para operar. No arquivo há mais e mais dados (para fins de informação). |
12 | fnControlFile.mqh | Função que realiza o arquivo de log. Aqui estão descritas todas as aberturas e fechamentos com os dados necessários. |
13 | fnCloseThree.mqh | Fechamento do triângulo |
14 | fnCloseCheck.mqh | Verificamos se o triângulo foi fechado completamente |
15 | fnCmnt.mqh | Exibição de comentários na tela |
16 | fnRestart.mqh | Ao iniciar o robô, verificamos se há um triângulo previamente aberto, e se houver, continuamos imprimindo-os. |
17 | fnOpen.mqh | Abrimos o triângulo |
18 | Support.mqh | Classe de apoio adicional. Nela há apenas uma função que se encarrega de contar o número de casas decimais do número fracionário. |
19 | head.mqh | Este arquivo descreve os cabeçalhos de todos os arquivos acima. |
Traduzido do russo pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/ru/articles/3150





- 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
Olá meus parabéns pelo EA, mas eu percebi que em conta demonstrativa ele não abre ordens, deixei mais de uma semana e não abriu nenhuma ordem, existe algo que eu possa fazer para resolver isto?
Boa tarde. O código do EA está aberto, você precisa olhar nele. Eu não uso mais essa estratégia.
Boa tarde. O código do EA está aberto, você precisa olhar nele. Eu não uso mais essa estratégia.
Olá, Qual a razão de não utilizar mais essa estratégia, ela não se mostrou lucrativa?
Qual estrategia esta utilizando no momento?
Este fórum é em Português, não Inglês. Por favor honrar o idioma.
Este fórum é em Português, não Inglês. Por favor honrar o idioma.
Opá fernando, desculpe mesmo.
Eu sou brasileiro mas estou acostumado em usar a tradução do google nas paginas dos foruns que nem percebi que esse era em português.
Mas obrigado pela correção.