fix bug
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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
|
||||
|
||||
@@ -10,15 +10,13 @@ from threading import Lock
|
||||
from typing import Iterable
|
||||
|
||||
from sdk import OrderItem, PositionItem
|
||||
from .order import BUSY_STATUSES, COMPLETED_STATUSES
|
||||
|
||||
|
||||
# 委托状态:无操作、处理中、已完成。
|
||||
STATUS_NONE = ""
|
||||
STATUS_ING = "ING"
|
||||
STATUS_OK = "OK"
|
||||
STATUS_FAILED = "FAILED"
|
||||
STATUS_CANCELED = "CANCELED"
|
||||
STATUS_UNKNOWN = "UNKNOWN"
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
@@ -106,6 +104,7 @@ class State:
|
||||
|
||||
self.set(
|
||||
StateItem(
|
||||
base_order_id=position.trade_id,
|
||||
code=position.stock_code,
|
||||
base_qty=position.volume,
|
||||
base_cost=round(position.open_price, 2),
|
||||
@@ -143,22 +142,14 @@ class State:
|
||||
):
|
||||
local_order_id = getattr(item, order_id_attr)
|
||||
current_status = getattr(item, status_attr)
|
||||
if (
|
||||
current_status not in {STATUS_ING, STATUS_UNKNOWN}
|
||||
or not local_order_id
|
||||
):
|
||||
if current_status != STATUS_ING or not local_order_id:
|
||||
continue
|
||||
|
||||
matching_orders = orders_by_local_id.get(local_order_id)
|
||||
if matching_orders:
|
||||
status = (
|
||||
STATUS_OK
|
||||
if all(order.status == "56" for order in matching_orders)
|
||||
else STATUS_ING
|
||||
)
|
||||
if status != current_status:
|
||||
log.info("[状态] %s 订单=%s,状态=%s->%s", code, local_order_id, current_status, status)
|
||||
setattr(item, status_attr, status)
|
||||
status = _order_status(matching_orders)
|
||||
if status != current_status:
|
||||
log.info("[状态] %s 订单=%s,状态=%s->%s", code, local_order_id, current_status, status)
|
||||
setattr(item, status_attr, status)
|
||||
self.set(item)
|
||||
|
||||
# Opening orders normally have no position until their first fill. Order
|
||||
@@ -209,3 +200,15 @@ class State:
|
||||
}
|
||||
except (TypeError, ValueError) as exc:
|
||||
raise ValueError(f"[状态] 状态字段无效: {exc}") from exc
|
||||
|
||||
|
||||
def _order_status(orders: list[OrderItem] | None) -> str:
|
||||
"""将柜台订单简化为无状态、处理中或已成交。"""
|
||||
if not orders:
|
||||
return STATUS_NONE
|
||||
statuses = {order.status for order in orders}
|
||||
if statuses <= COMPLETED_STATUSES:
|
||||
return STATUS_OK
|
||||
if statuses <= BUSY_STATUSES | COMPLETED_STATUSES:
|
||||
return STATUS_ING
|
||||
return STATUS_NONE
|
||||
|
||||
Reference in New Issue
Block a user