docs(srs/batch): dcy-021 REQ + BAT-031 + Implemented 전환
- draft-req: REQ-066(사이징 도메인·end-to-end), REQ-067(baseline 정합) 신규 - change-req: REQ-057(CHG-03 holding→pending_buy)·REQ-061(CHG-04 사이징 intent)· REQ-062(CHG-03 부분 매도 min(주문,잔고)) — Implemented→Approved 후 재구현 - confirm-req: REQ-066/067 Approved - BAT-031 batch(index/planning/design D1~D4/implementation/verification) → release-candidate - confirm-batch-verification 승인 + confirm-req-implemented: REQ-057/061/062/066/067 → Implemented (BAT-031 verification 근거) - srs/batch index + dcy-021 discovery 생성 REQ 역참조 갱신 (issue #21 dcy-022 는 별도 작업으로 미포함) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,47 @@
|
||||
# BAT-031 Design
|
||||
|
||||
- Status: confirmed (D1~D4, 2026-06-10)
|
||||
- Batch: BAT-031
|
||||
|
||||
## 설계 원칙
|
||||
- 수량 정본 = **브로커 잔고**(REQ-062). 슬롯 `qty` 는 (a) pending 동안 = **주문 수량**, (b) 체결/롤백 후 = **브로커 잔고(보유)** 로 재동기화한다. core 는 "의도 수량"을 별도 저장하지 않는다(dcy-021 OQ-1).
|
||||
- avg_price 는 증액 시 가중평균, 매도(부분/전량) 시 cost basis 유지(dcy-021 OQ-3).
|
||||
- 모든 전이는 dcy-020 자산 중심 모델에 additive(무회귀, NFR-2).
|
||||
|
||||
## D1 — 증액: 주문 수량 vs 보유 수량 (브로커 정본 재동기화)
|
||||
- `HOLDING→PENDING_BUY`(증액): `slot.qty := intent.qty`(추가 주문 수량), `avg_price` **보존**(가중평균용), `limit_price := price`. (generic 전이 규칙이 caller None 일 때 avg 보존 → 별도 service 분기 불요)
|
||||
- dispatch 매수: `qty = slot.qty`(주문 수량) 그대로.
|
||||
- `PENDING_BUY→HOLDING`(체결): 보유 = **브로커 잔고 total**, avg_price = 가중평균.
|
||||
- `old_qty = max(0, broker_total − filled)`, `old_avg = slot.avg_price or 0`, `new_avg = (old_qty×old_avg + filled×fill_price)/broker_total`(broker_total 0 이면 fill_price).
|
||||
- 초기 매수도 동일 공식(avg None→0, old_qty=0 → new_avg=fill_price) — 통일.
|
||||
|
||||
## D2 — 초기 매수 vs 증액 판별
|
||||
- `INACTIVE→PENDING_BUY`(초기): slot 이 inactive 라 avg_price=None.
|
||||
- `HOLDING→PENDING_BUY`(증액): avg_price 보존(>0).
|
||||
- 체결/롤백 핸들러는 **브로커 잔고**로 판별(잔고>0 → 증액/보유 잔존, 0 → 초기/미보유). avg_price 보존은 가중평균 계산에만 사용.
|
||||
|
||||
## D3 — 증액/매도 롤백
|
||||
- 매수 거부 `_rollback`: 브로커 잔고>0 → `PENDING_BUY→HOLDING`(qty=브로커 잔고, 증액 실패해도 기존 보유 유지) / 잔고 0 → `PENDING_BUY→INACTIVE`(초기 매수 실패).
|
||||
- 매도 거부: `PENDING_SELL→HOLDING`(qty=브로커 잔고) (기존).
|
||||
|
||||
## D4 — 부분 매도 + 전량 매도 구분 + 부분 체결
|
||||
- pending_sell 의 `slot.qty` 의미: **부분 매도** intent(SellIntent.qty 지정) → `slot.qty = 주문 수량`; **전량 매도**(svc.sell/safety-sell/kill-switch, qty 미지정) → `slot.qty = None`("전량" sentinel). → `_apply_transition_fields` 의 PENDING_SELL 진입 규칙: `new_qty = caller qty`(None 보존, mirror 안 씀).
|
||||
- dispatch 매도: `qty = 브로커잔고 if slot.qty is None else min(int(slot.qty), 브로커잔고)`. 0 이면 skip+WARN(divergence, REQ-062). → 전량은 항상 브로커 정본(stale mirror 무관), 부분은 min(주문, 잔고)·잔고 이상이면 전량.
|
||||
- `PENDING_SELL→체결`: 체결 후 **브로커 잔고**로 분기 — 잔고>0 → `HOLDING`(qty=잔고, avg=cost basis 유지) / 0 → `INACTIVE`. (기존 항상 INACTIVE 에서 변경 — 부분 매도 잔량 holding 지원)
|
||||
- 부분 체결: 실제 체결분만 ledger(기존 `_record_ledger` = `fs.filled_qty`), 미체결 잔량 미추적. dcy-013 `_on_partial_cancel` 도 브로커 정본 resync 로 정합.
|
||||
|
||||
## D-impl — 브로커 잔고 조회
|
||||
- `_poll` tick 시작 시 `broker.get_balance()` 1회 fetch → symbol→hldg_qty 맵을 fill/rollback 핸들러에 전달(체결당 중복 호출 회피). dispatch 의 매도 잔고 맵과 동일 패턴(BAT-030 M2).
|
||||
|
||||
## intent (REQ-061)
|
||||
- 매수 admission: 대상 슬롯이 `INACTIVE` → 초기 매수(M 캡 적용); `HOLDING` → **증액**(M 캡 미적용 — active 수 불변); `pending_*` → `already_active`. (증액은 별도 intent 타입 없이 holding 슬롯 buy 로 검출)
|
||||
- 매도 admission: `SellIntent.qty`(선택) 추가 — 지정 시 부분 매도(slot.qty=order), 미지정 시 전량(slot.qty=None).
|
||||
|
||||
## 상태머신 (REQ-057)
|
||||
- `_BASE_TRANSITIONS` 에 `(HOLDING, PENDING_BUY)` 추가 → 13 transitions.
|
||||
|
||||
## 롤백/회귀
|
||||
- 전부 additive. 기존 초기 매수/전량 매도/safety-sell/kill-switch/reconcile 경로 무회귀(전량 매도 slot.qty=None·dispatch 브로커 정본은 기존 동작과 동일 결과).
|
||||
|
||||
## 체크포인트 결정
|
||||
- D1~D4 확정(브로커 정본 재동기화로 "주문 수량 vs 보유 수량" 충돌 해소, 별도 필드 없음). 구현 진행.
|
||||
Reference in New Issue
Block a user