根据网上的网贷计算公式实现了一个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拖拽实现,就不贴代码了。
实现效果如下:
实现完成后,发现一个巨大的坑,包括各大网站/银行公布的计算器都有这个问题。总账和分账不平衡。
期数(月) | 应还金额 | 应还本金 | 应还利息 |
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操作系统运行。