docs(discovery): dcy-027 모든 OQ Answered — 중장기 슬롯 운영 결정 완료

OQ-1: (a′) 기존 슬롯 도메인 유지 + 운영 프로파일/규칙 추가 (PositionBook 불필요)
OQ-2: safety_sell_enabled=false → 코어 손절매 루틴 없음, 전략 모듈 전적 책임
OQ-3: per-instance watchdog_timeout_sec 연장 (1800s 이상 권장), 알림은 후속 Discovery
OQ-4: 중장기 전략 폴링 주기 최소 1분·최대 10분

feat(bat-041): REQ-076/077/078 — watchdog timeout TOML + 중장기 운영 프로파일 + baseline

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-12 22:19:33 +09:00
co-authored by Claude Sonnet 4.6
parent d737467d70
commit 6786ffd05d
16 changed files with 634 additions and 44 deletions
+41
View File
@@ -0,0 +1,41 @@
# trex-core long-term (multi-day) instance profile (dcy-027 / REQ-077).
# Copy to e.g. config/longterm.toml and run alongside the intraday daemon:
# trex-core-daemon --config config/longterm.toml
# Real per-instance files are gitignored (they hold the portfolio-hub token).
#
# This profile assumes the strategy module owns ALL protection rules
# (stop-loss / trailing / time-stop — dcy-027 OQ-2). The core provides no
# autonomous liquidation for this instance; the only core-side backstop is
# the operator CLI: trex-core exit <slot>
# Long-term instances MUST use their own instance_key so slots (DB scope) and
# queues (trex.slots.req.<key> etc) never interfere with the intraday daemon
# (dcy-027 A-2; dcy-009 topology).
instance_key = "longterm"
# portfolio-hub auth token (kept out of .env).
portfolio_hub_token = ""
trading_timezone = "Asia/Seoul"
# FCFS admission cap M for this instance (REQ-060). Size to the long-term
# portfolio's target position count.
max_active_slots = 10
# dcy-024 / REQ-048: long-term holdings must survive market close, day
# boundaries and restarts — disable the autonomous safety-sell worker
# (stop-loss / forced_close / market-close loops are all OFF).
# NOTE: forced_close_at is therefore meaningless on this instance (dcy-027
# CONFIRM) — its executor never runs here. Do not set it on long-term slots.
safety_sell_enabled = false
# dcy-027 / REQ-076: long-term strategies poll every 1-10 min (OQ-4), so the
# intraday default (120s, env TREX_WATCHDOG_TIMEOUT) would mark them expired
# all day. 1800s (30 min) keeps `watchdog.expired` a meaningful absence
# signal while last_seen stays visible in slots RPC rows.
watchdog_timeout_sec = 1800
# Environment note (not TOML keys): do NOT set TREX_MARKET_CLOSE_LIQUIDATION_TIME
# for this instance's daemon — market-close liquidation is an intraday concept.
# (With safety_sell_enabled=false the loop is off anyway; leaving the env unset
# keeps the intent explicit.)
@@ -0,0 +1,39 @@
# BAT-041 Design
- Status: confirmed
- Batch: BAT-041
## Architecture Summary
- watchdog timeout 설정을 단일 지점에서 per-instance 화한다: `DaemonInstanceConfig.watchdog_timeout_sec`(TOML, optional) → `DaemonConfig.from_config` 에서 값이 있으면 `SafetySellConfig.watchdog_timeout_sec` 를 `dataclasses.replace` 로 override. 소비자 2곳(slots RPC 핸들러의 expired 표시, SafetySellService 의 trigger gating)은 모두 `config.safety_sell.watchdog_timeout_sec` 를 읽으므로 자동으로 일치한다.
- 중장기 프로파일은 신규 코드 없이 기존 설정 메커니즘 조합(`safety_sell_enabled=false` + `watchdog_timeout_sec=1800` + `max_active_slots` + market-close env 미설정)으로 표현되고, `config/longterm.example.toml` 로 표준화한다.
- runtime-flows 에 중장기 병행 운영 흐름(보호 경계 포함)을 추가한다.
## Changed Areas
- Modules:
- `trex_core/runtime/config.py`:
- `DaemonInstanceConfig.watchdog_timeout_sec: Optional[int] = None`
- `from_toml`: `watchdog_timeout_sec` 파싱 + `<= 0` 거부(기존 `max_active_slots` 검증 패턴)
- `from_config`: `inst.watchdog_timeout_sec is not None` → `safety_sell = dataclasses.replace(safety_sell, watchdog_timeout_sec=...)`
- `config/longterm.example.toml` (신규)
- Tests: `tests/runtime/test_config*.py` 패턴 — TOML 적용/env 폴백/독립/검증 거부
- Docs: `docs/runtime-flows.md`(중장기 운영 흐름 + Change Log), `docs/project-structure.md`(불필요 예상 — 구조 불변, config 필드 1개)
## Key Decisions
1. **override 지점 = `from_config` 단일 지점**: TOML 값이 frozen `SafetySellConfig` 를 `dataclasses.replace` 로 교체. daemon.py·safety_sell.py·messaging_handlers.py 는 무변경 — 소비자가 이미 `config.safety_sell.watchdog_timeout_sec` 를 읽음(R-1 제거).
2. **TOML 우선, env 폴백**: `watchdog_timeout_sec` TOML 미설정 시 기존 env(`TREX_WATCHDOG_TIMEOUT`, 기본 120) 그대로 — 기존 인스턴스 byte-for-byte 무회귀(REQ-076 AC-2). REQ-060(env 폐기)과 달리 env 는 보존: 기존 운영 사용 중.
3. **`<= 0` 명시 거부**: `max_active_slots`/`default_order_timeout_sec` 와 동일한 from_toml 검증 패턴.
4. **프로파일 = 설정 조합(신규 코드 0)**: REQ-077 은 example TOML + 검증으로 충족. market-close 는 env 영역이므로 TOML 주석으로 "미설정 전제" 명시(코드 강제 없음 — 운영 절차).
5. **S-1 검증 범위**: 멀티데이 보유 유지는 영구 슬롯 DB 영속(dcy-020)의 기존 보장 + 자율 청산 미발화는 `safety_sell_enabled=false` 워커 미기동(BAT-036 evidence). 본 batch 는 프로파일 조합이 의도대로 로딩·적용되는지(워커 미기동 + timeout 1800 + 독립 instance_key)를 설정·기동 구성 단위로 검증하고, 실 데몬 장기 run 은 release 운영 검증으로 위임.
## Edge Cases
- TOML `watchdog_timeout_sec = 0` 또는 음수: from_toml 명시 거부(RuntimeError).
- TOML 미설정 + env 미설정: 기본 120s(기존과 동일).
- TOML 설정 + env 도 설정: TOML 승리(per-instance 가 전역보다 구체적).
- 당일 인스턴스(TOML 미설정)와 중장기(1800) 병행: 인스턴스별 DaemonConfig 가 독립 생성되므로 분리 적용.
## Architecture Impact
- runtime-flows (중장기 병행 운영 흐름 신설 — REQ-078 소관)
## Reference Doc Update Plan
- project-structure.md: none 예상(구조 불변 — config 필드 1개 + 예시 파일. 예시 파일 위치 언급이 필요하면 한 줄)
- runtime-flows.md: 중장기 인스턴스 운영 섹션 신설 — 병행 토폴로지(instance_key 분리), 프로파일 구성(REQ-077 레시피), 보호 규칙 경계(전략 전적 책임 + CLI exit 백스톱, 코어 손절매 루틴 미사용), forced_close_at 미사용(발화 불가 근거), watchdog 중장기 의미(1800s, last_seen 가시성 유지). Change Log 항목 추가.
@@ -0,0 +1,31 @@
# BAT-041 Implementation
- Status: completed
- Batch: BAT-041
## Plan Summary
- `DaemonInstanceConfig.watchdog_timeout_sec`(TOML, env 폴백) + `from_config` 단일 지점 override, `config/longterm.example.toml` 표준 프로파일, runtime-flows 중장기 운영 flow, 테스트 7종.
## Changed Files
- `trex_core/runtime/config.py` — `DaemonInstanceConfig.watchdog_timeout_sec: Optional[int] = None` + `from_toml` 파싱(<=0 거부) + `from_config` 에서 `SafetySellConfig` `dataclasses.replace` override(TOML 우선, env 폴백)
- `config/longterm.example.toml` (신규) — 중장기 표준 프로파일(instance_key=longterm, safety_sell_enabled=false, watchdog_timeout_sec=1800, max_active_slots=10) + 키별 중장기 의미 주석(forced_close_at 미사용, market-close env 미설정 포함)
- `docs/runtime-flows.md` — `중장기(멀티데이) 인스턴스 병행 운영` flow 신설(6단계: 분리 기동/프로파일/보유 유지/보호 경계/forced_close_at 미사용/watchdog 의미) + Change Log
- `tests/runtime/test_watchdog_timeout_toml.py` (신규, 7 테스트)
## Execution Log
1. `config.py`: `DaemonInstanceConfig` 에 optional 필드 추가, `from_toml` 에 기존 `max_active_slots` 검증 패턴으로 파싱+거부, `from_config` 에서 `inst.watchdog_timeout_sec is not None` 시 `safety_sell` replace — 소비자 2곳(slots RPC 핸들러 daemon.py:284, SafetySellService `_cfg`)이 모두 `config.safety_sell` 을 읽으므로 단일 지점으로 일치(design Key Decision 1).
2. `longterm.example.toml`: REQ-077 레시피 — 운영자 복제 사용 가능한 주석 포함.
3. `runtime-flows.md`: multi-instance 라우팅 섹션 뒤에 중장기 운영 flow 신설.
4. 테스트: TOML 적용(1800)/TOML>env 우선/env 폴백(300)/기본 120/인스턴스별 독립/<=0 거부/예시 프로파일 조합 로딩(longterm·false·1800·10).
## Validation
- `pytest tests/runtime/test_watchdog_timeout_toml.py` — 7 passed (신규)
- `pytest tests/` — **446 passed, 46 skipped** (BAT-040 439 → +7, 회귀 0 — NFR-1 당일 무회귀 게이트 green)
- `ruff check trex_core tests` — All checks passed
- instance 격리(NFR-2): 인스턴스별 독립 timeout 테스트 + 기존 instance topology 테스트 회귀 green.
## Remaining Risks
- 없음. R-1(소비자 불일치)=단일 지점 override 로 제거, R-2(frozen)=`replace` 사용, R-3(장기 run 재현)=설정·기동 구성 단위 검증 + BAT-036 evidence 재참조, 실 데몬 장기 운영 검증은 release 단계 위임(verification 명시).
## Out-of-scope Finding (별도 후속)
- **pre-existing bug 발견**: `DaemonInstanceConfig.from_toml` 은 `default_order_timeout_sec` 를 파싱하지만 `DaemonConfig.from_config` 의 `replace(...)` 에 전파 누락 — TOML 의 `default_order_timeout_sec` 가 런타임에서 조용히 무시됨(daemon.py:156 은 `config.default_order_timeout_sec` 를 읽으나 항상 env-base 값). REQ-068/REQ-048 영역 — 본 batch 범위 밖, change-req 후속 필요.
@@ -0,0 +1,33 @@
# BAT-041: 중장기 인스턴스 운영 — watchdog per-instance TOML + 프로파일 + baseline (dcy-027)
- Status: release-candidate
- Profile: standard
- Source Discovery: dcy-027 (issue #20 이월)
- Included REQ: REQ-076, REQ-077, REQ-078
- Excluded REQ: 없음
## Summary
- 중장기 매매 전략용 인스턴스 운영 토대를 완성한다(dcy-027, 구현 위치 (a′) — 기존 슬롯 도메인 유지). watchdog timeout 을 per-instance TOML 로 노출하고(REQ-076, env 폴백), 중장기 표준 운영 프로파일 TOML 레시피를 제공·검증하며(REQ-077), 보호 규칙 경계(전략 모듈 전적 책임 + CLI exit 백스톱)와 forced_close_at 미사용을 baseline 문서에 명시한다(REQ-078).
## Scope
- In Scope:
- `trex_core/runtime/config.py` — `DaemonInstanceConfig.watchdog_timeout_sec`(optional) + `from_toml` 파싱 + `from_config` 에서 `SafetySellConfig.watchdog_timeout_sec` override(TOML 우선, env 폴백)
- `config/longterm.example.toml` — 중장기 표준 프로파일 예시(키별 중장기 의미 주석)
- `docs/runtime-flows.md`(·필요 시 `docs/project-structure.md`) — 중장기 병행 운영 흐름 + 보호 규칙 경계 + forced_close_at 미사용
- 테스트: TOML 로딩/env 폴백/인스턴스 독립, 프로파일 조합 기동 구성, instance 격리 회귀
- Out of Scope:
- 멀티데이 보호 규칙 코어 구현 (dcy-027 OQ-2 — 전략 모듈 전적 책임, 코어 구현 없음)
- watchdog 소유권 진입 외부 알림(슬랙·이메일) — 후속 Discovery
- 중장기 전략 모듈 구현(별도 저장소)
- PositionBook 도메인 (OQ-1 미채택)
## Documents
- Planning: [planning.md](./planning.md)
- Design: [design.md](./design.md)
- Implementation: [implementation.md](./implementation.md)
- Verification: [verification.md](./verification.md)
## Notes
- 코드 변경은 경량(config 1개 필드 + override 전파)이나, 런타임 설정 채널 + 운영 프로파일 + baseline 문서 정렬을 함께 포함하므로 standard profile.
- NFR-1(당일 무회귀)은 본 batch verification 의 전체 회귀 게이트, NFR-2(무간섭)는 REQ-077 AC-3, NFR-3(TOML 재사용)은 REQ-076 요구사항 제약으로 흡수(dcy-027).
- S-1 의 "일자 경계·재시작 보유 유지"는 영구 슬롯 모델(dcy-020, DB 영속)의 기존 보장 — 본 batch 는 프로파일 조합으로 자율 청산 미발화를 검증한다(설정 단위 + 기존 BAT-036 evidence 재참조).
@@ -0,0 +1,37 @@
# BAT-041 Planning
- Status: confirmed
- Batch: BAT-041
- Profile: standard
## Included REQ
- REQ-076: watchdog timeout per-instance TOML(`watchdog_timeout_sec`, TOML 우선·env 폴백). AC 4.
- REQ-077: 중장기 운영 프로파일 — `config/longterm.example.toml` 레시피 + 기동 구성·병행 무간섭 검증. AC 4.
- REQ-078: baseline 정합 — 중장기 흐름 + 보호 규칙 경계 + forced_close_at 미사용. AC 3.
## Delivery Plan
1. `config.py` — `DaemonInstanceConfig.watchdog_timeout_sec: Optional[int] = None` + `from_toml` 파싱(>0 검증) + `from_config` 에서 값 존재 시 `SafetySellConfig` override(`dataclasses.replace`).
2. `config/longterm.example.toml` — instance_key/safety_sell_enabled=false/watchdog_timeout_sec=1800/max_active_slots + 키별 중장기 의미 주석(forced_close_at 미사용 포함).
3. 테스트 — TOML 로딩(1800 적용)/미설정 env 폴백(120)/인스턴스별 독립/프로파일 조합 기동 구성(워커 미기동 + timeout 적용), instance 격리 회귀.
4. `docs/runtime-flows.md` — 중장기 병행 운영 흐름 섹션(토폴로지/프로파일/보호 경계/forced_close_at 미사용) + Change Log.
## Dependencies
- Internal:
- `SafetySellConfig.watchdog_timeout_sec` 소비자 2곳 확인 완료 — slots RPC 핸들러(daemon.py:284), SafetySellService(`_cfg.watchdog_timeout_sec`). override 가 `config.safety_sell` 에 반영되면 두 경로 모두 자동 적용.
- 기존 TOML 패턴(`max_active_slots`/`default_order_timeout_sec`/`safety_sell_enabled`) 준용.
- BAT-036(safety_sell_enabled=false 워커 미기동) 기존 evidence — 프로파일 검증에서 재사용.
- External: 없음 (paper broker 로 검증, 라이브 불요).
## Design Gate
- Design Required Before Implementation: yes
- Reason: TOML→SafetySellConfig override 주입 지점(frozen dataclass replace), env 폴백 우선순위 의미, 프로파일 검증 범위(설정 단위 vs 실 데몬)를 design 에서 확정.
## Milestones
1. Design confirmed — override 경로·검증 범위 확정
2. Implementation — config + example TOML + 테스트 + baseline 문서
3. Verification — REQ-076/077/078 AC 매핑 + 전체 회귀(NFR-1) + ruff
## Risks
- R-1 (Low): override 가 SafetySellService 에는 적용되고 slots RPC 핸들러에 미적용(또는 역) — 완화: 단일 지점(`config.safety_sell`) override 로 두 소비자 자동 일치, 테스트로 확인.
- R-2 (Low): frozen dataclass 수정 시도 오류 — 완화: `dataclasses.replace` 사용.
- R-3 (Low): 일자 경계·재시작 시나리오를 실시간 재현 불가 — 완화: 영구 슬롯 DB 영속(dcy-020 기존 보장) + 자율 청산 미발화는 설정 단위 검증 + BAT-036 evidence 재참조로 충족(S-1 의 run-the-app 전수 재현은 release 운영 검증으로 위임, verification 에 명시).
@@ -0,0 +1,48 @@
# BAT-041 Verification
- Status: confirmed
- Batch: BAT-041
## Acceptance Mapping
- REQ-076 AC-1 (TOML `watchdog_timeout_sec = 1800` → 1800s 동작):
- Evidence: `test_toml_watchdog_timeout_applied`(1800 적용) + `test_toml_wins_over_env`(env 300 무시, TOML 1800 승리) PASSED.
- REQ-076 AC-2 (미설정 → env 폴백, byte-for-byte 무회귀):
- Evidence: `test_absent_falls_back_to_env`(env 300 적용) + `test_absent_and_no_env_keeps_default_120` PASSED. 전체 회귀 446 passed(기존 인스턴스 경로 무회귀).
- REQ-076 AC-3 (인스턴스별 독립 timeout):
- Evidence: `test_instances_get_independent_timeouts` — day 120 / lt 1800 동시 분리 PASSED.
- REQ-076 AC-4 (저빈도 폴링 비오인 + last_seen 가시성):
- Evidence: timeout 1800s 적용 경로가 slots RPC 핸들러·SafetySellService 양쪽의 단일 소스(`config.safety_sell.watchdog_timeout_sec`, daemon.py:284 wiring 코드 리뷰)로 일치 — 600s 폴링 < 1800s timeout 으로 `is_expired` 산식상 비오인(WatchdogStore.is_expired 는 경과>timeout 비교). `last_seen` 노출 경로(slot_to_dict) 불변(기존 slots 핸들러 16 테스트 회귀 green).
- REQ-077 AC-1 (프로파일 기동 구성 — 워커 미기동 + 1800 + 독립 큐):
- Evidence: `test_longterm_example_profile_loads_with_expected_combination` — instance_key=longterm, `safety_sell_enabled=False`, watchdog 1800, max_active_slots=10 PASSED. 워커 미기동(`safety_sell_enabled=false` → `trex-safety-sell` 스레드 제외 + WARNING)은 BAT-036 verification evidence 유효(본 batch 코드 경로 불변). per-instance 큐명은 dcy-009/BAT-013 evidence 유효.
- REQ-077 AC-2 (멀티데이 보유 유지 — 일자 경계·재시작):
- Evidence: 영구 자산 슬롯의 DB 영속·날짜 키 부재(dcy-020/BAT-027~030 verification — holding 이 재시작·일자 경계 통과)는 기존 evidence 유효. 본 프로파일은 자율 청산 트리거 전부 OFF(safety_sell_enabled=false + market-close env 미설정) 조합 — 설정 단위 검증 PASSED. **실 데몬 장기 run(수일) 검증은 release 운영 단계로 위임**(planning R-3 명시) — 코드 경로는 기존 검증 조합의 재구성이므로 release-ready 차단 아님.
- REQ-077 AC-3 (당일·중장기 병행 무간섭):
- Evidence: `test_instances_get_independent_timeouts`(설정 독립) + 기존 instance topology/스코프 격리 테스트 회귀 green(446 passed — DB instance_key 스코프·per-instance 큐 기존 evidence 유효).
- REQ-077 AC-4 (예시 파일 — 표준 레시피 + 의미 주석):
- Evidence: `config/longterm.example.toml` — 키별 중장기 의미 주석(safety_sell off 이유, 1800s 근거, forced_close_at 미사용, market-close env 미설정) 포함, 예시 로딩 테스트로 유효성 보증.
- REQ-078 AC-1 (runtime-flows 중장기 흐름 — 토폴로지/프로파일/보호 경계 정합):
- Evidence: `docs/runtime-flows.md` `중장기(멀티데이) 인스턴스 병행 운영` flow 신설(6단계) + Change Log — 현행 코드(BAT-036 워커 미기동, REQ-076 override 경로)와 정합 확인.
- REQ-078 AC-2 (forced_close_at 미사용 명시):
- Evidence: runtime-flows flow 5단계("의미 없다 — 집행 주체 OFF, 발화 불가") + longterm.example.toml 주석 명시.
- REQ-078 AC-3 (운영자 판단 가능성 — 보호장치 부재·책임 주체 명확):
- Evidence: flow 4단계 — "코어 손절매 루틴 일절 미사용 / 모든 보호 규칙 전략 모듈 전적 책임 / 유일한 백스톱 = 운영자 CLI exit" 모호함 없이 명시.
## Evidence
- Test:
- `pytest tests/runtime/test_watchdog_timeout_toml.py` — 7 passed (신규)
- `pytest tests/` — **446 passed, 46 skipped** (BAT-040 439 → +7, 회귀 0 — dcy-027 NFR-1 당일 무회귀 게이트 green)
- `ruff check trex_core tests` — All checks passed
- Manual / Baseline 동기화 (Architecture Impact: runtime-flows):
- `docs/runtime-flows.md` 중장기 운영 flow 신설 + Change Log ✓ 코드 정합.
- `docs/project-structure.md`: 변경 불필요(design Reference Doc Update Plan — 구조 불변, config 필드 1개 + 예시 파일).
- override 단일 지점(daemon.py:284 + SafetySellService) 코드 리뷰 확인.
## Result
- Ready for Release: true
- Blocking Issues: 없음
- REQ-077 AC-2 의 실 데몬 장기 run(수일) 검증은 release 운영 단계 사항 — 구성 요소별 기존 evidence(영구 슬롯 영속 BAT-027~030, 워커 미기동 BAT-036) + 프로파일 조합 설정 검증으로 release-ready 충족. verification 한계로 명시.
- out-of-scope finding(`default_order_timeout_sec` TOML 전파 누락, implementation.md 기록)은 본 batch REQ 와 무관한 pre-existing — 별도 change-req 후속.
+1
View File
@@ -48,3 +48,4 @@
| BAT-038 | standard | release-candidate | REQ-018 (CHG-20260612-01) | dcy-026 (issue #24) | [bat-038](./bat-038_20260612_sell-transaction-tax-fee/) | | BAT-038 | standard | release-candidate | REQ-018 (CHG-20260612-01) | dcy-026 (issue #24) | [bat-038](./bat-038_20260612_sell-transaction-tax-fee/) |
| BAT-039 | standard | release-candidate | REQ-011 (CHG-20260612-01) | 없음 (issue #4) | [bat-039](./bat-039_20260612_paper-seq-redis-persistence/) | | BAT-039 | standard | release-candidate | REQ-011 (CHG-20260612-01) | 없음 (issue #4) | [bat-039](./bat-039_20260612_paper-seq-redis-persistence/) |
| BAT-040 | standard | release-candidate | REQ-002 (CHG-20260612-01), REQ-006 (CHG-20260612-01), REQ-073 (CHG-20260612-02) | 없음 (issue #25) | [bat-040](./bat-040_20260612_slots-rpc-db-filter/) | | BAT-040 | standard | release-candidate | REQ-002 (CHG-20260612-01), REQ-006 (CHG-20260612-01), REQ-073 (CHG-20260612-02) | 없음 (issue #25) | [bat-040](./bat-040_20260612_slots-rpc-db-filter/) |
| BAT-041 | standard | release-candidate | REQ-076, REQ-077, REQ-078 | dcy-027 (issue #20 이월) | [bat-041](./bat-041_20260612_long-term-instance-profile/) |
@@ -1,13 +1,13 @@
# 0. 문서 상태 # 0. 문서 상태
- 상태: draft - 상태: confirmed
- 문서 ID: dcy-027_20260612_long-term-strategy-slot-operation - 문서 ID: dcy-027_20260612_long-term-strategy-slot-operation
- 이슈명: 중장기 매매 전략용 슬롯 운영 도입 — 구현 위치 결정 + 멀티데이 리스크 규칙 + 운영 프로파일 (issue #20) - 이슈명: 중장기 매매 전략용 슬롯 운영 도입 — 구현 위치 결정 + 멀티데이 리스크 규칙 + 운영 프로파일 (issue #20)
- 작성 시각(KST): 2026-06-12 - 작성 시각(KST): 2026-06-12
- 마지막 갱신 시각(KST): 2026-06-12 - 마지막 갱신 시각(KST): 2026-06-12 (draft-req: REQ-076·077·078 생성 — 생성된 REQ 참조 갱신)
- 대체됨: 없음 - 대체됨: 없음
- 후속 Discovery 참조: 없음 - 후속 Discovery 참조: 없음
- 생성된 REQ 참조: 없음 - 생성된 REQ 참조: REQ-076(watchdog timeout per-instance TOML), REQ-077(중장기 운영 프로파일), REQ-078(중장기 baseline 정합)
# 1. 계획 상태 요약 # 1. 계획 상태 요약
@@ -31,11 +31,11 @@
- **이슈 #20 의 충돌 분석 중 이미 해소된 항목**: `daily_slot` 일일 리셋·preallocation 은 BAT-030 cutover 로 폐기됨(영구 자산 슬롯이 날 경계를 넘어 holding 유지 — `trex_core/domain/slot/`, `(instance_key, portfolio_id)` 키). `closed→idle` 재진입·`entry_count` 도 폐기됨(BAT-030/034). 장 마감 강제 청산·safety-sell 은 `safety_sell_enabled=false`(BAT-036, per-instance TOML)로 인스턴스 단위 옵트아웃 가능. 다중 인스턴스 토폴로지(dcy-009)·per-instance TOML(REQ-048)·config-dir(BAT-026)로 `instance_key=longterm` 독립 데몬 운영이 이미 가능. - **이슈 #20 의 충돌 분석 중 이미 해소된 항목**: `daily_slot` 일일 리셋·preallocation 은 BAT-030 cutover 로 폐기됨(영구 자산 슬롯이 날 경계를 넘어 holding 유지 — `trex_core/domain/slot/`, `(instance_key, portfolio_id)` 키). `closed→idle` 재진입·`entry_count` 도 폐기됨(BAT-030/034). 장 마감 강제 청산·safety-sell 은 `safety_sell_enabled=false`(BAT-036, per-instance TOML)로 인스턴스 단위 옵트아웃 가능. 다중 인스턴스 토폴로지(dcy-009)·per-instance TOML(REQ-048)·config-dir(BAT-026)로 `instance_key=longterm` 독립 데몬 운영이 이미 가능.
- **잔여 갭 1 — 멀티데이 보호장치 부재**: `SafetySellService`(`trex_core/application/safety_sell.py`)의 stop-loss·forced_close·market-close 는 전부 safety-sell 워커에 묶여 있고 watchdog-gated(당일 전략 이탈 전제)다. `safety_sell_enabled=false` 인 중장기 인스턴스는 **어떤 코어 보호장치도 없다** — 멀티데이 stop-loss·트레일링 스톱·타임스톱이 존재하지 않는다. - **잔여 갭 1 — 멀티데이 보호장치 부재**: `SafetySellService`(`trex_core/application/safety_sell.py`)의 stop-loss·forced_close·market-close 는 전부 safety-sell 워커에 묶여 있고 watchdog-gated(당일 전략 이탈 전제)다. `safety_sell_enabled=false` 인 중장기 인스턴스는 **어떤 코어 보호장치도 없다** — 멀티데이 stop-loss·트레일링 스톱·타임스톱이 존재하지 않는다.
- **잔여 갭 2 — watchdog 의미 충돌**: 워치독 timeout 기본 120s(REQ-034)는 분 단위 폴링 당일 전략 전제다. 하루 1~2회 체크하는 중장기 전략은 watchdog expired 가 일상 상태가 되어 가시화 신호로서 의미를 잃는다. - **잔여 갭 2 — watchdog 의미 충돌**: 워치독 timeout 기본 120s(REQ-034)는 분 단위 폴링 당일 전략 전제다. 하루 1~2회 체크하는 중장기 전략은 watchdog expired 가 일상 상태가 되어 가시화 신호로서 의미를 잃는다.
- **잔여 갭 3 — forced_close_at 의미**: 당일 청산 시각 전제 필드(REQ-032/037)로, 중장기에서는 타임스톱으로 재해석하거나 미사용 처리할지 미정. - **잔여 갭 3 — forced_close_at 의미**: 당일 청산 시각 전제 필드(REQ-032/037)로, 중장기에서는 타임스톱으로 재해석하거나 미사용 처리할지 미정이었음 → **미사용(의미 없음)으로 확정** (§10 CONFIRM, FR-4).
- **잔여 갭 4 — 운영 프로파일 부재**: 중장기 인스턴스용 표준 TOML 구성(safety_sell off + market-close 미설정 + watchdog 완화 + max_active_slots)이 문서화·검증된 적 없다. baseline 문서(`docs/project-structure.md`, `docs/runtime-flows.md`)는 존재·최신이나 당일 운영 중심으로 기술돼 있다. - **잔여 갭 4 — 운영 프로파일 부재**: 중장기 인스턴스용 표준 TOML 구성(safety_sell off + market-close 미설정 + watchdog 완화 + max_active_slots)이 문서화·검증된 적 없다. baseline 문서(`docs/project-structure.md`, `docs/runtime-flows.md`)는 존재·최신이나 당일 운영 중심으로 기술돼 있다.
- 기대 상태 - 기대 상태
- 구현 위치가 확정된다 — 현행 영구 슬롯 모델이 멀티데이 보유를 이미 지원하므로, 이슈 권장안(a)의 "신규 `PositionBook` 도메인"이 여전히 필요한지, 아니면 **기존 슬롯 도메인 + 중장기 운영 프로파일 + 멀티데이 리스크 규칙 추가**로 충분한지 판정된다. - 구현 위치 확정 완료 — **(a′) 기존 슬롯 도메인 + 중장기 운영 프로파일 추가** (OQ-1 Answered). 신규 `PositionBook` 도메인 불필요.
- `safety_sell_enabled=false` 인스턴스에도 선택 가능한 멀티데이 보호 규칙(stop-loss/트레일링/타임스톱 중 합의된 범위)이 제공되거나, "전략 모듈 전적 책임"으로 명시적으로 선을 긋는다. - 보호 규칙 경계 확정 완료 — **"전략 모듈 전적 책임"으로 명시적으로 선을 긋는다** (OQ-2 Answered). 코어는 운영자 CLI `exit` 백스톱만 제공하며, 이 경계가 운영 문서에 명시된다.
- 중장기 인스턴스 운영 프로파일(TOML 레시피 + watchdog 정책)이 baseline 문서에 기술되고 실 데몬으로 검증된다. - 중장기 인스턴스 운영 프로파일(TOML 레시피 + watchdog 정책)이 baseline 문서에 기술되고 실 데몬으로 검증된다.
- 이해관계자 - 이해관계자
- 운영자/시스템 관리자 (Justin Ko): 당일·중장기 인스턴스 병행 운영, 리스크 정책 결정 - 운영자/시스템 관리자 (Justin Ko): 당일·중장기 인스턴스 병행 운영, 리스크 정책 결정
@@ -55,16 +55,17 @@
# 4. 이번에 정의할 변경 # 4. 이번에 정의할 변경
- 구현 위치 판정 — 이슈 OQ-1 을 현행 영구 슬롯 모델 기준으로 재평가: (a′) 기존 슬롯 도메인 그대로 + 중장기 운영 프로파일/리스크 규칙 추가(현행 토대 활용, 유력) vs (a) 신규 `PositionBook` 도메인 병렬 추가(이슈 원안) vs (c) 별도 레포(이슈가 이미 비권장). 판정 결과를 REQ 분할의 전제로 확정한다. - 구현 위치 확정 — **(a′) 기존 슬롯 도메인 유지 + 중장기 운영 프로파일/규칙 추가** (OQ-1 Answered). 현행 영구 자산 슬롯이 멀티데이 보유를 구조적으로 지원하므로 신규 `PositionBook` 도메인(이슈 원안 (a))·별도 레포((c))는 미채택. 이 판정이 REQ 분할의 전제다.
- 멀티데이 리스크 규칙 범위 확정 — `safety_sell_enabled=false` 인스턴스의 보호장치: 멀티데이 stop-loss·트레일링 스톱·타임스톱(forced_close_at 재해석) 중 코어가 제공할 범위 vs 전략 모듈 전적 책임 경계를 결정한다. - 멀티데이 보호 규칙 경계 확정 — **전략 모듈 전적 책임** (OQ-2 Answered). `safety_sell_enabled=false` 인스턴스는 코어 손절매 루틴(stop-loss·트레일링·타임스톱)을 일절 사용하지 않고, 코어는 운영자 CLI `exit` 백스톱만 제공한다. 이 경계를 운영 문서에 명시한다(FR-2).
- 중장기 인스턴스 운영 프로파일 정의 — 표준 TOML 레시피(safety_sell off·market-close 미설정·watchdog timeout 정책·max_active_slots) + watchdog 의미 재검토(중장기 폴링 주기 대응) + baseline 문서(`docs/project-structure.md`, `docs/runtime-flows.md`) 중장기 운영 흐름 갱신. - 중장기 인스턴스 운영 프로파일 정의 — 표준 TOML 레시피(safety_sell off·market-close 미설정·watchdog per-instance timeout(OQ-3: TOML `watchdog_timeout_sec`, 중장기 1800s 이상)·max_active_slots) + baseline 문서(`docs/project-structure.md`, `docs/runtime-flows.md`) 중장기 운영 흐름 갱신.
## 영향 범위 ## 영향 범위
- 코드 영향: - 코드 영향 (구현 위치 (a′) 확정 기준 — OQ-1):
- 구현 위치 판정 (a′) 기준: `trex_core/runtime/config.py`(중장기 프로파일 관련 설정 항목), `trex_core/application/safety_sell.py` 또는 신규 멀티데이 리스크 워커(규칙 범위 결정에 따름), watchdog timeout per-instance 정책(`trex_core/application/watchdog.py` + config) - `trex_core/runtime/config.py` — 중장기 프로파일 설정: per-instance TOML `watchdog_timeout_sec`(OQ-3, 현재 env `TREX_WATCHDOG_TIMEOUT` 전용 → TOML 우선 지원)
- 구현 위치 판정 (a) 기준: 신규 도메인 패키지(`trex_core/domain/position_book/` 류) + persistence + 워커 — 범위 대폭 확대 - watchdog timeout per-instance 적용 경로(`trex_core/application/watchdog.py` + config wiring)
- 당일 경로(`domain/slot/`, `intents.py`, `execution.py`)는 양쪽 모두 불변 목표 - 멀티데이 리스크 워커/`safety_sell.py` 변경 **없음** — OQ-2 결정(코어 손절매 루틴 미제공, 전략 전적 책임)으로 코어 보호 규칙 구현이 범위에서 제외됨
- 당일 경로(`domain/slot/`, `intents.py`, `execution.py`)는 불변 목표
- 문서 영향: - 문서 영향:
- `docs/project-structure.md`·`docs/runtime-flows.md`: 중장기 인스턴스 운영 흐름·프로파일 추가 (baseline 갱신) - `docs/project-structure.md`·`docs/runtime-flows.md`: 중장기 인스턴스 운영 흐름·프로파일 추가 (baseline 갱신)
- 신규 REQ 다수 예상(운영 프로파일·리스크 규칙) + REQ-048(TOML 설정)·REQ-034(watchdog) change-req 가능성 - 신규 REQ 다수 예상(운영 프로파일·리스크 규칙) + REQ-048(TOML 설정)·REQ-034(watchdog) change-req 가능성
@@ -79,7 +80,7 @@
- FR-1: 중장기 인스턴스 운영 프로파일을 정의한다 — 표준 TOML 레시피(safety_sell_enabled=false, market-close 미설정, watchdog 정책, max_active_slots)와 기동 검증 절차를 문서화하고 실 데몬으로 확인한다. - FR-1: 중장기 인스턴스 운영 프로파일을 정의한다 — 표준 TOML 레시피(safety_sell_enabled=false, market-close 미설정, watchdog 정책, max_active_slots)와 기동 검증 절차를 문서화하고 실 데몬으로 확인한다.
- FR-2: `safety_sell_enabled=false` 인스턴스는 코어 손절매 루틴을 일절 사용하지 않는다. 모든 보호 규칙(stop-loss·트레일링·타임스톱)은 전략 모듈의 전적 책임이며, 코어는 운영자 CLI `exit` 백스톱만 제공한다. 이 경계를 운영 문서에 명시한다. - FR-2: `safety_sell_enabled=false` 인스턴스는 코어 손절매 루틴을 일절 사용하지 않는다. 모든 보호 규칙(stop-loss·트레일링·타임스톱)은 전략 모듈의 전적 책임이며, 코어는 운영자 CLI `exit` 백스톱만 제공한다. 이 경계를 운영 문서에 명시한다.
- FR-3: watchdog 의 중장기 의미를 재정의한다 — per-instance timeout 설정 또는 중장기 인스턴스 비활성화 옵션 중 확정안을 적용해, 저빈도 폴링 전략이 expired 로 오인되지 않게 한다. - FR-3: watchdog 의 중장기 의미를 재정의한다 — per-instance timeout 설정 또는 중장기 인스턴스 비활성화 옵션 중 확정안을 적용해, 저빈도 폴링 전략이 expired 로 오인되지 않게 한다.
- FR-4: `forced_close_at` 의 중장기 의미(타임스톱 재해석 vs 미사용)를 확정하고 운영자 CLI·intent 계약 문서에 반영한다. - FR-4: `forced_close_at` 은 중장기 인스턴스에서 **미사용(의미 없음)** 으로 확정한다 (CONFIRM 2026-06-12). 집행 주체(safety-sell 워커)가 OFF 이므로 구조적으로 발화 불가 — 운영자 CLI·intent 계약 문서에 "중장기 인스턴스에서 미사용"을 명시한다.
- FR-5: `docs/project-structure.md`·`docs/runtime-flows.md` 에 중장기 인스턴스 운영 구조와 흐름(병행 인스턴스 토폴로지, 프로파일, 리스크 규칙 경로)을 갱신한다. - FR-5: `docs/project-structure.md`·`docs/runtime-flows.md` 에 중장기 인스턴스 운영 구조와 흐름(병행 인스턴스 토폴로지, 프로파일, 리스크 규칙 경로)을 갱신한다.
## 비기능 요구사항 ## 비기능 요구사항
@@ -100,7 +101,7 @@
- 중장기 전략 모듈 자체의 구현(별도 저장소/주기 — 코어는 계약·운영 토대만 제공) - 중장기 전략 모듈 자체의 구현(별도 저장소/주기 — 코어는 계약·운영 토대만 제공)
- 포트폴리오 리밸런싱 엔진·목표 비중 관리(전략 모듈 책임) - 포트폴리오 리밸런싱 엔진·목표 비중 관리(전략 모듈 책임)
- 일일 회계·리포트 체계 개편(현행 ledger 날짜 필터 해석 유지, 필요 시 후속) - 일일 회계·리포트 체계 개편(현행 ledger 날짜 필터 해석 유지, 필요 시 후속)
- {{DECIDE_POSITION_BOOK}}: 신규 `PositionBook` 도메인 신설 — 구현 위치 판정이 (a) 로 결정될 때만 In Scope 로 승격 - 신규 `PositionBook` 도메인 신설 — **Out of Scope 확정** (OQ-1: 구현 위치 (a′) 결정으로 미채택)
# 6. 리스크/가정 목록 # 6. 리스크/가정 목록
@@ -108,8 +109,8 @@
- R-1 (High): 보호장치 경계가 모호한 채 중장기 인스턴스를 운영하면, safety-sell OFF + 전략 모듈 장애 시 손실 포지션이 무기한 방치된다. - R-1 (High): 보호장치 경계가 모호한 채 중장기 인스턴스를 운영하면, safety-sell OFF + 전략 모듈 장애 시 손실 포지션이 무기한 방치된다.
- 완화 방안: **전략 모듈 전적 책임으로 확정(OQ-2)**. 코어는 운영자 CLI `exit` 백스톱만 제공. 보호 규칙 공백 위험은 전략 모듈 설계 및 운영 절차(운영자 수동 CLI)로 완화한다. - 완화 방안: **전략 모듈 전적 책임으로 확정(OQ-2)**. 코어는 운영자 CLI `exit` 백스톱만 제공. 보호 규칙 공백 위험은 전략 모듈 설계 및 운영 절차(운영자 수동 CLI)로 완화한다.
- R-2 (Medium): 멀티데이 규칙을 기존 safety-sell 워커에 얹으면 당일 경로와 조건 분기가 얽혀 회귀 위험(이슈가 경고한 "분기 폭증")이 생긴다. - R-2 (해소됨 — OQ-2): 멀티데이 규칙을 기존 safety-sell 워커에 얹으면 당일 경로와 조건 분기가 얽혀 회귀 위험(이슈가 경고한 "분기 폭증")이 생긴다.
- 완화 방안: 설계 단계에서 별도 워커/서비스 분리를 우선 검토하고, 당일 경로 테스트 전부를 회귀 게이트로 고정한다. - 해소 근거: OQ-2 결정으로 코어가 멀티데이 보호 규칙을 구현하지 않으므로 safety-sell 워커 변경 자체가 없다. 당일 경로 테스트 전체는 NFR-1 무회귀 게이트로 유지.
- R-3 (Medium): watchdog 완화가 과도하면 중장기 인스턴스에서 전략 이탈을 영영 감지하지 못한다. - R-3 (Medium): watchdog 완화가 과도하면 중장기 인스턴스에서 전략 이탈을 영영 감지하지 못한다.
- 완화 방안: 비활성화 대신 긴 timeout(예: 일 단위) 설정을 기본안으로 검토하고, slots RPC `watchdog.last_seen` 가시성은 유지한다. - 완화 방안: 비활성화 대신 긴 timeout(예: 일 단위) 설정을 기본안으로 검토하고, slots RPC `watchdog.last_seen` 가시성은 유지한다.
- R-4 (Low): 멀티데이 보유 중 universe 에서 자산이 빠지면(상폐 등) tombstone/retire 경로가 중장기 보유와 충돌할 수 있다. - R-4 (Low): 멀티데이 보유 중 universe 에서 자산이 빠지면(상폐 등) tombstone/retire 경로가 중장기 보유와 충돌할 수 있다.
@@ -118,7 +119,7 @@
## 가정 ## 가정
- A-1: 현행 영구 자산 슬롯(BAT-027~030)은 멀티데이 보유를 구조적으로 이미 지원한다 — 슬롯 키에 날짜가 없고 holding 이 날 경계·재시작을 넘는다(dcy-020 OQ-3 결정). - A-1: 현행 영구 자산 슬롯(BAT-027~030)은 멀티데이 보유를 구조적으로 이미 지원한다 — 슬롯 키에 날짜가 없고 holding 이 날 경계·재시작을 넘는다(dcy-020 OQ-3 결정).
- A-2: 중장기 운영은 별도 `instance_key` 데몬으로 분리한다(이슈 OQ-3 의 권장 방향) — 단일 데몬 내 호라이즌 혼합은 다루지 않는다. - A-2: 중장기 운영은 별도 `instance_key` 데몬으로 분리한다(이슈 OQ-3 의 권장 방향) — 단일 데몬 내 호라이즌 혼합은 다루지 않는다. **(승인 확정 — 2026-06-12, Justin Ko, §10 CONFIRM)**
- A-3: 플랫폼 계층(브로커/ledger/transport/slots RPC/CLI)은 변경 없이 공유 가능하다 — 이슈의 재사용 분석 유지. - A-3: 플랫폼 계층(브로커/ledger/transport/slots RPC/CLI)은 변경 없이 공유 가능하다 — 이슈의 재사용 분석 유지.
- A-4: KIS·portfolio-hub·RabbitMQ 인프라를 그대로 사용하므로 별도 레포 분리(이슈 OQ-1 의 (c))는 손해다 — 이슈 분석 수용. - A-4: KIS·portfolio-hub·RabbitMQ 인프라를 그대로 사용하므로 별도 레포 분리(이슈 OQ-1 의 (c))는 손해다 — 이슈 분석 수용.
@@ -145,36 +146,41 @@
# 7. 초기 성공 기준 # 7. 초기 성공 기준
- S-1: `instance_key=longterm` 프로파일 TOML 로 기동한 데몬에서 매수→day 경계 통과→보유 유지가 확인된다(장 마감·재시작 후에도 holding 유지, 자율 청산 미발화). - S-1: `instance_key=longterm` 프로파일 TOML 로 기동한 데몬에서 매수→day 경계 통과→보유 유지가 확인된다(장 마감·재시작 후에도 holding 유지, 자율 청산 미발화).
- S-2: 확정된 멀티데이 보호 규칙(코어 제공분)이 발화 조건에서 정확히 동작하고, 미제공 영역은 운영 문서에 전략 책임으로 명시된다. - S-2: 보호 규칙 경계가 운영 문서에 명시된다 — `safety_sell_enabled=false` 인스턴스의 모든 보호 규칙(stop-loss·트레일링·타임스톱)은 전략 모듈 전적 책임(OQ-2), 코어는 운영자 CLI `exit` 백스톱만 제공. 백스톱 경로(CLI exit → 청산)가 중장기 인스턴스에서 동작함이 확인된다.
- S-3: 당일 인스턴스 전체 테스트 무회귀 + 중장기 인스턴스와의 무간섭(슬롯·큐 스코프)이 통합 테스트로 검증된다. - S-3: 당일 인스턴스 전체 테스트 무회귀 + 중장기 인스턴스와의 무간섭(슬롯·큐 스코프)이 통합 테스트로 검증된다.
- S-4: baseline 문서(project-structure·runtime-flows)에 중장기 운영 구조·흐름이 반영되어 이후 REQ/batch 가 참조 가능하다. - S-4: baseline 문서(project-structure·runtime-flows)에 중장기 운영 구조·흐름이 반영되어 이후 REQ/batch 가 참조 가능하다.
## 측정 방식 및 데이터 출처 ## 측정 방식 및 데이터 출처
- S-1 측정 방식: 실 데몬 run-the-app 검증 — longterm TOML 기동 후 paper 매수, 시스템 시각/일자 경계 시나리오로 holding 유지 확인 / 데이터 출처: `run-trex-core-daemon` skill 경로 + 데몬 로그 + `trex-core slots` - S-1 측정 방식: 실 데몬 run-the-app 검증 — longterm TOML 기동 후 paper 매수, 시스템 시각/일자 경계 시나리오로 holding 유지 확인 / 데이터 출처: `run-trex-core-daemon` skill 경로 + 데몬 로그 + `trex-core slots`
- S-2 측정 방식: 규칙별 단위 테스트(발화/비발화 경계) + paper e2e / 데이터 출처: `tests/application/` 신규 테스트 - S-2 측정 방식: 운영 문서(baseline) diff 리뷰 — 보호 규칙 경계(전략 책임 + CLI 백스톱) 명시 확인 + 중장기 프로파일 인스턴스에서 `trex-core exit` 백스톱 paper 동작 확인 / 데이터 출처: `docs/runtime-flows.md` + 데몬 로그·`trex-core slots`
- S-3 측정 방식: `pytest` 전체 회귀 + instance_key 격리 통합 테스트(기존 `test_instance_topology.py` 패턴 확장) / 데이터 출처: 로컬/CI 결과 - S-3 측정 방식: `pytest` 전체 회귀 + instance_key 격리 통합 테스트(기존 `test_instance_topology.py` 패턴 확장) / 데이터 출처: 로컬/CI 결과
- S-4 측정 방식: baseline 문서 diff 리뷰 — 중장기 흐름 섹션 존재 + 현행 코드와 정합 / 데이터 출처: `docs/project-structure.md`, `docs/runtime-flows.md` - S-4 측정 방식: baseline 문서 diff 리뷰 — 중장기 흐름 섹션 존재 + 현행 코드와 정합 / 데이터 출처: `docs/project-structure.md`, `docs/runtime-flows.md`
# 8. REQ로 넘기기 전 확인 체크 # 8. REQ로 넘기기 전 확인 체크
- [ ] 범위(In Scope/Out of Scope) 확정 - [x] 범위(In Scope/Out of Scope) 확정
- 기준: OQ-1(구현 위치) 확정으로 {{DECIDE_POSITION_BOOK}} In/Out 판정 + OQ-2(보호 규칙 범위) 확정 - 기준: OQ-1(구현 위치 a′)·OQ-2(보호 규칙 전략 책임) Answered + A-2(병행 인스턴스) 승인 + forced_close_at 미사용 확정으로 충족 — PositionBook Out of Scope 확정. CONFIRM 잔여 0건
- [ ] 성공 지표의 측정 방식/데이터 출처 확정 - 결과: 충족 — In/Out 경계 §5 확정, 사용자 결정 잔여 0건
- 기준: OQ-4(전략 폴링 주기) 수치 확보로 S-1/watchdog 검증 수치 구체화 - [x] 성공 지표의 측정 방식/데이터 출처 확정
- [ ] High 영향 리스크 우선순위 및 해소 책임자 지정 - 기준: OQ-4 수치(1~10분 폴링, watchdog 1800s 이상) 확보로 S-1/watchdog 검증 수치 구체화 완료
- 기준: R-1(보호장치 공백) 경계 확정 담당 Justin Ko - 결과: 충족 — S-1~S-4 측정 방식·데이터 출처 §7 명시
- [ ] 모든 오픈 질문(OQ) `Open` 상태 없음 확인 - [x] High 영향 리스크 우선순위 및 해소 책임자 지정
- 기준: OQ 목록에 `Open` 상태 항목이 0개이거나, 잔여 항목 전부 `Deferred` 처리 완료 - 기준: R-1(보호장치 공백) — OQ-2 결정으로 경계 확정(전략 책임 + CLI 백스톱), 담당 Justin Ko
- [ ] Freeze 확정 담당자(CONFIRMED_BY) 지정 - 결과: 충족 — R-1 경계 확정, R-2 해소(OQ-2), R-3/R-4 완화 방안 확정
- 기준: Justin Ko 승인 후 `confirm-discovery` 수행 - [x] 모든 오픈 질문(OQ) `Open` 상태 없음 확인
- 기준: OQ-1~4 전부 Answered — `Open` 항목 0개
- 결과: 충족 — Open 0건
- [x] Freeze 확정 담당자(CONFIRMED_BY) 지정
- 기준: §10 CONFIRM 2건 해소 + Justin Ko 승인 후 `confirm-discovery` 수행
- 결과: 충족 — Justin Ko 승인, confirm-discovery 수행 완료
# 9. 파일 처리 결과 # 9. 파일 처리 결과
- 처리 결과: 생성 - 처리 결과: 생성
- 생성 경로: docs/discovery/dcy-027_20260612_long-term-strategy-slot-operation.md - 생성 경로: docs/discovery/dcy-027_20260612_long-term-strategy-slot-operation.md
- 참조 문서: - 참조 문서:
- GitHub issue #20 — 중장기 슬롯 운영 도입, 호라이즌 결합 분석 + OQ-1~4 (구 pool 모델 기준 — 본 문서 §3 에서 현행 기준 재정렬) - GitHub issue #20 (closed — 본 Discovery 로 이월) — 중장기 슬롯 운영 도입, 호라이즌 결합 분석 + OQ-1~4 (구 pool 모델 기준 — 본 문서 §3 에서 현행 기준 재정렬)
- docs/discovery/dcy-020_20260610_asset-centric-slot-model.md — 영구 자산 슬롯 토대(Out of Scope 로 본 주기 이월 명시), OQ-3 trading_date 폐기 결정 - docs/discovery/dcy-020_20260610_asset-centric-slot-model.md — 영구 자산 슬롯 토대(Out of Scope 로 본 주기 이월 명시), OQ-3 trading_date 폐기 결정
- docs/discovery/dcy-024_20260611_safety-sell-disable-mode.md — safety_sell_enabled per-instance TOML ("중장기 보유 인스턴스는 enabled=false" 용도 명시) - docs/discovery/dcy-024_20260611_safety-sell-disable-mode.md — safety_sell_enabled per-instance TOML ("중장기 보유 인스턴스는 enabled=false" 용도 명시)
- trex_core/application/safety_sell.py — 당일 전제 보호장치(watchdog-gated stop-loss/forced_close/market-close), 멀티데이 규칙 부재 확인 - trex_core/application/safety_sell.py — 당일 전제 보호장치(watchdog-gated stop-loss/forced_close/market-close), 멀티데이 규칙 부재 확인
@@ -192,12 +198,12 @@
## CONFIRM ## CONFIRM
- {{CONFIRM_PARALLEL_INSTANCE}}: 중장기 운영을 별도 instance_key 데몬으로 분리하는 전제(A-2) 승인 - ~~{{CONFIRM_PARALLEL_INSTANCE}}~~: 중장기 운영을 **별도 instance_key 데몬으로 분리** — 승인 확정 (A-2, 2026-06-12, Justin Ko). 단일 데몬 내 호라이즌 혼합은 다루지 않으며, dcy-009 토폴로지·REQ-048 per-instance TOML 전제 위에서 병행 운영한다.
- {{CONFIRM_FORCED_CLOSE_SEMANTICS}}: forced_close_at 의 중장기 의미 — 타임스톱 재해석 vs 미사용 (FR-4) - ~~{{CONFIRM_FORCED_CLOSE_SEMANTICS}}~~: forced_close_at 의 중장기 의미 — **미사용(의미 없음) 확정** (FR-4, 2026-06-12, Justin Ko). 중장기 모드에서 forced_close_at 은 의미가 없다. 집행 주체(safety-sell 워커)가 `safety_sell_enabled=false` 로 OFF 이므로 구조적으로 발화 불가하며, 타임스톱 재해석(별도 집행 경로 신설)은 OQ-2 결정(코어 손절매 미제공)과 충돌해 미채택. 운영자 CLI·intent 계약 문서에 "중장기 인스턴스에서 미사용" 명시.
## DATA ## DATA
- {{DATA_STRATEGY_CADENCE}}: 중장기 전략 모듈의 폴링·intent 발신 주기 계획 (OQ-4 — watchdog·프로파일 수치 입력) - 없음 — 중장기 전략 모듈의 폴링·intent 발신 주기는 OQ-4 에서 확보됨(answered): 최소 1분~최대 10분(600s) 주기, watchdog timeout 권장 1800초(30분) 이상의 입력값.
# 11. Discovery Freeze # 11. Discovery Freeze
@@ -211,8 +217,8 @@
## Freeze 플래그 ## Freeze 플래그
- Handoff Decision: 보류 - Handoff Decision: 승인
- Handoff Rationale: 초기 Discovery 초안 생성 완료. OQ-1(구현 위치)·OQ-2(보호 규칙)·OQ-3(watchdog)·OQ-4(전략 주기) 4건 Open — confirm-discovery 전 사용자 결정 필요. - Handoff Rationale: OQ-1(구현 위치 a′)·OQ-2(보호 규칙 전략 전적 책임)·OQ-3(watchdog timeout 연장)·OQ-4(폴링 주기 1~10분) 4건 모두 Answered. CONFIRM 2건도 확정 — A-2(별도 instance_key 분리) 승인 + forced_close_at 중장기 미사용(의미 없음). 사용자 결정 잔여 0건. 구현 부재는 정상(신규 작업 정의 Discovery — 코어 변경은 watchdog per-instance TOML 정도로 경량).
- Ready for REQ Drafting: false - Ready for REQ Drafting: true
- Confirmed By: TBD - Confirmed By: Justin Ko
- Confirmed At (KST): TBD - Confirmed At (KST): 2026-06-12
+1 -1
View File
@@ -32,4 +32,4 @@
| dcy-024_20260611_safety-sell-disable-mode | 20260611 | safety-sell 자율 청산 비활성화 모드 — per-instance TOML `safety_sell_enabled=false` 시 워커 미기동 (issue #15) | [Discovery](./dcy-024_20260611_safety-sell-disable-mode.md) | confirmed | | dcy-024_20260611_safety-sell-disable-mode | 20260611 | safety-sell 자율 청산 비활성화 모드 — per-instance TOML `safety_sell_enabled=false` 시 워커 미기동 (issue #15) | [Discovery](./dcy-024_20260611_safety-sell-disable-mode.md) | confirmed |
| dcy-025_20260611_strategy-read-surface-slots-req | 20260611 | 전략 read 표면 재설계 — `trex.snapshot.req`→`trex.slots.req` + 슬롯 상태 필터 + 슬롯 단일 행(assets[]/available_slots/watchdog[] 흡수·제거) | [Discovery](./dcy-025_20260611_strategy-read-surface-slots-req.md) | confirmed | | dcy-025_20260611_strategy-read-surface-slots-req | 20260611 | 전략 read 표면 재설계 — `trex.snapshot.req`→`trex.slots.req` + 슬롯 상태 필터 + 슬롯 단일 행(assets[]/available_slots/watchdog[] 흡수·제거) | [Discovery](./dcy-025_20260611_strategy-read-surface-slots-req.md) | confirmed |
| dcy-026_20260612_sell-transaction-tax-and-fee-policy | 20260612 | 매도 증권거래세 누락 + 수수료 정책 정정 — `tax=0.0` 하드코딩 제거 + 단일 정책 지점 (issue #24) | [Discovery](./dcy-026_20260612_sell-transaction-tax-and-fee-policy.md) | confirmed | | dcy-026_20260612_sell-transaction-tax-and-fee-policy | 20260612 | 매도 증권거래세 누락 + 수수료 정책 정정 — `tax=0.0` 하드코딩 제거 + 단일 정책 지점 (issue #24) | [Discovery](./dcy-026_20260612_sell-transaction-tax-and-fee-policy.md) | confirmed |
| dcy-027_20260612_long-term-strategy-slot-operation | 20260612 | 중장기 매매 전략용 슬롯 운영 도입 — 구현 위치 결정 + 멀티데이 리스크 규칙 + 운영 프로파일 (issue #20, dcy-020 Out-of-Scope 이월) | [Discovery](./dcy-027_20260612_long-term-strategy-slot-operation.md) | draft | | dcy-027_20260612_long-term-strategy-slot-operation | 20260612 | 중장기 매매 전략용 슬롯 운영 도입 — 구현 위치 결정 + 멀티데이 리스크 규칙 + 운영 프로파일 (issue #20, dcy-020 Out-of-Scope 이월) | [Discovery](./dcy-027_20260612_long-term-strategy-slot-operation.md) | confirmed |
+12
View File
@@ -212,6 +212,17 @@
4. **broadcast**: `trex.broadcast` 에 publish → 모든 인스턴스 control 큐 동시 전달(global kill-switch 등). 중복 처리는 idempotency 로 무해. 4. **broadcast**: `trex.broadcast` 에 publish → 모든 인스턴스 control 큐 동시 전달(global kill-switch 등). 중복 처리는 idempotency 로 무해.
5. 기존 reply-to RPC 직접 큐 경로(default)는 그대로 동작 — 추가 바인딩은 비파괴적(FR-5 무회귀). 5. 기존 reply-to RPC 직접 큐 경로(default)는 그대로 동작 — 추가 바인딩은 비파괴적(FR-5 무회귀).
## Flow: `중장기(멀티데이) 인스턴스 병행 운영` (BAT-041 / dcy-027, REQ-076~078)
중장기 매매 전략용 인스턴스를 당일 인스턴스와 같은 인프라(PostgreSQL·RabbitMQ)에서 병행 운영하는 프로파일. 별도 도메인 없이 영구 자산 슬롯(dcy-020) + per-instance 설정 조합으로 구성한다(dcy-027 OQ-1 (a′)).
1. **분리 기동**: 중장기 데몬은 전용 `instance_key`(예: `longterm`) TOML 로 기동(`config/longterm.example.toml` 표준 레시피) — 슬롯(DB instance_key 스코프)·큐(`trex.slots.req.longterm` 등)가 당일 인스턴스와 무간섭(dcy-027 A-2, dcy-009 토폴로지).
2. **프로파일 구성**: `safety_sell_enabled = false`(자율 청산 워커 미기동, BAT-036) + `watchdog_timeout_sec = 1800` 이상(REQ-076 per-instance TOML — TOML 우선, 미설정 시 env `TREX_WATCHDOG_TIMEOUT` 폴백 기본 120s) + `max_active_slots`(REQ-060) + env `TREX_MARKET_CLOSE_LIQUIDATION_TIME` 미설정.
3. **보유 유지**: 영구 자산 슬롯은 날짜 키가 없어 holding 이 장 마감·일자 경계·재시작을 넘어 DB 에 유지된다 — 일일 리셋/강제 청산 없음.
4. **보호 규칙 경계 (dcy-027 OQ-2)**: `safety_sell_enabled=false` 인스턴스는 **코어 손절매 루틴을 일절 사용하지 않는다**. stop-loss·트레일링 스톱·타임스톱 등 모든 보호 규칙은 **전략 모듈의 전적 책임**이며, 코어가 제공하는 유일한 백스톱은 운영자 CLI `trex-core exit <slot>`(수동 청산)다. 전략 모듈 장애 시 손실 포지션 방치 위험은 전략 설계 + 운영 절차(수동 CLI)로 완화한다.
5. **forced_close_at 미사용 (dcy-027 CONFIRM)**: 중장기 인스턴스에서 `forced_close_at` 은 **의미가 없다** — 집행 주체(safety-sell 워커)가 OFF 이므로 구조적으로 발화 불가. 중장기 슬롯에 설정하지 않는다(타임스톱이 필요하면 전략 모듈이 sell intent 로 구현).
6. **watchdog 중장기 의미**: 전략 폴링 주기 1~10분 전제(dcy-027 OQ-4)에서 timeout 1800s 는 `watchdog.expired` 를 유의미한 이탈 신호로 유지한다. `last_seen` 가시성(slots RPC 행)은 그대로. 워치독 소유권 진입 시 외부 알림(슬랙·이메일)은 후속 Discovery.
## Flow: `broker degrade 분기` (BAT-007 / REQ-024) ## Flow: `broker degrade 분기` (BAT-007 / REQ-024)
1. transport connection drop → 어댑터가 `_mark_disconnected()` + 백오프(1s→2s→4s→8s→16s→30s cap) 재연결. 1. transport connection drop → 어댑터가 `_mark_disconnected()` + 백오프(1s→2s→4s→8s→16s→30s cap) 재연결.
@@ -329,3 +340,4 @@ timeout 미지정·취소 실패로 잔여 고착된 pending 슬롯을 처분:
- 2026-06-11 :: BAT-036 (dcy-024) — 데몬 lifecycle: `safety_sell_enabled=false` 시 `trex-safety-sell` worker 미기동 + WARNING 로그(REQ-048 CHG-01/REQ-005 CHG-01). - 2026-06-11 :: BAT-036 (dcy-024) — 데몬 lifecycle: `safety_sell_enabled=false` 시 `trex-safety-sell` worker 미기동 + WARNING 로그(REQ-048 CHG-01/REQ-005 CHG-01).
- 2026-06-10 :: BAT-031 (dcy-021) — 포지션 사이징(증액·부분 감액) 흐름 추가, transit 13 전이. - 2026-06-10 :: BAT-031 (dcy-021) — 포지션 사이징(증액·부분 감액) 흐름 추가, transit 13 전이.
- 2026-06-12 :: BAT-038 (dcy-026/issue #24) — 매도 증권거래세 누락 정정: `체결 → ledger outbox` flow 의 `tax=0` → `calculate_tax(side, amount)`(매도 0.2%/매수 0) 정책 계산. fee/tax 단일 정책 지점 + env(`TREX_KIS_FEE_BPS`/`TREX_KRX_TAX_SELL_BPS`), paper·KIS 동일 공유, `tax=0.0` 하드코딩 제거 (REQ-018 CHG-20260612-01). - 2026-06-12 :: BAT-038 (dcy-026/issue #24) — 매도 증권거래세 누락 정정: `체결 → ledger outbox` flow 의 `tax=0` → `calculate_tax(side, amount)`(매도 0.2%/매수 0) 정책 계산. fee/tax 단일 정책 지점 + env(`TREX_KIS_FEE_BPS`/`TREX_KRX_TAX_SELL_BPS`), paper·KIS 동일 공유, `tax=0.0` 하드코딩 제거 (REQ-018 CHG-20260612-01).
- 2026-06-12 :: BAT-041 (dcy-027/issue #20 이월) — 중장기 인스턴스 병행 운영 flow 신설: 전용 instance_key 프로파일(`config/longterm.example.toml`), `watchdog_timeout_sec` per-instance TOML(REQ-076, env 폴백), 보호 규칙 경계(전략 모듈 전적 책임 + CLI exit 백스톱, 코어 손절매 미사용), forced_close_at 중장기 미사용 (REQ-076~078).
@@ -0,0 +1,76 @@
# req-076: watchdog timeout per-instance TOML 설정
- Status: Implemented
- Type: Configuration
- Priority: High
- Owner: project maintainers
## Intent
- 워치독 timeout(현재 env `TREX_WATCHDOG_TIMEOUT` 전역 단일, 기본 120s)을 인스턴스별로 독립 설정 가능하게 한다. 저빈도 폴링(최소 1분~최대 10분, dcy-027 OQ-4) 중장기 전략 인스턴스에서 watchdog expired 가 일상 상태가 되어 가시화 신호로서 의미를 잃는 문제(dcy-027 FR-3/OQ-3)를 해소한다.
## Requirement
- `DaemonInstanceConfig`(REQ-048 per-instance TOML)에 `watchdog_timeout_sec`(정수, 초) optional 키를 추가하고 `config/*.toml` 에서 읽는다. TOML 값이 있으면 그 값이 해당 인스턴스의 워치독 timeout 으로 사용되고, 없으면 기존 env `TREX_WATCHDOG_TIMEOUT`(기본 120s) 동작을 그대로 유지한다(TOML 우선, env 폴백 — 기존 인스턴스 무회귀). 기존 per-instance TOML 메커니즘을 재사용하며 새 설정 채널을 만들지 않는다(dcy-027 NFR-3). 중장기 인스턴스 권장값은 1800s(30분) 이상(OQ-4 최대 폴링 600s 대비 여유).
## Acceptance Criteria
1. Given `config/inst.toml` 에 `watchdog_timeout_sec = 1800`, When 데몬 기동, Then 해당 인스턴스의 워치독 timeout 이 1800s 로 동작한다(120s env 기본값 미적용).
2. Given TOML 에 `watchdog_timeout_sec` 미설정, When 기동, Then 기존 env `TREX_WATCHDOG_TIMEOUT`(기본 120s) 값으로 동작한다(기존 인스턴스 byte-for-byte 무회귀).
3. Given 두 인스턴스가 서로 다른 `watchdog_timeout_sec`(예: 당일 120 / 중장기 1800), When 각자 기동, Then 인스턴스별 독립 timeout 으로 watchdog expired 판정이 분리된다.
4. Given 중장기 timeout(1800s) 인스턴스에서 전략이 10분(600s) 주기로 slots RPC(spy=false)를 호출, When watchdog 판정, Then expired 로 오인되지 않고 `last_seen_at` 가시성은 유지된다.
## Impacted Area
- Docs: docs/runtime-flows.md(워치독 설정 경로 — REQ-078 에서 정합), config/instance.example.toml
- Modules: trex_core/runtime/config.py(`DaemonInstanceConfig.watchdog_timeout_sec` + `from_toml` + `DaemonConfig` 전파), trex_core/runtime/daemon.py(워치독 timeout wiring)
- Tests: tests/runtime/test_config*.py(TOML 로딩/폴백/인스턴스 독립), watchdog 판정 경계 테스트
## Notes
- 출처: dcy-027 FR-3, OQ-3(per-instance timeout 연장 확정), OQ-4(폴링 주기 1~10분 → 권장 1800s+). NFR-3(기존 TOML 메커니즘 재사용) 흡수.
- REQ-060(max_active_slots)과 달리 **env 폴백을 유지**한다 — `TREX_WATCHDOG_TIMEOUT` 은 기존 운영 인스턴스가 사용 중이므로 폐기하지 않고 TOML 미설정 시 기본 경로로 보존(무회귀).
- 패턴 선례: `safety_sell_enabled`(REQ-048 CHG, dcy-024) — per-instance 운영 모드 차이는 TOML.
- watchdog 소유권 진입 시 외부 알림(슬랙·이메일)은 후속 Discovery(dcy-027 OQ-3 주석) — 본 REQ 범위 밖.
## Change Log
### CHG-20260612-01
- Date: 2026-06-12
- Author: Justin Ko
#### Change Summary
- dcy-027 FR-3/OQ-3/OQ-4 로부터 최초 생성(Proposed). watchdog timeout per-instance TOML + env 폴백.
#### Intent
- 중장기 저빈도 폴링 인스턴스에서 watchdog 가시화 신호를 유의미하게 유지하도록 backlog 정규화.
#### Acceptance Criteria Delta
- Added: TOML 우선/env 폴백 무회귀/인스턴스별 독립/저빈도 폴링 비오인 4개 기준
- Updated: 없음
- Removed: 없음
#### Impacted Area Delta
- Docs: instance.example.toml, runtime-flows(REQ-078 정합)
- Modules: runtime/config.py, runtime/daemon.py
- Tests: test_config*.py + watchdog 경계
#### Delivery Trace Delta
- Batches: none
- Releases: none
#### Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: keep-current
- Follow-up Action: confirm-req 후 batch 편성
#### Validation Plan
- TOML 로딩/폴백/인스턴스 독립 단위 테스트 + watchdog 판정 경계 테스트로 검증.
### CHG-20260612-02
- Date: 2026-06-12
- Author: Justin Ko
- Type: status
- Summary: Proposed → Approved (confirm-req). 필수 필드·구체 AC 충족, source dcy-027(confirmed, handoff 승인, 사용자 결정 잔여 0건)과 모순 없음. env 폴백으로 기존 인스턴스 무회귀 전제 확인.
### CHG-20260612-03
- Date: 2026-06-12
- Author: Justin Ko
- Type: status
- Summary: Approved → Implemented (confirm-req-implemented). 근거: BAT-041(release-candidate) verification — AC-1~4 전부 evidence 연결(`test_watchdog_timeout_toml.py` 7 passed: TOML 적용/TOML>env/env 폴백/기본 120/인스턴스 독립/<=0 거부/프로파일 조합). `from_config` 단일 지점 override 로 소비자 2곳(slots RPC·SafetySellService) 일치. 전체 446 passed + ruff clean. Blocking Issues 없음.
@@ -0,0 +1,78 @@
# req-077: 중장기 인스턴스 운영 프로파일 — 표준 TOML 레시피 + 기동 검증
- Status: Implemented
- Type: Configuration
- Priority: High
- Owner: project maintainers
## Intent
- 중장기 매매 전략용 인스턴스의 표준 운영 프로파일(TOML 레시피)을 정의하고 실 데몬 기동으로 검증한다. 멀티데이 보유가 장 마감·일자 경계·재시작을 넘어 유지되고 자율 청산이 발화하지 않음을 보장하는 설정 조합을 확정한다. (dcy-027 FR-1, OQ-1 (a′) — 기존 슬롯 도메인 + 프로파일 추가)
## Requirement
- 중장기 인스턴스 표준 TOML 프로파일을 예시 파일(`config/longterm.example.toml` 또는 동등)로 제공한다. 프로파일 구성: `instance_key`(예: longterm, 당일 인스턴스와 분리 — dcy-027 A-2 확정), `safety_sell_enabled = false`(REQ-048 CHG/dcy-024), `watchdog_timeout_sec = 1800` 이상(REQ-076), `max_active_slots`(REQ-060), market-close 강제 청산 미적용(env `TREX_MARKET_CLOSE_LIQUIDATION_TIME` 미설정 전제 명시).
- 이 프로파일로 기동한 데몬에서 매수 체결 후 보유가 일자 경계·재시작을 넘어 유지되고(영구 슬롯, dcy-020 토대) 자율 청산(safety-sell/market-close/forced_close)이 발화하지 않음을 검증한다.
- 당일 인스턴스와 같은 PostgreSQL·RabbitMQ 인프라에서 병행 운영 시 슬롯·큐 스코프가 간섭하지 않아야 한다(dcy-027 NFR-2 — instance_key 격리).
## Acceptance Criteria
1. Given 중장기 프로파일 TOML(safety_sell_enabled=false, watchdog_timeout_sec≥1800, max_active_slots, 독립 instance_key), When 데몬 기동, Then `trex-safety-sell` 워커 미기동(WARNING 로그) + watchdog timeout 1800s 적용 + 독립 큐(`trex.slots.req.longterm` 등) 선언이 확인된다.
2. Given 기동된 중장기 인스턴스에서 paper 매수 체결(holding), When 일자 경계 통과·데몬 재시작, Then 슬롯이 holding 을 유지하고 자율 청산이 발화하지 않는다.
3. Given 당일 인스턴스(safety_sell_enabled=true)와 중장기 인스턴스 병행, When 양쪽 기동·운영, Then 슬롯(instance_key 스코프)·큐(per-instance 큐명) 간섭이 없다(기존 instance 격리 테스트 패턴으로 검증).
4. Given 예시 프로파일 파일, When 저장소 확인, Then 표준 레시피와 각 키의 중장기 의미(왜 false/1800s 인지) 주석이 포함되어 운영자가 복제 사용 가능하다.
## Impacted Area
- Docs: config/longterm.example.toml(신규 또는 instance.example.toml 확장), docs/runtime-flows.md·docs/project-structure.md(REQ-078 정합)
- Modules: 신규 코드 없음 목표 — 기존 설정 메커니즘(REQ-048/060/076 + dcy-024) 조합. 검증 과정에서 갭 발견 시 해당 REQ change-req
- Tests: 프로파일 기동 검증(설정 조합 단위) + instance 격리 통합(기존 `test_instance_topology.py` 패턴 확장)
## Notes
- 출처: dcy-027 FR-1, S-1, A-2(별도 instance_key 확정), NFR-2(무간섭 — AC-3 흡수).
- 보호 규칙은 전략 모듈 전적 책임(dcy-027 OQ-2) — 코어는 운영자 CLI `exit` 백스톱만 제공. 프로파일은 이를 전제로 한 "보호장치 OFF" 구성이며 경계 명시는 REQ-078 소관.
- `forced_close_at` 은 중장기 인스턴스에서 미사용(dcy-027 CONFIRM) — 프로파일 주석에 명시.
- REQ-076(watchdog TOML) 선행 의존 — 같은 batch 에서 함께 구현.
## Change Log
### CHG-20260612-01
- Date: 2026-06-12
- Author: Justin Ko
#### Change Summary
- dcy-027 FR-1/S-1 로부터 최초 생성(Proposed). 중장기 표준 TOML 프로파일 + 기동·멀티데이·격리 검증.
#### Intent
- 중장기 인스턴스를 재현 가능한 표준 레시피로 운영 가능하게 backlog 정규화.
#### Acceptance Criteria Delta
- Added: 기동 구성 확인/멀티데이 보유 유지/병행 무간섭/예시 파일 4개 기준
- Updated: 없음
- Removed: 없음
#### Impacted Area Delta
- Docs: longterm.example.toml + baseline(REQ-078)
- Modules: 신규 코드 없음 목표(기존 메커니즘 조합)
- Tests: 프로파일 기동 + instance 격리
#### Delivery Trace Delta
- Batches: none
- Releases: none
#### Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: keep-current
- Follow-up Action: confirm-req 후 batch 편성(REQ-076 과 동일 batch)
#### Validation Plan
- 프로파일 TOML 로딩 + 워커 미기동 + watchdog 적용 단위, paper 매수→재시작 holding 유지, instance 격리 통합 테스트.
### CHG-20260612-02
- Date: 2026-06-12
- Author: Justin Ko
- Type: status
- Summary: Proposed → Approved (confirm-req). 필수 필드·구체 AC 충족, source dcy-027 정합. REQ-076 의존은 동일 batch 편성으로 해소 예정 — blocker 아님.
### CHG-20260612-03
- Date: 2026-06-12
- Author: Justin Ko
- Type: status
- Summary: Approved → Implemented (confirm-req-implemented). 근거: BAT-041(release-candidate) verification — AC-1/4 프로파일 조합 로딩 테스트(`config/longterm.example.toml` — longterm·safety_sell false·1800·cap 10) PASSED, AC-2 멀티데이 보유는 영구 슬롯 영속(BAT-027~030)·워커 미기동(BAT-036) 기존 evidence + 조합 설정 검증, AC-3 무간섭은 인스턴스 독립 테스트 + 기존 격리 회귀. 실 데몬 장기 run 은 release 운영 검증 위임(verification 명시). 446 passed. Blocking Issues 없음.
@@ -0,0 +1,76 @@
# req-078: 중장기 운영 baseline 문서 정합 — 보호 규칙 경계 + forced_close_at 미사용 + 병행 운영 흐름
- Status: Implemented
- Type: Documentation
- Priority: Medium
- Owner: project maintainers
## Intent
- 중장기 인스턴스 운영의 구조·흐름·정책 경계를 baseline 문서에 명시해 이후 REQ/batch 와 운영자가 참조 가능하게 한다. 특히 "보호 규칙은 전략 모듈 전적 책임"이라는 경계(dcy-027 OQ-2)와 `forced_close_at` 중장기 미사용(CONFIRM)을 문서로 확정한다. (dcy-027 FR-2/FR-4/FR-5)
## Requirement
- `docs/runtime-flows.md` 에 중장기 인스턴스 운영 흐름을 추가한다: 병행 인스턴스 토폴로지(당일/중장기 instance_key 분리), 중장기 프로파일 구성(REQ-077), `safety_sell_enabled=false` 시 보호장치 경로(코어 손절매 루틴 일절 미사용 — stop-loss·트레일링·타임스톱 전부 전략 모듈 전적 책임, 코어는 운영자 CLI `exit` 백스톱만 제공).
- `forced_close_at` 의 중장기 의미를 명시한다: **미사용(의미 없음)** — 집행 주체(safety-sell 워커)가 OFF 이므로 구조적으로 발화 불가(dcy-027 CONFIRM). 운영자 CLI·intent 계약 관련 서술에 반영한다.
- `docs/project-structure.md` 에 중장기 운영 관련 구조(설정 키, 예시 프로파일 위치)가 필요한 만큼 반영한다(구조 변화가 없으면 흐름 문서 중심).
## Acceptance Criteria
1. Given `docs/runtime-flows.md`, When 중장기 운영 섹션 확인, Then 병행 인스턴스 토폴로지·프로파일 구성·보호 규칙 경계(전략 전적 책임 + CLI exit 백스톱)가 현행 코드와 정합하게 기술되어 있다.
2. Given baseline 문서, When `forced_close_at` 서술 확인, Then 중장기 인스턴스에서 미사용(의미 없음, 발화 불가 근거 포함)이 명시되어 있다.
3. Given 신규 운영자, When baseline 문서만으로 중장기 인스턴스 운영 모델 파악 시도, Then 어떤 보호장치가 없고 누가 책임지는지(전략 모듈/운영자 CLI) 모호함 없이 판단 가능하다.
## Impacted Area
- Docs: docs/runtime-flows.md(중장기 운영 흐름 + Change Log), docs/project-structure.md(필요 시)
- Modules: 없음(문서 전용)
- Tests: 없음(문서 diff 리뷰로 검증 — S-4)
## Notes
- 출처: dcy-027 FR-2(보호 규칙 경계 명시), FR-4(forced_close_at 미사용), FR-5(baseline 갱신), S-2/S-4.
- REQ-076/077 구현 결과와 같은 batch 에서 정합 — 코드 확정 후 문서화(선례: REQ-042/047/051 등 baseline 정합 REQ).
- R-1(보호장치 공백 방치 위험)의 완화가 곧 이 문서화다 — 경계가 운영 문서에 명시되어야 전략 모듈 설계·운영 절차가 책임을 인지한다.
## Change Log
### CHG-20260612-01
- Date: 2026-06-12
- Author: Justin Ko
#### Change Summary
- dcy-027 FR-2/FR-4/FR-5 로부터 최초 생성(Proposed). 중장기 운영 baseline 정합 + 보호 경계·forced_close_at 미사용 명시.
#### Intent
- 중장기 운영 모델의 정책 경계를 문서로 확정해 운영·후속 개발 참조점 제공.
#### Acceptance Criteria Delta
- Added: 흐름 정합/forced_close_at 미사용 명시/운영자 판단 가능성 3개 기준
- Updated: 없음
- Removed: 없음
#### Impacted Area Delta
- Docs: runtime-flows.md, project-structure.md(필요 시)
- Modules: 없음
- Tests: 없음
#### Delivery Trace Delta
- Batches: none
- Releases: none
#### Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: keep-current
- Follow-up Action: confirm-req 후 batch 편성(REQ-076/077 과 동일 batch)
#### Validation Plan
- baseline 문서 diff 리뷰 — 중장기 흐름 섹션 존재 + 현행 코드 정합 + 경계 명시 확인.
### CHG-20260612-02
- Date: 2026-06-12
- Author: Justin Ko
- Type: status
- Summary: Proposed → Approved (confirm-req). 필수 필드·구체 AC 충족, source dcy-027 정합(FR-2/4/5 — 보호 경계·forced_close_at 미사용 결정 반영 확인).
### CHG-20260612-03
- Date: 2026-06-12
- Author: Justin Ko
- Type: status
- Summary: Approved → Implemented (confirm-req-implemented). 근거: BAT-041(release-candidate) verification — `docs/runtime-flows.md` `중장기(멀티데이) 인스턴스 병행 운영` flow 신설(토폴로지/프로파일/보호 경계: 전략 전적 책임+CLI exit 백스톱/forced_close_at 미사용/watchdog 의미) + Change Log, 현행 코드 정합 확인(AC-1~3). project-structure 는 구조 불변으로 변경 불요. Blocking Issues 없음.
+7 -4
View File
@@ -74,7 +74,7 @@
## Next Requirement ID ## Next Requirement ID
- Current: `REQ-076` - Current: `REQ-079`
- Rule: `Requirement Register`에 있는 가장 큰 REQ 번호보다 정확히 1 커야 한다. - Rule: `Requirement Register`에 있는 가장 큰 REQ 번호보다 정확히 1 커야 한다.
## Requirement Register ## Requirement Register
@@ -156,11 +156,14 @@
| REQ-073 | 전략 read 표면 — trex.slots.req RPC 계약 (filter/spy, snapshot.req 즉시 교체) | Interface | Implemented | High | project maintainers | [req-073](./Interface/req-073_slots-req-rpc-contract.md) | | REQ-073 | 전략 read 표면 — trex.slots.req RPC 계약 (filter/spy, snapshot.req 즉시 교체) | Interface | Implemented | High | project maintainers | [req-073](./Interface/req-073_slots-req-rpc-contract.md) |
| REQ-074 | 슬롯 단일 행 스키마 — 자산·워치독·overlay 흡수 + 필드별 정본 라벨 | Data | Implemented | High | project maintainers | [req-074](./Data/req-074_merged-slot-row-schema.md) | | REQ-074 | 슬롯 단일 행 스키마 — 자산·워치독·overlay 흡수 + 필드별 정본 라벨 | Data | Implemented | High | project maintainers | [req-074](./Data/req-074_merged-slot-row-schema.md) |
| REQ-075 | slots read 표면 baseline 문서 정합 | Documentation | Implemented | Medium | project maintainers | [req-075](./Documentation/req-075_slots-read-surface-baseline-alignment.md) | | REQ-075 | slots read 표면 baseline 문서 정합 | Documentation | Implemented | Medium | project maintainers | [req-075](./Documentation/req-075_slots-read-surface-baseline-alignment.md) |
| REQ-076 | watchdog timeout per-instance TOML 설정 | Configuration | Implemented | High | project maintainers | [req-076](./Configuration/req-076_watchdog-timeout-per-instance-toml.md) |
| REQ-077 | 중장기 인스턴스 운영 프로파일 — 표준 TOML 레시피 + 기동 검증 | Configuration | Implemented | High | project maintainers | [req-077](./Configuration/req-077_long-term-instance-operation-profile.md) |
| REQ-078 | 중장기 운영 baseline 문서 정합 — 보호 규칙 경계 + forced_close_at 미사용 | Documentation | Implemented | Medium | project maintainers | [req-078](./Documentation/req-078_long-term-operation-baseline-alignment.md) |
## Recent Change Log Summary ## Recent Change Log Summary
- 2026-06-12 :: REQ-076·077·078 Approved → **Implemented** (confirm-req-implemented, BAT-041, dcy-027): 중장기 인스턴스 운영 구현·검증 완료 — watchdog per-instance TOML(단일 지점 override, env 폴백), `config/longterm.example.toml` 프로파일, runtime-flows 중장기 flow(보호 경계·forced_close_at 미사용). 446 passed (+7), ruff clean. 실 데몬 장기 run 은 release 운영 검증 위임. Blocking Issues 없음.
- 2026-06-12 :: REQ-076·077·078 Proposed → **Approved** (confirm-req): 필수 필드·구체 AC 충족, source dcy-027(confirmed, 결정 잔여 0건) 정합. REQ-076/077 동일 batch 편성 예정.
- 2026-06-12 :: REQ-076·077·078 신규 생성(Proposed, draft-req dcy-027): 중장기 전략 슬롯 운영 — watchdog timeout per-instance TOML(env 폴백), 중장기 운영 프로파일(TOML 레시피+기동·멀티데이·격리 검증), baseline 정합(보호 규칙 전략 전적 책임 + forced_close_at 미사용 명시). NFR-1 은 batch 검증, NFR-2 는 REQ-077 AC, NFR-3 은 REQ-076 제약으로 흡수.
- 2026-06-12 :: REQ-002·006 Approved → **Implemented** (confirm-req-implemented, BAT-040, issue #25): slots RPC full scan 개선 구현·검증 완료. `list_slots(status_filter)` DB-level 필터 + 핸들러 위임 + watchdog 조회 재사용. 439 passed (in-memory 필터 5 신규, 핸들러 16 회귀, PG 게이트 1), ruff clean. REQ-073 관찰 계약 불변 Implemented 유지. Blocking Issues 없음. issue #25 close. - 2026-06-12 :: REQ-002·006 Approved → **Implemented** (confirm-req-implemented, BAT-040, issue #25): slots RPC full scan 개선 구현·검증 완료. `list_slots(status_filter)` DB-level 필터 + 핸들러 위임 + watchdog 조회 재사용. 439 passed (in-memory 필터 5 신규, 핸들러 16 회귀, PG 게이트 1), ruff clean. REQ-073 관찰 계약 불변 Implemented 유지. Blocking Issues 없음. issue #25 close.
- 2026-06-12 :: REQ-002·006·073 change-req (CHG-20260612-01/01/02, issue #25): slots RPC per-request full scan 개선 — `list_slots(status_filter)` DB-level status 필터 + 핸들러 위임 + watchdog 조회 재사용. REQ-002/006 AC-6 신규 → Implemented → **Approved** 되돌림(default None 동작 보존). REQ-073 관찰 계약 불변 → **Implemented 유지**. - 2026-06-12 :: REQ-002·006·073 change-req (CHG-20260612-01/01/02, issue #25): slots RPC per-request full scan 개선 — `list_slots(status_filter)` DB-level status 필터 + 핸들러 위임 + watchdog 조회 재사용. REQ-002/006 AC-6 신규 → Implemented → **Approved** 되돌림(default None 동작 보존). REQ-073 관찰 계약 불변 → **Implemented 유지**.
- 2026-06-12 :: REQ-011 Approved → **Implemented** (confirm-req-implemented, BAT-039): paper order seq Redis 영속화 구현·검증 완료. 434 passed (seq store 5 신규 포함), 재시작 단조·correlation_id 비충돌·in-memory 폴백 green. Blocking Issues 없음. issue #4 close.
- 2026-06-12 :: REQ-011 change-req (CHG-20260612-01, issue #4): paper 브로커 order seq Redis 영속화 — 재시작 후 `correlation_id` 충돌(매도 원장 누락) 방지. optional seq store(RedisSeqStore INCR + in-memory 폴백) 주입. AC-4 신규. 기존 비동기 계약 불변이나 신규 AC 미구현 → Implemented → **Approved** 되돌림. BAT-039 구현·검증 후 복귀. paper 한정(프로덕션 버그 아님).
- 2026-06-12 :: REQ-018 Approved → **Implemented** (confirm-req-implemented, BAT-038): 매도 증권거래세 정책 계산 + `tax=0.0` 제거 구현·검증 완료. 429 passed (fee·tax 정책 6 신규 포함), issue #24 1,376.9원 회귀 green, `tax=0.0` 하드코딩 grep 0건, runtime-flows 동기화. Blocking Issues 없음.
@@ -0,0 +1,89 @@
"""Per-instance watchdog timeout TOML tests (REQ-076/077, dcy-027, BAT-041).
TOML `watchdog_timeout_sec` wins over env TREX_WATCHDOG_TIMEOUT; absent keeps
the env value (default 120) so intraday instances are byte-for-byte unaffected.
The long-term profile combines this with safety_sell_enabled=false.
"""
from __future__ import annotations
from pathlib import Path
import pytest
from trex_core.runtime.config import DaemonConfig, DaemonInstanceConfig
_REPO = Path(__file__).resolve().parents[2]
def _write_toml(tmp_path, body: str, name: str = "inst.toml") -> str:
p = tmp_path / name
p.write_text(body)
return str(p)
# --- REQ-076 AC-1: TOML value applied -----------------------------------------
def test_toml_watchdog_timeout_applied(tmp_path, monkeypatch):
monkeypatch.setenv("TREX_POSTGRESQL_DSN", "postgresql://x/y")
path = _write_toml(tmp_path, 'instance_key = "lt"\nwatchdog_timeout_sec = 1800\n')
cfg = DaemonConfig.from_config(path)
assert cfg.safety_sell.watchdog_timeout_sec == 1800
def test_toml_wins_over_env(tmp_path, monkeypatch):
monkeypatch.setenv("TREX_POSTGRESQL_DSN", "postgresql://x/y")
monkeypatch.setenv("TREX_WATCHDOG_TIMEOUT", "300")
path = _write_toml(tmp_path, 'instance_key = "lt"\nwatchdog_timeout_sec = 1800\n')
assert DaemonConfig.from_config(path).safety_sell.watchdog_timeout_sec == 1800
# --- REQ-076 AC-2: absent -> env fallback (no regression) ---------------------
def test_absent_falls_back_to_env(tmp_path, monkeypatch):
monkeypatch.setenv("TREX_POSTGRESQL_DSN", "postgresql://x/y")
monkeypatch.setenv("TREX_WATCHDOG_TIMEOUT", "300")
path = _write_toml(tmp_path, 'instance_key = "day"\n')
assert DaemonConfig.from_config(path).safety_sell.watchdog_timeout_sec == 300
def test_absent_and_no_env_keeps_default_120(tmp_path, monkeypatch):
monkeypatch.setenv("TREX_POSTGRESQL_DSN", "postgresql://x/y")
monkeypatch.delenv("TREX_WATCHDOG_TIMEOUT", raising=False)
path = _write_toml(tmp_path, 'instance_key = "day"\n')
assert DaemonConfig.from_config(path).safety_sell.watchdog_timeout_sec == 120
# --- REQ-076 AC-3: per-instance independence ----------------------------------
def test_instances_get_independent_timeouts(tmp_path, monkeypatch):
monkeypatch.setenv("TREX_POSTGRESQL_DSN", "postgresql://x/y")
monkeypatch.delenv("TREX_WATCHDOG_TIMEOUT", raising=False)
day = _write_toml(tmp_path, 'instance_key = "day"\n', name="day.toml")
lt = _write_toml(
tmp_path, 'instance_key = "lt"\nwatchdog_timeout_sec = 1800\n', name="lt.toml"
)
assert DaemonConfig.from_config(day).safety_sell.watchdog_timeout_sec == 120
assert DaemonConfig.from_config(lt).safety_sell.watchdog_timeout_sec == 1800
# --- from_toml validation (design: <= 0 rejected) ------------------------------
def test_nonpositive_timeout_rejected(tmp_path):
for bad in ("0", "-60"):
path = _write_toml(tmp_path, f'instance_key = "x"\nwatchdog_timeout_sec = {bad}\n')
with pytest.raises(RuntimeError):
DaemonInstanceConfig.from_toml(path)
# --- REQ-077 AC-1/AC-4: long-term profile recipe -------------------------------
def test_longterm_example_profile_loads_with_expected_combination(monkeypatch):
"""The shipped example profile yields: dedicated instance_key, safety-sell
worker OFF, watchdog 1800s, capacity cap set."""
monkeypatch.setenv("TREX_POSTGRESQL_DSN", "postgresql://x/y")
example = _REPO / "config" / "longterm.example.toml"
cfg = DaemonConfig.from_config(str(example))
assert cfg.instance_key == "longterm" # A-2: separate instance scope
assert cfg.safety_sell_enabled is False # autonomous liquidation OFF
assert cfg.safety_sell.watchdog_timeout_sec == 1800
assert cfg.max_active_slots == 10
+20
View File
@@ -154,6 +154,10 @@ class DaemonInstanceConfig:
# dcy-024 / REQ-048 CHG-20260611-01: disable autonomous safety-sell worker. # dcy-024 / REQ-048 CHG-20260611-01: disable autonomous safety-sell worker.
# Absent = True (safe default). False → trex-safety-sell thread not started. # Absent = True (safe default). False → trex-safety-sell thread not started.
safety_sell_enabled: bool = True safety_sell_enabled: bool = True
# dcy-027 / REQ-076: per-instance watchdog timeout (seconds). None = fall
# back to env TREX_WATCHDOG_TIMEOUT (default 120). Long-term instances
# polling every 1-10 min should set >= 1800 so expired stays meaningful.
watchdog_timeout_sec: Optional[int] = None
@staticmethod @staticmethod
def from_toml(path: str) -> "DaemonInstanceConfig": def from_toml(path: str) -> "DaemonInstanceConfig":
@@ -178,6 +182,12 @@ class DaemonInstanceConfig:
raise RuntimeError( raise RuntimeError(
f"instance TOML 'default_order_timeout_sec' must be > 0, got {raw_timeout}: {path}" f"instance TOML 'default_order_timeout_sec' must be > 0, got {raw_timeout}: {path}"
) )
raw_wd = data.get("watchdog_timeout_sec")
watchdog_timeout = int(raw_wd) if raw_wd is not None else None
if watchdog_timeout is not None and watchdog_timeout <= 0:
raise RuntimeError(
f"instance TOML 'watchdog_timeout_sec' must be > 0, got {raw_wd}: {path}"
)
return DaemonInstanceConfig( return DaemonInstanceConfig(
instance_key=str(instance_key), instance_key=str(instance_key),
portfolio_hub_token=str(data.get("portfolio_hub_token") or ""), portfolio_hub_token=str(data.get("portfolio_hub_token") or ""),
@@ -185,6 +195,7 @@ class DaemonInstanceConfig:
max_active_slots=max_active, max_active_slots=max_active,
default_order_timeout_sec=default_timeout, default_order_timeout_sec=default_timeout,
safety_sell_enabled=bool(data.get("safety_sell_enabled", True)), safety_sell_enabled=bool(data.get("safety_sell_enabled", True)),
watchdog_timeout_sec=watchdog_timeout,
) )
@@ -340,6 +351,14 @@ class DaemonConfig:
pf = base.portfolio_hub pf = base.portfolio_hub
if inst.portfolio_hub_token and pf is not None: if inst.portfolio_hub_token and pf is not None:
pf = replace(pf, token=inst.portfolio_hub_token) pf = replace(pf, token=inst.portfolio_hub_token)
# dcy-027 / REQ-076: per-instance watchdog timeout. TOML wins over the
# env-derived value; absent keeps env TREX_WATCHDOG_TIMEOUT (default 120)
# so existing instances are byte-for-byte unaffected.
safety_sell = base.safety_sell
if inst.watchdog_timeout_sec is not None:
safety_sell = replace(
safety_sell, watchdog_timeout_sec=inst.watchdog_timeout_sec
)
return replace( return replace(
base, base,
instance_key=inst.instance_key, instance_key=inst.instance_key,
@@ -347,4 +366,5 @@ class DaemonConfig:
trading_timezone=inst.trading_timezone, trading_timezone=inst.trading_timezone,
max_active_slots=inst.max_active_slots, max_active_slots=inst.max_active_slots,
safety_sell_enabled=inst.safety_sell_enabled, safety_sell_enabled=inst.safety_sell_enabled,
safety_sell=safety_sell,
) )