PYield 0.46.1__tar.gz → 0.46.3__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.1 → pyield-0.46.3}/PKG-INFO +12 -1
- {pyield-0.46.1 → pyield-0.46.3}/README.md +11 -0
- pyield-0.46.3/pyield/__about__.py +1 -0
- {pyield-0.46.1 → pyield-0.46.3}/pyield/_internal/data_cache.py +1 -1
- pyield-0.46.3/pyield/anbima/tpf.py +293 -0
- {pyield-0.46.1 → pyield-0.46.3}/pyield/b3/_validar_pregao.py +4 -12
- {pyield-0.46.1 → pyield-0.46.3}/pyield/b3/di1.py +4 -4
- {pyield-0.46.1 → pyield-0.46.3}/pyield/bc/ptax_api.py +26 -24
- {pyield-0.46.1 → pyield-0.46.3}/pyield/bc/trades_monthly.py +3 -0
- {pyield-0.46.1 → pyield-0.46.3}/pyield/fwd.py +1 -1
- {pyield-0.46.1 → pyield-0.46.3}/pyield/tn/lft.py +35 -36
- {pyield-0.46.1 → pyield-0.46.3}/pyield/tn/ltn.py +95 -94
- {pyield-0.46.1 → pyield-0.46.3}/pyield/tn/ntnb.py +210 -205
- {pyield-0.46.1 → pyield-0.46.3}/pyield/tn/ntnb1.py +30 -30
- {pyield-0.46.1 → pyield-0.46.3}/pyield/tn/ntnc.py +72 -67
- {pyield-0.46.1 → pyield-0.46.3}/pyield/tn/ntnf.py +186 -181
- pyield-0.46.3/pyield/tn/pre.py +203 -0
- {pyield-0.46.1 → pyield-0.46.3}/pyield/tn/utils.py +38 -13
- pyield-0.46.1/pyield/__about__.py +0 -1
- pyield-0.46.1/pyield/anbima/tpf.py +0 -356
- pyield-0.46.1/pyield/tn/pre.py +0 -198
- {pyield-0.46.1 → pyield-0.46.3}/.gitignore +0 -0
- {pyield-0.46.1 → pyield-0.46.3}/LICENSE +0 -0
- {pyield-0.46.1 → pyield-0.46.3}/pyield/__init__.py +0 -0
- {pyield-0.46.1 → pyield-0.46.3}/pyield/_internal/__init__.py +0 -0
- {pyield-0.46.1 → pyield-0.46.3}/pyield/_internal/br_numbers.py +0 -0
- {pyield-0.46.1 → pyield-0.46.3}/pyield/_internal/cache.py +0 -0
- {pyield-0.46.1 → pyield-0.46.3}/pyield/_internal/converters.py +0 -0
- {pyield-0.46.1 → pyield-0.46.3}/pyield/_internal/retry.py +0 -0
- {pyield-0.46.1 → pyield-0.46.3}/pyield/_internal/types.py +0 -0
- {pyield-0.46.1 → pyield-0.46.3}/pyield/anbima/__init__.py +0 -0
- {pyield-0.46.1 → pyield-0.46.3}/pyield/anbima/difusao.py +0 -0
- {pyield-0.46.1 → pyield-0.46.3}/pyield/anbima/ettj_intraday.py +0 -0
- {pyield-0.46.1 → pyield-0.46.3}/pyield/anbima/ettj_last.py +0 -0
- {pyield-0.46.1 → pyield-0.46.3}/pyield/anbima/ima.py +0 -0
- {pyield-0.46.1 → pyield-0.46.3}/pyield/anbima/imaq.py +0 -0
- {pyield-0.46.1 → pyield-0.46.3}/pyield/b3/__init__.py +0 -0
- {pyield-0.46.1 → pyield-0.46.3}/pyield/b3/_contracts.py +0 -0
- {pyield-0.46.1 → pyield-0.46.3}/pyield/b3/di_over.py +0 -0
- {pyield-0.46.1 → pyield-0.46.3}/pyield/b3/futures/__init__.py +0 -0
- {pyield-0.46.1 → pyield-0.46.3}/pyield/b3/futures/common.py +0 -0
- {pyield-0.46.1 → pyield-0.46.3}/pyield/b3/futures/historical.py +0 -0
- {pyield-0.46.1 → pyield-0.46.3}/pyield/b3/futures/intraday.py +0 -0
- {pyield-0.46.1 → pyield-0.46.3}/pyield/b3/intraday_derivatives.py +0 -0
- {pyield-0.46.1 → pyield-0.46.3}/pyield/b3/price_report.py +0 -0
- {pyield-0.46.1 → pyield-0.46.3}/pyield/bc/__init__.py +0 -0
- {pyield-0.46.1 → pyield-0.46.3}/pyield/bc/auction.py +0 -0
- {pyield-0.46.1 → pyield-0.46.3}/pyield/bc/copom.py +0 -0
- {pyield-0.46.1 → pyield-0.46.3}/pyield/bc/rates.py +0 -0
- {pyield-0.46.1 → pyield-0.46.3}/pyield/bc/repo.py +0 -0
- {pyield-0.46.1 → pyield-0.46.3}/pyield/bc/trades_intraday.py +0 -0
- {pyield-0.46.1 → pyield-0.46.3}/pyield/bc/vna.py +0 -0
- {pyield-0.46.1 → pyield-0.46.3}/pyield/bday/__init__.py +0 -0
- {pyield-0.46.1 → pyield-0.46.3}/pyield/bday/core.py +0 -0
- {pyield-0.46.1 → pyield-0.46.3}/pyield/bday/holidays/br_holidays_new.txt +0 -0
- {pyield-0.46.1 → pyield-0.46.3}/pyield/bday/holidays/br_holidays_old.txt +0 -0
- {pyield-0.46.1 → pyield-0.46.3}/pyield/bday/holidays/brholidays.py +0 -0
- {pyield-0.46.1 → pyield-0.46.3}/pyield/clock.py +0 -0
- {pyield-0.46.1 → pyield-0.46.3}/pyield/interpolator.py +0 -0
- {pyield-0.46.1 → pyield-0.46.3}/pyield/ipca/__init__.py +0 -0
- {pyield-0.46.1 → pyield-0.46.3}/pyield/ipca/historical.py +0 -0
- {pyield-0.46.1 → pyield-0.46.3}/pyield/ipca/projected.py +0 -0
- {pyield-0.46.1 → pyield-0.46.3}/pyield/py.typed +0 -0
- {pyield-0.46.1 → pyield-0.46.3}/pyield/rmd.py +0 -0
- {pyield-0.46.1 → pyield-0.46.3}/pyield/selic/__init__.py +0 -0
- {pyield-0.46.1 → pyield-0.46.3}/pyield/selic/cpm.py +0 -0
- {pyield-0.46.1 → pyield-0.46.3}/pyield/selic/probabilities.py +0 -0
- {pyield-0.46.1 → pyield-0.46.3}/pyield/tn/__init__.py +0 -0
- {pyield-0.46.1 → pyield-0.46.3}/pyield/tn/auctions.py +0 -0
- {pyield-0.46.1 → pyield-0.46.3}/pyield/tn/benchmark.py +0 -0
- {pyield-0.46.1 → pyield-0.46.3}/pyield/tn/ntnbprinc.py +0 -0
- {pyield-0.46.1 → pyield-0.46.3}/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.3
|
|
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
|
|
@@ -247,6 +247,17 @@ ntnb.quotation(None, "15-05-2035", 0.06149) # -> nan
|
|
|
247
247
|
bday.count(["01-01-2024", None], "01-02-2024") # -> Series: [22, null]
|
|
248
248
|
```
|
|
249
249
|
|
|
250
|
+
Consultas sem dados disponíveis (data futura, feriado, fim de semana ou
|
|
251
|
+
fonte indisponível) retornam DataFrame vazio ou `nan`, sem lançar exceção:
|
|
252
|
+
|
|
253
|
+
```python
|
|
254
|
+
from pyield import bc, futures
|
|
255
|
+
|
|
256
|
+
futures("01-01-2030", "DI1").is_empty() # -> True (data futura)
|
|
257
|
+
bc.tpf_monthly_trades("01-01-2030").is_empty() # -> True (mês futuro)
|
|
258
|
+
bc.ptax("25-12-2025") # -> nan (feriado)
|
|
259
|
+
```
|
|
260
|
+
|
|
250
261
|
## Migração para Polars (v0.40.0+)
|
|
251
262
|
|
|
252
263
|
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:
|
|
@@ -203,6 +203,17 @@ ntnb.quotation(None, "15-05-2035", 0.06149) # -> nan
|
|
|
203
203
|
bday.count(["01-01-2024", None], "01-02-2024") # -> Series: [22, null]
|
|
204
204
|
```
|
|
205
205
|
|
|
206
|
+
Consultas sem dados disponíveis (data futura, feriado, fim de semana ou
|
|
207
|
+
fonte indisponível) retornam DataFrame vazio ou `nan`, sem lançar exceção:
|
|
208
|
+
|
|
209
|
+
```python
|
|
210
|
+
from pyield import bc, futures
|
|
211
|
+
|
|
212
|
+
futures("01-01-2030", "DI1").is_empty() # -> True (data futura)
|
|
213
|
+
bc.tpf_monthly_trades("01-01-2030").is_empty() # -> True (mês futuro)
|
|
214
|
+
bc.ptax("25-12-2025") # -> nan (feriado)
|
|
215
|
+
```
|
|
216
|
+
|
|
206
217
|
## Migração para Polars (v0.40.0+)
|
|
207
218
|
|
|
208
219
|
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:
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.46.3"
|
|
@@ -16,7 +16,7 @@ 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", "
|
|
19
|
+
TPF = ("anbima_tpf.parquet", "data_referencia", "TPF (ANBIMA)")
|
|
20
20
|
PR = ("b3_price_report.parquet", "TradeDate", "Price Report (B3)")
|
|
21
21
|
|
|
22
22
|
def __init__(self, nome_arquivo: str, coluna_data: str, descricao: str):
|
|
@@ -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()
|
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
import datetime as dt
|
|
2
|
-
import logging
|
|
3
2
|
|
|
4
3
|
from pyield import bday, clock
|
|
5
4
|
|
|
6
|
-
registro = logging.getLogger(__name__)
|
|
7
|
-
|
|
8
5
|
|
|
9
6
|
def data_negociacao_valida(data_negociacao: dt.date) -> bool:
|
|
10
7
|
"""Valida se a data de referência é utilizável para consulta.
|
|
@@ -13,24 +10,19 @@ def data_negociacao_valida(data_negociacao: dt.date) -> bool:
|
|
|
13
10
|
- Deve ser um dia útil brasileiro.
|
|
14
11
|
- Não pode estar no futuro (maior que a data corrente no Brasil).
|
|
15
12
|
|
|
16
|
-
Retorna True se válida, False caso contrário
|
|
13
|
+
Retorna True se válida, False caso contrário.
|
|
17
14
|
"""
|
|
18
15
|
if data_negociacao > clock.today():
|
|
19
|
-
registro.warning(f"A data informada {data_negociacao} está no futuro.")
|
|
20
16
|
return False
|
|
21
17
|
if not bday.is_business_day(data_negociacao):
|
|
22
|
-
registro.warning(f"A data informada {data_negociacao} não é dia útil.")
|
|
23
18
|
return False
|
|
24
19
|
|
|
25
20
|
# Não tem pregão na véspera de Natal e Ano Novo
|
|
26
|
-
datas_fechadas_especiais = {
|
|
27
|
-
dt.date(data_negociacao.year, 12, 24),
|
|
28
|
-
dt.date(data_negociacao.year, 12, 31),
|
|
21
|
+
datas_fechadas_especiais = {
|
|
22
|
+
dt.date(data_negociacao.year, 12, 24),
|
|
23
|
+
dt.date(data_negociacao.year, 12, 31),
|
|
29
24
|
}
|
|
30
25
|
if data_negociacao in datas_fechadas_especiais:
|
|
31
|
-
registro.warning(
|
|
32
|
-
f"Não há pregão na véspera de Natal e de Ano Novo: {data_negociacao}"
|
|
33
|
-
)
|
|
34
26
|
return False
|
|
35
27
|
|
|
36
28
|
return True
|
|
@@ -62,11 +62,11 @@ def data(
|
|
|
62
62
|
if pre_filter:
|
|
63
63
|
df_tpf = (
|
|
64
64
|
obter_dataset_cacheado("tpf")
|
|
65
|
-
.filter(pl.col("
|
|
66
|
-
.unique(subset=["
|
|
65
|
+
.filter(pl.col("titulo").is_in(["LTN", "NTN-F"]))
|
|
66
|
+
.unique(subset=["data_vencimento", "data_referencia"])
|
|
67
67
|
.select(
|
|
68
|
-
TradeDate_tpf=pl.col("
|
|
69
|
-
ExpirationDate=bday.offset_expr("
|
|
68
|
+
TradeDate_tpf=pl.col("data_referencia"),
|
|
69
|
+
ExpirationDate=bday.offset_expr("data_vencimento", 0),
|
|
70
70
|
)
|
|
71
71
|
.sort("TradeDate_tpf", "ExpirationDate")
|
|
72
72
|
)
|
|
@@ -69,13 +69,15 @@ def _processar_df(df: pl.DataFrame) -> pl.DataFrame:
|
|
|
69
69
|
)
|
|
70
70
|
.with_columns(
|
|
71
71
|
data=pl.col("data_hora").cast(pl.Date),
|
|
72
|
+
hora=pl.col("data_hora").dt.time(),
|
|
72
73
|
cotacao_media=pl.mean_horizontal("cotacao_compra", "cotacao_venda").round(
|
|
73
74
|
5
|
|
74
75
|
),
|
|
75
76
|
)
|
|
76
|
-
.unique(subset=["data"], keep="last")
|
|
77
|
-
.select("data", "data_hora", "cotacao_compra", "cotacao_venda", "cotacao_media")
|
|
78
77
|
.sort("data_hora")
|
|
78
|
+
.unique(subset=["data"], keep="last")
|
|
79
|
+
.select("data", "hora", "cotacao_compra", "cotacao_venda", "cotacao_media")
|
|
80
|
+
.sort("data")
|
|
79
81
|
)
|
|
80
82
|
|
|
81
83
|
|
|
@@ -100,7 +102,7 @@ def ptax_series(
|
|
|
100
102
|
|
|
101
103
|
Output Columns:
|
|
102
104
|
* data (Date): data da cotação.
|
|
103
|
-
*
|
|
105
|
+
* hora (Time): hora da cotação.
|
|
104
106
|
* cotacao_compra (Float64): taxa de compra em R$.
|
|
105
107
|
* cotacao_venda (Float64): taxa de venda em R$.
|
|
106
108
|
* cotacao_media (Float64): média de compra/venda (5 casas).
|
|
@@ -122,30 +124,30 @@ def ptax_series(
|
|
|
122
124
|
>>> from pyield import bc
|
|
123
125
|
>>> bc.ptax_series(start="20-04-2025", end="25-04-2025")
|
|
124
126
|
shape: (4, 5)
|
|
125
|
-
|
|
126
|
-
│ data ┆
|
|
127
|
-
│ --- ┆ ---
|
|
128
|
-
│ date ┆
|
|
129
|
-
|
|
130
|
-
│ 2025-04-22 ┆
|
|
131
|
-
│ 2025-04-23 ┆
|
|
132
|
-
│ 2025-04-24 ┆
|
|
133
|
-
│ 2025-04-25 ┆
|
|
134
|
-
|
|
127
|
+
┌────────────┬──────────────┬────────────────┬───────────────┬───────────────┐
|
|
128
|
+
│ data ┆ hora ┆ cotacao_compra ┆ cotacao_venda ┆ cotacao_media │
|
|
129
|
+
│ --- ┆ --- ┆ --- ┆ --- ┆ --- │
|
|
130
|
+
│ date ┆ time ┆ f64 ┆ f64 ┆ f64 │
|
|
131
|
+
╞════════════╪══════════════╪════════════════╪═══════════════╪═══════════════╡
|
|
132
|
+
│ 2025-04-22 ┆ 13:09:35.629 ┆ 5.749 ┆ 5.7496 ┆ 5.7493 │
|
|
133
|
+
│ 2025-04-23 ┆ 13:06:30.443 ┆ 5.6874 ┆ 5.688 ┆ 5.6877 │
|
|
134
|
+
│ 2025-04-24 ┆ 13:04:29.639 ┆ 5.6732 ┆ 5.6738 ┆ 5.6735 │
|
|
135
|
+
│ 2025-04-25 ┆ 13:09:26.592 ┆ 5.684 ┆ 5.6846 ┆ 5.6843 │
|
|
136
|
+
└────────────┴──────────────┴────────────────┴───────────────┴───────────────┘
|
|
135
137
|
|
|
136
138
|
>>> bc.ptax_series(start="02-01-1995", end="06-01-1995")
|
|
137
139
|
shape: (5, 5)
|
|
138
|
-
|
|
139
|
-
│ data ┆
|
|
140
|
-
│ --- ┆ ---
|
|
141
|
-
│ date ┆
|
|
142
|
-
|
|
143
|
-
│ 1995-01-02 ┆
|
|
144
|
-
│ 1995-01-03 ┆
|
|
145
|
-
│ 1995-01-04 ┆
|
|
146
|
-
│ 1995-01-05 ┆
|
|
147
|
-
│ 1995-01-06 ┆
|
|
148
|
-
|
|
140
|
+
┌────────────┬──────────┬────────────────┬───────────────┬───────────────┐
|
|
141
|
+
│ data ┆ hora ┆ cotacao_compra ┆ cotacao_venda ┆ cotacao_media │
|
|
142
|
+
│ --- ┆ --- ┆ --- ┆ --- ┆ --- │
|
|
143
|
+
│ date ┆ time ┆ f64 ┆ f64 ┆ f64 │
|
|
144
|
+
╞════════════╪══════════╪════════════════╪═══════════════╪═══════════════╡
|
|
145
|
+
│ 1995-01-02 ┆ 18:20:00 ┆ 0.843 ┆ 0.845 ┆ 0.844 │
|
|
146
|
+
│ 1995-01-03 ┆ 18:25:00 ┆ 0.844 ┆ 0.846 ┆ 0.845 │
|
|
147
|
+
│ 1995-01-04 ┆ 18:12:00 ┆ 0.844 ┆ 0.846 ┆ 0.845 │
|
|
148
|
+
│ 1995-01-05 ┆ 18:07:00 ┆ 0.842 ┆ 0.844 ┆ 0.843 │
|
|
149
|
+
│ 1995-01-06 ┆ 18:12:00 ┆ 0.839 ┆ 0.841 ┆ 0.84 │
|
|
150
|
+
└────────────┴──────────┴────────────────┴───────────────┴───────────────┘
|
|
149
151
|
"""
|
|
150
152
|
if start:
|
|
151
153
|
start = cv.converter_datas(start)
|
|
@@ -23,6 +23,7 @@ from pyield._internal.cache import ttl_cache
|
|
|
23
23
|
from pyield._internal.converters import converter_datas
|
|
24
24
|
from pyield._internal.retry import retry_padrao
|
|
25
25
|
from pyield._internal.types import DateLike, any_is_empty
|
|
26
|
+
from pyield.clock import today
|
|
26
27
|
|
|
27
28
|
URL_BASE = "https://www4.bcb.gov.br/pom/demab/negociacoes/download"
|
|
28
29
|
|
|
@@ -148,6 +149,8 @@ def tpf_monthly_trades(target_date: DateLike, extragroup: bool = False) -> pl.Da
|
|
|
148
149
|
if any_is_empty(target_date):
|
|
149
150
|
return pl.DataFrame()
|
|
150
151
|
data_alvo = converter_datas(target_date)
|
|
152
|
+
if (data_alvo.year, data_alvo.month) > (today().year, today().month):
|
|
153
|
+
return pl.DataFrame()
|
|
151
154
|
url = _montar_url(data_alvo, extragroup)
|
|
152
155
|
conteudo_zip = _baixar_zip(url)
|
|
153
156
|
arquivo_extraido = _descompactar_zip(conteudo_zip)
|
|
@@ -103,7 +103,7 @@ def forwards(
|
|
|
103
103
|
>>> # Exemplo com taxas indicativas de NTN-B em 16-09-2025
|
|
104
104
|
>>> from pyield import ntnb
|
|
105
105
|
>>> df = ntnb.data("16-09-2025")
|
|
106
|
-
>>> yd.forwards(df["
|
|
106
|
+
>>> yd.forwards(df["dias_uteis"], df["taxa_indicativa"])
|
|
107
107
|
shape: (13,)
|
|
108
108
|
Series: 'fwd' [f64]
|
|
109
109
|
[
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import polars as pl
|
|
2
2
|
|
|
3
3
|
import pyield._internal.converters as cv
|
|
4
|
-
from pyield import
|
|
4
|
+
from pyield import bday
|
|
5
5
|
from pyield._internal.types import DateLike, any_is_empty
|
|
6
6
|
from pyield.tn import utils
|
|
7
7
|
|
|
@@ -17,62 +17,61 @@ def data(date: DateLike) -> pl.DataFrame:
|
|
|
17
17
|
pl.DataFrame: DataFrame Polars com os dados de LFT.
|
|
18
18
|
|
|
19
19
|
Output Columns:
|
|
20
|
-
-
|
|
21
|
-
-
|
|
22
|
-
-
|
|
23
|
-
-
|
|
24
|
-
-
|
|
25
|
-
-
|
|
26
|
-
-
|
|
27
|
-
-
|
|
28
|
-
-
|
|
29
|
-
-
|
|
30
|
-
-
|
|
31
|
-
-
|
|
32
|
-
|
|
20
|
+
- data_referencia (Date): Data de referência dos dados.
|
|
21
|
+
- titulo (String): Tipo do título (ex.: "LFT").
|
|
22
|
+
- codigo_selic (Int64): Código do título no SELIC.
|
|
23
|
+
- data_base (Date): Data base de emissão do título.
|
|
24
|
+
- data_vencimento (Date): Data de vencimento do título.
|
|
25
|
+
- dias_uteis (Int64): Dias úteis entre referência e vencimento.
|
|
26
|
+
- prazo_medio (Float64): Prazo médio do título em anos.
|
|
27
|
+
- pu (Float64): Preço unitário (PU).
|
|
28
|
+
- taxa_compra (Float64): Taxa de compra (decimal).
|
|
29
|
+
- taxa_venda (Float64): Taxa de venda (decimal).
|
|
30
|
+
- taxa_indicativa (Float64): Taxa indicativa (decimal).
|
|
31
|
+
- taxa_di (Float64): Taxa de ajuste do DI Futuro interpolada pelo
|
|
32
|
+
método flat forward.
|
|
33
|
+
- rentabilidade (Float64): Rentabilidade da LFT sobre o DI.
|
|
33
34
|
|
|
34
35
|
Examples:
|
|
35
36
|
>>> from pyield import lft
|
|
36
37
|
>>> df_lft = lft.data("23-08-2024") # doctest: +SKIP
|
|
37
38
|
"""
|
|
38
|
-
df =
|
|
39
|
+
df = utils.obter_tpf(date, "LFT")
|
|
39
40
|
if df.is_empty():
|
|
40
41
|
return df
|
|
41
42
|
|
|
42
43
|
data_ref = cv.converter_datas(date)
|
|
43
44
|
|
|
44
|
-
# Adiciona BDToMat (dado derivado, não vem da ANBIMA)
|
|
45
45
|
df = df.with_columns(
|
|
46
|
-
|
|
46
|
+
dias_uteis=bday.count_expr("data_referencia", "data_vencimento"),
|
|
47
47
|
)
|
|
48
48
|
|
|
49
|
-
# Adiciona AvgMaturity e DIRate
|
|
50
49
|
df = df.with_columns(
|
|
51
|
-
|
|
50
|
+
prazo_medio=pl.col("dias_uteis") / 252,
|
|
52
51
|
)
|
|
53
52
|
df = utils.adicionar_taxa_di(df, data_ref)
|
|
54
53
|
|
|
55
54
|
df = df.with_columns(
|
|
56
|
-
|
|
57
|
-
lambda s: premium(s["
|
|
55
|
+
rentabilidade=pl.struct("taxa_indicativa", "taxa_di").map_elements(
|
|
56
|
+
lambda s: premium(s["taxa_indicativa"], s["taxa_di"]),
|
|
58
57
|
return_dtype=pl.Float64,
|
|
59
58
|
)
|
|
60
59
|
)
|
|
61
60
|
|
|
62
61
|
return df.select(
|
|
63
|
-
"
|
|
64
|
-
"
|
|
65
|
-
"
|
|
66
|
-
"
|
|
67
|
-
"
|
|
68
|
-
"
|
|
69
|
-
"
|
|
70
|
-
"
|
|
71
|
-
"
|
|
72
|
-
"
|
|
73
|
-
"
|
|
74
|
-
"
|
|
75
|
-
"
|
|
62
|
+
"data_referencia",
|
|
63
|
+
"titulo",
|
|
64
|
+
"codigo_selic",
|
|
65
|
+
"data_base",
|
|
66
|
+
"data_vencimento",
|
|
67
|
+
"dias_uteis",
|
|
68
|
+
"prazo_medio",
|
|
69
|
+
"pu",
|
|
70
|
+
"taxa_compra",
|
|
71
|
+
"taxa_venda",
|
|
72
|
+
"taxa_indicativa",
|
|
73
|
+
"taxa_di",
|
|
74
|
+
"rentabilidade",
|
|
76
75
|
)
|
|
77
76
|
|
|
78
77
|
|
|
@@ -90,7 +89,7 @@ def maturities(date: DateLike) -> pl.Series:
|
|
|
90
89
|
>>> from pyield import lft
|
|
91
90
|
>>> lft.maturities("22-08-2024")
|
|
92
91
|
shape: (14,)
|
|
93
|
-
Series: '
|
|
92
|
+
Series: 'data_vencimento' [date]
|
|
94
93
|
[
|
|
95
94
|
2024-09-01
|
|
96
95
|
2025-03-01
|
|
@@ -105,7 +104,7 @@ def maturities(date: DateLike) -> pl.Series:
|
|
|
105
104
|
2030-09-01
|
|
106
105
|
]
|
|
107
106
|
"""
|
|
108
|
-
return data(date)["
|
|
107
|
+
return data(date)["data_vencimento"]
|
|
109
108
|
|
|
110
109
|
|
|
111
110
|
def quotation(
|