feat(rel-007): dcy-025/024 + BAT-035/036 — slots.req 즉시 교체 + safety_sell_enabled
BAT-035 (dcy-025): trex.snapshot.req → trex.slots.req 즉시 교체
- topology: SNAPSHOT_REQ_QUEUE 제거, SLOTS_REQ_QUEUE = "trex.slots.req" 추가
- portfolio.py: View/build_snapshot_envelope/SnapshotEnvelope/_asset_to_dict 제거;
slot_to_dict 시그니처 확장(snapshot_store/watchdog_store/pause_store/watchdog_timeout_sec)
— asset_quantity(hub)/cash_balance/cash_estimated/warned/watchdog{}/paused 병합 (REQ-074)
- messaging_handlers.py: make_snapshot_rpc_handler 제거, make_slots_rpc_handler 추가
— filter(all/inactive/pending/holding/error) + spy(bool) 처리; strategy 필드 제외 (REQ-073/074)
- daemon.py: _snapshot_rpc_loop → _slots_rpc_loop, make_slots_rpc_handler 연결
- cli.py: universe/slots → slots_queue + spy=True + server-side filter; buyable→inactive 매핑 (REQ-063)
- rabbitmq.py: snapshot_queue → slots_queue
- docs: interface-contract.md + runtime-flows.md slots.req 계약으로 업데이트 (REQ-075)
- REQ-028: Implemented → Deprecated (코드 삭제 완료)
- 테스트: test_snapshot_handler/selection/rpc_watchdog/messaging_snapshot_consumer 삭제;
test_slots_handler.py(신규, 13 cases), test_killswitch_masking_resume 재작성,
test_ledger_option_d._apply_cash_estimate → slots handler 경유, test_topology/cli_operator 갱신
BAT-036 (dcy-024): per-instance safety_sell_enabled 플래그
- config.py: DaemonInstanceConfig + DaemonConfig 에 safety_sell_enabled: bool = True 추가;
from_toml 파싱, from_config 전파
- daemon.py: start() workers 조건부 — safety_sell_enabled=false 시 trex-safety-sell 미기동 + WARNING
- tests: test_instance_config.py 에 safety_sell_enabled 파싱 3개 케이스 추가
REQ: 073/074/075/021/034/063/048/005 Approved → Implemented
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+27
-20
@@ -23,7 +23,7 @@
|
||||
|
||||
- `trex-core-daemon` :: 슬롯 코어 데몬 (lifecycle + RabbitMQ 통신, BAT-002/BAT-007)
|
||||
- `trex-core-reset` :: DB 를 자산 중심 스키마로 wipe + 재초기화 (REQ-064, 운영자 실행 — 데이터 보존 cutover 대체; 파괴적·DB 전역, dry-run 기본)
|
||||
- RabbitMQ `trex.snapshot.req` :: 전략 모듈의 스냅샷 RPC (request-reply, BAT-007)
|
||||
- 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)
|
||||
@@ -38,18 +38,19 @@
|
||||
- `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-snapshot-rpc` :: `trex.snapshot.req` 소비자 (응답 holding 슬롯마다 워치독 `last_seen_at` 갱신 REQ-034)
|
||||
- `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.snapshot.req`, BAT-007)
|
||||
## Flow: `슬롯 RPC` (RabbitMQ `trex.slots.req`, BAT-035 — REQ-028 `trex.snapshot.req` 대체)
|
||||
|
||||
1. 전략 모듈이 RPC 요청을 `trex.snapshot.req` 큐로 publish — payload 에 `view`/`strategy_id`/`trading_date`, properties 에 `correlation_id` + `reply_to`(direct reply-to).
|
||||
2. `trex-snapshot-rpc` 스레드가 메시지 수신 → `make_snapshot_rpc_handler(slot_repo, slot_service)` 로 슬롯 목록 + 가용성 계산.
|
||||
3. 응답을 `reply_to` 큐로 publish (`correlation_id` 일치) → manual ack.
|
||||
4. read-only 이므로 dedup 불요; 재요청 시 동일 응답을 다시 생성한다.
|
||||
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)
|
||||
|
||||
@@ -112,30 +113,34 @@
|
||||
운영자 CLI(`trex-core`)는 조회·매매를 데몬 RabbitMQ RPC 로 통일해 단일 writer admission 과 불변식을 보존한다(DB 직접 쓰기 없음). pause/resume/paused 만 기존 direct-DB 유지(OQ-3).
|
||||
|
||||
1. 인스턴스 라우팅: `--config <toml>`(canonical, REQ-048 `instance_key`) 또는 `--instance <key>`(단축·override), 미지정 시 `default`. 큐명은 `_instance_queue` 규칙(`{base}` 또는 `{base}.{instance}`). 모든 출력에 대상 인스턴스 표시(R-2).
|
||||
2. `universe`/`slots`: `trex.snapshot.req` RPC(`actor="operator"`/`touch_watchdog=false`) → 응답 envelope 의 assets 출력. **워치독 억제**로 운영자 조회가 전략 생존 신호로 오인되지 않는다(REQ-034, R-4). `slots --status holding`→`view=held`.
|
||||
2. `universe`/`slots`: `trex.slots.req` RPC(`spy=true`) → 응답 `slots[]` 출력. **워치독 억제**(`spy=true`)로 운영자 조회가 전략 생존 신호로 오인되지 않는다(REQ-034 CHG-01, R-4). `universe`→`filter=inactive`; `slots --status <val>`→server-side `filter=<val>`(`buyable`→`inactive`). BAT-035.
|
||||
3. `enter <portfolio_id>`/`exit <slot_id>`: `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-008 / REQ-028, REQ-029)
|
||||
## Flow: `슬롯 RPC 응답 산출` (BAT-035 / REQ-073/074)
|
||||
|
||||
`trex.snapshot.req` 메시지 처리.
|
||||
`trex.slots.req` 메시지 처리 (`make_slots_rpc_handler`).
|
||||
|
||||
1. 요청 payload 디코드: `{view ∈ {all, buyable, held}, strategy_key?, trading_date?}`.
|
||||
2. `slot_service.compute_slot_capacity(max_active_slots)` → `available_slots = available_count`(= M 설정 시 `max(0, M − active)`, 미설정 시 inactive 수; REQ-059).
|
||||
3. `build_snapshot_envelope(store, pause_store, slot_repo, view, strategy_key, available_slots, trading_date)`:
|
||||
- base 집합: ALL = `universe ∪ holding`, BUYABLE = `universe − (pending ∪ holding)`(`available_slots=0` 이면 `[]`), HELD = `holding 슬롯만`.
|
||||
- 마스킹: pending(slot) OR warning(REQ-031) OR pause(REQ-032) — 어느 하나라도면 제외. slot overlay > portfolio-hub 보유 여부(불변식 A).
|
||||
- `strategy_key` 일치(`==`) 직교 필터.
|
||||
- degraded 상태가 있으면 `degraded=true + reason + sanitized message` 동봉.
|
||||
4. envelope: `{assets[], view, available_slots, degraded, reason, message}` — `assets` 는 항상 배열, null 금지. 항목 필드는 `symbol/asset_id/cash_balance/asset_quantity/active_strategy_id/strategy_params`, **price 필드는 없음**.
|
||||
5. 추정 현금 오버레이(dcy-018/REQ-029, OQ-4): 미정산(pending) ledger outbox 행이 있는 자산은 `cash_balance += pending cash delta`(매수 −, 매도 +) + `cash_estimated:true` 라벨로 노출 → option D write-back 도착 시 권위값으로 수렴(pending 제거). best-effort(실패 시 응답 불변). 수량은 슬롯 오버레이(REQ-029)가 정확값이라 추정 불요.
|
||||
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-snapshot-rpc` 가 `trex.snapshot.req` 를 처리할 때 응답에 포함된 모든 `holding` 슬롯에 대해 `WatchdogStore.update(slot_id)`(`last_seen_at = now`)를 호출한다. 전략 모듈이 스냅샷을 폴링하는 한 그 슬롯은 "활성"으로 판정된다.
|
||||
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 가 자동 재시작된다(부팅 직후 오탐 방지).
|
||||
@@ -320,4 +325,6 @@ timeout 미지정·취소 실패로 잔여 고착된 pending 슬롯을 처분:
|
||||
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 전이.
|
||||
|
||||
Reference in New Issue
Block a user