Source code for annuallife.TradLife_A_EX1.Projection

# modelx: pseudo-python
# This file is part of a modelx model.
# It can be imported as a Python module, but functions defined herein
# are model formulas and may not be executable as standard Python.

"""Per-policy projection, present values and Solvency II life-risk results.

This Space is :mod:`TradLife_A.Projection <annuallife.TradLife_A.Projection>` with the Solvency
II life-risk outputs added and a new inner projection child Space. The
parameters (``idx``, ``scen_id``), the inherited cashflow Cells (from
:mod:`TradLife_A.BaseProj <annuallife.TradLife_A.BaseProj>`) and present-value Cells (from
:mod:`TradLife_A.PV <annuallife.TradLife_A.PV>`) are unchanged; see
:mod:`TradLife_A.Projection <annuallife.TradLife_A.Projection>`.

.. rubric:: New child Space

:mod:`~annuallife.TradLife_A_EX1.Projection.InnerProj` re-runs the
cashflows under a single prescribed life stress, anchored at a valuation
time ``t0``.

Cells Summary
-------------

New Cells
^^^^^^^^^

:func:`risk_life_sub` is the capital requirement for a single life
sub-risk -- the loss in :func:`TradLife_A.PV.pv_net_cf <annuallife.TradLife_A.PV.pv_net_cf>` under
the stress, floored at zero. :func:`risk_life` aggregates the sub-risks
with the correlation matrix from
:func:`~annuallife.TradLife_A_EX1.Assumptions.life_corr`.
:func:`risk_margin` is the Solvency II risk margin: the cost-of-capital
rate :func:`~annuallife.TradLife_A_EX1.Assumptions.coc_rate` applied to
the projected :func:`risk_life`, discounted to ``t``.

.. autosummary::

   ~risk_life_sub
   ~risk_life
   ~risk_margin

"""

from modelx.serialize.jsonvalues import *

_formula = lambda idx, scen_id=1: None

_bases = [
    ".BaseProj",
    ".PV"
]

_allow_none = None

_spaces = [
    "InnerProj"
]

# ---------------------------------------------------------------------------
# Cells

[docs] def risk_life_sub(t, risk): r"""Life underwriting capital requirement for sub-risk ``risk`` at time ``t``. The capital requirement for a single life sub-risk is the loss in the value of in-force business caused by applying the prescribed Solvency II life stress: the baseline present value of net cashflows less the stressed present value, floored at zero. .. math:: \mathrm{risk\_life\_sub}(t, risk) = \max\left(\mathrm{pv\_net\_cf}_{base}(t) - \mathrm{pv\_net\_cf}_{risk}(t),\; 0\right) Both present values are taken at the valuation time ``t`` from the inner projection :mod:`~annuallife.TradLife_A_EX1.Projection.InnerProj`, which is anchored at ``t``. ``InnerProj[t]`` is the unstressed (baseline) run, while ``InnerProj[t, risk]`` applies the life shock selected by ``risk``. For the lapse risk the requirement is the worst loss across the three prescribed lapse shocks (up, down and mass). The individual sub-risk requirements are aggregated into the total life underwriting requirement by :func:`risk_life`. This mirrors ``SCR_life.Life`` (and, for the lapse shocks, ``SCR_life.LapseRisk``) in the ``solvency2`` library, with :func:`TradLife_A.PV.pv_net_cf <annuallife.TradLife_A.PV.pv_net_cf>` standing in for the net asset value. Args: t: Valuation time at which the inner projection is anchored. risk: A ``LifeRiskID`` value selecting the life sub-risk (e.g. ``MORT``, ``LONGV``, ``LAPSE``, ``EXPS``). .. seealso:: * :func:`risk_life` * :func:`TradLife_A.PV.pv_net_cf <annuallife.TradLife_A.PV.pv_net_cf>` * :mod:`~annuallife.TradLife_A_EX1.Projection.InnerProj` """ base_pv = InnerProj[t].pv_net_cf(t) if risk == LifeRiskID.LAPSE: return max( max(base_pv - InnerProj[t, risk, shock].pv_net_cf(t), 0) for shock in (LapseShockID.UP, LapseShockID.DOWN, LapseShockID.MASS)) else: return max(base_pv - InnerProj[t, risk].pv_net_cf(t), 0)
[docs] def risk_life(t): r"""Aggregated life underwriting capital requirement at valuation time ``t``. The individual life sub-risk requirements :func:`risk_life_sub` are combined with the prescribed life-risk correlation matrix: .. math:: \mathrm{risk\_life}(t) = \sqrt{\sum_{i,j} Corr_{i,j}\, \mathrm{risk\_life\_sub}(t, i)\,\mathrm{risk\_life\_sub}(t, j)} where ``i`` and ``j`` range over the life sub-risks and :math:`Corr_{i,j}` is the correlation coefficient between them, supplied per pair by :func:`~annuallife.TradLife_A_EX1.Assumptions.life_corr`. This mirrors ``SCR_life.SCR_life`` in the ``solvency2`` library, parameterized by the valuation time ``t``. The aggregation is kept on native scalar types (a tuple of integer risk codes, with :func:`risk_life_sub` and :func:`~annuallife.TradLife_A_EX1.Assumptions.life_corr` returning :obj:`float`) so the Space stays efficient when compiled with Cython. Args: t: Valuation time at which the inner projections are anchored. .. seealso:: * :func:`risk_life_sub` * :func:`~annuallife.TradLife_A_EX1.Assumptions.life_corr` """ risks = (LifeRiskID.MORT, LifeRiskID.LONGV, LifeRiskID.DISAB, LifeRiskID.LAPSE, LifeRiskID.EXPS, LifeRiskID.REV, LifeRiskID.CAT) return sum(risk_life_sub(t, i) * risk_life_sub(t, j) * asmp.life_corr(i, j) for i in risks for j in risks) ** 0.5
[docs] def risk_margin(t): r"""Solvency II risk margin at valuation time ``t``. The risk margin is the cost of holding the future life underwriting capital over the run-off of the in-force business: the cost-of-capital rate :func:`~annuallife.TradLife_A_EX1.Assumptions.coc_rate` applied to each future aggregated life SCR :func:`risk_life`, discounted to ``t``. .. math:: \mathrm{risk\_margin}(t) = \mathrm{CoC} \sum_{s=t}^{\mathrm{proj\_len}} \frac{\mathrm{risk\_life}(s)} {\prod_{u=t}^{s}\left(1 + \mathrm{disc\_rate}(u)\right)} The cost of capital for the capital held over year ``[s, s+1]`` is taken to be incurred at ``s + 1``, so each ``risk_life(s)`` is discounted by ``s - t + 1`` periods (the standard EIOPA convention). This is evaluated by the recursion .. math:: \mathrm{risk\_margin}(t) = \frac{\mathrm{CoC}\cdot\mathrm{risk\_life}(t) + \mathrm{risk\_margin}(t + 1)} {1 + \mathrm{disc\_rate}(t)} which terminates at ``0`` once ``t`` is beyond :func:`TradLife_A.BaseProj.proj_len <annuallife.TradLife_A.BaseProj.proj_len>`. At ``t = 0`` it is the risk margin at the valuation date. Args: t: Valuation time at which the risk margin is evaluated. .. seealso:: * :func:`risk_life` * :func:`~annuallife.TradLife_A_EX1.Assumptions.coc_rate` """ if t > proj_len(): return 0 else: return (asmp.coc_rate() * risk_life(t) + risk_margin(t + 1)) / (1 + disc_rate(t))
# --------------------------------------------------------------------------- # References LifeRiskID = ("Interface", ("..", "Enums", "LifeRiskID"), "auto") LapseShockID = ("Interface", ("..", "Enums", "LapseShockID"), "auto")