실 DB 점검 중 마이그레이션 009 부분 유니크 인덱스가 legacy 풀 데이터(같은 자산이 closed+재진입 holding 으로 portfolio_id 중복)에 적용 불가 → cutover· 데몬 부팅 모두 차단되는 결함 확인. 데이터 보존 cutover 를 폐기하고 DB wipe + 자산 중심 재초기화로 전환(사용자 결정). 빈 테이블에는 009 인덱스가 정상 생성되고 자산 중심에서 (instance_key, portfolio_id)는 설계상 유일. - trex_core/runtime/reset.py 신규(trex-core-reset): slot-core 7테이블 wipe + apply_migrations 재적용 → 자산 중심 스키마 착지 검증. dry-run 기본/--yes 적용, 파괴적·DB 전역. 보유 carry-over 없음(재초기화 후 reconcile_core_holdings 재편입). - runtime/cutover.py + trex-core-cutover console script + test_cutover_rehearsal.py 삭제. - pyproject: trex-core-reset 등록. - tests/runtime/test_reset.py 신규 5건(PG-gated): wipe+착지·dry-run 무변경·멱등· wipe 후 admission·테이블 커버리지. - change-req REQ-064 CHG-20260610-03(cutover→재초기화 재정의) + srs index 갱신. - baseline 4문서(data-model/runtime-flows/project-structure/interface-contract) + BAT-030 design/planning/index/implementation/verification 도구 서술 reset 으로 교체. 검증: ruff clean, 단위 367 passed, PG+RabbitMQ 통합 411 passed/1 skip(KIS sandbox). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
9.0 KiB
9.0 KiB
BAT-030 Implementation
- Status: done
- Batch: BAT-030
- 일자: 2026-06-10
구현 요약
design.md 확정 결정(D2=전부 완성 / D3=매도 qty 브로커 정본) 구현 완료. M1~M5 수행. 단, D1(데이터 보존 cutover)은 폐기 — 실 DB 점검 중 마이그레이션 009 부분 유니크 인덱스가 legacy 풀 데이터(같은 자산이 closed+재진입 holding 으로 portfolio_id 중복)와 충돌해 cutover/데몬 부팅이 모두 막히는 결함 발견. 사용자 결정으로 데이터 보존 마이그레이션을 버리고 DB wipe + 자산 중심 재초기화(trex-core-reset)로 대체(REQ-064 CHG-20260610-03). 빈 테이블에는 009 인덱스가 정상 생성되고 자산 중심에서 (instance_key,portfolio_id)는 설계상 유일.
- M1 — literal inactive 전면화 (REQ-057 잔여):
SlotState5-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.pybuyable 필터inactive+universe_detached제외.ops_checks.pyledger-gap 검출을slot_transition_log(reason=sell_filled/sell_partial_cancel) 기반으로 재작성(closed상태 소멸 대응). - M2 — 집행 매도 수량 브로커 정본 (REQ-062 M2(b), D3): dispatch 매도 qty =
broker.get_balance()의 해당 symbolhldg_qty(tick 당 1회 lazy fetch). 미러slot.qtyfallback 없음. 브로커 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_dateDROP(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 — DB 재초기화 도구 (REQ-064, CHG-20260610-03):
trex_core/runtime/reset.py+ console scripttrex-core-reset(기본 dry-run,--yes적용). 절차: 대상 DB 확인 → slot-core 7개 테이블 중 존재하는 것 행수 집계(dry-run 은 여기서 보고만) →--yes면TRUNCATE ... RESTART IDENTITY CASCADE→apply_migrations(빈 테이블이라 009 유니크 인덱스 정상 생성·010 inactive CHECK·011 컬럼 drop) →daily_slot컬럼 == EXPECTED_SLOT_FIELDS 검증. 파괴적·DB 전역(모든 instance). 데이터 보존(보유 carry-over)은 하지 않음 — 재초기화 후 universe reconcile 이 자산 슬롯 생성, 보유는reconcile_core_holdings(REQ-062)로 운영자 재편입. (구cutover.py·trex-core-cutover·test_cutover_rehearsal.py삭제.) - 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 · reset.py(신규) ·
cutover.py(삭제) - pyproject.toml:
trex-core-resetconsole script (구trex-core-cutover제거) - 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_reset.py 신규 5건(PG-gated), test_postgresql_repository 자산 중심 재작성+dispatch 인스턴스 격리, test_ops_checks 재작성)
테스트
- 단위 전체: 367 passed (PG/RabbitMQ/KIS unset 시 40 skipped), ruff clean
- PG-gated(trex_core_test): 26 passed — 마이그레이션 010/011 round-trip + 멱등 재적용, daily_slot 컬럼 == EXPECTED_SLOT_FIELDS 드리프트 가드(컬럼 drop 후 정합), reconcile ON CONFLICT, dispatch 인스턴스 격리, ops_checks 재작성, reset 5건(wipe+자산 중심 스키마 착지·dry-run 무변경·멱등·wipe 후 즉시 admission 가능·slot-core 테이블 커버리지)
- RabbitMQ-gated: 21 passed (실 브로커)
- 통합(PG+RabbitMQ enabled): 411 passed, 1 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 로 데몬 기동:
- boot 직후 universe reconcile
{'created': 2}— 영구 슬롯 자동 생성 - snapshot RPC: slots=2(inactive),
available_slots=1(M−active) - buy by slot_id admitted(REQ-061) → 4. 동일 intent_id 중복 → identical 응답(전이 1회)
- M=1 에서 2번째 자산 buy →
capacity_full(REQ-059) → 6. paper fill → holding, available=0 - 동일 슬롯 재매수 →
already_active→ 8. sell by slot_id → 체결 → inactive(qty=None, portfolio_id 보존, available=1) - 동일 영구 슬롯 재활성(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
- 실 DB 재초기화 미실행(의도) — 운영자가
trex-core-reset --config <instance> --yes실행(파괴적·DB 전역). 사전 백업 필수. 재초기화라 롤백 = 백업 복원. - 데이터 보존 cutover 폐기로 기존 보유 포지션은 재초기화 시 소실 — 재초기화 후 브로커 보유는 universe reconcile +
reconcile_core_holdings(REQ-062)에서 unmanaged 로 표면화되어 운영자가 재편입. 실거래 보유가 있는 DB 라면 재편입 절차를 release 노트에 포함. - 마이그레이션 009 부분 유니크 인덱스는 legacy 풀 중복 데이터에 적용 불가(이 결함이 cutover→reset 전환의 직접 원인). 자산 중심 DB(신규/재초기화 후)에서는 정상.
- 전략 모듈(trex-orb) 계약 전환: buy
slot_id정본(snapshot slots[] round-trip),trading_date키 소멸, 이벤트 payload 변경 — portfolio_id dual-accept 로 무중단이나 전략 측 전환 권고(REL 노트 대상). max_active_slots미설정 = 무캡 — 운영 TOML 에 반드시 설정(배포 체크리스트, design §5).