Ir para o conteúdo principal

Importando dados públicos de cotações das empresas da Bovespa

highlighted:
beforeContent:

Muita gente não sabe mas a Bovespa disponibiliza os dados da cotação de empresas do dia anterior com um histórico bem grande. Já faz mais de 1 ano que criei alguns serviços importando e realizando calculos desses dados para o projeto Monitor do Mercado que usa muitos tipos de dados diferentes incluíndo os dados da Bovespa e neste post compartilho como importar os dados públicos de cotação.

Se você é iniciante em desenvolvimento ou tem dúvidas de como baixar dados de páginas e locais na internet assista o meu video sobre o assunto: https://youtu.be/Znpbz6RzLFI .

Os dados são disponibilizados em um arquivo .txt compactado com 1 cotação em cada linha e dados limitados por quantidade de caracteres. Dados da Bovespa com menos de 1 dia de atraso são disponibilizados por empresas parceiras que cobram um valor pelo acesso com uma API mais amigável que a API que vamos usar abaixo.

A a página sobre esses dados e documentação relácionada na Bovespa é: http://www.bmfbovespa.com.br/pt_br/servicos/market-data/historico/mercado-a-vista/cotacoes-historicas/

O padrão das URLs de acesso é http://bvmf.bmfbovespa.com.br/InstDados/SerHist/COTAHIST_D04042019.ZIP e o valor 04042019 na url indica a data dos dados no formato DDMMYYYY .

Para acesso aos dados é necessário baixar o arquivo .zip, descompacta-lo, abrir o arquivo .txt e formatar as informações em cada linha.

Nos exemplos de código abaixo estou usando Node.js e Javascript com  os módulos npm extract-zip, moment e request. A mesma lógica é replicável em qualquer linguagem.

Então vamos ao passo à passo para acessar os dados:

1: Para começar precisamos baixar o arquivo do dia selecionado.
1.1: Vamos usar a data 04/04/2019 como exemplo, o formato final da data fica 04042019 e a url final: http://bvmf.bmfbovespa.com.br/InstDados/SerHist/COTAHIST_D04042019.ZIP
1.2: Para começar defini 2 funções, 1 para começar todo o processo e a segunda para baixar o arquivo compactado:

 /**
 * Função para retornar os dados de cotação de 1 dia
 *
 * @param  {Object} date  moment.js date
 * @param  {Function} done  callback
 */
getDailyData(date, done) {
  let day = date.format('DDMMYYYY');
  let zipName = 'COTAHIST_D'+day+'.ZIP';
  let name = 'COTAHIST_D'+day+'.TXT';
// url final do arquivo:
  let externalFileName = 'http://bvmf.bmfbovespa.com.br/InstDados/SerHist/'+zipName;
// destino do arquivo dentro do servidor ou computador local:
  let zipDest = path.join(this.tmpFolder, zipName);
  let contentsDest = path.join(this.tmpFolder, 'COTAHIST_D'+day);
   let contentFile = path.join(this.tmpFolder, 'COTAHIST_D'+day, name);
// funções de download, extração e interpretação dos dados:
  this.downloadFile(externalFileName, zipDest, (err)=> {
    if (err) return done(err);
    this.extractFileContents(zipDest, contentsDest, (err)=> {
      if (err) return done(err);
      this.parseFileData(contentFile, done);
    });
  });
},
/**
* Realiza o download de 1 arquivo .zip
*/
downloadFile(url, dest, done) {
  const r = request({ uri: url });
  r.on('response', (resp)=> {
    if (resp.statusCode == '404') {
      return done('List not avaible for selected date');
    } else if (resp.statusCode != '200') {
      return done(JSON.stringify(resp));
    }
    r.pipe(fs.createWriteStream(dest))
    .on('close', ()=> {
      done();
    });
  });
},
// ...

2: Extrair os conteúdos de um arquivo .zip é muito simples com o módulo extract-zip:

 extractFileContents(source, target, cb) {   
// A partir da versão 2 o módulo extract zip deixou de suportar callbacks (breacking changes!)
extract(source, { dir: target })
.then((r)=> {
cb(null, r);
})
.catch(cb);
},

3: E com acesso ao arquivo .txt, precisamos decifrar qual é o valor de cada dado de cada cotação.
3.1: Para decifrar os dados de cada linha escrevi a função parseLine que recebe 1 linha de texto retirada do arquivo .txt e converte para um objeto com os vários dados disponíveis.
3.2: O código abaixo também mostra o padrão dos dados no arquivo .txt, por exemplo o tipo de registro (TIPREG) está nos 2 primeiros digitos da linha.

 parseLine(line) {
  let historicRow = {};
  // TIPREG - TIPO DE REGISTRO
  historicRow.TIPREG = line.substring(0, 2);
  // DATA DO PREGÃO
  historicRow.date = line.substring(2, 10);
  // CODBDI - CÓDIGO BDI
  historicRow.CODBDI = line.substring(10, 12);
  // CODNEG - CÓDIGO DE NEGOCIAÇÃO DO PAPEL
  historicRow.CODNEG = (line.substring(12, 24)).trim();
  // symbol:
  historicRow.symbol = ('BVMF:'+historicRow.CODNEG).trim();
  // date/value identifier:
  historicRow.identifier = historicRow.symbol+'::'+historicRow.date;
  historicRow.date = moment(historicRow.date, 'YYYYMMDD').format();
  // TPMERC - TIPO DE MERCADO
  historicRow.TPMERC = line.substring(24, 27);
  // NOMRES - NOME RESUMIDO DA EMPRESA EMISSORA DO PAPEL
  historicRow.NOMRES = (line.substring(27, 39)).trim();
  // ESPECI - ESPECIFICAÇÃO DO PAPEL
  historicRow.ESPECI = line.substring(39, 49);
  // PRAZOT - PRAZO EM DIAS DO MERCADO A TERMO
  historicRow.PRAZOT = line.substring(49, 52);
  // MODREF - MOEDA DE REFERÊNCIA
  historicRow.MODREF = line.substring(52, 56);
  // PREABE - PREÇO DE ABERTURA DO PAPEL- MERCADO NO PREGÃO
  historicRow.PREABE = addDot( line.substring(56, 69) );
  // PREMAX - PREÇO MÁXIMO DO PAPEL- MERCADO NO PREGÃO
  historicRow.PREMAX = addDot( line.substring(69, 82) );
  // PREMIN - PREÇO MÍNIMO DO PAPEL- MERCADO NO PREGÃO
  historicRow.PREMIN = addDot( line.substring(82, 95) );
  // PREMED - PREÇO MÉDIO DO PAPEL- MERCADO NO PREGÃO
  historicRow.PREMED = addDot( line.substring(95, 108) );
  // PREULT - PREÇO DO ÚLTIMO NEGÓCIO DO PAPEL-MERCADO NO PREGÃO
  historicRow.PREULT = addDot( line.substring(108, 121) );
  // PREOFC - PREÇO DA MELHOR OFERTA DE COMPRA DO PAPEL- MERCADO
  historicRow.PREOFC = addDot( line.substring(121, 134) );
  // PREOFV - PREÇO DA MELHOR OFERTA DE VENDA DO PAPEL- MERCADO
  historicRow.PREOFV = addDot( line.substring(134, 147) );
  // TOTNEG - NEG. - NÚMERO DE NEGÓCIOS EFETUADOS COM O PAPEL- MERCADO NO PREGÃO
  historicRow.TOTNEG = line.substring(147, 152);
  // QUATOT - QUANTIDADE TOTAL DE TÍTULOS NEGOCIADOS NESTE PAPEL- MERCADO
  historicRow.QUATOT = line.substring(147, 170);
  // VOLTOT - VOLUME TOTAL DE TÍTULOS NEGOCIADOS NESTE PAPEL- MERCADO
  historicRow.VOLTOT = addDot( line.substring(170, 188) );
  // PREEXE - PREÇO DE EXERCÍCIO PARA O MERCADO DE OPÇÕES OU VALOR DO CONTRATO PARA O MERCADO DE TERMO SECUNDÁRIO
  historicRow.PREEXE = addDot( line.substring(188, 201) );
  // INDOPC - INDICADOR DE CORREÇÃO DE PREÇOS DE EXERCÍCIOS OU VALORES DE CONTRATO PARA OS MERCADOS DE OPÇÕES OU TERMO SECUNDÁRIO
  historicRow.INDOPC = line.substring(201, 202);
  // DATVEN - DATA DO VENCIMENTO PARA OS MERCADOS DE OPÇÕES OU TERMO SECUNDÁRIO
  historicRow.DATVEN = line.substring(202, 210);
  // FATCOT - FATOR DE COTAÇÃO DO PAPEL
  historicRow.FATCOT = line.substring(210, 217);
  // PTOEXE - PREÇO DE EXERCÍCIO EM PONTOS PARA OPÇÕES REFERENCIADAS EM DÓLAR OU VALOR DE CONTRATO EM PONTOS PARA TERMO SECUNDÁRIO
  historicRow.PTOEXE = addDot( line.substring(217, 230), 6);
  // CODISI - CÓDIGO DO PAPEL NO SISTEMA ISIN OU CÓDIGO INTERNO DO PAPEL
  historicRow.CODISI = line.substring(230, 242);
  // DISMES - NÚMERO DE DISTRIBUIÇÃO DO PAPEL
  historicRow.DISMES = line.substring(242, 245);
  return historicRow;
 }

Código final:

https://gist.github.com/albertosouza/82d635957ea6d12de5d4056a8d39e083

/**
* Script simples para importação de dados de cotação da Bovespa
*
* Requisitos:
* - Node.js instalado
* - instalar as dependencias: npm install extract-zip moment request
*
* Exemplo de uso: node script_simples_importaca_cota_hist_bovespa.js 05022019
*
* Post relacionado: https://albertosouza.net/artigos/22-importando-dados-bovespa
*/ const fs = require('fs'), path = require('path'), extract = require('extract-zip'), moment = require('moment'), request = require('request'); const bovespaHD = { tmpFolder: path.resolve(process.cwd()), /** * Função para retornar os dados de cotação de 1 dia * * @param {Object} date moment.js date * @param {Function} done callback */ getDailyData(date, done) { let day = date.format('DDMMYYYY'); let zipName = 'COTAHIST_D'+day+'.ZIP'; let name = 'COTAHIST_D'+day+'.TXT'; let externalFileName = 'http://bvmf.bmfbovespa.com.br/InstDados/SerHist/'+zipName; let zipDest = path.join(this.tmpFolder, zipName); let contentsDest = path.join(this.tmpFolder, 'COTAHIST_D'+day); let contentFile = path.join(this.tmpFolder, 'COTAHIST_D'+day, name); this.downloadFile(externalFileName, zipDest, (err)=> { if (err) return done(err); this.extractFileContents(zipDest, contentsDest, (err)=> { if (err) return done(err); this.parseFileData(contentFile, done); }); }); }, downloadFile(url, dest, done) { const r = request({ uri: url }); r.on('response', (resp)=> { if (resp.statusCode == '404') { return done('List not avaible for selected date'); } else if (resp.statusCode != '200') { return done(JSON.stringify(resp)); } r.pipe(fs.createWriteStream(dest)) .on('close', ()=> { done(); }); }); },  extractFileContents(source, target, cb) {
extract(source, { dir: target })
.then((r)=> {
cb(null, r);
})
.catch(cb);
}, parseFileData(file, done) { fs.readFile(file, 'ascii', (err, contents)=> { if (err) return done(err); let lines = contents.split('\r\n'); const parsedData = []; let length = lines.length-2; // ignore last 2 lines for (let i = 1; i < length; i++) { let line = lines[i]; if (line.length === 245) { parsedData.push(this.parseLine(line)); } else { console.log('Unknow line size>', i, line.length, line); } } done(null, parsedData); }); }, parseLine(line) { let historicRow = {}; // TIPREG - TIPO DE REGISTRO historicRow.TIPREG = line.substring(0, 2); // DATA DO PREGÃO historicRow.date = line.substring(2, 10); // CODBDI - CÓDIGO BDI historicRow.CODBDI = line.substring(10, 12); // CODNEG - CÓDIGO DE NEGOCIAÇÃO DO PAPEL historicRow.CODNEG = (line.substring(12, 24)).trim(); // symbol: historicRow.symbol = ('BVMF:'+historicRow.CODNEG).trim(); // date/value identifier: historicRow.identifier = historicRow.symbol+'::'+historicRow.date; historicRow.date = moment(historicRow.date, 'YYYYMMDD').format(); // TPMERC - TIPO DE MERCADO historicRow.TPMERC = line.substring(24, 27); // NOMRES - NOME RESUMIDO DA EMPRESA EMISSORA DO PAPEL historicRow.NOMRES = (line.substring(27, 39)).trim(); // ESPECI - ESPECIFICAÇÃO DO PAPEL historicRow.ESPECI = line.substring(39, 49); // PRAZOT - PRAZO EM DIAS DO MERCADO A TERMO historicRow.PRAZOT = line.substring(49, 52); // MODREF - MOEDA DE REFERÊNCIA historicRow.MODREF = line.substring(52, 56); // PREABE - PREÇO DE ABERTURA DO PAPEL- MERCADO NO PREGÃO historicRow.PREABE = addDot( line.substring(56, 69) ); // PREMAX - PREÇO MÁXIMO DO PAPEL- MERCADO NO PREGÃO historicRow.PREMAX = addDot( line.substring(69, 82) ); // PREMIN - PREÇO MÍNIMO DO PAPEL- MERCADO NO PREGÃO historicRow.PREMIN = addDot( line.substring(82, 95) ); // PREMED - PREÇO MÉDIO DO PAPEL- MERCADO NO PREGÃO historicRow.PREMED = addDot( line.substring(95, 108) ); // PREULT - PREÇO DO ÚLTIMO NEGÓCIO DO PAPEL-MERCADO NO PREGÃO historicRow.PREULT = addDot( line.substring(108, 121) ); // PREOFC - PREÇO DA MELHOR OFERTA DE COMPRA DO PAPEL- MERCADO historicRow.PREOFC = addDot( line.substring(121, 134) ); // PREOFV - PREÇO DA MELHOR OFERTA DE VENDA DO PAPEL- MERCADO historicRow.PREOFV = addDot( line.substring(134, 147) ); // TOTNEG - NEG. - NÚMERO DE NEGÓCIOS EFETUADOS COM O PAPEL- MERCADO NO PREGÃO historicRow.TOTNEG = line.substring(147, 152); // QUATOT - QUANTIDADE TOTAL DE TÍTULOS NEGOCIADOS NESTE PAPEL- MERCADO historicRow.QUATOT = line.substring(147, 170); // VOLTOT - VOLUME TOTAL DE TÍTULOS NEGOCIADOS NESTE PAPEL- MERCADO historicRow.VOLTOT = addDot( line.substring(170, 188) ); // PREEXE - PREÇO DE EXERCÍCIO PARA O MERCADO DE OPÇÕES OU VALOR DO CONTRATO PARA O MERCADO DE TERMO SECUNDÁRIO historicRow.PREEXE = addDot( line.substring(188, 201) ); // INDOPC - INDICADOR DE CORREÇÃO DE PREÇOS DE EXERCÍCIOS OU VALORES DE CONTRATO PARA OS MERCADOS DE OPÇÕES OU TERMO SECUNDÁRIO historicRow.INDOPC = line.substring(201, 202); // DATVEN - DATA DO VENCIMENTO PARA OS MERCADOS DE OPÇÕES OU TERMO SECUNDÁRIO historicRow.DATVEN = line.substring(202, 210); // FATCOT - FATOR DE COTAÇÃO DO PAPEL historicRow.FATCOT = line.substring(210, 217); // PTOEXE - PREÇO DE EXERCÍCIO EM PONTOS PARA OPÇÕES REFERENCIADAS EM DÓLAR OU VALOR DE CONTRATO EM PONTOS PARA TERMO SECUNDÁRIO historicRow.PTOEXE = addDot( line.substring(217, 230), 6); // CODISI - CÓDIGO DO PAPEL NO SISTEMA ISIN OU CÓDIGO INTERNO DO PAPEL historicRow.CODISI = line.substring(230, 242); // DISMES - NÚMERO DE DISTRIBUIÇÃO DO PAPEL historicRow.DISMES = line.substring(242, 245); return historicRow; } } // Helpers: function addDot(str, right) { if (!right) right = 2; return str.substring(0,str.length - right)+'.'+str.substring(str.length - right); } module.exports = bovespaHD; // Extra code for allow file execution: if (require.main === module) { console.log('called directly'); let args = process.argv.slice(2); if (args && args.length) { let d = moment(args[0], 'DDMMYYYY'); if (d.isValid()) { bovespaHD.getDailyData(d, (err, d)=> { if (err) console.warn('Error on result:', err); console.log('result>', d); process.exit(); }); } } }
# Como rodar o script acima:

npm install extract-zip moment request
node script_simples_importaca_cota_hist_bovespa.js 05022019

Links e referência:

Atualizações!

  • 2020/08/10 - A lib extract-zip passou a usar promise e removeu o suporte à callbacks ... atualizei os scripts e exemplos deste post
  • 2020/08/10 - Código testado hoje e funcionando como esperado após os ajustes do extract-zip. 

 


afterContent:
Widget: Comments 3:

Comments