前段時間豌熄,工作中遇到了一個難題,網(wǎng)上查了很多資料也沒有明確的解決方案物咳,后來自己想到一種解決方法锣险,決定分享出來。
下面先說下問題:如何計算irr(內(nèi)部收益率)
我們先來看下百度百科對這1名詞的解釋和計算方式的描述:
看完上面的解釋览闰,給了一大堆公式芯肤,還是沒有告訴怎么求,查看其它資料也大部分言盡于此压鉴,固執(zhí)的我看了更多的名詞解釋崖咨,終于明白了這個是怎么來的,大概就是下面這個公式:
上面公式中的y和a1到an都是已知量油吭,x就是要求的irr击蹲。這個公式怎么來的,或者說在生活中的具體例子是什么呢上鞠,可以這樣理解:
小明往銀行存錢际邻,假設(shè)月利率是1%(只為舉例,實際生活中肯定是低于此值的)芍阎,1月到12月每月存入100塊世曾,問到年底小明能拿回多少錢,肯定是像下圖這樣算。
那么問題再升級一下轮听,如果小明每月存入的金額不固定了骗露,用an表示每月存入的金額,那結(jié)果怎么算呢血巍?那其實還是很簡單萧锉,把上面公式的100改成a1到a12就好了,如下公式:
那么問題再次升級述寡,如果告訴你小明年底能拿到1000元柿隙,每期存的金額分別是a1到a12,假設(shè)利率是穩(wěn)定不變的鲫凶,此時問你利率是多少禀崖,你該怎么算呢?首先肯定還是列出下面的公式:
公式是有了螟炫,但是這是一個高階方程波附,該如何求解呢,縱觀數(shù)學(xué)史上昼钻,方程一旦高于5次是沒有求根公式的掸屡,但是偉大的先賢們還是找到了一些解決方案,就是通過多次迭代之后然评,近似的找到方程的解仅财。今天要介紹的就是幾種迭代方案中的一種——牛頓迭代,用于求解f(x)=0的解:
下面我們來看看牛頓迭代的原理:
首先我們根據(jù)泰勒公式碗淌,我們知道f(x)可由其鄰域x0的n階導(dǎo)數(shù)的表達(dá)式近似表示满着,公式如下:
因為公式中后面項的分母越來越大,其對f(x)值的影響就越來越小贯莺,所以我們?nèi)【€性部分风喇,再通過迭代的方式可達(dá)到近似求解的目的:
1、取泰勒展開的線性部分
2缕探、解線性部分的方程
3魂莫、對第2部不斷進(jìn)行迭代,直到到達(dá)一定次數(shù)或者滿足自己所求的精度爹耗,停止迭代耙考。
4、幾何意義
第二步的公式中的f'(x0)可以看做△f(x0)/△x0=(f(x0)-0)/(x0-x1)=f(x0)/(x0-x1)潭兽,所以原式可化作x=x0-f(x0)/(f(x0/(x0-x1)))=x0-(x0-x1)=x1倦始,這相當(dāng)于做了一步什么操作呢,就是從(x0,f(x0))的點做函數(shù)f(x)的切線山卦,交x軸于x1鞋邑,然后從(x1,f(x1))的點做函數(shù)f(x)的切線,交x軸于x2,不斷迭代枚碗,我們會發(fā)現(xiàn)f(xn)會越來越趨近于0逾一,這也就是牛頓迭代的中心思想。
方法是有了肮雨,可是我們怎么用程序?qū)崿F(xiàn)呢遵堵,因為公式中涉及了導(dǎo)數(shù),需要用到微分怨规,當(dāng)然陌宿,你可以用數(shù)學(xué)的方式把導(dǎo)數(shù)的表達(dá)式都計算好了,然后再通過代碼的形式帶入波丰,但是這顯然不夠智能限番,如果函數(shù)f(x)會發(fā)生變化的話,每次還要去改代碼呀舔,非常的不靈活。
在此介紹一個非常好用的模塊扩灯,python的sympy模塊媚赖,通過這個模塊,我們可以非常方便的算出微積分的結(jié)果表達(dá)式珠插,下面來看demo:
from sympy import *
# 定義一個數(shù)學(xué)符號x
x, y = symbols('x y')
# 定義一個數(shù)學(xué)表達(dá)式
exam = 3*x**4 + 5*x**3 + x**2 + 8*x + 6
# ======================求導(dǎo)
# 求導(dǎo)
print(exam.diff(x))
print(diff(exam, x))
# 結(jié)果:12*x**3 + 15*x**2 + 2*x + 8
# 求二次導(dǎo)
print(exam.diff(x, x))
print(diff(exam, x, x))
# 結(jié)果:2*(18*x**2 + 15*x + 1)
# 可以先將計算公式存儲到一個變量中惧磺,然后再通過doit計算,其實和diff的效果是一樣的
deriv = Derivative(exam, x, x)
print(deriv.doit())
# 結(jié)果:2*(18*x**2 + 15*x + 1)
# ======================求積分
# 求不定積分
print(integrate(exam, x))
print(exam.integrate(x))
# 結(jié)果:3*x**5/5 + 5*x**4/4 + x**3/3 + 4*x**2 + 6*x
# 二次不定積分
print(integrate(exam, x, x))
print(exam.integrate(x, x))
# 結(jié)果:x**6/10 + x**5/4 + x**4/12 + 4*x**3/3 + 3*x**2
# 定積分捻撑,x在0到2的積分
print(integrate(exam, (x, 0, 2)))
print(exam.integrate((x, 0, 2)))
# 結(jié)果:1048/15
# 二元積分(可定積分和不定積分結(jié)合)
print(integrate(x**2 + y**2, (x, 0, 1), (y, 0, 1)))
# 結(jié)果:2/3
print((x**2 + y**2).integrate((x, 0, 1), y))
# 結(jié)果:y**3/3 + y/3
# ======================求極限
print(limit(sin(x)/x, x, 0))
# 結(jié)果:1
print(limit(sin(x)/x, x, oo))
# 結(jié)果:0
# 當(dāng)x趨于0時磨隘,從負(fù)方向或是正方向逼近,可能會有不同的結(jié)果
print(limit(1/x, x, 0, '+'))
# 結(jié)果:oo
print(limit(1/x, x, 0, '-'))
# 結(jié)果:-oo
# ======================求泰勒展開公式
# 傳入的參數(shù)分別為x0的取值顾患、泰勒展開項數(shù)
print((x**4).series(x, 2, 2))
# 結(jié)果:-48 + 32*x + O((x - 2)**2, (x, 2))
print(series((x**4), x, 2, 6))
# 結(jié)果:32*x + (x - 2)**4 + 8*(x - 2)**3 + 24*(x - 2)**2 - 48
好了番捂,有了以上知識為基礎(chǔ)就可以解決問題了,還是回到上面的公式江解,為了方便碼字设预,我們假設(shè)小明每期的投資都是50元,上面的公式可表示為:
可以轉(zhuǎn)化為f(x)=0的形式:
接下來初始化一個x0犁河,比如0.2鳖枕,然后一步步進(jìn)行迭代x0,直到f(x0)足夠接近0桨螺,或是達(dá)到一定輪數(shù)停止宾符,即可得到近似解,下面用代碼進(jìn)行展示:
from sympy import *
x = Symbol('x')
# 獲取fx的表達(dá)式
def get_fx():
result = 10000
for i in range(1, 13):
result -= 50 * (1 + x)**i
return result
# 獲取fx導(dǎo)數(shù)的表達(dá)式
def get_diff_fx():
fx = get_fx()
diff_fx = fx.diff(x)
return diff_fx
# 獲取函數(shù)的值
def get_fx_value(fx, x, limit_val):
return limit(fx, x, limit_val)
# 牛頓迭代計算近似解
def newton_iter():
# 初值灭翔,根據(jù)經(jīng)驗選取魏烫,如果選取的值與最終求得值得接近,會減少迭代次數(shù),提高程序效率
x0 = 0.2
# 精度要求则奥,當(dāng)誤差小于此值時考润,結(jié)束迭代
prec = 0.0001
# 最多迭代20次
for i in range(15):
fx = get_fx_value(get_fx(), x, x0)
print(fx)
if abs(fx) <= prec:
break
diff_fx = get_fx_value(get_diff_fx(), x, x0)
x0 -= fx / diff_fx
print("共進(jìn)行了%s次迭代,最終的解為%s读处,最終算得的fx的值為%s" % (i, x0, round(fx, 10)))
newton_iter()
# 運行結(jié)果:共進(jìn)行了7次迭代糊治,最終的解為0.403702825570710,最終算得的fx的值為-2.86490831058472e-11
以上為代碼罚舱,我用x0=0.4又做了一次測試井辜,發(fā)現(xiàn)只要迭代3次就是算出結(jié)果了,
共進(jìn)行了3次迭代管闷,最終的解為0.403702825570709粥脚,最終算得的fx的值為-2.86490831058472e-11
以上內(nèi)容適用范圍并不僅限于計算irr或是銀行率,而是所有涉及到高階方程的場景包个。