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) {
  extract(source, { dir: target }, 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
 */
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 }, 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();
      });

    }
  }
}

Links e referência:

 


afterContent:
Widget: Comments 3:

Comments