Momentum ou Reversão à Média?

Introdução

Recentemente, recebi no formato de newsletter de uma casa de análise (a qual sou assinante!) um alerta de cautela. O analista responsável pelo e-mail disparou o alerta que após a subida de 40% no Ibovespa desde a mínima do período de pandemia, deveríamos tomar cuidado com o grau de exposição à bolsa, uma vez que alguma realização estivesse por surgir.

É claro que a recomendação não era para desfazer todas as posições em Bolsa, mas sim, controlá-la se expondo mais (agora) à renda fixa. Pois bem, o argumento apresentado foi o gráfico do retorno acumulado em janela móvel de 4 meses para o IBOV. Ali, ficava claro que alguma realização seria o que o comportamento histórico do índice teria a nos contar.

Assim, tendo em vista o alerta, resolvi fazer um teste para responder a seguinte pergunta: “Seria o retorno acumulado um bom indicador para encontrar movimentos de reversão à média?”

A Metodologia

O teste desenhado para responder a pergunta consiste em: (a) Criar o mesmo indicador de rentabilidade acumulada em janela móvel; (b) Obter dados de média e desvio-padrão móvel para a mesma janela temporal; (c) Normalizar os dados; (d) Operar a reversão a média por critérios objetivos de bandas de abertura e fechamento.

A respeito dessas etapas, para a etapa (a), usamos a equação de retorno em tempo discreto: \[r_t=P_t/P_{t-1}-1\] onde \(r_t\) é o retorno diário; \(P_t\) é o preço de fechamento do ativo no período \(t\); A frequência dos dados é diária.

O retorno acumulado em janela de 4 meses seria: \[r_{4m}=\Pi_{t=1}^{t-m-1}(1+r_t)-1\] onde \(m\) é a janela de 84 dias úteis e \(r_{4m}\) é o retorno acumulado de 4 meses.

Em termos de script, faremos o download dos dados de BOVA11 e VOO, sendo ambos ETFs que replicam o Ibovespa e o SP500 respectivamente.

rm(list=ls())

library(PortfolioAnalytics)
library(xtsanalytics)


Ativos <- c("BOVA11.SA","VOO")

Precos   <- mget_symbols(Ativos, OHLC = "Ad")
## Getting data from yahoo...
## Downloading symbols: BOVA11.SA VOO
## 
## Successfully downloaded symbol: BOVA11.SA
## Successfully downloaded symbol: VOO
## 
## 
## DOWNLOAD SUMMARY:
## =================
## Symbols successfully downloaded: BOVA11.SA VOO 
## Symbols NOT downloaded: 
## 
## Applying na.locf to the matrix...
Precos2  <- Precos[which(index(Precos) >= "2010-09-10"),]
Retornos <- Return.calculate(Precos2)

plot(Retornos, main = "Retorno Diário dos Ativos")

## Cálculo do Retorno Móvel Acumulado
Janela_Movel <- 84

Cum_4m_Bova <- apply.rolling(Retornos[,1], FUN="Return.cumulative", width=Janela_Movel)

Cum_4m_voo  <- apply.rolling(Retornos[,2], FUN="Return.cumulative", width=Janela_Movel)

### Plots dos Retornos Acumulados
plot(Cum_4m_Bova, 
     main = "Retorno Acumulado Móvel de 4 Meses - BOVA11",
     col  = "deeppink3")

plot(Cum_4m_voo, 
     main = "Retorno Acumulado Móvel de 4 Meses - VOO",
     col  = "deeppink3")

Na sequência, realizamos as etapas (b) e (c). Para isso, normalizamos o retorno acumulado através do cálculo da média e desvio-padrão móvel de 4 meses. Ambos são inputados na equação \[z_t=\frac{X_t-\overline{X}_t}{\sigma_t}\] Que deve fornecer agora valores normalizados. Em termos de script, temos:

# Média, desvio-padrão móveis e normalização para BOVA11.
Mean_4m_Bova <- apply.rolling(Cum_4m_Bova, FUN="mean", width=Janela_Movel)
Sd_4m_Bova   <- apply.rolling(Cum_4m_Bova, FUN="sd", width=Janela_Movel)
Norm_4m_Bova <- (Cum_4m_Bova - Mean_4m_Bova)/Sd_4m_Bova

# Média, desvio-padrão móveis e normalização para VOO.
Mean_4m_voo <- apply.rolling(Cum_4m_voo, FUN="mean", width=Janela_Movel)
Sd_4m_voo   <- apply.rolling(Cum_4m_voo, FUN="sd", width=Janela_Movel)
Norm_4m_voo <- (Cum_4m_voo - Mean_4m_voo)/Sd_4m_voo

plot(Norm_4m_Bova, main = "Retorno Normalizado de 4 Meses - BOVA11")

plot(Norm_4m_voo,  main = "Retorno Normalizado de 4 Meses - VOO")

Tendo em vista o comportamento normalizado (que até lembra o de funções trigonométricas com algum ruído adicionado), criamos as seguintes regras operacionais, como etapa (d):

  • Operação de Compra: Compra se \(z_t<-2.5\); Fechamento se \(z_t<|0.5|\)

  • Operação de Venda a Descoberto: Venda (a descoberto) se \(z_t>2.5\); Fechamento se \(z_t<|0.5|\)

Segue aqui o script corresponde ao Backtesting do Resultado operacional dessas regras. A ideia é comprar em momentos que atingimos a banda inferior do gráfico de retorno acumulado normalizado, tendo em vista que quedas acentuadas devam abrir oportunidade para o retorno a média. A outra ideia é vender a descoberto em momentos de elevada subida de preço aguardando uma realização até a média.

  • Backtesting para BOVA11
### Loop de Trading
Limite_Venda_Compra <- 2.0
Limite_Fechamento   <- 0.5

# Filtro para remover dias que 
Norm_4m_Bova_2 <- Norm_4m_Bova[which(index(Norm_4m_Bova) >= "2011-08-15"),]

### Backtesting da Venda

Venda <- matrix(0, nrow = nrow(Norm_4m_Bova_2), ncol = 1)
i <- NULL

for(i in 2:nrow(Norm_4m_Bova_2)){
  
  if(Norm_4m_Bova_2[i,] >= Limite_Venda_Compra){
  
    Venda[i,] = -1   
  }
  
  else if((Venda[i-1,] == -1) & (abs(Norm_4m_Bova_2[i,])) > Limite_Fechamento){
    
    Venda[i,] = -1
  } 
}

Venda_xts <- xts(Venda, order.by = index(Norm_4m_Bova_2))
Venda_Ret <- Retornos[,1]*Venda_xts

Resultado_Venda <- Venda_Ret[which(index(Venda_Ret) >= "2011-08-15"),]
Tabela_Venda    <- cbind(Norm_4m_Bova,Venda_xts,Retornos[,1],Venda_Ret)


f <- (cumprod(1+Resultado_Venda) -1)
plot(f*100, main = "Backtesting de Venda a Descoberto - BOVA11", col = "red")

### Backtesting da Compra

Compra <- matrix(0, nrow = nrow(Norm_4m_Bova_2), ncol = 1)
i <- NULL

for(i in 2:nrow(Norm_4m_Bova_2)){
  
  if(Norm_4m_Bova_2[i,] <= -Limite_Venda_Compra){
    
    Compra[i,] = 1   
  }
  
  else if((Compra[i-1,] == 1) & (abs(Norm_4m_Bova_2[i,])) > Limite_Fechamento){
    
    Compra[i,] = 1
  } 
}

Compra_xts <- xts(Compra, order.by = index(Norm_4m_Bova_2))
Compra_Ret <- Retornos[,1]*Compra_xts

Resultado_Compra <- Compra_Ret[which(index(Compra_Ret) >= "2011-08-15"),]
Tabela_Compra    <- cbind(Norm_4m_Bova,Compra_xts,Retornos[,1],Compra_Ret)


f1 <- (cumprod(1+Resultado_Compra) -1)
plot(f1*100, main = "Backtesting da Compra - BOVA11", col = "dark green")

  • Backtesting para o VOO…
# Filtro para remover dias que 
Norm_4m_voo_2 <- Norm_4m_voo[which(index(Norm_4m_voo) >= "2011-08-15"),]

### Backtesting da Venda

Venda <- matrix(0, nrow = nrow(Norm_4m_voo_2), ncol = 1)
i <- NULL

for(i in 2:nrow(Norm_4m_voo_2)){
  
  if(Norm_4m_voo_2[i,] >= Limite_Venda_Compra){
  
    Venda[i,] = -1   
  }
  
  else if((Venda[i-1,] == -1) & (abs(Norm_4m_voo_2[i,])) > Limite_Fechamento){
    
    Venda[i,] = -1
  } 
}

Venda_xts <- xts(Venda, order.by = index(Norm_4m_voo_2))
Venda_Ret <- Retornos[,2]*Venda_xts

Resultado_Venda <- Venda_Ret[which(index(Venda_Ret) >= "2011-08-15"),]
Tabela_Venda    <- cbind(Norm_4m_voo,Venda_xts,Retornos[,2],Venda_Ret)


f <- (cumprod(1+Resultado_Venda) -1)
plot(f*100, main = "Backtesting de Venda a Descoberto - VOO", col = "red")

### Backtesting da Compra

Compra <- matrix(0, nrow = nrow(Norm_4m_Bova_2), ncol = 1)
i <- NULL

for(i in 2:nrow(Norm_4m_voo_2)){
  
  if(Norm_4m_voo_2[i,] <= -Limite_Venda_Compra){
    
    Compra[i,] = 1   
  }
  
  else if((Compra[i-1,] == 1) & (abs(Norm_4m_voo_2[i,])) > Limite_Fechamento){
    
    Compra[i,] = 1
  } 
}

Compra_xts <- xts(Compra, order.by = index(Norm_4m_voo_2))
Compra_Ret <- Retornos[,2]*Compra_xts

Resultado_Compra <- Compra_Ret[which(index(Compra_Ret) >= "2011-08-15"),]
Tabela_Compra    <- cbind(Norm_4m_voo,Compra_xts,Retornos[,2],Compra_Ret)


f1 <- (cumprod(1+Resultado_Compra) -1)
plot(f1*100, main = "Backtesting da Compra - VOO", col = "dark green")

Conclusão

Podemos ver que em ambas as estratégias, teríamos perdido em torno da metade do capital nas operações (exceto na compra de VOO). Por que isso ocorre? Segue aqui uma possível interpretação:

  • Para que uma média móvel caia, basta que no cálculo do valor entrante seja menor do que o último que está saindo para a sua atualização de cálculo. Dessa forma, períodos de elevada alta podem ser alternados por movimentos de acumulação (mercado andando de lado) para novas altas. Se isso ocorre, os desvios da normalidade seriam basicamente um sinal para apenas aguardar uma nova entrada e operar momentum quando nos aproximássemos da média.

  • Dessa forma, com relação ao e-mail que recebi inicialmente, o único sinal que poderíamos inferir com essa “pernada” de 40% de alta, é que o mercado pode acumular e manter a tendência de alta. É claro, caso não seja atingido por algum fato novo que modifique o ciclo subsequente, o que seria imprevissível de qualquer maneira…

  • Um detalhe importante: Um indicador que erre 90% das vezes é muito melhor do que um indicador que acerte 50% das vezes (desconsiderando o tamanho dos ganhos e das perdas). Afinal, basta fazer o contrário para acertar no primeiro caso, enquanto no segundo é similar a própria aleatoriedade.

Júlio Fernando Costa Santos
Júlio Fernando Costa Santos
Professor of Economics

My research interests include Finance, Macroeconomics and Econometric techniques.

Related