baodan/api/insurance/ppt/irr.py

104 lines
2.6 KiB
Python
Raw Normal View History

2026-07-23 13:10:50 +08:00
"""IRR 精算计算模块 — Modified-Actuarial NPV IRR二分法"""
# HK IA 监管上限
HK_IA_CAP_HKD = 0.06 # HKD 6.0%
HK_IA_CAP_OTHER = 0.065 # 非 HKD 6.5%
def ia_irr_cap(currency: str = "HKD") -> float:
"""HK IA 监管上限。"""
return HK_IA_CAP_HKD if currency.upper() == "HKD" else HK_IA_CAP_OTHER
def compute_irr_ma(
annual_prem: float,
pay_yrs: int,
sv: float,
yr: int,
cap: float = HK_IA_CAP_HKD,
) -> float | None:
"""Modified-Actuarial NPV IRR二分法 200 次迭代)。
Args:
annual_prem: 年缴保费
pay_yrs: 缴费年期
sv: yr 年的退保价值
yr: 保单年度
cap: IRR 上限HK IA 监管
Returns:
IRR 0~cap 之间无法计算返回 None
"""
if yr <= 0 or annual_prem <= 0 or sv <= 0:
return None
def npv(r):
"""计算净现值。"""
if r <= -1:
return float("inf")
total = 0.0
for t in range(1, min(yr, pay_yrs) + 1):
total += annual_prem / ((1 + r) ** t)
# 退保价值折现到第 yr 年
total -= sv / ((1 + r) ** yr)
return total
# 二分法求解
low, high = 0.0, cap
for _ in range(200):
mid = (low + high) / 2
if npv(mid) > 0:
low = mid
else:
high = mid
return round((low + high) / 2, 6)
def compute_irr_ma_withdraw(
annual_prem: float,
pay_yrs: int,
start_yr: int,
annual_wd: float,
sv: float,
yr: int,
cap: float = HK_IA_CAP_HKD,
) -> float | None:
"""含退保场景的 Modified-Actuarial NPV IRR。
Args:
annual_prem: 年缴保费
pay_yrs: 缴费年期
start_yr: 开始退保年度
annual_wd: 每年退保金额
sv: yr 年的退保价值
yr: 保单年度
cap: IRR 上限
Returns:
IRR 无法计算返回 None
"""
if yr <= 0 or annual_prem <= 0:
return None
def npv(r):
if r <= -1:
return float("inf")
total = 0.0
for t in range(1, min(yr, pay_yrs) + 1):
total += annual_prem / ((1 + r) ** t)
# 退保收入折现
for t in range(start_yr, yr + 1):
total -= annual_wd / ((1 + r) ** t)
# 最终退保价值折现
total -= sv / ((1 + r) ** yr)
return total
low, high = -0.01, cap
for _ in range(200):
mid = (low + high) / 2
if npv(mid) > 0:
low = mid
else:
high = mid
return round((low + high) / 2, 6)