Python 房贷计算器小工具

博客介绍了使用Python实现等额本金和等额本息房贷计算器的过程,以及在实际计算中遇到的总账与分账不平衡问题。作者通过末期调整函数实现了账务平衡,确保分期本金之和等于贷款总额。文章还提供了代码下载链接。
摘要由CSDN通过智能技术生成

根据网上的网贷计算公式实现了一个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拖拽实现,就不贴代码了。

实现效果如下:

 

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

期数(月)应还金额应还本金应还利息
117213.2916213.291000
217213.2916294.36918.93
317213.2916375.83837.46
417213.2916457.71755.58
517213.2916540673.29
617213.2916622.7590.59
717213.2916705.81507.48
817213.2916789.34423.95
917213.2916873.28340.01
1017213.2916957.65255.64
1117213.2917042.44170.85
1217213.2917127.6585.64
总计206559.48200000.066559.42

 

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

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

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

调整后账目如下图:

期数(月)应还金额应还本金应还利息
117213.2916213.291000
217213.2916294.36918.93
317213.2916375.83837.46
417213.2916457.71755.58
517213.2916540673.29
617213.2916622.7590.59
717213.2916705.81507.48
817213.2916789.34423.95
917213.2916873.28340.01
1017213.2916957.65255.64
1117213.2917042.44170.85
1217213.2317127.5985.64
总计206559.422000006559.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操作系统运行。

 

 

 

 

评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值