PYield 0.44.0__tar.gz → 0.44.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 (68) hide show
  1. {pyield-0.44.0 → pyield-0.44.2}/.gitignore +3 -1
  2. {pyield-0.44.0 → pyield-0.44.2}/PKG-INFO +2 -1
  3. pyield-0.44.2/pyield/__about__.py +1 -0
  4. {pyield-0.44.0 → pyield-0.44.2}/pyield/anbima/difusao.py +27 -26
  5. {pyield-0.44.0 → pyield-0.44.2}/pyield/anbima/ettj_intraday.py +2 -0
  6. {pyield-0.44.0 → pyield-0.44.2}/pyield/anbima/ettj_last.py +2 -0
  7. {pyield-0.44.0 → pyield-0.44.2}/pyield/anbima/ima.py +27 -25
  8. {pyield-0.44.0 → pyield-0.44.2}/pyield/anbima/imaq.py +18 -16
  9. {pyield-0.44.0 → pyield-0.44.2}/pyield/anbima/tpf.py +27 -43
  10. {pyield-0.44.0 → pyield-0.44.2}/pyield/b3/futures/__init__.py +24 -34
  11. {pyield-0.44.0 → pyield-0.44.2}/pyield/b3/futures/historical.py +1 -2
  12. {pyield-0.44.0 → pyield-0.44.2}/pyield/b3/intraday_derivatives.py +2 -0
  13. {pyield-0.44.0 → pyield-0.44.2}/pyield/b3/price_report.py +32 -19
  14. {pyield-0.44.0 → pyield-0.44.2}/pyield/bc/auction.py +26 -26
  15. {pyield-0.44.0 → pyield-0.44.2}/pyield/bc/ptax_api.py +10 -8
  16. {pyield-0.44.0 → pyield-0.44.2}/pyield/bc/rates.py +2 -0
  17. {pyield-0.44.0 → pyield-0.44.2}/pyield/bc/repo.py +18 -18
  18. {pyield-0.44.0 → pyield-0.44.2}/pyield/bc/trades_intraday.py +37 -37
  19. {pyield-0.44.0 → pyield-0.44.2}/pyield/bc/trades_monthly.py +24 -24
  20. {pyield-0.44.0 → pyield-0.44.2}/pyield/bc/vna.py +2 -0
  21. {pyield-0.44.0 → pyield-0.44.2}/pyield/ipca/projected.py +2 -0
  22. {pyield-0.44.0 → pyield-0.44.2}/pyield/selic/__init__.py +3 -3
  23. {pyield-0.44.0 → pyield-0.44.2}/pyield/selic/cpm.py +122 -3
  24. pyield-0.44.2/pyield/selic/probabilities.py +340 -0
  25. {pyield-0.44.0 → pyield-0.44.2}/pyield/tn/auctions.py +26 -26
  26. {pyield-0.44.0 → pyield-0.44.2}/pyield/tn/benchmark.py +10 -10
  27. {pyield-0.44.0 → pyield-0.44.2}/pyproject.toml +1 -0
  28. pyield-0.44.0/pyield/__about__.py +0 -1
  29. {pyield-0.44.0 → pyield-0.44.2}/LICENSE +0 -0
  30. {pyield-0.44.0 → pyield-0.44.2}/README.md +0 -0
  31. {pyield-0.44.0 → pyield-0.44.2}/pyield/__init__.py +0 -0
  32. {pyield-0.44.0 → pyield-0.44.2}/pyield/_internal/__init__.py +0 -0
  33. {pyield-0.44.0 → pyield-0.44.2}/pyield/_internal/converters.py +0 -0
  34. {pyield-0.44.0 → pyield-0.44.2}/pyield/_internal/data_cache.py +0 -0
  35. {pyield-0.44.0 → pyield-0.44.2}/pyield/_internal/retry.py +0 -0
  36. {pyield-0.44.0 → pyield-0.44.2}/pyield/_internal/types.py +0 -0
  37. {pyield-0.44.0 → pyield-0.44.2}/pyield/anbima/__init__.py +0 -0
  38. {pyield-0.44.0 → pyield-0.44.2}/pyield/b3/__init__.py +0 -0
  39. {pyield-0.44.0 → pyield-0.44.2}/pyield/b3/_contracts.py +0 -0
  40. {pyield-0.44.0 → pyield-0.44.2}/pyield/b3/_validar_pregao.py +0 -0
  41. {pyield-0.44.0 → pyield-0.44.2}/pyield/b3/di1.py +0 -0
  42. {pyield-0.44.0 → pyield-0.44.2}/pyield/b3/di_over.py +0 -0
  43. {pyield-0.44.0 → pyield-0.44.2}/pyield/b3/futures/common.py +0 -0
  44. {pyield-0.44.0 → pyield-0.44.2}/pyield/b3/futures/intraday.py +0 -0
  45. {pyield-0.44.0 → pyield-0.44.2}/pyield/bc/__init__.py +0 -0
  46. {pyield-0.44.0 → pyield-0.44.2}/pyield/bc/copom.py +0 -0
  47. {pyield-0.44.0 → pyield-0.44.2}/pyield/bday/__init__.py +0 -0
  48. {pyield-0.44.0 → pyield-0.44.2}/pyield/bday/core.py +0 -0
  49. {pyield-0.44.0 → pyield-0.44.2}/pyield/bday/holidays/br_holidays_new.txt +0 -0
  50. {pyield-0.44.0 → pyield-0.44.2}/pyield/bday/holidays/br_holidays_old.txt +0 -0
  51. {pyield-0.44.0 → pyield-0.44.2}/pyield/bday/holidays/brholidays.py +0 -0
  52. {pyield-0.44.0 → pyield-0.44.2}/pyield/clock.py +0 -0
  53. {pyield-0.44.0 → pyield-0.44.2}/pyield/fwd.py +0 -0
  54. {pyield-0.44.0 → pyield-0.44.2}/pyield/interpolator.py +0 -0
  55. {pyield-0.44.0 → pyield-0.44.2}/pyield/ipca/__init__.py +0 -0
  56. {pyield-0.44.0 → pyield-0.44.2}/pyield/ipca/historical.py +0 -0
  57. {pyield-0.44.0 → pyield-0.44.2}/pyield/py.typed +0 -0
  58. {pyield-0.44.0 → pyield-0.44.2}/pyield/rmd.py +0 -0
  59. {pyield-0.44.0 → pyield-0.44.2}/pyield/tn/__init__.py +0 -0
  60. {pyield-0.44.0 → pyield-0.44.2}/pyield/tn/lft.py +0 -0
  61. {pyield-0.44.0 → pyield-0.44.2}/pyield/tn/ltn.py +0 -0
  62. {pyield-0.44.0 → pyield-0.44.2}/pyield/tn/ntnb.py +0 -0
  63. {pyield-0.44.0 → pyield-0.44.2}/pyield/tn/ntnb1.py +0 -0
  64. {pyield-0.44.0 → pyield-0.44.2}/pyield/tn/ntnbprinc.py +0 -0
  65. {pyield-0.44.0 → pyield-0.44.2}/pyield/tn/ntnc.py +0 -0
  66. {pyield-0.44.0 → pyield-0.44.2}/pyield/tn/ntnf.py +0 -0
  67. {pyield-0.44.0 → pyield-0.44.2}/pyield/tn/pre.py +0 -0
  68. {pyield-0.44.0 → pyield-0.44.2}/pyield/tn/tools.py +0 -0
@@ -18,4 +18,6 @@ dev/
18
18
  site/
19
19
 
20
20
  # Test data
21
- tests/data/
21
+ tests/data/
22
+
23
+ cgen.py
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyield
3
- Version: 0.44.0
3
+ Version: 0.44.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
@@ -36,6 +36,7 @@ Classifier: Operating System :: OS Independent
36
36
  Classifier: Programming Language :: Python :: 3
37
37
  Classifier: Topic :: Office/Business :: Financial :: Investment
38
38
  Requires-Python: >=3.12
39
+ Requires-Dist: cachetools>=5.0.0
39
40
  Requires-Dist: fastexcel>=0.19.0
40
41
  Requires-Dist: lxml>=6.0.0
41
42
  Requires-Dist: polars>=1.33.0
@@ -0,0 +1 @@
1
+ __version__ = "0.44.2"
@@ -18,31 +18,31 @@ URL_PAGINA_INICIAL = f"{URL_BASE}/taxasOnline.asp"
18
18
  URL_CONSULTA_DADOS = f"{URL_BASE}/exibedados.asp"
19
19
  URL_DOWNLOAD = f"{URL_BASE}/download_dados.asp?extensao=csv"
20
20
 
21
- MAPA_COLUNAS = {
22
- "Título": ("titulo", pl.String),
23
- "Vencimento": ("data_vencimento", pl.String),
24
- "Código ISIN": ("codigo_isin", pl.String),
25
- "Provedor": ("provedor", pl.String),
26
- "Edital": ("edital", pl.String),
27
- "Horário": ("horario", pl.String),
28
- "Prazo": ("prazo", pl.Int64),
29
- "Lote": ("lote", pl.String),
30
- "Fech D-1": ("taxa_indicativa_anterior", pl.Float64),
31
- "Indicativo Superior": ("taxa_limite_superior", pl.Float64),
32
- "Máxima": ("taxa_maxima", pl.Float64),
33
- "Média": ("taxa_media", pl.Float64),
34
- "Mínima": ("taxa_minima", pl.Float64),
35
- "Indicativo Inferior": ("taxa_limite_inferior", pl.Float64),
36
- "Última": ("taxa_ultima", pl.Float64),
37
- "Oferta Compra": ("taxa_compra", pl.Float64),
38
- "Oferta Venda": ("taxa_venda", pl.Float64),
39
- "Nº de Negócios": ("num_negocios", pl.Int64),
40
- "Quantidade Negociada": ("quantidade_negociada", pl.Int64),
41
- "Volume Negociado (R$)": ("volume_negociado", pl.Float64),
42
- }
43
-
44
- ESQUEMA_API = {col: dtype for col, (_alias, dtype) in MAPA_COLUNAS.items()}
45
- ALIAS_COLUNAS = {col: alias for col, (alias, _dtype) in MAPA_COLUNAS.items()}
21
+ MAPA_COLUNAS = [
22
+ ("Título", "titulo", pl.String),
23
+ ("Vencimento", "data_vencimento", pl.String),
24
+ ("Código ISIN", "codigo_isin", pl.String),
25
+ ("Provedor", "provedor", pl.String),
26
+ ("Edital", "edital", pl.String),
27
+ ("Horário", "horario", pl.String),
28
+ ("Prazo", "prazo", pl.Int64),
29
+ ("Lote", "lote", pl.String),
30
+ ("Fech D-1", "taxa_indicativa_anterior", pl.Float64),
31
+ ("Indicativo Superior", "taxa_limite_superior", pl.Float64),
32
+ ("Máxima", "taxa_maxima", pl.Float64),
33
+ ("Média", "taxa_media", pl.Float64),
34
+ ("Mínima", "taxa_minima", pl.Float64),
35
+ ("Indicativo Inferior", "taxa_limite_inferior", pl.Float64),
36
+ ("Última", "taxa_ultima", pl.Float64),
37
+ ("Oferta Compra", "taxa_compra", pl.Float64),
38
+ ("Oferta Venda", "taxa_venda", pl.Float64),
39
+ ("Nº de Negócios", "num_negocios", pl.Int64),
40
+ ("Quantidade Negociada", "quantidade_negociada", pl.Int64),
41
+ ("Volume Negociado (R$)", "volume_negociado", pl.Float64),
42
+ ]
43
+
44
+ ESQUEMA_API = {csv: tipo for csv, _, tipo in MAPA_COLUNAS}
45
+ ALIAS_COLUNAS = {csv: novo for csv, novo, _ in MAPA_COLUNAS}
46
46
 
47
47
  # Colunas não selecionadas estão vazias na API.
48
48
  ORDEM_COLUNAS_FINAL = [
@@ -211,9 +211,10 @@ def tpf_difusao(data_referencia: DateLike) -> pl.DataFrame:
211
211
  - taxa_ultima (float): Última taxa negociada (decimal).
212
212
  """
213
213
  if any_is_empty(data_referencia):
214
- logger.warning("Nenhuma data fornecida. Retornando DataFrame vazio.")
215
214
  return pl.DataFrame()
216
215
  data = cv.converter_datas(data_referencia)
216
+ if not bday.is_business_day(data):
217
+ return pl.DataFrame()
217
218
  data_str = data.strftime("%d/%m/%Y")
218
219
  csv_data = _buscar_dados_url(data_str)
219
220
 
@@ -2,6 +2,7 @@ import datetime as dt
2
2
  import logging
3
3
  from io import StringIO
4
4
 
5
+ import cachetools
5
6
  import polars as pl
6
7
  import requests
7
8
 
@@ -18,6 +19,7 @@ URL_ETTJ_INTRADAY = (
18
19
  CASAS_DECIMAIS = 6
19
20
 
20
21
 
22
+ @cachetools.cached(cache=cachetools.TTLCache(maxsize=16, ttl=60))
21
23
  @retry_padrao
22
24
  def _buscar_texto_intraday() -> str:
23
25
  carga_requisicao = {"Dt_Ref": "", "saida": "csv"}
@@ -2,6 +2,7 @@ import datetime as dt
2
2
  import logging
3
3
  from io import StringIO
4
4
 
5
+ import cachetools
5
6
  import polars as pl
6
7
  import requests
7
8
 
@@ -15,6 +16,7 @@ URL_ULTIMA_ETTJ = "https://www.anbima.com.br/informacoes/est-termo/CZ-down.asp"
15
16
  CASAS_DECIMAIS = 6
16
17
 
17
18
 
19
+ @cachetools.cached(cache=cachetools.TTLCache(maxsize=16, ttl=60))
18
20
  @retry_padrao
19
21
  def _buscar_texto_ultima_ettj() -> str:
20
22
  """Busca o texto bruto da curva de juros na ANBIMA."""
@@ -2,6 +2,7 @@ import io
2
2
  import logging
3
3
  from typing import Literal
4
4
 
5
+ import cachetools
5
6
  import polars as pl
6
7
  import requests
7
8
 
@@ -24,33 +25,33 @@ TiposIMA = Literal[
24
25
 
25
26
  # Única fonte de verdade para colunas do CSV: (novo_nome, tipo)
26
27
  # Colunas com None no nome são descartadas após a leitura.
27
- IMA_COLUNAS = {
28
- "2": (None, pl.Int64),
29
- "Data de Referência": ("Date", pl.String),
30
- "INDICE": ("IMAType", pl.String),
31
- "Títulos": ("BondType", pl.String),
32
- "Data de Vencimento": ("Maturity", pl.String),
33
- "Código SELIC": ("SelicCode", pl.Int64),
34
- "Código ISIN": ("ISIN", pl.String),
35
- "Taxa Indicativa (% a.a.)": ("IndicativeRate", pl.Float64),
36
- "PU (R$)": ("Price", pl.Float64),
37
- "PU de Juros (R$)": ("InterestPrice", pl.Float64),
38
- "Quantidade (1.000 títulos)": ("MarketQuantity", pl.Float64),
39
- "Quantidade Teórica (1.000 títulos)": ("TheoreticalQuantity", pl.Float64),
40
- "Carteira a Mercado (R$ mil)": ("MarketValue", pl.Float64),
41
- "Peso (%)": ("Weight", pl.Float64),
42
- "Prazo (d.u.)": ("BDToMat", pl.Int64),
43
- "Duration (d.u.)": ("Duration", pl.Int64),
44
- "Número de Operações *": ("NumberOfOperations", pl.Int64),
45
- "Quant. Negociada (1.000 títulos) *": ("NegotiatedQuantity", pl.Float64),
46
- "Valor Negociado (R$ mil) *": ("NegotiatedValue", pl.Float64),
47
- "PMR": ("PMR", pl.Float64),
48
- "Convexidade": ("Convexity", pl.Float64),
49
- }
28
+ IMA_COLUNAS = [
29
+ ("2", None, pl.Int64),
30
+ ("Data de Referência", "Date", pl.String),
31
+ ("INDICE", "IMAType", pl.String),
32
+ ("Títulos", "BondType", pl.String),
33
+ ("Data de Vencimento", "Maturity", pl.String),
34
+ ("Código SELIC", "SelicCode", pl.Int64),
35
+ ("Código ISIN", "ISIN", pl.String),
36
+ ("Taxa Indicativa (% a.a.)", "IndicativeRate", pl.Float64),
37
+ ("PU (R$)", "Price", pl.Float64),
38
+ ("PU de Juros (R$)", "InterestPrice", pl.Float64),
39
+ ("Quantidade (1.000 títulos)", "MarketQuantity", pl.Float64),
40
+ ("Quantidade Teórica (1.000 títulos)", "TheoreticalQuantity", pl.Float64),
41
+ ("Carteira a Mercado (R$ mil)", "MarketValue", pl.Float64),
42
+ ("Peso (%)", "Weight", pl.Float64),
43
+ ("Prazo (d.u.)", "BDToMat", pl.Int64),
44
+ ("Duration (d.u.)", "Duration", pl.Int64),
45
+ ("Número de Operações *", "NumberOfOperations", pl.Int64),
46
+ ("Quant. Negociada (1.000 títulos) *", "NegotiatedQuantity", pl.Float64),
47
+ ("Valor Negociado (R$ mil) *", "NegotiatedValue", pl.Float64),
48
+ ("PMR", "PMR", pl.Float64),
49
+ ("Convexidade", "Convexity", pl.Float64),
50
+ ]
50
51
 
51
52
  # Derivados automaticamente
52
- ESQUEMA_IMA = {k: v[1] for k, v in IMA_COLUNAS.items()}
53
- MAPA_COLUNAS_IMA = {k: v[0] for k, v in IMA_COLUNAS.items() if v[0] is not None}
53
+ ESQUEMA_IMA = {csv: tipo for csv, _, tipo in IMA_COLUNAS}
54
+ MAPA_COLUNAS_IMA = {csv: novo for csv, novo, _ in IMA_COLUNAS if novo is not None}
54
55
 
55
56
  ORDEM_COLUNAS_FINAL = [
56
57
  "Date",
@@ -81,6 +82,7 @@ ORDEM_COLUNAS_FINAL = [
81
82
  URL_ULTIMO_IMA = "https://www.anbima.com.br/informacoes/ima/arqs/ima_completo.txt"
82
83
 
83
84
 
85
+ @cachetools.cached(cache=cachetools.TTLCache(maxsize=16, ttl=60))
84
86
  @retry_padrao
85
87
  def _buscar_texto_ultimo_ima() -> str:
86
88
  resposta = requests.get(URL_ULTIMO_IMA, timeout=3)
@@ -17,27 +17,28 @@ from lxml.html import HTMLParser
17
17
  from lxml.html import fromstring as html_fromstring
18
18
 
19
19
  import pyield._internal.converters as cv
20
- from pyield.anbima.tpf import tpf_data
20
+ from pyield import bday
21
21
  from pyield._internal.types import DateLike, any_is_empty
22
+ from pyield.anbima.tpf import tpf_data
22
23
 
23
24
  logger = logging.getLogger(__name__)
24
25
 
25
26
  URL_IMA = "https://www.anbima.com.br/informacoes/ima/ima-quantidade-mercado.asp"
26
27
 
27
- MAPA_COLUNAS = {
28
- "Título": ("BondType", pl.String),
29
- "Codigo Selic": ("SelicCode", pl.Int64),
30
- "Código ISIN": ("ISIN", pl.String),
31
- "Data de Vencimento": ("MaturityDate", pl.String),
32
- "Quantidade em Mercado (1.000 Títulos)": ("MarketQuantity", pl.Float64),
33
- "PU (R$)": ("Price", pl.Float64),
34
- "Valor de Mercado (R$ Mil)": ("MarketValue", pl.Float64),
35
- "Variação da Quantidade (1.000 Títulos)": ("QuantityVariation", pl.Float64),
36
- "Status do Titulo": ("BondStatus", pl.String),
37
- }
38
-
39
- ALIAS_COLUNAS = {col: alias for col, (alias, _) in MAPA_COLUNAS.items()}
40
- ESQUEMA_DADOS = {alias: dtype for _, (alias, dtype) in MAPA_COLUNAS.items()}
28
+ MAPA_COLUNAS = [
29
+ ("Título", "BondType", pl.String),
30
+ ("Codigo Selic", "SelicCode", pl.Int64),
31
+ ("Código ISIN", "ISIN", pl.String),
32
+ ("Data de Vencimento", "MaturityDate", pl.String),
33
+ ("Quantidade em Mercado (1.000 Títulos)", "MarketQuantity", pl.Float64),
34
+ ("PU (R$)", "Price", pl.Float64),
35
+ ("Valor de Mercado (R$ Mil)", "MarketValue", pl.Float64),
36
+ ("Variação da Quantidade (1.000 Títulos)", "QuantityVariation", pl.Float64),
37
+ ("Status do Titulo", "BondStatus", pl.String),
38
+ ]
39
+
40
+ ALIAS_COLUNAS = {html: novo for html, novo, _ in MAPA_COLUNAS}
41
+ ESQUEMA_DADOS = {novo: tipo for _, novo, tipo in MAPA_COLUNAS}
41
42
 
42
43
  COLUNAS_INT = [
43
44
  "MarketQuantity",
@@ -246,9 +247,10 @@ def imaq(date: DateLike) -> pl.DataFrame:
246
247
  True
247
248
  """
248
249
  if any_is_empty(date):
249
- logger.warning("Nenhuma data informada. Retornando DataFrame vazio.")
250
250
  return pl.DataFrame()
251
251
  data = cv.converter_datas(date)
252
+ if not bday.is_business_day(data):
253
+ return pl.DataFrame()
252
254
  data_str = data.strftime("%d/%m/%Y")
253
255
  try:
254
256
  url_content = _buscar_conteudo_url(data)
@@ -9,15 +9,15 @@ import requests
9
9
  from requests.exceptions import HTTPError, RequestException
10
10
 
11
11
  from pyield import bday, clock
12
- from pyield.b3 import di1
13
- from pyield.bc.ptax_api import ptax
14
12
  from pyield._internal.converters import converter_datas
15
13
  from pyield._internal.data_cache import obter_dataset_cacheado
16
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
17
18
  from pyield.tn.ntnb import duration as duration_b
18
19
  from pyield.tn.ntnc import duration as duration_c
19
20
  from pyield.tn.ntnf import duration as duration_f
20
- from pyield._internal.types import DateLike, any_is_empty
21
21
 
22
22
  BOND_TYPES = Literal["LFT", "NTN-B", "NTN-C", "LTN", "NTN-F", "PRE"]
23
23
 
@@ -31,29 +31,29 @@ DATA_MUDANCA_FORMATO = dt.date(2014, 5, 13)
31
31
 
32
32
  DIAS_RETENCAO_PUBLICA = 5
33
33
 
34
- # Única fonte de verdade para colunas do CSV: (novo_nome, tipo)
34
+ # Única fonte de verdade para colunas do CSV: (nome_csv, nome_novo, tipo)
35
35
  # Colunas de data são lidas como String e convertidas em _processar_df_bruto
36
- TPF_COLUNAS = {
37
- "Titulo": ("BondType", pl.String),
38
- "Data Referencia": ("ReferenceDate", pl.String),
39
- "Codigo SELIC": ("SelicCode", pl.Int64),
40
- "Data Base/Emissao": ("IssueBaseDate", pl.String),
41
- "Data Vencimento": ("MaturityDate", pl.String),
42
- "Tx. Compra": ("BidRate", pl.Float64),
43
- "Tx. Venda": ("AskRate", pl.Float64),
44
- "Tx. Indicativas": ("IndicativeRate", pl.Float64),
45
- "PU": ("Price", pl.Float64),
46
- "Desvio padrao": ("StdDev", pl.Float64),
47
- "Interv. Ind. Inf. (D0)": ("LowerBoundRateD0", pl.Float64),
48
- "Interv. Ind. Sup. (D0)": ("UpperBoundRateD0", pl.Float64),
49
- "Interv. Ind. Inf. (D+1)": ("LowerBoundRateD1", pl.Float64),
50
- "Interv. Ind. Sup. (D+1)": ("UpperBoundRateD1", pl.Float64),
51
- "Criterio": ("Criteria", pl.String),
52
- }
36
+ TPF_COLUNAS = [
37
+ ("Titulo", "BondType", pl.String),
38
+ ("Data Referencia", "ReferenceDate", pl.String),
39
+ ("Codigo SELIC", "SelicCode", pl.Int64),
40
+ ("Data Base/Emissao", "IssueBaseDate", pl.String),
41
+ ("Data Vencimento", "MaturityDate", pl.String),
42
+ ("Tx. Compra", "BidRate", pl.Float64),
43
+ ("Tx. Venda", "AskRate", pl.Float64),
44
+ ("Tx. Indicativas", "IndicativeRate", pl.Float64),
45
+ ("PU", "Price", pl.Float64),
46
+ ("Desvio padrao", "StdDev", pl.Float64),
47
+ ("Interv. Ind. Inf. (D0)", "LowerBoundRateD0", pl.Float64),
48
+ ("Interv. Ind. Sup. (D0)", "UpperBoundRateD0", pl.Float64),
49
+ ("Interv. Ind. Inf. (D+1)", "LowerBoundRateD1", pl.Float64),
50
+ ("Interv. Ind. Sup. (D+1)", "UpperBoundRateD1", pl.Float64),
51
+ ("Criterio", "Criteria", pl.String),
52
+ ]
53
53
 
54
54
  # Derivados automaticamente
55
- ESQUEMA_TPF = {k: v[1] for k, v in TPF_COLUNAS.items()}
56
- MAPA_NOMES_COLUNAS = {k: v[0] for k, v in TPF_COLUNAS.items()}
55
+ ESQUEMA_TPF = {csv: tipo for csv, _, tipo in TPF_COLUNAS}
56
+ MAPA_NOMES_COLUNAS = {csv: novo for csv, novo, _ in TPF_COLUNAS}
57
57
 
58
58
  logger = logging.getLogger(__name__)
59
59
 
@@ -345,25 +345,7 @@ def tpf_data(
345
345
 
346
346
  Examples:
347
347
  >>> from pyield import anbima
348
- >>> anbima.tpf_data(date="22-08-2025")
349
- shape: (49, 14)
350
- ┌───────────────┬──────────┬───────────┬───────────────┬───┬───────────┬───────────┬────────────────┬──────────┐
351
- │ ReferenceDate ┆ BondType ┆ SelicCode ┆ IssueBaseDate ┆ … ┆ BidRate ┆ AskRate ┆ IndicativeRate ┆ DIRate │
352
- │ --- ┆ --- ┆ --- ┆ --- ┆ ┆ --- ┆ --- ┆ --- ┆ --- │
353
- │ date ┆ str ┆ i64 ┆ date ┆ ┆ f64 ┆ f64 ┆ f64 ┆ f64 │
354
- ╞═══════════════╪══════════╪═══════════╪═══════════════╪═══╪═══════════╪═══════════╪════════════════╪══════════╡
355
- │ 2025-08-22 ┆ LFT ┆ 210100 ┆ 2000-07-01 ┆ … ┆ 0.000198 ┆ 0.0001 ┆ 0.000165 ┆ 0.14906 │
356
- │ 2025-08-22 ┆ LFT ┆ 210100 ┆ 2000-07-01 ┆ … ┆ -0.000053 ┆ -0.000156 ┆ -0.000116 ┆ 0.14843 │
357
- │ 2025-08-22 ┆ LFT ┆ 210100 ┆ 2000-07-01 ┆ … ┆ -0.000053 ┆ -0.000143 ┆ -0.000107 ┆ 0.1436 │
358
- │ 2025-08-22 ┆ LFT ┆ 210100 ┆ 2000-07-01 ┆ … ┆ 0.000309 ┆ 0.000292 ┆ 0.000302 ┆ 0.138189 │
359
- │ 2025-08-22 ┆ LFT ┆ 210100 ┆ 2000-07-01 ┆ … ┆ 0.000421 ┆ 0.000399 ┆ 0.000411 ┆ 0.134548 │
360
- │ … ┆ … ┆ … ┆ … ┆ … ┆ … ┆ … ┆ … ┆ … │
361
- │ 2025-08-22 ┆ NTN-F ┆ 950199 ┆ 2016-01-15 ┆ … ┆ 0.139379 ┆ 0.139163 ┆ 0.139268 ┆ 0.13959 │
362
- │ 2025-08-22 ┆ NTN-F ┆ 950199 ┆ 2018-01-05 ┆ … ┆ 0.134252 ┆ 0.134018 ┆ 0.13414 ┆ 0.1327 │
363
- │ 2025-08-22 ┆ NTN-F ┆ 950199 ┆ 2020-01-10 ┆ … ┆ 0.13846 ┆ 0.138355 ┆ 0.13841 ┆ 0.13626 │
364
- │ 2025-08-22 ┆ NTN-F ┆ 950199 ┆ 2022-01-07 ┆ … ┆ 0.139503 ┆ 0.139321 ┆ 0.139398 ┆ 0.13807 │
365
- │ 2025-08-22 ┆ NTN-F ┆ 950199 ┆ 2024-01-05 ┆ … ┆ 0.140673 ┆ 0.140566 ┆ 0.140633 ┆ 0.13845 │
366
- └───────────────┴──────────┴───────────┴───────────────┴───┴───────────┴───────────┴────────────────┴──────────┘
348
+ >>> df = anbima.tpf_data(date="06-02-2026")
367
349
 
368
350
  Data columns:
369
351
  - BondType: Tipo do título público (e.g., 'LTN', 'NTN-B').
@@ -399,13 +381,15 @@ def tpf_data(
399
381
  datas com mais de 5 dias úteis. O acesso ao histórico completo
400
382
  requer uma conexão à rede RTM. Sem ela, a consulta para datas
401
383
  antigas retornará um DataFrame vazio.
402
- """ # noqa
384
+ """
403
385
  if any_is_empty(date):
404
386
  return pl.DataFrame()
405
387
  date = converter_datas(date)
406
388
  _validar_data_nao_futura(date)
407
389
 
408
390
  if fetch_from_source:
391
+ if not bday.is_business_day(date):
392
+ return pl.DataFrame()
409
393
  # Tenta buscar os dados diretamente da fonte (ANBIMA)
410
394
  df = _buscar_dados_tpf(date)
411
395
  else:
@@ -1,28 +1,15 @@
1
1
  import logging
2
- from collections.abc import Sequence
3
- from typing import Literal
4
2
 
5
3
  import polars as pl
6
4
 
7
5
  import pyield._internal.converters as cv
8
6
  from pyield import clock
7
+ from pyield._internal.retry import DadoIndisponivelError
9
8
  from pyield._internal.types import DateLike, any_is_empty
10
9
  from pyield.b3._contracts import normalizar_codigos_contrato
11
10
  from pyield.b3._validar_pregao import data_negociacao_valida
12
11
  from pyield.b3.futures import historical, intraday
13
12
 
14
- OpcoesContrato = Literal[
15
- "DI1",
16
- "DDI",
17
- "FRC",
18
- "FRO",
19
- "DAP",
20
- "DOL",
21
- "WDO",
22
- "IND",
23
- "WIN",
24
- ]
25
-
26
13
  logger = logging.getLogger(__name__)
27
14
 
28
15
 
@@ -36,12 +23,17 @@ def _buscar_intraday_ou_historico(
36
23
  return pl.DataFrame()
37
24
 
38
25
  if horario_atual >= intraday.HORA_FIM_INTRADAY:
39
- df_historico = historical.historical(
40
- data_negociacao, codigos_contrato, full_report
41
- )
42
- if not df_historico.is_empty():
43
- logger.info("Dados consolidados disponíveis. Usando histórico.")
44
- return df_historico
26
+ for tentativa_full_report in (full_report, not full_report):
27
+ try:
28
+ df_historico = historical.historical(
29
+ data_negociacao, codigos_contrato, tentativa_full_report
30
+ )
31
+ except DadoIndisponivelError:
32
+ continue
33
+ if not df_historico.is_empty():
34
+ logger.info("Dados consolidados disponíveis. Usando histórico.")
35
+ return df_historico
36
+ logger.info("Histórico consolidado indisponível. Tentando intraday.")
45
37
 
46
38
  dataframes_intraday = [intraday.intraday(codigo) for codigo in codigos_contrato]
47
39
  dataframes_intraday = [df for df in dataframes_intraday if not df.is_empty()]
@@ -54,7 +46,7 @@ def _buscar_intraday_ou_historico(
54
46
 
55
47
  def futures(
56
48
  date: DateLike,
57
- contract_code: OpcoesContrato | str | Sequence[OpcoesContrato | str] | pl.Series,
49
+ contract_code: str | list[str],
58
50
  full_report: bool = False,
59
51
  ) -> pl.DataFrame:
60
52
  """Busca dados de um contrato futuro da B3 para a data de referência.
@@ -62,6 +54,11 @@ def futures(
62
54
  Args:
63
55
  date: Data de referência para consulta.
64
56
  contract_code: Código do contrato futuro na B3 ou coleção de códigos.
57
+ Exemplos de códigos válidos:
58
+ - Juros: DI1, DDI, OC1, DAP, IAP
59
+ - Moedas: DOL, WDO, EUR, GBR, JAP, CNY
60
+ - Índices: IND, WIN, ISP, WSP
61
+ - Commodities: BGI, CCM, ICF, CNL, SJC, SOY, ETH, GLD
65
62
  full_report: Se False (padrão), usa o simplified price report (SPR),
66
63
  arquivo leve (~2 KB) com apenas preços de ajuste. Se True, usa o
67
64
  price report completo (PR, ~2 MB) com todos os dados de negociação.
@@ -70,22 +67,15 @@ def futures(
70
67
  Returns:
71
68
  DataFrame Polars com os dados do contrato informado.
72
69
 
70
+ Notes:
71
+ Os contratos DI1, DDI, FRC, FRO, DAP, DOL, WDO, IND e WIN possuem
72
+ histórico pré-cacheado (desde 2018) e são retornados instantaneamente.
73
+ Para os demais contratos, os dados são baixados diretamente da B3 a
74
+ cada chamada, o que pode ser mais lento.
75
+
73
76
  Examples:
74
77
  >>> df = futures("31-05-2024", "DI1")
75
- >>> {"TradeDate", "TickerSymbol", "ExpirationDate", "SettlementRate"}.issubset(
76
- ... set(df.columns)
77
- ... )
78
- True
79
- >>> df.shape[0] > 0
80
- True
81
-
82
78
  >>> df = futures("31-05-2024", "DAP")
83
- >>> {"TradeDate", "TickerSymbol", "ExpirationDate", "SettlementRate"}.issubset(
84
- ... set(df.columns)
85
- ... )
86
- True
87
- >>> df.shape[0] > 0
88
- True
89
79
 
90
80
  Véspera de Natal e Ano Novo não têm pregão:
91
81
 
@@ -1,5 +1,4 @@
1
1
  import datetime as dt
2
- from collections.abc import Sequence
3
2
 
4
3
  import polars as pl
5
4
 
@@ -17,7 +16,7 @@ CONTRATOS_TAXA = {"DI1", "DAP", "DDI", "FRC", "FRO"}
17
16
 
18
17
  def historical(
19
18
  data: dt.date,
20
- codigo_contrato: str | Sequence[str] | pl.Series,
19
+ codigo_contrato: str | list[str],
21
20
  full_report: bool = False,
22
21
  ) -> pl.DataFrame:
23
22
  """Busca histórico de futuros priorizando o dataset PR cacheado.
@@ -1,5 +1,6 @@
1
1
  import logging
2
2
 
3
+ import cachetools
3
4
  import polars as pl
4
5
  import requests
5
6
 
@@ -49,6 +50,7 @@ def _mapa_renomeacao_intraday() -> dict[str, str]:
49
50
  return {nome_orig: nome_novo for nome_orig, nome_novo, _ in COLUNAS_INTRADAY}
50
51
 
51
52
 
53
+ @cachetools.cached(cache=cachetools.TTLCache(maxsize=16, ttl=15))
52
54
  @retry_padrao
53
55
  def _buscar_json_intraday(codigo_contrato: str) -> list[dict]:
54
56
  url = f"{URL_BASE_INTRADAY}/{codigo_contrato}"
@@ -12,7 +12,7 @@ from lxml.etree import _Element
12
12
  import pyield._internal.converters as cv
13
13
  from pyield._internal.retry import DadoIndisponivelError, retry_padrao
14
14
  from pyield._internal.types import DateLike, any_is_empty
15
- from pyield.b3._contracts import normalizar_codigo_contrato
15
+ from pyield.b3._contracts import normalizar_codigos_contrato
16
16
  from pyield.b3._validar_pregao import data_negociacao_valida
17
17
 
18
18
  registro = logging.getLogger(__name__)
@@ -236,7 +236,7 @@ def _obter_xml_price_report(data: dt.date, relatorio_completo: bool) -> bytes:
236
236
 
237
237
  def fetch_price_report(
238
238
  date: DateLike,
239
- contract_code: str,
239
+ contract_code: str | list[str],
240
240
  full_report: bool = False,
241
241
  ) -> pl.DataFrame:
242
242
  """Busca e processa o price report da B3 no site oficial.
@@ -257,8 +257,8 @@ def fetch_price_report(
257
257
  Args:
258
258
  date: Data de negociação no formato 'DD-MM-YYYY', 'DD/MM/YYYY',
259
259
  'YYYY-MM-DD' ou objeto datetime.date.
260
- contract_code: Código B3 (ex.: 'DI1', 'DOL', 'DAP', 'FRC', 'DDI',
261
- 'WDO', 'IND', 'WIN'). Os 3 primeiros caracteres são usados no XML.
260
+ contract_code: Código B3 único ou lista de códigos (ex.: 'DI1',
261
+ ['DI1', 'DAP']). Os 3 primeiros caracteres são usados no XML.
262
262
  full_report: Se False (padrão), usa o simplified price report (SPR),
263
263
  arquivo leve (~2 KB) com apenas preços de ajuste. Se True, usa o
264
264
  price report completo (PR, ~2 MB) com todos os dados de negociação.
@@ -311,7 +311,6 @@ def fetch_price_report(
311
311
  - MinLimitValue (Float64): Limite mínimo de variação.
312
312
 
313
313
  Raises:
314
- ValueError: Se full_report não for bool.
315
314
  DadoIndisponivelError: Se a data for válida, mas o endpoint não fornecer
316
315
  arquivo para a data consultada.
317
316
  requests.HTTPError: Se a requisição HTTP ao endpoint falhar.
@@ -322,18 +321,17 @@ def fetch_price_report(
322
321
  Examples:
323
322
  >>> import pyield as yd
324
323
  >>> df = yd.b3.fetch_price_report("26-04-2024", "DI1")
325
- >>> df.is_empty() or {"TradeDate", "TickerSymbol", "CloseValue"}.issubset(
326
- ... set(df.columns)
327
- ... )
328
- True
324
+
325
+ >>> # Múltiplos contratos de uma vez
326
+ >>> df = yd.b3.fetch_price_report("26-04-2024", ["DI1", "DAP"])
329
327
 
330
328
  >>> # Feriado ou fim de semana (retorna DataFrame vazio)
331
329
  >>> df = yd.b3.fetch_price_report("25-12-2023", "DI1") # Véspera de Natal
332
330
  >>> df.is_empty()
333
331
  True
334
332
  """
335
- contrato = normalizar_codigo_contrato(contract_code)
336
- if any_is_empty(date) or not contrato:
333
+ contratos = normalizar_codigos_contrato(contract_code)
334
+ if any_is_empty(date) or not contratos:
337
335
  return pl.DataFrame()
338
336
 
339
337
  date = cv.converter_datas(date)
@@ -343,21 +341,28 @@ def fetch_price_report(
343
341
 
344
342
  try:
345
343
  xml_bytes = _obter_xml_price_report(date, full_report)
346
- return _processar_xml_extraido(xml_bytes, contrato)
344
+ dataframes = []
345
+ for contrato in contratos:
346
+ df = _processar_xml_extraido(xml_bytes, contrato)
347
+ if not df.is_empty():
348
+ dataframes.append(df)
349
+ if not dataframes:
350
+ return pl.DataFrame()
351
+ return pl.concat(dataframes).sort("TickerSymbol")
347
352
  except (zipfile.BadZipFile, etree.XMLSyntaxError):
348
- registro.exception(f"Falha ao parsear o price report de {contrato} em {date}.")
353
+ registro.exception(f"Falha ao parsear o price report de {contratos} em {date}.")
349
354
  raise
350
355
  except Exception:
351
356
  tipo = "PR" if full_report else "SPR"
352
357
  registro.exception(
353
- f"ERRO CRÍTICO: Falha ao processar {contrato} {tipo} em {date}"
358
+ f"ERRO CRÍTICO: Falha ao processar {contratos} {tipo} em {date}"
354
359
  )
355
360
  raise
356
361
 
357
362
 
358
363
  def read_price_report(
359
364
  xml_bytes: bytes,
360
- contract_code: str,
365
+ contract_code: str | list[str],
361
366
  ) -> pl.DataFrame:
362
367
  """Lê e processa o price report da B3 a partir do conteúdo XML bruto.
363
368
 
@@ -369,7 +374,8 @@ def read_price_report(
369
374
 
370
375
  Args:
371
376
  xml_bytes: Conteúdo do XML em bytes (já descomprimido).
372
- contract_code: Código B3 do contrato.
377
+ contract_code: Código B3 único ou lista de códigos (ex.: 'DI1',
378
+ ['DI1', 'DAP']).
373
379
 
374
380
  Returns:
375
381
  pl.DataFrame: DataFrame com colunas tipadas e renomeadas, ordenado por
@@ -416,8 +422,15 @@ def read_price_report(
416
422
  - MaxLimitValue (Float64): Limite máximo de variação.
417
423
  - MinLimitValue (Float64): Limite mínimo de variação.
418
424
  """
419
- contrato = normalizar_codigo_contrato(contract_code)
420
- if any_is_empty(xml_bytes) or not contrato:
425
+ contratos = normalizar_codigos_contrato(contract_code)
426
+ if any_is_empty(xml_bytes) or not contratos:
421
427
  return pl.DataFrame()
422
428
 
423
- return _processar_xml_extraido(xml_bytes, contrato)
429
+ dataframes = []
430
+ for contrato in contratos:
431
+ df = _processar_xml_extraido(xml_bytes, contrato)
432
+ if not df.is_empty():
433
+ dataframes.append(df)
434
+ if not dataframes:
435
+ return pl.DataFrame()
436
+ return pl.concat(dataframes).sort("TickerSymbol")