pyield 0.58.0__tar.gz → 0.58.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pyield-0.58.0 → pyield-0.58.2}/PKG-INFO +3 -3
- {pyield-0.58.0 → pyield-0.58.2}/README.md +2 -2
- {pyield-0.58.0 → pyield-0.58.2}/pyield/b3/boletim.py +0 -4
- {pyield-0.58.0 → pyield-0.58.2}/pyield/bc/sgs.py +3 -1
- {pyield-0.58.0 → pyield-0.58.2}/pyield/tpf/titulos/_utils.py +15 -0
- {pyield-0.58.0 → pyield-0.58.2}/pyield/tpf/titulos/lft.py +7 -12
- {pyield-0.58.0 → pyield-0.58.2}/pyield/tpf/titulos/ltn.py +2 -0
- {pyield-0.58.0 → pyield-0.58.2}/pyield/tpf/titulos/ntnb.py +8 -6
- {pyield-0.58.0 → pyield-0.58.2}/pyield/tpf/titulos/ntnb1.py +7 -19
- {pyield-0.58.0 → pyield-0.58.2}/pyield/tpf/titulos/ntnbp.py +52 -7
- {pyield-0.58.0 → pyield-0.58.2}/pyield/tpf/titulos/ntnc.py +162 -19
- {pyield-0.58.0 → pyield-0.58.2}/pyield/tpf/titulos/ntnf.py +4 -4
- {pyield-0.58.0 → pyield-0.58.2}/pyield/vna/__init__.py +9 -1
- {pyield-0.58.0 → pyield-0.58.2}/pyield/vna/_consulta.py +58 -10
- {pyield-0.58.0 → pyield-0.58.2}/pyield/vna/_lft.py +84 -1
- {pyield-0.58.0 → pyield-0.58.2}/pyproject.toml +1 -1
- {pyield-0.58.0 → pyield-0.58.2}/pyproject.toml.orig +1 -1
- {pyield-0.58.0 → pyield-0.58.2}/LICENSE +0 -0
- {pyield-0.58.0 → pyield-0.58.2}/pyield/__init__.py +0 -0
- {pyield-0.58.0 → pyield-0.58.2}/pyield/_internal/__init__.py +0 -0
- {pyield-0.58.0 → pyield-0.58.2}/pyield/_internal/br_numbers.py +0 -0
- {pyield-0.58.0 → pyield-0.58.2}/pyield/_internal/cache.py +0 -0
- {pyield-0.58.0 → pyield-0.58.2}/pyield/_internal/converters.py +0 -0
- {pyield-0.58.0 → pyield-0.58.2}/pyield/_internal/data_cache.py +0 -0
- {pyield-0.58.0 → pyield-0.58.2}/pyield/_internal/excel.py +0 -0
- {pyield-0.58.0 → pyield-0.58.2}/pyield/_internal/numbers.py +0 -0
- {pyield-0.58.0 → pyield-0.58.2}/pyield/_internal/retry.py +0 -0
- {pyield-0.58.0 → pyield-0.58.2}/pyield/_internal/types.py +0 -0
- {pyield-0.58.0 → pyield-0.58.2}/pyield/anbima/__init__.py +0 -0
- {pyield-0.58.0 → pyield-0.58.2}/pyield/anbima/imaq.py +0 -0
- {pyield-0.58.0 → pyield-0.58.2}/pyield/anbima/taxas.py +0 -0
- {pyield-0.58.0 → pyield-0.58.2}/pyield/b3/__init__.py +0 -0
- {pyield-0.58.0 → pyield-0.58.2}/pyield/b3/_contratos.py +0 -0
- {pyield-0.58.0 → pyield-0.58.2}/pyield/b3/_validar_pregao.py +0 -0
- {pyield-0.58.0 → pyield-0.58.2}/pyield/b3/derivativos_intradia.py +0 -0
- {pyield-0.58.0 → pyield-0.58.2}/pyield/b3/di_over.py +0 -0
- {pyield-0.58.0 → pyield-0.58.2}/pyield/bc/__init__.py +0 -0
- {pyield-0.58.0 → pyield-0.58.2}/pyield/bc/_olinda.py +0 -0
- {pyield-0.58.0 → pyield-0.58.2}/pyield/bc/leiloes.py +0 -0
- {pyield-0.58.0 → pyield-0.58.2}/pyield/compromissada.py +0 -0
- {pyield-0.58.0 → pyield-0.58.2}/pyield/copom/__init__.py +0 -0
- {pyield-0.58.0 → pyield-0.58.2}/pyield/copom/_calendario.py +0 -0
- {pyield-0.58.0 → pyield-0.58.2}/pyield/cpm/__init__.py +0 -0
- {pyield-0.58.0 → pyield-0.58.2}/pyield/cpm/probabilidades.py +0 -0
- {pyield-0.58.0 → pyield-0.58.2}/pyield/du/__init__.py +0 -0
- {pyield-0.58.0 → pyield-0.58.2}/pyield/du/core.py +0 -0
- {pyield-0.58.0 → pyield-0.58.2}/pyield/du/feriados_br/__init__.py +0 -0
- {pyield-0.58.0 → pyield-0.58.2}/pyield/du/feriados_br/anteriores.txt +0 -0
- {pyield-0.58.0 → pyield-0.58.2}/pyield/du/feriados_br/atuais.txt +0 -0
- {pyield-0.58.0 → pyield-0.58.2}/pyield/futuro/__init__.py +0 -0
- {pyield-0.58.0 → pyield-0.58.2}/pyield/futuro/contratos.py +0 -0
- {pyield-0.58.0 → pyield-0.58.2}/pyield/futuro/di1.py +0 -0
- {pyield-0.58.0 → pyield-0.58.2}/pyield/futuro/historico.py +0 -0
- {pyield-0.58.0 → pyield-0.58.2}/pyield/futuro/intradia.py +0 -0
- {pyield-0.58.0 → pyield-0.58.2}/pyield/fwd.py +0 -0
- {pyield-0.58.0 → pyield-0.58.2}/pyield/interpolador.py +0 -0
- {pyield-0.58.0 → pyield-0.58.2}/pyield/ipca/__init__.py +0 -0
- {pyield-0.58.0 → pyield-0.58.2}/pyield/ipca/historico.py +0 -0
- {pyield-0.58.0 → pyield-0.58.2}/pyield/ipca/projetado.py +0 -0
- {pyield-0.58.0 → pyield-0.58.2}/pyield/py.typed +0 -0
- {pyield-0.58.0 → pyield-0.58.2}/pyield/relogio.py +0 -0
- {pyield-0.58.0 → pyield-0.58.2}/pyield/rmd/__init__.py +0 -0
- {pyield-0.58.0 → pyield-0.58.2}/pyield/rmd/_aba_1_3.py +0 -0
- {pyield-0.58.0 → pyield-0.58.2}/pyield/rmd/_aba_2_1.py +0 -0
- {pyield-0.58.0 → pyield-0.58.2}/pyield/rmd/_common.py +0 -0
- {pyield-0.58.0 → pyield-0.58.2}/pyield/rmd/_download.py +0 -0
- {pyield-0.58.0 → pyield-0.58.2}/pyield/selic/__init__.py +0 -0
- {pyield-0.58.0 → pyield-0.58.2}/pyield/tpf/__init__.py +0 -0
- {pyield-0.58.0 → pyield-0.58.2}/pyield/tpf/_taxas.py +0 -0
- {pyield-0.58.0 → pyield-0.58.2}/pyield/tpf/benchmark.py +0 -0
- {pyield-0.58.0 → pyield-0.58.2}/pyield/tpf/dealers.py +0 -0
- {pyield-0.58.0 → pyield-0.58.2}/pyield/tpf/leiloes.py +0 -0
- {pyield-0.58.0 → pyield-0.58.2}/pyield/tpf/secundario/__init__.py +0 -0
- {pyield-0.58.0 → pyield-0.58.2}/pyield/tpf/secundario/_intradia.py +0 -0
- {pyield-0.58.0 → pyield-0.58.2}/pyield/tpf/secundario/_mensal.py +0 -0
- {pyield-0.58.0 → pyield-0.58.2}/pyield/tpf/titulos/__init__.py +0 -0
- {pyield-0.58.0 → pyield-0.58.2}/pyield/tpf/titulos/_ntnb_bootstrap.py +0 -0
- {pyield-0.58.0 → pyield-0.58.2}/pyield/tpf/titulos/_ntnf_bootstrap.py +0 -0
- {pyield-0.58.0 → pyield-0.58.2}/pyield/tpf/titulos/pre.py +0 -0
- {pyield-0.58.0 → pyield-0.58.2}/pyield/vna/_calculo.py +0 -0
- {pyield-0.58.0 → pyield-0.58.2}/pyield/vna/_download.py +0 -0
- {pyield-0.58.0 → pyield-0.58.2}/pyield/vna/_ntnb.py +0 -0
- {pyield-0.58.0 → pyield-0.58.2}/pyield/vna/_ntnc.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pyield
|
|
3
|
-
Version: 0.58.
|
|
3
|
+
Version: 0.58.2
|
|
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,7 +78,7 @@ 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`, `
|
|
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` |
|
|
@@ -142,7 +142,7 @@ ou de versões anteriores estão no histórico das
|
|
|
142
142
|
| `pyield.tpf.vna.calcular_vna(...)` | `yd.vna.calcular_vna(...)` |
|
|
143
143
|
| `yd.ntnb.vna(data)` | `yd.vna.valor("NTN-B", data)` |
|
|
144
144
|
| `yd.ntnb.vnas()` | `yd.vna.historico("NTN-B")` |
|
|
145
|
-
| `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=` |
|
|
146
146
|
| `vigencia(data)` nos módulos de títulos | `yd.vna.vigencia(titulo, data)` |
|
|
147
147
|
|
|
148
148
|
O namespace público segue a autonomia semântica do conceito que o usuário
|
|
@@ -53,7 +53,7 @@ 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`, `
|
|
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` |
|
|
@@ -117,7 +117,7 @@ ou de versões anteriores estão no histórico das
|
|
|
117
117
|
| `pyield.tpf.vna.calcular_vna(...)` | `yd.vna.calcular_vna(...)` |
|
|
118
118
|
| `yd.ntnb.vna(data)` | `yd.vna.valor("NTN-B", data)` |
|
|
119
119
|
| `yd.ntnb.vnas()` | `yd.vna.historico("NTN-B")` |
|
|
120
|
-
| `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=` |
|
|
121
121
|
| `vigencia(data)` nos módulos de títulos | `yd.vna.vigencia(titulo, data)` |
|
|
122
122
|
|
|
123
123
|
O namespace público segue a autonomia semântica do conceito que o usuário
|
|
@@ -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()
|
|
@@ -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
|
-
|
|
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
|
|
|
@@ -302,3 +302,18 @@ def encontrar_raiz(
|
|
|
302
302
|
if not math.isfinite(a) or not math.isfinite(b) or a > b:
|
|
303
303
|
raise ValueError("Os limites do intervalo devem ser finitos e ordenados.")
|
|
304
304
|
return _metodo_bissecao(func_diferenca_preco, a, b)
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
def cotacao_por_taxas(pagamentos: pl.DataFrame) -> float:
|
|
308
|
+
"""
|
|
309
|
+
Soma os valores presentes dos fluxos, arredondados na 10ª casa decimal.
|
|
310
|
+
|
|
311
|
+
Args:
|
|
312
|
+
pagamentos: DataFrame com uma linha por fluxo e as colunas
|
|
313
|
+
``valor_pagamento`` (Float64), ``dias_uteis`` (Int64) e
|
|
314
|
+
``taxa`` (Float64) alinhadas por linha.
|
|
315
|
+
"""
|
|
316
|
+
anos_uteis = truncar(pagamentos["dias_uteis"] / 252, 14)
|
|
317
|
+
fatores = (1 + pagamentos["taxa"]) ** anos_uteis
|
|
318
|
+
valores_presentes = pagamentos["valor_pagamento"] / fatores
|
|
319
|
+
return float(valores_presentes.round(10).sum())
|
|
@@ -129,11 +129,13 @@ def cotacao(
|
|
|
129
129
|
data_vencimento: Data de vencimento do título.
|
|
130
130
|
taxa: Taxa anualizada do título em formato decimal.
|
|
131
131
|
Aceita também percentual explícito: "5.75%" ou "5,75%".
|
|
132
|
+
Antes do cálculo, é truncada em oito casas decimais (seis na
|
|
133
|
+
forma percentual), descartando as casas excedentes sem arredondar.
|
|
132
134
|
|
|
133
135
|
Returns:
|
|
134
136
|
Decimal: Cotação em base 100, truncada em 4 casas decimais.
|
|
135
|
-
Retorna ``Decimal("NaN")``
|
|
136
|
-
positivo.
|
|
137
|
+
Retorna ``Decimal("NaN")`` para entradas nulas ou quando o prazo
|
|
138
|
+
até o vencimento não é positivo.
|
|
137
139
|
|
|
138
140
|
Notes:
|
|
139
141
|
A cotação é calculada e retornada na escala percentual (base 100),
|
|
@@ -149,14 +151,7 @@ def cotacao(
|
|
|
149
151
|
... taxa="0.1717%",
|
|
150
152
|
... )
|
|
151
153
|
Decimal('98.9645')
|
|
152
|
-
>>> lft.cotacao("21-05-2008", "07-03-2014", "-0.0200009%")
|
|
153
|
-
Decimal('100.1158')
|
|
154
154
|
|
|
155
|
-
Entradas nulas retornam Decimal('NaN'):
|
|
156
|
-
>>> lft.cotacao(
|
|
157
|
-
... data_liquidacao=None, data_vencimento="01-09-2030", taxa="0.1717%"
|
|
158
|
-
... )
|
|
159
|
-
Decimal('NaN')
|
|
160
155
|
"""
|
|
161
156
|
taxa = _utils.converter_taxa(taxa)
|
|
162
157
|
if any_is_empty(data_liquidacao, data_vencimento, taxa):
|
|
@@ -306,8 +301,10 @@ def pu(
|
|
|
306
301
|
Calcula o PU da LFT pela metodologia da STN para leilões primários.
|
|
307
302
|
|
|
308
303
|
Args:
|
|
309
|
-
vna: Valor nominal atualizado (VNA)
|
|
304
|
+
vna: Valor nominal atualizado (VNA), truncado em seis casas decimais
|
|
305
|
+
antes do cálculo, sem arredondar.
|
|
310
306
|
cotacao: Cotação da LFT em base 100.
|
|
307
|
+
Truncada em quatro casas decimais antes do cálculo, sem arredondar.
|
|
311
308
|
|
|
312
309
|
Returns:
|
|
313
310
|
Decimal: Preço da LFT truncado em 6 casas decimais.
|
|
@@ -321,7 +318,5 @@ def pu(
|
|
|
321
318
|
>>> from pyield import lft
|
|
322
319
|
>>> lft.pu(15785.324502, 99.9291)
|
|
323
320
|
Decimal('15774.132706')
|
|
324
|
-
>>> lft.pu(3451.2153459, 100.11589)
|
|
325
|
-
Decimal('3455.211852')
|
|
326
321
|
"""
|
|
327
322
|
return _calcular_pu(vna, cotacao)
|
|
@@ -138,6 +138,8 @@ def pu(
|
|
|
138
138
|
data_vencimento: Data de vencimento.
|
|
139
139
|
taxa: Taxa de desconto (YTM) do título em formato decimal.
|
|
140
140
|
Aceita também percentual explícito: "5.75%" ou "5,75%".
|
|
141
|
+
Antes do cálculo, é truncada em oito casas decimais (seis na
|
|
142
|
+
forma percentual), descartando as casas excedentes sem arredondar.
|
|
141
143
|
|
|
142
144
|
Returns:
|
|
143
145
|
Decimal: PU da LTN truncado em seis casas decimais. Retorna
|
|
@@ -9,10 +9,10 @@ Convenções de precificação (STN, tabela 3):
|
|
|
9
9
|
equivalente a 6 casas em termos percentuais.
|
|
10
10
|
- Cada fluxo descontado: arredondado a 10 casas.
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
Valores de referência derivados conforme as regras da STN, exibidos em base 100:
|
|
13
13
|
TAXA_CUPOM = ((0.06 + 1) ** 0.5 - 1) * 100 # 6% a.a. com capitalização semestral
|
|
14
14
|
VALOR_CUPOM = round(TAXA_CUPOM, 6) -> 2.956301
|
|
15
|
-
VALOR_FINAL =
|
|
15
|
+
VALOR_FINAL = 102.956301
|
|
16
16
|
DIA_CUPOM = 15
|
|
17
17
|
MESES_CUPOM = {2, 5, 8, 11}
|
|
18
18
|
"""
|
|
@@ -35,7 +35,7 @@ from . import _utils
|
|
|
35
35
|
|
|
36
36
|
BASE_COTACAO = 100
|
|
37
37
|
VALOR_CUPOM = 2.956301
|
|
38
|
-
VALOR_FINAL =
|
|
38
|
+
VALOR_FINAL = 102.956301
|
|
39
39
|
|
|
40
40
|
logger = logging.getLogger(__name__)
|
|
41
41
|
|
|
@@ -291,6 +291,8 @@ def cotacao(
|
|
|
291
291
|
data_vencimento (DateLike): Data de vencimento da NTN-B.
|
|
292
292
|
taxa: Taxa de desconto (TIR) em formato decimal.
|
|
293
293
|
Aceita também percentual explícito: "5.75%" ou "5,75%".
|
|
294
|
+
Antes do cálculo, é truncada em oito casas decimais (seis na
|
|
295
|
+
forma percentual), descartando as casas excedentes sem arredondar.
|
|
294
296
|
|
|
295
297
|
Returns:
|
|
296
298
|
Decimal: Cotação em base 100, truncada em 4 casas. Retorna
|
|
@@ -364,8 +366,10 @@ def pu(
|
|
|
364
366
|
Calcula o PU da NTN-B pela metodologia da STN para leilões primários.
|
|
365
367
|
|
|
366
368
|
Args:
|
|
367
|
-
vna: Valor nominal atualizado (VNA)
|
|
369
|
+
vna: Valor nominal atualizado (VNA), truncado em seis casas decimais
|
|
370
|
+
antes do cálculo, sem arredondar.
|
|
368
371
|
cotacao: Cotação da NTN-B em base 100.
|
|
372
|
+
Truncada em quatro casas decimais antes do cálculo, sem arredondar.
|
|
369
373
|
|
|
370
374
|
Returns:
|
|
371
375
|
Decimal: Preço da NTN-B truncado em 6 casas decimais.
|
|
@@ -381,8 +385,6 @@ def pu(
|
|
|
381
385
|
Decimal('4271.864805')
|
|
382
386
|
>>> ntnb.pu(4315.498383, 100.6409)
|
|
383
387
|
Decimal('4343.156412')
|
|
384
|
-
>>> ntnb.pu(1728.4611369, 97.08139)
|
|
385
|
-
Decimal('1678.012540')
|
|
386
388
|
"""
|
|
387
389
|
return _calcular_pu(vna, cotacao)
|
|
388
390
|
|
|
@@ -227,6 +227,7 @@ def cotacao(
|
|
|
227
227
|
data_vencimento: Data de vencimento da NTN-B1.
|
|
228
228
|
taxa: Taxa de desconto (YTM) usada no valor presente.
|
|
229
229
|
Aceita também percentual explícito: "5.75%" ou "5,75%".
|
|
230
|
+
A taxa de entrada não é truncada antes do cálculo.
|
|
230
231
|
nome_comercial: Nome comercial (Renda+ ou Educa+).
|
|
231
232
|
|
|
232
233
|
Returns:
|
|
@@ -285,21 +286,6 @@ def _validar_curva_zero(curva_zero: pl.DataFrame) -> pl.DataFrame:
|
|
|
285
286
|
)
|
|
286
287
|
|
|
287
288
|
|
|
288
|
-
def _cotacao_por_taxas(pagamentos: pl.DataFrame) -> float:
|
|
289
|
-
"""
|
|
290
|
-
Soma os valores presentes dos fluxos, arredondados na 10ª casa decimal.
|
|
291
|
-
|
|
292
|
-
Args:
|
|
293
|
-
pagamentos: DataFrame com uma linha por fluxo e as colunas
|
|
294
|
-
``valor_pagamento`` (Float64), ``dias_uteis`` (Int64) e
|
|
295
|
-
``taxa`` (Float64) alinhadas por linha.
|
|
296
|
-
"""
|
|
297
|
-
anos_uteis = _utils.truncar(pagamentos["dias_uteis"] / 252, 14)
|
|
298
|
-
fatores = (1 + pagamentos["taxa"]) ** anos_uteis
|
|
299
|
-
valores_presentes = pagamentos["valor_pagamento"] / fatores
|
|
300
|
-
return float(valores_presentes.round(10).sum())
|
|
301
|
-
|
|
302
|
-
|
|
303
289
|
def cotacao_curva_zero(
|
|
304
290
|
data_liquidacao: DateLike,
|
|
305
291
|
data_vencimento: DateLike,
|
|
@@ -347,7 +333,7 @@ def cotacao_curva_zero(
|
|
|
347
333
|
extrapolar=True,
|
|
348
334
|
)
|
|
349
335
|
pagamentos = fluxos.with_columns(dias_uteis=dias_fluxos, taxa=taxas_fluxos)
|
|
350
|
-
return
|
|
336
|
+
return _utils.cotacao_por_taxas(pagamentos)
|
|
351
337
|
|
|
352
338
|
|
|
353
339
|
def _resolver_taxa_equivalente(
|
|
@@ -368,7 +354,7 @@ def _resolver_taxa_equivalente(
|
|
|
368
354
|
|
|
369
355
|
def erro(taxa: float) -> float:
|
|
370
356
|
pagamentos = pagamentos_base.with_columns(taxa=pl.lit(taxa, dtype=pl.Float64))
|
|
371
|
-
return
|
|
357
|
+
return _utils.cotacao_por_taxas(pagamentos) - cotacao_alvo
|
|
372
358
|
|
|
373
359
|
limite_inferior = -0.99
|
|
374
360
|
limite_superior = max(1.0, 2 * taxa_inicial + 0.01)
|
|
@@ -435,7 +421,7 @@ def taxa_curva_zero(
|
|
|
435
421
|
extrapolar=True,
|
|
436
422
|
)
|
|
437
423
|
pagamentos_base = fluxos.with_columns(dias_uteis=dias_fluxos)
|
|
438
|
-
cotacao_alvo =
|
|
424
|
+
cotacao_alvo = _utils.cotacao_por_taxas(pagamentos_base.with_columns(taxa=taxas_zero))
|
|
439
425
|
return _resolver_taxa_equivalente(
|
|
440
426
|
cotacao_alvo,
|
|
441
427
|
pagamentos_base,
|
|
@@ -451,8 +437,10 @@ def pu(
|
|
|
451
437
|
Calcula o preço (PU) da NTN-B1 pelas regras do Tesouro Nacional.
|
|
452
438
|
|
|
453
439
|
Args:
|
|
454
|
-
vna: Valor nominal atualizado (VNA)
|
|
440
|
+
vna: Valor nominal atualizado (VNA), truncado em seis casas decimais
|
|
441
|
+
antes do cálculo, sem arredondar.
|
|
455
442
|
cotacao: Cotação da NTN-B1 em base 100.
|
|
443
|
+
Truncada em quatro casas decimais antes do cálculo, sem arredondar.
|
|
456
444
|
|
|
457
445
|
Returns:
|
|
458
446
|
Decimal: Preço da NTN-B1 truncado em 6 casas decimais.
|
|
@@ -1,4 +1,22 @@
|
|
|
1
|
-
"""Precificação de NTN-B Principal.
|
|
1
|
+
"""Precificação de NTN-B Principal (Tesouro IPCA+ sem cupons).
|
|
2
|
+
|
|
3
|
+
Convenções de cálculo:
|
|
4
|
+
- Fluxo único no vencimento, atualizado pelo IPCA via VNA.
|
|
5
|
+
- Prazo de desconto: dias úteis / 252, truncado a 14 casas.
|
|
6
|
+
- Cotação: base 100, truncada a 4 casas.
|
|
7
|
+
- VNA de entrada e PU retornado: truncados a 6 casas.
|
|
8
|
+
- Taxa da curva: float interpolado por flat-forward, sem arredondamento.
|
|
9
|
+
|
|
10
|
+
O exemplo de precificação do Tesouro Direto (páginas 2 a 4) usa cotação com
|
|
11
|
+
4 casas, embora o texto da página 4 mencione 2. O PU da biblioteca mantém
|
|
12
|
+
6 casas; o preço em centavos do documento exige truncamento adicional.
|
|
13
|
+
O documento parte de uma taxa pactuada e não especifica a regra de
|
|
14
|
+
arredondamento da taxa obtida por curva zero.
|
|
15
|
+
|
|
16
|
+
References:
|
|
17
|
+
- Tesouro Direto — Cálculo da Rentabilidade: Tesouro IPCA+.
|
|
18
|
+
<https://www.tesourodireto.com.br/documents/d/guest/tesouro_ipca_juros_semestrais>
|
|
19
|
+
"""
|
|
2
20
|
|
|
3
21
|
from decimal import Decimal
|
|
4
22
|
|
|
@@ -24,6 +42,7 @@ def cotacao(
|
|
|
24
42
|
data_vencimento: Data de vencimento.
|
|
25
43
|
taxa_tir: Taxa interna de retorno anualizada do título.
|
|
26
44
|
Aceita também percentual explícito: "5.75%" ou "5,75%".
|
|
45
|
+
A taxa de entrada não é truncada antes do cálculo.
|
|
27
46
|
|
|
28
47
|
Returns:
|
|
29
48
|
Decimal: Cotação em base 100, truncada em 4 casas decimais. Retorna
|
|
@@ -33,6 +52,16 @@ def cotacao(
|
|
|
33
52
|
>>> from pyield import ntnbp
|
|
34
53
|
>>> ntnbp.cotacao("02-12-2025", "15-05-2029", "7.77%")
|
|
35
54
|
Decimal('77.4630')
|
|
55
|
+
|
|
56
|
+
Exemplo do Tesouro Direto, páginas 2 a 4: liquidação em 03/01/2012,
|
|
57
|
+
vencimento em 15/05/2015, prazo de 846 dias úteis e taxa de 5,17% a.a.
|
|
58
|
+
|
|
59
|
+
>>> ntnbp.cotacao("03-01-2012", "15-05-2015", "5.17%")
|
|
60
|
+
Decimal('84.4317')
|
|
61
|
+
|
|
62
|
+
References:
|
|
63
|
+
- Tesouro Direto — Tesouro IPCA+, páginas 2 a 4.
|
|
64
|
+
<https://www.tesourodireto.com.br/documents/d/guest/tesouro_ipca_juros_semestrais>
|
|
36
65
|
"""
|
|
37
66
|
if isinstance(taxa_tir, str):
|
|
38
67
|
taxa_tir = float(_utils.converter_taxa(taxa_tir))
|
|
@@ -52,8 +81,10 @@ def pu(vna: float | Decimal, cotacao: float | Decimal) -> Decimal:
|
|
|
52
81
|
Calcula o preço (PU) da NTN-B Principal.
|
|
53
82
|
|
|
54
83
|
Args:
|
|
55
|
-
vna: Valor nominal atualizado (VNA)
|
|
84
|
+
vna: Valor nominal atualizado (VNA), truncado em seis casas decimais
|
|
85
|
+
antes do cálculo, sem arredondar.
|
|
56
86
|
cotacao: Cotação da NTN-B Principal em base 100.
|
|
87
|
+
Truncada em quatro casas decimais antes do cálculo, sem arredondar.
|
|
57
88
|
|
|
58
89
|
Returns:
|
|
59
90
|
Decimal: Preço da NTN-B Principal truncado em 6 casas decimais.
|
|
@@ -63,6 +94,18 @@ def pu(vna: float | Decimal, cotacao: float | Decimal) -> Decimal:
|
|
|
63
94
|
>>> cot = ntnbp.cotacao("02-12-2025", "15-05-2029", "7.77%")
|
|
64
95
|
>>> ntnbp.pu(4567.033825, cot)
|
|
65
96
|
Decimal('3537.761411')
|
|
97
|
+
|
|
98
|
+
Exemplo do Tesouro Direto, página 4, com o VNA projetado publicado.
|
|
99
|
+
O retorno preserva seis casas; truncado em centavos, corresponde
|
|
100
|
+
ao preço de R$ 1.776,77 apresentado no documento.
|
|
101
|
+
|
|
102
|
+
>>> cot = ntnbp.cotacao("03-01-2012", "15-05-2015", "5.17%")
|
|
103
|
+
>>> ntnbp.pu(Decimal("2104.390122"), cot)
|
|
104
|
+
Decimal('1776.772354')
|
|
105
|
+
|
|
106
|
+
References:
|
|
107
|
+
- Tesouro Direto — Tesouro IPCA+, página 4.
|
|
108
|
+
<https://www.tesourodireto.com.br/documents/d/guest/tesouro_ipca_juros_semestrais>
|
|
66
109
|
"""
|
|
67
110
|
if any_is_empty(vna, cotacao):
|
|
68
111
|
return Decimal("NaN")
|
|
@@ -97,10 +140,13 @@ def taxa(
|
|
|
97
140
|
"""
|
|
98
141
|
Obtém a TIR da NTN-B Principal a partir da curva zero informada.
|
|
99
142
|
|
|
100
|
-
A taxa zero correspondente ao vencimento é interpolada por flat-forward
|
|
101
|
-
|
|
143
|
+
A taxa zero correspondente ao vencimento é interpolada por flat-forward,
|
|
144
|
+
sem arredondamento. Como a NTN-B Principal possui um único
|
|
102
145
|
fluxo no vencimento, essa taxa zero também é a TIR do título.
|
|
103
146
|
|
|
147
|
+
Arredondamentos comerciais e spreads de compra ou venda ficam a cargo
|
|
148
|
+
do consumidor.
|
|
149
|
+
|
|
104
150
|
A curva pode ser produzida por :func:`pyield.ntnb.taxas_zero`. Para
|
|
105
151
|
cálculos em lote, ela deve ser construída uma única vez e reutilizada entre
|
|
106
152
|
os títulos.
|
|
@@ -111,7 +157,7 @@ def taxa(
|
|
|
111
157
|
curva_zero: DataFrame com as colunas ``dias_uteis`` e ``taxa_zero``.
|
|
112
158
|
|
|
113
159
|
Returns:
|
|
114
|
-
float: TIR anualizada
|
|
160
|
+
float: TIR anualizada em formato decimal, sem arredondamento.
|
|
115
161
|
Retorna ``NaN`` se a liquidação for igual ou posterior ao vencimento.
|
|
116
162
|
"""
|
|
117
163
|
if any_is_empty(data_liquidacao, data_vencimento):
|
|
@@ -122,12 +168,11 @@ def taxa(
|
|
|
122
168
|
return float("nan")
|
|
123
169
|
|
|
124
170
|
curva = _normalizar_curva_zero(curva_zero)
|
|
125
|
-
|
|
171
|
+
return interpolador.Interpolador(
|
|
126
172
|
curva["dias_uteis"],
|
|
127
173
|
curva["taxa_zero"],
|
|
128
174
|
metodo="flat_forward",
|
|
129
175
|
).interpolar(dias_uteis)
|
|
130
|
-
return round(taxa_zero, 4)
|
|
131
176
|
|
|
132
177
|
|
|
133
178
|
def dv01(
|
|
@@ -1,28 +1,40 @@
|
|
|
1
|
+
"""Cálculos e dados de NTN-C.
|
|
2
|
+
|
|
3
|
+
Convenções de precificação (STN, tabela 3):
|
|
4
|
+
- Cotação em base 100, truncada a 4 casas.
|
|
5
|
+
- VNA recebido no cálculo do PU: truncado a 6 casas.
|
|
6
|
+
- Prazo de desconto: dias úteis / 252, truncado a 14 casas.
|
|
7
|
+
- PU: truncado a 6 casas.
|
|
8
|
+
- Taxa implícita retornada: decimal, truncada a 8 casas,
|
|
9
|
+
equivalente a 6 casas em termos percentuais.
|
|
10
|
+
- Cada fluxo descontado: arredondado a 10 casas.
|
|
11
|
+
|
|
12
|
+
Valores de referência derivados conforme as regras da STN, exibidos em base 100.
|
|
13
|
+
Para NTN-C com vencimento em 01-01-2031:
|
|
14
|
+
PRINCIPAL = 100
|
|
15
|
+
TAXA_CUPOM = ((0.12 + 1) ** 0.5 - 1) * 100 # 12% a.a. com capitalização semestral
|
|
16
|
+
VALOR_CUPOM_2031 = round(TAXA_CUPOM, 6) -> 5.830052
|
|
17
|
+
VALOR_FINAL_2031 = 105.830052
|
|
18
|
+
|
|
19
|
+
Para as demais NTN-C:
|
|
20
|
+
TAXA_CUPOM = ((0.06 + 1) ** 0.5 - 1) * 100 # 6% a.a. com capitalização semestral
|
|
21
|
+
VALOR_CUPOM = round(TAXA_CUPOM, 6) -> 2.956301
|
|
22
|
+
VALOR_FINAL = 102.956301
|
|
23
|
+
"""
|
|
24
|
+
|
|
1
25
|
import datetime as dt
|
|
26
|
+
import math
|
|
2
27
|
from decimal import Decimal
|
|
3
28
|
|
|
4
29
|
import polars as pl
|
|
5
30
|
|
|
6
31
|
import pyield._internal.converters as conversores
|
|
7
|
-
from pyield import du
|
|
32
|
+
from pyield import du, interpolador
|
|
8
33
|
from pyield._internal.numbers import truncar_decimal
|
|
9
34
|
from pyield._internal.types import DateLike, any_is_empty
|
|
10
35
|
|
|
11
36
|
from . import _utils
|
|
12
37
|
|
|
13
|
-
"""
|
|
14
|
-
Constantes calculadas conforme regras da STN, exibidas em base 100.
|
|
15
|
-
Válido para NTN-C com vencimento 01-01-2031:
|
|
16
|
-
PRINCIPAL = 100
|
|
17
|
-
TAXA_CUPOM = ((0.12 + 1) ** 0.5 - 1) * 100 # 12% a.a. com capitalização semestral
|
|
18
|
-
VALOR_CUPOM_2031 = round(TAXA_CUPOM, 6) -> 5.830052
|
|
19
|
-
VALOR_FINAL_2031 = principal + último cupom = 100 + 5.830052
|
|
20
|
-
|
|
21
|
-
Para as demais NTN-C:
|
|
22
|
-
TAXA_CUPOM = ((0.06 + 1) ** 0.5 - 1) * 100 # 6% a.a. com capitalização semestral
|
|
23
|
-
VALOR_CUPOM = round(TAXA_CUPOM, 6) -> 2.956301
|
|
24
|
-
VALOR_FINAL = principal + último cupom = 100 + 2.956301
|
|
25
|
-
"""
|
|
26
38
|
# Valores usados nos cálculos, em base 100, com 6 casas decimais
|
|
27
39
|
VALOR_CUPOM_2031 = 5.830052
|
|
28
40
|
VALOR_FINAL_2031 = 105.830052
|
|
@@ -242,6 +254,8 @@ def cotacao(
|
|
|
242
254
|
data_vencimento: Data de vencimento da NTN-C.
|
|
243
255
|
taxa: Taxa de desconto (YTM) em formato decimal.
|
|
244
256
|
Aceita também percentual explícito: "5.75%" ou "5,75%".
|
|
257
|
+
Antes do cálculo, é truncada em oito casas decimais (seis na
|
|
258
|
+
forma percentual), descartando as casas excedentes sem arredondar.
|
|
245
259
|
|
|
246
260
|
Returns:
|
|
247
261
|
Decimal: Cotação em base 100, truncada em 4 casas decimais.
|
|
@@ -263,8 +277,6 @@ def cotacao(
|
|
|
263
277
|
>>> from pyield import ntnc
|
|
264
278
|
>>> ntnc.cotacao("21-03-2025", "01-01-2031", "6.7626%")
|
|
265
279
|
Decimal('126.4958')
|
|
266
|
-
>>> ntnc.cotacao("21-05-2008", "01-03-2011", "6.9000009%")
|
|
267
|
-
Decimal('99.0981')
|
|
268
280
|
"""
|
|
269
281
|
taxa = _utils.converter_taxa(taxa)
|
|
270
282
|
if any_is_empty(data_liquidacao, data_vencimento, taxa):
|
|
@@ -287,6 +299,137 @@ def cotacao(
|
|
|
287
299
|
return truncar_decimal(cotacao_total, 4)
|
|
288
300
|
|
|
289
301
|
|
|
302
|
+
def _pagamentos_curva_zero(
|
|
303
|
+
data_liquidacao: DateLike,
|
|
304
|
+
data_vencimento: DateLike,
|
|
305
|
+
curva_zero: pl.DataFrame,
|
|
306
|
+
) -> pl.DataFrame:
|
|
307
|
+
"""Prepara fluxos e taxas da curva fornecida, sem escolher seu indexador."""
|
|
308
|
+
if not {"dias_uteis", "taxa_zero"}.issubset(curva_zero.columns):
|
|
309
|
+
raise ValueError(
|
|
310
|
+
"Curva zero deve conter as colunas 'dias_uteis' e 'taxa_zero'."
|
|
311
|
+
)
|
|
312
|
+
curva = curva_zero.select(
|
|
313
|
+
pl.col("dias_uteis").cast(pl.Float64),
|
|
314
|
+
pl.col("taxa_zero").cast(pl.Float64),
|
|
315
|
+
)
|
|
316
|
+
if (
|
|
317
|
+
curva.is_empty()
|
|
318
|
+
or curva.select(
|
|
319
|
+
(
|
|
320
|
+
pl.col("dias_uteis").is_null()
|
|
321
|
+
| ~pl.col("dias_uteis").is_finite()
|
|
322
|
+
| (pl.col("dias_uteis") <= 0)
|
|
323
|
+
| (pl.col("dias_uteis") != pl.col("dias_uteis").floor())
|
|
324
|
+
| pl.col("taxa_zero").is_null()
|
|
325
|
+
| ~pl.col("taxa_zero").is_finite()
|
|
326
|
+
| (pl.col("taxa_zero") <= -1)
|
|
327
|
+
).any()
|
|
328
|
+
).item()
|
|
329
|
+
):
|
|
330
|
+
raise ValueError(
|
|
331
|
+
"Curva deve ter prazos inteiros positivos e taxas finitas > -1."
|
|
332
|
+
)
|
|
333
|
+
if curva["dias_uteis"].n_unique() != curva.height:
|
|
334
|
+
raise ValueError("Curva zero não pode conter prazos duplicados.")
|
|
335
|
+
|
|
336
|
+
fluxos = fluxos_caixa(data_liquidacao, data_vencimento)
|
|
337
|
+
dias = du.contar(data_liquidacao, fluxos["data_pagamento"])
|
|
338
|
+
taxas = interpolador.interpolar(
|
|
339
|
+
dias, curva["dias_uteis"].cast(pl.Int64), curva["taxa_zero"], extrapolar=True
|
|
340
|
+
)
|
|
341
|
+
return fluxos.with_columns(dias_uteis=dias, taxa=taxas)
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
def cotacao_curva_zero(
|
|
345
|
+
data_liquidacao: DateLike,
|
|
346
|
+
data_vencimento: DateLike,
|
|
347
|
+
curva_zero: pl.DataFrame,
|
|
348
|
+
) -> float:
|
|
349
|
+
"""Calcula a cotação da NTN-C descontando seus fluxos pela curva informada.
|
|
350
|
+
|
|
351
|
+
Args:
|
|
352
|
+
data_liquidacao: Data de liquidação, exclusiva para seleção dos fluxos.
|
|
353
|
+
data_vencimento: Data de vencimento da NTN-C.
|
|
354
|
+
curva_zero: DataFrame com ``dias_uteis`` (prazos inteiros positivos,
|
|
355
|
+
únicos) e ``taxa_zero`` (taxas anuais decimais finitas maiores que -1).
|
|
356
|
+
A curva deve conter ao menos um vértice; a ordem é indiferente.
|
|
357
|
+
|
|
358
|
+
Returns:
|
|
359
|
+
float: Cotação em base 100, sem truncamento final. Retorna NaN para
|
|
360
|
+
datas ausentes, ausência de fluxos ou resultado não finito.
|
|
361
|
+
|
|
362
|
+
Raises:
|
|
363
|
+
ValueError: Curva vazia, colunas ausentes ou vértices inválidos.
|
|
364
|
+
|
|
365
|
+
Notes:
|
|
366
|
+
Usa os fluxos contratuais de ``fluxos_caixa``, sem deslocar suas datas.
|
|
367
|
+
O prazo em dias úteis / 252 é truncado em 14 casas. A interpolação é
|
|
368
|
+
flat-forward, com taxa constante nas duas pontas da curva. Cada valor
|
|
369
|
+
presente é arredondado em 10 casas, como em ``cotacao``; a soma não é
|
|
370
|
+
truncada, pois serve de alvo para a TIR equivalente.
|
|
371
|
+
|
|
372
|
+
A curva é uma hipótese do consumidor. A biblioteca não escolhe uma
|
|
373
|
+
curva de IPCA como aproximação para IGP-M nem aplica piso ANBIMA.
|
|
374
|
+
Esta operação não representa uma cotação consultada em fonte externa.
|
|
375
|
+
"""
|
|
376
|
+
if any_is_empty(data_liquidacao, data_vencimento):
|
|
377
|
+
return float("nan")
|
|
378
|
+
pagamentos = _pagamentos_curva_zero(data_liquidacao, data_vencimento, curva_zero)
|
|
379
|
+
if pagamentos.is_empty():
|
|
380
|
+
return float("nan")
|
|
381
|
+
cotacao = _utils.cotacao_por_taxas(pagamentos)
|
|
382
|
+
return cotacao if math.isfinite(cotacao) else float("nan")
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
def taxa_curva_zero(
|
|
386
|
+
data_liquidacao: DateLike,
|
|
387
|
+
data_vencimento: DateLike,
|
|
388
|
+
curva_zero: pl.DataFrame,
|
|
389
|
+
) -> float:
|
|
390
|
+
"""Calcula a TIR equivalente à cotação da NTN-C pela curva zero informada.
|
|
391
|
+
|
|
392
|
+
Args:
|
|
393
|
+
data_liquidacao: Data de liquidação.
|
|
394
|
+
data_vencimento: Data de vencimento da NTN-C.
|
|
395
|
+
curva_zero: DataFrame com ``dias_uteis`` e ``taxa_zero``, com os mesmos
|
|
396
|
+
requisitos de ``cotacao_curva_zero``.
|
|
397
|
+
|
|
398
|
+
Returns:
|
|
399
|
+
float: TIR anualizada decimal, sem arredondamento comercial. Retorna
|
|
400
|
+
NaN para datas ausentes, ausência de fluxos com prazo positivo,
|
|
401
|
+
resultado não finito ou falha de convergência.
|
|
402
|
+
|
|
403
|
+
Raises:
|
|
404
|
+
ValueError: Curva vazia, colunas ausentes ou vértices inválidos.
|
|
405
|
+
|
|
406
|
+
Notes:
|
|
407
|
+
Usa datas, interpolação e precisão de ``cotacao_curva_zero``. Resolve
|
|
408
|
+
por bisseção a taxa única que reproduz sua cotação, sem truncar a taxa
|
|
409
|
+
durante a busca. Não inverte um PU nem a cotação truncada de ``cotacao``.
|
|
410
|
+
O intervalo é delimitado pelas menores e maiores taxas interpoladas
|
|
411
|
+
dos fluxos. A busca para por erro de cotação zero ou meia largura do
|
|
412
|
+
intervalo inferior a 1e-12, com limite de 100 iterações.
|
|
413
|
+
A escolha da curva e eventuais spreads ou pisos ficam com o consumidor.
|
|
414
|
+
"""
|
|
415
|
+
if any_is_empty(data_liquidacao, data_vencimento):
|
|
416
|
+
return float("nan")
|
|
417
|
+
pagamentos = _pagamentos_curva_zero(data_liquidacao, data_vencimento, curva_zero)
|
|
418
|
+
if pagamentos.is_empty() or not (pagamentos["dias_uteis"] > 0).any():
|
|
419
|
+
return float("nan")
|
|
420
|
+
alvo = _utils.cotacao_por_taxas(pagamentos)
|
|
421
|
+
if not math.isfinite(alvo):
|
|
422
|
+
return float("nan")
|
|
423
|
+
|
|
424
|
+
def erro(taxa: float) -> float:
|
|
425
|
+
return (
|
|
426
|
+
_utils.cotacao_por_taxas(pagamentos.with_columns(taxa=pl.lit(taxa))) - alvo
|
|
427
|
+
)
|
|
428
|
+
|
|
429
|
+
taxas = pagamentos["taxa"].sort()
|
|
430
|
+
return _utils.encontrar_raiz(erro, intervalo=(float(taxas[0]), float(taxas[-1])))
|
|
431
|
+
|
|
432
|
+
|
|
290
433
|
def _calcular_pu(
|
|
291
434
|
vna: float | Decimal,
|
|
292
435
|
cotacao: float | Decimal,
|
|
@@ -309,8 +452,10 @@ def pu(
|
|
|
309
452
|
pu = VNA * cotacao / 100
|
|
310
453
|
|
|
311
454
|
Args:
|
|
312
|
-
vna: Valor nominal atualizado (VNA)
|
|
455
|
+
vna: Valor nominal atualizado (VNA), truncado em seis casas decimais
|
|
456
|
+
antes do cálculo, sem arredondar.
|
|
313
457
|
cotacao: Cotação da NTN-C em base 100.
|
|
458
|
+
Truncada em quatro casas decimais antes do cálculo, sem arredondar.
|
|
314
459
|
|
|
315
460
|
Returns:
|
|
316
461
|
Decimal: Preço da NTN-C truncado em 6 casas decimais.
|
|
@@ -324,8 +469,6 @@ def pu(
|
|
|
324
469
|
>>> from pyield import ntnc
|
|
325
470
|
>>> ntnc.pu(6598.913723, 126.4958)
|
|
326
471
|
Decimal('8347.348705')
|
|
327
|
-
>>> ntnc.pu(2126.4737349, 99.09819)
|
|
328
|
-
Decimal('2107.295067')
|
|
329
472
|
"""
|
|
330
473
|
return _calcular_pu(vna, cotacao)
|
|
331
474
|
|
|
@@ -7,7 +7,7 @@ Convenções de precificação (STN, tabela 3):
|
|
|
7
7
|
equivalente a 6 casas em termos percentuais.
|
|
8
8
|
- Cada fluxo descontado: arredondado a 9 casas.
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
Valores de referência derivados conforme as regras da STN:
|
|
11
11
|
TAXA_CUPOM = (0.10 + 1) ** 0.5 - 1 -> 10% a.a. com capitalização semestral
|
|
12
12
|
VALOR_FACE = 1000
|
|
13
13
|
VALOR_CUPOM = round(VALOR_FACE * TAXA_CUPOM, 5)
|
|
@@ -40,7 +40,7 @@ from .pre import premios_pre as _premios_pre
|
|
|
40
40
|
|
|
41
41
|
VALOR_FACE = 1000
|
|
42
42
|
VALOR_CUPOM = 48.80885
|
|
43
|
-
VALOR_FINAL =
|
|
43
|
+
VALOR_FINAL = 1048.80885
|
|
44
44
|
|
|
45
45
|
logger = logging.getLogger(__name__)
|
|
46
46
|
|
|
@@ -340,6 +340,8 @@ def pu(
|
|
|
340
340
|
data_vencimento (DateLike): Data de vencimento do título.
|
|
341
341
|
taxa: Taxa de desconto (TIR) em formato decimal.
|
|
342
342
|
Aceita também percentual explícito: "5.75%" ou "5,75%".
|
|
343
|
+
Antes do cálculo, é truncada em oito casas decimais (seis na
|
|
344
|
+
forma percentual), descartando as casas excedentes sem arredondar.
|
|
343
345
|
|
|
344
346
|
Returns:
|
|
345
347
|
Decimal: Preço da NTN-F truncado em seis casas decimais.
|
|
@@ -357,8 +359,6 @@ def pu(
|
|
|
357
359
|
Decimal('895.359254')
|
|
358
360
|
>>> ntnf.pu("01-07-2024", "01-01-2027", "10%")
|
|
359
361
|
Decimal('999.931303')
|
|
360
|
-
>>> ntnf.pu("21-05-2008", "01-01-2014", "13.6600009%")
|
|
361
|
-
Decimal('903.075616')
|
|
362
362
|
"""
|
|
363
363
|
return truncar_decimal(_calcular_pu(data_liquidacao, data_vencimento, taxa), 6)
|
|
364
364
|
|
|
@@ -10,4 +10,12 @@ from pyield.vna._consulta import (
|
|
|
10
10
|
vigencia,
|
|
11
11
|
)
|
|
12
12
|
|
|
13
|
-
__all__ = [
|
|
13
|
+
__all__ = [
|
|
14
|
+
"calcular_vna",
|
|
15
|
+
"TipoTitulo",
|
|
16
|
+
"historico",
|
|
17
|
+
"projetado",
|
|
18
|
+
"ultimo",
|
|
19
|
+
"valor",
|
|
20
|
+
"vigencia",
|
|
21
|
+
]
|
|
@@ -6,6 +6,7 @@ from typing import Literal
|
|
|
6
6
|
|
|
7
7
|
import polars as pl
|
|
8
8
|
|
|
9
|
+
from pyield import du
|
|
9
10
|
from pyield._internal.converters import converter_datas
|
|
10
11
|
from pyield._internal.types import DateLike, any_is_empty
|
|
11
12
|
from pyield.vna import _lft, _ntnb, _ntnc
|
|
@@ -171,26 +172,73 @@ def projetado(
|
|
|
171
172
|
titulo: TipoTitulo,
|
|
172
173
|
data: DateLike,
|
|
173
174
|
vna_base: float | Decimal,
|
|
174
|
-
inflacao: float | Decimal,
|
|
175
|
+
inflacao: float | Decimal | None = None,
|
|
176
|
+
*,
|
|
177
|
+
selic: float | Decimal | str | None = None,
|
|
175
178
|
) -> Decimal:
|
|
176
|
-
"""Projeta o VNA
|
|
179
|
+
"""Projeta o VNA do título conforme sua metodologia.
|
|
177
180
|
|
|
178
181
|
Args:
|
|
179
|
-
titulo: NTN-B ou NTN-C.
|
|
182
|
+
titulo: LFT, NTN-B ou NTN-C.
|
|
180
183
|
data: Data para a qual projetar o VNA.
|
|
181
|
-
vna_base: VNA
|
|
182
|
-
|
|
184
|
+
vna_base: VNA-base correspondente ao início da vigência ou ao último
|
|
185
|
+
dia útil anterior à data, conforme o título.
|
|
186
|
+
inflacao: Para NTN-B e NTN-C, variação mensal percentual; ``0.45``
|
|
187
|
+
representa 0,45%. Para LFT, taxa anual decimal, finita e maior
|
|
188
|
+
que -1; informe-a em ``selic``.
|
|
189
|
+
selic: Obrigatória apenas para LFT. Aceita taxa decimal ou
|
|
190
|
+
percentual explícito, como ``"13.65%"``. Para reproduzir o
|
|
191
|
+
acruamento oficial da LFT, use a taxa Selic diária publicada pelo
|
|
192
|
+
SGS 11. Como ela é publicada em percentual, ``0,050788%`` equivale
|
|
193
|
+
à taxa decimal ``0,00050788`` e ao fator derivado ``1,00050788``.
|
|
194
|
+
Converta essa taxa diária para uma taxa anual equivalente. A Selic
|
|
195
|
+
Over do SGS 1178 é anualizada e serve apenas como aproximação. Para
|
|
196
|
+
NTN-B e NTN-C, deve ser omitida.
|
|
183
197
|
|
|
184
198
|
Returns:
|
|
185
199
|
Decimal: VNA projetado com seis casas; NaN para entradas nulas.
|
|
186
200
|
|
|
187
201
|
Notes:
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
202
|
+
NTN-B e NTN-C seguem a metodologia STN: base truncada em seis casas,
|
|
203
|
+
inflação arredondada em duas e expoente em dias corridos truncado em
|
|
204
|
+
catorze. LFT usa dias úteis, base 252 e capitalização pela taxa anual
|
|
205
|
+
informada. A taxa Selic diária publicada pelo SGS 11 é a referência
|
|
206
|
+
exata para o acruamento; o fator usado no cálculo é ``1 + taxa`` em
|
|
207
|
+
formato decimal. A função não busca a taxa automaticamente nem faz
|
|
208
|
+
projeções externas.
|
|
191
209
|
|
|
192
210
|
Raises:
|
|
193
|
-
ValueError: Se o título
|
|
194
|
-
|
|
211
|
+
ValueError: Se o título for inválido, a taxa for informada para o
|
|
212
|
+
título errado, ou houver violação dos limites da metodologia
|
|
213
|
+
específica.
|
|
214
|
+
|
|
215
|
+
Examples:
|
|
216
|
+
>>> import pyield as yd
|
|
217
|
+
>>> yd.vna.projetado("NTN-B", "30-06-2026", 4731.856412, 0.45)
|
|
218
|
+
Decimal('4742.491138')
|
|
219
|
+
>>> yd.vna.projetado(
|
|
220
|
+
... "LFT",
|
|
221
|
+
... "18-09-2026",
|
|
222
|
+
... 19905.773236,
|
|
223
|
+
... selic="13.65%",
|
|
224
|
+
... )
|
|
225
|
+
Decimal('19915.882987')
|
|
195
226
|
"""
|
|
227
|
+
if titulo not in {"LFT", "NTN-B", "NTN-C"}:
|
|
228
|
+
raise ValueError("Título deve ser LFT, NTN-B ou NTN-C.")
|
|
229
|
+
if titulo == "LFT":
|
|
230
|
+
if inflacao is not None:
|
|
231
|
+
raise ValueError("Use selic para a projeção da LFT.")
|
|
232
|
+
if selic is None:
|
|
233
|
+
return Decimal("NaN")
|
|
234
|
+
if any_is_empty(selic):
|
|
235
|
+
return Decimal("NaN")
|
|
236
|
+
data_convertida = converter_datas(data)
|
|
237
|
+
if du.eh_dia_util(data_convertida):
|
|
238
|
+
data_base = du.deslocar(data_convertida, -1, ajuste="anterior")
|
|
239
|
+
else:
|
|
240
|
+
data_base = du.deslocar(data_convertida, 0, ajuste="anterior")
|
|
241
|
+
return _lft.projetado_lft(data_base, data, vna_base, selic)
|
|
242
|
+
if selic is not None:
|
|
243
|
+
raise ValueError("selic aplica-se apenas à projeção da LFT.")
|
|
196
244
|
return _modulo_mensal(titulo).vna_projetado(data, vna_base, inflacao)
|
|
@@ -16,16 +16,99 @@ valores e retorna o VNA único.
|
|
|
16
16
|
"""
|
|
17
17
|
|
|
18
18
|
import datetime as dt
|
|
19
|
-
|
|
19
|
+
import logging
|
|
20
|
+
from decimal import Decimal, localcontext
|
|
20
21
|
|
|
21
22
|
import requests
|
|
22
23
|
|
|
24
|
+
from pyield import du
|
|
23
25
|
from pyield._internal.cache import ttl_cache
|
|
24
26
|
from pyield._internal.converters import converter_datas, data_referencia_valida
|
|
27
|
+
from pyield._internal.numbers import truncar_decimal
|
|
25
28
|
from pyield._internal.retry import retry_padrao
|
|
26
29
|
from pyield._internal.types import DateLike, any_is_empty
|
|
30
|
+
from pyield.tpf.titulos._utils import converter_taxa
|
|
27
31
|
|
|
28
32
|
CODIGO_LFT = "210100"
|
|
33
|
+
_LOGGER = logging.getLogger(__name__)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def projetado_lft(
|
|
37
|
+
data_base: DateLike,
|
|
38
|
+
data: DateLike,
|
|
39
|
+
vna_base: float | Decimal,
|
|
40
|
+
taxa_anual: float | Decimal | str,
|
|
41
|
+
) -> Decimal:
|
|
42
|
+
"""Projeta o VNA da LFT com uma taxa anual constante fornecida pelo consumidor.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
data_base: Data à qual corresponde o VNA-base.
|
|
46
|
+
data: Data do próximo dia útil de acruamento após a data-base.
|
|
47
|
+
vna_base: VNA-base em reais, positivo e finito.
|
|
48
|
+
taxa_anual: Taxa anual decimal, finita e maior que -1. Aceita também
|
|
49
|
+
percentual explícito, como ``"13.65%"`` ou ``"13,65%"``.
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
Decimal: VNA projetado, truncado em seis casas decimais. Retorna
|
|
53
|
+
``Decimal('NaN')`` para entradas ausentes ou NaN.
|
|
54
|
+
|
|
55
|
+
Notes:
|
|
56
|
+
Calcula ``vna_base * (1 + taxa_anual) ** (dias_uteis / 252)``.
|
|
57
|
+
Usa ``yd.du.contar`` com calendário automático de feriados brasileiros:
|
|
58
|
+
data-base inclusiva e data final exclusiva, sem deslocar as datas.
|
|
59
|
+
Com zero dias úteis, retorna o VNA-base truncado em seis casas.
|
|
60
|
+
|
|
61
|
+
O cálculo usa Decimal com 28 dígitos de precisão. Entradas float são
|
|
62
|
+
convertidas pela representação decimal textual. Percentuais explícitos
|
|
63
|
+
são convertidos para decimal sem truncamento. Não há truncamentos
|
|
64
|
+
intermediários nem arredondamento comercial da taxa.
|
|
65
|
+
|
|
66
|
+
Não consulta fontes externas nem escolhe automaticamente uma taxa. Para
|
|
67
|
+
reproduzir o acruamento oficial da LFT, use a taxa Selic diária
|
|
68
|
+
publicada pela série SGS 11. Como ela é publicada em percentual,
|
|
69
|
+
``0,050788%`` corresponde à taxa decimal ``0,00050788`` e ao fator
|
|
70
|
+
derivado ``1,00050788``. Converta essa taxa diária para uma taxa anual
|
|
71
|
+
equivalente antes de informar ``taxa_anual``. A Selic Over da série
|
|
72
|
+
SGS 1178 é anualizada e serve apenas como aproximação. O resultado é
|
|
73
|
+
uma projeção sob a taxa informada, não um VNA oficial realizado. A
|
|
74
|
+
taxa pode ser negativa, desde que maior que -100%.
|
|
75
|
+
|
|
76
|
+
Raises:
|
|
77
|
+
ValueError: Data final anterior à base, VNA não positivo ou não finito,
|
|
78
|
+
taxa não finita ou menor ou igual a -100%, ou data malformada.
|
|
79
|
+
|
|
80
|
+
Examples:
|
|
81
|
+
>>> from decimal import Decimal
|
|
82
|
+
>>> projetado_lft(
|
|
83
|
+
... "17-09-2026",
|
|
84
|
+
... "18-09-2026",
|
|
85
|
+
... Decimal("19905.773236"),
|
|
86
|
+
... "13.65%",
|
|
87
|
+
... )
|
|
88
|
+
Decimal('19915.882987')
|
|
89
|
+
"""
|
|
90
|
+
if any_is_empty(data_base, data, vna_base, taxa_anual):
|
|
91
|
+
return Decimal("NaN")
|
|
92
|
+
inicio = converter_datas(data_base)
|
|
93
|
+
fim = converter_datas(data)
|
|
94
|
+
if fim < inicio:
|
|
95
|
+
raise ValueError("A data de projeção não pode ser anterior à data-base.")
|
|
96
|
+
dias_uteis = du.contar(inicio, fim)
|
|
97
|
+
if dias_uteis != 1:
|
|
98
|
+
_LOGGER.warning(
|
|
99
|
+
"A projeção da LFT avançou uma quantidade diferente de um dia útil.",
|
|
100
|
+
)
|
|
101
|
+
base = vna_base if isinstance(vna_base, Decimal) else Decimal(str(vna_base))
|
|
102
|
+
taxa_anual = converter_taxa(taxa_anual)
|
|
103
|
+
taxa = taxa_anual if isinstance(taxa_anual, Decimal) else Decimal(str(taxa_anual))
|
|
104
|
+
if not base.is_finite() or base <= 0:
|
|
105
|
+
raise ValueError("O VNA-base deve ser positivo e finito.")
|
|
106
|
+
if not taxa.is_finite() or taxa <= -1:
|
|
107
|
+
raise ValueError("A taxa anual deve ser finita e maior que -100%.")
|
|
108
|
+
with localcontext() as contexto:
|
|
109
|
+
contexto.prec = 28
|
|
110
|
+
expoente = Decimal(dias_uteis) / 252
|
|
111
|
+
return truncar_decimal(base * (1 + taxa) ** expoente, 6)
|
|
29
112
|
|
|
30
113
|
|
|
31
114
|
@ttl_cache()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|