Vender um Tesouro Prefixado antes do vencimento pode dar prejuízo nominal — mesmo quando "a Selic está subindo". É a marcação a mercado em ação. Neste post você mede o efeito em dez linhas de Python, usando o endpoint
/api/v2/treasury/indicators/historyda brapi.
A marcação a mercado Tesouro Direto (quanto o título vale agora se você vender hoje, antes do vencimento) pega muita gente de surpresa. Quem comprou Tesouro Prefixado 2037 no começo de fevereiro de 2026 pagou R$ 815. Poucas semanas depois, o preço caiu para R$ 795. Isso é uma queda de 3,63% — a "maior mergulhada" do período. E nada de grave aconteceu na economia. No mesmo intervalo, quem ficou com o Tesouro Selic 2031 viu o preço subir devagar e sem sustos.
A diferença tem nome. Este guia mostra, passo a passo, como medir esse risco com dados diários da API da brapi.dev e um pouco de Python.
O que é marcação a mercado
Quando você compra um título prefixado, trava uma taxa com o Tesouro. Por exemplo, 13,68% ao ano até o vencimento. Esse acordo não muda.
O que muda todo dia é o preço de revenda desse acordo.
Se o mercado começa a pedir uma taxa mais alta para emprestar o mesmo prazo (sobe para 14,5%), o seu papel de 13,68% perde valor. Ninguém quer pagar caro por algo que rende menos do que o atual. O Tesouro, ao recomprar antes do prazo, ajusta o preço com a taxa nova. Por isso o preço cai.
A regra é simples e mecânica:
| Movimento na curva | Efeito no preço |
|---|---|
| Taxa sobe | Preço cai |
| Taxa cai | Preço sobe |
| Quanto maior o prazo | Maior a sensibilidade (duration) |
Duration é a duração média (quanto tempo, em média, demora pra você receber de volta o seu dinheiro). Prazo longo balança mais. Prazo curto balança pouco.
Carregar até o vencimento neutraliza a marcação
Se você ignora o preço diário e segura o título até o vencimento, recebe exatamente a taxa contratada. A marcação a mercado só "machuca" quem vende antes — e só "premia" quem compra na baixa.
Para te ajudar a decidir se a marcação importa para a sua estratégia, use esta árvore rápida:
Decisão rápida: se você não vai vender antes, pode esquecer a marcação a mercado.
Pré-requisitos
- Conta brapi.dev no plano Pro (Tesouro Direto
detalhado é Pro). Os três títulos sandbox (
tesouro-selic-01032031,tesouro-prefixado-com-juros-semestrais-01012037,tesouro-ipca-com-juros-semestrais-15082060) respondem mesmo sem token, ótimos para reproduzir este tutorial. - Python 3.10+ com:
pip install requests pandas matplotlib- Token salvo em
BRAPI_TOKENno ambiente:
export BRAPI_TOKEN=seu_token_aquiEndpoint base
O ponto de entrada deste post é
/api/v2/treasury/indicators/history.
Ele devolve, dia a dia, as taxas e os preços de cada título:
curl "https://brapi.dev/api/v2/treasury/indicators/history?\
symbols=tesouro-prefixado-com-juros-semestrais-01012037&\
startDate=2026-02-01&endDate=2026-05-15&sortOrder=asc" \
-H "Authorization: Bearer $BRAPI_TOKEN"Resposta (resumida):
{
"results": [
{
"symbol": "tesouro-prefixado-com-juros-semestrais-01012037",
"bondType": "Tesouro Prefixado com Juros Semestrais",
"indexer": "prefixado",
"couponType": "semestral",
"maturityDate": "2037-01-01",
"rateInfo": {
"rateType": "nominalAnnualRate",
"rateUnit": "% a.a.",
"description": "Para Tesouro Prefixado, buyRate e sellRate representam a taxa nominal anual contratada."
},
"history": [
{
"baseDate": "2026-02-02",
"buyRate": 13.56,
"sellRate": 13.68,
"buyPrice": 820.97,
"sellPrice": 815.06,
"basePrice": 815.06
},
{
"baseDate": "2026-05-15",
"buyRate": 14.36,
"sellRate": 14.48,
"buyPrice": 814.75,
"sellPrice": 809.22,
"basePrice": 809.22
}
]
}
],
"requestedAt": "2026-05-17T23:29:20.468Z",
"took": 169
}Atenção ao rateInfo
buyRate e sellRate mudam de significado por indexador. Em Selic são
o spread sobre a Selic (valor cobrado a mais ou de menos em cima do
preço base, não a rentabilidade total). Em Prefixado são a taxa
nominal. Em IPCA+ são a taxa real acima da inflação. Antes de
comparar números, leia rateInfo.rateType — a brapi devolve esse
metadado em toda resposta justamente para evitar confusão.
Baixando o histórico em Python
Vamos criar uma função que recebe symbol, start, end e devolve a
lista de observações diárias. Dá pra reusar para qualquer título:
import os
import requests
BASE = "https://brapi.dev/api/v2/treasury"
TOKEN = os.getenv("BRAPI_TOKEN")
HEADERS = {"Authorization": f"Bearer {TOKEN}"} if TOKEN else {}
def fetch_history(symbol: str, start: str, end: str) -> list[dict]:
"""Histórico diário de taxas e preços para um título do Tesouro Direto."""
response = requests.get(
f"{BASE}/indicators/history",
params={
"symbols": symbol,
"startDate": start,
"endDate": end,
"sortOrder": "asc",
},
headers=HEADERS,
timeout=30,
)
response.raise_for_status()
return response.json()["results"][0]["history"]Agora puxamos três séries — Prefixado longo, IPCA+ longo e Selic — e
montamos um DataFrame pra cada uma:
import pandas as pd
START, END = "2025-01-01", "2026-05-15"
pre = pd.DataFrame(fetch_history(
"tesouro-prefixado-com-juros-semestrais-01012037", START, END,
))
ipca = pd.DataFrame(fetch_history(
"tesouro-ipca-com-juros-semestrais-15082060", START, END,
))
selic = pd.DataFrame(fetch_history(
"tesouro-selic-01032031", START, END,
))
for df in (pre, ipca, selic):
df["baseDate"] = pd.to_datetime(df["baseDate"])
df.set_index("baseDate", inplace=True)
print(pre[["sellRate", "sellPrice"]].head())
print(pre[["sellRate", "sellPrice"]].tail())Saída real:
sellRate sellPrice
baseDate
2026-02-02 13.68 815.06
2026-02-03 13.65 816.85
2026-02-04 13.75 812.71
2026-02-05 13.75 813.13
2026-02-06 13.76 813.09
sellRate sellPrice
baseDate
2026-05-11 14.31 811.34
2026-05-12 14.40 808.83
2026-05-13 14.36 810.16
2026-05-14 14.36 809.49
2026-05-15 14.48 809.22Olha o que acontece: a sellRate foi de 13,58% a 14,48% no período, e o
sellPrice foi pro lado oposto. Esse é o jeito mais simples de ver a
marcação a mercado funcionando.
Plotando a marcação do Prefixado 2037
import matplotlib.pyplot as plt
fig, ax_price = plt.subplots(figsize=(10, 5))
ax_rate = ax_price.twinx()
ax_price.plot(pre.index, pre["sellPrice"], color="#0F62FE", label="sellPrice (R$)")
ax_rate.plot(pre.index, pre["sellRate"], color="#DA1E28", label="sellRate (% a.a.)")
ax_price.set_ylabel("Preço de venda (R$)", color="#0F62FE")
ax_rate.set_ylabel("Taxa (% a.a.)", color="#DA1E28")
ax_price.set_title("Tesouro Prefixado 2037 — preço × taxa")
fig.tight_layout()
plt.savefig("prefixado_2037.png", dpi=120)O gráfico mostra duas linhas espelhadas. Quando a taxa de venda sobe, o preço cai. A ligação entre a variação da taxa em pontos percentuais e a variação do preço é quase perfeita:
import numpy as np
delta_rate = pre["sellRate"].diff().dropna() # variação em p.p.
delta_price = pre["sellPrice"].pct_change().dropna() # variação relativa
corr = np.corrcoef(delta_rate, delta_price)[0, 1]
beta = np.cov(delta_rate, delta_price, ddof=0)[0, 1] / delta_rate.var(ddof=0)
print(f"correlação ΔsellRate × ΔsellPrice: {corr:.3f}")
print(f"beta: +1 p.p. na taxa → {beta*100:.2f}% no preço")Saída real:
correlação ΔsellRate × ΔsellPrice: -1.000
beta: +1 p.p. na taxa → -5.42% no preçoLendo em português claro: cada 1 ponto a mais na taxa de venda derruba o preço do Prefixado 2037 em mais ou menos 5,42% no mesmo dia. Esse é o efeito da duration aparecendo nos dados. Para o IPCA+ 2060, que tem prazo muito maior, o número sobe pra -11,79% por p.p. Veja na seção dele.
Quantificando o drawdown
Drawdown é a queda máxima de preço entre um pico e o próximo fundo (a maior "mergulhada"). Quanto mais fundo, mais dor para quem precisou vender no pior momento.
def max_drawdown(series: pd.Series) -> tuple[float, pd.Timestamp, pd.Timestamp]:
cummax = series.cummax()
drawdown = series / cummax - 1
trough = drawdown.idxmin()
peak = series.loc[:trough].idxmax()
return drawdown.min(), peak, trough
dd, peak, trough = max_drawdown(pre["sellPrice"])
print(f"max drawdown Prefixado 2037: {dd*100:.2f}%")
print(f"pico {peak.date()} → fundo {trough.date()}")Saída real:
max drawdown Prefixado 2037: -3.63%
pico 2026-02-25 → fundo 2026-03-09Em dez pregões o preço caiu 3,63%. Quem precisou vender nessa janela perdeu, em dinheiro nominal, mais do que vários meses do CDI rendendo junto. O pior dia isolado foi este:
| Data | Δ sellRate | Δ sellPrice |
|---|---|---|
| 2026-03-19 | +0,37 p.p. (13,94 → 14,31) | -1,93% (815,92 → 800,16) |
E o melhor dia (2026-03-10) foi o oposto:
| Data | Δ sellRate | Δ sellPrice |
|---|---|---|
| 2026-03-10 | -0,40 p.p. (14,33 → 13,93) | +2,25% (795,91 → 813,83) |
É esse vai-e-volta que define um título marcado a mercado. Ele sobe ou desce mais de 2% num pregão só, sem nenhum acontecimento extraordinário na economia. É só a curva se mexendo.
Por que Tesouro Selic não sofre
Vamos rodar a mesma rotina no tesouro-selic-01032031:
dd_sel, _, _ = max_drawdown(selic["sellPrice"])
ret_sel = selic["sellPrice"].pct_change().dropna()
print(f"Selic 2031 — max drawdown: {dd_sel*100:.2f}%")
print(f"Selic 2031 — retorno médio diário: {ret_sel.mean()*100:.4f}%")
print(f"Selic 2031 — desvio-padrão diário: {ret_sel.std(ddof=0)*100:.4f}%")
print(f"Selic 2031 — sellRate range: {selic['sellRate'].min()} a {selic['sellRate'].max()}")Saída real (janela 2025-02-03 → 2026-05-15, 319 pregões):
Selic 2031 — max drawdown: 0.00%
Selic 2031 — retorno médio diário: 0.0554%
Selic 2031 — desvio-padrão diário: 0.0075%
Selic 2031 — sellRate range: 0.09 a 0.14Três coisas chamam atenção:
- Zero drawdown em 319 pregões. O preço só sobe.
- O retorno médio diário de 0,0554% bate em mais ou menos 14,87% ao ano — combina com a Selic em 14,5% no período.
- A
sellRatefica colada em 0,09–0,14 p.p. Em Tesouro Selic, esse campo é o spread sobre a Selic (valor cobrado a mais ou de menos em cima do preço base), não o rendimento total. O grosso do retorno vem do indexador.
Por que o Selic não tem marcação relevante
O Tesouro Selic é pós-fixado puro: o basePrice acumula a Selic dia a
dia. Não há "contrato de taxa fixa" para o mercado repactuar. O único
pedaço sujeito a marcação é o pequeno spread, que costuma se mover em
centésimos. Em prazos curtos, o efeito é desprezível.
Comparativo de volatilidade na mesma janela
Volatilidade é o tamanho dos balanços diários do preço, em média. Pra
comparar de forma justa, colocamos as três séries na mesma janela de
tempo e medimos o desvio-padrão dos retornos diários do sellPrice:
def vol_window(name: str, df: pd.DataFrame) -> dict:
rets = df["sellPrice"].pct_change().dropna()
return {
"titulo": name,
"obs": len(rets),
"mean_diario_%": round(rets.mean() * 100, 4),
"std_diario_%": round(rets.std(ddof=0) * 100, 4),
}
# janela 2026-02-02 → 2026-05-15 (interseção do Prefixado 2037)
janela = pre.index
print(vol_window("Prefixado 2037", pre.loc[janela]))
print(vol_window("Selic 2031", selic.loc[janela]))
# janela longa 2025-02-03 → 2026-05-15 (interseção IPCA × Selic)
janela_longa = ipca.index
print(vol_window("IPCA+ 2060", ipca.loc[janela_longa]))
print(vol_window("Selic 2031", selic.loc[janela_longa]))Saída real:
| Janela | Título | Obs | Média diária | Desvio-padrão diário |
|---|---|---|---|---|
| 2026-02-02 → 2026-05-15 | Prefixado 2037 | 69 | -0,0082% | 0,6733% |
| 2026-02-02 → 2026-05-15 | Selic 2031 | 69 | +0,0562% | 0,0023% |
| 2025-02-03 → 2026-05-15 | IPCA+ 2060 | 318 | +0,0304% | 0,7965% |
| 2025-02-03 → 2026-05-15 | Selic 2031 | 318 | +0,0554% | 0,0075% |
Visualizando o desvio-padrão diário em barras fica mais fácil enxergar a diferença de risco entre eles:
Quanto maior a barra, maior o balanço diário do preço. O Selic mal se mexe; o IPCA+ longo é o campeão de oscilação.
Os números fecham o argumento:
- Prefixado 2037 é cerca de 296× mais volátil que Selic 2031 na mesma janela curta.
- IPCA+ 2060 é cerca de 106× mais volátil que Selic 2031 na janela longa — e ainda mais que o Prefixado 2037, porque tem mais que o dobro de prazo até o vencimento (35 anos vs 11 anos).
- O retorno médio diário do Prefixado foi um pouquinho negativo na janela curta. A marcação a mercado puxou mais forte do que o ganho diário da taxa contratada. Em 2 meses, quem comprou no início ficou no vermelho no papel.
Mid-cycle: o caso do IPCA+ 2060
O Tesouro IPCA+ 2060 vence em 35 anos. Cada centésimo a mais na taxa real exigida pelo mercado vira um movimento bem maior no preço. É a duração média (duration) trabalhando contra você.
Saída real (mesma rotina das seções anteriores):
IPCA+ 2060
observações: 319
janela: 2025-02-03 → 2026-05-15
sellRate range: 6.990% a.a. a 7.660% a.a.
sellPrice range: R$ 3.594,71 a R$ 4.124,86
max drawdown sellPrice: -7.29% (pico 2025-07-01 → fundo 2025-09-04)
desvio-padrão diário: 0.7965%
beta: +1 p.p. na taxa → -11.79% no preçoSete por cento de queda em menos de dois meses num título do governo brasileiro. Esse é o preço de carregar prazo muito longo. Quem comprou em 1º de julho de 2025 por R$ 4.124 viu o papel cair pra R$ 3.825 em 4 de setembro. Em compensação, a taxa real subiu de 6,99% para 7,66% a.a. Quem comprou no fundo travou 67 pontos a mais de juro acima da inflação.
Pra fechar, compare a sensibilidade dos três títulos lado a lado. Esse é o beta: quanto o preço varia para cada +1 p.p. na curva.
O IPCA+ 2060 reage mais forte porque tem duration maior. Prazo dobrado, balanço dobrado.
Drawdown não é prejuízo realizado
-7,29% no sellPrice só vira prejuízo se você vender nesse fundo.
Quem carregou até hoje (R$ 4.023) recuperou a maior parte e, ao
vencimento, recebe a taxa contratada (IPCA + a taxa do dia da compra)
independente do que aconteceu no meio.
Quando faz sentido carregar até o vencimento
A marcação a mercado só pesa se você precisa de dinheiro antes do vencimento. A escolha entre Prefixado, IPCA+ e Selic depende do seu horizonte (quando você vai usar o dinheiro):
| Horizonte | Sensibilidade ao mercado | Título indicado |
|---|---|---|
| Reserva de emergência | Não tolera drawdown | Tesouro Selic |
| 1–3 anos com data definida | Baixa, se casar prazo | Prefixado curto (vencimento próximo) |
| 5–10 anos | Média a alta | IPCA+ com prazo casado |
| Aposentadoria (15+ anos) | Alta, mas irrelevante se carregar | IPCA+ longo ou Renda+ |
Regra simples: se o seu prazo é menor que a duration do título, você fica preso à marcação a mercado. Se o prazo for igual ou maior, o drawdown vira só um susto no meio do caminho.
A própria API mostra durationDays no endpoint
/api/v2/treasury/list. Use esse campo
pra casar o prazo do título com o seu objetivo. Pra começar do zero,
veja o guia do Tesouro
Direto.
Pipeline completo
Tudo junto num script único pra você copiar e colar:
import os
import pandas as pd
import requests
BASE = "https://brapi.dev/api/v2/treasury"
HEADERS = {"Authorization": f"Bearer {os.getenv('BRAPI_TOKEN', '')}"}
def fetch_history(symbol, start, end):
response = requests.get(
f"{BASE}/indicators/history",
params={"symbols": symbol, "startDate": start, "endDate": end, "sortOrder": "asc"},
headers=HEADERS,
timeout=30,
)
response.raise_for_status()
return response.json()["results"][0]["history"]
def analyze(symbol, start, end):
df = pd.DataFrame(fetch_history(symbol, start, end))
df["baseDate"] = pd.to_datetime(df["baseDate"])
df.set_index("baseDate", inplace=True)
rets = df["sellPrice"].pct_change().dropna()
drate = df["sellRate"].diff().dropna()
cummax = df["sellPrice"].cummax()
dd = (df["sellPrice"] / cummax - 1).min()
beta = drate.cov(rets) / drate.var()
return {
"symbol": symbol,
"obs": len(df),
"rate_min": df["sellRate"].min(),
"rate_max": df["sellRate"].max(),
"max_drawdown_%": round(dd * 100, 2),
"std_diario_%": round(rets.std(ddof=0) * 100, 4),
"beta_%_por_pp": round(beta * 100, 2),
}
symbols = [
"tesouro-selic-01032031",
"tesouro-prefixado-com-juros-semestrais-01012037",
"tesouro-ipca-com-juros-semestrais-15082060",
]
resumo = pd.DataFrame([analyze(s, "2025-01-01", "2026-05-15") for s in symbols])
print(resumo.to_string(index=False))Saída real:
symbol obs rate_min rate_max max_drawdown_% std_diario_% beta_%_por_pp
tesouro-selic-01032031 319 0.09 0.14 0.00 0.0075 -1.17
tesouro-prefixado-com-juros-semestrais-01012037 70 13.58 14.48 -3.63 0.6733 -5.42
tesouro-ipca-com-juros-semestrais-15082060 319 6.99 7.66 -7.29 0.7965 -11.79Esse DataFrame é o seu painel de risco da curva: três linhas, três
títulos, três comportamentos bem diferentes na marcação a mercado.
Próximos passos
- Guia completo do Tesouro Direto — conceitos, tributação, escolha de título.
- CDI Histórico via API — benchmark de renda fixa pós-fixada.
- API de Macroeconomia BCB — Selic, IPCA, IGP-M, juro real.
- Documentação do Tesouro Direto na brapi — referência completa dos endpoints.
FAQ
1. Por que o sellRate aparece em 0,12% no Tesouro Selic e em 13,68%
no Prefixado? São comparáveis?
Não. O campo rateInfo.rateType mostra como cada número deve ser lido.
No Selic é o spread sobre a Selic — você ganha Selic + 0,12 p.p. a.a.
No Prefixado é o rendimento nominal anual contratado. Comparar os
números crus é erro. Sempre confira rateInfo antes de cruzar séries.
2. O drawdown de -7,29% no IPCA+ 2060 significa que perdi dinheiro?
Só se você vendeu no fundo. Drawdown mede o preço de venda antecipada. Quem deixou o título carregado recebe, no vencimento, a taxa real contratada (IPCA + X% a.a.) e a marcação a mercado vira só ruído no caminho.
3. Tesouro Selic é "sem risco"?
Sem risco relevante de marcação, sim. Mas tem risco de rentabilidade real: se o IPCA acelerar acima da Selic real, você perde poder de compra. Por isso quem quer se proteger da inflação mistura Selic com IPCA+, de acordo com o prazo do objetivo.
4. Posso buscar o histórico desde 2005?
Sim. A base do Tesouro Direto começa em 2005. Basta passar
startDate=2005-01-01 no endpoint. A brapi devolve a série completa,
respeitando o limite de 20 símbolos por request.
5. Qual a frequência de atualização?
Diária, conforme publicação do arquivo público do Tesouro Direto.
O baseDate no payload é o dia útil daquele preço/taxa.
A marcação a mercado deixa de ser um conceito abstrato no momento em que
você bota o sellPrice ao lado da sellRate no gráfico. São duas linhas
espelhadas contando a mesma história. Com o endpoint
/api/v2/treasury/indicators/history,
qualquer carteira em Python ganha um termômetro de risco diário em menos
de cinquenta linhas de código.
Crie sua conta em brapi.dev, ative o plano Pro e pare de operar Prefixado e IPCA+ no escuro.
