房贷量化分析

使用Python分析贷款信息,包括利率变化、提前还款

动机

网络上有很多贷款计算器,包括提前还款、利率变化等。但大多数只能输入一次利率变化或提前还款。然而,实际中,可能会经过多次LPR调整,以及多次部分还款。用excel计算过于复杂,所以码了代码用以具体分析。在对比结果的过程中,发现了很多有意思的现象,比如提前还款时利息的计算,下面会提到。

理论

  1. 等额本息还款公式
    月还款额 = 本金 × ( 1 + 月利率 ) 期数 × 期数 ( 1 + 月利率 ) 期数 + 1 月还款额=\frac{本金\times(1+月利率)^{期数} \times 期数}{(1+月利率)^{期数} +1} 月还款额=(1+月利率)期数+1本金×(1+月利率)期数×期数
    注:期数单位为月

  2. 月还款利息:
    月还款利息 = 剩余本金 ∗ 月利率 月还款利息=剩余本金*月利率 月还款利息=剩余本金月利率

  3. 月还款本金
    月还款本金 = 月还款额 − 月还款利息 月还款本金=月还款额-月还款利息 月还款本金=月还款额月还款利息

  4. 关于提前还款:提前还款利息计算是分段的,按照日利率计算,且每月按照30天计算,日期计算计头不计尾。例如,上期还款日为2/24,提前还款日为3/9,那么:
    当期应还利息 = ( 原剩余本金 × 15 + 提前还款后剩余本金 × 15 ) × 日利率 当期应还利息= (原剩余本金 \times 15+提前还款后剩余本金 \times 15) \times 日利率 当期应还利息=(原剩余本金×15+提前还款后剩余本金×15)×日利率
    其中,2月份按照30天计算,则从24日到次月9日(提前还款日)共15天(日期区间[24,30]+[1,9)),到下次还款日15天。根据央行规定,
    日利率 = 年利率 / 360 = 月利率 / 12 日利率=年利率/360=月利率/12 日利率=年利率/360=月利率/12

  5. 关于提前还款扩展:包括三种,a)期限不变,月供减少;b)月供不变,期限变短;c)两者都减少。大部分银行只支持前两种。其中,月供不变,期限变短后计算公式可以从月还款额计算公式轻易推导出来:
    新的还款期限 = l o g ( 月还款额 / ( 月还款额 − 剩余本金 ∗ 月利率 ) ) l o g ( 1 + 月利率 ) 新的还款期限=\frac{log(月还款额/(月还款额-剩余本金*月利率))}{log(1+月利率)} 新的还款期限=log(1+月利率)log(月还款额/(月还款额剩余本金月利率))

公式推导网上很多资料,不赘述。

代码

话不多说,上代码

def cal_basic(monthly_interest_rate, total_months, capital):
    # 计算基础信息,包括月供、累计还款额,累计利息
    # 参数分别为月利率,总期数,本金
    total_per_month = capital * (1+monthly_interest_rate)** total_months * monthly_interest_rate / ((1+monthly_interest_rate)**total_months-1)  # 月供
    cumulative_pay = total_months * capital * (1+monthly_interest_rate)** total_months * monthly_interest_rate / ((1+monthly_interest_rate)**total_months-1)  # 累计还款,包括本金和利息
    cumulative_interest = cumulative_pay - capital # 累计还利息
    return (round(total_per_month, val_decimal_num), round(cumulative_pay,val_decimal_num), round(cumulative_interest,val_decimal_num))

def cal_per_month_by_left_capital(monthly_interest_rate, left_capital, total_per_month):
    # 逐月计算月还款利息等信息,包括每月还款利息、每月还款本金、剩余本金
    # 输入参数为月还款利息,剩余本金、月供
    interest_month = left_capital * monthly_interest_rate
    capital_month = total_per_month - interest_month
    if capital_month > left_capital:
        # 最后一个月,本金可能小于基于月供计算的数
        capital_month = left_capital
    # print(left_capital, capital_month, left_capital - capital_month)
    return (round(interest_month, val_decimal_num), round(capital_month, val_decimal_num), round(left_capital - capital_month, val_decimal_num))

以上两个函数主要用于计算月供、每月还款利息、每月还款本金等信息。
下面是几个辅助函数,功能见说明

def cal_delta_months(start_date, event_happen_date):
    # 计算某个时间发生的日期相较于起始日期的月份偏移数
    delta_year = event_happen_date.year - start_date.year
    delta_month = event_happen_date.month - start_date.month
    delta_day = event_happen_date.day - start_date.day
    res = delta_year * 12 + delta_month
    if delta_day < 0:
        res -= 1
    return res
def next_month(current_date):
    # 获取下个月的datetime对象
    year = current_date.year
    month = current_date.month
    if month == 12:
        year += 1
        month = 1
    else:
        month += 1
    return datetime(year, month, current_date.day)  
def cal_prepapment_interest(original_captical, prepay_amount, monthly_interest_rate, last_pay_date, prepay_date):
    # 前提:上次付款日和提前还款日差值不超过30天
    # 计算提前还款当月应还利息
    if prepay_date.month == last_pay_date.month:
        # 同一个月
        duration_before = prepay_date.day - last_pay_date.day
        duration_after = 30 - last_pay_date.day + last_pay_date.day  # 提前还款后,下次还款日相同  = 30-duration_before
    else:
        # 不同月,则一个月按照30天计算
        duration_before = 30 - last_pay_date.day + prepay_date.day
        duration_after = last_pay_date.day - prepay_date.day # 日期算头不算尾   = 30-duration_before
    # print(duration_before, duration_after)
    return round((original_captical * duration_before + (original_captical - prepay_amount) * duration_after)  * monthly_interest_rate / 30, val_decimal_num)
        

下面函数为总体函数。

def view_all_pay_info(monthly_interest_rate, total_months, capital, start_date, change_event_list):
    # 主函数。根据起始月利息,期数、本金、开始还款日期、事件发生列表等,生成每月还款信息表
    current_monthly_interest_rate = monthly_interest_rate
    m_pay_fixed,_,_ = cal_basic(monthly_interest_rate, total_months, capital)  # 总的还款信息:月供、累计还款、累计还利息
    current_date = start_date
    full_info_list = []
    total_left_capital = capital # 剩余本金
    # 计算事件发生改变的月
    has_event_month_indexes = []
    for event in change_event_list:
        has_event_month_indexes.append(cal_delta_months(start_date, event['happen_date']) + 1 + 1)  # +1是从第一个月开始计算,再+1是事件生效月
    event_index = 0
    print(has_event_month_indexes)
    for m in range(1, total_months+1):
        if m > total_months:
            # 总还款时间有可能变化
            break
        # 处理事件
        description = ''
        changed_interest = 0 # 如果提前还款,可能会导致当月利息变化
        if m in has_event_month_indexes:
            for n in range(has_event_month_indexes.count(m)):
                # 可能发生多个事件
                event = change_event_list[event_index]
                event_index += 1
                if event['event_type'] == 0:
                    # 利率改变
                    current_monthly_interest_rate = round(event['new_yearly_interest_rate'] / 12, rate_decimal_num)  # 保留x位小数
                    m_pay_fixed,_,_ = cal_basic(current_monthly_interest_rate, total_months-m+1, total_left_capital)  # 更新总还款信息
                    description += '利率变化:' + str(current_monthly_interest_rate) + ' '
                elif event['event_type'] == 1:
                    # 提前还款
                    changed_interest = cal_prepapment_interest(total_left_capital, event['amount'], current_monthly_interest_rate, last_date, event['happen_date'])
                    total_left_capital -= event['amount']
                    if event['prepay_type'] == 0:
                        # 期限不变,月供减少
                        m_pay_fixed,_,_ = cal_basic(current_monthly_interest_rate, total_months-m+1, total_left_capital)  # 更新总还款信息
                        description += '提前还款:' + str(event['amount'] / 10000) + '万 '
                    elif event['prepay_type'] == 1:
                        # 期限变短,月供不变
                        # 计算期限
                        new_duration = prepayment_decrease_duration(m_pay_fixed, current_monthly_interest_rate, total_left_capital)
                        print(new_duration, m)
                        total_months = new_duration + m -1
                        description += '提前还款:' + str(event['amount'] / 10000) + '万 '
                    else:
                        # 月供减少,同时期限变短
                        total_months = event['new_total_months']
                        m_pay_fixed,_,_ = cal_basic(current_monthly_interest_rate, total_months-m+1, total_left_capital)  # 更新总还款信息
                        description += '提前还款:' + str(event['amount'] / 10000) + '万 '
        m_interest, m_capital, total_left_capital = cal_per_month_by_left_capital(current_monthly_interest_rate, total_left_capital, m_pay_fixed) # 每月还利息、每月还本金、剩余本金
        m_pay_total = round(m_pay_fixed, val_decimal_num) # 当月还款总额
        if changed_interest != 0:
            # 利息变化
            m_interest = changed_interest
            m_pay_total = m_capital + changed_interest
        full_info_list.append((current_date, m, m_pay_total, m_interest, m_capital, total_left_capital, description)) 
        last_date = current_date                                                 
        current_date = next_month(current_date)
    full_info_list_df = pd.DataFrame(full_info_list, columns=['日期', '月数', '月供', '还利息', '还本金', '剩余本金', '事件描述'])
    return full_info_list_df

输入参数,可以计算输出相应结果。

# 基础参数
capital = 3040000 # 本金
months = 25 * 12 # 月数
start_date = datetime(2021,7,24)
year_interest_rate = 5.2 / 100
change_event_list = []  # 注意,这里按照事件的发生时间由远及近
change_event_list.append(
    {
        'happen_date': datetime(2022,7,1),
        'event_type': 0,  # 0-->利率改变,1-->提前还款
        'new_yearly_interest_rate': 0.05
    })
change_event_list.append(
    {
        'happen_date': datetime(2023,3,9),
        'event_type': 1,  # 0-->利率改变,1-->提前还款
        'amount': 200000,
        'prepay_type': 0,  # 0:期限不变,月供变少;1月供不变,期限变短; 2月供减少,同时期限变短
    })
change_event_list.append(
    {
        'happen_date': datetime(2023,6,24),
        'event_type': 0,  # 0-->利率改变,1-->提前还款
        'new_yearly_interest_rate': 0.0475
    })
change_event_list.append(
    {
        'happen_date': datetime(2023,7, 22),
        'event_type': 1,  # 0-->利率改变,1-->提前还款
        'amount': 200000,
        'prepay_type': 1,  # 0:期限不变,月供变少;1月供不变,期限变短; 2月供减少,同时期限变短
    })
rate_decimal_num = 8  # 利率精度
val_decimal_num = 2  # 数值精度

res = view_all_pay_info(round(year_interest_rate / 12, rate_decimal_num), months, capital, start_date, change_event_list)
res.to_csv('house_loan.csv', index=False)

可以在change_event_list列表中添加利率变化、提前还款等利息。

结果示例

由于已经拿到了DataFrame格式的所有信息,可以进行各种对比分析。图形化分析部分不做展开,仅显示下表格。其中数据已经与真实数据对比准确无误。
每月还款信息一览

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值