Files
trex-core/docs/batches/bat-010_20260607_safety-sell-foundation/design.md
T
genichinandClaude Opus 4.8 a30efa8f3b feat(safety-sell): dcy-007 core 자율 안전 매도 — BAT-010 기반 + BAT-011 트리거
dcy-007(core 자율 안전 매도) 전체 delivery. BAT-010 기반 인프라가 미커밋
상태였고 BAT-011 트리거 레이어와 공유 파일에서 섞여 있어 한 커밋으로 묶음.

BAT-010 (REQ-034/035/036/041, release-candidate):
- WatchdogStore(application/watchdog.py) + snapshot RPC 워치독 갱신
- Slot stop_loss_price/take_profit_price 필드 + 004_add_price_thresholds.sql
- SlotService.sell() 자율 매도 내부 경로 + idempotency
- SafetySellConfig env 5종 파싱

BAT-011 (REQ-037/038/039/040/042, release-candidate):
- SafetySellService(application/safety_sell.py): forced_close 만기 / stop-loss·
  take-profit KIS polling / 장 마감 강제 청산 + trex-safety-sell 7번째 스레드
- BrokerPort.get_current_price + KIS inquire-price(TTTC8397R/VTTC8397R, stck_prpr)
  + paper stub
- SlotService.update_thresholds() + intent RPC update_thresholds 액션
  (부분 갱신/null 해제, 워치독 last_seen_at 리셋)
- baseline 3문서 정합(interface-contract/runtime-flows/project-structure)

거버넌스: REQ-034~042 Implemented 동기화, BAT-010/011 release-candidate,
dcy-007 discovery 포함.

검증: 신규 38 테스트 green, 단위 236 passed/23 skipped,
인프라 게이트(PG+RabbitMQ) 259 passed(마이그레이션 001~004 round-trip 포함),
ruff clean.

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

4.4 KiB

BAT-010 Design

  • Status: confirmed
  • Batch: BAT-010

Architecture Summary

BAT-010은 safety-sell 기능의 기반 레이어 세 가지를 추가한다.

  1. WatchdogStore (application/watchdog.py 신규): dict[str, datetime] in-memory store. snapshot RPC handler가 응답 슬롯의 last_seen_at을 갱신하고 BAT-011 트리거 루프가 만료 판정에 사용한다.
  2. 도메인 필드 확장 (models.py, postgresql.py, SQL 마이그레이션): stop_loss_price, take_profit_price Optional[Decimal] additive 추가. BAT-011 KIS polling이 트리거 기준으로 사용한다.
  3. SlotService.sell() (domain/slot/service.py): RabbitMQ 우회 내부 매도 경로. BAT-011의 모든 트리거 루프(forced_close, KIS polling, 장 마감)가 이 메서드를 호출한다.

Changed Areas

  • Docs:
    • 없음 (baseline 문서 갱신은 REQ-042 / BAT-011에서 처리)
  • Modules:
    • trex_core/runtime/config.py — DaemonConfig에 safety-sell env vars 5종 파싱 추가
    • trex_core/domain/slot/models.py — stop_loss_price: Optional[Decimal], take_profit_price: Optional[Decimal] 추가
    • trex_core/adapters/persistence/postgresql.py — 두 필드 read/write 포함
    • trex_core/adapters/persistence/sql/ — 마이그레이션 스크립트 신규 추가
    • trex_core/application/intents.py — IntentIn에 stop_loss_price, take_profit_price Optional 필드 추가
    • trex_core/application/messaging_handlers.py — make_snapshot_rpc_handler 확장(last_seen_at 갱신) + intent RPC payload 파싱 확장(price fields)
    • trex_core/application/watchdog.py (신규) — WatchdogStore class
    • trex_core/domain/slot/service.py — SlotService.sell(slot_id, actor, reason) 추가
  • Interfaces:
    • SlotService.sell(slot_id: str, actor: str, reason: str) -> None (신규 내부 인터페이스)
    • WatchdogStore.update(slot_id: str) -> None (신규)
    • WatchdogStore.is_expired(slot_id: str, timeout_s: int) -> bool (신규 — BAT-011 트리거 루프 계약)
    • WatchdogStore.in_grace_period(grace_s: int) -> bool (신규)

Key Decisions

  1. WatchdogStore를 watchdog.py 신규 모듈로 분리: portfolio.py는 snapshot 조회·직렬화 책임이 있어 watchdog 관심사(타임스탬프 추적·만료 판정)를 추가하면 단일 책임 위반. 신규 모듈이 독립 테스트와 BAT-011 참조에 유리하다.
  2. REQ-041 env vars 5종 전체를 BAT-010에서 파싱 완성: TREX_FORCED_CLOSE_CHECK_INTERVAL·TREX_STOP_LOSS_POLL_INTERVAL·TREX_MARKET_CLOSE_LIQUIDATION_TIME은 BAT-010에서 사용하지 않지만, REQ-041을 두 batch에 분리하면 REQ 단위 거버넌스 trace가 깨진다. 파싱만 완성하고 BAT-011이 config 값을 참조한다.
  3. WatchdogStore 동시성은 Python GIL에 의존: dict[str, datetime] 단일 dict의 Python 레벨 read/write는 GIL 보장 하에 원자적. 별도 threading.Lock 불필요. grace period 판정: datetime.utcnow() - self.started_at < timedelta(seconds=grace_s) 단순 비교.
  4. SlotService.sell()은 transit_slot() 재사용: 기존 transit_slot(slot_id, from_state=HOLDING, to_state=PENDING_SELL, ...) advisory lock + outbox 경로를 그대로 재사용. 신규 로직을 최소화해 BAT-004 검증 coverage를 그대로 이어받는다.

Edge Cases

  • grace period 중 is_expired() 호출: in_grace_period() True이면 store에 없는 슬롯 포함 전체 만료 판정 유예.
  • 데몬 재시작 시 watchdog store: in-memory 초기화 + started_at 재설정으로 grace period 자동 재시작.
  • sell() 호출 시 이미 pending_sell 상태: transit_slot 호출 전 상태 조회 → silent return (중복 매도 intent 제출 없음).
  • sell() 호출 시 idle·cancelled 등 비정상 상태: SlotStateError(또는 동등 예외) raise, 슬롯 상태 불변.
  • 마이그레이션 기존 row: ALTER TABLE slots ADD COLUMN IF NOT EXISTS stop_loss_price NUMERIC 패턴으로 멱등성 보장. 기존 row NULL 유지.

Architecture Impact

  • runtime-flows: snapshot RPC handler → watchdog last_seen_at 갱신 흐름 추가 (BAT-011 REQ-042에서 문서화)
  • project-structure: watchdog.py 신규 모듈 추가 (BAT-011 REQ-042에서 문서화)

Reference Doc Update Plan

  • project-structure.md: BAT-011 REQ-042에서 처리 (watchdog.py 신규 모듈 반영)
  • runtime-flows.md: BAT-011 REQ-042에서 처리 (snapshot RPC → watchdog update + sell() 내부 경로 반영)