Skip to main content

Simulation API

The /v1/simulation surface is a live, key-gated historical fill and backtest contract on production (https://api.polysimulator.com, since 2026-09-05). Without a key it returns 401, not 404 — the endpoints exist and are serving. Each API host has an operator switch for this surface (FEATURE_SIMULATION_API_ENABLED, off unless the host sets it; production sets it on). Where it is off, every /v1/simulation path answers 404 with {"error": "NOT_FOUND"}, with or without a key. The staging host (https://staging-api.polysimulator.com) runs pre-release code; it has the switch on and the same archive as production since 2026-10-22. A 404 NOT_FOUND on every simulation call usually means POLYSIM_BASE_URL points at another host.
Live does not mean unlimited. Access is gated on your plan’s analytics.backtesting entitlement and metered in simulated market-hours, so a call can still be refused on entitlement or quota rather than on auth. The volume caps below are plan-matrix numbers. This page does not claim a Telonex or vendor-validated SKU.

First successful call

simulateFill is the hook: one condition, one timestamp, one memorable VWAP. Do not start with a backtest.
All money and share fields are decimal strings. Parse with Decimal, never float. Measured on staging (2026-10-22): vwap_fill_price "0.4500", 10 shares, fee "0.12375". A condition_id or hour that is not in the archive does not return an error from /fill: it returns 200 with vwap_fill_price: null, filled_shares: "0.0000" and price_source: "missing". Check price_source before using the price.

Markets you can backtest

The archive is a curated sample, not the whole venue: hourly order-book snapshots of about 960 markets, between 2026-09-05 and 2026-09-08. There is no listing endpoint yet. Most markets in it hold a single archived hour (crypto up/down markets live for one hour), and a single-hour window cannot fill at the default latency_ms (see below). These markets hold several consecutive archived hours and were checked with GET /v1/simulation/coverage (100%, universe_eligible: true) on 2026-10-22: strategy.outcome must be one of the market’s own labels: "Yes" only fits the Yes / No rows; for the others name a team or player. A backtest’s charge is the window’s hour marks times the number of markets (14:00 to 21:00 is 8).

Latency (latency_ms)

POST /v1/simulation/backtests takes latency_ms, default 104: our measured production API p50, in milliseconds. An order decided at a tick is priced on the book as it stood latency_ms later, which is how a real order would have reached the venue. Where the archive cannot reconstruct that instant, the order fills on the first archived book at a later tick in the window; with neither, it does not fill and the summary counts it under execution.orders_refused as NO_BOOK_AFTER_LATENCY. The archive is hourly, so in practice every order fills on the next hour’s book, and a window with a single archived hour never fills at the default. "latency_ms": 0 prices each order on the book its own decision saw; the summary then reports execution.signals_used_pre_decision_data_only: false, because no real order could have traded at that price. Results at 0 look better than a live bot would do. POST /v1/simulation/fill also takes latency_ms and prices the book at at + latency_ms, but there it defaults to 0 (echoed in leakage.latency_ms). Pass 104 to price a fill the way a backtest does. The latency_ms model ships with the 2.0 engine that staging runs. Until 2.0 reaches https://api.polysimulator.com, production prices backtest orders on their decision tick’s own book.

Paths

GET /v1/simulation/book resolves a bounded hour record from Redis or an adjacent v4 sidecar. It never walks an unbound hours list.

Token identity

Fill and book responses echo the resolved token_id and comp_token_id. Backtest create accepts an optional token_ids map (condition_id → token_id). The job stores the resolved map so clients can verify the exact books that were walked. Exact asset_id matching is preserved: primary / yes / no never match a real token.

Errors

Every simulation error is a structured /v1 envelope. Branch on X-Polysim-Code, not the body text. Declared codes include:
  • INVALID_REQUEST, INVALID_WINDOW, INVALID_CURSOR
  • UPGRADE_REQUIRED, INSUFFICIENT_PERMISSION, NOT_FOUND
  • IDEMPOTENCY_KEY_REUSE, IDEMPOTENCY_CONFLICT_PENDING
  • UNIVERSE_EXCLUDED
  • BACKTEST_MONTHLY_CAP, BACKTEST_MARKET_CAP, BACKTEST_WINDOW_CAP
  • SIMULATION_HOURS_EXCEEDED
  • METERING_UNAVAILABLE, JOB_STORE_UNAVAILABLE (503)
  • CONDITION_HOUR_NOT_COVERED, ARCHIVE_UNAVAILABLE, ARCHIVE_NOT_FOUND
OpenAPI advertises 503 plus those codes. A missing sidecar next to a v4 capture object is fail-closed (quarantined), not a fabricated valid hour.

Volume caps (matrix, not activation)

FEATURE_MATRIX gates volume, not the fill/coverage features: analytics.backtesting remains false for free/pro. depth_walk_depletion and signed exports still require dataset.historical_full. Do not treat these numbers as a live commercial SKU. A tier’s monthly budget always exceeds one maximum-size backtest (max_days x 24 x max_markets) by at least 3x — otherwise the plan would advertise a run it then refuses to meter. backend/tests/test_backtest_ budget_invariant.py asserts that for every tier. unlimited is a real absence of a limit, not a large number.

SDK

wait() polls get_backtest until completed / failed / cancelled. It does not enable the flag and does not talk to Telonex.