Python 房贷计算器小工具

根据网上的网贷计算公式实现了一个Python计算器,能够计算房贷的还款计划,支持等额本金和等额本息两种方式。

开始以为套用一下公式就可以了,做完才知道远没有看起来那么简单。

 

根据房贷计算公式:

等额本息计算方式

每月还款额=贷款本金×[月利率×(1+月利率)^还款月数]÷[(1+月利率)^还款月数-1]

总支付利息:总利息=还款月数×每月月供额-贷款本金

每月应还利息=贷款本金×月利率×〔(1+月利率)^还款月数-(1+月利率)^(还款月序号-1)〕÷〔(1+月利率)^还款月数-1〕

每月应还本金=贷款本金×月利率×(1+月利率)^(还款月序号-1)÷〔(1+月利率)^还款月数-1〕

总利息=还款月数×每月月供额-贷款本金

等额本金计算方式

每月月供额=(贷款本金÷还款月数)+(贷款本金-已归还本金累计额)×月利率

每月应还本金=贷款本金÷还款月数

每月应还利息=剩余本金×月利率=(贷款本金-已归还本金累计额)×月利率。

每月月供递减额=每月应还本金×月利率=贷款本金÷还款月数×月利率

总利息=还款月数×(总贷款额×月利率-月利率×(总贷款额÷还款月数)*(还款月数-1)÷2+总贷款额÷还款月数) [1] 

==========================================================================================

第一版实现代码如下:

#  coding=utf-8
# 房贷计算器,计算等额本金和等额本息得计算器,可以导出Excel

import sys
from PyQt5.QtWidgets import QApplication, QMainWindow,QTableWidgetItem,QMessageBox
from w7 import Ui_MainWindow

import xlwt
import xlrd

def outExlTo(tList):

    wb = xlwt.Workbook(encoding="utf-8")
    ws = wb.add_sheet("confirmt")
    listTit=['期数(月)','应还金额','应还本金','应还利息']
    t1Con=0
    for t1 in listTit:
        ws.write(0,t1Con,t1)
        t1Con=t1Con+1
    rownum=1
    columnum=0
    for row in tList:
        for column in row:
            
            outT=float(column)
            #设置每个位置的文本值
            ws.write(rownum,columnum,outT)
            #print(rownum,columnum,item)
            columnum=columnum+1
        columnum=0
        rownum=rownum+1    

    
    wb.save('outPut.xls')  
    
def isFloat(str):
    try:
        float(str)
    except ValueError:
        return False
    else:
        return True  

def isInt(str):
    try:
        int(str)
    except ValueError:
        return False
    else:
        return True
#等额本金计算
def calF1(loan,monthRate,period):
    # 每月应还本金,初始化列表有period*12个元素
    monthPrincipalPayment = [loan/(period*12)]*period*12
    #print(monthPrincipalPayment)
    # 每月应还本息
    monthInterestPayment = [(loan - loan*n/(period*12))*monthRate+loan/(period*12) for n in range(0,period*12)]
    print(monthInterestPayment)
    # 还款期数
    month = [n for n in range(1,period*12+1)]
    rowSet=[]
    for x in month:
        print(x)
        col=[]
        col.append(x)
        col.append(round(monthInterestPayment[x-1],2))
        col.append(round(monthPrincipalPayment[x-1],2))
        f_intD=round(round(monthInterestPayment[x-1],2)-round(monthPrincipalPayment[x-1],2),2) #计算应还利息
        col.append(f_intD)
        rowSet.append(col)
    print("等额本金计算:")
    #print(rowSet)
    return rowSet

#等额本息计算
def calF2(loan,monthRate,period):
    # 还款期数
    month = [n for n in range(1,period*12+1)]
    # 首月应还利息
    firstMonthInterest = loan*monthRate
    print(firstMonthInterest)
    # 每月应还本息
    monthPayment = (loan*monthRate*(1+monthRate)**(period*12))/((1+monthRate)**(period*12)-1)
    loanPI = [loan*(1+monthRate)-monthPayment]
    
    # 每期应还利息
    loanInterest = [loan*monthRate]
    
    for n in range(1, period*12):
        loanPI.append((loanPI[n-1]*(1+monthRate)-monthPayment))
        loanInterest.append(round(loanPI[n-1]*monthRate,2))
    
    # 每期应还本金
    loanPrincipal = [monthPayment-loanInterest[n] for n in range(0,len(loanInterest))]
    
    
    print(loanPrincipal)
    print(loanInterest)
    print(monthPayment)
    rowSet=[]
    for x in month:
        print(x)
        col=[]
        col.append(x)
        col.append(round(monthPayment,2))
        col.append(round(loanPrincipal[x-1],2))
        f_intD=round(round(monthPayment,2)-round(loanPrincipal[x-1],2),2) #计算应还利息
        col.append(f_intD)
        rowSet.append(col)
    print("等额本息计算:")
    #print(rowSet)
    return rowSet
#输出项检查
def checkInput(self):
    loan = self.textEdit.toPlainText() # 贷款金额,万元
    if isFloat(loan):
        #print('贷款金额是数字!');
        pass
    else:
        print('贷款金额不是数字!');
        QMessageBox.critical(self, '警告', '贷款金额不是数字!',QMessageBox.Yes )
        return None,None,None
    annualRate = self.textEdit_2.toPlainText() # 贷款年利率
    if isFloat(annualRate):
        #print('贷款年利率是数字!');
        pass
    else:
        print('贷款年利率不是数字!');
        QMessageBox.critical(self, '警告', '贷款年利率不是数字!',QMessageBox.Yes )
        return None,None,None
    
    period = self.textEdit_3.toPlainText() # 贷款期限30年
    if isInt(period):
        #print('贷款期限是整数!');
        pass
    else:
        print('贷款期限不是数字!');
        #show_message(self,'贷款期限不是数字!')
        QMessageBox.critical(self, '警告', '贷款期限不是整数!',QMessageBox.Yes )  
        return None,None,None
    return loan,annualRate,period
class MainWindow(QMainWindow,Ui_MainWindow):
    outPutList=[]
    def __init__(self, parent=None):
        super(MainWindow, self).__init__(parent)
        #loadUi('test.ui', self)
        self.setupUi(self)
        self.pushButton.clicked.connect(self.say)
        self.pushButton_2.clicked.connect(self.closew)
        self.pushButton_3.clicked.connect(self.outPExl)
    def show_message(self,errMsgstr):
        QMessageBox.critical(self,  errMsgstr)   
    def say(self):      
        try:  
            loan,annualRate,period=checkInput(self)
            if(loan is None):
                print("输出检查出问题了!")
                return
            print(loan)
            print(annualRate)
            print(period)
            f_load=float(loan)*10000
            f_monthRate = float(annualRate)/1200 # 贷款月利率
            int_period=int(period)
            retList=[]
            sFlag=self.comboBox.currentText()   # 获得当前内容
            if sFlag=='等额本金':
                
                retList=calF1(f_load,f_monthRate,int_period)
            else:
                retList=calF2(f_load,f_monthRate,int_period)
            self.tableWidget.setRowCount(len(retList))
            self.tableWidget.setColumnCount(4)
            self.tableWidget.setHorizontalHeaderLabels(['期数(月)','应还金额','应还本金','应还利息'])
            self.tableWidget.verticalHeader().hide()  #隐藏默认的行编号
           
            rownum=0
            columnum=0
            for row in retList:
                for column in row:
                    
                    item=QTableWidgetItem(str(column))
                    #设置每个位置的文本值
                    self.tableWidget.setItem(rownum,columnum,item)
                    #print(rownum,columnum,item)
                    columnum=columnum+1
                columnum=0
                rownum=rownum+1
            self.outPutList=retList
        except Exception as e:
            print(e)
    def closew(self):  
        try:                         
            self.close()
            sys.exit(app.exec())
        except Exception as e:
            print(e)
    #导出Excel        
    def outPExl(self):  
        try:  
            #print(self.outPutList)
            outExlTo(self.outPutList)                      
            print('导出成Excel文件!')
            QMessageBox.critical(self, '成功', '导出文件结果为outPut.xls!',QMessageBox.Yes )
        except Exception as e:
            print(e)   
            

    
app = QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec())


========================================================================

窗体部分代码使用Qt5 Designer拖拽实现,就不贴代码了。

实现效果如下:

Python 房贷计算器小工具_第1张图片

 

实现完成后,发现一个巨大的坑,包括各大网站/银行公布的计算器都有这个问题。总账和分账不平衡。

期数(月) 应还金额 应还本金 应还利息
1 17213.29 16213.29 1000
2 17213.29 16294.36 918.93
3 17213.29 16375.83 837.46
4 17213.29 16457.71 755.58
5 17213.29 16540 673.29
6 17213.29 16622.7 590.59
7 17213.29 16705.81 507.48
8 17213.29 16789.34 423.95
9 17213.29 16873.28 340.01
10 17213.29 16957.65 255.64
11 17213.29 17042.44 170.85
12 17213.29 17127.65 85.64
总计 206559.48 200000.06 6559.42

 

如上表所示:分期还的本金之和竟然不等于20万元。这样的账目是无法提交银行或者有对账要求的财务公司的。(有的网上的计算器,甚至应还本金+应还利息不等于应还金额。)

所以,必须要对算法改造。理论上的算法没有考虑元角分单位的不连续性,导致分期本金计算出现了累计误差。

思考后,决定对分期账目进行末期平衡修正,这样会导致最后一笔还款金额不再是等额本息。但是账务是可以平衡了。

调整后账目如下图:

期数(月) 应还金额 应还本金 应还利息
1 17213.29 16213.29 1000
2 17213.29 16294.36 918.93
3 17213.29 16375.83 837.46
4 17213.29 16457.71 755.58
5 17213.29 16540 673.29
6 17213.29 16622.7 590.59
7 17213.29 16705.81 507.48
8 17213.29 16789.34 423.95
9 17213.29 16873.28 340.01
10 17213.29 16957.65 255.64
11 17213.29 17042.44 170.85
12 17213.23 17127.59 85.64
总计 206559.42 200000 6559.42

 

 

=================================================================================

增加的末期调整函数如下:

#调整末期本金账务平衡关系
def cBalance(rowSet,loan):
    rownum=0
    columnum=0  
    setLen=len(rowSet)
    sumBen=0 #累计本金
    for row in rowSet:
        sumBen=sumBen+row[2]
        rownum=rownum+1
        if(rownum>setLen-2):
            break
    print("末期调整:"+str(sumBen))
    print(loan-sumBen)
    endBen=round(loan-sumBen,2)
    endInv=rowSet[setLen-1][3]
    endSum=round(endBen+endInv,2) #末期本息=末期本金+末期利息
    print(endSum)
    rowSet[setLen-1][1]=endSum
    rowSet[setLen-1][2]=endBen

 

=================================================================================

计算器下载链接:https://download.csdn.net/download/rishengcsdn/19122239?spm=1001.2014.3001.5503

只支持win10操作系统运行。

 

 

 

 

你可能感兴趣的:(金融知识,python)