本文分享自华为云社区《房贷核算器-从原理、核算到提早还款和可视化》,作者: 蜉蝣与海 。

摘要:最近各地楼市震动不断,不少银行纷繁降息,随后更是引发了一波提早还款的大潮。通过楼市小程序上借款核算器等东西人们能够很简单的了解每期还款本金、不同还款方法的利息差异、提早还款节约利息等问题。了解这些东西的核算原理,能够做到心中有数,临危不慌。本文已发布至华为云生态社区AI Gallery,文中触及一切代码能够直接通过页面进入云上Code Lab运转,欢迎开发者前往体会。

前言

最近各地楼市震动不断,2022年12月份以来不少银行纷繁降息,随后更是引发了一波提早还款的大潮。不少区域楼市相关的微信小程序也自带了借款核算器、提早还款核算器等东西,通过这些东西人们能够很简单的了解每期还款本金、等额本金/本息的利息差异、提早还款节约利息的问题。

了解这些核算东西的相关原理,能够做到心中有数,临危不慌。

  1. 本文对应代码和脚本发布至华为云生态社区AI Gallery:借款核算器-从原理、公式到提早还款和可视化欢迎开发者前往体会,文中触及一切代码能够直接通过页面进入Model Arts Code Lab运转。运用该脚本稍加修正后即可尝试开发一个合适自身区域政策的借款核算&提早还款小程序。

  2. 本文只是研究借款生成、提早还贷方面的相关核算原理,不构成任何出资理财方面的建议。

如何核算利息

布景:等额本金和等额本息的共同点

了解过借款的小伙伴都知道,借款有等额本金和等额本息这两种方法,前者每月还款的本金相同,利息逐月递减;后者每月还款额相同,刚开始还款时利息还的多,后边本金还的逐渐增多。参阅网上评论利息核算的许多文章,两个模型理论上,都有下列共同特色:

  • 利息按月利率核算,一月一期
  • 如期还款状况下当月应还利息只由未还完的本金决定
  • 每月还款额除了未还本金发生的全部利息外,剩下的金额应该全部用于归还本金

像最近部分银行提出的先息后本(先还利息若干年,终究一次性归还本金)则不契合这个条件。

还款额的核算

知乎文章为什么买房借款,最好挑选等额本金?中提到了一个比如:

前阵子,院长有位朋友在惠州买了套120平米的房,总价125万左右,大约贷了87.5万。
办房贷的时分,他听从出售的建议,选了【等额本息】的还款方法。每个月固定还5726.39元。这个还款额度在他的承受范围之内,因而就选了。
那假定挑选等额本金呢?第一个月要还的金额为7218.75元,尔后每个月少还14.89元,直至20年后还完。

通过描绘可知,借款87.5万,贷20年,等额本息每月还款5726.39元,等额本金首月还款7218.75元。假定文中的借款未运用公积金,核算时利率为固定利率,依据网上的借款核算器可知此刻的借款年利率为4.9%。

以这个比如为例,简单说明等额本金和等额本息的核算方法:

首要贷20年,按月分期,借款为

20∗12=240期。

年利率4.9%,月利率为

0.04912=0.004983 即0.4083%。

等额本金 状况下:

每月应还本金=总本金期数

每月应还利息=剩下本金月利率

每月还款额=每月应还本金+每月应还利息

在这个比如中:

  • 每月应还本金为
    875000240=3645.83元
  • 首月应还利息为
    8750000.4083元
  • 首月应还:
    3645.83+3572.92=7218.75元。
  • 第2月剩下本金为
    875000−3645.83=871354.17元。
  • 第2月应还利息为
    871354.170.4083元。
  • 第2月应还:
    3645.83+3558.03=7203.86元。

将这段逻辑笼统为代码有:

import matplotlib.pyplot as plt
import numpy as np
def averageCapital(months, principal, rate):
    month_rate = rate / 12
    monthly_capital = principal / months
    interests = [0] * months
    capitals = [0] * months
    left_principal = [0] * months
    left_principal[0] = principal
    total_payment = [0] * months
    for i in range(0, months):
        interests[i] = left_principal[i] * month_rate
        capitals[i] = monthly_capital
        total_payment[i] = monthly_capital + interests[i]
        if i + 1 < months:
            left_principal[i + 1] = left_principal[i] - monthly_capital
    return capitals, interests, total_payment

为了便于检查再封装一个打印成表格的函数:

import pandas as pd
def drawTable(months, fn, *args, **kwargs):
    capitals, interests, total_payment = fn(months, *args, **kwargs)
    paid_capital = [0] * months
    paid_interests = [0] * months
    paid_capital[0] = capitals[0]
    paid_interests[0] = interests[0]
    for x in range(1, months):
        paid_capital[x] = paid_capital[x - 1] + capitals[x]
        paid_interests[x] = paid_interests[x - 1] + interests[x]
    origin = pd.DataFrame([total_payment, capitals, interests, paid_capital, paid_interests])
    return pd.DataFrame(origin.values.T, columns=['还款额','还款本金','还款利息','已还本金','已还利息'], index=np.arange(1, months + 1))

咱们运转一下知乎上的比如,看看头几年还款的本金、利息等:

pd.options.display.float_format = '{:.2f}'.format
drawTable(12 * 20, averageCapital, 875000, 0.049)[0:10]

还在头疼每月房贷还款,这个房贷计算机让你一目了然

能够看到和文中描绘共同,运用微信房小团小程序,也能够打印出共同的成果。
等额本息 的核算方法有些杂乱,参阅用Python深度解读房贷利率文中的解法,设A为本金,第i个月月末所欠银行本金为Ai​,每月所还借款总额为X,月利率为, 则有:

还在头疼每月房贷还款,这个房贷计算机让你一目了然

由于终究一期时剩下本金为0,可反解得:

还在头疼每月房贷还款,这个房贷计算机让你一目了然

这儿m为总期数(在刚刚的比如中,m=240)。而后就能够运用与等额本金核算中类似的逻辑,从第一期所还利息开始,反推每期的利息与本金。详细代码如下:

def averageCapitalPlusInterest(months, principal, rate):
    month_rate = rate / 12
    monthly_payment = principal * month_rate * (1 + month_rate) ** months / ((1 + month_rate) ** months - 1)
    interests = [0] * months
    capitals = [0] * months
    left_principal = [0] * months
    left_principal[0] = principal
    total_payment = [0] * months
    for i in range(0, months):
        total_payment[i] = monthly_payment
        interests[i] = left_principal[i] * month_rate
        capitals[i] = total_payment[i] - interests[i]
        if i + 1 < months:
            left_principal[i + 1] = left_principal[i] - capitals[i]
    return capitals, interests, total_payment

咱们运转一下知乎上的比如,看看等额本息方式下第8年附近,到底还了多少利息和本金:

drawTable(12 * 20, averageCapitalPlusInterest, 875000, 0.049)[90:100]

还在头疼每月房贷还款,这个房贷计算机让你一目了然

能够看到第96期(第8年年终)时,本金还了25万,但利息现已还了近30万了,和之前文中比如的数据是能够对得上的。

还款可视化

刚刚咱们现已将还款的各项数据以表格的方式打印。此外咱们还能够借助python的才能,打印还款的柱状图。

import numpy as np
def printStatistics(capitals, interests, total_payment, months):
    print("总本金:" + str(np.sum(capitals)))
    print("总利息:" + str(np.sum(interests)))
    print("总利息/总本金" + str(np.sum(interests)/np.sum(capitals)))
    print("首月还款 %.2f 末月还款: %.2f" % (total_payment[0], total_payment[months - 1]))
def drawDiagram(months, fn, *args, **kwargs):
    capitals, interests, total_payment = fn(months, *args, **kwargs)
    printStatistics(capitals, interests, total_payment, months)
    month_array = np.arange(1, months + 1, 1)
    height = interests
    plt.bar(month_array, capitals, width=0.2, align='center', color='red')
    plt.bar(month_array, interests, width=0.2, align='center', color='blue', bottom=capitals)
    plt.show()

再跑一下知乎的比如,制作等额本金和等额本息的还款柱状图:

drawDiagram(12 * 20, averageCapital, 875000, 0.049)

还在头疼每月房贷还款,这个房贷计算机让你一目了然

如图,蓝色是所还利息,红色是所还本金。能够看出本金每月不变,利息逐月递减的特征。

等额本息状况下:

drawDiagram(12 * 20, averageCapitalPlusInterest, 875000, 0.049)

还在头疼每月房贷还款,这个房贷计算机让你一目了然

也能看出所绘图形和等额本息的含义根本共同。

别的部分城市能够公积金借款,以杭州为例,目前杭州公积金充足状况下可贷50w-60w,这儿考虑一下公积金的状况:

def averageCapitalWithPublicFund(months, principal1, rate1, principal2, rate2):
    a, b, c = averageCapital(months, principal1, rate1)
    a1, b1, c1 = averageCapital(months, principal2, rate2)
    return np.sum([a,a1],axis=0).tolist(), np.sum([b,b1],axis=0).tolist(), np.sum([c,c1],axis=0).tolist()
drawTable(12 * 20, averageCapitalWithPublicFund, 700000, 0.041, 300000, 0.031)[0:10]

还在头疼每月房贷还款,这个房贷计算机让你一目了然

这儿算了下商贷70w(利率4.1%),公积金贷30w(利率3.1%)下组合借款的状况,和微信小程序房小团的核算是共同的。

提早还款相关原理

再来评论下提早还款。如果知乎文中买房的那位,在借款1年后提早还款10w会怎样呢?了解一点布景常识的朋友,都知晓提早还款分两种状况:

  • 年限不变,月供削减
  • 年限缩短,月供不变

现在分状况评论,并给出核算函数。

注:notebook中一切核算成果均在微信房小团小程序上得到互相验证。

年限不变,月供削减

这种状况下,相当于在提早还款月之后从头做了一次借款。咱们首要对刚刚的核算函数进行一定的简化,笼统一下公共的部分。

def normalPaid(months, principal, rate, capitalAveraged):
    month_rate = rate / 12
    monthly_capital = principal / months
    monthly_payment = principal * month_rate * (1 + month_rate) ** months / ((1 + month_rate) ** months - 1)
    interests = [0] * months
    capitals = [0] * months
    left_principal = [0] * months
    left_principal[0] = principal
    total_payment = [0] * months
    for i in range(0, months):
        interests[i] = left_principal[i] * month_rate
        if capitalAveraged:
            capitals[i] = monthly_capital
            total_payment[i] = monthly_capital + interests[i]
        else:
            total_payment[i] = monthly_payment
            capitals[i] = total_payment[i] - interests[i]
        if i + 1 < months:
            left_principal[i + 1] = left_principal[i] - capitals[i]
    return capitals, interests, total_payment
drawTable(12 * 20, normalPaid, 875000, 0.049, False)[10:14]

还在头疼每月房贷还款,这个房贷计算机让你一目了然

drawTable(12 * 20, normalPaid, 875000, 0.049, True)[10:14]

还在头疼每月房贷还款,这个房贷计算机让你一目了然

能够看到笼统出公共结构后,前后的核算成果并没有发生变化。

考虑年限不变提早还款的状况,这儿将每次提早还款的时间和金额组成python的元组,若干个(账期,还款金额)元组组成一个list输入函数。函数首要核算正常状况下的还款信息,而后依据提早还款信息,修正提早还款日的剩下本金,并从各个提早还款日从头核算剩下还款。

def extraPaidWithFixedPeriod(months, principal, rate, capitalAveraged, extraPaidList :list):
    capitals, interests, total_payment = normalPaid(months, principal, rate, capitalAveraged)
    extraPaidList.sort(key=lambda x:x[0])
    originCapital, originInterests, originTotal = capitals.copy(), interests.copy(), total_payment.copy()
    left_principal = [0] * months
    left_principal[0] = principal
    for x in range(0,months):
        if x < months - 1:
            left_principal[x + 1] = left_principal[x] - capitals[x]
    def normalPaidOffset(left_months, principal, rate, capitalAveraged, offset):
        month_rate = rate / 12
        monthly_capital = left_principal[offset] / left_months
        monthly_payment = left_principal[offset] * month_rate * (1 + month_rate) ** left_months / ((1 + month_rate) ** left_months - 1)
        for i in range(0, left_months):
            interests[offset + i] = left_principal[offset + i] * month_rate
            if capitalAveraged:
                capitals[offset + i] = monthly_capital
                total_payment[offset + i] = monthly_capital + interests[offset + i]
            else:
                total_payment[offset + i] = monthly_payment
                capitals[offset + i] = total_payment[offset + i] - interests[offset + i]
            if i == 0:
                print("次月还款 %.2f" % total_payment[offset + i])
            if offset + i + 1 < months:
                left_principal[offset + i + 1] = left_principal[offset + i] - capitals[offset + i]
        return
    for x,y in extraPaidList:
        capitals[x] = capitals[x] + y
        left_principal[x + 1] = left_principal[x] - capitals[x]
        total_payment[x] = capitals[x] + interests[x]
        print("当月需还 %.f 剩下本金 %.f" %(total_payment[x], left_principal[x + 1]))
        normalPaidOffset(months - x - 1, left_principal[x + 1], rate, capitalAveraged, x + 1)
    printStatistics(originCapital, originInterests, originTotal, months)
    print("")
    printStatistics(capitals, interests, total_payment, months)
    print("节约利息 %.2f" % (np.sum(originInterests) - np.sum(interests)))
    return capitals, interests, total_payment, originTotal, originInterests

再定义几个函数对提早还款节约的利息进行可视化。

def drawDiagramExtraPaid(months, capitals, interests, originalTotal, originalInterests, showOriginTotal=True):
    month_array = np.arange(1, months + 1, 1)
    capital_with_origin_interest = [0] * months
    height = interests
    for x in range(1, months):
        capital_with_origin_interest[x] = capitals[x] + originalInterests[x]
    l1 = plt.bar(month_array, originalTotal if showOriginTotal else capital_with_origin_interest, width=0.2, align='center', color='yellow')
    l2 = plt.bar(month_array, capitals, width=0.2, align='center', color='red')
    l3 = plt.bar(month_array, interests, width=0.2, align='center', color='blue', bottom=capitals)
    # plt.legend(handles = [l1, l2,l3], labels = ['每月少还' if showOriginTotal else '节约利息', '本金','利息'], loc = 'best',fontsize=20)
    plt.ylim(0, (capitals[0]+interests[0])*1.1)
    plt.show()
def drawTableExtraPaid(months, capitals, interests, total_payment, originalTotal, originalInterests):
    paid_capital = [0] * months
    paid_interests = [0] * months
    saved_money = [0] * months
    paid_capital[0] = capitals[0]
    paid_interests[0] = interests[0]
    for x in range(1, months):
        paid_capital[x] = paid_capital[x - 1] + capitals[x]
        paid_interests[x] = paid_interests[x - 1] + interests[x]
        saved_money[x] = saved_money[x - 1] + (originalInterests[x] - interests[x] )
    origin = pd.DataFrame([total_payment, capitals, interests, paid_capital, paid_interests,saved_money])
    return pd.DataFrame(origin.values.T, columns=['还款额','还款本金','还款利息','已还本金','已还利息','累计节约'], index=np.arange(1, months + 1))

通过参数showOriginTotal的取值,能够别离制作每月少还的钱与当月节约利息的状况。下面别离制作了等额本金和等额本息状况下,87.5万贷20年,在第一年还10万后还款和利息的变化状况。

a, b, c, d, e = extraPaidWithFixedPeriod(12 * 20, 875000, 0.049, True, [(13,100000)])
drawDiagramExtraPaid(12 * 20, a, b, d, e)
drawDiagramExtraPaid(12 * 20, a, b, d, e, False)
drawTableExtraPaid(12 * 20, a, b, c, d, e)[10:20]

还在头疼每月房贷还款,这个房贷计算机让你一目了然

还在头疼每月房贷还款,这个房贷计算机让你一目了然

还在头疼每月房贷还款,这个房贷计算机让你一目了然

还在头疼每月房贷还款,这个房贷计算机让你一目了然

a, b, c, d, e = extraPaidWithFixedPeriod(12 * 20, 875000, 0.049, False, [(13,100000)])
drawDiagramExtraPaid(12 * 20, a, b, d, e)
drawDiagramExtraPaid(12 * 20, a, b, d, e, False)
drawTableExtraPaid(12 * 20, a, b, c, d, e)[10:20]

还在头疼每月房贷还款,这个房贷计算机让你一目了然

还在头疼每月房贷还款,这个房贷计算机让你一目了然

还在头疼每月房贷还款,这个房贷计算机让你一目了然

还在头疼每月房贷还款,这个房贷计算机让你一目了然

能够很方便地看出节约利息在每个月还款额中的比重。

月供不变,年限缩短

这种状况下提早还款导致后续每个月发生的利息少了,可是月供没变,相当于后续每个月额定多还了本金。可是在各类提早还款核算器的核算中,月供并不是和之前相同的,通过反复的核算后和网上的借款核算器成果终究共同,发现各类提早还款核算器隐含了下列约束:

  • 提早还款相当于用剩下本金新做一个借款。
  • “月供”不是真的不变。而是通过缩短年限方法,使得新借款首月月供尽可能和当时月供相当。
  • 如果是等额本金方式,新借款首月月供中,归还本金并未增多,需求略低于上月归还本金,等额本息方式则无此约束。

想想这个逻辑也有道理,如果真的“月供不变”,那么等额本金方式下提早还款后,后续每个月归还的本金都会比新做借款的归还的本金多,相当于后续每个月都在提早还款,后续每个月月供本金就不能称为“等额”了。
咱们下面先写个求解首月月供的函数,以及通过缩短年限迫临上月月供总额和月供本金的函数。而后核算“月供不变,年限缩短”方式下节约的详细利息。

def getFirstPaid(months, principal, rate, capitalAveraged):
    month_rate = rate / 12
    monthly_capital = principal / months
    monthly_payment = principal * month_rate * (1 + month_rate) ** months / ((1 + month_rate) ** months - 1)
    interests1 = principal * month_rate
    if capitalAveraged:
        return monthly_capital + interests1, monthly_capital
    else:
        return monthly_payment, monthly_payment - interests1
def getLeftMonths(leftMonthsMax, capitalPaidMax, paidMax, leftPrincipal, rate, capitalAveraged):
    lastPaid, lastCapitalPaid, lastMonths = 0, 0, 0
    for i in range(leftMonthsMax, 1, -1):
        paid, capitalPaid = getFirstPaid(i, leftPrincipal, rate, capitalAveraged)
        if paid > paidMax or (capitalAveraged and capitalPaid > capitalPaidMax):
            return lastMonths, lastPaid, lastCapitalPaid
        else:
            lastPaid, lastCapitalPaid, lastMonths = paid, capitalPaid, i
def extraPaidWithFixedPaid(months, principal, rate,
                           capitalAveraged, extraPaidList: list):
    capitals, interests, total_payment = normalPaid(
        months, principal, rate, capitalAveraged)
    extraPaidList.sort(key=lambda x: x[0])
    originCapital, originInterests, originTotal = capitals.copy(), interests.copy(), total_payment.copy()
    left_principal = [0] * months
    left_principal[0] = principal
    for x in range(0, months):
        if x < months - 1:
            left_principal[x + 1] = left_principal[x] - capitals[x]
    def normalPaidOffset(left_months, principal, rate,
                         capitalAveraged, offset, left_months2):
        month_rate = rate / 12
        monthly_capital = left_principal[offset] / left_months
        monthly_payment = left_principal[offset] * month_rate * (1 + month_rate) ** left_months / ((1 + month_rate) ** left_months - 1)
        for i in range(0, left_months):
            interests[offset + i] = left_principal[offset + i] * month_rate
            if capitalAveraged:
                capitals[offset + i] = monthly_capital
                total_payment[offset + i] = monthly_capital + interests[offset + i]
            else:
                total_payment[offset + i] = monthly_payment
                capitals[offset + i] = total_payment[offset + i] - interests[offset + i]
            if i == 0:
                print("次月还款 %.2f" % total_payment[offset + i])
            if offset + i + 1 < months:
                left_principal[offset + i + 1] = left_principal[offset + i] - capitals[offset + i]
        for i in range(left_months, left_months2):
            interests[offset + i] = 0
            capitals[offset + i] = 0
            total_payment[offset + i] = 0
        return
    realMonth = months
    for x, y in extraPaidList:
        capitalParam = capitals[x]
        capitals[x] = capitals[x] + y
        left_principal[x + 1] = left_principal[x] - capitals[x]
        total_payment[x] = capitals[x] + interests[x]
        maxMonth, maxPaid, maxPaidCapital = getLeftMonths(months - x - 1, capitalParam, total_payment[x - 1], left_principal[x + 1], rate, capitalAveraged)
        normalPaidOffset(maxMonth, left_principal[x + 1], rate, capitalAveraged, x + 1, months - x - 1)
        realMonth = x + 1 + maxMonth
        print("当月需还 %.2f 剩下本金 %.2f 下月需还:%.2f  原本剩下账期:%d,当时剩下账期:%d, 账期缩短:%d" %(total_payment[x], left_principal[x + 1],total_payment[x + 1], months - x - 1,maxMonth, months - x - 1 - maxMonth))
    printStatistics(originCapital, originInterests, originTotal, months)
    print("")
    printStatistics(capitals, interests, total_payment, realMonth)
    print("节约利息 %.2f" % (np.sum(originInterests) - np.sum(interests)))
    return capitals, interests, total_payment, originTotal, originInterests
a, b, c, d, e = extraPaidWithFixedPaid(12 * 20, 875000, 0.049, True, [(13, 100000)])
drawDiagramExtraPaid(12 * 20, a, b, d, e)
drawTableExtraPaid(12 * 20, a, b, c, d, e)[10:20]

还在头疼每月房贷还款,这个房贷计算机让你一目了然

还在头疼每月房贷还款,这个房贷计算机让你一目了然

还在头疼每月房贷还款,这个房贷计算机让你一目了然

a, b, c, d, e = extraPaidWithFixedPaid(12 * 20, 875000, 0.049, False, [(13, 100000)])
drawDiagramExtraPaid(12 * 20, a, b, d, e)
drawTableExtraPaid(12 * 20, a, b, c, d, e)[10:20]

还在头疼每月房贷还款,这个房贷计算机让你一目了然

还在头疼每月房贷还款,这个房贷计算机让你一目了然

还在头疼每月房贷还款,这个房贷计算机让你一目了然

能够看出,虽然缩短年限的实质也是从头做一次借款,但的确能够节约许多利息。

小结

本文初稿写于华为云AI-Gallery借款核算器-从原理、公式到提早还款和可视化,通过页面进入CodeLab能够直接在界面上调整参数进行房贷利息、提早还款等相关核算,核算过程原理直观,合作可视化方便了解,欢迎开发者前往体会。

整篇文章带大家了解了不同房贷借款方法的差异,以及对房贷利息核算、提早还款的原理做了较为详尽的剖析和数据可视化。后续在面对借款利息核算的问题时,能够直面原理、心中有数、临危不慌。

参阅资料

[1]用Python深度解读房贷利率

[2]为什么买房借款,最好挑选等额本金?

[3]杭州房小团微信小程序-借款核算

[4]杭州房小团微信小程序-提早还款

点击关注,第一时间了解华为云新鲜技术~