Files
calminer-docs/specifications/financial_metrics.md
zwitschi 29f16139a3 feat: documentation update
- Completed export workflow implementation (query builders, CSV/XLSX serializers, streaming API endpoints, UI modals, automated tests).
- Added export modal UI and client script to trigger downloads directly from dashboard.
- Documented import/export field mapping and usage guidelines in FR-008.
- Updated installation guide with export environment variables, dependencies, and CLI/CI usage instructions.
2025-11-11 18:34:02 +01:00

7.2 KiB
Raw Permalink Blame History

Financial Metrics Specification

1. Purpose

Define the standard methodology CalMiner uses to compute discounted-cash-flow profitability metrics, including Net Present Value (NPV), Internal Rate of Return (IRR), and Payback Period. These calculations underpin scenario evaluation, reporting, and investment decision support within the platform.

2. Scope

  • Applies to scenario-level profitability analysis using cash flows stored in the application database.
  • Covers deterministic cash-flow evaluation; stochastic extensions (e.g., Monte Carlo) may overlay these metrics but should reference this specification.
  • Documents the assumptions implemented in services/financial.py and the related pytest coverage in tests/test_financial.py.

3. Inputs and Definitions

Symbol Name Description Units / Domain Notes
CF_t Cash flow at period t Currency amount (positive or negative) Scenario currency Negative values typically represent investments/outflows.
t Period index Fractional period (0 = anchor) Real number Derived from explicit index or calendar date.
r Discount rate Decimal representation of the annual rate r > -1 Scenario configuration provides default rate.
m Compounds per year Compounding frequency Positive integer Defaults to 1 (annual).
RV Residual value Terminal value realised after final period Scenario currency Optional.
t_{RV} Residual periods Timing of residual value relative to anchor Real number Defaults to last period + 1.

Period Anchoring and Timing

Cash flows can be supplied with either:

  1. period_index — explicit integers/floats overriding all other timing.
  2. date — calendar dates. The earliest dated flow anchors the timeline and subsequent dates convert the day difference into fractional periods using a 365-day year divided by compounds_per_year.
  3. Neither — flows default to sequential periods based on input order.

This aligns with normalize_cash_flows in services/financial.py, ensuring all calculations receive (amount, periods) tuples.

4. Net Present Value (NPV)

Formula

For a set of cash flows CF_t with discount rate r and compounding frequency m:


\text{NPV} = \sum_{t=0}^{n} \frac{CF_t}{\left(1 + \frac{r}{m}\right)^{t}} +
\begin{cases}
\dfrac{RV}{\left(1 + \frac{r}{m}\right)^{t_{RV}}} & \text{if residual value present} \\
0 & \text{otherwise}
\end{cases}

Implementation Notes

  • discount_factor computes $(1 + r/m)^{-t}; NPV iterates over the normalised flows and sums amount * factor`.
  • Residual values default to one period after the final cash flow when residual_periods is omitted.
  • Empty cash-flow sequences return 0 unless a residual value is supplied.

5. Internal Rate of Return (IRR)

Definition

IRR is the discount rate r for which NPV equals zero:


0 = \sum_{t=0}^{n} \frac{CF_t}{\left(1 + \frac{r}{m}\right)^{t}}

Solver Behaviour

  • NewtonRaphson iteration starts from guess (default 10%).
  • Derivative instability or non-finite values trigger a fallback to a bracketed bisection search between:
    • Lower bound: -0.99 \times m
    • Upper bound: 10.0 (doubles until the root is bracketed or attempts exceed 12)
  • Raises ConvergenceError when no sign change is found or the bisection fails within the iteration budget (max_iterations default 100; bisection uses double this limit).
  • Validates that the cash-flow series includes at least one negative and one positive value; otherwise IRR is undefined and a ValueError is raised.

Caveats

  • Multiple sign changes may yield multiple IRRs. The solver returns the root it finds within the configured bounds; scenarios must interpret the result in context.
  • Rates less than -1 * m imply nonphysical periodic rates and are excluded.

6. Payback Period

Definition

The payback period is the earliest period t where cumulative cash flows become non-negative. With fractional interpolation (default behaviour), the period is calculated as:


\text{Payback} = t_{prev} + \left(\frac{-\text{Cumulative}_{prev}}{CF_t}\right)
\times (t - t_{prev})

where t_{prev} is the previous period with negative cumulative cash flow.

Implementation Notes

  • Cash flows are sorted by period to ensure chronological accumulation.
  • When allow_fractional is False, the function returns the first period with non-negative cumulative total without interpolation.
  • PaybackNotReachedError is raised if the cumulative total never becomes non-negative.

7. Examples

Example 1: Baseline Project

  • Initial investment: -1,000,000 at period 0.
  • Annual inflows: 300k, 320k, 340k, 360k, 450k (periods 1-5).
  • Discount rate: 8% annual, compounds_per_year = 1.
Period Cash Flow (currency)
0 -1,000,000
1 300,000
2 320,000
3 340,000
4 360,000
5 450,000
  • net_present_value ≈ 205,759
  • internal_rate_of_return ≈ 0.158
  • payback_period ≈ 4.13 periods

Example 2: Residual Value with Irregular Timing

  • Investment: -500,000 on 2024-01-01
  • Cash inflows on irregular dates (2024-07-01: 180k, 2025-01-01: 200k, 2025-11-01: 260k)
  • Residual value 150k realised two years after final inflow
  • Discount rate: 10%, compounds_per_year = 4

NPV discounts each cash flow by converting day deltas to quarterly periods. The residual is discounted at t_{RV} = last_period + 2 (because the override is supplied).

8. Testing Strategy

tests/test_financial.py exercises:

  • normalize_cash_flows with date-based, index-based, and sequential cash-flow inputs.
  • NPV calculations with and without residual values, including discount-rate sensitivity checks.
  • IRR convergence success cases, invalid inputs, and non-converging scenarios.
  • Payback period exact, fractional, and never-payback cases.

Developers extending the financial metrics should add regression tests covering new assumptions or solver behaviour.

9. Integration Notes

  • Scenario evaluation services should pass cash flows as CashFlow instances to reuse the shared normalisation logic.
  • UI and reporting layers should display rates as percentages but supply them as decimals to the service layer.
  • Future Monte Carlo or sensitivity analyses can reuse the same helpers to evaluate each simulated cash-flow path.

10. References

  • Internal implementation: calminer/services/financial.py
  • Tests: calminer/tests/test_financial.py
  • Related specification: calminer-docs/specifications/price_calculation.md
  • Architecture context: calminer-docs/architecture/08_concepts/02_data_model.md