Files
trex-core/docs/batches/bat-030_20260610_asset-centric-cutover/implementation.md
T
genichinandClaude Opus 4.8 7d727824dc 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>
2026-06-10 15:16:15 +09:00

8.1 KiB
Raw Blame History

BAT-030 Implementation

  • Status: done
  • Batch: BAT-030
  • 일자: 2026-06-10

구현 요약

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회)
  4. M=1 에서 2번째 자산 buy → capacity_full(REQ-059) → 6. paper fill → holding, available=0
  5. 동일 슬롯 재매수 → already_active → 8. sell by slot_id → 체결 → inactive(qty=None, portfolio_id 보존, available=1)
  6. 동일 영구 슬롯 재활성(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).