# 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() 내부 경로 반영)