pyield 0.47.2__tar.gz → 0.47.4__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.4}/PKG-INFO +9 -10
  2. {pyield-0.47.2 → pyield-0.47.4}/README.md +8 -9
  3. pyield-0.47.4/pyield/__about__.py +1 -0
  4. {pyield-0.47.2 → pyield-0.47.4}/pyield/b3/price_report.py +138 -114
  5. pyield-0.47.2/pyield/__about__.py +0 -1
  6. {pyield-0.47.2 → pyield-0.47.4}/.gitignore +0 -0
  7. {pyield-0.47.2 → pyield-0.47.4}/LICENSE +0 -0
  8. {pyield-0.47.2 → pyield-0.47.4}/pyield/__init__.py +0 -0
  9. {pyield-0.47.2 → pyield-0.47.4}/pyield/_internal/__init__.py +0 -0
  10. {pyield-0.47.2 → pyield-0.47.4}/pyield/_internal/br_numbers.py +0 -0
  11. {pyield-0.47.2 → pyield-0.47.4}/pyield/_internal/cache.py +0 -0
  12. {pyield-0.47.2 → pyield-0.47.4}/pyield/_internal/converters.py +0 -0
  13. {pyield-0.47.2 → pyield-0.47.4}/pyield/_internal/data_cache.py +0 -0
  14. {pyield-0.47.2 → pyield-0.47.4}/pyield/_internal/retry.py +0 -0
  15. {pyield-0.47.2 → pyield-0.47.4}/pyield/_internal/types.py +0 -0
  16. {pyield-0.47.2 → pyield-0.47.4}/pyield/anbima/__init__.py +0 -0
  17. {pyield-0.47.2 → pyield-0.47.4}/pyield/anbima/ettj_intraday.py +0 -0
  18. {pyield-0.47.2 → pyield-0.47.4}/pyield/anbima/ettj_last.py +0 -0
  19. {pyield-0.47.2 → pyield-0.47.4}/pyield/anbima/ima.py +0 -0
  20. {pyield-0.47.2 → pyield-0.47.4}/pyield/anbima/imaq.py +0 -0
  21. {pyield-0.47.2 → pyield-0.47.4}/pyield/anbima/tpf.py +0 -0
  22. {pyield-0.47.2 → pyield-0.47.4}/pyield/b3/__init__.py +0 -0
  23. {pyield-0.47.2 → pyield-0.47.4}/pyield/b3/_contracts.py +0 -0
  24. {pyield-0.47.2 → pyield-0.47.4}/pyield/b3/_validar_pregao.py +0 -0
  25. {pyield-0.47.2 → pyield-0.47.4}/pyield/b3/derivatives_intraday.py +0 -0
  26. {pyield-0.47.2 → pyield-0.47.4}/pyield/b3/di1.py +0 -0
  27. {pyield-0.47.2 → pyield-0.47.4}/pyield/b3/di_over.py +0 -0
  28. {pyield-0.47.2 → pyield-0.47.4}/pyield/b3/futures/__init__.py +0 -0
  29. {pyield-0.47.2 → pyield-0.47.4}/pyield/b3/futures/common.py +0 -0
  30. {pyield-0.47.2 → pyield-0.47.4}/pyield/b3/futures/historical.py +0 -0
  31. {pyield-0.47.2 → pyield-0.47.4}/pyield/b3/futures/intraday.py +0 -0
  32. {pyield-0.47.2 → pyield-0.47.4}/pyield/bc/__init__.py +0 -0
  33. {pyield-0.47.2 → pyield-0.47.4}/pyield/bc/auction.py +0 -0
  34. {pyield-0.47.2 → pyield-0.47.4}/pyield/bc/copom.py +0 -0
  35. {pyield-0.47.2 → pyield-0.47.4}/pyield/bc/ptax_api.py +0 -0
  36. {pyield-0.47.2 → pyield-0.47.4}/pyield/bc/rates.py +0 -0
  37. {pyield-0.47.2 → pyield-0.47.4}/pyield/bc/repo.py +0 -0
  38. {pyield-0.47.2 → pyield-0.47.4}/pyield/bc/trades_intraday.py +0 -0
  39. {pyield-0.47.2 → pyield-0.47.4}/pyield/bc/trades_monthly.py +0 -0
  40. {pyield-0.47.2 → pyield-0.47.4}/pyield/bc/vna.py +0 -0
  41. {pyield-0.47.2 → pyield-0.47.4}/pyield/bday/__init__.py +0 -0
  42. {pyield-0.47.2 → pyield-0.47.4}/pyield/bday/core.py +0 -0
  43. {pyield-0.47.2 → pyield-0.47.4}/pyield/bday/holidays/br_holidays_new.txt +0 -0
  44. {pyield-0.47.2 → pyield-0.47.4}/pyield/bday/holidays/br_holidays_old.txt +0 -0
  45. {pyield-0.47.2 → pyield-0.47.4}/pyield/bday/holidays/brholidays.py +0 -0
  46. {pyield-0.47.2 → pyield-0.47.4}/pyield/clock.py +0 -0
  47. {pyield-0.47.2 → pyield-0.47.4}/pyield/fwd.py +0 -0
  48. {pyield-0.47.2 → pyield-0.47.4}/pyield/interpolator.py +0 -0
  49. {pyield-0.47.2 → pyield-0.47.4}/pyield/ipca/__init__.py +0 -0
  50. {pyield-0.47.2 → pyield-0.47.4}/pyield/ipca/historical.py +0 -0
  51. {pyield-0.47.2 → pyield-0.47.4}/pyield/ipca/projected.py +0 -0
  52. {pyield-0.47.2 → pyield-0.47.4}/pyield/py.typed +0 -0
  53. {pyield-0.47.2 → pyield-0.47.4}/pyield/rmd.py +0 -0
  54. {pyield-0.47.2 → pyield-0.47.4}/pyield/selic/__init__.py +0 -0
  55. {pyield-0.47.2 → pyield-0.47.4}/pyield/selic/cpm.py +0 -0
  56. {pyield-0.47.2 → pyield-0.47.4}/pyield/selic/probabilities.py +0 -0
  57. {pyield-0.47.2 → pyield-0.47.4}/pyield/tn/__init__.py +0 -0
  58. {pyield-0.47.2 → pyield-0.47.4}/pyield/tn/auctions.py +0 -0
  59. {pyield-0.47.2 → pyield-0.47.4}/pyield/tn/benchmark.py +0 -0
  60. {pyield-0.47.2 → pyield-0.47.4}/pyield/tn/lft.py +0 -0
  61. {pyield-0.47.2 → pyield-0.47.4}/pyield/tn/ltn.py +0 -0
  62. {pyield-0.47.2 → pyield-0.47.4}/pyield/tn/ntnb.py +0 -0
  63. {pyield-0.47.2 → pyield-0.47.4}/pyield/tn/ntnb1.py +0 -0
  64. {pyield-0.47.2 → pyield-0.47.4}/pyield/tn/ntnbprinc.py +0 -0
  65. {pyield-0.47.2 → pyield-0.47.4}/pyield/tn/ntnc.py +0 -0
  66. {pyield-0.47.2 → pyield-0.47.4}/pyield/tn/ntnf.py +0 -0
  67. {pyield-0.47.2 → pyield-0.47.4}/pyield/tn/pre.py +0 -0
  68. {pyield-0.47.2 → pyield-0.47.4}/pyield/tn/utils.py +0 -0
  69. {pyield-0.47.2 → pyield-0.47.4}/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.4
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.4"
@@ -1,34 +1,58 @@
1
+ """
2
+ Exemplo de trecho do XML bruto da B3:
3
+ <PricRpt>
4
+ <TradDt>
5
+ <Dt>2026-04-01</Dt>
6
+ </TradDt>
7
+ <SctyId>
8
+ <TckrSymb>DI1F31</TckrSymb>
9
+ </SctyId>
10
+ <FinInstrmId>
11
+ <OthrId>
12
+ <Id>200000235664</Id>
13
+ <Tp>
14
+ <Prtry>8</Prtry>
15
+ </Tp>
16
+ </OthrId>
17
+ <PlcOfListg>
18
+ <MktIdrCd>BVMF</MktIdrCd>
19
+ </PlcOfListg>
20
+ </FinInstrmId>
21
+ <TradDtls>
22
+ <TradQty>29880</TradQty>
23
+ </TradDtls>
24
+ <FinInstrmAttrbts>
25
+ <MktDataStrmId>E</MktDataStrmId>
26
+ ...
27
+ <MinTradLmt Ccy="BRL">12.87</MinTradLmt>
28
+ </FinInstrmAttrbts>
29
+ </PricRpt>
30
+ """
31
+
1
32
  import datetime as dt
2
33
  import io
3
- import logging
34
+ import re
4
35
  import zipfile
5
- from functools import lru_cache
6
36
 
7
37
  import polars as pl
8
38
  import requests
9
39
  from lxml import etree
10
- from lxml.etree import _Element
11
40
 
12
41
  import pyield._internal.converters as cv
42
+ from pyield._internal.cache import ttl_cache
13
43
  from pyield._internal.retry import retry_padrao
14
44
  from pyield._internal.types import DateLike, any_is_empty
15
45
  from pyield.b3._contracts import normalizar_codigos_contrato
16
46
  from pyield.b3._validar_pregao import data_negociacao_valida
17
47
 
18
- registro = logging.getLogger(__name__)
19
-
20
48
  # --- Constantes de Processamento XML ---
21
49
  NAMESPACE_B3 = "urn:bvmf.217.01.xsd"
22
50
  NAMESPACES = {"ns": NAMESPACE_B3}
23
51
  # ZIP válido do price report ~2KB; 1KB detecta arquivos "sem dados"
24
52
  MIN_TAMANHO_ZIP_BYTES = 1024
25
- MODELO_XPATH_TICKER = '//ns:TckrSymb[starts-with(text(), "{prefixo_ticker}")]'
26
- XPATH_DATA_NEGOCIACAO = ".//ns:TradDt/ns:Dt"
27
- XPATH_ATRIBUTOS_INSTRUMENTO = ".//ns:FinInstrmAttrbts"
28
- XPATH_DETALHES_NEGOCIO = ".//ns:TradDtls"
53
+ XPATH_PRICE_REPORT = "//ns:PricRpt"
29
54
 
30
55
  # --- Mapeamento de Colunas ---
31
-
32
56
  # Estrutura: (id_pdf, nome_xml, tipo_polars)
33
57
  # Esta camada base preserva os nomes originais do XML da B3.
34
58
  # https://www.b3.com.br/data/files/16/70/29/9C/6219D710C8F297D7AC094EA8/Catalogo_precos_v1.3.pdf
@@ -74,10 +98,11 @@ COLUNAS_PRICE_REPORT: list[tuple[str, str, type[pl.DataType]]] = [
74
98
  ("5.32", "MinTradLmt", pl.Float64),
75
99
  ]
76
100
 
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}
101
+ # Schema completo: nome_xml tipo_polars. Garante ordem e tipagem constante.
102
+ SCHEMA_PRICE_REPORT = {nome: tipo for _, nome, tipo in COLUNAS_PRICE_REPORT}
79
103
 
80
104
 
105
+ @ttl_cache()
81
106
  @retry_padrao
82
107
  def _baixar_zip_url(data: dt.date, relatorio_completo: bool) -> bytes:
83
108
  data_str = data.strftime("%y%m%d")
@@ -124,42 +149,51 @@ def price_report_extract(conteudo_zip: bytes) -> bytes:
124
149
  return zip_interno.read(nomes_xml[-1])
125
150
 
126
151
 
127
- def _extrair_dados_contrato(elemento_ticker: _Element) -> dict | None:
128
- if elemento_ticker.text is None:
129
- return None
130
- pai = elemento_ticker.getparent()
131
- if pai is None:
132
- return None
133
- registro_pregao = pai.getparent()
134
- if registro_pregao is None:
135
- return None
136
- elemento_data = registro_pregao.find(XPATH_DATA_NEGOCIACAO, NAMESPACES)
137
- if elemento_data is None:
138
- return None
152
+ def _extrair_dados_contrato(pric_rpt: etree._Element) -> dict | None:
153
+ dados = {}
154
+ tem_ticker = False
155
+ tem_data = False
139
156
 
140
- dados_ticker = {"TradDt": elemento_data.text, "TckrSymb": elemento_ticker.text}
141
- atributos_instr = registro_pregao.find(XPATH_ATRIBUTOS_INSTRUMENTO, NAMESPACES)
142
- if atributos_instr is None:
143
- return None
157
+ for elem in pric_rpt.iter():
158
+ text = elem.text
159
+ if not text:
160
+ continue
144
161
 
145
- for attr in atributos_instr:
146
- nome_tag = etree.QName(attr).localname
147
- dados_ticker[nome_tag] = attr.text
162
+ tag = elem.tag
163
+ if tag[0] == "{":
164
+ tag = tag[tag.find("}") + 1 :]
148
165
 
149
- detalhes_negocio = registro_pregao.find(XPATH_DETALHES_NEGOCIO, NAMESPACES)
150
- if detalhes_negocio is not None:
151
- for detalhe in detalhes_negocio:
152
- nome_tag = etree.QName(detalhe).localname
153
- dados_ticker[nome_tag] = detalhe.text
166
+ # 🔑 obrigatórios primeiro
167
+ if tag == "TckrSymb":
168
+ dados["TckrSymb"] = text
169
+ tem_ticker = True
170
+ continue
154
171
 
155
- return dados_ticker
172
+ if tag == "Dt":
173
+ pai = elem.getparent()
174
+ if pai is None:
175
+ continue
156
176
 
177
+ parent = pai.tag
178
+ if parent[0] == "{":
179
+ parent = parent[parent.find("}") + 1 :]
157
180
 
158
- def _parsear_xml_registros(
159
- xml_bytes: bytes,
160
- prefixo_ticker: str,
161
- comprimento_ticker: int | None = None,
162
- ) -> list[dict]:
181
+ if parent == "TradDt":
182
+ dados["TradDt"] = text
183
+ tem_data = True
184
+ continue
185
+
186
+ # ⚡ resto
187
+ if tag in SCHEMA_PRICE_REPORT:
188
+ dados[tag] = text
189
+
190
+ if not tem_ticker or not tem_data:
191
+ return None
192
+
193
+ return dados
194
+
195
+
196
+ def _parsear_xml_registros(xml_bytes: bytes) -> list[dict]:
163
197
  analisador = etree.XMLParser(
164
198
  ns_clean=True,
165
199
  remove_blank_text=True,
@@ -169,72 +203,65 @@ def _parsear_xml_registros(
169
203
  no_network=True,
170
204
  load_dtd=False,
171
205
  )
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:
182
- return []
183
-
184
- registros = []
185
- for elemento in elementos_ticker:
186
- if not isinstance(elemento, etree._Element):
187
- continue
188
- dados_contrato = _extrair_dados_contrato(elemento)
189
- 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)
206
+ arvore = etree.parse(io.BytesIO(xml_bytes), parser=analisador)
207
+ resultado = arvore.xpath(XPATH_PRICE_REPORT, namespaces=NAMESPACES)
208
+ elementos: list[etree._Element] = resultado # type: ignore[assignment]
209
+ registros = [
210
+ dados
211
+ for pric_rpt in elementos
212
+ if (dados := _extrair_dados_contrato(pric_rpt)) is not None
213
+ ]
193
214
  return registros
194
215
 
195
216
 
196
217
  def _converter_para_df(registros: list[dict]) -> pl.DataFrame:
197
218
  df = pl.DataFrame(registros)
198
219
  # 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
220
+ tipos_coluna = {k: v for k, v in SCHEMA_PRICE_REPORT.items() if k in df.columns}
221
+ df = df.cast(tipos_coluna, strict=False) # type: ignore
222
+ # Adiciona colunas ausentes com null e garante ordem/schema constante
223
+ colunas_faltantes = {
224
+ nome: pl.lit(None).cast(tipo)
225
+ for nome, tipo in SCHEMA_PRICE_REPORT.items()
226
+ if nome not in df.columns
227
+ }
228
+ if colunas_faltantes:
229
+ df = df.with_columns(**colunas_faltantes)
230
+ return df.select(SCHEMA_PRICE_REPORT.keys())
231
+
232
+
233
+ def _processar_xml_extraido(xml_bytes: bytes) -> pl.DataFrame:
234
+ registros = _parsear_xml_registros(xml_bytes)
235
+ if not registros:
236
+ return pl.DataFrame()
237
+ return _converter_para_df(registros).sort("TckrSymb")
201
238
 
202
239
 
203
- def _processar_xml_extraido(
204
- xml_bytes: bytes,
205
- prefixo_ticker: str,
240
+ def _filtrar_df(
241
+ df: pl.DataFrame,
242
+ prefixos: list[str],
206
243
  comprimento_ticker: int | None = None,
207
244
  ) -> 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()
215
-
216
- df = _converter_para_df(registros)
245
+ ticker = pl.col("TckrSymb")
246
+ if comprimento_ticker:
247
+ df = df.filter(ticker.str.len_chars() == comprimento_ticker)
248
+ if prefixos:
249
+ padrao = f"^({'|'.join(re.escape(prefixo) for prefixo in prefixos)})"
250
+ df = df.filter(ticker.str.contains(padrao))
217
251
  return df.sort("TckrSymb")
218
252
 
219
253
 
220
- @lru_cache(maxsize=64)
221
- def _obter_xml_price_report(data: dt.date, relatorio_completo: bool) -> bytes:
254
+ def _obter_df_price_report(data: dt.date, relatorio_completo: bool) -> pl.DataFrame:
222
255
  dados_zip = _baixar_zip_url(data, relatorio_completo)
223
256
  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)
257
+ return pl.DataFrame()
258
+ xml_bytes = price_report_extract(dados_zip)
259
+ return _processar_xml_extraido(xml_bytes)
233
260
 
234
261
 
235
262
  def price_report_fetch(
236
263
  date: DateLike,
237
- ticker_prefix: str | list[str],
264
+ ticker_prefix: str | list[str] | None = None,
238
265
  ticker_length: int | None = None,
239
266
  full_report: bool = False,
240
267
  ) -> pl.DataFrame:
@@ -259,7 +286,8 @@ def price_report_fetch(
259
286
  'YYYY-MM-DD' ou objeto datetime.date.
260
287
  ticker_prefix: Prefixo do ticker B3 (ex.: 'DI1', 'DOL',
261
288
  'CPM') ou lista de prefixos (ex.: ['DI1', 'DAP']).
262
- Usado como filtro starts-with no XML.
289
+ Usado como filtro starts-with no XML. Se None (padrão),
290
+ retorna todos os ativos sem filtro.
263
291
  ticker_length: Comprimento exato do ticker para filtrar registros.
264
292
  Se None (padrão), retorna todos os tickers que casam com o
265
293
  prefixo (ex.: 6 para futuros, 13 para opções).
@@ -286,10 +314,8 @@ def price_report_fetch(
286
314
  * IntlFinVol (Float64): volume financeiro internacional.
287
315
  * OpnIntrst (Int64): contratos em aberto.
288
316
  * FinInstrmQty (Int64): quantidade negociada de instrumentos financeiros.
289
- * BestBidPric (Float64): ultima melhor oferta de compra no snapshot
290
- diario; pode ser nulo.
291
- * BestAskPric (Float64): ultima melhor oferta de venda no snapshot
292
- diario; pode ser nulo.
317
+ * BestBidPric (Float64): ultima melhor oferta de compra no snapshot diario; pode ser nulo.
318
+ * BestAskPric (Float64): ultima melhor oferta de venda no snapshot diario; pode ser nulo. diario; pode ser nulo.
293
319
  * FrstPric (Float64): preço de abertura.
294
320
  * MinPric (Float64): preço mínimo negociado.
295
321
  * MaxPric (Float64): preço máximo negociado.
@@ -333,8 +359,7 @@ def price_report_fetch(
333
359
  >>> df.is_empty()
334
360
  True
335
361
  """
336
- prefixos = normalizar_codigos_contrato(ticker_prefix)
337
- if any_is_empty(date) or not prefixos:
362
+ if any_is_empty(date):
338
363
  return pl.DataFrame()
339
364
 
340
365
  date = cv.converter_datas(date)
@@ -342,20 +367,19 @@ def price_report_fetch(
342
367
  if not data_negociacao_valida(date):
343
368
  return pl.DataFrame()
344
369
 
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:
370
+ df = _obter_df_price_report(date, full_report)
371
+ if df.is_empty() or ticker_prefix is None:
372
+ return df
373
+
374
+ prefixos = normalizar_codigos_contrato(ticker_prefix)
375
+ if not prefixos:
352
376
  return pl.DataFrame()
353
- return pl.concat(dataframes, how="diagonal").sort("TckrSymb")
377
+ return _filtrar_df(df, prefixos, ticker_length)
354
378
 
355
379
 
356
380
  def price_report_read(
357
381
  xml_bytes: bytes,
358
- ticker_prefix: str | list[str],
382
+ ticker_prefix: str | list[str] | None = None,
359
383
  ticker_length: int | None = None,
360
384
  ) -> pl.DataFrame:
361
385
  """Lê e processa o price report da B3 a partir do conteúdo XML bruto.
@@ -367,6 +391,7 @@ def price_report_read(
367
391
  xml_bytes: Conteúdo do XML em bytes (já descomprimido).
368
392
  ticker_prefix: Prefixo do ticker B3 (ex.: 'DI1', 'DOL',
369
393
  'CPM') ou lista de prefixos (ex.: ['DI1', 'DAP']).
394
+ Se None (padrão), retorna todos os ativos sem filtro.
370
395
  ticker_length: Comprimento exato do ticker para filtrar registros.
371
396
  Se None (padrão), retorna todos os tickers que casam com o
372
397
  prefixo.
@@ -375,15 +400,14 @@ def price_report_read(
375
400
  pl.DataFrame: DataFrame com as mesmas colunas documentadas em
376
401
  :func:`price_report_fetch`.
377
402
  """
378
- prefixos = normalizar_codigos_contrato(ticker_prefix)
379
- if any_is_empty(xml_bytes) or not prefixos:
403
+ if any_is_empty(xml_bytes):
380
404
  return pl.DataFrame()
381
405
 
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:
406
+ df = _processar_xml_extraido(xml_bytes)
407
+ if df.is_empty() or ticker_prefix is None:
408
+ return df
409
+
410
+ prefixos = normalizar_codigos_contrato(ticker_prefix)
411
+ if not prefixos:
388
412
  return pl.DataFrame()
389
- return pl.concat(dataframes, how="diagonal").sort("TckrSymb")
413
+ 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