Quando estamos construindo ferramentas e robôs para automação de investimentos é muito comum a gente precisar automatizar principalmente a lógica de reconhecimento dos pontos de entrada e saída. Isso porque toda estratégia, para que seja automatizada, precisa de algoritmos que substituam o “olho humano”, ou seja, programas de computador que analisem matematicamente os dados dos gráficos tirem as mesmas conclusões, senão melhores, que um trader treinado conseguiria.
Dentre os padrões gráficos mais úteis aos traders nós temos o Suporte e a Resistência, onde diz-se que o suporte de um ativo é o preço baixo mais comum que ele costuma chegar, enquanto que a resistência é o preço alto mais comum, ambos atualmente. Esses valores são definidos traçando linhas imaginárias no gráfico, na altura do preço comum, e não são exatos, mas aproximados baseado na quantidade de ocorrências passadas.
Também não há qualquer garantia de que o suporte e resistência atuais vão permanecer por muito tempo, até porque o rompimento de um suporte ou resistência costumam também serem indicativos a serem considerados nas estratégias. Não é incomum juntar estes padrões gráficos com indicadores técnicos para compor análises mais elaboradas.
O objetivo deste tutorial é propor um algoritmo de detecção de suporte e resistência em gráficos de velas, usando como base as velas fornecidas pela corretora de criptomoedas Binance, a maior do mundo no segmento. Ele pode facilmente ser adaptado a outras fontes de dados e mercados, conforme o sue conhecimento e a disponibilidade de tais informações. Usaremos a linguagem JavaScript com o runtime Node.js, mas dada as características básicas de tal algoritmo você pode facilmente adaptá-lo para outras tecnologias também.
Se preferir, ao invés de ler este post você pode assistir ao vídeo abaixo, com o mesmo conteúdo.
#1 – Pegando os Dados
O primeiro passo é você obter os dados das velas do ativo que será analisado pelo algoritmo. Enquanto que nós humanos olhamos o gráfico, para o algoritmo basta os dados crus mesmo que ele fará os cálculos matemáticos necessários em cima deles e nos trará os valores de suporte e resistência do ativo ao final. Ou assim esperamos.
Para pegar os dados do mercado de criptomoedas eu costumo utilizar as APIs da Binance, sendo que a API de velas pode ser facilmente acessada de maneira pública usando a URL https://api.binance.com/api/v3/klines?symbol=S&interval=I&limit=L onde as variáveis são:
- S: o par de moedas. Ex: BTCUSDT
- I: o intervalo gráfico. Ex: 1h
- L: a quantidade de velas que deseja. Ex: 120
Se você multiplicar a quantidade de velas pelo tempo gráfico você terá o tamanho total da amostra, sendo que não existe uma regra exata de quantas velas precisam ser analisadas ou em qual tempo gráfico. Do ponto de vista de dados em si, quanto mais melhor, mas quem faz trade sabe que não é bem assim que o mercado funciona.
Para escrever nosso algoritmo vamos criar um projeto Node.js, primeiro através da criação de uma pasta, depois a inicialização do projeto e por fim a instalação do pacote Axios, tudo como mostrado nos comandos abaixo.
1 2 3 4 5 6 |
mkdir support-resistance cd support-resistance npm init -y npm install axios |
Agora vamos criar um arquivo index.js e dentro dele coloque a chamada à API da Binance, sendo que aqui estou configurando tudo para BTCUSDT.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
//index.js const axios = require("axios"); const SYMBOL = "BTCUSDT"; const INTERVAL = "1h"; const LIMIT = 120; const TICK_SIZE = 0.01; async function start() { const response = await axios.get(`https://api.binance.com/api/v3/klines?symbol=${SYMBOL}&interval=${INTERVAL}&limit=${LIMIT}`);//5 dias console.log(response.data); } start(); |
Repare que eu comecei a implementação definindo as configurações do nosso algoritmo com o par de moedas, o intervalo gráfico, a quantidade de velas e o tick size, que basicamente é a menor unidade de preço que pode ser negociada daquele ativo na corretora Binance. Como estamos pareados com o dólar neste caso, o tick size é 1 centavo e esta informação é importante.
Depois criamos uma função start que vai ser invocada ao final do módulo para iniciar a aplicação. Nesta função nós usamos o Axios para fazer uma requisição HTTP GET no endereço que informei antes e jogamos o resultado dela no console. O resultado será um array de arrays, onde cada array interno possui em cada posição uma informação diferente da vela (no caso da Binance, outras APIs podem retornar os dados de maneiras diferentes).
Para tratarmos esta informação de uma maneira um pouco mais fácil vamos criar uma classe que transforma este array mais interno em um objeto de vela, como abaixo, em um novo arquivo Kline.js.
1 2 3 4 5 6 7 8 9 10 11 |
module.exports = class Kline { constructor(arr){ this.time = arr[0]; this.open = parseFloat(arr[1]); this.high = parseFloat(arr[2]); this.low = parseFloat(arr[3]); this.close = parseFloat(arr[4]); } } |
Agora com esta classe podemos voltar ao index.js, importar ela e utilizar no carregamento dos nossos dados.
1 2 3 4 5 6 7 8 9 10 |
const Kline = require("./Kline"); async function start() { const response = await axios.get(`https://api.binance.com/api/v3/klines?symbol=${SYMBOL}&interval=${INTERVAL}&limit=${LIMIT}`);//5 dias console.log(response.data.map(k => new Kline(k))); } start(); |
E com isso, se tudo deu certo, agora você tem os dados que precisaremos e no formato que precisaremos.
#2 – Processando os Dados
Neste momento nós temos um array de velas/klines, certo? O próximo passo é começarmos a extrair as informações necessárias para entender os padrões de suporte e resistência no gráfico. Precisamos de três informações referenciais para começar, que quando olhamos o gráfico visualmente, são fáceis de notar: o valor máximo, o valor mínimo e a média do período.
Para facilitar esses processamentos e isolar bem as responsabilidades, vamos criar outra classe que vai receber o gráfico como um todo. Crie um arquivo Candlechart.js com o seguinte conteúdo:
1 2 3 4 5 6 7 8 9 |
const Kline = require("./Kline"); module.exports = class Candlechart { constructor(arr, tickSize) { this.klines = arr.map(k => new Kline(k)); this.TICK_SIZE = tickSize; } } |
Aqui nós carregamos a mesma classe Kline de antes e criamos uma nova classe Candlechart que é inicializada com o array de velas e o tick size. Essas duas informações devem vir do index.js, que deve ser ajustado como abaixo.
1 2 3 4 5 6 7 8 9 10 11 |
const Candlechart = require("./Candlechart"); async function start() { const response = await axios.get(`https://api.binance.com/api/v3/klines?symbol=${SYMBOL}&interval=${INTERVAL}&limit=${LIMIT}`);//5 dias const candlechart = new Candlechart(response.data, TICK_SIZE); console.log(candlechart); } start(); |
Agora vamos trabalhar dentro da classe Candlechart, que deverá ter toda a lógica de processamento dos dados das velas. A primeira dessas lógicas é descobrir qual o valor mais alto que a moeda atingiu no período, o que faremos com a função abaixo.
1 2 3 4 5 6 |
highestPrice() { const orderedKlines = this.klines.sort((a, b) => a.high - b.high); return orderedKlines[orderedKlines.length - 1].high; } |
Nessa função nós ordenamos todas as velas baseado no valor da máxima de cada uma (high) e retornamos o high da última vela após a ordenação, o que nos dará a máxima global no período analisado. Para testar basta chamar esta função no index.js e mandar imprimir o resultado.
A próxima função faz o oposto: pega a mínima global.
1 2 3 4 5 6 |
lowestPrice() { const orderedKlines = this.klines.sort((a, b) => a.low - b.low); return orderedKlines[0].low; } |
Ela dispensa explicações adicionais, já que é praticamente a mesma coisa da anterior. Teste ajustando o index.js de acordo.
E por fim, se eu tenho a máxima e a mínima no período, então eu posso calcular a média simples, com a função abaixo.
1 2 3 4 5 6 7 8 9 10 11 |
getMedium(support, resistance) { if (support === undefined) support = this.lowestPrice(); if (resistance === undefined) resistance = this.highestPrice(); return ((resistance - support) / 2) + support; } |
Repare que eu verifico se os parâmetros foram passados, caso contrário, eu chamo as funções que o retornam. O cálculo em si é bem direto: dados os suporte e resistências (mínima e máxima) passados por parâmetro, a gente calcula o valor médio entre eles, de maneira simples. Ex: se as extremidades estão entre 18.000 e 20.000, o valor médio estará em 19.000. Esta informação é importante depois na detecção do suporte e da resistência.
Para testar, abaixo eu incluí um exemplo de código a ser colocado no index.js.
1 2 3 4 5 6 7 8 9 10 11 12 |
const response = await axios.get(`https://api.binance.com/api/v3/klines?symbol=${SYMBOL}&interval=${INTERVAL}&limit=${LIMIT}`);//5 dias const candlechart = new Candlechart(response.data, TICK_SIZE); let atl = candlechart.lowestPrice(); let ath = candlechart.highestPrice(); let medium = candlechart.getMedium(atl, ath); console.log("ATH: " + ath); console.log("ATL: " + atl); console.log("Medium: " + medium); |
E com isso aprendemos a processar os dados das velas e obter algumas informações importantes. Recomendo além de executar este algoritmo, verificar visualmente no gráfico se as informações fornecidas por ele estão de acordo com o esperado.
#3 – Mapeando a Oscilação de Preço
A análise mais simples que podemos fazer para detectar um suporte (vamos começar pelo suporte) pode ser ilustrada pelo passo a passo abaixo:
- pegamos uma vela, ela está acima ou abaixo do preço médio?
- se ela estiver abaixo, é o que estamos buscando (suporte), caso contrário, descartamos ela e vamos para a próxima vela (passo 1);
- analisando a vela recebida, nós mapeamos a variação de preços no período da vela, tick a tick (1 tick = 1 centavo, no exemplo com dólar);
- com todas as variações de preço (do low até o high da vela), nós agrupamos esses preços em uma estrutura de dados que irá nos ajudar a dizer o preço mais comum;
- após fazer esse processamento com todas as velas, o preço mais comum depois do agrupamento é eleito o preço de suporte;
Os passos 1 e 2 são os mais fáceis, para fazer eles vamos criar uma função findSupport que faz o filtro inicial.
1 2 3 4 5 6 |
findSupport(medium) { const candles = this.klines.filter(k => k.low < medium); console.log(candles); } |
Agora para o passo 3 do nosso algoritmo nós temos de mapear todas as oscilações de preço dentro de cada vela. Ou seja, se uma vela tem um mínima de 1 dólar e uma máxima de 2 dólares, queremos mapear o 1, o 1.01, o 1.02…até chegar no 2. Assim, quando olharmos todas as oscilações, será fácil perceber as mais comuns e a mais comum de todas será eleita o nosso suporte.
Para pegar todas oscilações de uma vela eu vou propor uma função específica para isso.
1 2 3 4 5 6 |
getTicks(kline) { const priceOsc = kline.high - kline.low; return priceOsc * (1 / this.TICK_SIZE); } |
Aqui recebemos a vela por parâmetro, pegamos a diferença entre a mínima e máxima e calculamos todos os ticks percorridos da mínima até a máxima. No caso do dólar, é de centavo em centavo, mas em outras moedas pode variar, por isso o uso do TICK_SIZE que é inicializado na construção do Candlechart.
Com essa getTicks pronta, podemos criar uma função que recebe todas as oscilações da vela e joga elas em um objeto JS que funcionará como agrupador global, sendo recebido por parâmetro a fim de usar sempre o mesmo em todas chamadas. Se o novo tick analisado já estava no objeto agrupador (grouped), a gente incrementa mais uma ocorrência, caso contrário, cria a primeira.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
getGroupedTicks(grouped, kline) { const ticks = this.getTicks(kline); for (let i = 0; i < ticks; i++) { const tick = kline.low + (this.TICK_SIZE * i); if (grouped[tick]) grouped[tick]++; else grouped[tick] = 1; } return grouped; } |
Ao final de uma execução desta função nós teremos mapeado todas as oscilações de preço de uma vela. Agora basta chamarmos esta função para cada uma das velas e teremos os passos 3 e 4 do nosso algoritmo.
1 2 3 4 5 6 7 8 |
findSupport(medium) { const candles = this.klines.filter(k => k.low < medium); let grouped = {}; candles.map(kline => grouped = this.getGroupedTicks(grouped, kline)); console.log(grouped); } |
Repare como declaro o grouped fora do laço para que a gente sempre passe ele por parâmetro a cada iteração, bem como mantemos ele atualizado com o retorno das execução do getGroupedTicks. Nosso próximo passo agora é analisar o preço mais comum nessa faixa, que dará o nosso suporte.
#4 – Detectando Suporte e Resistência
Agora que temos o objeto grouped com todas as oscilações agrupadas, podemos criar mais uma função que servirá para pegar o preço mais comum do agrupador.
1 2 3 4 5 6 7 8 9 |
getTrendTick(grouped, total) { let tickArr = Object.keys(grouped).map(k => { return { tick: k, count: grouped[k] } }); tickArr = tickArr.sort((a, b) => a.count - b.count); return {...tickArr[tickArr.length - 1], total }; } |
O primeiro passo é transformar o objeto agrupador em um array de agrupamentos, onde em cada posição eu terei o preço (tick) e a quantidade de velas que tocaram naquele preço. Depois disso ordenamos de maneira crescente baseado na quantidade de ocorrências e pegamos o preço mais popular como sendo o nosso trend tick.
Com isso podemos finalizar a nossa função que detecta o suporte.
1 2 3 4 5 6 7 8 |
findSupport(medium) { const candles = this.klines.filter(k => k.low < medium); let grouped = {}; candles.map(kline => grouped = this.getGroupedTicks(grouped, kline)); return this.getTrendTick(grouped, candles.length); } |
E de maneira análoga, fica muito simples de ajustar a função acima em uma variação dela, que detecta a resistência.
1 2 3 4 5 6 7 8 9 |
findResistance(medium) { const candles = this.klines.filter(k => k.high > medium); let grouped = {}; candles.map(kline => grouped = this.getGroupedTicks(grouped, kline)); return this.getTrendTick(grouped, candles.length); } |
Para testar, basta ajustar o seu index.js de acordo.
1 2 3 4 5 6 7 |
const support = candlechart.findSupport(medium); console.log("Support: " + JSON.stringify(support)); const resistance = candlechart.findResistance(medium); console.log("Resistance: " + JSON.stringify(resistance)); |
O resultado será impresso no console, em formato de objetos, como abaixo.
Agora se você pegar estes valores e plotar linhas no gráfico, onde o algoritmo sinalizou suporte e resistência, você terá algo como a imagem abaixo. Repare como de fato o algoritmo parece ter detectado um preço que realmente pode representar um suporte (linha de baixo) e uma resistência (linha de cima).
Claro que uma olhar mais treinado pode pensar em maneiras de otimizar essa detecção, mas essas otimizações deixa para você fazer.
Caso queira aprender a plotar informações em gráficos, recomendo este tutorial onde ensino como criar gráficos plotando essas linhas de suporte e resistência. Caso queira usar estas informações para disparar compras e vendas, recomendo este outro tutorial de bot cripto.
Até a próxima!
Olá, tudo bem?
O que você achou deste conteúdo? Conte nos comentários.