天天看點

對幾種常用貸款進行資料分析

最近在看《Python程式設計導論》(第二版), 看到類那一章時,後面有個對比幾種不同類型貸款的例子,有一天在回看時,突然發現,這不就是一種量化的方式麼?之前本來是草草地一帶而過,現在又來了興趣,打算仔細研究研究。

注:本文對書中原有的代碼基本不做改動,主要通過注釋的方式進行說明,并增加了另一種貸款方式進行比較,旨在幫助大家對代碼做進一步分析。

一、什麼是是量化?

“量化”從字面上可以了解為數量化,如果應用在投資領域上的話,簡單的說,就是将每一種投資産品,通過資料分析的方式,将其特點(風險、潛力等等)用詳細的資料反映出來,更好地指導投資者擷取穩定收益。

其實作在很多金融産品都是非常複雜的,普通投資者沒點金融常識根本看不懂。不會計算産品的實際收益,看不到潛在的風險,很容易就被套住了。是以,如果有量化分析的經驗,就可以對産品有比較清楚的了解。

好了,先來看看書中描述幾種貸款方式吧:

1.每月償還固定金額的貸款(簡稱等額本息貸款);

2.先還一筆固定點數(百分比)的貸款,然後每月再以較低的利率還款(簡稱固定點數貸款);

3.先以較低的利率還款,滿x個月後再以較高的利率還款(簡稱雙利率貸款)。

二、簡述各種貸款類型

1.等額本息貸款

在查等額本息的概念時,發現還有一種叫做等額本金的貸款方式也會被經常用到,下面将依次做介紹。

等額本息:根據固定的還款時間,計算出應還的總利息,再加上本金,然後每個月平均等額的還款。

根據貸款總額loan、月利率r及還款時間m(月),可以推導出每個月的還款金額。

假設每月固定還款X,則每月的還款情況如下:

月數 | 每月剩餘欠款

0 | loan

1 | loan*(1 + r) - X

2 | [loan*(1 + r) - X] * (1 + r) - X => loan*(1+r)^2 - X*(1+r) -X

3 | [loan*(1+r)^2 - X*(1+r) -X]*(1+r) - X => loan*(1+r)^3 - X*(1+r)^2 -X*(1+r) -X

... ...

n | loan*(1+r)^n - X*(1+r)^(n-1) -X*(1+r)^(n-2) - ... -X(1+r)^0           

當n=m時,欠款為0,即:

loan*(1+r)^m - X*(1+r)^(m-1) - X*(1+r)^(m-2) - ... - X(1+r)^0 = 0

觀察等号左邊,不看第一項,提取其餘多項式的-X公約數後,發現其實是一個首項a1為1, q=1+r,項數為m的等比數列

根據等比數列的求和公式Sn = a1*(1 - q^n)/(1 - q) 得:

loan*(1+r)^n - X[(1+r)^0 + (1+r)^1 + ... + (1+r)^(m-1)] = 0

loan*(1+r)^n -X*(1-(1+r)^m) / (-r) = 0

即:

loan*(1+r)^n = X*[(1+r)^m-1]/r

是以得到:

X = loan*r*(1+r)^n/[(1+r)^m-1]

那麼針對等額本息的還款方式,我們就可以用Python寫一個專門的函數,這樣就不用每次計算的時候都把這個公式推導一遍了:

import pylab


import matplotlib


# 指定預設字型


matplotlib.rcParams['font.sans-serif'] = ['SimHei']


matplotlib.rcParams['font.family'] = 'sans-serif'


 

 # 計算在貸款額loan 月利率r和期限m個月下,每月需返還的固定金額


def findPayment(loan, r, m):


 return loan * ((r * (1 + r) ** m) / ((1 + r) ** m - 1))           

這樣,每次在計算等額本息貸款的每月還款金額時,隻需要将貸款總額loan, 月利率r和還款總月數m代入此函數中,就能得出結果了。

2.等額本金貸款

等額本金相對來說要簡單一些,每月所還的本金是相同的,利息由每個月的剩餘本金計算得出。

第n個月需還的金額:(loan/m) + (loan - n * loan/m) * r

3.固定點數貸款

按照定義,我們在首次還款時先按固定的點數還一部分貸款,然後再按較低的利率還完剩餘的貸款。

4.雙利率貸款

前x個月以較低的r1利率還款,後m-x個月以較高的r2利率還款(假設還款總月數為m)。

三、編寫與貸款相關的類及子類

我們研究的貸款類型一共有4種(書中的3種+等額本金貸款),是以最好的辦法是先定義一個貸款大類,裡面包含貸款的基本屬性,然後根據各種類型貸款的不同,再定義不同的子類。具體過程如下:

class Mortgage(object):


 # 建構貸款類, 定義四種貸款都有的屬性


 def __init__(self, loan, annRate, months):


 self.loan = loan # 貸款總額


 self.rate = annRate / 12 # 将年利率轉化為月利率


 self.outstanding = [loan] # 剩餘本金


 self.months = months # 還款月數


 self.paid = [0.0] # 已支付金額

 self.legend = None # 貸款描述


 self.payment = findPayment(loan, self.rate, months) # 每月還款金額, 預設按照等額本息的方式






 def makePayment(self):

 # 還款函數,調用此函數進行還款

 self.outstanding.append(self.outstanding[-1] - reduction) # 記錄剩餘本金


 self.paid.append(self.payment)


 reduction = self.payment - self.outstanding[-1] * self.rate # 還款金額中的本金







 def getTotalPaid(self):

# 傳回已支付貸款總額

 return self.legend


 return sum(self.paid)







 def __str__(self):


 # 傳回貸款描述


class Fixed(Mortgage):


 # 等額本息子類


 # 隻需繼承Mortgage(),重寫描述屬性self.legend即可


 def __init__(self, loan, r, months):


 self.legend = "等額本息, " + str(round(r * 100, 2)) + '%'


 Mortgage.__init__(self, loan, r, months)


 

 # 等額本金類貸款每個月要還的本金不變,而利息是随着還款月數的增加而減少的,是以定義一個函數,直接傳回一個包含每月還款金額的清單即可


def totalCapitalPayment(loan, months, r):


 total = []

 for m in range(months):

class FixedCapital(Mortgage):


 total.append(loan / months + (loan - m * (loan / months)) * r)


 return total



 # 等額本金子類

 def __init__(self, loan, r, months):

 self.total = totalCapitalPayment(loan, months, self.rate)


 Mortgage.__init__(self, loan, r, months)


 # self.payment =capitalPayment(loan, r, months)


 self.capital = loan / months

 # 本期剩餘本金 = 上期剩餘本金 - 每月固定本金


 self.legend = "等額本金, " + str(round(r * 100, 2)) + '%'






 def makePayment(self, m):


 self.paid.append(self.total[m])



 # 由于等額本金的每月還款額與目前還款月數相關,是以引入還款月數m


 Mortgage.__init__(self, loan, r, months)


 self.outstanding.append(self.outstanding[-1] - self.capital)







class FixedWithPts(Mortgage):

# 固定點數子類

 self.pts = pts # 固定點數



 def __init__(self, loan, r, months, pts):

 # 計算第一次按照固定點數還款的金額

 def __init__(self, loan, r, months, teaserRate, teaserMonths):


 self.paid = [self.loan * (self.pts / 100)]


 self.legend = "固定點數, " + str(round(r * 100, 2)) + '%, ' + str(pts) + ' points'







class TwoRate(Mortgage):

# 雙利率子類

 str(round(r * 100, 2)) + '%'



 Mortgage.__init__(self, loan, teaserRate, months)

 self.teaserMonths = teaserMonths # 前期低利率月數

 self.nextRate = r / 12 # 後期高利率



 self.teaserRate = teaserRate # 前期低利率


 self.legend = '雙利率, ' + str(teaserRate * 100) + '% for ' + str(self.teaserMonths) + ' months, then ' + \







 def makePayment(self):


 Mortgage.makePayment(self) # 未到達teaserMonths時,每月的還款金額


 # 如果到達teaserMonths,則使用self.nextRate高利率,後面每月的付款金額按照剩餘本金、利率和月數重新計算



 if len(self.paid) - 1 == self.teaserMonths:


 self.payment = findPayment(self.outstanding[-1], self.rate, self.months - self.teaserMonths)



 self.rate = self.nextRate           

四、使用pylab定義繪制還款曲線函數

繪制曲線的目的在于比較這幾種貸款,哪個最劃算(或者哪個最坑),是以可以按照每月還款額、還款總額、剩餘本金、支付的總利息這幾個次元進行分析。

在Mortgage()類中分别添加統計這些資訊的函數:

def plotPayments(self, style):


 # 統計每月還款額


 pylab.plot(self.paid[1:], style, label=self.legend)







def plotTotPd(self, style):


 # 統計每月還款總額


 totPd.append(totPd[-1] + self.paid[i])



 totPd = [self.paid[0]]


 for i in range(len(self.paid)):



def plotBalance(self, style):



 pylab.plot(totPd, style, label=self.legend)



 # 統計每月剩餘本金


 totPd = [self.paid[0]]



 pylab.plot(self.outstanding, style, label=self.legend)



def plotNet(self, style):

# 統計每月支付總利息

 equityAcquired = pylab.array([self.loan] * len(self.outstanding)) - pylab.array(self.outstanding)



 for i in range(1, len(self.paid)):


 totPd.append(totPd[-1] + self.paid[i])


 # 先通過數組計算出每月償還的本金(貸款總額 - 每月剩餘本金)


 pylab.plot(net, style, label=self.legend)



 # 再用每月的總還款額 - 每月償還本金 = 每月償還利息



 net = pylab.array(totPd) - equityAcquired           

為了能夠繪制出這些曲線,我們還需要定義一個繪圖函數,并且設定标題、标簽等屬性,讓每個圖都更加美觀:

def plotMortgages(morts, amt):


 def labelPlot(figure, title, xLabel, yLabel):


 pylab.figure(figure) # 指定目前圖,即繪制前要先指定圖的figure值


 pylab.title(title) # 設定标題


 pylab.ylabel(yLabel) # 設定y軸标簽


 pylab.xlabel(xLabel) # 設定x軸标簽


 styles = ['k-', 'k-.', 'k:', 'b-'] # 設定各類貸款對應的曲線樣式


 pylab.legend(loc='best') # 将描述資訊放在不與曲線沖突的最合适區域


 for i in range(len(morts)):


 payments, cost, balance, netCost = 0, 1, 2, 3 # 設定圖的figure,對各類曲線按照名額分類


 morts[i].plotTotPd(styles[i]) # 總還款額


 pylab.figure(payments) # 根據figure值,将各類曲線繪制到對應的圖中


 morts[i].plotPayments(styles[i]) # 月還款額

 morts[i].plotNet(styles[i]) # 月支付利息


 pylab.figure(cost)

 pylab.figure(balance)

 morts[i].plotBalance(styles[i]) # 剩餘本金


 pylab.figure(netCost)

 labelPlot(balance, '貸款' + str(amt) + '元的每月本金剩餘情況', '月數', '剩餘未還本金')


 labelPlot(payments, '貸款' + str(amt) + '元的每月還款情況', '月數', '月還款金額')


 labelPlot(cost, '貸款' + str(amt) + '元的還款總額', '月數', '已支付金額')

 labelPlot(netCost, '貸款' + str(amt) + '元的累計利息支付情況', '月數', '支付的累計利息')           

最後在比較函數compareMortgages()函數中進行調用即可:

def compareMortgages(amt, years, fixedRate, pts, ptsRate, lowRate, highRate, lowMonths):



 # 比較各類貸款的總還款額


 totMonths = years * 12


 fixed1 = Fixed(amt, fixedRate, totMonths)



 fixed3 = FixedWithPts(amt, ptsRate, totMonths, pts)



 fixed2 = FixedCapital(amt, fixedRate, totMonths)


 twoRate = TwoRate(amt, highRate, totMonths, lowRate, lowMonths)



 fixed2.makePayment(m) # 單獨調用包含參數m的等額本金還款參數



 morts = [fixed1, fixed3, twoRate] # 先對除等額本金外的其他三類貸款進行還款


 for m in range(totMonths):

for mort in morts:
 morts.insert(1, fixed2) # 還款完畢後再加入貸款清單


 mort.makePayment()


 pylab.show()



 # 展示四種貸款方式各自的還款總額


 # for m in morts:

 # print(" Total payments = $" + str(int(m.getTotalPaid())))


 # print(m)




 plotMortgages(morts, amt)


# 帶入實際的值進行比較:


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 = $393011


Total payments = $410583


Fixed, 5.0%, 3.25 points

4.5% for 48 months, then 9.5%


Total payments = $551444           

五、分析各類曲線

最後我們得到了4張反應貸款各個名額的分析圖:

1.每月還款額的變化:

對幾種常用貸款進行資料分析

可以看出,等額本息和固定點數從始至終都維持着恒定的還款金額,但是由于固定點數提前還了一部分的貸款,是以後期支付的金額就會少一些;

等額本金是唯一一個利率呈負增長的貸款,因為每月的還款額與剩餘本金息息相關,是以一開始支付的金額是最高的,但利息會随着本金的不斷減少,到了最後一個月,甚至隻還了558塊多。适合貸款前期有充裕的錢進行還款的人群;

雙利率雖然前48個月的還款額最低,但是後面的300多個月,都不得不付給銀行高額的貸款。

2.整個貸款周期的本金+利息總和:

對幾種常用貸款進行資料分析

該圖反映了總還款額随時間變化的情況。如果對比最後貸款結束時的還款金額,很明顯,固定點數 < 等額本金 < 等額本息 < 雙利率。

而在整個還款過程中,總還款額的增長速率也并非一直不變。例如,除了等額本息和固定點數外(每月還款數固定),等額本金的增長幅度是先快後慢(曲線是一段弧線);而雙利率的模式則是先慢後快,有一個明顯的拐點。用前期很少的還款額做誘餌,引誘借款人最終償還高達551444元的高額貸款(别緊張,你沒有看錯,200000元的本金+351444元的利息)。

3.剩餘未還本金的情況:

對幾種常用貸款進行資料分析

因為4類貸款的本金都是200000萬,是以起點是相同的。曲線越陡峭,則說明,本金的還款速度越快;曲線越平滑,就說明可能每個月給銀行還的錢大部分都用來付利息了。

4.每月累計支付的利息情況:

對幾種常用貸款進行資料分析

大體上都呈抛物線,可以看到,即使最劃算的固定點數模式的貸款,在經曆了30年後,也不得不支付高達193011元的利息,基本上已經跟本金差不多了。是以,這就是銀行用複利在賺很多借款人的錢。而大部分人可能還覺得,這樣的還款方式好像還OK,但是如果經過了這樣量化分析後,感覺還是挺吓人的,是以沒事還是少貸款吧。

六、結語

通過這樣一步步的分析,我們在了解了這些貸款的各種資訊後,同時也提高了自己的編碼能力和資料分析的能力,簡直是一舉三得~這本書中還有一些其他很有意思的問題,涉及面很廣,有興趣的同學可以做深入研究。

原文釋出時間為:2018-08-27

本文作者:劉亞輝

本文來自雲栖社群合作夥伴“

Python愛好者社群

”,了解相關資訊可以關注“

”。