添加链接
link之家
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

资料来源:万得资讯,中金公司研究部,

注:图来自2021年6月25日发布的《离开低点两个月后》

对转债投资者来说,将原始的走势进行此类划分的意义是什么? ——主要在两方面,一是将富含“噪声”的行情波动,用更为简洁的趋势概括出来,从而进行行情分类。至少,我们在划分好这些后,我们能够知道,当前转债是走在上行趋势还是下行趋势中,以及结合MACD等,还可知道其运行阶段。对于标的普遍波动较大的转债而言,这一点尤其重要。另一方面,则在于转债投资者应当比股票投资者更关注弹性,而用每日波动计算出的弹性存在诸多弊端,比如无法反映趋势型。而如果将行情分好段,直观上平均线段的长度,就是对正股弹性一个很好的度量方式。

近期在十大推荐转债中的双环转债正股走势

资料来源:万得资讯,中金公司研究部

注:图来自2020年8月28日发布的《有关转债的技术分析——体系篇》

简单来说,做出这样的图,我们需要:

1、处理K线之间的包含关系,即若出现前后2个K线,其中一个被另一个完全包含的情况,我们需要合并处理;

2、划分顶分型和底分型,定义见上图;

3、连接“合格”的顶和底,组成图上的笔。

下面我们来逐步分解:首先处理K线之间的包含关系 。实践中我们发现此举主要是为了避免某一个交易日,即是顶也是底的状态。其中可能出现两种可能性,即前后2个K线,后者包含前者,以及前者包含后者。我们最终需要将存在包含关系的K线图合并,其中原本处于上行方向的,合并后的新K线的最高、最低价均为原本两个K线图的最高者,反之则相反。

K线的合并——以光大转债正股为例

资料来源:万得资讯,中金公司研究部

这里不需要更高级的算法处理,除了应用Python语言原生的Interval库(主要用来处理区间),只是要借助几个函数来分步处理:

1)处理确认两个K线是否有包含关系,以及这种关系的类型,这里要用到下面的isIncluding和intervalCompute两个辅助函数;

2)按照此前走势方向,合并这两个K线图,即includingProcess和_reviseInclude。程序实现逻辑如下:

处理K线之间包含关系的程序实现(一)

def intervalCompute (intvA, intvB) :

# 计算A、B区间是否重叠,如有,则返回重叠部分,否则返回方向关系(b > a则为up反之down),和None

if intvA.overlaps(intvB):

intvRet = Interval(max((intvA.lower_bound, intvB.lower_bound)), min((intvA.upper_bound, intvB.upper_bound)))

return "overlap" , intvRet

elif intvA.upper_bound < intvB.lower_bound:

return "up" , None

else :

return "down" , None

def isIncluding (intvA, intvB) :

# 计算AB是否为包含关系,只要一方包含另一方,则返回True, 否则False

isOverlap, intvOvlp = intervalCompute(intvA, intvB)

if isOverlap == "overlap" and intvOvlp == intvA:

# 重复段为前者,即后包前

return True , 0

if isOverlap == "overlap" and intvOvlp == intvB:

# 重复段为后者,即前包后

return True , 1

else :

# 无包含关系

return False , None

def includingProcess (intvA, intvB, direction= "up" ) :

# 计算相互有包含关系的A、B合并后的新区间,拟缠论规则

if direction == "up" :

return Interval(max((intvA.lower_bound, intvB.lower_bound)), max((intvA.upper_bound, intvB.upper_bound)))

elif direction == "down" :

return Interval(min((intvA.lower_bound, intvB.lower_bound)), min((intvA.upper_bound, intvB.upper_bound)))

def _reviseInclude (dfKlinesCopy, intvRet, close, iloc) :

# 辅助函数,用于最后按包含关系修改dfKlinesCopy

dfKlinesCopy[ "HIGH" ][iloc] = intvRet.upper_bound

dfKlinesCopy[ "LOW" ][iloc] = intvRet.lower_bound

dfKlinesCopy[ "CLOSE" ][iloc] = close

return dfKlinesCopy

资料来源:万得资讯,中金公司研究部

有了这些准备工作,处理包含关系并合并的工作就能相对简单完成。我们用_exclude函数作为这一步的汇总处理,程序逻辑如下:

处理K线之间包含关系的程序实现(二)

def _exInclude (dfKlines) :

# 私有函数,处理包含关系,lastValid和lstValid用于在后面的遍历中标识有意义的K线的记录

# 同时为避免破坏数据源,先制作了一个备份变量dfKlinesCopy

lastValid = 0 ; lstValid = [dfKlines.index[ 0 ]]

dfKlinesCopy = dfKlines.copy()

for i, idx in enumerate(dfKlines.index):

# 把当日股价运行区间变换成一个区间对象,以便用上面的“intervalCompute”

intvKi = Interval(dfKlines[ "LOW" ][i], dfKlines[ "HIGH" ][i])

intvLast = Interval(dfKlinesCopy[ "LOW" ][lastValid], dfKlinesCopy[ "HIGH" ][lastValid])

if i > 1 :

biIn, inType = isIncluding(intvLast, intvKi)

if not biIn:

# 如果没有包含关系,不改动数据

lastValid = i

lstValid.append(idx)

else :

direction = "up" if dfKlines[ "HIGH" ][lastValid] >= dfKlines[ "HIGH" ][lastValid -1 ] else "down"

intvRet = includingProcess(intvLast, intvKi, direction)

if inType == 1 :

dfKlinesCopy = _reviseInclude(dfKlinesCopy, intvRet, dfKlines[ "CLOSE" ][i], lastValid)

elif inType == 0 :

dfKlinesCopy = _reviseInclude(dfKlinesCopy, intvRet, dfKlines[ "CLOSE" ][i], i)

lastValid = i

lstValid.pop()

lstValid.append(idx)

# 最后,我们只输出在lstValid中的数据

dfRet = dfKlinesCopy.loc[lstValid]

return dfRet

资料来源:万得资讯,中金公司研究部

下一步较为关键,我们需要根据K线的高点、低点来生成“分型”。 实际上,技术上分型的叫法常与几何中的“分形”混淆,而我们认为其更不容易出现误解的概念应该是“拐点”。技术上的经典定义为某K线图的高点高于其两侧的高点则定义为顶点(也有相应底点的定义) —— 但这样会形成较多互相临近的“假拐点”,于是我们选择用《混沌交易法》中的方式,需要某K线高于前后各两个K线图的高点,才定义为顶点。示意如下:

分型——以光大转债正股为例

资料来源:万得资讯,中金公司研究部

而这一步只需要巧用滚动窗口的计算,即可将符合条件的点选择出来。随后,我们可以用一个DataFrame结构保存这个结果,见getRet。程序逻辑很简易,如下:

处理K线分型的程序实现

def getInflection(dfK):

srsMax, srsMin = dfK[ "HIGH" ].rolling( 5 ). max ().shift(- 2 ), dfK[ "LOW" ].rolling( 5 ). min ().shift(- 2 )

lstUp = list (dfK. loc [dfK[ "HIGH" ] == srsMax]. index )

lstDown = list (dfK. loc [dfK[ "LOW" ] == srsMin]. index )

return lstUp, lstDown

def getRet(dfK, lstUp, lstDown):

dfRet = pd.DataFrame( index =sorted(lstUp + lstDown), columns=[ "ALL" , "pointType" ])

dfRet. loc [lstUp, "ALL" ] = dfK. loc [lstUp, "HIGH" ]

dfRet. loc [lstUp, "pointType" ] = 1

dfRet. loc [lstDown, "ALL" ] = dfK. loc [lstDown, "LOW" ]

dfRet. loc [lstDown, "pointType" ] = - 1

print dfRet

return dfRet

资料来源:万得资讯,中金公司研究部

接下来,理论上连接顶和底就可以得到我们需要的趋势图了,但这里我们还要针对两种情况进一步提炼:

1、不能出现两个顶点相邻或者两个底点相邻的情况。由于存在多顶点相邻的情景,同时最终我们要对多个顶部相邻的情景抽取最高者(底点则相反),这里我们不得已要对顶点、底点序列进行遍历。算法逻辑如下:

避免多顶/底点相邻的程序实现

def dropSameDirection (dfRet) :

dictSeg = []; flag = 0

for i,d in enumerate(dfRet.index):

if i >= 1 :

if dfRet.pointType[i] == dfRet.pointType[i -1 ]:

if flag == 0 :

tempList = [dfRet.index[i -1 ], d]

flag = 1

else :

tempList.append(d)

elif flag == 1 :

dictSeg.append(tempList)

flag = 0

continue

if d == dfRet.index[ -1 ] and flag == 1 :

dictSeg.append(tempList)

if len(dictSeg):

lst2drop = []

for lst in dictSeg:

if dfRet.pointType[lst[ 0 ]] == 1 :

lst.remove(pd.to_numeric(dfRet.loc[lst, "ALL" ]).argmax())

else :

lst.remove(pd.to_numeric(dfRet.loc[lst, "ALL" ]).argmin())

lst2drop += lst

dfRet.drop(index = lst2drop, inplace= True )

资料来源:万得资讯,中金公司研究部

2、为屏蔽短期变动带来的干扰,顶与底之间的时间距离不能太短。不过,有些顶和底之间的距离虽然很近,但波动足够大,尤其近两年来市场可能还会受到隔夜海外市场、大宗商品的影响,这些短线波动是有意义的。因此,我们会剔除的是:

1)底点后很快迎来顶点,且顶点相比此前一个顶点更低的情况(即虽然有底部反转的迹象,但下一个顶点很快到来,且这个底点结构没能给市场带来足够高度的反弹)。

2)同样的情况,适用于顶点。

上述算法的程序逻辑如下。这里需要注意,由于不能破坏顶、底相连的规则,我们每次要剔除偶数个点,这里借助一个flag个变量:

控制顶/底点相连的程序实现

def dropNearPunc (dfRet, dfK) :

print dfRet

dfRet[ "locInK" ] = [dfK.index.get_loc(d) for d in dfRet.index]

lst2BeDropped = []; flag = 0

for i, d in enumerate(dfRet.index):

if flag :

flag = 0

continue

if i > 1 and d != dfRet.index[ -1 ] and (dfRet.locInK[i+ 1 ] - dfRet.locInK[i] <= 3 ):

if dfRet.pointType[i] == 1 :

if dfRet.ALL[i+ 1 ] >= dfRet.ALL[i -1 ]:

lst2BeDropped.append(d)

lst2BeDropped.append(dfRet.index[i+ 1 ])

flag = 1

else :

if dfRet.ALL[i+ 1 ] <= dfRet.ALL[i -1 ]:

lst2BeDropped.append(d)

lst2BeDropped.append(dfRet.index[i+ 1 ])

flag = 1

lstValid = sorted(set(dfRet.index).difference(lst2BeDropped))

return dfRet.loc[lstValid]

资料来源:万得资讯,中金公司研究部

有了上面的准备,最终投入使用的函数就显得相对容易,只需要将上述辅助工具组合到一起即可。如下:

合并函数的程序实现

def generatePunc (dfKlines) :

# 处理包含关系

dfK = _exInclude(dfKlines)

# 通过滚动算法,找到符合顶分型、底分型的K线,存在lstUp和lstDown里面

lstUp, lstDown = getInflection(dfK)

print sorted(lstUp + lstDown)

# 将这些代表“拐点”的分型存入dfRet中,准备进一步过滤

dfRet = getRet(dfK, lstUp, lstDown)

# 去除同向相连的情况

dropSameDirection(dfRet)

# 去除二点过近且波动意义不大的情况

dfRet = dropNearPunc(dfRet, dfK)

# 将终结点与最后一个拐点连接

if not dfKlines.index[ -1 ] in dfRet.index:

dr = "up" if dfRet[ "pointType" ][ -1 ] == -1 else "down"

dfRet.loc[dfKlines.index[ -1 ], "ALL" ] = dfKlines.loc[dfKlines.index[ -1 ], "HIGH" ] if dr == "up" else dfKlines.loc[dfKlines.index[ -1 ], "LOW" ]

return dfRet

资料来源:万得资讯,中金公司研究部

对当前转债市场,一些有用的结论:

1、哪些比较大的品种,是处于日线级别的上行中? 大中盘转债中,至少苏银、光大、东财、大秦、国君、本钢、盛虹、青农、中化EB、本钢以及海亮符合这样的要求。当然,这样的择券倾向,和我们上期周报中提到的策略(乃至我们的十大个券),也比较接近。但相比于长期、中期动量,趋势划分的结果更具灵活性。

2、按照上述每一笔连线的长度,哪些正股是转债标的中,比较富有弹性的? 当下转债的标的很多,我们只能列举一部分。不过,既然在考虑弹性,那么在择券时,一般也与价格、溢价率搭配考虑,如此方可取得不错的组合盈亏比。

正股弹性较好的转债列表(截至2021年6月25日)

资料来源:万得资讯,中金公司研究部

转债一级市场跟踪

本周新公告了2个转债预案,分别为阿拉丁(4.01亿元)与禾丰股份(15亿元)。10个转债预案通过股东大会,包括慧云钛业(4.9亿元)、东杰智能(6亿元)、会通股份(8.5亿元)、中富通(5.05亿元)、华友钴业(76亿元)、科伦药业(30亿元)、中环环保(8.64亿元)、中天精装(6.07亿元)、五洲特纸(6.7亿元)、精工钢构(20亿元);6个预案获受理,分别为立华股份(21亿元)、温州宏丰(3.21亿元)、珀莱雅(8.04亿元)、科蓝软件(5.38亿元)、华翔股份(8亿元)、百润股份(12.8亿元);闻泰科技(86亿元)与川恒股份(11.6亿元)过发审委;台华新材(6亿元)与中大力德(2.7亿元)获核准。目前已核准待发行个券合计25只,金额共计382.86亿元;已过会未核准个券10只,合计金额212.61亿元。