Procedimentos

Organização e tratamento dos dados – Parte 2 Organize sua planilha e crie os retornos diários de cada ativo da sua base de dados.

• Elimine dados dos finais de semana ou de dias sem negociação (sugestão: alternativamente a análise do calendário de funcionamento da bolsa, tome como referência ações que possuem alta liquidez) • Estime os retornos diários de cada ação em uma nova planilha ou “aba” do excel • Estime a média e o desvio padrão dos retornos diários de cada ativo durante todo o período

# ==============================================================================
# 1. CARREGAR PACOTES NECESSÁRIOS
# ==============================================================================
library(readxl)
library(quantmod)
library(dplyr)
library(httr)
library(jsonlite)

# ==============================================================================
# 2. IMPORTAR E TRATAR DADOS DE PREÇOS (ECONOMATICA)
# ==============================================================================
df <- read_excel("C:/Users/Usuario/Desktop/noticia/economatica-precos_ativos-2000-2025.xlsx", sheet = 2)

df$Data <- as.Date(df$Data, format = "%Y-%m-%d") # Ajuste se necessário para "%d/%m/%Y"
df$Ano <- format(df$Data, "%Y")

# ==============================================================================
# 3. BAIXAR O ÍNDICE BOVESPA (IBOV) DO YAHOO FINANCE
# ==============================================================================
getSymbols("^BVSP", src = "yahoo", from = "2000-01-01", to = "2025-12-30", auto.assign = TRUE)
## [1] "BVSP"
df_ibov <- data.frame(
  Data = index(BVSP),
  IBOV_Fechamento = as.numeric(Cl(BVSP))
)
df_ibov <- na.omit(df_ibov)

# ==============================================================================
# 4. BAIXAR A TAXA CDI DO BANCO CENTRAL DO BRASIL (BCB)
# ==============================================================================
url_bloco1 <- "https://api.bcb.gov.br/dados/serie/bcdata.sgs.11/dados?formato=json&dataInicial=01/01/2000&dataFinal=31/12/2009"
url_bloco2 <- "https://api.bcb.gov.br/dados/serie/bcdata.sgs.11/dados?formato=json&dataInicial=01/01/2010&dataFinal=31/12/2019"
url_bloco3 <- "https://api.bcb.gov.br/dados/serie/bcdata.sgs.11/dados?formato=json&dataInicial=01/01/2020&dataFinal=30/12/2025"

baixar_bcb <- function(url) {
  resp <- GET(url, user_agent("Mozilla/5.0"))
  txt <- content(resp, as = "text", encoding = "UTF-8")
  fromJSON(txt)
}

df1 <- baixar_bcb(url_bloco1)
df2 <- baixar_bcb(url_bloco2)
df3 <- baixar_bcb(url_bloco3)

# Definindo o caminho da pasta solicitada
#pasta_destino <- "C:/Users/Usuario/Desktop/noticia/mercado de acoes"




# ==============================================================================
# 2. COMANDO PARA PUXAR / ABRIR OS DATAFRAMES DESSA MESMA PASTA
# ==============================================================================
#df1 <- readRDS(file.path(pasta_destino, "df1_bloco.rds"))
#df2 <- readRDS(file.path(pasta_destino, "df2_bloco.rds"))
#df3 <- readRDS(file.path(pasta_destino, "df3_bloco.rds"))


df_cdi <- bind_rows(df1, df2, df3)
df_cdi$Data <- as.Date(df_cdi$data, format = "%d/%m/%Y")
df_cdi$CDI_Diario <- as.numeric(df_cdi$valor) / 100  
df_cdi <- df_cdi %>% select(Data, CDI_Diario)

# ==============================================================================
# 5. INTEGRAR BASES DE MERCADO E FILTRAR PERÍODO (2005 - 2025)
# ==============================================================================
base_mercado <- merge(df_ibov, df_cdi, by = "Data", all.x = TRUE)
base_mercado$Data <- as.Date(base_mercado$Data)

df_atualizado <- df %>%
  left_join(base_mercado, by = "Data")

df_filtrado <- df_atualizado %>%
  filter(Data >= as.Date("2005-01-01") & Data <= as.Date("2025-12-30")) %>%
  filter(!is.na(IBOV_Fechamento) & !is.na(CDI_Diario))

# ==============================================================================
# 6. FILTRAGEM RIGOROSA E CONVERSÃO NUMÉRICA DOS ATIVOS
# ==============================================================================
colunas_candidatas <- setdiff(colnames(df_filtrado), c("Data", "Ano", "IBOV_Fechamento", "CDI_Diario"))

nas_totais <- sapply(df_filtrado[colunas_candidatas], function(x) sum(is.na(as.numeric(gsub(",", ".", x)))))
acoes_com_ate_3_nas <- names(which(nas_totais <= 150))

colunas_essenciais <- intersect(c("Data", "Ano", "IBOV_Fechamento", "CDI_Diario"), colnames(df_filtrado))
colunas_finais_base <- unique(c(colunas_essenciais, acoes_com_ate_3_nas))

df_base_rigorosa <- df_filtrado %>%
  select(all_of(colunas_finais_base))

# Identificar dinamicamente as colunas de ativos na base rigorosa
colunas_ativos <- setdiff(colnames(df_base_rigorosa), c("Data", "Ano", "IBOV_Fechamento", "CDI_Diario"))

# FORÇAR A CONVERSÃO DE TODAS AS COLUNAS DE ATIVOS PARA NUMÉRICO COM SEGURANÇA
df_base_rigorosa[colunas_ativos] <- lapply(df_base_rigorosa[colunas_ativos], function(x) {
  as.numeric(gsub(",", ".", as.character(x)))
})

df <- na.omit(df_base_rigorosa)

# ==============================================================================
# 7. CÁLCULO DE RETORNOS E RANKING DE SHARPE (TOP 10 ATIVOS)
# ==============================================================================
df_retornos <- df %>%
  mutate(
    Ret_IBOV = (IBOV_Fechamento / lag(IBOV_Fechamento)) - 1,
    CDI_Dia = (1 + CDI_Diario)^(1/252) - 1,  
    across(all_of(colunas_ativos), ~ (. / lag(.)) - 1)
  ) %>%
  slice(-1) # Remove a primeira linha gerada pelo lag



# Função para limpar o nome do ativo
limpar_nome_ativo <- function(nome) {
  partes <- unlist(strsplit(as.character(nome), "[\r\n]+"))
  trimws(tail(partes, 1))
}

df_retornos <- df_retornos %>%
  rename_with(~ sapply(.x, limpar_nome_ativo))

• Estime janelas de 1 ano para cada papel e as respectivas médias e desvio padrão de cada ativo para cada janela de 1 ano. Comece essas janelas anuais a partir de 2005. (serão 21 janelas) • Estime os retornos acumulados para todos os ativos durante todo o período e em cada janela de 1 ano no período de 2005 a 2025. • Complemente sua base de dados com os valores do índice Bovespa (IBOV) e do CDI, sempre em base diária, informando a fonte de consulta. Isso será necessário para calcular o Beta das ações, performance, fronteira de eficiência além de fazer comparativos e análise adicionais.

library(dplyr)
library(knitr)

# 1. Definir as colunas que devem ser ignoradas
colunas_excluir <- c("Data", "Ano", "IBOV_Fechamento", "CDI_Diario", "Em moeda orig")

# 2. Selecionar o restante das colunas dinamicamente
colunas_alvo <- setdiff(names(df_retornos), colunas_excluir)

# 3. Aplicar o agrupamento e o summarise
estatisticas_geral <- df_retornos %>% filter(Ano >= 2005) %>%
  group_by(Ano) %>%
  summarise(
    across(
      all_of(colunas_alvo),
      list(
        Média = ~ mean(.x, na.rm = TRUE),
        `Desvio-padrão` = ~ sd(.x, na.rm = TRUE)
      ),
      .names = "{.col}_{.fn}"
    ),
    .groups = "drop"
  ) %>%
  mutate(
    across(
      where(is.numeric),
      ~ round(.x, 4)
    )
  )

• Estime os retornos acumulados para todos os ativos durante todo o período e em cada janela de 1 ano no período de 2005 a 2025.

library(tidyverse)
library(plotly)

# 1. Filtrar o período de 2005 a 2025 (assumindo que a coluna de data se chama 'Data' ou similar)
# Certifique-se de ajustar 'Data' e as colunas alvo de retornos conforme sua base original.
colunas_excluir <- c("Data", "Ano", "IBOV_Fechamento", "CDI_diario", "Em moeda orig")
colunas_ativos <- setdiff(names(df_retornos), colunas_excluir)

# 2. Retorno Acumulado Total por Ativo (de 2005 até 2025)
# Fórmula padrão para retorno acumulado composto: Prod(1 + r) - 1
retorno_total <- df_retornos %>%
  filter(Ano >= 2005 & Ano <= 2025) %>%
  summarise(
    across(
      all_of(colunas_ativos),
      ~ prod(1 + .x, na.rm = TRUE) - 1,
      .names = "{.col}_Acumulado_Total"
    )
  )

# 3. Retorno Acumulado por Janela de 1 Ano (agrupado por Ano)
retorno_anual <- df_retornos %>%
  filter(Ano >= 2005 & Ano <= 2025) %>%
  group_by(Ano) %>%
  summarise(
    across(
      all_of(colunas_ativos),
      ~ prod(1 + .x, na.rm = TRUE) - 1,
      .names = "{.col}"
    ),
    .groups = "drop"
  )



# 4. Transformar a base de retornos anuais para o formato longo (para o Plotly)
df_long_retorno <- retorno_anual %>%
  pivot_longer(
    cols = -Ano,
    names_to = "Ativo",
    values_to = "Retorno_Acumulado"
  ) %>%
  mutate(
    Ativo = sapply(Ativo, limpar_nome_ativo),
    Retorno_Acumulado = round(Retorno_Acumulado * 100, 2) # Opcional: transformar em percentual
  )

# Lista de ativos únicos
ativos_ret <- unique(df_long_retorno$Ativo)
n_ativos_ret <- length(ativos_ret)

# 5. Criar o Gráfico Interativo com Plotly para o Retorno Acumulado
p_ret <- plot_ly()

for (ativo in ativos_ret) {
  df_sub <- df_long_retorno %>% filter(Ativo == ativo)
  p_ret <- p_ret %>% add_lines(
    data = df_sub,
    x = ~Ano,
    y = ~Retorno_Acumulado,
    name = paste(ativo, "- Retorno 1 Ano"),
    legendgroup = ativo,
    visible = TRUE
  )
}

# Criar o Menu Suspenso de Ativos
botoes_ativos_ret <- list(
  list(
    method = "restyle",
    args = list("visible", TRUE),
    label = "Todos os Ativos"
  )
)

for (i in seq_along(ativos_ret)) {
  vis_vector <- rep(FALSE, n_ativos_ret)
  vis_vector[i] <- TRUE
  
  botoes_ativos_ret[[i + 1]] <- list(
    method = "restyle",
    args = list("visible", vis_vector),
    label = ativos_ret[i]
  )
}

# Configurar Layout e remover a barra de ferramentas
p_ret <- p_ret %>% layout(
  title = "Retorno Acumulado Anual (2005 - 2025)",
  xaxis = list(title = "Ano", dtick = 1),
  yaxis = list(title = "Retorno Acumulado (%)"),
  updatemenus = list(
    list(
      type = "dropdown",
      active = 0,
      x = 0.1,
      y = 1.15,
      buttons = botoes_ativos_ret
    )
  )
) %>% 
  config(displayModeBar = FALSE)

# Exibir o gráfico de retornos acumulados
p_ret

• Estime os betas das ações para todos os ativos durante todo o período e em cada janela de 1 ano no período de 2005 a 2025.

library(tidyverse)
library(broom)

# Desativa a notação científica (ex: exibe 0.0001 em vez de 1e-04)
options(scipen = 999)

# 1. Definir as colunas que devem ser ignoradas
colunas_excluir <- c("Data", "Ano", "IBOV_Fechamento", "Ret_IBOV", "CDI_Diario", "Em moeda orig")

# 2. Pegar todas as colunas restantes de df_retornos
colunas_ativos_geral <- setdiff(names(df_retornos), colunas_excluir)

# ==============================================================================
# 3. CALCULAR O BETA E P-VALOR GLOBAL (Todo o período) para TODAS as ações
# ==============================================================================
beta_global_list <- map_dfr(colunas_ativos_geral, function(acao) {
  modelo <- tryCatch({
    lm(df_retornos[[acao]] ~ df_retornos$Ret_IBOV, data = df_retornos)
  }, error = function(e) { NULL })
  
  if (is.null(modelo)) return(tibble(Ativo = acao, Beta = NA_real_, P_Valor = NA_real_))
  
  resumo <- summary(modelo)$coefficients
  if (!"df_retornos$Ret_IBOV" %in% rownames(resumo) && nrow(resumo) < 2) {
    return(tibble(Ativo = acao, Beta = NA_real_, P_Valor = NA_real_))
  }
  
  # Pega o coeficiente e o p-valor da variável de mercado (linha 2)
  tibble(
    Ativo = acao,
    Beta = round(resumo[2, "Estimate"], 4),
    P_Valor = round(resumo[2, "Pr(>|t|)"], 4)
  )
})

df_beta_global <- beta_global_list %>%
  mutate(Ativo = sapply(Ativo, function(x) {
    partes <- unlist(strsplit(x, "[\r\n\\.]+"))
    ticker <- partes[grepl("^[A-Z0-9]{4,6}$", partes)]
    if(length(ticker) == 0) ticker <- tail(partes, 2)[1]  
    return(trimws(ticker))
  }))

# ==============================================================================
# 4. CALCULAR O BETA E P-VALOR POR JANELAS ANUAIS (2005 a 2025)
# ==============================================================================
# Como o summarise tradicional com across() dificulta extrair o p-valor simultaneamente,
# a forma mais segura e limpa para janelas anuais com p-valor é iterar por Ano e Ativo via map_dfr:

df_periodo <- df_retornos %>% filter(Ano >= 2005 & Ano <= 2025)
anos_unicos <- sort(unique(df_periodo$Ano))

beta_anuais_longo <- map_dfr(anos_unicos, function(ano_alvo) {
  dados_ano <- df_periodo %>% filter(Ano == ano_alvo)
  
  map_dfr(colunas_ativos_geral, function(acao) {
    # Verifica se há dados suficientes
    subset_dados <- dados_ano %>% select(all_of(c(acao, "Ret_IBOV"))) %>% drop_na()
    
    if(nrow(subset_dados) > 5) {
      modelo <- tryCatch({
        lm(subset_dados[[acao]] ~ subset_dados$Ret_IBOV)
      }, error = function(e) { NULL })
      
      if(!is.null(modelo)) {
        resumo <- summary(modelo)$coefficients
        if(nrow(resumo) >= 2) {
          return(tibble(
            Ano = ano_alvo,
            Ativo = acao,
            Beta = round(resumo[2, "Estimate"], 4),
            P_Valor = round(resumo[2, "Pr(>|t|)"], 4)
          ))
        }
      }
    }
    tibble(Ano = ano_alvo, Ativo = acao, Beta = NA_real_, P_Valor = NA_real_)
  })
}) %>%
  mutate(Ativo = sapply(Ativo, limpar_nome_ativo))

Para ver - los

Painel de Análise de Betas

Nenhuma ação selecionada. Escolha um ativo acima para carregar os dados.

• Estime também os betas das ações para todos os ativos durante todo o período e em cada janela de 1 ano no período de 2005 a 2025, mas agora com dados dos últimos 5 anos. Ou seja, para o último dia de cada janela de 1 ano, observe os dados dos 3 anos anteriores para estimar o beta.

library(tidyverse)
library(htmltools)

options(scipen = 999)

# 1. Definir colunas ignoradas e ativos (com o nome correto do CDI)
colunas_excluir <- c("Data", "Ano", "IBOV_Fechamento", "Ret_IBOV", "CDI_diario", "CDI_Diario", "Em moeda orig")
colunas_ativos_geral <- setdiff(names(df_retornos), colunas_excluir)

# Garantir ordenação por Ano
df_retornos <- df_retornos %>% arrange(Ano)

# Função robusta para cálculo de Beta e p-valor (retornando um tibble limpo)
calcular_beta_pvalor <- function(dados, acao) {
  subset_dados <- dados %>% select(all_of(c(acao, "Ret_IBOV"))) %>% drop_na()
  
  if(nrow(subset_dados) > 10) {
    modelo <- tryCatch({
      lm(subset_dados[[acao]] ~ subset_dados$Ret_IBOV)
    }, error = function(e) { NULL })
    
    if(!is.null(modelo)) {
      resumo <- summary(modelo)$coefficients
      if(nrow(resumo) >= 2) {
        return(tibble(
          Beta = resumo[2, "Estimate"],
          P_Valor = resumo[2, "Pr(>|t|)"]
        ))
      }
    }
  }
  tibble(Beta = NA_real_, P_Valor = NA_real_)
}

# ==============================================================================
# 2. CÁLCULO MÓVEL DE 5 ANOS (Janela de 5 anos anteriores para cada ano de 2005 a 2025)
# ==============================================================================
anos_alvo <- 2005:2025

betas_5anos_longo <- map_dfr(anos_alvo, function(ano_fim) {
  ano_inicio <- ano_fim - 4 # Janela móvel exata de 5 anos (ex: 2001-2005, ..., 2021-2025)
  
  dados_janela <- df_retornos %>% filter(Ano >= ano_inicio & Ano <= ano_fim)
  
  map_dfr(colunas_ativos_geral, function(acao) {
    res <- calcular_beta_pvalor(dados_janela, acao)
    tibble(
      Periodo = paste0(ano_inicio, "-", ano_fim),
      Ativo = limpar_nome_ativo(acao),
      Beta = round(res$Beta, 4),
      P_Valor = round(res$P_Valor, 4)
    )
  })
})

# Preparação para o JSON
df_completo_5anos <- betas_5anos_longo %>%
  mutate(Ativo = trimws(Ativo)) %>%
  drop_na(Beta)

lista_ativos_5anos <- sort(unique(df_completo_5anos$Ativo))
json_dados_5anos <- jsonlite::toJSON(df_completo_5anos, dataframe = "rows")

# ==============================================================================
# 3. DASHBOARD PROFISSIONAL E COLORIDO
# ==============================================================================
browsable(
  tagList(
    tags$style(HTML("
      body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; background-color: #f8f9fa; color: #333; margin: 30px; }
      .dashboard-card { background: #ffffff; padding: 25px; border-radius: 12px; box-shadow: 0 4px 15px rgba(0,0,0,0.08); max-width: 900px; margin: auto; }
      h2 { color: #2c3e50; text-align: center; margin-bottom: 25px; font-weight: 600; }
      .select-container { text-align: center; margin-bottom: 30px; }
      select { padding: 12px 20px; font-size: 16px; border-radius: 8px; border: 1px solid #cbd5e1; background-color: #fff; width: 60%; cursor: pointer; transition: all 0.3s; }
      select:focus { outline: none; border-color: #3b82f6; box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.15); }
      table { width: 100%; border-collapse: collapse; margin-top: 15px; background: white; border-radius: 8px; overflow: hidden; }
      th, td { padding: 12px 18px; text-align: center; border-bottom: 1px solid #e2e8f0; font-size: 14px; }
      th { background-color: #1e293b; color: white; font-weight: 600; text-transform: uppercase; font-size: 12px; letter-spacing: 0.5px; }
      tr:hover { background-color: #f1f5f9; }
      .badge-sig { background-color: #dcfce7; color: #166534; padding: 4px 8px; border-radius: 4px; font-weight: 600; }
      .badge-nao-sig { background-color: #fee2e2; color: #991b1b; padding: 4px 8px; border-radius: 4px; }
      .placeholder-text { text-align: center; color: #94a3b8; font-style: italic; padding: 40px; font-size: 16px; }
    ")),
    
    tags$div(
      class = "dashboard-card",
      tags$h2("Painel de Betas - Janela Móvel de 5 Anos"),
      
      tags$div(
        class = "select-container",
        tags$select(
          id = "ativoSelect5Anos",
          onchange = "filtrarDados5Anos()",
          tags$option(value = "", "Selecione uma ação...", selected = TRUE, disabled = TRUE),
          lapply(lista_ativos_5anos, function(a) tags$option(value = a, a))
        )
      ),
      
      tags$div(
        id = "tabelaContainer5Anos",
        tags$div(class = "placeholder-text", "Nenhuma ação selecionada. Escolha um ativo acima para carregar o histórico de 5 anos.")
      )
    ),
    
    tags$script(HTML(sprintf("
      const dados5Anos = %s;

      function filtrarDados5Anos() {
        const select = document.getElementById('ativoSelect5Anos');
        const ativoSelecionado = select.value;
        const container = document.getElementById('tabelaContainer5Anos');

        if (!ativoSelecionado) {
          container.innerHTML = '<div class=\"placeholder-text\">Nenhuma ação selecionada. Escolha um ativo acima para carregar o histórico de 5 anos.</div>';
          return;
        }

        const filtrados = dados5Anos.filter(d => d.Ativo === ativoSelecionado);

        if (filtrados.length === 0) {
          container.innerHTML = '<div class=\"placeholder-text\">Nenhum registro encontrado para este ativo.</div>';
          return;
        }

        let html = '<table>';
        html += '<thead><tr><th>Janela Móvel (5 Anos)</th><th>Ativo</th><th>Beta</th><th>p-valor</th></tr></thead>';
        html += '<tbody>';

        filtrados.forEach(row => {
          let pValFormatado = row.P_Valor !== null ? row.P_Valor.toFixed(4) : 'NA';
          let betaFormatado = row.Beta !== null ? row.Beta.toFixed(4) : 'NA';
          
          let pValClass = '';
          if (row.P_Valor !== null) {
            pValClass = row.P_Valor < 0.05 ? 'badge-sig' : 'badge-nao-sig';
          }

          html += `<tr>
            <td><b>${row.Periodo}</b></td>
            <td>${row.Ativo}</td>
            <td>${betaFormatado}</td>
            <td><span class=\"${pValClass}\">${pValFormatado}</span></td>
          </tr>`;
        });

        html += '</tbody></table>';
        container.innerHTML = html;
      }
    ", json_dados_5anos)))
  )
)

Painel de Betas - Janela Móvel de 5 Anos

Nenhuma ação selecionada. Escolha um ativo acima para carregar o histórico de 5 anos.

• Apresente os 10 maiores e menores retornos acumulados e os 10 maiores e menores desvios padrão para o período de 2005 a 2025 e também as médias das 21 janelas desses indicadores. • Compare os betas de 1 ano, 3 anos e o de 5 anos dessas ações

library(tidyverse)
library(htmltools)

options(scipen = 999)

# 1. Preparação das colunas
colunas_excluir <- c("Data", "Ano", "IBOV_Fechamento", "Ret_IBOV", "CDI_diario", "CDI_Diario", "Em moeda orig")
colunas_ativos <- setdiff(names(df_retornos), colunas_excluir)

# Garantir limpeza e ordenação
df_retornos <- df_retornos %>% arrange(Ano)
colnames(df_retornos) <- sapply(colnames(df_retornos), limpar_nome_ativo)
colunas_ativos_limpos <- setdiff(names(df_retornos), colunas_excluir)

# 2. Cálculos Globais e Médias das 21 Janelas (2005 a 2025)
df_2005_2025 <- df_retornos %>% filter(Ano >= 2005 & Ano <= 2025)

retorno_total <- df_2005_2025 %>%
  summarise(across(all_of(colunas_ativos_limpos), ~ prod(1 + .x, na.rm = TRUE) - 1)) %>%
  pivot_longer(everything(), names_to = "Ativo", values_to = "Retorno_Acumulado_Total")

retorno_anual_medio <- df_2005_2025 %>%
  group_by(Ano) %>%
  summarise(across(all_of(colunas_ativos_limpos), ~ prod(1 + .x, na.rm = TRUE) - 1), .groups = "drop") %>%
  summarise(across(-Ano, ~ mean(.x, na.rm = TRUE))) %>%
  pivot_longer(everything(), names_to = "Ativo", values_to = "Retorno_Anual_Medio_21Anos")

desvio_total <- df_2005_2025 %>%
  summarise(across(all_of(colunas_ativos_limpos), ~ sd(.x, na.rm = TRUE))) %>%
  pivot_longer(everything(), names_to = "Ativo", values_to = "Desvio_Padrao_Total")

desvio_anual_medio <- df_2005_2025 %>%
  group_by(Ano) %>%
  summarise(across(all_of(colunas_ativos_limpos), ~ sd(.x, na.rm = TRUE)), .groups = "drop") %>%
  summarise(across(-Ano, ~ mean(.x, na.rm = TRUE))) %>%
  pivot_longer(everything(), names_to = "Ativo", values_to = "Desvio_Padrao_Medio_21Anos")

calcular_beta_pvalor_janela <- function(dados, acao, anos_janela) {
  sub_dados <- dados %>% filter(Ano %in% anos_janela) %>% select(all_of(c(acao, "Ret_IBOV"))) %>% drop_na()
  if(nrow(sub_dados) > 5) {
    mod <- tryCatch(lm(sub_dados[[acao]] ~ sub_dados$Ret_IBOV), error = function(e) NULL)
    if(!is.null(mod)) {
      res <- summary(mod)$coefficients
      if(nrow(res) >= 2) {
        return(tibble(Beta = res[2, "Estimate"], P_Valor = res[2, "Pr(>|t|)"]))
      }
    }
  }
  return(tibble(Beta = NA_real_, P_Valor = NA_real_))
}

betas_comparativo <- map_dfr(colunas_ativos_limpos, function(acao) {
  b1 <- calcular_beta_pvalor_janela(df_retornos, acao, 2025)
  b3_list <- map_dfr(2023:2025, function(a) calcular_beta_pvalor_janela(df_retornos, acao, (a-2):a))
  b5_list <- map_dfr(2021:2025, function(a) calcular_beta_pvalor_janela(df_retornos, acao, (a-4):a))
  
  tibble(
    Ativo = acao,
    Beta_1Ano = round(b1$Beta, 4), P_Valor_1Ano = round(b1$P_Valor, 4),
    Beta_Media_3Anos = round(mean(b3_list$Beta, na.rm = TRUE), 4), P_Valor_Media_3Anos = round(mean(b3_list$P_Valor, na.rm = TRUE), 4),
    Beta_Media_5Anos = round(mean(b5_list$Beta, na.rm = TRUE), 4), P_Valor_Media_5Anos = round(mean(b5_list$P_Valor, na.rm = TRUE), 4)
  )
})

# 3. Consolidar Base Geral
df_final_consolidado <- retorno_total %>%
  left_join(retorno_anual_medio, by = "Ativo") %>%
  left_join(desvio_total, by = "Ativo") %>%
  left_join(desvio_anual_medio, by = "Ativo") %>%
  left_join(betas_comparativo, by = "Ativo")

# 4. Criar as 4 listas solicitadas (Top 10 Maiores e Menores)
top10_maiores_medias <- df_final_consolidado %>% arrange(desc(Retorno_Anual_Medio_21Anos)) %>% head(10)
top10_menores_medias <- df_final_consolidado %>% arrange(Retorno_Anual_Medio_21Anos) %>% head(10)
top10_maiores_desvios <- df_final_consolidado %>% arrange(desc(Desvio_Padrao_Medio_21Anos)) %>% head(10)
top10_menores_desvios <- df_final_consolidado %>% arrange(Desvio_Padrao_Medio_21Anos) %>% head(10)

# Estrutura agrupada para o JSON do Dashboard
dados_painel_rankings <- list(
  maiores_medias = top10_maiores_medias,
  menores_medias = top10_menores_medias,
  maiores_desvios = top10_maiores_desvios,
  menores_desvios = top10_menores_desvios
)

json_rankings <- jsonlite::toJSON(dados_painel_rankings, dataframe = "rows")

# 5. Dashboard Profissional Interativo com as 4 opções específicas
browsable(
  tagList(
    tags$style(HTML("
      body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; background-color: #f8f9fa; color: #333; margin: 30px; }
      .dashboard-card { background: #ffffff; padding: 25px; border-radius: 12px; box-shadow: 0 4px 15px rgba(0,0,0,0.08); max-width: 1200px; margin: auto; }
      h2 { color: #2c3e50; text-align: center; margin-bottom: 25px; font-weight: 600; }
      .select-container { text-align: center; margin-bottom: 30px; }
      select { padding: 12px 20px; font-size: 16px; border-radius: 8px; border: 1px solid #cbd5e1; background-color: #fff; width: 60%; cursor: pointer; }
      table { width: 100%; border-collapse: collapse; margin-top: 15px; background: white; border-radius: 8px; overflow: hidden; }
      th, td { padding: 12px 10px; text-align: center; border-bottom: 1px solid #e2e8f0; font-size: 12px; }
      th { background-color: #1e293b; color: white; font-weight: 600; text-transform: uppercase; font-size: 11px; letter-spacing: 0.5px; }
      tr:hover { background-color: #f1f5f9; }
      .placeholder-text { text-align: center; color: #94a3b8; font-style: italic; padding: 40px; font-size: 16px; }
      .highlight { font-weight: bold; color: #0284c7; }
      .badge-sig { background-color: #dcfce7; color: #166534; padding: 2px 6px; border-radius: 4px; font-weight: 600; }
      .badge-nao-sig { background-color: #fee2e2; color: #991b1b; padding: 2px 6px; border-radius: 4px; }
    ")),
    
    tags$div(
      class = "dashboard-card",
      tags$h2("Painel Executivo - Rankings de Médias e Desvios Padrões (2005-2025)"),
      
      tags$div(
        class = "select-container",
        tags$select(
          id = "rankingSelect",
          onchange = "renderizarRanking()",
          tags$option(value = "", "Selecione uma categoria de ranking...", selected = TRUE, disabled = TRUE),
          tags$option(value = "maiores_medias", "10 Maiores Médias (Retorno)"),
          tags$option(value = "menores_medias", "10 Menores Médias (Retorno)"),
          tags$option(value = "maiores_desvios", "10 Maiores Desvios Padrões (Risco)"),
          tags$option(value = "menores_desvios", "10 Menores Desvios Padrões (Risco)")
        )
      ),
      
      tags$div(
        id = "rankingContainer",
        tags$div(class = "placeholder-text", "Nenhuma categoria selecionada. Escolha uma opção acima para exibir o ranking.")
      )
    ),
    
    tags$script(HTML(sprintf("
      const dadosRankings = %s;

      function renderizarRanking() {
        const select = document.getElementById('rankingSelect');
        const categoria = select.value;
        const container = document.getElementById('rankingContainer');

        if (!categoria) {
          container.innerHTML = '<div class=\"placeholder-text\">Nenhuma categoria selecionada. Escolha uma opção acima para exibir o ranking.</div>';
          return;
        }

        const lista = dadosRankings[categoria];
        if (!lista || lista.length === 0) {
          container.innerHTML = '<div class=\"placeholder-text\">Nenhum registro encontrado para esta categoria.</div>';
          return;
        }

        function pClass(val) {
          if (val === null || isNaN(val)) return '';
          return val < 0.05 ? 'badge-sig' : 'badge-nao-sig';
        }

        let html = '<table>';
        html += '<thead><tr>';
        html += '<th>#</th><th>Ativo</th><th>Ret. Acum. Total</th><th>Média Ret. (21A)</th><th>D.P. Total</th><th>Média D.P. (21A)</th>';
        html += '<th>Beta (1A)</th><th>p-val (1A)</th><th>Beta (3A)</th><th>p-val (3A)</th><th>Beta (5A)</th><th>p-val (5A)</th>';
        html += '</tr></thead><tbody>';

        lista.forEach((d, index) => {
          html += `<tr>`;
          html += `<td><b>${index + 1}º</b></td>`;
          html += `<td class=\"highlight\">${d.Ativo}</td>`;
          html += `<td>${(d.Retorno_Acumulado_Total * 100).toFixed(2)}%%</td>`;
          html += `<td>${(d.Retorno_Anual_Medio_21Anos * 100).toFixed(2)}%%</td>`;
          html += `<td>${d.Desvio_Padrao_Total.toFixed(4)}</td>`;
          html += `<td>${d.Desvio_Padrao_Medio_21Anos.toFixed(4)}</td>`;
          html += `<td>${d.Beta_1Ano !== null ? d.Beta_1Ano.toFixed(4) : 'NA'}</td>`;
          html += `<td><span class=\"${pClass(d.P_Valor_1Ano)}\">${d.P_Valor_1Ano !== null ? d.P_Valor_1Ano.toFixed(4) : 'NA'}</span></td>`;
          html += `<td>${d.Beta_Media_3Anos !== null ? d.Beta_Media_3Anos.toFixed(4) : 'NA'}</td>`;
          html += `<td><span class=\"${pClass(d.P_Valor_Media_3Anos)}\">${d.P_Valor_Media_3Anos !== null ? d.P_Valor_Media_3Anos.toFixed(4) : 'NA'}</span></td>`;
          html += `<td>${d.Beta_Media_5Anos !== null ? d.Beta_Media_5Anos.toFixed(4) : 'NA'}</td>`;
          html += `<td><span class=\"${pClass(d.P_Valor_Media_5Anos)}\">${d.P_Valor_Media_5Anos !== null ? d.P_Valor_Media_5Anos.toFixed(4) : 'NA'}</span></td>`;
          html += `</tr>`;
        });

        html += '</tbody></table>';
        container.innerHTML = html;
      }
    ", json_rankings)))
  )
)

Painel Executivo - Rankings de Médias e Desvios Padrões (2005-2025)

Nenhuma categoria selecionada. Escolha uma opção acima para exibir o ranking.

• Escreva um pequeno texto de apresentação de sua base de dados e comente sobre as ações que você excluiu e que tipo de viés isso pode causar

FORMACAO DE 20 CARTEIRAS PELO INDICE DE SHARPE

# ============================================================
# FORMACAO DE 20 CARTEIRAS PELO INDICE DE SHARPE
# Janela movel de cinco anos
# Formacao: final de 2005 a final de 2024
# Avaliacao: anos de 2006 a 2025
# Tabelas interativas com DT
# ============================================================

library(dplyr)
library(purrr)
library(lubridate)
library(tibble)
library(DT)

# ============================================================
# 1. FUNCAO PARA LIMPAR O NOME DO ATIVO
# ============================================================

limpar_nome_ativo <- function(nome) {
  partes <- unlist(strsplit(nome, "[\r\n]+"))
  return(trimws(tail(partes, 1)))
}

# ============================================================
# 2. CORRECAO DE CODIFICACAO UTF-8
# ============================================================

corrigir_utf8 <- function(x) {

  if (is.factor(x)) {
    x <- as.character(x)
  }

  if (!is.character(x)) {
    return(x)
  }

  resultado <- iconv(
    x,
    from = "",
    to = "UTF-8",
    sub = ""
  )

  resultado[is.na(resultado)] <- ""
  Encoding(resultado) <- "UTF-8"

  resultado
}

limpar_base_dt <- function(base) {

  base <- as.data.frame(base)

  names(base) <- corrigir_utf8(names(base))

  nomes_vazios <- which(
    is.na(names(base)) | names(base) == ""
  )

  if (length(nomes_vazios) > 0) {
    names(base)[nomes_vazios] <- paste0(
      "Coluna_",
      nomes_vazios
    )
  }

  names(base) <- make.unique(names(base))

  for (j in seq_along(base)) {
    if (is.character(base[[j]]) || is.factor(base[[j]])) {
      base[[j]] <- corrigir_utf8(base[[j]])
    }
  }

  base
}

# ============================================================
# 3. PREPARAR df_retornos
# ============================================================

if (!exists("df_retornos")) {
  stop("O objeto df_retornos nao esta carregado.")
}

df_retornos <- as.data.frame(df_retornos)

if (!"Data" %in% names(df_retornos)) {
  stop(
    "A coluna Data nao foi encontrada em df_retornos. ",
    "Confira names(df_retornos)."
  )
}

# Converter a coluna de data
if (inherits(df_retornos$Data, "Date")) {

  # Nenhuma conversao necessaria

} else if (inherits(df_retornos$Data, "POSIXt")) {

  df_retornos$Data <- as.Date(df_retornos$Data)

} else {

  df_retornos$Data <- as.Date(
    as.character(df_retornos$Data)
  )
}
## NULL
if (anyNA(df_retornos$Data)) {
  stop(
    "Existem datas invalidas na coluna Data. ",
    "Verifique o formato original."
  )
}

df_retornos1 <- df_retornos %>%
  arrange(Data) %>%
  mutate(Ano = lubridate::year(Data)) %>% select(-IBOV_Fechamento)

# ============================================================
# 4. IDENTIFICAR A COLUNA DO CDI
# ============================================================

# Ajuste se o nome real da coluna for diferente.
coluna_cdi <- "CDI_Diario"

if (!coluna_cdi %in% names(df_retornos1)) {
  stop(
    paste0(
      "Coluna do CDI nao encontrada: ", coluna_cdi,
      "\nColunas disponiveis:\n",
      paste(names(df_retornos1), collapse = ", "),
      "\nAltere a variavel coluna_cdi para o nome correto."
    )
  )
}

if (!is.numeric(df_retornos[[coluna_cdi]])) {
  stop("A coluna do CDI precisa ser numerica.")
}

# ============================================================
# 5. IDENTIFICAR AS COLUNAS DAS ACOES
# ============================================================

colunas_acoes <- setdiff(
  names(df_retornos1),
  c("Data", "Ano", coluna_cdi)
)

colunas_acoes <- colunas_acoes[
  vapply(
    df_retornos[colunas_acoes],
    is.numeric,
    logical(1)
  )
]

if (length(colunas_acoes) < 10) {
  stop(
    "Foram identificadas menos de dez colunas numericas de acoes."
  )
}

# Mapa entre nome original e ticker limpo
mapa_ativos <- tibble(
  Coluna_Original = colunas_acoes,
  Ativo = vapply(
    colunas_acoes,
    limpar_nome_ativo,
    character(1)
  )
)

# Nao permitir que nomes diferentes sejam confundidos
if (anyDuplicated(mapa_ativos$Ativo)) {
  duplicados <- mapa_ativos %>%
    count(Ativo) %>%
    filter(n > 1)

  print(duplicados)

  stop(
    "Ha tickers duplicados depois da limpeza. ",
    "Confira mapa_ativos antes de continuar."
  )
}

cat(
  "Observacoes na base:", nrow(df_retornos1), "\n",
  "Acoes identificadas:", length(colunas_acoes), "\n"
)
## Observacoes na base: 4359 
##  Acoes identificadas: 67
# ============================================================
# 6. DEFINIR AS 20 JANELAS DE FORMACAO
# ============================================================

anos_formacao <- 2005:2024

# Janela historica de cinco anos-calendario:
# 2005 utiliza 2001-2005.
# 2006 utiliza 2002-2006.
# ...
# 2024 utiliza 2020-2024.

# ============================================================
# 7. FUNCAO DE CALCULO DO INDICE DE SHARPE
# ============================================================

calcular_sharpe <- function(base, ano_formacao) {

  ano_inicio <- ano_formacao - 4

  data_inicio <- as.Date(
    paste0(ano_inicio, "-01-01")
  )

  data_fim <- as.Date(
    paste0(ano_formacao, "-12-31")
  )

  janela <- base %>%
    filter(
      Data >= data_inicio,
      Data <= data_fim
    )

  if (nrow(janela) == 0) {
    warning(
      paste("Janela sem observacoes:", ano_formacao)
    )
    return(tibble())
  }

  retorno_cdi <- janela[[coluna_cdi]]

  resultados <- purrr::map_dfr(
    colunas_acoes,
    function(acao) {

      retorno_acao <- janela[[acao]]

      dados <- tibble(
        Retorno_Ativo = retorno_acao,
        Retorno_CDI = retorno_cdi
      ) %>%
        filter(
          is.finite(Retorno_Ativo),
          is.finite(Retorno_CDI)
        )

      n <- nrow(dados)

      # Exigir pelo menos 30 observacoes validas
      if (n < 30) {
        return(
          tibble(
            Coluna_Original = acao,
            Ativo = limpar_nome_ativo(acao),
            Observacoes = n,
            Retorno_Medio = NA_real_,
            CDI_Medio = NA_real_,
            Volatilidade = NA_real_,
            Sharpe = NA_real_
          )
        )
      }

      media_acao <- mean(dados$Retorno_Ativo)
      media_cdi <- mean(dados$Retorno_CDI)
      volatilidade <- sd(dados$Retorno_Ativo)

      valor_sharpe <- if (
        is.finite(volatilidade) &&
        volatilidade > 0
      ) {
        (media_acao - media_cdi) / volatilidade
      } else {
        NA_real_
      }

      tibble(
        Coluna_Original = acao,
        Ativo = limpar_nome_ativo(acao),
        Observacoes = n,
        Retorno_Medio = media_acao,
        CDI_Medio = media_cdi,
        Volatilidade = volatilidade,
        Sharpe = valor_sharpe
      )
    }
  )

  resultados %>%
    mutate(
      Ano_Formacao = ano_formacao,
      Ano_Inicio_Janela = ano_inicio,
      Ano_Fim_Janela = ano_formacao,
      Ano_Avaliacao = ano_formacao + 1
    ) %>%
    group_by(Ano_Formacao) %>%
    mutate(
      Ranking = if_else(
        is.na(Sharpe),
        NA_integer_,
        min_rank(desc(Sharpe))
      )
    ) %>%
    ungroup() %>%
    select(
      Ano_Formacao,
      Ano_Inicio_Janela,
      Ano_Fim_Janela,
      Ano_Avaliacao,
      Ranking,
      Ativo,
      Coluna_Original,
      Observacoes,
      Retorno_Medio,
      CDI_Medio,
      Volatilidade,
      Sharpe
    ) %>%
    arrange(Ano_Formacao, Ranking, Ativo)
}

# ============================================================
# 8. CALCULAR OS RANKINGS DE TODAS AS ACOES
# ============================================================

rankings <- purrr::map_dfr(
  anos_formacao,
  function(ano) {
    calcular_sharpe(
      base = df_retornos,
      ano_formacao = ano
    )
  }
)

if (nrow(rankings) == 0) {
  stop("Nenhum ranking foi calculado.")
}

# ============================================================
# 9. SELECIONAR AS DEZ MELHORES ACOES POR ANO
# ============================================================

carteiras_20 <- rankings %>%
  filter(!is.na(Sharpe)) %>%
  group_by(Ano_Formacao) %>%
  arrange(
    desc(Sharpe),
    Ativo,
    .by_group = TRUE
  ) %>%
  slice_head(n = 10) %>%
  mutate(
    Peso_Inicial = 0.10
  ) %>%
  ungroup() %>%
  arrange(Ano_Formacao, Ranking)

# ============================================================
# 10. RESUMO DA COMPOSICAO DAS CARTEIRAS
# ============================================================

resumo_carteiras <- carteiras_20 %>%
  group_by(
    Ano_Formacao,
    Ano_Avaliacao
  ) %>%
  summarise(
    Numero_Ativos = n_distinct(Ativo),
    Ativos_Selecionados = paste(
      Ativo,
      collapse = ", "
    ),
    .groups = "drop"
  ) %>%
  arrange(Ano_Formacao)

cat(
  "Numero de carteiras formadas:",
  n_distinct(carteiras_20$Ano_Formacao),
  "\n"
)
## Numero de carteiras formadas: 20
cat(
  "Numero de acoes disponiveis:",
  length(colunas_acoes),
  "\n"
)
## Numero de acoes disponiveis: 67
# Verificar se todas as carteiras possuem dez ativos
carteiras_incompletas <- resumo_carteiras %>%
  filter(Numero_Ativos != 10)

if (nrow(carteiras_incompletas) > 0) {
  warning(
    "Existem carteiras com menos de dez ativos. ",
    "Confira a disponibilidade de dados em cada janela."
  )
}

# ============================================================
# 11. PREPARAR OBJETOS PARA O DT
# ============================================================

rankings_dt <- limpar_base_dt(rankings)
carteiras_dt <- limpar_base_dt(carteiras_20)
resumo_dt <- limpar_base_dt(resumo_carteiras)

# Verificar strings invalidas antes de criar as tabelas
verificar_utf8 <- function(base, nome) {

  nomes_validos <- iconv(
    names(base),
    from = "UTF-8",
    to = "UTF-8",
    sub = NA
  )

  if (anyNA(nomes_validos)) {
    stop(
      paste(
        "Existem nomes de colunas invalidos em",
        nome
      )
    )
  }

  for (j in seq_along(base)) {

    if (is.character(base[[j]])) {

      x <- base[[j]]

      resultado <- iconv(
        x,
        from = "UTF-8",
        to = "UTF-8",
        sub = NA
      )

      if (anyNA(resultado)) {
        stop(
          paste(
            "Texto UTF-8 invalido em",
            nome,
            "- coluna:",
            names(base)[j]
          )
        )
      }
    }
  }

  cat("UTF-8 validado:", nome, "\n")
}

verificar_utf8(rankings_dt, "rankings")
## UTF-8 validado: rankings
verificar_utf8(carteiras_dt, "carteiras_20")
## UTF-8 validado: carteiras_20
verificar_utf8(resumo_dt, "resumo_carteiras")
## UTF-8 validado: resumo_carteiras
# ============================================================
# 12. TABELA INTERATIVA DO RANKING COMPLETO
# ============================================================

tabela_ranking <- DT::datatable(
  rankings_dt,
  caption = "Ranking do Indice de Sharpe por janela de formacao",
  rownames = FALSE,
  filter = "top",
  escape = TRUE,
  extensions = "Buttons",
  options = list(
    pageLength = 15,
    lengthMenu = c(10, 15, 25, 50, 100),
    scrollX = TRUE,
    dom = "Bfrtip",
    buttons = c("copy", "csv", "excel"),
    order = list(
      list(0, "asc"),
      list(4, "asc")
    )
  )
) %>%
  DT::formatRound(
    columns = c(
      "Retorno_Medio",
      "CDI_Medio",
      "Volatilidade",
      "Sharpe"
    ),
    digits = 6
  )



# ============================================================
# 13. TABELA INTERATIVA DAS 20 CARTEIRAS
# ============================================================

tabela_carteiras <- DT::datatable(
  carteiras_dt,
  caption = "Composicao das 20 carteiras pelo Indice de Sharpe",
  rownames = FALSE,
  filter = "top",
  escape = TRUE,
  extensions = "Buttons",
  options = list(
    pageLength = 10,
    lengthMenu = c(10, 20, 50, 100),
    scrollX = TRUE,
    dom = "Bfrtip",
    buttons = c("copy", "csv", "excel"),
    order = list(
      list(0, "asc"),
      list(4, "asc")
    )
  )
) %>%
  DT::formatRound(
    columns = c(
      "Retorno_Medio",
      "CDI_Medio",
      "Volatilidade",
      "Sharpe",
      "Peso_Inicial"
    ),
    digits = 6
  )



# ============================================================
# 14. TABELA INTERATIVA DO RESUMO ANUAL
# ============================================================

tabela_resumo <- DT::datatable(
  resumo_dt,
  caption = "Resumo das carteiras anuais",
  rownames = FALSE,
  filter = "top",
  escape = TRUE,
  extensions = "Buttons",
  options = list(
    pageLength = 20,
    scrollX = TRUE,
    dom = "Bfrtip",
    buttons = c("copy", "csv", "excel")
  )
)

tabela_resumo
# ============================================================
# 15. EXPORTAR OS RESULTADOS
# ============================================================

write.csv(
  rankings_dt,
  "ranking_sharpe_2005_2024.csv",
  row.names = FALSE,
  fileEncoding = "UTF-8"
)

write.csv(
  carteiras_dt,
  "carteiras_20_sharpe.csv",
  row.names = FALSE,
  fileEncoding = "UTF-8"
)

write.csv(
  resumo_dt,
  "resumo_carteiras_20.csv",
  row.names = FALSE,
  fileEncoding = "UTF-8"
)

cat(
  "\nArquivos exportados para o diretorio de trabalho:\n",
  getwd(),
  "\n"
)
## 
## Arquivos exportados para o diretorio de trabalho:
##  C:/Users/Usuario/Documents
# ============================================================
# ANÁLISE COMPLETA DAS 20 CARTEIRAS ANUAIS
# Retorno acumulado, média, risco, beta, Sharpe,
# Treynor e alfa de Jensen
# ============================================================

library(dplyr)
library(purrr)
library(tibble)
library(DT)

# ------------------------------------------------------------
# 1. FUNÇÃO PARA CORRIGIR CODIFICAÇÃO UTF-8
# ------------------------------------------------------------

corrigir_utf8 <- function(x) {

  if (is.factor(x)) {
    x <- as.character(x)
  }

  if (is.character(x)) {
    x <- iconv(
      x,
      from = "",
      to = "UTF-8",
      sub = ""
    )
  }

  x
}

limpar_base_dt <- function(dados) {

  dados <- as.data.frame(
    dados,
    stringsAsFactors = FALSE
  )

  names(dados) <- corrigir_utf8(names(dados))

  dados[] <- lapply(dados, corrigir_utf8)

  dados
}

# ------------------------------------------------------------
# 2. PREPARAR A BASE DE RETORNOS
# ------------------------------------------------------------

df_base <- df_retornos %>%
  mutate(
    Data = as.Date(Data)
  ) %>%
  arrange(Data)

# Verificar variáveis necessárias
necessarias <- c(
  "Data",
  "CDI_Diario",
  "IBOV_Fechamento"
)

ausentes <- setdiff(
  necessarias,
  names(df_base)
)

if (length(ausentes) > 0) {
  stop(
    paste(
      "Variáveis ausentes em df_retornos:",
      paste(ausentes, collapse = ", ")
    )
  )
}

if (anyDuplicated(df_base$Data)) {
  stop(
    "Há datas repetidas em df_retornos. ",
    "Corrija as duplicações antes de continuar."
  )
}

# Retorno diário do Ibovespa calculado a partir do índice
df_base <- df_base %>%
  mutate(
    Ano = as.integer(format(Data, "%Y")),
    Retorno_IBOV =
      IBOV_Fechamento / lag(IBOV_Fechamento) - 1
  )

# ------------------------------------------------------------
# 3. VALIDAR A ESTRUTURA DAS CARTEIRAS
# ------------------------------------------------------------

colunas_obrigatorias <- c(
  "Ano_Formacao",
  "Ativo",
  "Coluna_Original"
)

ausentes <- setdiff(
  colunas_obrigatorias,
  names(carteiras_20)
)

if (length(ausentes) > 0) {
  stop(
    paste(
      "Colunas ausentes em carteiras_20:",
      paste(ausentes, collapse = ", ")
    )
  )
}

if (anyNA(carteiras_20$Coluna_Original)) {
  stop("Existem nomes de colunas de ativos ausentes.")
}

# Verificar se as colunas selecionadas existem na base
ativos_ausentes <- setdiff(
  unique(carteiras_20$Coluna_Original),
  names(df_base)
)

if (length(ativos_ausentes) > 0) {
  stop(
    paste(
      "Ativos não encontrados em df_retornos:",
      paste(ativos_ausentes, collapse = ", ")
    )
  )
}

# Garantir dez ações distintas por carteira
conferencia <- carteiras_20 %>%
  group_by(Ano_Formacao) %>%
  summarise(
    Numero_Ativos = n_distinct(Coluna_Original),
    .groups = "drop"
  )

if (any(conferencia$Numero_Ativos != 10)) {
  print(conferencia)
  stop("Cada carteira precisa conter dez ações distintas.")
}

# ------------------------------------------------------------
# 4. FUNÇÃO DE AVALIAÇÃO DE CADA CARTEIRA
# ------------------------------------------------------------

avaliar_carteira <- function(
    ano_formacao,
    dados_carteira,
    base_dados) {

  ano_avaliacao <- ano_formacao + 1

  # Ações selecionadas no ano de formação
  ativos <- dados_carteira %>%
    filter(Ano_Formacao == ano_formacao)

  colunas_ativos <- ativos$Coluna_Original

  # Pesos iguais: dez ações com 10% cada
  pesos <- rep(
    1 / length(colunas_ativos),
    length(colunas_ativos)
  )

  # Dados do ano seguinte
  dados_ano <- base_dados %>%
    filter(Ano == ano_avaliacao) %>%
    select(
      Data,
      CDI_Diario,
      Retorno_IBOV,
      all_of(colunas_ativos)
    )

  # Remover observações incompletas
  dados_ano <- dados_ano %>%
    filter(
      is.finite(CDI_Diario),
      is.finite(Retorno_IBOV),
      if_all(
        all_of(colunas_ativos),
        ~ is.finite(.x)
      )
    )

  # Resultado ausente se não houver observações suficientes
  if (nrow(dados_ano) < 2) {
    return(
      tibble(
        Ano_Formacao = ano_formacao,
        Ano_Avaliacao = ano_avaliacao,
        Numero_Dias = nrow(dados_ano),
        Retorno_Acumulado = NA_real_,
        Retorno_Medio_Diario = NA_real_,
        Desvio_Padrao_Diario = NA_real_,
        Beta = NA_real_,
        Sharpe_Diario = NA_real_,
        Treynor_Diario = NA_real_,
        Alfa_Jensen_Diario = NA_real_
      )
    )
  }

  # ----------------------------------------------------------
  # 5. RETORNO DIÁRIO DA CARTEIRA
  # ----------------------------------------------------------

  matriz_retornos <- as.matrix(
    dados_ano[, colunas_ativos, drop = FALSE]
  )

  storage.mode(matriz_retornos) <- "double"

  retorno_carteira <- as.vector(
    matriz_retornos %*% pesos
  )

  retorno_cdi <- dados_ano$CDI_Diario
  retorno_mercado <- dados_ano$Retorno_IBOV

  # ----------------------------------------------------------
  # 6. RETORNO ACUMULADO, MÉDIA E DESVIO-PADRÃO
  # ----------------------------------------------------------

  retorno_acumulado <- prod(
    1 + retorno_carteira
  ) - 1

  retorno_medio <- mean(retorno_carteira)

  desvio_padrao <- sd(retorno_carteira)

  # ----------------------------------------------------------
  # 7. BETA EM RELAÇÃO AO IBOVESPA
  # ----------------------------------------------------------

  variancia_mercado <- var(retorno_mercado)

  beta <- if (
    is.finite(variancia_mercado) &&
    variancia_mercado > 0
  ) {
    cov(retorno_carteira, retorno_mercado) /
      variancia_mercado
  } else {
    NA_real_
  }

  # ----------------------------------------------------------
  # 8. ÍNDICE DE SHARPE
  # ----------------------------------------------------------

  sharpe <- if (
    is.finite(desvio_padrao) &&
    desvio_padrao > 0
  ) {
    mean(retorno_carteira - retorno_cdi) /
      desvio_padrao
  } else {
    NA_real_
  }

  # ----------------------------------------------------------
  # 9. ÍNDICE DE TREYNOR
  # ----------------------------------------------------------

  treynor <- if (
    is.finite(beta) &&
    abs(beta) > 1e-10
  ) {
    mean(retorno_carteira - retorno_cdi) /
      beta
  } else {
    NA_real_
  }

  # ----------------------------------------------------------
  # 10. ALFA DE JENSEN
  # ----------------------------------------------------------

  alfa_jensen <- if (is.finite(beta)) {

    excesso_carteira <- mean(
      retorno_carteira - retorno_cdi
    )

    excesso_mercado <- mean(
      retorno_mercado - retorno_cdi
    )

    excesso_carteira -
      beta * excesso_mercado

  } else {
    NA_real_
  }

  # ----------------------------------------------------------
  # 11. RESULTADOS DA CARTEIRA
  # ----------------------------------------------------------

  tibble(
    Ano_Formacao = ano_formacao,
    Ano_Avaliacao = ano_avaliacao,
    Numero_Dias = length(retorno_carteira),
    Retorno_Acumulado = retorno_acumulado,
    Retorno_Medio_Diario = retorno_medio,
    Desvio_Padrao_Diario = desvio_padrao,
    Beta = beta,
    Sharpe_Diario = sharpe,
    Treynor_Diario = treynor,
    Alfa_Jensen_Diario = alfa_jensen
  )
}

# ------------------------------------------------------------
# 12. CALCULAR AS CARTEIRAS ANUAIS
# ------------------------------------------------------------

anos_formacao <- sort(
  unique(carteiras_20$Ano_Formacao)
)

resultados_carteiras <- map_dfr(
  anos_formacao,
  ~ avaliar_carteira(
    ano_formacao = .x,
    dados_carteira = carteiras_20,
    base_dados = df_base
  )
)

# ------------------------------------------------------------
# 13. FORMATAR OS RESULTADOS
# ------------------------------------------------------------

resultados_formatados <- resultados_carteiras %>%
  mutate(
    Retorno_Acumulado =
      100 * Retorno_Acumulado,

    Retorno_Medio_Diario =
      100 * Retorno_Medio_Diario,

    Desvio_Padrao_Diario =
      100 * Desvio_Padrao_Diario,

    Alfa_Jensen_Diario =
      100 * Alfa_Jensen_Diario
  ) %>%
  rename(
    `Ano de formacao` = Ano_Formacao,
    `Ano de avaliacao` = Ano_Avaliacao,
    `Dias observados` = Numero_Dias,
    `Retorno acumulado (%)` = Retorno_Acumulado,
    `Retorno medio diario (%)` = Retorno_Medio_Diario,
    `Desvio-padrao diario (%)` = Desvio_Padrao_Diario,
    `Beta da carteira` = Beta,
    `Indice de Sharpe diario` = Sharpe_Diario,
    `Indice de Treynor diario` = Treynor_Diario,
    `Alfa de Jensen diario (%)` = Alfa_Jensen_Diario
  )

# ------------------------------------------------------------
# 14. CORRIGIR UTF-8 ANTES DO DT
# ------------------------------------------------------------

resultados_dt <- limpar_base_dt(
  resultados_formatados
)

# ------------------------------------------------------------
# 15. TABELA INTERATIVA
# ------------------------------------------------------------

tabela_carteiras <- DT::datatable(
  resultados_dt,
  rownames = FALSE,
  filter = "top",
  escape = TRUE,
  options = list(
    pageLength = 20,
    scrollX = TRUE,
    autoWidth = TRUE
  ),
  caption = "Desempenho das carteiras anuais"
) %>%
  DT::formatRound(
    columns = c(
      "Retorno acumulado (%)",
      "Retorno medio diario (%)",
      "Desvio-padrao diario (%)",
      "Beta da carteira",
      "Indice de Sharpe diario",
      "Indice de Treynor diario",
      "Alfa de Jensen diario (%)"
    ),
    digits = 4
  )

tabela_carteiras
# ------------------------------------------------------------
# 16. EXPORTAR RESULTADOS
# ------------------------------------------------------------

write.csv(
  resultados_dt,
  "resultados_carteiras_anuais.csv",
  row.names = FALSE,
  fileEncoding = "UTF-8"
)

# ------------------------------------------------------------
# 17. CONFERÊNCIA FINAL
# ------------------------------------------------------------



cat(
  "\nNúmero de carteiras avaliadas:",
  nrow(resultados_dt),
  "\n"
)
## 
## Número de carteiras avaliadas: 20
cat(
  "Arquivo exportado para:",
  file.path(
    getwd(),
    "resultados_carteiras_anuais.csv"
  ),
  "\n"
)
## Arquivo exportado para: C:/Users/Usuario/Documents/resultados_carteiras_anuais.csv
## Painel gerado com sucesso: painel_carteiras_interativo.html

Analise de Carteiras de Investimento

Fronteira eficiente, Capital Market Line (CML), Security Market Line (SML), composicao e comparacao de carteiras formadas por dez acoes.

1. Graficos interativos

Selecione o ano da carteira e depois escolha entre a fronteira eficiente com CML e a SML/CAPM.

Carteira de avaliacao 2005

Janela de formacao: 2001 a 2005 | Dez ativos selecionados



2. Tabelas interativas

Explore o resumo, a composicao e as mudancas estruturais das carteiras.

Fonte: df_retornos e carteiras_20. Retornos anualizados por 252 pregoes; risco anualizado pela raiz de 252.