Visão geral e motivação

Este trabalho é dedicado ao curso de Introdução à Ciência de Dados no Mestrado Profissional em Economia e Finanças (MFEE) da FGV - RIO, ministrado pelo professor Rafael Martins.

Nesse estudo em específico, será estudado, através de dados e análises estatísticas, o resultado de uma carteira que utiliza como base a Teoria Moderna do Portfolio na escolha dos pesos dos ativos. Para efeito de comparação, a carteira vai ser comparada com outra que pondera igualmente os ativos.

AVISO: NADA DO QUE SERÁ EXPOSTO NESSE TRABALHO SE TRATA DE UMA RECOMENDAÇÃO DE INVESTIMENTO.

Bibliotecas utilizadas

Para realizar esse estudo, foram utilizados alguns pacotes em R. Antes de iniciar a busca dos dados e as análises, é necessário importar os mesmos.

library('tidyquant') #usado para buscar cotações e dados financeiros
library('tidyverse') #pacote para tratamento de dados
library('xts') #pacote de manipulação de objetos do tipo XTS
library('PerformanceAnalytics') # pacote com diversas funções de financas
library('IntroCompFinR') #pacote com mais funções de finanças
library('fPortfolio') #pacote de fronteira eficiente
library("plotly") #pacote para criação de gráficos

Obtenção dos dados para o estudo

Parâmetros utilizados

Primeiramente, serão definidos os principais parâmetros que vão ser utilizadas. Para esse estudo, foi delimitado o período de análise, quais serão os ativos que vão compor a carteira e o rendimento diário do ativo livre de risco (no caso do mercado brasileiro, será usado o DI do período)

data_inicio = '2018-01-01' #data de início da análise

data_fim = '2022-06-30' #data de fim da análise

tickers = c('VALE3.SA' , 'ITUB3.SA' , 'RENT3.SA' , 'ELET3.SA' , 'VIVT3.SA') #ações que serão analisadas

di_diario_periodo = 0.000218633892891118 #retorno diário médio do DI durante o período (fonte: https://www.b3.com.br/pt_br/market-data-e-indices/indices/indices-de-segmentos-e-setoriais/serie-historica-do-di.htm)

Download das cotações e tratamento dos dados.

Dadas as varíaveis do estudo, será feito a importação das cotações diárias dos ativos em questão. Para isso, a biblioteca tidyquant vai ser utilizada para realizar essa busca, puxando os dados diretamente do site Yahoo Finance.

Após o download, serão feitos alguns tratamentos para melhor organização dos dados.

cotacoes = tq_get(tickers , from = data_inicio , to = data_fim) %>% #baixando as cotações usando tidyquant
  dplyr::ungroup() %>% #resolve um pequeno bug da base de dados
  dplyr::select(date , symbol , adjusted) %>% #seleciona as colunas que nos interessam
  tidyr::pivot_wider(names_from = symbol, values_from = adjusted) #coloca os ativos em forma de colunas e os valores de preço ajustado nas linhas
cotacoes %>% rmarkdown::paged_table()

Cálculo da série de retornos diários

Com as cotações presentes, calcularemos o retorno discreto para cada ação, criando assim uma série histórica de retorno (mostrando quanto cada ação rendeu para cada dia no período analisado).

Esse objeto que será criado vai ser do tipo XTS (eXtensible Time Series), formato que será necessário na utilização de pacotes mais a frente.

retornos_ativos = xts::as.xts(cotacoes %>%
                dplyr::select(-date), order.by = cotacoes$date) %>%
  PerformanceAnalytics::Return.calculate(. ,method = 'discrete') %>%
  na.omit()
retornos_ativos %>% as.data.frame(.)%>% rmarkdown::paged_table()

Criação da Fronteira Eficiente

Retornos esperados e Volatilidade diária

Para obtermos a Fronteira Eficiente e, consequentemente, a carteira ótima (carteira que maximiza o sharpe) , será necessário calcular os retornos esperados, a volatilidade diária e a matriz de covariância dos ativos.

Para o retorno esperado, será calculado a média da série de retornos. Já para a volatilidade, será obtida através do desvio padrão desses retornos.

#media e desvio padrao das acoes
media_desvpad = matrix(data = NA,
                         nrow = length(tickers), 
                         ncol = 2) 

for(i in c(1:length(tickers))) {
  
  media_desvpad[i,1] = mean(retornos_ativos[,i]) 
  media_desvpad[i,2] = sd(retornos_ativos[,i])
  
  }

media_desvpad = as.data.frame(media_desvpad)

 media_desvpad = media_desvpad %>% rename(Retorno = V1) %>%
   rename(Desv = V2)
 
rownames(media_desvpad) = tickers

 #montando matriz de covariancia 
 
 cov_acoes = cov(retornos_ativos)
#media e desvio padrao das acoes
media_desvpad
##               Retorno       Desv
## VALE3.SA 0.0011901301 0.02602570
## ITUB3.SA 0.0001490917 0.01980024
## RENT3.SA 0.0012833768 0.03029923
## ELET3.SA 0.0016505981 0.03605490
## VIVT3.SA 0.0005741308 0.01801585
#covariancia
cov_acoes
##              VALE3.SA     ITUB3.SA     RENT3.SA     ELET3.SA     VIVT3.SA
## VALE3.SA 0.0006773370 0.0001690137 0.0002428833 0.0002873608 0.0001043847
## ITUB3.SA 0.0001690137 0.0003920497 0.0002975812 0.0003440513 0.0001274301
## RENT3.SA 0.0002428833 0.0002975812 0.0009180433 0.0004939854 0.0001658424
## ELET3.SA 0.0002873608 0.0003440513 0.0004939854 0.0012999557 0.0002092093
## VIVT3.SA 0.0001043847 0.0001274301 0.0001658424 0.0002092093 0.0003245709

Visualização da Fronteira Eficiente

Com todos os dados encontrados até então, será possível construir a Fronteira Eficiente de Markowiz. Para facilitar a construção, será usado o pacote fPortfolio.

fronteira_eficiente = fPortfolio::portfolioFrontier(data =as.timeSeries(retornos_ativos))

frontierPlot(fronteira_eficiente,
             pch = 20,
             cex = 1,
             type = "o",
             lwd = 2)


monteCarloPoints(fronteira_eficiente,
                 mcSteps = 5000, 
                 pch = 20,
                 cex = 0.1,
                 col = "blue")

Encontrando a carteira ótima

Após encontrar a Fronteira Eficiente, é possível encontrar a carteira ótima. Essa carteira é aquela que maximiza o sharpe, ou seja, apresenta a melhor relação risco x retorno.

Para isso, será utilizado o pacote IntroCompFinR.

 carteira_otima = tangency.portfolio(
   er = media_desvpad$Retorno, #retorno esperado
   cov.mat = cov_acoes, #matriz de covariancia
   risk.free = di_diario_periodo, #taxa livre de risco da economia
   shorts = FALSE #parâmetro que proíbe o algoritmo de montar posições vendidas (shorts) na carteira
 )
 
pesos_otimos = carteira_otima[["weights"]]
#pesos otimos (VALE3, ITUB3, RENT3, ELET3 e VIVT3)
pesos_otimos
## [1] 0.422068 0.000000 0.237144 0.303260 0.037527
#Retorno esperado carteira
carteira_otima[["er"]]
## [1] 0.001328767
#Desvio padrão
carteira_otima[["sd"]]
## [1] 0.0222835

Comparação das carteiras

Retornos da carteira ótima

Após encontrar os pesos dos repectivos ativos para a criação da carteira ótima, será possível fazer simulações com esse portfolio.

Primeiramente, é necessário calcular a série de retornos diários dessa carteira ótima. Para isso, basta multiplicar o retorno de cada ativo pelo respectivo peso. Além disso, vai ser considerado um rebalanceamento mensal da carteira, fazendo com que a proporção dos ativos na carteira volte à alocação ótima sempre no início do mês (simulando, por exemplo, um investidor rebalanceando sua carteira pessoal assim que receber seu salário).

Para auxiliar no cálculo, será utilizado o pacote PerformanceAnalytics.

retornos_carteira = Return.portfolio(
  R = retornos_ativos , #XTS dos retornos dos ativos
  weights = pesos_otimos, #vetor com os pesos da carteira otima
  rebalance_on = 'months'#parametro que define a periodicidade do rebalanceamento
)
## Warning in Return.portfolio.geometric(R = R, weights = weights, wealth.index =
## wealth.index, : The weights for one or more periods do not sum up to 1: assuming
## a return of 0 for the residual weights
retornos_carteira %>% as.data.frame(.)%>% rmarkdown::paged_table()

Tendo em mãos as rentabilidades diárias, é possível calcular o retorno acumulado da carteira. Para visualizar o resultado, o pacote Plotly será usado.

chart.CumReturns(R = retornos_carteira , wealth.index = FALSE , legend.loc = 'topright' , plot.engine = "plotly")

Retornos de uma carteira de pesos iguais (Equal Weighted)

Para podermos fazer uma comparação justa da carteira ótima com outra carteira, utilizaremos os mesmos ativos para a composição da mesma. No entanto, dessa vez será definida uma estratégia mais simples para a escolha das proporções das ações, fazendo com que todas as ações tenham o mesmo peso (Equal Weighted).

Assim como foi feito para a carteira ótima, será necessário calcular a série de retornos da carteira de pesos iguais. Nesse caso também será mantido o rebalanceamento mensal.

retornos_ew = Return.portfolio(
  R = retornos_ativos , 
  weights = c(0.2 , 0.2 , 0.2 , 0.2 , 0.2),
  rebalance_on = 'months'
)
retornos_ew %>% as.data.frame(.)%>% rmarkdown::paged_table()

Criação de um XTS para a comparação

Após todos os tratamentos, será criado um único objeto XTS que auxiliará na criação dos gráficos e análise de ambas as carteiras.

retornos_carteiras = merge.xts(retornos_carteira , retornos_ew , join="inner")

colnames(retornos_carteiras) <- c("Carteira Otima", "Carteira EW")
retornos_carteiras %>% as.data.frame(.)%>% rmarkdown::paged_table()

Gráficos

Tendo criado um único objeto XTS, serão criados gráficos e estatísticas para auxiliar na comparação das carteiras.

Rentabilidade Acumulada

chart.CumReturns(retornos_carteiras , legend.loc = 'topright' , plot.engine = 'plotly')

Correlação

chart.Correlation(retornos_carteiras)

Boxplot

chart.Boxplot(retornos_carteiras)

Sharpes

#carteira otima
SharpeRatio(
  R = retornos_carteiras$`Carteira Otima` ,
  Rf = di_diario_periodo , 
  FUN = 'StdDev'
)
##                               Carteira Otima
## StdDev Sharpe (Rf=0%, p=95%):     0.04896323
#Carteira pesos iguais
SharpeRatio(
  R = retornos_carteiras$`Carteira EW` ,
  Rf = di_diario_periodo , 
  FUN = 'StdDev'
)
##                               Carteira EW
## StdDev Sharpe (Rf=0%, p=95%):  0.03941626

Volatilidades anualizadas

#carteira otima
StdDev.annualized(retornos_carteiras$`Carteira Otima` , scale=252)
##                               Carteira Otima
## Annualized Standard Deviation      0.3506752
#Carteira pesos iguais
StdDev.annualized(retornos_carteiras$`Carteira EW` , scale=252)
##                               Carteira EW
## Annualized Standard Deviation   0.2888002

Gráfico de Underwater

chart.Drawdown(retornos_carteiras , plot.engine = 'plotly')

Conclusão

Tendo em vista os resultados obtidos, é possível observar que a alocação que segue o modelo de Markowitz tem se mostrado possuir um melhor desempenho do que a de pesos iguais. Por mais que a carteira ótima tenha um comportamento mais arriscado (maior volatilidade anualizada e maiores drawdowns), o excesso de retorno que ela possui em relação à carteira equal weighted acabou compensando esse aumento de risco (se traduzindo em um maior sharpe).

No entanto, é importante ressaltar que o modelo de Markowitz tem suas falhas. Da forma como ele foi exposto nesse trabalho, foi levado em consideração que o retorno esperado de cada ativo se dá pela média do retorno histórico da ação. Isso acaba não sendo verdade, tendo em vista os que retornos passados não são garantia de retornos futuros. Além disso, é possível observar que a carteira ótima desconsiderou uma possível alocação em ITUB3, tendo em vista que a mesma apresentou um retorno bem inferior aos demais ativos da carteira. Porém, o futuro desempenho do ativo pode não ter sido claro no inicio da janela estudada, podendo criar assim um possível Hindsight Bias na simulação.

De forma geral, o estudo visa mostrar que a Teoria Moderna do Portfolio, quando bem utilizada, pode auxiliar os investidores a fazer uma alocação mais eficiente de sua carteira, trazendo desempenhos melhores do que alocações mais simples. Porém, como qualquer modelo, a teoria não é perfeita e suas limitações devem ser bem conhecidas para melhor utilizá-la.