Market Simulator Service
The Market Simulator Service generates real trading activity by driving the live service pipeline over HTTP — exactly as a real client would. It provisions fleets of synthetic users (signup → KYC → deposit), then has them place randomized orders against the Order Service REST API. There is no special path: every simulated order goes through auth, risk, wallet locking, the matching engine, and downstream consumers identically to a real user, making it suitable for integration testing, load testing, and demo environments without real capital.
Not a market-data generator
This service does not publish md.* or order.command.v1 Kafka topics, and it does not run a synthetic price walk. It is an HTTP client of the other services. Market data (md.trades.v1, md.orderbook.delta.v1, OHLCV, etc.) emerges naturally downstream once the matching engine processes the orders it submits.
Responsibilities
Section titled “Responsibilities”- Account provisioning: Create synthetic accounts via the Auth Service (
Signup→/v1/api-keys), wait for the User Service to confirm the profile, and approve KYC using an admin JWT. - Funding: Deposit funds via the Wallet Service (
POST /v1/transactions/deposits) and re-deposit (top-up) when an account’s available balance drops below a configurable threshold. - Order generation: Place randomized orders via the Order Service (
POST /v1/orders) per account on a randomized interval — randomizing symbol, side (honoring a buy bias), size, order type, leverage, aggressiveness, price offset, and post-only flag. - Job orchestration: Run multiple named simulation jobs concurrently (actor-per-job, one account loop per synthetic account), each independently configurable, startable, pausable, and stoppable via the admin REST API.
- Stats & analytics: Track orders placed / filled / rejected, fill rate, total volume, active accounts, and top-ups; snapshot them periodically to Postgres and expose live + historical stats and per-account analytics.
Architecture
Section titled “Architecture”one per job] Actor --> Loop[Account loops
one per synthetic account] end Loop -->|signup + api-key| Auth[Auth Service] Loop -->|profile poll| UserSvc[User Service] Loop -->|deposit| Wallet[Wallet Service] Loop -->|POST /v1/orders| OrderSvc[Order Service] Loop -->|fees / instruments| Meta[Metadata Service] Loop -. index price WS .-> MD[Market Data Service] Sim --> PG[(Postgres
jobs / accounts / orders / stats)]
The simulator owns its own Postgres database (jobs, accounts, orders, stats — SQLC + Atlas) to track what it created, but it reaches all trading services exclusively over their public HTTP/WS APIs. It never writes to another service’s database, Kafka topics, or the matching engine directly.
Language / Framework
Section titled “Language / Framework”Go (Fiber HTTP server, actor model, SQLC + Atlas).
External Dependencies (outbound HTTP / WS)
Section titled “External Dependencies (outbound HTTP / WS)”| Client | Target | Used for |
|---|---|---|
auth_client | Auth Service | Signup, login, /v1/api-keys, api-key exchange |
user_client | User Service | Profile-creation polling |
wallet_client | Wallet Service | POST /v1/transactions/deposits, GET /v1/balance |
order_client | Order Service | POST /v1/orders, GET /v1/orders/:id, GET /v1/market/mark-price/:symbol |
metadata_client | Metadata Service | Instrument fees / definitions for analytics |
index_price_client | Market Data Service WS | Read-only index-price subscription (mid-price reference) |
Admin REST API
Section titled “Admin REST API”All routes are under /v1:
| Method | Path | Description |
|---|---|---|
GET | /sim-jobs | List jobs |
POST | /sim-jobs | Create a job |
GET | /sim-jobs/:id | Get a job |
PUT | /sim-jobs/:id | Update a job |
DELETE | /sim-jobs/:id | Delete a job (only if not running) |
POST | /sim-jobs/:id/start | Start a job |
POST | /sim-jobs/:id/pause | Pause a job |
POST | /sim-jobs/:id/stop | Stop a job |
GET | /sim-jobs/:id/stats | Live stats (actor memory, or last snapshot if stopped) |
GET | /sim-jobs/:id/stats/history | Historical stats snapshots |
GET | /sim-jobs/:id/accounts | Synthetic accounts for a job |
GET | /sim-jobs/:id/orders | Orders placed by a job |
GET | /sim-jobs/:id/analytics | Per-account trading analytics |
GET | /sim/status | Summary of all currently running jobs |
Service Configuration
Section titled “Service Configuration”Set via environment variables (defaults shown):
| Variable | Description | Default |
|---|---|---|
PORT | HTTP port | 3007 |
POSTGRES_URL | Simulator’s own database — required | — |
ADMIN_JWT_TOKEN | Admin token used for KYC approval + deposits — required | — |
AUTH_SERVICE_URL | Auth Service base URL | http://localhost:3001 |
USER_SERVICE_URL | User Service base URL | http://localhost:3002 |
WALLET_SERVICE_URL | Wallet Service base URL | http://localhost:3003 |
ORDER_SERVICE_URL | Order Service base URL | http://localhost:3004 |
METADATA_SERVICE_URL | Metadata Service base URL | http://localhost:8080 |
MARKETDATA_SERVICE_WS_URL | Market Data WS URL (index prices) | ws://localhost:8080/ws |
KYC_LEVEL / KYC_PROVIDER | KYC approval settings | L1 / hyperverge |
STATS_SNAPSHOT_INTERVAL | Stats snapshot cadence | 10s |
PROFILE_POLL_MAX_RETRIES / PROFILE_POLL_DELAY | User-profile confirmation polling | 20 / 3s |
HTTP_CLIENT_TIMEOUT | Outbound HTTP timeout | 30s |
Per-Job Configuration
Section titled “Per-Job Configuration”Supplied in the POST /v1/sim-jobs body (CreateJobRequest):
| Field | Description |
|---|---|
name | Job name |
symbols | Symbols to trade (randomly chosen per order) |
num_accounts | Number of synthetic accounts to provision |
order_interval_min_ms / order_interval_max_ms | Randomized sleep between orders per account |
deposit_amount_min / deposit_amount_max | Initial deposit range |
deposit_top_up_min_inr | Re-deposit when available balance falls below this |
order_size_min / order_size_max | Order quantity range |
order_types | Allowed order types (default ["limit","market"]) |
leverage_min / leverage_max | Leverage range (default 1–10) |
price_offset_min_pct / price_offset_max_pct | Passive-order price offset range |
buy_bias_pct | Probability an order is a BUY (default 50) |
aggressive_order_pct | Probability an order crosses the spread |
post_only_pct | Probability a limit order is post-only |
duration_seconds | Auto-stop the job after this duration |
Use Cases
Section titled “Use Cases”Integration Testing
Section titled “Integration Testing”Run a job against a local or staging stack to exercise the full signup → KYC → deposit → order → match → settle → position path with real cross-service calls — no manual order placement.
Load Testing
Section titled “Load Testing”Raise num_accounts and lower order_interval_*_ms to drive high order rates, stressing Order Service admission throughput, matching-engine processing, Kafka consumer lag tolerance, and WebSocket broadcast latency.
Demo Environments
Section titled “Demo Environments”Run a long-lived job to produce a live-looking order book, trade feed, and candlestick charts driven by genuine (synthetic) order flow.
Safety Constraints
Section titled “Safety Constraints”- ❌ Does NOT publish to Kafka or write to any other service’s database directly.
- ❌ Does NOT bypass Auth, Risk, Wallet, Order Service, or the Matching Engine — every action is an authenticated public-API call.
- ✅ Only ever touches synthetic accounts it created (tracked in its own database); never real user accounts.
- ✅ Requires an admin JWT solely for KYC approval and deposits of those synthetic accounts.
- ⚠️ Synthetic account emails are derived deterministically — run a single instance. Multiple replicas would collide on signup and double the generated load. Scale via job config (
num_accounts, interval), not pod count.