文章目录
1. 交易策略2. Backtrader回测程序3. 回测效果3.1 1月1日 - 1月1日3.2 1月1日 — 1月1日3.3 1月1日 — 1月1日动量因子的概述与测算,阿隆指标测算请参考:/weixin_35757704/article/details/128767040
1. 交易策略
阿隆动量因子策略构建:
相同权重,满仓持有5个指数;每卖出1个或多个指数后,都会等权重用现金买入指数,始终保持5个指数的持仓买入:AroonDown+AroonUp>50,且当AroonUp > AroonDown时买入卖出:AroonDown+AroonUp>50,且当AroonDown > AroonUp时卖出买入时: 计算【aroon差额】= AroonUp-AroonDown;得到上涨与下跌动量的差额每个指数按照 aroon差额 从大到小的顺序排序;为了买入上涨动量最强,且下跌动量最弱的指数如果多个指数的aroon差额值相同,则按照到测算的胜率高低,按照历史测算时综合胜率的先后关系排序然后从上到下依次买入指数,最终保持始终持仓5个指数
以 1/1000 作为摩擦成本,不计算管理费
2. Backtrader回测程序
这里我们使用backtrader回测框架,回测的内容除了在 量化策略——准备3 数据、Backtrader回测框架与quantstats评价指标 中提供的一些方法外,核心的策略代码如下:
class AroonStrategy(TemplateStrategy):params = (("start_date", None), ('end_date', None),)def __init__(self):super().__init__()# 基本配置self.max_hold = 5self.this_month = self.params.start_date.monthtotal_bond_code = []for this_data in self.datas:if type(this_data).__name__ == "StockData":total_bond_code.append(this_data._name)self.total_bond_code = total_bond_codeself.vic_dict = {'801210.SI': 0, '801110.SI': 1, '801750.SI': 2, '801120.SI': 3, '801890.SI': 4, '801080.SI': 5,'801200.SI': 6, '801140.SI': 7, '801160.SI': 8, '801730.SI': 9, '801010.SI': 10,'801130.SI': 11, '801760.SI': 12, '801770.SI': 13, '801050.SI': 14, '801040.SI': 15,'801180.SI': 16, '801720.SI': 17, '801710.SI': 18, '801030.SI': 19, '801880.SI': 20,'801170.SI': 21, '801790.SI': 22, '801150.SI': 23, '801230.SI': 24, '801740.SI': 25,'801950.SI': 26, '801780.SI': 27}def next(self):"""最核心的触发策略"""hold_bond_name = [_p._name for _p in self.broker.positions if self.broker.getposition(_p).size > 0] # 查看持仓# 计算指标_candidate_dict = {}for _candidate_code in self.total_bond_code:_candidate_dict[_candidate_code] = {"aroondown": self.getdatabyname(_candidate_code).aroondown[0],"aroonup": self.getdatabyname(_candidate_code).aroonup[0],}candidate_df = pd.DataFrame(_candidate_dict).Tcandidate_df['aroo_energy'] = candidate_df['aroondown'] + candidate_df['aroonup']candidate_df['aroo_mines'] = candidate_df['aroonup'] - candidate_df['aroondown']candidate_df = pd.merge(candidate_df, pd.DataFrame(self.vic_dict, index=['rank']).T,left_index=True, right_index=True)candidate_df = candidate_df.sort_values(['aroo_mines', "rank"], ascending=[False, True])if candidate_df['aroo_energy'].sum() == 0:returnif len(hold_bond_name) < self.max_hold:self.get_buy_bond(candidate_df, self.max_hold - len(hold_bond_name))# 卖出的逻辑for _index, _series in candidate_df.iterrows():if _index in hold_bond_name:if _series['aroonup'] < _series['aroondown']:self.sell(data=_index, size=self.getpositionbyname(_index).size,valid=self.getdatabyname(_index).datetime.date(1))def get_buy_bond(self, candidate_df, buy_num):hold_bond_name = [_p._name for _p in self.broker.positions if self.broker.getposition(_p).size > 0]for index, series in candidate_df.iterrows():if series["aroo_energy"] <= 50: # 当 AroonDown + AroonUp > 50时才执行判操作continueif index in hold_bond_name:continuebuy_data = self.getdatabyname(index)if len(buy_data) >= buy_data.buflen():continueif series['aroonup'] > series['aroondown']:buy_cost_value = self.broker.getcash() / (self.max_hold - len(hold_bond_name)) * (1 - minfo[None].mission)buy_size = buy_cost_value / self.getdatabyname(index).close[0]self.buy(data=buy_data, size=buy_size, exectype=bt.Order.Limit,price=buy_data.close[0],valid=buy_data.datetime.date(1))logger.debug("买入 {} size:{} 预计费用:{}".format(index, buy_size, buy_cost_value))buy_num -= 1if buy_num == 0:breakdef stop(self):# 绘制净值曲线wealth_curve_data = {}for _k, _v in self.value_record.items():wealth_curve_data[_k] = _v / self.broker.startingcashself.plot_wealth_curve(wealth_curve_data, "arron_{}_{}".format(self.params.start_date.strftime("%Y-%m-%d"), self.params.end_date.strftime("%Y-%m-%d")))# 最终结果daily_return = cal_daily_return(pd.Series(self.value_record))_, record_dict = cal_rolling_feature(daily_return)print(record_dict)print('a')
在策略中,当【aroon差额】相同时,按照测算的胜率从大到小依次买入,下面的字典便是每个行业指数胜率从大到小的排名,用于辅助排序:
{'801210.SI': 0, '801110.SI': 1, '801750.SI': 2, '801120.SI': 3, '801890.SI': 4, '801080.SI': 5,'801200.SI': 6, '801140.SI': 7, '801160.SI': 8, '801730.SI': 9, '801010.SI': 10,'801130.SI': 11, '801760.SI': 12, '801770.SI': 13, '801050.SI': 14, '801040.SI': 15,'801180.SI': 16, '801720.SI': 17, '801710.SI': 18, '801030.SI': 19, '801880.SI': 20,'801170.SI': 21, '801790.SI': 22, '801150.SI': 23, '801230.SI': 24, '801740.SI': 25,'801950.SI': 26, '801780.SI': 27}
3. 回测效果
3.1 1月1日 - 1月1日
最终净值:1.21复合年增长: 0.226夏普比率: 0.885索蒂诺: 1.167omega: 1.175最大回撤: -0.173年波动率: 0.2533.2 1月1日 — 1月1日
最终净值:0.909复合年增长: -0.092夏普比率: -0.481索蒂诺: -0.663omega: 0.924最大回撤: -0.191年波动率: 0.2053.3 1月1日 — 1月1日
最终净值:0.74复合年增长: -0.255夏普比率: -1.681索蒂诺: -2.073omega: 0.741最大回撤: -0.258年波动率: 0.186如果觉得《量化选股——基于动量因子的行业风格轮动策略(第2部分—策略回测)》对你有帮助,请点赞、收藏,并留下你的观点哦!