Reimaginando Estratégias Clássicas: Petróleo Bruto
Introdução
O petróleo é a mercadoria mais importante do mundo. O petróleo bruto em sua forma original é inútil; no entanto, depois de refinado, ele é utilizado em diversas indústrias, desde as mais simples, como a agricultura, até as mais complexas, como a farmacêutica. O petróleo é uma das poucas mercadorias verdadeiramente demandadas em todas as indústrias. O preço do petróleo é um indicador econométrico fundamental dos níveis de produção global e crescimento econômico.
O comércio global de petróleo bruto é dominado por dois referenciais: o West Texas Intermediate (WTI), que é o referencial da América do Norte, e o Brent, usado para cotar a maior parte do petróleo no mundo.
Nesta discussão, revisitaremos uma estratégia clássica de negociação de spread de petróleo, na esperança de encontrar uma estratégia otimizada de aprendizado de máquina para tornar essa estratégia clássica mais adequada a um mercado moderno de petróleo, dominado por algoritmos.
Começaremos nossa discussão destacando as diferenças entre os dois referenciais de petróleo mencionados acima. A partir daí, começaremos a visualizar o spread Brent-WTI no MQL5 e discutiremos a estratégia clássica de negociação de spread. Isso nos preparará para demonstrar como utilizar o aprendizado de máquina supervisionado no spread entre os preços do petróleo West Texas Intermediate e Brent, para possivelmente descobrir indicadores de mudanças no preço. Após ler este artigo, você terá um entendimento claro dos seguintes pontos:
- A diferença entre os referenciais Brent e WTI, e por que eles são importantes.
- Como usar as funções de matriz e vetor do MQL5 para construir modelos de aprendizado de máquina compactos, fáceis de manter e implementar do zero.
- Como empregar a técnica do pseudo inverso para encontrar uma solução de mínimos quadrados para prever o preço futuro do Brent, usando o spread WTI-Brent.
Referencial Global de Petróleo Bruto: Brent
Quando o petróleo bruto é extraído do solo, ele é uma mistura de oxigênio, carbono, hidrogênio e impurezas de enxofre. Brent é uma classificação dada a misturas de petróleo bruto que são consideradas leves e doces. Para ser considerado doce, a mistura deve ter baixas concentrações de impurezas de enxofre. Além disso, é chamado de leve porque tem baixa densidade. Essas propriedades são desejáveis porque indicam que a mistura será refinada facilmente. A última característica do Brent que destacaremos é que ele é de qualidade inferior ao WTI. O Brent é extraído principalmente no Mar do Norte. Após ser extraído, ele é facilmente armazenado em barris a bordo de grandes petroleiros, o que lhe confere uma vantagem distinta sobre o WTI: é muito acessível. Atualmente, o Brent está sendo negociado com um prêmio em relação ao WTI.
Fig 1: Preço histórico do Brent no MQL5
Referencial de Petróleo Bruto da América do Norte: West Texas Intermediate
West Texas Intermediate (WTI) é uma classificação dada a uma determinada mistura de petróleo bruto; deve ser um petróleo "leve e doce". O WTI é extraído em diversas partes dos EUA, mas principalmente no Texas. Ele é mais doce e mais leve que o Brent, o que significa que é mais fácil de ser refinado em produtos acabados. Historicamente, era extraído em regiões sem saída para o mar nos EUA e, portanto, era muito menos acessível que o Brent. No entanto, devido a investimentos maciços na Costa do Golfo e à revogação da proibição de exportação de petróleo em 2015, o WTI agora é mais acessível do que nunca.
Fig 2: Preço histórico do WTI no MQL5
Começando: Visualizando o Spread
Para começar, podemos criar um script prático para visualizar o spread entre as duas commodities. Podemos usar a biblioteca de gráficos do MQL5 para nos ajudar a traçar facilmente qualquer função que desejarmos. A biblioteca de gráficos gerencia a escala para você, o que é sempre útil. Após incluir a biblioteca de gráficos, você notará uma variável definida como 'consumption'. Essa variável nos ajuda a selecionar facilmente metade, um quarto ou qualquer fração dos dados totais disponíveis.
Dado que estamos solicitando dados históricos de dois ativos diferentes, precisamos saber o número total de barras disponíveis em cada mercado. A partir daí, assumimos que o menor número de barras disponíveis corresponde ao número total de barras disponíveis. Usamos um operador ternário para selecionar o número correto de barras.
Após determinar o número correto de barras a serem usadas, podemos plotar o spread.
//+------------------------------------------------------------------+ //| Brent-WTI Spread.mq5 | //| Copyright 2024, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2024, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" #include <Graphics\Graphic.mqh> //Set this value between 0 and 1 to control how much data is used double consumption = 1.0; int brent_bars = (int) NormalizeDouble((iBars("UK Brent Oil",PERIOD_CURRENT) * consumption),0); int wti_bars = (int) NormalizeDouble((iBars("WTI_OIL",PERIOD_CURRENT) * consumption),0); //We want to know which symbol has the least number of bars. int max_bars = (brent_bars < wti_bars) ? brent_bars : wti_bars; //+------------------------------------------------------------------+ //|This event handler is only triggered when the script launches | //+------------------------------------------------------------------+ void OnStart() { CGraphic graphic; double from = 0; double to = max_bars; double step = 1; graphic.Create(0,"G",0,0,0,600,200); CColorGenerator generator; uint spread = generator.Next(); CCurve *curve = graphic.CurveAdd(SpreadFunction,from,to,step,spread,CURVE_LINES,"Blue"); curve.Name("Spread"); graphic.XAxis().Name("Time"); graphic.XAxis().NameSize(12); graphic.YAxis().Name("Brent-WTI Spread"); graphic.YAxis().NameSize(12); graphic.CurvePlotAll(); graphic.Update(); } //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //|This function returns the Brent-WTI spread | //+------------------------------------------------------------------+ double SpreadFunction(double x) { return(iClose("UK Brent Oil",PERIOD_CURRENT,(max_bars - x)) - iClose("WTI_OIL",PERIOD_CURRENT,(max_bars - x))); } //+------------------------------------------------------------------+
Fig 3: Visualizando o spread Brent-WTI no MQL5.
Visão Geral de Nossa Estratégia de Negociação: Empregando Aprendizado de Máquina Supervisionado
A premissa da estratégia clássica de petróleo bruto é que o equilíbrio de preços sempre será restaurado a longo prazo. A estratégia clássica de negociação de spread de petróleo afirmaria que começamos observando o spread atual entre Brent e WTI. Se o spread estiver acima de sua linha de base, por exemplo, a linha de base poderia ser a média móvel de 20 períodos, inferiríamos que o spread voltará à sua média no futuro próximo. Portanto, se os preços do Brent estivessem subindo, venderíamos. Por outro lado, se os preços do Brent estivessem caindo, compraríamos.
No entanto, desde que essa estratégia foi desenvolvida, o mercado de petróleo mudou consideravelmente. Precisamos de um procedimento objetivo para inferir quais relações existem entre o spread e o preço futuro do Brent. O aprendizado de máquina permite que o computador aprenda suas próprias regras de negociação a partir de quaisquer relações que possa observar analiticamente.
Para permitir que nosso computador crie sua própria estratégia de negociação, começamos com uma matriz de dados A.
A simboliza os dados históricos de preços que temos disponíveis sobre o Brent. Usaremos o preço de fechamento, o spread e uma constante que terá o valor de um. Em seguida, construiremos um vetor de coluna separado, x, que terá 1 coeficiente para cada coluna em A. Este valor será calculado diretamente a partir dos dados do mercado e será usado pelo nosso modelo para prever o preço futuro.
Fig 4: Enquadrando o problema de mínimos quadrados.
Após criar nossa matriz de entrada A, precisamos saber quais preços de fechamento do Brent foram associados a cada uma das entradas em A. Armazenaremos o preço de saída em um vetor, y. Nosso objetivo é encontrar uma maneira de mapear a matriz de dados de entrada A para o vetor de dados de saída y, aproximando o menor erro possível em todas as observações de treinamento que temos. A resposta para esse problema é chamada de solução de mínimos quadrados.
Fig 5: Nosso vetor de saída y.
Fig 6: Apresentando a solução de pseudo-inversa.
As duas equações acima nos dizem primeiro que estamos procurando um valor de x que minimize o erro entre nossa previsão, A*x, e o preço de fechamento real do Brent, y. Observe as linhas verticais duplas em torno de Ax-y. Essas linhas verticais duplas representam a norma L2. Quando lidamos com objetos físicos no mundo real, podemos perguntar "Qual é o tamanho disso?". No entanto, quando queremos saber o tamanho de um vetor ou de uma matriz, pedimos sua norma. Existem diferentes maneiras de calcular a norma, mas com mais frequência você encontrará a norma L1 ou L2. Para nossa discussão, consideraremos apenas a norma L2.
A norma L2 é calculada elevando ao quadrado cada entidade no vetor, somando todos os valores quadrados e depois calculando a raiz quadrada da soma. Ela também é chamada de norma euclidiana. Em linguagem simples, diríamos "Estamos procurando valores de x que reduzam o tamanho de todos os erros que nosso modelo comete", e em linguagem mais técnica diríamos "Encontre valores ótimos de x que minimizem a norma L2 dos resíduos".
O valor de x que satisfaz nossas restrições é denotado como x*. Para encontrar x*, calculamos o produto escalar da pseudo-inversa de A e y. É altamente improvável que você precise implementar a função de pseudo-inversa por conta própria, a menos que seja um exercício de álgebra linear. Caso contrário, confiaremos na função integrada no MQL5.
//+------------------------------------------------------------------+ //|Demonstrating the pseudo-inverse solution in action. | | //+------------------------------------------------------------------+ void OnStart() { //Training and test data matrix A; //A is the input data. look at the figure above if you need a reminder. matrix y,x; //y is the output data, x is the coefficients. A.CopyRates(_Symbol,PERIOD_CURRENT,COPY_RATES_OHLC,20,1000); y.CopyRates(_Symbol,PERIOD_CURRENT,COPY_RATES_CLOSE,1,1000); A.Reshape(1000,4); y.Reshape(1000,1); Print("Attempting Psuedoinverse Decomposition"); Print("Attempting to calculate the Pseudoinverse Coefficients: "); x = A.PInv().MatMul(y); Print("Coefficients: "); Print("Open: ",x[0][0],"\nHigh: ",x[1][0],"\nLow: ",x[3][0],"\nClose: ",x[3][0]); } //+------------------------------------------------------------------+
Fig 7: Um exemplo de implementação da técnica de pseudo-inversa.
O código acima fornece uma demonstração simples de como utilizar a técnica de pseudo-inversa. Neste exemplo, nosso objetivo é prever o preço de fechamento de um símbolo usando seus preços atuais de abertura, máxima, mínima e fechamento. Este exemplo simples encapsula os princípios fundamentais que precisamos entender. Começamos definindo nossos dados de entrada, que são armazenados na matriz A. Para buscar os dados, usamos a função CopyRates, que requer os seguintes parâmetros na ordem especificada:
- Nome do símbolo: O nome do símbolo que desejamos negociar.
- Intervalo de tempo: O intervalo de tempo que se alinha com nossos níveis de risco.
- Máscara de taxas: Isso especifica quais preços copiar, permitindo que selecionemos, por exemplo, apenas os preços de abertura, se desejado.
- A partir de: A data de início para copiar os dados, garantindo um intervalo entre os dados de entrada e saída e que os dados de entrada comecem em uma data anterior.
- Contagem: O número de candles a serem copiados.
Após configurar a matriz de dados de entrada A, repetimos o processo para a matriz de dados de saída y. Em seguida, remodelamos ambas as matrizes para garantir que tenham o tamanho apropriado e sejam compatíveis para as operações que pretendemos realizar.
Em seguida, preenchemos o vetor de coluna x com valores derivados de A e y. Felizmente, a API do MQL5 suporta o encadeamento de operações de matriz, permitindo-nos calcular a solução de pseudo-inversa com uma única linha de código. Uma vez concluído, podemos imprimir os coeficientes em nosso vetor de coluna x.
Usaremos as mesmas etapas para desenvolver nossa estratégia de negociação. O único passo adicional, não demonstrado aqui, é usar nosso modelo para fazer previsões, o que será explicado mais adiante em nossa discussão. Com essa base, estamos prontos para começar a construir nossa estratégia de negociação.
Unindo Tudo
Agora estamos prontos para definir o coração de nosso algoritmo. Começamos incluindo a biblioteca Trade necessária para abrirmos e gerenciarmos posições.
//+------------------------------------------------------------------+ //| Brent EA.mq5 | //| Copyright 2024, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2024, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" //Libraries #include <Trade\Trade.mqh> CTrade ExtTrade; #include <TrailingStop\ATRTrailingStop3.mqh> ATRTrailingStop ExtATRTrailingStop;
Subsequentemente, definimos o tamanho de nossa posição de negociação e os parâmetros de risco. A primeira entrada determina quantas vezes maior que o lote mínimo cada posição será. A segunda entrada define o nível de lucro no qual todas as posições abertas serão fechadas. Em seguida, temos o parâmetro de entrada que limita o rebaixamento total permitido nesta conta. E, por fim, definimos quantas posições gostaríamos de abrir a cada vez que realizarmos uma negociação.
//Inputs input double lot_multiple = 1.0; input double profit_target = 10; input double max_loss = 20; input int position_size = 2;
Seguindo em frente, agora precisamos saber quantos "bars" estão disponíveis em cada mercado para garantir que sempre tentemos copiar o número certo de "bars" que estarão disponíveis em ambos os mercados. O "número certo", no nosso caso, é o menor número de "bars" disponível. Também definimos uma variável chamada "consumption" porque ela nos permite controlar quanto de dados queremos usar. No exemplo de código abaixo, estamos utilizando 1% de todos os dados históricos disponíveis.
//Set this value between 0 and 1 to control how much data is used double consumption = 0.01; //We want to know which symbol has the least number of bars. double brent_bars = (double) NormalizeDouble((iBars("UK Brent Oil",PERIOD_CURRENT) * consumption),0); double wti_bars = (double) NormalizeDouble((iBars("WTI_OIL",PERIOD_CURRENT) * consumption),0);
Aqui é onde realmente determinamos qual mercado tem menos "bars" disponíveis e usamos esse número de "bars" como nosso limite. Se pulássemos essa etapa, as datas entre os dois mercados podem não coincidir, a menos que seu corretor garanta conjuntos de dados correspondentes uniformemente nos preços históricos para ambos os ativos. "Look Ahead" é o nosso horizonte de previsão, ou seja, quantos passos no futuro estamos prevendo.
//Select the lowest double max_bars = (brent_bars < wti_bars) ? brent_bars : wti_bars; //How far into the future are we forecasting double look_ahead = NormalizeDouble((max_bars / 4),0); //How many bars should we fetch? int fetch = (int) (max_bars - look_ahead) - 1;
Seguindo em frente, agora precisamos definir variáveis para aquelas que definimos na nossa notação; vou incluir uma cópia da imagem para que você não precise rolar a página para cima. Lembre-se, A é a matriz que armazena nossos dados de entrada. Podemos escolher quantas ou poucas entradas quisermos; neste exemplo, usarei 3 entradas. x* representa o valor de x que minimiza a norma L2 de nossos resíduos.
Fig 6: Um lembrete da notação que definimos.
//A Matriz A armazena nossas entradas. y é a saída. x is the coefficients. matrix A = matrix::Zeros(fetch,6); matrix y = matrix::Zeros(fetch,1); vector wti_price = vector::Zeros(fetch); vector brent_price = vector::Zeros(fetch); vector spread; vector intercept = vector::Ones(fetch); matrix x = matrix::Zeros(6,1); double forecast = 0; double ask = 0; double bid = 0; double min_volume = 0;
Vamos definir duas variáveis do tipo string para armazenar os nomes dos símbolos que desejamos negociar. Após concluir isso, chegamos à nossa função OnInit. Esta função é simples no nosso caso; apenas precisamos saber o volume mínimo de negociação permitido no Brent.
string brent = "UK Brent Oil"; string wti = "WTI_OIL"; bool model_initialized = false; int OnInit() { //Initialise trailing stops if(atr_multiple > 0) ExtATRTrailingStop.Init(atr_multiple); min_volume = SymbolInfoDouble(brent,SYMBOL_VOLUME_MIN); return(INIT_SUCCEEDED); //--- }
Agora estamos trabalhando na nossa função OnTick. Dentro do corpo, primeiro atualizamos os preços do bid e do ask que estamos acompanhando. Em seguida, verificamos se o nosso modelo foi inicializado; se não foi, ele será treinado e ajustado. Caso contrário, se já foi inicializado, seguimos para verificar se temos alguma posição aberta. No caso de não termos posições abertas, obtemos uma previsão do nosso modelo e, em seguida, negociamos na direção prevista pelo modelo. Caso contrário, se tivermos posições abertas, verificamos se nossas posições não excederam o alvo de lucro ou o nível máximo de perda.
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- ask = SymbolInfoDouble(brent,SYMBOL_ASK); bid = SymbolInfoDouble(brent,SYMBOL_BID); if(model_initialized) { if(PositionsTotal() == 0) { forecast = 0; forecast = ModelForecast(); InterpretForecast(); } else { ManageTrades(); } } else { model_initialized = InitializeModel(); } } //+------------------------------------------------------------------+
Esta é a função responsável por verificar se ultrapassamos os níveis de risco ou alcançamos nosso alvo de lucro. Ela é chamada apenas no manipulador de eventos OnTick em condições onde temos operações abertas.
void ManageTrades() { if(AccountInfoDouble(ACCOUNT_PROFIT) > profit_target) CloseAll(); if(AccountInfoDouble(ACCOUNT_PROFIT) < (-1 * max_loss)) CloseAll(); }
Sempre que o nosso modelo fizer uma previsão, chamaremos a função InterpretForecast para interpretar as previsões do modelo e abrir as posições apropriadas em resposta.
void InterpretForecast() { if(forecast != 0) { if(forecast > iClose(_Symbol,PERIOD_CURRENT,0)) { check_buy(); } else if(forecast < iClose(_Symbol,PERIOD_CURRENT,0)) { check_sell(); } } }
Temos um procedimento dedicado para entrar em posições de compra. Observe que o volume mínimo que determinamos anteriormente está sendo multiplicado pela entrada do múltiplo de lote, dando ao usuário controle sobre o tamanho do lote usado para entrar nas operações.
void check_buy() { if(PositionsTotal() == 0) { for(int i = 0; i < position_size; i++) { ExtTrade.Buy(lot_multiple * min_volume,brent,ask,0,0,"BUY"); } } }
Também incluí procedimentos dedicados para entrar em posições vendidas. Fiz isso caso percebamos regras específicas que se apliquem exclusivamente a cada lado da posição.
void check_sell() { if(PositionsTotal() == 0) { for(int i = 0; i < position_size; i++) { ExtTrade.Sell(lot_multiple * min_volume,brent,bid,0,0,"SELL"); } } }
Agora, definimos uma função que fechará todas as posições abertas que temos. Ela percorre as posições abertas e fecha apenas aquelas que foram abertas no Brent. Observe que, se você quiser negociar tanto Brent quanto WTI usando este EA, basta remover as verificações de segurança que coloquei para garantir que o símbolo seja Brent. Lembre-se de que escolhi Brent apenas para fins de demonstração. Você é livre para personalizar o EA.
void CloseAll(void) { for(int i=PositionsTotal()-1; i>=0; i--) { if(PositionSelectByTicket(PositionGetTicket(i))) { if(PositionGetSymbol(i) == brent) { ulong ticket; ticket = PositionGetTicket(i); ExtTrade.PositionClose(ticket); } } } }
Agora definiremos dois métodos para fechar posições longas e curtas, respectivamente. Novamente, como antes, realizamos isso iterando sobre todas as posições e obtendo o respectivo ticket para cada uma. Em seguida, validamos se o tipo de posição corresponde ao tipo que estamos procurando. Se tudo estiver correto, fecharemos a posição.
void close_buy() { ulong ticket; int type; if(PositionsTotal() > 0) { for(int i = 0; i < PositionsTotal(); i++) { ticket = PositionGetTicket(i); type = (int)PositionGetInteger(POSITION_TYPE); if(type == POSITION_TYPE_BUY) { ExtTrade.PositionClose(ticket); } } } } void close_sell() { ulong ticket; int type; if(PositionsTotal() > 0) { for(int i = 0; i < PositionsTotal(); i++) { ticket = PositionGetTicket(i); type = (int)PositionGetInteger(POSITION_TYPE); if(type == POSITION_TYPE_SELL) { ExtTrade.PositionClose(ticket); } } } }
Agora vamos definir como nosso modelo deve ser inicializado:
- Certifique-se de que ambos os símbolos estão disponíveis e adicionados à janela do mercado.
- Copie os dados de saída para a matriz y (o preço de fechamento do Brent, começando a partir da vela 1).
- Copie os dados de entrada para a matriz A (o preço de fechamento do Brent, começando 1 vela à frente do nosso horizonte de previsão).
- Redimensione a matriz de dados A.
- Calcule o spread entre Brent e WTI e adicione-o a A.
- Adicione uma linha de 1's em A para o intercepto.
- Transponha tanto A quanto y.
Uma vez que esses passos tenham sido concluídos, verificaremos se nossos dados de entrada são válidos. Caso contrário, registraremos uma mensagem de erro. Se forem válidos, prosseguiremos para calcular a matriz de coeficientes x.
bool InitializeModel() { //Try select the symbols if(SymbolSelect(brent,true) && SymbolSelect(wti,true)) { Print("Symbols Available. Bars: ",max_bars," Fetch: ",fetch," Look ahead: ",look_ahead); //Get historical data on Brent , our model output y.CopyRates(brent,PERIOD_CURRENT,COPY_RATES_CLOSE,1,fetch); //model input A.CopyRates(brent,PERIOD_CURRENT,COPY_RATES_CLOSE,(1 + look_ahead),fetch); brent_price.CopyRates(brent,PERIOD_CURRENT,COPY_RATES_CLOSE,(1+look_ahead),fetch); wti_price.CopyRates(wti,PERIOD_CURRENT,COPY_RATES_CLOSE,(1+look_ahead),fetch); //Calculate the spread spread = brent_price - wti_price; Print("The Current Spread: ",spread); A.Reshape(3,fetch); //Add the spread to the input matrix A.Row(spread,1); //Add a column for the intercept A.Row(intercept,2); //Reshape the matrices A = A.Transpose(); y = y.Transpose(); //Inspect the matrices if((A.Cols() == 0 || y.Cols() == 0)) { Print("Error occured when copying historical data"); Print("A rows: ",A.Rows()," y rows: ",y.Rows()," A columns: ",A.Cols()," y cols: ",y.Cols()); Print("A"); Print(A); Print("y"); Print(y); return(false); } else { Print("No errors occured when copying historical data"); x = A.PInv().MatMul(y); Print("Finished Fitting The Model"); Print(x); return(true); } } Print("Faield to select symbols"); return(false); }
Por fim, precisamos definir uma função para prever os valores futuros do preço de fechamento do Brent.
double ModelForecast() { if(model_initialized) { //model input A.CopyRates(brent,PERIOD_CURRENT,COPY_RATES_CLOSE,0,1); brent_price.CopyRates(brent,PERIOD_CURRENT,COPY_RATES_CLOSE,0,1); wti_price.CopyRates(wti,PERIOD_CURRENT,COPY_RATES_CLOSE,0,1); //Calculate the spread spread = brent_price - wti_price; Print("The Spread: ",spread); A.Reshape(3,fetch); //Add the spread to the input matrix A.Row(spread,1); //Add a column for the intercept A.Row(intercept,2); //Reshape the matrices A = A.Transpose(); double _forecast = (A[0][0]*x[0][0]) + (A[1][0]*x[1][0]) + (A[2][0]*x[2][0]); return(_forecast); } return(0); }
Colocando tudo junto, isso é o que nossa aplicação resulta.
//+------------------------------------------------------------------+ //| Brent EA.mq5 | //| Copyright 2024, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2024, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" //Libraries #include <Trade\Trade.mqh> CTrade ExtTrade; #include <TrailingStop\ATRTrailingStop3.mqh> ATRTrailingStop ExtATRTrailingStop; //Inputs input double atr_multiple = 5.0; input double lot_multiple = 1.0; input double profit_target = 10; input double max_loss = 20; input int position_size = 2; //Set this value between 0 and 1 to control how much data is used double consumption = 0.01; //We want to know which symbol has the least number of bars. double brent_bars = (double) NormalizeDouble((iBars("UK Brent Oil",PERIOD_CURRENT) * consumption),0); double wti_bars = (double) NormalizeDouble((iBars("WTI_OIL",PERIOD_CURRENT) * consumption),0); //Select the lowest double max_bars = (brent_bars < wti_bars) ? brent_bars : wti_bars; //How far into the future are we forecasting double look_ahead = NormalizeDouble((max_bars / 4),0); //How many bars should we fetch? int fetch = (int)(max_bars - look_ahead) - 1; //Matrix A stores our inputs. y é a saída. x is the coefficients. matrix A = matrix::Zeros(fetch,6); matrix y = matrix::Zeros(fetch,1); vector wti_price = vector::Zeros(fetch); vector brent_price = vector::Zeros(fetch); vector spread; vector intercept = vector::Ones(fetch); matrix x = matrix::Zeros(6,1); double forecast = 0; double ask = 0; double bid = 0; double min_volume = 0; string brent = "UK Brent Oil"; string wti = "WTI_OIL"; bool model_initialized = false; int OnInit() { //Initialise trailing stops if(atr_multiple > 0) ExtATRTrailingStop.Init(atr_multiple); min_volume = SymbolInfoDouble(brent,SYMBOL_VOLUME_MIN); return(INIT_SUCCEEDED); //--- } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- ask = SymbolInfoDouble(brent,SYMBOL_ASK); bid = SymbolInfoDouble(brent,SYMBOL_BID); if(model_initialized) { if(PositionsTotal() == 0) { forecast = 0; forecast = ModelForecast(); InterpretForecast(); } else { ManageTrades(); } } else { model_initialized = InitializeModel(); } } //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //|This function closes trades if we reach our profit or loss limit | | //+------------------------------------------------------------------+ void ManageTrades() { if(AccountInfoDouble(ACCOUNT_PROFIT) > profit_target) CloseAll(); if(AccountInfoDouble(ACCOUNT_PROFIT) < (-1 * max_loss)) CloseAll(); } //+------------------------------------------------------------------+ //|This function judges if our model is giving a long or short signal| | //+------------------------------------------------------------------+ void InterpretForecast() { if(forecast != 0) { if(forecast > iClose(_Symbol,PERIOD_CURRENT,0)) { check_buy(); } else if(forecast < iClose(_Symbol,PERIOD_CURRENT,0)) { check_sell(); } } } //+------------------------------------------------------------------+ //|This function checks if we can open buy positions | //+------------------------------------------------------------------+ void check_buy() { if(PositionsTotal() == 0) { for(int i = 0; i < position_size; i++) { ExtTrade.Buy(lot_multiple * min_volume,brent,ask,0,0,"BUY"); } } } //+------------------------------------------------------------------+ //|This function checks if we can open sell positions | //+------------------------------------------------------------------+ void check_sell() { if(PositionsTotal() == 0) { for(int i = 0; i < position_size; i++) { ExtTrade.Sell(lot_multiple * min_volume,brent,bid,0,0,"SELL"); } } } //+------------------------------------------------------------------+ //|This function will close all open trades | //+------------------------------------------------------------------+ void CloseAll(void) { for(int i=PositionsTotal()-1; i>=0; i--) { if(PositionSelectByTicket(PositionGetTicket(i))) { if(PositionGetSymbol(i) == brent) { ulong ticket; ticket = PositionGetTicket(i); ExtTrade.PositionClose(ticket); } } } } //+------------------------------------------------------------------+ //|This function closes any open buy trades | //+------------------------------------------------------------------+ void close_buy() { ulong ticket; int type; if(PositionsTotal() > 0) { for(int i = 0; i < PositionsTotal(); i++) { ticket = PositionGetTicket(i); type = (int)PositionGetInteger(POSITION_TYPE); if(type == POSITION_TYPE_BUY) { ExtTrade.PositionClose(ticket); } } } } //+------------------------------------------------------------------+ //|This function closes any open sell trades | //+------------------------------------------------------------------+ void close_sell() { ulong ticket; int type; if(PositionsTotal() > 0) { for(int i = 0; i < PositionsTotal(); i++) { ticket = PositionGetTicket(i); type = (int)PositionGetInteger(POSITION_TYPE); if(type == POSITION_TYPE_SELL) { ExtTrade.PositionClose(ticket); } } } } //+------------------------------------------------------------------+ //|This function initializes our model and fits it onto the data | //+------------------------------------------------------------------+ bool InitializeModel() { //Try select the symbols if(SymbolSelect(brent,true) && SymbolSelect(wti,true)) { Print("Symbols Available. Bars: ",max_bars," Fetch: ",fetch," Look ahead: ",look_ahead); //Get historical data on Brent , our model output y.CopyRates(brent,PERIOD_CURRENT,COPY_RATES_CLOSE,1,fetch); //model input A.CopyRates(brent,PERIOD_CURRENT,COPY_RATES_CLOSE,(1 + look_ahead),fetch); brent_price.CopyRates(brent,PERIOD_CURRENT,COPY_RATES_CLOSE,(1+look_ahead),fetch); wti_price.CopyRates(wti,PERIOD_CURRENT,COPY_RATES_CLOSE,(1+look_ahead),fetch); //Calculate the spread spread = brent_price - wti_price; Print("The Current Spread: ",spread); A.Reshape(3,fetch); //Add the spread to the input matrix A.Row(spread,1); //Add a column for the intercept A.Row(intercept,2); //Reshape the matrices A = A.Transpose(); y = y.Transpose(); //Inspect the matrices if((A.Cols() == 0 || y.Cols() == 0)) { Print("Error occured when copying historical data"); Print("A rows: ",A.Rows()," y rows: ",y.Rows()," A columns: ",A.Cols()," y cols: ",y.Cols()); Print("A"); Print(A); Print("y"); Print(y); return(false); } else { Print("No errors occured when copying historical data"); x = A.PInv().MatMul(y); Print("Finished Fitting The Model"); Print(x); return(true); } } Print("Faield to select symbols"); return(false); } //+------------------------------------------------------------------+ //|This function makes a prediction once our model has been trained | //+------------------------------------------------------------------+ double ModelForecast() { if(model_initialized) { //model input A.CopyRates(brent,PERIOD_CURRENT,COPY_RATES_CLOSE,0,1); brent_price.CopyRates(brent,PERIOD_CURRENT,COPY_RATES_CLOSE,0,1); wti_price.CopyRates(wti,PERIOD_CURRENT,COPY_RATES_CLOSE,0,1); //Calculate the spread spread = brent_price - wti_price; Print("The Spread: ",spread); A.Reshape(3,fetch); //Add the spread to the input matrix A.Row(spread,1); //Add a column for the intercept A.Row(intercept,2); //Reshape the matrices A = A.Transpose(); double _forecast = (A[0][0]*x[0][0]) + (A[1][0]*x[1][0]) + (A[2][0]*x[2][0]); return(_forecast); } return(0); } //+------------------------------------------------------------------+
Agora estamos prontos para testar nosso algoritmo de negociação usando o MetaTrader 5 Strategy Tester embutido.
Fig 7: Teste retrospectivo do nosso algoritmo de negociação quantitativa.
Fig 8: Retornos históricos do nosso teste retrospectivo.
Conclusão
Há espaço para melhorias na estratégia que consideramos hoje. Por exemplo, 67% de todas as reservas conhecidas de petróleo no mundo estão localizadas no Oriente Médio, mas não consideramos nenhum dos benchmarks de petróleo do Golfo Pérsico. Além disso, existem outros spreads interessantes que podem ter qualidades preditivas que merecem mais pesquisa, como o crack spread. O crack spread mede a rentabilidade das refinarias. Historicamente, quando os spreads de crack são altos, a oferta tende a aumentar e, quando são baixos, a oferta tende a cair. Se você leu o artigo até aqui, deve ver de imediato as possíveis implicações que o crack spread pode ter no preço do petróleo bruto.
Nossa estratégia é lucrativa, mas é suscetível a períodos irregulares de drawdown. Os mercados de petróleo são notoriamente voláteis, e melhorias futuras serão alcançadas aplicando princípios de gerenciamento de risco mais robustos que ainda sejam lucrativos.
Desejando a você paz, prosperidade e negociações lucrativas.
Traduzido do Inglês pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/en/articles/14855
- 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