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'))