上手 ②:聚宽侧配置及测试教程(含模拟位开通)
开通模拟位(含实时/延时这个坑)、粘贴接入代码、核对配置项,并用三段式自检加两次行为验证把链路测通。
这一篇解决什么
让聚宽上的策略在下单的那一刻,把「买什么、买多少、什么价」这套意图推给中转站。
聚宽侧只做一件事:说出来。它不关心 QMT 在不在线、也不等结果,推完就走。真正的下单在 QMT 那边(见第 ④ 篇)。
动手前先确认:你已经在中转站建好通道、并复制出了「聚宽接入代码」(第 ③ 篇)。 那段代码里已经写好了你的中转站地址、通道令牌和策略名,本文后面直接叫它接入代码。
第 1 步:开通模拟位(这一步决定你能不能正常联调)
为什么必须用模拟交易
接入代码会判断当前运行环境,只有 模拟交易 和 实盘 才推送信号:
| 运行环境 | 会推信号吗 | 原下单函数 |
|---|---|---|
回测(simple_backtest / full_backtest) | 不推 | 照常执行 |
模拟交易(sim_trade) | 推 | 照常执行 |
实盘(live_trade,如启用) | 推 | 照常执行 |
回测故意不推:回测的时间戳是历史日期,推上去会被当成过期信号,而且会让你的历史回测凭空产生一笔笔无意义的信号记录。这正是我们想要的边界 —— 回测成交、净值、收益率与改造前完全一致。
所以联调要在模拟交易里做。
聚宽有多少个模拟位
聚宽官方口径:
- 每个账号默认 3 个免费永久模拟位。
- 会员用户的永久模拟位是实时运行;普通用户的永久模拟位是延时运行。
- 需要更多,可以用积分兑换或购买限时模拟位;限时模拟位全部是实时运行。
⚠ 这是本链路最容易踩的坑之一。 本工具的策略面板里有一个
[风控] 信号时效(秒),默认 120 秒 —— 信号从生成到执行超过这个时间就会被拒。 如果你用的是普通用户的延时永久模拟位,模拟盘的行情和下单动作都带延迟,信号打到中转站时可能已经超出时效窗口,表现就是工作台里一堆expired。所以:联调阶段请用「实时运行」的模拟位 —— 也就是会员的永久位,或用积分兑换 / 购买的限时模拟位。 如果你只有免费永久位,先把
[风控] 信号时效(秒)临时调大(例如600)来跑通链路,但不要带着这个宽松值上传实盘。
开通步骤
方式 A:从已有策略直接开(推荐)
- 打开你的策略 → 点「回测」跑一次,拿到回测结果。
- 在回测结果界面右上方点 「模拟交易」 按钮。
- 弹出「新建模拟交易」对话框,填写:
| 字段 | 建议值 | 说明 |
|---|---|---|
| 交易名称 | 例如 信号链路联调 | 起个好认的名字,后面工作台信号列表里都靠它区分 |
| 初始资金 | 例如 100000 | 模拟资金,随便设 |
| 选择策略 | 你的策略 | |
| 数据频率 | 每天 | 与策略的 run_daily 对应;用分钟策略就选分钟 |
| 开始日期 | 当天或近几天 | |
| 时限 | 永久 / 限时 | 免费永久位选「永久」 |
- 点「确定」提交。
方式 B:从「我的交易」开
导航栏 → 「我的交易」 → 点 「新建模拟交易」。对话框字段与上面一致,可以选已有的策略或回测。
提交后不会立刻看到结果。 聚宽官方说明:模拟交易创建成功后,需要等待 A 股至少开盘一次才能查看运行结果。所以周末或盘后建的模拟盘,「我的交易」里是空的 —— 这不是没成功。
顺手把微信通知打开
「我的交易」列表里每条模拟交易右侧都有一个微信通知开关。打开 → 扫码绑定 → 之后策略每次买卖都会给你发一条微信。
联调阶段这个很有用:你能一眼看出「聚宽自己有没有在下单」,从而区分是策略没跑起来,还是只是中转站那一段没通。
第 2 步:准备策略
不需要为接入改写策略逻辑。唯一的要求是:
- 策略里要有下单动作(
order/order_value/order_target_value等),否则没什么可推的。 - 下单语句要写在带
context的聚宽回调里(handle_data/run_daily里调用的函数等)。这是聚宽的常规写法,正常写就满足。
接入代码靠从调用栈回溯拿到
context,再读context.run_params.type判断运行环境。 取不到context或判断不出类型时,它会保守跳过推送,同时照常执行你的原下单函数 —— 绝不干预策略。
第 3 步:粘贴接入代码
- 聚宽 → 「我的策略」 → 选中你的策略 → 进入代码编辑。
- 把接入代码整段粘到策略代码的最前面(第 1 行之前)。
┌────────────────────────────────────────────┐
│ # ===== 聚宽跟单接入代码 · BEGIN ===== │ ← 粘贴在这一段(在你自己代码之前)
│ ...(约 400 行,自包含,不需要改) │
│ # ===== 聚宽跟单接入代码 · END ======= │
├────────────────────────────────────────────┤
│ import jqdata │ ← 你原来的代码,一行不动
│ def initialize(context): │
│ ... │
│ def handle_data(context, data): │
│ order_target_value('510300.XSHG', 1) │
└────────────────────────────────────────────┘
- 点「编译」保存。
⚠ 必须是「最前面」,这是硬性要求。 接入代码要在模块加载阶段完成对聚宽下单函数的接管,必须跑在你的策略定义之前。贴在中间或末尾,接管就没装上去。
粘贴时的两个常见事故
| 现象 | 原因 | 处理 |
|---|---|---|
编译报 SyntaxError: future feature annotations is not defined | 编辑器给代码加了额外的头 | 用中转站里复制的原文,不要手工改 |
编译报 invalid character in identifier | 粘贴时带进了中文全角标点(中文引号、中文逗号) | 重新复制一次原文,整段替换 |
第 4 步:核对配置项
接入代码顶部有一个 _JQR 字典,前三项已经按你的通道填好了,其余按需调整。
| 键 | 默认 | 作用 |
|---|---|---|
base_url | 已填 | 中转站地址。不要改 |
token | 已填 | 通道令牌,等同于钥匙。不要改,也不要截图外发 |
strategy | 已填 | 策略名,显示在中转站工作台的信号列表里 |
enabled | True | 改成 False 可临时关闭推送,策略照常运行 |
takeover | False | 改成 True 则「推送后不再下原单」,让 QMT 独占下单 |
debug | False | 改成 True 打开 [jqr] 前缀的日志 |
price_code | counterparty | 报价方式:counterparty 对手价 / last 最新价 / b1~b5 / s1~s5 / limit |
timeout | 5 | 单次推送超时(秒) |
size_on_joinquant | True | 在聚宽侧就把金额折算成股数,两边下的才是同一个数(强烈建议保持 True) |
lot_size | 100 | 一手股数,科创板会自动上调为 200 |
allow_slippage | True | 允许 QMT 在参考价基础上让价成交 |
slippage_ticks | 2 | 允许让价几个最小变动价位(1 档 = 0.01 元) |
关于 size_on_joinquant:为什么默认在聚宽侧折算
order_value('510300.XSHG', 2500) 只表达「大约 2500 元」这个意图。真正买多少股,是聚宽按它自己那一刻的成交价折算出来的。
如果把这个折算留给 QMT 做,QMT 会拿它的价格再算一遍 —— 两边价格只要差一点,整手数就可能差一手;QMT 拿不到价时更是直接归零。
所以接入代码默认在聚宽侧就把股数算死,只把确定的股数发出去。两边下的才是同一个数,这笔账才对得上。
关于 takeover:先用 False
takeover = False(默认)时,聚宽照常下它自己的模拟单,同时把信号推给中转站。这是联调阶段最安全的状态:你能直观看到「聚宽想下什么」,再拿它跟 QMT 的实际动作对照。
等你确认链路上每个环节都对了,再考虑改成 True。
第 5 步:三段式测试(从松到紧,别跳)
打开聚宽左侧的 「研究环境」(Jupyter),依次执行下面三段。
第 1 段:看配置对不对
_jqr_status()
正常输出类似:
[jqr] base_url=https://你的域名 strategy=双均线ETF enabled=True takeover=False 已接管=7 暂存=0
要确认三件事:base_url 是你的中转站、已接管=7(7 个下单/撤单函数都被接管了)、暂存=0(没有积压待补发的信号)。
第 2 段:验网络可达
_jqr_selfcheck()
它会依次打印中转站地址、令牌是否填入、策略名、已接管几个函数,以及中转站 /api/health 的返回。
返回 200 就说明网通、配置都在。 这一步只验「能连通」,不验整条链路。
如果这一步就失败了,先别往下走:检查
base_url是否公网可达、令牌是否为空。 常见原因是公司网络 / 杀毒软件 / 防火墙拦了这台机器上的聚宽出口请求 —— 注意聚宽的代码是在聚宽的云服务器上跑的,不是在你本机跑,所以拦的不会是你的本地防火墙。
第 3 段:验整条链路(三跳)
_jqr_e2e_test()
它会发一条 100 股的探针信号,把整条链路走一整圈,然后逐跳打印:
1 聚宽 → 中转站 [OK] 已入库 23:45:01
2 中转站 → QMT [OK] (+3.1s)
3 QMT 回报 [OK] (+3.2s)
结论:全链路通,三跳全部走通。
断在哪一跳会直说,并告诉你该去查什么。
想换标的或数量:
_jqr_e2e_test("600000.XSHG", 200)
探针不取价、不报单,所以收盘后 / 周末 / 节假日跑都有效,不用等开盘。 但第 2 跳要过,前提是 QMT 那边的策略正在【运行】 —— 否则探针会卡在中转站里「待领取」。所以顺序上,第 ④ 篇的 QMT 侧要先跑起来。
第 6 步:两次「真实」验证(这一步很多人跳过,但它最关键)
上面三段测的都是「链路通不通」。下面两件事测的是「行为对不对」。
验证一:回测不应该推信号
- 回到策略,点「回测」跑一段。
- 去中转站工作台看 「最近策略信号」。
期待结果:没有新增记录。
如果这里出现了记录,说明接入代码没能正确判断运行环境,或者被谁改动了。必须停下来查清楚再往下 —— 带着这个状态上实盘,你会把一堆历史信号打进真实账户。
验证二:模拟盘应该推信号
- 「我的交易」→ 确认模拟交易在运行中。
- 等策略产生一次下单动作。
- 回中转站工作台看「最近策略信号」,应能看到新记录,状态会从 待领取 → 已领取 → 已完成 依次推进。
到这一步,聚宽侧就完成了。
常见问题
Q:日志里没有 [jqr] 前缀的输出? A:先确认 _JQR["debug"] 是不是 True;再确认不是在回测模式 —— 回测下既不打日志也不推送,这是设计如此。
Q:推送失败 HTTP 401? A:通道令牌不对或已被更换。回中转站工作台重新复制一次接入代码,整段替换。
Q:推送失败 HTTP 402? A:权益已到期。去中转站的「积分与钱包 / 权益兑换」续费,续费后通道自动恢复。
Q:信号返回 expired? A:信号生成时间比执行时间早超了 [风控] 信号时效(秒)(默认 120 秒)。两个常见原因:① 上面说的延时模拟位;② 服务器时间不准。先排查这两条。
Q:信号返回 rejected? A:被风控拦了。中转站工作台的「回报 / 原因」列会写明具体是哪条规则。
Q:_jqr_e2e_test() 卡在第 2 跳? A:探针进了中转站但没人领 —— 也就是 QMT 侧的跟单策略没在【运行】。这不是聚宽的问题。
Q:推送成功但 QMT 没动作? A:去查第 ④ 篇。最常见的两个原因:QMT 策略只点了「编译」没点【运行】;或者参数面板里 [测试] 空跑模式 开着。
Q:能同时接多个策略吗? A:能。每个策略对应中转站上的一个通道,各自复制各自的接入代码。不要两个策略共用同一个通道令牌 —— 那会让工作台分不清信号是谁发的(还会触发「N 个客户端」的黄牌告警)。
Q:回测里的成交和净值会变吗? A:不会。接入代码在回测下不推送,回测成交、净值、收益率与改造前完全一致。
Q:聚宽的 Python 版本有什么限制? A:聚宽云端是 Python 3.6。你自己改接入代码时不要引入 3.7+ 特性:from __future__ import annotations、list[int]、X | None、海象运算符 := 都会直接报错。
本篇检查单
- [ ] 已开通实时运行的模拟位(会员永久位 / 限时模拟位 / 积分兑换位)
- [ ] 模拟交易已创建且在「运行中」
- [ ] (可选)微信通知已打开,能收到聚宽自己的成交提醒
- [ ] 接入代码已粘在策略代码最前面,编译通过
- [ ]
_jqr_status()显示已接管=7、暂存=0 - [ ]
_jqr_selfcheck()里/api/health返回 200 - [ ]
_jqr_e2e_test()三跳全[OK] - [ ] 回测跑一遍,工作台「最近策略信号」没有新增记录
- [ ] 模拟盘产生信号,工作台状态推进到「已完成」
- [ ] 重复推送同一条信号只产生一条记录(幂等生效)
下一篇:③ 信号中转站配置及测试教程(如果还没做,建议现在先去补上,因为接入代码要从那里复制)。
