This commit is contained in:
2026-09-05 00:23:55 +08:00
parent 9f01eb8dc5
commit 6c2eca8fdc
18 changed files with 59 additions and 51 deletions

View File

@@ -8,6 +8,7 @@ from dataclasses import dataclass
from datetime import datetime, timedelta
from threading import Lock
from typing import Any
from cachelib import SimpleCache
from sdk import APIError, ORDER_SIDE_BY_OFFSET, Client, OrderItem
@@ -33,30 +34,32 @@ class PlaceOrderRequest:
class OrderBook:
"""线程安全的活动委托缓存。"""
def __init__(self, lock_timeout_sec: float = 180, cancel_timeout_sec: float = 10) -> None:
self.lock_timeout_sec = max(0.0, float(lock_timeout_sec))
def __init__(self, lock_timeout_sec: int = 180, cancel_timeout_sec: float = 10) -> None:
self.lock_timeout_sec = max(1, lock_timeout_sec)
self.cancel_timeout_sec = timedelta(seconds=cancel_timeout_sec)
self.data: list[OrderItem] = []
self.lock: dict[str, float] = {}
self.busy_cache = SimpleCache(threshold=10_000, default_timeout=self.lock_timeout_sec)
self.mutex = Lock()
@staticmethod
def new_order_id(_leg: str) -> str:
def new_order_id() -> str:
"""生成 ``trend-xxxxxxxx`` 格式的本地订单号。"""
return f"trend-{secrets.token_hex(4)}"
return f"trend-{secrets.token_hex(12)}"
def busy(self, code: str, side: str) -> bool:
"""判断证券是否存在仍在处理中的同方向委托。"""
with self.mutex:
key = f"{side}-{code}"
return key in self.lock
return self.busy_cache.has(self._busy_key(side, code))
@staticmethod
def _busy_key(side: str, code: str) -> str:
return f"{side}-{code}"
def refresh(self, client: Client, orders: list[OrderItem]) -> None:
"""用账户快照刷新委托,并撤销超时的活动委托。"""
current = datetime.now()
now_timestamp = current.timestamp()
data: list[OrderItem] = []
lock: dict[str, float] = {}
busy_keys: set[str] = set()
canceled = 0
for item in orders:
@@ -78,21 +81,27 @@ class OrderBook:
data.append(item)
if item.status in BUSY_STATUSES:
key = f"{item.side}-{item.code}"
lock[key] = (
item.created_at.timestamp()
if item.created_at is not None
else now_timestamp
)
busy_keys.add(self._busy_key(item.side, item.code))
with self.mutex:
self.data = data
self.lock = lock
log.info("[Order] 刷新完成,跟踪=%d,处理中=%d,撤销=%d", len(data), len(lock), canceled)
log.info("[Order] 刷新完成,跟踪=%d,处理中=%d,撤销=%d", len(data), len(busy_keys), canceled)
def place(self, request: PlaceOrderRequest) -> bool:
"""按最新价提交委托,并立即写入本地方向锁。"""
side = ORDER_SIDE_BY_OFFSET.get(str(request.op), "")
if not side:
log.warning("[Order] 下单失败,代码=%s,原因=未知买卖方向(%s)", request.code, request.op)
return False
key = self._busy_key(side, request.code)
with self.mutex:
if self.busy_cache.has(key):
log.info("[Order] 跳过重复下单,代码=%s,方向=%s", request.code, side)
return False
self.busy_cache.set(key, True, timeout=self.lock_timeout_sec)
try:
result = request.client.passorder_latest_tagged(
request.op,
@@ -112,7 +121,6 @@ class OrderBook:
log.warning("[Order] 下单被拒绝,代码=%s,本地订单=%s,状态=%s,柜台订单=%s", request.code, request.order_id, result.get("status"), order_ref)
return False
side = ORDER_SIDE_BY_OFFSET.get(str(request.op), "")
pending = OrderItem(
id=order_ref,
code=request.code,
@@ -124,8 +132,6 @@ class OrderBook:
local_order_id=request.order_id,
)
with self.mutex:
key = f"{side}-{request.code}"
self.data.append(pending)
self.lock[key] = pending.created_at.timestamp()
log.info("[Order] 下单已受理,代码=%s,方向=%s,数量=%d,本地订单=%s,柜台订单=%s", request.code, side, request.volume, request.order_id, order_ref)
return True