PYield 0.46.7__tar.gz → 0.47.0__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.46.7 → pyield-0.47.0}/PKG-INFO +1 -1
  2. pyield-0.47.0/pyield/__about__.py +1 -0
  3. {pyield-0.46.7 → pyield-0.47.0}/pyield/__init__.py +2 -1
  4. {pyield-0.46.7 → pyield-0.47.0}/pyield/b3/__init__.py +2 -1
  5. {pyield-0.46.7 → pyield-0.47.0}/pyield/b3/di1.py +2 -2
  6. {pyield-0.46.7 → pyield-0.47.0}/pyield/b3/futures/__init__.py +62 -75
  7. {pyield-0.46.7 → pyield-0.47.0}/pyield/b3/futures/historical.py +9 -53
  8. {pyield-0.46.7 → pyield-0.47.0}/pyield/b3/price_report.py +9 -21
  9. pyield-0.46.7/pyield/__about__.py +0 -1
  10. {pyield-0.46.7 → pyield-0.47.0}/.gitignore +0 -0
  11. {pyield-0.46.7 → pyield-0.47.0}/LICENSE +0 -0
  12. {pyield-0.46.7 → pyield-0.47.0}/README.md +0 -0
  13. {pyield-0.46.7 → pyield-0.47.0}/pyield/_internal/__init__.py +0 -0
  14. {pyield-0.46.7 → pyield-0.47.0}/pyield/_internal/br_numbers.py +0 -0
  15. {pyield-0.46.7 → pyield-0.47.0}/pyield/_internal/cache.py +0 -0
  16. {pyield-0.46.7 → pyield-0.47.0}/pyield/_internal/converters.py +0 -0
  17. {pyield-0.46.7 → pyield-0.47.0}/pyield/_internal/data_cache.py +0 -0
  18. {pyield-0.46.7 → pyield-0.47.0}/pyield/_internal/retry.py +0 -0
  19. {pyield-0.46.7 → pyield-0.47.0}/pyield/_internal/types.py +0 -0
  20. {pyield-0.46.7 → pyield-0.47.0}/pyield/anbima/__init__.py +0 -0
  21. {pyield-0.46.7 → pyield-0.47.0}/pyield/anbima/ettj_intraday.py +0 -0
  22. {pyield-0.46.7 → pyield-0.47.0}/pyield/anbima/ettj_last.py +0 -0
  23. {pyield-0.46.7 → pyield-0.47.0}/pyield/anbima/ima.py +0 -0
  24. {pyield-0.46.7 → pyield-0.47.0}/pyield/anbima/imaq.py +0 -0
  25. {pyield-0.46.7 → pyield-0.47.0}/pyield/anbima/tpf.py +0 -0
  26. {pyield-0.46.7 → pyield-0.47.0}/pyield/b3/_contracts.py +0 -0
  27. {pyield-0.46.7 → pyield-0.47.0}/pyield/b3/_validar_pregao.py +0 -0
  28. {pyield-0.46.7 → pyield-0.47.0}/pyield/b3/di_over.py +0 -0
  29. {pyield-0.46.7 → pyield-0.47.0}/pyield/b3/futures/common.py +0 -0
  30. {pyield-0.46.7 → pyield-0.47.0}/pyield/b3/futures/intraday.py +0 -0
  31. {pyield-0.46.7 → pyield-0.47.0}/pyield/b3/intraday_derivatives.py +0 -0
  32. {pyield-0.46.7 → pyield-0.47.0}/pyield/bc/__init__.py +0 -0
  33. {pyield-0.46.7 → pyield-0.47.0}/pyield/bc/auction.py +0 -0
  34. {pyield-0.46.7 → pyield-0.47.0}/pyield/bc/copom.py +0 -0
  35. {pyield-0.46.7 → pyield-0.47.0}/pyield/bc/ptax_api.py +0 -0
  36. {pyield-0.46.7 → pyield-0.47.0}/pyield/bc/rates.py +0 -0
  37. {pyield-0.46.7 → pyield-0.47.0}/pyield/bc/repo.py +0 -0
  38. {pyield-0.46.7 → pyield-0.47.0}/pyield/bc/trades_intraday.py +0 -0
  39. {pyield-0.46.7 → pyield-0.47.0}/pyield/bc/trades_monthly.py +0 -0
  40. {pyield-0.46.7 → pyield-0.47.0}/pyield/bc/vna.py +0 -0
  41. {pyield-0.46.7 → pyield-0.47.0}/pyield/bday/__init__.py +0 -0
  42. {pyield-0.46.7 → pyield-0.47.0}/pyield/bday/core.py +0 -0
  43. {pyield-0.46.7 → pyield-0.47.0}/pyield/bday/holidays/br_holidays_new.txt +0 -0
  44. {pyield-0.46.7 → pyield-0.47.0}/pyield/bday/holidays/br_holidays_old.txt +0 -0
  45. {pyield-0.46.7 → pyield-0.47.0}/pyield/bday/holidays/brholidays.py +0 -0
  46. {pyield-0.46.7 → pyield-0.47.0}/pyield/clock.py +0 -0
  47. {pyield-0.46.7 → pyield-0.47.0}/pyield/fwd.py +0 -0
  48. {pyield-0.46.7 → pyield-0.47.0}/pyield/interpolator.py +0 -0
  49. {pyield-0.46.7 → pyield-0.47.0}/pyield/ipca/__init__.py +0 -0
  50. {pyield-0.46.7 → pyield-0.47.0}/pyield/ipca/historical.py +0 -0
  51. {pyield-0.46.7 → pyield-0.47.0}/pyield/ipca/projected.py +0 -0
  52. {pyield-0.46.7 → pyield-0.47.0}/pyield/py.typed +0 -0
  53. {pyield-0.46.7 → pyield-0.47.0}/pyield/rmd.py +0 -0
  54. {pyield-0.46.7 → pyield-0.47.0}/pyield/selic/__init__.py +0 -0
  55. {pyield-0.46.7 → pyield-0.47.0}/pyield/selic/cpm.py +0 -0
  56. {pyield-0.46.7 → pyield-0.47.0}/pyield/selic/probabilities.py +0 -0
  57. {pyield-0.46.7 → pyield-0.47.0}/pyield/tn/__init__.py +0 -0
  58. {pyield-0.46.7 → pyield-0.47.0}/pyield/tn/auctions.py +0 -0
  59. {pyield-0.46.7 → pyield-0.47.0}/pyield/tn/benchmark.py +0 -0
  60. {pyield-0.46.7 → pyield-0.47.0}/pyield/tn/lft.py +0 -0
  61. {pyield-0.46.7 → pyield-0.47.0}/pyield/tn/ltn.py +0 -0
  62. {pyield-0.46.7 → pyield-0.47.0}/pyield/tn/ntnb.py +0 -0
  63. {pyield-0.46.7 → pyield-0.47.0}/pyield/tn/ntnb1.py +0 -0
  64. {pyield-0.46.7 → pyield-0.47.0}/pyield/tn/ntnbprinc.py +0 -0
  65. {pyield-0.46.7 → pyield-0.47.0}/pyield/tn/ntnc.py +0 -0
  66. {pyield-0.46.7 → pyield-0.47.0}/pyield/tn/ntnf.py +0 -0
  67. {pyield-0.46.7 → pyield-0.47.0}/pyield/tn/pre.py +0 -0
  68. {pyield-0.46.7 → pyield-0.47.0}/pyield/tn/utils.py +0 -0
  69. {pyield-0.46.7 → pyield-0.47.0}/pyproject.toml +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyield
3
- Version: 0.46.7
3
+ Version: 0.47.0
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.0"
@@ -2,7 +2,7 @@ import logging
2
2
 
3
3
  from pyield import anbima, b3, bc, bday, ipca, selic, tn
4
4
  from pyield.__about__ import __version__
5
- from pyield.b3 import di1, futures
5
+ from pyield.b3 import di1, futures, futures_intraday
6
6
  from pyield.clock import now, today
7
7
  from pyield.fwd import forward, forwards
8
8
  from pyield.interpolator import Interpolator
@@ -37,6 +37,7 @@ __all__ = [
37
37
  "rmd",
38
38
  "bday",
39
39
  "selic",
40
+ "futures_intraday",
40
41
  ]
41
42
 
42
43
  # Configura o logger do pacote principal com um NullHandler
@@ -1,6 +1,6 @@
1
1
  from pyield.b3 import di1
2
2
  from pyield.b3.di_over import di_over
3
- from pyield.b3.futures import futures
3
+ from pyield.b3.futures import futures, futures_intraday
4
4
  from pyield.b3.intraday_derivatives import fetch_intraday_derivatives
5
5
  from pyield.b3.price_report import fetch_price_report, read_price_report
6
6
 
@@ -8,6 +8,7 @@ __all__ = [
8
8
  "fetch_intraday_derivatives",
9
9
  "di_over",
10
10
  "futures",
11
+ "futures_intraday",
11
12
  "fetch_price_report",
12
13
  "read_price_report",
13
14
  "di1",
@@ -62,7 +62,7 @@ def data(
62
62
  else:
63
63
  datas_lista = [datas_convertidas]
64
64
 
65
- df = b3.futures(date=datas_lista, contract_code="DI1", full_report=False)
65
+ df = b3.futures(date=datas_lista, contract_code="DI1")
66
66
  if df.is_empty():
67
67
  return df
68
68
 
@@ -195,7 +195,7 @@ def interpolate_rates(
195
195
 
196
196
  # Carrega dataset de taxas DI usando datas já convertidas do df_entrada
197
197
  datas_unicas = df_entrada["data_referencia"].drop_nulls().unique().sort().to_list()
198
- df_ref = b3.futures(date=datas_unicas, contract_code="DI1", full_report=False)
198
+ df_ref = b3.futures(date=datas_unicas, contract_code="DI1")
199
199
  # Retorna Series vazia se nenhuma taxa for encontrada
200
200
  if df_ref.is_empty():
201
201
  return pl.Series(dtype=pl.Float64)
@@ -1,56 +1,37 @@
1
1
  import datetime as dt
2
- import logging
3
2
 
4
3
  import polars as pl
5
4
 
6
5
  import pyield._internal.converters as cv
7
- from pyield import clock
8
6
  from pyield._internal.types import ArrayLike, DateLike, any_is_empty, is_collection
9
7
  from pyield.b3._contracts import normalizar_codigos_contrato
10
- from pyield.b3._validar_pregao import HORA_INICIO_INTRADAY, data_negociacao_valida
8
+ from pyield.b3._validar_pregao import data_negociacao_valida
11
9
  from pyield.b3.futures import historical, intraday
12
10
 
13
- # A partir desse horário, os dados consolidados (SPR/PR) já estão disponíveis.
14
- HORA_INICIO_CONSOLIDADO = dt.time(19, 0)
15
-
16
- logger = logging.getLogger(__name__)
17
-
18
11
 
19
12
  def futures(
20
13
  date: DateLike | ArrayLike,
21
14
  contract_code: str | list[str],
22
- full_report: bool | None = None,
23
15
  ) -> pl.DataFrame:
24
16
  """Busca dados de um contrato futuro da B3 para a data de referência.
25
17
 
18
+ Dados obtidos do dataset PR cacheado no GitHub (disponível desde 2018).
19
+ Para dados do pregão corrente, use ``futures_intraday``.
20
+
26
21
  Args:
27
22
  date: Data de referência para consulta ou coleção de datas.
28
23
  Quando uma coleção é fornecida, os dados são buscados para cada
29
24
  data individualmente e concatenados. Datas inválidas (feriados,
30
25
  fins de semana, futuras) são silenciosamente ignoradas.
31
- contract_code: Código do contrato futuro na B3 ou coleção de códigos.
32
- Exemplos de códigos válidos:
33
- - Juros: DI1, DDI, OC1, DAP, IAP
34
- - Moedas: DOL, WDO, EUR, GBR, JAP, CNY
35
- - Índices: IND, WIN, ISP, WSP
36
- - Commodities: BGI, CCM, ICF, CNL, SJC, SOY, ETH, GLD
37
- full_report: Controla a fonte de dados quando o dado não está no cache.
38
- Se None (padrão) e a data é hoje: entre 09:16 e 19:00 prioriza
39
- dados intraday; a partir das 19:00 prioriza os dados consolidados
40
- (SPR → PR) com intraday como fallback.
41
- Se False, usa o simplified price report (SPR, ~2 KB).
42
- Se True, usa apenas o price report completo (PR, ~2 MB) —
43
- indicado para processos batch noturnos.
26
+ contract_code: Código do contrato futuro na B3 ou coleção de
27
+ códigos. Contratos disponíveis no cache histórico:
28
+ - Juros: DI1, DDI, FRC, FRO, DAP
29
+ - Moedas: DOL, WDO
30
+ - Índices: IND, WIN
44
31
 
45
32
  Returns:
46
33
  DataFrame Polars com os dados do contrato informado.
47
34
 
48
- Notes:
49
- Os contratos DI1, DDI, FRC, FRO, DAP, DOL, WDO, IND e WIN possuem
50
- histórico pré-cacheado (desde 2018) e são retornados instantaneamente.
51
- Para os demais contratos, os dados são baixados diretamente da B3 a
52
- cada chamada, o que pode ser mais lento.
53
-
54
35
  Examples:
55
36
  >>> df = futures("31-05-2024", "DI1")
56
37
  >>> df = futures("31-05-2024", "DAP")
@@ -88,26 +69,21 @@ def futures(
88
69
  if is_collection(date):
89
70
  # date é ArrayLike neste branch
90
71
  datas: ArrayLike = date # type: ignore[assignment]
91
- return _buscar_varias_datas(datas, codigos_contrato, full_report)
72
+ return _buscar_varias_datas(datas, codigos_contrato)
92
73
 
93
74
  # date é escalar (DateLike) neste branch
94
75
  data_negociacao: dt.date = cv.converter_datas(date) # type: ignore[assignment]
95
76
  if not data_negociacao_valida(data_negociacao):
96
77
  return pl.DataFrame()
97
78
 
98
- return _buscar_por_fonte(data_negociacao, codigos_contrato, full_report)
79
+ return historical.historical(data_negociacao, codigos_contrato)
99
80
 
100
81
 
101
82
  def _buscar_varias_datas(
102
83
  datas: ArrayLike,
103
84
  codigos: list[str],
104
- full_report: bool | None,
105
85
  ) -> pl.DataFrame:
106
- """Busca dados para múltiplas datas e concatena os resultados.
107
-
108
- Prioriza o dataset PR cacheado (bulk). Para datas ausentes no cache,
109
- busca individualmente via _buscar_por_fonte.
110
- """
86
+ """Busca dados para múltiplas datas do dataset PR cacheado."""
111
87
  serie_datas = cv.converter_datas(datas)
112
88
  datas_validas = [
113
89
  d for d in serie_datas if d is not None and data_negociacao_valida(d)
@@ -115,54 +91,65 @@ def _buscar_varias_datas(
115
91
  if not datas_validas:
116
92
  return pl.DataFrame()
117
93
 
118
- # Bulk: carrega todas as datas de uma vez do cache, por contrato
119
- resultados = []
120
- for codigo in codigos:
121
- df_cache = historical._obter_futuros_pr(datas_validas, codigo)
122
- if not df_cache.is_empty():
123
- resultados.append(df_cache)
124
-
125
- # Identifica datas que não vieram do cache
126
- datas_no_cache: set[dt.date] = set()
127
- for df in resultados:
128
- datas_no_cache.update(df["data_referencia"].unique().to_list())
129
- datas_faltantes = [d for d in datas_validas if d not in datas_no_cache]
130
-
131
- # Fallback individual apenas para datas faltantes
132
- for data in datas_faltantes:
133
- df = _buscar_por_fonte(data, codigos, full_report)
134
- if not df.is_empty():
135
- resultados.append(df)
94
+ resultados = [
95
+ df
96
+ for codigo in codigos
97
+ if not (df := historical._obter_futuros_pr(datas_validas, codigo)).is_empty()
98
+ ]
136
99
 
137
100
  if not resultados:
138
101
  return pl.DataFrame()
139
102
  return pl.concat(resultados, how="diagonal_relaxed")
140
103
 
141
104
 
142
- def _buscar_por_fonte(
143
- data: dt.date,
144
- codigos: list[str],
145
- full_report: bool | None,
105
+ def futures_intraday(
106
+ contract_code: str | list[str],
146
107
  ) -> pl.DataFrame:
147
- """Seleciona a fonte de dados com base no horário e parâmetros."""
148
- if full_report is not None or data != clock.today():
149
- return historical.historical(data, codigos, full_report)
108
+ """Busca dados intraday de contratos futuros da B3.
150
109
 
151
- horario = clock.now().time()
152
- if horario >= HORA_INICIO_CONSOLIDADO:
153
- # Consolidado (SPR → PR) primeiro; intraday como fallback
154
- df = historical.historical(data, codigos, full_report)
155
- if not df.is_empty():
156
- return df
157
- return intraday.intraday(codigos)
110
+ Retorna os dados mais recentes do pregão corrente, com atraso
111
+ aproximado de 15 minutos. Para dados históricos consolidados,
112
+ use ``futures``.
113
+
114
+ Args:
115
+ contract_code: Código do contrato futuro na B3 ou lista de
116
+ códigos (ex.: 'DI1', ['DI1', 'DAP']).
158
117
 
159
- if horario >= HORA_INICIO_INTRADAY:
160
- # Intraday primeiro; historical como fallback
161
- df = intraday.intraday(codigos)
162
- if not df.is_empty():
163
- return df
118
+ Returns:
119
+ DataFrame Polars com dados intraday. Retorna DataFrame vazio
120
+ fora do horário de pregão.
121
+
122
+ Output Columns:
123
+ * data_referencia (Date): data de negociação.
124
+ * atualizado_as (Datetime): horário a que o dado se refere
125
+ (com atraso de ~15 min).
126
+ * codigo_negociacao (String): código de negociação na B3.
127
+ * data_vencimento (Date): data de vencimento do contrato.
128
+ * dias_uteis (Int64): dias úteis até o vencimento.
129
+ * dias_corridos (Int64): dias corridos até o vencimento.
130
+ * contratos_abertos (Int64): contratos em aberto.
131
+ * numero_negocios (Int64): número de negócios.
132
+ * volume_negociado (Int64): quantidade de contratos negociados.
133
+ * volume_financeiro (Float64): volume financeiro bruto.
134
+ * dv01 (Float64): variação no preço para 1bp (apenas DI1).
135
+ * preco_ultimo (Float64): último preço (apenas DI1/DAP).
136
+ * taxa_ajuste_anterior (Float64): taxa de ajuste do dia anterior.
137
+ * taxa_limite_minimo (Float64): limite mínimo de variação.
138
+ * taxa_limite_maximo (Float64): limite máximo de variação.
139
+ * taxa_abertura (Float64): taxa de abertura.
140
+ * taxa_minima (Float64): taxa mínima negociada.
141
+ * taxa_media (Float64): taxa média negociada.
142
+ * taxa_maxima (Float64): taxa máxima negociada.
143
+ * taxa_oferta_compra (Float64): melhor oferta de compra.
144
+ * taxa_oferta_venda (Float64): melhor oferta de venda.
145
+ * taxa_ultima (Float64): última taxa negociada.
146
+ * taxa_forward (Float64): taxa a termo (apenas DI1/DAP).
147
+ """
148
+ codigos = normalizar_codigos_contrato(contract_code)
149
+ if not codigos:
150
+ return pl.DataFrame()
164
151
 
165
- return historical.historical(data, codigos, full_report)
152
+ return intraday.intraday(codigos)
166
153
 
167
154
 
168
155
  def available_dates(contract_code: str) -> pl.Series:
@@ -188,4 +175,4 @@ def available_dates(contract_code: str) -> pl.Series:
188
175
  return historical.listar_datas_disponiveis(contract_code)
189
176
 
190
177
 
191
- __all__ = ["available_dates", "futures"]
178
+ __all__ = ["available_dates", "futures", "futures_intraday"]
@@ -6,7 +6,6 @@ from pyield import bday
6
6
  from pyield._internal.data_cache import obter_dataset_cacheado
7
7
  from pyield.b3._contracts import normalizar_codigos_contrato
8
8
  from pyield.b3.futures.common import adicionar_vencimento, expr_dv01
9
- from pyield.b3.price_report import fetch_price_report
10
9
  from pyield.fwd import forwards
11
10
 
12
11
  # Lista de contratos que negociam por taxa (juros/cupom).
@@ -63,16 +62,12 @@ def _obter_pr_normalizado() -> pl.DataFrame:
63
62
  def historical(
64
63
  data: dt.date,
65
64
  codigo_contrato: str | list[str],
66
- full_report: bool | None = None,
67
65
  ) -> pl.DataFrame:
68
- """Busca histórico de futuros priorizando o dataset PR cacheado.
66
+ """Busca histórico de futuros no dataset PR cacheado.
69
67
 
70
68
  Args:
71
69
  data: Data de negociação.
72
70
  codigo_contrato: Código(s) do contrato futuro na B3.
73
- full_report: Se None (padrão), tenta SPR primeiro e PR como fallback.
74
- Se False, usa o simplified price report (SPR, ~2 KB).
75
- Se True, usa o price report completo (PR, ~2 MB).
76
71
 
77
72
  Returns:
78
73
  DataFrame Polars com dados históricos de futuros.
@@ -115,6 +110,9 @@ def historical(
115
110
  * taxa_forward (Float64): taxa a termo (apenas DI1/DAP).
116
111
 
117
112
  Notes:
113
+ Usa exclusivamente o dataset PR cacheado no GitHub. Contratos
114
+ disponíveis: DI1, DDI, FRC, FRO, DAP, DOL, WDO, IND, WIN.
115
+
118
116
  As colunas com prefixo ``preco_`` aparecem para contratos cotados por
119
117
  preço (ex.: DOL, IND). As com prefixo ``taxa_`` aparecem para contratos
120
118
  cotados por taxa (ex.: DI1, DAP, DDI, FRC, FRO). Nem todas as colunas
@@ -131,28 +129,11 @@ def historical(
131
129
  if not codigos:
132
130
  return pl.DataFrame()
133
131
 
134
- dataframes: list[pl.DataFrame] = []
135
- codigos_sem_cache: list[str] = []
136
-
137
- for codigo in codigos:
138
- df_cache = _obter_futuros_pr([data], codigo)
139
- if df_cache.is_empty():
140
- codigos_sem_cache.append(codigo)
141
- else:
142
- dataframes.append(df_cache)
143
-
144
- if codigos_sem_cache:
145
- for codigo in codigos_sem_cache:
146
- df_bruto = _buscar_price_report(data, codigo, full_report)
147
- if df_bruto.is_empty():
148
- continue
149
-
150
- df_bruto = adicionar_vencimento(df_bruto, codigo, "codigo_negociacao")
151
- df = _enriquecer_dados(df_bruto, codigo)
152
- df_saida = _selecionar_colunas_saida(df)
153
- if "data_vencimento" in df_saida.columns:
154
- df_saida = df_saida.sort("data_vencimento")
155
- dataframes.append(df_saida)
132
+ dataframes = [
133
+ df
134
+ for codigo in codigos
135
+ if not (df := _obter_futuros_pr([data], codigo)).is_empty()
136
+ ]
156
137
 
157
138
  if not dataframes:
158
139
  return pl.DataFrame()
@@ -202,31 +183,6 @@ def listar_datas_disponiveis(codigo_contrato: str) -> pl.Series:
202
183
  )
203
184
 
204
185
 
205
- def _buscar_price_report(
206
- data: dt.date, codigo: str, full_report: bool | None
207
- ) -> pl.DataFrame:
208
- """Busca o price report da B3, com fallback SPR→PR.
209
-
210
- Quando full_report é True, usa apenas o PR (completo).
211
- Nos demais casos (None ou False), tenta o SPR (leve) primeiro
212
- e faz fallback para o PR se o SPR estiver vazio.
213
- """
214
- if full_report is True:
215
- return _normalizar_colunas_pr(
216
- fetch_price_report(date=data, contract_code=codigo, full_report=True)
217
- )
218
-
219
- # SPR (leve) primeiro; PR (pesado) como fallback
220
- df = _normalizar_colunas_pr(
221
- fetch_price_report(date=data, contract_code=codigo, full_report=False)
222
- )
223
- if not df.is_empty():
224
- return df
225
- return _normalizar_colunas_pr(
226
- fetch_price_report(date=data, contract_code=codigo, full_report=True)
227
- )
228
-
229
-
230
186
  def _filtrar_e_renomear(
231
187
  df: pl.DataFrame, datas: list[dt.date], codigo_contrato: str
232
188
  ) -> pl.DataFrame:
@@ -102,30 +102,18 @@ def _baixar_zip_url(data: dt.date, relatorio_completo: bool) -> bytes:
102
102
 
103
103
 
104
104
  def _extrair_xml_de_zip(conteudo_zip: bytes) -> bytes:
105
- """Extrai o XML de um ZIP, suportando ZIP aninhado (ZIP→ZIP→XML) ou simples (ZIP→XML)."""
105
+ """Extrai o XML do ZIP aninhado da B3 (ZIP externo → ZIP interno → XML)."""
106
106
  with zipfile.ZipFile(io.BytesIO(conteudo_zip), "r") as zip_externo:
107
- nomes_externos = zip_externo.namelist()
108
- if not nomes_externos:
107
+ nomes = zip_externo.namelist()
108
+ if not nomes:
109
109
  raise ValueError("ZIP externo está vazio")
110
110
 
111
- # Caso 1: ZIP contém XML diretamente
112
- nomes_xml = sorted(n for n in nomes_externos if n.endswith(".xml"))
113
- if nomes_xml:
114
- return zip_externo.read(nomes_xml[-1])
115
-
116
- # Caso 2: ZIP aninhado — busca o primeiro ZIP interno válido
117
- for nome in nomes_externos:
118
- conteudo_interno = zip_externo.read(nome)
119
- if conteudo_interno[:4] != b"PK\x03\x04":
120
- continue
121
- with zipfile.ZipFile(io.BytesIO(conteudo_interno), "r") as zip_interno:
122
- nomes_xml = sorted(
123
- n for n in zip_interno.namelist() if n.endswith(".xml")
124
- )
125
- if nomes_xml:
126
- return zip_interno.read(nomes_xml[-1])
127
-
128
- raise ValueError("Nenhum XML encontrado no ZIP")
111
+ conteudo_interno = zip_externo.read(nomes[0])
112
+ with zipfile.ZipFile(io.BytesIO(conteudo_interno), "r") as zip_interno:
113
+ nomes_xml = sorted(n for n in zip_interno.namelist() if n.endswith(".xml"))
114
+ if not nomes_xml:
115
+ raise ValueError("Nenhum XML encontrado no ZIP interno")
116
+ return zip_interno.read(nomes_xml[-1])
129
117
 
130
118
 
131
119
  def _ticker_valido_para_contrato(ticker: str, codigo_contrato: str) -> bool:
@@ -1 +0,0 @@
1
- __version__ = "0.46.7"
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