# 0. 문서 상태 - 상태: confirmed - 문서 ID: dcy-024_20260611_safety-sell-disable-mode - 이슈명: safety-sell 자율 청산 비활성화 모드 (issue #15) - 작성 시각(KST): 2026-06-11 - 마지막 갱신 시각(KST): 2026-06-11 13:50 KST (change-req 진행: REQ-048 CHG-20260611-01(safety_sell_enabled TOML) + REQ-005 CHG-20260611-01(조건부 워커·기동 로그·runtime-flows), 둘 다 Implemented→Approved. 신규 REQ 없음(OQ-1).) - 대체됨: 없음 - 후속 Discovery 참조: 없음 - 생성된 REQ 참조: 없음 (change-req only, dcy-024 OQ-1). Delivery: REQ-048(CHG-20260611-01)·REQ-005(CHG-20260611-01) change-req → 차기 batch. # 1. 계획 상태 요약 - 연계 문서 현황: - 관련 점수 파일: 없음 - 관련 TODO/메모: GitHub issue #15 (자율 청산 비활성화 모드 설계 전문 포함) - 해석: - REL-006 피드백 환류 Discovery 후보로 식별됨. 자율 청산(forced_close / SL·TP / market_close) 워커를 완전히 끄고 전략·운영자 지시 매도만 허용하는 운영 모드 요구. - REQ-048(per-instance TOML) + REQ-005(daemon bootstrap) change-req 2건으로 닫는다 — OQ-1·OQ-2 확정. SafetySellConfig(env) 변경 없음. # 2. 요구사항 판정 결과 - 판정: 신규 - 근거 - dcy-007(core-autonomous-safety-sell)은 자율 매도 기능을 구현한 Discovery로 REL-003에서 released됨. 비활성화 모드는 그 기능의 확장이 아닌 운영 정책 플래그 추가 — 독립 후속 주기. - 현재 SafetySellConfig에 `enabled` 플래그가 없고, `daemon.start()`의 workers 리스트에 `trex-safety-sell` 스레드가 조건 없이 포함됨. 코드 갭이 실존하는 신규 요구. # 3. 문제점의 요약 - 현재 상태 - `trex_core/runtime/daemon.py:335`: `("trex-safety-sell", self._safety_sell_loop)` 이 workers 리스트에 항상 포함되어 데몬 기동 시 매번 자율 청산 워커가 시작됨. - `SafetySellConfig`(config.py:113)에는 `enabled` 플래그가 없고, per-trigger 비활성화 수단도 없음. 관찰 전용 인스턴스·신규 전략 검증 환경에서 자율 청산을 막을 방법이 없음. - 기대 상태 - `DaemonInstanceConfig`(per-instance TOML)에 `safety_sell_enabled: bool = True` 추가(OQ-2 — env 제외, `max_active_slots`/`default_order_timeout_sec` 선례)로, `false` 시 `trex-safety-sell` 스레드를 데몬 기동부에서 아예 시작하지 않음. - 기동 로그에 `safety_sell=disabled` 표시로 운영자가 안전망 없는 인스턴스임을 즉시 인지 가능(스냅샷 응답 미포함 — OQ-3). - 이해관계자 - 운영자/시스템 관리자 (Justin Ko): 관찰 전용 인스턴스·신규 전략 검증 시 자율 청산 OFF 필요 - 전략 모듈(trex-orb-v1): 전략 지시 매도는 항상 유지되어야 함 - 리뷰어/승인자: 안전망 없음을 명시한 운영 가이드 확인 - 제약사항 - `ExecutionService`(집행 루프), 운영자 CLI, kill-switch는 이 플래그에 영향받지 않아야 함 — 자율 청산(safety-sell 워커)만 OFF. - Redis·RabbitMQ·PostgreSQL 의존성 변경 없음. config.py + daemon.py 한정 수정. ## 주요 사용자 시나리오 - 관찰 전용 인스턴스: per-instance TOML `safety_sell_enabled = false` 설정 후 데몬 기동 → 기동 로그에 `safety_sell=disabled` 확인 → 포지션 유지 중 자동 청산 미발생, 전략 sell intent는 정상 집행. - 신규 전략 검증(paper 브로커): safety-sell 없이 전략 모듈 매도 타이밍만 테스트 — 의도치 않은 장마감 강제청산 없이 전략 e2e 검증. - 기본 운영(enabled=true): 기존 동작과 완전 동일. 하위 호환 변경 없음. # 4. 이번에 정의할 변경 - `DaemonInstanceConfig`(per-instance TOML)에 `safety_sell_enabled: bool = True` 필드 추가(OQ-2 — env 제외, SafetySellConfig 변경 없음). - `daemon.start()` workers 리스트 구성부에서 `safety_sell_enabled=false` 시 `trex-safety-sell` 스레드 제외 — run_once 내부 if-가드보다 스레드 미기동이 물리적으로 자율 청산을 차단. - 기동 로그(`RUNNING`)에 `safety_sell_enabled` 상태 노출(스냅샷 응답 미포함 — OQ-3) — 운영자가 인지 없이 안전망 없는 인스턴스를 운영하는 사고 방지. ## 영향 범위 - 코드 영향: - `trex_core/runtime/config.py`: `DaemonInstanceConfig`에 `safety_sell_enabled: bool = True` 필드 추가 + `from_toml()` 파싱 (SafetySellConfig 변경 없음) - `trex_core/runtime/daemon.py`: `start()` workers 조건부 구성 — `config.instance.safety_sell_enabled` 참조 + 기동 로그 `safety_sell=disabled/enabled` - 문서 영향: - REQ-048(per-instance TOML 설정) change-req: `safety_sell_enabled` 필드 추가 - REQ-005(daemon bootstrap) change-req: 워커 조건부 기동 명시 - `docs/runtime-flows.md`: safety-sell 비활성 모드 플로우 추가 - 운영 영향: - 기본값 `true` — 기존 운영 인스턴스 동작 무변. 기동 전 `.env` 또는 TOML에 `TREX_SAFETY_SELL_ENABLED=false` 추가 시에만 비활성 모드로 전환. - 비활성 모드 인스턴스는 손절/강제만기/장마감 안전망 없음 — 운영 가이드·기동 로그 명시 필수. # 5. 요구사항 목록 ## 기능 요구사항 - FR-1: `DaemonInstanceConfig`(per-instance TOML)에 `safety_sell_enabled: bool = True` 필드를 추가하고, TOML `safety_sell_enabled = false`로 인스턴스별 비활성화를 제어한다. 기본값 `true`(미지정 시 기존 동작 유지). env var 경로 미노출 — 인스턴스별 운영 모드 차이(`max_active_slots` 선례). - FR-2: `daemon.start()` 시 `DaemonInstanceConfig.safety_sell_enabled == False` 이면 `trex-safety-sell` 워커 스레드를 workers 리스트(`daemon.py:335`)에 포함하지 않는다. `True`(기본)이면 기존과 동일하게 기동한다. - FR-3: 기동 시 RUNNING 로그에 `safety_sell_enabled=` 필드를 포함한다. 비활성 모드(`false`)이면 추가로 `no autonomous liquidation` 경고를 로그에 남긴다. - FR-4: `safety_sell_enabled` 모드는 **기동 로그(FR-3)로만** 노출하고 **스냅샷/slots 응답에는 포함하지 않는다**(OQ-3). 근거: 인스턴스 config 이지 per-slot 데이터가 아니며, dcy-025 가 read 표면을 "응답=상태(slots[])+health"로 최소화하므로 config 를 응답에 싣지 않는 패턴과 정합한다. 런타임 노출이 필요해지면 per-slot 이 아니라 dcy-025 envelope-level(또는 별도 instance-status 표면)에서 조율한다. ## 비기능 요구사항 - NFR-1: `enabled=false` 시 `ExecutionService`(집행 루프)·운영자 CLI(`enter`/`exit`/`cancel`)·kill-switch·messaging handler에는 영향 없음. 자율 청산(safety-sell 워커)만 미기동. - NFR-2: 기본값 `true` — 기존 운영 인스턴스 재배포 시 config 변경 없이 기존 동작 보존(완전 하위 호환). - NFR-3: 비활성 모드는 테스트에서 `config.safety_sell.enabled=False`를 주입하는 것만으로 재현 가능. 추가 mocking 불필요. ## 범위 경계 - In Scope - SafetySellConfig `enabled` 필드 + env + TOML 파싱 - daemon.start() workers 조건부 구성 - 기동 로그에 모드 노출(스냅샷 응답 미포함 — OQ-3) - REQ-048(per-instance TOML) change-req + REQ-005(daemon bootstrap) change-req + runtime-flows 갱신 - Out of Scope - per-trigger 개별 플래그(forced_close / SL·TP / market_close 각각 ON/OFF) — 마스터 플래그 이후 별도 후속 이슈로 분리 - kill-switch 포함 여부 변경 — 운영자 지시 경로이므로 유지(혼용 금지) - 비활성 모드용 별도 watchdog 가시화 — 워커 미기동 시 로깅 없어도 무방(과설계 회피) - trex-orb-v1 전략 모듈 변경 — 전략 sell intent는 messaging handler 경로이며 영향 없음 # 6. 리스크/가정 목록 ## 리스크 - R-1 (Medium): 비활성 모드 인스턴스에서 전략 모듈이 죽어 폴링이 끊겨도 안전망 없이 포지션이 계속 유지됨 — 의도적 모드이지만 운영자 인지 없이 설정하면 무제한 손실 리스크. - 완화 방안: 기동 로그 `no autonomous liquidation` 경고로 운영자가 즉시 인지(스냅샷 응답 미포함 — OQ-3). 운영 가이드에 위험 명시. - R-2 (Low): TOML `safety_sell_enabled` 오타/잘못된 타입이 의도와 다르게 해석될 경우 기대와 반대로 자율 청산이 활성화됨. - 완화 방안: TOML bool 파싱 규약을 따르되(미지정/비-false → `true`), 기동 로그에 파싱된 값을 항상 출력해 운영자가 확인 가능하게 함. - R-3 (Low): 기존 SafetySellConfig 단위 테스트가 `enabled` 필드 추가 후 drift할 수 있음. - 완화 방안: `EXPECTED_SLOT_FIELDS` drift test 선례처럼 config 계약 가드 테스트 추가 또는 기존 테스트 보완. ## 가정 - A-1: 단일 마스터 플래그(`enabled`) 구현으로 시작하고, per-trigger 세분화는 후속 이슈로 분리한다. - A-2: `enabled=false` 시 kill-switch, 운영자 CLI exit, 전략 sell intent는 그대로 동작한다. - A-3: 신규 REQ 없이 REQ-048(per-instance TOML) + REQ-005(daemon bootstrap) change-req 2건으로 닫는다 — OQ-1·OQ-2 Justin Ko 확정. SafetySellConfig(env) 변경 없음. ## 오픈 질문 - OQ-1: 신규 REQ 없이 change-req 경로(REQ-041·005)만으로 진행할지, 별도 Configuration REQ를 신규 발행할지? - 상태: Answered - 처리 방안: Justin Ko 확정 — **change-req 우선(신규 REQ 미발행)**. REQ-048(DaemonInstanceConfig TOML) + REQ-005(daemon bootstrap) change-req 2건으로 닫는다. - 종료 조건: 완료 - OQ-2: per-instance TOML `[safety_sell] enabled = false` 지원 포함 여부(env만 지원할지 TOML도 포함할지)? - 상태: Answered - 처리 방안: Justin Ko 확정 — **TOML 포함(env 제외)**. 당일 매매 인스턴스는 enabled=true, 중장기 보유 인스턴스는 enabled=false — 인스턴스마다 다른 운영 모드이므로 `DaemonInstanceConfig`(TOML)에 배치. `max_active_slots`·`default_order_timeout_sec` 선례 동일. REQ-048 change-req 추가. - 종료 조건: 완료 - OQ-3: 스냅샷 응답(`slots[]` envelope)에 `safety_sell_enabled` 필드 추가 포함 여부(FR-4)? - 상태: Answered (2026-06-11, 사용자 결정) - 결정: **기동 로그만, 스냅샷/slots 응답 미포함.** 근거: `safety_sell_enabled`는 인스턴스 config(per-slot 데이터 아님)이고, dcy-025 가 read 표면을 "응답=상태(slots[])+health"로 최소화하므로 config 를 응답에 싣지 않는 패턴과 정합. **per-slot 행에 넣지 않는다.** 런타임 노출이 필요해지면 per-slot 이 아니라 dcy-025 envelope-level(또는 별도 instance-status 표면)에서 조율(후속). 반영: FR-4. # 7. 초기 성공 기준 - S-1: per-instance TOML `safety_sell_enabled = false`로 데몬 기동 시 `trex-safety-sell` 스레드가 시작되지 않는다. 기동 로그에 `safety_sell_enabled=false`와 `no autonomous liquidation` 경고가 포함된다. - S-2: 비활성 모드에서 forced_close_at 만기·SL·TP·장마감 시각이 지나도 슬롯이 자동 청산되지 않는다. 전략 모듈 sell intent + 운영자 CLI exit는 정상 집행된다. - S-3: `enabled=true`(기본값) 시 기존 동작과 동일하며 기존 단위·통합 테스트 회귀가 없다. ## 측정 방식 및 데이터 출처 - S-1 측정 방식: 단위 테스트 — `enabled=False` config 주입 + `daemon.start()` 후 thread 이름 목록에 `trex-safety-sell` 부재 확인 / 데이터 출처: `tests/runtime/test_daemon.py` 또는 신규 테스트 - S-2 측정 방식: run-the-app 스모크 — 비활성 모드 데몬 기동 후 holding 슬롯 유지 중 장마감 시각 경과 → 슬롯 상태 `holding` 유지 + 전략 sell intent 처리 확인 / 데이터 출처: 데몬 기동 로그(`safety_sell_enabled=false`) + 슬롯 상태(`trex-core slots`로 holding 유지 확인 — slots 출력 자체엔 safety_sell_enabled 미포함, OQ-3) - S-3 측정 방식: `pytest` 전체 실행 회귀 없음, `ruff check` clean / 데이터 출처: CI 또는 로컬 테스트 결과 # 8. REQ로 넘기기 전 확인 체크 - [x] 범위(In Scope/Out of Scope) 확정 - 기준: per-trigger 플래그 Out of Scope + TOML 지원(OQ-2 TOML·env 제외) 확정 - [x] 성공 지표의 측정 방식/데이터 출처 확정 - 기준: S-1 단위(스레드 부재)·S-2 run-the-app 스모크·S-3 회귀로 정의됨 - [x] High 영향 리스크 우선순위 및 해소 책임자 지정 - 기준: R-1(비활성 모드 손실)은 기동 로그 경고 + 운영 가이드로 해소, 가이드 작성 담당 = 구현 batch/Justin Ko(아래) - [x] 모든 오픈 질문(OQ) `Open` 상태 없음 확인 - 기준: OQ-1/2/3 전부 Answered - [x] Freeze 확정 담당자(CONFIRMED_BY) 지정 - 기준: Justin Ko 승인 후 `confirm-discovery` 수행 - [ ] (delivery 인계) 비활성 모드 운영 가이드(손실 리스크 경고) 작성 — 구현 batch 단계 산출물, 담당 project maintainers/Justin Ko (Discovery confirm 비차단) # 9. 파일 처리 결과 - 처리 결과: 생성 - 생성 경로: docs/discovery/dcy-024_20260611_safety-sell-disable-mode.md - 참조 문서: - GitHub issue #15 — 자율 청산 비활성화 모드 설계 전문 (영향 범위·구현 방안·OQ 상세) - docs/discovery/dcy-007_20260606_core-autonomous-safety-sell.md — 자율 매도 구현 Discovery (선행 참조) - trex_core/runtime/config.py:113 — SafetySellConfig (변경 대상) - trex_core/runtime/daemon.py:335 — workers 리스트 (`trex-safety-sell` 항목, 변경 대상) # 10. 사용자 결정 필요 항목 요약 ## DECIDE - 없음 (OQ-1·OQ-2 확정 완료) ## CONFIRM - 없음. (1) 스냅샷 응답 노출 = OQ-3 Answered(기동 로그만). (2) kill-switch·운영자 CLI exit가 `safety_sell_enabled=false`와 무관하게 유지됨은 설계로 확정(A-2/NFR-1) — 저장소상 `KillSwitchService`(daemon.py:166)·운영자 CLI exit 는 `trex-safety-sell` 워커와 **독립 경로**라 워커 미기동이 그 둘에 영향 없음(확인 완료). ## DATA - 없음. (운영 가이드(손실 리스크 경고) 작성은 Discovery 정책 결정이 아니라 **delivery 단계 산출물**이므로 §8 인계 체크로 이동 — 담당은 구현 batch/project maintainers.) # 11. Discovery Freeze ## 섹션 참조 - 범위: [# 5. 요구사항 목록 > 범위 경계](#5-요구사항-목록) - 리스크/오픈 질문: [# 6. 리스크/가정 목록](#6-리스크가정-목록) - 성공 기준: [# 7. 초기 성공 기준](#7-초기-성공-기준) - 인계 전 확인: [# 8. REQ로 넘기기 전 확인 체크](#8-req로-넘기기-전-확인-체크) - 사용자 결정 필요: [# 10. 사용자 결정 필요 항목 요약](#10-사용자-결정-필요-항목-요약) ## Freeze 플래그 - Handoff Decision: 승인 - Handoff Rationale: OQ-1/2/3 전부 Answered(change-req 경로·TOML·기동 로그만). C형 플레이스홀더 0(CONFIRM/DATA 해소). 저장소 사실 재확인 일치. 운영 가이드 작성만 delivery 단계 비차단 인계. REQ Drafting 인계 가능. - Ready for REQ Drafting: true - Confirmed By: Justin Ko - Confirmed At (KST): 2026-06-11 13:29 KST