可转债市场介绍
关于可转债,从发行到投资你需要知道的
可转债研究框架与历史回顾
探讨可转债投资策略
基于二叉树模型的可转债定价
可转债的Delta对冲套利策略
美国转债市场多大?
美国投资者如何投资可转债?
转债定价专题研究(1):可转债定价方法综述
转债定价专题研究(2):细节决定成败
转债定价,你应该知道的事之一:可转债票面利率规律探析
转债定价,你应该知道的事之二:可转债转股价格和转股溢价率规律探析
转债定价,你应该知道的事之三:债底的作用
转债定价,你应该知道的事之四:条款博弈与价格
转债定价,你应该知道的事之五:退市方式全解析
转债定价,你应该知道的事之六:投资者结构与行为
转债定价,你应该知道的事之七:一级申购的故事
转债定价,你应该知道的事之八:转债的信用风险
转债定价,你应该知道的事之九:转债下修知多少
转债量化投资策略思考(Ⅰ)
转债量化思考(II):转股溢价率与价格的二维分析
转债量化思考(III):存在超涨超跌的机会吗?
转债量化思考(IV):可转债Delta策略
可转债专题之一:历史已退市可转债分析:可转债收益源自何处?
可转债专题之二:可转债定价模型研究
可转债专题之三:银行及券商类转债分析
可转债专题之四:可转债基础知识手册
可转债专题之六:2018年可转债下修回顾
可转债专题之七:2019年可转债下修预测
可转债研究系列一:纵览转债市场,探索“价值投资”
可转债研究系列二:了解转债条款,寻找价值“洼地”
可转债研究系列四:股债轮动:可转债股性和债性的博弈
可转债研究系列五:事件驱动:可转债之转股、赎回与下修
可转债研究系列六:发现价格异动,捕捉转债套利机会
可转债提前赎回条款研究
高价转债风险收益初探
关于转债回售的二三事
转债供给冲击专题研究:千淘万漉虽辛苦,吹尽狂沙始到金
医疗行业转债专题研究:晴空一鹤排云上,便引诗情到碧霄
转债专题研究之新经济转债:黄沙百战穿金甲,不破楼兰终不还
可转债正股投资组合:事件效应下的投资机遇
可转债正股投资组合:探寻可转债正股投资的量化因子
转债“大时代”系列专题之一:转债定价及其希腊字母分析
转债“大时代”系列专题之二:年转债市场回顾
转债“大时代”系列专题之三:基金持仓行为历史大盘点
转债“大时代”系列专题之四:大数据看转债史
转债“大时代”系列专题之五:银行业转债分析手册
转债“大时代”系列专题之六:TMT之手机产业链转债分析手册
转债“大时代”系列专题之七:煤炭、钢铁行业转债分析手册
转债“ 大时代”系列专题之八:汽车整车行业转债分析手册
转债“大时代”系列专题之九:汽车零部件行业转债分析手册
转债“大时代”系列专题之十:新能源汽车产业链转债分析手册
转债“大时代”系列专题之十一:PCB产业链转债分析手册
转债“大时代”系列专题之十二:转债和交换债信用分析初探
转债“大时代”系列专题之十三:电气设备行业转债分析手册
转债“大时代”系列专题之十四:转债择券策略系列:基于量、价、投资指标体系的行业选择
转债“大时代”系列专题之十五:转债择券策略系列:基于财务指标体系的正股选择
转债“大时代”系列专题之十六:转债择券策略系列:基于价格、溢价率和流动性的转债选择
转债“大时代”系列专题之十三:电气设备行业转债分析手册
转债“大时代”系列专题之十八:高价转债的历史表现梳理上篇:绝对价格维度
转债“大时代”系列专题之十九:如何识别下修博弈机会
转债“大时代”系列专题之二十:从货币信用周期看转债在大类资产中的比价关系和轮动规律
[h1]【国信宏观固收】转债“大时代”系列专题汇编[/h1]国信固收研究
国信转债“大时代”系列专题回顾
转债“大时代”系列专题之一:转债定价及其希腊字母分析
转债“大时代”系列专题之三:基金持仓行为历史大盘点
转债“大时代”系列专题之四:大数据看转债史
转债“大时代”系列专题之五:银行业转债分析手册
转债“大时代”系列专题之六:TMT之手机产业链转债分析手册
转债“大时代”系列专题之七:煤炭、钢铁行业转债分析手册
转债“大时代”系列专题之八:汽车整车行业转债分析手册
转债“大时代”系列专题之九:汽车零部件行业转债分析手册
转债“大时代”系列专题之十:新能源汽车产业链转债分析手册
转债“大时代”系列专题之十一:PCB产业链转债分析手册
转债“大时代”系列专题之十二:转债和交换债信用分析初探
转债“大时代”系列专题之十三:电气设备行业转债分析手册
转债“大时代”系列专题之十四:转债择券策略系列:基于量、价、投资指标体系的行业选择
转债“大时代”系列专题之十五:转债择券策略系列:基于财务指标体系的正股选择
转债“大时代”系列专题之十六:转债择券策略系列:基于价格、溢价率和流动性的转债选择
转债“大时代”系列专题之十七:转债择券策略系列:基于行业、公司和转债的综合评分体系
转债专题报告:2017年-2018年转债打分择券策略回测
转债“大时代”系列专题之十八:高价转债的历史表现梳理上篇:绝对价格维度
转债“大时代”系列专题之十九:如何识别下修博弈机会
转债“大时代”系列专题之二十:从货币信用周期看转债在大类资产中的比价关系和轮动规律
转债“大时代”系列专题之二十一:什么是定向可转债?
转债“大时代”系列专题之二十二:关于转债回售的二三事
转债“大时代”系列专题之二十三:国信转债交债风格指数编制
转债“大时代”系列专题之二十四:国信转债交债高流动性指数编制
国信转债投资策略报告回顾
2019年转债市场策略:底部蛰伏,出击平衡型转债
2018年三季度转债市场回顾
2018年下半年转债市场策略:转债正处于风险收益比极佳的位置
2018年上半年转债市场回顾
2018年春季转债市场策略:守“价值”之正,出“创新”之奇
2018年转债投资策略:“飓风”过岗,“秀木”可存
2017年转债市场回顾
[h1]【中金固收·可转债】简易的转债策略测试框架——以及python实现方法 20190519[/h1]中金固定收益研究
作者杨 冰分析员,SAC执业证书编号: S0080515120002
房 铎联系人,SAC执业证书编号: S0080117080049
姬江帆分析员,SAC执业证书编号:S0080511030008;SFC CE Ref: BDF391
简易的转债策略测试框架[quote]策略好不好,测了才知道 —— 但对于转债来说,可能没那么容易。虽然转债已经基本告别当年迷你市场的窘境,但依然是一个小市场,这是我们在年度回顾报告中的一个判断 —— 而附带的一个影响是,很多配套的东西还不完备,比如策略测试的代码框架。幸好开源的理念之下,这些事情自行处理也并不太复杂。我们在此介绍一个简单的测试框架及其Python实现方法。
首先还是看大的框架,然后再一步一步完成细化实现。大体的流程应该包括:
1、初始化定义:测试的时间段、考虑的转债的范围(比如含不含EB、含不含那些因股改而退市的品种)、调仓周期、以及最终的返回值——策略的净值和必要的记录;
2、进入测试循环:计算净值、并在调仓的时点上进行调仓;
3、返回结果。此时的Python代码如下:
# 引入三个必须引用的库
import datetime as dt
import pandas as pd
import numpy as np
def frameStrategy(obj, start='2015/12/31'):
'''
这里的参数还不完全,为了简单先只留最简单的两个
obj是我们自己设定的一个class,进行日常的转债数据维护和计算,不过此时投资者不必太过在意,因为后面我们将只用其作为数据库的功能obj.DB
例如 obj.DB['Amt']将返回一个记录转债成交额的pd.DataFrame,index是yyyy/mm/dd型的日期,columns是各转债的代码
'''
# 设定起始日期在库中的位置(我们的数据从2002年开始,这里要返回一个整数,记录start在其中的位置,比如2015/12/31对应的是3391)
# 这个getStartLoc将在后面介绍,后面还有很多这类函数
intStart = getStartLoc(obj, start)
# dfRet是最终要返回的表,'NAV'这一列就是最重要的了:策略净值(我们这里是100为起点)
dfRet = pd.DataFrame(index=obj.DB['Amt'].index[intStart:],columns=['NAV','LOG:SEL','LOG:WEIGHT'])
# 这个表记录了持仓,index是转债代码,初始先设定成[Nothing]
dfAssetBook = pd.DataFrame(index=['Nothing'],columns=['costPrice', 'w'])
# 需要一个变量来记录持仓中的现金(或者借款)
cash = 100.0
# 设定转债代码范围
codes = defineCodes(obj, defineMethod)
# 一个调仓的日期列表,这里设定的是每21个交易日调仓一次
isAdjustDate = roundOfAdjust(obj, start, 21)
# 进入循环,enumerate是python里面一个很好用的迭代函数
for i,date in enumerate(dfRet.index):
# 这一步来记录净值变化
checkBook(obj, dfRet, dfAssetBook, cash,date)
# 判定当日是否需要调仓
if date in isAdjustDate:
# 如果需要调仓,进入selectCodes函数,根据策略选择个券
sel = selectCodes(obj, codes, date, selMethod)
if sel:
# 这一步得到权重变量
w = getWeight(obj, sel, date, weightMethod)
else:
sel = ['Nothing']
w = 0.0
dfAssetBook = pd.DataFrame(index=sel, columns=['costPrice', 'w'])
dfAssetBook['costPrice'] = 100.0
dfAssetBook['w'] = w
# 无论如何,都用dfRet来记录当日持仓的个券和权重
# join函数非常实用,用来连接字符串
dfRet['LOG:SEL'][date] =','.join(list(dfAssetBook.index))
# [func(t) for t in ...] 是非常具备python特色的一个处理方法
dfRet['LOG:WEIGHT'][date] =','.join([str(t) for t in list(dfAssetBook['w'])])
return dfRet
下面来逐个击破中间的小函数。首先是getStartLoc,实际上pd.DataFrame的index有一个get_loc的方法也能得到这个结果,但早期的版本没考虑过万一要找的变量不在index中怎么办。而后来的版本中,虽然给予了一定容忍度,但也基本没考虑过当index本身是不可比变量时的处理。所以此时我们要进行简单的改造,如下:
def getStartLoc(obj,date):
# 如果get_loc能解决,就交给它吧
if date in obj.DB['Amt'].index:
i= obj.DB['Amt'].index.get_loc(date)
else:
# 如果解决不了,要先把index转化成datetime型,而非原本的字符型,这样get_loc就能万用了
fakeIndex = obj.DB['Amt'].index.map(str2dt)
i= fakeIndex.get_loc(str2dt(date),method='ffill')
return i
接下来是定义个券大致范围的defineCodes:一般要剔除因股改而退市的那些转债,有时候我们也希望剔除EB。投资者也可以设定其他的规则,这就需要用到一个python特性:函数可以作为参数传入另一个函数。这样的话,投资者可以自行编写一个函数,作为定义范围的方法。实现如下:
def defineCodes(obj,method='default'):
if method== 'default':
return obj._excludeSpecial()
elif method== 'nonEB':
return obj._excludeSpecial(hasEB=0)
elif hasatrr(method,’__call__’): # 这一句是判断method是不是一个函数,如果是,则调用这个函数
return method(obj)
# _excludeSpecial() 是我们的obj中的方法,如下:
def _excludeSpecial(self,hasEB=1):
columns = set(list(self.DB['Amt'].columns))
# 这个cb_data.lstSpecial里面存了那些因股改而退市的转债的代码
columns -= set(cb_data.lstSpecial)
columns = list(columns)
# 如果不要EB,进入下面的程序
if not hasEB:
for code in columns:
if code[:3] == '132' or code[:3] == '120':
columns.remove(code)
return columns
下面是择券的代码,也是对策略决定意义最大的函数。在调仓日期会调用这个函数。同样,为了给予投资者外部接口,这里也要保留传入函数的可能性。如下:
def selectCodes(obj, codes, date,selMethod=None):
i = getStartLoc(obj,date)
n = min([i,5])
# 这里利用一下pandas.DataFrame的逻辑运算做最基本的条件设定:前5个交易日必须有最少10万的交易
# 且存量不低于3000万
condition = (obj.DB['Amt'].iloc[i-n:i][codes].fillna(0).min() >100000.0) & \
(obj.DB['Outstanding'].iloc[codes]> 30000000.0)
## 如果selMethod不为空
If selMethod:
tempCodes= list(condition[condition].index)
moreCon= selMethod(obj, codes, date, tempCodes)
condition&= moreCon
# 这个函数最后返回的变量是这个
retCodes = list(condition[condition].index)
# 如果一个都没有,进入这里,并给出提示
if not retCodes:
print 'its a empty selection, when date: ',date
return retCodes
# 下面以低价策略举例,如果我们希望在调仓时买入所有价格低于均价的品种,则可以写下面这个函数,并把_lowprice作为selMethod传入上面的函数:
def _lowPrice(obj, codes, date, tempCodes):
avgPrice = obj.DB['Close'].loc[date][tempCodes].mean()
return obj.DB['Close'].loc[date, codes] |
|