/** * 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); const start_capital = p.start_capital || 0; // --- state --- let balance = loan; let home_value = p.home_price; // renter invests all startkapital; buyer spends upfront_buy and invests the remainder let portfolio = start_capital; let buyer_portfolio = Math.max(0, start_capital - upfront_buy); 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; buyer_portfolio *= (1 + invest_r_m); const selling_costs = home_value * (p.closing_sell_pct / 100); const buyer_nw = home_value - selling_costs - balance + buyer_portfolio; // --- 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 - start_capital; 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', }; }