Primeira vez aqui? Seja bem vindo e cheque o FAQ!
x

Matemática Financeira com Python

0 votos
43 visitas
perguntada Out 16 em Finanças por Felipe Yudi (21 pontos)  
editado Out 31 por Felipe Yudi

O Exercício pede para que os cálculos do capítulo 1 do livro Financial Modeling - Beninga, que são originalmente feitos em Excel, sejam reproduzidos em Python. Tais cálculos são todos de matemática financeira básica, e são apresentados por meio de tabelas do Excel. Apresentaremos as contas feitas ao longo do capítulo, bem como as tabelas, no corpo da resposta. Quando necessário, também apresentaremos as fórmulas utilizadas.

Compartilhe

3 Respostas

0 votos
respondida Out 16 por Felipe Yudi (21 pontos)  
editado Out 22 por Felipe Yudi

Parte 1:

Usaremos aqui a estrutura de dataframes por meio do módulo pandas, além de alguns outros módulos em situações específicas.
Em Python, dataframes são uma forma de organizar informações. São semelhantes a tabelas e podemos utilizar o módulo pandas para lidar com eles. Contudo, na maioria dos casos, os utilizaremos apenas para organizar as informações obtidas após a realização dos cálculos, uma vez que realizar cálculos dentro de uma estrutura de dataframes torna a execução do programa bastante lenta.
Antes de começar a resolver o exercício propriamente dito, segue um breve tutorial das funções básicas do módulo pandas:

Dataframes em panda são tabelas com linhas e colunas. Podemos criar um dataframe importando de bases de dados SQL, CVS e Excel. Também podemos criar dataframes a partir de listas, dicionários, listas de dicionários etc.

Vamos criar uma dataframe usando listas:

import pandas as pd

lst = ["salário", "horas", "idade"] #Criando uma lista.

df1 = pd.DataFrame(lst) #Cria o dataframe a partir da lista

print(df1)

#Vamos agora criar um dataframe a partir de um dicionário:

data2 = {"Nome": ["José", "Maria", "João", "Ana"], 
        "Idade": [23, 24, 54, 14]}
df2 = pd.DataFrame(data2)
print(df2)

Podemos fazer operações matemáticas nas linhas e colunas de uma tabela. Para isso, vamos criar uma tabela. Uma maneira de fazer isso é definindo uma lista de listas e criar uma tabela vazia, dizendo ao panda para preenchê-la com os elementos da lista:

import pandas as pd

lst = [[1,2,3,4], 
       [5,6,7,8], 
       [9,10,11,12], 
       [13,14,15,16], 
       [17,18,19,20]]

df = pd.DataFrame(lst, index = ["1", "2", "3", "4", "5"], 
                  columns = ["A", "B", "C", "D"])
#Acima estamos criando uma tabela 5x4 vazia e dizendo para preencher com os 
#elementos da lista.
print(df)

#Multiplicação de uma tabela por outra:É feita célula por célula:  
dfsqr = df*df
print(dfsqr)

#Multiplicação de uma tabela por um escalar: Ocorre da maneira intuitiva:
dezdf = df*10
print(dezdf)

#Soma de tabelas: Ocorre da maneira intuitiva:
df2 = df+df
print(df2)

#Soma por escalar: Soma o escalar a cada célula:
dfmais = df+10
print(dfmais)

Caso desejemos selecionar uma coluna em específico:

import pandas as pd

data = {"Nome": ["José", "Maria", "João", "Ana"], 
        "Idade": [23, 24, 54, 45],
        "Endereço": ["Rua 1", "Rua 2", "Rua 3", "Rua 4"],
         "Renda": [123, 2345, 235, 568453]}
df = pd.DataFrame(data)

print(df) #Imprime a tabela toda

print("############")

#Selecionando uma coluna desejada:
print(df[["Nome", "Renda"]]) #Imprime apenas as colunas indicadas.

#ou:
colnome = df[['Nome']]
print(colnome)

#Adicionar uma nova coluna:
educ = [12, 8, 13, 22] #Definico a coluna a ser adicionada

df["educ"] = educ #Incorporando a coluna

print(df) 

#Deletando uma coluna:
df1 = df.drop(columns = ['Idade']) #Deleta as colunas selecionadas. 

print(df1)

Vamos agora começar o exercício. Para isso, vamos definir funções para realizar os cálculos desejados. A maioria das funções aqui não retornará nenhum valor (i.e., não usaremos “return” dentro da função). Elas só realizarão cálculos, montarão tabelas e imprimirão resultados. Tais funções são chamadas de procedimentos (procedure em inglês).
É recomendável que se examine um código por vez, já que de forma geral, o código seguinte usa conceitos do anterior.
Abra um novo script e importe os seguintes módulos:

import pandas as pd
import numpy as np
import math
import datetime
from sympy import Symbol
from matplotlib import pyplot as plt

A primeira tarefa é calcular o valor presente do fluxo de caixa como na tabela a seguir:

A imagem será apresentada aqui.

Lembre-se que o valor presente é obtido fazendo \(PV = \frac{\sum_{t=1}^{N}CF_{t}}{(1+r)^{t}}\)

Em Python, isso pode ser feito da seguinte maneira:

def PV01(lst, tx):
    "Argumentos: lista de listas com uma data e o fluxo e taxa de juros"
    #Convertendo a lista em dataframe
    df = pd.DataFrame(lst, index = ["1", "2", "3", "4", "5"], 
                  columns = ["Ano", "Fluxo de Caixa"])

    print(df)

    #Calculando o valor presente 
    df["VP"] = (df["Fluxo de Caixa"])/((1+tx)**df["Ano"])
    print(df)

    #Soma dos valores presentes
    SPV = df["VP"].sum()
    print("Valor Presente: ", SPV)

#Criando uma lista com os dados.
#A lista deve ser composta por sublistas
#contendo a data e o fluxo na data.
lst01 = [[1, 100], 
       [2, 100], 
       [3, 100], 
       [4, 100], 
       [5, 100]]

#Definindo a taxa de juros
tx01 = 0.1

PV01(lst01,tx01)

Em seguida é apresentada a diferença entre as funções PV e NPV de Excel. Isso não é um problema para nós. Os cálculos feitos na figura a seguir são reproduzidos em Python da mesma maneira:

A imagem será apresentada aqui.

def PV02(lst, tx):
    "Argumentos: lista de listas com uma data e o fluxo e taxa de juros"
    #Convertendo a lista em dataframe
    df = pd.DataFrame(lst, index = ["1", "2", "3", "4", "5"], 
                      columns = ["Ano", "Fluxo de Caixa"])
    print(df)

    #Calculando o valor presente 
    df["VP"] = (df["Fluxo de Caixa"])/((1+tx)**df["Ano"])
    print(df)

    #Soma dos valores presentes
    NPV = df["VP"].sum()
    print("Valor Presente: ", NPV)

#Criando uma lista com os dados.
#A lista deve ser composta por sublistas
#contendo a data e o fluxo na data.
lst02 = [[1, 100], 
       [2, 200], 
       [3, 300], 
       [4, 400], 
       [5, 500]]

#Definindo a taxa de juros
tx02 = 0.1

PV02(lst02, tx02)

A seguir, calcularemos o valor presente para um fluxo de caixa com um valor negativo:
A imagem será apresentada aqui.

def NPV03(lst, tx):
    "Argumentos: lista de listas com uma data e o fluxo e taxa de juros"
    #Convertendo a lista em dataframe
    df = pd.DataFrame(lst, 
                  columns = ["Ano", "Fluxo de Caixa"])
    print(df)

    #Calculando o valor presente 
    df["VP"] = (df["Fluxo de Caixa"])/((1+tx)**df["Ano"])
    print(df)

    #Soma dos valores presentes
    NPV = df["VP"].sum()
    print("Valor Presente: ", NPV)

#Criando uma lista com os dados.
#A lista deve ser composta por sublistas
#contendo a data e o fluxo na data.
lst03 = [[0,-250],
       [1, 100], 
       [2, 100], 
       [3, 100], 
       [4, 100], 
       [5, 100]]

#Definindo a taxa de juros
tx03 = 0.1

NPV03(lst03, tx03)

Em seguida, vamos calcular o valor presente de uma anuidade finita, que pode ser calculado com \(PV = C\left ( \frac{1-\frac{1}{(1+r)^{n}}}{r} \right )\)

A imagem será apresentada aqui.
Em Python:

def ANN04(c, r, lst):
    "Argumentos: "
    #Convertendo a lista em dataframe
    df = pd.DataFrame(lst, index = ["1", "2", "3", "4", "5"], 
                  columns = ["Ano", "Pagamento Anual"])

    #Calculando o valor presente
    df["VP"] = (df["Pagamento Anual"])/((1+r)**df["Ano"])
    print(df)

    #Soma dos valores presentes
    SPV = df["VP"].sum()
    print("Valor Presente: ", SPV)

#Definindo os parâmetros:
C_04 = 1000

#Criando uma lista com os dados.
#A lista deve ser composta por sublistas
#contendo a data e o fluxo na data.
lst_04 = [[1, C_04], 
       [2, C_04], 
       [3, C_04], 
       [4, C_04], 
       [5, C_04]]

ANN04(1000, 0.12, lst_04)

Já uma anuidade infinita pode ser calculada com \(PV = \frac{C}{r} \)

A imagem será apresentada aqui.

Em Python, claramente não é necessário utilizar a estrutura de dataframes:

def IANN05(c,r):
    "Argumentos: pagamento periódico e taxa de juros"
    VP = c/r
    print("Valor Presente: ", VP)

IANN05(1000,0.12)

O valor de uma anuidade crescente finita é calculado com \(PV = \frac{C\left (1-\left ( \frac{1+g}{1+r} \right )^{n} \right )}{r-g} )\) . Vamos fazer os cálculos de duas maneiras: primeiramente vamos realizar os cálculos período por período. Depois, vamos usar a fórmula acima e chegar no mesmo resultado.

A imagem será apresentada aqui.

def VGA06(lst, c, g, r):
    "Argumentos: lista com períodos e fluxos, pagamento, taxa de crescimento, juros"
    #Convertendo a lista em dataframe
    df = pd.DataFrame(lst, index = ["1", "2", "3", "4", "5"], 
                  columns = ["Ano", "Pagamento Anual"])
    print(df)

    #Calculando C levando em conta g
    df["VPG"] = c*((1+g))**(df["Ano"]-1)
    print(df)

    #Calculndo o valor presente dos fluxos com G
    df["VP2"] = (df["VPG"])/((1+r)**df["Ano"])
    print(df)

    #Soma dos valores presentes
    SPV = df["VP2"].sum()
    print("Valor Presente: ", SPV)

#Utilizando a fórmula:
def FVGA06(c, g, r, n):
    "Argumentos: pagamento, taxa de crescimento, taxa de juros, número de períodos"
    VP = c*(1-((1+g)/(1+r))**n)/(r-g)
    print("Utilizando a Fórmula o Valor Presente é: ", VP)

#Definindo os parâmetros:
C_06 = 1000

#Criando uma lista com os dados.
#A lista deve ser composta por sublistas
#contendo a data e o fluxo na data.
lst_06 = [[1, C_06], 
       [2, C_06], 
       [3, C_06], 
       [4, C_06], 
       [5, C_06]]

VGA06(lst_06, 1000, 0.06, 0.12)
FVGA06(1000, 0.06, 0.12, 5)

Agora vamos calcular o valor de uma anuidade crescente infinita usando \(PV = \frac{C}{r-g} \)
A imagem será apresentada aqui.
Em Python:

def VGIA07(c, r, g):
    "Argumentos: fluxo, taxa de juros, taxa de crescimento"
    VP = c/(r-g)
    print("Valor Presente: ", VP)

VGIA07(1000, 0.12, 0.06)

Vamos agora calcular a taxa interna de retorno. Lembre-se que a TIR é a taxa de juros que zera o valor presente de um fluxo de caixa. No exemplo a seguir, o autor utiliza uma função do Excel para fazer esse cálculo. Por isso, vamos utilizar a função irr() do módulo numpy, que também calcula a taxa interna de retorno.

A imagem será apresentada aqui.

def TIR08(lst):
    "Argumentos: lista de listas"
    #Convertendo a lista em dataframe
    df = pd.DataFrame(lst, 
                  columns = ["Ano", "Fluxo de Caixa"])
    print(df)
    #Definindo um símbolo para taxa de juros
    r = Symbol("r")
    #Calculando o valor presente 
    df["VP"] = (df["Fluxo de Caixa"])/((1+r)**df["Ano"])
    print(df)

    #Explicitando a conta que estamos fazendo.
    eq = df["VP"].sum()
    print("A conta é:", eq, "= 0")
    #Criando uma lista com os fluxos de caixa a partir 
    #da lista original:
    fluxos = [fluxo[1] for fluxo in lst]
    print("Os fluxos são: ", fluxos)

    #Calculando a TIR
    TIR = np.irr(fluxos)
    print("A TIR é: ", 100*TIR, "%")

#Criando uma lista com os dados.
#A lista deve ser composta por sublistas
#contendo a data e o fluxo na data.
lst08 = [[0,-800],
       [1, 200], 
       [2, 250], 
       [3, 300], 
       [4, 350], 
       [5, 400]]

TIR08(lst08)

A função do Excel para calcular a TIR o faz por tentativa e erro. É possível simular esse procedimento no Excel “chutando” vários valores para a TIR e checando para quais deles o valor presente é zero:
A imagem será apresentada aqui.
A imagem será apresentada aqui.

Em Python, vamos escrever uma função para isso. O código abaixo apenas reproduz a tabela acima.

def TABLE09(lst, r):
    #Convertendo a lista em dataframe
    df = pd.DataFrame(lst, 
                  columns = ["Ano", "Fluxo de Caixa"])
    print(df)

    #Calculando o valor presente 
    df["VP"] = (df["Fluxo de Caixa"])/((1+r)**df["Ano"])
    print(df)

    #Soma dos valores presentes
    NPV = df["VP"].sum()
    print("Valor Presente: ", NPV)

#Criando uma lista com os dados.
#A lista deve ser composta por sublistas
#contendo a data e o fluxo na data.
lst_09 = [[0,-800],
       [1, 200], 
       [2, 250], 
       [3, 300], 
       [4, 350], 
       [5, 400]]

TABLE09(lst_09, 0.12)

O objetivo é zerar o valor presente. Vamos fazer isso definindo um range e usar um loop para que o programa teste para quais valores de juros o valor presente entra em um intervalo ao redor do zero. Vamos gerar um intervalo de -1 a 1 com 4 casas decimais e testar para quais dos valores no intervalo o valor presente entra no intervalo [-0.1, 0.1]. Tais valores serão nossa aproximação a TIR:

def TIR10(lst):
    df = pd.DataFrame(lst, 
                  columns = ["Ano", "Fluxo de Caixa"])
    print(df)
    for i in range(-10000, 10001):
        r = i/10000
        df["VP"] = (df["Fluxo de Caixa"])/((1+r)**df["Ano"])
        NPV = df["VP"].sum()
        if -0.1<= NPV <=0.1:
            print("Uma Aproximação Para a TIR é: ", r)

#Criando uma lista com os dados.
#A lista deve ser composta por sublistas
#contendo a data e o fluxo na data.
lst_10 = [[0,-800],
       [1, 200], 
       [2, 250], 
       [3, 300], 
       [4, 350], 
       [5, 400]]

TIR10(lst_10)

Há dois problemas evidentes com esse método. O primeiro é o intervalo para os chutes da taxa de juros. Como ele só vai de -1 a 1, a função não retornará nenhum resultado caso a TIR esteja fora dele. O segundo é o intervalo ao redor do zero para o valor presente. Se por azar todos os valores presentes calculados para todas as taxas de juros caírem fora dele, a função também não retornará nenhum resultado. Contudo, dada a facilidade de implementação e o fácil entendimento desse método, continuaremos a usá-lo no restante do exercício.
Note que no script acima utilizamos a estrutura de dataframes dentro do loop. É mais eficiente, porém , utilizar listas (seria mais eficiente ainda utilizar arrays, mas como o foco do exercício são os cálculos contidos no livro, procuraremos usar a menor quantidade possível de módulos). Assim sendo, passaremos a iterar sobre listas e não mais sobre dataframes. Utilizaremos o módulo pandas apenas para organizar as informações.

0 votos
respondida Out 16 por Felipe Yudi (21 pontos)  
editado Out 30 por Felipe Yudi

Parte 2:

Na próxima tabela, o autor monta uma tabela de amortização e a usa para mostrar que a TIR é o valor da taxa de juros que zera o investimento após o período final da duração de um projeto:
A imagem será apresentada aqui.

Para calcular a loan table, devemos iterar sobre duas listas. A primeira lista será uma formada pelos fluxos de caixa ao final do ano. Essa lista será gerada a partir de lst. Já a segunda lista será composta dos investimentos no começo do ano. Ela começará com o valor 800, mas será acrescida de novos valores a cada iteração, uma vez que seus valores são gerados no final de cada loop.Para iterar sobre duas listas, é necessário usar zip().

def TITLT11(lst):
    "Argumentos: lista"
    #Criando uma lista com os fluxos de caixa a partir 
    #da lista original:
    fluxos = [fluxo[1] for fluxo in lst]
    print("Os fluxos são: ", fluxos)

    #Calculando a TIR com a função do numpy
    tir = np.irr(fluxos)
    print("TIR: ", tir)
    #Criando o fluxo de caixa ao final de um ano
    CFEY = [fluxo[1] for fluxo in lst[1:]]
    print("OS fluxos de caixa são: """, CFEY)

    #Criando a lista Investimento no início do ano.
    #Adicionaremos novos valores a ela a medida que 
    #formos iterando sobre ela.
    IBY = [-lst[0][1]] #Pega o valor 800

    #Criando as listas de renda e retorno ao principal.
    #A cada iteração, novos valores serão adicionados.
    INCOME = []
    ROP = []

    for (cfey, iby) in zip(CFEY, IBY):
        income = iby*tir
        INCOME.append(income)
        rop = cfey - income
        ROP.append(rop)
        ibyl = iby - rop
        IBY.append(ibyl)

    ANO = list(range(1,len(lst)+1))

    pd.set_option('display.max_columns', None) #Faz com que todas as colunas sejam impressas, sem truncagem.
    pd.set_option('display.max_rows', None)

    df1 = pd.DataFrame({"Ano": pd.Series(ANO), "Investimento no Início do Ano": pd.Series(IBY), 
                    "Fluxo de Caixa no Final do Ano": pd.Series(CFEY), "Renda": pd.Series(INCOME), 
                    "Retorno ao Principal" : pd.Series(ROP)})
    print(df1)
    print("O investimento após o último período é: ", df1.iloc[-1, 1])

#Criando uma lista com os dados.
#A lista deve ser composta por sublistas
#contendo a data e o fluxo na data.
lst_11 = [[0,-800],
       [1, 200], 
       [2, 250], 
       [3, 300], 
       [4, 350], 
       [5, 400]]

TITLT11(lst_11)

O que estamos fazendo no script acima é: Calcule a TIR. Pegue os fluxos de caixa positivos Pegue o primeiro fluxo(que é negativo) e multiplique por -1. Pegue esse fluxo e multiplique pela TIR para obter a renda. Subtraia o primeiro fluxo de caixa da renda para obter o retorno ao principal. Subtraia o primeiro investimento ao começo do ano do retorno ao principal e obtenha o segundo investimento ao começo do ano. Faça tudo de novo até o final da lista.

Também é possível usar a tabela de amortização para calcular a TIR.

A imagem será apresentada aqui.
A imagem será apresentada aqui.
A imagem será apresentada aqui.

Nas tabelas acima, o autor pede ao Excel para que chute vários valores para a taxa de juros até que a célula B11 (que contém o valor do principal no período seguinte ao fim do projeto) seja zero. Em Python, podemos obter o mesmo resultado combinando as estratégias usadas nos dois scripts anteriores.
Para isso, faremos um loop dentro de um loop. O loop "de fora" define as listas a serem preenchidas e um chute para a TIR (r). O loop de dentro usa esse chute para a TIR e calcula, para cada período, o investimento no início do ano, a renda e o retorno do principal, colocando tais valores nas respectivas listas. Finalmente, imprime-se uma taxa caso o sexto elemento da lista "Investimento no Início do Ano" (IBY) for próximo de zero. Essa taxa é a TIR.

def TIR12(lst):
    "Argumentos: lista"
    #Criando uma lista com os fluxos de caixa a partir 
    #da lista original:
    fluxos = [fluxo[1] for fluxo in lst]

    #Calculando a TIR pela função do numpy para fins de comparação.
    TIR = np.irr(fluxos)
    print("TIR usando np.irr(): ", TIR)

    #Parte importante:
    for i in range(-100000, 100001):
        IBY = [-lst[0][1]]
        INCOME = []
        ROP = []
        CFEY = [fluxo[1] for fluxo in lst[1:]]
        r = i/100000

        for (cfey, iby) in zip(CFEY, IBY):
            income = iby*r
            INCOME.append(income)
            rop = cfey - income
            ROP.append(rop)
            ibyl = iby - rop
            IBY.append(ibyl)

        if -0.01 <= IBY[-1] <= 0.01:
            print("TIR usando a loan table: ", r)

#Criando uma lista com os dados.
#A lista deve ser composta por sublistas
#contendo a data e o fluxo na data.
lst_12 = [[0,-1000],
       [1, 300], 
       [2, 200], 
       [3, 150], 
       [4, 600], 
       [5, 900]]

TIR12(lst_12)

Na tabela seguinte, apenas calcula-se a TIR com a função própria para isso do excel.

A imagem será apresentada aqui.

Isso é facilmente feito em Python (e já foi feito alguns passos atrás com outros valores).

def TIRP14(lst):
    "Argumentos: lista de listas com o período e o fluxo."
    #Convertendo a lista em dataframe
    df = pd.DataFrame(lst, 
              columns = ["Ano", "Fluxo de Caixa"])
    print(df)
    #Criando uma lista com os fluxos de caixa a partir 
    #da lista original:
    fluxos = [fluxo[1] for fluxo in lst]
    print("Os fluxos são: ", fluxos)
#Calculando a TIR
    TIR = np.irr(fluxos)
    print("A TIR é: ", 100*TIR, "%")

#Criando uma lista com os dados.
lst_p14 = [[0,-1000],
       [1, 300], 
       [2, 200], 
       [3, 150], 
       [4, 600], 
       [5, 900]]

TIRP14(lst_p14)

O Excel possui uma função para calcular a TIR para um fluxo de caixa constante.
A imagem será apresentada aqui.

Em Python, podemos fazer isso da mesma maneira que fizemos anteriormente:

def TIR13(ii, cf, t):
    "ii = investimento inicial, cf = fluxo constante, t = período"
    CF = [-ii]
    #Inserindo um fluxo de caixa periódico na lista CF t vezes:
    for i in range(1,t+1):
        CF.append(cf)

    #Calculando a TIR:
    TIR = np.irr(CF)
    print("A TIR é: ", TIR)

TIR13(ii = 1000, cf = 100, t = 30)

Em alguns casos é possível que haja duas taxas internas de retorno. Isso ocorre caso o fluxo de caixa tenha duas mudanças de sinal.

A imagem será apresentada aqui.

Para reproduzir os cálculos acima em Python, podemos proceder da mesma maneira que temos feito até agora, ou seja, vamos encontrar a TIR por tentativa e erro. Já o gráfico será feito utilizando o módulo matplotlib.

def MTIR14(lst):
    "Argumentos: lista"
    df = pd.DataFrame(lst, 
                  columns = ["Ano", "Fluxo de Caixa"])

    #Criando uma lista com os fluxos de caixa a partir 
    #da lista original:
    fluxos = [fluxo[1] for fluxo in lst]
    print("Os fluxos são: ", fluxos)

    #Calculando a TIR pela função do numpy, só é retornado um valor.
    TIR = np.irr(fluxos)
    print("Calculando a TIR pela função do numpy, só é retornado um valor:")
    print("A TIR pela função irr() é: ",TIR)
    #Encontrando as duas TIR's por chute.
    for i in range(-10000, 10001):
        tir = i/10000
        df["VP"] = (df["Fluxo de Caixa"])/((1+tir)**df["Ano"])
        NPV = df["VP"].sum()
        if -0.01<= NPV <=0.01:
            print("Uma Aproximação Para a TIR é: ", tir)

#Note que o método acima nos fornece mais do 
#duas aproximações para a TIR.

#Para verificar quantas TIR's podem haver, é de grande 
#ajuda plotar um gráfico com as taxas de desconto 
#no eixo x e o valor presente líquido no y.

def PLOT14(lst):
    "Argumentos: lista"
    fluxos = [fluxo[1] for fluxo in lst]
    anos = [ano[0] for ano in lst]
    NPV = []
    R = []

    for i in range(-200,501):
        r = i/1000
        VP = []
        for (ano,fluxo) in zip(anos, fluxos):
            vp = fluxo/((1+r)**ano)
            VP.append(vp)
        npv = sum(VP)    
        NPV.append(npv)
        R.append(r)

    df = pd.DataFrame({"Taxas": pd.Series(R), 
                   "Valor Presente": pd.Series(NPV)})
    print(df)

    plt.plot(R, NPV)
    plt.title("Duas Taxas Internas de Retorno")
    plt.xlabel("Candidatos para a TIR")
    plt.ylabel("Valor Presente Líquido")
    plt.grid(True)
    #Definindo os limites do gráfico
    plt.xlim(0,0.4)
    plt.ylim(-25,5)
    #Desenhando uma reta ao longo do eixo x
    ex = plt.axhline(color = "k")

    plt.show()

#Criando uma lista com os dados.
#A lista deve ser composta por sublistas
#contendo a data e o fluxo na data.
lst_14 = [[0,-145],
       [1, 100], 
       [2, 100], 
       [3, 100], 
       [4, 100], 
       [5, -275]]

MTIR14(lst_14)
PLOT14(lst_14)

Como dito anteriormente, um fluxo de caixa com apenas uma mudança de sinal não pode ter duas taxas internas de retorno. Para entender isso, veja a seguinte tabela.

A imagem será apresentada aqui.

Note que a curva passa pelo eixo x apenas uma vez. O código que reproduz a tabela acima em Python é basicamente o mesmo que o anterior. Desta vez, optou-se por colocar tanto o cálculo da TIR quanto o gráfico em uma mesma função:

def TIR15(lst):
    "Argumentos: lista"
    #Criando uma lista com os fluxos de caixa a partir 
    #da lista original:
    fluxos = [fluxo[1] for fluxo in lst]
    anos = [ano[0] for ano in lst]

    #Calculando a TIR pela função do numpy.
    TIR = np.irr(fluxos)
    print("A TIR pela função irr() é: ",TIR)

    NPV = []
    R = []

    for i in range(-20000,100001):
        r = i/100000
        VP = []
        for (ano,fluxo) in zip(anos, fluxos):
            vp = fluxo/((1+r)**ano)
            VP.append(vp)
        npv = sum(VP)    
        NPV.append(npv)
        R.append(r)
        if -0.01 <= npv <= 0.01:
            print("Candidato para a TIR: ", r)

    df = pd.DataFrame({"Ano": pd.Series(anos), 
                       "Fluxo de Caixa": pd.Series(fluxos),
                       "Taxas": pd.Series(R),
                       "Valor Presente": pd.Series(NPV)})
    print(df)


    plt.plot(R, NPV)
    plt.title("Taxa Interna de Retorno")
    plt.xlabel("Candidatos para a TIR")
    plt.ylabel("Valor Presente Líquido")
    plt.grid(True)
    ex = plt.axhline(color = "k")

    plt.show()

#Criando uma lista com os dados.
#A lista deve ser composta por sublistas
#contendo a data e o fluxo na data.
lst_15 = [[0,-800],
       [1, 100], 
       [2, 100], 
       [3, 100], 
       [4, 100],
       [5, 100], 
       [6, 100], 
       [7, 100], 
       [8, 1100]]

TIR15(lst_15)
0 votos
respondida Out 16 por Felipe Yudi (21 pontos)  
editado Out 22 por Felipe Yudi

Parte 3:

Suponha agora que você faça um empréstimo no banco de 10000 a uma taxa de 7% e que pretende pagar em 6 anos. Qual deve ser o valor das parcelas caso elas sejam iguais?
Para resolver esse problema, podemos montar a seguinte tabela:

A imagem será apresentada aqui.

Para fazer isso em Python vamos criar uma função que calcula o valor das prestações de um empréstimo e depois usar uma tabela de amortização para checar se a função calcula a parcela correta. Aqui, o VP dos pagamentos iniciais deve ser igual ao valor do financiamento, onde a TIR é a taxa de juros.

def PRESTACAO16(tir, t):
    "Argumentos são uma taxa de juros e o período."
    for n in range(1000000):
        pe = n/100
        LP = []
        for i in range(1,t+1):
            lp = pe/(1+tir)**i
            LP.append(lp)
        slp = sum(LP)
        if 9999.99 <= slp <= 10000.01:
            print("Os pagamentos são no valor de: ", pe)

#Podemos nos certificar de que esse é o valor 
#correto montando uma tabela de amortização:

def TABAM16(tir, t):
    "Argumentos são uma taxa de juros e o período."
    #Criando uma lista com o valor das parcelas
    PEY = []
    for i in range(1,t+1):
        PEY.append(2097.96)
    #Criando a lista do principal no início do ano:
    PBY = [10000]
    #Criando as listas dos juros pagos e do retorno ao principal.
    INTEREST = []
    RP = []
    #Montando a tabela de pagamentos.
    for (pey, pby) in zip(PEY, PBY):
        interest = pby*tir
        rp = pey - interest
        pby = pby - rp
        INTEREST.append(interest)
        RP.append(rp)
        PBY.append(pby)    
    #Agora que temos todos os dados vamos montar um 
    #dataframe para melhor visualização.

    ANO = list(range(1,t+1))

    pd.set_option('display.max_columns', None)
    pd.set_option('display.max_rows', None)
    df = pd.DataFrame({"Ano": pd.Series(ANO), "Principal no Início do Ano": pd.Series(PBY), 
                    "Pagamento no Final do Ano": pd.Series(PEY), "Juros": pd.Series(INTEREST), 
                    "Retorno ao Principal" : pd.Series(RP)})
    print(df)
    print("O principal no começo de ano é: ", df.iloc[-1,1])
    df["VP"] = (df["Pagamento no Final do Ano"])/((1+tir)**df["Ano"])
    #Soma dos valores presentes
    SPV = df["VP"].sum()
    print("Valor Presente: ", SPV)

PRESTACAO16(0.07, 6)
TABAM16(0.07, 6)

Note que o termo da sétima linha da coluna "Principal no Início do Ano" é bem próximo de zero. De fato, é fácil verificar que o valor presente dos seis fluxos de 2097.96 a uma TIR de 0.07 é 10000.
Na tabela a seguir, o autor faz uma conta de juros compostos. Quer-se saber qual o valor de uma aplicação de 1000 após 11 períodos a uma taxa de juros de 10%.

A imagem será apresentada aqui.

Em Python, isso pode ser feito da seguinte forma:

def JC17():
    Y = list(range(1,11))
    ABBY = [1000]
    IEDY = []
    TAEY = []
    r = 0.1

    #Note que estamos usando Y para que o loop pare.
    for (y, abby) in zip(Y, ABBY):
        iedy = abby*r
        IEDY.append(iedy)
        taey = abby + iedy
        TAEY.append(taey)
        abby = taey
        ABBY.append(abby)    
    print("Quantia Final: ", ABBY[-1])
    """Montando um dataframe para melhor visualização: """

    pd.set_option('display.max_columns', None)
    pd.set_option('display.max_rows', None)

    df = pd.DataFrame({"Ano": pd.Series(Y), "Conta no Início do Ano": pd.Series(ABBY), 
                    "Juros Ganhos no Ano": pd.Series(IEDY), "Conta no Final do Ano": pd.Series(TAEY)})
    print(df)

JC17()

Claramente essa é uma conta de juros compostos e poderia ser facilmente resolvida fazendo:

def FJC17(r, vi):
    "Arumentos: taxa de juros, valor inicial"
    FV = 1000*(1+r)**10
    print(FV)
FJC17(0.1, 1000)

A próxima tabela complica um pouco o problema anterior. Suponha que além do depósito inicial de 1000, mais depósitos também de 1000, sejam feitos até o nono período. Qual será o total em conta no décimo período?

A imagem será apresentada aqui.

def FVAD18(r, da, t):
    "Argumentos: taxa de juros, depósitos, período"
    Y = list(range(1,t+1))
    ABBY = [0]
    DBY = [da]*10
    IEDY = []
    TAEY = []

    for (dby, abby) in zip(DBY, ABBY):
        iedy = r*(abby+dby)
        IEDY.append(iedy)
        taey = abby + dby + iedy
        TAEY.append(taey)
        ABBY.append(taey)

    ABBY.pop(-1)

    pd.set_option('display.max_columns', None)
    pd.set_option('display.max_rows', None)

    df = pd.DataFrame({"Ano": pd.Series(Y), "Balanço no Início do Ano": pd.Series(ABBY), 
                    "Depósito no Início do Ano": pd.Series(DBY), "Juros": pd.Series(IEDY), 
                    "Total em Conta" : pd.Series(TAEY)})
    print(df)
    print("Valor final: ", df.iloc[-1,4])

FVAD18(0.1, 1000, 10)


"""Poderíamos usar ainda a seguinte função, que tem 
como parâmetros o período de tempo pelo qual se 
irá fazer depósitos, o valor dos depósitos e a 
taxa de juros:"""

def somas18(T, dep, i):
    "Argumentos: período, depósitos, taxa de juros"
    P = []
    for t in range(1,T+1):
        p = dep*(1+i)**t
        P.append(p)
    total = sum(P)
    print("O total no início do período", T,  "é: ", total)

somas18(10, 1000, 0.1)

O seguinte problema é um problema de aposentadoria. Um indivíduo de 55 quer se aposentar aos 60. No início de cada ano é feito um depósito. Os juros são de 8% ao ano. Após a aposentadoria, em cada ano será feito um saque de 30000. Após os 60 anos, acredita-se que o indivíduo viverá por mais 8 anos. Qual deve ser o valor dos depósitos?
A solução errada para o problema é a seguinte: se desconsiderarmos os juros compostos, seríamos levados a acreditar que seria necessário contribuir com \((30000*8)/5 = 48000\) por cinco anos. A seguinte tabela mostra que esse resultado é incorreto:

A imagem será apresentada aqui.

Em Python:

def APOSENTADORIA19(r, deposit, withdrawal):
    ANO = list(range(1,14))
    ABBY = [0]
    DBY = [deposit]*5 + [-withdrawal]*8
    IEDY = []
    TAEY = []

    for (dby, abby) in zip(DBY, ABBY):
        iedy = (dby + abby)*r
        taey = abby + dby + iedy
        IEDY.append(iedy)
        TAEY.append(taey)
        ABBY.append(taey)

    ABBY.pop(-1)

    pd.set_option('display.max_columns', None)
    pd.set_option('display.max_rows', None)

    df = pd.DataFrame({"Ano": pd.Series(ANO), "Balanço no Início do Ano": pd.Series(ABBY), 
                    "Depósito no Início do Ano": pd.Series(DBY), "Juros Ganhos no Ano": pd.Series(IEDY), 
                    "Total em Conta no Final do Ano" : pd.Series(TAEY)})
    print(df)
    print("Sobra o valor de ", df.iloc[12,4], "na conta.")

APOSENTADORIA19(0.08, 48000, 30000)

Note que fazendo as contas dessa maneira sobra dinheiro na conta. Podemos resolver esse problema numericamente utilizando o recurso solver do Excel para obter a seguinte tabela:

A imagem será apresentada aqui.

Para resolver esse problema em Python, vamos primeiro escrever uma função que calcula qual deve ser o valor do depósito e depois usar uma tabela para conferir se tal valor é realmente o correto. Não se assuste se o código a seguir demorar para rodar.

def DEPOSITOS20(r, withdrawal, withdrawalperiod, depositperiod):
    for i in range(1, 10000000):
        deposit = i/100
        DBY = [-withdrawal]*withdrawalperiod
        ABBY = [0]
        IEDY = []
        TAEY = []
        for n in range(1,depositperiod+1):
            DBY.insert(0,deposit)
        for (dby, abby) in zip(DBY, ABBY):
            iedy = (dby + abby)*r
            taey = abby + dby + iedy
            IEDY.append(iedy)
            TAEY.append(taey)
            ABBY.append(taey)
        if -0.1 <= TAEY[-1] <= 0.1:
            print("Os depositos devem ser de: ", deposit)

DEPOSITOS20(0.08, 30000, 8, 5)

#Podemos facilmente verificar que 29386.55 é o 
#valor correto montando uma tabela:

def DEPT20(r, dep, withdrawal, withdrawalperiod, depositperiod):
    ANO = list(range(1,withdrawalperiod+depositperiod+1))
    ABBY1 = [0]
    DBY1 = [dep]*depositperiod + [-withdrawal]*withdrawalperiod
    IEDY1 = []
    TAEY1 = []

    for (dby1, abby1) in zip(DBY1, ABBY1):
        iedy1 = (dby1 + abby1)*r
        taey1 = abby1 + dby1 + iedy1
        IEDY1.append(iedy1)
        TAEY1.append(taey1)
        ABBY1.append(taey1)

    ABBY1.pop(-1)

    pd.set_option('display.max_columns', None)
    pd.set_option('display.max_rows', None)

    df = pd.DataFrame({"Ano": pd.Series(ANO), "Balanço no Início do Ano": pd.Series(ABBY1), 
                    "Depósito no Início do Ano": pd.Series(DBY1), "Juros Ganhos no Ano": pd.Series(IEDY1), 
                    "Total em Conta no Final do Ano" : pd.Series(TAEY1)})
    print(df)
    print("Sobra o valor de ", df.iloc[-1,4], "na conta.")

DEPT20(0.08, 29386.55, 30000, 8, 5)

Também é possível resolver o problema acima com fórmulas. Basta usar
\( \sum_{t=0}^{4}\frac{Dep. Inicial}{(1,08)^{t}}-\sum_{t=5}^{12}\frac{30000}{(1,08)^{t}}=0\)

A imagem será apresentada aqui.

def DEPANUAL21(r, withdrawalperiod, totalperiod, withdrawal):
    "Argumentos: Taxa de juros, período de saque, período total (período de saque + depósito), valor do saque."
    #Numerador.
    fluxosW = []
    for t in range(5,totalperiod):
        vpw = withdrawal/(1+r)**t
        fluxosW.append(vpw)
    svpw = sum(fluxosW) 
    #Denominador.
    fluxosI = []
    for t in range(0,totalperiod - withdrawalperiod):
        vpi = 1/(1+r)**t
        fluxosI.append(vpi)
    svpi = sum(fluxosI)
    #Calculando o depósito anual.
    ad = svpw/svpi
    print("O depósito anual deve ser de: ", ad)

DEPANUAL21(0.08, 8, 13, 30000)

Em seguida autor explora um pouco o conceito de capitalização contínua, que ocorre quando o número de períodos de tempo da capitalização tende para infinito. A seguinte tabela é apresentada:

A imagem será apresentada aqui.

A função que escreveremos em Python fará três coisas. Em primeiro lugar calcularemos o valor da capitalização contínua usando \(Val.Final = Def.Inicial*e^{r}\) e o número \(e\) do módulo math. Depois, reproduziremos a parte inferior da tabela. Em seguida, utilizaremos o modulo matplotlib para plotar o gráfico com base na tabela.

def MCP21(di, r, cpp, CPPY):
    "Argumentos: depósito inicial, taxa de juros, número de períodos, períodos compostos em um ano."
    #Parte de cima da tabela:
    icp = r/cpp
    aoy = di*(1+icp)**cpp
    cce = di*math.exp(r)
    print("Depósito inicial: ", di)
    print("Juros: ", r)
    print("Número de períodos compostos: ", cpp)
    print("Juros por período composto: ", icp)
    print("Acréscimo em um ano: ", aoy)
    print("Capitalização contínua usando e: ", cce)
    #Parte de baixo da tabela:
    EYA = []
    for p in CPPY:
        eya = di*(1+r/p)**p
        EYA.append(eya)
        #Colocando tudo em um dataframe:
    pd.set_option('display.max_columns', None)
    pd.set_option('display.max_rows', None)
    df = pd.DataFrame({"Períodos compostos/ano": pd.Series(CPPY), "Acréscimo ao Final do Ano": pd.Series(EYA)})
    print(df)
    #Montando o gráfico:
    #O eixo x conterá o log dos períodos compostos para melhor visualização.
    plt.plot(CPPY, EYA, "k-")
    plt.xscale('log')
    plt.title("Efeito de Múltiplos Períodos Compostos")
    plt.xlabel("Períodos Compostos")
    plt.ylabel("Acréscimo ao Final do Ano")
    plt.grid(True)

    plt.show()

CPPY_21 = [1, 2, 10, 20, 50, 100, 150, 300, 800]
MCP21(1000, 0.05, 2, CPPY_21)

Em seguida calcula-se o valor de um fluxo descontado continuamente a uma taxa r por t períodos. Isso pode ser obtido com \(VP = \sum C_{t}e^{-rt}\)

A imagem será apresentada aqui.

Em Python, basta escrever uma função que faça esses cálculos usando a fórmula acima:

def CDF22(r, t, CF):
    "Argumentos: Taxa de juros, período, fluxo"
    ANO = []
    for i in range(1, t+1):
        ANO.append(i)
    CDPV = []
    for (ano, cf) in zip(ANO, CF):
        cdpv = cf*math.exp(-r*ano)
        CDPV.append(cdpv)

    #Colocando tudo em um dataframe:
    pd.set_option('display.max_columns', None)
    pd.set_option('display.max_rows', None)

    df = pd.DataFrame({"Ano": pd.Series(ANO), "Fluxo": pd.Series(CF), 
                   "Valor Presente Descontado Continuamente": pd.Series(CDPV)})
    print(df)
    VP = sum(CDPV)
    print("Valor Presente: ", VP)

CF_22 = [100, 200, 300, 400, 500]

CDF22(0.08, 5, CF_22)

Na tabela seguinte, o autor passa a calcular retornos. Suponha que na data 0 você tenha 1000 no banco e que no período seguinte esse valor passe para 1200. Se há somente um período, o retorno é: \(\frac{1200}{1000}-1=0,2\). Com dois períodos, temos que resolver a equação \(1000\left ( 1+\frac{r}{2} \right )^{2}=1200\). De maneira mais geral, se no número de períodos tende para infinito, temos que \(r = ln(\frac{1200}{1000}) = 0,18232\).

A imagem será apresentada aqui.

Usar juros contínuos também torna as contas mais fáceis. Suponha que o valor inicial de 1000 tenha se tornado 1500 em um ano e nove meses. Qual a taxa anual de retorno? Basta resolver a equação: \(1500 = 1000*e^{(r*1,75)}\), pois um ano e nove meses são 1.75 anos.
Para reproduzir os cálculos acima em Python, vamos utilizar a função log do módulo math.

#Um período
def RET123(vi, vf):
    "Argumentos: valor inicial e valor final"
    ret1 = vf/vi -1
    print("Retorno Após Um Período: ", ret1)

#Com dois períodos:
def RET223(vi, vf):
    "Argumentos: valor inicial e valor final"
    ret2 = 2*((vf/vi)**(1/2)-1)
    print("Taxa de Juros Anual Implícita: ", ret2)

#Se o número de períodos vai para o infinito o retono é:
def RET23(vi, vf):
    "Argumentos: valor inicial e valor final"
    retinf = math.log(vf/vi)
    print("Retorno Contínuo: ", retinf)
    NCP = [1, 2, 4, 8, 20, 1000]
    RATE = []
    for t in NCP:
        rett = t*((vf/vi)**(1/t)-1)
        RATE.append(rett)

    pd.set_option('display.max_columns', None)
    pd.set_option('display.max_rows', None)

    df = pd.DataFrame({"Períodos compostos/ano": pd.Series(NCP), "Retorno": pd.Series(RATE)})
    print(df)

RET123(1000, 1200)
RET223(1000, 1200)
RET23(1000, 1200)

#Taxa anual de retorno com 1 ano e 9 meses
def RETCONT23(vi, vf, a, m):
    "Argumentos: valor inicial, valor final, anos, meses."
    y = a + m/12
    ret = (1/y)*math.log(vf/vi)
    print("Retorno: ", ret)
RETCONT23(1000, 1500, 1, 9)

Até agora trabalhamos com fluxos em intervalos regulares. Para lidar com fluxos em datas específicas, o procedimento é um pouco diferente. Queremos calcular a TIR para um fluxo irregular. O Excel possui a função XIRR para executar essa tarefa. Isso é feito usando a fórmula \(0 = \sum_{i = 1}^{N}\frac{P_{i}}{(1+r)^{\frac{d_{i}-d_{1}}{365}}}\). Onde \(P_{i}\) é o i-ésimo ou último pagamento, \(d_{i}\) é a data do i-ésimo ou último pagamento e \(d_{1}\) é a data do primeiro pagamento.

A imagem será apresentada aqui.

Em Python, podemos usar o módulo datetime, que nos permite trabalhar com dias específicos. Segue um pequeno tutorial sobre timedate, que deve ser rodado preferencialmente em outro file:

import datetime
t1 = datetime.date(2019, 2, 1)
t2 = datetime.date(2019, 2, 3)

delta = t2-t1
print(delta)
"""Subtrair duas datas nos dá um objeto timedelta.
Para obter o número de dias, basta usar .days """
a = delta.days
print(a)

Agora estamos prontos para escrever uma função similar a XIRR:

def XIRR(TIME, CF):
    "Artgumentos: data do depósito (yy/mm/dd) e fluxo"
    DELTA = [] #Aqui colocaremos a direfença entre um período e outro em dias
    #Colocando em um dataframe:
    pd.set_option('display.max_columns', None)
    pd.set_option('display.max_rows', None)
    df = pd.DataFrame({"Datas": pd.Series(TIME), "Fluxos": pd.Series(CF)})
    print(df)
    #Calculando as diferenças em dias:
    for t in range(len(TIME)):
        d = TIME[t] - TIME[0] #Calcula a diferença em dias
        dd = d.days #Converte a diferença de timedate oara int
        DELTA.append(dd)

    #Calculando a TIR:
    for i in range(-999, 1001):
        r = i/1000
        VP = []
        for (cf, delta) in zip(CF, DELTA):
            vp = (cf)/(1+r)**((delta)/365)
            VP.append(vp)
        svp = sum(VP)
        if -0.1 <= svp <= 0.1:
            print("A TIR é: ", r)

#Definindo os parêmetros:
TIME_24 = [datetime.date(2006, 1, 1), datetime.date(2006, 3, 3), 
datetime.date(2006, 7,4), datetime.date(2006, 10, 12), datetime.date(2006, 12, 25)]
CF_24 = [-1000, 150, 100, 50, 1000]

XIRR(TIME_24, CF_24)

Já para calcular o Valor presente para um fluxo irregular, o Excel possui a função XNPV.

A imagem será apresentada aqui.

Isso também é facilmente feito em Python (com algum erro de aproximação):

def XNPV(TIME, CF, r):
    "Argumentos: data do depósito (yy/mm/dd), fluxo e taxa de juros"
    DELTA = []
    print("Taxa Anual de Desconto: ", r)
    #Colocando em um dataframe:
    pd.set_option('display.max_columns', None)
    pd.set_option('display.max_rows', None)

    df = pd.DataFrame({"Datas": pd.Series(TIME), "Fluxos": pd.Series(CF)})
    print(df)

    #Calculando as diferenças em dias:
    for t in range(len(TIME)):
        d = TIME[t] - TIME[0] #Calcula a diferença em dias
        dd = d.days #Converte a diferença de timedate oara int
        DELTA.append(dd)
    #Calculando o valor presente:
    VP = []
    for (cf, delta) in zip(CF, DELTA):
        vp = (cf)/(1+r)**((delta)/365)
        VP.append(vp)
    svp = sum(VP)
    print("O Valor Presente é: ", svp)

#Definindo os parêmetros:
TIME_25 = [datetime.date(2006, 1, 1), datetime.date(2007, 3, 7), 
           datetime.date(2007, 7, 4), datetime.date(2008, 10, 12), datetime.date(2009, 12, 25)]
CF_25 = [-1000, 100, 195, 350, 800]
XNPV(TIME_25, CF_25, 0.12)   
comentou Out 30 por danielcajueiro (5,776 pontos)  
Felipe, vc precisa colocar um titulo sugestivo para a sua pergunta (Resuma o problema em uma linha) e tb uma descrição melhor do que eh feito esse capitulo: "A proposta desse exercicio é replicar... Nesse capitulo discute-se... ). Vc colocou de onde saiu a questao. Isso vc pode colocar junto com o resto do material da pergunta em uma nota.
...