Introdução à otimização de portfólio em R

Introdução

O objetivo principal do investidor é obter o máximo retorno com o mínimo risco. Neste âmbito, a otimização de portfólio surge como uma teoria que permite que o investidor aloque eficientemente os seus recursos com base nos seus níveis de aversão e aceitação do risco. Basicamente, esta teoria está fundamentada no pressuposto de que a diversificção da carteira é o passo básico para a minimização do risco e que a participação dos ativos de maior risco deve ser ponderada na carteira de investimento de acordo com as suas respectivas relações risco-retorno.

O modelo de Markowitz

O modelo de Markowitz é uma ferramenta que permite calcular o risco de uma carteira de investimentos, independentemente do tipo de ativos que a compõem. O objetivo do modelo de Markowitz, é possibilitar que os investidores que não gostam de riscos, possam construir uma carteira de investimentos com maior potencial de rentabilidade, a partir de determinado nível de risco de mercado. A ideia central da teoria é a construção de fronteiras eficientes em carteiras otimizadas, levando em consideração que o risco e o retorno precisam ser avaliados em conjunto dentro da carteira.

De maneira generalizada, o modelo de Markowitz retorna um conjunto de combinações possíveis de ativos, representados como pontos no gráfico, cujo eixo X corresponde ao risco e o Y é o retorno. Os pontos no gráfico formam uma curva hiperbólica ascendente, que reflete a fronteira mais eficiente possível. Desse modo, o investidor pode identificar qual a combinação de ativos que é capaz de produzir o resultado mais eficiente.

Escolhendo a carteira ótima

Para demonstrar a aliação do modelo de Markowitz, suponha que queiramos identificar o peso de cada ativo em uma carteira composta por ações das empresas Suzano, Vale, Telefônica e Marfrig. O primeiro passo é liberar as bibliotecas necessárias para a elaboração da carteira ótima.

lapply(list('BatchGetSymbols', 'tidyverse', 'ggplot2', 'fPortfolio'), function(x){
              if(x %in% installed.packages() == FALSE){
                install.packages(x)
                require(x, character.only = TRUE)
              }else{
                require(x, character.only = TRUE)
              }
            })
## Carregando pacotes exigidos: BatchGetSymbols
## Carregando pacotes exigidos: rvest
## Carregando pacotes exigidos: dplyr
## 
## Attaching package: 'dplyr'
## The following objects are masked from 'package:stats':
## 
##     filter, lag
## The following objects are masked from 'package:base':
## 
##     intersect, setdiff, setequal, union
## 
## Carregando pacotes exigidos: tidyverse
## -- Attaching packages --------------------------------------- tidyverse 1.3.2 --
## v ggplot2 3.3.6     v purrr   0.3.4
## v tibble  3.1.8     v stringr 1.4.0
## v tidyr   1.2.0     v forcats 0.5.2
## v readr   2.1.2     
## -- Conflicts ------------------------------------------ tidyverse_conflicts() --
## x dplyr::filter()         masks stats::filter()
## x readr::guess_encoding() masks rvest::guess_encoding()
## x dplyr::lag()            masks stats::lag()
## Carregando pacotes exigidos: fPortfolio
## 
## Carregando pacotes exigidos: timeDate
## 
## Carregando pacotes exigidos: timeSeries
## 
## Carregando pacotes exigidos: fBasics
## 
## Carregando pacotes exigidos: fAssets

Feito isto, o próximo passo é definir o horizote de tempo em que a análise será exeutada, isto é, o tempo de dispersão dos dados sobre o qual os riscos e possíveis retornos serão calculados. Especificamente, este documento optará por escolher um intervalo desde janeiro de 2015 até a data atual, optando por uma periodicidade semanal.

first.date <- as.Date('2015-01-01')
last.date <- Sys.Date()
freq.data <- 'weekly'

O próximo passo é identificar os códigos de negociação das empresas na bolsa de valores e fazer o download das cotações no período escolhido. Neste caso, é preciso que os códigos de negociação sejam sucedidos da sígla “.SA”.

tickers <- c('SUZB3.SA','VALE3.SA','VIVT3.SA','MRFG3.SA')
l.out <- BatchGetSymbols(tickers = tickers, 
                         first.date = first.date,
                         last.date = last.date, 
                         freq.data = freq.data,
                         cache.folder = file.path(tempdir(), 'BGS_Cache'))
## Warning: `BatchGetSymbols()` was deprecated in BatchGetSymbols 2.6.4.
## Please use `yfR::yf_get()` instead.
## 2022-05-01: Package BatchGetSymbols will soon be replaced by yfR. 
## More details about the change is available at github <<www.github.com/msperlin/yfR>
## You can install yfR by executing:
## 
## remotes::install_github('msperlin/yfR')
## 
## Running BatchGetSymbols for:
##    tickers =SUZB3.SA, VALE3.SA, VIVT3.SA, MRFG3.SA
##    Downloading data for benchmark ticker
## ^GSPC | yahoo (1|1) | Not Cached | Saving cache
## SUZB3.SA | yahoo (1|4) | Not Cached | Saving cache - Got 96% of valid prices | Got it!
## VALE3.SA | yahoo (2|4) | Not Cached | Saving cache - Got 96% of valid prices | Well done!
## VIVT3.SA | yahoo (3|4) | Not Cached | Saving cache - Got 96% of valid prices | Good stuff!
## MRFG3.SA | yahoo (4|4) | Not Cached | Saving cache - Got 96% of valid prices | Mas bah tche, que coisa linda!

Agora é possível examinar o valor das cotações de cada empresa que compõe a carteira de investimentos.

ggplot(l.out$df.tickers, aes(x = ref.date, y = price.close)) +
  geom_line() + 
  facet_wrap(~ticker, scales = 'free_y')+
  theme_bw()

O próximo passo é montar uma matriz com as séries temporais de retornos obtidos na periodicidade escolhida (semanal).

suzano = l.out$df.tickers[l.out$df.tickers$ticker == "SUZB3.SA",] %>% 
  na.omit() %>% 
  select( ret.closing.prices) %>% 
  rename( Suzano = ret.closing.prices)

vale = l.out$df.tickers[l.out$df.tickers$ticker == "VALE3.SA",] %>% 
  na.omit() %>% 
  select( ret.closing.prices) %>% 
  rename( vale = ret.closing.prices)

vivo = l.out$df.tickers[l.out$df.tickers$ticker == "VIVT3.SA",] %>% 
  na.omit() %>% 
  select( ret.closing.prices) %>% 
  rename( Vivo = ret.closing.prices)

marfrig = l.out$df.tickers[l.out$df.tickers$ticker == "MRFG3.SA",] %>% 
  na.omit() %>% 
  select( ret.closing.prices) %>% 
  rename( Marfrig = ret.closing.prices)

retornos <- cbind(suzano, vale, vivo, marfrig)


retornos <- as.timeSeries(retornos)

Tendo em mãos os retornos computados, o próximo passo é calcular os pesos de cada ativo na carteira ótima. A função tangencyPortfolio possibilita esse cálculo.

portfolio.eficiente <- tangencyPortfolio(retornos, spec = portfolioSpec(), constraints = "LongOnly")
portfolio.eficiente
## 
## Title:
##  MV Tangency Portfolio 
##  Estimator:         covEstimator 
##  Solver:            solveRquadprog 
##  Optimize:          minRisk 
##  Constraints:       LongOnly 
## 
## Portfolio Weights:
##  Suzano    vale    Vivo Marfrig 
##  0.3351  0.3368  0.0785  0.2495 
## 
## Covariance Risk Budgets:
##  Suzano    vale    Vivo Marfrig 
##  0.2911  0.4422  0.0174  0.2493 
## 
## Target Returns and Risks:
##   mean    Cov   CVaR    VaR 
## 0.0037 0.0367 0.0746 0.0500 
## 
## Description:
##  Wed Sep 14 09:33:09 2022 by user: helso
summary(portfolio.eficiente)
## 
## Title:
##  MV Tangency Portfolio 
##  Estimator:         covEstimator 
##  Solver:            solveRquadprog 
##  Optimize:          minRisk 
##  Constraints:       LongOnly 
## 
## Portfolio Weights:
##  Suzano    vale    Vivo Marfrig 
##  0.3351  0.3368  0.0785  0.2495 
## 
## Covariance Risk Budgets:
##  Suzano    vale    Vivo Marfrig 
##  0.2911  0.4422  0.0174  0.2493 
## 
## Target Returns and Risks:
##   mean    Cov   CVaR    VaR 
## 0.0037 0.0367 0.0746 0.0500 
## 
## Description:
##  Wed Sep 14 09:33:09 2022 by user: helso

É possível notar que a carteira ótima deve denotar um maior peso (33,68%) para a empresa vale, enquato Suzano e Marfrig devem ter um peso de 33,51% e 24,95% na carteira, respectivamente.Nota-se facilmente que a empresa Telefônica possui o menor peso (7,85%), indicando que os riscos de investimento na Telefônica não são condizentes com o retorno esperado a esse nível de preços.

O próximo passo é elaborar a fronteira eficiente de Markowitz. A função portfolioFrontier possibilita a execução desta tarefa.

fronteira <- portfolioFrontier(retornos)

# Gráfico com a fronteira eficiente
frontierPlot(fronteira, col = c('blue', 'red'), pch = 20)

## Adicionando informações no gráfico
monteCarloPoints(fronteira, mcSteps = 5000, pch = 20, cex = 0.25 )

A fronteira ótima permite concluir que, se o agente é averso ao risco, então a sua posição comprada deve assumir um stop loss de cerca de 2,75% do valor investido, o que corresponde à exposição mínima ao risco indicada na fronteira. Em contrapartida, se o agente é propenso ao risco, então é possível estipular um stop loss de aproximadamente 6,25% da sua posição comprada, o que corresponde ao risco que atribuiria o maior retorno esperado da carteira.