【python量化回测】可转债折价策略收益率与最大回撤

论坛 期权论坛 期权     
可转债量化分析   2021-5-3 14:45   9764   0

假期终于有时间更新一波。因为文章更新并不频繁,微信并不会把最新的公众号文章及时推送在读者最新的列表中,可以将公众号设为星标,这样就能第一时间收到笔者的最新文章啦。


在写公众号初期就想着写一篇折价策略收益率的回测。因为拖延症原因,一直没有行文着手写,趁假期休闲时间把它写了。
[h2]策略说明:[/h2]顾名思义,折价策略,就是选择折价率最高的10只可转债,在一个时间范围里面轮动。如果到了下一个时间节点(比如一周后),重新获取10只折价率最高的可转债,那么就把上一批可转债掉出前10名的卖掉,买入最新一批进入前十的折价可转债。周而复此,不断轮动,中途记录每笔买卖,计算最大回撤与收益率。
[h2]回测条件:[/h2]笔者把回测开始时间设定在2018年1月1日,可转债信用申购新规后,可转债容量开始扩容。
回测终止时间为2021年4月30日,也就是当前时间的最后一个交易日,时间跨度为3年4个月。
买入标的为可转债债,不包含可交换债和纯债。
回测平台使用优矿,如果动手能力强,也可以在本地使用python配置相应的量化回测平台。
回测资金使用100W。
[h2]代码实现:[/h2]部分代码可以复用之前公众号里面文章里面的,比如获取所有可转债代码,并且过滤纯债,EB可交换债。可转债历史连续最多跌多少周?
核心代码如下:
  • 初始参数
  1. 1MyCash = 1000000  #现金
  2. 2Start_Cash= 1000000
  3. 3HighValue = MyCash  #最高市值
  4. 4Withdraw = 0  #最大回撤
  5. 5HoldRank = 10  #排名多少之后卖出
  6. 6HoldNum = 10  #持债支数
复制代码
上面的的参数看个人喜好而定,有人喜欢折价策略摊大饼的,那么可以把持债个数设为20,30等等,可以看看转债分散程度对最大回撤和收益的影响。
  • handle_data: 周期运行,设定为5个交易日(一周)运行一次,可以使用更长周期或更短周期,1天或者1个月。
    1. 1freq = 'd'         # 策略类型,'d'表示日间策略使用日线回测,'m'表示日内策略使用分钟线回测         
    2. 2refresh_rate = 5    # 调仓频率,表示执行handle_data的时间间隔,若freq = 'd' 时间间隔的单位为交易日,取盘前数据,若freq = 'm' 时间间隔为分钟
    复制代码
  • 周期运行的函数handle_data: 这个是优矿固定的函数,会在每个周期执行一遍,里面也就是核心计算函数代码。因为优矿不支持可转债的模拟买卖,所以需要自己写一个模拟买卖的交易数据与仓位。
  1. 1def handle_data(context):
  2. 2    global MyPosition, HighValue, MyCash, Withdraw, HoldRank, HoldNum,Start_Cash
  3. 3    previous_date = context.previous_date.strftime('%Y%m%d')
  4. 4    today_date = context.now.strftime('%Y%m%d')
  5. 5
  6. 6    #每天重新计算双低排名
  7. 7    ticker_list=bonds(today_date,today_date)
  8. 8    df = DataAPI.MktConsBondPerfGet(beginDate=today_date,endDate=today_date,secID='',tickerBond=ticker_list,
  9. 9                                      tickerEqu=u"",field=u"secID,closePriceBond,bondPremRatio,secShortNameBond",pandas="1")
  10. 10    df.set_index('secID',inplace=True)
  11. 11    df = df.sort_values(by="bondPremRatio" , ascending=True)
  12. 12    PosValue = MyCash
  13. 13
  14. 14    for stock in MyPosition:
  15. 15        try:
  16. 16            CurPrice = df.loc[stock]['closePriceBond']
  17. 17        except:
  18. 18            NoPrice = DataAPI.MktConsBondPerfGet(beginDate=previous_date,endDate=previous_date,secID=stock,tickerBond=u"",tickerEqu=u"",field=u"closePriceBond",pandas="1")
  19. 19            if len(NoPrice)==0:
  20. 20                last_date = (context.now + datetime.timedelta(days=-7)).strftime('%Y%m%d')
  21. 21                CurPrice=get_last_price(stock,last_date,today_date)
  22. 22
  23. 23            else:
  24. 24                CurPrice = NoPrice.closePriceBond[0]
  25. 25
  26. 26        PosValue += MyPosition[stock] * CurPrice * 10
  27. 27
  28. 28        if stock not in df.index[:HoldRank]:
  29. 29            MyCash += MyPosition[stock] * CurPrice * 10
  30. 30            try:
  31. 31                name=df.loc[stock]['secShortNameBond']
  32. 32            except:
  33. 33                name = stock
  34. 34            log.info('{} 卖出{},{},价格:{}'.format(today_date,stock,name,CurPrice))
  35. 35            del MyPosition[stock]
  36. 36
  37. 37    if PosValue > HighValue:HighValue = PosValue
  38. 38    if (HighValue - PosValue) / HighValue > Withdraw:Withdraw = (HighValue - PosValue) / HighValue
  39. 39
  40. 40    #买入排在HoldRank内的,总持有数量HoldNum
  41. 41    for i in range(HoldRank):
  42. 42        if len(MyPosition) == HoldNum or len(data.index) < HoldNum:break
  43. 43        if df.index[i] not in MyPosition.keys():
  44. 44            name=df.loc[df.index[i]]['secShortNameBond']
  45. 45            price=df.loc[df.index[i]]['closePriceBond']
  46. 46            log.info('{} 买入{}, {}, 价格{}, 折价{}'.format(today_date,df.index[i],name,price,cb_ration))
  47. 47            MyPosition[df.index[i]] = int(MyCash / (HoldNum - len(MyPosition)) / df['closePriceBond'][i] / 10)
  48. 48            MyCash -= MyPosition[df.index[i]] * df['closePriceBond'][i] * 10
  49. 49
  50. 50    ratio = (PosValue-Start_Cash)/Start_Cash*100
  51. 51
  52. 52    log.info(today_date + ': 最高市值 ' + str(HighValue) + ' , 当前市值 ' + str(PosValue) + '收益率 : '
  53. 53             +str(ratio)+'% , 最大回撤 ' + str(round(Withdraw*100,2))+'%')
复制代码
代码中有些异常处理要注意的,比如上周买入的折价已经被强赎了,下一周的时候需要按照强赎日的收盘价卖出,不然但交易日当天会找不到被强赎的转债数据。
[h2]回测运行结果:[/h2]截取部分中间执行结果,文末有获取所有交易记录的方法。



2018年开始执行的中间数据



2018年开始执行的中间数据,跑到2018年8月,折价策略勉强保本,最大回撤有11%
中间因为篇幅省略若干,基本每周都会发生交易。
直接跳到最后:






最后一次调仓记录是2021年4月27日,当初的100W现在已经变成了510W,当前的收益率为410%!最大回撤值为18%
换算为年化收益,年化收益率达到了64%。这个收益率与最大回撤比很多优秀的基金经理都要优秀,回撤也控制的很好。
同时也大幅跑赢沪深300



沪深300基准年化7收益率为7.7%。
根据日志输出的收益率绘制收益率曲线。



折价策略收益率曲线
最大回撤发生在2019年6月(数字在上图不好看得出来,因为曲线被后期收益率拉伸后,前半段显得很窄的跌幅)。
从数据日志中可以得到的:


因为上一个最大回撤是17.19%,后面的最大回撤就保持在18.59%。
如果从收益分布来看,其实绝大部分的收益从2020年贡献的,因为从数据得到,截至2019年10月份,折价策略的总收益为32%,经过2020年的抱团行情和疫情收益股,才把总体收益率拉伸到410%。所以笔者认为折价策略是一个优秀的策略,但同时有一丝丝的不确定的担忧,因为对于脱离债底的高价折价可转债(转债价格大于180以上),其实质和买入正股差不多(排除像英科转债这种高价高分红导致转股价大幅下调的个例),因为从回测数据中,2018年的熊市中,对于折价可转债而言,虽然有一定回撤,但是因为本身转债价格不高,在面值附近折价,有债底的保护,即使遇到暴跌行情亏损也不会太大。


对于激进投资者,买入高价折价转债等同于买入正股,可以采用转债套利策略,买入的高价折价转债后转股,股票可卖后继续买回其折价转债,因为本质都是看好正股继续涨。


目前还没大面积发生高价折价转债的正股进入漫长熊市,从而让转债也跟随大幅下跌,即使轮到到其他折价的转债,在普跌行情下轮动后转债也跟随下跌,这种情形下的回撤应该会非常大,虽然还没发生,但转债行情多变,每年都有新花样,就像突破3000元的英科转债,溢价率400%多,其价格也能维持在400多的横河转债。所以对比高价折价转债,笔者还是比较慎重,大仓位选择的是双底策略,降低波动,但策略并无对错,只是个人投资的偏好风格使然。本来想把双低策略的回测也一同分析下,由于篇幅有限就放在下一篇了,就代码而言,改动不多,改一行就可以的了,对该策略的读者朋友敬请留意哦。
如果需要获取文中每次轮动的交易数据,可以后台留言:折价交易数据

分享到 :
0 人收藏
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

积分:32
帖子:4
精华:0
期权论坛 期权论坛
发布
内容

下载期权论坛手机APP