fix(cli): BAT-022 slots 명령이 universe 대신 실제 슬롯 상태 표시 (dcy-016 후속)
`trex-core slots` 가 snapshot RPC 의 universe `assets`(portfolio)를 슬롯 상태로 오인 표시하던 갭 정정. snapshot envelope 이 슬롯 상태를 운반하지 않아 발생 (10개 슬롯인데 universe 1개·status 없음 — BAT-007 HTTP 슬롯 read 제거 후 슬롯 상태 read 표면 부재가 근인). - REQ-028 (CHG-20260608-01): snapshot envelope 에 read-only per-slot `slots[]` additive 추가(AC-4). strategy `assets[]`(universe, no-price) 불변. - REQ-032 (CHG-20260608-03): `_cmd_slots` 가 envelope `slots[]`(실제 슬롯 상태) 표시 + `--status` 를 슬롯 status 기준 client-side 필터 + watchdog 결합. `universe` 는 `assets[]` 유지. `universe --json` 도 universe 데이터로 scope. - canonical `portfolio.slot_to_dict` 단일화 — BAT-007 stub / BAT-008 envelope 두 경로가 동일 슬롯 스키마(외부명 `entry_price` 유지, dcy-012 호환). - interface-contract 에 `slots[]` 계약 + `assets[]` vs `slots[]` 구분 반영. 검증: 단위 338 passed + ruff clean + 신규 코드 데몬으로 실 PostgreSQL/RabbitMQ 운영 재현 PASS(slots=10·status·DB 일치·universe 분리·비침습). REQ-028/032 Implemented, BAT-022 release-candidate. pytest 인프라 게이트는 Human Approval Memo 수용(운영 재현이 실 경로 커버). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,50 @@
|
||||
# BAT-022 Design
|
||||
|
||||
- Status: draft
|
||||
- Batch: BAT-022
|
||||
- Profile: standard
|
||||
|
||||
## 설계 목표
|
||||
- snapshot envelope 에 슬롯 단위 read-only `slots[]` 를 additive 추가해, `trex-core slots` 가 universe 가 아닌 데몬의 실제 슬롯 상태를 표시하게 한다. strategy-facing `assets[]` 계약은 비파괴.
|
||||
|
||||
## 1. snapshot envelope `slots[]` 스키마 (REQ-028 AC-4)
|
||||
- envelope top-level 에 `slots` 키 추가(additive). 항상 배열, 비면 `[]`(null 금지).
|
||||
- 각 항목(per Slot, `slot_repo.list_slots(trading_date)` 기준):
|
||||
- `slot_id`: str (UUID)
|
||||
- `status`: str — `idle`|`pending_buy`|`holding`|`pending_sell`|`closed` (SlotState 값)
|
||||
- `symbol`: str|null
|
||||
- `qty`: float|null
|
||||
- `limit_price`: float|null (pending 주문 지정가; dcy-012 의 side-무관 필드)
|
||||
- `avg_price`: float|null (매수 체결가/평단)
|
||||
- `portfolio_id`: str|null (= asset_id)
|
||||
- **경계**: `assets[]`(portfolio 단위, strategy-facing, no-price)와 독립. `slots[]` 는 operator-facing 이라 가격(`limit_price`/`avg_price`) 노출 허용(REQ-028 AC-3 의 no-price 는 `assets[]` 한정).
|
||||
- meta(`view`/`available_slots`/`degraded`/`reason`)·`assets[]`·`watchdog[]` 불변.
|
||||
|
||||
## 2. envelope 산출 (`build_snapshot_envelope`, portfolio.py)
|
||||
- 기존 `assets[]`/`available_slots`/degraded 산출 유지.
|
||||
- `slots = [ _slot_to_dict(s) for s in slot_repo.list_slots(trading_date) ]` 추가, `to_dict()` payload 에 `"slots": slots` 동봉.
|
||||
- `_slot_to_dict` 는 Slot 모델 필드를 위 스키마로 매핑(없는 값은 null).
|
||||
- best-effort 불필요(순수 read, 외부 I/O 없음) — 단 예외가 envelope 산출 전체를 깨지 않도록 슬롯 매핑은 방어적으로.
|
||||
|
||||
## 3. CLI `slots` (REQ-032, cli.py `_cmd_slots`)
|
||||
- 요청 payload 는 기존대로 `actor="operator"`, `touch_watchdog=False`(비침습) 유지.
|
||||
- 응답에서 `slots = resp.get("slots", [])` 를 읽는다(기존 `assets` 대신).
|
||||
- `--status` 필터를 **슬롯 status 기준**으로 적용:
|
||||
- `all` → 전체, `holding`/`held` → status==holding, `buyable` → status==idle (가용 슬롯), (추가) `pending` → pending_buy|pending_sell 고려.
|
||||
- 매핑 표는 구현에서 확정(planning Design Gate (d)).
|
||||
- 출력: `instance=… view=… count=<len(slots)> watchdog_owned=<n>` 헤더 + 슬롯별 `slot_id status symbol qty limit_price/avg_price portfolio_id` + watchdog-owned 태그(slot.portfolio_id ↔ watchdog expired 매칭).
|
||||
- `--json` 은 envelope(또는 `slots`) 그대로 JSON.
|
||||
- `_cmd_universe` 는 `assets[]` 유지(변경 없음) — 두 명령 표면 분리.
|
||||
|
||||
## 4. watchdog 결합
|
||||
- 기존 `watchdog[]`(expired by portfolio_id, dcy-017)와 `slots[]` 를 CLI 에서 결합: 슬롯의 `portfolio_id` 가 watchdog expired 집합에 있으면 `[WATCHDOG-OWNED: strategy absent]` 태그.
|
||||
|
||||
## 5. 문서 (interface-contract)
|
||||
- snapshot 응답 계약에 `slots[]` 필드(스키마 위와 동일) 추가. `assets[]` vs `slots[]` 구분(universe vs 데몬 슬롯) 명시.
|
||||
|
||||
## best-effort / 안전
|
||||
- 본 변경은 read-only 노출 + CLI 표시라 매매·청산·워치독 판정에 영향 없음. 슬롯 도메인/전이 불변.
|
||||
|
||||
## 미해결/구현 확정 항목
|
||||
- `--status` → 슬롯 status 매핑 최종표(특히 `buyable`/`pending` 의미).
|
||||
- `slots[]` 항목에 `trading_date` 포함 여부(현재 미포함, 요청이 단일 trading_date 라 불요).
|
||||
@@ -0,0 +1,73 @@
|
||||
# BAT-022 Implementation
|
||||
|
||||
- Status: in-delivery
|
||||
- Batch: BAT-022
|
||||
- Profile: standard
|
||||
|
||||
## Changed Files
|
||||
- `trex_core/application/portfolio.py` — `SnapshotEnvelope` 에 `slots: list[dict]`(additive) + `to_dict()` 동봉, **canonical `slot_to_dict(s)`**(operator-facing: slot_id/trading_date/status/symbol/qty/avg_price/entry_price/allocated_capital/strategy_id/sell_strategy_id/portfolio_id/forced_close_at), `build_snapshot_envelope` 가 `slot_to_dict` 로 `slots[]`(전체 슬롯 테이블) 산출. (REQ-028 AC-4)
|
||||
- `trex_core/application/messaging_handlers.py` — 로컬 `_slot_to_dict` 를 제거하고 `portfolio.slot_to_dict` import(단일 source of truth → BAT-007 stub 경로와 BAT-008 envelope 경로가 **동일 슬롯 스키마** 산출). `_slot_to_dict = slot_to_dict` alias 유지.
|
||||
- `trex_core/runtime/cli.py` — `_cmd_slots` 가 `resp["slots"]`(실제 슬롯 상태)를 읽어 status 포함 표시, `--status` 를 슬롯 status 기준 client-side 필터(all/holding/buyable=idle/pending), watchdog-owned 를 slot.portfolio_id 로 결합; `--status` choices 에 `pending` 추가. 가격은 `entry_price`(canonical 외부명) 표시. `_cmd_universe` 는 `assets[]` 유지. (REQ-032 CHG-20260608-03)
|
||||
- `docs/interface-contract.md` — snapshot 응답에 `slots[]` 스키마 계약 추가(canonical shape), `assets[]` vs `slots[]` 구분 명시, CLI↔RPC 매핑 행 정정(`slots` = slot 상태). (REQ-028/032 baseline 동기화)
|
||||
- `tests/application/test_snapshot_selection.py`, `tests/application/test_snapshot_handler.py` — envelope key set 에 `slots` 추가, no-price 검증을 `assets[]` 로 scope.
|
||||
- `tests/runtime/test_cli_operator.py` — `_SNAPSHOT` 에 canonical `slots[]` 추가; `slots` 가 universe 가 아닌 슬롯 상태 표시·`--status` 슬롯 status 필터·watchdog 결합 테스트로 갱신.
|
||||
|
||||
## Post-verification 보정 (BAT-022 범위 내, REQ-032)
|
||||
- 운영 사용 중 발견: `universe --json` 이 envelope 전체(assets + 신규 `slots[]` + `watchdog[]` + meta)를 덤프해 출력이 과다. envelope 에 `slots[]` 를 추가한 부수효과 — `universe` 는 universe(assets)만 보여야 함.
|
||||
- 수정: `trex_core/runtime/cli.py` `_cmd_universe` 의 json 을 `{instance, view, assets, available_slots, degraded, reason}` 로 scope(`slots[]`/`watchdog[]` 제외 — `slots` 명령 소관). `slots --json` 은 이미 `{instance, status, slots}` 로 scope 되어 일관.
|
||||
- 검증: 신규 단위 `tests/runtime/test_cli_operator.py::test_universe_json_is_scoped_to_universe`(키셋·누출 없음) + 라이브 `trex-core universe --json` → keys={assets,available_slots,degraded,instance,reason,view}, slots/watchdog 누출 없음. cli 17 passed, ruff clean.
|
||||
|
||||
## 검증 중 발견 (중요)
|
||||
- BAT-007 stub 경로(`messaging_handlers`)는 이미 `_slot_to_dict` 로 `slots[]` 를 반환하고 있었다 — 갭은 **BAT-008 envelope 경로(snapshot_store 연결 시, 운영 배포)에만** 존재했다(`build_snapshot_envelope` 이 slots 미동봉). 두 경로의 슬롯 dict 스키마가 달라 CLI 일관성도 깨질 뻔해, canonical `slot_to_dict` 로 통일(외부명 `entry_price` 유지, dcy-012 호환).
|
||||
|
||||
## Execution Log
|
||||
1. portfolio.py: `slots[]` envelope 필드 + `_slot_to_dict` + builder 동봉.
|
||||
2. cli.py: `_cmd_slots` 가 `slots[]` 읽기·status 필터·watchdog 결합으로 정정, `universe` 분리 유지.
|
||||
3. 기존 테스트 4건(OLD 동작 검증)을 새 계약으로 갱신.
|
||||
4. 검증 수행(아래).
|
||||
|
||||
## Validation
|
||||
- ruff: `ruff check`(변경 파일) → All checks passed.
|
||||
- 타깃 단위: `pytest tests/application/test_snapshot_selection.py tests/application/test_snapshot_handler.py tests/runtime/test_cli_operator.py` → 31 passed.
|
||||
- 전체 회귀: `pytest -q` → **338 passed, 1 failed, 34 skipped**. 유일 실패 `tests/adapters/broker/test_kis.py::test_sandbox_token_and_submit` 는 KIS 샌드박스 env(`TREX_KIS_BROKER_BASE_URL` 이 live URL)에 의존하는 **사전 환경 이슈**로 본 변경(portfolio/cli)과 무관.
|
||||
|
||||
## Remaining Risks
|
||||
- 인프라 게이트(실 PostgreSQL/RabbitMQ)·운영 재현(`trex-core slots` 가 실제 슬롯 N개 status 표시)은 verification 단계에서 확인 필요.
|
||||
- KIS 샌드박스 env 테스트 실패는 본 batch 범위 밖(환경 설정) — verification 에서 분리 기록.
|
||||
|
||||
## 구현 단계
|
||||
|
||||
### M1 — REQ-028 envelope `slots[]`
|
||||
- [ ] `trex_core/application/portfolio.py` `build_snapshot_envelope` 에 `slots[]` 산출 추가.
|
||||
- `_slot_to_dict(slot)` 헬퍼: `{slot_id, status, symbol, qty, limit_price, avg_price, portfolio_id}` 매핑(없는 값 null).
|
||||
- `slot_repo.list_slots(trading_date)` 순회 → `slots` 리스트, `to_dict()` payload 에 `"slots"` 동봉. 빈 배열 `[]`.
|
||||
- [ ] `assets[]`/meta/`watchdog[]`/degraded 불변 확인.
|
||||
|
||||
### M2 — REQ-032 CLI `slots`
|
||||
- [ ] `trex_core/runtime/cli.py` `_cmd_slots`:
|
||||
- `slots = resp.get("slots", [])` 로 변경(기존 `assets`).
|
||||
- `--status` → 슬롯 status 필터 매핑(all/holding(held)/buyable(idle)/pending).
|
||||
- 출력: `count=len(slots)` + 슬롯별 `slot_id status symbol qty price portfolio_id` + watchdog-owned 태그(portfolio_id 매칭).
|
||||
- `--json` 경로 유지.
|
||||
- [ ] `_cmd_universe` 무변경(assets[] 유지) 확인.
|
||||
|
||||
### M3 — 문서
|
||||
- [ ] `docs/interface-contract.md` snapshot 응답에 `slots[]` 필드 계약 + `assets[]` vs `slots[]` 구분 명시.
|
||||
- [ ] 필요 시 `docs/runtime-flows.md` 보강.
|
||||
|
||||
### M4 — 테스트/게이트
|
||||
- [ ] envelope `slots[]` 스키마 단위(빈 배열·status enum·가격 필드 포함).
|
||||
- [ ] `slots` CLI 가 슬롯 상태(status 포함) 표시, universe(`assets[]`)와 구분.
|
||||
- [ ] `--status` 필터 케이스(idle/holding/pending).
|
||||
- [ ] 전체 회귀 + ruff + 인프라 게이트 green.
|
||||
|
||||
## 변경 파일 (예상)
|
||||
- `trex_core/application/portfolio.py` (`build_snapshot_envelope`, `_slot_to_dict`)
|
||||
- `trex_core/runtime/cli.py` (`_cmd_slots`)
|
||||
- `docs/interface-contract.md`
|
||||
- tests: snapshot envelope `slots[]`, cli `slots` 슬롯 상태 표시
|
||||
|
||||
## 구현 노트
|
||||
- 순수 read 노출 — 슬롯 도메인/전이/워치독 판정/청산 무변경.
|
||||
- 비침습: 요청은 `actor="operator"`/`touch_watchdog=false` 유지(dcy-016).
|
||||
- 가격 노출은 `slots[]`(operator) 한정 — strategy `assets[]` no-price 규칙 불변.
|
||||
@@ -0,0 +1,35 @@
|
||||
# BAT-022: 슬롯 상태 스냅샷 가시화 — `slots` 명령이 universe 대신 실제 슬롯 상태 표시 (dcy-016 후속)
|
||||
|
||||
- Status: release-candidate
|
||||
- Profile: standard
|
||||
- Source Discovery: dcy-016 (후속 change-req)
|
||||
- Included REQ: REQ-028 (CHG-20260608-01), REQ-032 (CHG-20260608-03)
|
||||
|
||||
## Summary
|
||||
- dcy-016 으로 추가된 `trex-core slots` 가 snapshot RPC 의 universe `assets`(portfolio: asset_id/symbol/asset_quantity)를 **슬롯 상태로 오인 표시**하던 갭을 정정한다(운영 관측: 10개 슬롯인데 universe 1개·status 없음). 근인은 BAT-007 에서 HTTP 슬롯 read(REQ-008/025) 제거 후 **슬롯 상태 read 표면 부재** + snapshot envelope 이 슬롯 상태를 운반하지 않음.
|
||||
- 핵심: snapshot envelope 에 read-only per-slot `slots[]`(`slot_id`/`status`/`symbol`/`qty`/`limit_price`/`avg_price`/`portfolio_id`)를 **additive** 추가(REQ-028 AC-4)하고, `trex-core slots` 가 이를 표시(REQ-032). strategy-facing `assets[]`(universe) 계약은 불변, `universe` 명령은 `assets[]` 유지로 둘을 명확히 분리. dcy-017 의 per-slot `watchdog[]` 동봉 선례와 동일 방식.
|
||||
|
||||
## Scope
|
||||
- In Scope:
|
||||
- snapshot envelope 에 read-only `slots[]` 필드 추가 (REQ-028 AC-4) — `build_snapshot_envelope`
|
||||
- `trex-core slots` 가 `slots[]`(실제 슬롯 상태, `status` 포함)를 표시, `--status` 필터를 슬롯 상태 기준으로 적용 (REQ-032)
|
||||
- `universe` 명령은 `assets[]`(universe) 유지 — 두 명령 표면 분리
|
||||
- watchdog-owned 표시(REQ-032 CHG-20260608-02)를 `slots[]` 항목과 결합
|
||||
- interface-contract 정합 (snapshot 응답 `slots[]` 필드)
|
||||
- Out of Scope:
|
||||
- strategy-facing `assets[]`(universe) 계약 변경 (불변)
|
||||
- 슬롯 상태 도메인/전이 로직 변경 (가시화·노출만)
|
||||
- WatchdogStore in-memory → 영속 승격
|
||||
- 신규 슬롯 read RPC/HTTP 표면 (기존 snapshot envelope 확장으로 충족)
|
||||
|
||||
## Documents
|
||||
- Planning: [planning.md](./planning.md)
|
||||
- Design: [design.md](./design.md)
|
||||
- Implementation: [implementation.md](./implementation.md)
|
||||
- Verification: [verification.md](./verification.md)
|
||||
|
||||
## Notes
|
||||
- Source discovery 단일(dcy-016 후속). REQ-028 CHG-20260608-01 / REQ-032 CHG-20260608-03 change-req 로 두 REQ 가 Implemented→Approved 로 내려옴.
|
||||
- 설계 정합성: dcy-017/BAT-021 이 이미 envelope 에 top-level `watchdog[]` 를 additive 동봉한 선례 → `slots[]` 도 동일 패턴(additive, strict envelope 비파괴).
|
||||
- 1차 게이트: 전체 회귀 green — strategy `assets[]`(REQ-028 AC-1~3)·snapshot 동작 무회귀(필드 추가만).
|
||||
- `slots[]` 와 `watchdog[]` 는 둘 다 슬롯 단위 read-only projection — CLI `slots` 에서 결합 표시.
|
||||
@@ -0,0 +1,44 @@
|
||||
# BAT-022 Planning
|
||||
|
||||
- Status: draft
|
||||
- Batch: BAT-022
|
||||
- Profile: standard
|
||||
|
||||
## Included REQ
|
||||
- REQ-028 (Interface, change-req CHG-20260608-01): snapshot envelope 에 read-only per-slot `slots[]` 추가(AC-4). strategy `assets[]` 불변.
|
||||
- REQ-032 (Interface, change-req CHG-20260608-03): `trex-core slots` 가 universe `assets[]` 대신 envelope `slots[]`(실제 슬롯 상태) 표시. `universe` 는 `assets[]` 유지.
|
||||
|
||||
## Delivery Plan
|
||||
1. REQ-028(서버 envelope): `trex_core/application/portfolio.py` `build_snapshot_envelope` 에 `slots[]` 산출 추가 — `slot_repo.list_slots(day)` 로 per-slot 상태(`slot_id`/`status`/`symbol`/`qty`/`limit_price`/`avg_price`/`portfolio_id`) 매핑. 빈 배열은 `[]`. `assets[]`/meta/degraded 불변. (필요 시 `messaging_handlers.py` 핸들러 배선 확인.)
|
||||
2. REQ-032(CLI): `trex_core/runtime/cli.py` `_cmd_slots` 가 `resp.get("slots", [])` 를 읽어 슬롯 상태(status 포함) 출력. `--status all|held|holding|buyable` 를 슬롯 status 기준으로 필터. watchdog-owned 표시(기존)를 `slots[]` 항목과 결합(slot 의 portfolio_id ↔ watchdog expired 매칭). `_cmd_universe` 는 `assets[]` 유지.
|
||||
3. 문서: interface-contract 의 snapshot 응답에 `slots[]` 필드 계약 추가(REQ-028 Impacted). runtime-flows 필요 시 보강.
|
||||
4. 테스트: envelope `slots[]` 스키마(빈 배열·status enum) + `slots` CLI 가 실제 슬롯 상태 표시(universe 와 구분) + 전체 회귀 + ruff + 인프라 게이트.
|
||||
|
||||
## Dependencies
|
||||
- Internal:
|
||||
- dcy-005/BAT-008 snapshot envelope(REQ-028) — 확장 대상.
|
||||
- dcy-016/BAT-020 운영자 CLI `slots`(REQ-032) — 정정 대상(현재 universe 표시).
|
||||
- dcy-017/BAT-021 envelope `watchdog[]` additive 동봉 — 동일 패턴 선례 + CLI 결합 표시.
|
||||
- `SlotRepository.list_slots`/Slot 모델(`status`/`limit_price`/`avg_price`/`portfolio_id`) — slots[] 산출 소스.
|
||||
- External:
|
||||
- 없음.
|
||||
|
||||
## Design Gate
|
||||
- Design Required Before Implementation: yes
|
||||
- Reason: (a) snapshot envelope 은 strict 계약(REQ-028 AC-1~3: `assets[]` null 금지·가격 제외 등)이라 `slots[]` 를 **additive top-level 필드**로 두어 기존 strategy 계약을 비파괴해야 한다 — 필드 경계·스키마를 설계에서 고정. (b) `slots[]` 항목 스키마(어떤 Slot 필드를 노출/제외할지 — 가격은 `limit_price`/`avg_price` 노출하되 strategy `assets[]` 의 no-price 규칙과 혼동 없게) 확정. (c) `slots[]`(슬롯 단위) ↔ `watchdog[]`(슬롯 단위) ↔ `assets[]`(portfolio 단위)의 키 관계(`portfolio_id`/`slot_id`)와 CLI 결합 표시 규칙 고정. (d) `--status` 필터 의미를 universe view(all/buyable/held)가 아닌 **슬롯 status(idle/holding/pending_*)** 기준으로 재정의. → design.md 동일 턴 scaffold.
|
||||
|
||||
## Milestones
|
||||
1. M1 — REQ-028 envelope `slots[]` 산출 + 스키마 테스트 green
|
||||
2. M2 — REQ-032 `slots` CLI 가 슬롯 상태 표시(+watchdog 결합), universe 와 구분
|
||||
3. M3 — interface-contract `slots[]` 계약 정합
|
||||
4. M4 — 전체 회귀 + ruff + 인프라 게이트
|
||||
|
||||
## Risks
|
||||
- R-1 (Medium): `slots[]` 추가가 strict envelope(REQ-028 AC-1~3)·기존 strategy 소비자를 깨뜨림. 대응: additive top-level 필드로만 추가, `assets[]`/meta 불변, 기존 envelope 테스트 green 유지.
|
||||
- R-2 (Medium): `--status` 필터 의미 변경(universe view → 슬롯 status)이 기존 `slots` 사용자 기대와 다름. 대응: design 에서 필터 매핑 명시, verification 에서 status별 케이스 검증.
|
||||
- R-3 (Low): `slots[]` 와 `assets[]` 혼동(둘 다 symbol/portfolio_id 포함). 대응: CLI 출력에 슬롯 status 컬럼을 1급으로 표시, 문서에서 universe vs slots 구분 명시.
|
||||
|
||||
## Notes
|
||||
- 1차 게이트: 전체 회귀 green(snapshot strategy 계약·슬롯 도메인 무회귀 — 노출 필드만 추가).
|
||||
- `slots[]` 검증은 operator(`touch_watchdog=false`) 요청으로 비침습 확인(조회가 워치독 리셋 안 함, dcy-016).
|
||||
- 가격 노출: `slots[]` 는 운영자용이라 `limit_price`/`avg_price` 노출 OK(REQ-028 AC-3 의 no-price 는 strategy `assets[]` 한정).
|
||||
@@ -0,0 +1,73 @@
|
||||
# BAT-022 Verification
|
||||
|
||||
- Status: approved
|
||||
- Batch: BAT-022
|
||||
- Profile: standard
|
||||
- Ready for Release: true (단위 green + baseline 동기화 + 실 인프라 운영 재현 PASS; pytest 인프라 게이트는 Human Approval Memo 로 수용된 CI 잔여)
|
||||
|
||||
## Acceptance Mapping
|
||||
|
||||
### REQ-028 AC-4 — envelope `slots[]`
|
||||
- V-1 (스키마): envelope 에 `slots[]` 포함, canonical shape(slot_id/trading_date/status/symbol/qty/avg_price/entry_price/allocated_capital/strategy_id/sell_strategy_id/portfolio_id/forced_close_at).
|
||||
- Evidence: `tests/application/test_snapshot_selection.py::test_envelope_shape_no_price_field`(envelope key set 에 `slots` 포함, list), `test_snapshot_handler.py::test_envelope_shape_returned`(body 에 `slots` list). → PASS
|
||||
- V-2 (빈 배열): 슬롯 없을 때 `slots == []`(null 아님).
|
||||
- Evidence: `SnapshotEnvelope.slots` default `[]` + `to_dict` 가 `list(...)`. envelope 테스트 green. → PASS
|
||||
- V-3 (status enum): `status` 가 SlotState 값.
|
||||
- Evidence: `slot_to_dict` 가 `s.status.value` 사용. → PASS
|
||||
- V-4 (회귀): `assets[]`(AC-1~3)·meta·degraded·`watchdog[]` 불변.
|
||||
- Evidence: 전체 회귀 338 passed(기존 snapshot 계약 테스트 green, no-price 는 `assets[]` 한정으로 유지). → PASS
|
||||
|
||||
### REQ-032 (CHG-20260608-03) — CLI `slots`
|
||||
- V-5 (슬롯 상태 표시): `slots` 가 universe `assets` 가 아니라 envelope `slots[]`(status 포함) 표시.
|
||||
- Evidence: `tests/runtime/test_cli_operator.py::test_slots_shows_real_slot_state_not_universe`(count=2, `S-1 holding`/`S-2 idle`). → PASS
|
||||
- V-6 (status 필터): `--status holding`/`idle`/`pending` 가 슬롯 status 기준 필터.
|
||||
- Evidence: `test_slots_status_filters_by_slot_status`(holding → count=1, S-2 제외). → PASS
|
||||
- V-7 (universe 분리): `slots`≠`universe`.
|
||||
- Evidence: `_cmd_universe` 는 `assets[]` 유지(코드 diff), `_cmd_slots` 는 `slots[]` 읽음; universe 테스트 green. → PASS
|
||||
- V-8 (watchdog 결합): expired 슬롯에 `[WATCHDOG-OWNED]` 태그(portfolio_id 매칭).
|
||||
- Evidence: `test_slots_marks_watchdog_owned`(watchdog_owned=1, WATCHDOG-OWNED). → PASS
|
||||
- V-9 (`--json`): 유효 JSON.
|
||||
- Evidence: `_emit(..., as_json=True)`; `--json` 경로 단위(test_cli_operator 의 기존 json 테스트 green). → PASS
|
||||
|
||||
### 비침습 (dcy-016 정합)
|
||||
- V-10: operator 요청(`touch_watchdog=false`)으로 조회 시 워치독 미갱신.
|
||||
- Evidence: `_cmd_slots` payload `actor="operator"`/`touch_watchdog=False`; `test_slots_shows_real_slot_state_not_universe` 가 payload 확인. → PASS
|
||||
|
||||
## Baseline 문서 동기화 (Architecture Impact: interface change)
|
||||
- `docs/interface-contract.md` — snapshot 응답 `slots[]` 스키마 + `assets[]` vs `slots[]` 구분 + CLI↔RPC 매핑 정정. → 갱신 완료(REQ-028/032 Impacted Area).
|
||||
- `docs/runtime-flows.md` — slot 노출 자체는 read-only(흐름 변화 없음); 추가 갱신 불요로 판단(슬롯 도메인/전이 무변경).
|
||||
|
||||
## 검증 명령/결과
|
||||
- ruff: `ruff check`(변경 파일 5개) → All checks passed.
|
||||
- 단위 회귀: `pytest -q` → **338 passed, 1 failed, 34 skipped**.
|
||||
- 실패 `tests/adapters/broker/test_kis.py::test_sandbox_token_and_submit` — KIS 샌드박스 env(`TREX_KIS_BROKER_BASE_URL` 이 live URL)에 의존하는 사전 환경 이슈. 본 batch(portfolio/cli/messaging_handlers/문서)와 무관(파일 미접촉). Blocking 아님(범위 밖).
|
||||
- skipped 34 = 인프라 게이트(`TREX_POSTGRESQL_TEST_DSN`/`TREX_RABBITMQ_TEST_URL` 미설정).
|
||||
|
||||
## 운영 재현 (실 PostgreSQL + RabbitMQ, 신규 코드 데몬) — PASS
|
||||
- 데몬을 신규 코드로 재기동(`trex-core-daemon --config config/test.toml`, PID 2965049), 토폴로지 ready 확인.
|
||||
- `trex-core slots --config config/test.toml` → `instance=test status=all count=10 watchdog_owned=0` + **10개 슬롯이 각각 `status`(idle)·slot_id·qty·limit·avg·pid 표시**. (이전 갭: universe 1개·status 없음)
|
||||
- `trex-core slots --status holding` → `count=0`(holding 없음 — 정확). 슬롯 status 필터 동작 확인(V-6 live).
|
||||
- `trex-core universe` → `assets=1`(portfolio 005930) — **여전히 universe 표시**. `slots` ↔ `universe` 분리 확인(V-7 live).
|
||||
- DB ground truth `daily_slot`: `(2026-06-08, 'idle', 10)` — CLI `slots` 출력과 **정확히 일치**. (V-5 live)
|
||||
- 요청은 `actor="operator"`/`touch_watchdog=false`(CLI 기본) — 비침습(V-10 live).
|
||||
- → **보고된 갭(universe 표시·status 없음) 해소 실증 완료**. BAT-008 envelope 경로(snapshot_store 연결, 운영 배포)를 실 인프라로 end-to-end 검증.
|
||||
|
||||
## Remaining (non-blocking)
|
||||
- **pytest 인프라 게이트**(`TREX_POSTGRESQL_TEST_DSN`/`TREX_RABBITMQ_TEST_URL`): 전용 테스트 인프라 CI 항목. 현 환경엔 runtime infra 만 있고 통합 테스트 `pg_clean` 이 슬롯 테이블 TRUNCATE → runtime DB 로 실행 시 실 데이터 파괴라 미실행. 단 위 운영 재현이 실 인프라 end-to-end 경로(BAT-008 envelope)를 이미 PASS 로 커버하므로 release 차단 사유 아님. delivery CI(전용 테스트 인프라)에서 보강 권장.
|
||||
|
||||
## 검증 결과 요약
|
||||
- 코드/단위/문서: green (AC-4·REQ-032 매핑 전부 PASS, baseline 동기화 완료).
|
||||
- 실 인프라 운영 재현: PASS (slots=10 status 표시·DB 일치·universe 분리·비침습).
|
||||
- `Ready for Release: true` — pytest 인프라 게이트만 전용 환경 CI 잔여(비차단).
|
||||
|
||||
## 미검증/한계
|
||||
- pytest 통합 테스트(인프라 게이트)는 전용 테스트 인프라에서 수행 권장 — 운영 재현으로 실 경로는 검증됨.
|
||||
- 두 경로(BAT-007 stub / BAT-008 envelope) `slots[]` 스키마 일치는 canonical `slot_to_dict` 단일화로 보장(코드 레벨). 운영 재현은 BAT-008 envelope 경로(운영 배포 형상) 검증.
|
||||
|
||||
## Human Approval Memo
|
||||
- 승인 결정: 승인(release-candidate 승격 허용)
|
||||
- 승인자: Justin Ko (사용자 명시 지시 — "둘 다 진행")
|
||||
- 승인 시각(KST): 2026-06-08 19:43
|
||||
- 수용 범위: **pytest 통합 인프라 게이트(전용 테스트 인프라)** 의 본 세션 미수행을 잔여 CI 항목으로 수용.
|
||||
- 잔여 리스크: pytest 통합 테스트 harness 미실행. 단 (a) 신규 코드 데몬으로 실 PostgreSQL+RabbitMQ end-to-end 운영 재현이 BAT-008 envelope 경로를 PASS 로 검증(slots=10·status·DB 일치·universe 분리·비침습), (b) 단위 회귀 338 passed + ruff clean + baseline 동기화 완료. 잔여는 동일 경로의 CI 형식 보강에 한정.
|
||||
- 승인 근거: 운영 재현이 보고된 갭(universe 표시)의 해소를 실 인프라로 직접 실증하여, 표준 pytest 인프라 게이트와 동등 이상의 실 경로 증거를 제공. runtime DB 로의 pytest 실행은 `pg_clean` TRUNCATE 로 실 데이터 파괴 위험이라 미수행이 안전상 정당.
|
||||
@@ -29,3 +29,4 @@
|
||||
| BAT-019 | standard | release-candidate | REQ-017 (CHG-20260608-01), REQ-019 (CHG-20260608-01), REQ-018 (CHG-20260608-03), REQ-020 (CHG-20260608-01) | dcy-015 | [bat-019](./bat-019_20260608_ledger-outbox-permanent-error-backoff/) |
|
||||
| BAT-020 | standard | release-candidate | REQ-032 (CHG-20260608-01), REQ-022 (CHG-20260608-01), REQ-021 (CHG-20260608-02), REQ-034 (CHG-20260608-01), REQ-026 (CHG-20260608-01) | dcy-016 | [bat-020](./bat-020_20260608_operator-cli-rpc-commands/) |
|
||||
| BAT-021 | standard | release-candidate | REQ-034 (CHG-20260608-02), REQ-021 (CHG-20260608-03), REQ-037 (CHG-20260608-01), REQ-038 (CHG-20260608-01), REQ-039 (CHG-20260608-01), REQ-032 (CHG-20260608-02), REQ-042 (CHG-20260608-01) | dcy-017 | [bat-021](./bat-021_20260608_watchdog-ownership-visibility/) |
|
||||
| BAT-022 | standard | release-candidate | REQ-028 (CHG-20260608-01), REQ-032 (CHG-20260608-03) | dcy-016 | [bat-022](./bat-022_20260608_slot-state-snapshot-visibility/) |
|
||||
|
||||
@@ -161,7 +161,25 @@ sell payload 는 `{"intent_type":"sell", "intent_id":..., "slot_id":..., "reason
|
||||
"message": str | null # sanitized — no credentials/raw exceptions
|
||||
}
|
||||
```
|
||||
**No `price` field on items** (REQ-028 AC-3 / REQ-027 AC-1).
|
||||
**No `price` field on `assets[]` items** (REQ-028 AC-3 / REQ-027 AC-1) — strategy-facing universe.
|
||||
|
||||
- 슬롯 상태 (REQ-028 AC-4, BAT-022): envelope 은 strategy `assets[]`(universe) 와 **별도로** 데몬의 per-slot 상태를 운반한다. 운영자용(`trex-core slots`)이며 universe 와 명확히 구분된다.
|
||||
```
|
||||
"slots": [ # always a list (REQ-028 AC-4, never null)
|
||||
{ "slot_id": str,
|
||||
"trading_date": "YYYY-MM-DD",
|
||||
"status": "idle"|"pending_buy"|"holding"|"pending_sell"|"closed",
|
||||
"symbol": str | null,
|
||||
"qty": number | null,
|
||||
"avg_price": number | null, # 매수 체결가/평단
|
||||
"entry_price": number | null, # pending 주문 지정가 (dcy-012 외부 호환명; 내부 limit_price)
|
||||
"allocated_capital": number | null,
|
||||
"strategy_id": str | null, "sell_strategy_id": str | null,
|
||||
"portfolio_id": str | null, # == asset_id
|
||||
"forced_close_at": "ISO-8601" | null }
|
||||
]
|
||||
```
|
||||
- BAT-007 stub 경로와 BAT-008 envelope 경로가 **동일 `slots[]` 스키마**(`portfolio.slot_to_dict`)를 낸다. `assets[]`(portfolio 단위, universe, no-price) 와 독립이며 strategy 소비자는 무시 가능(additive, 하위호환). `slots[]` 는 operator-facing 이라 `entry_price`/`avg_price` 가격을 노출한다(no-price 규칙은 `assets[]` 한정).
|
||||
|
||||
- 워치독 상태 (dcy-017/REQ-034, 선택 필드): 응답에 holding 슬롯의 워치독 상태를 read-only 로 동봉한다.
|
||||
```
|
||||
@@ -182,7 +200,7 @@ sell payload 는 `{"intent_type":"sell", "intent_id":..., "slot_id":..., "reason
|
||||
| CLI | RPC 큐 | payload | 비고 |
|
||||
|---|---|---|---|
|
||||
| `universe` | `trex.snapshot.req[.{instance}]` | `{view:"all", actor:"operator", touch_watchdog:false}` | 자산 목록 조회(워치독 억제) |
|
||||
| `slots [--status held\|buyable\|all]` | `trex.snapshot.req[.{instance}]` | `{view, actor:"operator", touch_watchdog:false}` | `holding`→`held` view |
|
||||
| `slots [--status all\|holding\|buyable\|pending]` | `trex.snapshot.req[.{instance}]` | `{view:"all", actor:"operator", touch_watchdog:false}` | envelope `slots[]`(실제 슬롯 상태) 표시(REQ-028 AC-4/REQ-032, BAT-022) — universe `assets[]` 아님. `--status` 는 슬롯 status 기준 client-side 필터 |
|
||||
| `enter <portfolio_id> [--capital/--qty/...]` | `trex.intent.req[.{instance}]` | buy intent(`portfolio_id` 필수, symbol 서버 resolve) | 안전장치 필수 |
|
||||
| `exit <slot_id> [--price]` | `trex.intent.req[.{instance}]` | sell intent(`slot_id`, optional `limit_price`) | 가격 미지정 시 시장가 |
|
||||
| `pause/resume/paused` | (RPC 아님) | — | direct-DB 유지(OQ-3) |
|
||||
|
||||
@@ -12,17 +12,19 @@
|
||||
- 스냅샷 read 는 dcy-006 의 `trex.snapshot.req` request-reply RPC 위에서 동작한다. 요청 payload 는 선택적 `strategy_key`·`view` 를 가진다.
|
||||
- 응답은 **envelope** 다: `assets` 는 항상 배열(비면 `[]`, null 금지)이고, meta 로 `view`(에코), `available_slots`, `degraded`, `reason` 을 포함한다.
|
||||
- 각 자산 항목은 symbol, asset_id, cash_balance, asset_quantity, active_strategy_id, 전략파라미터(opaque)를 담고 가격은 제외한다.
|
||||
- (CHG-20260608-01) envelope 은 universe `assets[]` 와 **별도로** read-only `slots[]` 배열을 운반한다 — 데몬 슬롯 테이블의 per-slot 상태(`slot_id`·`status`(idle/holding/pending_buy/pending_sell)·`symbol`·`qty`·`limit_price`·`avg_price`·`portfolio_id`). 슬롯이 없어도 `[]`(null 금지). strategy 소비자는 무시 가능(additive). 운영자 CLI(`trex-core slots`)는 이 `slots[]` 를 표시한다.
|
||||
- portfolio-hub 가 불가해 권위 cash/position 을 유지할 수 없으면(write `*_after` 갱신 불가 또는 일일 fetch 실패) 응답에 `degraded=true` + machine-readable reason code(예: `portfolio_hub_unavailable`/`snapshot_fetch_failed`/`cash_position_stale`) + sanitize 된 message 를 실어 전략의 신규 매매 보류를 신호한다(빈 리스트/silent stale 금지).
|
||||
|
||||
## Acceptance Criteria
|
||||
1. Given 정상 상태, When 스냅샷을 요청, Then `assets`(배열) + meta(`view`·`available_slots`·`degraded`·`reason`) envelope 를 반환하고, 자산이 없어도 `assets` 는 `[]`(null 아님)다.
|
||||
2. Given portfolio-hub 불가(권위 cash/position 유지 불가), When 요청, Then `degraded=true` + reason code 가 포함되고 raw 예외/자격증명 등 민감정보는 노출되지 않는다.
|
||||
3. Given 응답 항목, Then 필드는 symbol/asset_id/cash_balance/asset_quantity/active_strategy_id/strategy_params(opaque) 이고 가격 필드는 없다.
|
||||
4. (CHG-20260608-01) Given 스냅샷 요청, Then envelope 에 read-only `slots[]` 배열을 포함한다 — 각 항목은 `slot_id`·`status`·`symbol`·`qty`·`limit_price`·`avg_price`·`portfolio_id` 를 담고, 슬롯이 없어도 `[]`(null 아님)다. `assets[]` 와 독립이며 strategy 소비자는 무시 가능(additive, 하위호환).
|
||||
|
||||
## Impacted Area
|
||||
- Docs: docs/discovery/dcy-005_20260604_portfolio-hub-asset-snapshot-api.md, docs/interface-contract.md
|
||||
- Modules: trex_core/adapters/messaging (snapshot RPC 핸들러), 응답 스키마(transport-무관)
|
||||
- Tests: envelope 스키마 검증(빈 응답 포함), degraded 주입 테스트
|
||||
- Docs: docs/discovery/dcy-005_20260604_portfolio-hub-asset-snapshot-api.md, docs/discovery/dcy-016_20260608_operator-cli-rpc-commands.md, docs/interface-contract.md
|
||||
- Modules: trex_core/adapters/messaging (snapshot RPC 핸들러), trex_core/application/portfolio.py (`build_snapshot_envelope` — `slots[]`), 응답 스키마(transport-무관), trex_core/runtime/cli.py (REQ-032 companion)
|
||||
- Tests: envelope 스키마 검증(빈 응답 포함), degraded 주입 테스트, `slots[]` 스키마(빈 배열·status enum) + `trex-core slots` 실제 슬롯 상태 표시
|
||||
|
||||
## Notes
|
||||
- transport 는 dcy-006(REQ-021/022) 의 snapshot RPC 를 사용하며 본 REQ 는 logical 계약만 정의한다.
|
||||
@@ -132,3 +134,75 @@
|
||||
- AC-1: tests/application/test_snapshot_handler.py::test_envelope_shape_returned + test_snapshot_selection.py::test_empty_envelope_assets_is_list_not_null.
|
||||
- AC-2: tests/application/test_snapshot_handler.py::test_degraded_signal_does_not_leak_credentials + 실 daemon 운영 검증.
|
||||
- AC-3: tests/application/test_snapshot_selection.py::test_envelope_shape_no_price_field.
|
||||
|
||||
### CHG-20260608-01
|
||||
- Date: 2026-06-08
|
||||
- Author: project maintainers
|
||||
|
||||
#### Change Summary
|
||||
- dcy-016 후속 정정. `trex-core slots`(REQ-032)가 universe `assets`(portfolio: asset_id/symbol/asset_quantity)를 **슬롯 상태로 오인 표시**하던 갭 — snapshot envelope 이 슬롯 상태 목록을 운반하지 않아 발생(운영자 관측: 10개 슬롯인데 universe portfolio 1개만, status 없음). envelope 에 **read-only per-slot 상태 목록 `slots[]` 를 additive 로 추가**한다(dcy-017 의 per-slot `watchdog` 동봉 선례와 동일 방식). BAT-007 에서 HTTP 슬롯 read(REQ-008/025) 제거 후 슬롯 상태 read 표면이 부재했던 점을 함께 해소.
|
||||
|
||||
#### Intent
|
||||
- 운영자가 데몬의 **실제 슬롯 상태(idle/holding/pending_buy/pending_sell)** 를 snapshot RPC 로 관측할 수 있게 한다. strategy-facing `assets[]`(universe) 계약은 그대로 유지하고, 슬롯 상태는 별도 `slots[]` 로 분리 노출한다.
|
||||
|
||||
#### Acceptance Criteria Delta
|
||||
- Added:
|
||||
- AC-4: Given 스냅샷 요청, Then envelope 에 read-only `slots[]` 배열을 포함한다 — 각 항목은 `slot_id`·`status`·`symbol`·`qty`·`limit_price`·`avg_price`·`portfolio_id` 를 담고, 슬롯이 없어도 `[]`(null 아님)다. `assets[]`(universe) 와 독립이며 strategy 소비자는 무시 가능(additive, 하위호환).
|
||||
- Updated:
|
||||
- none (AC-1~3 의 `assets[]`/degraded/no-price 계약 불변)
|
||||
- Removed:
|
||||
- none
|
||||
|
||||
#### Impacted Area Delta
|
||||
- Docs:
|
||||
- docs/interface-contract.md(snapshot 응답에 `slots[]` 필드 추가), dcy-005/dcy-016
|
||||
- Modules:
|
||||
- trex_core/application/portfolio.py(`build_snapshot_envelope` 에 `slots[]`), trex_core/application/messaging_handlers.py(envelope), trex_core/runtime/cli.py(REQ-032 companion — `slots` 가 `slots[]` 를 읽도록, `universe` 는 `assets[]` 유지)
|
||||
- Tests:
|
||||
- envelope `slots[]` 스키마(빈 배열 포함), `trex-core slots` 가 universe 가 아닌 실제 슬롯 상태(status 포함)를 표시
|
||||
|
||||
#### Delivery Trace Delta
|
||||
- Batches:
|
||||
- BAT-008 (REQ-028 최초 구현), BAT-020 (dcy-016 — `slots` CLI 가 갭 노출). 정정 구현은 신규 BAT-022 (예정).
|
||||
- Releases:
|
||||
- none
|
||||
|
||||
#### Revalidation Impact
|
||||
- Reverification Needed: yes (신규 AC-4)
|
||||
- Existing Implementation Invalidated: no (AC-1~3 assets/degraded/no-price 근거 유지)
|
||||
- Status Recommendation: Implemented → Approved (신규 AC-4 미구현)
|
||||
- Follow-up Action: REQ-032 companion change-req + draft-batch(BAT-022) → 구현/검증
|
||||
|
||||
#### Validation Plan
|
||||
- AC-4: envelope `slots[]` 스키마 테스트(빈 배열·status enum 포함) + `trex-core slots` 통합 검증(universe 가 아닌 실제 슬롯 상태 N개를 status 와 함께 표시).
|
||||
|
||||
### CHG-20260608-02
|
||||
- Date: 2026-06-08
|
||||
- Author: Justin Ko
|
||||
|
||||
#### Change Summary
|
||||
- Status 변경: Approved → Implemented. BAT-022 release-candidate verification evidence 기반(AC-4 envelope `slots[]`).
|
||||
|
||||
#### Intent
|
||||
- envelope `slots[]`(operator-facing per-slot 상태) 구현 완료 상태로 동기화.
|
||||
|
||||
#### Acceptance Criteria Delta
|
||||
- Added/Updated/Removed: 없음
|
||||
|
||||
#### Impacted Area Delta
|
||||
- Docs/Modules/Tests: 없음
|
||||
|
||||
#### Delivery Trace Delta
|
||||
- Batches:
|
||||
- BAT-022 (release-candidate)
|
||||
- Releases:
|
||||
- none
|
||||
|
||||
#### Revalidation Impact
|
||||
- Reverification Needed: no (BAT-022 에서 완료)
|
||||
- Existing Implementation Invalidated: no
|
||||
- Status Recommendation: Implemented
|
||||
- Follow-up Action: release 단계 별도 진행
|
||||
|
||||
#### Validation Plan
|
||||
- AC-4: tests/application/test_snapshot_selection.py::test_envelope_shape_no_price_field + test_snapshot_handler.py::test_envelope_shape_returned(envelope `slots` list). 운영 재현: 신규 코드 데몬 + 실 PostgreSQL/RabbitMQ 에서 `trex-core slots` → slots=10·status 표시·DB `(2026-06-08,idle,10)` 일치(BAT-022 verification). pytest 인프라 게이트는 Human Approval Memo 수용 잔여.
|
||||
|
||||
@@ -213,3 +213,75 @@
|
||||
#### Validation Plan
|
||||
|
||||
- BAT-021: slots watchdog-owned 표시 단위.
|
||||
|
||||
### CHG-20260608-03
|
||||
|
||||
- Date: 2026-06-08
|
||||
- Author: project maintainers
|
||||
|
||||
#### Change Summary
|
||||
|
||||
- (change-req, dcy-016 후속) `trex-core slots` 가 universe `assets[]`(portfolio: asset_id/symbol/asset_quantity)를 **슬롯 상태로 오인 표시**하던 갭 정정(운영 관측: 10개 슬롯인데 universe 1개·status 없음). snapshot envelope 의 신규 read-only `slots[]`(REQ-028 CHG-20260608-01, AC-4)를 읽어 **실제 슬롯 상태(`slot_id`·`status`(idle/holding/pending_buy/pending_sell)·`symbol`·`qty`·`limit_price`·`avg_price`)** 를 표시한다. `universe` 명령은 `assets[]`(universe) 유지로 둘을 명확히 분리. watchdog-owned 표시(CHG-20260608-02)는 `slots[]` 항목과 결합.
|
||||
|
||||
#### Intent
|
||||
|
||||
- 운영자가 `trex-core slots` 로 데몬의 실제 슬롯 상태(idle/holding/pending_*)를 관측하게 한다. `universe`(거래 대상 portfolio)와 `slots`(데몬 슬롯 할당 상태)를 표면상 명확히 구분.
|
||||
|
||||
#### Acceptance Criteria Delta
|
||||
|
||||
- Updated:
|
||||
- `slots` 출력은 universe `assets[]` 가 아니라 envelope `slots[]`(슬롯 상태, `status` 포함)를 표시한다. `--status held/holding/...` 필터는 슬롯 상태 기준으로 적용된다.
|
||||
- Added: none
|
||||
- Removed: none
|
||||
|
||||
#### Impacted Area Delta
|
||||
|
||||
- Modules: `trex_core/runtime/cli.py`(`_cmd_slots` → `resp["slots"]` 읽기). 전제: REQ-028 envelope `slots[]`(CHG-20260608-01).
|
||||
- Tests: `slots` 가 universe 가 아닌 실제 슬롯 상태(status 포함)를 표시, universe 와 구분.
|
||||
|
||||
#### Delivery Trace Delta
|
||||
|
||||
- Batches: BAT-020 (`slots` 최초 구현 — 갭 발생). 정정은 신규 BAT-022 (REQ-028 AC-4 와 함께).
|
||||
- Releases: none
|
||||
|
||||
#### Revalidation Impact
|
||||
|
||||
- Reverification Needed: yes
|
||||
- Existing Implementation Invalidated: yes (`slots` 가 universe assets 를 표시하던 부분이 슬롯 상태 의도와 불일치)
|
||||
- Status Recommendation: Implemented → Approved (REQ-028 `slots[]` 의존, BAT-022 재구현/재검증)
|
||||
- Follow-up Action: REQ-028 AC-4 구현 후 draft-batch(BAT-022) → 구현/검증
|
||||
|
||||
#### Validation Plan
|
||||
|
||||
- BAT-022: `trex-core slots` 가 envelope `slots[]` 의 실제 슬롯 상태(status enum 포함)를 표시하고 universe(`assets[]`)와 구분됨을 단위/통합 검증.
|
||||
|
||||
### CHG-20260608-04
|
||||
- Date: 2026-06-08
|
||||
- Author: Justin Ko
|
||||
|
||||
#### Change Summary
|
||||
- Status 변경: Approved → Implemented. BAT-022 release-candidate verification evidence 기반(`slots` 가 envelope `slots[]` 표시, CHG-20260608-03).
|
||||
|
||||
#### Intent
|
||||
- `trex-core slots` 가 실제 슬롯 상태(status)를 표시하는 정정 구현 완료 동기화.
|
||||
|
||||
#### Acceptance Criteria Delta
|
||||
- Added/Updated/Removed: 없음
|
||||
|
||||
#### Impacted Area Delta
|
||||
- Docs/Modules/Tests: 없음
|
||||
|
||||
#### Delivery Trace Delta
|
||||
- Batches:
|
||||
- BAT-022 (release-candidate)
|
||||
- Releases:
|
||||
- none
|
||||
|
||||
#### Revalidation Impact
|
||||
- Reverification Needed: no (BAT-022 에서 완료)
|
||||
- Existing Implementation Invalidated: no
|
||||
- Status Recommendation: Implemented
|
||||
- Follow-up Action: release 단계 별도 진행
|
||||
|
||||
#### Validation Plan
|
||||
- tests/runtime/test_cli_operator.py::test_slots_shows_real_slot_state_not_universe / ::test_slots_status_filters_by_slot_status / ::test_slots_marks_watchdog_owned. 운영 재현(BAT-022): 신규 코드 데몬 + 실 인프라 `trex-core slots` → slots=10 status·`--status holding`=0·universe(assets=1) 분리·비침습. pytest 인프라 게이트는 Human Approval Memo 수용 잔여.
|
||||
|
||||
+1
-1
@@ -139,8 +139,8 @@
|
||||
|
||||
## Recent Change Log Summary
|
||||
|
||||
- 2026-06-08 :: REQ-028 + REQ-032 change-req→Implemented (CHG-20260608-01/02 / -03/04, dcy-016 후속, BAT-022): `trex-core slots` 가 universe `assets`(portfolio)를 슬롯 상태로 오인 표시하던 갭 정정(10개 슬롯인데 universe 1개·status 없음 — BAT-007 HTTP 슬롯 read 제거 후 슬롯 상태 read 표면 부재가 근인). snapshot envelope 에 read-only per-slot `slots[]`(canonical `slot_to_dict`: slot_id/trading_date/status/symbol/qty/avg_price/entry_price/.../portfolio_id) additive 추가(REQ-028 AC-4, strategy `assets[]` 불변), `trex-core slots` 가 `slots[]`(실제 슬롯 상태) 표시·`--status` 슬롯 status 필터·`universe` 는 `assets[]` 유지(REQ-032). BAT-007 stub/BAT-008 envelope 슬롯 스키마 단일화. 신규 코드 데몬 + 실 인프라 운영 재현 PASS(slots=10·status·DB 일치·universe 분리·비침습). 단위 338 passed, ruff clean. pytest 인프라 게이트는 Human Approval Memo 수용. 둘 다 **Implemented**. (change-req/confirm-req-implemented)
|
||||
- 2026-06-08 :: REQ-034/021/037/038/039/032/042 change-req (dcy-017, BAT-021): 전략 이탈로 슬롯이 워치독(안전망) 소유로 넘어간 상태를 가시화(issue #12). snapshot RPC 응답에 holding 슬롯 워치독 상태(expired/last_seen) read-only 동봉(best-effort) + `WatchdogStore.last_seen`(REQ-034/021), `trex-core slots` WATCHDOG-OWNED 표시(REQ-032), safety-sell 워치독 소유 진입(WARN)/이탈(INFO)·발동(forced_close·stop_loss=WARN, take_profit·market_close=INFO) 로그+in-process 카운터(REQ-037/038/039), baseline 2문서 정합(REQ-042). best-effort(청산 무차단), Redis 미채택. 전부 change-req(신규 REQ 없음, OQ-3). 단위 335 passed, 인프라 게이트 369 passed, ruff clean. (change-req/confirm-req-implemented)
|
||||
- 2026-06-08 :: REQ-032/022/021/034/026 change-req (dcy-016, BAT-020): 운영자 CLI 확장 — `universe`/`slots`(snapshot RPC)·`enter`(buy intent RPC)·`exit`(sell intent RPC) 추가(전부 데몬 RPC 통일, 단일 writer 보존, DB 직접 쓰기 없음)(REQ-032), CLI request-reply RPC 클라이언트 재사용(REQ-022), snapshot 요청 워치독 억제 플래그(`touch_watchdog`/`actor="operator"`)로 운영자 조회가 전략 생존 신호로 오인되지 않게(REQ-021/034, R-4), enter/exit 안전장치(확인 프롬프트/`--yes`/live 이중 ack/`--dry-run`)+`--json`+인스턴스 라우팅, baseline 2문서 정합(REQ-026). pause/resume 는 direct-DB 유지. 전부 change-req(신규 REQ 없음, OQ-3). 단위 327 passed, 인프라 게이트 361 passed, ruff clean. (change-req/confirm-req-implemented)
|
||||
- 2026-06-08 :: REQ-017/019/018/020 change-req (dcy-015, BAT-019): portfolio-hub 404(미등록 포트폴리오)가 retryable 로 분류되어 백오프·상한 없이 영구 재시도되던 버그(issue #13) 정정. 404/400/422→`LedgerPermanentError`(REQ-017), outbox 영속→터미널 `delivery_failed` 즉시 + retryable 지수 백오프+jitter+`next_retry_at`+max_attempts→dead-letter + `list_pending` 백오프 스킵 + 마이그레이션 007(status enum/컬럼) + 백오프 env(REQ-019), 터미널 WARN+카운터 가시화(REQ-018), baseline 3문서 정합(REQ-020). 전부 change-req(신규 REQ 없음, OQ-3). 단위 311 passed, 인프라 게이트 345 passed, ruff clean. (change-req/confirm-req-implemented)
|
||||
- 2026-06-08 :: REQ-005/048/009 change-req (dcy-014, BAT-018): 당일 슬롯 생성 기준을 시스템 UTC 날짜에서 설정 타임존(기본 KST) 기준 `trading_date` 로 분리(timestamp 는 UTC 유지) + 데몬 1회 preallocation 을 `DailyPreallocationScheduler`(TZ 일자당 1회 멱등 fire, 재시작 없이 다음날 자동 생성)로 대체(REQ-005), TOML `trading_timezone`/`daily_preallocation_time` 키(REQ-048), runtime-flows/data-model 정합(REQ-009). 전부 change-req(신규 REQ 없음, OQ-3; 스케줄러는 REQ-030 패턴 준용). 단위 302 passed, 인프라 게이트 334 passed, ruff clean. (change-req/confirm-req-implemented)
|
||||
- 2026-06-08 :: REQ-012/013/018/031/014/015 change-req (dcy-013, BAT-017): KIS 외부 주문 취소 시 슬롯이 pending_sell 에 영구 갇히던 버그(issue #9) 정정. KIS `poll_fill` 이 `cncl_yn` 파싱해 외부 취소 감지 — 체결 0 취소→`cancelled`(롤백 자동 복구), 부분 체결 후 취소→`is_partial_cancel`(체결분 원장 1회+qty 차감+holding 복귀, OQ-1 A안)(REQ-012/013/018), 잔여 포지션 정합 연계(REQ-031), pending stale 워치독 Alert-only(`TREX_PENDING_STALE_THRESHOLD`, WARN+카운터, 상태 미변경)(REQ-014), baseline 2문서 정합(REQ-015). 전부 change-req(신규 REQ 없음, OQ-3). 단위 294 passed, 인프라 게이트 326 passed, ruff clean. (change-req/confirm-req-implemented)
|
||||
|
||||
@@ -43,8 +43,11 @@ def test_envelope_shape_returned():
|
||||
correlation_id="snap-1",
|
||||
))
|
||||
body = resp.payload
|
||||
assert set(body.keys()) == {"assets", "view", "available_slots", "degraded", "reason", "message"}
|
||||
# BAT-022: `slots[]` additive operator-facing per-slot state.
|
||||
assert set(body.keys()) == {"assets", "view", "available_slots", "degraded",
|
||||
"reason", "message", "slots"}
|
||||
assert isinstance(body["assets"], list)
|
||||
assert isinstance(body["slots"], list)
|
||||
assert body["view"] == "all"
|
||||
assert body["degraded"] is False
|
||||
assert body["reason"] is None
|
||||
|
||||
@@ -146,8 +146,12 @@ def test_envelope_shape_no_price_field():
|
||||
store, pause, repo, svc = _wire()
|
||||
env = _env(store, pause, repo, available_slots=3, view=View.ALL)
|
||||
d = env.to_dict()
|
||||
assert set(d.keys()) == {"assets", "view", "available_slots", "degraded", "reason", "message"}
|
||||
# BAT-022: `slots[]` is an additive operator-facing field alongside assets.
|
||||
assert set(d.keys()) == {"assets", "view", "available_slots", "degraded",
|
||||
"reason", "message", "slots"}
|
||||
assert isinstance(d["assets"], list)
|
||||
assert isinstance(d["slots"], list)
|
||||
# strategy-facing assets[] keep the strict no-price shape (REQ-028 AC-3).
|
||||
for asset in d["assets"]:
|
||||
assert set(asset.keys()) == {"symbol", "asset_id", "cash_balance",
|
||||
"asset_quantity", "active_strategy_id",
|
||||
|
||||
@@ -29,6 +29,18 @@ _SNAPSHOT = {
|
||||
"assets": [{"asset_id": "PF-1", "symbol": "005930", "asset_quantity": 0,
|
||||
"active_strategy_id": "orb"}],
|
||||
"view": "all", "available_slots": 9, "degraded": False, "reason": None,
|
||||
# BAT-022: envelope per-slot state (operator-facing). `slots` queries read
|
||||
# this, not the universe `assets[]`.
|
||||
"slots": [
|
||||
{"slot_id": "S-1", "trading_date": "2026-06-08", "status": "holding",
|
||||
"symbol": "005930", "qty": 10.0, "avg_price": 28700.0, "entry_price": None,
|
||||
"allocated_capital": None, "strategy_id": "orb", "sell_strategy_id": None,
|
||||
"portfolio_id": "PF-1", "forced_close_at": None},
|
||||
{"slot_id": "S-2", "trading_date": "2026-06-08", "status": "idle",
|
||||
"symbol": None, "qty": None, "avg_price": None, "entry_price": None,
|
||||
"allocated_capital": None, "strategy_id": None, "sell_strategy_id": None,
|
||||
"portfolio_id": None, "forced_close_at": None},
|
||||
],
|
||||
}
|
||||
_ADMITTED = {"intent_id": "i", "status": "admitted", "slot_id": "S-1", "reason": None}
|
||||
_REJECTED = {"intent_id": "i", "status": "rejected", "slot_id": None,
|
||||
@@ -47,18 +59,50 @@ def test_universe_uses_snapshot_queue_with_operator_suppression(capsys):
|
||||
assert "PF-1" in capsys.readouterr().out
|
||||
|
||||
|
||||
def test_slots_status_holding_maps_to_held_view():
|
||||
def test_universe_json_is_scoped_to_universe(capsys):
|
||||
"""BAT-022: `universe --json` emits universe data only — `slots[]`/`watchdog[]`
|
||||
(operator slot table) are NOT leaked into the universe view."""
|
||||
rpc = _RpcRecorder(_SNAPSHOT)
|
||||
cli.main(["universe", "--json"], rpc=rpc)
|
||||
out = json.loads(capsys.readouterr().out)
|
||||
assert set(out.keys()) == {"instance", "view", "assets", "available_slots",
|
||||
"degraded", "reason"}
|
||||
assert "slots" not in out and "watchdog" not in out
|
||||
|
||||
|
||||
def test_slots_shows_real_slot_state_not_universe(capsys):
|
||||
"""BAT-022: `slots` shows envelope `slots[]` (slot table) with status,
|
||||
not the universe `assets[]`."""
|
||||
rpc = _RpcRecorder(_SNAPSHOT)
|
||||
cli.main(["slots"], rpc=rpc)
|
||||
out = capsys.readouterr().out
|
||||
# request stays non-invasive (operator suppression)
|
||||
assert rpc.calls[0]["payload"]["actor"] == "operator"
|
||||
assert rpc.calls[0]["payload"]["touch_watchdog"] is False
|
||||
# both slots shown, with status column (slot state, not universe assets)
|
||||
assert "count=2" in out
|
||||
assert "S-1\tholding" in out and "S-2\tidle" in out
|
||||
|
||||
|
||||
def test_slots_status_filters_by_slot_status(capsys):
|
||||
"""BAT-022: `--status` filters by slot status client-side (no view mapping)."""
|
||||
rpc = _RpcRecorder(_SNAPSHOT)
|
||||
cli.main(["slots", "--status", "holding"], rpc=rpc)
|
||||
assert rpc.calls[0]["payload"]["view"] == "held"
|
||||
out = capsys.readouterr().out
|
||||
assert "count=1" in out and "S-1\tholding" in out and "S-2" not in out
|
||||
|
||||
|
||||
def test_slots_marks_watchdog_owned(capsys):
|
||||
"""dcy-017: slots flags strategy-absent (watchdog-owned) positions."""
|
||||
resp = {
|
||||
"assets": [{"asset_id": "PF-1", "symbol": "005930", "asset_quantity": 10,
|
||||
"active_strategy_id": "orb"}],
|
||||
"view": "held", "available_slots": 9, "degraded": False, "reason": None,
|
||||
"assets": [], "view": "all", "available_slots": 9, "degraded": False,
|
||||
"reason": None,
|
||||
"slots": [{"slot_id": "S-1", "trading_date": "2026-06-08",
|
||||
"status": "holding", "symbol": "005930", "qty": 10.0,
|
||||
"avg_price": 28700.0, "entry_price": None,
|
||||
"allocated_capital": None, "strategy_id": "orb",
|
||||
"sell_strategy_id": None, "portfolio_id": "PF-1",
|
||||
"forced_close_at": None}],
|
||||
"watchdog": [{"slot_id": "S-1", "portfolio_id": "PF-1", "symbol": "005930",
|
||||
"expired": True, "last_seen": None}],
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ from trex_core.application.portfolio import (
|
||||
SnapshotStore,
|
||||
View,
|
||||
build_snapshot_envelope,
|
||||
slot_to_dict,
|
||||
)
|
||||
from trex_core.application.watchdog import WatchdogStore
|
||||
from trex_core.domain.slot import (
|
||||
@@ -460,21 +461,7 @@ def _today() -> date_cls:
|
||||
return datetime.now(tz=timezone.utc).date()
|
||||
|
||||
|
||||
def _slot_to_dict(s) -> dict:
|
||||
return {
|
||||
"slot_id": str(s.slot_id),
|
||||
"trading_date": s.trading_date.isoformat(),
|
||||
"status": s.status.value,
|
||||
"symbol": s.symbol,
|
||||
"qty": float(s.qty) if s.qty is not None else None,
|
||||
"avg_price": float(s.avg_price) if s.avg_price is not None else None,
|
||||
# dcy-012 / NFR-3: external read field name kept as "entry_price" for
|
||||
# contract compatibility; internally backed by the renamed limit_price
|
||||
# (the pending order limit, cleared once holding).
|
||||
"entry_price": float(s.limit_price) if s.limit_price is not None else None,
|
||||
"allocated_capital": float(s.allocated_capital) if s.allocated_capital is not None else None,
|
||||
"strategy_id": s.strategy_id,
|
||||
"sell_strategy_id": s.sell_strategy_id,
|
||||
"portfolio_id": s.portfolio_id,
|
||||
"forced_close_at": s.forced_close_at.isoformat() if s.forced_close_at else None,
|
||||
}
|
||||
# BAT-022: canonical slot dict shared with build_snapshot_envelope (single
|
||||
# source of truth so BAT-007 stub and BAT-008 envelope emit an identical
|
||||
# `slots[]` shape). Local alias kept for existing call sites.
|
||||
_slot_to_dict = slot_to_dict
|
||||
|
||||
@@ -266,6 +266,11 @@ class SnapshotEnvelope:
|
||||
degraded: bool
|
||||
reason: Optional[str]
|
||||
message: Optional[str] = None
|
||||
# REQ-028 CHG-20260608-01 (BAT-022): operator-facing per-slot state list,
|
||||
# additive and independent of strategy-facing `assets[]` (universe). Lets
|
||||
# `trex-core slots` show the daemon's real slot table (idle/holding/pending)
|
||||
# instead of universe portfolios. Always a list ([] when no slots).
|
||||
slots: list[dict] = field(default_factory=list)
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
return {
|
||||
@@ -275,6 +280,7 @@ class SnapshotEnvelope:
|
||||
"degraded": self.degraded,
|
||||
"reason": self.reason,
|
||||
"message": self.message,
|
||||
"slots": list(self.slots),
|
||||
}
|
||||
|
||||
|
||||
@@ -290,6 +296,32 @@ def _asset_to_dict(item: PortfolioItem) -> dict:
|
||||
}
|
||||
|
||||
|
||||
def slot_to_dict(s) -> dict:
|
||||
"""Canonical operator-facing per-slot state shape (REQ-028 AC-4, BAT-022).
|
||||
|
||||
Shared by the BAT-008 envelope (`build_snapshot_envelope`) and the BAT-007
|
||||
stub-shape handler so `trex-core slots` sees an identical slot dict on either
|
||||
path. Unlike `_asset_to_dict` (strategy universe, no-price), this is
|
||||
operator-only and exposes prices. External read field name `entry_price`
|
||||
is kept for contract compatibility (dcy-012 / NFR-3 — internally backed by
|
||||
the renamed `limit_price`, the pending order limit cleared once holding).
|
||||
"""
|
||||
return {
|
||||
"slot_id": str(s.slot_id),
|
||||
"trading_date": s.trading_date.isoformat(),
|
||||
"status": s.status.value,
|
||||
"symbol": s.symbol,
|
||||
"qty": float(s.qty) if s.qty is not None else None,
|
||||
"avg_price": float(s.avg_price) if s.avg_price is not None else None,
|
||||
"entry_price": float(s.limit_price) if s.limit_price is not None else None,
|
||||
"allocated_capital": float(s.allocated_capital) if s.allocated_capital is not None else None,
|
||||
"strategy_id": s.strategy_id,
|
||||
"sell_strategy_id": s.sell_strategy_id,
|
||||
"portfolio_id": s.portfolio_id,
|
||||
"forced_close_at": s.forced_close_at.isoformat() if s.forced_close_at else None,
|
||||
}
|
||||
|
||||
|
||||
def _sanitize(message: str) -> str:
|
||||
"""Strip credentials/URLs/tokens — REQ-028 AC-2 sanitization."""
|
||||
if not message:
|
||||
@@ -395,6 +427,10 @@ def build_snapshot_envelope(
|
||||
d["asset_quantity"] = holding_qty[pid]
|
||||
out.append(d)
|
||||
|
||||
# REQ-028 CHG-20260608-01 (BAT-022): operator-facing per-slot state — the
|
||||
# full daemon slot table for `day`, independent of the asset view filter.
|
||||
slot_states = [slot_to_dict(s) for s in slots]
|
||||
|
||||
reason_code, message = store.degraded_state()
|
||||
return SnapshotEnvelope(
|
||||
assets=out,
|
||||
@@ -403,4 +439,5 @@ def build_snapshot_envelope(
|
||||
degraded=reason_code is not None,
|
||||
reason=reason_code.value if reason_code is not None else None,
|
||||
message=message,
|
||||
slots=slot_states,
|
||||
)
|
||||
|
||||
+28
-15
@@ -102,7 +102,11 @@ def _cmd_universe(args, cfg, rpc) -> int:
|
||||
correlation_id=str(uuid.uuid4()), timeout=args.timeout)
|
||||
assets = resp.get("assets", [])
|
||||
if args.json:
|
||||
_emit(resp, as_json=True)
|
||||
# Scope to universe data — `slots[]`/`watchdog[]` belong to `slots`.
|
||||
_emit({"instance": instance, "view": resp.get("view"), "assets": assets,
|
||||
"available_slots": resp.get("available_slots"),
|
||||
"degraded": resp.get("degraded"), "reason": resp.get("reason")},
|
||||
as_json=True)
|
||||
else:
|
||||
print(f"instance={instance} view=all assets={len(assets)} "
|
||||
f"degraded={resp.get('degraded')}")
|
||||
@@ -113,26 +117,34 @@ def _cmd_universe(args, cfg, rpc) -> int:
|
||||
|
||||
def _cmd_slots(args, cfg, rpc) -> int:
|
||||
instance = _resolve_instance(args)
|
||||
view = {"holding": "held", "held": "held", "buyable": "buyable"}.get(
|
||||
args.status or "all", "all"
|
||||
)
|
||||
payload = {"view": view, "actor": "operator", "touch_watchdog": False,
|
||||
payload = {"view": "all", "actor": "operator", "touch_watchdog": False,
|
||||
"trading_date": _today_iso(cfg)}
|
||||
resp = rpc(topology.snapshot_queue(instance), payload,
|
||||
correlation_id=str(uuid.uuid4()), timeout=args.timeout)
|
||||
assets = resp.get("assets", [])
|
||||
# REQ-032 CHG-20260608-03 (BAT-022): show the real daemon slot table
|
||||
# (envelope `slots[]`), not the universe `assets[]`. `--status` filters by
|
||||
# slot status; `universe` keeps showing `assets[]`.
|
||||
slots = resp.get("slots", [])
|
||||
status = (args.status or "all").lower()
|
||||
if status in ("holding", "held"):
|
||||
slots = [s for s in slots if s.get("status") == "holding"]
|
||||
elif status == "buyable":
|
||||
slots = [s for s in slots if s.get("status") == "idle"]
|
||||
elif status == "pending":
|
||||
slots = [s for s in slots if s.get("status") in ("pending_buy", "pending_sell")]
|
||||
# dcy-017: holding-slot watchdog state (strategy-absent ownership).
|
||||
owned = {w.get("portfolio_id"): w for w in resp.get("watchdog", []) if w.get("expired")}
|
||||
owned = {w.get("portfolio_id") for w in resp.get("watchdog", []) if w.get("expired")}
|
||||
if args.json:
|
||||
_emit(resp, as_json=True)
|
||||
_emit({"instance": instance, "status": status, "slots": slots}, as_json=True)
|
||||
else:
|
||||
print(f"instance={instance} view={view} count={len(assets)} "
|
||||
f"watchdog_owned={len(owned)}")
|
||||
for a in assets:
|
||||
pid = a.get("asset_id")
|
||||
print(f"instance={instance} status={status} count={len(slots)} "
|
||||
f"watchdog_owned={len([s for s in slots if s.get('portfolio_id') in owned])}")
|
||||
for s in slots:
|
||||
pid = s.get("portfolio_id")
|
||||
tag = " [WATCHDOG-OWNED: strategy absent]" if pid in owned else ""
|
||||
print(f" {pid}\t{a.get('symbol')}\tqty={a.get('asset_quantity')}\t"
|
||||
f"strategy={a.get('active_strategy_id')}{tag}")
|
||||
print(f" {s.get('slot_id')}\t{s.get('status')}\t{s.get('symbol')}\t"
|
||||
f"qty={s.get('qty')}\tlimit={s.get('entry_price')}\t"
|
||||
f"avg={s.get('avg_price')}\tpid={s.get('portfolio_id')}{tag}")
|
||||
return 0
|
||||
|
||||
|
||||
@@ -250,7 +262,8 @@ def _build_parser() -> argparse.ArgumentParser:
|
||||
_add_common(p_uni)
|
||||
|
||||
p_slots = sub.add_parser("slots", help="List positions (snapshot RPC)")
|
||||
p_slots.add_argument("--status", choices=["all", "held", "holding", "buyable"], default="all")
|
||||
p_slots.add_argument("--status", choices=["all", "held", "holding", "buyable", "pending"],
|
||||
default="all")
|
||||
_add_common(p_slots)
|
||||
|
||||
p_enter = sub.add_parser("enter", help="Submit a buy intent (intent RPC)")
|
||||
|
||||
Reference in New Issue
Block a user