"""趋势策略委托簿,对应 Go 客户端的 ``logic/order.go``。""" from __future__ import annotations import secrets from dataclasses import dataclass from datetime import datetime, timedelta from threading import Lock from typing import Any # QMT 开平方向字段到本地买卖方向的映射。 OFFSET_FLAG = {"23": "BUY", "24": "SELL", "48": "BUY", "49": "SELL"} # 表示委托仍在处理、可能继续成交的 QMT 状态。 BUSY_STATUSES = {"48", "49", "50", "51", "52", "55"} @dataclass(frozen=True, slots=True) class PlaceOrderRequest: """``OrderBook.place`` 提交委托所需的全部参数。""" client: Any op: int code: str volume: int order_id: str strategy_name: str @dataclass(slots=True) class OrderItem: """从 QMT 委托明细转换得到的本地订单记录。""" id: str code: str side: str remark: str status: str created_at: datetime | None volume: int local_order_id: str = "" class OrderBook: """线程安全的活动委托缓存。""" def __init__(self, timeout_seconds: float = 300) -> None: self.timeout = timedelta(seconds=timeout_seconds) self.data: dict[str, OrderItem] = {} self.index: list[str] = [] self.lock = Lock() @staticmethod def new_order_id(leg: str) -> str: """生成短订单号,为 QMT 备注中的信号键预留空间。""" return f"zt-{leg[:1]}-{secrets.token_hex(4)}" def is_lock(self, side: str, code: str) -> bool: """判断证券在指定买卖方向上是否已经被委托锁定。""" with self.lock: return f"{side}-{code}" in self.index def busy(self, code: str, side: str) -> bool: """判断证券是否存在仍在处理中的同方向委托。""" with self.lock: key = f"{side}-{code}" order = self.data.get(key) return key in self.index or bool(order and order.status in BUSY_STATUSES) def refresh(self, client: Any) -> None: """从 QMT 刷新当前委托明细和方向索引。""" parsed_orders = [ parse_order(row) for row in client.trade_detail_data("order") ] with self.lock: self.data = {key: item for key, item in parsed_orders} self.index = [ key for key, item in parsed_orders if item.status in BUSY_STATUSES ] def cancel_expired(self, client: Any, now: datetime | None = None) -> None: """尝试撤销超过有效期且具有委托编号的订单。""" self.refresh(client) current = now or datetime.now() # 使用快照遍历,避免网络调用期间长期持有互斥锁。 for order in list(self.data.values()): if ( order.created_at is not None and current - order.created_at > self.timeout and order.id ): client.cancel_by_id(order.id) def place(self, request: PlaceOrderRequest) -> bool: """按最新价提交委托,并立即写入本地方向锁。""" result = request.client.passorder_latest_tagged( request.op, request.code, request.volume, request.strategy_name, request.order_id, ) if not isinstance(result, dict): return False order_ref = str(result.get("order_ref") or "").strip().lower() if result.get("status") != "success" or order_ref in {"", "unknown", "none"}: return False side = OFFSET_FLAG.get(str(request.op), "") pending = OrderItem( id=order_ref, code=request.code, side=side, remark=request.order_id, status="48", created_at=datetime.now(), volume=request.volume, local_order_id=request.order_id, ) with self.lock: key = f"{side}-{request.code}" self.data[key] = pending if key not in self.index: self.index.append(key) return True def parse_order(row: dict[str, Any]) -> tuple[str, OrderItem]: """把 QMT 原始委托字段转换为本地订单及其索引键。""" volume = _as_int(row.get("m_nVolumeTotal")) + _as_int( row.get("m_nVolumeTraded") ) timestamp = _as_int(row.get("m_nOrderTime")) if timestamp > 100_000_000_000: # QMT 某些版本返回毫秒时间戳。 timestamp /= 1000 created_at = ( datetime.fromtimestamp(timestamp) if timestamp else _parse_insert_datetime(row) ) item = OrderItem( id=str(row.get("m_strOrderSysID") or ""), code=str(row.get("m_strInstrumentID") or ""), side=OFFSET_FLAG.get(str(row.get("m_nOffsetFlag")), ""), remark=str(row.get("m_strRemark") or ""), status=str(row.get("m_nOrderStatus") or ""), created_at=created_at, volume=volume, local_order_id=_local_order_id(str(row.get("m_strRemark") or "")), ) return f"{item.side}-{item.code}", item def _as_int(value: Any) -> int: """安全转换整数,无效值按 0 处理。""" try: return int(value or 0) except (TypeError, ValueError): return 0 def _parse_insert_datetime(row: dict[str, Any]) -> datetime | None: """使用委托日期和时间字段构造本地时间。""" date = str(row.get("m_strInsertDate") or "") clock = str(row.get("m_strInsertTime") or "").replace(":", "").zfill(6) try: return datetime.strptime(date + clock, "%Y%m%d%H%M%S") except ValueError: return None def _local_order_id(remark: str) -> str: """兼容 ``local_order_id|signal_key`` 形式的 QMT 备注。""" return remark.split("|", 1)[0] if remark else ""