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)})