fix(execution): KIS child cancel identity binding, sanitized logging, PG cancel SQL tests
Co-authored-by: genichin <4282227+genichin@users.noreply.github.com>
This commit is contained in:
co-authored by
genichin
parent
f0ac909958
commit
65df10542b
@@ -443,22 +443,70 @@ class KISBroker:
|
||||
# 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":
|
||||
# this case.
|
||||
#
|
||||
# Safety requirements:
|
||||
# - Child must share the parent's canonical identity:
|
||||
# ord_gno_brno (branch), ord_dt (date), pdno (symbol).
|
||||
# Mismatched or ambiguous children → fail-closed as "unknown".
|
||||
# - Correction (정정) children have cncl_yn != "Y"; they are not
|
||||
# treated as cancels here. If a correction child is present with
|
||||
# a new odno the evidence is ambiguous → "unknown" so the slot is
|
||||
# not incorrectly unlocked.
|
||||
parent_brno = str(row.get("ord_gno_brno") or "").strip()
|
||||
parent_dt = str(row.get("ord_dt") or "").strip()
|
||||
parent_pdno = str(row.get("pdno") or "").strip()
|
||||
|
||||
child_cancel_candidates = [
|
||||
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"
|
||||
and str(r.get("ord_gno_brno") or "").strip() == parent_brno
|
||||
and str(r.get("ord_dt") or "").strip() == parent_dt
|
||||
and str(r.get("pdno") or "").strip() == parent_pdno
|
||||
]
|
||||
|
||||
# Correction children: same orgn_odno but cncl_yn != "Y". If present,
|
||||
# the situation is ambiguous — a replacement order may be live.
|
||||
correction_candidates = [
|
||||
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"
|
||||
]
|
||||
|
||||
if len(child_cancel_candidates) > 1:
|
||||
# More than one matching cancel child — identity is ambiguous.
|
||||
return FillStatus(
|
||||
state="unknown",
|
||||
raw={
|
||||
"reason": "child_cancel_ambiguous",
|
||||
"order_visible": True,
|
||||
"inquiry_date": inquiry_date,
|
||||
},
|
||||
)
|
||||
|
||||
if correction_candidates:
|
||||
# A correction/replacement child is present; the original order is
|
||||
# superseded but we cannot safely infer terminal status without
|
||||
# explicit broker confirmation of the replacement identity.
|
||||
return FillStatus(
|
||||
state="unknown",
|
||||
raw={
|
||||
"reason": "correction_child_present_unresolved",
|
||||
"order_visible": True,
|
||||
"inquiry_date": inquiry_date,
|
||||
},
|
||||
)
|
||||
|
||||
if child_cancel_candidates and str(row.get("cncl_yn") or "").strip().upper() != "Y":
|
||||
child_cancel_row = child_cancel_candidates[0]
|
||||
# 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
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user