BAT-032 (dcy-022): timeout_sec/slippage_pct 실행 정책, 미체결 자동 취소, 보호 지정가, `trex-core cancel` 백스톱. 마이그레이션 012. BAT-033: `trex-core enter --qty` 필수 검증 — 미지정 시 RPC 전 즉시 오류. BAT-034 (dcy-023): allocated_capital 죽은 필드 전 경로 제거 (domain·intent· persistence·snapshot·CLI). 마이그레이션 013(비가역 DROP). 외부 발신자 unknown key 무시 수신 의미론 적용. REL-006 confirmed: BAT-026~034 통합 릴리즈, pre-release smoke 증거 포함. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
21 KiB
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.mdpayload,docs/runtime-flows.md집행) 갱신 대상.
- 주문에 실행 정책이 없다: buy/sell intent payload(
- 기대 상태
- 전략이 intent 에
timeout_sec(예 60)·slippage_pct(예 0.1%)를 실으면, core 가 이를 집행한다 — timeout 초과 미체결 주문은 자동 취소·롤백(고착 예방), slippage 로 보호 지정가를 산출해 과도한 불리 체결을 막는다. - timeout 이 없는(또는 timeout 취소 자체가 실패한) 잔여 고착은 운영자 강제 취소 + (선택) 코어 fallback 으로 처리한다(백스톱).
- 가격·청산시간·수량과 함께 실행 정책이 하나의 주문 명세로 통합되어, 전략이 "어떻게 체결할지"를 선언한다.
- 전략이 intent 에
- 이해관계자
- 문서 작성자(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 <slot_id>로 취소·롤백한다.
4. 이번에 정의할 변경
이번 Discovery에서 다루는 핵심 변경 항목을 목록으로 적는다. 기능, 구조, 규칙 변경을 함께 포함해도 된다.
- intent 실행 정책 필드 추가(주축): buy/sell payload 에 optional
timeout_sec(주문 유효시간, TIF)·slippage_pct(슬리피지 허용) 추가. 계약(REQ-007/021/061) 확장. 운영자 config 기본 timeout(per-instance TOMLdefault_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.mdpayload,docs/runtime-flows.md집행/취소 흐름) 반영.
영향 범위
변경이 미칠 코드/문서/운영 범위를 명시해 REQ 정제와 batch planning에서 추정 누락을 줄인다.
- 코드 영향:
trex_core/application/intents.py/messaging_handlers.py— payloadtimeout_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_secopt-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 TOMLdefault_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 정합
- intent
- 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 TOMLdefault_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 초안 작성 단계로 넘기기 전에 무엇이 확정되어야 하는지 확인한다. 미완료 항목은 후속 결정이 필요함을 뜻한다.
- 범위(In Scope/Out of Scope) 확정
- 기준: TIF 범위(OQ-1 timeout만)·슬리피지 규칙(OQ-2 entry_price 밴드) 합의 → In/Out 경계 명확
- 성공 지표의 측정 방식/데이터 출처 확정
- 기준: S-1~3 측정 시나리오·데이터 출처가 테스트 가능한 형태로 정의됨
- High 영향 리스크 우선순위 및 해소 책임자 지정
- 기준: R-1(취소-체결 race)은 FR-4(취소 송신·확인 분리)로 해소 방향 확정. 해소 책임은 구현 batch.
- 모든 오픈 질문(OQ)
Open상태 없음 확인- 기준: OQ-1~4 전부 Answered (2026-06-10 사용자 결정)
- 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. 요구사항 목록 > 범위 경계
- 리스크/오픈 질문: # 6. 리스크/가정 목록
- 성공 기준: # 7. 초기 성공 기준
- 인계 전 확인: # 8. REQ로 넘기기 전 확인 체크
- 사용자 결정 필요: # 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