pyield 0.45.0__tar.gz → 0.45.2__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.
Files changed (70) hide show
  1. {pyield-0.45.0 → pyield-0.45.2}/PKG-INFO +1 -1
  2. pyield-0.45.2/pyield/__about__.py +1 -0
  3. {pyield-0.45.0 → pyield-0.45.2}/pyield/_internal/converters.py +9 -0
  4. {pyield-0.45.0 → pyield-0.45.2}/pyield/anbima/__init__.py +3 -2
  5. pyield-0.45.2/pyield/anbima/imaq.py +170 -0
  6. {pyield-0.45.0 → pyield-0.45.2}/pyield/anbima/tpf.py +60 -157
  7. {pyield-0.45.0 → pyield-0.45.2}/pyield/b3/futures/historical.py +8 -5
  8. {pyield-0.45.0 → pyield-0.45.2}/pyield/b3/intraday_derivatives.py +1 -1
  9. {pyield-0.45.0 → pyield-0.45.2}/pyield/bc/ptax_api.py +1 -1
  10. {pyield-0.45.0 → pyield-0.45.2}/pyield/bc/rates.py +1 -1
  11. {pyield-0.45.0 → pyield-0.45.2}/pyield/bc/trades_intraday.py +1 -1
  12. {pyield-0.45.0 → pyield-0.45.2}/pyield/bc/vna.py +1 -1
  13. {pyield-0.45.0 → pyield-0.45.2}/pyield/tn/auctions.py +32 -27
  14. {pyield-0.45.0 → pyield-0.45.2}/pyield/tn/lft.py +32 -7
  15. {pyield-0.45.0 → pyield-0.45.2}/pyield/tn/ltn.py +36 -3
  16. {pyield-0.45.0 → pyield-0.45.2}/pyield/tn/ntnb.py +40 -6
  17. {pyield-0.45.0 → pyield-0.45.2}/pyield/tn/ntnc.py +35 -11
  18. {pyield-0.45.0 → pyield-0.45.2}/pyield/tn/ntnf.py +36 -4
  19. {pyield-0.45.0 → pyield-0.45.2}/pyield/tn/pre.py +15 -7
  20. {pyield-0.45.0 → pyield-0.45.2}/pyield/tn/utils.py +51 -0
  21. pyield-0.45.0/pyield/__about__.py +0 -1
  22. pyield-0.45.0/pyield/anbima/imaq.py +0 -286
  23. {pyield-0.45.0 → pyield-0.45.2}/.gitignore +0 -0
  24. {pyield-0.45.0 → pyield-0.45.2}/LICENSE +0 -0
  25. {pyield-0.45.0 → pyield-0.45.2}/README.md +0 -0
  26. {pyield-0.45.0 → pyield-0.45.2}/pyield/__init__.py +0 -0
  27. {pyield-0.45.0 → pyield-0.45.2}/pyield/_internal/__init__.py +0 -0
  28. {pyield-0.45.0 → pyield-0.45.2}/pyield/_internal/cache.py +0 -0
  29. {pyield-0.45.0 → pyield-0.45.2}/pyield/_internal/data_cache.py +0 -0
  30. {pyield-0.45.0 → pyield-0.45.2}/pyield/_internal/retry.py +0 -0
  31. {pyield-0.45.0 → pyield-0.45.2}/pyield/_internal/types.py +0 -0
  32. {pyield-0.45.0 → pyield-0.45.2}/pyield/anbima/difusao.py +0 -0
  33. {pyield-0.45.0 → pyield-0.45.2}/pyield/anbima/ettj_intraday.py +0 -0
  34. {pyield-0.45.0 → pyield-0.45.2}/pyield/anbima/ettj_last.py +0 -0
  35. {pyield-0.45.0 → pyield-0.45.2}/pyield/anbima/ima.py +0 -0
  36. {pyield-0.45.0 → pyield-0.45.2}/pyield/b3/__init__.py +0 -0
  37. {pyield-0.45.0 → pyield-0.45.2}/pyield/b3/_contracts.py +0 -0
  38. {pyield-0.45.0 → pyield-0.45.2}/pyield/b3/_validar_pregao.py +0 -0
  39. {pyield-0.45.0 → pyield-0.45.2}/pyield/b3/di1.py +0 -0
  40. {pyield-0.45.0 → pyield-0.45.2}/pyield/b3/di_over.py +0 -0
  41. {pyield-0.45.0 → pyield-0.45.2}/pyield/b3/futures/__init__.py +0 -0
  42. {pyield-0.45.0 → pyield-0.45.2}/pyield/b3/futures/common.py +0 -0
  43. {pyield-0.45.0 → pyield-0.45.2}/pyield/b3/futures/intraday.py +0 -0
  44. {pyield-0.45.0 → pyield-0.45.2}/pyield/b3/price_report.py +0 -0
  45. {pyield-0.45.0 → pyield-0.45.2}/pyield/bc/__init__.py +0 -0
  46. {pyield-0.45.0 → pyield-0.45.2}/pyield/bc/auction.py +0 -0
  47. {pyield-0.45.0 → pyield-0.45.2}/pyield/bc/copom.py +0 -0
  48. {pyield-0.45.0 → pyield-0.45.2}/pyield/bc/repo.py +0 -0
  49. {pyield-0.45.0 → pyield-0.45.2}/pyield/bc/trades_monthly.py +0 -0
  50. {pyield-0.45.0 → pyield-0.45.2}/pyield/bday/__init__.py +0 -0
  51. {pyield-0.45.0 → pyield-0.45.2}/pyield/bday/core.py +0 -0
  52. {pyield-0.45.0 → pyield-0.45.2}/pyield/bday/holidays/br_holidays_new.txt +0 -0
  53. {pyield-0.45.0 → pyield-0.45.2}/pyield/bday/holidays/br_holidays_old.txt +0 -0
  54. {pyield-0.45.0 → pyield-0.45.2}/pyield/bday/holidays/brholidays.py +0 -0
  55. {pyield-0.45.0 → pyield-0.45.2}/pyield/clock.py +0 -0
  56. {pyield-0.45.0 → pyield-0.45.2}/pyield/fwd.py +0 -0
  57. {pyield-0.45.0 → pyield-0.45.2}/pyield/interpolator.py +0 -0
  58. {pyield-0.45.0 → pyield-0.45.2}/pyield/ipca/__init__.py +0 -0
  59. {pyield-0.45.0 → pyield-0.45.2}/pyield/ipca/historical.py +0 -0
  60. {pyield-0.45.0 → pyield-0.45.2}/pyield/ipca/projected.py +0 -0
  61. {pyield-0.45.0 → pyield-0.45.2}/pyield/py.typed +0 -0
  62. {pyield-0.45.0 → pyield-0.45.2}/pyield/rmd.py +0 -0
  63. {pyield-0.45.0 → pyield-0.45.2}/pyield/selic/__init__.py +0 -0
  64. {pyield-0.45.0 → pyield-0.45.2}/pyield/selic/cpm.py +0 -0
  65. {pyield-0.45.0 → pyield-0.45.2}/pyield/selic/probabilities.py +0 -0
  66. {pyield-0.45.0 → pyield-0.45.2}/pyield/tn/__init__.py +0 -0
  67. {pyield-0.45.0 → pyield-0.45.2}/pyield/tn/benchmark.py +0 -0
  68. {pyield-0.45.0 → pyield-0.45.2}/pyield/tn/ntnb1.py +0 -0
  69. {pyield-0.45.0 → pyield-0.45.2}/pyield/tn/ntnbprinc.py +0 -0
  70. {pyield-0.45.0 → pyield-0.45.2}/pyproject.toml +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyield
3
- Version: 0.45.0
3
+ Version: 0.45.2
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.45.2"
@@ -7,6 +7,15 @@ from pyield._internal import types
7
7
  from pyield._internal.types import ArrayLike, DateLike
8
8
 
9
9
 
10
+ def data_referencia_valida(date: dt.date | None) -> bool:
11
+ """Verifica se a data é dia útil e não está no futuro."""
12
+ if date is None:
13
+ return False
14
+ from pyield import bday, clock # noqa: PLC0415
15
+
16
+ return bday.is_business_day(date) and date <= clock.today()
17
+
18
+
10
19
  def converter_datas_expr(expr: pl.Expr | str) -> pl.Expr:
11
20
  """Converte expressão Polars para ``Date`` com parse tolerante por linha.
12
21
 
@@ -3,13 +3,14 @@ from pyield.anbima.ettj_intraday import intraday_ettj
3
3
  from pyield.anbima.ettj_last import last_ettj
4
4
  from pyield.anbima.ima import last_ima
5
5
  from pyield.anbima.imaq import imaq
6
- from pyield.anbima.tpf import tpf_data, tpf_maturities
6
+ from pyield.anbima.tpf import fetch_tpf, tpf, tpf_maturities
7
7
 
8
8
  __all__ = [
9
9
  "last_ima",
10
10
  "imaq",
11
- "tpf_data",
11
+ "tpf",
12
12
  "tpf_maturities",
13
+ "fetch_tpf",
13
14
  "last_ettj",
14
15
  "intraday_ettj",
15
16
  "tpf_difusao",
@@ -0,0 +1,170 @@
1
+ """
2
+ Exemplo de página HTML:
3
+ Título Codigo Selic Código ISIN Data de Vencimento Quantidade em Mercado (1.000 Títulos) PU (R$) Valor de Mercado (R$ Mil) Variação da Quantidade (1.000 Títulos) Status do Titulo
4
+ LTN 100000 BRSTNCLTN863 01/10/2025 115.870,772 997,241543 115.551.147 0,000 Participante Definitivo
5
+ LTN 100000 BRSTNCLTN7U7 01/01/2026 176.807,732 963,001853 170.266.174 -1,987 Participante Definitivo
6
+ LTN 100000 BRSTNCLTN8B5 01/04/2026 115.826,847 931,607124 107.905.116 0,000 Participante Definitivo
7
+ """ # noqa
8
+
9
+ import datetime as dt
10
+
11
+ import polars as pl
12
+ import polars.selectors as ps
13
+ import requests
14
+ from lxml.html import HTMLParser
15
+ from lxml.html import fromstring as html_fromstring
16
+
17
+ import pyield._internal.converters as cv
18
+ from pyield._internal.cache import ttl_cache
19
+ from pyield._internal.retry import retry_padrao
20
+ from pyield._internal.types import DateLike
21
+
22
+ URL_IMA = "https://www.anbima.com.br/informacoes/ima/ima-quantidade-mercado.asp"
23
+
24
+
25
+ @ttl_cache()
26
+ @retry_padrao
27
+ def _buscar_conteudo_url(data_referencia: dt.date) -> bytes:
28
+ data_referencia_str = data_referencia.strftime("%d/%m/%Y")
29
+ payload = {
30
+ "Tipo": "",
31
+ "DataRef": "",
32
+ "Pai": "ima",
33
+ "Dt_Ref_Ver": "20250117",
34
+ "Dt_Ref": f"{data_referencia_str}",
35
+ }
36
+
37
+ resposta = requests.post(URL_IMA, data=payload, timeout=10)
38
+ resposta.raise_for_status()
39
+ if "Não há dados disponíveis" in resposta.text:
40
+ return b""
41
+ return resposta.content
42
+
43
+
44
+ def _normalizar_nome_coluna(texto: str) -> str:
45
+ """Normaliza cabeçalhos removendo quebras de linha e espaços extras."""
46
+ return " ".join(texto.strip().split())
47
+
48
+
49
+ def _parsear_tabelas_html(html_content: bytes) -> pl.DataFrame:
50
+ """Parseia tabelas HTML com lxml e retorna DataFrame via read_csv.
51
+
52
+ Extrai dados das tabelas aninhadas (com parent::td),
53
+ converte para CSV (separador tab) e lê com Polars.
54
+ """
55
+ html_content = html_content.replace(b"<br>", b" ").replace(b"<BR>", b" ")
56
+
57
+ parser = HTMLParser(encoding="iso-8859-1")
58
+ tree = html_fromstring(html_content, parser=parser)
59
+
60
+ nested_tables = tree.xpath("//table[@width='100%'][parent::td]")
61
+
62
+ linhas = []
63
+ nomes_colunas = None
64
+
65
+ for table in nested_tables: # type: ignore[misc]
66
+ headers = table.xpath(".//thead//th")
67
+ if not nomes_colunas:
68
+ nomes_colunas = [_normalizar_nome_coluna(h.text_content()) for h in headers]
69
+
70
+ data_rows = table.xpath(".//tbody//tr[td]")
71
+ for row in data_rows:
72
+ cells = row.xpath(".//td")
73
+ if len(cells) != len(nomes_colunas):
74
+ continue
75
+ linhas.append("\t".join(c.text_content().strip() for c in cells))
76
+
77
+ if not linhas or not nomes_colunas:
78
+ return pl.DataFrame()
79
+
80
+ cabecalho = "\t".join(nomes_colunas)
81
+ csv_bytes = ("\n".join([cabecalho, *linhas])).encode("utf-8")
82
+ return pl.read_csv(
83
+ csv_bytes,
84
+ separator="\t",
85
+ infer_schema=False,
86
+ null_values="--",
87
+ )
88
+
89
+
90
+ def _numero_br(coluna: str) -> pl.Expr:
91
+ """Converte coluna string no formato numérico brasileiro para Float64."""
92
+ return (
93
+ pl.col(coluna)
94
+ .str.replace_all(".", "", literal=True)
95
+ .str.replace(",", ".", literal=True)
96
+ .cast(pl.Float64)
97
+ )
98
+
99
+
100
+ def _inteiro_m(coluna: str) -> pl.Expr:
101
+ """Converte coluna numérica BR em milhares para inteiro (unidades)."""
102
+ return (_numero_br(coluna) * 1000).round(0).cast(pl.Int64)
103
+
104
+
105
+ def _processar_df(df: pl.DataFrame, data_referencia: dt.date) -> pl.DataFrame:
106
+ """Filtra, converte tipos e aplica transformações numéricas."""
107
+ return (
108
+ df.with_columns(ps.string().str.strip_chars().name.keep())
109
+ .filter(
110
+ pl.col("Data de Vencimento").is_not_null(),
111
+ pl.col("Título") != "Título",
112
+ )
113
+ .unique(subset="Código ISIN")
114
+ .select(
115
+ Date=data_referencia,
116
+ BondType=pl.col("Título"),
117
+ MaturityDate=pl.col("Data de Vencimento").str.to_date(format="%d/%m/%Y"),
118
+ SelicCode=pl.col("Codigo Selic").cast(pl.Int64),
119
+ ISIN=pl.col("Código ISIN"),
120
+ Price=_numero_br("PU (R$)"),
121
+ MarketQuantity=_inteiro_m("Quantidade em Mercado (1.000 Títulos)"),
122
+ MarketValue=_inteiro_m("Valor de Mercado (R$ Mil)"),
123
+ QuantityVariation=_inteiro_m("Variação da Quantidade (1.000 Títulos)"),
124
+ BondStatus=pl.col("Status do Titulo"),
125
+ )
126
+ .sort("BondType", "MaturityDate")
127
+ )
128
+
129
+
130
+ def imaq(date: DateLike) -> pl.DataFrame:
131
+ """Consulta e processa dados de estoque IMA-Q da ANBIMA para uma data.
132
+
133
+ Args:
134
+ date: Data de referência. Apenas os últimos 5 dias úteis estão disponíveis;
135
+ o dado do dia anterior costuma ser publicado ao longo do dia.
136
+
137
+ Returns:
138
+ DataFrame com dados processados ou DataFrame vazio se a data for
139
+ inválida ou não houver dados disponíveis.
140
+
141
+ Output Columns:
142
+ - Date (Date): data de referência dos dados.
143
+ - BondType (String): tipo do título (LTN, NTN-B, NTN-F, LFT, …).
144
+ - MaturityDate (Date): data de vencimento do título.
145
+ - SelicCode (Int64): código SELIC do título.
146
+ - ISIN (String): código ISIN (International Securities Id Number).
147
+ - Price (Float64): PU do título em R$.
148
+ - MarketQuantity (Int64): quantidade em mercado (unidades).
149
+ - MarketValue (Int64): valor de mercado em R$.
150
+ - QuantityVariation (Int64): variação diária da quantidade.
151
+ - BondStatus (String): status do título.
152
+
153
+ Notes:
154
+ Valores convertidos para unidades puras (ex: MarketQuantity × 1.000).
155
+
156
+ Examples:
157
+ >>> yd.anbima.imaq("04-02-2026") # doctest: +SKIP
158
+ """
159
+ data = cv.converter_datas(date)
160
+ if not cv.data_referencia_valida(data):
161
+ return pl.DataFrame()
162
+
163
+ url_content = _buscar_conteudo_url(data)
164
+ if not url_content:
165
+ return pl.DataFrame()
166
+
167
+ df = _parsear_tabelas_html(url_content)
168
+ if df.is_empty():
169
+ return pl.DataFrame()
170
+ return _processar_df(df, data)
@@ -8,16 +8,11 @@ import polars.selectors as cs
8
8
  import requests
9
9
  from requests.exceptions import HTTPError, RequestException
10
10
 
11
- from pyield import bday, clock
12
- from pyield._internal.converters import converter_datas
11
+ from pyield import bday
12
+ from pyield._internal.converters import converter_datas, data_referencia_valida
13
13
  from pyield._internal.data_cache import obter_dataset_cacheado
14
14
  from pyield._internal.retry import retry_padrao
15
- from pyield._internal.types import DateLike, any_is_empty
16
- from pyield.b3 import di1
17
- from pyield.bc.ptax_api import ptax
18
- from pyield.tn.ntnb import duration as duration_b
19
- from pyield.tn.ntnc import duration as duration_c
20
- from pyield.tn.ntnf import duration as duration_f
15
+ from pyield._internal.types import DateLike
21
16
 
22
17
  BOND_TYPES = Literal["LFT", "NTN-B", "NTN-C", "LTN", "NTN-F", "PRE"]
23
18
 
@@ -131,89 +126,9 @@ def _processar_df_bruto(df: pl.DataFrame) -> pl.DataFrame:
131
126
  cs.contains("Rate").truediv(100).round(6),
132
127
  cs.ends_with("Date").str.to_date(format="%Y%m%d"),
133
128
  )
134
- .with_columns(
135
- BDToMat=bday.count_expr("ReferenceDate", "MaturityDate"),
136
- )
137
- )
138
- return df
139
-
140
-
141
- def _calcular_duracao_por_linha(linha: dict) -> float:
142
- """Função auxiliar que será aplicada a cada linha do struct."""
143
- # Mapeia o BondType para a função de duration correspondente
144
- # Isso torna a lógica dentro do lambda ainda mais limpa
145
- tipo_titulo = linha["BondType"]
146
- if tipo_titulo == "LTN":
147
- return linha["BDToMat"] / 252 # A lógica da LTN depende apenas do BDToMat
148
-
149
- funcoes_duracao = {
150
- "NTN-F": duration_f,
151
- "NTN-B": duration_b,
152
- "NTN-C": duration_c,
153
- }
154
-
155
- func_duracao = funcoes_duracao.get(tipo_titulo) # Busca da função correta
156
- if func_duracao:
157
- return func_duracao(
158
- linha["ReferenceDate"],
159
- linha["MaturityDate"],
160
- linha["IndicativeRate"],
161
- )
162
- # Se o BondType não for reconhecido, retorna 0.0 (LFT ou outros)
163
- return 0.0
164
-
165
-
166
- def _adicionar_duracao(df_input: pl.DataFrame) -> pl.DataFrame:
167
- """Adiciona a coluna 'Duration' ao DataFrame Polars de forma otimizada."""
168
- colunas_necessarias = [
169
- "BondType",
170
- "ReferenceDate",
171
- "MaturityDate",
172
- "IndicativeRate",
173
- "BDToMat", # Necessário para LTN
174
- ]
175
- # Adiciona a coluna Duration
176
- df = df_input.with_columns(
177
- pl.struct(colunas_necessarias)
178
- .map_elements(_calcular_duracao_por_linha, return_dtype=pl.Float64)
179
- .alias("Duration")
180
- )
181
- return df
182
-
183
-
184
- def _adicionar_prazo_medio(df: pl.DataFrame) -> pl.DataFrame:
185
- # Na metodologia do Tesouro Nacional, a maturidade média é a mesma que a duração
186
- df = df.with_columns(
187
- pl.when(pl.col("BondType") == "LFT")
188
- .then(pl.col("BDToMat") / 252)
189
- .otherwise("Duration")
190
- .alias("AvgMaturity")
191
- )
192
- return df
193
-
194
-
195
- def _adicionar_dv01(df_input: pl.DataFrame, data_ref: dt.date) -> pl.DataFrame:
196
- """Adiciona as colunas de DV01 ao DataFrame."""
197
- expr_duracao_mod = pl.col("Duration") / (1 + pl.col("IndicativeRate"))
198
- df = df_input.with_columns(DV01=0.0001 * expr_duracao_mod * pl.col("Price"))
199
-
200
- # DV01 em USD
201
- try:
202
- taxa_ptax = ptax(date=data_ref)
203
- df = df.with_columns(DV01USD=pl.col("DV01") / taxa_ptax)
204
- except Exception as e:
205
- logger.error("Erro ao adicionar DV01 em USD: %s", e)
206
- return df
207
-
208
-
209
- def _adicionar_taxa_di(df: pl.DataFrame, data_ref: dt.date) -> pl.DataFrame:
210
- """Adiciona a coluna de taxa DI ao DataFrame."""
211
- taxas_di = di1.interpolate_rates(
212
- dates=data_ref,
213
- expirations=df["MaturityDate"],
214
- extrapolate=True,
129
+ # Substituir eventuais NaNs por None para compatibilidade com bancos de dados
130
+ .with_columns(cs.float().fill_nan(None))
215
131
  )
216
- df = df.with_columns(DIRate=taxas_di)
217
132
  return df
218
133
 
219
134
 
@@ -225,22 +140,10 @@ def _selecionar_e_ordenar_colunas(df: pl.DataFrame) -> pl.DataFrame:
225
140
  "SelicCode",
226
141
  "IssueBaseDate",
227
142
  "MaturityDate",
228
- "BDToMat",
229
- "Duration",
230
- "AvgMaturity",
231
- "DV01",
232
- "DV01USD",
233
143
  "Price",
234
144
  "BidRate",
235
145
  "AskRate",
236
146
  "IndicativeRate",
237
- "DIRate",
238
- # "StdDev",
239
- # "LowerBoundRateD0",
240
- # "UpperBoundRateD0",
241
- # "LowerBoundRateD1",
242
- # "UpperBoundRateD1",
243
- "Criteria",
244
147
  ]
245
148
  ordem_colunas = [col for col in ordem_colunas if col in df.columns]
246
149
  return df.select(ordem_colunas).sort("BondType", "MaturityDate")
@@ -291,13 +194,6 @@ def _buscar_dados_tpf(date: dt.date) -> pl.DataFrame:
291
194
 
292
195
  df = _ler_csv(csv_texto)
293
196
  df = _processar_df_bruto(df)
294
- df = _adicionar_duracao(df)
295
- df = _adicionar_prazo_medio(df)
296
- df = _adicionar_dv01(df, date)
297
- df = _adicionar_taxa_di(df, date)
298
- df = _selecionar_e_ordenar_colunas(df)
299
- # Substituir eventuais NaNs por None para compatibilidade com bancos de dados
300
- df = df.with_columns(cs.float().fill_nan(None))
301
197
 
302
198
  return df
303
199
 
@@ -324,24 +220,21 @@ def _buscar_dados_tpf(date: dt.date) -> pl.DataFrame:
324
220
  raise
325
221
 
326
222
 
327
- def tpf_data(
223
+ def tpf(
328
224
  date: DateLike,
329
225
  bond_type: BOND_TYPES | None = None,
330
- fetch_from_source: bool = False,
331
226
  ) -> pl.DataFrame:
332
227
  """Recupera os dados do mercado secundário de TPF da ANBIMA.
333
228
 
334
229
  Esta função busca taxas indicativas e outros dados de títulos públicos
335
- brasileiros. A obtenção dos dados segue uma hierarquia de fontes para
336
- otimizar o desempenho e o acesso.
230
+ brasileiros. Primeiro consulta o cache local; se não houver dados,
231
+ busca diretamente na fonte (ANBIMA).
337
232
 
338
233
  Args:
339
234
  date (DateLike): A data de referência para os dados (ex: '2024-06-14').
340
- bond_type (str, optional): Filtra os resultados por um tipo de título
341
- específico (ex: 'LTN', 'NTN-B'). Por padrão, retorna todos os tipos.
342
- fetch_from_source (bool, optional): Se True, força a função a ignorar o
343
- cache e buscar os dados diretamente da fonte (ANBIMA).
344
- Padrão é False.
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').
345
238
 
346
239
  Returns:
347
240
  pl.DataFrame: Um DataFrame contendo os dados solicitados.
@@ -350,7 +243,7 @@ def tpf_data(
350
243
 
351
244
  Examples:
352
245
  >>> from pyield import anbima
353
- >>> df = anbima.tpf_data(date="06-02-2026")
246
+ >>> df = anbima.tpf(date="06-02-2026")
354
247
 
355
248
  Data columns:
356
249
  - BondType: Tipo do título público (e.g., 'LTN', 'NTN-B').
@@ -358,53 +251,36 @@ def tpf_data(
358
251
  - SelicCode: Código do título no SELIC.
359
252
  - IssueBaseDate: Data base ou de emissão do título.
360
253
  - MaturityDate: Data de vencimento do título.
361
- - BDToMat: Número de dias úteis entre a data de referência e o vencimento.
362
- - Duration: Macaulay Duration do título em anos.
363
- - AvgMaturity: Prazo médio do título (em anos), conforme metodologia
364
- do Tesouro Nacional.
365
- - DV01: Variação financeira no preço do título (em BRL) para uma
366
- mudança de 1 basis point (0,01%) na taxa de juros.
367
- - DV01USD: O mesmo que DV01, mas convertido para USD pela PTAX do dia.
368
- - Price: Preço Unitário (PU) do título na data de referência.
369
- - BidRate: Taxa de compra em formato decimal (e.g., 0.10 para 10%).
370
- - AskRate: Taxa de venda em formato decimal.
371
- - IndicativeRate: Taxa indicativa em formato decimal.
372
- - DIRate: Taxa DI interpolada (flatforward) no vencimento do título.
373
- - StdDev: Desvio padrão da taxa indicativa.
374
- - LowerBoundRateD0: Limite inferior do intervalo indicativo para D+0.
375
- - UpperBoundRateD0: Limite superior do intervalo indicativo para D+0.
376
- - LowerBoundRateD1: Limite inferior do intervalo indicativo para D+1.
377
- - UpperBoundRateD1: Limite superior do intervalo indicativo para D+1.
378
- - Criteria: Critério utilizado pela ANBIMA para o cálculo.
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).
379
258
 
380
259
  Notes:
381
260
  A fonte dos dados segue a seguinte hierarquia:
382
261
 
383
- 1. **Cache Local (Padrão):** Fornece acesso rápido a dados históricos
384
- desde 01/01/2020. É utilizado por padrão (`fetch_from_source=False`).
385
- 2. **Site Público da ANBIMA:** Acessado quando `fetch_from_source=True`,
386
- disponibiliza os dados dos últimos 5 dias úteis.
387
- 3. **Rede RTM da ANBIMA:** Acessada quando `fetch_from_source=True` para
388
- datas com mais de 5 dias úteis. O acesso ao histórico completo
389
- requer uma conexão à rede RTM. Sem ela, a consulta para datas
390
- antigas retornará um DataFrame vazio.
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`.
391
271
  """
392
- if any_is_empty(date):
393
- return pl.DataFrame()
394
272
  date = converter_datas(date)
395
273
 
396
- if not bday.is_business_day(date):
397
- return pl.DataFrame()
398
-
399
- if date > clock.today():
274
+ if not data_referencia_valida(date):
400
275
  return pl.DataFrame()
401
276
 
402
- if fetch_from_source:
403
- # Tenta buscar os dados diretamente da fonte (ANBIMA)
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():
404
282
  df = _buscar_dados_tpf(date)
405
- else:
406
- # Caso contrário, obtém os dados do cache local
407
- df = obter_dataset_cacheado("tpf").filter(pl.col("ReferenceDate") == date)
283
+ df = _selecionar_e_ordenar_colunas(df)
408
284
 
409
285
  if df.is_empty():
410
286
  return pl.DataFrame()
@@ -416,6 +292,33 @@ def tpf_data(
416
292
  return df.sort("ReferenceDate", "BondType", "MaturityDate")
417
293
 
418
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
+
419
322
  def tpf_maturities(
420
323
  date: DateLike,
421
324
  bond_type: BOND_TYPES,
@@ -450,4 +353,4 @@ def tpf_maturities(
450
353
  2035-01-01
451
354
  ]
452
355
  """
453
- return tpf_data(date, bond_type)["MaturityDate"].unique().sort()
356
+ return tpf(date, bond_type)["MaturityDate"].unique().sort()
@@ -149,11 +149,14 @@ def listar_datas_disponiveis(codigo_contrato: str) -> pl.Series:
149
149
  def _buscar_price_report(
150
150
  data: dt.date, codigo: str, full_report: bool | None
151
151
  ) -> pl.DataFrame:
152
- """Busca o price report da B3, com fallback SPR→PR quando full_report=None."""
153
- if full_report is not None:
154
- return fetch_price_report(
155
- date=data, contract_code=codigo, full_report=full_report
156
- )
152
+ """Busca o price report da B3, com fallback SPR→PR.
153
+
154
+ Quando full_report é True, usa apenas o PR (completo).
155
+ Nos demais casos (None ou False), tenta o SPR (leve) primeiro
156
+ e faz fallback para o PR se o SPR estiver vazio.
157
+ """
158
+ if full_report is True:
159
+ return fetch_price_report(date=data, contract_code=codigo, full_report=True)
157
160
 
158
161
  # SPR (leve) primeiro; PR (pesado) como fallback
159
162
  df = fetch_price_report(date=data, contract_code=codigo, full_report=False)
@@ -50,7 +50,7 @@ def _mapa_renomeacao_intraday() -> dict[str, str]:
50
50
  return {nome_orig: nome_novo for nome_orig, nome_novo, _ in COLUNAS_INTRADAY}
51
51
 
52
52
 
53
- @ttl_cache(ttl=15)
53
+ @ttl_cache(ttl=10)
54
54
  @retry_padrao
55
55
  def _buscar_json_intraday(codigo_contrato: str) -> list[dict]:
56
56
  url = f"{URL_BASE_INTRADAY}/{codigo_contrato}"
@@ -54,7 +54,7 @@ def _montar_url_api(inicio: dt.date, fim: dt.date) -> str:
54
54
  return url
55
55
 
56
56
 
57
- @ttl_cache(ttl=15)
57
+ @ttl_cache()
58
58
  @retry_padrao
59
59
  def _buscar_texto_api(url: str) -> bytes:
60
60
  resposta = requests.get(url, timeout=10)
@@ -43,7 +43,7 @@ class SerieBC(Enum):
43
43
  DI_OVER = 11
44
44
 
45
45
 
46
- @ttl_cache(ttl=15)
46
+ @ttl_cache()
47
47
  @retry_padrao
48
48
  def _chamar_api(url_api: str) -> list[dict[str, str]]:
49
49
  """Executa uma chamada GET na API do BCB e retorna o JSON.
@@ -94,7 +94,7 @@ ORDEM_COLUNAS_FINAL = [
94
94
  ]
95
95
 
96
96
 
97
- @ttl_cache(ttl=15)
97
+ @ttl_cache()
98
98
  @retry_padrao
99
99
  def _buscar_csv() -> str:
100
100
  """
@@ -10,7 +10,7 @@ from pyield._internal.types import DateLike, any_is_empty
10
10
  logger = logging.getLogger(__name__)
11
11
 
12
12
 
13
- @ttl_cache(ttl=15)
13
+ @ttl_cache()
14
14
  @retry_padrao
15
15
  def _baixar_texto(date: DateLike) -> str:
16
16
  """Baixa o arquivo diário do SELIC no site do BCB."""