Example 1: Simple Annuity
This example demonstrates how to get started using pyprotolinc
. We look at plain vanilla annuity policies already in the payout phase.
To follow along download the repository and change into the subdirectory of the examples folder that corresponds with example 1.
A First Run
[1]:
# This is for Windows, otherwise use "!pwd"
!echo %CD%
D:\programming\pyprotolinc\examples\01_annuity_in_payment_simple
Copy the file results_viewer_generic_template.xslx from the parent directory to this one (and if using version control) rename it to results_viewer_generic.xslx. The directory is laid out as follows:
To start the projection run
pyprotolinc run
After the execution has completed a new csv file should appear in the subdirectory results. A convenient way to examine it is to import the file into the results_viewer_generic.xlsx.
We can also import the results programmatically:
[2]:
import pandas as pd
pd.read_csv("results/ncf_out_generic.csv", index_col=0).head()
[2]:
YEAR | QUARTER | MONTH | PREMIUM | ANNUITY_PAYMENT1 | ANNUITY_PAYMENT2 | DEATH_PAYMENT | DI_LUMPSUM_PAYMENT | RESERVE_BOM(DIS1) | RESERVE_BOM(DEATH) | ... | MV_ACTIVE_DIS1 | MV_ACT_DIS2 | MV_ACT_LAPSED | MV_ACT_MATURED | MV_DIS1_DEATH | MV_DIS1_DIS2 | MV_DIS1_ACT | MV_DIS2_DEATH | MV_DIS2_DIS1 | MV_DIS2_ACT | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 2021 | 4 | 12 | 0.0 | 0.000000 | 0.0 | 0.0 | 0.0 | 0.000000 | 0.0 | ... | 0.0 | 0.0 | 0.0 | 0.0 | 0.000000 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 |
1 | 2022 | 1 | 1 | 0.0 | -100.000000 | 0.0 | 0.0 | 0.0 | 123528.208672 | 0.0 | ... | 0.0 | 0.0 | 0.0 | 0.0 | 0.000125 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 |
2 | 2022 | 1 | 2 | 0.0 | -99.987500 | 0.0 | 0.0 | 0.0 | 123438.496499 | 0.0 | ... | 0.0 | 0.0 | 0.0 | 0.0 | 0.000125 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 |
3 | 2022 | 1 | 3 | 0.0 | -99.975002 | 0.0 | 0.0 | 0.0 | 123348.789350 | 0.0 | ... | 0.0 | 0.0 | 0.0 | 0.0 | 0.000125 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 |
4 | 2022 | 2 | 4 | 0.0 | -99.962505 | 0.0 | 0.0 | 0.0 | 123259.087223 | 0.0 | ... | 0.0 | 0.0 | 0.0 | 0.0 | 0.000125 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 |
5 rows × 27 columns
The interpretation of the outputs should be straight forward, the column ANNUITY_PAYMENT1 shows the ongoing monthly annuity payments due and the column RESERVE_BOM(DIS1) shows the mathematical reserve progression calculated by pyprotolinc.
Having a Look at the Configuration
The subdirectory portfolio contains the portfolio file.
[3]:
import pandas as pd
pd.read_excel("portfolio/portfolio_annuity_small.xlsx")
[3]:
DATE_PORTFOLIO | ID | DATE_OF_BIRTH | DATE_START_OF_COVER | SUM_INSURED | CURRENT_STATUS | SEX | PRODUCT | PRODUCT_PARAMETERS | SMOKERSTATUS | RESERVING_RATE | |
---|---|---|---|---|---|---|---|---|---|---|---|
0 | 2021-12-31 | 1 | 1942-04-23 | 2022-01-01 | 1200 | DIS1 | m | AnnuityInPayment | NaN | U | 0 |
Currently there is just one record in the portfolio file. The attributes should be clear, just some remarks:
DATE_PORTFOLIO is the snapshot date at which the status captured in the file was valid. It should be identical for all policies in the portfolio
SUM_INSURED holds the annual annuity amount. Note that the yearly amount divided by 12 matches the initial annuity amount due as seen in the results output.
CURRENT_STATUS is set to DIS1. This setting links back to the state model which will be discussed below. In this case DIS1 means that the policy is in the payment phase.
RESERVING_RATE is used for discounting when calculating the reserves (and is set to 0 above)
The runs are controlled by two further files, the config.yml and the longevity_assumptions_simple.yml. Let’s have a look at both:
[4]:
with open('config.yml', 'r') as f:
print(f.read())
io:
portfolio_cache: "portfolio/portfolio_cache"
profile_out_dir: "."
model:
type: "GenericMultiState"
years_to_simulate: 119
steps_per_month: 1
use_multicore: false
run_type_specs:
GenericMultiState:
state_model: "AnnuityRunoffStates"
assumptions_spec: "longevity_assumptions_simple.yml"
outfile: "results/ncf_out_generic.csv"
portfolio_path: "portfolio/portfolio_annuity_small.xlsx"
portfolio_chunk_size: 1024
The file config.yml is the main configuration file and pyprotolinc is looking for it by default in the current working directory while it can also be specified as a command line option as can be seen by looking at the CLI output when adding the –help flag:
[5]:
!pyprotolinc run --help
INFO: Showing help with the command 'pyprotolinc run -- --help'.
NAME
pyprotolinc run - Perform a projection run.
SYNOPSIS
pyprotolinc run <flags>
DESCRIPTION
Perform a projection run.
FLAGS
--config_file=CONFIG_FILE
Default: 'config.yml'
--multi_processing_overwrite=MULTI_PROCESSING_OVERWRITE
Type: Optional[]
Default: None
Note that among other settings made here this configuration points to the assumption specification file longevity_assumptions_simple.yml and to the portfolio file. Futhermore, a state model is specified as AnnuityRunoffStates. This state model is an important element for the projector and is mapped to a class in pyprotolinc. The docstring of this class looks like this:
[6]:
from pyprotolinc.models.model_annuity_runoff import AnnuityRunoffStates
print(AnnuityRunoffStates.__doc__)
A state model consisting of two states:
- DIS1 (=0) representing the annuity phase
- DEATH (=1)
When running pyprotolinc the state model is used in conjunction with the product information AnnuityInPayment (which is extracted from the field PRODUCT in the portfolio file) and the assumptions which are specified in the file longevity_assumptions.yml:
[7]:
with open('longevity_assumptions_simple.yml', 'r') as f:
print(f.read())
# for the annuity model
assumptions_spec:
be:
# annuitant's deaths
- [0, 1, ["Scalar", 0.0015]]
res:
# annuitant's deaths
- [0, 1, ["Scalar", 0.0025]]
Here we are specifying the assumptions to be used for the state transitions under the best estimate view (be) and under the reserving view. In this case the projection in the best estimate view specifies assumptions for a state transition as follows:
[0, 1, ["Scalar", 0.0015]]
This encodes the transition from state 0 (=DIS=“in annuity phase”) to 1(=DEATH). In this case the structure of the assumptions is trivial: use use a flat transition rate of 1.5/1000 (2.5/1000 for reserving respectively) independent on risk factors like age or gender.