目录
免责声明:本文由作者参考相关资料,并结合自身实践和思考独立完成,对全文内容的准确性、完整性或可靠性不作任何保证。同时,文中提及的数据仅作为举例使用,不构成推荐;文中所有观点均不构成任何投资建议。请读者仔细阅读本声明,若读者阅读此文章,默认知晓此声明。本文使用掘金量化软件仅做为举例,不涉及软件宣传,推广及盈利。
本文主要简介一下量化CTA策略(改进版本BOLL策略),使用金量化3.0进行回测。
1. 策略流程
2. 策略逻辑
为了简化思路,对许多步骤的处理设置为静态。
2.1 回测参数
回测参数 | |
交易品种 | DCE.i2401, DCE.m2401, CZCE.MA401, SHFE.rb2401 |
交易价格 | 收盘价 |
交易频率 | 1分钟 |
起始时间 | 2023-10-15 9:00 |
结束时间 | 2023-10-20 15:00 |
交易费率 | 0.01% |
交易滑点 | 0.01% |
2.2 交易信号
这里使用一个类似于BOLL策略的指标作为交易信号。考虑到1分钟K线的频率太高,可能出现较多的交易信号。因此,考虑使用5分钟的K线(使用1分钟的K线来进行合成)。
参数名称 | 计算方式 |
均值 | 过去X个5分钟K线对应收盘价序列的均值 |
标准差 | 过去X个5分钟K线对应收盘价序列的标砖差 |
上轨低 | 均值加上M个标准差 |
上轨高 | 均值加上N个标准差 |
下轨低 | 均值减去N个标准差 |
下轨高 | 均值减去M个标准差 |
* 其中:X为整数,远大于M,N;M,N为整数,M<N 。
设置最新的价格为1分钟K线的收盘价,记为p。
1. 当p介于上轨高和上轨低之间,同时标准差大于5时,触发买入信号。
1. 当p介于下轨高和下轨低之间,同时标准差大于5时,触发卖出信号。
策略的主要逻辑还是基于动量效应,对于价格上涨和下跌分别设定相应的压力线和阻力线,当价格位于区间内时,触发相应的交易信号。需要注意,这里设置两根线的原因:以上涨为例,如果仅仅是突破上轨低线,此种情况下交易信号会非常多,会产生许多无效甚至是负向的信号;同时设定上轨高来作为限制,可以使买入的价格在可控范围内,不至于过高。
2.3 交易设置
交易品种 | 交易数量 | 止盈点位 | 止损点位 |
DCE.i2401 | 10 | 9 | 6 |
DCE.m2401 | 1 | 40 | 28 |
CZCE.MA401 | 1 | 24 | 17 |
SHFE.rb2401 | 1 | 38 | 27 |
止损和止盈基于静态设置,基准为对应的开仓均价,实时价格为对应时点1分钟K线的收盘价。止盈空间大概是止损的70%。另外,为了降低隔夜持仓的风险,设置相应的定时任务,在每天的15:00,对所有的持仓进行平仓。
为了进一步防止连续出现交易信号的情况,在交易上,对于单个交易品种,设定如下规则:
1. 在当前无持仓的情况下,出现交易信号,进行相应的开仓交易。
2. 在当前有持仓的情况下,即使出现交易信号,也不再进行开仓交易。
3. 代码实例
3.1 参数及源代码
设置5分钟频率线的数量X为40,M,N的值分别为1.5和3。源代码如下:
from gm.api import *
import numpy as np
"""
多品种改进boll策略
"""
def get_five_min(m):
# 获取5分钟对应的序列
symbol_list = ['00', '05']
for x in range(2, 13):
symbol_list.append(str(x * 5))
return 1 if m in symbol_list else 0
def init(context):
# 设置历史数据的参数,订阅相关品种数据
context.x = 40 # 数据量
context.M, context.N = 1.5, 3 # 标准差的下限和上限
context.count = (context.x + 1) * 5 + 1 # 分钟线的数量,多订阅便于合成5分钟线
context.frequency = '1m'
context.goods = ['DCE.i2401', 'DCE.m2401', 'CZCE.MA401', 'SHFE.rb2401']
schedule(schedule_func=all_close, date_rule='1d', time_rule='15:00:00') # 定时任务
for code in context.goods:
subscribe(symbols=code, frequency=context.frequency, count=context.count, wait_group=True)
def all_close(context):
position = context.account().positions()
if position:
order_close_all()
print('尾盘已清仓!')
def per(code):
# 设置止损止盈的标准
per_dict = {'DCE.i2401': 9, 'DCE.m2401': 40, 'CZCE.MA401': 24, 'SHFE.rb2401': 38}
win = per_dict.get(code)
lose = round(win * 0.7, 0)
return win, lose
def set_vol(code):
# 设置交易数量
vol_dict = {'DCE.i2401': 10, 'DCE.m2401': 1, 'CZCE.MA401': 1, 'SHFE.rb2401': 1}
return vol_dict.get(code)
def Trade_signal(context, close, new_close):
# 交易信号处理
'''
L和H分别表示均值减去标准差对应的倍数
'''
his_close = close[:-1]
M, N = context.M, context.N
ma, std = np.mean(his_close), np.std(his_close)
down_L = ma - N * std
down_H = ma - M * std
up_L = ma + M * std
up_H = ma + N * std
if down_L <= new_close <= down_H and std >= 5:
trade_signal = 'sell'
elif up_L <= new_close <= up_H and std >= 5:
trade_signal = 'buy'
else:
trade_signal = 'none'
return trade_signal
def open_trade(trade_signal, code):
# 开仓交易
vol = set_vol(code)
if trade_signal == 'buy':
order_volume(symbol=code, volume=vol, order_type=OrderType_Market,
side=OrderSide_Buy, position_effect=PositionEffect_Open)
print(code, '多单信号')
elif trade_signal == 'sell':
order_volume(symbol=code, volume=vol, order_type=OrderType_Market,
side=OrderSide_Sell, position_effect=PositionEffect_Open)
print(code, '空单信号')
else:
pass
def close_trade(code, cost, side, new_close, lose, win):
# 平仓交易
buy_lose, buy_win = cost - lose, cost + win
sell_lose, sell_win = cost + lose, cost - win
vol = set_vol(code)
if side == 1:
# 多单止损
if new_close <= buy_lose:
order_volume(symbol=code, volume=vol, order_type=OrderType_Market,
side=OrderSide_Sell, position_effect=PositionEffect_Close)
# 多单止盈
if new_close >= buy_win:
order_volume(symbol=code, volume=vol, order_type=OrderType_Market,
side=OrderSide_Sell, position_effect=PositionEffect_Close)
if side == 2:
# 空单止损
if new_close >= sell_lose:
order_volume(symbol=code, volume=vol, order_type=OrderType_Market,
side=OrderSide_Buy, position_effect=PositionEffect_Close)
# 空单止盈
if new_close <= sell_win:
order_volume(symbol=code, volume=vol, order_type=OrderType_Market,
side=OrderSide_Buy, position_effect=PositionEffect_Close)
else:
pass
def one_code_trade(context, code):
# 单个合约的交易
data = context.data(symbol=code, frequency=context.frequency, count=context.count)
# 合成对应的5min_K线
data['date'] = data['eob'].apply(lambda x: get_five_min(str(x).split(':')[1].split(' ')[0]))
data_5min = data.loc[data['date'] == 1]
close = data_5min['close'][len(data_5min) - 40:].tolist()
new_close = data['close'].values[-1]
# 设置交易信号所需参数,获取交易信号
trade_signal = Trade_signal(context, close, new_close)
# 获取单个产品对应的持仓
long_position = context.account().position(symbol=code, side=PositionSide_Long)
short_position = context.account().position(symbol=code, side=PositionSide_Short)
# 如果没有持仓,判断交易信号,满足条件下进行开仓交易
if not long_position and not short_position:
open_trade(trade_signal, code)
# 如果有持仓,判断止损和止盈,满足条件下进行平仓交易
else:
try:
cost = long_position.get('vwap') # 持仓均价
side = long_position.get('side') # 持仓方向
except:
cost = short_position.get('vwap') # 持仓均价
side = short_position.get('side') # 持仓方向
win, lose = per(code)
close_trade(code, cost, side, new_close, lose, win)
def on_bar(context, bar):
# 计算单个指标的
for code in context.goods:
one_code_trade(context, code)
if __name__ == '__main__':
'''
strategy_id策略ID,由系统生成
filename文件名,请与本文件名保持一致
mode实时模式:MODE_LIVE回测模式:MODE_BACKTEST
token绑定计算机的ID,可在系统设置-密钥管理中生成
backtest_start_time回测开始时间
backtest_end_time回测结束时间
backtest_adjust股票复权方式不复权:ADJUST_NONE前复权:ADJUST_PREV后复权:ADJUST_POST
backtest_initial_cash回测初始资金
backtest_commission_ratio回测佣金比例
backtest_slippage_ratio回测滑点比例
'''
run(strategy_id='',
filename='main.py',
mode=MODE_BACKTEST,
token='',
backtest_start_time='2023-10-15 09:00:00',
backtest_end_time='2023-10-20 15:00:00',
backtest_adjust=ADJUST_PREV,
backtest_initial_cash=500000,
backtest_commission_ratio=0.0001,
backtest_slippage_ratio=0.0001)
3.2 注意事项
1. 代码中的strategy_id由掘金量化软件自动生成,token由掘金设定。
2. 代码需要在掘金量化平台上运行,才能在软件上获得可视化的结果。
4. 策略结果
4.1 结果展示
从上图可以看出,策略整体取得正收益,但是基于回测角度且时间较短,稳定性未进一步检验。同时各类绩效指标的计算感觉还是不叫迷,同时收益的计算 是按照股票的规则,并未按照期货(即没有考虑杠杆)。
4.2 信号分析
4.3 交易明细
4.4 改进方向
可以改进的方向还有很多,以下主要例举几点:
1. 过往5分钟线的数量
2. 将5分钟线切换为其他频率
3. 调整M,N的值
4. 拉长回测时间
5. 增加交易品种
改进的效果主要还是基于收益和回撤来做判断,稳定性基于时间区间作为判断。
本期分享结束,有何问题欢迎交流!
免责声明:本文由作者参考相关资料,并结合自身实践和思考独立完成,对全文内容的准确性、完整性或可靠性不作任何保证。同时,文中提及的数据仅作为举例使用,不构成推荐;文中所有观点均不构成任何投资建议。请读者仔细阅读本声明,若读者阅读此文章,默认知晓此声明。本文使用掘金量化软件仅做为举例,不涉及软件宣传,推广及盈利。