# Trex Core Interface Contract - Baseline Mode: mixed - Baseline Seed: .stage-pilot/bootstrap/baseline.yaml - Status: draft - Owner: project maintainers - Last Updated (KST): 2026-06-04 (BAT-006) - Source Discovery / Batch: bootstrap-baseline (pre-Discovery); HTTP API = dcy-002 / BAT-002 - Primary Runtime: daemon (HTTP API) + library (slot 도메인) ## Project Summary - trex 제품군의 공용 베이스로, 첫 도메인으로 trex-orbit 의 일일 거래 슬롯 생명주기(slot) 순수 도메인을 storage-agnostic 하게 이식한 코어다. ## Primary Domain - 자동매매 일일 거래 슬롯 생명주기 도메인 (equity trading slot lifecycle) ## Purpose - This document captures the approved external and cross-boundary interface contracts for the project. - It is a cross-cutting reference for REQ drafting, batch design, implementation, verification, and handoff. ## Scope - Covers the HTTP `/v1` API(슬롯 코어 데몬, BAT-002), in-process domain-service APIs, and the persistence boundary Protocol. - Does not duplicate full business requirements already owned by REQ documents. - Does not replace low-level code references or generated API artifacts. ## Interface Summary - HTTP `/v1` API :: 슬롯 코어 데몬이 노출, 매수·매도 전략 데몬이 호출(REQ-005/007/008) - SlotService :: 슬롯 생명주기 도메인 서비스(전이/사전할당/가용성/재진입 카운트) - SlotRepository (Protocol) :: 도메인 ↔ 저장소 경계 계약 - BrokerPort (Protocol) :: 단일 브로커 세션 경계(submit/poll/cancel) + `OrderRequest`/`OrderAck`/`FillStatus` - LedgerPort (Protocol) :: 외부 원장(portfolio-hub) 기록 경계 + `LedgerTrade`/`LedgerWriteResult`/예외 (BAT-006) - LedgerOutbox (Protocol) + LedgerWriter :: 체결 → 원장 기록의 outbox/재시도/멱등 (BAT-006) ## Interface: `HTTP /v1` (슬롯 코어 데몬) - Type: http-api - Consumers / Producers: 매수·매도 전략 데몬(클라이언트) → trex-core 슬롯 코어 데몬(서버). FastAPI+uvicorn. - Purpose: 전략이 intent 를 제출하고 holding 스냅샷/capacity 를 조회. 코어가 단독 admission + 슬롯 전이(단일 writer). - Stability: experimental (v1, 슬라이스 1) | 엔드포인트 | 입력 | 출력 | 에러/거부 | |---|---|---|---| | `GET /v1/healthz` | — | `{status:"ok"}` 200 | — | | `POST /v1/intents/buy` | intent_id, trading_date, symbol, qty?, allocated_capital?, entry_price?, strategy_id?, sell_strategy_id?, portfolio_id?, forced_close_at? | `{intent_id, status:admitted|rejected, slot_id?, reason?}` | reject: `no_idle_slot`, `idle_slot_contended` | | `POST /v1/intents/sell` | intent_id, slot_id, reason?, sell_strategy_id? | `{intent_id, status, slot_id?, reason?}` | reject: `slot_not_found`, `slot_not_holding:`, `slot_state_changed` | | `GET /v1/intents/{intent_id}` | path intent_id | `{intent_id, status, slot_id?, reason?}` | 404 intent not found | | `GET /v1/slots?status=&trading_date=` | query | `[SlotSnapshot]` | — | | `GET /v1/capacity?trading_date=&total_slots=` | query | `{idle_count, holding_count, pending_count, total_slots, scarcity_ratio}` | — | ### Compatibility / Invariants - 멱등: `intent_id` dedup(intent_log UNIQUE gate). at-least-once 재전송에도 슬롯 1회만 전이; 재조회는 `GET /v1/intents/{id}`. - 단일 writer: 모든 슬롯 쓰기는 코어의 SlotService(advisory lock)만. 전략은 제안만, 슬롯/브로커 직접 접근 금지. - 동기 커밋: 수락 시 admission+전이를 동기 커밋 후 admit/reject 응답(완전 비동기 outbox 는 브로커 Discovery Deferred). ## Interface: `SlotService` - Type: internal-service - Consumers / Producers: 상위 애플리케이션(예: trex-orbit 류 매매 데몬)의 매수/매도/주문 결정 경로 및 운영자 도구 - Purpose: 슬롯 상태 전이를 레이스-프리하게 수행하고 일일 슬롯 가용성/재진입 카운트를 계산한다. - Stability: internal ### Inputs - preallocate_daily_slots(trading_date, count) :: 거래일 idle 슬롯 멱등 사전 할당 (count > 0) - transit_slot(slot_id, from_state, to_state, reason, actor, **fields) :: advisory lock 하 원자 상태 전이 - get_today_entry_count(trading_date, symbol) :: 재진입 정책 입력 조회 - compute_slot_capacity(trading_date, total_slots) :: 가용성 스냅샷 계산 (total_slots > 0) ### Outputs - Slot :: 전이 후 슬롯 행 스냅샷 - SlotCapacity :: idle/holding/pending 카운트 + scarcity_ratio - int :: 사전 할당 수 / 재진입 카운트 - slot_transition_log append :: 모든 전이의 append-only 감사 기록(부수 효과) ### Error Contract - SlotTransitionError :: 허용되지 않은 (from_state, to_state) 전이 - SlotStateMismatchError :: read-write 사이 상태 변동(레이스 감지) - SlotNotFoundError :: 존재하지 않는 slot_id - ValueError :: count <= 0 또는 total_slots <= 0 ### Compatibility Rules - 6 상태 / 8 전이 패밀리 계약을 깨는 변경은 breaking 으로 취급한다. - 상태 변경 API 는 transactional_advisory_lock 안에서만 수행하고 lock 보유 중 외부 I/O 를 금지한다. ## Interface: `SlotRepository (Protocol)` - Type: internal-service - Consumers / Producers: SlotService(소비) ↔ 저장소 구현체(InMemorySlotRepository / 향후 PostgreSQLSlotRepository) - Purpose: 도메인을 특정 저장소 기술에서 분리하는 영속화 경계 계약. - Stability: internal ### Inputs - transactional_advisory_lock(lock_id) :: 단일 트랜잭션 + advisory lock 컨텍스트 매니저 - pre_allocate_slots / fetch_slot / find_idle_slot / update_slot :: daily_slot CRUD - append_transition_log :: append-only 전이 로그 - increment_symbol_entry / get_symbol_entry_count :: 재진입 카운터 - count_slots_by_status :: 상태별 집계 ### Outputs - Slot / SlotTransitionRecord :: 도메인 모델 - dict[SlotState, int] :: 상태별 카운트 - int :: 재진입 카운트 / 사전 할당 수 ### Error Contract - SlotNotFoundError :: update 대상 row 부재 ### Compatibility Rules - 구현체는 advisory lock 직렬화와 append-only 전이 로그 의미를 반드시 보장한다. - 구현체는 lock 보유 중 외부 I/O 를 수행하지 않는다. ## Interface: `LedgerPort` (BAT-006) - Type: external-service (HTTP, portfolio-hub) - Consumers / Producers: `LedgerWriter`(소비) → `PortfolioHubLedger` 어댑터 또는 `InMemoryLedger` stub - Purpose: 체결을 외부 회계/포지션 원장에 기록한다. orbit `post_ledger_trade` 패턴. - Stability: experimental (v1) ### Inputs - record_trade(trade: LedgerTrade) :: 단일 체결을 원장에 기록 (`execution_id` 멱등) ### `LedgerTrade` Fields - asset_id :: str :: ledger path param (= 슬롯 `portfolio_id`, dcy-004 OQ-1) - order_id :: str :: 로컬 주문 식별자 (`broker_order_id`) - execution_id :: str :: 결정적 합성 (paper: `paper:{broker_order_id}:{filled_qty}`, KIS: `kis-daily-ccld:{ord_dt}:{ord_gno_brno}:{odno}:{tot_ccld_qty}`) - side :: 'buy' | 'sell' - qty / price :: float :: 체결 수량 / 평단 - fee :: float :: `BrokerPort.calculate_fee(side, qty*price)` 결과 - tax :: float :: 기본 0 (정밀 모델 후속) - executed_at :: datetime? - correlation_id (derived) :: `{asset_id}:{order_id}:{execution_id}` — 로컬 dedup 키 ### Outputs / Outcomes - `LedgerWriteResult.NEW` :: 새 원장 엔트리 적재 - `LedgerWriteResult.DUPLICATE_OK` :: 같은 execution_id 가 이미 기록되어 있음(서버 멱등) ### Error Contract - `LedgerConflictError` :: portfolio-hub 409 — 운영자 해소 대상(자동 재시도 중단) - `LedgerRetryableError` :: 5xx / timeout / network — outbox 가 재시도 ### portfolio-hub Endpoint (어댑터 구현) | HTTP | URL | Body | 응답 | |---|---|---|---| | POST | `/v1/assets/{asset_id}/ledger/trades` | `side, qty, price, fee, tax, executed_at, execution_id, order_id` | 2xx OK(`duplicate:true` 면 server-side dedup), 409 conflict, 5xx/network → retryable | ## Interface: `LedgerOutbox` Protocol + `LedgerWriter` (BAT-006) - Type: internal-service (durable work-item store + drainer) - Consumers / Producers: `ExecutionService`(생산자, fill 시 enqueue) → `LedgerWriter`(소비자, 백그라운드 워커) - Purpose: 체결 → 원장 기록을 슬롯 전이와 분리해 무유실 + 멱등으로 처리한다. correlation_id 로 outbox 적재 dedup. - Stability: internal ### Inputs - enqueue(trade: LedgerTrade) :: `correlation_id` 키로 멱등 적재 - list_pending(limit) :: FIFO pending work item - get(correlation_id) :: 단일 row 조회 - mark_outcome(correlation_id, outcome, error?) :: outcome 기록 + 상태 전이 ### Outcomes (`OutboxOutcome`) - `completed` :: 어댑터 NEW - `duplicate_completed` :: 어댑터 DUPLICATE_OK 또는 로컬 dedup 히트 - `retryable_failure` :: 다음 tick 재시도 (status 는 `pending` 유지) - `conflict_blocked` :: 409, 자동 재시도 중단(운영자 해소) ### WorkItem Status - pending → completed (성공/중복) - pending → conflict_blocked (409) - pending → pending (retryable, attempts++) ### Persistence - PostgreSQL: `ledger_outbox(correlation_id PK, asset_id, order_id, execution_id, side, qty, price, fee, tax, executed_at, status, attempts, last_outcome, last_error, created_at, updated_at)` — `ON CONFLICT (correlation_id) DO NOTHING` 이 outbox dedup 게이트. ## `FillStatus` 확장 (BAT-006 / REQ-018) - `state, filled_qty, avg_price, raw` 외에 다음 필드를 운반한다. - `execution_id :: str?` :: 결정적 합성(브로커별 규칙). filled 상태에서 필수. - `fee :: float` :: BrokerPort `calculate_fee` 결과(기본 0). - `tax :: float` :: 기본 0(정밀 모델 후속). ## Shared Constraints - 도메인은 외부 의존(브로커, DB 드라이버 등)을 직접 호출하지 않는다(순수 도메인 경계). - 모든 상태 변경은 단일 advisory lock(SLOT_ADVISORY_LOCK_ID)으로 직렬화한다. ## Current Gaps / Planned Changes - 영속화 구현체(PostgreSQL)와 그에 딸린 SQL 스키마/마이그레이션은 이번 이관 범위 밖이며 향후 도입 예정. - 외부 HTTP API / CLI 표면 없음(trex-orbit 의 `trex slot` CLI 미이식). - 패키지 네임스페이스 결정(plan.md OQ-1)에 따라 import 경로 계약이 확정된다. ## Update Triggers - Update this document when an externally visible API, event, CLI, or file contract changes. - Update this document when a batch changes compatibility, validation, or error behavior. - Update this document when a new external consumer or producer is introduced. ## Change Log - 2026-06-03 :: bootstrap-baseline 으로 초기 인터페이스 계약 baseline 생성(SlotService + SlotRepository Protocol). - 2026-06-03 :: BAT-002 — HTTP `/v1` API 계약(데몬, 6 엔드포인트) + 멱등/단일writer 불변식 추가(REQ-007/008/009). - 2026-06-04 :: BAT-004 — 브로커 집행 레이어 반영(BrokerPort/paper·KIS 어댑터, execution 루프, broker_order_id 실행큐·멱등, reconciliation) (REQ-011~015). - 2026-06-04 :: BAT-006 — LedgerPort/LedgerTrade/LedgerWriteResult 계약, LedgerOutbox/LedgerWriter, portfolio-hub `/v1/assets/{asset_id}/ledger/trades` 엔드포인트 + 409→`LedgerConflictError`, FillStatus.execution_id/fee/tax 확장 (REQ-017~020).