# Trex Core Runtime Flows - Baseline Mode: mixed - Baseline Seed: .stage-pilot/bootstrap/baseline.yaml - Status: draft - Owner: project maintainers - Last Updated (KST): 2026-06-10 (BAT-031 — dcy-021 포지션 사이징) - Source Discovery / Batch: bootstrap-baseline (pre-Discovery); 데몬/HTTP 흐름 = dcy-002 / BAT-002 - Primary Runtime: daemon (HTTP API) + library (slot 도메인) ## Project Summary - trex 제품군의 공용 베이스로, 첫 도메인으로 trex-orbit 의 일일 거래 슬롯 생명주기(slot) 순수 도메인을 storage-agnostic 하게 이식한 코어다. ## Purpose - This document is the current approved execution-flow baseline for the project. - It is a cross-cutting reference document for Discovery, REQ, Batch Design, Batch Verification, and implementation updates. ## Scope - Covers representative domain-service flows (library 형태이므로 외부 명령이 아니라 in-process API 호출 흐름). - Does not duplicate full acceptance criteria or low-level implementation details. ## Covered Entry Points - `trex-core-daemon` :: 슬롯 코어 데몬 (lifecycle + RabbitMQ 통신, BAT-002/BAT-007) - `trex-core-reset` :: DB 를 자산 중심 스키마로 wipe + 재초기화 (REQ-064, 운영자 실행 — 데이터 보존 cutover 대체; 파괴적·DB 전역, dry-run 기본) - 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) - SlotService :: 슬롯 생명주기 도메인 서비스 (transit / sell / thresholds / capacity — 자산 중심, BAT-030) - SlotRepository (Protocol) :: 저장소 구현체 주입 경계 - MessagingTransport (Protocol) :: 메시지 transport 추상화 (RabbitMQ / InMemory stub) ## Flow: `데몬 lifecycle` (BAT-002, BAT-007) 1. `trex-core-daemon --config .toml` 기동 → `DaemonConfig.from_config(toml)` = `from_env()`(TREX_POSTGRESQL_DSN/`TREX_RABBITMQ_URL` 등) + TOML(`instance_key`, `portfolio_hub_token`, `trading_timezone`, `max_active_slots`) 병합. `--config` 필수(없으면 오류 종료, BAT-013/REQ-048). `instance_key` 로 큐명·라우팅·DB 행 스코프 분리(REQ-056). 2. setup: `apply_migrations(dsn)`(멱등; 010 inactive CHECK·011 컬럼 drop 포함) → `execution.reconcile()`(REQ-014) → 이벤트 publisher 의 `declare_topology()`. (일일 preallocation 은 BAT-030 에서 폐기 — 슬롯 생성은 universe reconcile 이 담당.) 3. start: 7개 백그라운드 스레드 동시 기동 - `trex-execution` :: 브로커 dispatch/poll 루프 (REQ-013/014; 매도 수량 브로커 정본 REQ-062) - `trex-ledger` :: ledger outbox 드레인 (REQ-019) - `trex-intent-rpc` :: `trex.intent.req` 소비자 (prefetch=1, FIFO, dedup via intent_log; `update_thresholds` 액션 포함 REQ-040) - `trex-slots-rpc` :: `trex.slots.req` 소비자 (spy=false 시 holding 슬롯 워치독 `last_seen_at` 갱신 REQ-034; `safety_sell_enabled=false` 시 `trex-safety-sell` 미기동 BAT-036) - `trex-control-rpc` :: `trex.control.req` 소비자 — pause/resume/list + kill_switch/resume_kill_switch (REQ-032/043) - `trex-snapshot-scheduler` :: 일일 portfolio-hub 스냅샷 refresh + **universe 슬롯 reconcile**(REQ-058) + holding 불일치 reconcile(REQ-062) - `trex-safety-sell` :: 자율 안전 매도 트리거 루프 (forced_close / stop-loss·take-profit / 장 마감, REQ-037/038/039) 4. shutdown: `SIGINT`/`SIGTERM` → `stop.set()` → 모든 스레드 join + 모든 transport `close()`. HTTP 표면 0, 별도 포트 바인딩 없음. ## Flow: `슬롯 RPC` (RabbitMQ `trex.slots.req`, BAT-035 — REQ-028 `trex.snapshot.req` 대체) 1. 전략 모듈 또는 운영자가 RPC 요청을 `trex.slots.req` 큐로 publish — payload 에 `filter`(status 필터) + `spy`(워치독 억제 여부), properties 에 `correlation_id` + `reply_to`. 2. `trex-slots-rpc` 스레드가 메시지 수신 → `make_slots_rpc_handler(slot_repo, ...)` 로 슬롯 목록 산출. 3. `spy=false`(기본) 면 holding 슬롯 `WatchdogStore.update(slot_id)` 호출(전략 폴링 = 생존 신호, REQ-034). 4. 응답 `{slots[], degraded, reason, message}` 를 `reply_to` 큐로 publish → manual ack. 5. read-only 이므로 dedup 불요; 재요청 시 동일 응답을 다시 생성한다. ## Flow: `매수/매도 intent admission RPC` (RabbitMQ `trex.intent.req`, BAT-007) 1. 전략 모듈이 RPC 요청을 `trex.intent.req` 큐로 publish — payload `{intent_type, intent_id, ...}`, properties `correlation_id` = intent 멱등키, `reply_to`. 2. `trex-intent-rpc` 스레드(단일, prefetch=1)가 메시지 수신 → `IntentService.submit_buy/sell` 호출. 3. `intent_log.try_insert(intent_id, ...)` 가 dedup 게이트 — 중복 메시지는 기존 decision 재반환(슬롯 무전이). 4. 신규면 admission(자산 중심, REQ-059/061): **slot_id 정본 키**(미스 시 `unknown_slot`) 또는 **portfolio_id dual-accept**(미스 시 `unknown_portfolio`; 둘 다 없으면 `slot_id_or_portfolio_required`) 로 대상 영구 자산 슬롯 resolve → 이미 active(holding/pending) 면 `already_active` → symbol resolve(슬롯 `portfolio_id` 기준 universe) → `count_active >= max_active_slots(M)` 면 `capacity_full`(FCFS·무치환) → `SlotService.transit_slot(inactive→pending_buy)` → Postgres durable commit. (buy payload 에 client symbol·trading_date 없음) 5. admission 응답(`status, slot_id?, reason?`)을 `reply_to` 큐로 publish → manual ack. 6. ack-after-commit 시퀀스: durable commit 이 ack 보다 먼저이므로 ack 전 크래시 시 재전달돼도 (3) 의 dedup 으로 재실행되지 않는다. 7. 체결/종료는 응답이 아니라 후속 이벤트(아래)로 전달 — admission 동기 + fill 비동기 2단계 시맨틱. ## Flow: `슬롯 이벤트 발행/구독` (RabbitMQ `trex.events`, BAT-007) 1. 체결: execution 루프가 `pending_buy→holding`(또는 `pending_sell→closed`)을 commit + ledger outbox enqueue (REQ-018/019). 2. 같은 스레드에서 `event_publisher.publish_event(rk, payload)` 호출. - routing key: `{event_type}.{strategy_id}.{symbol}` (예: `slot_filled_buy.A.005930`). - payload: 현재 상태 + `sequence_at`(슬롯 `updated_at` iso) 운반 → 수신자는 last-wins 적용 가능 (순서 보증 불요). - publisher confirms 활성, 발행 실패는 로그 + DLQ 라우팅(`trex.events.dlq`). publish 실패가 슬롯 전이를 롤백하지 않는다. 3. 전략별 durable queue(`trex.events.strategy.{strategy_id}`)는 `*.{strategy_id}.#` 패턴으로 바인딩 → broker 단 필터로 자기 자산 이벤트만 수신. 4. 전략 down 중 적체된 메시지는 catch-up 으로 복구; TTL(1h) / max-length(10k) 초과는 `drop-head` + 후속 스냅샷 RPC reconcile. ## Flow: `일일 스냅샷 refresh` (BAT-008 / REQ-030) 데몬 lifespan 의 `trex-snapshot-scheduler` 스레드가 portfolio-hub universe 를 fetch 해 in-memory 스냅샷을 atomic-swap 으로 교체하고, 같은 패스에서 정합 검증을 수행한다. 1. boot: `SnapshotScheduler.run_once()` 한 번 호출 → 첫 universe 로딩(또는 degraded 마킹). 2. tick(`TREX_SNAPSHOT_TICK_SEC`, 기본 60s): 현재 KST 시각이 `TREX_SNAPSHOT_SCHEDULE_KST`(기본 07:30) 이상이고 오늘 아직 안 돌았으면 fire. 3. fire 단계: `PortfolioHubReadPort.whoami()` 로 daemon_id 도출(1회 캐시) → `get_portfolios(daemon_id)` 로 universe 페치 → `SnapshotStore.swap(items)` atomic 교체 → `reconcile_positions(store, broker)` 정합 검증. 4. 실패 분기: `PortfolioHubUnavailableError` → `degraded = portfolio_hub_unavailable`; 응답 파싱 실패 → `snapshot_fetch_failed`. 둘 다 다음 RPC 응답 envelope 에 즉시 노출(REQ-028 AC-2). ## Flow: `universe 슬롯 reconcile` (BAT-028/030 / REQ-058 — 일일 preallocation 대체) `trex-snapshot-scheduler` 가 universe fetch 직후 같은 패스에서 영구 자산 슬롯 집합을 universe 에 정합시킨다 (일일 preallocation/dcy-014 는 BAT-030 에서 폐기). 1. 신규 자산(portfolio_id 에 슬롯 없음) → `inactive` 영구 슬롯 생성(멱등 — `ON CONFLICT (instance_key, portfolio_id)` partial unique). 2. universe 이탈 + inactive → soft-retire(`retired_at` set; 행 보존, 운영 뷰 `universe_detached`). 이탈 + holding/pending → tombstone(그대로 유지 — safety-sell 경로 보존). 3. 가드: universe 빈 응답이면 reconcile skip(degrade 가드); 직전 대비 ≥50% 축소면 retire 보류(`allow_retire=False`, 대량 소실 가드 — dcy-020 OQ-2 D3). 4. 이어서 `reconcile_core_holdings`(REQ-062): 브로커 잔고 ground truth 로 core holding 검증 — core holding/브로커 0 → slot `error` 격리+WARN, 수량 불일치 → WARN(상태 불변), 브로커 보유/슬롯 없음 → WARN(unmanaged). 자동 청산 없음. ## Flow: `포지션 정합 검증` (BAT-008 / REQ-031) `reconcile_positions` 는 portfolio-hub 보유 수량을 symbol 별로 합산해 broker `get_balance()` 결과와 비교한다. 1. `BrokerPort.get_balance() → list[BalanceItem(symbol, hldg_qty)]` 조회. KIS 어댑터는 `TTTC8434R`/`VTTC8434R` `inquire-balance`, paper 는 결정론 fill aggregate. **0수량 row 는 양쪽 모두 drop**(D-2 leftover 처리, Decision 7). 2. 자산을 `symbol` 로 그룹화해 `hub_total = sum(asset_quantity)` 계산 → broker 의 같은 symbol `hldg_qty` 와 비교. 3. 불일치: 해당 symbol 의 모든 portfolio_id 에 `SnapshotStore.set_warning(pid, True)` → 다음 envelope 부터 응답에서 마스킹. 첫 raise 시 한 번 로그. 4. 일치: 이전에 `warning` 이었던 자산은 자동 해제 → 다음 envelope 부터 다시 포함. resume 과 달리 자동·임시 overlay(영속 X). ## Flow: `pause/resume 제어` (BAT-008 / REQ-032) 운영자가 자산을 매매에서 제외/복귀시키는 두 경로(CLI + RPC)는 동일한 `snapshot_pause` Postgres 테이블을 가리킨다. 1. CLI: `trex-core pause ` / `trex-core resume ` → `PostgreSQLPauseStore.pause/resume` 호출(`portfolio_id` PK upsert/delete). 2. RPC: 운영자가 `trex.control.req` 큐에 `{action: pause|resume|list, portfolio_id, actor}` 발행 → `make_control_rpc_handler` 가 pause action 을 같은 store 로 위임 → `{status, paused[], action}` 응답. (BAT-012: 구 `trex.pause.req` → `trex.control.req` 확장) 3. 응답 산출: `build_snapshot_envelope` 가 `pause_store.list_paused()` 를 매 요청마다 읽음 → `mask(pending | warning | pause)` 합성. resume 후 **다음 요청부터 즉시 복귀**(별도 reload 불필요). 4. 영속: daemon 재시작 + 일일 refresh 후에도 `snapshot_pause` row 유지(universe swap 은 pause 를 만지지 않음). ## Flow: `운영자 CLI` (BAT-020 / dcy-016, REQ-032) 운영자 CLI(`trex-core`)는 조회·매매를 데몬 RabbitMQ RPC 로 통일해 단일 writer admission 과 불변식을 보존한다(DB 직접 쓰기 없음). pause/resume/paused 만 기존 direct-DB 유지(OQ-3). 1. 인스턴스 라우팅: `--config `(canonical, REQ-048 `instance_key`) 또는 `--instance `(단축·override), 미지정 시 `default`. 큐명은 `_instance_queue` 규칙(`{base}` 또는 `{base}.{instance}`). 모든 출력에 대상 인스턴스 표시(R-2). 2. `universe`/`slots`: `trex.slots.req` RPC(`spy=true`) → 응답 `slots[]` 출력. **워치독 억제**(`spy=true`)로 운영자 조회가 전략 생존 신호로 오인되지 않는다(REQ-034 CHG-01, R-4). `universe`→`filter=inactive`; `slots --status `→server-side `filter=`(`buyable`→`inactive`). BAT-035. 3. `enter `/`exit `: `trex.intent.req` buy/sell RPC. buy 는 portfolio_id 필수(symbol 서버 resolve, dcy-011), sell 은 slot_id + optional price(시장가 기본, dcy-012). admission 응답(`status/slot_id/reason`)을 표시, rejected 면 비0 종료. 4. enter/exit 안전장치(OQ-2): 확인 프롬프트 기본 ON(대상 인스턴스·broker·요약, 기본 No) → `--yes`(paper 생략) → **live 는 `--yes --live` 이중 ack**(`TREX_KIS_ALLOW_LIVE` 배포 게이트 위 액션 게이트) → `--dry-run` 미제출 미리보기. `--json` 비대화형은 enter/exit 에 `--yes` 필수(미지정 시 자동 제출 없이 에러). 5. 실패 표시: RPC 타임아웃·데몬 미가동·라우팅 오류는 명확한 에러(비0 종료)로 표시(NFR-4). RPC 통일이라 데몬·RabbitMQ 미가동 시 조회 불가는 의도된 트레이드오프(R-3). ## Flow: `슬롯 RPC 응답 산출` (BAT-035 / REQ-073/074) `trex.slots.req` 메시지 처리 (`make_slots_rpc_handler`). 1. 요청 payload 디코드: `{filter ∈ {all, inactive, pending, holding, error}, spy: bool}`. 2. `slot_repo.list_slots(instance_key)` → status 기준 서버 필터 적용(`inactive`/`pending`/`holding`/`error`; 미인식 = `all`). 3. `spy=false` 면 `slot_repo.list_holding_slots()` 의 모든 holding 슬롯에 `WatchdogStore.update(slot_id)`. 4. 각 슬롯에 `slot_to_dict(s, snapshot_store=..., watchdog_store=..., pause_store=..., watchdog_timeout_sec=...)` 적용 → 병합 row 산출: - slot 코어 필드: `slot_id/status/symbol/qty/avg_price/entry_price/portfolio_id/forced_close_at/universe_detached` - hub 권위 필드: `asset_quantity`/`cash_balance` (SnapshotStore) - 파생: `warned = (qty != asset_quantity)` (REQ-074 AC-2) - 추정 현금(REQ-029 OQ-4): `cash_estimated=True` + `cash_balance += pending ledger delta`(best-effort) - 워치독: `watchdog: {expired, last_seen}` inline (WatchdogStore) - 오버레이: `paused` (PauseStore, REQ-032) - strategy 필드 없음 (REQ-074 AC-5) 5. degraded 상태 동봉: `SnapshotStore.degraded_state()` → `{degraded, reason, message}`. 6. 응답: `{slots[], degraded, reason, message}` — `slots` 는 항상 배열, null 금지. ## Flow: `per-slot 워치독 갱신` (BAT-010/011 / REQ-034, REQ-040) 전략 모듈의 생존 신호를 추적해 자율 안전 매도가 정상 운용 슬롯에 개입하지 않게 한다. 1. `trex-slots-rpc` 가 `trex.slots.req` 를 처리할 때, 요청 `spy=false`(기본) 인 경우 모든 `holding` 슬롯에 대해 `WatchdogStore.update(slot_id)`(`last_seen_at = now`)를 호출한다. 전략 모듈이 슬롯 RPC를 폴링하는 한 그 슬롯은 "활성"으로 판정된다. `spy=true` 면 갱신 생략(운영자 조회 — REQ-034 CHG-01). 2. `update_thresholds` intent(REQ-040)도 수신 시 해당 슬롯 `last_seen_at` 을 리셋한다(임계값 갱신 = 생존 신호). 3. `WatchdogStore.is_expired(slot_id, TREX_WATCHDOG_TIMEOUT)` 가 True 이고 grace period(`TREX_WATCHDOG_GRACE_PERIOD`, 데몬 부팅 직후) 밖이면 그 슬롯은 워치독 만료 → forced_close/가격 트리거 대상이 된다. 4. in-memory store 라 데몬 재시작 시 초기화되며 grace period 가 자동 재시작된다(부팅 직후 오탐 방지). 5. 가시화(dcy-017/REQ-034): 스냅샷 RPC 응답에 holding 슬롯의 워치독 상태 `watchdog: [{slot_id, portfolio_id, symbol, expired, last_seen}]` 를 read-only 로 동봉한다(best-effort — 실패 시 응답 불변, NFR-1). `trex-core slots` 가 `expired=true` 슬롯을 **WATCHDOG-OWNED(전략 이탈)** 로 표시한다. 운영자 조회는 `actor="operator"`/`touch_watchdog=false`(dcy-016)라 이 노출이 워치독을 리셋하지 않는다(비침습). Redis projection·push 알림은 미채택(OQ-1). ## Flow: `forced_close_at 만기 자동 매도` (BAT-011 / REQ-037) `trex-safety-sell` worker 가 `TREX_FORCED_CLOSE_CHECK_INTERVAL` 주기로 수행. 1. 오늘 `holding` 슬롯 중 워치독 만료(grace 밖)이고 `forced_close_at <= now(UTC)` 인 슬롯을 선별. 2. 대상 슬롯에 `SlotService.sell(actor="trex-safety-sell", reason="forced_close_at")` 호출 → `holding→pending_sell`(REQ-036). 이후 체결은 집행 루프가 처리. 3. 워치독 활성 슬롯·`forced_close_at=None` 슬롯은 제외. 이미 `pending_sell` 이면 idempotent(중복 매도 없음). KIS 호출 없음(DB + in-memory 비교만). ## Flow: `stop-loss·take-profit 가격 체크` (BAT-011 / REQ-038) `trex-safety-sell` worker 가 `TREX_STOP_LOSS_POLL_INTERVAL` 주기로 수행. 1. `holding` + 워치독 만료 + (`stop_loss_price` 또는 `take_profit_price` 설정) 슬롯만 대상(워치독 활성 슬롯은 KIS 호출조차 안 함). 2. `BrokerPort.get_current_price(symbol)` → KIS 주식현재가 시세 `inquire-price`(tr `FHKST01010100`, 실전/모의 공용, BAT-011 CHG-04 정정)의 `stck_prpr`. paper 는 프로그램된 가격(미설정 시 None). 3. `현재가 <= stop_loss_price` → 손절(`reason="stop_loss"`), `현재가 >= take_profit_price` → 익절(`reason="take_profit"`)으로 `SlotService.sell()`. 가격 미수신(None) 시 skip(오탐 방지, 다음 tick 재시도). 4. 쿼터 가드: 시간당 호출 상한 `N × (3600 / interval)` 추정이 임계 초과 시 경고 로그(REQ-038 AC-8). ## Flow: `장 마감 강제 청산` (BAT-011 / REQ-039) `TREX_MARKET_CLOSE_LIQUIDATION_TIME`(HH:MM KST, optional) 설정 시에만 동작. 1. `trex-safety-sell` worker 가 매 tick 현재 KST 시각을 평가 → 설정 시각 도달 + 같은 거래일 미발동(`last_liquidation_date` 가드)이면 발동. 2. 발동 시 **워치독·grace 무관** 모든 `holding` 슬롯에 `SlotService.sell(reason="market_close_liquidation")`. 이미 `pending_sell` 은 idempotent. 3. 미설정이면 동작하지 않음. in-memory 가드라 재시작 시 같은 날 재발동 가능(설계상 허용 — REQ-039 Notes). ## Flow: `safety-sell 재시도 쿨다운` (BAT-014 / REQ-052) 영속 거부(휴장/잔고/규제)로 동일 슬롯에 매도가 무한 재제출되는 것을 막는 per-slot 쿨다운. `forced_close`/`stop_loss`/`take_profit` 트리거에 적용(`market_close` 제외 — `_last_liquidation_date`로 하루 1회 보호). 1. `run_once` tick 시작 시 `_holding()`에 없는 슬롯의 쿨다운 상태(`_last_attempt`/`_attempt_count`)를 정리(FR-4). 2. 트리거가 `_safe_sell` 호출 직전, 슬롯이 `now - last_attempt_at < TREX_SAFETY_SELL_MIN_RETRY_INTERVAL`(기본 600s)이면 **건너뜀**(stop-loss/take-profit 은 KIS 시세 poll 도 skip). 3. 쿨다운 만료 후 트리거 조건이 여전히 충족되면 재시도 + `last_attempt_at` 갱신 + `attempt_count` 증가(성공 여부 무관). 4. `attempt_count >= TREX_SAFETY_SELL_MAX_RETRY_WARN_COUNT`(기본 3) 도달 시 재시도 직전 운영자 `WARNING` 로그(slot_id/count/reason/last_attempt_at). 5. 트리거 조건 해소(가격 회복 등)·체결 완료(슬롯 holding 이탈) 시 재시도/카운트 증가 없음. 데몬 재시작 시 쿨다운 초기화(재시작 직후 1회 재시도 허용). ## Flow: `safety-sell 가시화` (BAT-021 / dcy-017, REQ-037) 전략 이탈로 슬롯이 안전망(워치독) 소유로 넘어가거나 실제 청산이 발동하는 결과 기반 신호를 무성에서 가시화한다(issue #12). best-effort — 어떤 로깅/메트릭 실패도 워치독 판정·청산을 막지 않는다(NFR-1). 1. 워치독 소유 진입/이탈: `run_once` 가 매 tick `_update_ownership` 로 holding+워치독 만료(grace 밖) 슬롯 집합을 직전 tick 과 비교 → **진입(전략 이탈)=WARN**, **이탈(복귀)=INFO** + 카운터(`owned:enter`/`owned:exit`). grace 중엔 판정 안 함. 2. 발동 가시화(OQ-2 차등): **WARN** = `forced_close_at`·`stop_loss` 발동 / **INFO** = `take_profit`·`market_close_liquidation` 발동 + 카운터(`fire:{reason}`). 모든 로그는 `slot_id`·`portfolio_id`·`reason`·`phase`·`instance_key` 구조화 필드 포함. 3. per-tick 스킵/no-fire 평가는 로그하지 않고 카운터만(30초 주기 로그 스팸 방지; 영속 거부 반복 경고는 REQ-052 가 별도 담당). 4. WatchdogStore 가 in-memory 라 소유 *상태* 는 재시작 시 소실(폴링 재개 시 재구성); 발동 *로그* 는 이력으로 남는다(R-3). 메트릭은 in-process 카운터(`SafetySellService.counters`). ## Flow: `kill-switch 비상 정지` (BAT-012 / REQ-043/045/046) 운영자가 `trex.control.req` 로 비상 정지를 발동해 범위별 holding 을 즉시 청산하고 당일 전략을 차단한다. 1. `trex-control-rpc` 가 `{action:"kill_switch", scope, portfolio_id?}` 수신 → `KillSwitchService.fire()`. 2. 대상 범위(portfolio 또는 전역)의 `holding` 슬롯을 `SlotService.sell(actor="kill-switch", reason="emergency_stop")` 로 순차 전이(`holding→pending_sell`). 이미 pending_sell 은 idempotent(REQ-036). 이후 체결은 집행 루프가 처리. 3. 발동 범위를 `killswitch_exclusion(portfolio_id|'*', trading_date)` 에 day-scoped 로 표시(REQ-044) + 발동 이력 로깅(actor/scope/처리 수). 4. masking: 이후 snapshot RPC 응답 산출이 `mask = pending|warning|pause|killswitch_today` 로 대상 자산을 제외 → 당일 추가 매수 차단. 전역(`'*'`)이면 전체 universe 마스킹. 5. 자동 복귀: `killswitch_exclusion` 은 `trading_date` 키라 다음 거래일 조회에서 빠짐(별도 동작 불필요). 재시작해도 같은 거래일이면 영속 유지. 6. 수동 해제: `{action:"resume_kill_switch", scope, portfolio_id?}` → `KillSwitchService.resume()` 가 day-scoped row 삭제 → 다음 응답부터 정상 노출. **이미 청산된 슬롯은 비가역**(매도 되돌리지 않음). 7. 독립성: kill-switch 는 자율 안전 매도 worker(dcy-007)와 스레드/상태 공유 없음. 동일 슬롯 동시 sell() 은 REQ-036 idempotency 로 1회만 전이. ## Flow: `multi-instance 라우팅` (BAT-013 / REQ-049/050) 다중 인스턴스를 동일 RabbitMQ 에 붙여도 split-brain 없이 운영하기 위한 큐/exchange 분리. 1. 각 인스턴스는 `--config inst-N.toml`(`instance_key`)로 기동 → 수신 큐명이 `{base}.{instance_key}` 로 분리(`default` 는 리터럴 유지, 하위호환). 2. `declare_topology(instance_key)`: `trex.direct`(topic) + `trex.broadcast`(fanout) 선언 + 인스턴스 intent/snapshot/control 큐를 `trex.direct` 에 `{instance_key}.{channel}` 로 바인딩 + control 큐를 `trex.broadcast` 에 dual 바인딩. 3. **directed**: strategy/운영자가 `trex.direct` 에 `inst-1.intent` 로 publish → `trex.intent.req.inst-1` 에만 전달(다른 인스턴스 큐 미수신). 인스턴스별 WatchdogStore/SafetySellService 가 자기 큐만 소비 → split-brain 방지(NFR-3). 4. **broadcast**: `trex.broadcast` 에 publish → 모든 인스턴스 control 큐 동시 전달(global kill-switch 등). 중복 처리는 idempotency 로 무해. 5. 기존 reply-to RPC 직접 큐 경로(default)는 그대로 동작 — 추가 바인딩은 비파괴적(FR-5 무회귀). ## Flow: `중장기(멀티데이) 인스턴스 병행 운영` (BAT-041 / dcy-027, REQ-076~078) 중장기 매매 전략용 인스턴스를 당일 인스턴스와 같은 인프라(PostgreSQL·RabbitMQ)에서 병행 운영하는 프로파일. 별도 도메인 없이 영구 자산 슬롯(dcy-020) + per-instance 설정 조합으로 구성한다(dcy-027 OQ-1 (a′)). 1. **분리 기동**: 중장기 데몬은 전용 `instance_key`(예: `longterm`) TOML 로 기동(`config/longterm.example.toml` 표준 레시피) — 슬롯(DB instance_key 스코프)·큐(`trex.slots.req.longterm` 등)가 당일 인스턴스와 무간섭(dcy-027 A-2, dcy-009 토폴로지). 2. **프로파일 구성**: `safety_sell_enabled = false`(자율 청산 워커 미기동, BAT-036) + `watchdog_timeout_sec = 1800` 이상(REQ-076 per-instance TOML — TOML 우선, 미설정 시 env `TREX_WATCHDOG_TIMEOUT` 폴백 기본 120s) + `max_active_slots`(REQ-060) + env `TREX_MARKET_CLOSE_LIQUIDATION_TIME` 미설정. 3. **보유 유지**: 영구 자산 슬롯은 날짜 키가 없어 holding 이 장 마감·일자 경계·재시작을 넘어 DB 에 유지된다 — 일일 리셋/강제 청산 없음. 4. **보호 규칙 경계 (dcy-027 OQ-2)**: `safety_sell_enabled=false` 인스턴스는 **코어 손절매 루틴을 일절 사용하지 않는다**. stop-loss·트레일링 스톱·타임스톱 등 모든 보호 규칙은 **전략 모듈의 전적 책임**이며, 코어가 제공하는 유일한 백스톱은 운영자 CLI `trex-core exit `(수동 청산)다. 전략 모듈 장애 시 손실 포지션 방치 위험은 전략 설계 + 운영 절차(수동 CLI)로 완화한다. 5. **forced_close_at 미사용 (dcy-027 CONFIRM)**: 중장기 인스턴스에서 `forced_close_at` 은 **의미가 없다** — 집행 주체(safety-sell 워커)가 OFF 이므로 구조적으로 발화 불가. 중장기 슬롯에 설정하지 않는다(타임스톱이 필요하면 전략 모듈이 sell intent 로 구현). 6. **watchdog 중장기 의미**: 전략 폴링 주기 1~10분 전제(dcy-027 OQ-4)에서 timeout 1800s 는 `watchdog.expired` 를 유의미한 이탈 신호로 유지한다. `last_seen` 가시성(slots RPC 행)은 그대로. 워치독 소유권 진입 시 외부 알림(슬랙·이메일)은 후속 Discovery. ## Flow: `broker degrade 분기` (BAT-007 / REQ-024) 1. transport connection drop → 어댑터가 `_mark_disconnected()` + 백오프(1s→2s→4s→8s→16s→30s cap) 재연결. 2. application 레이어는 `transport.health()` 가 `disconnected/reconnecting` 인 동안 신규 발행/RPC 가 `TransportUnavailableError` 로 빠르게 실패한다 → 전략은 매매 보류. 3. core 자율 안전 매도(stop-loss·take-profit / forced_close / 장 마감 청산)는 transport 와 무관하게 `trex-safety-sell` worker 가 수행한다(아래 safety-sell 흐름; dcy-007 REQ-037~041 구현, BAT-010/011). 이전 "A-5 미구현" 항목은 dcy-007 로 해소됨. 4. 로컬 버퍼링 없음 — broker 복구 후 옛 intent 가 자동 재생되지 않는다(전략이 신선 상태로 재판단). ## Flow: `주문 집행 루프` (백그라운드, BAT-004) 데몬 lifecycle 에서 시작되는 단일 백그라운드 execution 루프가 주기적으로(설정 간격) 수행: 1. dispatch: `list_undispatched_pending(instance_key)`(status pending_* AND `broker_order_id IS NULL`; 인스턴스 스코프, REQ-056) → `BrokerPort.submit_order` → `mark_dispatched(broker_order_id)`(멱등 가드). 거부 시 `pending_buy→inactive` / `pending_sell→holding` 롤백. - **매도 수량 = 브로커 잔고 정본**(dcy-020/REQ-062, D3): 매도 dispatch 는 `broker.get_balance()` 의 해당 symbol `hldg_qty` 로 수량을 정한다(슬롯 미러 `qty` fallback 없음). 브로커 0 이면 주문 미제출 + WARN + `sell_skipped_no_balance` 카운터(divergence 는 reconcile 경로로 표면화). skip 된 매도가 큐를 막지 않도록 리스트 기반 dispatch. - 가격/주문유형 결정(dcy-012/REQ-013, issue #10): `_dispatch` 는 side 분기 없이 슬롯 `limit_price` **한 필드**만 읽는다 — 값이 있으면 지정가(`order_type="00"`, 그 가격), 없으면 시장가(`order_type="01"`). `limit_price` 는 pending 진입 시 set 되고 pending 이탈(→holding/→closed) 시 clear 되므로, 매수 지정가(과거 `entry_price`)가 매도 주문에 재사용되지 않는다. 자율 매도(safety-sell/kill-switch)는 가격 미전달 → 시장가로 즉시 체결(미체결 `pending_sell` 갇힘 방지, issue #9 연계). - **슬리피지 보호 지정가(dcy-022/REQ-070)**: 슬롯에 `slippage_pct` 가 있고 `limit_price` 도 있으면, 실효 지정가 = `limit_price×(1+pct/100)` (매수, ceil) / `limit_price×(1-pct/100)` (매도, floor). `limit_price` 없으면(시장가) `slippage_pct` 무시. 미지정 슬롯은 기존 동작(회귀 없음). 2. poll: `list_dispatched_pending(instance_key)`(broker_order_id NOT NULL) → `BrokerPort.poll_fill` → 체결 시 `pending_buy→holding`(avg_price/qty) / `pending_sell→inactive`(점유 초기화·자산 정체성 보존), 거부 시 롤백. 전이 후 `clear_dispatch`. - 외부 취소 감지(dcy-013/REQ-012/013, issue #9): 계좌에서 직접 취소된 주문을 KIS `poll_fill` 이 `cncl_yn="Y"` 로 파싱한다. **체결 0 취소** → `FillStatus(state="cancelled")` → 기존 롤백 경로(`pending_sell→holding`, `clear_dispatch`)로 자동 복구(영구 갇힘 제거). **부분 체결 후 취소**(`is_partial_cancel`) → 체결분 원장 1회 기록 + 슬롯 qty 차감(매도: 잔여 held, cost basis 유지 / 매수: 체결분만) + `holding` 복귀(OQ-1 A안, 과매도·이중기록 방지). - pending stale 워치독(dcy-013/REQ-014 FR-3, Alert-only): `accepted`/`partially_filled`(취소 아님)로 다음 tick 대기 중인 슬롯이 `TREX_PENDING_STALE_THRESHOLD`(초, 0=비활성)를 초과해 머물면 WARN 로그 + `ExecutionResult.stale_pending` 카운터로 가시화한다. **상태는 변경하지 않는다**(auto-remediation 후속) — 취소 감지 누락(R-1) 등 미지 원인의 무한 갇힘이 silent 로 남지 않게 하는 안전망. - **timeout 자동 취소(dcy-022/REQ-069)**: `accepted`/`partially_filled` 상태 슬롯에 `timeout_sec` 가 있고 경과 시간(`now - slot.updated_at`)이 초과하면 `broker.cancel_order` 를 best-effort 로 송신하고 `ExecutionResult.timeout_cancelled` 를 증가시킨다. **슬롯 상태는 이 시점에 변경하지 않는다** — 다음 poll 에서 `is_terminal_reject`(전량 취소) → rollback / `is_partial_cancel`(부분 체결 후 취소) → 부분 보존(기존 경로 재사용). 취소-체결 race: `cancel_order` 가 이미 체결된 주문을 받으면 브로커가 거부 → 다음 poll `is_filled` → 정상 체결 경로 수렴(임의 롤백 없음, FR-4). 기존 `timeout_sec` 없는 슬롯은 stale-alert 만 실행(REQ-014 회귀 없음). 3. reconcile(재기동): 서빙 전 `list_dispatched_pending` 재폴링으로 크래시 후 복구(중복 주문 0). 미디스패치(broker_order_id NULL) 슬롯은 다음 dispatch 가 1회만 송신. - 모든 슬롯 쓰기는 SlotService(advisory lock) 단일 경로(불변식 A). 브로커 주문/취소는 BrokerPort 단일 세션만(불변식 B). - 슬롯 `limit_price` 생애주기(dcy-012/REQ-002): INACTIVE→PENDING_BUY(매수 지정가 set) → PENDING_BUY→HOLDING(clear) → HOLDING→PENDING_SELL(매도 지정가 set 또는 None=시장가) → PENDING_SELL→INACTIVE(clear). 롤백(→INACTIVE) 전이에서도 clear. 외부 취소 후 holding 복귀에서도 clear 되어 재매도는 시장가. - **실행 정책 생애주기(dcy-022/REQ-068)**: `timeout_sec`·`slippage_pct` 는 `limit_price` 와 동일한 생애주기(PENDING_* 진입 시 set, HOLDING/INACTIVE 탈출 시 clear). 슬롯의 `updated_at` 이 dispatch 시각 proxy 로 사용된다(mark_dispatched 가 갱신, stale-alert 와 동일 방법). - 어댑터: paper(결정적 async 더블) / KIS(모의투자 검증, 실계좌 게이팅). ### 운영자 cancel 백스톱 (dcy-022/REQ-071) timeout 미지정·취소 실패로 잔여 고착된 pending 슬롯을 처분: 1. 운영자 `trex-core cancel ` → `trex.control.req` 에 `{action:"cancel_pending", slot_id}` 전송. 2. 제어 핸들러가 `ExecutionService.cancel_pending_slot(slot_id)` 호출 → 슬롯 pending 여부 확인 → `broker.cancel_order` 송신(슬롯 직접 전이 없음). 3. 다음 집행 루프 poll 에서 `is_terminal_reject`/`is_partial_cancel` 경로로 rollback/부분 보존 수렴(단일 writer 불변식 A 보존). - 비-pending(holding/inactive) 슬롯 → `{status:"ignored", reason:"not_pending"}`. 미존재 → `{status:"error", reason:"slot_not_found"}`. ## Flow: `체결 → ledger outbox` (BAT-006) 집행 루프 fill 전이 직후 `ExecutionService` 가 ledger 기록을 outbox 에 적재해 슬롯 전이 critical path 와 ledger write 를 분리한다(원장 기록 단일 주체 = 집행 경로, NFR-3). - ledger skip 가시화(dcy-011/REQ-018, issue #11): `_record_ledger` 가 `portfolio_id` 또는 `execution_id` 누락으로 skip 하면 무성 반환하지 않고 **WARN 로그 + `ExecutionResult.ledger_skipped` 카운터**로 노출한다(슬롯 전이는 그대로 진행). portfolio_id 필수화(REQ-007)로 주원인은 제거됐고, 잔여 누락은 운영 점검 쿼리(REQ-055: closed 슬롯 ↔ `ledger_outbox` anti-join, `find_ledger_gap_slots`)로 영구 탐지한다. 1. fill 감지: `BrokerPort.poll_fill` 이 `state="filled"` 와 결정적 `execution_id`(paper: `paper:{broker_order_id}:{filled_qty}` / KIS: `kis-daily-ccld:{ord_dt}:{ord_gno_brno}:{odno}:{tot_ccld_qty}`), 계산된 `fee`(`calculate_fee(side, amount)`)·`tax`(`calculate_tax(side, amount)`)를 운반. fee/tax 는 단일 정책 지점(`broker_port.py`)에서 env 로 관리한다(dcy-026/REQ-018 CHG-20260612-01): `TREX_KIS_FEE_BPS`(기본 1.5 = 0.015%, 매수·매도 공통)·`TREX_KRX_TAX_SELL_BPS`(기본 20.0 = 0.2%, **매도 증권거래세**; 매수 tax=0). paper·KIS 가 동일 정책을 공유하며, KIS daily-ccld 응답은 per-fill fee/tax 필드를 제공하지 않아 정책 추정값을 사용한다(향후 노출 endpoint 도입 시 브로커 정본 우선). 2. 슬롯 전이: `pending_buy→holding`(avg_price/qty) 또는 `pending_sell→closed` 를 advisory lock 안에서 먼저 commit. 이 단계가 critical path. 3. ledger enqueue: 슬롯의 `portfolio_id`(== ledger `asset_id`, OQ-1 결정) 가 있으면 `LedgerTrade(asset_id, order_id, execution_id, side, qty, price, fee, tax, executed_at)` 을 `LedgerOutbox.enqueue` 로 적재. 행 식별자는 `correlation_id = {asset_id}:{order_id}:{execution_id}`(로컬 dedup). `portfolio_id` 부재면 ledger 기록 skip 후 슬롯 전이는 정상 유지. 4. `clear_dispatch` 로 `broker_order_id` 해제. ## Flow: `ledger writer` (백그라운드, BAT-006) 데몬 lifespan 에서 별도 백그라운드 워커가 ledger outbox 를 drain 한다. 슬롯 전이와 분리되어 eventually-consistent. 1. `LedgerOutbox.list_pending(limit)` 으로 pending work item 을 FIFO 로 가져온다. **백오프 중(`next_retry_at > now`)인 행은 건너뛴다**(dcy-015/REQ-019 head-of-line 보호, NFR-3). 2. row 의 현재 상태가 이미 `completed` 면 (e.g. 동시/재처리) `duplicate_completed` 로 마킹하고 어댑터를 호출하지 않는다. 3. `LedgerPort.record_trade(trade)` 호출 → `LedgerWriteOutcome{result, cash_balance_after?, asset_quantity_after?}`. - `result=NEW` → outcome `completed`, row status `completed`. - `result=DUPLICATE_OK`(어댑터 server-side dedup) → outcome `duplicate_completed`, row status `completed`, ledger 중복 0. - **option D write-back(dcy-018/REQ-019/030)**: `new`·`duplicate` outcome 의 `cash_balance_after`/`asset_quantity_after`(None 아님)를 `SnapshotStore.update_cash_position(asset_id, …)` 로 반영(best-effort — 실패해도 ledger outcome 무차단, NFR-1). `*_after` 가 **절대 권위값**이라 duplicate 도 멱등. 응답 누락(구 hub)·409/422 는 write-back skip. - `LedgerConflictError`(portfolio-hub 409) → outcome `conflict_blocked`, row status `conflict_blocked` (자동 재시도 중단, 운영자 해소 대상). - `LedgerPermanentError`(portfolio-hub **404/400/422**, dcy-015/REQ-017, issue #13) → outcome `permanent_failure`, row status **`delivery_failed`**(터미널, 재시도 0). 재시도로 해소 불가한 클라이언트 오류(미등록 포트폴리오 등) → 즉시 중단 + WARN. - `LedgerRetryableError`(timeout/5xx/network/408/429) → **지수 백오프 + jitter**(`delay = min(base × 2^(attempts-1), cap)`, `next_retry_at` 설정) 후 다음 due tick 에 재시도. **`max_attempts` 초과 시** outcome `dead_lettered`, row status **`delivery_failed`**(dead-letter, 터미널) + WARN. 무한 1초 재시도 제거. 4. 터미널 실패 가시화(dcy-015/REQ-018): `delivery_failed`(permanent/dead-letter)는 `WriterStats.delivery_failed` 카운터 + WARN 로그 + 구분 status 로 노출되어 정상 `pending` 과 구별된다(무성 영속 실패 금지). dead-letter 는 폐기가 아니라 운영자 재처리 가능 상태이며 `find_ledger_gap_slots`(REQ-055)·skip 가시화와 연계해 누락을 추적한다. 5. 백오프 파라미터는 env 설정: `TREX_LEDGER_RETRY_BASE_SEC`(기본 1.0)/`TREX_LEDGER_RETRY_CAP_SEC`(기본 300)/`TREX_LEDGER_MAX_ATTEMPTS`(기본 8). 6. portfolio-hub 장기 장애에도 outbox 가 적재되어 무유실. 재시작 시 PostgreSQL `ledger_outbox.status='pending'` 행을 그대로 이어 처리한다(터미널 `delivery_failed`/`conflict_blocked` 는 제외). ## Shared Components - SlotRepository Protocol :: 저장소 추상화(자산 중심: list/count/reconcile 모두 instance_key 스코프). 구현체(InMemorySlotRepository, PostgreSQLSlotRepository)를 주입. - slot_advisory_lock_id(instance_key) :: 인스턴스 스코프 advisory lock 식별자 — 공유 DB 의 인스턴스 간 직렬화 격리(REQ-057 NFR-1). - transactional_advisory_lock :: 단일 트랜잭션 + advisory lock 컨텍스트(외부 I/O 금지). ## Flow: `slot 상태 전이 (transit_slot)` (BAT-030 자산 중심) 1. 호출자가 `transit_slot(slot_id, from_state, to_state, reason, actor, **fields)` 를 호출한다. 2. (from_state, to_state) 가 전이 패밀리(7 base[dcy-021 holding→pending_buy 증액 포함] + *→error + error→{inactive,holding} = 13)에 속하는지 먼저 검증한다(아니면 SlotTransitionError). 3. 인스턴스 스코프 advisory lock(`slot_advisory_lock_id(instance_key)`) 획득 후 슬롯을 fetch 한다(없으면 SlotNotFoundError). 4. 현재 status == from_state 인지 확인한다(아니면 SlotStateMismatchError — 레이스 감지). 5. to_state 의미에 맞춰 필드를 계산하고 슬롯 행을 update 한다 — **INACTIVE 진입 시 포지션·주문 점유 필드 초기화 + 자산 정체성(instance_key/portfolio_id/symbol/retired_at) 보존**. 6. slot_transition_log 에 전이 기록 1행을 append 하고 갱신된 슬롯을 반환한다. 7. 외부 I/O(브로커 등)는 lock 밖에서 호출자가 수행한다(lock 보유 중 금지). ## Flow: `slot strategy-state 저장소` (BAT-042 / dcy-028, REQ-079~082) 전략 데몬(예: trex-tsl)이 슬롯 점유 동안 누적하는 opaque 상태(예: TSL peak)를 슬롯 수명에 묶어 server-side 로 보관한다. core 는 `data` 를 해석하지 않는다(opaque UTF-8 + byte size cap 만 검증). 1. **read (piggyback, C-1)**: 데몬이 매 사이클 `trex.slots.req` 로 슬롯을 읽으면 각 행에 `occupancy_seq`(int)와, 보유 시 `strategy_state` 맵(`{: {version,data,updated_at}}`)이 함께 실린다. 맵은 핸들러가 반환 slot_id 들로 `StrategyStateStore.get_for_slots()` 1회 batched 조회 후 group by 해 조립한다(추가 왕복 없음). 슬롯 현재 occupancy_seq 와 다른 stale 엔트리는 노출하지 않는다. 2. **evaluate (pure)**: 데몬은 행의 `strategy_state[strategy_id]` 를 read-only 입력으로 `evaluate()` 에 넣는다(상태 write 는 evaluate 밖, 엔진이 수행). 3. **write (별도 RPC, C-2)**: 상태가 바뀌면 `trex.slots.strategy_state.set` 으로 `{slot_id, strategy_id, expected_version, expected_occupancy_seq, data}` 를 보낸다. core 는 슬롯의 현재 occupancy_seq·active 여부를 조회해 CAS 검증: 점유 불일치 → `occupancy_changed`, version 불일치 → `version_conflict`, inactive/미존재 → `slot_inactive`, size 초과 → `state_too_large`, 일치(없으면 expected_version=0) → commit + version+1. malformed → `bad_request`. 동일 입력 재전송은 무부작용. 4. **occupancy 가드 (C-2b, R-1)**: 슬롯은 monotonic `occupancy_seq` 를 갖고 새 점유 시작(`inactive→pending_buy`)마다 ++. 영구 슬롯 재사용 시 read v0→close→reopen→write v0 의 ABA(옛 점유 계산이 새 점유에 바인딩)를 occupancy_seq CAS 가 구조적으로 차단한다. 5. **close 삭제 (C-3)**: 슬롯이 어떤 경로로든 `→INACTIVE`(전략 매도/safety-sell/forced-close/operator) 되면 `transit_slot` 단일 전이 지점에서 해당 slot_id 의 모든 strategy_state 를 삭제한다. 재사용 슬롯은 새 occupancy_seq + 빈 상태로 시작 — 전략/데몬은 cleanup 책임이 없다(슬롯이 source of truth + GC). 저장은 별도 `slot_strategy_state` 테이블(복합 PK `(slot_id, strategy_id)`), 슬롯 본체에는 `occupancy_seq` 컬럼 1개만 추가(full 3계층 분할은 후속 Discovery). full 3계층(occupancy 테이블 분할)·다중 writer 동시성·장기 보존은 범위 밖. ## Flow Constraints - 모든 상태 변경 API 는 advisory-locked 단일 트랜잭션 안에서 수행한다. - lock 보유 중에는 외부 I/O 를 수행하지 않는다. - slot_transition_log 는 append-only 다(UPDATE/DELETE 금지). - 상태 전이는 read(현재 상태)-검증-write 가 동일 트랜잭션에서 원자적으로 이뤄져 레이스를 차단한다. ## Current Gaps / Planned Changes - 실제 영속화(PostgreSQL) 흐름은 미구현 — 현재는 in-memory repository 로만 구동. - CLI/데몬 런타임 흐름 없음(library). 상위 애플리케이션 통합 흐름은 영속화 단계 이후 정의. - 강제 청산/EOD 같은 상위 운영 흐름은 이번 순수 도메인 범위 밖. ## Update Triggers - Update this document when a command entry point changes. - Update this document when a runtime orchestration path changes. - Update this document when a batch changes the approved flow baseline. ## Change Log - 2026-06-03 :: bootstrap-baseline 으로 초기 흐름 baseline 생성(slot 도메인 in-process API 흐름 기준). - 2026-06-03 :: BAT-001 이식 완료 반영 — preallocate/transit 흐름이 `trex_core.domain.slot` 실제 코드로 구현됨(REQ-004). 영속화/CLI 흐름은 여전히 미구현(범위 밖). - 2026-06-03 :: BAT-002 — 데몬 lifecycle + 매수/매도 intent HTTP 흐름 추가, Primary Runtime=daemon+library(REQ-005~009). 브로커 집행/safety floor 흐름은 후속 Discovery. - 2026-06-04 :: BAT-004 — 브로커 집행 레이어 반영(BrokerPort/paper·KIS 어댑터, execution 루프, broker_order_id 실행큐·멱등, reconciliation) (REQ-011~015). - 2026-06-04 :: BAT-006 — ledger 통합 반영(체결→ledger outbox enqueue, LedgerWriter retry/dedup/409 흐름, FillStatus.execution_id/fee 운반, slot 전이와 ledger write 분리) (REQ-017~020). - 2026-06-05 :: BAT-007 — transport 전환(HTTP `/v1` API 제거, RabbitMQ 메시징 도입: 스냅샷/intent RPC + 이벤트 topic, sync+thread daemon, intent_log 멱등 ack-after-commit, publisher confirms + DLQ, per-strategy durable queue + TTL/max-length, broker degrade 분기) (REQ-021~026). - 2026-06-06 :: BAT-008 — portfolio-hub 스냅샷 read 레이어 추가(`PortfolioHubReadPort` + hub_read/stub_read 어댑터, `SnapshotStore` in-memory + `snapshot_pause` Postgres, `SnapshotScheduler` 일일 refresh, `reconcile_positions` 정합 검증 + `BrokerPort.get_balance()` 확장, `trex.snapshot.req` envelope 응답 + degraded 신호, `trex.pause.req` 제어 RPC, `trex-core` CLI) (REQ-027~033; 옵션 D AC-2/AC-3 은 portfolio-hub#8 배포 후 별도 구현). - 2026-06-10 :: BAT-030 (dcy-020 마감) — 자산 중심 전환 완료: 일일 preallocation 폐기(→universe reconcile), intent admission slot_id 정본 + M 캡, 집행 매도 수량 브로커 정본 + 인스턴스 스코프 dispatch, 5-state(inactive) 전이, DB 재초기화 도구(`trex-core-reset`, 데이터 보존 cutover 대체 — REQ-064 CHG-20260610-03) (REQ-056~065). - 2026-06-15 :: BAT-042 (dcy-028) — slot strategy-state 저장소: opaque per-strategy state(별도 `slot_strategy_state` 테이블), read piggyback(`trex.slots.req` 행에 `strategy_state` 맵+`occupancy_seq`), write 별도 RPC(`trex.slots.strategy_state.set`, occupancy_seq+version CAS), occupancy_seq 점유 가드(점유 전환 ABA 차단), close(`→INACTIVE`) 시 자동 삭제 (REQ-079~082, REQ-073/074 CHG). full 3계층 분할은 후속. ## Flow: `포지션 사이징` (BAT-031 / dcy-021, REQ-066) 보유(holding) 자산 슬롯의 수량을 명령형으로 조정한다. 전략이 스냅샷 cash+quantity 로 qty+price 를 계산해 보내고 core 는 실행만 한다(target 미저장). 1. **증액**: 보유 슬롯에 매수 intent(qty+price) → admission 이 `holding→pending_buy`(M 캡 미적용, active 수 불변) → dispatch 매수(qty=주문) → 체결 `pending_buy→holding`: 보유 qty=**브로커 잔고**, avg_price=가중평균(`(old_qty×old_avg+filled×fill)/new_total`, old_qty=브로커총−체결). 거부 시 브로커 잔고>0 → holding 복귀(기존 보유 유지), 0 → inactive. 2. **부분 감액**: 보유 슬롯에 매도 intent(qty+price) → `holding→pending_sell`(slot.qty=주문) → dispatch 매도 `min(주문, 브로커잔고)` → 체결 후 브로커 잔고>0 → `pending_sell→holding`(잔량, cost basis 유지) / 0 → `pending_sell→inactive`. 전량 매도(qty 미지정, safety-sell/kill-switch)는 slot.qty=None → dispatch=브로커 잔고. 3. **부분 체결**: 실제 체결분만 ledger(REQ-018/019)·슬롯 반영(브로커 정본 resync), 미체결 잔량은 core 추적 안 함 — 전략이 다음 스냅샷 기준 재발행. - 2026-06-11 :: BAT-035 (dcy-025) — `trex.snapshot.req` → `trex.slots.req` lockstep 교체: `trex-snapshot-rpc` → `trex-slots-rpc` worker, `make_snapshot_rpc_handler` → `make_slots_rpc_handler`, `build_snapshot_envelope`/`View` 제거. 슬롯 RPC 응답 산출 flow, 워치독 spy=true 갱신 억제 flow 추가. 운영자 CLI flow: `trex.snapshot.req` → `trex.slots.req` (REQ-073/074/075). - 2026-06-11 :: BAT-036 (dcy-024) — 데몬 lifecycle: `safety_sell_enabled=false` 시 `trex-safety-sell` worker 미기동 + WARNING 로그(REQ-048 CHG-01/REQ-005 CHG-01). - 2026-06-10 :: BAT-031 (dcy-021) — 포지션 사이징(증액·부분 감액) 흐름 추가, transit 13 전이. - 2026-06-12 :: BAT-038 (dcy-026/issue #24) — 매도 증권거래세 누락 정정: `체결 → ledger outbox` flow 의 `tax=0` → `calculate_tax(side, amount)`(매도 0.2%/매수 0) 정책 계산. fee/tax 단일 정책 지점 + env(`TREX_KIS_FEE_BPS`/`TREX_KRX_TAX_SELL_BPS`), paper·KIS 동일 공유, `tax=0.0` 하드코딩 제거 (REQ-018 CHG-20260612-01). - 2026-06-12 :: BAT-041 (dcy-027/issue #20 이월) — 중장기 인스턴스 병행 운영 flow 신설: 전용 instance_key 프로파일(`config/longterm.example.toml`), `watchdog_timeout_sec` per-instance TOML(REQ-076, env 폴백), 보호 규칙 경계(전략 모듈 전적 책임 + CLI exit 백스톱, 코어 손절매 미사용), forced_close_at 중장기 미사용 (REQ-076~078).