PYield 0.46.2__tar.gz → 0.46.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pyield-0.46.2 → pyield-0.46.3}/PKG-INFO +1 -1
- pyield-0.46.3/pyield/__about__.py +1 -0
- {pyield-0.46.2 → pyield-0.46.3}/pyield/_internal/data_cache.py +1 -1
- pyield-0.46.3/pyield/anbima/tpf.py +293 -0
- {pyield-0.46.2 → pyield-0.46.3}/pyield/b3/di1.py +4 -4
- {pyield-0.46.2 → pyield-0.46.3}/pyield/tn/lft.py +3 -2
- {pyield-0.46.2 → pyield-0.46.3}/pyield/tn/ltn.py +3 -2
- {pyield-0.46.2 → pyield-0.46.3}/pyield/tn/ntnb.py +3 -2
- {pyield-0.46.2 → pyield-0.46.3}/pyield/tn/ntnc.py +3 -2
- {pyield-0.46.2 → pyield-0.46.3}/pyield/tn/ntnf.py +8 -10
- {pyield-0.46.2 → pyield-0.46.3}/pyield/tn/utils.py +1 -15
- pyield-0.46.2/pyield/__about__.py +0 -1
- pyield-0.46.2/pyield/anbima/tpf.py +0 -356
- {pyield-0.46.2 → pyield-0.46.3}/.gitignore +0 -0
- {pyield-0.46.2 → pyield-0.46.3}/LICENSE +0 -0
- {pyield-0.46.2 → pyield-0.46.3}/README.md +0 -0
- {pyield-0.46.2 → pyield-0.46.3}/pyield/__init__.py +0 -0
- {pyield-0.46.2 → pyield-0.46.3}/pyield/_internal/__init__.py +0 -0
- {pyield-0.46.2 → pyield-0.46.3}/pyield/_internal/br_numbers.py +0 -0
- {pyield-0.46.2 → pyield-0.46.3}/pyield/_internal/cache.py +0 -0
- {pyield-0.46.2 → pyield-0.46.3}/pyield/_internal/converters.py +0 -0
- {pyield-0.46.2 → pyield-0.46.3}/pyield/_internal/retry.py +0 -0
- {pyield-0.46.2 → pyield-0.46.3}/pyield/_internal/types.py +0 -0
- {pyield-0.46.2 → pyield-0.46.3}/pyield/anbima/__init__.py +0 -0
- {pyield-0.46.2 → pyield-0.46.3}/pyield/anbima/difusao.py +0 -0
- {pyield-0.46.2 → pyield-0.46.3}/pyield/anbima/ettj_intraday.py +0 -0
- {pyield-0.46.2 → pyield-0.46.3}/pyield/anbima/ettj_last.py +0 -0
- {pyield-0.46.2 → pyield-0.46.3}/pyield/anbima/ima.py +0 -0
- {pyield-0.46.2 → pyield-0.46.3}/pyield/anbima/imaq.py +0 -0
- {pyield-0.46.2 → pyield-0.46.3}/pyield/b3/__init__.py +0 -0
- {pyield-0.46.2 → pyield-0.46.3}/pyield/b3/_contracts.py +0 -0
- {pyield-0.46.2 → pyield-0.46.3}/pyield/b3/_validar_pregao.py +0 -0
- {pyield-0.46.2 → pyield-0.46.3}/pyield/b3/di_over.py +0 -0
- {pyield-0.46.2 → pyield-0.46.3}/pyield/b3/futures/__init__.py +0 -0
- {pyield-0.46.2 → pyield-0.46.3}/pyield/b3/futures/common.py +0 -0
- {pyield-0.46.2 → pyield-0.46.3}/pyield/b3/futures/historical.py +0 -0
- {pyield-0.46.2 → pyield-0.46.3}/pyield/b3/futures/intraday.py +0 -0
- {pyield-0.46.2 → pyield-0.46.3}/pyield/b3/intraday_derivatives.py +0 -0
- {pyield-0.46.2 → pyield-0.46.3}/pyield/b3/price_report.py +0 -0
- {pyield-0.46.2 → pyield-0.46.3}/pyield/bc/__init__.py +0 -0
- {pyield-0.46.2 → pyield-0.46.3}/pyield/bc/auction.py +0 -0
- {pyield-0.46.2 → pyield-0.46.3}/pyield/bc/copom.py +0 -0
- {pyield-0.46.2 → pyield-0.46.3}/pyield/bc/ptax_api.py +0 -0
- {pyield-0.46.2 → pyield-0.46.3}/pyield/bc/rates.py +0 -0
- {pyield-0.46.2 → pyield-0.46.3}/pyield/bc/repo.py +0 -0
- {pyield-0.46.2 → pyield-0.46.3}/pyield/bc/trades_intraday.py +0 -0
- {pyield-0.46.2 → pyield-0.46.3}/pyield/bc/trades_monthly.py +0 -0
- {pyield-0.46.2 → pyield-0.46.3}/pyield/bc/vna.py +0 -0
- {pyield-0.46.2 → pyield-0.46.3}/pyield/bday/__init__.py +0 -0
- {pyield-0.46.2 → pyield-0.46.3}/pyield/bday/core.py +0 -0
- {pyield-0.46.2 → pyield-0.46.3}/pyield/bday/holidays/br_holidays_new.txt +0 -0
- {pyield-0.46.2 → pyield-0.46.3}/pyield/bday/holidays/br_holidays_old.txt +0 -0
- {pyield-0.46.2 → pyield-0.46.3}/pyield/bday/holidays/brholidays.py +0 -0
- {pyield-0.46.2 → pyield-0.46.3}/pyield/clock.py +0 -0
- {pyield-0.46.2 → pyield-0.46.3}/pyield/fwd.py +0 -0
- {pyield-0.46.2 → pyield-0.46.3}/pyield/interpolator.py +0 -0
- {pyield-0.46.2 → pyield-0.46.3}/pyield/ipca/__init__.py +0 -0
- {pyield-0.46.2 → pyield-0.46.3}/pyield/ipca/historical.py +0 -0
- {pyield-0.46.2 → pyield-0.46.3}/pyield/ipca/projected.py +0 -0
- {pyield-0.46.2 → pyield-0.46.3}/pyield/py.typed +0 -0
- {pyield-0.46.2 → pyield-0.46.3}/pyield/rmd.py +0 -0
- {pyield-0.46.2 → pyield-0.46.3}/pyield/selic/__init__.py +0 -0
- {pyield-0.46.2 → pyield-0.46.3}/pyield/selic/cpm.py +0 -0
- {pyield-0.46.2 → pyield-0.46.3}/pyield/selic/probabilities.py +0 -0
- {pyield-0.46.2 → pyield-0.46.3}/pyield/tn/__init__.py +0 -0
- {pyield-0.46.2 → pyield-0.46.3}/pyield/tn/auctions.py +0 -0
- {pyield-0.46.2 → pyield-0.46.3}/pyield/tn/benchmark.py +0 -0
- {pyield-0.46.2 → pyield-0.46.3}/pyield/tn/ntnb1.py +0 -0
- {pyield-0.46.2 → pyield-0.46.3}/pyield/tn/ntnbprinc.py +0 -0
- {pyield-0.46.2 → pyield-0.46.3}/pyield/tn/pre.py +0 -0
- {pyield-0.46.2 → pyield-0.46.3}/pyproject.toml +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pyield
|
|
3
|
-
Version: 0.46.
|
|
3
|
+
Version: 0.46.3
|
|
4
4
|
Summary: A Python library for analysis of fixed income instruments in Brazil
|
|
5
5
|
Project-URL: Homepage, https://github.com/crdcj/PYield
|
|
6
6
|
Project-URL: Documentation, https://crdcj.github.io/PYield
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.46.3"
|
|
@@ -16,7 +16,7 @@ registro = logging.getLogger(__name__)
|
|
|
16
16
|
|
|
17
17
|
# Estrutura interna única — usuário não vê isso
|
|
18
18
|
class _Dataset(Enum):
|
|
19
|
-
TPF = ("anbima_tpf.parquet", "
|
|
19
|
+
TPF = ("anbima_tpf.parquet", "data_referencia", "TPF (ANBIMA)")
|
|
20
20
|
PR = ("b3_price_report.parquet", "TradeDate", "Price Report (B3)")
|
|
21
21
|
|
|
22
22
|
def __init__(self, nome_arquivo: str, coluna_data: str, descricao: str):
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
"""Dados do mercado secundário de Títulos Públicos Federais (TPF) da ANBIMA.
|
|
2
|
+
|
|
3
|
+
Exemplo de URL:
|
|
4
|
+
https://www.anbima.com.br/informacoes/merc-sec/arqs/ms240614.txt
|
|
5
|
+
|
|
6
|
+
Exemplo de dado bruto (CSV separado por @, encoding latin1):
|
|
7
|
+
ANBIMA - Associação Brasileira das Entidades dos Mercados Financeiro e de Capitais
|
|
8
|
+
|
|
9
|
+
Titulo@Data Referencia@Codigo SELIC@Data Base/Emissao@Data Vencimento@Tx. Compra@Tx. Venda@Tx. Indicativas@PU@Desvio padrao@Interv. Ind. Inf. (D0)@Interv. Ind. Sup. (D0)@Interv. Ind. Inf. (D+1)@Interv. Ind. Sup. (D+1)@Criterio
|
|
10
|
+
LTN@20250924@100000@20230707@20251001@14,9483@14,9263@14,9375@997,241543@0,00433039162894@14,7341@15,2612@14,7316@15,2689@Calculado
|
|
11
|
+
LTN@20250924@100000@20200206@20260101@14,7741@14,7485@14,7616@963,001853@0,00729826731971@14,7008@14,9986@14,7021@14,9975@Calculado
|
|
12
|
+
""" # noqa: E501
|
|
13
|
+
|
|
14
|
+
import datetime as dt
|
|
15
|
+
import logging
|
|
16
|
+
import socket
|
|
17
|
+
from typing import Literal
|
|
18
|
+
|
|
19
|
+
import polars as pl
|
|
20
|
+
import requests
|
|
21
|
+
|
|
22
|
+
from pyield import bday
|
|
23
|
+
from pyield._internal.br_numbers import float_br, taxa_br
|
|
24
|
+
from pyield._internal.converters import converter_datas, data_referencia_valida
|
|
25
|
+
from pyield._internal.data_cache import obter_dataset_cacheado
|
|
26
|
+
from pyield._internal.retry import retry_padrao
|
|
27
|
+
from pyield._internal.types import DateLike
|
|
28
|
+
|
|
29
|
+
BOND_TYPES = Literal["LFT", "NTN-B", "NTN-C", "LTN", "NTN-F", "PRE"]
|
|
30
|
+
|
|
31
|
+
ANBIMA_URL = "https://www.anbima.com.br/informacoes/merc-sec/arqs"
|
|
32
|
+
ANBIMA_RTM_HOSTNAME = "www.anbima.associados.rtm"
|
|
33
|
+
ANBIMA_RTM_URL = f"http://{ANBIMA_RTM_HOSTNAME}/merc_sec/arqs"
|
|
34
|
+
|
|
35
|
+
# Antes de 13/05/2014 o arquivo era zipado e o endpoint terminava com ".exe"
|
|
36
|
+
DATA_MUDANCA_FORMATO = dt.date(2014, 5, 13)
|
|
37
|
+
|
|
38
|
+
DIAS_RETENCAO_PUBLICA = 5
|
|
39
|
+
|
|
40
|
+
# Colunas selecionadas pela função pública tpf()
|
|
41
|
+
COLUNAS_TPF = (
|
|
42
|
+
"titulo",
|
|
43
|
+
"data_referencia",
|
|
44
|
+
"codigo_selic",
|
|
45
|
+
"data_base",
|
|
46
|
+
"data_vencimento",
|
|
47
|
+
"pu",
|
|
48
|
+
"taxa_compra",
|
|
49
|
+
"taxa_venda",
|
|
50
|
+
"taxa_indicativa",
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
logger = logging.getLogger(__name__)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _mapear_tipo_titulo(tipo_titulo: str) -> list[str]:
|
|
57
|
+
tipo_titulo = tipo_titulo.upper()
|
|
58
|
+
mapa_titulos = {
|
|
59
|
+
"PRE": ["LTN", "NTN-F"],
|
|
60
|
+
"NTNB": ["NTN-B"],
|
|
61
|
+
"NTNC": ["NTN-C"],
|
|
62
|
+
"NTNF": ["NTN-F"],
|
|
63
|
+
}
|
|
64
|
+
return mapa_titulos.get(tipo_titulo, [tipo_titulo])
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _montar_nome_arquivo(data: dt.date) -> str:
|
|
68
|
+
data_url = data.strftime("%y%m%d")
|
|
69
|
+
if data < DATA_MUDANCA_FORMATO:
|
|
70
|
+
nome_arquivo = f"ms{data_url}.exe"
|
|
71
|
+
else:
|
|
72
|
+
nome_arquivo = f"ms{data_url}.txt"
|
|
73
|
+
return nome_arquivo
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _montar_url_arquivo(data: dt.date) -> str:
|
|
77
|
+
ultimo_dia_util = bday.last_business_day()
|
|
78
|
+
qtd_dias_uteis = bday.count(data, ultimo_dia_util)
|
|
79
|
+
if qtd_dias_uteis > DIAS_RETENCAO_PUBLICA:
|
|
80
|
+
# Para datas com mais de 5 dias úteis, apenas os dados da RTM estão disponíveis
|
|
81
|
+
logger.info("Tentando buscar dados RTM para %s", data.strftime("%d/%m/%Y"))
|
|
82
|
+
url_arquivo = f"{ANBIMA_RTM_URL}/{_montar_nome_arquivo(data)}"
|
|
83
|
+
else:
|
|
84
|
+
url_arquivo = f"{ANBIMA_URL}/{_montar_nome_arquivo(data)}"
|
|
85
|
+
return url_arquivo
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
@retry_padrao
|
|
89
|
+
def _obter_csv(data: dt.date) -> bytes:
|
|
90
|
+
url_arquivo = _montar_url_arquivo(data)
|
|
91
|
+
resposta = requests.get(url_arquivo, timeout=10)
|
|
92
|
+
resposta.raise_for_status()
|
|
93
|
+
return resposta.content
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _parsear_df(csv_bytes: bytes) -> pl.DataFrame:
|
|
97
|
+
"""Converte bytes brutos do CSV da ANBIMA em DataFrame (tudo string)."""
|
|
98
|
+
return pl.read_csv(
|
|
99
|
+
source=csv_bytes,
|
|
100
|
+
skip_lines=2,
|
|
101
|
+
separator="@",
|
|
102
|
+
null_values=["--"],
|
|
103
|
+
infer_schema=False,
|
|
104
|
+
encoding="latin1",
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _processar_df(df: pl.DataFrame) -> pl.DataFrame:
|
|
109
|
+
"""Renomeia, converte tipos e define a ordem das colunas."""
|
|
110
|
+
return df.select(
|
|
111
|
+
titulo=pl.col("Titulo"),
|
|
112
|
+
data_referencia=pl.col("Data Referencia").str.to_date("%Y%m%d"),
|
|
113
|
+
codigo_selic=pl.col("Codigo SELIC").cast(pl.Int64),
|
|
114
|
+
data_base=pl.col("Data Base/Emissao").str.to_date("%Y%m%d"),
|
|
115
|
+
data_vencimento=pl.col("Data Vencimento").str.to_date("%Y%m%d"),
|
|
116
|
+
taxa_compra=taxa_br("Tx. Compra"),
|
|
117
|
+
taxa_venda=taxa_br("Tx. Venda"),
|
|
118
|
+
taxa_indicativa=taxa_br("Tx. Indicativas"),
|
|
119
|
+
pu=float_br("PU"),
|
|
120
|
+
desvio_padrao=float_br("Desvio padrao"),
|
|
121
|
+
taxa_intervalo_inf_d0=taxa_br("Interv. Ind. Inf. (D0)"),
|
|
122
|
+
taxa_intervalo_sup_d0=taxa_br("Interv. Ind. Sup. (D0)"),
|
|
123
|
+
taxa_intervalo_inf_d1=taxa_br("Interv. Ind. Inf. (D+1)"),
|
|
124
|
+
taxa_intervalo_sup_d1=taxa_br("Interv. Ind. Sup. (D+1)"),
|
|
125
|
+
criterio=pl.col("Criterio"),
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def _buscar_dados_tpf(data: dt.date) -> pl.DataFrame:
|
|
130
|
+
"""Busca e processa dados do mercado secundário de TPF na fonte ANBIMA.
|
|
131
|
+
|
|
132
|
+
Args:
|
|
133
|
+
data: Data de referência.
|
|
134
|
+
|
|
135
|
+
Returns:
|
|
136
|
+
DataFrame processado ou DataFrame vazio se indisponível.
|
|
137
|
+
"""
|
|
138
|
+
url_arquivo = _montar_url_arquivo(data)
|
|
139
|
+
|
|
140
|
+
# Fail-fast: se a URL é RTM e o host não resolve, não adianta tentar
|
|
141
|
+
if ANBIMA_RTM_URL in url_arquivo:
|
|
142
|
+
try:
|
|
143
|
+
socket.gethostbyname(ANBIMA_RTM_HOSTNAME)
|
|
144
|
+
except socket.gaierror:
|
|
145
|
+
data_str = data.strftime("%d/%m/%Y")
|
|
146
|
+
logger.warning(
|
|
147
|
+
f"Não foi possível resolver o host da RTM para {data_str}. "
|
|
148
|
+
"Dados históricos exigem acesso à rede RTM."
|
|
149
|
+
)
|
|
150
|
+
return pl.DataFrame()
|
|
151
|
+
|
|
152
|
+
csv_bytes = _obter_csv(data)
|
|
153
|
+
if not csv_bytes.strip():
|
|
154
|
+
return pl.DataFrame()
|
|
155
|
+
|
|
156
|
+
df = _parsear_df(csv_bytes)
|
|
157
|
+
return _processar_df(df)
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def tpf(
|
|
161
|
+
date: DateLike,
|
|
162
|
+
bond_type: BOND_TYPES | None = None,
|
|
163
|
+
) -> pl.DataFrame:
|
|
164
|
+
"""Recupera os dados do mercado secundário de TPF da ANBIMA.
|
|
165
|
+
|
|
166
|
+
Esta função busca taxas indicativas e outros dados de títulos públicos
|
|
167
|
+
brasileiros. Primeiro consulta o cache local; se não houver dados,
|
|
168
|
+
busca diretamente na fonte (ANBIMA).
|
|
169
|
+
|
|
170
|
+
Args:
|
|
171
|
+
date (DateLike): A data de referência para os dados (ex: '2024-06-14').
|
|
172
|
+
bond_type (str, optional): Filtra por tipo de título. Aceita os tipos
|
|
173
|
+
individuais ('LTN', 'NTN-F', 'NTN-B', 'NTN-C', 'LFT') ou 'PRE'
|
|
174
|
+
como atalho para prefixados ('LTN' e 'NTN-F').
|
|
175
|
+
|
|
176
|
+
Returns:
|
|
177
|
+
pl.DataFrame: Um DataFrame contendo os dados solicitados.
|
|
178
|
+
Retorna um DataFrame vazio se não houver dados para a data especificada (ex:
|
|
179
|
+
finais de semana, feriados ou datas futuras).
|
|
180
|
+
|
|
181
|
+
Examples:
|
|
182
|
+
>>> from pyield import anbima
|
|
183
|
+
>>> df = anbima.tpf(date="06-02-2026")
|
|
184
|
+
|
|
185
|
+
Output Columns:
|
|
186
|
+
* titulo (String): tipo do título público (ex: 'LTN', 'NTN-B').
|
|
187
|
+
* data_referencia (Date): data de referência dos dados.
|
|
188
|
+
* codigo_selic (Int64): código do título no SELIC.
|
|
189
|
+
* data_base (Date): data base ou de emissão do título.
|
|
190
|
+
* data_vencimento (Date): data de vencimento do título.
|
|
191
|
+
* pu (Float64): preço unitário (PU) para liquidação em D0.
|
|
192
|
+
* taxa_compra (Float64): taxa de compra em D0 (decimal).
|
|
193
|
+
* taxa_venda (Float64): taxa de venda em D0 (decimal).
|
|
194
|
+
* taxa_indicativa (Float64): taxa indicativa em D0 (decimal).
|
|
195
|
+
|
|
196
|
+
Notes:
|
|
197
|
+
A fonte dos dados segue a seguinte hierarquia:
|
|
198
|
+
|
|
199
|
+
1. **Cache Local:** Fornece acesso rápido a dados históricos
|
|
200
|
+
desde 01/01/2020.
|
|
201
|
+
2. **Fonte ANBIMA (fallback):** Se a data não estiver no cache,
|
|
202
|
+
busca automaticamente na fonte. Para datas recentes (até 5 dias
|
|
203
|
+
úteis), usa o site público. Para datas mais antigas, requer
|
|
204
|
+
acesso à rede RTM.
|
|
205
|
+
|
|
206
|
+
Para obter o dado completo direto da fonte (todas as colunas),
|
|
207
|
+
use :func:`fetch_tpf`.
|
|
208
|
+
"""
|
|
209
|
+
date = converter_datas(date)
|
|
210
|
+
|
|
211
|
+
if not data_referencia_valida(date):
|
|
212
|
+
return pl.DataFrame()
|
|
213
|
+
|
|
214
|
+
# Cache primeiro; se não tiver, busca na fonte
|
|
215
|
+
df = obter_dataset_cacheado("tpf")
|
|
216
|
+
if not df.is_empty():
|
|
217
|
+
df = df.filter(pl.col("data_referencia") == date)
|
|
218
|
+
if df.is_empty():
|
|
219
|
+
df = _buscar_dados_tpf(date)
|
|
220
|
+
if df.is_empty():
|
|
221
|
+
return pl.DataFrame()
|
|
222
|
+
|
|
223
|
+
df = df.select(col for col in COLUNAS_TPF if col in df.columns)
|
|
224
|
+
|
|
225
|
+
if bond_type:
|
|
226
|
+
tipos_titulo = _mapear_tipo_titulo(bond_type)
|
|
227
|
+
df = df.filter(pl.col("titulo").is_in(tipos_titulo))
|
|
228
|
+
|
|
229
|
+
return df.sort("data_referencia", "titulo", "data_vencimento")
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def fetch_tpf(
|
|
233
|
+
date: DateLike,
|
|
234
|
+
) -> pl.DataFrame:
|
|
235
|
+
"""Busca os dados do mercado secundário de TPF direto da fonte ANBIMA.
|
|
236
|
+
|
|
237
|
+
Retorna todas as colunas publicadas pela ANBIMA, sem cache e sem
|
|
238
|
+
filtro de colunas. Indicado para uso em jobs e pipelines de dados.
|
|
239
|
+
|
|
240
|
+
Args:
|
|
241
|
+
date (DateLike): Data de referência (ex: '2024-06-14').
|
|
242
|
+
|
|
243
|
+
Returns:
|
|
244
|
+
pl.DataFrame: DataFrame com todas as colunas da ANBIMA.
|
|
245
|
+
Retorna DataFrame vazio se não houver dados.
|
|
246
|
+
"""
|
|
247
|
+
date = converter_datas(date)
|
|
248
|
+
|
|
249
|
+
if not data_referencia_valida(date):
|
|
250
|
+
return pl.DataFrame()
|
|
251
|
+
|
|
252
|
+
df = _buscar_dados_tpf(date)
|
|
253
|
+
if df.is_empty():
|
|
254
|
+
return pl.DataFrame()
|
|
255
|
+
|
|
256
|
+
return df.sort("data_referencia", "titulo", "data_vencimento")
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def tpf_maturities(
|
|
260
|
+
date: DateLike,
|
|
261
|
+
bond_type: BOND_TYPES,
|
|
262
|
+
) -> pl.Series:
|
|
263
|
+
"""Recupera os vencimentos existentes para um tipo de título na data especificada.
|
|
264
|
+
|
|
265
|
+
Args:
|
|
266
|
+
date (DateLike): A data de referência para os vencimentos.
|
|
267
|
+
bond_type (BOND_TYPES): O tipo de título para filtrar (ex: 'PRE' para 'LTN'
|
|
268
|
+
e 'NTN-F', ou especifique 'LTN' ou 'NTN-F' diretamente).
|
|
269
|
+
|
|
270
|
+
Returns:
|
|
271
|
+
pl.Series: Uma Series contendo as datas de vencimento únicas para o(s)
|
|
272
|
+
tipo(s) de título especificado(s).
|
|
273
|
+
|
|
274
|
+
Examples:
|
|
275
|
+
>>> from pyield import anbima
|
|
276
|
+
>>> anbima.tpf_maturities(date="22-08-2025", bond_type="PRE")
|
|
277
|
+
shape: (18,)
|
|
278
|
+
Series: 'data_vencimento' [date]
|
|
279
|
+
[
|
|
280
|
+
2025-10-01
|
|
281
|
+
2026-01-01
|
|
282
|
+
2026-04-01
|
|
283
|
+
2026-07-01
|
|
284
|
+
2026-10-01
|
|
285
|
+
…
|
|
286
|
+
2030-01-01
|
|
287
|
+
2031-01-01
|
|
288
|
+
2032-01-01
|
|
289
|
+
2033-01-01
|
|
290
|
+
2035-01-01
|
|
291
|
+
]
|
|
292
|
+
"""
|
|
293
|
+
return tpf(date, bond_type)["data_vencimento"].unique().sort()
|
|
@@ -62,11 +62,11 @@ def data(
|
|
|
62
62
|
if pre_filter:
|
|
63
63
|
df_tpf = (
|
|
64
64
|
obter_dataset_cacheado("tpf")
|
|
65
|
-
.filter(pl.col("
|
|
66
|
-
.unique(subset=["
|
|
65
|
+
.filter(pl.col("titulo").is_in(["LTN", "NTN-F"]))
|
|
66
|
+
.unique(subset=["data_vencimento", "data_referencia"])
|
|
67
67
|
.select(
|
|
68
|
-
TradeDate_tpf=pl.col("
|
|
69
|
-
ExpirationDate=bday.offset_expr("
|
|
68
|
+
TradeDate_tpf=pl.col("data_referencia"),
|
|
69
|
+
ExpirationDate=bday.offset_expr("data_vencimento", 0),
|
|
70
70
|
)
|
|
71
71
|
.sort("TradeDate_tpf", "ExpirationDate")
|
|
72
72
|
)
|
|
@@ -20,7 +20,7 @@ def data(date: DateLike) -> pl.DataFrame:
|
|
|
20
20
|
- data_referencia (Date): Data de referência dos dados.
|
|
21
21
|
- titulo (String): Tipo do título (ex.: "LFT").
|
|
22
22
|
- codigo_selic (Int64): Código do título no SELIC.
|
|
23
|
-
- data_base (Date): Data base
|
|
23
|
+
- data_base (Date): Data base de emissão do título.
|
|
24
24
|
- data_vencimento (Date): Data de vencimento do título.
|
|
25
25
|
- dias_uteis (Int64): Dias úteis entre referência e vencimento.
|
|
26
26
|
- prazo_medio (Float64): Prazo médio do título em anos.
|
|
@@ -28,7 +28,8 @@ def data(date: DateLike) -> pl.DataFrame:
|
|
|
28
28
|
- taxa_compra (Float64): Taxa de compra (decimal).
|
|
29
29
|
- taxa_venda (Float64): Taxa de venda (decimal).
|
|
30
30
|
- taxa_indicativa (Float64): Taxa indicativa (decimal).
|
|
31
|
-
- taxa_di (Float64): Taxa DI interpolada pelo
|
|
31
|
+
- taxa_di (Float64): Taxa de ajuste do DI Futuro interpolada pelo
|
|
32
|
+
método flat forward.
|
|
32
33
|
- rentabilidade (Float64): Rentabilidade da LFT sobre o DI.
|
|
33
34
|
|
|
34
35
|
Examples:
|
|
@@ -23,7 +23,7 @@ def data(date: DateLike) -> pl.DataFrame:
|
|
|
23
23
|
- data_referencia (Date): Data de referência dos dados.
|
|
24
24
|
- titulo (String): Tipo do título (ex.: "LTN").
|
|
25
25
|
- codigo_selic (Int64): Código do título no SELIC.
|
|
26
|
-
- data_base (Date): Data base
|
|
26
|
+
- data_base (Date): Data base de emissão do título.
|
|
27
27
|
- data_vencimento (Date): Data de vencimento do título.
|
|
28
28
|
- dias_uteis (Int64): Dias úteis entre referência e vencimento.
|
|
29
29
|
- duration (Float64): Macaulay Duration do título (anos).
|
|
@@ -34,7 +34,8 @@ def data(date: DateLike) -> pl.DataFrame:
|
|
|
34
34
|
- taxa_compra (Float64): Taxa de compra (decimal).
|
|
35
35
|
- taxa_venda (Float64): Taxa de venda (decimal).
|
|
36
36
|
- taxa_indicativa (Float64): Taxa indicativa (decimal).
|
|
37
|
-
- taxa_di (Float64): Taxa DI interpolada pelo
|
|
37
|
+
- taxa_di (Float64): Taxa de ajuste do DI Futuro interpolada pelo
|
|
38
|
+
método flat forward.
|
|
38
39
|
- spread_di (Float64): Spread sobre o DI (também conhecido como
|
|
39
40
|
prêmio).
|
|
40
41
|
- rentabilidade (Float64): Rentabilidade diária da LTN sobre o DI.
|
|
@@ -37,7 +37,7 @@ def data(date: DateLike) -> pl.DataFrame:
|
|
|
37
37
|
- data_referencia (Date): Data de referência dos dados.
|
|
38
38
|
- titulo (String): Tipo do título (ex.: "NTN-B").
|
|
39
39
|
- codigo_selic (Int64): Código do título no SELIC.
|
|
40
|
-
- data_base (Date): Data base
|
|
40
|
+
- data_base (Date): Data base de emissão do título.
|
|
41
41
|
- data_vencimento (Date): Data de vencimento do título.
|
|
42
42
|
- dias_uteis (Int64): Dias úteis entre referência e vencimento.
|
|
43
43
|
- duration (Float64): Macaulay Duration do título (anos).
|
|
@@ -48,7 +48,8 @@ def data(date: DateLike) -> pl.DataFrame:
|
|
|
48
48
|
- taxa_compra (Float64): Taxa de compra (decimal).
|
|
49
49
|
- taxa_venda (Float64): Taxa de venda (decimal).
|
|
50
50
|
- taxa_indicativa (Float64): Taxa indicativa (decimal).
|
|
51
|
-
- taxa_di (Float64): Taxa DI interpolada pelo
|
|
51
|
+
- taxa_di (Float64): Taxa de ajuste do DI Futuro interpolada pelo
|
|
52
|
+
método flat forward.
|
|
52
53
|
- taxa_zero (Float64): Taxa zero real (via bootstrap das taxas indicativas).
|
|
53
54
|
- taxa_forward (Float64): Taxa forward real (a partir das taxas zero).
|
|
54
55
|
- inflacao_implicita (Float64): Inflação implícita (breakeven) calculada
|
|
@@ -53,7 +53,7 @@ def data(date: DateLike) -> pl.DataFrame:
|
|
|
53
53
|
- data_referencia (Date): Data de referência dos dados.
|
|
54
54
|
- titulo (String): Tipo do título (ex.: "NTN-C").
|
|
55
55
|
- codigo_selic (Int64): Código do título no SELIC.
|
|
56
|
-
- data_base (Date): Data base
|
|
56
|
+
- data_base (Date): Data base de emissão do título.
|
|
57
57
|
- data_vencimento (Date): Data de vencimento do título.
|
|
58
58
|
- dias_uteis (Int64): Dias úteis entre referência e vencimento.
|
|
59
59
|
- duration (Float64): Macaulay Duration do título (anos).
|
|
@@ -64,7 +64,8 @@ def data(date: DateLike) -> pl.DataFrame:
|
|
|
64
64
|
- taxa_compra (Float64): Taxa de compra (decimal).
|
|
65
65
|
- taxa_venda (Float64): Taxa de venda (decimal).
|
|
66
66
|
- taxa_indicativa (Float64): Taxa indicativa (decimal).
|
|
67
|
-
- taxa_di (Float64): Taxa DI interpolada pelo
|
|
67
|
+
- taxa_di (Float64): Taxa de ajuste do DI Futuro interpolada pelo
|
|
68
|
+
método flat forward.
|
|
68
69
|
|
|
69
70
|
Examples:
|
|
70
71
|
>>> from pyield import ntnc
|
|
@@ -43,7 +43,7 @@ def data(date: DateLike) -> pl.DataFrame:
|
|
|
43
43
|
- data_referencia (Date): Data de referência dos dados.
|
|
44
44
|
- titulo (String): Tipo do título (ex.: "NTN-F").
|
|
45
45
|
- codigo_selic (Int64): Código do título no SELIC.
|
|
46
|
-
- data_base (Date): Data base
|
|
46
|
+
- data_base (Date): Data base de emissão do título.
|
|
47
47
|
- data_vencimento (Date): Data de vencimento do título.
|
|
48
48
|
- dias_uteis (Int64): Dias úteis entre referência e vencimento.
|
|
49
49
|
- prazo_medio (Float64): Prazo médio do título em dias corridos.
|
|
@@ -54,11 +54,11 @@ def data(date: DateLike) -> pl.DataFrame:
|
|
|
54
54
|
- taxa_compra (Float64): Taxa de compra (decimal).
|
|
55
55
|
- taxa_venda (Float64): Taxa de venda (decimal).
|
|
56
56
|
- taxa_indicativa (Float64): Taxa indicativa (decimal).
|
|
57
|
-
- taxa_di (Float64): Taxa DI interpolada pelo
|
|
57
|
+
- taxa_di (Float64): Taxa de ajuste do DI Futuro interpolada pelo
|
|
58
|
+
método flat forward.
|
|
58
59
|
- taxa_zero (Float64): Taxa zero (zero cupom via bootstrap).
|
|
59
|
-
- spread_di (Float64): Spread sobre o DI (
|
|
60
|
-
|
|
61
|
-
- spread_di_liquido (Float64): Spread líquido sobre a curva DI.
|
|
60
|
+
- spread_di (Float64): Spread sobre o DI (conhecido como prêmio).
|
|
61
|
+
- spread_di_limpo (Float64): Spread limpo sobre a curva DI (conhecido como prêmio limpo).
|
|
62
62
|
- rentabilidade (Float64): Rentabilidade da NTN-F sobre a curva DI.
|
|
63
63
|
|
|
64
64
|
Examples:
|
|
@@ -98,7 +98,7 @@ def data(date: DateLike) -> pl.DataFrame:
|
|
|
98
98
|
# Calcula spreads e rentabilidade para cada vencimento
|
|
99
99
|
df = df.with_columns(
|
|
100
100
|
spread_di=pl.col("taxa_indicativa") - pl.col("taxa_di"),
|
|
101
|
-
|
|
101
|
+
spread_di_limpo=pl.struct("data_vencimento", "taxa_indicativa").map_elements(
|
|
102
102
|
lambda row: di_net_spread(
|
|
103
103
|
settlement=date, # Usa a variável externa explicitamente aqui
|
|
104
104
|
ntnf_maturity=row["data_vencimento"],
|
|
@@ -138,7 +138,7 @@ def data(date: DateLike) -> pl.DataFrame:
|
|
|
138
138
|
"taxa_di",
|
|
139
139
|
"taxa_zero",
|
|
140
140
|
"spread_di",
|
|
141
|
-
"
|
|
141
|
+
"spread_di_limpo",
|
|
142
142
|
"rentabilidade",
|
|
143
143
|
)
|
|
144
144
|
|
|
@@ -293,9 +293,7 @@ def cash_flows(
|
|
|
293
293
|
)
|
|
294
294
|
|
|
295
295
|
if adj_payment_dates:
|
|
296
|
-
df = df.with_columns(
|
|
297
|
-
data_pagamento=bday.offset_expr("data_pagamento", 0)
|
|
298
|
-
)
|
|
296
|
+
df = df.with_columns(data_pagamento=bday.offset_expr("data_pagamento", 0))
|
|
299
297
|
return df
|
|
300
298
|
|
|
301
299
|
|
|
@@ -11,18 +11,6 @@ from pyield.anbima.tpf import BOND_TYPES
|
|
|
11
11
|
|
|
12
12
|
logger = logging.getLogger(__name__)
|
|
13
13
|
|
|
14
|
-
MAPA_COLUNAS_TPF = {
|
|
15
|
-
"ReferenceDate": "data_referencia",
|
|
16
|
-
"BondType": "titulo",
|
|
17
|
-
"SelicCode": "codigo_selic",
|
|
18
|
-
"IssueBaseDate": "data_base",
|
|
19
|
-
"MaturityDate": "data_vencimento",
|
|
20
|
-
"Price": "pu",
|
|
21
|
-
"BidRate": "taxa_compra",
|
|
22
|
-
"AskRate": "taxa_venda",
|
|
23
|
-
"IndicativeRate": "taxa_indicativa",
|
|
24
|
-
}
|
|
25
|
-
|
|
26
14
|
COLUNAS_DADOS_TPF = (
|
|
27
15
|
"data_referencia",
|
|
28
16
|
"titulo",
|
|
@@ -51,9 +39,7 @@ def obter_tpf(
|
|
|
51
39
|
"""Busca dados de ``anbima.tpf()`` no padrão de colunas usado por ``tn``."""
|
|
52
40
|
from pyield import anbima # noqa: PLC0415
|
|
53
41
|
|
|
54
|
-
return anbima.tpf(date, tipo_titulo).select(
|
|
55
|
-
MAPA_COLUNAS_TPF
|
|
56
|
-
)
|
|
42
|
+
return anbima.tpf(date, tipo_titulo).select(COLUNAS_DADOS_TPF)
|
|
57
43
|
|
|
58
44
|
|
|
59
45
|
def adicionar_taxa_di(df: pl.DataFrame, data_ref: dt.date) -> pl.DataFrame:
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.46.2"
|
|
@@ -1,356 +0,0 @@
|
|
|
1
|
-
import datetime as dt
|
|
2
|
-
import logging
|
|
3
|
-
import socket
|
|
4
|
-
from typing import Literal
|
|
5
|
-
|
|
6
|
-
import polars as pl
|
|
7
|
-
import polars.selectors as cs
|
|
8
|
-
import requests
|
|
9
|
-
from requests.exceptions import HTTPError, RequestException
|
|
10
|
-
|
|
11
|
-
from pyield import bday
|
|
12
|
-
from pyield._internal.converters import converter_datas, data_referencia_valida
|
|
13
|
-
from pyield._internal.data_cache import obter_dataset_cacheado
|
|
14
|
-
from pyield._internal.retry import retry_padrao
|
|
15
|
-
from pyield._internal.types import DateLike
|
|
16
|
-
|
|
17
|
-
BOND_TYPES = Literal["LFT", "NTN-B", "NTN-C", "LTN", "NTN-F", "PRE"]
|
|
18
|
-
|
|
19
|
-
ANBIMA_URL = "https://www.anbima.com.br/informacoes/merc-sec/arqs"
|
|
20
|
-
ANBIMA_RTM_HOSTNAME = "www.anbima.associados.rtm"
|
|
21
|
-
ANBIMA_RTM_URL = f"http://{ANBIMA_RTM_HOSTNAME}/merc_sec/arqs"
|
|
22
|
-
# Exemplo de URL: https://www.anbima.com.br/informacoes/merc-sec/arqs/ms240614.txt
|
|
23
|
-
|
|
24
|
-
# Antes de 13/05/2014 o arquivo era zipado e o endpoint terminava com ".exe"
|
|
25
|
-
DATA_MUDANCA_FORMATO = dt.date(2014, 5, 13)
|
|
26
|
-
|
|
27
|
-
DIAS_RETENCAO_PUBLICA = 5
|
|
28
|
-
|
|
29
|
-
# Única fonte de verdade para colunas do CSV: (nome_csv, nome_novo, tipo)
|
|
30
|
-
# Colunas de data são lidas como String e convertidas em _processar_df_bruto
|
|
31
|
-
TPF_COLUNAS = [
|
|
32
|
-
("Titulo", "BondType", pl.String),
|
|
33
|
-
("Data Referencia", "ReferenceDate", pl.String),
|
|
34
|
-
("Codigo SELIC", "SelicCode", pl.Int64),
|
|
35
|
-
("Data Base/Emissao", "IssueBaseDate", pl.String),
|
|
36
|
-
("Data Vencimento", "MaturityDate", pl.String),
|
|
37
|
-
("Tx. Compra", "BidRate", pl.Float64),
|
|
38
|
-
("Tx. Venda", "AskRate", pl.Float64),
|
|
39
|
-
("Tx. Indicativas", "IndicativeRate", pl.Float64),
|
|
40
|
-
("PU", "Price", pl.Float64),
|
|
41
|
-
("Desvio padrao", "StdDev", pl.Float64),
|
|
42
|
-
("Interv. Ind. Inf. (D0)", "LowerBoundRateD0", pl.Float64),
|
|
43
|
-
("Interv. Ind. Sup. (D0)", "UpperBoundRateD0", pl.Float64),
|
|
44
|
-
("Interv. Ind. Inf. (D+1)", "LowerBoundRateD1", pl.Float64),
|
|
45
|
-
("Interv. Ind. Sup. (D+1)", "UpperBoundRateD1", pl.Float64),
|
|
46
|
-
("Criterio", "Criteria", pl.String),
|
|
47
|
-
]
|
|
48
|
-
|
|
49
|
-
# Derivados automaticamente
|
|
50
|
-
ESQUEMA_TPF = {csv: tipo for csv, _, tipo in TPF_COLUNAS}
|
|
51
|
-
MAPA_NOMES_COLUNAS = {csv: novo for csv, novo, _ in TPF_COLUNAS}
|
|
52
|
-
|
|
53
|
-
logger = logging.getLogger(__name__)
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
def _mapear_tipo_titulo(tipo_titulo: str) -> list[str]:
|
|
57
|
-
tipo_titulo = tipo_titulo.upper()
|
|
58
|
-
mapa_titulos = {
|
|
59
|
-
"PRE": ["LTN", "NTN-F"],
|
|
60
|
-
"NTNB": ["NTN-B"],
|
|
61
|
-
"NTNC": ["NTN-C"],
|
|
62
|
-
"NTNF": ["NTN-F"],
|
|
63
|
-
}
|
|
64
|
-
return mapa_titulos.get(tipo_titulo, [tipo_titulo])
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
def _montar_nome_arquivo(data: dt.date) -> str:
|
|
68
|
-
data_url = data.strftime("%y%m%d")
|
|
69
|
-
if data < DATA_MUDANCA_FORMATO:
|
|
70
|
-
nome_arquivo = f"ms{data_url}.exe"
|
|
71
|
-
else:
|
|
72
|
-
nome_arquivo = f"ms{data_url}.txt"
|
|
73
|
-
return nome_arquivo
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
def _montar_url_arquivo(data: dt.date) -> str:
|
|
77
|
-
ultimo_dia_util = bday.last_business_day()
|
|
78
|
-
qtd_dias_uteis = bday.count(data, ultimo_dia_util)
|
|
79
|
-
if qtd_dias_uteis > DIAS_RETENCAO_PUBLICA:
|
|
80
|
-
# Para datas com mais de 5 dias úteis, apenas os dados da RTM estão disponíveis
|
|
81
|
-
logger.info("Tentando buscar dados RTM para %s", data.strftime("%d/%m/%Y"))
|
|
82
|
-
url_arquivo = f"{ANBIMA_RTM_URL}/{_montar_nome_arquivo(data)}"
|
|
83
|
-
else:
|
|
84
|
-
url_arquivo = f"{ANBIMA_URL}/{_montar_nome_arquivo(data)}"
|
|
85
|
-
return url_arquivo
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
@retry_padrao
|
|
89
|
-
def _obter_csv(data: dt.date) -> bytes:
|
|
90
|
-
url_arquivo = _montar_url_arquivo(data)
|
|
91
|
-
resposta = requests.get(url_arquivo, timeout=10)
|
|
92
|
-
resposta.raise_for_status()
|
|
93
|
-
return resposta.content
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
def _ler_csv(csv_texto: bytes) -> pl.DataFrame:
|
|
97
|
-
"""
|
|
98
|
-
Exemplo de arquivo bruto da ANBIMA:
|
|
99
|
-
ANBIMA - Associação Brasileira das Entidades dos Mercados Financeiro e de Capitais
|
|
100
|
-
|
|
101
|
-
Titulo@Data Referencia@Codigo SELIC@Data Base/Emissao@Data Vencimento@Tx. Compra@Tx. Venda@Tx. Indicativas@PU@Desvio padrao@Interv. Ind. Inf. (D0)@Interv. Ind. Sup. (D0)@Interv. Ind. Inf. (D+1)@Interv. Ind. Sup. (D+1)@Criterio
|
|
102
|
-
LTN@20250924@100000@20230707@20251001@14,9483@14,9263@14,9375@997,241543@0,00433039162894@14,7341@15,2612@14,7316@15,2689@Calculado
|
|
103
|
-
LTN@20250924@100000@20200206@20260101@14,7741@14,7485@14,7616@963,001853@0,00729826731971@14,7008@14,9986@14,7021@14,9975@Calculado
|
|
104
|
-
LTN@20250924@100000@20240105@20260401@14,7357@14,707@14,7205@931,607124@0,00317937979329@14,5525@14,9847@14,5669@14,9959@Calculado
|
|
105
|
-
...
|
|
106
|
-
""" # noqa
|
|
107
|
-
df = pl.read_csv(
|
|
108
|
-
source=csv_texto,
|
|
109
|
-
skip_lines=2,
|
|
110
|
-
separator="@",
|
|
111
|
-
null_values=["--"],
|
|
112
|
-
decimal_comma=True,
|
|
113
|
-
schema_overrides=ESQUEMA_TPF,
|
|
114
|
-
encoding="latin1",
|
|
115
|
-
)
|
|
116
|
-
return df
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
def _processar_df_bruto(df: pl.DataFrame) -> pl.DataFrame:
|
|
120
|
-
df = (
|
|
121
|
-
df.rename(MAPA_NOMES_COLUNAS)
|
|
122
|
-
.with_columns(
|
|
123
|
-
# Remove o percentual das taxas
|
|
124
|
-
# Colunas de taxa têm valores percentuais com 4 casas decimais
|
|
125
|
-
# Arredonda para 6 casas decimais para minimizar erros de ponto flutuante
|
|
126
|
-
cs.contains("Rate").truediv(100).round(6),
|
|
127
|
-
cs.ends_with("Date").str.to_date(format="%Y%m%d"),
|
|
128
|
-
)
|
|
129
|
-
# Substituir eventuais NaNs por None para compatibilidade com bancos de dados
|
|
130
|
-
.with_columns(cs.float().fill_nan(None))
|
|
131
|
-
)
|
|
132
|
-
return df
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
def _selecionar_e_ordenar_colunas(df: pl.DataFrame) -> pl.DataFrame:
|
|
136
|
-
"""Reordena as colunas do DataFrame de acordo com a ordem especificada."""
|
|
137
|
-
ordem_colunas = [
|
|
138
|
-
"BondType",
|
|
139
|
-
"ReferenceDate",
|
|
140
|
-
"SelicCode",
|
|
141
|
-
"IssueBaseDate",
|
|
142
|
-
"MaturityDate",
|
|
143
|
-
"Price",
|
|
144
|
-
"BidRate",
|
|
145
|
-
"AskRate",
|
|
146
|
-
"IndicativeRate",
|
|
147
|
-
]
|
|
148
|
-
ordem_colunas = [col for col in ordem_colunas if col in df.columns]
|
|
149
|
-
return df.select(ordem_colunas).sort("BondType", "MaturityDate")
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
def _buscar_dados_tpf(date: dt.date) -> pl.DataFrame:
|
|
153
|
-
"""Busca e processa dados do mercado secundário de TPF diretamente da fonte ANBIMA.
|
|
154
|
-
|
|
155
|
-
Esta é uma função de baixo nível para uso interno. Ela lida com a lógica
|
|
156
|
-
de construir a URL correta (pública ou RTM), baixar os dados com novas
|
|
157
|
-
tentativas e processá-los em um DataFrame estruturado.
|
|
158
|
-
|
|
159
|
-
Args:
|
|
160
|
-
date (dt.date): A data de referência para os dados.
|
|
161
|
-
|
|
162
|
-
Returns:
|
|
163
|
-
pl.DataFrame: Um DataFrame contendo os dados de mercado de títulos
|
|
164
|
-
processados, ou um DataFrame vazio se os dados não estiverem
|
|
165
|
-
disponíveis ou ocorrer um erro de conexão.
|
|
166
|
-
"""
|
|
167
|
-
url_arquivo = _montar_url_arquivo(date)
|
|
168
|
-
data_str = date.strftime("%d/%m/%Y")
|
|
169
|
-
|
|
170
|
-
# --- "FAIL-FAST" PARA EVITAR RETRIES DESNECESSÁRIOS NA RTM ---
|
|
171
|
-
if ANBIMA_RTM_URL in url_arquivo:
|
|
172
|
-
try:
|
|
173
|
-
# Tenta resolver o hostname da RTM. É uma verificação de rede rápida.
|
|
174
|
-
socket.gethostbyname(ANBIMA_RTM_HOSTNAME)
|
|
175
|
-
except socket.gaierror:
|
|
176
|
-
# Se falhar (gaierror = get address info error), não estamos na RTM.
|
|
177
|
-
# Não adianta prosseguir para a função com retry.
|
|
178
|
-
logger.warning(
|
|
179
|
-
f"Não foi possível resolver o host da RTM para {data_str}. "
|
|
180
|
-
"Isso é esperado fora da rede RTM. Dados históricos exigem acesso "
|
|
181
|
-
"à RTM. Retornando DataFrame vazio."
|
|
182
|
-
)
|
|
183
|
-
return pl.DataFrame()
|
|
184
|
-
|
|
185
|
-
try:
|
|
186
|
-
# Se passamos pela verificação da RTM, agora podemos chamar a função com retry.
|
|
187
|
-
csv_texto = _obter_csv(date)
|
|
188
|
-
if not csv_texto.strip():
|
|
189
|
-
logger.info(
|
|
190
|
-
f"Dados TPF de mercado secundário para {data_str} não disponíveis. "
|
|
191
|
-
"Retornando DataFrame vazio."
|
|
192
|
-
)
|
|
193
|
-
return pl.DataFrame()
|
|
194
|
-
|
|
195
|
-
df = _ler_csv(csv_texto)
|
|
196
|
-
df = _processar_df_bruto(df)
|
|
197
|
-
|
|
198
|
-
return df
|
|
199
|
-
|
|
200
|
-
except HTTPError as e:
|
|
201
|
-
if e.response.status_code == 404: # noqa
|
|
202
|
-
logger.info(
|
|
203
|
-
f"Dados TPF de mercado secundário para {data_str} (HTTP 404). "
|
|
204
|
-
"Retornando DataFrame vazio."
|
|
205
|
-
)
|
|
206
|
-
return pl.DataFrame()
|
|
207
|
-
logger.error(
|
|
208
|
-
"Erro HTTP ao buscar dados para %s de %s: %s", data_str, url_arquivo, e
|
|
209
|
-
)
|
|
210
|
-
raise
|
|
211
|
-
|
|
212
|
-
# Este bloco ainda é útil para outros URLErrors (ex: timeout genuíno na URL pública)
|
|
213
|
-
except RequestException:
|
|
214
|
-
logger.exception("RequestException ao buscar dados TPF para %s", data_str)
|
|
215
|
-
raise
|
|
216
|
-
|
|
217
|
-
except Exception:
|
|
218
|
-
msg = f"Ocorreu um erro inesperado ao buscar dados TPF para {data_str}"
|
|
219
|
-
logger.exception(msg)
|
|
220
|
-
raise
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
def tpf(
|
|
224
|
-
date: DateLike,
|
|
225
|
-
bond_type: BOND_TYPES | None = None,
|
|
226
|
-
) -> pl.DataFrame:
|
|
227
|
-
"""Recupera os dados do mercado secundário de TPF da ANBIMA.
|
|
228
|
-
|
|
229
|
-
Esta função busca taxas indicativas e outros dados de títulos públicos
|
|
230
|
-
brasileiros. Primeiro consulta o cache local; se não houver dados,
|
|
231
|
-
busca diretamente na fonte (ANBIMA).
|
|
232
|
-
|
|
233
|
-
Args:
|
|
234
|
-
date (DateLike): A data de referência para os dados (ex: '2024-06-14').
|
|
235
|
-
bond_type (str, optional): Filtra por tipo de título. Aceita os tipos
|
|
236
|
-
individuais ('LTN', 'NTN-F', 'NTN-B', 'NTN-C', 'LFT') ou 'PRE'
|
|
237
|
-
como atalho para prefixados ('LTN' e 'NTN-F').
|
|
238
|
-
|
|
239
|
-
Returns:
|
|
240
|
-
pl.DataFrame: Um DataFrame contendo os dados solicitados.
|
|
241
|
-
Retorna um DataFrame vazio se não houver dados para a data especificada (ex:
|
|
242
|
-
finais de semana, feriados ou datas futuras).
|
|
243
|
-
|
|
244
|
-
Examples:
|
|
245
|
-
>>> from pyield import anbima
|
|
246
|
-
>>> df = anbima.tpf(date="06-02-2026")
|
|
247
|
-
|
|
248
|
-
Data columns:
|
|
249
|
-
- BondType: Tipo do título público (e.g., 'LTN', 'NTN-B').
|
|
250
|
-
- ReferenceDate: Data de referência dos dados.
|
|
251
|
-
- SelicCode: Código do título no SELIC.
|
|
252
|
-
- IssueBaseDate: Data base ou de emissão do título.
|
|
253
|
-
- MaturityDate: Data de vencimento do título.
|
|
254
|
-
- Price: Preço Unitário (PU) calculado para liquidação em D0.
|
|
255
|
-
- BidRate: Taxa de compra para liquidação em D0 (decimal).
|
|
256
|
-
- AskRate: Taxa de venda para liquidação em D0 (decimal).
|
|
257
|
-
- IndicativeRate: Taxa indicativa para liquidação em D0 (decimal).
|
|
258
|
-
|
|
259
|
-
Notes:
|
|
260
|
-
A fonte dos dados segue a seguinte hierarquia:
|
|
261
|
-
|
|
262
|
-
1. **Cache Local:** Fornece acesso rápido a dados históricos
|
|
263
|
-
desde 01/01/2020.
|
|
264
|
-
2. **Fonte ANBIMA (fallback):** Se a data não estiver no cache,
|
|
265
|
-
busca automaticamente na fonte. Para datas recentes (até 5 dias
|
|
266
|
-
úteis), usa o site público. Para datas mais antigas, requer
|
|
267
|
-
acesso à rede RTM.
|
|
268
|
-
|
|
269
|
-
Para obter o dado completo direto da fonte (todas as colunas),
|
|
270
|
-
use :func:`fetch_tpf`.
|
|
271
|
-
"""
|
|
272
|
-
date = converter_datas(date)
|
|
273
|
-
|
|
274
|
-
if not data_referencia_valida(date):
|
|
275
|
-
return pl.DataFrame()
|
|
276
|
-
|
|
277
|
-
# Cache primeiro; se não tiver, busca na fonte
|
|
278
|
-
df = obter_dataset_cacheado("tpf")
|
|
279
|
-
if not df.is_empty():
|
|
280
|
-
df = df.filter(pl.col("ReferenceDate") == date)
|
|
281
|
-
if df.is_empty():
|
|
282
|
-
df = _buscar_dados_tpf(date)
|
|
283
|
-
df = _selecionar_e_ordenar_colunas(df)
|
|
284
|
-
|
|
285
|
-
if df.is_empty():
|
|
286
|
-
return pl.DataFrame()
|
|
287
|
-
|
|
288
|
-
if bond_type:
|
|
289
|
-
norm_bond_type = _mapear_tipo_titulo(bond_type)
|
|
290
|
-
df = df.filter(pl.col("BondType").is_in(norm_bond_type))
|
|
291
|
-
|
|
292
|
-
return df.sort("ReferenceDate", "BondType", "MaturityDate")
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
def fetch_tpf(
|
|
296
|
-
date: DateLike,
|
|
297
|
-
) -> pl.DataFrame:
|
|
298
|
-
"""Busca os dados do mercado secundário de TPF direto da fonte ANBIMA.
|
|
299
|
-
|
|
300
|
-
Retorna todas as colunas publicadas pela ANBIMA, sem cache e sem
|
|
301
|
-
filtro de colunas. Indicado para uso em jobs e pipelines de dados.
|
|
302
|
-
|
|
303
|
-
Args:
|
|
304
|
-
date (DateLike): Data de referência (ex: '2024-06-14').
|
|
305
|
-
|
|
306
|
-
Returns:
|
|
307
|
-
pl.DataFrame: DataFrame com todas as colunas da ANBIMA.
|
|
308
|
-
Retorna DataFrame vazio se não houver dados.
|
|
309
|
-
"""
|
|
310
|
-
date = converter_datas(date)
|
|
311
|
-
|
|
312
|
-
if not data_referencia_valida(date):
|
|
313
|
-
return pl.DataFrame()
|
|
314
|
-
|
|
315
|
-
df = _buscar_dados_tpf(date)
|
|
316
|
-
if df.is_empty():
|
|
317
|
-
return pl.DataFrame()
|
|
318
|
-
|
|
319
|
-
return df.sort("ReferenceDate", "BondType", "MaturityDate")
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
def tpf_maturities(
|
|
323
|
-
date: DateLike,
|
|
324
|
-
bond_type: BOND_TYPES,
|
|
325
|
-
) -> pl.Series:
|
|
326
|
-
"""Recupera os vencimentos existentes para um tipo de título na data especificada.
|
|
327
|
-
|
|
328
|
-
Args:
|
|
329
|
-
date (DateLike): A data de referência para os vencimentos.
|
|
330
|
-
bond_type (BOND_TYPES): O tipo de título para filtrar (ex: 'PRE' para 'LTN'
|
|
331
|
-
e 'NTN-F', ou especifique 'LTN' ou 'NTN-F' diretamente).
|
|
332
|
-
|
|
333
|
-
Returns:
|
|
334
|
-
pl.Series: Uma Series contendo as datas de vencimento únicas para o(s)
|
|
335
|
-
tipo(s) de título especificado(s).
|
|
336
|
-
|
|
337
|
-
Examples:
|
|
338
|
-
>>> from pyield import anbima
|
|
339
|
-
>>> anbima.tpf_maturities(date="22-08-2025", bond_type="PRE")
|
|
340
|
-
shape: (18,)
|
|
341
|
-
Series: 'MaturityDate' [date]
|
|
342
|
-
[
|
|
343
|
-
2025-10-01
|
|
344
|
-
2026-01-01
|
|
345
|
-
2026-04-01
|
|
346
|
-
2026-07-01
|
|
347
|
-
2026-10-01
|
|
348
|
-
…
|
|
349
|
-
2030-01-01
|
|
350
|
-
2031-01-01
|
|
351
|
-
2032-01-01
|
|
352
|
-
2033-01-01
|
|
353
|
-
2035-01-01
|
|
354
|
-
]
|
|
355
|
-
"""
|
|
356
|
-
return tpf(date, bond_type)["MaturityDate"].unique().sort()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|