# req-052: safety-sell per-slot 재시도 쿨다운 + 영속 거부 경고 - Status: Implemented - Type: Integration - Priority: High - Owner: project maintainers ## Intent `SafetySellService`가 브로커 영속 거부(휴장/잔고부족/규제) 상황에서 동일 슬롯에 매도를 무한 재제출하는 문제(2026-06-07 실 KIS LIVE SL/TP e2e 발견, REL-003 Feedback)를 막는다. 슬롯별 마지막 시도 시각·시도 횟수를 인메모리로 추적해 `min_retry_interval_sec` 이내 재시도를 건너뛰고, 반복 거부 시 운영자 경고를 발행한다. 추가 스레드·DB 스키마 변경 없음(dcy-010 FR-1~4, NFR-1/2/4). ## Requirement - `SafetySellService`는 슬롯별 `last_attempt_at: dict[UUID, datetime]`, `attempt_count: dict[UUID, int]`를 인메모리로 추적한다. - 각 트리거(`forced_close`/`stop_loss`/`take_profit`)가 `_safe_sell()`을 호출하기 전, 해당 슬롯이 `now - last_attempt_at < min_retry_interval_sec`이면 **건너뛴다**(재시도 안 함). market_close 는 `_last_liquidation_date`로 이미 하루 1회 보호되므로 쿨다운 대상에서 제외(dcy-010 A-3). - 쿨다운 만료(`>= min_retry_interval_sec`) 후 트리거 조건이 여전히 충족되면 재시도하고, 성공 여부와 무관하게 `last_attempt_at`을 갱신하고 `attempt_count`를 증가시킨다. - `attempt_count >= max_retry_warn_count`인 슬롯은 재시도 직전 `WARNING` 로그를 발행한다(포함: `slot_id`, `attempt_count`, `reason`, `last_attempt_at`). - 슬롯이 `_holding()` 목록에서 사라지면(CLOSED/IDLE 등) 해당 슬롯의 `last_attempt_at`/`attempt_count` 항목을 제거한다(상태 누수 방지). - `min_retry_interval_sec`/`max_retry_warn_count`는 `SafetySellConfig`(REQ-053)에서 주입받는다. 단일 `trex-safety-sell` 스레드 호출이라 별도 락 없음. ## Acceptance Criteria 1. Given paper 브로커 `reject_symbols`로 거부 시뮬레이션 + stop-loss 트리거 슬롯, When `run_once()`를 쿨다운 간격 이내에 N회 호출, Then `_safe_sell`(또는 `SlotService.sell`) 재호출이 첫 1회 이후 발생하지 않는다(S-1). 2. Given 슬롯 `last_attempt_at`을 `min_retry_interval_sec` 이전 과거로 설정 + 트리거 조건 유지, When `run_once()`/`check_*` 호출, Then 재시도 1회 실행 + `last_attempt_at` 갱신(S-2). 3. Given `attempt_count = max_retry_warn_count - 1` 상태에서 다음 재시도, When 재시도 직전, Then `WARNING` 로그가 발행되고 `slot_id`/`attempt_count`/`reason` 포함(S-3). 4. Given 슬롯이 CLOSED 로 전환되어 `_holding()`에서 사라짐, When 다음 `run_once()`, Then `last_attempt_at`/`attempt_count`에서 해당 슬롯 항목이 제거된다(S-4). 5. Given market_close 트리거, When 쿨다운 로직 평가, Then market_close 는 쿨다운 적용 대상이 아니다(하루 1회 가드 유지). 6. Given 가격 회복 등으로 트리거 조건이 해소된 슬롯, When `check_prices()`, Then `_safe_sell` 미호출 + `attempt_count` 미증가(R-1). ## Impacted Area - Docs: - `docs/runtime-flows.md` — safety-sell tick 에 쿨다운 체크 단계 (REQ-054 처리) - Modules: - `trex_core/application/safety_sell.py` — `__init__`(`_last_attempt`/`_attempt_count`), `check_forced_close`/`check_prices`(쿨다운 check/update), `run_once`(holding 이탈 슬롯 정리), 경고 로그 - Tests: - `tests/application/test_safety_sell_worker.py`(또는 신규) — 쿨다운 skip/만료 재시도/경고 로그/정리/market_close 제외/트리거 해소(paper reject_symbols 시뮬레이션) ## Notes - dcy-010 OQ-3 Answered: 단일 per-slot 쿨다운(트리거별 분리 안 함). - ExecutionService→SafetySellService 거부 사유 피드백 채널은 Out of Scope — `_holding()` 재등장으로 암묵적 거부 감지(A-2). - 쿨다운 상태는 재시작 시 초기화(허용, R-2) — DB 영속 Out of Scope. - NFR-1(인메모리)/NFR-2(무락)/NFR-4(paper reject 시뮬 테스트)는 본 REQ AC 로 흡수. ## Change Log ### CHG-20260608-01 - Date: 2026-06-08 - Author: Justin Ko #### Change Summary - dcy-010(confirmed) → REQ-052 초안 생성. #### Intent - safety-sell 영속 거부 per-slot 쿨다운 + 경고(FR-1~4)를 Integration REQ 로 정규화. #### Acceptance Criteria Delta - Added: AC-1~6 전체 #### Impacted Area Delta - Docs: runtime-flows.md (REQ-054) - Modules: safety_sell.py - Tests: test_safety_sell_worker.py(쿨다운) #### 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 로 승인. ### CHG-20260608-02 - Date: 2026-06-08 - Author: Justin Ko #### Change Summary - Proposed → Approved (confirm-req). #### Intent - 승인 게이트 통과: 필수 필드·구체 AC(1~6)·Impacted Area·Change Log 완비. dcy-010(confirmed) FR-1~4/OQ 와 모순 없음. ExecutionService 피드백 채널·DB 영속은 Out of Scope(비핵심). #### 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: draft-batch 로 delivery 편성(batch-lite). ### CHG-20260608-03 - Date: 2026-06-08 - Author: Justin Ko #### Change Summary - Status Approved → Implemented (confirm-req-implemented, 근거 batch BAT-014 release-candidate). #### Intent - BAT-014 verification 의 AC↔evidence(쿨다운 6 단위: skip/만료 재시도/경고/prune/market_close 제외/트리거 해소)로 AC-1~6 충족. 단위 278 passed, ruff clean. #### Acceptance Criteria Delta - (변경 없음) #### Impacted Area Delta - (변경 없음) #### Delivery Trace Delta - Batches: BAT-014 (release-candidate) - Releases: none #### Revalidation Impact - Reverification Needed: no - Existing Implementation Invalidated: no - Status Recommendation: keep-current (Implemented) - Follow-up Action: draft-release 로 BAT-014 배포 편성. 실 운영 거부 시나리오는 차기 거래일 관찰.