pyield 0.48.4__tar.gz → 0.48.6__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.48.4 → pyield-0.48.6}/PKG-INFO +1 -1
- pyield-0.48.6/pyield/__about__.py +1 -0
- {pyield-0.48.4 → pyield-0.48.6}/pyield/b3/di_over.py +40 -26
- {pyield-0.48.4 → pyield-0.48.6}/pyield/bc/__init__.py +3 -6
- {pyield-0.48.4 → pyield-0.48.6}/pyield/bc/leiloes.py +3 -3
- pyield-0.48.6/pyield/bc/sgs.py +386 -0
- {pyield-0.48.4 → pyield-0.48.6}/pyield/tn/leiloes.py +2 -2
- {pyield-0.48.4 → pyield-0.48.6}/pyield/tn/utils.py +1 -1
- pyield-0.48.4/pyield/__about__.py +0 -1
- pyield-0.48.4/pyield/bc/ptax.py +0 -196
- pyield-0.48.4/pyield/bc/taxas.py +0 -317
- {pyield-0.48.4 → pyield-0.48.6}/.gitignore +0 -0
- {pyield-0.48.4 → pyield-0.48.6}/LICENSE +0 -0
- {pyield-0.48.4 → pyield-0.48.6}/README.md +0 -0
- {pyield-0.48.4 → pyield-0.48.6}/pyield/__init__.py +0 -0
- {pyield-0.48.4 → pyield-0.48.6}/pyield/_internal/__init__.py +0 -0
- {pyield-0.48.4 → pyield-0.48.6}/pyield/_internal/br_numbers.py +0 -0
- {pyield-0.48.4 → pyield-0.48.6}/pyield/_internal/cache.py +0 -0
- {pyield-0.48.4 → pyield-0.48.6}/pyield/_internal/converters.py +0 -0
- {pyield-0.48.4 → pyield-0.48.6}/pyield/_internal/data_cache.py +0 -0
- {pyield-0.48.4 → pyield-0.48.6}/pyield/_internal/retry.py +0 -0
- {pyield-0.48.4 → pyield-0.48.6}/pyield/_internal/types.py +0 -0
- {pyield-0.48.4 → pyield-0.48.6}/pyield/anbima/__init__.py +0 -0
- {pyield-0.48.4 → pyield-0.48.6}/pyield/anbima/ettj_intradia.py +0 -0
- {pyield-0.48.4 → pyield-0.48.6}/pyield/anbima/ettj_ultima.py +0 -0
- {pyield-0.48.4 → pyield-0.48.6}/pyield/anbima/ima_ultimo.py +0 -0
- {pyield-0.48.4 → pyield-0.48.6}/pyield/anbima/imaq.py +0 -0
- {pyield-0.48.4 → pyield-0.48.6}/pyield/anbima/tpf.py +0 -0
- {pyield-0.48.4 → pyield-0.48.6}/pyield/b3/__init__.py +0 -0
- {pyield-0.48.4 → pyield-0.48.6}/pyield/b3/_contratos.py +0 -0
- {pyield-0.48.4 → pyield-0.48.6}/pyield/b3/_validar_pregao.py +0 -0
- {pyield-0.48.4 → pyield-0.48.6}/pyield/b3/boletim.py +0 -0
- {pyield-0.48.4 → pyield-0.48.6}/pyield/b3/derivativos_intradia.py +0 -0
- {pyield-0.48.4 → pyield-0.48.6}/pyield/b3/di1.py +0 -0
- {pyield-0.48.4 → pyield-0.48.6}/pyield/b3/futuro/__init__.py +0 -0
- {pyield-0.48.4 → pyield-0.48.6}/pyield/b3/futuro/contratos.py +0 -0
- {pyield-0.48.4 → pyield-0.48.6}/pyield/b3/futuro/historico.py +0 -0
- {pyield-0.48.4 → pyield-0.48.6}/pyield/b3/futuro/intradia.py +0 -0
- {pyield-0.48.4 → pyield-0.48.6}/pyield/bc/compromissada.py +0 -0
- {pyield-0.48.4 → pyield-0.48.6}/pyield/bc/copom.py +0 -0
- {pyield-0.48.4 → pyield-0.48.6}/pyield/bc/tpf_intradia.py +0 -0
- {pyield-0.48.4 → pyield-0.48.6}/pyield/bc/tpf_mensal.py +0 -0
- {pyield-0.48.4 → pyield-0.48.6}/pyield/bc/vna.py +0 -0
- {pyield-0.48.4 → pyield-0.48.6}/pyield/du/__init__.py +0 -0
- {pyield-0.48.4 → pyield-0.48.6}/pyield/du/core.py +0 -0
- {pyield-0.48.4 → pyield-0.48.6}/pyield/du/feriados/__init__.py +0 -0
- {pyield-0.48.4 → pyield-0.48.6}/pyield/du/feriados/feriados_antigos_br.txt +0 -0
- {pyield-0.48.4 → pyield-0.48.6}/pyield/du/feriados/feriados_br.py +0 -0
- {pyield-0.48.4 → pyield-0.48.6}/pyield/du/feriados/feriados_novos_br.txt +0 -0
- {pyield-0.48.4 → pyield-0.48.6}/pyield/fwd.py +0 -0
- {pyield-0.48.4 → pyield-0.48.6}/pyield/interpolador.py +0 -0
- {pyield-0.48.4 → pyield-0.48.6}/pyield/ipca/__init__.py +0 -0
- {pyield-0.48.4 → pyield-0.48.6}/pyield/ipca/historico.py +0 -0
- {pyield-0.48.4 → pyield-0.48.6}/pyield/ipca/projetado.py +0 -0
- {pyield-0.48.4 → pyield-0.48.6}/pyield/py.typed +0 -0
- {pyield-0.48.4 → pyield-0.48.6}/pyield/relogio.py +0 -0
- {pyield-0.48.4 → pyield-0.48.6}/pyield/selic/__init__.py +0 -0
- {pyield-0.48.4 → pyield-0.48.6}/pyield/selic/cpm.py +0 -0
- {pyield-0.48.4 → pyield-0.48.6}/pyield/selic/probabilities.py +0 -0
- {pyield-0.48.4 → pyield-0.48.6}/pyield/tn/__init__.py +0 -0
- {pyield-0.48.4 → pyield-0.48.6}/pyield/tn/benchmark.py +0 -0
- {pyield-0.48.4 → pyield-0.48.6}/pyield/tn/lft.py +0 -0
- {pyield-0.48.4 → pyield-0.48.6}/pyield/tn/ltn.py +0 -0
- {pyield-0.48.4 → pyield-0.48.6}/pyield/tn/ntnb.py +0 -0
- {pyield-0.48.4 → pyield-0.48.6}/pyield/tn/ntnb1.py +0 -0
- {pyield-0.48.4 → pyield-0.48.6}/pyield/tn/ntnbprinc.py +0 -0
- {pyield-0.48.4 → pyield-0.48.6}/pyield/tn/ntnc.py +0 -0
- {pyield-0.48.4 → pyield-0.48.6}/pyield/tn/ntnf.py +0 -0
- {pyield-0.48.4 → pyield-0.48.6}/pyield/tn/pre.py +0 -0
- {pyield-0.48.4 → pyield-0.48.6}/pyield/tn/rmd.py +0 -0
- {pyield-0.48.4 → pyield-0.48.6}/pyproject.toml +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pyield
|
|
3
|
-
Version: 0.48.
|
|
3
|
+
Version: 0.48.6
|
|
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.48.6"
|
|
@@ -9,46 +9,29 @@ Formato do arquivo (ex.: 20250228.txt):
|
|
|
9
9
|
Notas de implementação:
|
|
10
10
|
- O valor "00001315" representa 1315 / 10^4 = 0.1315 (13,15% a.a.).
|
|
11
11
|
- Arquivos ausentes (feriados/fins de semana) retornam erro FTP 550.
|
|
12
|
+
- Série disponível a partir de 20/08/2012 (primeiro arquivo no FTP).
|
|
12
13
|
"""
|
|
13
14
|
|
|
15
|
+
import datetime as dt
|
|
14
16
|
import ftplib
|
|
15
17
|
import logging
|
|
16
18
|
|
|
19
|
+
from pyield._internal.cache import ttl_cache
|
|
17
20
|
from pyield._internal.converters import converter_datas
|
|
18
21
|
from pyield._internal.types import DateLike, any_is_empty
|
|
19
22
|
|
|
20
23
|
registro = logging.getLogger(__name__)
|
|
21
24
|
|
|
25
|
+
# Primeiro arquivo disponível no FTP da CETIP
|
|
26
|
+
DATA_INICIO = dt.date(2012, 8, 20)
|
|
27
|
+
|
|
22
28
|
# 4 casas decimais na taxa = 2 casas decimais em percentual
|
|
23
29
|
CASAS_DECIMAIS_DI_OVER = 4
|
|
24
30
|
|
|
25
31
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
Busca o arquivo de taxa DI (Depósito Interfinanceiro) no servidor
|
|
30
|
-
FTP da CETIP para a data informada.
|
|
31
|
-
|
|
32
|
-
Args:
|
|
33
|
-
data: data da consulta para buscar a taxa DI.
|
|
34
|
-
|
|
35
|
-
Returns:
|
|
36
|
-
Taxa DI para a data especificada (ex: 0.1315 para 13,15%).
|
|
37
|
-
Retorna ``nan`` se a data for feriado ou fim de semana.
|
|
38
|
-
|
|
39
|
-
Examples:
|
|
40
|
-
>>> di_over("28/02/2025")
|
|
41
|
-
0.1315
|
|
42
|
-
>>> di_over("01/01/2025") # Feriado
|
|
43
|
-
nan
|
|
44
|
-
"""
|
|
45
|
-
if any_is_empty(data):
|
|
46
|
-
return float("nan")
|
|
47
|
-
|
|
48
|
-
# Converte a data para o formato esperado do arquivo: YYYYMMDD.txt
|
|
49
|
-
data_ref = converter_datas(data)
|
|
50
|
-
nome_arquivo = data_ref.strftime("%Y%m%d.txt")
|
|
51
|
-
|
|
32
|
+
@ttl_cache()
|
|
33
|
+
def _buscar_taxa(nome_arquivo: str) -> float:
|
|
34
|
+
"""Busca a taxa DI no FTP da CETIP para o arquivo informado."""
|
|
52
35
|
try:
|
|
53
36
|
with ftplib.FTP("ftp.cetip.com.br", timeout=10) as ftp:
|
|
54
37
|
ftp.login()
|
|
@@ -75,3 +58,34 @@ def di_over(data: DateLike) -> float:
|
|
|
75
58
|
except ftplib.all_errors as e:
|
|
76
59
|
registro.error("Erro de conexão ou transferência FTP: %s", e)
|
|
77
60
|
raise ConnectionError(f"Falha ao buscar taxa DI via FTP: {e}") from e
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def di_over(data: DateLike) -> float:
|
|
64
|
+
"""Obtém a taxa DI over via FTP da B3/CETIP.
|
|
65
|
+
|
|
66
|
+
Busca o arquivo de taxa DI (Depósito Interfinanceiro) no servidor
|
|
67
|
+
FTP da CETIP para a data informada.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
data: data da consulta para buscar a taxa DI.
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
Taxa DI para a data especificada (ex: 0.1315 para 13,15%).
|
|
74
|
+
Retorna ``nan`` se a data for feriado, fim de semana ou
|
|
75
|
+
anterior a 20/08/2012 (início da série no FTP).
|
|
76
|
+
|
|
77
|
+
Examples:
|
|
78
|
+
>>> di_over("28/02/2025")
|
|
79
|
+
0.1315
|
|
80
|
+
>>> di_over("01/01/2025") # Feriado
|
|
81
|
+
nan
|
|
82
|
+
"""
|
|
83
|
+
if any_is_empty(data):
|
|
84
|
+
return float("nan")
|
|
85
|
+
|
|
86
|
+
data_ref = converter_datas(data)
|
|
87
|
+
if data_ref < DATA_INICIO:
|
|
88
|
+
return float("nan")
|
|
89
|
+
|
|
90
|
+
nome_arquivo = data_ref.strftime("%Y%m%d.txt")
|
|
91
|
+
return _buscar_taxa(nome_arquivo)
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
from pyield.bc import compromissada, copom
|
|
2
2
|
from pyield.bc.compromissada import compromissadas
|
|
3
3
|
from pyield.bc.leiloes import leiloes
|
|
4
|
-
from pyield.bc.
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
di_over_serie,
|
|
4
|
+
from pyield.bc.sgs import (
|
|
5
|
+
ptax,
|
|
6
|
+
ptax_serie,
|
|
8
7
|
selic_meta,
|
|
9
8
|
selic_meta_serie,
|
|
10
9
|
selic_over,
|
|
@@ -17,8 +16,6 @@ from pyield.bc.vna import vna_lft
|
|
|
17
16
|
__all__ = [
|
|
18
17
|
"copom",
|
|
19
18
|
"compromissada",
|
|
20
|
-
"di_over",
|
|
21
|
-
"di_over_serie",
|
|
22
19
|
"leiloes",
|
|
23
20
|
"ptax_serie",
|
|
24
21
|
"compromissadas",
|
|
@@ -19,7 +19,7 @@ from pyield import du
|
|
|
19
19
|
from pyield._internal.br_numbers import float_br, taxa_br
|
|
20
20
|
from pyield._internal.retry import retry_padrao
|
|
21
21
|
from pyield._internal.types import DateLike
|
|
22
|
-
from pyield.bc.
|
|
22
|
+
from pyield.bc.sgs import ptax_serie
|
|
23
23
|
from pyield.tn.ntnb import duration as duration_b
|
|
24
24
|
from pyield.tn.ntnf import duration as duration_f
|
|
25
25
|
|
|
@@ -240,8 +240,8 @@ def _buscar_ptax(df: pl.DataFrame) -> pl.DataFrame:
|
|
|
240
240
|
return pl.DataFrame()
|
|
241
241
|
|
|
242
242
|
return (
|
|
243
|
-
df_ptax.select("data", "
|
|
244
|
-
.rename({"data": "data_ref", "
|
|
243
|
+
df_ptax.select("data", "cotacao")
|
|
244
|
+
.rename({"data": "data_ref", "cotacao": "ptax"})
|
|
245
245
|
.sort("data_ref")
|
|
246
246
|
)
|
|
247
247
|
|
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
"""Séries do Sistema Gerenciador de Séries (SGS) do Banco Central.
|
|
2
|
+
|
|
3
|
+
Séries disponíveis:
|
|
4
|
+
- PTAX Venda (SGS 1)
|
|
5
|
+
- SELIC Meta (SGS 432)
|
|
6
|
+
- SELIC Over (SGS 1178)
|
|
7
|
+
|
|
8
|
+
Exemplos de chamada à API:
|
|
9
|
+
https://api.bcb.gov.br/dados/serie/bcdata.sgs.1178/dados?formato=json&dataInicial=29/01/2025&dataFinal=31/01/2025
|
|
10
|
+
https://api.bcb.gov.br/dados/serie/bcdata.sgs.1178/dados/ultimos/5?formato=json
|
|
11
|
+
|
|
12
|
+
Exemplo de resposta JSON da API do BCB:
|
|
13
|
+
[{"data":"29/01/2025","valor":"12.15"},
|
|
14
|
+
{"data":"30/01/2025","valor":"13.15"},
|
|
15
|
+
{"data":"31/01/2025","valor":"13.15"}]
|
|
16
|
+
|
|
17
|
+
Notas de implementação:
|
|
18
|
+
- Intervalos > 10 anos são divididos automaticamente em blocos.
|
|
19
|
+
- SELIC Over e Meta: valores percentuais convertidos para decimal
|
|
20
|
+
(divididos por 100) e arredondados para 4 casas decimais.
|
|
21
|
+
- PTAX Venda: valor absoluto em R$ arredondado para 4 casas.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
import datetime as dt
|
|
25
|
+
from enum import Enum
|
|
26
|
+
|
|
27
|
+
import polars as pl
|
|
28
|
+
import requests
|
|
29
|
+
|
|
30
|
+
from pyield import relogio
|
|
31
|
+
from pyield._internal.cache import ttl_cache
|
|
32
|
+
from pyield._internal.converters import converter_datas
|
|
33
|
+
from pyield._internal.retry import retry_padrao
|
|
34
|
+
from pyield._internal.types import DateLike, any_is_empty
|
|
35
|
+
|
|
36
|
+
URL_BASE = "https://api.bcb.gov.br/dados/serie/bcdata.sgs."
|
|
37
|
+
|
|
38
|
+
ESQUEMA_BRUTO = {"data": pl.Date, "valor": pl.Float64}
|
|
39
|
+
|
|
40
|
+
# Limite de segurança em dias, correspondendo a ~9.5 anos.
|
|
41
|
+
# Evita a complexidade do cálculo exato de 10 anos-calendário.
|
|
42
|
+
LIMITE_DIAS_SEGURO = 3500 # aprox 365 * 9.5
|
|
43
|
+
|
|
44
|
+
CASAS_DECIMAIS_TAXA = 4 # Selic: 2 casas no formato percentual → 4 em decimal
|
|
45
|
+
CASAS_DECIMAIS_PTAX = 4
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class SerieSGS(Enum):
|
|
49
|
+
"""Enum para as séries disponíveis no SGS do Banco Central."""
|
|
50
|
+
|
|
51
|
+
PTAX_VENDA = 1
|
|
52
|
+
SELIC_META = 432
|
|
53
|
+
SELIC_OVER = 1178
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
# ── Infraestrutura de acesso à API SGS ──────────────────────────────
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@ttl_cache()
|
|
60
|
+
@retry_padrao
|
|
61
|
+
def _chamar_api(url_api: str) -> list[dict[str, str]]:
|
|
62
|
+
resposta = requests.get(url_api, timeout=10)
|
|
63
|
+
resposta.raise_for_status()
|
|
64
|
+
return resposta.json()
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _montar_url_intervalo(
|
|
68
|
+
serie: SerieSGS, inicio: dt.date, fim: dt.date | None = None
|
|
69
|
+
) -> str:
|
|
70
|
+
inicio_str = inicio.strftime("%d/%m/%Y")
|
|
71
|
+
url = f"{URL_BASE}{serie.value}/dados?formato=json&dataInicial={inicio_str}"
|
|
72
|
+
if fim:
|
|
73
|
+
url += f"&dataFinal={fim.strftime('%d/%m/%Y')}"
|
|
74
|
+
return url
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _montar_url_ultimos(serie: SerieSGS, n: int) -> str:
|
|
78
|
+
return f"{URL_BASE}{serie.value}/dados/ultimos/{n}?formato=json"
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _buscar_api(url_api: str) -> pl.DataFrame:
|
|
82
|
+
"""Busca dados da API e retorna DataFrame bruto {data, valor}."""
|
|
83
|
+
try:
|
|
84
|
+
dados = _chamar_api(url_api)
|
|
85
|
+
except requests.exceptions.HTTPError as e:
|
|
86
|
+
if e.response.status_code == 404: # noqa
|
|
87
|
+
return pl.DataFrame(schema=ESQUEMA_BRUTO)
|
|
88
|
+
raise
|
|
89
|
+
|
|
90
|
+
if not dados:
|
|
91
|
+
return pl.DataFrame(schema=ESQUEMA_BRUTO)
|
|
92
|
+
|
|
93
|
+
return pl.from_dicts(dados).select(
|
|
94
|
+
pl.col("data").str.to_date("%d/%m/%Y"),
|
|
95
|
+
pl.col("valor").cast(pl.Float64),
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _buscar_dados_url(
|
|
100
|
+
serie: SerieSGS,
|
|
101
|
+
inicio: DateLike,
|
|
102
|
+
fim: DateLike | None = None,
|
|
103
|
+
) -> pl.DataFrame:
|
|
104
|
+
"""Orquestra a busca, dividindo intervalos > 10 anos em blocos."""
|
|
105
|
+
data_inicio = converter_datas(inicio)
|
|
106
|
+
data_fim = converter_datas(fim) if fim else relogio.hoje()
|
|
107
|
+
|
|
108
|
+
if (data_fim - data_inicio).days < LIMITE_DIAS_SEGURO:
|
|
109
|
+
return _buscar_api(_montar_url_intervalo(serie, data_inicio, data_fim))
|
|
110
|
+
|
|
111
|
+
inicios = pl.date_range(start=data_inicio, end=data_fim, interval="10y", eager=True)
|
|
112
|
+
fins = inicios.dt.offset_by("10y").clip(upper_bound=data_fim)
|
|
113
|
+
|
|
114
|
+
todos_dfs = [
|
|
115
|
+
_buscar_api(_montar_url_intervalo(serie, ini, fim))
|
|
116
|
+
for ini, fim in zip(inicios, fins)
|
|
117
|
+
]
|
|
118
|
+
|
|
119
|
+
todos_dfs = [df for df in todos_dfs if not df.is_empty()]
|
|
120
|
+
|
|
121
|
+
if not todos_dfs:
|
|
122
|
+
return pl.DataFrame(schema=ESQUEMA_BRUTO)
|
|
123
|
+
|
|
124
|
+
return pl.concat(todos_dfs).unique(subset=["data"], keep="first").sort("data")
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def _buscar_serie(
|
|
128
|
+
serie: SerieSGS,
|
|
129
|
+
inicio: DateLike | None,
|
|
130
|
+
fim: DateLike | None,
|
|
131
|
+
ultimos: int | None,
|
|
132
|
+
) -> pl.DataFrame:
|
|
133
|
+
"""Busca genérica para qualquer série SGS."""
|
|
134
|
+
if ultimos is not None:
|
|
135
|
+
return _buscar_api(_montar_url_ultimos(serie, ultimos))
|
|
136
|
+
if inicio is not None:
|
|
137
|
+
return _buscar_dados_url(serie, inicio, fim)
|
|
138
|
+
raise ValueError("Informe 'inicio' ou 'ultimos'.")
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
# ── Helpers de transformação ─────────────────────────────────────────
|
|
142
|
+
|
|
143
|
+
ESQUEMA_TAXA = {"data": pl.Date, "taxa": pl.Float64}
|
|
144
|
+
ESQUEMA_PTAX = {"data": pl.Date, "cotacao": pl.Float64}
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def _converter_para_taxa(df: pl.DataFrame) -> pl.DataFrame:
|
|
148
|
+
"""Converte valor percentual para decimal e renomeia para 'taxa'."""
|
|
149
|
+
if df.is_empty():
|
|
150
|
+
return pl.DataFrame(schema=ESQUEMA_TAXA)
|
|
151
|
+
return df.select(
|
|
152
|
+
"data",
|
|
153
|
+
taxa=pl.col("valor").truediv(100).round(CASAS_DECIMAIS_TAXA),
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def _extrair_escalar(df: pl.DataFrame, coluna: str) -> float:
|
|
158
|
+
"""Extrai um valor escalar de um DataFrame ou retorna nan se vazio."""
|
|
159
|
+
if df.is_empty():
|
|
160
|
+
return float("nan")
|
|
161
|
+
return df[coluna].item(0)
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
# ── SELIC Over ───────────────────────────────────────────────────────
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def selic_over_serie(
|
|
168
|
+
inicio: DateLike | None = None,
|
|
169
|
+
fim: DateLike | None = None,
|
|
170
|
+
*,
|
|
171
|
+
ultimos: int | None = None,
|
|
172
|
+
) -> pl.DataFrame:
|
|
173
|
+
"""Taxa SELIC Over (série SGS 1178).
|
|
174
|
+
|
|
175
|
+
Taxa de juros média diária praticada no mercado interbancário,
|
|
176
|
+
com títulos públicos como garantia.
|
|
177
|
+
|
|
178
|
+
Args:
|
|
179
|
+
inicio: Data inicial.
|
|
180
|
+
fim: Data final. Se ``None``, usa a data mais recente.
|
|
181
|
+
ultimos: Número de registros mais recentes a retornar.
|
|
182
|
+
Mutuamente exclusivo com ``inicio``/``fim``.
|
|
183
|
+
|
|
184
|
+
Returns:
|
|
185
|
+
DataFrame com colunas data e taxa, ou DataFrame vazio.
|
|
186
|
+
|
|
187
|
+
Examples:
|
|
188
|
+
>>> from pyield import bc
|
|
189
|
+
>>> # Sem dados em 26-01-2025 (domingo). Selic mudou por reunião do Copom.
|
|
190
|
+
>>> bc.selic_over_serie("26-01-2025").head(5) # Primeiras 5 linhas
|
|
191
|
+
shape: (5, 2)
|
|
192
|
+
┌────────────┬────────┐
|
|
193
|
+
│ data ┆ taxa │
|
|
194
|
+
│ --- ┆ --- │
|
|
195
|
+
│ date ┆ f64 │
|
|
196
|
+
╞════════════╪════════╡
|
|
197
|
+
│ 2025-01-27 ┆ 0.1215 │
|
|
198
|
+
│ 2025-01-28 ┆ 0.1215 │
|
|
199
|
+
│ 2025-01-29 ┆ 0.1215 │
|
|
200
|
+
│ 2025-01-30 ┆ 0.1315 │
|
|
201
|
+
│ 2025-01-31 ┆ 0.1315 │
|
|
202
|
+
└────────────┴────────┘
|
|
203
|
+
|
|
204
|
+
>>> # Buscando dados para um intervalo específico
|
|
205
|
+
>>> bc.selic_over_serie("14-09-2025", "17-09-2025")
|
|
206
|
+
shape: (3, 2)
|
|
207
|
+
┌────────────┬───────┐
|
|
208
|
+
│ data ┆ taxa │
|
|
209
|
+
│ --- ┆ --- │
|
|
210
|
+
│ date ┆ f64 │
|
|
211
|
+
╞════════════╪═══════╡
|
|
212
|
+
│ 2025-09-15 ┆ 0.149 │
|
|
213
|
+
│ 2025-09-16 ┆ 0.149 │
|
|
214
|
+
│ 2025-09-17 ┆ 0.149 │
|
|
215
|
+
└────────────┴───────┘
|
|
216
|
+
"""
|
|
217
|
+
return _converter_para_taxa(
|
|
218
|
+
_buscar_serie(SerieSGS.SELIC_OVER, inicio, fim, ultimos)
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def selic_over(data: DateLike) -> float:
|
|
223
|
+
"""Taxa SELIC Over para uma data específica.
|
|
224
|
+
|
|
225
|
+
Args:
|
|
226
|
+
data: Data da consulta.
|
|
227
|
+
|
|
228
|
+
Returns:
|
|
229
|
+
Taxa SELIC Over ou ``nan`` se não disponível.
|
|
230
|
+
|
|
231
|
+
Examples:
|
|
232
|
+
>>> from pyield import bc
|
|
233
|
+
>>> bc.selic_over("31-05-2024")
|
|
234
|
+
0.104
|
|
235
|
+
"""
|
|
236
|
+
if any_is_empty(data):
|
|
237
|
+
return float("nan")
|
|
238
|
+
return _extrair_escalar(selic_over_serie(data, data), "taxa")
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
# ── SELIC Meta ───────────────────────────────────────────────────────
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def selic_meta_serie(
|
|
245
|
+
inicio: DateLike | None = None,
|
|
246
|
+
fim: DateLike | None = None,
|
|
247
|
+
*,
|
|
248
|
+
ultimos: int | None = None,
|
|
249
|
+
) -> pl.DataFrame:
|
|
250
|
+
"""Taxa SELIC Meta (série SGS 432).
|
|
251
|
+
|
|
252
|
+
Taxa de juros oficial definida pelo COPOM.
|
|
253
|
+
|
|
254
|
+
Args:
|
|
255
|
+
inicio: Data inicial.
|
|
256
|
+
fim: Data final. Se ``None``, usa a data mais recente.
|
|
257
|
+
ultimos: Número de registros mais recentes a retornar.
|
|
258
|
+
Mutuamente exclusivo com ``inicio``/``fim``.
|
|
259
|
+
|
|
260
|
+
Returns:
|
|
261
|
+
DataFrame com colunas data e taxa, ou DataFrame vazio.
|
|
262
|
+
|
|
263
|
+
Examples:
|
|
264
|
+
>>> from pyield import bc
|
|
265
|
+
>>> bc.selic_meta_serie("31-05-2024", "31-05-2024")
|
|
266
|
+
shape: (1, 2)
|
|
267
|
+
┌────────────┬───────┐
|
|
268
|
+
│ data ┆ taxa │
|
|
269
|
+
│ --- ┆ --- │
|
|
270
|
+
│ date ┆ f64 │
|
|
271
|
+
╞════════════╪═══════╡
|
|
272
|
+
│ 2024-05-31 ┆ 0.105 │
|
|
273
|
+
└────────────┴───────┘
|
|
274
|
+
"""
|
|
275
|
+
return _converter_para_taxa(
|
|
276
|
+
_buscar_serie(SerieSGS.SELIC_META, inicio, fim, ultimos)
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
def selic_meta(data: DateLike) -> float:
|
|
281
|
+
"""Taxa SELIC Meta para uma data específica.
|
|
282
|
+
|
|
283
|
+
Args:
|
|
284
|
+
data: Data da consulta.
|
|
285
|
+
|
|
286
|
+
Returns:
|
|
287
|
+
Taxa SELIC Meta ou ``nan`` se não disponível.
|
|
288
|
+
|
|
289
|
+
Examples:
|
|
290
|
+
>>> from pyield import bc
|
|
291
|
+
>>> bc.selic_meta("31-05-2024")
|
|
292
|
+
0.105
|
|
293
|
+
"""
|
|
294
|
+
if any_is_empty(data):
|
|
295
|
+
return float("nan")
|
|
296
|
+
return _extrair_escalar(selic_meta_serie(data, data), "taxa")
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
# ── PTAX ─────────────────────────────────────────────────────────────
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
def ptax_serie(
|
|
303
|
+
inicio: DateLike | None = None,
|
|
304
|
+
fim: DateLike | None = None,
|
|
305
|
+
*,
|
|
306
|
+
ultimos: int | None = None,
|
|
307
|
+
) -> pl.DataFrame:
|
|
308
|
+
"""Cotação PTAX de venda do dólar (série SGS 1).
|
|
309
|
+
|
|
310
|
+
Fonte: Banco Central do Brasil (BCB). Frequência diária.
|
|
311
|
+
|
|
312
|
+
A cotação retornada é a PTAX de **venda**, a taxa de câmbio
|
|
313
|
+
oficial do dólar no Brasil, usada como referência para
|
|
314
|
+
liquidação de contratos, marcação a mercado de derivativos,
|
|
315
|
+
conversão de DV01 e índices de fundos cambiais.
|
|
316
|
+
|
|
317
|
+
Args:
|
|
318
|
+
inicio: Data inicial.
|
|
319
|
+
fim: Data final. Se ``None``, usa a data mais recente.
|
|
320
|
+
ultimos: Número de registros mais recentes a retornar.
|
|
321
|
+
Mutuamente exclusivo com ``inicio``/``fim``.
|
|
322
|
+
|
|
323
|
+
Returns:
|
|
324
|
+
DataFrame com colunas data e cotacao, ou DataFrame vazio
|
|
325
|
+
se não houver dados.
|
|
326
|
+
|
|
327
|
+
Output Columns:
|
|
328
|
+
* data (Date): data da cotação.
|
|
329
|
+
* cotacao (Float64): cotação PTAX de venda em R$/US$.
|
|
330
|
+
|
|
331
|
+
Notes:
|
|
332
|
+
Disponível desde 28.11.1984. Refere-se às taxas administradas até
|
|
333
|
+
março de 1990 e às taxas livres a partir de então (Resolução 1690,
|
|
334
|
+
de 18.3.1990). A partir de março de 1992, essa taxa recebeu a
|
|
335
|
+
denominação PTAX. Desde 1 de julho de 2011 (Circular 3506), a PTAX
|
|
336
|
+
corresponde à média aritmética das taxas obtidas em quatro consultas
|
|
337
|
+
diárias aos dealers de câmbio.
|
|
338
|
+
|
|
339
|
+
Examples:
|
|
340
|
+
>>> from pyield import bc
|
|
341
|
+
>>> bc.ptax_serie("20-04-2025", "25-04-2025")
|
|
342
|
+
shape: (4, 2)
|
|
343
|
+
┌────────────┬─────────┐
|
|
344
|
+
│ data ┆ cotacao │
|
|
345
|
+
│ --- ┆ --- │
|
|
346
|
+
│ date ┆ f64 │
|
|
347
|
+
╞════════════╪═════════╡
|
|
348
|
+
│ 2025-04-22 ┆ 5.7496 │
|
|
349
|
+
│ 2025-04-23 ┆ 5.688 │
|
|
350
|
+
│ 2025-04-24 ┆ 5.6738 │
|
|
351
|
+
│ 2025-04-25 ┆ 5.6846 │
|
|
352
|
+
└────────────┴─────────┘
|
|
353
|
+
"""
|
|
354
|
+
df = _buscar_serie(SerieSGS.PTAX_VENDA, inicio, fim, ultimos)
|
|
355
|
+
if df.is_empty():
|
|
356
|
+
return pl.DataFrame(schema=ESQUEMA_PTAX)
|
|
357
|
+
return df.select(
|
|
358
|
+
"data",
|
|
359
|
+
cotacao=pl.col("valor").round(CASAS_DECIMAIS_PTAX),
|
|
360
|
+
)
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
def ptax(data: DateLike) -> float:
|
|
364
|
+
"""Cotação PTAX de venda para uma data específica.
|
|
365
|
+
|
|
366
|
+
Retorna a PTAX de venda, taxa de câmbio oficial do dólar no
|
|
367
|
+
Brasil.
|
|
368
|
+
|
|
369
|
+
Args:
|
|
370
|
+
data: Data desejada.
|
|
371
|
+
|
|
372
|
+
Returns:
|
|
373
|
+
Cotação PTAX de venda em R$/US$, ou ``nan`` se não houver
|
|
374
|
+
cotação (feriado, fim de semana ou data futura).
|
|
375
|
+
|
|
376
|
+
Examples:
|
|
377
|
+
>>> from pyield import bc
|
|
378
|
+
>>> bc.ptax("22-04-2025")
|
|
379
|
+
5.7496
|
|
380
|
+
|
|
381
|
+
>>> bc.ptax("20-04-2025")
|
|
382
|
+
nan
|
|
383
|
+
"""
|
|
384
|
+
if any_is_empty(data):
|
|
385
|
+
return float("nan")
|
|
386
|
+
return _extrair_escalar(ptax_serie(data, data), "cotacao")
|
|
@@ -292,8 +292,8 @@ def _buscar_ptax(data_leilao: dt.date) -> pl.DataFrame:
|
|
|
292
292
|
return pl.DataFrame()
|
|
293
293
|
|
|
294
294
|
return (
|
|
295
|
-
df.select("data", "
|
|
296
|
-
.rename({"data": "data_ref", "
|
|
295
|
+
df.select("data", "cotacao")
|
|
296
|
+
.rename({"data": "data_ref", "cotacao": "ptax"})
|
|
297
297
|
.sort("data_ref")
|
|
298
298
|
)
|
|
299
299
|
|
|
@@ -80,7 +80,7 @@ def adicionar_duration(
|
|
|
80
80
|
|
|
81
81
|
def adicionar_dv01(df: pl.DataFrame, data_ref: dt.date) -> pl.DataFrame:
|
|
82
82
|
"""Adiciona `dv01` e `dv01_usd` ao DataFrame. Requer coluna `duration`."""
|
|
83
|
-
from pyield.bc.
|
|
83
|
+
from pyield.bc.sgs import ptax # noqa: PLC0415
|
|
84
84
|
|
|
85
85
|
expr_duracao_mod = pl.col("duration") / (1 + pl.col("taxa_indicativa"))
|
|
86
86
|
df = df.with_columns(dv01=0.0001 * expr_duracao_mod * pl.col("pu"))
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.48.4"
|
pyield-0.48.4/pyield/bc/ptax.py
DELETED
|
@@ -1,196 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Módulo para acessar a API de cotações PTAX do Banco Central do Brasil (BCB)
|
|
3
|
-
|
|
4
|
-
Exemplo de chamada à API:
|
|
5
|
-
https://olinda.bcb.gov.br/olinda/servico/PTAX/versao/v1/odata/CotacaoDolarPeriodo(dataInicial=@dataInicial,dataFinalCotacao=@dataFinalCotacao)?@dataInicial='09-01-2025'&@dataFinalCotacao='09-10-2025'&$format=text/csv
|
|
6
|
-
|
|
7
|
-
Exemplo de resposta CSV da API do BCB:
|
|
8
|
-
cotacaoCompra, cotacaoVenda, dataHoraCotacao
|
|
9
|
-
2814 , 2828 , 1984-12-03 11:29:00.0
|
|
10
|
-
2814 , 2828 , 1984-12-03 16:38:00.0
|
|
11
|
-
2867 , 2881 , 1984-12-04 11:17:00.0
|
|
12
|
-
"0,843" , "0,845" , 1995-01-02 18:20:00.0
|
|
13
|
-
...
|
|
14
|
-
"5,4272" , "5,4278" , 2025-09-08 13:09:40.608
|
|
15
|
-
"5,4272" , "5,4278" , 2025-09-09 13:07:27.786
|
|
16
|
-
"5,4117" , "5,4123" , 2025-09-10 13:06:29.196
|
|
17
|
-
|
|
18
|
-
ATENÇÃO: a fração de segundo varia entre .0 (datas antigas) e .608
|
|
19
|
-
(datas recentes). Usar %.f (variável) no parsing, nunca %.3f (fixo).
|
|
20
|
-
"""
|
|
21
|
-
|
|
22
|
-
import datetime as dt
|
|
23
|
-
|
|
24
|
-
import polars as pl
|
|
25
|
-
import requests
|
|
26
|
-
|
|
27
|
-
import pyield._internal.converters as cv
|
|
28
|
-
from pyield import relogio
|
|
29
|
-
from pyield._internal.br_numbers import float_br
|
|
30
|
-
from pyield._internal.cache import ttl_cache
|
|
31
|
-
from pyield._internal.retry import retry_padrao
|
|
32
|
-
from pyield._internal.types import DateLike
|
|
33
|
-
|
|
34
|
-
URL_API_PTAX = "https://olinda.bcb.gov.br/olinda/servico/PTAX/versao/v1/odata/CotacaoDolarPeriodo(dataInicial=@dataInicial,dataFinalCotacao=@dataFinalCotacao)?"
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
def _montar_url_api(inicio: dt.date, fim: dt.date) -> str:
|
|
38
|
-
inicio_str = inicio.strftime("%m-%d-%Y")
|
|
39
|
-
fim_str = fim.strftime("%m-%d-%Y")
|
|
40
|
-
return (
|
|
41
|
-
f"{URL_API_PTAX}"
|
|
42
|
-
f"@dataInicial='{inicio_str}'"
|
|
43
|
-
f"&@dataFinalCotacao='{fim_str}'"
|
|
44
|
-
f"&$format=text/csv"
|
|
45
|
-
)
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
@ttl_cache()
|
|
49
|
-
@retry_padrao
|
|
50
|
-
def _buscar_texto_api(url: str) -> bytes:
|
|
51
|
-
resposta = requests.get(url, timeout=10)
|
|
52
|
-
resposta.raise_for_status()
|
|
53
|
-
return resposta.content
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
def _parsear_df(conteudo_csv: bytes) -> pl.DataFrame:
|
|
57
|
-
"""Lê o CSV bruto da API PTAX em DataFrame com todas as colunas como string."""
|
|
58
|
-
return pl.read_csv(conteudo_csv, infer_schema=False)
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
def _processar_df(df: pl.DataFrame) -> pl.DataFrame:
|
|
62
|
-
return (
|
|
63
|
-
df.with_columns(
|
|
64
|
-
cotacao_compra=float_br("cotacaoCompra"),
|
|
65
|
-
cotacao_venda=float_br("cotacaoVenda"),
|
|
66
|
-
data_hora=pl.col("dataHoraCotacao").str.to_datetime(
|
|
67
|
-
format="%Y-%m-%d %H:%M:%S%.f", strict=False
|
|
68
|
-
),
|
|
69
|
-
)
|
|
70
|
-
.with_columns(
|
|
71
|
-
data=pl.col("data_hora").cast(pl.Date),
|
|
72
|
-
hora=pl.col("data_hora").dt.time(),
|
|
73
|
-
cotacao_media=pl.mean_horizontal("cotacao_compra", "cotacao_venda").round(
|
|
74
|
-
5
|
|
75
|
-
),
|
|
76
|
-
)
|
|
77
|
-
.sort("data_hora")
|
|
78
|
-
.unique(subset=["data"], keep="last")
|
|
79
|
-
.select("data", "hora", "cotacao_compra", "cotacao_venda", "cotacao_media")
|
|
80
|
-
.sort("data")
|
|
81
|
-
)
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
def ptax_serie(
|
|
85
|
-
inicio: DateLike | None = None,
|
|
86
|
-
fim: DateLike | None = None,
|
|
87
|
-
) -> pl.DataFrame:
|
|
88
|
-
"""Cotações de fechamento do Dólar PTAX (taxa de câmbio).
|
|
89
|
-
|
|
90
|
-
Fonte: Banco Central do Brasil (BCB). Frequência diária.
|
|
91
|
-
|
|
92
|
-
Se `inicio` não for informada, usa 28.11.1984 (primeira data
|
|
93
|
-
disponível). Se `fim` não for informada, usa a data de hoje.
|
|
94
|
-
|
|
95
|
-
Args:
|
|
96
|
-
inicio: Data de início da consulta. Padrão é ``None``.
|
|
97
|
-
fim: Data de fim da consulta. Padrão é ``None``.
|
|
98
|
-
|
|
99
|
-
Returns:
|
|
100
|
-
DataFrame com as cotações do período, ou DataFrame vazio
|
|
101
|
-
se não houver dados.
|
|
102
|
-
|
|
103
|
-
Output Columns:
|
|
104
|
-
* data (Date): data da cotação.
|
|
105
|
-
* hora (Time): hora da cotação.
|
|
106
|
-
* cotacao_compra (Float64): taxa de compra em R$.
|
|
107
|
-
* cotacao_venda (Float64): taxa de venda em R$.
|
|
108
|
-
* cotacao_media (Float64): média de compra/venda (5 casas).
|
|
109
|
-
|
|
110
|
-
Notes:
|
|
111
|
-
Disponível desde 28.11.1984; refere-se às taxas
|
|
112
|
-
administradas até março de 1990 e às taxas livres a
|
|
113
|
-
partir de então (Resolução 1690, de 18.3.1990). A
|
|
114
|
-
partir de março de 1992, essa taxa recebeu a
|
|
115
|
-
denominação PTAX. Desde 1 de julho de 2011 (Circular
|
|
116
|
-
3506), a PTAX corresponde à média aritmética das
|
|
117
|
-
taxas obtidas em quatro consultas diárias aos dealers
|
|
118
|
-
de câmbio.
|
|
119
|
-
|
|
120
|
-
Documentação da API:
|
|
121
|
-
https://olinda.bcb.gov.br/olinda/servico/PTAX/versao/v1/documentacao
|
|
122
|
-
|
|
123
|
-
Examples:
|
|
124
|
-
>>> from pyield import bc
|
|
125
|
-
>>> bc.ptax_serie(inicio="20-04-2025", fim="25-04-2025")
|
|
126
|
-
shape: (4, 5)
|
|
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
|
-
└────────────┴──────────────┴────────────────┴───────────────┴───────────────┘
|
|
137
|
-
|
|
138
|
-
>>> bc.ptax_serie(inicio="02-01-1995", fim="06-01-1995")
|
|
139
|
-
shape: (5, 5)
|
|
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
|
-
└────────────┴──────────┴────────────────┴───────────────┴───────────────┘
|
|
151
|
-
"""
|
|
152
|
-
if inicio:
|
|
153
|
-
inicio = cv.converter_datas(inicio)
|
|
154
|
-
else:
|
|
155
|
-
inicio = dt.date(1984, 11, 28)
|
|
156
|
-
|
|
157
|
-
if fim:
|
|
158
|
-
fim = cv.converter_datas(fim)
|
|
159
|
-
else:
|
|
160
|
-
fim = relogio.hoje()
|
|
161
|
-
|
|
162
|
-
url = _montar_url_api(inicio, fim)
|
|
163
|
-
texto = _buscar_texto_api(url)
|
|
164
|
-
df = _parsear_df(texto)
|
|
165
|
-
if df.is_empty():
|
|
166
|
-
return pl.DataFrame()
|
|
167
|
-
return _processar_df(df)
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
def ptax(data: DateLike) -> float:
|
|
171
|
-
"""Cotação PTAX média de fechamento para uma data específica.
|
|
172
|
-
|
|
173
|
-
Args:
|
|
174
|
-
data: Data desejada.
|
|
175
|
-
|
|
176
|
-
Returns:
|
|
177
|
-
Taxa média (cotacao_media) do dia, ou ``nan`` se não
|
|
178
|
-
houver cotação (feriado, fim de semana ou data futura).
|
|
179
|
-
|
|
180
|
-
Examples:
|
|
181
|
-
>>> from pyield import bc
|
|
182
|
-
>>> # Busca a PTAX para um dia útil
|
|
183
|
-
>>> bc.ptax("22-08-2025")
|
|
184
|
-
5.4389
|
|
185
|
-
|
|
186
|
-
>>> # Busca a PTAX para um fim de semana (sem dados)
|
|
187
|
-
>>> bc.ptax("23-08-2025")
|
|
188
|
-
nan
|
|
189
|
-
"""
|
|
190
|
-
dados_ptax = ptax_serie(
|
|
191
|
-
inicio=data,
|
|
192
|
-
fim=data,
|
|
193
|
-
)
|
|
194
|
-
if dados_ptax.is_empty():
|
|
195
|
-
return float("nan")
|
|
196
|
-
return dados_ptax["cotacao_media"].item(0)
|
pyield-0.48.4/pyield/bc/taxas.py
DELETED
|
@@ -1,317 +0,0 @@
|
|
|
1
|
-
"""Taxas de juros do Banco Central do Brasil (séries SGS).
|
|
2
|
-
|
|
3
|
-
Séries disponíveis:
|
|
4
|
-
- SELIC Over (SGS 1178)
|
|
5
|
-
- SELIC Meta (SGS 432)
|
|
6
|
-
- DI Over (SGS 11)
|
|
7
|
-
|
|
8
|
-
Exemplo de chamada à API:
|
|
9
|
-
https://api.bcb.gov.br/dados/serie/bcdata.sgs.1178/dados?formato=json&dataInicial=29/01/2025&dataFinal=31/01/2025
|
|
10
|
-
|
|
11
|
-
Exemplo de resposta JSON da API do BCB (valores em percentual):
|
|
12
|
-
[{"data":"29/01/2025","valor":"12.15"},
|
|
13
|
-
{"data":"30/01/2025","valor":"13.15"},
|
|
14
|
-
{"data":"31/01/2025","valor":"13.15"}]
|
|
15
|
-
|
|
16
|
-
Notas de implementação:
|
|
17
|
-
- Valores percentuais são convertidos para decimal (divididos por 100).
|
|
18
|
-
- SELIC Over e Meta: arredondadas para 4 casas decimais.
|
|
19
|
-
- DI Over diária: 8 casas decimais; anualizada: 4 casas decimais.
|
|
20
|
-
- Intervalos > 10 anos são divididos automaticamente em blocos.
|
|
21
|
-
"""
|
|
22
|
-
|
|
23
|
-
import datetime as dt
|
|
24
|
-
from enum import Enum
|
|
25
|
-
|
|
26
|
-
import polars as pl
|
|
27
|
-
import requests
|
|
28
|
-
|
|
29
|
-
from pyield import relogio
|
|
30
|
-
from pyield._internal.cache import ttl_cache
|
|
31
|
-
from pyield._internal.converters import converter_datas
|
|
32
|
-
from pyield._internal.retry import retry_padrao
|
|
33
|
-
from pyield._internal.types import DateLike, any_is_empty
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
def _extrair_taxa(df: pl.DataFrame) -> float:
|
|
37
|
-
"""Extrai a taxa escalar de um DataFrame ou retorna nan se vazio."""
|
|
38
|
-
if df.is_empty():
|
|
39
|
-
return float("nan")
|
|
40
|
-
return df["taxa"].item(0)
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
URL_BASE = "https://api.bcb.gov.br/dados/serie/bcdata.sgs."
|
|
44
|
-
CASAS_DECIMAIS_ANUALIZADA = 4 # 2 casas no formato percentual
|
|
45
|
-
CASAS_DECIMAIS_DIARIA = 8 # 6 casas no formato percentual
|
|
46
|
-
|
|
47
|
-
# Limite de segurança em dias, correspondendo a ~9.5 anos.
|
|
48
|
-
# Evita a complexidade do cálculo exato de 10 anos-calendário.
|
|
49
|
-
LIMITE_DIAS_SEGURO = 3500 # aprox 365 * 9.5
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
class SerieBC(Enum):
|
|
53
|
-
"""Enum para as séries disponíveis no Banco Central."""
|
|
54
|
-
|
|
55
|
-
SELIC_OVER = 1178
|
|
56
|
-
SELIC_META = 432
|
|
57
|
-
DI_OVER = 11
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
@ttl_cache()
|
|
61
|
-
@retry_padrao
|
|
62
|
-
def _chamar_api(url_api: str) -> list[dict[str, str]]:
|
|
63
|
-
resposta = requests.get(url_api, timeout=10)
|
|
64
|
-
resposta.raise_for_status()
|
|
65
|
-
return resposta.json()
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
def _montar_url_download(
|
|
69
|
-
serie: SerieBC, inicio: dt.date, fim: dt.date | None = None
|
|
70
|
-
) -> str:
|
|
71
|
-
inicio_str = inicio.strftime("%d/%m/%Y")
|
|
72
|
-
url = f"{URL_BASE}{serie.value}/dados?formato=json&dataInicial={inicio_str}"
|
|
73
|
-
if fim:
|
|
74
|
-
url += f"&dataFinal={fim.strftime('%d/%m/%Y')}"
|
|
75
|
-
return url
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
def _buscar_requisicao(
|
|
79
|
-
serie: SerieBC,
|
|
80
|
-
inicio: dt.date,
|
|
81
|
-
fim: dt.date | None,
|
|
82
|
-
) -> pl.DataFrame:
|
|
83
|
-
"""Busca dados da API para um intervalo."""
|
|
84
|
-
esquema_esperado = {"data": pl.Date, "taxa": pl.Float64}
|
|
85
|
-
url_api = _montar_url_download(serie, inicio, fim)
|
|
86
|
-
|
|
87
|
-
try:
|
|
88
|
-
dados = _chamar_api(url_api)
|
|
89
|
-
except requests.exceptions.HTTPError as e:
|
|
90
|
-
if e.response.status_code == 404: # noqa
|
|
91
|
-
return pl.DataFrame(schema=esquema_esperado)
|
|
92
|
-
raise
|
|
93
|
-
|
|
94
|
-
if not dados:
|
|
95
|
-
return pl.DataFrame(schema=esquema_esperado)
|
|
96
|
-
|
|
97
|
-
return pl.from_dicts(dados).select(
|
|
98
|
-
pl.col("data").str.to_date("%d/%m/%Y"),
|
|
99
|
-
taxa=pl.col("valor").cast(pl.Float64) / 100,
|
|
100
|
-
)
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
def _buscar_dados_url(
|
|
104
|
-
serie: SerieBC,
|
|
105
|
-
inicio: DateLike,
|
|
106
|
-
fim: DateLike | None = None,
|
|
107
|
-
) -> pl.DataFrame:
|
|
108
|
-
"""Orquestra a busca, dividindo intervalos > 10 anos em blocos."""
|
|
109
|
-
data_inicio = converter_datas(inicio)
|
|
110
|
-
data_fim = converter_datas(fim) if fim else relogio.hoje()
|
|
111
|
-
|
|
112
|
-
if (data_fim - data_inicio).days < LIMITE_DIAS_SEGURO:
|
|
113
|
-
return _buscar_requisicao(serie, data_inicio, data_fim)
|
|
114
|
-
|
|
115
|
-
inicios = pl.date_range(start=data_inicio, end=data_fim, interval="10y", eager=True)
|
|
116
|
-
fins = inicios.dt.offset_by("10y").clip(upper_bound=data_fim)
|
|
117
|
-
|
|
118
|
-
todos_dfs = [_buscar_requisicao(serie, ini, fim) for ini, fim in zip(inicios, fins)]
|
|
119
|
-
|
|
120
|
-
todos_dfs = [df for df in todos_dfs if not df.is_empty()]
|
|
121
|
-
|
|
122
|
-
if not todos_dfs:
|
|
123
|
-
return pl.DataFrame()
|
|
124
|
-
|
|
125
|
-
return pl.concat(todos_dfs).unique(subset=["data"], keep="first").sort("data")
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
def selic_over_serie(
|
|
129
|
-
inicio: DateLike,
|
|
130
|
-
fim: DateLike | None = None,
|
|
131
|
-
) -> pl.DataFrame:
|
|
132
|
-
"""Taxa SELIC Over (série SGS 1178).
|
|
133
|
-
|
|
134
|
-
Taxa de juros média diária praticada no mercado interbancário,
|
|
135
|
-
com títulos públicos como garantia.
|
|
136
|
-
|
|
137
|
-
Args:
|
|
138
|
-
inicio: Data inicial.
|
|
139
|
-
fim: Data final. Se ``None``, usa a data mais recente.
|
|
140
|
-
|
|
141
|
-
Returns:
|
|
142
|
-
DataFrame com colunas data e taxa, ou DataFrame vazio.
|
|
143
|
-
|
|
144
|
-
Examples:
|
|
145
|
-
>>> from pyield import bc
|
|
146
|
-
>>> # Sem dados em 26-01-2025 (domingo). Selic mudou por reunião do Copom.
|
|
147
|
-
>>> bc.selic_over_serie("26-01-2025").head(5) # Primeiras 5 linhas
|
|
148
|
-
shape: (5, 2)
|
|
149
|
-
┌────────────┬────────┐
|
|
150
|
-
│ data ┆ taxa │
|
|
151
|
-
│ --- ┆ --- │
|
|
152
|
-
│ date ┆ f64 │
|
|
153
|
-
╞════════════╪════════╡
|
|
154
|
-
│ 2025-01-27 ┆ 0.1215 │
|
|
155
|
-
│ 2025-01-28 ┆ 0.1215 │
|
|
156
|
-
│ 2025-01-29 ┆ 0.1215 │
|
|
157
|
-
│ 2025-01-30 ┆ 0.1315 │
|
|
158
|
-
│ 2025-01-31 ┆ 0.1315 │
|
|
159
|
-
└────────────┴────────┘
|
|
160
|
-
|
|
161
|
-
>>> # Buscando dados para um intervalo específico
|
|
162
|
-
>>> bc.selic_over_serie("14-09-2025", "17-09-2025")
|
|
163
|
-
shape: (3, 2)
|
|
164
|
-
┌────────────┬───────┐
|
|
165
|
-
│ data ┆ taxa │
|
|
166
|
-
│ --- ┆ --- │
|
|
167
|
-
│ date ┆ f64 │
|
|
168
|
-
╞════════════╪═══════╡
|
|
169
|
-
│ 2025-09-15 ┆ 0.149 │
|
|
170
|
-
│ 2025-09-16 ┆ 0.149 │
|
|
171
|
-
│ 2025-09-17 ┆ 0.149 │
|
|
172
|
-
└────────────┴───────┘
|
|
173
|
-
"""
|
|
174
|
-
if any_is_empty(inicio):
|
|
175
|
-
return pl.DataFrame()
|
|
176
|
-
df = _buscar_dados_url(SerieBC.SELIC_OVER, inicio, fim)
|
|
177
|
-
return df.with_columns(pl.col("taxa").round(CASAS_DECIMAIS_ANUALIZADA))
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
def selic_over(data: DateLike) -> float:
|
|
181
|
-
"""Taxa SELIC Over para uma data específica.
|
|
182
|
-
|
|
183
|
-
Args:
|
|
184
|
-
data: Data da consulta.
|
|
185
|
-
|
|
186
|
-
Returns:
|
|
187
|
-
Taxa SELIC Over ou ``nan`` se não disponível.
|
|
188
|
-
|
|
189
|
-
Examples:
|
|
190
|
-
>>> from pyield import bc
|
|
191
|
-
>>> bc.selic_over("31-05-2024")
|
|
192
|
-
0.104
|
|
193
|
-
"""
|
|
194
|
-
if any_is_empty(data):
|
|
195
|
-
return float("nan")
|
|
196
|
-
return _extrair_taxa(selic_over_serie(data, data))
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
def selic_meta_serie(
|
|
200
|
-
inicio: DateLike,
|
|
201
|
-
fim: DateLike | None = None,
|
|
202
|
-
) -> pl.DataFrame:
|
|
203
|
-
"""Taxa SELIC Meta (série SGS 432).
|
|
204
|
-
|
|
205
|
-
Taxa de juros oficial definida pelo COPOM.
|
|
206
|
-
|
|
207
|
-
Args:
|
|
208
|
-
inicio: Data inicial.
|
|
209
|
-
fim: Data final. Se ``None``, usa a data mais recente.
|
|
210
|
-
|
|
211
|
-
Returns:
|
|
212
|
-
DataFrame com colunas data e taxa, ou DataFrame vazio.
|
|
213
|
-
|
|
214
|
-
Examples:
|
|
215
|
-
>>> from pyield import bc
|
|
216
|
-
>>> bc.selic_meta_serie("31-05-2024", "31-05-2024")
|
|
217
|
-
shape: (1, 2)
|
|
218
|
-
┌────────────┬───────┐
|
|
219
|
-
│ data ┆ taxa │
|
|
220
|
-
│ --- ┆ --- │
|
|
221
|
-
│ date ┆ f64 │
|
|
222
|
-
╞════════════╪═══════╡
|
|
223
|
-
│ 2024-05-31 ┆ 0.105 │
|
|
224
|
-
└────────────┴───────┘
|
|
225
|
-
"""
|
|
226
|
-
if any_is_empty(inicio):
|
|
227
|
-
return pl.DataFrame()
|
|
228
|
-
df = _buscar_dados_url(SerieBC.SELIC_META, inicio, fim)
|
|
229
|
-
return df.with_columns(pl.col("taxa").round(CASAS_DECIMAIS_ANUALIZADA))
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
def selic_meta(data: DateLike) -> float:
|
|
233
|
-
"""Taxa SELIC Meta para uma data específica.
|
|
234
|
-
|
|
235
|
-
Args:
|
|
236
|
-
data: Data da consulta.
|
|
237
|
-
|
|
238
|
-
Returns:
|
|
239
|
-
Taxa SELIC Meta ou ``nan`` se não disponível.
|
|
240
|
-
|
|
241
|
-
Examples:
|
|
242
|
-
>>> from pyield import bc
|
|
243
|
-
>>> bc.selic_meta("31-05-2024")
|
|
244
|
-
0.105
|
|
245
|
-
"""
|
|
246
|
-
if any_is_empty(data):
|
|
247
|
-
return float("nan")
|
|
248
|
-
return _extrair_taxa(selic_meta_serie(data, data))
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
def di_over_serie(
|
|
252
|
-
inicio: DateLike,
|
|
253
|
-
fim: DateLike | None = None,
|
|
254
|
-
anualizada: bool = True,
|
|
255
|
-
) -> pl.DataFrame:
|
|
256
|
-
"""Taxa DI Over (série SGS 11).
|
|
257
|
-
|
|
258
|
-
Taxa de juros média dos empréstimos interbancários.
|
|
259
|
-
|
|
260
|
-
Args:
|
|
261
|
-
inicio: Data inicial.
|
|
262
|
-
fim: Data final. Se ``None``, usa a data mais recente.
|
|
263
|
-
anualizada: Se ``True``, retorna a taxa anualizada (base
|
|
264
|
-
252 d.u.). Caso contrário, retorna a taxa diária.
|
|
265
|
-
|
|
266
|
-
Returns:
|
|
267
|
-
DataFrame com colunas data e taxa, ou DataFrame vazio.
|
|
268
|
-
|
|
269
|
-
Examples:
|
|
270
|
-
>>> from pyield import bc
|
|
271
|
-
>>> # Retorna todos os dados desde 29-01-2025
|
|
272
|
-
>>> bc.di_over_serie("29-01-2025").head(5) # Primeiras 5 linhas
|
|
273
|
-
shape: (5, 2)
|
|
274
|
-
┌────────────┬────────┐
|
|
275
|
-
│ data ┆ taxa │
|
|
276
|
-
│ --- ┆ --- │
|
|
277
|
-
│ date ┆ f64 │
|
|
278
|
-
╞════════════╪════════╡
|
|
279
|
-
│ 2025-01-29 ┆ 0.1215 │
|
|
280
|
-
│ 2025-01-30 ┆ 0.1315 │
|
|
281
|
-
│ 2025-01-31 ┆ 0.1315 │
|
|
282
|
-
│ 2025-02-03 ┆ 0.1315 │
|
|
283
|
-
│ 2025-02-04 ┆ 0.1315 │
|
|
284
|
-
└────────────┴────────┘
|
|
285
|
-
"""
|
|
286
|
-
if any_is_empty(inicio):
|
|
287
|
-
return pl.DataFrame()
|
|
288
|
-
df = _buscar_dados_url(SerieBC.DI_OVER, inicio, fim)
|
|
289
|
-
if anualizada:
|
|
290
|
-
return df.with_columns(
|
|
291
|
-
taxa=(((pl.col("taxa") + 1).pow(252)) - 1).round(CASAS_DECIMAIS_ANUALIZADA)
|
|
292
|
-
)
|
|
293
|
-
return df.with_columns(taxa=pl.col("taxa").round(CASAS_DECIMAIS_DIARIA))
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
def di_over(data: DateLike, anualizada: bool = True) -> float:
|
|
297
|
-
"""Taxa DI Over para uma data específica.
|
|
298
|
-
|
|
299
|
-
Args:
|
|
300
|
-
data: Data da consulta.
|
|
301
|
-
anualizada: Se ``True``, retorna a taxa anualizada (base
|
|
302
|
-
252 d.u.). Caso contrário, retorna a taxa diária.
|
|
303
|
-
|
|
304
|
-
Returns:
|
|
305
|
-
Taxa DI Over ou ``nan`` se não disponível.
|
|
306
|
-
|
|
307
|
-
Examples:
|
|
308
|
-
>>> from pyield import bc
|
|
309
|
-
>>> bc.di_over("31-05-2024")
|
|
310
|
-
0.104
|
|
311
|
-
|
|
312
|
-
>>> bc.di_over("28-01-2025", anualizada=False)
|
|
313
|
-
0.00045513
|
|
314
|
-
"""
|
|
315
|
-
if any_is_empty(data):
|
|
316
|
-
return float("nan")
|
|
317
|
-
return _extrair_taxa(di_over_serie(data, data, anualizada))
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|