QuantBrasil

Entenda o Drawdown e Calcule essa Medida de Volatilidade para Qualquer Ativo

Rafael Quintanilha
Por Rafael Quintanilha
28 dezembro, 2020
Compartilhar:

O drawdown é uma importante medida de volatilidade para os investidores. De forma simples, o drawdown é calculado como a porcentagem de quanto um ativo, fundo ou portfólio caiu em relação ao seu topo.

Suponha que o Ibovespa esteja cotado a 100.000 pontos em uma determinada data. Durante os próximos dias, ele cai para 90.000 sem superar a marca dos 100.000 pontos. O drawdown nesse período foi de 10%.

Agora, se depois de alcançar os 90.000 pontos o Ibovespa suba para 115.000 pontos, o próximo drawdown será calculado utilizando o mais novo topo. Nesse caso, se ele cair para 100.000 logo em seguida, o drawdown será de:

(115.000100.000)115.000×10013% \dfrac{(115.000 - 100.000)}{115.000} \times 100 \approx 13\%

A importância do drawdown

O drawdown é bastante utilizado para medir a volatilidade, ou o risco, de um determinado investimento. Se dois ativos, A e B, possuem o mesmo retorno ao longo de um período, mas o drawdown de A é menor que o de B, o ativo A tende a ser preferível.

A discussão entre volatilidade x risco é extensa e está além do escopo desse artigo. No entanto, como exemplos, se um investimento observa quedas maiores, o investidor incorre no risco:

  • de ter uma chamada de margem (caso alavancado),
  • de encerrar o investimento prematuramente antes da recuperação (comum em fundos de investimento),
  • e de precisar resgatar em um momento de forte queda (devido a uma emergência ou até mesmo para a aposentadoria).

No caso específico de uma estratégia de trade, um menor drawdown significa uma menor exposição ao risco, e portanto minimizá-lo pode ser interessante mesmo que a rentabilidade da estratégia caia.

O que é melhor: uma estratégia A com rentabilidade de 20% e um drawdown de 15%, ou uma estratégia B com rentabilidade de 25% e drawdown de 40%? Mesmo que a resposta correta não exista, uma vez que é dependente da gestão de risco e perfil de cada pessoa, calcular o drawdown é de suma importância para qualquer investimento.

Calculando o drawdown do Ibovespa

Nossa primeira tarefa será calcular o drawdown máximo do Ibovespa em um determinado período. É importante entender o termo drawdown máximo: ele é a medida do maior drawdown ocorrido no período estabelecido. No exemplo anterior, a primeira queda foi de 100.000 para 90.000 (drawdown de 10%), e depois de 115.000 para 100.000 (drawdown de 13%). Um investidor analisando esse período específico observaria que o drawdown máximo foi de 13%.

O primeiro passo é importar as bibliotecas que vamos utilizar e baixar os preços de fechamento do Ibovespa em 2020 utilizando o Yahoo Finance. Note que o ticker do Ibovespa no yfinance é ^BVSP. Os símbolos podem ser pesquisados diretamente na plataforma.

# %%capture means we suppress the output
%%capture

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

!pip install yfinance 
import yfinance as yf
data = yf.download("^BVSP", start="2020-01-01", end="2020-12-31").copy()[['Adj Close']]
data.head() # returns the first 5 rows of the dataframe
Adj Close
Date
2020-01-02118573.0
2020-01-03117707.0
2020-01-06116878.0
2020-01-07116662.0
2020-01-08116247.0

Em seguida vamos utilizar a função cummax para criar uma nova coluna com o valor máximo de Adj Close até o elemento em questão. Repare a diferença entre cummax e max: enquanto cummax retorna, para cada elemento, o valor máximo anterior ou igual a ele, max retorna o valor máximo na coluna inteira, independente de ter ocorrido antes ou depois do elemento.

data["Max"] = data['Adj Close'].cummax()
data.head(20)
Adj CloseMax
Date
2020-01-02118573.0118573.0
2020-01-03117707.0118573.0
2020-01-06116878.0118573.0
2020-01-07116662.0118573.0
2020-01-08116247.0118573.0
2020-01-09115947.0118573.0
2020-01-10115503.0118573.0
2020-01-13117325.0118573.0
2020-01-14117632.0118573.0
2020-01-15116414.0118573.0
2020-01-16116704.0118573.0
2020-01-17118478.0118573.0
2020-01-20118862.0118862.0
2020-01-21117026.0118862.0
2020-01-22118391.0118862.0
2020-01-23119528.0119528.0
2020-01-24118376.0119528.0
2020-01-27114482.0119528.0
2020-01-28116479.0119528.0
2020-01-29115385.0119528.0

Agora que temos o valor do último topo (Max) para cada linha, podemos calcular o drawdown (em %):

data["Delta"] = data["Max"] - data["Adj Close"]
data["Drawdown"] = 100 * (data["Delta"] / data["Max"])
data.head(20)
Adj CloseMaxDeltaDrawdown
Date
2020-01-02118573.0118573.00.00.000000
2020-01-03117707.0118573.0866.00.730352
2020-01-06116878.0118573.01695.01.429499
2020-01-07116662.0118573.01911.01.611665
2020-01-08116247.0118573.02326.01.961661
2020-01-09115947.0118573.02626.02.214669
2020-01-10115503.0118573.03070.02.589122
2020-01-13117325.0118573.01248.01.052516
2020-01-14117632.0118573.0941.00.793604
2020-01-15116414.0118573.02159.01.820819
2020-01-16116704.0118573.01869.01.576244
2020-01-17118478.0118573.095.00.080119
2020-01-20118862.0118862.00.00.000000
2020-01-21117026.0118862.01836.01.544648
2020-01-22118391.0118862.0471.00.396258
2020-01-23119528.0119528.00.00.000000
2020-01-24118376.0119528.01152.00.963791
2020-01-27114482.0119528.05046.04.221605
2020-01-28116479.0119528.03049.02.550867
2020-01-29115385.0119528.04143.03.466133

Pronto! O código acima nos diz qual o drawdown atual do ativo, dentro do período selecionado, em qualquer momento do tempo. Assim, podemos rapidamente dizer que o drawdown do Ibovespa em 2020, no dia 27 de Janeiro de 2020, era de 4,22%.

Como fazemos então para calcular o drawdown máximo no período?

max_drawdown = data["Drawdown"].max()
max_drawdown
46.815808848136

Simples assim. O drawdown máximo do Ibovespa em 2020 foi de impressionantes 46,18% (2020 não foi pra amadores!)

Encontrando o período de máximo drawdown no gráfico

Como sempre, faz parte das nossas análises facilitar o entendimento com a melhor visualização possível. Vamos então identificar, via código, os pontos que compreenderam o drawdown máximo de 2020 e marcá-los em um gráfico.

# Get the position of the point with maximum Drawdown value
bottom_day = np.argmax(data['Drawdown'])
bottom_index = data[['Drawdown']].index.get_loc(bottom_day)
print(bottom_day, bottom_index, sep="\n")
2020-03-23 00:00:00 54

Repare que nós utilizamos argmax para retornar o índice (no caso, o dia) onde observamos o maior drawdown. Esse dia é o fundo do mercado e foi observado na linha 55 (índice número 54).

Para identificar o topo, vamos achar o valor máximo do Ibovespa na data do fundo, e buscar quando foi a primeira ocorrência desse preço.

# Get the position of the point with peak value before bottom
max_value = data.iloc[bottom_index]['Max']
top_day = (data['Max'] == max_value).idxmax()
top_index = data[['Max']].index.get_loc(top_day)
print(top_day, top_index, sep="\n")
2020-01-23 00:00:00 15

Dessa vez usamos idxmax para retornar a primeira ocorrência de quando o index foi igual ao valor máximo observado anteriormente ao fundo estabelecido. O topo foi definido no dia 23 de Janeiro de 2020, no 16º pregão do ano.

Plotando os pontos de topo e fundo

Com os pontos em mãos, podemos utilizar o matplotlib para visualizar esse período no gráfico:

data["Adj Close"].plot(
    marker='o', 
    markerfacecolor="red", 
    markevery=[top_index, bottom_index],
)

Veja como a visualização nos ajuda a confirmar que os pontos marcados em vermelho foram, de fato, os pontos que compreenderam a maior queda do Ibovespa durante o ano de 2020.

Calculando o drawdown de vários anos

Nosso código está pronto e é capaz de nos retornar o drawdown em qualquer período, para qualquer ativo. Vamos facilitar ainda mais a nossa vida e escrever um código que nos permita analisar o drawdown máximo por ano em um determinado período.

O primeiro passo é transformar o cálculo do drawdown máximo em uma função:

def get_drawdown(data, column = "Adj Close"):
  data["Max"] = data[column].cummax()
  data["Delta"] = data['Max'] - data[column]
  data["Drawdown"] = 100 * (data["Delta"] / data["Max"])
  max_drawdown = data["Drawdown"].max()
  return max_drawdown

Agora, vamos baixar as cotações do Ibovespa dos últimos 5 anos:

start_date = "2015-01-01"
end_date = "2020-12-31"
data = yf.download("^BVSP", start=start_date, end=end_date).copy()[['Adj Close']]
data
Adj Close
Date
2015-01-0248512.0
2015-01-0547517.0
2015-01-0648001.0
2015-01-0749463.0
2015-01-0849943.0
......
2020-12-22116348.0
2020-12-23117857.0
2020-12-28119051.0
2020-12-29119475.0
2020-12-30119306.0

1480 rows × 1 columns

Nós podemos facilmente filtrar os dados por ano:

data_in_2019 = data[data.index.year == 2019]
data_in_2019
Adj Close
Date
2019-01-0291012.0
2019-01-0391564.0
2019-01-0491841.0
2019-01-0791699.0
2019-01-0892032.0
......
2019-12-20115121.0
2019-12-23115863.0
2019-12-26117203.0
2019-12-27116534.0
2019-12-30115964.0

247 rows × 1 columns

Para descobrir o drawdown anual do período estabelecido, precisamos realizar os seguintes passos:

  1. Identificar quais os anos estão compreendidos entre nossas datas inicial e final;
  2. Iterar sobre cada um dos anos e filtrar apenas os dados para aquele ano;
  3. Calcular o drawdown para o dataframe filtrado.

Vamos construir uma lista com os anos entre start_date e end_date:

from datetime import datetime

start = datetime.strptime(start_date, "%Y-%m-%d")
end = datetime.strptime(end_date, "%Y-%m-%d")
years = range(start.year, end.year + 1)

list(years)
[2015, 2016, 2017, 2018, 2019, 2020]

Com a lista definida, vamos iterar sobre cada ano, filtrar os dados necessários e adicionar o drawdown do ano em um dicionário:

drawdowns = {}

for year in years:
  yearly_data = data[data.index.year == year].copy()
  yearly_drawdown = get_drawdown(yearly_data)
  drawdowns[year] = yearly_drawdown

drawdowns
{2015: 25.583959208985046, 2016: 12.035425490951097, 2017: 12.00544517175462, 2018: 20.35070105986104, 2019: 10.00160009600576, 2020: 46.815808848136}

Pronto! Temos todos os drawdowns do Ibovespa nos últimos 5 anos de forma extremamente simples.

Calculando o drawdown de diversos ativos

Nós mencionamos anteriormente que o drawdown pode ser utilizado como uma medida comparativa entre duas estratégias de investimento. Sendo assim, é bastante útil colocar o drawdown em contexto.

Agora que calculamos o drawdown do Ibovespa nos últimos 5 anos, podemos repetir o raciocínio para diversos outros papeis da bolsa. No entanto, em vez de fazermos isso manualmente, vamos escrever um código que nos possibilite compará-los de forma bastante objetiva:

# Arbitrary list of stocks
tickers = [
    "^BVSP", 
    "PETR4.SA", 
    "VALE3.SA", 
    "VVAR3.SA", 
    "EQTL3.SA", 
    "LREN3.SA", 
    "GOLL4.SA", 
    "BTOW3.SA", 
    "WEGE3.SA", 
    "ITUB4.SA", 
    "BBAS3.SA", 
    "B3SA3.SA", 
    "MOVI3.SA", 
    "AZUL4.SA", 
    "IRBR3.SA"
]
stocks = yf.download(tickers, start=start_date, end=end_date).copy()['Adj Close']
stocks
AZUL4.SAB3SA3.SABBAS3.SABTOW3.SAEQTL3.SAGOLL4.SAIRBR3.SAITUB4.SALREN3.SAMOVI3.SAPETR4.SAVALE3.SAVVAR3.SAWEGE3.SA^BVSP
Date
2015-01-02NaN7.69260316.42970321.1403013.88235214.990000NaN13.51654410.508009NaN8.68329317.2728866.54355610.32350748512.0
2015-01-05NaN7.48228616.08877920.4646003.73411714.850000NaN13.58449610.215857NaN7.94113517.0131406.54355610.39383547517.0
2015-01-06NaN7.55508916.31365220.5224993.94447115.210000NaN13.80422410.333539NaN7.68137817.6949676.54355610.23967448001.0
2015-01-07NaN7.85438017.03176720.3197993.91058914.550000NaN14.30365310.509348NaN8.04318118.3443267.08243610.12237549463.0
2015-01-08NaN7.74922217.08979419.6537993.88235214.270000NaN14.52744010.521830NaN8.56269518.5391297.08243610.29326649943.0
................................................
2020-12-2235.27000059.73788838.29000175.97000122.65000023.2999997.1931.23518043.16000019.20805727.28000186.94000215.94000072.820000116348.0
2020-12-2337.70000159.45050038.91999875.59999822.67000024.5400017.2431.92485443.72000119.82350727.95000187.36000116.12999972.620003117857.0
2020-12-2837.77000060.94691839.34999877.04000123.04000124.2500008.1132.15474343.97000119.89299628.18000087.30999816.59000075.500000119051.0
2020-12-2937.66999861.26403839.11999975.69999722.98000024.4800008.2132.08477844.13000120.03196728.27000087.07000016.58000075.150002119475.0
2020-12-3039.29999961.42260038.79999975.61000123.16000024.9400018.1831.61500043.54000120.49852028.34000087.44999716.16000075.739998119306.0

1489 rows × 15 columns


O raciocínio é o mesmo: iteraremos sobre a lista de ativos e calcularemos o drawdown anual, armazenando essa informação em um dicionário. Eis o código:

all_drawdowns = {}

for ticker in tickers:
  data = stocks[[ticker]]
  drawdowns = {}
  for year in years:
    yearly_data = data[data.index.year == year].copy()
    yearly_drawdown = get_drawdown(yearly_data, column = ticker)
    drawdowns[year] = yearly_drawdown
  all_drawdowns[ticker] = drawdowns

all_drawdowns
{'^BVSP': {2015: 25.583959208985046, 2016: 12.035425490951097, 2017: 12.00544517175462, 2018: 20.35070105986104, 2019: 10.00160009600576, 2020: 46.815808848136}, 'PETR4.SA': {2015: 55.370760542208906, 2016: 38.864638802102405, 2017: 27.656924921683206, 2018: 46.95510222158168, 2019: 17.24379544534969, 2020: 63.35605336711879}, 'VALE3.SA': {2015: 56.08258065080083, 2016: 35.56986161485101, 2017: 28.093024393482068, 2018: 20.418006567945692, 2019: 25.93054585334471, 2020: 40.550904946181845}, 'VVAR3.SA': {2015: 82.87937752873043, 2016: 27.113703092645718, 2017: 27.897840647678603, 2018: 50.44702954161212, 2019: 36.000001430511475, 2020: 75.36057659242984}, 'EQTL3.SA': {2015: 14.331557740431938, 2016: 11.292590784644522, 2017: 11.805536665040066, 2018: 21.719273446438702, 2019: 11.631328831997262, 2020: 40.9665448071925}, 'LREN3.SA': {2015: 28.140179772014978, 2016: 21.70930481448726, 2017: 13.185765146517658, 2018: 27.71149584936101, 2019: 10.916516169469098, 2020: 50.61581662818844}, 'GOLL4.SA': {2015: 84.28665286712683, 2016: 53.83693244372522, 2017: 35.822224087185326, 2018: 61.02971679584582, 2019: 33.202998881506915, 2020: 85.65941097556318}, 'BTOW3.SA': {2015: 51.53851005883385, 2016: 44.8766768778324, 2017: 37.82624008178883, 2018: 28.102685346893097, 2019: 40.610198199484834, 2020: 45.63492063492063}, 'WEGE3.SA': {2015: 29.04629568162771, 2016: 20.417983053408694, 2017: 14.691519912317894, 2018: 23.65182624442001, 2019: 12.363907200496495, 2020: 46.66773431693059}, 'ITUB4.SA': {2015: 25.684997781423753, 2016: 19.146231930567005, 2017: 15.206064144768739, 2018: 28.817874021335587, 2019: 18.424106664124775, 2020: 45.213991230064984}, 'BBAS3.SA': {2015: 46.03704780847024, 2016: 30.13645556938046, 2017: 27.294988974337496, 2018: 43.24986947539893, 2019: 20.929829022200064, 2020: 58.286981851334964}, 'B3SA3.SA': {2015: 20.408398517908605, 2016: 25.492304232567232, 2017: 13.647302811687819, 2018: 29.13132745018055, 2019: 14.231530205537815, 2020: 42.687741180836376}, 'MOVI3.SA': {2015: nan, 2016: nan, 2017: 39.285931215455165, 2018: 40.5945655236307, 2019: 20.034250511972054, 2020: 65.71809662426533}, 'AZUL4.SA': {2015: nan, 2016: nan, 2017: 17.14285488927662, 2018: 47.19565512966372, 2019: 24.91710126385561, 2020: 83.41611855988668}, 'IRBR3.SA': {2015: nan, 2016: nan, 2017: 6.0834261004924155, 2018: 9.88859148803348, 2019: 15.05290894618084, 2020: 87.41285870315963}}

Os drawdowns estão calculados mas a visualização não é das melhores. Vamos criar um dataframe onde os ativos estarão nas linhas e cada ano será uma nova coluna:

table = pd.DataFrame(columns=years, index=tickers)
table
201520162017201820192020
^BVSPNaNNaNNaNNaNNaNNaN
PETR4.SANaNNaNNaNNaNNaNNaN
VALE3.SANaNNaNNaNNaNNaNNaN
VVAR3.SANaNNaNNaNNaNNaNNaN
EQTL3.SANaNNaNNaNNaNNaNNaN
LREN3.SANaNNaNNaNNaNNaNNaN
GOLL4.SANaNNaNNaNNaNNaNNaN
BTOW3.SANaNNaNNaNNaNNaNNaN
WEGE3.SANaNNaNNaNNaNNaNNaN
ITUB4.SANaNNaNNaNNaNNaNNaN
BBAS3.SANaNNaNNaNNaNNaNNaN
B3SA3.SANaNNaNNaNNaNNaNNaN
MOVI3.SANaNNaNNaNNaNNaNNaN
AZUL4.SANaNNaNNaNNaNNaNNaN
IRBR3.SANaNNaNNaNNaNNaNNaN

O próximo passo é preencher o dataframe com os valores do dicionário calculado anteriormente:

for ticker in all_drawdowns:
  table.loc[ticker] = all_drawdowns[ticker]

table
201520162017201820192020
^BVSP25.58412.035412.005420.350710.001646.8158
PETR4.SA55.370838.864627.656946.955117.243863.3561
VALE3.SA56.082635.569928.09320.41825.930540.5509
VVAR3.SA82.879427.113727.897850.4473675.3606
EQTL3.SA14.331611.292611.805521.719311.631340.9665
LREN3.SA28.140221.709313.185827.711510.916550.6158
GOLL4.SA84.286753.836935.822261.029733.20385.6594
BTOW3.SA51.538544.876737.826228.102740.610245.6349
WEGE3.SA29.046320.41814.691523.651812.363946.6677
ITUB4.SA25.68519.146215.206128.817918.424145.214
BBAS3.SA46.03730.136527.29543.249920.929858.287
B3SA3.SA20.408425.492313.647329.131314.231542.6877
MOVI3.SANaNNaN39.285940.594620.034365.7181
AZUL4.SANaNNaN17.142947.195724.917183.4161
IRBR3.SANaNNaN6.083439.8885915.052987.4129

Muito melhor! O último passo é criar uma coluna para a média simples dos drawdowns e ordenar da menor média para a maior:

table["Average"] = table.mean(axis=1)
table = table.sort_values("Average")
table
201520162017201820192020Average
EQTL3.SA14.331611.292611.805521.719311.631340.966518.624472
^BVSP25.58412.035412.005420.350710.001646.815821.132157
B3SA3.SA20.408425.492313.647329.131314.231542.687724.266434
WEGE3.SA29.046320.41814.691523.651812.363946.667724.473211
LREN3.SA28.140221.709313.185827.711510.916550.615825.379846
ITUB4.SA25.68519.146215.206128.817918.424145.21425.415544
IRBR3.SANaNNaN6.083439.8885915.052987.412929.609446
VALE3.SA56.082635.569928.09320.41825.930540.550934.440821
BBAS3.SA46.03730.136527.29543.249920.929858.28737.655862
MOVI3.SANaNNaN39.285940.594620.034365.718141.408211
BTOW3.SA51.538544.876737.826228.102740.610245.634941.431539
PETR4.SA55.370838.864627.656946.955117.243863.356141.574546
AZUL4.SANaNNaN17.142947.195724.917183.416143.167932
VVAR3.SA82.879427.113727.897850.4473675.360649.949755
GOLL4.SA84.286753.836935.822261.029733.20385.659458.972989

Considerações finais

  • O drawdown é uma forma de expor a volatilidade de um ativo. Note que mesmo em um ano relativamente calmo e direcional para a bolsa como 2019, ativos como GOLL4 e BTOW3 chacoalharam bastante. Em contrapartida, EQTL3 costuma ser bastante estável e mesmo em um ano de queda brutal como 2020, ela teve o menor drawdown da lista;
  • IRBR3, AZUL4 e MOVI3 fizeram seu IPO em 2017 e portanto não possuem drawdown antes disso;
  • A média é apenas uma pista, mas não conta a história toda. Note como IRBR3 possuía uma média extremamente baixa até o ano de 2020, onde apresentou o maior tombo de todos (inclusive maior que as aéreas!)

Agora que já sabemos calcular o drawdown podemos utilizar essa medida nas nossas estratégias de trade. Fique ligado para os próximos posts, onde compareremos estratégias não somente no seu retorno financeiro, mas no seu drawdown máximo.