年化17.5%,十年8倍的“双低”转债策略:从零实现量化回测系统之三

本文介绍了将AI技术应用于量化投资,特别是可转债投资的策略。通过‘双低’策略选择投资标的,并利用权重调整进行动态调仓,实现投资组合的再平衡。同时,借助qlib数据库获取全市场数据,实现回测。最后,通过SelectTopK算子优化策略,展示了策略的通用性,并讨论了回测结果和优化空间。
摘要由CSDN通过智能技术生成

持续行动1期 43/100,“AI技术应用于量化投资研资”之可转债投资。

投资的心法大同小异,都是以合适的价格买入好的东西。

由于所处的周期位置不同,判断的标准产生了差异罢了。

为何我们先从转债入手,因为转债与股票相比,多了债性,估值更容易,风险相对更低。若是操作得当,收益并不比股票差,而且转债背后仍然是上市公司分析,是股票。

所以,对于基本面的分析逻辑是类似的,后面可以平滑切换到股票投资。

转债投资里最经典的“双低”策略,大部分的策略都是它的变种,心法是一样的。

前面的文章讲到“积木式”的策略搭建,做到了自动选股和权重分配。

今天我们要把这些信息落实到模拟交易里。

01 执行“按权重调仓”交易

前面我们实现了order_by_mv, order_sell_mv这两个方法,是买入某支股票mv,或者卖出某支股票mv。

在投资组合管理中,更通用的操作是按总市值的仓位比例来分配的。

比如股债平稳策略,股票70%,债券30%,无论你当前市值是多少,都在这个比例来操作,我们并不关心具体市值是多少。

另外这个方法同样适用于“动态再平稳”,就是定时执行这个操作,把仓位恢复到一个即定的比例。

在实际操盘过程中,这里涉及到先卖再买(当然存在卖不了或者滑点的情况),出于简化的考虑,我们可以先忽略——投资讲“模糊的正确”

把当前持仓市值计算出来:

# 持仓市值,不包括cash
def _calc_total_holding_mv(self):
    total_mv = 0.0
    for s, mv in self.curr_holding.items():
        total_mv += mv
    return total_mv

执行按比例调仓:

# weights的格式 {'symbol1':0.2, 'symbol2':0.7} weights加和需要0<= x <=1,若小于1,则剩余部分按cash算。
def adjust_weights(self, date, weights):

    # 计算当前的总市值
    total_mv = self._calc_total_holding_mv()
    total_mv += self.curr_cash

    old_pos = self.curr_holding.copy()
    self.curr_holding.clear()
    # 分配新权重
    for s, w in weights.items():
        self.curr_holding[s] = total_mv * w

    self.curr_cash = total_mv - self._calc_total_holding_mv()
    logger.info('发起权重调仓,日期:{}, 旧仓位:{},新仓位:{}'.format(date, old_pos, self.curr_holding))

在引擎处直接调用即可:

e = Engine(init_cash=100000, datafeed=feed)
e.run(algo_list=[RunOnce(), SelectAll(), WeightEqually(), AdjustWeights()])
logger.info('回测完成!')
logger.info(e.acc.cache_portfolio_mv[-1])

茅台就算不复权,从02年买入并持有,也是50多倍呀!

02 使用qlib数据库

我们需要对可转债全市场数据做回测,所以一个高性能的数据库是必要的。

import qlib
from qlib.constant import REG_CN
from qlib.data import D
from qlib.data.dataset.loader import QlibDataLoader


class QlibDataFeed:
    def __init__(self):
        self.all_df = None
    def add_data(self, data_dir):
        qlib.init(provider_uri=data_dir, region=REG_CN)

    def get_all_df(self, start_date='2010-01-01'):
        if self.all_df:
            return self.all_df

        fields = ['$close',
                  '$close/Ref($close,1)-1',
                  '$close+ ($close/(100/$chg_price*$stk_close)-1)*100'
                  ]
        names = ['close','rate', 'double_low']
        data_loader_config = {
            "feature": (fields, names),
            # "label": (labels, label_names)
        }
        data_loader = QlibDataLoader(config=data_loader_config)
        instruments = D.instruments(market='all')
        df = data_loader.load(instruments=instruments, start_time=start_date)
        df = df['feature']
        df.reset_index(level=1, inplace=True)
        df.rename(columns={'instrument':'code'}, inplace=True)
        self.all_df = df
        return self.all_df

从2010年开始所有转债的日频交易数据,以及它们的“双低值”一次计算出来,一共30万+条数据:

03 SelectTopK算子

由于我们是每天执行一次,所以不需要加时间算子。

策略上需要把“买入并持有”的SelectAll改成SelectTopK即可,也就是选择“双低值”最小的前K个。

我们的策略是不是很通用?

Qlib框架有TopK的策略,我们可以借用过来,封装成我们自己的“算子”。

class SelectTopK:
    def __init__(self, K=10, order_by='pred_score', ascending=False):
        self.K = K
        self.order_by = order_by
        self.ascending = ascending

    def __call__(self, context):
        logger.debug(self.__class__.__name__)
        df_bar = context['bar']

        # 前面还可以加规则,所以先看有没有选股过程selected
        if 'selected' in context.keys():
            if len(context['selected']) == 0:
                # print('SelectTopK遇空仓,直接跳过')
                # 若前面计算过selected,是空仓,那不需要排序,继续下一轮,但不退出——有可能要清仓操作。
                return False
            to_select = []
            for s in context['selected']:
                if s in df_bar.index:
                    to_select.append(s)
            #规则选股后,在子集里排序
            df_bar = df_bar.loc[to_select]

        df_bar.sort_values(by=self.order_by, ascending=self.ascending, inplace=True)  # 倒序
        symbols = df_bar.index[:self.K]
        logger.debug('选股结果:{}'.format(symbols))
        context['selected'] = symbols

这里暂未考虑,已经持仓的就不动的,只调仓新增的和卖出的,这个下一步实现。

算子使用非常简单:

e = Engine(init_cash=100000, datafeed=feed)
# e.run(algo_list=[RunOnce(),SelectAll(), WeightEqually(), AdjustWeights()])
e.run(algo_list=[SelectTopK(ascending=True, K=5, order_by='double_low'), WeightEqually(), AdjustWeights()])

相比“买入并持有”到“双低"策略我们仅改动一行代码

一个基础的版本,没有仔细筛选转债背后公司的质量,也没有判断回售期之类的,作为一个benchmark,十年8倍!

年化17.5%,不过最大回撤有点大,达到45.9%,所以这里还有较大的优化空间,今天主要是检验回测系统。

从零开始实现一个量化回测系统(一)

从零开始实现一个量化回测系统(二)

关于投资的思考

无论要不要以此为职业、事业,如同写作技能一样,每个人应该掌握一点投资知识。

投资能力其实反映了对世界的认知

除去必要的金融知识,比如你要交易转债,你肯定得知道转债的交易规则,以及背后的定价逻辑——这些基础知识都是可以很快学会的。

投资的天花板,有点像比特币之于李笑来。

他不是职业投资人,他讲投资也只讲定投。但他的认知结构和”常识“,在他初次见到BTC的时候,敏感的认为这是个机会,并抓住这个时间窗口。

飞狐,科技公司CTO,用AI技术做量化投资;以投资视角观历史,解时事;专注个人成长与财富自由。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

AI量化投资实验室

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值