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>
This commit is contained in:
@@ -0,0 +1,54 @@
|
||||
# BAT-011 Planning
|
||||
|
||||
- Status: confirmed
|
||||
- Batch: BAT-011
|
||||
- Profile: standard
|
||||
|
||||
## Included REQ
|
||||
- REQ-037 (Integration): `forced_close_at` 만기 자동 매도 루프 — `holding` + 워치독 만료 + `forced_close_at <= now(KST)` 슬롯에 `SlotService.sell()`. `TREX_FORCED_CLOSE_CHECK_INTERVAL` 주기. KIS 호출 없음(DB + in-memory watchdog 비교).
|
||||
- REQ-038 (Integration): stop-loss·take-profit KIS 현재가 polling + 트리거 — `TTTC8397R`(실전)/`VTTC8397R`(모의) `stck_prpr` 기준, `현재가 <= stop_loss_price`(손절)/`현재가 >= take_profit_price`(익절) 시 `sell()`. `TREX_STOP_LOSS_POLL_INTERVAL` 주기. 쿼터 상한 `N × (3600/interval)` 경고.
|
||||
- REQ-039 (Integration): 장 마감 강제 청산 — `TREX_MARKET_CLOSE_LIQUIDATION_TIME`(HH:MM KST) 도래 시 워치독 무관 전체 `holding` 청산. 거래일 1회 발동 가드(`last_liquidation_date`).
|
||||
- REQ-040 (Interface): `update_thresholds` intent RPC 액션 — `{action, slot_id, stop_loss_price?, take_profit_price?, forced_close_at?}` 부분 갱신 + 워치독 `last_seen_at` 리셋 + holding-only. `reply_to` 시 `{status: updated|ignored, slot_id}` 응답.
|
||||
- REQ-042 (Documentation): baseline 3문서 정합 — `interface-contract.md`(update_thresholds + price 필드), `runtime-flows.md`(워치독 갱신·forced_close·가격 체크·장 마감 흐름 + `:114` A-5 주석 갱신), `project-structure.md`(`trex-safety-sell` 스레드 + env 5종 + KIS polling/watchdog 모듈).
|
||||
|
||||
## Delivery Plan
|
||||
1. REQ-038 KIS 현재가 polling 어댑터: `get_current_price(symbol)` (`TTTC8397R`/`VTTC8397R`, `stck_prpr` 파싱, 실전/모의 분기). paper 환경 stub. 어댑터 단위 테스트.
|
||||
2. REQ-037 + REQ-038 + REQ-039 통합 worker: `daemon.py`에 `trex-safety-sell` 스레드 + `_safety_sell_loop()` 추가. 루프 1 tick에서 (a) forced_close_at 만기, (b) stop-loss/take-profit 가격 체크, (c) 장 마감 청산 시각 평가를 순차 수행. 각 경로 `SlotService.sell()` 호출. mock 기반 단위 테스트(만기/손절/익절/미충족/워치독 보호/idempotency/장 마감 전체·중복가드).
|
||||
3. REQ-040 `update_thresholds`: `SlotService.update_thresholds(...)` + `make_intent_rpc_handler` action 라우팅 + `last_seen_at` 리셋 + holding-only 응답. handler 단위 테스트(부분 갱신/null 해제/non-holding ignored/last_seen_at 리셋).
|
||||
4. REQ-039 config: `TREX_MARKET_CLOSE_LIQUIDATION_TIME` 활용 확인(REQ-041에서 파싱 완료 — 본 batch는 소비).
|
||||
5. REQ-042 baseline 문서 3종 정합 — 구현 완료 상태 반영.
|
||||
6. 전체 회귀 green + ruff lint clean + BAT-010 잔여 R-2(실 PG 마이그레이션 round-trip) 검증.
|
||||
|
||||
## Dependencies
|
||||
- Internal:
|
||||
- BAT-010/REQ-034: `WatchdogStore.is_expired()` / `in_grace_period()` — 트리거 루프의 워치독 만료 판정 계약(read 측).
|
||||
- BAT-010/REQ-035: `stop_loss_price`/`take_profit_price` 도메인 필드 — 가격 체크 기준값.
|
||||
- BAT-010/REQ-036: `SlotService.sell(slot_id, actor, reason)` — 모든 트리거의 매도 제출 경로.
|
||||
- BAT-010/REQ-041: safety-sell env vars 5종 파싱(`DaemonConfig`) — 본 batch가 값을 소비.
|
||||
- BAT-007/REQ-022: `make_intent_rpc_handler` — `update_thresholds` action 확장 대상.
|
||||
- BAT-004/REQ-012: `KISBroker`(`kis.py`) — 현재가 polling 어댑터 추가/확장 대상.
|
||||
- External:
|
||||
- KIS Open API 현재가 시세 endpoint(`TTTC8397R`/`VTTC8397R`). 실 호출은 운영(`broker=kis`) 환경; 테스트는 mock/stub.
|
||||
|
||||
## Design Gate
|
||||
- Design Required Before Implementation: yes
|
||||
- Reason: 본 batch는 (a) **신규 daemon worker 스레드(`trex-safety-sell`)**를 추가하고 BAT-010의 WatchdogStore를 새로운 스레드에서 read하므로 동시성 계약(GIL 의존 read vs. snapshot RPC handler write)을 명시해야 한다. (b) **신규 외부 I/O(KIS 현재가 polling)** 어댑터의 배치(`kis.py` 확장 vs. 신규 `kis_price.py`), 실전/모의 endpoint 분기, paper stub 전략, 쿼터 상한 산정을 설계에서 확정해야 한다. (c) forced_close·가격 체크·장 마감을 **단일 통합 루프**로 묶는 구조와 장 마감 1회 발동 가드(`last_liquidation_date`) 상태 모델을 설계에서 고정해야 구현·검증 drift가 없다. → `design.md`를 동일 턴에 scaffold한다.
|
||||
|
||||
## Milestones
|
||||
1. M1 — REQ-038 KIS 현재가 polling 어댑터 + 단위 테스트 green (실전/모의 분기, `stck_prpr` 파싱)
|
||||
2. M2 — `trex-safety-sell` worker + forced_close_at 만기 루프(REQ-037) + 단위 테스트 green
|
||||
3. M3 — stop-loss·take-profit 가격 체크(REQ-038) worker 통합 + mock 단위 테스트 green(손절/익절/미충족/워치독 보호/쿼터 산정)
|
||||
4. M4 — 장 마감 강제 청산(REQ-039) + 거래일 1회 가드 + 단위 테스트 green
|
||||
5. M5 — `update_thresholds` RPC(REQ-040) + handler 단위 테스트 green
|
||||
6. M6 — baseline 3문서 정합(REQ-042) + 전체 회귀 + ruff clean + BAT-010 R-2 검증
|
||||
|
||||
## Risks
|
||||
- R-1 (Medium): KIS 현재가 polling 쿼터 — 워치독 만료 슬롯 N개 × (3600/`TREX_STOP_LOSS_POLL_INTERVAL`) 호출이 KIS 분당/일일 쿼터를 초과할 수 있음. 대응: REQ-038 AC-8 상한 산정 + 임계 초과 시 경고 로그. 폴링 대상은 "워치독 만료" 슬롯으로 한정해 평시 호출 최소화.
|
||||
- R-2 (Medium): WatchdogStore cross-thread read — safety-sell worker가 snapshot RPC handler thread의 write와 동시 접근. 대응: BAT-010 Key Decision 3(GIL 보장 단일 dict, lock 없음) 계약을 그대로 따르고 read-only 판정만 수행. 단위 테스트로 만료 판정 고정.
|
||||
- R-3 (Medium): 장 마감 청산 발동 정밀도/중복 — worker 루프 주기에 의존하는 시각 평가 + 재시작 시 in-memory `last_liquidation_date` 초기화로 동일 거래일 재발동 위험. 대응: 발동 가드 + (필요 시) 발동 시각 경계 처리 설계에서 확정.
|
||||
- R-4 (Low): KIS 거부/네트워크 예외 시 루프 안정성 — polling 또는 sell 제출 중 예외가 worker 스레드를 죽이면 안 됨. 대응: tick 단위 try/except + 로그 후 다음 tick 지속(REQ-024 degrade 정책 정합).
|
||||
- R-5 (Low, 이월): BAT-010 잔여 R-2 — `004_add_price_thresholds.sql` 실 PostgreSQL round-trip 미검증. 대응: 본 batch verification에서 실 PG 적용 + round-trip 확인.
|
||||
|
||||
## Notes
|
||||
- safety-sell worker는 REQ-037 Notes의 권고(forced_close·stop-loss/take-profit·장 마감을 하나의 통합 루프로 처리)를 따른다. 루프 주기와 가격 체크 주기가 다를 경우(`TREX_FORCED_CLOSE_CHECK_INTERVAL` vs `TREX_STOP_LOSS_POLL_INTERVAL`) tick 내 sub-scheduling 방식은 design에서 확정한다.
|
||||
- REQ-040 OQ-6(non-holding 슬롯 처리)는 REQ-040 AC-6에서 `{status: ignored, reason: "not_holding"}` 응답으로 이미 결정됨 — 구현은 이를 따른다.
|
||||
Reference in New Issue
Block a user