Files
trex-core/docs/srs/Integration/req-052_safety-sell-per-slot-cooldown.md
T
genichinandClaude Opus 4.8 565af6792c feat(safety-sell): dcy-010 BAT-014 — 영속 거부 재시도 쿨다운 (REQ-052~054)
dcy-010(safety-sell 영속 거부 재시도 백오프) 전체 SDLC: draft-req → confirm-req →
draft-batch(BAT-014 batch-lite) → run-batch-delivery → confirm-req-implemented.
2026-06-07 실 KIS LIVE SL/TP e2e에서 발견한 KIS 주문 spam 위험(영속 거부 시
매도 무한 재제출) 차단.

- REQ-052 (Integration): SafetySellService per-slot 인메모리 쿨다운
  (_in_cooldown/_attempt_sell/_prune_cooldown) — forced_close·stop-loss·take-profit
  트리거에 min_retry_interval_sec(600s) 이내 재시도 skip(가격 poll 포함) +
  max_retry_warn_count(3) 도달 시 WARNING + holding 이탈 정리. market_close 제외.
- REQ-053 (Configuration): SafetySellConfig min_retry_interval_sec/max_retry_warn_count
  + TREX_SAFETY_SELL_MIN_RETRY_INTERVAL/MAX_RETRY_WARN_COUNT env (additive)
- REQ-054 (Documentation): runtime-flows 쿨다운 flow + 가격체크 tr_id(FHKST01010100) 정정

인메모리 only(DB/스레드/인터페이스 변경 없음), default 미설정 시 기존 동작 불변.
검증: 신규 8 단위(쿨다운 6 + config 2), 전체 278 passed, ruff clean.
REQ-052~054 Implemented, BAT-014 release-candidate.

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

6.1 KiB

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(16)·Impacted Area·Change Log 완비. dcy-010(confirmed) FR-14/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 배포 편성. 실 운영 거부 시나리오는 차기 거래일 관찰.