Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Project — Chapter 83: Financial Growth Model

Prerequisites: ch058 (Linear), ch059 (Quadratic), ch042 (Exponential Growth)

Concepts: Compound interest, exponential vs logistic growth, present value, IRR

Output: Multi-scenario investment comparison with NPV, IRR, and sensitivity

Difficulty: Intermediate | ~45 minutes


Stage 1 — Setup and Compound Interest

import numpy as np
import matplotlib.pyplot as plt
plt.style.use('seaborn-v0_8-whitegrid')

def compound_interest(P, r, n, t):
    """Compound interest: A = P(1 + r/n)^(nt)."""
    return P * (1 + r/n)**(n*t)

def continuous_compound(P, r, t):
    """Continuous compounding: A = P*e^(rt)."""
    return P * np.exp(r * t)

P0, r = 10000, 0.07  # $10k at 7% annual
t = np.linspace(0, 40, 400)

fig, axes = plt.subplots(1, 2, figsize=(14, 5))
for n, label, color in [(1,'Annual','steelblue'),(4,'Quarterly','crimson'),(12,'Monthly','darkgreen'),(365,'Daily','darkorange')]:
    axes[0].plot(t, compound_interest(P0, r, n, t)/1000, color=color, linewidth=2, label=label)
axes[0].plot(t, continuous_compound(P0, r, t)/1000, 'k--', linewidth=2, label='Continuous')
axes[0].set_title('$10,000 at 7% — Effect of Compounding Frequency')
axes[0].set_xlabel('Years'); axes[0].set_ylabel('Value ($k)'); axes[0].legend(fontsize=9)

# Doubling time: rule of 72 (approx) vs exact
rates = np.linspace(0.01, 0.20, 100)
exact_double = np.log(2) / rates
rule72 = 72 / (rates * 100)
axes[1].plot(rates*100, exact_double, color='steelblue', linewidth=2, label='Exact: ln(2)/r')
axes[1].plot(rates*100, rule72, color='crimson', linewidth=2, linestyle='--', label='Rule of 72')
axes[1].set_title('Doubling Time vs Interest Rate'); axes[1].set_xlabel('Annual rate (%)')
axes[1].set_ylabel('Years to double'); axes[1].legend()

plt.suptitle('Financial Growth Models', fontsize=13, fontweight='bold')
plt.tight_layout(); plt.show()
<Figure size 1400x500 with 2 Axes>

Stage 2 — NPV and IRR

# Net Present Value and Internal Rate of Return
def npv(cashflows, rate):
    """NPV: Σ CFₜ / (1+r)^t."""
    return sum(cf / (1+rate)**t for t, cf in enumerate(cashflows))

def irr(cashflows, tol=1e-6, max_iter=100):
    """Find IRR: the rate where NPV=0 (bisection method)."""
    lo, hi = -0.999, 10.0
    for _ in range(max_iter):
        mid = (lo + hi) / 2
        if npv(cashflows, mid) > 0:
            lo = mid
        else:
            hi = mid
        if hi - lo < tol:
            break
    return mid

# Compare three investment scenarios
scenarios = {
    'Conservative bonds':  [-10000] + [600]*15 + [10000],
    'Growth stocks':       [-10000] + [-500]*3 + [2000]*12 + [15000],
    'Real estate':         [-10000, -2000] + [800]*10 + [20000],
}

rates = np.linspace(0.01, 0.20, 200)
fig, ax = plt.subplots(figsize=(10, 5))
for name, cfs in scenarios.items():
    npvs = [npv(cfs, r) for r in rates]
    irr_val = irr(cfs)
    ax.plot(rates*100, npvs, linewidth=2, label=f'{name} (IRR={irr_val*100:.1f}%)')
ax.axhline(0, color='black', linewidth=0.8)
ax.set_title('NPV vs Discount Rate for Three Investment Scenarios')
ax.set_xlabel('Discount rate (%)'); ax.set_ylabel('NPV ($)'); ax.legend(fontsize=9)
plt.tight_layout(); plt.show()
<Figure size 1000x500 with 1 Axes>

Stage 3 — Logistic Savings Model

# Savings with diminishing returns (logistic model)
# Motivated by: hard to save the first $100k, harder still above that
def savings_model(t, S0, K, r):
    """Logistic savings growth with capacity K."""
    return K / (1 + (K/S0 - 1) * np.exp(-r * t))

t = np.linspace(0, 40, 500)
fig, ax = plt.subplots(figsize=(10, 5))
for K, color in [(500000,'steelblue'),(1000000,'crimson'),(2000000,'darkgreen')]:
    ax.plot(t, savings_model(t, 10000, K, 0.12)/1000, color=color, linewidth=2, label=f'K=${K//1000}k')
ax.set_title('Logistic Savings Growth: Realistic Capacity Constraints')
ax.set_xlabel('Years'); ax.set_ylabel('Savings ($k)'); ax.legend()
plt.tight_layout(); plt.show()
<Figure size 1000x500 with 1 Axes>

Results & Reflection

What was built: Compound interest calculator, NPV/IRR analysis, and logistic savings model.

Math used: Exponential growth (ch042), logistic functions (ch064), iterative root finding for IRR (ch074).

Extensions: 1) Add inflation-adjusted returns. 2) Monte Carlo simulation with random return rates. 3) Compare DCA vs lump-sum investing strategies.