feat(rel-006): dcy-022/023 + BAT-032~034 — 실행 정책·qty 검증·allocated_capital 제거 통합

BAT-032 (dcy-022): timeout_sec/slippage_pct 실행 정책, 미체결 자동 취소,
보호 지정가, `trex-core cancel` 백스톱. 마이그레이션 012.
BAT-033: `trex-core enter --qty` 필수 검증 — 미지정 시 RPC 전 즉시 오류.
BAT-034 (dcy-023): allocated_capital 죽은 필드 전 경로 제거 (domain·intent·
persistence·snapshot·CLI). 마이그레이션 013(비가역 DROP). 외부 발신자
unknown key 무시 수신 의미론 적용.
REL-006 confirmed: BAT-026~034 통합 릴리즈, pre-release smoke 증거 포함.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-11 07:42:03 +09:00
co-authored by Claude Sonnet 4.6
parent 5ee5f7f4e2
commit fb05a73130
56 changed files with 3151 additions and 103 deletions
@@ -0,0 +1,64 @@
# BAT-032 Planning
- Status: done
- Batch: BAT-032
## Milestones
| ID | 목표 | 연결 REQ |
|---|---|---|
| M1 | 실행 정책 계약·파싱·슬롯 저장 | REQ-068, REQ-061(CHG-05) |
| M2 | slippage 보호 지정가 dispatch | REQ-070, REQ-013(CHG-03 partial) |
| M3 | timeout 집행 + race 처리 | REQ-069, REQ-013(CHG-03 partial) |
| M4 | 운영자 cancel 백스톱 CLI + control RPC | REQ-071 |
| M5 | baseline 문서 정합 | REQ-072 |
## M1 상세 (실행 정책 계약·파싱·슬롯 저장)
- `Slot` 모델: `timeout_sec: Optional[int]`, `slippage_pct: Optional[float]` 필드 추가. `EXPECTED_SLOT_FIELDS` 갱신.
- Migration 012: `ALTER TABLE daily_slot ADD COLUMN IF NOT EXISTS timeout_sec INTEGER; ADD COLUMN IF NOT EXISTS slippage_pct FLOAT;`
- `BuyIntent`/`SellIntent`: `timeout_sec`/`slippage_pct` 추가.
- `IntentService`: `__init__` 에 `default_order_timeout_sec: Optional[int] = None` 추가. `_admit_buy`/`_admit_sell` 에서 effective_timeout = intent.timeout_sec ?? config_default → transit_slot 에 전달.
- `SlotService.transit_slot` kwargs: `timeout_sec`/`slippage_pct` 전달. `_apply_transition_fields` 에서 PENDING_* 진입 시 set, 탈출(→HOLDING/→INACTIVE) 시 clear.
- `messaging_handlers.py`: `timeout_sec=_opt_int(body.get("timeout_sec"))`, `slippage_pct=_opt_float(body.get("slippage_pct"))` 파싱 추가.
- `DaemonInstanceConfig`: `default_order_timeout_sec: Optional[int] = None` 추가, from_toml 파싱.
- `DaemonConfig` + daemon runtime 배선: IntentService 에 default_order_timeout_sec 전달.
## M2 상세 (slippage 보호 지정가 dispatch)
- `execution._dispatch`: limit 계산 후 slippage 적용:
```python
import math
if limit is not None and slot.slippage_pct:
factor = 1 + slot.slippage_pct/100 if side == "buy" else 1 - slot.slippage_pct/100
limit = int(math.ceil(limit * factor)) if side == "buy" else int(math.floor(limit * factor))
```
- `_log.debug(...)` 로 slippage 적용 기록.
## M3 상세 (timeout 집행)
- `ExecutionResult`: `timeout_cancelled: int = 0` 추가.
- `_poll` 의 else branch(`_check_stale_pending` 호출)에서 timeout 체크 추가:
```python
self._check_stale_pending(slot, now, r) # Alert-only (REQ-014, no change)
self._try_timeout_cancel(slot, now, r) # NEW: cancel if timeout_sec exceeded
```
- `_try_timeout_cancel`: `slot.timeout_sec` 있고 경과 시간 초과 시 → `broker.cancel_order(...)` best-effort → `r.timeout_cancelled += 1` → WARNING 로그. 실패 시 WARNING, 상태 불변.
## M4 상세 (운영자 cancel 백스톱)
- `messaging_handlers.make_control_rpc_handler`: `cancel_pending` 액션 추가. `cancel_pending_fn` 콜백(slot_id → bool) 주입.
- `execution.ExecutionService.cancel_pending_slot(slot_id)`: 슬롯 조회 → PENDING_* 이면 `broker.cancel_order` 송신. 슬롯 직접 전이 없음.
- `cli.py`: `cancel <slot_id>` 서브커맨드 추가. `--dry-run`/`--yes`/`--live` 안전장치. control RPC 경유.
- daemon.py 배선: make_control_rpc_handler 에 cancel_pending_fn 전달.
## M5 상세 (baseline 문서)
- `docs/interface-contract.md`: buy/sell payload 에 `timeout_sec`/`slippage_pct` 추가.
- `docs/runtime-flows.md`: 집행 루프·dispatch 섹션에 timeout 취소·slippage·cancel 백스톱 흐름 추가.
## 위험 요소
- **R-1 (취소-체결 race)**: cancel_order 송신 후 즉시 롤백하지 않고 다음 poll 수렴 → 기존 is_terminal_reject/is_partial_cancel 경로 재사용 완화.
- **R-2 (부분 체결 보존)**: timeout cancel → is_partial_cancel → 기존 `_on_partial_cancel` 선례(dcy-013) 재사용.
- **R-3 (단일 writer)**: cancel CLI 는 cancel_order 만 송신; 슬롯 전이는 집행 루프 단독 → 불변식 보존.