Skip to content

QT desk: local simulator (sim/) - #136

Merged
dominickdupuy merged 3 commits into
mainfrom
qt/desk-sim
Oct 14, 2026
Merged

dominickdupuy merged 3 commits into
mainfrom
qt/desk-sim

Conversation

@dominickdupuy

Copy link
Copy Markdown
Member

A local simulator for the QT desk: the real AlgoLens, the real engine (engine-rpc and the C++ binary it spawns) and Postgres in Docker, on one clock you can move. It answers "what happens at 09:29, 09:31, 10:01 New York" without waiting. Everything is under sim/; sim/README.md is the guide.

What is in it

  • sim/docker-compose.sim.yml: postgres:16, Mailpit, engine-rpc (ENGINE_IMAGE, default trade-ngin:rpc-0b9dbf6e), the AlgoLens backend and frontend built from the checked-out branch, and an edge on http://localhost:8080 that mounts deployment/edge-routes.conf.
  • The clock. libfaketime is preloaded in postgres, engine-rpc and the backend. All three read one offset in seconds from sim/clock/faketime.rc. An offset is used instead of @YYYY-MM-DD HH:MM:SS, because @ restarts the clock in every new process, including each engine run the desk service spawns.
  • sim/simctl (+ simctl.ps1): build, up, down, load <snapshot|synthetic>, reset (from a template DB), save, clock HH:MM / +5m, day, model-run, pass (one scheduler pass), status, mail, logs, psql.
  • sim/snapshot.sh: a read-only production snapshot over ssh (pg_dump and \copy ... TO STDOUT, streamed back; nothing is written on the server). It is never run by tooling; Dom runs it.
  • sim/seed/users.sql: qt@, vp@, president@sim.local, password sim, screens member+qt.
  • sim/seed/synthetic.sql: trade-ngin's QT migrations (021-029, read from the engine image) plus one seeded QT day, for checking the stack without a snapshot.
  • The banner (the only app change). With SIM_MODE=1 the backend registers GET /api/sim, and App.tsx renders a fixed "SIMULATION — clock ... New York" banner. Without that flag the route is absent (404), so the banner never shows. Covered by tests/test_sim_mode.py and src/domain/sim/simClock.test.ts. It does not touch components/qt/* or application/qt/*.

Verified locally (synthetic fixture)

  • clock 09:29: date in postgres, engine-rpc and backend, and clock_timestamp() in Postgres, all read 2026-10-15 13:29:42 UTC, within 0.25 s of each other. Real time was 2026-10-14 20:30.
  • Logged in as qt@sim.local with POST /auth/login through the edge, then GET /portfolio/desk/QT_CONSERVATIVE_PORTFOLIO (cookie + X-CSRF-TOKEN). serverTime was 2026-10-15T13:29:02Z and approvalClosed false. At 09:31 it was still false. At 10:01 it was true, and POST .../publish returned 409 "Approval of the 2026-10-15 book closed at 10:00 New York".
  • The desk risk check (gRPC to engine-rpc) answered, and the engine-rpc scheduler ran its passes on sim time. At 10:01 it started the C++ fallback, whose log lines are stamped 2026-10-15 10:01:59. On synthetic data that run fails, as expected, and the president's alert landed in Mailpit (STARTTLS + login). An smtplib test mail from the engine container also arrived.
  • Backend: 374 passed. Frontend: vitest 155 passed, and the build passes.

Not verified: real engine runs (model run, approval, send, fallback) need the production snapshot.

🤖 Generated with Claude Code

https://claude.ai/code/session_01VCt4NHq2wcnzSiVtdabnEJ

The real AlgoLens, the real engine (engine-rpc) and Postgres in Docker, on
one clock that sim/simctl moves. libfaketime is preloaded in postgres,
engine-rpc and the backend; all three read one offset from
sim/clock/faketime.rc, so 09:29, 09:31 and 10:01 New York can be tested
without waiting. Mailpit catches every e-mail.

- sim/docker-compose.sim.yml, thin Dockerfiles adding libfaketime, an edge
  on :8080 with the production routes
- sim/simctl (+ simctl.ps1): build, up, load, reset, save, clock, day,
  model-run, pass, status, mail, logs, psql
- sim/snapshot.sh: read-only production snapshot over ssh (run by hand)
- sim/seed: the sim users, and a synthetic fixture from trade-ngin's QT
  migrations for checking the stack without a snapshot
- SIM_MODE=1 only: GET /api/sim and an App-level "SIMULATION" banner

Claude-Session: https://claude.ai/code/session_01VCt4NHq2wcnzSiVtdabnEJ
…seed fixes

- snapshot: the AlgoLens app role from the host's roles file; only the schemas and tables that
  login can read (pg_dump locks every table); the market data copied with \copy (SELECT *)
  as the engine's login, since TimescaleDB hypertables dump no rows from their parent; the
  definition of tables the first login cannot read (ohlcv_1d_raw).
- load: restores schema-extra and every <schema>.<table>.csv.gz.
- users seed: prod's auth.users has no unique email; replace the three sim users.
- drop_from_day: tables without a date column use their timestamp in New York.
@dominickdupuy
dominickdupuy marked this pull request as ready for review October 14, 2026 23:41
@dominickdupuy
dominickdupuy merged commit e96ec5a into main Oct 14, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant