持续行动1期 54/100,“AI技术应用于量化投资研究”。
昨天的文章相当于是backtrader的"hello world",直接感受一下bt的使用。
今天开始,我们要来实战一下,“轮动”模型是量化中经典的范式。
如果仅交易一个标的,比如一只股票或者指数,那么叫“择时”模型。
“择时”是所有模型里最难的。大家知道金融数据里噪声多,择时就是“预测”,难度很高。
“轮动”模型天然就是一个投资组合策略,本身组合的波动就在下降(标的池里的指数相关性越低,效果越好,比如沪深300+标普500+日经225等)。
“轮动”模型除了交易信号之外,还有明确的排序信号。
以“动量”为例,比如动量(20)>0.02的发出买入信号,但我们选择动量最大的K支才真正进行交易——所谓“骑最快的马”的逻辑。
01 数据准备
传统有大小盘轮动,行业轮动等。
我们以A股,美股为例进行演示。
feed = CSVDatafeed() codes = ['000300.SH','SPX'] for code in codes: feed.add_data(code, DATA_DIR_CSV.joinpath('{}.csv'.format(code))) df = feed.get_df(code) df = to_backtrader_dataframe(df) print(df)
A股沪深300指数:
美股标普500指数:
02 指标计算
我们考虑一个简单的策略:动量轮动。
20日动量>0.02时买入,20日动量<0时卖出,每次最多持有一支,超过一支时取动量大者。
我们只需要计算一个指标就是“20日动量”,是买卖规则指标,也是排序指标。
class StrategyRotation(bt.Strategy): params = dict( period=20, # 动量周期 ) def __init__(self): # 指标计算 self.inds = {} for data in self.datas: self.inds[data] = bt.ind.ROC(data, period=self.p.period)
03 交易规则
判断to_buy即ROC(20)>0.02,判断to_sell即ROC(20)<0,当前持仓>0
# 计算to_buy,判断roc>0.02 # 计算to_sell,判断roc<0 # 判断当前已经持仓 to_buy = [] to_sell = [] holding = [] for data, roc in self.inds.items(): if roc[0] > 0.02: to_buy.append(data) if roc[0] < 0: to_sell.append(data) if self.getposition(data).size > 0: holding.append(data)
对于已经持仓且要得到卖出信号的清仓:
for sell in to_sell: if self.getposition(sell).size > 0: logger.debug('清仓'+sell.p.name) self.close(sell)
计算新的待持仓组合 new_hold= to_buy+holding-to_sell
new_hold = list(set(to_buy + holding)) for data in to_sell: if data in new_hold: new_hold.remove(data)
等权重分配仓位:
# 等权重分配 todo: 已持仓的应应该不变,对cash对新增的等权分配 weight = 1 / len(new_hold) for data in new_hold: self.order_target_percent(data, weight)
04 风险/收益分析
核心指标:年化收益、波动率、最大回撤、夏普比。
分析器需要一个个添加:
self.cerebro.addanalyzer(bt.analyzers.AnnualReturn, _name='AnnualReturn') self.cerebro.addanalyzer(bt.analyzers.SharpeRatio, riskfreerate=0.0, annualize=True, _name='SharpeRatio') self.cerebro.addanalyzer(bt.analyzers.DrawDown, _name='DrawDown')
回测完成后打印出来:
print("--------------- AnnualReturn -----------------") print(strat.analyzers.AnnualReturn.get_analysis()) print("--------------- SharpeRatio -----------------") print(strat.analyzers.SharpeRatio.get_analysis()) print("--------------- DrawDown -----------------") print(strat.analyzers.DrawDown.get_analysis())
总体回报率155%,最大回撤20.6%。
backtrader写一个策略比较简单。
遗留问题,与benchmark对比,以及如何更加优雅的可视化。
内置的matplotlib的绘图有一些局限,网上有不同的解法,这个我们需要单独起一个篇幅来说明。
按以往经验,不用仓促造轮子,网上有成熟的解决方案,学习,改造成适应我们的版本。
总结:backtrader是一个成熟的框架,有成熟的生态,包括各种案例等。后续会围绕这个基础框架开展智能量化。
心得:
如果你要开一家包子铺,重点就关注如何做出好吃的包子,更快更好地做出好包子。不要去研究面粉怎么来的,小麦怎么种的,蒸笼是如何做出来的,竹子是如何生长的。也许它们之间有些许的联系,但是竹子对包子的影响,远不及你把调料好好研究一番——直面结果而去,站在巨人的肩膀上这很重要。
但你可能会说,我需要研究“综述”,才知道这个行业的全貌。也没问题,抓住主线,之于包子,最重要就是口味;之于量化,最重要就是策略。你要落地策略的时候,也许先碰到了pyalgotrade,那就用好,功能不够了,qlib或backtrader,甚至是joinquant或者bigquant这样的网上平台,反正终会遇到,但要最快的速度渡过这一步。