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.
- {pyield-0.44.4 → pyield-0.45.0}/PKG-INFO +25 -10
- {pyield-0.44.4 → pyield-0.45.0}/README.md +23 -7
- pyield-0.45.0/pyield/__about__.py +1 -0
- pyield-0.45.0/pyield/_internal/cache.py +39 -0
- {pyield-0.44.4 → pyield-0.45.0}/pyield/_internal/converters.py +9 -4
- {pyield-0.44.4 → pyield-0.45.0}/pyield/anbima/ettj_intraday.py +2 -2
- {pyield-0.44.4 → pyield-0.45.0}/pyield/anbima/ettj_last.py +2 -2
- {pyield-0.44.4 → pyield-0.45.0}/pyield/anbima/ima.py +2 -2
- {pyield-0.44.4 → pyield-0.45.0}/pyield/anbima/tpf.py +26 -16
- {pyield-0.44.4 → pyield-0.45.0}/pyield/b3/di1.py +24 -94
- pyield-0.45.0/pyield/b3/futures/__init__.py +191 -0
- {pyield-0.44.4 → pyield-0.45.0}/pyield/b3/futures/historical.py +23 -13
- {pyield-0.44.4 → pyield-0.45.0}/pyield/b3/futures/intraday.py +1 -1
- {pyield-0.44.4 → pyield-0.45.0}/pyield/b3/intraday_derivatives.py +2 -2
- {pyield-0.44.4 → pyield-0.45.0}/pyield/bc/ptax_api.py +2 -2
- {pyield-0.44.4 → pyield-0.45.0}/pyield/bc/rates.py +2 -2
- {pyield-0.44.4 → pyield-0.45.0}/pyield/bc/trades_intraday.py +2 -2
- {pyield-0.44.4 → pyield-0.45.0}/pyield/bc/trades_monthly.py +11 -58
- {pyield-0.44.4 → pyield-0.45.0}/pyield/bc/vna.py +2 -2
- {pyield-0.44.4 → pyield-0.45.0}/pyield/bday/core.py +23 -26
- {pyield-0.44.4 → pyield-0.45.0}/pyield/fwd.py +0 -4
- pyield-0.45.0/pyield/ipca/projected.py +103 -0
- {pyield-0.44.4 → pyield-0.45.0}/pyield/rmd.py +13 -35
- {pyield-0.44.4 → pyield-0.45.0}/pyield/tn/lft.py +66 -34
- {pyield-0.44.4 → pyield-0.45.0}/pyield/tn/ltn.py +63 -37
- {pyield-0.44.4 → pyield-0.45.0}/pyield/tn/ntnb.py +128 -81
- {pyield-0.44.4 → pyield-0.45.0}/pyield/tn/ntnb1.py +17 -25
- {pyield-0.44.4 → pyield-0.45.0}/pyield/tn/ntnbprinc.py +6 -6
- {pyield-0.44.4 → pyield-0.45.0}/pyield/tn/ntnc.py +63 -28
- {pyield-0.44.4 → pyield-0.45.0}/pyield/tn/ntnf.py +118 -185
- pyield-0.45.0/pyield/tn/utils.py +216 -0
- {pyield-0.44.4 → pyield-0.45.0}/pyproject.toml +4 -6
- pyield-0.44.4/pyield/__about__.py +0 -1
- pyield-0.44.4/pyield/b3/futures/__init__.py +0 -92
- pyield-0.44.4/pyield/ipca/projected.py +0 -117
- pyield-0.44.4/pyield/tn/tools.py +0 -112
- {pyield-0.44.4 → pyield-0.45.0}/.gitignore +0 -0
- {pyield-0.44.4 → pyield-0.45.0}/LICENSE +0 -0
- {pyield-0.44.4 → pyield-0.45.0}/pyield/__init__.py +0 -0
- {pyield-0.44.4 → pyield-0.45.0}/pyield/_internal/__init__.py +0 -0
- {pyield-0.44.4 → pyield-0.45.0}/pyield/_internal/data_cache.py +0 -0
- {pyield-0.44.4 → pyield-0.45.0}/pyield/_internal/retry.py +0 -0
- {pyield-0.44.4 → pyield-0.45.0}/pyield/_internal/types.py +0 -0
- {pyield-0.44.4 → pyield-0.45.0}/pyield/anbima/__init__.py +0 -0
- {pyield-0.44.4 → pyield-0.45.0}/pyield/anbima/difusao.py +0 -0
- {pyield-0.44.4 → pyield-0.45.0}/pyield/anbima/imaq.py +0 -0
- {pyield-0.44.4 → pyield-0.45.0}/pyield/b3/__init__.py +0 -0
- {pyield-0.44.4 → pyield-0.45.0}/pyield/b3/_contracts.py +0 -0
- {pyield-0.44.4 → pyield-0.45.0}/pyield/b3/_validar_pregao.py +0 -0
- {pyield-0.44.4 → pyield-0.45.0}/pyield/b3/di_over.py +0 -0
- {pyield-0.44.4 → pyield-0.45.0}/pyield/b3/futures/common.py +0 -0
- {pyield-0.44.4 → pyield-0.45.0}/pyield/b3/price_report.py +0 -0
- {pyield-0.44.4 → pyield-0.45.0}/pyield/bc/__init__.py +0 -0
- {pyield-0.44.4 → pyield-0.45.0}/pyield/bc/auction.py +0 -0
- {pyield-0.44.4 → pyield-0.45.0}/pyield/bc/copom.py +0 -0
- {pyield-0.44.4 → pyield-0.45.0}/pyield/bc/repo.py +0 -0
- {pyield-0.44.4 → pyield-0.45.0}/pyield/bday/__init__.py +0 -0
- {pyield-0.44.4 → pyield-0.45.0}/pyield/bday/holidays/br_holidays_new.txt +0 -0
- {pyield-0.44.4 → pyield-0.45.0}/pyield/bday/holidays/br_holidays_old.txt +0 -0
- {pyield-0.44.4 → pyield-0.45.0}/pyield/bday/holidays/brholidays.py +0 -0
- {pyield-0.44.4 → pyield-0.45.0}/pyield/clock.py +0 -0
- {pyield-0.44.4 → pyield-0.45.0}/pyield/interpolator.py +0 -0
- {pyield-0.44.4 → pyield-0.45.0}/pyield/ipca/__init__.py +0 -0
- {pyield-0.44.4 → pyield-0.45.0}/pyield/ipca/historical.py +0 -0
- {pyield-0.44.4 → pyield-0.45.0}/pyield/py.typed +0 -0
- {pyield-0.44.4 → pyield-0.45.0}/pyield/selic/__init__.py +0 -0
- {pyield-0.44.4 → pyield-0.45.0}/pyield/selic/cpm.py +0 -0
- {pyield-0.44.4 → pyield-0.45.0}/pyield/selic/probabilities.py +0 -0
- {pyield-0.44.4 → pyield-0.45.0}/pyield/tn/__init__.py +0 -0
- {pyield-0.44.4 → pyield-0.45.0}/pyield/tn/auctions.py +0 -0
- {pyield-0.44.4 → pyield-0.45.0}/pyield/tn/benchmark.py +0 -0
- {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.
|
|
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.
|
|
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,
|
|
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
|
|
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:
|
|
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
|
-
|
|
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
|
|
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,
|
|
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
|
|
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:
|
|
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
|
-
|
|
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
|
|
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,
|
|
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
|
|
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-
|
|
144
|
-
shape: (
|
|
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
|
-
@
|
|
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
|
-
@
|
|
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
|
-
@
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
261
|
-
|
|
262
|
-
df_ref =
|
|
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
|
|
312
|
+
return valor
|
|
384
313
|
|
|
385
314
|
|
|
386
|
-
def
|
|
387
|
-
"""Retorna
|
|
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
|
|
390
|
-
|
|
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
|
|
394
|
-
|
|
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.
|
|
329
|
+
>>> di1.available_dates().head(5)
|
|
400
330
|
shape: (5,)
|
|
401
|
-
Series: '
|
|
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
|
|
340
|
+
return _listar_datas("DI1")
|