Multiple Symbol Analysis With Python And MQL5 (Part 3): Taxas de Câmbio Triangulares
Os mercados financeiros são inerentemente ruidosos. É comum que traders sofram drawdowns desnecessários causados por sinais de negociação falsos que incentivaram o trader a abrir suas posições prematuramente. Existem muitas estratégias e princípios de trading desenvolvidos à luz desse problema. A maioria desses axiomas essencialmente ensina o trader a esperar antes de agir, buscando fontes alternativas de confirmação ou sinais adicionais de força.
Essas regras geralmente não possuem um horizonte especificado dentro do qual os sinais de confirmação devem ser encontrados, e ocasionalmente podem causar arrependimento ao trader, já que traders que seguem essas regras práticas tendem a perder bons níveis de entrada na maioria das operações que eventualmente decidem executar.
É evidente para o leitor que existe uma clara necessidade de tentar formular estratégias de negociação capazes de medir a força em um mercado o mais próximo possível do tempo real. Pode ser possível alcançar isso se tentarmos aproveitar mercados conectados e buscar padrões previsíveis que façam sentido para nós. Podemos observar padrões entre mercados que acreditamos ser previsíveis, em vez de potencialmente perder pips em cada operação que nos interessa, acumulando perdas ao longo do tempo.
Para leitores que talvez sejam novos na linha de pensamento relacionada a padrões entre mercados, forneceremos uma breve introdução ao tema para que você compreenda por que alguns traders afirmam que esses padrões entre mercados geralmente são previsíveis e, quando aplicados corretamente, têm potencial para serem robustos.
Cerca de 90% das commodities do mundo são precificadas em Dólares Americanos. No entanto, algumas commodities são tão amplamente negociadas que frequentemente são cotadas em várias moedas ao mesmo tempo.
Metais preciosos, como a Prata, geralmente são cotados simultaneamente em Dólares Americanos e Euros. Usaremos a Prata como exemplo neste artigo. É possível que sua corretora ofereça simultaneamente cotações do preço da prata em Dólares (XAGUSD) e em Euros (XAGEUR).
Se quisermos negociar XAGUSD e sabemos que ele está fundamentalmente conectado ao XAGEUR, como podemos aproveitar esse entendimento a nosso favor para aceitar ou rejeitar oportunidades de negociação que nos são apresentadas sem precisar esperar indefinidamente por confirmação?
Ao considerar a taxa de câmbio vigente entre Euros e Dólares (EURUSD), pode ser possível formar uma estratégia de negociação triangular que identifique padrões previsíveis entre o preço da Prata em Dólares, em Euros e a taxa de câmbio justa entre Euro e Dólar (EURUSD). Nosso objetivo é fornecer ao leitor uma estratégia de negociação robusta ao ruído e capaz de revelar o “sentimento oculto” do mercado a partir das cotações comuns recebidas no seu terminal MetaTrader 5.
Visão Geral da Nossa Estratégia de Trading
Antes de entrar nos detalhes da estratégia, primeiro precisamos garantir que todos os leitores tenham uma compreensão fundamental dos conceitos de moeda base e moeda de cotação em um par negociado. Se tomarmos o par EURUSD como exemplo, o EUR (Euro) é a moeda base. A primeira abreviação à esquerda do par é a base. Quando a taxa de câmbio exibida em um gráfico se afasta de 0 para cima, o valor da moeda base está aumentando. Portanto, se estivermos olhando um gráfico da taxa de câmbio EURUSD e o gráfico sobe, isso indica que precisamos vender mais Dólares Americanos para obter 1 Euro no mercado FX spot.

Fig 1: Entendendo a diferença entre a base e a cotação em um par
Por outro lado, o segundo símbolo listado é a moeda de cotação. O valor da cotação aumenta à medida que a taxa de câmbio exibida no gráfico cai em direção a 0. Isso significa que precisaremos vender cada vez menos Dólares Americanos para obter 1 Euro.

Fig 2: Focando na cotação
Agora podemos considerar nossa configuração específica de trading sabendo que nenhum leitor ficará para trás. Estamos analisando três mercados diferentes com o objetivo de negociar apenas um. Nosso alvo é o XAGUSD. Se quisermos entender em qual direção o XAGUSD pode estar se movendo, nossa estratégia começará verificando primeiro a taxa de câmbio EURUSD.
Se a taxa de câmbio EURUSD estiver subindo, então o Euro (cotação) está se valorizando e o Dólar (base) está se desvalorizando. Pode-se esperar que uma commodity precificada tanto em Euros quanto em Dólares possa se tornar mais barata em Euros, já que o Euro ganhou poder de compra, e mais cara em Dólares, já que o Dólar perdeu valor no mercado FX.
Esse experimento mental deve ter dado aos leitores que talvez não estejam familiarizados com esse tipo de estratégia uma ideia de como três mercados podem “espelhar” uns aos outros sob determinadas suposições.
Ao analisar o desempenho desses três mercados ao mesmo tempo, podemos essencialmente criar uma estratégia que talvez não precise sempre esperar por confirmação. Se nossas suposições sobre a natureza interconectada desses mercados forem verdadeiras, então teremos um framework viável que poderá ser adaptado a outros mercados que o leitor deseje negociar.
Um mapa mental visual da estratégia é ilustrado na Fig 4. De forma geral, se a taxa de câmbio EURUSD estiver caindo, esperaríamos que bens precificados em Euros se tornassem mais caros e bens precificados em Dólares se tornassem mais baratos. Nossa estratégia verificará a taxa EURUSD, o preço da Prata em Euros e o preço da Prata em Dólares, procurando identificar esse padrão específico. Se esse padrão entre mercados for encontrado, venderemos o preço da Prata em Dólares.

Fig 3: Visualizando nossas regras para abrir venda no XAUUSD
Aplicamos o mesmo conjunto de regras no sentido oposto quando queremos abrir uma posição comprada no preço da Prata em Dólares. Desejamos observar força no mercado FX respaldando a ação de preço observada tanto na cotação europeia quanto na americana da commodity. Caso contrário, movimentos que não são sustentados por padrões entre mercados podem ser frágeis e facilmente revertidos. Se nossas suposições sobre o comportamento do mercado forem verdadeiras, então nossa estratégia deverá ser sólida. E pode substituir a necessidade de “esperar confirmação”, caso a análise entre mercados faça sentido para o seu portfólio individual.

Fig 4: Visualizando nossas regras de negociação para abrir compra em XAGUSD
O leitor deve estar ciente de que, na realidade, provavelmente existem inúmeros fatores complexos que fazem o preço da Prata subir ou cair. Estamos apenas tentando resumir essas relações complexas utilizando relações mais simples.
Visão Geral do Período de Backtest
Testaremos nossa estratégia de 1º de novembro de 2023 até 1º de janeiro de 2025. Os dados restantes que possuímos, de 1º de novembro de 2022 até o final de outubro de 2023, serão usados para treinar nossa aplicação. Em versões futuras deste artigo, substituiremos nosso modelo simples da natureza interconectada desses mercados por modelos que nosso computador poderá aprender de forma independente. Portanto, embora não utilizemos a partição de treinamento nesta discussão, faremos uso dela em discussões futuras.

Fig 5: Entendendo o período de backtest para nossa discussão
Primeiros Passos em MQL5
Nossa estratégia exige que acompanhemos três mercados simultaneamente. Portanto, vamos começar criando constantes do sistema para acompanhar cada mercado no qual temos interesse. Isso nos permite alternar facilmente entre esses mercados e comparar seu desempenho.
//+------------------------------------------------------------------+ //| Baseline Model.mq5 | //| Gamuchirai Ndawana | //| https://www.mql5.com/en/users/gamuchiraindawa | //+------------------------------------------------------------------+ #property copyright "Gamuchirai Ndawana" #property link "https://www.mql5.com/en/users/gamuchiraindawa" #property version "1.00" //+------------------------------------------------------------------+ //| System Constants | //+------------------------------------------------------------------+ #define SYMBOL_ONE "XAGUSD" //--- Our primary symbol, the price of Silver in USD #define SYMBOL_TWO "XAGEUR" //--- Our secondary symbol, the price of Silver in EUR #define SYMBOL_THREE "EURUSD" //--- Our EURUSD exchange rate. #define FETCH 24 //--- How many bars of data should we fetch? #define TF_1 PERIOD_H1 //--- Our intended time frame #define VOLUME SymbolInfoDouble(SYMBOL_ONE,SYMBOL_VOLUME_MIN) * 10 //--- Our trading volume
Utilizaremos o tipo vector do MQL5 para obter dados de mercado e transformá-los rapidamente.
//+------------------------------------------------------------------+ //| Global variables | //+------------------------------------------------------------------+ vector eurusd,xagusd,xageur; double eurusd_growth,xagusd_growth,xageur_growth,bid,ask; double sl_width = 3e2 * _Point;
A biblioteca de trading nos permite abrir e gerenciar nossas operações, e precisamos dela para o exercício de hoje.
//+------------------------------------------------------------------+ //| Libraries | //+------------------------------------------------------------------+ #include <Trade\Trade.mqh> CTrade Trade;
A linguagem MQL5 foi projetada para nos ajudar a negociar de forma eficaz. Cada evento que você pode imaginar acontecendo no mercado é mapeado para eventos específicos. Receber novos preços é um evento. Quando esse evento é registrado, o manipulador de eventos OnTick() é chamado. As funções dentro desse manipulador serão executadas.
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Setup our technical indicators setup(); //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- New prices have been quoted new_quotes_received(); } //+------------------------------------------------------------------+
Sempre que recebermos novas cotações, primeiro queremos verificar se um candle completo foi formado. Nos casos em que um novo candle for formado, atualizaremos as variáveis do sistema e tentaremos encontrar uma oportunidade de negociação.
//+------------------------------------------------------------------+ //| Custom functions | //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| Updates system variables accordingly | //+------------------------------------------------------------------+ void new_quotes_received(void) { static datetime time_stamp; datetime time = iTime(SYMBOL_ONE,TF_1,0); if(time_stamp != time) { time_stamp = time; update(); } }
Quando nosso sistema é carregado pela primeira vez, queremos garantir que todos os mercados de que precisamos estejam ativados e disponíveis.
//+------------------------------------------------------------------+ //| Setup our technical indicators and select the symbols we need | //+------------------------------------------------------------------+ void setup(void) { //--- Select the symbols we need SymbolSelect(SYMBOL_ONE,true); SymbolSelect(SYMBOL_TWO,true); SymbolSelect(SYMBOL_THREE,true); }
Assim que um novo candle for formado, leremos os preços atualizados de cada um dos mercados que estamos acompanhando. Podemos medir o crescimento de um mercado dividindo seu valor atual pelo seu valor passado. Se essa divisão produzir um valor menor que 1, o valor do mercado está diminuindo. Caso contrário, o mercado está se valorizando.
//+------------------------------------------------------------------+ //| Update our system setup | //+------------------------------------------------------------------+ void update(void) { //--- Fetch updated prices xagusd.CopyRates(SYMBOL_ONE,TF_1,COPY_RATES_CLOSE,1,FETCH); xageur.CopyRates(SYMBOL_TWO,TF_1,COPY_RATES_CLOSE,1,FETCH); eurusd.CopyRates(SYMBOL_THREE,TF_1,COPY_RATES_CLOSE,1,FETCH); //--- Calculate the growth in market prices eurusd_growth = eurusd[0] / eurusd[FETCH - 1]; xageur_growth = xageur[0] / xageur[FETCH - 1]; xagusd_growth = xagusd[0] / xagusd[FETCH - 1]; //--- Update system variables SymbolSelect(SYMBOL_ONE,true); bid = SymbolInfoDouble(SYMBOL_ONE,SYMBOL_BID); ask = SymbolInfoDouble(SYMBOL_ONE,SYMBOL_ASK); //--- Check if we need to setup a new position if(PositionsTotal() == 0) find_setup(); //--- Check if we need to manage our positions if(PositionsTotal() > 0) manage_setup(); //--- Give feedback on the market growth Comment("EURUSD Growth: ",eurusd_growth,"\nXAGEUR Growth: ",xageur_growth,"\nXAGUSD Grwoth: ",xagusd_growth); }
Nossa configuração de trading pode ser imaginada como uma configuração específica que queremos encontrar entre os mercados em relação uns aos outros. Essencialmente queremos ver os movimentos no mercado XAGUSD sendo respaldados pelos movimentos nos mercados EURUSD e XAGEUR, respectivamente. As regras especificadas abaixo são equivalentes à ilustração visual apresentada nas Figuras 4 e 5.
//+------------------------------------------------------------------+ //| Find setup | //+------------------------------------------------------------------+ void find_setup(void) { //--- Check if the current market setup matches our expectations for selling if((eurusd_growth < 1) && (xageur_growth > 1) && (xagusd_growth < 1)) { Trade.Sell(VOLUME,SYMBOL_ONE,bid,(ask + sl_width),(ask - sl_width),""); } //--- Check if the current market setup matches our expectations for buying if((eurusd_growth > 1) && (xageur_growth < 1) && (xagusd_growth > 1)) { Trade.Buy(VOLUME,SYMBOL_ONE,ask,(bid - sl_width),(bid + sl_width),""); } }
Uma vez abertas, nossas operações serão supervisionadas por um trailing stop loss para ajudar a preservar nossos lucros.
//+------------------------------------------------------------------+ //| Manage setup | //+------------------------------------------------------------------+ void manage_setup(void) { //--- Select our open position if(PositionSelect(SYMBOL_ONE)) { double current_sl = PositionGetDouble(POSITION_SL); double current_tp = PositionGetDouble(POSITION_TP); //--- Buy setup if(current_sl < current_tp) { if((bid - sl_width) > current_sl) Trade.PositionModify(SYMBOL_ONE,(bid - sl_width),(bid + sl_width)); } //--- Sell setup if(current_sl > current_tp) { if((ask + sl_width) < current_sl) Trade.PositionModify(SYMBOL_ONE,(ask + sl_width),(ask - sl_width)); } } }Por fim, removeremos as definições das constantes do sistema que definimos anteriormente.
//+------------------------------------------------------------------+ //| Undefine system constants | //+------------------------------------------------------------------+ #undef TF_1 #undef SYMBOL_ONE #undef SYMBOL_TWO #undef SYMBOL_THREE #undef VOLUME #undef FETCH
Quando juntamos todos os componentes do sistema, nossa aplicação está completa.
//+------------------------------------------------------------------+ //| Baseline Model.mq5 | //| Gamuchirai Ndawana | //| https://www.mql5.com/en/users/gamuchiraindawa | //+------------------------------------------------------------------+ #property copyright "Gamuchirai Ndawana" #property link "https://www.mql5.com/en/users/gamuchiraindawa" #property version "1.00" //+------------------------------------------------------------------+ //| System Constants | //+------------------------------------------------------------------+ #define SYMBOL_ONE "XAGUSD" //--- Our primary symbol, the price of Silver in USD #define SYMBOL_TWO "XAGEUR" //--- Our secondary symbol, the price of Silver in EUR #define SYMBOL_THREE "EURUSD" //--- Our EURUSD exchange rate. #define FETCH 24 //--- How many bars of data should we fetch? #define TF_1 PERIOD_H1 //--- Our intended time frame #define VOLUME SymbolInfoDouble(SYMBOL_ONE,SYMBOL_VOLUME_MIN) * 10 //--- Our trading volume //+------------------------------------------------------------------+ //| Global variables | //+------------------------------------------------------------------+ vector eurusd,xagusd,xageur; double eurusd_growth,xagusd_growth,xageur_growth,bid,ask; double sl_width = 3e2 * _Point; //+------------------------------------------------------------------+ //| Libraries | //+------------------------------------------------------------------+ #include <Trade\Trade.mqh> CTrade Trade; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Setup our technical indicators setup(); //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- New prices have been quoted new_quotes_received(); } //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| Custom functions | //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| Updates system variables accordingly | //+------------------------------------------------------------------+ void new_quotes_received(void) { static datetime time_stamp; datetime time = iTime(SYMBOL_ONE,TF_1,0); if(time_stamp != time) { time_stamp = time; update(); } } //+------------------------------------------------------------------+ //| Setup our technical indicators and select the symbols we need | //+------------------------------------------------------------------+ void setup(void) { //--- Select the symbols we need SymbolSelect(SYMBOL_ONE,true); SymbolSelect(SYMBOL_TWO,true); SymbolSelect(SYMBOL_THREE,true); } //+------------------------------------------------------------------+ //| Update our system setup | //+------------------------------------------------------------------+ void update(void) { //--- Fetch updated prices xagusd.CopyRates(SYMBOL_ONE,TF_1,COPY_RATES_CLOSE,1,FETCH); xageur.CopyRates(SYMBOL_TWO,TF_1,COPY_RATES_CLOSE,1,FETCH); eurusd.CopyRates(SYMBOL_THREE,TF_1,COPY_RATES_CLOSE,1,FETCH); //--- Calculate the growth in market prices eurusd_growth = eurusd[0] / eurusd[FETCH - 1]; xageur_growth = xageur[0] / xageur[FETCH - 1]; xagusd_growth = xagusd[0] / xagusd[FETCH - 1]; //--- Update system variables SymbolSelect(SYMBOL_ONE,true); bid = SymbolInfoDouble(SYMBOL_ONE,SYMBOL_BID); ask = SymbolInfoDouble(SYMBOL_ONE,SYMBOL_ASK); //--- Check if we need to setup a new position if(PositionsTotal() == 0) find_setup(); //--- Check if we need to manage our positions if(PositionsTotal() > 0) manage_setup(); //--- Give feedback on the market growth Comment("EURUSD Growth: ",eurusd_growth,"\nXAGEUR Growth: ",xageur_growth,"\nXAGUSD Grwoth: ",xagusd_growth); } //+------------------------------------------------------------------+ //| Find setup | //+------------------------------------------------------------------+ void find_setup(void) { //--- Check if the current market setup matches our expectations for selling if((eurusd_growth < 1) && (xageur_growth > 1) && (xagusd_growth < 1)) { Trade.Sell(VOLUME,SYMBOL_ONE,bid,(ask + sl_width),(ask - sl_width),""); } //--- Check if the current market setup matches our expectations for buying if((eurusd_growth > 1) && (xageur_growth < 1) && (xagusd_growth > 1)) { Trade.Buy(VOLUME,SYMBOL_ONE,ask,(bid - sl_width),(bid + sl_width),""); } } //+------------------------------------------------------------------+ //| Manage setup | //+------------------------------------------------------------------+ void manage_setup(void) { //--- Select our open position if(PositionSelect(SYMBOL_ONE)) { double current_sl = PositionGetDouble(POSITION_SL); double current_tp = PositionGetDouble(POSITION_TP); //--- Buy setup if(current_sl < current_tp) { if((bid - sl_width) > current_sl) Trade.PositionModify(SYMBOL_ONE,(bid - sl_width),(bid + sl_width)); } //--- Sell setup if(current_sl > current_tp) { if((ask + sl_width) < current_sl) Trade.PositionModify(SYMBOL_ONE,(ask + sl_width),(ask - sl_width)); } } } //+------------------------------------------------------------------+ //| Undefine system constants | //+------------------------------------------------------------------+ #undef TF_1 #undef SYMBOL_ONE #undef SYMBOL_TWO #undef SYMBOL_THREE #undef VOLUME #undef FETCH
No início da discussão, especificamos que o backtest será realizado de 1º de novembro de 2023 até janeiro de 2025. Utilizaremos o timeframe H1 para realizar o teste. O H1 deverá fornecer mais oportunidades de negociação do que timeframes maiores, como o Diário, sem nos confundir com tanto ruído quanto timeframes muito pequenos, como o M1.

Fig 6: Datas utilizadas para o backtest da nossa estratégia de XAGUSD
Nossas condições de negociação serão configuradas para simular a experiência de trading real. Selecionaremos atraso aleatório com cada tick baseado em ticks reais. Isso proporciona uma simulação confiável das condições de mercado observadas no passado.

Fig 7: Nossas condições de negociação, utilizando ticks reais, representam a configuração mais realista oferecida para nós
Nossa estratégia produziu uma curva de equity caracterizada por períodos de alta lucratividade, seguidos por períodos prolongados de perdas. A estratégia é lucrativa, mas é instável nessa forma. Como nossa estratégia continua “oscilando” entre períodos lucrativos e não lucrativos, é razoável supor que aprender a identificar esse comportamento periódico pode nos ajudar a tornar a estratégia mais estável.

Fig 8: A curva de equity produzida pela nossa estratégia de negociação
Realizar um backtest utilizando nossa estratégia de negociação revela que nosso sistema poderia potencialmente ter sido muito mais lucrativo. Nosso objetivo de utilizar três mercados para negociar com sucesso apenas um deles está ao nosso alcance. É possível que nossas regras de negociação precisem ser revisadas e possivelmente desafiadas por regras que nosso computador possa gerar a partir dos dados. Isso pode servir como solução para nossas preocupações sobre a estabilidade do algoritmo.

Fig 9: Uma análise detalhada do desempenho da nossa estratégia de negociação
Melhorando Nosso Desempenho Inicial
Há bastante espaço para melhorias em relação à nossa estratégia inicial. Tentaremos corrigir o comportamento instável da estratégia inicial e suavizar as oscilações periódicas que observamos entre períodos lucrativos e períodos prolongados de perdas. A seguir, resumiremos brevemente as mudanças feitas no sistema para obter melhores resultados com nossa estratégia de negociação:
| Melhoria Proposta | Finalidade Pretendida |
|---|---|
| Integração de Indicadores Técnicos para Confirmação Adicional | Podemos filtrar uma quantidade significativa de ruído do mercado utilizando confirmação adicional de indicadores técnicos no nosso terminal MetaTrader 5, reduzindo o atraso em nossa tomada de decisão. |
| Construção de Modelos Estatísticos Individuais para Cada Mercado que Estamos Acompanhando | Ter um modelo do mercado pode nos ajudar a antecipar mudanças na direção do mercado ou na volatilidade, e até mesmo ajudar posteriormente a reduzir o tamanho das posições em períodos de incerteza. |
Para iniciar esse processo, primeiro precisamos obter cotações históricas do mercado para que tenhamos dados para treinar nosso modelo. Ao utilizar modelos estatísticos, não sabemos desde o início qual conjunto de entradas produzirá o melhor modelo. Portanto, geralmente é uma boa prática fornecer o maior número possível de características que você consiga imaginar e depois reduzir a seleção posteriormente.
//+------------------------------------------------------------------+ //| ProjectName | //| Copyright 2020, CompanyName | //| http://www.companyname.net | //+------------------------------------------------------------------+ #property copyright "Copyright 2024, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" #property script_show_inputs //--- File name string file_name = "XAGEUR XAGUSD EURUSD Triangular Exchange Rates.csv"; //--- Amount of data requested input int size = 3000; //+------------------------------------------------------------------+ //| Our script execution | //+------------------------------------------------------------------+ void OnStart() { //---Write to file int file_handle=FileOpen(file_name,FILE_WRITE|FILE_ANSI|FILE_CSV,","); for(int i=size;i>=1;i--) { if(i == size) { FileWrite(file_handle,"Time","XAGUSD Open","XAGUSD High","XAGUSD Low","XAGUSD Close","XAGEUR Open","XAGEUR High","XAGEUR Low","XAGEUR Close","EURUSD Open","EURUSD High","EURUSD Low","EURUSD Close","Open Squared","High Squared","Low Squared","Close Squared","Open Cubed","High Cubed","Low Cubed","Close Cubed","Open Squre Root","High Square Root","Low Square Root","Close Square Root","Open Growth","High Growth","Low Grwoth","Close Growth","O / H","O / L","O / C","H / L","Log Open Growth","Log High Grwoth","Log Low Growth","Log Close Grwoth","Sin H / L","Cos O / C"); } else { FileWrite(file_handle, iTime("XAGUSD",PERIOD_CURRENT,i), iOpen("XAGUSD",PERIOD_CURRENT,i), iHigh("XAGUSD",PERIOD_CURRENT,i), iLow("XAGUSD",PERIOD_CURRENT,i), iClose("XAGUSD",PERIOD_CURRENT,i), iOpen("XAGEUR",PERIOD_CURRENT,i), iHigh("XAGEUR",PERIOD_CURRENT,i), iLow("XAGEUR",PERIOD_CURRENT,i), iClose("XAGEUR",PERIOD_CURRENT,i), iOpen("EURUSD",PERIOD_CURRENT,i), iHigh("EURUSD",PERIOD_CURRENT,i), iLow("EURUSD",PERIOD_CURRENT,i), iClose("EURUSD",PERIOD_CURRENT,i), MathPow(iOpen("XAGUSD",PERIOD_CURRENT,i),2), MathPow(iHigh("XAGUSD",PERIOD_CURRENT,i),2), MathPow(iLow("XAGUSD",PERIOD_CURRENT,i),2), MathPow(iClose("XAGUSD",PERIOD_CURRENT,i),2), MathPow(iOpen("XAGUSD",PERIOD_CURRENT,i),3), MathPow(iHigh("XAGUSD",PERIOD_CURRENT,i),3), MathPow(iLow("XAGUSD",PERIOD_CURRENT,i),3), MathPow(iClose("XAGUSD",PERIOD_CURRENT,i),3), MathSqrt(iOpen("XAGUSD",PERIOD_CURRENT,i)), MathSqrt(iHigh("XAGUSD",PERIOD_CURRENT,i)), MathSqrt(iLow("XAGUSD",PERIOD_CURRENT,i)), MathSqrt(iClose("XAGUSD",PERIOD_CURRENT,i)), (iOpen("XAGUSD",PERIOD_CURRENT,i) / iOpen("XAGUSD",PERIOD_CURRENT,i+1)), (iHigh("XAGUSD",PERIOD_CURRENT,i) / iHigh("XAGUSD",PERIOD_CURRENT,i+1)), (iLow("XAGUSD",PERIOD_CURRENT,i) / iLow("XAGUSD",PERIOD_CURRENT,i+1)), (iClose("XAGUSD",PERIOD_CURRENT,i) / iClose("XAGUSD",PERIOD_CURRENT,i+1)), (iOpen("XAGUSD",PERIOD_CURRENT,i) / iHigh("XAGUSD",PERIOD_CURRENT,i+1)), (iOpen("XAGUSD",PERIOD_CURRENT,i) / iLow("XAGUSD",PERIOD_CURRENT,i+1)), (iOpen("XAGUSD",PERIOD_CURRENT,i) / iClose("XAGUSD",PERIOD_CURRENT,i+1)), (iHigh("XAGUSD",PERIOD_CURRENT,i) / iLow("XAGUSD",PERIOD_CURRENT,i+1)), MathLog10(iOpen("XAGUSD",PERIOD_CURRENT,i) / iOpen("XAGUSD",PERIOD_CURRENT,i+1)), MathLog10(iHigh("XAGUSD",PERIOD_CURRENT,i) / iHigh("XAGUSD",PERIOD_CURRENT,i+1)), MathLog10(iLow("XAGUSD",PERIOD_CURRENT,i) / iLow("XAGUSD",PERIOD_CURRENT,i+1)), MathLog10(iClose("XAGUSD",PERIOD_CURRENT,i) / iClose("XAGUSD",PERIOD_CURRENT,i+1)), (MathSin(iHigh("XAGUSD",PERIOD_CURRENT,i) / iLow("XAGUSD",PERIOD_CURRENT,i))), (MathCos(iOpen("XAGUSD",PERIOD_CURRENT,i) / iClose("XAGUSD",PERIOD_CURRENT,i))) ); } } //--- Close the file FileClose(file_handle); } //+------------------------------------------------------------------+
Analisando Nossos Dados em Python
Assim que os dados de treinamento forem coletados, estaremos prontos para começar a construir um modelo estatístico dos dados. Primeiro, vamos importar as bibliotecas Python de que precisamos.
#Import libraries we need import pandas as pd import numpy as np import seaborn as sns import matplotlib.pyplot as plt
Agora vamos rotular os dados. Lembre-se de que estamos negociando no timeframe H1, portanto definiremos nosso rótulo como a variação dos níveis de preço ao longo de 24 horas (1 dia de negociação).
#Clean up the data LOOK_AHEAD = 24 data = pd.read_csv("../XAGEUR XAGUSD EURUSD Triangular Exchange Rates.csv") data["Target"] = data["XAGUSD Close"].shift(-LOOK_AHEAD) - data["XAGUSD Close"] data.dropna(inplace=True) data.reset_index(inplace=True,drop=True)
Este é o passo mais importante de todo o processo que estamos seguindo juntos. Inicialmente, nossos dados conterão cotações de mercado que se sobrepõem ao período de backtest. Isso é indesejável para profissionais que desejam obter uma estimativa honesta do valor de sua estratégia de negociação.
Portanto, excluiremos todos os dados de mercado que se sobrepõem ao período de backtest pretendido Lembre-se de que na Fig 6 nosso período de backtest começa claramente em 1º de novembro de 2023, e na Fig 10 nossos dados de treinamento terminam em 31 de outubro de 2023.
#Drop the dates corresponding to our backtest _ = data.iloc[-((24 * 365) - 918):,:] #Keep the dates before our backtest data = data.iloc[:-((24 * 365) - 918),:] data

Fig 10: Certifique-se de que os dados de treinamento usados em seus modelos estatísticos não vazem informações do futuro
Se visualizarmos o crescimento que ocorre nos mercados XAGUSD e XAGEUR, observamos que o spread entre os dois mercados se expande e se contrai ao longo do tempo. Isso pode indicar potencialmente que existem oportunidades de arbitragem entre esses dois mercados. Caso contrário, se não existissem oportunidades de arbitragem, as linhas vermelha e verde deveriam se sobrepor perfeitamente sem nunca se separar, desde o início até os dias atuais. Obviamente, esse não é o caso. Os dois mercados claramente se desacoplam por algum tempo antes de serem posteriormente corrigidos.
plt.title("Comparing XAGUSD & XAGEUR Growth") plt.plot((data['XAGUSD Close'] / data.loc[0,"XAGUSD Close"]) / (data['XAGUSD Close'].max() - data['XAGUSD Close'].min()),color="red") plt.plot((data['XAGEUR Close'] / data.loc[0,"XAGEUR Close"]) / (data['XAGEUR Close'].max() - data['XAGEUR Close'].min()),color="green") plt.ylabel("Commodity Growth") plt.xlabel("Time") plt.legend(["XAGUSD","XAGEUR"]) plt.grid()

Fig 11: Visualizar nossos dois mercados financeiros sobrepostos revela possíveis oportunidades de arbitragem que podemos explorar
Rotular nossas entradas e o alvo.
X = data.iloc[:,1:-1].columns y = "Target"
Preparar para ajustar uma Gradient Boosting Tree aos nossos dados e exportá-la no formato ONNX. Gradient Boosted Trees são conhecidas por sua capacidade de detectar interações dentro de um conjunto de dados. Esperamos aproveitar sua poderosa capacidade de detecção de padrões para melhorar nossa estratégia de negociação.
import onnx from skl2onnx import convert_sklearn from skl2onnx.common.data_types import FloatTensorTypea from sklearn.ensemble import GradientBoostingRegressor
Agora vamos construir modelos estatísticos dedicados para cada mercado em que estamos interessados. Isso deverá nos ajudar a filtrar rompimentos falsos. Ajustaremos cada modelo aos dados e os exportaremos para o formato ONNX para que possamos posteriormente importá-los todos os três em nossa aplicação MetaTrader 5.
Vamos começar primeiro construindo um modelo do mercado XAGUSD.
model = GradientBoostingRegressor() model.fit(data.loc[:,["XAGUSD Open","XAGUSD High","XAGUSD Low","XAGUSD Close"]],data.loc[:,y]) initial_types = [("float_input",FloatTensorType([1,4]))] xagusd_model_proto = convert_sklearn(model,initial_types=initial_types,target_opset=12) onnx.save(xagusd_model_proto,"../XAGUSD State Model.onnx")
Em seguida, prosseguiremos com o mercado XAGEUR.
model = GradientBoostingRegressor() model.fit(data.loc[:,["XAGEUR Open","XAGEUR High","XAGEUR Low","XAGEUR Close"]],data.loc[:,"XAGEUR Target"]) initial_types = [("float_input",FloatTensorType([1,4]))] xageur_model_proto = convert_sklearn(model,initial_types=initial_types,target_opset=12) onnx.save(xageur_model_proto,"../XAGEUR State Model.onnx")
E por fim, exportaremos nosso modelo estatístico do mercado EURUSD.
model = GradientBoostingRegressor() model.fit(data.loc[:,["EURUSD Open","EURUSD High","EURUSD Low","EURUSD Close"]],data.loc[:,"EURUSD Target"]) initial_types = [("float_input",FloatTensorType([1,4]))] eurusd_model_proto = convert_sklearn(model,initial_types=initial_types,target_opset=12) onnx.save(eurusd_model_proto,"../EURUSD State Model.onnx")
Nosso sistema atribuirá maior peso aos movimentos de mercado que forem respaldados pelos três modelos simultaneamente. Primeiro queremos observar os padrões que descrevemos na Fig 3 e na Fig 4 se formando no mercado, seguidos pela previsão dos nossos três modelos indicando que esses padrões não desaparecerão rapidamente, mas se manterão ao longo do tempo. Isso nos dará uma medida razoável de confiança na força que sustenta os movimentos de mercado que esperamos observar.
Implementando Nossas Melhorias em MQL5
Agora podemos começar a implementar as melhorias na versão original da nossa estratégia de negociação. Primeiro, adicionaremos indicadores técnicos adicionais ao nosso conjunto de ferramentas para fornecer ao nosso computador melhor orientação e detecção de tendências. O cruzamento de médias móveis é uma boa estratégia para essa tarefa. No entanto, utilizaremos uma versão mais responsiva da estratégia, que minimiza o atraso nos sinais de negociação. Leitores interessados em aprender mais sobre essa versão da estratégia de cruzamento de médias móveis podem aprender mais aqui.
#define XAGUSD_MA_PERIOD 8
Carregar os modelos ONNX que acabamos de construir como recursos do sistema.
//+------------------------------------------------------------------+ //| System resources | //+------------------------------------------------------------------+ #resource "\\Files\\XAGUSD State Model.onnx" as uchar xagusd_onnx_buffer[] #resource "\\Files\\XAGEUR State Model.onnx" as uchar xageur_onnx_buffer[] #resource "\\Files\\EURUSD State Model.onnx" as uchar eurusd_onnx_buffer[]
Precisaremos de novas variáveis globais correspondentes aos indicadores de média móvel que estamos introduzindo, bem como aos componentes individuais necessários para completar nosso modelo ONNX.
//+------------------------------------------------------------------+ //| Global variables | //+------------------------------------------------------------------+ vector eurusd,xagusd,xageur; double eurusd_growth,xagusd_growth,xageur_growth,bid,ask; double sl_width = 3e2 * _Point; int xagusd_f_ma_handler,xagusd_s_ma_handler; double xagusd_f[],xagusd_s[]; vectorf model_output = vectorf::Zeros(1); long onnx_model; vectorf xageur_model_output = vectorf::Zeros(1); long xageur_onnx_model; vectorf eurusd_model_output = vectorf::Zeros(1); long eurusd_onnx_model;
Algumas de nossas funções precisam ser refatoradas para atender às novas exigências. Para começar, quando nossa aplicação de trading não estiver mais em uso, precisamos liberar com segurança os recursos utilizados pelos nossos modelos ONNX e pelas duas médias móveis.
//+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- OnnxRelease(onnx_model); OnnxRelease(xageur_onnx_model); OnnxRelease(eurusd_onnx_model); IndicatorRelease(xagusd_f_ma_handler); IndicatorRelease(xagusd_s_ma_handler); Print("System deinitialized"); }
Além disso, nosso procedimento de configuração também deve acomodar nossos modelos ONNX e nossos indicadores técnicos. Precisamos validar se os indicadores e modelos desejados foram carregados e especificados corretamente. Caso contrário, interromperemos o procedimento de inicialização e informaremos ao usuário o que deu errado.
//+------------------------------------------------------------------+ //| Setup our technical indicators and select the symbols we need | //+------------------------------------------------------------------+ bool setup(void) { //--- Select the symbols we need SymbolSelect(SYMBOL_ONE,true); SymbolSelect(SYMBOL_TWO,true); SymbolSelect(SYMBOL_THREE,true); //--- Setup the moving averages xagusd_f_ma_handler = iMA(SYMBOL_ONE,TF_1,XAGUSD_MA_PERIOD,0,MODE_SMA,PRICE_OPEN); xagusd_s_ma_handler = iMA(SYMBOL_ONE,TF_1,XAGUSD_MA_PERIOD,0,MODE_SMA,PRICE_CLOSE); if((xagusd_f_ma_handler == INVALID_HANDLE) || (xagusd_s_ma_handler == INVALID_HANDLE)) { Comment("Failed to load our technical indicators correctly. ", GetLastError()); return(false); } //--- Setup our statistical models onnx_model = OnnxCreateFromBuffer(xagusd_onnx_buffer,ONNX_DEFAULT); xageur_onnx_model = OnnxCreateFromBuffer(xageur_onnx_buffer,ONNX_DEFAULT); eurusd_onnx_model = OnnxCreateFromBuffer(eurusd_onnx_buffer,ONNX_DEFAULT); if(onnx_model == INVALID_HANDLE) { Comment("Failed to create our XAGUSD ONNX model correctly. ",GetLastError()); return(false); } if(xageur_onnx_model == INVALID_HANDLE) { Comment("Failed to create our XAGEUR ONNX model correctly. ",GetLastError()); return(false); } if(eurusd_onnx_model == INVALID_HANDLE) { Comment("Failed to create our EURUSD ONNX model correctly. ",GetLastError()); return(false); } ulong input_shape[] = {1,4}; ulong output_shape[] = {1,1}; if(!(OnnxSetInputShape(onnx_model,0,input_shape))) { Comment("Failed to specify XAGUSD model input shape. ",GetLastError()); return(false); } if(!(OnnxSetInputShape(xageur_onnx_model,0,input_shape))) { Comment("Failed to specify XAGEUR model input shape. ",GetLastError()); return(false); } if(!(OnnxSetInputShape(eurusd_onnx_model,0,input_shape))) { Comment("Failed to specify EURUSD model input shape. ",GetLastError()); return(false); } if(!(OnnxSetOutputShape(onnx_model,0,output_shape))) { Comment("Failed to specify XAGUSD model output shape. ",GetLastError()); return(false); } if(!(OnnxSetOutputShape(xageur_onnx_model,0,output_shape))) { Comment("Failed to specify XAGEUR model output shape. ",GetLastError()); return(false); } if(!(OnnxSetOutputShape(eurusd_onnx_model,0,output_shape))) { Comment("Failed to specify EURUSD model output shape. ",GetLastError()); return(false); } Print("System initialized succefully"); //--- If we have gotten this far, everything went fine. return(true); }
Também precisaremos de uma função dedicada para obter previsões do nosso modelo ONNX. Nossa função primeiro preparará as entradas do modelo ONNX e então chamará a função OnnxRun para obter uma previsão do modelo.
//+------------------------------------------------------------------+ //| Fetch a prediction from our model | //+------------------------------------------------------------------+ void model_predict(void) { vectorf model_inputs = { (float) iOpen(SYMBOL_ONE,TF_1,1), (float) iClose(SYMBOL_ONE,TF_1,1)}; OnnxRun(onnx_model,ONNX_DATA_TYPE_FLOAT,model_inputs,model_output); Print(StringFormat("Model forecast: %d",model_output)); }
Nosso procedimento para encontrar uma configuração de negociação também deve ser revisado. O primeiro passo é obter uma previsão do nosso modelo; uma vez concluído, buscamos confirmação adicional a partir dos cruzamentos de médias móveis. Lembre-se de que nossa versão específica do cruzamento de médias móveis posiciona uma média móvel no preço de abertura e a outra no preço de fechamento, ambas compartilhando o mesmo período. Quando a média móvel do preço de abertura está acima, interpretamos isso como um sinal de venda. Caso contrário, consideramos um sinal de compra.
Portanto, nossa condição final será que o Gradient Boosted Regressor espere que a ação do preço seja consistente com ambos os sinais observados. Se esse for o caso, consideraremos isso uma configuração de negociação de alta probabilidade e dobraremos o tamanho do lote. Caso contrário, operaremos de forma conservadora.
//+------------------------------------------------------------------+ //| Find setup | //+------------------------------------------------------------------+ void find_setup(void) { model_predict(); //--- Check if the current market setup matches our expectations for selling if((eurusd_growth < 1) && (xageur_growth > 1) && (xagusd_growth < 1)) { if(xagusd_s[0] < xagusd_f[0]) { if(model_output[0] < 0) { //--- If all our systems align, we have a high probability trade setup Trade.Sell(VOLUME * 2,SYMBOL_ONE,bid,(ask + sl_width),(ask - sl_width),""); } //--- Otherwise, we should trade conservatively Trade.Sell(VOLUME,SYMBOL_ONE,bid,(ask + sl_width),(ask - sl_width),""); } } //--- Check if the current market setup matches our expectations for buying if((eurusd_growth > 1) && (xageur_growth < 1) && (xagusd_growth > 1)) { if(xagusd_s[0] > xagusd_f[0]) { if(model_output[0] > 0) { Trade.Buy(VOLUME * 2,SYMBOL_ONE,ask,(bid - sl_width),(bid + sl_width),""); } Trade.Buy(VOLUME,SYMBOL_ONE,ask,(bid - sl_width),(bid + sl_width),""); } } }
Agora vamos repetir o mesmo teste que realizamos anteriormente, mas desta vez utilizando nossa versão refinada da estratégia de negociação. Lembre-se de que garantimos que as datas do backtest não se sobreponham às datas usadas para treinar nossos modelos estatísticos.

Fig 12: Estamos utilizando o mesmo período de teste em ambos os testes. Além disso, nenhum dos nossos modelos estatísticos viu esses dados anteriormente.
Como o leitor pode esperar, todas as configurações serão mantidas constantes para garantir uma comparação justa entre as duas estratégias de negociação.

Fig 13: Idealmente, essas configurações devem permanecer iguais em ambos os testes realizados.
Agora vamos analisar os resultados obtidos. No nosso backtest inicial demonstramos um Sharpe ratio de 0.14, enquanto nossa estratégia revisada apresenta um Sharpe ratio de 1.85. Isso representa uma melhoria significativa no Sharpe ratio, indicando que conseguimos ser mais lucrativos enquanto assumimos risco adicional de forma responsável. Sharpe ratios baixos estão associados a alta variância com retornos baixos.
Além disso, nossa perda média caiu de cerca de $115 por operação para cerca de $109 por operação, enquanto nosso lucro médio aumentou de $188 para cerca de $213. Isso representa um retorno positivo para nós. Nosso lucro total também aumentou de $395 na primeira versão da estratégia para $1 449 nesta versão atual. Tudo isso enquanto realizamos menos operações do que na versão configurada manualmente da estratégia.

Fig 14: Um resumo detalhado do desempenho histórico da nossa estratégia de negociação no mercado XAGUSD.
As mudanças aplicadas ao nosso sistema corrigiram as oscilações instáveis no saldo da conta que observamos na versão inicial da estratégia. De acordo com nosso backtest, em média, nossa nova estratégia tende a lucrar mais do que perder em cada operação. Isso faz com que nossa nova curva de lucro e perda apresente períodos negativos mais rasos do que as perdas acumuladas na versão original e mais arriscada da estratégia.

Fig 15: Visualizando a curva de lucro e perda produzida pela versão revisada da estratégia de negociação.
Conclusão
Após ler este artigo, espera-se que o leitor adquira uma estratégia algorítmica para negociar mercados interconectados. O leitor compreenderá como combinar seu conhecimento de domínio para encontrar e negociar de forma lucrativa mercados triangulares. Utilizando modelos estatísticos, o leitor poderá identificar com maior precisão as configurações de negociação que procura. Ao aproveitar a natureza interconectada de determinados mercados, podemos criar indicadores em tempo real da verdadeira força do mercado.
| Nome dos Arquivos Anexados | Descrição |
|---|---|
| Modelo de linha de base | Nossa versão inicial da estratégia triangular de negociação. |
| Segunda Versão | A versão revisada e mais lucrativa da nossa estratégia de negociação. |
| Modelo de Estado EURUSD | Nosso modelo estatístico do mercado EURUSD. |
| Modelo de Estado XAGEUR | Nosso modelo estatístico do mercado XAGEUR. |
| Modelo Estadual XAGUSD | Nosso modelo estatístico do mercado XAGUSD. |
| Taxas de câmbio triangulares | O Jupyter Notebook que utilizamos para analisar nossos dados de mercado e construir nossos modelos estatísticos. |
Traduzido do Inglês pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/en/articles/17258
Aviso: Todos os direitos sobre esses materiais pertencem à MetaQuotes Ltd. É proibida a reimpressão total ou parcial.
Esse artigo foi escrito por um usuário do site e reflete seu ponto de vista pessoal. A MetaQuotes Ltd. não se responsabiliza pela precisão das informações apresentadas nem pelas possíveis consequências decorrentes do uso das soluções, estratégias ou recomendações descritas.
Introdução ao MQL5 (Parte 13): Um Guia para Iniciantes na Construção de Indicadores Personalizados (II)
Explorando modelos de regressão para inferência causal e trading
Está chegando o novo MetaTrader 5 e MQL5
Algoritmo do mercado acionário: Exchange Market Algorithm (EMA)
- 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