hscredit.core.financial.basic 源代码

"""基础金融计算函数.

提供货币时间价值(Time Value of Money)相关的基础计算,包括未来值、现值、
每期付款额、期数、利息/本金拆分以及利率反解。函数命名与参数约定对齐 Excel
财务函数与 ``numpy_financial``,所有函数均支持标量与数组(向量化)输入。

**符号约定(现金流方向)**

遵循"现金流入为正、现金流出为负"的通用约定:贷款本金对借款人是流入(正),
每期还款是流出(负);因此 ``pmt`` 等结果常为负值。

**向量化约定**

标量输入返回标量;如需对多组参数批量计算,须将 *所有* 数值参数(含 ``when``)
统一传为等长 numpy 数组,函数对其逐元素并行求解。

**子函数**

- fv: 未来值 Future Value
- pv: 现值 Present Value
- pmt: 每期付款额 Payment
- nper: 期数 Number of Periods
- ipmt: 某期利息部分 Interest Payment
- ppmt: 某期本金部分 Principal Payment
- rate: 每期利率 Rate(牛顿迭代反解)

**引用**

- numpy-financial 文档:https://numpy.org/numpy-financial/latest/
- Excel 财务函数 FV/PV/PMT/NPER/IPMT/PPMT/RATE:
  https://support.microsoft.com/zh-cn/office/fv-函数-2eef9f44-a084-4c61-bdd8-4fe4bb1b71b3
"""

import numpy as np


def _convert_when(when):
    """转换付款时间参数."""
    _when_to_num = {
        'end': 0, 'begin': 1,
        'e': 0, 'b': 1,
        0: 0, 1: 1,
        'beginning': 1,
        'start': 1,
        'finish': 0
    }

    if isinstance(when, np.ndarray):
        return when
    try:
        return _when_to_num[when]
    except (KeyError, TypeError):
        return [_when_to_num[x] for x in when]


[文档] def fv(rate, nper, pmt, pv, when='end'): """计算未来值 (Future Value). 在固定每期利率与等额分期付款条件下,计算一系列现金流在最后一期期末的累计价值。 常用于储蓄/投资终值、定投账户余额测算等场景。 满足等式 ``fv + pv*(1+rate)**nper + pmt*(1+rate*when)/rate*((1+rate)**nper-1) = 0`` (当 ``rate == 0`` 时退化为 ``fv = -(pv + pmt*nper)``)。 :param rate: 每期利率,小数表示(如年利率 5% 按月计息则传 ``0.05/12``) :param nper: 总付款(计息)期数 :param pmt: 每期固定付款额,按现金流方向约定(流出为负,如每月储蓄 -100) :param pv: 现值,即期初一次性金额(流出为负,如初始投入 -100) :param when: 每期付款发生的时点,默认 ``'end'``。可取以下枚举值: - ``'end'`` / ``'e'`` / ``'finish'`` / ``0``:期末付款(普通年金 ordinary annuity),每期现金流在期末发生——最常见 - ``'begin'`` / ``'b'`` / ``'beginning'`` / ``'start'`` / ``1``:期初付款 (预付年金 annuity-due),每期现金流在期初发生,比期末多计一期利息 - 也可传入 numpy 数组对每个元素分别指定时点(向量化) :return: 未来值(与输入现金流方向相反,通常为正表示终值收入);标量入参返回标量, 数组入参返回 numpy 数组 **参考样例** 标量计算(月利率 0.05/12,10 年共 120 期,每月储蓄 -100,初始 -100):: >>> fv(0.05/12, 10*12, -100, -100) 15692.92889433575 期初付款(预付年金,终值更高):: >>> fv(0.05/12, 10*12, -100, -100, when='begin') 15757.629844104778 向量化计算(同时评估多个利率;注意所有数值参数需为等长数组,含 ``when``):: >>> import numpy as np >>> fv(np.array([0.05/12, 0.06/12]), np.array([120, 120]), ... np.array([-100, -100]), np.array([-100, -100]), np.array([0, 0])) array([15692.92889434, 16569.87435405]) **引用** 对应 Excel ``FV`` 函数与 ``numpy_financial.fv``: https://numpy.org/numpy-financial/latest/functions/fv.html """ when = _convert_when(when) rate = np.asarray(rate) nper = np.asarray(nper) pmt = np.asarray(pmt) pv = np.asarray(pv) when = np.asarray(when) if rate.ndim == 0: # 标量情况 if rate == 0: return -(pv + pmt * nper) else: return -(pv * (1 + rate) ** nper + pmt * (1 + rate * when) / rate * ((1 + rate) ** nper - 1)) # 数组情况 result = np.zeros_like(rate) zero_rate = rate == 0 result[zero_rate] = -(pv[zero_rate] + pmt[zero_rate] * nper[zero_rate]) non_zero = ~zero_rate result[non_zero] = -(pv[non_zero] * (1 + rate[non_zero]) ** nper[non_zero] + pmt[non_zero] * (1 + rate[non_zero] * when[non_zero]) / rate[non_zero] * ((1 + rate[non_zero]) ** nper[non_zero] - 1)) return result
# 函数别名:供 ipmt 等内部调用,避免被 ``fv`` 形参遮蔽 _fv = fv
[文档] def pv(rate, nper, pmt, fv=0, when='end'): """计算现值 (Present Value). 在固定每期利率与等额分期付款条件下,将未来各期现金流与终值折算到期初的等价价值。 常用于贷款可借本金测算、未来收益的当前估值等场景。 :param rate: 每期利率,小数表示(如年利率 5% 按月计息则传 ``0.05/12``) :param nper: 总付款(计息)期数 :param pmt: 每期固定付款额,按现金流方向约定(流出为负) :param fv: 未来值,即最后一期期末的目标余额,默认为 ``0`` :param when: 每期付款发生的时点,默认 ``'end'``。可取以下枚举值: - ``'end'`` / ``'e'`` / ``'finish'`` / ``0``:期末付款(普通年金) - ``'begin'`` / ``'b'`` / ``'beginning'`` / ``'start'`` / ``1``:期初付款(预付年金) - 也可传入 numpy 数组对每个元素分别指定时点(向量化) :return: 现值(与未来现金流方向相反,通常为负表示期初支出) **参考样例** 每月收款 100、共 120 期、月利率 0.05/12 时,期初一次性等价价值:: >>> pv(0.05/12, 10*12, -100) 9428.135032823473 向量化计算(所有数值参数需为等长数组):: >>> import numpy as np >>> pv(np.array([0.05/12, 0.06/12]), np.array([120, 120]), ... np.array([-100, -100]), np.array([0, 0]), np.array([0, 0])) array([9428.13503282, 9007.34533272]) **引用** 对应 Excel ``PV`` 函数与 ``numpy_financial.pv``: https://numpy.org/numpy-financial/latest/functions/pv.html """ when = _convert_when(when) rate = np.asarray(rate) nper = np.asarray(nper) pmt = np.asarray(pmt) fv = np.asarray(fv) when = np.asarray(when) if rate.ndim == 0: if rate == 0: return -(fv + pmt * nper) else: return -(fv + pmt * (1 + rate * when) / rate * ((1 + rate) ** nper - 1)) / (1 + rate) ** nper result = np.zeros_like(rate) zero_rate = rate == 0 result[zero_rate] = -(fv[zero_rate] + pmt[zero_rate] * nper[zero_rate]) non_zero = ~zero_rate result[non_zero] = (-(fv[non_zero] + pmt[non_zero] * (1 + rate[non_zero] * when[non_zero]) / rate[non_zero] * ((1 + rate[non_zero]) ** nper[non_zero] - 1)) / (1 + rate[non_zero]) ** nper[non_zero]) return result
[文档] def pmt(rate, nper, pv, fv=0, when='end'): """计算每期付款额 (Payment). 在给定现值、每期利率与期数条件下,计算等额本息分期的每期偿付额。 是评分卡/信贷场景中测算月供的核心函数。 :param rate: 每期利率,小数表示(如年利率 5% 按月计息则传 ``0.05/12``) :param nper: 总付款(计息)期数 :param pv: 现值,即贷款本金或投资额(借款人视角下本金为正流入) :param fv: 未来值,即最后一期期末的目标余额,默认为 ``0`` :param when: 每期付款发生的时点,默认 ``'end'``。可取以下枚举值: - ``'end'`` / ``'e'`` / ``'finish'`` / ``0``:期末付款(普通年金) - ``'begin'`` / ``'b'`` / ``'beginning'`` / ``'start'`` / ``1``:期初付款(预付年金) - 也可传入 numpy 数组对每个元素分别指定时点(向量化) :return: 每期付款额(与本金方向相反,通常为负表示每期支出) **参考样例** 本金 10000、月利率 0.05/12、分 120 期等额本息的月供:: >>> pmt(0.05/12, 10*12, 10000) -106.06551523907554 **引用** 对应 Excel ``PMT`` 函数与 ``numpy_financial.pmt``: https://numpy.org/numpy-financial/latest/functions/pmt.html """ when = _convert_when(when) rate = np.asarray(rate) nper = np.asarray(nper) pv = np.asarray(pv) fv = np.asarray(fv) when = np.asarray(when) if rate.ndim == 0: if rate == 0: return -(fv + pv) / nper else: return -(fv + pv * (1 + rate) ** nper) * rate / \ ((1 + rate * when) * ((1 + rate) ** nper - 1)) result = np.zeros_like(rate) zero_rate = rate == 0 result[zero_rate] = -(fv[zero_rate] + pv[zero_rate]) / nper[zero_rate] non_zero = ~zero_rate result[non_zero] = (-(fv[non_zero] + pv[non_zero] * (1 + rate[non_zero]) ** nper[non_zero]) * rate[non_zero] / ((1 + rate[non_zero] * when[non_zero]) * ((1 + rate[non_zero]) ** nper[non_zero] - 1))) return result
[文档] def nper(rate, pmt, pv, fv=0, when='end'): """计算期数 (Number of Periods). 在给定每期利率、每期付款额与现值条件下,计算达到目标未来值所需的付款期数。 返回值一般为非整数,表示理论上的精确期数。 :param rate: 每期利率,小数表示(如年利率 5% 按月计息则传 ``0.05/12``) :param pmt: 每期固定付款额,按现金流方向约定(流出为负) :param pv: 现值,即初始投资或贷款本金 :param fv: 未来值,即目标余额,默认为 ``0`` :param when: 每期付款发生的时点,默认 ``'end'``。可取以下枚举值: - ``'end'`` / ``'e'`` / ``'finish'`` / ``0``:期末付款(普通年金) - ``'begin'`` / ``'b'`` / ``'beginning'`` / ``'start'`` / ``1``:期初付款(预付年金) - 也可传入 numpy 数组对每个元素分别指定时点(向量化) :return: 达到目标未来值所需的期数(通常为非整数) **参考样例** 本金 10000、每期还 -100、月利率 0.05/12 时所需期数:: >>> nper(0.05/12, -100, 10000) 129.62847166352213 **引用** 对应 Excel ``NPER`` 函数与 ``numpy_financial.nper``: https://numpy.org/numpy-financial/latest/functions/nper.html """ when = _convert_when(when) rate = np.asarray(rate) pmt = np.asarray(pmt) pv = np.asarray(pv) fv = np.asarray(fv) when = np.asarray(when) if rate.ndim == 0: if rate == 0: return -(fv + pv) / pmt else: return (np.log((-fv * rate + pmt * (1 + rate * when)) / (pv * rate + pmt * (1 + rate * when))) / np.log(1 + rate)) result = np.zeros_like(rate) zero_rate = rate == 0 result[zero_rate] = -(fv[zero_rate] + pv[zero_rate]) / pmt[zero_rate] non_zero = ~zero_rate result[non_zero] = (np.log((-fv[non_zero] * rate[non_zero] + pmt[non_zero] * (1 + rate[non_zero] * when[non_zero])) / (pv[non_zero] * rate[non_zero] + pmt[non_zero] * (1 + rate[non_zero] * when[non_zero]))) / np.log(1 + rate[non_zero])) return result
[文档] def ipmt(rate, per, nper, pv, fv=0, when='end'): """计算给定期间的利息部分 (Interest Payment). 将等额本息分期中第 ``per`` 期的还款额拆分出"利息"部分。基于摊销公式: 第 ``per`` 期利息 = 期初剩余本金 × ``rate``。满足 ``ipmt + ppmt == pmt``。 :param rate: 每期利率,小数表示(如年利率 5% 按月计息则传 ``0.05/12``) :param per: 指定期次,取值范围 1 ~ ``nper``(第 1 期至第 ``nper`` 期) :param nper: 总付款(计息)期数 :param pv: 现值,即贷款本金或投资额 :param fv: 未来值,即最后一期期末的目标余额,默认为 ``0`` :param when: 每期付款发生的时点,默认 ``'end'``。可取以下枚举值: - ``'end'`` / ``'e'`` / ``'finish'`` / ``0``:期末付款(普通年金) - ``'begin'`` / ``'b'`` / ``'beginning'`` / ``'start'`` / ``1``:期初付款 (预付年金;此时首期无利息,返回 0,其余期相应折现一期) :return: 第 ``per`` 期的利息支付额(与付款额同向,通常为负) **参考样例** 本金 10000、月利率 0.05/12、共 120 期,第 1 期的利息(≈本金×月利率):: >>> ipmt(0.05/12, 1, 12*10, 10000) -41.666666666666664 **引用** 对应 Excel ``IPMT`` 函数与 ``numpy_financial.ipmt``: https://numpy.org/numpy-financial/latest/functions/ipmt.html """ when = _convert_when(when) rate_a = np.asarray(rate, dtype=float) per_a = np.asarray(per) when_a = np.asarray(when) # 每期等额付款额 total_pmt = pmt(rate, nper, pv, fv, when) # 第 per 期期初的剩余本金 = 现值经过 (per-1) 期后的未来值 remaining_balance = _fv(rate, per_a - 1, total_pmt, pv, when) # 利息 = 剩余本金 × 利率(与 pmt 同号,表示支出为负) result = remaining_balance * rate_a # 期初付款(when='begin')修正:首期无利息,其余期需折现一期 result = np.where(when_a == 1, result / (1 + rate_a), result) result = np.where((when_a == 1) & (per_a == 1), 0.0, result) if result.ndim == 0: return float(result) return result
[文档] def ppmt(rate, per, nper, pv, fv=0, when='end'): """计算给定期间的本金部分 (Principal Payment). 将等额本息分期中第 ``per`` 期的还款额拆分出"本金"部分,等于该期总付款额减去利息 (``ppmt = pmt - ipmt``)。随着期次推进,本金占比逐期增大、利息占比逐期减小。 :param rate: 每期利率,小数表示(如年利率 5% 按月计息则传 ``0.05/12``) :param per: 指定期次,取值范围 1 ~ ``nper``(第 1 期至第 ``nper`` 期) :param nper: 总付款(计息)期数 :param pv: 现值,即贷款本金或投资额 :param fv: 未来值,即最后一期期末的目标余额,默认为 ``0`` :param when: 每期付款发生的时点,默认 ``'end'``。可取以下枚举值: - ``'end'`` / ``'e'`` / ``'finish'`` / ``0``:期末付款(普通年金) - ``'begin'`` / ``'b'`` / ``'beginning'`` / ``'start'`` / ``1``:期初付款(预付年金) :return: 第 ``per`` 期的本金支付额(与付款额同向,通常为负) **参考样例** 本金 10000、月利率 0.05/12、共 120 期,第 1 期偿还的本金:: >>> ppmt(0.05/12, 1, 12*10, 10000) -64.39884857240887 **引用** 对应 Excel ``PPMT`` 函数与 ``numpy_financial.ppmt``: https://numpy.org/numpy-financial/latest/functions/ppmt.html """ total = pmt(rate, nper, pv, fv, when) interest = ipmt(rate, per, nper, pv, fv, when) return total - interest
[文档] def rate(nper, pmt, pv, fv=0, when='end', guess=0.1, tol=1e-6, max_iter=100): """计算每期利率 (Rate). 在给定期数、每期付款额、现值与未来值条件下,使用牛顿迭代法(Newton-Raphson) 反解使现金流等式成立(净现值为零)的 *每期* 利率。如需年化,自行乘以每年期数。 :param nper: 总付款(计息)期数 :param pmt: 每期固定付款额,按现金流方向约定(流出为负) :param pv: 现值,即初始投资或贷款本金 :param fv: 未来值,即最后一期期末的目标余额,默认为 ``0`` :param when: 每期付款发生的时点,默认 ``'end'``。可取以下枚举值: - ``'end'`` / ``'e'`` / ``'finish'`` / ``0``:期末付款(普通年金) - ``'begin'`` / ``'b'`` / ``'beginning'`` / ``'start'`` / ``1``:期初付款(预付年金) :param guess: 牛顿迭代的初始猜测利率,默认为 ``0.1``(即 10%)。当方程存在多解或 迭代不收敛时,可调整该初值 :param tol: 收敛容差,残差或步长小于该值即视为收敛,默认为 ``1e-6`` :param max_iter: 最大迭代次数,默认为 ``100`` :return: 每期利率(小数表示) :raises ValueError: 导数过小或在 ``max_iter`` 次迭代内无法收敛时抛出 **参考样例** 本金 10000、每期还 -100、共 120 期时反解出的月利率:: >>> rate(10*12, -100, 10000) 0.0031141819460226306 **引用** 对应 Excel ``RATE`` 函数与 ``numpy_financial.rate``;牛顿迭代法参见 https://numpy.org/numpy-financial/latest/functions/rate.html """ when = _convert_when(when) def _f(r): if r == 0: return fv + pv + pmt * nper return fv + pv * (1 + r) ** nper + pmt * (1 + r * when) / r * ((1 + r) ** nper - 1) def _fprime(r): if r == 0: return pmt * nper * (nper + 1) / 2 + nper * pv # 数值导数 h = 1e-8 return (_f(r + h) - _f(r - h)) / (2 * h) r = guess for _ in range(max_iter): f_val = _f(r) if abs(f_val) < tol: return r fprime_val = _fprime(r) if abs(fprime_val) < 1e-12: raise ValueError("导数过小,无法继续迭代") r_new = r - f_val / fprime_val if abs(r_new - r) < tol: return r_new r = r_new raise ValueError(f"迭代 {max_iter} 次后仍未收敛")