pyield 0.57.2__tar.gz → 0.58.1__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 (85) hide show
  1. {pyield-0.57.2 → pyield-0.58.1}/PKG-INFO +35 -6
  2. {pyield-0.57.2 → pyield-0.58.1}/README.md +34 -5
  3. {pyield-0.57.2 → pyield-0.58.1}/pyield/__init__.py +1 -2
  4. {pyield-0.57.2 → pyield-0.58.1}/pyield/_internal/cache.py +7 -1
  5. {pyield-0.57.2 → pyield-0.58.1}/pyield/b3/boletim.py +0 -4
  6. {pyield-0.57.2 → pyield-0.58.1}/pyield/bc/leiloes.py +2 -2
  7. {pyield-0.57.2 → pyield-0.58.1}/pyield/bc/sgs.py +3 -1
  8. {pyield-0.57.2 → pyield-0.58.1}/pyield/cpm/__init__.py +4 -0
  9. pyield-0.58.1/pyield/cpm/probabilidades.py +347 -0
  10. {pyield-0.57.2 → pyield-0.58.1}/pyield/fwd.py +20 -68
  11. {pyield-0.57.2 → pyield-0.58.1}/pyield/interpolador.py +50 -81
  12. {pyield-0.57.2 → pyield-0.58.1}/pyield/tpf/__init__.py +1 -2
  13. {pyield-0.57.2 → pyield-0.58.1}/pyield/tpf/leiloes.py +2 -2
  14. {pyield-0.57.2 → pyield-0.58.1}/pyield/tpf/titulos/_ntnb_bootstrap.py +29 -11
  15. {pyield-0.57.2 → pyield-0.58.1}/pyield/tpf/titulos/_ntnf_bootstrap.py +3 -2
  16. {pyield-0.57.2 → pyield-0.58.1}/pyield/tpf/titulos/_utils.py +100 -122
  17. {pyield-0.57.2 → pyield-0.58.1}/pyield/tpf/titulos/lft.py +68 -56
  18. {pyield-0.57.2 → pyield-0.58.1}/pyield/tpf/titulos/ltn.py +66 -54
  19. {pyield-0.57.2 → pyield-0.58.1}/pyield/tpf/titulos/ntnb.py +104 -81
  20. {pyield-0.57.2 → pyield-0.58.1}/pyield/tpf/titulos/ntnb1.py +125 -73
  21. {pyield-0.57.2 → pyield-0.58.1}/pyield/tpf/titulos/ntnbp.py +76 -24
  22. {pyield-0.57.2 → pyield-0.58.1}/pyield/tpf/titulos/ntnc.py +238 -86
  23. {pyield-0.57.2 → pyield-0.58.1}/pyield/tpf/titulos/ntnf.py +123 -121
  24. {pyield-0.57.2 → pyield-0.58.1}/pyield/tpf/titulos/pre.py +71 -5
  25. {pyield-0.57.2 → pyield-0.58.1}/pyield/vna/__init__.py +9 -1
  26. {pyield-0.57.2 → pyield-0.58.1}/pyield/vna/_consulta.py +78 -15
  27. {pyield-0.57.2 → pyield-0.58.1}/pyield/vna/_lft.py +83 -3
  28. {pyield-0.57.2 → pyield-0.58.1}/pyield/vna/_ntnb.py +4 -4
  29. {pyield-0.57.2 → pyield-0.58.1}/pyield/vna/_ntnc.py +5 -3
  30. {pyield-0.57.2 → pyield-0.58.1}/pyproject.toml +1 -1
  31. {pyield-0.57.2 → pyield-0.58.1}/pyproject.toml.orig +1 -1
  32. pyield-0.57.2/pyield/.DS_Store +0 -0
  33. pyield-0.57.2/pyield/cpm/probabilidades.py +0 -343
  34. {pyield-0.57.2 → pyield-0.58.1}/LICENSE +0 -0
  35. {pyield-0.57.2 → pyield-0.58.1}/pyield/_internal/__init__.py +0 -0
  36. {pyield-0.57.2 → pyield-0.58.1}/pyield/_internal/br_numbers.py +0 -0
  37. {pyield-0.57.2 → pyield-0.58.1}/pyield/_internal/converters.py +0 -0
  38. {pyield-0.57.2 → pyield-0.58.1}/pyield/_internal/data_cache.py +0 -0
  39. {pyield-0.57.2 → pyield-0.58.1}/pyield/_internal/excel.py +0 -0
  40. {pyield-0.57.2 → pyield-0.58.1}/pyield/_internal/numbers.py +0 -0
  41. {pyield-0.57.2 → pyield-0.58.1}/pyield/_internal/retry.py +0 -0
  42. {pyield-0.57.2 → pyield-0.58.1}/pyield/_internal/types.py +0 -0
  43. {pyield-0.57.2 → pyield-0.58.1}/pyield/anbima/__init__.py +0 -0
  44. {pyield-0.57.2 → pyield-0.58.1}/pyield/anbima/imaq.py +0 -0
  45. {pyield-0.57.2 → pyield-0.58.1}/pyield/anbima/taxas.py +0 -0
  46. {pyield-0.57.2 → pyield-0.58.1}/pyield/b3/__init__.py +0 -0
  47. {pyield-0.57.2 → pyield-0.58.1}/pyield/b3/_contratos.py +0 -0
  48. {pyield-0.57.2 → pyield-0.58.1}/pyield/b3/_validar_pregao.py +0 -0
  49. {pyield-0.57.2 → pyield-0.58.1}/pyield/b3/derivativos_intradia.py +0 -0
  50. {pyield-0.57.2 → pyield-0.58.1}/pyield/b3/di_over.py +0 -0
  51. {pyield-0.57.2 → pyield-0.58.1}/pyield/bc/__init__.py +0 -0
  52. {pyield-0.57.2 → pyield-0.58.1}/pyield/bc/_olinda.py +0 -0
  53. {pyield-0.57.2 → pyield-0.58.1}/pyield/compromissada.py +0 -0
  54. {pyield-0.57.2 → pyield-0.58.1}/pyield/copom/__init__.py +0 -0
  55. {pyield-0.57.2 → pyield-0.58.1}/pyield/copom/_calendario.py +0 -0
  56. {pyield-0.57.2 → pyield-0.58.1}/pyield/du/__init__.py +0 -0
  57. {pyield-0.57.2 → pyield-0.58.1}/pyield/du/core.py +0 -0
  58. {pyield-0.57.2 → pyield-0.58.1}/pyield/du/feriados_br/__init__.py +0 -0
  59. {pyield-0.57.2 → pyield-0.58.1}/pyield/du/feriados_br/anteriores.txt +0 -0
  60. {pyield-0.57.2 → pyield-0.58.1}/pyield/du/feriados_br/atuais.txt +0 -0
  61. {pyield-0.57.2 → pyield-0.58.1}/pyield/futuro/__init__.py +0 -0
  62. {pyield-0.57.2 → pyield-0.58.1}/pyield/futuro/contratos.py +0 -0
  63. {pyield-0.57.2 → pyield-0.58.1}/pyield/futuro/di1.py +0 -0
  64. {pyield-0.57.2 → pyield-0.58.1}/pyield/futuro/historico.py +0 -0
  65. {pyield-0.57.2 → pyield-0.58.1}/pyield/futuro/intradia.py +0 -0
  66. {pyield-0.57.2 → pyield-0.58.1}/pyield/ipca/__init__.py +0 -0
  67. {pyield-0.57.2 → pyield-0.58.1}/pyield/ipca/historico.py +0 -0
  68. {pyield-0.57.2 → pyield-0.58.1}/pyield/ipca/projetado.py +0 -0
  69. {pyield-0.57.2 → pyield-0.58.1}/pyield/py.typed +0 -0
  70. {pyield-0.57.2 → pyield-0.58.1}/pyield/relogio.py +0 -0
  71. {pyield-0.57.2/pyield/tpf → pyield-0.58.1/pyield}/rmd/__init__.py +0 -0
  72. {pyield-0.57.2/pyield/tpf → pyield-0.58.1/pyield}/rmd/_aba_1_3.py +0 -0
  73. {pyield-0.57.2/pyield/tpf → pyield-0.58.1/pyield}/rmd/_aba_2_1.py +0 -0
  74. {pyield-0.57.2/pyield/tpf → pyield-0.58.1/pyield}/rmd/_common.py +0 -0
  75. {pyield-0.57.2/pyield/tpf → pyield-0.58.1/pyield}/rmd/_download.py +0 -0
  76. {pyield-0.57.2 → pyield-0.58.1}/pyield/selic/__init__.py +0 -0
  77. {pyield-0.57.2 → pyield-0.58.1}/pyield/tpf/_taxas.py +0 -0
  78. {pyield-0.57.2 → pyield-0.58.1}/pyield/tpf/benchmark.py +0 -0
  79. {pyield-0.57.2 → pyield-0.58.1}/pyield/tpf/dealers.py +0 -0
  80. {pyield-0.57.2 → pyield-0.58.1}/pyield/tpf/secundario/__init__.py +0 -0
  81. {pyield-0.57.2 → pyield-0.58.1}/pyield/tpf/secundario/_intradia.py +0 -0
  82. {pyield-0.57.2 → pyield-0.58.1}/pyield/tpf/secundario/_mensal.py +0 -0
  83. {pyield-0.57.2 → pyield-0.58.1}/pyield/tpf/titulos/__init__.py +0 -0
  84. {pyield-0.57.2 → pyield-0.58.1}/pyield/vna/_calculo.py +0 -0
  85. {pyield-0.57.2 → pyield-0.58.1}/pyield/vna/_download.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyield
3
- Version: 0.57.2
3
+ Version: 0.58.1
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
@@ -78,13 +78,13 @@ uv add pyield
78
78
  | `yd.di1` | módulo | Curva DI1 e interpolação | `dados`, `interpolar_taxas`, `interpolar_taxa`, `datas_disponiveis` |
79
79
  | `yd.tpf` | módulo | Títulos públicos federais | `taxas`, `taxas_historicas`, `vencimentos`, `estoque`, `leiloes`, `benchmarks`, `curva_pre`, `premios_pre`, `secundario` |
80
80
  | `yd.rmd(aba)` | função | Relatório Mensal da Dívida do Tesouro Nacional | |
81
- | `yd.lft` | módulo | LFT | `dados`, `vencimentos`, `cotacao`, `pu`, `taxa`, `vna`, `rentabilidade`, `rentabilidade_expr` |
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` |
83
83
  | `yd.ntnb` | módulo | NTN-B | `dados`, `vencimentos`, `datas_pagamento`, `fluxos_caixa`, `cotacao`, `pu`, `taxa`, `taxas_zero`, `duration`, `dv01`, `dv01_expr`, `implicitas`, `curva` |
84
84
  | `yd.ntnb1` | módulo | NTN-B1 (Educa+ e Renda+) | `NomeComercial`, `datas_pagamento`, `fluxos_caixa`, `cotacao`, `cotacao_curva_zero`, `taxa_curva_zero`, `pu`, `duration`, `dv01` |
85
85
  | `yd.ntnbp` | módulo | NTN-B Principal | `cotacao`, `taxa`, `pu`, `dv01` |
86
86
  | `yd.ntnc` | módulo | NTN-C | `dados`, `datas_pagamento`, `fluxos_caixa`, `cotacao`, `pu`, `taxa`, `duration`, `duration_expr`, `dv01`, `dv01_expr` |
87
- | `yd.ntnf` | módulo | NTN-F | `dados`, `vencimentos`, `datas_pagamento`, `fluxos_caixa`, `pu`, `taxa`, `taxas_zero`, `premio`, `premio_limpo`, `premio_limpo_expr`, `rentabilidade`, `rentabilidade_expr`, `duration`, `duration_expr`, `dv01`, `dv01_expr` |
87
+ | `yd.ntnf` | módulo | NTN-F | `dados`, `vencimentos`, `datas_pagamento`, `fluxos_caixa`, `pu`, `taxa`, `taxas_zero`, `taxas_zero_forwards`, `premio`, `premio_limpo`, `premio_limpo_expr`, `rentabilidade`, `rentabilidade_expr`, `duration`, `duration_expr`, `dv01`, `dv01_expr` |
88
88
  | `yd.vna` | módulo | Valores nominais atualizados dos títulos públicos | `valor`, `historico`, `projetado`, `vigencia` |
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` |
@@ -102,6 +102,32 @@ documentação detalhada e as assinaturas públicas.
102
102
 
103
103
  ## Compatibilidade da API
104
104
 
105
+ ### Cotações em base 100 — 0.58.0
106
+
107
+ As funções escalares de cálculo dos títulos também aceitam taxas percentuais
108
+ explícitas, como `"5.75%"` ou `"5,75%"`, equivalentes a `0.0575`.
109
+ Números continuam em formato decimal; strings sem `%` são rejeitadas.
110
+
111
+ As funções `cotacao` de `yd.lft`, `yd.ntnb`, `yd.ntnc`, `yd.ntnbp` e
112
+ `yd.ntnb1` passam a retornar base 100 com quatro casas decimais:
113
+ `0.993651` passa a ser `99.3651`. As funções `pu` desses títulos recebem a
114
+ cotação nessa mesma base e aplicam `VNA * cotacao / 100`.
115
+
116
+ A coluna `valor_pagamento` de `fluxos_caixa` de NTN-B, NTN-C e NTN-B1 e o
117
+ retorno de `ntnb1.cotacao_curva_zero` também passam para base 100. Esta última
118
+ mantém a soma sem truncamento para calibrar a TIR equivalente.
119
+
120
+ Para migrar, multiplique cotações e fluxos antigos armazenados por 100, remova
121
+ multiplicações por 100 usadas apenas para exibir cotações e ajuste cálculos
122
+ manuais de PU para dividir por 100. A composição `pu(vna, cotacao(...))`
123
+ preserva a unidade e as regras de precisão do preço. Taxas continuam em formato
124
+ decimal, e VNA e PU continuam em reais.
125
+
126
+ As funções `yd.ltn.dv01` e `yd.ntnf.dv01` não recebem mais um PU de referência:
127
+ calculam a redução do PU teórico para um aumento de 1 ponto-base na taxa.
128
+
129
+ ### Demais alterações
130
+
105
131
  Este resumo parte da API da `0.56.0`. Mudanças que já faziam parte dessa versão
106
132
  ou de versões anteriores estão no histórico das
107
133
  [releases do GitHub](https://github.com/crdcj/PYield/releases).
@@ -116,11 +142,14 @@ ou de versões anteriores estão no histórico das
116
142
  | `pyield.tpf.vna.calcular_vna(...)` | `yd.vna.calcular_vna(...)` |
117
143
  | `yd.ntnb.vna(data)` | `yd.vna.valor("NTN-B", data)` |
118
144
  | `yd.ntnb.vnas()` | `yd.vna.historico("NTN-B")` |
119
- | `vna_projetado(...)` nos módulos de títulos | `yd.vna.projetado(titulo, data, vna_base, inflacao)` |
145
+ | `vna_projetado(...)` nos módulos de títulos | `yd.vna.projetado(titulo, data, vna_base, inflacao)`; para LFT, use `selic=` |
120
146
  | `vigencia(data)` nos módulos de títulos | `yd.vna.vigencia(titulo, data)` |
121
147
 
122
- Os módulos públicos de títulos ficam disponíveis na raiz, enquanto as
123
- implementações continuam organizadas internamente em `pyield/tpf/titulos/`.
148
+ O namespace público segue a autonomia semântica do conceito que o usuário
149
+ precisa conhecer, e não apenas a fonte dos dados ou sua relação temática com
150
+ outro domínio. Por isso, conceitos como `yd.ltn`, `yd.ntnb`, `yd.vna` e `yd.rmd`
151
+ ficam na raiz, enquanto as implementações podem continuar agrupadas
152
+ internamente por domínio para manter o código coeso.
124
153
  Consulte as [releases](https://github.com/crdcj/PYield/releases) para o
125
154
  histórico completo.
126
155
 
@@ -53,13 +53,13 @@ uv add pyield
53
53
  | `yd.di1` | módulo | Curva DI1 e interpolação | `dados`, `interpolar_taxas`, `interpolar_taxa`, `datas_disponiveis` |
54
54
  | `yd.tpf` | módulo | Títulos públicos federais | `taxas`, `taxas_historicas`, `vencimentos`, `estoque`, `leiloes`, `benchmarks`, `curva_pre`, `premios_pre`, `secundario` |
55
55
  | `yd.rmd(aba)` | função | Relatório Mensal da Dívida do Tesouro Nacional | |
56
- | `yd.lft` | módulo | LFT | `dados`, `vencimentos`, `cotacao`, `pu`, `taxa`, `vna`, `rentabilidade`, `rentabilidade_expr` |
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` |
58
58
  | `yd.ntnb` | módulo | NTN-B | `dados`, `vencimentos`, `datas_pagamento`, `fluxos_caixa`, `cotacao`, `pu`, `taxa`, `taxas_zero`, `duration`, `dv01`, `dv01_expr`, `implicitas`, `curva` |
59
59
  | `yd.ntnb1` | módulo | NTN-B1 (Educa+ e Renda+) | `NomeComercial`, `datas_pagamento`, `fluxos_caixa`, `cotacao`, `cotacao_curva_zero`, `taxa_curva_zero`, `pu`, `duration`, `dv01` |
60
60
  | `yd.ntnbp` | módulo | NTN-B Principal | `cotacao`, `taxa`, `pu`, `dv01` |
61
61
  | `yd.ntnc` | módulo | NTN-C | `dados`, `datas_pagamento`, `fluxos_caixa`, `cotacao`, `pu`, `taxa`, `duration`, `duration_expr`, `dv01`, `dv01_expr` |
62
- | `yd.ntnf` | módulo | NTN-F | `dados`, `vencimentos`, `datas_pagamento`, `fluxos_caixa`, `pu`, `taxa`, `taxas_zero`, `premio`, `premio_limpo`, `premio_limpo_expr`, `rentabilidade`, `rentabilidade_expr`, `duration`, `duration_expr`, `dv01`, `dv01_expr` |
62
+ | `yd.ntnf` | módulo | NTN-F | `dados`, `vencimentos`, `datas_pagamento`, `fluxos_caixa`, `pu`, `taxa`, `taxas_zero`, `taxas_zero_forwards`, `premio`, `premio_limpo`, `premio_limpo_expr`, `rentabilidade`, `rentabilidade_expr`, `duration`, `duration_expr`, `dv01`, `dv01_expr` |
63
63
  | `yd.vna` | módulo | Valores nominais atualizados dos títulos públicos | `valor`, `historico`, `projetado`, `vigencia` |
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` |
@@ -77,6 +77,32 @@ documentação detalhada e as assinaturas públicas.
77
77
 
78
78
  ## Compatibilidade da API
79
79
 
80
+ ### Cotações em base 100 — 0.58.0
81
+
82
+ As funções escalares de cálculo dos títulos também aceitam taxas percentuais
83
+ explícitas, como `"5.75%"` ou `"5,75%"`, equivalentes a `0.0575`.
84
+ Números continuam em formato decimal; strings sem `%` são rejeitadas.
85
+
86
+ As funções `cotacao` de `yd.lft`, `yd.ntnb`, `yd.ntnc`, `yd.ntnbp` e
87
+ `yd.ntnb1` passam a retornar base 100 com quatro casas decimais:
88
+ `0.993651` passa a ser `99.3651`. As funções `pu` desses títulos recebem a
89
+ cotação nessa mesma base e aplicam `VNA * cotacao / 100`.
90
+
91
+ A coluna `valor_pagamento` de `fluxos_caixa` de NTN-B, NTN-C e NTN-B1 e o
92
+ retorno de `ntnb1.cotacao_curva_zero` também passam para base 100. Esta última
93
+ mantém a soma sem truncamento para calibrar a TIR equivalente.
94
+
95
+ Para migrar, multiplique cotações e fluxos antigos armazenados por 100, remova
96
+ multiplicações por 100 usadas apenas para exibir cotações e ajuste cálculos
97
+ manuais de PU para dividir por 100. A composição `pu(vna, cotacao(...))`
98
+ preserva a unidade e as regras de precisão do preço. Taxas continuam em formato
99
+ decimal, e VNA e PU continuam em reais.
100
+
101
+ As funções `yd.ltn.dv01` e `yd.ntnf.dv01` não recebem mais um PU de referência:
102
+ calculam a redução do PU teórico para um aumento de 1 ponto-base na taxa.
103
+
104
+ ### Demais alterações
105
+
80
106
  Este resumo parte da API da `0.56.0`. Mudanças que já faziam parte dessa versão
81
107
  ou de versões anteriores estão no histórico das
82
108
  [releases do GitHub](https://github.com/crdcj/PYield/releases).
@@ -91,11 +117,14 @@ ou de versões anteriores estão no histórico das
91
117
  | `pyield.tpf.vna.calcular_vna(...)` | `yd.vna.calcular_vna(...)` |
92
118
  | `yd.ntnb.vna(data)` | `yd.vna.valor("NTN-B", data)` |
93
119
  | `yd.ntnb.vnas()` | `yd.vna.historico("NTN-B")` |
94
- | `vna_projetado(...)` nos módulos de títulos | `yd.vna.projetado(titulo, data, vna_base, inflacao)` |
120
+ | `vna_projetado(...)` nos módulos de títulos | `yd.vna.projetado(titulo, data, vna_base, inflacao)`; para LFT, use `selic=` |
95
121
  | `vigencia(data)` nos módulos de títulos | `yd.vna.vigencia(titulo, data)` |
96
122
 
97
- Os módulos públicos de títulos ficam disponíveis na raiz, enquanto as
98
- implementações continuam organizadas internamente em `pyield/tpf/titulos/`.
123
+ O namespace público segue a autonomia semântica do conceito que o usuário
124
+ precisa conhecer, e não apenas a fonte dos dados ou sua relação temática com
125
+ outro domínio. Por isso, conceitos como `yd.ltn`, `yd.ntnb`, `yd.vna` e `yd.rmd`
126
+ ficam na raiz, enquanto as implementações podem continuar agrupadas
127
+ internamente por domínio para manter o código coeso.
99
128
  Consulte as [releases](https://github.com/crdcj/PYield/releases) para o
100
129
  histórico completo.
101
130
 
@@ -11,13 +11,12 @@ from pyield.futuro import di1
11
11
  # Ordem intencional: alguns módulos importam `di1` a partir de `pyield`
12
12
  # durante a inicialização do pacote.
13
13
  from pyield import cpm, copom, selic, tpf, vna
14
- from pyield.cpm import probabilidades as _cpm_probabilidades # noqa: F401
15
14
  from pyield.bc.sgs import ptax, ptax_serie
16
15
  from pyield.compromissada import compromissadas
17
16
  from pyield.fwd import forward, forwards, forwards_expr
18
17
  from pyield.interpolador import Interpolador, interpolar
19
18
  from pyield.relogio import agora, hoje
20
- from pyield.tpf.rmd import rmd
19
+ from pyield.rmd import rmd
21
20
  from pyield.tpf.titulos import lft, ltn, ntnb, ntnb1, ntnbp, ntnc, ntnf
22
21
 
23
22
  try:
@@ -15,6 +15,11 @@ def ttl_cache(ttl: int = _TTL_PADRAO, maxsize: int = _TAMANHO_MAXIMO):
15
15
  Args:
16
16
  ttl: Tempo de vida de cada entrada em segundos.
17
17
  maxsize: Número máximo de entradas no cache.
18
+
19
+ Notes:
20
+ O wrapper consome _atualizar=True para ignorar e renovar apenas a
21
+ entrada consultada. Erros são propagados, preservando a entrada
22
+ anterior até sua expiração.
18
23
  """
19
24
 
20
25
  def decorador(func):
@@ -22,9 +27,10 @@ def ttl_cache(ttl: int = _TTL_PADRAO, maxsize: int = _TAMANHO_MAXIMO):
22
27
 
23
28
  @wraps(func)
24
29
  def wrapper(*args, **kwargs):
30
+ atualizar = kwargs.pop("_atualizar", False)
25
31
  chave = (args, tuple(sorted(kwargs.items())))
26
32
  agora = time.monotonic()
27
- if chave in _cache:
33
+ if not atualizar and chave in _cache:
28
34
  resultado, expira_em = _cache[chave]
29
35
  if agora < expira_em:
30
36
  return resultado
@@ -436,10 +436,6 @@ def buscar(
436
436
  >>> # Múltiplos contratos de uma vez
437
437
  >>> df = yd.b3.boletim.buscar("26-04-2024", ["DI1", "DAP"])
438
438
 
439
- >>> # Feriado ou fim de semana (retorna DataFrame vazio)
440
- >>> df = yd.b3.boletim.buscar("25-12-2023", "DI1")
441
- >>> df.is_empty()
442
- True
443
439
  """
444
440
  if any_is_empty(data):
445
441
  return pl.DataFrame()
@@ -93,13 +93,13 @@ def _processar_df(df: pl.DataFrame) -> pl.DataFrame:
93
93
  pl.when(pl.col("titulo") == "LTN")
94
94
  .then(
95
95
  ltn.dv01_expr(
96
- "data_liquidacao", "data_vencimento", "taxa_media", "pu_medio"
96
+ "data_liquidacao", "data_vencimento", "taxa_media"
97
97
  )
98
98
  )
99
99
  .when(pl.col("titulo") == "NTN-F")
100
100
  .then(
101
101
  ntnf.dv01_expr(
102
- "data_liquidacao", "data_vencimento", "taxa_media", "pu_medio"
102
+ "data_liquidacao", "data_vencimento", "taxa_media"
103
103
  )
104
104
  )
105
105
  .when(pl.col("titulo") == "NTN-B")
@@ -2,6 +2,7 @@
2
2
 
3
3
  Séries disponíveis:
4
4
  - PTAX Venda (SGS 1)
5
+ - SELIC Diária (SGS 11)
5
6
  - SELIC Meta (SGS 432)
6
7
  - SELIC Over (SGS 1178)
7
8
 
@@ -16,7 +17,7 @@ Exemplo de resposta JSON da API do BCB:
16
17
 
17
18
  Notas de implementação:
18
19
  - Intervalos longos são divididos automaticamente em blocos seguros.
19
- - SELIC Over e Meta: valores percentuais convertidos para decimal
20
+ - SELIC Diária, Over e Meta: valores percentuais convertidos para decimal
20
21
  (divididos por 100) e arredondados para 10 casas decimais.
21
22
  - PTAX Venda: valor absoluto em R$ arredondado para 4 casas.
22
23
  """
@@ -54,6 +55,7 @@ class SerieSGS(Enum):
54
55
  """Enum para as séries disponíveis no SGS do Banco Central."""
55
56
 
56
57
  PTAX_VENDA = 1
58
+ SELIC_DIARIA = 11
57
59
  SELIC_META = 432
58
60
  SELIC_OVER = 1178
59
61
 
@@ -350,3 +350,7 @@ def data(date: DateLike) -> pl.DataFrame:
350
350
  pl.col("preco_ajuste"),
351
351
  pl.col("dias_uteis"),
352
352
  ).sort("data_expiracao", "variacao_strike_bps")
353
+
354
+
355
+ # Importado após as definições porque probabilidades utiliza este módulo.
356
+ from pyield.cpm import probabilidades as probabilidades # noqa: E402, PLC0414
@@ -0,0 +1,347 @@
1
+ """
2
+ probabilidades — Probabilidades implícitas das reuniões do COPOM a partir dos
3
+ preços das opções CPM.
4
+
5
+ O contrato CPM é uma opção europeia cash-or-nothing. No apreçamento neutro ao
6
+ risco, o preço de ajuste da B3 em pontos (0–100) representa a probabilidade
7
+ implícita de cada cenário de alteração da Selic, descontada pela taxa DI até o
8
+ vencimento (Manual de Apreçamento da B3, §3.5).
9
+
10
+ Convenções de probabilidade
11
+ ---------------------------
12
+ prob_bruta = preco_ajuste * fator_desconto / 100
13
+
14
+ Probabilidade neutra ao risco direta segundo o Manual da B3 §3.5
15
+ (fórmula invertida):
16
+
17
+ p_n(K) = PR_n * exp(+n * r_n) / N
18
+
19
+ onde
20
+ PR_n = preco_ajuste ("Preço de Referência" da B3, pontos 0–100)
21
+ N = 100 (valor nocional fixo)
22
+ n = dias_uteis / 252 (tempo em anos, convenção de dias úteis)
23
+ r_n = ln(1 + taxa_di1) (taxa DI1 continuamente capitalizada até o
24
+ vencimento)
25
+ taxa_di1 = taxa DI1 interpolada por flat-forward entre a data de
26
+ referência e a data de expiração
27
+
28
+ O preco_ajuste de cpm.data() é o "Preço de Referência" oficial da B3,
29
+ obtido no endpoint CSV: o preço exibido no painel da B3 ("Probabilidades
30
+ da Taxa Selic Meta") e resultado da metodologia P1/P2/P3/P4 da B3. Pode
31
+ ser nulo para datas com mais de aproximadamente um mês, quando o
32
+ endpoint CSV não está disponível.
33
+
34
+ Pode não somar 1,0 por reunião devido ao spread entre oferta e demanda ou
35
+ ao apreçamento P1/P2 da B3. Quando dias_uteis == 0 (o próprio dia da
36
+ reunião), fator_desconto == 1,0 exatamente e prob_bruta se reduz a
37
+ preco_ajuste / 100.
38
+
39
+ prob = prob_bruta / soma(prob_bruta) no grupo de data_expiracao
40
+ Normalizada para que cada reunião some exatamente 1,0. Este é o ajuste de
41
+ apreçamento P3 da B3. Use esta coluna para análise de cenários e gráficos.
42
+
43
+ prob_acumulada = soma acumulada de prob, ordenada por
44
+ variacao_strike_bps em ordem crescente.
45
+
46
+ Observações sobre preco_ajuste nulo
47
+ -----------------------------------
48
+ Às vezes os contratos CPM são listados sem preço de ajuste (não há apreçamento
49
+ oficial da B3 para aquele strike na data, ou o endpoint CSV não está disponível
50
+ para datas antigas). Strikes com preco_ajuste nulo são excluídos da saída de
51
+ probabilidades porque:
52
+
53
+ 1. Sua contribuição para a distribuição normalizada é indefinida.
54
+ 2. ``group_by().agg(sum())`` do Polars retorna 0,0 (e não nulo) para grupos
55
+ totalmente nulos, o que quebraria a invariável ``prob.sum() == 1.0`` por
56
+ reunião.
57
+
58
+ Como consequência, uma reunião em que TODOS os strikes listados tenham preços
59
+ nulos (por exemplo, CPMH25 no dia da reunião do COPOM de janeiro de 2025) não
60
+ aparecerá na saída. O ranking_reuniao é, portanto, atribuído somente entre as
61
+ reuniões com preços e sempre forma uma sequência consecutiva [1, 2, ..., n].
62
+
63
+ Observações sobre o fallback do DI1
64
+ -----------------------------------
65
+ Se os dados do DI1 não estiverem disponíveis para a data de referência (erro de
66
+ rede, feriado etc.), taxa_di1 assume 0,0 e fator_desconto assume 1,0. Assim,
67
+ prob_bruta se reduz a preco_ajuste / 100, equivalente à fórmula antiga
68
+ (incorreta). Essa degradação é registrada como aviso, mas nunca gera uma
69
+ exceção.
70
+ """
71
+
72
+ import logging
73
+
74
+ import polars as pl
75
+
76
+ from pyield import cpm
77
+ from pyield._internal.converters import converter_datas
78
+ from pyield._internal.types import DateLike
79
+ from pyield.futuro import di1
80
+
81
+ logger = logging.getLogger(__name__)
82
+
83
+
84
+ # ---------------------------------------------------------------------------
85
+ # Schema
86
+ # ---------------------------------------------------------------------------
87
+
88
+
89
+ def _empty_schema() -> pl.DataFrame:
90
+ return pl.DataFrame(
91
+ schema={
92
+ "data_referencia": pl.Date,
93
+ "data_fim_reuniao": pl.Date,
94
+ "data_expiracao": pl.Date,
95
+ "ranking_reuniao": pl.Int32,
96
+ "variacao_strike_bps": pl.Int32,
97
+ "dias_uteis": pl.Int32,
98
+ "preco_ajuste": pl.Float64,
99
+ "taxa_di1": pl.Float64,
100
+ "fator_desconto": pl.Float64,
101
+ "prob_bruta": pl.Float64,
102
+ "prob": pl.Float64,
103
+ "prob_acumulada": pl.Float64,
104
+ }
105
+ )
106
+
107
+
108
+ # ---------------------------------------------------------------------------
109
+ # Internal helpers
110
+ # ---------------------------------------------------------------------------
111
+
112
+
113
+ def _add_meeting_rank(df: pl.DataFrame) -> pl.DataFrame:
114
+ """
115
+ Adiciona ranking_reuniao: 1 = data_expiracao mais próxima, 2 = seguinte, etc.
116
+ Calculado como dense rank sobre data_expiracao.
117
+ """
118
+ return df.with_columns(
119
+ ranking_reuniao=pl.col("data_expiracao").rank("dense").cast(pl.Int32)
120
+ )
121
+
122
+
123
+ def _add_discount_factors(df: pl.DataFrame) -> pl.DataFrame:
124
+ """
125
+ Adiciona as colunas de taxa DI1 e fator de desconto.
126
+
127
+ A função usa ``di1.interpolar_taxas()`` de forma vetorizada para buscar
128
+ todas as taxas DI1 em uma única chamada, com uma busca e um interpolador por
129
+ data_referencia única. Em seguida, calcula os fatores de desconto com
130
+ expressões Polars.
131
+
132
+ Método de interpolação
133
+ ----------------------
134
+ ``di1.interpolar_taxas()`` implementa o Manual da B3 §1.4.2 — Flat Forward
135
+ 252, que interpola de forma log-linear os fatores acumulados do DI1 (valores
136
+ de PU):
137
+
138
+ fa_j = (1 + r_j)^(du_j/252) # fator acumulado no vértice j
139
+ fa_k = (1 + r_k)^(du_k/252) # fator acumulado no vértice k
140
+ ft = (du - du_j) / (du_k - du_j) # fração do intervalo
141
+ r = (fa_j * (fa_k / fa_j)^ft)^(252/du) - 1
142
+
143
+ Isso equivale à interpolação log-linear dos preços de ajuste do DI1
144
+ (PU = 100_000 / (1+r)^(du/252)), daí a expressão "interpolação exponencial
145
+ dos preços de ajuste do DI1" no Manual de Apreçamento do CPM §3.5.
146
+
147
+ Não é o método §1.4.1 (Exponencial 252), que interpola diretamente as taxas:
148
+ r = (1 + r_j) * ((1 + r_k)/(1 + r_j))^ft - 1
149
+ Os dois métodos divergem alguns pontos-base em vencimentos intermediários
150
+ (por exemplo, aproximadamente 4,6 pontos-base em du=17 para taxas na faixa
151
+ da Selic em 2026).
152
+
153
+ Na ausência de dados do DI1, usa taxa_di1=0,0 e fator_desconto=1,0.
154
+ """
155
+ pairs = (
156
+ df.select("data_referencia", "data_expiracao", "dias_uteis")
157
+ .unique(subset=["data_referencia", "data_expiracao"])
158
+ .sort("data_referencia", "data_expiracao")
159
+ )
160
+
161
+ # Chamada vetorizada: um fetch por data_referencia única
162
+ try:
163
+ rates = di1.interpolar_taxas(
164
+ datas_referencia=pairs["data_referencia"],
165
+ datas_vencimento=pairs["data_expiracao"],
166
+ extrapolar=True,
167
+ )
168
+ except Exception:
169
+ logger.warning("Falha na busca DI1; usando fallback taxa=0.0.")
170
+ rates = pl.Series("taxa_interpolada", [None] * len(pairs), dtype=pl.Float64)
171
+
172
+ discount_df = (
173
+ pairs.with_columns(
174
+ taxa_di1=rates.fill_null(0.0).fill_nan(0.0),
175
+ )
176
+ .with_columns(
177
+ fator_desconto=(
178
+ (pl.col("dias_uteis") / 252 * (1 + pl.col("taxa_di1")).log()).exp()
179
+ ),
180
+ )
181
+ .select("data_referencia", "data_expiracao", "taxa_di1", "fator_desconto")
182
+ )
183
+
184
+ return df.join(discount_df, on=["data_referencia", "data_expiracao"], how="left")
185
+
186
+
187
+ def _add_probabilities(df: pl.DataFrame) -> pl.DataFrame:
188
+ """
189
+ Adiciona as colunas prob_bruta, prob e prob_acumulada conforme o Manual da
190
+ B3 §3.5.
191
+
192
+ Assume que o DataFrame já foi filtrado para um tipo de opção e para linhas
193
+ com preco_ajuste não nulo, e que ``_add_discount_factors`` já foi chamada
194
+ para adicionar taxa_di1 e fator_desconto.
195
+
196
+ prob_bruta = preco_ajuste * fator_desconto / 100
197
+ prob = prob_bruta / soma(prob_bruta) no grupo de data_expiracao
198
+ prob_acumulada = soma acumulada de prob, ordenada por
199
+ variacao_strike_bps em ordem crescente.
200
+ """
201
+ df = _add_discount_factors(df)
202
+
203
+ return (
204
+ df.sort("data_expiracao", "variacao_strike_bps")
205
+ .with_columns(
206
+ prob_bruta=(pl.col("preco_ajuste") * pl.col("fator_desconto") / 100),
207
+ )
208
+ .with_columns(
209
+ prob=(
210
+ pl.col("prob_bruta") / pl.col("prob_bruta").sum().over("data_expiracao")
211
+ ),
212
+ )
213
+ .with_columns(prob_acumulada=pl.col("prob").cum_sum().over("data_expiracao"))
214
+ )
215
+
216
+
217
+ # ---------------------------------------------------------------------------
218
+ # API pública
219
+ # ---------------------------------------------------------------------------
220
+
221
+
222
+ def all_meetings(
223
+ date: DateLike,
224
+ option_type: str = "call",
225
+ ) -> pl.DataFrame:
226
+ """
227
+ Calcula as probabilidades implícitas de todas as reuniões do COPOM com
228
+ contratos CPM negociados na data informada em `date`.
229
+
230
+ Somente strikes com `preco_ajuste` não nulo são incluídos. Reuniões em que
231
+ todos os strikes listados têm preço nulo são excluídas integralmente (ver
232
+ as observações no nível do módulo).
233
+
234
+ Args:
235
+ date: Data de referência da consulta; aceita os formatos de `DateLike`.
236
+ option_type: Tipo de opção, `"call"` ou `"put"`. O padrão é `"call"`,
237
+ que na prática é o lado mais líquido.
238
+
239
+ Returns:
240
+ `polars.DataFrame` ordenado por `ranking_reuniao` e
241
+ `variacao_strike_bps`. Em caso de ausência de dados, retorna um
242
+ DataFrame vazio com o schema correto.
243
+
244
+ Output Columns:
245
+ - data_referencia (Date): Data de negociação.
246
+ - data_fim_reuniao (Date): Último dia da reunião do COPOM.
247
+ - data_expiracao (Date): Data de expiração do contrato CPM.
248
+ - ranking_reuniao (Int32): 1 para a reunião mais próxima com preços.
249
+ - variacao_strike_bps (Int32): Variação do strike em pontos-base,
250
+ crescente dentro de cada reunião.
251
+ - dias_uteis (Int32): Dias úteis até a expiração.
252
+ - preco_ajuste (Float64): Preço de Referência da B3 em pontos (0–100).
253
+ - taxa_di1 (Float64): Taxa DI1 interpolada até a expiração.
254
+ - fator_desconto (Float64): Fator aplicado na inversão da fórmula da B3,
255
+ calculado como `exp(dias_uteis / 252 * ln(1 + taxa_di1))`.
256
+ - prob_bruta (Float64): Probabilidade antes da normalização,
257
+ calculada como `preco_ajuste * fator_desconto / 100`.
258
+ - prob (Float64): Probabilidade normalizada, calculada como
259
+ `prob_bruta / soma(prob_bruta)` por `data_expiracao`, com soma
260
+ 1,0 por reunião.
261
+ - prob_acumulada (Float64): Probabilidade acumulada em ordem
262
+ crescente de strike.
263
+
264
+ Examples:
265
+ >>> import pyield as yd
266
+ >>> import polars as pl
267
+ >>> df = yd.cpm.probabilidades.all_meetings("29-01-2025") # doctest: +SKIP
268
+ >>> df.is_empty() or df["ranking_reuniao"].min() == 1 # doctest: +SKIP
269
+ True
270
+ >>> sums = df.group_by("data_expiracao").agg(
271
+ ... pl.col("prob").sum()
272
+ ... ) # doctest: +SKIP
273
+ >>> df.is_empty() or (sums["prob"] - 1.0).abs().max() < 1e-9 # doctest: +SKIP
274
+ True
275
+ """
276
+ raw = cpm.data(date)
277
+ if raw.is_empty():
278
+ return _empty_schema()
279
+
280
+ df = (
281
+ raw.filter(pl.col("tipo_opcao") == option_type)
282
+ # Excluir strikes sem preço de ajuste — ver docstring do módulo.
283
+ .filter(pl.col("preco_ajuste").is_not_null())
284
+ .pipe(_add_meeting_rank)
285
+ .pipe(_add_probabilities)
286
+ .select(
287
+ "data_referencia",
288
+ "data_fim_reuniao",
289
+ "data_expiracao",
290
+ "ranking_reuniao",
291
+ "variacao_strike_bps",
292
+ "dias_uteis",
293
+ "preco_ajuste",
294
+ "taxa_di1",
295
+ "fator_desconto",
296
+ "prob_bruta",
297
+ "prob",
298
+ "prob_acumulada",
299
+ )
300
+ .sort("ranking_reuniao", "variacao_strike_bps")
301
+ )
302
+
303
+ return df if not df.is_empty() else _empty_schema()
304
+
305
+
306
+ def meeting(
307
+ date: DateLike,
308
+ expiration: DateLike | None = None,
309
+ option_type: str = "call",
310
+ ) -> pl.DataFrame:
311
+ """
312
+ Calcula as probabilidades implícitas de uma única reunião do COPOM.
313
+
314
+ Args:
315
+ date: Data de referência da consulta.
316
+ expiration: Data de expiração do contrato CPM da reunião desejada, ou
317
+ `None` para selecionar a reunião mais próxima com preços. A expiração
318
+ corresponde ao primeiro dia útil após o término da reunião.
319
+ option_type: Tipo de opção, `"call"` ou `"put"`. O padrão é `"call"`.
320
+
321
+ Returns:
322
+ `polars.DataFrame` com o mesmo schema de `all_meetings()`, filtrado
323
+ para uma única reunião. `ranking_reuniao` é sempre 1 nesta saída,
324
+ relativo à reunião selecionada, e não ao conjunto de reuniões.
325
+
326
+ Examples:
327
+ >>> import pyield as yd
328
+ >>> df = yd.cpm.probabilidades.meeting("29-01-2025") # doctest: +SKIP
329
+ >>> df.is_empty() or abs(df["prob"].sum() - 1.0) < 1e-9 # doctest: +SKIP
330
+ True
331
+ >>> df.is_empty() or df["prob_acumulada"].tail(
332
+ ... 1
333
+ ... ).item() == 1.0 # doctest: +SKIP
334
+ True
335
+ """
336
+ df = all_meetings(date, option_type=option_type)
337
+ if df.is_empty():
338
+ return _empty_schema()
339
+
340
+ if expiration is None:
341
+ target_expiry = df.filter(pl.col("ranking_reuniao") == 1)["data_expiracao"][0]
342
+ else:
343
+ target_expiry = converter_datas(expiration)
344
+
345
+ return df.filter(pl.col("data_expiracao") == target_expiry).with_columns(
346
+ ranking_reuniao=pl.lit(1, dtype=pl.Int32)
347
+ )