CEI + rvest: Explorando dados financeiros com R - parte 2

Com o pacote rvest você pode baixar todo seu histórico de compra e venda de ações diretamente do CEI e passar a gerenciar sua carteira pelo R

Saulo Guerra https://github.com/sauloguerra
09-10-2019

No post anterior em B3 + rvest: Explorando dados financeiros com R - parte 1 começamos a explorar, com a ajuda do rvest, alguns dados superficiais sobre a bolsa de valores brasileira. Nesse post vamos explorar o CEI - Canal Eletrônico do Investidor, também da B3, e baixar todo o histórico de compra e venda de ações.

Definindo objetivo

Como objetivo a ser alcançado, vamos tentar baixar todo o histórico de compra e venda e montar uma tabela como ponto de partida para uma gestão da própria carteira no R.

Achando a fonte de dados

Se você investe em renda variável você certamente o faz por meio de uma corretora. A [B]³ é a instituição que centraliza toda a gestão das informações e dos contratos, e o CEI é onde o investidor pode ter acesso aos dados direto na fonte.

Dessa vez o ponto de partida da nossa fonte de dados está protegido por usuário e senha no site do CEI em https://cei.b3.com.br/CEI_Responsivo/. Como havia dito, todo investidor pode ter conta no CEI, aqui eles explicam como fazer isso.

Infelizmente não localizei nenhuma forma rápida de baixar todos os seus próprios dados em formato aberto, csv, json, xml ou algo do tipo. Sendo assim, mais uma vez vamos raspar os dados para o R. O desafio da vez é raspar dados de um site com controle de acesso (usuário e senha). Felizmente o rvest torna essa tarefa um pouco mais fácil.

Vamos usar os seguintes pacotes:


library(rvest) # para nossa raspagem
library(tidyverse) # para tudo
library(glue) # para grudar nossas strings
library(keyring)

Além dos pacotes clássicos, destaco o pacote keyring excelente pacote que ajuda a não ficar escrevendo seu usuário e senha em texto aberto no R. Veremos a seguir.

Mesmo fazendo scraping via R vamos precisar informar nosso usuário (CPF) e senha para acessar o CEI. Considerando que vamos acessar os próprios dados para gerir a própria carteira o pacote keyring atende muito bem. Ao acessar o site https://cei.b3.com.br nos deparamos com a seguinte tela:

Tela inicial
Tela inicial

Comecemos a preparar nosso scraping a partir desta tela. Diferente do post anterior, não vamos começar baixando os dados da tela e sim abrindo uma sessão para simular navegação na parte restrita do site e só depois baixar os dados da tela.

Vamos usar o rvest para simular uma sessão (controle de acesso com autenticação) do browser. Para isso vamos precisar preencher, via R, o usuário e senha do formulário e enviar uma requisição para que os servidores da B3 nos autentiquem e nos mande de volta um cookie. Leia mais sobre sessão, autenticação e cookies em: https://pt.wikipedia.org/wiki/Cookie_(inform%C3%A1tica), https://developer.mozilla.org/pt-BR/docs/Web/HTTP/Cookies ou http://material.curso-r.com/scrape/#sess%C3%B5es-e-cookies

Primeiro criamos uma sessão.


url <- 'https://cei.b3.com.br'
sessao <- html_session(url)

Por ser uma página de usuário e senha, temos um formulário para preenchermos. Com o html_form() podemos pegar os formulários disponíveis na página da sessão que abrimos.


(form_login <- html_form(sessao))

[[1]]
<form> 'aspnetForm' (POST login.aspx)
  <input hidden> '__VIEWSTATE': /wEPDwUKMjEwNTg1MDYwNmRkwOWN09etib3cEcYwkEwmQuk4eGE=
  <input hidden> '__VIEWSTATEGENERATOR': 803C878C
  <input hidden> '__EVENTVALIDATION': /wEWBAKE+eKZBgKezL2tCQKt6MueDQKW9Z+RAXuXwk35k3s3/1HrG9WTDCFP3/l4
  <input text> 'ctl00$ContentPlaceHolder1$txtLogin': 
  <input password> 'ctl00$ContentPlaceHolder1$txtSenha': 
  <input submit> 'ctl00$ContentPlaceHolder1$btnLogar': Entrar

O retorno é uma lista com o formulário de login. Reparem nos nomes dos campos disponíveis para preenchimento. Temos um input text para preencher o usuário (CPF) e um input password para preencher a senha. Respetivamente temos ctl00$ContentPlaceHolder1$txtLogin e ctl00$ContentPlaceHolder1$txtSenha para preencher.

Nesse ponto que usaremos o keyring para não deixar explícito no seu código sua senha de acesso ao CEI. Ele vai ajudar a ocultar suas senhas e usá-las de modo mais seguro.

Primeiro crie um novo “perfil” no keyring para ele armazenar sua senha de forma criptografada. Vamos chamar de cei-b3. Como o keyring vai criptografar suas senhas, você precisa definir uma senha mestra para descriptografar quando quiser. Ele vai pedir uma nova senha mestra para o perfil cei-b3. Não é a senha do CEI ainda! A partir de agora, todas as senhas que você armazenar no perfil cei-b3 você vai precisar da senha mestra que escolher nesse ponto.


# definindo uma senha mestra para armazenamento criptografado
keyring_create('cei-b3') 

Em seguida defina uma entrada para a senha do seu usuário. Você pode definir a entrada que quiser. Vamos definir a entrada senha e nosso username será o próprio CPF. No lugar de 12345678900 coloque seu CPF de verdade. Agora sim o keyring vai solicitar a senha que você vai usar no CEI.


key_set('senha', username = '12345678900', keyring = 'cei-b3')

Pronto, você definiu uma senha mestra e definiu a senha do CEI para o seu CPF. Agora vamos retornar ao formulário do CEI e usar a senha de forma mais segura. Basta chamar da seguinte forma:


form_preenchido <- set_values(form_login[[1]], 
           'ctl00$ContentPlaceHolder1$txtLogin' = '12345678900',
           'ctl00$ContentPlaceHolder1$txtSenha' = key_get('senha', '12345678900', 'cei-b3'))

Com o formulário preenchido vamos simular a submissão dos dados na mesma sessão, seria o mesmo que o “click de entrar”. A B3 vai nos autenticar e devolver a página do usuário logado.


submit_form(sessao, form_preenchido)

<session> https://cei.b3.com.br/CEI_Responsivo/home.aspx
  Status: 200
  Type:   text/html; charset=utf-8
  Size:   48490

Agora estamos na página inicial da área restrita, pós login, já com a sessão autenticada.

Tela pós login
Tela pós login

A página com os dados que queremos pegar está no menu Extratos e Informativos > Negociação de Ativos. Repare na URL da página, é ela que iremos usar agora. Como estamos em uma página autenticada com uma sessão, precisamos simular a navegação nesta página sem perder a sessão. Para isso usaremos o jump_to do rvest.


url_dados <- glue('{url}/CEI_Responsivo/negociacao-de-ativos.aspx')
pagina_dados <- jump_to(sessao, url_dados)

E agora temos mais um formulário a ser preenchido. Temos os seguintes campos para submeter: instituição, conta, data início e data fim. Aqui no meu caso a data início e data fim já vieram preenchidos com os períodos disponíveis.


(form_dados <- html_form(pagina_dados))

[[1]]
<form> 'aspnetForm' (POST negociacao-de-ativos.aspx)
  <input hidden> 'ctl00_ContentPlaceHolder1_ToolkitScriptManager1_HiddenField': 
  <input hidden> '__VIEWSTATE': /wEPDwUKLTI2MTA0ODczNg9kFgJmD2QWAgIDD2QWCAIBDw8WAh4EVGV4dAUoU0FVTE8gREUgU09VWkEgR1VFUlJBIEZFUlJFSVJBIERFIENBU1RST2RkAgMPZBYCAgEPFgIeB1Zpc2libGVoZAIFD2QWBAIBDw8WAh8ABRZOZWdvY2lhw6fDo28gZGUgYXRpdm9zZGQCAw9kFgICAw8PFgIfAAUzIC8gRXh0cmF0b3MgZSBJbmZvcm1hdGl2b3MgLyBOZWdvY2lhw6fDo28gZGUgYXRpdm9zZGQCBw9kFgICBQ9kFgJmD2QWCAIDDxBkEBUGCVNlbGVjaW9uZRo4NSAtIEJURyBQQUNUVUFMIENUVk0gUy5BLhgxMDI2IC0gQkFOQ08gQlRHIFBBQ1RVQUwWMTA5OSAtIElOVEVSIERUVk0gTFREQR4zIC0gWFAgSU5WRVNUSU1FTlRPUyBDQ1RWTSBTL0EjMzg2IC0gUklDTyBJTlZFU1RJTUVOVE9TIC0gR1JVUE8gWFAVBgItMQI4NQQxMDI2BDEwOTkBMwMzODYUKwMGZ2dnZ2dnFgFmZAIFDxAPFgIeB0VuYWJsZWRoZBAVAQxTZWxlY2lvbmUuLi4VAQEwFCsDAWcWAWZkAgcPDxYCHwFnZBYEAgUPDxYCHwAFCjE5LzAzLzIwMThkZAIHDw8WAh8ABQowOS8wOS8yMDE5ZGQCCQ9kFggCAQ8PFgIfAAUKMTkvMDMvMjAxOGRkAgMPDxYCHwAFCjA5LzA5LzIwMTlkZAIFDw8WAh8ABQoxOS8wMy8yMDE4ZGQCBw8PFgIfAAUKMDkvMDkvMjAxOWRkZPqigqMb6fRqydjTFyhGIDiRNa0b
  <input hidden> '__VIEWSTATEGENERATOR': B345DEBA
  <input hidden> '__EVENTVALIDATION': /wEWDALKnLLZBgL5hcn2DgLcmdTJDgLHmeTJDgKA9qOrAgKzu7rVBwLSmZjKDgKW09KdCgLT7OvYBALmr6naDAK4koCdDgK0rs+gCNrac6AAGrj+8chIgEiLkj0AS/Rm
  <input hidden> 'ctl00$ContentPlaceHolder1$hdnPDF_EXCEL': 
  <select> 'ctl00$ContentPlaceHolder1$ddlAgentes' [1/6]
  <select> 'ctl00$ContentPlaceHolder1$ddlContas' [1/1]
  <input text> 'ctl00$ContentPlaceHolder1$txtDataDeBolsa': 19/03/2018
  <input text> 'ctl00$ContentPlaceHolder1$txtDataAteBolsa': 09/09/2019
  <input submit> 'ctl00$ContentPlaceHolder1$btnConsultar': Consultar

No meu caso, abri contas em alguns bancos gratuitos de investimentos para experimentar o serviço deles. Como você pode comprar ações por diferentes bancos, e como queremos montar uma base com todas as ações, independente de corretora/banco, vamos percorrer todos as instituições para coletar todas as ações registradas.


instituicoes <- read_html(pagina_dados) %>% 
  html_nodes('option') %>% 
  html_text()

#tirando o placeholder 'Selecione...'
instituicoes <- instituicoes[!grepl("Selecione", instituicoes)] 

instituicoes

[1] "85 - BTG PACTUAL CTVM S.A."         
[2] "1026 - BANCO BTG PACTUAL"           
[3] "1099 - INTER DTVM LTDA"             
[4] "3 - XP INVESTIMENTOS CCTVM S/A"     
[5] "386 - RICO INVESTIMENTOS - GRUPO XP"

Agora basta percorrer cada um dos bancos, passando o código do banco e submetendo a consulta na página para resgatar o resultado de cada página e montar um dataframe. Com um simples loop resolvemos isso.

Durante o loop faremos um controle para baixar os dados apenas das instituições que apresentam a tabela de compra e venda de ações. As instituições que você nunca comprou apresentam a mensagem “Não foram encontrados resultados para esta pesquisa”. Ao detectar a presença dessa frase, vamos assumir que não foi comprada ações nessa instituição.

As datas e o número de conta são preenchidos automaticamente, basta, portanto, enviar o código número que aparece na frente do nome de cada instituição.

Ao acessar a página que de fato apresenta os dados, serão exibidas duas tabelas, vamos raspar apenas as informações da tabela com o gigantesco id ctl00_ContentPlaceHolder1_rptAgenteBolsa_ctl00_rptContaBolsa_ctl00_pnAtivosNegociados. Dela extrairemos a tag e descartaremos a última linha de totais.

O resultado é o seguinte loop:


dados_totais <- data.frame()
for(i in instituicoes) {
  #separando o código do banco do nome
  banco <- str_split(i, ' - ', simplify = TRUE)
  numero <- banco[1]
  nome <- banco[2]
  #o formulário espera receber a seleção do código númerico do banco
  form_dados_preenchidos <- set_values(form_dados[[1]], 
                                       'ctl00$ContentPlaceHolder1$ddlAgentes' = numero)

  #submetemos o formulário na mesma sessão logada
  resultado <- submit_form(sessao, form_dados_preenchidos)
  
  #caso não tenha ações, a mensagem será exibida
  nao_teve_resultado <- resultado %>%
    read_html() %>% 
    html_text() %>% 
    str_detect('Não foram encontrados resultados para esta pesquisa')
  
  #caso a mensagem não apareça
  if(!nao_teve_resultado) {
    dados_de_um_banco <- resultado %>% 
      html_nodes('#ctl00_ContentPlaceHolder1_rptAgenteBolsa_ctl00_rptContaBolsa_ctl00_pnAtivosNegociados') %>%
      html_node('table') %>%
      html_table() %>% 
      .[[1]] %>% 
      .[-nrow(.),] #descartando a última linha da tabela por ser um totalizador
    
    dados_de_um_banco$nome_banco <- nome #adicionado nome do banco no dataframe
    
    #bind dos dados de compra e venda de cada banco formando um dataframe total
    dados_totais <- bind_rows(dados_de_um_banco, dados_totais)
  }
}

Como estou utilizando meus próprios dados, vou retirar os campos de Valor total e quantidade par questões de segurança. Vamos dar uma olhada no resultado.


dados_totais <- dados_totais %>% 
  select(-`Valor Total(R$)`, -Quantidade)

glimpse(dados_totais)

Observations: 44
Variables: 9
$ `Data do Negócio`        <chr> "06/03/2019", "06/03/2019", "06/03…
$ `Compra/Venda`           <chr> "C", "C", "C", "C", "C", "C", "C",…
$ Mercado                  <chr> "Mercado a Vista", "Mercado a Vist…
$ `Prazo/Vencimento`       <lgl> NA, NA, NA, NA, NA, NA, NA, NA, NA…
$ `Código Negociação`      <chr> "GRND3", "ITUB3", "ABEV3F", "ABEV3…
$ `Especificação do Ativo` <chr> "GRENDENE    ON      NM", "ITAUUNI…
$ `Preço (R$)`             <chr> "8,54", "30,42", "16,54", "16,54",…
$ `Fator de Cotação`       <int> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1…
$ nome_banco               <chr> "XP INVESTIMENTOS CCTVM S/A", "XP …

head(dados_totais)

  Data do Negócio Compra/Venda           Mercado Prazo/Vencimento
1      06/03/2019            C   Mercado a Vista               NA
2      06/03/2019            C   Mercado a Vista               NA
3      06/03/2019            C Merc. Fracionário               NA
4      06/03/2019            C Merc. Fracionário               NA
5      06/03/2019            C Merc. Fracionário               NA
6      06/03/2019            C Merc. Fracionário               NA
  Código Negociação Especificação do Ativo Preço (R$)
1             GRND3 GRENDENE    ON      NM       8,54
2             ITUB3 ITAUUNIBANCOON  ED  N1      30,42
3            ABEV3F         AMBEV S/A   ON      16,54
4            ABEV3F         AMBEV S/A   ON      16,54
5            B3SA3F B3          ON      NM      31,74
6            B3SA3F B3          ON      NM      31,74
  Fator de Cotação                 nome_banco
1                1 XP INVESTIMENTOS CCTVM S/A
2                1 XP INVESTIMENTOS CCTVM S/A
3                1 XP INVESTIMENTOS CCTVM S/A
4                1 XP INVESTIMENTOS CCTVM S/A
5                1 XP INVESTIMENTOS CCTVM S/A
6                1 XP INVESTIMENTOS CCTVM S/A

tail(dados_totais)

   Data do Negócio Compra/Venda           Mercado Prazo/Vencimento
39      04/09/2019            C Merc. Fracionário               NA
40      04/09/2019            C Merc. Fracionário               NA
41      05/09/2019            C Merc. Fracionário               NA
42      05/09/2019            C Merc. Fracionário               NA
43      06/09/2019            C   Mercado a Vista               NA
44      06/09/2019            C   Mercado a Vista               NA
   Código Negociação Especificação do Ativo Preço (R$)
39            MDIA3F M.DIASBRANCOON      NM      35,50
40            MDIA3F M.DIASBRANCOON      NM      35,50
41            PSSA3F PORTO SEGUROON      NM      55,70
42            PSSA3F PORTO SEGUROON      NM      55,70
43            ALZR11         FII ALIANZA CI     106,20
44            GGRC11     FII GGRCOVEPCI  ER     141,38
   Fator de Cotação        nome_banco
39                1 BANCO BTG PACTUAL
40                1 BANCO BTG PACTUAL
41                1 BANCO BTG PACTUAL
42                1 BANCO BTG PACTUAL
43                1 BANCO BTG PACTUAL
44                1 BANCO BTG PACTUAL

Utilizaremos este dataframe final para os próximos posts. Para facilitar nossa vida depois, vamos estruturar uma função. Futuramente, caso os próximos resultados sejam interessantes podemos inclusive estruturar um pacote para facilitar a gestão/análise da sua carteira no R.


consulta_carteira <- function(cpf, senha) {
  url <- 'https://cei.b3.com.br'
  sessao <- html_session(url)
  form_login <- html_form(sessao)
  
  form_preenchido <- set_values(form_login[[1]], 
                               'ctl00$ContentPlaceHolder1$txtLogin' = cpf,
                               'ctl00$ContentPlaceHolder1$txtSenha' = senha)
  
  submit_form(sessao, form_preenchido)
  url_dados <- glue('{url}/CEI_Responsivo/negociacao-de-ativos.aspx')
  pagina_dados <- jump_to(sessao, url_dados)
  instituicoes <- read_html(pagina_dados) %>% 
    html_nodes('option') %>% 
    html_text()
  
  instituicoes <- instituicoes[!grepl("Selecione", instituicoes)] 
  
  form_dados <- html_form(pagina_dados)
  
  dados_totais <- data.frame()
  for(i in instituicoes) {
    banco <- str_split(i, ' - ', simplify = TRUE)
    numero <- banco[1]
    nome <- banco[2]
    form_dados_preenchidos <- set_values(form_dados[[1]], 
                                         'ctl00$ContentPlaceHolder1$ddlAgentes' = numero)
    
    resultado <- submit_form(sessao, form_dados_preenchidos)
    nao_teve_resultado <- resultado %>%
      read_html() %>% 
      html_text() %>% 
      str_detect('Não foram encontrados resultados para esta pesquisa')
    
    if(!nao_teve_resultado) {
      dados_de_um_banco <- resultado %>% 
        html_nodes('#ctl00_ContentPlaceHolder1_rptAgenteBolsa_ctl00_rptContaBolsa_ctl00_pnAtivosNegociados') %>%
        html_node('table') %>%
        html_table() %>% 
        .[[1]] %>% 
        .[-nrow(.),]
      
      dados_de_um_banco$nome_banco <- nome
      
      dados_totais <- bind_rows(dados_de_um_banco, dados_totais)
    }
  }
  return(dados_totais)
}

E chamamos nossa função assim


senha <- keyring::key_get('senha', 'SEU_CPF', 'cei-b3')
final <- consulta_carteira('SEU_CPF', senha)

Conclusão

Temos um dataframe com todo nosso histórico de compra e venda de ações de todas as corretoras utilizadas. Com este resultado poderemos partir para a gestão da carteira, avaliar os rendimentos, gestão de risco e outros controles detalhados dos seus investimentos em renda variável.

No próximo post deste assunto partiremos deste resultado para fazer algumas análises mais interessantes!

Citation

For attribution, please cite this work as

Guerra (2019, Sept. 10). Fulljoin: CEI + rvest: Explorando dados financeiros com R - parte 2. Retrieved from https://www.fulljoin.com.br/posts/2019-09-03-b3-rvest-cei/

BibTeX citation

@misc{guerra2019cei,
  author = {Guerra, Saulo},
  title = {Fulljoin: CEI + rvest: Explorando dados financeiros com R - parte 2},
  url = {https://www.fulljoin.com.br/posts/2019-09-03-b3-rvest-cei/},
  year = {2019}
}