大家好,我是花姐 👩💻。 最近有粉丝在用 xtquant+miniQMT 做实盘的时候,问我一个很典型的问题:
"我用
subscribe_quote
订阅分钟级行情,通过callback
来拿数据,这种实盘场景里,有哪些坑?比如未来函数、最新一分钟K线到底怎么处理?"
今天我们就来聊聊这个话题。
对 xtquant 不太熟悉的朋友,我这里先简单普及一下。
在 xtquant 里,我们可以通过行情接口来订阅单只股票的行情,比如:
python
subscribe_quote(stock_code, period='1d', start_time='', end_time='', count=0, callback='cbf')
其中:
- stock_code:股票代码
- period :周期,比如
'1d'
表示日线,'1m'
就是分钟线 - start_time / end_time:请求历史数据的时间范围
- count :历史数据的条数,一般实盘订阅时传
0
就行,表示只要实时推送数据 - callback:回调函数,用来接收行情数据
订阅成功后,行情数据会通过回调函数推送回来,格式和你订阅的周期一致。数据会先进入缓存,保证连续性,然后再传到你的回调里。
比如一个最简单的回调:
python
def on_data(datas):
for stock_code in datas:
print(stock_code, datas[stock_code])
这里 datas
的格式是:
python
{ stock_code : [data1, data2, ...] }
也就是说,它是一个以股票代码为 key、以对应行情数据列表为 value 的字典。你在回调里就可以直接拿到每只股票对应的行情,进一步做处理。
举个例子:
python
from xtquant import xtdata
def cbf(datas):
for stock_code in datas:
print(datas[stock_code])
xtdata.subscribe_quote2('300124.SZ',period='1m',dividend_type='none',count=2,callback=cbf)
xtdata.run()
运行以后每隔3s就会收到一次数据,可以获取最新1分钟K线的行情
css
[{'time': 1757050080000, 'open': 72.2, 'high': 72.3, 'low': 72.2, 'close': 72.3, 'volume': 1535, 'amount': 11089523.0, 'settlementPrice': 0.0, 'openInterest': 13, 'dr': 1.0, 'totaldr': 24.93064385174295, 'preClose': 6.9528939467091e-310, 'suspendFlag': -802392741}]
[{'time': 1757050080000, 'open': 72.2, 'high': 72.31, 'low': 72.2, 'close': 72.31, 'volume': 1554, 'amount': 11226909.0, 'settlementPrice': 0.0, 'openInterest': 13, 'dr': 1.0, 'totaldr': 24.93064385174295, 'preClose': 6.9528939467091e-310, 'suspendFlag': -802392741}]
[{'time': 1757050080000, 'open': 72.2, 'high': 72.31, 'low': 72.2, 'close': 72.31, 'volume': 1613, 'amount': 11653538.0, 'settlementPrice': 0.0, 'openInterest': 13, 'dr': 1.0, 'totaldr': 24.93064385174295, 'preClose': 6.9528939467091e-310, 'suspendFlag': -802392741}]
[{'time': 1757050080000, 'open': 72.2, 'high': 72.31, 'low': 72.2, 'close': 72.31, 'volume': 1621, 'amount': 11711389.0, 'settlementPrice': 0.0, 'openInterest': 13, 'dr': 1.0, 'totaldr': 24.93064385174295, 'preClose': 6.9528939467091e-310, 'suspendFlag': -802392741}]
[{'time': 1757050140000, 'open': 72.31, 'high': 72.31, 'low': 72.31, 'close': 72.31, 'volume': 15, 'amount': 108467.0, 'settlementPrice': 0.0, 'openInterest': 13, 'dr': 1.0, 'totaldr': 24.93064385174295, 'preClose': 6.9528939467091e-310, 'suspendFlag': -802392741}]
[{'time': 1757050140000, 'open': 72.31, 'high': 72.31, 'low': 72.31, 'close': 72.31, 'volume': 52, 'amount': 376042.0, 'settlementPrice': 0.0, 'openInterest': 13, 'dr': 1.0, 'totaldr': 24.93064385174295, 'preClose': 6.9528939467091e-310, 'suspendFlag': -802392741}]
通过稍加改造,我们就能把回调推送的数据转换成 DataFrame 格式,然后将它与历史行情拼接起来,这样就可以实时获取带有最新 1 分钟 K 线的完整数据集。
python
from xtquant import xtdata
def cbf(datas):
for stock_code in datas:
kline_in_callabck = xtdata.get_market_data_ex([],[stock_code],period = '1m',count=10) # 在回调中获取klines数据
df_kline = kline_in_callabck[stock_code]
print(df_kline)
xtdata.subscribe_quote2('300124.SZ',period='1m',dividend_type='none',count=2,callback=cbf)
xtdata.run()
print(df_kline)
的打印结果如下:
lua
time open high low close volume amount settelementPrice openInterest preClose suspendFlag
20250905132400 1757049840000 71.42 71.42 71.42 71.42 0 0.0 0.0 0 71.42 1
20250905132500 1757049900000 72.11 72.20 72.11 72.17 1063 7670608.0 0.0 13 72.10 0
20250905132600 1757049960000 72.19 72.20 72.16 72.17 353 2547925.0 0.0 13 72.17 0
20250905132700 1757050020000 72.17 72.20 72.15 72.20 852 6148857.0 0.0 13 72.17 0
20250905132800 1757050080000 72.20 72.31 72.20 72.31 1621 11711389.0 0.0 13 72.20 0
20250905132900 1757050140000 72.31 72.31 72.31 72.31 52 376042.0 0.0 13 72.31 0
20250905133000 1757050200000 72.31 72.31 72.31 72.31 0 0.0 0.0 0 72.31 1
20250905133100 1757050260000 72.31 72.31 72.31 72.31 0 0.0 0.0 0 72.31 1
20250905133200 1757050320000 72.29 72.31 72.24 72.24 887 6410071.0 0.0 13 72.30 0
20250905133300 1757050380000 72.25 72.29 72.19 72.28 538 3887385.0 0.0 13 72.24 0
不知道大家有没有发现通过
datas[stock_code]
获取到的最新行情数据preClose
居然是错误的,实盘的时候大家得注意。
接下来书归正传,我们来说说实盘时候会遇到的坑。
1. 未来函数的陷阱
这是实盘里最常见的坑。很多人没意识到,实盘和回测在数据上有本质差异:
- 回测时:分钟K线是收盘价、最高、最低、成交量全都定好的。
- 实盘时:当下正在走的那根K线是"未完成的",数据会不断变化。
如果你在实盘中把 "当前分钟K线" (Dataframe 格式数据最后一行或者datas[stock_code]
里的数据)直接当成完整数据来算信号,那就相当于用了"未来函数"。
解决方案 ✅
- 在实盘信号里,只用"上一根完整K线"的数据。
- 当前分钟的数据可以用来做预警或辅助判断,但不能当做回测时的收盘价来下单。
- 等分钟结束后再触发交易逻辑(例如,9:31这一分钟的完整数据要等到9:32:00才拿来用)。
- 等当前K线返回的时间和系统时间差小于等于5s的时候再出发交易逻辑(认为这个最新的K线走完了,实际上还差3s才算走完一个完整的K线,在换手率比较低的情况下3s时间K线变化不会太大)
这里我把 "当前分钟K线" 和 实际的系统时间都打印出来了方便大家理解上面的这个问题。

在数据时间: 2025-09-05 21:50:00
,实际时间: 2025-09-05 13:49:47
的时候,行情数据已经到50分了,但实际时间是49分47秒,这就表示50分的这个K线还没走完,我们可以等到实际时间: 2025-09-05 13:49:59
或者实际时间: 2025-09-05 13:49:57
的时候触发K线走完的逻辑,或者等到实际时间: 2025-09-05 13:50:03
的时候触发K线走完的逻辑。
前者是等最新K线马上走完的时候处理,后者是等最新K线走完以后立马处理,具体情况可以根据业务实际需求来选择。
2. 最新一分钟K线的处理
实盘中,callback 会不停推送分钟行情更新,但这一分钟没结束前,它的数据是不完整的。 比如:
- 9:31 这一分钟内,价格可能会来回波动,最高、最低、成交量都还在变化。
- 如果你一股脑用它来算策略,很可能"信号乱跳"。
解决方案 ✅
-
给当前分钟的数据加一个"锁",只用来显示/记录,不参与交易逻辑。
-
在 callback 中识别 "分钟结束" 的标志:
- 例如,发现分钟时间戳发生变化(从 9:31 变到 9:32),说明上一分钟K线已经固定,可以拿来跑策略。
-
如果想做超短线,可以把"未完成K线"的 tick 数据和盘口结合使用,但这就属于高频逻辑了,要额外小心。
3. 回调函数里的常见问题
很多人实盘写回调时,容易掉坑:
- 阻塞问题:如果在 callback 里直接跑策略逻辑,容易卡住数据接收,导致延迟甚至丢包。
- 多线程问题:多个订阅并发回调时,如果没加锁,容易出现数据错乱。
解决方案 ✅
- 在 callback 里只做数据接收和存储,最好写到一个队列里。
- 策略逻辑单独开线程/进程去消费队列,保证行情接收不受影响。
- 如果需要持久化,建议写入内存数据库(Redis)或者本地缓存,再由策略读取。
4. 数据同步与延迟
实盘最怕延迟,尤其是分钟级别的策略:
- 如果你只盯分钟线,延迟一两秒问题不大。
- 但如果你用盘口数据或秒级逻辑,延迟就可能直接吃亏。
解决方案 ✅
- 确保订阅源和网络稳定。
- 订阅时尽量减少不必要的合约/股票数量,降低推送压力。
- 可以做一个延迟检测机制:比如 tick 时间戳和本地系统时间比对,如果延迟过大,触发告警。
5. 和回测对齐的问题
很多朋友在实盘时发现策略表现和回测差距很大,大部分原因其实就是 K线定义差异。
- 回测时,你用的可能是"已收盘"的分钟线。
- 实盘时,你在用"未完成"的分钟线。
这两个逻辑对齐不好,结果自然天差地别。
解决方案 ✅
- 回测时可以模拟"分钟内逐笔更新",避免过度理想化。
- 实盘一定要在代码层面保证:和回测保持同样的触发条件。
- 最好写一个 统一的K线管理模块,无论回测还是实盘,都走同一套逻辑。
6. 交易撮合和下单延迟
行情没问题,但下单时也有坑:
- 你看到的是9:32的信号,但下单时可能已经9:32:01,价格动了。
- 如果策略是"收盘价买入",实盘永远不可能买在收盘价。
解决方案 ✅
- 在回测时加入滑点模型,模拟延迟和成交价偏差。
- 实盘时使用限价单保护,不要盲目追单。
- 最好在策略里加容错逻辑,例如信号触发后1~2个价位的波动仍然有效。
总结
实盘不是回测的"复制粘贴",而是更复杂的动态过程。尤其是分钟级别的策略,几个关键点一定要注意:
- 避免未来函数:只用上一根完整K线。
- 最新K线要谨慎:未完成数据只能参考,不能直接下单。
- 回调逻辑要解耦:接收与处理分离,避免阻塞。
- 对齐回测逻辑:统一K线生成方式,保持一致性。
- 下单考虑滑点:实盘和回测要有合理差距模型。
做好这些,才能让策略在实盘中更稳定、更接近回测效果 🎯。
花姐我个人的建议是: 👉 如果你刚开始实盘,先跑一个 监控模式(不下单,只记录信号和行情),跑一两周,把逻辑和回测完全对齐后,再考虑上线资金,这样风险会小很多。