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入門(Decimal型を使った生命保険計算)

Posted at

アクチュアリーのためのPython入門(第6回)

Decimal型に切り替えて生命保険計算を書く

はじめに

これまでの回では、

  • 生命表
  • 基数表(Dx, Cx, Nx, Mx)
  • 給付現価
  • 保険料・保険料積立金

を順にPythonで実装してきました。

第5回では、端数処理にDecimal型を使うことを説明しましたが、
実務的には次の疑問が残ります。

「最後の四捨五入だけ Decimal にすれば十分なのか?」

今回はこの疑問に答えるために、
計算の途中からDecimal型に切り替える実装例を示します。

どこから Decimal に切り替えるべきか

生命保険の計算を分解すると、

  1. 死亡率 → 生命表
  2. 生命表 → 基数表(Dx, Cx, Nx, Mx)
  3. 基数表 → 給付現価・収入現価
  4. 給付現価・収入現価 → 保険料・保険料積立金

という流れになります。
このうち、誤差が問題になりやすいのは 4. の部分です。

  • 保険料
  • 保険料積立金

はいずれも
支払現価 − 収入現価」や「割り算」を含み、
ここでfloatの誤差が表に出やすくなります。

そのため本記事では、
基数表・給付現価まではfloat
現価を使って金額を確定させる段階からDecimalに切り替える
という構成を採用します。

Decimal変換の考え方

ポイントは単純で、

  • floatの値を計算途中で無理に丸めない
  • Decimalに切り替えるときはDecimal(str(value))を使う

という2点です。

Decimal(str(value))

とすることで、
floatが内部的に持っている誤差をそのままDecimalに持ち込むのを防ぎます。

途中からDecimal変換をしたコード

先のことから、保険料積立金の**「支払現価 − 収入現価」**の部分から、
Decimal変換をします。全体のコードは次のとおりです。

# 年齢
ages = list(range(40, 50))

# 死亡率(標準生命表2018)
qx = [0.00118,0.00129,0.00140,0.00151,0.00163,
      0.00177,0.00194,0.00214,0.00236,0.00259]

# 初期生存者数
l0 = 100000

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

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

# 利率と現価率
i = 0.006
v = 1 / (1 + i)

# Dx, Cx
Dx = []
Cx = []

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

# Nx, Mx
Nx = []
Mx = []

Dx_sum = 0
Cx_sum = 0

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

Nx = list(reversed(Nx))
Mx = list(reversed(Mx))

# 死亡給付現価
def A1xn(start_age, n):
    age_index = start_age - 40
    return (Mx[age_index] - Mx[age_index + n]) / Dx[age_index]

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

# 純保険料
def netPremT(start_age, n):
    return A1xn(start_age, n) / axn(start_age, n)

# 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)

# 営業保険料
def PremT(start_age, n):
    alpha = 0.001
    delta = 0.06
    gamma = 0.00115
    beta = 0.03
    value = (A1xn(start_age, n) + alpha + gamma * axn(start_age, n)) \
            / ((1 - beta - delta) * axn(start_age, n))
    return roundhu(value, 6)

# 保険料積立金
def ResT(start_age, n, t):
    # floatで現価を計算
    benefit = A1xn(start_age + t, n - t)
    income = netPremT(start_age, n) * axn(start_age + t, n - t)
    # 確定時にDecimalへ
    value = Decimal(str(benefit)) - Decimal(str(income))
    return roundhu(value, 6)

まとめと今後について

ここまで、生命表から始めて、

  • 基数表
  • 給付現価
  • 保険料
  • 保険料積立金
  • 端数処理

までを、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?