In this series of posts, we explore how to calculate the capital requirements that make up the Solvency Capital Requirement (SCR) under Solvency II regulation.
In this specific article, we focus on mortality risk. We'll demonstrate how to build a simple actuarial model for a term life insurance product using Python's cashflower package. Our goal is to calculate and compare liabilities under two scenarios: a base scenario (normal mortality assumptions) and a stressed scenario (increased mortality rates).
Mortality stress applies to all insurance products in a company's portfolio that incur losses if mortality rates rise. If certain products, such as annuities, actually benefit from increased mortality, these are excluded from the mortality risk calculations and instead considered under longevity risk.
By the end of this post, you'll clearly understand how mortality risk is assessed according to Solvency II standards, using a practical example.
List of content:
1. Regulation
According to Article 185 of the Delegated Act of Solvency II:
"The capital requirement for mortality risk shall be equal to the loss in basic own funds that would result from an instantaneous permanent increase of 15% in the mortality rates assumed in the calculation of technical provisions."
In simpler terms, insurers need to calculate how much money they would lose if mortality rates suddenly increased by 15%. This loss determines how much capital they must hold to cover mortality risk.
2. Input
To illustrate the calculation clearly, we'll use a straightforward example of a term life insurance product. We detail the inputs step-by-step, including the specific policy data, mortality assumptions, and financial assumptions.
2.1 Model points
In our example, we have three policyholders with the following characteristics:
- Policyholder 1: Male, age 45, coverage of €100,000 for 36 months remaining.
- Policyholder 2: Female, age 37, coverage of €150,000 for 60 months remaining.
- Policyholder 3: Male, age 59, coverage of €130,000 for 42 months remaining.
# input.py
import pandas as pd
from cashflower import ModelPointSet, Runplan
policy = ModelPointSet(data=pd.DataFrame({
"sum_assured": [100_000, 150_000, 130_000],
"remaining_term": [36, 60, 42],
"sex": ["M", "F", "M"],
"age": [45, 37, 59]
}))
2.2 Mortality data
The mortality data used in this example is sourced from the official European Union data portal (data.europa.eu). Specifically, we've filtered the data for Austria (AT) to use realistic and region-specific mortality rates.
assumption = dict()
assumption["mortality"] = pd.read_csv("./input/mortality_data.csv")
2.3 Other assumptions
For simplicity, we assume a constant monthly interest rate of 0.5%. In real-world models, interest rates typically vary over time, but this simplification helps illustrate the mortality risk calculation clearly.
assumption["interest_rate"] = 0.005
2.4 Runplan
We define two scenarios in our runplan:
- Version 1: Base scenario with standard mortality rates.
- Version 2: Stressed scenario where mortality rates are increased by 15% to assess mortality risk.
runplan = Runplan(data=pd.DataFrame({
"version": [1, 2],
"mortality_stress": [1, 1.15]
}))
3. Model
Below is the actuarial model code and a brief description of each variable used:
age(t)
from cashflower import variable
from input import policy, assumption, runplan
from settings import settings
@variable()
def age(t):
if t == 0:
return policy.get("age")
elif t % 12 == 0:
return age(t-1) + 1
else:
return age(t-1)
Tracks the policyholder's age as it increases each year.
mortality_rate(t)
@variable()
def mortality_rate(t):
df = assumption["mortality"]
age_t = f"Y{int(age(t))}" if age(t) < 85 else "Y_GE85"
base_rate = df.loc[(df["sex"] == policy.get("sex")) & (df["age"] == age_t), "OBS_VALUE"].values[0]
return base_rate * runplan.get("mortality_stress")
Calculates the probability of death at a given age, adjusted by the mortality stress factor.
survival_rate(t)
@variable()
def survival_rate(t):
if t == 0:
return 1
return survival_rate(t-1) * (1 - mortality_rate(t))
Computes the likelihood that a policyholder survives up to each time period.
expected_benefit(t)
@variable()
def expected_benefit(t):
if t == 0 or t > policy.get("remaining_term"):
return 0
return survival_rate(t-1) * mortality_rate(t) * policy.get("sum_assured")
Estimates the insurance payout expected in each period based on survival and mortality probabilities.
liabilities(t)
@variable()
def liabilities(t):
if t == settings["T_MAX_CALCULATION"]:
return expected_benefit(t)
return expected_benefit(t) + liabilities(t+1) * 1/(1+assumption["interest_rate"])
Calculates the present value of all future expected benefits, accounting for the interest rate.
4. Run and results
In this section, we run our model using two scenarios and interpret the outcomes.
4.1 Base scenario
To calculate liabilities under normal conditions (base scenario), run:
python run.py --version 1
Result: €43,810
This value represents liabilities calculated using standard mortality assumptions.
4.2 Stressed scenario
To calculate liabilities with increased mortality (stressed scenario), run:
python run.py --version 2
Result: €49,493
Due to the increased mortality rates in this scenario, expected payouts rise, resulting in higher liabilities.
4.3 Capital requirement
The capital requirement for mortality risk is calculated as the difference in liabilities between the stressed scenario and the base scenario:
- BEL in base scenario: €43,810
- BEL in stressed scenario: €49,493
- Difference: €49,493 - €43,810 = €5,683
Thus, the capital requirement due to mortality risk is €5,683. This calculated amount is then used as part of the total Solvency Capital Requirement (SCR).
This wraps up our practical demonstration of mortality risk calculation under Solvency II standards.