Files
trex-core/docs/discovery/dcy-004_20260604_portfolio-hub-ledger-integration.md
T
genichinandClaude Opus 4.8 6277d610ea Add dcy-005 Discovery (draft): portfolio-hub asset snapshot read API
- new-discovery dcy-005: read-direction follow-up of dcy-004 (deferred
  get_assets/get_portfolios). trex-core exposes portfolio-hub asset snapshot
  as a read API, returning buy candidates with slot-occupied symbols filtered
  out (slot overlay) and an empty list when no slots remain.
- 6 open questions pinned: snapshot shape, freshness/cache, core-proxy policy,
  in-flight overlay (ledger outbox eventual consistency), availability degrade,
  empty-list semantics.
- Link dcy-004 후속 Discovery 참조 → dcy-005; add dcy-005 to discovery index.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 22:51:30 +09:00

18 KiB

0. 문서 상태

이 문서의 현재 상태와 식별 정보를 기록한다. 문서 추적과 승인 흐름에 필요한 최소 메타데이터를 적는다.

  • 상태: confirmed
  • 문서 ID: dcy-004_20260604_portfolio-hub-ledger-integration
  • 이슈명: portfolio-hub ledger 연동 — 체결을 외부 원장에 기록
  • 작성 시각(KST): 2026-06-04
  • 마지막 갱신 시각(KST): 2026-06-04 22:46
  • 대체됨: 없음
  • 후속 Discovery 참조: docs/discovery/dcy-005_20260604_portfolio-hub-asset-snapshot-api.md (read 방향 후속)
  • 생성된 REQ 참조: REQ-017, REQ-018, REQ-019, REQ-020

1. 계획 상태 요약

현재 Discovery가 어떤 배경에서 작성되고 있는지, 연계 문서가 있는지, 지금 시점의 해석이 무엇인지 짧게 요약한다.

  • 연계 문서 현황:
    • 관련 점수 파일: 미생성
    • 관련 TODO/메모: trex-orbit app/clients/portfolio_client.py(post_ledger_trade 등), orbit BAT-006/REQ-025(체결→portfolio-hub ledger write-back)
  • 해석:
    • BAT-004 로 체결까지 코어가 수행하지만, 체결을 외부 회계/포지션 원장(portfolio-hub)에 기록하지 않는다. 슬롯이 portfolio_id 를 운반하는데 소비처가 없다.
    • 이번 Discovery 는 체결 시 portfolio-hub ledger 에 거래를 기록하는 통합을 더해 운영 회계 정확성을 확보한다.

2. 요구사항 판정 결과

이번 요청이 기존 요구사항과 비교해 신규인지, 유사한지, 중복인지 판단하고 그 근거를 남긴다.

  • 판정: 신규
  • 근거
    • dcy-001~003 어디에도 portfolio-hub ledger 연동이 In Scope 가 아니었다(dcy-003 미명시 deferral).
    • 기존 REQ 본문 수정이 아니라 신규 외부 통합(원장 기록)이라 change-req 가 아니라 새 Discovery 다.

3. 문제점의 요약

문제의 현재 상태, 기대 상태, 관련 이해관계자, 제약사항을 정리한다. Discovery의 핵심 맥락을 가장 먼저 이해할 수 있어야 한다.

  • 현재 상태
    • 코어 집행 루프(BAT-004)는 체결 시 슬롯을 holding/closed 로만 전이한다. portfolio-hub 등 외부 원장에 거래를 기록하지 않는다(상태탐색: portfolio/ledger 모듈 0).
    • 슬롯 계약에 portfolio_id 가 있으나(dcy-001 부록 A, intent API 수신) 소비처가 없다(배선만 됨).
    • orbit 에는 PortfolioHubClient.post_ledger_trade(POST /v1/assets/{asset_id}/ledger/trades, execution_id 멱등, 409 LedgerConflictError)가 있어 체결을 원장에 기록한다.
  • 기대 상태
    • 체결(매수/매도) 시 코어가 portfolio-hub ledger 에 거래(asset/side/qty/price/fee/tax/executed_at)를 멱등하게 기록한다.
    • portfolio-hub 일시 장애에도 원장 기록이 유실되지 않게 처리한다(재시도/보류).
  • 이해관계자
    • 문서 작성자/구현자(AI agent)
    • 리뷰어/승인자(운영자, genichin@gmail.com)
    • portfolio-hub(외부 회계/포지션 서비스) 및 그 데이터 소비자
  • 제약사항
    • 도메인 순수성 — trex_core.domain.slot 은 portfolio-hub/네트워크 의존 0(어댑터 격리).
    • 멱등 — 동일 체결(execution_id)이 원장에 중복 기록되지 않는다.
    • 단일 기록 주체 — 코어 집행 경로만 원장에 기록(전략/도메인 미접근).

주요 사용자 시나리오

사용자 또는 운영자가 실제로 어떤 상황에서 이 변경을 필요로 하는지 대표 시나리오를 적는다.

  • 매수 체결(pending_buy→holding) 시 코어가 portfolio-hub ledger 에 buy 거래를 기록 → 외부 포지션/잔고 반영.
  • 매도 체결(pending_sell→closed) 시 sell 거래 기록 → 실현 손익/잔고 반영.
  • portfolio-hub 가 잠시 불가 → 코어가 재시도/보류로 원장 기록을 결국 반영(유실 없음), 슬롯 전이는 별도 보장.

4. 이번에 정의할 변경

이번 Discovery에서 다루는 핵심 변경 항목을 목록으로 적는다. 기능, 구조, 규칙 변경을 함께 포함해도 된다.

  • LedgerPort + portfolio-hub 어댑터: LedgerPort.record_trade(...) + PortfolioHubLedger(orbit post_ledger_trade 패턴) + 결정적 테스트용 stub/in-memory 원장.
  • 체결→원장 기록: 집행 루프의 fill 전이 시 거래를 원장에 기록(buy/sell), execution_id 멱등.
  • 내구성: portfolio-hub 장애 시 재시도/보류(원장 기록 유실 방지) — 방식은 OQ.
  • baseline 갱신: runtime-flows(원장 기록 흐름)·interface-contract(LedgerPort)·project-structure(ledger 어댑터)·data-model(원장 기록/필드).

영향 범위

변경이 미칠 코드/문서/운영 범위를 명시해 REQ 정제와 batch planning에서 추정 누락을 줄인다.

  • 코드 영향:
    • 신규: trex_core/ 하위 LedgerPort + portfolio-hub 어댑터 + stub, 집행 루프 fill 경로에 ledger 기록 hook. config(portfolio-hub URL/토큰).
    • 기존: ExecutionService fill 전이에 원장 기록 단계 추가.
  • 문서 영향:
    • runtime-flows/interface-contract/project-structure/data-model 갱신.
  • 운영 영향:
    • 신규 외부 의존(portfolio-hub) + 자격증명(.env). 장애 시 재시도/보류 운영 절차.

5. 요구사항 목록

실제로 구현과 검증으로 이어질 요구사항을 기능/비기능으로 나누어 정리하고, 범위 경계를 명확히 적는다.

기능 요구사항

  • FR-1: LedgerPort.record_trade(asset, order_id, execution_id, side, qty, price, fee, tax, executed_at) 추상화 + portfolio-hub 어댑터(POST /v1/assets/{asset_id}/ledger/trades) + 결정적 검증용 stub/in-memory 원장을 제공한다.
  • FR-2: 집행 루프가 매수 체결(pending_buy→holding)·매도 체결(pending_sell→closed) 시 해당 거래를 원장에 기록한다. ledger path 의 asset_id 는 슬롯 portfolio_id(=asset_id, OQ-1 결정)를 사용하고 side/qty/price/체결데이터를 함께 보낸다.
  • FR-3: 원장 기록은 correlation_id({asset_id}:{order_id}:{execution_id})로 멱등하다 — 동일 체결 재기록 시 로컬 dedup + portfolio-hub 중복/409(LedgerConflict)를 안전 처리하고 중복 원장 0. execution_id 는 결정적 합성(OQ-3).
  • FR-4: 체결 시 원장 기록을 ledger outbox(work item)에 적재하고 재시도 루프가 portfolio-hub write 를 수행한다(orbit LedgerWriter 패턴). outcome = completed/duplicate_completed/retryable_failure/conflict_blocked. 슬롯 전이와 분리되어 일시 장애에도 유실 없이 결국 기록(conflict 는 운영자 해소).
  • FR-5: baseline 문서(runtime-flows/interface-contract/project-structure/data-model)를 ledger 레이어에 맞게 갱신한다.

비기능 요구사항

성능, 운영성, 일관성, 자동화 안정성 등 기능 외 요구사항을 적는다.

  • NFR-1: 도메인 순수성 유지 — trex_core.domain.slot 은 portfolio-hub/네트워크 의존 0(어댑터 격리, import 스윕).
  • NFR-2: 멱등/무유실 — 동일 execution_id 중복 기록 없음 + 장애 시 결국 기록(at-least-once + dedup).
  • NFR-3: 단일 기록 주체 — 코어 집행 경로만 원장에 기록.

범위 경계

이번 반복에서 반드시 포함할 범위와 명시적으로 제외할 범위를 구분한다.

  • In Scope
    • LedgerPort + portfolio-hub 어댑터(post_ledger_trade) + stub, 체결→원장 기록(buy/sell), execution_id 멱등, 장애 재시도/보류, baseline 갱신.
  • Out of Scope (후속 Discovery)
    • post_portfolio_transaction(입출금 등 거래 외 트랜잭션), asset master 동기화·get_assets/get_portfolios 연동.
    • portfolio-hub ↔ 코어 양방향 정합(reconciliation), 포지션 재구성.
    • safety floor, 매수·매도 전략 데몬, 실계좌 go-live(타 Discovery).

6. 리스크/가정 목록

불확실성과 전제 조건을 분리해서 적는다. 리스크는 영향과 완화 방안을, 가정은 현재 판단의 전제를 기록한다.

리스크

  • R-1 (High): 원장 기록 유실/중복 — 체결은 됐는데 원장 미기록(장애) 또는 중복 기록 시 회계 부정확.
    • 완화 방안(OQ-2/OQ-3): correlation_id 멱등(409/duplicate 안전) + ledger outbox 재시도 루프(무유실) + conflict_blocked 운영자 해소. stub 으로 결정적 테스트.
  • R-2 (해소, OQ-1): asset_id 해석 — orbit 근거로 asset_id == portfolio_id 확정. 슬롯 portfolio_id 를 path 로 직접 사용(추가 매핑/조회 없음).
  • R-3 (Medium): 외부 의존 결합 — portfolio-hub 장애가 집행 경로를 막을 위험.
    • 완화 방안: 원장 기록을 슬롯 전이와 분리(전이는 우선, 기록은 재시도/보류). 어댑터 격리(NFR-1).
  • R-4 (해소, OQ-3): fee/tax/execution_id 출처 — execution_id 결정적 합성(KIS 규칙/paper synth), fee 는 BrokerPort 계산(orbit calculate_fee), tax 기본 0(정밀 모델 후속).

가정

아직 확정되지 않았지만 현재 문서 작성의 기반이 되는 전제를 적는다.

  • A-1: portfolio-hub 는 orbit 과 동일 계약(POST /v1/assets/{asset_id}/ledger/trades, execution_id 멱등, 409 conflict)을 제공한다.
  • A-2: 코어는 단일 인스턴스로 원장 기록의 단일 주체다.
  • A-3: 자격증명(URL/토큰)은 .env(REQ-010 패턴)로 주입한다(orbit TREX_PORTFOLIO_HUB_*).

오픈 질문

답을 아직 모르는 항목을 적는다. 추정으로 덮지 말고 REQ 초안 작성 이전에 확인할 질문으로 남긴다.

  • OQ-1: asset_id 해석 — portfolio-hub {asset_id} 를 슬롯의 무엇으로 채울지(portfolio_id == asset_id? symbol→asset_id 매핑? portfolio-hub get_assets 조회?).
    • 상태: Answered (2026-06-04)
    • 결정: asset_id == portfolio_id — 슬롯의 portfolio_id 를 ledger path param 으로 직접 사용. symbol 매핑/get_assets 조회 불필요. 근거: trex-orbit app/domain/ledger.py("asset_id == portfolio_id for this system"), migration 003(Order Execution 이 {portfolio_id} path 로 write-back, 매수 커밋 시 슬롯에 캐시), coordinator.py(asset_id = asset_id or portfolio_id). trex-core 는 이미 buy intent 가 portfolio_id 를 공급해 슬롯에 캐시하므로 추가 조회 없이 그대로 사용.
    • 반영 위치: # 5 FR-1·FR-2, # 6 R-2(해소). buy intent 의 portfolio_id 필수 여부는 구현 시 결정(없으면 기록 skip 또는 거부).
  • OQ-2: 기록 내구성 방식 — fill 전이와 동일 경로 동기 기록 vs 별도 ledger outbox 비동기.
    • 상태: Answered (2026-06-04)
    • 결정: 별도 ledger outbox(work item) + 재시도 루프 (orbit LedgerWriter/recovery_work_item 패턴 차용). 체결 시 ledger 기록을 outbox 에 적재(슬롯 전이와 분리 → 전이는 우선, 기록은 eventually-consistent), 루프가 portfolio-hub write 시도. correlation_id = {asset_id}:{order_id}:{execution_id} 로 dedup, outcome 어휘 = completed / duplicate_completed / retryable_failure(재시도) / conflict_blocked(409, 운영자 해소). 근거: orbit app/domain/ledger.py 가 동일 패턴으로 무유실+멱등을 보장.
    • 반영 위치: # 4, # 5 FR-3·FR-4, # 6 R-1. (BAT-004 의 옵션 B outbox 와 유사 구조 — ledger 전용.)
  • OQ-3: 체결 데이터(execution_id/fee/tax) 출처 — KIS 체결조회/주문응답에서 추출 가능 범위, paper 더블의 합성 값.
    • 상태: Answered (2026-06-04)
    • 결정: execution_id 는 결정적 합성 — KIS: kis-daily-ccld:{ord_dt}:{ord_gno_brno}:{odno}:{tot_ccld_qty}(orbit kis.py 규칙 차용), paper: 결정적 synth(예: paper:{broker_order_id}:{filled_qty}). fee 는 BrokerPort fee 계산(orbit calculate_fee(side, amount) 차용), tax 는 기본 0(정밀 세금 모델은 후속). FillStatus/브로커가 execution_id·fee 를 운반하도록 contract 확장.
    • 반영 위치: # 5 FR-1·FR-3, # 6 R-4. (BrokerPort 에 fee 계산 + FillStatus.execution_id 추가는 구현 세부.)

오픈 질문 처리 기준

  • Open: 아직 답이 없고 다음 단계 진행에 영향이 있는 상태다. 문서에 유지한다.
  • Answered: 답이 확정된 상태다. 관련 본문/요구사항에 반영한 뒤 오픈 질문 목록에서는 제거하거나 resolved 기록으로 이동한다.
  • Deferred: 지금 단계에서 결정하지 않고 다음 단계 또는 후속 이슈로 넘기는 상태다. 넘길 대상과 이유를 반드시 적는다.
  • Dropped: 더 이상 유효하지 않은 질문이다. 삭제 사유를 짧게 남기고 목록에서 제거한다.
  • 처리 원칙 1: 질문만 남기지 말고 반드시 상태, 처리 방안, 종료 조건을 함께 적는다.
  • 처리 원칙 2: 답이 나온 질문은 본문 반영 없이 단순 삭제하지 않는다. 반영 위치를 먼저 갱신한다.
  • 처리 원칙 3: High 영향 질문은 Deferred 처리 후 처리 방안에 REQ 인계 추적 방식을 명시한다.

7. 초기 성공 기준

이 변경이 성공했다고 판단할 기준을 측정 가능하게 정의한다. 가능하면 데이터 출처까지 연결한다.

  • S-1: stub 원장으로, 매수 체결 시 buy 거래가 원장에 1건 기록되고 슬롯이 holding 으로 전이된다.
  • S-2: 매도 체결 시 sell 거래가 기록되고 슬롯이 closed 로 전이된다.
  • S-3: 동일 execution_id 재기록 시 원장에 중복이 생기지 않는다(멱등).
  • S-4: 원장 기록이 일시 실패해도 재시도/보류로 결국 기록되고, 슬롯 전이 일관성이 유지된다.
  • S-5: 도메인 import 스윕에서 portfolio-hub/네트워크 의존이 0 으로 유지된다.

측정 방식 및 데이터 출처

각 성공 기준을 어떻게 측정할지와 어떤 데이터나 문서를 근거로 쓸지 적는다.

  • S-1/S-2 측정 방식: stub 원장 통합테스트(체결 lifecycle) / 데이터 출처: 테스트 결과, 원장 기록 목록·슬롯 상태.
  • S-3 측정 방식: 동일 execution_id 재기록 테스트 / 데이터 출처: 원장 엔트리 수.
  • S-4 측정 방식: 원장 실패 주입 후 재시도 테스트 / 데이터 출처: 기록 결과·슬롯 상태.
  • S-5 측정 방식: 정적 import 스윕 / 데이터 출처: trex_core/domain import 목록.

8. REQ로 넘기기 전 확인 체크

REQ 초안 작성 단계로 넘기기 전에 무엇이 확정되어야 하는지 확인한다. 미완료 항목은 후속 결정이 필요함을 뜻한다.

  • 범위(In Scope/Out of Scope) 확정 — 특히 거래 외 트랜잭션/asset 동기화 제외
    • 기준: 운영자가 슬라이스 경계를 확인했다. (운영자 확정 2026-06-04 — 문서대로 수용)
  • 성공 지표의 측정 방식/데이터 출처 확정
    • 기준: S-1~S-5 측정 방식·출처 명시(본 문서 # 7).
  • High 영향 리스크(R-1 원장 유실/중복) 처리 방침 확정
    • 기준: execution_id 멱등 + 재시도/보류로 수용 가능한지 운영자 확인. (운영자 확정 2026-06-04 — 멱등+outbox 재시도 수용)
  • 모든 오픈 질문(OQ) Open 상태 없음 확인
    • 기준: OQ 목록에 Open 0개 또는 잔여 전부 Deferred (OQ-1~OQ-3 전부 Answered → Open 0개)
  • Freeze 확정 담당자(CONFIRMED_BY) 지정
    • 기준: 운영자가 confirm-discovery 시점에 CONFIRMED_BY 를 지정한다. (Confirmed By = Justin Ko)

9. 파일 처리 결과

이 Discovery 문서 생성/갱신 결과와 참조 문서를 기록한다. 이후 추적과 리뷰 시 근거로 사용한다.

  • 처리 결과: 생성
  • 생성 경로: docs/discovery/dcy-004_20260604_portfolio-hub-ledger-integration.md
  • 참조 문서:
    • docs/discovery/dcy-003_20260604_broker-execution-and-outbox.md (집행/체결 — 본 Discovery 가 체결에 원장 기록을 더한다)
    • docs/project-structure.md, docs/runtime-flows.md, docs/interface-contract.md, docs/data-model.md (baseline — 본 Discovery 가 갱신 요구)
    • trex-orbit app/clients/portfolio_client.py (post_ledger_trade 계약 참고)

10. 사용자 결정 필요 항목 요약

사용자나 승인자가 결정해야 하는 항목만 모아 정리한다. 결정, 확인, 데이터 요청을 구분해서 적는다.

  • asset_id 소스 → 결정됨: asset_id == portfolio_id(슬롯 portfolio_id 직접 사용, orbit 근거) (OQ-1, 2026-06-04)
  • 기록 내구성 → 결정됨: 별도 ledger outbox + 재시도 루프(orbit LedgerWriter 패턴), correlation_id dedup, outcome 어휘 (OQ-2, 2026-06-04)

CONFIRM

  • 체결 데이터 출처 → 결정됨: execution_id 결정적 합성(KIS 규칙/paper) + fee BrokerPort 계산 + tax 0 (OQ-3, 2026-06-04)
  • 범위 경계 확정 → 결정됨: 거래 외 트랜잭션(post_portfolio_transaction)·asset 동기화·reconciliation 을 후속 Discovery 로 미루는 In/Out Scope 경계를 문서대로 확정 (운영자 Justin Ko, 2026-06-04)

DATA

  • portfolio-hub ledger API 계약/자격증명 → REQ/delivery 단계 계측·확정으로 위임(비차단): 설계는 orbit post_ledger_trade 검증 패턴 + stub/in-memory 결정론 테스트(S-1~S-5)로 REQ 초안 작성에 충분하며, 실제 계약/자격증명(orbit 동일 가정 A-1) 검증은 REQ acceptance/verification evidence 로 수집. (운영자 Justin Ko, 2026-06-04)

11. Discovery Freeze

REQ 초안 작성 인계 시점의 확정 플래그와 섹션 참조만 기록한다. 내용 재작성 없이 원본 섹션을 링크로 대신한다.

섹션 참조

Freeze 플래그

  • Handoff Decision: 승인
  • Handoff Rationale: OQ-1~OQ-3(asset_id 소스·기록 내구성·체결 데이터 출처) 전부 Answered, 범위 경계·R-1 처리 방침 운영자 확정(문서대로). portfolio-hub ledger API 계약/자격증명(DATA)은 orbit 동일 가정 + stub 결정론 검증으로 REQ 초안 작성을 막지 않아 REQ/delivery 계측 evidence 로 위임(비차단). 저장소 사실 관계는 현재 상태와 일치(ledger 모듈 0, portfolio_id 배선만 됨, BAT-004 fill 전이 확인). REQ Drafting 으로 인계한다.
  • Ready for REQ Drafting: true
  • Confirmed By: Justin Ko
  • Confirmed At (KST): 2026-06-04 21:47