# 0. 문서 상태 - 상태: confirmed - 문서 ID: dcy-041_20260729_broker-uncertainty-and-adapter-retirement - 이슈명: Core 브로커 불확실 상태 구분과 로컬 KIS 어댑터 축소 - 작성 시각(KST): 2026-07-29 16:59:16 KST - 마지막 갱신 시각(KST): 2026-07-29 17:54:02 KST - 대체됨: 없음 - 선행 Discovery: - dcy-013_20260608_kis-external-cancel-pending-sell-stuck - dcy-022_20260610_stuck-pending-order-recovery - dcy-039_20260727_intent-venue-end-to-end-propagation - 후속 Discovery 참조: 없음 - GitHub 이슈: #44, #45 # 1. 계획 상태 요약 - 해석: - Core에는 intent 거래소 전달, 외부 취소 감지, 결과불명 주문 복구가 일부 구현돼 있다. - 남은 공용 공백은 `AUTO` 거래소의 실제 제출 거래소 확정, 취소 요청 결과의 명시적 상태, 짧은 계좌 스냅샷 복구다. - 목표: - 공용 기능을 Core가 소유하고 로컬 KIS 호환 어댑터는 KIS 중계만 남도록 축소한다. - 호출 실패를 실제 불일치로 오판하거나 결과불명 주문을 재전송하지 않는다. # 2. 요구사항 판정 결과 - 판정: 신규 운영 후속 Discovery - 근거: - 선행 Discovery들은 이미 확정·구현됐지만 2026-07-28~29 실전 운영에서 취소 응답과 계좌조회 불확실 상태가 다시 확인됐다. - 이번 변경은 기존 기능의 문구 보강이 아니라 Core와 외부 호환 어댑터의 책임 경계를 바꾸므로 독립 추적한다. # 3. 문제점의 요약 ## 현재 상태 - `dcy-039`는 intent의 `KRX|NXT|SOR` 거래소를 슬롯과 주문·취소까지 보존한다. - 전략이 거래소를 보내지 않으면 기본 KRX이므로, 전략을 바꾸지 않고 08:00 NXT 주문을 보내는 `AUTO` 계약은 없다. - `BrokerPort.cancel_order()`는 참/거짓만 반환하므로 요청 접수, 명시적 거부, 응답유실을 구분하지 못한다. - KIS `get_balance()`는 호출 실패를 검증된 빈 잔고와 구분해 예외로 올리지만, 마지막 정상 계좌 스냅샷의 출처·시각·만료 정책은 Core에 없다. - 로컬 `nxt_adapter`가 주문번호-거래소와 짧은 잔고 스냅샷을 별도 SQLite에 저장한다. ## 기대 상태 - `AUTO` 주문은 KIS 브로커 경계에서 KST 세션에 따라 실제 거래소를 확정하고 슬롯·취소·Core 원장에 보존한다. - 취소 요청 결과는 `요청 접수`, `명시적 거부`, `결과불명`으로 구분하고 최종 취소·체결은 기존 체결조회로 확정한다. - 마지막 정상 계좌 스냅샷은 계정 범위와 캡처 시각을 포함해 원자적으로 저장하고 짧은 유효시간 안에서만 사용한다. - 신규매수와 위험축소 매도의 허용시간을 분리하고 결과불명 주문은 종목·방향 범위에서 재전송을 차단한다. # 4. 이번에 정의할 변경 1. `AUTO` 거래소를 실제 KRX/NXT로 확정하고 재시작 후에도 원 주문 거래소를 복구한다. 2. 취소 API 결과를 명시적 상태값으로 바꾸고 응답유실 시 재취소·재주문을 자동 반복하지 않는다. 3. 계정별 마지막 정상 잔고 스냅샷과 방향별 만료 정책을 Core/KIS 브로커 계층에 둔다. # 5. 요구사항 목록 ## 기능 요구사항 - FR-1: `AUTO|KRX|NXT|SOR`를 입력받고 `AUTO`는 KST 세션에 따라 실제 KRX/NXT로 결정한다. 결정 불가능 시간은 KRX로 대체하지 않고 거부한다. - FR-2: 실제 제출 거래소를 슬롯과 Core 체결 전달 대기 원장에 저장하며 취소·재시작 복구에서 재사용한다. - FR-3: 취소 요청은 `requested|rejected|unknown` 결과를 반환한다. `requested`는 최종 취소가 아니며 체결조회가 `cancelled|filled`를 확정한다. - FR-4: `unknown` 취소 결과와 결과불명 주문은 자동 재주문하지 않고 기존 주문 조회로만 수렴한다. - FR-5: 성공한 계좌조회 영역만 계정 해시·독립 캡처 시각과 함께 원자 저장한다. 보유수량과 주문가능금액 중 한쪽 실패가 다른 정상값을 덮지 않는다. - FR-6: 일시적 전송 오류에만 미만료 스냅샷을 사용한다. 계정 불일치·만료·응답 오류는 차단한다. - FR-7: 신규매수와 위험축소 매도의 스냅샷 최대 나이를 분리하며 KIS 복구 후 다음 백그라운드 조회가 정상값으로 교체한다. ## 비기능 요구사항 - NFR-1: 동일 intent와 동일 결과불명 주문의 중복 제출은 0건이어야 한다. - NFR-2: 기존 KRX 명시 주문, PaperBroker, 전략 wire의 미지정 필드 호환성을 보존한다. - NFR-3: 토큰·키·계좌번호·원본 KIS 응답은 로그·문서·테스트 산출물에 남기지 않는다. - NFR-4: DB 마이그레이션은 멱등이고 기존 슬롯·체결 전달 대기 원장을 보존한다. ## 범위 경계 - In Scope: - TREX Core의 KIS 브로커, 주문·취소 실행, 슬롯·계좌 스냅샷 영속화, 회귀·장애·재시작 테스트. - Out of Scope: - Portfolio Hub API·스키마 변경, 전략 자금배분, 계정관리 대시보드, 토큰 갱신, NXT 분봉 수집, 수동 보유수량 분리. # 6. 리스크/가정 목록 - R-1: 취소 요청 접수를 취소 확정으로 처리하면 체결 경쟁에서 중복매수가 발생할 수 있다. - 완화: 슬롯은 pending을 유지하고 체결조회만 최종 상태를 전이한다. - R-2: 오래된 계좌 스냅샷이 신규매수를 허용하면 주문가능금액을 초과할 수 있다. - 완화: 신규매수 유효시간을 짧게 두고 수량·금액 한도를 기존 전략 intent 이하로 제한한다. - R-3: `AUTO` 실제 거래소가 영속화되지 않으면 재시작 후 취소가 잘못된 거래소로 갈 수 있다. - 완화: 브로커 응답 직후 슬롯 저장을 주문 접수의 완료 조건으로 둔다. - A-1: KST 세션은 NXT 08:00~08:49:59, KRX 09:00:30~15:29:59, NXT 15:30~20:00으로 운영한다. - A-2: 미확인주문 상태는 기존 슬롯의 broker_order_id와 결과불명 제출 메타데이터가 정본이다. - 오픈 질문: 없음. 사용자가 2026-07-29 핵심 3개 범위와 순차 구현을 승인했다. # 7. 초기 성공 기준 - S-1: 거래소 미지정 전략 intent가 08:00 KST에 NXT로 제출되고 재시작 후 NXT로 취소된다. - S-2: 취소 응답유실은 `unknown`으로 남고 같은 주문의 자동 재취소·재매수가 발생하지 않는다. - S-3: KIS 계좌조회 전송 실패에서 미만료 동일계정 스냅샷만 사용하며 신규매수·매도 유효시간이 각각 적용된다. - S-4: 스냅샷 만료 후 신규매수는 차단되고 보유분 매도·체결조회·원장복구는 독립적으로 검증된다. - S-5: 단위→회귀→가상계약→가상종단간→오류주입→재시작→원장대조가 모두 통과한다. # 8. REQ로 넘기기 전 확인 체크 - [x] 기존 Core와 로컬 어댑터의 중복 책임을 코드로 확인했다. - [x] GitHub #44·#45의 완료 기준과 범위를 확인했다. - [x] 실전 반영 전 가상 검증과 원복 게이트를 고정했다. - [x] 사용자 결정이 필요한 오픈 질문이 없다. # 9. 파일 처리 결과 - 처리 결과: 생성 - 생성 경로: `docs/discovery/dcy-041_20260729_broker-uncertainty-and-adapter-retirement.md` - 참조: - `trex_core/application/broker_port.py` - `trex_core/application/execution.py` - `trex_core/adapters/broker/kis.py` - `trex_core/application/snapshot_scheduler.py` - `docker-local/KIS_ADAPTER_REMOVAL_CONTRACT.md` # 10. 사용자 결정 필요 항목 요약 - 없음. 사용자가 핵심 범위, 기능별 브랜치, Docker 선검증, 서버 차이 검증, 실전 적용 후 어댑터 축소 순서를 승인했다. # 11. Discovery Freeze - Handoff Decision: 승인 - Handoff Rationale: Core의 기존 거래소 전달·결과불명 주문 처리와 남은 취소·계좌조회 공백을 재확인했고, 사용자 승인 범위와 충돌하는 오픈 질문이 없다. - Ready for REQ Drafting: true - Confirmed By: 형님 - Confirmed At (KST): 2026-07-29 17:01:50 KST # 12. 구현 증거 - `AUTO` 실제 거래소 확정·주문번호 동시 저장·재시작/취소 재사용: 구현 완료. - 취소 `requested|rejected|unknown` 영속화와 poll 전용 최종확정: 구현 완료. - 계정 해시·독립 TTL·백그라운드 단일 소유자·매수/매도 분리: 구현 완료. - 계정 해시는 KIS endpoint·계좌·상품·app identity를 함께 묶어 실전/모의·자격증명 출처를 분리한다. - 동일 instance advisory lock 안에서 현금 예약검사와 pending 전이를 원자 처리한다. - timeout·운영자 취소 모두 compare-and-set 예약으로 동일 주문 재취소를 차단한다. - KIS 현금 권위 필드: `inquire-psbl-order.output.nrcvb_buy_amt`. - 수동 혼합 보유 계정: `OwnershipAwareBroker` 투영 뒤 스냅샷을 생성하여 전략 소유 수량만 매도 허용. - 운영 기본값: 기능 OFF, 매수 600초, 매도 3600초, 갱신 300초. - 검증: 로컬 903 passed, 격리 Docker PostgreSQL/RabbitMQ 953 passed, 공용 가상 인증 7단계 PASS. - 검증 문서: `docs/validation/dcy-041-core-adapter-gap-closure-20260729.md` - 실전 반영: 가상 검증과 사용자 별도 승인 전까지 미적용.