pyield 0.52.2__tar.gz → 0.53.0__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.52.2 → pyield-0.53.0}/PKG-INFO +3 -3
- {pyield-0.52.2 → pyield-0.53.0}/README.md +2 -2
- {pyield-0.52.2 → pyield-0.53.0}/pyield/_internal/converters.py +3 -2
- {pyield-0.52.2 → pyield-0.53.0}/pyield/b3/boletim.py +1 -0
- {pyield-0.52.2 → pyield-0.53.0}/pyield/b3/derivativos_intradia.py +7 -0
- {pyield-0.52.2 → pyield-0.53.0}/pyield/tpf/__init__.py +2 -4
- pyield-0.53.0/pyield/tpf/secundario.py +410 -0
- {pyield-0.52.2 → pyield-0.53.0}/pyproject.toml +1 -2
- pyield-0.52.2/pyield/bc/tpf_intradia.py +0 -148
- pyield-0.52.2/pyield/bc/tpf_mensal.py +0 -146
- {pyield-0.52.2 → pyield-0.53.0}/LICENSE +0 -0
- {pyield-0.52.2 → pyield-0.53.0}/pyield/__init__.py +0 -0
- {pyield-0.52.2 → pyield-0.53.0}/pyield/_internal/__init__.py +0 -0
- {pyield-0.52.2 → pyield-0.53.0}/pyield/_internal/br_numbers.py +0 -0
- {pyield-0.52.2 → pyield-0.53.0}/pyield/_internal/cache.py +0 -0
- {pyield-0.52.2 → pyield-0.53.0}/pyield/_internal/data_cache.py +0 -0
- {pyield-0.52.2 → pyield-0.53.0}/pyield/_internal/retry.py +0 -0
- {pyield-0.52.2 → pyield-0.53.0}/pyield/_internal/types.py +0 -0
- {pyield-0.52.2 → pyield-0.53.0}/pyield/anbima/__init__.py +0 -0
- {pyield-0.52.2 → pyield-0.53.0}/pyield/anbima/imaq.py +0 -0
- {pyield-0.52.2 → pyield-0.53.0}/pyield/anbima/mercado_secundario.py +0 -0
- {pyield-0.52.2 → pyield-0.53.0}/pyield/b3/__init__.py +0 -0
- {pyield-0.52.2 → pyield-0.53.0}/pyield/b3/_contratos.py +0 -0
- {pyield-0.52.2 → pyield-0.53.0}/pyield/b3/_validar_pregao.py +0 -0
- {pyield-0.52.2 → pyield-0.53.0}/pyield/b3/di_over.py +0 -0
- {pyield-0.52.2 → pyield-0.53.0}/pyield/bc/__init__.py +0 -0
- {pyield-0.52.2 → pyield-0.53.0}/pyield/bc/_olinda.py +0 -0
- {pyield-0.52.2 → pyield-0.53.0}/pyield/bc/leiloes.py +0 -0
- {pyield-0.52.2 → pyield-0.53.0}/pyield/bc/lft.py +0 -0
- {pyield-0.52.2 → pyield-0.53.0}/pyield/bc/sgs.py +0 -0
- {pyield-0.52.2 → pyield-0.53.0}/pyield/du/__init__.py +0 -0
- {pyield-0.52.2 → pyield-0.53.0}/pyield/du/core.py +0 -0
- {pyield-0.52.2 → pyield-0.53.0}/pyield/du/feriados/__init__.py +0 -0
- {pyield-0.52.2 → pyield-0.53.0}/pyield/du/feriados/feriados_antigos_br.txt +0 -0
- {pyield-0.52.2 → pyield-0.53.0}/pyield/du/feriados/feriados_br.py +0 -0
- {pyield-0.52.2 → pyield-0.53.0}/pyield/du/feriados/feriados_novos_br.txt +0 -0
- {pyield-0.52.2 → pyield-0.53.0}/pyield/futuro/__init__.py +0 -0
- {pyield-0.52.2 → pyield-0.53.0}/pyield/futuro/contratos.py +0 -0
- {pyield-0.52.2 → pyield-0.53.0}/pyield/futuro/di1.py +0 -0
- {pyield-0.52.2 → pyield-0.53.0}/pyield/futuro/historico.py +0 -0
- {pyield-0.52.2 → pyield-0.53.0}/pyield/futuro/intradia.py +0 -0
- {pyield-0.52.2 → pyield-0.53.0}/pyield/fwd.py +0 -0
- {pyield-0.52.2 → pyield-0.53.0}/pyield/interpolador.py +0 -0
- {pyield-0.52.2 → pyield-0.53.0}/pyield/ipca/__init__.py +0 -0
- {pyield-0.52.2 → pyield-0.53.0}/pyield/ipca/historico.py +0 -0
- {pyield-0.52.2 → pyield-0.53.0}/pyield/ipca/projetado.py +0 -0
- {pyield-0.52.2 → pyield-0.53.0}/pyield/py.typed +0 -0
- {pyield-0.52.2 → pyield-0.53.0}/pyield/relogio.py +0 -0
- {pyield-0.52.2 → pyield-0.53.0}/pyield/selic/__init__.py +0 -0
- {pyield-0.52.2 → pyield-0.53.0}/pyield/selic/compromissada.py +0 -0
- {pyield-0.52.2 → pyield-0.53.0}/pyield/selic/copom.py +0 -0
- {pyield-0.52.2 → pyield-0.53.0}/pyield/selic/cpm.py +0 -0
- {pyield-0.52.2 → pyield-0.53.0}/pyield/selic/probabilities.py +0 -0
- {pyield-0.52.2 → pyield-0.53.0}/pyield/tpf/_titulos/__init__.py +0 -0
- {pyield-0.52.2 → pyield-0.53.0}/pyield/tpf/_titulos/lft.py +0 -0
- {pyield-0.52.2 → pyield-0.53.0}/pyield/tpf/_titulos/ltn.py +0 -0
- {pyield-0.52.2 → pyield-0.53.0}/pyield/tpf/_titulos/ntnb.py +0 -0
- {pyield-0.52.2 → pyield-0.53.0}/pyield/tpf/_titulos/ntnb1.py +0 -0
- {pyield-0.52.2 → pyield-0.53.0}/pyield/tpf/_titulos/ntnbprinc.py +0 -0
- {pyield-0.52.2 → pyield-0.53.0}/pyield/tpf/_titulos/ntnc.py +0 -0
- {pyield-0.52.2 → pyield-0.53.0}/pyield/tpf/_titulos/ntnf.py +0 -0
- {pyield-0.52.2 → pyield-0.53.0}/pyield/tpf/benchmark.py +0 -0
- {pyield-0.52.2 → pyield-0.53.0}/pyield/tpf/leiloes.py +0 -0
- {pyield-0.52.2 → pyield-0.53.0}/pyield/tpf/pre.py +0 -0
- {pyield-0.52.2 → pyield-0.53.0}/pyield/tpf/rmd/__init__.py +0 -0
- {pyield-0.52.2 → pyield-0.53.0}/pyield/tpf/rmd/_aba_1_3.py +0 -0
- {pyield-0.52.2 → pyield-0.53.0}/pyield/tpf/rmd/_aba_2_1.py +0 -0
- {pyield-0.52.2 → pyield-0.53.0}/pyield/tpf/rmd/_common.py +0 -0
- {pyield-0.52.2 → pyield-0.53.0}/pyield/tpf/rmd/_download.py +0 -0
- {pyield-0.52.2 → pyield-0.53.0}/pyield/tpf/utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: pyield
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.53.0
|
|
4
4
|
Summary: Polars-powered toolkit for Brazilian fixed income analysis
|
|
5
5
|
Keywords: fixed-income,brazil,finance,analysis,bonds
|
|
6
6
|
Author: Carlos Carvalho
|
|
@@ -113,7 +113,7 @@ documentação.
|
|
|
113
113
|
| `yd.forwards(...)` | função | Curva de taxas a termo | |
|
|
114
114
|
| `yd.futuro` | módulo | Contratos futuros da B3 | `di1`, `historico`, `intradia`, `datas_disponiveis`, `vencimento`, `enriquecer`, `vencimento_expr` |
|
|
115
115
|
| `yd.di1` | módulo | Curva DI1 e interpolação | `dados`, `interpolar_taxas`, `interpolar_taxa`, `datas_disponiveis` |
|
|
116
|
-
| `yd.tpf` | módulo | Títulos públicos federais | `taxas`, `vencimentos`, `estoque`, `leiloes`, `benchmarks`, `curva_pre`, `premio_pre`, `rmd`, `
|
|
116
|
+
| `yd.tpf` | módulo | Títulos públicos federais | `taxas`, `vencimentos`, `estoque`, `leiloes`, `benchmarks`, `curva_pre`, `premio_pre`, `rmd`, `secundario` |
|
|
117
117
|
| `yd.lft` | módulo | LFT | `dados`, `vencimentos`, `cotacao`, `pu`, `taxa`, `vna`, `rentabilidade`, `rentabilidade_expr` |
|
|
118
118
|
| `yd.ltn` | módulo | LTN | `dados`, `vencimentos`, `pu`, `taxa`, `duration_expr`, `dv01`, `dv01_expr`, `rentabilidade`, `rentabilidade_expr`, `taxas_forward` |
|
|
119
119
|
| `yd.ntnb` | módulo | NTN-B | `dados`, `vencimentos`, `datas_pagamento`, `fluxos_caixa`, `cotacao`, `pu`, `taxa`, `taxas_zero`, `duration`, `duration_expr`, `dv01`, `dv01_expr`, `implicitas`, `curva` |
|
|
@@ -305,7 +305,7 @@ fonte indisponível) retornam DataFrame vazio ou `nan`, sem lançar exceção:
|
|
|
305
305
|
import pyield as yd
|
|
306
306
|
|
|
307
307
|
yd.futuro.historico("01-01-2030", "DI1").is_empty() # -> True
|
|
308
|
-
yd.tpf.
|
|
308
|
+
yd.tpf.secundario.mensal("01-01-2030").is_empty() # -> True
|
|
309
309
|
yd.ptax("25-12-2025") # -> nan
|
|
310
310
|
```
|
|
311
311
|
|
|
@@ -69,7 +69,7 @@ documentação.
|
|
|
69
69
|
| `yd.forwards(...)` | função | Curva de taxas a termo | |
|
|
70
70
|
| `yd.futuro` | módulo | Contratos futuros da B3 | `di1`, `historico`, `intradia`, `datas_disponiveis`, `vencimento`, `enriquecer`, `vencimento_expr` |
|
|
71
71
|
| `yd.di1` | módulo | Curva DI1 e interpolação | `dados`, `interpolar_taxas`, `interpolar_taxa`, `datas_disponiveis` |
|
|
72
|
-
| `yd.tpf` | módulo | Títulos públicos federais | `taxas`, `vencimentos`, `estoque`, `leiloes`, `benchmarks`, `curva_pre`, `premio_pre`, `rmd`, `
|
|
72
|
+
| `yd.tpf` | módulo | Títulos públicos federais | `taxas`, `vencimentos`, `estoque`, `leiloes`, `benchmarks`, `curva_pre`, `premio_pre`, `rmd`, `secundario` |
|
|
73
73
|
| `yd.lft` | módulo | LFT | `dados`, `vencimentos`, `cotacao`, `pu`, `taxa`, `vna`, `rentabilidade`, `rentabilidade_expr` |
|
|
74
74
|
| `yd.ltn` | módulo | LTN | `dados`, `vencimentos`, `pu`, `taxa`, `duration_expr`, `dv01`, `dv01_expr`, `rentabilidade`, `rentabilidade_expr`, `taxas_forward` |
|
|
75
75
|
| `yd.ntnb` | módulo | NTN-B | `dados`, `vencimentos`, `datas_pagamento`, `fluxos_caixa`, `cotacao`, `pu`, `taxa`, `taxas_zero`, `duration`, `duration_expr`, `dv01`, `dv01_expr`, `implicitas`, `curva` |
|
|
@@ -261,7 +261,7 @@ fonte indisponível) retornam DataFrame vazio ou `nan`, sem lançar exceção:
|
|
|
261
261
|
import pyield as yd
|
|
262
262
|
|
|
263
263
|
yd.futuro.historico("01-01-2030", "DI1").is_empty() # -> True
|
|
264
|
-
yd.tpf.
|
|
264
|
+
yd.tpf.secundario.mensal("01-01-2030").is_empty() # -> True
|
|
265
265
|
yd.ptax("25-12-2025") # -> nan
|
|
266
266
|
```
|
|
267
267
|
|
|
@@ -52,14 +52,15 @@ def converter_datas_expr(expr: pl.Expr | str) -> pl.Expr:
|
|
|
52
52
|
expr = pl.col(expr)
|
|
53
53
|
|
|
54
54
|
# Fallback por linha: o primeiro valor não-nulo vence.
|
|
55
|
-
#
|
|
55
|
+
# Datas e datetimes Python chegam ao Polars como strings ISO.
|
|
56
56
|
# Se o elemento for null, ele permanece null em todos os termos.
|
|
57
57
|
expr_str = expr.cast(pl.String, strict=False).str.strip_chars()
|
|
58
58
|
return pl.coalesce(
|
|
59
|
-
expr.cast(pl.Date, strict=False),
|
|
60
59
|
expr_str.str.to_date(format="%d-%m-%Y", strict=False),
|
|
61
60
|
expr_str.str.to_date(format="%d/%m/%Y", strict=False),
|
|
62
61
|
expr_str.str.to_date(format="%Y-%m-%d", strict=False),
|
|
62
|
+
expr_str.str.to_date(format="%Y-%m-%d %H:%M:%S", strict=False),
|
|
63
|
+
expr_str.str.to_date(format="%Y-%m-%d %H:%M:%S%.f", strict=False),
|
|
63
64
|
)
|
|
64
65
|
|
|
65
66
|
|
|
@@ -298,6 +298,7 @@ def _converter_para_df(registros: list[dict]) -> pl.DataFrame:
|
|
|
298
298
|
# (pl.DataFrame infere colunas de ~50 primeiras linhas por padrão.)
|
|
299
299
|
schema_str = {nome: pl.String for nome in SCHEMA_PRICE_REPORT}
|
|
300
300
|
df = pl.DataFrame(registros, schema=schema_str)
|
|
301
|
+
df = df.with_columns(TradDt=pl.col("TradDt").str.to_date("%Y-%m-%d"))
|
|
301
302
|
return df.cast(SCHEMA_PRICE_REPORT, strict=False)
|
|
302
303
|
|
|
303
304
|
|
|
@@ -103,8 +103,15 @@ def _converter_json_intradia(dados_json: list[dict]) -> pl.DataFrame:
|
|
|
103
103
|
def _processar_colunas_intradia(df: pl.DataFrame) -> pl.DataFrame:
|
|
104
104
|
colunas_disponiveis = [col for col in MAPEAMENTO if col in df.columns]
|
|
105
105
|
tipos_disponiveis = pl.Schema({col: TIPOS[col] for col in colunas_disponiveis})
|
|
106
|
+
coluna_vencimento = "asset.AsstSummry.mtrtyCode"
|
|
107
|
+
exprs_data = []
|
|
108
|
+
if coluna_vencimento in colunas_disponiveis:
|
|
109
|
+
exprs_data.append(
|
|
110
|
+
pl.col(coluna_vencimento).str.to_date("%Y-%m-%d", strict=False)
|
|
111
|
+
)
|
|
106
112
|
return (
|
|
107
113
|
df.select(colunas_disponiveis)
|
|
114
|
+
.with_columns(exprs_data)
|
|
108
115
|
.cast(tipos_disponiveis, strict=False)
|
|
109
116
|
.rename(MAPEAMENTO, strict=False)
|
|
110
117
|
)
|
|
@@ -2,8 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from pyield.anbima.imaq import estoque
|
|
4
4
|
from pyield.anbima.mercado_secundario import TipoTPF, taxas, vencimentos
|
|
5
|
-
from pyield.
|
|
6
|
-
from pyield.bc.tpf_mensal import secundario_mensal
|
|
5
|
+
from pyield.tpf import secundario
|
|
7
6
|
from pyield.tpf._titulos import lft, ltn, ntnb, ntnb1, ntnbprinc, ntnc, ntnf
|
|
8
7
|
from pyield.tpf.benchmark import benchmarks
|
|
9
8
|
from pyield.tpf.leiloes import leiloes
|
|
@@ -26,8 +25,7 @@ __all__ = [
|
|
|
26
25
|
"ntnf",
|
|
27
26
|
"premio_pre",
|
|
28
27
|
"rmd",
|
|
29
|
-
"
|
|
30
|
-
"secundario_mensal",
|
|
28
|
+
"secundario",
|
|
31
29
|
"taxas",
|
|
32
30
|
"vencimentos",
|
|
33
31
|
]
|
|
@@ -0,0 +1,410 @@
|
|
|
1
|
+
"""Negociações do mercado secundário de TPFs no sistema Selic do BCB."""
|
|
2
|
+
|
|
3
|
+
import datetime as dt
|
|
4
|
+
import io
|
|
5
|
+
import os
|
|
6
|
+
import zipfile as zf
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
import polars as pl
|
|
10
|
+
import polars.selectors as ps
|
|
11
|
+
import requests
|
|
12
|
+
|
|
13
|
+
from pyield import du, relogio
|
|
14
|
+
from pyield._internal.br_numbers import float_br, inteiro_br, taxa_br
|
|
15
|
+
from pyield._internal.cache import ttl_cache
|
|
16
|
+
from pyield._internal.converters import converter_datas
|
|
17
|
+
from pyield._internal.retry import retry_padrao
|
|
18
|
+
from pyield._internal.types import DateLike, any_is_empty
|
|
19
|
+
|
|
20
|
+
URL_BASE_MENSAL = "https://www4.bcb.gov.br/pom/demab/negociacoes/download"
|
|
21
|
+
URL_BASE_TEMPO_REAL = (
|
|
22
|
+
"https://www3.bcb.gov.br/novoselic/rest/precosNegociacao/pub/download/estatisticas/"
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
HORA_INICIO_TEMPO_REAL = dt.time(9, 0, 0)
|
|
26
|
+
HORA_FIM_TEMPO_REAL = dt.time(22, 0, 0)
|
|
27
|
+
CHAVES_ORDENACAO = ["data_liquidacao", "titulo", "data_vencimento"]
|
|
28
|
+
COLUNAS_MINIMAS_CSV = 2
|
|
29
|
+
|
|
30
|
+
type CaminhoArquivo = str | os.PathLike[str]
|
|
31
|
+
|
|
32
|
+
__all__ = [
|
|
33
|
+
"baixar_zip",
|
|
34
|
+
"intradia",
|
|
35
|
+
"ler_zip",
|
|
36
|
+
"mensal",
|
|
37
|
+
"zip_para_silver",
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _tipo_arquivo(extragrupo: bool) -> str:
|
|
42
|
+
return "E" if extragrupo else "T"
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _data_mensal(data: DateLike) -> dt.date:
|
|
46
|
+
data_alvo = converter_datas(data)
|
|
47
|
+
if not isinstance(data_alvo, dt.date):
|
|
48
|
+
msg = "data deve ser escalar para consultas mensais do secundário de TPFs"
|
|
49
|
+
raise ValueError(msg)
|
|
50
|
+
return data_alvo
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _nome_arquivo(data: DateLike, extragrupo: bool = False) -> str:
|
|
54
|
+
data_alvo = _data_mensal(data)
|
|
55
|
+
return f"Neg{_tipo_arquivo(extragrupo)}{data_alvo:%Y%m}.ZIP"
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@ttl_cache()
|
|
59
|
+
@retry_padrao
|
|
60
|
+
def _baixar_url_zip(url_arquivo: str) -> bytes:
|
|
61
|
+
resposta = requests.get(url_arquivo, allow_redirects=True, timeout=60)
|
|
62
|
+
resposta.raise_for_status()
|
|
63
|
+
return resposta.content
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def baixar_zip(data: DateLike, extragrupo: bool = False) -> bytes:
|
|
67
|
+
"""Baixa o ZIP bruto mensal de negociações secundárias de TPFs.
|
|
68
|
+
|
|
69
|
+
Fonte: Banco Central do Brasil, sistema SELIC. A fonte publica um arquivo
|
|
70
|
+
por mês; por isso, apenas o ano e o mês de ``data`` são usados.
|
|
71
|
+
|
|
72
|
+
A função valida a estrutura mínima do ZIP antes de retornar os bytes, para
|
|
73
|
+
evitar que pipelines de ingestão salvem bronze vazio, corrompido ou sem CSV
|
|
74
|
+
plausível.
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
data: Data de referência. Apenas ano e mês definem o arquivo.
|
|
78
|
+
extragrupo: Se verdadeiro, baixa o arquivo extragrupo.
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
Bytes validados do arquivo ZIP mensal publicado pelo BCB.
|
|
82
|
+
|
|
83
|
+
Raises:
|
|
84
|
+
requests.HTTPError: Se o arquivo não estiver disponível no BCB ou a
|
|
85
|
+
resposta HTTP indicar erro.
|
|
86
|
+
ValueError: Se o conteúdo baixado não for um ZIP bruto plausível.
|
|
87
|
+
|
|
88
|
+
Examples:
|
|
89
|
+
>>> conteudo = yd.tpf.secundario.baixar_zip("07-01-2025") # doctest: +SKIP
|
|
90
|
+
"""
|
|
91
|
+
arquivo = _nome_arquivo(data, extragrupo)
|
|
92
|
+
conteudo_zip = _baixar_url_zip(f"{URL_BASE_MENSAL}/{arquivo}")
|
|
93
|
+
_validar_zip(conteudo_zip, arquivo)
|
|
94
|
+
return conteudo_zip
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _extrair_csv_zip(conteudo_zip: bytes) -> bytes:
|
|
98
|
+
try:
|
|
99
|
+
arquivo_zip = zf.ZipFile(io.BytesIO(conteudo_zip), "r")
|
|
100
|
+
except zf.BadZipFile as exc:
|
|
101
|
+
msg = "ZIP inválido ou ilegível"
|
|
102
|
+
raise ValueError(msg) from exc
|
|
103
|
+
|
|
104
|
+
with arquivo_zip:
|
|
105
|
+
nomes = arquivo_zip.namelist()
|
|
106
|
+
if not nomes:
|
|
107
|
+
raise ValueError("ZIP vazio")
|
|
108
|
+
|
|
109
|
+
arquivo_corrompido = arquivo_zip.testzip()
|
|
110
|
+
if arquivo_corrompido is not None:
|
|
111
|
+
msg = f"ZIP contém arquivo corrompido: {arquivo_corrompido}"
|
|
112
|
+
raise ValueError(msg)
|
|
113
|
+
|
|
114
|
+
return arquivo_zip.read(nomes[0])
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _validar_zip(conteudo_zip: bytes, nome: str | None = None) -> None:
|
|
118
|
+
rotulo = f"{nome}: " if nome else ""
|
|
119
|
+
try:
|
|
120
|
+
conteudo_csv = _extrair_csv_zip(conteudo_zip)
|
|
121
|
+
except ValueError as exc:
|
|
122
|
+
msg = f"{rotulo}{exc}"
|
|
123
|
+
raise ValueError(msg) from exc
|
|
124
|
+
|
|
125
|
+
df_amostra = pl.read_csv(
|
|
126
|
+
conteudo_csv,
|
|
127
|
+
encoding="latin1",
|
|
128
|
+
separator=";",
|
|
129
|
+
infer_schema=False,
|
|
130
|
+
null_values="",
|
|
131
|
+
n_rows=1,
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
if len(df_amostra.columns) < COLUNAS_MINIMAS_CSV:
|
|
135
|
+
msg = f"{rotulo}CSV não parece estar separado por ponto e vírgula"
|
|
136
|
+
raise ValueError(msg)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def _parsear_csv_mensal(conteudo_csv: bytes) -> pl.DataFrame:
|
|
140
|
+
return pl.read_csv(
|
|
141
|
+
conteudo_csv,
|
|
142
|
+
encoding="latin1",
|
|
143
|
+
separator=";",
|
|
144
|
+
infer_schema=False,
|
|
145
|
+
null_values="",
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def _processar_df_mensal(df: pl.DataFrame) -> pl.DataFrame:
|
|
150
|
+
return (
|
|
151
|
+
df.with_columns(ps.string().str.strip_chars())
|
|
152
|
+
.with_columns(
|
|
153
|
+
quantidade=pl.col("QUANT NEGOCIADA").cast(pl.Int64),
|
|
154
|
+
pu_medio=float_br("PU MED"),
|
|
155
|
+
)
|
|
156
|
+
.select(
|
|
157
|
+
data_liquidacao=pl.col("DATA MOV").str.to_date("%d/%m/%Y", strict=False),
|
|
158
|
+
titulo=pl.col("SIGLA"),
|
|
159
|
+
codigo_selic=pl.col("CODIGO").cast(pl.Int64),
|
|
160
|
+
isin=pl.col("CODIGO ISIN"),
|
|
161
|
+
data_emissao=pl.col("EMISSAO").str.to_date("%d/%m/%Y", strict=False),
|
|
162
|
+
data_vencimento=pl.col("VENCIMENTO").str.to_date("%d/%m/%Y", strict=False),
|
|
163
|
+
operacoes=pl.col("NUM DE OPER").cast(pl.Int64),
|
|
164
|
+
quantidade=pl.col("quantidade"),
|
|
165
|
+
financeiro=(pl.col("quantidade") * pl.col("pu_medio")).round(2),
|
|
166
|
+
pu_minimo=float_br("PU MIN"),
|
|
167
|
+
pu_medio=pl.col("pu_medio"),
|
|
168
|
+
pu_maximo=float_br("PU MAX"),
|
|
169
|
+
pu_lastro=float_br("PU LASTRO"),
|
|
170
|
+
valor_par=float_br("VALOR PAR"),
|
|
171
|
+
taxa_minima=float_br("TAXA MIN"),
|
|
172
|
+
taxa_media=float_br("TAXA MED"),
|
|
173
|
+
taxa_maxima=float_br("TAXA MAX"),
|
|
174
|
+
operacoes_corretagem=pl.col("NUM OPER COM CORRETAGEM").cast(pl.Int64),
|
|
175
|
+
quantidade_corretagem=pl.col("QUANT NEG COM CORRETAGEM").cast(pl.Int64),
|
|
176
|
+
)
|
|
177
|
+
.sort(CHAVES_ORDENACAO)
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def zip_para_silver(conteudo_zip: bytes) -> pl.DataFrame:
|
|
182
|
+
"""Converte o ZIP mensal bruto do secundário de TPFs em silver.
|
|
183
|
+
|
|
184
|
+
Fonte: Banco Central do Brasil, sistema SELIC. Esta função representa a
|
|
185
|
+
etapa bronze -> silver: extrai o CSV interno, limpa os campos, converte
|
|
186
|
+
tipos e retorna o schema Polars canônico usado pela PYield. Ela não faz
|
|
187
|
+
enriquecimento para camada ouro.
|
|
188
|
+
|
|
189
|
+
Args:
|
|
190
|
+
conteudo_zip: Bytes do arquivo ZIP bruto mensal.
|
|
191
|
+
|
|
192
|
+
Returns:
|
|
193
|
+
DataFrame Polars com dados mensais do mercado secundário.
|
|
194
|
+
|
|
195
|
+
Output Columns:
|
|
196
|
+
* data_liquidacao (Date): data de liquidação da negociação.
|
|
197
|
+
* titulo (String): sigla do título público.
|
|
198
|
+
* codigo_selic (Int64): código único no sistema SELIC.
|
|
199
|
+
* isin (String): código ISIN.
|
|
200
|
+
* data_emissao (Date): data de emissão do título.
|
|
201
|
+
* data_vencimento (Date): data de vencimento do título.
|
|
202
|
+
* operacoes (Int64): número total de operações.
|
|
203
|
+
* quantidade (Int64): quantidade total negociada.
|
|
204
|
+
* financeiro (Float64): valor financeiro negociado.
|
|
205
|
+
* pu_minimo (Float64): preço unitário mínimo.
|
|
206
|
+
* pu_medio (Float64): preço unitário médio.
|
|
207
|
+
* pu_maximo (Float64): preço unitário máximo.
|
|
208
|
+
* pu_lastro (Float64): preço unitário de lastro.
|
|
209
|
+
* valor_par (Float64): valor par do título.
|
|
210
|
+
* taxa_minima (Float64): taxa mínima.
|
|
211
|
+
* taxa_media (Float64): taxa média.
|
|
212
|
+
* taxa_maxima (Float64): taxa máxima.
|
|
213
|
+
* operacoes_corretagem (Int64): operações com corretagem.
|
|
214
|
+
* quantidade_corretagem (Int64): quantidade com corretagem.
|
|
215
|
+
|
|
216
|
+
Examples:
|
|
217
|
+
>>> conteudo = yd.tpf.secundario.baixar_zip("07-01-2025") # doctest: +SKIP
|
|
218
|
+
>>> df = yd.tpf.secundario.zip_para_silver(conteudo) # doctest: +SKIP
|
|
219
|
+
"""
|
|
220
|
+
conteudo_csv = _extrair_csv_zip(conteudo_zip)
|
|
221
|
+
return _processar_df_mensal(_parsear_csv_mensal(conteudo_csv))
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def ler_zip(caminho: CaminhoArquivo) -> pl.DataFrame:
|
|
225
|
+
"""Lê um ZIP mensal local do secundário de TPFs e converte para silver.
|
|
226
|
+
|
|
227
|
+
Fonte: Banco Central do Brasil, sistema SELIC. Esta função é um atalho para
|
|
228
|
+
pipelines que salvam o bronze bruto e depois processam o arquivo local com o
|
|
229
|
+
mesmo schema de ``zip_para_silver``.
|
|
230
|
+
|
|
231
|
+
Args:
|
|
232
|
+
caminho: Caminho do arquivo ZIP bruto.
|
|
233
|
+
|
|
234
|
+
Returns:
|
|
235
|
+
DataFrame Polars com dados mensais do mercado secundário.
|
|
236
|
+
|
|
237
|
+
Examples:
|
|
238
|
+
>>> df = yd.tpf.secundario.ler_zip("NegT202501.ZIP") # doctest: +SKIP
|
|
239
|
+
"""
|
|
240
|
+
return zip_para_silver(Path(caminho).read_bytes())
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def mensal(data: DateLike, extragrupo: bool = False) -> pl.DataFrame:
|
|
244
|
+
"""Busca dados mensais do mercado secundário de TPFs.
|
|
245
|
+
|
|
246
|
+
Fonte: Banco Central do Brasil, sistema SELIC. Baixa o ZIP mensal de
|
|
247
|
+
negociações secundárias, valida o bronze bruto e retorna o DataFrame silver.
|
|
248
|
+
Apenas o ano e o mês de ``data`` são usados para identificar o arquivo.
|
|
249
|
+
|
|
250
|
+
Args:
|
|
251
|
+
data: Data de referência. Apenas ano e mês definem o arquivo.
|
|
252
|
+
extragrupo: Se verdadeiro, busca apenas negociações extragrupo.
|
|
253
|
+
|
|
254
|
+
Returns:
|
|
255
|
+
DataFrame Polars com dados mensais do mercado secundário.
|
|
256
|
+
|
|
257
|
+
Output Columns:
|
|
258
|
+
* data_liquidacao (Date): data de liquidação da negociação.
|
|
259
|
+
* titulo (String): sigla do título público.
|
|
260
|
+
* codigo_selic (Int64): código único no sistema SELIC.
|
|
261
|
+
* isin (String): código ISIN.
|
|
262
|
+
* data_emissao (Date): data de emissão do título.
|
|
263
|
+
* data_vencimento (Date): data de vencimento do título.
|
|
264
|
+
* operacoes (Int64): número total de operações.
|
|
265
|
+
* quantidade (Int64): quantidade total negociada.
|
|
266
|
+
* financeiro (Float64): valor financeiro negociado.
|
|
267
|
+
* pu_minimo (Float64): preço unitário mínimo.
|
|
268
|
+
* pu_medio (Float64): preço unitário médio.
|
|
269
|
+
* pu_maximo (Float64): preço unitário máximo.
|
|
270
|
+
* pu_lastro (Float64): preço unitário de lastro.
|
|
271
|
+
* valor_par (Float64): valor par do título.
|
|
272
|
+
* taxa_minima (Float64): taxa mínima.
|
|
273
|
+
* taxa_media (Float64): taxa média.
|
|
274
|
+
* taxa_maxima (Float64): taxa máxima.
|
|
275
|
+
* operacoes_corretagem (Int64): operações com corretagem.
|
|
276
|
+
* quantidade_corretagem (Int64): quantidade com corretagem.
|
|
277
|
+
|
|
278
|
+
Examples:
|
|
279
|
+
>>> df = yd.tpf.secundario.mensal("07-01-2025", extragrupo=True)
|
|
280
|
+
"""
|
|
281
|
+
if any_is_empty(data):
|
|
282
|
+
return pl.DataFrame()
|
|
283
|
+
|
|
284
|
+
data_alvo = _data_mensal(data)
|
|
285
|
+
hoje = relogio.hoje()
|
|
286
|
+
if (data_alvo.year, data_alvo.month) > (hoje.year, hoje.month):
|
|
287
|
+
return pl.DataFrame()
|
|
288
|
+
|
|
289
|
+
return zip_para_silver(baixar_zip(data_alvo, extragrupo))
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
@ttl_cache()
|
|
293
|
+
@retry_padrao
|
|
294
|
+
def _buscar_csv_intradia() -> bytes:
|
|
295
|
+
hoje = relogio.hoje()
|
|
296
|
+
data_formatada = hoje.strftime("%d-%m-%Y")
|
|
297
|
+
url = f"{URL_BASE_TEMPO_REAL}{data_formatada}"
|
|
298
|
+
resposta = requests.get(url, timeout=30) # API costuma levar ~10s
|
|
299
|
+
resposta.raise_for_status()
|
|
300
|
+
return resposta.content
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
def _parsear_csv_intradia(dados: bytes) -> pl.DataFrame:
|
|
304
|
+
return pl.read_csv(
|
|
305
|
+
dados,
|
|
306
|
+
separator=";",
|
|
307
|
+
infer_schema=False,
|
|
308
|
+
null_values="-",
|
|
309
|
+
).rename(lambda c: c.strip())
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
def _processar_df_intradia(df: pl.DataFrame) -> pl.DataFrame:
|
|
313
|
+
agora = relogio.agora()
|
|
314
|
+
return df.filter(pl.col("//1") == "1").select(
|
|
315
|
+
data_hora_consulta=agora,
|
|
316
|
+
data_liquidacao=agora.date(),
|
|
317
|
+
titulo=pl.col("sigla").str.strip_chars(),
|
|
318
|
+
codigo_selic=inteiro_br("código título"),
|
|
319
|
+
data_vencimento=pl.col("data vencimento").str.to_date("%d/%m/%Y"),
|
|
320
|
+
pu_minimo=float_br("pu mínimo"),
|
|
321
|
+
pu_medio=float_br("pu médio"),
|
|
322
|
+
pu_maximo=float_br("pu máximo"),
|
|
323
|
+
pu_ultimo=float_br("mercado à vista pu último"),
|
|
324
|
+
taxa_minima=taxa_br("tx mínimo"),
|
|
325
|
+
taxa_media=taxa_br("tx médio"),
|
|
326
|
+
taxa_maxima=taxa_br("tx máximo"),
|
|
327
|
+
taxa_ultima=taxa_br("tx último"),
|
|
328
|
+
operacoes=inteiro_br("totais liquidados operações"),
|
|
329
|
+
quantidade=inteiro_br("títulos"),
|
|
330
|
+
financeiro=float_br("financeiro"),
|
|
331
|
+
operacoes_corretagem=inteiro_br("corretagem liquidados operações"),
|
|
332
|
+
quantidade_corretagem=inteiro_br("corretagem títulos"),
|
|
333
|
+
termo_pu_minimo=float_br("pu mínimo_duplicated_0"),
|
|
334
|
+
termo_pu_medio=float_br("pu médio_duplicated_0"),
|
|
335
|
+
termo_pu_ultimo=float_br("mercado a termo pu último"),
|
|
336
|
+
termo_pu_maximo=float_br("pu máximo_duplicated_0"),
|
|
337
|
+
termo_taxa_ultima=taxa_br("tx último_duplicated_0"),
|
|
338
|
+
termo_taxa_minima=taxa_br("tx mínimo_duplicated_0"),
|
|
339
|
+
termo_taxa_media=taxa_br("tx médio_duplicated_0"),
|
|
340
|
+
termo_taxa_maxima=taxa_br("tx máximo_duplicated_0"),
|
|
341
|
+
termo_operacoes=inteiro_br("totais contratados operações"),
|
|
342
|
+
termo_quantidade=inteiro_br("títulos_duplicated_0"),
|
|
343
|
+
termo_financeiro=float_br("financeiro_duplicated_0"),
|
|
344
|
+
termo_operacoes_corretagem=inteiro_br("corretagem contratados operações"),
|
|
345
|
+
termo_quantidade_corretagem=inteiro_br("corretagem títulos_duplicated_0"),
|
|
346
|
+
)
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
def _mercado_selic_aberto() -> bool:
|
|
350
|
+
agora = relogio.agora()
|
|
351
|
+
hoje = agora.date()
|
|
352
|
+
hora = agora.time()
|
|
353
|
+
eh_dia_util = du.eh_dia_util(hoje)
|
|
354
|
+
eh_horario = HORA_INICIO_TEMPO_REAL <= hora <= HORA_FIM_TEMPO_REAL
|
|
355
|
+
|
|
356
|
+
return eh_dia_util and eh_horario
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
def intradia() -> pl.DataFrame:
|
|
360
|
+
"""Busca dados intradia do mercado secundário de TPFs.
|
|
361
|
+
|
|
362
|
+
Fonte: Banco Central do Brasil, sistema SELIC. Os dados ficam disponíveis
|
|
363
|
+
apenas durante o horário do SELIC (09:00-22:00 BRT) em dias úteis.
|
|
364
|
+
|
|
365
|
+
Returns:
|
|
366
|
+
DataFrame Polars com negociações intradia do mercado secundário.
|
|
367
|
+
Retorna DataFrame vazio fora do horário do SELIC.
|
|
368
|
+
|
|
369
|
+
Output Columns:
|
|
370
|
+
* data_hora_consulta (Datetime): data e hora da consulta.
|
|
371
|
+
* data_liquidacao (Date): data de liquidação à vista.
|
|
372
|
+
* titulo (String): sigla do título público.
|
|
373
|
+
* codigo_selic (Int64): código SELIC do título.
|
|
374
|
+
* data_vencimento (Date): data de vencimento do título.
|
|
375
|
+
* pu_minimo (Float64): menor preço negociado.
|
|
376
|
+
* pu_medio (Float64): preço médio negociado.
|
|
377
|
+
* pu_maximo (Float64): maior preço negociado.
|
|
378
|
+
* pu_ultimo (Float64): último preço negociado.
|
|
379
|
+
* taxa_minima (Float64): menor taxa negociada.
|
|
380
|
+
* taxa_media (Float64): taxa média negociada.
|
|
381
|
+
* taxa_maxima (Float64): maior taxa negociada.
|
|
382
|
+
* taxa_ultima (Float64): última taxa negociada.
|
|
383
|
+
* operacoes (Int64): total de operações liquidadas.
|
|
384
|
+
* quantidade (Int64): quantidade total de títulos negociados.
|
|
385
|
+
* financeiro (Float64): valor financeiro total negociado.
|
|
386
|
+
* operacoes_corretagem (Int64): operações via corretagem.
|
|
387
|
+
* quantidade_corretagem (Int64): títulos via corretagem.
|
|
388
|
+
* termo_pu_minimo (Float64): menor preço a termo negociado.
|
|
389
|
+
* termo_pu_medio (Float64): preço médio a termo negociado.
|
|
390
|
+
* termo_pu_ultimo (Float64): último preço a termo negociado.
|
|
391
|
+
* termo_pu_maximo (Float64): maior preço a termo negociado.
|
|
392
|
+
* termo_taxa_ultima (Float64): última taxa a termo negociada.
|
|
393
|
+
* termo_taxa_minima (Float64): menor taxa a termo negociada.
|
|
394
|
+
* termo_taxa_media (Float64): taxa média a termo negociada.
|
|
395
|
+
* termo_taxa_maxima (Float64): maior taxa a termo negociada.
|
|
396
|
+
* termo_operacoes (Int64): total de operações a termo.
|
|
397
|
+
* termo_quantidade (Int64): total de títulos a termo negociados.
|
|
398
|
+
* termo_financeiro (Float64): valor financeiro total a termo.
|
|
399
|
+
* termo_operacoes_corretagem (Int64): operações a termo via corretagem.
|
|
400
|
+
* termo_quantidade_corretagem (Int64): títulos a termo via corretagem.
|
|
401
|
+
|
|
402
|
+
Examples:
|
|
403
|
+
>>> df = yd.tpf.secundario.intradia() # doctest: +SKIP
|
|
404
|
+
"""
|
|
405
|
+
if not _mercado_selic_aberto():
|
|
406
|
+
return pl.DataFrame()
|
|
407
|
+
|
|
408
|
+
texto_bruto = _buscar_csv_intradia()
|
|
409
|
+
df = _parsear_csv_intradia(texto_bruto)
|
|
410
|
+
return _processar_df_intradia(df)
|
|
@@ -19,7 +19,7 @@ dependencies = [
|
|
|
19
19
|
"fastexcel>=0.19.0",
|
|
20
20
|
"tzdata>=2024.1; platform_system == 'Windows'",
|
|
21
21
|
]
|
|
22
|
-
version = "0.
|
|
22
|
+
version = "0.53.0"
|
|
23
23
|
|
|
24
24
|
[project.urls]
|
|
25
25
|
Homepage = "https://github.com/crdcj/PYield"
|
|
@@ -32,7 +32,6 @@ dev = [
|
|
|
32
32
|
"python-dotenv[cli]",
|
|
33
33
|
"ipykernel",
|
|
34
34
|
"pytest",
|
|
35
|
-
"pyright",
|
|
36
35
|
"ruff",
|
|
37
36
|
"mkdocs",
|
|
38
37
|
"mkdocs-material",
|
|
@@ -1,148 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Busca dados intradiários de negociações secundárias da dívida pública federal.
|
|
3
|
-
https://www.bcb.gov.br/htms/selic/selicprecos.asp?frame=1
|
|
4
|
-
"""
|
|
5
|
-
|
|
6
|
-
import datetime as dt
|
|
7
|
-
|
|
8
|
-
import polars as pl
|
|
9
|
-
import requests
|
|
10
|
-
|
|
11
|
-
from pyield import du, relogio
|
|
12
|
-
from pyield._internal.br_numbers import float_br, inteiro_br, taxa_br
|
|
13
|
-
from pyield._internal.cache import ttl_cache
|
|
14
|
-
from pyield._internal.retry import retry_padrao
|
|
15
|
-
|
|
16
|
-
HORA_INICIO_TEMPO_REAL = dt.time(9, 0, 0)
|
|
17
|
-
HORA_FIM_TEMPO_REAL = dt.time(22, 0, 0)
|
|
18
|
-
URL_BASE_TEMPO_REAL = (
|
|
19
|
-
"https://www3.bcb.gov.br/novoselic/rest/precosNegociacao/pub/download/estatisticas/"
|
|
20
|
-
)
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
@ttl_cache()
|
|
24
|
-
@retry_padrao
|
|
25
|
-
def _buscar_csv() -> bytes:
|
|
26
|
-
"""
|
|
27
|
-
Exemplo de URL do CSV com dados intradiários:
|
|
28
|
-
https://www3.bcb.gov.br/novoselic/rest/precosNegociacao/pub/download/estatisticas/02-06-2025
|
|
29
|
-
"""
|
|
30
|
-
hoje = relogio.hoje()
|
|
31
|
-
data_formatada = hoje.strftime("%d-%m-%Y")
|
|
32
|
-
url = f"{URL_BASE_TEMPO_REAL}{data_formatada}"
|
|
33
|
-
r = requests.get(url, timeout=30) # API costuma levar ~10s
|
|
34
|
-
r.raise_for_status()
|
|
35
|
-
return r.content
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
def _parsear_df(dados: bytes) -> pl.DataFrame:
|
|
39
|
-
"""Lê CSV como strings."""
|
|
40
|
-
return pl.read_csv(
|
|
41
|
-
dados,
|
|
42
|
-
separator=";",
|
|
43
|
-
infer_schema=False,
|
|
44
|
-
null_values="-",
|
|
45
|
-
).rename(lambda c: c.strip())
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
def _processar_df(df: pl.DataFrame) -> pl.DataFrame:
|
|
49
|
-
"""Filtra registros, converte tipos e reordena colunas."""
|
|
50
|
-
agora = relogio.agora()
|
|
51
|
-
return df.filter(pl.col("//1") == "1").select(
|
|
52
|
-
data_hora_consulta=agora,
|
|
53
|
-
data_liquidacao=agora.date(),
|
|
54
|
-
titulo=pl.col("sigla").str.strip_chars(),
|
|
55
|
-
codigo_selic=inteiro_br("código título"),
|
|
56
|
-
data_vencimento=pl.col("data vencimento").str.to_date("%d/%m/%Y"),
|
|
57
|
-
pu_minimo=float_br("pu mínimo"),
|
|
58
|
-
pu_medio=float_br("pu médio"),
|
|
59
|
-
pu_maximo=float_br("pu máximo"),
|
|
60
|
-
pu_ultimo=float_br("mercado à vista pu último"),
|
|
61
|
-
taxa_minima=taxa_br("tx mínimo"),
|
|
62
|
-
taxa_media=taxa_br("tx médio"),
|
|
63
|
-
taxa_maxima=taxa_br("tx máximo"),
|
|
64
|
-
taxa_ultima=taxa_br("tx último"),
|
|
65
|
-
operacoes=inteiro_br("totais liquidados operações"),
|
|
66
|
-
quantidade=inteiro_br("títulos"),
|
|
67
|
-
financeiro=float_br("financeiro"),
|
|
68
|
-
operacoes_corretagem=inteiro_br("corretagem liquidados operações"),
|
|
69
|
-
quantidade_corretagem=inteiro_br("corretagem títulos"),
|
|
70
|
-
termo_pu_minimo=float_br("pu mínimo_duplicated_0"),
|
|
71
|
-
termo_pu_medio=float_br("pu médio_duplicated_0"),
|
|
72
|
-
termo_pu_ultimo=float_br("mercado a termo pu último"),
|
|
73
|
-
termo_pu_maximo=float_br("pu máximo_duplicated_0"),
|
|
74
|
-
termo_taxa_ultima=taxa_br("tx último_duplicated_0"),
|
|
75
|
-
termo_taxa_minima=taxa_br("tx mínimo_duplicated_0"),
|
|
76
|
-
termo_taxa_media=taxa_br("tx médio_duplicated_0"),
|
|
77
|
-
termo_taxa_maxima=taxa_br("tx máximo_duplicated_0"),
|
|
78
|
-
termo_operacoes=inteiro_br("totais contratados operações"),
|
|
79
|
-
termo_quantidade=inteiro_br("títulos_duplicated_0"),
|
|
80
|
-
termo_financeiro=float_br("financeiro_duplicated_0"),
|
|
81
|
-
termo_operacoes_corretagem=inteiro_br("corretagem contratados operações"),
|
|
82
|
-
termo_quantidade_corretagem=inteiro_br("corretagem títulos_duplicated_0"),
|
|
83
|
-
)
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
def _mercado_selic_aberto() -> bool:
|
|
87
|
-
"""Verifica se o mercado SELIC está aberto no momento."""
|
|
88
|
-
agora = relogio.agora()
|
|
89
|
-
hoje = agora.date()
|
|
90
|
-
hora = agora.time()
|
|
91
|
-
eh_dia_util = du.eh_dia_util(hoje)
|
|
92
|
-
eh_horario = HORA_INICIO_TEMPO_REAL <= hora <= HORA_FIM_TEMPO_REAL
|
|
93
|
-
|
|
94
|
-
return eh_dia_util and eh_horario
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
def secundario_intradia() -> pl.DataFrame:
|
|
98
|
-
"""Busca dados intradia do mercado secundário de TPFs.
|
|
99
|
-
|
|
100
|
-
Fonte: Banco Central do Brasil, sistema SELIC. Os dados ficam disponíveis
|
|
101
|
-
apenas durante o horário do SELIC (09:00-22:00 BRT) em dias úteis.
|
|
102
|
-
|
|
103
|
-
Returns:
|
|
104
|
-
DataFrame Polars com negociações intradia do mercado secundário.
|
|
105
|
-
Retorna DataFrame vazio fora do horário do SELIC.
|
|
106
|
-
|
|
107
|
-
Output Columns:
|
|
108
|
-
* data_hora_consulta (Datetime): data e hora da consulta.
|
|
109
|
-
* data_liquidacao (Date): data de liquidação à vista.
|
|
110
|
-
* titulo (String): sigla do título público.
|
|
111
|
-
* codigo_selic (Int64): código SELIC do título.
|
|
112
|
-
* data_vencimento (Date): data de vencimento do título.
|
|
113
|
-
* pu_minimo (Float64): menor preço negociado.
|
|
114
|
-
* pu_medio (Float64): preço médio negociado.
|
|
115
|
-
* pu_maximo (Float64): maior preço negociado.
|
|
116
|
-
* pu_ultimo (Float64): último preço negociado.
|
|
117
|
-
* taxa_minima (Float64): menor taxa negociada.
|
|
118
|
-
* taxa_media (Float64): taxa média negociada.
|
|
119
|
-
* taxa_maxima (Float64): maior taxa negociada.
|
|
120
|
-
* taxa_ultima (Float64): última taxa negociada.
|
|
121
|
-
* operacoes (Int64): total de operações liquidadas.
|
|
122
|
-
* quantidade (Int64): quantidade total de títulos negociados.
|
|
123
|
-
* financeiro (Float64): valor financeiro total negociado.
|
|
124
|
-
* operacoes_corretagem (Int64): operações via corretagem.
|
|
125
|
-
* quantidade_corretagem (Int64): títulos via corretagem.
|
|
126
|
-
* termo_pu_minimo (Float64): menor preço a termo negociado.
|
|
127
|
-
* termo_pu_medio (Float64): preço médio a termo negociado.
|
|
128
|
-
* termo_pu_ultimo (Float64): último preço a termo negociado.
|
|
129
|
-
* termo_pu_maximo (Float64): maior preço a termo negociado.
|
|
130
|
-
* termo_taxa_ultima (Float64): última taxa a termo negociada.
|
|
131
|
-
* termo_taxa_minima (Float64): menor taxa a termo negociada.
|
|
132
|
-
* termo_taxa_media (Float64): taxa média a termo negociada.
|
|
133
|
-
* termo_taxa_maxima (Float64): maior taxa a termo negociada.
|
|
134
|
-
* termo_operacoes (Int64): total de operações a termo.
|
|
135
|
-
* termo_quantidade (Int64): total de títulos a termo negociados.
|
|
136
|
-
* termo_financeiro (Float64): valor financeiro total a termo.
|
|
137
|
-
* termo_operacoes_corretagem (Int64): operações a termo via corretagem.
|
|
138
|
-
* termo_quantidade_corretagem (Int64): títulos a termo via corretagem.
|
|
139
|
-
|
|
140
|
-
Examples:
|
|
141
|
-
>>> df = yd.tpf.secundario_intradia() # doctest: +SKIP
|
|
142
|
-
"""
|
|
143
|
-
if not _mercado_selic_aberto():
|
|
144
|
-
return pl.DataFrame()
|
|
145
|
-
|
|
146
|
-
texto_bruto = _buscar_csv()
|
|
147
|
-
df = _parsear_df(texto_bruto)
|
|
148
|
-
return _processar_df(df)
|
|
@@ -1,146 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Módulo para buscar dados mensais de negociações secundárias da Dívida Pública
|
|
3
|
-
Federal (TPF) registradas no sistema Selic do Banco Central do Brasil (BCB).
|
|
4
|
-
Os dados são baixados em ZIP, extraídos e carregados em um DataFrame Polars.
|
|
5
|
-
Exemplo do formato dos dados (3 primeiras linhas):
|
|
6
|
-
DATA MOV ; SIGLA; CODIGO; CODIGO ISIN ; EMISSAO ; VENCIMENTO; NUM DE OPER; QUANT NEGOCIADA; VALOR NEGOCIADO; PU MIN ; PU MED ; PU MAX ; PU LASTRO ; VALOR PAR ; TAXA MIN; TAXA MED; TAXA MAX; NUM OPER COM CORRETAGEM; QUANT NEG COM CORRETAGEM
|
|
7
|
-
02/09/2024; LFT ; 210100; BRSTNCLF1RC4; 26/10/2018; 01/03/2025; 48; 100221; ; 15288,00898200; 15292,57098100; 15302,77742100; 15285,54813387; 15288,23830700; -0,1897 ; -0,0565 ; 0,0032 ; 20; 16155
|
|
8
|
-
02/09/2024; LFT ; 210100; BRSTNCLF1RD2; 08/03/2019; 01/09/2025; 101; 230120; ; 15288,23830700; 15294,25937800; 15311,01778200; 15279,49187722; 15288,23830700; -0,1498 ; -0,0395 ; 0,0000 ; 21; 19059
|
|
9
|
-
02/09/2024; LFT ; 210100; BRSTNCLF1RE0; 06/09/2019; 01/03/2026; 88; 512642; ; 15286,63304100; 15288,20025100; 15292,77891300; 15268,60295396; 15288,23830700; -0,0198 ; 0,0002 ; 0,0071 ; 27; 121742
|
|
10
|
-
...
|
|
11
|
-
"""
|
|
12
|
-
|
|
13
|
-
import datetime as dt
|
|
14
|
-
import io
|
|
15
|
-
import zipfile as zf
|
|
16
|
-
|
|
17
|
-
import polars as pl
|
|
18
|
-
import polars.selectors as ps
|
|
19
|
-
import requests
|
|
20
|
-
|
|
21
|
-
from pyield._internal.br_numbers import float_br
|
|
22
|
-
from pyield._internal.cache import ttl_cache
|
|
23
|
-
from pyield._internal.converters import converter_datas
|
|
24
|
-
from pyield._internal.retry import retry_padrao
|
|
25
|
-
from pyield._internal.types import DateLike, any_is_empty
|
|
26
|
-
from pyield.relogio import hoje
|
|
27
|
-
|
|
28
|
-
URL_BASE = "https://www4.bcb.gov.br/pom/demab/negociacoes/download"
|
|
29
|
-
|
|
30
|
-
CHAVES_ORDENACAO = ["data_liquidacao", "titulo", "data_vencimento"]
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
def _montar_url(data_alvo: dt.date, extragrupo: bool) -> str:
|
|
34
|
-
ano_mes = data_alvo.strftime("%Y%m")
|
|
35
|
-
sufixo = "E" if extragrupo else "T"
|
|
36
|
-
return f"{URL_BASE}/Neg{sufixo}{ano_mes}.ZIP"
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
@ttl_cache()
|
|
40
|
-
@retry_padrao
|
|
41
|
-
def _baixar_zip(url_arquivo: str) -> bytes:
|
|
42
|
-
"""Baixa o conteúdo ZIP e retorna os bytes."""
|
|
43
|
-
resposta = requests.get(url_arquivo, timeout=10)
|
|
44
|
-
resposta.raise_for_status()
|
|
45
|
-
return resposta.content
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
def _descompactar_zip(conteudo_zip: bytes) -> bytes:
|
|
49
|
-
"""Descompacta o ZIP e retorna o conteúdo do primeiro arquivo."""
|
|
50
|
-
with zf.ZipFile(io.BytesIO(conteudo_zip), "r") as arquivo_zip:
|
|
51
|
-
return arquivo_zip.read(arquivo_zip.namelist()[0])
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
def _parsear_df(conteudo_csv: bytes) -> pl.DataFrame:
|
|
55
|
-
"""Lê o CSV extraído do ZIP em DataFrame com todas as colunas como string."""
|
|
56
|
-
return pl.read_csv(
|
|
57
|
-
conteudo_csv,
|
|
58
|
-
encoding="latin1",
|
|
59
|
-
separator=";",
|
|
60
|
-
infer_schema=False,
|
|
61
|
-
null_values="",
|
|
62
|
-
)
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
def _processar_df(df: pl.DataFrame) -> pl.DataFrame:
|
|
66
|
-
return (
|
|
67
|
-
df.with_columns(ps.string().str.strip_chars())
|
|
68
|
-
.with_columns(
|
|
69
|
-
quantidade=pl.col("QUANT NEGOCIADA").cast(pl.Int64),
|
|
70
|
-
pu_medio=float_br("PU MED"),
|
|
71
|
-
)
|
|
72
|
-
.select(
|
|
73
|
-
data_liquidacao=pl.col("DATA MOV").str.to_date("%d/%m/%Y", strict=False),
|
|
74
|
-
titulo=pl.col("SIGLA"),
|
|
75
|
-
codigo_selic=pl.col("CODIGO").cast(pl.Int64),
|
|
76
|
-
isin=pl.col("CODIGO ISIN"),
|
|
77
|
-
data_emissao=pl.col("EMISSAO").str.to_date("%d/%m/%Y", strict=False),
|
|
78
|
-
data_vencimento=pl.col("VENCIMENTO").str.to_date("%d/%m/%Y", strict=False),
|
|
79
|
-
operacoes=pl.col("NUM DE OPER").cast(pl.Int64),
|
|
80
|
-
quantidade=pl.col("quantidade"),
|
|
81
|
-
financeiro=(pl.col("quantidade") * pl.col("pu_medio")).round(2),
|
|
82
|
-
pu_minimo=float_br("PU MIN"),
|
|
83
|
-
pu_medio=pl.col("pu_medio"),
|
|
84
|
-
pu_maximo=float_br("PU MAX"),
|
|
85
|
-
pu_lastro=float_br("PU LASTRO"),
|
|
86
|
-
valor_par=float_br("VALOR PAR"),
|
|
87
|
-
taxa_minima=float_br("TAXA MIN"),
|
|
88
|
-
taxa_media=float_br("TAXA MED"),
|
|
89
|
-
taxa_maxima=float_br("TAXA MAX"),
|
|
90
|
-
operacoes_corretagem=pl.col("NUM OPER COM CORRETAGEM").cast(pl.Int64),
|
|
91
|
-
quantidade_corretagem=pl.col("QUANT NEG COM CORRETAGEM").cast(pl.Int64),
|
|
92
|
-
)
|
|
93
|
-
.sort(CHAVES_ORDENACAO)
|
|
94
|
-
)
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
def secundario_mensal(
|
|
98
|
-
data: DateLike,
|
|
99
|
-
extragrupo: bool = False,
|
|
100
|
-
) -> pl.DataFrame:
|
|
101
|
-
"""Busca dados mensais do mercado secundário de TPFs.
|
|
102
|
-
|
|
103
|
-
Fonte: Banco Central do Brasil, sistema SELIC. Baixa o arquivo mensal de
|
|
104
|
-
negociações secundárias para o mês correspondente à data informada.
|
|
105
|
-
|
|
106
|
-
Args:
|
|
107
|
-
data: Data de referência.
|
|
108
|
-
extragrupo: Se verdadeiro, busca apenas negociações extragrupo.
|
|
109
|
-
|
|
110
|
-
Returns:
|
|
111
|
-
DataFrame Polars com dados mensais do mercado secundário.
|
|
112
|
-
|
|
113
|
-
Output Columns:
|
|
114
|
-
* data_liquidacao (Date): data de liquidação da negociação.
|
|
115
|
-
* titulo (String): sigla do título público.
|
|
116
|
-
* codigo_selic (Int64): código único no sistema SELIC.
|
|
117
|
-
* isin (String): código ISIN.
|
|
118
|
-
* data_emissao (Date): data de emissão do título.
|
|
119
|
-
* data_vencimento (Date): data de vencimento do título.
|
|
120
|
-
* operacoes (Int64): número total de operações.
|
|
121
|
-
* quantidade (Int64): quantidade total negociada.
|
|
122
|
-
* financeiro (Float64): valor financeiro negociado.
|
|
123
|
-
* pu_minimo (Float64): preço unitário mínimo.
|
|
124
|
-
* pu_medio (Float64): preço unitário médio.
|
|
125
|
-
* pu_maximo (Float64): preço unitário máximo.
|
|
126
|
-
* pu_lastro (Float64): preço unitário de lastro.
|
|
127
|
-
* valor_par (Float64): valor par do título.
|
|
128
|
-
* taxa_minima (Float64): taxa mínima.
|
|
129
|
-
* taxa_media (Float64): taxa média.
|
|
130
|
-
* taxa_maxima (Float64): taxa máxima.
|
|
131
|
-
* operacoes_corretagem (Int64): operações com corretagem.
|
|
132
|
-
* quantidade_corretagem (Int64): quantidade com corretagem.
|
|
133
|
-
|
|
134
|
-
Examples:
|
|
135
|
-
>>> df = yd.tpf.secundario_mensal("07-01-2025", extragrupo=True)
|
|
136
|
-
"""
|
|
137
|
-
if any_is_empty(data):
|
|
138
|
-
return pl.DataFrame()
|
|
139
|
-
data_alvo = converter_datas(data)
|
|
140
|
-
if (data_alvo.year, data_alvo.month) > (hoje().year, hoje().month):
|
|
141
|
-
return pl.DataFrame()
|
|
142
|
-
url = _montar_url(data_alvo, extragrupo)
|
|
143
|
-
conteudo_zip = _baixar_zip(url)
|
|
144
|
-
arquivo_extraido = _descompactar_zip(conteudo_zip)
|
|
145
|
-
df = _parsear_df(arquivo_extraido)
|
|
146
|
-
return _processar_df(df)
|
|
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
|