pyield 0.47.3__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.3 → pyield-0.47.4}/PKG-INFO +1 -1
  2. pyield-0.47.4/pyield/__about__.py +1 -0
  3. {pyield-0.47.3 → pyield-0.47.4}/pyield/b3/price_report.py +84 -50
  4. pyield-0.47.3/pyield/__about__.py +0 -1
  5. {pyield-0.47.3 → pyield-0.47.4}/.gitignore +0 -0
  6. {pyield-0.47.3 → pyield-0.47.4}/LICENSE +0 -0
  7. {pyield-0.47.3 → pyield-0.47.4}/README.md +0 -0
  8. {pyield-0.47.3 → pyield-0.47.4}/pyield/__init__.py +0 -0
  9. {pyield-0.47.3 → pyield-0.47.4}/pyield/_internal/__init__.py +0 -0
  10. {pyield-0.47.3 → pyield-0.47.4}/pyield/_internal/br_numbers.py +0 -0
  11. {pyield-0.47.3 → pyield-0.47.4}/pyield/_internal/cache.py +0 -0
  12. {pyield-0.47.3 → pyield-0.47.4}/pyield/_internal/converters.py +0 -0
  13. {pyield-0.47.3 → pyield-0.47.4}/pyield/_internal/data_cache.py +0 -0
  14. {pyield-0.47.3 → pyield-0.47.4}/pyield/_internal/retry.py +0 -0
  15. {pyield-0.47.3 → pyield-0.47.4}/pyield/_internal/types.py +0 -0
  16. {pyield-0.47.3 → pyield-0.47.4}/pyield/anbima/__init__.py +0 -0
  17. {pyield-0.47.3 → pyield-0.47.4}/pyield/anbima/ettj_intraday.py +0 -0
  18. {pyield-0.47.3 → pyield-0.47.4}/pyield/anbima/ettj_last.py +0 -0
  19. {pyield-0.47.3 → pyield-0.47.4}/pyield/anbima/ima.py +0 -0
  20. {pyield-0.47.3 → pyield-0.47.4}/pyield/anbima/imaq.py +0 -0
  21. {pyield-0.47.3 → pyield-0.47.4}/pyield/anbima/tpf.py +0 -0
  22. {pyield-0.47.3 → pyield-0.47.4}/pyield/b3/__init__.py +0 -0
  23. {pyield-0.47.3 → pyield-0.47.4}/pyield/b3/_contracts.py +0 -0
  24. {pyield-0.47.3 → pyield-0.47.4}/pyield/b3/_validar_pregao.py +0 -0
  25. {pyield-0.47.3 → pyield-0.47.4}/pyield/b3/derivatives_intraday.py +0 -0
  26. {pyield-0.47.3 → pyield-0.47.4}/pyield/b3/di1.py +0 -0
  27. {pyield-0.47.3 → pyield-0.47.4}/pyield/b3/di_over.py +0 -0
  28. {pyield-0.47.3 → pyield-0.47.4}/pyield/b3/futures/__init__.py +0 -0
  29. {pyield-0.47.3 → pyield-0.47.4}/pyield/b3/futures/common.py +0 -0
  30. {pyield-0.47.3 → pyield-0.47.4}/pyield/b3/futures/historical.py +0 -0
  31. {pyield-0.47.3 → pyield-0.47.4}/pyield/b3/futures/intraday.py +0 -0
  32. {pyield-0.47.3 → pyield-0.47.4}/pyield/bc/__init__.py +0 -0
  33. {pyield-0.47.3 → pyield-0.47.4}/pyield/bc/auction.py +0 -0
  34. {pyield-0.47.3 → pyield-0.47.4}/pyield/bc/copom.py +0 -0
  35. {pyield-0.47.3 → pyield-0.47.4}/pyield/bc/ptax_api.py +0 -0
  36. {pyield-0.47.3 → pyield-0.47.4}/pyield/bc/rates.py +0 -0
  37. {pyield-0.47.3 → pyield-0.47.4}/pyield/bc/repo.py +0 -0
  38. {pyield-0.47.3 → pyield-0.47.4}/pyield/bc/trades_intraday.py +0 -0
  39. {pyield-0.47.3 → pyield-0.47.4}/pyield/bc/trades_monthly.py +0 -0
  40. {pyield-0.47.3 → pyield-0.47.4}/pyield/bc/vna.py +0 -0
  41. {pyield-0.47.3 → pyield-0.47.4}/pyield/bday/__init__.py +0 -0
  42. {pyield-0.47.3 → pyield-0.47.4}/pyield/bday/core.py +0 -0
  43. {pyield-0.47.3 → pyield-0.47.4}/pyield/bday/holidays/br_holidays_new.txt +0 -0
  44. {pyield-0.47.3 → pyield-0.47.4}/pyield/bday/holidays/br_holidays_old.txt +0 -0
  45. {pyield-0.47.3 → pyield-0.47.4}/pyield/bday/holidays/brholidays.py +0 -0
  46. {pyield-0.47.3 → pyield-0.47.4}/pyield/clock.py +0 -0
  47. {pyield-0.47.3 → pyield-0.47.4}/pyield/fwd.py +0 -0
  48. {pyield-0.47.3 → pyield-0.47.4}/pyield/interpolator.py +0 -0
  49. {pyield-0.47.3 → pyield-0.47.4}/pyield/ipca/__init__.py +0 -0
  50. {pyield-0.47.3 → pyield-0.47.4}/pyield/ipca/historical.py +0 -0
  51. {pyield-0.47.3 → pyield-0.47.4}/pyield/ipca/projected.py +0 -0
  52. {pyield-0.47.3 → pyield-0.47.4}/pyield/py.typed +0 -0
  53. {pyield-0.47.3 → pyield-0.47.4}/pyield/rmd.py +0 -0
  54. {pyield-0.47.3 → pyield-0.47.4}/pyield/selic/__init__.py +0 -0
  55. {pyield-0.47.3 → pyield-0.47.4}/pyield/selic/cpm.py +0 -0
  56. {pyield-0.47.3 → pyield-0.47.4}/pyield/selic/probabilities.py +0 -0
  57. {pyield-0.47.3 → pyield-0.47.4}/pyield/tn/__init__.py +0 -0
  58. {pyield-0.47.3 → pyield-0.47.4}/pyield/tn/auctions.py +0 -0
  59. {pyield-0.47.3 → pyield-0.47.4}/pyield/tn/benchmark.py +0 -0
  60. {pyield-0.47.3 → pyield-0.47.4}/pyield/tn/lft.py +0 -0
  61. {pyield-0.47.3 → pyield-0.47.4}/pyield/tn/ltn.py +0 -0
  62. {pyield-0.47.3 → pyield-0.47.4}/pyield/tn/ntnb.py +0 -0
  63. {pyield-0.47.3 → pyield-0.47.4}/pyield/tn/ntnb1.py +0 -0
  64. {pyield-0.47.3 → pyield-0.47.4}/pyield/tn/ntnbprinc.py +0 -0
  65. {pyield-0.47.3 → pyield-0.47.4}/pyield/tn/ntnc.py +0 -0
  66. {pyield-0.47.3 → pyield-0.47.4}/pyield/tn/ntnf.py +0 -0
  67. {pyield-0.47.3 → pyield-0.47.4}/pyield/tn/pre.py +0 -0
  68. {pyield-0.47.3 → pyield-0.47.4}/pyield/tn/utils.py +0 -0
  69. {pyield-0.47.3 → 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.3
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
@@ -0,0 +1 @@
1
+ __version__ = "0.47.4"
@@ -1,5 +1,37 @@
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
34
+ import re
3
35
  import zipfile
4
36
 
5
37
  import polars as pl
@@ -18,13 +50,9 @@ NAMESPACE_B3 = "urn:bvmf.217.01.xsd"
18
50
  NAMESPACES = {"ns": NAMESPACE_B3}
19
51
  # ZIP válido do price report ~2KB; 1KB detecta arquivos "sem dados"
20
52
  MIN_TAMANHO_ZIP_BYTES = 1024
21
- XPATH_TODOS_TICKERS = "//ns:TckrSymb"
22
- XPATH_DATA_NEGOCIACAO = ".//ns:TradDt/ns:Dt"
23
- XPATH_ATRIBUTOS_INSTRUMENTO = ".//ns:FinInstrmAttrbts"
24
- XPATH_DETALHES_NEGOCIO = ".//ns:TradDtls"
53
+ XPATH_PRICE_REPORT = "//ns:PricRpt"
25
54
 
26
55
  # --- Mapeamento de Colunas ---
27
-
28
56
  # Estrutura: (id_pdf, nome_xml, tipo_polars)
29
57
  # Esta camada base preserva os nomes originais do XML da B3.
30
58
  # https://www.b3.com.br/data/files/16/70/29/9C/6219D710C8F297D7AC094EA8/Catalogo_precos_v1.3.pdf
@@ -121,35 +149,48 @@ def price_report_extract(conteudo_zip: bytes) -> bytes:
121
149
  return zip_interno.read(nomes_xml[-1])
122
150
 
123
151
 
124
- def _extrair_dados_contrato(elemento_ticker: etree._Element) -> dict | None:
125
- if elemento_ticker.text is None:
126
- return None
127
- pai = elemento_ticker.getparent()
128
- if pai is None:
129
- return None
130
- registro_pregao = pai.getparent()
131
- if registro_pregao is None:
132
- return None
133
- elemento_data = registro_pregao.find(XPATH_DATA_NEGOCIACAO, NAMESPACES)
134
- if elemento_data is None:
135
- return None
152
+ def _extrair_dados_contrato(pric_rpt: etree._Element) -> dict | None:
153
+ dados = {}
154
+ tem_ticker = False
155
+ tem_data = False
136
156
 
137
- dados_ticker = {"TradDt": elemento_data.text, "TckrSymb": elemento_ticker.text}
138
- atributos_instr = registro_pregao.find(XPATH_ATRIBUTOS_INSTRUMENTO, NAMESPACES)
139
- if atributos_instr is None:
140
- return None
157
+ for elem in pric_rpt.iter():
158
+ text = elem.text
159
+ if not text:
160
+ continue
161
+
162
+ tag = elem.tag
163
+ if tag[0] == "{":
164
+ tag = tag[tag.find("}") + 1 :]
141
165
 
142
- for attr in atributos_instr:
143
- nome_tag = etree.QName(attr).localname
144
- dados_ticker[nome_tag] = attr.text
166
+ # 🔑 obrigatórios primeiro
167
+ if tag == "TckrSymb":
168
+ dados["TckrSymb"] = text
169
+ tem_ticker = True
170
+ continue
145
171
 
146
- detalhes_negocio = registro_pregao.find(XPATH_DETALHES_NEGOCIO, NAMESPACES)
147
- if detalhes_negocio is not None:
148
- for detalhe in detalhes_negocio:
149
- nome_tag = etree.QName(detalhe).localname
150
- dados_ticker[nome_tag] = detalhe.text
172
+ if tag == "Dt":
173
+ pai = elem.getparent()
174
+ if pai is None:
175
+ continue
151
176
 
152
- return dados_ticker
177
+ parent = pai.tag
178
+ if parent[0] == "{":
179
+ parent = parent[parent.find("}") + 1 :]
180
+
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
153
194
 
154
195
 
155
196
  def _parsear_xml_registros(xml_bytes: bytes) -> list[dict]:
@@ -163,16 +204,13 @@ def _parsear_xml_registros(xml_bytes: bytes) -> list[dict]:
163
204
  load_dtd=False,
164
205
  )
165
206
  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:
169
- return []
170
-
171
- registros = []
172
- for elemento in elementos:
173
- dados_contrato = _extrair_dados_contrato(elemento)
174
- if dados_contrato is not None:
175
- registros.append(dados_contrato)
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
+ ]
176
214
  return registros
177
215
 
178
216
 
@@ -204,14 +242,12 @@ def _filtrar_df(
204
242
  prefixos: list[str],
205
243
  comprimento_ticker: int | None = None,
206
244
  ) -> pl.DataFrame:
245
+ ticker = pl.col("TckrSymb")
207
246
  if comprimento_ticker:
208
- df = df.filter(pl.col("TckrSymb").str.len_chars() == comprimento_ticker)
209
-
247
+ df = df.filter(ticker.str.len_chars() == comprimento_ticker)
210
248
  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)
249
+ padrao = f"^({'|'.join(re.escape(prefixo) for prefixo in prefixos)})"
250
+ df = df.filter(ticker.str.contains(padrao))
215
251
  return df.sort("TckrSymb")
216
252
 
217
253
 
@@ -278,10 +314,8 @@ def price_report_fetch(
278
314
  * IntlFinVol (Float64): volume financeiro internacional.
279
315
  * OpnIntrst (Int64): contratos em aberto.
280
316
  * FinInstrmQty (Int64): quantidade negociada de instrumentos financeiros.
281
- * BestBidPric (Float64): ultima melhor oferta de compra no snapshot
282
- diario; pode ser nulo.
283
- * BestAskPric (Float64): ultima melhor oferta de venda no snapshot
284
- 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.
285
319
  * FrstPric (Float64): preço de abertura.
286
320
  * MinPric (Float64): preço mínimo negociado.
287
321
  * MaxPric (Float64): preço máximo negociado.
@@ -1 +0,0 @@
1
- __version__ = "0.47.3"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes