加载出来的数据长这样,日期是20080102至20210521:
--------------------------------直接使用print(data)打印出来的结果-------------------------
作者的打印明显好看不少,这个后续再研究,不是关键。
-----------------------------------------------------------------------------------------------------------------
我们要用到的关键的4列分别是转债代码、交易日期、收盘价、转股溢价率,这几项信息已经足够我们回测的了。
接下来需要做的是构造一个收益率矩阵,列名为转债代码,行号为交易日期。我们首先来构造一个收盘价的矩阵
pricedf=data[['tickerBond','tradeDate','closePriceBond']]
------------------------------------------------------------------------------------------------------------------
这句代码应该是从原有的表中,选出三列
print结果如下:
-------------------------------------------------------------------------------------------------------------
pricedf=pricedf.set_index(['tradeDate','tickerBond']).unstack()['closePriceBond']
-------------------------------------------------------------------------------------------------------------
这句的作用应该是将tradeDate和ticketBond分别作为行索引和列索引,然后将closePriceBond作为二维索引中的数据。
print打印如下:
之前查过这个set_index的用法,说的乱七八糟,看着很高级,其实还不如打印出来结果,一下就看明白了。
-------------------------------------------------------------------------------------------------------------
pricedf.index=pd.to_datetime(pricedf.index)
-------------------------------------------------------------------------------------------------------------
这句查阅资料,应该是pricedf.index原来不是time类型,没法进行时间相关的运算,调这个函数,之后就可以当做时间类型来运算了。
打印出来的内容没啥区别
资料描述:
通过pandas.read_csv()或者pandas.read_excel()读取文件过后,得到的数据列对应的类型是“object”,这样没法对时间数据处理,可以用过pd.to_datetime将该列数据转换为时间类型,即datetime。
-------------------------------------------------------------------------------------------------------------
构造好的价格矩阵长这样:
注意,这里面有很多数据为NaN,这是因为在这些日期这些转债还没上市或已退市。
接下来用价格矩阵来构造收益率矩阵,直接用DataFrame的pct_change函数就可以了。
day_return=pricedf.pct_change().shift(-1)
-------------------------------------------------------------------------------------------------------------
函数定义:
df.pct_change()
DataFrame.pct_change(periods=1, fill_method=‘pad’, limit=None, freq=None, **kwargs)
表示当前元素与先前元素的相差百分比,当然指定periods=n,表示当前元素与先前n 个元素的相差百分比。
简单来说就是下一个元素减上一个元素,差值除以上一个元素。
后面的shift(-1)原文也写了,是将数据往前移动一天。
-------------------------------------------------------------------------------------------------------------
这里要注意,我算完之后用shift(-1)往前移了一天,这样算出来的2021年5月20日的收益率实际上对应的是20日收盘到21日收盘之间的收益率,这是因为我们在后面的计算中算出的策略信号矩阵是用20日的数据求得20日盘尾的信号,我们如果在20日盘尾按此信号买入的话那么实现的收益率正好对应后一天的收益率,这样我们在后面计算的时候就实现了日期对齐了。
下面要做的就是构造信号矩阵,信号矩阵要表达的信息是基于每个日期的收盘数据求得的下个日期的持仓信息。其中列名代表的是转债代码,行号代表的是信号计算日期。我们假设等权买入,每个单元格都用0和1来区分是否买入。由于我们在收益率矩阵中已经处理了日期对齐问题,这里日期就不用再处理了。计算信号矩阵前,我们先要计算双低因子,即双低=价格+溢价率。价格矩阵我们前面已经构造好了,同样的方法来构造溢价率矩阵。
premdf=data[['tickerBond','tradeDate','bondPremRatio']]
premdf=premdf.set_index(['tradeDate','tickerBond']).unstack()['bondPremRatio']
premdf.index=pd.to_datetime(premdf.index)
然后求出双低因子
factor=premdf+pricedf
-------------------------------------------------------------------------------------------------------------
我理解这里是两个矩阵相加,得到一个新的矩阵
-------------------------------------------------------------------------------------------------------------
接下来,就可以通过筛选每日最低的20个转债来得到信号矩阵了,这个过程稍微需要用到点技巧。
N=20
def selectTopN(tmp):
tmp=tmp.copy()
symbols=tmp.nsmallest(N).index
tmp[:]=0
tmp[symbols]=1
return tmp
signal=factor.apply(selectTopN,axis=1)
终于,我们得到了信号矩阵,全是零是不是有点慌?不要着急,因为总共几百列里只有20个1,每行里的1其实都是很稀疏的,我们随便找一列来验证一下。
row=signal.iloc[-1]row[row>0]
没错,不多不少正好20个。最好把这个持仓列表的双低值再人工校验一遍,保证我们的信号计算是正确的,这里就先跳过此步。
接下来终于到了激动人心的收益序列计算了,其实这步是最简单的,直接两个矩阵乘一下,注意这里并不是线性代数里面的矩阵乘法,而是两个矩阵的相同位置的数字直接相乘。然后将每行的结果求和除以20,就得到收益率序列了。
pnl=(signal*day_return).sum(axis=1)/N
注意,这里得出21日的收益率是0,这是正确的,因为我们在日期处理时将收益率的时间对齐到了信号的时间,21日产生的信号需要22日的数据才能知道收益。之所以要反复强调时间对齐问题,是因为一旦时间没弄对,就会引入未来函数,造成收益率超高的假象。
到这里,计算就已经完成了,我们还可以画个净值曲线来直观地看下回测结果,只要把每日的收益率加1后连乘就可以了。
(pnl+1).cumprod().plot(figsize=(10,5),grid=True)
好了,一个简单的按日调仓的双低转债策略就回测完成了,我数了一下,也就20行代码,真的是非常简单。为了方便你们复制粘贴,我把它们再合起来放一遍:
import pandas as pd
data=pd.read_pickle('convdata.pckl')
pricedf=data[['tickerBond','tradeDate','closePriceBond']]
pricedf=pricedf.set_index(['tradeDate','tickerBond']).unstack()['closePriceBond']
pricedf.index=pd.to_datetime(pricedf.index)
day_return=pricedf.pct_change().shift(-1)
premdf=data[['tickerBond','tradeDate','bondPremRatio']]
premdf=premdf.set_index(['tradeDate','tickerBond']).unstack()['bondPremRatio']
premdf.index=pd.to_datetime(premdf.index)
factor=premdf+pricedf
N=20
def selectTopN(tmp):
tmp=tmp.copy()
symbols=tmp.nsmallest(N).index
tmp[:]=0
tmp[symbols]=1
return tmp
signal=factor.apply(selectTopN,axis=1)
pnl=(signal*day_return).sum(axis=1)/N
(pnl+1).cumprod().plot(figsize=(10,5),grid=True)
最后再加点难度,如果我不想每天调仓,我想5天调一次仓,怎么做呢?这里要对信号矩阵做个采样,即每5天才算一次信号,稍微要一点技巧,直接给出代码和结果:
tmpdf=signal.iloc[range(0,len(signal),5)]
week_selected_df=pd.DataFrame(index=signal.index)
week_selected_df=week_selected_df.join(tmpdf)
week_selected_df=week_selected_df.fillna(method='pad')
pnl=(week_selected_df*day_return).sum(axis=1)/N
(pnl+1).cumprod().plot(figsize=(10,5),grid=True)