KIS 계좌에서 직접 매도 주문을 취소하면 poll_fill 이 cncl_yn 을 파싱하지 않아 슬롯이 pending_sell 에 영구히 갇히고 죽은 주문을 무한 폴링하던 버그(issue #9) 정정. 거버넌스: 전부 change-req (신규 REQ 없음, dcy-013 OQ-3). - REQ-012: KIS poll_fill cncl_yn 파싱 → 외부 취소 감지. FillStatus.cancelled /is_partial_cancel 추가. 체결 0 취소→cancelled, 부분 체결 후 취소→partial+cancelled - REQ-013: 집행 _on_partial_cancel — 체결분 원장 1회 + qty 차감(매도 잔여/매수 체결분) + holding 복귀 (OQ-1 A안). 전량 취소는 기존 롤백 경로 재사용 - REQ-018: 부분 체결분 원장 1회 기록 (_record_ledger 재사용) - REQ-031: 잔여 포지션 정합 연계 (slot.qty ↔ 실 잔고, 과매도 방지) - REQ-014: pending stale 워치독 Alert-only (TREX_PENDING_STALE_THRESHOLD, WARN+카운터, 상태 미변경) — 취소 감지 누락 backstop - REQ-015: interface-contract/runtime-flows 정합 전량 취소는 holding 자동 복구(limit_price clear→재매도 시장가, dcy-012 연계). KIS cncl_yn 실 규약은 운영 검증(미지 값 보수 처리 + 워치독 backstop). 검증: 단위 294 passed/32 skipped, 인프라 게이트(PG+RabbitMQ) 326 passed, ruff clean. 신규 test_kis_cancel(httpx stub) + test_execution_cancel(programmable broker). BAT-017 release-candidate. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3.1 KiB
3.1 KiB
BAT-017 Implementation
- Status: completed
- Batch: BAT-017
구현 요약
- dcy-013 KIS 외부 취소 감지(issue #9) + 부분 체결 회계 + pending stale 워치독(Alert-only)을 전부 change-req 로 구현(신규 REQ 없음).
변경 내역
REQ-012 — KIS 취소 감지 + FillStatus 확장
trex_core/application/broker_port.py:FillStatus.cancelled: bool = False+is_partial_cancel(=cancelled and filled_qty > 0).trex_core/adapters/broker/kis.py::poll_fill:cncl_yn파싱(=="Y", 미지 값 보수적 비취소). 체결 0+취소 →state="cancelled"; 부분 체결+취소 →state="partially_filled"+cancelled=True+ execution_id/fee 합성. 전량 체결 경로 불변.
REQ-013/018 — 집행 부분 체결 회계
trex_core/application/execution.py::_poll:is_filled→is_partial_cancel→is_terminal_reject→ else(대기+stale) 분기._on_partial_cancel: 매도PENDING_SELL→HOLDING qty=잔여(cost basis 유지), 매수PENDING_BUY→HOLDING qty=체결분 avg_price=체결가;_record_ledger(REQ-018 재사용)로 체결분 원장 1회 + clear_dispatch + event.r.filled += 1.- 전량 취소(
cancelled, 체결 0)는 기존is_terminal_reject→_rollback(holding 복귀 + clear_dispatch) 재사용.
REQ-014 — pending stale 워치독 (Alert-only)
execution.py:ExecutionService(stale_pending_threshold_sec=...)(<=0/None 비활성),_check_stale_pending(age=now-updated_at, 초과 시 WARN +r.stale_pending += 1, 상태 불변),ExecutionResult.stale_pending.trex_core/runtime/config.py:DaemonConfig.pending_stale_threshold_sec(envTREX_PENDING_STALE_THRESHOLD, default 0).trex_core/runtime/daemon.py: ExecutionService 에 threshold 주입.
REQ-031 — 포지션 정합 연계
_on_partial_cancel이 slot.qty 를 잔여 held 와 일치시켜 정합 입력 유지(과매도 방지). reconcile_positions 코드 변경 없음.
REQ-015 — baseline 문서
docs/interface-contract.md: FillStatus.cancelled/is_partial_cancel + state별 집행 처리 + KIS cncl_yn 매핑. Last Updated → BAT-017.docs/runtime-flows.md: poll 단계 외부 취소(전량→롤백 / 부분→회계) + Alert-only stale 워치독. Last Updated → BAT-017.
테스트
- 신규
tests/adapters/broker/test_kis_cancel.py(httpx stub): 4건 — full cancel→cancelled, zero-fill non-cancel→accepted, partial+cancel→is_partial_cancel(execution_id), full fill 회귀. - 신규
tests/application/test_execution_cancel.py(programmable broker): 4건 — 전량 취소→holding 복귀+clear, 부분 취소→sell 원장 1건+qty 6 잔여+cost basis 1000 유지, stale 워치독 WARN(상태 PENDING_SELL 불변), 비활성 default 무경고.
검증 실행
- 단위:
TREX_BROKER=paper .venv/bin/python -m pytest -q→294 passed / 32 skipped(::test_sandbox_token_and_submitdeselect — LIVE.env환경 의존, 본 batch 무관). - 인프라 게이트:
TREX_POSTGRESQL_TEST_DSN=...(trex_core_test) TREX_RABBITMQ_TEST_URL=... pytest -q→326 passed. ruff check trex_core tests→ All checks passed.