The TradLife_A_EX1 Model#

Example of a nested projection model, illustrated with a Solvency II life-risk SCR and risk margin.

Overview#

The purpose of TradLife_A_EX1 is to demonstrate how to build a nested projection in modelx — a projection that, at each step, runs further inner projections under different assumptions.

Nested projections arise in many situations in actuarial practice.

One example is projecting a stream of book profits under a real-world scenario — for budgeting, embedded or appraisal value reporting, stress testing and the like — when reserves are held on a current, unlocked basis. The reserve at each future point must then be recalculated using assumptions that reflect the state at that point as simulated by the outer real-world scenario. This is the case when book profits are recognized under IFRS 17 or US GAAP.

Another example is the risk-margin calculation under the cost-of-capital method, as in Solvency II, where each future annual projection step requires several inner projections under prescribed stresses to derive the required capital for non-hedgeable risks at that point, and ultimately the risk margin at time 0.

This model takes the latter case as its example, showing how such a nested projection can be implemented in modelx. It is not intended as a complete Solvency II model; the Solvency II life-risk SCR and risk margin serve only as a concrete example to motivate the nested projection.

The model extends TradLife_A. At a valuation time t0 it re-runs the per-policy cashflow projection under each prescribed life stress (the inner projections) and compares the stressed and unstressed present values of net cashflows to obtain:

  • the capital requirement for each life sub-risk (mortality, longevity, lapse and expense),

  • the aggregated life underwriting SCR, and

  • the risk margin.

The inner projections are carried out by a new Space, InnerProj, nested under Projection and anchored at the valuation time t0.

The products, projection logic, assumptions, economic scenarios and input workbook are otherwise the same as TradLife_A. Only the Spaces that change are described below and on their own pages; for everything else, refer to TradLife_A.

How the nested projection is implemented#

To represent a projection that starts a fresh projection at each projection step, a child Space of Projection, InnerProj, is defined. Projection[idx].InnerProj[t0] is the inner projection of model point idx started at time t0.

InnerProj is parameterized with t0, the start time of the inner projection (together with risk and shock, which select the life stress). The whole projection logic is inherited from TradLife_A.BaseProj and TradLife_A.PV; only the Cells specific to the inner projection are overridden.

The inner projection starts from the state of the outer projection at t0. The overridden pols_if() returns the outer projection’s in-force at t == t0 and projects forward for t > t0:

def pols_if(t):
    if t == t0:
        return _space._parent._parent.pols_if(t)
    else:
        return pols_if_beg1(t - 1) - pols_death(t - 1) - pols_lapse(t - 1)

In a Cells formula _space is the current InnerProj[t0, risk, shock] ItemSpace; its parent is the InnerProj Space and that Space’s parent is the enclosing Projection[idx] ItemSpace. So _space._parent._parent is the outer projection, and _space._parent._parent.pols_if(t) reads its in-force at t0. The same idiom is used by the other overridden rate Cells — mort_rate(), lapse_rate() and commissions_ren_pp() — which read the unstressed rate from the outer projection and apply the life shock to it.

The outer Projection reads results back from the inner projection by indexing into the child Space. For example, risk_life_sub() evaluates the present value of net cashflows of the unstressed and stressed inner projections started at t and takes their difference:

def risk_life_sub(t, risk):
    base_pv = InnerProj[t].pv_net_cf(t)
    ...
    return max(base_pv - InnerProj[t, risk].pv_net_cf(t), 0)

InnerProj[t] is the unstressed inner projection started at t and InnerProj[t, risk] the one under the risk stress, with TradLife_A.PV.pv_net_cf evaluated at t on each.

Changes from TradLife_A#

New Spaces

  • InnerProj — an inner projection, parameterized by the valuation time t0 and a (risk, shock) pair, that re-runs the cashflow projection under a single prescribed life stress. It inherits from TradLife_A.BaseProj and TradLife_A.PV and overrides the decrement, mortality, lapse and expense Cells to apply the shock, taking the unstressed rates from the outer projection.

  • Four enum child Spaces under EnumsLifeRiskID (life sub-risks), LapseShockID (lapse up / down / mass), LapseScopeID (retail / non-retail) and ExtraKeyID (extra shock qualifiers).

Updated Spaces

The TradLife_A.BaseProj, TradLife_A.PV, TradLife_A.Economic, TradLife_A.CommTable and TradLife_A.Utilities Spaces are unchanged from TradLife_A; refer to it for them. In particular the life shocks are applied entirely within InnerProj, so TradLife_A.BaseProj needs no modification.

Input workbook

TradLife_A_EX1 reads the same input.xlsx as TradLife_A, with three additional inputs: the LifeShocks and LifeCorr named ranges and a CoCRate row in the ConstParams range.

Model Structure#

Unlike the rest of this page, which documents only the Spaces that change from TradLife_A, this section shows the complete space structure of the model, so the new and updated Spaces can be seen alongside the ones inherited unchanged from TradLife_A. The Status column marks each Space as new, updated or unchanged; the unchanged Spaces link back to TradLife_A.

Space

Status

Description

InputData

Updated

Reads input.xlsx; adds the LifeShocks and LifeCorr readers and the CoCRate constant.

TradLife_A.Economic

Unchanged

Scenario-dependent economic (discount) rates; parameter scen_id.

TradLife_A.BaseProj

Unchanged

Per-period cashflow projection cells; base of Projection and InnerProj.

TradLife_A.PV

Unchanged

Present values of the projected cashflows; base of Projection and InnerProj.

Projection

Updated

Per-model-point projection; adds the life SCR and risk-margin Cells and the InnerProj child Space.

InnerProj

New

Inner projection re-run under a single life stress, anchored at t0; child Space of Projection.

Assumptions

Updated

Per-policy assumptions; adds the life-shock, correlation and cost-of-capital parameters.

PolicyAttrs

Updated

Per-policy attributes; adds the lapse-risk segment.

TradLife_A.Utilities

Unchanged

Helper cells (pandas_to_array, map_to_policies); base of Assumptions and PolicyAttrs.

TradLife_A.CommTable

Unchanged

Commutation functions and actuarial notations; parameters Sex, IntRate and Table.

Enums

Updated

Enum types; adds the life-risk enums (LifeRiskID, LapseShockID, LapseScopeID, ExtraKeyID).

Inheritance#

As in TradLife_A, Projection inherits from TradLife_A.BaseProj and TradLife_A.PV, while Assumptions and PolicyAttrs inherit from TradLife_A.Utilities. The new InnerProj also inherits the same projection logic from TradLife_A.BaseProj and TradLife_A.PV.

        %%{init: {"class": {"hideEmptyMembersBox": true}}}%%
classDiagram
    BaseProj <|-- Projection
    PV <|-- Projection
    BaseProj <|-- InnerProj
    PV <|-- InnerProj
    Utilities <|-- Assumptions
    Utilities <|-- PolicyAttrs
    

Composition#

Besides inheritance, the model nests Spaces as child Spaces. The tree below is rooted at the TradLife_A_EX1 model and includes the Spaces inherited unchanged from TradLife_A; the new inner projection InnerProj is a child Space of Projection. The enum child Spaces under Enums (e.g. LifeRiskID) and the AsmpID enum under Assumptions are omitted from the diagram for clarity.

        %%{init: {"class": {"hideEmptyMembersBox": true}}}%%
classDiagram
    direction TB
    TradLife_A_EX1 *-- InputData
    TradLife_A_EX1 *-- Economic
    TradLife_A_EX1 *-- BaseProj
    TradLife_A_EX1 *-- PV
    TradLife_A_EX1 *-- Projection
    Projection *-- InnerProj
    TradLife_A_EX1 *-- Assumptions
    TradLife_A_EX1 *-- PolicyAttrs
    TradLife_A_EX1 *-- Utilities
    TradLife_A_EX1 *-- CommTable
    TradLife_A_EX1 *-- Enums
    

Cross-space references#

The cross-space References are the same as in TradLife_A: Projection (and, by inheritance, InnerProj) resolve scen, asmp, pol and comm_table to TradLife_A.Economic, Assumptions, PolicyAttrs and TradLife_A.CommTable, which in turn reference InputData as input_data. In addition, InnerProj reads the outer Projection through _space._parent._parent, and the outer projection reads results back from the inner projections.

        graph LR
    Projection -- scen --> Economic
    Projection -- asmp --> Assumptions
    Projection -- pol --> PolicyAttrs
    Projection -- comm_table --> CommTable
    Projection -- risk_life --> InnerProj
    InnerProj -. reads outer .-> Projection
    Economic -- input_data --> InputData
    Assumptions -- input_data --> InputData
    PolicyAttrs -- input_data --> InputData
    CommTable -- input_data --> InputData
    

Basic Usage#

Read and run the model exactly as TradLife_A (see its Basic Usage section), using the TradLife_A_EX1 folder:

>>> import modelx as mx

>>> m = mx.read_model("TradLife_A_EX1")

The Solvency II results are obtained from the Projection Space, for example:

>>> m.Projection[0].risk_life(0)        # aggregated life SCR at t=0

>>> m.Projection[0].risk_margin(0)      # risk margin at t=0

>>> m.Projection[0].risk_life_sub(0, m.Enums.LifeRiskID.MORT)

Global References#

The model-level References (pd, np, ProductID, SexID and RateBasisID) are the same as in TradLife_A.