dcy-008(비상 정지/kill-switch) OQ-2(별도 테이블/store)·OQ-4(수동 해제 resume_kill_switch) 결정 → confirm-discovery(confirmed) → draft-req → confirm-req(5개 Approved). - REQ-043 (Interface): trex.pause.req → trex.control.req 제어 채널 + action dispatch (OQ-1) - REQ-044 (Data): day-scoped killswitch_exclusion 테이블 + store + 005 마이그레이션 (OQ-2) - REQ-045 (Interface): kill_switch 실행 — 범위별 holding 순차 sell() + idempotency + 이력 (NFR-1/2 흡수) - REQ-046 (Interface): 당일 제외 masking + 다음 거래일 자동복귀 + resume_kill_switch (OQ-4, NFR-3 흡수) - REQ-047 (Documentation): baseline 3문서 정합 OQ-3(pending_buy 롤백)은 REQ Deferred. srs index Next ID REQ-048, Register +5, 요약 갱신. dcy-008 생성된 REQ 참조 기록. dcy-009(멀티 인스턴스 RabbitMQ, draft)는 별도 주제로 제외. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
19 KiB
19 KiB
0. 문서 상태
- 상태: confirmed
- 문서 ID: dcy-008_20260607_emergency-stop-kill-switch
- 이슈명: 비상 정지/강제 청산(kill-switch) — portfolio/전역 슬롯 청산 + 당일 전략 제외
- 작성 시각(KST): 2026-06-07
- 마지막 갱신 시각(KST): 2026-06-07 11:11 (draft-req: REQ-043~047 생성)
- 대체됨: 없음
- 후속 Discovery 참조: 없음
- 생성된 REQ 참조: REQ-043, REQ-044, REQ-045, REQ-046, REQ-047
1. 계획 상태 요약
- 연계 문서 현황:
- 관련 참조 파일: GitHub #3 (feat: 비상 정지/강제 청산(kill-switch) — portfolio/전역 단위 슬롯 청산 + 당일 전략 제외)
- 관련 TODO/메모: dcy-007 Out of Scope "운영자 수동 즉시 청산(kill-switch): 별도 Discovery(trex-core#3 기반)" 명시
- 해석:
- dcy-007(core 자율 안전 매도)이 자동 조건 기반 청산(워치독 만료 + 가격/시간 트리거)을 구현했지만, 운영자가 즉시 개입할 수 있는 비상 정지 수단은 범위에서 제외됐다. 기존 pause(REQ-032)는 신규 매매만 중단하고 현재 holding 포지션을 청산하지 않으므로 비상 시 실제 보호 수단이 되지 못한다.
- 이번 Discovery는 (1) 운영자가 명령 한 번으로 portfolio/전역 단위 holding 슬롯을 즉시 청산하고, (2) 당일 전략 실행을 중단시키며, (3) 다음 거래일에 자동 복귀하는 비상 정지 기능의 범위·제약·오픈 질문을 정의한다.
2. 요구사항 판정 결과
- 판정: 신규
- 근거
- dcy-007이 명시적으로 "운영자 수동 즉시 청산(kill-switch): 별도 Discovery(trex-core#3 기반)"를 Out of Scope로 분리했다.
- 기존 pause(REQ-032)는 holding 슬롯을 청산하지 않으며, day-scoped 제외도 지원하지 않는다. 기능 성격이 근본적으로 다르다.
3. 문제점의 요약
- 현재 상태
- trex-core는
pause/resumeRPC(REQ-032,trex.pause.req)로 신규 매매를 중단할 수 있으나, 현재holding슬롯을 즉시 매도하는 운영자 트리거가 없다(trex_core/adapters/persistence/postgresql.py,PostgreSQLPauseStore). - 자율 안전 매도(dcy-007, REL-003)는 워치독 만료 + 가격·시간 조건이 충족된 경우에만 청산하며, 운영자가 즉시 개입하는 비상 경로가 없다.
- kill-switch를 위한 슬롯 청산 경로(
SlotService.sell(), REQ-036/BAT-010)와 실행 인프라(outbox, execution worker)는 이미 구현되어 있다. 호출 진입점만 없는 상태다.
- trex-core는
- 기대 상태
- 운영자가 RPC 메시지 또는 CLI 명령으로 즉시 모든(또는 특정 portfolio의) holding 슬롯을 청산하고 당일 전략 실행을 중단시킬 수 있다.
- 당일 전략 제외는 다음 거래일 자동 복귀한다(day-scoped, pause와 달리 수동 resume 불필요).
SlotService.sell()(REQ-036)을 재사용해 기존 outbox 내구성·슬롯 상태 전이·ledger 기록 경로를 그대로 따른다.
- 이해관계자
- 운영자(Justin Ko): 실 KIS 브로커 연동 환경에서 이상 상황 발생 시 즉시 포지션 청산이 필요한 주요 사용자
- trex-core 데몬 운영자: kill-switch 발동 후 데몬 상태·로그 모니터링 담당
- 전략 모듈 개발자(orb-v1): kill-switch 발동 시 당일 추가 매수 의도가 거부됨을 인지해야 함
- 제약사항
SlotService.sell()(REQ-036)은 HOLDING 슬롯만 대상이며, PENDING_SELL 슬롯에는 idempotent로 동작한다. PENDING_BUY 슬롯 처리는 별도 결정 필요(OQ-3).- 당일 제외 상태는 데몬 재시작 후에도 유지되어야 하므로 영속화가 필요하다(pause_store 패턴 참조).
- trex-core sync+thread 런타임 — kill-switch 발동 시 다수 슬롯 순차 sell()이 execution worker FIFO 큐에 집중될 수 있다.
주요 사용자 시나리오
- 실 KIS 브로커 환경에서 예기치 않은 시장 급변이 발생한 경우 — 운영자가 kill-switch를 발동해 전체 holding 포지션을 즉시 청산하고 당일 추가 매수를 차단한다.
- 특정 portfolio의 전략 오동작을 감지한 경우 — portfolio 단위로 kill-switch를 발동해 해당 portfolio 슬롯만 청산하고, 다른 portfolio는 정상 운행한다.
- 데몬 점검 또는 긴급 배포 전 포지션 청산이 필요한 경우 — 수동 kill-switch로 전체 포지션을 먼저 닫고 데몬을 안전하게 중단한다.
4. 이번에 정의할 변경
- kill-switch 트리거 진입점 추가: 운영자가 RPC 메시지(
trex.control.req,action: "kill_switch") 또는 CLI로 kill-switch를 발동하면, 대상 범위(portfolio/전역)의 모든 holding 슬롯에SlotService.sell()을 순차 호출한다. 기존trex.pause.req를trex.control.req로 개념 확장하고 action 필드 기반 dispatch로 구현한다(새 큐 불필요). - day-scoped 전략 제외 기능 추가: kill-switch 발동 후 대상 범위 자산을 당일 전략 실행에서 제외한다. snapshot RPC 응답의 masking 소스에 "kill-switch 당일 제외"를 추가한다. 다음 거래일 데몬 부팅 시 자동 복귀한다.
- 당일 제외 상태 영속화: kill-switch 당일 제외 상태를 데몬 재시작 후에도 유지하기 위해 DB에 영속한다. 별도 테이블/store 어댑터를 신설한다(OQ-2 Answered) —
snapshot_pause(영구 pause)와 분리하고trading_date키로 day-scoped 표현. 다음 거래일 부팅 시 자동 무효. - 수동 해제(resume) 지원: 운영자가 같은 거래일 내에 kill-switch 당일 제외를 해제할 수 있도록
trex.control.req에action: "resume_kill_switch"(범위: portfolio/전역)를 추가한다(OQ-4 Answered). 해당 범위의 day-scoped 제외 row 를 삭제해 정상 매매를 재개한다. 단, 이미 청산된 슬롯은 되돌리지 않는다(매도 비가역).
영향 범위
- 코드 영향:
trex_core/domain/slot/service.py: kill-switch 발동 시 복수 슬롯에 sell()을 순차 호출하는 진입점(메서드 또는 헬퍼)trex_core/application/messaging_handlers.py:trex.control.reqaction dispatch — kill-switch handler 추가trex_core/adapters/persistence/postgresql.py또는 신규 어댑터: day-scoped 제외 상태 영속 read/writetrex_core/runtime/daemon.py: pause consumer를 control consumer(trex.control.req)로 확장 — action 기반 dispatchtrex_core/application/portfolio.py또는 snapshot handler: masking 소스에 "kill-switch 당일 제외" 추가
- 문서 영향:
docs/interface-contract.md: kill-switch RPC 메시지 계약(payload 구조, 대상 범위, 응답 구조)docs/runtime-flows.md: kill-switch 발동 흐름(트리거 → 순차 sell() → day-scoped 제외) 추가docs/project-structure.md: kill-switch 관련 신규 모듈/handler 반영
- 운영 영향:
- DB 스키마: day-scoped 제외 상태 영속 컬럼 또는 테이블 추가(additive)
trex.pause.req→trex.control.req개념 확장. kill-switch는action: "kill_switch"필드로 dispatch(새 큐 불필요). daemon.py의 pause consumer 스레드를 control consumer로 교체.
5. 요구사항 목록
기능 요구사항
- FR-1: 운영자가 kill-switch 명령을 발행하면 대상 범위(portfolio_id 지정 또는 전역)의 모든
holding슬롯에SlotService.sell(actor="kill-switch", reason="emergency_stop")을 순차 호출해 즉시 청산을 제출한다. - FR-2: kill-switch 발동 후 대상 범위의 자산(또는 모든 자산)을 당일 거래일 전략 실행에서 제외한다. snapshot RPC 응답의 masking 소스에 "kill-switch 당일 제외" 항목을 추가한다.
- FR-3: 당일 제외 상태는 데몬 재시작 후에도 유지된다. 다음 거래일 첫 부팅 시 자동으로 해제된다(day-scoped).
- FR-4: portfolio 단위(특정 portfolio_id의 슬롯만)와 전역(모든 슬롯)을 구분해 지원한다.
- FR-5: 이미
pending_sell상태인 슬롯에 대해 sell()을 중복 호출하지 않는다(REQ-036 idempotency 적용). - FR-6: kill-switch 발동 이력(actor, 발동 시각, 대상 범위, 처리 슬롯 수)이 데몬 로그에 기록된다.
비기능 요구사항
- NFR-1: kill-switch 발동 후 holding 슬롯 전체에 대한 sell() 제출 완료까지 지연이 슬롯 수 N 기준 N × 100ms + 5s 이내이어야 한다(순차 처리 기준, 실 KIS 연동 outbox 전송 제외).
- NFR-2: kill-switch 발동이 기존 자율 안전 매도 루프(dcy-007/REL-003)의 동작에 영향을 주지 않는다. 두 기능은 독립적으로 동작한다.
- NFR-3: kill-switch 당일 제외 상태가 영속화되어 데몬 재시작 후에도 snapshot RPC masking이 유지된다.
범위 경계
- In Scope
- holding 슬롯 즉시 청산 (portfolio/전역 단위,
SlotService.sell()재사용) - 당일 전략 실행 제외 + 다음 거래일 자동 복귀 (day-scoped, 영속 필요)
- kill-switch 트리거 표면:
trex.control.reqRPC,action: "kill_switch"메시지 (CLI는 REQ 단계에서 결정) - kill-switch 발동 후 당일 추가 매수 거부 (masking 소스 확장)
- kill-switch 발동 이력 로깅
- holding 슬롯 즉시 청산 (portfolio/전역 단위,
- Out of Scope
- 부분 수량 청산 (GitHub #5 별도 Discovery)
- 전략 모듈 프로세스 강제 종료 (trex-core 범위 밖)
- pending_buy 슬롯의 롤백 처리 (OQ-3 Deferred — REQ 단계에서 결정)
- kill-switch 발동 후 자율 안전 매도(dcy-007) 비활성화
- trailing stop 등 고급 종료 전략
6. 리스크/가정 목록
리스크
- R-1 (Medium): kill-switch 발동 시 다수 슬롯에 순차 sell()을 호출하면 execution worker FIFO 큐에 부하가 집중된다. 슬롯 수가 많을 경우 처리 지연이 발생할 수 있다.
- 완화 방안: sell() 호출은 PENDING_SELL 전이만 수행하므로 빠르다(DB write 1회). execution worker는 기존 순차 처리 유지. 슬롯 수 10 미만 운영 환경 기준으로 NFR-1 허용 지연 설정.
- R-2 (해소): 당일 제외 상태 영속 방식 — 별도 테이블/store 신설로 결정(OQ-2 Answered). pause는 "resume 전까지 유지(persistent)", kill-switch 제외는 "당일만(day-scoped,
trading_date키)"으로 테이블 분리 → 의미 충돌 없음, 기존 pause 마스킹 코드 무영향. - R-3 (Low): kill-switch 발동 중 브로커 오류로 일부 슬롯의 sell()이 실패하거나 지연될 수 있다.
- 완화 방안: sell() → PENDING_SELL 전이 성공 시 기존 outbox retry 경로가 eventual consistency를 보장한다. 발동 이력(FR-6)으로 미처리 슬롯 추적 가능.
- R-4 (Low): kill-switch와 자율 안전 매도(dcy-007)가 동시에 같은 슬롯에 sell()을 호출하면 중복 전이가 발생할 수 있다.
- 완화 방안: REQ-036 idempotency가 PENDING_SELL 상태 중복 sell()을 무시한다. 코드 충돌 없음.
가정
- A-1:
SlotService.sell()(REQ-036/BAT-010)이 이미 구현되어 있어 kill-switch가 직접 재사용할 수 있다. - A-2: 당일 전략 제외는 snapshot RPC 응답의 masking 소스 확장으로 구현한다. 기존 pause masking 코드 패턴(
portfolio.py)을 참조한다. - A-3: kill-switch 발동 후에도 기존 자율 안전 매도 루프(dcy-007)는 독립적으로 동작한다.
- A-4: 운영 환경의 holding 슬롯 수는 통상 10 미만이다. 대규모 병렬 청산 최적화는 Out of Scope.
오픈 질문
- OQ-1: kill-switch 트리거 표면 — 기존
trex.pause.req채널에action: "kill_switch"메시지를 추가할지, 신규 채널(trex.killswitch.req)을 별도로 열지?- 상태: Answered
- 결정:
trex.pause.req→trex.control.req개념 확장. kill-switch는action: "kill_switch"필드로 라우팅. 새 큐 불필요. 기능마다 채널을 추가하면 consumer 스레드/커넥션이 기능 수에 비례해 증가하므로 단일 제어 채널 + action discriminator 패턴을 채택한다. - 반영 위치: # 4 변경 정의, # 4 영향 범위 운영 영향, # 5 범위 경계 In Scope
- OQ-2: 당일 제외 상태 영속 방식 —
PostgreSQLPauseStore(pause_store)를 확장해 day-scoped 컬럼을 추가할지, 별도 테이블/어댑터를 신설할지?- 상태: Answered
- 결정: 별도 테이블/어댑터를 신설한다. kill-switch 당일 제외는
trading_date키로 day-scoped 표현이 자연스럽고, 기존snapshot_pause(portfolio_id PK, resume까지 영구·날짜 없음)와 의미가 달라 같은 테이블에 섞으면 마스킹/만료 로직 복잡도가 올라간다(R-2). 신규 테이블(예:killswitch_exclusion(portfolio_id, trading_date, actor, ...)) + 전용 store 어댑터로 관심사를 분리하고, snapshot 마스킹은mask = pending | warning | pause | killswitch_today로 합성한다. 기존 pause 코드는 무영향. - 반영 위치: # 4 변경 정의(당일 제외 상태 영속화), # 4 영향 범위(신규 테이블/마이그레이션 + store 어댑터), # 6 R-2 완화, # 5 범위 경계
- 종료 조건: 충족(REQ 단계에서 정확한 테이블 컬럼/만료 기준 = trading_date 경과 자동 무효 상세화).
- OQ-3: kill-switch 발동 시
pending_buy상태 슬롯 처리 — 현재 진행 중인 매수를 무시하고 HOLDING이 된 이후에 청산하도록 기다릴지, 즉시 롤백(PENDING_BUY→IDLE) 처리를 별도로 추가할지?- 상태: Deferred → REQ 단계
- 처리 방안: PENDING_BUY 롤백은 execution 루프와 상호작용이 복잡하므로 REQ 단계에서 상세 설계. 기본 구현은 HOLDING 슬롯만 대상.
- 종료 조건: REQ 초안 작성 시 pending_buy 범위 명시.
- OQ-4: 전역 halt 수동 해제 지원 여부 — kill-switch 발동 후 같은 거래일 내에 운영자가 당일 제외를 해제하고 정상 매매를 재개할 수 있어야 하는가?
- 상태: Answered
- 결정: 수동 해제를 지원한다. 운영자가 같은 거래일 내에 kill-switch 당일 제외를 해제하고 정상 매매를 재개할 수 있어야 한다.
trex.control.req에action: "resume_kill_switch"(범위: portfolio/전역) 를 추가하고, 해당 범위의 day-scoped 제외 row(OQ-2 신규 테이블)를 삭제(또는 무효화)한다. 해제는 "당일 제외"만 풀며 이미 청산된 슬롯을 되돌리지 않는다(매도는 비가역). - 반영 위치: # 4 변경 정의(수동 해제 action), # 4 영향 범위(control RPC handler), # 5 범위 경계 In Scope, # 10 DECIDE
- 종료 조건: 충족(REQ 단계에서
resume_kill_switchpayload/응답 + 권한/감사 상세화).
오픈 질문 처리 기준
Open: 아직 답이 없고 다음 단계 진행에 영향이 있는 상태다. 문서에 유지한다.Deferred: 지금 단계에서 결정하지 않고 다음 단계 또는 후속 이슈로 넘기는 상태다. 넘길 대상과 이유를 반드시 적는다.
7. 초기 성공 기준
- S-1: kill-switch 명령 발동 후 모든 holding 슬롯에 PENDING_SELL 전이 제출이 완료된다(슬롯 수 N 기준 NFR-1 이내).
- S-2: kill-switch 발동 후 다음 snapshot RPC 응답에서 대상 자산이 masking되어 buyable/held 뷰에 노출되지 않는다(당일 전략 제외 확인).
- S-3: 데몬 재시작 후에도 kill-switch 당일 제외 상태가 유지되어, 당일 전략 실행이 차단된다.
- S-4: 다음 거래일 데몬 부팅 시 kill-switch 당일 제외 상태가 자동 해제되어 정상 매매 대상이 된다.
측정 방식 및 데이터 출처
- S-1 측정 방식: kill-switch 발동 후
slot_repo.list_slots(day)결과에서 PENDING_SELL 수 == 발동 전 HOLDING 수 확인 / 데이터 출처: 단위 테스트(mock repo) + 실 e2e 로그 - S-2 측정 방식: kill-switch 발동 후 snapshot RPC 요청 → 응답 assets 목록에서 청산된 symbol 부재 확인 / 데이터 출처: 실 RabbitMQ e2e 테스트 또는 단위 테스트(masking 로직)
- S-3 측정 방식: kill-switch 발동 → 데몬 재시작 → snapshot RPC → masking 지속 확인 / 데이터 출처: 통합 테스트 또는 manual e2e
- S-4 측정 방식: 다음 거래일 날짜로 데몬 부팅 → snapshot RPC → 청산된 symbol이 buyable 뷰에 복귀 확인 / 데이터 출처: 통합 테스트(날짜 mock) 또는 manual e2e
8. REQ로 넘기기 전 확인 체크
- 범위(In Scope/Out of Scope) 확정
- OQ-1/2/4 Answered, OQ-3 REQ Deferred. scope = portfolio+전역 둘 다(FR-4). 확정.
- 성공 지표의 측정 방식/데이터 출처 확정
- S-1~S-4 각각 단위 테스트/e2e 근거 명시(# 7 측정 방식). 확정.
- High 영향 리스크 우선순위 및 해소 책임자 지정
- R-1(큐 부하) 완화 = 순차 sell()·N<10 가정으로 NFR-1 인계, R-2(영속 방식) 해소(별도 테이블). REQ 단계 인계.
- 모든 오픈 질문(OQ)
Open상태 없음 확인- OQ-1/2/4 Answered, OQ-3 Deferred →
Open없음.
- OQ-1/2/4 Answered, OQ-3 Deferred →
- Freeze 확정 담당자(CONFIRMED_BY) 지정
- Confirmed By = Justin.
9. 파일 처리 결과
- 처리 결과: 생성
- 생성 경로: docs/discovery/dcy-008_20260607_emergency-stop-kill-switch.md
- 참조 문서:
- GitHub #3: feat: 비상 정지/강제 청산(kill-switch) — portfolio/전역 단위 슬롯 청산 + 당일 전략 제외
- docs/discovery/dcy-007_20260606_core-autonomous-safety-sell.md (Out of Scope 분리 근거)
- docs/srs/Interface/req-036_auto-sell-internal-path.md (SlotService.sell() 재사용)
- docs/srs/Interface/req-032_runtime-pause-resume-control.md (pause 패턴 참조)
10. 사용자 결정 필요 항목 요약
DECIDE
- ✅ DECIDE_PERSIST_METHOD (OQ-2, Answered): 당일 제외 상태 영속 방식 = 별도 테이블/store 신설(
snapshot_pause와 분리,trading_dateday-scoped). - ✅ DECIDE_MANUAL_RESUME (OQ-4, Answered): 수동 해제 지원 —
trex.control.req action: "resume_kill_switch"추가, day-scoped 제외 해제(청산된 슬롯은 비가역).
CONFIRM
- ✅ CONFIRM_SCOPE_PORTFOLIO_AND_GLOBAL: portfolio 단위 + 전역 단위 둘 다 포함. 본문 FR-4("portfolio 단위와 전역 단위 둘 다 구분해 지원") + In Scope("portfolio/전역 단위") + 이슈명에서 이미 확정 — 동일하게 유지.
- ✅ CONFIRM_PENDING_BUY_DEFERRED: REQ 단계 Deferred로 처리(동의). OQ-3(Deferred → REQ 단계) + Out of Scope("pending_buy 슬롯의 롤백 처리 — OQ-3 Deferred")에 비차단으로 명시됨. 기본 구현은 HOLDING 슬롯만 대상.
DATA
- (비차단, REQ 단계 계측) DATA_SLOT_COUNT_UPPER_BOUND: 실 운영 환경의 최대 동시 holding 슬롯 수. A-4 가정(통상 10 미만)으로 NFR-1(N×100ms+5s) 산정 충분. 실측 상한은 REQ acceptance/verification 단계에서 확정 — Discovery confirm 차단 아님.
11. Discovery Freeze
섹션 참조
- 범위: # 5. 요구사항 목록 > 범위 경계
- 리스크/오픈 질문: # 6. 리스크/가정 목록
- 성공 기준: # 7. 초기 성공 기준
- 인계 전 확인: # 8. REQ로 넘기기 전 확인 체크
- 사용자 결정 필요: # 10. 사용자 결정 필요 항목 요약
Freeze 플래그
- Handoff Decision: 승인
- Handoff Rationale: OQ-1(trex.control.req+action), OQ-2(별도 테이블/store 신설), OQ-4(수동 해제=resume_kill_switch) Answered, OQ-3(pending_buy) REQ Deferred. CONFIRM(scope=portfolio+전역=FR-4, pending_buy=REQ Deferred) 본문 근거로 확정, DATA(slot 수 상한)는 REQ 단계 계측(비차단). 코드 사실관계(SlotService.sell REQ-036, trex.pause.req) 일치, baseline 모순 없음. REQ 초안 작성 가능.
- Ready for REQ Drafting: true
- Confirmed By: Justin
- Confirmed At (KST): 2026-06-07 11:11