{
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "# 📘 Fincept Notebook — Time Value of Money\n",
        "\n",
        "**Finance · Beginner · ~10 min · Standard library only**\n",
        "\n",
        "Money today is worth more than the same money tomorrow. This notebook walks through the core building blocks of valuation: discounting and compounding a single cash flow, valuing a stream of cash flows with NPV, solving for the internal rate of return (IRR), and breaking a loan into its amortization schedule.\n",
        "\n",
        "**What you'll learn**\n",
        "- Present value (PV) and future value (FV), and how compounding frequency changes the answer\n",
        "- Net present value (NPV) of a cash-flow stream at a given discount rate\n",
        "- How to solve for IRR numerically with the bisection method\n",
        "- How to build a loan amortization schedule\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## 1. Present value & future value\n",
        "\n",
        "A single cash flow `C` grows to a **future value** `FV = C · (1 + r/m)^(m·t)` and a future amount discounts back to a **present value** `PV = C / (1 + r/m)^(m·t)`, where `r` is the annual rate, `m` is the compounding frequency per year, and `t` is years.\n",
        "\n",
        "The table below shows how $1,000 grows over time and how the discount rate changes today's value of a fixed future amount.\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "fincept_title": "PV / FV tables"
      },
      "outputs": [],
      "source": [
        "import math\n",
        "\n",
        "def future_value(pv, annual_rate, years, m=1):\n",
        "    \"\"\"Future value of a lump sum with m compounding periods per year.\"\"\"\n",
        "    return pv * (1 + annual_rate / m) ** (m * years)\n",
        "\n",
        "def present_value(fv, annual_rate, years, m=1):\n",
        "    \"\"\"Present value of a future lump sum.\"\"\"\n",
        "    return fv / (1 + annual_rate / m) ** (m * years)\n",
        "\n",
        "# --- Worked example -------------------------------------------------\n",
        "principal = 1000.0\n",
        "rate = 0.06          # 6% annual\n",
        "years = 5\n",
        "fv_annual = future_value(principal, rate, years, m=1)\n",
        "print(f\"Invest ${principal:,.2f} at {rate:.1%} for {years} years (annual compounding):\")\n",
        "print(f\"  Future value = ${fv_annual:,.2f}\")\n",
        "print(f\"  Check PV     = ${present_value(fv_annual, rate, years, m=1):,.2f}  (should return the principal)\")\n",
        "\n",
        "# --- Compounding frequency matters ----------------------------------\n",
        "print(\"\\nEffect of compounding frequency ($1,000 at 6% for 5 years):\")\n",
        "print(f\"{'Frequency':<14}{'m':>4}{'Future value':>16}\")\n",
        "print('-' * 34)\n",
        "for label, m in [('Annual', 1), ('Semiannual', 2), ('Quarterly', 4), ('Monthly', 12), ('Daily', 365)]:\n",
        "    print(f\"{label:<14}{m:>4}{future_value(principal, rate, years, m):>16,.2f}\")\n",
        "\n",
        "# --- FV grid over several years and rates ---------------------------\n",
        "rates = [0.03, 0.05, 0.07, 0.10]\n",
        "horizons = [1, 3, 5, 10, 20]\n",
        "print(\"\\nFuture value of $1,000 (annual compounding):\")\n",
        "header = f\"{'Years':>6}\" + ''.join(f\"{r:>12.0%}\" for r in rates)\n",
        "print(header)\n",
        "print('-' * len(header))\n",
        "for t in horizons:\n",
        "    row = f\"{t:>6}\" + ''.join(f\"{future_value(principal, r, t):>12,.0f}\" for r in rates)\n",
        "    print(row)\n",
        "\n",
        "# --- PV of a fixed future amount at several discount rates ----------\n",
        "target = 10000.0\n",
        "print(f\"\\nPresent value of ${target:,.0f} received in 5 years:\")\n",
        "print(f\"{'Discount rate':>14}{'Present value':>16}\")\n",
        "print('-' * 30)\n",
        "for r in rates:\n",
        "    print(f\"{r:>14.0%}{present_value(target, r, 5):>16,.2f}\")\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## 2. Net present value of a cash-flow stream\n",
        "\n",
        "A project's **NPV** discounts every future cash flow back to today and sums them:\n",
        "\n",
        "`NPV = Σ  Cₜ / (1 + r)ᵗ`  for t = 0, 1, 2, …\n",
        "\n",
        "Year 0 is the up-front outlay (a negative number). A positive NPV means the project adds value at the chosen discount rate. Below we value a project with an initial $10,000 investment followed by five years of inflows, and tabulate how NPV falls as the discount rate rises.\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "fincept_title": "Compute NPV"
      },
      "outputs": [],
      "source": [
        "def npv(rate, cashflows):\n",
        "    \"\"\"NPV of cashflows[0..n] where index = period (0 = today).\"\"\"\n",
        "    return sum(cf / (1 + rate) ** t for t, cf in enumerate(cashflows))\n",
        "\n",
        "# Initial outlay then five annual inflows\n",
        "cashflows = [-10000, 2500, 3000, 3500, 4000, 4500]\n",
        "discount_rate = 0.08\n",
        "\n",
        "print(\"Project cash flows:\")\n",
        "print(f\"{'Year':>5}{'Cash flow':>14}{'Discount factor':>18}{'Present value':>16}\")\n",
        "print('-' * 53)\n",
        "for t, cf in enumerate(cashflows):\n",
        "    df = 1 / (1 + discount_rate) ** t\n",
        "    print(f\"{t:>5}{cf:>14,.2f}{df:>18.4f}{cf * df:>16,.2f}\")\n",
        "print('-' * 53)\n",
        "print(f\"{'NPV at ' + format(discount_rate, '.0%'):>23}{'':>18}{npv(discount_rate, cashflows):>16,.2f}\")\n",
        "\n",
        "# NPV at a range of discount rates\n",
        "print(\"\\nNPV sensitivity to the discount rate:\")\n",
        "print(f\"{'Discount rate':>14}{'NPV':>16}\")\n",
        "print('-' * 30)\n",
        "for r in [0.00, 0.04, 0.08, 0.12, 0.16, 0.20]:\n",
        "    print(f\"{r:>14.0%}{npv(r, cashflows):>16,.2f}\")\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## 3. Internal rate of return (IRR) by bisection\n",
        "\n",
        "The **IRR** is the discount rate that makes NPV exactly zero — the project's break-even rate of return. There is no closed-form solution in general, so we find it numerically. The **bisection method** brackets the IRR between a low and high rate (where NPV changes sign) and repeatedly halves the interval until it converges.\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "fincept_title": "Solve IRR"
      },
      "outputs": [],
      "source": [
        "def npv(rate, cashflows):\n",
        "    return sum(cf / (1 + rate) ** t for t, cf in enumerate(cashflows))\n",
        "\n",
        "def irr_bisection(cashflows, low=-0.9, high=1.0, tol=1e-9, max_iter=200):\n",
        "    \"\"\"Find IRR via bisection. Requires NPV(low) and NPV(high) to differ in sign.\"\"\"\n",
        "    f_low, f_high = npv(low, cashflows), npv(high, cashflows)\n",
        "    if f_low * f_high > 0:\n",
        "        raise ValueError(\"NPV does not change sign on the bracket; widen [low, high].\")\n",
        "    for _ in range(max_iter):\n",
        "        mid = (low + high) / 2\n",
        "        f_mid = npv(mid, cashflows)\n",
        "        if abs(f_mid) < tol:\n",
        "            return mid\n",
        "        if f_low * f_mid < 0:\n",
        "            high, f_high = mid, f_mid\n",
        "        else:\n",
        "            low, f_low = mid, f_mid\n",
        "    return (low + high) / 2\n",
        "\n",
        "# Same project as before (re-embedded so this cell stands alone)\n",
        "cashflows = [-10000, 2500, 3000, 3500, 4000, 4500]\n",
        "\n",
        "irr = irr_bisection(cashflows)\n",
        "print(f\"IRR = {irr:.6f}  ({irr:.2%})\")\n",
        "print(f\"Verification: NPV at the IRR = {npv(irr, cashflows):,.6f}  (should be ~0)\")\n",
        "\n",
        "print(\"\\nDecision rule: accept the project when IRR exceeds your cost of capital.\")\n",
        "for hurdle in [0.06, 0.08, 0.12, 0.15]:\n",
        "    verdict = 'ACCEPT' if irr > hurdle else 'REJECT'\n",
        "    print(f\"  Hurdle rate {hurdle:>5.0%}  ->  {verdict}\")\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## 4. Loan amortization schedule\n",
        "\n",
        "A fully-amortizing loan is repaid in equal periodic payments. Each payment covers the period's interest first, and whatever is left reduces the principal. The level payment is:\n",
        "\n",
        "`PMT = P · i / (1 − (1 + i)^(−n))`\n",
        "\n",
        "where `P` is the principal, `i` is the per-period rate, and `n` is the number of payments. As the balance shrinks, the interest portion falls and the principal portion grows.\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "fincept_title": "Amortization schedule"
      },
      "outputs": [],
      "source": [
        "def level_payment(principal, annual_rate, years, periods_per_year=12):\n",
        "    \"\"\"Equal periodic payment for a fully-amortizing loan.\"\"\"\n",
        "    i = annual_rate / periods_per_year\n",
        "    n = years * periods_per_year\n",
        "    if i == 0:\n",
        "        return principal / n\n",
        "    return principal * i / (1 - (1 + i) ** -n)\n",
        "\n",
        "# A $25,000 loan at 7.5% APR over 2 years, monthly payments\n",
        "principal = 25000.0\n",
        "annual_rate = 0.075\n",
        "years = 2\n",
        "ppy = 12\n",
        "i = annual_rate / ppy\n",
        "n = years * ppy\n",
        "pmt = level_payment(principal, annual_rate, years, ppy)\n",
        "\n",
        "print(f\"Loan: ${principal:,.2f} at {annual_rate:.2%} APR, {n} monthly payments\")\n",
        "print(f\"Level monthly payment = ${pmt:,.2f}\\n\")\n",
        "\n",
        "print(f\"{'Pmt':>4}{'Payment':>12}{'Interest':>12}{'Principal':>12}{'Balance':>14}\")\n",
        "print('-' * 54)\n",
        "balance = principal\n",
        "total_interest = 0.0\n",
        "for k in range(1, n + 1):\n",
        "    interest = balance * i\n",
        "    principal_paid = pmt - interest\n",
        "    balance -= principal_paid\n",
        "    if k == n:  # absorb tiny rounding drift in the final payment\n",
        "        principal_paid += balance\n",
        "        balance = 0.0\n",
        "    total_interest += interest\n",
        "    print(f\"{k:>4}{pmt:>12,.2f}{interest:>12,.2f}{principal_paid:>12,.2f}{balance:>14,.2f}\")\n",
        "print('-' * 54)\n",
        "print(f\"Total paid     = ${pmt * n:,.2f}\")\n",
        "print(f\"Total interest = ${total_interest:,.2f}\")\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "---\n",
        "*— Fincept Notebook · part of Fincept Terminal. Edit any cell and press Ctrl+Enter to run.*\n"
      ]
    }
  ],
  "metadata": {
    "kernelspec": {
      "display_name": "Python 3",
      "language": "python",
      "name": "python3"
    },
    "language_info": {
      "name": "python"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 5
}
