Last active
October 9, 2025 17:28
-
-
Save plinionaves/5d9c31f4a86a6fa42431480a38545c79 to your computer and use it in GitHub Desktop.
Infer installments principal from payment schedule (price table)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import pandas as pd | |
from pyxirr import xirr, xnpv | |
from typing import List, Dict | |
# Esta função já estava correta, pois usa a taxa anual para o XNPV. | |
# Nenhuma alteração é necessária aqui. | |
def get_inferred_principal( | |
proposed_schedule: List[Dict], | |
loan_monthly_rate: float, | |
disbursement_date_str: str, | |
) -> float: | |
"""Calcula e retorna o principal implícito de um cronograma.""" | |
disbursement_date = pd.to_datetime(disbursement_date_str) | |
payment_dates = [pd.to_datetime(p['due_date']) for p in proposed_schedule] | |
payment_amounts = [-p['fixed_amount'] for p in proposed_schedule] | |
# Converte a taxa mensal em uma taxa anual efetiva para o cálculo do XNPV | |
loan_annual_rate = (1 + loan_monthly_rate)**12 - 1 | |
# O XNPV dos pagamentos nos dá o valor presente. O principal é o valor absoluto. | |
inferred_principal = -xnpv(loan_annual_rate, [disbursement_date] + payment_dates, [0] + payment_amounts) | |
return inferred_principal | |
# --- FUNÇÃO AJUSTADA --- | |
def generate_amortization_schedule( | |
proposed_schedule: List[Dict], | |
loan_monthly_rate: float, | |
disbursement_date_str: str, | |
) -> pd.DataFrame: | |
""" | |
Gera a Tabela de Amortização detalhada para um cronograma, inferindo seu principal. | |
""" | |
# 1. Obter o Saldo Devedor Inicial, que é o nosso principal inferido | |
initial_balance = get_inferred_principal(proposed_schedule, loan_monthly_rate, disbursement_date_str) | |
# --- AJUSTE PRINCIPAL AQUI --- | |
# 2. Calcular a taxa de juros anual e diária equivalentes | |
# A taxa anual é a base para um cálculo preciso em dias corridos (convenção Actual/365) | |
loan_annual_rate = (1 + loan_monthly_rate)**12 - 1 | |
daily_rate = (1 + loan_annual_rate)**(1 / 365) - 1 | |
# --- FIM DO AJUSTE --- | |
# 3. Preparando os dados para o loop de cálculo da amortização | |
amortization_data = [] | |
outstanding_balance = initial_balance | |
previous_date = pd.to_datetime(disbursement_date_str) | |
print(f"--- Gerando Tabela de Amortização ---") | |
print(f"Principal Inferido (Saldo Devedor Inicial): {initial_balance:,.2f}") | |
print(f"Taxa Mensal: {loan_monthly_rate:.2%}") | |
print(f"Taxa Anual Equivalente: {loan_annual_rate:.4%}") | |
print(f"Taxa Diária Equivalente (base 365): {daily_rate:.8f}\n") | |
for i, installment in enumerate(proposed_schedule): | |
payment_date = pd.to_datetime(installment['due_date']) | |
payment_amount = installment['fixed_amount'] | |
# Calcula os juros para o período exato em dias usando a nova taxa diária | |
days_in_period = (payment_date - previous_date).days | |
interest_paid = outstanding_balance * ((1 + daily_rate)**days_in_period - 1) | |
# A amortização é o que sobra da parcela após pagar os juros | |
principal_paid = payment_amount - interest_paid | |
# Atualiza o saldo devedor | |
outstanding_balance -= principal_paid | |
# Ajuste final na última parcela para zerar o saldo devido a arredondamentos | |
if i == len(proposed_schedule) - 1: | |
principal_paid += outstanding_balance | |
outstanding_balance = 0.0 | |
amortization_data.append({ | |
"Parcela #": i + 1, | |
"Data Venc.": payment_date.date(), | |
"Dias Período": days_in_period, | |
"Valor Parcela": payment_amount, | |
"Juros": interest_paid, | |
"Amortização (Principal)": principal_paid, | |
"Saldo Devedor": outstanding_balance | |
}) | |
previous_date = payment_date | |
# Cria um DataFrame do Pandas para uma visualização clara | |
df = pd.DataFrame(amortization_data) | |
# Formatando as colunas para melhor leitura | |
for col in ["Valor Parcela", "Juros", "Amortização (Principal)", "Saldo Devedor"]: | |
df[col] = df[col].map('{:,.2f}'.format) | |
return df | |
# --- DEMONSTRAÇÃO DE USO --- | |
if __name__ == "__main__": | |
schedule_recebido = [ | |
{"due_date": "2025-11-10", "fixed_amount": 5867.00}, | |
{"due_date": "2025-12-10", "fixed_amount": 5867.00}, | |
{"due_date": "2026-01-10", "fixed_amount": 5867.00}, | |
{"due_date": "2026-02-10", "fixed_amount": 5867.00}, | |
{"due_date": "2026-03-10", "fixed_amount": 5867.00}, | |
] | |
taxa_do_contrato = 0.12 # 12% a.m. | |
data_desembolso = '2025-09-30' | |
# Gerando a tabela de amortização completa | |
tabela_amortizacao = generate_amortization_schedule( | |
schedule_recebido, | |
taxa_do_contrato, | |
data_desembolso | |
) | |
print(tabela_amortizacao.to_string(index=False)) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment