#!/usr/bin/env python3 """End-to-end smoke for trex-core (BAT-030 asset-centric). Assumes the daemon is already running with `--config ` (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())