fix(execution): cancel SQL nullable type error, KIS child cancel detection, worker exception logging
Co-authored-by: genichin <4282227+genichin@users.noreply.github.com>
This commit is contained in:
co-authored by
genichin
parent
2b412fd786
commit
f0ac909958
@@ -440,6 +440,25 @@ class KISBroker:
|
||||
"inquiry_date": inquiry_date,
|
||||
},
|
||||
)
|
||||
# dcy-013 ext: KIS sometimes records an external cancel as a separate
|
||||
# child row with orgn_odno=<original> and cncl_yn="Y" rather than
|
||||
# setting cncl_yn on the original row. Scan sibling rows to detect
|
||||
# this case. Correction (정정) children will NOT have cncl_yn="Y" so
|
||||
# they are not misclassified as cancels here.
|
||||
child_cancel_row = next(
|
||||
(
|
||||
r
|
||||
for r in rows
|
||||
if (str(r.get("orgn_odno") or "").strip().lstrip("0") or "0") == normalized_odno
|
||||
and str(r.get("cncl_yn") or "").strip().upper() == "Y"
|
||||
),
|
||||
None,
|
||||
)
|
||||
if child_cancel_row is not None and str(row.get("cncl_yn") or "").strip().upper() != "Y":
|
||||
# Inject the cancel signal from the child row into the parent row so
|
||||
# _fill_status_from_row can treat it as cancelled, while keeping
|
||||
# the original row's fill quantities authoritative.
|
||||
row = {**row, "cncl_yn": "Y", "_child_cancel_odno": str(child_cancel_row.get("odno") or "")}
|
||||
status = self._fill_status_from_row(
|
||||
row, side=side, today=inquiry_date, fallback_odno=odno
|
||||
)
|
||||
|
||||
@@ -385,20 +385,37 @@ class PostgreSQLSlotRepository:
|
||||
*,
|
||||
retry_before: Optional[datetime] = None,
|
||||
) -> bool:
|
||||
"""Atomically reserve first cancel or an authoritative backoff-gated retry."""
|
||||
"""Atomically reserve first cancel or an authoritative backoff-gated retry.
|
||||
|
||||
Two separate SQL paths avoid nullable-placeholder type-inference errors
|
||||
(PostgreSQL IndeterminateDatatype when binding None to %s IS NOT NULL).
|
||||
"""
|
||||
now = datetime.now(tz=timezone.utc)
|
||||
with self._psycopg.connect(self._dsn, autocommit=False) as conn:
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(
|
||||
"UPDATE daily_slot SET cancel_state='unknown', cancel_requested_at=%s, "
|
||||
"cancel_attempts=cancel_attempts+1, updated_at=%s "
|
||||
"WHERE slot_id=%s AND broker_order_id=%s "
|
||||
"AND status IN ('pending_buy','pending_sell') "
|
||||
"AND (cancel_state IS NULL OR "
|
||||
"(%s IS NOT NULL AND cancel_state IN ('rejected','unknown') "
|
||||
"AND cancel_attempts < 4 AND cancel_requested_at <= %s))",
|
||||
(now, now, slot_id, broker_order_id, retry_before, retry_before),
|
||||
)
|
||||
if retry_before is None:
|
||||
# First reservation: only match when no cancel has been
|
||||
# attempted yet (cancel_state IS NULL).
|
||||
cur.execute(
|
||||
"UPDATE daily_slot SET cancel_state='unknown', cancel_requested_at=%s, "
|
||||
"cancel_attempts=cancel_attempts+1, updated_at=%s "
|
||||
"WHERE slot_id=%s AND broker_order_id=%s "
|
||||
"AND status IN ('pending_buy','pending_sell') "
|
||||
"AND cancel_state IS NULL",
|
||||
(now, now, slot_id, broker_order_id),
|
||||
)
|
||||
else:
|
||||
# Retry: backoff-gated, only match retryable states.
|
||||
cur.execute(
|
||||
"UPDATE daily_slot SET cancel_state='unknown', cancel_requested_at=%s, "
|
||||
"cancel_attempts=cancel_attempts+1, updated_at=%s "
|
||||
"WHERE slot_id=%s AND broker_order_id=%s "
|
||||
"AND status IN ('pending_buy','pending_sell') "
|
||||
"AND cancel_state IN ('rejected','unknown') "
|
||||
"AND cancel_attempts < 4 "
|
||||
"AND cancel_requested_at <= %s",
|
||||
(now, now, slot_id, broker_order_id, retry_before),
|
||||
)
|
||||
ok = cur.rowcount == 1
|
||||
conn.commit()
|
||||
return ok
|
||||
|
||||
@@ -409,7 +409,7 @@ class Daemon:
|
||||
try:
|
||||
self.execution.run_once()
|
||||
except Exception: # noqa: BLE001 - loop must survive transient errors
|
||||
continue
|
||||
_log.exception("execution loop error — worker continues")
|
||||
|
||||
def _ledger_loop(self) -> None:
|
||||
while not self.stop.wait(self.config.ledger_interval_sec):
|
||||
|
||||
Reference in New Issue
Block a user