Files
trex-core/docs/batches/bat-023_20260608_ledger-cash-position-writeback-option-d/design.md
T
genichinandClaude Opus 4.8 2bfecd59a8 feat(ledger): dcy-018 option D cash/position write-back + 추정 현금 (BAT-023)
ledger write 응답의 portfolio-hub 권위 cash/quantity 를 스냅샷에 write-back 해
매수/매도 직후 "수량 live·현금 stale" 비대칭 제거(option D 활성화). portfolio-hub#8
(*_after 응답) 동작 중 확인으로 그간 deferred 됐던 REQ-030 을 닫음.

거버넌스: 전부 change-req (신규 REQ 없음, dcy-018 OQ-3 a).
- REQ-017: record_trade → LedgerWriteOutcome{result, cash_balance_after,
  asset_quantity_after}. 어댑터가 응답 root flat *_after 파싱(new+duplicate, 누락 None)
- REQ-019: LedgerWriter 에 SnapshotStore 주입 → new+duplicate write-back
  (best-effort, *_after 절대값이라 멱등). LedgerOutbox.pending_cash_delta()
- REQ-029: 추정 현금 오버레이(_apply_cash_estimate, cash_estimated 라벨, OQ-4 i)
  + 슬롯 오버레이 유지(OQ-1 B). 미정산 outbox delta 기반, write-back 수렴
- REQ-030: option D 활성화 → Approved→Implemented

best-effort 격리(NFR-1): write-back/추정 실패가 ledger 멱등·청산 무차단.
배치 번호: 동시 커밋된 BAT-022(slot-state, dcy-016)와 충돌해 BAT-023 으로 배정.

검증: 단위 350 passed/35 skipped, 인프라 게이트(PG+RabbitMQ) 385 passed, ruff clean.
신규 test_ledger_option_d + adapter/PG 테스트. BAT-023 release-candidate.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-08 21:50:56 +09:00

3.4 KiB
Raw Blame History

BAT-023 Design

  • Status: confirmed
  • Batch: BAT-023

설계 목표

  • ledger write 응답의 portfolio-hub 권위 cash/quantity 를 스냅샷에 write-back 해 "수량 live·현금 stale" 비대칭을 제거(option D 활성화). 공백 구간은 추정 현금으로 메우되 안전·멱등·best-effort 를 보장.

1. 응답 계약 확장 (REQ-017)

  • LedgerWriteOutcome{result: LedgerWriteResult, cash_balance_after: Optional[float], asset_quantity_after: Optional[float]} 신설. record_trade 반환 타입 변경.
  • portfolio_hub.py: 2xx 응답 root flat cash_balance_after/asset_quantity_after 파싱(_opt_num, 누락/형변환 실패 시 None). new(201)/duplicate(200) 모두. 409→Conflict, 404/400/422→Permanent(dcy-015) 예외 유지.
  • in_memory.py: cash_after/quantity_after 주입 파라미터 → outcome 에 실어 반환(테스트용).

2. write-back (REQ-019/030, option D)

  • LedgerWriter(snapshot_store=None) 주입. _process 가 new·duplicate outcome 후 _writeback(trade, outcome) 호출.
  • _writeback: snapshot_store None 또는 *_after None 이면 skip. 아니면 update_cash_position(trade.asset_id, cash_after, qty_after). try/except 로 격리(실패는 WARN, ledger outcome·청산 무차단, NFR-1).
  • duplicate 도 write-back(OQ-2 b): *_after 가 절대 권위값이라 멱등(중복 적용 위험 없음, NFR-3). 409/422 는 예외라 자연 skip.

3. 추정 현금 오버레이 (REQ-029 / OQ-4)

  • source = 미정산 outbox delta(슬롯 아님): 슬롯 기반이면 write-back 후에도 슬롯이 holding 이라 double-count. 미정산(pending) outbox 행은 정확히 "fill∼write-back 공백" 집합.
  • trade_cash_delta(trade): buy → -(qtyprice + fee + tax), sell → +(qtyprice - fee - tax)(코어 근사).
  • LedgerOutbox.pending_cash_delta() -> {asset_id: Σdelta}(InMemory: pending 행 합 / PG: SQL SUM CASE). 백오프 무관 전체 pending.
  • _apply_cash_estimate(payload, ledger_outbox)(snapshot 핸들러): pending delta 있는 자산에 cash_balance += delta + cash_estimated:true. best-effort(예외 시 응답 불변).
  • write-back 완료 → 행이 pending 이탈 → delta 0 → 추정 사라지고 store 는 권위값. 자연 수렴, double-count 없음.

4. 수량 source (REQ-029, OQ-1 B)

  • 슬롯 오버레이(build_snapshot_envelope) 변경 없음 — lifecycle 중 슬롯이 코어 권위 즉시값(불변식 A). write-back 은 현금 + 슬롯 밖 base 수량을 신선화. 슬롯↔write-back 불일치는 REQ-031 포지션 정합 경고로 노출(숨기지 않음).

5. daemon 배선

  • 생성 순서: SnapshotStore 를 LedgerWriter 보다 먼저 생성(write-back target).
  • LedgerWriter(snapshot_store=self.snapshot_store), snapshot 핸들러 ledger_outbox=self.outbox.

6. baseline 문서 (REQ-030 정합 포함)

  • interface-contract: LedgerWriteOutcome/*_after/write-back/envelope cash_estimated.
  • runtime-flows: ledger writer write-back 단계 + snapshot 응답 추정 오버레이.
  • REQ-030 Status Approved→Implemented(파일 + srs/index register).

테스트 설계

  • adapter: *_after 파싱(new/duplicate/누락).
  • writer: write-back new/duplicate/skip(None)/best-effort(store 예외)/store 미주입; pending_cash_delta 합; estimate overlay + write-back 후 수렴.
  • PG 게이트: pending_cash_delta SQL.
  • 회귀: record_trade 반환 타입 변경에 따른 기존 ledger 테스트(outcome.result) 정합 — 기존 테스트 더블/소비처 전환.