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 보유 수량" 충돌 해소, 별도 필드 없음). 구현 진행.
|
||||
@@ -0,0 +1,39 @@
|
||||
# BAT-031 Implementation
|
||||
|
||||
- Status: done
|
||||
- Batch: BAT-031
|
||||
- 일자: 2026-06-10
|
||||
|
||||
## 구현 요약
|
||||
design.md D1~D4 그대로 구현. 수량 정본=브로커 잔고 재동기화로 "주문 수량 vs 보유 수량" 충돌을 별도 필드 없이 해소.
|
||||
|
||||
- **M1 (REQ-057)**: `state.py` `_BASE_TRANSITIONS` 에 `(HOLDING, PENDING_BUY)` 추가 → 13 전이(증액).
|
||||
- **M2 (REQ-066/062)**: `service.py` `_apply_transition_fields` — PENDING_SELL 진입 시 `qty = caller as-is`(None=전량 sentinel, 값=부분 주문 qty). 증액 pending(HOLDING→PENDING_BUY)은 generic 분기가 avg_price 보존(caller None)·qty=주문으로 처리(별도 분기 불요).
|
||||
- **M3 (REQ-061)**: `intents.py` — `_admit_buy` 가 HOLDING 슬롯 매수를 **증액**으로 검출(M 캡 미적용, `position_increase` reason), PENDING_* 만 `already_active`. `SellIntent.qty`(부분) 추가, `_admit_sell` 가 qty 를 transit 에 전달. `messaging_handlers` sell 파서에 `qty` 추출.
|
||||
- **M4 (REQ-062/066)**: `execution.py` — `_balance_map()` 헬퍼. dispatch 매도 `qty = 브로커잔고 if slot.qty is None else min(int(slot.qty), 브로커잔고)`(0이면 skip+WARN). `_on_filled`: 매수 체결 → 보유=브로커 잔고·avg 가중평균; 매도 체결 → 브로커 잔고>0 `pending_sell→holding`(cost basis) / 0 `→inactive`. `_on_partial_cancel` 도 브로커 정본 resync. `_rollback`: 매수 거부 시 브로커>0 `→holding`(증액 실패, 기존 보유 유지) / 0 `→inactive`.
|
||||
- **M5 (REQ-067)**: data-model/runtime-flows/interface-contract/project-structure 4문서 사이징 정합.
|
||||
|
||||
## 변경 내역 (Changed Files)
|
||||
- trex_core/domain/slot/state.py — holding→pending_buy 전이
|
||||
- trex_core/domain/slot/service.py — PENDING_SELL qty sentinel
|
||||
- trex_core/application/intents.py — 증액 admission, SellIntent.qty, partial sell
|
||||
- trex_core/application/execution.py — _balance_map, 부분/전량 매도 dispatch, 매수 체결 가중평균, 브로커 정본 resync, 증액 롤백
|
||||
- trex_core/application/messaging_handlers.py — sell qty 파싱
|
||||
- docs: data-model/runtime-flows/interface-contract/project-structure (REQ-067)
|
||||
- tests: test_state(13 전이), test_service(TestPositionSizing 3), test_execution(전량/부분 매도·증액·capacity 5신규), test_execution_cancel(브로커 정본 잔량)
|
||||
|
||||
## 테스트
|
||||
- 단위 전체: **378 passed** (45 skipped), ruff clean
|
||||
- 통합(PG+RabbitMQ): **422 passed, 1 skipped**(KIS sandbox, 환경 무관)
|
||||
|
||||
## 검증 실행 (run-the-app, 사이징 e2e)
|
||||
라이브 trex_core(reset 후) + 실 RabbitMQ + paper + instance=test, M=5:
|
||||
1. 초기 매수 qty=10 @1000 → holding 10 @1000
|
||||
2. **증액 +5 @1200**(holding 슬롯 매수, admitted) → holding **15 @1066.6667**(가중평균)
|
||||
3. **부분 매도 qty=6** → holding **9 @1066.6667**(cost basis 유지)
|
||||
4. **전량 매도**(qty 미지정) → **inactive**(qty None)
|
||||
- 데몬 로그 ERROR/Traceback 0. messaging sell qty 갭은 e2e 에서 발견·수정(부분 매도가 전량으로 가던 버그) 후 재검증 green.
|
||||
|
||||
## Remaining Risks / Blockers
|
||||
- 전략 모듈(trex-orb) 사이징 intent 발행 전환 필요 — 기존 buy/sell 와 병존(additive)이라 무중단.
|
||||
- 미체결 pending 고착 처분은 본 배치 범위 밖(issue #21 별도 Discovery).
|
||||
@@ -0,0 +1,23 @@
|
||||
# BAT-031: 보유 자산 포지션 사이징 (dcy-021)
|
||||
|
||||
- Status: release-candidate
|
||||
- Profile: standard
|
||||
- Source Discovery: dcy-021
|
||||
- Included REQ: REQ-066 (사이징 도메인·end-to-end), REQ-067 (baseline 정합), REQ-057 (CHG-03 상태머신 holding→pending_buy), REQ-061 (CHG-04 사이징 intent), REQ-062 (CHG-03 부분 매도 집행)
|
||||
|
||||
## Summary
|
||||
- dcy-021: 자산 영구 슬롯(dcy-020)의 수량 가변 전제 위에 **보유 포지션 수량 조정**(증액·부분 감액)을 구현한다. 명령형 계약(전략이 cash+quantity 로 qty+price 계산·전송), core 는 실행 substrate(target 미저장·미추적), 수량 정본 = 브로커 잔고.
|
||||
|
||||
## Scope
|
||||
- In: 증액(holding→pending_buy→holding, avg_price 가중평균), 부분 감액(부분 매도 min(intent,잔고), 잔량 holding/전량 inactive, cost basis), 부분 체결 실제만 반영·미추적, 사이징 intent + 멱등, baseline 4문서.
|
||||
- Out: 능동 capacity 배분, 자동 리밸런싱 스케줄러, 미체결 pending 고착 처분(issue #21).
|
||||
|
||||
## Documents
|
||||
- Planning: [planning.md](./planning.md)
|
||||
- Design: [design.md](./design.md)
|
||||
- Implementation: [implementation.md](./implementation.md)
|
||||
- Verification: [verification.md](./verification.md)
|
||||
|
||||
## Notes
|
||||
- 집행 핫패스(라이브 주문) 변경 — additive·paper 검증. design.md 의 D1~D4 확정 후 구현.
|
||||
- 본 배치 완료 후 REQ-057/061/062/066/067 confirm-req-implemented.
|
||||
@@ -0,0 +1,42 @@
|
||||
# BAT-031 Planning
|
||||
|
||||
- Status: confirmed
|
||||
- Batch: BAT-031
|
||||
- Profile: standard
|
||||
|
||||
## Included REQ
|
||||
- REQ-066 (Interface, Approved): 사이징 도메인 수량/avg_price 재계산 + end-to-end.
|
||||
- REQ-067 (Documentation, Approved): baseline 4문서 정합.
|
||||
- REQ-057 (CHG-20260610-03, Approved): 상태머신 `holding→pending_buy` 전이.
|
||||
- REQ-061 (CHG-20260610-04, Approved): 사이징 intent(증액/부분 매도, 명령형 qty+price).
|
||||
- REQ-062 (CHG-20260610-03, Approved): 부분 매도 집행 `min(intent,잔고)`·부분 체결 실제만.
|
||||
|
||||
## Delivery Plan (마일스톤)
|
||||
1. **M1 (REQ-057)**: `state.py` 전이 집합에 `HOLDING→PENDING_BUY` 추가(증액). test_state 갱신(13 transitions).
|
||||
2. **M2 (REQ-066)**: `service.py` 전이 필드 규칙 — 증액 pending(avg_price 보존), 매수 체결 avg_price 가중평균 + 수량 = 브로커 정본 resync.
|
||||
3. **M3 (REQ-061)**: `intents.py` 사이징 admission — 증액 매수(holding 슬롯 buy, `already_active` 예외)·부분 매도(SellIntent qty). messaging_handlers payload(qty/price).
|
||||
4. **M4 (REQ-062)**: `execution.py` — 부분 매도 dispatch qty = `min(intent, 브로커 잔고)`, 매수/매도 체결 시 broker 정본 resync·avg 가중평균, 증액 롤백은 holding 복귀.
|
||||
5. **M5 (REQ-067)**: baseline 4문서 정합 + run-the-app e2e.
|
||||
|
||||
## Dependencies
|
||||
- Internal: dcy-020(자산 영구 슬롯·REQ-062 브로커 정본·집행 루프) 전 단계 green.
|
||||
- External: 전략 모듈(trex-orb)이 사이징 intent 발행 — 기존 buy/sell 와 병존(additive)이라 무중단.
|
||||
|
||||
## Design Gate
|
||||
- Design Required Before Implementation: yes
|
||||
- Reason: M3/M4 는 **라이브 주문 집행 동작 변경**(증액 매수·부분 매도 dispatch). 증액 시 "주문 수량 vs 보유 수량" 충돌·avg_price 가중평균·증액 롤백·부분 체결 처리는 잘못 구현 시 과매수/과매도/평단 오류 → design.md 에서 확정.
|
||||
|
||||
## Milestones
|
||||
1. M1 상태머신 + test
|
||||
2. M2 service avg/qty
|
||||
3. M3 intent
|
||||
4. M4 execution
|
||||
5. M5 baseline + e2e
|
||||
|
||||
## Risks
|
||||
- R-exec (High): 증액/부분 매도 집행 회귀(과매수·과매도·avg drift). 대응: 브로커 정본 resync(REQ-062), 멱등, 단위+paper e2e.
|
||||
- R-statemachine (Medium): 전이 추가 회귀. 대응: additive + test_state 회귀 가드.
|
||||
- R-strategy (Medium): trex-orb 계약 전환. 대응: 기존 intent 병존.
|
||||
|
||||
## Notes
|
||||
- green 기준: 단위 전체 회귀(증액/부분 매도/부분 체결/avg 가중평균) + paper run-the-app e2e + ruff.
|
||||
@@ -0,0 +1,51 @@
|
||||
# BAT-031 Verification
|
||||
|
||||
- Status: approved (confirm-batch-verification, 2026-06-10)
|
||||
- Batch: BAT-031
|
||||
- 검증 환경: Python 3.12 / PostgreSQL trex_core_test / 실 RabbitMQ / paper broker
|
||||
|
||||
## Acceptance Mapping
|
||||
|
||||
### REQ-066 (포지션 사이징 end-to-end)
|
||||
| AC | 판정 | Evidence |
|
||||
|---|---|---|
|
||||
| AC-1 증액 가중평균 | pass | `test_increase_accumulates_qty_and_weights_avg_price`(10@1000 +5@1200 → 15@1066.67) + e2e 2단계 |
|
||||
| AC-2 부분 매도 잔량 holding·cost basis | pass | `test_partial_sell_qty_is_min_intent_broker`(잔량 6, avg 1000) + e2e 3단계(9 @1066.67) |
|
||||
| AC-3 전량 매도 → inactive | pass | `test_full_sell_qty_is_broker_balance`·`test_partial_sell_caps_at_broker_balance` + e2e 4단계 |
|
||||
| AC-4 부분 체결 실제만 반영·미추적 | pass | `test_execution_cancel`(부분 체결→브로커 정본 잔량) + _on_filled/_on_partial_cancel 브로커 resync |
|
||||
| AC-5 무회귀 | pass | 단위 378 / 통합 422 passed |
|
||||
|
||||
### REQ-057 (CHG-03 상태머신 holding→pending_buy)
|
||||
| AC | 판정 | Evidence |
|
||||
|---|---|---|
|
||||
| AC-1 전이 집합(13) | pass | `test_state.py`(holding→pending_buy allowed, size 13) |
|
||||
| AC-5 증액 전이·복귀 | pass | `test_holding_to_pending_buy_increase_preserves_avg` + e2e |
|
||||
|
||||
### REQ-061 (CHG-04 사이징 intent)
|
||||
| AC | 판정 | Evidence |
|
||||
|---|---|---|
|
||||
| AC-6 증액 매수(holding) | pass | `test_increase_*` + e2e 2단계(admitted) |
|
||||
| AC-7 부분 매도 qty·명령형·core 미저장 | pass | `test_partial_sell_*` + messaging sell qty 파싱 + e2e 3단계 |
|
||||
|
||||
### REQ-062 (CHG-03 부분 매도 집행)
|
||||
| AC | 판정 | Evidence |
|
||||
|---|---|---|
|
||||
| AC-1 매도 = min(intent, 브로커)·이상이면 전량 | pass | `test_partial_sell_qty_is_min_intent_broker`·`test_partial_sell_caps_at_broker_balance` |
|
||||
| AC-5 부분 체결 실제만·미추적 | pass | `test_execution_cancel` + _balance_map resync |
|
||||
|
||||
### REQ-067 (baseline 정합)
|
||||
| AC | 판정 | Evidence |
|
||||
|---|---|---|
|
||||
| AC-1~3 data-model/runtime-flows/interface-contract/project-structure 정합 | pass | commit (사이징 흐름·13 전이·intent qty·가중평균 반영) |
|
||||
|
||||
## Evidence
|
||||
1. 단위: `pytest tests` → **378 passed**, ruff clean.
|
||||
2. 통합(PG `trex_core_test` + 실 RabbitMQ): **422 passed, 1 skipped**(KIS sandbox 환경 이슈, 무관).
|
||||
3. **run-the-app e2e**(라이브 trex_core + 실 MQ + paper, M=5): 초기 매수 10@1000 → 증액 +5@1200 → holding 15@1066.67(가중평균) → 부분 매도 6 → holding 9@1066.67(cost basis) → 전량 매도 → inactive. 데몬 ERROR 0.
|
||||
4. baseline 동기화: 4문서 + (run-the-app e2e 가 증액·부분/전량 매도 흐름 실증).
|
||||
|
||||
## Blocking Issues
|
||||
- 없음. 전략 모듈(trex-orb) 사이징 전환은 병존(additive)이라 무중단; 미체결 pending 처분은 issue #21(별도) 범위.
|
||||
|
||||
## Result
|
||||
- 포함 REQ AC 전부 evidence 연결. **Ready for Release: true** (코드·단위·통합·e2e·baseline). 실 운영 전환은 전략 모듈 사이징 발행 시 활성(계약 병존).
|
||||
@@ -38,3 +38,4 @@
|
||||
| BAT-028 | standard | release-candidate | REQ-058, REQ-059, REQ-060 | dcy-020 (BAT-β) | [bat-028](./bat-028_20260610_universe-reconcile-and-active-admission/) |
|
||||
| BAT-029 | standard | release-candidate | REQ-061, REQ-062, REQ-063 | dcy-020 (BAT-γ) | [bat-029](./bat-029_20260610_asset-keyed-intent-and-divergence/) |
|
||||
| BAT-030 | standard | release-candidate | REQ-064, REQ-062, REQ-056·057·065(잔여) | dcy-020 (BAT-δ) | [bat-030](./bat-030_20260610_asset-centric-cutover/) |
|
||||
| BAT-031 | standard | release-candidate | REQ-066, REQ-067, REQ-057·061·062(CHG) | dcy-021 | [bat-031](./bat-031_20260610_position-sizing/) |
|
||||
|
||||
Reference in New Issue
Block a user