Files
trex-core/docs/srs/Integration/req-037_forced-close-at-safety-sell-loop.md
T
genichinandClaude Opus 4.8 6d7bca1460 feat(safety-sell): dcy-017 워치독 소유권 가시화 — snapshot 상태 + 발동 로깅 (BAT-021)
전략 이탈로 슬롯이 내부 청산 루틴(워치독) 소유로 넘어간 상태가 무성이라
운영자가 인지할 수 없던 문제(issue #12)를 가시화한다.

거버넌스: 전부 change-req (신규 REQ 없음, dcy-017 OQ-3; Redis 미채택).
- REQ-034/021: WatchdogStore.last_seen + snapshot RPC 응답에 holding 슬롯
  워치독 상태(expired/last_seen) read-only 동봉(best-effort)
- REQ-032: trex-core slots 가 expired 슬롯을 WATCHDOG-OWNED(전략 이탈) 표시
- REQ-037/038/039: safety-sell 워치독 소유 진입(WARN)/이탈(INFO) + 발동
  (forced_close·stop_loss=WARN, take_profit·market_close=INFO) 로그 +
  in-process 카운터(SafetySellService.counters), 구조화 필드(slot_id/
  portfolio_id/reason/phase/instance_key). per-tick 스킵은 카운터만
- REQ-042: runtime-flows/interface-contract 정합

best-effort 격리(NFR-1): 모든 가시화는 예외 격리 — 워치독 판정·청산 불변.
dcy-016 억제 읽기로 관측이 워치독을 리셋하지 않음(비침습).

검증: 단위 335 passed/34 skipped, 인프라 게이트(PG+RabbitMQ) 369 passed, ruff clean.
신규 test_safety_sell_visibility(6) + snapshot watchdog(1) + CLI(1). BAT-021 rc.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-08 17:10:26 +09:00

7.1 KiB
Raw Blame History

req-037: forced_close_at 만기 자동 매도 루프

  • Status: Implemented
  • Type: Integration
  • Priority: High
  • Owner: project maintainers

Intent

슬롯에 설정된 forced_close_at 만기 시각이 도달했을 때 데몬이 자율적으로 매도를 제출하는 백그라운드 루프를 추가한다. 워치독이 만료된 슬롯에 한해 동작하므로 전략 모듈이 정상 활성 중인 슬롯에는 개입하지 않는다(dcy-007 FR-1, NFR-1).

Requirement

  • 데몬은 TREX_FORCED_CLOSE_CHECK_INTERVAL(초) 주기로 holding 상태이며 워치독이 만료된 슬롯 중 forced_close_at <= now(KST)인 슬롯을 감지해야 한다.
  • 조건에 해당하는 슬롯에 대해 SlotService.sell()(REQ-036)을 통해 내부 매도를 제출해야 한다.
  • 이 루프는 daemon.py의 safety-sell worker 스레드에서 실행된다. 기존 6개 스레드에 safety-sell worker(trex-safety-sell)를 추가한다.
  • forced_close_at이 None인 슬롯은 이 루프의 대상이 아니다.
  • 워치독 활성(만료되지 않은) 슬롯은 이 루프의 대상이 아니다.

Acceptance Criteria

  1. Given holding 상태이고 워치독 만료이며 forced_close_at = now() − 1s인 슬롯 S가 있을 때, When checker 1회가 수행되면, Then S에 대해 SlotService.sell()이 호출된다.
  2. Given holding 상태이고 워치독 만료이며 forced_close_at = now() + 1hour인 슬롯 S가 있을 때, When checker 1회가 수행되면, Then S에 대해 sell()이 호출되지 않는다.
  3. Given holding 상태이고 워치독 활성이며 forced_close_at = now() − 1s인 슬롯 S가 있을 때, When checker 1회가 수행되면, Then S에 대해 sell()이 호출되지 않는다(워치독 보호).
  4. Given forced_close_at = None인 슬롯 S가 있을 때, When checker 1회가 수행되면, Then S는 만기 체크 대상에서 제외된다.
  5. Given 대상 슬롯이 이미 pending_sell일 때, When checker 1회가 수행되면, Then 중복 sell() 호출이 발생하지 않는다(REQ-036 idempotency 적용).
  6. Given TREX_FORCED_CLOSE_CHECK_INTERVAL=60일 때, When daemon이 정상 실행 중이면, Then checker는 60±허용오차 초 주기로 반복 실행된다(NFR-1: 감지 지연 ≤ 60초).

Impacted Area

  • Docs:
    • docs/runtime-flows.md — forced_close_at 만기 감지 루프 흐름 추가 (REQ-042 참조)
    • docs/project-structure.md — trex-safety-sell 스레드 추가 반영 (REQ-042 참조)
  • Modules:
    • trex_core/runtime/daemon.py — trex-safety-sell worker 스레드 추가, _safety_sell_loop() 메서드 구현
    • trex_core/domain/slot/service.py — forced_close_at 조건 평가 메서드(또는 checker 내 인라인)
  • Tests:
    • forced_close_at checker 단위 테스트 — 만기 슬롯 감지 + sell() 호출 확인
    • 워치독 활성 슬롯 통과 확인
    • idempotency 확인 (pending_sell 슬롯 중복 처리 없음)

Notes

  • safety-sell worker(trex-safety-sell)는 forced_close_at checker(REQ-037), stop-loss·take-profit checker(REQ-038), 장 마감 청산(REQ-039)을 하나의 통합 루프로 묶어 처리할 수 있다. 구체적인 스레드 분리/통합 방식은 batch planning에서 결정한다.
  • 체크 로직은 DB 조회(forced_close_at 필드)와 in-memory 워치독 store 비교만 수행하므로 KIS API 호출이 없다. 낮은 연산 비용.
  • TREX_FORCED_CLOSE_CHECK_INTERVAL env var는 REQ-041 참조.
  • SlotService.sell() 구현은 REQ-036 참조.

Change Log

CHG-20260607-01

  • Date: 2026-06-07
  • Author: Justin Ko

Change Summary

  • dcy-007 confirmed → REQ-037 초안 생성

Intent

  • forced_close_at 만기 자동 매도 루프(FR-1)를 독립 Integration REQ로 정규화

Acceptance Criteria Delta

  • Added: AC-1~6 전체

Impacted Area Delta

  • Docs: runtime-flows.md, project-structure.md (REQ-042에서 처리)
  • Modules: daemon.py (스레드 추가), service.py (forced_close 조건 평가)
  • Tests: checker 단위 테스트 3종

Delivery Trace Delta

  • Batches: none (미할당)
  • Releases: none

Revalidation Impact

  • Reverification Needed: no
  • Existing Implementation Invalidated: no
  • Status Recommendation: keep-current
  • Follow-up Action: suggest-batch-reqs 후 batch 배정

CHG-20260607-02

  • Date: 2026-06-07
  • Author: Justin Ko

Change Summary

  • Proposed → Approved (confirm-req)

Intent

  • 승인 게이트 통과: 필수 필드·AC·Impacted Area·Change Log 완비 확인. dcy-007(confirmed) 범위 및 결정 사항과 모순 없음.

Acceptance Criteria Delta

  • (변경 없음)

Impacted Area Delta

  • (변경 없음)

Delivery Trace Delta

  • Batches: none (미할당)
  • Releases: none

Revalidation Impact

  • Reverification Needed: no
  • Existing Implementation Invalidated: no
  • Status Recommendation: keep-current
  • Follow-up Action: suggest-batch-reqs 후 batch 배정

CHG-20260607-03

  • Date: 2026-06-07
  • Author: Justin Ko

Change Summary

  • Status Approved → Implemented (confirm-req-implemented, 근거 batch BAT-011 release-candidate).

Intent

  • BAT-011 verification.md 의 AC↔evidence(SafetySellService forced_close checker — 만기/미만기/워치독 보호/None 제외/idempotency/캐던스)로 AC-1~6 충족 → 구현 완료 동기화.

Acceptance Criteria Delta

  • (변경 없음)

Impacted Area Delta

  • (변경 없음)

Delivery Trace Delta

  • Batches: BAT-011 (release-candidate)
  • Releases: none

Revalidation Impact

  • Reverification Needed: no
  • Existing Implementation Invalidated: no
  • Status Recommendation: keep-current (Implemented)
  • Follow-up Action: draft-release 로 BAT-011 배포 편성.

CHG-20260608-01

  • Date: 2026-06-08
  • Author: Justin Ko

Change Summary

  • (change-req, dcy-017/issue #12) safety-sell 가시화: 워치독 소유 진입(전략 이탈)=WARN/이탈(복귀)=INFO(_update_ownership, run_once 매 tick), forced_close 발동=WARN(_log_fire) + in-process 카운터(SafetySellService.counters). 구조화 필드(slot_id/portfolio_id/reason/phase/instance_key). best-effort(가시화 실패가 청산을 막지 않음). 트리거 판정 동작 불변. BAT-021 재구현·재검증 완료 → Implemented 유지.

Intent

  • 전략 이탈→안전망 소유, forced_close 발동을 무성에서 가시화(OQ-2 차등 매트릭스).

Acceptance Criteria Delta

  • Added:
    • 워치독 소유 진입/이탈 + forced_close 발동 로그/카운터. per-tick 스킵은 카운터만.

Impacted Area Delta

  • Modules: trex_core/application/safety_sell.py(_update_ownership/_log_fire/counters/instance_key), trex_core/runtime/daemon.py(instance_key 배선)
  • Tests: tests/application/test_safety_sell_visibility.py

Delivery Trace Delta

  • Batches: BAT-021
  • Releases: none

Revalidation Impact

  • Reverification Needed: yes (BAT-021 에서 완료)
  • Existing Implementation Invalidated: no (관측만 추가, 트리거 불변)
  • Status Recommendation: Implemented (change-req → BAT-021 재검증)
  • Follow-up Action: confirm-req-implemented(BAT-021).

Validation Plan

  • BAT-021: 소유 진입 WARN/이탈 INFO/forced_close WARN + 카운터 단위(caplog).