Pular para conteúdo

NTN-B1

Precificação de NTN-B1 pelas regras do Tesouro Direto.

NomeComercial

Bases: Enum

Enum do nome comercial usado para identificar o tipo de NTN-B1 (Renda+ ou Educa+).

cotacao(data_liquidacao, data_vencimento, taxa, nome_comercial)

Calcula a cotação da NTN-B1 em base 1 pelo método do Tesouro Direto.

Parameters:

Name Type Description Default
data_liquidacao DateLike

Data de liquidação da operação.

required
data_vencimento DateLike

Data de vencimento da NTN-B1.

required
taxa float | Decimal

Taxa de desconto (YTM) usada no valor presente.

required
nome_comercial NomeComercial

Nome comercial (Renda+ ou Educa+).

required

Returns:

Name Type Description
Decimal Decimal

Cotação da NTN-B1 em base 1, truncada em 6 casas decimais.

Examples:

>>> from pyield import ntnb1
>>> r_mais = ntnb1.NomeComercial.RENDA_MAIS
>>> ntnb1.cotacao("18-06-2025", "15-12-2084", 0.07010, r_mais)
Decimal('0.038332')
Source code in pyield/tpf/titulos/ntnb1.py
def cotacao(
    data_liquidacao: DateLike,
    data_vencimento: DateLike,
    taxa: float | Decimal,
    nome_comercial: NomeComercial,
) -> Decimal:
    """
    Calcula a cotação da NTN-B1 em base 1 pelo método do Tesouro Direto.

    Args:
        data_liquidacao: Data de liquidação da operação.
        data_vencimento: Data de vencimento da NTN-B1.
        taxa: Taxa de desconto (YTM) usada no valor presente.
        nome_comercial: Nome comercial (Renda+ ou Educa+).

    Returns:
        Decimal: Cotação da NTN-B1 em base 1, truncada em 6 casas decimais.

    Examples:
        >>> from pyield import ntnb1
        >>> r_mais = ntnb1.NomeComercial.RENDA_MAIS
        >>> ntnb1.cotacao("18-06-2025", "15-12-2084", 0.07010, r_mais)
        Decimal('0.038332')
    """
    if any_is_empty(data_liquidacao, data_vencimento, taxa, nome_comercial):
        return Decimal("NaN")

    taxa_float = float(taxa)
    df_fluxos = fluxos_caixa(data_liquidacao, data_vencimento, nome_comercial)
    valores_fluxo = df_fluxos["valor_pagamento"]
    dias_uteis = du.contar(data_liquidacao, df_fluxos["data_pagamento"])
    anos_uteis = utils.truncar(dias_uteis / 252, 14)
    fatores_desconto = (1 + taxa_float) ** anos_uteis
    # Na base 1, cada valor presente é arredondado na 12ª casa decimal.
    vp = (valores_fluxo / fatores_desconto).round(12)
    # Retorna a cotação em base 1, truncada na 6ª casa decimal.
    return truncar_decimal(vp.sum(), 6)

cotacao_curva_zero(data_liquidacao, data_vencimento, curva_zero, nome_comercial)

Calcula a cotação de uma NTN-B1 descontando cada fluxo pela curva zero.

A função usa interpolação flat-forward entre os vértices da curva e mantém a última taxa zero após o maior vértice, conforme a extrapolação do método TD. Cada valor presente, em base 1, é arredondado na 12ª casa decimal; a soma final não é truncada porque ela é o alvo da calibração da TIR equivalente.

Parameters:

Name Type Description Default
data_liquidacao DateLike

Data de liquidação.

required
data_vencimento DateLike

Data da última amortização da NTN-B1.

required
curva_zero DataFrame

DataFrame com as colunas dias_uteis e taxa_zero.

required
nome_comercial NomeComercial

Nome comercial, Renda+ ou Educa+.

required

Returns:

Name Type Description
float float

Cotação em base 1 calculada pela curva zero.

Source code in pyield/tpf/titulos/ntnb1.py
def cotacao_curva_zero(
    data_liquidacao: DateLike,
    data_vencimento: DateLike,
    curva_zero: pl.DataFrame,
    nome_comercial: NomeComercial,
) -> float:
    """
    Calcula a cotação de uma NTN-B1 descontando cada fluxo pela curva zero.

    A função usa interpolação flat-forward entre os vértices da curva e mantém
    a última taxa zero após o maior vértice, conforme a extrapolação do método
    TD. Cada valor presente, em base 1, é arredondado na 12ª casa decimal; a
    soma final não é truncada porque ela é o alvo da calibração da TIR
    equivalente.

    Args:
        data_liquidacao: Data de liquidação.
        data_vencimento: Data da última amortização da NTN-B1.
        curva_zero: DataFrame com as colunas ``dias_uteis`` e ``taxa_zero``.
        nome_comercial: Nome comercial, Renda+ ou Educa+.

    Returns:
        float: Cotação em base 1 calculada pela curva zero.
    """
    if any_is_empty(data_liquidacao, data_vencimento, nome_comercial):
        return float("nan")

    curva = _validar_curva_zero(curva_zero)
    fluxos = fluxos_caixa(data_liquidacao, data_vencimento, nome_comercial)
    dias_fluxos = du.contar(data_liquidacao, fluxos["data_pagamento"])
    taxas_fluxos = interpolador.interpolar(
        dias_fluxos,
        curva["dias_uteis"],
        curva["taxa_zero"],
        extrapolar=True,
    )
    pagamentos = fluxos.with_columns(dias_uteis=dias_fluxos, taxa=taxas_fluxos)
    return _cotacao_por_taxas(pagamentos)

datas_pagamento(data_liquidacao, data_vencimento, nome_comercial)

Gera todas as datas de amortização entre liquidação e vencimento.

A liquidação é exclusiva e o vencimento é inclusivo. Os pagamentos ocorrem de 15/01 do ano de conversão até 15/12 do ano de vencimento. As datas são contratuais e não são ajustadas para dias úteis.

Parameters:

Name Type Description Default
data_liquidacao DateLike

Data de liquidação (exclusiva).

required
data_vencimento DateLike

Data de vencimento.

required
nome_comercial NomeComercial

Nome comercial (Renda+ ou Educa+).

required

Returns:

Type Description
Series

pl.Series: Série de datas de amortização no intervalo.

Notes

Para obter as datas efetivas de processamento, use yd.du.deslocar(..., 0).

Examples:

>>> from pyield import ntnb1
>>> r_mais = ntnb1.NomeComercial.RENDA_MAIS
>>> ntnb1.datas_pagamento("10-05-2024", "15-12-2050", r_mais)
shape: (240,)
Series: 'datas_pagamento' [date]
[
    2031-01-15
    2031-02-15
    2031-03-15
    2031-04-15
    2031-05-15

    2050-08-15
    2050-09-15
    2050-10-15
    2050-11-15
    2050-12-15
]
Source code in pyield/tpf/titulos/ntnb1.py
def datas_pagamento(
    data_liquidacao: DateLike,
    data_vencimento: DateLike,
    nome_comercial: NomeComercial,
) -> pl.Series:
    """
    Gera todas as datas de amortização entre liquidação e vencimento.

    A liquidação é exclusiva e o vencimento é inclusivo. Os pagamentos ocorrem
    de 15/01 do ano de conversão até 15/12 do ano de vencimento. As datas são
    contratuais e não são ajustadas para dias úteis.

    Args:
        data_liquidacao (DateLike): Data de liquidação (exclusiva).
        data_vencimento (DateLike): Data de vencimento.
        nome_comercial (NomeComercial): Nome comercial (Renda+ ou Educa+).

    Returns:
        pl.Series: Série de datas de amortização no intervalo.

    Notes:
        Para obter as datas efetivas de processamento, use
        ``yd.du.deslocar(..., 0)``.

    Examples:
        >>> from pyield import ntnb1
        >>> r_mais = ntnb1.NomeComercial.RENDA_MAIS
        >>> ntnb1.datas_pagamento("10-05-2024", "15-12-2050", r_mais)
        shape: (240,)
        Series: 'datas_pagamento' [date]
        [
            2031-01-15
            2031-02-15
            2031-03-15
            2031-04-15
            2031-05-15

            2050-08-15
            2050-09-15
            2050-10-15
            2050-11-15
            2050-12-15
        ]
    """
    if any_is_empty(data_liquidacao, data_vencimento, nome_comercial):
        return pl.Series("datas_pagamento", dtype=pl.Date)

    # Valida e normaliza datas
    liquidacao = conversores.converter_datas(data_liquidacao)
    vencimento = conversores.converter_datas(data_vencimento)

    if vencimento <= liquidacao:
        raise ValueError("A data de vencimento deve ser posterior à liquidação.")

    vencimento = vencimento.replace(day=15)

    # Parâmetros do título
    _, _, numero_amortizacoes = _obter_parametros_titulo(nome_comercial)

    datas_amortizacao = [
        utils.subtrair_meses(vencimento, i) for i in range(numero_amortizacoes)
    ]

    if len(datas_amortizacao) == 0:
        raise ValueError("Nenhuma data de amortização após a liquidação.")

    datas_pagamento = pl.Series(name="datas_pagamento", values=datas_amortizacao)

    return datas_pagamento.filter(datas_pagamento > liquidacao).sort()

duration(data_liquidacao, data_vencimento, taxa, nome_comercial)

Calcula a Macaulay duration da NTN-B1 em anos úteis.

Parameters:

Name Type Description Default
data_liquidacao DateLike

Data de liquidação da operação.

required
data_vencimento DateLike

Data de vencimento.

required
taxa float

Taxa de desconto usada no cálculo.

required
nome_comercial NomeComercial

Nome comercial (Renda+ ou Educa+).

required

Returns:

Name Type Description
float float

Macaulay duration em anos úteis.

Examples:

>>> from pyield import ntnb1
>>> r_mais = ntnb1.NomeComercial.RENDA_MAIS
>>> ntnb1.duration("23-06-2025", "15-12-2084", 0.0686, r_mais)
47.10494386899197
Source code in pyield/tpf/titulos/ntnb1.py
def duration(
    data_liquidacao: DateLike,
    data_vencimento: DateLike,
    taxa: float,
    nome_comercial: NomeComercial,
) -> float:
    """
    Calcula a Macaulay duration da NTN-B1 em anos úteis.

    Args:
        data_liquidacao (DateLike): Data de liquidação da operação.
        data_vencimento (DateLike): Data de vencimento.
        taxa (float): Taxa de desconto usada no cálculo.
        nome_comercial (NomeComercial): Nome comercial (Renda+ ou Educa+).

    Returns:
        float: Macaulay duration em anos úteis.

    Examples:
        >>> from pyield import ntnb1
        >>> r_mais = ntnb1.NomeComercial.RENDA_MAIS
        >>> ntnb1.duration("23-06-2025", "15-12-2084", 0.0686, r_mais)
        47.10494386899197
    """
    # Retorna NaN se houver entradas nulas
    if any_is_empty(data_liquidacao, data_vencimento, taxa, nome_comercial):
        return float("nan")

    df_fluxos = fluxos_caixa(data_liquidacao, data_vencimento, nome_comercial)
    anos_uteis = du.contar(data_liquidacao, df_fluxos["data_pagamento"]) / 252
    vp = df_fluxos["valor_pagamento"] / (1 + taxa) ** anos_uteis
    duration = float((vp * anos_uteis).sum()) / float(vp.sum())

    # Trunca a duração para 14 casas para reprodutibilidade
    return utils.truncar(duration, 14)

dv01(data_liquidacao, data_vencimento, taxa, pu, nome_comercial=NomeComercial.RENDA_MAIS)

Calcula o DV01 (Dollar Value of 01) da NTN-B1 em R$.

Representa a variação do PU informado para um aumento de 1 bp (0,01%) na taxa.

Parameters:

Name Type Description Default
data_liquidacao DateLike

Data de liquidação.

required
data_vencimento DateLike

Data de vencimento.

required
taxa float

Taxa de desconto (YTM) da NTN-B1.

required
pu float | Decimal

PU usado como base para o cálculo.

required
nome_comercial NomeComercial

Nome comercial (Renda+ ou Educa+).

RENDA_MAIS

Returns:

Name Type Description
float float

DV01, variação de preço para 1 bp.

Examples:

>>> from pyield import ntnb1
>>> r_mais = ntnb1.NomeComercial.RENDA_MAIS
>>> cot = ntnb1.cotacao("23-06-2025", "15-12-2084", 0.0686, r_mais)
>>> pu = ntnb1.pu(4299.160173, cot)
>>> ntnb1.dv01("23-06-2025", "15-12-2084", 0.0686, pu, r_mais)
0.7738488291718512
Source code in pyield/tpf/titulos/ntnb1.py
def dv01(
    data_liquidacao: DateLike,
    data_vencimento: DateLike,
    taxa: float,
    pu: float | Decimal,
    nome_comercial: NomeComercial = NomeComercial.RENDA_MAIS,
) -> float:
    """
    Calcula o DV01 (Dollar Value of 01) da NTN-B1 em R$.

    Representa a variação do PU informado para um aumento de 1 bp (0,01%) na
    taxa.

    Args:
        data_liquidacao (DateLike): Data de liquidação.
        data_vencimento (DateLike): Data de vencimento.
        taxa (float): Taxa de desconto (YTM) da NTN-B1.
        pu: PU usado como base para o cálculo.
        nome_comercial (NomeComercial): Nome comercial (Renda+ ou Educa+).

    Returns:
        float: DV01, variação de preço para 1 bp.

    Examples:
        >>> from pyield import ntnb1
        >>> r_mais = ntnb1.NomeComercial.RENDA_MAIS
        >>> cot = ntnb1.cotacao("23-06-2025", "15-12-2084", 0.0686, r_mais)
        >>> pu = ntnb1.pu(4299.160173, cot)
        >>> ntnb1.dv01("23-06-2025", "15-12-2084", 0.0686, pu, r_mais)
        0.7738488291718512
    """
    if any_is_empty(data_liquidacao, data_vencimento, taxa, pu, nome_comercial):
        return float("nan")

    cotacao_1 = cotacao(data_liquidacao, data_vencimento, taxa, nome_comercial)
    cotacao_2 = cotacao(
        data_liquidacao,
        data_vencimento,
        taxa + 0.0001,
        nome_comercial,
    )
    fator_variacao = 1 - float(cotacao_2) / float(cotacao_1)
    return float(pu) * fator_variacao

fluxos_caixa(data_liquidacao, data_vencimento, nome_comercial)

Gera os fluxos de caixa da NTN-B1 entre liquidação e vencimento.

Parameters:

Name Type Description Default
data_liquidacao DateLike

Data de liquidação (exclusiva).

required
data_vencimento DateLike

Data de vencimento.

required
nome_comercial NomeComercial

Nome comercial (Renda+ ou Educa+).

required

Returns:

Type Description
DataFrame

pl.DataFrame: DataFrame com as colunas de fluxo.

Output Columns
  • data_pagamento (Date): Data contratual do pagamento, sem ajuste para dia útil.
  • valor_pagamento (Float64): Valor do pagamento.
Notes

Para obter as datas efetivas de processamento, use yd.du.deslocar(..., 0).

Examples:

>>> from pyield import ntnb1
>>> r_mais = ntnb1.NomeComercial.RENDA_MAIS
>>> ntnb1.fluxos_caixa("10-05-2024", "15-12-2060", r_mais)
shape: (240, 2)
┌────────────────┬─────────────────┐
│ data_pagamento ┆ valor_pagamento │
│ ---            ┆ ---             │
│ date           ┆ f64             │
╞════════════════╪═════════════════╡
│ 2041-01-15     ┆ 0.004167        │
│ 2041-02-15     ┆ 0.004167        │
│ 2041-03-15     ┆ 0.004167        │
│ 2041-04-15     ┆ 0.004167        │
│ 2041-05-15     ┆ 0.004167        │
│ …              ┆ …               │
│ 2060-08-15     ┆ 0.004167        │
│ 2060-09-15     ┆ 0.004167        │
│ 2060-10-15     ┆ 0.004167        │
│ 2060-11-15     ┆ 0.004167        │
│ 2060-12-15     ┆ 0.004168        │
└────────────────┴─────────────────┘
Source code in pyield/tpf/titulos/ntnb1.py
def fluxos_caixa(
    data_liquidacao: DateLike,
    data_vencimento: DateLike,
    nome_comercial: NomeComercial,
) -> pl.DataFrame:
    """
    Gera os fluxos de caixa da NTN-B1 entre liquidação e vencimento.

    Args:
        data_liquidacao (DateLike): Data de liquidação (exclusiva).
        data_vencimento (DateLike): Data de vencimento.
        nome_comercial (NomeComercial): Nome comercial (Renda+ ou Educa+).

    Returns:
        pl.DataFrame: DataFrame com as colunas de fluxo.

    Output Columns:
        - data_pagamento (Date): Data contratual do pagamento, sem ajuste para
            dia útil.
        - valor_pagamento (Float64): Valor do pagamento.

    Notes:
        Para obter as datas efetivas de processamento, use
        ``yd.du.deslocar(..., 0)``.

    Examples:
        >>> from pyield import ntnb1
        >>> r_mais = ntnb1.NomeComercial.RENDA_MAIS
        >>> ntnb1.fluxos_caixa("10-05-2024", "15-12-2060", r_mais)
        shape: (240, 2)
        ┌────────────────┬─────────────────┐
        │ data_pagamento ┆ valor_pagamento │
        │ ---            ┆ ---             │
        │ date           ┆ f64             │
        ╞════════════════╪═════════════════╡
        │ 2041-01-15     ┆ 0.004167        │
        │ 2041-02-15     ┆ 0.004167        │
        │ 2041-03-15     ┆ 0.004167        │
        │ 2041-04-15     ┆ 0.004167        │
        │ 2041-05-15     ┆ 0.004167        │
        │ …              ┆ …               │
        │ 2060-08-15     ┆ 0.004167        │
        │ 2060-09-15     ┆ 0.004167        │
        │ 2060-10-15     ┆ 0.004167        │
        │ 2060-11-15     ┆ 0.004167        │
        │ 2060-12-15     ┆ 0.004168        │
        └────────────────┴─────────────────┘

    """
    if any_is_empty(data_liquidacao, data_vencimento, nome_comercial):
        return pl.DataFrame({"data_pagamento": [], "valor_pagamento": []})

    # Valida e normaliza datas
    liquidacao = conversores.converter_datas(data_liquidacao)
    vencimento = conversores.converter_datas(data_vencimento)

    # Obtém as datas de amortização
    serie_datas_pagamento = datas_pagamento(liquidacao, vencimento, nome_comercial)
    df = pl.DataFrame({"data_pagamento": serie_datas_pagamento})

    # Parâmetros do título
    pagamento_amort, pagamento_amort_final, _ = _obter_parametros_titulo(nome_comercial)

    # Define o fluxo final no vencimento e os demais como amortizações
    df = df.with_columns(
        pl.when(pl.col("data_pagamento") == vencimento)
        .then(pagamento_amort_final)
        .otherwise(pagamento_amort)
        .alias("valor_pagamento")
    )

    # Retorna o DataFrame com datas e fluxos
    return df

pu(vna, cotacao)

Calcula o preço (PU) da NTN-B1 pelas regras do Tesouro Nacional.

Parameters:

Name Type Description Default
vna float | Decimal

Valor nominal atualizado (VNA).

required
cotacao float | Decimal

Cotação da NTN-B1 em base 1.

required

Returns:

Name Type Description
Decimal Decimal

Preço da NTN-B1 truncado em 6 casas decimais.

References
  • SEI Proccess 17944.005214/2024-09

Examples:

>>> from pyield import ntnb1
>>> ntnb1.pu(4299.160173, 0.993651)
Decimal('4271.864805')
>>> ntnb1.pu(4315.498383, 1.006409)
Decimal('4343.156412')
Source code in pyield/tpf/titulos/ntnb1.py
def pu(
    vna: float | Decimal,
    cotacao: float | Decimal,
) -> Decimal:
    """
    Calcula o preço (PU) da NTN-B1 pelas regras do Tesouro Nacional.

    Args:
        vna: Valor nominal atualizado (VNA).
        cotacao: Cotação da NTN-B1 em base 1.

    Returns:
        Decimal: Preço da NTN-B1 truncado em 6 casas decimais.

    References:
         - SEI Proccess 17944.005214/2024-09

    Examples:
        >>> from pyield import ntnb1
        >>> ntnb1.pu(4299.160173, 0.993651)
        Decimal('4271.864805')
        >>> ntnb1.pu(4315.498383, 1.006409)
        Decimal('4343.156412')
    """
    if any_is_empty(vna, cotacao):
        return Decimal("NaN")
    vna_decimal = truncar_decimal(vna, 6)
    cotacao_decimal = truncar_decimal(cotacao, 6)
    return truncar_decimal(vna_decimal * cotacao_decimal, 6)

taxa_curva_zero(data_liquidacao, data_vencimento, curva_zero, nome_comercial)

Calcula a TIR equivalente de uma NTN-B1 pela curva zero do método TD.

Primeiro, cada amortização mensal do Renda+ ou Educa+ é descontada pela taxa zero correspondente à sua data. Em seguida, a função encontra por bisseção a taxa única que produz a mesma cotação quando aplicada a todos os fluxos. Essa é a taxa equivalente do título calculada pelo método TD.

Parameters:

Name Type Description Default
data_liquidacao DateLike

Data de liquidação.

required
data_vencimento DateLike

Data da última amortização da NTN-B1.

required
curva_zero DataFrame

DataFrame com as colunas dias_uteis e taxa_zero.

required
nome_comercial NomeComercial

Nome comercial, Renda+ ou Educa+.

required

Returns:

Name Type Description
float float

TIR equivalente anualizada, em formato decimal.

Source code in pyield/tpf/titulos/ntnb1.py
def taxa_curva_zero(
    data_liquidacao: DateLike,
    data_vencimento: DateLike,
    curva_zero: pl.DataFrame,
    nome_comercial: NomeComercial,
) -> float:
    """
    Calcula a TIR equivalente de uma NTN-B1 pela curva zero do método TD.

    Primeiro, cada amortização mensal do Renda+ ou Educa+ é descontada pela
    taxa zero correspondente à sua data. Em seguida, a função encontra por
    bisseção a taxa única que produz a mesma cotação quando aplicada a todos os
    fluxos. Essa é a taxa equivalente do título calculada pelo método TD.

    Args:
        data_liquidacao: Data de liquidação.
        data_vencimento: Data da última amortização da NTN-B1.
        curva_zero: DataFrame com as colunas ``dias_uteis`` e ``taxa_zero``.
        nome_comercial: Nome comercial, Renda+ ou Educa+.

    Returns:
        float: TIR equivalente anualizada, em formato decimal.
    """
    if any_is_empty(data_liquidacao, data_vencimento, nome_comercial):
        return float("nan")

    curva = _validar_curva_zero(curva_zero)
    fluxos = fluxos_caixa(data_liquidacao, data_vencimento, nome_comercial)
    dias_fluxos = du.contar(data_liquidacao, fluxos["data_pagamento"])
    taxas_zero = interpolador.interpolar(
        dias_fluxos,
        curva["dias_uteis"],
        curva["taxa_zero"],
        extrapolar=True,
    )
    pagamentos_base = fluxos.with_columns(dias_uteis=dias_fluxos)
    cotacao_alvo = _cotacao_por_taxas(pagamentos_base.with_columns(taxa=taxas_zero))
    return _resolver_taxa_equivalente(
        cotacao_alvo,
        pagamentos_base,
        taxa_inicial=float(taxas_zero[-1]),
    )