pyield 0.45.0__tar.gz → 0.45.2__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.45.0 → pyield-0.45.2}/PKG-INFO +1 -1
- pyield-0.45.2/pyield/__about__.py +1 -0
- {pyield-0.45.0 → pyield-0.45.2}/pyield/_internal/converters.py +9 -0
- {pyield-0.45.0 → pyield-0.45.2}/pyield/anbima/__init__.py +3 -2
- pyield-0.45.2/pyield/anbima/imaq.py +170 -0
- {pyield-0.45.0 → pyield-0.45.2}/pyield/anbima/tpf.py +60 -157
- {pyield-0.45.0 → pyield-0.45.2}/pyield/b3/futures/historical.py +8 -5
- {pyield-0.45.0 → pyield-0.45.2}/pyield/b3/intraday_derivatives.py +1 -1
- {pyield-0.45.0 → pyield-0.45.2}/pyield/bc/ptax_api.py +1 -1
- {pyield-0.45.0 → pyield-0.45.2}/pyield/bc/rates.py +1 -1
- {pyield-0.45.0 → pyield-0.45.2}/pyield/bc/trades_intraday.py +1 -1
- {pyield-0.45.0 → pyield-0.45.2}/pyield/bc/vna.py +1 -1
- {pyield-0.45.0 → pyield-0.45.2}/pyield/tn/auctions.py +32 -27
- {pyield-0.45.0 → pyield-0.45.2}/pyield/tn/lft.py +32 -7
- {pyield-0.45.0 → pyield-0.45.2}/pyield/tn/ltn.py +36 -3
- {pyield-0.45.0 → pyield-0.45.2}/pyield/tn/ntnb.py +40 -6
- {pyield-0.45.0 → pyield-0.45.2}/pyield/tn/ntnc.py +35 -11
- {pyield-0.45.0 → pyield-0.45.2}/pyield/tn/ntnf.py +36 -4
- {pyield-0.45.0 → pyield-0.45.2}/pyield/tn/pre.py +15 -7
- {pyield-0.45.0 → pyield-0.45.2}/pyield/tn/utils.py +51 -0
- pyield-0.45.0/pyield/__about__.py +0 -1
- pyield-0.45.0/pyield/anbima/imaq.py +0 -286
- {pyield-0.45.0 → pyield-0.45.2}/.gitignore +0 -0
- {pyield-0.45.0 → pyield-0.45.2}/LICENSE +0 -0
- {pyield-0.45.0 → pyield-0.45.2}/README.md +0 -0
- {pyield-0.45.0 → pyield-0.45.2}/pyield/__init__.py +0 -0
- {pyield-0.45.0 → pyield-0.45.2}/pyield/_internal/__init__.py +0 -0
- {pyield-0.45.0 → pyield-0.45.2}/pyield/_internal/cache.py +0 -0
- {pyield-0.45.0 → pyield-0.45.2}/pyield/_internal/data_cache.py +0 -0
- {pyield-0.45.0 → pyield-0.45.2}/pyield/_internal/retry.py +0 -0
- {pyield-0.45.0 → pyield-0.45.2}/pyield/_internal/types.py +0 -0
- {pyield-0.45.0 → pyield-0.45.2}/pyield/anbima/difusao.py +0 -0
- {pyield-0.45.0 → pyield-0.45.2}/pyield/anbima/ettj_intraday.py +0 -0
- {pyield-0.45.0 → pyield-0.45.2}/pyield/anbima/ettj_last.py +0 -0
- {pyield-0.45.0 → pyield-0.45.2}/pyield/anbima/ima.py +0 -0
- {pyield-0.45.0 → pyield-0.45.2}/pyield/b3/__init__.py +0 -0
- {pyield-0.45.0 → pyield-0.45.2}/pyield/b3/_contracts.py +0 -0
- {pyield-0.45.0 → pyield-0.45.2}/pyield/b3/_validar_pregao.py +0 -0
- {pyield-0.45.0 → pyield-0.45.2}/pyield/b3/di1.py +0 -0
- {pyield-0.45.0 → pyield-0.45.2}/pyield/b3/di_over.py +0 -0
- {pyield-0.45.0 → pyield-0.45.2}/pyield/b3/futures/__init__.py +0 -0
- {pyield-0.45.0 → pyield-0.45.2}/pyield/b3/futures/common.py +0 -0
- {pyield-0.45.0 → pyield-0.45.2}/pyield/b3/futures/intraday.py +0 -0
- {pyield-0.45.0 → pyield-0.45.2}/pyield/b3/price_report.py +0 -0
- {pyield-0.45.0 → pyield-0.45.2}/pyield/bc/__init__.py +0 -0
- {pyield-0.45.0 → pyield-0.45.2}/pyield/bc/auction.py +0 -0
- {pyield-0.45.0 → pyield-0.45.2}/pyield/bc/copom.py +0 -0
- {pyield-0.45.0 → pyield-0.45.2}/pyield/bc/repo.py +0 -0
- {pyield-0.45.0 → pyield-0.45.2}/pyield/bc/trades_monthly.py +0 -0
- {pyield-0.45.0 → pyield-0.45.2}/pyield/bday/__init__.py +0 -0
- {pyield-0.45.0 → pyield-0.45.2}/pyield/bday/core.py +0 -0
- {pyield-0.45.0 → pyield-0.45.2}/pyield/bday/holidays/br_holidays_new.txt +0 -0
- {pyield-0.45.0 → pyield-0.45.2}/pyield/bday/holidays/br_holidays_old.txt +0 -0
- {pyield-0.45.0 → pyield-0.45.2}/pyield/bday/holidays/brholidays.py +0 -0
- {pyield-0.45.0 → pyield-0.45.2}/pyield/clock.py +0 -0
- {pyield-0.45.0 → pyield-0.45.2}/pyield/fwd.py +0 -0
- {pyield-0.45.0 → pyield-0.45.2}/pyield/interpolator.py +0 -0
- {pyield-0.45.0 → pyield-0.45.2}/pyield/ipca/__init__.py +0 -0
- {pyield-0.45.0 → pyield-0.45.2}/pyield/ipca/historical.py +0 -0
- {pyield-0.45.0 → pyield-0.45.2}/pyield/ipca/projected.py +0 -0
- {pyield-0.45.0 → pyield-0.45.2}/pyield/py.typed +0 -0
- {pyield-0.45.0 → pyield-0.45.2}/pyield/rmd.py +0 -0
- {pyield-0.45.0 → pyield-0.45.2}/pyield/selic/__init__.py +0 -0
- {pyield-0.45.0 → pyield-0.45.2}/pyield/selic/cpm.py +0 -0
- {pyield-0.45.0 → pyield-0.45.2}/pyield/selic/probabilities.py +0 -0
- {pyield-0.45.0 → pyield-0.45.2}/pyield/tn/__init__.py +0 -0
- {pyield-0.45.0 → pyield-0.45.2}/pyield/tn/benchmark.py +0 -0
- {pyield-0.45.0 → pyield-0.45.2}/pyield/tn/ntnb1.py +0 -0
- {pyield-0.45.0 → pyield-0.45.2}/pyield/tn/ntnbprinc.py +0 -0
- {pyield-0.45.0 → pyield-0.45.2}/pyproject.toml +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pyield
|
|
3
|
-
Version: 0.45.
|
|
3
|
+
Version: 0.45.2
|
|
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
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.45.2"
|
|
@@ -7,6 +7,15 @@ from pyield._internal import types
|
|
|
7
7
|
from pyield._internal.types import ArrayLike, DateLike
|
|
8
8
|
|
|
9
9
|
|
|
10
|
+
def data_referencia_valida(date: dt.date | None) -> bool:
|
|
11
|
+
"""Verifica se a data é dia útil e não está no futuro."""
|
|
12
|
+
if date is None:
|
|
13
|
+
return False
|
|
14
|
+
from pyield import bday, clock # noqa: PLC0415
|
|
15
|
+
|
|
16
|
+
return bday.is_business_day(date) and date <= clock.today()
|
|
17
|
+
|
|
18
|
+
|
|
10
19
|
def converter_datas_expr(expr: pl.Expr | str) -> pl.Expr:
|
|
11
20
|
"""Converte expressão Polars para ``Date`` com parse tolerante por linha.
|
|
12
21
|
|
|
@@ -3,13 +3,14 @@ from pyield.anbima.ettj_intraday import intraday_ettj
|
|
|
3
3
|
from pyield.anbima.ettj_last import last_ettj
|
|
4
4
|
from pyield.anbima.ima import last_ima
|
|
5
5
|
from pyield.anbima.imaq import imaq
|
|
6
|
-
from pyield.anbima.tpf import
|
|
6
|
+
from pyield.anbima.tpf import fetch_tpf, tpf, tpf_maturities
|
|
7
7
|
|
|
8
8
|
__all__ = [
|
|
9
9
|
"last_ima",
|
|
10
10
|
"imaq",
|
|
11
|
-
"
|
|
11
|
+
"tpf",
|
|
12
12
|
"tpf_maturities",
|
|
13
|
+
"fetch_tpf",
|
|
13
14
|
"last_ettj",
|
|
14
15
|
"intraday_ettj",
|
|
15
16
|
"tpf_difusao",
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Exemplo de página HTML:
|
|
3
|
+
Título Codigo Selic Código ISIN Data de Vencimento Quantidade em Mercado (1.000 Títulos) PU (R$) Valor de Mercado (R$ Mil) Variação da Quantidade (1.000 Títulos) Status do Titulo
|
|
4
|
+
LTN 100000 BRSTNCLTN863 01/10/2025 115.870,772 997,241543 115.551.147 0,000 Participante Definitivo
|
|
5
|
+
LTN 100000 BRSTNCLTN7U7 01/01/2026 176.807,732 963,001853 170.266.174 -1,987 Participante Definitivo
|
|
6
|
+
LTN 100000 BRSTNCLTN8B5 01/04/2026 115.826,847 931,607124 107.905.116 0,000 Participante Definitivo
|
|
7
|
+
""" # noqa
|
|
8
|
+
|
|
9
|
+
import datetime as dt
|
|
10
|
+
|
|
11
|
+
import polars as pl
|
|
12
|
+
import polars.selectors as ps
|
|
13
|
+
import requests
|
|
14
|
+
from lxml.html import HTMLParser
|
|
15
|
+
from lxml.html import fromstring as html_fromstring
|
|
16
|
+
|
|
17
|
+
import pyield._internal.converters as cv
|
|
18
|
+
from pyield._internal.cache import ttl_cache
|
|
19
|
+
from pyield._internal.retry import retry_padrao
|
|
20
|
+
from pyield._internal.types import DateLike
|
|
21
|
+
|
|
22
|
+
URL_IMA = "https://www.anbima.com.br/informacoes/ima/ima-quantidade-mercado.asp"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@ttl_cache()
|
|
26
|
+
@retry_padrao
|
|
27
|
+
def _buscar_conteudo_url(data_referencia: dt.date) -> bytes:
|
|
28
|
+
data_referencia_str = data_referencia.strftime("%d/%m/%Y")
|
|
29
|
+
payload = {
|
|
30
|
+
"Tipo": "",
|
|
31
|
+
"DataRef": "",
|
|
32
|
+
"Pai": "ima",
|
|
33
|
+
"Dt_Ref_Ver": "20250117",
|
|
34
|
+
"Dt_Ref": f"{data_referencia_str}",
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
resposta = requests.post(URL_IMA, data=payload, timeout=10)
|
|
38
|
+
resposta.raise_for_status()
|
|
39
|
+
if "Não há dados disponíveis" in resposta.text:
|
|
40
|
+
return b""
|
|
41
|
+
return resposta.content
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _normalizar_nome_coluna(texto: str) -> str:
|
|
45
|
+
"""Normaliza cabeçalhos removendo quebras de linha e espaços extras."""
|
|
46
|
+
return " ".join(texto.strip().split())
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _parsear_tabelas_html(html_content: bytes) -> pl.DataFrame:
|
|
50
|
+
"""Parseia tabelas HTML com lxml e retorna DataFrame via read_csv.
|
|
51
|
+
|
|
52
|
+
Extrai dados das tabelas aninhadas (com parent::td),
|
|
53
|
+
converte para CSV (separador tab) e lê com Polars.
|
|
54
|
+
"""
|
|
55
|
+
html_content = html_content.replace(b"<br>", b" ").replace(b"<BR>", b" ")
|
|
56
|
+
|
|
57
|
+
parser = HTMLParser(encoding="iso-8859-1")
|
|
58
|
+
tree = html_fromstring(html_content, parser=parser)
|
|
59
|
+
|
|
60
|
+
nested_tables = tree.xpath("//table[@width='100%'][parent::td]")
|
|
61
|
+
|
|
62
|
+
linhas = []
|
|
63
|
+
nomes_colunas = None
|
|
64
|
+
|
|
65
|
+
for table in nested_tables: # type: ignore[misc]
|
|
66
|
+
headers = table.xpath(".//thead//th")
|
|
67
|
+
if not nomes_colunas:
|
|
68
|
+
nomes_colunas = [_normalizar_nome_coluna(h.text_content()) for h in headers]
|
|
69
|
+
|
|
70
|
+
data_rows = table.xpath(".//tbody//tr[td]")
|
|
71
|
+
for row in data_rows:
|
|
72
|
+
cells = row.xpath(".//td")
|
|
73
|
+
if len(cells) != len(nomes_colunas):
|
|
74
|
+
continue
|
|
75
|
+
linhas.append("\t".join(c.text_content().strip() for c in cells))
|
|
76
|
+
|
|
77
|
+
if not linhas or not nomes_colunas:
|
|
78
|
+
return pl.DataFrame()
|
|
79
|
+
|
|
80
|
+
cabecalho = "\t".join(nomes_colunas)
|
|
81
|
+
csv_bytes = ("\n".join([cabecalho, *linhas])).encode("utf-8")
|
|
82
|
+
return pl.read_csv(
|
|
83
|
+
csv_bytes,
|
|
84
|
+
separator="\t",
|
|
85
|
+
infer_schema=False,
|
|
86
|
+
null_values="--",
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def _numero_br(coluna: str) -> pl.Expr:
|
|
91
|
+
"""Converte coluna string no formato numérico brasileiro para Float64."""
|
|
92
|
+
return (
|
|
93
|
+
pl.col(coluna)
|
|
94
|
+
.str.replace_all(".", "", literal=True)
|
|
95
|
+
.str.replace(",", ".", literal=True)
|
|
96
|
+
.cast(pl.Float64)
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _inteiro_m(coluna: str) -> pl.Expr:
|
|
101
|
+
"""Converte coluna numérica BR em milhares para inteiro (unidades)."""
|
|
102
|
+
return (_numero_br(coluna) * 1000).round(0).cast(pl.Int64)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def _processar_df(df: pl.DataFrame, data_referencia: dt.date) -> pl.DataFrame:
|
|
106
|
+
"""Filtra, converte tipos e aplica transformações numéricas."""
|
|
107
|
+
return (
|
|
108
|
+
df.with_columns(ps.string().str.strip_chars().name.keep())
|
|
109
|
+
.filter(
|
|
110
|
+
pl.col("Data de Vencimento").is_not_null(),
|
|
111
|
+
pl.col("Título") != "Título",
|
|
112
|
+
)
|
|
113
|
+
.unique(subset="Código ISIN")
|
|
114
|
+
.select(
|
|
115
|
+
Date=data_referencia,
|
|
116
|
+
BondType=pl.col("Título"),
|
|
117
|
+
MaturityDate=pl.col("Data de Vencimento").str.to_date(format="%d/%m/%Y"),
|
|
118
|
+
SelicCode=pl.col("Codigo Selic").cast(pl.Int64),
|
|
119
|
+
ISIN=pl.col("Código ISIN"),
|
|
120
|
+
Price=_numero_br("PU (R$)"),
|
|
121
|
+
MarketQuantity=_inteiro_m("Quantidade em Mercado (1.000 Títulos)"),
|
|
122
|
+
MarketValue=_inteiro_m("Valor de Mercado (R$ Mil)"),
|
|
123
|
+
QuantityVariation=_inteiro_m("Variação da Quantidade (1.000 Títulos)"),
|
|
124
|
+
BondStatus=pl.col("Status do Titulo"),
|
|
125
|
+
)
|
|
126
|
+
.sort("BondType", "MaturityDate")
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def imaq(date: DateLike) -> pl.DataFrame:
|
|
131
|
+
"""Consulta e processa dados de estoque IMA-Q da ANBIMA para uma data.
|
|
132
|
+
|
|
133
|
+
Args:
|
|
134
|
+
date: Data de referência. Apenas os últimos 5 dias úteis estão disponíveis;
|
|
135
|
+
o dado do dia anterior costuma ser publicado ao longo do dia.
|
|
136
|
+
|
|
137
|
+
Returns:
|
|
138
|
+
DataFrame com dados processados ou DataFrame vazio se a data for
|
|
139
|
+
inválida ou não houver dados disponíveis.
|
|
140
|
+
|
|
141
|
+
Output Columns:
|
|
142
|
+
- Date (Date): data de referência dos dados.
|
|
143
|
+
- BondType (String): tipo do título (LTN, NTN-B, NTN-F, LFT, …).
|
|
144
|
+
- MaturityDate (Date): data de vencimento do título.
|
|
145
|
+
- SelicCode (Int64): código SELIC do título.
|
|
146
|
+
- ISIN (String): código ISIN (International Securities Id Number).
|
|
147
|
+
- Price (Float64): PU do título em R$.
|
|
148
|
+
- MarketQuantity (Int64): quantidade em mercado (unidades).
|
|
149
|
+
- MarketValue (Int64): valor de mercado em R$.
|
|
150
|
+
- QuantityVariation (Int64): variação diária da quantidade.
|
|
151
|
+
- BondStatus (String): status do título.
|
|
152
|
+
|
|
153
|
+
Notes:
|
|
154
|
+
Valores convertidos para unidades puras (ex: MarketQuantity × 1.000).
|
|
155
|
+
|
|
156
|
+
Examples:
|
|
157
|
+
>>> yd.anbima.imaq("04-02-2026") # doctest: +SKIP
|
|
158
|
+
"""
|
|
159
|
+
data = cv.converter_datas(date)
|
|
160
|
+
if not cv.data_referencia_valida(data):
|
|
161
|
+
return pl.DataFrame()
|
|
162
|
+
|
|
163
|
+
url_content = _buscar_conteudo_url(data)
|
|
164
|
+
if not url_content:
|
|
165
|
+
return pl.DataFrame()
|
|
166
|
+
|
|
167
|
+
df = _parsear_tabelas_html(url_content)
|
|
168
|
+
if df.is_empty():
|
|
169
|
+
return pl.DataFrame()
|
|
170
|
+
return _processar_df(df, data)
|
|
@@ -8,16 +8,11 @@ import polars.selectors as cs
|
|
|
8
8
|
import requests
|
|
9
9
|
from requests.exceptions import HTTPError, RequestException
|
|
10
10
|
|
|
11
|
-
from pyield import bday
|
|
12
|
-
from pyield._internal.converters import converter_datas
|
|
11
|
+
from pyield import bday
|
|
12
|
+
from pyield._internal.converters import converter_datas, data_referencia_valida
|
|
13
13
|
from pyield._internal.data_cache import obter_dataset_cacheado
|
|
14
14
|
from pyield._internal.retry import retry_padrao
|
|
15
|
-
from pyield._internal.types import DateLike
|
|
16
|
-
from pyield.b3 import di1
|
|
17
|
-
from pyield.bc.ptax_api import ptax
|
|
18
|
-
from pyield.tn.ntnb import duration as duration_b
|
|
19
|
-
from pyield.tn.ntnc import duration as duration_c
|
|
20
|
-
from pyield.tn.ntnf import duration as duration_f
|
|
15
|
+
from pyield._internal.types import DateLike
|
|
21
16
|
|
|
22
17
|
BOND_TYPES = Literal["LFT", "NTN-B", "NTN-C", "LTN", "NTN-F", "PRE"]
|
|
23
18
|
|
|
@@ -131,89 +126,9 @@ def _processar_df_bruto(df: pl.DataFrame) -> pl.DataFrame:
|
|
|
131
126
|
cs.contains("Rate").truediv(100).round(6),
|
|
132
127
|
cs.ends_with("Date").str.to_date(format="%Y%m%d"),
|
|
133
128
|
)
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
)
|
|
137
|
-
)
|
|
138
|
-
return df
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
def _calcular_duracao_por_linha(linha: dict) -> float:
|
|
142
|
-
"""Função auxiliar que será aplicada a cada linha do struct."""
|
|
143
|
-
# Mapeia o BondType para a função de duration correspondente
|
|
144
|
-
# Isso torna a lógica dentro do lambda ainda mais limpa
|
|
145
|
-
tipo_titulo = linha["BondType"]
|
|
146
|
-
if tipo_titulo == "LTN":
|
|
147
|
-
return linha["BDToMat"] / 252 # A lógica da LTN depende apenas do BDToMat
|
|
148
|
-
|
|
149
|
-
funcoes_duracao = {
|
|
150
|
-
"NTN-F": duration_f,
|
|
151
|
-
"NTN-B": duration_b,
|
|
152
|
-
"NTN-C": duration_c,
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
func_duracao = funcoes_duracao.get(tipo_titulo) # Busca da função correta
|
|
156
|
-
if func_duracao:
|
|
157
|
-
return func_duracao(
|
|
158
|
-
linha["ReferenceDate"],
|
|
159
|
-
linha["MaturityDate"],
|
|
160
|
-
linha["IndicativeRate"],
|
|
161
|
-
)
|
|
162
|
-
# Se o BondType não for reconhecido, retorna 0.0 (LFT ou outros)
|
|
163
|
-
return 0.0
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
def _adicionar_duracao(df_input: pl.DataFrame) -> pl.DataFrame:
|
|
167
|
-
"""Adiciona a coluna 'Duration' ao DataFrame Polars de forma otimizada."""
|
|
168
|
-
colunas_necessarias = [
|
|
169
|
-
"BondType",
|
|
170
|
-
"ReferenceDate",
|
|
171
|
-
"MaturityDate",
|
|
172
|
-
"IndicativeRate",
|
|
173
|
-
"BDToMat", # Necessário para LTN
|
|
174
|
-
]
|
|
175
|
-
# Adiciona a coluna Duration
|
|
176
|
-
df = df_input.with_columns(
|
|
177
|
-
pl.struct(colunas_necessarias)
|
|
178
|
-
.map_elements(_calcular_duracao_por_linha, return_dtype=pl.Float64)
|
|
179
|
-
.alias("Duration")
|
|
180
|
-
)
|
|
181
|
-
return df
|
|
182
|
-
|
|
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
|
-
|
|
195
|
-
def _adicionar_dv01(df_input: pl.DataFrame, data_ref: dt.date) -> pl.DataFrame:
|
|
196
|
-
"""Adiciona as colunas de DV01 ao DataFrame."""
|
|
197
|
-
expr_duracao_mod = pl.col("Duration") / (1 + pl.col("IndicativeRate"))
|
|
198
|
-
df = df_input.with_columns(DV01=0.0001 * expr_duracao_mod * pl.col("Price"))
|
|
199
|
-
|
|
200
|
-
# DV01 em USD
|
|
201
|
-
try:
|
|
202
|
-
taxa_ptax = ptax(date=data_ref)
|
|
203
|
-
df = df.with_columns(DV01USD=pl.col("DV01") / taxa_ptax)
|
|
204
|
-
except Exception as e:
|
|
205
|
-
logger.error("Erro ao adicionar DV01 em USD: %s", e)
|
|
206
|
-
return df
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
def _adicionar_taxa_di(df: pl.DataFrame, data_ref: dt.date) -> pl.DataFrame:
|
|
210
|
-
"""Adiciona a coluna de taxa DI ao DataFrame."""
|
|
211
|
-
taxas_di = di1.interpolate_rates(
|
|
212
|
-
dates=data_ref,
|
|
213
|
-
expirations=df["MaturityDate"],
|
|
214
|
-
extrapolate=True,
|
|
129
|
+
# Substituir eventuais NaNs por None para compatibilidade com bancos de dados
|
|
130
|
+
.with_columns(cs.float().fill_nan(None))
|
|
215
131
|
)
|
|
216
|
-
df = df.with_columns(DIRate=taxas_di)
|
|
217
132
|
return df
|
|
218
133
|
|
|
219
134
|
|
|
@@ -225,22 +140,10 @@ def _selecionar_e_ordenar_colunas(df: pl.DataFrame) -> pl.DataFrame:
|
|
|
225
140
|
"SelicCode",
|
|
226
141
|
"IssueBaseDate",
|
|
227
142
|
"MaturityDate",
|
|
228
|
-
"BDToMat",
|
|
229
|
-
"Duration",
|
|
230
|
-
"AvgMaturity",
|
|
231
|
-
"DV01",
|
|
232
|
-
"DV01USD",
|
|
233
143
|
"Price",
|
|
234
144
|
"BidRate",
|
|
235
145
|
"AskRate",
|
|
236
146
|
"IndicativeRate",
|
|
237
|
-
"DIRate",
|
|
238
|
-
# "StdDev",
|
|
239
|
-
# "LowerBoundRateD0",
|
|
240
|
-
# "UpperBoundRateD0",
|
|
241
|
-
# "LowerBoundRateD1",
|
|
242
|
-
# "UpperBoundRateD1",
|
|
243
|
-
"Criteria",
|
|
244
147
|
]
|
|
245
148
|
ordem_colunas = [col for col in ordem_colunas if col in df.columns]
|
|
246
149
|
return df.select(ordem_colunas).sort("BondType", "MaturityDate")
|
|
@@ -291,13 +194,6 @@ def _buscar_dados_tpf(date: dt.date) -> pl.DataFrame:
|
|
|
291
194
|
|
|
292
195
|
df = _ler_csv(csv_texto)
|
|
293
196
|
df = _processar_df_bruto(df)
|
|
294
|
-
df = _adicionar_duracao(df)
|
|
295
|
-
df = _adicionar_prazo_medio(df)
|
|
296
|
-
df = _adicionar_dv01(df, date)
|
|
297
|
-
df = _adicionar_taxa_di(df, date)
|
|
298
|
-
df = _selecionar_e_ordenar_colunas(df)
|
|
299
|
-
# Substituir eventuais NaNs por None para compatibilidade com bancos de dados
|
|
300
|
-
df = df.with_columns(cs.float().fill_nan(None))
|
|
301
197
|
|
|
302
198
|
return df
|
|
303
199
|
|
|
@@ -324,24 +220,21 @@ def _buscar_dados_tpf(date: dt.date) -> pl.DataFrame:
|
|
|
324
220
|
raise
|
|
325
221
|
|
|
326
222
|
|
|
327
|
-
def
|
|
223
|
+
def tpf(
|
|
328
224
|
date: DateLike,
|
|
329
225
|
bond_type: BOND_TYPES | None = None,
|
|
330
|
-
fetch_from_source: bool = False,
|
|
331
226
|
) -> pl.DataFrame:
|
|
332
227
|
"""Recupera os dados do mercado secundário de TPF da ANBIMA.
|
|
333
228
|
|
|
334
229
|
Esta função busca taxas indicativas e outros dados de títulos públicos
|
|
335
|
-
brasileiros.
|
|
336
|
-
|
|
230
|
+
brasileiros. Primeiro consulta o cache local; se não houver dados,
|
|
231
|
+
busca diretamente na fonte (ANBIMA).
|
|
337
232
|
|
|
338
233
|
Args:
|
|
339
234
|
date (DateLike): A data de referência para os dados (ex: '2024-06-14').
|
|
340
|
-
bond_type (str, optional): Filtra
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
cache e buscar os dados diretamente da fonte (ANBIMA).
|
|
344
|
-
Padrão é False.
|
|
235
|
+
bond_type (str, optional): Filtra por tipo de título. Aceita os tipos
|
|
236
|
+
individuais ('LTN', 'NTN-F', 'NTN-B', 'NTN-C', 'LFT') ou 'PRE'
|
|
237
|
+
como atalho para prefixados ('LTN' e 'NTN-F').
|
|
345
238
|
|
|
346
239
|
Returns:
|
|
347
240
|
pl.DataFrame: Um DataFrame contendo os dados solicitados.
|
|
@@ -350,7 +243,7 @@ def tpf_data(
|
|
|
350
243
|
|
|
351
244
|
Examples:
|
|
352
245
|
>>> from pyield import anbima
|
|
353
|
-
>>> df = anbima.
|
|
246
|
+
>>> df = anbima.tpf(date="06-02-2026")
|
|
354
247
|
|
|
355
248
|
Data columns:
|
|
356
249
|
- BondType: Tipo do título público (e.g., 'LTN', 'NTN-B').
|
|
@@ -358,53 +251,36 @@ def tpf_data(
|
|
|
358
251
|
- SelicCode: Código do título no SELIC.
|
|
359
252
|
- IssueBaseDate: Data base ou de emissão do título.
|
|
360
253
|
- MaturityDate: Data de vencimento do título.
|
|
361
|
-
-
|
|
362
|
-
-
|
|
363
|
-
-
|
|
364
|
-
|
|
365
|
-
- DV01: Variação financeira no preço do título (em BRL) para uma
|
|
366
|
-
mudança de 1 basis point (0,01%) na taxa de juros.
|
|
367
|
-
- DV01USD: O mesmo que DV01, mas convertido para USD pela PTAX do dia.
|
|
368
|
-
- Price: Preço Unitário (PU) do título na data de referência.
|
|
369
|
-
- BidRate: Taxa de compra em formato decimal (e.g., 0.10 para 10%).
|
|
370
|
-
- AskRate: Taxa de venda em formato decimal.
|
|
371
|
-
- IndicativeRate: Taxa indicativa em formato decimal.
|
|
372
|
-
- DIRate: Taxa DI interpolada (flatforward) no vencimento do título.
|
|
373
|
-
- StdDev: Desvio padrão da taxa indicativa.
|
|
374
|
-
- LowerBoundRateD0: Limite inferior do intervalo indicativo para D+0.
|
|
375
|
-
- UpperBoundRateD0: Limite superior do intervalo indicativo para D+0.
|
|
376
|
-
- LowerBoundRateD1: Limite inferior do intervalo indicativo para D+1.
|
|
377
|
-
- UpperBoundRateD1: Limite superior do intervalo indicativo para D+1.
|
|
378
|
-
- Criteria: Critério utilizado pela ANBIMA para o cálculo.
|
|
254
|
+
- Price: Preço Unitário (PU) calculado para liquidação em D0.
|
|
255
|
+
- BidRate: Taxa de compra para liquidação em D0 (decimal).
|
|
256
|
+
- AskRate: Taxa de venda para liquidação em D0 (decimal).
|
|
257
|
+
- IndicativeRate: Taxa indicativa para liquidação em D0 (decimal).
|
|
379
258
|
|
|
380
259
|
Notes:
|
|
381
260
|
A fonte dos dados segue a seguinte hierarquia:
|
|
382
261
|
|
|
383
|
-
1. **Cache Local
|
|
384
|
-
desde 01/01/2020.
|
|
385
|
-
2. **
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
262
|
+
1. **Cache Local:** Fornece acesso rápido a dados históricos
|
|
263
|
+
desde 01/01/2020.
|
|
264
|
+
2. **Fonte ANBIMA (fallback):** Se a data não estiver no cache,
|
|
265
|
+
busca automaticamente na fonte. Para datas recentes (até 5 dias
|
|
266
|
+
úteis), usa o site público. Para datas mais antigas, requer
|
|
267
|
+
acesso à rede RTM.
|
|
268
|
+
|
|
269
|
+
Para obter o dado completo direto da fonte (todas as colunas),
|
|
270
|
+
use :func:`fetch_tpf`.
|
|
391
271
|
"""
|
|
392
|
-
if any_is_empty(date):
|
|
393
|
-
return pl.DataFrame()
|
|
394
272
|
date = converter_datas(date)
|
|
395
273
|
|
|
396
|
-
if not
|
|
397
|
-
return pl.DataFrame()
|
|
398
|
-
|
|
399
|
-
if date > clock.today():
|
|
274
|
+
if not data_referencia_valida(date):
|
|
400
275
|
return pl.DataFrame()
|
|
401
276
|
|
|
402
|
-
|
|
403
|
-
|
|
277
|
+
# Cache primeiro; se não tiver, busca na fonte
|
|
278
|
+
df = obter_dataset_cacheado("tpf")
|
|
279
|
+
if not df.is_empty():
|
|
280
|
+
df = df.filter(pl.col("ReferenceDate") == date)
|
|
281
|
+
if df.is_empty():
|
|
404
282
|
df = _buscar_dados_tpf(date)
|
|
405
|
-
|
|
406
|
-
# Caso contrário, obtém os dados do cache local
|
|
407
|
-
df = obter_dataset_cacheado("tpf").filter(pl.col("ReferenceDate") == date)
|
|
283
|
+
df = _selecionar_e_ordenar_colunas(df)
|
|
408
284
|
|
|
409
285
|
if df.is_empty():
|
|
410
286
|
return pl.DataFrame()
|
|
@@ -416,6 +292,33 @@ def tpf_data(
|
|
|
416
292
|
return df.sort("ReferenceDate", "BondType", "MaturityDate")
|
|
417
293
|
|
|
418
294
|
|
|
295
|
+
def fetch_tpf(
|
|
296
|
+
date: DateLike,
|
|
297
|
+
) -> pl.DataFrame:
|
|
298
|
+
"""Busca os dados do mercado secundário de TPF direto da fonte ANBIMA.
|
|
299
|
+
|
|
300
|
+
Retorna todas as colunas publicadas pela ANBIMA, sem cache e sem
|
|
301
|
+
filtro de colunas. Indicado para uso em jobs e pipelines de dados.
|
|
302
|
+
|
|
303
|
+
Args:
|
|
304
|
+
date (DateLike): Data de referência (ex: '2024-06-14').
|
|
305
|
+
|
|
306
|
+
Returns:
|
|
307
|
+
pl.DataFrame: DataFrame com todas as colunas da ANBIMA.
|
|
308
|
+
Retorna DataFrame vazio se não houver dados.
|
|
309
|
+
"""
|
|
310
|
+
date = converter_datas(date)
|
|
311
|
+
|
|
312
|
+
if not data_referencia_valida(date):
|
|
313
|
+
return pl.DataFrame()
|
|
314
|
+
|
|
315
|
+
df = _buscar_dados_tpf(date)
|
|
316
|
+
if df.is_empty():
|
|
317
|
+
return pl.DataFrame()
|
|
318
|
+
|
|
319
|
+
return df.sort("ReferenceDate", "BondType", "MaturityDate")
|
|
320
|
+
|
|
321
|
+
|
|
419
322
|
def tpf_maturities(
|
|
420
323
|
date: DateLike,
|
|
421
324
|
bond_type: BOND_TYPES,
|
|
@@ -450,4 +353,4 @@ def tpf_maturities(
|
|
|
450
353
|
2035-01-01
|
|
451
354
|
]
|
|
452
355
|
"""
|
|
453
|
-
return
|
|
356
|
+
return tpf(date, bond_type)["MaturityDate"].unique().sort()
|
|
@@ -149,11 +149,14 @@ def listar_datas_disponiveis(codigo_contrato: str) -> pl.Series:
|
|
|
149
149
|
def _buscar_price_report(
|
|
150
150
|
data: dt.date, codigo: str, full_report: bool | None
|
|
151
151
|
) -> pl.DataFrame:
|
|
152
|
-
"""Busca o price report da B3, com fallback SPR→PR
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
152
|
+
"""Busca o price report da B3, com fallback SPR→PR.
|
|
153
|
+
|
|
154
|
+
Quando full_report é True, usa apenas o PR (completo).
|
|
155
|
+
Nos demais casos (None ou False), tenta o SPR (leve) primeiro
|
|
156
|
+
e faz fallback para o PR se o SPR estiver vazio.
|
|
157
|
+
"""
|
|
158
|
+
if full_report is True:
|
|
159
|
+
return fetch_price_report(date=data, contract_code=codigo, full_report=True)
|
|
157
160
|
|
|
158
161
|
# SPR (leve) primeiro; PR (pesado) como fallback
|
|
159
162
|
df = fetch_price_report(date=data, contract_code=codigo, full_report=False)
|
|
@@ -50,7 +50,7 @@ def _mapa_renomeacao_intraday() -> dict[str, str]:
|
|
|
50
50
|
return {nome_orig: nome_novo for nome_orig, nome_novo, _ in COLUNAS_INTRADAY}
|
|
51
51
|
|
|
52
52
|
|
|
53
|
-
@ttl_cache(ttl=
|
|
53
|
+
@ttl_cache(ttl=10)
|
|
54
54
|
@retry_padrao
|
|
55
55
|
def _buscar_json_intraday(codigo_contrato: str) -> list[dict]:
|
|
56
56
|
url = f"{URL_BASE_INTRADAY}/{codigo_contrato}"
|
|
@@ -10,7 +10,7 @@ from pyield._internal.types import DateLike, any_is_empty
|
|
|
10
10
|
logger = logging.getLogger(__name__)
|
|
11
11
|
|
|
12
12
|
|
|
13
|
-
@ttl_cache(
|
|
13
|
+
@ttl_cache()
|
|
14
14
|
@retry_padrao
|
|
15
15
|
def _baixar_texto(date: DateLike) -> str:
|
|
16
16
|
"""Baixa o arquivo diário do SELIC no site do BCB."""
|