3 分钟快速生成代码
输入想法,AI 即刻生成可运行代码
这是一个基于JoinQuant(聚宽)平台的商品期货布林带(Bollinger Bands)突破策略。
# -*- 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: 将 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) 表示使用真实价格(动态复权)。在期货回测中,这非常重要,因为它能更真实地模拟账户资金变动和保证金占用情况。如果不开启,回测系统可能会使用前复权价格,导致资金计算与实际严重不符。