PYield 0.43.2__tar.gz → 0.43.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 (67) hide show
  1. {pyield-0.43.2 → pyield-0.43.4}/PKG-INFO +9 -1
  2. {pyield-0.43.2 → pyield-0.43.4}/README.md +7 -0
  3. pyield-0.43.4/pyield/__about__.py +1 -0
  4. {pyield-0.43.2 → pyield-0.43.4}/pyield/__init__.py +2 -0
  5. {pyield-0.43.2 → pyield-0.43.4}/pyield/_internal/__init__.py +0 -1
  6. {pyield-0.43.2 → pyield-0.43.4}/pyield/anbima/difusao.py +11 -11
  7. {pyield-0.43.2 → pyield-0.43.4}/pyield/anbima/ettj_intraday.py +5 -5
  8. {pyield-0.43.2 → pyield-0.43.4}/pyield/anbima/ettj_last.py +5 -5
  9. {pyield-0.43.2 → pyield-0.43.4}/pyield/anbima/ima.py +22 -22
  10. {pyield-0.43.2 → pyield-0.43.4}/pyield/anbima/imaq.py +12 -12
  11. {pyield-0.43.2 → pyield-0.43.4}/pyield/b3/common.py +20 -0
  12. pyield-0.43.4/pyield/b3/futures/core.py +117 -0
  13. pyield-0.43.4/pyield/b3/futures/historical/core.py +42 -0
  14. {pyield-0.43.2 → pyield-0.43.4}/pyield/b3/futures/historical/historical_b3.py +5 -8
  15. {pyield-0.43.2 → pyield-0.43.4}/pyield/b3/futures/historical/historical_bmf.py +4 -4
  16. {pyield-0.43.2 → pyield-0.43.4}/pyield/b3/futures/intraday.py +2 -3
  17. {pyield-0.43.2 → pyield-0.43.4}/pyield/b3/price_report.py +18 -14
  18. {pyield-0.43.2 → pyield-0.43.4}/pyield/bc/ptax_api.py +0 -4
  19. {pyield-0.43.2 → pyield-0.43.4}/pyield/bc/repo.py +12 -12
  20. {pyield-0.43.2 → pyield-0.43.4}/pyield/bc/trades_intraday.py +31 -31
  21. {pyield-0.43.2 → pyield-0.43.4}/pyield/bc/trades_monthly.py +19 -19
  22. {pyield-0.43.2 → pyield-0.43.4}/pyield/bday/core.py +20 -20
  23. {pyield-0.43.2 → pyield-0.43.4}/pyield/fwd.py +55 -35
  24. {pyield-0.43.2 → pyield-0.43.4}/pyield/interpolator.py +87 -82
  25. pyield-0.43.4/pyield/rmd.py +373 -0
  26. {pyield-0.43.2 → pyield-0.43.4}/pyield/tn/auctions.py +40 -40
  27. {pyield-0.43.2 → pyield-0.43.4}/pyield/tn/benchmark.py +9 -9
  28. {pyield-0.43.2 → pyield-0.43.4}/pyield/tn/lft.py +20 -20
  29. {pyield-0.43.2 → pyield-0.43.4}/pyield/tn/ltn.py +27 -27
  30. {pyield-0.43.2 → pyield-0.43.4}/pyield/tn/ntnb.py +32 -32
  31. {pyield-0.43.2 → pyield-0.43.4}/pyield/tn/ntnb1.py +2 -2
  32. {pyield-0.43.2 → pyield-0.43.4}/pyield/tn/ntnc.py +16 -16
  33. {pyield-0.43.2 → pyield-0.43.4}/pyield/tn/ntnf.py +28 -28
  34. {pyield-0.43.2 → pyield-0.43.4}/pyield/tn/pre.py +6 -6
  35. {pyield-0.43.2 → pyield-0.43.4}/pyproject.toml +21 -4
  36. pyield-0.43.2/pyield/__about__.py +0 -1
  37. pyield-0.43.2/pyield/b3/futures/core.py +0 -141
  38. pyield-0.43.2/pyield/b3/futures/historical/core.py +0 -16
  39. {pyield-0.43.2 → pyield-0.43.4}/.gitignore +0 -0
  40. {pyield-0.43.2 → pyield-0.43.4}/LICENSE +0 -0
  41. {pyield-0.43.2 → pyield-0.43.4}/pyield/_internal/converters.py +0 -0
  42. {pyield-0.43.2 → pyield-0.43.4}/pyield/_internal/data_cache.py +1 -1
  43. {pyield-0.43.2 → pyield-0.43.4}/pyield/_internal/retry.py +0 -0
  44. {pyield-0.43.2 → pyield-0.43.4}/pyield/_internal/types.py +0 -0
  45. {pyield-0.43.2 → pyield-0.43.4}/pyield/anbima/__init__.py +0 -0
  46. {pyield-0.43.2 → pyield-0.43.4}/pyield/anbima/tpf.py +0 -0
  47. {pyield-0.43.2 → pyield-0.43.4}/pyield/b3/__init__.py +0 -0
  48. {pyield-0.43.2 → pyield-0.43.4}/pyield/b3/di1.py +0 -0
  49. {pyield-0.43.2 → pyield-0.43.4}/pyield/b3/di_over.py +0 -0
  50. {pyield-0.43.2 → pyield-0.43.4}/pyield/b3/futures/__init__.py +0 -0
  51. {pyield-0.43.2 → pyield-0.43.4}/pyield/b3/futures/historical/__init__.py +0 -0
  52. {pyield-0.43.2 → pyield-0.43.4}/pyield/bc/__init__.py +0 -0
  53. {pyield-0.43.2 → pyield-0.43.4}/pyield/bc/auction.py +0 -0
  54. {pyield-0.43.2 → pyield-0.43.4}/pyield/bc/rates.py +0 -0
  55. {pyield-0.43.2 → pyield-0.43.4}/pyield/bc/vna.py +0 -0
  56. {pyield-0.43.2 → pyield-0.43.4}/pyield/bday/__init__.py +0 -0
  57. {pyield-0.43.2 → pyield-0.43.4}/pyield/bday/holidays/br_holidays_new.txt +0 -0
  58. {pyield-0.43.2 → pyield-0.43.4}/pyield/bday/holidays/br_holidays_old.txt +0 -0
  59. {pyield-0.43.2 → pyield-0.43.4}/pyield/bday/holidays/brholidays.py +0 -0
  60. {pyield-0.43.2 → pyield-0.43.4}/pyield/clock.py +0 -0
  61. {pyield-0.43.2 → pyield-0.43.4}/pyield/ipca/__init__.py +0 -0
  62. {pyield-0.43.2 → pyield-0.43.4}/pyield/ipca/historical.py +0 -0
  63. {pyield-0.43.2 → pyield-0.43.4}/pyield/ipca/projected.py +0 -0
  64. {pyield-0.43.2 → pyield-0.43.4}/pyield/py.typed +0 -0
  65. {pyield-0.43.2 → pyield-0.43.4}/pyield/tn/__init__.py +0 -0
  66. {pyield-0.43.2 → pyield-0.43.4}/pyield/tn/ntnbprinc.py +0 -0
  67. {pyield-0.43.2 → pyield-0.43.4}/pyield/tn/tools.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyield
3
- Version: 0.43.2
3
+ Version: 0.43.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
@@ -36,6 +36,7 @@ Classifier: Operating System :: OS Independent
36
36
  Classifier: Programming Language :: Python :: 3
37
37
  Classifier: Topic :: Office/Business :: Financial :: Investment
38
38
  Requires-Python: >=3.12
39
+ Requires-Dist: fastexcel>=0.19.0
39
40
  Requires-Dist: lxml>=6.0.0
40
41
  Requires-Dist: polars>=1.33.0
41
42
  Requires-Dist: python-dateutil>=2.8.0
@@ -148,6 +149,12 @@ Interpolator("flat_forward", known_bdays, known_rates, extrapolate=True)(100) #
148
149
 
149
150
  Calcula taxas a termo a partir de curvas spot:
150
151
 
152
+ Convenção utilizada:
153
+
154
+ - `fwd_k = fwd_{j->k}` (forward do vértice `j` para `k`)
155
+ - `f_k = 1 + tx_k` (fator de capitalização no vértice `k`)
156
+ - `fwd_k = (f_k^au_k / f_j^au_j)^(1 / (au_k - au_j)) - 1`, com `au = du / 252`
157
+
151
158
  ```python
152
159
  from pyield import forward, forwards
153
160
 
@@ -172,6 +179,7 @@ forwards(bdays, rates) # -> Series: [0.05, 0.070095, 0.090284]
172
179
  | `anbima` | Dados da ANBIMA (preços de TPF, curvas de juros, índices IMA) |
173
180
  | `bc` | Indicadores do BCB (SELIC, PTAX, repos, VNA) |
174
181
  | `ipca` | Dados de inflação (histórico e projeções) |
182
+ | `rmd` | Relatório Mensal da Dívida do Tesouro Nacional |
175
183
  | `today` / `now` | Data/hora atual no Brasil (America/Sao_Paulo) |
176
184
 
177
185
  ## Títulos Públicos
@@ -103,6 +103,12 @@ Interpolator("flat_forward", known_bdays, known_rates, extrapolate=True)(100) #
103
103
 
104
104
  Calcula taxas a termo a partir de curvas spot:
105
105
 
106
+ Convenção utilizada:
107
+
108
+ - `fwd_k = fwd_{j->k}` (forward do vértice `j` para `k`)
109
+ - `f_k = 1 + tx_k` (fator de capitalização no vértice `k`)
110
+ - `fwd_k = (f_k^au_k / f_j^au_j)^(1 / (au_k - au_j)) - 1`, com `au = du / 252`
111
+
106
112
  ```python
107
113
  from pyield import forward, forwards
108
114
 
@@ -127,6 +133,7 @@ forwards(bdays, rates) # -> Series: [0.05, 0.070095, 0.090284]
127
133
  | `anbima` | Dados da ANBIMA (preços de TPF, curvas de juros, índices IMA) |
128
134
  | `bc` | Indicadores do BCB (SELIC, PTAX, repos, VNA) |
129
135
  | `ipca` | Dados de inflação (histórico e projeções) |
136
+ | `rmd` | Relatório Mensal da Dívida do Tesouro Nacional |
130
137
  | `today` / `now` | Data/hora atual no Brasil (America/Sao_Paulo) |
131
138
 
132
139
  ## Títulos Públicos
@@ -0,0 +1 @@
1
+ __version__ = "0.43.4"
@@ -6,6 +6,7 @@ from pyield.b3 import di1, futures
6
6
  from pyield.clock import now, today
7
7
  from pyield.fwd import forward, forwards
8
8
  from pyield.interpolator import Interpolator
9
+ from pyield.rmd import rmd
9
10
  from pyield.tn import lft, ltn, ntnb, ntnb1, ntnbprinc, ntnc, ntnf, pre
10
11
 
11
12
  __all__ = [
@@ -31,6 +32,7 @@ __all__ = [
31
32
  "ntnc",
32
33
  "ntnf",
33
34
  "pre",
35
+ "rmd",
34
36
  "bday",
35
37
  ]
36
38
 
@@ -2,4 +2,3 @@
2
2
 
3
3
  Este módulo não faz parte da API pública estável do PYield.
4
4
  """
5
-
@@ -181,17 +181,17 @@ def tpf_difusao(data_referencia: DateLike) -> pl.DataFrame:
181
181
  não houver dados ou em caso de erro.
182
182
 
183
183
  Output Columns:
184
- * data_hora_referencia (datetime): Data e hora de referência da taxa.
185
- * provedor (string): Provedor dos dados.
186
- * titulo (string): Nome do título (ex: LFT, LTN).
187
- * data_vencimento (date): Data de vencimento do título.
188
- * codigo_isin (string): Código ISIN do título.
189
- * dias_uteis (int): Dias úteis entre a data de referência e o vencimento.
190
- * taxa_indicativa_anterior (float): Taxa indicativa de fechamento D-1 (decimal).
191
- * taxa_venda (float): Taxa de oferta de venda (Ask rate) (decimal).
192
- * taxa_compra (float): Taxa de oferta de compra (Bid rate) (decimal).
193
- * taxa_media (float): Média entre a taxa de compra e venda (decimal).
194
- * taxa_ultima (float): Última taxa negociada (decimal).
184
+ - data_hora_referencia (datetime): Data e hora de referência da taxa.
185
+ - provedor (string): Provedor dos dados.
186
+ - titulo (string): Nome do título (ex: LFT, LTN).
187
+ - data_vencimento (date): Data de vencimento do título.
188
+ - codigo_isin (string): Código ISIN do título.
189
+ - dias_uteis (int): Dias úteis entre a data de referência e o vencimento.
190
+ - taxa_indicativa_anterior (float): Taxa indicativa de fechamento D-1 (decimal).
191
+ - taxa_venda (float): Taxa de oferta de venda (Ask rate) (decimal).
192
+ - taxa_compra (float): Taxa de oferta de compra (Bid rate) (decimal).
193
+ - taxa_media (float): Média entre a taxa de compra e venda (decimal).
194
+ - taxa_ultima (float): Última taxa negociada (decimal).
195
195
  """
196
196
  if any_is_empty(data_referencia):
197
197
  logger.warning("Nenhuma data fornecida. Retornando DataFrame vazio.")
@@ -109,11 +109,11 @@ def intraday_ettj() -> pl.DataFrame:
109
109
  pl.DataFrame: DataFrame com os dados intradiários da ETTJ.
110
110
 
111
111
  Output Columns:
112
- * date (Date): data de referência da curva de juros.
113
- * vertex (Int64): vértice em dias úteis.
114
- * nominal_rate (Float64): taxa de juros nominal zero-cupom.
115
- * real_rate (Float64): taxa de juros real zero-cupom (indexada ao IPCA).
116
- * implied_inflation (Float64): taxa de inflação implícita (breakeven).
112
+ - date (Date): data de referência da curva de juros.
113
+ - vertex (Int64): vértice em dias úteis.
114
+ - nominal_rate (Float64): taxa de juros nominal zero-cupom.
115
+ - real_rate (Float64): taxa de juros real zero-cupom (indexada ao IPCA).
116
+ - implied_inflation (Float64): taxa de inflação implícita (breakeven).
117
117
 
118
118
  Note:
119
119
  Todas as taxas são expressas em formato decimal (ex: 0.12 para 12%).
@@ -98,11 +98,11 @@ def last_ettj() -> pl.DataFrame:
98
98
  pl.DataFrame: DataFrame com os dados da ETTJ de fechamento.
99
99
 
100
100
  Output Columns:
101
- * date (Date): data de referência da curva de juros.
102
- * vertex (Int64): vértice em dias úteis.
103
- * nominal_rate (Float64): taxa de juros nominal zero-cupom.
104
- * real_rate (Float64): taxa de juros real zero-cupom (indexada ao IPCA).
105
- * implied_inflation (Float64): taxa de inflação implícita (breakeven).
101
+ - date (Date): data de referência da curva de juros.
102
+ - vertex (Int64): vértice em dias úteis.
103
+ - nominal_rate (Float64): taxa de juros nominal zero-cupom.
104
+ - real_rate (Float64): taxa de juros real zero-cupom (indexada ao IPCA).
105
+ - implied_inflation (Float64): taxa de inflação implícita (breakeven).
106
106
 
107
107
  Note:
108
108
  Todas as taxas são expressas em formato decimal (ex: 0.12 para 12%).
@@ -141,28 +141,28 @@ def last_ima(ima_type: TiposIMA | None = None) -> pl.DataFrame:
141
141
  em caso de erro.
142
142
 
143
143
  Output Columns:
144
- * Date (Date): data de referência.
145
- * IMAType (String): tipo de índice IMA (ex: 'IMA-B', 'IRF-M').
146
- * BondType (String): tipo de título (ex: 'LTN', 'NTN-B').
147
- * Maturity (Date): data de vencimento do título.
148
- * SelicCode (Int64): código do título no sistema SELIC.
149
- * ISIN (String): código ISIN (International Securities Identification Number).
150
- * BDToMat (Int64): dias úteis até o vencimento.
151
- * Duration (Float64): duration do título em anos úteis (252 d.u./ano).
152
- * IndicativeRate (Float64): taxa indicativa em decimal (ex: 0.10 para 10%).
153
- * Price (Float64): preço unitário (PU) em R$.
154
- * InterestPrice (Float64): PU de juros em R$.
155
- * DV01 (Float64): DV01 em R$.
156
- * PMR (Float64): prazo médio de repactuação.
157
- * Weight (Float64): peso do título no índice (%).
158
- * Convexity (Float64): convexidade do título.
159
- * TheoreticalQuantity (Float64): quantidade teórica (em 1.000 títulos).
160
- * NumberOfOperations (Int64): número de operações.
161
- * NegotiatedQuantity (Int64): quantidade negociada (unidades).
162
- * NegotiatedValue (Float64): valor negociado em R$.
163
- * MarketDV01 (Float64): DV01 de mercado em R$.
164
- * MarketQuantity (Int64): quantidade em carteira (unidades).
165
- * MarketValue (Float64): valor de mercado em R$.
144
+ - Date (Date): data de referência.
145
+ - IMAType (String): tipo de índice IMA (ex: 'IMA-B', 'IRF-M').
146
+ - BondType (String): tipo de título (ex: 'LTN', 'NTN-B').
147
+ - Maturity (Date): data de vencimento do título.
148
+ - SelicCode (Int64): código do título no sistema SELIC.
149
+ - ISIN (String): código ISIN (International Securities Identification Number).
150
+ - BDToMat (Int64): dias úteis até o vencimento.
151
+ - Duration (Float64): duration do título em anos úteis (252 d.u./ano).
152
+ - IndicativeRate (Float64): taxa indicativa em decimal (ex: 0.10 para 10%).
153
+ - Price (Float64): preço unitário (PU) em R$.
154
+ - InterestPrice (Float64): PU de juros em R$.
155
+ - DV01 (Float64): DV01 em R$.
156
+ - PMR (Float64): prazo médio de repactuação.
157
+ - Weight (Float64): peso do título no índice (%).
158
+ - Convexity (Float64): convexidade do título.
159
+ - TheoreticalQuantity (Float64): quantidade teórica (em 1.000 títulos).
160
+ - NumberOfOperations (Int64): número de operações.
161
+ - NegotiatedQuantity (Int64): quantidade negociada (unidades).
162
+ - NegotiatedValue (Float64): valor negociado em R$.
163
+ - MarketDV01 (Float64): DV01 de mercado em R$.
164
+ - MarketQuantity (Int64): quantidade em carteira (unidades).
165
+ - MarketValue (Float64): valor de mercado em R$.
166
166
 
167
167
  Examples:
168
168
  >>> from pyield import anbima
@@ -221,18 +221,18 @@ def imaq(date: DateLike) -> pl.DataFrame:
221
221
  vazio e registra log da exceção.
222
222
 
223
223
  Output Columns:
224
- * Date (Date): data de referência dos dados.
225
- * BondType (String): tipo do título (LTN, NTN-B, NTN-F, LFT, …).
226
- * MaturityDate (Date): data de vencimento do título.
227
- * SelicCode (Int64): código SELIC do título.
228
- * ISIN (String): código ISIN (International Securities Id Number).
229
- * Price (Float64): PU do título em R$.
230
- * MarketQuantity (Int64): quantidade em mercado (unidades).
231
- * MarketDV01 (Int64): DV01 do estoque em R$.
232
- * MarketDV01USD (Int64): DV01 do estoque em USD.
233
- * MarketValue (Int64): valor de mercado em R$.
234
- * QuantityVariation (Int64): variação diária da quantidade.
235
- * BondStatus (String): status do título.
224
+ - Date (Date): data de referência dos dados.
225
+ - BondType (String): tipo do título (LTN, NTN-B, NTN-F, LFT, …).
226
+ - MaturityDate (Date): data de vencimento do título.
227
+ - SelicCode (Int64): código SELIC do título.
228
+ - ISIN (String): código ISIN (International Securities Id Number).
229
+ - Price (Float64): PU do título em R$.
230
+ - MarketQuantity (Int64): quantidade em mercado (unidades).
231
+ - MarketDV01 (Int64): DV01 do estoque em R$.
232
+ - MarketDV01USD (Int64): DV01 do estoque em USD.
233
+ - MarketValue (Int64): valor de mercado em R$.
234
+ - QuantityVariation (Int64): variação diária da quantidade.
235
+ - BondStatus (String): status do título.
236
236
 
237
237
  Notes:
238
238
  - Valores convertidos para unidades puras (ex: MarketQuantity × 1.000).
@@ -8,6 +8,26 @@ from pyield import bday, clock
8
8
  registro = logging.getLogger(__name__)
9
9
 
10
10
 
11
+ def expr_dv01(
12
+ coluna_dias_uteis: str,
13
+ coluna_taxa: str,
14
+ coluna_preco: str,
15
+ ) -> pl.Expr:
16
+ """Retorna a expressão Polars para cálculo de DV01.
17
+
18
+ Fórmula:
19
+ DV01 = (Duration / (1 + taxa)) * preço * 0,0001
20
+
21
+ Onde:
22
+ - Duration = dias_uteis / 252
23
+ - taxa deve estar em formato decimal (ex.: 0.145)
24
+ - preço é o PU do contrato.
25
+ """
26
+ duracao = pl.col(coluna_dias_uteis) / 252
27
+ duracao_modificada = duracao / (1 + pl.col(coluna_taxa))
28
+ return 0.0001 * duracao_modificada * pl.col(coluna_preco)
29
+
30
+
11
31
  def adicionar_vencimento(
12
32
  df: pl.DataFrame, codigo_contrato: str, coluna_ticker: str
13
33
  ) -> pl.DataFrame:
@@ -0,0 +1,117 @@
1
+ import datetime as dt
2
+ import logging
3
+ from typing import Literal
4
+
5
+ import polars as pl
6
+
7
+ import pyield._internal.converters as cv
8
+ import pyield.b3.common as cm
9
+ import pyield.b3.futures.historical.core as hcore
10
+ from pyield import clock
11
+ from pyield._internal.types import DateLike, any_is_empty
12
+ from pyield.b3.futures.intraday import fetch_intraday_df
13
+
14
+ OpcoesContrato = Literal["DI1", "DDI", "FRC", "DAP", "DOL", "WDO", "IND", "WIN"]
15
+ logger = logging.getLogger(__name__)
16
+
17
+
18
+ # Pregão abre às 9:00, porém os dados têm atraso de 15 minutos.
19
+ # Esperar 1 minuto adicional para garantir que estejam disponíveis (9:16h).
20
+ HORA_INICIO_INTRADAY = dt.time(9, 16)
21
+ # Pregão fecha às 18:00h, momento em que os dados consolidados começam a ser preparados.
22
+ HORA_FIM_INTRADAY = dt.time(18, 30)
23
+
24
+
25
+ def _data_intraday_valida(data_verificacao: dt.date) -> bool:
26
+ """Verifica se a data é um dia de negociação intraday."""
27
+ # Primeiro valida regra geral de dia futuro / não útil / datas especiais
28
+ if not cm.data_negociacao_valida(data_verificacao):
29
+ return False
30
+
31
+ # Intraday só existe para 'hoje'
32
+ hoje_brasil = clock.today()
33
+ if data_verificacao != hoje_brasil:
34
+ return False
35
+
36
+ return True
37
+
38
+
39
+ def futures(
40
+ date: DateLike,
41
+ contract_code: OpcoesContrato | str,
42
+ ) -> pl.DataFrame:
43
+ """
44
+ Fetches data for a specified futures contract based on type and reference date.
45
+
46
+ Args:
47
+ contract_code (str): The B3 futures contract code identifying the derivative.
48
+ Supported contract codes are:
49
+ - "DI1": One-day Interbank Deposit Futures (Futuro de DI) from B3.
50
+ - "DDI": DI x U.S. Dollar Spread Futures (Futuro de Cupom Cambial) from B3.
51
+ - "FRC": Forward Rate Agreement (FRA).
52
+ - "FRO": FRA DE CUPOM CAMBIAL EM OC1
53
+ - "DAP": DI x IPCA Spread Futures.
54
+ - "DOL": U.S. Dollar Futures from B3.
55
+ - "WDO": Mini U.S. Dollar Futures from B3.
56
+ - "IND": Ibovespa Futures from B3.
57
+ - "WIN": Mini Ibovespa Futures from B3.
58
+ date (DateLike): The reference date for fetching the data.
59
+
60
+ Returns:
61
+ pl.DataFrame: DataFrame containing the fetched data for the specified futures
62
+ contract.
63
+
64
+ Raises:
65
+ ValueError: If the futures contract code is not recognized or supported.
66
+
67
+ Examples:
68
+ >>> df = futures("31-05-2024", "DI1")
69
+ >>> {"TradeDate", "TickerSymbol", "ExpirationDate", "SettlementRate"}.issubset(
70
+ ... set(df.columns)
71
+ ... )
72
+ True
73
+ >>> df.shape[0] > 0
74
+ True
75
+
76
+ >>> df = futures("31-05-2024", "DAP")
77
+ >>> {"TradeDate", "TickerSymbol", "ExpirationDate", "SettlementRate"}.issubset(
78
+ ... set(df.columns)
79
+ ... )
80
+ True
81
+ >>> df.shape[0] > 0
82
+ True
83
+
84
+ """ # noqa: E501
85
+ if any_is_empty(date, contract_code):
86
+ return pl.DataFrame()
87
+ data_negociacao = cv.converter_datas(date)
88
+
89
+ # Validação centralizada (evita chamadas desnecessárias às APIs B3)
90
+ if not cm.data_negociacao_valida(data_negociacao):
91
+ logger.warning(
92
+ "A data %s não é válida. Retornando DataFrame vazio.",
93
+ data_negociacao,
94
+ )
95
+ return pl.DataFrame()
96
+
97
+ contrato_selecionado = str(contract_code).upper()
98
+
99
+ if _data_intraday_valida(data_negociacao):
100
+ # É um dia de negociação intraday
101
+ horario_atual = clock.now().time()
102
+ if horario_atual < HORA_INICIO_INTRADAY: # Mercado não está aberto ainda
103
+ logger.warning("Mercado ainda não abriu. Retornando DataFrame vazio.")
104
+ return pl.DataFrame()
105
+
106
+ # Existe a chance de que os dados consolidados estejam disponíveis após as 18h
107
+ if horario_atual >= HORA_FIM_INTRADAY:
108
+ df_hist = hcore.buscar_df_historico(data_negociacao, contrato_selecionado)
109
+ if not df_hist.is_empty():
110
+ logger.info("Dados consolidados disponíveis. Usando histórico.")
111
+ return df_hist
112
+
113
+ # Mercado está aberto e não há dados consolidados disponíveis ainda
114
+ return fetch_intraday_df(contrato_selecionado)
115
+
116
+ else: # É um dia histórico
117
+ return hcore.buscar_df_historico(data_negociacao, contrato_selecionado)
@@ -0,0 +1,42 @@
1
+ import datetime as dt
2
+
3
+ import polars as pl
4
+ from dateutil.relativedelta import relativedelta
5
+
6
+ from pyield import clock
7
+ from pyield.b3.price_report import fetch_price_report
8
+
9
+ from . import historical_b3 as hb3
10
+
11
+ JANELA_DADOS_RECENTES = relativedelta(months=1)
12
+
13
+
14
+ def buscar_df_historico(data: dt.date, codigo_contrato: str) -> pl.DataFrame:
15
+ """Busca o histórico com priorização de fonte por recência da data.
16
+
17
+ Regras:
18
+ - Data até 1 mês atrás: busca direto no Price Report (SPR).
19
+ - Data dentro da janela recente: tenta endpoint curto (`historical_b3`).
20
+ - Se o endpoint curto vier vazio: faz fallback para Price Report (SPR).
21
+ """
22
+ data_limite_recente = clock.today() - JANELA_DADOS_RECENTES
23
+
24
+ # Datas antigas não tentam endpoint de janela curta.
25
+ if data <= data_limite_recente:
26
+ try:
27
+ return fetch_price_report(
28
+ date=data, contract_code=codigo_contrato, source_type="SPR"
29
+ )
30
+ except Exception:
31
+ return pl.DataFrame()
32
+
33
+ # Datas recentes priorizam endpoint curto; fallback para price report.
34
+ df_recente = hb3.buscar_df_historico(data, codigo_contrato)
35
+ if not df_recente.is_empty():
36
+ return df_recente
37
+ try:
38
+ return fetch_price_report(
39
+ date=data, contract_code=codigo_contrato, source_type="SPR"
40
+ )
41
+ except Exception:
42
+ return pl.DataFrame()
@@ -67,7 +67,6 @@ def _buscar_csv(data: dt.date) -> bytes:
67
67
  url, params=parametros, json=carga, headers=cabecalhos, timeout=(5, 30)
68
68
  )
69
69
  resposta.raise_for_status()
70
- resposta.encoding = "utf-8-sig"
71
70
  return resposta.content
72
71
 
73
72
 
@@ -121,7 +120,7 @@ def _processar_df(
121
120
  colunas_taxa = [c for c in df.columns if c.endswith("Rate")]
122
121
 
123
122
  # Divide por 100 para transformar percentual em decimal (14.50 -> 0.1450)
124
- df = df.with_columns((pl.col(colunas_taxa) / 100).round(6))
123
+ df = df.with_columns(pl.col(colunas_taxa).truediv(100).round(6))
125
124
 
126
125
  # 4. Cálculo do DV01 (Apenas para DI1 e se tivermos as colunas necessárias)
127
126
  # SettlementPrice aqui já é o PU vindo do CSV (Ex: 99.000)
@@ -131,11 +130,9 @@ def _processar_df(
131
130
  and "SettlementPrice" in df.columns
132
131
  and "SettlementRate" in df.columns
133
132
  ):
134
- # DV01 = (Duration / (1 + Taxa)) * PU * 0.0001
135
- # Duration Modificada * PU * 1bp
136
- duracao = pl.col("BDaysToExp") / 252
137
- duracao_mod = duracao / (1 + pl.col("SettlementRate"))
138
- df = df.with_columns(DV01=duracao_mod * pl.col("SettlementPrice") * 0.0001)
133
+ df = df.with_columns(
134
+ DV01=cm.expr_dv01("BDaysToExp", "SettlementRate", "SettlementPrice")
135
+ )
139
136
 
140
137
  # 5. Forward Rates (Para DI1 e DAP)
141
138
  if codigo_contrato in {"DI1", "DAP"} and "SettlementRate" in df.columns:
@@ -191,7 +188,7 @@ def _selecionar_e_reordenar_colunas(df: pl.DataFrame) -> pl.DataFrame:
191
188
  return df.select(colunas_existentes)
192
189
 
193
190
 
194
- def _buscar_df_historico_b3(data: dt.date, codigo_contrato: str) -> pl.DataFrame:
191
+ def buscar_df_historico(data: dt.date, codigo_contrato: str) -> pl.DataFrame:
195
192
  """Busca o histórico de futuros na B3 para a data informada."""
196
193
  try:
197
194
  # Tenta baixar os dados
@@ -279,9 +279,9 @@ def _adicionar_colunas_derivadas(
279
279
  if codigo_contrato == "DI1" and {"SettlementRate", "SettlementPrice"}.issubset(
280
280
  df.columns
281
281
  ):
282
- duracao = pl.col("BDaysToExp") / 252
283
- duracao_mod = duracao / (1 + pl.col("SettlementRate"))
284
- df = df.with_columns(DV01=0.0001 * duracao_mod * pl.col("SettlementPrice"))
282
+ df = df.with_columns(
283
+ DV01=cm.expr_dv01("BDaysToExp", "SettlementRate", "SettlementPrice")
284
+ )
285
285
 
286
286
  if codigo_contrato in {"DI1", "DAP"} and "SettlementRate" in df.columns:
287
287
  df = df.with_columns(
@@ -290,7 +290,7 @@ def _adicionar_colunas_derivadas(
290
290
  return df
291
291
 
292
292
 
293
- def _buscar_df_historico(data: dt.date, codigo_contrato: str) -> pl.DataFrame:
293
+ def buscar_df_historico(data: dt.date, codigo_contrato: str) -> pl.DataFrame:
294
294
  """Obtém e processa o histórico da BMF para um contrato e data."""
295
295
  html_texto = _buscar_html(data, codigo_contrato)
296
296
  df = _parsear_html_lxml(html_texto)
@@ -34,6 +34,7 @@ import polars as pl
34
34
  import polars.selectors as cs
35
35
  import requests
36
36
 
37
+ import pyield.b3.common as cm
37
38
  from pyield import bday, clock
38
39
  from pyield._internal.retry import retry_padrao
39
40
  from pyield.fwd import forwards
@@ -142,9 +143,7 @@ def _processar_df(df: pl.DataFrame, codigo_contrato: str) -> pl.DataFrame:
142
143
  )
143
144
 
144
145
  if codigo_contrato == "DI1": # Adiciona DV01 para DI1
145
- duracao = pl.col("BDaysToExp") / 252
146
- duracao_mod = duracao / (1 + pl.col("LastRate"))
147
- df = df.with_columns(DV01=0.0001 * duracao_mod * pl.col("LastPrice"))
146
+ df = df.with_columns(DV01=cm.expr_dv01("BDaysToExp", "LastRate", "LastPrice"))
148
147
 
149
148
  return df.filter(pl.col("DaysToExp") > 0) # Remove expiring contracts
150
149
 
@@ -275,9 +275,9 @@ def _processar_df(df: pl.DataFrame, contract_code: str) -> pl.DataFrame:
275
275
  if contract_code == "DI1":
276
276
  # DV01 requer SettlementRate e SettlementPrice
277
277
  if "SettlementRate" in df.columns and "SettlementPrice" in df.columns:
278
- anos_base = pl.col("BDaysToExp") / 252
279
- duracao_mod = anos_base / (1 + pl.col("SettlementRate"))
280
- df = df.with_columns(DV01=0.0001 * duracao_mod * pl.col("SettlementPrice"))
278
+ df = df.with_columns(
279
+ DV01=cm.expr_dv01("BDaysToExp", "SettlementRate", "SettlementPrice")
280
+ )
281
281
 
282
282
  if contract_code in {"DI1", "DAP"} and "SettlementRate" in df.columns:
283
283
  forward_rates = forwards(bdays=df["BDaysToExp"], rates=df["SettlementRate"])
@@ -287,9 +287,7 @@ def _processar_df(df: pl.DataFrame, contract_code: str) -> pl.DataFrame:
287
287
  return df.select(coluna_ordem).filter(pl.col("DaysToExp") > 0)
288
288
 
289
289
 
290
- def _processar_zip(
291
- zip_data: bytes, contract_code: str, source_type: str = "SPR"
292
- ) -> pl.DataFrame:
290
+ def _processar_zip(zip_data: bytes, contract_code: str) -> pl.DataFrame:
293
291
  if not zip_data:
294
292
  registro.warning("ZIP XML vazio.")
295
293
  return pl.DataFrame()
@@ -341,19 +339,25 @@ def fetch_price_report(
341
339
  Returns:
342
340
  pl.DataFrame: DataFrame com colunas ordenadas conforme COLUNAS_SAIDA,
343
341
  filtrado para excluir contratos vencidos (DaysToExp <= 0). Retorna
344
- DataFrame vazio se não houver dados ou se a data for inválida.
342
+ DataFrame vazio para data inválida, resposta vazia ou falhas de parsing
343
+ recuperáveis.
345
344
 
346
345
  Raises:
347
346
  ValueError: Se source_type for inválido.
348
- DadoIndisponivelError: Se a data for válida mas não houver dados.
349
- requests.HTTPError: Se a requisição HTTP falhar.
347
+ DadoIndisponivelError: Se a data for válida, mas o endpoint não fornecer
348
+ arquivo para a data consultada.
349
+ requests.HTTPError: Se a requisição HTTP ao endpoint falhar.
350
350
 
351
351
  Examples:
352
352
  >>> import pyield as yd
353
353
  >>> df = yd.b3.fetch_price_report("26-04-2024", "DI1")
354
- >>> df.columns[:5]
355
- ['TradeDate', 'TickerSymbol', 'ExpirationDate', 'BDaysToExp', 'DaysToExp']
356
- >>> df.shape[0] > 0
354
+ >>> df.is_empty() or df.columns[:5] == [
355
+ ... "TradeDate",
356
+ ... "TickerSymbol",
357
+ ... "ExpirationDate",
358
+ ... "BDaysToExp",
359
+ ... "DaysToExp",
360
+ ... ]
357
361
  True
358
362
 
359
363
  >>> # Feriado ou fim de semana (retorna DataFrame vazio)
@@ -379,7 +383,7 @@ def fetch_price_report(
379
383
  registro.warning(msg_vazia)
380
384
  return pl.DataFrame()
381
385
 
382
- df = _processar_zip(dados_zip, contract_code, source_type)
386
+ df = _processar_zip(dados_zip, contract_code)
383
387
 
384
388
  if df.is_empty():
385
389
  registro.warning(msg_vazia)
@@ -420,5 +424,5 @@ def read_price_report(
420
424
  source_type = "SPR" if filename.startswith("SPRD") else "PR"
421
425
 
422
426
  dados_zip = _ler_zip_arquivo(file_path)
423
- df = _processar_zip(dados_zip, contract_code, source_type)
427
+ df = _processar_zip(dados_zip, contract_code)
424
428
  return df
@@ -110,7 +110,6 @@ def ptax_series(
110
110
  https://olinda.bcb.gov.br/olinda/servico/PTAX/versao/v1/odata/CotacaoDolarPeriodo(dataInicial=@dataInicial,dataFinalCotacao=@dataFinalCotacao)?@dataInicial='08-01-2025'&@dataFinalCotacao='08-05-2025'&$format=text/csv
111
111
 
112
112
  Consultas de Período:
113
-
114
113
  - Para consultar dados de um intervalo, forneça as datas de `start` e `end`.
115
114
  Exemplo:
116
115
 
@@ -127,7 +126,6 @@ def ptax_series(
127
126
  `ptax_series(end='27-10-2024')`
128
127
 
129
128
  Série Histórica Completa:
130
-
131
129
  - Para recuperar a série histórica completa de leilões (desde 28.11.1984
132
130
  até o último dia útil), chame a função sem fornecer os parâmetros `start` e `end`.
133
131
  Exemplo:
@@ -135,7 +133,6 @@ def ptax_series(
135
133
  `ptax_series()`
136
134
 
137
135
  Busca dados de cotações de dólar PTAX (taxa de câmbio) para o período:
138
-
139
136
  - Se `start` for fornecido e `end` não, a função retorna dados de `start` até o fim.
140
137
  - Se `end` for fornecido e `start` não, a API retorna dados do início até `end`.
141
138
  - Se ambos `start` e `end` forem omitidos, a API retorna a série histórica completa.
@@ -189,7 +186,6 @@ def ptax_series(
189
186
  - Última data disponível: data atual
190
187
 
191
188
  O DataFrame possui as seguintes colunas:
192
-
193
189
  - Date: Data da cotação.
194
190
  - DateTime: Data e hora da cotação.
195
191
  - BuyRate: Taxa de compra.
@@ -174,18 +174,18 @@ def repos(
174
174
  retorna DataFrame vazio e registra log da exceção.
175
175
 
176
176
  Output Columns:
177
- * data_leilao (Date): data de ocorrência do leilão.
178
- * data_liquidacao (Date): data de liquidação (início da operação).
179
- * data_retorno (Date): data de recompra / término da operação.
180
- * hora_inicio (Time): horário de início do leilão.
181
- * prazo_dias_corridos (Int64): dias corridos até a data de retorno.
182
- * prazo_dias_uteis (Int64): dias úteis entre liquidação e retorno (bday.count).
183
- * numero_comunicado (Int64): número do comunicado/aviso do BC (pode ser nulo).
184
- * tipo_oferta (String): classif. do tipo de oferta (ex: Tomador, Compromissada 1047).
185
- * publico_permitido (String): escopo de participantes (SomenteDealer, TodoMercado).
186
- * volume_aceito (Int64): volume aceito no leilão em reais (convertido de milhares).
187
- * taxa_corte (Float64): taxa de corte (ex. 0.1490 = 14,90%). Nula se volume_aceito = 0.
188
- * percentual_aceito (Float64): percentual do volume ofertado efetivamente aceito (0-100).
177
+ - data_leilao (Date): data de ocorrência do leilão.
178
+ - data_liquidacao (Date): data de liquidação (início da operação).
179
+ - data_retorno (Date): data de recompra / término da operação.
180
+ - hora_inicio (Time): horário de início do leilão.
181
+ - prazo_dias_corridos (Int64): dias corridos até a data de retorno.
182
+ - prazo_dias_uteis (Int64): dias úteis entre liquidação e retorno (bday.count).
183
+ - numero_comunicado (Int64): número do comunicado/aviso do BC (pode ser nulo).
184
+ - tipo_oferta (String): classif. do tipo de oferta (ex: Tomador, Compromissada 1047).
185
+ - publico_permitido (String): escopo de participantes (SomenteDealer, TodoMercado).
186
+ - volume_aceito (Int64): volume aceito no leilão em reais (convertido de milhares).
187
+ - taxa_corte (Float64): taxa de corte (ex. 0.1490 = 14,90%). Nula se volume_aceito = 0.
188
+ - percentual_aceito (Float64): percentual do volume ofertado efetivamente aceito (0-100).
189
189
  100 = nenhuma rejeição. 0 indica nada aceito (volume_aceito = 0).
190
190
 
191
191
  Notes: