From 6786ffd05d84213a3c1e2e6b8d002d3a8616134a Mon Sep 17 00:00:00 2001 From: Justin Ko Date: Fri, 12 Jun 2026 22:19:33 +0900 Subject: [PATCH] =?UTF-8?q?docs(discovery):=20dcy-027=20=EB=AA=A8=EB=93=A0?= =?UTF-8?q?=20OQ=20Answered=20=E2=80=94=20=EC=A4=91=EC=9E=A5=EA=B8=B0=20?= =?UTF-8?q?=EC=8A=AC=EB=A1=AF=20=EC=9A=B4=EC=98=81=20=EA=B2=B0=EC=A0=95=20?= =?UTF-8?q?=EC=99=84=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- config/longterm.example.toml | 41 +++++++++ .../design.md | 39 ++++++++ .../implementation.md | 31 +++++++ .../index.md | 33 +++++++ .../planning.md | 37 ++++++++ .../verification.md | 48 ++++++++++ docs/batches/index.md | 1 + ...60612_long-term-strategy-slot-operation.md | 84 +++++++++-------- docs/discovery/index.md | 2 +- docs/runtime-flows.md | 12 +++ ...-076_watchdog-timeout-per-instance-toml.md | 76 ++++++++++++++++ ...77_long-term-instance-operation-profile.md | 78 ++++++++++++++++ ..._long-term-operation-baseline-alignment.md | 76 ++++++++++++++++ docs/srs/index.md | 11 ++- tests/runtime/test_watchdog_timeout_toml.py | 89 +++++++++++++++++++ trex_core/runtime/config.py | 20 +++++ 16 files changed, 634 insertions(+), 44 deletions(-) create mode 100644 config/longterm.example.toml create mode 100644 docs/batches/bat-041_20260612_long-term-instance-profile/design.md create mode 100644 docs/batches/bat-041_20260612_long-term-instance-profile/implementation.md create mode 100644 docs/batches/bat-041_20260612_long-term-instance-profile/index.md create mode 100644 docs/batches/bat-041_20260612_long-term-instance-profile/planning.md create mode 100644 docs/batches/bat-041_20260612_long-term-instance-profile/verification.md create mode 100644 docs/srs/Configuration/req-076_watchdog-timeout-per-instance-toml.md create mode 100644 docs/srs/Configuration/req-077_long-term-instance-operation-profile.md create mode 100644 docs/srs/Documentation/req-078_long-term-operation-baseline-alignment.md create mode 100644 tests/runtime/test_watchdog_timeout_toml.py diff --git a/config/longterm.example.toml b/config/longterm.example.toml new file mode 100644 index 0000000..198a0e8 --- /dev/null +++ b/config/longterm.example.toml @@ -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 + +# Long-term instances MUST use their own instance_key so slots (DB scope) and +# queues (trex.slots.req. 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.) diff --git a/docs/batches/bat-041_20260612_long-term-instance-profile/design.md b/docs/batches/bat-041_20260612_long-term-instance-profile/design.md new file mode 100644 index 0000000..4dda704 --- /dev/null +++ b/docs/batches/bat-041_20260612_long-term-instance-profile/design.md @@ -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 항목 추가. diff --git a/docs/batches/bat-041_20260612_long-term-instance-profile/implementation.md b/docs/batches/bat-041_20260612_long-term-instance-profile/implementation.md new file mode 100644 index 0000000..721e4e2 --- /dev/null +++ b/docs/batches/bat-041_20260612_long-term-instance-profile/implementation.md @@ -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 후속 필요. diff --git a/docs/batches/bat-041_20260612_long-term-instance-profile/index.md b/docs/batches/bat-041_20260612_long-term-instance-profile/index.md new file mode 100644 index 0000000..35fbe21 --- /dev/null +++ b/docs/batches/bat-041_20260612_long-term-instance-profile/index.md @@ -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 재참조). diff --git a/docs/batches/bat-041_20260612_long-term-instance-profile/planning.md b/docs/batches/bat-041_20260612_long-term-instance-profile/planning.md new file mode 100644 index 0000000..fa43a01 --- /dev/null +++ b/docs/batches/bat-041_20260612_long-term-instance-profile/planning.md @@ -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 에 명시). diff --git a/docs/batches/bat-041_20260612_long-term-instance-profile/verification.md b/docs/batches/bat-041_20260612_long-term-instance-profile/verification.md new file mode 100644 index 0000000..f36c7e8 --- /dev/null +++ b/docs/batches/bat-041_20260612_long-term-instance-profile/verification.md @@ -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 후속. diff --git a/docs/batches/index.md b/docs/batches/index.md index 73d4f72..0d62dd4 100644 --- a/docs/batches/index.md +++ b/docs/batches/index.md @@ -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-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-041 | standard | release-candidate | REQ-076, REQ-077, REQ-078 | dcy-027 (issue #20 이월) | [bat-041](./bat-041_20260612_long-term-instance-profile/) | diff --git a/docs/discovery/dcy-027_20260612_long-term-strategy-slot-operation.md b/docs/discovery/dcy-027_20260612_long-term-strategy-slot-operation.md index 04e5a11..c726f0a 100644 --- a/docs/discovery/dcy-027_20260612_long-term-strategy-slot-operation.md +++ b/docs/discovery/dcy-027_20260612_long-term-strategy-slot-operation.md @@ -1,13 +1,13 @@ # 0. 문서 상태 -- 상태: draft +- 상태: confirmed - 문서 ID: dcy-027_20260612_long-term-strategy-slot-operation - 이슈명: 중장기 매매 전략용 슬롯 운영 도입 — 구현 위치 결정 + 멀티데이 리스크 규칙 + 운영 프로파일 (issue #20) - 작성 시각(KST): 2026-06-12 -- 마지막 갱신 시각(KST): 2026-06-12 +- 마지막 갱신 시각(KST): 2026-06-12 (draft-req: REQ-076·077·078 생성 — 생성된 REQ 참조 갱신) - 대체됨: 없음 - 후속 Discovery 참조: 없음 -- 생성된 REQ 참조: 없음 +- 생성된 REQ 참조: REQ-076(watchdog timeout per-instance TOML), REQ-077(중장기 운영 프로파일), REQ-078(중장기 baseline 정합) # 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` 독립 데몬 운영이 이미 가능. - **잔여 갭 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 가 일상 상태가 되어 가시화 신호로서 의미를 잃는다. - - **잔여 갭 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`)는 존재·최신이나 당일 운영 중심으로 기술돼 있다. - 기대 상태 - - 구현 위치가 확정된다 — 현행 영구 슬롯 모델이 멀티데이 보유를 이미 지원하므로, 이슈 권장안(a)의 "신규 `PositionBook` 도메인"이 여전히 필요한지, 아니면 **기존 슬롯 도메인 + 중장기 운영 프로파일 + 멀티데이 리스크 규칙 추가**로 충분한지 판정된다. - - `safety_sell_enabled=false` 인스턴스에도 선택 가능한 멀티데이 보호 규칙(stop-loss/트레일링/타임스톱 중 합의된 범위)이 제공되거나, "전략 모듈 전적 책임"으로 명시적으로 선을 긋는다. + - 구현 위치 확정 완료 — **(a′) 기존 슬롯 도메인 + 중장기 운영 프로파일 추가** (OQ-1 Answered). 신규 `PositionBook` 도메인 불필요. + - 보호 규칙 경계 확정 완료 — **"전략 모듈 전적 책임"으로 명시적으로 선을 긋는다** (OQ-2 Answered). 코어는 운영자 CLI `exit` 백스톱만 제공하며, 이 경계가 운영 문서에 명시된다. - 중장기 인스턴스 운영 프로파일(TOML 레시피 + watchdog 정책)이 baseline 문서에 기술되고 실 데몬으로 검증된다. - 이해관계자 - 운영자/시스템 관리자 (Justin Ko): 당일·중장기 인스턴스 병행 운영, 리스크 정책 결정 @@ -55,16 +55,17 @@ # 4. 이번에 정의할 변경 -- 구현 위치 판정 — 이슈 OQ-1 을 현행 영구 슬롯 모델 기준으로 재평가: (a′) 기존 슬롯 도메인 그대로 + 중장기 운영 프로파일/리스크 규칙 추가(현행 토대 활용, 유력) vs (a) 신규 `PositionBook` 도메인 병렬 추가(이슈 원안) vs (c) 별도 레포(이슈가 이미 비권장). 판정 결과를 REQ 분할의 전제로 확정한다. -- 멀티데이 리스크 규칙 범위 확정 — `safety_sell_enabled=false` 인스턴스의 보호장치: 멀티데이 stop-loss·트레일링 스톱·타임스톱(forced_close_at 재해석) 중 코어가 제공할 범위 vs 전략 모듈 전적 책임 경계를 결정한다. -- 중장기 인스턴스 운영 프로파일 정의 — 표준 TOML 레시피(safety_sell off·market-close 미설정·watchdog timeout 정책·max_active_slots) + watchdog 의미 재검토(중장기 폴링 주기 대응) + baseline 문서(`docs/project-structure.md`, `docs/runtime-flows.md`) 중장기 운영 흐름 갱신. +- 구현 위치 확정 — **(a′) 기존 슬롯 도메인 유지 + 중장기 운영 프로파일/규칙 추가** (OQ-1 Answered). 현행 영구 자산 슬롯이 멀티데이 보유를 구조적으로 지원하므로 신규 `PositionBook` 도메인(이슈 원안 (a))·별도 레포((c))는 미채택. 이 판정이 REQ 분할의 전제다. +- 멀티데이 보호 규칙 경계 확정 — **전략 모듈 전적 책임** (OQ-2 Answered). `safety_sell_enabled=false` 인스턴스는 코어 손절매 루틴(stop-loss·트레일링·타임스톱)을 일절 사용하지 않고, 코어는 운영자 CLI `exit` 백스톱만 제공한다. 이 경계를 운영 문서에 명시한다(FR-2). +- 중장기 인스턴스 운영 프로파일 정의 — 표준 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′) 기준: `trex_core/runtime/config.py`(중장기 프로파일 관련 설정 항목), `trex_core/application/safety_sell.py` 또는 신규 멀티데이 리스크 워커(규칙 범위 결정에 따름), watchdog timeout per-instance 정책(`trex_core/application/watchdog.py` + config) - - 구현 위치 판정 (a) 기준: 신규 도메인 패키지(`trex_core/domain/position_book/` 류) + persistence + 워커 — 범위 대폭 확대 - - 당일 경로(`domain/slot/`, `intents.py`, `execution.py`)는 양쪽 모두 불변 목표 +- 코드 영향 (구현 위치 (a′) 확정 기준 — OQ-1): + - `trex_core/runtime/config.py` — 중장기 프로파일 설정: per-instance TOML `watchdog_timeout_sec`(OQ-3, 현재 env `TREX_WATCHDOG_TIMEOUT` 전용 → TOML 우선 지원) + - watchdog timeout per-instance 적용 경로(`trex_core/application/watchdog.py` + config wiring) + - 멀티데이 리스크 워커/`safety_sell.py` 변경 **없음** — OQ-2 결정(코어 손절매 루틴 미제공, 전략 전적 책임)으로 코어 보호 규칙 구현이 범위에서 제외됨 + - 당일 경로(`domain/slot/`, `intents.py`, `execution.py`)는 불변 목표 - 문서 영향: - `docs/project-structure.md`·`docs/runtime-flows.md`: 중장기 인스턴스 운영 흐름·프로파일 추가 (baseline 갱신) - 신규 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-2: `safety_sell_enabled=false` 인스턴스는 코어 손절매 루틴을 일절 사용하지 않는다. 모든 보호 규칙(stop-loss·트레일링·타임스톱)은 전략 모듈의 전적 책임이며, 코어는 운영자 CLI `exit` 백스톱만 제공한다. 이 경계를 운영 문서에 명시한다. - 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` 에 중장기 인스턴스 운영 구조와 흐름(병행 인스턴스 토폴로지, 프로파일, 리스크 규칙 경로)을 갱신한다. ## 비기능 요구사항 @@ -100,7 +101,7 @@ - 중장기 전략 모듈 자체의 구현(별도 저장소/주기 — 코어는 계약·운영 토대만 제공) - 포트폴리오 리밸런싱 엔진·목표 비중 관리(전략 모듈 책임) - 일일 회계·리포트 체계 개편(현행 ledger 날짜 필터 해석 유지, 필요 시 후속) - - {{DECIDE_POSITION_BOOK}}: 신규 `PositionBook` 도메인 신설 — 구현 위치 판정이 (a) 로 결정될 때만 In Scope 로 승격 + - 신규 `PositionBook` 도메인 신설 — **Out of Scope 확정** (OQ-1: 구현 위치 (a′) 결정으로 미채택) # 6. 리스크/가정 목록 @@ -108,8 +109,8 @@ - R-1 (High): 보호장치 경계가 모호한 채 중장기 인스턴스를 운영하면, safety-sell OFF + 전략 모듈 장애 시 손실 포지션이 무기한 방치된다. - 완화 방안: **전략 모듈 전적 책임으로 확정(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 완화가 과도하면 중장기 인스턴스에서 전략 이탈을 영영 감지하지 못한다. - 완화 방안: 비활성화 대신 긴 timeout(예: 일 단위) 설정을 기본안으로 검토하고, slots RPC `watchdog.last_seen` 가시성은 유지한다. - R-4 (Low): 멀티데이 보유 중 universe 에서 자산이 빠지면(상폐 등) tombstone/retire 경로가 중장기 보유와 충돌할 수 있다. @@ -118,7 +119,7 @@ ## 가정 - 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-4: KIS·portfolio-hub·RabbitMQ 인프라를 그대로 사용하므로 별도 레포 분리(이슈 OQ-1 의 (c))는 손해다 — 이슈 분석 수용. @@ -145,36 +146,41 @@ # 7. 초기 성공 기준 - 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-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-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-4 측정 방식: baseline 문서 diff 리뷰 — 중장기 흐름 섹션 존재 + 현행 코드와 정합 / 데이터 출처: `docs/project-structure.md`, `docs/runtime-flows.md` # 8. REQ로 넘기기 전 확인 체크 -- [ ] 범위(In Scope/Out of Scope) 확정 - - 기준: OQ-1(구현 위치) 확정으로 {{DECIDE_POSITION_BOOK}} In/Out 판정 + OQ-2(보호 규칙 범위) 확정 -- [ ] 성공 지표의 측정 방식/데이터 출처 확정 - - 기준: OQ-4(전략 폴링 주기) 수치 확보로 S-1/watchdog 검증 수치 구체화 -- [ ] High 영향 리스크 우선순위 및 해소 책임자 지정 - - 기준: R-1(보호장치 공백) 경계 확정 담당 Justin Ko -- [ ] 모든 오픈 질문(OQ) `Open` 상태 없음 확인 - - 기준: OQ 목록에 `Open` 상태 항목이 0개이거나, 잔여 항목 전부 `Deferred` 처리 완료 -- [ ] Freeze 확정 담당자(CONFIRMED_BY) 지정 - - 기준: Justin Ko 승인 후 `confirm-discovery` 수행 +- [x] 범위(In Scope/Out of Scope) 확정 + - 기준: OQ-1(구현 위치 a′)·OQ-2(보호 규칙 전략 책임) Answered + A-2(병행 인스턴스) 승인 + forced_close_at 미사용 확정으로 충족 — PositionBook Out of Scope 확정. CONFIRM 잔여 0건 + - 결과: 충족 — In/Out 경계 §5 확정, 사용자 결정 잔여 0건 +- [x] 성공 지표의 측정 방식/데이터 출처 확정 + - 기준: OQ-4 수치(1~10분 폴링, watchdog 1800s 이상) 확보로 S-1/watchdog 검증 수치 구체화 완료 + - 결과: 충족 — S-1~S-4 측정 방식·데이터 출처 §7 명시 +- [x] High 영향 리스크 우선순위 및 해소 책임자 지정 + - 기준: R-1(보호장치 공백) — OQ-2 결정으로 경계 확정(전략 책임 + CLI 백스톱), 담당 Justin Ko + - 결과: 충족 — R-1 경계 확정, R-2 해소(OQ-2), R-3/R-4 완화 방안 확정 +- [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. 파일 처리 결과 - 처리 결과: 생성 - 생성 경로: 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-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), 멀티데이 규칙 부재 확인 @@ -192,12 +198,12 @@ ## CONFIRM -- {{CONFIRM_PARALLEL_INSTANCE}}: 중장기 운영을 별도 instance_key 데몬으로 분리하는 전제(A-2) 승인 -- {{CONFIRM_FORCED_CLOSE_SEMANTICS}}: forced_close_at 의 중장기 의미 — 타임스톱 재해석 vs 미사용 (FR-4) +- ~~{{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 의 중장기 의미 — **미사용(의미 없음) 확정** (FR-4, 2026-06-12, Justin Ko). 중장기 모드에서 forced_close_at 은 의미가 없다. 집행 주체(safety-sell 워커)가 `safety_sell_enabled=false` 로 OFF 이므로 구조적으로 발화 불가하며, 타임스톱 재해석(별도 집행 경로 신설)은 OQ-2 결정(코어 손절매 미제공)과 충돌해 미채택. 운영자 CLI·intent 계약 문서에 "중장기 인스턴스에서 미사용" 명시. ## DATA -- {{DATA_STRATEGY_CADENCE}}: 중장기 전략 모듈의 폴링·intent 발신 주기 계획 (OQ-4 — watchdog·프로파일 수치 입력) +- 없음 — 중장기 전략 모듈의 폴링·intent 발신 주기는 OQ-4 에서 확보됨(answered): 최소 1분~최대 10분(600s) 주기, watchdog timeout 권장 1800초(30분) 이상의 입력값. # 11. Discovery Freeze @@ -211,8 +217,8 @@ ## Freeze 플래그 -- Handoff Decision: 보류 -- Handoff Rationale: 초기 Discovery 초안 생성 완료. OQ-1(구현 위치)·OQ-2(보호 규칙)·OQ-3(watchdog)·OQ-4(전략 주기) 4건 Open — confirm-discovery 전 사용자 결정 필요. -- Ready for REQ Drafting: false -- Confirmed By: TBD -- Confirmed At (KST): TBD +- Handoff Decision: 승인 +- 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: true +- Confirmed By: Justin Ko +- Confirmed At (KST): 2026-06-12 diff --git a/docs/discovery/index.md b/docs/discovery/index.md index 77e2e2f..4b020f3 100644 --- a/docs/discovery/index.md +++ b/docs/discovery/index.md @@ -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-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-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 | diff --git a/docs/runtime-flows.md b/docs/runtime-flows.md index ecaa74f..2ec17a3 100644 --- a/docs/runtime-flows.md +++ b/docs/runtime-flows.md @@ -212,6 +212,17 @@ 4. **broadcast**: `trex.broadcast` 에 publish → 모든 인스턴스 control 큐 동시 전달(global kill-switch 등). 중복 처리는 idempotency 로 무해. 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 `(수동 청산)다. 전략 모듈 장애 시 손실 포지션 방치 위험은 전략 설계 + 운영 절차(수동 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) 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-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-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). diff --git a/docs/srs/Configuration/req-076_watchdog-timeout-per-instance-toml.md b/docs/srs/Configuration/req-076_watchdog-timeout-per-instance-toml.md new file mode 100644 index 0000000..906b82d --- /dev/null +++ b/docs/srs/Configuration/req-076_watchdog-timeout-per-instance-toml.md @@ -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 없음. diff --git a/docs/srs/Configuration/req-077_long-term-instance-operation-profile.md b/docs/srs/Configuration/req-077_long-term-instance-operation-profile.md new file mode 100644 index 0000000..9563cab --- /dev/null +++ b/docs/srs/Configuration/req-077_long-term-instance-operation-profile.md @@ -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 없음. diff --git a/docs/srs/Documentation/req-078_long-term-operation-baseline-alignment.md b/docs/srs/Documentation/req-078_long-term-operation-baseline-alignment.md new file mode 100644 index 0000000..670c6d0 --- /dev/null +++ b/docs/srs/Documentation/req-078_long-term-operation-baseline-alignment.md @@ -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 없음. diff --git a/docs/srs/index.md b/docs/srs/index.md index b2553ab..97de52a 100644 --- a/docs/srs/index.md +++ b/docs/srs/index.md @@ -74,7 +74,7 @@ ## Next Requirement ID -- Current: `REQ-076` +- Current: `REQ-079` - Rule: `Requirement Register`에 있는 가장 큰 REQ 번호보다 정확히 1 커야 한다. ## 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-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-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 +- 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·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 없음. diff --git a/tests/runtime/test_watchdog_timeout_toml.py b/tests/runtime/test_watchdog_timeout_toml.py new file mode 100644 index 0000000..caaa1f4 --- /dev/null +++ b/tests/runtime/test_watchdog_timeout_toml.py @@ -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 diff --git a/trex_core/runtime/config.py b/trex_core/runtime/config.py index 07da5ba..69a2050 100644 --- a/trex_core/runtime/config.py +++ b/trex_core/runtime/config.py @@ -154,6 +154,10 @@ class DaemonInstanceConfig: # dcy-024 / REQ-048 CHG-20260611-01: disable autonomous safety-sell worker. # Absent = True (safe default). False → trex-safety-sell thread not started. 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 def from_toml(path: str) -> "DaemonInstanceConfig": @@ -178,6 +182,12 @@ class DaemonInstanceConfig: raise RuntimeError( 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( instance_key=str(instance_key), portfolio_hub_token=str(data.get("portfolio_hub_token") or ""), @@ -185,6 +195,7 @@ class DaemonInstanceConfig: max_active_slots=max_active, default_order_timeout_sec=default_timeout, 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 if inst.portfolio_hub_token and pf is not None: 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( base, instance_key=inst.instance_key, @@ -347,4 +366,5 @@ class DaemonConfig: trading_timezone=inst.trading_timezone, max_active_slots=inst.max_active_slots, safety_sell_enabled=inst.safety_sell_enabled, + safety_sell=safety_sell, )