pyield 0.47.2__tar.gz → 0.47.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.
Files changed (69) hide show
  1. {pyield-0.47.2 → pyield-0.47.3}/PKG-INFO +9 -10
  2. {pyield-0.47.2 → pyield-0.47.3}/README.md +8 -9
  3. pyield-0.47.3/pyield/__about__.py +1 -0
  4. {pyield-0.47.2 → pyield-0.47.3}/pyield/b3/price_report.py +66 -76
  5. pyield-0.47.2/pyield/__about__.py +0 -1
  6. {pyield-0.47.2 → pyield-0.47.3}/.gitignore +0 -0
  7. {pyield-0.47.2 → pyield-0.47.3}/LICENSE +0 -0
  8. {pyield-0.47.2 → pyield-0.47.3}/pyield/__init__.py +0 -0
  9. {pyield-0.47.2 → pyield-0.47.3}/pyield/_internal/__init__.py +0 -0
  10. {pyield-0.47.2 → pyield-0.47.3}/pyield/_internal/br_numbers.py +0 -0
  11. {pyield-0.47.2 → pyield-0.47.3}/pyield/_internal/cache.py +0 -0
  12. {pyield-0.47.2 → pyield-0.47.3}/pyield/_internal/converters.py +0 -0
  13. {pyield-0.47.2 → pyield-0.47.3}/pyield/_internal/data_cache.py +0 -0
  14. {pyield-0.47.2 → pyield-0.47.3}/pyield/_internal/retry.py +0 -0
  15. {pyield-0.47.2 → pyield-0.47.3}/pyield/_internal/types.py +0 -0
  16. {pyield-0.47.2 → pyield-0.47.3}/pyield/anbima/__init__.py +0 -0
  17. {pyield-0.47.2 → pyield-0.47.3}/pyield/anbima/ettj_intraday.py +0 -0
  18. {pyield-0.47.2 → pyield-0.47.3}/pyield/anbima/ettj_last.py +0 -0
  19. {pyield-0.47.2 → pyield-0.47.3}/pyield/anbima/ima.py +0 -0
  20. {pyield-0.47.2 → pyield-0.47.3}/pyield/anbima/imaq.py +0 -0
  21. {pyield-0.47.2 → pyield-0.47.3}/pyield/anbima/tpf.py +0 -0
  22. {pyield-0.47.2 → pyield-0.47.3}/pyield/b3/__init__.py +0 -0
  23. {pyield-0.47.2 → pyield-0.47.3}/pyield/b3/_contracts.py +0 -0
  24. {pyield-0.47.2 → pyield-0.47.3}/pyield/b3/_validar_pregao.py +0 -0
  25. {pyield-0.47.2 → pyield-0.47.3}/pyield/b3/derivatives_intraday.py +0 -0
  26. {pyield-0.47.2 → pyield-0.47.3}/pyield/b3/di1.py +0 -0
  27. {pyield-0.47.2 → pyield-0.47.3}/pyield/b3/di_over.py +0 -0
  28. {pyield-0.47.2 → pyield-0.47.3}/pyield/b3/futures/__init__.py +0 -0
  29. {pyield-0.47.2 → pyield-0.47.3}/pyield/b3/futures/common.py +0 -0
  30. {pyield-0.47.2 → pyield-0.47.3}/pyield/b3/futures/historical.py +0 -0
  31. {pyield-0.47.2 → pyield-0.47.3}/pyield/b3/futures/intraday.py +0 -0
  32. {pyield-0.47.2 → pyield-0.47.3}/pyield/bc/__init__.py +0 -0
  33. {pyield-0.47.2 → pyield-0.47.3}/pyield/bc/auction.py +0 -0
  34. {pyield-0.47.2 → pyield-0.47.3}/pyield/bc/copom.py +0 -0
  35. {pyield-0.47.2 → pyield-0.47.3}/pyield/bc/ptax_api.py +0 -0
  36. {pyield-0.47.2 → pyield-0.47.3}/pyield/bc/rates.py +0 -0
  37. {pyield-0.47.2 → pyield-0.47.3}/pyield/bc/repo.py +0 -0
  38. {pyield-0.47.2 → pyield-0.47.3}/pyield/bc/trades_intraday.py +0 -0
  39. {pyield-0.47.2 → pyield-0.47.3}/pyield/bc/trades_monthly.py +0 -0
  40. {pyield-0.47.2 → pyield-0.47.3}/pyield/bc/vna.py +0 -0
  41. {pyield-0.47.2 → pyield-0.47.3}/pyield/bday/__init__.py +0 -0
  42. {pyield-0.47.2 → pyield-0.47.3}/pyield/bday/core.py +0 -0
  43. {pyield-0.47.2 → pyield-0.47.3}/pyield/bday/holidays/br_holidays_new.txt +0 -0
  44. {pyield-0.47.2 → pyield-0.47.3}/pyield/bday/holidays/br_holidays_old.txt +0 -0
  45. {pyield-0.47.2 → pyield-0.47.3}/pyield/bday/holidays/brholidays.py +0 -0
  46. {pyield-0.47.2 → pyield-0.47.3}/pyield/clock.py +0 -0
  47. {pyield-0.47.2 → pyield-0.47.3}/pyield/fwd.py +0 -0
  48. {pyield-0.47.2 → pyield-0.47.3}/pyield/interpolator.py +0 -0
  49. {pyield-0.47.2 → pyield-0.47.3}/pyield/ipca/__init__.py +0 -0
  50. {pyield-0.47.2 → pyield-0.47.3}/pyield/ipca/historical.py +0 -0
  51. {pyield-0.47.2 → pyield-0.47.3}/pyield/ipca/projected.py +0 -0
  52. {pyield-0.47.2 → pyield-0.47.3}/pyield/py.typed +0 -0
  53. {pyield-0.47.2 → pyield-0.47.3}/pyield/rmd.py +0 -0
  54. {pyield-0.47.2 → pyield-0.47.3}/pyield/selic/__init__.py +0 -0
  55. {pyield-0.47.2 → pyield-0.47.3}/pyield/selic/cpm.py +0 -0
  56. {pyield-0.47.2 → pyield-0.47.3}/pyield/selic/probabilities.py +0 -0
  57. {pyield-0.47.2 → pyield-0.47.3}/pyield/tn/__init__.py +0 -0
  58. {pyield-0.47.2 → pyield-0.47.3}/pyield/tn/auctions.py +0 -0
  59. {pyield-0.47.2 → pyield-0.47.3}/pyield/tn/benchmark.py +0 -0
  60. {pyield-0.47.2 → pyield-0.47.3}/pyield/tn/lft.py +0 -0
  61. {pyield-0.47.2 → pyield-0.47.3}/pyield/tn/ltn.py +0 -0
  62. {pyield-0.47.2 → pyield-0.47.3}/pyield/tn/ntnb.py +0 -0
  63. {pyield-0.47.2 → pyield-0.47.3}/pyield/tn/ntnb1.py +0 -0
  64. {pyield-0.47.2 → pyield-0.47.3}/pyield/tn/ntnbprinc.py +0 -0
  65. {pyield-0.47.2 → pyield-0.47.3}/pyield/tn/ntnc.py +0 -0
  66. {pyield-0.47.2 → pyield-0.47.3}/pyield/tn/ntnf.py +0 -0
  67. {pyield-0.47.2 → pyield-0.47.3}/pyield/tn/pre.py +0 -0
  68. {pyield-0.47.2 → pyield-0.47.3}/pyield/tn/utils.py +0 -0
  69. {pyield-0.47.2 → pyield-0.47.3}/pyproject.toml +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyield
3
- Version: 0.47.2
3
+ Version: 0.47.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
@@ -206,23 +206,22 @@ ntnf.di_spreads("30-05-2025", bps=True)
206
206
  ## Dados de Futuros
207
207
 
208
208
  ```python
209
- from pyield import futures
209
+ from pyield import futures, futures_intraday
210
210
 
211
211
  # DI1 (Futuro de Depósito Interfinanceiro)
212
212
  futures("31-05-2024", "DI1")
213
213
 
214
- # Outros contratos disponíveis:
215
- # - Juros: DI1, DDI, OC1, DAP, IAP
216
- # - Moedas: DOL, WDO, EUR, GBR, JAP, CNY
217
- # - Índices: IND, WIN, ISP, WSP
218
- # - Commodities: BGI, CCM, ICF, CNL, SJC, SOY, ETH, GLD
214
+ # Outros contratos disponíveis no cache histórico:
215
+ # - Juros: DI1, DDI, FRC, FRO, DAP
216
+ # - Moedas: DOL, WDO
217
+ # - Índices: IND, WIN
219
218
  futures("31-05-2024", "DAP")
220
219
 
221
- # Múltiplos contratos de uma vez
222
- futures("31-05-2024", ["DI1", "DAP"])
220
+ # Múltiplas datas de uma vez
221
+ futures(["29-05-2024", "31-05-2024"], "DI1")
223
222
 
224
223
  # Dados intradiários (quando o mercado estiver aberto)
225
- futures("16-01-2025", "DI1") # Retorna dados ao vivo durante o horário de negociação
224
+ futures_intraday("DI1") # Retorna dados ao vivo durante o horário de negociação
226
225
  ```
227
226
 
228
227
  ## Tratamento de Datas
@@ -162,23 +162,22 @@ ntnf.di_spreads("30-05-2025", bps=True)
162
162
  ## Dados de Futuros
163
163
 
164
164
  ```python
165
- from pyield import futures
165
+ from pyield import futures, futures_intraday
166
166
 
167
167
  # DI1 (Futuro de Depósito Interfinanceiro)
168
168
  futures("31-05-2024", "DI1")
169
169
 
170
- # Outros contratos disponíveis:
171
- # - Juros: DI1, DDI, OC1, DAP, IAP
172
- # - Moedas: DOL, WDO, EUR, GBR, JAP, CNY
173
- # - Índices: IND, WIN, ISP, WSP
174
- # - Commodities: BGI, CCM, ICF, CNL, SJC, SOY, ETH, GLD
170
+ # Outros contratos disponíveis no cache histórico:
171
+ # - Juros: DI1, DDI, FRC, FRO, DAP
172
+ # - Moedas: DOL, WDO
173
+ # - Índices: IND, WIN
175
174
  futures("31-05-2024", "DAP")
176
175
 
177
- # Múltiplos contratos de uma vez
178
- futures("31-05-2024", ["DI1", "DAP"])
176
+ # Múltiplas datas de uma vez
177
+ futures(["29-05-2024", "31-05-2024"], "DI1")
179
178
 
180
179
  # Dados intradiários (quando o mercado estiver aberto)
181
- futures("16-01-2025", "DI1") # Retorna dados ao vivo durante o horário de negociação
180
+ futures_intraday("DI1") # Retorna dados ao vivo durante o horário de negociação
182
181
  ```
183
182
 
184
183
  ## Tratamento de Datas
@@ -0,0 +1 @@
1
+ __version__ = "0.47.3"
@@ -1,28 +1,24 @@
1
1
  import datetime as dt
2
2
  import io
3
- import logging
4
3
  import zipfile
5
- from functools import lru_cache
6
4
 
7
5
  import polars as pl
8
6
  import requests
9
7
  from lxml import etree
10
- from lxml.etree import _Element
11
8
 
12
9
  import pyield._internal.converters as cv
10
+ from pyield._internal.cache import ttl_cache
13
11
  from pyield._internal.retry import retry_padrao
14
12
  from pyield._internal.types import DateLike, any_is_empty
15
13
  from pyield.b3._contracts import normalizar_codigos_contrato
16
14
  from pyield.b3._validar_pregao import data_negociacao_valida
17
15
 
18
- registro = logging.getLogger(__name__)
19
-
20
16
  # --- Constantes de Processamento XML ---
21
17
  NAMESPACE_B3 = "urn:bvmf.217.01.xsd"
22
18
  NAMESPACES = {"ns": NAMESPACE_B3}
23
19
  # ZIP válido do price report ~2KB; 1KB detecta arquivos "sem dados"
24
20
  MIN_TAMANHO_ZIP_BYTES = 1024
25
- MODELO_XPATH_TICKER = '//ns:TckrSymb[starts-with(text(), "{prefixo_ticker}")]'
21
+ XPATH_TODOS_TICKERS = "//ns:TckrSymb"
26
22
  XPATH_DATA_NEGOCIACAO = ".//ns:TradDt/ns:Dt"
27
23
  XPATH_ATRIBUTOS_INSTRUMENTO = ".//ns:FinInstrmAttrbts"
28
24
  XPATH_DETALHES_NEGOCIO = ".//ns:TradDtls"
@@ -74,10 +70,11 @@ COLUNAS_PRICE_REPORT: list[tuple[str, str, type[pl.DataType]]] = [
74
70
  ("5.32", "MinTradLmt", pl.Float64),
75
71
  ]
76
72
 
77
- # Mapa de tipos para cast inicial usando os nomes originais do XML.
78
- TIPOS_XML = {nome_xml: tipo for _, nome_xml, tipo in COLUNAS_PRICE_REPORT}
73
+ # Schema completo: nome_xml tipo_polars. Garante ordem e tipagem constante.
74
+ SCHEMA_PRICE_REPORT = {nome: tipo for _, nome, tipo in COLUNAS_PRICE_REPORT}
79
75
 
80
76
 
77
+ @ttl_cache()
81
78
  @retry_padrao
82
79
  def _baixar_zip_url(data: dt.date, relatorio_completo: bool) -> bytes:
83
80
  data_str = data.strftime("%y%m%d")
@@ -124,7 +121,7 @@ def price_report_extract(conteudo_zip: bytes) -> bytes:
124
121
  return zip_interno.read(nomes_xml[-1])
125
122
 
126
123
 
127
- def _extrair_dados_contrato(elemento_ticker: _Element) -> dict | None:
124
+ def _extrair_dados_contrato(elemento_ticker: etree._Element) -> dict | None:
128
125
  if elemento_ticker.text is None:
129
126
  return None
130
127
  pai = elemento_ticker.getparent()
@@ -155,11 +152,7 @@ def _extrair_dados_contrato(elemento_ticker: _Element) -> dict | None:
155
152
  return dados_ticker
156
153
 
157
154
 
158
- def _parsear_xml_registros(
159
- xml_bytes: bytes,
160
- prefixo_ticker: str,
161
- comprimento_ticker: int | None = None,
162
- ) -> list[dict]:
155
+ def _parsear_xml_registros(xml_bytes: bytes) -> list[dict]:
163
156
  analisador = etree.XMLParser(
164
157
  ns_clean=True,
165
158
  remove_blank_text=True,
@@ -169,72 +162,70 @@ def _parsear_xml_registros(
169
162
  no_network=True,
170
163
  load_dtd=False,
171
164
  )
172
- arquivo_xml = io.BytesIO(xml_bytes)
173
- arvore = etree.parse(arquivo_xml, parser=analisador)
174
- caminho_xpath = MODELO_XPATH_TICKER.format(prefixo_ticker=prefixo_ticker)
175
- resultado_xpath = arvore.xpath(caminho_xpath, namespaces=NAMESPACES)
176
- if not isinstance(resultado_xpath, list):
177
- return []
178
-
179
- elementos_ticker = resultado_xpath
180
-
181
- if not elementos_ticker:
165
+ arvore = etree.parse(io.BytesIO(xml_bytes), parser=analisador)
166
+ resultado_xpath = arvore.xpath(XPATH_TODOS_TICKERS, namespaces=NAMESPACES)
167
+ elementos: list[etree._Element] = resultado_xpath # type: ignore[assignment]
168
+ if not elementos:
182
169
  return []
183
170
 
184
171
  registros = []
185
- for elemento in elementos_ticker:
186
- if not isinstance(elemento, etree._Element):
187
- continue
172
+ for elemento in elementos:
188
173
  dados_contrato = _extrair_dados_contrato(elemento)
189
174
  if dados_contrato is not None:
190
- ticker = dados_contrato["TckrSymb"]
191
- if comprimento_ticker is None or len(ticker) == comprimento_ticker:
192
- registros.append(dados_contrato)
175
+ registros.append(dados_contrato)
193
176
  return registros
194
177
 
195
178
 
196
179
  def _converter_para_df(registros: list[dict]) -> pl.DataFrame:
197
180
  df = pl.DataFrame(registros)
198
181
  # Casting usa os nomes originais do XML, que são constantes
199
- tipos_coluna = {k: v for k, v in TIPOS_XML.items() if k in df.columns}
200
- return df.cast(tipos_coluna, strict=False) # type: ignore
182
+ tipos_coluna = {k: v for k, v in SCHEMA_PRICE_REPORT.items() if k in df.columns}
183
+ df = df.cast(tipos_coluna, strict=False) # type: ignore
184
+ # Adiciona colunas ausentes com null e garante ordem/schema constante
185
+ colunas_faltantes = {
186
+ nome: pl.lit(None).cast(tipo)
187
+ for nome, tipo in SCHEMA_PRICE_REPORT.items()
188
+ if nome not in df.columns
189
+ }
190
+ if colunas_faltantes:
191
+ df = df.with_columns(**colunas_faltantes)
192
+ return df.select(SCHEMA_PRICE_REPORT.keys())
193
+
194
+
195
+ def _processar_xml_extraido(xml_bytes: bytes) -> pl.DataFrame:
196
+ registros = _parsear_xml_registros(xml_bytes)
197
+ if not registros:
198
+ return pl.DataFrame()
199
+ return _converter_para_df(registros).sort("TckrSymb")
201
200
 
202
201
 
203
- def _processar_xml_extraido(
204
- xml_bytes: bytes,
205
- prefixo_ticker: str,
202
+ def _filtrar_df(
203
+ df: pl.DataFrame,
204
+ prefixos: list[str],
206
205
  comprimento_ticker: int | None = None,
207
206
  ) -> pl.DataFrame:
208
- registros = (
209
- _parsear_xml_registros(xml_bytes, prefixo_ticker, comprimento_ticker)
210
- if xml_bytes
211
- else []
212
- )
213
- if not registros:
214
- return pl.DataFrame()
207
+ if comprimento_ticker:
208
+ df = df.filter(pl.col("TckrSymb").str.len_chars() == comprimento_ticker)
215
209
 
216
- df = _converter_para_df(registros)
210
+ if prefixos:
211
+ filtro = pl.any_horizontal(
212
+ pl.col("TckrSymb").str.starts_with(p) for p in prefixos
213
+ )
214
+ df = df.filter(filtro)
217
215
  return df.sort("TckrSymb")
218
216
 
219
217
 
220
- @lru_cache(maxsize=64)
221
- def _obter_xml_price_report(data: dt.date, relatorio_completo: bool) -> bytes:
218
+ def _obter_df_price_report(data: dt.date, relatorio_completo: bool) -> pl.DataFrame:
222
219
  dados_zip = _baixar_zip_url(data, relatorio_completo)
223
220
  if not dados_zip:
224
- return bytes()
225
- try:
226
- return price_report_extract(dados_zip)
227
- except zipfile.BadZipFile:
228
- registro.warning("ZIP corrompido na transmissão, re-baixando...")
229
- dados_zip = _baixar_zip_url(data, relatorio_completo)
230
- if not dados_zip:
231
- return bytes()
232
- return price_report_extract(dados_zip)
221
+ return pl.DataFrame()
222
+ xml_bytes = price_report_extract(dados_zip)
223
+ return _processar_xml_extraido(xml_bytes)
233
224
 
234
225
 
235
226
  def price_report_fetch(
236
227
  date: DateLike,
237
- ticker_prefix: str | list[str],
228
+ ticker_prefix: str | list[str] | None = None,
238
229
  ticker_length: int | None = None,
239
230
  full_report: bool = False,
240
231
  ) -> pl.DataFrame:
@@ -259,7 +250,8 @@ def price_report_fetch(
259
250
  'YYYY-MM-DD' ou objeto datetime.date.
260
251
  ticker_prefix: Prefixo do ticker B3 (ex.: 'DI1', 'DOL',
261
252
  'CPM') ou lista de prefixos (ex.: ['DI1', 'DAP']).
262
- Usado como filtro starts-with no XML.
253
+ Usado como filtro starts-with no XML. Se None (padrão),
254
+ retorna todos os ativos sem filtro.
263
255
  ticker_length: Comprimento exato do ticker para filtrar registros.
264
256
  Se None (padrão), retorna todos os tickers que casam com o
265
257
  prefixo (ex.: 6 para futuros, 13 para opções).
@@ -333,8 +325,7 @@ def price_report_fetch(
333
325
  >>> df.is_empty()
334
326
  True
335
327
  """
336
- prefixos = normalizar_codigos_contrato(ticker_prefix)
337
- if any_is_empty(date) or not prefixos:
328
+ if any_is_empty(date):
338
329
  return pl.DataFrame()
339
330
 
340
331
  date = cv.converter_datas(date)
@@ -342,20 +333,19 @@ def price_report_fetch(
342
333
  if not data_negociacao_valida(date):
343
334
  return pl.DataFrame()
344
335
 
345
- xml_bytes = _obter_xml_price_report(date, full_report)
346
- dataframes = []
347
- for prefixo in prefixos:
348
- df = _processar_xml_extraido(xml_bytes, prefixo, ticker_length)
349
- if not df.is_empty():
350
- dataframes.append(df)
351
- if not dataframes:
336
+ df = _obter_df_price_report(date, full_report)
337
+ if df.is_empty() or ticker_prefix is None:
338
+ return df
339
+
340
+ prefixos = normalizar_codigos_contrato(ticker_prefix)
341
+ if not prefixos:
352
342
  return pl.DataFrame()
353
- return pl.concat(dataframes, how="diagonal").sort("TckrSymb")
343
+ return _filtrar_df(df, prefixos, ticker_length)
354
344
 
355
345
 
356
346
  def price_report_read(
357
347
  xml_bytes: bytes,
358
- ticker_prefix: str | list[str],
348
+ ticker_prefix: str | list[str] | None = None,
359
349
  ticker_length: int | None = None,
360
350
  ) -> pl.DataFrame:
361
351
  """Lê e processa o price report da B3 a partir do conteúdo XML bruto.
@@ -367,6 +357,7 @@ def price_report_read(
367
357
  xml_bytes: Conteúdo do XML em bytes (já descomprimido).
368
358
  ticker_prefix: Prefixo do ticker B3 (ex.: 'DI1', 'DOL',
369
359
  'CPM') ou lista de prefixos (ex.: ['DI1', 'DAP']).
360
+ Se None (padrão), retorna todos os ativos sem filtro.
370
361
  ticker_length: Comprimento exato do ticker para filtrar registros.
371
362
  Se None (padrão), retorna todos os tickers que casam com o
372
363
  prefixo.
@@ -375,15 +366,14 @@ def price_report_read(
375
366
  pl.DataFrame: DataFrame com as mesmas colunas documentadas em
376
367
  :func:`price_report_fetch`.
377
368
  """
378
- prefixos = normalizar_codigos_contrato(ticker_prefix)
379
- if any_is_empty(xml_bytes) or not prefixos:
369
+ if any_is_empty(xml_bytes):
380
370
  return pl.DataFrame()
381
371
 
382
- dataframes = []
383
- for prefixo in prefixos:
384
- df = _processar_xml_extraido(xml_bytes, prefixo, ticker_length)
385
- if not df.is_empty():
386
- dataframes.append(df)
387
- if not dataframes:
372
+ df = _processar_xml_extraido(xml_bytes)
373
+ if df.is_empty() or ticker_prefix is None:
374
+ return df
375
+
376
+ prefixos = normalizar_codigos_contrato(ticker_prefix)
377
+ if not prefixos:
388
378
  return pl.DataFrame()
389
- return pl.concat(dataframes, how="diagonal").sort("TckrSymb")
379
+ return _filtrar_df(df, prefixos, ticker_length)
@@ -1 +0,0 @@
1
- __version__ = "0.47.2"
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