0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

アクチュアリーのためのPython入門(計算基礎別の給付現価)

0
Last updated at Posted at 2026-06-27

アクチュアリーのためのPython入門(決算数理編第1回)

計算基礎を加えた給付現価

📚 アクチュアリーのためのPython入門
この記事は決算数理編の一部です。
目次はこちら
逆引きガイドはこちら

はじめに

ここでは決算に関係する計算や
決算システムを再現する内容を勉強していきます。

アクチュアリーの生命保険数学のテキストでは触れられていませんが、
生命保険会社では、保険料や保険料積立金を複数種類計算します。

例えば、今まで保険料積立金は保険料と同じく予定利率0.6%で計算していましたが、
事業年度末の保険料積立金の予定利率は保険業法などで定められていて、
年払契約なら2025年度末では0.25%です。

また、予定死亡率も事業年度末の保険料積立金は、
標準生命表を使って計算することが定められていますが、
営業保険料の予定死亡率は必ずしも標準生命表である必要がありません。

保険料を計算するための計算基礎を保険料計算基礎といいます。
事業年度末の保険料積立金を計算するための計算基礎を
責任準備金計算基礎といいます。

ここでは、計算基礎を次のように設定します。

  • 保険料計算基礎
    • 予定死亡率:標準生命表2018の1.5$\sigma$水準(上限25%)
    • 予定利率:0.6%
  • 責任準備金計算基礎
    • 予定死亡率:標準生命表2018
    • 予定利率:0.25%

上記以外の計算基礎は共通とします。
それと、今までは保険商品を年払いのみを対象としていましたが、
この決算数理編では月払いのみとします。

年払いや半年払いの実務上の解約返戻金は、
生命保険数学のテキストと異なる場合があり、
その計算方法は全社共通ではないからです。

なお、保険料計算基礎の予定死亡率は
こちらのブログ記事で作成しています。

計算基数表

続いて、具体的なコードの記載です。
入門編では、標準生命表だけでしたので、
決算数理編では、1.5$\sigma$の予定死亡率も加えます。
計算基数や給付現価の引数には計算基礎を加えていきます。

まずは共通部分です。
今までとはファイル名を変更して、
core_b.pyとしておきます。

最初に、標準生命表とは別に1.5$\sigma$の予定死亡率を加えます。

core_b.py
# 責任準備金計算基礎を含む
# 年齢の範囲
ages = list(range(0, 114))

# 死亡率(標準生命表2018)
# 男性
qx2M = [0.00081,0.00056,0.00036,0.00022,0.00014,0.0001,0.00009,0.00009,0.00009,0.00009,
      0.0001,0.0001,0.00011,0.00013,0.00017,0.00023,0.0003,0.00038,0.00046,0.00053,
      0.00059,0.00063,0.00066,0.00068,0.00068,0.00067,0.00065,0.00064,0.00064,0.00066,
      0.00068,0.00069,0.0007,0.00072,0.00074,0.00077,0.00083,0.0009,0.00099,0.00109,
      0.00118,0.00129,0.0014,0.00151,0.00163,0.00177,0.00194,0.00214,0.00236,0.00259,
      0.00285,0.00311,0.00337,0.00364,0.00391,0.00422,0.00458,0.005,0.00546,0.00597,
      0.00653,0.00716,0.00785,0.00858,0.00935,0.01015,0.011,0.0119,0.01292,0.01408,
      0.01544,0.01702,0.01886,0.02099,0.02346,0.02637,0.02978,0.03381,0.03853,0.04396,
      0.05006,0.05673,0.06402,0.07235,0.08177,0.09175,0.10269,0.11466,0.12775,0.14204,
      0.1576,0.17453,0.1929,0.21279,0.23426,0.25739,0.28222,0.30878,0.33708,0.3671,
      0.39881,0.4321,0.46686,0.50292,0.54006,0.578,0.61642,0.65494,0.69314,1]
# 女性
qx2F = [0.00078,0.00053,0.00033,0.00019,0.00011,0.00008,0.00008,0.00008,0.00007,0.00007,
      0.00007,0.00007,0.00008,0.0001,0.00012,0.00014,0.00016,0.00019,0.00021,0.00023,
      0.00025,0.00026,0.00027,0.00028,0.00029,0.00029,0.0003,0.00031,0.00032,0.00034,
      0.00037,0.0004,0.00044,0.00049,0.00054,0.00059,0.00065,0.00071,0.00077,0.00083,
      0.00088,0.00093,0.00099,0.00104,0.00112,0.00122,0.00135,0.0015,0.00167,0.00182,
      0.00197,0.00211,0.00225,0.00241,0.00256,0.0027,0.00284,0.003,0.00317,0.00338,
      0.00363,0.00389,0.00414,0.00436,0.00458,0.00484,0.00515,0.00554,0.00603,0.00661,
      0.0073,0.00814,0.00912,0.01026,0.01152,0.01289,0.01443,0.01623,0.0184,0.02101,
      0.02414,0.02778,0.03195,0.03659,0.04249,0.04885,0.05596,0.0639,0.07275,0.08261,
      0.09357,0.10576,0.11928,0.13424,0.15078,0.16901,0.18906,0.21104,0.23506,0.26122,
      0.28959,0.32021,0.3531,0.3882,0.42543,0.46462,0.50554,0.54785,0.59115,0.63494,
      0.67863,0.72158,0.76308,1]

# 死亡率(標準生命表2018の1.5σ)
# 男性
qx15M = [
    0.00078,0.00054,0.00035,0.00021,0.00013,0.0001,0.00009,0.00009,0.00009,0.00009,
    0.00009,0.0001,0.00011,0.00013,0.00016,0.00022,0.00029,0.00037,0.00045,0.00051,
    0.00057,0.00061,0.00064,0.00066,0.00066,0.00065,0.00063,0.00062,0.00062,0.00064,
    0.00065,0.00066,0.00067,0.00069,0.00071,0.00074,0.0008,0.00087,0.00095,0.00104,
    0.00114,0.00124,0.00135,0.00146,0.00157,0.0017,0.00185,0.00203,0.00223,0.00245,
    0.0027,0.00294,0.0032,0.00345,0.00372,0.00402,0.00437,0.00477,0.00521,0.0057,
    0.00625,0.00685,0.00751,0.00822,0.00896,0.00973,0.01054,0.01141,0.01238,0.0135,
    0.01479,0.01631,0.01808,0.02012,0.0225,0.02529,0.02857,0.03245,0.03698,0.04221,
    0.04807,0.05447,0.06146,0.06945,0.07853,0.08807,0.09849,0.10986,0.12226,0.13575,
    0.15041,0.16632,0.18354,0.20215,0.22221,0.24378,0.26691,0.29164,0.31799,0.34595,
    0.3755,0.4066,0.43915,0.47303,0.50807,0.54408,0.5808,0.61793,0.65512,0.69202,
    1
]
qx15F = [
    0.00075,0.00051,0.00032,0.00018,0.00011,0.00008,0.00008,0.00008,0.00007,0.00007,
    0.00006,0.00007,0.00008,0.00009,0.00011,0.00014,0.00016,0.00019,0.00021,0.00023,
    0.00024,0.00025,0.00026,0.00027,0.00028,0.00028,0.00029,0.0003,0.00031,0.00033,
    0.00035,0.00039,0.00043,0.00047,0.00052,0.00057,0.00063,0.00068,0.00074,0.0008,
    0.00085,0.0009,0.00095,0.001,0.00107,0.00117,0.0013,0.00145,0.00161,0.00176,
    0.0019,0.00203,0.00216,0.00229,0.00243,0.00255,0.00268,0.00282,0.00299,0.00319,
    0.00343,0.00367,0.00391,0.00412,0.00433,0.00457,0.00487,0.00524,0.0057,0.00625,
    0.00691,0.00771,0.00865,0.00973,0.01093,0.01224,0.01371,0.01544,0.01751,0.02002,
    0.02301,0.0265,0.0305,0.03495,0.04064,0.04675,0.05357,0.06117,0.06963,0.07905,
    0.08952,0.10113,0.114,0.12823,0.14395,0.16127,0.18031,0.20118,0.22399,0.24883,
    0.27579,0.30493,0.33626,0.36977,0.4054,0.44302,0.48246,0.52344,0.56561,0.60855,
    0.65174,0.6946,0.73648,1
]

続いて、死亡率を2次元の辞書化にして、キーに計算基礎を加えます。
こうした場合、例えば、qx["P"]["M"]で1.5$\sigma$の男性の死亡率を、
qx["V"]["F"]で標準生命表の女性の死亡率を呼び出します。

また、予定利率も保険料計算基礎と、
責任準備金計算基礎で異なるので、
こちらも分けられるように設定します。

あとは、計算基数も計算は同じですが、[base]を加えています。

core_b.py
# 死亡率(P基礎、V基礎)
qx = {"P":{"M":qx15M, "F":qx15F}, "V":{"M":qx2M, "F":qx2F}}
# 予定利率(P基礎、V基礎)
ival = {"P":0.006, "V":0.0025}
# 最終年齢
def omega(base, sex):
    return len(qx[base][sex])

# 辞書の作成
Dx = {}
Cx = {}
Nx = {}
Mx = {}

result = []

# 計算基数表の作成
for base in ("P", "V"):

    # baseごとの辞書を初期化
    Dx[base] = {}
    Cx[base] = {}
    Nx[base] = {}
    Mx[base] = {}

    for sex in ("M","F"):

        # 初期生存者数
        l0 = 100000

        # lx, dx
        lx = [l0]
        dx = []

        # 生命表の作成
        for q in qx[base][sex]:
            next_d = lx[-1] * q
            next_l = lx[-1] - next_d
            lx.append(next_l)
            dx.append(next_d)

        # 利率と現価率
        i = ival[base]
        v = 1 / (1 + i)

        # Dx, Cx
        Dx[base][sex] = []
        Cx[base][sex] = []

        for age, l, d in zip(ages, lx, dx):
            Dx[base][sex].append(l * v ** age)
            Cx[base][sex].append(d * v ** (age + 0.5))  # 即時払(年央近似)

        # Nx, Mx
        Nx[base][sex] = []
        Mx[base][sex] = []

        Dx_sum = 0
        Cx_sum = 0

        for D, C in zip(reversed(Dx[base][sex]), reversed(Cx[base][sex])):
            Dx_sum += D
            Cx_sum += C
            Nx[base][sex].append(Dx_sum)
            Mx[base][sex].append(Cx_sum)

        Nx[base][sex] = list(reversed(Nx[base][sex]))
        Mx[base][sex] = list(reversed(Mx[base][sex]))

給付現価

給付現価についても基本は計算基礎[base]を加えるだけですが、
月払保険料を計算するために、月払期始年金現価も加えておきます。

保険期間の変換parse_termにて、CSVファイルを取り込んで、
ダブルクォーテーション付の数値でも
動作するように少し修正を加えています。

core_b.py
# 死亡給付現価
def A1xn(base, sex, start_age, n):
    return (Mx[base][sex][start_age] - Mx[base][sex][start_age + n]) / Dx[base][sex][start_age]

# 養老給付現価
def Axn(base, sex, start_age, n):
    return (Mx[base][sex][start_age] - Mx[base][sex][start_age + n] + Dx[base][sex][start_age + n]) / Dx[base][sex][start_age]

# 終身給付現価
def A1x(base, sex, start_age):
    return Mx[base][sex][start_age]/Dx[base][sex][start_age]

# 定期年金現価(期始年払)
def axn(base, sex, start_age, n):
    return (Nx[base][sex][start_age] - Nx[base][sex][start_age + n]) / Dx[base][sex][start_age]

# 定期年金現価(期始月払)
def axn12(base, sex, start_age, n):
    a = (Nx[base][sex][start_age] - Nx[base][sex][start_age + n]) / Dx[base][sex][start_age]
    a12 = a - 11/24 * (1- Dx[base][sex][start_age + n] / Dx[base][sex][start_age])
    return a12

# 終身年金現価(期始払)
def ax(base, sex, start_age):
    return Nx[base][sex][start_age] / Dx[base][sex][start_age]

# 保険期間の変換
def parse_term(period, age):
    # 数値の場合はそのまま保険期間
    if isinstance(period, int): # ←isinstanceは整数かどうかの判定
        return period

    # CSVから読み込んだ""文字列
    if isinstance(period, str):
        period = period.strip()      # 前後の空白を除去

        # 数字ならそのまま
        if period.isdigit():
            return int(period)

        # 文字列かつAではじまる場合は保険期間=満期年齢-加入年齢
        # startswithはAではじまっているかの判定
        if period.upper().startswith("A"): 
            maturity_age = int(period[1:])
            term = maturity_age - age
            # 保険期間が0以下ならエラー
            if term <= 0:
                raise ValueError("満期年齢が加入年齢以下です")
            return term

    raise ValueError(f"不正な期間指定: {period}")  

# decimal のインポート
from decimal import Decimal, ROUND_HALF_UP, getcontext

# 四捨五入
def roundhu(value, ndigits):
    getcontext().prec = 28
    d = Decimal(str(value))
    quant = Decimal("1").scaleb(-ndigits)
    return d.quantize(quant, rounding=ROUND_HALF_UP)

ここまでで、共通部分のcore.pyの修正ができました。
次回以降は、各保険商品の計算ファイルendowment.py
term.pywhole.pyを新しく作り直していきます。

まとめ

今回は、計算基礎別の給付現価の計算までできました。
次回は実際の決算で使用する計算基数別の
保険料積立金の計算を再現していきます。


📚 ナビゲーション

次の記事
計算基礎別の保険料積立金

📚 目次
アクチュアリーのためのPython入門

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?