Criando um objeto eficiente para simular caminhos de decisões do COPOM [Python]
Um problema que sempre tive se relaciona intimamente com a taxa de juros, é o de simular caminhos possíveis para as decisões do COPOM. Apesar de serem infinitos cenários possíveis para determinação do caminho de juros, é importante simular alguns (ou vários) para determinação dos cenários de choque do portfólio ou para simular os caminhos mais prováveis em estratégias de opção de IDI, por exemplo.
Otimizando o cálculo do caminho do DI Over
Uma prática muito comum no mercado é a de definir todas as datas entre o dia de análise e o dia de vencimento do futuro desejado e calcular, diariamente, qual seria o DI Over, acumulando todos os fatores de juros diários até o vencimento, chegando no valor de carrego e, por construção, na taxa de juros equivalente do período.
Qual o problema desta metodologia?
Vamos supor que, se utilizando desta metodologia, alguém queira simular um cenário de hoje (02/09/2022) até o vencimento do F24 (02/01/2024), para que a conta pudesse ser executada, seriam necessários 331 (du) fatores diários para serem acumulados até a data em questão, fazendo com que diariamente fosse necessário analisar as decisões de juros que antecederam a data, para ver qual seria o DI Over.
Como proceder, então?
Uma maneira mais eficiente para proceder com as contas é a de quebrar os fatores de juros por setores, ou seja, ignorando a existência de reuniões do COPOM extraordinárias, é possível criar uma simulação mais efetiva e rápida para se chegar no valor final de juros.
O cálculo de caminhos do COPOM já foi discutido no meu post “DI1 e COPOM — Uma proposta para gestão do risco de taxa de juros no curto prazo” e aqui aproveito somente para sinalizar a equação mais importante sobre esta relação, que é discutida no post em questão:
Nesta equação é nítido que, se a esperança diária do DI Over só sofre alterações nas datas de COPOM, o produtório fica constante por alguns intervalos de tempo predefinidos, sendo eles os momentos intermediários entre reuniões do COPOM.
Desta forma, caso o mesmo cenário (F24) fosse analisado desta ótica, a quantidade de contas, que era 331, decairia para 12, visto que seriam 11 cortes de data por COPOM (11 reuniões até F24) + 1 corte temporal, da última reunião do COPOM até o vencimento do futuro.
Simulação visualmente demonstrada
Na tabela abaixo, o DI Over indicato é o que estava vigorando até a data em questão, por isso o “lag” em um período. Esta simulação, por curiosidade, geraria um F24 efetivo de 13,345% no período, contra 12,875% do registrado no ajuste de 01/09/2022.
Os degraus demonstram os momentos em que a taxa DI Over fica flat, até a próxima decisão do COPOM.
A classe de simulações
O objetivo, como mencionado acima, é fazer uma classe capaz de gerar simulações de maneira eficiente. Para o post consegui fazer um código capaz de precificar até 20k de cenários por segundo para o futuro F24, como demonstro abaixo em um breve print:
1. Função simples feita com Numpy
2. Programação orientada ao objeto
De fato entre os métodos 1 e 2 existem uma diferença de performance ao redor de 8% (pior no objeto, que possui mais características e definições, além de propriedades mais interessantes).
Caso seja feita uma requisição massiva de simulações, por exemplo, 1 milhão de cenários, a diferença será de 4,2 segundos, então nada que seja tão relevante em tempo tangível, mas deixarei as duas versões aqui apresentadas.
Propriedades interessantes do objeto criado
Ao criar o objeto de SimulaCenariosDI, as propriedades mais interessantes são:
- Caso seja fornecido ‘download_probabilities’ = True, irá se utilizar do código presente neste post para calcular a matriz de transição e irá inserir, para cada uma das simulações solicitadas posteriormente, a probabilidade do caminho ocorrer, isso prejudica bastante o tempo de processamento, vai para 4500 simulações por segundo
- self._possible_copom, gera todos os cenários possíveis de ocorrência dado um intervalo de possíveis COPOM e número de reuniões entre o início e o fim da simulação
- self._cdv01, gera as exposições dos vencimentos solicitados à cada um dos COPOM fornecidos ao objeto, seguindo metodologia presente neste post
Exemplo de self._cdv01:
- self._fator, calcula resultados para vários cenário e vencimento fornecido na função
- self._fator_multiplo, calcula resultados para vários cenários e vencimentos fornecidos simultaneamente, ao ser chamado, o objeto por padrão se utilizará deste método para gerar os cenários pedidos para os dados vencimentos
Exemplo: Fornecendo 10 cenários possíveis para o código e pedindo para simular os vencimentos abaixo,
- Caso download_probabilities = True:
— self._fator e seus derivados passarão a incorporar na sua resposta a probabilidade do caminho
— self.transition_matrix será a matriz de transição
— self.obj_transition_matrix será o objeto da classe de Matriz de Transição, optei por não colocar aqui um ‘super’ para não comprometer possíveis futuras funções que possam ser incorporadas na classe de simulação de caminhos do DI
Referências Bibliográficas
Diferenciação numérica, Autor: Wagner Bonat, UFPR
[Neto et. al. (2019)] José Monteiro Varanda Neto, de Souza Santos, José Carlos, Mello, e Eduardo Morato. O mercado de renda fixa no Brasil: conceitos, precificação e risco. Saint Paul, 2019
[Santos e Silva (2017)] José Santos e Marcos Silva. Derivativos e renda fixa: Teoria e aplicações ao mercado brasileiro, volume 1. Atlas, São Paulo, 2017.
Simon, C. P., Blume, L., & Doering, C. I. (2004). Matemática para economistas. Bookman.
Silva e Takarabe (2015). Impacto das Reuniões do Copom no Preço de Opções de Índice de Taxas de Juros (IDI). Resenha da Bolsa Ago/2015
Guerra, Leandro. Introdução — Cadeias de Markov (Markov Chains) — Outspoken Market. Disponível no Youtube, no canal Outspoken Market