Co-authored-by: genichin <4282227+genichin@users.noreply.github.com>
419 lines
24 KiB
Markdown
419 lines
24 KiB
Markdown
# trex-core 운영자 런북 (Operations Runbook)
|
|
|
|
> 대상: trex-core 데몬을 배포·운영·디버깅하는 운영자.
|
|
> 범위: 배포/실행, 운영자 CLI, 로깅/관측성, 디버깅/트러블슈팅.
|
|
> 출처: dcy-029(REQ-089) — `.claude/skills/run-trex-core-daemon/SKILL.md` 사실 자료 + 관측성 기능(REQ-084~088) 사용법 통합.
|
|
|
|
---
|
|
|
|
## 1. 배포 / 실행
|
|
|
|
trex-core 데몬은 **HTTP 표면이 없다**(BAT-007). 모든 전략-facing 트래픽은 RabbitMQ로 흐른다(스냅샷/슬롯 RPC, intent RPC, 슬롯 이벤트). 운영자 조작은 `trex-core` CLI.
|
|
|
|
### 필수 인프라
|
|
PostgreSQL + RabbitMQ가 떠 있어야 한다. (paper 브로커는 외부 증권사 불필요.)
|
|
|
|
### 환경 변수 (`config/.env`, gitignore)
|
|
| 변수 | 필수 | 설명 |
|
|
|---|---|---|
|
|
| `TREX_POSTGRESQL_DSN` | 예 | psycopg DSN |
|
|
| `TREX_RABBITMQ_URL` | 실사용 시 예 | 예 `amqp://admin:admin@localhost:5672/` (미설정 시 in-memory stub — 전략 모듈 도달 불가) |
|
|
| `TREX_BROKER` | 아니오 | `paper`(기본) 또는 `kis` |
|
|
| `TREX_PORTFOLIO_HUB_BASE_URL` | 아니오 | 설정 시 실제 portfolio-hub universe+ledger, 미설정 시 stub universe(pf-005930/pf-000660) |
|
|
| `TREX_LOG_FORMAT` | 아니오 | `text`(기본, 사람용) 또는 `json`(Loki 적재용) — §3 |
|
|
| `TREX_EXECUTION_INTERVAL_SEC` / `TREX_LEDGER_INTERVAL_SEC` | 아니오 | 루프 간격(기본 1.0) |
|
|
|
|
### 인스턴스 TOML
|
|
`--config <name>` (BAT-013 이후 필수)는 `config/<name>.toml`을 해석 — `instance_key`(큐 이름 `trex.intent.req.<key>` 등; `default`는 literal), `max_active_slots`(M cap).
|
|
|
|
### 기동 / 종료
|
|
```bash
|
|
# 기동 (로그 리다이렉트). 테스트는 trex_core_test DB로 — 라이브 DB 금지.
|
|
.venv/bin/trex-core-daemon --config <instance> &> /tmp/trex-core.log &
|
|
|
|
# 종료 (SIGTERM → 워커 join + RabbitMQ close, 마지막 로그 'Normal shutdown')
|
|
pkill -TERM -f trex-core-daemon
|
|
```
|
|
데몬은 TCP 포트를 열지 않는다. "ready" = 4개 워커 스레드 + RabbitMQ 토폴로지 선언 완료. `/tmp/trex-core.log`에 ERROR/traceback이 없으면 부팅 성공.
|
|
|
|
### 안전 수칙
|
|
- `config/.env`는 **라이브 KIS 키 + portfolio_hub 토큰 + 라이브 PostgreSQL DSN**을 담는다 — 커밋 금지, 라이브 DB에 파괴적 작업 금지.
|
|
- 테스트는 `TREX_BROKER=paper` + 파생 `trex_core_test` DSN.
|
|
|
|
---
|
|
|
|
## 2. 운영자 CLI
|
|
|
|
```bash
|
|
.venv/bin/trex-core <command> [options] # 콘솔 스크립트
|
|
```
|
|
|
|
| 명령 | 용도 | 경로 |
|
|
|---|---|---|
|
|
| `slots [--status all\|holding\|buyable\|pending\|error]` | 슬롯/포지션 목록 | RPC |
|
|
| `universe` | 오늘 universe 자산 | RPC |
|
|
| `enter <portfolio_id> [--qty/--entry-price/--stop-loss/--take-profit/--forced-close-at]` | 매수 intent | RPC |
|
|
| `exit <slot_id> [--price]` | 매도 intent | RPC |
|
|
| `cancel <slot_id>` | 대기 주문 취소(백스톱) | RPC |
|
|
| `pause/resume/paused` | portfolio 일시정지 | direct-DB |
|
|
| **`report [--json]`** | **보유·오늘 체결·실현 PnL·대기 주문** | **direct-DB (REQ-088)** |
|
|
|
|
- enter/exit 안전장치: 확인 프롬프트 기본 ON, `--yes`(paper 생략), live(`broker=kis`)는 `--yes --live` 이중 ack, `--dry-run` 미리보기. `--json`은 비대화형이라 enter/exit에 `--yes` 필수.
|
|
- 인스턴스 라우팅: `--config <toml>` 또는 `--instance <key>`, 미지정 시 `default`.
|
|
|
|
### `sync` / `verify` broker truth 확인과 release gate (BAT-057)
|
|
|
|
- `sync`와 `verify`는 broker balance를 먼저 한 번 읽어 해당 실행 안에서 재사용한다. balance/credential read 실패는 안전한 error code와 함께 exit `1`로 종료하며, 빈 broker balance를 추정하지 않는다. 성공한 빈 목록만 flat balance로 취급한다.
|
|
- source evidence에는 mode(`fetched`/`env`/`break_glass`)와 비밀 없는 label만 기록한다. fetched mode는 KIS + `credential_fetch_enabled=true`에서만 사용하고, break-glass는 명시적 운영자 환경 설정으로 fetch를 건너뛴다. Paper와 env KIS 경로는 fetch하지 않는다.
|
|
- reconciliation에서 `unmanaged_position` 또는 `managed_non_holding_divergence`가 보이면 자동 보정·자동 주문·자동 청산이 없다. broker, ledger, slot evidence를 교차 확인하고 warning/slot 상태를 보존한 채 triage한다.
|
|
- `unmanaged_position` triage는 `trex-core unmanaged list`로 현재 lifecycle state를, `trex-core unmanaged audit <symbol>`로 lifecycle/operator audit trail을 확인한 뒤 `trex-core unmanaged record <symbol> <action> --evidence-ref ...`로만 disposition을 남긴다.
|
|
- remediation은 **dry-run → broker/ledger/slot 교차 확인 → 사장님 명시 승인 → 단일 대상 `sync --live --yes` → 재-reconcile** 순서의 별도 release gate다. 이 문서는 실제 target 선택, live 실행 또는 승인을 부여하지 않는다.
|
|
|
|
### `report` — 일일 운영 리포트 (REQ-088 / dcy-029, REQ-110/111 / dcy-035)
|
|
```bash
|
|
.venv/bin/trex-core report # 사람용 표
|
|
.venv/bin/trex-core report --json # 자동화용 JSON
|
|
```
|
|
출력: **현재 보유**(symbol/qty/avg_price), **오늘(KST) 체결 건수**, **WAC 실현 PnL**, **대기 주문**.
|
|
|
|
**WAC 실현 PnL (REQ-110/111, BAT-052)**: portfolio-hub WAC 기반 실현손익(`/pnl/realized`). 매수 fee가 cost basis에 편입돼 멀티데이에도 정확한 취득원가 기준 손익을 반영한다. 매수 전용일에 별도 fee 라인은 없다(매수 fee는 cost basis 편입 → 매도 시 실현).
|
|
- **portfolio-hub 가용 시**: WAC 실현 PnL 섹션에 자산별 분해 + 이력 불완전 경고(data_completeness_warning) 출력.
|
|
- **portfolio-hub 미가용 시**: WAC 실현 PnL 섹션에 에러 메시지 출력. 보유/대기/체결 건수 등 나머지 섹션은 정상 출력. 폴백 없음(당일 net을 WAC 위치에 대체 출력하지 않는다).
|
|
- "오늘" 경계는 trading_date(KST 00:00~익일 00:00). 전일 체결은 제외. 미청산 보유의 평가손익은 포함하지 않는다(실현분만).
|
|
|
|
> 참고: 당일 net 현금흐름(`realized_pnl(trades)` 함수)은 회귀 비교용으로 코드에 보존돼 있으나 report 출력의 기본 경로가 아니다. WAC 경로는 `TREX_PORTFOLIO_HUB_BASE_URL` 설정 시 자동 활성화된다.
|
|
|
|
---
|
|
|
|
## 3. 로깅 / 관측성 (REQ-084~087 / dcy-029)
|
|
|
|
### 포맷
|
|
- 모든 로그 라인: `<ISO8601 KST> <LEVEL> <logger> [cid=<slot_id>] <msg>`.
|
|
- `TREX_LOG_FORMAT=json` → Loki 적재용 한 줄 JSON(`ts`/`level`/`logger`/`msg`/`correlation_id` + 구조화 필드). Grafana/LogQL 대시보드용.
|
|
- **correlation id** = `slot_id` (intent→order→fill 공통 조인 키). 한 슬롯의 submit/fill/reject 로그가 같은 `cid`를 공유 → 한 거래 추적.
|
|
|
|
### 주문 결과 로그 (grep 키)
|
|
```bash
|
|
grep "event=order_submit" /tmp/trex-core.log # 제출 주문
|
|
grep "event=order_fill" /tmp/trex-core.log # 체결(fill_price/filled_qty/fee/tax)
|
|
grep "event=order_reject" /tmp/trex-core.log # 거부 — ERROR 레벨
|
|
```
|
|
- 거부는 **ERROR 레벨**로 즉시 보인다(조용한 롤백 제거). 호가단위 위반(issue #28) 같은 거부가 `event=order_reject ... reason=...`로 노출.
|
|
|
|
### 청산 사유 해석 — reason + actor (REQ-086)
|
|
- `safety-sell fired: ... reason=<r> actor=trex-safety-sell` → **안전망 자율청산**(전략 침묵으로 watchdog가 슬롯 소유). `reason=stop_loss`여도 이는 "전략 손절"이 아니라 안전망이 SL선에서 청산한 것.
|
|
- `actor=execution_loop` → 집행 루프 정상 체결/롤백. 전략 SellIntent 매도는 전략 actor.
|
|
- 즉 **reason(조건)과 actor(주체)를 함께** 보고 안전망/전략을 구분한다.
|
|
|
|
---
|
|
|
|
## 4. DLQ 가시성 / 이벤트 발행 경보 (REQ-093 / dcy-031 / BAT-047)
|
|
|
|
trex-core는 `trex.events.dlq`(durable) 큐와 구조화 로그를 통해 이벤트 발행 실패를 운영자에게 노출한다.
|
|
|
|
### DLQ 유입 원인
|
|
|
|
| 원인 | 경로 | 로그 |
|
|
|---|---|---|
|
|
| 구독자 없는 이벤트(unroutable) | `UnroutableError` → `_publish_dlq(reason="unroutable")` | ERROR `DLQ entry: reason=unroutable rk=...` |
|
|
| DLQ 재발행 자체 실패 | `_publish_dlq` 내 exception | ERROR `DLQ publish failed (rk=...)` |
|
|
| 발행 최종 실패(재연결+재시도 후) | `_make_event_emitter` → `TransportUnavailableError` | ERROR `event publish failed (rk=... slot_id=... strategy=...)` |
|
|
|
|
### 로그 기반 경보 (Loki / grep)
|
|
|
|
```bash
|
|
# DLQ 유입 확인
|
|
grep '"level":"error"' /tmp/trex-core.log | grep "DLQ"
|
|
# 또는 text 포맷
|
|
grep "ERROR.*DLQ" /tmp/trex-core.log
|
|
|
|
# 발행 최종 실패 확인
|
|
grep "event publish failed" /tmp/trex-core.log
|
|
```
|
|
|
|
Loki LogQL:
|
|
```logql
|
|
{job="trex-core"} | json | level="error" | line_contains "DLQ"
|
|
{job="trex-core"} | json | level="error" | line_contains "event publish failed"
|
|
```
|
|
|
|
### RabbitMQ queue depth 확인
|
|
|
|
```bash
|
|
# rabbitmqctl로 DLQ 깊이 조회
|
|
rabbitmqctl list_queues name messages | grep dlq
|
|
|
|
# RabbitMQ 관리 UI: http://<broker>:15672 → Queues → trex.events.dlq
|
|
```
|
|
|
|
### 운영자 조치 절차
|
|
|
|
1. **DLQ 유입 경보 수신**: 로그 `ERROR ... DLQ entry` 발견.
|
|
2. **원인 분류**:
|
|
- `reason=unroutable`: 해당 strategy_id의 전략 데몬(orb/tsl)이 꺼져 있거나 큐 바인딩이 없는 경우. 전략 데몬 기동 상태 확인.
|
|
- `DLQ publish failed`: RabbitMQ 자체 연결 문제. broker 상태 확인.
|
|
- `event publish failed`: 발행 연결 재연결+재시도 후에도 실패. broker 재시작 또는 네트워크 장애. trex-core 재기동 검토.
|
|
3. **DLQ 내용 소비**(선택): 적체된 메시지는 수동 drain 또는 strategy 재기동 후 catch-up.
|
|
4. **임계값 기준**: DLQ depth > 0 = 경보 대상. depth > 100 = 즉시 조치 권고.
|
|
|
|
### idle heartbeat 예방 (REQ-092)
|
|
|
|
trex-core는 발행 연결의 idle 종료를 예방하기 위해 `trex-execution` 루프 각 틱(기본 1초)마다 `event_publisher.keepalive_tick()`(`process_data_events(time_limit=0)`)을 호출한다. `heartbeat=30s` 설정 대비 충분한 I/O 주기를 보장한다.
|
|
|
|
---
|
|
|
|
## 5. 체결가 백필 WARN 발생 시 운영자 점검 절차 (REQ-095/096 / dcy-032 / BAT-048)
|
|
|
|
KIS 체결조회가 체결수량은 보고하면서 평균체결가(`avg`)를 None 또는 0으로 반환하는 순간 레이스가 발생하면, trex-core는 주문 지정가로 백필해 슬롯 평단·원장 cost basis에 0이 저장되지 않도록 보호하고 WARN 로그를 발행한다.
|
|
|
|
### WARN 유형 및 로그 식별
|
|
|
|
| WARN 유형 | `reason` | 발생 조건 | 의미 |
|
|
|---|---|---|---|
|
|
| `avg_price_backfill` | `reason=avg_price_backfill` | avg_price=None/0, limit_price>0 | 지정가로 백필 성공 — 값은 보정됐으나 추적 필요 |
|
|
| `fill_price_unresolvable` | `reason=fill_price_unresolvable` | avg_price=None/0, limit_price=None/≤0 | 백필 불가 — 체결 skip, 슬롯 PENDING 유지 |
|
|
|
|
```bash
|
|
# avg_price_backfill 확인 (백필 성공)
|
|
grep "avg_price_backfill" /tmp/trex-core.log
|
|
|
|
# fill_price_unresolvable 확인 (백필 불가, 운영자 즉시 개입)
|
|
grep "fill_price_unresolvable" /tmp/trex-core.log
|
|
```
|
|
|
|
Loki LogQL:
|
|
```logql
|
|
{job="trex-core"} | json | line_contains "avg_price_backfill"
|
|
{job="trex-core"} | json | line_contains "fill_price_unresolvable"
|
|
```
|
|
|
|
### `avg_price_backfill` WARN 발생 시 점검 항목
|
|
|
|
1. **로그에서 슬롯·주문 정보 확인**: `slot_id`, `symbol`, `broker_order_id`, `avg_price`(원 값, None 또는 0), `backfill_price`(대체 지정가)를 로그에서 추출.
|
|
2. **원장 레코드 확인**: 해당 `slot_id`의 `ledger_outbox` price가 백필값(지정가)으로 기록됐는지 확인.
|
|
```bash
|
|
psql "$TREX_POSTGRESQL_DSN" -c "SELECT * FROM ledger_outbox WHERE correlation_id LIKE '%<broker_order_id>%'"
|
|
```
|
|
3. **슬롯 평단 확인**: `trex-core slots` 또는 DB에서 해당 슬롯의 `avg_price`가 0이 아닌 지정가 기반 값인지 확인.
|
|
4. **판단**: 백필 지정가는 "0보다 합리적인 근사"지만 실제 체결가와 다를 수 있다. 중요한 포지션이라면 KIS HTS/API에서 실제 체결가를 확인해 `ledger_outbox` 수기 보정 여부를 검토한다(Discovery R-1).
|
|
|
|
### `fill_price_unresolvable` WARN 발생 시 즉시 조치
|
|
|
|
1. **로그에서 슬롯 정보 확인**: `slot_id`, `symbol`, `broker_order_id` 추출.
|
|
2. **슬롯 상태 확인**: `trex-core slots --status pending` — 해당 슬롯이 PENDING_BUY 상태로 고착됐는지 확인.
|
|
3. **KIS HTS/API에서 실제 체결 여부와 가격 확인**.
|
|
4. **운영자 조치 선택**:
|
|
- 체결됐다면: `trex-core cancel <slot_id>`로 슬롯을 롤백 후 재진입 매수.
|
|
- 미체결이라면: `trex-core cancel <slot_id>`로 주문 취소 → 슬롯 rollback(다음 폴링에서 is_terminal_reject 경로).
|
|
5. **상황이 반복된다면**: Discovery 또는 이슈 트래커에 기록 — `limit_price=None` 매수 주문이 들어온 경로 조사 필요(정상 운용 시 orb 전략은 지정가 주문을 제출한다, Discovery A-1).
|
|
|
|
---
|
|
|
|
## 6. 디버깅 / 트러블슈팅
|
|
|
|
| 증상 | 원인 | 조치 |
|
|
|---|---|---|
|
|
| 매수가 계속 거부 | (구) 슬리피지 한계가 호가단위 위반(issue #28) | dcy-030으로 수정됨(호가단위 스냅). `grep event=order_reject`로 사유 확인 |
|
|
| `slots returned: 0` | universe reconcile 미실행(hub down/boot race) | 스케줄러 로그 `slot reconcile:` 확인; hub 미설정 시 stub 2슬롯 |
|
|
| intent `rejected: unknown_slot` | slot_id/portfolio_id가 인스턴스 슬롯셋에 없음 | 먼저 `slots`로 조회 후 그 slot_id 사용 |
|
|
| intent `rejected: capacity_full` | `max_active_slots`(M) 도달 | 매도 후 재시도 또는 M 상향 |
|
|
| 매도 미발생 + WARN `sell dispatch skipped` | 브로커 잔고 0(divergence) | reconcile(운영자) — 미러 fallback 없음(설계) |
|
|
| `RpcTimeoutError` | 데몬 미기동/소비 스레드 크래시 | `/tmp/trex-core.log` traceback 확인 |
|
|
| 미체결 `pending_*` 고착 | 취소 감지 누락/timeout 미설정/fill_price_unresolvable | `trex-core cancel <slot_id>` 백스톱; pending stale WARN + `fill_price_unresolvable` WARN 확인(§5) |
|
|
| KIS 접수 뒤 전량 거부인데 `pending_buy` 유지 | 일별 주문조회에서 `tot_ccld_qty=0`, `rjct_qty=ord_qty` 확인 | Core가 `rejected`로 롤백하는지 `event=order_reject`와 슬롯 `inactive`를 확인. 재주문 전 브로커 잔고·미체결을 먼저 대조 |
|
|
| `avg_price_backfill` WARN | KIS avg_price 순간 레이스(None/0 반환) | 슬롯 평단·원장 price는 지정가로 보정됨 — §5 점검 절차 참고 |
|
|
|
|
### DB 직접 조회
|
|
```bash
|
|
# 슬롯 상태 분포
|
|
psql "$TREX_POSTGRESQL_DSN" -c "SELECT instance_key, status, COUNT(*) FROM daily_slot GROUP BY 1,2 ORDER BY 1,2"
|
|
# ledger outbox 상태
|
|
psql "$TREX_POSTGRESQL_DSN" -c "SELECT status, last_outcome, COUNT(*) FROM ledger_outbox GROUP BY 1,2"
|
|
# 슬롯 전이 이력(reason+actor)
|
|
psql "$TREX_POSTGRESQL_DSN" -c "SELECT slot_id, from_state, to_state, reason, actor, created_at FROM slot_transition_log ORDER BY created_at DESC LIMIT 20"
|
|
```
|
|
|
|
---
|
|
|
|
---
|
|
|
|
## 7. 무데이터/죽은 종목 위생 (dcy-017 후속)
|
|
|
|
dcy-017이 "지속 무데이터" WARN 다이제스트를 운영자에게 표시한다. 그 후속 조치로 해당 종목을 매매 유니버스에서 pause 또는 영구 제거하는 절차.
|
|
|
|
> **용어**: `portfolio_id` = trex-core에서 종목을 식별하는 키 (portfolio-hub asset_id와 동일). pause/resume 명령의 대상이다.
|
|
|
|
### 트리거
|
|
|
|
- dcy-017 지속 무데이터 WARN 알람(시세 N회 연속 무응답).
|
|
- 운영자가 직접 관찰: KRX 거래정지/상장폐지 공시, 종목 이상 감지.
|
|
|
|
---
|
|
|
|
### pause 시맨틱 (코드 검증 결과)
|
|
|
|
> 아래는 코드 직접 확인 결과다. 추측 없이 file:line을 근거로 기술한다.
|
|
|
|
**pause가 하는 것 — 신규 진입(매수) 차단**
|
|
|
|
`pause_store.pause(portfolio_id)` = `INSERT INTO snapshot_pause (portfolio_id, ...) ON CONFLICT DO UPDATE` (`adapters/portfolio/pause_store.py:23`). 이후 slots RPC 응답에서 해당 slot의 `paused=True` 필드가 반환된다 (`application/portfolio.py:309-313`, `application/messaging_handlers.py:264-273`).
|
|
|
|
trex-core 코어 자체(`application/intents.py` `_admit_buy`, line 179~257)에는 pause 체크가 없다. **pause는 전략 데몬(orb 등)이 slots 응답의 `paused=True`를 읽고 그 slot에 buy intent를 보내지 않는 방식으로 신규 진입을 차단한다.** 전략 데몬이 pause를 무시하고 buy intent를 보내면 코어는 수락한다(전략 데몬 쪽 정책).
|
|
|
|
**pause가 하지 않는 것 — 기존 보유분 매도는 막지 않는다**
|
|
|
|
`application/safety_sell.py`의 `check_forced_close` / `check_prices` / `check_market_close` 모두 `self._holding(now)` (= `slot_repo.list_holding_slots()`)를 직접 조회하고 pause_store를 참조하지 않는다. tsl 전략 SellIntent, `trex-core exit` 명령도 마찬가지다.
|
|
|
|
**결론**: HOLDING 슬롯이 있는 종목을 pause해도 그 보유분은 safety-sell, tsl 전략, 운영자 exit 등 모든 매도 경로로 정상 청산된다. **pause는 신규 매수만 막고, 기존 포지션의 exit은 방해하지 않는다.** 포지션이 있어도 pause 명령은 안전하다.
|
|
|
|
**resume 원복**
|
|
|
|
`pause_store.resume(portfolio_id)` = `DELETE FROM snapshot_pause WHERE portfolio_id = %s` (`adapters/portfolio/pause_store.py:35`). 삭제 즉시 다음 slots RPC 응답부터 `paused=False`로 반환되어 정상 진입이 재개된다.
|
|
|
|
---
|
|
|
|
### Step 1 — 무데이터 알람 검증
|
|
|
|
알람 수신 후 즉시 종목 상태를 확인한다.
|
|
|
|
```bash
|
|
# 해당 종목의 슬롯 상태 확인 (symbol 또는 portfolio_id로 필터)
|
|
.venv/bin/trex-core slots --symbol <SYMBOL>
|
|
.venv/bin/trex-core slots --status all # 전체 목록에서 확인
|
|
```
|
|
|
|
KRX 공시/HTS에서 상태 분류:
|
|
|
|
| 상태 | 의미 | 권장 조치 |
|
|
|---|---|---|
|
|
| 일시적 시세 장애 | 단기 데이터 공백, 종목 정상 | 관망 → 자동 복구 확인 |
|
|
| 임시 거래정지 (공시/사유 있음) | 거래 재개 예정 가능 | pause (Step 3a) |
|
|
| 장기 거래정지 / 상폐 예정 | 재개 불명확 | pause 또는 영구 제거 검토 (Step 3b) |
|
|
| 상장폐지 완료 | 거래 불가 | 포지션 0 확인 후 영구 제거 (Step 3b) |
|
|
|
|
---
|
|
|
|
### Step 2 — 포지션 확인
|
|
|
|
pause 또는 제거 전에 **해당 종목에 보유 포지션이 있는지** 반드시 확인한다.
|
|
|
|
```bash
|
|
# 1) 운영자 리포트 — 보유·대기 주문 한눈에 확인
|
|
.venv/bin/trex-core report
|
|
|
|
# 2) 해당 종목 슬롯 상세 조회
|
|
.venv/bin/trex-core slots --symbol <SYMBOL>
|
|
# 또는 portfolio_id를 알고 있을 때
|
|
.venv/bin/trex-core slots --status holding
|
|
|
|
# 3) DB 직접 확인 (정밀 조회)
|
|
psql "$TREX_POSTGRESQL_DSN" -c \
|
|
"SELECT slot_id, status, symbol, qty, avg_price, portfolio_id FROM daily_slot WHERE symbol = '<SYMBOL>'"
|
|
```
|
|
|
|
출력의 `status` 확인:
|
|
- `inactive` — 포지션 없음. pause 또는 제거가 자유롭다.
|
|
- `holding` — **보유 포지션 있음.** pause는 안전하다(아래 설명). 영구 삭제는 포지션 청산 후에만 한다.
|
|
- `pending_buy` / `pending_sell` — 주문 대기 중. 완료 대기 또는 `trex-core cancel <slot_id>`로 취소 후 진행.
|
|
|
|
---
|
|
|
|
### Step 3a — pause (가역, 권장 기본)
|
|
|
|
임시 거래정지, 무데이터 장기화, 불확실한 상황에서는 **pause가 기본 선택**이다. 언제든 resume으로 원복 가능하다.
|
|
|
|
```bash
|
|
# pause 적용 (direct-DB write, 데몬 RPC 불필요)
|
|
.venv/bin/trex-core pause <portfolio_id>
|
|
# 예: trex-core pause pf-005930
|
|
|
|
# pause 상태 확인
|
|
.venv/bin/trex-core paused
|
|
|
|
# 슬롯 조회 시 paused=True 확인
|
|
.venv/bin/trex-core slots --symbol <SYMBOL>
|
|
```
|
|
|
|
pause 후 동작:
|
|
- **다음 slots RPC 응답부터** 해당 slot의 `paused=True` 반환 → orb 등 전략 데몬이 그 slot에 buy intent를 보내지 않음 → **신규 매수 차단**.
|
|
- **기존 HOLDING 포지션은 영향 없음** — safety-sell(watchdog, stop-loss, 장마감 청산), tsl 전략 SellIntent, `trex-core exit` 모두 pause와 무관하게 정상 작동.
|
|
- pause는 Postgres `snapshot_pause` 테이블에 영속 저장되므로 **데몬 재시작 후에도 유지**된다.
|
|
|
|
복구 (종목이 정상화되면):
|
|
```bash
|
|
.venv/bin/trex-core resume <portfolio_id>
|
|
# 예: trex-core resume pf-005930
|
|
|
|
# 확인
|
|
.venv/bin/trex-core paused # 목록에서 사라졌는지
|
|
.venv/bin/trex-core slots --symbol <SYMBOL> # paused=False 확인
|
|
```
|
|
|
|
---
|
|
|
|
### Step 3b — 영구 제거 (상장폐지 등, 드문 케이스)
|
|
|
|
> **안전 경고**: 영구 제거는 비가역이다. 반드시 포지션이 0인 것을 확인한 후 진행한다. portfolio-hub 삭제는 고아 슬롯 위험이 있으므로 아래 순서를 엄수한다.
|
|
|
|
**전제 조건**:
|
|
1. 해당 종목 slot이 `inactive` 상태 (보유·대기 포지션 없음).
|
|
2. 포지션이 남아 있으면 먼저 청산(tsl/safety-sell 대기 또는 `trex-core exit`으로 수동 매도) 후 진행.
|
|
|
|
**절차**:
|
|
1. `trex-core pause <portfolio_id>` — 추가 진입 차단(옵션이나 권장).
|
|
2. `trex-core slots --symbol <SYMBOL>` — `status=inactive`, `qty=None` 확인.
|
|
3. portfolio-hub에서 해당 portfolio(asset) 삭제 → **portfolio-hub 소관 작업. 직접 invocation은 portfolio-hub 운영자에게 요청하거나 사장님과 협의.** (portfolio-hub가 삭제하면 다음 universe 스냅샷 갱신 시 해당 slot이 자동 retire된다.)
|
|
4. 다음 날 또는 스냅샷 갱신 후 `trex-core slots`에서 해당 종목이 사라졌는지 확인.
|
|
|
|
> portfolio-hub 삭제 없이 trex-core DB에서 slot을 직접 제거하면 고아 원장 레코드가 생길 수 있다. DB 직접 삭제는 금지한다.
|
|
|
|
---
|
|
|
|
### 안전 경고 요약
|
|
|
|
```
|
|
[PAUSE] 가역·원장보존. 언제든 resume 가능. 포지션 있어도 안전(매도 경로 유지).
|
|
[DELETE] 영구·비가역. 반드시 포지션 0 확인 후. portfolio-hub와 협의.
|
|
|
|
포지션(HOLDING)이 있는 종목은 pause만 적용하라.
|
|
영구 삭제는 포지션을 완전히 청산한 뒤에만 수행한다.
|
|
```
|
|
|
|
---
|
|
|
|
## 관련 문서
|
|
- `docs/interface-contract.md` — RabbitMQ 토폴로지 + 메시지 payload + reason/actor 어휘
|
|
- `docs/runtime-flows.md` — 흐름 다이어그램(스냅샷/intent RPC/이벤트/집행 루프/로깅)
|
|
- `.claude/skills/run-trex-core-daemon/SKILL.md` — AI 에이전트용 실행 레시피(이 런북의 사실 자료 출처)
|
|
|
|
|
|
### `sync` 출력 진단 포맷 (REQ-123)
|
|
|
|
`trex-core sync [--dry-run|--live]` 실행 시 `--json` 또는 일반 출력에서 다음 진단 필드로 no-write/fail-closed를 확인한다.
|
|
|
|
- `seeded_avg_price`: true면 seed된 avg_price가 반영 대상인지 표시
|
|
- `cost_basis_source`: `canonical` / `legacy` / `none`
|
|
- `cost_basis_reason`: `missing`, `invalid`, `http_status_<3-digit>`, `transport`, `invalid_json`, `invalid_payload`, `not_configured`, `diagnostic_exception`, `diagnostic_source_invalid` 또는 유효한 값이 seed되었을 때의 `null`. `http_status_<3-digit>`만 동적 형식이며 provider field name, parser detail, 임의 오류 텍스트는 출력 계약 값이 아니다.
|
|
- `skip_reason`: `cost_basis_unavailable`, `broker_qty_zero`, `status_in_flight:*` 등
|
|
- `error`: live fail-closed (`cost_basis_unavailable`) 등 write 중단 사유
|
|
- `before`/`after`는 write/미리보기 비교에 사용하며, dry-run은 `warning_cleared=False`로 고정
|
|
|
|
`sync --dry-run`은 실제 DB 쓰기·warning clear를 하지 않으므로 운영자가 미리보기를 확인한 뒤 직접 승인된 live 실행으로 전환한다.
|
|
|
|
### KIS 주문 결과불명 복구
|
|
|
|
- 주문 POST의 transport 오류 또는 게이트웨이 5xx는 거절로 단정하지 않는다.
|
|
Core는 종목·방향·수량·가격·시장·제출시각이 포함된 내구성
|
|
`submission-unknown` 식별자를 슬롯에 저장해 같은 주문의 즉시 재제출을 막는다.
|
|
- 주문·체결 조회가 유일한 일치 주문을 찾으면 실제 브로커 주문번호로 슬롯을
|
|
원자적으로 교체한 뒤 정상 체결·취소 경로를 계속한다. 재시작 후에도 저장된 실제
|
|
주문번호를 조회하므로 중복 주문을 만들지 않는다.
|
|
- 조회가 성공했지만 제출 후 90초가 지나도록 일치 주문이 없으면 실제 미접수로
|
|
확정하고 슬롯을 이전 상태로 되돌린다. 다음 전략 신호부터 새 주문 제출이 가능하다.
|
|
조회 실패 또는 후보가 여러 개면 결과불명 상태를 유지한다.
|
|
- 정상 체결조회는 주문별 최소 5초 간격, transport 실패 뒤에는 30초 간격으로
|
|
제한한다. 전략의 미체결 취소 시간과는 별도이며 결과불명 주문은 자동 취소하지 않는다.
|
|
- 회귀 검증은 KIS 호환 로컬 가상거래소에서 `접수 후 응답 유실`과
|
|
`접수 전 응답 유실`을 각각 장애주입하고, 주문 중복 0·재시작 복구·확정 미접수
|
|
이후에만 재시도됨을 확인한다.
|