# 0. 문서 상태 이 문서의 현재 상태와 식별 정보를 기록한다. 문서 추적과 승인 흐름에 필요한 최소 메타데이터를 적는다. - 상태: confirmed - 문서 ID: dcy-022_20260610_stuck-pending-order-recovery - 이슈명: 전략 발신 intent 실행 정책 — 주문 timeout(TIF)/슬리피지 허용 + 미체결 고착 백스톱 (issue #21, dcy-020/021 모델 전제) - 작성 시각(KST): 2026-06-10 17:43 KST - 마지막 갱신 시각(KST): 2026-06-10 19:30 KST (confirm-discovery 승인 — OQ-1~4 Answered, 저장소 사실 재확인(cancel_order 미배선·payload 무 timeout/slippage), C형 0 → 상태 confirmed) - 대체됨: 없음 - 후속 Discovery 참조: 없음 - 생성된 REQ 참조: 없음 # 1. 계획 상태 요약 현재 Discovery가 어떤 배경에서 작성되고 있는지, 연계 문서가 있는지, 지금 시점의 해석이 무엇인지 짧게 요약한다. - 연계 문서 현황: - 관련 점수 파일: 미생성 - 관련 TODO/메모: GitHub issue #21(pending 고착). dcy-020(자산 중심 영구 슬롯·slot_id 정본·상태머신 — 토대, BAT-027~030 delivery), dcy-021(포지션 사이징·부분 매도 — BAT-031), dcy-013(stale 워치독 Alert-only + 외부취소 cncl_yn 롤백 선례). - 해석: - 본래 issue #21 은 "미체결 고착을 운영자/코어가 사후 정리"하는 반응적 문제였다. 그러나 더 나은 근원 해법은 **전략이 intent 제출 시 실행 정책(주문 timeout, 슬리피지 허용)을 함께 실어** core 가 이를 집행하는 것이다 — 미체결 주문은 전략이 지정한 timeout 에 자동 취소되어 애초에 고착되지 않는다. - 따라서 본 Discovery 의 **주축**은 intent 실행 정책(timeout=TIF + slippage)이고, 운영자 강제 취소·코어 fallback 은 **백스톱**으로 둔다. 가격(`entry_price`)·청산시간(`forced_close_at`)·수량(`qty`)은 이미 계약에 있으므로 **신규는 timeout 과 slippage 두 가지**다. 브로커 `cancel_order` 는 어댑터에 이미 존재(미배선)라 timeout 집행 배선만 필요하다. # 2. 요구사항 판정 결과 이번 요청이 기존 요구사항과 비교해 신규인지, 유사한지, 중복인지 판단하고 그 근거를 남긴다. - 판정: 신규 - 근거 - dcy-013(REQ-014)은 stale pending 을 **Alert-only**(상태 변경 없음)로 의도 고정했다. 본 주기는 전략이 명시한 실행 정책(timeout)을 core 가 집행하므로 "코어 임의 취소" 우려가 해소되며, 능동적 취소·실행 정책을 정의하는 신규 주기다. - intent 에 실행 정책(timeout/slippage)을 추가하는 것은 매수/매도 계약(REQ-007/021/061)의 확장이며, 기존 dcy 어디에도 정의되지 않은 신규 능력이다. # 3. 문제점의 요약 문제의 현재 상태, 기대 상태, 관련 이해관계자, 제약사항을 정리한다. Discovery의 핵심 맥락을 가장 먼저 이해할 수 있어야 한다. - 현재 상태 - **주문에 실행 정책이 없다**: buy/sell intent payload(`interface-contract.md`)는 `entry_price`(지정가)·`qty`·`forced_close_at`(청산시간)·`stop_loss_price`/`take_profit_price` 는 있으나, **주문 유효시간(TIF/timeout)·슬리피지 허용**을 지정할 수단이 없다. 지정가 미체결 시 무기한 대기. - **미체결 시 무기한 pending**: 집행 루프(`execution.py`)는 poll 결과가 "접수/부분(취소 아님)"이면 상태를 두고 재polling 한다 — 체결·외부취소·거부가 없으면 자산 슬롯이 pending_buy/pending_sell 에 영구 고착(issue #21). - **stale 워치독은 Alert-only**: `_check_stale_pending`(dcy-013/REQ-014)은 임계 초과 시 WARN+카운터만 — 자동 조치 없음. 의도는 "코어가 전략 의도를 몰라 임의 취소 불가"였다. - **브로커 취소 송신 미배선**: 어댑터에 `cancel_order(...)`가 이미 존재(`paper.py`/`kis.py`)하나 어떤 자동/운영자 흐름에도 호출되지 않는다. - **운영자 처분 경로 부재**: `trex-core exit` 는 holding 대상이라 pending 에 적용 불가. - **(dcy-021 후) 부분 매도 고착**: 포지션 사이징 부분 매도 주문도 pending_sell 로 나가 미체결 시 같은 고착 + 보유 슬롯 조정 잠금. - baseline(`docs/interface-contract.md` payload, `docs/runtime-flows.md` 집행) 갱신 대상. - 기대 상태 - 전략이 intent 에 `timeout_sec`(예 60)·`slippage_pct`(예 0.1%)를 실으면, core 가 이를 집행한다 — timeout 초과 미체결 주문은 자동 취소·롤백(고착 예방), slippage 로 보호 지정가를 산출해 과도한 불리 체결을 막는다. - timeout 이 없는(또는 timeout 취소 자체가 실패한) 잔여 고착은 운영자 강제 취소 + (선택) 코어 fallback 으로 처리한다(백스톱). - 가격·청산시간·수량과 함께 실행 정책이 하나의 주문 명세로 통합되어, 전략이 "어떻게 체결할지"를 선언한다. - 이해관계자 - 문서 작성자(AI agent) — Discovery/REQ/Batch 산출물 - 리뷰어/승인자 — 실행 정책 계약·취소 집행·안전성 승인 - 전략 모듈(orb-v1 등) — intent 에 timeout/slippage 발신(외부 repo 발신 측 변경) - 운영 담당자 — 잔여 고착 백스톱 처분·관측 - 제약사항 - 취소·롤백은 단일 writer·멱등·불변식(advisory lock, instance 스코프 dcy-020)을 보존하고, 이미 체결된 부분 수량을 잃지 않아야 한다(dcy-013 부분 취소 회계 계승). - 취소 요청 송신과 실제 취소 확인(cncl_yn/poll)을 분리 — 취소-체결 race 처리. - 슬리피지·timeout 미지정 시 기존 동작(무기한 지정가/시장가)과 호환(하위호환·옵션 필드). ## 주요 사용자 시나리오 사용자 또는 운영자가 실제로 어떤 상황에서 이 변경을 필요로 하는지 대표 시나리오를 적는다. - orb-v1 이 매수 신호를 줄 때 `entry_price`·`qty`·`forced_close_at` 과 함께 `timeout_sec=60`·`slippage_pct=0.1` 을 실어 보낸다. 60초 내 미체결이면 core 가 주문을 취소하고 슬롯을 inactive 로 롤백한다(고착 없음). - 시장가 매수에 `slippage_pct=0.1` 을 주면 core 가 현재가×(1+0.1%) 보호 지정가로 제출해 과도한 불리 체결을 막는다. 밴드 밖이면 미체결 → timeout 취소로 수렴. - 부분 매도(dcy-021)에 timeout 을 주면 미체결 시 자동 취소되어 보유로 되돌아가고 부분 체결분은 보존된다. - (백스톱) timeout 없는 옛 주문이 고착되면 운영자가 `trex-core cancel ` 로 취소·롤백한다. # 4. 이번에 정의할 변경 이번 Discovery에서 다루는 핵심 변경 항목을 목록으로 적는다. 기능, 구조, 규칙 변경을 함께 포함해도 된다. - **intent 실행 정책 필드 추가(주축)**: buy/sell payload 에 optional `timeout_sec`(주문 유효시간, TIF)·`slippage_pct`(슬리피지 허용) 추가. 계약(REQ-007/021/061) 확장. 운영자 config 기본 timeout(per-instance TOML `default_order_timeout_sec`, opt-in) + per-intent override(OQ-4). deprecate 없음. - **timeout 집행**: 집행 루프가 dispatched-pending 주문의 경과시간을 추적해 `timeout_sec` 초과 시 브로커 `cancel_order` 송신 → 취소 확인 후 롤백(pending_buy→inactive / pending_sell→holding, 부분 체결 보존). 기존 어댑터 `cancel_order` 배선. - **슬리피지 집행(OQ-2/3)**: `slippage_pct` 는 `entry_price` 기준 밴드 — 실효 지정가 = 매수 `entry_price*(1+pct)`/매도 `entry_price*(1-pct)`(호가단위 반올림). `entry_price` 없으면 무시(순수 시장가). 즉 `entry_price`=기준/목표, `slippage_pct`=허용폭, 실효 limit=둘의 곱(`entry_price` 단독이면 하드 지정가=현행). - **고착 백스톱**: timeout 미지정/취소 실패 잔여 고착에 운영자 강제 취소(`cancel`) + (선택) 코어 fallback. dcy-013 Alert-only 는 기본 유지. - baseline 문서(`docs/interface-contract.md` payload, `docs/runtime-flows.md` 집행/취소 흐름) 반영. ## 영향 범위 변경이 미칠 코드/문서/운영 범위를 명시해 REQ 정제와 batch planning에서 추정 누락을 줄인다. - 코드 영향: - `trex_core/application/intents.py` / `messaging_handlers.py` — payload `timeout_sec`/`slippage_pct` 파싱·운반, 슬롯/주문에 전달 - `trex_core/application/execution.py` — dispatched-pending 경과시간 추적 + timeout 취소 송신, slippage→limit_price 산출, 취소 확인 후 롤백 - `trex_core/adapters/broker/*` — 기존 `cancel_order` 배선(신규 메서드 아님), 보호 지정가 주문 - `trex_core/domain/slot/service.py` / `models.py` — (필요 시) 주문 정책/제출시각 보유 + 취소 롤백 전이(부분 보존) - `trex_core/runtime/cli.py` / control RPC — 운영자 `cancel` 백스톱 - `trex_core/runtime/config.py`(`DaemonInstanceConfig`) + `config/*.toml` — `default_order_timeout_sec` opt-in 기본값(REQ-048 per-instance TOML 계열, OQ-4) - 문서 영향: - `docs/interface-contract.md`(buy/sell payload 에 timeout/slippage), `docs/runtime-flows.md`(집행·timeout 취소·slippage·백스톱) - 운영 영향: - 미체결 고착 예방으로 활성 capacity 회복. 전략 모듈(외부) 발신 계약 변경 조율 필요. # 5. 요구사항 목록 실제로 구현과 검증으로 이어질 요구사항을 기능/비기능으로 나누어 정리하고, 범위 경계를 명확히 적는다. ## 기능 요구사항 - FR-1: buy/sell intent payload 에 optional `timeout_sec`(주문 유효시간)·`slippage_pct`(슬리피지 허용)를 추가하고 core 가 파싱·운반한다(REQ-007/021/061 확장). core 는 수신 측 먼저 배포 가능(필드 누락 시 무시 — orb-v1 발신과 lockstep 불필요, 하위호환). `timeout_sec` 미지정 시 운영자 config 기본값(per-instance TOML `default_order_timeout_sec`, 미설정=OFF=무기한)을 적용하고 per-intent 값이 override 한다(OQ-4). `slippage_pct` 는 기본값 없음(미지정=보호밴드 없음). - FR-2: 집행 루프는 dispatched-pending 주문이 `timeout_sec` 를 초과하면 브로커 `cancel_order` 를 송신하고, 취소 확인 후 슬롯을 롤백한다(pending_buy→inactive / pending_sell→holding). 이미 체결된 부분 수량은 보존한다. - FR-3: `slippage_pct` 는 **기준가(`entry_price`)가 있을 때만** 적용한다(OQ-2) — 실효 지정가 = 매수 `entry_price*(1+pct)` / 매도 `entry_price*(1-pct)`(KRX 호가단위로 반올림: 체결 보장 측 — 매수 올림/매도 내림). `entry_price` 가 없으면(순수 시장가) `slippage_pct` 는 **무시**(무제한 시장가). 밴드 밖 미체결은 timeout 취소(FR-2)로 수렴한다. - FR-4: 취소 요청 송신과 실제 취소 확인(cncl_yn/poll)을 분리한다 — 취소가 거부(이미 체결)되면 정상 체결 경로로 수렴하고 슬롯을 임의 변경하지 않는다(취소-체결 race). - FR-5: (백스톱) 운영자가 자산 슬롯의 고착 pending 을 강제 취소·롤백할 수 있다(timeout 미지정/취소 실패 대비). dcy-013 Alert-only 기본 동작은 회귀 없이 유지한다. - FR-6: `docs/interface-contract.md`·`docs/runtime-flows.md` 에 실행 정책·timeout 취소·slippage·백스톱을 반영한다. ## 비기능 요구사항 성능, 운영성, 일관성, 자동화 안정성 등 기능 외 요구사항을 적는다. - NFR-1: 안전·멱등 — 취소·롤백은 단일 writer·advisory lock(instance 스코프)·멱등을 보존하고, 중복 취소/경합에서 부분 체결분을 잃지 않는다. - NFR-2: 비차단 — timeout 취소 송신 실패·지연이 집행 루프·다른 슬롯 처리를 막지 않는다(best-effort, 요청/확인 분리). - NFR-3: 하위호환 — `timeout_sec`/`slippage_pct` 미지정 시 기존 매수/매도 경로와 byte 동등(회귀 없음). - NFR-4: 관측성 — timeout 취소·slippage 적용·백스톱 처분이 로그+카운터로 가시화된다(stale 카운터 계승). ## 범위 경계 이번 반복에서 반드시 포함할 범위와 명시적으로 제외할 범위를 구분한다. - In Scope - intent `timeout_sec`/`slippage_pct` 계약 + core 집행(timeout 취소·보호 지정가) - 브로커 `cancel_order` 배선 + 취소-체결 race 처리(부분 보존) - 운영자 강제 취소 백스톱 + runtime-flows/interface-contract 정합 - Out of Scope - 브로커 `cancel_order` 신규 구현(이미 존재 — 배선만) - 전략 모듈(orb-v1) 내부 신호/사이징 로직(외부 repo, 발신 계약만 합의) - IOC/FOK 등 네이티브 TIF 분류 확장 — **후속 주기**(본 주기는 코어-타이밍 `timeout_sec` 만, OQ-1 Answered) # 6. 리스크/가정 목록 불확실성과 전제 조건을 분리해서 적는다. 리스크는 영향과 완화 방안을, 가정은 현재 판단의 전제를 기록한다. ## 리스크 - R-1 (High): 취소 요청과 실제 체결의 경쟁(race) — timeout 취소 송신 직전/직후 체결되면 슬롯과 브로커 실제가 어긋날 수 있다. - 완화 방안: 취소 송신 후 즉시 롤백하지 말고 cncl_yn/poll 확인 후 전이(FR-4). 이미 체결 시 정상 경로로 수렴. - R-2 (Medium): 슬리피지 보호 지정가 산출 오류로 의도와 다른 체결/미체결. - 완화 방안: 방향별 부호·산출식을 명확히 정의(OQ-2), 단위 테스트. 밴드 밖은 timeout 취소로 안전 수렴. - R-3 (Medium): 부분 체결분 처리 오류로 회계/포지션 불일치. - 완화 방안: dcy-013 부분 취소 회계 선례 재사용 + 브로커 정본 대조(REQ-062) + 테스트. ## 가정 아직 확정되지 않았지만 현재 문서 작성의 기반이 되는 전제를 적는다. - A-1: dcy-020(자산 중심 영구 슬롯·slot_id 정본)·dcy-021(부분 매도)이 구현 완료된 상태다(BAT-027~031). - A-2: 브로커 어댑터 `cancel_order` 가 동작하며 cncl_yn 으로 취소 결과를 관측 가능하다(dcy-013). - A-3: 전략 모듈이 intent 에 실행 정책 필드를 발신하도록 합의·변경 가능하다(외부 repo). ## 오픈 질문 답을 아직 모르는 항목을 적는다. 추정으로 덮지 말고 REQ 초안 작성 이전에 확인할 질문으로 남긴다. - OQ-1: timeout 시맨틱 범위 — 단순 `timeout_sec`(미체결 시 취소)만 우선 도입하고 IOC/FOK 등 TIF 분류는 후속으로 둘지, 본 주기에 함께 정의할지. - 상태: Answered (2026-06-10, 사용자 결정) - 결정: **timeout 만 도입** — `timeout_sec`(N초 미체결 시 코어가 `cancel_order` 송신·롤백, GTD/timeout 성격, 브로커 무관). **IOC/FOK 등 네이티브 TIF 는 후속 주기**(브로커 네이티브 플래그·부분 체결 규칙 별도). 반영: §4, FR-1/2, §5 범위 경계. - OQ-2: 슬리피지 산출 규칙. - 상태: Answered (2026-06-10, 사용자 결정) - 결정: `slippage_pct` 는 **기준가 `entry_price` 가 있을 때만** 적용 — 실효 지정가 = 매수 `entry_price*(1+pct)` / 매도 `entry_price*(1-pct)`(KRX 호가단위 반올림, 체결 보장 측). `entry_price` 없으면(순수 시장가) **무시**(무제한). 시장가엔 곱할 기준이 없다는 점에서 "기준가 없으면 슬리피지 없음". 반영: FR-3, §4. - OQ-3: 필드 상호작용/우선순위. - 상태: Answered (2026-06-10, OQ-2 동반 결정) - 결정: `entry_price`=기준/목표, `slippage_pct`=허용폭 → 실효 limit = `entry_price*(1±pct)`. `entry_price` 단독(slippage 없음)=하드 지정가(현행). `timeout_sec`=주문 유효시간(미체결 취소), `forced_close_at`=포지션 만기 청산(별개 축, REQ-066/037). 네 필드 역할 분리 명확. 반영: FR-3, §4, payload 계약(REQ 단계 interface-contract). - OQ-4: 계약/발신 위치 — `timeout_sec`/`slippage_pct` 를 buy/sell payload optional 로 추가(core 수신)하고, 전략 모듈(orb-v1) 발신 변경을 어떻게 조율할지(기본값·하위호환·deprecate 정책). - 상태: Answered (2026-06-10, 사용자 결정) - 결정: (1) buy/sell payload 에 **optional** 필드 추가 — core 수신 측 먼저 배포(누락 시 무시), orb-v1 발신은 별도 일정(lockstep 불필요·하위호환). (2) `timeout_sec` 기본값 = **운영자 config opt-in**(per-instance TOML `default_order_timeout_sec`, 미설정=OFF=현행), per-intent override. `slippage_pct` 기본값 없음. (3) **deprecate 없음**(전부 신규 optional 필드, `entry_price` 등 기존 필드 유지). 반영: FR-1, §4, 영향 범위(config). # 7. 초기 성공 기준 이 변경이 성공했다고 판단할 기준을 측정 가능하게 정의한다. 가능하면 데이터 출처까지 연결한다. - S-1: `timeout_sec=N` 을 실은 intent 가 N초 내 미체결이면 core 가 주문을 취소하고 슬롯을 롤백하며 부분 체결분을 보존한다(고착 없음). - S-2: `slippage_pct` 가 주어진 주문이 방향별 보호 지정가로 제출되고, 밴드 밖이면 미체결 → timeout 취소로 수렴한다. 미지정 주문은 기존 동작과 동일(회귀 없음). - S-3: 취소-체결 race 에서 이미 체결된 경우 정상 체결 경로로 수렴(임의 롤백 없음). 백스톱 운영자 `cancel` 로 timeout 없는 고착도 해소된다. ## 측정 방식 및 데이터 출처 각 성공 기준을 어떻게 측정할지와 어떤 데이터나 문서를 근거로 쓸지 적는다. - S-1 측정 방식: timeout 경과 픽스처 → 취소·롤백·부분 보존 단위/통합 / 데이터 출처: 슬롯 전이, 브로커 cancel_order/poll - S-2 측정 방식: 시장가/지정가 + slippage 산출 단언 + 미지정 회귀 / 데이터 출처: OrderRequest limit_price, 슬롯 - S-3 측정 방식: 취소-체결 동시 시나리오 수렴 + 운영자 cancel 백스톱 / 데이터 출처: poll_fill/cncl_yn, control RPC # 8. REQ로 넘기기 전 확인 체크 REQ 초안 작성 단계로 넘기기 전에 무엇이 확정되어야 하는지 확인한다. 미완료 항목은 후속 결정이 필요함을 뜻한다. - [x] 범위(In Scope/Out of Scope) 확정 - 기준: TIF 범위(OQ-1 timeout만)·슬리피지 규칙(OQ-2 entry_price 밴드) 합의 → In/Out 경계 명확 - [x] 성공 지표의 측정 방식/데이터 출처 확정 - 기준: S-1~3 측정 시나리오·데이터 출처가 테스트 가능한 형태로 정의됨 - [x] High 영향 리스크 우선순위 및 해소 책임자 지정 - 기준: R-1(취소-체결 race)은 FR-4(취소 송신·확인 분리)로 해소 방향 확정. 해소 책임은 구현 batch. - [x] 모든 오픈 질문(OQ) `Open` 상태 없음 확인 - 기준: OQ-1~4 전부 Answered (2026-06-10 사용자 결정) - [x] Freeze 확정 담당자(CONFIRMED_BY) 지정 - 기준: 승인자 Justin Ko confirm-discovery 수행 # 9. 파일 처리 결과 이 Discovery 문서 생성/갱신 결과와 참조 문서를 기록한다. 이후 추적과 리뷰 시 근거로 사용한다. - 처리 결과: 갱신 (주축 재작성) - 생성 경로: docs/discovery/dcy-022_20260610_stuck-pending-order-recovery.md - 참조 문서: - docs/discovery/dcy-020_20260610_asset-centric-slot-model.md (자산 중심 영구 슬롯·slot_id 정본·상태머신 — 토대) - docs/discovery/dcy-021_20260610_position-sizing-target-intent.md (부분 매도 — 실행 정책 적용 대상) - docs/discovery/dcy-013_20260608_kis-external-cancel-pending-sell-stuck.md (stale Alert-only + cncl_yn 롤백·부분 취소 회계 선례) - docs/interface-contract.md(buy/sell payload), docs/runtime-flows.md (baseline — 갱신 대상) - GitHub issue #21(pending 고착) # 10. 사용자 결정 필요 항목 요약 사용자나 승인자가 결정해야 하는 항목만 모아 정리한다. 결정, 확인, 데이터 요청을 구분해서 적는다. ## DECIDE - 없음 (OQ-1~4 전부 Answered — §6 참조) ## CONFIRM - 없음 (전제 dcy-020/021 구현은 BAT-027~031 로 충족 — A-1) ## DATA - 없음 (외부 데이터 의존 없음 — 내부 도메인/브로커 계약 + 전략 발신 합의) # 11. Discovery Freeze REQ 초안 작성 인계 시점의 확정 플래그와 섹션 참조만 기록한다. 내용 재작성 없이 원본 섹션을 링크로 대신한다. ## 섹션 참조 - 범위: [# 5. 요구사항 목록 > 범위 경계](#5-요구사항-목록) - 리스크/오픈 질문: [# 6. 리스크/가정 목록](#6-리스크가정-목록) - 성공 기준: [# 7. 초기 성공 기준](#7-초기-성공-기준) - 인계 전 확인: [# 8. REQ로 넘기기 전 확인 체크](#8-req로-넘기기-전-확인-체크) - 사용자 결정 필요: [# 10. 사용자 결정 필요 항목 요약](#10-사용자-결정-필요-항목-요약) ## Freeze 플래그 - Handoff Decision: 승인 - Handoff Rationale: OQ-1~4 전부 Answered, C형 플레이스홀더 0, 저장소 사실 재확인 일치(cancel_order 어댑터 존재·미배선, payload 무 timeout/slippage, stale Alert-only). REQ Drafting 인계 가능. - Ready for REQ Drafting: true - Confirmed By: Justin Ko - Confirmed At (KST): 2026-06-10 19:30 KST