Coletando Dados de Demonstrativos Financeiros

Contextualização

Neste projeto código tentaremos criar um banco de dados ou repositório onde possamos consultar dados de empresas com capital aberto que sejam negociadas na bolsa de valores brasileira.

Para isso, vamos coletar as informações que são atualizadas no site da CVM. Realizar os filtros necessários para deixar todas as informações úteis no mesmo lugar e nas mesmas grandezas possibilitando que façamos filtros rápidos dentro deles e que apenas rodando o código novamente tenhamos a atualização das informações baseada nos arquivos da CVM.

Informações de coleta

Primeiro de tudo, precisavamos verificar onde e como estão armazenadas as informações e para isso teremos que acessar o site de dados abertos da CVM e acessar a parte que diz respeito aos dados trimestrais das empresas: https://dados.cvm.gov.br/dados/CIA_ABERTA/DOC/ITR/DADOS/

Como as informações de ITR não contém os dados do quarto trimestre e queremos as informações trimestrais, também precisaremos baixar os dados anuais, que ficam em: https://dados.cvm.gov.br/dados/CIA_ABERTA/DOC/DFP/DADOS/

Tem o mesmo padrão de arquivo do ITR, porém com apenas as informações do quarto trimestre. Retirada no mês 12.

Aqui é possível verificar que os dados são armazenados em zip e são separados pelo tipo de demonstrativos em vários csv. Então, precisaremos criar um código que realize o download dos zips, a extração dos arquivos csv e a unificação dos tipos de demonstrativos, e o tratamento de ajuste pois queremos essas informações no mesmo banco de dados.

Além disso, vamos coletar outras informações no site da B3, como TICKER e setor de atuação da empresa, para facilitar análises comparativas futuras, e podemos encontrar esses dados em: https://www.b3.com.br/pt_br/produtos-e-servicos/negociacao/renda-variavel/empresas-listadas.htm

Será necessário para realização de alguns filtros, bem como o pacote GetDFPData2 do R.

Usaremos essas informações para retirar dos dados as empresas que não são negociadas na bolsa, que tem seus demonstrativos na B3 devido emissão de debêntures e também empresas que já não são mais negociadas.

Código de coleta

Primeiro de tudo, vamos utilizar os pacotes abaixo, sendo o tidyverse para tratamento e coleta e o GetDFPData2 para ajudar nos filtros. Caso queira realizar uma análise ano contra ano, o GetDFPData2 pode ajudar, pois ele traz essas informações já organizadas e também coletadas da CVM. Porém, aqui queremos criar uma cosulta trimestral.

# Pacotes

library(tidyverse)

library(httr)

library(utils)

library(fs)

library(GetDFPData2)

Depois de instalarmos os pacotes que vamos utilizar devemos baixar as informações dentro dos links da CVM. Ao acessar os sites é possível verificar que existe um padrão nos nomes dos arquivos, dessa forma. Podemos criar um loop no R para fazer o download de cada um dos zips e colocar em alguma pasta destino:

# URL base onde os arquivos estão localizados

url_base <- "https://dados.cvm.gov.br/dados/CIA_ABERTA/DOC/ITR/DADOS/"


# Anos de 2010 a 2023

anos <- 2010:2023


# Loop para criar a lista de URLs e baixar os arquivos

for (ano in anos) {

  nome_arquivo <- paste("itr_cia_aberta_", ano, ".zip", sep = "")  # Cria o nome do arquivo com base no ano

  

  url_arquivo <- paste0(url_base, nome_arquivo)  # Cria a URL completa

  

  GET(url_arquivo, write_disk(nome_arquivo, overwrite = TRUE))  # Faz o download do arquivo

  

  file.rename(from = nome_arquivo, to = file.path("~/OneDrive/Documents/cvm/arquivos", nome_arquivo)) # Move o arquivo baixado para a pasta desejada

}

Basicamente criamos um vetor para os anos que desejamos baixar e colocamos a url no objeto "url_base". Depois criamos um loop que vai unificar o começo do nome de cada um dos arquivos o começo do nome: "itr_cia_aberta_" depois roda o loop nos anos entre 2011 e 2023, e por último inclui o ".zip" no final. Incluindo as informações dentro do objeto de nome de cada arquivo, depois, cria a URL com o nome_arquivo + url_base, que gera a URL de download de cada um. Assim, fazendo o download de cada um dos arquivos e por último coloca os downloads dentro da pasta de selecionamos.

Agora que temos os zips na pasta "arquivos" vamos extrair os arquivos csv dela, para isso, criaremos um outro loop:

caminho_pasta_zip <- "~/OneDrive/Documents/cvm/arquivos"  # Caminho para a pasta que contém os arquivos ZIP


caminho_pasta_destino_dados <- "~/OneDrive/Documents/cvm/cvm_dados"  # Caminho para a pasta onde você deseja extrair os arquivos


# Loop para extrair o conteúdo de cada arquivo ZIP

for (ano in anos) {

  

  nome_arquivo_zip <- paste("itr_cia_aberta_", ano, ".zip", sep = "")   # Nome do arquivo ZIP para o ano atual

  

  caminho_arquivo_zip <- file.path(caminho_pasta_zip, nome_arquivo_zip) # Caminho completo para o arquivo ZIP

  

  unzip(caminho_arquivo_zip, exdir = caminho_pasta_destino_dados)      # Extrair o conteúdo do arquivo ZIP para a pasta de destino

}

Agora criamos um novo loop, utilizando o vetor de anos que já temos, e criando dois objetos sendo o destino de onde estão os zips e para onde vamos extrair os aquivos csv.

Como temos todos os arquivos csv dentro da pasta "dados", vamos unificar todos eles em um único csv:

# Defina o diretorio onde estão os arquivos CSV originais

diretorio <- "~/OneDrive/Documents/cvm/cvm_dados"


# Diretorio para salvar os arquivos unificados

diretorio_destino <- "~/OneDrive/Documents/cvm/dados"


# Lista de nomes de arquivos para unificar

nomes_arquivos <- c(

  "itr_cia_aberta",

  "itr_cia_aberta_BPA_con",

  "itr_cia_aberta_BPA_ind",

  "itr_cia_aberta_BPP_con",

  "itr_cia_aberta_BPP_ind",

  "itr_cia_aberta_DFC_MD_con",

  "itr_cia_aberta_DFC_MD_ind",

  "itr_cia_aberta_DFC_MI_con",

  "itr_cia_aberta_DFC_MI_ind",

  "itr_cia_aberta_DMPL_con",

  "itr_cia_aberta_DMPL_ind",

  "itr_cia_aberta_DRA_con",

  "itr_cia_aberta_DRA_ind",

  "itr_cia_aberta_DRE_con",

  "itr_cia_aberta_DRE_ind",

  "itr_cia_aberta_DVA_con",

  "itr_cia_aberta_DVA_ind"

)


# Loop para processar e unir os arquivos

for (nome_arquivo in nomes_arquivos) {

  # Nome completo do arquivo

  nome_completo <- paste0(diretorio, "/", nome_arquivo, "_", anos, ".csv")

  

  # Verificar se o arquivo existe

  if (all(file.exists(nome_completo))) {

    # Ler todos os arquivos e unir

    lista_dataframes <- lapply(nome_completo, read.csv2, sep = ";")

    df_unificado <- do.call(rbind, lista_dataframes)

    

    # Salvar o arquivo unificado no diretório de destino

    nome_arquivo_destino <- paste0(diretorio_destino, "/", nome_arquivo, ".csv")

    write.csv2(df_unificado, nome_arquivo_destino, row.names = FALSE)

  }

}


# Diretório onde estão os arquivos CSV

diretorio_base <- "~/OneDrive/Documents/cvm/dados"


# Listar os arquivos CSV no diretório

arquivos <- fs::dir_ls(path = diretorio_base, regexp = "\\.csv$")


# Inicializar uma lista para armazenar os dataframes

lista_dataframes <- list()


# Ler e armazenar os dataframes individuais

for (arquivo in arquivos) {

  df_temp <- read.csv2(arquivo, sep = ";", stringsAsFactors = FALSE)

  lista_dataframes[[length(lista_dataframes) + 1]] <- df_temp

}


# Combinar todos os dataframes em um único dataframe

df_combined <- bind_rows(lista_dataframes)


# Salvar o dataframe combinado como um arquivo CSV

destino <- "~/OneDrive/Documents/cvm/base/itr_cia_aberta_base.csv"

write.csv2(df_combined, destino, row.names = FALSE)

Primeiro criamos uma lista com as variantes de cada nome de arquivo depois usamos um loop que checa todos os arquivos dentro da pasta dados e vai criando um outro que vai baixando os dados iguais. Uma vez que ele não encontra o arquivo com o nome dentro da pasta base e começa a procurar e ler todos os csv com nomes iguais, até não encontrar mais nenhum, os que são encontrados são incluídos no mesmo csv. Quando não encontra mais, nenhum ele salva o csv com write.csv2.

Como podem ver os arquivos são bem grandes, então podem demorar um pouco para rodar esse tipo de código.

Agora que temos os dados itr, basta realizar o mesmo processo com os dados dfp, apenas mudando o nome nos códigos a url de download no começo do código.

Tratamento

Como já temos os dois csv com as informações da csv, vamos realizar o tramento para termos eles no mesmo formato e assim facilitar futuras análises. Primeiro vamos baixar os links úteis que coloquei no início do texto. Ou seja, usar o search_company do GetDFPData2 para retirarmos as empresas com status não ativo. E também baixar o csv com os dados de setores e TICKER do site da b3:

# status da empresa na bolsa


companies <- search_company("a") %>%

  distinct()


# ticker e cnpj das empresas para padronizar os nomes segundo b3


tickers <- read.csv("C:/Users/pedro/OneDrive/Documents/cvm/tickers.csv", sep = ",") %>%

  rename("CNPJ_CIA" = "CNPJ") %>%

  select(CNPJ_CIA, TICKER)


# setores das empresas na bolsa segundo a b3


sectors <- read.csv("C:/Users/pedro/OneDrive/Documents/cvm/sectors.csv", sep = ",") %>%

  rename("TICKER" = "CÓDIGO")

Depois disso, vamos ler o csv geral que criamos e retirar alguns dados que vem duplicados. Pois os arquivos da CVM na coluna ORDEM_EXERC tem as informações do exercício atual e do penúltimo. O que não faz sentido para nós, uma vez que estamos baixando todos os dados históricos já temos a informação do penúltimo exercício de qualquer forma:

## leitura do csv da base de dados cvm


base <- read.csv2("C:/Users/pedro/OneDrive/Documents/cvm/base/itr_cia_aberta_base.csv", 

                  sep = ";", 

                  encoding = "latin1")

base <- base %>% 

  filter(`ORDEM_EXERC` == "ÚLTIMO") %>%

  select(-`ORDEM_EXERC`)

Depois disso, vamos separar os dados de demonstração de resultado, pois eles vem com dois dados para o mesmo exercício: Um acumulado e um do exercício atual.
Como queremos criar as variações trimestrais, num primeiro momento os dados acumulados não nos interessam. Para isso, vamos utilizar duas colunas: DT_INI_EXERC e DT_FIM_EXERC, pois como só precisamos de dados que só considerem o exercício atual, vamos retirar tudo que não tem uma diferença entre 31 e 91 dias.

 
Então, por exemplo, calculando a diferença em dias da data de início e data final de exercício teremos a informação de quantos dias estão sendo considerados no acumulado da variável. Como só queremos as informações do exercício atual, caso tenha mais de 90 dias, ele está considerando um período maior que o trimestre, então não é a informação que queremos. E também tiraremos o que está abaixo de 31, pois algumas empresas lançam valores zerados após a inclusão dos dados na CVM, que também não são necessários nos nossos indicadores futuros. Depois de realizar o filtro, nós retiraremos a coluna que conta os dias:

### separar demonstracoes do restante para tratamento das acumuladas


demonstracao <- base %>%

  filter(GRUPO_DFP %in% c("DF Consolidado - Demonstração do Fluxo de Caixa (Método Direto)",

                          "DF Individual - Demonstração do Fluxo de Caixa (Método Direto)",

                          "DF Consolidado - Demonstração do Fluxo de Caixa (Método Indireto)",

                          "DF Individual - Demonstração do Fluxo de Caixa (Método Indireto)",

                          "DF Consolidado - Demonstração das Mutações do Patrimônio Líquido",

                          "DF Individual - Demonstração das Mutações do Patrimônio Líquido",

                          "DF Consolidado - Demonstração de Resultado Abrangente",

                          "DF Individual - Demonstração de Resultado Abrangente",

                          "DF Consolidado - Demonstração do Resultado",

                          "DF Individual - Demonstração do Resultado",

                          "DF Consolidado - Demonstração de Valor Adicionado",

                          "DF Individual - Demonstração de Valor Adicionado"))



demonstracao <- demonstracao %>%

  mutate(dif_date = as.numeric(difftime(DT_FIM_EXERC, DT_INI_EXERC, units = "days"))) %>%

  filter(dif_date < 93 & dif_date > 31)        ##retira valores acumulados da DRE


demonstracao <- demonstracao %>%

  select(-dif_date)

Como precisamos salvar o demonstrativo e não queremos duplicar as informações da base, vamos salvar um outro objeto com as informações restantes e por fim, unificar os demonstrativos que já mostravam os valores corretos com os demonstrativos corrigidos no padrão que queremos das demonstrações: 

## restante


base <- base %>% 

  filter(GRUPO_DFP %in% c("DF Consolidado - Balanço Patrimonial Ativo", "DF Individual - Balanço Patrimonial Ativo",

                          "DF Consolidado - Balanço Patrimonial Passivo", "DF Individual - Balanço Patrimonial Passivo",

                          "DF Consolidado - Demonstração do Fluxo de Caixa (Método Direto)", 

                          "DF Individual - Demonstração do Fluxo de Caixa (Método Direto)"))

### unificar

base <- rbind(base, demonstracao) 


base <- base %>%

  select(CNPJ_CIA, DT_REFER, DT_INI_EXERC, 

         DT_FIM_EXERC, DENOM_CIA, CD_CVM, GRUPO_DFP,

         CD_CONTA, DS_CONTA, VL_CONTA)

Agora vamos utilizar aqueles dados que puxamos da B3 e do GetDFPData no início do código para realizar dois filtros:

1 - Retirar empresas que estão com status Inativo;

2 - Retirar empresas que tem demonstrativos na cvm, mas não são negociadas em bolsa;

## unificar o status de cada empresa


base <- merge(base, companies, by = "CD_CVM", all.x = TRUE)


## filtrar cadastros ativos


base <- base %>%

  filter(SIT_REG == "ATIVO")


## unir tickers pelo cnpj


base <- merge(base, tickers, by = "CNPJ_CIA", all.x = TRUE)


### omitir colunas sem ticker


base <- subset(base, !is.na(TICKER))

Agora vamos separar duas colunas, o padrão nos dados é termos na coluna GRUPO_DFP, a distinção de DF Consolidado e Individual e depois o tipo do demonstrativo, separados por um hífen. Vamos separar em dois, para termos a informação separada em duas colunas:

### separar grupo dfp


base <- base %>%

  separate(`GRUPO_DFP`, into = c("TIPO_DF", "TIPO_DEM"), sep = "-", remove = FALSE)


base <- base %>%

  select(-`GRUPO_DFP`)

E por fim, vamos apenas atualizar as informações, primeiro trocaremos "," por ".". Depois transformaremos em tipo númerico com a função as.numeric e também deixaremos apenas 2 casas decimais, já que são valores monetários.

base <- base %>% 

  select(DT_REFER, TICKER, DENOM_SOCIAL, TIPO_DF, TIPO_DEM, CD_CONTA, DS_CONTA, VL_CONTA)



base$VL_CONTA <- gsub(",", ".", base$VL_CONTA)


base$VL_CONTA <- as.numeric(base$VL_CONTA)


base$VL_CONTA <- round(base$VL_CONTA, digits = 2)

Agora vamos pegar os dados do mês 12, e fazer as mesmas tratativas inicias que fizemos anteriormente:

## leitura do csv da base de dados cvm



base2 <- read.csv2("C:/Users/pedro/OneDrive/Documents/cvm/base/dfp_cia_aberta_base.csv", 

                  sep = ";", 

                  encoding = "latin1")


base2 <- base2 %>% 

  filter(`ORDEM_EXERC` == "ÚLTIMO") %>%

  select(-`ORDEM_EXERC`)


### separar demonstracoes do restante para tratamento das acumuladas


base2 <- base2 %>%

  select(CNPJ_CIA, DT_REFER, DT_INI_EXERC, 

         DT_FIM_EXERC, DENOM_CIA, CD_CVM, GRUPO_DFP,

         CD_CONTA, DS_CONTA, VL_CONTA)


### unificar o status de cada empresa


base2 <- merge(base2, companies, by = "CD_CVM", all.x = TRUE)


### filtrar cadastros ativos


base2 <- base2 %>%

  filter(SIT_REG == "ATIVO")


### unir tickers pelo cnpj


base2 <- merge(base2, tickers, by = "CNPJ_CIA", all.x = TRUE)


### omitir colunas sem ticker


base2 <- subset(base2, !is.na(TICKER))



### ajustando a coluna de contas


base2 <- base2 %>% 

  select(DT_REFER, TICKER, DENOM_SOCIAL, GRUPO_DFP, CD_CONTA, DS_CONTA, VL_CONTA)



base2 <- base2 %>%

  separate(`GRUPO_DFP`, into = c("TIPO_DF", "TIPO_DEM"), sep = "-", remove = FALSE)


base2 <- base2 %>%

  select(-`GRUPO_DFP`)


## valores numericos


base2$VL_CONTA <- gsub(",", ".", base2$VL_CONTA)


base2$VL_CONTA <- as.numeric(base2$VL_CONTA)


base2$VL_CONTA <- round(base2$VL_CONTA, digits = 2)

Agora só vamos juntar as duas bases, a primeira tem os dados do primeiro ao terceiro trimestre e a segunda tem os dados do quarto trimestre. 

Além disso, juntaremos com o csv de setores que baixamos no início do código com os setores da bolsa: 

## unificacao

data_base <- rbind(base, base2)


data_base$TICKER <- substr(data_base$TICKER, 1, 4)


## setores


data_base <- merge(data_base, sectors, by = "TICKER", all.x = TRUE)


data_base <- data_base %>% 

  select(-EMPRESA)

Agora, como eu falei no início, temos um problema relacionado as grandezas das duas fontes de dados. Nos dados trimestrais, pegamos a DRE com valores acumulados e trimestrais, e realizamos o filtro no começo. 

Já no caso das dfp anuais, eles vem acumulados. Nesse caso, vamos precisar criar um código que nos entregue as informações do mês 12 em relação ao trimestre. E podemos fazer isso somando os trimestres 1, 2 e 3 e depois subtraindo pelo valor acumulado no trimestre 4. O código abaixo realiza isso e depois substitui os valores dentro da base original: 

### valores acumulados


df_resultado <- data_base %>% 

  mutate(Ano = as.integer(substr(DT_REFER, 1, 4))) %>%       # Filtrar apenas os meses 03, 06, 09 e 12

  filter(substr(DT_REFER, 6, 7) %in% c("03", "06", "09", "12")) %>%

  group_by(TICKER, TIPO_DF, 

           CD_CONTA, Ano) %>%                                # Calcular a soma dos meses 03, 06 e 09

  mutate(Soma_03_06_09 = sum(VL_CONTA[substr(DT_REFER, 6, 7) %in% c("03", "06", "09")])) %>%

  filter(substr(DT_REFER, 6, 7) == "12") %>%                 # Filtrar o mês 12

  mutate(Diferenca = first(VL_CONTA) - Soma_03_06_09) %>%    # Calcular a diferença entre a soma dos meses 03, 06 e 09 e o mês 12

  ungroup() %>%

  select(-Soma_03_06_09, -Ano)                               # Selecionar apenas as colunas relevantes


data_base$VL_CONTA[data_base$DT_REFER %in% df_resultado$DT_REFER] <- df_resultado$Diferenca[!is.na(df_resultado$Diferenca)]

Agora temos tudo pronto vamos salvar em csv: 

write.csv2(data_base, "C:/Users/pedro/OneDrive/Documents/cvm/base/data_base.csv", row.names = FALSE)

Podemos ver o resultado, é um data frame com 7.5 milhões de linhas, onde podemos realizar filtros por ticker, conta ou setor:

Usaremos esses dados para criação de indicadores e análises contábeis em outros textos, você pode acompanhar pela seção "Textos" do portfólio.

Referências