Skip to content

Instantly share code, notes, and snippets.

@bobular
Last active April 26, 2018 08:32
Show Gist options
  • Save bobular/914eed165f77a0ee54624a328deeb411 to your computer and use it in GitHub Desktop.
Save bobular/914eed165f77a0ee54624a328deeb411 to your computer and use it in GitHub Desktop.
Same model as previous v3 version but with a step change expected return in the contribution period.
#!/usr/bin/env perl
# -*- mode: cperl -*-
#
# The purpose of this little model is to figure out what kind of
# investment returns the pension fund needs to make in order to meet
# its defined benefits promises.
#
# It assumes a single employee with their own "pot" and a fixed payout
# period. I'm not suggesting euthanasia... Obviously in reality the
# longevity risk is shared by the many thousands of scheme
# members. I'm trying to keep this simple...
#
# Salary progresses through early career to a fixed level until
# retirement. A very simple career-averaged earnings approach is used
# to calculate the pension payout.
#
# This is 100% defined benefit. There is no 55k cap (as found in the
# USS scheme). No in-service death benefits are included.
#
# In v3 - an annual payrise parameter is added, and the lump sum and payout parameters
# are changed to be expressed as annual accrual fractions
#
# In this (v4) version, the expected growth can be different for the first B years. Fixed small bug in annual_increment.
#
# ./pension-model-with-USS-returns.pl 0.23 0.01 10 0.04 35000 20 60000 20 0.02 3/75 1/75 0.02 25
#
# 0.23 = C, fraction of salary contribution total (employer+employee)
# 0.01 = I, expected annual asset investment return (e.g. 1%)
# 10 = B, number of years at investment return rate I
# 0.04 = K, expected investment return after B years (e.g. 4%)
# 35000 = S, starting salary
# 20 = X, number of years working with linear annual salary increments
# 60000 = T, salary after X years of increments
# 20 = Y, number of years working at fixed salary T until retirement
# 0.02 = R, annual above-inflation payrise
# 3/75 = L, lump sum accrual rate
# 1/75 = A, annual pension accrual rate
# 0.02 = J, expected annual asset return in drawdown period*
# 25 = Z, number of years retirement
#
# (* in a large and long-running scheme with good cashflow there is no
# need to de-risk and be more prudent here, but I give the option anyway)
#
# example output shows that USS's projection of 1% return for first 10 years
# followed by 4% for the next 30 years (and then 2% return thereafter to be
# conservative)
# will require
# 15% contributions will fund current benefits:
# 1/75 accrual rate, 3/75 accrual of lump sum
#
#
# ./pension-model-with-USS-returns.pl 0.15 0.01 10 0.04 35000 20 60000 20 0 3/75 1/75 0.02 25
# Starting salary was 35000 and after 20y it was 60000
# Career averaged salary over 40 years is 53437
# Pot is 682301 after 40y contributions at 15% of salary and 1% (then 4% after 10 years) annual growth of pot
# Lump sum payout of 1.6 x career avg salary = 85500 upon retirement
# After 25y retirement paying out 53% of career-averaged salary, the pot (with 2% annual return) is 47994
#
# If you reduce the 0.15 (15% contribution rate) any lower, the pot ends
# up in negative territory
#
use strict;
use warnings;
my ($contrib, $growth1, $growth1_years, $growth2, $start_salary, $years_1, $end_salary, $years_2,
$payrise, $lump_sum_accrual, $accrual, $growth3, $years_retired) = @ARGV;
$lump_sum_accrual = eval $lump_sum_accrual;
$accrual = eval $accrual;
my $pot = 0;
my $salary = $start_salary;
my $annual_increment = ($end_salary-$start_salary)/$years_1;
my $sum_salary = 0; # for calculating career-averaged salary
my $total_years = $years_1+$years_2;
my $year = 0; # year counter used to decide between growth rates 1&2
for (1 .. $years_1) {
$year++;
$pot *= 1+($year <= $growth1_years ? $growth1 : $growth2);
$pot += $salary*$contrib;
$sum_salary += $salary;
$salary += $annual_increment;
$salary *= 1+$payrise;
$annual_increment *= 1+$payrise;
}
for (1 .. $years_2) {
# same as above but without the annual increment
$year++;
$pot *= 1+($year <= $growth1_years ? $growth1 : $growth2);
$pot += $salary*$contrib;
$sum_salary += $salary;
$salary *= 1+$payrise;
}
printf "Starting salary was %d and after %dy it was %d\n",
$start_salary, $years_1, $salary;
my $career_averaged_salary = $sum_salary/$total_years;
printf "Career averaged salary over %d years is %d\n",
$total_years, $career_averaged_salary;
printf "Pot is %d after %dy contributions at %d%% of salary and %d%% (then %d%% after %d years) annual growth of pot\n",
$pot, $total_years, $contrib*100, $growth1*100, $growth2*100, $growth1_years;
my $lump_sum = $career_averaged_salary*$total_years*$lump_sum_accrual;
$pot -= $lump_sum;
printf "Lump sum payout of %.1f x career avg salary = %d upon retirement\n",
$total_years*$lump_sum_accrual, $lump_sum;
for (1 .. $years_retired) {
$pot -= $career_averaged_salary*$total_years*$accrual;
$pot *= 1+$growth3;
}
printf "After %dy retirement paying out %d%% of career-averaged salary, the pot (with %d%% annual return) is %d\n",
$years_retired, 100*$total_years*$accrual, $growth3*100, $pot;
@bobular
Copy link
Author

bobular commented Apr 26, 2018

Late joiners are expensive too...

In a flat CPI+2% growth scenario (wages at CPI+0%), someone joining 10 years before retirement at 50k and progressing to 60k requires 28% contributions on career-averaged basis and 31% contributions in a final salary scheme.

Here's the career-averaged run:

$ ./pension-model-with-USS-returns.pl 0.28 0.02 10 0.02 50000 10 60000 0 0.0 3/75 1/75 0.02 25
Starting salary was 50000 and after 10y it was 60000
Career averaged salary over 10 years is 54500
Pot is 166592 after 10y contributions at 28% of salary and 2% (then 2% after 10 years) annual growth of pot
Lump sum payout of 0.4 x career avg salary = 21800 upon retirement
After 25y retirement paying out 13% of career-averaged salary, the pot (with 2% annual return) is 138

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment