update api,libs

This commit is contained in:
2026-08-30 00:34:27 +08:00
parent 9a43aaba23
commit cdccc48d8c
21 changed files with 2616 additions and 179 deletions

View File

@@ -8,7 +8,7 @@ from pathlib import Path
from threading import Lock
from typing import Iterable
from sdk import Position
from sdk import OrderItem, PositionItem
# 委托状态:无操作、处理中、已完成。
@@ -70,6 +70,16 @@ class State:
with self.lock:
return list(self.items)
@property
def unresolved_codes(self) -> list[str]:
"""返回存在处理中或未知订单状态的证券代码快照。"""
with self.lock:
return [
code
for code, item in self.items.items()
if _has_unresolved_order(item)
]
def get(self, code: str) -> StateItem:
"""获取指定证券的状态;不存在时抛出 KeyError。"""
with self.lock:
@@ -80,12 +90,21 @@ class State:
with self.lock:
self.items[item.code] = item
def delete(self, code: str) -> None:
"""删除证券状态;证券不存在时不报错"""
def delete(self, code: str) -> bool:
"""删除已终结的证券状态,并返回是否实际删除"""
with self.lock:
self.items.pop(code, None)
item = self.items.get(code)
if item is not None and _has_unresolved_order(item):
return False
return self.items.pop(code, None) is not None
def sync_positions(self, positions: Iterable[Position]) -> None:
def has_unresolved_order(self, code: str) -> bool:
"""判断证券是否存在必须阻止自动下单的未决订单。"""
with self.lock:
item = self.items.get(code)
return item is not None and _has_unresolved_order(item)
def sync_positions(self, positions: Iterable[PositionItem]) -> None:
"""把尚未接管的真实持仓初始化为已完成底仓。
无证券代码、无持仓数量或成本无效的记录会被忽略。同步结束后
@@ -115,29 +134,31 @@ class State:
def reconcile(
self,
positions: Iterable[Position],
orders: list[dict[str, str]],
positions: Iterable[PositionItem],
orders: list[OrderItem],
deals: list[dict[str, str]],
) -> None:
"""用真实持仓、委托和成交恢复本地状态,不增加持久化字段。"""
position_list = list(positions)
self.sync_positions(position_list)
active_codes = {
position_codes = {
item.stock_code for item in position_list if item.volume > 0
}
for code in list(self.codes):
if code not in active_codes:
self.delete(code)
for code in list(self.codes):
item = self.get(code)
item.base_status = _reconcile_leg(
item.base_order_id, item.base_status, orders, deals
item.base_order_id, item.base_status, item.base_qty, orders, deals
)
item.added_status = _reconcile_leg(
item.added_order_id, item.added_status, orders, deals
item.added_order_id, item.added_status, item.added_qty, orders, deals
)
self.set(item)
# Opening orders normally have no position until their first fill. Order
# reconciliation must therefore happen before stale state is removed.
for code in list(self.codes):
if code not in position_codes:
self.delete(code)
self.save()
def save(self) -> None:
@@ -179,31 +200,70 @@ class State:
def _reconcile_leg(
local_order_id: str,
current_status: str,
orders: list[dict[str, str]],
expected_qty: int,
orders: list[OrderItem],
deals: list[dict[str, str]],
) -> str:
if current_status != STATUS_ING or not local_order_id:
if current_status not in {STATUS_ING, STATUS_UNKNOWN} or not local_order_id:
return current_status
if any(local_order_id in row.get("m_strRemark", "") for row in deals):
return STATUS_OK
order = next(
(
row for row in orders
if local_order_id in row.get("m_strRemark", "")
),
None,
)
matching_orders = [
order for order in orders if order.local_order_id == local_order_id
]
order = matching_orders[-1] if matching_orders else None
if order is None:
matching_deals = [
row for row in deals if _matches_local_order(row, local_order_id)
]
dealt = sum(_deal_volume(row) for row in matching_deals)
if expected_qty > 0 and dealt >= expected_qty:
return STATUS_OK
if expected_qty <= 0 and matching_deals:
return STATUS_OK
return STATUS_UNKNOWN
traded = _as_int(order.get("m_nVolumeTraded"))
status = str(order.get("m_nOrderStatus", ""))
if traded > 0 and status not in {"48", "49", "50", "51", "52", "55"}:
system_order_id = order.id.strip()
matching_deals = [
row
for row in deals
if (
system_order_id
and str(row.get("m_strOrderSysID") or "").strip() == system_order_id
)
or (not system_order_id and _matches_local_order(row, local_order_id))
]
dealt = sum(_deal_volume(row) for row in matching_deals)
traded = max(order.traded_volume, dealt)
ordered = order.volume or expected_qty
status = order.status
if ordered > 0 and traded >= ordered:
return STATUS_OK
if status in {"48", "49", "50", "51", "52", "55"}:
return STATUS_ING
if status in {"54", "56"}:
return STATUS_CANCELED
return STATUS_UNKNOWN if traded > 0 else STATUS_CANCELED
if status in {"57", "58"}:
return STATUS_FAILED
return STATUS_ING
return STATUS_UNKNOWN if traded > 0 else STATUS_FAILED
return STATUS_UNKNOWN
def _has_unresolved_order(item: StateItem) -> bool:
return item.base_status in {STATUS_ING, STATUS_UNKNOWN} or item.added_status in {
STATUS_ING,
STATUS_UNKNOWN,
}
def _matches_local_order(row: dict[str, str], local_order_id: str) -> bool:
remark = str(row.get("m_strRemark") or "")
return remark.split("|", 1)[0] == local_order_id
def _deal_volume(deal: dict[str, str]) -> int:
for key in ("m_nVolume", "m_nTradeVolume", "m_nVolumeTraded"):
volume = _as_int(deal.get(key))
if volume > 0:
return volume
return 0
def _as_int(value: object) -> int: