Files
trex-core/docs/interface-contract.md
T

45 KiB

Trex Core Interface Contract

  • Baseline Mode: mixed
  • Baseline Seed: .stage-pilot/bootstrap/baseline.yaml
  • Status: draft
  • Owner: project maintainers
  • Last Updated (KST): 2026-07-15 (BAT-057 — operator broker truth hotfix baseline alignment)
  • Source Discovery / Batch: bootstrap-baseline (pre-Discovery); HTTP API = dcy-002 / BAT-002; operator broker truth = dcy-037 / BAT-057
  • 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

  • RabbitMQ trex.slots.req :: 전략 모듈/운영자의 슬롯 읽기 RPC (request-reply, BAT-035 — REQ-028 trex.snapshot.req 대체)
  • RabbitMQ trex.intent.req :: 전략 모듈의 매수/매도 admission RPC (request-reply, BAT-007)
  • RabbitMQ trex.events :: 체결/슬롯 상태 이벤트 topic exchange (pub/sub, BAT-007)
  • RabbitMQ trex.control.req :: 운영자 제어 RPC — pause/resume/list + kill_switch/resume_kill_switch (request-reply, BAT-012; 구 trex.pause.req 확장)
  • trex-core CLI :: 운영 명령 — pause/resume/paused(direct-DB, BAT-008) + universe/slots/enter/exit(데몬 RPC, dcy-016/BAT-020) + cancel(pending 취소 백스톱, dcy-022/BAT-032)
  • 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)
  • MessagingTransport (Protocol) :: RabbitMQ / InMemory stub transport 경계 + TransportHealth enum (BAT-007)
  • PortfolioHubReadPort (Protocol) :: portfolio-hub universe read 경계 + PortfolioItem/PortfolioHubUnavailableError (BAT-008)
  • SnapshotStore + PauseStore + SnapshotScheduler :: in-memory snapshot + durable pause overlay + 일일 refresh (BAT-008)

BAT-007 에서 HTTP /v1 API 표면이 0 으로 제거됨(REQ-025). 이전 baseline 의 HTTP /v1 인터페이스 섹션은 더 이상 유효하지 않으며 본 인터페이스 인덱스에서 제거되었다. 전략-facing 통신은 위의 3 RabbitMQ 표면이 전부다.

Interface: RabbitMQ messaging (전략 모듈 ↔ trex-core, BAT-007)

  • Type: amqp-messaging
  • Consumers / Producers: trex-core daemon ↔ 전략 모듈(외부 소비자). RabbitMQ broker(TREX_RABBITMQ_URL) 가 transport.
  • Purpose: HTTP /v1 API 를 대체하는 메시지 기반 통신. 코어가 단독 admission + 슬롯 전이(단일 writer 불변식 A) 를 유지.
  • Stability: experimental (BAT-007)

Topology

종류 이름 타입/속성 용도
큐 trex.slots.req durable 슬롯 RPC 요청 큐 (read-only) — REQ-028 trex.snapshot.req 대체(BAT-035)
큐 trex.intent.req durable, x-dead-letter-exchange=trex.events.dlq intent admission RPC 요청 큐
익스체인지 trex.events topic, durable 체결/슬롯 상태 이벤트 발행
익스체인지 trex.events.dlq fanout, durable 처리 실패/unroutable 메시지 격리
큐 trex.events.dlq durable DLQ 수집기 큐 (trex.events.dlq 익스체인지 바인딩)
큐 (전략별) trex.events.strategy.{strategy_id} durable, x-message-ttl=3.6e6, x-max-length=10000, x-overflow=drop-head 전략 모듈별 이벤트 수신 큐 (*.{strategy_id}.# 바인딩)

Patterns

패턴 표면 요청 속성 응답
슬롯 RPC trex.slots.req correlation_id, reply_to=amq.rabbitmq.reply-to reply-to 큐로 {slots[], degraded, reason, message} (REQ-073/074, BAT-035)
intent RPC trex.intent.req correlation_id = intent 멱등키, reply_to=amq.rabbitmq.reply-to, delivery_mode=2 reply-to 큐로 `{intent_id, status:admitted
update_thresholds trex.intent.req action="update_thresholds", reply_to reply-to 큐로 `{status:updated
이벤트 pub/sub trex.events (topic) routing key {event_type}.{strategy_id}.{symbol} 전략 큐가 자기 strategy_id 패턴으로 바인딩

Message contracts

trex.slots.req 요청 payload (REQ-073 AC-1, BAT-035; REQ-116 BAT-055)

{ "filter":   "all" | "inactive" | "pending" | "holding" | "error",  # default "all"
  "spy":      true | false,  # default false; true = watchdog 갱신 안 함(운영자 조회)
  "strategy": str  # optional (REQ-116 BAT-055) — exact-match on strategy_name
}
  • filter: 서버 측 슬롯 status 필터. "inactive" 는 buyable 슬롯(universe에 바인딩된 매수 가능 슬롯). 미인식 값은 "all" 로 처리.
  • spy=true: 응답에 포함된 holding 슬롯의 워치독 last_seen_at 을 갱신하지 않는다(운영자 CLI 조회가 전략 생존 신호로 오인되지 않도록 — REQ-034 CHG-01).
  • strategy (REQ-116 additive): 지정 시 strategy_name == strategy 인 슬롯 행만 반환. 대소문자 exact-match. 미지정 시 기존과 완전 동일. filter 와 AND 조합. 매칭 없으면 slots=[](에러 없음). available_slots 는 strategy 필터와 무관하게 인스턴스 전체 기준으로 계산(REQ-073 AC-6 불변).

코어가 reply-to 큐로 보내는 응답 payload:

{
  "slots": [
    { "slot_id": "str (UUID)",
      "status": "inactive | pending_buy | pending_sell | holding | error",
      "symbol": "str | null",
      "qty": "float | null",             // slot mirror (REQ-062)
      "asset_quantity": "float | null",  // hub authority (REQ-062)
      "avg_price": "float | null",
      "entry_price": "float | null",
      "portfolio_id": "str | null",
      "forced_close_at": "ISO-8601 | null",
      "universe_detached": "bool",
      "cash_balance": "float | null",    // hub authority
      "cash_estimated": "bool",          // true = pending ledger outbox delta 반영
      "warned": "bool",                  // qty != asset_quantity 시 true (REQ-062)
      "watchdog": {                      // null if watchdog store unavailable
        "expired": "bool",
        "last_seen": "ISO-8601 | null"
      },
      "paused": "bool",
      "strategy_name": "str | null",     // REQ-116 additive (BAT-055) — portfolio-hub PortfolioItem 출처
      "strategy_params": "dict",         // REQ-116 additive (BAT-055) — portfolio-hub opaque dict; {} if absent
      "markets": ["str"]                 // REQ-116 additive (BAT-055) — portfolio-hub markets list; [] if absent
    }
  ],
  "degraded": "bool",
  "reason": "portfolio_hub_unavailable | snapshot_fetch_failed | cash_position_stale | null",
  "message": "str | null"              // sanitized — no credentials/raw exceptions
}

필드 소스 테이블 (REQ-074 AC-1, REQ-116 추가):

필드 권위 소스 비고
slot_id, status, avg_price, entry_price, forced_close_at, universe_detached slot (core) 슬롯 row 직접
qty slot (core) 슬롯 mirror — broker position
symbol, portfolio_id slot (core) 자산 바인딩
asset_quantity hub (SnapshotStore) portfolio-hub 권위값 (REQ-062)
cash_balance hub (SnapshotStore) portfolio-hub 권위값; cash_estimated=true 시 ledger outbox delta 추정값
cash_estimated ledger outbox pending delta 있을 때 true (REQ-029)
warned 파생 qty != asset_quantity (REQ-074 AC-2)
watchdog WatchdogStore 만료 여부 + last_seen; null=store 미연결
paused PauseStore durable pause overlay (REQ-032)
strategy_name hub (PortfolioItem via SnapshotStore) REQ-116 additive. portfolio-hub 정본. hub 미제공 시 null
strategy_params hub (PortfolioItem via SnapshotStore) REQ-116 additive. opaque dict; hub 미제공 시 {}
markets hub (PortfolioItem via SnapshotStore) REQ-116 additive. list[str]; hub 미제공 시 []
  • slot-origin strategy 필드 없음 (REQ-074 AC-5, REQ-116 additive 재도입 후에도 유지): strategy_id, sell_strategy_id, active_strategy_id 는 응답에 포함되지 않는다. strategy_name/strategy_params/markets 는 PortfolioItem(hub) 출처이므로 AC-5 제외 대상과 구분된다 — REQ-116 Notes 참조.
  • spy=false(기본): holding 슬롯 응답 후 WatchdogStore.update(slot_id) 호출 — 전략 폴링 = 생존 신호.
  • spy=true: watchdog 갱신 건너뜀 — 운영자 CLI(universe/slots) 가 항상 사용.

운영자 CLI ↔ RPC 매핑 (dcy-016 / REQ-032)

운영자 CLI 명령은 데몬 RabbitMQ RPC 로 통일된다(단일 writer admission 보존, DB 직접 쓰기 없음). 인스턴스 라우팅: --config <toml>(canonical, REQ-048) 또는 --instance <key>(단축·override), 미지정 시 default.

CLI RPC 큐 payload 비고
universe trex.slots.req[.{instance}] {filter:"inactive", spy:true} inactive 슬롯 목록 조회(워치독 억제)
slots [--status all|holding|buyable|pending|error] trex.slots.req[.{instance}] {filter:"<status>", spy:true} — buyable→filter:"inactive" 슬롯 상태 목록(server-side filter, 워치독 억제, BAT-035)
enter <portfolio_id> [--capital/--qty/--stop-loss/--take-profit/--forced-close-at ...] trex.intent.req[.{instance}] buy intent(portfolio_id 필수, symbol 서버 resolve; --forced-close-at <ISO8601>→forced_close_at, REQ-032 CHG-20260609-01) 안전장치 필수; --forced-close-at 형식은 제출 전 검증
exit <slot_id> [--price] trex.intent.req[.{instance}] sell intent(slot_id, optional limit_price) 가격 미지정 시 시장가
pause/resume/paused (RPC 아님) — direct-DB 유지(OQ-3)
report [--json] (RPC 아님) — direct-DB 시점 집계: 현재 보유 / 오늘(KST) 체결·실현 PnL(ledger_outbox net 매매 현금흐름, 수수료·세금 반영) / 대기 주문. 신규 RPC 없음(REQ-088/dcy-029 OQ-1)
  • enter/exit 안전장치(OQ-2): 확인 프롬프트 기본 ON, --yes(paper 생략), live(broker=kis+allow_live)는 --yes --live 이중 ack, --dry-run 미리보기. --json 은 비대화형이라 enter/exit 에 --yes 필수(미지정 시 자동 제출 없이 에러). 모든 명령 출력에 대상 인스턴스 표시, admission 응답(status/slot_id/reason)을 표시하며 rejected 면 비0 종료.

청산 사유 어휘 — reason + actor (REQ-086 / dcy-029 FR-3)

슬롯 청산은 두 축으로 노출된다(신규 reason 값·필드 없음; 기존 reason·actor를 함께 본다):

  • 조건축 reason — 안전망(safety-sell) 자율청산 family: stop_loss · take_profit · forced_close_at · market_close_liquidation. 운영자 매도: operator_exit. 비상정지: emergency_stop. 정상 체결: buy_filled/sell_filled/sell_filled_partial.
  • 주체축 actor — trex-safety-sell(안전망 watchdog 소유 자율청산) · execution_loop(집행 루프 체결/롤백) · operator(운영자) · 전략(전략 SellIntent).

핵심: stop_loss/take_profit/forced_close_at 는 모두 watchdog expired(전략 침묵 → 안전망 소유) 에서만 발화하므로, actor=trex-safety-sell 가 동반될 때의 reason=stop_loss 는 "전략의 손절"이 아니라 안전망 자율청산이다. 로그(safety-sell fired ... reason= actor=), slot_transition_log, 운영 report 가 reason+actor 를 함께 노출해 오해(2026-06-15 064400)를 제거한다.

trex.control.req 제어 RPC (BAT-012 / REQ-043, REQ-032)

운영자 제어 채널. action discriminator 로 분기한다(REQ-043). 기존 trex.pause.req(REQ-032)의 개념 확장이며, 단일 제어 큐 + action 패턴으로 기능마다 큐/consumer 를 늘리지 않는다.

action 목록: pause | resume | list (REQ-032) · kill_switch (REQ-045) · resume_kill_switch (REQ-046). 알 수 없는 action 은 {status:"error", reason:"unknown_action:<a>"}.

pause/resume/list 요청·응답:

{ "action": "pause" | "resume" | "list", "portfolio_id": str, "actor": str? }
→ { "status": "ok"|"error", "paused": [str,...], "action": echo, "reason"?: str }
  • pause 는 snapshot_pause(portfolio_id PK, 영구) upsert — daemon 재시작·일일 refresh 후 유지(REQ-032 AC-2). CLI trex-core pause/resume/paused 가 같은 store 를 본다.

kill_switch 요청·응답 (REQ-045):

{ "action": "kill_switch", "scope": "portfolio"|"global", "portfolio_id": str?, "actor": str? }
→ { "status": "ok", "scope": echo, "portfolio_id"?: str, "liquidated": int }
  • scope 범위의 모든 holding 슬롯을 SlotService.sell(reason="emergency_stop") 로 즉시 청산하고, 해당 범위를 당일 제외(killswitch_exclusion)로 표시한다. scope=portfolio 인데 portfolio_id 누락 시 {status:error, reason:portfolio_id_required}.

resume_kill_switch 요청·응답 (REQ-046):

{ "action": "resume_kill_switch", "scope": "portfolio"|"global", "portfolio_id": str? }
→ { "status": "ok", "scope": echo, "portfolio_id"?: str }
  • 해당 범위의 당일 제외만 해제(killswitch_exclusion row 삭제)해 정상 매매로 복귀. 이미 청산(매도)된 슬롯은 되돌리지 않는다(매도 비가역).

  • snapshot masking 소스(REQ-046): mask = pending | warning | pause | killswitch_today. kill-switch 당일 제외는 (portfolio_id, trading_date) day-scoped — 다음 거래일 자동 복귀. portfolio_id="*" sentinel 은 전역(전체 universe) 마스킹.

  • 하위호환: trex.pause.req 는 legacy alias. 운영 배포 시 제어 클라이언트는 trex.control.req 로 이전한다(rollout 결정, REQ-043 Notes).

Multi-instance routing (BAT-013 / REQ-049/050)

다중 인스턴스 운영 시 큐명과 라우팅을 instance_key 로 분리한다(dcy-009).

  • 큐명 규칙: {base}.{instance_key}(예: trex.intent.req.inst-1). instance_key = "default" 는 기존 리터럴 큐명(trex.intent.req 등)을 그대로 사용한다(하위호환, FR-5). trex.snapshot.req → trex.slots.req 로 교체됨(BAT-035).
  • trex.direct(topic exchange): 특정 인스턴스 지정 전달. routing key = {instance_key}.{channel_type}(channel_type ∈ {intent, slots, control}). 각 인스턴스 큐가 자신의 routing key 로 바인딩 → inst-1.intent publish 는 trex.intent.req.inst-1 에만 도달.
  • trex.broadcast(fanout exchange): 전역 제어. 각 인스턴스의 control 큐가 dual 바인딩되어, broadcast publish 는 모든 인스턴스 control 큐에 동시 전달(global kill-switch 등). 중복 전달은 핸들러 idempotency(REQ-036/advisory lock)로 무해.
  • 기동: 데몬은 --config <path>.toml(필수)로 instance_key/portfolio_hub_token 을 읽는다(.env 아님, REQ-048).

Interface: PortfolioHubReadPort (Protocol, BAT-008)

  • Type: external-service (HTTP, portfolio-hub)
  • Consumers / Producers: SnapshotScheduler(소비) ↔ PortfolioHubReadAdapter 또는 StubPortfolioRead
  • Purpose: portfolio-hub universe(이 데몬이 관리하는 portfolio 집합) read 경계 — write 는 BAT-006 LedgerPort 와 분리.
  • Stability: experimental (BAT-008)

Inputs

  • whoami() -> str :: 자격증명/토큰에서 daemon_id 도출(1회). 별도 env 1차 출처 두지 않음.
  • get_portfolios(daemon_id) -> list[PortfolioItem] :: managed_by_daemon_id == daemon_id scope 조회.

PortfolioItem Fields

  • portfolio_id :: str :: == asset_id (dcy-004 OQ-1)
  • symbol :: str
  • cash_balance :: float
  • asset_quantity :: float :: integer shares semantically
  • active_strategy_id :: str | None
  • strategy_params :: dict :: opaque pass-through (no core parsing)
  • strategy_name :: str | None :: REQ-116 additive (BAT-055) — portfolio-hub 정본; 미제공 시 None
  • markets :: list[str] :: REQ-116 additive (BAT-055) — portfolio-hub 정본; 미제공 시 []

Outputs / Outcomes

  • 정상: list[PortfolioItem] (배열, 비면 []).
  • 어댑터 어떤 실패든 PortfolioHubUnavailableError 로 통일 (호출자는 SnapshotStore.set_degraded 로 전환).

Error Contract

  • PortfolioHubUnavailableError :: 네트워크/5xx/4xx/JSON 파싱 실패 등 fetch 불가. 자격증명/예외 본문은 호출 측에서 sanitize 후 노출(REQ-028 AC-2).

portfolio-hub Endpoint (어댑터 구현)

HTTP URL 응답
GET /v1/auth/whoami {daemon_id: str, ...}
GET /v1/portfolios?managed_by_daemon_id=<id> {items: [PortfolioItem, ...]} 또는 list root

Interface: SnapshotStore + PauseStore + SnapshotScheduler (Protocol, BAT-008)

  • Type: internal-service
  • Consumers / Producers: SnapshotScheduler(생산) → SnapshotStore(read 채널) ← make_snapshot_rpc_handler(소비). PauseStore(PostgreSQLPauseStore 또는 InMemoryPauseStore)는 CLI/RPC 양쪽이 쓰고 handler 가 읽는다.

Inputs

  • SnapshotStore.swap(items) :: atomic immutable swap. 직전 warning flag 는 carry-over(다음 reconcile 에서 자동 해제 판단).
  • SnapshotStore.set_warning(pid, on) :: 정합 결과 overlay(in-memory only).
  • SnapshotStore.update_cash_position(pid, cash, qty) :: 옵션 D wiring point(REQ-030 AC-2/AC-3 — portfolio-hub#8 배포 후에만 ledger writer 가 호출).
  • SnapshotStore.set_degraded(reason, message) / clear_degraded() :: degraded signal.
  • PauseStore.pause(pid, actor) / resume(pid) / list_paused() :: durable manual overlay.
  • SnapshotScheduler.run_once() :: fetch + swap + reconcile 1 회.
  • SnapshotScheduler.run_forever(stop, tick_sec) :: boot 즉시 1 회 + 매일 TREX_SNAPSHOT_SCHEDULE_KST 시각에 1 회.

Outputs / Outcomes

  • run_once() returns True(성공) / False(degraded). True 시 reconcile 한 패스가 함께 돈다.
  • list_assets() + degraded_state() 를 envelope builder 가 매 RPC 요청마다 읽음.

Compatibility Rules

  • pause 의 영속성 단위는 portfolio_id(= asset_id). 동일 portfolio_id 재 pause 는 upsert(actor만 갱신).

  • warning 은 in-memory only — 재시작 시 초기화되며 다음 정합 패스가 다시 판정한다.

  • universe swap 은 pause/warning 의 결정을 invalidate 하지 않는다(warning 은 carry-over, pause 는 store 가 별도 영속).

  • Type: internal-service

  • Consumers / Producers: 상위 애플리케이션(예: trex-orbit 류 매매 데몬)의 매수/매도/주문 결정 경로 및 운영자 도구

  • Purpose: 슬롯 상태 전이를 레이스-프리하게 수행하고 일일 슬롯 가용성/재진입 카운트를 계산한다.

  • Stability: internal

Inputs

  • transit_slot(slot_id, from_state, to_state, reason, actor, **fields) :: advisory lock 하 원자 상태 전이 (INACTIVE 진입 시 점유 초기화 + 자산 정체성 보존)
  • sell(slot_id, actor, reason, limit_price=None) :: holding 슬롯 자율 매도(멱등)
  • update_thresholds(slot_id, ...) :: SL/TP/forced_close 부분 갱신
  • compute_slot_capacity(max_active_slots=None) :: instance 스코프 가용성 스냅샷 (BAT-030)

Outputs

  • Slot :: 전이 후 슬롯 행 스냅샷
  • SlotCapacity :: inactive/holding/pending/active/available 카운트 + total + scarcity_ratio
  • slot_transition_log append :: 모든 전이의 append-only 감사 기록(부수 효과)

Error Contract

  • SlotTransitionError :: 허용되지 않은 (from_state, to_state) 전이
  • SlotStateMismatchError :: read-write 사이 상태 변동(레이스 감지)
  • SlotNotFoundError :: 존재하지 않는 slot_id

Compatibility Rules

  • 5 상태 / 13 전이 계약(REQ-057, dcy-021 holding→pending_buy 증액 포함)을 깨는 변경은 breaking 으로 취급한다.
  • 상태 변경 API 는 transactional_advisory_lock(인스턴스 스코프 lock id) 안에서만 수행하고 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 컨텍스트 매니저
  • list_slots(instance_key) / fetch_slot / update_slot :: daily_slot CRUD (instance 스코프)
  • find_slot_by_portfolio / count_active / list_holding_slots / reconcile_universe :: 자산 중심 표면 (REQ-058/059/062)
  • list_undispatched_pending / mark_dispatched / list_dispatched_pending / clear_dispatch :: 집행 큐 (instance 스코프)
  • append_transition_log :: append-only 전이 로그
  • count_slots_by_status(instance_key) :: 상태별 집계

Outputs

  • Slot / SlotTransitionRecord :: 도메인 모델
  • dict[SlotState, int] :: 상태별 카운트
  • dict[str, int] :: reconcile 결과 {created, retired, tombstoned}

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 멱등) → LedgerWriteOutcome 반환

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

  • LedgerWriteOutcome (dcy-018/REQ-017) :: {result, cash_balance_after?, asset_quantity_after?}
    • result = LedgerWriteResult.NEW :: 새 원장 엔트리 적재
    • result = LedgerWriteResult.DUPLICATE_OK :: 같은 execution_id 가 이미 기록되어 있음(서버 멱등)
    • cash_balance_after/asset_quantity_after :: portfolio-hub#8 ledger write 응답 root flat 권위값(option D). new(201)·duplicate(200) 에 존재, 응답 누락 시 None(write-back skip). 409/422 는 예외(아래)라 outcome 미반환.
  • write-back(REQ-019/030): LedgerWriter 가 new·duplicate outcome 의 *_after(None 아님)를 SnapshotStore.update_cash_position(asset_id, cash, qty) 로 반영(best-effort). 절대 권위값이라 duplicate 도 멱등.
  • 스냅샷 응답 추정 현금(REQ-029/OQ-4): write-back 전 공백 동안 자산에 cash_balance += 미정산 pending 합 + cash_estimated: true 플래그(라벨). write-back 도착 시 권위값으로 수렴(pending 제거).

Error Contract

  • LedgerConflictError :: portfolio-hub 409 — 운영자 해소 대상(자동 재시도 중단)
  • LedgerPermanentError :: portfolio-hub 404/400/422(dcy-015/REQ-017, issue #13) — 재시도로 해소 불가(미등록 포트폴리오·malformed). outbox 가 즉시 터미널 delivery_failed 처리(재시도 0)
  • LedgerRetryableError :: 5xx / timeout / network / 408 / 429 — outbox 가 지수 백오프 + jitter 로 재시도, max_attempts 초과 시 delivery_failed(dead-letter)

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 게이트.

OrderRequest DTO (BAT-004 / REQ-011, BAT-056 / REQ-117)

포트-어댑터 경계에서 주문 요청을 전달하는 frozen dataclass.

필드 타입 기본값 설명
slot_id str (필수) 발원 슬롯 (감사/상관 식별자)
side str (필수) 'buy' | 'sell'
symbol str (필수) 종목코드 (KIS PDNO)
qty int (필수) 주문 수량
limit_price int | None None 지정가 (None = 시장가)
order_type str "00" KIS ORD_DVSN: 00=지정가, 01=시장가
venue str "KRX" 거래소 구분 (REQ-117 / BAT-056 additive): KRX=정규 KRX, NXT=대체거래소(연장세션), SOR=최선집행(현재 비권고 — OQ-1 잔여)

Additive Notes (REQ-117)

  • venue 필드는 BAT-056에서 additive 추가. 기존 orb/tsl 미지정 호출 → 기본값 "KRX" 자동 적용 → 하위 호환.
  • OrderRequest는 AMQP wire 위를 직접 흐르지 않으므로 wire 계약 영향 없음. intent 메시지에서 venue를 전달하는 것은 tvr 팬-인 batch(후속) 범위.
  • KIS 어댑터(REQ-118)는 venue → EXCG_ID_DVSN_CD 로 변환해 KIS HTTP 요청에 항상 명시 전송.

BrokerPort Protocol 메서드 (BAT-056 / REQ-118)

Balance-read failure and reconciliation boundary (BAT-057 / REQ-120/121)

  • get_balance()는 검증된 list[BalanceItem]만 반환한다. KIS의 token/auth/authorization/transport/response/rejection 실패는 빈 목록으로 축소되지 않고 BrokerBalanceError를 raise한다. 안정 code allow-list는 token_issuance_failed, authentication_failed, authorization_failed, transport_failed, malformed_response, broker_rejected다.
  • sync와 verify CLI는 첫 balance read를 완료한 뒤 cache한 결과를 해당 invocation에 전달한다. 이 read 또는 broker credential resolution이 실패하면 exit code 1이며, --json stderr는 { "error": {"code": "…", "message": "broker balance unavailable"} } 형태의 안전한 값만 낸다. upstream payload, credential, account 값은 계약상 출력하지 않는다.
  • reconcile_core_holdings()의 broker-only 결과는 unmanaged_position(대상 instance의 managed slot 없음) 또는 managed_non_holding_divergence(동일 instance의 non-holding managed slot 존재)다. find_managed_slot_by_symbol(instance_key, symbol) 조회는 instance-scoped이며, 이 두 분류 자체는 slot/warning write나 자동 주문·청산·slot 생성을 하지 않는다.
  • 이 변경은 AMQP 또는 portfolio-hub external wire contract와 DB schema를 바꾸지 않는다.

cancel_order

cancel_order(broker_order_id: str, *, side: str, symbol: str, qty: int, venue: str = "KRX") -> bool
  • venue: 원주문 venue. 취소 시 원주문과 동일한 EXCG_ID_DVSN_CD를 사용해야 한다. 기본값 "KRX"로 기존 호출자 하위 호환.

get_current_price

get_current_price(symbol: str, market: str = "J") -> Optional[Decimal]
  • market: KIS FID_COND_MRKT_DIV_CODE. 기본값 "J" (KRX 주식) → 기존 safety-sell 경로 무영향. "UN" 지정 시 해외/NXT 연장세션 시세 조회 가능.

KIS 어댑터 EXCG 명시화 (REQ-118)

  • submit_order: body에 EXCG_ID_DVSN_CD: req.venue 항상 명시 전송 (이전: 미전송·서버 기본값 의존 → 명시화로 견고성 향상). 실측(2026-07-06): NXT(EXCG=NXT) 주문 rt_cd=0 확인.
  • cancel_order: body에 EXCG_ID_DVSN_CD: venue 명시 전송. 실측(2026-07-06): NXT 취소 rt_cd=0 확인.
  • poll_fill: 구 TR(TTTC8001R/VTTC8001R) 유지. NXT 체결 포함 여부 미확인(OPEN ITEM — 운영 스모크 필요). 확인 후 미포함이면 TTTC0081R(EXCG=ALL) 전환 별도 batch 처리.

FillStatus 확장 (BAT-006 / REQ-018)

  • state, filled_qty, avg_price, raw 외에 다음 필드를 운반한다.
    • execution_id :: str? :: 결정적 합성(브로커별 규칙). filled 상태에서 필수.
    • fee :: float :: BrokerPort calculate_fee 결과(기본 0).
    • tax :: float :: 기본 0(정밀 모델 후속).
    • cancelled :: bool :: 외부 취소 감지(dcy-013/REQ-012, issue #9). 기본 False.
  • state 값과 집행 처리(dcy-013):
    • accepted / partially_filled(취소 아님) :: 다음 tick 대기. (FR-3) TREX_PENDING_STALE_THRESHOLD(초, 0=비활성) 초과 시 Alert-only WARN+카운터(상태 미변경).
    • filled :: 전량 체결 → pending_buy→holding / pending_sell→inactive + 원장 1건.
    • cancelled(체결 0 외부 취소) :: is_terminal_reject → 롤백(pending_sell→holding / pending_buy→inactive), clear_dispatch → 자동 재매도 가능.
    • partially_filled + cancelled=True(부분 체결 후 외부 취소) :: is_partial_cancel → 체결분 원장 1회 기록 + 슬롯 qty 차감(매도: 잔여 held / 매수: 체결분만) + holding 복귀. 매도는 cost basis(avg_price) 유지.
  • KIS 매핑: inquire-daily-ccld 응답 row 의 cncl_yn="Y" 를 취소로 해석한다(미지/부재 값은 보수적으로 비취소 처리, stale 워치독이 backstop).

Interface: MessagingTransport (Protocol, BAT-007)

  • Type: internal-service (transport boundary)
  • Consumers / Producers: 데몬 런타임(소비) ↔ 구현체(RabbitMQTransport 운영용 / InMemoryTransport 결정론 stub)
  • Purpose: 메시지 transport 추상화. 도메인은 transport/AMQP 의존 0(NFR-1).
  • Stability: experimental (BAT-007)

Inputs

  • declare_topology() :: 익스체인지/큐/바인딩 선언(멱등)
  • publish_event(routing_key, payload, mandatory=True) :: trex.events 발행. publisher confirms; unroutable → DLQ
  • call_rpc(queue, payload, *, correlation_id, timeout) :: 클라이언트 RPC (직접 reply-to 큐 대기)
  • consume_rpc_forever(queue, handler, stop) :: 서버측 RPC 소비 루프(stop 까지 블로킹, 백오프 재연결)
  • consume_events_forever(queue, binding_pattern, handler, stop) :: 이벤트 구독 루프

Outputs / Outcomes

  • health() -> TransportHealth :: connected | disconnected | reconnecting
  • DLQ 라우팅 :: 처리 실패/unroutable 메시지는 trex.events.dlq exchange/queue 로 격리

Error Contract

  • TransportUnavailableError :: connected 가 아닌 상태에서 publish_event/call_rpc 호출 시 즉시 실패(REQ-024 — 로컬 버퍼링 금지)
  • RpcTimeoutError :: 설정된 timeout 안에 reply 없음

Compatibility Rules

  • pika BlockingConnection 은 thread-safe 가 아니므로 consumer/publisher 마다 자기 connection/channel 을 가져야 한다.
  • 토폴로지 선언은 멱등하지만 attribute(durable, args) 가 기존 큐와 다르면 RabbitMQ 가 PRECONDITION_FAILED 로 거부 → batch 마이그레이션 시 큐 재선언 절차 필요.

Interface: trex.slots.strategy_state.set RPC (BAT-042 / dcy-028, REQ-080)

  • Type: external-service (strategy daemon ↔ core write RPC)
  • Consumers / Producers: 전략 데몬(produce) ↔ core 핸들러(consume). per-instance 큐({base}.{instance_key}), direct reply-to, JSON.
  • Purpose: 슬롯의 opaque strategy-state 를 occupancy_seq+version CAS 로 write. read 는 trex.slots.req 에 piggyback(별도 RPC 아님).

Inputs

  • {slot_id, strategy_id, expected_version:int, expected_occupancy_seq:int, data:str}. correlation_id = "{slot_id}:{strategy_id}:{expected_version}"(멱등), delivery persistent. data 는 opaque UTF-8(core 무해석), byte size cap(기본 4KB ceiling, 운영 권장 1~2KB).

Outputs / Outcomes

  • 성공: {ok:true, slot_id, strategy_id, version:<new>}.
  • 실패 reason 전체 집합: occupancy_changed(+occupancy_seq) / version_conflict(+version) / slot_inactive / state_too_large / bad_request:<detail>. 소비자는 미인식 reason 에 fail-closed.

Compatibility Rules

  • read 표면(trex.slots.req)의 각 슬롯 행에 additive 로 occupancy_seq(int)와, 보유 시 strategy_state 맵({<strategy_id>:{version,data,updated_at}})이 실린다(REQ-073/074 CHG). 기존 소비자는 unknown 필드 무시로 하위호환. strategy_state 는 strategy_* reasoning 필드(strategy_id 등, REQ-074 AC-5 제외 대상)와 구분되는 opaque 상태 맵이다.

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 경로 계약이 확정된다.

Interface: trex.alerts (알람 채널, dcy-033)

  • Type: amqp-messaging (topic exchange)
  • Consumers / Producers: 모든 모듈(trex-core/orb/tsl/kdh)(발행자) ↔ trex-notifier(단일 소비자)
  • Purpose: 제품군 CRITICAL 알람 발신 전용 채널. trex.events와 독립 분리. best-effort 비차단.
  • Stability: experimental (BAT-049)

Exchange 토폴로지

종류 이름 타입/속성 용도
익스체인지 trex.alerts topic, durable=True 알람 envelope 발행 전용 독립 채널
  • 발행자: 모든 모듈(trex-core, trex-orb-v1, trex-tsl-v1, kis-data-hub). 각 모듈이 자신의 RabbitMQ 연결에서 직접 발행.
  • 소비자: trex-notifier 단독. 자신의 큐를 alarm.#(또는 severity별 alarm.critical.#) 패턴으로 바인딩. 큐 선언·바인딩은 trex-notifier 소유.
  • 발행 속성: mandatory=False — notifier 큐 미연결(unroutable) 시 silent drop. core 발행 경로 비차단.
  • 기존 exchange 불변: trex.events(topic), trex.events.dlq(fanout), trex.direct(topic), trex.broadcast(fanout) 등 기존 AMQP 엔티티는 이 채널 추가로 변경되지 않는다(가산·격리 원칙).

Routing Key 규약

alarm.{severity}.{source}
  • severity ∈ critical | warn | info (소문자 리터럴)
  • source = 발신 모듈 식별자 (예: trex-core, trex-orb-v1, trex-tsl-v1, kis-data-hub)
  • 예시: alarm.critical.trex-core, alarm.warn.trex-tsl-v1

Alarm Envelope JSON 스키마 (schema_version="1")

{
  "source":         "trex-core",
  "severity":       "critical",
  "code":           "kill_switch_fired",
  "title":          "긴급 청산 발동: global scope, liquidated=3",
  "fields":         {"scope": "global", "liquidated": 3},
  "occurred_at":    "2026-06-21T09:30:00+09:00",
  "schema_version": "1"
}
필드 타입 필수 설명
source str 필수 발신 모듈 식별자. 소비자(trex-notifier)의 연쇄 분석 기준.
severity "critical" | "warn" | "info" 필수 등급 (소문자 리터럴 3개).
code str 필수 알람 식별 코드. ALARM-INVENTORY.md의 code 키 채택.
title str 필수 사람이 읽는 1행 요약. 자격증명·내부 예외 본문 제외.
fields dict[str, any] 선택 구조화 컨텍스트. opaque — core/notifier 내용 강제 파싱 없음. 빈 dict {} 허용.
occurred_at ISO-8601 datetime 필수 발생 시점. 권장: KST+09:00 offset-aware. UTC도 수용.
schema_version str 필수 현재 "1". 소비자는 미인식 버전 무시·WARN.

trex-core CRITICAL code 목록 (schema_version="1")

trex-core가 source="trex-core", severity="critical"로 발행하는 초기 배선 code:

code 발생 지점 조건
fill_price_unresolvable execution.py 체결가·지정가 모두 미해결 → 슬롯 PENDING 고착
sell_skipped_no_balance execution.py 매도 dispatch skip (브로커 잔고 0)
core_holding_broker_flat reconciliation.py 슬롯 보유인데 브로커 잔고 0 (정산 불일치)
ledger_delivery_failed ledger_outbox.py 원장 영구 실패/dead-letter/409 충돌
kill_switch_fired killswitch.py 긴급 청산 발동
publish_unavailable_after_reconnect adapters/messaging/rabbitmq.py 재연결 후에도 이벤트 발행 불가
dlq_publish_failed adapters/messaging/rabbitmq.py DLQ 발행 실패
portfolio_hub_unavailable portfolio.py / snapshot_scheduler.py 포트폴리오/현금 정보 부실화
schema_verification_failed reset.py 리셋 후 schema 검증 실패
migration_failed reset.py 마이그레이션 실패

Update Trigger (알람 envelope 스키마)

  • 알람 envelope 스키마를 변경(필드 추가 포함)하면 이 섹션을 갱신하고 schema_version을 올리거나 additive 변경 여부를 명시한다.
  • breaking 변경은 schema_version 주 버전 증가 필수.
  • additive 변경(신규 선택 필드 추가)은 schema_version 유지 가능하되 이 섹션에 변경 이력 기술.
  • 다른 모듈(orb/tsl/kdh)은 이 계약 섹션을 단일 출처로 참조한다. trex_core import 없이 envelope 구성에 필요한 모든 정보가 이 섹션에 포함된다.

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.
  • 알람 envelope 스키마 변경 시 Interface: trex.alerts 섹션 Update Trigger 참조.

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).
  • 2026-06-05 :: BAT-007 — HTTP /v1 API 인터페이스 제거(adapters/api + fastapi/uvicorn 의존 정리), RabbitMQ messaging 계약 추가(스냅샷 RPC trex.snapshot.req, intent RPC trex.intent.req, 이벤트 topic trex.events, 전략별 durable queue + TTL/max-length, DLQ, publisher confirms, ack-after-commit), MessagingTransport Protocol + TransportHealth 노출 (REQ-021~026).
  • 2026-06-06 :: BAT-008 — trex.snapshot.req envelope 계약 정의(assets[]/view/available_slots/degraded/reason/message, no price field), trex.pause.req 제어 RPC 추가, PortfolioHubReadPort/SnapshotStore/PauseStore/SnapshotScheduler 인터페이스, BrokerPort.get_balance() 확장, trex-core CLI 추가 (REQ-027~033; REQ-030 옵션 D 는 portfolio-hub#8 배포 후).
  • 2026-06-10 :: BAT-030 (dcy-020 마감) — buy intent 계약 키 slot_id 정본(+portfolio_id dual-accept, trading_date 제거), 거부 어휘(unknown_slot/already_active/capacity_full/slot_id_or_portfolio_required), slots[] 5-state + universe_detached(trading_date 키 제거), 이벤트 payload trading_date 제거, available_slots=admission headroom, SlotService/SlotRepository 자산 중심 계약, 매도 체결 pending_sell→inactive (REQ-056~065).
  • 2026-06-15 :: BAT-043 (issue #27, REQ-007 CHG-20260615-01) — rejected SellIntent 응답에 additive retryable: bool(core 소유 reason→retryable 매핑, 미인식 기본 true, bad_request:*/terminal false). 손절매 재시도 안전 경로 decoupling. 완전 additive(admitted/processing/buy·기존 필드 불변).
  • 2026-06-15 :: BAT-042 (dcy-028) — slot strategy-state 계약: 신규 write RPC trex.slots.strategy_state.set(occupancy_seq+version CAS, reject reason 집합 occupancy_changed/version_conflict/slot_inactive/state_too_large/bad_request), trex.slots.req 행에 additive occupancy_seq+strategy_state 맵(REQ-073 AC-7/REQ-074 AC-6, opaque UTF-8+size cap, strategy_* reasoning 필드와 구분). close(→INACTIVE) 시 state 자동 삭제 (REQ-079~082).
  • 2026-06-11 :: BAT-035 (dcy-025) — trex.snapshot.req → trex.slots.req lockstep 교체: 요청 {filter, spy}, 응답 {slots[], degraded, reason, message}. 슬롯 row 에 hub asset_quantity/cash_balance + warned/cash_estimated/watchdog/paused 병합(REQ-073/074). trex.snapshot.req envelope(assets[]/available_slots/view enum) 제거(REQ-028 Deprecated). strategy 필드 제거(REQ-074 AC-5). CLI universe/slots → trex.slots.req(spy=true). trex.direct channel_type snapshot → slots.
  • 2026-06-11 :: BAT-036 (dcy-024) — safety_sell_enabled: bool=True per-instance TOML 플래그 추가(REQ-048 CHG-01). false 시 trex-safety-sell worker 미기동 + WARNING 로그(REQ-005 CHG-01).
  • 2026-06-21 :: BAT-049 (dcy-033) — 알람 채널 trex.alerts (topic, durable) 신규 추가. 알람 envelope 표준 JSON 스키마(7필드: source/severity/code/title/fields/occurred_at/schema_version, schema_version="1"), routing key alarm.{severity}.{source}, 발행자(모든 모듈)/소비자(trex-notifier), mandatory=False best-effort 정책. trex-core CRITICAL code 10종 초기 배선. 기존 wire 계약 불변(가산·격리). (REQ-098~101)
  • 2026-06-10 :: BAT-031 (dcy-021 포지션 사이징) — 매수 intent 가 holding 슬롯 대상이면 증액(qty+price 추가 매수; M 캡 미적용); 매도 intent 에 qty(부분 매도, 선택) 추가 — 지정 시 min(qty, 브로커잔고) 부분 매도(잔량 holding), 미지정 시 전량(→inactive). 상태머신 13 전이(holding→pending_buy). avg_price 증액 가중평균·부분 매도 cost basis 유지. (REQ-066/057/061/062)
  • 2026-07-04 :: BAT-055 (dcy-001/002 v3 FR-8, REQ-116) — trex.slots.req additive 확장: 요청에 선택적 strategy: str 필터 추가(exact-match, filter AND 조합, available_slots 불변 — REQ-073 AC-6); 응답 슬롯 행에 strategy_name(null)/strategy_params({})/markets([]) 3필드 추가(PortfolioItem/hub 정본, safe default). 기존 소비자(orb·tsl) backward compatible(unknown 필드 무시). REQ-074 AC-5 additive 재도입(dcy-025 OQ-4 후속 확정). PortfolioItem에 strategy_name/markets 필드 추가 + hub_read.py 파싱 확장.
  • 2026-07-06 :: BAT-056 (dcy-002 FR-6, REQ-117/118) — OrderRequest.venue: str = "KRX" additive 추가(기존 위치 인수 순서 불변 — AMQP wire 계약 영향 없음, 브로커 어댑터 계층 DTO까지만). KIS 어댑터: submit_order/cancel_order body에 EXCG_ID_DVSN_CD 항상 명시 전송(기존 미전송 → KRX 명시화 견고성 향상), cancel_order/get_current_price 시그니처 venue/market 파라미터 추가(기본값 KRX/"J" — 기존 호출자 무영향). poll_fill TR 유지(NXT 체결 포함 여부 미확인 open item). paper broker 시그니처 정합(venue/market 무시). 실측 근거: 2026-07-06 NXT 주문/취소 rt_cd=0 확인.