加一个监测脚本

This commit is contained in:
zlt 2026-08-03 10:28:09 +08:00
parent d376a55fbd
commit deecd40ffc
6 changed files with 151 additions and 20 deletions

View File

@ -126,9 +126,13 @@ rebuild-accept: ## 只看判收: 安全垫分布 / 批次账 / 行业集中度
# 实现 B = PMS 自己算 (分日配额 / 分笔 / VWAP / 回踩 / 不追高 / 14:45 兜底) —— 已实现 # 实现 B = PMS 自己算 (分日配额 / 分笔 / VWAP / 回踩 / 不追高 / 14:45 兜底) —— 已实现
# 所以跑 A 段不需要动决策系统的任何逻辑, 它这一段本来就不参与。 # 所以跑 A 段不需要动决策系统的任何逻辑, 它这一段本来就不参与。
A ?= http://127.0.0.1:38100 A ?= http://127.0.0.1:38100
# --no-ensure-ascii 是必须的: 不加的话中文全被转义成 \uXXXX, 每条 reason 都得自己解码, # 中文必须原样出来: `json.tool` 默认把中文转义成 \uXXXX, 而这些接口的 reason 恰恰是最该
# 而这些接口的 reason 恰恰是最该看的东西 (Python 3.9+) # 看的东西。**不能用 `json.tool --no-ensure-ascii`** —— 这条管道跑在**宿主机**上不是容器里,
J := python3 -m json.tool --no-ensure-ascii # 宿主机的 python3 可能低于 3.9 (2026-08-03 实机: unrecognized arguments)。下面这行任何
# python3 都认。解析失败时原样透传, 免得把错误信息也一起吞了。
J := python3 -c 'import sys,json;\
d=sys.stdin.read();\
sys.stdout.write(json.dumps(json.loads(d),ensure_ascii=False,indent=2)+"\n" if d.strip().startswith(("{","[")) else d)'
# t-pool 与 t-plans 是**两个完全不同的东西**, 别被名字带偏 (原名 t-plan/t-plans 只差一个 s, # t-pool 与 t-plans 是**两个完全不同的东西**, 别被名字带偏 (原名 t-plan/t-plans 只差一个 s,
# 实测把人绕进去过): # 实测把人绕进去过):

View File

@ -67,7 +67,7 @@ scripts/
test_batch7_units.py 上游选股计划: 解析/新鲜度/候选筛选/取数守卫 32 例 test_batch7_units.py 上游选股计划: 解析/新鲜度/候选筛选/取数守卫 32 例
test_batch8_units.py 榜单变化: 名册指纹/三种语义/尾部闸/落库往返 60 例 test_batch8_units.py 榜单变化: 名册指纹/三种语义/尾部闸/落库往返 60 例
test_batch9_units.py 成本价体检 / 对账按日推进 / 行业闸 / 取整记账 49 例 test_batch9_units.py 成本价体检 / 对账按日推进 / 行业闸 / 取整记账 49 例
test_batch10_units.py 静默失败专项: 关键路径不许丢返回值 + 八条实例 40 test_batch10_units.py 静默失败专项: 关键路径不许丢返回值 + 八条实例 46
test_wiring.py 装配自检: 服务层→核心→落表 全链路 (内存桩) 58 例 test_wiring.py 装配自检: 服务层→核心→落表 全链路 (内存桩) 58 例
init_db.py 建表 (应用 ddl_pms_v1.sql, 幂等, 默认演练; 含 DDL 体检) init_db.py 建表 (应用 ddl_pms_v1.sql, 幂等, 默认演练; 含 DDL 体检)
check_db.py 实机连通性与表结构自检 (需真实 .env) check_db.py 实机连通性与表结构自检 (需真实 .env)
@ -304,7 +304,7 @@ make t-gate # 随时: 规则闸/研判闸拒了什么、为什么
## 已实现 / 待开发 ## 已实现 / 待开发
**已实现**:建表 DDL 与建表脚本配置与运行参数中心仓位规划器与安全垫账命令系统27 类命令全目录 + 双状态机 + 冲突识别);方案生成器(降仓凑额四档、升仓、建仓分批、清仓/减至、行业清仓与限额、暂停买入撤单);账本回放与对账引擎(成交认领、外部成交并入 BASE 告警、以下游为准修正、除权检测、T+1 可用量、连续不一致升级);规则闸终检;择时执行器实现 B分日配额、分笔、VWAP/回踩/不追高、14:45 兜底、停牌一字板顺延、窗口耗尽收口、挂单有效期);动作引擎四类自主动作 + 研判闸客户端 + 提议分流;决策系统信号消化(两条流独立消费组订阅、置信度分档转清仓指令或提议);管理页面四块 + 运维/日报抽屉;调度器九个调度位;**上游选股计划接口接入**`/plan` 取候选池、交易日龄硬校验、`theme` 灌行业映射表、页面预览抽屉与不可用横幅);**榜单变化提示**(名册快照 + 新进/掉榜/档位升降/覆盖翻转/名次跳变,持仓票单列,榜尾截断噪音闸);**ws 直连通道的连接层**(常驻进程 + 出口队列 + 签名 + seq 水位与累积确认,见下);**单测 393 例**。 **已实现**:建表 DDL 与建表脚本配置与运行参数中心仓位规划器与安全垫账命令系统27 类命令全目录 + 双状态机 + 冲突识别);方案生成器(降仓凑额四档、升仓、建仓分批、清仓/减至、行业清仓与限额、暂停买入撤单);账本回放与对账引擎(成交认领、外部成交并入 BASE 告警、以下游为准修正、除权检测、T+1 可用量、连续不一致升级);规则闸终检;择时执行器实现 B分日配额、分笔、VWAP/回踩/不追高、14:45 兜底、停牌一字板顺延、窗口耗尽收口、挂单有效期);动作引擎四类自主动作 + 研判闸客户端 + 提议分流;决策系统信号消化(两条流独立消费组订阅、置信度分档转清仓指令或提议);管理页面四块 + 运维/日报抽屉;调度器九个调度位;**上游选股计划接口接入**`/plan` 取候选池、交易日龄硬校验、`theme` 灌行业映射表、页面预览抽屉与不可用横幅);**榜单变化提示**(名册快照 + 新进/掉榜/档位升降/覆盖翻转/名次跳变,持仓票单列,榜尾截断噪音闸);**ws 直连通道的连接层**(常驻进程 + 出口队列 + 签名 + seq 水位与累积确认,见下);**单测 399 例**。
### 静默失败专项2026-07-31八条已修 ### 静默失败专项2026-07-31八条已修
@ -346,6 +346,10 @@ make t-gate # 随时: 规则闸/研判闸拒了什么、为什么
`make rebuild` 在「下游一只持仓都没有」时印 `[FAIL]` 并退出码 2make 报 `Error 2`——可账户空 + 账本空 = 两边一致,**这是终态不是故障**,压根不需要重建这一步。预检现在给出 `verdict``READY` / `EMPTY` / `NOT_READY` / `NO_SOURCE``EMPTY` 退 0 并明说「没有需要接管的持仓,重建这一步跳过」。只有一种 `EMPTY` 仍然要拦:**账户空而账本非空**,那是顺序反了,得先 `reset_ledger` 清账本,别让对账拿空集去核销已有持仓。 `make rebuild` 在「下游一只持仓都没有」时印 `[FAIL]` 并退出码 2make 报 `Error 2`——可账户空 + 账本空 = 两边一致,**这是终态不是故障**,压根不需要重建这一步。预检现在给出 `verdict``READY` / `EMPTY` / `NOT_READY` / `NO_SOURCE``EMPTY` 退 0 并明说「没有需要接管的持仓,重建这一步跳过」。只有一种 `EMPTY` 仍然要拦:**账户空而账本非空**,那是顺序反了,得先 `reset_ledger` 清账本,别让对账拿空集去核销已有持仓。
**第四种变体:测试桩比真依赖宽松,于是单测替真依赖打掩护。** 2026-08-03 切 ws 当天撞到:`executor.run_tick` 给 `pms_repo.update_instruction` 多传了一个 `limit_price=None`,真 repo 没这个形参 → `TypeError` → 被外层 `except Exception` 吞成 `out["errors"]` 里的一条。后果是**单子已经发到 QMT 了,而本端一个字没记**——父指令停在 `PROPOSED`、`children` 空、`dispatch_ref` 空,当日配额恒按 0 算,下一跳会拿同一个子单号 `_D01` 再发一次(出口表唯一索引拦住之后就彻底卡死)。
单测全程没看见,因为 `FakeRepo.update_instruction` 写的是 `def update_instruction(self, iid, **kw)`——什么关键字都收。**桩比真依赖宽松,等于单测在替真依赖打掩护。** `test_batch10_units.py`**[M] 组**守这一条:从**源码**AST`pms_repo` 的真实签名,逐个比对 `FakeRepo` 的同名方法,桩收 `**kw` 而真 repo 是固定签名就判失败。注意必须从源码读——`install_fakes` 已经把 `pms_repo` 的函数替换成桩的绑定方法了,`getattr(pms_repo, name)` 拿到的是桩自己,那样这条守卫会永远绿(第一版就是这么写错的,守卫本身也会静默失效)。`make watch` 里另有一段常驻交叉检查:出口表有委托而父指令还停在 `PROPOSED`、或 `children` 条数跟出口表对不上,直接顶在屏幕上。
**第三种变体最阴,是「没跑长得像通过」。** 源码是打进镜像的,`docker compose run pms-web python scripts/run_tests.py` 跑的是**镜像里那份**。于是有这么一条路径: **第三种变体最阴,是「没跑长得像通过」。** 源码是打进镜像的,`docker compose run pms-web python scripts/run_tests.py` 跑的是**镜像里那份**。于是有这么一条路径:
``` ```

View File

@ -236,9 +236,23 @@ def run_tick(*, now=None, dry_run: bool = False) -> dict:
prog.setdefault("dispatched_at", now.strftime("%Y-%m-%d %H:%M:%S")) prog.setdefault("dispatched_at", now.strftime("%Y-%m-%d %H:%M:%S"))
prog["last_decision"] = {"at": now.strftime("%H:%M"), "action": et.ACT_FIRE, prog["last_decision"] = {"at": now.strftime("%H:%M"), "action": et.ACT_FIRE,
"reason": d["reason"]} "reason": d["reason"]}
pms_repo.update_instruction(ins["instruction_id"], status=ST_DISPATCHED, # 这里曾经多传了一个 limit_price=None (2026-08-03 实机): 真 repo 的
limit_price=None, dispatch_ref=res["ref"], # update_instruction 没有这个形参 → TypeError → 被本函数外层的
progress=prog) # `except Exception` 吞成 out["errors"] 里的一条。后果是**单子已经发到 QMT
# 了, 而本端一个字没记**: 父指令停在 PROPOSED、children 空、dispatch_ref 空,
# 于是当日配额恒按 0 算, 下一跳会拿同一个子单号 _D01 再发一次 —— 出口表唯一
# 索引拦住之后就彻底卡死。单测全程没看见, 因为 FakeRepo.update_instruction
# 是 `**kw` 全收, 比真依赖宽松 (见 test_batch10 的 [M] 组)。
r = pms_repo.update_instruction(ins["instruction_id"], status=ST_DISPATCHED,
dispatch_ref=res["ref"], progress=prog)
if not r:
# 单子已经出去了, 这里回滚不了; 但绝不能让它悄悄过去
logger.error("[出手] %s 已下发到通道, 但父指令没更新到任何行 —— "
"本端与出口表将不一致, 请核对 pms_instruction",
ins["instruction_id"])
out["errors"].append(
f"{ins['instruction_id']} 已下发但父指令未更新 (影响 0 行) —— "
f"出口表与本端不一致")
pms_repo.insert_ledger(ts_code=code, action=ins.get("action"), arbiter="rule", pms_repo.insert_ledger(ts_code=code, action=ins.get("action"), arbiter="rule",
verdict="PASS", price_at=day_ctx.get("price") or 0, verdict="PASS", price_at=day_ctx.get("price") or 0,
hard_numbers={**gate["hard_numbers"], "limit": d["limit_price"], hard_numbers={**gate["hard_numbers"], "limit": d["limit_price"],

View File

@ -14,9 +14,9 @@
test_batch7_units.py 上游选股计划: 解析/新鲜度/候选筛选/取数守卫 (32 ) test_batch7_units.py 上游选股计划: 解析/新鲜度/候选筛选/取数守卫 (32 )
test_batch8_units.py 榜单变化: 名册指纹/三种语义/尾部闸/落库往返 (60 ) test_batch8_units.py 榜单变化: 名册指纹/三种语义/尾部闸/落库往返 (60 )
test_batch9_units.py 成本价体检 / 对账按日推进 / 行业闸 / 取整记账 (49 ) test_batch9_units.py 成本价体检 / 对账按日推进 / 行业闸 / 取整记账 (49 )
test_batch10_units.py 静默失败专项: 关键路径不许丢返回值 + 八条实例 (40 ) test_batch10_units.py 静默失败专项: 关键路径不许丢返回值 + 八条实例 (46 )
test_wiring.py 装配自检: 服务层核心落表 全链路 (内存桩) (58 ) test_wiring.py 装配自检: 服务层核心落表 全链路 (内存桩) (58 )
393 399
任一子集失败即整体失败 (退出码 1) 任一子集失败即整体失败 (退出码 1)
""" """
import os import os

View File

@ -830,6 +830,86 @@ def _():
assert "code_fingerprint" in src, "run_tests 要打印代码指纹, 好识别跑的是哪份代码" assert "code_fingerprint" in src, "run_tests 要打印代码指纹, 好识别跑的是哪份代码"
# ================================================================
# [M] 测试桩不许比真依赖宽松
# ================================================================
@case("[M1] FakeRepo 的方法签名不许比 pms_repo 宽松 (`**kw` 全收会替真依赖打掩护)")
def _():
import ast
import inspect
from test_wiring import FakeRepo
# 2026-08-03 实机: executor 给 update_instruction 多传了一个 limit_price=None,
# 真 repo 没这个形参 → TypeError → 被外层 except 吞掉 → **单子已发到 QMT 而本端
# 一个字没记**。单测全程没看见, 因为桩是 `def update_instruction(self, iid, **kw)`,
# 什么关键字都收。桩比真依赖宽松, 等于单测在替真依赖打掩护 —— 这一条守住它。
#
# 真 repo 的签名**必须从源码读**, 不能 getattr(pms_repo, name): install_fakes 已经
# 把 pms_repo 的函数替换成桩的绑定方法了, 那样等于拿桩跟自己比, 这条守卫会永远绿。
# (第一版就是这么写错的 —— 守卫本身也会静默失效。)
real = {}
path = os.path.join(ROOT, "app", "repo", "pms_repo.py")
with open(path, encoding="utf-8") as f:
for node in ast.parse(f.read(), path).body:
if isinstance(node, ast.FunctionDef) and not node.name.startswith("_"):
a = node.args
real[node.name] = {
"kw": [x.arg for x in (a.args + a.kwonlyargs)],
"var_kw": a.kwarg is not None}
assert "update_instruction" in real, "没能从源码解析出 pms_repo 的签名"
bad = []
for name, r in sorted(real.items()):
fake = getattr(FakeRepo, name, None)
if not callable(fake):
continue
fp = inspect.signature(fake).parameters
fake_var_kw = any(p.kind is inspect.Parameter.VAR_KEYWORD for p in fp.values())
if fake_var_kw and not r["var_kw"]:
bad.append(f"{name}: 桩收 **kw 而真 repo 是固定签名 "
f"({', '.join(r['kw'])}) —— 调用方多传一个参数, "
f"真机抛 TypeError 而单测照样绿")
assert not bad, ("测试桩比真 repo 宽松, 单测会盖住真机才暴露的错:\n "
+ "\n ".join(bad))
@case("[M2] 扫描器本身有效: 造一个宽松桩, 必须被 M1 那套判据抓出来")
def _():
import inspect
class _Real:
def f(self, a, *, b=None):
return 1
class _Fake:
def f(self, a, **kw):
return 1
rp = inspect.signature(_Real.f).parameters
fp = inspect.signature(_Fake.f).parameters
assert any(p.kind is inspect.Parameter.VAR_KEYWORD for p in fp.values())
assert not any(p.kind is inspect.Parameter.VAR_KEYWORD for p in rp.values())
@case("[M3] 下发成功但父指令没更新到任何行 → 必须冒进 errors, 不许静默")
def _():
import ast
path = os.path.join(ROOT, "app", "services", "executor.py")
with open(path, encoding="utf-8") as f:
src = f.read()
# 只看真实调用的实参, 不看注释 —— 注释里提这个词是在解释这个坑, 不该被自己抓
bad = []
for node in ast.walk(ast.parse(src, path)):
if not isinstance(node, ast.Call):
continue
fn = node.func
if isinstance(fn, ast.Attribute) and fn.attr == "update_instruction":
names = {k.arg for k in node.keywords if k.arg}
extra = names - {"status", "exec_qty", "exec_avg_price", "dispatch_ref",
"progress"}
if extra:
bad.append(f"{node.lineno} 行传了 pms_repo 不认的参数 {sorted(extra)}")
assert not bad, "update_instruction 调用点参数不合真 repo 签名:\n " + "\n ".join(bad)
assert "已下发但父指令未更新" in src, "下发成功但更新影响 0 行时没有留出口"
# ================================================================ # ================================================================
def main(): def main():
ok = fail = 0 ok = fail = 0

View File

@ -52,7 +52,11 @@ class FakeRepo:
def next_command_seq(self, ymd): def next_command_seq(self, ymd):
return len(self.commands) + 1 return len(self.commands) + 1
def insert_command(self, **kw): def insert_command(self, *, command_id, cmd_class, cmd_type, ts_code, params, status,
issued_by=None, note=None, progress=None):
kw = {"command_id": command_id, "cmd_class": cmd_class, "cmd_type": cmd_type,
"ts_code": ts_code, "params": params, "status": status,
"issued_by": issued_by, "note": note, "progress": progress}
self.commands[kw["command_id"]] = { self.commands[kw["command_id"]] = {
"id": len(self.commands) + 1, "command_id": kw["command_id"], "id": len(self.commands) + 1, "command_id": kw["command_id"],
"cmd_class": kw["cmd_class"], "cmd_type": kw["cmd_type"], "ts_code": kw["ts_code"], "cmd_class": kw["cmd_class"], "cmd_type": kw["cmd_type"], "ts_code": kw["ts_code"],
@ -221,7 +225,13 @@ class FakeRepo:
"note": note}) "note": note})
return 1 return 1
def insert_instruction(self, **kw): def insert_instruction(self, *, instruction_id, origin_type, origin_id, ts_code, action,
side, qty, limit_price=None, window_tdays=3, status="PROPOSED",
progress=None):
kw = {"instruction_id": instruction_id, "origin_type": origin_type,
"origin_id": origin_id, "ts_code": ts_code, "action": action, "side": side,
"qty": qty, "limit_price": limit_price, "window_tdays": window_tdays,
"status": status, "progress": progress or {}}
kw.setdefault("exec_qty", 0) kw.setdefault("exec_qty", 0)
kw["created_at"] = kw["updated_at"] = datetime.now() kw["created_at"] = kw["updated_at"] = datetime.now()
self.instructions[kw["instruction_id"]] = kw self.instructions[kw["instruction_id"]] = kw
@ -237,11 +247,18 @@ class FakeRepo:
def get_instruction(self, iid): def get_instruction(self, iid):
return self.instructions.get(iid) return self.instructions.get(iid)
def update_instruction(self, iid, **kw): # **签名必须跟真 repo 一模一样**, 不许写成 `**kw`。2026-08-03 实机: executor 多传了
# 一个 limit_price=None, 真 repo 抛 TypeError、单子已发出而本端没记账, 而这里 `**kw`
# 全收所以单测一路绿。桩比真依赖宽松 = 单测在替真依赖打掩护。
# test_batch10 的 [M] 组会扫这一条, 别改回 **kw。
def update_instruction(self, iid, *, status=None, exec_qty=None, exec_avg_price=None,
dispatch_ref=None, progress=None):
i = self.instructions.get(iid) i = self.instructions.get(iid)
if not i: if not i:
return 0 return 0
for k, v in kw.items(): for k, v in (("status", status), ("exec_qty", exec_qty),
("exec_avg_price", exec_avg_price), ("dispatch_ref", dispatch_ref),
("progress", progress)):
if v is not None: if v is not None:
i[k] = v i[k] = v
i["updated_at"] = datetime.now() i["updated_at"] = datetime.now()
@ -253,7 +270,11 @@ class FakeRepo:
i["exec_qty"] = int(i.get("exec_qty") or 0) + int(qty) i["exec_qty"] = int(i.get("exec_qty") or 0) + int(qty)
return 1 return 1
def insert_proposal(self, **kw): def insert_proposal(self, *, proposal_id, ts_code, action, qty, hard_numbers, expire_at,
judge_verdict=None, judge_reason=None, status="WAIT_USER"):
kw = {"proposal_id": proposal_id, "ts_code": ts_code, "action": action, "qty": qty,
"hard_numbers": hard_numbers, "expire_at": expire_at,
"judge_verdict": judge_verdict, "judge_reason": judge_reason, "status": status}
kw["status"] = kw.get("status", "WAIT_USER") kw["status"] = kw.get("status", "WAIT_USER")
kw["hard_numbers"] = kw.get("hard_numbers") or {} kw["hard_numbers"] = kw.get("hard_numbers") or {}
self.proposals[kw["proposal_id"]] = kw self.proposals[kw["proposal_id"]] = kw
@ -275,7 +296,12 @@ class FakeRepo:
def expire_proposals(self, now=None): def expire_proposals(self, now=None):
return 0 return 0
def insert_ledger(self, **kw): def insert_ledger(self, *, ts_code, action, arbiter, verdict, price_at, hard_numbers=None,
failed_checks=None, reason=None, ref_id=None):
kw = {"ts_code": ts_code, "action": action, "arbiter": arbiter, "verdict": verdict,
"price_at": price_at, "hard_numbers": hard_numbers or {},
"failed_checks": failed_checks or [], "reason": reason, "ref_id": ref_id,
"decided_at": datetime.now()}
self.ledger.append(kw) self.ledger.append(kw)
return 1 return 1
@ -1152,17 +1178,20 @@ def _():
"deadline": "2026-07-27", "status": "EXECUTING", "reason": "收利润"}]) "deadline": "2026-07-27", "status": "EXECUTING", "reason": "收利润"}])
fake.insert_instruction(instruction_id="INS_F", origin_type="plan", origin_id="P_F", fake.insert_instruction(instruction_id="INS_F", origin_type="plan", origin_id="P_F",
ts_code="600000.SH", action="TRIM", side="sell", qty=2000, ts_code="600000.SH", action="TRIM", side="sell", qty=2000,
status="DISPATCHED", exec_qty=2000, status="DISPATCHED",
progress={"deadline": "2026-07-27", "is_command": True, progress={"deadline": "2026-07-27", "is_command": True,
"children": []}) "children": []})
# exec_qty 不是 insert_instruction 的形参 (真 repo 没有), 造数据直接改桩里的字段
fake.instructions["INS_F"]["exec_qty"] = 2000
fake.insert_instruction(instruction_id="INS_P", origin_type="plan", origin_id="P_F", fake.insert_instruction(instruction_id="INS_P", origin_type="plan", origin_id="P_F",
ts_code="600000.SH", action="TRIM", side="sell", qty=2000, ts_code="600000.SH", action="TRIM", side="sell", qty=2000,
status="DISPATCHED", exec_qty=500, status="DISPATCHED",
progress={"deadline": "2026-07-20", "is_command": True, progress={"deadline": "2026-07-20", "is_command": True,
"children": []}) "children": []})
fake.instructions["INS_P"]["exec_qty"] = 500
fake.insert_instruction(instruction_id="INS_A2", origin_type="proposal", origin_id="PR1", fake.insert_instruction(instruction_id="INS_A2", origin_type="proposal", origin_id="PR1",
ts_code="600000.SH", action="ADD", side="buy", qty=1000, ts_code="600000.SH", action="ADD", side="buy", qty=1000,
status="DISPATCHED", exec_qty=0, status="DISPATCHED",
progress={"deadline": "2026-07-20", "is_command": False, progress={"deadline": "2026-07-20", "is_command": False,
"children": []}) "children": []})
r = executor.sweep_windows(now=_dt(2026, 7, 27, 15, 10)) r = executor.sweep_windows(now=_dt(2026, 7, 27, 15, 10))
@ -1317,7 +1346,7 @@ def _():
fake = install_fakes(prices={"600000.SH": 10.0}) fake = install_fakes(prices={"600000.SH": 10.0})
fake.insert_instruction(instruction_id="INS_R", origin_type="plan", origin_id="P1", fake.insert_instruction(instruction_id="INS_R", origin_type="plan", origin_id="P1",
ts_code="600000.SH", action="OPEN", side="buy", qty=100, ts_code="600000.SH", action="OPEN", side="buy", qty=100,
price_hint=10.0, status="DISPATCHED") status="DISPATCHED")
# 真单: 出口表有行、父指令不带 SMOKE_ # 真单: 出口表有行、父指令不带 SMOKE_
fake.qmt.enqueue_order(instruction_id="INS_R-1", parent_id="INS_R", ts_code="600000.SH", fake.qmt.enqueue_order(instruction_id="INS_R-1", parent_id="INS_R", ts_code="600000.SH",
side="buy", qty=100, limit_price=10.0, valid_until=0) side="buy", qty=100, limit_price=10.0, valid_until=0)