Files
trex-core/.claude/skills/run-trex-core-daemon/smoke.py
T
genichinandClaude Opus 4.8 7d727824dc feat(slot): BAT-030 — 자산 중심 cutover 완성 (dcy-020 BAT-δ, REQ-056·057·062·064·065)
M1 literal inactive 전면화: SlotState 5-state(idle/closed 별칭 제거),
마이그레이션 010(CHECK 교체+행 rewrite), ops_checks 를 transition_log 기반 재작성.
M2 집행 매도 수량 브로커 정본(D3): dispatch 매도 qty=broker hldg_qty,
미러 fallback 없음, 브로커 0 → skip+WARN, 리스트 기반 dispatch(head-block 방지).
M3 컬럼 drop + legacy pool 제거: 마이그레이션 011(trading_date·entry_count DROP),
repo 계약 instance 스코프 개편(집행 큐 포함), preallocation 폐기(데몬 7스레드),
admission 자산 중심 단일 경로(slot_id 정본), capacity 재정의(available=M−active),
daily_symbol_entries 코드 경로 제거.
M4 cutover 도구(D1·D4): trex-core-cutover(전체 rebuild, preflight/브로커 carry-over/
--allow-unmapped/--promote-unique/dry-run) + PG 리허설 테스트 8건.
M5 baseline 마감: data-model/runtime-flows/project-structure/interface-contract
자산 중심 최종 갱신 + run-trex-core-daemon 스킬 정합.

검증: 단위 367 passed + ruff clean; PG-gated 29 passed(010/011 round-trip·드리프트
가드·cutover 리허설); RabbitMQ 21 passed; run-the-app e2e(test DB+실 MQ, M=1):
reconcile 슬롯 생성→slot_id 매수→dedup→capacity_full→fill→already_active→
매도→inactive(자산 바인딩 보존)→영구 슬롯 재활성 전 구간 green.
실 cutover 는 운영자 실행(REL 단계, D4).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 15:16:15 +09:00

108 lines
3.9 KiB
Python

#!/usr/bin/env python3
"""End-to-end smoke for trex-core (BAT-030 asset-centric).
Assumes the daemon is already running with `--config <toml>` (see SKILL.md).
Drives every RabbitMQ surface of the asset-centric model:
1. snapshot RPC (view=all) — daemon answers; slots[] = permanent
asset slots created by the universe
reconcile (status `inactive`)
2. buy intent RPC keyed by slot_id — admission + slot → pending_buy
(REQ-061 canonical key; portfolio_id is the legacy dual-accept)
3. duplicate buy (same intent_id) — intent_log dedup gate (REQ-023 AC-1)
4. wait for execution tick + fill — slot → holding
5. sell intent RPC — slot → pending_sell → fill →
`inactive` (occupancy cleared,
asset binding kept; NOT 'closed')
Pass the instance key as argv[1] (default: "default"). Env from config/.env.
"""
from __future__ import annotations
import os
import sys
import time
import uuid
from dotenv import load_dotenv
load_dotenv("config/.env")
from trex_core.adapters.messaging import topology # noqa: E402
from trex_core.adapters.messaging.rabbitmq import RabbitMQTransport # noqa: E402
URL = os.environ.get("TREX_RABBITMQ_URL", "amqp://admin:admin@localhost:5672/")
INST = sys.argv[1] if len(sys.argv) > 1 else "default"
def main() -> int:
mq = RabbitMQTransport(URL, timeout=10.0)
snap_q = topology.snapshot_queue(INST)
intent_q = topology.intent_queue(INST)
def snap():
return mq.call_rpc(
snap_q,
{"view": "all", "actor": "operator", "touch_watchdog": False},
correlation_id=str(uuid.uuid4()), timeout=8.0,
)
def intent(payload):
cid = payload.get("intent_id") or str(uuid.uuid4())
return mq.call_rpc(intent_q, payload, correlation_id=cid, timeout=8.0)
print("=== 1) snapshot RPC ===")
s = snap()
slots = s["slots"]
print(f"assets={len(s['assets'])} slots={len(slots)} "
f"available_slots={s['available_slots']}")
inactive = [x for x in slots if x["status"] == "inactive"
and not x["universe_detached"]]
if not inactive:
print("no admittable (inactive) asset slot — universe reconcile not "
"done yet, or all assets active")
return 1
target = inactive[0]
sid = target["slot_id"]
print(f"target slot_id={sid} portfolio_id={target['portfolio_id']}")
print("=== 2) buy by slot_id ===")
iid = str(uuid.uuid4())
d = intent({"intent_type": "buy", "intent_id": iid, "slot_id": sid,
"qty": 10, "entry_price": 1000, "strategy_id": "STRAT-A"})
print(f"-> {d['status']} reason={d['reason']}")
if d["status"] != "admitted":
return 1
print("=== 3) duplicate buy (same intent_id) ===")
d2 = intent({"intent_type": "buy", "intent_id": iid, "slot_id": sid, "qty": 10})
print(f"identical: {d2 == d}")
print("=== 4) wait for fill ===")
for _ in range(20):
time.sleep(0.5)
st = next(x for x in snap()["slots"] if x["slot_id"] == sid)
if st["status"] == "holding":
break
print(f"slot status={st['status']} qty={st['qty']} avg_price={st['avg_price']}")
print("=== 5) sell intent ===")
d3 = intent({"intent_type": "sell", "intent_id": str(uuid.uuid4()),
"slot_id": sid})
print(f"-> {d3['status']}")
for _ in range(20):
time.sleep(0.5)
st = next(x for x in snap()["slots"] if x["slot_id"] == sid)
if st["status"] == "inactive":
break
print(f"slot status={st['status']} qty={st['qty']} "
f"portfolio_id={st['portfolio_id']} (asset binding kept)")
mq.close()
print("DONE")
return 0
if __name__ == "__main__":
raise SystemExit(main())