期货多品种和定时交易任务
1.1 定时交易任务
(1)、整体策略逻辑
信号逻辑
使用前一根 1 分钟 K 线的方向(收盘价 vs 开盘价)来决定下一分钟是开多还是开空。
若为阳线:buy_open 开多 1 手;若为阴线:sell_open 开空 1 手;十字线不交易。
这是一个非常简单的“跟随上一分钟方向”的动量/追涨杀跌类超短策略,没有过滤任何噪音或趋势确认逻辑,实盘会非常敏感且易受偶然波动干扰。
持仓与平仓逻辑
仅在无任何持仓(多头=0 且 空头=0)时,根据前一分钟 K 线方向开仓。
一旦有持仓后,不再开新的仓位(即始终最多持 1 手单向仓位),这是合理的风控简化。
平仓逻辑:记录开仓时间 open_minute,当持仓时间达到或超过 5 分钟,则按方向全平(多头 sell_close,空头 buy_close)。
这是一个时间止盈/止损的简化版,没有使用价格止损止盈。
合约选择逻辑
SYMBOLS = [“AG2604.SHF”] 当前只有一个标的;
实际上 idx = now.minute % len(SYMBOLS) 是为了在有多个合约时按分钟轮换,目前效果等同于一直交易 AG2604.SHF。
如果未来你扩展多个合约,这个轮换逻辑要注意:
会导致不同分钟交易不同合约,持仓管理更复杂;
目前平仓逻辑中的 symbol 是按当前分钟选择的,可能与开仓时的合约不一致,时间久了容易出错。
(2)、实现细节 Review
1.1.1. 数据获取与时间处理
使用 panda_quant.get_previous_trading_date(datetime.now().strftime(’%Y%m%d’)) 获取上一交易日作为 start_date,end_date 是当前日期:
每分钟都重新取从上一交易日至今的所有 1 分钟数据,数据量会越来越大,性能开销较高。
可以考虑:
仅取当日数据:start_date = end_date = 当前交易日;
或者在框架允许的情况下,使用 data 参数提供的最新 K线,而不是每分钟重新调用行情接口。
排序逻辑:
若存在 “time” 列,则按 [“date”, “time”] 排序,否则按 “date”;
这是合理的,但要确保 future_api_quotation 输出的列名与此兼容。
取前一根 K 线:
prev_bar = quotation_df_1m.iloc[-2] 逻辑正确,可以避免使用正在形成的最新 bar;
但你在调用 future_api_quotation 时,end_date 是当前日期,不控制到具体分钟,可能会包含当前正在形成的未完结 bar,具体要看接口定义,必要时可以只使用到上一分钟的完整数据。
1.1.2. 持仓获取与空值风险
position = context.future_account_dict[account].positions[symbol]
long_quantity = position.buy_quantity
short_quantity = position.sell_quantity
假设:
context.future_account_dict[account].positions 是一个 dict-like 结构;
symbol 不一定总在 positions 中(比如还没交易过就访问)。
风险点:
直接 positions[symbol] 在没有该合约持仓时会抛异常,建议改为:
positions = context.future_account_dict[account].positions
position = positions.get(symbol)
if position is None:
long_quantity = 0
short_quantity = 0
else:
long_quantity = position.buy_quantity
short_quantity = position.sell_quantity
1.1.3. context.trade_state 的使用
通过 context.trade_state = getattr(context, “trade_state”, {}) 来存放每个合约的:
side (“long”/“short”)
open_minute (开仓时刻)
问题与建议:
初始化时机:
在 initialize 中没有显式初始化 context.trade_state,每次在 handle_data 用 getattr 创建,功能上没问题,但可读性稍差;
建议在 initialize 中:
context.trade_state = {}
时间类型:
保存的是 datetime.now(),包含日期+时间;
用 (now - open_minute).total_seconds() / 60.0 来计算分钟差;
但在回测环境中,context.now 可能代表当前仿真时间,使用真实 datetime.now() 会混淆真实时间和仿真时间(尤其是历史回测时)。
更规范的做法:使用平台提供的 context.now,比如:
now = context.now
open_minute = context.now
1.1.4. 时间止盈/止损实现
当前逻辑:
held_minutes = (now - open_minute).total_seconds() / 60.0
if held_minutes >= 5: 即 ≥5 分钟平仓。
逻辑上成立,但注意:
在不同时间频率下(比如改成 5 分钟 bar)要重新审视该条件;
若在某分钟 trade_state 丢失(例如重启策略、状态未持久化),持仓就无法按时间平仓,只能依赖人工/其他逻辑。
1.1.5. 日志打印与调试信息
SRLogger.info 使用得比较充分,对调试有帮助;
但 print(‘时间’, now) 在实盘环境建议也改为统一使用日志:
SRLogger.info(f"当前时间: {now}")
1.2 期货多品种
- 交易逻辑本身
1.1 策略核心是否合理
对每个品种:
取最近一段 1 分钟 K 数据
计算 10 根均线
当前价 > MA10 :做多;当前价 < MA10:做空
有持仓时,反向信号就平仓
逻辑上是一个最简单的均线单信号反转策略,结构清晰:
无仓 → 开仓(顺势开)
有多 → 信号转空 → 平多
有空 → 信号转多 → 平空
1.2 可能的交易行为问题
多空对冲/反手问题未处理
当前代码只“平仓”不“反手”:
有多仓,价格从上穿 MA10 → 下破 MA10:只卖出平多 DEFAULT_VOLUME 手,而不会开空。若 buy_quantity > DEFAULT_VOLUME,会分多次平掉。
类似地,有空仓时只逐步买入平空,不会同时开多。
这是否符合你的设计意图:
若想做反手策略(多转空 / 空转多),需要在平完仓(或在一个信号中同时平旧开新)之后再开反向仓。
信号时间与下单时间不一致
用 future_api_quotation(start_date=上一交易日, end_date=今天, period=‘1m’) 取一整段历史,然后用最后一根 K的 close 作为信号。
在实盘中,这根“最后一根 K”可能是几分钟前的数据(取决于 handle_data 调用时机),与当前交易时间有偏差。
更严谨做法:
要么只用“上一分钟收盘”的 K(且确认已经收盘)
要么在回测里使用 context.current_dt / 引擎提供的 bar 时间,而不是 datetime.now()。
信号过于频繁
每次 handle_data 都重新拉一段 1 分钟数据、算一遍 MA10,并立即决策,下一个 tick/bundle 再重复。
在 1 分钟级别上,这意味着每分钟都可能重复判断并下单,容易造成过度交易。
更好的方法是:
只在“新 1 分钟 bar 生成完成”时触发逻辑;
或在引擎提供类似 handle_bar、on_bar 时,按 bar 驱动执行。