English Русский 中文 Español Deutsch 日本語
preview
Reimaginando Estratégias Clássicas (Parte II): Rompimentos das Bandas de Bollinger

Reimaginando Estratégias Clássicas (Parte II): Rompimentos das Bandas de Bollinger

MetaTrader 5Exemplos |
186 2
Gamuchirai Zororo Ndawana
Gamuchirai Zororo Ndawana

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:

  1. Como comparar analiticamente duas possíveis estratégias de trading.
  2. Como implementar a Análise Discriminante Linear do zero em MQL5.
  3. 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.

    Exportando os dados necessários

    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.

    Buscar os dados.

    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 ta
    
    Em 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 = 20
    Agora 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")
    

    Visualizando o comportamento do preço

    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")
    


    Visualizando o comportamento do preço dentro das zonas das Bandas de Bollinger

    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")
    

    Visualizando o comportamento do preço dentro das 4 zonas.

    Fig 5: Visualizando o comportamento do preço dentro das 4 zonas.

    Vamos criar um gráfico categórico com o preço de fechamento no eixo y e dois valores no eixo x. O primeiro valor, Estado do Preço 0, indica instâncias em que o preço caiu nos 10 candles anteriores. Acima do Estado 0, há uma nuvem de pontos azuis e laranjas. Esses pontos representam instâncias onde, após cair por 10 candles, o preço ou continuou caindo ou reverteu e começou a subir nos 10 candles seguintes, respectivamente. Observe que não há uma separação clara entre as instâncias em que o preço continuou caindo e as em que ele inverteu a tendência e começou a subir. Parece que o único ponto de separação bem definido ocorre quando o preço se aproxima de valores extremos. Por exemplo, em todos os níveis de preço abaixo de 1.1 no Estado 0, o preço consistentemente se recuperou.

    #we have very poor separation in the data
    sns.catplot(data=csv,x="Price State",y="Close",hue="Price Target")
    

    Visualizando a separação dos dados dentro do conjunto de dados.

    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")

    Visualizando a separação dos dados nos estados das Bandas de Bollinger.

    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")

    Visualizando os dados

    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")
    

    Visualizando a separação no conjunto de dados

    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

    Os novos níveis de precisão que obtivemos

    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:

    1. Se a previsão indicar um movimento da zona 1 para a zona 2, isso gera um sinal de venda.
    2. Por outro lado, uma previsão de movimento da zona 4 para a zona 3 indica um sinal de compra.
    3. 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
         }
      }
    //+------------------------------------------------------------------+
    

    Nosso sistema em ação

    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

    Últimos Comentários | Ir para discussão (2)
    Anil Varma
    Anil Varma | 10 ago. 2024 em 08:52

    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?

    Gamuchirai Zororo Ndawana
    Gamuchirai Zororo Ndawana | 10 ago. 2024 em 12:35
    Anil Varma #:

    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?

    Olá, Anil, deixe-me começar dizendo que nada será interrompido se usarmos vetores em vez de matrizes, portanto, sim, poderíamos ter usado matrizes simples.

    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.
    //+------------------------------------------------------------------+
    //|Anil.mq5
    //|Gamuchirai Zororo Ndawana |
    //| https://www.mql5.com/pt/gamuchiraindawa |
    //+------------------------------------------------------------------+
    #property copyright "Gamuchirai Zororo Ndawana"
    #property link      "https://www.mql5.com/en/gamuchiraindawa"
    #property version   "1.00"
    //+------------------------------------------------------------------+
    //| Função de início do programa de script|
    //+------------------------------------------------------------------+
    void OnStart()
      {
    //--- Aqui está meu problema com matrizes
    double anil_array[3];
    ArrayFill(anil_array,0,3,0);
    ArrayPrint(anil_array);
    //--- Temos que iterar sobre todos os elementos para realizar os cálculos
    for(int i = 0; i < 3; i++)
       {
          anil_array[i] += 1;
       }
    ArrayPrint(anil_array);
    //--- E a mesma operação com o vetor
    vector anil_vector = vector::Zeros(3); //Similar a uma matriz cheia de Zeros 
    Print(anil_vector);   
    //--- Os vetores nos permitem realizar cálculos em todos os elementos de uma só vez
    anil_vector = anil_vector + 1;
    Print(anil_vector);
      }
    //+------------------------------------------------------------------+
    

    Vetores VS Matrizes.

    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.

    Algoritmo de arquearia — Archery Algorithm (AA) Algoritmo de arquearia — Archery Algorithm (AA)
    Neste artigo, examinamos detalhadamente o algoritmo de otimização inspirado na arquearia, com foco no uso do método de roleta como mecanismo de seleção de áreas promissoras para a colocação das "flechas". Esse método permite avaliar a qualidade das soluções e selecionar as posições mais promissoras para um estudo mais aprofundado.
    Soluções simples para trabalhar com indicadores Soluções simples para trabalhar com indicadores
    Neste artigo, explicarei como criar um painel simples para ajustar as configurações de um indicador diretamente no gráfico e quais modificações são necessárias no indicador para integrar esse painel. O artigo é voltado exclusivamente para quem está começando a aprender a linguagem MQL5.
    Abordagem quantitativa na gestão de riscos: aplicação do modelo VaR para otimização de portfólio multimoeda com Python e MetaTrader 5 Abordagem quantitativa na gestão de riscos: aplicação do modelo VaR para otimização de portfólio multimoeda com Python e MetaTrader 5
    Neste artigo, revelamos o potencial do modelo Value at Risk (VaR) para a otimização de portfólios multimoeda. Utilizando o Python e as funcionalidades do MetaTrader 5, demonstramos como implementar a análise VaR para uma distribuição eficiente de capital e gerenciamento de posições. Desde os fundamentos teóricos até a implementação prática, o artigo abrange todos os aspectos da aplicação de um dos sistemas mais robustos de cálculo de risco — o VaR — no trading algorítmico.
    Redes neurais em trading: Transformer para nuvens de pontos (Pointformer) Redes neurais em trading: Transformer para nuvens de pontos (Pointformer)
    Neste artigo, falaremos sobre os algoritmos que utilizam métodos de atenção para resolver tarefas de detecção de objetos em nuvens de pontos. A detecção de objetos em nuvens de pontos é de grande importância para diversas aplicações práticas.