Files
trex-core/docs/batches/bat-017_20260608_external-cancel-detection-stale-watchdog/implementation.md
T
genichinandClaude Opus 4.8 4746f74aae fix(broker): dcy-013 KIS 외부 취소 감지 + 부분 체결 회계 + stale 워치독 (BAT-017)
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>
2026-06-08 14:11:49 +09:00

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(env TREX_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_submit deselect — 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.