Present value of periodic flows (tvm.present_value)¶
Discounts the cash flows of periods 1 to \(n\) of a plan, with either one rate for every period or a different rate for each period, and with the flows placed at the end, the middle or the beginning of their period. It returns the discount factor and the present value of each flow, and their sum. Use it when the cost of capital changes over the plan, or when the timing of the flows within each period must be explicit.
Formula¶
With period rates \(r_1, \ldots, r_n\) (all equal when a single rate is given), the end of
period discount factor of period \(k\) is the product of the period factors up to \(k\):
The other timings start from the factor of the previous period:
| Timing | timing |
Flow \(k\) occurs at |
|---|---|---|
| End of period | end_of_period |
\(t = k\) |
| Mid period | mid_period |
\(t = k - 0.5\), half a period at the rate \(r_k\) of that period |
| Beginning of period | beginning_of_period |
\(t = k - 1\) |
With a constant rate and end-of-period timing the result equals tvm.npv of the
same flows preceded by a zero flow at \(t = 0\).
Example¶
Three flows of 100 with rates of 5%, 6% and 7% for periods 1 to 3, under each timing:
from decimal import Decimal
import fairmark as fm
flows = [100, 100, 100]
curve = ["0.05", "0.06", "0.07"]
end = fm.tvm.present_value(flows, period_rates=curve, timing="end_of_period")
mid = fm.tvm.present_value(flows, period_rates=curve, timing="mid_period")
beg = fm.tvm.present_value(flows, period_rates=curve, timing="beginning_of_period")
assert round(end.present_value, 18) == Decimal("269.054756446750804007")
assert abs(mid.present_value - Decimal("276.95204711016002295632")) < Decimal("1e-15")
assert round(beg.present_value, 18) == Decimal("285.085354896675651393")
print(end.discount_factors[1]) # 1 / (1.05 × 1.06) = 0.8984725965858041329739442947
Inputs¶
| Field | Meaning | Default |
|---|---|---|
cash_flows |
Cash flows of periods 1, 2, ..., n | required |
rate |
One discount rate for every period, decimal fraction per period | give exactly one of rate and period_rates |
period_rates |
One discount rate per period (forward period rates), decimal fractions; exactly one per cash flow | give exactly one of rate and period_rates |
timing |
end_of_period, mid_period or beginning_of_period |
required |
Outputs¶
| Field | Meaning |
|---|---|
present_value |
Sum of the present values |
discount_factors |
Discount factor applied to each flow |
present_values |
Present value of each flow |
Conventions (explicit, reported in explain.conventions)¶
| Convention | Options | Default |
|---|---|---|
| Timing | end of period, mid period, beginning of period | none: must be chosen |
| Rates | one rate for every period, or one rate per period | none: give exactly one |
| Period rates | forward: \(r_k\) applies to period \(k\) only | fixed |
| Mid period | half a period at the rate of the same period, a division by \(\sqrt{1 + r_k}\) | fixed |
Timing has no default because practice varies: business plans often use mid period, contracts end of period. With a constant rate, mid-period discounting is the usual \((1 + r)^{-(k - 0.5)}\).
Errors¶
| Code | When |
|---|---|
FM-E001 |
Both or neither of rate and period_rates are given, period_rates does not have exactly one rate per cash flow, or a field is missing or unknown |
FM-E002 |
cash_flows is empty |
FM-E003 |
A period rate is −100% or lower |
FM-E008 |
Arithmetic overflow (inputs far outside a realistic range) |
Accuracy¶
End-of-period and beginning-of-period factors use only division; measured against an independent 60-digit computation, they agree to about \(10^{-24}\) relative or better. Mid-period factors use the decimal square root, which agrees to about \(10^{-18}\) relative or better on typical inputs. The golden cases are checked to \(10^{-18}\) for end and beginning timing and \(10^{-19}\) for mid-period timing.
Verified against¶
- Golden cases in
tests/golden/tvm.present_value/, from an independent 50-digit computation (scripts/reference_time.py). - With a constant rate and end-of-period timing, agrees with the spreadsheet
NPVfunction (Excel, LibreOffice), which discounts its first value by one period, and withnumpy_financial.npvapplied to the same flows preceded by a zero flow.