对几种常见贷款进行数据分析
最近在看《Python编程导论》(第二版), 看到类那一章时,后面有个对比几种不同类型贷款的例子,有一天在回看时,突然发现,这不就是一种量化的方式么?之前本来是草草地一带而过,现在又来了兴趣,打算仔细研究研究。
注:本文对书中原有的代码基本不做改动,主要通过注释的方式进行说明,并增加了另一种贷款方式进行比较,旨在帮助大家对代码做进一步分析。
def findPayment(loan, r, m):
# 计算在贷款额loan 月利率r和期限m个月下,每月需返还的固定金额
return loan * ((r * (1 + r) ** m) / ((1 + r) ** m - 1))
class Mortgage(object):
# 构建贷款类, 定义四种贷款都有的属性
def __init__(self, loan, annRate, months):
self.loan = loan # 贷款总额
self.rate = annRate / 12 # 将年利率转化为月利率
self.months = months # 还款月数
self.paid = [0.0] # 已支付金额
self.outstanding = [loan] # 剩余本金
self.payment = findPayment(loan, self.rate, months) # 每月还款金额, 默认按照等额本息的方式
self.legend = None # 贷款描述
def makePayment(self):
# 还款函数,调用此函数进行还款
self.paid.append(self.payment)
reduction = self.payment - self.outstanding[-1] * self.rate # 还款金额中的本金
self.outstanding.append(self.outstanding[-1] - reduction) # 记录剩余本金
def getTotalPaid(self):
# 返回已支付贷款总额
return sum(self.paid)
def __str__(self):
# 返回贷款描述
return self.legend
class Fixed(Mortgage):
# 等额本息子类
# 只需继承Mortgage(),重写描述属性self.legend即可
def __init__(self, loan, r, months):
Mortgage.__init__(self, loan, r, months)
self.legend = "等额本息, " + str(round(r * 100, 2)) + \'%\'
def totalCapitalPayment(loan, months, r):
# 等额本金类贷款每个月要还的本金不变,而利息是随着还款月数的增加而减少的,所以定义一个函数,直接返回一个包含每月还款金额的列表即可
total = []
for m in range(months):
total.append(loan / months + (loan - m * (loan / months)) * r)
return total
class FixedCapital(Mortgage):
# 等额本金子类
def __init__(self, loan, r, months):
Mortgage.__init__(self, loan, r, months)
# self.payment =capitalPayment(loan, r, months)
self.capital = loan / months
self.total = totalCapitalPayment(loan, months, self.rate)
self.legend = "等额本金, " + str(round(r * 100, 2)) + \'%\'
def makePayment(self, m):
# 由于等额本金的每月还款额与当前还款月数相关,所以引入还款月数m
self.paid.append(self.total[m])
# 本期剩余本金 = 上期剩余本金 - 每月固定本金
self.outstanding.append(self.outstanding[-1] - self.capital)
class FixedWithPts(Mortgage):
# 固定点数子类
def __init__(self, loan, r, months, pts):
Mortgage.__init__(self, loan, r, months)
self.pts = pts # 固定点数
# 计算第一次按照固定点数还款的金额
self.paid = [self.loan * (self.pts / 100)]
self.legend = "固定点数, " + str(round(r * 100, 2)) + \'%, \' + str(pts) + \' points\'
class TwoRate(Mortgage):
# 双利率子类
def __init__(self, loan, r, months, teaserRate, teaserMonths):
Mortgage.__init__(self, loan, teaserRate, months)
self.teaserMonths = teaserMonths # 前期低利率月数
self.teaserRate = teaserRate # 前期低利率
self.nextRate = r / 12 # 后期高利率
self.legend = \'双利率, \' + str(teaserRate * 100) + \'% for \' + str(self.teaserMonths) + \' months, then \' + \
str(round(r * 100, 2)) + \'%\'
def makePayment(self):
# 如果到达teaserMonths,则使用self.nextRate高利率,后面每月的付款金额按照剩余本金、利率和月数重新计算
if len(self.paid) - 1 == self.teaserMonths:
self.rate = self.nextRate
self.payment = findPayment(self.outstanding[-1], self.rate, self.months - self.teaserMonths)
Mortgage.makePayment(self) # 未到达teaserMonths时,每月的还款金额
def compareMortgages(amt, years, fixedRate, pts, ptsRate, lowRate, highRate, lowMonths):
# 比较各类贷款的总还款额
totMonths = years * 12
fixed1 = Fixed(amt, fixedRate, totMonths)
fixed2 = FixedCapital(amt, fixedRate, totMonths)
fixed3 = FixedWithPts(amt, ptsRate, totMonths, pts)
twoRate = TwoRate(amt, highRate, totMonths, lowRate, lowMonths)
morts = [fixed1, fixed3, twoRate] # 先对除等额本金外的其他三类贷款进行还款
for m in range(totMonths):
for mort in morts:
mort.makePayment()
fixed2.makePayment(m) # 单独调用包含参数m的等额本金还款参数
morts.insert(1, fixed2) # 还款完毕后再加入贷款列表
# 展示四种贷款方式各自的还款总额
for m in morts:
print(m)
print(" Total payments = $" + str(int(m.getTotalPaid())))
# 带入实际的值进行比较:
compareMortgages(200000, 30, 0.07, 3.25, 0.05, 0.045, 0.095, 48)
# 比较结果
Fixed, 7.0%
Total payments = $479017
Fixed Capital, 7.0%
Total payments = $410583
Fixed, 5.0%, 3.25 points
Total payments = $393011
4.5% for 48 months, then 9.5%
Total payments = $551444
从比较结果可以看出,还款总额从高到低依次为:
双利率 > 等额本息 > 等额本金 > 固定点数
def plotPayments(self, style):
# 统计每月还款额
pylab.plot(self.paid[1:], style, label=self.legend)
def plotTotPd(self, style):
# 统计每月还款总额
totPd = [self.paid[0]]
for i in range(len(self.paid)):
totPd.append(totPd[-1] + self.paid[i])
pylab.plot(totPd, style, label=self.legend)
def plotBalance(self, style):
# 统计每月剩余本金
pylab.plot(self.outstanding, style, label=self.legend)
def plotNet(self, style):
# 统计每月支付总利息
totPd = [self.paid[0]]
for i in range(1, len(self.paid)):
totPd.append(totPd[-1] + self.paid[i])
# 先通过数组计算出每月偿还的本金(贷款总额 - 每月剩余本金)
equityAcquired = pylab.array([self.loan] * len(self.outstanding)) - pylab.array(self.outstanding)
# 再用每月的总还款额 - 每月偿还本金 = 每月偿还利息
net = pylab.array(totPd) - equityAcquired
pylab.plot(net, style, label=self.legend)
def plotMortgages(morts, amt):
def labelPlot(figure, title, xLabel, yLabel):
pylab.figure(figure) # 指定当前图,即绘制前要先指定图的figure值
pylab.title(title) # 设置标题
pylab.xlabel(xLabel) # 设置x轴标签
pylab.ylabel(yLabel) # 设置y轴标签
pylab.legend(loc=\'best\') # 将描述信息放在不与曲线冲突的最合适区域
styles = [\'k-\', \'k-.\', \'k:\', \'b-\'] # 设置各类贷款对应的曲线样式
payments, cost, balance, netCost = 0, 1, 2, 3 # 设置图的figure,对各类曲线按照指标分类
for i in range(len(morts)):
pylab.figure(payments) # 根据figure值,将各类曲线绘制到对应的图中
morts[i].plotPayments(styles[i]) # 月还款额
pylab.figure(cost)
morts[i].plotTotPd(styles[i]) # 总还款额
pylab.figure(balance)
morts[i].plotBalance(styles[i]) # 剩余本金
pylab.figure(netCost)
morts[i].plotNet(styles[i]) # 月支付利息
labelPlot(payments, \'贷款\' + str(amt) + \'元的每月还款情况\', \'月数\', \'月还款金额\')
labelPlot(cost, \'贷款\' + str(amt) + \'元的还款总额\', \'月数\', \'已支付金额\')
labelPlot(balance, \'贷款\' + str(amt) + \'元的每月本金剩余情况\', \'月数\', \'剩余未还本金\')
labelPlot(netCost, \'贷款\' + str(amt) + \'元的累计利息支付情况\', \'月数\', \'支付的累计利息\')
def compareMortgages(amt, years, fixedRate, pts, ptsRate, lowRate, highRate, lowMonths):
# 比较各类贷款的总还款额
totMonths = years * 12
""" ... 此处省略前面的部分代码 ... """
# for m in morts:
# print(m)
# print(" Total payments = $" + str(int(m.getTotalPaid())))
# 展示四种贷款方式的对比曲线
plotMortgages(morts, amt)
pylab.show()
import matplotlib
# 修改默认字体
matplotlib.rcParams[\'font.sans-serif\'] = [\'SimHei\']
matplotlib.rcParams[\'font.family\'] = \'sans-serif\'
可以看出,等额本息和固定点数从始至终都维持着恒定的还款金额,但是由于固定点数提前还了一部分的贷款,所以后期支付的金额就会少一些;
该图反映了总还款额随时间变化的情况。如果对比最后贷款结束时的还款金额,很明显,固定点数 < 等额本金 < 等额本息 < 双利率。