From b37b16be934a7eb08f07f58002166a1b71913e74 Mon Sep 17 00:00:00 2001 From: Justin Ko Date: Thu, 4 Jun 2026 19:30:38 +0900 Subject: [PATCH] Add dcy-004 Discovery (draft): portfolio-hub ledger integration Record fills to the portfolio-hub ledger (orbit post_ledger_trade pattern) so external accounting/positions reflect trades. The slot already carries portfolio_id but nothing consumes it; this closes a deferral not previously made explicit in dcy-003. Scope: LedgerPort + portfolio-hub adapter + stub, record on buy/sell fill, execution_id idempotency, failure retry/hold; out of scope: non-trade transactions, asset sync, reconciliation. OQ-1 (asset_id source), OQ-2 (durability), OQ-3 (exec data source) left for confirm-discovery; handoff held. Co-Authored-By: Claude Opus 4.8 (1M context) --- ...260604_portfolio-hub-ledger-integration.md | 218 ++++++++++++++++++ docs/discovery/index.md | 3 +- 2 files changed, 220 insertions(+), 1 deletion(-) create mode 100644 docs/discovery/dcy-004_20260604_portfolio-hub-ledger-integration.md diff --git a/docs/discovery/dcy-004_20260604_portfolio-hub-ledger-integration.md b/docs/discovery/dcy-004_20260604_portfolio-hub-ledger-integration.md new file mode 100644 index 0000000..cb99892 --- /dev/null +++ b/docs/discovery/dcy-004_20260604_portfolio-hub-ledger-integration.md @@ -0,0 +1,218 @@ +# 0. 문서 상태 +이 문서의 현재 상태와 식별 정보를 기록한다. 문서 추적과 승인 흐름에 필요한 최소 메타데이터를 적는다. + +- 상태: draft +- 문서 ID: dcy-004_20260604_portfolio-hub-ledger-integration +- 이슈명: portfolio-hub ledger 연동 — 체결을 외부 원장에 기록 +- 작성 시각(KST): 2026-06-04 +- 마지막 갱신 시각(KST): 2026-06-04 +- 대체됨: 없음 +- 후속 Discovery 참조: 없음 +- 생성된 REQ 참조: 없음 + +# 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`) 시 해당 거래를 원장에 기록한다(슬롯 `portfolio_id`/symbol + 체결 데이터 사용). +- FR-3: 원장 기록은 `execution_id` 로 멱등하다 — 동일 체결 재기록 시 portfolio-hub 중복/409(LedgerConflict)를 안전 처리하고 중복 원장 0. +- FR-4: portfolio-hub 일시 장애 시 원장 기록을 재시도/보류해 유실하지 않는다(슬롯 전이 일관성과 분리). +- 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): 원장 기록 유실/중복 — 체결은 됐는데 원장 미기록(장애) 또는 중복 기록 시 회계 부정확. + - 완화 방안: execution_id 멱등(409/duplicate 안전 처리, FR-3) + 장애 재시도/보류(FR-4). stub 으로 결정적 테스트. +- R-2 (Medium): asset_id 해석 — portfolio-hub 경로는 `asset_id`(canonical) 인데 슬롯은 symbol/portfolio_id 보유. 매핑 불명확. + - 완화 방안: 매핑 규칙을 OQ-1 로 확정(portfolio_id/asset 매핑 또는 symbol→asset_id). +- R-3 (Medium): 외부 의존 결합 — portfolio-hub 장애가 집행 경로를 막을 위험. + - 완화 방안: 원장 기록을 슬롯 전이와 분리(전이는 우선, 기록은 재시도/보류). 어댑터 격리(NFR-1). +- R-4 (Medium): fee/tax/execution_id 출처 — 체결 데이터에 수수료/세금/체결ID가 항상 있지는 않음(paper vs KIS). + - 완화 방안: KIS 체결 조회/주문 응답에서 추출, 없으면 0/derived. OQ-3. + +## 가정 +아직 확정되지 않았지만 현재 문서 작성의 기반이 되는 전제를 적는다. + +- 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 조회?). + - 상태: Open + - 처리 방안: 운영자/portfolio-hub 계약 확인. 기본안 검토: 슬롯 `portfolio_id` 를 asset_id 로 직접 사용 또는 symbol 기반 매핑. + - 종료 조건: asset_id 소스가 정해져 FR-1/FR-2 에 연결되면 Answered. +- OQ-2: 기록 내구성 방식 — fill 전이와 동일 경로 동기 기록(실패 시 재시도) vs 별도 ledger outbox(BAT-004 옵션 B 와 유사) 비동기. + - 상태: Open + - 처리 방안: 운영자 확인. 기본안: 동기 기록 + 실패 시 재시도/보류 마킹(슬롯에 ledger_recorded 플래그 또는 보류 큐). 강한 내구성 필요 시 outbox. + - 종료 조건: 내구성 방식이 정해져 FR-4 수용 기준에 연결되면 Answered. +- OQ-3: 체결 데이터(execution_id/fee/tax) 출처 — KIS 체결조회/주문응답에서 추출 가능 범위, paper 더블의 합성 값. + - 상태: Open + - 처리 방안: KIS inquire-daily-ccld 응답 필드 확인 + paper 더블이 결정적 execution_id/fee 제공. + - 종료 조건: 필드 출처/기본값이 정해지면 Answered. + +### 오픈 질문 처리 기준 +- `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 동기화 제외 + - 기준: 운영자가 슬라이스 경계를 확인했다. +- [x] 성공 지표의 측정 방식/데이터 출처 확정 + - 기준: S-1~S-5 측정 방식·출처 명시(본 문서 # 7). +- [ ] High 영향 리스크(R-1 원장 유실/중복) 처리 방침 확정 + - 기준: execution_id 멱등 + 재시도/보류로 수용 가능한지 운영자 확인. +- [ ] 모든 오픈 질문(OQ) `Open` 상태 없음 확인 + - 기준: OQ 목록에 `Open` 0개 또는 잔여 전부 `Deferred` (현재 OQ-1~OQ-3 Open) +- [ ] Freeze 확정 담당자(CONFIRMED_BY) 지정 + - 기준: 운영자가 confirm-discovery 시점에 CONFIRMED_BY 를 지정한다. + +# 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. 사용자 결정 필요 항목 요약 +사용자나 승인자가 결정해야 하는 항목만 모아 정리한다. 결정, 확인, 데이터 요청을 구분해서 적는다. + +## DECIDE +- {{DECIDE_ASSET_ID_SOURCE}} — portfolio-hub `{asset_id}` 소스: 슬롯 portfolio_id 직접 vs symbol→asset_id 매핑 vs get_assets 조회 (OQ-1) +- {{DECIDE_LEDGER_DURABILITY}} — 기록 내구성: 동기+재시도/보류 vs 별도 ledger outbox (OQ-2) + +## CONFIRM +- {{CONFIRM_EXEC_DATA_SOURCE}} — 체결 데이터(execution_id/fee/tax) 출처(KIS 추출 + paper 합성) 확정 (OQ-3) +- {{CONFIRM_SCOPE_DEFER}} — 거래 외 트랜잭션·asset 동기화·reconciliation 을 후속으로 미루는 범위 경계 확정 + +## DATA +- {{DATA_PORTFOLIO_HUB_CONTRACT}} — portfolio-hub ledger API 계약/자격증명(orbit 와 동일 가정 검증) + +# 11. Discovery Freeze +REQ 초안 작성 인계 시점의 확정 플래그와 섹션 참조만 기록한다. 내용 재작성 없이 원본 섹션을 링크로 대신한다. + +## 섹션 참조 +- 범위: [# 5. 요구사항 목록 > 범위 경계](#5-요구사항-목록) +- 리스크/오픈 질문: [# 6. 리스크/가정 목록](#6-리스크가정-목록) +- 성공 기준: [# 7. 초기 성공 기준](#7-초기-성공-기준) +- 인계 전 확인: [# 8. REQ로 넘기기 전 확인 체크](#8-req로-넘기기-전-확인-체크) +- 사용자 결정 필요: [# 10. 사용자 결정 필요 항목 요약](#10-사용자-결정-필요-항목-요약) + +## Freeze 플래그 +- Handoff Decision: 보류 +- Handoff Rationale: 초기 Discovery 초안 생성 완료. OQ-1~OQ-3(asset_id 소스·기록 내구성·체결 데이터 출처)와 범위 경계가 미확정이며, confirm-discovery 에서 운영자 결정 후 REQ Drafting 으로 인계한다. +- Ready for REQ Drafting: false +- Confirmed By: TBD +- Confirmed At (KST): TBD diff --git a/docs/discovery/index.md b/docs/discovery/index.md index 6085194..da14653 100644 --- a/docs/discovery/index.md +++ b/docs/discovery/index.md @@ -8,4 +8,5 @@ |---|---|---|---|---| | dcy-001_20260603_slot-pure-domain-migration | 20260603 | trex-core 베이스 수립 및 slot 순수 도메인 이식 | [Discovery](./dcy-001_20260603_slot-pure-domain-migration.md) | confirmed | | dcy-002_20260603_slot-core-daemon-http-api | 20260603 | trex-core 슬롯 코어 데몬화 + HTTP intent API | [Discovery](./dcy-002_20260603_slot-core-daemon-http-api.md) | confirmed | -| dcy-003_20260604_broker-execution-and-outbox | 20260604 | 브로커 단일 세션 + 주문 집행 + durable outbox (불변식 B) | [Discovery](./dcy-003_20260604_broker-execution-and-outbox.md) | confirmed | \ No newline at end of file +| dcy-003_20260604_broker-execution-and-outbox | 20260604 | 브로커 단일 세션 + 주문 집행 + durable outbox (불변식 B) | [Discovery](./dcy-003_20260604_broker-execution-and-outbox.md) | confirmed | +| dcy-004_20260604_portfolio-hub-ledger-integration | 20260604 | portfolio-hub ledger 연동 — 체결을 외부 원장에 기록 | [Discovery](./dcy-004_20260604_portfolio-hub-ledger-integration.md) | draft | \ No newline at end of file