PYield 0.44.4__tar.gz → 0.45.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 (72) hide show
  1. {pyield-0.44.4 → pyield-0.45.0}/PKG-INFO +25 -10
  2. {pyield-0.44.4 → pyield-0.45.0}/README.md +23 -7
  3. pyield-0.45.0/pyield/__about__.py +1 -0
  4. pyield-0.45.0/pyield/_internal/cache.py +39 -0
  5. {pyield-0.44.4 → pyield-0.45.0}/pyield/_internal/converters.py +9 -4
  6. {pyield-0.44.4 → pyield-0.45.0}/pyield/anbima/ettj_intraday.py +2 -2
  7. {pyield-0.44.4 → pyield-0.45.0}/pyield/anbima/ettj_last.py +2 -2
  8. {pyield-0.44.4 → pyield-0.45.0}/pyield/anbima/ima.py +2 -2
  9. {pyield-0.44.4 → pyield-0.45.0}/pyield/anbima/tpf.py +26 -16
  10. {pyield-0.44.4 → pyield-0.45.0}/pyield/b3/di1.py +24 -94
  11. pyield-0.45.0/pyield/b3/futures/__init__.py +191 -0
  12. {pyield-0.44.4 → pyield-0.45.0}/pyield/b3/futures/historical.py +23 -13
  13. {pyield-0.44.4 → pyield-0.45.0}/pyield/b3/futures/intraday.py +1 -1
  14. {pyield-0.44.4 → pyield-0.45.0}/pyield/b3/intraday_derivatives.py +2 -2
  15. {pyield-0.44.4 → pyield-0.45.0}/pyield/bc/ptax_api.py +2 -2
  16. {pyield-0.44.4 → pyield-0.45.0}/pyield/bc/rates.py +2 -2
  17. {pyield-0.44.4 → pyield-0.45.0}/pyield/bc/trades_intraday.py +2 -2
  18. {pyield-0.44.4 → pyield-0.45.0}/pyield/bc/trades_monthly.py +11 -58
  19. {pyield-0.44.4 → pyield-0.45.0}/pyield/bc/vna.py +2 -2
  20. {pyield-0.44.4 → pyield-0.45.0}/pyield/bday/core.py +23 -26
  21. {pyield-0.44.4 → pyield-0.45.0}/pyield/fwd.py +0 -4
  22. pyield-0.45.0/pyield/ipca/projected.py +103 -0
  23. {pyield-0.44.4 → pyield-0.45.0}/pyield/rmd.py +13 -35
  24. {pyield-0.44.4 → pyield-0.45.0}/pyield/tn/lft.py +66 -34
  25. {pyield-0.44.4 → pyield-0.45.0}/pyield/tn/ltn.py +63 -37
  26. {pyield-0.44.4 → pyield-0.45.0}/pyield/tn/ntnb.py +128 -81
  27. {pyield-0.44.4 → pyield-0.45.0}/pyield/tn/ntnb1.py +17 -25
  28. {pyield-0.44.4 → pyield-0.45.0}/pyield/tn/ntnbprinc.py +6 -6
  29. {pyield-0.44.4 → pyield-0.45.0}/pyield/tn/ntnc.py +63 -28
  30. {pyield-0.44.4 → pyield-0.45.0}/pyield/tn/ntnf.py +118 -185
  31. pyield-0.45.0/pyield/tn/utils.py +216 -0
  32. {pyield-0.44.4 → pyield-0.45.0}/pyproject.toml +4 -6
  33. pyield-0.44.4/pyield/__about__.py +0 -1
  34. pyield-0.44.4/pyield/b3/futures/__init__.py +0 -92
  35. pyield-0.44.4/pyield/ipca/projected.py +0 -117
  36. pyield-0.44.4/pyield/tn/tools.py +0 -112
  37. {pyield-0.44.4 → pyield-0.45.0}/.gitignore +0 -0
  38. {pyield-0.44.4 → pyield-0.45.0}/LICENSE +0 -0
  39. {pyield-0.44.4 → pyield-0.45.0}/pyield/__init__.py +0 -0
  40. {pyield-0.44.4 → pyield-0.45.0}/pyield/_internal/__init__.py +0 -0
  41. {pyield-0.44.4 → pyield-0.45.0}/pyield/_internal/data_cache.py +0 -0
  42. {pyield-0.44.4 → pyield-0.45.0}/pyield/_internal/retry.py +0 -0
  43. {pyield-0.44.4 → pyield-0.45.0}/pyield/_internal/types.py +0 -0
  44. {pyield-0.44.4 → pyield-0.45.0}/pyield/anbima/__init__.py +0 -0
  45. {pyield-0.44.4 → pyield-0.45.0}/pyield/anbima/difusao.py +0 -0
  46. {pyield-0.44.4 → pyield-0.45.0}/pyield/anbima/imaq.py +0 -0
  47. {pyield-0.44.4 → pyield-0.45.0}/pyield/b3/__init__.py +0 -0
  48. {pyield-0.44.4 → pyield-0.45.0}/pyield/b3/_contracts.py +0 -0
  49. {pyield-0.44.4 → pyield-0.45.0}/pyield/b3/_validar_pregao.py +0 -0
  50. {pyield-0.44.4 → pyield-0.45.0}/pyield/b3/di_over.py +0 -0
  51. {pyield-0.44.4 → pyield-0.45.0}/pyield/b3/futures/common.py +0 -0
  52. {pyield-0.44.4 → pyield-0.45.0}/pyield/b3/price_report.py +0 -0
  53. {pyield-0.44.4 → pyield-0.45.0}/pyield/bc/__init__.py +0 -0
  54. {pyield-0.44.4 → pyield-0.45.0}/pyield/bc/auction.py +0 -0
  55. {pyield-0.44.4 → pyield-0.45.0}/pyield/bc/copom.py +0 -0
  56. {pyield-0.44.4 → pyield-0.45.0}/pyield/bc/repo.py +0 -0
  57. {pyield-0.44.4 → pyield-0.45.0}/pyield/bday/__init__.py +0 -0
  58. {pyield-0.44.4 → pyield-0.45.0}/pyield/bday/holidays/br_holidays_new.txt +0 -0
  59. {pyield-0.44.4 → pyield-0.45.0}/pyield/bday/holidays/br_holidays_old.txt +0 -0
  60. {pyield-0.44.4 → pyield-0.45.0}/pyield/bday/holidays/brholidays.py +0 -0
  61. {pyield-0.44.4 → pyield-0.45.0}/pyield/clock.py +0 -0
  62. {pyield-0.44.4 → pyield-0.45.0}/pyield/interpolator.py +0 -0
  63. {pyield-0.44.4 → pyield-0.45.0}/pyield/ipca/__init__.py +0 -0
  64. {pyield-0.44.4 → pyield-0.45.0}/pyield/ipca/historical.py +0 -0
  65. {pyield-0.44.4 → pyield-0.45.0}/pyield/py.typed +0 -0
  66. {pyield-0.44.4 → pyield-0.45.0}/pyield/selic/__init__.py +0 -0
  67. {pyield-0.44.4 → pyield-0.45.0}/pyield/selic/cpm.py +0 -0
  68. {pyield-0.44.4 → pyield-0.45.0}/pyield/selic/probabilities.py +0 -0
  69. {pyield-0.44.4 → pyield-0.45.0}/pyield/tn/__init__.py +0 -0
  70. {pyield-0.44.4 → pyield-0.45.0}/pyield/tn/auctions.py +0 -0
  71. {pyield-0.44.4 → pyield-0.45.0}/pyield/tn/benchmark.py +0 -0
  72. {pyield-0.44.4 → pyield-0.45.0}/pyield/tn/pre.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.45.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
@@ -36,10 +36,9 @@ 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: cachetools>=5.0.0
40
39
  Requires-Dist: fastexcel>=0.19.0
41
40
  Requires-Dist: lxml>=6.0.0
42
- Requires-Dist: polars>=1.33.0
41
+ Requires-Dist: polars>=1.39.0
43
42
  Requires-Dist: python-dateutil>=2.8.0
44
43
  Requires-Dist: requests>=2.31.0
45
44
  Requires-Dist: tenacity>=9.0.0
@@ -173,13 +172,18 @@ forwards(bdays, rates) # -> Series: [0.05, 0.070095, 0.090284]
173
172
  | Módulo | Finalidade |
174
173
  |--------|---------|
175
174
  | `bday` | Calendário de dias úteis com feriados brasileiros |
176
- | `futures` | Dados de futuros da B3 (DI1, DDI, FRC, DAP, DOL, WDO, IND, WIN) |
175
+ | `futures` | Dados de futuros da B3 (DI1, DDI, DAP, DOL, WDO, IND, WIN e outros) |
176
+ | `di1` | Curva DI1 interpolada e datas de negociação disponíveis |
177
177
  | `Interpolator` | Interpolação de taxas (flat_forward, linear) |
178
178
  | `forward` / `forwards` | Cálculo de taxas a termo |
179
- | `ltn`, `ntnb`, `ntnf`, `lft`, `ntnc` | Precificação e análise de títulos públicos |
179
+ | `ltn`, `ntnb`, `ntnf`, `lft`, `ntnc` | Precificação e análise dos títulos públicos principais |
180
+ | `ntnb1`, `ntnbprinc`, `pre` | Títulos e curvas adicionais (NTN-B1, NTN-B Principal, curva PRE) |
181
+ | `tn.auction` / `tn.benchmarks` | Leilões e benchmarks de títulos públicos |
180
182
  | `anbima` | Dados da ANBIMA (preços de TPF, curvas de juros, índices IMA) |
181
- | `bc` | Indicadores do BCB (SELIC, PTAX, repos, VNA) |
183
+ | `bc` | Indicadores do BCB (SELIC, PTAX, repos, VNA, leilões, negociações) |
184
+ | `b3` | Dados da B3 (DI over, price reports, derivativos intradiários) |
182
185
  | `ipca` | Dados de inflação (histórico e projeções) |
186
+ | `selic` | Opções digitais de COPOM e probabilidades implícitas |
183
187
  | `rmd` | Relatório Mensal da Dívida do Tesouro Nacional |
184
188
  | `today` / `now` | Data/hora atual no Brasil (America/Sao_Paulo) |
185
189
 
@@ -209,9 +213,16 @@ from pyield import futures
209
213
  # DI1 (Futuro de Depósito Interfinanceiro)
210
214
  futures("31-05-2024", "DI1")
211
215
 
212
- # Outros contratos: DDI, FRC, DAP, DOL, WDO, IND, WIN
216
+ # Outros contratos disponíveis:
217
+ # - Juros: DI1, DDI, OC1, DAP, IAP
218
+ # - Moedas: DOL, WDO, EUR, GBR, JAP, CNY
219
+ # - Índices: IND, WIN, ISP, WSP
220
+ # - Commodities: BGI, CCM, ICF, CNL, SJC, SOY, ETH, GLD
213
221
  futures("31-05-2024", "DAP")
214
222
 
223
+ # Múltiplos contratos de uma vez
224
+ futures("31-05-2024", ["DI1", "DAP"])
225
+
215
226
  # Dados intradiários (quando o mercado estiver aberto)
216
227
  futures("16-01-2025", "DI1") # Retorna dados ao vivo durante o horário de negociação
217
228
  ```
@@ -221,7 +232,6 @@ futures("16-01-2025", "DI1") # Retorna dados ao vivo durante o horário de nego
221
232
  PYield aceita entradas de data flexíveis (`DateLike`):
222
233
  - Strings: `"31-05-2024"`, `"31/05/2024"`, `"2024-05-31"`
223
234
  - `datetime.date`, `datetime.datetime`
224
- - `pandas.Timestamp`, `numpy.datetime64`
225
235
 
226
236
  Funções escalares retornam `datetime.date`. Funções vetorizadas retornam `polars.Series`.
227
237
 
@@ -241,9 +251,14 @@ bday.count(["01-01-2024", None], "01-02-2024") # -> Series: [22, null]
241
251
 
242
252
  ## Migração para Polars (v0.40.0+)
243
253
 
244
- Todas as funções retornam **DataFrames/Series do Polars**. Para converter para Pandas:
254
+ 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
255
 
246
256
  ```python
257
+ # Entrada: converter coleções Pandas/NumPy antes de passar para o PYield
258
+ pl_series = pl.from_pandas(pd_series) # pd.Series → pl.Series (requer pandas)
259
+ pl_series = pl.Series(np_array) # np.ndarray → pl.Series (requer numpy)
260
+
261
+ # Saída: converter DataFrame Polars para Pandas
247
262
  df_pandas = df.to_pandas(use_pyarrow_extension_array=True)
248
263
  ```
249
264
 
@@ -254,5 +269,5 @@ Documentação completa: [crdcj.github.io/PYield](https://crdcj.github.io/PYield
254
269
  ## Testes
255
270
 
256
271
  ```sh
257
- pytest pyield --doctest-modules
272
+ pytest
258
273
  ```
@@ -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.45.0"
@@ -0,0 +1,39 @@
1
+ """Cache com expiração por tempo (TTL) para proteger APIs externas contra
2
+ chamadas repetidas acidentais (ex.: re-execução de célula em notebook).
3
+ """
4
+
5
+ import time
6
+ from functools import wraps
7
+
8
+ _TTL_PADRAO = 60 # segundos
9
+ _TAMANHO_MAXIMO = 16
10
+
11
+
12
+ def ttl_cache(ttl: int = _TTL_PADRAO, maxsize: int = _TAMANHO_MAXIMO):
13
+ """Decorador de cache com expiração por tempo.
14
+
15
+ Args:
16
+ ttl: Tempo de vida de cada entrada em segundos.
17
+ maxsize: Número máximo de entradas no cache.
18
+ """
19
+
20
+ def decorador(func):
21
+ _cache: dict = {}
22
+
23
+ @wraps(func)
24
+ def wrapper(*args):
25
+ agora = time.monotonic()
26
+ if args in _cache:
27
+ resultado, expira_em = _cache[args]
28
+ if agora < expira_em:
29
+ return resultado
30
+ resultado = func(*args)
31
+ _cache[args] = (resultado, agora + ttl)
32
+ # Remove entrada mais antiga quando excede o tamanho máximo
33
+ if len(_cache) > maxsize:
34
+ _cache.pop(next(iter(_cache)))
35
+ return resultado
36
+
37
+ return wrapper
38
+
39
+ return decorador
@@ -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:
@@ -121,6 +121,10 @@ def converter_datas(
121
121
  >>> converter_datas("") is None
122
122
  True
123
123
 
124
+ Data inválida retorna None:
125
+ >>> converter_datas("31-02-2024") is None
126
+ True
127
+
124
128
  Lista com valores nulos propaga os nulls:
125
129
  >>> converter_datas(["01-01-2024", None])
126
130
  shape: (2,)
@@ -140,12 +144,13 @@ def converter_datas(
140
144
  ]
141
145
 
142
146
  Formatos mistos por linha:
143
- >>> converter_datas(["25-12-2024", "2024-12-26"])
144
- shape: (2,)
147
+ >>> converter_datas(["25-12-2024", "26/12/2024", "2024-12-27"])
148
+ shape: (3,)
145
149
  Series: 'dates' [date]
146
150
  [
147
151
  2024-12-25
148
152
  2024-12-26
153
+ 2024-12-27
149
154
  ]
150
155
  """
151
156
  if not types.is_collection(dates):
@@ -2,10 +2,10 @@ import datetime as dt
2
2
  import logging
3
3
  from io import StringIO
4
4
 
5
- import cachetools
6
5
  import polars as pl
7
6
  import requests
8
7
 
8
+ from pyield._internal.cache import ttl_cache
9
9
  from pyield._internal.retry import retry_padrao
10
10
 
11
11
  logger = logging.getLogger(__name__)
@@ -19,7 +19,7 @@ URL_ETTJ_INTRADAY = (
19
19
  CASAS_DECIMAIS = 6
20
20
 
21
21
 
22
- @cachetools.cached(cache=cachetools.TTLCache(maxsize=16, ttl=60))
22
+ @ttl_cache()
23
23
  @retry_padrao
24
24
  def _buscar_texto_intraday() -> str:
25
25
  carga_requisicao = {"Dt_Ref": "", "saida": "csv"}
@@ -2,10 +2,10 @@ import datetime as dt
2
2
  import logging
3
3
  from io import StringIO
4
4
 
5
- import cachetools
6
5
  import polars as pl
7
6
  import requests
8
7
 
8
+ from pyield._internal.cache import ttl_cache
9
9
  from pyield._internal.retry import retry_padrao
10
10
 
11
11
  logger = logging.getLogger(__name__)
@@ -16,7 +16,7 @@ URL_ULTIMA_ETTJ = "https://www.anbima.com.br/informacoes/est-termo/CZ-down.asp"
16
16
  CASAS_DECIMAIS = 6
17
17
 
18
18
 
19
- @cachetools.cached(cache=cachetools.TTLCache(maxsize=16, ttl=60))
19
+ @ttl_cache()
20
20
  @retry_padrao
21
21
  def _buscar_texto_ultima_ettj() -> str:
22
22
  """Busca o texto bruto da curva de juros na ANBIMA."""
@@ -2,10 +2,10 @@ import io
2
2
  import logging
3
3
  from typing import Literal
4
4
 
5
- import cachetools
6
5
  import polars as pl
7
6
  import requests
8
7
 
8
+ from pyield._internal.cache import ttl_cache
9
9
  from pyield._internal.retry import retry_padrao
10
10
 
11
11
  logger = logging.getLogger(__name__)
@@ -82,7 +82,7 @@ ORDEM_COLUNAS_FINAL = [
82
82
  URL_ULTIMO_IMA = "https://www.anbima.com.br/informacoes/ima/arqs/ima_completo.txt"
83
83
 
84
84
 
85
- @cachetools.cached(cache=cachetools.TTLCache(maxsize=16, ttl=60))
85
+ @ttl_cache()
86
86
  @retry_padrao
87
87
  def _buscar_texto_ultimo_ima() -> str:
88
88
  resposta = requests.get(URL_ULTIMO_IMA, timeout=3)
@@ -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)
@@ -360,10 +293,6 @@ def interpolate_rate(
360
293
  >>> # Extrapola taxa para uma data de vencimento futura
361
294
  >>> di1.interpolate_rate("25-04-2025", "01-01-2050", extrapolate=True)
362
295
  0.13881
363
-
364
- >>> # Entradas nulas retornam NaN
365
- >>> di1.interpolate_rate(None, "01-01-2030")
366
- nan
367
296
  """
368
297
  if any_is_collection(date, expiration):
369
298
  raise ValueError("As entradas 'date' e 'expiration' devem ser datas escalares.")
@@ -380,25 +309,26 @@ def interpolate_rate(
380
309
  if valor is None:
381
310
  return float("nan")
382
311
 
383
- return float(valor)
312
+ return valor
384
313
 
385
314
 
386
- def available_trade_dates() -> pl.Series:
387
- """Retorna todas as datas de negociação disponíveis para DI1 em futures.
315
+ def available_dates() -> pl.Series:
316
+ """Retorna as datas de negociação disponíveis para DI1.
388
317
 
389
- Obtém valores distintos de 'TradeDate' para DI1, com base no mesmo
390
- dataset PR utilizado por `futures`.
318
+ Obtém valores distintos de 'TradeDate' para DI1, com base no dataset histórico
319
+ PR da B3 (mesmo utilizado por `futures`). Inclui apenas datas com preço e taxa
320
+ de ajuste já definidos; o pregão do dia corrente não está incluído.
391
321
 
392
322
  Returns:
393
- Series ordenada de datas de negociação únicas (dt.date) para as quais
394
- dados de DI estão disponíveis.
323
+ Series ordenada de datas de negociação (dt.date) para as quais dados de
324
+ ajuste de DI estão disponíveis.
395
325
 
396
326
  Examples:
397
327
  >>> from pyield import di1
398
328
  >>> # Série disponível no dataset PR começa em 2018-01-02
399
- >>> di1.available_trade_dates().head(5)
329
+ >>> di1.available_dates().head(5)
400
330
  shape: (5,)
401
- Series: 'available_dates' [date]
331
+ Series: 'TradeDate' [date]
402
332
  [
403
333
  2018-01-02
404
334
  2018-01-03
@@ -407,4 +337,4 @@ def available_trade_dates() -> pl.Series:
407
337
  2018-01-08
408
338
  ]
409
339
  """
410
- return futuros_historico.listar_datas_disponiveis("DI1").alias("available_dates")
340
+ return _listar_datas("DI1")