API reference#

Inputs#

class fastcashflow.ModelPoints(issue_age, premium, term_months, benefits=None, maturity_benefit=None, annuity_payment=None, disability_income=None, disability_benefit=None, surrender_base_amount=None, contract_boundary_months=None, premium_term_months=None, premium_frequency_months=None, annuity_frequency_months=None, account_value=None, minimum_crediting_rate=None, minimum_death_benefit=None, minimum_accumulation_benefit=None, annuitization_months=None, annuitization_rate=None, annuity_air_annual=None, annuity_start_months=None, annuity_term_months=None, annuity_guarantee_months=None, coverage_index=None, coverage_amount=None, coverage_offset=None, coverage_waiting=None, coverage_reduction_end=None, coverage_reduction_factor=None, coverage_step_month=None, coverage_step_factor=None, coverage_escalation_annual=None, coverage_escalation_cap=None, coverage_term=None, coverage_is_main=None, count=None, sex=None, state=None, issue_class=None, elapsed_months=None, product=None, channel=None, calculation_methods=None, coverage_codes=None, issue_date=None, attributes=None, mp_id=None)[소스]#

Columnar model point data.

Every scalar field is a numpy array of length n_mp; the model-point axis is the vectorised dimension throughout the engine. Monetary amounts are stated per single policy; count is how many policies the model point stands for – it defaults to one (one row per policy: seriatim), and a larger value scales the policy linearly through the projection.

A policy’s claim benefits are a variable-length list of coverages (see fastcashflow.coverage), held in CSR (Compressed Sparse Row) form so the kernels loop them generically – new benefit types add no fields:

  • coverage_index[k] – the coverage code; an integer index into Basis.coverages (entry i of that tuple lives at code i). No code is reserved.

  • coverage_amount[k] – the benefit amount of coverage k.

  • coverage_offset(n_mp+1,); policy mp’s coverages are the slice [coverage_offset[mp] : coverage_offset[mp+1]].

Each coverage may carry a benefit rule: coverage_waiting (months from issue with no benefit), coverage_reduction_end / coverage_reduction_factor (a benefit multiplier in force until a cut-off month) and coverage_term (the coverage’s own maturity in months from issue – it pays nothing from that month on, even when the contract boundary runs longer, e.g. a whole-life main with an 80-age term rider; 0 = run to the contract boundary). All are CSR arrays aligned with coverage_index and default to off – no waiting, full benefit, no per-coverage maturity.

The coverage list is built one of two ways. benefits is the general form: a {cov_idx: amount array} map keyed by coverage code (the index into Basis.coverages). Or pass the CSR arrays coverage_index / coverage_amount / coverage_offset directly – the preferred form for a portfolio with per-coverage benefit rules (waiting / reduction periods).

Premiums and survival benefits stay as plain fields – they do not proliferate the way claim benefits do:

  • premium – premium charged each payment occurrence.

  • premium_term_months – months the level premium is collected, defaulting to the full coverage term.

  • premium_frequency_months – months between level-premium payments (1 monthly, 3 quarterly, 6 half-yearly, 12 annual), defaulting to 1.

  • maturity_benefit – benefit on survival to the end of the term.

  • annuity_payment – survival income paid each payout occurrence.

  • annuity_frequency_months – months between annuity payouts, defaulting to 1.

  • disability_income – income paid each month a benefit state is occupied (disability income on a disabled state).

  • disability_benefit – lump sum paid when a lump-sum transition fires (a disability lump sum on becoming disabled).

매개변수:
property n_mp: int#

Number of model points.

axis(name)[소스]#

Resolve a grouping axis to a (n_mp,) label array by name.

Used by fastcashflow.group() to aggregate on any axis. Resolution order: the derived issue_year (calendar year of issue_date); the named source fields product / channel / issue_date; then any key in attributes (portfolio_id, profitability_group, risk_class, …). Raises KeyError listing the available axes when the name is unknown.

매개변수:

name (str)

반환 형식:

ndarray

classmethod single(issue_age, premium, term_months, benefits=None, maturity_benefit=0.0, annuity_payment=0.0, disability_income=0.0, disability_benefit=0.0, premium_term_months=None, premium_frequency_months=1, annuity_frequency_months=1, account_value=0.0, minimum_crediting_rate=None, minimum_death_benefit=0.0, minimum_accumulation_benefit=0.0, annuitization_months=0, annuitization_rate=0.0, annuity_air_annual=nan, annuity_start_months=0, annuity_term_months=0, annuity_guarantee_months=0, count=1.0, sex=0, state=0, calculation_methods=None)[소스]#

Build a single-model-point set – a convenience for hand checks.

benefits is the per-coverage benefit-amount map keyed by coverage CODE (str), e.g. {"DEATH": 1_000_000.0} – the engine aligns it to Basis.coverages by code, not by position. Each code must also be mapped to a CalculationMethod via calculation_methods (no code-as-method auto-inference). None means no claim benefits.

매개변수:
  • issue_age (float)

  • premium (float)

  • term_months (int)

  • benefits (dict[str, float] | None)

  • maturity_benefit (float)

  • annuity_payment (float)

  • disability_income (float)

  • disability_benefit (float)

  • premium_term_months (int | None)

  • premium_frequency_months (int)

  • annuity_frequency_months (int)

  • account_value (float)

  • minimum_crediting_rate (float | None)

  • minimum_death_benefit (float)

  • minimum_accumulation_benefit (float)

  • annuitization_months (int)

  • annuitization_rate (float)

  • annuity_air_annual (float)

  • annuity_start_months (int)

  • annuity_term_months (int)

  • annuity_guarantee_months (int)

  • count (float)

  • sex (int)

  • state (int)

  • calculation_methods (dict[str, CalculationMethod] | None)

반환 형식:

ModelPoints

subset(indices)[소스]#

Return a new ModelPoints carrying the rows at indices.

Per-row fields (issue_age, premium, …) and the segment metadata (product, channel) are sliced. The coverage CSR is rebuilt: each selected row’s coverage slice coverage_index[coverage_offset[i]:coverage_offset[i+1]] is concatenated, and coverage_offset is reset to the new running cumulative sum. Used by fastcashflow.gmm.measure() to split a portfolio by (product, channel) before per-segment measurement.

indices is expected to select distinct rows – it is a row selection, not a gather. As an optimisation the result skips the re-validation the constructor runs (the parent was already validated), so a repeated index (subset([0, 0])) would carry a duplicate mp_id the constructor would otherwise reject. Every engine caller passes a unique segment index, so this is safe on the hot path; pass distinct indices when calling it directly.

반환 형식:

ModelPoints

class fastcashflow.Basis(mortality_annual, lapse_annual, discount_annual, ra_confidence, mortality_cv, expense_items=(), expense_inflation=0.0, surrender_value_curve=None, surrender_value_basis='cum_premium_factor', waiver_incidence_annual=None, lapse_paidup_annual=None, lapse_waiver_annual=None, premium_factor_annual=None, annuity_factor_annual=None, surrender_charge_annual=None, ci_incidence_annual=None, ci_reincidence_annual=None, disability_recovery_annual=None, state_mortality_annual=None, longevity_cv=0.0, morbidity_cv=0.0, expense_cv=0.0, disability_cv=0.0, ra_method='confidence_level', cost_of_capital_rate=0.06, coverage_unit_discount=False, investment_return=0.0, fund_fee=0.0, coi_annual=None, premium_load=0.0, settlement_pattern=None, coverages=(), state_machine=None)[소스]#

Deterministic assumption set – no assumption changes over time.

매개변수:
  • mortality_annual (collections.abc.Callable[[numpy.ndarray[tuple[Any, ...], numpy.dtype[numpy.int64]], numpy.ndarray[tuple[Any, ...], numpy.dtype[numpy.float64]], numpy.ndarray[tuple[Any, ...], numpy.dtype[numpy.int64]], numpy.ndarray[tuple[Any, ...], numpy.dtype[numpy.int64]], numpy.ndarray[tuple[Any, ...], numpy.dtype[numpy.int64]]], numpy.ndarray[tuple[Any, ...], numpy.dtype[numpy.float64]]]) –

    Annual mortality-rate callable. Like every rate function on Basis, it takes the unified five positional grids (sex, issue_age, duration, issue_class, elapsed) and returns an array of annual rates of the same shape – see RateFn (in fastcashflow._typing) for the full contract: sex (0 male, 1 female), issue_age (years), duration (completed policy years, 0-based), issue_class (at-issue / underwriting class), elapsed (semi-Markov sojourn). A table without a given axis broadcasts over it. The engine converts the annual rate to a monthly one (see annual_to_monthly()). A select-and-ultimate basis lets the rate depend on duration within the select period and on attained age (issue_age + duration) beyond it; that logic lives in this callable, not the engine.

    A legacy three-arg (sex, issue_age, duration) callable still works (it is auto-wrapped to the five-arg shape). WARNING: do not bake a constant in as a fourth default parameter – lambda s, a, d, f=factor: ... is read as a four-arg rate, and the engine passes issue_class into f, silently overriding it (wrong rates, no error). Capture the constant in a closure instead.

  • lapse_annual (collections.abc.Callable[[numpy.ndarray[tuple[Any, ...], numpy.dtype[numpy.int64]], numpy.ndarray[tuple[Any, ...], numpy.dtype[numpy.float64]], numpy.ndarray[tuple[Any, ...], numpy.dtype[numpy.int64]], numpy.ndarray[tuple[Any, ...], numpy.dtype[numpy.int64]], numpy.ndarray[tuple[Any, ...], numpy.dtype[numpy.int64]]], numpy.ndarray[tuple[Any, ...], numpy.dtype[numpy.float64]]]) – Same five-arg RateFn shape as mortality_annual. Typical lapse depends only on duration, but the signature also lets a table key on sex / issue_age / issue_class when the workbook carries those axes (the engine reads the callable on the full grid either way).

  • discount_annual (float | numpy.ndarray[tuple[Any, ...], numpy.dtype[numpy.float64]]) – Annual locked-in discount rate (paragraph 36). Either a flat scalar or a per-year (n_years,) array; the engine expands either to a per-month rate curve via fastcashflow.curves.discount_monthly_curve(). Used for discounting cash flows and for CSM interest accretion.

  • expense_items (tuple[fastcashflow.basis.ExpenseItem, ...]) –

    Row-form expense ledger – a tuple of ExpenseItem. Each row carries a category (EXPENSE_CATEGORIES – acquisition / maintenance / collection / lae; the first three are the Korean alpha / beta / gamma convention), a base (EXPENSE_BASES – per_policy / premium / surrender_value / face / claim) and a numeric value. The engine projects every row through derive_expense_components() into the kernel-side primitives (named <category>_<base>). An empty tuple is the no-expense basis.

    IFRS 17 (paragraphs B65-B66): only DIRECTLY ATTRIBUTABLE expenses enter the fulfilment cash flows. Put the directly-attributable amount in value – i.e. gross expense already multiplied by the company’s direct-allocation ratio. The engine does NOT split direct from indirect; the non-attributable portion is excluded from the FCF by simply not being entered (that allocation is an ETL / expense-policy step upstream of measurement).

  • expense_inflation (float | numpy.ndarray[tuple[Any, ...], numpy.dtype[numpy.float64]]) – Global annual inflation applied to the recurring expense items (maintenance_per_policy and lae). Either a flat scalar – closed-form (1+i)^(t/12) growth – or a per-year (n_years,) array (compounds across years, in-year fractional ramp on the current year, held flat past the end). Macro-economic assumption, defined once per segment; the I/O layer points the segments sheet at one named scenario in the inflation_tables sheet (analogous to discount_annual / discount_tables). Does not apply to the two _init bases (one-time at t=0) or to premium_pct (which already rides the premium).

  • ra_confidence (float) – Confidence level for the Risk Adjustment (e.g. 0.75). The RA lifts the liability from its best estimate to this percentile.

  • mortality_cv (float) – Coefficient of variation of death claims – the mortality-risk component of the RA.

  • waiver_incidence_annual (collections.abc.Callable[[numpy.ndarray[tuple[Any, ...], numpy.dtype[numpy.int64]], numpy.ndarray[tuple[Any, ...], numpy.dtype[numpy.float64]], numpy.ndarray[tuple[Any, ...], numpy.dtype[numpy.int64]], numpy.ndarray[tuple[Any, ...], numpy.dtype[numpy.int64]], numpy.ndarray[tuple[Any, ...], numpy.dtype[numpy.int64]]], numpy.ndarray[tuple[Any, ...], numpy.dtype[numpy.float64]]] | None) – Maps (sex, issue_age, duration_years) to an array of annual waiver-incidence rates – the rate at which active in-force transitions to the premium-waived state. Same signature as mortality_annual. None means no transitions: every model point keeps its input state for the whole projection. The spelling matches the standard actuarial term incidence – a per-unit-time event rate – used by the rest of the engine for analogous rates (ci_incidence_annual, ci_reincidence_annual).

  • longevity_cv (float) – Coefficient of variation of survival benefits (maturity benefits and annuity payments) – the longevity-risk component of the RA. The RA components are added (the natural mortality / longevity hedge is not credited – conservative for mixed contracts).

  • morbidity_cv (float) – Coefficient of variation of morbidity claims (hospitalisation, surgery, outpatient) – the morbidity-risk component of the RA.

  • expense_cv (float) – Coefficient of variation of expense cash flows – the expense-risk component of the Risk Adjustment. VFA-only in v1: vfa.measure uses it directly, but the GMM / PAA RA sums the mortality / morbidity / disability / longevity components only. Adding the expense term to the GMM RA – and so closing the gap to the IFRS 17 non-financial-risk RA – is future work; a non-zero expense_cv on a GMM / PAA measurement raises NotImplementedError rather than silently doing nothing (set it to 0, or use VFA).

  • disability_cv (float) – Coefficient of variation of disability cash flows – disability income and the on-transition lump sum – the disability-risk component of the Risk Adjustment.

  • ra_method (str) – Which Risk Adjustment technique to use – "confidence_level" (the default; a percentile margin on the benefit present values) or "cost_of_capital". The cost-of-capital method is available through measure(..., full=True); the fast path (full=False) computes the confidence-level RA.

  • cost_of_capital_rate (float) – Annual cost-of-capital rate for the cost-of-capital RA – the rate charged on the non-financial-risk capital held over the run-off.

  • investment_return (float) – Annual return earned on the underlying items backing an account-value (VFA) contract.

  • fund_fee (float) – Annual variable-fee rate – the entity’s share of the underlying items, deducted from the account value each period (VFA).

  • coi_annual (collections.abc.Callable[[numpy.ndarray[tuple[Any, ...], numpy.dtype[numpy.int64]], numpy.ndarray[tuple[Any, ...], numpy.dtype[numpy.float64]], numpy.ndarray[tuple[Any, ...], numpy.dtype[numpy.int64]], numpy.ndarray[tuple[Any, ...], numpy.dtype[numpy.int64]], numpy.ndarray[tuple[Any, ...], numpy.dtype[numpy.int64]]], numpy.ndarray[tuple[Any, ...], numpy.dtype[numpy.float64]]] | None) – Universal-life cost-of-insurance charge rate – the same five-arg RateFn shape as mortality_annual. The monthly COI deducted from a UL account is annual_to_monthly(coi_annual(grid)) * NAR, where the net amount at risk NAR = max(0, face - account value) and the face is the model point’s minimum_death_benefit. It is a contractual charge, DISTINCT from the best-estimate mortality_annual used to value actual death claims; their spread is the mortality margin that drives the UL CSM. None charges no COI. UL-only.

  • premium_load (float) – Universal-life premium load – the fraction (0..1) of each premium withheld before crediting to the account (prem_to_av = premium * (1 - premium_load)). The full premium is still the insurer inflow; the load margin emerges in the fulfilment cash flows because only the net-of-load amount grows the account. An account-mechanics parameter, not an expense-ledger row. UL-only.

  • settlement_pattern (numpy.ndarray[tuple[Any, ...], numpy.dtype[numpy.float64]] | None) – Claims run-off pattern – the fractions of an incurred claim paid in the month it is incurred, the next month, and so on, summing to 1. None settles every claim immediately. It measures the liability for incurred claims and discounts claims to their payment dates in the best-estimate liability.

  • coverages (tuple[fastcashflow.basis.CoverageRate, ...]) – Ordered tuple of CoverageRate – the rate-driven coverages (death-type, morbidity and diagnosis), one per coverage code. No code is reserved: entry i lives at code i, the integer the portfolio’s coverage_index CSR uses to index this tuple. A contract’s death coverage, if any, is just one entry whose rate_table typically references the same mortality table the engine uses as the in-force decrement (mortality_annual) – the two are different mathematical quantities (decrement vs claim payout) that happen to share a table in most products. The taxonomy side – whether a coverage code runs as a diagnosis pool vs a recurring claim – lives on the portfolio (fastcashflow.model_points.ModelPoints.calculation_methods), not here.

  • state_machine (fastcashflow.multistate.Model | None) – The product’s in-force state machine – a Model declaring the transient states, their transitions and which states pay premium or a benefit. None uses the default active / waiver model (ACTIVE_WAIVER_MODEL); the waiver_incidence_annual rate then drives the active -> waiver transition. A product with a different state set supplies its own.

  • surrender_value_curve (ndarray[tuple[Any, ...], dtype[float64]] | None)

  • surrender_value_basis (str)

  • lapse_paidup_annual (Callable[[ndarray[tuple[Any, ...], dtype[int64]], ndarray[tuple[Any, ...], dtype[float64]], ndarray[tuple[Any, ...], dtype[int64]], ndarray[tuple[Any, ...], dtype[int64]], ndarray[tuple[Any, ...], dtype[int64]]], ndarray[tuple[Any, ...], dtype[float64]]] | None)

  • lapse_waiver_annual (Callable[[ndarray[tuple[Any, ...], dtype[int64]], ndarray[tuple[Any, ...], dtype[float64]], ndarray[tuple[Any, ...], dtype[int64]], ndarray[tuple[Any, ...], dtype[int64]], ndarray[tuple[Any, ...], dtype[int64]]], ndarray[tuple[Any, ...], dtype[float64]]] | None)

  • premium_factor_annual (Callable[[ndarray[tuple[Any, ...], dtype[int64]], ndarray[tuple[Any, ...], dtype[float64]], ndarray[tuple[Any, ...], dtype[int64]], ndarray[tuple[Any, ...], dtype[int64]], ndarray[tuple[Any, ...], dtype[int64]]], ndarray[tuple[Any, ...], dtype[float64]]] | None)

  • annuity_factor_annual (Callable[[ndarray[tuple[Any, ...], dtype[int64]], ndarray[tuple[Any, ...], dtype[float64]], ndarray[tuple[Any, ...], dtype[int64]], ndarray[tuple[Any, ...], dtype[int64]], ndarray[tuple[Any, ...], dtype[int64]]], ndarray[tuple[Any, ...], dtype[float64]]] | None)

  • surrender_charge_annual (Callable[[ndarray[tuple[Any, ...], dtype[int64]], ndarray[tuple[Any, ...], dtype[float64]], ndarray[tuple[Any, ...], dtype[int64]], ndarray[tuple[Any, ...], dtype[int64]], ndarray[tuple[Any, ...], dtype[int64]]], ndarray[tuple[Any, ...], dtype[float64]]] | None)

  • ci_incidence_annual (Callable[[ndarray[tuple[Any, ...], dtype[int64]], ndarray[tuple[Any, ...], dtype[float64]], ndarray[tuple[Any, ...], dtype[int64]], ndarray[tuple[Any, ...], dtype[int64]], ndarray[tuple[Any, ...], dtype[int64]]], ndarray[tuple[Any, ...], dtype[float64]]] | None)

  • ci_reincidence_annual (Callable[[ndarray[tuple[Any, ...], dtype[int64]], ndarray[tuple[Any, ...], dtype[float64]], ndarray[tuple[Any, ...], dtype[int64]], ndarray[tuple[Any, ...], dtype[int64]], ndarray[tuple[Any, ...], dtype[int64]]], ndarray[tuple[Any, ...], dtype[float64]]] | None)

  • disability_recovery_annual (Callable[[ndarray[tuple[Any, ...], dtype[int64]], ndarray[tuple[Any, ...], dtype[float64]], ndarray[tuple[Any, ...], dtype[int64]], ndarray[tuple[Any, ...], dtype[int64]], ndarray[tuple[Any, ...], dtype[int64]]], ndarray[tuple[Any, ...], dtype[float64]]] | None)

  • state_mortality_annual (dict[str, Callable[[ndarray[tuple[Any, ...], dtype[int64]], ndarray[tuple[Any, ...], dtype[float64]], ndarray[tuple[Any, ...], dtype[int64]], ndarray[tuple[Any, ...], dtype[int64]], ndarray[tuple[Any, ...], dtype[int64]]], ndarray[tuple[Any, ...], dtype[float64]]]] | None)

  • coverage_unit_discount (bool)

property discount_monthly: float#

First-year monthly discount rate, used as a representative scalar.

Reserved for the few places that need a single rate – the claims settlement-pattern present-value factor (paragraph 40 / B71) – where the in-year rate is the right reference. The per-month rate curve the kernels consume is composed by fastcashflow.curves.discount_monthly_curve(), which handles both a flat scalar and a per-year curve uniformly.

class fastcashflow.BasisRouter(segments, segment_axes=('product', 'channel'), measurement_models=None)[소스]#

Routes a model point to its segment’s SegmentSpec.

Returned by fastcashflow.read_basis(). A BasisRouter is not a Basis and not a dict – it is the routing policy that maps a segment key (a tuple over segment_axes, e.g. ("TERM_LIFE_A", "GA")) to that segment’s SegmentSpec (its Basis + measurement model). measure(mp, router) reads segment_axes to route each model point with no segment_by argument; an entry point that needs one Basis calls resolve_one().

매개변수:
  • segments{segment-key: Basis} mapping. Copied; the router does not alias it.

  • segment_axes – The axis names a segment key is read over – ("product", "channel") by default, or whatever non-assumption columns the segments sheet declares.

  • measurement_models – Optional {segment-key: "GMM"|"PAA"|"VFA"}; every other segment defaults to "GMM". Validated keyed to segments so a model can never name a non-existent segment.

참고

It deliberately does not implement the mapping protocol (no [] / iteration / len) – reach the underlying mapping explicitly through segments (a read-only view of {key: Basis}), or resolve through resolve() / resolve_spec() / resolve_one(). Both internal stores are immutable, so the per-segment model can never drift from its Basis after construction.

property axes: tuple#

The segment axis names (alias of segment_axes).

property segments#

Read-only {segment-key: Basis} view (immutable).

resolve(key)[소스]#

The Basis for one segment key, e.g. ("TERM_LIFE_A", "GA").

반환 형식:

Basis

resolve_spec(key)[소스]#

The full SegmentSpec (Basis + measurement model) for a key.

반환 형식:

SegmentSpec

measurement_model_of(key)[소스]#

The IFRS 17 measurement model (‘GMM’|’PAA’|’VFA’) for one segment.

반환 형식:

str

resolve_one(*, entry='this operation')[소스]#

The single Basis when there is exactly one segment.

Raises with an actionable message when the router carries more than one segment (the caller does not route segments).

매개변수:

entry (str)

반환 형식:

Basis

class fastcashflow.CoverageRate(code, rate, funds_from_account=False, pays_account_balance=False)[소스]#

One rate-driven coverage’s assumption – a coverage code and how it runs.

매개변수:
  • code (str) – The coverage’s code label. The engine works in the integer grid index this factorises to; the label is what the model-point file names a coverage by.

  • rate (float | int | collections.abc.Sequence[float] | collections.abc.Callable[[numpy.ndarray[tuple[Any, ...], numpy.dtype[numpy.int64]], numpy.ndarray[tuple[Any, ...], numpy.dtype[numpy.float64]], numpy.ndarray[tuple[Any, ...], numpy.dtype[numpy.int64]], numpy.ndarray[tuple[Any, ...], numpy.dtype[numpy.int64]], numpy.ndarray[tuple[Any, ...], numpy.dtype[numpy.int64]]], numpy.ndarray[tuple[Any, ...], numpy.dtype[numpy.float64]]]) – A RateLike – a flat scalar, a per-policy-year array, a polars / pandas rate table, or a RateFn callable (the same signature as mortality_annual). Whatever the form, it is normalised to a RateFn here; the engine converts the annual rate to a monthly one (see annual_to_monthly()).

  • funds_from_account (bool) – Account-chassis interaction flag (universal-life funding mechanism). When True the coverage’s monthly risk charge is drawn from the policy’s account value – the death leg’s cost-of-insurance on the net amount at risk. The coverage’s rate is then the COI rate (coi_annual). Defaults to False (a plain rate-driven claim).

  • pays_account_balance (bool) – Account-chassis interaction flag. When True the coverage’s benefit reads the account balance – death pays max(account value, face). Such a coverage is EXCLUDED from the aggregate claim-rate accumulator and the rule-bearing claim loop; the account death benefit is written once from the rolled account value. Defaults to False.

참고

Whether a coverage runs as a depleting diagnosis pool vs a recurring claim, and which risk class the RA prices it as, is derived from the portfolio-level CalculationMethod taxonomy (the calculation_methods.csv file, surfaced as fastcashflow.model_points.ModelPoints.calculation_methods). Those two flags do not live on CoverageRate. The two account-chassis flags above DO live here – they are a contract-level funding choice, not a benefit-method routing key.

class fastcashflow.CalculationMethod(*values)[소스]#

How a benefit pays out – the engine’s calculation routing key.

Five uniform methods: every rate-driven death coverage (main contract or attached, accidental or all-cause, ADB / disease / disaster) is the same DEATH method; the rate table is what differentiates them. The method is purely a calculation-routing label – there is no “main-contract” method, because the engine has no reserved coverage slot.

str, Enum – members compare equal to their string value (CalculationMethod.MORBIDITY == "MORBIDITY"), so existing numpy array comparisons and dict keys keep working unchanged.

class fastcashflow.ExpenseItem(category, base, value, months=None)[소스]#

One typed entry in the expense ledger.

An expense is two orthogonal axes – WHAT it is for (category) and WHAT it is proportional to (base) – plus a value. The pair (category, base) dispatches the row onto a kernel-side primitive; the timing (at issue, every in-force month, or paying months only) is implied by the category, so it is not a separate field. Inflation is not a row attribute – it lives on Basis (expense_inflation, matching the way discount_annual lives on Basis), so a company’s economic basis is named in one place and every inflation-bearing row picks it up automatically.

매개변수:
  • category (str) – WHAT the expense is for – one of EXPENSE_CATEGORIES: "acquisition" (at issue), "maintenance" (every in-force month), "collection" (premium-collection cost, paying months), "lae" (Loss Adjustment Expense, claims-handling cost). The first three map to the Korean actuarial alpha / beta / gamma convention – acquisition = alpha, maintenance = beta, collection = gamma. The category also fixes the timing and whether expense_inflation applies (see Notes).

  • base (str) – WHAT the expense is proportional to – one of EXPENSE_BASES: "per_policy" (a flat amount per policy), "premium" (a fraction of premium), "surrender_value" (a fraction of the in-force cash surrender value), "claim" (a fraction of the month’s claim outflow). The valid (category, base) pairs are the keys of the dispatch table; e.g. "surrender_value" is a maintenance base only (the Korean % -of-surrender-reserve loading), requires a surrender_value_curve on the basis, and is rejected on an account-backed (universal-life / VFA) book where the account fund_fee already charges the account value.

  • value (float) – Numeric value – an amount per policy for base="per_policy", a fraction (0..1) for the proportional bases.

  • months (int | None) – Optional window (in policy months) over which the item is charged, in force, instead of the category’s natural timing. None (the default) keeps that timing – acquisition once at t=0, maintenance every in-force month. An integer N >= 1 charges the item only on policy months [0, N) – the installment / front-loaded commission shape (the Korean installment new-business commission): e.g. ExpenseItem("acquisition", "premium", 0.02, months=12) is a first-year % premium commission trail, charged on the in-force, premium-paying book for the first 12 months. Supported only on the "premium" base (the commission use case); the t=0 lump stays a plain months=None (acquisition, premium) row.

참고

The category fixes the inflation treatment: maintenance/per_policy and claims/claim recur every month and so inflate; acquisition pays once at t=0, the premium bases ride the premium itself, and maintenance/surrender_value rides the surrender-value curve’s own growth, so for those a second inflation factor would double-count.

fastcashflow.derive_expense_components(expense_items, n_time, inflation_index=None)[소스]#

Project expense_items onto the seven kernel-side primitives.

Each ExpenseItem is dispatched by its (category, base) pair (see _EXPENSE_DISPATCH) onto one of these primitives. Returns (acquisition_premium, acquisition_per_policy, maintenance_premium, maintenance_per_policy, lae, maintenance_surrender_value, maintenance_face):

  • acquisition_premium – sum of value over (acquisition, premium) rows. Paid at t=0 on annualized premium.

  • acquisition_per_policy – sum of value over (acquisition, per_policy) rows. Paid at t=0 per policy.

  • maintenance_premium[t] – per-month % premium rate: (maintenance, premium) AND (collection, premium) rows charge value every premium-paying month, and any months-windowed premium row (a commission / installment leg, including a windowed (acquisition, premium) row) charges value only on months [0, months). The kernel applies inforce_t * maintenance_premium[t] * monthly_premium while premium is paid. Not inflated (it rides the premium).

  • maintenance_per_policy[t] – per-month per-policy maintenance: each (maintenance, per_policy) row contributes value / 12 * inflation_index[t].

  • lae[t] – LAE (Loss Adjustment Expense) fraction: each (claims, claim) row contributes value * inflation_index[t]. Applied to the month’s claim + morbidity + disability total.

  • maintenance_surrender_value – sum of value over (maintenance, surrender_value) rows. A scalar annual rate charged each in-force month on the in-force surrender value (value / 12 * inforce_surrender_value[t]); the surrender-value base is built downstream in project_cashflows from the basis’ surrender_value_curve / surrender_value_basis. NOT inflated: the base (a reserve-/surrender-value curve) already carries its own growth, so a second inflation factor would double-count – the same reasoning that exempts the alpha_* and maintenance_premium bases.

  • maintenance_face – sum of value over (maintenance, face) rows. A scalar annual rate charged each in-force month on the policy’s sum assured (value / 12 * inflation_index[t] * face_amount); the face amount (the main coverage’s coverage_amount, flagged by ModelPoints.coverage_is_main) is applied downstream in project_cashflows. Inflated like maintenance_per_policy: the base (a level sum assured) does not grow, so the maintenance rate inflates.

inflation_index is the (n_time,) per-month inflation multiplier produced by fastcashflow.curves.inflation_index(); a scalar economic expense_inflation = i gives inflation_index[t] = (1+i)^(t/12) and a per-year curve compounds across years. Pass None for a no-inflation basis (every month equal to 1.0).

매개변수:
반환 형식:

tuple[float, float, ndarray[tuple[Any, …], dtype[float64]], ndarray[tuple[Any, …], dtype[float64]], ndarray[tuple[Any, …], dtype[float64]], float, float]

fastcashflow.EXPENSE_BASES = ('per_policy', 'premium', 'surrender_value', 'face', 'claim')#

Built-in immutable sequence.

If no argument is given, the constructor returns an empty tuple. If iterable is specified the tuple is initialized from iterable’s items.

If the argument is a tuple, the return value is the same object.

fastcashflow.RA_METHODS = ('confidence_level', 'cost_of_capital')#

Built-in immutable sequence.

If no argument is given, the constructor returns an empty tuple. If iterable is specified the tuple is initialized from iterable’s items.

If the argument is a tuple, the return value is the same object.

fastcashflow.SURRENDER_VALUE_BASES = ('cum_premium_factor', 'amount_per_policy', 'amount_per_unit')#

Built-in immutable sequence.

If no argument is given, the constructor returns an empty tuple. If iterable is specified the tuple is initialized from iterable’s items.

If the argument is a tuple, the return value is the same object.

fastcashflow.RISK_MORTALITY = 0#

int([x]) -> integer int(x, base=10) -> integer

Convert a number or string to an integer, or return 0 if no arguments are given. If x is a number, return x.__int__(). For floating-point numbers, this truncates towards zero.

If x is not a number or if base is given, then x must be a string, bytes, or bytearray instance representing an integer literal in the given base. The literal can be preceded by ‘+’ or ‘-’ and be surrounded by whitespace. The base defaults to 10. Valid bases are 0 and 2-36. Base 0 means to interpret the base from the string as an integer literal. >>> int(‘0b100’, base=0) 4

fastcashflow.RISK_MORBIDITY = 1#

int([x]) -> integer int(x, base=10) -> integer

Convert a number or string to an integer, or return 0 if no arguments are given. If x is a number, return x.__int__(). For floating-point numbers, this truncates towards zero.

If x is not a number or if base is given, then x must be a string, bytes, or bytearray instance representing an integer literal in the given base. The literal can be preceded by ‘+’ or ‘-’ and be surrounded by whitespace. The base defaults to 10. Valid bases are 0 and 2-36. Base 0 means to interpret the base from the string as an integer literal. >>> int(‘0b100’, base=0) 4

Measurement (GMM)#

fastcashflow.gmm.measure(model_points, basis, *, full=True, backend='cpu', discount_curve=None, segment_by=None, state_reserve=False, sum_at_risk=False)[소스]#

GMM measurement – the single entry point.

full=True (default) returns the complete roll-forward: the (n_mp,) inception headline and the (n_mp, n_time+1) *_path trajectories. Those trajectories make it memory-bound – several dense (n_mp, n_time+1) float64 arrays, on the order of ~100 KB per model point for a long horizon, so a million-policy full=True run needs ~100 GB and will OOM on a typical box. full=False is the fused, memory-minimal fast path – it fills only the headline (*_path are None) at a few hundred bytes per model point, and is the right choice for large-scale valuation; reserve full=True for movement analysis or per-segment / chunked runs.

basis may be a single Basis (uniform portfolio) or a {(product, channel): Basis} dict; with a dict each segment is routed to its own basis. segment_by names the routing axes (resolved via ModelPoints.axis(), so any attributes column works) and the dict keys are tuples of those axes in order. Left as None (the default) it is taken from the basis: a BasisRouter from read_basis() carries the axes its workbook declared, and a plain dict falls back to ("product", "channel"). So a workbook keyed by (product, channel, risk_class) routes by all three with no extra argument; passing segment_by explicitly overrides. Cost scales with the number of distinct segments, not the number of axes. backend ("cpu"/"gpu") and discount_curve apply to the fast path only.

state_reserve (full=True, single Basis, Markov book) also fills Measurement.state_reserve – the per-state policy value V^i(t) shaped (n_mp, n_states, n_time+1), with sum_i occ_i(t) V^i(t) == bel_path[t]. sum_at_risk additionally fills Measurement.sum_at_risk / .transitions – the per-transition net exposure S^ij + V^j - V^i (death / lapse / inter-state), shaped (n_mp, n_transition, n_time+1).

매개변수:
반환 형식:

Measurement

fastcashflow.gmm.measure_aggregate(model_points, basis, *, chunk_size=200000)[소스]#

Portfolio-aggregate full=True measurement in bounded memory.

measure(full=True) materialises dense (n_mp, n_time+1) trajectories – ~100 KB per model point – so a million-policy book needs ~100 GB and OOMs. But BEL / RA / CSM are additive across contracts, so the portfolio’s liability run-off is the per-model-point trajectories summed over the model-point axis. This runs the full trajectory kernel over row-blocks of chunk_size model points and accumulates only that (n_time+1,) sum, so peak memory is O(chunk_size x n_time) regardless of n_mp.

Returns an Aggregate (scalar totals + aggregate bel_path / ra_path / csm_path). For the per-model-point detail (movement, in-force slicing) use measure() on a book small enough to hold every trajectory. basis may be a single Basis or a per-segment dict, routed per chunk exactly as measure() routes it.

매개변수:
반환 형식:

Aggregate

fastcashflow.gmm.measure_inforce(model_points, state, basis, *, period_months=None, full=True)[소스]#

In-force diagnostic / runoff valuation at a single date.

The diagnostic companion to fastcashflow.gmm.settle() (the paragraph-44 period-close settlement). Each model point is valued at its elapsed_months duration: the BEL / RA are full current estimates at that date (paragraph 40), while the CSM is a carry-only approximation – the prior period’s closing CSM (state.prior_csm) accreted at state.lock_in_rate and released over coverage units across period_months (default 12), with no paragraph-44(c) unlocking. The result is stamped measurement_basis='settlement_carry' and the inception-only consumers (group / group_of_contracts / roll_forward / report / transition / the plots) reject it; period-close balances come from settle.

The bel / ra are re-based to the valuation date: the projection runs from inception, so the slice is scaled by count / inforce[elapsed] to set the as-of in-force to the input count – exact for every cash flow linear in the in-force (premium, claim, morbidity, expense, maturity, annuity, and the amount_per_policy / amount_per_unit surrender value). The one approximation is the cum_premium_factor surrender mode: it reconstructs the base from the projected cumulative premium (lapse x cum_premium x factor), which ignores premiums paid before the valuation date, so it is path-dependent and only sample-grade. A UserWarning fires only in that mode (basis carries a cum_premium_factor surrender curve and any elapsed_months > 0); the contractual amount_per_policy / amount_per_unit curves are linear in the in-force and re-base exactly, so they are the production-grade surrender input and warn-free.

state is the InforceState returned by read_inforce_policies() (it carries prior_csm / lock_in_rate, plus the elapsed_months / count reconciled onto model_points). model_points and state must be reconciled by apply_inforce_state() first – read_inforce_policies returns the pair already reconciled; the two-file path (read_model_points + read_inforce_state) calls it explicitly. A model_points whose elapsed_months / count disagree with state is rejected (a stale snapshot must not borrow a fresh state’s CSM). full=True (default) returns the BEL / RA / CSM trajectories and cash flows for movement analysis; full=False returns just the headline numbers (faster).

Paragraph-44(c) unlocking, experience adjustments and the loss-component movement live in fastcashflow.gmm.settle() (loss_component is zero in this mode); the opening -> closing movement of a reporting period comes from settle’s SettlementMovement, not from this projector.

매개변수:
반환 형식:

Measurement

fastcashflow.gmm.settle(model_points, state, basis, *, period_months=None, premium_experience_future_fraction=0.0)[소스]#

Paragraph-44 subsequent-measurement settlement of a GMM in-force book.

The opening -> closing movement over one reporting period: BEL / RA re-measured at current rates (B72(a)), the CSM accreted at the locked-in rate (44(b)/B72(b), direct compounding), adjusted for the future-service change measured at the locked-in rate (44(c)/B72(c) – the gap to the current-rate measure is the finance_wedge, insurance finance income/expense per B97(a), outside the CSM block), run through the paragraph-48/50(b) loss-component algebra, and released once at the period end over coverage units (44(e)/B119, em_open denominator).

GMM carries no account value, so the expected and observed legs share one unit projection (count = 1) and differ only by scale: k_exp = prior_count / unit_inforce[em_open] (the on-track expectation) and k_obs = count / unit_inforce[em_close] (the observation). On-track counts make every experience line zero and the closing CSM telescopes to measure_inforce’s monthly carry exactly.

Premium experience (B96(a)/B97(c)): pass state.actual_premium (the premium cash actually received over the period) and the entity’s premium_experience_future_fraction to split the experience adjustment actual_premium - expected_premium between future service (the CSM, at the B72(c) locked-in measure – csm_premium_experience) and current/past service (a P&L memo – premium_experience_revenue, recognised in insurance revenue, TRG 2018-09). The fraction defaults to 0.0 (all current/past, the BC233 general rule); the standard leaves the split to entity judgment. The lapse-driven future-premium effect is already carried by the count channel, so default 0.0 avoids double-counting – set the fraction above 0 only for premium received now for genuinely future coverage the count deviation does not capture.

An onerous book amortises its loss component through the paragraph-50(a)/51 incurred-service channel (loss_component_finance / loss_component_amortised on the movement): the period’s released claims and expenses (51a), RA release (51b) and finance (51c) are split on the systematic loss-component ratio r = loss_component_opening / pool_opening between the loss component and the LRC excluding it, running the loss component to zero by the end of coverage (52).

A settlement_pattern basis is supported: the movement carries the liability for incurred claims (lic_opening / claims_incurred / claims_paid / lic_closing, paragraphs 40(b) / 42 / 103(b)) – claims build it up as incurred and run it off over the pattern, undiscounted and at the expected scale, reconstructed from the projection each period.

Within-period claims and expense experience (B97(b)/(c)) is surfaced when state.actual_claims / state.actual_expenses are given: the actual-minus-expected difference is recognised in the insurance service result (claims_experience / expense_experience, P&L memos, not the CSM and not a balance recursion). Absent the inputs they are zero.

v1 scope (documented cuts, mirroring vfa.settle): the closing balances (BEL / RA / CSM / LIC) are still built on the expected within-period run (the experience above is a P&L memo, not a re-derivation of the balances); the LIC roll is expected-scale and undiscounted (no 42(c) finance / 33-37 discount + RA on the LIC, the same cut the measure takes); no B96(c) investment-component split, so the paragraph-50(a) pool includes the whole non-premium outflow (surrender / maturity not separated as investment components, B124(ii)); the RA change enters the CSM at its current measure (B96(d) prescribes no rate); no OCI – the finance_wedge is the period’s P&L line, not an accumulated-OCI state. A maturity falling inside the period is expected service (it seeds the unit BEL at the boundary and runs off through the release line), not experience.

매개변수:
반환 형식:

SettlementMovement

fastcashflow.gmm.settle_aggregate(model_points, state, basis, *, period_months=None, premium_experience_future_fraction=0.0, chunk_size=200000)[소스]#

Portfolio-total paragraph-44 settlement in bounded memory.

settle() materialises (n_mp, n_time) projection intermediates – two backward kernel passes over the whole book – so a million-policy close would peak far beyond memory. Every line of the settlement movement is additive across contracts, so this runs settle() over row blocks of chunk_size model points and accumulates only the scalar line totals; peak memory is O(chunk_size x n_time) regardless of n_mp.

Returns a SettlementAggregate: the movement’s lines summed, movement-positive (reconcile applies the display negation and reproduces the per-MP movement’s table exactly). The aggregate cannot be chained – closing_inputs() raises; chain per-MP movements instead. state joins model_points by mp_id once, before chunking, so a period-close file in its own row order never pairs one contract’s rows with another’s prior balances.

매개변수:
반환 형식:

SettlementAggregate

fastcashflow.gmm.settle_stream(input_path, output_dir, basis, *, coverages=None, calculation_methods=None, state_path=None, period_months=None, chunk_size=200000, id_column=None, validate_unique_mp_id=True)[소스]#

Stream a paragraph-44 period close through a parquet file, chunk by chunk.

The out-of-core variant of fastcashflow.gmm.settle(): reads the in-force book in chunk_size blocks, settles each block, and writes the per-MP settlement movements as a parquet dataset – one part-NNNNN.parquet per chunk under output_dir, every movement line plus the measurement_basis marker. Peak memory is one chunk’s projection, so a book whose per-MP movements would not fit in memory still closes. Returns the number of model points processed.

Input layouts (both produce identical output):

  • One combined file (primary): input_path carries the policies spec plus the closing-state columns (elapsed_months, count, prior_csm, lock_in_rate, prior_count, prior_loss_component) – the period-close snapshot of read_inforce_policies().

  • Two files: input_path is the standard policies parquet and state_path the state parquet (the read_inforce_state() layout), semi-joined per chunk on mp_id. The global id sets must match in both directions (validated up front – a semi-join would silently drop a mismatch); a duplicate state mp_id is rejected like a duplicate policies mp_id.

coverages is the per-contract coverage parquet (required, as in measure_stream()). lock_in_rate may vary by row – a cohort-aware book whose issue cohorts / GoCs locked in different inception rates (paragraph B72(b)): each chunk’s settle partitions by rate, so the streamed close equals the in-memory settle() per contract.

Chaining on disk: each part carries the closing-state columns – count, lock_in_rate, elapsed_months and the closing balances – so the next period’s state file is assembled from the parts alone: prior_csm <- csm_closing, prior_loss_component <- loss_component_closing, prior_count <- count, then advance elapsed_months / count to the next observation. The disk side of SettlementMovement.closing_inputs().

매개변수:
반환 형식:

int

fastcashflow.gmm.recognition_schedule(model_points, state, basis, *, band_edges_months=(12, 36, 60), period_months=None)[소스]#

Paragraph-109 maturity-band disclosure for a settled GMM book.

Allocates the closing CSM (the settle() closing of model_points / state) to maturity bands by each contract’s forward coverage-unit fraction, so the bands SUM TO the closing CSM – when, in maturity terms, the remaining CSM is expected to be recognised in profit or loss. The coverage units are the in-force count from the valuation date (the B119 amortisation proxy, undiscounted), so the schedule matches the actual CSM release. Onerous contracts carry no CSM and contribute nothing.

band_edges_months are the band boundaries in months from the valuation date (default 12 / 36 / 60, the four-band disclosure axis); period_months is the settlement period (default 12), as for settle().

매개변수:
반환 형식:

CSMRecognitionSchedule

class fastcashflow.gmm.CSMRecognitionSchedule(band_edges_months, csm, closing_csm)[소스]#

IFRS 17 paragraph-109 disclosure: the closing CSM allocated to maturity bands by expected coverage-unit recognition.

band_edges_months are the band boundaries in months from the valuation date (default 12 / 36 / 60, the four-band disclosure axis); the bands are [0, e0), [e0, e1), ..., [e_last, end). csm[b] is the closing CSM expected to be recognised in band b – allocated by each contract’s forward coverage-unit fraction, so the bands SUM TO closing_csm. It is an allocation of the remaining balance, not the accreted nominal release; the coverage-unit proxy is the in-force count, undiscounted, matching the B119 amortisation kernel, so the schedule tracks the actual release pattern.

매개변수:
property labels: tuple#

Band labels, in years when the edges are whole years.

class fastcashflow.gmm.Measurement(bel, ra, csm, loss_component, bel_path=None, ra_path=None, csm_path=None, csm_accretion=None, csm_release=None, lic_path=None, state_reserve=None, sum_at_risk=None, transitions=None, cashflows=None, discount_factor_bom=None, discount_factor_mid=None, model_points=None, group_labels=None, group_sizes=None, measurement_basis='inception')[소스]#

IFRS 17 GMM measurement: BEL, RA and CSM.

The headline fields (bel, ra, csm, loss_component) are (n_mp,) inception values and are always present.

The trajectory fields are the roll-forward over time and are populated only by measure(..., full=True); on the headline-only fast path (full=False) they are None. bel_path / ra_path / csm_path are the (n_mp, n_time+1) trajectories whose column 0 is the inception value (so bel == bel_path[:, 0] when full). The CSM roll-forward decomposes as csm_path[:, t+1] = csm_path[:, t] + csm_accretion[:, t] - csm_release[:, t]. lic_path is the liability for incurred claims – zero unless a claims settlement pattern is set, which also discounts claims to their payment dates in the BEL.

매개변수:
  • bel (FloatArray)

  • ra (FloatArray)

  • csm (FloatArray)

  • loss_component (FloatArray)

  • bel_path (FloatArray | None)

  • ra_path (FloatArray | None)

  • csm_path (FloatArray | None)

  • csm_accretion (FloatArray | None)

  • csm_release (FloatArray | None)

  • lic_path (FloatArray | None)

  • state_reserve (FloatArray | None)

  • sum_at_risk (FloatArray | None)

  • transitions (tuple | None)

  • cashflows (Cashflows | None)

  • discount_factor_bom (FloatArray | None)

  • discount_factor_mid (FloatArray | None)

  • model_points (ModelPoints | None)

  • group_labels (np.ndarray | None)

  • group_sizes (IntArray | None)

  • measurement_basis (str)

estimate_at(month)[소스]#

The current estimate (BEL / RA / CSM / LIC) at a future month.

This is the deterministic nested-projection view (IFRS 17 paragraph 40): the cohort liability the entity would carry at month t if the central best-estimate scenario unfolds to it. It is column t of the trajectories – so estimate_at(0).bel equals the inception headline bel – and equals a fresh in-force measurement at that month, gmm.measure_inforce(elapsed=t) carrying the deterministic survivor count (reading the trajectory and re-projecting agree; the tests assert this for every t). The returned object’s per_survivor re-bases every figure to one surviving policy.

Requires a full=True measurement (the trajectory paths); the fast path carries only the inception headline. GMM only for now – VFA / PAA carry the same *_path shape and can gain this later.

매개변수:

month (int)

반환 형식:

CurrentEstimate

Premium allocation approach#

fastcashflow.paa.measure(model_points, basis, *, revenue_basis='time', full=True)[소스]#

Measure a portfolio under the Premium Allocation Approach.

The LRC rolls forward as LRC[t+1] = LRC[t] + premium[t] - revenue[t] from LRC[0] = 0 – premiums received build it up, insurance revenue releases it. A single-premium contract gives the textbook pro-rata unearned premium reserve.

revenue_basis selects the paragraph B126 allocation of insurance revenue, which always sums to the total premium:

  • "time" – B126(a), passage of time: the premium earned straight-line over the coverage period (the default).

  • "claims" – B126(b), the expected timing of incurred claims and expenses; for when the release of risk differs significantly from the passage of time. A policy with no service expense has no such pattern and falls back to "time".

The onerous test reuses the GMM fulfilment cash flows: a contract whose inception fulfilment cash flows are a net outflow carries that outflow as a loss component.

full=True (default) returns the LRC / revenue / LIC trajectories; full=False fills only the headline lrc / loss_component / fcf and leaves the trajectory and cash-flow fields None. The headline path skips the LRC roll, revenue allocation and LIC entirely (only the onerous test is needed), so revenue_basis is immaterial there. It is the building block the portfolio orchestrator chunks to bound memory. basis must resolve to a single Basis; multi-segment routers are not accepted.

매개변수:
반환 형식:

Measurement

fastcashflow.paa.measure_aggregate(model_points, basis, *, revenue_basis='time', chunk_size=200000)[소스]#

Portfolio-aggregate PAA measurement in bounded memory.

The PAA analogue of fastcashflow.gmm.measure_aggregate(): the LRC, revenue, service expense and LIC are additive across contracts, so the portfolio’s run-off is the per-model-point trajectories summed over the model-point axis. Runs measure(..., full=True) over row-blocks of chunk_size model points and accumulates only the (n_time+1,) / (n_time,) sums, so peak memory is O(chunk_size x n_time) regardless of n_mp (the PAA has no fused kernel – a block still materialises dense transients, so chunking is the memory lever).

Returns a Aggregate (scalar LRC / loss-component totals + the aggregate lrc_path / revenue / service_expense / lic_path). It is a scalable sum of the measured model-point results – not a group remeasurement; the onerous loss is each contract’s floored loss summed, not a group-level re-floor. basis is a single Basis (mixed / routed portfolios go through fastcashflow.portfolio.measure_aggregate()).

매개변수:
반환 형식:

Aggregate

fastcashflow.paa.measure_inforce(model_points, state, basis, *, revenue_basis='time', full=True)[소스]#

In-force diagnostic / runoff valuation of a PAA book at a single date.

The PAA has no CSM, so there is no prior-CSM carry-forward (IFRS 17 paragraph 44 is the CSM roll, which the PAA does not have): the in-force Liability for Remaining Coverage is the unearned premium still to be earned at each contract’s elapsed_months duration. The projection runs from inception (measure()) and the LRC trajectory is sliced at elapsed_months per model point, re-based by count / inforce[elapsed] so the as-of in-force equals the input count – exact for the LRC, which is linear in the in-force.

state (an InforceState) supplies the period-close elapsed_months / count, reconciled onto model_points by apply_inforce_state(); its prior_csm / lock_in_rate are ignored – the PAA has no CSM. The subsequent onerous re-test on remaining coverage (paragraphs 57-58) belongs to the PAA period-close settlement (a later phase), so loss_component is zero and fcf is None here – this is a diagnostic / runoff view, like gmm.measure_inforce.

full=True (default) keeps the inception-to-horizon LRC / revenue / service-expense / LIC trajectories for movement analysis, with the headline lrc the as-of valuation-date value; full=False returns just the headline lrc.

Limitations (v1): because fcf is None (the onerous re-test is deferred), group_of_contracts() on a PAA in-force result raises – the re-floor has no fulfilment-cash-flow input.

매개변수:
반환 형식:

Measurement

fastcashflow.paa.settle(model_points, state, basis, *, revenue_basis='time', period_months=None)[소스]#

Paragraph-55(b) period-close settlement of a PAA in-force book.

The opening -> closing movement over one reporting period: the Liability for Remaining Coverage rolled per paragraph 55(b), the loss component recalculated per paragraphs 57-58 at each date (no balance tracking), and the Liability for Incurred Claims rolled on the expected basis – including settlement-pattern books, unlike gmm.settle / vfa.settle.

The PAA’s structural simplification: the opening balances are RECONSTRUCTED from a unit projection rather than carried. The GMM CSM is history-dependent (accumulated unlocking) and must arrive in state.prior_csm; the PAA LRC is the mechanical paragraph 55(b) roll, so with expected within-period cash flows (the v1 cut) the unit projection rebuilds every opening figure exactly. state.prior_count is therefore the only required prior-date input; prior_csm / lock_in_rate / prior_loss_component are ignored (prior_loss_component is echoed by closing_inputs() for state-file continuity only). Chaining telescopes without any carried state – the next period’s reconstructed opening equals this period’s closing as an identity, on- or off-track.

One unit projection (count = 1), two scales: k_exp = prior_count / unit_inforce[em_open] (the expected leg – opening LRC, premiums, revenue, the whole LIC block) and k_obs = count / unit_inforce[em_close] (the observation – closing LRC). Their gap is the lrc_experience line. The LIC block stays entirely at k_exp: incurred claims are past events, not in-force, so re-scaling them by the closing count would be meaningless.

paragraph 55(b)(i) premium experience (the PAA counterpart of the GMM B96(a)): when state.actual_premium is given, the premiums line is the actual premium received over the period rather than the expected. PAA has no CSM, so the difference sits in the LRC (the unearned premium, paragraph 55(a)) and is earned as revenue over the remaining coverage – there is no future/current split. The higher LRC also feeds the paragraphs 57-58 onerous re-test. Absent the input the premiums stay expected (byte-identical).

paragraph 55(b) items with no movement line are documented engine cuts, not omissions: acquisition cash flows and their amortisation are zero under the paragraph 59(a) expensed-as-incurred option, the financing adjustment is zero because the LRC is held undiscounted (paragraph 56), and investment components are out of scope for the short-coverage book (v1). For the paragraph 100(c) incurred-claims table the LIC lines supply the cash-flow column; the risk-adjustment column is structurally zero (the PAA LIC carries no risk adjustment, paragraph 59(b)).

A pure-LIC-runoff close (opening date at or past the contract boundary, only the claims tail of already-incurred claims left) is supported: coverage has ended so every in-force-scaled line is zero, and the carried state.prior_lic seeds the LIC run-off over an extended horizon (claims incurred in the last coverage months still settle). A final settlement (em_close >= boundary) is allowed with a zero closing count: the LRC releases in full through revenue while the LIC tail stays outstanding, and closing_inputs() carries that tail forward as the next period’s prior_lic.

v1 scope (documented cuts, mirroring the gmm / vfa settles): within-period cash flows are as expected – the observed input is the closing count only (actual premiums received are the paragraph 55(b)(i) input verbatim, so they and actual claims paid are the v1.1 priority); no subsequent paragraph 53 eligibility re-test; no OCI (the paragraph 56 undiscounted LRC has no finance line at all).

매개변수:
반환 형식:

SettlementMovement

fastcashflow.paa.settle_aggregate(model_points, state, basis, *, revenue_basis='time', period_months=None, chunk_size=200000)[소스]#

Portfolio-total paragraph-55(b) PAA settlement in bounded memory.

The PAA counterpart of settle_aggregate(): runs settle() over row blocks of chunk_size model points and accumulates only the scalar line totals (every LRC / loss-component / LIC line is additive across contracts), combined with math.fsum so the total does not depend on the chunking. state joins model_points by mp_id once, before chunking. The aggregate cannot be chained – closing_inputs() raises; chain per-MP movements instead.

매개변수:
반환 형식:

SettlementAggregate

fastcashflow.paa.settle_stream(input_path, output_dir, basis, *, coverages=None, calculation_methods=None, state_path=None, revenue_basis='time', period_months=None, chunk_size=200000, id_column=None, validate_unique_mp_id=True)[소스]#

Stream a paragraph-55(b) PAA period close through a parquet file.

The out-of-core variant of settle(): reads the in-force book in chunk_size blocks, settles each, and writes the per-MP settlement movements as a parquet dataset (one part-NNNNN.parquet per chunk). Same one-combined-file / two-file (state_path) layouts as settle_stream(); the PAA needs only prior_count in the state (the LRC roll reconstructs the rest). Returns the number of model points processed.

매개변수:
  • basis (Basis)

  • revenue_basis (str)

  • period_months (int | None)

  • chunk_size (int)

  • id_column (str | None)

  • validate_unique_mp_id (bool)

반환 형식:

int

class fastcashflow.paa.Measurement(lrc, loss_component, fcf=None, lrc_path=None, revenue=None, service_expense=None, lic_path=None, cashflows=None, model_points=None, group_labels=None, group_sizes=None, measurement_basis='inception')[소스]#

PAA measurement – the Liability for Remaining Coverage and the underwriting result released from it.

lrc is an (n_mp, n_time+1) trajectory; column 0 is the inception LRC. revenue and service_expense are (n_mp, n_time) – the insurance revenue earned and the insurance service expense incurred each month. service_result (a property) is their difference. lic_path is the (n_mp, n_time+1) liability for incurred claims – claims build it up as they are incurred and run it off as they are paid.

매개변수:
  • lrc (FloatArray)

  • loss_component (FloatArray)

  • fcf (FloatArray | None)

  • lrc_path (FloatArray | None)

  • revenue (FloatArray | None)

  • service_expense (FloatArray | None)

  • lic_path (FloatArray | None)

  • cashflows (Cashflows | None)

  • model_points (ModelPoints | None)

  • group_labels (np.ndarray | None)

  • group_sizes (IntArray | None)

  • measurement_basis (str)

property service_result: ndarray[tuple[Any, ...], dtype[float64]]#

Insurance service result – revenue less service expense.

Variable fee approach#

fastcashflow.vfa.measure(model_points, basis, return_scenarios=None, *, full=True, lapse_sensitivity=None, lapse_floor=0.0, lapse_cap=None)[소스]#

Measure a direct-participation portfolio under the Variable Fee Approach.

The account value rolls forward as AV[t+1] = AV[t] * (1 + max(r, g)) * (1 - f) – the credited rate (the underlying-items return r floored at any guaranteed rate g) less the variable fee f – from AV[0] = the model point’s account_value. A surrender pays the account value; a death exit pays max(account value, minimum_death_benefit) (GMDB) and the survivors reaching term pay max(account value, minimum_accumulation_benefit) (GMAB), so the excess over the account value is each guarantee’s intrinsic cost. When return_scenarios is given, each guarantee’s time value (the extra cost from return volatility) is folded into the CSM too – the credit-rate guarantee through the account-value growth, the GMDB and GMAB floors as put options on the account value.

BEL is the present value of benefits and expenses less the premium, all at the underlying-items return; the CSM is max(0, -(BEL + RA)) – the entity’s unearned variable fee – accreted at the same return and released by coverage units. The RA is a confidence-level margin for expense risk.

full=True (default) returns the BEL / RA / CSM / account-value trajectories; full=False fills only the headline bel / ra / csm / variable_fee / time_value / loss_component (the inception CSM is csm0, so the release kernel is skipped) and leaves the trajectory and cash-flow fields None – the building block the portfolio orchestrator chunks to bound memory. basis must resolve to a single Basis; multi-segment routers are not accepted.

BEL, RA and CSM are returned as month-by-month trajectories. The deterministic BEL carries the guarantee’s intrinsic value only; when return_scenarios – an (n_scenarios, n_time) array of monthly underlying-items returns – is supplied, the time value of the guarantee enters the inception fulfilment cash flows too, so the CSM absorbs it, and time_value records that amount per model point.

lapse_sensitivity (default None – a static lapse) turns on a dynamic lapse driven by the account-value moneyness: the lapse decrement is scaled by the per-policy-year moneyness factor (lapse_sensitivity the elasticity, clamped to [lapse_floor, lapse_cap]), which lifts surrenders when the GMAB is out-of-the-money and lowers them when the floor bites. The factor keys on the GMAB; both the closed-form variable-annuity path (the account value is the closed-form growth path) and the account-backed universal-life path (the value is read from the rolled account) are supported.

매개변수:
반환 형식:

Measurement

fastcashflow.vfa.measure_aggregate(model_points, basis, *, chunk_size=200000)[소스]#

Portfolio-aggregate VFA measurement in bounded memory.

The VFA analogue of fastcashflow.gmm.measure_aggregate(): BEL / RA / CSM / variable fee / time value are additive across contracts, so the portfolio’s run-off is the per-model-point trajectories summed over the model-point axis. Runs measure(..., full=True) over row-blocks of chunk_size model points and accumulates only the (n_time+1,) sums, so peak memory is O(chunk_size x n_time) regardless of n_mp.

Returns a Aggregate (scalar totals + aggregate bel_path / ra_path / csm_path / lic_path). account_value does not carry: it is a per-policy level whose closed-form growth never terminates at the boundary, so summing it is horizon-dependent (the group VFA result drops it for the same reason). The deterministic intrinsic value only – the guarantee time value over return scenarios is a per-contract analysis (vfa.tvog()), not aggregated here. basis is a single Basis (mixed / routed portfolios go through fastcashflow.portfolio.measure_aggregate()).

매개변수:
반환 형식:

Aggregate

fastcashflow.vfa.measure_inforce(model_points, state, basis, *, period_months=None)[소스]#

In-force diagnostic / runoff valuation of a VFA book at a single date.

Unlike the PAA (no CSM) and like the GMM carry (fastcashflow.gmm.measure_inforce()), the prior period’s closing CSM is carried forward; unlike the GMM, the VFA CSM accretes at the underlying-items return (not a locked-in rate), so the carry is _csm_kernel(state.prior_csm, coverage_units, r_m) – the GMM carry roll with the return substituted for the lock-in rate. The fulfilment cash flows (BEL / RA / variable fee / guarantee intrinsic value) are re-measured from the observed fund value at the valuation date (state.account_value): the account-value path is re-anchored at that observed value (_project()), the decrements come from the inception projection (they depend on policy duration, not the fund), and the result is sliced at each contract’s elapsed_months and re-based by count / inforce[elapsed] (exact for cash flows linear in the in-force).

state (an InforceState) supplies the period-close elapsed_months / count (reconciled onto model_points by apply_inforce_state()), the carried prior_csm, and the observed account_value (required here – a VFA in-force needs the real fund value, not the modelled one). period_months (default 12) is the length of the period the prior CSM is rolled.

v1 returns the as-of valuation-date headline (bel / ra / csm / variable_fee / time_value / loss_component); the trajectory fields are None. Deferred here: the paragraph-45 remeasurement (the change in the entity’s share of the underlying-items fair value and the guarantee future-service cash flows) and the loss-component movement – both live in settle(), the paragraph-45 settlement of the same state – plus the stochastic time value of the guarantee (time_value is zero – the deterministic intrinsic value only) and the full movement trajectory.

경고

The CSM is a carry-only approximation: it is the prior CSM accreted at the basis underlying-items return and released by the expected (inception-run) coverage units. Unlike the BEL / RA / variable fee, it does not respond to the observed account value – the paragraph-45 adjustment for the change in the entity’s share of the underlying-items fair value is deferred. So the fulfilment-cash-flow figures are observed-AV-consistent but the CSM is not yet a paragraph-45-compliant settlement CSM; for that, settle the same state with settle().

The result is tagged csm_basis = 'carry_only' (see fastcashflow.vfa.CSM_BASES), and the accounting-output entry points – roll_forward(), report(), group(), group_of_contracts() and write_measurement() – reject it, so this figure cannot be silently consumed as a settlement CSM.

매개변수:
반환 형식:

Measurement

fastcashflow.vfa.settle(model_points, state, basis, *, period_months=None, premium_experience_future_fraction=0.0)[소스]#

Paragraph-45 subsequent-measurement settlement of a VFA in-force book (period close).

The real IFRS 17 paragraph-45 opening -> closing movement, replacing the carry-only vfa.measure_inforce headline for settlement / disclosure: the CSM is adjusted for the change in the entity’s share of the fair value of the underlying items (45(b)) and for the changes in fulfilment cash flows relating to future service (45(c)), the loss component is reversed / recognised per paragraphs 48 and 50(b), and one paragraph-B119 coverage-unit release is taken on the post-adjustment balance. Returns a SettlementMovement whose blocks reconcile exactly and whose closing_measurement is a settlement-grade closing balance sheet (csm_basis='paragraph_45_settlement').

state is the closing-dated InforceState extended with the prior reporting date’s figures, all at month elapsed_months - period_months: prior_csm (the opening CSM), prior_count, prior_account_value and (optionally) prior_loss_component; plus the closing snapshot’s count and observed account_value. The same state feeds measure_inforce (the fast carry-only diagnostic) and this settlement.

How it computes: two forward projections of the same book share one decrement run – the EXPECTED leg anchors the account value at the opening observation and advances it under the basis return; the OBSERVED leg anchors at the closing observation – and both are sliced at the closing date. The paragraph-45 future-service change is minus the observed-vs-expected difference of the engine’s own BEL and RA (exact with respect to every engine convention, including the end-of-month fee timing and a binding minimum-crediting-rate floor); the 45(b) / 45(c) split is a disclosure decomposition of that exact total, the 45(b) line being the fund-consistent (end-of-month-weighted) variable-fee PV difference. The CSM accretes by direct compounding at the underlying-items return over the period and is released once at period end over the coverage units provided in the period against those provided plus expected from the opening date. With no experience deviation this reproduces the measure_inforce monthly carry exactly (a telescoping identity of the release weights).

A contract whose paragraph 34 boundary falls inside the period is a final settlement: allowed, provided its closing count and account_value are zero (validated) – its remaining CSM releases in full. The opening date must lie strictly within every contract’s boundary.

An onerous book amortises its loss component through the paragraph-50(a)/51 incurred-service channel (loss_component_finance / loss_component_amortised): the period’s guarantee-excess + expense release (the claims+expenses pool, which for VFA excludes the account-value investment component) is split on the loss-component ratio, running the loss component to zero by the end of coverage (52). The B96(c) investment- component experience (csm_investment_experience – the expected less the actual account value returned on exits) adjusts the CSM when state.actual_investment_component is given.

v1 scope (see SettlementMovement for the full statement): deterministic, single basis rate (the finance-not-adjusting-CSM split is an exact zero residual), intrinsic guarantee only, no assumption change, within-period experience assumed equal to expected (only the closing count and observed account value deviate), per-model-point floors. A basis with a settlement_pattern is supported: the movement carries the liability for incurred claims (lic_opening / claims_incurred / claims_paid / lic_closing, paragraphs 40(b) / 42 / 103(b)) – benefit claims build it up as incurred and run it off over the pattern, undiscounted and at the expected scale, reconstructed from the projection each period (the same v1 cuts as gmm.settle: no 42(b)/(c)).

매개변수:
반환 형식:

SettlementMovement

fastcashflow.vfa.settle_aggregate(model_points, state, basis, *, period_months=None, chunk_size=200000, premium_experience_future_fraction=0.0)[소스]#

Portfolio-total paragraph-45 settlement in bounded memory.

settle() materialises (n_mp, n_time) projection intermediates – two forward projections over the whole book – so a million-policy close would peak far beyond memory. Every line of the settlement movement is additive across contracts, so this runs settle() over row blocks of chunk_size model points and accumulates only the scalar line totals; peak memory is O(chunk_size x n_time) regardless of n_mp.

Returns a SettlementAggregate: the movement’s lines summed, movement-positive (reconcile applies the display negation and reproduces the per-MP movement’s table exactly). The aggregate cannot be chained – closing_inputs() raises; chain per-MP movements instead. state joins model_points by mp_id once, before chunking, so a period-close file in its own row order never pairs one contract’s rows with another’s prior balances.

매개변수:
반환 형식:

SettlementAggregate

fastcashflow.vfa.settle_stream(input_path, output_dir, basis, *, calculation_methods=None, state_path=None, period_months=None, chunk_size=200000, id_column=None, validate_unique_mp_id=True)[소스]#

Stream a paragraph-45 period close through a parquet file, chunk by chunk.

The out-of-core variant of settle() and the VFA counterpart of fastcashflow.gmm.settle_stream(). The VFA base is a single policies frame (account value + guarantee floors, no coverages), so input_path is read in chunk_size blocks, each block’s (ModelPoints, InforceState) pair is settled, and the per-MP settlement movements land as one part-NNNNN.parquet per chunk under output_dir – every movement line plus the measurement_basis marker. Returns the model points processed.

Input layouts, as in the GMM variant: ONE combined file (policies spec plus the closing-state columns, including the observed account_value and the prior_count / prior_account_value / prior_loss_component figures settle() needs) or TWO files (policies parquet + state_path state parquet, semi-joined per chunk on mp_id with the global id sets validated bidirectionally). lock_in_rate must be uniform across the book (v1 scalar; the VFA carries it as a state echo only).

Chaining on disk: each part carries count, lock_in_rate, elapsed_months, account_value_closing and the closing balances, so the next period’s state file is assembled from the parts alone (prior_csm <- csm_closing, prior_loss_component <- loss_component_closing, prior_count <- count, prior_account_value <- account_value_closing, then advance to the next observation) – the disk side of SettlementMovement.closing_inputs().

매개변수:
  • basis (Basis)

  • period_months (int | None)

  • chunk_size (int)

  • id_column (str | None)

  • validate_unique_mp_id (bool)

반환 형식:

int

fastcashflow.vfa.recognition_schedule(model_points, state, basis, *, band_edges_months=(12, 36, 60), period_months=None)[소스]#

Paragraph-109 maturity-band disclosure for a settled VFA book.

The VFA counterpart of recognition_schedule(): allocates the settle() closing CSM to maturity bands by each contract’s forward coverage-unit (in-force) fraction, so the bands sum to the closing CSM – when, in maturity terms, the remaining CSM is expected to be recognised in profit or loss. Onerous contracts carry no CSM and contribute nothing. band_edges_months are the band boundaries in months from the valuation date (default 12 / 36 / 60); period_months is the settlement period (default 12).

매개변수:
class fastcashflow.vfa.Measurement(bel, ra, csm, variable_fee, time_value, loss_component, bel_path=None, ra_path=None, csm_path=None, account_value_path=None, csm_accretion=None, csm_release=None, lic_path=None, guarantee_excess_cf=None, benefit_cf=None, fee_cf=None, discount_factor_bom=None, cashflows=None, model_points=None, group_labels=None, group_sizes=None, csm_basis='projected_runoff')[소스]#

VFA measurement of a direct-participation (account-value) portfolio.

The headline bel, ra, csm, variable_fee, time_value and loss_component are (n_mp,) as-of figures – at inception for measure, at the valuation date for measure_inforce (the RA a confidence-level margin for expense risk; the BEL net of the account value the entity holds; variable_fee the present value of the entity’s fee – its share of the underlying items). The full path adds the (n_mp, n_time+1) trajectories bel_path / ra_path / csm_path / account_value_path (column 0 the as-of figure), None on the headline-only path; a grouped result also leaves account_value_path None (the account value is a per-policy level, not a group quantity). The CSM is accreted at the underlying-items return and released by coverage units:

csm_path[:, t+1] = csm_path[:, t] + csm_accretion[:, t] - csm_release[:, t]

The guarantee time value drives the CSM but is reported separately in time_value, not folded into bel.

매개변수:
  • bel (FloatArray)

  • ra (FloatArray)

  • csm (FloatArray)

  • variable_fee (FloatArray)

  • time_value (FloatArray)

  • loss_component (FloatArray)

  • bel_path (FloatArray | None)

  • ra_path (FloatArray | None)

  • csm_path (FloatArray | None)

  • account_value_path (FloatArray | None)

  • csm_accretion (FloatArray | None)

  • csm_release (FloatArray | None)

  • lic_path (FloatArray | None)

  • guarantee_excess_cf (FloatArray | None)

  • benefit_cf (FloatArray | None)

  • fee_cf (FloatArray | None)

  • discount_factor_bom (FloatArray | None)

  • cashflows (Cashflows | None)

  • model_points (ModelPoints | None)

  • group_labels (np.ndarray | None)

  • group_sizes (IntArray | None)

  • csm_basis (str)

property measurement_basis: str#

Cross-model time-basis discriminator, derived from csm_basis (the VFA’s stored field stays the single source of truth).

fastcashflow.vfa.CSM_BASES = ('initial_measurement', 'projected_runoff', 'carry_only', 'paragraph_45_settlement')#

Built-in immutable sequence.

If no argument is given, the constructor returns an empty tuple. If iterable is specified the tuple is initialized from iterable’s items.

If the argument is a tuple, the return value is the same object.

fastcashflow.vfa.tvog(model_points, basis, return_scenarios)#

Measure the time value of a VFA contract’s minimum-crediting-rate guarantee.

Values the crediting-rate floor only – the guarantee that the account is credited max(return, minimum_crediting_rate) each month. The GMDB / GMAB account-value floors are NOT included here; their time value is folded into vfa.measure(..., return_scenarios).time_value instead. This function is the standalone crediting-rate analysis.

return_scenarios is an (n_scenarios, n_time) array of monthly underlying-items returns – one path per scenario, n_time being the projection horizon. The model points must carry a crediting guarantee – a minimum_crediting_rate of 0.0 (a real 0% floor, max(return, 0)) or a positive rate. A contract with no crediting guarantee (NO_GUARANTEE_RATE) is rejected: it has no crediting-rate time value to measure (use vfa.measure(..., return_scenarios).time_value for the GMDB / GMAB floor time value instead). In v1 the rate is taken as a portfolio-wide scalar (per-MP varying rates with stochastic returns are a future extension), so the column is required to be uniform across rows.

The guarantee cost is the present value of account-value benefits in excess of the no-guarantee benefits. Its mean over the scenarios is the total value; the cost in the central scenario (investment_return) is the intrinsic value; the difference is the time value (TVOG).

Maturity survivors are weighted at the matured term (time = term, one month past their term - 1 exit column), the same re-seat the folded credit-rate TVOG in vfa.measure() uses, so the two agree on a mixed-term book.

매개변수:
반환 형식:

TVOGResult

class fastcashflow.TVOGResult(guarantee_cost, intrinsic_value, time_value)[소스]#

The cost of a minimum guarantee, split into intrinsic and time value.

guarantee_cost is the (n_scenarios,) present value of the guarantee under each scenario – its distribution. intrinsic_value is the cost in the central (deterministic) scenario; time_value is the TVOG, the mean cost in excess of the intrinsic value; and total_value is their sum, the guarantee’s full economic cost.

매개변수:
property total_value: float#

Intrinsic value plus time value – the guarantee’s full cost.

Portfolio (mixed-model orchestration)#

One heterogeneous portfolio – GMM, PAA and VFA contracts in a single routed file – measured in one call. Each contract is routed to its segment’s measurement model and each model’s native result is kept separate (a BEL and an LRC are never summed into one array). measure returns the per-model-point PortfolioMeasurement; measure_aggregate returns the chunked, bounded-memory PortfolioAggregate (a scalable sum of the measured model-point results – not an IFRS group remeasurement and not a group of contracts re-floor). loss_component is the lone quantity summed across models.

fastcashflow.portfolio.measure(model_points, basis, *, full=True, backend='cpu', chunk_size=200000, return_scenarios=None)[소스]#

Measure a mixed-model portfolio in one call.

basis must be a BasisRouter whose segments carry their IFRS 17 measurement model (read_basis reads it from the measurement_model column of the segments sheet). Each row is routed to its segment’s model; the result keeps each model’s native measurement separate. full matches fcf.gmm.measure() (the full trajectory vs the fused headline).

With full=False the PAA / VFA partitions are measured headline-only in chunk_size row-blocks, so peak memory stays O(chunk_size x n_time) instead of materialising every contract’s trajectory at once (GMM’s full=False is already a fused, no-trajectory kernel; chunk_size does not affect it). The headline-only result still serves summary() / loss_component_total(); group / roll_forward / report need full=True. full=True keeps every trajectory (and is memory-bound).

Each model’s rows are routed to its own kernel and kept in a separate slot of the result. A non-GMM row is never silently measured as GMM.

return_scenarios – an (n_scenarios, horizon) array of monthly underlying-items returns – prices the guarantee time value (TVOG) of the VFA partition, exactly as fcf.vfa.measure(..., return_scenarios=...) does for a single book; it is forwarded only to the VFA slot (GMM / PAA carry no return guarantee). It is an error to pass it for a portfolio with no VFA rows. When supplied, the VFA partition is measured without chunking and the scenario is sliced to each segment’s own horizon, so horizon need only cover the longest VFA contract (the per-model-point time value is additive), and chunk_size does not bound the VFA scenario pass. Omitted (the default), the mixed path measures the VFA intrinsic value (deterministic) only.

When the router declares a single measurement model the model partition is a no-op, so the whole book routes straight to that model – no per-row partition factorise, no subset copy. (A router that declares PAA / VFA segments but whose rows happen to be all GMM is not this case: it keeps the row partition, which is what makes the unused declared segment harmless.)

매개변수:
반환 형식:

PortfolioMeasurement

fastcashflow.portfolio.measure_aggregate(model_points, basis, *, chunk_size=200000)[소스]#

Chunked aggregate measurement of a mixed-model portfolio.

A scalable sum of measured model-point results: each model’s inception totals and run-off trajectories summed over the model-point axis, computed in chunk_size row-blocks so peak memory is O(chunk_size x n_time) – it works where the per-model-point measure() full=True would OOM. Returns a PortfolioAggregate keeping each model’s native figures separate (a BEL and an LRC are never pooled).

Not an IFRS group remeasurement and not a group re-floor engine: every figure is the sum of the per-model-point results (CSM is the sum of each contract’s floored CSM – the measure() headline aggregated, not group()’s CSM(sum FCF)). A per-group re-floor is a separate concern.

매개변수:
반환 형식:

PortfolioAggregate

class fastcashflow.portfolio.PortfolioMeasurement(model_points, gmm=None, paa=None, vfa=None)[소스]#

Result of measure(): one ModelMeasurement per model present (None when absent), keyed by model so a BEL and an LRC are never conflated. model_points is the full portfolio – the grouping axes for downstream per-segment / group of contracts analysis (e.g. group(pm.gmm.measurement, by=["product"])). The per-model indices must partition 0..n_mp-1 exactly; this is checked at construction.

매개변수:
loss_component_total()[소스]#

The portfolio’s total onerous-contract loss at inception.

This is the only quantity summed across measurement models: the loss component is max(0, fulfilment cash flows) at inception, defined and signed identically under GMM, PAA and VFA, so adding it across models is meaningful. A BEL, an LRC and a VFA BEL are not added – they measure different things; reach those through the per-model blocks of summary() (e.g. pm.gmm.measurement.bel).

반환 형식:

float

summary()[소스]#

Per-model headline totals, each model in its own block.

Returns {"loss_component_total": float, <model>: {...}} with a block only for the models the portfolio carries. Each block sums that model’s own headline figures over its rows – gmm / vfa give bel / ra / csm / loss_component, paa gives lrc / loss_component. Figures of different meaning are never pooled into one number; loss_component_total is the single cross-model sum (see loss_component_total()).

반환 형식:

dict

class fastcashflow.portfolio.ModelMeasurement(index, measurement)[소스]#

One measurement model’s slice of a portfolio: the original row positions (index, sorted and unique) and the native result over exactly those rows.

매개변수:
class fastcashflow.portfolio.PortfolioAggregate(gmm=None, paa=None, vfa=None)[소스]#

Result of measure_aggregate(): one aggregate per model present (None when absent), each holding that model’s inception totals and run-off trajectories summed over the model-point axis. A scalable sum of measured model-point results – no per-model-point row, so it works at a scale where the per-row PortfolioMeasurement would not fit in memory. Not an IFRS group remeasurement and not a group re-floor engine: every figure is the sum of the per-model-point results (CSM is the sum of each contract’s floored CSM, the headline aggregated – not group()’s CSM(sum FCF)). A BEL and an LRC are never pooled; loss_component_total is the one cross-model sum.

매개변수:
  • gmm (Aggregate | None)

  • paa (Aggregate | None)

  • vfa (Aggregate | None)

loss_component_total()[소스]#

Portfolio total onerous-contract loss – the only cross-model sum (max(0, FCF) at inception, sign-identical under GMM / PAA / VFA).

반환 형식:

float

summary()[소스]#

Per-model headline totals, each model in its own block (a BEL and an LRC are never pooled); loss_component_total is the lone cross-model sum. A block appears only for a model the portfolio carries.

반환 형식:

dict

Per-group aggregate (scalable group of contracts)#

measure_group_of_contracts is the chunked, bounded-memory form of fastcashflow.group_of_contracts(): the IFRS 17 unit of account (portfolio x annual cohort x profitability) computed where holding the per-model-point measure(full=True) would not fit in memory. measure_group is the same machinery on any axis (the scalable fastcashflow.group()). Both return a PortfolioGroups holding each model’s native grouped measurement – its rows the groups – so the group rows flow on into fastcashflow.roll_forward(), fastcashflow.reconcile() and fastcashflow.report().

The floor unit is what distinguishes this from measure_aggregate:

  • measure_aggregate floors per model point, then sums – sum max(0, -FCF_i). It is a scalable sum of the already-floored per-contract results, never re-grouping them.

  • measure_group_of_contracts / measure_group re-floor per group, on the summed fulfilment cash flows – max(0, -sum FCF_in_group) per group, applied once on the fully-accumulated group (a group spans chunks, so it is never floored per chunk).

At initial recognition these agree: under any paragraph-16-compliant grouping a group never mixes inception-FCF signs, so CSM(sum FCF) == sum CSM(FCF) and measure_group_of_contracts and measure_aggregate report the same totals. The re-floor changes the number only for a deliberately coarser, sign-mixing grouping (e.g. measure_group(by="product") with no profitability axis – within-group mutualisation) or in subsequent measurement (out of scope here). So at inception measure_group_of_contracts’s value over measure_aggregate is the per-group rows (disclosure, roll-forward, the paragraph-44 foundation), not a different number.

fastcashflow.portfolio.measure_group_of_contracts(model_points, basis, *, portfolio='product', cohort='issue_year', profitability=None, chunk_size=200000)[소스]#

Scalable group-of-contracts aggregation – the IFRS 17 unit of account.

The chunked, memory-bounded form of fcf.group_of_contracts(): the portfolio (paragraph 14) x annual cohort (22) x profitability (16) grouping, re-flooring the CSM / loss component on each group’s fulfilment cash flows (CSM(sum FCF), unlike measure_aggregate() which sums each contract’s already-floored CSM). Computed in chunk_size row-blocks so it works where holding the per-model-point measure(full=True) would OOM.

At initial recognition, under any paragraph-16-compliant grouping a group never mixes inception-FCF signs, so the re-floor equals the per-model-point floor sum – measure_group_of_contracts and measure_aggregate() report the same totals; measure_group_of_contracts’s value is the per-group rows (disclosure / roll-forward / the paragraph-44 foundation).

Arguments mirror fcf.group_of_contracts(): portfolio / cohort name the axis columns; profitability is None (derive the onerous / remaining split per model point from the inception loss component, one rule across GMM / PAA / VFA), a column name (a locked classification, paragraph 24), or a precomputed (n_mp,) array (e.g. the three-way split).

Unlike fcf.group_of_contracts(), a missing issue_date with the default cohort is rejected, not silently collapsed to one cohort: a silent collapse would mutualise across annual cohorts (paragraph 22) invisibly at settlement scale. issue_age / term_months are never a cohort substitute.

매개변수:
반환 형식:

PortfolioGroups

fastcashflow.portfolio.settle_group_of_contracts(model_points, inforce_state, basis, period_months=None, *, portfolio='product', cohort='issue_year', coverage_units=None, profitability=None, premium_experience_future_fraction=0.0, chunk_size=200000)[소스]#

Group-of-contracts settlement for a routed CSM portfolio.

A pure GMM book returns a GoCSettlement (paragraph 44); a pure VFA book returns a _vfa.GoCSettlement (paragraph 45). coverage_units and profitability are required and explicit. PAA is rejected (no CSM / floor, so a per-GoC algebra is meaningless – use paa.settle and sum by your own groupby), and a book mixing GMM and VFA is rejected whole: a group of contracts sits in one product, hence one measurement model. The premium_experience_future_fraction argument applies to both the GMM and VFA paths (paragraph B96(a); each routes the future leg into its own paragraph-45 algebra).

매개변수:
반환 형식:

GoCSettlement | GoCSettlement

fastcashflow.portfolio.measure_group(model_points, basis, by, *, chunk_size=200000)[소스]#

Scalable group aggregation of a mixed-model portfolio on any axis.

The chunked, memory-bounded form of fcf.group(): each model’s rows are aggregated to the by axis (re-flooring the CSM / loss component on each group’s fulfilment cash flows, CSM(sum FCF)), computed in chunk_size row-blocks so peak memory is O(chunk_size x n_time) plus the O(n_groups x n_time) accumulator – it works where holding the per-model-point measure(full=True) would OOM. by is one of a single axis name, a list of axis names and/or precomputed (n_mp,) label arrays, or a single (n_mp,) label array (as fcf.group()). For the IFRS 17 unit of account use measure_group_of_contracts(), the preset on portfolio x annual cohort x profitability.

매개변수:
반환 형식:

PortfolioGroups

class fastcashflow.portfolio.PortfolioGroups(gmm=None, paa=None, vfa=None)[소스]#

Result of measure_group() / measure_group_of_contracts(): one native grouped measurement per model present (None when absent), its rows the groups (an IFRS 17 group of contracts for measure_group_of_contracts()). The scalable form of fcf.group_of_contracts() – it re-floors on each group’s fulfilment cash flows (CSM(sum FCF)), computed in bounded memory so it works where holding the per-model-point measure(full=True) would OOM. A BEL and an LRC are never pooled; loss_component_total is the one cross-model sum.

Each slot is the same native type fcf.group() returns (_gmm.Measurement / _paa.Measurement / _vfa.Measurement with group_labels / group_sizes set), so the group rows flow straight into fcf.roll_forward() / fcf.reconcile() / fcf.report(). There is no cross-model group of contracts: a portfolio (product) carries one measurement model, so a group always sits inside one model’s slot.

매개변수:
loss_component_total()[소스]#

The portfolio’s total onerous-group loss – the only quantity summed across measurement models (max(0, group FCF), defined and signed identically under GMM / PAA / VFA). A BEL, an LRC and a VFA BEL are not added; reach those through summary().

반환 형식:

float

summary()[소스]#

Per-model headline totals, each model in its own block (a BEL and an LRC are never pooled); loss_component_total is the lone cross-model sum. Each block sums that model’s figures over its group rows – gmm / vfa give bel / ra / csm / loss_component, paa gives lrc / loss_component. A block appears only for a model the portfolio carries.

반환 형식:

dict

class fastcashflow.portfolio.GoCSettlement(group_labels, group_sizes, period_months, bel_opening, bel_interest, bel_release, bel_experience, bel_closing, ra_opening, ra_interest, ra_release, ra_experience, ra_closing, finance_wedge, premium_experience_revenue, csm_opening, csm_accretion, csm_experience_unlocking, csm_premium_experience, csm_investment_experience, claims_experience, expense_experience, loss_component_opening, loss_component_finance, loss_component_amortised, lic_opening, claims_incurred, lic_finance, claims_paid, lic_closing, coverage_units_provided, coverage_units_future, csm_release, csm_closing, loss_component_reversed, loss_component_recognised, loss_component_closing, lock_in_rate, model_points=None, group_inverse=None, lock_in_rate_by_mp=0.0, profitability_by_mp=None, measurement_basis='settlement')[소스]#

Group-of-contracts paragraph-44 settlement movement.

Rows are IFRS 17 groups. Linear GMM settlement lines are group-summed; the paragraph-48/50(b) CSM/loss-component algebra and the B119 release are applied once at group grain. closing_inputs() allocates group closing balances back to model points by closing-count pro-rata, or by an explicit per-row allocation weight.

매개변수:
class fastcashflow.vfa.GoCSettlement(group_labels, group_sizes, period_months, bel_opening, bel_interest, bel_release, bel_experience, bel_closing, ra_opening, ra_interest, ra_release, ra_experience, ra_closing, csm_fv_share, csm_future_service, csm_premium_experience, premium_experience_revenue, csm_investment_experience, claims_experience, expense_experience, csm_opening, csm_accretion, variable_fee_closing, account_value_closing, loss_component_opening, loss_component_finance, loss_component_amortised, lic_opening, claims_incurred, lic_finance, claims_paid, lic_closing, coverage_units_provided, coverage_units_future, csm_release, csm_closing, loss_component_reversed, loss_component_recognised, loss_component_closing, lock_in_rate, model_points=None, group_inverse=None, lock_in_rate_by_mp=0.0, profitability_by_mp=None, account_value_by_mp=None, measurement_basis='settlement')[소스]#

Group-of-contracts paragraph-45 settlement movement (VFA).

The VFA mirror of GoCSettlement. Rows are IFRS 17 groups. The LINEAR VFA settlement lines are group-summed – including csm_fv_share (45(b)) and csm_future_service (45(c)), each carrying its own v_half / k_obs, so the group fv_share is the SUM of the per-MP fv_shares (not a re-derivation from a re-summed group account value). The paragraph-48/50(b) algebra and the single B119 release are applied once at group grain on the group-summed inputs (the future-service change is sum(csm_fv_share + csm_future_service)). closing_inputs() allocates the group closing CSM / loss component back to model points by closing- count pro-rata (or an explicit weight) and carries each contract’s observed account value forward.

매개변수:
  • group_labels (np.ndarray)

  • group_sizes (IntArray)

  • period_months (int)

  • bel_opening (FloatArray)

  • bel_interest (FloatArray)

  • bel_release (FloatArray)

  • bel_experience (FloatArray)

  • bel_closing (FloatArray)

  • ra_opening (FloatArray)

  • ra_interest (FloatArray)

  • ra_release (FloatArray)

  • ra_experience (FloatArray)

  • ra_closing (FloatArray)

  • csm_fv_share (FloatArray)

  • csm_future_service (FloatArray)

  • csm_premium_experience (FloatArray)

  • premium_experience_revenue (FloatArray)

  • csm_investment_experience (FloatArray)

  • claims_experience (FloatArray)

  • expense_experience (FloatArray)

  • csm_opening (FloatArray)

  • csm_accretion (FloatArray)

  • variable_fee_closing (FloatArray)

  • account_value_closing (FloatArray)

  • loss_component_opening (FloatArray)

  • loss_component_finance (FloatArray)

  • loss_component_amortised (FloatArray)

  • lic_opening (FloatArray)

  • claims_incurred (FloatArray)

  • lic_finance (FloatArray)

  • claims_paid (FloatArray)

  • lic_closing (FloatArray)

  • coverage_units_provided (FloatArray)

  • coverage_units_future (FloatArray)

  • csm_release (FloatArray)

  • csm_closing (FloatArray)

  • loss_component_reversed (FloatArray)

  • loss_component_recognised (FloatArray)

  • loss_component_closing (FloatArray)

  • lock_in_rate (FloatArray)

  • model_points (ModelPoints | None)

  • group_inverse (IntArray | None)

  • lock_in_rate_by_mp (FloatArray | float)

  • profitability_by_mp (np.ndarray | None)

  • account_value_by_mp (FloatArray | None)

  • measurement_basis (str)

Tracing and validation#

Per-contract tracers that unfold a single model point’s measurement as an ASCII tree – which segment, table and rate feed each step, the year-by-year rates and cash flows, and the anchor-month discount / BEL / CSM. Used for hand-calculation validation, learning and debugging. Each measurement approach has its own tracer.

fastcashflow.gmm.trace(mp_index, model_points, basis, *, file=None)[소스]#

Print a tree of how one model point’s BEL / RA / CSM is computed.

매개변수:
  • mp_index (int) – 0-based row index in model_points.

  • model_points (ModelPoints) – Portfolio ModelPoints. The function slices to a single row before running measure(), so a 1M-row portfolio does not pay for the trace of one contract.

  • basis (Basis | dict) – A single Basis, or the BasisRouter returned by fastcashflow.io.read_basis() / fastcashflow.io.load_sample_basis(). With the router form the function looks up the segment via the model point’s (product, channel).

  • file (IO | None) – Where to write. None writes to sys.stdout.

  • calculation (Use it when an engine result disagrees with a hand)

  • segment (tree shows the)

  • tables

  • values (rate)

  • roll- (cash flows and)

  • step (forward step by)

  • glance. (so the diverging step is visible at a)

반환 형식:

None

fastcashflow.gmm.trace_diff(mp_index, model_points, basis_a, basis_b, *, label_a='before', label_b='after', file=None)[소스]#

Print a tree of how the BEL / RA / CSM of one model point moves when basis change.

매개변수:
  • mp_index (int) – 0-based row index in model_points.

  • model_points (ModelPoints) – Portfolio ModelPoints. Subset to the single row before each measure() so the diff cost stays proportional to one MP.

  • basis_a (Basis | dict) – Two basis to compare. Either a Basis or the BasisRouter from fastcashflow.io.read_basis(). With a router, each is routed independently by the model point’s (product, channel) – comparing two segments is also fine.

  • basis_b (Basis | dict) – Two basis to compare. Either a Basis or the BasisRouter from fastcashflow.io.read_basis(). With a router, each is routed independently by the model point’s (product, channel) – comparing two segments is also fine.

  • label_a (str) – Short labels for the two columns in the printed diff (e.g. "baseline" vs "mortality+10%"). Default "before" / "after".

  • label_b (str) – Short labels for the two columns in the printed diff (e.g. "baseline" vs "mortality+10%"). Default "before" / "after".

  • file (IO | None) – Where to write. None writes to sys.stdout.

  • Sections

  • --------

  • identity) (1. Header (the model-point)

  • names) (2. Labels (the two basis)

  • differ (3. Assumption changes -- only the fields that)

  • side (4. Rate deltas -- year-by-year annual rates side by)

  • component (5. Cash flow deltas -- annual sum of each cash-flow)

  • months (7. BEL / CSM deltas at anchor)

  • months

  • loss_component (8. Final -- BEL / RA / FCF / CSM /) – percentage change

  • and (with absolute) – percentage change

  • so (Equal values are suppressed from the assumption-change section)

  • moved. (the eye lands on what actually)

반환 형식:

None

fastcashflow.gmm.trace_bel_step(mp_index, model_points, basis, *, months=None, file=None)[소스]#

Print, term by term, how one model point’s BEL[t] is built.

The kernel runs the IFRS 17 backward recursion:

BEL[t] = annuity[t] - premium[t]
       + (mortality + morbidity + disability + expense + surrender)[t]
         * (1 + i[t])^(-1/2)
       + BEL[t+1] * (1 + i[t])^(-1)

seeded by BEL[term] = maturity_benefit. This function unrolls the equation at chosen months: prints each cash-flow component at t, the half-month and full-month discount factors, the mid-term piece (cash flows at mid-month) and the tail piece (carry from the next month), then the resulting BEL[t] against the engine’s value. When the printed identity holds the engine and a hand calculation are in agreement; when it does not, the offending term is right there in the row.

매개변수:
  • mp_index (int) – 0-based row index in model_points.

  • model_points (ModelPoints) – Portfolio ModelPoints. Subset to the single row before running measure().

  • basis (Basis | dict) – A Basis or the BasisRouter from fastcashflow.io.read_basis() (routed by the row’s (product, channel) like trace()).

  • months (list[int] | None) – Anchor months at which to unroll the recursion. None uses {0, 12, term//2, term-1, term} – inception, end of year 1, the half-way point, the last recursion step, and the seed. Out-of-range entries are ignored.

  • file (IO | None) – Where to write. None -> sys.stdout.

반환 형식:

None

fastcashflow.gmm.trace_csm_step(mp_index, model_points, basis, *, months=None, file=None)[소스]#

Print, term by term, how one model point’s CSM[t] is built.

The kernel runs the forward recursion:

csm[0]   = max(0, -(BEL[0] + RA[0]))
csm[t]   = csm[t-1] + accretion[t-1] - release[t-1]
accretion[t-1] = csm[t-1] * i[t-1]
release[t-1]   = (csm[t-1] + accretion[t-1])
                 * coverage_units[t-1] / sum(coverage_units[t-1:])

coverage_units is the in-force survival series. This function unrolls the step at chosen months: prints the prior CSM, the monthly rate, the accretion, the coverage-unit share consumed in that month, the release amount, and the resulting CSM[t] against the engine’s value.

For an onerous contract (csm[0] == 0), every subsequent step is zero too – the printed trace then visibly says so, which is itself useful when checking that the engine is honouring the floor.

매개변수:
  • mp_index (int) – 0-based row index in model_points.

  • model_points (ModelPoints) – Same shape as trace_bel_step().

  • basis (Basis | dict) – Same shape as trace_bel_step().

  • file (IO | None) – Same shape as trace_bel_step().

  • months (list[int] | None) – Months at which to unroll the step (each row shows the computation that produced csm[t] from csm[t-1]). None uses {1, 12, term//2, term}. Out-of-range entries are ignored. t = 0 is the seed and is always printed regardless of months.

반환 형식:

None

fastcashflow.vfa.trace(mp_index, model_points, basis, *, return_scenarios=None, file=None)[소스]#

Print a tree of how one VFA model point’s BEL / RA / CSM is computed.

The VFA (variable-fee, account-value) counterpart of trace(). It slices to a single row, runs _vfa.measure(), and shows the account-value trajectory, the GMDB / GMAB floors (where the guarantee bites), the variable fee and the BEL / RA / CSM – plus the guarantee time value (TVOG) when return_scenarios is supplied. Use it on direct-participation contracts; trace() traces the GMM measure and does not cover the account-value mechanic.

매개변수:
반환 형식:

None

fastcashflow.paa.trace(mp_index, model_points, basis, *, revenue_basis='time', file=None)[소스]#

Print a tree of how one PAA model point’s LRC / revenue / LIC is built.

The PAA (Premium Allocation Approach, the short-duration simplification) counterpart of trace(). PAA has no CSM – the liability for remaining coverage (LRC) is an unearned-premium-style balance – so the tree shows the LRC roll-forward (premium in, revenue released), the insurance service result (revenue less service expense) and the liability for incurred claims (LIC). Use it on PAA contracts; trace() traces the GMM measure (BEL / RA / CSM).

매개변수:
반환 형식:

None

Reinsurance#

fastcashflow.reinsurance.measure(model_points, basis, *, treaty, underlying_loss_component=None, recovery_percentage=None, full=True)[소스]#

Measure a reinsurance contract held over a direct portfolio.

treaty describes how the cover cedes the direct cash flows – e.g. QuotaShare(cession=0.5). The BEL is the present value of reinsurance premiums less recoveries; the RA is the margin on the ceded claims (the risk transferred). The CSM is -(BEL - RA) – the net cost or gain of the cover – and may be negative; it is accreted and released by coverage units like a direct contract’s CSM, but with no loss component (paragraph 65). basis must resolve to a single Basis; multi-segment routers are not accepted. full=False returns only the headline BEL / RA / CSM and leaves all trajectory and cash-flow fields None.

매개변수:
반환 형식:

Measurement

fastcashflow.reinsurance.settle(model_points, state, basis, *, treaty, period_months=None, underlying_loss_opening=None, underlying_loss_closing=None, recovery_percentage=None)[소스]#

Paragraph-66 subsequent-measurement settlement of a reinsurance contract held (the reinsurance counterpart of settle()).

The opening -> closing movement over one reporting period: BEL / RA re-measured at current rates, the CSM accreted at the locked-in rate (66(b)/B72(b)), adjusted for the future-service change measured at the locked-in rate (66(c)/B72(c) – the current-rate gap is the finance_wedge), and released once at the period end over coverage units (66(e)/B119). The ONE difference from gmm.settle: a reinsurance contract held cannot be onerous (paragraph 65), so the CSM is NOT floored and there is no loss component – the closing CSM may be negative (a net cost of cover). The BEL is PV(reinsurance premium) - PV(recovery), so the locked-in second leg re-prices the ceded cash flows at the locked-in rate.

On-track experience makes every experience line zero and telescopes the closing CSM to the carry bridge (measure_inforce()) exactly. A row whose closing date reaches the contract boundary with count = 0 is a final settlement (full B119 derecognition, paragraph 76).

The loss-recovery component (paragraphs 66A-66B / B119F) is tracked when the cover is held over an onerous underlying group: pass underlying_loss_opening / underlying_loss_closing (the underlying group’s loss component at the two dates, from the direct gmm.settle) and, for a non-proportional treaty, recovery_percentage. The four loss_recovery_* movement lines re-derive the component as the underlying loss x the claim recovery % and amortise it in lock-step with the underlying loss (the recovery reverses in P&L as the underlying runs off). The CSM is NOT re-adjusted here – the 66A CSM effect (csm_after = csm0 - loss_recovery) is a one-time inception event in reinsurance.measure. Absent the inputs => zero (byte-identical). B119C timing (the cover entered before/at the onerous underlying) is the caller’s responsibility.

매개변수:
반환 형식:

SettlementMovement

fastcashflow.reinsurance.settle_aggregate(model_points, state, basis, *, treaty, period_months=None, chunk_size=200000, underlying_loss_opening=None, underlying_loss_closing=None, recovery_percentage=None)[소스]#

Portfolio-total paragraph-66 reinsurance settlement in bounded memory.

Runs settle() over row blocks of chunk_size model points and accumulates only the scalar line totals (every settlement line is additive across contracts), combined with math.fsum so the total does not depend on the chunking. Replaces the carry-bridge aggregate measure_inforce_aggregate() with a true settlement.

매개변수:
반환 형식:

SettlementAggregate

fastcashflow.reinsurance.settle_stream(input_path, output_dir, basis, *, treaty, coverages=None, calculation_methods=None, state_path=None, period_months=None, chunk_size=200000, id_column=None, validate_unique_mp_id=True)[소스]#

Stream a paragraph-66 reinsurance period close through a parquet file.

The out-of-core variant of settle(): reads the direct policies + coverages parquet in chunk_size blocks, cedes with treaty, settles each block, and writes the per-MP settlement movements (one part-NNNNN.parquet per chunk). Same one-combined-file / two-file (state_path) layouts as settle_stream(); the reinsurance state carries prior_csm (which may be negative – a net cost) and prior_count. Returns the number of model points processed. A ceded book is usually small enough for settle() / settle_aggregate(); this exists for API symmetry with the other models.

매개변수:
  • basis (Basis)

  • treaty (Treaty)

  • period_months (int | None)

  • chunk_size (int)

  • id_column (str | None)

  • validate_unique_mp_id (bool)

반환 형식:

int

class fastcashflow.reinsurance.QuotaShare(cession)[소스]#

Proportional reinsurance – cede a fixed fraction of claims and premiums.

cession (in [0, 1]) is the ceded fraction: the cedant recovers that fraction of its claims and pays the same fraction of its premiums as reinsurance premium.

매개변수:

cession (float)

class fastcashflow.reinsurance.Measurement(bel, ra, csm, loss_recovery_component=None, bel_path=None, ra_path=None, csm_path=None, csm_accretion=None, csm_release=None, recovery=None, reinsurance_premium=None, cashflows=None, discount_factor_bom=None, model_points=None, group_labels=None, group_sizes=None, measurement_basis='inception')[소스]#

Measurement of a reinsurance contract held.

Headline bel, ra and csm are (n_mp,) inception figures – bel is the present value of reinsurance premiums less recoveries (a net cost when positive), ra is the risk transferred, csm is the inception net cost or gain (may be negative). The bel symbol is shared with the GMM result for a uniform surface, but for reinsurance held it is the present value of fulfilment cash flows of a reinsurance ASSET (IFRS 17 paragraph 63), not a liability – a negative bel is a net reinsurance asset. The trajectory fields are populated only on the full path; csm_path reconciles as csm_path[:, t+1] = csm_path[:, t] + csm_accretion[:, t] - csm_release[:, t].

매개변수:
  • bel (FloatArray)

  • ra (FloatArray)

  • csm (FloatArray)

  • loss_recovery_component (FloatArray | None)

  • bel_path (FloatArray | None)

  • ra_path (FloatArray | None)

  • csm_path (FloatArray | None)

  • csm_accretion (FloatArray | None)

  • csm_release (FloatArray | None)

  • recovery (FloatArray | None)

  • reinsurance_premium (FloatArray | None)

  • cashflows (Cashflows | None)

  • discount_factor_bom (FloatArray | None)

  • model_points (ModelPoints | None)

  • group_labels (np.ndarray | None)

  • group_sizes (IntArray | None)

  • measurement_basis (str)

Pricing#

fastcashflow.solve_premium(model_points, basis, *, break_even=False, margin=None, csm=None)[소스]#

Solve the level premium that meets a profitability target.

Exactly one target must be given:

  • break_even – the lowest non-onerous premium (FCF = 0, zero CSM).

  • margin – a profit margin, CSM / PV(premiums) = margin (e.g. 0.10 for 10%); must satisfy 0 <= margin < 1.

  • csm – an absolute target CSM (profit) per model point.

Every product field of model_points is used as given – only premium is ignored, since it is the unknown being solved for. Returns the solved premium per model point, shape (n_mp,).

매개변수:
반환 형식:

ndarray[tuple[Any, …], dtype[float64]]

Profit testing#

Inception value (CSM + RA), profit margin and profit emergence over a GMM measurement, plus the traditional net-level-premium reserve and statutory profit test.

fastcashflow.pricing.csm_plus_ra(measurement)[소스]#

CSM + RA per model point – the present value at issue of the profit the contract is expected to release: CSM + RA - loss component (equivalently -BEL). Pre-tax and pre-required-capital. NOT the value of new business (vnb()), which nets the cost of capital.

반환 형식:

ndarray[tuple[Any, …], dtype[float64]]

fastcashflow.pricing.profit_margin(measurement)[소스]#

Profit margin per model point – csm_plus_ra over the present value of premiums (the PVNBP margin). Zero-premium contracts return 0.

반환 형식:

ndarray[tuple[Any, …], dtype[float64]]

fastcashflow.pricing.signature(measurement, period_months=12)[소스]#

The IFRS 17 profit signature – the per-period insurance service result (CSM release + RA release on a best-estimate run), summed over the book.

Built from report(); the present value of the signature at the locked-in rate approximately reconciles to the portfolio csm_plus_ra() total (the exact figure is csm_plus_ra; the annual signature is an aggregated presentation that re-discounts a year’s profit from its mid-point).

매개변수:

period_months (int)

반환 형식:

ProfitSignature

class fastcashflow.ProfitSignature(period_months, month_end, profit)[소스]#

Period-by-period shareholder profit emergence of a book.

profit is (n_periods,) – the profit recognised in each reporting period of period_months months (the portfolio total). month_end is the elapsed month at the end of each period. present_value discounts the stream; total is its undiscounted sum.

매개변수:
property total: float#

Undiscounted lifetime profit.

present_value(annual_rate)[소스]#

Present value of the profit stream at a flat annual annual_rate, each period discounted to issue from its mid-point (the standard mid-year convention – the period’s profit emerges over the period). This approximately reconciles to csm_plus_ra(); the exact figure is csm_plus_ra (CSM + RA), not this aggregated-and-re-discounted stream.

매개변수:

annual_rate (float)

반환 형식:

float

fastcashflow.pricing.irr(cashflows, *, period_months=12, low=-0.99, high=10.0)[소스]#

Internal rate of return of a shareholder cash-flow stream (one entry per period of period_months months, period 0 first).

The rate r (annual) at which the net present value is zero, found by bisection. The stream must change sign (a day-0 outgo / strain followed by profit), else there is no internal rate and a ValueError is raised – an all-positive IFRS 17 signature has none; pair it with the new-business strain (the statutory profit test) for a meaningful IRR.

매개변수:
반환 형식:

float

fastcashflow.pricing.break_even_year(cashflows, *, period_months=12)[소스]#

The first period (1-based, in period_months units) at which the cumulative shareholder cash flow turns non-negative – the payback point. Returns -1 if it never recovers.

매개변수:
반환 형식:

int

fastcashflow.pricing.statutory_reserve(model_points, statutory_basis)[소스]#

The net-level-premium (NLP) reserve trajectory on a locked statutory basis.

Computed by projection – the engine’s backward present value IS the prospective reserve, so no commutation functions (Dx / Nx / Mx) are needed: the net premium is the break-even premium (it funds the benefits with no margin), and the reserve at each month is the BEL carrying that net premium.

Returns (reserve, net_premium). reserve is (n_mp, n_time+1) – the cohort prospective reserve, column 0 approximately zero (the net premium makes the issue value nil); net_premium is (n_mp,).

statutory_basis is the locked reserving basis (its mortality / interest / lapse). For a pure NLP reserve use a deterministic one (mortality_cv = 0, no expense loading); a gross-premium or expense-loaded reserve follows from putting those into the basis.

매개변수:
반환 형식:

tuple[ndarray[tuple[Any, …], dtype[float64]], ndarray[tuple[Any, …], dtype[float64]]]

fastcashflow.pricing.statutory_profit_signature(model_points, pricing_basis, statutory_basis, *, period_months=12, earned_rate=None)[소스]#

The traditional / statutory profit signature.

Holds the net-level-premium reserve V on statutory_basis and lets the profit emerge on the pricing_basis experience (the actual gross premium in model_points and the best-estimate decrements). Per month, matching the engine’s within-month discount convention (premium / annuity beginning of month, claims and expenses mid-month):

profit_t = (V_t + premium_t - annuity_t)(1 + i)
  • outgo_t (1 + i)^0.5 - V_{t+1}

with i the monthly earned rate (earned_rate if given, else the pricing discount). On a run where the pricing basis equals the statutory basis and the premium is the net premium this is identically zero (the reserve is self-financing); profit emerges from the premium loading (gross over net) and the interest spread (earned over the valuation rate). The result feeds irr() / break_even_year() once the day-0 strain is prepended.

v1 assumes the pricing and statutory bases share decrements (mortality / lapse) – only the valuation interest differs; a reserve re-based onto different decrements is a follow-up. Pass a single Basis (not a router) for each.

매개변수:
반환 형식:

ProfitSignature

fastcashflow.pricing.interest_tvog(model_points, statutory_basis, rate_scenarios, *, guaranteed_rate=None, central_rates=None, initial_prices=None)[소스]#

The cost of a traditional minimum interest-rate guarantee, split into intrinsic value and time value (TVOG).

A general-account (GMM) traditional / interest-sensitive contract credits the policy reserve at a minimum guaranteed rate i_g. When the company’s earned investment rate r falls below i_g the company funds the shortfall on the reserve. Because max(i_g - r, 0) is convex, a deterministic projection at the central rate sees only the intrinsic value; the extra cost from rate volatility – the time value – appears only over many scenarios.

This composes two pieces: the net-level-premium reserve V_t from statutory_reserve() (which accrues at i_g), and the earned-rate scenarios rate_scenarios (e.g. fastcashflow.esg.simulate(...).rates). Per scenario s the guarantee cost is the present value of the funded shortfall:

cost_s = sum_t  D_s(t) * max(i_g_m - r_m[s, t], 0) * V_t

with monthly rates i_g_m = (1 + i_g) ** (1/12) - 1 and likewise r_m, V_t the portfolio reserve held at the start of month t, and D_s(t) the end-of-month stochastic discount along the scenario’s own short rate (the shortfall rides the reserve’s full-month interest credit). The scenario mean of D_s reprices P(0, t+1), so the measure stays risk-neutral.

매개변수:
  • rate_scenarios (ndarray[tuple[Any, ...], dtype[float64]]) – (n_scenarios, n_time) annual earned rate per projection month, where n_time = statutory_reserve(...)[0].shape[1] - 1 (the contract-boundary horizon, NOT the term – the same horizon convention as the projection).

  • guaranteed_rate (float | None) – The minimum guaranteed annual rate i_g. Defaults to statutory_basis.discount_annual when that is a scalar; pass it explicitly if the statutory basis uses a per-year discount curve (v1 takes a scalar i_g).

  • central_rates (ndarray[tuple[Any, ...], dtype[float64]] | None) – (n_time,) annual central path for the intrinsic value. If omitted, the forward path implied by initial_prices is used; exactly one of the two must be given (no silent scenario-mean fallback).

  • initial_prices (ndarray[tuple[Any, ...], dtype[float64]] | None) – (n_time+1,) P(0, t) the scenarios were calibrated to (e.g. EconomicScenarios.initial_prices), used to derive the forward central path when central_rates is omitted.

  • model_points (ModelPoints)

  • statutory_basis (Basis)

반환:

guarantee_cost is the (n_scenarios,) cost distribution; intrinsic_value the central-path cost; time_value the TVOG (mean(cost) - intrinsic); total_value their sum. All are >= 0: the guarantee is a cost to the entity. Net it by ADDING total_value to the fulfilment cash flows / BEL (subtracting from CSM / csm_plus_ra); intrinsic_value is the part a deterministic central-rate valuation already captures, time_value the extra only stochastic scenarios show.

반환 형식:

TVOGResult

fastcashflow.vnb(profit_signature, *, reference_rate, discount_monthly=None, required_capital=None, reserve=None, frictional_spread=0.0, tvog=0.0)[소스]#

Value of new business from a profit signature and a cost of capital.

VNB = PVFP - CoC - TVOG (portfolio total). The function is basis-agnostic: pass the IFRS 17 signature() or the traditional statutory_profit_signature() as profit_signature – only its present value is used.

매개변수:
  • reference_rate (float) – The annual rate discounting both the profit stream (via ProfitSignature.present_value()) and the capital-cost stream.

  • discount_monthly (ndarray[tuple[Any, ...], dtype[float64]] | None) – (n_time,) per-month rate curve for the cost-of-capital present value (e.g. fastcashflow.curves.discount_monthly_curve()). Required when a non-zero capital charge is requested; otherwise the CoC is zero.

  • required_capital (ndarray[tuple[Any, ...], dtype[float64]] | float | None) – The required-capital trajectory RC_t, portfolio total. Either an explicit (n_time,) / (n_time+1,) array (the capital held at the start of each month – e.g. the confidence-level measurement.ra_path.sum(0) as a risk-capital proxy, or a regulatory capital path), or a scalar capital factor applied to reserve. None gives a zero capital charge.

  • reserve (ndarray[tuple[Any, ...], dtype[float64]] | None) – (n_time+1,) reserve path, used only when required_capital is a scalar factor (the capital is required_capital * reserve); e.g. statutory_reserve(...)[0].sum(0) or measurement.bel_path.sum(0).

  • frictional_spread (float) – The annual spread charged on the required capital (the cost of locking it up). Zero gives a zero capital charge.

  • tvog (float) – The time value of options and guarantees to deduct (e.g. interest_tvog(...).total_value). Default 0.

  • profit_signature (ProfitSignature)

반환:

pvfp, cost_of_capital, tvog and the derived value.

반환 형식:

VNB

참고

Double counting: pairing the traditional statutory_profit_signature (whose profit carries no risk adjustment) with any required capital is clean. Pairing the IFRS 17 signature (whose profit already includes the risk-adjustment release) with the confidence-level RA path as the capital is sound but mixes views – the RA release is value flowing into the PVFP, the CoC is the frictional drag on holding capital, two distinct quantities – so the traditional pairing is the cleaner default.

The capital charge (frictional_spread / 12) * sum_t RC_t * df_bom(t) is the same inception value the engine’s cost-of-capital risk adjustment produces for the same capital path and annual spread (the per-month-charged backward present value of the capital), so passing the confidence-level ra_path.sum(0) with frictional_spread = cost_of_capital_rate reproduces that figure exactly.

class fastcashflow.VNB(pvfp, cost_of_capital, tvog)[소스]#

The value of new business, split into its components (portfolio total).

pvfp is the present value of future shareholder profit; cost_of_capital the frictional cost of holding the required capital; tvog the time value of options and guarantees. value is the value of new business pvfp - cost_of_capital - tvog (positive = value-creating).

매개변수:
property value: float#

pvfp - cost_of_capital - tvog.

Type:

Value of new business

Required capital (solvency)#

Regime-agnostic required capital (SCR) by shock-and-aggregate, with the Solvency II and K-ICS calibrations. Liability-side (insurance and interest risk); the asset side and available capital are caller-supplied.

fastcashflow.gmm.required_capital(model_points, basis, *, regime, catastrophe=0.0, property_codes=(), interest_scenarios=None, measure_fn=None)[소스]#

Required capital (SCR) for a portfolio under regime.

Re-measures the liability under each sub-risk’s stress, takes max(Delta BEL, 0) as the sub-risk capital, correlation-aggregates the insurance module, adds the interest-rate stress, and computes the regime risk margin. v1 is liability-side: the total is insurance_scr + interest_capital (no inter-module diversification). Pass SCRResult on to fastcashflow.pricing.vnb() via its required_capital argument.

measure_fn is the liability measurement the stresses re-run (default the GMM measure()); pass measure() to price a variable book’s sub-risks on its net BEL (see fastcashflow.vfa.required_capital()). It must accept (mp, basis, full=...) and return a result carrying bel (and ra_path / bel_path for a cost-of-capital risk margin).

Interest-rate capital comes from interest_scenarios when supplied – a KICSInterest (the five K-ICS shock scenarios, aggregated by the handbook p.205 formula); its five scenario amounts also land in sub_risk_capital (keys interest_up …). Otherwise it is the worst-of regime.interest_curves (the Solvency II maturity-relative up / down table), or zero when neither is present. The K-ICS shock spreads are supervisor- published, so they are supplied at call time rather than baked into the regime.

Two EXTRA insurance sub-risks fold into the module through table 6 when the regime supports them: property_codes (the long-term property / other coverages – a +16% rate shock, re-measured) via property_correlation, and catastrophe (the factor-based amount from kics_catastrophe()) via catastrophe_correlation. The risk margin EXCLUDES catastrophe (handbook: the margin is the insurance amount ex-catastrophe), but INCLUDES property.

매개변수:
반환 형식:

SCRResult

fastcashflow.solvency.kics_catastrophe(*, pandemic_death=0.0, accident_death=0.0, disability=0.0, property=0.0, prior_year_claims=None)[소스]#

The K-ICS catastrophe risk amount (handbook 2-8) – a factor on sum assured.

pandemic_death is the sum assured of pandemic death-exposed coverages (charged 0.1%). accident_death / disability / property are the large-accident sum-assured buckets, each charged the zone-exposure factors against max(sum_assured x shock - prior_year_claims[bucket], 0). The result is sqrt(pandemic^2 + large_accident^2) (the two are uncorrelated). The exposure buckets are caller-supplied (the catastrophe categorisation of a coverage is a mapping decision, not derivable from the engine type).

매개변수:
반환 형식:

float

class fastcashflow.solvency.SCRResult(regime, sub_risk_capital, insurance_scr, interest_capital, total_scr, risk_margin, base_bel, scr_path=None)[소스]#

The required-capital breakdown for a portfolio under one regime.

sub_risk_capital is the per-sub-risk capital (pre-aggregation); insurance_scr the correlation-aggregated insurance module; interest_capital the worst-of interest stress; total_scr their sum (v1: no inter-module diversification). scr_path is the projected capital run-off used for a cost-of-capital risk margin (None for percentile).

매개변수:
class fastcashflow.solvency.RegimeSpec(name, sub_risks, correlation, interest_curves=None, risk_margin_method='percentile', risk_margin_factor=0.0, risk_margin_coc_rate=0.06, catastrophe_correlation=None, property_correlation=None)[소스]#

A solvency regime’s calibration (K-ICS, Solvency II).

sub_risks order is locked to the correlation matrix axes. interest_curves is a tuple of interest-rate Stress (worst taken), or None when the regime’s interest scenarios are caller-supplied. The risk margin is either "percentile" (insurance_scr * risk_margin_factor) or "cost_of_capital" (risk_margin_coc_rate over the capital run-off).

catastrophe_correlation and property_correlation are the catastrophe / long-term-property sub-risks’ correlation with each of the sub_risks (same order), used when a caller passes a catastrophe charge / property coverage codes to required_capital(); None means the regime does not fold that (extra) sub-risk into its insurance module.

매개변수:
fastcashflow.solvency.ratio(scr, available_capital)[소스]#

The solvency ratio – available capital over the required capital (available_capital / scr.total_scr).

available_capital is a CALLER INPUT: the market value of assets less the market value of liabilities (on the prudential balance sheet), tiered per the regime. fastcashflow is a liability engine with no asset model, so it cannot produce the available capital itself – supply it (e.g. from an asset system). The denominator is the liability-side required capital this module computes; asset-side market-risk modules are out of scope, so for a book with material asset risk the ratio is an upper bound on the regulatory one.

매개변수:
반환 형식:

float

fastcashflow.solvency.SII#

A solvency regime’s calibration (K-ICS, Solvency II).

sub_risks order is locked to the correlation matrix axes. interest_curves is a tuple of interest-rate Stress (worst taken), or None when the regime’s interest scenarios are caller-supplied. The risk margin is either "percentile" (insurance_scr * risk_margin_factor) or "cost_of_capital" (risk_margin_coc_rate over the capital run-off).

catastrophe_correlation and property_correlation are the catastrophe / long-term-property sub-risks’ correlation with each of the sub_risks (same order), used when a caller passes a catastrophe charge / property coverage codes to required_capital(); None means the regime does not fold that (extra) sub-risk into its insurance module.

fastcashflow.solvency.KICS#

A solvency regime’s calibration (K-ICS, Solvency II).

sub_risks order is locked to the correlation matrix axes. interest_curves is a tuple of interest-rate Stress (worst taken), or None when the regime’s interest scenarios are caller-supplied. The risk margin is either "percentile" (insurance_scr * risk_margin_factor) or "cost_of_capital" (risk_margin_coc_rate over the capital run-off).

catastrophe_correlation and property_correlation are the catastrophe / long-term-property sub-risks’ correlation with each of the sub_risks (same order), used when a caller passes a catastrophe charge / property coverage codes to required_capital(); None means the regime does not fold that (extra) sub-risk into its insurance module.

Asset-liability management#

Deterministic interest-rate sensitivity of the liability and the assets backing it – duration, DV01, key-rate duration, and the asset-liability DV01 gap.

class fastcashflow.alm.DurationResult(pv, macaulay, modified, dv01, convexity=nan)[소스]#

Interest-rate sensitivity of a present value.

pv is the present value (the BEL for a liability, the market value for a bond). macaulay / modified are durations in years (macaulay is nan where it is not well defined – a mixed-sign liability stream). dv01 is the decrease in pv for a +1bp parallel rise in the curve (positive for a normal positive-duration instrument). convexity is the second-order yield sensitivity (1/pv) d2pv/dy2 in years^2 (the curvature that the linear duration misses for a large rate move: dpv/pv ~ -modified*dy + 0.5*convexity*dy^2); nan where it is not well defined (a near-zero pv).

매개변수:
fastcashflow.alm.liability_duration(model_points, basis, *, bump=0.0001)[소스]#

The liability’s interest-rate sensitivity – pv (BEL), dv01, an effective modified duration (= dv01 / (|pv| * 1bp)) and an effective convexity (the second central difference of the BEL under a parallel curve shift, (BEL(+b) + BEL(-b) - 2 BEL(0)) / (|pv| * b^2)). macaulay is nan (the mixed-sign liability stream has no clean Macaulay time); modified / convexity are nan when |pv| is negligible (the ratios are then ill-conditioned – read the dv01 instead).

매개변수:
반환 형식:

DurationResult

fastcashflow.alm.liability_dv01(model_points, basis, *, bump=0.0001)[소스]#

The liability DV01 – the decrease in BEL for a +1bp parallel rise in the discount curve, by central difference (re-measure at +/-bump).

Robust for any BEL (positive, negative, near zero). bump is the parallel rate shift used for the finite difference (default 1bp); the result is scaled to a per-1bp figure.

매개변수:
반환 형식:

float

fastcashflow.alm.key_rate_dv01s(model_points, basis, *, bump=0.0001)[소스]#

Key-rate DV01s – the liability DV01 attributed to each policy-year bucket of the curve, by bumping one year of the per-year discount curve at a time (central difference). Returns (n_years,); the buckets sum to approximately the parallel liability_dv01() (the key-rate decomposition of it).

매개변수:
반환 형식:

ndarray[tuple[Any, …], dtype[float64]]

fastcashflow.alm.gap(asset_dv01, liability_dv01)[소스]#

The asset-liability DV01 gap – asset_dv01 - liability_dv01. Zero means the net value is immunised against a small parallel rate move (the asset and liability fall by the same amount per 1bp). Both inputs are DV01s on the same curve (e.g. summed bond_duration() DV01s and liability_dv01()).

매개변수:
반환 형식:

dict

Solvency balance sheet (assets)#

Static (t=0) asset valuation – the portfolio market value, available capital, the net interest-rate SCR, and the assembled solvency ratio.

class fastcashflow.assets.Portfolio(holdings)[소스]#

An immutable set of holdings. Bonds are priced off the discount curve; equity / property / cash carry a given market value.

매개변수:

holdings (tuple)

class fastcashflow.assets.Equity(market_value, risk_type='developed', currency='KRW', issuer='', credit_rating='AA')[소스]#

An equity holding carried at a given market value (asset-positive).

risk_type selects the market-risk shock ("developed" or "emerging" market listed equity); the shock magnitude is the regime’s calibration. currency (ISO code, “KRW” for domestic) drives the FX SCR. issuer (counterparty) and credit_rating group exposures for the concentration SCR.

매개변수:
  • market_value (float)

  • risk_type (str)

  • currency (str)

  • issuer (str)

  • credit_rating (str)

class fastcashflow.assets.Property(market_value, currency='KRW')[소스]#

A property holding carried at a given market value. currency (ISO code) drives the FX SCR; property contributes to the property concentration SCR.

매개변수:
class fastcashflow.assets.Cash(market_value, currency='KRW', issuer='', credit_rating='AA')[소스]#

A cash holding, carried at face (curve-insensitive). currency (ISO code) drives the FX SCR. issuer (the deposit counterparty) and credit_rating group exposures for the concentration SCR.

매개변수:
  • market_value (float)

  • currency (str)

  • issuer (str)

  • credit_rating (str)

fastcashflow.assets.portfolio_value(portfolio, discount_annual)[소스]#

Total market value of the portfolio at the given discount curve.

매개변수:

portfolio (Portfolio)

반환 형식:

float

fastcashflow.solvency.available_capital(asset_portfolio_value, bel, risk_margin)[소스]#

Available capital (own funds) – assets less liabilities on the prudential balance sheet: asset_portfolio_value - (bel + risk_margin). The liability is the technical provision (best estimate plus risk margin). Positive = solvent surplus. (Other balance-sheet liabilities, if any, are the caller’s to net out of the portfolio value.)

매개변수:
반환 형식:

float

fastcashflow.solvency.net_interest_scr(portfolio, model_points, basis, *, interest_curves)[소스]#

The net interest-rate SCR – the worst loss in own funds (assets less liabilities) over the regime’s up / down curve shocks.

A rate rise lowers BOTH the asset value (bonds) and the BEL; the capital is the fall in net asset value (see _nav_delta()). The worst of the up / down shocks is taken, floored at zero. A duration-matched book gives ~ 0 – the immunised gap. This is the Solvency II form; K-ICS uses the five-scenario net_interest_kics_scr().

interest_curves is the regime’s tuple of interest-rate stresses (RegimeSpec.interest_curves); pass a non-empty tuple (the assembler handles a regime with no curves).

매개변수:
반환 형식:

float

fastcashflow.solvency.equity_scr(portfolio, regime)[소스]#

The equity market-risk SCR – the per-type amounts (each type’s holdings’ market value times its price-fall shock) aggregated at the 0.75 inter-type correlation (handbook 4-3). Types: developed / emerging listed, infrastructure, long_term, other, preferred. Raises on an unknown type.

매개변수:

portfolio (Portfolio)

반환 형식:

float

fastcashflow.solvency.property_scr(portfolio, regime)[소스]#

The property market-risk SCR – property market value times the regime’s price-fall shock.

매개변수:

portfolio (Portfolio)

반환 형식:

float

fastcashflow.solvency.fx_scr(portfolio, regime, discount_annual)[소스]#

The FX-risk SCR on the net foreign-currency exposure (vs the won, the local currency here).

K-ICS: each currency’s table-22 shock, summing the net-asset-value losses of the declining currencies under a won-up and a won-down scenario through a 0.5 correlation, the worse of the two. Solvency II (Article 188): a flat 25% per currency, each currency’s larger of the up / down loss, SUMMED (no diversification). Returns 0 for an unknown regime.

매개변수:

portfolio (Portfolio)

반환 형식:

float

fastcashflow.solvency.concentration_scr(portfolio, regime, discount_annual, *, total_assets=None)[소스]#

The asset-concentration SCR – sqrt(counterparty^2 + property^2).

Counterparty: exposures are grouped by issuer (deposits, equity and bonds); the amount above the limit (total_assets times the rating band’s percentage, table 23) is charged the band’s factor, and the per-issuer charges combine at correlation 0. Property: each holding above the individual limit (6% of total assets) and the whole book above the total limit (25%) are charged 20% (table 24), taking the worse of the two. total_assets defaults to the portfolio value. Solvency II (Articles 184-187) uses the single-name excess max(0, exposure - threshold(CQS) x assets) x g(CQS) aggregated as a root-sum-of-squares. Returns 0 when a book has no tagged issuers and no property, or for an unknown regime.

매개변수:
반환 형식:

float

fastcashflow.solvency.market_scr(portfolio, model_points, basis, *, regime, interest_scenarios=None)[소스]#

The market-risk module SCR – the interest (net of liabilities), equity, property, FX and asset-concentration sub-risks aggregated through the regime’s market correlation matrix (sqrt(c^T R c)). Interest is the K-ICS five- scenario net_interest_kics_scr() when interest_scenarios is supplied (the supervisor-published shock set), else the worst-of-curves net_interest_scr() (Solvency II), else zero.

매개변수:
반환 형식:

float

fastcashflow.solvency.credit_scr(portfolio, regime, discount_annual)[소스]#

The credit-risk SCR – each bond’s market value times its credit factor.

The factor is read off the K-ICS (rating x effective-maturity) grid for the bond’s exposure_class (handbook tables 29 / 30 / 31); it is a percent of market value and already embeds the default + downgrade charge, so the SCR is sum(market_value x factor) – no re-measure. Cash is risk-free and equity / property carry market (not credit) risk, so only bonds contribute. Solvency II uses the Art-176 spread stress (piecewise-linear in modified duration by credit quality step).

매개변수:

portfolio (Portfolio)

반환 형식:

float

fastcashflow.solvency.operational_scr(model_points, basis, regime, *, bscr=None, measure_fn=None)[소스]#

The operational-risk SCR – a factor on the liability (premiums and BEL).

K-ICS: max(premium x 3.5%, BEL x 0.4%). Solvency II: min(0.3 x bscr, max(0.04 x premium, 0.0045 x BEL)) + 0.25 x unit-linked expenses – pass bscr (the basic SCR) for the cap; unit-linked expenses are 0 in v1. The premium exposure is the first projection year’s earned premium; the BEL exposure is floored at zero.

measure_fn is the liability measurement (default the GMM measure()); pass measure() for a variable book’s BEL / premium exposure.

매개변수:
반환 형식:

float

fastcashflow.solvency.basic_scr(insurance, market, credit, *, regime, operational=0.0, general_insurance=0.0)[소스]#

The basic required capital from disclosed module amounts – the top-level aggregate of the (life) insurance, market and credit modules plus the operational charge (added OUTSIDE the aggregate).

K-ICS uses the table-3 correlation; Solvency II uses the Annex IV BSCR matrix. For (life, market, credit) the two coincide (all pairs 0.25), so the 3-module aggregate is the same; with general_insurance (a fourth P&C module) they differ only in general-vs-credit (K-ICS 0.25, Solvency II 0.5) and share life-vs-general 0. The disclosed diversification effect is the simple module sum minus this aggregate. Use it to reproduce a disclosed basic required capital from the published module risk amounts, or for a what-if on the module mix without re-running a book.

매개변수:
반환 형식:

float

fastcashflow.gmm.assess(portfolio, model_points, basis, *, regime, tax_rate=0.0, tax_recoverability_limit=None, catastrophe=0.0, property_codes=(), general_insurance_scr=0.0, interest_scenarios=None, relief=None)[소스]#

Assemble the t=0 solvency ratio from the assets and the liability SCR.

Runs required_capital() for the liability (insurance) SCR, values the portfolio, forms available capital (assets less the technical provision), and builds the market-risk module (net interest, equity, property, FX, concentration) aggregated through the market correlation. interest_scenarios (a KICSInterest) makes the net interest sub-risk the K-ICS five-scenario amount on net asset value; without it the net interest is the regime’s worst-of curves (Solvency II) or zero (K-ICS supplies no curves). The interest risk sits in the market module (net of assets and liabilities), NOT in the insurance module – required_capital is run without interest here. The BSCR aggregates the insurance, market and credit modules at the top level: K-ICS uses the table-3 correlation, Solvency II the Annex IV BSCR matrix; for the (life, market, credit) modules the two coincide (all pairwise 0.25). The operational-risk SCR is added on top to form the basic required capital.

tax_adjustment (K-ICS chapter 7 – the loss-absorbing capacity of deferred taxes) is then subtracted to give the total required capital, the ratio denominator: min(basic_required_capital x tax_rate, tax_recoverability_limit). tax_rate is the company’s average effective rate (over its recent pre-tax profits) and defaults to 0 (no tax relief – conservative); supply tax_recoverability_limit for the regulatory recoverability cap (else the relief is uncapped at basic x tax_rate).

catastrophe (the K-ICS catastrophe amount from kics_catastrophe()) and property_codes (the long-term property / other coverages, a +16% rate shock) fold into the insurance module (table-6 correlation); both default to off. general_insurance_scr (a caller-supplied P&C amount for a life + general book) enters the BSCR as a fourth top-level module (table 3: life-vs-general 0, else 0.25); the life-only engine leaves it at 0.

relief (a CedantSolvencyRelief from cedant_solvency_relief()) folds a mass-lapse reinsurance treaty into the ratio: the insurance module drops by relief.insurance_relief (the diversified life-module lapse relief), the counterparty-default charge relief.counterparty_default is added into the credit module, and the risk margin falls by relief.risk_margin_relief (raising available capital). Compute the relief under the SAME regime (its own required_capital run must match). Both default off (None). Because the counterparty-default charge here enters the BSCR through the top-level correlation (it diversifies with the life and market modules), the ratio benefit can differ from the undiversified relief.net_scr_benefit; the insurance / credit modules and the risk margin reported are the post-treaty (net) figures. Pricing the relief on a catastrophe / property_codes insurance module is a small approximation – the relief delta is built on the base life module (those default off, the common case). A relief that exceeds the module it offsets (almost always a regime / basis mismatch) raises a ValueError rather than flooring to zero and understating the SCR.

Notes: K-ICS supplies no interest curves (its scenarios are caller-supplied), so the net interest component is zero here – equity and property still apply. Credit, FX and concentration risk are calibrated for both regimes (K-ICS handbook / Solvency II Delegated Regulation). A non-positive total required capital (a risk-free book) gives an unbounded ratio.

매개변수:
반환 형식:

Assessment

class fastcashflow.solvency.Assessment(asset_portfolio_value, bel, risk_margin, available_capital, insurance_scr, general_insurance_scr, net_interest_scr, equity_scr, property_scr, fx_scr, concentration_scr, market_scr, credit_scr, operational_scr, basic_scr, basic_required_capital, tax_adjustment, total_scr, ratio)[소스]#

The asset-inclusive solvency picture at t=0 – the full ratio and its parts.

available_capital is asset_portfolio_value - (bel + risk_margin). The market module aggregates the net_interest_scr (assets and liabilities), the equity_scr, the property_scr, the fx_scr and the concentration_scr through the market correlation; the basic_scr (basic SCR) aggregates the insurance_scr, the (optional) general_insurance_scr, the market_scr and the credit_scr at the top level; basic_required_capital adds the operational_scr on top of the BSCR. total_scr (the ratio denominator) subtracts the tax_adjustment (loss-absorbing capacity of deferred taxes) from the basic required capital. ratio is available_capital / total_scr.

매개변수:

Reporting#

fastcashflow.report(measurement)[소스]#
fastcashflow.report(measurement)
fastcashflow.report(measurement)
fastcashflow.report(measurement)
fastcashflow.report(measurement)
fastcashflow.report(result)
fastcashflow.report(measurement)
fastcashflow.report(measurement)

Assemble the IFRS 17 report from a GMM, PAA, VFA or reinsurance measurement.

See the module docstring for the basis (IFRS 17 paragraphs B120-B124). Dispatches on the measurement type; a new model registers its own report with @report.register. A reinsurance-held measurement returns a _reinsurance.Report (the mirror layout, IFRS 17 paragraphs 82 + 86), not a Report. A mixed-portfolio container (PortfolioMeasurement or PortfolioGroups) is also accepted: each model slot is reported on its own measurement and a PortfolioReport is returned (a GMM, PAA and VFA report are never merged).

반환 형식:

Report

class fastcashflow.Report(insurance_revenue, insurance_service_expense, insurance_service_result, insurance_finance_expense, bel_finance_expense, ra_finance_expense, csm_finance_expense, loss_component, csm_opening, csm_accretion, csm_release, csm_closing)[소스]#

IFRS 17 reporting figures, period by period.

Each flow array is shaped (n_mp, n_time) – one row per model point, one column per month; loss_component is (n_mp,) – the onerous loss at inception. insurance_service_result is revenue less service expense; insurance_finance_expense is signed (positive an expense). The CSM analysis of change reconciles as csm_opening + csm_accretion - csm_release = csm_closing (the CSM columns are zero for a PAA measurement, which has no CSM).

insurance_finance_expense is also disaggregated by source (IFRS 17 B130-B136) into bel_finance_expense (finance on the estimates of future cash flows), ra_finance_expense (finance on the risk adjustment) and csm_finance_expense (the CSM interest accreted at the locked-in rate, B72). The three sum to insurance_finance_expense up to floating-point rounding (the aggregate is kept as its own expression, so the parts may differ from it by a rounding step rather than re-deriving it). The split is the structural basis for a later P&L / OCI allocation.

매개변수:
annual()[소스]#

Portfolio totals aggregated to policy years.

Each per-period line item is summed across model points and then across the twelve months of each policy year.

반환 형식:

dict[str, ndarray[tuple[Any, …], dtype[float64]]]

by_period(period_months=12, *, basis='elapsed', inception_month=None)[소스]#

Portfolio totals bucketed into reporting periods of period_months.

The general form of annual() (which is by_period(12) on the elapsed basis for its six lines): every flow line of the report – revenue, service expense, service result, the finance expense and its B130-B136 split, and the CSM accretion / release – summed across model points into each reporting period, plus loss_component placed in the period of each cohort’s inception. basis='elapsed' (default) buckets by elapsed policy time; basis='calendar' shifts each cohort by its inception_month so flows fall in the calendar period they occur in (see _period_offsets()). Returns dict[str, FloatArray], each array one entry per period (period 0 first).

매개변수:
  • period_months (int)

  • basis (str)

반환 형식:

dict[str, ndarray[tuple[Any, …], dtype[float64]]]

report also accepts a mixed-portfolio container (PortfolioMeasurement or PortfolioGroups) and returns a PortfolioReport – one Report per model, never merged.

class fastcashflow.portfolio.PortfolioReport(gmm=None, paa=None, vfa=None)[소스]#

Result of fcf.report() on a portfolio container: one Report per model present (None when absent), keyed by model. Accepts a PortfolioMeasurement (per-model-point) or a PortfolioGroups (grouped); a BEL and an LRC report are never pooled.

매개변수:

Period-close analysis of change#

fastcashflow.roll_forward(measurement, period_months=12, *, revised=None, revised_at=None, actual_inforce=None, experience_at=None)[소스]#
fastcashflow.roll_forward(measurement, period_months=12, *, revised=None, revised_at=None, actual_inforce=None, experience_at=None)
fastcashflow.roll_forward(measurement, period_months=12, *, revised=None, revised_at=None, actual_inforce=None, experience_at=None)
fastcashflow.roll_forward(measurement, period_months=12, *, revised=None, revised_at=None, actual_inforce=None, experience_at=None)
fastcashflow.roll_forward(measurement, period_months=12, *, revised=None, revised_at=None, actual_inforce=None, experience_at=None)
fastcashflow.roll_forward(measurement, period_months=12, *, revised=None, revised_at=None, actual_inforce=None, experience_at=None)
fastcashflow.roll_forward(measurement, period_months=12, *, revised=None, revised_at=None, actual_inforce=None, experience_at=None)

Slice a measurement into reporting-period movements.

Returns one movement per reporting period of period_months months, reconciling the opening and closing figures; consecutive periods chain and a partial final period is allowed. Dispatches on the measurement type – a new model registers with @roll_forward.register.

For a GMM measurement, an assumption revision is recognised by passing revised (a second measurement of the same book under updated basis) and revised_at (the month it takes effect); in-force experience by actual_inforce (the (n_mp,) in-force remaining at the period end, or a 2-D (n_periods, n_mp) array to roll experience through every period) and experience_at. Either change adjusts the CSM by the resulting change in fulfilment cash flows (floored at zero, any excess falling into the loss component); v1 recognises one or the other, not both in a single call. A PAA or VFA measurement is also accepted – the movement is then the roll of the LRC or of the CSM, to which the revision and experience options do not apply.

A mixed-portfolio container (PortfolioMeasurement or PortfolioGroups) is also accepted: each model slot is rolled forward on its own measurement and a PortfolioMovements is returned (the revision / experience options, being single-GMM-measurement features, are rejected on the container).

매개변수:

period_months (int)

fastcashflow.reconcile(movements)[소스]#
fastcashflow.reconcile(aggregate)
fastcashflow.reconcile(aggregate)
fastcashflow.reconcile(aggregate)
fastcashflow.reconcile(aggregate)
fastcashflow.reconcile(settlement)
fastcashflow.reconcile(settlement)
fastcashflow.reconcile(movements)

Aggregate period movements into IFRS 17 reconciliation tables.

Each _gmm.PeriodMovement – per model point – becomes one portfolio-total _gmm.Reconciliation in the layout of IFRS 17 paragraph 101. Run-off rows are shown negative, so opening plus every row equals closing. A list of _paa.PeriodMovement or _vfa.PeriodMovement is reconciled instead into the PAA liability-for-remaining-coverage or VFA contractual-service-margin tables.

The base implementation takes a list of movements (dispatch falls through to it for any list); a mixed-portfolio PortfolioMovements registers its own arm (returning a PortfolioReconciliation).

매개변수:

movements (list[PeriodMovement] | list[PeriodMovement] | list[PeriodMovement])

반환 형식:

list[Reconciliation] | list[Reconciliation] | list[Reconciliation]

class fastcashflow.gmm.PeriodMovement(month_start, month_end, bel_opening, bel_assumption_change, bel_experience, bel_interest, bel_release, bel_closing, ra_opening, ra_assumption_change, ra_experience, ra_interest, ra_release, ra_closing, csm_opening, csm_assumption_change, csm_experience, csm_accretion, csm_release, csm_closing, loss_component_recognised)[소스]#

One reporting period’s analysis of change.

The period covers months [month_start, month_end). Every array is (n_mp,), and each block reconciles exactly:

bel_opening + bel_assumption_change + bel_experience
    + bel_interest - bel_release == bel_closing

and likewise for RA and CSM (with csm_accretion in place of *_interest).

*_interest / csm_accretion is the unwind of discount at the locked-in rate; *_release is the expected run-off over the period. *_assumption_change and *_experience are the effect of an assumption revision and of in-force experience – non-zero only in the period the change is recognised. Both relate to future service and so adjust the CSM. loss_component_recognised is the part of an unfavourable change beyond the CSM, which falls into the loss component.

매개변수:
class fastcashflow.gmm.Reconciliation(month_start, month_end, bel_opening, bel_future_service, bel_finance, bel_release, bel_closing, ra_opening, ra_future_service, ra_finance, ra_release, ra_closing, csm_opening, csm_future_service, csm_finance, csm_release, csm_closing, loss_component_recognised)[소스]#

An IFRS 17 reconciliation of the insurance contract liability.

Portfolio totals for one reporting period, in the layout of IFRS 17 paragraph 101: the estimates of the present value of future cash flows (bel), the risk adjustment (ra) and the CSM each reconcile from opening to closing. *_future_service is the assumption and experience effect; *_finance is the interest unwind; *_release is the run-off, shown negative – so opening plus every row equals closing.

매개변수:

roll_forward and reconcile also accept the mixed-portfolio containers: roll_forward on a PortfolioMeasurement / PortfolioGroups returns a PortfolioMovements (one movement list per model), which reconcile() turns into a PortfolioReconciliation – a GMM CSM movement and a PAA LRC movement are never merged.

class fastcashflow.portfolio.PortfolioMovements(gmm=None, paa=None, vfa=None)[소스]#

Result of fcf.roll_forward() on a portfolio container: one list of period movements per model present (None when absent), keyed by model. Each slot is the model’s own movement list (list[gmm.PeriodMovement] / list[paa.PeriodMovement] / list[vfa.PeriodMovement]) – a GMM CSM movement and a PAA LRC movement are never merged. Feed it to fcf.reconcile() for a PortfolioReconciliation.

매개변수:
class fastcashflow.portfolio.PortfolioReconciliation(gmm=None, paa=None, vfa=None)[소스]#

Result of fcf.reconcile() on a PortfolioMovements: one list of reconciliation tables per model present (None when absent), keyed by model (list[gmm.Reconciliation] / list[paa.Reconciliation] / list[vfa.Reconciliation]).

매개변수:

Aggregation and transition#

fastcashflow.group(measurement, by)[소스]#
fastcashflow.group(measurement, by)
fastcashflow.group(measurement, by)
fastcashflow.group(measurement, by)
fastcashflow.group(measurement, by)
fastcashflow.group(measurement, by)

Aggregate a per-model-point measurement to any axis.

A general aggregation primitive – not IFRS 17-specific. by is one of:

  • a single axis name (e.g. "product");

  • a list of axis names and/or precomputed (n_mp,) label arrays (e.g. ["product", "issue_year"], or ["product", onerous_array]), joined into one composite label;

  • a single precomputed (n_mp,) array of group labels.

Names are resolved per model point via ModelPoints.axis() against the model points the measure stamped on the result, so no re-passing is needed; a computed axis with no source column (e.g. an onerous flag from loss_component) is passed as an array instead – an np.ndarray, since a Python list is read as a list of axes, not a single label vector.

BEL and RA are summed within each group; the CSM and the loss component are re-derived on the group aggregate, so the max(0, ...) floor nets the contracts within a group but not across groups. The IFRS 17 unit of account (portfolio x annual cohort x profitability) is one choice of axes – group_of_contracts() is the preset for it; management-accounting, profitability and validation views are other choices of by.

Dispatches on the measurement type (_gmm.Measurement, _vfa.Measurement, _reinsurance.Measurement, _paa.Measurement). A PortfolioMeasurement (the mixed-model container) is also accepted: each model slot is grouped on its own native measurement and a PortfolioGroups is returned (a precomputed array by is subset to each slot’s rows). Returns a measurement of the same type whose rows are the groups, in ascending label order – usable in turn by roll_forward(), reconcile() and report(). Its group_labels attribute carries the composite label of each row, so a caller can map a group back to its key (e.g. "|"-split a group_of_contracts() label into portfolio / cohort / profitability) without rebuilding the keys; group_sizes carries the number of model points in each group (model-point rows, not the policy count – they differ when a model point’s count stands for several policies).

fastcashflow.group_of_contracts(measurement, *, portfolio='product', cohort='issue_year', profitability=None)[소스]#
fastcashflow.group_of_contracts(measurement, *, portfolio='product', cohort='issue_year', profitability=None)
fastcashflow.group_of_contracts(measurement, *, portfolio='product', cohort='issue_year', profitability=None)
fastcashflow.group_of_contracts(measurement, *, portfolio='product', cohort='issue_year', profitability=None)
fastcashflow.group_of_contracts(measurement, *, portfolio='product', cohort='issue_year', profitability=None)
fastcashflow.group_of_contracts(measurement, *, portfolio='product', cohort='issue_year', profitability=None)

Aggregate a measurement to the IFRS 17 group of insurance contracts.

The unit of account (paragraphs 14-24) is a portfolio (14) x annual cohort (22) x profitability (16). This preset builds that grouping from the model points measure() stamped on the measurement and runs group(), so the CSM floor nets within a group but not across.

Dispatches on the measurement type; the profitability axis differs by type (a new measurement registers with @group_of_contracts.register):

  • _gmm.Measurement / _vfa.Measurement / _paa.Measurement – insurance contracts issued, direct-participating, and short-coverage (PAA) contracts; profitability is the onerous / remaining split (paragraph 16, and 57 for the PAA). The per-type re-derivation differs (VFA accretes the CSM at the underlying-items return; the PAA has no CSM, only the LRC and the onerous loss), handled by group()’s own dispatch.

  • _reinsurance.Measurement – reinsurance contracts held; profitability is the net-gain split (paragraph 61, csm > 0), and there is no loss component or floor (paragraph 65), so the grouped CSM is the sum of the contract CSMs.

  • PortfolioMeasurement – the mixed-model container; each model slot is grouped on its own native measurement and a PortfolioGroups is returned. For a book too large to hold the full per-model-point measurement, use the chunked fastcashflow.portfolio.measure_group_of_contracts() instead.

Arguments (keyword-only):

  • portfolio – the column naming the portfolio axis (default "product": paragraph 14’s product line). Pass another column name to group on a different portfolio definition.

  • cohort – the column naming the annual-cohort axis (default "issue_year", derived from issue_date: paragraph 22). Pass another column (e.g. "issue_quarter" carried in the data) for a finer cohort; paragraph 22 caps the span at one year, so a cohort may be finer than annual but not coarser.

  • profitability – the profitability classification. None (default) derives it from the measurement, since it is an output, not a known input (paragraph 16 / 47’s net-outflow test). Pass a precomputed (n_mp,) array for a custom split (e.g. the paragraph-16 three-way split using a CSM-vs-RA threshold), or a column name for a locked classification carried in the data (paragraph 24: the group is fixed at inception).

Requires a full=True measurement.

매개변수:
  • portfolio (str)

  • cohort (str)

반환 형식:

Measurement

fastcashflow.transition(measurement, fair_value)[소스]#

Re-set the CSM on the IFRS 17 fair value transition basis.

measurement is a measurement of the in-force book at the transition date – its inception column being that date. fair_value is the (n_mp,) fair value of each contract or group. The CSM becomes max(0, fair_value - fulfilment cash flows), any excess of the fulfilment cash flows over the fair value falling into the loss component, and is rolled forward from there.

Returns a measurement with the re-set CSM and loss component; the BEL and RA are unchanged. It flows on into roll_forward(), reconcile() and report().

매개변수:
반환 형식:

Measurement

Period close and disclosure#

The reporting-period close assembles the settlement reconciliations of a period (one per group of contracts) into the aggregate IFRS 17 statements – the statement of financial position, the insurance finance statement and the insurance service result – and serialises them to a disclosure artifact. The tidy-frame helpers expose the same content as audit-traceable rows.

fastcashflow.close(reconciliations, *, reports=None, group_ids=None)[소스]#

Assemble the close pack from a reporting period’s settlement reconciliations.

reconciliations is the GMM / VFA / PAA / reinsurance settlement reconciliations of one reporting period (what fastcashflow.reconcile() returns, one per model / group) – the source of the SoFP, the finance statement and the reconciliation detail. reports, if given, is the list of Report / Report that adds the insurance service result statement (sourced from the report, not the settlement – see assemble_service_result()). group_ids, if given, names the group of contracts each reconciliation belongs to (parallel to reconciliations); it stamps the reconciliation detail so per-group lines stay identifiable.

All reconciliations must share the same period_months – a close pack is one reporting period.

반환 형식:

ClosePackage

class fastcashflow.ClosePackage(period_months, sofp, finance, reconciliation, service_result=None)[소스]#

The assembled IFRS 17 close pack for one reporting period.

sofp is the statement of financial position (assemble_sofp()); finance is the insurance finance statement (assemble_finance()); service_result is the insurance service result statement (assemble_service_result()), present only when close is given the reports; reconciliation is the stacked per-model settlement detail (the lean tidy frame of reconciliation_to_frame(), one block of rows per reconciliation, stamped with group_id). The disclosure emitter materialises these into the multi-sheet close-pack artifact.

매개변수:
  • period_months (int)

  • sofp (DataFrame)

  • finance (DataFrame)

  • reconciliation (DataFrame)

  • service_result (DataFrame | None)

to_frames()[소스]#

The close pack as named frames – the sheet-shaped views the disclosure emitter writes (the service result only if it was assembled).

반환 형식:

dict[str, DataFrame]

fastcashflow.assemble_sofp(reconciliations)[소스]#

The statement of financial position (IFRS 17 paragraphs 78, 99-101).

The closing carrying amount of insurance contracts, split into LRC excluding the loss component / loss component / liability for incurred claims, for contracts issued, reinsurance contracts held, and the net – each with the opening balance, the period change and the closing balance. Per row, opening + change == closing; per kind, the Total row is the sum of the three components (the carrying amount); the Net kind sums issued and reinsurance held in the one signed liability frame (a reinsurance recoverable is a negative carrying amount, so it reduces the net).

반환 형식:

DataFrame

fastcashflow.assemble_finance(reconciliations)[소스]#

The insurance finance statement (IFRS 17 paragraphs 87-89, B130-B136).

The period’s insurance finance income or expenses disaggregated by source – finance on the BEL, the RA, the CSM (accretion at the locked-in rate, B72), the liability for incurred claims (42(c)), and the B97(a) locked-in rate adjustment (the current-vs-locked-in rate gap on the experience adjustment) – for contracts issued, reinsurance contracts held, and the net. The five sources sum to the Insurance finance income or expenses total line. Loss component finance is a memo: the loss component’s share of the BEL finance (51(c)), already inside the BEL finance line, not an additional amount.

반환 형식:

DataFrame

fastcashflow.assemble_service_result(reports, *, period_months=12)[소스]#

The insurance service result statement (IFRS 17 paragraphs 83, B120-B124).

The period-by-period insurance revenue, service expense and service result for contracts issued, and the premiums / recoveries / net / service result for reinsurance contracts held (presented separately, paragraph 82), summed across the reports of each kind. reports is a list of Report (issued) and / or Report (held).

The service result is sourced from Report.by_period(), NOT the settlement reconciliation: insurance revenue (B120-B124) needs the gross expected claims and expenses, which the settlement table does not carry (its BEL release is net of premiums). It is therefore the EARNED / projected P&L of the measurement – for a new-business group’s first reporting period it ties to that period’s settlement; for a later in-force period it is the projection, with experience variances carried in the reconciliation and finance memos. v1 buckets on the elapsed basis (the calendar basis needs a per-report inception offset – use Report.by_period() directly for it).

매개변수:

period_months (int)

반환 형식:

DataFrame

fastcashflow.reconciliation_to_frame(recon)[소스]#
fastcashflow.reconciliation_to_frame(recon)
fastcashflow.reconciliation_to_frame(recon)
fastcashflow.reconciliation_to_frame(recon)
fastcashflow.reconciliation_to_frame(recon)

Return the lean canonical tidy frame for a settlement reconciliation.

반환 형식:

DataFrame

fastcashflow.line_metadata()[소스]#

The disclosure line registry as a frame – (model, block, line, line_code, ifrs17_paragraph, is_memo, sort_order) – the single source the lean reconciliation_to_frame() and the emitter both read. Exposed so a user can join the reference columns onto a lean frame themselves.

반환 형식:

DataFrame

fastcashflow.write_reconciliation(reconciliation, path)[소스]#

Serialize a settlement reconciliation (or a list of them, one per reporting period) to a tidy file – parquet / csv / xlsx – with the rich audit columns materialised. A list is stacked with a 0-based period_index so a multi-period close schedule round-trips as one long frame.

Mirrors write_measurement() (which serializes the per-MP movements); this is the disclosure-shaped (reconciliation / close) serialization path.

반환 형식:

None

fastcashflow.write_close_pack(package, path, *, movements=None)[소스]#

Write a close pack (a ClosePackage) to a multi-sheet .xlsx – the aggregate IFRS 17 statements an entity reads – and, when movements is given, a per-model-point parquet sidecar.

The workbook carries an index cover, the statement of financial position, the service result (if it was assembled), the finance statement, and the reconciliation detail with the rich audit columns (line_code, the IFRS 17 paragraph anchor, the memo flag, the deterministic order) materialised by joining line_metadata() – so the artifact is self-contained.

The per-model-point settlement movement does NOT go in the workbook (a sheet caps at ~1,048,576 rows); movements – one settlement movement or a list – is written to <path>_per_mp[_i].parquet beside the workbook via write_measurement(), and the index sheet names the file(s).

반환 형식:

None

State models#

The in-force state machine driving multi-state products (waiver, paid-up, disability income, reincidence, long-term care), under the fcf.multistate namespace. A Model is a tuple of State objects, each carrying its Transition edges; Model.from_preset holds the bundled models.

class fastcashflow.multistate.Model(states, seating=(0,))[소스]#

A product’s in-force state machine, declared as data.

states are the transient states; position fixes the kernel state index, and state 0 is the issue state. seating maps a model point’s input contract state – the ModelPoints.state code (STATE_ACTIVE, STATE_WAIVER, STATE_PAIDUP) – to the index of the state its in-force is seated on at the valuation date: seating[code] is that index. It defaults to seating every model point on state 0.

The occupancy recursion treats every state identically, so an arbitrary Model runs on the existing kernels with no per-product code – see the module docstring and compile_model().

매개변수:
property transitions: tuple[TransitionRecord, ...]#

The model’s transition structure as an ordered tuple of records.

The transMat analogue: every transition the topology declares, in a stable order, derived from the state / transition declaration alone (a transition is listed even if a particular book sets its rate to zero). The order groups all death exits, then all lapse exits, then all inter-state transfers – each group in state-index order, and within a state in declaration order:

  • a death record for each state that declares a mortality decrement (a transition with rate == "mortality");

  • a lapse record for each state that declares a lapse decrement (a rate-driven to=None exit that is not the mortality one);

  • a transfer record for each declared inter-state transition (to set to a different state), the ModelPoints occupancy moving from from_state to to_state.

The per-transition sum at risk reads this for its axis order and its descriptors; it emits a row for the transitions a given book actually exercises (so a book with a zero decrement carries no row for it), keeping this list a structural superset of that book’s axis.

classmethod from_preset(name)[소스]#

Return the bundled model registered under name.

A non-programmer actuary can pick a topology by name – in the segments sheet’s state_machine column, or in Python via Model.from_preset("ACTIVE_WAIVER"). The preset key lists the transient states in state-index order (ACTIVE_WAIVER = active + waiver; ACTIVE_WAIVER_PAIDUP = active + waiver + paid-up). Users with a topology outside the registry build their own Model.

매개변수:

name (str)

반환 형식:

Model

classmethod presets()[소스]#

The available from_preset() names, in sorted order.

반환 형식:

tuple[str, …]

class fastcashflow.multistate.State(name, pays_premium=False, pays_periodic_benefit=False, transitions=(), sojourn_tracking_months=0, periodic_benefit_term_months=0, mortality_rate='mortality', death_benefit_factor=1.0)[소스]#

One transient state of the in-force model.

pays_premium flags a premium-paying state – the level and single premium accrue on the occupancy of the states so flagged. pays_periodic_benefit flags a benefit-paying state – the ModelPoints.disability_income amount is paid each month its occupancy is held (disability income on a disabled state). transitions are the transitions out of the state, held in application order: the competing-decrement convention (see the module docstring) applies each in turn to the survivors of the previous.

sojourn_tracking_months switches the state to a semi-Markov model. When set to D > 0, the engine tracks D monthly cohorts of in-force in this state (cohort 0 entered this month, cohort 1 entered last month, and cohort D - 1 absorbs everyone who has been here D - 1 months or longer). Transitions with sojourn_dependent=True then receive a cohort index and may carry different rates per cohort – the natural way to express recovery, reincidence, exclusion periods, and other duration-since-entry effects. The default 0 keeps the state Markov (a single cohort, identical to the pre-Phase-(c) behaviour).

mortality_rate routes this state’s in-force death decrement to a named rate (default "mortality", the global decrement). A post-diagnosis state can carry an elevated death rate by naming a different rate, supplied via Basis.state_mortality_annual.

periodic_benefit_term_months caps how many months a benefit state pays (0 = unbounded); see the field comment in __post_init__.

death_benefit_factor scales the death-coverage benefit paid for the lives residing in this state (default 1.0 = no change). The aggregate death claim is occupancy-weighted: claim = (sum_s occ[s]*factor[s]) * claim_rate. It multiplies the benefit AMOUNT, not the decrement, so the death count is unchanged. A post-diagnosis state paying a richer death benefit (e.g. 2x after a cancer diagnosis) sets death_benefit_factor=2.0. Supported on the full path only (measure(full=True)); the fast path and the VFA path reject a non-default factor.

A cover that ends after a fixed sojourn (or a guaranteed conversion to another state at a fixed sojourn) is a deterministic Transition(after_sojourn_months=K, to=...)to=None ends the cover, to="active" converts. It is distinct from periodic_benefit_term_months (which stops the payment but keeps the lives in force): a guaranteed-payout state that pays a fixed term then lapses sets periodic_benefit_term_months (pay window) and a Transition(after_sojourn_months=K, to=None) (cover end) together.

매개변수:
  • name (str)

  • pays_premium (bool)

  • pays_periodic_benefit (bool)

  • transitions (tuple[Transition, ...])

  • sojourn_tracking_months (int)

  • periodic_benefit_term_months (int)

  • mortality_rate (str)

  • death_benefit_factor (float)

class fastcashflow.multistate.Transition(rate=None, to=None, pays_lump_sum=False, sojourn_dependent=False, after_sojourn_months=0, at_premium_term=False)[소스]#

One transition out of a state.

rate names an assumption rate – "mortality", "lapse", "waiver_incidence" and so on – evaluated by the engine and supplied to compile_model(). to is the destination state’s name when the transition moves occupancy to another transient state (waiver inception, recovery, reincidence), or None when it removes occupancy from the in-force set entirely (death, lapse).

pays_lump_sum flags a transition that pays a one-off benefit when it fires – the ModelPoints.disability_benefit amount times the transitioning occupancy. It applies only to a transition with a destination; death and diagnosis lump sums stay on the coverage list.

sojourn_dependent flags a semi-Markov transition: the rate depends on the sojourn time in the source state (time since entering it), not just on the policy duration. The source state must have sojourn_tracking_months > 0 – the engine tracks per-cohort occupancy there. The rate function for a duration-dependent transition takes a fourth argument state_duration (months in source state).

after_sojourn_months makes the transition deterministic (probability one) at a fixed sojourn: when a cohort’s sojourn in the source state reaches this many months, all of it moves to to (or leaves the in-force set when to is None). It carries no rate (the move is certain). This expresses a cover that ends after a fixed term (to=None), or a guaranteed conversion to another state (to="active"). At most one deterministic transition per state.

매개변수:
  • rate (str | None)

  • to (str | None)

  • pays_lump_sum (bool)

  • sojourn_dependent (bool)

  • after_sojourn_months (int)

  • at_premium_term (bool)

class fastcashflow.multistate.TransitionRecord(from_state, to_state, kind, from_name, to_name)[소스]#

One transition in a model’s transition structure (its transMat analogue).

Enumerated by Model.transitions – the model-level list of every transition the topology declares, in a stable order, independent of any particular book’s rates (a transition is listed if declared, even where a book sets its rate to zero). It is the descriptor axis the per-transition sum at risk reads for its n_transition order and labels.

from_state / to_state are transient-state indices; to_state is None for an absorbing exit (death / lapse leave the in-force set). kind is "death", "lapse" or "transfer" (an inter-state edge). from_name / to_name label them for display (to_name is the destination state name, or "death" / "lapse").

매개변수:
  • from_state (int)

  • to_state (int | None)

  • kind (str)

  • from_name (str)

  • to_name (str)

fastcashflow.STATE_ACTIVE = 0#

int([x]) -> integer int(x, base=10) -> integer

Convert a number or string to an integer, or return 0 if no arguments are given. If x is a number, return x.__int__(). For floating-point numbers, this truncates towards zero.

If x is not a number or if base is given, then x must be a string, bytes, or bytearray instance representing an integer literal in the given base. The literal can be preceded by ‘+’ or ‘-’ and be surrounded by whitespace. The base defaults to 10. Valid bases are 0 and 2-36. Base 0 means to interpret the base from the string as an integer literal. >>> int(‘0b100’, base=0) 4

fastcashflow.STATE_WAIVER = 1#

int([x]) -> integer int(x, base=10) -> integer

Convert a number or string to an integer, or return 0 if no arguments are given. If x is a number, return x.__int__(). For floating-point numbers, this truncates towards zero.

If x is not a number or if base is given, then x must be a string, bytes, or bytearray instance representing an integer literal in the given base. The literal can be preceded by ‘+’ or ‘-’ and be surrounded by whitespace. The base defaults to 10. Valid bases are 0 and 2-36. Base 0 means to interpret the base from the string as an integer literal. >>> int(‘0b100’, base=0) 4

fastcashflow.STATE_PAIDUP = 2#

int([x]) -> integer int(x, base=10) -> integer

Convert a number or string to an integer, or return 0 if no arguments are given. If x is a number, return x.__int__(). For floating-point numbers, this truncates towards zero.

If x is not a number or if base is given, then x must be a string, bytes, or bytearray instance representing an integer literal in the given base. The literal can be preceded by ‘+’ or ‘-’ and be surrounded by whitespace. The base defaults to 10. Valid bases are 0 and 2-36. Base 0 means to interpret the base from the string as an integer literal. >>> int(‘0b100’, base=0) 4

Stochastic valuation#

fastcashflow.gmm.stochastic(model_points, basis, rate_scenarios)#

Value a portfolio under each economic scenario – the liability distribution.

rate_scenarios is either

  • a 1-D (n_scenarios,) array – one flat annual discount rate per scenario; or

  • a 2-D (n_scenarios, n_time) array – one discount-rate curve per scenario, an annual rate for each projection month.

The portfolio total of every figure is recorded under each scenario, so the distribution – mean, percentiles – can be read from the result. The projection runs once and a single parallel kernel sweeps the scenario axis (see module docstring); the settlement-pattern and cost-of-capital paths fall back to a per-scenario measure loop (full=False for the confidence-level RA, full=True for cost-of-capital, which the fast path does not compute). Cost-of-capital supports flat (1-D) rate_scenarios only.

매개변수:
반환 형식:

StochasticResult

class fastcashflow.StochasticResult(bel, ra, csm, loss_component)[소스]#

Per-scenario portfolio totals from a stochastic valuation.

Each array is (n_scenarios,) – the portfolio total of that figure under each scenario. Read the distribution off with mean() and percentile(), or from the arrays directly.

매개변수:
mean()[소스]#

The mean of each line across the scenarios.

반환 형식:

dict[str, float]

percentile(q)[소스]#

The q-th percentile of each line across the scenarios.

매개변수:

q (float)

반환 형식:

dict[str, float]

Input and output#

fastcashflow.read_model_points(path, coverages=None, calculation_methods=None)[소스]#

Read model points from a parquet, CSV, Excel or feather file.

Reads the portfolio without any basis – the model points and the actuarial basis are separate inputs. The basis enters only at the engine call (measure), which aligns its coverages to the portfolio’s coverage order.

The portfolio is two frames – a policies frame plus a coverages frame:

  • a policies frame (mp_id, issue_age, term_months, optional sex / count / state / issue_class / issue_date / premium / premium_term_months / premium_frequency_months / annuity_frequency_months / contract_boundary_months / product / channel), one row per policy. Any other column is read as a grouping attribute (portfolio_id, profitability_group, risk_class, region, …) into ModelPoints.attributes, for group() / group_of_contracts();

  • a coverages frame (mp_id, coverage, amount, optional premium / waiting / reduction_end / reduction_factor and the benefit step-up / escalation columns step_month / step_factor (a benefit step at a duration) / escalation_annual / escalation_cap (annual compounding growth, capped – the escalating-benefits recipe in the cookbook)), one row per policy x coverage – so per-coverage rules (waiting, reduction and escalation) ride along, which a flat one-row-per-policy file cannot carry.

Pass them as read_model_points(policies, coverages=coverages_path, calculation_methods=...), or as a single .xlsx carrying policies and coverages sheets. The normalised two-frame shape mirrors a policy table joined to a coverage table – the form data arrives in from a policy system. calculation_methods is the company taxonomy file (CSV / parquet / feather / xlsx) – the third side of the split between portfolio (policies + coverages), basis (basis.xlsx) and catalogue (calculation_methods.csv).

The policies frame is the inception-time static spec – issue_age, term, sex, and so on. The in-force closing state (elapsed_months, prior_csm, lock_in_rate) belongs in a separate file read by read_inforce_state(). An elapsed_months column on the policies side is ignored and a UserWarning is emitted; do not encode the as-of date by mixing it into the static spec.

매개변수:
반환 형식:

ModelPoints

fastcashflow.read_vfa_model_points(path, *, calculation_methods=None)[소스]#

Read the account-value base of variable (VFA) contracts from a policies file.

This reads the part measured under the VFA model: the account value and its guarantee floors (GMDB / GMAB), all named policy columns (account_value, minimum_death_benefit, minimum_accumulation_benefit, minimum_crediting_rate). That base carries no coverage-code coverages, so it is a single policies frame. issue_age and term_months are required; the named policy / account columns are read if present.

Protection riders attached to a variable product (death / cancer / hospitalisation rider) are separate coverages, read and measured on their own – a policies + coverages book through read_model_points() (GMM). So a <coverage>_benefit column is rejected here: a coverage encoded as a column is the lossy wide form, and coverages belong in their own frame which can hold the per-coverage waiting / reduction rules a flat column cannot.

매개변수:
반환 형식:

ModelPoints

fastcashflow.read_basis(path)[소스]#

Read the basis workbook into a per-segment BasisRouter.

path is a single basis.xlsx workbook holding both the rate tables and the segment mapping (see the module header for the sheet layout). The segments sheet maps each (product, channel) to which tables it uses plus scalar parameters, with a _DEFAULTS row whose values blank cells inherit; the coverages sheet attaches rate-driven coverages to products.

Returns a BasisRouter keyed by the segment axes – (product, channel) by default, or whatever non-assumption columns the segments sheet declares (one axis, or three); .segment_axes records the axis names so measure() routes without a segment_by argument.

v1: the discount and inflation tables are read but used flat (their first entry); the per-segment BasisRouter is returned for the caller to value segment by segment.

매개변수:

path (Path | str)

반환 형식:

BasisRouter

fastcashflow.read_inforce_policies(path, coverages=None, calculation_methods=None)[소스]#

Read a single combined policies + in-force state file.

A self-contained snapshot at a settlement date – one file per valuation date, one row per surviving contract. Columns combine the permanent contract spec (issue_age, sex, term_months, premiums, benefits, …) and the closing state from the prior period (elapsed_months, count, prior_csm, lock_in_rate). This matches the Korean industry period-close file pattern – one self-contained snapshot per period, no separate state file to keep in sync.

Returns a (ModelPoints, InforceState) tuple. The ModelPoints has the state’s elapsed_months and count already folded in; the InforceState carries prior_csm and lock_in_rate for the period-close settlement call:

mp, state = fcf.read_inforce_policies(
    "inforce_2026Q1.csv",
    coverages="coverages.csv",
    calculation_methods="calculation_methods.csv",
)
movement = fcf.gmm.settle(mp, state, basis, period_months=3)

(For a diagnostics / run-off view without the movement lines, the same inputs feed fastcashflow.gmm.measure_inforce().)

For the two-file equivalent (separate policies.csv + inforce_state.csv), see read_model_points() + read_inforce_state() + apply_inforce_state(). Both workflows produce valuation-ready inputs that give the same valuation: settle and measure_inforce re-align the state by mp_id internally, so the answer does not depend on the state file’s row order. The returned InforceState is itself row-aligned to the model points here; in the two-file path the state object keeps its file order (only the model points are reordered by apply_inforce_state()), so call align_inforce_state() before slicing or reading state.prior_csm directly. Pick the form that fits the company’s extract pipeline.

Required columns: mp_id, elapsed_months, count, prior_csm, lock_in_rate, plus whatever the spec side of read_model_points() needs (issue_age, term_months, optional sex, premiums, <code>_benefit columns for wide form). Optional settlement columns: prior_count (the in-force count at the opening date – both fastcashflow.gmm.settle() and fastcashflow.vfa.settle() need it) and prior_loss_component (the prior period’s closing loss component). Optional VFA state columns: account_value (the observed fund value at the valuation date – it rides on the returned InforceState; the snapshot has no separate inception fund column) and prior_account_value (the prior reporting date’s observed fund value, which vfa.settle needs). Variance / movement analysis (roll_forward(), reconcile()) is unaffected – mp_id-based matching across periods works the same regardless of which reader built each snapshot.

매개변수:
반환 형식:

tuple[ModelPoints, InforceState]

fastcashflow.read_inforce_state(path)[소스]#

Read an in-force state file – the per-MP closing state from the prior reporting period.

The file has one row per model point with columns mp_id, elapsed_months, count, prior_csm and lock_in_rate, plus the optional settlement columns prior_count and prior_loss_component (the prior reporting date’s figures fastcashflow.gmm.settle() / fastcashflow.vfa.settle() need) and the VFA columns account_value (observed fund value at the valuation date) and prior_account_value. Reads .parquet, .csv, .xlsx or .feather / .arrow via _read_frame().

Pair with apply_inforce_state() to join the state onto a ModelPoints built from the static policies file, then settle the period with fastcashflow.gmm.settle() and the returned InforceState (or value the book diagnostically with fastcashflow.gmm.measure_inforce()).

lock_in_rate is required to be uniform across rows in v1 – the engine takes a scalar locked-in rate. Cohort-aware per-MP rates are a future extension; for now the reader errors out if the column is not constant rather than silently dropping the per-row detail.

매개변수:

path (Path | str)

반환 형식:

InforceState

fastcashflow.apply_inforce_state(model_points, state)[소스]#

Return a ModelPoints with the state’s elapsed_months and count substituted in, joined on mp_id (see align_inforce_state() for the join rules).

Note this substitutes only elapsed_months / count onto the model points; the state’s prior_csm rides on the (separately passed) InforceState. measure_inforce() re-aligns that state by mp_id internally, so prior_csm cannot drift out of order.

매개변수:
반환 형식:

ModelPoints

fastcashflow.align_inforce_state(model_points, state)[소스]#

Return state reordered so its rows line up with model_points.

Every per-MP field of the returned state (elapsed_months, count, prior_csm, mp_id) is row-for-row aligned with the model points. When both carry mp_id the match is by mp_id – reordered when the two files are in different orders, and rejected when their id sets differ – so a misaligned period-close file cannot silently assign one contract’s state (including its prior CSM) to another. When the model points have no mp_id (a hand-built set), the rows are taken positionally after a length check; align them yourself in that case.

매개변수:
반환 형식:

InforceState

class fastcashflow.InforceState(mp_id, elapsed_months, count, prior_csm, lock_in_rate, account_value=None, prior_count=None, prior_account_value=None, prior_loss_component=None, profitability=None, actual_premium=None, actual_investment_component=None, actual_claims=None, actual_expenses=None, prior_lic=None)[소스]#

Per-MP closing state from the prior reporting period.

The input layer for in-force / subsequent-measurement workflows. A fresh inforce_state.csv is produced at each period close from the company’s policy administration system and joined onto the static policies.csv to value the in-force at the next reporting date.

Fields:

  • mp_id – join key, matches the mp_id column on the policies file.

  • elapsed_months – months since each contract’s inception as of the valuation date (= valuation date - inception date).

  • count – in-force at the valuation date (the user has already scaled it down for past lapses); seats the projection.

  • prior_csm – closing CSM at month elapsed_months - period_months, the prior reporting date’s result carried into this period.

  • lock_in_rate – annual locked-in discount rate (paragraph B72(b)). Usually scalar; per-MP cohort-aware rates are accepted for GoC-grain settlement, where the portfolio entry validates uniformity inside each group before calling the scalar GMM kernel.

  • account_value – observed per-MP fund value at the valuation date (None for non-VFA states). VFA subsequent measurement (vfa.measure_inforce) re-anchors the account-value path at this observed value; GMM / PAA ignore it. It stays on the state – it does not overwrite the model point’s inception account_value.

  • prior_count – in-force at month elapsed_months - period_months, the prior reporting date (None unless the state feeds vfa.settle). Mirrors prior_csm: a prior-date figure carried on the closing-dated state.

  • prior_account_value – observed per-MP fund value at the prior reporting date (None unless the state feeds vfa.settle).

  • prior_loss_component – closing loss component at the prior reporting date (None means zero). Read by vfa.settle; the paragraph-48/50(b) algebra reverses it on favourable changes before rebuilding the CSM.

  • profitability – optional inception-frozen profitability class used as an explicit group-of-contracts axis at settlement.

  • actual_premium – observed per-MP premium cash actually received over the reporting period (None means as expected). gmm.settle splits the experience adjustment actual_premium - expected_premium between future service (CSM, paragraph B96(a)) and current/past service (P&L, paragraph B97(c)). May be negative (a net refund period, TRG 2018-09 Example B).

  • actual_investment_component – observed per-MP investment component actually paid over the period (surrender values, annuity / maturity repayments – the amounts repaid regardless of an insured event; None means as expected). gmm.settle routes the whole difference expected - actual into the CSM (paragraph B96(c)); investment components do not affect insurance revenue.

  • actual_claims / actual_expenses – observed per-MP claims incurred / expenses incurred over the period (None means as expected). The difference from expected is an experience adjustment relating to past / current service (paragraph B97(b)/(c)): it is recognised in the insurance service result (P&L) and does NOT adjust the CSM. Reported on gmm.settle as claims_experience / expense_experience.

  • prior_lic – closing liability for incurred claims at the prior reporting date (None means reconstruct from the in-force). Required to settle a PAA pure-LIC-runoff period (the opening date at or past the contract boundary): once coverage has ended there is no in-force to scale the LIC by, so the carried balance seeds the run-off. paa.settle’s closing_inputs() carries the period’s lic_closing here.

매개변수:
fastcashflow.read_scenarios(path)[소스]#

Read a stochastic scenario set from a file.

The file is a 2-D table – one row per scenario, one column per projection month, every cell a rate or return. Reads .parquet, .csv, .xlsx or .feather / .arrow via _read_frame().

Returns a numpy float64 array of shape (n_scenarios, n_time), or (n_scenarios,) when the file has a single column (flat-rate scenarios). The result is what measure_stochastic() and measure_tvog() accept as their scenarios / return_scenarios input.

Calibration – Hull-White, Vasicek, regime-switching, climate paths, etc. – is left to a separate scenario-generator step; this reader is just the storage / handover layer. For large scenario sets (thousands of paths) prefer .parquet or .feather over .xlsx.

매개변수:

path (Path | str)

반환 형식:

ndarray[tuple[Any, …], dtype[float64]]

fastcashflow.describe_basis(obj, *, file=None)[소스]#

Print the tree structure of a Basis (or read_basis BasisRouter).

Groups the fields by role – rates, economic / expense, risk adjustment, coverages / coverage types, state machine, other – so a reader can see what is inside the object without scanning every dataclass field.

Pass a single Basis to see one segment, or pass the BasisRouter returned by fastcashflow.io.read_basis() / fastcashflow.io.load_sample_basis() to also see the (product, channel) keys.

반환 형식:

None

fastcashflow.samples.model_points(template='gmm')[소스]#

Bundled sample model points (template="gmm" default, "vfa" for the variable account-value contracts, "ul" for the account-backed universal-life contracts, "ul-annuity" for the universal-life-annuity 2-phase accumulation -> income contracts).

매개변수:

template (str)

fastcashflow.samples.basis(template='gmm')[소스]#

Bundled sample basis. template="gmm" (default) returns the per-segment BasisRouter (a (product, channel) -> Basis mapping); template="vfa" returns the single variable-contract Basis; template="ul" returns the single account-backed universal-life Basis; template="ul-annuity" returns the universal-life-annuity (2-phase accumulation -> income) Basis.

매개변수:

template (str)

fastcashflow.samples.inforce_state()[소스]#

Bundled sample in-force state (elapsed_months / count / prior_csm / …).

fastcashflow.samples.calculation_methods()[소스]#

Bundled sample coverage-code -> calculation-method taxonomy.

fastcashflow.samples.treaty(cession=0.3)[소스]#

Bundled sample reinsurance treaty – a quota share ceding cession of the direct book (default 30%).

A treaty is a parameter object, not a data file, so this is the one reinsurance-specific sample object: the underlying ceded contracts are the same model_points() / basis() portfolio. Pass it to measure() or settle() over a segment of the sample book (reinsurance is measured on a single Basis).

매개변수:

cession (float)

fastcashflow.samples.export(output_dir, template='gmm', format='csv', *, quiet=False)[소스]#

Write a starter set of input template files to output_dir.

template="gmm" writes basis.xlsx plus policies / coverages / calculation_methods / inforce_state and the combined inforce_policies (the period-close one-file form); template="vfa" writes the variable-contract basis.xlsx and policies. Edit them and read back with read_model_points() / read_basis().

format picks the data-file extension – "csv" (default), "parquet", "feather" or "xlsx". The basis is always a multi-sheet .xlsx workbook (it cannot be a flat table), so format applies only to the policies / coverages / state files. Use "parquet" for a portfolio large enough to stream with measure_stream().

Prints a tree of the files written – expanding basis.xlsx into its sheets – so it is clear what landed where. Pass quiet=True to suppress (e.g. in scripts). Returns the destination directory.

매개변수:
반환 형식:

Path

fastcashflow.samples.templates()[소스]#

The available export() / load template names (["gmm", "vfa", "paa", "ul", "ul-annuity", "ul-cost-deduct", "ul-var-annuity"]).

반환 형식:

list[str]

fastcashflow.write_measurement(measurement, path, *, ids=None)[소스]#
fastcashflow.write_measurement(measurement, path, *, ids=None)
fastcashflow.write_measurement(measurement, path, *, ids=None)
fastcashflow.write_measurement(measurement, path, *, ids=None)
fastcashflow.write_measurement(measurement, path, *, ids=None)
fastcashflow.write_measurement(movement, path, *, ids=None)
fastcashflow.write_measurement(movement, path, *, ids=None)
fastcashflow.write_measurement(movement, path, *, ids=None)
fastcashflow.write_measurement(movement, path, *, ids=None)
fastcashflow.write_measurement(measurement, path, *, ids=None)
fastcashflow.write_measurement(settlement, path, *, ids=None)
fastcashflow.write_measurement(settlement, path, *, ids=None)

Write a measurement’s per-model-point headline results to parquet / CSV.

One row per model point, in model-point order. Pass ids for a leading id column so the results join back to policies. Dispatches on the measurement type – GMM writes bel / ra / csm / loss_component, PAA writes lrc / loss_component, VFA adds variable_fee / time_value, reinsurance held writes bel / ra / csm. A mixed-portfolio PortfolioMeasurement writes one file per model present (results.parquet becomes results-gmm.parquet / results-paa.parquet / …), each with an id column joining its rows back to the portfolio. A new model registers its columns with @write_measurement.register in the module that defines its measurement type (so io.py stays free of the engine import).

매개변수:
반환 형식:

None

fastcashflow.gmm.measure_stream(input_path, output_dir, basis, *, coverages=None, calculation_methods=None, chunk_size=20000000, backend='cpu', id_column=None, validate_unique_mp_id=True)[소스]#

Stream a valuation through a parquet file one chunk at a time.

Reads the input in chunks of chunk_size model points, values each chunk with the fused fast path (measure(..., full=False)), and writes the results as a parquet dataset – one part-NNNNN.parquet file per chunk – under output_dir. Peak memory is a single chunk, so this scales past what an in-memory run could hold.

The input is a policies + coverages pair, mirroring read_model_points(): input_path is the policies parquet and coverages the coverages parquet. Each chunk of policies pulls its coverage rows by mp_id, so sorting the coverages file by mp_id lets the parquet reader prune row groups. A flat one-row-per-policy (wide) file is not accepted – it cannot carry the per-coverage waiting and reduction rules.

basis may be a single Basis (uniform portfolio) or a {(product, channel): Basis} dict, exactly as measure. With a dict each chunk routes its model points to their segment’s basis, so the policies parquet must carry product / channel columns.

id_column names the policies column written as the result id (so the output parquet joins back to a business key); it defaults to mp_id. The coverages are always joined on mp_id regardless.

validate_unique_mp_id (default True) scans the whole policies file once up front and rejects a duplicate mp_id – the same data error read_model_points() raises, which a chunk-by-chunk read would otherwise miss when the same id falls in different chunks. Set it False to skip the scan when the upstream extract already guarantees uniqueness.

Returns the total number of model points processed.

매개변수:
반환 형식:

int

fastcashflow.sample_data_dir()[소스]#

Return the on-disk path of the bundled sample data directory.

The directory contains sample_basis.xlsx, sample_policies.csv and sample_coverages.csv – the inputs behind load_sample_basis() and load_sample_model_points(). Use this to open the workbook in Excel and see what a complete fastcashflow input looks like before preparing your own.

반환 형식:

Path

fastcashflow.clear_codegen_cache(*, prune_older_than_days=None)[소스]#

Remove generated kernel sources from the on-disk codegen cache.

Each unique state-machine topology persists a fast_kernel_*.py (or fast_kernel_sm_*.py) source file plus its __pycache__ sidecars. Over a session that explores many topologies these accumulate. This helper sweeps them.

매개변수:

prune_older_than_days (float | None) – Only drop files whose mtime is older than this many days. None (the default) drops everything.

반환:

Number of files removed (sources and __pycache__ artefacts).

반환 형식:

int

참고

Disk-only: modules already imported in the current process continue to work via sys.modules. Codegen for a still-needed topology will transparently regenerate on the next call.

Visualisation#

The plotting helpers use matplotlib, which is included in the standard install. The measurement and reconciliation charts dispatch on the result type – GMM, PAA, VFA and reinsurance held each draw their own model’s quantities (a PAA result draws its LRC and LIC; a reinsurance-held result draws the ceded streams and its possibly-negative CSM). A portfolio container is refused – plot one model slot’s native result instead.

fastcashflow.plot_liability(measurement, *, ax=None, title='Liability components over time')[소스]#
fastcashflow.plot_liability(measurement, *, ax=None, title='Liability components over time')
fastcashflow.plot_liability(measurement, *, ax=None, title='Liability components over time')
fastcashflow.plot_liability(measurement, *, ax=None, title='Reinsurance-held components over time')
fastcashflow.plot_liability(measurement, *, ax=None, title='Liability components over time')

Plot the liability components over the contract’s life.

Dispatches on the measurement type: a GMM, VFA or reinsurance-held measurement draws the BEL, RA and CSM trajectories; a PAA measurement draws the LRC and the LIC (its liability has no BEL / RA / CSM split – and the LRC line excludes the loss component, whose run-off plot_analysis_of_change() shows with component="loss_component"). Each line is the portfolio total of that component at each month. Needs the trajectories, so measure with full=True.

매개변수:
  • ax (Axes | None)

  • title (str)

반환 형식:

Axes

fastcashflow.plot_cashflows(measurement, *, period_months=12, ax=None, title='Projected cash flows')[소스]#
fastcashflow.plot_cashflows(measurement, *, period_months=12, ax=None, title='Projected cash flows')
fastcashflow.plot_cashflows(measurement, *, period_months=12, ax=None, title='Projected cash flows')
fastcashflow.plot_cashflows(measurement, *, period_months=12, ax=None, title='Projected cash flows')
fastcashflow.plot_cashflows(measurement, *, period_months=12, ax=None, title='Projected ceded cash flows')

Plot the projected money in against the money out.

Dispatches on the measurement type. A GMM, PAA or VFA measurement draws premium income against claim and expense outgo; a reinsurance-held measurement draws the ceded streams – recoveries in against reinsurance premiums out. The monthly cash flows are aggregated into buckets of period_months months – a policy year by default. Money in is drawn upward, money out downward, and the marked line is the net cash flow each period. Bucketing keeps a front-loaded month from dominating the chart while the cash-flow shape stays visible.

매개변수:
  • period_months (int)

  • ax (Axes | None)

  • title (str)

반환 형식:

Axes

fastcashflow.plot_csm_runoff(measurement, *, ax=None, title='CSM run-off')[소스]#
fastcashflow.plot_csm_runoff(measurement, *, ax=None, title='CSM run-off')
fastcashflow.plot_csm_runoff(measurement, *, ax=None, title='CSM run-off')
fastcashflow.plot_csm_runoff(measurement, *, ax=None, title='Reinsurance CSM run-off')
fastcashflow.plot_csm_runoff(measurement, *, ax=None, title='CSM run-off')

Plot the contractual service margin running off to zero.

Dispatches on the measurement type. A GMM or VFA measurement draws the unearned profit emerging into the income statement as service is provided; a reinsurance-held measurement draws its net cost or gain amortising – that CSM may be negative, so its axis is not clamped at zero. A PAA measurement is rejected: the PAA carries no CSM.

매개변수:
  • ax (Axes | None)

  • title (str)

반환 형식:

Axes

fastcashflow.plot_risk_adjustment(measurement, basis, *, bands=(0.75, 0.85), ax=None, title='The risk adjustment as a confidence level')[소스]#
fastcashflow.plot_risk_adjustment(measurement, basis, *, bands=(0.75, 0.85), ax=None, title='The risk adjustment as a confidence level')
fastcashflow.plot_risk_adjustment(measurement, basis, *, bands=(0.75, 0.85), ax=None, title='The risk adjustment as a confidence level')
fastcashflow.plot_risk_adjustment(measurement, basis, *, bands=(0.75, 0.85), ax=None, title='The risk adjustment as a confidence level')
fastcashflow.plot_risk_adjustment(measurement, basis, *, bands=(0.75, 0.85), ax=None, title='The risk adjustment as a confidence level')

Plot the risk adjustment as a percentile of the liability distribution.

The confidence-level method models the value arising from non-financial risk as a normal distribution centred on the best estimate; the risk adjustment is the margin from that mean out to a chosen percentile. This chart draws that normal distribution and shades the margin up to each confidence level in bands. Dispatches on the measurement type: a GMM measurement requires a confidence-level basis; a VFA measurement’s RA is always this construct (a confidence-level margin for expense risk), as is a reinsurance-held measurement’s (the margin on the ceded claims – the risk transferred, which reduces the net cost, so its margin shades to the left of the best estimate). A PAA measurement is rejected – the PAA carries no explicit risk adjustment.

매개변수:
반환 형식:

Axes

fastcashflow.plot_analysis_of_change(reconciliation, *, component='csm', ax=None, title=None)[소스]#
fastcashflow.plot_analysis_of_change(reconciliation, *, component='csm', ax=None, title=None)
fastcashflow.plot_analysis_of_change(reconciliation, *, component='csm', ax=None, title=None)
fastcashflow.plot_analysis_of_change(reconciliation, *, component='csm', ax=None, title=None)
fastcashflow.plot_analysis_of_change(reconciliation, *, component='lrc', ax=None, title=None)

Plot one reporting period’s analysis of change as a waterfall.

Dispatches on the reconciliation type. A GMM reconciliation bridges component"bel", "ra" or "csm" – from the opening balance to the closing balance through the future-service, finance and release drivers; a VFA or reinsurance reconciliation through finance and release. A PAA reconciliation selects one of its paragraph-100 blocks: component is "lrc" (the default there), "loss_component" or "lic_path".

A settlement reconciliation (from gmm.settle / vfa.settle via reconcile) has no waterfall arm in v1 and is rejected here; its str() form prints the full paragraph-44 / paragraph-45 table.

매개변수:
  • component (str)

  • ax (Axes | None)

  • title (str | None)

반환 형식:

Axes

fastcashflow.plot_stochastic(result, *, line='bel', ax=None, bins=30, kde=True, title=None)[소스]#

Plot the distribution of a figure across the stochastic scenarios.

line selects "bel", "ra", "csm" or "loss_component". A smooth Gaussian kernel density estimate is drawn over the histogram unless kde is False; the dashed line marks the mean.

매개변수:
반환 형식:

Axes