问题描述
解决方案
很多量化开发者在JoinQuant(聚宽)平台上编写策略时,会发现在9:30开盘前后获取的数据存在差异。这并非系统Bug,而是由A股市场的交易机制(集合竞价)以及平台的底层数据处理逻辑共同决定的。以下是详细的原因解析及正确的处理方法。
一、 为什么9:30前后数据不一样?
1. K线时间标识与对齐机制
在JoinQuant中,所有的行情K线数据为后对齐,标识K线的时间为数据的结束时间。
- 一分钟K线没有09:30:每天的第一根1分钟K线标识为 09:31。这根K线包含了
09:25:00 ~ 09:30:59的所有数据。 - 开盘价的归属:09:31这根K线的开盘价,实际上就是09:25集合竞价产生的开盘价。
如果在9:30整去获取分钟K线,此时09:31的K线尚未走完,你获取到的最新完整K线其实是前一交易日15:00的K线。
2. 集合竞价数据的生成延迟
A股的开盘集合竞价在09:25结束并产生当天的开盘价。但在实际的模拟盘或实盘中,交易所的数据分发和平台的数据处理需要时间。
- 根据官方文档,当天的开盘价(
day_open)至少在09:27分之后才可获取。 - 集合竞价的详细Tick数据(通过
get_call_auction获取),最晚于 09:28分 返回。
如果在09:25到09:27之间去请求当天的开盘价,可能会获取不到或者获取到异常值。
3. 模拟盘的“提前运行”机制
为了减少并发运行模拟盘数量的压力,对于使用当日开盘价撮合的日级模拟盘,JoinQuant会在9:25集合竞价完成时获取开盘价,并将策略提前到 9:27~9:30 之间运行。
- 虽然策略实际运行的物理时间可能是9:28,但策略内获取到的逻辑时间(
context.current_dt)仍然是 9:30。 - 这种物理时间与逻辑时间的错位,会导致你在“以为是9:30”的时候,底层数据切片可能还停留在9:28的状态。
二、 如何正确获取开盘相关数据?
为了避免9:30前后的数据混乱,建议根据具体需求使用专门的API:
1. 获取当天的开盘价、涨跌停价(推荐)
如果你在 handle_data 或 run_daily(..., time='09:30') 中需要知道今天的开盘价,请使用 get_current_data()。这个API专门用于获取当前单位时间已知的数据。
def market_open(context):
# 此时逻辑时间为9:30,实际物理时间可能在9:27-9:30之间
current_data = get_current_data()
security = '000001.XSHE'
# 获取当天开盘价(09:27之后可用)
today_open = current_data[security].day_open
# 获取最新价(09:30之前调用通常返回昨日收盘价或集合竞价价格)
last_price = current_data[security].last_price
# 获取涨跌停价
high_limit = current_data[security].high_limit
log.info(f"今日开盘价: {today_open}, 最新价: {last_price}")
2. 获取集合竞价的详细盘口数据
如果你需要分析集合竞价的买卖盘力量,可以使用 get_call_auction。建议在09:30的函数中调用,以确保数据已经完全就绪。
from jqdata import *
def market_open(context):
# 获取当天的集合竞价数据
today = context.current_dt.strftime('%Y-%m-%d')
df_auction = get_call_auction('000001.XSHE', start_date=today, end_date=today)
if not df_auction.empty:
log.info(f"集合竞价成交价: {df_auction['current'].iloc[-1]}")
log.info(f"集合竞价成交量: {df_auction['volume'].iloc[-1]}")
3. 获取历史K线数据
如果你使用 history 或 attribute_history 获取日线数据,请注意:
- 日线数据不包含当天:即使在15:00收盘后调用,获取的日线数据也不包含当天。要获取当天数据,必须取分钟线(如
frequency='1m')。 - 避免未来函数:如果在9:30获取当天的日线收盘价,属于未来数据。平台开启
avoid_future_data时会报错。
总结
9:30前后数据不一致的核心在于集合竞价的特殊性与K线生成规则。在编写早盘策略时,切忌用常规的分钟K线逻辑去套用9:30这个时间点。明确区分“昨日收盘价”、“今日集合竞价/开盘价”和“9:31第一根分钟线”,并使用 get_current_data() 和 get_call_auction(),就能完美避开这个坑。