模型公式
模型的公式主要如下:
参数 | 定义 | 设定方式 |
S | 估值日在证券交易所上市交易的同一股票量化交易软件,股票的公允价值 | 估值日当日股票量化交易软件,股票收盘价 |
T | 剩余限售期,以年为单位表示 | 估值日距离到期日的自然日天数/365 |
q | 股票预期年化股利收益率(送股和现金分红的收益) | 设定为0.01,由于部分股票数据缺失且参数对估值的影响极低 |
σ | 股票在剩余限售期内的股价的预期年化波动率 | 估值日前180日年化波动率,年化波动率计算时,收益率使用对数收益率 |
这里的剩余期限等价于到期期限,波动率取180的原因是:大宗和定增的限售期限约为180天。
代码实现
本文以000001作为测试例子,当前日为2023-06-1到期日为2023-11-12;数据使用akshare开源API获取。
1计算波动率和收盘价
import akshare as ak
import pandas as pd
import numpy as np
from scipy.stats import norm
from math import exp, sqrt,log
import datetime
def get_volatility_and_close(data, date):
"""
获取估值日波动率
----------
data:dataFream,股票历史数据
date:str,估值日,格式如‘2023-06-12’
Returns:int,保留4位小数的年化波动率数据和收盘价
-------
"""
# 数据按日期降序处理,同时若不满足180天,则选择当前全量天数
data["pre_close"] = data["close"].shift(1)
data_part = data.loc[data["date"]<=date].sort_values(by=["date"], ascending=False)
try:
new_data = data_part[0:180]
except:
new_data = data_part
# 计算对数收益率
new_data["value"] =new_data["close"] / new_data["pre_close"]
new_data["ln_return"] = new_data["value"].apply(lambda x: log(x))
# 计算年化波动率
vol = sqrt(252) * (np.std(new_data["ln_return"]))
# 获取收盘价
close = data_part["close"].values[0]
return round(vol,4), close
if __name__ == "__main__":
code = "sz000001"
date = "2023-06-12"
end_day = "2023-11-12"
test_data = ak.stock_zh_index_daily_em(symbol=code)
vol, close = get_volatility_and_close(test_data, date)
通过测算,20230612前180日的年化波动率约为274%,收盘价为17这里需要注意,数据需要用前复权的,这里仅做举例,所以直接使用的是获取历史价格的一个函数。
2计算剩余期限
def get_residue_day(date,end_day):
"""
计算剩余期限,折算为年
----------
today:str,估值日,格式如‘2023-06-12’
end_day:str,估值日,格式如‘2023-11-12’
-------
输出剩余期限
"""
day_diff = (pd.to_datetime(end_day)-pd.to_datetime(date)).days
T = day_diff/365
return round(T,4)
if __name__ == "__main__":
date = "2023-06-12"
end_day = "2023-11-12"
residue_day = get_residue_day(date, end_day)
测算出的剩余期限的值约为0.419
3计算限售估值
def get_fv_value(vol, T, close):
"""
计算限售股估值
----------
vol:int,预期波动率
T:int,剩余期限
close:int,估值日收盘价
Returns:int, 保留2位小数的限售股估值
-------
"""
# 获取v和根号T的乘积, 若T为0,ln(0)不存在,默认取值为0
if T!=0:
a = vol ** 2 * T
b = log(2*(exp(a)-a-1))
c = 2*log(exp(a)-1)
result = sqrt(a + b -c )
else:
result = 0
# 计算p值
q = 0.01 # 股息率,默认为0.01
one = exp(-q*T)
p = close*one * ( norm.cdf(result/2) - norm.cdf(-result/2) )
# 计算限售估值
Lomd = p/close
fv = close*(1-Lomd)
return round(fv,2)
if __name__ == "__main__":
code = "sz000001"
date = "2023-06-12"
end_day = "2023-11-12"
test_data = ak.stock_zh_index_daily_em(symbol=code)
vol, close = get_volatility_and_close(test_data, date)
residue_day = get_residue_day(date, end_day)
fv = get_fv_value(vol, residue_day, close)
当剩余期限为0时,看跌期权的价格也为0,此时限售估值等于估值日收盘价。最终得到的FV结果为12相对于当日收盘价17折扣约为96%。
4完整代码
import akshare as ak
import pandas as pd
import numpy as np
from scipy.stats import norm
from math import exp, sqrt,log
import datetime
def get_volatility_and_close(data, date):
"""
获取估值日波动率
----------
data:dataFream,股票历史数据
date:str,估值日,格式如‘2023-06-12’
Returns:int,保留4位小数的年化波动率数据和收盘价
-------
"""
# 数据按日期降序处理,同时若不满足180天,则选择当前全量天数
data["pre_close"] = data["close"].shift(1)
data_part = data.loc[data["date"]<=date].sort_values(by=["date"], ascending=False)
try:
new_data = data_part[0:180]
except:
new_data = data_part
# 计算对数收益率
new_data["value"] =new_data["close"] / new_data["pre_close"]
new_data["ln_return"] = new_data["value"].apply(lambda x: log(x))
# 计算年化波动率
vol = sqrt(252) * (np.std(new_data["ln_return"]))
# 获取收盘价
close = data_part["close"].values[0]
return round(vol,4), close
def get_residue_day(date, end_day):
"""
计算剩余期限,折算为年
----------
today:str,估值日,格式如‘2023-06-12’
end_day:str,到期日,格式如‘2023-06-12’
Returns:int,保留4位小数的剩余期限
-------
"""
day_diff = (pd.to_datetime(end_day)-pd.to_datetime(date)).days
T = day_diff/365
return round(T,4)
def get_fv_value(vol, T, close):
"""
计算限售股估值
----------
vol:int,预期波动率
T:int,剩余期限
close:int,估值日收盘价
Returns:int, 保留2位小数的限售股估值
-------
"""
# 获取v和根号T的乘积, 若T为0,ln(0)不存在,默认取值为0
if T!=0:
a = vol ** 2 * T
b = log(2*(exp(a)-a-1))
c = 2*log(exp(a)-1)
result = sqrt(a + b -c )
else:
result = 0
# 计算p值
q = 0.01 # 股息率,默认为0.01
one = exp(-q*T)
p = close*one * ( norm.cdf(result/2) - norm.cdf(-result/2) )
# 计算限售估值
Lomd = p/close
fv = close*(1-Lomd)
return round(fv,2)
if __name__ == "__main__":
code = "sz000001"
date = "2023-06-12"
end_day = "2023-11-12"
test_data = ak.stock_zh_index_daily_em(symbol=code)
vol, close = get_volatility_and_close(test_data, date)
residue_day = get_residue_day(date, end_day)
fv = get_fv_value(vol, residue_day, close)
需要注意的是,关于波动率的取值问题相对灵活,并没有标准的答案。本文采取合理即可的原则,在这个方向不做过多探讨。
案例实战
还是以000001为例,限售起始日为2022122终止为2023052测算期间限售估值的走势,即测算限售估值的时序数据。
整体的思路是获取时间的序列,然后循环日期,获取对应每一个交易日的限售估值。
if __name__ == "__main__":
# 参数,股息率默认为1
code = "sz000001"
start_date = "2022-12-26"
end_day = "2023-05-26"
# 筛选出区间段数据,并按时间升序排列
test_data = ak.stock_zh_index_daily_em(symbol=code)
new_data = test_data.loc[test_data["date"]>=start_date]
last_data = new_data.loc[new_data["date"]<=end_day].sort_values(by=["date"], ascending=True)
# 创建保存FV的列表,然后进行循环
fv_list = []
for date in last_data["date"]:
vol, close = get_volatility_and_close(test_data, date)
residue_day = get_residue_day(date, end_day)
fv = get_fv_value(vol, residue_day, close)
fv_list.append(fv)
# 转换数据并输出
out_df = pd.DataFrame({"日期":last_data["date"],
"收盘价":last_data["close"],
"限售估值":fv_list})
out_df.to_excel(code+"限售估值数据.xlsx", index=False)
将上述得到的数据作,结果如下:
我们从中可以发现,到期前,限售估值都低于收盘价,这是因为限售股存在一个流动性的约束,因此价值相对会低于公允价值;随着到期,限售股解禁,其估值等于公允价值。
本次分享到此结束,若有疑问欢迎留言交流。
文章为作者独立观点,不代表 股票程序化软件自动交易接口观点