pyield 0.48.5__tar.gz → 0.48.7__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.5 → pyield-0.48.7}/PKG-INFO +1 -1
- pyield-0.48.7/pyield/__about__.py +1 -0
- {pyield-0.48.5 → pyield-0.48.7}/pyield/bc/__init__.py +3 -2
- pyield-0.48.7/pyield/bc/_olinda.py +33 -0
- {pyield-0.48.5 → pyield-0.48.7}/pyield/bc/compromissada.py +38 -69
- {pyield-0.48.5 → pyield-0.48.7}/pyield/bc/leiloes.py +29 -57
- pyield-0.48.5/pyield/bc/selic.py → pyield-0.48.7/pyield/bc/sgs.py +170 -50
- {pyield-0.48.5 → pyield-0.48.7}/pyield/tn/leiloes.py +2 -2
- {pyield-0.48.5 → pyield-0.48.7}/pyield/tn/utils.py +1 -1
- pyield-0.48.5/pyield/__about__.py +0 -1
- pyield-0.48.5/pyield/bc/ptax.py +0 -196
- {pyield-0.48.5 → pyield-0.48.7}/.gitignore +0 -0
- {pyield-0.48.5 → pyield-0.48.7}/LICENSE +0 -0
- {pyield-0.48.5 → pyield-0.48.7}/README.md +0 -0
- {pyield-0.48.5 → pyield-0.48.7}/pyield/__init__.py +0 -0
- {pyield-0.48.5 → pyield-0.48.7}/pyield/_internal/__init__.py +0 -0
- {pyield-0.48.5 → pyield-0.48.7}/pyield/_internal/br_numbers.py +0 -0
- {pyield-0.48.5 → pyield-0.48.7}/pyield/_internal/cache.py +0 -0
- {pyield-0.48.5 → pyield-0.48.7}/pyield/_internal/converters.py +0 -0
- {pyield-0.48.5 → pyield-0.48.7}/pyield/_internal/data_cache.py +0 -0
- {pyield-0.48.5 → pyield-0.48.7}/pyield/_internal/retry.py +0 -0
- {pyield-0.48.5 → pyield-0.48.7}/pyield/_internal/types.py +0 -0
- {pyield-0.48.5 → pyield-0.48.7}/pyield/anbima/__init__.py +0 -0
- {pyield-0.48.5 → pyield-0.48.7}/pyield/anbima/ettj_intradia.py +0 -0
- {pyield-0.48.5 → pyield-0.48.7}/pyield/anbima/ettj_ultima.py +0 -0
- {pyield-0.48.5 → pyield-0.48.7}/pyield/anbima/ima_ultimo.py +0 -0
- {pyield-0.48.5 → pyield-0.48.7}/pyield/anbima/imaq.py +0 -0
- {pyield-0.48.5 → pyield-0.48.7}/pyield/anbima/tpf.py +0 -0
- {pyield-0.48.5 → pyield-0.48.7}/pyield/b3/__init__.py +0 -0
- {pyield-0.48.5 → pyield-0.48.7}/pyield/b3/_contratos.py +0 -0
- {pyield-0.48.5 → pyield-0.48.7}/pyield/b3/_validar_pregao.py +0 -0
- {pyield-0.48.5 → pyield-0.48.7}/pyield/b3/boletim.py +0 -0
- {pyield-0.48.5 → pyield-0.48.7}/pyield/b3/derivativos_intradia.py +0 -0
- {pyield-0.48.5 → pyield-0.48.7}/pyield/b3/di1.py +0 -0
- {pyield-0.48.5 → pyield-0.48.7}/pyield/b3/di_over.py +0 -0
- {pyield-0.48.5 → pyield-0.48.7}/pyield/b3/futuro/__init__.py +0 -0
- {pyield-0.48.5 → pyield-0.48.7}/pyield/b3/futuro/contratos.py +0 -0
- {pyield-0.48.5 → pyield-0.48.7}/pyield/b3/futuro/historico.py +0 -0
- {pyield-0.48.5 → pyield-0.48.7}/pyield/b3/futuro/intradia.py +0 -0
- {pyield-0.48.5 → pyield-0.48.7}/pyield/bc/copom.py +0 -0
- {pyield-0.48.5 → pyield-0.48.7}/pyield/bc/tpf_intradia.py +0 -0
- {pyield-0.48.5 → pyield-0.48.7}/pyield/bc/tpf_mensal.py +0 -0
- {pyield-0.48.5 → pyield-0.48.7}/pyield/bc/vna.py +0 -0
- {pyield-0.48.5 → pyield-0.48.7}/pyield/du/__init__.py +0 -0
- {pyield-0.48.5 → pyield-0.48.7}/pyield/du/core.py +0 -0
- {pyield-0.48.5 → pyield-0.48.7}/pyield/du/feriados/__init__.py +0 -0
- {pyield-0.48.5 → pyield-0.48.7}/pyield/du/feriados/feriados_antigos_br.txt +0 -0
- {pyield-0.48.5 → pyield-0.48.7}/pyield/du/feriados/feriados_br.py +0 -0
- {pyield-0.48.5 → pyield-0.48.7}/pyield/du/feriados/feriados_novos_br.txt +0 -0
- {pyield-0.48.5 → pyield-0.48.7}/pyield/fwd.py +0 -0
- {pyield-0.48.5 → pyield-0.48.7}/pyield/interpolador.py +0 -0
- {pyield-0.48.5 → pyield-0.48.7}/pyield/ipca/__init__.py +0 -0
- {pyield-0.48.5 → pyield-0.48.7}/pyield/ipca/historico.py +0 -0
- {pyield-0.48.5 → pyield-0.48.7}/pyield/ipca/projetado.py +0 -0
- {pyield-0.48.5 → pyield-0.48.7}/pyield/py.typed +0 -0
- {pyield-0.48.5 → pyield-0.48.7}/pyield/relogio.py +0 -0
- {pyield-0.48.5 → pyield-0.48.7}/pyield/selic/__init__.py +0 -0
- {pyield-0.48.5 → pyield-0.48.7}/pyield/selic/cpm.py +0 -0
- {pyield-0.48.5 → pyield-0.48.7}/pyield/selic/probabilities.py +0 -0
- {pyield-0.48.5 → pyield-0.48.7}/pyield/tn/__init__.py +0 -0
- {pyield-0.48.5 → pyield-0.48.7}/pyield/tn/benchmark.py +0 -0
- {pyield-0.48.5 → pyield-0.48.7}/pyield/tn/lft.py +0 -0
- {pyield-0.48.5 → pyield-0.48.7}/pyield/tn/ltn.py +0 -0
- {pyield-0.48.5 → pyield-0.48.7}/pyield/tn/ntnb.py +0 -0
- {pyield-0.48.5 → pyield-0.48.7}/pyield/tn/ntnb1.py +0 -0
- {pyield-0.48.5 → pyield-0.48.7}/pyield/tn/ntnbprinc.py +0 -0
- {pyield-0.48.5 → pyield-0.48.7}/pyield/tn/ntnc.py +0 -0
- {pyield-0.48.5 → pyield-0.48.7}/pyield/tn/ntnf.py +0 -0
- {pyield-0.48.5 → pyield-0.48.7}/pyield/tn/pre.py +0 -0
- {pyield-0.48.5 → pyield-0.48.7}/pyield/tn/rmd.py +0 -0
- {pyield-0.48.5 → pyield-0.48.7}/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.7
|
|
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.7"
|
|
@@ -1,8 +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
|
-
|
|
4
|
+
from pyield.bc.sgs import (
|
|
5
|
+
ptax,
|
|
6
|
+
ptax_serie,
|
|
6
7
|
selic_meta,
|
|
7
8
|
selic_meta_serie,
|
|
8
9
|
selic_over,
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"""Helpers compartilhados para acesso à API OData do BCB (olinda.bcb.gov.br)."""
|
|
2
|
+
|
|
3
|
+
import polars as pl
|
|
4
|
+
import requests
|
|
5
|
+
|
|
6
|
+
from pyield._internal.retry import retry_padrao
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def montar_url(url_base: str, parametros: dict[str, str]) -> str:
|
|
10
|
+
"""Monta URL OData com parâmetros opcionais e formato CSV.
|
|
11
|
+
|
|
12
|
+
Args:
|
|
13
|
+
url_base: URL base do endpoint OData (com '?' no final).
|
|
14
|
+
parametros: Dicionário ``{nome_param: valor}`` já formatado.
|
|
15
|
+
Parâmetros com valor vazio são ignorados.
|
|
16
|
+
"""
|
|
17
|
+
partes = [f"@{k}='{v}'" for k, v in parametros.items() if v]
|
|
18
|
+
return url_base + "&".join(partes) + "&$format=text/csv"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@retry_padrao
|
|
22
|
+
def buscar_csv(url: str) -> bytes:
|
|
23
|
+
"""Busca CSV da API OData do BCB com retry automático."""
|
|
24
|
+
r = requests.get(url, timeout=10)
|
|
25
|
+
r.raise_for_status()
|
|
26
|
+
return r.content
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def parsear_csv(dados: bytes) -> pl.DataFrame:
|
|
30
|
+
"""Lê CSV OData como DataFrame sem inferência de tipos."""
|
|
31
|
+
if not dados.strip():
|
|
32
|
+
return pl.DataFrame()
|
|
33
|
+
return pl.read_csv(dados, infer_schema=False, null_values=["null", ""])
|
|
@@ -13,63 +13,27 @@ ac1b013d13d6fb1d9d9e251b8000121e, 2025-08-21 , 12:00 , TodoMercado
|
|
|
13
13
|
"""
|
|
14
14
|
|
|
15
15
|
import polars as pl
|
|
16
|
-
import requests
|
|
17
16
|
|
|
18
17
|
import pyield._internal.converters as cv
|
|
19
18
|
from pyield import du
|
|
20
19
|
from pyield._internal.br_numbers import float_br, taxa_br
|
|
21
|
-
from pyield._internal.retry import retry_padrao
|
|
22
20
|
from pyield._internal.types import DateLike
|
|
21
|
+
from pyield.bc._olinda import buscar_csv, montar_url, parsear_csv
|
|
23
22
|
|
|
24
23
|
URL_BASE_API = "https://olinda.bcb.gov.br/olinda/servico/leiloes_selic/versao/v1/odata/leiloes_compromissadas(dataLancamentoInicio=@dataLancamentoInicio,dataLancamentoFim=@dataLancamentoFim,horaInicio=@horaInicio,dataLiquidacao=@dataLiquidacao,dataRetorno=@dataRetorno,publicoPermitidoLeilao=@publicoPermitidoLeilao,nomeTipoOferta=@nomeTipoOferta)?"
|
|
25
24
|
|
|
26
25
|
|
|
27
|
-
def
|
|
26
|
+
def _montar_parametros(
|
|
28
27
|
inicio: DateLike | None,
|
|
29
28
|
fim: DateLike | None,
|
|
30
|
-
) -> str:
|
|
31
|
-
"""
|
|
32
|
-
|
|
33
|
-
Regras da API:
|
|
34
|
-
- Apenas inicio: retorna de inicio até o fim da série.
|
|
35
|
-
- Apenas fim: retorna do início da série até fim.
|
|
36
|
-
- Ambos ausentes: retorna a série completa.
|
|
37
|
-
"""
|
|
38
|
-
url = URL_BASE_API
|
|
29
|
+
) -> dict[str, str]:
|
|
30
|
+
"""Converte parâmetros opcionais de período em dicionário para a URL."""
|
|
31
|
+
params: dict[str, str] = {}
|
|
39
32
|
if inicio:
|
|
40
|
-
|
|
41
|
-
inicio_str = inicio_dt.strftime("%Y-%m-%d")
|
|
42
|
-
url += f"@dataLancamentoInicio='{inicio_str}'"
|
|
43
|
-
|
|
33
|
+
params["dataLancamentoInicio"] = cv.converter_datas(inicio).strftime("%Y-%m-%d")
|
|
44
34
|
if fim:
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
url += f"&@dataLancamentoFim='{fim_str}'"
|
|
48
|
-
|
|
49
|
-
url += "&$format=text/csv" # Adiciona o formato CSV ao final
|
|
50
|
-
|
|
51
|
-
return url
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
@retry_padrao
|
|
55
|
-
def _buscar_csv_api(url: str) -> bytes:
|
|
56
|
-
"""Executa requisição HTTP e retorna o corpo CSV como string.
|
|
57
|
-
|
|
58
|
-
Decorado com ``retry_padrao`` para resiliência a falhas transitórias.
|
|
59
|
-
Levanta exceções de status HTTP para tratamento a montante.
|
|
60
|
-
"""
|
|
61
|
-
r = requests.get(url, timeout=10)
|
|
62
|
-
r.raise_for_status()
|
|
63
|
-
return r.content
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
def _ler_csv(csv_bytes: bytes) -> pl.DataFrame:
|
|
67
|
-
"""Lê o CSV (bytes) em um DataFrame Polars sem inferência de tipos."""
|
|
68
|
-
return pl.read_csv(
|
|
69
|
-
csv_bytes,
|
|
70
|
-
infer_schema=False,
|
|
71
|
-
null_values=["null", ""],
|
|
72
|
-
)
|
|
35
|
+
params["dataLancamentoFim"] = cv.converter_datas(fim).strftime("%Y-%m-%d")
|
|
36
|
+
return params
|
|
73
37
|
|
|
74
38
|
|
|
75
39
|
def _processar_df(df: pl.DataFrame) -> pl.DataFrame:
|
|
@@ -80,14 +44,14 @@ def _processar_df(df: pl.DataFrame) -> pl.DataFrame:
|
|
|
80
44
|
data_liquidacao=pl.col("dataLiquidacao").str.to_date("%Y-%m-%d"),
|
|
81
45
|
data_retorno=pl.col("dataRetorno").str.to_date("%Y-%m-%d"),
|
|
82
46
|
hora_inicio=pl.col("horaInicio").str.to_time("%H:%M"),
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
47
|
+
prazo_dc=pl.col("prazoDiasCorridos").cast(pl.Int64),
|
|
48
|
+
prazo_du=du.contar_expr("dataLiquidacao", "dataRetorno"),
|
|
49
|
+
comunicado=pl.col("numeroComunicado").cast(pl.Int64),
|
|
86
50
|
tipo_oferta=pl.col("nomeTipoOferta"),
|
|
87
|
-
|
|
88
|
-
|
|
51
|
+
publico=pl.col("publicoPermitidoLeilao"),
|
|
52
|
+
financeiro_aceito=1000 * pl.col("volumeAceito").cast(pl.Float64),
|
|
89
53
|
taxa_corte=pl.when(vol_zero).then(None).otherwise(taxa_br("taxaCorte")),
|
|
90
|
-
|
|
54
|
+
pct_aceito=pl.when(vol_zero)
|
|
91
55
|
.then(0.0)
|
|
92
56
|
.otherwise(100 - float_br("percentualCorte")),
|
|
93
57
|
).sort("data_leilao", "hora_inicio", "tipo_oferta")
|
|
@@ -117,36 +81,41 @@ def compromissadas(
|
|
|
117
81
|
- data_liquidacao (Date): data de liquidação (início da operação).
|
|
118
82
|
- data_retorno (Date): data de recompra / término da operação.
|
|
119
83
|
- hora_inicio (Time): horário de início do leilão.
|
|
120
|
-
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
- numero_comunicado (Int64): número do comunicado/aviso do BC (pode ser nulo).
|
|
84
|
+
- prazo_dc (Int64): dias corridos até a data de retorno.
|
|
85
|
+
- prazo_du (Int64): dias úteis entre liquidação e retorno.
|
|
86
|
+
- comunicado (Int64): número do comunicado/aviso do BC (pode ser nulo).
|
|
124
87
|
- tipo_oferta (String): classif. do tipo de oferta (ex: Tomador, Compromissada 1047).
|
|
125
|
-
-
|
|
126
|
-
-
|
|
127
|
-
- taxa_corte (Float64): taxa de corte (ex. 0.1490 = 14,90%). Nula se
|
|
128
|
-
-
|
|
88
|
+
- publico (String): público permitido no leilão (SomenteDealer, TodoMercado).
|
|
89
|
+
- financeiro_aceito (Float64): financeiro aceito no leilão em reais (convertido de milhares).
|
|
90
|
+
- taxa_corte (Float64): taxa de corte (ex. 0.1490 = 14,90%). Nula se financeiro_aceito = 0.
|
|
91
|
+
- pct_aceito (Float64): percentual do volume ofertado efetivamente aceito (0-100).
|
|
129
92
|
100 = nenhuma rejeição. 0 indica nada aceito (volume_aceito = 0).
|
|
130
93
|
|
|
131
94
|
Notes:
|
|
132
95
|
- Dados ordenados por: data_leilao, hora_inicio, tipo_oferta.
|
|
133
96
|
|
|
134
97
|
Examples:
|
|
98
|
+
>>> import polars as pl
|
|
99
|
+
>>> _ = pl.Config.set_tbl_width_chars(210)
|
|
100
|
+
>>> _ = pl.Config.set_tbl_cols(-1)
|
|
135
101
|
>>> from pyield import bc
|
|
136
102
|
>>> bc.compromissadas(inicio="21-08-2025", fim="21-08-2025")
|
|
137
103
|
shape: (2, 12)
|
|
138
|
-
|
|
139
|
-
│ data_leilao ┆ data_liquidacao ┆ data_retorno ┆ hora_inicio ┆
|
|
140
|
-
│ --- ┆ --- ┆ --- ┆ --- ┆
|
|
141
|
-
│ date ┆ date ┆ date ┆ time ┆
|
|
142
|
-
|
|
143
|
-
│ 2025-08-21 ┆ 2025-08-21 ┆ 2025-08-22 ┆ 09:00:00 ┆
|
|
144
|
-
│ 2025-08-21 ┆ 2025-08-22 ┆ 2025-11-21 ┆ 12:00:00 ┆
|
|
145
|
-
|
|
104
|
+
┌─────────────┬─────────────────┬──────────────┬─────────────┬──────────┬──────────┬────────────┬────────────────────┬───────────────┬───────────────────┬────────────┬────────────┐
|
|
105
|
+
│ data_leilao ┆ data_liquidacao ┆ data_retorno ┆ hora_inicio ┆ prazo_dc ┆ prazo_du ┆ comunicado ┆ tipo_oferta ┆ publico ┆ financeiro_aceito ┆ taxa_corte ┆ pct_aceito │
|
|
106
|
+
│ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- │
|
|
107
|
+
│ date ┆ date ┆ date ┆ time ┆ i64 ┆ i64 ┆ i64 ┆ str ┆ str ┆ f64 ┆ f64 ┆ f64 │
|
|
108
|
+
╞═════════════╪═════════════════╪══════════════╪═════════════╪══════════╪══════════╪════════════╪════════════════════╪═══════════════╪═══════════════════╪════════════╪════════════╡
|
|
109
|
+
│ 2025-08-21 ┆ 2025-08-21 ┆ 2025-08-22 ┆ 09:00:00 ┆ 1 ┆ 1 ┆ null ┆ Tomador ┆ SomenteDealer ┆ 6.4771e11 ┆ 0.149 ┆ 100.0 │
|
|
110
|
+
│ 2025-08-21 ┆ 2025-08-22 ┆ 2025-11-21 ┆ 12:00:00 ┆ 91 ┆ 64 ┆ 43716 ┆ Compromissada 1047 ┆ TodoMercado ┆ 5.0000e9 ┆ 0.9978 ┆ 35.87 │
|
|
111
|
+
└─────────────┴─────────────────┴──────────────┴─────────────┴──────────┴──────────┴────────────┴────────────────────┴───────────────┴───────────────────┴────────────┴────────────┘
|
|
112
|
+
>>> _ = pl.Config.restore_defaults()
|
|
113
|
+
>>> _ = pl.Config.set_tbl_width_chars(150)
|
|
146
114
|
"""
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
115
|
+
params = _montar_parametros(inicio, fim)
|
|
116
|
+
url = montar_url(URL_BASE_API, params)
|
|
117
|
+
dados = buscar_csv(url)
|
|
118
|
+
df = parsear_csv(dados)
|
|
150
119
|
if df.is_empty():
|
|
151
120
|
return pl.DataFrame()
|
|
152
121
|
return _processar_df(df)
|
|
@@ -12,14 +12,13 @@ from typing import Literal
|
|
|
12
12
|
|
|
13
13
|
import polars as pl
|
|
14
14
|
import polars.selectors as cs
|
|
15
|
-
import requests
|
|
16
15
|
|
|
17
16
|
import pyield._internal.converters as cv
|
|
18
17
|
from pyield import du
|
|
19
18
|
from pyield._internal.br_numbers import float_br, taxa_br
|
|
20
|
-
from pyield._internal.retry import retry_padrao
|
|
21
19
|
from pyield._internal.types import DateLike
|
|
22
|
-
from pyield.bc.
|
|
20
|
+
from pyield.bc._olinda import buscar_csv, montar_url, parsear_csv
|
|
21
|
+
from pyield.bc.sgs import ptax_serie
|
|
23
22
|
from pyield.tn.ntnb import duration as duration_b
|
|
24
23
|
from pyield.tn.ntnf import duration as duration_f
|
|
25
24
|
|
|
@@ -74,39 +73,17 @@ CHAVES_ORDENACAO = ["data_leilao", "tipo_leilao", "titulo", "data_vencimento"]
|
|
|
74
73
|
URL_BASE_API = "https://olinda.bcb.gov.br/olinda/servico/leiloes_selic/versao/v1/odata/leiloesTitulosPublicos(dataMovimentoInicio=@dataMovimentoInicio,dataMovimentoFim=@dataMovimentoFim,dataLiquidacao=@dataLiquidacao,codigoTitulo=@codigoTitulo,dataVencimento=@dataVencimento,edital=@edital,tipoPublico=@tipoPublico,tipoOferta=@tipoOferta)?"
|
|
75
74
|
|
|
76
75
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
def _montar_url(
|
|
76
|
+
def _montar_parametros(
|
|
81
77
|
inicio: DateLike | None = None,
|
|
82
78
|
fim: DateLike | None = None,
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
79
|
+
) -> dict[str, str]:
|
|
80
|
+
"""Converte parâmetros opcionais de período em dicionário para a URL."""
|
|
81
|
+
params: dict[str, str] = {}
|
|
86
82
|
if inicio:
|
|
87
|
-
|
|
88
|
-
url += f"@dataMovimentoInicio='{inicio:%Y-%m-%d}'"
|
|
83
|
+
params["dataMovimentoInicio"] = cv.converter_datas(inicio).strftime("%Y-%m-%d")
|
|
89
84
|
if fim:
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
if tipo_leilao:
|
|
93
|
-
url += f"&@tipoOferta='{MAPA_TIPO_LEILAO[tipo_leilao.lower()]}'"
|
|
94
|
-
url += "&$format=text/csv"
|
|
95
|
-
return url
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
@retry_padrao
|
|
99
|
-
def _buscar_csv(url: str) -> bytes:
|
|
100
|
-
resposta = requests.get(url, timeout=10)
|
|
101
|
-
resposta.raise_for_status()
|
|
102
|
-
return resposta.content
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
def _parsear_df(dados: bytes) -> pl.DataFrame:
|
|
106
|
-
"""Lê CSV como strings."""
|
|
107
|
-
if not dados.strip():
|
|
108
|
-
return pl.DataFrame()
|
|
109
|
-
return pl.read_csv(dados, infer_schema=False, null_values=["null"])
|
|
85
|
+
params["dataMovimentoFim"] = cv.converter_datas(fim).strftime("%Y-%m-%d")
|
|
86
|
+
return params
|
|
110
87
|
|
|
111
88
|
|
|
112
89
|
def _processar_df(df: pl.DataFrame) -> pl.DataFrame:
|
|
@@ -147,9 +124,7 @@ def _processar_df(df: pl.DataFrame) -> pl.DataFrame:
|
|
|
147
124
|
pu_corte=float_br("cotacaoCorte"),
|
|
148
125
|
taxa_media=taxa_br("taxaMedia"),
|
|
149
126
|
taxa_corte=taxa_br("taxaCorte"),
|
|
150
|
-
financeiro_total=
|
|
151
|
-
.round(0)
|
|
152
|
-
.cast(pl.Int64),
|
|
127
|
+
financeiro_total=float_br("financeiro") * 1_000_000,
|
|
153
128
|
quantidade_ofertada_1v=pl.col("quantidadeOfertada").cast(pl.Int64),
|
|
154
129
|
quantidade_aceita_1v=pl.col("quantidadeAceita").cast(pl.Int64),
|
|
155
130
|
quantidade_liquidada_1v=pl.col("quantidadeLiquidada").cast(pl.Int64),
|
|
@@ -182,9 +157,7 @@ def _processar_df(df: pl.DataFrame) -> pl.DataFrame:
|
|
|
182
157
|
(pl.col("quantidade_aceita_1v") / pl.col("quantidade_aceita_total"))
|
|
183
158
|
* pl.col("financeiro_total")
|
|
184
159
|
)
|
|
185
|
-
.otherwise(0)
|
|
186
|
-
.round(0)
|
|
187
|
-
.cast(pl.Int64),
|
|
160
|
+
.otherwise(0.0),
|
|
188
161
|
)
|
|
189
162
|
.with_columns(
|
|
190
163
|
financeiro_2v=pl.col("financeiro_total") - pl.col("financeiro_1v"),
|
|
@@ -240,8 +213,8 @@ def _buscar_ptax(df: pl.DataFrame) -> pl.DataFrame:
|
|
|
240
213
|
return pl.DataFrame()
|
|
241
214
|
|
|
242
215
|
return (
|
|
243
|
-
df_ptax.select("data", "
|
|
244
|
-
.rename({"data": "data_ref", "
|
|
216
|
+
df_ptax.select("data", "cotacao")
|
|
217
|
+
.rename({"data": "data_ref", "cotacao": "ptax"})
|
|
245
218
|
.sort("data_ref")
|
|
246
219
|
)
|
|
247
220
|
|
|
@@ -319,9 +292,9 @@ def leiloes(
|
|
|
319
292
|
* quantidade_aceita_1v (Int64): qtd aceita 1ª volta.
|
|
320
293
|
* quantidade_aceita_2v (Int64): qtd aceita 2ª volta.
|
|
321
294
|
* quantidade_aceita_total (Int64): qtd total aceita.
|
|
322
|
-
* financeiro_1v (
|
|
323
|
-
* financeiro_2v (
|
|
324
|
-
* financeiro_total (
|
|
295
|
+
* financeiro_1v (Float64): financeiro 1ª volta em R$.
|
|
296
|
+
* financeiro_2v (Float64): financeiro 2ª volta em R$.
|
|
297
|
+
* financeiro_total (Float64): financeiro total em R$.
|
|
325
298
|
|
|
326
299
|
Notes:
|
|
327
300
|
1v = primeira volta (rodada), 2v = segunda volta.
|
|
@@ -333,24 +306,23 @@ def leiloes(
|
|
|
333
306
|
┌─────────────┬─────────────────┬─────────────┬───────────────┬───┬─────────────────────────┬───────────────┬───────────────┬──────────────────┐
|
|
334
307
|
│ data_leilao ┆ data_liquidacao ┆ tipo_leilao ┆ numero_edital ┆ … ┆ quantidade_aceita_total ┆ financeiro_1v ┆ financeiro_2v ┆ financeiro_total │
|
|
335
308
|
│ --- ┆ --- ┆ --- ┆ --- ┆ ┆ --- ┆ --- ┆ --- ┆ --- │
|
|
336
|
-
│ date ┆ date ┆ str ┆ i64 ┆ ┆ i64 ┆
|
|
309
|
+
│ date ┆ date ┆ str ┆ i64 ┆ ┆ i64 ┆ f64 ┆ f64 ┆ f64 │
|
|
337
310
|
╞═════════════╪═════════════════╪═════════════╪═══════════════╪═══╪═════════════════════════╪═══════════════╪═══════════════╪══════════════════╡
|
|
338
|
-
│ 2025-08-19 ┆ 2025-08-20 ┆ Venda ┆ 192 ┆ … ┆ 150000 ┆
|
|
339
|
-
│ 2025-08-19 ┆ 2025-08-20 ┆ Venda ┆ 192 ┆ … ┆ 751003 ┆
|
|
340
|
-
│ 2025-08-19 ┆ 2025-08-20 ┆ Venda ┆ 193 ┆ … ┆ 300759 ┆
|
|
341
|
-
│ 2025-08-19 ┆ 2025-08-20 ┆ Venda ┆ 194 ┆ … ┆ 500542 ┆
|
|
342
|
-
│ 2025-08-19 ┆ 2025-08-20 ┆ Venda ┆ 194 ┆ … ┆ 500000 ┆
|
|
311
|
+
│ 2025-08-19 ┆ 2025-08-20 ┆ Venda ┆ 192 ┆ … ┆ 150000 ┆ 2.5724e9 ┆ 0.0 ┆ 2.5724e9 │
|
|
312
|
+
│ 2025-08-19 ┆ 2025-08-20 ┆ Venda ┆ 192 ┆ … ┆ 751003 ┆ 1.2804e10 ┆ 1.7124e7 ┆ 1.2822e10 │
|
|
313
|
+
│ 2025-08-19 ┆ 2025-08-20 ┆ Venda ┆ 193 ┆ … ┆ 300759 ┆ 1.2899e9 ┆ 3.2635e6 ┆ 1.2932e9 │
|
|
314
|
+
│ 2025-08-19 ┆ 2025-08-20 ┆ Venda ┆ 194 ┆ … ┆ 500542 ┆ 2.0717e9 ┆ 2.2457e6 ┆ 2.0739e9 │
|
|
315
|
+
│ 2025-08-19 ┆ 2025-08-20 ┆ Venda ┆ 194 ┆ … ┆ 500000 ┆ 2.0107e9 ┆ 0.0 ┆ 2.0107e9 │
|
|
343
316
|
└─────────────┴─────────────────┴─────────────┴───────────────┴───┴─────────────────────────┴───────────────┴───────────────┴──────────────────┘
|
|
344
317
|
"""
|
|
345
|
-
url =
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
tipo_leilao=tipo_leilao,
|
|
349
|
-
)
|
|
350
|
-
dados = _buscar_csv(url)
|
|
351
|
-
df = _parsear_df(dados)
|
|
318
|
+
url = montar_url(URL_BASE_API, _montar_parametros(inicio, fim))
|
|
319
|
+
dados = buscar_csv(url)
|
|
320
|
+
df = parsear_csv(dados)
|
|
352
321
|
if df.is_empty():
|
|
353
322
|
return pl.DataFrame()
|
|
354
323
|
df = _processar_df(df)
|
|
355
324
|
df = _adicionar_dv01_usd(df)
|
|
356
|
-
|
|
325
|
+
df = df.select(ORDEM_COLUNAS_FINAL).sort(CHAVES_ORDENACAO)
|
|
326
|
+
if tipo_leilao:
|
|
327
|
+
df = df.filter(pl.col("tipo_leilao").str.to_lowercase() == tipo_leilao.lower())
|
|
328
|
+
return df
|
|
@@ -1,22 +1,24 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""Séries do Sistema Gerenciador de Séries (SGS) do Banco Central.
|
|
2
2
|
|
|
3
3
|
Séries disponíveis:
|
|
4
|
-
-
|
|
4
|
+
- PTAX Venda (SGS 1)
|
|
5
5
|
- SELIC Meta (SGS 432)
|
|
6
|
+
- SELIC Over (SGS 1178)
|
|
6
7
|
|
|
7
8
|
Exemplos de chamada à API:
|
|
8
9
|
https://api.bcb.gov.br/dados/serie/bcdata.sgs.1178/dados?formato=json&dataInicial=29/01/2025&dataFinal=31/01/2025
|
|
9
10
|
https://api.bcb.gov.br/dados/serie/bcdata.sgs.1178/dados/ultimos/5?formato=json
|
|
10
11
|
|
|
11
|
-
Exemplo de resposta JSON da API do BCB
|
|
12
|
+
Exemplo de resposta JSON da API do BCB:
|
|
12
13
|
[{"data":"29/01/2025","valor":"12.15"},
|
|
13
14
|
{"data":"30/01/2025","valor":"13.15"},
|
|
14
15
|
{"data":"31/01/2025","valor":"13.15"}]
|
|
15
16
|
|
|
16
17
|
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
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.
|
|
20
22
|
"""
|
|
21
23
|
|
|
22
24
|
import datetime as dt
|
|
@@ -31,39 +33,39 @@ from pyield._internal.converters import converter_datas
|
|
|
31
33
|
from pyield._internal.retry import retry_padrao
|
|
32
34
|
from pyield._internal.types import DateLike, any_is_empty
|
|
33
35
|
|
|
34
|
-
|
|
35
|
-
def _extrair_taxa(df: pl.DataFrame) -> float:
|
|
36
|
-
"""Extrai a taxa escalar de um DataFrame ou retorna nan se vazio."""
|
|
37
|
-
if df.is_empty():
|
|
38
|
-
return float("nan")
|
|
39
|
-
return df["taxa"].item(0)
|
|
40
|
-
|
|
41
|
-
|
|
42
36
|
URL_BASE = "https://api.bcb.gov.br/dados/serie/bcdata.sgs."
|
|
43
|
-
|
|
37
|
+
|
|
38
|
+
ESQUEMA_BRUTO = {"data": pl.Date, "valor": pl.Float64}
|
|
44
39
|
|
|
45
40
|
# Limite de segurança em dias, correspondendo a ~9.5 anos.
|
|
46
41
|
# Evita a complexidade do cálculo exato de 10 anos-calendário.
|
|
47
42
|
LIMITE_DIAS_SEGURO = 3500 # aprox 365 * 9.5
|
|
48
43
|
|
|
44
|
+
CASAS_DECIMAIS_TAXA = 4 # Selic: 2 casas no formato percentual → 4 em decimal
|
|
45
|
+
CASAS_DECIMAIS_PTAX = 4
|
|
49
46
|
|
|
50
|
-
class SerieBC(Enum):
|
|
51
|
-
"""Enum para as séries disponíveis no Banco Central."""
|
|
52
47
|
|
|
53
|
-
|
|
48
|
+
class SerieSGS(Enum):
|
|
49
|
+
"""Enum para as séries disponíveis no SGS do Banco Central."""
|
|
50
|
+
|
|
51
|
+
PTAX_VENDA = 1
|
|
54
52
|
SELIC_META = 432
|
|
53
|
+
SELIC_OVER = 1178
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
# ── Infraestrutura de acesso à API SGS ──────────────────────────────
|
|
55
57
|
|
|
56
58
|
|
|
57
59
|
@ttl_cache()
|
|
58
60
|
@retry_padrao
|
|
59
61
|
def _chamar_api(url_api: str) -> list[dict[str, str]]:
|
|
60
|
-
resposta = requests.get(url_api, timeout=
|
|
62
|
+
resposta = requests.get(url_api, timeout=30)
|
|
61
63
|
resposta.raise_for_status()
|
|
62
64
|
return resposta.json()
|
|
63
65
|
|
|
64
66
|
|
|
65
67
|
def _montar_url_intervalo(
|
|
66
|
-
serie:
|
|
68
|
+
serie: SerieSGS, inicio: dt.date, fim: dt.date | None = None
|
|
67
69
|
) -> str:
|
|
68
70
|
inicio_str = inicio.strftime("%d/%m/%Y")
|
|
69
71
|
url = f"{URL_BASE}{serie.value}/dados?formato=json&dataInicial={inicio_str}"
|
|
@@ -72,37 +74,30 @@ def _montar_url_intervalo(
|
|
|
72
74
|
return url
|
|
73
75
|
|
|
74
76
|
|
|
75
|
-
def _montar_url_ultimos(serie:
|
|
77
|
+
def _montar_url_ultimos(serie: SerieSGS, n: int) -> str:
|
|
76
78
|
return f"{URL_BASE}{serie.value}/dados/ultimos/{n}?formato=json"
|
|
77
79
|
|
|
78
80
|
|
|
79
81
|
def _buscar_api(url_api: str) -> pl.DataFrame:
|
|
80
|
-
"""Busca dados da API e
|
|
81
|
-
esquema = {"data": pl.Date, "taxa": pl.Float64}
|
|
82
|
-
|
|
82
|
+
"""Busca dados da API e retorna DataFrame bruto {data, valor}."""
|
|
83
83
|
try:
|
|
84
84
|
dados = _chamar_api(url_api)
|
|
85
85
|
except requests.exceptions.HTTPError as e:
|
|
86
86
|
if e.response.status_code == 404: # noqa
|
|
87
|
-
return pl.DataFrame(schema=
|
|
87
|
+
return pl.DataFrame(schema=ESQUEMA_BRUTO)
|
|
88
88
|
raise
|
|
89
89
|
|
|
90
90
|
if not dados:
|
|
91
|
-
return pl.DataFrame(schema=
|
|
91
|
+
return pl.DataFrame(schema=ESQUEMA_BRUTO)
|
|
92
92
|
|
|
93
|
-
|
|
93
|
+
return pl.from_dicts(dados).select(
|
|
94
94
|
pl.col("data").str.to_date("%d/%m/%Y"),
|
|
95
|
-
pl.col("valor")
|
|
96
|
-
.cast(pl.Float64)
|
|
97
|
-
.truediv(100)
|
|
98
|
-
.round(CASAS_DECIMAIS_ANUALIZADA)
|
|
99
|
-
.alias("taxa"),
|
|
95
|
+
pl.col("valor").cast(pl.Float64),
|
|
100
96
|
)
|
|
101
|
-
return df
|
|
102
97
|
|
|
103
98
|
|
|
104
99
|
def _buscar_dados_url(
|
|
105
|
-
serie:
|
|
100
|
+
serie: SerieSGS,
|
|
106
101
|
inicio: DateLike,
|
|
107
102
|
fim: DateLike | None = None,
|
|
108
103
|
) -> pl.DataFrame:
|
|
@@ -124,11 +119,51 @@ def _buscar_dados_url(
|
|
|
124
119
|
todos_dfs = [df for df in todos_dfs if not df.is_empty()]
|
|
125
120
|
|
|
126
121
|
if not todos_dfs:
|
|
127
|
-
return pl.DataFrame()
|
|
122
|
+
return pl.DataFrame(schema=ESQUEMA_BRUTO)
|
|
128
123
|
|
|
129
124
|
return pl.concat(todos_dfs).unique(subset=["data"], keep="first").sort("data")
|
|
130
125
|
|
|
131
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
|
+
|
|
132
167
|
def selic_over_serie(
|
|
133
168
|
inicio: DateLike | None = None,
|
|
134
169
|
fim: DateLike | None = None,
|
|
@@ -179,13 +214,9 @@ def selic_over_serie(
|
|
|
179
214
|
│ 2025-09-17 ┆ 0.149 │
|
|
180
215
|
└────────────┴───────┘
|
|
181
216
|
"""
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
df = _buscar_dados_url(SerieBC.SELIC_OVER, inicio, fim)
|
|
186
|
-
else:
|
|
187
|
-
raise ValueError("Informe 'inicio' ou 'ultimos'.")
|
|
188
|
-
return df
|
|
217
|
+
return _converter_para_taxa(
|
|
218
|
+
_buscar_serie(SerieSGS.SELIC_OVER, inicio, fim, ultimos)
|
|
219
|
+
)
|
|
189
220
|
|
|
190
221
|
|
|
191
222
|
def selic_over(data: DateLike) -> float:
|
|
@@ -204,7 +235,10 @@ def selic_over(data: DateLike) -> float:
|
|
|
204
235
|
"""
|
|
205
236
|
if any_is_empty(data):
|
|
206
237
|
return float("nan")
|
|
207
|
-
return
|
|
238
|
+
return _extrair_escalar(selic_over_serie(data, data), "taxa")
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
# ── SELIC Meta ───────────────────────────────────────────────────────
|
|
208
242
|
|
|
209
243
|
|
|
210
244
|
def selic_meta_serie(
|
|
@@ -238,13 +272,9 @@ def selic_meta_serie(
|
|
|
238
272
|
│ 2024-05-31 ┆ 0.105 │
|
|
239
273
|
└────────────┴───────┘
|
|
240
274
|
"""
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
df = _buscar_dados_url(SerieBC.SELIC_META, inicio, fim)
|
|
245
|
-
else:
|
|
246
|
-
raise ValueError("Informe 'inicio' ou 'ultimos'.")
|
|
247
|
-
return df
|
|
275
|
+
return _converter_para_taxa(
|
|
276
|
+
_buscar_serie(SerieSGS.SELIC_META, inicio, fim, ultimos)
|
|
277
|
+
)
|
|
248
278
|
|
|
249
279
|
|
|
250
280
|
def selic_meta(data: DateLike) -> float:
|
|
@@ -263,4 +293,94 @@ def selic_meta(data: DateLike) -> float:
|
|
|
263
293
|
"""
|
|
264
294
|
if any_is_empty(data):
|
|
265
295
|
return float("nan")
|
|
266
|
-
return
|
|
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.5"
|
pyield-0.48.5/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)
|
|
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
|