Skip to content

Instantly share code, notes, and snippets.

@jacmkno
Last active November 3, 2023 14:11
Show Gist options
  • Save jacmkno/3036f5ef518a915ba6db1e90402a68bb to your computer and use it in GitHub Desktop.
Save jacmkno/3036f5ef518a915ba6db1e90402a68bb to your computer and use it in GitHub Desktop.
Loan Simulator Fixed rate, Fixed Installments, Fixed Monthly Payment

Función simulateLoan

Esta función simula un préstamo y genera una tabla de amortización.

Parámetros:

  • {P}: El monto del préstamo principal.
  • {n}: El número total de períodos de amortización.
  • {rate} (opcional, valor predeterminado: 0.03): La tasa de interés por período.
  • {insurance} (opcional, valor predeterminado: 0.0012): La tasa de seguro por período.
  • {round} (opcional, valor predeterminado: false): Un booleano que indica si se deben redondear los valores en la tabla de amortización.
  • {iter} (opcional, valor predeterminado: una función nula): Una función de devolución de llamada que se llama para cada período de amortización, pasando información sobre ese período.

La función calcula la tabla de amortización del préstamo, incluyendo información sobre el saldo inicial, intereses, seguros, amortización y cuota por período. Luego, devuelve un arreglo de objetos que representan cada período de amortización.

Función printSimulation

Esta función imprime una tabla de simulación en la consola.

Parámetros:

  • s: Un arreglo de objetos que representa la simulación de un préstamo, como se genera con la función simulateLoan.

La función formatea la información contenida en el arreglo s y la imprime en la consola en forma de una tabla.

Función testSimulation

Esta función prueba una simulación de préstamo para verificar su precisión.

Parámetros:

  • {P}: El monto del préstamo principal.
  • {n}: El número total de períodos de amortización.
  • {rate} (opcional, valor predeterminado: 0.03): La tasa de interés por período.
  • {insurance} (opcional, valor predeterminado: 0.0012): La tasa de seguro por período.

La función simula un préstamo utilizando la función simulateLoan y luego realiza una serie de comprobaciones para asegurarse de que los resultados sean coherentes con tasas por período. Si encuentra discrepancias, lanza excepciones con mensajes detallados.

Función testSimulations

Esta función realiza múltiples pruebas de simulación de préstamos utilizando la función testSimulation con diferentes configuraciones de préstamo y períodos de amortización.

Función assert

Esta función es una función de utilidad que se utiliza para lanzar excepciones si una condición dada no se cumple. Se utiliza dentro de la función testSimulation para comprobar condiciones y lanzar excepciones en caso de falla.

Función simulateInvestment

Esta función calcula la cantidad mínima de dinero que se debe pedir prestado para poder pagar las cuotas durante el período de gracia y, al mismo tiempo, gastar amountToSpend. También devuelve información sobre el dinero que queda pendiente de pago, el valor de la cuota y cuántas cuotas quedan por pagar.

Parámetros:

  • amountToSpend: El monto máximo que se puede gastar.
  • gracePeriods: El número de períodos de gracia deseado.
  • installments: El número total de períodos de amortización.
  • rate: La tasa de interés por período.
  • insurance: La tasa de seguro por período.

La función utiliza una estrategia de búsqueda para encontrar la cantidad óptima que se debe pedir prestada y, a partir de eso, calcula la información relevante sobre el préstamo, incluyendo la cantidad que queda pendiente de pago, el valor de la cuota y cuántas cuotas quedan por pagar. Luego, devuelve un objeto que contiene esta información.

Espero que esta documentación en Markdown sea útil. Si tienes alguna pregunta adicional o necesitas más detalles sobre alguna de las funciones, no dudes en preguntar.

function simulateLoan({P, n, rate=0.03, insurance = 0.0012, round=false, iter=()=>null}){
if(rate + insurance > 0.2){
throw 'Maximum safe rate for insurance plus interst is 0.2. Not sure if it is due to precission issues.';
}
const _r = (n, decimals=2) => round?(
(pK)=>(Math.round(n * pK) / pK)
)(Math.pow(10, decimals)):n;
const r = rate + insurance;
const Mp = _r((P * r * Math.pow(1 + r, n)) / (Math.pow(1 + r, n) - 1));
let D = P;
const rows = [];
let Tp = 0;
while(D > 0) rows.push((()=>{
const [Mi, Ms] = [_r(rate * D), _r(insurance * D)];
const A = Math.min(_r(Mp - Mi - Ms), D);
const B0 = D;
D = _r(D - A);
Tp += Mi + Ms + A;
const rt = {
n: rows.length + 1,
B0,
R: Mi,
I: Ms,
A,
M: _r(Mi + Ms + A),
BF: D,
Tp
};
iter(rt);
return rt;
})())
return rows;
}
function printSimulation(s){
const M = {
n: "Period",
B0: "Start Balance",
BF: "Final Balance",
R: "Interest",
I: "Insurance",
A: "Amortization",
M: "Installment",
Tp: "Total Payed"
}
console.table(
Object.fromEntries(s.map(({n, ...row})=>[n,
Object.fromEntries(
Object.entries(row).map(([k, v])=>[M[k], v])
)
]))
);
}
function testSimulation(P, n, rate=0.03, insurance = 0.0012){
const _eq = (a, b, tolerance=0.1)=>Math.abs(a-b) < tolerance;
if(n <= 0 ) return;
const S = simulateLoan({P, n, rate, insurance});
const L = S.length;
if(L != n) throw ['Wrong installment count:', {P, n, rate, insurance, L, S}];
if(S[L - 1].BF != 0) throw ['Repayment Incomplete:', {P, n, rate, insurance, BF: S[L - 1].BF, S}];
S.forEach( (p, i) => {
if(p.B0 <= p.BF) throw ['Debt not reducing:', p];
if( i > 0 && i < L - 1)
if(!_eq(p.M, p.R + p.I + p.A) || !_eq(p.M, S[0].M) )
throw ['Inconsistent Monthly Payment:', {p, S}];
if(!_eq(p.BF, p.B0 - p.A))
throw ['Unexpected Final Balance:', {p, S}];
if(!_eq(p.R, p.B0 * rate))
throw ['Unexpected Interest on Period:', {p, S}];
if(!_eq(p.I, p.B0 * insurance))
throw ['Unexpected Insurance on Period:', {p, S}];
});
}
function testSimulations(){
testSimulation(100000000, 60);
testSimulation(100000, 10);
testSimulation(100000, 1);
testSimulation(100000, 2);
testSimulation(100000000, 60, 1)
}
function assert(condition, message) {
if (!condition) {
throw message;
}
}
function simulateInvestment(amountToSpend, gracePeriods, installments, rate, insurance){
const rP0 = {p: 0}; // Reference Minimum
const rPf = {p: 1}; // Reference Max
let bL = null; // Best Loan
let pL = false; // Previous Best Loan
while(true){
const iP = (rP0.p + rPf.p)/2;
const remaining = amountToSpend/iP - amountToSpend;
const P = amountToSpend/iP;
const lP = {P, n:installments, rate, insurance};
let gpl = null; // Grace Periods for this loan
let nLr = simulateLoan({...lP,
iter: (row) => { if(row.Tp <= remaining) gpl = row; }
});
if(!gpl) return null;
if(gpl.n == gracePeriods){
return {...gpl, P};
} else if(gpl.n > gracePeriods){
rP0.p = iP;
rP0.L = {params: lP, result: nLr};
} else {
rPf.p = iP;
rPf.L = {params: lP, result: nLr};
}
}
}
printSimulation(simulateLoan({P:100000000, n:60}))
<!DOCTYPE html>
<html>
<head>
<title>Simular Inversión</title>
</head>
<body>
<h2>Simular Inversión</h2>
<form id="investmentForm">
<label for="amountToSpend">Monto a Gastar:</label>
<input type="number" id="amountToSpend" name="amountToSpend" step="0.01" required><br><br>
<label for="gracePeriods">Período de Gracia:</label>
<input type="number" id="gracePeriods" name="gracePeriods" required><br><br>
<label for="installments">Total de Cuotas:</label>
<input type="number" id="installments" name="installments" required><br><br>
<label for="rate">Tasa de Interés por Período:</label>
<input type="number" id="rate" name="rate" step="0.0001" required><br><br>
<label for="insurance">Tasa de Seguro por Período:</label>
<input type="number" id="insurance" name="insurance" step="0.0001" required><br><br>
<input type="submit" value="Calcular">
</form>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<title>Simular Préstamo</title>
</head>
<body>
<h2>Simular Préstamo</h2>
<form id="loanForm">
<label for="P">Monto del Préstamo:</label>
<input type="number" id="P" name="P" step="0.01" required><br><br>
<label for="n">Número de Cuotas:</label>
<input type="number" id="n" name="n" required><br><br>
<label for="rate">Tasa de Interés por Período:</label>
<input type="number" id="rate" name="rate" step="0.0001" required><br><br>
<label for="insurance">Tasa de Seguro por Período:</label>
<input type="number" id="insurance" name="insurance" step="0.0001" required><br><br>
<label for="round">Redondear Valores:</label>
<input type="checkbox" id="round" name="round"><br><br>
<input type="submit" value="Calcular">
</form>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment