我想问一下关于回测中的现价。
我现在写的代码中是每天早上9点15的时候选出信号,信号是选出2个ETF中从本周期(因为是9点15选信号,实际上取的是按昨天的收盘价计算)至前20周期内的涨幅高的那个在9点30的时候买入。
# 开盘前分析买卖信号
run_daily(before_market_open, time='9:15')
# 开盘后进行交易
run_daily(market_open, time='9:30')
# 收盘后运行记录当天收益
run_daily(after_market_close, time='after_close')
我现在想信号最后收盘价是按当天每分钟的收盘价运行一次,然后买卖的判断也能是每分钟判断一次呢?要怎么做,求教
问题描述
解决方案
要将您的策略从“每天判断一次”改造为“每分钟判断一次”,在 JoinQuant 平台上非常容易实现。核心思路是将回测频率设置为“分钟”,并使用每分钟都会触发的函数来执行您的逻辑。
以下是具体的改造步骤和代码示例:
核心改造步骤
- 修改回测频率:在 JoinQuant 的回测界面右上角,将“运行频率”从“天”修改为“分钟”。
- 调整定时运行函数:将
run_daily(..., time='9:15')替换为run_daily(..., time='every_bar'),或者直接使用handle_data(context, data)函数。这两个函数在分钟频率下,都会在交易时间的每一分钟被调用一次。 - 获取分钟级数据:使用
history或attribute_history时,将时间单位参数从'1d'(天)改为'1m'(分钟)。
完整代码示例
假设您要比较的两个 ETF 是沪深300ETF(510300.XSHG)和中证500ETF(510500.XSHG),比较它们过去20分钟的涨幅,并在每分钟进行调仓:
# 导入聚宽函数库
import jqdata
def initialize(context):
# 设定沪深300作为基准
set_benchmark('000300.XSHG')
# 开启动态复权模式(真实价格)
set_option('use_real_price', True)
# 定义要操作的两个ETF
g.etf1 = '510300.XSHG'
g.etf2 = '510500.XSHG'
# 核心修改:设置每分钟运行一次交易函数
run_daily(market_trade, time='every_bar')
# 每分钟都会被调用一次的函数
def market_trade(context):
# 获取过去20分钟的收盘价数据
# 注意:'1m'代表分钟线,获取的数据不包含当前正在运行的这一分钟(避免未来函数)
hist1 = attribute_history(g.etf1, 20, '1m', ['close'])
hist2 = attribute_history(g.etf2, 20, '1m', ['close'])
# 确保有足够的数据(例如刚上市的标的可能数据不足)
if len(hist1) < 20 or len(hist2) < 20:
return
# 计算过去20分钟的涨幅
# 涨幅 = (最新一分钟的收盘价 - 20分钟前的收盘价) / 20分钟前的收盘价
ret1 = (hist1['close'][-1] - hist1['close'][0]) / hist1['close'][0]
ret2 = (hist2['close'][-1] - hist2['close'][0]) / hist2['close'][0]
# 获取当前账户持仓
positions = context.portfolio.positions
# 比较涨幅并进行交易判断
if ret1 > ret2:
# 如果 ETF1 涨幅大,且当前持有 ETF2,则卖出 ETF2
if g.etf2 in positions and positions[g.etf2].closeable_amount > 0:
order_target(g.etf2, 0)
log.info("卖出 %s" % g.etf2)
# 全仓买入 ETF1
if g.etf1 not in positions or positions[g.etf1].closeable_amount == 0:
order_target_value(g.etf1, context.portfolio.total_value)
log.info("买入 %s" % g.etf1)
elif ret2 > ret1:
# 如果 ETF2 涨幅大,且当前持有 ETF1,则卖出 ETF1
if g.etf1 in positions and positions[g.etf1].closeable_amount > 0:
order_target(g.etf1, 0)
log.info("卖出 %s" % g.etf1)
# 全仓买入 ETF2
if g.etf2 not in positions or positions[g.etf2].closeable_amount == 0:
order_target_value(g.etf2, context.portfolio.total_value)
log.info("买入 %s" % g.etf2)
注意事项与细节解析
time='every_bar'的含义:在run_daily中使用every_bar,意味着该函数会与您设置的回测频率保持一致。如果您在界面上选择了“按分钟回测”,它就会在每天的 9:30, 9:31... 一直到 15:00 每分钟执行一次。- 数据的获取:
attribute_history(security, 20, '1m', ['close'])获取的是前20个已经走完的分钟K线。例如,在 10:05 分执行时,获取的是 09:45 到 10:04 这20根分钟K线的收盘价。这完全符合实盘逻辑,不会引入未来函数。 - 交易限制:A股和场内基金实行 T+1 交易制度。如果您在同一天内买入了某个 ETF,当天是无法卖出的。代码中的
closeable_amount > 0判断就是为了确保只卖出可用的(非今天买入的)持仓。 - 性能考虑:分钟级回测每天会执行 240 次逻辑判断,相比日线策略耗时会显著增加。建议在测试阶段先选择较短的时间区间(如一个月)进行验证。