7 people like it.

Single Life Annuity

A single life annuity function in F# including supporting functions such as probability of survival, pure endowment and discounted interest rate calculation. I've gone for (what I believe to be) a more functional approach than the previous version. I've cobbled together a sort of computation expression type to facilitate transforming the AgeVector. The code below contains test data and sample tests so that you can see how it should be used. If you have any queries or advice about this please contact me on twitter @CdeRoiste . Have fun!

  1: 
  2: 
  3: 
  4: 
  5: 
  6: 
  7: 
  8: 
  9: 
 10: 
 11: 
 12: 
 13: 
 14: 
 15: 
 16: 
 17: 
 18: 
 19: 
 20: 
 21: 
 22: 
 23: 
 24: 
 25: 
 26: 
 27: 
 28: 
 29: 
 30: 
 31: 
 32: 
 33: 
 34: 
 35: 
 36: 
 37: 
 38: 
 39: 
 40: 
 41: 
 42: 
 43: 
 44: 
 45: 
 46: 
 47: 
 48: 
 49: 
 50: 
 51: 
 52: 
 53: 
 54: 
 55: 
 56: 
 57: 
 58: 
 59: 
 60: 
 61: 
 62: 
 63: 
 64: 
 65: 
 66: 
 67: 
 68: 
 69: 
 70: 
 71: 
 72: 
 73: 
 74: 
 75: 
 76: 
 77: 
 78: 
 79: 
 80: 
 81: 
 82: 
 83: 
 84: 
 85: 
 86: 
 87: 
 88: 
 89: 
 90: 
 91: 
 92: 
 93: 
 94: 
 95: 
 96: 
 97: 
 98: 
 99: 
100: 
101: 
// A type specifically for Annuity calculation values
// and some supporting functions.
// Not particularlry necessary but I'm using for completeness
type AnnuityVector = AnnuityVector of Map<int, double>
let toMap = function
    | AnnuityVector (a) -> a
let sum = function
    | AnnuityVector (a) -> 
        a 
        |> Map.toSeq
        |> Seq.fold (fun acc (k,v) -> acc + v) 0.0
let valueAtAge age = function
    | AnnuityVector (a) -> a.[age]


// Discount: Small helper function to calculate the discount rate over a number of years (term)
// due to the net of pension increaes and interest rate.
let discount pensionIncr intr (term : int) =
    ((1.0 + pensionIncr) / (1.0 + intr)) ** (float)term


// Probability of survival: given a number of years and a mortality table tells you how likely the life is to survive
// to the end of that period
// term: a number of years
// mortaltyTable: In this example the mortality table is a table of Qx values (prob of death) 
let probSurvival term mortalityTable =
    // for zero term just intialise a Map
    if term = 0 then
        let survivalCertain = Seq.init 120 (fun idx -> idx, 1.0) 
        survivalCertain |> Map.ofSeq |> AnnuityVector
    else
        // take a window of term values and multiply (1-value) to 
        // get the probability of survival to age+term
        let survival = 
            mortalityTable
            |> Map.toSeq
            |> Seq.windowed term    
            |> Seq.map (fun arr ->  
                let srvProd = 
                    arr 
                    |> Array.fold (fun acc el ->
                                let age, survProd = (fst acc), ((snd acc) * (1.0 - (snd el)))
                                age, survProd) ((fst arr.[0]), 1.0)
                srvProd)
            |> Map.ofSeq |> AnnuityVector
        survival

// Pure endowment: A standard calculation of a pure endowment over a number of years (term)
// intr: a fixed interest rate (a more interesting version would take a time-varying sequence of interest rates)
// penIncr: an assumed fixed increment in pension over time (e.g. index-linkage, again we could assign a time-varying sequence)
// mortalityTable: In this example the mortality table is a table of Qx values (prob of death) 
// term: the term (in years) over which to calculate the endowment's value.
let pureEndowment intr pensionIncr mortalityTable  term =
    let disc = discount pensionIncr intr term
    mortalityTable
    |> probSurvival term
    |> toMap
    |> Map.map (fun k v -> v*disc)
    |> AnnuityVector

// Single Life Annuity calculation: This standard actuarial function accepts;
// intr: a fixed interest rate (a more interesting version would take a time-varying sequence of interest rates)
// penIncr: an assumed fixed increment in pension over time (e.g. index-linkage, again we could assign a time-varying sequence)
// mortalityTable: In this example the mortality table is a table of Qx values (prob of death) 
// age: Age of the life
let singleLifeAnnuity intr penIncr mortalityTable age = 
    seq {
            for term in 1 .. (120 - age) do
                let peAtAge = (pureEndowment intr penIncr mortalityTable term) |> valueAtAge age
                yield peAtAge
        }
    |> Seq.sum
    |> (+) 0.5  // An adjustment term to move the calculation to mid-year, whether this is required is 
                // a property of the particular rules your scheme applies.

// set up mortality table of Qx values
let mort = 
    [(1, 0.0);(2, 0.0);(3, 0.0);(4, 0.0);(5, 0.0);(6, 0.0);(7, 0.0);(8, 0.0);(9, 0.0);(10, 0.0);(11, 0.0);(12, 0.0);
    (13, 0.0);(14, 0.0);(15, 0.0);(16, 0.0);(17, 0.0);(18, 0.0);(19, 0.0);(20, 0.000109);(21, 0.000108);(22, 0.000108);
    (23, 0.000107);(24, 0.000107);(25, 0.000107);(26, 0.000107);(27, 0.000106);(28, 0.000106);(29, 0.000107);(30, 0.000107);
    (31, 0.000107);(32, 0.000108);(33, 0.00011);(34, 0.000112);(35, 0.000115);(36, 0.000118);(37, 0.000122);(38, 0.000127);
    (39, 0.000134);(40, 0.000142);(41, 0.000152);(42, 0.000165);(43, 0.00018);(44, 0.000199);(45, 0.000221);(46, 0.000248);
    (47, 0.000281);(48, 0.00032);(49, 0.000366);(50, 0.000422);(51, 0.000487);(52, 0.000565);(53, 0.000656);(54, 0.000763);
    (55, 0.000889);(56, 0.001036);(57, 0.001206);(58, 0.001404);(59, 0.001633);(60, 0.001897);(61, 0.002323);(62, 0.00283);
    (63, 0.003433);(64, 0.004145);(65, 0.004983);(66, 0.005965);(67, 0.007112);(68, 0.008443);(69, 0.009983);(70, 0.011757);
    (71, 0.013792);(72, 0.016116);(73, 0.01876);(74, 0.021753);(75, 0.02513);(76, 0.028921);(77, 0.033162);(78, 0.037883);
    (79, 0.043118);(80, 0.048897);(81, 0.05525);(82, 0.062203);(83, 0.069779);(84, 0.077998);(85, 0.086875);(86, 0.096421);
    (87, 0.106639);(88, 0.117529);(89, 0.129081);(90, 0.141281);(91, 0.154106);(92, 0.167526);(93, 0.181505);(94, 0.195998);
    (95, 0.210957);(96, 0.226322);(97, 0.242034);(98, 0.258024);(99, 0.274221);(100, 0.290551);(101, 0.306936);(102, 0.323298);
    (103, 0.339556);(104, 0.355633);(105, 0.371449);(106, 0.386926);(107, 0.401992);(108, 0.416574);(109, 0.430603);
    (110, 0.444014);(111, 0.453033);(112, 0.461297);(113, 0.46878);(114, 0.475459);(115, 0.481313);(116, 0.486326);
    (117, 0.490484);(118, 0.493776);(119, 0.496194);(120, 1.0)]
    |> Map.ofList

// finally let's test the above for a number of ages:
printf "SingleLifeAnnuity data @ 18: %A\n" (singleLifeAnnuity 0.03 0.0 mort 18)
printf "SingleLifeAnnuity data @ 20: %A\n" (singleLifeAnnuity 0.03 0.0 mort 20)
printf "SingleLifeAnnuity data @ 33: %A\n" (singleLifeAnnuity 0.03 0.0 mort 33)
printf "SingleLifeAnnuity data @ 45: %A\n" (singleLifeAnnuity 0.03 0.0 mort 45)
printf "SingleLifeAnnuity data @ 56: %A\n" (singleLifeAnnuity 0.03 0.0 mort 56)
printf "SingleLifeAnnuity data @ 65: %A\n" (singleLifeAnnuity 0.03 0.0 mort 65)
Multiple items
union case AnnuityVector.AnnuityVector: Map<int,double> -> AnnuityVector

--------------------
type AnnuityVector = | AnnuityVector of Map<int,double>

Full name: Script.AnnuityVector
Multiple items
module Map

from Microsoft.FSharp.Collections

--------------------
type Map<'Key,'Value (requires comparison)> =
  interface IEnumerable
  interface IComparable
  interface IEnumerable<KeyValuePair<'Key,'Value>>
  interface ICollection<KeyValuePair<'Key,'Value>>
  interface IDictionary<'Key,'Value>
  new : elements:seq<'Key * 'Value> -> Map<'Key,'Value>
  member Add : key:'Key * value:'Value -> Map<'Key,'Value>
  member ContainsKey : key:'Key -> bool
  override Equals : obj -> bool
  member Remove : key:'Key -> Map<'Key,'Value>
  ...

Full name: Microsoft.FSharp.Collections.Map<_,_>

--------------------
new : elements:seq<'Key * 'Value> -> Map<'Key,'Value>
Multiple items
val int : value:'T -> int (requires member op_Explicit)

Full name: Microsoft.FSharp.Core.Operators.int

--------------------
type int = int32

Full name: Microsoft.FSharp.Core.int

--------------------
type int<'Measure> = int

Full name: Microsoft.FSharp.Core.int<_>
Multiple items
val double : value:'T -> float (requires member op_Explicit)

Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.double

--------------------
type double = System.Double

Full name: Microsoft.FSharp.Core.double
val toMap : _arg1:AnnuityVector -> Map<int,double>

Full name: Script.toMap
val a : Map<int,double>
val sum : _arg1:AnnuityVector -> float

Full name: Script.sum
val toSeq : table:Map<'Key,'T> -> seq<'Key * 'T> (requires comparison)

Full name: Microsoft.FSharp.Collections.Map.toSeq
module Seq

from Microsoft.FSharp.Collections
val fold : folder:('State -> 'T -> 'State) -> state:'State -> source:seq<'T> -> 'State

Full name: Microsoft.FSharp.Collections.Seq.fold
val acc : float
val k : int
val v : double
val valueAtAge : age:int -> _arg1:AnnuityVector -> double

Full name: Script.valueAtAge
val age : int
val discount : pensionIncr:float -> intr:float -> term:int -> float

Full name: Script.discount
val pensionIncr : float
val intr : float
val term : int
Multiple items
val float : value:'T -> float (requires member op_Explicit)

Full name: Microsoft.FSharp.Core.Operators.float

--------------------
type float = System.Double

Full name: Microsoft.FSharp.Core.float

--------------------
type float<'Measure> = float

Full name: Microsoft.FSharp.Core.float<_>
val probSurvival : term:int -> mortalityTable:Map<int,float> -> AnnuityVector

Full name: Script.probSurvival
val mortalityTable : Map<int,float>
val survivalCertain : seq<int * float>
val init : count:int -> initializer:(int -> 'T) -> seq<'T>

Full name: Microsoft.FSharp.Collections.Seq.init
val idx : int
val ofSeq : elements:seq<'Key * 'T> -> Map<'Key,'T> (requires comparison)

Full name: Microsoft.FSharp.Collections.Map.ofSeq
val survival : AnnuityVector
val windowed : windowSize:int -> source:seq<'T> -> seq<'T []>

Full name: Microsoft.FSharp.Collections.Seq.windowed
val map : mapping:('T -> 'U) -> source:seq<'T> -> seq<'U>

Full name: Microsoft.FSharp.Collections.Seq.map
val arr : (int * float) []
val srvProd : int * float
module Array

from Microsoft.FSharp.Collections
val fold : folder:('State -> 'T -> 'State) -> state:'State -> array:'T [] -> 'State

Full name: Microsoft.FSharp.Collections.Array.fold
val acc : int * float
val el : int * float
val survProd : float
val fst : tuple:('T1 * 'T2) -> 'T1

Full name: Microsoft.FSharp.Core.Operators.fst
val snd : tuple:('T1 * 'T2) -> 'T2

Full name: Microsoft.FSharp.Core.Operators.snd
val pureEndowment : intr:float -> pensionIncr:float -> mortalityTable:Map<int,float> -> term:int -> AnnuityVector

Full name: Script.pureEndowment
val disc : float
val map : mapping:('Key -> 'T -> 'U) -> table:Map<'Key,'T> -> Map<'Key,'U> (requires comparison)

Full name: Microsoft.FSharp.Collections.Map.map
val singleLifeAnnuity : intr:float -> penIncr:float -> mortalityTable:Map<int,float> -> age:int -> float

Full name: Script.singleLifeAnnuity
val penIncr : float
Multiple items
val seq : sequence:seq<'T> -> seq<'T>

Full name: Microsoft.FSharp.Core.Operators.seq

--------------------
type seq<'T> = System.Collections.Generic.IEnumerable<'T>

Full name: Microsoft.FSharp.Collections.seq<_>
val peAtAge : double
val sum : source:seq<'T> -> 'T (requires member ( + ) and member get_Zero)

Full name: Microsoft.FSharp.Collections.Seq.sum
val mort : Map<int,float>

Full name: Script.mort
val ofList : elements:('Key * 'T) list -> Map<'Key,'T> (requires comparison)

Full name: Microsoft.FSharp.Collections.Map.ofList
val printf : format:Printf.TextWriterFormat<'T> -> 'T

Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.printf

More information

Link:http://fssnip.net/bp
Posted:12 years ago
Author:Kevin Roche
Tags: actuarial , annuity