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.
Files changed (70) hide show
  1. {pyield-0.52.2 → pyield-0.53.0}/PKG-INFO +3 -3
  2. {pyield-0.52.2 → pyield-0.53.0}/README.md +2 -2
  3. {pyield-0.52.2 → pyield-0.53.0}/pyield/_internal/converters.py +3 -2
  4. {pyield-0.52.2 → pyield-0.53.0}/pyield/b3/boletim.py +1 -0
  5. {pyield-0.52.2 → pyield-0.53.0}/pyield/b3/derivativos_intradia.py +7 -0
  6. {pyield-0.52.2 → pyield-0.53.0}/pyield/tpf/__init__.py +2 -4
  7. pyield-0.53.0/pyield/tpf/secundario.py +410 -0
  8. {pyield-0.52.2 → pyield-0.53.0}/pyproject.toml +1 -2
  9. pyield-0.52.2/pyield/bc/tpf_intradia.py +0 -148
  10. pyield-0.52.2/pyield/bc/tpf_mensal.py +0 -146
  11. {pyield-0.52.2 → pyield-0.53.0}/LICENSE +0 -0
  12. {pyield-0.52.2 → pyield-0.53.0}/pyield/__init__.py +0 -0
  13. {pyield-0.52.2 → pyield-0.53.0}/pyield/_internal/__init__.py +0 -0
  14. {pyield-0.52.2 → pyield-0.53.0}/pyield/_internal/br_numbers.py +0 -0
  15. {pyield-0.52.2 → pyield-0.53.0}/pyield/_internal/cache.py +0 -0
  16. {pyield-0.52.2 → pyield-0.53.0}/pyield/_internal/data_cache.py +0 -0
  17. {pyield-0.52.2 → pyield-0.53.0}/pyield/_internal/retry.py +0 -0
  18. {pyield-0.52.2 → pyield-0.53.0}/pyield/_internal/types.py +0 -0
  19. {pyield-0.52.2 → pyield-0.53.0}/pyield/anbima/__init__.py +0 -0
  20. {pyield-0.52.2 → pyield-0.53.0}/pyield/anbima/imaq.py +0 -0
  21. {pyield-0.52.2 → pyield-0.53.0}/pyield/anbima/mercado_secundario.py +0 -0
  22. {pyield-0.52.2 → pyield-0.53.0}/pyield/b3/__init__.py +0 -0
  23. {pyield-0.52.2 → pyield-0.53.0}/pyield/b3/_contratos.py +0 -0
  24. {pyield-0.52.2 → pyield-0.53.0}/pyield/b3/_validar_pregao.py +0 -0
  25. {pyield-0.52.2 → pyield-0.53.0}/pyield/b3/di_over.py +0 -0
  26. {pyield-0.52.2 → pyield-0.53.0}/pyield/bc/__init__.py +0 -0
  27. {pyield-0.52.2 → pyield-0.53.0}/pyield/bc/_olinda.py +0 -0
  28. {pyield-0.52.2 → pyield-0.53.0}/pyield/bc/leiloes.py +0 -0
  29. {pyield-0.52.2 → pyield-0.53.0}/pyield/bc/lft.py +0 -0
  30. {pyield-0.52.2 → pyield-0.53.0}/pyield/bc/sgs.py +0 -0
  31. {pyield-0.52.2 → pyield-0.53.0}/pyield/du/__init__.py +0 -0
  32. {pyield-0.52.2 → pyield-0.53.0}/pyield/du/core.py +0 -0
  33. {pyield-0.52.2 → pyield-0.53.0}/pyield/du/feriados/__init__.py +0 -0
  34. {pyield-0.52.2 → pyield-0.53.0}/pyield/du/feriados/feriados_antigos_br.txt +0 -0
  35. {pyield-0.52.2 → pyield-0.53.0}/pyield/du/feriados/feriados_br.py +0 -0
  36. {pyield-0.52.2 → pyield-0.53.0}/pyield/du/feriados/feriados_novos_br.txt +0 -0
  37. {pyield-0.52.2 → pyield-0.53.0}/pyield/futuro/__init__.py +0 -0
  38. {pyield-0.52.2 → pyield-0.53.0}/pyield/futuro/contratos.py +0 -0
  39. {pyield-0.52.2 → pyield-0.53.0}/pyield/futuro/di1.py +0 -0
  40. {pyield-0.52.2 → pyield-0.53.0}/pyield/futuro/historico.py +0 -0
  41. {pyield-0.52.2 → pyield-0.53.0}/pyield/futuro/intradia.py +0 -0
  42. {pyield-0.52.2 → pyield-0.53.0}/pyield/fwd.py +0 -0
  43. {pyield-0.52.2 → pyield-0.53.0}/pyield/interpolador.py +0 -0
  44. {pyield-0.52.2 → pyield-0.53.0}/pyield/ipca/__init__.py +0 -0
  45. {pyield-0.52.2 → pyield-0.53.0}/pyield/ipca/historico.py +0 -0
  46. {pyield-0.52.2 → pyield-0.53.0}/pyield/ipca/projetado.py +0 -0
  47. {pyield-0.52.2 → pyield-0.53.0}/pyield/py.typed +0 -0
  48. {pyield-0.52.2 → pyield-0.53.0}/pyield/relogio.py +0 -0
  49. {pyield-0.52.2 → pyield-0.53.0}/pyield/selic/__init__.py +0 -0
  50. {pyield-0.52.2 → pyield-0.53.0}/pyield/selic/compromissada.py +0 -0
  51. {pyield-0.52.2 → pyield-0.53.0}/pyield/selic/copom.py +0 -0
  52. {pyield-0.52.2 → pyield-0.53.0}/pyield/selic/cpm.py +0 -0
  53. {pyield-0.52.2 → pyield-0.53.0}/pyield/selic/probabilities.py +0 -0
  54. {pyield-0.52.2 → pyield-0.53.0}/pyield/tpf/_titulos/__init__.py +0 -0
  55. {pyield-0.52.2 → pyield-0.53.0}/pyield/tpf/_titulos/lft.py +0 -0
  56. {pyield-0.52.2 → pyield-0.53.0}/pyield/tpf/_titulos/ltn.py +0 -0
  57. {pyield-0.52.2 → pyield-0.53.0}/pyield/tpf/_titulos/ntnb.py +0 -0
  58. {pyield-0.52.2 → pyield-0.53.0}/pyield/tpf/_titulos/ntnb1.py +0 -0
  59. {pyield-0.52.2 → pyield-0.53.0}/pyield/tpf/_titulos/ntnbprinc.py +0 -0
  60. {pyield-0.52.2 → pyield-0.53.0}/pyield/tpf/_titulos/ntnc.py +0 -0
  61. {pyield-0.52.2 → pyield-0.53.0}/pyield/tpf/_titulos/ntnf.py +0 -0
  62. {pyield-0.52.2 → pyield-0.53.0}/pyield/tpf/benchmark.py +0 -0
  63. {pyield-0.52.2 → pyield-0.53.0}/pyield/tpf/leiloes.py +0 -0
  64. {pyield-0.52.2 → pyield-0.53.0}/pyield/tpf/pre.py +0 -0
  65. {pyield-0.52.2 → pyield-0.53.0}/pyield/tpf/rmd/__init__.py +0 -0
  66. {pyield-0.52.2 → pyield-0.53.0}/pyield/tpf/rmd/_aba_1_3.py +0 -0
  67. {pyield-0.52.2 → pyield-0.53.0}/pyield/tpf/rmd/_aba_2_1.py +0 -0
  68. {pyield-0.52.2 → pyield-0.53.0}/pyield/tpf/rmd/_common.py +0 -0
  69. {pyield-0.52.2 → pyield-0.53.0}/pyield/tpf/rmd/_download.py +0 -0
  70. {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.52.2
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`, `secundario_mensal`, `secundario_intradia` |
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.secundario_mensal("01-01-2030").is_empty() # -> True
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`, `secundario_mensal`, `secundario_intradia` |
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.secundario_mensal("01-01-2030").is_empty() # -> True
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
- # Se o elemento for Date, o cast resolve no 1o termo.
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.bc.tpf_intradia import secundario_intradia
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
- "secundario_intradia",
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.52.2"
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