问题描述
解决方案
这是一个基于JoinQuant(聚宽)平台的商品期货布林带(Bollinger Bands)突破策略。
策略逻辑
- 标的:以螺纹钢(RB)主力合约为例。
- 指标:计算N日的移动平均线(中轨),以及标准差。上轨 = 中轨 + K * 标准差,下轨 = 中轨 - K * 标准差。
- 开仓条件:
- 做多:当价格突破上轨时,平空仓并开多仓。
- 做空:当价格跌破下轨时,平多仓并开空仓。
- 平仓条件(回归中轨):
- 持有多单时,价格跌破中轨,平多仓。
- 持有空单时,价格升破中轨,平空仓。
- 移仓换月:自动获取主力合约,当主力合约切换时,策略会自动在新合约上交易(旧合约平仓逻辑需根据实际需求处理,本示例简化为只在当前主力上操作)。
策略代码
# -*- coding: utf-8 -*-
import numpy as np
import pandas as pd
from jqdata import *
def initialize(context):
"""
初始化函数,设定基准、滑点、手续费、期货账户等
"""
# 设定基准(这里随意设定一个,期货策略主要看绝对收益)
set_benchmark('000300.XSHG')
# 开启动态复权模式(真实价格)
set_option('use_real_price', True)
# 过滤掉order系列API产生的比error级别低的log
log.set_level('order', 'error')
# 设定期货账户
# 初始资金 500,000
init_cash = 500000
set_subportfolios([SubPortfolioConfig(cash=init_cash, type='futures')])
# 设定期货手续费(以螺纹钢为例,万分之一)
set_order_cost(OrderCost(open_commission=0.0001, close_commission=0.0001, close_today_commission=0.0001), type='futures')
# 设定滑点
set_slippage(PriceRelatedSlippage(0.002), type='futures')
# --- 策略参数设置 ---
g.underlying_symbol = 'RB' # 标的品种:螺纹钢
g.n = 20 # 布林带窗口周期
g.k = 2.0 # 布林带宽度(标准差倍数)
g.trade_lot = 2 # 每次交易手数
# 每天开盘时运行
run_daily(trade_func, time='open')
def trade_func(context):
"""
交易主逻辑函数
"""
# 1. 获取当前主力合约
dom_contract = get_dominant_future(g.underlying_symbol)
# 如果获取不到主力合约(比如上市初期或数据缺失),则跳过
if not dom_contract:
return
# 2. 获取历史数据
# 取过去 N + 2 天的数据,保证计算指标足够
# fields包含 'close', 'high', 'low'
data = attribute_history(dom_contract, g.n + 2, '1d', ['close'])
# 如果数据长度不足,无法计算指标,跳过
if len(data) < g.n:
return
# 3. 计算布林带指标
close_prices = data['close'].values
# 计算移动平均(中轨)
# 使用 pandas 的 rolling 函数计算
df_close = pd.Series(close_prices)
ma = df_close.rolling(window=g.n).mean()
std = df_close.rolling(window=g.n).std()
# 获取最新的指标值(昨天收盘的数据,用于决定今天的操作)
# 注意:attribute_history 获取的是不包含今天的数据(如果是按天回测且在open运行)
current_ma = ma.iloc[-1]
current_std = std.iloc[-1]
up_band = current_ma + g.k * current_std # 上轨
dn_band = current_ma - g.k * current_std # 下轨
# 获取昨日收盘价作为信号判断依据
last_close = close_prices[-1]
# 4. 获取当前持仓情况
# long_positions 和 short_positions 是字典,key是合约代码
long_pos = context.portfolio.long_positions.get(dom_contract)
short_pos = context.portfolio.short_positions.get(dom_contract)
has_long = long_pos and long_pos.total_amount > 0
has_short = short_pos and short_pos.total_amount > 0
# 5. 交易逻辑
# --- 开仓/反手逻辑 ---
# 价格突破上轨,做多
if last_close > up_band:
# 如果有空单,先平空
if has_short:
log.info("价格突破上轨,平空仓: %s" % dom_contract)
order_target(dom_contract, 0, side='short')
# 如果没有多单,开多
if not has_long:
log.info("价格突破上轨,开多仓: %s" % dom_contract)
order(dom_contract, g.trade_lot, side='long')
# 价格跌破下轨,做空
elif last_close < dn_band:
# 如果有多单,先平多
if has_long:
log.info("价格跌破下轨,平多仓: %s" % dom_contract)
order_target(dom_contract, 0, side='long')
# 如果没有空单,开空
if not has_short:
log.info("价格跌破下轨,开空仓: %s" % dom_contract)
order(dom_contract, g.trade_lot, side='short')
# --- 止盈/回归中轨平仓逻辑 ---
# 持有多单,价格跌破中轨,平多
elif has_long and last_close < current_ma:
log.info("多单回归中轨,平多仓: %s" % dom_contract)
order_target(dom_contract, 0, side='long')
# 持有空单,价格升破中轨,平空
elif has_short and last_close > current_ma:
log.info("空单回归中轨,平空仓: %s" % dom_contract)
order_target(dom_contract, 0, side='short')
# 6. 移仓换月处理 (简单版)
# 检查持仓中是否有非主力合约,如果有,则平仓
for security in list(context.portfolio.long_positions.keys()):
if security != dom_contract:
order_target(security, 0, side='long')
log.info("移仓换月,平旧合约多单: %s" % security)
for security in list(context.portfolio.short_positions.keys()):
if security != dom_contract:
order_target(security, 0, side='short')
log.info("移仓换月,平旧合约空单: %s" % security)
代码要点解析
-
期货账户设置:
- 必须在
initialize中使用set_subportfolios将账户类型设置为futures,否则无法进行期货交易。 - 使用
set_order_cost设置期货特定的手续费结构。
- 必须在
-
主力合约获取:
- 使用
get_dominant_future('RB')动态获取当前的主力合约代码(如RB2310.XSGE)。这是期货策略的关键,因为合约会到期。
- 使用
-
数据获取:
- 使用
attribute_history获取历史收盘价。这里设置为g.n + 2是为了确保计算滚动平均值时有足够的数据点。
- 使用
-
交易指令:
- 期货交易需要指定方向
side参数:'long'(多) 或'short'(空)。 order(security, amount, side='long'):开多单。order_target(security, 0, side='short'):平空单(将空单仓位调整为0)。
- 期货交易需要指定方向
-
移仓换月:
- 代码末尾包含了一个简单的循环,检查当前持仓是否为主力合约。如果持仓合约不再是主力合约(
get_dominant_future返回了新合约),则平掉旧合约的仓位。
- 代码末尾包含了一个简单的循环,检查当前持仓是否为主力合约。如果持仓合约不再是主力合约(
Q&A
Q: 如何修改策略的交易频率(例如改为分钟级)?
A: 将 run_daily(trade_func, time='open') 修改为 run_daily(trade_func, time='every_bar'),并在回测设置中选择“分钟回测”。同时,attribute_history 中的 '1d' 参数可能需要改为 '1m' 或其他分钟周期,并相应调整布林带的参数 g.n。
Q: 为什么使用 order_target 为 0 来平仓?
A: order_target 是一个目标仓位函数。将目标设为 0 是最简单的平仓方式,它会自动计算需要卖出或买入多少手来使持仓归零,避免了手动计算可用仓位的麻烦。
Q: 策略中的 use_real_price 是什么意思?
A: set_option('use_real_price', True) 表示使用真实价格(动态复权)。在期货回测中,这非常重要,因为它能更真实地模拟账户资金变动和保证金占用情况。如果不开启,回测系统可能会使用前复权价格,导致资金计算与实际严重不符。