REQ-058(universe reconcile)+REQ-059(자산 admission FCFS/M)+REQ-060(max_active_slots). design 확정(사용자 결정): - D1: config 게이트 이중경로 (max_active_slots 설정 시 자산중심, 미설정 시 legacy pool; 운영 데몬은 cutover 전까지 pool 무중단) - D2: "활성"(M 캡) = holding+pending_buy+pending_sell - D3: 대량소멸 가드 = N→0 + 급감(50%↓) 경고 batches/index BAT-028(standard, planning) 등록. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3.8 KiB
3.8 KiB
BAT-028 Design
- Status: confirmed (D1/D2/D3 확정 — 2026-06-10, 사용자 결정)
- Batch: BAT-028
설계 목표
- 슬롯 lifecycle 을 universe reconcile 로, admission 을 자산 중심 FCFS+M 캡으로 전환하되, 라이브 데몬 무중단을 위해 config 게이트로 도입하고 legacy pool 경로를 보존한다(안 B).
핵심 결정 — config 게이트 이중 경로 (권장)
DaemonInstanceConfig.max_active_slots: Optional[int].- 설정됨 → 자산 중심 모드: 슬롯 생성=universe reconcile, admission=자산 슬롯 활성화+M 캡.
- 미설정 → legacy pool 모드: 기존 preallocation + find_allocatable_slot (현행 그대로).
- 운영 데몬은 cutover(BAT-δ) 전까지
max_active_slots미설정 → legacy 유지, 무중단. 자산 중심은 테스트/단계 전환에서 활성. - 대안(전면 switch)은 라이브 admission 을 한 번에 바꿔 위험 → 안 B에서 비채택.
REQ-058 reconcile
- repo 계약 추가:
reconcile_universe(instance_key, trading_date, universe_portfolio_ids: set[str]) -> ReconcileResult.- universe 에 있고 슬롯 없음(instance_key, portfolio_id) → 자산 슬롯 생성(inactive, portfolio_id NOT NULL, 멱등).
- 슬롯 있고 universe 에서 빠짐 + status==inactive(idle/closed 별칭) + retired_at IS NULL →
retired_at=now(soft-retire). - 빠짐 + holding/pending → tombstone(retired_at 미설정, 유지; 신규 활성 후보 제외는 admission/조회에서 retired_at·universe 기준).
- 호출:
SnapshotScheduler.run_once()의store.swap(items)직후, 자산 중심 모드일 때만. degrade(get_portfolios 실패) → reconcile 건너뜀. 대량소멸 가드:len(universe) == 0또는 급감(예: 직전 대비 50%↓) 시 retire 보류 + 경고(생성은 진행). - 단일 writer: reconcile 쓰기는 인스턴스 스코프 advisory lock 안에서.
REQ-059 admission (자산 중심 분기)
IntentService._admit_buy: 모드 분기.- 자산 중심:
repo.find_slot_by_portfolio(instance_key, trading_date, portfolio_id)로 그 자산의 슬롯을 잡는다.- 슬롯 없음(universe 밖) → reject
unknown_portfolio. - 슬롯이 이미 활성(pending_buy/holding/pending_sell) → reject
already_active(멱등). - 활성 수 계산
count_active(instance_key, trading_date)≥ M → rejectcapacity_full(무치환). - else inactive→pending_buy 활성화(인스턴스 lock + 재시도). over-cap(M 축소)도 같은 경로로 신규만 차단.
- 슬롯 없음(universe 밖) → reject
- legacy: 기존 find_allocatable_slot 경로.
- 자산 중심:
- "활성(active)" 정의(M 캡 대상):
holding + pending_buy + pending_sell(= inactive/error 아닌 포지션 점유 상태). pending_sell 은 청산 진행 중이나 아직 포지션 보유 → 캡에 포함. (체크포인트 D2)
REQ-060 config
max_active_slotsper-instance TOML 로드(REQ-048 계열). 자산 중심 모드에서 admission 이 M 으로 사용. legacy 모드는 무시.
테스트 설계
- reconcile: 생성 멱등 / 비활성 soft-retire / 보유·pending tombstone / degrade·대량소멸 보류 (in-memory + PG).
- admission(자산 중심): 활성화 / capacity_full / already_active / over-cap / 동시성 경합 (인스턴스 lock).
- config: max_active_slots 로드 / 모드 게이트.
- legacy 회귀: max_active_slots 미설정 시 기존 pool admission/preallocation 무파손(기존 테스트 green).
체크포인트 결정 (2026-06-10, 사용자 확정)
- D1 = config 게이트 이중 경로.
max_active_slots설정 시 자산 중심, 미설정 시 legacy pool. 운영 무중단(cutover 전 pool 유지). - D2 = "활성"(M 캡) =
holding + pending_buy + pending_sell(inactive/error 아닌 포지션 점유 상태 전부). - D3 = N→0 + 급감 경고. universe 가 0이면 retire 보류; 직전 대비 급감(50%↓)도 retire 보류 + 경고(생성은 진행).