InterestRate#
InterestRate represents a constant or piecewise term-structured
interest rate curve used for discounting actuarial cash-flows. It supports:
Constant rates: a single effective annual rate \(i\).
Piecewise curves: segment rates \(i_1, i_2, \ldots\) with durations \(t_1, t_2, \ldots\) (the last rate applies indefinitely).
Multi-scenario containers: a named set of constant or piecewise curves with an active-scenario switch for stress testing.
Discount factors \(v^n = (1+i)^{-n}\) are computed via vn()
(direct \(n\)-year discount factor) and vx()
(discount factor for \(x - x_0\) years, equivalent to vn(x - x0)). Term lengths
can be expressed in years, months, weeks, or days.
See also
Interest Rates — Full guide to interest rate curves and scenarios.
- class lactuca.InterestRate(rate: float | int | floating | integer | dict[str, float | int | floating | integer | tuple[Sequence[float | int | floating | integer] | NDArray[floating] | NDArray[integer], Sequence[float | int | floating | integer] | NDArray[floating] | NDArray[integer]]] | None = None, terms: Sequence[float | int | floating | integer] | NDArray[floating] | NDArray[integer] | None = None, rates: Sequence[float | int | floating | integer] | NDArray[floating] | NDArray[integer] | None = None, term_unit: Literal['years', 'months', 'weeks', 'days'] = 'years')#
Bases:
objectInterest rate curves with efficient discount computations.
Represents a single
InterestRatecurve (constant or piecewise) or a multi-scenario container (named scenarios with an active scenario).Notes
Constant or piecewise term-structured rates; the last rate applies indefinitely.
Fully vectorized discount factor computation (
vn(),vx()) for piecewise curves.Numeric stability: all internal computations use
np.float64.export()supports"dict","json", and"regulatory"formats.Public APIs normalize and validate inputs.
shifted()implements temporal displacement with tolerance-aware boundary handling.Prefer
float64inputs to avoid implicit precision loss in actuarial calculations.
Examples
Constant rate (scalar):
>>> from lactuca import InterestRate >>> ir = InterestRate(0.03) >>> ir.vn(2) 0.942595909133754...
Piecewise rates (years);
terms=[1.0, 1.0]means two 1-year segments:>>> from lactuca import InterestRate >>> ir = InterestRate(terms=[1.0, 1.0], rates=[0.01, 0.015, 0.02]) >>> ir.vn(2.5) # 1 year at 1%, 1 year at 1.5%, 0.5 year at 2% 0.9659...
Months/days/weeks term units:
>>> from lactuca import InterestRate >>> ir = InterestRate(terms=[6, 12], rates=[0.01, 0.012, 0.013], term_unit="months") >>> ir.vn(1.0) # 1 year 0.9876...
Multi-scenario usage:
>>> from lactuca import InterestRate >>> ir = InterestRate({"base": 0.01, "stress": ([5], [0.02, 0.03])}) >>> ir.active_scenario = "stress" >>> ir.vn(3.0) 0.9423...
See also
InterestRate.vnDiscount factor over duration
n.InterestRate.vxDiscount factor at time
t.InterestRate.copyReturn an independent deep copy of this instance.
InterestRate.exportSerialize the curve to dict, JSON, or regulatory format.
- a(*, ts: object = 0.0, d: object = 0.0, n: object = None, m: Literal[1, 2, 3, 4, 6, 12, 14, 24, 26, 52, 365] | Sequence[int] | NDArray[int64] = 1, gr: GrowthRate | float | None | Sequence[GrowthRate | float | None] = None, cashflow_times: Sequence[float] | None = None, cashflow_amounts: Sequence[float] | None = None, benefits: Sequence[float] | NDArray[float64] | None = None, return_flows: bool = False, t_output: NDArray[float64] | None = None, on_error: Literal['raise', 'nan'] = 'raise', record_ids: Sequence[Any] | None = None) float | NDArray[float64] | dict | BatchResult#
Present value of an annuity-immediate (postpayable) using the current curve.
Accepts scalar or array inputs for
n,d, andts. When any of these parameters is a sequence or array, the method operates in batch mode and returns anNDArray[np.float64]of shape(N,).- Parameters:
ts (float, list, NDArray[np.float64], or Pandas/Polars Series, optional) – Forward shift (years) applied to the schedule before valuation. Accepts a scalar or an array/Series of shape
(N,)for per-policy values. Pandas and Polars Series are accepted without.to_numpy(). Default 0.0.d (float, list, NDArray[np.float64], or Pandas/Polars Series, optional) – Deferment (years) before payments start. Accepts a scalar or an array/Series of shape
(N,)for per-policy values. Pandas and Polars Series are accepted without.to_numpy(). Default 0.0.n (float, None, list, NDArray[np.float64], or Pandas/Polars Series, optional) – Term in years. If
None(scalar) ornp.inf(batch entry) the annuity is treated as a perpetuity. Accepts a scalar or an array/Series of shape(N,)for per-policy terms. Pandas and Polars Series are accepted without.to_numpy().m (PaymentFrequencyLiteral, sequence of int, NDArray[np.int64], or Pandas/Polars Series of int, optional) – Payments per year (payment frequency). Default 1 (annual). In batch mode accepts a scalar or a per-policy array of shape
(N,). Pandas and Polars Series are accepted without.to_numpy(); they are converted internally via the array protocol. When heterogeneous values are provided,return_flows=TrueraisesValueError; all other combinations are supported.gr (GrowthRate, float, None, sequence thereof, or Pandas/Polars Series of float, optional) – Growth rate applied to annuity payments. Default
None(no growth). A scalarfloatis auto-wrapped asGrowthRate(gr)(geometric growth). In batch mode accepts a shared scalar/object or a per-policy list of lengthN. Pandas and Polars Series of floats are accepted without.to_numpy(); each element is treated as a plainfloatgrowth rate (noGrowthRateobjects inside a Series). Distinct per-policy values are processed in sub-groups; withreturn_flows=Trueportfolio flows are aggregated across groups (same contract asLifeTable.ax).cashflow_times (sequence of float or None, optional) – Explicit payment times (years). When provided,
nis ignored and payments are taken from these times (after applying shift/deferment). In batch mode defines the shared payment schedule applied to all policies. Pandas and Polars Series are accepted; converted to NDArray[np.float64] internally.cashflow_amounts (sequence of float or None, optional) – Payment amounts aligned with
cashflow_times(or generated schedule). If provided, length must match the number of payments. Pandas and Polars Series are accepted; converted to NDArray[np.float64] internally.benefits (sequence of float or NDArray[np.float64] or None, optional) – Per-policy benefit weights, shape
(N,). Each policy’s cashflow contribution to the aggregate portfolio is scaled by its benefit value. Requires batch mode (arrayn,d,ts,m, orgr). Withreturn_flows=False, per-policy output follows the batch unit-PV finalizer (round(unit_pv, decimals) * benefits— exact equality witha(n) * benefits). Any sequence is accepted, including Pandas and Polars Series; values are converted to float64 internally. Compatible withreturn_flows=False(returnsNDArray[np.float64]with per-policy scaled PVs) andon_error='nan'. All values must be finite and non-negative. DefaultNone(unit weights — equivalent to scaling by 1).return_flows (bool, optional) – If True returns detailed cash-flow information instead of a scalar. In scalar mode: engine-specific
dict. In batch mode: aggregate portfoliodictwith keystime_grid,expected_cf,pv_cf,total_pv. Default False.t_output (NDArray[np.float64] or None, optional) – External time grid onto which aggregate cash flows are projected. Pandas and Polars Series are accepted; converted to NDArray[np.float64] before use. Only effective with
return_flows=Truein batch mode; raisesValueErrorifreturn_flows=False. When provided,flows['time_grid']equalst_outputandflows['pv_cf']/flows['expected_cf']are binned accordingly. DefaultNone(natural union grid of all payment times).on_error ({'raise', 'nan'}, optional) – Error-handling policy for batch mode (ignored in scalar mode).
'raise'(default): raiseValueErroron the first invalid record.'nan': return aBatchResultwithnp.nanfor each invalid record and a structuredBatchErrorReport.record_ids (sequence or None, optional) – Policy identifiers of length
N, propagated into theBatchErrorReportwhenon_error='nan'. Ignored in scalar mode. Any sequence is accepted, including Pandas and Polars Series; elements are accessed by positional integer index.
- Returns:
float – When
n,d, andtsare all scalar andreturn_flows=False.NDArray[np.float64] – Shape
(N,)when any ofn,d,tsis a sequence or array andreturn_flows=False.dict – When
return_flows=True. Scalar mode: engine dict. Batch mode:{'time_grid', 'expected_cf', 'pv_cf', 'total_pv'}.BatchResult – When batch mode and
on_error='nan': namedtuple withvalues(NDArray, NaN for invalid) anderrors(BatchErrorReport).
Notes
Boolean values for
ts,d, ornraiseTypeError(they are not accepted as numeric durations or shifts). Accepted types for each parameter are described in Parameters above.The present value of an annuity-immediate paying at frequency \(m\) per year for \(n\) years is:
\[a^{(m)}_{\overline{n}|} = \frac{1 - v^n}{i^{(m)}}\]where \(v^n = (1+i)^{-n}\) and \(i^{(m)} = m\left[(1+i)^{1/m} - 1\right]\). For \(m = 1\) this reduces to \(a_{\overline{n}|} = (1 - v^n)/i\).
Batch mode
When any of
n,d,ts,m, orgris a sequence or array, the method computes present values for all N policies under the same interest rate (self). Broadcasting rules: a length-1 array broadcasts against any other length. Incompatible non-unit lengths raiseValueError.Pandas and Polars Series are accepted for
n,d,ts,m, andgrwithout calling.to_numpy(); they are converted element-wise to the appropriate dtype internally.Since there is no mortality, all policies sharing the same
(n, d, ts)triplet produce an identical result. The implementation groups unique triplets (O(K) dispatches, K ≤ N) and broadcasts the result — maximally efficient for homogeneous portfolios.return_flows=Truewith a batch input returns an aggregate portfolio cash-flow dictionary. Supported for all four calculation modes. Theexpected_cfcolumn reflects the pure payment schedule without mortality discounting. Whenbenefitsis provided, each policy’s contribution toexpected_cfandpv_cfis scaled by its benefit value:total_pv = sum_i(benefits_i * unit_pv_i). Withreturn_flows=False, per-policy values use the batch unit-PV finalizer (round unit PV, then multiply bybenefits).This method does not mutate the original curve.
on_error=’nan’ and return_flows=True
Combining
on_error='nan'withreturn_flows=Truein batch mode always raisesValueError. This matches the behaviour ofLifeTable.ax.t_output — external time grid
When
t_outputis provided withreturn_flows=Truein batch mode, the aggregate cash-flow arrays are projected onto that grid instead of the natural union grid of all payment times. Useful for IFRS 17 balance-sheet dates, ALM reporting grids, or quarterly/annual alignment. Matches thet_outputsemantics ofLifeTable.ax. Passingt_outputwithoutreturn_flows=TrueraisesValueError.- Raises:
TypeError – If
ts,d, ornisboolor otherwise non-numeric in scalar mode.ValueError – If any input parameter is invalid (e.g., negative
d,n, orts; incompatiblegrandcashflow_amounts; incompatible batch array lengths; heterogeneous per-policymcombined withreturn_flows=True;on_error='nan'combined withreturn_flows=Truein batch mode;t_outputset withreturn_flows=False;t_outputset in scalar (non-batch) mode;on_error='nan'in scalar (non-batch) mode;record_idsin scalar (non-batch) mode).
See also
InterestRate.äPresent value of an annuity-due (payments at period start).
InterestRate.vnDiscount factor used in the present-value calculation.
LifeTable.axMortality-weighted annuity-immediate with per-policy
ir,m,gr, andt_outputsupport.
Examples
Simple 10-year annual immediate annuity at 3%:
>>> from lactuca import InterestRate >>> ir = InterestRate(0.03) >>> ir.a(n=10) 8.5302...
Batch over terms — returns NDArray shape (3,):
>>> import numpy as np >>> ir = InterestRate(0.03) >>> ir.a(n=np.array([10.0, 20.0, 30.0])) array([ 8.53..., 14.87..., 19.60...])
Per-policy deferment (broadcast scalar
n):>>> ir.a(n=20.0, d=np.array([0.0, 5.0, 10.0])) array([14.87..., 12.83..., 11.07...])
Aggregate portfolio cash flows (IFRS 17 / ALM use case):
>>> flows = ir.a(n=np.array([15.0, 20.0, 25.0]), return_flows=True) >>> sorted(flows.keys()) ['expected_cf', 'pv_cf', 'time_grid', 'total_pv']
Duration analysis — PV over a range of maturities:
>>> maturities = np.arange(1.0, 11.0, 1.0) >>> pv_curve = ir.a(n=maturities) >>> pv_curve.shape (10,)
Benefit-weighted portfolio cash flows (ALM / bond portfolio):
>>> import numpy as np >>> ir = InterestRate(0.03) >>> n_arr = np.array([10.0, 20.0, 30.0]) >>> benefits = np.array([50_000.0, 100_000.0, 75_000.0]) >>> flows = ir.a(n=n_arr, benefits=benefits, return_flows=True) >>> sorted(flows.keys()) ['expected_cf', 'pv_cf', 'time_grid', 'total_pv']
Per-policy payment frequency:
>>> import numpy as np >>> ir = InterestRate(0.03) >>> result = ir.a(n=np.array([10.0, 10.0, 10.0]), m=[1, 2, 12]) >>> result.shape (3,)
Project cash flows onto an annual reporting grid (IFRS 17):
>>> import numpy as np >>> ir = InterestRate(0.03) >>> t_out = np.arange(1.0, 21.0, 1.0) >>> flows = ir.a(n=np.array([10.0, 20.0]), return_flows=True, t_output=t_out) >>> len(flows['time_grid']) == len(t_out) True
- add_scenario(name: str, scenario: InterestRate) None#
Add a named scenario to this instance.
- Parameters:
name (str) – Name of the scenario to add. Must be unique among existing scenarios.
scenario (InterestRate) – An
InterestRateinstance (constant or piecewise). Nested multi-scenarioInterestRateinstances are not allowed.
- Raises:
TypeError – If
scenariois not anInterestRateinstance.ValueError – If
scenariocontains its own scenarios (nested multi-scenario).
Notes
If no active scenario is set, the newly added scenario becomes active.
Examples
>>> from lactuca import InterestRate >>> ir = InterestRate({'base': 0.01}) >>> ir.add_scenario('optimistic', InterestRate(0.012))
- copy() InterestRate#
Return a deep copy of this instance.
- Returns:
Independent copy with all state and caches duplicated. The
Configsingleton is shared;_cache_lockis a fresh lock.- Return type:
Examples
>>> from lactuca import InterestRate >>> ir = InterestRate(0.03) >>> ir2 = ir.copy()
- curve_analysis() dict#
Compute a concise analytical summary of the curve for diagnostics and reporting.
- Returns:
Structured summary with top-level keys:
"type":"constant"or"piecewise""curve_policy": dict of factual discount-engine scope flags
Constant curves include these additional keys:
"rate": float"rate_properties": dict (is_zero, is_negative, is_positive, absolute_value)"key_discount_factors": {“10y”: float, “30y”: float}
Piecewise curves include these additional keys:
"curve_length": float (sum of terms)"segment_count": int"rate_range": {“min”: float, “max”: float, “spread”: float}"rate_properties": counts and finiteness info"curve_properties": monotonicity, zero/negative counts, volatility, mean_rate"key_discount_factors": dict of selected vn values (vectorized)
Returned numeric values are Python floats/ints (cast from float64) for easy serialization.
For multi-scenario containers, returns a dict mapping each scenario name to its individual
curve_analysis()result; no top-level"type"or"curve_policy"keys are present in that case.- Return type:
dict
Notes
This method does not mutate instance state.
Designed for quick diagnostics;
curve_policystates what the discount engine supports — not regulatory compliance. For exhaustive reports usevalidate()andexport()(format="regulatory").Outputs use native Python types (
float,int,bool).
Examples
>>> from lactuca import InterestRate >>> ir = InterestRate(0.03) >>> ir.curve_analysis()["type"] 'constant' >>> ir = InterestRate(terms=[5, 5], rates=[0.02, 0.03, 0.04]) >>> ir.curve_analysis()["segment_count"] 2
- d_m(m, t: object = 0.0) float#
Compute the nominal discount rate convertible m-thly, \(d^{(m)}\).
The nominal discount rate \(d^{(m)}\) is the per-period discount rate, applied \(m\) times per year, that is equivalent to the effective annual rate \(i\):
\[d^{(m)} = m \left[1 - (1 + i)^{-1/m}\right]\]For piecewise term structures the segment rate \(i(t)\) at time
tis used for the conversion.- Parameters:
m (int) – Discounting frequency per year. Must be a positive integer — any value is valid (e.g.
12monthly,4quarterly,1annual,5or24for non-standard frequencies). Thismis not restricted to the payment frequencies accepted by annuity methods (a,ä); it is a pure rate-conversion parameter.t (float or int, optional) – Time in years used to select the applicable segment rate for piecewise curves. Has no effect for constant-rate instances. Must be non-negative. Default
0.0.
- Returns:
Nominal discount rate convertible m-thly, \(d^{(m)}\).
- Return type:
float
- Raises:
TypeError – If
misbool, not a plainint, or iftis not numeric.ValueError – If
mis not positive, iftis negative, or if the segment rate \(i\) satisfies \(i \leq -1\).
Notes
boolis rejected explicitly even though it is a subclass ofintin Python.
Key identities:
\(d^{(1)} = d = i / (1+i)\) (annual effective discount rate).
For \(i > 0\) and \(m > 1\): \(d^{(m)} > d\) (more frequent discounting requires a higher nominal rate).
Ordering: \(d < d^{(m)} < \delta < i^{(m)} < i\) for \(i > 0\), \(m > 1\).
Relationship with nominal interest rate: \(d^{(m)} = i^{(m)} \big/ (1 + i^{(m)}/m)\).
Limiting case: \(\lim_{m \to \infty} d^{(m)} = \delta = \ln(1+i)\).
The notation \(d^{(m)}\) (coded as
d_m) is standard in actuarial literature: Bowers et al., Dickson et al., and other international actuarial literature.Examples
Monthly nominal discount rate from 3 % effective annual:
>>> from lactuca import InterestRate >>> ir = InterestRate(0.03) >>> ir.d_m(12) 0.029522...
Annual (m=1) returns the annual effective discount rate \(d = i/(1+i)\):
>>> ir.d_m(1) 0.029126...
Ordering \(d^{(m)} < i^{(m)}\):
>>> ir = InterestRate(0.03) >>> ir.d_m(12) < ir.i_m(12) True
Piecewise:
>>> ir = InterestRate(terms=[5, 5], rates=[0.02, 0.03, 0.04]) >>> ir.d_m(12, t=6.0) 0.029522...
See also
InterestRate.i_mNominal interest rate \(i^{(m)}\).
InterestRate.deltaForce of interest \(\delta = \lim_{m \to \infty} d^{(m)}\).
InterestRate.snAccumulation factor \((1+i)^n\).
InterestRate.get_effective_rateEffective rate over a custom time interval.
- delta(t: object = None) object#
Compute the instantaneous force of interest (\(\delta\)) at the given time(s).
- Parameters:
t (float, int, list, NDArray[np.float64], Pandas/Polars Series, or None, optional) – Time(s) in years at which to compute the force. If
tisNoneand the curve is constant, the scalar constant force is returned. For piecewise curvestmust be provided.- Returns:
Force of interest \(\delta = \ln(1+i)\), returning 0.0 where
i == 0. A Pythonfloatfor scalar input (or whentisNonefor a constant curve);NDArray[np.float64]for array-like input.- Return type:
float or NDArray[np.float64]
- Raises:
TypeError – If
tis boolean or otherwise non-numeric (when provided).ValueError – If any queried instantaneous rate \(i\) satisfies \(i \leq -1\) (which makes the logarithm undefined). If
tisNonefor a piecewise curve.
Notes
The force of interest is defined by:
\[\delta = \ln(1 + i)\]For zero rates (\(i = 0\)), the force of interest is exactly \(0\). This method supports negative rates per modern regulatory practice and only rejects values that make \(\ln(1 + i)\) undefined (\(i \leq -1\)).
Examples
Constant curve:
>>> from lactuca import InterestRate >>> ir = InterestRate(0.03) >>> ir.delta() 0.0295588...
Piecewise scalar:
>>> ir = InterestRate(terms=[5], rates=[0.02, 0.03]) >>> ir.delta(2.0) 0.0198026...
Vectorized:
>>> ir.delta([0.5, 1.5, 6.0])
- export(format: Literal['dict', 'json', 'regulatory'] = 'dict') dict | str#
Serialize the InterestRate instance for API integration or regulatory reporting.
Three output formats are supported:
"dict"(default): plain Python dict with curve data and minimal metadata."json": JSON-formatted string (indent=2) suitable for storage or HTTP transport."regulatory": enriched dict with additional metadata required for regulatory reporting (timestamps, compliance notes, per-scenario summary).
Payload structure by curve type:
Constant curve:
{"type": "constant", "rate": <float>}.Piecewise curve:
{"type": "piecewise", "terms": [...], "rates": [...]}.Multi-scenario container: maps scenario names to each scenario’s dict export.
When
format == "regulatory", extra keys"_regulatory_metadata"(single curves) and"_metadata"(multi-scenario) are added, both including an ISO 8601export_timestamp.
Use
"dict"when downstream code needs to inspect or transform the payload programmatically; use"json"for serialisation to storage or HTTP transport.- Parameters:
format (str, optional) – Output format. One of
"dict","json", or"regulatory". Default"dict".- Returns:
The exported payload as a
dict(for"dict"and"regulatory") or a JSON string (for"json").- Return type:
Union[dict, str]
Notes
For piecewise curves, term lengths and rates are rounded to 6 decimal places in the exported payload for stable serialisation; constant curve rates are exported at full float64 precision. Instance state is unaffected in all cases.
Use the
"dict"payload to reconstruct an equivalentInterestRateprogrammatically. For complex roundtrips (scenarios, metadata) reconstruct by creatingInterestRateobjects from the exported scenario entries.
Examples
Constant curve:
>>> from lactuca import InterestRate >>> ir = InterestRate(0.03) >>> ir.export("dict") {'type': 'constant', 'rate': 0.03}
Piecewise curve to JSON:
>>> ir = InterestRate(terms=[1.0, 2.0], rates=[0.01, 0.015, 0.02]) >>> s = ir.export("json") # JSON string
Multi-scenario regulatory export:
>>> multi = InterestRate({'base': 0.01, 'stress': ([5], [0.02, 0.03])}) >>> multi.export("regulatory") {...}
See also
InterestRate.validateValidate the curve and return a structured report.
InterestRate.curve_analysisQuick analytical summary for diagnostics.
- get_average_force(n: object) float#
Compute the time-average (mean) force of interest over \([0, n]\).
The average force \(\bar{\delta}\) is defined so that \(\exp(-n \bar{\delta}) = v^n\), where \(v^n\) is the discount factor for duration \(n\). Equivalently:
\[\bar{\delta} = -\frac{\ln(v^n)}{n}\]- Parameters:
n (float or int) – Positive duration in years over which to compute the average force.
- Returns:
Time-average force of interest \(\bar{\delta}\) as a Python float.
- Return type:
float
- Raises:
TypeError – If
nis boolean or not numeric.ValueError – If
n<= 0 or the resulting discount factor \(v^n \leq 0\) (which makes the average force undefined).
Notes
The average force satisfies the following identities:
\(\exp(-n\bar{\delta}) = v^n\) where \(v^n\) is the n-year discount factor.
\(\bar{\delta} = \frac{1}{n}\int_0^n \delta(t)\,dt\) (true time-average of the instantaneous force).
For constant-rate curves: \(\bar{\delta} = \delta = \ln(1+i)\).
For piecewise curves: \(\bar{\delta}\) captures the weighted-average effect of all segments.
Implementation preserves float64 precision throughout. Supports zero and negative nominal rates per modern regulatory practice; only rejects cases where \(v^n \leq 0\).
Examples
Constant rate:
>>> from lactuca import InterestRate >>> ir = InterestRate(0.03) >>> round(ir.get_average_force(10), 6) 0.029559
Piecewise curve:
>>> ir = InterestRate(terms=[5, 5], rates=[0.02, 0.04, 0.03]) >>> ir.get_average_force(10)
- get_effective_rate(t_start: object, t_end: object) float#
Compute the effective periodic interest rate between two time points.
The effective rate \(r_\text{eff}\) for period \([t_\text{start},\, t_\text{end}]\) is the single-period compound rate that satisfies:
\[\left(1 + r_\text{eff}\right) = \left(\frac{v(t_\text{end})}{v(t_\text{start})}\right)^{-1/\tau}\]where \(v(t)\) is the present-value discount factor at time \(t\) and \(\tau = t_\text{end} - t_\text{start}\) is the period length in years.
- Parameters:
t_start (float or int) – Start time in years (must be < t_end).
t_end (float or int) – End time in years (must be > t_start).
- Returns:
Effective rate for the interval
[t_start, t_end]. Returns0.0when there is no discounting over the interval (i.e.v_end == v_start).- Return type:
float
- Raises:
TypeError – If
t_startort_endis boolean or otherwise non-numeric.ValueError – If
t_end <= t_start, or if the discount factor att_startort_endis zero (which makes the effective rate undefined).
Notes
Preserves float64 precision throughout.
Supports zero and negative rate environments per Solvency II / IFRS 17.
Prefer
delta()for force-of-interest calculations; this method returns the discrete (effective) rate for the requested interval.The method is robust against common numerical issues.
Examples
>>> from lactuca import InterestRate >>> ir = InterestRate(0.03) >>> round(ir.get_effective_rate(0.0, 1.0), 8) 0.03
>>> ir = InterestRate(terms=[5, 5], rates=[0.02, 0.03, 0.04]) >>> ir.get_effective_rate(2.0, 7.0)
- get_rate(t: object) object#
Retrieve the instantaneous rate(s) at time t.
- Parameters:
t (float, int, list, NDArray[np.float64], or Pandas/Polars Series) – Time(s) in years at which to obtain the spot rate. Accepts a scalar numeric (
int/float) or an array-like of numeric values. Pandas and Polars Series are accepted; values are converted toNDArray[np.float64]internally.- Returns:
Rate applying at time
t. A Pythonfloatfor scalar input;NDArray[np.float64]for array-like input.- Return type:
float or NDArray[np.float64]
- Raises:
TypeError – If
tis boolean or otherwise non-numeric.
Notes
If this
InterestRatecontains named scenarios, the call is delegated to the currently active scenario.For constant curves this method returns the constant rate (scalar or an array filled with the constant value).
For piecewise curves the rate for each queried time is the rate of the segment that contains it; the final rate applies indefinitely beyond the last term.
At an exact cumulative junction \(t = T_k\), the rate of the segment ending at \(T_k\) is returned (left-closed intervals \([T_{k-1}, T_k]\)). Implementation:
numpy.searchsorted(cum_terms, t, side='left'), consistent withget_segment_info()andvn().
Examples
>>> from lactuca import InterestRate >>> ir = InterestRate(terms=[1.0, 2.0], rates=[0.01, 0.015, 0.02]) >>> ir.get_rate(0.5) 0.01 >>> ir.get_rate([0.5, 1.5, 5.0]) array([0.01 , 0.015, 0.02 ])
- get_segment_info(t: object) dict#
Return detailed information about the rate segment that applies at time
t.- Parameters:
t (float or int) – Query time in years (must be non-negative).
- Returns:
A dictionary describing the segment at time
t. Keys present for both constant and piecewise curves:type(str) –"constant"or"piecewise".rate(float) – The rate applying at timet.rate_properties(dict) – Booleans{'is_zero', 'is_negative', 'is_positive'}.segment_start(float) – Start time of the segment (years).segment_end(float) – End time of the segment (float('inf')for the last or only segment).time_queried(float) – The originaltvalue.
Additional keys for piecewise curves only:
segment_index(int) – Zero-based index of the segment.segment_duration(float) – Duration of the segment (orfloat('inf')for the last segment).position_in_segment(float) – Offset oftfromsegment_start.
- Return type:
dict
- Raises:
TypeError – If
tis boolean or not a numeric scalar.
Notes
For multi-scenario containers, the result describes the active scenario.
For constant curves the segment covers \([0, \infty)\).
For piecewise curves the final rate applies indefinitely beyond the last cumulative term.
All numeric values are returned as native Python floats for easy serialization and display.
Examples
>>> from lactuca import InterestRate >>> ir = InterestRate(terms=[2.0, 3.0], rates=[0.01, 0.02, 0.03]) >>> info = ir.get_segment_info(1.5) >>> info['segment_index'] 0 >>> info['position_in_segment'] # 1.5 years from origin, first segment starts at 0.0 1.5
- i_m(m, t: object = 0.0) float#
Compute the nominal interest rate convertible m-thly, \(i^{(m)}\).
The nominal rate \(i^{(m)}\) is the per-period rate, compounded \(m\) times per year, that is equivalent to the effective annual rate \(i\):
\[i^{(m)} = m \left[(1 + i)^{1/m} - 1\right]\]For piecewise term structures the segment rate \(i(t)\) at time
tis used for the conversion.- Parameters:
m (int) – Compounding frequency per year. Must be a positive integer — any value is valid (e.g.
12monthly,4quarterly,2semi-annual,1annual,5or24for non-standard frequencies). Thismis not restricted to the payment frequencies accepted by annuity methods (a,ä); it is a pure rate-conversion parameter.t (float or int, optional) – Time in years used to select the applicable segment rate for piecewise curves. Has no effect for constant-rate instances. Must be non-negative. Default
0.0.
- Returns:
Nominal interest rate convertible m-thly, \(i^{(m)}\).
- Return type:
float
- Raises:
TypeError – If
misbool, not a plainint, or iftis not numeric.ValueError – If
mis not positive, iftis negative, or if the segment rate \(i\) satisfies \(i \leq -1\) (which makes \((1+i)^{1/m}\) undefined for real arithmetic).
Notes
boolis rejected explicitly even though it is a subclass ofintin Python.
Key identities:
\(i^{(1)} = i\) (annual, no conversion).
For \(i > 0\) and \(m > 1\): \(d^{(m)} < \delta < i^{(m)} < i\) (more frequent compounding requires a lower nominal rate).
Relationship with nominal discount rate: \(d^{(m)} = i^{(m)} \big/ \left(1 + i^{(m)}/m\right)\).
Limiting case: \(\lim_{m \to \infty} i^{(m)} = \delta = \ln(1+i)\).
For piecewise term structures this method returns the point-in-time conversion at the single segment containing
t, not a weighted average over the full curve. To derive a representative nominal rate over a multi-segment horizon, useget_effective_rate()to obtain the period effective rate first, then apply the formula above.The notation \(i^{(m)}\) (coded as
i_m) is standard in actuarial literature: Bowers et al., Dickson et al., and other international actuarial literature.Examples
Monthly nominal rate from 3 % effective annual:
>>> from lactuca import InterestRate >>> ir = InterestRate(0.03) >>> ir.i_m(12) 0.029595...
Annual (m=1) returns the effective rate unchanged:
>>> ir.i_m(1) 0.03
Piecewise — rate at the segment containing t=6.0:
>>> ir = InterestRate(terms=[5, 5], rates=[0.02, 0.03, 0.04]) >>> ir.i_m(12, t=6.0) 0.029595...
See also
InterestRate.d_mNominal discount rate \(d^{(m)}\).
InterestRate.deltaForce of interest \(\delta = \lim_{m \to \infty} i^{(m)}\).
InterestRate.snAccumulation factor \((1+i)^n\).
InterestRate.get_effective_rateEffective rate over a custom time interval.
- shifted(ts: object) InterestRate#
Shift the interest-rate curve forward by ts years and return the adjusted curve.
- Parameters:
ts (float or int) – Forward displacement in years. If
ts> 0 the curve is shifted forward (initial segments are shortened or removed). Ifts<= 0 the method returns an independent copy of this instance (no temporal change).- Returns:
A new InterestRate instance representing the original curve displaced forward by
tsyears. Iftsexceeds the total term length the result is a constant-rate curve using the final rate. Whents<= 0 or the curve is constant, returnsself.copy()so callers never share mutable state.- Return type:
- Raises:
TypeError – If ts is boolean or not a numeric type.
Notes
For multi-scenario containers, the result applies to the active scenario.
Boundary handling: when the remaining first-term duration after the shift is at or below the configured boundary tolerance, that segment is skipped and the next rate takes effect immediately.
Returned curves use
'years'as the internal term unit.This method does not mutate the original instance.
Examples
>>> from lactuca import InterestRate >>> ir = InterestRate(terms=[5, 5], rates=[0.02, 0.03, 0.04]) >>> ir.shifted(0.0) is not ir # zero shift returns independent copy True >>> shifted = ir.shifted(2.5) # new curve with first term shortened >>> shifted.is_constant False
- sn(n: object) object#
Compute the accumulation factor \(s_n = (1+i)^n\).
The accumulation factor is the reciprocal of the discount factor \(v^n\). For piecewise term structures the formula compounds correctly across segment boundaries, delegating to the optimised prefix-product implementation in
vn().- Parameters:
n (float, int, list, NDArray[np.float64], or Pandas/Polars Series) – Duration(s) in years. Accepts a scalar, a list, or a NumPy array. Pandas and Polars Series are accepted; values are converted to float64 internally.
- Returns:
Accumulation factor(s) \((1+i)^n\). Returns
float('inf')(element-wise for arrays) when the corresponding discount factor underflows to exactly zero (may occur at extremely long durations or very high rates).- Return type:
float or NDArray[np.float64]
- Raises:
TypeError – If
nis boolean or otherwise non-numeric.
Notes
For a constant rate \(i\):
\[s_n = (1 + i)^n\]For a piecewise term structure with segment lengths \(\Delta_1, \Delta_2, \ldots\) and effective annual rates \(i_1, i_2, \ldots\):
\[s_n = \prod_{k} (1 + i_k)^{\min(\Delta_k,\, \max(0,\, n - T_{k-1}))}\]Supports zero and negative rates per modern European regulatory practice (Solvency II / IFRS 17).
The
sn/vnduality holds exactly up to float64 precision:sn(n) * vn(n) == 1.0.Examples
Constant rate:
>>> from lactuca import InterestRate >>> ir = InterestRate(0.03) >>> ir.sn(5) 1.159274074...
Vectorized:
>>> ir.sn([1, 5, 10]) array([1.03 , 1.15927407, 1.34391638])
Piecewise:
>>> ir = InterestRate(terms=[5, 5], rates=[0.02, 0.03, 0.04]) >>> ir.sn(7.5)
Duality with
vn():>>> ir = InterestRate(0.03) >>> abs(ir.sn(10) * ir.vn(10) - 1.0) < 1e-12 True
See also
InterestRate.vnDiscount factor \(v^n = 1 / s_n\).
InterestRate.deltaForce of interest \(\delta = \ln(1+i)\).
InterestRate.get_average_forceTime-average force of interest over \([0, n]\).
- summary() str#
Alias for
__str__for compatibility.- Returns:
Human-readable string representation, equivalent to calling
str()on the curve.- Return type:
str
- validate() dict#
Validate this InterestRate instance and return a structured report.
Perform structural and numeric checks for the current curve (or all named scenarios) and produce a deterministic, machine-friendly validation report. The method does not raise on validation issues; it returns a dict describing findings so callers can decide whether to raise, log, or continue.
- Returns:
Hierarchical report whose structure depends on the curve type.
Single curves (constant or piecewise) always include:
"type"(str):"constant"or"piecewise"."status"(str):"valid","warning", or"invalid".
Constant curves add:
"rate","rate_properties","curve_policy".Piecewise curves add:
"structure","rates","rate_statistics","curve_policy","actuarial_metrics".Multi-scenario containers return a dict with:
"scenarios"(dict): mapping of scenario name to each scenario’svalidate()report."scenario_summary"(dict):"total_scenarios","active_scenario","scenario_names"."cross_scenario_analysis"(dict): aggregate statistics across all scenarios (counts, min/max rates, spread).
Intended for programmatic consumption and regulatory exports.
- Return type:
dict
Notes
Validation preserves float64 precision throughout; zero and negative rates are allowed (modern European curve practice).
curve_policydescribes discount-engine capabilities only — not Solvency II or IFRS 17 regulatory sign-off.Callers that require exceptions on invalid data should inspect the returned
statusand raise accordingly.
Examples
>>> from lactuca import InterestRate >>> ir = InterestRate(terms=[5, 5], rates=[0.02, 0.03, 0.04]) >>> report = ir.validate() >>> report["status"] == "valid" True
>>> multi = InterestRate({'base': 0.01, 'shock': ([5], [0.02, 0.03])}) >>> multi_report = multi.validate() >>> "scenarios" in multi_report True
See also
InterestRate.curve_analysisQuick analytical summary for diagnostics.
InterestRate.exportSerialize the curve for API integration or regulatory reporting.
- vn(n: object) object#
Compute the discount factor(s) \(v^n = (1+i)^{-n}\) for a given duration.
For constant curves this equals \((1+i)^{-n}\) directly; for piecewise term structures the formula compounds correctly across segment boundaries.
- Parameters:
n (float, int, list, NDArray[np.float64], or Pandas/Polars Series) – Duration(s) in years for which the discount factor(s) are calculated. Pandas and Polars Series are accepted; values are converted to float64 internally. Scalar inputs return a Python
float; array-like inputs return aNDArray[np.float64].- Returns:
Discount factor(s) corresponding to the given duration(s)
n. A Pythonfloatfor scalar input;NDArray[np.float64]for array-like input.- Return type:
float or NDArray[np.float64]
- Raises:
TypeError – If
nis boolean or otherwise non-numeric.ValueError – If any rate in the curve satisfies \(i \leq -1\), because \((1+i)^{-n}\) requires \((1+i) > 0\). Rates in \((-1, 0)\) are valid (e.g. negative ECB deposit rates) and do not raise. For piecewise curves, the message includes the offending array indices.
Notes
If the interest rate is zero, the discount factor is always 1.0, regardless of
n.For piecewise curves, non-positive durations return 1.0. For constant curves, the formula \((1+i)^{-n}\) is applied as-is; negative
nyields an accumulation factor greater than 1.Rate validation for \(i \leq -1\) is performed on this public path only; internal callers with
validate=Falsebypass the check for performance.
Examples
Constant rate:
>>> from lactuca import InterestRate >>> ir = InterestRate(0.03) >>> ir.vn(5) 0.862608784384163... >>> ir.vn([1, 5, 10]) # Vectorized array calculation array([0.97087379, 0.86260878, 0.74409391])
Negative but valid rate (deflation scenario):
>>> ir_neg = InterestRate(-0.005) # -0.5 %, valid per ECB practice >>> ir_neg.vn(1) 1.005025...
Invalid rate raises ValueError:
>>> InterestRate(-1.0).vn(5) ValueError: Rate -1.0 ≤ -1: (1+i) must be positive ...
Piecewise rate:
>>> ir = InterestRate(terms=[5, 5], rates=[0.02, 0.03, 0.04]) >>> ir.vn(7.5) >>> ir.vn([2.5, 7.5, 12.5])
Zero rate:
>>> ir = InterestRate(0.0) >>> ir.vn(10) # Always returns 1.0 for zero rates 1.0
Multi-scenario:
>>> ir = InterestRate({'base': 0.03, 'stress': ([5, 5], [0.02, 0.03, 0.04])}) >>> ir.active_scenario = 'stress' >>> ir.vn(7.5)
See also
InterestRate.vxRelative discount factor between two time points.
InterestRate.snAccumulation factor \((1+i)^n\), the reciprocal of \(v^n\).
InterestRate.deltaForce of interest; raises on the same rate ≤ -1 condition.
- vx(x: object, x0: object = 0.0) object#
Compute the discount factor for a duration of \(x - x_0\) years.
Applies the interest rate curve over the duration \(x - x_0\), starting from the curve origin. For constant-rate curves this equals \((1+i)^{-(x-x_0)}\); for piecewise term structures the segments are traversed from the curve origin over \(x - x_0\) years.
This is equivalent to
vn(x - x0)in all cases. The parameterx0is the temporal anchor of the curve (for example, the reference age for commutation functions), so the first rate segment is applied starting atx0and the discount factor covers the period \([x_0,\, x]\).- Parameters:
x (float, int, list, NDArray[np.float64], or Pandas/Polars Series) – Target time(s) in years. Scalar, sequence, or array-like input. Pandas and Polars Series are accepted; values are converted to float64 internally.
x0 (float, int, list, NDArray[np.float64], or Pandas/Polars Series, optional) – Initial time(s) in years. Default is 0.0. Scalar, sequence, or array-like input. Pandas and Polars Series are accepted; values are converted to float64 internally.
- Returns:
Discount factor(s) for the duration \(x - x_0\). A Python
floatwhen bothxandx0are scalars;NDArray[np.float64]when either is array-like.- Return type:
float or NDArray[np.float64]
- Raises:
TypeError – If
xorx0is boolean or otherwise non-numeric.ValueError – If x or x0 contains invalid values (e.g., negative times). If x and x0 have incompatible shapes for broadcasting. If any value in x is less than the corresponding value in x0.
Examples
Valid inputs:
>>> from lactuca import InterestRate >>> ir = InterestRate(0.03) >>> ir.vx(10, 5) 0.862608784384163... >>> ir.vx([10, 15], 5) array([0.86260878, 0.74409391])
Broadcasting:
>>> ir.vx([10, 15], [5, 10]) array([0.86260878, 0.86260878])
Invalid inputs raise
ValueError:>>> ir.vx(-10, 5) >>> ir.vx([10, 15], [5, 20])
See also
InterestRate.vnAbsolute discount factor for a given duration.
InterestRate.snAccumulation factor \((1+i)^n\), the reciprocal of \(v^n\).
- ä(*, ts: object = 0.0, d: object = 0.0, n: object = None, m: Literal[1, 2, 3, 4, 6, 12, 14, 24, 26, 52, 365] | Sequence[int] | NDArray[int64] = 1, gr: GrowthRate | float | None | Sequence[GrowthRate | float | None] = None, cashflow_amounts: Sequence[float] | None = None, benefits: Sequence[float] | NDArray[float64] | None = None, return_flows: bool = False, t_output: NDArray[float64] | None = None, on_error: Literal['raise', 'nan'] = 'raise', record_ids: Sequence[Any] | None = None) float | NDArray[float64] | dict | BatchResult#
Present value of an annuity-due (prepayable) using the current interest curve.
Accepts scalar or array inputs for
n,d, andts. When any of these parameters is a sequence or array, the method operates in batch mode and returns anNDArray[np.float64]of shape(N,).- Parameters:
ts (float, list, NDArray[np.float64], or Pandas/Polars Series, optional) – Forward shift (years) applied to the schedule before valuation. Accepts a scalar or an array/Series of shape
(N,)for per-policy values. Pandas and Polars Series are accepted without.to_numpy(). Default 0.0.d (float, list, NDArray[np.float64], or Pandas/Polars Series, optional) – Deferment (years) before payments start. Accepts a scalar or an array/Series of shape
(N,)for per-policy values. Pandas and Polars Series are accepted without.to_numpy(). Default 0.0.n (float, None, list, NDArray[np.float64], or Pandas/Polars Series, optional) – Term in years. If
None(scalar) ornp.inf(batch entry) the annuity is treated as a perpetuity. Accepts a scalar or an array/Series of shape(N,)for per-policy terms. Pandas and Polars Series are accepted without.to_numpy().m (PaymentFrequencyLiteral, sequence of int, NDArray[np.int64], or Pandas/Polars Series of int, optional) – Payments per year (payment frequency). Default 1 (annual). In batch mode accepts a scalar or a per-policy array of shape
(N,). Pandas and Polars Series are accepted without.to_numpy(); they are converted internally via the array protocol. When heterogeneous values are provided,return_flows=TrueraisesValueError; all other combinations are supported.gr (GrowthRate, float, None, sequence thereof, or Pandas/Polars Series of float, optional) – Growth rate applied to annuity payments. Default
None(no growth). A scalarfloatis auto-wrapped asGrowthRate(gr)(geometric growth). In batch mode accepts a shared scalar/object or a per-policy list of lengthN. Pandas and Polars Series of floats are accepted without.to_numpy(); each element is treated as a plainfloatgrowth rate (noGrowthRateobjects inside a Series). Distinct per-policy values are processed in sub-groups; withreturn_flows=Trueportfolio flows are aggregated across groups (same contract asLifeTable.äx).cashflow_amounts (sequence of float or None, optional) – Payment amounts for the regular due-timing schedule generated by
nandm. If provided, length must match the number of scheduled payments (n * m). Mutually exclusive withgr. Note:cashflow_timesis intentionally absent — supplying explicit payment times makes the due/immediate distinction void. Usea()withcashflow_timeswhen payment timing is fully specified by the caller.benefits (sequence of float or NDArray[np.float64] or None, optional) – Per-policy benefit weights, shape
(N,). Each policy’s cashflow contribution to the aggregate portfolio is scaled by its benefit value. Requires batch mode (arrayn,d,ts,m, orgr). Withreturn_flows=False, per-policy output follows the batch unit-PV finalizer (round(unit_pv, decimals) * benefits— exact equality witha(n) * benefits). Any sequence is accepted, including Pandas and Polars Series; values are converted to float64 internally. Compatible withreturn_flows=False(returnsNDArray[np.float64]with per-policy scaled PVs) andon_error='nan'. All values must be finite and non-negative. DefaultNone(unit weights — equivalent to scaling by 1).return_flows (bool, optional) – If True returns detailed cash-flow information instead of a scalar. In scalar mode: engine-specific
dict. In batch mode: aggregate portfoliodictwith keystime_grid,expected_cf,pv_cf,total_pv. Default False.t_output (NDArray[np.float64] or None, optional) – External time grid onto which aggregate cash flows are projected. Pandas and Polars Series are accepted; converted to NDArray[np.float64] before use. Only effective with
return_flows=Truein batch mode; raisesValueErrorifreturn_flows=False. When provided,flows['time_grid']equalst_outputandflows['pv_cf']/flows['expected_cf']are binned accordingly. DefaultNone(natural union grid of all payment times).on_error ({'raise', 'nan'}, optional) – Error-handling policy for batch mode (ignored in scalar mode).
'raise'(default): raiseValueErroron the first invalid record.'nan': return aBatchResultwithnp.nanfor each invalid record and a structuredBatchErrorReport.record_ids (sequence or None, optional) – Policy identifiers of length
N, propagated into theBatchErrorReportwhenon_error='nan'. Ignored in scalar mode. Any sequence is accepted, including Pandas and Polars Series; elements are accessed by positional integer index.
- Returns:
float – When
n,d, andtsare all scalar andreturn_flows=False.NDArray[np.float64] – Shape
(N,)when any ofn,d,tsis a sequence or array andreturn_flows=False.dict – When
return_flows=True. Scalar mode: engine dict. Batch mode:{'time_grid', 'expected_cf', 'pv_cf', 'total_pv'}.BatchResult – When batch mode and
on_error='nan': namedtuple withvalues(NDArray, NaN for invalid) anderrors(BatchErrorReport).
Notes
Boolean values for
ts,d, ornraiseTypeError(they are not accepted as numeric durations or shifts). Accepted types for each parameter are described in Parameters above.The present value of an annuity-due paying at frequency \(m\) per year for \(n\) years is:
\[\ddot{a}^{(m)}_{\overline{n}|} = \frac{1 - v^n}{d^{(m)}}\]where \(v^n = (1+i)^{-n}\) and \(d^{(m)} = m\left[1 - (1+i)^{-1/m}\right]\). For \(m = 1\): \(\ddot{a}_{\overline{n}|} = (1 - v^n)/d\) where \(d = i/(1+i)\).
This is the prepayable (annuity-due) variant: payments occur at the start of each period.
Batch mode
When any of
n,d,ts,m, orgris a sequence or array, the method computes present values for all N policies under the same interest rate (self). Broadcasting rules: a length-1 array broadcasts against any other length. Incompatible non-unit lengths raiseValueError.Pandas and Polars Series are accepted for
n,d,ts,m, andgrwithout calling.to_numpy(); they are converted element-wise to the appropriate dtype internally.Since there is no mortality, all policies sharing the same
(n, d, ts)triplet produce an identical result. The implementation groups unique triplets (O(K) dispatches, K ≤ N) and broadcasts the result — maximally efficient for homogeneous portfolios.return_flows=Truewith a batch input returns an aggregate portfolio cash-flow dictionary. Theexpected_cfcolumn reflects the pure payment schedule without mortality discounting. Whenbenefitsis provided, each policy’s contribution is scaled by its benefit value:total_pv = sum_i(benefits_i * unit_pv_i). Withreturn_flows=False, per-policy values use the batch unit-PV finalizer (round unit PV, then multiply bybenefits).Does not mutate the original curve.
on_error=’nan’ and return_flows=True
Combining
on_error='nan'withreturn_flows=Truein batch mode always raisesValueError. This matches the behaviour ofLifeTable.äx.t_output — external time grid
When
t_outputis provided withreturn_flows=Truein batch mode, the aggregate cash-flow arrays are projected onto that grid instead of the natural union grid of all payment times. Useful for IFRS 17 balance-sheet dates, ALM reporting grids, or quarterly/annual alignment. Matches thet_outputsemantics ofLifeTable.äx. Passingt_outputwithoutreturn_flows=TrueraisesValueError.- Raises:
TypeError – If
ts,d, ornisboolor otherwise non-numeric in scalar mode.ValueError – If any input parameter is invalid (e.g., negative
d,n, orts; incompatiblegrandcashflow_amounts; incompatible batch array lengths; heterogeneous per-policymcombined withreturn_flows=True;on_error='nan'combined withreturn_flows=Truein batch mode;t_outputset withreturn_flows=False;t_outputset in scalar (non-batch) mode;on_error='nan'in scalar (non-batch) mode;record_idsin scalar (non-batch) mode).
See also
InterestRate.aPresent value of an annuity-immediate (payments at period end).
InterestRate.vnDiscount factor used in the present-value calculation.
LifeTable.äxMortality-weighted annuity-due with per-policy
ir,m,gr, andt_outputsupport.
Examples
Simple 5-year annual annuity-due at 3%:
>>> from lactuca import InterestRate >>> ir = InterestRate(0.03) >>> ir.ä(n=5) 4.71709...
Batch over terms — returns NDArray shape (3,):
>>> import numpy as np >>> ir = InterestRate(0.03) >>> ir.ä(n=np.array([5.0, 10.0, 20.0])) array([ 4.71..., 8.78..., 15.32...])
Per-policy deferment:
>>> ir.ä(n=20.0, d=np.array([0.0, 5.0, 10.0])) array([15.32..., 13.21..., 11.40...])
Custom per-payment amounts on a 3-year annual due schedule:
>>> ir = InterestRate(0.03) >>> ir.ä(n=3, cashflow_amounts=[100, 110, 121]) 320.8...
Benefit-weighted portfolio cash flows (deferred pension portfolio):
>>> import numpy as np >>> ir = InterestRate(0.03) >>> n_arr = np.array([15.0, 20.0, 25.0]) >>> annual_pensions = np.array([12_000.0, 18_000.0, 24_000.0]) >>> flows = ir.ä(n=n_arr, benefits=annual_pensions, return_flows=True) >>> sorted(flows.keys()) ['expected_cf', 'pv_cf', 'time_grid', 'total_pv']
Per-policy payment frequency:
>>> import numpy as np >>> ir = InterestRate(0.03) >>> result = ir.ä(n=np.array([10.0, 10.0, 10.0]), m=[1, 2, 12]) >>> result.shape (3,)
Project cash flows onto an annual reporting grid (IFRS 17):
>>> import numpy as np >>> ir = InterestRate(0.03) >>> t_out = np.arange(0.0, 21.0, 1.0) >>> flows = ir.ä(n=np.array([10.0, 20.0]), return_flows=True, t_output=t_out) >>> len(flows['time_grid']) == len(t_out) True
- property active_scenario: str | None#
Name of the currently active scenario.
Returns
Nonefor single-curve instances (no scenarios defined).- Returns:
Active scenario name, or
Nonewhen no scenarios are present.- Return type:
Union[str, None]
See also
InterestRate.scenario_namesList of all stored scenario names.
InterestRate.add_scenarioAdd a named scenario to this container.
Examples
>>> from lactuca import InterestRate >>> ir = InterestRate({'base': 0.01, 'stress': 0.03}) >>> ir.active_scenario 'base' >>> ir.active_scenario = 'stress' >>> ir.active_scenario 'stress'
- property calculation_mode: Literal['discrete_precision', 'discrete_simplified', 'continuous_precision', 'continuous_simplified']#
Annuity calculation mode used by
a()andä().Returns an internal metadata override when present; otherwise mirrors the global
Config.calculation_modesetting. The publicInterestRate(...)constructor does not acceptcalculation_mode; change the mode viaConfigor theconfigalias before calling annuity methods (same source asLifeTable).- Returns:
One of
'discrete_precision','discrete_simplified','continuous_precision', or'continuous_simplified'.- Return type:
CalculationModeLiteral
See also
Config.calculation_mode : Global calculation mode setting. Interest Rates : Calculation mode section.
Examples
>>> from lactuca import InterestRate >>> ir = InterestRate(0.03) >>> ir.calculation_mode 'discrete_precision'
- property days_per_year: float#
Number of days per year used for day-denominator term conversions.
Returns the instance-level override (set at construction) when provided; falls back to the global
Configsetting otherwise.- Returns:
Days per year (one of 360.0, 365.0, or 365.25).
- Return type:
float
See also
InterestRate.weeks_per_yearEquivalent setting for week-denominator conversions.
Examples
>>> from lactuca import InterestRate >>> ir = InterestRate(0.03) >>> ir.days_per_year 365.0
- property is_constant: bool#
Whether the active curve (or this instance) is a constant-rate curve.
For multi-scenario containers, reflects the
is_constantflag of the currently active scenario.- Returns:
Trueif the active curve has a single constant rate;Falsefor piecewise term-structured curves.- Return type:
bool
Examples
>>> from lactuca import InterestRate >>> InterestRate(0.03).is_constant True >>> InterestRate(terms=[5], rates=[0.02, 0.03]).is_constant False
- property rates: NDArray[float64] | None#
Return a copy of the normalized rates array for this piecewise curve.
- Returns:
A copy of the one-dimensional float64 rates array for each segment. None if curve is constant or rates have not been set.
- Return type:
Union[NDArray[np.float64], None]
Notes
For piecewise curves, the last rate applies indefinitely (perpetuity behavior).
See also
InterestRate.termsCorresponding segment term lengths.
- property scenario_names: list[str]#
List of all stored scenario names.
- Returns:
Names of all stored scenarios in insertion order. Returns an empty list for single-curve instances.
- Return type:
list[str]
See also
InterestRate.active_scenarioCurrently active scenario name.
InterestRate.add_scenarioAdd a named scenario to this container.
Examples
>>> from lactuca import InterestRate >>> ir = InterestRate({'base': 0.01, 'stress': 0.03}) >>> ir.scenario_names ['base', 'stress']
- property terms: NDArray[float64] | None#
Return a copy of the normalized segment term lengths for this piecewise curve.
- Returns:
A copy of the one-dimensional float64 term-length array (in years). None if curve is constant or terms have not been set.
- Return type:
Union[NDArray[np.float64], None]
Notes
Terms represent the duration of each rate segment. For a piecewise curve with \(n\) terms, there must be \(n+1\) rates (the last rate applies beyond the final term).
See also
InterestRate.ratesCorresponding interest rates for each segment.
- property weeks_per_year: float#
Number of weeks per year used for week-denominator term conversions.
Returns the instance-level override (set at construction) when provided; falls back to the global
Configsetting otherwise.- Returns:
Weeks per year (52.0 or 52.1775).
- Return type:
float
See also
InterestRate.days_per_yearEquivalent setting for day-denominator conversions.
Examples
>>> from lactuca import InterestRate >>> ir = InterestRate(0.03) >>> ir.weeks_per_year 52.0