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

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

 

根据房贷计算公式:

等额本息计算方式

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

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

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

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

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

等额本金计算方式

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

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

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

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

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

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

第一版实现代码如下:

#  coding=utf-8
 # 房贷计算器,计算等额本金和等额本息得计算器,可以导出Excelimport sys
 from PyQt5.QtWidgets import QApplication, QMainWindow,QTableWidgetItem,QMessageBox
 from w7 import Ui_MainWindowimport xlwt
 import xlrddef 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拖拽实现,就不贴代码了。

实现效果如下:

Java 房贷提前换款计算 提前还房贷计算器明细_拖拽

 

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

期数(月)

应还金额

应还本金

应还利息

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

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

计算器下载链接:

只支持win10操作系统运行。