
Reimaginando Estratégias Clássicas (Parte II): Rompimentos das Bandas de Bollinger
Introdução
As Bandas de Bollinger são ferramentas versáteis em estratégias de trading, sendo eficazes tanto para seguir tendências quanto para identificar possíveis pontos de reversão. Tecnicamente, o indicador é composto por uma média móvel exponencial (EMA), que suaviza o preço de fechamento de um ativo. Essa linha central é envolvida por duas linhas adicionais, posicionadas acima e abaixo da EMA, normalmente a 2 desvios-padrão.
Neste artigo, nosso objetivo é analisar empiricamente os benefícios dessa estratégia desde o início. Queremos ajudar os leitores que estão considerando o uso das Bandas de Bollinger a decidir se essa estratégia é a mais adequada para eles. Além disso, mostraremos como os indicadores técnicos podem ser usados para orientar modelos de IA e, com sorte, desenvolver estratégias de trading mais estáveis.
Conseguimos isso treinando dois modelos de IA equivalentes usando o algoritmo de Análise Discriminante Linear e comparando-os com validação cruzada em séries temporais, contando exclusivamente com a biblioteca scikit-learn para os testes. O primeiro modelo foi treinado para prever simplesmente se o preço iria se valorizar ou desvalorizar, enquanto o segundo aprendeu a prever como o preço se move entre as quatro zonas definidas pelas Bandas de Bollinger. Infelizmente, para os fãs das Bandas de Bollinger, nossas observações empíricas nos levaram à conclusão de que prever o preço diretamente pode ser mais eficaz do que prever a transição entre as quatro zonas criadas pelas Bandas de Bollinger. No entanto, vale ressaltar que nenhuma técnica de otimização foi empregada para definir os parâmetros do indicador.
Este artigo tem como objetivo demonstrar:
- Como comparar analiticamente duas possíveis estratégias de trading.
- Como implementar a Análise Discriminante Linear do zero em MQL5.
- Como construir estratégias de trading estáveis que incorporam IA.
Visão Geral da Estratégia e Nossas Motivações
O termo "Inteligência Artificial" (IA) é, sem dúvida, uma das nomenclaturas mais enganosas da história. Depois de ler este artigo, você pode concordar que IA é um nome inadequado. Como autor, minha crítica está na palavra "inteligência". Modelos de IA não são inteligentes no sentido humano. Em vez disso, são aplicações inteligentes de algoritmos de otimização.
Os modelos de IA têm como principal objetivo minimizar erros ou maximizar recompensas dentro de um sistema. No entanto, as soluções derivadas desses modelos nem sempre são práticas. Por exemplo, um sistema de IA projetado para minimizar perdas em uma conta de trading pode concluir que a melhor solução é não realizar nenhuma operação, pois isso garante nenhuma perda. Embora essa solução satisfaça matematicamente o problema em questão, ela é impraticável para o trading.
Como praticantes inteligentes de IA, devemos guiar nossos modelos com restrições cuidadosamente planejadas. Neste artigo, direcionaremos nossos modelos de IA usando as Bandas de Bollinger. Identificaremos quatro possíveis zonas onde o preço pode estar a qualquer momento. Observe que o preço só pode estar em uma dessas quatro zonas em um dado momento:
- Zona 1: O preço está completamente acima das Bandas de Bollinger.
- Zona 2: O preço está acima da banda média, mas abaixo da banda superior.
- Zona 3: O preço está acima da banda inferior, mas abaixo da banda média.
- Zona 4: O preço está abaixo da banda inferior.
Treinaremos um modelo para entender como o preço transita entre essas quatro zonas e prever para qual zona ele se moverá em seguida. Os sinais de trading são gerados sempre que o preço muda de uma zona para outra. Por exemplo, se nosso modelo prevê que o preço se moverá da Zona 2 para a Zona 1, interpretamos isso como um movimento de alta e iniciamos uma ordem de compra. Nosso modelo e Expert Advisor serão totalmente implementados em MQL5 nativo.
As Bandas de Bollinger podem ser utilizadas em uma variedade de estratégias de trading, desde seguir tendências até identificar pontos de reversão. Tecnicamente, esse indicador consiste em uma média móvel exponencial (EMA), que normalmente suaviza o preço de fechamento de um ativo. Ele é flanqueado por duas bandas adicionais: uma posicionada acima e outra abaixo da EMA, ambas normalmente definidas a 2 desvios-padrão.
Tradicionalmente, as Bandas de Bollinger são usadas para identificar níveis de preço sobrecomprados e sobrevendidos. Quando os preços atingem a banda superior, eles tendem a retornar ao valor mediano, e esse comportamento geralmente se aplica também à banda inferior. Isso pode ser interpretado como o ativo sendo descontado em 2 desvios-padrão quando atinge a banda inferior, potencialmente atraindo investidores a comprá-lo a um preço atrativo. No entanto, há momentos em que os preços podem romper violentamente as Bandas de Bollinger e continuar em uma forte tendência. Infelizmente, nossa análise estatística mostra que pode ser mais difícil prever rompimentos das Bandas de Bollinger do que prever mudanças no preço.Obtendo os Dados do Nosso Terminal MetaTrader 5
Para começar, abra seu terminal MetaTrader5 e clique no ícone de Símbolo no menu de contexto. Você verá uma lista de símbolos disponíveis no seu terminal.
Fig 1: Preparando para buscar dados do nosso Terminal MetaTrader5.
Em seguida, clique na janela Barras e pesquise pelo Símbolo que deseja modelar. Selecione o período de tempo que deseja usar. Para o nosso exemplo, estarei modelando a taxa de câmbio diária do GBPUSD.
Fig 2: Preparando para exportar nossos dados.
Em seguida, clique no botão "Exportar Barras", e agora continuaremos nossa análise no Python.
Análise Exploratória de Dados
Vamos visualizar as interações entre as Bandas de Bollinger e as mudanças nos níveis de preço.
Começaremos importando as bibliotecas necessárias.
#Import libraries import pandas as pd import numpy as np import seaborn as sns import pandas_ta as taEm seguida, leremos o arquivo CSV que geramos para nosso teste empírico. Observe que passamos o parâmetro sep="\t" para indicar que nosso arquivo CSV é delimitado por tabulação. Esse é o formato de saída padrão do Terminal MetaTrader5.
#Read in the csv file csv = pd.read_csv("/home/volatily/market_data/GBPUSD_Daily_20160103_20240131.csv",sep="\t")Agora, vamos definir nosso horizonte de previsão.
#Define how far into the future we should forecast look_ahead = 20Agora calcularemos as Bandas de Bollinger para os dados usando a biblioteca pandas_ta.
#Add the Bollinger bands csv.ta.bbands(length=30,std=2,append=True)Em seguida, precisamos de uma coluna para armazenar o preço de fechamento futuro.
#Add a column to show the future price csv["Future Close"] = csv["Close"].shift(-look_ahead)
Agora, vamos rotular nossos dados. Teremos dois rótulos: um indicando a mudança no preço e o outro indicando a mudança no preço entre as zonas das Bandas de Bollinger. Mudanças no preço serão rotuladas como 1 para alta e 0 para baixa. Os rótulos das Bandas de Bollinger foram definidos anteriormente.
#Add the normal target, predicting changes in the close price csv["Price Target"] = 0 csv["Price State"] = 0 #Label the data our conditions #If price depreciated, our label is 0 csv.loc[csv["Close"] < csv["Close"].shift(look_ahead),"Price State"] = 0 csv.loc[csv["Close"] > csv["Future Close"], "Price Target"] = 0 #If price appreciated, our label is 1 csv.loc[csv["Close"] > csv["Close"].shift(look_ahead),"Price State"] = 1 csv.loc[csv["Close"] < csv["Future Close"], "Price Target"] = 1 #Label the Bollinger bands #The label to store the current state of the market csv["Current State"] = -1 #If price is above the upper-band, our label is 1 csv.loc[csv["Close"] > csv["BBU_30_2.0"], "Current State"] = 1 #If price is below the upper-band and still above the mid-band,our label is 2 csv.loc[(csv["Close"] < csv["BBU_30_2.0"]) & (csv["Close"] > csv["BBM_30_2.0"]),"Current State"] = 2 #If price is below the mid-band and still above the low-band,our label is 3 csv.loc[(csv["Close"] < csv["BBM_30_2.0"]) & (csv["Close"] > csv["BBL_30_2.0"]),"Current State"] = 3 #Finally, if price is beneath the low-band our label is 4 csv.loc[csv["Close"] < csv["BBL_30_2.0"], "Current State"] = 4 #Now we can add a column to denote the future state the market will be in csv["State Target"] = csv["Current State"].shift(-look_ahead)
Agora, vamos excluir quaisquer entradas nulas.
#Let's drop any NaN values csv.dropna(inplace=True)
Agora estamos prontos para começar a visualizar nossos dados, começando pelas mudanças nos níveis de preço usando box plots. No eixo y, exibiremos os preços de fechamento, e no eixo x, teremos dois valores. O primeiro valor no eixo x representa as instâncias nos dados em que o preço estava caindo, marcado como 0. Dentro do valor 0, você verá dois box plots. O primeiro box plot, mostrado em azul, representa as instâncias onde o preço caiu por 20 candles e continuou caindo por mais 20 candles. O box plot laranja representa as instâncias onde o preço caiu por 20 candles, mas depois se valorizou nos 20 candles seguintes. Observe que, nos dados coletados, sempre que os níveis de preço caíram abaixo de 1.1, eles sempre se recuperaram. Por outro lado, o valor 1 no eixo x também tem dois box plots acima dele. O primeiro box plot azul resume instâncias onde o preço se valorizou e depois caiu, enquanto o segundo box plot laranja resume instâncias onde o preço subiu e continuou subindo.
Observe que para o valor 1, ou seja, quando o preço sobe por 20 candles, a cauda do box plot azul é maior do que a do box plot laranja. Isso pode indicar que sempre que a taxa de câmbio GBPUSD sobe em direção ao nível de 1.5, ela tende a cair, enquanto na coluna 0, quando a taxa de câmbio cai para cerca de 1.1, parece que o preço tem uma tendência de reverter e começar a subir.
#Notice that the tails of the box plots have regions where they stop overlapping these zones may guide us as boundaries sns.boxplot(data=csv,x="Price State",y="Close",hue="Price Target")
Fig 3: Visualizando as mudanças nos níveis de preço.
Também podemos realizar visualizações semelhantes usando os estados definidos pelas Bandas de Bollinger. Como antes, o preço de fechamento estará no eixo y, e a localização atual do preço dentro das Bandas de Bollinger será marcada pelos quatro valores no eixo x. Observe que as caudas dos box plots têm regiões onde naturalmente não se sobrepõem. Essas regiões podem potencialmente servir como limites de classificação. Por exemplo, observe que sempre que o preço está no estado 4, ou completamente abaixo das Bandas de Bollinger, e se aproxima do nível 1.1, ele parece sempre se recuperar.
#Notice that the tails of the box plots have regions where they stop overlapping these zones may guide us as boundaries sns.boxplot(data=csv,x="Current State",y="Close",hue="Price Target")
Fig 4: Visualizando o comportamento do preço dentro das 4 zonas das Bandas de Bollinger.
Além disso, também podemos visualizar como o preço transita entre os quatro estados da Banda de Bollinger usando gráficos de caixa. Por exemplo, o box plot abaixo tem o preço de fechamento no eixo y e quatro valores no eixo x representando as quatro zonas criadas pelas Bandas de Bollinger. Cada box plot resume para onde o preço transitou após aparecer nessa zona. Vamos interpretar os dados juntos. Observe que o primeiro valor, estado 1, tem apenas três box plots. Isso significa que, a partir do estado 1, o preço só pode transitar para três estados possíveis: ele pode permanecer no estado 1 ou transitar para os estados 2 ou 3.
#Notice that the tails of the box plots have regions where they stop overlapping these zones may guide us as boundaries sns.boxplot(data=csv,x="Current State",y="Close",hue="State Target")
Fig 5: Visualizando o comportamento do preço dentro das 4 zonas.
#we have very poor separation in the data sns.catplot(data=csv,x="Price State",y="Close",hue="Price Target")
Fig 6: Visualizando a separação no conjunto de dados.
Podemos realizar as mesmas visualizações usando os quatro estados definidos pelas Bandas de Bollinger. Mais uma vez, observamos que os pontos azuis e laranjas estão melhor separados nos níveis de preços extremos.
#Visualizing the separation of data in the Bollinger band zones sns.catplot(data=csv,x="Current State",y="Close",hue="Price Target")
Fig 7: Visualizando a separação dos dados nas zonas das Bandas de Bollinger.
Agora podemos criar um gráfico de dispersão com o valor de fechamento no eixo x e o preço de fechamento futuro no eixo y. Vamos colorir os pontos de laranja ou azul, dependendo se o preço subiu ou caiu nos últimos 20 candles. Imagine traçar uma linha dourada do canto inferior esquerdo ao canto superior direito do gráfico. Todos os pontos acima dessa linha dourada representam instâncias em que o preço subiu nos próximos 20 candles, independentemente de ter caído (azul) ou subido (laranja) nos 20 candles anteriores. Observe que há uma mistura de pontos azuis e laranjas em ambos os lados da linha dourada.
Além disso, note que, se traçarmos uma linha vermelha imaginária no valor de fechamento de 1.3, haveria muitos pontos azuis e laranjas tocando essa linha. Isso implica que outras variáveis afetam o preço de fechamento futuro, além do preço de fechamento atual. Outra maneira de interpretar essas observações é que o mesmo valor de entrada pode resultar em diferentes valores de saída, indicando que nosso conjunto de dados é ruidoso!
#Notice that using the price target gives us beautiful separation in the data set sns.scatterplot(data=csv,x="Close",y="Future Close",hue="Price Target")
Fig 8: Nosso conjunto de dados tem pouca separação natural.
Agora realizaremos a mesma visualização usando o estado-alvo das Bandas de Bollinger para colorir o gráfico de dispersão. Observe que temos uma separação muito ruim dentro do nosso conjunto de dados ao usar as Bandas de Bollinger. Visualmente, parece ainda pior do que a separação obtida quando usamos apenas o próprio preço.
#Using the Bollinger bands to define states, however, gives us rather mixed separation sns.scatterplot(data=csv,x="Close",y="Future Close",hue="Current State")
Fig 9: Visualizando a separação no conjunto de dados criado pelas Zonas das Bandas de Bollinger.
Agora, vamos realizar nossos testes analíticos para determinar se conseguimos maior precisão ao prever mudanças nos níveis de preço ou mudanças nos estados das Bandas de Bollinger. Primeiro, importamos as bibliotecas necessárias.
#Now let us compare our accuracy forecasting the original price target and the new Bollinger bands target from sklearn.discriminant_analysis import LinearDiscriminantAnalysis from sklearn.model_selection import TimeSeriesSplit from sklearn.metrics import accuracy_score
Em seguida, definiremos nossos parâmetros de validação cruzada para séries temporais. O primeiro parâmetro, splits, especifica o número de partições a serem criadas a partir dos nossos dados. O segundo parâmetro, gap, determina o tamanho do intervalo entre cada partição. Esse intervalo deve ser pelo menos tão grande quanto nosso horizonte de previsão.
#Now let us define the cross validation parameters splits = 10 gap = look_ahead
Agora podemos criar nosso objeto de série temporal, que nos fornecerá os índices apropriados para nosso conjunto de treinamento e conjunto de teste. Em nosso exemplo, ele gerará 10 pares de índices para treinar e avaliar nosso modelo.
#Now create the cross validation object
tscv = TimeSeriesSplit(n_splits=splits,gap=gap)
Em seguida, criaremos um DataFrame para armazenar a precisão do nosso modelo para prever cada alvo.
#We need a dataframe to store the accuracy associated with each target target_accuracy = pd.DataFrame(index=np.arange(0,splits),columns=["Price Target Accuracy","New Target Accuracy"])
Agora definiremos as entradas do nosso modelo.
#Define the inputs predictors = ["Open","High","Low","Close"] target = "Price Target"
Agora realizaremos o teste de validação cruzada.
#Now let us perform the cross validation for i,(train,test) in enumerate(tscv.split(csv)): #First initialize the model model = LinearDiscriminantAnalysis() #Now train the model model.fit(csv.loc[train[0]:train[-1],predictors],csv.loc[train[0]:train[-1],target]) #Now record the accuracy target_accuracy.iloc[i,0] = accuracy_score(csv.loc[test[0]:test[-1],target],model.predict(csv.loc[test[0]:test[-1],predictors]))
Agora podemos finalmente analisar o resultado dos testes.
precisão_alvo
Fig 10: Nosso modelo teve um desempenho melhor ao prever mudanças no preço diretamente.
Conforme mencionado anteriormente, nossos testes mostraram que nosso modelo é mais eficaz na previsão dos níveis de preço do que nas transições entre as Bandas de Bollinger. No entanto, observe que, em média, as duas estratégias não são significativamente diferentes.
Em seguida, implementaremos a estratégia no código MQL5 para testá-la e ver como ela se comporta com dados reais do mercado.
Implementando a Estratégia
Para começar, primeiro importaremos as bibliotecas necessárias que utilizaremos ao longo do nosso programa.
//+------------------------------------------------------------------+ //| Target Engineering.mq5 | //| Gamuchirai Zororo Ndawana | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Gamuchirai Zororo Ndawana" #property link "https://www.mql5.com" #property version "1.00" //+------------------------------------------------------------------+ //| Libraries we need | //+------------------------------------------------------------------+ /* This Expert Advisor will implement the Linear Discriminant Anlysis algorithm to help us successfully trade Bollinger Band Breakouts. Gamuchirai Zororo Ndawana Selebi Phikwe Botswana Wednesday 10 July 2024 15:42 */ #include <Trade/Trade.mqh>//Trade class CTrade Trade;
Em seguida, definiremos entradas configuráveis pelo usuário, como o período das Bandas de Bollinger e o desvio padrão.
//+------------------------------------------------------------------+ //| Input variables | //+------------------------------------------------------------------+ input double bband_deviation = 2.0;//Bollinger Bands standard deviation input int bband_period = 60; //Bollinger Bands Period input int look_ahead = 10; //How far into the future should we forecast? int input lot_multiple = 1; //How many times bigger than minimum lot? int input fetch = 200;//How much data should we fetch? input double stop_loss_values = 1;//Stop loss values
Posteriormente, definiremos as variáveis globais que serão utilizadas em nossa aplicação.
//+------------------------------------------------------------------+ //| Global variables | //+------------------------------------------------------------------+ int bband_handler;//Technical Indicator Handlers vector bband_high_reading = vector::Ones(fetch);//Bollinger band high reading vector bband_mid_reading = vector::Ones(fetch);//Bollinger band mid reading vector bband_low_reading = vector::Ones(fetch);//Bollinger band low reading double minimum_volume;//The smallest contract size allowed double ask_price;//Ask double bid_price;//Bid vector input_data = vector::Zeros(fetch);//All our input data will be kept in vectors int training_output_array[];//Our output data will be stored in a vector vector output_data = vector::Zeros(fetch); double variance;//This is the variance of our input data int classes = 4;//The total number of output classes we have vector mean_values = vector::Zeros(classes);//This vector will store the mean value for each class vector probability_values = vector::Zeros(classes);//This vector will store the prior probability the target will belong each class vector total_class_count = vector::Zeros(classes);//This vector will count the number of times each class was the target bool model_trained = false;//Has our model been trained? bool training_procedure_running = false;//Have we started the training process? int forecast = 0;//Our model's forecast double discriminant_values[4];//The discriminant function int current_state = 0;//The current state of the system
Em seguida, precisamos definir a função de inicialização do nosso Expert Advisor. Nesta função, inicializaremos o indicador Bandas de Bollinger e buscaremos dados importantes do mercado.
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Initialize the bollinger bands bband_handler = iBands(_Symbol,PERIOD_CURRENT,bband_period,0,bband_deviation,PRICE_CLOSE); //--- Market data minimum_volume = SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MIN); //--- End of initilization return(INIT_SUCCEEDED); }
Seguindo isso, definiremos funções auxiliares essenciais para dividir nosso código em segmentos menores e mais gerenciáveis. A primeira função que criaremos será responsável por atualizar os dados do mercado.
//+------------------------------------------------------------------+ //|This function will update the price and other technical data | //+------------------------------------------------------------------+ void update_technical_data(void) { //--- Update the bid and ask prices ask_price = SymbolInfoDouble(_Symbol,SYMBOL_ASK); bid_price = SymbolInfoDouble(_Symbol,SYMBOL_BID); }
Posteriormente, precisamos implementar uma função que orquestre o procedimento de inicialização. Essa função garantirá que busquemos os dados de treinamento, ajustemos nosso modelo e comecemos a fazer previsões na sequência correta.
//+------------------------------------------------------------------+ //|This function will start training our model | //+------------------------------------------------------------------+ void model_initialize(void) { //--- First we have to fetch the input and output data Print("Initializing the model"); int input_start = 1 + (look_ahead * 2); int output_start = 1+ look_ahead; fetch_input_data(input_start,fetch); fetch_output_data(output_start,fetch); //--- Fit the model fit_lda_model(); }
Seguindo isso, definiremos a função responsável por buscar os dados de entrada para treinar nosso modelo. É importante notar que a entrada do modelo consistirá no estado atual do mercado—especificamente, em qual zona o mercado se encontra atualmente. O modelo então preverá para qual zona o mercado se moverá a seguir.
//+------------------------------------------------------------------+ //|This function will fetch the inputs for our model | //+------------------------------------------------------------------+ void fetch_input_data(int f_start,int f_fetch) { //--- This function will fetch input data for our model Print("Fetching input data"); //--- The input for our model will be the current state of the market //--- To know the current state of the market, we have to first update our indicator readings bband_mid_reading.CopyIndicatorBuffer(bband_handler,0,f_start,f_fetch); bband_high_reading.CopyIndicatorBuffer(bband_handler,1,f_start,f_fetch); bband_low_reading.CopyIndicatorBuffer(bband_handler,2,f_start,f_fetch); vector historical_prices; historical_prices.CopyRates(_Symbol,PERIOD_CURRENT,COPY_RATES_CLOSE,f_start,f_fetch); //--- Reshape the input data input_data.Resize(f_fetch); //--- Now we will input the state of the market for(int i = 0; i < f_fetch;i++) { //--- Are we above the bollinger bands entirely? if(historical_prices[i] > bband_high_reading[i]) { input_data[i] = 1; } //--- Are we between the upper and mid band? else if((historical_prices[i] < bband_high_reading[i]) && (historical_prices[i] > bband_mid_reading[i])) { input_data[i] = 2; } //--- Are we between the mid and lower band? else if((historical_prices[i] < bband_mid_reading[i]) && (historical_prices[i] > bband_low_reading[i])) { input_data[i] = 3; } //--- Are we below the bollinger bands entirely? else if(historical_prices[i] < bband_low_reading[i]) { input_data[i] = 4; } } //--- Show the input data Print(input_data); }
Seguindo em frente, precisamos de uma função para recuperar os dados de saída para nosso modelo. Essa tarefa é mais intrincada do que buscar os dados de entrada. Devemos não apenas registrar a zona final em que o preço terminou, mas também rastrear quantas vezes cada zona foi o resultado. Essa contagem é crucial para estimar os parâmetros de nosso modelo LDA em uma fase posterior.
A partir deste ponto, estamos preparados para ajustar nosso modelo LDA. Existem vários métodos disponíveis para ajustar o modelo; hoje, focaremos em uma abordagem específica.
//+---------------------------------------------------------------------+ //|Fetch the output data for our model | //+---------------------------------------------------------------------+ void fetch_output_data(int f_start,int f_fetch) { //--- The output for our model will be the state of the market //--- To know the state of the market, we have to first update our indicator readings Print("Fetching output data"); bband_mid_reading.CopyIndicatorBuffer(bband_handler,0,f_start,(f_fetch)); bband_high_reading.CopyIndicatorBuffer(bband_handler,1,f_start,(f_fetch)); bband_low_reading.CopyIndicatorBuffer(bband_handler,2,f_start,(f_fetch)); vector historical_prices; historical_prices.CopyRates(_Symbol,PERIOD_CURRENT,COPY_RATES_CLOSE,f_start,f_fetch); //--- First we have to ensure that the class count has been reset total_class_count[0] = 0; total_class_count[1] = 0; total_class_count[2] = 0; total_class_count[3] = 0; //--- Now we need to resize the matrix ArrayResize(training_output_array,f_fetch); //--- Now we will input the state of the market to our output vector for(int i =0 ; i < f_fetch;i++) { //--- Are we above the bollinger bands entirely? if(historical_prices[i] > bband_high_reading[i]) { training_output_array[i] = 1; total_class_count[0] += 1; } //--- Are we between the upper and mid band? else if((historical_prices[i] < bband_high_reading[i]) && (historical_prices[i] > bband_mid_reading[i])) { training_output_array[i] = 2; total_class_count[1] += 1; } //--- Are we between the mid and lower band? else if((historical_prices[i] < bband_mid_reading[i]) && (historical_prices[i] > bband_low_reading[i])) { training_output_array[i] = 3; total_class_count[2] += 1; } //--- Are we below the bollinger bands entirely? else if(historical_prices[i] < bband_low_reading[i]) { training_output_array[i] = 4; total_class_count[3] += 1; } } //--- Show the output data Print("Final state of output vector"); ArrayPrint(training_output_array); //--- Show the total number of times each class appeared as the target. Print(total_class_count); }
O processo é um pouco intricado e requer uma explicação detalhada. Inicialmente, calculamos a soma total de todos os valores de entrada correspondentes a cada classe na saída. Por exemplo, para cada instância onde o alvo foi 1, computamos a soma de todos os valores de entrada mapeados para uma saída de 1, e assim por diante para cada classe de saída. Em seguida, calculamos o valor médio de X para cada classe. Se houvesse múltiplas entradas, calcularíamos o valor médio para cada entrada. Seguindo em frente, procedemos para determinar a probabilidade de cada classe aparecer como o alvo real, com base nos dados do conjunto de treinamento. Depois disso, calculamos a variância de X para cada classe de y. Por fim, atualizamos nossas flags para indicar a conclusão do procedimento de treinamento.
//+------------------------------------------------------------------+ //|Fit the LDA model | //+------------------------------------------------------------------+ void fit_lda_model(void) { //--- To fit the LDA model, we first need to know the mean value for each our inputs for each of our 4 classes double sum_class_one = 0; double sum_class_two = 0; double sum_class_three = 0; double sum_class_four = 0; //--- In this case we only have 1 input for(int i = 0; i < fetch;i++) { //--- Class 1 if(training_output_array[i] == 1) { sum_class_one += input_data[i]; } //--- Class 2 else if(training_output_array[i] == 2) { sum_class_two += input_data[i]; } //--- Class 3 else if(training_output_array[i] == 3) { sum_class_three += input_data[i]; } //--- Class 4 else if(training_output_array[i] == 4) { sum_class_four += input_data[i]; } } //--- Show the sums Print("Class 1: ",sum_class_one," Class 2: ",sum_class_two," Class 3: ",sum_class_three," Class 4: ",sum_class_four); //--- Calculate the mean value for each class mean_values[0] = sum_class_one / fetch; mean_values[1] = sum_class_two / fetch; mean_values[2] = sum_class_three / fetch; mean_values[3] = sum_class_four / fetch; Print("Mean values"); Print(mean_values); //--- Now we need to calculate class probabilities for(int i=0;i<classes;i++) { probability_values[i] = total_class_count[i] / fetch; } Print("Class probability values"); Print(probability_values); //--- Calculating the variance Print("Calculating the variance"); //--- Next we need to calculate the variance of the inputs within each class of y. //--- This process can be simplified into 2 steps //--- First we calculate the difference of each instance of x from the group mean. double squared_difference[4]; for(int i =0; i < fetch;i++) { //--- If the output value was 1, find the input value that created the output //--- Calculate how far that value is from it's group mean and square the difference if(training_output_array[i] == 1) { squared_difference[0] = MathPow((input_data[i]-mean_values[0]),2); } else if(training_output_array[i] == 2) { squared_difference[1] = MathPow((input_data[i]-mean_values[1]),2); } else if(training_output_array[i] == 3) { squared_difference[2] = MathPow((input_data[i]-mean_values[2]),2); } else if(training_output_array[i] == 4) { squared_difference[3] = MathPow((input_data[i]-mean_values[3]),2); } } //--- Show the squared difference values Print("Squared difference value for each output value of y"); ArrayPrint(squared_difference); //--- Next we calculate the variance as the average squared difference from the mean variance = (1.0/(fetch - 4.0)) * (squared_difference[0] + squared_difference[1] + squared_difference[2] + squared_difference[3]); Print("Variance: ",variance); //--- Update our flags to denote the model has been trained model_trained = true; training_procedure_running = false; } //+------------------------------------------------------------------+
Para fazer uma previsão com nosso modelo, começamos buscando os dados de entrada mais recentes do mercado. Usando esses dados de entrada, calculamos a função discriminante para cada classe possível. A classe com o maior valor da função discriminante será nossa classe prevista.
No MQL5, os arrays oferecem uma função útil chamada ArrayMaximum(), que retorna o índice do maior valor em um array 1D. Como os arrays têm índice zero, adicionamos 1 ao resultado do ArrayMaximum() para obter a classe prevista.
//+------------------------------------------------------------------+ //|This function will obtain forecasts from our model | //+------------------------------------------------------------------+ int model_forecast(void) { //--- First we need to fetch the most recent input data fetch_input_data(0,1); //--- Update the current state of the system current_state = input_data[0]; //--- We need to calculate the discriminant function for each class //--- The predicted class is the one with the largest discriminant function Print("Calculating discriminant values."); for(int i = 0; i < classes; i++) { discriminant_values[i] = (input_data[0] * (mean_values[i]/variance) - (MathPow(mean_values[i],2)/(2*variance)) + (MathLog(probability_values[i]))); } ArrayPrint(discriminant_values); return(ArrayMaximum(discriminant_values) + 1); }
Após obter uma previsão do nosso modelo, o próximo passo é interpretá-la e agir conforme necessário. Como mencionado anteriormente, nossos sinais de negociação são gerados quando o modelo prevê que o preço se moverá para uma zona diferente:
- Se a previsão indicar um movimento da zona 1 para a zona 2, isso gera um sinal de venda.
- Por outro lado, uma previsão de movimento da zona 4 para a zona 3 indica um sinal de compra.
- No entanto, se a previsão sugerir que o preço permanecerá na mesma zona (por exemplo, da zona 1 para a zona 1), isso não gera um sinal de entrada.
//+--------------------------------------------------------------------+ //|This function will interpret out model's forecast and execute trades| //+--------------------------------------------------------------------+ void find_entry(void) { //--- If the model's forecast is not equal to the current state then we are interested //--- Otherwise whenever the model forecasts that the state will remain the same //--- We are uncertain whether price levels will rise or fall if(forecast != current_state) { //--- If the model forecasts that we will move from a small state to a greater state //--- That is from 1 to 2 or from 2 to 4 then that is a down move if(forecast > current_state) { Trade.Sell(minimum_volume * lot_multiple,_Symbol,bid_price,(bid_price + stop_loss_values),(bid_price - stop_loss_values)); } //--- Otherwise we have a buy setup else { Trade.Buy(minimum_volume * lot_multiple,_Symbol,ask_price,(ask_price - stop_loss_values),(ask_price +stop_loss_values)); } } //--- Otherwise we do not have an entry signal from our model }
Finalmente, nosso manipulador de evento OnTick() é responsável por gerenciar o fluxo de eventos e garantir que só realizemos negociações quando nosso modelo estiver treinado, além de satisfazer nossas outras condições de negociação.
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- We must always update market data update_technical_data(); //--- First we must ensure our model has been trained switch(model_trained) { //--- Our model has been trained case(true): //--- If we have no open positions, let's obtain a forecast from our model if(PositionsTotal() == 0) { //--- Obtaining a forecast forecast = model_forecast(); Comment("Model forecast: ",forecast); //--- Find an entry setup find_entry(); } break; //--- End of case 1 //--- Our model has not been trained default: //--- We haven't started the training procedure! if(!training_procedure_running) { Print("Our model has not been trained, starting the training procedure now."); //--- Initialize the model model_initialize(); } break; //--- End of default case } } //+------------------------------------------------------------------+
Fig 11: Nosso sistema de negociação em ação.
Limitações
Até este ponto, nossa estratégia enfrenta uma limitação significativa: pode ser desafiador interpretá-la. Quando nosso modelo prevê que o preço ficará na mesma zona, não temos clareza sobre se os preços irão subir ou cair. Esse trade-off decorre da nossa decisão de categorizar os estados do mercado em quatro zonas distintas, o que aumenta a precisão, mas sacrifica a transparência em comparação com a previsão direta dos movimentos de preço. Além disso, essa abordagem gera menos sinais de negociação porque precisamos esperar que o modelo preveja uma mudança de zona antes de tomar uma ação.
Conclusão
Em conclusão, nossa estratégia aproveita o poder do aprendizado de máquina, especificamente a Análise Discriminante Linear (LDA), integrada com as Bandas de Bollinger para sinais de negociação. Embora ofereça maior precisão, nossa abordagem sacrifica um pouco da transparência. No geral, os traders podem estar melhor ao prever mudanças de preço do que ao prever rompimentos das Bandas de Bollinger.
Traduzido do Inglês pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/en/articles/15336
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.





- 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
Confira o novo artigo: Reimaginando estratégias clássicas (Parte II): Quebra das Bandas de Bollinger.
Autor: Gamuchirai Zororo Ndawana
Olá, Ndawana
Antes de mais nada, obrigado pelo artigo e por simplificar o mito da IA :) Estou tentando usar o sinal gerado por ele em meu código com algumas modificações.
Você poderia explicar o(s) motivo(s) pelo qual você usou vetores em vez de simples matrizes no seu código?
Oi Ndawana
Antes de mais nada, obrigado pelo artigo e por simplificar o mito da IA :) Estou tentando usar o sinal gerado por ele em meu código com algumas modificações.
Você poderia explicar o(s) motivo(s) pelo(s) qual(is) você usou vetores em vez de simples matrizes em seu código?
Minha preferência por vetores se deve às funções especializadas disponíveis somente para vetores e, além dessas funções especiais, os vetores também nos permitem executar cálculos em todos os elementos de uma só vez. Aqui está um exemplo simples.
Portanto, imagine se, no futuro, pensarmos em um cálculo que possa ser útil, será muito mais fácil modificar a base de código, já que estamos usando vetores.