This commit is contained in:
2026-09-06 14:49:46 +08:00
parent 2eafbb8303
commit fdcbdc7869
5 changed files with 162 additions and 68 deletions

View File

@@ -9,10 +9,9 @@ from dataclasses import asdict, dataclass, field
from pathlib import Path
from time import time
from sdk import OrderItem, PositionItem
from sdk import DealItem, PositionItem
READY, SOLD, DONE = "READY", "SOLD", "DONE"
TERMINAL_STATUSES = {"53", "54", "56", "57"}
@dataclass(slots=True)
@@ -72,14 +71,14 @@ class TState:
raise
def reconcile(
self, positions: list[PositionItem], orders: list[OrderItem], today: str
self, positions: list[PositionItem], deals: list[DealItem], today: str
) -> None:
"""先按实际成交记账,再接管未知持仓;不覆盖已记录的底仓成本。"""
# 同一本地委托可能拆单;按券商订单号去重,数量齐全且全部结束才记账
by_id: dict[str, dict[str, OrderItem]] = {}
for order in orders:
if order.local_order_id:
by_id.setdefault(order.local_order_id, {})[order.id] = order
# 同一本地委托可能有多笔成交;按成交编号去重后合并数量和金额
by_id: dict[str, dict[str, DealItem]] = {}
for deal in deals:
if deal.local_order_id and deal.id:
by_id.setdefault(deal.local_order_id, {})[deal.id] = deal
for order_id, pending in list(self.pending.items()):
side = "SELL" if pending.kind == "sell" else "BUY"
rows = [
@@ -88,25 +87,16 @@ class TState:
if row.code == pending.code and row.side == side
]
if not rows:
log.warning("[ZT 状态] 委托暂未查到,保留待确认:%s", order_id)
log.warning("[ZT 状态] 成交暂未查到,保留待确认:%s", order_id)
continue
if sum(row.volume for row in rows) != pending.qty:
qty = sum(row.volume for row in rows)
# 成交未达到计划数量时继续等待,防止后续成交到达后重复记账。
if qty != pending.qty:
continue
if any(row.status not in TERMINAL_STATUSES for row in rows):
continue
if any(
row.status == "56" and row.traded_volume != row.volume for row in rows
):
continue
qty = sum(row.traded_volume for row in rows)
amounts = [
(
row.trade_amount
if row.trade_amount > 0
else row.trade_price * row.traded_volume
)
row.amount if row.amount > 0 else row.price * row.volume
for row in rows
if row.traded_volume > 0
if row.volume > 0
]
if any(not math.isfinite(amount) or amount <= 0 for amount in amounts):
continue
@@ -132,7 +122,7 @@ class TState:
item.phase = DONE if total >= item.sell_qty else SOLD
if item.phase == DONE:
item.trade_date = today
# 零成交撤单也记录,保留计划、实际数量、均价和柜台终态
# 记录真实成交编号,重启后仍可核对本次状态变更的来源
self.records.append(
{
**asdict(pending),
@@ -140,7 +130,7 @@ class TState:
"filled_qty": qty,
"filled_cost": cost,
"amount": amount,
"statuses": [row.status for row in rows],
"deal_ids": [row.id for row in rows],
}
)
del self.pending[order_id]
@@ -201,19 +191,10 @@ class TState:
if not self.path.is_file():
return
raw = json.loads(self.path.read_text(encoding="utf-8"))
# 兼容原 ZT 文件,保留原底仓成本;没有额外版本字段。
self.items = {
code: TStateItem(**item) for code, item in raw.get("items", raw).items()
code: TStateItem(**item) for code, item in raw["items"].items()
}
self.pending = {
key: PendingOrder(**item) for key, item in raw.get("pending", {}).items()
key: PendingOrder(**item) for key, item in raw["pending"].items()
}
self.records = raw.get("records", [])
if "items" not in raw:
# 旧记录只有提交行情价,不把它伪装成真实成交历史。
self.records.append({"kind": "legacy_import", "items": raw})
for item in self.items.values():
if item.phase in {"SELLING", "BUYING", SOLD}:
raise ValueError(
f"[ZT 状态] {item.code} 旧做 T 轮次未结束,需先核对成交再迁移"
)
self.records = raw["records"]