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))
• 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)))
)
)
• 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)))
)
)
• 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
# 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
Fronteira eficiente, Capital Market Line (CML), Security Market Line (SML), composicao e comparacao de carteiras formadas por dez acoes.
Selecione o ano da carteira e depois escolha entre a fronteira eficiente com CML e a SML/CAPM.
Janela de formacao: 2001 a 2005 | Dez ativos selecionados
Explore o resumo, a composicao e as mudancas estruturais das carteiras.