financialkit.time_value#

Compounding at any frequency and equivalent rates; the rule of 72 and compound-interest tables; present value, net present value and Fisher’s real rate; annuities, perpetuities, life annuities and sinking funds; the internal rate of return (scipy.optimize.brentq), every root of the NPV polynomial (numpy.roots) and the modified IRR; amortization schedules and the Truth-in-Lending APR; day counts, year fractions and accrued interest for every DayCount convention.

financialkit.time_value: the time value of money.

Compounding at any frequency and the equivalence of rates (continuous by default); Pacioli’s rule of 72 and Witt’s compound-interest tables; present value, Fisher’s net present value and real rate of interest; annuities, perpetuities, De Witt’s life annuities and Price’s sinking fund; the internal rate of return (scipy.optimize.brentq()), every root of the NPV polynomial (numpy.roots()) with a warning when there are several, and Baldwin’s modified IRR; level-payment amortization schedules and the Truth-in-Lending APR; day counts, year fractions and accrued interest for every DayCount convention.

The year-fraction functions here are the package’s single implementation; later domains import them rather than redefining them.

class financialkit.time_value.AmortizationSchedule(periods, payment, interest, principal, balance, periods_per_year=12)[source]#

Bases: object

A level-payment loan schedule, one row per payment.

Parameters:
balance: ndarray#

Outstanding balance after each payment (ends at zero).

Type:

ndarray, shape (n,)

interest: ndarray#

Interest part of each payment.

Type:

ndarray, shape (n,)

payment: float#

The level payment per period.

Type:

float

periods: ndarray#

Payment numbers 1, ..., n.

Type:

ndarray, shape (n,)

periods_per_year: int = 12#

Payments per year.

Type:

int

principal: ndarray#

Principal repaid by each payment.

Type:

ndarray, shape (n,)

property total_interest: float#

Interest paid over the life of the loan.

Type:

float

class financialkit.time_value.CompoundingTable(rate, periods, amount, discount, annuity_present, annuity_future)[source]#

Bases: object

A compound-interest table for one rate, in the layout of Witt (1613).

Parameters:
amount: ndarray#

Accumulation factors \((1 + i)^k\).

Type:

ndarray, shape (n,)

annuity_future: ndarray#

Accumulated value of 1 per period, \(s_{\overline{k}|}\).

Type:

ndarray, shape (n,)

annuity_present: ndarray#

Present value of 1 per period, \(a_{\overline{k}|}\).

Type:

ndarray, shape (n,)

discount: ndarray#

Discount factors \(v^k = (1 + i)^{-k}\).

Type:

ndarray, shape (n,)

periods: ndarray#

Period numbers 1, ..., n.

Type:

ndarray, shape (n,)

rate: float#

Effective interest rate per period.

Type:

float

exception financialkit.time_value.MultipleIRRWarning[source]#

Bases: UserWarning

Raised when a cash-flow stream has more than one internal rate of return.

By Descartes’ rule of signs a stream with more than one sign change can have several IRRs (Lorie and Savage, 1955), and then no single one of them is a meaningful rate of return.

class financialkit.time_value.NPVProfile(rates, npv, irrs)[source]#

Bases: object

Net present value as a function of the discount rate.

Parameters:
irrs: ndarray#

Every real internal rate of return (roots of the NPV), ascending.

Type:

ndarray

npv: ndarray#

Net present value at each rate.

Type:

ndarray, shape (k,)

rates: ndarray#

Discount rates at which the NPV was evaluated.

Type:

ndarray, shape (k,)

class financialkit.time_value.SinkingFundSchedule(periods, deposit, interest, balance)[source]#

Bases: object

Level deposits accumulating at interest to repay a debt at maturity.

Parameters:
balance: ndarray#

Fund balance after each deposit (ends at the target).

Type:

ndarray, shape (n,)

deposit: float#

The level deposit per period.

Type:

float

interest: ndarray#

Interest earned by the fund during each period.

Type:

ndarray, shape (n,)

periods: ndarray#

Deposit numbers 1, ..., n.

Type:

ndarray, shape (n,)

financialkit.time_value.accrued_interest(face, coupon_rate, last_coupon, settlement, convention=DayCount.THIRTY_360_US)[source]#

Coupon interest accrued from the last coupon date to settlement.

Parameters:
  • face (float or array-like)

  • coupon_rate (float or array-like) – Annual coupon rate.

  • last_coupon (date-like or array-like of date-likes)

  • settlement (date-like or array-like of date-likes)

  • convention (DayCount or str)

Returns:

float or ndarray

Examples

Hull (10th ed.), Sec. 6.1: a 10% corporate bond, coupons on March 1 and September 1, settling July 3, 2018 (30/360).

>>> round(accrued_interest(100.0, 0.10, "2018-03-01", "2018-07-03"), 4)
3.3889
financialkit.time_value.amortization_schedule(principal, annual_rate, n_payments, periods_per_year=12)[source]#

Split each level payment into interest and principal.

Parameters:
  • principal (float)

  • annual_rate (float) – Nominal annual rate, compounded periods_per_year times a year.

  • n_payments (int)

  • periods_per_year (int)

Return type:

AmortizationSchedule

Returns:

AmortizationSchedule

Examples

>>> s = amortization_schedule(1000.0, 0.12, 12)
>>> round(s.payment, 2), float(s.interest[0]), abs(float(s.balance[-1])) < 1e-9
(88.85, 10.0, True)
financialkit.time_value.annual_percentage_rate(amount_financed, payment, n_payments, periods_per_year=12)[source]#

Truth-in-Lending APR of a level-payment loan.

Parameters:
  • amount_financed (float) – Loan principal less prepaid finance charges (fees, points).

  • payment (float) – Level payment per period.

  • n_payments (int)

  • periods_per_year (int)

Return type:

float

Returns:

float – Nominal annual rate periods_per_year * i, where i solves payment * a_n(i) == amount_financed.

Examples

A 1,000 loan at 12% with a 20 fee deducted up front:

>>> payment = loan_payment(1000.0, 0.12, 12)
>>> round(annual_percentage_rate(1000.0, payment, 12), 6)
0.12
>>> round(annual_percentage_rate(980.0, payment, 12), 4)
0.1585
financialkit.time_value.annuity_factor(rate, n, due=False)[source]#

Present value of 1 per period for n periods, \(a_{\overline{n}|}\).

Parameters:
  • rate (float or array-like) – Effective rate per period; zero is allowed (the factor is then n).

  • n (int or array-like) – Number of payments.

  • due (bool) – True for payments at the start of each period (annuity-due).

Returns:

float or ndarray

Examples

>>> round(annuity_factor(0.05, 10), 6)
7.721735
financialkit.time_value.annuity_future_value(payment, rate, n, due=False)[source]#

Accumulated value after n level payments, \(P s_{\overline{n}|}\).

Parameters:
  • payment (float or array-like)

  • rate (float or array-like) – Effective rate per period.

  • n (int or array-like)

  • due (bool)

Returns:

float or ndarray

Examples

>>> round(annuity_future_value(100.0, 0.05, 10), 2)
1257.79
financialkit.time_value.annuity_payment(present_value, rate, n, due=False)[source]#

Level payment whose n-period annuity is worth present_value.

Parameters:
  • present_value (float or array-like)

  • rate (float or array-like) – Effective rate per period.

  • n (int or array-like)

  • due (bool)

Returns:

float or ndarray

Examples

>>> round(annuity_payment(10_000.0, 0.01, 12), 2)
888.49
financialkit.time_value.annuity_present_value(payment, rate, n, due=False)[source]#

Present value of n level payments.

Parameters:
  • payment (float or array-like)

  • rate (float or array-like) – Effective rate per period.

  • n (int or array-like)

  • due (bool) – Payments at the start of each period.

Returns:

float or ndarray

Examples

>>> round(annuity_present_value(1000.0, 0.06, 5), 2)
4212.36
financialkit.time_value.compound_interest_table(rate, n_periods)[source]#

Build a compound-interest table for periods 1, ..., n_periods.

Witt (1613) printed, for each rate, the amount of 1, the present value of 1 and the values of an annuity of 1 per period; with them a clerk could price a loan, a lease or an annuity by look-up and multiplication.

Parameters:
  • rate (float) – Effective rate per period.

  • n_periods (int)

Return type:

CompoundingTable

Returns:

CompoundingTable

Examples

>>> table = compound_interest_table(0.10, 3)
>>> table.amount.round(3).tolist(), table.annuity_present.round(4).tolist()
([1.1, 1.21, 1.331], [0.9091, 1.7355, 2.4869])
financialkit.time_value.continuous_rate(rate, compounding=None)[source]#

Restate a rate as its continuously compounded equivalent, \(m \ln(1 + r/m)\).

Parameters:
  • rate (float or array-like) – Annual rate, compounded compounding times a year.

  • compounding (int | None) – None if rate is already continuous.

Returns:

float or ndarray

Examples

>>> round(continuous_rate(0.10, 1), 6)
0.09531
financialkit.time_value.day_count(start, end, convention=DayCount.ACT_365F)[source]#

Days between two dates under a day-count convention.

Parameters:
  • start (date-like or array-like of date-likes)

  • end (date-like or array-like of date-likes)

  • convention (DayCount or str) – One of DayCount; the ACT conventions count actual days.

Returns:

int or ndarray of int

Examples

Hull (10th ed.), Sec. 6.1: March 1 to July 3, 2018.

>>> day_count("2018-03-01", "2018-07-03", "30/360 US"), day_count("2018-03-01", "2018-07-03", "ACT/360")
(122, 124)
financialkit.time_value.days_in_year(year)[source]#

365 or 366 for each year.

Parameters:

year (int or array-like of int)

Return type:

ndarray

Returns:

ndarray of int

financialkit.time_value.de_witt_survival(age, times, base_hazard=0.01)[source]#

Survival probabilities under De Witt’s (1671) mortality hypothesis.

De Witt assumed that between ages 3 and 53 the chance of dying is the same in every half-year, and that it is 1.5, 2 and 3 times as large in the bands 53-63, 63-73 and 73-80, with no one living beyond 80. Here this is a piecewise-constant force of mortality base_hazard * w.

Parameters:
  • age (float) – Age of the annuitant today, at least 3.

  • times (array-like) – Years from today.

  • base_hazard (float) – Annual force of mortality in the youngest band.

Return type:

ndarray

Returns:

ndarray – Probability of being alive at each time.

Examples

>>> de_witt_survival(70.0, [0.0, 5.0, 9.0, 10.0]).round(4).tolist()
[1.0, 0.8869, 0.7866, 0.0]
financialkit.time_value.discount_factor(rate, t, compounding=None)[source]#

Value today of 1 paid at time t.

Parameters:
  • rate (float or array-like) – Annual rate.

  • t (float or array-like) – Time in years; broadcasts against rate.

  • compounding (int | None) – Periods per year, or None for continuous compounding.

Returns:

float or ndarray

Examples

>>> round(discount_factor(0.05, 2.0), 6)
0.904837
>>> round(discount_factor(0.05, 2.0, compounding=1), 6)
0.907029
financialkit.time_value.doubling_time(rate, compounding=None)[source]#

Exact time, in years, for money to double at rate.

Parameters:
  • rate (float or array-like) – Positive annual rate.

  • compounding (int | None)

Returns:

float or ndarray

Examples

>>> round(doubling_time(0.06, compounding=1), 4)
11.8957
financialkit.time_value.effective_annual_rate(rate, compounding=None)[source]#

Annual effective rate, \((1 + r/m)^m - 1\), equivalent to rate.

Parameters:
Returns:

float or ndarray

Examples

>>> round(effective_annual_rate(0.12, 12), 6)
0.126825
financialkit.time_value.equivalent_rate(rate, from_compounding, to_compounding)[source]#

Convert a rate between compounding frequencies at equal growth.

Parameters:
  • rate (float or array-like)

  • from_compounding (int | None) – Periods per year of the given and the requested rate (None is continuous).

  • to_compounding (int | None) – Periods per year of the given and the requested rate (None is continuous).

Returns:

float or ndarray

Examples

Hull (10th ed.), Example 4.1: 10% compounded semiannually is 9.758% continuously compounded.

>>> round(equivalent_rate(0.10, 2, None), 5)
0.09758
financialkit.time_value.fisher_nominal_rate(real, inflation)[source]#

Nominal rate implied by a real rate and inflation, \((1 + r)(1 + \pi) - 1\).

Parameters:
  • real (float or array-like)

  • inflation (float or array-like)

Returns:

float or ndarray

Examples

>>> round(fisher_nominal_rate(0.02, 0.03), 4)
0.0506
financialkit.time_value.fisher_real_rate(nominal, inflation, exact=True)[source]#

Real interest rate from a nominal rate and inflation (Fisher, 1896).

Parameters:
  • nominal (float or array-like) – Nominal rate per period.

  • inflation (float or array-like) – Inflation rate per period.

  • exact (bool) – True for \((1 + i)/(1 + \pi) - 1\), False for the first-order approximation \(i - \pi\).

Returns:

float or ndarray

Examples

>>> round(fisher_real_rate(0.10, 0.06), 6), round(fisher_real_rate(0.10, 0.06, exact=False), 6)
(0.037736, 0.04)
financialkit.time_value.future_value(present_value, rate, t, compounding=None)[source]#

Accumulated value at time t of present_value invested today.

Parameters:
  • present_value (float or array-like)

  • rate (float or array-like) – Annual rate.

  • t (float or array-like) – Time in years.

  • compounding (int | None) – Periods per year, or None for continuous compounding.

Returns:

float or ndarray

Examples

Bernoulli’s question: 1 at 100% a year, compounded ever more often.

>>> [round(future_value(1.0, 1.0, 1.0, m), 6) for m in (1, 12, 365)]
[2.0, 2.613035, 2.714567]
>>> round(future_value(1.0, 1.0, 1.0), 6)
2.718282
financialkit.time_value.irr(cash_flows, times=None, compounding=1, bracket=(-0.99, 10.0), n_grid=2001)[source]#

Internal rate of return: the rate at which npv() is zero.

Parameters:
  • cash_flows (array-like, shape (n,))

  • times (array-like, shape (n,), optional) – Payment times; defaults to 0, 1, ..., n - 1.

  • compounding (int | None) – Compounding of the returned rate; default annual.

  • bracket (tuple[float, float]) – Search interval for the rate.

  • n_grid (int) – Grid points used to bracket sign changes before root polishing.

Return type:

float

Returns:

float – The lowest IRR in bracket.

Raises:

ValueError – If the NPV does not change sign in bracket.

Warns:

MultipleIRRWarning – If the NPV has more than one root in bracket.

Examples

>>> round(irr([-100.0, 60.0, 60.0]), 6)
0.130662
financialkit.time_value.irr_all(cash_flows)[source]#

Every real IRR above -100% of cash flows at periods 0, 1, ..., n.

With \(x = 1/(1 + r)\) the NPV is the polynomial \(\sum_k c_k x^k\); its positive real roots give the IRRs.

Parameters:

cash_flows (array-like, shape (n + 1,))

Return type:

ndarray

Returns:

ndarray – IRRs per period, ascending (empty if there is none).

Examples

Lorie and Savage’s oil pump (1955): 25% and 400%.

>>> irr_all([-1600, 10000, -10000]).round(6).tolist()
[0.25, 4.0]
financialkit.time_value.is_leap_year(year)[source]#

Gregorian leap-year test, vectorised.

Parameters:

year (int or array-like of int)

Return type:

ndarray

Returns:

ndarray of bool

financialkit.time_value.life_annuity_value(payment, rate, survival, due=False)[source]#

Expected present value of a life annuity, \(P \sum_k {}_k p_x v^k\).

Parameters:
  • payment (float) – Payment per period while the annuitant lives.

  • rate (float or array-like) – Effective rate per period.

  • survival (array-like, shape (n,)) – survival[k - 1] is the probability of being alive at period k (or at k - 1 if due), e.g. from de_witt_survival().

  • due (bool)

Returns:

float or ndarray

Examples

With certain survival the life annuity is an annuity-certain:

>>> round(life_annuity_value(1.0, 0.04, [1.0, 1.0, 1.0]), 6) == round(annuity_factor(0.04, 3), 6)
True
financialkit.time_value.loan_payment(principal, annual_rate, n_payments, periods_per_year=12)[source]#

Level payment of a fully amortizing loan.

Parameters:
  • principal (float or array-like)

  • annual_rate (float or array-like) – Nominal annual rate, compounded periods_per_year times a year.

  • n_payments (int or array-like)

  • periods_per_year (int)

Returns:

float or ndarray

Examples

A 30-year, 200,000 mortgage at 6%:

>>> round(loan_payment(200_000.0, 0.06, 360), 2)
1199.1
financialkit.time_value.mirr(cash_flows, finance_rate, reinvest_rate)[source]#

Baldwin’s modified internal rate of return.

Inflows are compounded to the horizon at reinvest_rate, outflows are discounted to today at finance_rate, and the MIRR is the single rate linking the two: \((FV^+ / |PV^-|)^{1/n} - 1\).

Parameters:
  • cash_flows (array-like, shape (n + 1,)) – Cash flows at periods 0, ..., n, with at least one of each sign.

  • finance_rate (float) – Rate paid on the money invested (outflows).

  • reinvest_rate (float) – Rate earned on reinvested inflows.

Return type:

float

Returns:

float

Examples

>>> round(mirr([-1600, 10000, -10000], 0.10, 0.10), 6)
0.05599
financialkit.time_value.npv(rate, cash_flows, times=None, compounding=1)[source]#

Fisher’s net present value of a project, outflows negative.

Unlike present_value() the defaults follow capital budgeting: cash flows at the end of periods 0, 1, ..., n - 1 and an annually compounded rate.

Parameters:
  • rate (float or array-like) – Discount rate (the opportunity cost of capital).

  • cash_flows (array-like, shape (n,)) – cash_flows[0] is usually the (negative) initial investment.

  • times (array-like, shape (n,), optional) – Payment times; defaults to 0, 1, ..., n - 1.

  • compounding (int | None) – Periods per year of rate; default 1.

Returns:

float or ndarray

Examples

Brealey-Myers-Allen (13th ed.), Sec. 2.1: an office block costing 370,000 that sells for 420,000 in a year, at 5%:

>>> round(npv(0.05, [-370_000, 420_000]))
30000
financialkit.time_value.npv_profile(cash_flows, rates)[source]#

NPV across a range of discount rates, with every IRR marked.

Parameters:
  • cash_flows (array-like, shape (n + 1,)) – Cash flows at periods 0, ..., n.

  • rates (array-like) – Annually compounded discount rates.

Return type:

NPVProfile

Returns:

NPVProfile

Examples

>>> profile = npv_profile([-100.0, 60.0, 60.0], [0.0, 0.1, 0.2])
>>> profile.npv.round(2).tolist()
[20.0, 4.13, -8.33]
financialkit.time_value.perpetuity_present_value(payment, rate, growth=0.0, due=False)[source]#

Value of a payment that continues forever, \(P / (i - g)\).

Parameters:
  • payment (float or array-like) – First payment, made one period from now (or now if due).

  • rate (float or array-like) – Effective rate per period, greater than growth.

  • growth (float or array-like) – Growth rate of the payments per period.

  • due (bool)

Returns:

float or ndarray

Examples

A 3% consol paying 3 a year, when long yields are 4%:

>>> round(perpetuity_present_value(3.0, 0.04), 6)
75.0
financialkit.time_value.plot_amortization(schedule, ax=None)[source]#

Stacked interest and principal per payment, with the balance on a twin axis.

Parameters:
  • schedule (AmortizationSchedule) – From amortization_schedule().

  • ax (matplotlib.axes.Axes, optional)

Returns:

matplotlib.axes.Axes

financialkit.time_value.plot_cash_flows(cash_flows, times=None, ax=None, title='Cash flows')[source]#

Bar timeline of a cash-flow stream, inflows up and outflows down.

Parameters:
  • cash_flows (array-like, shape (n,))

  • times (array-like, shape (n,), optional) – Defaults to 0, 1, ..., n - 1.

  • ax (matplotlib.axes.Axes, optional) – Axes to draw on; a new figure is created if omitted.

  • title (str)

Returns:

matplotlib.axes.Axes

financialkit.time_value.plot_compounding_growth(rate, T, compoundings=(1, 4, 12, None), ax=None)[source]#

Growth of 1 at rate under several compounding frequencies.

Parameters:
  • rate (float) – Annual rate.

  • T (float) – Horizon in years.

  • compoundings (sequence of int or None) – Periods per year; None is continuous.

  • ax (matplotlib.axes.Axes, optional)

Returns:

matplotlib.axes.Axes

financialkit.time_value.plot_npv_profile(profile, ax=None, label=None)[source]#

NPV against the discount rate, with each IRR marked.

Parameters:
  • profile (NPVProfile) – From npv_profile().

  • ax (matplotlib.axes.Axes, optional)

  • label (str | None)

Returns:

matplotlib.axes.Axes

financialkit.time_value.present_value(cash_flows, times, rate, compounding=None)[source]#

Discounted value today of payments cash_flows made at times.

Parameters:
  • cash_flows (array-like, shape (n,))

  • times (array-like, shape (n,)) – Payment times in years.

  • rate (float or array-like) – Annual discount rate; an array gives one present value per rate.

  • compounding (int | None) – Periods per year, or None (default) for continuous compounding.

Returns:

float or ndarray – Same shape as rate.

Examples

>>> round(present_value([100.0, 100.0], [1.0, 2.0], 0.10, compounding=1), 4)
173.5537
financialkit.time_value.rule_of_72(rate, numerator=72.0)[source]#

Pacioli’s rule: money doubles in about 72 / (100 * rate) years.

The rule approximates the exact annual-compounding doubling time \(\ln 2 / \ln(1 + r)\); 72 rather than \(100 \ln 2 \approx 69.3\) corrects for the curvature of \(\ln(1 + r)\) near 8%, and 72 has many divisors. Use numerator=69.3 for continuous compounding.

Parameters:
  • rate (float or array-like) – Annual rate as a decimal.

  • numerator (float) – The rule’s constant.

Returns:

float or ndarray

Examples

>>> rule_of_72(0.06)
12.0
financialkit.time_value.sign_changes(cash_flows)[source]#

Number of sign changes in a cash-flow stream, ignoring zeros.

By Descartes’ rule of signs this bounds the number of positive roots of the NPV polynomial in the discount factor, i.e. the number of internal rates of return above -100%.

Parameters:

cash_flows (array-like)

Return type:

int

Returns:

int

Examples

>>> sign_changes([-1600, 10000, -10000])
2
financialkit.time_value.sinking_fund_payment(target, rate, n)[source]#

Level deposit that accumulates to target after n periods (Price, 1772).

Parameters:
  • target (float or array-like) – Amount to accumulate, e.g. the face value of a debt.

  • rate (float or array-like) – Effective rate earned by the fund per period.

  • n (int or array-like)

Returns:

float or ndarray

Examples

>>> round(sinking_fund_payment(1000.0, 0.05, 10), 4)
79.5046
financialkit.time_value.sinking_fund_schedule(target, rate, n)[source]#

Period-by-period growth of a sinking fund.

Parameters:
  • target (float)

  • rate (float) – Effective rate per period.

  • n (int)

Return type:

SinkingFundSchedule

Returns:

SinkingFundSchedule

Examples

>>> round(float(sinking_fund_schedule(1000.0, 0.05, 10).balance[-1]), 6)
1000.0
financialkit.time_value.year_fraction(start, end, convention=DayCount.ACT_365F)[source]#

Year fraction between two dates.

Parameters:
  • start (date-like or array-like of date-likes)

  • end (date-like or array-like of date-likes)

  • convention (DayCount or str)

Returns:

float or ndarray

Examples

ISDA (1998), Sec. 3, Example 1: November 1, 2003 to May 1, 2004 is 61/365 + 121/366 under actual/actual ISDA.

>>> round(year_fraction("2003-11-01", "2004-05-01", "ACT/ACT ISDA"), 5)
0.49772