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

Utilize Python para calcular as maiores quedas de um ativo em um determinado período de tempo.

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:

\[ \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.

Subscribe to Rafael Quintanilha

Don’t miss out on the latest articles. Sign up now to get access to the library of members-only articles.
john@example.com
Subscribe