用python计算基金内部收益率-基于scipy科学计算库的数值解

  最近在计算基金内部收益率的时候首先选择的是用纯python编写的sympy科学计算库,sympy在计算常微分方程,偏微分方程,一般线性方程组以及取微分、积分、极限等方面依靠其强大的符号体系游刃有余,并且编程语法更简单易懂,但是当用其求解高阶非线性方程的时候就暴露出python运行速度慢的短板,而在求解基金内部收益率的时候往往样本区间数据量很多,比如本文接下来将要采用的这支基金的数据就有80期。(其实我之前写完代码点击运行之后就去睡觉了,第二天起来仍然没有算出结果,可以说python面对这种问题从来不耽误人睡觉休息)
  其实python的numpy库已经内置了内部收益率计算函数,可以直接调用numpy.irr([NCF],round(n))。(就不要追问我写这篇博客的意义在哪里了!)接下来本文将会展示调用scipy.optimize最优化函数求内部收益率的高阶非线性方程的数值解。
  开始之前,需要介绍一下基金内部收益率(Internal Rate of Return, IRR)的计算方法,基金内部收益率的计算需要用到基金期末、期初的总净值(Total Net Asset, TNA)和各期现金流(Net Cash Flow, NCF),其计算公式如下:
T N A 0 ( 1 + I R R ) T + ∑ t = 1 T N C F t ( 1 + I R R ) ( T − t ) = T N A T (1) TNA_0{(1+IRR)^T}+\sum_{t=1}^TNCF_t{(1+IRR)^{(T-t)}}=TNA_T \tag{1} TNA0(1+IRR)T+t=1TNCFt(1+IRR)(Tt)=TNAT(1)
  其中 N C F t NCF_t NCFt的计算为:
N C F t = T N A t − T N A t − 1 ( 1 + R t ) (2) NCF_t=TNA_t-TNA_{t-1}(1+R_t)\tag{2} NCFt=TNAtTNAt1(1+Rt)(2)
  这里的 R t R_t Rt表示的是基金第 t t t期的收益率, T N A 0 TNA_0 TNA0 T N A T TNA_T TNAT分别为基金期初的总资产净值和期末的总资产净值。可见利用python的求解内部收益率 I R R IRR IRR速度主要就取决于 T T T的大小。
  首先,展示本文所使用到的数据,数据已经上传到百度云盘(提取码:6whe)。
用python计算基金内部收益率-基于scipy科学计算库的数值解_第1张图片
  导入数据,这里所使用的是基金月度的单月回报 R R R和单月总净值 T N A TNA TNA

>>>import pandas as pd
...Fund=pd.read_csv("C:\\Users\\psj\\Desktop\\Fund.csv",index_col="Date",header=0)
...Fund.index=pd.to_datetime(Fund.index)
>>>print(Fund.tail())
                   R          TNA
Date                             
2019-07-31  0.029619  14488.49821
2019-08-30  0.001370  14508.34547
2019-09-30  0.024624  17798.16869
2019-10-31 -0.006676  17679.35582
2019-11-29 -0.012097  17465.49264

  这里的 T = 80 T=80 T=80,当然如果有好奇用sympy求解内部收益率到底是什么情况的小伙伴,在本文末尾我将展示用sympy计算内部收益率的代码。
   按照公式(2)计算出基金各期的净现金流 N C F t NCF_t NCFt

NCF=Fund.TNA-Fund.TNA.shift(1)*(1+Fund.R)
...print(NCF.head())
Date
2013-03-29             NaN
2013-04-26        0.000013
2013-05-31       -0.000081
2013-06-28       -0.000014
2013-07-31   -59842.034588
dtype: float64

  Python的科学计算库scipy的优化器optimize中提供了基于hybrd和hybrj算法的内置函数fsolve,可以求高阶的非线性方程的数值解。这里需要设定求解内部收益率的方程,其实就是基于本文的数据按照公式(1)编写函数

def func(x):
    function=Fund.TNA[0]*(1+x)**(len(NCF)-1)-Fund.TNA[-1]#len(NCF)包括了NaN空值
    for i in range(1,len(NCF)):   #由于NCF的第一期值为空
        function+=NCF[i]*(1+x)**(len(NCF)-i-1) #i只能取到(len(NCF)-1)
    return function

  fsolve(func,x0)主要有两个参数,func为被求解方程,方程的等号右边为0,左边就是上面所定义的函数;x0为方程func的初始值,以列表的新式输入,返回值也为列表形式。将初始值设定为0,求解最终得到基金的内部收益率为-0.00419273。

from scipy.optimize import fsolve
root=fsolve(func,[0]) # x的初始值设为0,需要用list的形式输入
>>>print(root)
[-0.00419273]

  由于fsolve在优化过程中采用迭代的方式求解非线性方程的数值解,所以我们只得到了一个解,熟悉一元二次抛物线方程的小伙伴都知道这类方程的未知数 x x x最高次有几次就会有多少个解,如果是严格按照这种方法求解,我们应该得到80个解,当然其中包括了复数解。求出这80个解其实是没有必要的,而纯python编写的科学计算库sympy就可以做到,先附上代码

import pandas as pd
import sympy as sy
Fund=pd.read_csv("C:\\Users\\psj\\Desktop\\Fund.csv",index_col="Date",header=0)
Fund.index=pd.to_datetime(Fund.index)
NCF=Fund.TNA-Fund.TNA.shift(1)*(1+Fund.R)
x=sy.symbols("x")
f=Fund.TNA[0]*(1+x)**(len(NCF)-1)-Fund.TNA[-1]
for j in range(1,len(NCF)):
    f=f+NCF[j]*(1+x)**(len(NCF)-j-1)
result=sy.solve(f,x)
print(result)

  要得到运行结果可能是很久之后的事情了,当然也不是本人写的代码有bug算不出来,当我们把 T T T设定为5时,只需要一段小小的等待就能得到方程的全部5个解。

Fund=Fund.iloc[:6,:]
NCF=Fund.TNA-Fund.TNA.shift(1)*(1+Fund.R)
x=sy.symbols("x")
f=Fund.TNA[0]*(1+x)**(len(NCF)-1)-Fund.TNA[-1]
for j in range(1,len(NCF)):
    f=f+NCF[j]*(1+x)**(len(NCF)-j-1)
result=sy.solve(f,x)
>>>print(result)
[-0.00685850972132560, -1.62439354544479 - 0.206726589366808*I, -1.62439354544479 + 0.206726589366808*I, -0.872177199759201 - 0.925545835792086*I, -0.872177199759201 + 0.925545835792086*I]

  最后,我们可以发现采用sympy.solve()求解得到的内部收益率只有第一个是实数,其余全部为复数,这在我之前的实践当中也得到了反复验证,所以可见采用数值计算是完全合理科学高效的。

你可能感兴趣的:(金融数据分析)