Opiniões nas Volatilidades

Bernardo Reckziegel 2022-04-04 3 min read

Dando sequência aos posts anteriores, hoje mostro como adicionar opiniões nas volatilidades esperadas.

Continuo utilizando o dataset EuStockMarkets (que acompanha o R) para facilitar a replicação dos códigos utilizados:

x <- diff(log(EuStockMarkets))
head(x)
##               DAX          SMI          CAC         FTSE
## [1,] -0.009326550  0.006178360 -0.012658756  0.006770286
## [2,] -0.004422175 -0.005880448 -0.018740638 -0.004889587
## [3,]  0.009003794  0.003271184 -0.005779182  0.009027020
## [4,] -0.001778217  0.001483372  0.008743353  0.005771847
## [5,] -0.004676712 -0.008933417 -0.005120160 -0.007230164
## [6,]  0.012427042  0.006737244  0.011714353  0.008517217

Vamos assumir que um dos modelos proprietários do time de gestão aponte para os seguintes retornos esperados: DAX, \(+15\%\); SMI, \(+10\%\); CAC, \(+10\%\) e FTSE, \(+5\%\).

A gestora gostaria de construir um portfolio que leve em conta essas informações - subjetivas - mas sem deixar de lado o controle de risco, em particular, da volatilidade. Nesse caso, assumo que a gestora deseje utilizar a volatilidade um portfolio equal-weighted como referência:

ref_vol_model <- x %*% rep(0.25, 4)
vol_model <- stats::sd(ref_vol_model) * sqrt(252) 

paste0(round(100 * vol_model, 2), "%")
## [1] "13.21%"

Ou seja, um dos objetivos é “ancorar” a volatilidade em torno de \(13\%\) ao ano.

Para incorporar essas visões de mundo, é necessário criar opiniões nos retornos esperados e nas volatilidades. Esse processo é realizado com a família de funções view_on_*() que fazem parte do pacote ffp:

library(ffp)

# Subjective Valuation
valuation <- {(1 + c(0.15, 0.1, 0.1, 0.05)) ^ (1 / 252)} - 1

# Views Constructor
view_return     <- view_on_mean(x = x, mean = valuation)
view_volatility <- view_on_volatility(x = ref_vol_model, vol = stats::sd(ref_vol_model))

Para combinar múltiplas opiniões, o pacote ffp disponibiliza a função bind_views:

views <- bind_views(view_return, view_volatility)
views
## # ffp view
## Type:  Multiple Views
## Aeq :  Dim 5 x 1859 
## beq :  Dim 5 x 1 
## A :  Dim 0 x 1 
## b :  Dim 0 x 1

Lembre-se que em entropy-pooling, cada opinião entra como uma restrição linear no problema da Entropia Mínima Relativa (EMR). Em nosso caso, temos \(4\) opiniões para os retornos esperados e \(1\) opinião para volatilidade. Assim, as matrizes Aeq e beq contêm \(5\) linhas cada, uma para cada restrição.

O vetor de probabilidades ótimo - \(p^*\) - que cria a menor distorção possível em relação as probabilidades equal-weighted (uma prior que emerge naturalmente) é calculado com a função entropy_pooling:

prior <- rep(1 / nrow(x), nrow(x))
ep <- entropy_pooling(p = prior, Aeq = views$Aeq, beq = views$beq, solver = "nlminb")

O método autoplot está disponível para visualização de objetos da classe ffp:

library(ggplot2)

autoplot(ep) + 
  scale_color_viridis_c(option = "C", end = 0.75) + 
  labs(title    = "Distribuição de Probabilidades Posteriores", 
       subtitle = "Opiniões na Volatilidade e Retornos Esperados", 
       x        = NULL, 
       y        = NULL)

Os momentos condicionais - que acomodam as visões da gestora - são calculados com ffp_moments:

cond_moments <- ffp_moments(x = x, p = ep)
cond_moments
## $mu
##          DAX          SMI          CAC         FTSE 
## 0.0005547647 0.0003782865 0.0003782865 0.0001936305 
## 
## $sigma
##               DAX          SMI          CAC         FTSE
## DAX  1.063495e-04 6.746852e-05 8.355328e-05 5.284606e-05
## SMI  6.746852e-05 8.595718e-05 6.308962e-05 4.343771e-05
## CAC  8.355328e-05 6.308962e-05 1.214515e-04 5.720668e-05
## FTSE 5.284606e-05 4.343771e-05 5.720668e-05 6.292183e-05

Um teste rápido mostra que as opiniões para os retornos esperados foram respeitados durante o processo de otimização:

(1 + cond_moments$mu) ^ 252 - 1
##  DAX  SMI  CAC FTSE 
## 0.15 0.10 0.10 0.05

Assim como as expectativas em relação a volatilidade:

vol_results <- c(stats::sd(ref_vol_model), sqrt(ffp_moments(x = ref_vol_model, ep)$sigma))
names(vol_results) <- c("Prior", "Posterior")
vol_results * sqrt(252)
##     Prior Posterior 
## 0.1321068 0.1323336

Obviamente, a gestora poderia utilizar outros modelos para estimar a volatilidade, ao invés de stats::sd: GARCH, TVPVAR, FAVAR, etc. seriam igualmente válidos e provavelmente se sairíam melhores out-of-sample.

Uma vez que os momentos condicionais tenham sido computados, o impacto ex-ante das opiniões no ponto de ótimo é estimado num piscar de olhos via programação quadrática e/ou cônica.

Chegaremos lá…