Simulator (dry‑run execution)
Simulator (dry‑run execution)
What it is
The Simulator is a dry‑run execution engine that listens to live opportunity signals, runs the same safety checks you would use for real trading, and records “virtual” trades in the database. It never places live orders, but it can call real exchange APIs for validation (orderbook, market status, balance checks) to mimic live behavior as closely as possible.
The Simulator is designed for:
- validating end‑to‑end execution logic
- running “paper trading” loops for hours/days
- tracking profitability and timing assumptions before going live
High‑level flow
- Opportunity signals are produced by the ingest loop (e.g., Time Alignment Approx).
- Each signal emits an execution intent when net edge ≥ the user’s threshold.
- The Simulator consumes each intent and applies safety checks.
- If all checks pass, it records a simulated trade with fake execution + settlement timing.
- Trades, balance changes, and P&L are persisted for review.
What drives it
Inputs
- Execution intents generated from
app/data/ingest.py - User profile thresholds from
user_profiles(min edge, max leg, daily cap) - Exchange secrets from
user_secrets(encrypted)
Primary logic
- Simulator core:
app/orders/simulator.py - Orchestration and persistence:
app/orders/engine.py - Exchange validation:
app/orders/validators.py - API endpoints:
app/api/http.py
Safety checks (simulated)
For every intent, the following checks run:
- Has at least one order
- Net edge ≥ user minimum
- Instrument IDs present (Polymarket token ID + Kalshi market ticker)
- Exchange keys present
- Exchange validation via live orderbooks (dry‑run)
- Sufficient balance vs. starting fake USD
- Daily cap not exceeded
Exchange validation (dry‑run)
The simulator can call real exchange APIs for validation only:
- Kalshi: uses
kalshi_python_syncclient; validates market status and balance - Polymarket: uses
py-clob-client; validates orderbook, size, and balance
If those checks fail, the trade is skipped and logged with details.
Fake execution + settlement
When a simulated trade passes validation:
executed_atis set to “now”settled_atis set to now + a small delay- Partial fills are simulated for marginal edges
These timestamps are recorded so the system can be used to analyze timing and queue behavior.
Simulated trade states
UI trade status is derived from timestamps:
queued: current time is beforeexecuted_atexecuted: afterexecuted_atbut beforesettled_atsettled: aftersettled_at
Trades also track:
requested_size,matched_size,fill_pctsettlement_delay_s
Persistence
Two tables capture all simulator data:
simulator_runs
- One row per simulator session
- Stores thresholds, balances, and status
simulator_trades
- One row per simulated trade
- Stores checks, validation details, and timing data
Migrations:
migrations/014_create_simulator_tables.sqlmigrations/015_add_simulator_trade_fields.sqlmigrations/016_add_simulator_run_labels.sqlmigrations/017_add_simulator_run_filters.sqlmigrations/018_add_simulator_trade_decisions.sql
API endpoints
All endpoints require an authenticated user.
-
POST /simulator/start- Start a simulator run
- Payload:
{ starting_usd, opportunity_key, opportunity_keys, duration_minutes, label, notes }
-
POST /simulator/stop- Stop the current run (or pass
run_idto stop a specific simulator)
- Stop the current run (or pass
-
GET /simulator/status- Current simulator status + recent trades
-
GET /simulator/runs- History of simulator runs
-
GET /simulator/runs/{run_id}/trades- Trades for a specific run
-
GET /simulator/trades/{trade_id}- Full details for one simulated trade
-
GET /simulator/active- Active simulators for the current user
-
GET /simulator/report?hours=24&max_gap_s=120&min_edge_cents=3- 24h comparison of simulator trades vs. Success spans
-
Socket events:
simulator:tradeemits when a simulated trade is recorded
-
GET /execution/intents/{intent_id}- Raw intent + any linked trade
Required configuration
User profile
Set via Profile UI (or DB):
min_net_edge_centsmax_leg_usddaily_cap_usd
Exchange secrets
Saved per user (encrypted at rest):
Kalshi
api_key(required)extra.private_keyorextra.private_key_path(required)- Optional:
extra.use_demo,extra.host
Polymarket
extra.private_key(required)- Optional:
extra.funder,extra.host
UI
Simulator controls live on the /simulator page:
- Start / Stop controls
- Current status (balance, pnl, trade count)
- Trade list with detail modal
- Recent run history
Multiple simulators + duration
- You can run up to 5 simulators per user simultaneously.
- Each simulator can target a single opportunity key or a list of keys.
- If
duration_minutesis0or omitted, the simulator runs until stopped, with a hard 7‑day max.
Success vs execution intent decisions
- Every execution intent logs a simulator decision (
decision_source = intent). - Every success span start logs a simulator decision (
decision_source = success_span), even if no trade is taken. This shows which opportunities were considered and why they were skipped or eligible.
Labeling runs
You can attach a short label and optional notes when starting a simulator run. These are stored on the run and shown in the run history list.
What it is not (yet)
- No live orders
- No execution re‑tries
- No smart routing
- No automated liquidity management
Those will be layered in after we confirm safe, predictable behavior under dry‑run conditions.