发布于VeighNa社区公众号【vnpy-community】
原文作者:黄太哲、李思佳 | 发布时间:2023-5-30
本篇文章的分享内容为BollMACD策略,在各种互联网平台上围绕“Boll+MACD”主题的量化文章层出不穷,使得策略原作者的身份无法查证。
本文中通过VeighNa Elite平台对该思路进行了代码上的实现与改进,希望能给大家后续的策略研究带来些许灵感。
策略基本信息
策略核心原理
BollMACD策略希望利用布林通道和MACD双指标共振来完成对于交易信号的判断,由于在震荡行情下,MACD容易发生钝化,因此需要对于趋势判断更加敏锐的布林带协助对趋势的捕捉。
在VeighNa Elite平台下的EliteCtaStrategy模块中提供了专门的boll、macd等常用技术指标的计算函数来减轻策略代码化的工作量,并提供了协助交叉判断的cross_over及cross_below函数,这些函数的用法将在策略代码实现的内容里详细说明。
结合布林通道和MACD指标二者共同对趋势进行判断,可以得到以下的交易信号:
- 当期收盘价突破布林带上轨,且前n期内MACD出现金叉并无死叉,买入做多
- 当期收盘价突破布林带下轨,且前n期内MACD出现死叉并无金叉,卖出做空
布林通道从通道突破的角度进行判断,结合使用均线交叉的MACD,使得策略对于趋势的判断更加稳健,布林通道的判断又补全了MACD高延迟的缺陷。需要指出的是,尽管互联网资料中广泛使用布林通道中轨方向来进行开仓信号判断,但在本策略的实测中,该方法的效果并不理想。
策略代码实现
策略参数定义
和之前的RumiStrategy一样,BollMacdStrategy也同样采用EliteCtaStrategy模块下的策略模板类EliteCtaTemplate开发,通过基础参数来配置策略运行的K线数据周期的方式,使得策略的开发更加简易和规范:
1
|
class BollMacdStrategy(EliteCtaTemplate): """布林带MACD策略""" author = "VeighNa Elite" # 基础参数(必填) bar_window: int = Parameter(5) # K线窗口 bar_interval: int = Parameter("1m") # K线级别 bar_buffer: int = Parameter(500) # K线缓存
|
分别使用Parameter和Variable辅助类来定义策略的参数和变量:
1
|
# 策略参数(可选) boll_window: int = Parameter(20) # 布林带窗口 boll_dev: float = Parameter(2.0) # 布林带标准差乘数 fast_window: int = Parameter(9) # 快线窗口 slow_window: int = Parameter(26) # 慢线窗口 macd_window: int = Parameter(4) # MACD窗口 macd_patience: int = Parameter(3) # MACD交叉回看窗口 trailing_stop: int = Parameter(75) # 移动止损系数 risk_window: int = Parameter(10) # 风险计算窗口 risk_capital: int = Parameter(1_000_000) # 交易风险投入 price_add: int = Parameter(5) # 委托下单超价 min_pos_chg: float = Parameter(0.05) # 持仓变动下限 # 策略变量 trading_size: float = Variable(0.0) # 交易数量 boll_up: float = Variable(999_999.0) # 布林带上轨 boll_down: float = Variable(-1.0) # 布林带下轨 time_coef: float = Variable(1.0) # 跟踪时间系数 intra_trade_high: float = Variable(999_999.0) # 持仓期间高价 intra_trade_low: float = Variable(-1.0) # 持仓期间低价
|
信号指标计算
1
|
# 策略参数(可选) boll_window: int = Parameter(20) # 布林带窗口 boll_dev: float = Parameter(2.0) # 布林带标准差乘数 fast_window: int = Parameter(9) # 快线窗口 slow_window: int = Parameter(26) # 慢线窗口 macd_window: int = Parameter(4) # MACD窗口 macd_patience: int = Parameter(3) # MACD交叉回看窗口 trailing_stop: int = Parameter(75) # 移动止损系数 risk_window: int = Parameter(10) # 风险计算窗口 risk_capital: int = Parameter(1_000_000) # 交易风险投入 price_add: int = Parameter(5) # 委托下单超价 min_pos_chg: float = Parameter(0.05) # 持仓变动下限 # 策略变量 trading_size: float = Variable(0.0) # 交易数量 boll_up: float = Variable(999_999.0) # 布林带上轨 boll_down: float = Variable(-1.0) # 布林带下轨 time_coef: float = Variable(1.0) # 跟踪时间系数 intra_trade_high: float = Variable(999_999.0) # 持仓期间高价 intra_trade_low: float = Variable(-1.0) # 持仓期间低价
|
在上述代码中,展示了EliteCtaStrategy模块中的boll函数和macd函数,下文将专注于对这两个关键函数的讲解。
boll – 计算布林通道上下轨
参数:
- data [np.ndarray]: 可输入HistoryManager对象的成员,如hm.close
- window [int]: 布林通道窗口,即计算用的时间区间
- dev [float]:布林通道标准查乘数
返回:
-
tuple[np.ndarray, np.ndarray]
-
up [np.ndarray]:布林通道上轨,为输出元组中的第一个数组
-
down [np.ndarray]:布林通道下轨,为输出元组中的第二个数组
macd – 计算MACD指标
参数:
- data [np.ndarray]: 可输入HistoryManager对象的成员,如hm.close
- fastperiod [int]:快线窗口
- slowperiod [int]:慢线窗口
- signalperiod [int]:MACD窗口
返回:
-
tuple[np.ndarray, np.ndarray, np.ndarray]
-
macd [np.ndarray]:对应DIF,为输出元组中的第一个数组
-
macd_signal [np.ndarray]:对应DEA,为输出元组中的第二个数组
-
macd_hist [np.ndarray]:对应MACD柱状图,为输出元组中的第三个数组
动态仓位计算
同RUMI策略一样,借助EliteCtaTemplate提供的calculate_volume函数,可以轻松地计算出适合当前市场环境的交易委托数量,基于市场价格的波动水平对交易风险进行动态调整:
1
|
# 计算交易数量 self.trading_size = self.calculate_volume(self.risk_capital, self.risk_window, 1000, 1)
|
目标交易执行
在【Elite量化策略实验室】RUMI策略 - 1文章中,已对EliteCtaTemplate提供的目标仓位交易执行功能进行了详尽的阐述。相信阅读过此文的读者能够在接下来的代码部分发现许多熟悉的内容:
1
|
# 初始化新一轮目标(默认不变) new_target: int = last_target # 判断交易信号 recent_crosses: np.ndarray = self.cross_array[-self.macd_patience:] long_signal: bool = hm.close[-1] > self.boll_up and (recent_crosses >= 0).all() and (recent_crosses > 0).any() short_signal: bool = hm.close[-1] < self.boll_down and (recent_crosses <= 0).all() and (recent_crosses < 0).any() # 执行开仓信号 if not last_target: if long_signal: new_target = self.trading_size elif short_signal: new_target = -self.trading_size # 突破阈值动态调仓 if last_target != 0: pos_chg: float = abs(1 - self.trading_size / last_target) if pos_chg > self.min_pos_chg: if last_target > 0: new_target = self.trading_size elif last_target < 0: new_target = -self.trading_size # 记录时间系数变化 if not last_target: self.time_coef = 1 self.bar_num = 0 elif self.bar_since_entry() >= self.bar_num: self.time_coef = self.time_coef - 0.1 self.time_coef = max(self.time_coef, 0.5) self.bar_num = self.bar_since_entry() # 记录价格极大值和极小值 if self.bar_since_entry() == 0: self.intra_trade_high = hm.high[-1] self.intra_trade_low = hm.low[-1] else: self.intra_trade_high = min(self.intra_trade_high, hm.high[-1]) self.intra_trade_low = max(self.intra_trade_low, hm.low[-1]) # 移动止损平仓 trailing_value: float = (hm.open[-2] * self.trailing_stop / 1000) * self.time_coef long_stop: float = self.intra_trade_low - trailing_value short_stop: float = self.intra_trade_high + trailing_value if last_target > 0 and hm.low[-1] < long_stop: new_target = 0 elif last_target < 0 and hm.high[-1] > short_stop: new_target = 0 # 设置新一轮目标 self.set_target(new_target) # 执行目标交易 self.execute_trading(self.price_add) # 推送UI更新 self.put_event()
|
基于EliteCtaTemplate提供的目标交易执行模式,可以显著降低策略编写的难度,同时也削减了后续策略代码的管理维护成本。除此之外,该模式还能在开盘初始化时,通过历史数据回放直接恢复到正确的策略状态,从而降低了实盘运维中的错误风险。
回测结果
下图回测结果由米筐RQData提供的j99连续指数合约数据得到,回测配置如下:
| 字段名 |
值 |
| 本地代码 |
j99.DCE |
| K线周期 |
1分钟 |
| 开始日期 |
2013-1-18 |
| 结束日期 |
2023-4-11 |
| 手续费率 |
0.0001 |
| 交易滑点 |
1 |
| 合约乘数 |
1 |
| 价格跳动 |
1 |
| 回测资金 |
100W |
基于VeighNa Elite版CTA回测引擎中内置的R-Cubed统计指标,对BollMACD策略在j99.DCE的2013-2018时间段上进行遗传优化后,得到的策略参数如下:
| 字段名 |
值 |
| bar_window |
5 |
| bar_interval |
“1m” |
| bar_buffer |
500 |
| boll_dev |
2 |
| macd_patience |
3 |
| risk_window |
10 |
| risk_capital |
10000 |
| price_add |
4 |
| trailing_stop |
75 |
| min_pos_chg |
0.5 |
| bar_window |
5 |
| boll_window |
50 |
| fast_window |
9 |
| slow_window |
20 |
| macd_window |
5 |
将该参数应用到2013-2023数据上的回测结果为(2019后为样本外):
下篇文章将会进一步分享BollMACD策略在更多时间周期和期货品种上的回测绩效,欢迎关注。
VeighNa Elite版已正式上线发布1.0.3版本,目前对量化私募机构的投研人员提供一个月免费试用,感兴趣的同学请扫码添加小助手:
免责声明文章中的信息或观点仅供参考,作者不对其准确性或完整性做出任何保证。读者应以其独立判断做出投资决策,作者不对因使用本报告的内容而引致的损失承担任何责任。