Chapter 7: Financial Analysis ============================= Learning Objectives ------------------- After reading this chapter, you should be able to: - Calculate net present value (NPV) for forest management scenarios - Understand the components of forest financial analysis - Use ws3's financial functions to evaluate management plans - Compare alternative management strategies using financial metrics What Is Financial Analysis in Forest Planning? ---------------------------------------------- Financial analysis evaluates the economic viability of forest management plans. It answers questions like: - Is this harvest schedule profitable? - Which management strategy maximizes returns? - How sensitive is the plan to changes in timber prices? - What is the optimal rotation age? Key financial metrics: - **Net Present Value (NPV)**: Total value of all cash flows, discounted to present - **Internal Rate of Return (IRR)**: Discount rate that makes NPV = 0 - **Benefit-Cost Ratio (BCR)**: Total benefits divided by total costs - **Rotation Age**: Age that maximizes NPV per hectare Net Present Value ----------------- **Net Present Value (NPV)** is the most important financial metric in forest planning. It accounts for the time value of money: a dollar today is worth more than a dollar tomorrow. .. math:: NPV = \\sum_{t=0}^{T} \\frac{R_t - C_t}{(1 + r)^t} Where: - :math:`R_t` = Revenue in period :math:`t` - :math:`C_t` = Cost in period :math:`t` - :math:`r` = Discount rate - :math:`T` = Planning horizon .. mermaid:: graph TD REV["Revenue
(harvest sales)"] --> NPV["NPV Calculation"] COST["Costs
(harvesting, silviculture)"] --> NPV DISC["Discount rate
(time value of money)"] --> NPV NPV --> PROF["Profitability
decision"] Using ws3's Financial Functions ------------------------------- ws3 provides financial analysis functions in the :py:mod:`ws3.financial` module. Calculating NPV ~~~~~~~~~~~~~~~ .. code-block:: python # Financial calculations are done in pure Python (no ws3.financial module). # Define cash flows for each period revenues = [0, 0, 50000, 80000, 100000, 120000, 110000, 90000, 70000, 50000] costs = [10000, 5000, 20000, 25000, 30000, 35000, 30000, 25000, 20000, 15000] # Calculate NPV at 5% discount rate discount_rate = 0.05 npv = sum( (r - c) / (1 + discount_rate) ** t for t, (r, c) in enumerate(zip(revenues, costs)) ) print(f"NPV: ${npv:,.0f}") # Calculate NPV at different discount rates for rate in [0.02, 0.05, 0.08, 0.10]: npv = sum( (r - c) / (1 + rate) ** t for t, (r, c) in enumerate(zip(revenues, costs)) ) print(f"NPV at {rate*100:.0f}%: ${npv:,.0f}") Calculating IRR ~~~~~~~~~~~~~~~ The **Internal Rate of Return (IRR)** is the discount rate that makes NPV equal to zero. It represents the inherent rate of return of the investment. .. code-block:: python # Calculate IRR using numpy (or scipy.optimize) import numpy as np from scipy.optimize import brentq # Net cash flows net_flows = [r - c for r, c in zip(revenues, costs)] # IRR is the discount rate that makes NPV = 0 def npv_at_rate(rate): return sum(cf / (1 + rate) ** t for t, cf in enumerate(net_flows)) irr = brentq(npv_at_rate, -0.99, 0.99) print(f"IRR: {irr*100:.1f}%") # Compare to discount rate if irr > 0.05: print("Project is profitable at 5% discount rate") else: print("Project is not profitable at 5% discount rate") Rotation Economics ------------------ The **optimal rotation age** is the age that maximizes NPV per hectare. This is a fundamental concept in forest economics. .. mermaid:: graph TD AGE["Rotation age"] --> VOL["Volume at harvest"] AGE --> COST["Costs over rotation"] VOL --> REV["Revenue at harvest"] COST --> NPV["NPV"] REV --> NPV NPV --> OPT["Optimal rotation age
(max NPV)"] The Faustmann formula calculates the optimal rotation age: .. math:: V'(T) / V(T) = r / (1 - e^{-rT}) Where: - :math:`V(T)` = Volume at age :math:`T` - :math:`V'(T)` = Marginal growth at age :math:`T` - :math:`r` = Discount rate Using ws3 to Find Optimal Rotation ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. code-block:: python from ws3.forest import ForestModel from ws3.core import Curve # Define a volume curve volume_curve = Curve( label="DF_volume", points=[(age, vol) for age, vol in zip(range(0, 201, 10), [0, 2, 10, 30, 70, 130, 210, 300, 390, 460, 510, 540, 560, 575, 585, 590, 595, 598, 600, 601, 602, 602])] ) # Calculate NPV for each rotation age prices = 50 # $/m³ costs = 10000 # Fixed costs per hectare discount_rate = 0.05 npv_by_age = [] for age in range(10, 201, 10): volume = volume_curve(age) revenue = volume * prices # Discounted revenue minus costs npv = (revenue - costs) / (1 + discount_rate) ** age npv_by_age.append((age, npv)) # Find optimal rotation age optimal_age, max_npv = max(npv_by_age, key=lambda x: x[1]) print(f"Optimal rotation age: {optimal_age} years") print(f"Maximum NPV: ${max_npv:,.0f}") Sensitivity Analysis -------------------- Financial analysis should include sensitivity analysis to understand how results change with different assumptions: .. code-block:: python # Sensitivity to timber prices print("Sensitivity to timber prices:") base_price = 50 for price in [30, 40, 50, 60, 70]: # Adjust revenues for new price adjusted_revenues = [r * price / base_price for r in revenues] npv_adj = sum( (r - c) / (1 + 0.05) ** t for t, (r, c) in enumerate(zip(adjusted_revenues, costs)) ) print(f" Price = ${price}/m³: NPV = ${npv_adj:,.0f}") # Sensitivity to discount rates print("\nSensitivity to discount rates:") for rate in [0.02, 0.05, 0.08, 0.10, 0.15]: npv = sum( (r - c) / (1 + rate) ** t for t, (r, c) in enumerate(zip(revenues, costs)) ) print(f" Rate = {rate*100:.0f}%: NPV = ${npv:,.0f}") Common Financial Mistakes ------------------------- 1. **Ignoring discounting**: Failing to account for the time value of money 2. **Using nominal vs. real values**: Mixing nominal and real prices 3. **Ignoring costs**: Only considering revenue, not harvesting/silviculture costs 4. **Overlooking risk**: Not accounting for uncertainty in prices and volumes 5. **Incorrect rotation age**: Using biological maturity instead of economic optimum Exercises --------- **Exercise 1 (Easy)**: Calculate the NPV of a simple harvest scenario with revenues of $100,000 in year 20 and costs of $10,000 in year 0. **Exercise 2 (Medium)**: Find the optimal rotation age for a Douglas-fir stand with the volume curve defined in this chapter. **Exercise 3 (Hard)**: Perform a sensitivity analysis on the optimal rotation age with respect to discount rate and timber price. Further Reading --------------- - :doc:`ch05_optimization` — Optimization fundamentals - :doc:`/howto/faq` — Frequently asked questions - :doc:`/reference/contracts/index` — Data contracts and module boundaries