initial: rent vs buy calculator
This commit is contained in:
121
calc.js
Normal file
121
calc.js
Normal file
@@ -0,0 +1,121 @@
|
||||
/**
|
||||
* calc.js — pure calculation engine, no DOM
|
||||
*/
|
||||
|
||||
/**
|
||||
* Run the full rent vs buy simulation month by month.
|
||||
* Returns arrays (one value per year) plus summary stats.
|
||||
*
|
||||
* @param {Object} p - input parameters
|
||||
* @returns {Object} result
|
||||
*/
|
||||
function run_simulation(p) {
|
||||
const months = p.years * 12;
|
||||
|
||||
// --- mortgage setup ---
|
||||
const loan = p.home_price * (1 - p.down_pct / 100);
|
||||
const r_m = p.interest_rate / 100 / 12;
|
||||
const n = p.loan_years * 12;
|
||||
let monthly_payment;
|
||||
if (r_m === 0) {
|
||||
monthly_payment = loan / n;
|
||||
} else {
|
||||
monthly_payment = loan * (r_m * Math.pow(1 + r_m, n)) / (Math.pow(1 + r_m, n) - 1);
|
||||
}
|
||||
|
||||
// upfront costs for buyer
|
||||
const down_payment = p.home_price * (p.down_pct / 100);
|
||||
const upfront_buy = down_payment + p.home_price * (p.closing_buy_pct / 100);
|
||||
|
||||
// --- state ---
|
||||
let balance = loan;
|
||||
let home_value = p.home_price;
|
||||
let portfolio = upfront_buy; // renter invests this lump sum
|
||||
let monthly_rent = p.monthly_rent;
|
||||
|
||||
let cum_buy_cost = upfront_buy; // running cash spent on buying
|
||||
let cum_rent_cost = 0;
|
||||
|
||||
// per-year arrays (index 0 = end of year 1)
|
||||
const buyer_nw_arr = [];
|
||||
const renter_nw_arr = [];
|
||||
const cum_buy_arr = [];
|
||||
const cum_rent_arr = [];
|
||||
const labels = [];
|
||||
|
||||
let breakeven_month = null;
|
||||
|
||||
const invest_r_m = p.invest_return / 100 / 12;
|
||||
const app_r_m = p.appreciation / 100 / 12;
|
||||
const rent_inc_m = p.rent_increase / 100 / 12;
|
||||
|
||||
for (let m = 1; m <= months; m++) {
|
||||
// --- buy side ---
|
||||
const interest_m = balance * r_m;
|
||||
const principal_m = monthly_payment - interest_m;
|
||||
balance = Math.max(0, balance - principal_m);
|
||||
home_value *= (1 + app_r_m);
|
||||
|
||||
const prop_tax_m = home_value * (p.prop_tax_rate / 100) / 12;
|
||||
const maint_m = home_value * (p.maint_rate / 100) / 12;
|
||||
const insurance_m = p.insurance / 12;
|
||||
// German context: no mortgage interest tax deduction for private buyers
|
||||
const total_buy_m = monthly_payment + prop_tax_m + maint_m + insurance_m;
|
||||
|
||||
cum_buy_cost += total_buy_m;
|
||||
|
||||
const selling_costs = home_value * (p.closing_sell_pct / 100);
|
||||
const buyer_nw = home_value - selling_costs - balance;
|
||||
|
||||
// --- rent side ---
|
||||
const renters_ins_m = p.renters_ins / 12;
|
||||
const total_rent_m = monthly_rent + renters_ins_m;
|
||||
cum_rent_cost += total_rent_m;
|
||||
|
||||
// renter invests the difference if buying is more expensive
|
||||
const monthly_delta = Math.max(0, total_buy_m - total_rent_m);
|
||||
portfolio = portfolio * (1 + invest_r_m) + monthly_delta;
|
||||
|
||||
// apply capital gains tax on portfolio gains when we "cash out" at end
|
||||
// (we track gross portfolio, deduct tax in summary)
|
||||
const renter_nw = portfolio;
|
||||
|
||||
// breakeven: when buyer net worth overtakes renter
|
||||
if (breakeven_month === null && buyer_nw >= renter_nw) {
|
||||
breakeven_month = m;
|
||||
}
|
||||
|
||||
// rent increases monthly
|
||||
monthly_rent *= (1 + rent_inc_m);
|
||||
|
||||
// record yearly snapshots
|
||||
if (m % 12 === 0) {
|
||||
const yr = m / 12;
|
||||
labels.push(`Jahr ${yr}`);
|
||||
buyer_nw_arr.push(Math.round(buyer_nw));
|
||||
// apply Abgeltungssteuer on portfolio gains at cashout
|
||||
const portfolio_gains = portfolio - upfront_buy;
|
||||
const tax_on_gains = Math.max(0, portfolio_gains) * (p.tax_rate / 100);
|
||||
renter_nw_arr.push(Math.round(portfolio - tax_on_gains));
|
||||
cum_buy_arr.push(Math.round(cum_buy_cost));
|
||||
cum_rent_arr.push(Math.round(cum_rent_cost));
|
||||
}
|
||||
}
|
||||
|
||||
// final net worth at end of horizon
|
||||
const final_buyer_nw = buyer_nw_arr[buyer_nw_arr.length - 1];
|
||||
const final_renter_nw = renter_nw_arr[renter_nw_arr.length - 1];
|
||||
|
||||
return {
|
||||
labels,
|
||||
buyer_nw_arr,
|
||||
renter_nw_arr,
|
||||
cum_buy_arr,
|
||||
cum_rent_arr,
|
||||
monthly_payment: Math.round(monthly_payment),
|
||||
final_buyer_nw,
|
||||
final_renter_nw,
|
||||
breakeven_month,
|
||||
winner: final_buyer_nw >= final_renter_nw ? 'buy' : 'rent',
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user