python 估值模型_【中金固收·固收+】隐藏价值的角落:限售股AAP估值及Python实现方法(上)...
或许市场不够重视,但这可能也是隐藏价值的一个角落。配售和定增基金往往会持有一些流通受限的股票,而这些股票估值的方法在2017年经历了一次比较大的变化——根据《证券投资基金投资流通受限股票估值指引(试行)》,限售股适用AAP估值法(亚式看跌期权),取代此前近似摊余成本的方法。目前,主要是定增基金和配售基金涉及比较多这类问题。
实事求是地说,即使在以前的简易算法下,虽然公式简单,但数据处理存在一定难度,投资者对于当时(比如2016年)定增基金的净值也存在一定疑虑。而现在公式更为复杂了,对投资者来说黑箱化的程度也自然更高了。在以前的模式下,限售股(比如定增得来的股份)的估值为:V= min(当前价,成本价+(当前价-成本价)*已持有的时间/总锁定期),或者表示如下图。
而新的AAP模型估值法下,估值=市场价*(1 -折扣系数),折扣系数为按照亚式看跌期权计算出的期权价值(LoMD)。公式如下:
可以看出,想比之前近似摊余成本法的计算模式,这里复杂程度还是要高出不少的。其中,σ为该股票波动率,q为分红率,T为以年为单位的锁定时间,其他均为字面意思。实际上,上面公式在实现时基本没有难度,更多的难点在于数据的处理。
下面,我们来一步一步拆解这个LoMD。首先自然需要引入一些常见库:
import readSql as rs
import pandas as pd
from scipy import stats
from WindPy import w
import datetime as dt
其中的readSql为我们惯用的自编库,在此前的转债报告中也曾出现过,但为了不产生太多歧义,我们只用其中的几个函数即str2yyyymmdd、yyyymmdd2str:在日期的两种字符串之间互相转换,例如“2019/1/18”转成“20190118”及其逆运算。
下面我们先展示最后的主函数,然后逐个击破涉及到的中间函数。整个主函数如下所示。第一个参数dfHeld为pandas下的DataFrame型,其中保存的是基金所持限售股的信息(参考年报、半年报以及基金临时公告整理),示例格式如下图。而后面的start和end分别为计算期的起止时间,我们最后返回了在这个期间内,一组限售股的折扣系数,形式同样是DataFrame。
defdiscRatioTable(dfHeld, start,end):
'''输入限售股table,输出这些券对应的LoMD
start:计算首日,yyyy/mm/dd
end:计算终日, yyyy/mm/dd'''
#先计算这些股票的vol
lstStocks = list(dfHeld.index)
nDaysBefore, flag = backToTime(start, list(dfHeld[u'可流通日期'].apply(rs.yyyymmdd2str)))
ifflag:
dfAdjClose = priceData(lstStocks, nDaysBefore, end)
dfLogPct = logPctData(dfAdjClose)
dfLogPct.to_clipboard
else:
printu'没有限售股,不必算了'
returnNone
dfVol = pd.DataFrame(index=dfAdjClose.loc[start:end].index,columns=lstStocks)
forcode inlstStocks:
fordate indfVol.index:
days = _deletaTradingDate(date,rs.yyyymmdd2str(dfHeld.loc[code, u'可流通日期']))
dfVol.loc[date, code] =calcVol(dfLogPct, code, date, days) if days > 0 else None
#然后是q,股息率
dfDiv = dividendYield(lstStocks, start, end)
#最后计算discRatio表
dfDiscRatio = pd.DataFrame(index=dfVol.index, columns=lstStocks)
forcode inlstStocks:
strEnd = rs.yyyymmdd2str(dfHeld.loc[code, u'可流通日期'])
fordate indfDiscRatio.index:
t = deltaT(date, strEnd)
dfDiscRatio.loc[date,code] = aapDisc(dfDiv.loc[date, code], dfVol.loc[date, code], t)
returndfDiscRatio
观察整个函数结构,可以比较清晰地看到,整个过程分为三步
1、计算波动率表;
2、计算股息率表;
3、根据前两步的结果计算折扣系数并最终返回。
下面来计算波动率σ。根据《指引》,其计算方法为先确定计算时点距离股票可流通的到期日数(暂且记作n,但当n不足20时取20)。然后用计算日前n日股价的历史对数收益率计算波动率。因此,这里实际第一步是计算n(下方_deletaTradingDate函数),然后确定n个交易日之前是哪一天(下方backToTime函数)。两个函数中间,用tDaysBefore作为过渡。而由于我们每次对一个基金进行计算时,往往其所持限售股不止一个,因此我们将第二个参数类型预设为列表(list)。具体如下:
defbackToTime(now, lstDays):
‘’’now是计算时点,lstDays是解禁日列表’’’
dtNow = dt.datetime.strptime(now, '%Y/%m/%d')
dtMax = max([dt.datetime.strptime(t, '%Y/%m/%d') fort inlstDays])
strMax = dt.datetime.strftime(dtMax, '%Y/%m/%d')
ifdtMax > dtNow:
n = _deletaTradingDate(now, strMax)
returntDaysBefore(now, n), 1
else:
returnnow, 0
def_deletaTradingDate(start,end):
'''交易日差(以日为单位)'''
obj = w.tdayscount(start, end)
ifobj.ErrorCode == 0:
returnobj.Data[0][0]
else:
print"Connect ErrorCode",obj.ErrorCode
raiseValueError(u'算交易日出现错误')
deftDaysBefore(now, days):
obj = w.tdaysoffset(-days, now)
returndt.datetime.strftime(obj.Data[0][0],'%Y/%m/%d')
在主函数中,我们用上述方法确定计算时间的起止点,确定好之后,就可以提取股价、计算对数收益率并据此计算波动率了。这里有一定难点是:如果股票在预定的计算区间内无成交(比如停牌,或者新股未上市),需要用对应行业的AMAC指数数据作为替代,详见下方logPctData函数。
def loadData(tickers, field, start,end, *others):
#用万得API取数据的通用函数
strTickers = ','.join(tickers)
obj = w.wsd(strTickers, field, start, end, others)
arrData = np.array(obj.Data).transpose
srsDate = [dt2str(d) for d in obj.Times]
returnpd.DataFrame(arrData,index = srsDate, columns = tickers)
defpriceData(lstStocks, start,end):
'''取复权价
lstStocks:股票列表
start:yyyy/m/d格式的时间起点
end:时间终点'''
dfAdjClose = loadData(lstStocks, ‘close’, start, end, ‘Priceadj=B’)
returndfAdjClose
deflogPctData(dfAdjClose):
dfLogPct = dfAdjClose.applymap(np.log) - dfAdjClose.shift(1).applymap(np.log)
t = dfAdjClose.apply(lambdax: not(all(pd.notnull(x))))
lstNullCodes =list(set(t[t].index))
#下面的内容是,如果存在异常数据,则用行业指数替代
iflstNullCodes:
obj = w.wss(','.join(lstNullCodes), 'indexcode_AMAC', 'tradeDate={_end}'.format(_end=rs.str2yyyymmdd(dfAdjClose.index[-1])))
dfStockVsIndex = pd.DataFrame(np.array(obj.Data).transpose,index=obj.Codes, columns=['AMAC'])
dfIndex = loadData(dfStockVsIndex[‘AMAC’].values.tolist, ‘close’,dfAdjClose.index[0],dfAdjClose.index[-1])
dfPctIndex = dfIndex.applymap(np.log) - dfIndex.shift(1).applymap(np.log)
fori inxrange(1,len(dfLogPct)):
t = dfLogPct.iloc[i].isnull
lstNullInThisRow = list(t[t].index)
iflstNullInThisRow:
forc inlstNullInThisRow:
idxID =dfStockVsIndex.loc[c, 'AMAC']
dfLogPct.iloc[i].loc[c] =dfPctIndex.iloc[i].loc[idxID]
returndfLogPct
这一步最简单的反而是最终计算波动率,如下函数,没有太多值得解释的地方。注意,下面的函数是计算单个股票、单个交易日的波动率,实际存在一个效率的问题(显然不如整个DataFrame直接去算,这也是我们将在实际使用时修正的内容,这个问题在最后算折扣率时也存在)。
defcalcVol(dfLogPct, ticker, now,days):
'''dfLogPct:复权收益表
ticker:代码
now:yyyy/mm/dd
days:自然数字,时间窗口'''
loc = dfLogPct.index.get_loc(now)
start = max([0, loc-int(days)])
returndfLogPct.iloc[start:loc].loc[:, ticker].std *np.sqrt(250)
然后是股息率。类似地,这里有一个问题是如果上市未满一年,股息率需要用行业指数做替代。不过相比于波动率,这里毕竟不涉及日期处理,整体要简单一些。
defdividendYield(lstStocks,start, end):
obj = w.wss(','.join(list(set(lstStocks))), 'ipo_date')
dfIPO_Date = pd.DataFrame(np.array(obj.Data).transpose,index=lstStocks, columns=['ipo_date'])
dfIPO_Date['OneYearAfter'] = dfIPO_Date['ipo_date'].apply(lambdax: x + dt.timedelta(365))
obj = w.wss(','.join(list(set(lstStocks))), 'indexcode_AMAC','tradeDate={_end}'.format(_end=rs.str2yyyymmdd(end)))
dfStockVsIndex = pd.DataFrame(np.array(obj.Data).transpose,index=lstStocks, columns=['AMAC'])
dfStockVsIndex.fillna('881001.WI', inplace=True)
lstIndexCodes = list(set(dfStockVsIndex['AMAC'].values))
dfStockDiv = loadData(lstStocks, ‘dividendyield2’, start, end)
dfIndexDiv = loadData(lstIndexCodes, ‘dividendyield2’, start, end)
forcode indfStockDiv.columns:
foridx indfStockDiv.index:
ifpd.isnull(dfStockDiv.loc[idx, code]) ordt.datetime.strptime(idx,"%Y%m%d") <= dfIPO_Date.loc[code, 'OneYearAfter']:
idIndex =dfStockVsIndex.loc[code, 'AMAC']
dfStockDiv.loc[idx, code] =dfIndexDiv.loc[idx, idIndex]
dfStockDiv.index = [rs.yyyymmdd2str(t) fort indfStockDiv.index]
returndfStockDiv / 100.0
最后,是AAP估值数据。可以看到,这个函数才是最简单的,没有太多值得解释的地方。但也请注意,这里是为表达清楚,因此用了三个参数都是数值的模式——问题随之而来,这样就无可避免在最后的批量运算中调用两层嵌套的循环,显然会有效率问题,因此我们在最后使用时,实际在这里进行了调整,需要用类似矩阵运算的方法处理。
defaapDisc(q, v, t):
'''q:股息率(单位不是%)
v:波动率(单位不是%)
t:剩余时间(单位是年)'''
v2t = v**2*t
d = np.sqrt(v2t + np.log(2 * (np.exp(v2t) - v2t - 1)) - 2 *np.log(np.exp(v2t) - 1))
discRatio = np.exp(-q*t) * (stats.norm.cdf(d/2.0) -stats.norm.cdf(-d/2.0))
returndiscRatio
完成整个计算过程后的一点感受:
1、AAP估值和以前的摊余成本法真正的区别是什么?简单来说,老算法基本依赖于成本价和锁定时间,新的算法下,估值与成本价已经基本无关,取而代之的是锁定时间、波动率和股息率。这也意味着:1)无论配售和定增,基金净值都有可能在拿到股份的节点,出现跳升(当然定价不合理也)。因此配售定增发生的预期,对基金价值的影响会比较大;2)以及,定增的定价上,如果要引入公募基金,那么这个定价的折扣就不能低于AAP估值法太多,否则参与即浮亏,在基金这一端是比较难以接受的。
2、现行算法下,什么是重要的?三要素变成了波动率、锁定期和股息率。我们观察,最不重要的是股息率,最重要的是锁定期,波动率居于二者之间。
固收+ 市场跟踪
1、分级A市场方面,分级A指数近10个交易日上涨0.2%,其中此前大涨的R+3.5%指数回落,跌0.13%,而R+3.0%指数涨幅达到0.26%。主流品种整体差异不大,军工A上涨0.49%,而深成指A仍有0.24%的涨幅。
对于分级A市场,我们保持近期观点,目前市场平均折价仍较低,不具备鲜明的投资特性,因而对于博弈性投资者而言,分级A价值不大。但毕竟绝对收益率水平不低且股市的波幅实际已经变窄,加大了分级A产品的债券成分,因此存在债券替代价值。品种上,可关注券商A及深成指A。
2、打新基金:近期,由于科创板新股涨幅整体降低,打新基金的无风险收益已经有所降低。但11月新股上市的数量较多,也有部分个股的上市表现较好,因此我们筛选的打新基金样本池11月普遍表现出了正增长。
3、配售基金近10日平均下跌0.88%,与市场预期走势比较吻合。此前市场开始预期银行股的战略配售,配售基金随之上涨。不过近期市场情绪不佳,新股上市出现破发、涨幅偏低等情况,配售基金近期也出现回落。
定增基金分化稍大,平均涨幅1%,其中九泰瑞富、九泰泰富涨幅超过2%。整体上,定增基金的名义、隐含折价率有小幅压缩,但并不明显。
4、转债市场方面,相比股市来讲,转债指数的振幅要小得多,近10日小幅下跌0.32%。背后原因在于,近期调整的主力军消费白马,在转债中分布不多,即便存在,其所占权重也很低。而真正主导转债指数的,仍是银行转债。总体上,我们在8月底认为转债买入窗口关闭,机会减少,近期仍保持这一观点。但近期股市情绪大幅回落,转债新券上市的价位、估值也开始和老券拉开距离,我们建议投资者开始关注转债市场估值调整以及低估值品种数量,尤其12月中旬有可能随着低价新券上市增多,转债市场机会可能重新出现。
5、ABS市场方面,临近年底银行间市场的发行节奏有所加快。在多个资产共同发力的情况下,10-11月的发行规模已经突破2800亿。如果12月的发行节奏依然较快,则全年发行量仍保留了超预期的可能性,12月可能迎来一个投资价格较好的时点。
价格方面,在超预期的供给暂时还未出现之前,价格仍然保持在低位。
6、REITs市场方面,国内共有6支基金主要投资于REITs领域。其中,上投摩根、南方、广发的三只基金别分跟踪了3个较为主流的REITs指数;诺安、嘉实、鹏华的三只基金则为主动投资。
此前美国REITs市场出现较大的调整,近两周价格表现有了一定的恢复,各基金净值均出现了正增长。
python 估值模型_【中金固收·固收+】隐藏价值的角落:限售股AAP估值及Python实现方法(上)...相关推荐
- python做估值模型_通证估值模型-费雪模型与净现值模型详解
通证估值模型-费雪模型与净现值模型详解 一.费雪模型,适合货币型通证 1)公式:MV= PQ M: 货币供应量 V: 货币流通速度 P:劳务平均价格 V:劳务总数 维基百科地址:Equation of ...
- python 线性回归模型_如何在Python中建立和训练线性和逻辑回归ML模型
python 线性回归模型 Linear regression and logistic regression are two of the most popular machine learning ...
- python编辑程序模型_用Python的SimPy库简化复杂的编程模型的介绍
在我遇到 SimPy 包的其中一位创始人 Klaus Miller 时,从他那里知道了这个包.Miller 博士阅读过几篇提出使用 Python 2.2+ 生成器实现半协同例程和"轻便&qu ...
- python机器识别追踪_多目标追踪器:用OpenCV实现多目标追踪(C++/Python)
原标题:多目标追踪器:用OpenCV实现多目标追踪(C++/Python) MultiTracker : Multiple Object Tracking using OpenCV (C++/Pyth ...
- python硬件驱动_从零开始:手把手教你安装深度学习操作系统、驱动和各种python库!...
原标题:从零开始:手把手教你安装深度学习操作系统.驱动和各种python库! 为了研究强化学习,最近购置了一台基于 Ubuntu 和英伟达 GPU 的深度学习机器.尽管目前在网络中能找到一些环境部署指 ...
- python股票交易模型_如何用Python建模GGM模型并对股票估值?
内容首发 乐学偶得(http://lexueoude.com) 公众号: 乐学Fintech 用代码理解分析解决金融问题 首先我们快速了解一下什么是GGM模型. GGM模型又叫做"戈登增长模 ...
- python 估值模型_基于Python的客户价值细分模型(RFM)
RFM模型:是衡量客户价值和客户创利能力的重要工具和手段.在众多的客户关系管理(CRM)的分析模式中,RFM模型是被广泛提到的.该机械模型通过一个客户的近期购买行为(recency).购买的总体频率( ...
- python 估值模型_利用RFM模型建模,并通过聚类分析、分类,分别算出8中不同的价值会员...
RFM-Clustering 利用RFM模型建模,并通过聚类分析.分类,分别算出8中不同的价值会员 RFM模型构建会员价值标签 R:最近一次消费(Recency) F:消费频率(Frequency) ...
- python内存模型_内存篇3:CPython的内存管理架构-L2-块
本篇用到了C/C++的内存对齐的基础知识,我已经假定你有C/C++内存管理的相关基础. 我们在前一篇的流程图中留下了两个黑箱子,会涉及到内存模型第一层以上的其他话题,回顾下面关于第一层面向类型的内存A ...
最新文章
- android 无法接收广播_别告诉我你不认识Android中广播接收者(二)
- JBPM4.4与SSH2的整合
- 第一个OpenGL程序
- linux 启动脚本 tty,Linux启动过程简介
- scala面向对象之trait
- 将查询后的数据导入到其他表中
- Bitcoin0.21版 公链开发(5) PHP集成到Apache中(windows平台)
- 三巨头垄断全球农业-丰收节贸易会:世界最大种子农药公司
- c char转int_C指针精华知识大汇总
- LeetCode——动态规划:斐波那契数列
- Java Object.hashCode()方法
- Circle and Points POJ - 1981(单位圆覆盖最多点)
- apache mesos_Apache Mesos:编写自己的分布式框架
- 软件工程 工具之二—— PowerDesigner v12(六)
- Spring MVC搭建REST风格网站
- html富文本编辑器插件_vue中使用vuequilleditor富文本编辑器
- 第67课 选择排序 例67.1 《小学生C++编程入门》
- Android可滑动画板,Android 利用 Canvas 画画板
- AndroidUI 布局动画-为布局添加动画
- LeetCode(657)——机器人能否返回原点(JavaScript)