From 8c06ef1e5321751c8435d725bcd0c76dbaf1aaa3 Mon Sep 17 00:00:00 2001 From: claude_dev Date: Wed, 15 Jul 2026 07:12:46 +0800 Subject: [PATCH] =?UTF-8?q?feat(bridge):=20=E8=A1=A5=20/orders+/cancel=20?= =?UTF-8?q?=E7=AB=AF=E7=82=B9=E4=B8=8E=E5=AE=A2=E6=88=B7=E7=AB=AF(?= =?UTF-8?q?=E5=AE=9E=E7=9B=98=E6=9F=A5=E5=A7=94=E6=89=98/=E6=92=A4?= =?UTF-8?q?=E5=8D=95)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit bridge.py: POST /cancel + GET /orders; xt_gateway: query_orders/cancel_order(含xtquant状态映射50挂单/56已成/57拒单/54撤单); bridge_client: get_orders/cancel_order. 已在 VPS 生产验证. --- sanguo_qmt_bridge/bridge.py | 34 ++++++++++++++++++++++++++ sanguo_qmt_bridge/xt_gateway.py | 42 +++++++++++++++++++++++++++++++++ sanguo_trader/bridge_client.py | 11 +++++++++ 3 files changed, 87 insertions(+) diff --git a/sanguo_qmt_bridge/bridge.py b/sanguo_qmt_bridge/bridge.py index 0229503..9c5bcd2 100644 --- a/sanguo_qmt_bridge/bridge.py +++ b/sanguo_qmt_bridge/bridge.py @@ -151,3 +151,37 @@ async def query_positions() -> dict: logger.error("查持仓异常: %s", e) return {"ok": False, "error": f"查询异常: {e}"} return {"ok": True, "positions": positions} + + +class CancelRequest(BaseModel): + """撤单请求。""" + order_id: int + + +@app.post("/cancel", dependencies=[Depends(verify_token)]) +async def cancel_order(req: CancelRequest) -> dict: + """撤单 -> {ok, order_id} 或 {ok:false, error}。撤单请求提交后查 /orders 确认状态。""" + if not gateway.connected: + return {"ok": False, "error": "miniQMT 未连接"} + logger.info("撤单请求 order_id=%s", req.order_id) + try: + ret = gateway.cancel_order(req.order_id) + except (ValueError, RuntimeError) as e: + return {"ok": False, "error": str(e)} + except Exception as e: + logger.error("撤单异常: %s", e) + return {"ok": False, "error": f"撤单异常: {e}"} + return {"ok": True, "order_id": ret} + + +@app.get("/orders", dependencies=[Depends(verify_token)]) +async def query_orders() -> dict: + """查委托 -> {ok, orders:[{order_id, code, status, status_name, volume, traded, price}]}。""" + if not gateway.connected: + return {"ok": False, "error": "miniQMT 未连接"} + try: + orders = gateway.query_orders() + except Exception as e: + logger.error("查委托异常: %s", e) + return {"ok": False, "error": f"查询异常: {e}"} + return {"ok": True, "orders": orders} diff --git a/sanguo_qmt_bridge/xt_gateway.py b/sanguo_qmt_bridge/xt_gateway.py index b5b6fc7..5a3bae7 100644 --- a/sanguo_qmt_bridge/xt_gateway.py +++ b/sanguo_qmt_bridge/xt_gateway.py @@ -260,6 +260,48 @@ class XtGateway: return self._retry_with_reconnect(_do, "place_order") + def query_orders(self) -> list[dict[str, Any]]: + """查委托列表:[{order_id, code, status, status_name, volume, traded, price}]。""" + def _do() -> list[dict[str, Any]]: + if not self._connected: + raise RuntimeError("xtquant 未连接") + orders = self._xt.query_stock_orders(self._account) + if not orders: + return [] + # xtquant order_status 实测映射(50=挂单,56=已成,57=拒单,54=撤单) + _STATUS = {48: "unknown", 50: "pending", 51: "reporting", 52: "reported", + 53: "reported", 54: "canceled", 55: "canceled", + 56: "filled", 57: "rejected"} + result = [] + for o in orders: + try: + st = getattr(o, "order_status", None) + code = getattr(o, "stock_code", None) + result.append({ + "order_id": getattr(o, "order_id", None), + "code": to_sanguo_code(code) if code else None, + "status": st, + "status_name": _STATUS.get(st, str(st)), + "status_msg": getattr(o, "status_msg", "") or "", + "volume": int(getattr(o, "order_volume", 0) or 0), + "traded": int(getattr(o, "traded_volume", 0) or 0), + "price": float(getattr(o, "price", 0) or 0), + }) + except Exception as e: + logger.warning("query_orders 解析异常: %s", e) + return result + + return self._retry_with_reconnect(_do, "query_orders") + + def cancel_order(self, order_id: int) -> int: + """撤单,返回 order_id(>0=撤单请求受理,实际状态查 /orders)。""" + def _do() -> int: + if not self._connected: + raise RuntimeError("xtquant 未连接") + return int(self._xt.cancel_order_stock(self._account, int(order_id))) + + return self._retry_with_reconnect(_do, "cancel_order") + # 模块级单例(bridge.py 启动时调 connect) gateway = XtGateway() diff --git a/sanguo_trader/bridge_client.py b/sanguo_trader/bridge_client.py index 5ba91bb..f6eb5f1 100644 --- a/sanguo_trader/bridge_client.py +++ b/sanguo_trader/bridge_client.py @@ -110,3 +110,14 @@ class BridgeClient: if resp is None: return None return resp.get("positions") + + def get_orders(self) -> list | None: + """GET /orders → 委托列表;失败返回 None。""" + resp = self._get("/orders") + if resp is None: + return None + return resp.get("orders") + + def cancel_order(self, order_id: int) -> dict | None: + """POST /cancel → {ok, order_id} 或 {ok:false, error};失败返回 None。""" + return self._post("/cancel", {"order_id": int(order_id)})