A curva de juros mostra o rendimento que um investidor espera ganhar se emprestar seu dinheiro por um determinado período de tempo.
Com a curva de juros é possível projetar visualmente a tendência da evolução dos juros com o passar dos anos, conforme as condições atuais de mercado.
Desse modo é possível visualizar as diferentes taxas para os múltiplos vencimentos em um único gráfico.
Quem é responsável por calcular a curva de juros?
No Brasil, a instituição responsável por calcular a curva de juros à vista é a Associação Brasileira das Entidades dos Mercados Financeiro e de Capitais ANBIMA.
Qual é a finalidade da curva de juros?
Cabe frisar que a curva de juros considera o comportamento dos juros em relação ao período. Isto é, ela realiza previsões dos rendimentos em investimentos, empréstimos, etc. Isso permite mitigar os riscos envolvidos em cada aplicação financeira.
Por exemplo, uma curva de juros normal se inclina para cima e para a direita à medida que os rendimentos aumentam com o vencimento. Isso indica que as condições de mercado e a economia estão saudáveis e funcionando normalmente, o que permite que os investidores escolham ativos cuja rentabilidade esteja atrelada a juros maiores.
Por outro lado, também é possível que a previsão relacionada à curva de juros não seja otimista. Quando as taxas para prazos mais curtos são mais altas do que aquelas para prazos mais longos,temos uma curva de juros invertida.
Nesse caso, a curva de juros se inclina para baixo e para a direita. Isso pode indicar uma recessão ou um mercado em baixa, podendo sofrer quedas prolongadas nos preços e nos rendimentos dos títulos.
Exemplo usando Python
# Instalando e importando as bibliotecas necessárias
!pip install --upgrade pip
!pip install holidays
!pip install workalendar
from datetime import datetime, date, timedelta
from workalendar.america import Brazil
import requests
from time import sleep
from requests import ConnectTimeout, ReadTimeout
import pandas as pd
import plotly.graph_objects as go
import numpy as np
from io import StringIO
import holidays
import urllib3
urllib3.disable_warnings()
# Busca os feriados no Brasil
data_atual = datetime.today()
ano_atual = data_atual.year
dias_semana = {"SEGUNDA": 0, "TERCA": 1, "QUARTA": 2, "QUINTA": 3, "SEXTA": 4, "SABADO": 5, "DOMINGO": 6}
cal = Brazil()
feriados_brasil = []
for i in range(ano_atual, ano_atual+15):
feriados_brasil_raw = cal.holidays(i)
for j in feriados_brasil_raw:
feriados_brasil.append(j)
feriados_brasil = (list(zip(*feriados_brasil))[0])
feriados_brasil
# Métodos auxiliares
def iterdates(date1, date2):
one_day = timedelta(days = 1)
current = date1
while current < date2:
yield current
current += one_day
def ajustar_data(df):
meses = {'Jan': '01', 'Fev': '02', 'Mar': '03', 'Abr': '04', 'Mai': '05', 'Jun': '06',
'Jul': '07', 'Ago': '08', 'Set': '09', 'Out': '10', 'Nov': '11', 'Dez': '12'}
for mes, numero in meses.items():
df = df.str.replace(mes, numero)
df = df.str.replace(" ", "/")
df = pd.to_datetime(df, format="%d/%m/%Y")
return df
def buscar_vencimento_titulo(titulo):
meses = {'01': 'F', '02': 'G', '03': 'H', '04': 'J', '05': 'K', '06': 'M',
'07': 'N', '08': 'Q', '09': 'U', '10': 'V', '11': 'X', '12': 'Z'}
meses = {y: x for x, y in meses.items()}
ano = "20" + titulo[-2:]
mes = meses.get(titulo[3])
dias = pd.date_range(start= mes + '/1/' + ano, periods=7, freq='BMS')
dias = filter(lambda dia: datetime.fromtimestamp(dia.timestamp()) not in feriados_brasil, dias)
dia = list(dias)[0]
return dia
def buscar_dias_uteis_ate_vencimento_titulo(titulo):
dia_vencimento = buscar_vencimento_titulo(titulo)
start = date.today()
end = dia_vencimento.date()
dias_uteis = sum(1 for day in iterdates(start, end) if day.weekday() not in (dias_semana.get("SABADO"),dias_semana.get("DOMINGO")) and day not in feriados_brasil)
dias_de_semana = sum(1 for day in iterdates(start, end) if day.weekday() not in (dias_semana.get("SABADO"),dias_semana.get("DOMINGO")))
feriados_na_semana = dias_de_semana - dias_uteis
#print(f"Dias úteis entre {start} e {end}: {dias_uteis}")
#print(f"Dias de semana entre {start} e {end}: {dias_de_semana}")
# print(f"Feriados entre {start} e {end}: {feriados_na_semana}")
return dias_uteis
def buscar_proximas_series(anos_futuros=2, anos_anteriores=0):
nomes_series = []
data_atual = datetime.today()
mes_atual = data_atual.month
ano_atual = data_atual.year
meses = {'1': 'F', '2': 'G', '3': 'H', '4': 'J', '5': 'K', '6': 'M',
'7': 'N', '8': 'Q', '9': 'U', '10': 'V', '11': 'X', '12': 'Z'}
# Atualmente não é possível buscar séries passadas
#
# print("Busca dados anos anteriores")
# for i in range(ano_atual - anos_anteriores - 1, ano_atual - 1):
# ano = str(i)
# nomes_series.append("DI1F" + ano[2:4])
# print(nomes_series)
print("Busca dados ano atual")
for i, letra in meses.items():
if int(i) >= int(mes_atual):
nomes_series.append("DI1" + letra + str(ano_atual)[2:4])
#print(nomes_series)
print("Busca dados anos futuros")
for i in range(ano_atual + 1, ano_atual + anos_futuros + 1):
ano = str(i)
nomes_series.append("DI1F" + ano[2:4])
#print(nomes_series)
return nomes_series
def buscar_dados_futuros(titulo):
sleep(0.1)
headers = {'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36'}
url = "https://br.advfn.com/bolsa-de-valores/bmf/{}/historico/mais-dados-historicos".format(titulo)
print("Abrindo " + url)
html = requests.get(url, headers=headers, verify=False, timeout=5)
dif = pd.read_html(html.text, decimal=',', thousands='.')[1]
dif = dif[["Data", "Fechamento"]]
dif = dif.rename(columns={"Fechamento": titulo})
dif[['Data']] = dif[['Data']].apply(lambda x: ajustar_data(x))
dif = dif.set_index("Data")
return dif
# Definindo parâmetros iniciais
anos = 10 #@param {type:"integer"}
semanas = 7 #@param {type:"integer"}
# Buscando dados ADVFN
series_advfn = []
nomes_series = buscar_proximas_series(anos, anos_anteriores)
print(nomes_series)
for nome_serie in nomes_series:
try:
s = buscar_dados_futuros(nome_serie)
s = s.loc[~s.index.duplicated(keep='first')]
series_advfn.append(s)
except ConnectTimeout:
print("Timeout ao buscar dados de " + nome_serie + ". Buscando próxima série...")
except ReadTimeout:
print("Timeout ao buscar dados de " + nome_serie + ". Buscando próxima série...")
# Buscando dados ANBIMA
hoje = date.today().strftime('%d/%m/%Y')
ontem = date.today() - timedelta(days = 1)
ontem = ontem.strftime('%d/%m/%Y')
url = 'https://www.anbima.com.br/informacoes/est-termo/CZ-down.asp'
payload = {'Idioma': 'US', 'Dt_Ref': ontem, 'saida': 'xml'}
from urllib import request, parse
data = parse.urlencode(payload).encode()
req = request.Request(url, data=data) # this will make the method "POST"
with request.urlopen(req) as response:
xml = response.read()
vertices = pd.read_xml(xml, xpath="//TERM_STRUCTURE")
vertices.drop('Indexed', axis=1, inplace=True)
vertices.drop('BEI', axis=1, inplace=True)
vertices.dropna(inplace=True)
vertices = vertices.replace({',': ''}, regex=True)
vertices['Prefixed'] = vertices['Prefixed'].astype(float)
vertices['Business_Day'] = vertices['Business_Day'].astype(int)
vertices.rename({'Prefixed': 'Taxa'}, axis=1, inplace=True)
vertices.rename({'Business_Day': 'DiasUteis'}, axis=1, inplace=True)
# vertices
circular = pd.read_xml(xml, xpath="//CIRCULAR ")
circular = circular.replace({',': ''}, regex=True)
circular['Rate'] = circular['Rate'].astype(float)
circular['Business_Day'] = circular['Business_Day'].astype(int)
circular.rename({'Rate': 'Taxa'}, axis=1, inplace=True)
circular.rename({'Business_Day': 'DiasUteis'}, axis=1, inplace=True)
circular.dropna(inplace=True)
# circular
anbima_df = pd.concat([vertices, circular]).sort_values(by="DiasUteis").drop_duplicates().reset_index(drop=True)
anbima_df.set_index('DiasUteis', inplace=True)
# anbima_df
# Já buscamos os dados, agora é trabalhar neles...
difuturo_por_titulo = pd.concat(series_advfn, axis=1)
difuturo_por_titulo.index = pd.to_datetime(difuturo_por_titulo.index)
# difuturo_por_titulo
dayofweek = difuturo_por_titulo.index.dayofweek
semanal_por_titulo = difuturo_por_titulo.iloc[(dayofweek == 0) | (difuturo_por_titulo.index==difuturo_por_titulo.index.max())].copy()
semanal_por_titulo.sort_index(ascending=False, inplace=True)
# semanal_por_titulo
difuturo_por_titulo = semanal_por_titulo.head(semanas+1)
difuturo_por_titulo_transposto = difuturo_por_titulo.transpose(copy=True)
# difuturo_por_titulo_transposto
difuturo_por_vencimento_transposto = difuturo_por_titulo_transposto.copy(deep=True)
difuturo_por_vencimento_transposto['Vencimento'] = difuturo_por_vencimento_transposto.index
difuturo_por_vencimento_transposto['Vencimento'] = difuturo_por_vencimento_transposto['Vencimento'].apply(lambda x: buscar_vencimento_titulo(x))
difuturo_por_vencimento_transposto = difuturo_por_vencimento_transposto.set_index('Vencimento')
difuturo_por_vencimento_transposto.sort_index(ascending=False, inplace=True)
# difuturo_por_vencimento_transposto
difuturo_por_dias_uteis_transposto = difuturo_por_titulo_transposto.copy(deep=True)
difuturo_por_dias_uteis_transposto['Vencimento'] = difuturo_por_dias_uteis_transposto.index
difuturo_por_dias_uteis_transposto['Vencimento'] = difuturo_por_dias_uteis_transposto['Vencimento'].apply(lambda x: buscar_dias_uteis_ate_vencimento_titulo(x))
difuturo_por_dias_uteis_transposto = difuturo_por_dias_uteis_transposto.loc[(difuturo_por_dias_uteis_transposto['Vencimento'] > 0)].copy()
difuturo_por_dias_uteis_transposto = difuturo_por_dias_uteis_transposto.set_index('Vencimento')
difuturo_por_dias_uteis_transposto.sort_index(ascending=False, inplace=True)
# difuturo_por_dias_uteis_transposto
# Criando os gráficos... Gráfico por título usando Plotly
hoje = date.today().strftime('%d/%m/%Y')
layout = go.Layout(
annotations=[
dict(x=1.12, y=1.05, align="right", valign="top", text='Semanas:', showarrow=False, xref="paper", yref="paper",
xanchor="center", yanchor="top"),
dict(text = f"Fonte dos dados: ADVFN - https://br.advfn.com/
Data: {hoje}", showarrow=False, x = 0, y = -0.15,
xref='paper', yref='paper', xanchor='left', yanchor='bottom', xshift=-10, yshift=-150,
font=dict(size=10, color="grey"), align="left")
]
)
fig = go.Figure(layout=layout)
fig.update_layout(title_text="Curva de Juros", title_font_size=20)
fig.update_layout(autosize=False, width=900, height=700)
fig.update_xaxes(title_text="Títulos")
fig.update_xaxes(tickangle=45)
fig.update_xaxes(rangeslider_visible=True)
fig.update_yaxes(title_text="Taxas (em %)")
for numero, i in enumerate(difuturo_por_titulo_transposto):
#suavizado, conectando gaps https://plotly.com/python/line-charts/
ontem = date.today() - timedelta(days = 1)
if i.date() == ontem:
texto_legenda = "(ADVFN) Ontem: " + i.strftime('%d/%m/%Y')
else:
texto_legenda = "(ADVFN) Semana " + str(numero) + ": " + i.strftime('%d/%m/%Y')
fig.add_trace(go.Scatter(x=difuturo_por_titulo_transposto.index, y=difuturo_por_titulo_transposto[i], mode='lines',
name=texto_legenda, line_shape='spline', connectgaps=True))
fig.update_layout()
fig.show()
O gráfico ficou legal, mas tem um problema: a distorção gerada no eixo X pela falta de proporcionalidade entre os vencimentos dos títulos... Vamos trabalhar nisso, usando a data de vencimento dos títulos.
# Criando os gráficos... Gráfico por data de vencimento usando Plotly
hoje = date.today().strftime('%d/%m/%Y')
layout = go.Layout(
annotations=[
dict(x=1.12, y=1.05, align="right", valign="top", text='Semanas:', showarrow=False, xref="paper", yref="paper",
xanchor="center", yanchor="top"),
dict(text = f"Fonte dos dados: ADVFN - https://br.advfn.com/
Data: {hoje}", showarrow=False, x = 0, y = -0.15,
xref='paper', yref='paper', xanchor='left', yanchor='bottom', xshift=-10, yshift=-150,
font=dict(size=10, color="grey"), align="left")
]
)
fig = go.Figure(layout=layout)
fig.update_layout(title_text="Curva de Juros", title_font_size=20)
fig.update_layout(autosize=False, width=900, height=700)
fig.update_xaxes(title_text="Data de vencimento do título")
fig.update_xaxes(tickangle=45)
fig.update_xaxes(rangeslider_visible=True)
fig.update_yaxes(title_text="Taxas (em %)")
for numero, i in enumerate(difuturo_por_vencimento_transposto):
ontem = date.today() - timedelta(days = 1)
if i.date() == ontem:
texto_legenda = "(ADVFN) Ontem: " + i.strftime('%d/%m/%Y')
else:
texto_legenda = "(ADVFN) Semana " + str(numero) + ": " + i.strftime('%d/%m/%Y')
#suavizado, conectando gaps https://plotly.com/python/line-charts/
fig.add_trace(go.Scatter(x=difuturo_por_vencimento_transposto.index, y=difuturo_por_vencimento_transposto[i], mode='lines',
name=texto_legenda, line_shape='spline', connectgaps=True))
fig.update_layout()
fig.show()
Agora o gráfico não está distorcendo o eixo X, mas quero trabalhar com dias úteis até o vencimento, para poder comparar com os dados da ANBIMA.
# Criando os gráficos... Gráfico por dias úteis (ANBIMA e ADVFN) usando Plotly
hoje = date.today().strftime('%d/%m/%Y')
layout = go.Layout(
annotations=[
dict(x=1.12, y=1.05, align="right", valign="top", text='Semanas:', showarrow=False, xref="paper", yref="paper",
xanchor="center", yanchor="top"),
dict(text = f"Fonte dos dados: ADVFN - https://br.advfn.com/, ANBIMA - https://www.anbima.com.br/
Data: {hoje}",
showarrow=False, x = 0, y = -0.15,
xref='paper', yref='paper', xanchor='left', yanchor='bottom', xshift=-10, yshift=-150,
font=dict(size=10, color="grey"), align="left")
]
)
fig = go.Figure(layout=layout)
fig.update_layout(title_text="Curva de Juros", title_font_size=20)
fig.update_layout(autosize=False, width=900, height=700)
fig.update_xaxes(title_text="Dias úteis até o vencimento do título")
fig.update_xaxes(tickangle=45)
fig.update_xaxes(rangeslider_visible=True)
fig.update_yaxes(title_text="Taxas (em %)")
fig.add_trace(go.Scatter(x=anbima_df.index, y=anbima_df['Taxa'], name="(ANBIMA) Ontem: " + ontem.strftime('%d/%m/%Y'), line_shape='spline', mode='lines+markers', connectgaps=True, marker_size=10))
for numero, i in enumerate(difuturo_por_dias_uteis_transposto):
#suavizado, conectando gaps https://plotly.com/python/line-charts/
ontem = date.today() - timedelta(days = 1)
if i.date() == ontem:
texto_legenda = "(ADVFN) Ontem: " + i.strftime('%d/%m/%Y')
else:
texto_legenda = "(ADVFN) Semana " + str(numero) + ": " + i.strftime('%d/%m/%Y')
fig.add_trace(go.Scatter(x=difuturo_por_dias_uteis_transposto.index,
y=difuturo_por_dias_uteis_transposto[i], mode='lines+markers',
name=texto_legenda,
line_shape='spline', connectgaps=True, marker_size=10))
fig.update_layout()
fig.show()
Agora sim, o gráfico permite uma comparação entre os dados da ANBIMA e ADVFN. Podem ter pequenos problemas na exatidão do cálculo dos dias úteis...
Links relacionados
- https://plotly.com/python/
- https://deepnote.com/
- ANBIMA - Fechamento da Curva de Juros
- Código atualizado, no Deepnote
Problemas com o código?
Em caso de problema com o código, por favor, me avise no comentários.

Nenhum comentário:
Postar um comentário