Pular para conteúdo

NTN-F

Comparação experimental de curvas zero

yd.ntnf.taxas_zero_forwards constrói uma curva conjunta de LTN e NTN-F, com forwards constantes entre vértices. A LTN tem prioridade somente quando os vencimentos coincidem. Uma NTN-F entre duas LTNs participa da calibração. O método existente yd.ntnf.taxas_zero e yd.tpf.curva_pre permanecem inalterados.

import polars as pl

import pyield as yd

ltn = yd.ltn.dados("04-09-2026")
ntnf = yd.ntnf.dados("04-09-2026")
entradas = dict(
    data_liquidacao="04-09-2026",
    vencimentos_ltn=ltn["data_vencimento"],
    taxas_ltn=ltn["taxa_indicativa"],
    vencimentos_ntnf=ntnf["data_vencimento"],
    taxas_ntnf=ntnf["taxa_indicativa"],
)
antiga = yd.ntnf.taxas_zero(**entradas)
proposta = yd.ntnf.taxas_zero_forwards(**entradas)
comparacao = antiga.join(
    proposta, on=["data_vencimento", "dias_uteis"], suffix="_proposta"
).with_columns(
    diferenca_pb=(pl.col("taxa_zero_proposta") - pl.col("taxa_zero")) * 10_000
)

A proposta retorna todos os vencimentos selecionados, inclusive os de LTN. Preserve todos esses vértices ao interpolar por flat_forward: filtrar apenas as datas de NTN-F pode mudar os descontos intermediários. O preço-alvo da calibração não aplica arredondamentos ou truncamentos, como no bootstrap da NTN-B; por isso, pode diferir ligeiramente do PU oficial.

Cálculos e dados de NTN-F.

Convenções de precificação (STN, tabela 3): - Prazo de desconto: dias úteis / 252, truncado a 14 casas. - PU: truncado a 6 casas. - Cada fluxo descontado: arredondado a 9 casas.

Valores de referência derivados conforme as regras da STN: TAXA_CUPOM = (0.10 + 1) ** 0.5 - 1 -> 10% a.a. com capitalização semestral VALOR_FACE = 1000 VALOR_CUPOM = round(VALOR_FACE * TAXA_CUPOM, 5) VALOR_FINAL = VALOR_FACE + VALOR_CUPOM

A NTN-F paga dois cupons por ano (semestrais). As datas de cupom são derivadas do vencimento (retrocedendo 6 em 6 meses), sem depender de meses fixos. Ex.: vencimento 01-01-2027 gera cupons em 01-07-2026, 01-01-2026, ...

dados(data)

Busca as taxas indicativas de NTN-F para a data de referência.

Parameters:

Name Type Description Default
data DateLike

Data da consulta.

required

Returns:

Type Description
DataFrame

pl.DataFrame: DataFrame Polars com os dados de NTN-F. Na ausência de dados, retorna vazio com as mesmas colunas e tipos.

Output Columns
  • data_referencia (Date): Data de referência dos dados.
  • titulo (String): Tipo do título (ex.: "NTN-F").
  • codigo_selic (Int64): Código do título no SELIC.
  • data_base (Date): Data base de emissão do título.
  • data_vencimento (Date): Data de vencimento do título.
  • dias_uteis (Int64): Dias úteis entre referência e vencimento.
  • prazo_medio (Float64): Prazo médio do título em dias corridos.
  • duration (Float64): Macaulay Duration do título (anos).
  • dv01 (Float64): Variação no preço para 1bp de taxa.
  • pu (Float64): Preço unitário (PU).
  • taxa_compra (Float64): Taxa de compra (decimal).
  • taxa_venda (Float64): Taxa de venda (decimal).
  • taxa_indicativa (Float64): Taxa indicativa (decimal).
  • taxa_di (Float64): Taxa de ajuste do DI Futuro interpolada pelo método flat forward.
  • taxa_zero (Float64): Taxa zero (zero cupom via bootstrap).
  • premio (Float64): prêmio, isto é, o spread sobre o DI.
  • premio_limpo (Float64): prêmio limpo sobre a curva DI.
  • rentabilidade (Float64): Rentabilidade da NTN-F sobre a curva DI.

Examples:

>>> from pyield import ntnf
>>> df_ntnf = ntnf.dados("23-08-2024")
Source code in pyield/tpf/titulos/ntnf.py
def dados(data: DateLike) -> pl.DataFrame:
    """
    Busca as taxas indicativas de NTN-F para a data de referência.

    Args:
        data (DateLike): Data da consulta.

    Returns:
        pl.DataFrame: DataFrame Polars com os dados de NTN-F. Na ausência de
            dados, retorna vazio com as mesmas colunas e tipos.

    Output Columns:
        - data_referencia (Date): Data de referência dos dados.
        - titulo (String): Tipo do título (ex.: "NTN-F").
        - codigo_selic (Int64): Código do título no SELIC.
        - data_base (Date): Data base de emissão do título.
        - data_vencimento (Date): Data de vencimento do título.
        - dias_uteis (Int64): Dias úteis entre referência e vencimento.
        - prazo_medio (Float64): Prazo médio do título em dias corridos.
        - duration (Float64): Macaulay Duration do título (anos).
        - dv01 (Float64): Variação no preço para 1bp de taxa.
        - pu (Float64): Preço unitário (PU).
        - taxa_compra (Float64): Taxa de compra (decimal).
        - taxa_venda (Float64): Taxa de venda (decimal).
        - taxa_indicativa (Float64): Taxa indicativa (decimal).
        - taxa_di (Float64): Taxa de ajuste do DI Futuro interpolada pelo
            método flat forward.
        - taxa_zero (Float64): Taxa zero (zero cupom via bootstrap).
        - premio (Float64): prêmio, isto é, o spread sobre o DI.
        - premio_limpo (Float64): prêmio limpo sobre a curva DI.
        - rentabilidade (Float64): Rentabilidade da NTN-F sobre a curva DI.

    Examples:
        >>> from pyield import ntnf
        >>> df_ntnf = ntnf.dados("23-08-2024")  # doctest: +SKIP
    """
    df = _utils.obter_tpf(data, "NTN-F")
    if df.is_empty():
        return pl.DataFrame(schema=_SCHEMA_DADOS)

    # Adiciona duration, prazo_medio, dv01 e taxa_di
    df = df.with_columns(
        dias_uteis=du.contar_expr("data_referencia", "data_vencimento"),
        duration=duration_expr("data_referencia", "data_vencimento", "taxa_indicativa"),
    ).with_columns(
        prazo_medio=pl.col("duration"),
        dv01=dv01_expr("data_referencia", "data_vencimento", "taxa_indicativa"),
    )
    df = _utils.adicionar_taxa_di(df, data)

    # Busca dados de LTN para bootstrap das taxas spot
    df_ltn = _utils.obter_tpf(data, "LTN").select("data_vencimento", "taxa_indicativa")
    df_spots = taxas_zero(
        data_liquidacao=data,
        vencimentos_ltn=df_ltn["data_vencimento"],
        taxas_ltn=df_ltn["taxa_indicativa"],
        vencimentos_ntnf=df["data_vencimento"],
        taxas_ntnf=df["taxa_indicativa"],
    ).select("data_vencimento", "taxa_zero")
    df = df.join(df_spots, on="data_vencimento", how="left")

    # Busca curva DI para cálculo da rentabilidade
    df_di = di1.dados(data, inicio_mes=True)

    # Calcula prêmios e rentabilidade para cada vencimento
    df = df.with_columns(
        premio=pl.col("taxa_indicativa") - pl.col("taxa_di"),
        premio_limpo=premio_limpo_expr(
            data_liquidacao=data,
            data_vencimento="data_vencimento",
            taxa_ntnf="taxa_indicativa",
            vencimentos_di=df_di["data_vencimento"],
            taxas_di=df_di["taxa_ajuste"],
        ),
        rentabilidade=rentabilidade_expr(
            data_liquidacao=data,
            data_vencimento="data_vencimento",
            taxa_ntnf="taxa_indicativa",
            vencimentos_di=df_di["data_vencimento"],
            taxas_di=df_di["taxa_ajuste"],
        ),
    )

    return df.select(*_SCHEMA_DADOS)

datas_pagamento(data_liquidacao, data_vencimento)

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

As datas são exclusivas para a liquidação e inclusivas para o vencimento. Os pagamentos são semestrais, em 1º de janeiro e 1º de julho. No vencimento, o fluxo inclui o último cupom e a amortização do principal. O título NTN-F é determinado pela data 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.

required
data_vencimento DateLike

Data de vencimento.

required

Returns:

Type Description
Series

pl.Series: Série com as datas de pagamento entre a liquidação (exclusiva) e o vencimento (inclusivo). Retorna série vazia se o vencimento for menor ou igual à liquidação.

Notes

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

Examples:

>>> from pyield import ntnf
>>> ntnf.datas_pagamento("15-05-2024", "01-01-2027")
shape: (6,)
Series: 'datas_pagamento' [date]
[
    2024-07-01
    2025-01-01
    2025-07-01
    2026-01-01
    2026-07-01
    2027-01-01
]

A data de liquidação coincidente com um pagamento é exclusiva:

>>> ntnf.datas_pagamento("01-07-2024", "01-01-2027")
shape: (5,)
Series: 'datas_pagamento' [date]
[
    2025-01-01
    2025-07-01
    2026-01-01
    2026-07-01
    2027-01-01
]
Source code in pyield/tpf/titulos/ntnf.py
def datas_pagamento(
    data_liquidacao: DateLike,
    data_vencimento: DateLike,
) -> pl.Series:
    """
    Gera todas as datas de pagamento entre liquidação e vencimento.

    As datas são exclusivas para a liquidação e inclusivas para o vencimento.
    Os pagamentos são semestrais, em 1º de janeiro e 1º de julho. No
    vencimento, o fluxo inclui o último cupom e a amortização do principal. O
    título NTN-F é determinado pela data de vencimento. As datas são contratuais
    e não são ajustadas para dias úteis.

    Args:
        data_liquidacao (DateLike): Data de liquidação.
        data_vencimento (DateLike): Data de vencimento.

    Returns:
        pl.Series: Série com as datas de pagamento entre a liquidação (exclusiva)
            e o vencimento (inclusivo). Retorna série vazia se o vencimento
            for menor ou igual à liquidação.

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

    Examples:
        >>> from pyield import ntnf
        >>> ntnf.datas_pagamento("15-05-2024", "01-01-2027")
        shape: (6,)
        Series: 'datas_pagamento' [date]
        [
            2024-07-01
            2025-01-01
            2025-07-01
            2026-01-01
            2026-07-01
            2027-01-01
        ]

        A data de liquidação coincidente com um pagamento é exclusiva:

        >>> ntnf.datas_pagamento("01-07-2024", "01-01-2027")
        shape: (5,)
        Series: 'datas_pagamento' [date]
        [
            2025-01-01
            2025-07-01
            2026-01-01
            2026-07-01
            2027-01-01
        ]
    """
    return _utils.gerar_datas_pagamento(data_liquidacao, data_vencimento)

duration(data_liquidacao, data_vencimento, taxa)

Calcula a Macaulay duration de uma NTN-F em anos úteis.

Parameters:

Name Type Description Default
data_liquidacao DateLike

Data de liquidação para o cálculo.

required
data_vencimento DateLike

Data de vencimento do título.

required
taxa float | str

TIR anual efetiva, em decimal, na base de 252 dias úteis, usada para descontar os fluxos. Aceita também percentual explícito: "5.75%" ou "5,75%".

required

Returns:

Name Type Description
float float

Macaulay duration em anos úteis. Retorna NaN se inválido.

Notes

A Macaulay duration \(D\), em anos de 252 dias úteis, é:

\[ D = \frac{ \sum_{i=1}^{n}t_i\,\mathrm{FC}_i/(1+y)^{t_i}} {\mathrm{PU}} \]

onde:

  • \(\mathrm{FC}_i\): valor do pagamento \(i\), incluindo cupom e amortização quando aplicável.
  • \(\mathrm{DU}_i\): dias úteis entre liquidação e pagamento \(i\).
  • \(t_i = \mathrm{DU}_i/252\): prazo até o pagamento \(i\), em anos de 252 dias úteis.
  • \(n\): número de pagamentos restantes.
  • \(y\): TIR anual efetiva informada em taxa, em decimal, na base de 252 dias úteis.
  • \(\mathrm{PU}\): soma dos fluxos descontados pela TIR \(y\).

Os fluxos descontados não aplicam os arredondamentos e truncamentos do PU oficial.

Examples:

>>> from pyield import ntnf
>>> ntnf.duration("02-09-2024", "01-01-2035", "12.1785%")
6.32854218039796
Source code in pyield/tpf/titulos/ntnf.py
def duration(
    data_liquidacao: DateLike,
    data_vencimento: DateLike,
    taxa: float | str,
) -> float:
    r"""
    Calcula a Macaulay duration de uma NTN-F em anos úteis.

    Args:
        data_liquidacao (DateLike): Data de liquidação para o cálculo.
        data_vencimento (DateLike): Data de vencimento do título.
        taxa (float | str): TIR anual efetiva, em decimal, na base de 252 dias
            úteis, usada para descontar os fluxos.
            Aceita também percentual explícito: "5.75%" ou "5,75%".

    Returns:
        float: Macaulay duration em anos úteis. Retorna NaN se inválido.

    Notes:
        A Macaulay duration \(D\), em anos de 252 dias úteis, é:

        \[
        D = \frac{
        \sum_{i=1}^{n}t_i\,\mathrm{FC}_i/(1+y)^{t_i}}
        {\mathrm{PU}}
        \]

        onde:

        - \(\mathrm{FC}_i\): valor do pagamento \(i\), incluindo cupom e
          amortização quando aplicável.
        - \(\mathrm{DU}_i\): dias úteis entre liquidação e pagamento \(i\).
        - \(t_i = \mathrm{DU}_i/252\): prazo até o pagamento \(i\), em anos
          de 252 dias úteis.
        - \(n\): número de pagamentos restantes.
        - \(y\): TIR anual efetiva informada em `taxa`, em decimal, na
          base de 252 dias úteis.
        - \(\mathrm{PU}\): soma dos fluxos descontados pela TIR \(y\).

        Os fluxos descontados não aplicam os arredondamentos e truncamentos
        do PU oficial.

    Examples:
        >>> from pyield import ntnf
        >>> ntnf.duration("02-09-2024", "01-01-2035", "12.1785%")
        6.32854218039796
    """
    if isinstance(taxa, str):
        taxa = float(_utils.converter_taxa(taxa))
    if any_is_empty(data_liquidacao, data_vencimento, taxa):
        return float("nan")

    df_fluxos = fluxos_caixa(data_liquidacao, data_vencimento)
    if df_fluxos.is_empty():
        return float("nan")

    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())
    return duration

duration_expr(data_liquidacao, data_vencimento, taxa)

Cria expressão Polars para a duration da NTN-F.

O cálculo é aplicado linha a linha porque a duration depende dos fluxos de caixa do título.

Parameters:

Name Type Description Default
data_liquidacao Expr | str

Nome de coluna ou expressão Polars com a data de liquidação.

required
data_vencimento Expr | str

Nome de coluna ou expressão Polars com a data de vencimento.

required
taxa Expr | str

Nome de coluna ou expressão Polars com a TIR anual efetiva, em decimal, na base de 252 dias úteis.

required

Returns:

Type Description
Expr

pl.Expr: Expressão sem alias com a Macaulay duration em anos úteis.

Source code in pyield/tpf/titulos/ntnf.py
def duration_expr(
    data_liquidacao: pl.Expr | str,
    data_vencimento: pl.Expr | str,
    taxa: pl.Expr | str,
) -> pl.Expr:
    """Cria expressão Polars para a duration da NTN-F.

    O cálculo é aplicado linha a linha porque a duration depende dos fluxos de
    caixa do título.

    Args:
        data_liquidacao: Nome de coluna ou expressão Polars com a data de
            liquidação.
        data_vencimento: Nome de coluna ou expressão Polars com a data de
            vencimento.
        taxa: Nome de coluna ou expressão Polars com a TIR anual efetiva,
            em decimal, na base de 252 dias úteis.

    Returns:
        pl.Expr: Expressão sem alias com a Macaulay duration em anos úteis.
    """
    return pl.struct(
        _utils.coluna_ou_expr(data_liquidacao, "data_liquidacao"),
        _utils.coluna_ou_expr(data_vencimento, "data_vencimento"),
        _utils.coluna_ou_expr(taxa, "taxa"),
    ).map_elements(
        lambda s: duration(
            s["data_liquidacao"],
            s["data_vencimento"],
            s["taxa"],
        ),
        return_dtype=pl.Float64,
    )

dv01(data_liquidacao, data_vencimento, taxa)

Calcula o DV01 (Dollar Value of 01) de uma NTN-F em R$.

Representa a redução do PU teórico para um aumento de 1 bp (0,01 ponto percentual) na taxa, calculada pela diferença entre os preços antes e depois do aumento.

Parameters:

Name Type Description Default
data_liquidacao DateLike

Data de liquidação.

required
data_vencimento DateLike

Data de vencimento.

required
taxa float | Decimal | str

TIR anual efetiva, em decimal, na base de 252 dias úteis. Aceita também percentual explícito: "5.75%" ou "5,75%".

required

Returns:

Name Type Description
float float

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

Notes

Mantendo liquidação e vencimento fixos, os fluxos e seus prazos também ficam fixos. Nesse contexto, o preço teórico em função da TIR é:

\[ \mathrm{PU}(y) = \sum_{i=1}^{n}\frac{\mathrm{FC}_i}{(1+y)^{t_i}} \]

O \(\mathrm{DV01}\) é a redução do preço, em R$, para um aumento de 1 bp na TIR:

\[ \mathrm{DV01} = \mathrm{PU}(y) - \mathrm{PU}(y+0.0001) \]

onde:

  • \(\mathrm{FC}_i\): valor do pagamento \(i\), incluindo cupom e amortização quando aplicável.
  • \(\mathrm{DU}_i\): dias úteis entre liquidação e pagamento \(i\).
  • \(t_i = \mathrm{DU}_i/252\): prazo até o pagamento \(i\), em anos de 252 dias úteis.
  • \(n\): número de pagamentos restantes.
  • \(y\): TIR anual efetiva informada em taxa, em decimal, na base de 252 dias úteis.
  • \(0.0001\): aumento de 1 bp na TIR em decimal.

Os preços não aplicam os arredondamentos e truncamentos do PU oficial.

Examples:

>>> from pyield import ntnf
>>> ntnf.dv01("26-03-2025", "01-01-2035", "15.1375%")
0.39025157587195736
Source code in pyield/tpf/titulos/ntnf.py
def dv01(
    data_liquidacao: DateLike,
    data_vencimento: DateLike,
    taxa: float | Decimal | str,
) -> float:
    r"""
    Calcula o DV01 (Dollar Value of 01) de uma NTN-F em R$.

    Representa a redução do PU teórico para um aumento de 1 bp (0,01 ponto
    percentual) na taxa, calculada pela diferença entre os preços antes e depois
    do aumento.

    Args:
        data_liquidacao (DateLike): Data de liquidação.
        data_vencimento (DateLike): Data de vencimento.
        taxa (float | Decimal | str): TIR anual efetiva, em decimal, na base
            de 252 dias úteis.
            Aceita também percentual explícito: "5.75%" ou "5,75%".

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

    Notes:
        Mantendo liquidação e vencimento fixos, os fluxos e seus prazos
        também ficam fixos. Nesse contexto, o preço teórico em função da TIR é:

        \[
        \mathrm{PU}(y) = \sum_{i=1}^{n}\frac{\mathrm{FC}_i}{(1+y)^{t_i}}
        \]

        O \(\mathrm{DV01}\) é a redução do preço, em R$, para um aumento
        de 1 bp na TIR:

        \[
        \mathrm{DV01} = \mathrm{PU}(y) - \mathrm{PU}(y+0.0001)
        \]

        onde:

        - \(\mathrm{FC}_i\): valor do pagamento \(i\), incluindo cupom e
          amortização quando aplicável.
        - \(\mathrm{DU}_i\): dias úteis entre liquidação e pagamento \(i\).
        - \(t_i = \mathrm{DU}_i/252\): prazo até o pagamento \(i\), em anos
          de 252 dias úteis.
        - \(n\): número de pagamentos restantes.
        - \(y\): TIR anual efetiva informada em `taxa`, em decimal, na
          base de 252 dias úteis.
        - \(0.0001\): aumento de 1 bp na TIR em decimal.

        Os preços não aplicam os arredondamentos e truncamentos do PU oficial.

    Examples:
        >>> from pyield import ntnf
        >>> ntnf.dv01("26-03-2025", "01-01-2035", "15.1375%")
        0.39025157587195736
    """
    taxa = _utils.converter_taxa(taxa)
    if any_is_empty(data_liquidacao, data_vencimento, taxa):
        return float("nan")

    pu1 = _valor_presente(data_liquidacao, data_vencimento, taxa)
    pu2 = _valor_presente(data_liquidacao, data_vencimento, float(taxa) + 0.0001)
    return pu1 - pu2

dv01_expr(data_liquidacao, data_vencimento, taxa)

Cria expressão Polars para o DV01 da NTN-F.

O cálculo é aplicado linha a linha e reprifica o PU teórico para um aumento de 1 bp na taxa.

Parameters:

Name Type Description Default
data_liquidacao Expr | str

Nome de coluna ou expressão Polars com a data de liquidação.

required
data_vencimento Expr | str

Nome de coluna ou expressão Polars com a data de vencimento.

required
taxa Expr | str

Nome de coluna ou expressão Polars com a TIR anual efetiva, em decimal, na base de 252 dias úteis.

required

Returns:

Type Description
Expr

pl.Expr: Expressão sem alias com o DV01.

Source code in pyield/tpf/titulos/ntnf.py
def dv01_expr(
    data_liquidacao: pl.Expr | str,
    data_vencimento: pl.Expr | str,
    taxa: pl.Expr | str,
) -> pl.Expr:
    """Cria expressão Polars para o DV01 da NTN-F.

    O cálculo é aplicado linha a linha e reprifica o PU teórico para um
    aumento de 1 bp na taxa.

    Args:
        data_liquidacao: Nome de coluna ou expressão Polars com a data de
            liquidação.
        data_vencimento: Nome de coluna ou expressão Polars com a data de
            vencimento.
        taxa: Nome de coluna ou expressão Polars com a TIR anual efetiva,
            em decimal, na base de 252 dias úteis.

    Returns:
        pl.Expr: Expressão sem alias com o DV01.
    """
    return pl.struct(
        _utils.coluna_ou_expr(data_liquidacao, "data_liquidacao"),
        _utils.coluna_ou_expr(data_vencimento, "data_vencimento"),
        _utils.coluna_ou_expr(taxa, "taxa"),
    ).map_elements(
        lambda s: dv01(
            s["data_liquidacao"],
            s["data_vencimento"],
            s["taxa"],
        ),
        return_dtype=pl.Float64,
    )

fluxos_caixa(data_liquidacao, data_vencimento)

Gera os fluxos de caixa da NTN-F entre liquidação (exclusiva) e vencimento (inclusivo). Os fluxos incluem cupons e o pagamento final no vencimento.

Parameters:

Name Type Description Default
data_liquidacao DateLike

Data de liquidação (exclusiva).

required
data_vencimento DateLike

Data de vencimento do título.

required

Returns:

Type Description
DataFrame

pl.DataFrame: DataFrame com as colunas data_pagamento e valor_pagamento.

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 ntnf
>>> ntnf.fluxos_caixa("15-05-2024", "01-01-2027")
shape: (6, 2)
┌────────────────┬─────────────────┐
│ data_pagamento ┆ valor_pagamento │
│ ---            ┆ ---             │
│ date           ┆ f64             │
╞════════════════╪═════════════════╡
│ 2024-07-01     ┆ 48.80885        │
│ 2025-01-01     ┆ 48.80885        │
│ 2025-07-01     ┆ 48.80885        │
│ 2026-01-01     ┆ 48.80885        │
│ 2026-07-01     ┆ 48.80885        │
│ 2027-01-01     ┆ 1048.80885      │
└────────────────┴─────────────────┘
Source code in pyield/tpf/titulos/ntnf.py
def fluxos_caixa(
    data_liquidacao: DateLike,
    data_vencimento: DateLike,
) -> pl.DataFrame:
    """
    Gera os fluxos de caixa da NTN-F entre liquidação (exclusiva) e vencimento
    (inclusivo). Os fluxos incluem cupons e o pagamento final no vencimento.

    Args:
        data_liquidacao (DateLike): Data de liquidação (exclusiva).
        data_vencimento (DateLike): Data de vencimento do título.

    Returns:
        pl.DataFrame: DataFrame com as colunas `data_pagamento` e
            `valor_pagamento`.

    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 ntnf
        >>> ntnf.fluxos_caixa("15-05-2024", "01-01-2027")
        shape: (6, 2)
        ┌────────────────┬─────────────────┐
        │ data_pagamento ┆ valor_pagamento │
        │ ---            ┆ ---             │
        │ date           ┆ f64             │
        ╞════════════════╪═════════════════╡
        │ 2024-07-01     ┆ 48.80885        │
        │ 2025-01-01     ┆ 48.80885        │
        │ 2025-07-01     ┆ 48.80885        │
        │ 2026-01-01     ┆ 48.80885        │
        │ 2026-07-01     ┆ 48.80885        │
        │ 2027-01-01     ┆ 1048.80885      │
        └────────────────┴─────────────────┘
    """
    if any_is_empty(data_liquidacao, data_vencimento):
        return pl.DataFrame()
    # Normaliza datas de entrada
    liquidacao = cv.converter_datas(data_liquidacao)
    vencimento = cv.converter_datas(data_vencimento)

    # Obtém as datas de pagamento entre liquidação e vencimento
    serie_datas_pagamento = datas_pagamento(liquidacao, vencimento)

    # Retorna DataFrame vazio se não houver pagamentos (liquidação >= vencimento)
    if serie_datas_pagamento.is_empty():
        return pl.DataFrame(
            schema={"data_pagamento": pl.Date, "valor_pagamento": pl.Float64}
        )

    # Define o fluxo final no vencimento e os demais como cupom
    df = pl.DataFrame(
        data={"data_pagamento": serie_datas_pagamento},
    ).with_columns(
        pl.when(pl.col("data_pagamento") == vencimento)
        .then(VALOR_FINAL)
        .otherwise(VALOR_CUPOM)
        .alias("valor_pagamento")
    )

    return df

premio_limpo(data_liquidacao, data_vencimento, taxa_ntnf, vencimentos_di, taxas_di)

Calcula o spread líquido (prêmio limpo no jargão de mercado) da NTN-F sobre a curva DI.

A função determina o spread que iguala o valor presente dos fluxos ao preço do título. Interpola as taxas DI nas datas de pagamento e encontra o spread que zera a diferença de preços, retornado em decimal e conversível para bps.

Parameters:

Name Type Description Default
data_liquidacao DateLike

Data de liquidação para o cálculo.

required
data_vencimento DateLike

Data de vencimento do título.

required
taxa_ntnf float | str

TIR anual efetiva, em decimal, na base de 252 dias úteis. Aceita também percentual explícito: "5.75%" ou "5,75%".

required
vencimentos_di DatesLike

Vencimentos da curva DI.

required
taxas_di ArrayLike

Taxas DI anuais efetivas, em decimal, na base de 252 dias úteis, correspondentes aos vencimentos.

required

Returns:

Name Type Description
float float

Spread líquido em formato decimal (ex.: 0.0012 = 12 bps). Retorna NaN para entradas ausentes ou fluxos vazios. Também retorna NaN se a resolução numérica falhar.

Notes

O spread \(s\), adicionado a cada taxa DI anual em decimal, é a solução de:

\[ \sum_{i=1}^{n} \frac{\mathrm{FC}_i}{(1+\mathrm{DI}_i+s)^{t_i}} = \mathrm{PU} = \sum_{i=1}^{n} \frac{\mathrm{FC}_i}{(1+y)^{t_i}} \]

onde:

  • \(\mathrm{FC}_i\): valor do pagamento \(i\), incluindo cupom e amortização quando aplicável.
  • \(\mathrm{DU}_i\): dias úteis entre liquidação e pagamento \(i\).
  • \(t_i = \mathrm{DU}_i/252\): prazo até o pagamento \(i\), em anos de 252 dias úteis.
  • \(n\): número de pagamentos restantes.
  • \(y\): TIR anual efetiva informada em taxa_ntnf, em decimal, na base de 252 dias úteis.
  • \(\mathrm{DI}_i := \mathrm{DI}(t_i)\): taxa DI anual efetiva, em decimal, para o prazo do pagamento \(i\), obtida por interpolação flat_forward, na base de 252 dias úteis.

Para expressar o spread em pontos-base, multiplique por 10.000. Os preços são teóricos, sem os arredondamentos e truncamentos do PU oficial.

Examples:

Obs: apenas algumas taxas DI serão usadas no exemplo.

>>> exp_dates = ["2025-01-01", "2030-01-01", "2035-01-01"]
>>> taxas_di = [0.10823, 0.11594, 0.11531]
>>> spread = premio_limpo(
...     data_liquidacao="23-08-2024",
...     data_vencimento="01-01-2035",
...     taxa_ntnf=0.116586,
...     vencimentos_di=exp_dates,
...     taxas_di=taxas_di,
... )
>>> round(spread * 10_000, 2)  # Converte para bps para exibição
12.13
Source code in pyield/tpf/titulos/ntnf.py
def premio_limpo(  # noqa
    data_liquidacao: DateLike,
    data_vencimento: DateLike,
    taxa_ntnf: float | str,
    vencimentos_di: DatesLike,
    taxas_di: ArrayLike,
) -> float:
    r"""
    Calcula o spread líquido (prêmio limpo no jargão de mercado) da NTN-F sobre a curva DI.

    A função determina o spread que iguala o valor presente dos fluxos ao preço
    do título. Interpola as taxas DI nas datas de pagamento e encontra o spread
    que zera a diferença de preços, retornado em decimal e conversível para bps.

    Args:
        data_liquidacao (DateLike): Data de liquidação para o cálculo.
        data_vencimento (DateLike): Data de vencimento do título.
        taxa_ntnf (float | str): TIR anual efetiva, em decimal, na base de
            252 dias úteis.
            Aceita também percentual explícito: "5.75%" ou "5,75%".
        vencimentos_di (DatesLike): Vencimentos da curva DI.
        taxas_di (ArrayLike): Taxas DI anuais efetivas, em decimal, na base
            de 252 dias úteis, correspondentes aos vencimentos.

    Returns:
        float: Spread líquido em formato decimal (ex.: 0.0012 = 12 bps).
            Retorna NaN para entradas ausentes ou fluxos vazios.
            Também retorna NaN se a resolução numérica falhar.

    Notes:
        O spread \(s\), adicionado a cada taxa DI anual em decimal,
        é a solução de:

        \[
        \sum_{i=1}^{n}
        \frac{\mathrm{FC}_i}{(1+\mathrm{DI}_i+s)^{t_i}}
        = \mathrm{PU} = \sum_{i=1}^{n}
        \frac{\mathrm{FC}_i}{(1+y)^{t_i}}
        \]

        onde:

        - \(\mathrm{FC}_i\): valor do pagamento \(i\), incluindo cupom e
          amortização quando aplicável.
        - \(\mathrm{DU}_i\): dias úteis entre liquidação e pagamento \(i\).
        - \(t_i = \mathrm{DU}_i/252\): prazo até o pagamento \(i\), em anos
          de 252 dias úteis.
        - \(n\): número de pagamentos restantes.
        - \(y\): TIR anual efetiva informada em `taxa_ntnf`, em decimal,
          na base de 252 dias úteis.
        - \(\mathrm{DI}_i := \mathrm{DI}(t_i)\): taxa DI anual efetiva,
          em decimal, para o prazo do pagamento \(i\), obtida por
          interpolação `flat_forward`, na base de 252 dias úteis.

        Para expressar o spread em pontos-base, multiplique por 10.000. Os preços
        são teóricos, sem os arredondamentos e truncamentos do PU oficial.

    Examples:
        # Obs: apenas algumas taxas DI serão usadas no exemplo.
        >>> exp_dates = ["2025-01-01", "2030-01-01", "2035-01-01"]
        >>> taxas_di = [0.10823, 0.11594, 0.11531]
        >>> spread = premio_limpo(
        ...     data_liquidacao="23-08-2024",
        ...     data_vencimento="01-01-2035",
        ...     taxa_ntnf=0.116586,
        ...     vencimentos_di=exp_dates,
        ...     taxas_di=taxas_di,
        ... )
        >>> round(spread * 10_000, 2)  # Converte para bps para exibição
        12.13
    """
    if isinstance(taxa_ntnf, str):
        taxa_ntnf = float(_utils.converter_taxa(taxa_ntnf))
    if any_is_empty(
        data_liquidacao,
        data_vencimento,
        taxa_ntnf,
        vencimentos_di,
        taxas_di,
    ):
        return float("nan")

    if not isinstance(taxas_di, pl.Series):
        serie_taxas_di = pl.Series(taxas_di)
    else:
        serie_taxas_di = taxas_di

    interpolador_ff = ip.Interpolador(
        du.contar(data_liquidacao, vencimentos_di),
        serie_taxas_di,
        "flat_forward",
    )

    df = fluxos_caixa(data_liquidacao, data_vencimento)
    if df.is_empty():
        return float("nan")

    df = df.with_columns(
        dias_uteis_pagamento=du.contar(data_liquidacao, df["data_pagamento"]),
    ).with_columns(
        taxa_di_interpolada=interpolador_ff.interpolar_expr("dias_uteis_pagamento"),
    )

    anos_uteis_pagamento = df["dias_uteis_pagamento"] / 252
    preco_titulo = _valor_presente(data_liquidacao, data_vencimento, taxa_ntnf)
    fluxos_titulo = df["valor_pagamento"]
    di_interpolada = df["taxa_di_interpolada"]

    def diferenca_preco(spread: float) -> float:
        fluxos_descontados = (
            fluxos_titulo / (1 + di_interpolada + spread) ** anos_uteis_pagamento
        )
        return float(fluxos_descontados.sum()) - preco_titulo

    return _utils.encontrar_raiz(diferenca_preco)

premio_limpo_expr(data_liquidacao, data_vencimento, taxa_ntnf, vencimentos_di, taxas_di)

Cria expressão Polars para o prêmio limpo da NTN-F sobre a curva DI.

O cálculo é aplicado linha a linha porque o prêmio limpo depende dos fluxos de caixa do título, da interpolação da curva DI e da resolução de raiz. Falhas de resolução retornam NaN apenas na linha afetada. Outros erros são propagados.

Parameters:

Name Type Description Default
data_liquidacao DateLike

Data de liquidação para o cálculo.

required
data_vencimento Expr | str

Nome de coluna ou expressão Polars com a data de vencimento da NTN-F.

required
taxa_ntnf Expr | str

Nome de coluna ou expressão Polars com a TIR anual efetiva da NTN-F, em decimal, na base de 252 dias úteis.

required
vencimentos_di DatesLike

Datas de vencimento da curva DI.

required
taxas_di ArrayLike

Taxas DI anuais efetivas, em decimal, na base de 252 dias úteis, correspondentes aos vencimentos.

required

Returns:

Type Description
Expr

pl.Expr: Expressão sem alias com o prêmio limpo da NTN-F sobre a curva

Expr

DI.

Source code in pyield/tpf/titulos/ntnf.py
def premio_limpo_expr(
    data_liquidacao: DateLike,
    data_vencimento: pl.Expr | str,
    taxa_ntnf: pl.Expr | str,
    vencimentos_di: DatesLike,
    taxas_di: ArrayLike,
) -> pl.Expr:
    """Cria expressão Polars para o prêmio limpo da NTN-F sobre a curva DI.

    O cálculo é aplicado linha a linha porque o prêmio limpo depende dos fluxos
    de caixa do título, da interpolação da curva DI e da resolução de raiz.
    Falhas de resolução retornam NaN apenas na linha afetada. Outros erros
    são propagados.

    Args:
        data_liquidacao: Data de liquidação para o cálculo.
        data_vencimento: Nome de coluna ou expressão Polars com a data de
            vencimento da NTN-F.
        taxa_ntnf: Nome de coluna ou expressão Polars com a TIR anual efetiva
            da NTN-F, em decimal, na base de 252 dias úteis.
        vencimentos_di: Datas de vencimento da curva DI.
        taxas_di: Taxas DI anuais efetivas, em decimal, na base de 252 dias
            úteis, correspondentes aos vencimentos.

    Returns:
        pl.Expr: Expressão sem alias com o prêmio limpo da NTN-F sobre a curva
        DI.
    """
    return pl.struct(
        _utils.coluna_ou_expr(data_vencimento, "data_vencimento"),
        _utils.coluna_ou_expr(taxa_ntnf, "taxa_ntnf"),
    ).map_elements(
        lambda row: premio_limpo(
            data_liquidacao=data_liquidacao,
            data_vencimento=row["data_vencimento"],
            taxa_ntnf=row["taxa_ntnf"],
            vencimentos_di=vencimentos_di,
            taxas_di=taxas_di,
        ),
        return_dtype=pl.Float64,
    )

pu(data_liquidacao, data_vencimento, taxa)

Calcula o PU da NTN-F pela metodologia da STN para leilões primários.

O preço equivale ao valor presente dos fluxos descontados pela TIR informada.

Parameters:

Name Type Description Default
data_liquidacao DateLike

Data de liquidação para cálculo do preço.

required
data_vencimento DateLike

Data de vencimento do título.

required
taxa float | Decimal | str

TIR anual efetiva, em decimal, na base de 252 dias úteis. Aceita também percentual explícito: "5.75%" ou "5,75%". Antes do cálculo, é truncada em oito casas decimais (seis na forma percentual), descartando as casas excedentes sem arredondar.

required

Returns:

Name Type Description
Decimal Decimal

Preço da NTN-F truncado em seis casas decimais.

Notes

O PU teórico é:

\[ \mathrm{PU} = \sum_{i=1}^{n} \frac{\mathrm{FC}_i}{(1+y)^{t_i}} \]

onde:

  • \(\mathrm{FC}_i\): valor do pagamento \(i\), incluindo cupom e amortização quando aplicável.
  • \(\mathrm{DU}_i\): dias úteis entre liquidação e pagamento \(i\).
  • \(t_i = \mathrm{DU}_i/252\): prazo até o pagamento \(i\), em anos de 252 dias úteis.
  • \(n\): número de pagamentos restantes.
  • \(y\): TIR anual efetiva informada em taxa, em decimal, na base de 252 dias úteis.

Os fluxos e os prazos são determinados pela liquidação e pelo vencimento. No cálculo oficial, além do truncamento da TIR descrito em Args, cada prazo \(t_i\) é truncado em 14 casas decimais, cada fluxo descontado é arredondado em nove casas e a soma é truncada em seis.

References
  • Secretaria do Tesouro Nacional. Metodologia de Cálculo dos Títulos Públicos Federais Ofertados nos Leilões Primários. https://crdcj.github.io/PYield/referencias/metodologia-calculo-tpf-stn/
  • O cupom semestral é 48,81, que representa 10% a.a. com capitalização semestral e arredondamento para 5 casas, conforme a STN.

Examples:

>>> from pyield import ntnf
>>> ntnf.pu("05-07-2024", "01-01-2035", "11.921%")
Decimal('895.359254')
>>> ntnf.pu("01-07-2024", "01-01-2027", "10%")
Decimal('999.931303')
Source code in pyield/tpf/titulos/ntnf.py
def pu(
    data_liquidacao: DateLike,
    data_vencimento: DateLike,
    taxa: float | Decimal | str,
) -> Decimal:
    r"""
    Calcula o PU da NTN-F pela metodologia da STN para leilões primários.

    O preço equivale ao valor presente dos fluxos descontados pela TIR
    informada.

    Args:
        data_liquidacao (DateLike): Data de liquidação para cálculo do preço.
        data_vencimento (DateLike): Data de vencimento do título.
        taxa: TIR anual efetiva, em decimal, na base de 252 dias úteis.
            Aceita também percentual explícito: "5.75%" ou "5,75%".
            Antes do cálculo, é truncada em oito casas decimais (seis na
            forma percentual), descartando as casas excedentes sem arredondar.

    Returns:
        Decimal: Preço da NTN-F truncado em seis casas decimais.

    Notes:
        O PU teórico é:

        \[
        \mathrm{PU} = \sum_{i=1}^{n}
        \frac{\mathrm{FC}_i}{(1+y)^{t_i}}
        \]

        onde:

        - \(\mathrm{FC}_i\): valor do pagamento \(i\), incluindo cupom e
          amortização quando aplicável.
        - \(\mathrm{DU}_i\): dias úteis entre liquidação e pagamento \(i\).
        - \(t_i = \mathrm{DU}_i/252\): prazo até o pagamento \(i\), em anos
          de 252 dias úteis.
        - \(n\): número de pagamentos restantes.
        - \(y\): TIR anual efetiva informada em `taxa`, em decimal, na
          base de 252 dias úteis.

        Os fluxos e os prazos são determinados pela liquidação e pelo vencimento.
        No cálculo oficial, além do truncamento da TIR descrito em `Args`,
        cada prazo \(t_i\) é truncado em 14 casas decimais, cada fluxo
        descontado é arredondado em nove casas e a soma é truncada em seis.

    References:
        - Secretaria do Tesouro Nacional. Metodologia de Cálculo dos Títulos
          Públicos Federais Ofertados nos Leilões Primários.
          https://crdcj.github.io/PYield/referencias/metodologia-calculo-tpf-stn/
        - O cupom semestral é 48,81, que representa 10% a.a. com capitalização
          semestral e arredondamento para 5 casas, conforme a STN.

    Examples:
        >>> from pyield import ntnf
        >>> ntnf.pu("05-07-2024", "01-01-2035", "11.921%")
        Decimal('895.359254')
        >>> ntnf.pu("01-07-2024", "01-01-2027", "10%")
        Decimal('999.931303')
    """
    return truncar_decimal(_calcular_pu(data_liquidacao, data_vencimento, taxa), 6)

rentabilidade(data_liquidacao, data_vencimento, taxa_ntnf, vencimentos_di, taxas_di)

Calcula a rentabilidade de uma NTN-F sobre a curva DI.

Compara as taxas diárias equivalentes da TIR da NTN-F e da TIR de referência obtida pela curva DI, conforme o Anexo 2 de Dívida Pública: a experiência brasileira, do Tesouro Nacional. Interpola as taxas DI nas datas de pagamento, calcula o valor presente dos fluxos contratuais usando essas taxas e encontra a TIR única que reproduz esse preço de referência.

Parameters:

Name Type Description Default
data_liquidacao DateLike

Data de liquidação para o cálculo.

required
data_vencimento DateLike

Data de vencimento da NTN-F.

required
taxa_ntnf float | str

TIR anual efetiva da NTN-F, em formato decimal. Aceita também percentual explícito: "5.75%" ou "5,75%".

required
vencimentos_di DatesLike

Datas de vencimento da curva DI.

required
taxas_di ArrayLike

Taxas DI anuais efetivas, em formato decimal, correspondentes aos vencimentos.

required

Returns:

Name Type Description
float float

Razão entre as taxas diárias equivalentes. Por exemplo, 1.01 representa 101% da taxa diária equivalente de referência DI. Retorna NaN para entradas ausentes, fluxos vazios ou preço calculado NaN. Também retorna NaN se a resolução numérica falhar.

Examples:

>>> # Obs: apenas algumas taxas DI serão usadas no exemplo.
>>> exp_dates = ["2025-01-01", "2030-01-01", "2035-01-01"]
>>> taxas_di = [0.10823, 0.11594, 0.11531]
>>> rentabilidade(
...     data_liquidacao="23-08-2024",
...     data_vencimento="01-01-2035",
...     taxa_ntnf="11.6586%",
...     vencimentos_di=exp_dates,
...     taxas_di=taxas_di,
... )
1.0099602280683393
Notes

Preço de referência pela curva DI

O preço de referência é:

\[ P_{\mathrm{DI}} = \sum_{i=1}^{n} \frac{\mathrm{FC}_i}{(1+\mathrm{DI}_i)^{t_i}} \]

onde:

  • \(\mathrm{FC}_i\): valor do pagamento \(i\), incluindo cupom e amortização quando aplicável.
  • \(\mathrm{DU}_i\): dias úteis entre liquidação e pagamento \(i\).
  • \(t_i = \mathrm{DU}_i/252\): prazo até o pagamento \(i\), em anos de 252 dias úteis.
  • \(n\): número de pagamentos restantes.
  • \(\mathrm{DI}(t)\): taxa DI para o prazo \(t\), anual efetiva e em decimal, na base de 252 dias úteis.
  • \(\mathrm{DI}_i := \mathrm{DI}(t_i)\): taxa DI para o prazo do pagamento \(i\), obtida por interpolação flat_forward.

TIR equivalente da referência

A função resolve numericamente a TIR anual efetiva \(y_{\mathrm{DI}}\), em decimal, que desconta os mesmos fluxos ao preço de referência:

\[ \sum_{i=1}^{n} \frac{\mathrm{FC}_i}{(1+y_{\mathrm{DI}})^{t_i}} = P_{\mathrm{DI}} \]

Essa TIR é uma taxa única para todos os pagamentos, enquanto cada \(\mathrm{DI}_i\) corresponde ao prazo do pagamento \(i\).

Rentabilidade relativa

A rentabilidade relativa \(q\) é a razão entre as taxas diárias equivalentes:

\[ q = \frac{(1+y_{\mathrm{NTNF}})^{1/252}-1} {(1+y_{\mathrm{DI}})^{1/252}-1} \]

onde \(y_{\mathrm{NTNF}}\) é a TIR informada em taxa_ntnf.

O indicador compara taxas implícitas; não representa o retorno realizado entre compra e venda. Também não calibra um spread ou um multiplicador uniforme sobre os vértices da curva DI.

Os fluxos mantêm as datas contratuais, excluem pagamentos na liquidação e incluem o vencimento. O preço de referência não aplica os arredondamentos e truncamentos do PU oficial. Não há extrapolação na ponta longa da curva; pagamentos além do último vértice DI resultam em NaN.

Comparação com a LTN

A LTN possui um único pagamento. Nesse caso, a TIR equivalente da referência coincide com a taxa zero DI do vencimento, e a fórmula se reduz à utilizada por yd.ltn.rentabilidade. Na NTN-F, a TIR equivalente considera também os cupons intermediários.

Referência: Tesouro Nacional, Dívida Pública: a experiência brasileira, Parte 3, capítulo 2, Anexo 2, item 2, páginas 329–330: Prêmio das NTN-Fs.

Source code in pyield/tpf/titulos/ntnf.py
def rentabilidade(  # noqa
    data_liquidacao: DateLike,
    data_vencimento: DateLike,
    taxa_ntnf: float | str,
    vencimentos_di: DatesLike,
    taxas_di: ArrayLike,
) -> float:
    r"""
    Calcula a rentabilidade de uma NTN-F sobre a curva DI.

    Compara as taxas diárias equivalentes da TIR da NTN-F e da TIR de referência
    obtida pela curva DI, conforme o Anexo 2 de *Dívida Pública: a experiência
    brasileira*, do Tesouro Nacional. Interpola as taxas DI nas datas de pagamento,
    calcula o valor presente dos fluxos contratuais usando essas taxas e encontra
    a TIR única que reproduz esse preço de referência.

    Args:
        data_liquidacao (DateLike): Data de liquidação para o cálculo.
        data_vencimento (DateLike): Data de vencimento da NTN-F.
        taxa_ntnf (float | str): TIR anual efetiva da NTN-F, em formato decimal.
            Aceita também percentual explícito: "5.75%" ou "5,75%".
        vencimentos_di (DatesLike): Datas de vencimento da curva DI.
        taxas_di (ArrayLike): Taxas DI anuais efetivas, em formato decimal,
            correspondentes aos vencimentos.

    Returns:
        float: Razão entre as taxas diárias equivalentes. Por exemplo, 1.01
            representa 101% da taxa diária equivalente de referência DI.
            Retorna NaN para entradas ausentes, fluxos vazios ou preço calculado
            NaN. Também retorna NaN se a resolução numérica falhar.

    Examples:
        >>> # Obs: apenas algumas taxas DI serão usadas no exemplo.
        >>> exp_dates = ["2025-01-01", "2030-01-01", "2035-01-01"]
        >>> taxas_di = [0.10823, 0.11594, 0.11531]
        >>> rentabilidade(
        ...     data_liquidacao="23-08-2024",
        ...     data_vencimento="01-01-2035",
        ...     taxa_ntnf="11.6586%",
        ...     vencimentos_di=exp_dates,
        ...     taxas_di=taxas_di,
        ... )
        1.0099602280683393

    Notes:
        **Preço de referência pela curva DI**

        O preço de referência é:

        \[
        P_{\mathrm{DI}} = \sum_{i=1}^{n}
        \frac{\mathrm{FC}_i}{(1+\mathrm{DI}_i)^{t_i}}
        \]

        onde:

        - \(\mathrm{FC}_i\): valor do pagamento \(i\), incluindo cupom e
          amortização quando aplicável.
        - \(\mathrm{DU}_i\): dias úteis entre liquidação e pagamento \(i\).
        - \(t_i = \mathrm{DU}_i/252\): prazo até o pagamento \(i\), em anos
          de 252 dias úteis.
        - \(n\): número de pagamentos restantes.
        - \(\mathrm{DI}(t)\): taxa DI para o prazo \(t\), anual efetiva e
          em decimal, na base de 252 dias úteis.
        - \(\mathrm{DI}_i := \mathrm{DI}(t_i)\): taxa DI para o prazo do
          pagamento \(i\), obtida por interpolação `flat_forward`.

        **TIR equivalente da referência**

        A função resolve numericamente a TIR anual efetiva
        \(y_{\mathrm{DI}}\), em decimal, que desconta os mesmos fluxos ao
        preço de referência:

        \[
        \sum_{i=1}^{n}
        \frac{\mathrm{FC}_i}{(1+y_{\mathrm{DI}})^{t_i}}
        = P_{\mathrm{DI}}
        \]

        Essa TIR é uma taxa única para todos os pagamentos, enquanto cada
        \(\mathrm{DI}_i\) corresponde
        ao prazo do pagamento \(i\).

        **Rentabilidade relativa**

        A rentabilidade relativa \(q\) é a razão entre as taxas diárias
        equivalentes:

        \[
        q = \frac{(1+y_{\mathrm{NTNF}})^{1/252}-1}
        {(1+y_{\mathrm{DI}})^{1/252}-1}
        \]

        onde \(y_{\mathrm{NTNF}}\) é a TIR informada em `taxa_ntnf`.

        O indicador compara taxas implícitas; não representa o retorno realizado
        entre compra e venda. Também não calibra um spread ou um multiplicador
        uniforme sobre os vértices da curva DI.

        Os fluxos mantêm as datas contratuais, excluem pagamentos na liquidação
        e incluem o vencimento. O preço de referência não aplica os arredondamentos
        e truncamentos do PU oficial. Não há extrapolação na ponta longa da curva;
        pagamentos além do último vértice DI resultam em NaN.

        ??? note "Comparação com a LTN"

            A LTN possui um único pagamento. Nesse caso, a TIR equivalente da
            referência coincide com a taxa zero DI do vencimento, e a fórmula
            se reduz à utilizada por `yd.ltn.rentabilidade`. Na NTN-F, a TIR
            equivalente considera também os cupons intermediários.

        Referência: Tesouro Nacional, *Dívida Pública: a experiência brasileira*,
        Parte 3, capítulo 2, Anexo 2, item 2, páginas 329–330:
        [Prêmio das NTN-Fs](https://thot-arquivos.tesouro.gov.br/publicacao-anexo/4710).
    """
    if isinstance(taxa_ntnf, str):
        taxa_ntnf = float(_utils.converter_taxa(taxa_ntnf))
    if any_is_empty(
        data_liquidacao,
        data_vencimento,
        taxa_ntnf,
        vencimentos_di,
        taxas_di,
    ):
        return float("nan")

    if not isinstance(taxas_di, pl.Series):
        serie_taxas_di = pl.Series(taxas_di)
    else:
        serie_taxas_di = taxas_di

    df_fluxos = fluxos_caixa(data_liquidacao, data_vencimento)
    if df_fluxos.is_empty():
        return float("nan")

    interpolador_ff = ip.Interpolador(
        du.contar(data_liquidacao, vencimentos_di),
        serie_taxas_di,
        "flat_forward",
    )

    df = df_fluxos.with_columns(
        dias_uteis=du.contar(data_liquidacao, df_fluxos["data_pagamento"]),
    ).with_columns(
        anos_uteis=pl.col("dias_uteis") / 252,
        taxa_di=interpolador_ff.interpolar_expr("dias_uteis"),
    )

    preco_referencia = _utils.calcular_pv(
        fluxos_caixa=df["valor_pagamento"],
        taxas=df["taxa_di"],
        prazos=df["anos_uteis"],
    )

    if math.isnan(preco_referencia):
        return float("nan")

    def diferenca_preco(taxa: float) -> float:
        fluxos_descontados = df["valor_pagamento"] / (1 + taxa) ** df["anos_uteis"]
        return float(fluxos_descontados.sum()) - preco_referencia

    tir_di = _utils.encontrar_raiz(diferenca_preco)

    fator_ntnf = (1 + taxa_ntnf) ** (1 / 252)
    fator_di = (1 + tir_di) ** (1 / 252)
    if fator_di == 1:
        return float("inf") if fator_ntnf > 1 else 0.0

    rentabilidade = (fator_ntnf - 1) / (fator_di - 1)
    return rentabilidade

rentabilidade_expr(data_liquidacao, data_vencimento, taxa_ntnf, vencimentos_di, taxas_di)

Cria expressão Polars para a rentabilidade da NTN-F sobre a curva DI.

O cálculo é aplicado linha a linha porque a rentabilidade depende dos fluxos de caixa do título, da interpolação da curva DI e da resolução de raiz. Falhas de resolução retornam NaN apenas na linha afetada. Outros erros são propagados.

Parameters:

Name Type Description Default
data_liquidacao DateLike

Data de liquidação para o cálculo.

required
data_vencimento Expr | str

Nome de coluna ou expressão Polars com a data de vencimento da NTN-F.

required
taxa_ntnf Expr | str

Nome de coluna ou expressão Polars com a TIR anual efetiva da NTN-F, em decimal, na base de 252 dias úteis.

required
vencimentos_di DatesLike

Datas de vencimento da curva DI.

required
taxas_di ArrayLike

Taxas DI anuais efetivas, em decimal, na base de 252 dias úteis, correspondentes aos vencimentos.

required

Returns:

Type Description
Expr

pl.Expr: Expressão sem alias com a rentabilidade da NTN-F sobre a curva

Expr

DI.

Source code in pyield/tpf/titulos/ntnf.py
def rentabilidade_expr(
    data_liquidacao: DateLike,
    data_vencimento: pl.Expr | str,
    taxa_ntnf: pl.Expr | str,
    vencimentos_di: DatesLike,
    taxas_di: ArrayLike,
) -> pl.Expr:
    """Cria expressão Polars para a rentabilidade da NTN-F sobre a curva DI.

    O cálculo é aplicado linha a linha porque a rentabilidade depende dos fluxos
    de caixa do título, da interpolação da curva DI e da resolução de raiz.
    Falhas de resolução retornam NaN apenas na linha afetada. Outros erros
    são propagados.

    Args:
        data_liquidacao: Data de liquidação para o cálculo.
        data_vencimento: Nome de coluna ou expressão Polars com a data de
            vencimento da NTN-F.
        taxa_ntnf: Nome de coluna ou expressão Polars com a TIR anual efetiva
            da NTN-F, em decimal, na base de 252 dias úteis.
        vencimentos_di: Datas de vencimento da curva DI.
        taxas_di: Taxas DI anuais efetivas, em decimal, na base de 252 dias
            úteis, correspondentes aos vencimentos.

    Returns:
        pl.Expr: Expressão sem alias com a rentabilidade da NTN-F sobre a curva
        DI.
    """
    return pl.struct(
        _utils.coluna_ou_expr(data_vencimento, "data_vencimento"),
        _utils.coluna_ou_expr(taxa_ntnf, "taxa_ntnf"),
    ).map_elements(
        lambda row: rentabilidade(
            data_liquidacao=data_liquidacao,
            data_vencimento=row["data_vencimento"],
            taxa_ntnf=row["taxa_ntnf"],
            vencimentos_di=vencimentos_di,
            taxas_di=taxas_di,
        ),
        return_dtype=pl.Float64,
    )

taxa(data_liquidacao, data_vencimento, pu)

Calcula a TIR implícita de uma NTN-F a partir de um PU informado.

A função resolve numericamente a taxa que aproxima o PU informado pelo valor presente bruto dos fluxos. Como o valor presente não é arredondado nem truncado, o resultado não pretende reproduzir exatamente a taxa usada para gerar um PU canônico por pu().

Parameters:

Name Type Description Default
data_liquidacao DateLike

Data de liquidação.

required
data_vencimento DateLike

Data de vencimento.

required
pu float | Decimal

Preço unitário (PU) do título.

required

Returns:

Name Type Description
float float

TIR implícita em formato decimal, sem arredondamento. Retorna NaN para entradas ausentes, PU não positivo ou falha na resolução numérica.

Notes

A TIR implícita \(y\), anual efetiva, em decimal e na base de 252 dias úteis, é a solução de:

\[ \sum_{i=1}^{n} \frac{\mathrm{FC}_i}{(1+y)^{t_i}} = \mathrm{PU}_{\mathrm{informado}} \]

onde:

  • \(\mathrm{FC}_i\): valor do pagamento \(i\), incluindo cupom e amortização quando aplicável.
  • \(\mathrm{DU}_i\): dias úteis entre liquidação e pagamento \(i\).
  • \(t_i = \mathrm{DU}_i/252\): prazo até o pagamento \(i\), em anos de 252 dias úteis.
  • \(n\): número de pagamentos restantes.
  • \(\mathrm{PU}_{\mathrm{informado}}\): preço recebido em pu.

A soma representa o preço teórico, sem os arredondamentos e truncamentos do PU oficial.

Examples:

Exibe as taxas em formato decimal:

>>> from pyield import ntnf
>>> ntnf.taxa("13-03-2026", "01-01-2035", 820.995125)
0.1427430001649857
>>> ntnf.taxa("21-05-2008", "01-01-2014", 903.039091) * 100
13.661101023197176
Source code in pyield/tpf/titulos/ntnf.py
def taxa(
    data_liquidacao: DateLike,
    data_vencimento: DateLike,
    pu: float | Decimal,
) -> float:
    r"""
    Calcula a TIR implícita de uma NTN-F a partir de um PU informado.

    A função resolve numericamente a taxa que aproxima o PU informado pelo
    valor presente bruto dos fluxos. Como o valor presente não é arredondado
    nem truncado, o resultado não pretende reproduzir exatamente a taxa usada
    para gerar um PU canônico por ``pu()``.

    Args:
        data_liquidacao (DateLike): Data de liquidação.
        data_vencimento (DateLike): Data de vencimento.
        pu: Preço unitário (PU) do título.

    Returns:
        float: TIR implícita em formato decimal, sem arredondamento. Retorna
            NaN para entradas ausentes, PU não positivo ou falha na resolução
            numérica.

    Notes:
        A TIR implícita \(y\), anual efetiva, em decimal e na base de
        252 dias úteis, é a solução de:

        \[
        \sum_{i=1}^{n}
        \frac{\mathrm{FC}_i}{(1+y)^{t_i}}
        = \mathrm{PU}_{\mathrm{informado}}
        \]

        onde:

        - \(\mathrm{FC}_i\): valor do pagamento \(i\), incluindo cupom e
          amortização quando aplicável.
        - \(\mathrm{DU}_i\): dias úteis entre liquidação e pagamento \(i\).
        - \(t_i = \mathrm{DU}_i/252\): prazo até o pagamento \(i\), em anos
          de 252 dias úteis.
        - \(n\): número de pagamentos restantes.
        - \(\mathrm{PU}_{\mathrm{informado}}\): preço recebido em `pu`.

        A soma representa o preço teórico, sem os arredondamentos e truncamentos
        do PU oficial.

    Examples:
        Exibe as taxas em formato decimal:

        >>> from pyield import ntnf
        >>> ntnf.taxa("13-03-2026", "01-01-2035", 820.995125)
        0.1427430001649857
        >>> ntnf.taxa("21-05-2008", "01-01-2014", 903.039091) * 100
        13.661101023197176
    """
    if any_is_empty(data_liquidacao, data_vencimento, pu):
        return float("nan")

    pu_float = float(pu)
    if pu_float <= 0:
        return float("nan")

    def diferenca_preco(taxa_encontrada: float) -> float:
        return (
            _valor_presente(data_liquidacao, data_vencimento, taxa_encontrada)
            - pu_float
        )

    return _utils.encontrar_raiz(diferenca_preco)

taxas_zero(data_liquidacao, vencimentos_ltn, taxas_ltn, vencimentos_ntnf, taxas_ntnf, incluir_cupons=False)

Calcula as taxas spot (zero cupom) para NTN-F usando bootstrap.

O bootstrap determina as taxas spot a partir das TIRs dos títulos. O método resolve iterativamente as taxas que descontam os fluxos ao preço. Usa as LTNs (zero cupom) até o último vencimento LTN disponível. Após isso, calcula as taxas spot a partir das NTN-F.

Parameters:

Name Type Description Default
data_liquidacao DateLike

Data de liquidação para o cálculo.

required
vencimentos_ltn DatesLike

Datas de vencimento das LTNs usadas como vértices prefixados zero cupom.

required
taxas_ltn ArrayLike

Taxas das LTNs. Como a LTN é zero cupom, essas taxas são usadas diretamente como taxas zero no bootstrap.

required
vencimentos_ntnf DatesLike

Datas de vencimento das NTN-F usadas no bootstrap.

required
taxas_ntnf ArrayLike

TIRs das NTN-F correspondentes aos vencimentos informados.

required
incluir_cupons bool

Se True, inclui as datas de cupom (julho). Padrão False.

False

Returns:

Type Description
DataFrame

pl.DataFrame: DataFrame com colunas data_vencimento, dias_uteis e taxa_zero.

Output Columns
  • data_vencimento (Date): Data de vencimento.
  • dias_uteis (Int64): Dias úteis entre liquidação e vencimento.
  • taxa_zero (Float64): Taxa zero (zero cupom), em formato decimal.
Notes

Para cada NTN-F considerada no bootstrap, o preço teórico é:

\[ \mathrm{PU} = \sum_{i=1}^{n} \frac{\mathrm{FC}_i}{(1+y)^{t_i}} \]

onde:

  • \(\mathrm{FC}_i\): valor do pagamento \(i\), incluindo cupom e amortização quando aplicável.
  • \(\mathrm{DU}_i\): dias úteis entre liquidação e pagamento \(i\).
  • \(t_i = \mathrm{DU}_i/252\): prazo até o pagamento \(i\), em anos de 252 dias úteis.
  • \(n\): número de pagamentos restantes.
  • \(y\): TIR obtida por interpolação flat_forward das TIRs informadas para NTN-F.

Após o último vencimento LTN, o bootstrap encontra a taxa zero do pagamento final usando as taxas já determinadas para os pagamentos anteriores:

\[ r_n = \left( \frac{\mathrm{FC}_n} {\mathrm{PU}-\sum_{i=1}^{n-1}\mathrm{FC}_i/(1+r_i)^{t_i}} \right)^{1/t_n}-1 \]

onde \(r_i := r(t_i)\) é a taxa zero para o prazo do pagamento \(i\).

As taxas de entrada e de saída são anuais efetivas, em decimal, na base de 252 dias úteis. Para a LTN, a TIR coincide com a taxa zero, pois há apenas um pagamento. O preço teórico não aplica os arredondamentos e truncamentos do PU oficial.

Vencimentos menores ou iguais à liquidação são ignorados antes da construção dos interpoladores.

Examples:

>>> from pyield import ntnf, ltn
>>> import polars.selectors as cs
>>> df_ltn = ltn.dados("03-09-2024")
>>> df_ntnf = ntnf.dados("03-09-2024")
>>> curva = ntnf.taxas_zero(
...     data_liquidacao="03-09-2024",
...     vencimentos_ltn=df_ltn["data_vencimento"],
...     taxas_ltn=df_ltn["taxa_indicativa"],
...     vencimentos_ntnf=df_ntnf["data_vencimento"],
...     taxas_ntnf=df_ntnf["taxa_indicativa"],
... )
>>> curva.with_columns(cs.starts_with("taxa_") * 100)
shape: (6, 3)
┌─────────────────┬────────────┬───────────┐
│ data_vencimento ┆ dias_uteis ┆ taxa_zero │
│ ---             ┆ ---        ┆ ---       │
│ date            ┆ i64        ┆ f64       │
╞═════════════════╪════════════╪═══════════╡
│ 2025-01-01      ┆ 83         ┆ 10.8837   │
│ 2027-01-01      ┆ 584        ┆ 11.998105 │
│ 2029-01-01      ┆ 1083       ┆ 12.211302 │
│ 2031-01-01      ┆ 1584       ┆ 12.223069 │
│ 2033-01-01      ┆ 2088       ┆ 12.135544 │
│ 2035-01-01      ┆ 2587       ┆ 12.139796 │
└─────────────────┴────────────┴───────────┘
Source code in pyield/tpf/titulos/ntnf.py
def taxas_zero(  # noqa
    data_liquidacao: DateLike,
    vencimentos_ltn: DatesLike,
    taxas_ltn: ArrayLike,
    vencimentos_ntnf: DatesLike,
    taxas_ntnf: ArrayLike,
    incluir_cupons: bool = False,
) -> pl.DataFrame:
    r"""
    Calcula as taxas spot (zero cupom) para NTN-F usando bootstrap.

    O bootstrap determina as taxas spot a partir das TIRs dos títulos.
    O método resolve iterativamente as taxas que descontam os fluxos ao preço.
    Usa as LTNs (zero cupom) até o último vencimento LTN disponível. Após
    isso, calcula as taxas spot a partir das NTN-F.


    Args:
        data_liquidacao (DateLike): Data de liquidação para o cálculo.
        vencimentos_ltn (DatesLike): Datas de vencimento das LTNs usadas como
            vértices prefixados zero cupom.
        taxas_ltn (ArrayLike): Taxas das LTNs. Como a LTN é zero cupom, essas
            taxas são usadas diretamente como taxas zero no bootstrap.
        vencimentos_ntnf (DatesLike): Datas de vencimento das NTN-F usadas no
            bootstrap.
        taxas_ntnf (ArrayLike): TIRs das NTN-F correspondentes aos vencimentos
            informados.
        incluir_cupons (bool): Se True, inclui as datas de cupom (julho).
            Padrão False.

    Returns:
        pl.DataFrame: DataFrame com colunas `data_vencimento`, `dias_uteis`
            e `taxa_zero`.

    Output Columns:
        - data_vencimento (Date): Data de vencimento.
        - dias_uteis (Int64): Dias úteis entre liquidação e vencimento.
        - taxa_zero (Float64): Taxa zero (zero cupom), em formato decimal.

    Notes:
        Para cada NTN-F considerada no bootstrap, o preço teórico é:

        \[
        \mathrm{PU} = \sum_{i=1}^{n}
        \frac{\mathrm{FC}_i}{(1+y)^{t_i}}
        \]

        onde:

        - \(\mathrm{FC}_i\): valor do pagamento \(i\), incluindo cupom e
          amortização quando aplicável.
        - \(\mathrm{DU}_i\): dias úteis entre liquidação e pagamento \(i\).
        - \(t_i = \mathrm{DU}_i/252\): prazo até o pagamento \(i\), em anos
          de 252 dias úteis.
        - \(n\): número de pagamentos restantes.
        - \(y\): TIR obtida por interpolação `flat_forward` das TIRs
          informadas para NTN-F.

        Após o último vencimento LTN, o bootstrap encontra a taxa zero do
        pagamento final usando as taxas já determinadas para os pagamentos
        anteriores:

        \[
        r_n = \left(
        \frac{\mathrm{FC}_n}
        {\mathrm{PU}-\sum_{i=1}^{n-1}\mathrm{FC}_i/(1+r_i)^{t_i}}
        \right)^{1/t_n}-1
        \]

        onde \(r_i := r(t_i)\) é a taxa zero para o prazo do pagamento \(i\).

        As taxas de entrada e de saída são anuais efetivas, em decimal, na
        base de 252 dias úteis. Para a LTN, a TIR coincide com a taxa zero,
        pois há apenas um pagamento. O preço teórico não aplica os
        arredondamentos e truncamentos do PU oficial.

        Vencimentos menores ou iguais à liquidação são ignorados antes da
        construção dos interpoladores.

    Examples:
        >>> from pyield import ntnf, ltn
        >>> import polars.selectors as cs
        >>> df_ltn = ltn.dados("03-09-2024")
        >>> df_ntnf = ntnf.dados("03-09-2024")
        >>> curva = ntnf.taxas_zero(
        ...     data_liquidacao="03-09-2024",
        ...     vencimentos_ltn=df_ltn["data_vencimento"],
        ...     taxas_ltn=df_ltn["taxa_indicativa"],
        ...     vencimentos_ntnf=df_ntnf["data_vencimento"],
        ...     taxas_ntnf=df_ntnf["taxa_indicativa"],
        ... )
        >>> curva.with_columns(cs.starts_with("taxa_") * 100)
        shape: (6, 3)
        ┌─────────────────┬────────────┬───────────┐
        │ data_vencimento ┆ dias_uteis ┆ taxa_zero │
        │ ---             ┆ ---        ┆ ---       │
        │ date            ┆ i64        ┆ f64       │
        ╞═════════════════╪════════════╪═══════════╡
        │ 2025-01-01      ┆ 83         ┆ 10.8837   │
        │ 2027-01-01      ┆ 584        ┆ 11.998105 │
        │ 2029-01-01      ┆ 1083       ┆ 12.211302 │
        │ 2031-01-01      ┆ 1584       ┆ 12.223069 │
        │ 2033-01-01      ┆ 2088       ┆ 12.135544 │
        │ 2035-01-01      ┆ 2587       ┆ 12.139796 │
        └─────────────────┴────────────┴───────────┘
    """
    if any_is_empty(
        data_liquidacao,
        vencimentos_ltn,
        taxas_ltn,
        vencimentos_ntnf,
        taxas_ntnf,
    ):
        return pl.DataFrame()
    # 1. Converter e normalizar inputs para Polars
    liquidacao = cv.converter_datas(data_liquidacao)
    vencimentos_ltn = cv.converter_datas(vencimentos_ltn)
    vencimentos_ntnf = cv.converter_datas(vencimentos_ntnf)
    if not isinstance(taxas_ltn, pl.Series):
        serie_ltn_taxas = pl.Series(taxas_ltn).cast(pl.Float64)
    else:
        serie_ltn_taxas = taxas_ltn
    if not isinstance(taxas_ntnf, pl.Series):
        serie_ntnf_taxas = pl.Series(taxas_ntnf).cast(pl.Float64)
    else:
        serie_ntnf_taxas = taxas_ntnf

    vencimentos_ltn, serie_ltn_taxas = _filtrar_vertices_futuros(
        liquidacao,
        vencimentos_ltn,
        serie_ltn_taxas,
        "LTN",
    )
    vencimentos_ntnf, serie_ntnf_taxas = _filtrar_vertices_futuros(
        liquidacao,
        vencimentos_ntnf,
        serie_ntnf_taxas,
        "NTN-F",
    )
    if vencimentos_ltn.is_empty() or vencimentos_ntnf.is_empty():
        return pl.DataFrame(
            schema={
                "data_vencimento": pl.Date,
                "dias_uteis": pl.Int64,
                "taxa_zero": pl.Float64,
            }
        )

    # 2. Criar interpoladores (aceitam pl.Series diretamente)
    interpolador_ltn = ip.Interpolador(
        dias_uteis=du.contar(liquidacao, vencimentos_ltn),
        taxas=serie_ltn_taxas,
        metodo="flat_forward",
    )
    interpolador_ntnf = ip.Interpolador(
        dias_uteis=du.contar(liquidacao, vencimentos_ntnf),
        taxas=serie_ntnf_taxas,
        metodo="flat_forward",
    )

    # 3. Gerar todas as datas de pagamento até o último vencimento NTN-F
    ultimo_vencimento = vencimentos_ntnf.max()
    assert isinstance(ultimo_vencimento, dt.date)
    todas_datas_pagamento = datas_pagamento(liquidacao, ultimo_vencimento)

    # 4. Construir DataFrame inicial
    df = (
        pl.DataFrame({"data_vencimento": todas_datas_pagamento})
        .with_columns(dias_uteis=du.contar_expr(liquidacao, "data_vencimento"))
        .with_columns(
            anos_uteis=pl.col("dias_uteis") / 252,
            taxa_tir=interpolador_ntnf.interpolar_expr("dias_uteis"),
        )
    )

    # 5. Loop de bootstrap (iterativo por dependência sequencial)
    ultimo_vencimento_ltn = vencimentos_ltn.max()
    assert isinstance(ultimo_vencimento_ltn, dt.date)

    taxas_spot_resolvidas: list[float | None] = []
    mapa_spot: dict[dt.date, float | None] = {}

    for data_venc, dias_uteis_val, anos_uteis_val, tir_val in df.iter_rows():
        # Caso esteja antes (ou igual) ao último vencimento LTN: usar interpolador LTN
        if data_venc <= ultimo_vencimento_ltn:
            taxa_zero = interpolador_ltn(dias_uteis_val)
        else:
            # Datas de cupom (exclui último pagamento) para este vencimento
            datas_fluxo = datas_pagamento(liquidacao, data_venc)[:-1]
            taxa_zero = None
            if not datas_fluxo.is_empty():
                # Recupera taxas spot já solucionadas para estes cupons
                taxas_spot_fluxo = [mapa_spot[d] for d in datas_fluxo]
                periodos_fluxo = du.contar(liquidacao, datas_fluxo) / 252
                fluxos = [VALOR_CUPOM] * len(datas_fluxo)

                valor_presente_fluxo = _utils.calcular_pv(
                    fluxos_caixa=pl.Series(fluxos),
                    taxas=pl.Series(taxas_spot_fluxo),
                    prazos=periodos_fluxo,
                )

                preco_titulo = _valor_presente(liquidacao, data_venc, tir_val)
                fator_preco = VALOR_FINAL / (preco_titulo - valor_presente_fluxo)
                taxa_zero = fator_preco ** (1 / anos_uteis_val) - 1

        taxas_spot_resolvidas.append(taxa_zero)
        mapa_spot[data_venc] = taxa_zero

    # 6. Anexa a coluna taxa_zero
    df = df.with_columns(taxa_zero=pl.Series(taxas_spot_resolvidas, dtype=pl.Float64))

    # 7. Selecionar colunas finais
    df = df.select("data_vencimento", "dias_uteis", "taxa_zero")

    # 8. Remover cupons (Julho) se não solicitado
    if not incluir_cupons:
        df = df.filter(pl.col("data_vencimento").is_in(vencimentos_ntnf.implode()))

    return df

taxas_zero_forwards(data_liquidacao, vencimentos_ltn, taxas_ltn, vencimentos_ntnf, taxas_ntnf, incluir_cupons=False)

Calcula uma curva zero experimental conjunta de LTN e NTN-F.

Usa forwards constantes entre vencimentos, em base 252 dias úteis. Em vencimentos coincidentes, a LTN tem prioridade. Nos demais, a NTN-F determina o forward desde o vértice anterior, reproduzindo seu preço teórico. As taxas podem vir da ANBIMA ou ser fornecidas pelo usuário; não há consulta à rede. Este método é uma alternativa experimental a taxas_zero.

Parameters:

Name Type Description Default
data_liquidacao DateLike

Data de liquidação.

required
vencimentos_ltn DatesLike

Vencimentos das LTNs; aceita coleção vazia.

required
taxas_ltn ArrayLike

Taxas zero das LTNs em formato decimal.

required
vencimentos_ntnf DatesLike

Vencimentos das NTN-F; aceita coleção vazia.

required
taxas_ntnf ArrayLike

TIRs das NTN-F em formato decimal.

required
incluir_cupons bool

Inclui também as datas dos cupons das NTN-F selecionadas.

False

Returns:

Type Description
DataFrame

pl.DataFrame: União dos vencimentos futuros de LTN e NTN-F, ordenada. Retorna vazio com esquema quando não há títulos futuros.

Output Columns
  • data_vencimento (Date): Vértice da curva prefixada conjunta.
  • dias_uteis (Int64): Dias úteis desde a liquidação.
  • taxa_zero (Float64): Taxa zero nominal anual em formato decimal.

Raises:

Type Description
ValueError

Entradas inválidas, vencimentos duplicados na mesma família, prazos úteis coincidentes ou preço incompatível com a curva anterior.

RuntimeError

Falha na calibração numérica.

Notes

A prioridade da LTN vale apenas no mesmo vencimento, não em todo o intervalo até a última LTN. NTN-F entre duas LTNs participa da calibração. Antes do primeiro vértice, a taxa zero é constante. Use todos os vértices retornados com Interpolador(..., metodo="flat_forward") para preservar os descontos dos cupons. O retorno inclui LTNs mesmo sem NTN-F nessa data, diferentemente do método antigo.

A calibração usa os fluxos de ntnf.fluxos_caixa, DU/252 sem truncamento e valores presentes sem arredondamento, como no bootstrap de NTN-B. Portanto, o preço-alvo pode diferir ligeiramente de ntnf.pu, que aplica arredondamentos e truncamentos. As datas contratuais seguem a convenção de ntnf.fluxos_caixa. Vencimentos até a liquidação são ignorados.

Source code in pyield/tpf/titulos/_ntnf_bootstrap.py
def taxas_zero_forwards(  # noqa: PLR0913, PLR0917
    data_liquidacao: DateLike,
    vencimentos_ltn: DatesLike,
    taxas_ltn: ArrayLike,
    vencimentos_ntnf: DatesLike,
    taxas_ntnf: ArrayLike,
    incluir_cupons: bool = False,
) -> pl.DataFrame:
    """Calcula uma curva zero experimental conjunta de LTN e NTN-F.

    Usa forwards constantes entre vencimentos, em base 252 dias úteis.
    Em vencimentos coincidentes, a LTN tem prioridade. Nos demais, a NTN-F
    determina o forward desde o vértice anterior, reproduzindo seu preço teórico.
    As taxas podem vir da ANBIMA ou ser fornecidas pelo usuário; não há consulta
    à rede. Este método é uma alternativa experimental a ``taxas_zero``.

    Args:
        data_liquidacao: Data de liquidação.
        vencimentos_ltn: Vencimentos das LTNs; aceita coleção vazia.
        taxas_ltn: Taxas zero das LTNs em formato decimal.
        vencimentos_ntnf: Vencimentos das NTN-F; aceita coleção vazia.
        taxas_ntnf: TIRs das NTN-F em formato decimal.
        incluir_cupons: Inclui também as datas dos cupons das NTN-F selecionadas.

    Returns:
        pl.DataFrame: União dos vencimentos futuros de LTN e NTN-F, ordenada.
            Retorna vazio com esquema quando não há títulos futuros.

    Output Columns:
        - data_vencimento (Date): Vértice da curva prefixada conjunta.
        - dias_uteis (Int64): Dias úteis desde a liquidação.
        - taxa_zero (Float64): Taxa zero nominal anual em formato decimal.

    Raises:
        ValueError: Entradas inválidas, vencimentos duplicados na mesma família,
            prazos úteis coincidentes ou preço incompatível com a curva anterior.
        RuntimeError: Falha na calibração numérica.

    Notes:
        A prioridade da LTN vale apenas no mesmo vencimento, não em todo o
        intervalo até a última LTN. NTN-F entre duas LTNs participa da calibração.
        Antes do primeiro vértice, a taxa zero é constante. Use todos os vértices
        retornados com ``Interpolador(..., metodo="flat_forward")`` para preservar
        os descontos dos cupons. O retorno inclui LTNs mesmo sem NTN-F nessa data,
        diferentemente do método antigo.

        A calibração usa os fluxos de ``ntnf.fluxos_caixa``, DU/252 sem truncamento
        e valores presentes sem arredondamento, como no bootstrap de NTN-B.
        Portanto, o preço-alvo pode diferir ligeiramente de ``ntnf.pu``, que aplica
        arredondamentos e truncamentos. As datas contratuais seguem a convenção
        de ``ntnf.fluxos_caixa``. Vencimentos até a liquidação são ignorados.
    """
    from pyield.tpf.titulos.ntnf import datas_pagamento  # noqa: PLC0415

    liquidacao = cv.converter_datas(data_liquidacao)
    ltn = _preparar_titulos(liquidacao, vencimentos_ltn, taxas_ltn, "LTN")
    ntnf = _preparar_titulos(liquidacao, vencimentos_ntnf, taxas_ntnf, "NTN-F")
    titulos = (
        pl.concat([ltn, ntnf])
        .unique("data_vencimento", keep="first", maintain_order=True)
        .sort("data_vencimento")
    )
    esquema = {
        "data_vencimento": pl.Date,
        "dias_uteis": pl.Int64,
        "taxa_zero": pl.Float64,
    }
    if titulos.is_empty():
        return pl.DataFrame(schema=esquema)

    datas = titulos["data_vencimento"].to_list()
    prazos = du.contar(liquidacao, titulos["data_vencimento"]).to_list()
    if prazos[0] <= 0 or any(b <= a for a, b in zip(prazos, prazos[1:])):
        raise ValueError("Vértices devem ter prazos úteis positivos e distintos.")
    dias: list[int] = []
    taxas: list[float] = []
    cupons: set[dt.date] = set()
    for (vencimento, taxa, titulo), prazo in zip(titulos.iter_rows(), prazos):
        zero = (
            taxa
            if titulo == "LTN"
            else _calibrar_zero(liquidacao, vencimento, taxa, dias, taxas, prazo)
        )
        dias.append(prazo)
        taxas.append(zero)
        if incluir_cupons and titulo == "NTN-F":
            cupons.update(datas_pagamento(liquidacao, vencimento).to_list())
    curva = pl.DataFrame(dict(zip(esquema, [datas, dias, taxas])), schema=esquema)
    if not cupons:
        return curva
    datas_saida = pl.Series(sorted(set(datas) | cupons), dtype=pl.Date)
    dias_saida = du.contar(liquidacao, datas_saida)
    interpolar = Interpolador(dias, taxas, "flat_forward")
    return pl.DataFrame(
        {
            "data_vencimento": datas_saida,
            "dias_uteis": dias_saida,
            "taxa_zero": [interpolar(t) for t in dias_saida],
        },
        schema=esquema,
    )

vencimentos(data)

Busca os vencimentos de NTN-F disponíveis para a data de referência.

Parameters:

Name Type Description Default
data DateLike

Data da consulta.

required

Returns:

Type Description
Series

pl.Series: Série de datas de vencimento de NTN-F.

Examples:

>>> from pyield import ntnf
>>> ntnf.vencimentos("23-08-2024")
shape: (6,)
Series: 'data_vencimento' [date]
[
    2025-01-01
    2027-01-01
    2029-01-01
    2031-01-01
    2033-01-01
    2035-01-01
]
Source code in pyield/tpf/titulos/ntnf.py
def vencimentos(data: DateLike) -> pl.Series:
    """
    Busca os vencimentos de NTN-F disponíveis para a data de referência.

    Args:
        data (DateLike): Data da consulta.

    Returns:
        pl.Series: Série de datas de vencimento de NTN-F.

    Examples:
        >>> from pyield import ntnf
        >>> ntnf.vencimentos("23-08-2024")
        shape: (6,)
        Series: 'data_vencimento' [date]
        [
            2025-01-01
            2027-01-01
            2029-01-01
            2031-01-01
            2033-01-01
            2035-01-01
        ]
    """
    return dados(data)["data_vencimento"]