Portfólio de Ações Otimizado
Aplicação de Markowitz e Sharpe
Seleção dos Ativos
Para a montagem da carteira, foram escolhidas as seguintes ações:
- VALE3 - Mineração
- PRIO3 - Petrolífera
- SUZB3 - Papel e Celulose
- BBSA3 - Serviços Financeiros
- VIVT3 - Telecomunicações
- MULT3 - Shoppings
O critério para as escolhas foi a busca por empresas consolidadas, com exposição à receita dolarizada e com histórico de crescimento de receitas, geração de caixa e pagamento de dividendos.
O prazo considerado para análise dos dados históricos é de 6 meses (180 dias ou 126 dias úteis).
Importação dos Dados
Utilizamos da biblioteca quantmod para realizarmos a importação dos dados dos ativos para o RStudio.
#Importação da biblioteca para importação dos dados
library(quantmod)
prazo <- 180
VALE <- getSymbols("VALE3.SA", from = Sys.Date()-prazo, to = Sys.Date(), auto.assign = FALSE)
PRIO <- getSymbols("PRIO3.SA", from = Sys.Date()-prazo, to = Sys.Date(), auto.assign = FALSE)
SUZB <- getSymbols("SUZB3.SA", from = Sys.Date()-prazo, to = Sys.Date(), auto.assign = FALSE)
BBAS <- getSymbols("BBAS3.SA", from = Sys.Date()-prazo, to = Sys.Date(), auto.assign = FALSE)
VIVT <- getSymbols("VIVT3.SA", from = Sys.Date()-prazo, to = Sys.Date(), auto.assign = FALSE)
MULT <- getSymbols("MULT3.SA", from = Sys.Date()-prazo, to = Sys.Date(), auto.assign = FALSE)
IBOV <- getSymbols("^BVSP", from = Sys.Date()-prazo, to = Sys.Date(), auto.assign = FALSE)Arrumação dos Dados
Os dados trazidos por meio da API do Yahoo Finance retornam os chamados dados OHLC (Abertura, Máxima Intradia, Mínima Intradia e Fechamento), além do fechamento ajustado por dividendos e volume negociado. No entanto, para a análise principal, apenas o dado de preço ajustado nos é relevante. Dessa maneira, ajustam-se as tabelas:
library(magrittr)
library(janitor)
library(tibble)
library(dplyr)
library(ggplot2)
library(plotly)
#Organização das Tabelas
tabela_vale <- VALE %>%
as.data.frame() %>% #Transforma em Data Frame
rownames_to_column() %>% #Transforma o a data das linhas em coluna
clean_names() %>%
select(rowname, vale3_sa_adjusted) %>%
rename('data' = rowname)
tabela_prio <- PRIO %>%
as.data.frame() %>% #Transforma em Data Frame
rownames_to_column() %>% #Transforma o a data das linhas em coluna
clean_names() %>%
select(rowname, prio3_sa_adjusted) %>%
rename('data' = rowname)
tabela_suzb <- SUZB %>%
as.data.frame() %>% #Transforma em Data Frame
rownames_to_column() %>% #Transforma o a data das linhas em coluna
clean_names() %>%
select(rowname, suzb3_sa_adjusted) %>%
rename('data' = rowname)
tabela_bbas <- BBAS %>%
as.data.frame() %>% #Transforma em Data Frame
rownames_to_column() %>% #Transforma o a data das linhas em coluna
clean_names() %>%
select(rowname, bbas3_sa_adjusted) %>%
rename('data' = rowname)
tabela_vivt <- VIVT %>%
as.data.frame() %>% #Transforma em Data Frame
rownames_to_column() %>% #Transforma o a data das linhas em coluna
clean_names() %>%
select(rowname, vivt3_sa_adjusted) %>%
rename('data' = rowname)
tabela_mult <- MULT %>%
as.data.frame() %>% #Transforma em Data Frame
rownames_to_column() %>% #Transforma o a data das linhas em coluna
clean_names() %>%
select(rowname, mult3_sa_adjusted) %>%
rename('data' = rowname)
tabela_ibov <- IBOV %>%
as.data.frame() %>% #Transforma em Data Frame
rownames_to_column() %>% #Transforma o a data das linhas em coluna
clean_names() %>%
select(rowname, bvsp_adjusted) %>%
rename('data' = rowname)
#A partir deste ponto, as tabelas das ações contém somente a data e o preço ajustado de cada uma
#Sendo assim, agora, com a data em comum, conseguimos reunir todos os dados em apenas uma tabela
consolidado <- tabela_vale %>%
full_join(tabela_prio, by = c('data' = 'data')) %>%
full_join(tabela_suzb, by = c('data' = 'data')) %>%
full_join(tabela_bbas, by = c('data' = 'data')) %>%
full_join(tabela_vivt, by = c('data' = 'data')) %>%
full_join(tabela_mult, by = c('data' = 'data'))Visualização de Performance Passada
A partir dos dados obtidos, é possível avaliar a performance passadas dos ativos selecionados e compará-los ao benchmark (Ibovespa).
#Obtenção de um Dataframe com data como índice.
new_consolidado <- consolidado %>%
full_join(tabela_ibov, by = c('data' = 'data'))
row.names(new_consolidado) <- new_consolidado$data
new_consolidado <- select(new_consolidado, -data)
new_consolidado <- new_consolidado/new_consolidado[rep(1,nrow(new_consolidado)),]
new_consolidado <- new_consolidado %>%
rownames_to_column()
new_consolidado$rowname <- as.Date(new_consolidado$rowname)
plot <- ggplot(new_consolidado, aes(x = rowname)) +
geom_line(aes(y = vale3_sa_adjusted, color = "VALE"), size = 1) +
geom_line(aes(y = prio3_sa_adjusted, color = "PRIO"), size = 1) +
geom_line(aes(y = suzb3_sa_adjusted, color = "SUZB"), size = 1) +
geom_line(aes(y = bbas3_sa_adjusted, color = "BBAS"), size = 1) +
geom_line(aes(y = mult3_sa_adjusted, color = "MULT"), size = 1) +
geom_line(aes(y = vivt3_sa_adjusted, color = "VIVT"), size = 1) +
geom_line(aes(y = bvsp_adjusted, color = "BVSP"), size = 1) +
labs(title = "Performance das Ações e do IBOV no período",
subtitle = "Dados Normalizados em Base 1",
x = "Data",
y = "Valores") +
scale_x_date(date_breaks = "1 month", date_labels = "%b %Y") +
scale_color_manual(values = c(
"VALE" = "green",
"PRIO" = "darkgreen",
"SUZB" = "lightgreen",
"BBAS" = "yellow",
"MULT" = "orange",
"VIVT" = "purple",
"BVSP" = "blue"
)) +
theme_minimal() +
theme(legend.title = element_blank())
ggplotly(plot, width = 600)Fronteira Eficiente
Neste ponto da programação, o objeto consolidado guarda a data como uma coluna. Assim, precisamos transformá-lo de volta a um time-series (em que cada linha representa uma data).
Em Seguida, plota-se a fronteira eficiente para a carteira. A fronteira eficiente nos traz as carteiras com as melhores composições de risco e retorno.
library(timeSeries)
library(fPortfolio)
#Transformando em Série Temporal
consolidado_ts <- timeSeries(consolidado[,-1], consolidado[,1])
#Agora, é possível calcular os dados de retorno
retornos_ts <- returns(consolidado_ts)
#Calculando, a partir dos retornos, dados estatísticos das séries temporais de cada um dos ativos
retornos_acoes <- basicStats(retornos_ts) %>%
as.data.frame() %>%
rownames_to_column() %>%
rename("estatistica" = rowname) %>%
dplyr::filter(estatistica == 'Mean' | estatistica == 'Stdev')
#Com os dados de média diária (expected return) e desvio padrão (volatilidade ou risco da carteira), conseguimos plotar a fronteira eficiente de markowitz.
fronteira_eficiente <- portfolioFrontier(retornos_ts)
frontierPlot(fronteira_eficiente)Os pontos com maior transparência representam carteiras ineficientes da fronteira, ou seja, aquelas que apresentam um retorno inferior frente à outras carteiras, apesar da mesma exposição ao risco.
Dessa forma, é de interesse somente a parte superior da fronteira.
frontierPlot(fronteira_eficiente,
frontier = 'upper')Otimização pelo Mínimo Risco
Dada a fronteira eficiente, a primeira análise relevante é a carteira ideal para um investidor mais conservador, ou mais avesso ao risco. Esta apresenta o maior retorno com um nível de risco baixo mais baixo dentro da fronteira.
#Obtenção da composição de menor risco
carteira_minimo_risco <- minvariancePortfolio(retornos_ts)
frontierPlot(fronteira_eficiente,
frontier = 'upper')
minvariancePoints(fronteira_eficiente,
col = 'green',
cex = 1.1,
pch = 19)
A composição da carteira é a seguinte:
weightsPie(carteira_minimo_risco)carteira_minimo_risco##
## Title:
## MV Minimum Variance Portfolio
## Estimator: covEstimator
## Solver: solveRquadprog
## Optimize: minRisk
## Constraints: LongOnly
##
## Portfolio Weights:
## vale3_sa_adjusted prio3_sa_adjusted suzb3_sa_adjusted bbas3_sa_adjusted
## 0.1161 0.1053 0.2310 0.1929
## vivt3_sa_adjusted mult3_sa_adjusted
## 0.2589 0.0959
##
## Covariance Risk Budgets:
## vale3_sa_adjusted prio3_sa_adjusted suzb3_sa_adjusted bbas3_sa_adjusted
## 0.1161 0.1053 0.2310 0.1929
## vivt3_sa_adjusted mult3_sa_adjusted
## 0.2589 0.0959
##
## Target Returns and Risks:
## mean Cov CVaR VaR
## 0.0012 0.0085 0.0138 0.0125
##
## Description:
## Fri Nov 3 11:41:27 2023 by user: henri
Índice de Sharpe
Em uma segunda análise, incorporamos o índice de Sharpe para encontrarmos a melhor relação risco retorno dentro da fronteira eficiente.
frontierPlot(fronteira_eficiente, frontier = 'upper')
sharpeRatioLines(fronteira_eficiente,
col = 'green',
cex = 4,
pch = 50)Encontramos a seguir a composição da carteira ótima, que é interceptada pela Sharpe Line:
carteira_otima <- tangencyPortfolio(retornos_ts)
carteira_otima##
## Title:
## MV Tangency Portfolio
## Estimator: covEstimator
## Solver: solveRquadprog
## Optimize: minRisk
## Constraints: LongOnly
##
## Portfolio Weights:
## vale3_sa_adjusted prio3_sa_adjusted suzb3_sa_adjusted bbas3_sa_adjusted
## 0.0000 0.2733 0.2764 0.1783
## vivt3_sa_adjusted mult3_sa_adjusted
## 0.2719 0.0000
##
## Covariance Risk Budgets:
## vale3_sa_adjusted prio3_sa_adjusted suzb3_sa_adjusted bbas3_sa_adjusted
## 0.0000 0.4116 0.2588 0.1291
## vivt3_sa_adjusted mult3_sa_adjusted
## 0.2005 0.0000
##
## Target Returns and Risks:
## mean Cov CVaR VaR
## 0.0018 0.0094 0.0161 0.0129
##
## Description:
## Fri Nov 3 11:41:28 2023 by user: henri
weightsPie(carteira_otima)Incorporando Novas Estratégias
Anteriormente, estávamos adotando a estratégia em que o investidor se posiciona apenas na ponta long (comprada). Por meio da configuração de alguns parâmetros, é possível utilizarmos uma estratégia Long&Short, que consiste em uma carteira composta por posições compradas e vendidas.
configuracao_vendido <- portfolioSpec(
model = list(
type = "MV",
optimize = "minRisk",
estimator = "covEstimator",
tailRisk = list(),
params = list(alpha = 0.05, a = 2)),
portfolio = list(
weights = NULL,
targetReturn = NULL,
targetRisk = NULL,
riskFreeRate = 0,
nFrontierPoints = 50,
status = NA),
optim = list(
solver = "solveRshortExact",
objective = NULL,
parames = list(),
control = list(meq = 2),
trace = FALSE)
)
carteira_otima_long_short <- tangencyPortfolio(retornos_ts,
constraints = 'short',
spec = configuracao_vendido)
carteira_otima_long_short##
## Title:
## MV Tangency Portfolio
## Estimator: covEstimator
## Solver: solveRshortExact
## Optimize: minRisk
## Constraints:
##
## Portfolio Weights:
## vale3_sa_adjusted prio3_sa_adjusted suzb3_sa_adjusted bbas3_sa_adjusted
## -0.0772 0.3556 0.3071 0.3524
## vivt3_sa_adjusted mult3_sa_adjusted
## 0.4567 -0.3946
##
## Covariance Risk Budgets:
## vale3_sa_adjusted prio3_sa_adjusted suzb3_sa_adjusted bbas3_sa_adjusted
## -0.0146 0.3556 0.2018 0.1865
## vivt3_sa_adjusted mult3_sa_adjusted
## 0.2454 0.0252
##
## Target Returns and Risks:
## mean Cov CVaR VaR
## 0.0027 0.0119 0.0197 0.0163
##
## Description:
## Fri Nov 3 11:41:28 2023 by user: henri
tailoredFrontierPlot(portfolioFrontier(retornos_ts,
spec = configuracao_vendido,
constraint = 'short'))