7.0 KiB
name, description
| name | description |
|---|---|
| run-trex-core-daemon | Launch the trex-core daemon against the real PostgreSQL + RabbitMQ infrastructure and drive it via the messaging surfaces (snapshot RPC, intent admission RPC, slot events). Use when asked to run trex-core, smoke-test the daemon, confirm a change works end-to-end, or reproduce the BAT-007 transport lifecycle. |
Run trex-core (sync + thread daemon over RabbitMQ)
The daemon (BAT-007) has no HTTP surface. All strategy-facing traffic goes
over RabbitMQ — snapshot RPC on trex.snapshot.req, intent admission RPC on
trex.intent.req, slot lifecycle events on the trex.events topic exchange.
"Run" means: boot the daemon in the background, drive it with messages, look
at the responses + the events.
Prerequisites
The daemon needs three live services. Verify before launching:
.venv/bin/python - <<'PY'
import os, psycopg, pika
from dotenv import load_dotenv
load_dotenv(".env")
psycopg.connect(os.environ["TREX_POSTGRESQL_DSN"], connect_timeout=3).close()
print("postgres OK")
p = pika.URLParameters(os.environ["TREX_RABBITMQ_URL"]); p.socket_timeout = 3
pika.BlockingConnection(p).close()
print("rabbitmq OK")
PY
If either fails, fix the infra first — the daemon will not boot without
TREX_POSTGRESQL_DSN and will boot silently degraded if TREX_RABBITMQ_URL
is missing (stub InMemoryTransport — strategy modules can't reach it).
Required env (.env, gitignored)
| Variable | Required | Notes |
|---|---|---|
TREX_POSTGRESQL_DSN |
yes | psycopg DSN |
TREX_RABBITMQ_URL |
yes for real use | e.g. amqp://admin:admin@localhost:5672/ |
TREX_BROKER |
no | paper (default) or kis |
TREX_TOTAL_SLOTS |
no | default 10 |
TREX_EXECUTION_INTERVAL_SEC |
no | default 1.0 |
TREX_LEDGER_INTERVAL_SEC |
no | default 1.0 |
TREX_PORTFOLIO_HUB_BASE_URL |
no | if set, real portfolio-hub ledger; else in-memory stub |
The daemon preallocates slots for UTC today (datetime.now(tz=timezone.utc).date()).
KST users near midnight: check date -u before driving the daemon — the
trading_date you query must match what was preallocated.
Launch
Background, redirect log:
.venv/bin/trex-core-daemon &> /tmp/trex-core.log &
DAEMON_PID=$!
The daemon does not bind any TCP port; "ready" means four worker threads running and all RabbitMQ topology declared. Poll for the topology to appear:
for i in {1..15}; do
.venv/bin/python - <<'PY' && break
import pika, sys
p = pika.URLParameters("amqp://admin:admin@localhost:5672/"); p.socket_timeout = 2
c = pika.BlockingConnection(p)
try:
ch = c.channel()
# passive=True: succeeds only if the daemon already declared it
ch.queue_declare("trex.intent.req", durable=True, passive=True)
ch.queue_declare("trex.snapshot.req", durable=True, passive=True)
ch.exchange_declare("trex.events", exchange_type="topic", durable=True, passive=True)
print("daemon ready")
sys.exit(0)
except Exception as exc:
print(f"not ready: {exc}"); sys.exit(1)
finally:
c.close()
PY
sleep 0.5
done
/tmp/trex-core.log has the pika INFO chatter — first connection lines mean
boot succeeded; absence of ERROR/exception traceback means topology declare
worked.
Drive
The canonical end-to-end smoke is in smoke.py — copy/adapt it. It exercises:
- snapshot RPC (view=all) — confirms the daemon answers.
- buy intent RPC with a fresh
correlation_id— admitted, slot →pending_buy. - duplicate buy with the same
correlation_id— identical response, no double transition (intent_log dedup gate, REQ-023 AC-1). - wait one execution loop tick — paper broker fills, slot →
holding,slot_filled_buy.{strategy_id}.{symbol}event published. - sell intent RPC on the holding slot — slot →
closed,slot_filled_sell.{strategy_id}.{symbol}event published.
Run it:
.venv/bin/python .claude/skills/run-trex-core-daemon/smoke.py
Sample output (paper broker, strategy_id=STRAT-A):
=== 1) snapshot RPC ===
slots=10 capacity={idle=10, holding=0, pending=0}
=== 2) buy intent ===
admitted, slot_id=b2d60fa3-…
=== 3) duplicate buy ===
identical: True
=== 4) wait for fill ===
slot status=holding qty=10 avg_price=60000.0
=== 5) sell intent ===
admitted; event rk=slot_filled_sell.STRAT-A.005930 status=closed qty=10
DONE
Subscribing to events (timing caveat)
If you subscribe to trex.events after the publish happens, the message
is gone — topic exchanges drop unroutable publishes. Production strategy
modules have their per-strategy queue bound from boot, so they don't miss
anything; ad-hoc subscribers need to declare + bind their queue before
triggering the intent. smoke.py does this correctly (declares queue, then
sleeps 0.6s, then runs the RPC).
Stop
kill -TERM $DAEMON_PID
# or if you've lost the PID:
pkill -f trex-core-daemon
The daemon installs SIGINT/SIGTERM handlers that set the stop event,
join all worker threads, and close() every RabbitMQ connection — last
log line should be Normal shutdown from pika. Exit code 0.
Inspecting state
The daemon's source of truth is PostgreSQL daily_slot:
.venv/bin/python - <<'PY'
import os, psycopg
from dotenv import load_dotenv
load_dotenv(".env")
with psycopg.connect(os.environ["TREX_POSTGRESQL_DSN"]) as c:
with c.cursor() as cur:
cur.execute("""
SELECT trading_date, status, COUNT(*)
FROM daily_slot GROUP BY trading_date, status ORDER BY trading_date DESC, status
""")
for r in cur.fetchall(): print(r)
PY
Ledger outbox state (BAT-006):
.venv/bin/python - <<'PY'
import os, psycopg
from dotenv import load_dotenv
load_dotenv(".env")
with psycopg.connect(os.environ["TREX_POSTGRESQL_DSN"]) as c:
with c.cursor() as cur:
cur.execute("SELECT status, last_outcome, COUNT(*) FROM ledger_outbox GROUP BY 1,2 ORDER BY 1,2")
for r in cur.fetchall(): print(r)
PY
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
slots returned: 0 from snapshot |
querying wrong trading_date |
use UTC today, see date -u |
intent gets rejected: no_idle_slot |
no preallocated slots for that day OR all 10 in flight | check daily_slot rows; restart daemon to repreallocate next UTC day |
RpcTimeoutError on the client |
daemon not up, or consumer thread crashed | check /tmp/trex-core.log for exception trace |
TransportUnavailableError |
RabbitMQ down | the daemon does NOT buffer locally (REQ-024) — fix the broker, then re-issue |
| fill event missed by ad-hoc subscriber | queue bound after publish | bind queue + sleep ≥0.5s before triggering the intent |
import-time ModuleNotFoundError: pika |
dev deps not installed | .venv/bin/pip install -e ".[dev]" |
Related docs
docs/interface-contract.md— full RabbitMQ topology + message payloadsdocs/runtime-flows.md— flow diagrams (snapshot RPC, intent RPC, events, degrade branch)docs/batches/bat-007_20260605_strategy-module-rabbitmq-transport/— the batch that introduced this runtime shape