PYield 0.44.4__tar.gz → 0.44.5__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.44.4 → pyield-0.44.5}/PKG-INFO +25 -9
  2. {pyield-0.44.4 → pyield-0.44.5}/README.md +23 -7
  3. pyield-0.44.5/pyield/__about__.py +1 -0
  4. {pyield-0.44.4 → pyield-0.44.5}/pyield/_internal/converters.py +2 -2
  5. {pyield-0.44.4 → pyield-0.44.5}/pyield/anbima/tpf.py +26 -16
  6. {pyield-0.44.4 → pyield-0.44.5}/pyield/b3/di1.py +24 -90
  7. pyield-0.44.5/pyield/b3/futures/__init__.py +191 -0
  8. {pyield-0.44.4 → pyield-0.44.5}/pyield/b3/futures/historical.py +23 -13
  9. {pyield-0.44.4 → pyield-0.44.5}/pyield/bc/trades_monthly.py +3 -22
  10. {pyield-0.44.4 → pyield-0.44.5}/pyield/bday/core.py +19 -26
  11. {pyield-0.44.4 → pyield-0.44.5}/pyproject.toml +1 -4
  12. pyield-0.44.4/pyield/__about__.py +0 -1
  13. pyield-0.44.4/pyield/b3/futures/__init__.py +0 -92
  14. {pyield-0.44.4 → pyield-0.44.5}/.gitignore +0 -0
  15. {pyield-0.44.4 → pyield-0.44.5}/LICENSE +0 -0
  16. {pyield-0.44.4 → pyield-0.44.5}/pyield/__init__.py +0 -0
  17. {pyield-0.44.4 → pyield-0.44.5}/pyield/_internal/__init__.py +0 -0
  18. {pyield-0.44.4 → pyield-0.44.5}/pyield/_internal/data_cache.py +0 -0
  19. {pyield-0.44.4 → pyield-0.44.5}/pyield/_internal/retry.py +0 -0
  20. {pyield-0.44.4 → pyield-0.44.5}/pyield/_internal/types.py +0 -0
  21. {pyield-0.44.4 → pyield-0.44.5}/pyield/anbima/__init__.py +0 -0
  22. {pyield-0.44.4 → pyield-0.44.5}/pyield/anbima/difusao.py +0 -0
  23. {pyield-0.44.4 → pyield-0.44.5}/pyield/anbima/ettj_intraday.py +0 -0
  24. {pyield-0.44.4 → pyield-0.44.5}/pyield/anbima/ettj_last.py +0 -0
  25. {pyield-0.44.4 → pyield-0.44.5}/pyield/anbima/ima.py +0 -0
  26. {pyield-0.44.4 → pyield-0.44.5}/pyield/anbima/imaq.py +0 -0
  27. {pyield-0.44.4 → pyield-0.44.5}/pyield/b3/__init__.py +0 -0
  28. {pyield-0.44.4 → pyield-0.44.5}/pyield/b3/_contracts.py +0 -0
  29. {pyield-0.44.4 → pyield-0.44.5}/pyield/b3/_validar_pregao.py +0 -0
  30. {pyield-0.44.4 → pyield-0.44.5}/pyield/b3/di_over.py +0 -0
  31. {pyield-0.44.4 → pyield-0.44.5}/pyield/b3/futures/common.py +0 -0
  32. {pyield-0.44.4 → pyield-0.44.5}/pyield/b3/futures/intraday.py +0 -0
  33. {pyield-0.44.4 → pyield-0.44.5}/pyield/b3/intraday_derivatives.py +0 -0
  34. {pyield-0.44.4 → pyield-0.44.5}/pyield/b3/price_report.py +0 -0
  35. {pyield-0.44.4 → pyield-0.44.5}/pyield/bc/__init__.py +0 -0
  36. {pyield-0.44.4 → pyield-0.44.5}/pyield/bc/auction.py +0 -0
  37. {pyield-0.44.4 → pyield-0.44.5}/pyield/bc/copom.py +0 -0
  38. {pyield-0.44.4 → pyield-0.44.5}/pyield/bc/ptax_api.py +0 -0
  39. {pyield-0.44.4 → pyield-0.44.5}/pyield/bc/rates.py +0 -0
  40. {pyield-0.44.4 → pyield-0.44.5}/pyield/bc/repo.py +0 -0
  41. {pyield-0.44.4 → pyield-0.44.5}/pyield/bc/trades_intraday.py +0 -0
  42. {pyield-0.44.4 → pyield-0.44.5}/pyield/bc/vna.py +0 -0
  43. {pyield-0.44.4 → pyield-0.44.5}/pyield/bday/__init__.py +0 -0
  44. {pyield-0.44.4 → pyield-0.44.5}/pyield/bday/holidays/br_holidays_new.txt +0 -0
  45. {pyield-0.44.4 → pyield-0.44.5}/pyield/bday/holidays/br_holidays_old.txt +0 -0
  46. {pyield-0.44.4 → pyield-0.44.5}/pyield/bday/holidays/brholidays.py +0 -0
  47. {pyield-0.44.4 → pyield-0.44.5}/pyield/clock.py +0 -0
  48. {pyield-0.44.4 → pyield-0.44.5}/pyield/fwd.py +0 -0
  49. {pyield-0.44.4 → pyield-0.44.5}/pyield/interpolator.py +0 -0
  50. {pyield-0.44.4 → pyield-0.44.5}/pyield/ipca/__init__.py +0 -0
  51. {pyield-0.44.4 → pyield-0.44.5}/pyield/ipca/historical.py +0 -0
  52. {pyield-0.44.4 → pyield-0.44.5}/pyield/ipca/projected.py +0 -0
  53. {pyield-0.44.4 → pyield-0.44.5}/pyield/py.typed +0 -0
  54. {pyield-0.44.4 → pyield-0.44.5}/pyield/rmd.py +0 -0
  55. {pyield-0.44.4 → pyield-0.44.5}/pyield/selic/__init__.py +0 -0
  56. {pyield-0.44.4 → pyield-0.44.5}/pyield/selic/cpm.py +0 -0
  57. {pyield-0.44.4 → pyield-0.44.5}/pyield/selic/probabilities.py +0 -0
  58. {pyield-0.44.4 → pyield-0.44.5}/pyield/tn/__init__.py +0 -0
  59. {pyield-0.44.4 → pyield-0.44.5}/pyield/tn/auctions.py +0 -0
  60. {pyield-0.44.4 → pyield-0.44.5}/pyield/tn/benchmark.py +0 -0
  61. {pyield-0.44.4 → pyield-0.44.5}/pyield/tn/lft.py +0 -0
  62. {pyield-0.44.4 → pyield-0.44.5}/pyield/tn/ltn.py +0 -0
  63. {pyield-0.44.4 → pyield-0.44.5}/pyield/tn/ntnb.py +0 -0
  64. {pyield-0.44.4 → pyield-0.44.5}/pyield/tn/ntnb1.py +0 -0
  65. {pyield-0.44.4 → pyield-0.44.5}/pyield/tn/ntnbprinc.py +0 -0
  66. {pyield-0.44.4 → pyield-0.44.5}/pyield/tn/ntnc.py +0 -0
  67. {pyield-0.44.4 → pyield-0.44.5}/pyield/tn/ntnf.py +0 -0
  68. {pyield-0.44.4 → pyield-0.44.5}/pyield/tn/pre.py +0 -0
  69. {pyield-0.44.4 → pyield-0.44.5}/pyield/tn/tools.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyield
3
- Version: 0.44.4
3
+ Version: 0.44.5
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
@@ -39,7 +39,7 @@ Requires-Python: >=3.12
39
39
  Requires-Dist: cachetools>=5.0.0
40
40
  Requires-Dist: fastexcel>=0.19.0
41
41
  Requires-Dist: lxml>=6.0.0
42
- Requires-Dist: polars>=1.33.0
42
+ Requires-Dist: polars>=1.39.0
43
43
  Requires-Dist: python-dateutil>=2.8.0
44
44
  Requires-Dist: requests>=2.31.0
45
45
  Requires-Dist: tenacity>=9.0.0
@@ -173,13 +173,18 @@ forwards(bdays, rates) # -> Series: [0.05, 0.070095, 0.090284]
173
173
  | Módulo | Finalidade |
174
174
  |--------|---------|
175
175
  | `bday` | Calendário de dias úteis com feriados brasileiros |
176
- | `futures` | Dados de futuros da B3 (DI1, DDI, FRC, DAP, DOL, WDO, IND, WIN) |
176
+ | `futures` | Dados de futuros da B3 (DI1, DDI, DAP, DOL, WDO, IND, WIN e outros) |
177
+ | `di1` | Curva DI1 interpolada e datas de negociação disponíveis |
177
178
  | `Interpolator` | Interpolação de taxas (flat_forward, linear) |
178
179
  | `forward` / `forwards` | Cálculo de taxas a termo |
179
- | `ltn`, `ntnb`, `ntnf`, `lft`, `ntnc` | Precificação e análise de títulos públicos |
180
+ | `ltn`, `ntnb`, `ntnf`, `lft`, `ntnc` | Precificação e análise dos títulos públicos principais |
181
+ | `ntnb1`, `ntnbprinc`, `pre` | Títulos e curvas adicionais (NTN-B1, NTN-B Principal, curva PRE) |
182
+ | `tn.auction` / `tn.benchmarks` | Leilões e benchmarks de títulos públicos |
180
183
  | `anbima` | Dados da ANBIMA (preços de TPF, curvas de juros, índices IMA) |
181
- | `bc` | Indicadores do BCB (SELIC, PTAX, repos, VNA) |
184
+ | `bc` | Indicadores do BCB (SELIC, PTAX, repos, VNA, leilões, negociações) |
185
+ | `b3` | Dados da B3 (DI over, price reports, derivativos intradiários) |
182
186
  | `ipca` | Dados de inflação (histórico e projeções) |
187
+ | `selic` | Opções digitais de COPOM e probabilidades implícitas |
183
188
  | `rmd` | Relatório Mensal da Dívida do Tesouro Nacional |
184
189
  | `today` / `now` | Data/hora atual no Brasil (America/Sao_Paulo) |
185
190
 
@@ -209,9 +214,16 @@ from pyield import futures
209
214
  # DI1 (Futuro de Depósito Interfinanceiro)
210
215
  futures("31-05-2024", "DI1")
211
216
 
212
- # Outros contratos: DDI, FRC, DAP, DOL, WDO, IND, WIN
217
+ # Outros contratos disponíveis:
218
+ # - Juros: DI1, DDI, OC1, DAP, IAP
219
+ # - Moedas: DOL, WDO, EUR, GBR, JAP, CNY
220
+ # - Índices: IND, WIN, ISP, WSP
221
+ # - Commodities: BGI, CCM, ICF, CNL, SJC, SOY, ETH, GLD
213
222
  futures("31-05-2024", "DAP")
214
223
 
224
+ # Múltiplos contratos de uma vez
225
+ futures("31-05-2024", ["DI1", "DAP"])
226
+
215
227
  # Dados intradiários (quando o mercado estiver aberto)
216
228
  futures("16-01-2025", "DI1") # Retorna dados ao vivo durante o horário de negociação
217
229
  ```
@@ -221,7 +233,6 @@ futures("16-01-2025", "DI1") # Retorna dados ao vivo durante o horário de nego
221
233
  PYield aceita entradas de data flexíveis (`DateLike`):
222
234
  - Strings: `"31-05-2024"`, `"31/05/2024"`, `"2024-05-31"`
223
235
  - `datetime.date`, `datetime.datetime`
224
- - `pandas.Timestamp`, `numpy.datetime64`
225
236
 
226
237
  Funções escalares retornam `datetime.date`. Funções vetorizadas retornam `polars.Series`.
227
238
 
@@ -241,9 +252,14 @@ bday.count(["01-01-2024", None], "01-02-2024") # -> Series: [22, null]
241
252
 
242
253
  ## Migração para Polars (v0.40.0+)
243
254
 
244
- Todas as funções retornam **DataFrames/Series do Polars**. Para converter para Pandas:
255
+ A partir da versão 0.40.0, todas as saídas tabulares passaram de Pandas para **Polars**. Entradas vetoriais que antes aceitavam `pd.Series` e `np.ndarray` agora aceitam apenas listas, tuplas ou `pl.Series`. Para converter:
245
256
 
246
257
  ```python
258
+ # Entrada: converter coleções Pandas/NumPy antes de passar para o PYield
259
+ pl_series = pl.from_pandas(pd_series) # pd.Series → pl.Series (requer pandas)
260
+ pl_series = pl.Series(np_array) # np.ndarray → pl.Series (requer numpy)
261
+
262
+ # Saída: converter DataFrame Polars para Pandas
247
263
  df_pandas = df.to_pandas(use_pyarrow_extension_array=True)
248
264
  ```
249
265
 
@@ -254,5 +270,5 @@ Documentação completa: [crdcj.github.io/PYield](https://crdcj.github.io/PYield
254
270
  ## Testes
255
271
 
256
272
  ```sh
257
- pytest pyield --doctest-modules
273
+ pytest
258
274
  ```
@@ -126,13 +126,18 @@ forwards(bdays, rates) # -> Series: [0.05, 0.070095, 0.090284]
126
126
  | Módulo | Finalidade |
127
127
  |--------|---------|
128
128
  | `bday` | Calendário de dias úteis com feriados brasileiros |
129
- | `futures` | Dados de futuros da B3 (DI1, DDI, FRC, DAP, DOL, WDO, IND, WIN) |
129
+ | `futures` | Dados de futuros da B3 (DI1, DDI, DAP, DOL, WDO, IND, WIN e outros) |
130
+ | `di1` | Curva DI1 interpolada e datas de negociação disponíveis |
130
131
  | `Interpolator` | Interpolação de taxas (flat_forward, linear) |
131
132
  | `forward` / `forwards` | Cálculo de taxas a termo |
132
- | `ltn`, `ntnb`, `ntnf`, `lft`, `ntnc` | Precificação e análise de títulos públicos |
133
+ | `ltn`, `ntnb`, `ntnf`, `lft`, `ntnc` | Precificação e análise dos títulos públicos principais |
134
+ | `ntnb1`, `ntnbprinc`, `pre` | Títulos e curvas adicionais (NTN-B1, NTN-B Principal, curva PRE) |
135
+ | `tn.auction` / `tn.benchmarks` | Leilões e benchmarks de títulos públicos |
133
136
  | `anbima` | Dados da ANBIMA (preços de TPF, curvas de juros, índices IMA) |
134
- | `bc` | Indicadores do BCB (SELIC, PTAX, repos, VNA) |
137
+ | `bc` | Indicadores do BCB (SELIC, PTAX, repos, VNA, leilões, negociações) |
138
+ | `b3` | Dados da B3 (DI over, price reports, derivativos intradiários) |
135
139
  | `ipca` | Dados de inflação (histórico e projeções) |
140
+ | `selic` | Opções digitais de COPOM e probabilidades implícitas |
136
141
  | `rmd` | Relatório Mensal da Dívida do Tesouro Nacional |
137
142
  | `today` / `now` | Data/hora atual no Brasil (America/Sao_Paulo) |
138
143
 
@@ -162,9 +167,16 @@ from pyield import futures
162
167
  # DI1 (Futuro de Depósito Interfinanceiro)
163
168
  futures("31-05-2024", "DI1")
164
169
 
165
- # Outros contratos: DDI, FRC, DAP, DOL, WDO, IND, WIN
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
166
175
  futures("31-05-2024", "DAP")
167
176
 
177
+ # Múltiplos contratos de uma vez
178
+ futures("31-05-2024", ["DI1", "DAP"])
179
+
168
180
  # Dados intradiários (quando o mercado estiver aberto)
169
181
  futures("16-01-2025", "DI1") # Retorna dados ao vivo durante o horário de negociação
170
182
  ```
@@ -174,7 +186,6 @@ futures("16-01-2025", "DI1") # Retorna dados ao vivo durante o horário de nego
174
186
  PYield aceita entradas de data flexíveis (`DateLike`):
175
187
  - Strings: `"31-05-2024"`, `"31/05/2024"`, `"2024-05-31"`
176
188
  - `datetime.date`, `datetime.datetime`
177
- - `pandas.Timestamp`, `numpy.datetime64`
178
189
 
179
190
  Funções escalares retornam `datetime.date`. Funções vetorizadas retornam `polars.Series`.
180
191
 
@@ -194,9 +205,14 @@ bday.count(["01-01-2024", None], "01-02-2024") # -> Series: [22, null]
194
205
 
195
206
  ## Migração para Polars (v0.40.0+)
196
207
 
197
- Todas as funções retornam **DataFrames/Series do Polars**. Para converter para Pandas:
208
+ A partir da versão 0.40.0, todas as saídas tabulares passaram de Pandas para **Polars**. Entradas vetoriais que antes aceitavam `pd.Series` e `np.ndarray` agora aceitam apenas listas, tuplas ou `pl.Series`. Para converter:
198
209
 
199
210
  ```python
211
+ # Entrada: converter coleções Pandas/NumPy antes de passar para o PYield
212
+ pl_series = pl.from_pandas(pd_series) # pd.Series → pl.Series (requer pandas)
213
+ pl_series = pl.Series(np_array) # np.ndarray → pl.Series (requer numpy)
214
+
215
+ # Saída: converter DataFrame Polars para Pandas
200
216
  df_pandas = df.to_pandas(use_pyarrow_extension_array=True)
201
217
  ```
202
218
 
@@ -207,5 +223,5 @@ Documentação completa: [crdcj.github.io/PYield](https://crdcj.github.io/PYield
207
223
  ## Testes
208
224
 
209
225
  ```sh
210
- pytest pyield --doctest-modules
226
+ pytest
211
227
  ```
@@ -0,0 +1 @@
1
+ __version__ = "0.44.5"
@@ -69,7 +69,7 @@ def converter_datas(
69
69
 
70
70
  Normaliza datas em diversos formatos para um tipo consistente:
71
71
  - Entradas escalares (str, date, datetime) retornam ``datetime.date``
72
- - Coleções (list, tuple, ndarray, Series) retornam ``pl.Series`` com dtype ``Date``
72
+ - Coleções (list, tuple, Series) retornam ``pl.Series`` com dtype ``Date``
73
73
  - Entradas nulas retornam ``None``
74
74
 
75
75
  O parse de strings é feito por elemento com fallback entre formatos
@@ -79,7 +79,7 @@ def converter_datas(
79
79
  dates: Data(s) a converter. Aceita:
80
80
  - String nos formatos ``dd-mm-YYYY``, ``dd/mm/YYYY`` ou ``YYYY-mm-dd``
81
81
  - ``datetime.date`` ou ``datetime.datetime``
82
- - Lista, tupla, ndarray ou Series de datas
82
+ - Lista, tupla ou Series de datas
83
83
  - ``None``
84
84
 
85
85
  Returns:
@@ -58,14 +58,6 @@ MAPA_NOMES_COLUNAS = {csv: novo for csv, novo, _ in TPF_COLUNAS}
58
58
  logger = logging.getLogger(__name__)
59
59
 
60
60
 
61
- def _validar_data_nao_futura(data: dt.date):
62
- """Levanta ValueError se a data for no futuro."""
63
- if data > clock.today():
64
- data_log = data.strftime("%d/%m/%Y")
65
- msg = f"Não é possível processar dados para data futura ({data_log})."
66
- raise ValueError(msg)
67
-
68
-
69
61
  def _mapear_tipo_titulo(tipo_titulo: str) -> list[str]:
70
62
  tipo_titulo = tipo_titulo.upper()
71
63
  mapa_titulos = {
@@ -189,6 +181,17 @@ def _adicionar_duracao(df_input: pl.DataFrame) -> pl.DataFrame:
189
181
  return df
190
182
 
191
183
 
184
+ def _adicionar_prazo_medio(df: pl.DataFrame) -> pl.DataFrame:
185
+ # Na metodologia do Tesouro Nacional, a maturidade média é a mesma que a duração
186
+ df = df.with_columns(
187
+ pl.when(pl.col("BondType") == "LFT")
188
+ .then(pl.col("BDToMat") / 252)
189
+ .otherwise("Duration")
190
+ .alias("AvgMaturity")
191
+ )
192
+ return df
193
+
194
+
192
195
  def _adicionar_dv01(df_input: pl.DataFrame, data_ref: dt.date) -> pl.DataFrame:
193
196
  """Adiciona as colunas de DV01 ao DataFrame."""
194
197
  expr_duracao_mod = pl.col("Duration") / (1 + pl.col("IndicativeRate"))
@@ -224,6 +227,7 @@ def _selecionar_e_ordenar_colunas(df: pl.DataFrame) -> pl.DataFrame:
224
227
  "MaturityDate",
225
228
  "BDToMat",
226
229
  "Duration",
230
+ "AvgMaturity",
227
231
  "DV01",
228
232
  "DV01USD",
229
233
  "Price",
@@ -231,11 +235,11 @@ def _selecionar_e_ordenar_colunas(df: pl.DataFrame) -> pl.DataFrame:
231
235
  "AskRate",
232
236
  "IndicativeRate",
233
237
  "DIRate",
234
- "StdDev",
235
- "LowerBoundRateD0",
236
- "UpperBoundRateD0",
237
- "LowerBoundRateD1",
238
- "UpperBoundRateD1",
238
+ # "StdDev",
239
+ # "LowerBoundRateD0",
240
+ # "UpperBoundRateD0",
241
+ # "LowerBoundRateD1",
242
+ # "UpperBoundRateD1",
239
243
  "Criteria",
240
244
  ]
241
245
  ordem_colunas = [col for col in ordem_colunas if col in df.columns]
@@ -288,6 +292,7 @@ def _buscar_dados_tpf(date: dt.date) -> pl.DataFrame:
288
292
  df = _ler_csv(csv_texto)
289
293
  df = _processar_df_bruto(df)
290
294
  df = _adicionar_duracao(df)
295
+ df = _adicionar_prazo_medio(df)
291
296
  df = _adicionar_dv01(df, date)
292
297
  df = _adicionar_taxa_di(df, date)
293
298
  df = _selecionar_e_ordenar_colunas(df)
@@ -355,6 +360,8 @@ def tpf_data(
355
360
  - MaturityDate: Data de vencimento do título.
356
361
  - BDToMat: Número de dias úteis entre a data de referência e o vencimento.
357
362
  - Duration: Macaulay Duration do título em anos.
363
+ - AvgMaturity: Prazo médio do título (em anos), conforme metodologia
364
+ do Tesouro Nacional.
358
365
  - DV01: Variação financeira no preço do título (em BRL) para uma
359
366
  mudança de 1 basis point (0,01%) na taxa de juros.
360
367
  - DV01USD: O mesmo que DV01, mas convertido para USD pela PTAX do dia.
@@ -385,11 +392,14 @@ def tpf_data(
385
392
  if any_is_empty(date):
386
393
  return pl.DataFrame()
387
394
  date = converter_datas(date)
388
- _validar_data_nao_futura(date)
395
+
396
+ if not bday.is_business_day(date):
397
+ return pl.DataFrame()
398
+
399
+ if date > clock.today():
400
+ return pl.DataFrame()
389
401
 
390
402
  if fetch_from_source:
391
- if not bday.is_business_day(date):
392
- return pl.DataFrame()
393
403
  # Tenta buscar os dados diretamente da fonte (ANBIMA)
394
404
  df = _buscar_dados_tpf(date)
395
405
  else:
@@ -1,85 +1,10 @@
1
- import datetime as dt
2
- import logging
3
-
4
1
  import polars as pl
5
2
 
6
3
  import pyield._internal.converters as cv
7
4
  from pyield import b3, bday, interpolator
8
5
  from pyield._internal.data_cache import obter_dataset_cacheado
9
6
  from pyield._internal.types import ArrayLike, DateLike, any_is_collection, any_is_empty
10
- from pyield.b3.futures import historical as futuros_historico
11
-
12
- registro = logging.getLogger(__name__)
13
-
14
-
15
- def _carregar_cache_pr_di1(datas: list[dt.date]) -> pl.DataFrame:
16
- """Carrega e processa DI1 a partir do dataset PR para as datas informadas."""
17
- return futuros_historico.carregar_pr(datas=datas, codigo_contrato="DI1")
18
-
19
-
20
- def _carregar_com_intraday(datas: list[dt.date]) -> pl.DataFrame:
21
- """Busca dados de DI, incluindo dados intraday para datas ausentes no cache.
22
-
23
- Args:
24
- datas: Lista de datas a buscar.
25
-
26
- Returns:
27
- DataFrame com dados de DI para as datas solicitadas.
28
- """
29
- # 1. Busca inicial no cache PR com as datas solicitadas pelo usuário.
30
- df_cache = _carregar_cache_pr_di1(datas)
31
- if df_cache.is_empty():
32
- datas_cache = set()
33
- else:
34
- datas_cache = set(df_cache["TradeDate"].drop_nulls())
35
-
36
- # 2. Identifica datas solicitadas que não estão no cache
37
- datas_solicitadas = set(datas)
38
- datas_faltantes = datas_solicitadas - datas_cache
39
-
40
- # 3. Para cada data faltante, tenta buscar dados via API
41
- dfs_concat = [df_cache] if not df_cache.is_empty() else []
42
-
43
- for data_ref in datas_faltantes:
44
- try:
45
- df_faltante = b3.futures(contract_code="DI1", date=data_ref)
46
-
47
- # Só adiciona se tiver SettlementPrice
48
- if "SettlementPrice" in df_faltante.columns:
49
- dfs_concat.append(df_faltante)
50
- else:
51
- registro.warning(
52
- "Dados para %s não contêm 'SettlementPrice'. Pulando esta data.",
53
- data_ref,
54
- )
55
- except Exception as e:
56
- registro.error("Falha ao buscar dados para %s: %s", data_ref, e)
57
-
58
- # 4. Retorna concatenação de todos os DataFrames disponíveis
59
- if len(dfs_concat) == 0:
60
- return pl.DataFrame()
61
- elif len(dfs_concat) == 1:
62
- return dfs_concat[0]
63
- else:
64
- return pl.concat(dfs_concat, how="diagonal")
65
-
66
-
67
- def _obter_dados(datas: DateLike | ArrayLike) -> pl.DataFrame:
68
- datas_unicas = cv.converter_datas(datas)
69
- if datas_unicas is None:
70
- return pl.DataFrame()
71
-
72
- if isinstance(datas_unicas, pl.Series):
73
- datas_unicas = datas_unicas.drop_nulls().unique().sort().to_list()
74
- else:
75
- datas_unicas = [datas_unicas]
76
-
77
- df = _carregar_com_intraday(datas_unicas)
78
-
79
- if df.is_empty():
80
- return pl.DataFrame()
81
-
82
- return df.sort("TradeDate", "ExpirationDate")
7
+ from pyield.b3.futures import available_dates as _listar_datas
83
8
 
84
9
 
85
10
  def data(
@@ -122,7 +47,15 @@ def data(
122
47
  if any_is_empty(dates):
123
48
  return pl.DataFrame()
124
49
 
125
- df = _obter_dados(datas=dates)
50
+ datas_convertidas = cv.converter_datas(dates)
51
+ if datas_convertidas is None:
52
+ return pl.DataFrame()
53
+ if isinstance(datas_convertidas, pl.Series):
54
+ datas_lista = datas_convertidas.drop_nulls().unique().sort().to_list()
55
+ else:
56
+ datas_lista = [datas_convertidas]
57
+
58
+ df = b3.futures(date=datas_lista, contract_code="DI1", full_report=False)
126
59
  if df.is_empty():
127
60
  return df
128
61
 
@@ -257,9 +190,9 @@ def interpolate_rates(
257
190
  if df_entrada.is_empty():
258
191
  return pl.Series(dtype=pl.Float64)
259
192
 
260
- # Carrega dataset de taxas DI filtrado pelas datas de referência fornecidas
261
- # Usa datas já convertidas do DataFrame de entrada para evitar conversão dupla
262
- df_ref = _obter_dados(datas=dates)
193
+ # Carrega dataset de taxas DI usando datas já convertidas do df_entrada
194
+ datas_unicas = df_entrada["TradeDate"].drop_nulls().unique().sort().to_list()
195
+ df_ref = b3.futures(date=datas_unicas, contract_code="DI1", full_report=False)
263
196
  # Retorna Series vazia se nenhuma taxa for encontrada
264
197
  if df_ref.is_empty():
265
198
  return pl.Series(dtype=pl.Float64)
@@ -380,25 +313,26 @@ def interpolate_rate(
380
313
  if valor is None:
381
314
  return float("nan")
382
315
 
383
- return float(valor)
316
+ return valor
384
317
 
385
318
 
386
- def available_trade_dates() -> pl.Series:
387
- """Retorna todas as datas de negociação disponíveis para DI1 em futures.
319
+ def available_dates() -> pl.Series:
320
+ """Retorna as datas de negociação disponíveis para DI1.
388
321
 
389
- Obtém valores distintos de 'TradeDate' para DI1, com base no mesmo
390
- dataset PR utilizado por `futures`.
322
+ Obtém valores distintos de 'TradeDate' para DI1, com base no dataset histórico
323
+ PR da B3 (mesmo utilizado por `futures`). Inclui apenas datas com preço e taxa
324
+ de ajuste já definidos; o pregão do dia corrente não está incluído.
391
325
 
392
326
  Returns:
393
- Series ordenada de datas de negociação únicas (dt.date) para as quais
394
- dados de DI estão disponíveis.
327
+ Series ordenada de datas de negociação (dt.date) para as quais dados de
328
+ ajuste de DI estão disponíveis.
395
329
 
396
330
  Examples:
397
331
  >>> from pyield import di1
398
332
  >>> # Série disponível no dataset PR começa em 2018-01-02
399
- >>> di1.available_trade_dates().head(5)
333
+ >>> di1.available_dates().head(5)
400
334
  shape: (5,)
401
- Series: 'available_dates' [date]
335
+ Series: 'TradeDate' [date]
402
336
  [
403
337
  2018-01-02
404
338
  2018-01-03
@@ -407,4 +341,4 @@ def available_trade_dates() -> pl.Series:
407
341
  2018-01-08
408
342
  ]
409
343
  """
410
- return futuros_historico.listar_datas_disponiveis("DI1").alias("available_dates")
344
+ return _listar_datas("DI1")
@@ -0,0 +1,191 @@
1
+ import datetime as dt
2
+ import logging
3
+
4
+ import polars as pl
5
+
6
+ import pyield._internal.converters as cv
7
+ from pyield import clock
8
+ from pyield._internal.types import ArrayLike, DateLike, any_is_empty, is_collection
9
+ from pyield.b3._contracts import normalizar_codigos_contrato
10
+ from pyield.b3._validar_pregao import data_negociacao_valida
11
+ from pyield.b3.futures import historical, intraday
12
+
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
+
19
+ def futures(
20
+ date: DateLike | ArrayLike,
21
+ contract_code: str | list[str],
22
+ full_report: bool | None = None,
23
+ ) -> pl.DataFrame:
24
+ """Busca dados de um contrato futuro da B3 para a data de referência.
25
+
26
+ Args:
27
+ date: Data de referência para consulta ou coleção de datas.
28
+ Quando uma coleção é fornecida, os dados são buscados para cada
29
+ data individualmente e concatenados. Datas inválidas (feriados,
30
+ 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.
44
+
45
+ Returns:
46
+ DataFrame Polars com os dados do contrato informado.
47
+
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
+ Examples:
55
+ >>> df = futures("31-05-2024", "DI1")
56
+ >>> df = futures("31-05-2024", "DAP")
57
+
58
+ Lista de datas:
59
+
60
+ >>> df = futures(["29-05-2024", "31-05-2024"], "DI1")
61
+ >>> df["TradeDate"].unique().sort().to_list()
62
+ [datetime.date(2024, 5, 29), datetime.date(2024, 5, 31)]
63
+
64
+ Véspera de Natal e Ano Novo não têm pregão:
65
+
66
+ >>> futures("24-12-2024", "DI1").is_empty()
67
+ True
68
+ >>> futures("31-12-2024", "DI1").is_empty()
69
+ True
70
+
71
+ Data futura e fim de semana retornam DataFrame vazio:
72
+
73
+ >>> import datetime as dt
74
+ >>> amanha = dt.date.today() + dt.timedelta(days=1)
75
+ >>> futures(amanha, "DI1").is_empty()
76
+ True
77
+ >>> futures("04-01-2025", "DI1").is_empty() # sábado
78
+ True
79
+
80
+ """
81
+ if any_is_empty(date, contract_code):
82
+ return pl.DataFrame()
83
+
84
+ codigos_contrato = normalizar_codigos_contrato(contract_code)
85
+ if not codigos_contrato:
86
+ return pl.DataFrame()
87
+
88
+ if is_collection(date):
89
+ # date é ArrayLike neste branch
90
+ datas: ArrayLike = date # type: ignore[assignment]
91
+ return _buscar_varias_datas(datas, codigos_contrato, full_report)
92
+
93
+ # date é escalar (DateLike) neste branch
94
+ data_negociacao: dt.date = cv.converter_datas(date) # type: ignore[assignment]
95
+ if not data_negociacao_valida(data_negociacao):
96
+ return pl.DataFrame()
97
+
98
+ return _buscar_por_fonte(data_negociacao, codigos_contrato, full_report)
99
+
100
+
101
+ def _buscar_varias_datas(
102
+ datas: ArrayLike,
103
+ codigos: list[str],
104
+ full_report: bool | None,
105
+ ) -> 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
+ """
111
+ serie_datas = cv.converter_datas(datas)
112
+ datas_validas = [
113
+ d for d in serie_datas if d is not None and data_negociacao_valida(d)
114
+ ]
115
+ if not datas_validas:
116
+ return pl.DataFrame()
117
+
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["TradeDate"].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)
136
+
137
+ if not resultados:
138
+ return pl.DataFrame()
139
+ return pl.concat(resultados, how="diagonal_relaxed")
140
+
141
+
142
+ def _buscar_por_fonte(
143
+ data: dt.date,
144
+ codigos: list[str],
145
+ full_report: bool | None,
146
+ ) -> 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)
150
+
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)
158
+
159
+ if horario >= intraday.HORA_INICIO_INTRADAY:
160
+ # Intraday primeiro; historical como fallback
161
+ df = intraday.intraday(codigos)
162
+ if not df.is_empty():
163
+ return df
164
+
165
+ return historical.historical(data, codigos, full_report)
166
+
167
+
168
+ def available_dates(contract_code: str) -> pl.Series:
169
+ """Retorna as datas de negociação disponíveis no dataset histórico PR.
170
+
171
+ Args:
172
+ contract_code: Código do contrato futuro na B3 (ex.: DI1, DOL).
173
+
174
+ Returns:
175
+ Series ordenada de datas (Date) para as quais há dados de ajuste.
176
+
177
+ Examples:
178
+ >>> from pyield.b3.futures import available_dates
179
+ >>> available_dates("DI1").head(3)
180
+ shape: (3,)
181
+ Series: 'TradeDate' [date]
182
+ [
183
+ 2018-01-02
184
+ 2018-01-03
185
+ 2018-01-04
186
+ ]
187
+ """
188
+ return historical.listar_datas_disponiveis(contract_code)
189
+
190
+
191
+ __all__ = ["available_dates", "futures"]
@@ -17,15 +17,16 @@ CONTRATOS_TAXA = {"DI1", "DAP", "DDI", "FRC", "FRO"}
17
17
  def historical(
18
18
  data: dt.date,
19
19
  codigo_contrato: str | list[str],
20
- full_report: bool = False,
20
+ full_report: bool | None = None,
21
21
  ) -> pl.DataFrame:
22
22
  """Busca histórico de futuros priorizando o dataset PR cacheado.
23
23
 
24
24
  Args:
25
25
  data: Data de negociação.
26
26
  codigo_contrato: Código(s) do contrato futuro na B3.
27
- full_report: Se False (padrão), usa o simplified price report
28
- (SPR, ~2 KB). Se True, usa o price report completo (PR, ~2 MB).
27
+ full_report: Se None (padrão), tenta SPR primeiro e PR como fallback.
28
+ Se False, usa o simplified price report (SPR, ~2 KB).
29
+ Se True, usa o price report completo (PR, ~2 MB).
29
30
 
30
31
  Returns:
31
32
  DataFrame Polars com dados históricos de futuros.
@@ -78,7 +79,7 @@ def historical(
78
79
  codigos_sem_cache: list[str] = []
79
80
 
80
81
  for codigo in codigos:
81
- df_cache = _carregar_pr_por_data(data, codigo)
82
+ df_cache = obter_futuros_pr([data], codigo)
82
83
  if df_cache.is_empty():
83
84
  codigos_sem_cache.append(codigo)
84
85
  else:
@@ -86,9 +87,7 @@ def historical(
86
87
 
87
88
  if codigos_sem_cache:
88
89
  for codigo in codigos_sem_cache:
89
- df_bruto = fetch_price_report(
90
- date=data, contract_code=codigo, full_report=full_report
91
- )
90
+ df_bruto = _buscar_price_report(data, codigo, full_report)
92
91
  if df_bruto.is_empty():
93
92
  continue
94
93
 
@@ -117,12 +116,7 @@ def historical(
117
116
  return df_resultado.sort(*colunas_ordenacao)
118
117
 
119
118
 
120
- def _carregar_pr_por_data(data: dt.date, codigo_contrato: str) -> pl.DataFrame:
121
- """Busca o histórico de futuros no dataset PR cacheado para a data informada."""
122
- return carregar_pr([data], codigo_contrato)
123
-
124
-
125
- def carregar_pr(datas: list[dt.date], codigo_contrato: str) -> pl.DataFrame:
119
+ def obter_futuros_pr(datas: list[dt.date], codigo_contrato: str) -> pl.DataFrame:
126
120
  """Carrega histórico de futuros do dataset PR para uma lista de datas."""
127
121
  if not datas:
128
122
  return pl.DataFrame()
@@ -152,6 +146,22 @@ def listar_datas_disponiveis(codigo_contrato: str) -> pl.Series:
152
146
  )
153
147
 
154
148
 
149
+ def _buscar_price_report(
150
+ data: dt.date, codigo: str, full_report: bool | None
151
+ ) -> pl.DataFrame:
152
+ """Busca o price report da B3, com fallback SPR→PR quando full_report=None."""
153
+ if full_report is not None:
154
+ return fetch_price_report(
155
+ date=data, contract_code=codigo, full_report=full_report
156
+ )
157
+
158
+ # SPR (leve) primeiro; PR (pesado) como fallback
159
+ df = fetch_price_report(date=data, contract_code=codigo, full_report=False)
160
+ if not df.is_empty():
161
+ return df
162
+ return fetch_price_report(date=data, contract_code=codigo, full_report=True)
163
+
164
+
155
165
  def _filtrar_e_renomear(
156
166
  df: pl.DataFrame, datas: list[dt.date], codigo_contrato: str
157
167
  ) -> pl.DataFrame:
@@ -135,8 +135,8 @@ def _ordenar_selecionar_colunas(df: pl.DataFrame) -> pl.DataFrame:
135
135
 
136
136
 
137
137
  def tpf_monthly_trades(target_date: DateLike, extragroup: bool = False) -> pl.DataFrame:
138
- """Consulta negociações mensais no mercado secundário de Títulos Públicos Federais (TPF)
139
- registradas no sistema Selic do Banco Central do Brasil (BCB).
138
+ """Consulta negociações mensais no mercado secundário de TPF
139
+ registradas no sistema Selic do BCB.
140
140
 
141
141
  Baixa os dados mensais de negociação do site do BCB para o mês correspondente
142
142
  à data fornecida. Os dados são baixados como arquivo ZIP, extraídos e carregados
@@ -183,26 +183,7 @@ def tpf_monthly_trades(target_date: DateLike, extragroup: bool = False) -> pl.Da
183
183
 
184
184
  Examples:
185
185
  >>> from pyield import bc
186
- >>> # Busca todas as negociações de jan/2025
187
- >>> bc.tpf_monthly_trades("07-01-2025", extragroup=True)
188
- shape: (1_019, 19)
189
- ┌────────────────┬──────────┬───────────┬──────────────┬───┬─────────┬─────────┬─────────────────┬───────────────────┐
190
- │ SettlementDate ┆ BondType ┆ SelicCode ┆ ISIN ┆ … ┆ AvgRate ┆ MaxRate ┆ BrokerageTrades ┆ BrokerageQuantity │
191
- │ --- ┆ --- ┆ --- ┆ --- ┆ ┆ --- ┆ --- ┆ --- ┆ --- │
192
- │ date ┆ str ┆ i64 ┆ str ┆ ┆ f64 ┆ f64 ┆ i64 ┆ i64 │
193
- ╞════════════════╪══════════╪═══════════╪══════════════╪═══╪═════════╪═════════╪═════════════════╪═══════════════════╡
194
- │ 2025-01-02 ┆ LFT ┆ 210100 ┆ BRSTNCLF1RC4 ┆ … ┆ 0.0132 ┆ 0.0906 ┆ 2 ┆ 9581 │
195
- │ 2025-01-02 ┆ LFT ┆ 210100 ┆ BRSTNCLF1RD2 ┆ … ┆ 0.0561 ┆ 0.101 ┆ 11 ┆ 42823 │
196
- │ 2025-01-02 ┆ LFT ┆ 210100 ┆ BRSTNCLF1RE0 ┆ … ┆ 0.0191 ┆ 0.0405 ┆ 19 ┆ 33330 │
197
- │ 2025-01-02 ┆ LFT ┆ 210100 ┆ BRSTNCLF1RF7 ┆ … ┆ 0.0304 ┆ 0.05 ┆ 10 ┆ 14583 │
198
- │ 2025-01-02 ┆ LFT ┆ 210100 ┆ BRSTNCLF1RG5 ┆ … ┆ 0.0697 ┆ 0.0935 ┆ 12 ┆ 51776 │
199
- │ … ┆ … ┆ … ┆ … ┆ … ┆ … ┆ … ┆ … ┆ … │
200
- │ 2025-01-31 ┆ NTN-F ┆ 950199 ┆ BRSTNCNTF1P8 ┆ … ┆ null ┆ null ┆ 0 ┆ 0 │
201
- │ 2025-01-31 ┆ NTN-F ┆ 950199 ┆ BRSTNCNTF1Q6 ┆ … ┆ null ┆ null ┆ 0 ┆ 0 │
202
- │ 2025-01-31 ┆ NTN-F ┆ 950199 ┆ BRSTNCNTF204 ┆ … ┆ null ┆ null ┆ 12 ┆ 570000 │
203
- │ 2025-01-31 ┆ NTN-F ┆ 950199 ┆ BRSTNCNTF212 ┆ … ┆ null ┆ null ┆ 0 ┆ 0 │
204
- │ 2025-01-31 ┆ NTN-F ┆ 950199 ┆ BRSTNCNTF238 ┆ … ┆ null ┆ null ┆ 4 ┆ 115000 │
205
- └────────────────┴──────────┴───────────┴──────────────┴───┴─────────┴─────────┴─────────────────┴───────────────────┘
186
+ >>> df = bc.tpf_monthly_trades("07-01-2025", extragroup=True)
206
187
 
207
188
  """
208
189
  if any_is_empty(target_date):
@@ -17,6 +17,14 @@ FERIADOS_NOVOS = feriados_br.obter_feriados(opcao_feriado="new")
17
17
  DATA_TRANSICAO = BrHolidays.DATA_TRANSICAO
18
18
 
19
19
 
20
+ def _expressao_feriados(expr_data: pl.Expr) -> pl.Expr:
21
+ return (
22
+ pl.when(expr_data < DATA_TRANSICAO)
23
+ .then(pl.lit(FERIADOS_ANTIGOS))
24
+ .otherwise(pl.lit(FERIADOS_NOVOS))
25
+ )
26
+
27
+
20
28
  def count_expr(start: pl.Expr | str | dt.date, end: pl.Expr | str | dt.date) -> pl.Expr:
21
29
  """Cria uma expressão Polars para contar dias úteis (com suporte a LazyFrame).
22
30
 
@@ -69,20 +77,11 @@ def count_expr(start: pl.Expr | str | dt.date, end: pl.Expr | str | dt.date) ->
69
77
  else:
70
78
  end_date = cv.converter_datas_expr(end)
71
79
 
72
- return (
73
- pl.when(start_date < DATA_TRANSICAO)
74
- .then(
75
- pl.business_day_count(
76
- start=start_date, end=end_date, holidays=FERIADOS_ANTIGOS
77
- )
78
- )
79
- .otherwise(
80
- pl.business_day_count(
81
- start=start_date, end=end_date, holidays=FERIADOS_NOVOS
82
- )
83
- )
84
- .cast(pl.Int64)
85
- )
80
+ return pl.business_day_count(
81
+ start=start_date,
82
+ end=end_date,
83
+ holidays=_expressao_feriados(start_date),
84
+ ).cast(pl.Int64)
86
85
 
87
86
 
88
87
  @overload
@@ -265,12 +264,10 @@ def offset_expr(
265
264
 
266
265
  expr_date = cv.converter_datas_expr(expr)
267
266
 
268
- return (
269
- pl.when(expr_date < DATA_TRANSICAO)
270
- .then(expr_date.dt.add_business_days(n=n, roll=roll, holidays=FERIADOS_ANTIGOS))
271
- .otherwise(
272
- expr_date.dt.add_business_days(n=n, roll=roll, holidays=FERIADOS_NOVOS)
273
- )
267
+ return expr_date.dt.add_business_days(
268
+ n=n,
269
+ roll=roll,
270
+ holidays=_expressao_feriados(expr_date),
274
271
  )
275
272
 
276
273
 
@@ -573,11 +570,7 @@ def is_business_day_expr(expr: pl.Expr | str) -> pl.Expr:
573
570
  """
574
571
  expr_date = cv.converter_datas_expr(expr)
575
572
 
576
- return (
577
- pl.when(expr_date < DATA_TRANSICAO)
578
- .then(expr_date.dt.is_business_day(holidays=FERIADOS_ANTIGOS))
579
- .otherwise(expr_date.dt.is_business_day(holidays=FERIADOS_NOVOS))
580
- )
573
+ return expr_date.dt.is_business_day(holidays=_expressao_feriados(expr_date))
581
574
 
582
575
 
583
576
  @overload
@@ -617,7 +610,7 @@ def is_business_day(dates: None | DateLike | ArrayLike) -> None | bool | pl.Seri
617
610
  regime de feriados.
618
611
 
619
612
  Args:
620
- dates: Data única ou coleção (list/tuple/ndarray/Polars/Pandas Series).
613
+ dates: Data única ou coleção (list/tuple/Polars Series).
621
614
  Pode incluir nulos que propagam. Entrada escalar nula retorna ``None``.
622
615
 
623
616
  Returns:
@@ -15,7 +15,7 @@ classifiers = [
15
15
  ]
16
16
  dependencies = [
17
17
  "cachetools>=5.0.0",
18
- "polars>=1.33.0",
18
+ "polars>=1.39.0",
19
19
  "lxml>=6.0.0",
20
20
  "python-dateutil>=2.8.0",
21
21
  "requests>=2.31.0",
@@ -32,7 +32,6 @@ Source = "https://github.com/crdcj/PYield"
32
32
 
33
33
  [dependency-groups]
34
34
  dev = [
35
- "pandas[pyarrow]",
36
35
  "python-dotenv[cli]>=1.0.0",
37
36
  "ipykernel>=6.20.0",
38
37
  "pytest>=8.0.0",
@@ -41,8 +40,6 @@ dev = [
41
40
  "mkdocs>=1.6.0",
42
41
  "mkdocs-material>=9.0.0",
43
42
  "mkdocstrings[python]>=0.24.0",
44
- "mkdocs-autorefs>=1.0.0",
45
- "mkdocs-git-revision-date-localized-plugin>=1.2.0",
46
43
  "types-requests>=2.31.0",
47
44
  "lxml-stubs>=0.5.0",
48
45
  ]
@@ -1 +0,0 @@
1
- __version__ = "0.44.4"
@@ -1,92 +0,0 @@
1
- import logging
2
-
3
- import polars as pl
4
-
5
- import pyield._internal.converters as cv
6
- from pyield import clock
7
- from pyield._internal.types import DateLike, any_is_empty
8
- from pyield.b3._contracts import normalizar_codigos_contrato
9
- from pyield.b3._validar_pregao import data_negociacao_valida
10
- from pyield.b3.futures import historical, intraday
11
-
12
- logger = logging.getLogger(__name__)
13
-
14
-
15
- def futures(
16
- date: DateLike,
17
- contract_code: str | list[str],
18
- full_report: bool | None = None,
19
- ) -> pl.DataFrame:
20
- """Busca dados de um contrato futuro da B3 para a data de referência.
21
-
22
- Args:
23
- date: Data de referência para consulta.
24
- contract_code: Código do contrato futuro na B3 ou coleção de códigos.
25
- Exemplos de códigos válidos:
26
- - Juros: DI1, DDI, OC1, DAP, IAP
27
- - Moedas: DOL, WDO, EUR, GBR, JAP, CNY
28
- - Índices: IND, WIN, ISP, WSP
29
- - Commodities: BGI, CCM, ICF, CNL, SJC, SOY, ETH, GLD
30
- full_report: Controla a fonte de dados quando o dado não está no cache.
31
- Se None (padrão), prioriza dados intraday (para data de hoje) e
32
- usa SPR como fallback — ideal para uso interativo.
33
- Se False, usa o simplified price report (SPR, ~2 KB).
34
- Se True, usa apenas o price report completo (PR, ~2 MB) —
35
- indicado para processos batch noturnos.
36
-
37
- Returns:
38
- DataFrame Polars com os dados do contrato informado.
39
-
40
- Notes:
41
- Os contratos DI1, DDI, FRC, FRO, DAP, DOL, WDO, IND e WIN possuem
42
- histórico pré-cacheado (desde 2018) e são retornados instantaneamente.
43
- Para os demais contratos, os dados são baixados diretamente da B3 a
44
- cada chamada, o que pode ser mais lento.
45
-
46
- Examples:
47
- >>> df = futures("31-05-2024", "DI1")
48
- >>> df = futures("31-05-2024", "DAP")
49
-
50
- Véspera de Natal e Ano Novo não têm pregão:
51
-
52
- >>> futures("24-12-2024", "DI1").is_empty()
53
- True
54
- >>> futures("31-12-2024", "DI1").is_empty()
55
- True
56
-
57
- Data futura e fim de semana retornam DataFrame vazio:
58
-
59
- >>> import datetime as dt
60
- >>> amanha = dt.date.today() + dt.timedelta(days=1)
61
- >>> futures(amanha, "DI1").is_empty()
62
- True
63
- >>> futures("04-01-2025", "DI1").is_empty() # sábado
64
- True
65
-
66
- """
67
- if any_is_empty(date, contract_code):
68
- return pl.DataFrame()
69
-
70
- codigos_contrato = normalizar_codigos_contrato(contract_code)
71
- if not codigos_contrato:
72
- return pl.DataFrame()
73
-
74
- data_negociacao = cv.converter_datas(date)
75
- if not data_negociacao_valida(data_negociacao):
76
- return pl.DataFrame()
77
-
78
- # None: tenta intraday primeiro (só hoje + horário ok)
79
- if full_report is None:
80
- eh_hoje = intraday.data_intraday_valida(data_negociacao)
81
- if eh_hoje:
82
- horario = clock.now().time()
83
- if horario >= intraday.HORA_INICIO_INTRADAY:
84
- df = intraday.intraday(codigos_contrato)
85
- if not df.is_empty():
86
- return df
87
- full_report = False
88
-
89
- return historical.historical(data_negociacao, codigos_contrato, full_report)
90
-
91
-
92
- __all__ = ["futures"]
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