退市风险“意外”成为近期转债投资者中的"新话题"。此前市场经历过的"压力情形"还是2018年的辉丰转债,后来转债遭遇暂停上市(股票未退市或暂停,彼时转债与股票的上市要求不同),该转债历史最低价格为2018年8月14日创下的71.86元。但近期搜特、正邦转债的价格都低于了70元,背后的差别无需多言,投资者需要了解的可能是:如何更早地看到并避免这类情况。
好在问题并不复杂,一支转债跟随正股退市的情形无非是:市场因素(股价低于1元、市值低于3亿元)或者财务因素(触发财务退市指标),下面我们先对这些情景分别建模。
市场因素刻画
实践上,对市场退市的可能性建模,便可抵御大部分风险
。市场退市的建模我们曾在2年前(2021年《挑战:EasyBall可以更稳吗——转债退市风险测算与Python实践》)中进行过尝试。简而言之,我们就是通过股价波动范围来计算未来股价低于1元或者市值低于3亿元的。这里我们希望将模型做得更简单一些,也更便于投资者掌握。因此,我们不再计算耗时耗力的Hurst指数,而只是用当前股价、市值、波动率和剩余期限作为参数,计算退市概率,下面介绍具体实现。
首先是取得波动率的数值,考虑到很多转债正股上市未满2年,直接从数据终端取2年历史波动率会出现大量空缺。因此这里首先从自行取得过去2年周回报数据开始,计算股价波动率。
图表1:Python实现:波动率
def
转债与正股对照表
(codes)
:
_, df = w.wss(codes,
"underlyingcode"
, usedf=
True
)
return
df
def
正股周回报
(stocks, start, end)
:
sql =
f'''select s_info_windcode windcode,
trade_dt, s_wq_pctchange
from winddf.ashareweeklyyield a
where a.trade_dt >=
{pd.to_datetime(start).strftime(
"%Y%m%d"
)}
and
a.trade_dt <=
{pd.to_datetime(end).strftime(
"%Y%m%d"
)}
and
a.s_info_windcode in ('
{
"','"
.join(stocks)}
')
order by a.trade_dt
con = rs.login
# con为SQL登录变量,视不同机构的设置而定,如不具备,可以考虑用api代替
dfWeekly = pd.read_sql(sql, con)
con.close
return
dfWeekly.pivot(index=
"TRADE_DT"
, columns=
"WINDCODE"
, values=
"S_WQ_PCTCHANGE"
)
def
指定日期百周波动率
(dfWeekly, date)
:
srs = dfWeekly.loc[:date].tail(
100
).std * np.sqrt(
50
)
srs.name =
'波动率'
return
srs
资料来源:Wind,中金公司研究部
然后我们装载其他变量,即股价、市值、转债剩余期限。其中的obj为我们在《转债数据库规范与统计案例》中设计的集成变量,可以按照github版本配置即可。beDelisted函数可以根据上述内容计算退市概率,而在得到上述数据表后,我们在"市场退市概率"中,只需要对原表进行apply操作便可以批量产出市场退市概率数据。
图表2:补充数据与计算概率
def
装载股价市值
(obj, dfUnderlying, date)
:
date = pd.to_datetime(date).strftime(
"%Y%m%d"
)
if
not
w.isconnected: w.start
stocks = list(dfUnderlying[
"UNDERLYINGCODE"
].unique)
_, dfData = w.wss(stocks,
"close,,mkt_cap_ard"
,
f"tradedate=
{date}
"
,
usedf=
True
)
dfRet = dfUnderlying.merge(dfData, left_on=
"UNDERLYINGCODE"
, right_index=
True
)
dfRet[
"剩余期限"
] = obj.Ptm.loc[date, dfUnderlying.index]
dfRet[
"MKT_CAP_ARD"
] /=
100000000.0
return
dfRet
def
beDelisted
(s, mv, vol, t)
:
p1 = stats.norm.cdf((np.log(
1
) - np.log(s)) / (vol * (t **
0.5
)))
p2 = stats.norm.cdf((np.log(
3
) - np.log(mv)) / (vol * (t **
0.5
)))
return
max((p1,p2))
def
市场退市概率
(dfRet)
:
dfRet[
"市场退市概率"
] = dfRet.apply(
lambda
x: beDelisted(x[
"CLOSE"
], x[
"MKT_CAP_ARD"
],
x[
"波动率"
] /
100.0
,
np.min([x[
"剩余期限"
],
2
])), axis=
1
)
return
dfRet
资料来源:Wind,中金公司研究部
我们将上述操作拼装到下列函数,便可计算任意指定日期的市场违约概率,如下。
图表3:市场因素退市概率
def
generateDelistedProb
(obj, date)
:
# 转债代码与正股对应表
dfUnderlying = 转债与正股对照表(obj.selByAmt(date))
stocks = list(dfUnderlying[
"UNDERLYINGCODE"
].unique)
# 取开始时点,计算波动率
start = pd.to_datetime(date) - dt.timedelta(days=
730
)
dfWeekly = 正股周回报(stocks, start, date)
dfWeekly.index = pd.to_datetime(dfWeekly.index)
srsVol = 指定日期百周波动率(dfWeekly, date)
dfUnderlying = dfUnderlying.merge(srs, left_on=
"UNDERLYINGCODE"
, right_index=
True
)
# 加载其他数据
dfUnderlying = 装载股价市值(obj, dfUnderlying, date)
return
市场退市概率(dfUnderlying)
资料来源:Wind,中金公司研究部
财务因素刻画
近期案例中,也有一些品种的风险可以理解为由财务风险引发的。但对于转债投资者而言,难点在于取舍:1、一方面财务数据、指标众多,如果逐个查看再"综合分析",不仅效率低,一般也不易得到一个定量的结果;2、至少历史上,低评级(一般对应高的信用风险)转债的超额回报明显,即便考虑到近期案例,极端的风险概率毕竟很小。
因此,我们致力于一个简易、快速且答案明确的方法。首先尽可能控制决策成本(判断的过程),也让投资者在风险与收益的取舍中,有一个定量的数值进行参考。我们选取了过去3年有交易的、发行人为上市公司的公司债隐含评级作为目标变量y进行训练,而对于"X",一方面我们参考了评级机构的普遍做法(本质差异并不大),另一方面考虑数据的便捷、可靠,最终选取了:
1、"净债务比EBITDA"、"财务费用比EBITDA"、"总债务比总资本"来表征债务负担;
2、"EBITDA利润率"和"ROA"来表征盈利性;
3、"速动比率"和 "现金短期债务比"来表征流动性;
4、以及用"对数营收规模"和"对数总资产"表征规模。
图表4:财务数据获取代码
def
_financialData
(codes, rptDate)
:
if
not
w.isconnected: w.start
cols = [
"净债务"
,
"EBITDA"
,
"财务费用"
,
"货币资金"
,
"所有者权益"
,
"营业收入"
,
"ROA"
,
"速动比率"
,
"现金短期债务比"
,
"总资产"
]
_, df = w.wss(codes,
'netdebt,ebitda2_ttm,finaexpense_ttm2,monetary_cap,tot_equity,\
or_ttm2,roa2_ttm2,quick,cashtostdebt,tot_assets' ,
f"unit=1;rptDate=
{rptDate}
;rptType=1"
, usedf=
True
)
df.columns = cols
df.dropna(how=
"any"
, inplace=
True
)
df[
"净债务比EBITDA"
] = df[
"净债务"
] / df[
"EBITDA"
]
df[
"财务费用比EBITDA"
] = df[
"财务费用"
] / df[
"EBITDA"
]
df[
"总债务比总资本"
] = (df[
"净债务"
] + df[
"货币资金"
]) / df[
"所有者权益"
]
df[
"EBITDA利润率"
] = df[
"EBITDA"
] / df[
"营业收入"
]
df[
"对数营收规模"
] = df[
"营业收入"
].apply(
lambda
x: np.log(x /
100000000.0
))
df[
"对数总资产"
] = df[
"总资产"
].apply(
lambda
x: np.log(x /
100000000.0
))
colsRet = [
"净债务比EBITDA"
,
"财务费用比EBITDA"
,
"总债务比总资本"
,
"EBITDA利润率"
,
"速动比率"
,
"现金短期债务比"
,
"对数营收规模"
,
"对数总资产"
,
"ROA"
]
return
df[colsRet]
资料来源:Wind,中金公司研究部
我们选择随机森林作为学习模型。
我们没有考虑使用比较复杂的例如transformer或我们此前用过的Attention机制来进行训练,毕竟相对而言这一任务比较简单,数据量也并没有非常大。考虑到简易性和过拟合风险,我们使用在这个数据体量下表现较好的随机森林法来进行拟合学习。在已经清楚X和y的情况下,这个过程很容易,如下。注意,为控制过拟合,我们选择了最小枝叶为4的设定,投资者亦可尝试其他。
图表5:模型训练代码
from
sklearn.preprocessing
import
StandardScaler
from
sklearn.model_selection
import
train_test_split
from
sklearn.ensemble
import
RandomForestRegressor
def
trainTree
(X, y)
:
scaler = StandardScaler
scaler.fit(X)
X_scaled = scaler.transform(X)
X_train, X_test, y_train, y_test = train_test_split(X_scaled, y,\
test_size=
0.15
, random_state=
0
)
regr = RandomForestRegressor(min_samples_leaf=
4
)
regr.fit(X_train, y_train)
print(
'Training score:'
, regr.score(X_train, y_train))
print(
'Test score:'
, regr.score(X_test, y_test))
return
regr, scaler
资料来源:Wind,中金公司研究部
最终我们看到模型学习的结果也比较理想,训练集和测试集的拟合优度均可达到75%以上。
预测值与实际值之间也有明显而单调的关系。尤其对于仅仅需要提示少数个券风险的转债投资者而言,足以达到效果。
图表6:财务退市模型:预测值与实际值
资料来源:Wind,中金公司研究部
因此,在得到训练后的随机森林后,我们利用joblib将模型保存,并用于后续预测,预测函数如下。其中,rptDate为最近一期季报的时间。
图表7:财务退市模型实践代码
def
财务风险预测
(codes, rptDate)
:
df = _financialData(codes, rptDate)
regr = joblib.load(
'regr.pkl'
)
scaler = joblib.load(
'scaler.pkl'
)
x_scaled = scaler.transform(df.fillna(
0
).values)
yh = regr.predict(x_scaled)
return
pd.Series(yh, index=codes)
资料来源:Wind,中金公司研究部
对投资者来说,一个重要的问题在于“怎么用”前面的两个结果。
我们的一个建议是,在使用低价、双低类策略时,对市场因素的退市概率采取更为严格限定。因为一方面几乎所有面临退市风险的品种都要进入这个范围。另一方面,转债毕竟是很多发行人几乎唯一的公开债务,只要市场因素方面制约不大,公司回旋的余地就大。而财务因素则可能成为边际上的催化剂,因此我们可以作为补充限制条件,但只关注其中风险确实较大的品种。下图为我们的一些测试,在低价(前30%)、高YTM(前30%)和EasyBall基础上加入退市风险剔除的结果,剔除标准为:市场因素退市率 > 0.5%或财务因素退市率 > 16%,并进行每两个月一次的轮动。
图表8:三类策略净值情况
资料来源:Wind,中金公司研究部
我们如何看待近期的变化?
1、首先,退市并非新事物,这类问题的讨论早在两年前已经有过(见《挑战:EasyBall可以更稳吗——转债退市风险测算与Python实践》[3]),而即便是具体品种,也经历过长期的演化、风险警示公告也很充分。仍在其中的投资者往往有着不一样的目的或信息,因此我们很难认为近期陆续出现的样本会给市场带来"意外",或"增量信息"。
2、作为专业的转债投资者,更多的是注意不被过于相近的品种牵连即可,上述方法可以用来帮助排除。
3、但更重要的是反应不宜过激,我们此前没有更多地讨论也是从投资策略角度看,对投资效果帮助不大。
上述方法确实能够帮助一些低价策略降低最大回撤、提升卡玛比率——但投资者可以再对比下图,这是在上图基础上加入120日动量因子的效果,对于降回撤等方面的提升,要明显得多。
图表9:加入动量后的三类策略净值情况
资料来源:Wind,中金公司研究部
4、另一个层次的“不要过激”的原因,则来自融资层面,转债发行人的中小型标的较多,投资者易于接受且由于变通余地大,实际的极端风险也较小。因而实际对这类中小标的而言,转债的意义很大,甚至也是最后的工具。因此,投资端不给予这个本就不“新”的现象过激的反应、调整,也有利于市场,尤其中小企业保持正常融资功能。
Source
本文摘自:2023年5月12日已经发布的《退市风险模型与策略的Python实践》
Legal Disclaimer
中金公司对本公众号所载资料的准确性、可靠性、时效性及完整性不作任何明示或暗示的保证。对依据或者使用本公众号所载资料所造成的任何后果,中金公司及/或其关联人员均不承担任何形式的责任。
本资料较之中金公司正式发布的报告存在延时转发的情况,并有可能因报告发布日之后的情势或其他因素的变更而不再准确或失效。本资料所载意见、评估及预测仅为报告出具日的观点和判断。该等意见、评估及预测无需通知即可随时更改。证券或金融工具的价格或价值走势可能受各种因素影响,过往的表现不应作为日后表现的预示和担保。在不同时期,中金公司可能会发出与本资料所载意见、评估及预测不一致的研究报告。中金公司的销售人员、交易人员以及其他专业人士可能会依据不同假设和标准、采用不同的分析方法而口头或书面发表与本资料意见不一致的市场评论和/或交易观点。
返回搜狐,查看更多
责任编辑: