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 timet0and a(risk, shock)pair, that re-runs the cashflow projection under a single prescribed life stress. It inherits fromTradLife_A.BaseProjandTradLife_A.PVand 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
Enums—LifeRiskID(life sub-risks),LapseShockID(lapse up / down / mass),LapseScopeID(retail / non-retail) andExtraKeyID(extra shock qualifiers).
Updated Spaces
Projectionadds the Solvency II output Cellsrisk_life_sub()(capital requirement per life sub-risk),risk_life()(aggregated life SCR) andrisk_margin()(risk margin), and gains theInnerProjchild Space.Assumptionsaddslife_shock_param()(life shock factors),life_corr()(life-risk correlation coefficient) andcoc_rate()(cost-of-capital rate).InputDataaddslife_shock_data()andlife_corr_data(), which read theLifeShocksandLifeCorrnamed ranges, reads the newCoCRatevalue fromConstParams, and generalizesget_named_range_as_dict()to support named ranges with multi-column (tuple) keys.PolicyAttrsaddssegment(), the per-policy lapse-risk segment.
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 |
|---|---|---|
Updated |
Reads input.xlsx; adds the |
|
Unchanged |
Scenario-dependent economic (discount) rates; parameter
|
|
Unchanged |
Per-period cashflow projection cells; base of |
|
Unchanged |
Present values of the projected cashflows; base of |
|
Updated |
Per-model-point projection; adds the life SCR and risk-margin
Cells and the |
|
New |
Inner projection re-run under a single life stress, anchored at
|
|
Updated |
Per-policy assumptions; adds the life-shock, correlation and cost-of-capital parameters. |
|
Updated |
Per-policy attributes; adds the lapse-risk |
|
Unchanged |
Helper cells ( |
|
Unchanged |
Commutation functions and actuarial notations; parameters
|
|
Updated |
Enum types; adds the life-risk enums ( |
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.