moonactuary

    Actuarial library for the China Life Insurance Mortality Table (CL 2010-2013): survivorship, commutation columns, net single and annual premiums, and reserves by three independent methods, with a command line tool and a dependency-free SVG chart renderer.

    actuarial
    actuarial-science
    life-insurance
    life-table
    mortality
    china
    cl2010-2013
    reserves
    Download zip
    Author
    Version
    0.1.1
    License
    Apache-2.0
    Last updated
    3 hours ago
    Downloads
    4

    No dependencies

    #moonactuary

    用 MoonBit 实现的中国人身保险业经验生命表(CL 2010-2013)寿险精算库,以及一个可以把它全部功能跑一遍的命令行工具。

    • 六张经验表(CL1–CL6)的生存函数、换算函数、纯保费与准备金
    • 趸缴纯保费、年缴均衡纯保费,终身 / 定期 / 两全三种形态
    • 准备金三条独立路径:前瞻法、后顾法、Fackler 递推,互为交叉校核
    • 零第三方依赖(仅用 MoonBit 标准库),可在 wasm、wasm-gc、js、native 四个后端构建
    • 内置 SVG 绘图,对数刻度,用于读死亡率与生存曲线

    #项目目标

    中国人身保险业经验生命表(2010-2013)是中国寿险产品定价与法定准备金评估的基准表,但公开可复用的实现并不多,尤其缺少能自我校核的实现。这个项目的目标是:

    1. 把六张表做成可复用的库,生存函数严格复现行内公布的 Lx、Tx、ex;
    2. 让结论可以自证:第一原则恒等式 1 = A_x + d·ä_x 在 6 张表 × 3 利率 × 7 年龄上残差为 0,准备金三条路径互相印证,第 n 年末准备金恰等于满期给付额;
    3. 一条命令复现全部核心路径,任何人拿到仓库都能用 moon run cmd/main demo 看到同样一组数字;
    4. 保持零依赖与多后端,使它可以嵌入 WASM 前端、评估脚本或服务端程序。

    #环境要求

    项要求本项目验证环境
    MoonBit 编译器moonc ≥ 0.10.14v0.10.19+97801ee1b
    构建工具moon 工具链moon 0.1.20261029
    第三方依赖无—

    #安装

    #作为库使用

    moon add hua1104/moonactuary

    #从源码构建命令行工具

    # 安装 MoonBit 工具链(Linux / macOS) curl -fsSL https://cli.moonbitlang.com/install/unix.sh | bash export PATH="$HOME/.moon/bin:$PATH" git clone https://github.com/hua1104/moonactuary.git cd moonactuary moon build --target native # 产物:_build/native/debug/build/cmd/main/main.exe moon build --target all # 同时产出 wasm / wasm-gc / js / native

    不构建也可以直接运行,moon run 会自行编译:

    moon run cmd/main demo

    #快速开始(库)

    仓库里有一个可运行的最小样例,打印的就是下面这段代码的结果:

    $ moon run examples/quickstart CL1 非养老类业务一表(男) 30 岁生存人数 l30 986359 30 岁平均余命 e30 47.25 两全保险趸缴纯保费 A 0.614601 年缴均衡纯保费 P 0.038895 第 10 年末准备金 V 0.437487

    对应代码(examples/quickstart/main.mbt):

    // 取一张经验表,构造(表, 利率)估值对
    let china = @moonactuary.ChinaTable::of_code("CL1").unwrap()
    let table = china.table()
    let v = @moonactuary.Valuation::of(
    table=table,
    interest=@moonactuary.Interest::of(0.025), // 预定利率 2.5%
    )

    // 生存函数
    println(table.lx(30)) // 986359
    println(table.expectation(30)) // 47.25,30 岁男性的平均余命

    // 两全保险趸缴纯保费与年缴均衡纯保费
    println(v.endowment_insurance(30, 20)) // 0.614601
    println(v.annual_premium_endowment(30, 20)) // 0.038895

    // 准备金:前瞻法
    let premium = v.annual_premium_endowment(30, 20)
    println(v.reserve_endowment(30, 20, 10, premium)) // 0.437487,第 10 年末

    #命令行

    moonactuary <命令> [选项]

    命令作用
    tables列出六张经验表及其零岁平均余命
    table打印生命表函数 lx / dx / Lx / Tx / ex
    premium计算趸缴纯保费与年缴均衡纯保费
    compare按相同产品假设对比六张经验表的保费结果
    reserve打印逐年准备金表(前瞻 / 后顾 / Fackler 三列)
    plot生成 SVG 曲线,输出到标准输出
    demo最小演示:一条命令触发全部核心功能路径
    verify一键验收:跨六张表、三档利率、三类产品交叉校核

    选项含义默认
    --table经验表 CL1–CL6CL1
    --age投保年龄30
    --term保险期间(年)20
    --rate预定利率百分数,如 2.5 表示 2.5%2.5
    --product险种 whole(终身)/ term(定期)/ endowment(两全)endowment
    --from --to生命表年龄区间0 105
    --kind曲线 mortality / survivalmortality
    --formatcompare 输出格式 table / csvtable
    --help打印用法—

    #示例

    $ moon run cmd/main tables 中国人身保险业经验生命表(2010-2013) 代码 业务表 e0 e20 e40 e60 ------------------------------------------------------------ CL1 非养老类业务一表(男) 76.42 56.93 37.71 20.03 CL2 非养老类业务一表(女) 81.71 62.05 42.43 23.62 CL3 非养老类业务二表(男) 80.35 60.74 41.38 23.21 CL4 非养老类业务二表(女) 85.44 65.68 45.98 26.94 CL5 养老类业务表(男) 83.13 63.44 43.90 25.34 CL6 养老类业务表(女) 88.13 68.33 48.55 29.30

    $ moon run cmd/main premium --product whole --age 40 --rate 3.0 CL1 · 终身寿险 · 投保年龄 40 · 期间 20 年 · 利率 3.00% ---------------------------------------------------- 趸缴纯保费 0.343820 年缴均衡纯保费 0.015261

    六张表横向比较,并导出 CSV:

    moon run cmd/main compare --product term --age 40 --term 10 --rate 3.5 moon run cmd/main compare --format csv > premiums.csv

    $ moon run cmd/main table --from 30 --to 32 CL1 非养老类业务一表(男) · 生命表函数(基数 1,000,000) ------------------------------------------------------------------------------------ 年龄 qx lx dx Lx Tx ex ------------------------------------------------------------------------------------ 30 0.000797 986359 786 985966 46602129 47.25 31 0.000847 985573 835 985156 45616162 46.28 32 0.000903 984738 889 984294 44631007 45.32

    $ moon run cmd/main reserve --term 5 CL1 · 两全保险 · 投保年龄 30 · 期间 5 年 · 年缴保费 0.185955 ------------------------------------------------------------------ 年度 年龄 前瞻准备金 后顾准备金 Fackler 偏差 ------------------------------------------------------------------ 0 30 0.000000 0.000000 0.000000 0.00 1 31 0.189958 0.189958 0.189958 0.00 2 32 0.384790 0.384790 0.384790 0.00 3 33 0.584638 0.584638 0.584638 0.00 4 34 0.789655 0.789655 0.789655 0.00 5 35 1.000000 1.000000 1.000000 0.00

    $ moon run cmd/main plot --kind mortality > qx.svg # 对数刻度死亡率曲线 $ moon run cmd/main plot --kind survival > lx.svg # 生存人数曲线

    #最小演示(可复现)

    demo 一条命令走完教科书校核、恒等式扫描、保费计算与准备金三条路径:

    $ moon run cmd/main demo moonactuary demo — CL(2010-2013) 寿险精算最小演示 中国人身保险业经验生命表(2010-2013) 代码 业务表 e0 e20 e40 e60 ------------------------------------------------------------ CL1 非养老类业务一表(男) 76.42 56.93 37.71 20.03 CL2 非养老类业务一表(女) 81.71 62.05 42.43 23.62 CL3 非养老类业务二表(男) 80.35 60.74 41.38 23.21 CL4 非养老类业务二表(女) 85.44 65.68 45.98 26.94 CL5 养老类业务表(男) 83.13 63.44 43.90 25.34 CL6 养老类业务表(女) 88.13 68.33 48.55 29.30 【教科书基准校核】CL1 生存函数 参照:l0/d0/L0/T0/e0 = 1000000 / 867 / 999567 / 76420142 / 76.42 年龄 qx lx dx Lx Tx ex 0 0.000867 1000000 867 999567 76420142 76.42 1 0.000615 999133 614 998826 75420575 75.49 2 0.000445 998519 444 998296 74421750 74.53 3 0.000339 998074 338 997905 73423453 73.57 4 0.000280 997736 279 997596 72425548 72.59 5 0.000251 997456 250 997331 71427952 71.61 101 0.476447 1658 790 1263 2409 1.45 105 1.000000 85 85 43 43 0.50 【第一原则恒等式】1 = A_x + d · ä_x 6 张表 × 3 利率 × 7 年龄,残差上限 0.00 【示例保单】CL1 · 两全保险 · 投保年龄 30 · 期间 20 年 · 预定利率 2.50% 趸缴纯保费 0.614601 年缴均衡纯保费 0.038895 【准备金三条路径】前瞻法 / 后顾法 / Fackler 递推 年度 年龄 前瞻法 后顾法 Fackler 偏差 ------------------------------------------------------------------ 0 30 0.000000 0.000000 0.000000 0.00 1 31 0.039102 0.039102 0.039102 0.00 ... 19 49 0.936714 0.936714 0.936714 0.00 20 50 1.000000 1.000000 1.000000 0.00 三条独立路径最大偏差 0.00,第 20 年末准备金为 1.000000(等于满期给付额)

    上文中 ... 处为第 2–18 保单年度,数值逐年递增,命令本身会打印完整的 21 行。

    #一键验收

    评审或维护者可以运行 moon run cmd/main verify,自动对六张经验表、三档利率与三类产品执行恒等式及准备金交叉校核。成功时输出:

    moonactuary acceptance verification 生命表:6 张 CL(2010-2013) 经验表 利率:2.5% / 3.0% / 3.5% 产品:终身寿险 / 定期寿险 / 两全保险 恒等式校核:126 项,最大残差 0.000000000000 准备金校核:1134 项,最大偏差 0.000000000000 PASS:全部核心精算路径交叉校核通过

    CLI 会在计算前检查经验表代码、产品名称、年龄、期间、利率和绘图类型。非法输入会显示明确错误,不会静默替换为默认值或触发数组越界。

    #数据来源与校核

    qx 取自中国精算师协会发布的《中国人身保险业经验生命表(2010-2013)》,六张表分别为非养老类业务一表、二表(男、女)与养老类业务表(男、女)。生存函数在基数 1,000,000 与均匀死亡分布(UDD)假设下递推,Lx、Tx、ex 与教材中公布的 CL1 数值逐行一致(l0/d0/L0/T0/e0 = 1000000 / 867 / 999567 / 76420142 / 76.42)。除 UDD 外,库内还支持死亡力恒定(CFM)与 Balducci 双曲线两种分数年龄假设。

    #项目结构

    . ├── cltables.mbt 六张经验表的 qx 数据与业务分类 ├── lifetable.mbt 生存函数递推、分数年龄假设、概率函数 ├── interest.mbt 利息理论:名义/贴现/力、各类年金 ├── valuation.mbt 换算列 D C M N R S 与生命年金 ├── insurance.mbt 趸缴/年缴纯保费、准备金、变额保险 ├── tables.mbt ChinaTable 枚举与元数据 ├── fmt.mbt 定点小数与对齐排版 ├── svg.mbt 零依赖 SVG 渲染器(支持对数轴) ├── *_test.mbt 黑盒与白盒测试 ├── examples/ │ └── quickstart/ 最小可运行样例 ├── cli/ 命令行前端:参数解析与六个子命令 │ ├── args.mbt │ ├── commands.mbt │ └── run.mbt └── cmd/main/ 可执行文件入口(把 argv 交给 cli,仅此而已)

    #测试

    moon test # 62 项测试 moon coverage analyze # 覆盖率报告

    测试分三层:教科书公布值的逐行校核、与定义式求和的交叉验证(公式与其实现必须错成完全一样才能蒙混过关)、以及边界与退化输入(零利率、空期间、超范围年龄、单点与常量序列)。

    #本地一键验收

    Linux / macOS:

    bash scripts/acceptance.sh

    Windows PowerShell:

    powershell -ExecutionPolicy Bypass -File scripts/acceptance.ps1

    Windows 脚本默认验证 wasm、wasm-gc 和 JavaScript 后端;安装 MSVC Build Tools 后也可以直接运行 moon build --target native。

    #持续集成

    .github/workflows/ci.yml 在每次推送与每个 PR 上执行:

    阶段命令
    检查moon check --target all
    构建moon build --target all(wasm / wasm-gc / js / native)
    测试moon test --target all
    格式moon fmt --check
    接口moon info 后确认 .mbti 无漂移
    冒烟运行 demo / tables / premium / reserve 并核对关键数字

    黑客松评审的快速复现步骤、项目亮点和两分钟演示路线见 HACKATHON.md。 正式项目申报内容见 APPLICATION.md。

    #许可证

    Apache License 2.0。六张经验表的 qx 数据为行业公开发布的标准数据;实现为本项目原创。

    Chart

    pub struct Chart {
    title : String
    subtitle : String
    x_label : String
    y_label : String
    series : Array[Series]
    logarithmic : Bool
    width : Int
    height : Int
    }

    A chart: one or more series sharing axes.

    Chart::logarithmic

    fn Chart::logarithmic(self : Chart) -> Chart

    Draw the vertical axis on a logarithmic scale.

    Chart::new

    fn Chart::new(title : String) -> Chart

    A chart with sensible defaults, all of which can be overridden by rebuilding the value with the builders below.

    Chart::render

    fn Chart::render(self : Chart) -> String

    Render the chart to a standalone SVG document.

    Chart::series

    fn Chart::series(self : Chart, name : String, colour : String, points : Array[Point]) -> Chart

    Append a series to the chart.

    Chart::size

    fn Chart::size(self : Chart, width : Int, height : Int) -> Chart

    Override the output pixel size.

    Chart::subtitle

    fn Chart::subtitle(self : Chart, text : String) -> Chart

    Set the chart subtitle.

    Chart::x_label

    fn Chart::x_label(self : Chart, text : String) -> Chart

    Label the horizontal axis.

    Chart::y_label

    fn Chart::y_label(self : Chart, text : String) -> Chart

    Label the vertical axis.

    ChinaTable

    pub enum ChinaTable {
    NonPensionIMale
    NonPensionIFemale
    NonPensionIIMale
    NonPensionIIFemale
    PensionMale
    PensionFemale
    }

    One of the six tables published as the China Life Insurance Mortality Table (2010-2013). The three business lines differ substantially in observed longevity and are mandated for different product types: pension business carries the longest expected lifetimes and so the highest reserves.

    ChinaTable::chinese_name

    fn ChinaTable::chinese_name(self : ChinaTable) -> String

    The Chinese name of the table.

    ChinaTable::code

    fn ChinaTable::code(self : ChinaTable) -> String

    The short code used by the regulator, for example "CL1".

    ChinaTable::english_name

    fn ChinaTable::english_name(self : ChinaTable) -> String

    The English name of the table.

    ChinaTable::is_pension

    fn ChinaTable::is_pension(self : ChinaTable) -> Bool

    Whether the table is intended for pension (养老) business, which exhibits the longest observed lifetimes of the three business lines.

    ChinaTable::of_code

    fn ChinaTable::of_code(code : String) -> ChinaTable?

    Look a table up by its regulator code, accepting "CL1" through "CL6" in either case. all_china_tables remains the way to enumerate them.

    ChinaTable::qx

    fn ChinaTable::qx(self : ChinaTable) -> Array[Double]

    The raw annual mortality rates qx for ages 0 through 105.

    ChinaTable::table

    fn ChinaTable::table(self : ChinaTable, radix? : Double, fraction? : FractionalAge) -> LifeTable

    Build a full LifeTable from this table. The default radix of 1,000,000 reproduces the survivorship columns published alongside CL(2010-2013).

    Commutation

    pub struct Commutation {
    d : Array[Double]
    c : Array[Double]
    m : Array[Double]
    n : Array[Double]
    r : Array[Double]
    s : Array[Double]
    }

    The six classical commutation columns over ages 0 through omega.

    FractionalAge

    pub enum FractionalAge {
    UniformDeaths
    ConstantForce
    Hyperbolic
    }

    An assumption used to interpolate the survivorship function between two consecutive integer ages. The published CL(2010-2013) values of Lx are reproduced exactly by UniformDeaths.

    FractionalAge::parse

    fn FractionalAge::parse(name : String) -> FractionalAge?

    Parse a fractional age assumption by name, accepting the common abbreviations "udd", "cfm" and "balducci" plus their full English names. Used by the command line front end and by callers loading a table from configuration.

    Interest

    pub struct Interest {
    rate : Double
    }

    A compound interest environment described by its effective annual rate i.

    Every other quantity used in actuarial mathematics derives from i: the discount factor v, the effective annual discount rate d, the force of interest delta, and nominal rates convertible m times per year. Modelling these conversions explicitly is what keeps reserve recursion and premium algebra consistent with each other.

    Interest::accumulate

    fn Interest::accumulate(self : Interest, t : Double) -> Double

    Accumulation a(t) = (1 + i)^t from time 0 to time t. Accepts fractional years, and extrapolates backwards for negative t.

    Interest::accumulated_due

    fn Interest::accumulated_due(self : Interest, n : Int) -> Double

    s-double-dot_n: accumulated value at time n of payments of 1 at the start of each of n years.

    Interest::accumulated_immediate

    fn Interest::accumulated_immediate(self : Interest, n : Int) -> Double

    s_n: accumulated value at time n of payments of 1 at the end of each of n years.

    Interest::accumulation_factor

    fn Interest::accumulation_factor(self : Interest) -> Double

    The annual accumulation factor 1 + i.

    Interest::annual

    fn Interest::annual(self : Interest) -> Double

    The effective annual interest rate i.

    Interest::annuity_continuous

    fn Interest::annuity_continuous(self : Interest, n : Int) -> Double

    a-bar_n: present value of an annuity payable continuously at rate 1 per year for n years.

    Interest::annuity_due

    fn Interest::annuity_due(self : Interest, n : Int) -> Double

    a-double-dot_n: present value of an annuity-due paying 1 at the start of each of n years, first payment immediate.

    Interest::annuity_due_mthly

    fn Interest::annuity_due_mthly(self : Interest, n : Int, m : Int) -> Double

    a-double-dot_n^(m): present value of an annuity paying 1/m at the start of each m-th of a year for n years. This is the shape of a monthly premium annuity, and is what Chinese monthly-pay products are valued from.

    Interest::annuity_immediate

    fn Interest::annuity_immediate(self : Interest, n : Int) -> Double

    a_n: present value of an annuity-immediate paying 1 at the end of each of n years. Payments stop at year n.

    Interest::annuity_immediate_mthly

    fn Interest::annuity_immediate_mthly(self : Interest, n : Int, m : Int) -> Double

    a_n^(m): present value of an annuity paying 1/m at the end of each m-th of a year for n years.

    Interest::decreasing_annuity

    fn Interest::decreasing_annuity(self : Interest, n : Int) -> Double

    (Da)_n: present value of a decreasing annuity-immediate paying n at the end of year 1, n-1 at the end of year 2, down to 1 at the end of year n.

    Interest::discount

    fn Interest::discount(self : Interest, t : Double) -> Double

    Discount v^t = (1 + i)^(-t): the value at time 0 of one unit payable at time t.

    Interest::discount_factor

    fn Interest::discount_factor(self : Interest) -> Double

    The annual discount factor v = 1 / (1 + i).

    Interest::discount_rate

    fn Interest::discount_rate(self : Interest) -> Double

    The effective annual discount rate d = i / (1 + i) = i * v.

    Interest::force

    fn Interest::force(self : Interest) -> Double

    The constant force of interest delta = ln(1 + i) equivalent to i.

    Interest::increasing_annuity

    fn Interest::increasing_annuity(self : Interest, n : Int) -> Double

    (Ia)_n: present value of an increasing annuity-immediate paying 1 at the end of year 1, 2 at the end of year 2, up to n at the end of year n.

    Interest::nominal

    fn Interest::nominal(self : Interest, m : Int) -> Double

    The nominal rate i^(m) convertible m times per year. Twelve is the monthly conversion used by Chinese products that collect premiums monthly.

    Interest::nominal_discount

    fn Interest::nominal_discount(self : Interest, m : Int) -> Double

    The nominal discount rate d^(m) convertible m times per year.

    Interest::of

    fn Interest::of(rate : Double) -> Interest

    An interest environment at effective annual rate rate. A rate of 0.025 means 2.5% (预定利率); negative rates are rejected as they have no actuarial reading and make accumulation ill-behaved.

    Interest::of_discount_rate

    fn Interest::of_discount_rate(d : Double) -> Interest

    Given a discount rate d convertible once per year, recover the effective annual interest rate i = d / (1 - d).

    Interest::of_force

    fn Interest::of_force(delta : Double) -> Interest

    Given a constant force of interest delta, recover the effective annual rate i = e^delta - 1.

    Interest::of_nominal

    fn Interest::of_nominal(nominal : Double, m : Int) -> Interest

    Given a nominal rate i^(m) convertible m times per year, recover the effective annual rate. Inverse of nominal.

    Interest::perpetuity

    fn Interest::perpetuity(self : Interest) -> Double

    a_infinity: present value of a perpetuity-immediate paying 1 at the end of every year forever. Only finite when i > 0.

    Interest::perpetuity_due

    fn Interest::perpetuity_due(self : Interest) -> Double

    a-double-dot_infinity: present value of a perpetuity-due paying 1 at the start of every year forever.

    LifeTable

    pub struct LifeTable {
    id : String
    label : String
    omega : Int
    radix : Double
    fraction : FractionalAge
    qx : Array[Double]
    lx : Array[Double]
    dx : Array[Double]
    big_lx : Array[Double]
    big_tx : Array[Double]
    ex : Array[Double]
    }

    A complete life table. Indices run from 0 to limiting_age() inclusive.

    The radix follows the Chinese convention of 1,000,000 lives, matching the published CL(2010-2013) survivorship columns.

    LifeTable::curtate_expectation

    fn LifeTable::curtate_expectation(self : LifeTable, x : Int) -> Double

    The curtate expectation of life(取整余命)at age x: the expected number of whole future years survived, excluding the final partial year. Under the uniform distribution of deaths this sits exactly half a year below the complete expectation.

    LifeTable::death_probability

    fn LifeTable::death_probability(self : LifeTable, x : Int, t : Int) -> Double

    t q x: the probability that a life aged x dies within the next t years.

    LifeTable::deferred_death_probability

    fn LifeTable::deferred_death_probability(self : LifeTable, x : Int, t : Int) -> Double

    t | q x: the probability that a life aged x survives t years and then dies during the following year.

    LifeTable::dx

    fn LifeTable::dx(self : LifeTable, x : Int) -> Double

    Deaths dx occurring between ages x and x + 1.

    LifeTable::expectation

    fn LifeTable::expectation(self : LifeTable, x : Int) -> Double

    The complete expectation of life ex(平均余命)at exact age x, counting fractional years.

    LifeTable::fraction

    fn LifeTable::fraction(self : LifeTable) -> FractionalAge

    The fractional age assumption the table was built under.

    LifeTable::from_qx

    fn LifeTable::from_qx(qx~ : Array[Double], id? : String, label? : String, radix? : Double, fraction? : FractionalAge) -> LifeTable

    Build a life table from annual death probabilities qx, covering ages 0 through qx.length() - 1 inclusive. The final rate must terminate the table: survivors reaching the limiting age all die within that year, so qx[omega] is expected to be 1.0.

    LifeTable::id

    fn LifeTable::id(self : LifeTable) -> String

    The identifier of the table source, for example "CL1".

    LifeTable::label

    fn LifeTable::label(self : LifeTable) -> String

    A human readable name for the table.

    LifeTable::limiting_age

    fn LifeTable::limiting_age(self : LifeTable) -> Int

    The limiting age omega: every life attaining it dies within that year.

    LifeTable::lx

    fn LifeTable::lx(self : LifeTable, x : Int) -> Double

    Survivors lx attaining exact age x.

    LifeTable::person_years

    fn LifeTable::person_years(self : LifeTable, x : Int) -> Double

    Person-years Lx lived jointly by survivors of age x over the next year.

    LifeTable::px

    fn LifeTable::px(self : LifeTable, x : Int) -> Double

    The annual survival probability px = 1 - qx.

    LifeTable::qx

    fn LifeTable::qx(self : LifeTable, x : Int) -> Double

    The annual death probability qx at exact integral age x. Ages past omega report certainty of death; negative ages report zero mortality.

    LifeTable::radix

    fn LifeTable::radix(self : LifeTable) -> Double

    The radix l[0] the table was built from.

    LifeTable::survival

    fn LifeTable::survival(self : LifeTable, x : Int, t : Int) -> Double

    t p x: the probability that a life aged x survives at least t more whole years.

    LifeTable::total_person_years

    fn LifeTable::total_person_years(self : LifeTable, x : Int) -> Double

    Total future person-years Tx yet to be lived by survivors of age x.

    Point

    pub struct Point {
    x : Double
    y : Double
    }

    A single plotted observation.

    Point::of

    fn Point::of(x : Double, y : Double) -> Point

    Construct a point.

    Series

    pub struct Series {
    name : String
    colour : String
    points : Array[Point]
    }

    A labelled run of observations drawn as one polyline.

    Valuation

    pub struct Valuation {
    table : LifeTable
    interest : Interest
    comm : Commutation
    }

    A mortality table paired with an interest assumption. Every actuarial function in this library is a method on Valuation, so premiums and reserves computed together always agree on discounting and mortality.

    Valuation::annual_premium_endowment

    fn Valuation::annual_premium_endowment(self : Valuation, x : Int, n : Int) -> Double

    P_{x:n}: net annual premium for n-year endowment insurance, payable for the full term.

    Valuation::annual_premium_limited

    fn Valuation::annual_premium_limited(self : Valuation, x : Int, m : Int) -> Double

    m P_x: net annual premium for whole life insurance payable for at most m years, after which the policy is paid up. This limited-payment structure is common in Chinese whole life products.

    Valuation::annual_premium_term

    fn Valuation::annual_premium_term(self : Valuation, x : Int, n : Int) -> Double

    P_{x:n}^1: net annual premium for n-year term insurance, payable for the full term.

    Valuation::annual_premium_whole_life

    fn Valuation::annual_premium_whole_life(self : Valuation, x : Int) -> Double

    P_x: net annual premium for whole life insurance, level for life, obtained by the equivalence principle A_x = P * a-double-dot_x.

    Valuation::commutation_c

    fn Valuation::commutation_c(self : Valuation, x : Int) -> Double

    C_x = v^(x+1) d_x.

    Valuation::commutation_d

    fn Valuation::commutation_d(self : Valuation, x : Int) -> Double

    D_x = v^x l_x.

    Valuation::commutation_m

    fn Valuation::commutation_m(self : Valuation, x : Int) -> Double

    M_x = sum of C_k over k >= x.

    Valuation::commutation_n

    fn Valuation::commutation_n(self : Valuation, x : Int) -> Double

    N_x = sum of D_k over k >= x.

    Valuation::commutation_r

    fn Valuation::commutation_r(self : Valuation, x : Int) -> Double

    R_x = sum of M_k over k >= x.

    Valuation::commutation_s

    fn Valuation::commutation_s(self : Valuation, x : Int) -> Double

    S_x = sum of N_k over k >= x.

    Valuation::continuous_annuity

    fn Valuation::continuous_annuity(self : Valuation, x : Int) -> Double

    a-bar_x: a continuous whole life annuity paying at rate 1 per year for as long as (x) survives. Derived from the continuous insurance Ā_x under the UDD assumption using the identity 1 = Ā_x + delta * a-bar_x, with Ā_x = (i / delta) * A_x. Falls back to the discrete annuity when the force of interest degenerates to zero.

    Valuation::continuous_insurance

    fn Valuation::continuous_insurance(self : Valuation, x : Int) -> Double

    A-double-dot_x^(m): whole life insurance paying at the moment of death, converted from the end-of-year benefit under UDD by the factor i / delta.

    Valuation::decreasing_term_insurance

    fn Valuation::decreasing_term_insurance(self : Valuation, x : Int, n : Int) -> Double

    (DA){x:n}^1: decreasing n-year term insurance paying n - k at the end of the year of death, where death falls in the (k+1)-th policy year, so the face amount steps down yearly from n to 1. It relates to the increasing policy by (n + 1) A{x:n}^1 - (IA)_{x:n}^1.

    Valuation::deferred_annuity

    fn Valuation::deferred_annuity(self : Valuation, x : Int, u : Int) -> Double

    u|a-double-dot_x: an annuity-due deferred u years, first payment at age x + u. Equals N_{x+u} / D_x.

    Valuation::deferred_insurance

    fn Valuation::deferred_insurance(self : Valuation, x : Int, u : Int) -> Double

    u|A_x: net single premium for whole life insurance deferred u years, paying nothing on death during the deferral period.

    Valuation::endowment_insurance

    fn Valuation::endowment_insurance(self : Valuation, x : Int, n : Int) -> Double

    A_{x:n}: net single premium for an n-year endowment insurance, which pays 1 either on death within n years or on survival to age x + n. It decomposes into the term insurance plus the pure endowment.

    Valuation::increasing_annuity

    fn Valuation::increasing_annuity(self : Valuation, x : Int) -> Double

    (Ia-double-dot)_x: increasing whole life annuity-due paying 1 in the first year, 2 in the second, and so on. Equals S_x / D_x.

    Valuation::increasing_insurance

    fn Valuation::increasing_insurance(self : Valuation, x : Int) -> Double

    (IA)_x: increasing whole life insurance paying k + 1 at the end of the year of death, where k is the number of complete years survived. Equals R_x / D_x.

    Valuation::increasing_term_insurance

    fn Valuation::increasing_term_insurance(self : Valuation, x : Int, n : Int) -> Double

    (IA){x:n}^1: increasing n-year term insurance paying k + 1 at the end of the year of death, where death falls in the (k+1)-th policy year. Equals (R_x - R{x+n} - n * M_{x+n}) / D_x.

    Valuation::insurance_annuity_identity

    fn Valuation::insurance_annuity_identity(self : Valuation, x : Int) -> Double

    Check the universal identity 1 = A_x + d * a-double-dot_x, which must hold for any valuation regardless of table or interest rate. Returns the residual rather than a Boolean so a caller can judge its own tolerance.

    Valuation::interest

    fn Valuation::interest(self : Valuation) -> Interest

    The interest assumption this valuation was built from.

    Valuation::life_table

    fn Valuation::life_table(self : Valuation) -> LifeTable

    The mortality table this valuation was built from.

    Valuation::limiting_age

    fn Valuation::limiting_age(self : Valuation) -> Int

    The limiting age omega; no life survives past it.

    Valuation::of

    fn Valuation::of(table~ : LifeTable, interest~ : Interest) -> Valuation

    Build the commutation columns for a table and interest pair.

    Valuation::pure_endowment

    fn Valuation::pure_endowment(self : Valuation, x : Int, n : Int) -> Double

    nE_x: net single premium for a pure endowment paying 1 at time n only if (x) survives to age x + n. Equals D_{x+n} / D_x.

    Valuation::reserve_endowment

    fn Valuation::reserve_endowment(self : Valuation, x : Int, n : Int, t : Int, premium : Double) -> Double

    t V: the net level terminal reserve at duration t of an n-year endowment policy, with n - t years remaining at that point.

    Valuation::reserve_retrospective

    fn Valuation::reserve_retrospective(self : Valuation, x : Int, t : Int, premium : Double) -> Double

    t V computed retrospectively: accumulated premiums net of accumulated claims, per survivor, grown to duration t. Must agree with the prospective reserve exactly, which makes it a strong cross-check on both directions.

    Valuation::reserve_schedule_fackler

    fn Valuation::reserve_schedule_fackler(self : Valuation, x : Int, from : Int, to : Int, premium : Double, benefit : Double, opening : Double) -> Array[Double]

    Roll a level net premium reserve forward from duration from to duration to using only the Fackler recursion, starting from opening. Because the recursion never looks past the coming year, running it to the end of the contract and comparing against the prospective reserve validates the entire premium basis: if the premium is wrong, the two will disagree.

    Valuation::reserve_step

    fn Valuation::reserve_step(self : Valuation, held : Double, premium : Double, age : Int, benefit : Double) -> Double

    One step of the Fackler recursion: roll a terminal reserve forward one policy year. Given the reserve V_t held at duration t, a premium collected at the start of year t + 1, mortality q_{x+t} and a unit benefit paid at the end of the year of death,

    (V_t + P) (1 + i) = p_{x+t} V_{t+1} + q_{x+t} b

    so the recursion recovers V_{t+1} without referring to any future year.

    Valuation::reserve_term

    fn Valuation::reserve_term(self : Valuation, x : Int, n : Int, t : Int, premium : Double) -> Double

    t V: the net level terminal reserve at duration t of an n-year term policy.

    Valuation::reserve_whole_life

    fn Valuation::reserve_whole_life(self : Valuation, x : Int, t : Int, premium : Double) -> Double

    t V: the net level terminal reserve at duration t for a whole life policy issued at age x charging net annual premium premium. Computed prospectively as A_{x+t} - P * a-double-dot_{x+t}.

    Valuation::temporary_annuity

    fn Valuation::temporary_annuity(self : Valuation, x : Int, n : Int) -> Double

    a-double-dot_{x:n}: an n-year temporary annuity-due, paying at most n times at the start of each year while (x) survives. Equals (N_x - N_{x+n}) / D_x.

    Valuation::term_insurance

    fn Valuation::term_insurance(self : Valuation, x : Int, n : Int) -> Double

    A_{x:n}^1: net single premium for n-year term insurance, paying 1 at the end of the year of death only if death occurs within n years.

    Valuation::whole_life_annuity

    fn Valuation::whole_life_annuity(self : Valuation, x : Int) -> Double

    a-double-dot_x: expected present value of a whole life annuity-due paying 1 at the start of every year while (x) survives. Equals N_x / D_x.

    Valuation::whole_life_annuity_immediate

    fn Valuation::whole_life_annuity_immediate(self : Valuation, x : Int) -> Double

    a_x: the whole life annuity-immediate, paying at the end of each completed year survived. It is always exactly one payment less than the annuity-due.

    Valuation::whole_life_insurance

    fn Valuation::whole_life_insurance(self : Valuation, x : Int) -> Double

    A_x: net single premium for whole life insurance paying 1 at the end of the year of death. Equals M_x / D_x.

    all_china_tables

    fn all_china_tables() -> Array[ChinaTable]

    All six tables, in publication order.

    cl1_qx

    let cl1_qx : Array[Double]

    CL1(2010-2013) / 非养老类业务一表(男)

    Published by the China Insurance Regulatory Commission (保监发〔2016〕107号, 2016-12-21) as part of the China Life Insurance Mortality Table (2010-2013). Column 1 of the official six-column table; qx at ages 0..105 inclusive, with q_105 = 1 by construction (omega = 105).

    cl2_qx

    let cl2_qx : Array[Double]

    CL2(2010-2013) / 非养老类业务一表(女)

    Published by the China Insurance Regulatory Commission (保监发〔2016〕107号, 2016-12-21) as part of the China Life Insurance Mortality Table (2010-2013). Column 2 of the official six-column table; qx at ages 0..105 inclusive, with q_105 = 1 by construction (omega = 105).

    cl3_qx

    let cl3_qx : Array[Double]

    CL3(2010-2013) / 非养老类业务二表(男)

    Published by the China Insurance Regulatory Commission (保监发〔2016〕107号, 2016-12-21) as part of the China Life Insurance Mortality Table (2010-2013). Column 3 of the official six-column table; qx at ages 0..105 inclusive, with q_105 = 1 by construction (omega = 105).

    cl4_qx

    let cl4_qx : Array[Double]

    CL4(2010-2013) / 非养老类业务二表(女)

    Published by the China Insurance Regulatory Commission (保监发〔2016〕107号, 2016-12-21) as part of the China Life Insurance Mortality Table (2010-2013). Column 4 of the official six-column table; qx at ages 0..105 inclusive, with q_105 = 1 by construction (omega = 105).

    cl5_qx

    let cl5_qx : Array[Double]

    CL5(2010-2013) / 养老类业务表(男)

    Published by the China Insurance Regulatory Commission (保监发〔2016〕107号, 2016-12-21) as part of the China Life Insurance Mortality Table (2010-2013). Column 5 of the official six-column table; qx at ages 0..105 inclusive, with q_105 = 1 by construction (omega = 105).

    cl6_qx

    let cl6_qx : Array[Double]

    CL6(2010-2013) / 养老类业务表(女)

    Published by the China Insurance Regulatory Commission (保监发〔2016〕107号, 2016-12-21) as part of the China Life Insurance Mortality Table (2010-2013). Column 6 of the official six-column table; qx at ages 0..105 inclusive, with q_105 = 1 by construction (omega = 105).

    fixed

    fn fixed(value : Double, places : Int) -> String

    Render value rounded to places decimal places. Negative values keep their sign even when rounding to zero, which matters for small reserves.

    pad_left

    fn pad_left(value : String, width : Int) -> String

    Right-align value in a field width characters wide, growing past it rather than truncating when the content is longer.

    pad_right

    fn pad_right(value : String, width : Int) -> String

    Left-align value in a field width characters wide.

    percent

    fn percent(value : Double, places : Int) -> String

    Render a value as a percentage with places decimal places.

    rule

    fn rule(width : Int, pattern : Char) -> String

    A horizontal rule of width characters of the given pattern. Used for table rules without depending on Unicode box drawing.

    Powered by MoonBit

    Site sourceReport issueModulesSkillsBuild queueStatistics

    © 2026 mooncakes.io