PYield 0.46.2__tar.gz → 0.46.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pyield-0.46.2 → pyield-0.46.4}/PKG-INFO +4 -4
- {pyield-0.46.2 → pyield-0.46.4}/README.md +3 -3
- pyield-0.46.4/pyield/__about__.py +1 -0
- {pyield-0.46.2 → pyield-0.46.4}/pyield/_internal/data_cache.py +5 -5
- pyield-0.46.4/pyield/anbima/tpf.py +293 -0
- {pyield-0.46.2 → pyield-0.46.4}/pyield/b3/_validar_pregao.py +16 -0
- {pyield-0.46.2 → pyield-0.46.4}/pyield/b3/di1.py +48 -44
- {pyield-0.46.2 → pyield-0.46.4}/pyield/b3/futures/__init__.py +5 -5
- {pyield-0.46.2 → pyield-0.46.4}/pyield/b3/futures/common.py +6 -6
- pyield-0.46.4/pyield/b3/futures/historical.py +305 -0
- pyield-0.46.4/pyield/b3/futures/intraday.py +159 -0
- pyield-0.46.4/pyield/b3/intraday_derivatives.py +141 -0
- pyield-0.46.4/pyield/b3/price_report.py +381 -0
- {pyield-0.46.2 → pyield-0.46.4}/pyield/selic/cpm.py +73 -78
- {pyield-0.46.2 → pyield-0.46.4}/pyield/selic/probabilities.py +64 -64
- {pyield-0.46.2 → pyield-0.46.4}/pyield/tn/lft.py +3 -2
- {pyield-0.46.2 → pyield-0.46.4}/pyield/tn/ltn.py +4 -3
- {pyield-0.46.2 → pyield-0.46.4}/pyield/tn/ntnb.py +18 -9
- {pyield-0.46.2 → pyield-0.46.4}/pyield/tn/ntnc.py +3 -2
- {pyield-0.46.2 → pyield-0.46.4}/pyield/tn/ntnf.py +13 -15
- {pyield-0.46.2 → pyield-0.46.4}/pyield/tn/utils.py +1 -15
- pyield-0.46.2/pyield/__about__.py +0 -1
- pyield-0.46.2/pyield/anbima/tpf.py +0 -356
- pyield-0.46.2/pyield/b3/futures/historical.py +0 -247
- pyield-0.46.2/pyield/b3/futures/intraday.py +0 -173
- pyield-0.46.2/pyield/b3/intraday_derivatives.py +0 -132
- pyield-0.46.2/pyield/b3/price_report.py +0 -439
- {pyield-0.46.2 → pyield-0.46.4}/.gitignore +0 -0
- {pyield-0.46.2 → pyield-0.46.4}/LICENSE +0 -0
- {pyield-0.46.2 → pyield-0.46.4}/pyield/__init__.py +0 -0
- {pyield-0.46.2 → pyield-0.46.4}/pyield/_internal/__init__.py +0 -0
- {pyield-0.46.2 → pyield-0.46.4}/pyield/_internal/br_numbers.py +0 -0
- {pyield-0.46.2 → pyield-0.46.4}/pyield/_internal/cache.py +0 -0
- {pyield-0.46.2 → pyield-0.46.4}/pyield/_internal/converters.py +0 -0
- {pyield-0.46.2 → pyield-0.46.4}/pyield/_internal/retry.py +0 -0
- {pyield-0.46.2 → pyield-0.46.4}/pyield/_internal/types.py +0 -0
- {pyield-0.46.2 → pyield-0.46.4}/pyield/anbima/__init__.py +0 -0
- {pyield-0.46.2 → pyield-0.46.4}/pyield/anbima/difusao.py +0 -0
- {pyield-0.46.2 → pyield-0.46.4}/pyield/anbima/ettj_intraday.py +0 -0
- {pyield-0.46.2 → pyield-0.46.4}/pyield/anbima/ettj_last.py +0 -0
- {pyield-0.46.2 → pyield-0.46.4}/pyield/anbima/ima.py +0 -0
- {pyield-0.46.2 → pyield-0.46.4}/pyield/anbima/imaq.py +0 -0
- {pyield-0.46.2 → pyield-0.46.4}/pyield/b3/__init__.py +0 -0
- {pyield-0.46.2 → pyield-0.46.4}/pyield/b3/_contracts.py +0 -0
- {pyield-0.46.2 → pyield-0.46.4}/pyield/b3/di_over.py +0 -0
- {pyield-0.46.2 → pyield-0.46.4}/pyield/bc/__init__.py +0 -0
- {pyield-0.46.2 → pyield-0.46.4}/pyield/bc/auction.py +0 -0
- {pyield-0.46.2 → pyield-0.46.4}/pyield/bc/copom.py +0 -0
- {pyield-0.46.2 → pyield-0.46.4}/pyield/bc/ptax_api.py +0 -0
- {pyield-0.46.2 → pyield-0.46.4}/pyield/bc/rates.py +0 -0
- {pyield-0.46.2 → pyield-0.46.4}/pyield/bc/repo.py +0 -0
- {pyield-0.46.2 → pyield-0.46.4}/pyield/bc/trades_intraday.py +0 -0
- {pyield-0.46.2 → pyield-0.46.4}/pyield/bc/trades_monthly.py +0 -0
- {pyield-0.46.2 → pyield-0.46.4}/pyield/bc/vna.py +0 -0
- {pyield-0.46.2 → pyield-0.46.4}/pyield/bday/__init__.py +0 -0
- {pyield-0.46.2 → pyield-0.46.4}/pyield/bday/core.py +0 -0
- {pyield-0.46.2 → pyield-0.46.4}/pyield/bday/holidays/br_holidays_new.txt +0 -0
- {pyield-0.46.2 → pyield-0.46.4}/pyield/bday/holidays/br_holidays_old.txt +0 -0
- {pyield-0.46.2 → pyield-0.46.4}/pyield/bday/holidays/brholidays.py +0 -0
- {pyield-0.46.2 → pyield-0.46.4}/pyield/clock.py +0 -0
- {pyield-0.46.2 → pyield-0.46.4}/pyield/fwd.py +0 -0
- {pyield-0.46.2 → pyield-0.46.4}/pyield/interpolator.py +0 -0
- {pyield-0.46.2 → pyield-0.46.4}/pyield/ipca/__init__.py +0 -0
- {pyield-0.46.2 → pyield-0.46.4}/pyield/ipca/historical.py +0 -0
- {pyield-0.46.2 → pyield-0.46.4}/pyield/ipca/projected.py +0 -0
- {pyield-0.46.2 → pyield-0.46.4}/pyield/py.typed +0 -0
- {pyield-0.46.2 → pyield-0.46.4}/pyield/rmd.py +0 -0
- {pyield-0.46.2 → pyield-0.46.4}/pyield/selic/__init__.py +0 -0
- {pyield-0.46.2 → pyield-0.46.4}/pyield/tn/__init__.py +0 -0
- {pyield-0.46.2 → pyield-0.46.4}/pyield/tn/auctions.py +0 -0
- {pyield-0.46.2 → pyield-0.46.4}/pyield/tn/benchmark.py +0 -0
- {pyield-0.46.2 → pyield-0.46.4}/pyield/tn/ntnb1.py +0 -0
- {pyield-0.46.2 → pyield-0.46.4}/pyield/tn/ntnbprinc.py +0 -0
- {pyield-0.46.2 → pyield-0.46.4}/pyield/tn/pre.py +0 -0
- {pyield-0.46.2 → pyield-0.46.4}/pyproject.toml +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pyield
|
|
3
|
-
Version: 0.46.
|
|
3
|
+
Version: 0.46.4
|
|
4
4
|
Summary: A Python library for analysis of fixed income instruments in Brazil
|
|
5
5
|
Project-URL: Homepage, https://github.com/crdcj/PYield
|
|
6
6
|
Project-URL: Documentation, https://crdcj.github.io/PYield
|
|
@@ -71,10 +71,10 @@ yd.bday.offset("29-12-2023", 1) # -> datetime.date(2024, 1, 2)
|
|
|
71
71
|
|
|
72
72
|
# Curva de DI Futuro
|
|
73
73
|
df = yd.futures("31-05-2024", "DI1")
|
|
74
|
-
# Columns:
|
|
74
|
+
# Columns: data_referencia, codigo_negociacao, data_vencimento, dias_uteis, taxa_ajuste, ...
|
|
75
75
|
|
|
76
76
|
# Interpolação de taxas (flat forward, convenção 252 dias úteis/ano)
|
|
77
|
-
interp = yd.Interpolator("flat_forward", df["
|
|
77
|
+
interp = yd.Interpolator("flat_forward", df["dias_uteis"], df["taxa_ajuste"])
|
|
78
78
|
interp(45) # -> 0.04833...
|
|
79
79
|
interp([30, 60]) # -> pl.Series with interpolated rates
|
|
80
80
|
|
|
@@ -200,7 +200,7 @@ ntnb.quotation("31-05-2024", "15-08-2060", 0.061878) # -> 99.5341
|
|
|
200
200
|
|
|
201
201
|
# Spreads de DI (bps=True multiplica por 10.000)
|
|
202
202
|
ntnf.di_spreads("30-05-2025", bps=True)
|
|
203
|
-
# -> DataFrame:
|
|
203
|
+
# -> DataFrame: titulo, data_vencimento, spread_di
|
|
204
204
|
```
|
|
205
205
|
|
|
206
206
|
## Dados de Futuros
|
|
@@ -27,10 +27,10 @@ yd.bday.offset("29-12-2023", 1) # -> datetime.date(2024, 1, 2)
|
|
|
27
27
|
|
|
28
28
|
# Curva de DI Futuro
|
|
29
29
|
df = yd.futures("31-05-2024", "DI1")
|
|
30
|
-
# Columns:
|
|
30
|
+
# Columns: data_referencia, codigo_negociacao, data_vencimento, dias_uteis, taxa_ajuste, ...
|
|
31
31
|
|
|
32
32
|
# Interpolação de taxas (flat forward, convenção 252 dias úteis/ano)
|
|
33
|
-
interp = yd.Interpolator("flat_forward", df["
|
|
33
|
+
interp = yd.Interpolator("flat_forward", df["dias_uteis"], df["taxa_ajuste"])
|
|
34
34
|
interp(45) # -> 0.04833...
|
|
35
35
|
interp([30, 60]) # -> pl.Series with interpolated rates
|
|
36
36
|
|
|
@@ -156,7 +156,7 @@ ntnb.quotation("31-05-2024", "15-08-2060", 0.061878) # -> 99.5341
|
|
|
156
156
|
|
|
157
157
|
# Spreads de DI (bps=True multiplica por 10.000)
|
|
158
158
|
ntnf.di_spreads("30-05-2025", bps=True)
|
|
159
|
-
# -> DataFrame:
|
|
159
|
+
# -> DataFrame: titulo, data_vencimento, spread_di
|
|
160
160
|
```
|
|
161
161
|
|
|
162
162
|
## Dados de Futuros
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.46.4"
|
|
@@ -16,8 +16,8 @@ registro = logging.getLogger(__name__)
|
|
|
16
16
|
|
|
17
17
|
# Estrutura interna única — usuário não vê isso
|
|
18
18
|
class _Dataset(Enum):
|
|
19
|
-
TPF = ("anbima_tpf.parquet", "
|
|
20
|
-
|
|
19
|
+
TPF = ("anbima_tpf.parquet", "data_referencia", "TPF (ANBIMA)")
|
|
20
|
+
FUTURES = ("b3_futures.parquet", "data_referencia", "Futures (B3)")
|
|
21
21
|
|
|
22
22
|
def __init__(self, nome_arquivo: str, coluna_data: str, descricao: str):
|
|
23
23
|
self.nome_arquivo = nome_arquivo
|
|
@@ -25,7 +25,7 @@ class _Dataset(Enum):
|
|
|
25
25
|
self.descricao = descricao
|
|
26
26
|
|
|
27
27
|
|
|
28
|
-
type IdDataset = Literal["tpf", "
|
|
28
|
+
type IdDataset = Literal["tpf", "futures"]
|
|
29
29
|
|
|
30
30
|
|
|
31
31
|
def _obter_chave_data_hoje() -> str:
|
|
@@ -37,7 +37,7 @@ def _validar_id_dataset(id_dataset: str) -> _Dataset:
|
|
|
37
37
|
try:
|
|
38
38
|
return _Dataset[dataset_normalizado.upper()]
|
|
39
39
|
except KeyError as e:
|
|
40
|
-
msg = f"id_dataset inválido: '{id_dataset}'. Valores aceitos: 'tpf', '
|
|
40
|
+
msg = f"id_dataset inválido: '{id_dataset}'. Valores aceitos: 'tpf', 'futures'."
|
|
41
41
|
raise ValueError(msg) from e
|
|
42
42
|
|
|
43
43
|
|
|
@@ -81,7 +81,7 @@ def obter_dataset_cacheado(id_dataset: IdDataset) -> pl.DataFrame:
|
|
|
81
81
|
Obtém um dataset pelo ID. Cache expira diariamente.
|
|
82
82
|
|
|
83
83
|
Args:
|
|
84
|
-
id_dataset: "tpf" ou "
|
|
84
|
+
id_dataset: "tpf" ou "futures"
|
|
85
85
|
"""
|
|
86
86
|
df = _obter_dataset_com_ttl(id_dataset.lower(), _obter_chave_data_hoje())
|
|
87
87
|
return df.clone()
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
"""Dados do mercado secundário de Títulos Públicos Federais (TPF) da ANBIMA.
|
|
2
|
+
|
|
3
|
+
Exemplo de URL:
|
|
4
|
+
https://www.anbima.com.br/informacoes/merc-sec/arqs/ms240614.txt
|
|
5
|
+
|
|
6
|
+
Exemplo de dado bruto (CSV separado por @, encoding latin1):
|
|
7
|
+
ANBIMA - Associação Brasileira das Entidades dos Mercados Financeiro e de Capitais
|
|
8
|
+
|
|
9
|
+
Titulo@Data Referencia@Codigo SELIC@Data Base/Emissao@Data Vencimento@Tx. Compra@Tx. Venda@Tx. Indicativas@PU@Desvio padrao@Interv. Ind. Inf. (D0)@Interv. Ind. Sup. (D0)@Interv. Ind. Inf. (D+1)@Interv. Ind. Sup. (D+1)@Criterio
|
|
10
|
+
LTN@20250924@100000@20230707@20251001@14,9483@14,9263@14,9375@997,241543@0,00433039162894@14,7341@15,2612@14,7316@15,2689@Calculado
|
|
11
|
+
LTN@20250924@100000@20200206@20260101@14,7741@14,7485@14,7616@963,001853@0,00729826731971@14,7008@14,9986@14,7021@14,9975@Calculado
|
|
12
|
+
""" # noqa: E501
|
|
13
|
+
|
|
14
|
+
import datetime as dt
|
|
15
|
+
import logging
|
|
16
|
+
import socket
|
|
17
|
+
from typing import Literal
|
|
18
|
+
|
|
19
|
+
import polars as pl
|
|
20
|
+
import requests
|
|
21
|
+
|
|
22
|
+
from pyield import bday
|
|
23
|
+
from pyield._internal.br_numbers import float_br, taxa_br
|
|
24
|
+
from pyield._internal.converters import converter_datas, data_referencia_valida
|
|
25
|
+
from pyield._internal.data_cache import obter_dataset_cacheado
|
|
26
|
+
from pyield._internal.retry import retry_padrao
|
|
27
|
+
from pyield._internal.types import DateLike
|
|
28
|
+
|
|
29
|
+
BOND_TYPES = Literal["LFT", "NTN-B", "NTN-C", "LTN", "NTN-F", "PRE"]
|
|
30
|
+
|
|
31
|
+
ANBIMA_URL = "https://www.anbima.com.br/informacoes/merc-sec/arqs"
|
|
32
|
+
ANBIMA_RTM_HOSTNAME = "www.anbima.associados.rtm"
|
|
33
|
+
ANBIMA_RTM_URL = f"http://{ANBIMA_RTM_HOSTNAME}/merc_sec/arqs"
|
|
34
|
+
|
|
35
|
+
# Antes de 13/05/2014 o arquivo era zipado e o endpoint terminava com ".exe"
|
|
36
|
+
DATA_MUDANCA_FORMATO = dt.date(2014, 5, 13)
|
|
37
|
+
|
|
38
|
+
DIAS_RETENCAO_PUBLICA = 5
|
|
39
|
+
|
|
40
|
+
# Colunas selecionadas pela função pública tpf()
|
|
41
|
+
COLUNAS_TPF = (
|
|
42
|
+
"titulo",
|
|
43
|
+
"data_referencia",
|
|
44
|
+
"codigo_selic",
|
|
45
|
+
"data_base",
|
|
46
|
+
"data_vencimento",
|
|
47
|
+
"pu",
|
|
48
|
+
"taxa_compra",
|
|
49
|
+
"taxa_venda",
|
|
50
|
+
"taxa_indicativa",
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
logger = logging.getLogger(__name__)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _mapear_tipo_titulo(tipo_titulo: str) -> list[str]:
|
|
57
|
+
tipo_titulo = tipo_titulo.upper()
|
|
58
|
+
mapa_titulos = {
|
|
59
|
+
"PRE": ["LTN", "NTN-F"],
|
|
60
|
+
"NTNB": ["NTN-B"],
|
|
61
|
+
"NTNC": ["NTN-C"],
|
|
62
|
+
"NTNF": ["NTN-F"],
|
|
63
|
+
}
|
|
64
|
+
return mapa_titulos.get(tipo_titulo, [tipo_titulo])
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _montar_nome_arquivo(data: dt.date) -> str:
|
|
68
|
+
data_url = data.strftime("%y%m%d")
|
|
69
|
+
if data < DATA_MUDANCA_FORMATO:
|
|
70
|
+
nome_arquivo = f"ms{data_url}.exe"
|
|
71
|
+
else:
|
|
72
|
+
nome_arquivo = f"ms{data_url}.txt"
|
|
73
|
+
return nome_arquivo
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _montar_url_arquivo(data: dt.date) -> str:
|
|
77
|
+
ultimo_dia_util = bday.last_business_day()
|
|
78
|
+
qtd_dias_uteis = bday.count(data, ultimo_dia_util)
|
|
79
|
+
if qtd_dias_uteis > DIAS_RETENCAO_PUBLICA:
|
|
80
|
+
# Para datas com mais de 5 dias úteis, apenas os dados da RTM estão disponíveis
|
|
81
|
+
logger.info("Tentando buscar dados RTM para %s", data.strftime("%d/%m/%Y"))
|
|
82
|
+
url_arquivo = f"{ANBIMA_RTM_URL}/{_montar_nome_arquivo(data)}"
|
|
83
|
+
else:
|
|
84
|
+
url_arquivo = f"{ANBIMA_URL}/{_montar_nome_arquivo(data)}"
|
|
85
|
+
return url_arquivo
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
@retry_padrao
|
|
89
|
+
def _obter_csv(data: dt.date) -> bytes:
|
|
90
|
+
url_arquivo = _montar_url_arquivo(data)
|
|
91
|
+
resposta = requests.get(url_arquivo, timeout=10)
|
|
92
|
+
resposta.raise_for_status()
|
|
93
|
+
return resposta.content
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _parsear_df(csv_bytes: bytes) -> pl.DataFrame:
|
|
97
|
+
"""Converte bytes brutos do CSV da ANBIMA em DataFrame (tudo string)."""
|
|
98
|
+
return pl.read_csv(
|
|
99
|
+
source=csv_bytes,
|
|
100
|
+
skip_lines=2,
|
|
101
|
+
separator="@",
|
|
102
|
+
null_values=["--"],
|
|
103
|
+
infer_schema=False,
|
|
104
|
+
encoding="latin1",
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _processar_df(df: pl.DataFrame) -> pl.DataFrame:
|
|
109
|
+
"""Renomeia, converte tipos e define a ordem das colunas."""
|
|
110
|
+
return df.select(
|
|
111
|
+
titulo=pl.col("Titulo"),
|
|
112
|
+
data_referencia=pl.col("Data Referencia").str.to_date("%Y%m%d"),
|
|
113
|
+
codigo_selic=pl.col("Codigo SELIC").cast(pl.Int64),
|
|
114
|
+
data_base=pl.col("Data Base/Emissao").str.to_date("%Y%m%d"),
|
|
115
|
+
data_vencimento=pl.col("Data Vencimento").str.to_date("%Y%m%d"),
|
|
116
|
+
taxa_compra=taxa_br("Tx. Compra"),
|
|
117
|
+
taxa_venda=taxa_br("Tx. Venda"),
|
|
118
|
+
taxa_indicativa=taxa_br("Tx. Indicativas"),
|
|
119
|
+
pu=float_br("PU"),
|
|
120
|
+
desvio_padrao=float_br("Desvio padrao"),
|
|
121
|
+
taxa_intervalo_inf_d0=taxa_br("Interv. Ind. Inf. (D0)"),
|
|
122
|
+
taxa_intervalo_sup_d0=taxa_br("Interv. Ind. Sup. (D0)"),
|
|
123
|
+
taxa_intervalo_inf_d1=taxa_br("Interv. Ind. Inf. (D+1)"),
|
|
124
|
+
taxa_intervalo_sup_d1=taxa_br("Interv. Ind. Sup. (D+1)"),
|
|
125
|
+
criterio=pl.col("Criterio"),
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def _buscar_dados_tpf(data: dt.date) -> pl.DataFrame:
|
|
130
|
+
"""Busca e processa dados do mercado secundário de TPF na fonte ANBIMA.
|
|
131
|
+
|
|
132
|
+
Args:
|
|
133
|
+
data: Data de referência.
|
|
134
|
+
|
|
135
|
+
Returns:
|
|
136
|
+
DataFrame processado ou DataFrame vazio se indisponível.
|
|
137
|
+
"""
|
|
138
|
+
url_arquivo = _montar_url_arquivo(data)
|
|
139
|
+
|
|
140
|
+
# Fail-fast: se a URL é RTM e o host não resolve, não adianta tentar
|
|
141
|
+
if ANBIMA_RTM_URL in url_arquivo:
|
|
142
|
+
try:
|
|
143
|
+
socket.gethostbyname(ANBIMA_RTM_HOSTNAME)
|
|
144
|
+
except socket.gaierror:
|
|
145
|
+
data_str = data.strftime("%d/%m/%Y")
|
|
146
|
+
logger.warning(
|
|
147
|
+
f"Não foi possível resolver o host da RTM para {data_str}. "
|
|
148
|
+
"Dados históricos exigem acesso à rede RTM."
|
|
149
|
+
)
|
|
150
|
+
return pl.DataFrame()
|
|
151
|
+
|
|
152
|
+
csv_bytes = _obter_csv(data)
|
|
153
|
+
if not csv_bytes.strip():
|
|
154
|
+
return pl.DataFrame()
|
|
155
|
+
|
|
156
|
+
df = _parsear_df(csv_bytes)
|
|
157
|
+
return _processar_df(df)
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def tpf(
|
|
161
|
+
date: DateLike,
|
|
162
|
+
bond_type: BOND_TYPES | None = None,
|
|
163
|
+
) -> pl.DataFrame:
|
|
164
|
+
"""Recupera os dados do mercado secundário de TPF da ANBIMA.
|
|
165
|
+
|
|
166
|
+
Esta função busca taxas indicativas e outros dados de títulos públicos
|
|
167
|
+
brasileiros. Primeiro consulta o cache local; se não houver dados,
|
|
168
|
+
busca diretamente na fonte (ANBIMA).
|
|
169
|
+
|
|
170
|
+
Args:
|
|
171
|
+
date (DateLike): A data de referência para os dados (ex: '2024-06-14').
|
|
172
|
+
bond_type (str, optional): Filtra por tipo de título. Aceita os tipos
|
|
173
|
+
individuais ('LTN', 'NTN-F', 'NTN-B', 'NTN-C', 'LFT') ou 'PRE'
|
|
174
|
+
como atalho para prefixados ('LTN' e 'NTN-F').
|
|
175
|
+
|
|
176
|
+
Returns:
|
|
177
|
+
pl.DataFrame: Um DataFrame contendo os dados solicitados.
|
|
178
|
+
Retorna um DataFrame vazio se não houver dados para a data especificada (ex:
|
|
179
|
+
finais de semana, feriados ou datas futuras).
|
|
180
|
+
|
|
181
|
+
Examples:
|
|
182
|
+
>>> from pyield import anbima
|
|
183
|
+
>>> df = anbima.tpf(date="06-02-2026")
|
|
184
|
+
|
|
185
|
+
Output Columns:
|
|
186
|
+
* titulo (String): tipo do título público (ex: 'LTN', 'NTN-B').
|
|
187
|
+
* data_referencia (Date): data de referência dos dados.
|
|
188
|
+
* codigo_selic (Int64): código do título no SELIC.
|
|
189
|
+
* data_base (Date): data base ou de emissão do título.
|
|
190
|
+
* data_vencimento (Date): data de vencimento do título.
|
|
191
|
+
* pu (Float64): preço unitário (PU) para liquidação em D0.
|
|
192
|
+
* taxa_compra (Float64): taxa de compra em D0 (decimal).
|
|
193
|
+
* taxa_venda (Float64): taxa de venda em D0 (decimal).
|
|
194
|
+
* taxa_indicativa (Float64): taxa indicativa em D0 (decimal).
|
|
195
|
+
|
|
196
|
+
Notes:
|
|
197
|
+
A fonte dos dados segue a seguinte hierarquia:
|
|
198
|
+
|
|
199
|
+
1. **Cache Local:** Fornece acesso rápido a dados históricos
|
|
200
|
+
desde 01/01/2020.
|
|
201
|
+
2. **Fonte ANBIMA (fallback):** Se a data não estiver no cache,
|
|
202
|
+
busca automaticamente na fonte. Para datas recentes (até 5 dias
|
|
203
|
+
úteis), usa o site público. Para datas mais antigas, requer
|
|
204
|
+
acesso à rede RTM.
|
|
205
|
+
|
|
206
|
+
Para obter o dado completo direto da fonte (todas as colunas),
|
|
207
|
+
use :func:`fetch_tpf`.
|
|
208
|
+
"""
|
|
209
|
+
date = converter_datas(date)
|
|
210
|
+
|
|
211
|
+
if not data_referencia_valida(date):
|
|
212
|
+
return pl.DataFrame()
|
|
213
|
+
|
|
214
|
+
# Cache primeiro; se não tiver, busca na fonte
|
|
215
|
+
df = obter_dataset_cacheado("tpf")
|
|
216
|
+
if not df.is_empty():
|
|
217
|
+
df = df.filter(pl.col("data_referencia") == date)
|
|
218
|
+
if df.is_empty():
|
|
219
|
+
df = _buscar_dados_tpf(date)
|
|
220
|
+
if df.is_empty():
|
|
221
|
+
return pl.DataFrame()
|
|
222
|
+
|
|
223
|
+
df = df.select(col for col in COLUNAS_TPF if col in df.columns)
|
|
224
|
+
|
|
225
|
+
if bond_type:
|
|
226
|
+
tipos_titulo = _mapear_tipo_titulo(bond_type)
|
|
227
|
+
df = df.filter(pl.col("titulo").is_in(tipos_titulo))
|
|
228
|
+
|
|
229
|
+
return df.sort("data_referencia", "titulo", "data_vencimento")
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def fetch_tpf(
|
|
233
|
+
date: DateLike,
|
|
234
|
+
) -> pl.DataFrame:
|
|
235
|
+
"""Busca os dados do mercado secundário de TPF direto da fonte ANBIMA.
|
|
236
|
+
|
|
237
|
+
Retorna todas as colunas publicadas pela ANBIMA, sem cache e sem
|
|
238
|
+
filtro de colunas. Indicado para uso em jobs e pipelines de dados.
|
|
239
|
+
|
|
240
|
+
Args:
|
|
241
|
+
date (DateLike): Data de referência (ex: '2024-06-14').
|
|
242
|
+
|
|
243
|
+
Returns:
|
|
244
|
+
pl.DataFrame: DataFrame com todas as colunas da ANBIMA.
|
|
245
|
+
Retorna DataFrame vazio se não houver dados.
|
|
246
|
+
"""
|
|
247
|
+
date = converter_datas(date)
|
|
248
|
+
|
|
249
|
+
if not data_referencia_valida(date):
|
|
250
|
+
return pl.DataFrame()
|
|
251
|
+
|
|
252
|
+
df = _buscar_dados_tpf(date)
|
|
253
|
+
if df.is_empty():
|
|
254
|
+
return pl.DataFrame()
|
|
255
|
+
|
|
256
|
+
return df.sort("data_referencia", "titulo", "data_vencimento")
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def tpf_maturities(
|
|
260
|
+
date: DateLike,
|
|
261
|
+
bond_type: BOND_TYPES,
|
|
262
|
+
) -> pl.Series:
|
|
263
|
+
"""Recupera os vencimentos existentes para um tipo de título na data especificada.
|
|
264
|
+
|
|
265
|
+
Args:
|
|
266
|
+
date (DateLike): A data de referência para os vencimentos.
|
|
267
|
+
bond_type (BOND_TYPES): O tipo de título para filtrar (ex: 'PRE' para 'LTN'
|
|
268
|
+
e 'NTN-F', ou especifique 'LTN' ou 'NTN-F' diretamente).
|
|
269
|
+
|
|
270
|
+
Returns:
|
|
271
|
+
pl.Series: Uma Series contendo as datas de vencimento únicas para o(s)
|
|
272
|
+
tipo(s) de título especificado(s).
|
|
273
|
+
|
|
274
|
+
Examples:
|
|
275
|
+
>>> from pyield import anbima
|
|
276
|
+
>>> anbima.tpf_maturities(date="22-08-2025", bond_type="PRE")
|
|
277
|
+
shape: (18,)
|
|
278
|
+
Series: 'data_vencimento' [date]
|
|
279
|
+
[
|
|
280
|
+
2025-10-01
|
|
281
|
+
2026-01-01
|
|
282
|
+
2026-04-01
|
|
283
|
+
2026-07-01
|
|
284
|
+
2026-10-01
|
|
285
|
+
…
|
|
286
|
+
2030-01-01
|
|
287
|
+
2031-01-01
|
|
288
|
+
2032-01-01
|
|
289
|
+
2033-01-01
|
|
290
|
+
2035-01-01
|
|
291
|
+
]
|
|
292
|
+
"""
|
|
293
|
+
return tpf(date, bond_type)["data_vencimento"].unique().sort()
|
|
@@ -2,6 +2,10 @@ import datetime as dt
|
|
|
2
2
|
|
|
3
3
|
from pyield import bday, clock
|
|
4
4
|
|
|
5
|
+
# Pregão abre às 9:00, porém os dados intraday têm atraso de 15 minutos.
|
|
6
|
+
# Esperar 1 minuto adicional para garantir que estejam disponíveis (9:16h).
|
|
7
|
+
HORA_INICIO_INTRADAY = dt.time(9, 16)
|
|
8
|
+
|
|
5
9
|
|
|
6
10
|
def data_negociacao_valida(data_negociacao: dt.date) -> bool:
|
|
7
11
|
"""Valida se a data de referência é utilizável para consulta.
|
|
@@ -26,3 +30,15 @@ def data_negociacao_valida(data_negociacao: dt.date) -> bool:
|
|
|
26
30
|
return False
|
|
27
31
|
|
|
28
32
|
return True
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def intraday_disponivel() -> bool:
|
|
36
|
+
"""Verifica se dados intraday estão disponíveis agora.
|
|
37
|
+
|
|
38
|
+
Critérios:
|
|
39
|
+
- Hoje deve ser um dia de pregão válido.
|
|
40
|
+
- O horário atual deve ser após o início dos dados intraday (9:16h).
|
|
41
|
+
"""
|
|
42
|
+
if not data_negociacao_valida(clock.today()):
|
|
43
|
+
return False
|
|
44
|
+
return clock.now().time() >= HORA_INICIO_INTRADAY
|
|
@@ -36,12 +36,19 @@ def data(
|
|
|
36
36
|
Examples:
|
|
37
37
|
>>> from pyield import di1
|
|
38
38
|
>>> df = di1.data(dates="16-10-2024", month_start=True)
|
|
39
|
-
>>>
|
|
40
|
-
...
|
|
39
|
+
>>> df.head(3).select(
|
|
40
|
+
... "codigo_negociacao", "data_vencimento", "dias_uteis", "taxa_ajuste"
|
|
41
41
|
... )
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
42
|
+
shape: (3, 4)
|
|
43
|
+
┌───────────────────┬─────────────────┬────────────┬─────────────┐
|
|
44
|
+
│ codigo_negociacao ┆ data_vencimento ┆ dias_uteis ┆ taxa_ajuste │
|
|
45
|
+
│ --- ┆ --- ┆ --- ┆ --- │
|
|
46
|
+
│ str ┆ date ┆ i64 ┆ f64 │
|
|
47
|
+
╞═══════════════════╪═════════════════╪════════════╪═════════════╡
|
|
48
|
+
│ DI1X24 ┆ 2024-11-01 ┆ 12 ┆ 0.10653 │
|
|
49
|
+
│ DI1Z24 ┆ 2024-12-01 ┆ 31 ┆ 0.1091 │
|
|
50
|
+
│ DI1F25 ┆ 2025-01-01 ┆ 52 ┆ 0.11164 │
|
|
51
|
+
└───────────────────┴─────────────────┴────────────┴─────────────┘
|
|
45
52
|
|
|
46
53
|
"""
|
|
47
54
|
if any_is_empty(dates):
|
|
@@ -62,36 +69,32 @@ def data(
|
|
|
62
69
|
if pre_filter:
|
|
63
70
|
df_tpf = (
|
|
64
71
|
obter_dataset_cacheado("tpf")
|
|
65
|
-
.filter(pl.col("
|
|
66
|
-
.unique(subset=["
|
|
72
|
+
.filter(pl.col("titulo").is_in(["LTN", "NTN-F"]))
|
|
73
|
+
.unique(subset=["data_vencimento", "data_referencia"])
|
|
67
74
|
.select(
|
|
68
|
-
|
|
69
|
-
|
|
75
|
+
data_ref_tpf=pl.col("data_referencia"),
|
|
76
|
+
data_vencimento=bday.offset_expr("data_vencimento", 0),
|
|
70
77
|
)
|
|
71
|
-
.sort("
|
|
78
|
+
.sort("data_ref_tpf", "data_vencimento")
|
|
72
79
|
)
|
|
73
80
|
|
|
74
|
-
#
|
|
75
|
-
# exp_dates = bday.offset(df_tpf["ExpirationDate"], 0)
|
|
76
|
-
# df_tpf = df_tpf.with_columns(ExpirationDate=exp_dates)
|
|
77
|
-
|
|
78
|
-
# Mapear cada TradeDate do DI para a data TPF mais próxima
|
|
81
|
+
# Mapear cada data_referencia do DI para a data TPF mais próxima
|
|
79
82
|
df = df.join_asof(
|
|
80
|
-
df_tpf.select("
|
|
81
|
-
left_on="
|
|
82
|
-
right_on="
|
|
83
|
+
df_tpf.select("data_ref_tpf").unique().sort("data_ref_tpf"),
|
|
84
|
+
left_on="data_referencia",
|
|
85
|
+
right_on="data_ref_tpf",
|
|
83
86
|
strategy="backward",
|
|
84
87
|
)
|
|
85
88
|
|
|
86
89
|
# Filtrar apenas vencimentos que existem no TPF
|
|
87
|
-
df = df.join(df_tpf, on=["
|
|
88
|
-
"
|
|
90
|
+
df = df.join(df_tpf, on=["data_ref_tpf", "data_vencimento"], how="inner").drop(
|
|
91
|
+
"data_ref_tpf"
|
|
89
92
|
)
|
|
90
93
|
|
|
91
94
|
if month_start:
|
|
92
|
-
df = df.with_columns(pl.col("
|
|
95
|
+
df = df.with_columns(pl.col("data_vencimento").dt.truncate("1mo"))
|
|
93
96
|
|
|
94
|
-
return df.sort("
|
|
97
|
+
return df.sort("data_referencia", "data_vencimento")
|
|
95
98
|
|
|
96
99
|
|
|
97
100
|
def interpolate_rates(
|
|
@@ -145,7 +148,7 @@ def interpolate_rates(
|
|
|
145
148
|
... expirations=["01-01-2027", "25-11-2027", "01-01-2030"],
|
|
146
149
|
... )
|
|
147
150
|
shape: (3,)
|
|
148
|
-
Series: '
|
|
151
|
+
Series: 'taxa_interpolada' [f64]
|
|
149
152
|
[
|
|
150
153
|
0.13972
|
|
151
154
|
0.134613
|
|
@@ -159,7 +162,7 @@ def interpolate_rates(
|
|
|
159
162
|
... extrapolate=True,
|
|
160
163
|
... )
|
|
161
164
|
shape: (2,)
|
|
162
|
-
Series: '
|
|
165
|
+
Series: 'taxa_interpolada' [f64]
|
|
163
166
|
[
|
|
164
167
|
0.13901
|
|
165
168
|
0.13881
|
|
@@ -172,7 +175,7 @@ def interpolate_rates(
|
|
|
172
175
|
... extrapolate=False,
|
|
173
176
|
... )
|
|
174
177
|
shape: (2,)
|
|
175
|
-
Series: '
|
|
178
|
+
Series: 'taxa_interpolada' [f64]
|
|
176
179
|
[
|
|
177
180
|
0.135763
|
|
178
181
|
null
|
|
@@ -182,16 +185,16 @@ def interpolate_rates(
|
|
|
182
185
|
return pl.Series(dtype=pl.Float64)
|
|
183
186
|
|
|
184
187
|
df_entrada = pl.DataFrame(
|
|
185
|
-
data={"
|
|
188
|
+
data={"data_referencia": dates, "data_vencimento": expirations}
|
|
186
189
|
).with_columns(
|
|
187
|
-
|
|
188
|
-
|
|
190
|
+
data_referencia=cv.converter_datas_expr("data_referencia"),
|
|
191
|
+
data_vencimento=cv.converter_datas_expr("data_vencimento"),
|
|
189
192
|
)
|
|
190
193
|
if df_entrada.is_empty():
|
|
191
194
|
return pl.Series(dtype=pl.Float64)
|
|
192
195
|
|
|
193
196
|
# Carrega dataset de taxas DI usando datas já convertidas do df_entrada
|
|
194
|
-
datas_unicas = df_entrada["
|
|
197
|
+
datas_unicas = df_entrada["data_referencia"].drop_nulls().unique().sort().to_list()
|
|
195
198
|
df_ref = b3.futures(date=datas_unicas, contract_code="DI1", full_report=False)
|
|
196
199
|
# Retorna Series vazia se nenhuma taxa for encontrada
|
|
197
200
|
if df_ref.is_empty():
|
|
@@ -201,22 +204,22 @@ def interpolate_rates(
|
|
|
201
204
|
# Isso garante que saberemos a ordem exata depois
|
|
202
205
|
df_entrada = df_entrada.with_row_index("_temp_idx")
|
|
203
206
|
|
|
204
|
-
# Inicializa
|
|
207
|
+
# Inicializa taxa_interpolada como None
|
|
205
208
|
df_entrada = df_entrada.with_columns(
|
|
206
|
-
|
|
207
|
-
|
|
209
|
+
dias_uteis=bday.count_expr("data_referencia", "data_vencimento"),
|
|
210
|
+
taxa_interpolada=None,
|
|
208
211
|
)
|
|
209
212
|
|
|
210
213
|
# Lista para armazenar os blocos processados
|
|
211
214
|
blocos_processados = []
|
|
212
215
|
|
|
213
216
|
# Itera sobre cada data de referência única
|
|
214
|
-
for data_ref in df_entrada["
|
|
217
|
+
for data_ref in df_entrada["data_referencia"].unique():
|
|
215
218
|
# 1. Filtra apenas as linhas desta data (Particionamento)
|
|
216
|
-
df_parcial = df_entrada.filter(pl.col("
|
|
219
|
+
df_parcial = df_entrada.filter(pl.col("data_referencia") == data_ref)
|
|
217
220
|
|
|
218
221
|
# 2. Busca as taxas de referência para esta data
|
|
219
|
-
df_referencia = df_ref.filter(pl.col("
|
|
222
|
+
df_referencia = df_ref.filter(pl.col("data_referencia") == data_ref)
|
|
220
223
|
|
|
221
224
|
# Se não houver dados de curva, adicionamos o bloco como está (com nulos)
|
|
222
225
|
# e continuamos.
|
|
@@ -227,17 +230,17 @@ def interpolate_rates(
|
|
|
227
230
|
# Inicializa o interpolador com taxas e dias úteis conhecidos
|
|
228
231
|
interpolador = interpolator.Interpolator(
|
|
229
232
|
method="flat_forward",
|
|
230
|
-
known_bdays=df_referencia["
|
|
231
|
-
known_rates=df_referencia["
|
|
233
|
+
known_bdays=df_referencia["dias_uteis"],
|
|
234
|
+
known_rates=df_referencia["taxa_ajuste"],
|
|
232
235
|
extrapolate=extrapolate,
|
|
233
236
|
)
|
|
234
237
|
|
|
235
238
|
# 4. A mágica: map_batches passa a Series inteira para o interpolador
|
|
236
239
|
# O interpolador retorna uma Series, que o Polars alinha perfeitamente
|
|
237
240
|
df_parcial = df_parcial.with_columns(
|
|
238
|
-
pl.col("
|
|
241
|
+
pl.col("dias_uteis")
|
|
239
242
|
.map_batches(interpolador) # Passa Series -> Recebe Series
|
|
240
|
-
.alias("
|
|
243
|
+
.alias("taxa_interpolada")
|
|
241
244
|
)
|
|
242
245
|
|
|
243
246
|
blocos_processados.append(df_parcial)
|
|
@@ -249,7 +252,7 @@ def interpolate_rates(
|
|
|
249
252
|
# O sort("_temp_idx") restaura a ordem original dos inputs
|
|
250
253
|
df_saida = pl.concat(blocos_processados).sort("_temp_idx")
|
|
251
254
|
|
|
252
|
-
return df_saida["
|
|
255
|
+
return df_saida["taxa_interpolada"].fill_nan(None)
|
|
253
256
|
|
|
254
257
|
|
|
255
258
|
def interpolate_rate(
|
|
@@ -315,9 +318,10 @@ def interpolate_rate(
|
|
|
315
318
|
def available_dates() -> pl.Series:
|
|
316
319
|
"""Retorna as datas de negociação disponíveis para DI1.
|
|
317
320
|
|
|
318
|
-
Obtém valores distintos de '
|
|
319
|
-
PR da B3 (mesmo utilizado por `futures`). Inclui apenas datas com
|
|
320
|
-
de ajuste já definidos; o pregão do dia corrente não está
|
|
321
|
+
Obtém valores distintos de 'data_referencia' para DI1, com base no dataset
|
|
322
|
+
histórico PR da B3 (mesmo utilizado por `futures`). Inclui apenas datas com
|
|
323
|
+
preço e taxa de ajuste já definidos; o pregão do dia corrente não está
|
|
324
|
+
incluído.
|
|
321
325
|
|
|
322
326
|
Returns:
|
|
323
327
|
Series ordenada de datas de negociação (dt.date) para as quais dados de
|
|
@@ -328,7 +332,7 @@ def available_dates() -> pl.Series:
|
|
|
328
332
|
>>> # Série disponível no dataset PR começa em 2018-01-02
|
|
329
333
|
>>> di1.available_dates().head(5)
|
|
330
334
|
shape: (5,)
|
|
331
|
-
Series: '
|
|
335
|
+
Series: 'data_referencia' [date]
|
|
332
336
|
[
|
|
333
337
|
2018-01-02
|
|
334
338
|
2018-01-03
|
|
@@ -7,7 +7,7 @@ import pyield._internal.converters as cv
|
|
|
7
7
|
from pyield import clock
|
|
8
8
|
from pyield._internal.types import ArrayLike, DateLike, any_is_empty, is_collection
|
|
9
9
|
from pyield.b3._contracts import normalizar_codigos_contrato
|
|
10
|
-
from pyield.b3._validar_pregao import data_negociacao_valida
|
|
10
|
+
from pyield.b3._validar_pregao import HORA_INICIO_INTRADAY, data_negociacao_valida
|
|
11
11
|
from pyield.b3.futures import historical, intraday
|
|
12
12
|
|
|
13
13
|
# A partir desse horário, os dados consolidados (SPR/PR) já estão disponíveis.
|
|
@@ -58,7 +58,7 @@ def futures(
|
|
|
58
58
|
Lista de datas:
|
|
59
59
|
|
|
60
60
|
>>> df = futures(["29-05-2024", "31-05-2024"], "DI1")
|
|
61
|
-
>>> df["
|
|
61
|
+
>>> df["data_referencia"].unique().sort().to_list()
|
|
62
62
|
[datetime.date(2024, 5, 29), datetime.date(2024, 5, 31)]
|
|
63
63
|
|
|
64
64
|
Véspera de Natal e Ano Novo não têm pregão:
|
|
@@ -125,7 +125,7 @@ def _buscar_varias_datas(
|
|
|
125
125
|
# Identifica datas que não vieram do cache
|
|
126
126
|
datas_no_cache: set[dt.date] = set()
|
|
127
127
|
for df in resultados:
|
|
128
|
-
datas_no_cache.update(df["
|
|
128
|
+
datas_no_cache.update(df["data_referencia"].unique().to_list())
|
|
129
129
|
datas_faltantes = [d for d in datas_validas if d not in datas_no_cache]
|
|
130
130
|
|
|
131
131
|
# Fallback individual apenas para datas faltantes
|
|
@@ -156,7 +156,7 @@ def _buscar_por_fonte(
|
|
|
156
156
|
return df
|
|
157
157
|
return intraday.intraday(codigos)
|
|
158
158
|
|
|
159
|
-
if horario >=
|
|
159
|
+
if horario >= HORA_INICIO_INTRADAY:
|
|
160
160
|
# Intraday primeiro; historical como fallback
|
|
161
161
|
df = intraday.intraday(codigos)
|
|
162
162
|
if not df.is_empty():
|
|
@@ -178,7 +178,7 @@ def available_dates(contract_code: str) -> pl.Series:
|
|
|
178
178
|
>>> from pyield.b3.futures import available_dates
|
|
179
179
|
>>> available_dates("DI1").head(3)
|
|
180
180
|
shape: (3,)
|
|
181
|
-
Series: '
|
|
181
|
+
Series: 'data_referencia' [date]
|
|
182
182
|
[
|
|
183
183
|
2018-01-02
|
|
184
184
|
2018-01-03
|