No mercado financeiro as taxas de juros mais comuns são as taxas de juros à vista. As taxas à vista representam os juros pagos do momento atual até uma data no futuro. Temos diversas taxas à vista, taxas de 1 mês, de 6 meses, de 1 ano, e assim por diante. Estas taxas começam na data atual e têm a vigência determinada pelo seu período.

Uma consequência direta das taxas à vista são as taxas a termo. As taxas a termo começam em um instante no futuro e terminam em um instante mais adiante no futuro. Confuso, não? Vamos considerar duas taxas à vista, de 1 mês e 2 meses. Ao fim do período de 1 mês, podemos pegar outra taxa de 1 mês, dessa maneira, ao fim do segundo mês temos a composição de duas taxas de 1 mês. Este resultado pode ser superior ou inferior ao resultado de 2 meses, porque ao passar do primeiro mês as condições podem ter se alterado e a nova taxa de 1 mês pode ser melhor ou pior do que a do primeiro mês. Agora, se essa taxa a termo de 1 mês fosse negociada no momento atual, ela deve ser tal que composta com a taxa à vista de 1 mês, deve ser igua a taxa à vista de 2 meses. Porque caso contrário, gera oportunidade de arbitragem onde temos alternativas de investimento sem risco e equivalentes que apresentam resultados diferentes. Daí vem a relação de não arbitragem de taxas de juros

\[ (1 + r_{2M})^{T_{2M}} = (1 + r_{1M})^{T_{1M}} \cdot (1 + r_{1M-2M})^{T_{1M-2M}} \]

Neste exemplo com taxas de 1 e 2 meses.

Isso pode ser estendido para taxas diárias. Por exemplo, a estrutura a termo de taxas de juros (ETTJ) obtida dos contratos de Futuro de DI1 negociados na B3 representam a dinâmica do CDI da data atual até datas no futuro. Usando interpolação, podemos obter na curva as taxas de juros à vista para qualquer prazo e a partir dessas taxas calcular a estrutura a termo de taxas a termo diárias. É como se tivessemos todos os CDIs da data atual até datas futuras, que estão em conformidade com as taxas de juros praticadas na estrutura a termo de juros.

Vamos construir a curva de taxas a termo. Para fazer isso vamos começar importando os pacotes e configurando o calendário padrão em bizdays.

library(glue)
library(xml2)
library(stringr)
library(bizdays)
library(tidyverse)
library(tidyr)

bizdays.options$set(default.calendar="Brazil/ANBIMA")

Trago aqui uma função que baixa as curvas da B3 (ditas taxas referenciais). o argumento ticker padrão baixa a curva PRE, que é a curva obtida dos contratos Futuros de DI1, que negociam as expectativas de CDI para prazos futuros.

get_curve <- function (refdate, ticker="PRE") {
  refdate <- as.Date(refdate)
  url <- "http://www2.bmf.com.br/pages/portal/bmfbovespa/lumis/lum-taxas-referenciais-bmf-ptBR.asp"
  url <- glue("{url}?Data={format(refdate, '%d/%m/%Y')}&Data1={format(refdate, '%Y%m%d')}&slcTaxa={ticker}")
  doc <- read_html(url)
  tbl <- xml_find_all(doc, "//table[contains(@id, 'tb_principal1')]")
  num <- xml_find_all(tbl[[1]], "td") %>%
    xml_text() %>%
    str_trim() %>%
    str_replace(",", ".") %>%
    as.numeric()

  dc <- num[c(TRUE, FALSE, FALSE)]
  tx_252 <- num[c(FALSE, TRUE, FALSE)]

  terms <- bizdayse(refdate, dc)
  ix <- (terms %% 21) == 0
  terms <- c(terms[1], terms[ix])
  rates <- c(tx_252[1], tx_252[ix])/100
  log_pu <- log((1 + rates)^(terms/252))
  rate <- function(pu, term) pu^(252/term) - 1

  log_price_interpolator <- approxfun(terms, log_pu, method="linear")
  function (term) {
    pu <- exp(log_price_interpolator(term))
    rate(pu, term)*100
  }
}

Essa função retorna uma função (closure) que recebe como parâmetro um vetor com os prazos, em dias úteis, e retorna um vetor com as taxas associadas aos prazos fornecidos.

pre <- get_curve("2020-11-13")
pre(c(1, seq(21, 252, 21)))
##  [1] 1.900000 1.920000 2.006648 2.035547 2.050000 2.265595 2.409578 2.512547
##  [9] 2.589842 2.650000 2.820000 2.980000 3.189449

Pela função get_curve vamos obter as taxas à vista para todos os dias, em seguida vamos aplicar a relação de não arbitragem diária para calcular as taxas a termo diárias.

terms_ <- seq(1, 252, 1)
curve_ <- pre(terms_)
fwd_curve_ <- numeric(length(terms_))

fwd_curve_[1] <- curve_[1]
for (t_ in terms_[-1]) {
  f_short <- (1 + curve_[t_-1]/100)^((t_-1)/252)
  f_long <- (1 + curve_[t_]/100)^(t_/252)
  fwd_curve_[t_] <- ((f_long/f_short)^(252) - 1) * 100
}

Criamos 3 vetores: terms_ com os prazos da curva, curve_ com as taxas à vista e fwd_curve_ com as taxas a termo diárias.

Vamos visualizar estas duas curvas:

data.frame(
  Days = terms_,
  Rate = curve_,
  FwdRate = fwd_curve_
) %>%
  gather(Type, Rates, -Days) %>%
  ggplot(aes(x = Days, y = Rates, group = Type, colour = Type)) +
  geom_line(size=1)

A curva de taxas a termo se descola da curva de taxas à vista, mas podemos verificar que a relação de não arbitragem é obedecida. Vamos pegar todas as taxas em fwd_curve_ e compor e obter a taxa implicita.

(prod( (1 + fwd_curve_/100)^(1/252) ) - 1) * 100
## [1] 3.189449

Note que é equivalente a taxa de 252 dias, que é a última taxa de curve_.

tail(curve_, 1)
## [1] 3.189449

Esse descolamento da curva de taxas a termo é decorrente do formato da estrutura a termo, pois a curva à vista está pagando taxas de 2% até um pouco antes do dia 100, e para conseguir entregar 3,2% em 1 ano (252 dias úteis) é necessário que a taxa a termo diária (em linhas gerais, o CDI) cresça muito. Afinal, a taxa à vista é um acumulado das taxas diárias. Este comportamento traz implicitamente diversos riscos, pois indica que se as taxas à vista estão corretas, a taxa básica de juros vai precisar sofrer correções. Um exercício interessante é fazer com que os instantes de saltos na curva de juros a termo sejam as datas de reuniões do COPOM, assim teríamos um possível cenário de mudanças na taxa básica de juros implícito na estrutura a termo de taxas de juros.