fix order busy

This commit is contained in:
2026-09-05 11:32:10 +08:00
parent a53fc738cf
commit 3689a7ad86
4 changed files with 204 additions and 28 deletions

View File

@@ -52,24 +52,6 @@ class TradeMixin:
) or []
return response if isinstance(response, list) else []
def passorder_latest(self, side: int, stock_code: str, volume: int) -> dict[str, Any]:
return self.passorder(side, stock_code, volume)
def passorder_latest_tagged(
self,
side: int,
stock_code: str,
volume: int,
strategy_name: str,
order_id: str,
) -> dict[str, Any]:
return self.passorder(
side,
stock_code,
volume,
strategy_name=strategy_name,
order_id=order_id,
)
def cancel_by_id(self, order_id: str) -> dict[str, Any]:
return self._post_json("/api/trade/cancel_by_id", {"order_id": order_id})

View File

@@ -38,6 +38,7 @@ class OrderBook:
self.lock_timeout_sec = max(1, lock_timeout_sec)
self.cancel_timeout_sec = timedelta(seconds=cancel_timeout_sec)
self.data: list[OrderItem] = []
self.busy_keys: set[str] = set()
self.busy_cache = SimpleCache(threshold=10_000, default_timeout=self.lock_timeout_sec)
self.mutex = Lock()
@@ -49,7 +50,8 @@ class OrderBook:
def busy(self, code: str, side: str) -> bool:
"""判断证券是否存在仍在处理中的同方向委托。"""
with self.mutex:
return self.busy_cache.has(self._busy_key(side, code))
key = self._busy_key(side, code)
return key in self.busy_keys or self.busy_cache.has(key)
@staticmethod
def _busy_key(side: str, code: str) -> str:
@@ -66,6 +68,8 @@ class OrderBook:
# 不处理状态不对的
if item.status not in TRACKED_STATUSES:
continue
if item.status in BUSY_STATUSES:
busy_keys.add(self._busy_key(item.side, item.code))
# 清理过期的
if (
item.created_at is not None
@@ -80,12 +84,11 @@ class OrderBook:
# 缓存本次有效订单
data.append(item)
if item.status in BUSY_STATUSES:
busy_keys.add(self._busy_key(item.side, item.code))
with self.mutex:
self.data = data
self.busy_keys = busy_keys
log.info("[Order] 刷新完成,跟踪=%d,处理中=%d,撤销=%d", len(data), len(busy_keys), canceled)
def place(self, request: PlaceOrderRequest) -> bool:
@@ -97,18 +100,18 @@ class OrderBook:
key = self._busy_key(side, request.code)
with self.mutex:
if self.busy_cache.has(key):
if key in self.busy_keys or 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,
request.code,
request.volume,
request.strategy_name,
request.order_id,
result = request.client.passorder(
op_type=request.op,
stock_code=request.code,
volume=request.volume,
strategy_name=request.strategy_name,
order_id=request.order_id,
)
except APIError as exc:
log.exception("[Order] 下单失败,代码=%s,本地订单=%sHTTP状态=%d,错误=%s", request.code, request.order_id, exc.status_code, exc.message or str(exc))

View File

@@ -0,0 +1,42 @@
import unittest
from datetime import datetime, timedelta
from unittest.mock import Mock
from sdk import OrderItem
from strategy.trend.order import OrderBook, PlaceOrderRequest
class TrendBusyTests(unittest.TestCase):
def test_snapshot_blocks_order_without_local_cache(self):
book = OrderBook()
client = Mock()
order = OrderItem("1", "A", "BUY", "", "50", None, 100)
book.refresh(client, [order])
self.assertTrue(book.busy("A", "BUY"))
self.assertFalse(book.busy("A", "SELL"))
self.assertFalse(book.place(PlaceOrderRequest(client, 23, "A", 100, "local", "trend")))
client.passorder.assert_not_called()
book.refresh(client, [])
self.assertFalse(book.busy("A", "BUY"))
def test_cancel_request_keeps_order_busy_until_terminal_snapshot(self):
book = OrderBook()
client = Mock()
order = OrderItem("1", "A", "BUY", "", "50", datetime.now() - timedelta(seconds=20), 100)
book.refresh(client, [order])
client.cancel_by_id.assert_called_once_with("1")
self.assertTrue(book.busy("A", "BUY"))
order.status = "54"
book.refresh(client, [order])
self.assertFalse(book.busy("A", "BUY"))
def test_empty_snapshot_keeps_local_cache_protection(self):
book = OrderBook()
client = Mock()
client.passorder.return_value = {"status": "success", "order_ref": "1"}
request = PlaceOrderRequest(client, 23, "A", 100, "local", "trend")
self.assertTrue(book.place(request))
book.refresh(client, [])
self.assertTrue(book.busy("A", "BUY"))
self.assertFalse(book.place(request))
client.passorder.assert_called_once()