From b0539e752c0034b2b8f36777e0fca519178d0487 Mon Sep 17 00:00:00 2001 From: Justin Ko Date: Sun, 7 Jun 2026 17:21:16 +0900 Subject: [PATCH] =?UTF-8?q?docs(discovery):=20dcy-010=20OQ-3=20Answered=20?= =?UTF-8?q?=E2=80=94=20=EB=8B=A8=EC=9D=BC=20per-slot=20=EC=BF=A8=EB=8B=A4?= =?UTF-8?q?=EC=9A=B4=20=ED=99=95=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 4.6 --- ...afety-sell-persistent-rejection-backoff.md | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/docs/discovery/dcy-010_20260607_safety-sell-persistent-rejection-backoff.md b/docs/discovery/dcy-010_20260607_safety-sell-persistent-rejection-backoff.md index f198bf3..d670cc8 100644 --- a/docs/discovery/dcy-010_20260607_safety-sell-persistent-rejection-backoff.md +++ b/docs/discovery/dcy-010_20260607_safety-sell-persistent-rejection-backoff.md @@ -4,7 +4,7 @@ - 문서 ID: dcy-010_20260607_safety-sell-persistent-rejection-backoff - 이슈명: safety-sell 영속 거부 재시도 백오프 — 장 휴장·잔고 부족·규제 등 지속 거부 상황에서 KIS 주문 spam 방지를 위한 슬롯별 재시도 backoff/cooldown 정책 - 작성 시각(KST): 2026-06-07 -- 마지막 갱신 시각(KST): 2026-06-07 +- 마지막 갱신 시각(KST): 2026-06-07 (OQ-1 Answered: min_retry_interval_sec = 600s, OQ-2 Answered: max_retry_warn_count = 3 로그만 발행, OQ-3 Answered: 단일 per-slot 쿨다운) - 대체됨: 없음 - 후속 Discovery 참조: 없음 - 생성된 REQ 참조: 없음 @@ -59,7 +59,7 @@ # 4. 이번에 정의할 변경 - **`SafetySellService` per-slot 쿨다운 추적 도입**: `_last_attempt: dict[UUID, datetime]`와 `_attempt_count: dict[UUID, int]`를 추가한다. `_safe_sell()` 호출 직전 쿨다운 체크를 수행하고, 시도 후 `last_attempt`와 `attempt_count`를 갱신한다. 슬롯이 CLOSED 또는 IDLE로 전환되면(= `_holding()`에서 사라지면) 해당 슬롯의 쿨다운 상태를 자동 정리한다. -- **`SafetySellConfig` 확장**: `min_retry_interval_sec`(기본값 TBD), `max_retry_warn_count`(기본값 TBD) 두 env var을 추가한다. 기존 5종 env var(`TREX_WATCHDOG_TIMEOUT` 등)과 같은 방식으로 optional 파싱한다. +- **`SafetySellConfig` 확장**: `min_retry_interval_sec`(기본값 `600`, OQ-1 Answered), `max_retry_warn_count`(기본값 `3`, OQ-2 Answered) 두 env var을 추가한다. 기존 5종 env var(`TREX_WATCHDOG_TIMEOUT` 등)과 같은 방식으로 optional 파싱한다. - **운영자 경고 로그**: `attempt_count >= max_retry_warn_count`인 슬롯에 대해 `_log.warning("safety-sell repeated rejection: slot=%s count=%d reason=persistent_rejection", ...)` 경고를 발행한다. 이 경고는 쿨다운 체크 통과 후 재시도 직전에 발행한다. ## 영향 범위 @@ -81,7 +81,7 @@ - FR-2: 쿨다운 만료(`now - last_attempt_at >= min_retry_interval_sec`) 후 트리거 조건이 여전히 충족되면 재시도한다. 재시도 성공 여부와 무관하게 `last_attempt_at`을 갱신한다. - FR-3: `attempt_count >= max_retry_warn_count`인 슬롯에 대해 재시도 직전에 `WARNING` 로그를 발행한다. 로그에는 `slot_id`, `attempt_count`, `reason`, `last_attempt_at`을 포함한다. - FR-4: 슬롯이 HOLDING 상태를 벗어나 `_holding()` 목록에서 사라지면(CLOSED, IDLE 등) 해당 슬롯의 `last_attempt_at`과 `attempt_count` 항목을 제거한다. -- FR-5: `SafetySellConfig`에 `min_retry_interval_sec`(`TREX_SAFETY_SELL_MIN_RETRY_INTERVAL`, optional)과 `max_retry_warn_count`(`TREX_SAFETY_SELL_MAX_RETRY_WARN_COUNT`, optional) 두 env var를 추가한다. 미설정 시 기본값으로 동작한다. +- FR-5: `SafetySellConfig`에 `min_retry_interval_sec`(`TREX_SAFETY_SELL_MIN_RETRY_INTERVAL`, optional, 기본값 `600`)과 `max_retry_warn_count`(`TREX_SAFETY_SELL_MAX_RETRY_WARN_COUNT`, optional, 기본값 `3`) 두 env var를 추가한다. 미설정 시 기본값으로 동작한다. - FR-6: `docs/runtime-flows.md`를 갱신해 safety-sell tick 흐름에 "쿨다운 체크" 단계를 반영한다. ## 비기능 요구사항 @@ -118,7 +118,7 @@ ## 가정 -- A-1: 영속 거부의 주요 원인(휴장, 잔고 부족, 규제)은 수십 분~수 시간 지속된다. 따라서 `min_retry_interval_sec` 기본값은 분 단위(예: 300s~1800s)가 적합하다. +- A-1: 영속 거부의 주요 원인(휴장, 잔고 부족, 규제)은 수십 분~수 시간 지속된다. `min_retry_interval_sec = 600s`(기본값)는 일시 거부 원인이 해소되기를 기다리는 중간값으로 적합하다(OQ-1 Answered). - A-2: 슬롯이 쿨다운 중 HOLDING으로 돌아온 것은 이전 매도 시도가 브로커에서 거부됐음을 의미한다. 별도 피드백 채널 없이도 `_holding()` 재등장으로 암묵적 거부를 감지할 수 있다. - A-3: market_close 트리거(`check_market_close`)는 하루 1회 `_last_liquidation_date`로 이미 중복 방지가 되어 있어 쿨다운 적용 대상에서 제외한다. - A-4: `SafetySellService.run_once()`는 `trex-safety-sell` 단일 스레드에서만 호출되므로 쿨다운 `dict` 동시성 문제 없음. @@ -126,17 +126,17 @@ ## 오픈 질문 - OQ-1: `min_retry_interval_sec` 기본값 — 어느 정도가 적합한가? (예: 300s / 600s / 1800s) - - 상태: Open - - 처리 방안: Justin Ko가 실 KIS 운용 경험(휴장 시 평균 거부 지속 시간)을 기준으로 결정. - - 종료 조건: confirm-discovery 또는 REQ 단계에서 Justin Ko가 값을 확정하면 Answered. + - 상태: Answered + - 결정: `600s` (10분). 대부분의 일시 거부 원인(잔고 부족, 일시 규제)이 해소되기를 기다리는 중간값. 휴장 9시간 기준 최대 54회 재시도로 쿼터 소모를 수용 가능한 수준으로 제한. + - 반영 위치: FR-5 env var 기본값, A-1 - OQ-2: `max_retry_warn_count` 기본값 및 경고 이후 동작 — 경고 로그만? 아니면 N회 초과 시 쿨다운 무기한 전환 + 수동 해제? - - 상태: Open - - 처리 방안: Justin Ko가 운용 정책(로그만으로 충분한지, 수동 해제 필요한지)을 결정. - - 종료 조건: confirm-discovery에서 확정. + - 상태: Answered + - 결정: `max_retry_warn_count = 3`, 경고 로그만 발행. 무기한 차단 없음. 3회(600s × 3 = 30분 지속 거부) 시점에서 WARNING 로그 발행으로 운영자 인지. 재시도는 쿨다운 만료 후 계속 허용. + - 반영 위치: FR-3, FR-5 env var 기본값 - OQ-3: 트리거 종류별(`forced_close` vs `stop_loss/take_profit`) 쿨다운 분리 여부 — 단일 per-slot 쿨다운으로 통합할지, 트리거별로 독립 쿨다운을 유지할지? - - 상태: Open - - 처리 방안: 단일 쿨다운(단순)과 트리거별 분리(세밀 제어) 트레이드오프를 Justin Ko와 협의. - - 종료 조건: confirm-discovery에서 확정. + - 상태: Answered + - 결정: 단일 per-slot 쿨다운. 거부 원인(휴장/잔고/규제)은 트리거 종류와 무관하게 슬롯·시장 상태에 종속적이므로 트리거별 분리는 복잡도만 늘린다. `_last_attempt: dict[UUID, datetime]` 단일 구조로 구현. + - 반영 위치: FR-1, Section 4 쿨다운 추적 설계 ### 오픈 질문 처리 기준 @@ -188,9 +188,9 @@ ## DECIDE -- {{DECIDE_MIN_RETRY_INTERVAL}}: `min_retry_interval_sec` 기본값 결정 — 300s / 600s / 1800s 중 선택, 또는 다른 값. 실 KIS 운용에서 휴장·잔고 부족 등 영속 거부의 평균 지속 시간 기준으로 판단(OQ-1). -- {{DECIDE_MAX_RETRY_WARN_AND_ACTION}}: `max_retry_warn_count` 기본값 및 경고 이후 동작 결정 — 로그만 발행할지, N회 초과 시 쿨다운 무기한 전환 + 수동 해제 방식으로 전환할지(OQ-2). -- {{DECIDE_COOLDOWN_GRANULARITY}}: 단일 per-slot 쿨다운(simple) vs 트리거 종류별(`forced_close`/`stop_loss`/`take_profit`) 독립 쿨다운(granular) 결정(OQ-3). +- DECIDE_MIN_RETRY_INTERVAL: 확정됨. `min_retry_interval_sec = 600s` (OQ-1 Answered). +- DECIDE_MAX_RETRY_WARN_AND_ACTION: 확정됨. `max_retry_warn_count = 3`, 경고 로그만 발행 (OQ-2 Answered). +- DECIDE_COOLDOWN_GRANULARITY: 확정됨. 단일 per-slot 쿨다운 (OQ-3 Answered). ## CONFIRM @@ -213,7 +213,7 @@ ## Freeze 플래그 - Handoff Decision: 보류 -- Handoff Rationale: Safety-sell 쿨다운 메커니즘 설계 완료. OQ-1(min_retry_interval_sec 기본값), OQ-2(경고 후 동작), OQ-3(쿨다운 분리 여부) 세 항목이 Open 상태로 confirm-discovery 전 Justin Ko 결정 필요. +- Handoff Rationale: Safety-sell 쿨다운 메커니즘 설계 완료. OQ-1(600s), OQ-2(3회 경고 로그), OQ-3(단일 per-slot) 모두 Answered. confirm-discovery 전 Justin Ko 최종 승인 필요. - Ready for REQ Drafting: false - Confirmed By: TBD - Confirmed At (KST): TBD