# 0. 문서 상태 이 문서의 현재 상태와 식별 정보를 기록한다. 문서 추적과 승인 흐름에 필요한 최소 메타데이터를 적는다. - 상태: confirmed - 문서 ID: dcy-005_20260604_portfolio-hub-asset-snapshot-api - 이슈명: portfolio-hub 종목 스냅샷 read API — 전략 모듈에 전략 실행 대상 자산 리스트 제공 - 작성 시각(KST): 2026-06-04 22:46 - 마지막 갱신 시각(KST): 2026-06-06 00:20 - 대체됨: 없음 - 후속 Discovery 참조: 없음 - 생성된 REQ 참조: REQ-027, REQ-028, REQ-029, REQ-030, REQ-031, REQ-032, REQ-033 # 1. 계획 상태 요약 현재 Discovery가 어떤 배경에서 작성되고 있는지, 연계 문서가 있는지, 지금 시점의 해석이 무엇인지 짧게 요약한다. - 연계 문서 현황: - 관련 점수 파일: 미생성 - 관련 TODO/메모: dcy-004(portfolio-hub ledger write 통합, BAT-006 진행 중)의 Out of Scope `asset master 동기화·get_assets/get_portfolios 연동`. orbit `app/clients/portfolio_client.py`(read 계약 get_assets/get_portfolios 패턴). **dcy-006(confirmed) — trex-core ↔ 전략 모듈 통신 transport 를 RabbitMQ 로 확정(HTTP 표면 0). 본 Discovery 의 스냅샷 read 는 그 transport(RabbitMQ request-reply RPC) 위에서 전달된다.** - 해석: - dcy-004 는 체결을 portfolio-hub 원장에 기록하는 **write 방향**이었고, 본 Discovery 는 그 반대인 **read 방향** — portfolio-hub 종목 스냅샷을 trex-core 가 전략 모듈에 제공한다. dcy-004 가 명시적으로 후속으로 미룬 영역을 이어받는다. - **transport/계약 분리**: 본 Discovery 는 스냅샷의 **logical 계약**(응답 단위·필드·마스킹·view 모드·strategy_key 필터·신선도)을 정의하며 **transport-무관**이다. 전달 방식(HTTP 아님 — dcy-006 의 RabbitMQ RPC)은 dcy-006 소관이고, 본 문서는 그 transport 위에 logical 계약을 얹는다. - active-cycle 판정(BAT-006 draft 진행 중)이지만 명시적 신규 입력이고, BAT-006(write)과 read 방향이 분리되어 범위 충돌이 없으므로 병렬 독립 주기로 진행한다. core 경유의 핵심 가치는 단순 패스스루가 아니라 **core 의 권위 슬롯 상태를 겹쳐(slot overlay) 전략 실행 대상 자산을 선별(pending·warning·pause 마스킹)** 해 제공하는 것이다. # 2. 요구사항 판정 결과 이번 요청이 기존 요구사항과 비교해 신규인지, 유사한지, 중복인지 판단하고 그 근거를 남긴다. - 판정: 신규 - 근거 - dcy-004 는 write 방향(원장 기록)만 In Scope 였고 read 방향(get_assets/get_portfolios)은 명시적으로 Out of Scope(후속)였다. 본 Discovery 는 그 후속 read 통합이다. - 기존 REQ 본문 수정이 아니라 신규 외부 read 통합 + 신규 read 계약(스냅샷 응답)이라 `change-req` 가 아니라 새 Discovery 다. # 3. 문제점의 요약 문제의 현재 상태, 기대 상태, 관련 이해관계자, 제약사항을 정리한다. Discovery의 핵심 맥락을 가장 먼저 이해할 수 있어야 한다. - 현재 상태 - trex-core 의 read 경로는 슬롯/capacity 조회와 intent 조회뿐이고 portfolio-hub 종목을 노출하는 read 경로가 없다. (dcy-006 구현으로 통신 transport 가 RabbitMQ 로 전환됨 — `adapters/api` HTTP 어댑터 제거, `adapters/messaging`(rabbitmq/topology) 추가, daemon 이 `trex.snapshot.req`/`trex.intent.req` RPC consumer 운영. 따라서 본 스냅샷 read 는 그 snapshot RPC 위에 portfolio-hub 종목 logical 계약을 얹는다 — portfolio-hub read 포트/어댑터는 아직 없다.) - portfolio-hub read 어댑터(get_assets/get_portfolios)가 trex-core 에 없다. dcy-004/BAT-006 은 write 방향 `LedgerPort` + portfolio-hub 어댑터만 추가하며, read 포트는 다루지 않는다. - 결과적으로 전략 모듈이 "무엇을 매매할지" 정할 portfolio-hub 종목 정보를 trex-core 를 통해 받을 경로가 없다. - 기대 상태 - trex-core daemon 이 설정 가능한 시각(기본 07:30 KST, .env)에 일일 스케줄로 portfolio-hub 에서 자신의 `daemon_id` universe(= `managed_by_daemon_id` 가 해당 daemon 인 portfolio 집합)를 가져와 스냅샷으로 보유한다. universe 항목은 portfolio 단위로 symbol, asset_id(=portfolio_id), 현금(cash_balance), 포지션(asset_quantity), active_strategy_id, 전략파라미터(opaque JSON)를 담고 **가격(price)은 제외**한다. - 전략 모듈 요청 시 trex-core 가 이 스냅샷을 기반으로 **portfolio-hub 보유 자산(holding 슬롯 자산 포함)을 담되 pending·warning·pause 상태 자산은 마스킹한 전략 실행 대상 자산 리스트**를 반환한다(OQ-4). - 응답은 요청 `view` 파라미터(`all` 기본 | `buyable` | `held`)로 base 집합을 고르고 envelope(`assets[]` + `available_slots`·`degraded` 등 meta)로 반환한다(OQ-11/OQ-12/OQ-6). 가용 슬롯이 0 이어도 보유 자산은 계속 반환되며 신규 매수 가용성은 `available_slots` meta 로 신호한다. - 이해관계자 - 문서 작성자/구현자(AI agent) - 리뷰어/승인자(운영자, genichin@gmail.com) - 전략 모듈(매수/매도/position, trex-core 외부 소비자) 및 portfolio-hub(외부 자산/포지션 서비스) - 제약사항 - 도메인 순수성 — `trex_core.domain` 은 portfolio-hub/네트워크 의존 0(read 어댑터 격리). - 단일 통합 경계 — portfolio-hub 자격증명/계약 접점은 trex-core 어댑터(dcy-004 재사용)로 한정하며, 매수 전략 등 모든 소비자는 core 스냅샷 계약을 경유한다(portfolio-hub 직접 호출 금지, OQ-3). - core 는 슬롯/capacity 의 권위 단일 writer(불변식 A) — slot overlay 는 이 권위 상태를 사용한다. - transport 종속성 — 스냅샷 read 의 전달은 dcy-006(confirmed)의 RabbitMQ request-reply RPC 를 사용한다(HTTP 아님). 본 Discovery 는 logical 계약만 정의하고 transport 세부(토폴로지·delivery)는 dcy-006 소관이다. ## 주요 사용자 시나리오 사용자 또는 운영자가 실제로 어떤 상황에서 이 변경을 필요로 하는지 대표 시나리오를 적는다. - 전략 모듈이 자기 portfolio(전략 실행 대상 자산)를 trex-core 스냅샷 API 로 요청 → portfolio-hub 보유 자산(holding 슬롯 자산 포함)을 받되, pending·warning·pause 상태 자산은 마스킹되어 빠진 리스트를 받는다. - 모든 슬롯이 차 있어 더 살 수 없는 상황 → 보유 자산은 계속 응답에 포함되고(`view=all`/`held`), 신규 매수 가능 여부는 `available_slots` meta 로 신호한다. `view=buyable` 은 `available_slots=0` 이면 `[]` 를 반환한다(OQ-11/OQ-12). - 막 매수 주문되어 슬롯이 pending(미체결)인 종목 → 마스킹되어 전략 모듈 응답에서 빠져 중복 매수를 막고, 체결되어 holding 이 되면 보유 자산으로 응답에 포함된다. - 스냅샷 페치 시 portfolio-hub 포지션과 실제 브로커 계좌 포지션이 어긋난 자산 → `warning` 으로 표시되어 그 자산은 응답 리스트에서 마스킹되고 매매 전략이 수행되지 않는다. - 전략 모듈이 자기 `strategy_key` 를 파라미터로 넣어 포트폴리오를 요청 → 그 `strategy_key` 가 등록된 자산만 받는다. 파라미터를 생략하면 전체 자산을 받는다(여러 전략 모듈이 하나의 스냅샷 API 를 공유하되 자기 책임 자산만 조회). - 운영자가 특정 자산을 런타임에 pause → 전략 모듈이 포트폴리오를 요청해도 그 자산은 응답에서 마스킹되어 매매가 멈추고, resume 하면 다시 응답에 포함된다. # 4. 이번에 정의할 변경 이번 Discovery에서 다루는 핵심 변경 항목을 목록으로 적는다. 기능, 구조, 규칙 변경을 함께 포함해도 된다. - portfolio-hub read 포트 + 어댑터: `PortfolioHubReadPort.get_portfolios(daemon_id)` 추상화 + portfolio-hub 어댑터(`managed_by_daemon_id` 로 daemon universe 조회, dcy-004 어댑터/자격증명 재사용) + 결정론 stub. 전략파라미터는 trex-core 가 해석하지 않는 opaque JSON 으로 운반한다. - 일일 스냅샷 갱신: 설정 시각(기본 07:30 KST, .env)에 스케줄러(신규 daemon 컴포넌트)가 universe 를 페치해 스냅샷을 갱신한다. 현금/포지션은 baseline 에 더해, 체결 시 portfolio-hub ledger write 응답이 반환하는 변경 후 권위 cash/position 으로 갱신한다(옵션 D — 당초 옵션 C 의 core 로컬 체결 replay 보정을 대체). - 포지션 정합 검증 + warning: 페치 시 portfolio-hub 포지션 vs 실제 브로커 계좌 포지션을 대조하고, 불일치 자산은 `warning` 상태로 응답 리스트에서 마스킹한다. - 종목 스냅샷 read 계약: trex-core 가 보유 스냅샷을 read 계약(요청-응답, transport 는 dcy-006 RabbitMQ RPC)으로 제공하고, **holding 포함·pending·warning·pause 마스킹**한 전략 실행 대상 자산 리스트를 반환한다(slot overlay, OQ-4). - strategy_key 필터: 스냅샷 read 요청이 선택적 `strategy_key` 파라미터(요청 payload, dcy-006 OQ-2)를 받아, 주어지면 해당 `strategy_key` 가 등록된(= `active_strategy_id` 일치) 자산만 반환하고, 없으면 (pending·warning·pause 마스킹 후) 모든 자산을 반환한다. `strategy_key` 는 사람이 읽는 안정 유일 slug(예: `target-value-v1`), 단일 `active_strategy_id` 정확 일치(OQ-9). - 런타임 pause/resume: trex-core 가 보유한 스냅샷 universe 의 개별 항목을 사용자가 런타임에 일시 중단(pause)/재개(resume)할 수 있고, pause 된 항목은 전략 모듈의 포트폴리오 요청 결과에서 제외한다. pause 단위·제어 방식·영속성은 OQ-10. - 응답 view 모드 + envelope: 응답 base 집합을 요청 `view`(`all` 기본|`buyable`|`held`)로 선택하고 envelope(`assets[]` + `view`·`available_slots`·`degraded`·`reason` meta)로 반환한다(OQ-11/OQ-12/OQ-6). capacity 0 이어도 보유 자산은 반환, `buyable` 만 `available_slots=0` 시 `[]`. - baseline 갱신: interface-contract(read 포트/메시지 계약)·runtime-flows(스냅샷 조회 흐름, RabbitMQ RPC)·project-structure(read 어댑터) 갱신. (dcy-006 의 transport baseline 갱신과 정합.) ## 영향 범위 변경이 미칠 코드/문서/운영 범위를 명시해 REQ 정제와 batch planning에서 추정 누락을 줄인다. - 코드 영향: - 신규: `trex_core/application` read 포트, 신규 `adapters/portfolio` read 어댑터 + stub, 스냅샷 read 를 dcy-006 messaging transport(RabbitMQ RPC, `adapters/messaging`)로 노출 + 응답 스키마(transport-무관), slot overlay(점유 종목 제외) + 현금/포지션 write-응답 갱신 로직(`cash_balance_after`/`asset_quantity_after`, 옵션 D), 일일 스냅샷 스케줄러(런타임 신규), 포지션 정합 검증 + `warning` 상태, `config` 에 스냅샷 페치 시각 주입(`daemon_id` 는 portfolio-hub 토큰에서 도출 — 별도 config 불필요, OQ-7), `strategy_key` 필터(요청 payload → `active_strategy_id` 매칭), 스냅샷 항목 pause/resume 상태 저장(Postgres, portfolio_id 키, refresh 무관) + 제어 표면(CLI + dcy-006 RPC 제어 메시지) + 응답 산출 시 pause 마스킹(전역). - (dcy-006 구현 완료: `adapters/api` 제거·`adapters/messaging` 추가·`trex.snapshot.req` RPC consumer 존재. 스냅샷 read 는 이 RPC consumer 위에 portfolio-hub 종목 logical 계약을 채워 구현한다.) - 기존: capacity/slot 조회 재사용(점유 종목·가용 슬롯 판정), 브로커 계좌 포지션 조회(BrokerPort 잔고조회 = KIS 주식잔고조회 `TTTC8434R`/`VTTC8434R`, `pdno`+`hldg_qty`, symbol 집계 — 정합 검증용, OQ-8) 재사용/확장. - 문서 영향: - interface-contract/runtime-flows/project-structure 갱신. - 운영 영향: - portfolio-hub read 의존 추가(dcy-004 자격증명 재사용). 캐시 도입 시 신선도/무효화 운영 고려. # 5. 요구사항 목록 실제로 구현과 검증으로 이어질 요구사항을 기능/비기능으로 나누어 정리하고, 범위 경계를 명확히 적는다. ## 기능 요구사항 - FR-1: trex-core 가 종목 스냅샷을 read 계약(요청-응답)으로 제공한다. transport 는 dcy-006 의 RabbitMQ request-reply RPC 이며(HTTP 아님), 전략 모듈이 메시지로 소비한다. 스냅샷 단위는 daemon 이 관리하는 portfolio universe 이며, 항목당 symbol, asset_id(=portfolio_id), 현금(cash_balance), 포지션(asset_quantity), active_strategy_id, 전략파라미터(opaque JSON)를 담고 가격(price)은 제외한다. (OQ-1 Answered) - FR-2: 스냅샷 응답(전략 실행 대상 portfolio 자산 리스트)은 **portfolio-hub 에 보유된 자산을 기본 포함하며, holding 슬롯의 자산도 포함**한다(전략 모듈이 보유 포지션까지 받아 관리). 단 다음 상태의 자산은 **마스킹**하여 응답에서 제외한다: (a) 슬롯 상태 pending(미체결 in-flight), (b) 자산 상태 warning(브로커 실제 position 불일치, FR-7), (c) 사용자 pause(FR-9). 포함/마스킹 판정은 core 권위 슬롯 상태 + 정합 검증 + pause 상태를 사용하며 portfolio-hub 보유 여부보다 우선한다(slot overlay). 본 조항은 기본(default) view(`all`)의 구성 규칙이며, 응답 베이스 집합은 요청 `view` 파라미터(`all`/`buyable`/`held`, OQ-11/OQ-12)로 선택될 수 있고 마스킹은 선택된 베이스 위에 균일 적용된다. (변경 2026-06-05, OQ-4 결정: 종전 'pending·holding 점유 종목 제외, 매수 후보 리스트' → 'holding 포함, pending·warning·pause 만 마스킹'.) - FR-3: 응답은 **envelope** 로 제공한다(자산은 항상 `assets: []` 배열, null 금지; meta: `view`·`available_slots`·`degraded`·`reason`) (OQ-6). 자산 base 집합은 요청 `view` 파라미터로 선택한다(OQ-11/OQ-12) — **`all`(기본, universe 전체) | `buyable`(universe − 슬롯 점유) | `held`(holding 슬롯만)**. 마스킹(pending·warning·pause)은 전 모드 균일 적용(OQ-4; pending 은 모든 모드 마스킹이라 `held`=holding 만). capacity 는 `available_slots` meta 로 신호하며 `buyable` 은 `available_slots=0` 이면 `[]`. (종전 'capacity 0 → 매수 후보 빈 리스트'는 OQ-4 holding 포함 + 본 결정으로 대체.) - FR-4: portfolio-hub read 접근을 `PortfolioHubReadPort.get_portfolios(daemon_id)` 추상화 + portfolio-hub 어댑터(`managed_by_daemon_id` 기준) + 결정론 stub 로 제공하고, dcy-004 의 portfolio-hub 어댑터/자격증명을 재사용한다. `daemon_id` 는 portfolio-hub 자격증명/토큰에서 도출하며(OQ-7, 단일 진실원) 별도 config 값이 아니다(필요 시 whoami 1회 도출·캐시). 전략파라미터는 opaque JSON 으로 통과시킨다(trex-core 가 스키마를 해석하지 않음). - FR-5: baseline 문서(interface-contract/runtime-flows/project-structure)를 read 포트·스냅샷 read 계약(RabbitMQ RPC)·read 어댑터·일일 스케줄러에 맞게 갱신한다(dcy-006 transport baseline 과 정합). - FR-6: trex-core daemon 이 설정 가능한 시각(기본 07:30 KST, .env 로 지정)에 일일 스케줄로 `daemon_id` universe 를 페치해 스냅샷을 보유한다. universe 멤버십·전략파라미터는 일일 스냅샷으로 고정하고, 현금/포지션은 스냅샷 baseline 에 더해 **체결 시 portfolio-hub ledger write 응답의 `cash_balance_after` / `asset_quantity_after` 권위 값으로 갱신한다(옵션 D)**. (포지션/현금의 ground truth 는 portfolio-hub 이며 응답값으로 덮어써 로컬 누적 보정 drift 를 제거. portfolio-hub 가 ledger write 를 회계 단일 권위로 단일 트랜잭션 처리하고 `*_after` 를 반환하기로 확정 — A-7, portfolio-hub#8. portfolio-hub 배포(REL-003 등) 시점까지 구현 대기.) - FR-7: 스냅샷 페치 시 각 portfolio 의 portfolio-hub 포지션과 실제 브로커 계좌 포지션이 **수량 정확 일치**하는지 검증한다(symbol 단위 집계, 허용 오차 없음 — OQ-8). 불일치 자산은 `warning` 상태로 표시·응답 리스트에서 마스킹하고 해당 자산에 매매 전략을 수행하지 않으며, **다음 정합 성공 시 자동 해제**한다(영속 hold 는 pause/FR-9). 브로커 포지션은 KIS 주식잔고조회(`TTTC8434R`/`VTTC8434R`, `pdno`+`hldg_qty`)를 BrokerPort 로 조회한다. - FR-8: 스냅샷 read 요청은 선택적 `strategy_key` 파라미터(요청 payload, dcy-006 OQ-2 매핑)를 받는다. `strategy_key` 가 주어지면 해당 `strategy_key` 가 등록된(= 자산의 `active_strategy_id` 와 일치) 자산만 응답 리스트에 포함하고, `strategy_key` 가 없으면 (pending·warning·pause 마스킹 후) 모든 자산을 반환한다. 여러 전략 모듈이 같은 스냅샷 API 를 공유하되 각자 자기 책임 자산만 받는다. `strategy_key` 는 사람이 읽을 수 있는 안정 유일 slug(예: `target-value-v1`)이며 자산의 단일 `active_strategy_id` 와 대소문자 구분 정확 일치로 매칭한다(OQ-9; 다중 등록은 범위 밖). `strategy_key` 필터는 `view` 파라미터와 **직교 합성**된다(OQ-12). - FR-9: trex-core 가 보유한 스냅샷 universe 의 개별 항목을 사용자가 런타임에 일시 중단(pause)/재개(resume)할 수 있다. pause 된 항목은 전략 모듈의 포트폴리오 요청 결과(FR-2)에서 제외한다. 응답 리스트는 holding 포함을 기본으로 (pending 마스킹) ∧ (warning 마스킹) ∧ (pause 마스킹) ∧ (strategy_key 필터, 주어진 경우)의 합성으로 산출된다. pause 단위는 `portfolio_id`(=asset_id), 제어는 **CLI + dcy-006 RPC 제어 메시지**, **Postgres 영속**(재시작·일일 refresh 후 유지), **전역 적용**, resume 시 다음 요청부터 즉시 복귀(OQ-10). pause(수동·영속) vs warning(자동·일시)은 독립 overlay·OR 합성. ## 비기능 요구사항 성능, 운영성, 일관성, 자동화 안정성 등 기능 외 요구사항을 적는다. - NFR-1: 도메인 순수성 유지 — read 포트는 application 계약, 어댑터는 adapters 레이어 격리. `trex_core/domain` 은 portfolio-hub/네트워크 의존 0(import 스윕). - NFR-2: 일관성 — 점유 종목 제외/가용 슬롯 판정은 core 의 권위 슬롯 상태(강한 일관성)를 사용한다. 현금/포지션은 일일 baseline + 체결 시 portfolio-hub ledger write 응답의 `cash_balance_after` / `asset_quantity_after` 권위 값 갱신으로 유지하고(옵션 D), 스냅샷 페치 시 브로커 계좌와 정합 검증한다(불일치 자산 `warning` 제외). - NFR-3: 가용성 degrade — portfolio-hub 가 불가해 권위 cash/position 유지가 불가하면 **명시적 degraded 신호(reason code + sanitize 된 message 포함) + 신규 전략 매매 보수적 중단**(OQ-5; 빈 리스트/silent stale 금지). core 자율 보호 매도는 유지(ledger 는 outbox 로 결국 기록). dcy-006 OQ-4 와 공통 '보수적 안전' 철학. ## 범위 경계 이번 반복에서 반드시 포함할 범위와 명시적으로 제외할 범위를 구분한다. - In Scope - portfolio-hub read 포트 + 어댑터 + stub, 종목 스냅샷 read API, holding 포함·pending·warning·pause 마스킹(slot overlay, OQ-4), capacity 0 응답 재정의(OQ-11), 설정 시각 일일 스냅샷 + 현금/포지션 write-응답 갱신(옵션 D), 포지션 정합 검증 + `warning` 제외, baseline 갱신. - Out of Scope (후속 Discovery) - 매수 전략 의사결정 로직 자체(점수화·랭킹·진입 타이밍) — 전략 모듈 책임. - portfolio-hub ↔ core 양방향 reconciliation, 포지션 재구성, asset master 영속 동기화. - 매수/매도 전략 데몬, safety floor, 실계좌 go-live. # 6. 리스크/가정 목록 불확실성과 전제 조건을 분리해서 적는다. 리스크는 영향과 완화 방안을, 가정은 현재 판단의 전제를 기록한다. ## 리스크 - R-1 (Medium): stale 스냅샷 — 일일 캐시한 현금/포지션이 장중에 어긋날 수 있다. - 완화 방안(OQ-2 Answered, 옵션 D): 현금/포지션을 baseline + 체결 시 portfolio-hub ledger write 응답의 권위 cash/position 으로 갱신해 유지. daemon 이 유일 매매 주체이고 장중 입출금이 없다는 전제(A-6)로 정확. 추가로 페치 시 브로커 계좌 정합 검증(FR-7). 잔여 갭: 체결→write flush 사이에는 baseline 값이라 현금이 잠시 stale 할 수 있으나, 중복 매수 배제는 slot overlay(FR-2)가 즉시 담당. - R-2 (Medium): in-flight race — dcy-004 ledger outbox 가 eventually-consistent 라, 막 체결한 종목이 portfolio-hub 포지션에 아직 반영되기 전 중복 노출될 수 있다. - 완화 방안(OQ-4 Answered): pending 슬롯 마스킹으로 미체결 in-flight 종목을 응답에서 제외해 중복 노출을 차단한다. 체결 완료(holding)된 자산은 보유 자산으로 포함되며, 전략 모듈이 보유 상태를 보고 재매수하지 않는다(holding 자산의 보유 수량 표기 정합은 core 권위 슬롯 상태 기준 — 설계 시 확정). (종전 'pending·holding 동시 제외'에서 'pending 만 마스킹, holding 포함'으로 변경.) - R-3 (Medium): 가용성 결합 — portfolio-hub 장애가 전략 read 를 막는다. - 완화 방안(OQ-5 Answered): 권위 cash/position 유지 불가 시 명시적 degraded 신호로 신규 매매 보수적 중단(stale 매매·누적 drift 차단). core 보호 매도는 유지(outbox 로 ledger 결국 기록). 어댑터 격리(NFR-1). - R-4 (Low): 책임 경계 확장 — core 가 portfolio-hub read gateway 로 비대해질 위험. - 완화 방안: 모든 소비자가 core 를 경유하되(OQ-3 Answered), core 의 부가가치(slot overlay·현금/포지션 보정·정합 검증)로 범위를 한정하고 단순 패스스루 프록시는 지양한다. - R-5 (Medium): 포지션 drift — portfolio-hub 가 실제 브로커 계좌와 어긋나면(과거 누락·정합 오류) 스냅샷 baseline 자체가 틀린다. (옵션 D 로 전환해 core 로컬 replay 누적 drift 는 제거되었으나, portfolio-hub ↔ 브로커 계좌 간 drift 는 여전히 남는다.) - 완화 방안(FR-7, OQ-8 Answered): 스냅샷 페치 시 portfolio-hub 포지션 vs 브로커 계좌 포지션 **수량 정확 일치**(symbol 집계) 검증, 불일치 자산은 `warning` 으로 매매 제외(보수적 안전), 다음 정합 성공 시 자동 해제. 브로커 출처 = KIS 주식잔고조회. ## 가정 아직 확정되지 않았지만 현재 문서 작성의 기반이 되는 전제를 적는다. - A-1: portfolio-hub 는 daemon 인증(`auth_tokens.daemon_id`)으로 자신이 관리하는 portfolio 집합을 조회하는 read 계약을 제공한다. (근거: portfolio-hub `app/api/v1_portfolios.py`·`v1_user_info.py` 의 `auth.daemon_id` 스코프) - A-2: trex-core 는 슬롯/capacity 의 권위 단일 소스이며, 점유 종목·가용 슬롯 판정에 자기 상태를 사용할 수 있다. - A-3: 전략 모듈은 trex-core 외부 소비자이며 dcy-006 transport(RabbitMQ request-reply RPC)로 스냅샷을 받는다(HTTP 아님). logical 계약(필드/마스킹/view 모드/strategy_key)은 transport-무관. - A-4: dcy-004 의 portfolio-hub 어댑터/자격증명(.env `TREX_PORTFOLIO_HUB_*`)을 read 방향에서도 재사용한다. - A-5: portfolio-hub 의 `daemon` 은 1급 엔티티이며 portfolio 가 `managed_by_daemon_id` 로 daemon 에 귀속된다(1 daemon : N portfolio, 각 portfolio ≈ 1 asset, asset_id == portfolio_id). 전략파라미터는 portfolio-hub 가 `strategy_params` JSONB 로 보관한다. trex-core daemon 의 universe = 자신이 관리하는 portfolio 집합이다. (근거: portfolio-hub `app/domain/models.py` Daemon/Portfolio, `strategy_params` JSONB migration) - A-6: 이 trex-core daemon 은 자신이 관리하는 portfolio 의 유일한 매매 주체이며, 장중 거래 외 현금 변동(입출금)은 없다. 따라서 일일 baseline + 체결 시 portfolio-hub write 응답 갱신으로 현금/포지션을 정확히 유지할 수 있다(옵션 D 전제). (사용자 확인 2026-06-04) - A-7 (확정 2026-06-05, portfolio-hub#8): portfolio-hub 가 `POST /v1/portfolios/{portfolio_id}/ledger/trades` 응답 root 에 flat 필드 **`cash_balance_after`, `asset_quantity_after`** 2개를 비파괴 추가하기로 회신. 더 나아가 이 endpoint 를 회계 단일 권위로 만들어 **ledger row INSERT + portfolio cash/quantity UPDATE 를 단일 DB 트랜잭션(row lock)** 으로 처리한다 → trex-core 는 write 1회로 회계 완결, drift 원천 차단, read round-trip 불요. status 별: `new`(201) = 본 거래 반영 후 권위 값, `duplicate`(200) = 현재 권위 값(idempotent 정합), `409`/잔고부족 `422` = `*_after` 미정의(본문 미정). trex-core 는 이 응답값으로 스냅샷 현금/포지션을 갱신한다(옵션 D). 의존: portfolio-hub 측은 별도 Discovery(dcy-006)·release(REL-003 등)로 배포 예정 — **배포 시점까지 trex-core 구현은 대기**. 추적: https://github.com/fourblocks2020/portfolio-hub/issues/8 . (사용자 결정 2026-06-05 / portfolio-hub 회신 반영) ## 오픈 질문 답을 아직 모르는 항목을 적는다. 추정으로 덮지 말고 REQ 초안 작성 이전에 확인할 질문으로 남긴다. - OQ-1: 스냅샷의 정의 — 응답 단위와 필드. - 상태: Answered (2026-06-04) - 결정: 스냅샷 = daemon 이 관리하는 portfolio universe. 항목당 symbol, asset_id(=portfolio_id), 현금(cash_balance), 포지션(asset_quantity), active_strategy_id, 전략파라미터(opaque JSON). 가격(price)은 사용하지 않으므로 제외. 전략파라미터는 portfolio-hub 가 `strategy_params` JSONB 로 보관하므로 trex-core 는 해석 없이 opaque JSON 으로 통과시킨다. - 반영 위치: `# 3` 기대 상태, `# 5` FR-1·FR-4, `# 6` A-5. - OQ-2: 신선도/캐시 정책 — universe 페치 주기와 현금/포지션 신선도. - 상태: Answered (2026-06-04) - 결정(개정 2026-06-05, 옵션 C → 옵션 D): universe 멤버십·전략파라미터는 설정 시각(기본 07:30 KST, .env) 일일 스냅샷으로 고정. 현금/포지션은 스냅샷 baseline 에 더해 **체결 시 portfolio-hub ledger write 요청의 응답이 반환하는 변경 후 권위 cash/position 으로 갱신**한다(옵션 D). 근거: portfolio-hub 가 cash/position 의 ground truth 이므로 write 응답값으로 덮어쓰면 옵션 C 의 core 로컬 체결 replay 누적 보정에서 생길 수 있던 drift(R-5)가 발생하지 않는다. 갱신이 체결(write) 경로에서만 일어나므로 read 경로의 portfolio-hub 가용성 결합(옵션 B 의 매 요청 live 호출)도 피한다. A-6(유일 매매 주체·장중 외부 변동 없음) 전제는 유지. 의존(확정 2026-06-05, portfolio-hub#8): portfolio-hub 가 ledger write 응답에 `cash_balance_after` / `asset_quantity_after` 를 반환하고 ledger INSERT + 잔고 UPDATE 를 단일 트랜잭션으로 처리하기로 회신 — trex-core 는 write 응답값으로 스냅샷을 갱신한다. portfolio-hub 배포(dcy-006/REL-003) 시점까지 구현 대기. 체결→write flush 사이 즉시 갭의 중복 매수 배제는 slot overlay(FR-2)가 계속 담당. - 반영 위치: `# 4`, `# 5` FR-6·NFR-2·In Scope, `# 6` R-1·R-5·A-6·A-7. - OQ-3: core 경유 정책 — 모든 소비자가 trex-core 를 경유하는가, 일부는 portfolio-hub 직접 호출을 허용하는가? - 상태: Answered (2026-06-04) - 결정: 매수 전략 모듈 등 **모든 소비자는 trex-core 스냅샷 API 를 경유**한다. portfolio-hub 직접 호출은 허용하지 않는다. core 가 단일 통합 경계(자격증명·계약·slot overlay·현금/포지션 보정·정합 검증)를 일관 제공하고, 소비자는 portfolio-hub 계약/자격증명을 알 필요가 없다. - 반영 위치: `# 3` 제약사항, `# 6` R-4 완화. - OQ-4: in-flight 합성 규칙 — portfolio-hub 포지션과 core 슬롯 상태를 합쳐 응답을 만들 때의 포함/마스킹 규칙. - 상태: Answered (2026-06-05) - 결정: 응답(전략 실행 대상 portfolio 자산 리스트)은 **portfolio-hub 보유 자산을 기본 포함하고 holding 슬롯 자산도 포함**한다. 다음 상태만 **마스킹**(제외): (a) 슬롯 pending(미체결 in-flight), (b) warning(브로커 실제 position 불일치, FR-7), (c) 사용자 pause(FR-9). 우선순위: core 권위 슬롯 상태 + 정합 검증 + pause 가 portfolio-hub 보유 여부보다 우선(마스킹 조건 충족 시 보유 여부와 무관하게 제외). in-flight race(R-2)는 pending 마스킹으로 차단; 체결 완료분은 holding 으로 포함. (종전 'pending·holding 동시 제외' → 'holding 포함, pending·warning·pause 마스킹'으로 변경.) - 반영 위치: `# 3` 시나리오, `# 5` FR-2·FR-9, `# 6` R-2, `# 7` S-2. - 잔여: holding 자산의 보유 수량 표기를 core 권위 슬롯 vs 스냅샷 중 무엇 기준으로 할지(holding→ledger flush 갭의 수량 정합)는 설계 시 확정. 종전 FR-3(가용 슬롯 0 → 빈 리스트)은 'holding 포함' 변경과 충돌 → OQ-11 로 재정의. - OQ-5: 가용성 degrade 정책 — portfolio-hub 불가 시 스냅샷 read 동작. - 상태: Answered (2026-06-05) - 결정: portfolio-hub 가 불가해 **권위 cash/position 을 유지할 수 없는 상태**(옵션 D 의 ledger write `*_after` 갱신 불가, 또는 일일 universe fetch 실패)면 **신규 전략 매매를 보수적으로 중단**한다. 차단 사유는 'ledger 기록 불가'가 아니라(dcy-004 outbox 가 유실 없이 결국 기록) **stale cash/position 으로 매매 시 잘못된 사이징·누적 drift 위험**이다. - read 응답: 빈 리스트(`[]` — '후보 없음'과 모호)나 silent stale 캐시 서빙 대신 **명시적 degraded 신호**를 실어 전략이 신규 매매를 보류하게 한다. 신호는 `degraded=true` + **degraded 사유**를 포함한다 — machine-readable reason code(예: `portfolio_hub_unavailable` / `snapshot_fetch_failed` / `cash_position_stale`)로 전략이 원인별 분기 가능하게 하고, 사람용 message 로 운영 진단을 돕는다. 단 raw 예외/자격증명 등 민감정보는 노출하지 않고 sanitize 한다. - core 자율 보호 매도(손절·시간 만기, dcy-006 A-5)는 **계속 동작** — 그 체결의 ledger 도 outbox 가 복구 후 기록하므로 outage 중에도 열린 포지션은 보호된다. 즉 '중단'은 신규 전략 매매에 한정한다. - dcy-006 OQ-4(RabbitMQ degrade)와 **공통 '보수적 안전' 철학**: 임의의 핵심 의존성(portfolio-hub OR RabbitMQ) degrade 시 신규 매매 중단 + core 보호 매도 유지. - 반영 위치: `# 5` NFR-3, `# 6` R-3. - 종료 조건(충족): degrade 동작이 NFR-3/R-3 에 반영됨. - OQ-6: 빈 리스트 표현 시맨틱 — 응답이 비었을 때의 표현. - 상태: Answered (2026-06-05) - 결정: 응답은 **envelope** 로 감싸고 자산 목록은 항상 배열(`assets: []`, **null 금지**)로 준다. 빈 배열은 '항목 없음'을 그대로 의미하고, '왜 비었는지'는 envelope 의 meta 로 구분한다 — `view`(에코된 view 모드, OQ-11/12), `available_slots`(capacity 신호), `degraded`+`reason`(OQ-5). 근거: 리스트 필드에 null 을 섞으면 소비자 분기·버그 유발 → 항상 배열. 빈 리스트만으로는 capacity 0 / 전부 마스킹 / 필터 0매칭 / degrade 를 구분 못 하므로 envelope 필요(OQ-5 가 이미 `degraded`/`reason` 을 요구해 envelope 는 사실상 강제). transport 가 메시지 payload(dcy-006 RPC)라 REST status-code 가 아니라 구조화 envelope 가 자연스럽다. - 반영 위치: `# 5` FR-3 (자산 항목(FR-1)·마스킹(FR-2)은 envelope 의 `assets[]` 원소로 위치). - 종료 조건(충족): 표현 방식(envelope + `[]`)이 FR-3 에 반영됨. 정확한 envelope 필드 스키마는 OQ-12 view 파라미터와 함께 REQ/설계 단계 확정. - OQ-7: trex-core daemon 의 `daemon_id` 출처. - 상태: Answered (2026-06-05) - 결정: `daemon_id` 는 **portfolio-hub 자격증명/인증 토큰(`auth_tokens.daemon_id`)에서 도출**한다(단일 진실원). 별도 `TREX_DAEMON_ID` .env 설정값을 1차 출처로 두지 않는다 — 자격증명과 분리 설정 시 dual-source drift(잘못된 universe·auth 오류) 위험. portfolio-hub read 는 토큰으로 daemon 을 스코프하므로(A-1) trex-core 는 daemon_id 를 명시적으로 들 필요가 가급적 없고, 값이 필요하면 startup 때 whoami(portfolio-hub `v1_user_info` 류) 1회로 도출·캐시한다. .env override 는 테스트/stub 한정이며 startup 에서 토큰 daemon_id 와 일치 검증(불일치 시 fail-closed)한다. dcy-004 의 `TREX_PORTFOLIO_HUB_*` 자격증명을 재사용하므로 신규 `TREX_DAEMON_ID` 주입은 불필요. - 반영 위치: `# 4` 영향 범위(config), `# 5` FR-4. - 종료 조건(충족): daemon_id 출처가 FR-4/영향 범위에 반영됨. - OQ-8: 포지션 정합 검증 세부. - 상태: Answered (2026-06-05) - 결정: 1. **일치 기준 = 수량 정확 일치**(정수 shares, 허용 오차 없음). A-6(유일 매매 주체·장중 외부 변동 없음)상 portfolio-hub 포지션과 브로커 잔고는 동일 체결에서 파생되어 정확히 일치해야 하며, tolerance 는 실제 이상(누락 체결·외부 거래)을 가린다. 현금은 FR-7 범위 밖(수수료·세금·T+2 결제로 단순 비교 불가) — 수량만 대조. 2. **warning 회복 = 다음 정합 성공 시 자동 해제**(매 정합 사이클 재계산되는 파생 플래그, sticky 아님). raise 는 매번 로깅/alert. 영속 hold 가 필요하면 운영자 pause(FR-9)로 분리한다(자동 warning=일시 / 수동 pause=영속). 3. **브로커 포지션 출처 = KIS 주식잔고조회**(실전 `TTTC8434R` / 모의 `VTTC8434R`, `GET /uapi/domestic-stock/v1/trading/inquire-balance`)를 BrokerPort 잔고조회로 재사용. 항목당 `pdno`(종목코드) + `hldg_qty`(보유수량). paper broker 는 결정론 stub. - **symbol 단위 집계 대조**: 브로커는 단일 계좌(dcy-003 불변식 B)라 잔고가 symbol 단위 집계 → portfolio-hub per-portfolio 포지션을 symbol 로 합산해 broker per-symbol `hldg_qty` 와 대조(보통 1 portfolio:1 symbol 이나 안전하게 집계 기준). - 주의: 당일 전량매도분이 `hldg_qty=0` 으로 D-2 까지 잔고에 남을 수 있음(015 문서) → 0 수량 잔고 처리 규칙은 설계 시 확정. - 반영 위치: `# 4` 영향 범위, `# 5` FR-7, `# 6` R-5. - 참고: docs/broker/kis/rest/국내주식/주문/계좌/015_v1_국내주식-006_주식잔고조회.md - 종료 조건(충족): 정합 기준·회복 규칙·포지션 출처가 FR-7 에 반영됨. - OQ-9: 전략 ID 필터의 식별자/등록 시맨틱. - 상태: Answered (2026-06-05) - 결정: - **식별자 형태**: 전략 모듈이 넘기는 `strategy_key` 는 **사람이 읽을 수 있으면서 ID 역할을 하는 유일·안정 값**(human-readable stable slug, 예: `target-value-v1`)이다. 'display name vs opaque ID' 이원화 없이 이 slug 자체가 유일 키다. 매칭은 대소문자 구분 정확 일치(exact). - **명명(2026-06-05)**: 파라미터명을 `strategy_id` → **`strategy_key`** 로 변경한다. portfolio-hub 의 `strategy_id`(그쪽 내부 전략 엔티티 ID)와 이름이 겹쳐 혼동되므로 분리한 것이다. `strategy_key` 는 trex-core 요청 파라미터(전략 모듈 자기 식별자)이며, 그 **값은 자산의 portfolio-hub `active_strategy_id` 와 매칭**된다. - **등록 판정**: 자산의 **단일 `active_strategy_id` 와 동등 비교**(`strategy_key == active_strategy_id`). 한 자산은 한 시점에 하나의 active 전략에 귀속한다(portfolio-hub `active_strategy_id` 단일, A-5). 자산당 다중 전략 등록(예: buy/sell 분리, trex-core#1)은 **현재 범위 밖**이며 필요해지면 '등록 목록' 필드로 확장(후속). - 반영 위치: `# 5` FR-8, `# 3` 시나리오. - 종료 조건(충족): 식별자 형태·등록 판정이 FR-8 에 반영됨. - OQ-10: 런타임 pause/resume 메커니즘. - 상태: Answered (2026-06-05) - 결정: - **단위**: `portfolio_id`(= asset_id) — universe 항목 키. (symbol 은 다중 portfolio 공유 시 모호하므로 portfolio_id 기준.) - **제어 방식**: 운영자 admin 액션으로 **CLI + dcy-006 RPC 제어 메시지** 둘 다 제공(로컬 CLI + 원격 ops). config 리로드는 비채택. - **영속성**: **Postgres 영속**(기존 단일 store), portfolio_id 키. daemon 재시작 후 유지, 일일 스냅샷 refresh 와 무관(refresh 는 universe/포지션만 갱신, pause overlay 불변). - **적용 범위**: **전역** — 모든 전략 요청에서 마스킹(운영자 안전 액션, 어느 전략이 묻든 거래 금지). - **resume**: pause 는 request-time mask 라 resume 시 **다음 요청부터 즉시 응답 복귀**(일일 갱신 대기 불요). - pause(수동·영속) vs warning(자동·일시, OQ-8)은 독립 overlay 이며 OR 합성(둘 중 하나라도면 마스킹). - 반영 위치: `# 4` 영향 범위, `# 5` FR-9. - 종료 조건(충족): pause 단위·제어 방식·영속성·적용 범위가 FR-9 에 반영됨. - OQ-11: capacity 소진 시 응답 시맨틱. - 상태: Answered (2026-06-05) — 방향 확정, 파라미터 설계는 OQ-12 로 분리. - 결정: capacity(가용 슬롯)가 모두 소진됐을 때의 응답은 **고정값이 아니라 전략 모듈이 요청 시 지정하는 view 모드(파라미터)에 따라 달라진다.** 최소 3개 모드: (a) **빈 리스트 `[]`** (신규 매수 후보가 목적인 전략 — 살 게 없음), (b) **슬롯 점유 자산만** (보유 포지션 관리·매도/조절 전략 — 자기가 들고 있는 것만), (c) **portfolio 스냅샷 − 슬롯 점유 자산** (점유 안 된 잔여 universe). 같은 view 모드 개념은 capacity 0 이 아닐 때도 응답 구성을 좌우한다(이 파라미터는 strategy_key 필터(FR-8)와 별개의 두 번째 요청 파라미터). (→ OQ-12 에서 enum `all`/`buyable`/`held` 으로 확정.) - 반영 위치: `# 5` FR-2·FR-3, `# 6` OQ-12. - 잔여(→ OQ-12): 파라미터 이름/enum 값, 기본 모드(생략 시), 각 모드와 마스킹(pending·warning·pause, OQ-4)·strategy_key 필터의 합성 규칙, capacity 신호 메타 필드(OQ-6) 동반 여부. - OQ-12: 응답 view/scope 파라미터 설계. - 상태: Answered (2026-06-05) - 결정: - **파라미터**: 요청 payload `view`(dcy-006 OQ-2 매핑), enum = **`all` | `buyable` | `held`**. base 집합 — `all` = universe 전체(portfolio-hub 보유 + holding 슬롯), `buyable` = universe − 슬롯 점유(미점유 신규 진입 후보), `held` = holding 슬롯 자산만. (1차안의 `remainder` 는 `buyable` 과 동일이라 drop.) - **기본 모드**: 생략 시 **`all`**(OQ-4/FR-2 holding 포함·FR-8 전체와 정합). - **마스킹 직교·균일**: 모드는 base 집합만 고르고, 마스킹(pending·warning·pause)은 전 모드 균일 적용. **pending 은 모든 모드 마스킹** → `held` = holding 만(미체결 제외). warning/pause 도 전 모드 마스킹. - **strategy_key 직교**: `view` 와 `strategy_key`(FR-8)는 독립 합성 — `assets = base(view) − mask(pending·warning·pause) − (strategy_key 필터, 주어지면)`. - **capacity**: envelope 에 `available_slots` 항상 포함(OQ-6). `buyable` 은 capacity-gated — `available_slots=0` 이면 `[]`(OQ-11 정합). `held` 엔 정보성. - **전략 타입 매핑(trex-core#1)**: `buyable`=매수, `held`=position/매도, `all`=통합. - 반영 위치: `# 5` FR-2·FR-3·FR-8. - 종료 조건(충족): 파라미터 스펙·기본 모드·합성 규칙이 FR-2/FR-3/FR-8 에 반영됨. ### 오픈 질문 처리 기준 - `Open`: 아직 답이 없고 다음 단계 진행에 영향이 있는 상태다. 문서에 유지한다. - `Answered`: 답이 확정된 상태다. 관련 본문/요구사항에 반영한 뒤 오픈 질문 목록에서는 제거하거나 resolved 기록으로 이동한다. - `Deferred`: 지금 단계에서 결정하지 않고 다음 단계 또는 후속 이슈로 넘기는 상태다. 넘길 대상과 이유를 반드시 적는다. - `Dropped`: 더 이상 유효하지 않은 질문이다. 삭제 사유를 짧게 남기고 목록에서 제거한다. - 처리 원칙 1: 질문만 남기지 말고 반드시 `상태`, `처리 방안`, `종료 조건`을 함께 적는다. - 처리 원칙 2: 답이 나온 질문은 본문 반영 없이 단순 삭제하지 않는다. 반영 위치를 먼저 갱신한다. - 처리 원칙 3: High 영향 질문은 `Deferred` 처리 후 처리 방안에 REQ 인계 추적 방식을 명시한다. # 7. 초기 성공 기준 이 변경이 성공했다고 판단할 기준을 측정 가능하게 정의한다. 가능하면 데이터 출처까지 연결한다. - S-1: stub portfolio-hub read 로, 스냅샷 API 가 portfolio-hub 종목을 포함한 전략 실행 대상 자산 리스트를 반환한다. - S-2: holding 슬롯 자산은 응답에 포함되고, pending 슬롯 자산은 마스킹되어 제외된다(slot overlay). - S-3: 가용 슬롯 0 일 때 `view=all`/`held` 는 보유 자산을 계속 반환하고 `view=buyable` 은 `[]` 를 반환하며, `available_slots=0` meta 가 신호된다(OQ-11/OQ-12). - S-4: 도메인 import 스윕에서 portfolio-hub/네트워크 의존이 0 으로 유지된다. - S-5: interface-contract/runtime-flows/project-structure 가 read 레이어와 정합한다. - S-6: 스냅샷 페치 시 portfolio-hub 포지션과 브로커 계좌 포지션이 불일치하는 자산이 `warning` 으로 표시되고 응답 리스트에서 마스킹된다. - S-7: `strategy_key` 를 넣어 호출하면 해당 `strategy_key` 가 등록된 자산만, 생략하면 전체 자산이 반환된다. - S-8: 자산을 런타임에 pause 하면 응답 리스트에서 마스킹되고, resume 하면 다시 포함된다. - S-9: portfolio-hub 가 불가해 권위 cash/position 유지가 불가하면 응답에 `degraded=true` + reason code 가 실려 신규 매매 보류를 신호한다(OQ-5). - S-10: 요청 `view`(`all`/`buyable`/`held`)별로 base 집합이 다르고 마스킹(pending·warning·pause)은 전 모드 균일 적용된다(OQ-12). - S-11: 응답이 `assets`(항상 배열, 비면 `[]`·null 아님) + meta(`view`·`available_slots`·`degraded`·`reason`) envelope 형태다(OQ-6). - S-12: 별도 `TREX_DAEMON_ID` 없이 portfolio-hub 토큰에서 도출한 daemon_id 로 universe 를 조회한다(OQ-7). ## 측정 방식 및 데이터 출처 각 성공 기준을 어떻게 측정할지와 어떤 데이터나 문서를 근거로 쓸지 적는다. - S-1 측정 방식: stub read 통합테스트(스냅샷 응답) / 데이터 출처: 테스트 결과, 응답 본문. - S-2 측정 방식: 슬롯 holding/pending 진입 후 스냅샷 호출 테스트 / 데이터 출처: 응답 리스트(holding 포함·pending 마스킹) vs 슬롯 상태. - S-3 측정 방식: 슬롯 소진(capacity 0) 후 스냅샷 호출 테스트 / 데이터 출처: 응답 리스트·capacity 신호 메타(OQ-11 확정안 기준). - S-4 측정 방식: 정적 import 스윕 / 데이터 출처: `trex_core/domain` import 목록. - S-5 측정 방식: 문서-코드 정합 점검(리뷰) / 데이터 출처: baseline 문서 vs 실제 메시지 계약(RPC)/read 포트. - S-6 측정 방식: 포지션 불일치 주입 후 스냅샷 페치 테스트 / 데이터 출처: 자산 `warning` 상태·응답 리스트. - S-7 측정 방식: `strategy_key` 유/무 호출 비교 테스트 / 데이터 출처: 응답 리스트 vs 자산 `active_strategy_id`. - S-8 측정 방식: pause→호출→resume→호출 시퀀스 테스트 / 데이터 출처: 응답 리스트 포함/제외 여부. - S-9 측정 방식: portfolio-hub 불가(권위 유지 불가) 주입 후 호출 테스트 / 데이터 출처: 응답 `degraded`·`reason` 필드. - S-10 측정 방식: `view` 값별 호출 비교 테스트 / 데이터 출처: 응답 base 집합 vs 슬롯 상태·마스킹. - S-11 측정 방식: 응답 스키마 검증(빈 응답 포함) / 데이터 출처: 응답 envelope 구조(`assets` 타입·meta 필드). - S-12 측정 방식: `TREX_DAEMON_ID` 미설정 기동 후 universe 조회 테스트 / 데이터 출처: 토큰 도출 daemon_id vs 조회된 portfolio 집합. # 8. REQ로 넘기기 전 확인 체크 REQ 초안 작성 단계로 넘기기 전에 무엇이 확정되어야 하는지 확인한다. 미완료 항목은 후속 결정이 필요함을 뜻한다. - [x] 범위(In Scope/Out of Scope) 확정 — 특히 전략 의사결정 로직·reconciliation 제외 - 기준: 운영자가 read 슬라이스 경계를 확인했다. - [x] 성공 지표의 측정 방식/데이터 출처 확정 - 기준: S-1~S-12 측정 방식·출처 명시(본 문서 # 7). - [x] High 영향 리스크 우선순위 및 해소 책임자 지정 - 기준: 본 Discovery 의 리스크는 현재 전부 Medium/Low. 신선도(R-1)·race(R-2)·가용성(R-3) 처리 방침을 운영자가 확인. - [x] 모든 오픈 질문(OQ) `Open` 상태 없음 확인 - 기준: OQ-1~OQ-12 이 전부 Answered/Deferred 처리(현재 OQ-1~OQ-12 전부 Answered). - [x] Freeze 확정 담당자(CONFIRMED_BY) 지정 - 기준: 운영자가 confirm-discovery 시점에 CONFIRMED_BY 를 지정한다. # 9. 파일 처리 결과 이 Discovery 문서 생성/갱신 결과와 참조 문서를 기록한다. 이후 추적과 리뷰 시 근거로 사용한다. - 처리 결과: 생성 - 생성 경로: docs/discovery/dcy-005_20260604_portfolio-hub-asset-snapshot-api.md - 참조 문서: - docs/discovery/dcy-004_20260604_portfolio-hub-ledger-integration.md (write 방향 — 본 Discovery 가 read 방향으로 이어받음) - docs/discovery/dcy-006_20260605_strategy-module-rabbitmq-transport.md (confirmed — 스냅샷 read 의 전달 transport(RabbitMQ RPC, HTTP 0). 본 Discovery 는 logical 계약, dcy-006 은 transport) - docs/interface-contract.md, docs/runtime-flows.md, docs/project-structure.md (baseline — 본 Discovery 가 갱신 요구) - trex-orbit `app/clients/portfolio_client.py` (get_assets/get_portfolios read 계약 참고) # 10. 사용자 결정 필요 항목 요약 사용자나 승인자가 결정해야 하는 항목만 모아 정리한다. 결정, 확인, 데이터 요청을 구분해서 적는다. ## DECIDE - ~~스냅샷 응답 단위/필드~~ → 결정됨: daemon 관리 portfolio universe(symbol/asset_id/현금/포지션/active_strategy_id/전략파라미터 opaque JSON), 가격 제외 (OQ-1, 2026-06-04) - ~~신선도 정책~~ → 결정됨: universe·전략파라미터 설정 시각 일일 캐시 + 현금/포지션 baseline + 체결 시 portfolio-hub write 응답의 권위 cash/position 갱신(옵션 D) (OQ-2, 2026-06-04 / 옵션 C→D 개정 2026-06-05) - ~~daemon_id 주입~~ → 결정됨: portfolio-hub 자격증명/토큰에서 도출(단일 진실원), 별도 `TREX_DAEMON_ID` 불필요; .env override 는 테스트 한정 + 토큰 일치 fail-closed (OQ-7, 2026-06-05) - ~~빈 표현 시맨틱~~ → 결정됨: envelope + `assets: []`(null 금지), meta(`view`·`available_slots`·`degraded`·`reason`)로 의미 분리 (OQ-6, 2026-06-05) - ~~capacity 0 시 응답 재정의~~ → 결정됨: 응답 형태를 전략 모듈 요청 view 모드 파라미터로 선택(빈 `[]` / 슬롯 점유 자산만 / portfolio−점유) (OQ-11, 2026-06-05) - ~~응답 view/scope 파라미터~~ → 결정됨: `view`=`all`(기본)|`buyable`|`held`, strategy_key 직교 합성, 마스킹 전 모드 균일(`held`=holding만), `available_slots` 메타(buyable은 0이면 `[]`) (OQ-12, 2026-06-05) - ~~전략 ID 시맨틱~~ → 결정됨: 사람이 읽는 안정 유일 slug(예: `target-value-v1`), 자산의 단일 `active_strategy_id` 와 정확 일치; 다중 등록은 범위 밖 (OQ-9, 2026-06-05) - ~~pause/resume 메커니즘~~ → 결정됨: portfolio_id 단위, CLI + dcy-006 RPC 제어, Postgres 영속(refresh 무관), 전역, 즉시 복귀 (OQ-10, 2026-06-05) ## CONFIRM - ~~read 슬라이스 범위 경계~~ → 확정됨(2026-06-06, Justin): In Scope(portfolio-hub read 포트/어댑터·스냅샷 read 계약·마스킹·view 모드·옵션 D·정합 검증·baseline) / Out of Scope(전략 의사결정 로직·reconciliation·safety floor) 확인 - ~~core 경유 정책~~ → 결정됨: 모든 소비자 trex-core 경유, portfolio-hub 직접 호출 금지 (OQ-3, 2026-06-04) - ~~in-flight 합성/제외 규칙~~ → 결정됨: holding 포함, pending·warning·pause 마스킹, core 권위 우선 (OQ-4, 2026-06-05) - ~~가용성 degrade 정책~~ → 결정됨: 권위 cash/position 유지 불가 시 명시적 degraded 신호 + 신규 매매 보수적 중단, core 보호 매도 유지 (OQ-5, 2026-06-05). dcy-006 OQ-4 와 공통 보수적 안전. ## DATA - ~~portfolio-hub read 계약 확인~~ → 검증됨(2026-06-06, confirm-discovery): portfolio-hub `app/api/v1_portfolios.py` `list_portfolios_v1`(`GET`)이 `managed_by_daemon_id=auth.daemon_id` 로 daemon universe 스코프 조회를 제공하고, `PortfolioItem` 에 `cash_balance`·`asset_quantity`·`managed_by_daemon_id` 포함. 전략파라미터는 `strategy_params` JSONB(A-5). - ~~portfolio-hub write 응답 계약 확장~~ → 확정됨(2026-06-05, portfolio-hub#8): `POST /v1/portfolios/{portfolio_id}/ledger/trades` 응답 root 에 flat 필드 `cash_balance_after`, `asset_quantity_after` 추가 + ledger INSERT/잔고 UPDATE 단일 트랜잭션(회계 단일 권위). status 별 `*_after` 의미 정의(new=반영 후, duplicate=현재 권위, 409/422=미정). portfolio-hub 측은 dcy-006/REL-003 으로 배포 예정 — 배포 시점까지 trex-core 구현 대기. 추적: https://github.com/fourblocks2020/portfolio-hub/issues/8 (옵션 D 의존, A-7) # 11. Discovery Freeze REQ 초안 작성 인계 시점의 확정 플래그와 섹션 참조만 기록한다. 내용 재작성 없이 원본 섹션을 링크로 대신한다. ## 섹션 참조 - 범위: [# 5. 요구사항 목록 > 범위 경계](#5-요구사항-목록) - 리스크/오픈 질문: [# 6. 리스크/가정 목록](#6-리스크가정-목록) - 성공 기준: [# 7. 초기 성공 기준](#7-초기-성공-기준) - 인계 전 확인: [# 8. REQ로 넘기기 전 확인 체크](#8-req로-넘기기-전-확인-체크) - 사용자 결정 필요: [# 10. 사용자 결정 필요 항목 요약](#10-사용자-결정-필요-항목-요약) ## Freeze 플래그 - Handoff Decision: 승인 - Handoff Rationale: OQ-1(스냅샷 정의)·OQ-2(신선도, 옵션 D — portfolio-hub#8 write 응답 `*_after` 확정)·OQ-3(core 경유 정책)·OQ-4(in-flight 합성 — holding 포함, pending·warning·pause 마스킹)·OQ-5(가용성 degrade — 권위 cash/position 유지 불가 시 degraded 신호+보수적 중단)·OQ-6(빈 표현 — envelope + `assets: []`/meta)·OQ-7(daemon_id — 토큰 도출 단일 진실원)·OQ-8(정합 — 수량 정확 일치·symbol 집계·자동 해제·KIS 잔고조회)·OQ-9(전략 ID — 사람이 읽는 안정 slug·단일 active 일치)·OQ-10(pause — portfolio_id·CLI+MQ·Postgres 영속·전역·즉시 복귀)·OQ-11(capacity 0 응답 — view 모드 파라미터)·OQ-12(view 파라미터 — `all`/`buyable`/`held` + envelope) **전부 Answered**. 남은 인계 전 항목은 범위 경계 확인(운영자)과 CONFIRMED_BY 지정이며, confirm-discovery 에서 확정 후 REQ Drafting 으로 인계한다. - Ready for REQ Drafting: true - Confirmed By: Justin - Confirmed At (KST): 2026-06-06 00:08