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: 63d9d56a272a4ff689d5143dc82fe60f

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.

225119b436b244d295f2be66e742e975

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.