104 lines
2.6 KiB
Python
104 lines
2.6 KiB
Python
"""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)
|