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