pyield 0.58.1__tar.gz → 0.59.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 (88) hide show
  1. {pyield-0.58.1 → pyield-0.59.0}/PKG-INFO +34 -4
  2. {pyield-0.58.1 → pyield-0.59.0}/README.md +33 -3
  3. pyield-0.59.0/pyield/.DS_Store +0 -0
  4. {pyield-0.58.1 → pyield-0.59.0}/pyield/__init__.py +2 -1
  5. {pyield-0.58.1 → pyield-0.59.0}/pyield/bc/sgs.py +70 -0
  6. pyield-0.59.0/pyield/cpm/__init__.py +14 -0
  7. pyield-0.59.0/pyield/cpm/_contratos.py +186 -0
  8. pyield-0.58.1/pyield/cpm/probabilidades.py → pyield-0.59.0/pyield/cpm/_probabilidades.py +69 -160
  9. pyield-0.59.0/pyield/igpm.py +6 -0
  10. {pyield-0.58.1 → pyield-0.59.0}/pyield/ipca/__init__.py +4 -8
  11. {pyield-0.58.1 → pyield-0.59.0}/pyield/ipca/historico.py +61 -73
  12. {pyield-0.58.1 → pyield-0.59.0}/pyield/rmd/__init__.py +6 -8
  13. {pyield-0.58.1 → pyield-0.59.0}/pyield/tpf/__init__.py +2 -0
  14. pyield-0.59.0/pyield/tpf/_leiloes_bcb.py +316 -0
  15. {pyield-0.58.1 → pyield-0.59.0}/pyield/tpf/_taxas.py +15 -14
  16. {pyield-0.58.1 → pyield-0.59.0}/pyield/tpf/leiloes.py +45 -33
  17. {pyield-0.58.1 → pyield-0.59.0}/pyield/tpf/secundario/__init__.py +2 -8
  18. {pyield-0.58.1 → pyield-0.59.0}/pyield/tpf/secundario/_mensal.py +29 -81
  19. {pyield-0.58.1 → pyield-0.59.0}/pyield/tpf/titulos/lft.py +52 -37
  20. {pyield-0.58.1 → pyield-0.59.0}/pyield/tpf/titulos/ltn.py +64 -45
  21. {pyield-0.58.1 → pyield-0.59.0}/pyield/tpf/titulos/ntnb.py +81 -47
  22. {pyield-0.58.1 → pyield-0.59.0}/pyield/tpf/titulos/ntnb1.py +36 -10
  23. {pyield-0.58.1 → pyield-0.59.0}/pyield/tpf/titulos/ntnbp.py +13 -1
  24. {pyield-0.58.1 → pyield-0.59.0}/pyield/tpf/titulos/ntnc.py +75 -43
  25. {pyield-0.58.1 → pyield-0.59.0}/pyield/tpf/titulos/ntnf.py +88 -96
  26. {pyield-0.58.1 → pyield-0.59.0}/pyield/vna/_consulta.py +0 -3
  27. {pyield-0.58.1 → pyield-0.59.0}/pyield/vna/_lft.py +7 -4
  28. {pyield-0.58.1 → pyield-0.59.0}/pyield/vna/_ntnb.py +1 -1
  29. {pyield-0.58.1 → pyield-0.59.0}/pyproject.toml +1 -1
  30. {pyield-0.58.1 → pyield-0.59.0}/pyproject.toml.orig +1 -1
  31. pyield-0.58.1/pyield/bc/leiloes.py +0 -343
  32. pyield-0.58.1/pyield/cpm/__init__.py +0 -356
  33. {pyield-0.58.1 → pyield-0.59.0}/LICENSE +0 -0
  34. {pyield-0.58.1 → pyield-0.59.0}/pyield/_internal/__init__.py +0 -0
  35. {pyield-0.58.1 → pyield-0.59.0}/pyield/_internal/br_numbers.py +0 -0
  36. {pyield-0.58.1 → pyield-0.59.0}/pyield/_internal/cache.py +0 -0
  37. {pyield-0.58.1 → pyield-0.59.0}/pyield/_internal/converters.py +0 -0
  38. {pyield-0.58.1 → pyield-0.59.0}/pyield/_internal/data_cache.py +0 -0
  39. {pyield-0.58.1 → pyield-0.59.0}/pyield/_internal/excel.py +0 -0
  40. {pyield-0.58.1 → pyield-0.59.0}/pyield/_internal/numbers.py +0 -0
  41. {pyield-0.58.1 → pyield-0.59.0}/pyield/_internal/retry.py +0 -0
  42. {pyield-0.58.1 → pyield-0.59.0}/pyield/_internal/types.py +0 -0
  43. {pyield-0.58.1 → pyield-0.59.0}/pyield/anbima/__init__.py +0 -0
  44. {pyield-0.58.1 → pyield-0.59.0}/pyield/anbima/imaq.py +0 -0
  45. {pyield-0.58.1 → pyield-0.59.0}/pyield/anbima/taxas.py +0 -0
  46. {pyield-0.58.1 → pyield-0.59.0}/pyield/b3/__init__.py +0 -0
  47. {pyield-0.58.1 → pyield-0.59.0}/pyield/b3/_contratos.py +0 -0
  48. {pyield-0.58.1 → pyield-0.59.0}/pyield/b3/_validar_pregao.py +0 -0
  49. {pyield-0.58.1 → pyield-0.59.0}/pyield/b3/boletim.py +0 -0
  50. {pyield-0.58.1 → pyield-0.59.0}/pyield/b3/derivativos_intradia.py +0 -0
  51. {pyield-0.58.1 → pyield-0.59.0}/pyield/b3/di_over.py +0 -0
  52. {pyield-0.58.1 → pyield-0.59.0}/pyield/bc/__init__.py +0 -0
  53. {pyield-0.58.1 → pyield-0.59.0}/pyield/bc/_olinda.py +0 -0
  54. {pyield-0.58.1 → pyield-0.59.0}/pyield/compromissada.py +0 -0
  55. {pyield-0.58.1 → pyield-0.59.0}/pyield/copom/__init__.py +0 -0
  56. {pyield-0.58.1 → pyield-0.59.0}/pyield/copom/_calendario.py +0 -0
  57. {pyield-0.58.1 → pyield-0.59.0}/pyield/du/__init__.py +0 -0
  58. {pyield-0.58.1 → pyield-0.59.0}/pyield/du/core.py +0 -0
  59. {pyield-0.58.1 → pyield-0.59.0}/pyield/du/feriados_br/__init__.py +0 -0
  60. {pyield-0.58.1 → pyield-0.59.0}/pyield/du/feriados_br/anteriores.txt +0 -0
  61. {pyield-0.58.1 → pyield-0.59.0}/pyield/du/feriados_br/atuais.txt +0 -0
  62. {pyield-0.58.1 → pyield-0.59.0}/pyield/futuro/__init__.py +0 -0
  63. {pyield-0.58.1 → pyield-0.59.0}/pyield/futuro/contratos.py +0 -0
  64. {pyield-0.58.1 → pyield-0.59.0}/pyield/futuro/di1.py +0 -0
  65. {pyield-0.58.1 → pyield-0.59.0}/pyield/futuro/historico.py +0 -0
  66. {pyield-0.58.1 → pyield-0.59.0}/pyield/futuro/intradia.py +0 -0
  67. {pyield-0.58.1 → pyield-0.59.0}/pyield/fwd.py +0 -0
  68. {pyield-0.58.1 → pyield-0.59.0}/pyield/interpolador.py +0 -0
  69. {pyield-0.58.1 → pyield-0.59.0}/pyield/ipca/projetado.py +0 -0
  70. {pyield-0.58.1 → pyield-0.59.0}/pyield/py.typed +0 -0
  71. {pyield-0.58.1 → pyield-0.59.0}/pyield/relogio.py +0 -0
  72. {pyield-0.58.1 → pyield-0.59.0}/pyield/rmd/_aba_1_3.py +0 -0
  73. {pyield-0.58.1 → pyield-0.59.0}/pyield/rmd/_aba_2_1.py +0 -0
  74. {pyield-0.58.1 → pyield-0.59.0}/pyield/rmd/_common.py +0 -0
  75. {pyield-0.58.1 → pyield-0.59.0}/pyield/rmd/_download.py +0 -0
  76. {pyield-0.58.1 → pyield-0.59.0}/pyield/selic/__init__.py +0 -0
  77. {pyield-0.58.1 → pyield-0.59.0}/pyield/tpf/benchmark.py +0 -0
  78. {pyield-0.58.1 → pyield-0.59.0}/pyield/tpf/dealers.py +0 -0
  79. {pyield-0.58.1 → pyield-0.59.0}/pyield/tpf/secundario/_intradia.py +0 -0
  80. {pyield-0.58.1 → pyield-0.59.0}/pyield/tpf/titulos/__init__.py +0 -0
  81. {pyield-0.58.1 → pyield-0.59.0}/pyield/tpf/titulos/_ntnb_bootstrap.py +0 -0
  82. {pyield-0.58.1 → pyield-0.59.0}/pyield/tpf/titulos/_ntnf_bootstrap.py +0 -0
  83. {pyield-0.58.1 → pyield-0.59.0}/pyield/tpf/titulos/_utils.py +0 -0
  84. {pyield-0.58.1 → pyield-0.59.0}/pyield/tpf/titulos/pre.py +0 -0
  85. {pyield-0.58.1 → pyield-0.59.0}/pyield/vna/__init__.py +0 -0
  86. {pyield-0.58.1 → pyield-0.59.0}/pyield/vna/_calculo.py +0 -0
  87. {pyield-0.58.1 → pyield-0.59.0}/pyield/vna/_download.py +0 -0
  88. {pyield-0.58.1 → pyield-0.59.0}/pyield/vna/_ntnc.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyield
3
- Version: 0.58.1
3
+ Version: 0.59.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
@@ -76,7 +76,7 @@ uv add pyield
76
76
  | `yd.forwards(...)` | função | Curva de taxas a termo | |
77
77
  | `yd.futuro` | módulo | Contratos futuros da B3 | `di1`, `historico`, `intradia`, `datas_disponiveis`, `vencimento`, `enriquecer`, `vencimento_expr` |
78
78
  | `yd.di1` | módulo | Curva DI1 e interpolação | `dados`, `interpolar_taxas`, `interpolar_taxa`, `datas_disponiveis` |
79
- | `yd.tpf` | módulo | Títulos públicos federais | `taxas`, `taxas_historicas`, `vencimentos`, `estoque`, `leiloes`, `benchmarks`, `curva_pre`, `premios_pre`, `secundario` |
79
+ | `yd.tpf` | módulo | Títulos públicos federais | `taxas`, `taxas_historicas`, `vencimentos`, `estoque`, `leiloes`, `leiloes_bcb`, `benchmarks`, `curva_pre`, `premios_pre`, `secundario` |
80
80
  | `yd.rmd(aba)` | função | Relatório Mensal da Dívida do Tesouro Nacional | |
81
81
  | `yd.lft` | módulo | LFT | `dados`, `vencimentos`, `cotacao`, `pu`, `taxa`, `rentabilidade`, `rentabilidade_expr` |
82
82
  | `yd.ltn` | módulo | LTN | `dados`, `vencimentos`, `pu`, `taxa`, `duration_expr`, `dv01`, `dv01_expr`, `rentabilidade`, `rentabilidade_expr`, `taxas_forward` |
@@ -89,8 +89,9 @@ uv add pyield
89
89
  | `yd.copom` | módulo | Calendário automático do Copom | `calendario`, `proxima_reuniao` |
90
90
  | `yd.compromissadas(...)` | função | Leilões de operações compromissadas do BCB | `inicio`, `fim` |
91
91
  | `yd.selic` | módulo | Selic e política monetária | `over`, `over_serie`, `meta`, `meta_serie` |
92
- | `yd.cpm` | módulo | Opções digitais do COPOM e análises derivadas | `data`, `probabilidades` |
93
- | `yd.ipca` | módulo | IPCA histórico e projetado | `indice`, `indices`, `indices_ultimos`, `taxa`, `taxas`, `taxas_ultimas`, `taxa_projetada` |
92
+ | `yd.cpm` | módulo | Opções digitais do COPOM e análises derivadas | `contratos`, `probabilidades` |
93
+ | `yd.ipca` | módulo | IPCA histórico e projetado | `indice`, `indice_serie`, `taxa`, `taxa_serie`, `taxa_projetada` |
94
+ | `yd.igpm` | módulo | Taxas mensais do IGP-M via SGS/BCB | `taxa`, `taxa_serie` |
94
95
  | `yd.ptax(data)` | função | PTAX para uma data | |
95
96
  | `yd.ptax_serie(inicio, fim)` | função | Série histórica da PTAX | |
96
97
  | `yd.di_over(data)` | função | Taxa DI Over | |
@@ -102,6 +103,35 @@ documentação detalhada e as assinaturas públicas.
102
103
 
103
104
  ## Compatibilidade da API
104
105
 
106
+ ### Séries do IPCA
107
+
108
+ As consultas históricas do IPCA seguem o padrão das séries de Selic e PTAX:
109
+ `taxa(data)` e `indice(data)` retornam um valor; `taxa_serie` e `indice_serie`
110
+ retornam séries por intervalo ou pelos últimos meses.
111
+
112
+ Para migrar, substitua `ipca.taxas(inicio, fim)` por
113
+ `ipca.taxa_serie(inicio, fim)` e `ipca.indices(inicio, fim)` por
114
+ `ipca.indice_serie(inicio, fim)`. Substitua `ipca.taxas_ultimas(n)` por
115
+ `ipca.taxa_serie(ultimos=n)` e `ipca.indices_ultimos(n)` por
116
+ `ipca.indice_serie(ultimos=n)`. Os nomes antigos foram removidos; colunas,
117
+ unidades e valores retornados permanecem iguais.
118
+
119
+ Nas séries, `fim=None` usa a data atual no Brasil. Informe `inicio` ou
120
+ `ultimos`; quando informado, `ultimos` tem prioridade sobre as datas.
121
+ `taxa_projetada` permanece inalterada.
122
+
123
+ ### Taxas e DV01 analíticos — 0.59.0
124
+
125
+ As funções `taxa` de `yd.ltn`, `yd.ntnf`, `yd.ntnb`, `yd.ntnc` e `yd.lft`
126
+ passam a retornar `float` sem truncamento, em vez de `Decimal` truncado em oito
127
+ casas. Entradas inválidas retornam `float("nan")`; use `math.isnan` no lugar de
128
+ `.is_nan()`.
129
+
130
+ `taxa` e `dv01` de todos os títulos descontam os fluxos com `dias úteis / 252`,
131
+ sem os truncamentos intermediários da STN. Os valores mudam levemente em
132
+ relação à versão anterior, e `taxa(pu(...))` recupera a taxa original apenas
133
+ aproximadamente. `pu` e `cotacao` seguem a metodologia oficial e não mudaram.
134
+
105
135
  ### Cotações em base 100 — 0.58.0
106
136
 
107
137
  As funções escalares de cálculo dos títulos também aceitam taxas percentuais
@@ -51,7 +51,7 @@ uv add pyield
51
51
  | `yd.forwards(...)` | função | Curva de taxas a termo | |
52
52
  | `yd.futuro` | módulo | Contratos futuros da B3 | `di1`, `historico`, `intradia`, `datas_disponiveis`, `vencimento`, `enriquecer`, `vencimento_expr` |
53
53
  | `yd.di1` | módulo | Curva DI1 e interpolação | `dados`, `interpolar_taxas`, `interpolar_taxa`, `datas_disponiveis` |
54
- | `yd.tpf` | módulo | Títulos públicos federais | `taxas`, `taxas_historicas`, `vencimentos`, `estoque`, `leiloes`, `benchmarks`, `curva_pre`, `premios_pre`, `secundario` |
54
+ | `yd.tpf` | módulo | Títulos públicos federais | `taxas`, `taxas_historicas`, `vencimentos`, `estoque`, `leiloes`, `leiloes_bcb`, `benchmarks`, `curva_pre`, `premios_pre`, `secundario` |
55
55
  | `yd.rmd(aba)` | função | Relatório Mensal da Dívida do Tesouro Nacional | |
56
56
  | `yd.lft` | módulo | LFT | `dados`, `vencimentos`, `cotacao`, `pu`, `taxa`, `rentabilidade`, `rentabilidade_expr` |
57
57
  | `yd.ltn` | módulo | LTN | `dados`, `vencimentos`, `pu`, `taxa`, `duration_expr`, `dv01`, `dv01_expr`, `rentabilidade`, `rentabilidade_expr`, `taxas_forward` |
@@ -64,8 +64,9 @@ uv add pyield
64
64
  | `yd.copom` | módulo | Calendário automático do Copom | `calendario`, `proxima_reuniao` |
65
65
  | `yd.compromissadas(...)` | função | Leilões de operações compromissadas do BCB | `inicio`, `fim` |
66
66
  | `yd.selic` | módulo | Selic e política monetária | `over`, `over_serie`, `meta`, `meta_serie` |
67
- | `yd.cpm` | módulo | Opções digitais do COPOM e análises derivadas | `data`, `probabilidades` |
68
- | `yd.ipca` | módulo | IPCA histórico e projetado | `indice`, `indices`, `indices_ultimos`, `taxa`, `taxas`, `taxas_ultimas`, `taxa_projetada` |
67
+ | `yd.cpm` | módulo | Opções digitais do COPOM e análises derivadas | `contratos`, `probabilidades` |
68
+ | `yd.ipca` | módulo | IPCA histórico e projetado | `indice`, `indice_serie`, `taxa`, `taxa_serie`, `taxa_projetada` |
69
+ | `yd.igpm` | módulo | Taxas mensais do IGP-M via SGS/BCB | `taxa`, `taxa_serie` |
69
70
  | `yd.ptax(data)` | função | PTAX para uma data | |
70
71
  | `yd.ptax_serie(inicio, fim)` | função | Série histórica da PTAX | |
71
72
  | `yd.di_over(data)` | função | Taxa DI Over | |
@@ -77,6 +78,35 @@ documentação detalhada e as assinaturas públicas.
77
78
 
78
79
  ## Compatibilidade da API
79
80
 
81
+ ### Séries do IPCA
82
+
83
+ As consultas históricas do IPCA seguem o padrão das séries de Selic e PTAX:
84
+ `taxa(data)` e `indice(data)` retornam um valor; `taxa_serie` e `indice_serie`
85
+ retornam séries por intervalo ou pelos últimos meses.
86
+
87
+ Para migrar, substitua `ipca.taxas(inicio, fim)` por
88
+ `ipca.taxa_serie(inicio, fim)` e `ipca.indices(inicio, fim)` por
89
+ `ipca.indice_serie(inicio, fim)`. Substitua `ipca.taxas_ultimas(n)` por
90
+ `ipca.taxa_serie(ultimos=n)` e `ipca.indices_ultimos(n)` por
91
+ `ipca.indice_serie(ultimos=n)`. Os nomes antigos foram removidos; colunas,
92
+ unidades e valores retornados permanecem iguais.
93
+
94
+ Nas séries, `fim=None` usa a data atual no Brasil. Informe `inicio` ou
95
+ `ultimos`; quando informado, `ultimos` tem prioridade sobre as datas.
96
+ `taxa_projetada` permanece inalterada.
97
+
98
+ ### Taxas e DV01 analíticos — 0.59.0
99
+
100
+ As funções `taxa` de `yd.ltn`, `yd.ntnf`, `yd.ntnb`, `yd.ntnc` e `yd.lft`
101
+ passam a retornar `float` sem truncamento, em vez de `Decimal` truncado em oito
102
+ casas. Entradas inválidas retornam `float("nan")`; use `math.isnan` no lugar de
103
+ `.is_nan()`.
104
+
105
+ `taxa` e `dv01` de todos os títulos descontam os fluxos com `dias úteis / 252`,
106
+ sem os truncamentos intermediários da STN. Os valores mudam levemente em
107
+ relação à versão anterior, e `taxa(pu(...))` recupera a taxa original apenas
108
+ aproximadamente. `pu` e `cotacao` seguem a metodologia oficial e não mudaram.
109
+
80
110
  ### Cotações em base 100 — 0.58.0
81
111
 
82
112
  As funções escalares de cálculo dos títulos também aceitam taxas percentuais
Binary file
@@ -3,7 +3,7 @@
3
3
  import logging
4
4
  from importlib.metadata import PackageNotFoundError, version
5
5
 
6
- from pyield import b3, du, ipca
6
+ from pyield import b3, du, igpm, ipca
7
7
  from pyield.b3.di_over import di_over
8
8
  from pyield import futuro
9
9
  from pyield.futuro import di1
@@ -39,6 +39,7 @@ __all__ = [
39
39
  "forwards_expr",
40
40
  "futuro",
41
41
  "hoje",
42
+ "igpm",
42
43
  "Interpolador",
43
44
  "interpolar",
44
45
  "ipca",
@@ -1,6 +1,7 @@
1
1
  """Séries do Sistema Gerenciador de Séries (SGS) do Banco Central.
2
2
 
3
3
  Séries disponíveis:
4
+ - IGP-M mensal (SGS 189)
4
5
  - PTAX Venda (SGS 1)
5
6
  - SELIC Diária (SGS 11)
6
7
  - SELIC Meta (SGS 432)
@@ -56,6 +57,7 @@ class SerieSGS(Enum):
56
57
 
57
58
  PTAX_VENDA = 1
58
59
  SELIC_DIARIA = 11
60
+ IGPM = 189
59
61
  SELIC_META = 432
60
62
  SELIC_OVER = 1178
61
63
 
@@ -184,6 +186,74 @@ def _extrair_escalar(df: pl.DataFrame, coluna: str) -> float:
184
186
  return df[coluna].item(0)
185
187
 
186
188
 
189
+ def igpm_taxa_serie(
190
+ inicio: DateLike | None = None,
191
+ fim: DateLike | None = None,
192
+ *,
193
+ ultimos: int | None = None,
194
+ ) -> pl.DataFrame:
195
+ """Obtém as taxas mensais do IGP-M via SGS do Banco Central (série 189).
196
+
197
+ O índice é produzido pela FGV. A série representa a variação mensal,
198
+ não o número-índice nem a variação acumulada em 12 meses.
199
+
200
+ Args:
201
+ inicio: Qualquer data no primeiro mês do intervalo.
202
+ fim: Qualquer data no último mês do intervalo. Se ``None``, usa a
203
+ data atual no Brasil.
204
+ ultimos: Número de meses mais recentes a recuperar. Se informado,
205
+ tem prioridade sobre ``inicio`` e ``fim``.
206
+
207
+ Returns:
208
+ DataFrame com as taxas mensais em decimal, ou vazio se não houver dados.
209
+
210
+ Output Columns:
211
+ * periodo (Int64): competência mensal no formato YYYYMM.
212
+ * taxa (Float64): variação mensal em decimal (0.005 = 0,50%).
213
+
214
+ Raises:
215
+ ValueError: Se não informar ``inicio`` nem ``ultimos``, ou se
216
+ ``ultimos`` for menor ou igual a 0.
217
+
218
+ Examples:
219
+ >>> df = yd.igpm.taxa_serie("01-01-2025", "31-03-2025")
220
+ >>> df = yd.igpm.taxa_serie(ultimos=12)
221
+ """
222
+ if ultimos is not None:
223
+ if ultimos <= 0:
224
+ raise ValueError("O número de meses deve ser maior que 0.")
225
+ else:
226
+ if inicio is None:
227
+ raise ValueError("Informe 'inicio' ou 'ultimos'.")
228
+ inicio = converter_datas(inicio).replace(day=1)
229
+ fim = converter_datas(fim).replace(day=1) if fim is not None else None
230
+ return (
231
+ _buscar_serie(SerieSGS.IGPM, inicio, fim, ultimos)
232
+ .select(
233
+ periodo=pl.col("data").dt.strftime("%Y%m").cast(pl.Int64),
234
+ taxa=pct_para_decimal(pl.col("valor")),
235
+ )
236
+ .sort("periodo")
237
+ )
238
+
239
+
240
+ def igpm_taxa(data: DateLike) -> float:
241
+ """Taxa mensal do IGP-M via SGS do Banco Central (série 189, fonte FGV).
242
+
243
+ Args:
244
+ data: Qualquer data dentro do mês desejado.
245
+
246
+ Returns:
247
+ Variação mensal em decimal ou ``nan`` se não disponível.
248
+
249
+ Examples:
250
+ >>> taxa = yd.igpm.taxa("15-01-2025")
251
+ """
252
+ if any_is_empty(data):
253
+ return float("nan")
254
+ return _extrair_escalar(igpm_taxa_serie(data, data), "taxa")
255
+
256
+
187
257
  # ── SELIC Over ───────────────────────────────────────────────────────
188
258
 
189
259
 
@@ -0,0 +1,14 @@
1
+ """CPM: opções digitais da B3 sobre a decisão do COPOM.
2
+
3
+ Membros:
4
+ contratos: contratos CPM negociados em uma data.
5
+ probabilidades: probabilidades implícitas de cada reunião do COPOM.
6
+ """
7
+
8
+ from pyield.cpm._contratos import contratos
9
+ from pyield.cpm._probabilidades import probabilidades
10
+
11
+ __all__ = [
12
+ "contratos",
13
+ "probabilidades",
14
+ ]
@@ -0,0 +1,186 @@
1
+ """Contratos CPM: opções digitais da B3 sobre a decisão do COPOM.
2
+
3
+ Formato do código de negociação: ``CPM{mes}{ano2}{C|P}{strike6}``, por exemplo
4
+ ``CPMZ25C099500``: mês ``Z`` (dezembro), ano 2025, call, strike 99,500. A
5
+ variação da Selic em pontos-base é ``(strike - 100) * 100`` (aqui, -50 bps).
6
+
7
+ O CPM expira no primeiro dia útil após o fim da reunião do COPOM, e não no
8
+ primeiro dia útil do mês. Por isso, as datas da reunião e de expiração vêm do
9
+ calendário do COPOM, unido pelo mês e ano do código. A junção é ``left`` para
10
+ não descartar contratos de reuniões ainda ausentes do calendário.
11
+ """
12
+
13
+ import datetime as dt
14
+
15
+ import polars as pl
16
+ import requests
17
+
18
+ from pyield import copom, du
19
+ from pyield._internal.converters import converter_datas
20
+ from pyield._internal.retry import retry_padrao
21
+ from pyield._internal.types import DateLike
22
+ from pyield.b3 import boletim
23
+ from pyield.futuro.contratos import _MAPA_MESES
24
+
25
+ # Mapeamento mínimo para consumo do módulo CPM a partir do schema XML bruto.
26
+ _RENOMEAR_COLUNAS_CPM = {
27
+ "TckrSymb": "codigo_negociacao",
28
+ }
29
+
30
+
31
+ def _df_vazio() -> pl.DataFrame:
32
+ return pl.DataFrame(
33
+ schema={
34
+ "data_referencia": pl.Date,
35
+ "codigo_negociacao": pl.String,
36
+ "data_fim_reuniao": pl.Date,
37
+ "data_expiracao": pl.Date,
38
+ "tipo_opcao": pl.String,
39
+ "variacao_strike_bps": pl.Int32,
40
+ "preco_ajuste": pl.Float64,
41
+ "dias_uteis": pl.Int32,
42
+ }
43
+ )
44
+
45
+
46
+ _CSV_ESQUEMA = {"Instrumento financeiro": pl.String, "Preço de referência": pl.Float64}
47
+
48
+
49
+ @retry_padrao
50
+ def _buscar_csv(data: dt.date) -> bytes:
51
+ """Busca o CSV diário de derivativos consolidados na B3."""
52
+ url = "https://arquivos.b3.com.br/bdi/table/export/csv"
53
+ parametros = {"lang": "pt-BR"}
54
+ data_str = data.strftime("%Y-%m-%d")
55
+ carga = {
56
+ "Name": "ConsolidatedTradesDerivatives",
57
+ "Date": data_str,
58
+ "FinalDate": data_str,
59
+ "ClientId": "",
60
+ "Filters": {},
61
+ }
62
+ cabecalhos = {
63
+ "Content-Type": "application/json",
64
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", # noqa: E501
65
+ "Accept": "application/json, text/plain, */*",
66
+ }
67
+ resposta = requests.post(
68
+ url, params=parametros, json=carga, headers=cabecalhos, timeout=(5, 30)
69
+ )
70
+ resposta.raise_for_status()
71
+ return resposta.content
72
+
73
+
74
+ def _parsear_precos_referencia(csv_bytes: bytes) -> pl.DataFrame:
75
+ """Extrai o Preço de Referência dos contratos CPM do CSV da B3."""
76
+ try:
77
+ df = pl.read_csv(
78
+ csv_bytes.replace(b".", b""),
79
+ separator=";",
80
+ skip_lines=2,
81
+ null_values=["-"],
82
+ decimal_comma=True,
83
+ schema_overrides=_CSV_ESQUEMA,
84
+ encoding="utf-8-sig",
85
+ )
86
+ except pl.exceptions.NoDataError:
87
+ # Para datas antigas, a B3 devolve apenas o cabeçalho, sem preâmbulo.
88
+ return pl.DataFrame(
89
+ schema={"codigo_negociacao": pl.String, "preco_ajuste": pl.Float64}
90
+ )
91
+
92
+ return df.select(
93
+ codigo_negociacao="Instrumento financeiro",
94
+ preco_ajuste="Preço de referência",
95
+ ).filter(pl.col("codigo_negociacao").str.starts_with("CPM"))
96
+
97
+
98
+ def contratos(data: DateLike) -> pl.DataFrame:
99
+ """Busca os contratos CPM negociados em uma data.
100
+
101
+ Fonte: B3. A lista de contratos vem do boletim de preços (SPR) e o preço de
102
+ ajuste vem do "Preço de Referência" do boletim de derivativos consolidados,
103
+ o mesmo exibido no painel "Probabilidades da Taxa Selic Meta" da B3. As
104
+ datas da reunião e de expiração vêm do calendário do COPOM.
105
+
106
+ Args:
107
+ data: Data de negociação.
108
+
109
+ Returns:
110
+ DataFrame Polars ordenado por ``data_expiracao`` e
111
+ ``variacao_strike_bps``. Retorna DataFrame vazio, com o schema abaixo,
112
+ se não houver contratos CPM na data.
113
+
114
+ Output Columns:
115
+ * data_referencia (Date): data de negociação.
116
+ * codigo_negociacao (String): código do contrato (ex.: ``CPMZ25C099500``).
117
+ * data_fim_reuniao (Date): último dia da reunião do COPOM.
118
+ * data_expiracao (Date): primeiro dia útil após ``data_fim_reuniao``,
119
+ data de liquidação do contrato.
120
+ * tipo_opcao (String): ``"call"`` ou ``"put"``.
121
+ * variacao_strike_bps (Int32): variação da Selic Meta, em pontos-base,
122
+ associada ao strike.
123
+ * preco_ajuste (Float64): Preço de Referência da B3, em pontos (0–100).
124
+ * dias_uteis (Int32): dias úteis de ``data_referencia`` até
125
+ ``data_expiracao``.
126
+
127
+ Notes:
128
+ A B3 só publica o Preço de Referência de datas recentes (cerca de um
129
+ mês). Para datas mais antigas, ``preco_ajuste`` é nulo.
130
+
131
+ Examples:
132
+ >>> df = yd.cpm.contratos("29-01-2025") # doctest: +SKIP
133
+ """
134
+ data_negociacao = converter_datas(data)
135
+ df = boletim.buscar(data_negociacao, prefixo_ticker="CPM")
136
+ if df.is_empty():
137
+ return _df_vazio()
138
+
139
+ df = df.rename(_RENOMEAR_COLUNAS_CPM, strict=False).with_columns(
140
+ data_referencia=data_negociacao,
141
+ tipo_opcao=pl.col("codigo_negociacao")
142
+ .str.slice(6, 1)
143
+ .replace({"C": "call", "P": "put"}),
144
+ variacao_strike_bps=(
145
+ pl.col("codigo_negociacao")
146
+ .str.slice(7, 6)
147
+ .cast(pl.Int64, strict=False)
148
+ .floordiv(10)
149
+ .sub(10_000)
150
+ .cast(pl.Int32)
151
+ ),
152
+ _mes_reuniao=pl.col("codigo_negociacao")
153
+ .str.slice(3, 1)
154
+ .replace_strict(_MAPA_MESES, default=None, return_dtype=pl.Int32),
155
+ _ano_reuniao=(
156
+ pl.col("codigo_negociacao")
157
+ .str.slice(4, 2)
158
+ .cast(pl.Int32, strict=False)
159
+ .add(2000)
160
+ ),
161
+ )
162
+
163
+ cal = copom.calendario().select(
164
+ _mes_reuniao=pl.col("data_decisao").dt.month().cast(pl.Int32),
165
+ _ano_reuniao=pl.col("data_decisao").dt.year().cast(pl.Int32),
166
+ data_fim_reuniao=pl.col("data_decisao"),
167
+ data_expiracao=pl.col("data_efetividade"),
168
+ )
169
+
170
+ df = df.join(cal, on=["_mes_reuniao", "_ano_reuniao"], how="left").drop(
171
+ "_mes_reuniao", "_ano_reuniao"
172
+ )
173
+
174
+ # O SPR não traz o Preço de Referência das opções; ele vem do CSV consolidado.
175
+ precos = _parsear_precos_referencia(_buscar_csv(data_negociacao))
176
+
177
+ return (
178
+ df.join(precos, on="codigo_negociacao", how="left")
179
+ .with_columns(
180
+ dias_uteis=du.contar_expr("data_referencia", "data_expiracao").cast(
181
+ pl.Int32
182
+ )
183
+ )
184
+ .select(_df_vazio().columns)
185
+ .sort("data_expiracao", "variacao_strike_bps")
186
+ )