Harden residual mid-close: IOC partial fills, exchange reconcile, atomic book.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -185,7 +185,7 @@ class OkxTradeClient:
|
||||
if not rows:
|
||||
raise RuntimeError("OKX IOC 下单无返回")
|
||||
ord_id = str(rows[0].get("ordId") or "")
|
||||
return self._wait_fill(inst_id, ord_id)
|
||||
return self._wait_fill(inst_id, ord_id, allow_partial=True)
|
||||
|
||||
def _fill_from_order_row(self, inst_id: str, ord_id: str, row: dict[str, Any]) -> LiveFill:
|
||||
avg = safe_float(row.get("avgPx")) or 0.0
|
||||
@@ -206,7 +206,14 @@ class OkxTradeClient:
|
||||
raw=row,
|
||||
)
|
||||
|
||||
def _wait_fill(self, inst_id: str, ord_id: str, *, tries: int = 40) -> LiveFill:
|
||||
def _wait_fill(
|
||||
self,
|
||||
inst_id: str,
|
||||
ord_id: str,
|
||||
*,
|
||||
tries: int = 40,
|
||||
allow_partial: bool = False,
|
||||
) -> LiveFill:
|
||||
path = f"/api/v5/trade/order?instId={inst_id}&ordId={ord_id}"
|
||||
last: dict[str, Any] = {}
|
||||
for _ in range(tries):
|
||||
@@ -215,21 +222,38 @@ class OkxTradeClient:
|
||||
last = rows[0]
|
||||
state = str(last.get("state") or "")
|
||||
avg = safe_float(last.get("avgPx"))
|
||||
acc = safe_float(last.get("accFillSz")) or 0.0
|
||||
# 仅完全成交;部分成交继续等,避免账本张数与交易所不一致
|
||||
if state == "filled" and avg and avg > 0:
|
||||
return self._fill_from_order_row(inst_id, ord_id, last)
|
||||
if state in ("canceled", "failed"):
|
||||
# IOC:未成交部分取消;若已有成交量则按部分成交入账
|
||||
if (
|
||||
allow_partial
|
||||
and acc > 1e-12
|
||||
and avg
|
||||
and avg > 0
|
||||
):
|
||||
return self._fill_from_order_row(inst_id, ord_id, last)
|
||||
raise RuntimeError(f"OKX 订单失败 state={state} {last}")
|
||||
time.sleep(0.3)
|
||||
# 超时兜底:仅接受完全成交;部分成交不得当全成记账(会错张数/对冲)
|
||||
state = str(last.get("state") or "")
|
||||
avg = safe_float(last.get("avgPx"))
|
||||
acc = safe_float(last.get("accFillSz")) or 0.0
|
||||
if state == "filled" and avg and avg > 0:
|
||||
logger.warning(
|
||||
"OKX fill wait timeout but order filled ordId=%s",
|
||||
ord_id,
|
||||
)
|
||||
return self._fill_from_order_row(inst_id, ord_id, last)
|
||||
if allow_partial and acc > 1e-12 and avg and avg > 0:
|
||||
logger.warning(
|
||||
"OKX IOC partial fill on timeout ordId=%s acc=%s",
|
||||
ord_id,
|
||||
acc,
|
||||
)
|
||||
return self._fill_from_order_row(inst_id, ord_id, last)
|
||||
raise RuntimeError(f"OKX 订单未完全成交 ordId={ord_id} last={last}")
|
||||
|
||||
def sum_fill_fees(self, inst_id: str, ord_id: str) -> tuple[float, str]:
|
||||
|
||||
Reference in New Issue
Block a user