Financial Calculator Use deterministic calculations instead of ad hoc spreadsheet math when the user needs precise financial outputs. Use This For - Loan and mortgage math - Investment growth and savings goals - NPV, IRR, and payback analysis - Retirement projections and withdrawal planning - Monte Carlo style risk scenarios Workflow 1. Confirm units and assumptions first: rates, compounding, time horizon, taxes, and inflation. 2. Use as the source of truth for the computation. 3. Return both the answer and the assumptions that materially drive it. Guardrails - Treat outputs as calculations,…

\n decimal_places: int = 2\n\n\nclass FinancialCalculator:\n \"\"\"\n Comprehensive financial calculator with multiple analysis tools.\n \"\"\"\n\n def __init__(self):\n \"\"\"Initialize financial calculator.\"\"\"\n self.config = FinanceConfig()\n\n # ==================== LOAN CALCULATIONS ====================\n\n def loan_payment(\n self,\n principal: float,\n rate: float,\n years: int,\n payments_per_year: int = 12\n ) -> Dict[str, Any]:\n \"\"\"\n Calculate loan payment and summary.\n\n Args:\n principal: Loan amount\n rate: Annual interest rate (%)\n years: Loan term in years\n payments_per_year: Number of payments per year (default: 12)\n\n Returns:\n Dict with payment details\n \"\"\"\n if principal \u003c= 0:\n raise FinanceError(\"Principal must be positive\")\n if rate \u003c 0:\n raise FinanceError(\"Rate cannot be negative\")\n if years \u003c= 0:\n raise FinanceError(\"Term must be positive\")\n\n # Convert rate to periodic rate\n periodic_rate = rate / 100 / payments_per_year\n total_payments = years * payments_per_year\n\n if periodic_rate == 0:\n payment = principal / total_payments\n else:\n # PMT formula\n payment = principal * (\n periodic_rate * (1 + periodic_rate) ** total_payments\n ) / (\n (1 + periodic_rate) ** total_payments - 1\n )\n\n total_paid = payment * total_payments\n total_interest = total_paid - principal\n\n return {\n 'principal': principal,\n 'rate': rate,\n 'years': years,\n 'monthly_payment': payment,\n 'total_payments': total_paid,\n 'total_interest': total_interest,\n 'interest_ratio': total_interest / principal * 100,\n 'payment_breakdown': {\n 'avg_monthly_principal': principal / total_payments,\n 'avg_monthly_interest': total_interest / total_payments,\n }\n }\n\n def amortization_schedule(\n self,\n principal: float,\n rate: float,\n years: int,\n extra_payment: float = 0,\n payments_per_year: int = 12\n ) -> List[Dict[str, Any]]:\n \"\"\"\n Generate full amortization schedule.\n\n Args:\n principal: Loan amount\n rate: Annual interest rate (%)\n years: Loan term\n extra_payment: Additional payment each period\n payments_per_year: Payments per year\n\n Returns:\n List of payment records\n \"\"\"\n loan = self.loan_payment(principal, rate, years, payments_per_year)\n base_payment = loan['monthly_payment']\n payment = base_payment + extra_payment\n\n periodic_rate = rate / 100 / payments_per_year\n balance = principal\n schedule = []\n total_interest = 0\n total_principal = 0\n\n month = 0\n while balance > 0.01: # Account for rounding\n month += 1\n interest = balance * periodic_rate\n principal_paid = min(payment - interest, balance)\n\n # Handle final payment\n if principal_paid > balance:\n principal_paid = balance\n payment = principal_paid + interest\n\n balance -= principal_paid\n total_interest += interest\n total_principal += principal_paid\n\n schedule.append({\n 'month': month,\n 'payment': payment,\n 'principal': principal_paid,\n 'interest': interest,\n 'balance': max(0, balance),\n 'total_interest': total_interest,\n 'total_principal': total_principal,\n })\n\n if balance \u003c= 0:\n break\n\n return schedule\n\n def prepayment_comparison(\n self,\n principal: float,\n rate: float,\n years: int,\n extra_monthly: float = 0,\n extra_annual: float = 0,\n lump_sum: float = 0,\n lump_sum_month: int = 12\n ) -> Dict[str, Any]:\n \"\"\"\n Compare regular payments vs prepayment scenarios.\n\n Args:\n principal: Loan amount\n rate: Annual interest rate\n years: Loan term\n extra_monthly: Additional monthly payment\n extra_annual: Additional annual payment\n lump_sum: One-time extra payment\n lump_sum_month: Month for lump sum\n\n Returns:\n Comparison results\n \"\"\"\n # Original schedule\n original = self.amortization_schedule(principal, rate, years)\n original_months = len(original)\n original_interest = original[-1]['total_interest']\n\n # With prepayments\n new_schedule = self.amortization_schedule(\n principal, rate, years, extra_payment=extra_monthly\n )\n new_months = len(new_schedule)\n new_interest = new_schedule[-1]['total_interest']\n\n return {\n 'original_months': original_months,\n 'original_interest': original_interest,\n 'new_months': new_months,\n 'new_interest': new_interest,\n 'months_saved': original_months - new_months,\n 'years_saved': (original_months - new_months) / 12,\n 'interest_saved': original_interest - new_interest,\n 'new_term_years': new_months / 12,\n 'extra_payments_total': extra_monthly * new_months,\n }\n\n # ==================== INVESTMENT CALCULATIONS ====================\n\n def future_value(\n self,\n principal: float,\n rate: float,\n years: int,\n compound_frequency: int = 12\n ) -> Dict[str, Any]:\n \"\"\"\n Calculate future value with compound interest.\n\n Args:\n principal: Initial investment\n rate: Annual interest rate (%)\n years: Investment period\n compound_frequency: Compounding periods per year\n\n Returns:\n Future value details\n \"\"\"\n periodic_rate = rate / 100 / compound_frequency\n periods = years * compound_frequency\n\n fv = principal * (1 + periodic_rate) ** periods\n growth = fv - principal\n\n return {\n 'principal': principal,\n 'rate': rate,\n 'years': years,\n 'future_value': fv,\n 'total_growth': growth,\n 'growth_percentage': (growth / principal) * 100,\n 'effective_annual_rate': (1 + periodic_rate) ** compound_frequency - 1,\n }\n\n def investment_growth(\n self,\n principal: float,\n rate: float,\n years: int,\n monthly_contribution: float = 0,\n annual_contribution: float = 0,\n compound_frequency: int = 12\n ) -> Dict[str, Any]:\n \"\"\"\n Calculate investment growth with recurring contributions.\n\n Args:\n principal: Initial investment\n rate: Annual return rate (%)\n years: Investment period\n monthly_contribution: Monthly addition\n annual_contribution: Annual addition\n compound_frequency: Compounding frequency\n\n Returns:\n Growth projection\n \"\"\"\n monthly_rate = rate / 100 / 12\n total_months = years * 12\n\n balance = principal\n yearly_data = []\n total_contributions = principal\n\n for month in range(1, total_months + 1):\n # Monthly growth\n balance *= (1 + monthly_rate)\n\n # Monthly contribution\n if monthly_contribution > 0:\n balance += monthly_contribution\n total_contributions += monthly_contribution\n\n # Annual contribution (at year end)\n if annual_contribution > 0 and month % 12 == 0:\n balance += annual_contribution\n total_contributions += annual_contribution\n\n # Record yearly data\n if month % 12 == 0:\n year = month // 12\n yearly_data.append({\n 'year': year,\n 'balance': balance,\n 'contributions': total_contributions,\n 'growth': balance - total_contributions,\n })\n\n final_growth = balance - total_contributions\n\n return {\n 'principal': principal,\n 'rate': rate,\n 'years': years,\n 'monthly_contribution': monthly_contribution,\n 'final_value': balance,\n 'total_contributions': total_contributions,\n 'total_growth': final_growth,\n 'growth_percentage': (final_growth / total_contributions) * 100,\n 'yearly_data': yearly_data,\n }\n\n def compare_investments(\n self,\n scenarios: List[Dict[str, Any]],\n years: int\n ) -> List[Dict[str, Any]]:\n \"\"\"\n Compare multiple investment scenarios.\n\n Args:\n scenarios: List of scenario dicts with 'name', 'rate', 'principal', 'monthly'\n years: Investment period\n\n Returns:\n Comparison results\n \"\"\"\n results = []\n\n for scenario in scenarios:\n growth = self.investment_growth(\n principal=scenario.get('principal', 0),\n rate=scenario.get('rate', 0),\n years=years,\n monthly_contribution=scenario.get('monthly', 0),\n )\n\n results.append({\n 'name': scenario.get('name', 'Unnamed'),\n 'rate': scenario.get('rate', 0),\n 'final_value': growth['final_value'],\n 'total_contributions': growth['total_contributions'],\n 'total_growth': growth['total_growth'],\n 'growth_percentage': growth['growth_percentage'],\n })\n\n return sorted(results, key=lambda x: x['final_value'], reverse=True)\n\n # ==================== NPV/IRR CALCULATIONS ====================\n\n def npv(\n self,\n cash_flows: List[float],\n discount_rate: float\n ) -> float:\n \"\"\"\n Calculate Net Present Value.\n\n Args:\n cash_flows: List of cash flows (first is typically negative investment)\n discount_rate: Annual discount rate (%)\n\n Returns:\n NPV value\n \"\"\"\n rate = discount_rate / 100\n npv_value = sum(cf / (1 + rate) ** i for i, cf in enumerate(cash_flows))\n return npv_value\n\n def irr(self, cash_flows: List[float]) -> float:\n \"\"\"\n Calculate Internal Rate of Return.\n\n Args:\n cash_flows: List of cash flows\n\n Returns:\n IRR as percentage\n \"\"\"\n if HAS_NPF:\n irr_value = npf.irr(cash_flows)\n return irr_value * 100\n else:\n # Manual IRR calculation\n def npv_func(rate):\n return sum(cf / (1 + rate) ** i for i, cf in enumerate(cash_flows))\n\n try:\n result = optimize.brentq(npv_func, -0.99, 10.0)\n return result * 100\n except ValueError:\n raise FinanceError(\"IRR could not be calculated\")\n\n def payback_period(\n self,\n cash_flows: List[float],\n discount_rate: float = 0\n ) -> Dict[str, float]:\n \"\"\"\n Calculate simple and discounted payback period.\n\n Args:\n cash_flows: List of cash flows\n discount_rate: Optional discount rate for discounted payback\n\n Returns:\n Simple and discounted payback periods\n \"\"\"\n # Simple payback\n cumulative = 0\n simple_payback = None\n for i, cf in enumerate(cash_flows):\n cumulative += cf\n if cumulative >= 0 and simple_payback is None:\n # Interpolate\n simple_payback = i - 1 + (cumulative - cf) / (-cf) if i > 0 else i\n simple_payback = max(0, simple_payback)\n\n # Discounted payback\n rate = discount_rate / 100\n cumulative = 0\n discounted_payback = None\n for i, cf in enumerate(cash_flows):\n discounted_cf = cf / (1 + rate) ** i\n cumulative += discounted_cf\n if cumulative >= 0 and discounted_payback is None:\n discounted_payback = i - 1 + (cumulative - discounted_cf) / (-discounted_cf) if i > 0 else i\n discounted_payback = max(0, discounted_payback)\n\n return {\n 'simple': simple_payback or len(cash_flows),\n 'discounted': discounted_payback or len(cash_flows),\n }\n\n def compare_projects(\n self,\n projects: List[Dict[str, Any]],\n discount_rate: float\n ) -> List[Dict[str, Any]]:\n \"\"\"\n Compare multiple projects using NPV, IRR, and payback.\n\n Args:\n projects: List with 'name' and 'flows' keys\n discount_rate: Discount rate for NPV\n\n Returns:\n Comparison results\n \"\"\"\n results = []\n\n for project in projects:\n flows = project['flows']\n payback = self.payback_period(flows, discount_rate)\n\n results.append({\n 'name': project['name'],\n 'initial_investment': abs(flows[0]),\n 'npv': self.npv(flows, discount_rate),\n 'irr': self.irr(flows),\n 'simple_payback': payback['simple'],\n 'discounted_payback': payback['discounted'],\n })\n\n return sorted(results, key=lambda x: x['npv'], reverse=True)\n\n # ==================== RETIREMENT CALCULATIONS ====================\n\n def retirement_projection(\n self,\n current_age: int,\n retirement_age: int,\n current_savings: float,\n monthly_contribution: float,\n expected_return: float,\n inflation: float = 2.5\n ) -> Dict[str, Any]:\n \"\"\"\n Project retirement savings.\n\n Args:\n current_age: Current age\n retirement_age: Target retirement age\n current_savings: Current retirement savings\n monthly_contribution: Monthly contribution\n expected_return: Expected annual return (%)\n inflation: Expected inflation rate (%)\n\n Returns:\n Retirement projection\n \"\"\"\n years_to_retire = retirement_age - current_age\n if years_to_retire \u003c= 0:\n raise FinanceError(\"Retirement age must be greater than current age\")\n\n # Calculate nominal value\n growth = self.investment_growth(\n principal=current_savings,\n rate=expected_return,\n years=years_to_retire,\n monthly_contribution=monthly_contribution\n )\n\n # Calculate real value (adjusted for inflation)\n inflation_factor = (1 + inflation / 100) ** years_to_retire\n real_value = growth['final_value'] / inflation_factor\n\n return {\n 'current_age': current_age,\n 'retirement_age': retirement_age,\n 'years_to_retirement': years_to_retire,\n 'nominal_value': growth['final_value'],\n 'real_value': real_value,\n 'total_contributions': growth['total_contributions'],\n 'total_growth': growth['total_growth'],\n 'yearly_data': growth['yearly_data'],\n }\n\n def retirement_withdrawal(\n self,\n savings: float,\n annual_spending: float,\n expected_return: float,\n inflation: float,\n years: int\n ) -> Dict[str, Any]:\n \"\"\"\n Analyze retirement withdrawal strategy.\n\n Args:\n savings: Initial retirement savings\n annual_spending: Annual spending requirement\n expected_return: Expected return\n inflation: Expected inflation\n years: Retirement duration\n\n Returns:\n Withdrawal analysis\n \"\"\"\n balance = savings\n yearly_data = []\n spending = annual_spending\n\n for year in range(1, years + 1):\n # Withdraw at start of year\n balance -= spending\n\n # Growth during year\n if balance > 0:\n balance *= (1 + expected_return / 100)\n else:\n balance = 0\n\n # Increase spending for inflation\n spending *= (1 + inflation / 100)\n\n yearly_data.append({\n 'year': year,\n 'spending': spending,\n 'balance': max(0, balance),\n })\n\n if balance \u003c= 0:\n break\n\n success = balance > 0\n\n return {\n 'initial_savings': savings,\n 'annual_spending': annual_spending,\n 'success': success,\n 'success_rate': 100 if success else (len([y for y in yearly_data if y['balance'] > 0]) / years) * 100,\n 'ending_balance': max(0, balance),\n 'years_lasted': len([y for y in yearly_data if y['balance'] > 0]),\n 'yearly_data': yearly_data,\n }\n\n def fire_calculator(\n self,\n annual_expenses: float,\n current_savings: float,\n annual_savings: float,\n expected_return: float,\n safe_withdrawal_rate: float = 4\n ) -> Dict[str, Any]:\n \"\"\"\n Calculate Financial Independence / Retire Early (FIRE) numbers.\n\n Args:\n annual_expenses: Annual expenses in retirement\n current_savings: Current savings\n annual_savings: Annual savings amount\n expected_return: Expected investment return\n safe_withdrawal_rate: Safe withdrawal rate (%)\n\n Returns:\n FIRE analysis\n \"\"\"\n # FIRE number = annual expenses / SWR\n fire_number = annual_expenses / (safe_withdrawal_rate / 100)\n\n # Years to FIRE\n if current_savings >= fire_number:\n years_to_fire = 0\n else:\n monthly_return = expected_return / 100 / 12\n monthly_savings = annual_savings / 12\n balance = current_savings\n months = 0\n\n while balance \u003c fire_number and months \u003c 600: # Max 50 years\n balance = balance * (1 + monthly_return) + monthly_savings\n months += 1\n\n years_to_fire = months / 12\n\n return {\n 'annual_expenses': annual_expenses,\n 'safe_withdrawal_rate': safe_withdrawal_rate,\n 'fire_number': fire_number,\n 'current_savings': current_savings,\n 'gap': max(0, fire_number - current_savings),\n 'years_to_fire': years_to_fire,\n 'monthly_savings_needed': (fire_number - current_savings) / (years_to_fire * 12) if years_to_fire > 0 else 0,\n }\n\n # ==================== MONTE CARLO SIMULATION ====================\n\n def monte_carlo_investment(\n self,\n principal: float,\n monthly_contribution: float,\n years: int,\n mean_return: float,\n std_dev: float,\n simulations: int = 1000\n ) -> Dict[str, Any]:\n \"\"\"\n Monte Carlo simulation for investment growth.\n\n Args:\n principal: Initial investment\n monthly_contribution: Monthly contribution\n years: Investment period\n mean_return: Expected annual return (%)\n std_dev: Annual volatility/standard deviation (%)\n simulations: Number of simulations\n\n Returns:\n Simulation results\n \"\"\"\n np.random.seed(42) # For reproducibility\n months = years * 12\n results = []\n\n for _ in range(simulations):\n balance = principal\n for month in range(months):\n # Random monthly return\n monthly_return = np.random.normal(\n mean_return / 100 / 12,\n std_dev / 100 / np.sqrt(12)\n )\n balance = balance * (1 + monthly_return) + monthly_contribution\n\n results.append(balance)\n\n results = np.array(results)\n\n return {\n 'principal': principal,\n 'monthly_contribution': monthly_contribution,\n 'years': years,\n 'mean_return': mean_return,\n 'volatility': std_dev,\n 'simulations': simulations,\n 'mean': float(np.mean(results)),\n 'median': float(np.median(results)),\n 'std_dev': float(np.std(results)),\n 'min': float(np.min(results)),\n 'max': float(np.max(results)),\n 'p10': float(np.percentile(results, 10)),\n 'p25': float(np.percentile(results, 25)),\n 'p75': float(np.percentile(results, 75)),\n 'p90': float(np.percentile(results, 90)),\n 'prob_above_1m': float((results > 1000000).sum() / simulations * 100),\n 'prob_double': float((results > principal * 2).sum() / simulations * 100),\n 'results': results.tolist(),\n }\n\n def monte_carlo_retirement(\n self,\n savings: float,\n annual_withdrawal: float,\n years: int,\n mean_return: float,\n std_dev: float,\n inflation_mean: float = 2.5,\n inflation_std: float = 1.0,\n simulations: int = 1000\n ) -> Dict[str, Any]:\n \"\"\"\n Monte Carlo simulation for retirement withdrawals.\n\n Args:\n savings: Initial retirement savings\n annual_withdrawal: Initial annual withdrawal\n years: Retirement duration\n mean_return: Expected return\n std_dev: Return volatility\n inflation_mean: Expected inflation\n inflation_std: Inflation volatility\n simulations: Number of simulations\n\n Returns:\n Simulation results\n \"\"\"\n np.random.seed(42)\n results = []\n success_count = 0\n\n for _ in range(simulations):\n balance = savings\n withdrawal = annual_withdrawal\n\n for year in range(years):\n # Random return\n annual_return = np.random.normal(mean_return / 100, std_dev / 100)\n\n # Random inflation\n inflation = np.random.normal(inflation_mean / 100, inflation_std / 100)\n\n # Withdraw and grow\n balance -= withdrawal\n if balance > 0:\n balance *= (1 + annual_return)\n else:\n balance = 0\n break\n\n # Adjust withdrawal for inflation\n withdrawal *= (1 + inflation)\n\n results.append(balance)\n if balance > 0:\n success_count += 1\n\n results = np.array(results)\n\n return {\n 'initial_savings': savings,\n 'annual_withdrawal': annual_withdrawal,\n 'years': years,\n 'simulations': simulations,\n 'success_rate': success_count / simulations * 100,\n 'mean_ending': float(np.mean(results)),\n 'median_ending': float(np.median(results)),\n 'p10_ending': float(np.percentile(results, 10)),\n 'p90_ending': float(np.percentile(results, 90)),\n 'worst_case': float(np.min(results)),\n 'best_case': float(np.max(results)),\n }\n\n # ==================== MORTGAGE CALCULATIONS ====================\n\n def mortgage_affordability(\n self,\n annual_income: float,\n monthly_debt: float,\n down_payment: float,\n rate: float,\n term_years: int = 30,\n dti_limit: float = 43,\n property_tax_rate: float = 1.2,\n insurance_rate: float = 0.5\n ) -> Dict[str, Any]:\n \"\"\"\n Calculate maximum affordable home price.\n\n Args:\n annual_income: Gross annual income\n monthly_debt: Existing monthly debt payments\n down_payment: Available down payment\n rate: Mortgage interest rate\n term_years: Loan term\n dti_limit: Maximum debt-to-income ratio\n property_tax_rate: Annual property tax as % of home value\n insurance_rate: Annual insurance as % of home value\n\n Returns:\n Affordability analysis\n \"\"\"\n monthly_income = annual_income / 12\n max_total_debt = monthly_income * (dti_limit / 100)\n max_housing = max_total_debt - monthly_debt\n\n # Back-calculate max loan from max payment\n monthly_rate = rate / 100 / 12\n n_payments = term_years * 12\n\n if monthly_rate > 0:\n # Include property tax and insurance in housing payment\n # Assume max ~80% of housing payment goes to P&I\n max_pi = max_housing * 0.75\n\n max_loan = max_pi * (\n (1 - (1 + monthly_rate) ** (-n_payments)) / monthly_rate\n )\n else:\n max_loan = max_housing * 0.75 * n_payments\n\n max_price = max_loan + down_payment\n\n # Actual payment breakdown\n loan = self.loan_payment(max_loan, rate, term_years)\n monthly_tax = (max_price * property_tax_rate / 100) / 12\n monthly_insurance = (max_price * insurance_rate / 100) / 12\n total_payment = loan['monthly_payment'] + monthly_tax + monthly_insurance\n\n return {\n 'max_home_price': max_price,\n 'max_loan': max_loan,\n 'down_payment': down_payment,\n 'down_payment_percent': (down_payment / max_price) * 100,\n 'monthly_payment': loan['monthly_payment'],\n 'monthly_tax': monthly_tax,\n 'monthly_insurance': monthly_insurance,\n 'total_monthly': total_payment,\n 'dti_ratio': (total_payment + monthly_debt) / monthly_income * 100,\n }\n\n def refinance_analysis(\n self,\n current_balance: float,\n current_rate: float,\n current_payment: float,\n remaining_months: int,\n new_rate: float,\n new_term_years: int,\n closing_costs: float\n ) -> Dict[str, Any]:\n \"\"\"\n Analyze refinancing decision.\n\n Args:\n current_balance: Remaining loan balance\n current_rate: Current interest rate\n current_payment: Current monthly payment\n remaining_months: Remaining months on current loan\n new_rate: New interest rate\n new_term_years: New loan term\n closing_costs: Refinancing costs\n\n Returns:\n Refinance analysis\n \"\"\"\n # Current loan remaining cost\n current_total = current_payment * remaining_months\n\n # New loan\n new_loan = self.loan_payment(current_balance + closing_costs, new_rate, new_term_years)\n new_total = new_loan['monthly_payment'] * new_term_years * 12\n\n # Monthly savings\n monthly_savings = current_payment - new_loan['monthly_payment']\n\n # Break-even\n if monthly_savings > 0:\n break_even = closing_costs / monthly_savings\n else:\n break_even = float('inf')\n\n # Lifetime savings\n lifetime_savings = current_total - new_total - closing_costs\n\n return {\n 'current_balance': current_balance,\n 'current_rate': current_rate,\n 'current_payment': current_payment,\n 'new_rate': new_rate,\n 'new_payment': new_loan['monthly_payment'],\n 'monthly_savings': monthly_savings,\n 'closing_costs': closing_costs,\n 'break_even_months': break_even,\n 'break_even_years': break_even / 12,\n 'current_remaining_cost': current_total,\n 'new_total_cost': new_total,\n 'lifetime_savings': lifetime_savings,\n 'recommend_refinance': lifetime_savings > 0 and break_even \u003c remaining_months,\n }\n\n # ==================== SAVINGS GOAL ====================\n\n def savings_goal(\n self,\n target: float,\n current: float,\n rate: float,\n monthly_contribution: float\n ) -> Dict[str, Any]:\n \"\"\"\n Calculate time to reach savings goal.\n\n Args:\n target: Target savings amount\n current: Current savings\n rate: Annual return rate\n monthly_contribution: Monthly contribution\n\n Returns:\n Time to goal\n \"\"\"\n if current >= target:\n return {\n 'target': target,\n 'current': current,\n 'months': 0,\n 'years': 0,\n 'already_reached': True,\n }\n\n monthly_rate = rate / 100 / 12\n balance = current\n months = 0\n\n while balance \u003c target and months \u003c 1200: # Max 100 years\n balance = balance * (1 + monthly_rate) + monthly_contribution\n months += 1\n\n return {\n 'target': target,\n 'current': current,\n 'gap': target - current,\n 'rate': rate,\n 'monthly_contribution': monthly_contribution,\n 'months': months,\n 'years': months / 12,\n 'final_balance': balance,\n }\n\n def required_savings(\n self,\n target: float,\n current: float,\n rate: float,\n years: int\n ) -> Dict[str, Any]:\n \"\"\"\n Calculate required monthly savings to reach goal.\n\n Args:\n target: Target amount\n current: Current savings\n rate: Annual return rate\n years: Time period\n\n Returns:\n Required monthly savings\n \"\"\"\n months = years * 12\n monthly_rate = rate / 100 / 12\n\n # Future value of current savings\n fv_current = current * (1 + monthly_rate) ** months\n\n # Gap to fill with monthly payments\n gap = target - fv_current\n\n if gap \u003c= 0:\n return {\n 'target': target,\n 'current': current,\n 'monthly_needed': 0,\n 'already_sufficient': True,\n }\n\n # PMT formula\n if monthly_rate > 0:\n monthly_needed = gap * monthly_rate / ((1 + monthly_rate) ** months - 1)\n else:\n monthly_needed = gap / months\n\n return {\n 'target': target,\n 'current': current,\n 'years': years,\n 'rate': rate,\n 'monthly_needed': monthly_needed,\n 'annual_needed': monthly_needed * 12,\n 'total_contributions': monthly_needed * months,\n 'fv_current': fv_current,\n }\n\n # ==================== EXPORT FUNCTIONS ====================\n\n def export_amortization(\n self,\n schedule: List[Dict],\n filepath: Union[str, Path]\n ) -> str:\n \"\"\"Export amortization schedule to CSV.\"\"\"\n filepath = Path(filepath)\n df = pd.DataFrame(schedule)\n df.to_csv(filepath, index=False)\n return str(filepath)\n\n def export_json(\n self,\n data: Dict[str, Any],\n filepath: Union[str, Path]\n ) -> str:\n \"\"\"Export results to JSON.\"\"\"\n filepath = Path(filepath)\n\n # Convert numpy types\n def convert(obj):\n if isinstance(obj, np.ndarray):\n return obj.tolist()\n if isinstance(obj, (np.int64, np.int32)):\n return int(obj)\n if isinstance(obj, (np.float64, np.float32)):\n return float(obj)\n return obj\n\n clean_data = json.loads(json.dumps(data, default=convert))\n filepath.write_text(json.dumps(clean_data, indent=2))\n return str(filepath)\n\n # ==================== VISUALIZATION ====================\n\n def plot_amortization(\n self,\n schedule: List[Dict],\n filepath: Union[str, Path]\n ) -> str:\n \"\"\"Plot amortization schedule.\"\"\"\n df = pd.DataFrame(schedule)\n\n fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(10, 8))\n\n # Balance over time\n ax1.plot(df['month'], df['balance'], 'b-', linewidth=2)\n ax1.fill_between(df['month'], df['balance'], alpha=0.3)\n ax1.set_xlabel('Month')\n ax1.set_ylabel('Remaining Balance')\n ax1.set_title('Loan Balance Over Time')\n ax1.grid(True, alpha=0.3)\n\n # Principal vs Interest\n ax2.stackplot(\n df['month'],\n df['principal'],\n df['interest'],\n labels=['Principal', 'Interest'],\n alpha=0.7\n )\n ax2.set_xlabel('Month')\n ax2.set_ylabel('Payment Amount')\n ax2.set_title('Principal vs Interest Over Time')\n ax2.legend(loc='upper right')\n ax2.grid(True, alpha=0.3)\n\n plt.tight_layout()\n plt.savefig(filepath, dpi=150, bbox_inches='tight')\n plt.close()\n\n return str(filepath)\n\n def plot_investment_growth(\n self,\n growth_data: Dict[str, Any],\n filepath: Union[str, Path]\n ) -> str:\n \"\"\"Plot investment growth over time.\"\"\"\n yearly = growth_data['yearly_data']\n df = pd.DataFrame(yearly)\n\n fig, ax = plt.subplots(figsize=(10, 6))\n\n ax.bar(df['year'], df['contributions'], label='Contributions', alpha=0.7)\n ax.bar(df['year'], df['growth'], bottom=df['contributions'], label='Growth', alpha=0.7)\n\n ax.set_xlabel('Year')\n ax.set_ylabel('Value')\n ax.set_title('Investment Growth Over Time')\n ax.legend()\n ax.grid(True, alpha=0.3)\n\n plt.tight_layout()\n plt.savefig(filepath, dpi=150, bbox_inches='tight')\n plt.close()\n\n return str(filepath)\n\n def plot_monte_carlo(\n self,\n simulation: Dict[str, Any],\n filepath: Union[str, Path]\n ) -> str:\n \"\"\"Plot Monte Carlo simulation results.\"\"\"\n results = simulation['results']\n\n fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5))\n\n # Histogram\n ax1.hist(results, bins=50, edgecolor='white', alpha=0.7)\n ax1.axvline(simulation['median'], color='red', linestyle='--', label=f\"Median: ${simulation['median']:,.0f}\")\n ax1.axvline(simulation['p10'], color='orange', linestyle=':', label=f\"10th %ile: ${simulation['p10']:,.0f}\")\n ax1.axvline(simulation['p90'], color='green', linestyle=':', label=f\"90th %ile: ${simulation['p90']:,.0f}\")\n ax1.set_xlabel('Final Value')\n ax1.set_ylabel('Frequency')\n ax1.set_title('Distribution of Outcomes')\n ax1.legend()\n\n # Box plot\n ax2.boxplot(results, vert=True)\n ax2.set_ylabel('Final Value')\n ax2.set_title('Outcome Range')\n\n plt.tight_layout()\n plt.savefig(filepath, dpi=150, bbox_inches='tight')\n plt.close()\n\n return str(filepath)\n\n\n# ==================== CLI ====================\n\nif __name__ == \"__main__\":\n import argparse\n\n parser = argparse.ArgumentParser(description='Financial Calculator Suite')\n subparsers = parser.add_subparsers(dest='command', help='Calculator type')\n\n # Loan subcommand\n loan_parser = subparsers.add_parser('loan', help='Loan calculator')\n loan_parser.add_argument('--principal', type=float, required=True, help='Loan amount')\n loan_parser.add_argument('--rate', type=float, required=True, help='Interest rate (%)')\n loan_parser.add_argument('--years', type=int, required=True, help='Loan term')\n\n # Investment subcommand\n invest_parser = subparsers.add_parser('invest', help='Investment calculator')\n invest_parser.add_argument('--principal', type=float, required=True, help='Initial investment')\n invest_parser.add_argument('--rate', type=float, required=True, help='Expected return (%)')\n invest_parser.add_argument('--years', type=int, required=True, help='Investment period')\n invest_parser.add_argument('--monthly', type=float, default=0, help='Monthly contribution')\n\n # NPV subcommand\n npv_parser = subparsers.add_parser('npv', help='NPV/IRR calculator')\n npv_parser.add_argument('--flows', type=str, required=True, help='Cash flows (comma-separated)')\n npv_parser.add_argument('--rate', type=float, required=True, help='Discount rate (%)')\n\n # Retirement subcommand\n retire_parser = subparsers.add_parser('retire', help='Retirement calculator')\n retire_parser.add_argument('--age', type=int, required=True, help='Current age')\n retire_parser.add_argument('--retire-age', type=int, required=True, help='Retirement age')\n retire_parser.add_argument('--savings', type=float, required=True, help='Current savings')\n retire_parser.add_argument('--monthly', type=float, required=True, help='Monthly contribution')\n retire_parser.add_argument('--return', type=float, default=7, help='Expected return (%)')\n\n # Monte Carlo subcommand\n mc_parser = subparsers.add_parser('montecarlo', help='Monte Carlo simulation')\n mc_parser.add_argument('--principal', type=float, required=True, help='Initial investment')\n mc_parser.add_argument('--years', type=int, required=True, help='Investment period')\n mc_parser.add_argument('--return', type=float, required=True, help='Expected return (%)')\n mc_parser.add_argument('--volatility', type=float, required=True, help='Volatility (%)')\n mc_parser.add_argument('--monthly', type=float, default=0, help='Monthly contribution')\n\n args = parser.parse_args()\n calc = FinancialCalculator()\n\n if args.command == 'loan':\n result = calc.loan_payment(args.principal, args.rate, args.years)\n print(f\"Monthly Payment: ${result['monthly_payment']:,.2f}\")\n print(f\"Total Interest: ${result['total_interest']:,.2f}\")\n print(f\"Total Payments: ${result['total_payments']:,.2f}\")\n\n elif args.command == 'invest':\n result = calc.investment_growth(\n args.principal, args.rate, args.years, args.monthly\n )\n print(f\"Final Value: ${result['final_value']:,.2f}\")\n print(f\"Total Contributions: ${result['total_contributions']:,.2f}\")\n print(f\"Total Growth: ${result['total_growth']:,.2f}\")\n\n elif args.command == 'npv':\n flows = [float(x) for x in args.flows.split(',')]\n npv = calc.npv(flows, args.rate)\n irr = calc.irr(flows)\n print(f\"NPV: ${npv:,.2f}\")\n print(f\"IRR: {irr:.2f}%\")\n\n elif args.command == 'retire':\n result = calc.retirement_projection(\n args.age, args.retire_age, args.savings,\n args.monthly, getattr(args, 'return')\n )\n print(f\"Projected at retirement: ${result['nominal_value']:,.2f}\")\n print(f\"Real value (today's $): ${result['real_value']:,.2f}\")\n\n elif args.command == 'montecarlo':\n result = calc.monte_carlo_investment(\n args.principal, args.monthly, args.years,\n getattr(args, 'return'), args.volatility\n )\n print(f\"Median outcome: ${result['median']:,.2f}\")\n print(f\"10th percentile: ${result['p10']:,.2f}\")\n print(f\"90th percentile: ${result['p90']:,.2f}\")\n\n else:\n parser.print_help()\n","content_type":"text/x-python; charset=utf-8","language":"python","size":40146,"content_sha256":"15f1bae3145baf83f2a14daf05a4e66ebcaa22a32123305e14f95001fbab83ee"},{"filename":"scripts/requirements.txt","content":"numpy>=1.24.0\nnumpy-financial>=1.0.0\npandas>=2.0.0\nmatplotlib>=3.7.0\nscipy>=1.10.0\n","content_type":"text/plain; charset=utf-8","language":null,"size":83,"content_sha256":"fa903a9f7132031091bb4b1651ef6f4faeb9a166a29bbf4dd676e77fdbc0f5bd"}],"content_json":{"type":"doc","content":[{"type":"heading","attrs":{"level":1},"content":[{"text":"Financial Calculator","type":"text"}]},{"type":"paragraph","content":[{"text":"Use deterministic calculations instead of ad hoc spreadsheet math when the user needs precise financial outputs.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Use This For","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Loan and mortgage math","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Investment growth and savings goals","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"NPV, IRR, and payback analysis","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Retirement projections and withdrawal planning","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Monte Carlo style risk scenarios","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Workflow","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Confirm units and assumptions first: rates, compounding, time horizon, taxes, and inflation.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Use ","type":"text"},{"text":"scripts/financial_calc.py","type":"text","marks":[{"type":"code_inline"}]},{"text":" as the source of truth for the computation.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Return both the answer and the assumptions that materially drive it.","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Guardrails","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Treat outputs as calculations, not personalized financial advice.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Surface simplifying assumptions and scenario sensitivity.","type":"text"}]}]}]},{"type":"hr","attrs":{"markup":"---"}}]},"metadata":{"date":"2026-06-05","name":"financial-calculator","author":"@skillopedia","source":{"stars":60,"repo_name":"chatgpt-skills","origin_url":"https://github.com/dkyazzentwatwa/chatgpt-skills/blob/HEAD/financial-calculator/SKILL.md","repo_owner":"dkyazzentwatwa","body_sha256":"fdd842c0d0201b046b6f50416d537375744fecf05d76dc824fc387a90feb9ec0","cluster_key":"399ef10963b8e59334fc66763b7ad6002cff4a8929a88e48546cf55f61395f69","clean_bundle":{"format":"clean-skill-bundle-v1","source":"dkyazzentwatwa/chatgpt-skills/financial-calculator/SKILL.md","attachments":[{"id":"6bc0c289-9416-50e3-a373-80bd4f78dd1f","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/6bc0c289-9416-50e3-a373-80bd4f78dd1f/attachment.yaml","path":"agents/openai.yaml","size":194,"sha256":"89f0cfbc0796f23e22b3843f78fdf18b342f3f2488ba0626675534fd811f6cc8","contentType":"application/yaml; charset=utf-8"},{"id":"17918e78-669f-5ddc-81c8-dbed527f04b6","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/17918e78-669f-5ddc-81c8-dbed527f04b6/attachment.py","path":"scripts/financial_calc.py","size":40146,"sha256":"15f1bae3145baf83f2a14daf05a4e66ebcaa22a32123305e14f95001fbab83ee","contentType":"text/x-python; charset=utf-8"},{"id":"b869484b-68f9-5ed8-a877-50ff58dd5ed4","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/b869484b-68f9-5ed8-a877-50ff58dd5ed4/attachment.txt","path":"scripts/requirements.txt","size":83,"sha256":"fa903a9f7132031091bb4b1651ef6f4faeb9a166a29bbf4dd676e77fdbc0f5bd","contentType":"text/plain; charset=utf-8"}],"bundle_sha256":"05408c1ac375b73b31de36ed71299be0f13b6f39edab7e93fa5e37c19aad541b","attachment_count":3,"text_attachments":3,"attachment_storage":"skillopedia-attachments-v1","binary_attachments":0,"excluded_attachments":[]},"cluster_size":1,"skill_md_path":"financial-calculator/SKILL.md","import_metadata":{"date":"2026-06-05","author":"@skillopedia","version":"v1","category":"data-analytics","category_label":"Data"},"exact_dupes_collapsed_into_this":0},"version":"v1","category":"data-analytics","import_tag":"clean-skills-v1","description":"Run loan, investment, NPV, retirement, savings, and risk calculations with schedules and charts. Use for deterministic financial modeling tasks."}},"renderedAt":1782982930373}

Financial Calculator Use deterministic calculations instead of ad hoc spreadsheet math when the user needs precise financial outputs. Use This For - Loan and mortgage math - Investment growth and savings goals - NPV, IRR, and payback analysis - Retirement projections and withdrawal planning - Monte Carlo style risk scenarios Workflow 1. Confirm units and assumptions first: rates, compounding, time horizon, taxes, and inflation. 2. Use as the source of truth for the computation. 3. Return both the answer and the assumptions that materially drive it. Guardrails - Treat outputs as calculations,…