Data Science e ML (Parte 37): Utilizando padrões de Candlestick e IA para superar o mercado
Conteúdo
- Introdução
- Fundamentos de um Candlestick
- Padrões de Candlestick
- Indicador para detecção de padrões de Candlestick
- Coleta de padrões de Candlestick para Machine Learning
- Treinamento de um modelo de IA para realizar previsões com base em padrões de Candlestick
- Finalizando a implementação em um robô de negociação
- Conclusão
Introdução
A primeira estratégia que utilizei no trading era baseada em candlesticks. Hoje eu já não a chamaria de estratégia, mas minha primeira operação foi aberta em razão de alguns padrões de candlestick que aprendi ao ler o livro Candlestick Trading Bible, de Honma Munehisa, que me havia sido recomendado por um amigo.
Os padrões de candlestick presentes nos gráficos financeiros são utilizados pelos traders para identificar possíveis movimentos de preço com base em padrões anteriores. Eles são formados pelos movimentos de alta e baixa dos preços que, embora possam parecer aleatórios, são utilizados pelos traders para estimar a direção do preço no curto prazo.
Esse conceito teve origem no século XVIII com um trader chamado Honma Munehisa, considerado um dos traders mais bem-sucedidos da história. Conhecido em sua época como o Deus dos Mercados, estima-se que suas descobertas tenham lhe rendido o equivalente a mais de US$ 10 bilhões em valores atuais.
Munehisa descobriu que, embora a oferta e a demanda determinassem o preço do arroz, os mercados também eram influenciados pelas emoções humanas.
Essas emoções humanas podem ser refletidas em um candlestick por meio da amplitude dos movimentos de preço e de diferentes cores. Tradicionalmente, um candle preto costuma representar um movimento de baixa, enquanto um candle branco representa um movimento de alta. Atualmente, porém, essas cores não são relevantes, pois podem ser personalizadas em qualquer plataforma de negociação.
Fundamentos de um Candlestick

As sombras ou pavios dos candlesticks mostram os preços máximo e mínimo do período e sua relação com os preços de abertura e fechamento. O formato do candle varia de acordo com a relação entre seus preços OHLC (Open, High, Low e Close — abertura, máxima, mínima e fechamento). Existem diversos padrões de candlestick introduzidos por Munehisa, além de outros surgidos mais recentemente entre traders.
Com base nesses padrões, vamos identificá-los, coletá-los e utilizá-los em modelos de Machine Learning para avaliar de que forma podem agregar valor aos nossos modelos de negociação baseados em IA e verificar se podem verificar se podem nos ajudar a superar o desempenho dos mercados financeiros.
Padrões de Candlestick
Como existem inúmeros padrões de candlestick, abordaremos apenas 10. O principal motivo para essa escolha é que esses padrões são simples de compreender e dependem exclusivamente de uma única barra, ou seja, podem ser detectados na barra atual.
Descartaremos todos os padrões de candlestick cuja detecção exija o processamento de múltiplos candles.
O formato de codificação da classe CTALib foi inspirado na TALib (Technical Analysis Library), uma biblioteca de análise técnica disponível para C#, C++ e Python que oferece diversas funções para detecção de padrões de candlestick.
Candle Branco
Este é um candlestick de alta, no qual o preço de fechamento é superior ao preço de abertura. Esse candlestick simples indica um movimento de alta, não oferece nenhuma previsão. Ele apenas indica a direção do candle.
Podemos implementá-lo da seguinte forma.
bool CTALib::CDLWHITECANDLE(double open, double close) { return (close > open); }
Candle Preto
Ao contrário do candle branco, este é um candlestick de baixa, no qual o preço de abertura é superior ao preço de fechamento. Esse candlestick simples indica um movimento de baixa.
Assim como o candle branco, ele não fornece nenhuma previsão; apenas indica a direção do candle.
bool CTALib::CDLBLACKCANDLE(double open,double close) { return (open > close); }
Padrão de Candlestick Doji
Um candlestick Doji é aquele em que os preços de abertura e fechamento são praticamente iguais. Visualmente, ele pode se parecer com uma cruz, uma cruz invertida ou um sinal de mais. Esse padrão de candlestick é relativamente raro e costuma aparecer em grupos. Normalmente, é interpretado como um sinal de reversão de tendência, embora também possa indicar indecisão do mercado em relação aos preços futuros.
Ele apresenta algumas variações, como mostrado na imagem abaixo.

Criar uma função para detectar um candlestick Doji pode ser desafiador, justamente por causa da própria definição desse padrão. Para que um candle seja considerado um Doji, os preços de abertura e fechamento devem ser iguais. Entretanto, é extremamente raro encontrar preços de abertura e fechamento exatamente iguais, considerando o volume e a volatilidade presentes nos mercados atuais. Mesmo que os preços estejam muito próximos — por exemplo, Open = 1.0000 e Close = 1.0001 —, para um computador esses dois valores continuam sendo diferentes.
Para contornar esse problema, podemos utilizar um valor de tolerância. Sempre que a diferença entre abertura e fechamento estiver dentro desse limite, podemos considerar que os preços estão suficientemente próximos e concluir que se trata de um padrão de candlestick Doji.
bool CTALib::CDLDOJI(double open,double close, double torrelance=3) { return (fabs(open-close)<torrelance*_Point); //A torrelance in market points }
Dragonfly Doji
O padrão de candlestick Dragonfly Doji sinaliza uma possível reversão no preço de um ativo. Quando um padrão Dragonfly Doji aparece durante uma tendência de baixa, ele pode indicar que os preços estão prestes a subir ou que a tendência de baixa pode estar chegando ao fim.

Como esse padrão é essencialmente um candlestick Doji com uma sombra inferior mais longa que a sombra superior, no código podemos primeiro verificar se o candle é um Doji e, em seguida, garantir explicitamente que a sombra inferior seja, por padrão, pelo menos duas vezes maior que a sombra superior.
bool CTALib::CDLDRAGONFLYDOJI(double open, double high, double low, double close, double body_torrelance = 3, double shadow_ratio = 2.0) { double body_size = MathAbs(open - close); double upper_shadow = upperShadowCalc(open, close, high); double lower_shadow = lowerShadowCalc(open, close, low); //--- Body is very small (like a Doji) if (CDLDOJI(open, close, body_torrelance)) { //--- Lower shadow is significantly longer than upper shadow if (lower_shadow > upper_shadow * shadow_ratio) return true; } return false; }
Gravestone doji
Este é um candlestick Doji com uma longa sombra superior (pavio). Trata-se de um sinal de reversão de baixa que indica uma possível mudança na direção do mercado.
Quando esse candle aparece durante uma tendência de alta, ele pode indicar que a tendência atual está chegando ao fim e que um movimento de baixa pode estar prestes a ocorrer.

Como esse padrão é essencialmente um candlestick Doji com uma sombra superior mais longa que a inferior, no código podemos primeiro verificar se o candle é um Doji e, em seguida, garantir explicitamente que a sombra superior seja, por padrão, pelo menos duas vezes maior que a sombra inferior.
bool CTALib::CDLGRAVESTONEDOJI(double open, double high, double low, double close, double body_torrelance = 3, double shadow_ratio = 2.0) { double body_size = MathAbs(open - close); double upper_shadow = upperShadowCalc(open, close, high); double lower_shadow = lowerShadowCalc(open, close, low); //--- Body is very small (like a Doji) if (CDLDOJI(open, close, body_torrelance)) { //--- Lower shadow is significantly longer than upper shadow if (upper_shadow > lower_shadow * shadow_ratio) return true; } return false; }
Hammer
Este é um candle com corpo pequeno na parte superior e uma longa sombra inferior. Trata-se de um sinal de reversão de alta que, quando aparece durante uma tendência de baixa, sinaliza um possível movimento de alta à frente.

Os padrões de candlestick podem ser abstratos e confusos. Em alguns casos, um mesmo padrão pode ser interpretado de maneiras diferentes, dependendo da forma como é analisado e do que se pretende identificar naquele momento. Um padrão Hammer pode ser confundido com um Dragonfly Doji, pois a principal diferença entre eles está no tamanho do corpo, enquanto outras características são semelhantes.
Esse é um dos problemas que encontramos com frequência ao trabalhar com padrões de candlestick.
Podemos contornar esse problema definindo regras explícitas e valores-limite que podem ser ajustados de acordo com determinado instrumento (símbolo) e outras necessidades específicas.
bool CTALib::CDLHAMMER(double open, double high, double low, double close, double min_body_percentage = 0.2, // To avoid being a doji double lower_shadow_ratio = 2.0, // Lower shadow at least 2x the body double upper_shadow_max_ratio = 0.3) // Upper shadow must be small { double body_size = MathAbs(open - close); double total_range = high - low + DBL_EPSILON; double upper_shadow = upperShadowCalc(open, close, high); double lower_shadow = lowerShadowCalc(open, close, low); double body_percentage = body_size / total_range; return ( body_percentage >= min_body_percentage && lower_shadow >= lower_shadow_ratio * body_size && upper_shadow <= upper_shadow_max_ratio * body_size ); }
Como tanto o Dragonfly Doji quanto o Hammer apresentam uma sombra inferior longa, corpo pequeno e sombra superior curta, foi necessário introduzir o parâmetro min_body_percentage para determinar o tamanho mínimo que o corpo deve ter em relação à amplitude total do candle (High-Low) para que ele seja classificado como Hammer. Ao mesmo tempo, verificamos, por padrão, se a sombra inferior é pelo menos duas vezes maior que a sombra superior.
Inverted Hammer
Este padrão é semelhante ao Hammer, porém apresenta um corpo pequeno na parte inferior, uma longa sombra superior e uma pequena sombra inferior.
Esse padrão é encontrado em tendências de baixa e sinaliza a possibilidade de uma reversão de alta no mercado.

Podemos implementá-lo de forma semelhante ao Hammer, realizando apenas pequenas alterações nas condições relacionadas às sombras.
bool CTALib::CDLINVERTEDHAMMER(double open, double high, double low, double close, double min_body_percentage = 0.2, // Avoid doji double upper_shadow_ratio = 2.0, // Upper shadow must be long double lower_shadow_max_ratio = 0.3) // Lower shadow should be small { double body_size = MathAbs(open - close); double total_range = high - low + DBL_EPSILON; double upper_shadow = upperShadowCalc(open, close, high); double lower_shadow = lowerShadowCalc(open, close, low); double body_percentage = body_size / total_range; return ( body_percentage >= min_body_percentage && upper_shadow >= upper_shadow_ratio * body_size && lower_shadow <= lower_shadow_max_ratio * body_size ); }
Spinning Top
Este é um candlestick com corpo pequeno localizado na região central e sombras longas em ambos os lados.

Quando esse candle aparece, ele indica indecisão no mercado, o que muitas vezes pode significar a continuidade da tendência atual, já que nem compradores nem vendedores apresentam domínio claro.
Como esse candle é semelhante a um candlestick Doji — embora apresente um corpo maior —, precisamos garantir explicitamente que seu corpo não seja tão pequeno quanto o de um Doji e que esteja localizado aproximadamente no centro das sombras longas.
bool CTALib::CDLSPINNINGTOP(double open, double high, double low, double close, double body_percentage_threshold = 0.3, double shadow_ratio = 2.0, double shadow_symmetry_tolerance = 0.3) { double body_size = MathAbs(open - close); double total_range = high - low + DBL_EPSILON; double upper_shadow = upperShadowCalc(open, close, high); double lower_shadow = lowerShadowCalc(open, close, low); double body_percentage = body_size / total_range; //--- Calculate shadow symmetry ratio double shadow_diff = MathAbs(upper_shadow - lower_shadow); double shadow_sum = upper_shadow + lower_shadow + DBL_EPSILON; double symmetry_ratio = shadow_diff / shadow_sum; // Closer to 0 = more balanced return ( body_percentage < body_percentage_threshold && // Body is small compared to candle size upper_shadow > body_size * shadow_ratio && // Both shadows are significantly larger than the body lower_shadow > body_size * shadow_ratio && symmetry_ratio <= shadow_symmetry_tolerance //Shadows are roughly equal (symmetrical) ); }
Bullish Marubozu
O nome "Marubozu" vem de uma palavra japonesa que significa "raspado" ou "sem cabelo", fazendo referência a um candle sem sombras.
Um candle Bullish Marubozu é um candle de alta com sombras superior e inferior muito pequenas ou inexistentes. Trata-se de um forte sinal de alta, indicando impulso comprador.

Podemos adicionar um valor de tolerância em pontos para verificar se os preços de abertura e fechamento estão suficientemente próximos dos preços mínimo e máximo.
bool CTALib::CDLBULLISHMARUBOZU(double open, double high, double low, double close, double tolerance = 2) { return (MathAbs(open - low) <= (tolerance*_Point) && MathAbs(close - high) <= (tolerance*_Point) && close > open); }
Bearish Marubozu
Um candle Bearish Marubozu é um candle de baixa com sombras superior e inferior muito pequenas ou inexistentes. Trata-se de um forte sinal de baixa, indicando impulso vendedor.

Assim como no candlestick Bullish Marubozu, utilizamos um valor de tolerância em pontos para verificar se os preços de abertura e fechamento estão suficientemente próximos dos preços máximo e mínimo.
bool CTALib::CDLBEARISHMARUBOZU(double open, double high, double low, double close, double tolerance = 2) { return (MathAbs(open - high) <= (tolerance*_Point) && MathAbs(close - low) <= (tolerance*_Point) && close < open); }
Até aqui, estamos considerando apenas a detecção dos padrões de candlestick e seus respectivos sinais com base em sua aparência. No entanto, segundo as minhas fontes, a forma adequada de interpretar os sinais de um candlestick também deve considerar a tendência. Por exemplo, para que um Hammer seja interpretado como um sinal de alta, ele deve aparecer durante uma tendência de baixa.
A tendência é uma parte fundamental dessa análise e deve ser considerada caso você queira aprofundar este projeto.
Indicador de Detecção de Padrões de Candlestick
Vamos visualizar os padrões de candlestick utilizando o código empregado para identificá-los. Isso é importante porque esses padrões muitas vezes podem parecer abstratos e confusos. Além disso, como pretendemos utilizar esses dados para fins de Machine Learning, sabemos que a qualidade dos dados é um dos fatores mais importantes. Ao visualizá-los, entendemos melhor como foram calculados no código e qual é o resultado visual no gráfico.
Não hesite em ajustar alguns parâmetros e modificar o código de acordo com suas necessidades.
Primeiro, precisamos garantir que o código que acabamos de desenvolver seja capaz de identificar os mesmos padrões que nós, como seres humanos, conseguimos reconhecer visualmente no mercado.
Esse indicador baseado em candlesticks terá 5 buffers e um plot no gráfico principal.
Nome do arquivo: Candlestick Identifier.mq5
#property indicator_chart_window #property indicator_buffers 5 #property indicator_plots 1 #property indicator_type1 DRAW_COLOR_CANDLES #property indicator_color1 clrDodgerBlue, clrOrange, clrRed #property indicator_style1 STYLE_SOLID #property indicator_width1 1 double OpenBuff[]; double HighBuff[]; double LowBuff[]; double CloseBuff[]; double ColorBuff[]; #include <ta-lib.mqh> //!important for candlestick patterns
Como a biblioteca ta-lib.mqh utiliza uma classe estática, não é necessário instanciar suas classes. Podemos chamar diretamente, dentro da função OnCalculate, as funções responsáveis pela detecção dos padrões de candlestick.
int OnCalculate(const int rates_total, const int prev_calculated, const datetime &time[], const double &open[], const double &high[], const double &low[], const double &close[], const long &tick_volume[], const long &volume[], const int &spread[]) { //--- if (rates_total<1) return rates_total; for(int i = prev_calculated; i < rates_total; i++) { OpenBuff[i] = open[i]; HighBuff[i] = high[i]; LowBuff[i] = low[i]; CloseBuff[i] = close[i]; //--- if (close[i]>open[i]) ColorBuff[i] = 1.0; else ColorBuff[i] = 0.0; //--- double padding = MathAbs(high[i] - low[i]) * 0.2; // 20% padding if (CTALib::CDLDOJI(open[i], close[i])) { TextCreate(string(i)+(string)time[i], time[i]-PeriodSeconds(), high[i]+padding, "Doji", clrBlack, 90.0); ColorBuff[i] = 2.0; } if (CTALib::CDLDRAGONFLYDOJI(open[i], high[i], low[i], close[i])) { TextCreate(string(i)+(string)time[i], time[i]-PeriodSeconds(), high[i]+padding,"DragonFly Doji", clrBlack, 90.0); ColorBuff[i] = 2.0; } if (CTALib::CDLGRAVESTONEDOJI(open[i], high[i], low[i], close[i])) { TextCreate(string(i)+(string)time[i], time[i]-PeriodSeconds(), high[i]+padding,"GraveStone Doji", clrBlack, 90.0); ColorBuff[i] = 2.0; } if (CTALib::CDLHAMMER(open[i], high[i], low[i], close[i])) { TextCreate(string(i)+(string)time[i], time[i]-PeriodSeconds(), high[i]+padding,"Hammer", clrBlack, 90.0); ColorBuff[i] = 2.0; } if (CTALib::CDLINVERTEDHAMMER(open[i], high[i], low[i], close[i])) { TextCreate(string(i)+(string)time[i], time[i]-PeriodSeconds(), high[i]+padding,"Inverted Hammer", clrBlack, 90.0); ColorBuff[i] = 2.0; } if (CTALib::CDLSPINNINGTOP(open[i], high[i], low[i], close[i], 0.3, 2.0)) { TextCreate(string(i)+(string)time[i], time[i]-PeriodSeconds(), high[i]+padding,"Spinning Top", clrBlack, 90.0); ColorBuff[i] = 2.0; } if (CTALib::CDLBULLISHMARUBOZU(open[i], high[i], low[i], close[i], 2)) { TextCreate(string(i)+(string)time[i], time[i]-PeriodSeconds(), high[i]+padding,"Bullish Marubozu", clrBlack, 90.0); ColorBuff[i] = 2.0; } if (CTALib::CDLBEARISHMARUBOZU(open[i], high[i], low[i], close[i], 2)) { TextCreate(string(i)+(string)time[i], time[i]-PeriodSeconds(), high[i]+padding,"Bearish Marubozu", clrBlack, 90.0); ColorBuff[i] = 2.0; } } //--- return value of prev_calculated for next call return(rates_total); }
Por padrão, os candles de alta são exibidos em laranja, enquanto os candles de baixa são exibidos em azul. Qualquer candle identificado como um dos padrões abordados neste artigo será destacado em vermelho e acompanhado por um texto orientado em um ângulo de 90 graus, indicando a qual padrão de candlestick aquele candle pertence.

Como podemos observar na imagem acima, nosso indicador de detecção apresenta um bom desempenho na identificação desses padrões de candlestick. Com isso, podemos ter maior confiança na lógica implementada no código.
Agora, vamos coletar esses padrões e armazená-los em um arquivo CSV utilizando um script. Em seguida, utilizaremos essas informações no treinamento dos modelos de Machine Learning.
Coleta de Padrões de Candlestick para Machine Learning
Como alguns padrões de candlestick não aparecem com frequência no mercado, especialmente em timeframes mais altos, nos quais há menos barras disponíveis no histórico, vamos coletar nossos dados no período de 1º de janeiro de 2005 a 1º de janeiro de 2023.
Esse período de 18 anos deverá fornecer uma quantidade significativa de barras no timeframe diário e, consequentemente, diversos padrões para que nossos modelos de Machine Learning possam analisá-los.
#include <ta-lib.mqh> //Contains CTALib class for candlestick patterns detection #include <MALE5\Pandas\pandas.mqh> //https://www.mql5.com/en/articles/17030 input datetime start_date = D'2005.01.01'; input datetime end_date = D'2023.01.01'; input string symbol = "XAUUSD"; input ENUM_TIMEFRAMES timeframe = PERIOD_D1; //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { //--- vector open, high, low, close; open.CopyRates(symbol, timeframe, COPY_RATES_OPEN, start_date, end_date); high.CopyRates(symbol, timeframe, COPY_RATES_HIGH, start_date, end_date); low.CopyRates(symbol, timeframe, COPY_RATES_LOW, start_date, end_date); close.CopyRates(symbol, timeframe, COPY_RATES_CLOSE, start_date, end_date); CDataFrame df; vector cdl_patterns = {}; cdl_patterns = CTALib::CDLWHITECANDLE(open, close); df.insert("White Candle", cdl_patterns); cdl_patterns = CTALib::CDLBLACKCANDLE(open, close); df.insert("Black Candle", cdl_patterns); cdl_patterns = CTALib::CDLDOJI(open, close); df.insert("Doji Candle", cdl_patterns); cdl_patterns = CTALib::CDLDRAGONFLYDOJI(open, high, low, close); df.insert("Dragonflydoji Candle", cdl_patterns); cdl_patterns = CTALib::CDLGRAVESTONEDOJI(open, high, low, close); df.insert("Gravestonedoji Candle", cdl_patterns); cdl_patterns = CTALib::CDLHAMMER(open, high, low, close); df.insert("Hammer Candle", cdl_patterns); cdl_patterns = CTALib::CDLINVERTEDHAMMER(open, high, low, close); df.insert("Invertedhammer Candle", cdl_patterns); cdl_patterns = CTALib::CDLSPINNINGTOP(open, high, low, close); df.insert("Spinningtop Candle", cdl_patterns); cdl_patterns = CTALib::CDLBULLISHMARUBOZU(open, high, low, close); df.insert("BullishMarubozu Candle", cdl_patterns); cdl_patterns = CTALib::CDLBEARISHMARUBOZU(open, high, low, close); df.insert("BearishMarubozu Candle", cdl_patterns); df.insert("Open", open); df.insert("High", high); df.insert("Low", low); df.insert("Close", close); df.to_csv(StringFormat("CandlestickPatterns.%s.%s.csv",symbol,EnumToString(timeframe)), true); }
Também coletamos os valores OHLC (Open, High, Low e Close — abertura, máxima, mínima e fechamento) para preparar a variável-alvo e mantê-los disponíveis caso sejam necessários posteriormente.
Treinamento de um Modelo de IA para Realizar Previsões com Base em Padrões de Candlestick
Agora que temos um conjunto de dados, vamos carregá-lo em um script Python (Jupyter Notebook).
import pandas as pd symbol = "XAUUSD" df = pd.read_csv(f"/kaggle/input/forex-candlestick-patterns/CandlestickPatterns.{symbol}.PERIOD_D1.csv") df
Outputs
| White Candle | Black Candle | Doji Candle | Dragonflydoji Candle | Gravestonedoji Candle | Hammer Candle | Invertedhammer Candle | Spinningtop Candle | BullishMarubozu Candle | BearishMarubozu Candle | Open | High | Low | Close | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 0.0 | 1.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 438.45 | 438.71 | 426.72 | 429.55 |
| 1 | 0.0 | 1.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 429.52 | 430.18 | 423.71 | 427.51 |
| 2 | 0.0 | 1.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 427.50 | 428.77 | 425.10 | 426.58 |
| 3 | 0.0 | 1.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 426.31 | 427.85 | 420.17 | 421.37 |
| 4 | 0.0 | 1.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 421.39 | 425.48 | 416.57 | 419.02 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
O maior desafio que surge ao trabalhar com esses dados de candlestick é a criação da variável-alvo.
Na maioria dos problemas de classificação relacionados aos mercados financeiros, geralmente definimos a variável-alvo com base no movimento futuro do preço, utilizando um parâmetro que normalmente chamamos de "lookahead".
Esse parâmetro especifica quantas barras (ou etapas de tempo) consideramos à frente nos dados. Por exemplo, se o lookahead for definido como 1, comparamos o preço de fechamento da barra atual com o preço de fechamento da barra seguinte:
Se Close[próxima barra] > Close[barra atual], isso indica um movimento de alta e, portanto, atribuímos à variável-alvo o rótulo 1.
Caso contrário, se essa condição não for atendida, isso indica um movimento de baixa e atribuímos o rótulo 0.
Podemos aplicar a mesma abordagem aqui para garantir que nossas feature sejam utilizadas para prever informações futuras. No entanto, como mostrado na tabela acima, há muitos valores zero nesses dados, pois frequentemente nenhuma barra apresenta um padrão específico detectado. As exceções são os candles White e Black, que não são padrões especiais e, portanto, não os consideramos padrões de candlestick propriamente ditos.
Isso significa que, na maior parte do tempo, forneceremos valores zero aos nossos modelos de Machine Learning e os forçaremos a tentar compreender relações e prever a variável-alvo praticamente sem informações relevantes. Como consequência, os modelos poderão passar a depender excessivamente dos candles White e Black, algo que não queremos.
Podemos contornar esse problema removendo todas as linhas em que os valores de todos os padrões de candlestick sejam zero e treinando o modelo apenas com dados que contenham padrões de candlestick. Essa abordagem também exigiria que evitássemos a mesma situação durante a execução do modelo.
Outra forma de lidar com esse problema seria introduzir um sinal da classe hold, representado por -1, em todas as linhas nas quais todos os padrões de candlestick fossem 0 (false), com exceção das colunas White e Black. No entanto, isso criaria um grave problema de desbalanceamento entre as classes, questão que abordamos no artigo anterior. Ainda assim, mesmo essa abordagem não foi capaz de resolver o problema.
Por enquanto, vamos prosseguir com a preparação da variável-alvo independentemente dessa limitação.
lookahead = 1 new_df = df.copy() new_df["future_close"] = new_df["Close"].shift(-lookahead) new_df.dropna(inplace=True) # Drop NaNs caused by the shift operation signal = [] for i in range(len(new_df)): # Iterate over rows, not columns if new_df["future_close"].iloc[i] > new_df["Close"].iloc[i]: signal.append(1) else: signal.append(0) new_df["Signal"] = signal
Em seguida, organizamos os preditores em um array bidimensional chamado X, removendo as feature que não desejamos utilizar, como os valores OHLC, a coluna que queremos prever (target) e a feature de fechamento future_close, que utilizamos para criar a coluna target. Também atribuímos a coluna target, denominada Signal, ao array y.
X = new_df.drop(columns=[ "Signal", "Open", "High", "Low", "Close", "future_close" ]) y = new_df["Signal"] # Split data into train and test sets X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42, shuffle=False)
Escolhi trabalhar com um modelo CatBoost para esse problema, pois temos várias colunas categóricas que, em teoria, devem funcionar bem com o classificador CatBoost.
from catboost import CatBoostClassifier from sklearn.utils.class_weight import compute_class_weight # Automatically calculate class weights classes = np.unique(y) weights = compute_class_weight(class_weight='balanced', classes=classes, y=y) class_weights = dict(zip(classes, weights)) # Define the base model model = CatBoostClassifier( iterations=1000, learning_rate=0.01, depth=5, loss_function='Logloss', class_weights=class_weights, verbose=100 ) model.fit(X_train, y_train) # Training the classifier
Saídas.
0: learn: 0.6930586 total: 3.64ms remaining: 3.64s 100: learn: 0.6897625 total: 136ms remaining: 1.21s 200: learn: 0.6888030 total: 269ms remaining: 1.07s 300: learn: 0.6883559 total: 401ms remaining: 931ms 400: learn: 0.6881469 total: 532ms remaining: 795ms 500: learn: 0.6879966 total: 661ms remaining: 658ms 600: learn: 0.6879013 total: 789ms remaining: 524ms 700: learn: 0.6878311 total: 916ms remaining: 391ms 800: learn: 0.6877729 total: 1.04s remaining: 260ms 900: learn: 0.6877273 total: 1.17s remaining: 129ms 999: learn: 0.6876900 total: 1.3s remaining: 0us <catboost.core.CatBoostClassifier at 0x798cc6d08dd0>
Vamos avaliar esse modelo utilizando dados que ele ainda não viu anteriormente (a amostra de teste).
y_pred = model.predict(X_test)
print("\nClassification Report:\n", classification_report(y_test, y_pred)) Saídas.
Classification Report: precision recall f1-score support 0 0.49 0.55 0.52 429 1 0.58 0.52 0.55 511 accuracy 0.53 940 macro avg 0.53 0.53 0.53 940 weighted avg 0.54 0.53 0.53 940
Os resultados indicam um desempenho mediano do modelo, com precision de 0,58 para a classe 1 e 0,49 para a classe 0. Embora possamos ter alguma confiança no modelo ao prever a classe 1, para a qual apresenta precision de 58%, não podemos confiar da mesma forma em suas previsões para a classe 0; nesse caso, praticamente seria melhor tentar adivinhar.
A accuracy geral é de 53%, um resultado plausível no contexto de trading. Ainda assim, é superior ao resultado esperado de uma escolha aleatória entre duas possibilidades, que seria de aproximadamente 50%.
Vamos verificar o gráfico de importância das feature para identificar quais delas tiveram maior impacto sobre o modelo.
import matplotlib.pyplot as plt # Get feature importances importances = model.get_feature_importance() feature_names = X_train.columns if hasattr(X_train, 'columns') else [f'feature_{i}' for i in range(X_train.shape[1])] # Create DataFrame for plotting feat_imp_df = pd.DataFrame({ 'Feature': feature_names, 'Importance': importances }).sort_values(by='Importance', ascending=False) # Plot plt.figure(figsize=(7, 3)) plt.barh(feat_imp_df['Feature'], feat_imp_df['Importance']) plt.gca().invert_yaxis() # Highest importance on top plt.title('Feature Importances') plt.xlabel('Importance') plt.ylabel('Feature') plt.tight_layout() plt.show()
Saídas.

O padrão de candlestick Spinning Top foi a feature de maior impacto no modelo, seguido pelo candlestick Doji. Já o candle Bearish Marubozu foi o que apresentou menor impacto.
De acordo com o que li sobre esses padrões de candlestick, alguns deles são utilizados para indicar uma tendência ou sugerir o que poderá acontecer no mercado ao longo de um horizonte de tempo mais amplo. Por exemplo, o aparecimento de um candle Doji no topo de uma tendência de alta pode indicar que uma tendência de baixa está prestes a começar e poderá se prolongar por algum tempo.
Criamos a variável-alvo utilizando um valor de lookahead igual a 1. Portanto, estamos utilizando esses padrões de candlestick para prever apenas uma barra à frente, em vez de várias barras futuras.
Sinta-se à vontade para experimentar diferentes valores de lookahead superiores a 1 e observar o impacto desses padrões de candlestick em períodos mais longos ou em diferentes horizontes de previsão. Nos testes que realizei até agora, constatei que o modelo treinado com lookahead igual a 1 apresentou a maior accuracy.
Por enquanto, manteremos o valor de lookahead igual a 1, pois acredito que podemos lidar com essa questão do horizonte de previsão diretamente em nosso robô de negociação final, utilizando valores de Stop Loss e Take Profit ou encerrando as operações após a passagem de determinado número de barras.
Finalizando a Implementação em um Robô de Negociação
Agora que treinamos um modelo baseado em padrões de candlestick, vamos testá-lo em um ambiente real de negociação para verificar se esses padrões podem ser úteis no contexto da Inteligência Artificial (IA).
Primeiramente, precisamos salvar nosso modelo no formato ONNX, compatível com MQL5 e MetaTrader 5.
model_onnx = convert_sklearn( model, "catboost", [("input", FloatTensorType([None, X_train.shape[1]]))], target_opset={"": 12, "ai.onnx.ml": 2}, ) # And save. with open(f"CatBoost.CDLPatterns.{symbol}.onnx", "wb") as f: f.write(model_onnx.SerializeToString())
Mais informações sobre como salvar esse modelo CatBoost podem ser encontradas aqui.
Nosso Expert Advisor (EA) é relativamente simples.
#include <Trade\Trade.mqh> //The trading module #include <Trade\PositionInfo.mqh> //Position handling module #include <ta-lib.mqh> //For candlestick patterns #include <Catboost.mqh> //Has a class for deploying a catboost model CTrade m_trade; CPositionInfo m_position; CCatboostClassifier catboost; input int magic_number = 21042025; input int slippage = 100; input string symbol_ = "XAUUSD"; input ENUM_TIMEFRAMES timeframe_ = PERIOD_D1; input int lookahead = 1; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { if (!MQLInfoInteger(MQL_TESTER)) if (!ChartSetSymbolPeriod(0, symbol_, timeframe_)) { printf("%s failed to set symbol %s and timeframe %s, Check these values. Err = %d",__FUNCTION__,symbol_,EnumToString(timeframe_),GetLastError()); return INIT_FAILED; } //--- if (!catboost.Init(StringFormat("CatBoost.CDLPatterns.%s.onnx",symbol_), ONNX_COMMON_FOLDER)) //Initialize the catboost model return INIT_FAILED; //--- m_trade.SetExpertMagicNumber(magic_number); m_trade.SetDeviationInPoints(slippage); m_trade.SetMarginMode(); m_trade.SetTypeFillingBySymbol(Symbol()); //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- double open = iOpen(Symbol(), Period(), 1), high = iHigh(Symbol(), Period(), 1), low = iLow(Symbol(), Period(), 1), close = iClose(Symbol(), Period(), 1); vector x = { CTALib::CDLWHITECANDLE(open, close), CTALib::CDLBLACKCANDLE(open, close), CTALib::CDLDOJI(open, close), CTALib::CDLDRAGONFLYDOJI(open, high, low, close), CTALib::CDLGRAVESTONEDOJI(open, high, low, close), CTALib::CDLHAMMER(open, high, low, close), CTALib::CDLINVERTEDHAMMER(open, high, low, close), CTALib::CDLSPINNINGTOP(open, high, low, close), CTALib::CDLBULLISHMARUBOZU(open, high, low, close), CTALib::CDLBEARISHMARUBOZU(open, high, low, close) }; long signal = catboost.predict(x).cls; //Predicted class MqlTick ticks; if (!SymbolInfoTick(Symbol(), ticks)) { printf("Failed to obtain ticks information, Error = %d",GetLastError()); return; } double volume_ = SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_MIN); if (signal == 1) { if (!PosExists(POSITION_TYPE_BUY) && !PosExists(POSITION_TYPE_SELL)) m_trade.Buy(volume_, Symbol(), ticks.ask,0,0); } if (signal == 0) { if (!PosExists(POSITION_TYPE_SELL) && !PosExists(POSITION_TYPE_BUY)) m_trade.Sell(volume_, Symbol(), ticks.bid,0,0); } CloseTradeAfterTime((Timeframe2Minutes(Period())*lookahead)*60); //Close the trade after a certain lookahead and according the the trained timeframe }
Após inicializarmos o modelo CatBoost no formato ONNX, que foi salvo na pasta Common, podemos prosseguir com a lógica do EA.
Dentro da função OnTick, obtemos os valores Open, High, Low e Close da barra anteriormente fechada e os passamos para as funções da CTALib responsáveis pela detecção dos padrões de candlestick. Em seguida, utilizamos os resultados dessas funções para realizar previsões a partir de um vetor denominado x.
Precisamos ter atenção às feature e à ordem em que são fornecidas, pois elas devem seguir exatamente a mesma organização utilizada durante o treinamento do modelo CatBoost final no script Python.
X_train.columns
Em nosso modelo final, tínhamos:
Index(['White Candle', 'Black Candle', 'Doji Candle', 'Dragonflydoji Candle', 'Gravestonedoji Candle', 'Hammer Candle', 'Invertedhammer Candle', 'Spinningtop Candle', 'BullishMarubozu Candle', 'BearishMarubozu Candle'], dtype='object')
Essa mesma ordem foi preservada dentro do Expert Advisor.
No momento, não temos valores definidos para Stop Loss e Take Profit. Por isso, encerramos as operações (posições) abertas depois que transcorre o número de barras correspondente ao valor de lookahead no timeframe utilizado.
Configurações do Strategy Tester.

Resultados.

O que me chama a atenção é a semelhança entre os resultados das operações de compra e de venda. Durante esse período de dois anos, foram abertas 257 operações de venda. Isso representa apenas 2 operações a menos que as 259 operações de compra.
Esse resultado não é adequado e podemos concluir que, embora o modelo considere todos os padrões de candlestick, os candles White e Black acabam exercendo uma influência significativa, pois são os únicos presentes em todas as barras. Além disso, estamos encerrando cada operação após apenas uma barra (lookahead = 1).Esse problema está relacionado à forma como preparamos a variável-alvo e treinamos o modelo com dados que continham valores zero (false) em muitas das feature.
Para garantir que os padrões específicos de candlestick sejam efetivamente considerados, precisamos verificar quando todos os padrões especiais de candlestick forem iguais a 0 (false) — ou seja, quando nenhum deles tiver sido detectado — e impedir a abertura de uma operação nessa situação.
Queremos abrir uma operação somente quando algum padrão específico de candlestick for detectado, além dos candles White e Black.
void OnTick() { //--- double open = iOpen(Symbol(), Period(), 1), high = iHigh(Symbol(), Period(), 1), low = iLow(Symbol(), Period(), 1), close = iClose(Symbol(), Period(), 1); vector x = { CTALib::CDLWHITECANDLE(open, close), CTALib::CDLBLACKCANDLE(open, close), CTALib::CDLDOJI(open, close), CTALib::CDLDRAGONFLYDOJI(open, high, low, close), CTALib::CDLGRAVESTONEDOJI(open, high, low, close), CTALib::CDLHAMMER(open, high, low, close), CTALib::CDLINVERTEDHAMMER(open, high, low, close), CTALib::CDLSPINNINGTOP(open, high, low, close), CTALib::CDLBULLISHMARUBOZU(open, high, low, close), CTALib::CDLBEARISHMARUBOZU(open, high, low, close) }; vector patterns = { CTALib::CDLDOJI(open, close), CTALib::CDLDRAGONFLYDOJI(open, high, low, close), CTALib::CDLGRAVESTONEDOJI(open, high, low, close), CTALib::CDLHAMMER(open, high, low, close), CTALib::CDLINVERTEDHAMMER(open, high, low, close), CTALib::CDLSPINNINGTOP(open, high, low, close), CTALib::CDLBULLISHMARUBOZU(open, high, low, close), CTALib::CDLBEARISHMARUBOZU(open, high, low, close) }; //Store all the special patterns long signal = catboost.predict(x).cls; //Predicted class MqlTick ticks; if (!SymbolInfoTick(Symbol(), ticks)) { printf("Failed to obtain ticks information, Error = %d",GetLastError()); return; } double volume_ = SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_MIN); if (signal == 1 && patterns.Sum()>0) //Check if there are is atleast a special pattern before opening a trade { if (!PosExists(POSITION_TYPE_BUY) && !PosExists(POSITION_TYPE_SELL)) m_trade.Buy(volume_, Symbol(), ticks.ask,0,0); } if (signal == 0 && patterns.Sum()>0) //Check if there are is atleast a special pattern before opening a trade { if (!PosExists(POSITION_TYPE_SELL) && !PosExists(POSITION_TYPE_BUY)) m_trade.Sell(volume_, Symbol(), ticks.bid,0,0); } CloseTradeAfterTime((Timeframe2Minutes(Period())*lookahead)*60); //Close the trade after a certain lookahead and according the the trained timeframe }
Resultados do Strategy Tester.


Agora o resultado parece muito melhor. Foram abertas poucas operações, o que é compatível com a baixa frequência com que esses padrões de candlestick aparecem em timeframes mais altos, considerando que nosso modelo foi treinado no timeframe diário.
O percentual de operações lucrativas foi de 54,55%, valor bastante próximo da accuracy geral de 0,53 (53%) obtida no classification report. Essa proximidade entre os resultados indica que estamos seguindo na direção correta.
Conclusão
Portanto, é possível utilizar padrões de candlestick em conjunto com modelos de Inteligência Artificial (IA) e empregar o resultado final para realizar previsões de mercado. No entanto, diferentemente do uso de indicadores, cálculos matemáticos e outros tipos de dados normalmente empregados na previsão dos mercados, os padrões de candlestick exigem uma série de cuidados e atenção aos mínimos detalhes durante a coleta dos dados e a criação das feature derivadas dos candles observados no mercado. Uma pequena interpretação incorreta de um candle pode levar a um resultado completamente diferente.
Costuma-se dizer que nossos desejos e crenças influenciam a maneira como percebemos e interpretamos as informações. Em outras palavras, vemos aquilo que queremos ver.
Acredito que isso aconteça com frequência ao trabalhar com padrões de candlestick. Se você estiver procurando por um Hammer, um Dragonfly Doji poderá parecer um Hammer, e vice-versa.
Acredito que sejam necessárias muitas tentativas, ajustes e correções durante a preparação de dados baseados em candlesticks para alcançar um desempenho satisfatório ao utilizá-los em Machine Learning.
Atenciosamente.
Fontes e Referências
- O que é um padrão de candle Doji e o que ele indica?
- Candlestick Hammer: o que é e como os investidores o utilizam
- Shooting Star: o que significa na negociação de ações, com um exemplo
- O que é um padrão de Candlestick Spinning Top?
- Marubozu: o que significa, como funciona e por que é utilizado
- The Candlestick Trading Bible
Tabela de Anexos
| Nome do arquivo | Descrição/Uso |
|---|---|
| Experts\CandlestickPatterns AI-EA.mq5 | Um Expert Advisor (EA) que utiliza um modelo CatBoost para realizar previsões com base em padrões de Candlestick. |
| Indicators\Candlestick Identifier.mq5 | Um indicador para exibir padrões de Candlestick no gráfico. |
| Scripts\Candlestick Patterns Collect.mq5 | Um script para coletar padrões de Candlestick e armazenar essas informações em um arquivo CSV. |
| Include\Catboost.mqh | Uma biblioteca que contém classes para carregar, inicializar e utilizar o classificador CatBoost na realização de previsões de mercado. |
| Include\pandas.mqh | Módulo Pandas semelhante ao Python para armazenamento e manipulação de dados. |
| Include\ta-lib.mqh | Biblioteca de análise técnica que contém uma classe para detectar padrões de Candlestick. |
| Common\Files\*.csv | Arquivos CSV contendo dados de Candlestick para utilização em Machine Learning. |
| Common\Files\*.onnx | Modelos de Machine Learning no formato ONNX. |
| CandlestickMarket Prediction.ipynb | Um script Python (Jupyter Notebook) para treinamento do modelo CatBoost. |
Traduzido do Inglês pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/en/articles/17832
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.
Automatizando Estratégias de Trading em MQL5 (Parte 16): Rompimento da Faixa da Meia-Noite com Break of Structure (BoS) e Price Action
Automatizando Estratégias de Trading em MQL5 (Parte 15): Padrão Harmônico Cypher Baseado em Price Action com Visualização
Desenvolvimento do Kit de Ferramentas de Análise de Price Action (Parte 21): Detector de Inversão da Estrutura de Mercado
Técnicas do MQL5 Wizard que você deve conhecer (Parte 60): Aprendizado por Inferência (Wasserstein-VAE) com Padrões da Média Móvel e do Oscilador Estocástico
- 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
Já expliquei isso no artigo.
No momento, estamos considerando apenas a detecção de padrões de velas e seus sinais com base na aparência deles, mas a maneira correta de extrair os sinais de uma vela, de acordo com minhas fontes, deve incluir a detecção de tendência; por exemplo, para que um martelo seja considerado um sinal de alta, ele precisa aparecer em uma tendência de baixa.
A tendência é uma parte crucial da equação que você talvez queira levar em conta se quiser levar este projeto adiante.
A ação do preço é importante, não há como negar isso.
Já expliquei isso no artigo.
No momento, estamos considerando apenas a detecção de padrões de velas e seus sinais com base na aparência deles, mas, segundo minhas fontes, a maneira correta de extrair os sinais de uma vela deve incluir a detecção de tendência; por exemplo, para que um martelo seja considerado um sinal de alta, ele precisa aparecer em uma tendência de baixa.
A tendência é uma parte crucial da equação que você talvez queira levar em conta se quiser levar este projeto adiante.
A ação do preço é importante, não há como negar isso.
Sua citação não está relacionada à ideia principal do meu argumento.
Oi, projeto de aprendizado de máquina seguirá essa linha.
Desejo a você um Feliz Natal...