feat(slot): BAT-030 — 자산 중심 cutover 완성 (dcy-020 BAT-δ, REQ-056·057·062·064·065)
M1 literal inactive 전면화: SlotState 5-state(idle/closed 별칭 제거), 마이그레이션 010(CHECK 교체+행 rewrite), ops_checks 를 transition_log 기반 재작성. M2 집행 매도 수량 브로커 정본(D3): dispatch 매도 qty=broker hldg_qty, 미러 fallback 없음, 브로커 0 → skip+WARN, 리스트 기반 dispatch(head-block 방지). M3 컬럼 drop + legacy pool 제거: 마이그레이션 011(trading_date·entry_count DROP), repo 계약 instance 스코프 개편(집행 큐 포함), preallocation 폐기(데몬 7스레드), admission 자산 중심 단일 경로(slot_id 정본), capacity 재정의(available=M−active), daily_symbol_entries 코드 경로 제거. M4 cutover 도구(D1·D4): trex-core-cutover(전체 rebuild, preflight/브로커 carry-over/ --allow-unmapped/--promote-unique/dry-run) + PG 리허설 테스트 8건. M5 baseline 마감: data-model/runtime-flows/project-structure/interface-contract 자산 중심 최종 갱신 + run-trex-core-daemon 스킬 정합. 검증: 단위 367 passed + ruff clean; PG-gated 29 passed(010/011 round-trip·드리프트 가드·cutover 리허설); RabbitMQ 21 passed; run-the-app e2e(test DB+실 MQ, M=1): reconcile 슬롯 생성→slot_id 매수→dedup→capacity_full→fill→already_active→ 매도→inactive(자산 바인딩 보존)→영구 슬롯 재활성 전 구간 green. 실 cutover 는 운영자 실행(REL 단계, D4). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1,16 +1,51 @@
|
||||
# BAT-030 Implementation
|
||||
|
||||
- Status: not-started
|
||||
- Status: done
|
||||
- Batch: BAT-030
|
||||
- 일자: 2026-06-10
|
||||
|
||||
## 구현 요약
|
||||
- (run-batch-delivery 진입 시 작성) design.md 체크포인트(D1~D4) 확정 후 채운다.
|
||||
|
||||
## 변경 내역
|
||||
- (대기)
|
||||
design.md 확정 결정(D1=전체 rebuild / D2=전부 완성 / D3=매도 qty 브로커 정본 / D4=실 cutover 운영자) 전부 구현 완료. M1~M5 전 마일스톤 수행.
|
||||
|
||||
- **M1 — literal inactive 전면화 (REQ-057 잔여)**: `SlotState` 5-state(inactive/pending_buy/holding/pending_sell/error) — `IDLE`/`CLOSED` 별칭·`normalize_state` 완전 제거, 전이 12종(6 base + 4 to-error + 2 resolve). 마이그레이션 **010**: 기존 idle/closed 행 → inactive UPDATE + status CHECK 교체(drop-and-add 멱등). `execution.py` 매도 체결 `PENDING_SELL→INACTIVE`, 매수 롤백 `PENDING_BUY→INACTIVE`. `cli.py` buyable 필터 `inactive`+`universe_detached` 제외. `ops_checks.py` ledger-gap 검출을 `slot_transition_log`(reason=`sell_filled`/`sell_partial_cancel`) 기반으로 재작성(`closed` 상태 소멸 대응).
|
||||
- **M2 — 집행 매도 수량 브로커 정본 (REQ-062 M2(b), D3)**: dispatch 매도 qty = `broker.get_balance()` 의 해당 symbol `hldg_qty`(tick 당 1회 lazy fetch). 미러 `slot.qty` fallback 없음. 브로커 0 → 주문 미제출 + WARN + `ExecutionResult.sell_skipped_no_balance` 카운터(divergence 는 reconcile/운영자 경로로 표면화). skip 매도가 큐 head 를 막지 않도록 단건 claim(`find_pending_for_dispatch`) → **리스트 기반**(`list_undispatched_pending(instance_key, limit)`) dispatch 로 전환.
|
||||
- **M3 — 컬럼 drop + legacy pool 제거 (REQ-056 잔여)**: 마이그레이션 **011**: `daily_slot.entry_count`·`trading_date` DROP(IF EXISTS 멱등). 글롭 재적용 정합: 001 의 `(trading_date,status)` 인덱스 → `(status)` 교체, 008(entry_count ADD)은 no-op 화(매 boot 컬럼 부활 방지). `Slot` 모델/`EXPECTED_SLOT_FIELDS`/`_SLOT_COLUMNS`/`_row_to_slot` 정합(19 컬럼). repo 계약 개편: `pre_allocate_slots`/`find_idle_slot`/`find_allocatable_slot`/`increment_symbol_entry`/`get_symbol_entry_count` 제거, `list_slots(instance_key)`/`count_slots_by_status(instance_key)`/`reconcile_universe(instance_key, universe)` 로 전환, 집행 큐(`list_undispatched_pending`/`list_dispatched_pending`)도 **instance 스코프**(공유 DB 의 두 데몬이 서로의 주문을 집행하지 않음 — REQ-056). `preallocation_scheduler.py` 삭제 + 데몬 7 스레드. admission 은 자산 중심 단일 경로(slot_id 정본/portfolio_id dual-accept; `max_active_slots` 미설정 = 무캡 — design §5: 운영 TOML 로 M 부여). `BuyIntent.trading_date`·intent/이벤트/슬롯 뷰 payload 의 `trading_date` 키 제거. capacity 를 `SlotCapacity(inactive/holding/pending/active/available/total/scarcity)` 자산 중심 재정의(available = max(0, M−active)). `daily_symbol_entries` 코드 경로 제거(테이블 잔존, baseline 에 폐기 명시). config 에서 `total_slots`/`daily_preallocation_time` 제거. SnapshotScheduler reconcile 은 slot_repo 주입 시 항상 on.
|
||||
- **M4 — cutover 도구 (REQ-064, D1·D4)**: `trex_core/runtime/cutover.py` + console script `trex-core-cutover`(기본 dry-run, `--yes` 적용). 절차: preflight(pending_* in-flight 0, 위반 시 CutoverAbort) → instance 행 전체 DELETE → universe 자산별 영구 슬롯(inactive) 생성 → 브로커 잔고 (symbol,qty) → universe 매핑 → holding 설정(qty=브로커 정본, avg_price=NULL+WARN — 잔고 API 에 평단 없음) → `slot_transition_log` 감사(`cutover_rebuild`) → 옵션 `--promote-unique`(portfolio_id NOT NULL; 공유 DB 전 인스턴스 cutover 후). 매핑 실패 기본 중단(`--allow-unmapped` 로 WARN+skip). 단일 트랜잭션(중단/실패 시 무변경, dry-run 은 rollback).
|
||||
- **M5 — baseline 마감 (REQ-065)**: data-model/runtime-flows/project-structure/interface-contract 4문서 자산 중심 최종 갱신(daily_symbol_entries·dcy-014 preallocation·풀 admission 폐기 확정, slot_id 계약·5-state·M 캡·브로커 정본 반영). `config/instance.example.toml` 에 `max_active_slots` 안내. `.claude/skills/run-trex-core-daemon`(SKILL.md+smoke.py) 자산 중심으로 갱신.
|
||||
|
||||
## 변경 내역 (Changed Files)
|
||||
|
||||
- trex_core/domain/slot/: state.py(5-state) · models.py(컬럼 drop+SlotCapacity 재정의) · service.py(preallocate/카운터 제거, INACTIVE 리셋=점유 초기화+자산 정체성 보존, capacity 자산 중심) · repository.py · in_memory_repository.py(+add_slot 테스트 헬퍼) · __init__.py
|
||||
- trex_core/adapters/persistence/: postgresql.py · ops_checks.py(재작성) · sql/001(인덱스 교체)·008(no-op 화)·**010_literal_inactive_status.sql**(신규)·**011_drop_pool_model_columns.sql**(신규)
|
||||
- trex_core/application/: intents.py(단일 자산 중심 admission) · execution.py(M2+inactive 전이+instance 스코프) · messaging_handlers.py · portfolio.py · safety_sell.py · killswitch.py · snapshot_scheduler.py · **preallocation_scheduler.py 삭제**
|
||||
- trex_core/runtime/: daemon.py(7 스레드) · config.py · cli.py · **cutover.py(신규)**
|
||||
- pyproject.toml: `trex-core-cutover` console script
|
||||
- docs: data-model.md · runtime-flows.md · project-structure.md · interface-contract.md (자산 중심 마감)
|
||||
- config/instance.example.toml · .claude/skills/run-trex-core-daemon/{SKILL.md,smoke.py}
|
||||
- tests: 도메인/애플리케이션/어댑터/런타임 전면 정합(test_state·test_service 재작성, test_slot_entry_count·test_buy_admission_reallocation·test_preallocation_scheduler 삭제, test_execution 에 M2 3건 신설, **tests/runtime/test_cutover_rehearsal.py 신규 8건(PG-gated)**, test_postgresql_repository 자산 중심 재작성+dispatch 인스턴스 격리, test_ops_checks 재작성)
|
||||
|
||||
## 테스트
|
||||
- (대기)
|
||||
|
||||
## 검증 실행
|
||||
- (대기)
|
||||
- 단위 전체: **367 passed** (PG/RabbitMQ/KIS unset 시 40 skipped), ruff clean
|
||||
- PG-gated(trex_core_test): **29 passed** — 마이그레이션 010/011 round-trip + 멱등 재적용, daily_slot 컬럼 == EXPECTED_SLOT_FIELDS 드리프트 가드(컬럼 drop 후 정합), reconcile ON CONFLICT, dispatch 인스턴스 격리, ops_checks 재작성, **cutover 리허설 8건**(전체 rebuild·브로커 carry-over(미러 7 vs 브로커 12 → 12)·in-flight 중단·unmapped 중단/allow-skip·dry-run rollback·인스턴스 격리·promote-unique·감사 로그)
|
||||
- RabbitMQ-gated: 21 passed (실 브로커)
|
||||
- 통합(전부 enabled): **405 passed, 10 skipped**(KIS sandbox — 기존 환경 이슈, 본 배치 무관)
|
||||
|
||||
## 검증 실행 (run-the-app, 자산 중심 e2e)
|
||||
|
||||
test DB(trex_core_test) + 실 RabbitMQ + paper broker + stub universe(2 자산) + `instance_key=e2etest`, `max_active_slots=1` 로 데몬 기동:
|
||||
1. boot 직후 universe reconcile `{'created': 2}` — 영구 슬롯 자동 생성
|
||||
2. snapshot RPC: slots=2(inactive), `available_slots=1`(M−active)
|
||||
3. **buy by slot_id** admitted(REQ-061) → 4. 동일 intent_id 중복 → identical 응답(전이 1회)
|
||||
5. M=1 에서 2번째 자산 buy → `capacity_full`(REQ-059) → 6. paper fill → holding, available=0
|
||||
7. 동일 슬롯 재매수 → `already_active` → 8. sell by slot_id → 체결 → **inactive**(qty=None, portfolio_id 보존, available=1)
|
||||
9. **동일 영구 슬롯 재활성**(re-entry) admitted
|
||||
- DB 최종: `('e2etest','pf-000660','inactive',None)`, `('e2etest','pf-005930','holding',3.0)`; transition_log reason {buy_intent×2, buy_filled×2, discretionary_sell×1, sell_filled×1}; ledger_outbox completed×3; 데몬 로그 ERROR/Traceback 0, Normal shutdown.
|
||||
|
||||
## Remaining Risks / Blockers
|
||||
|
||||
- **실 cutover 미실행(의도)** — D4: 운영자가 REL 단계에서 리허설 후 `trex-core-cutover --config <instance> --yes` 실행. 사전 백업 + 거래 세션 외 + in-flight 0 필수. 롤백 = 백업 복원 + 직전 리비전.
|
||||
- cutover carry-over 의 `avg_price=NULL` — 잔고 API 미제공. SL/TP 는 절대가 기준이라 안전 매도 무영향이나, 운영자/전략이 필요 시 후속 갱신.
|
||||
- 전략 모듈(trex-orb) 계약 전환: buy `slot_id` 정본(snapshot slots[] round-trip), `trading_date` 키 소멸, 이벤트 payload 변경 — portfolio_id dual-accept 로 무중단이나 전략 측 전환 권고(REL 노트 대상).
|
||||
- `max_active_slots` 미설정 = 무캡 — 운영 TOML 에 반드시 설정(배포 체크리스트, design §5).
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# BAT-030: 자산 중심 cutover — 라이브 전환·집행 정본·모델 완성 (dcy-020 BAT-δ)
|
||||
|
||||
- Status: planning
|
||||
- Status: in-delivery
|
||||
- Profile: standard
|
||||
- Source Discovery: dcy-020
|
||||
- Included REQ: REQ-064 (풀→영구 cutover), REQ-062 (잔여: 청산 수량 브로커 정본), REQ-056/057/065 (잔여: entry_count/trading_date drop·literal inactive·baseline 마감)
|
||||
|
||||
Reference in New Issue
Block a user