PYield 0.44.3__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.
- {pyield-0.44.3 → pyield-0.44.5}/PKG-INFO +25 -9
- {pyield-0.44.3 → pyield-0.44.5}/README.md +23 -7
- pyield-0.44.5/pyield/__about__.py +1 -0
- {pyield-0.44.3 → pyield-0.44.5}/pyield/_internal/converters.py +2 -2
- {pyield-0.44.3 → pyield-0.44.5}/pyield/anbima/tpf.py +26 -16
- {pyield-0.44.3 → pyield-0.44.5}/pyield/b3/di1.py +24 -90
- pyield-0.44.5/pyield/b3/futures/__init__.py +191 -0
- {pyield-0.44.3 → pyield-0.44.5}/pyield/b3/futures/historical.py +23 -13
- {pyield-0.44.3 → pyield-0.44.5}/pyield/b3/price_report.py +2 -2
- {pyield-0.44.3 → pyield-0.44.5}/pyield/bc/trades_monthly.py +3 -22
- {pyield-0.44.3 → pyield-0.44.5}/pyield/bday/core.py +19 -26
- {pyield-0.44.3 → pyield-0.44.5}/pyproject.toml +1 -4
- pyield-0.44.3/pyield/__about__.py +0 -1
- pyield-0.44.3/pyield/b3/futures/__init__.py +0 -92
- {pyield-0.44.3 → pyield-0.44.5}/.gitignore +0 -0
- {pyield-0.44.3 → pyield-0.44.5}/LICENSE +0 -0
- {pyield-0.44.3 → pyield-0.44.5}/pyield/__init__.py +0 -0
- {pyield-0.44.3 → pyield-0.44.5}/pyield/_internal/__init__.py +0 -0
- {pyield-0.44.3 → pyield-0.44.5}/pyield/_internal/data_cache.py +0 -0
- {pyield-0.44.3 → pyield-0.44.5}/pyield/_internal/retry.py +0 -0
- {pyield-0.44.3 → pyield-0.44.5}/pyield/_internal/types.py +0 -0
- {pyield-0.44.3 → pyield-0.44.5}/pyield/anbima/__init__.py +0 -0
- {pyield-0.44.3 → pyield-0.44.5}/pyield/anbima/difusao.py +0 -0
- {pyield-0.44.3 → pyield-0.44.5}/pyield/anbima/ettj_intraday.py +0 -0
- {pyield-0.44.3 → pyield-0.44.5}/pyield/anbima/ettj_last.py +0 -0
- {pyield-0.44.3 → pyield-0.44.5}/pyield/anbima/ima.py +0 -0
- {pyield-0.44.3 → pyield-0.44.5}/pyield/anbima/imaq.py +0 -0
- {pyield-0.44.3 → pyield-0.44.5}/pyield/b3/__init__.py +0 -0
- {pyield-0.44.3 → pyield-0.44.5}/pyield/b3/_contracts.py +0 -0
- {pyield-0.44.3 → pyield-0.44.5}/pyield/b3/_validar_pregao.py +0 -0
- {pyield-0.44.3 → pyield-0.44.5}/pyield/b3/di_over.py +0 -0
- {pyield-0.44.3 → pyield-0.44.5}/pyield/b3/futures/common.py +0 -0
- {pyield-0.44.3 → pyield-0.44.5}/pyield/b3/futures/intraday.py +0 -0
- {pyield-0.44.3 → pyield-0.44.5}/pyield/b3/intraday_derivatives.py +0 -0
- {pyield-0.44.3 → pyield-0.44.5}/pyield/bc/__init__.py +0 -0
- {pyield-0.44.3 → pyield-0.44.5}/pyield/bc/auction.py +0 -0
- {pyield-0.44.3 → pyield-0.44.5}/pyield/bc/copom.py +0 -0
- {pyield-0.44.3 → pyield-0.44.5}/pyield/bc/ptax_api.py +0 -0
- {pyield-0.44.3 → pyield-0.44.5}/pyield/bc/rates.py +0 -0
- {pyield-0.44.3 → pyield-0.44.5}/pyield/bc/repo.py +0 -0
- {pyield-0.44.3 → pyield-0.44.5}/pyield/bc/trades_intraday.py +0 -0
- {pyield-0.44.3 → pyield-0.44.5}/pyield/bc/vna.py +0 -0
- {pyield-0.44.3 → pyield-0.44.5}/pyield/bday/__init__.py +0 -0
- {pyield-0.44.3 → pyield-0.44.5}/pyield/bday/holidays/br_holidays_new.txt +0 -0
- {pyield-0.44.3 → pyield-0.44.5}/pyield/bday/holidays/br_holidays_old.txt +0 -0
- {pyield-0.44.3 → pyield-0.44.5}/pyield/bday/holidays/brholidays.py +0 -0
- {pyield-0.44.3 → pyield-0.44.5}/pyield/clock.py +0 -0
- {pyield-0.44.3 → pyield-0.44.5}/pyield/fwd.py +0 -0
- {pyield-0.44.3 → pyield-0.44.5}/pyield/interpolator.py +0 -0
- {pyield-0.44.3 → pyield-0.44.5}/pyield/ipca/__init__.py +0 -0
- {pyield-0.44.3 → pyield-0.44.5}/pyield/ipca/historical.py +0 -0
- {pyield-0.44.3 → pyield-0.44.5}/pyield/ipca/projected.py +0 -0
- {pyield-0.44.3 → pyield-0.44.5}/pyield/py.typed +0 -0
- {pyield-0.44.3 → pyield-0.44.5}/pyield/rmd.py +0 -0
- {pyield-0.44.3 → pyield-0.44.5}/pyield/selic/__init__.py +0 -0
- {pyield-0.44.3 → pyield-0.44.5}/pyield/selic/cpm.py +0 -0
- {pyield-0.44.3 → pyield-0.44.5}/pyield/selic/probabilities.py +0 -0
- {pyield-0.44.3 → pyield-0.44.5}/pyield/tn/__init__.py +0 -0
- {pyield-0.44.3 → pyield-0.44.5}/pyield/tn/auctions.py +0 -0
- {pyield-0.44.3 → pyield-0.44.5}/pyield/tn/benchmark.py +0 -0
- {pyield-0.44.3 → pyield-0.44.5}/pyield/tn/lft.py +0 -0
- {pyield-0.44.3 → pyield-0.44.5}/pyield/tn/ltn.py +0 -0
- {pyield-0.44.3 → pyield-0.44.5}/pyield/tn/ntnb.py +0 -0
- {pyield-0.44.3 → pyield-0.44.5}/pyield/tn/ntnb1.py +0 -0
- {pyield-0.44.3 → pyield-0.44.5}/pyield/tn/ntnbprinc.py +0 -0
- {pyield-0.44.3 → pyield-0.44.5}/pyield/tn/ntnc.py +0 -0
- {pyield-0.44.3 → pyield-0.44.5}/pyield/tn/ntnf.py +0 -0
- {pyield-0.44.3 → pyield-0.44.5}/pyield/tn/pre.py +0 -0
- {pyield-0.44.3 → 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.
|
|
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.
|
|
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,
|
|
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
|
|
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:
|
|
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
|
-
|
|
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
|
|
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,
|
|
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.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,
|
|
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:
|
|
@@ -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)
|
|
@@ -380,25 +313,26 @@ def interpolate_rate(
|
|
|
380
313
|
if valor is None:
|
|
381
314
|
return float("nan")
|
|
382
315
|
|
|
383
|
-
return
|
|
316
|
+
return valor
|
|
384
317
|
|
|
385
318
|
|
|
386
|
-
def
|
|
387
|
-
"""Retorna
|
|
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
|
|
390
|
-
|
|
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
|
|
394
|
-
|
|
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.
|
|
333
|
+
>>> di1.available_dates().head(5)
|
|
400
334
|
shape: (5,)
|
|
401
|
-
Series: '
|
|
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
|
|
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 =
|
|
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
|
|
28
|
-
|
|
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 =
|
|
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 =
|
|
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
|
|
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:
|
|
@@ -347,7 +347,7 @@ def fetch_price_report(
|
|
|
347
347
|
dataframes.append(df)
|
|
348
348
|
if not dataframes:
|
|
349
349
|
return pl.DataFrame()
|
|
350
|
-
return pl.concat(dataframes).sort("TickerSymbol")
|
|
350
|
+
return pl.concat(dataframes, how="diagonal").sort("TickerSymbol")
|
|
351
351
|
except (zipfile.BadZipFile, etree.XMLSyntaxError):
|
|
352
352
|
registro.exception(f"Falha ao parsear o price report de {contratos} em {date}.")
|
|
353
353
|
raise
|
|
@@ -432,4 +432,4 @@ def read_price_report(
|
|
|
432
432
|
dataframes.append(df)
|
|
433
433
|
if not dataframes:
|
|
434
434
|
return pl.DataFrame()
|
|
435
|
-
return pl.concat(dataframes).sort("TickerSymbol")
|
|
435
|
+
return pl.concat(dataframes, how="diagonal").sort("TickerSymbol")
|
|
@@ -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
|
|
139
|
-
registradas no sistema Selic do
|
|
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
|
-
>>>
|
|
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
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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
|
-
|
|
270
|
-
|
|
271
|
-
|
|
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/
|
|
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.
|
|
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.3"
|
|
@@ -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
|
|
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
|