Simulations

Run agent-based market simulations and retrieve results.

HTTP endpoints on this page

  • GET/simulation/cached
  • POST/simulation/run
  • GET/simulation/jobs
  • GET/simulation/jobs/{job_id}/status
  • GET/simulation/jobs/{job_id}/results
  • GET/simulation/jobs/{job_id}/logs
  • GET/simulation/results/{sim_id}/data/{filename}

Parameters and response shapes for each are in the API reference.

A simulation replays one calibrated trading day of one instrument through the agent-based model (ABM), as several independent Monte Carlo runs. Use it to see how the book behaves under a market event, or how an execution algorithm would have fared against it. Everything lives on client.simulation; what you can do depends on your tier.

Free tier

  1. List cached simulations with list_cached()
  2. Download data with get_sim_data(sim_id)

Pro tier

  1. Submit a job with run() — returns a job_id
  2. Track progress with get_job_status(job_id)
  3. Find past jobs with get_jobs()
  4. Retrieve results with get_sim_data()

"Pro" means a pro or demo key. Foundation-model runs use the same run() with engine="fm" — see Foundation models.

Methods — full reference in the API reference

Cached simulations (free tier)

A fixed library of pre-run baseline simulations across a selection of HKEX and LSE symbols, calibration dates and scenarios, with up to 10 Monte Carlo runs per group. Only baselines are cached — nothing with an execution algorithm attached. Each group carries an example_sim_id you can pass straight to get_sim_data().

python
cached = client.simulation.list_cached(symbol="700.HK")
print(f"Found {cached['total']} simulation groups")

sim = cached["simulations"][0]
print(sim["symbol"], sim["date"], sim["scenario"], sim["n_runs"], "runs")

df = client.simulation.get_sim_data(sim["example_sim_id"])
print(df.shape)

Download limits

Free-tier downloads are metered per simulation group — all the Monte Carlo runs of one symbol/date/scenario combination. You can add up to 3 new groups per rolling 24-hour window by default, counted the same way on every download path: single-file downloads (get_sim_data), bulk ZIPs (get_bulk_data), and the web explorer. Not per call, per run, or per file format.

Once you have downloaded a group it is yours permanently: re-fetching it later — more runs, a different file format, a different endpoint — never consumes quota again. Your own job runs are never charged, and pro and demo tiers are unlimited. A request that would exceed the allowance returns HTTP 429 without charging anything. Check where you stand with client.profile.downloads(). If you need more than the default, email support@simudyne.com — we can raise your daily limit without a Pro upgrade.

Submitting a simulation (Pro)

An instrument is a compound key of four fields — symbol, cal_date, provider and exchange — and all four are required. A combination absent from calibrated_data() is rejected rather than calibrated on demand.

run() returns as soon as the job is queued, with a job_id for the whole submission and one sim_id per run. n_runs defaults to 5 on the ABM. The master seed (default 42) derives every per-run seed, so the same seed, instrument and date always reproduce the same paths. Each field belongs to one engine: passing an FM field such as model_id to an ABM run (or scenario to an FM run) is a 422, not silently ignored.

python
result = client.simulation.run(
    symbol="700.HK",
    cal_date="2025-09-02",
    provider="omd",
    exchange="hkex_securities",
    n_runs=2,
)

job_id = result["job_id"]
print(f"Submitted job {job_id} with {len(result['sim_ids'])} simulations")

Market scenarios

Inject a market event on top of the calibrated background agents to stress-test strategies. Set scenario to one of:

ScenarioDescription
normalNo injection — background agents only (default)
flash_crashLarge rapid SELL depleting bid-side liquidity
buy_panicLarge rapid BUY depleting ask-side liquidity
gradual_selloffSlow sustained SELL over extended period
trending_upSmall steady BUY producing persistent uptrend
trending_downSmall steady SELL producing persistent downtrend

Tune a scenario with scenario_params. There is no side: each scenario has its own direction, so pick the scenario for the direction. Any key you omit keeps its default; any other key, or an out-of-range value, is a 422 — even with scenario="normal", where the params otherwise have no effect.

KeyMeaning
impact_multiplierTotal scenario volume as a multiple of average resting liquidity (bid + ask). > 0.
order_size_ratioEach child order as a fraction of average resting liquidity. In (0, 1].
order_freqSpacing between child orders: number + ms, s, min (or m) or h, e.g. "500ms", "1min".
start_timeWall-clock time to begin injection, "HH:MM" or "HH:MM:SS".

Defaults, which mirror the engine's scenario builders. get_scenario_defaults(scenario) reads them from the installed SDK, so prefer it if the two ever disagree.

Scenarioimpact_multiplierorder_size_ratioorder_freqstart_time
flash_crash150.00.02100ms10:30:00
buy_panic150.00.02100ms10:30:00
gradual_selloff10.00.055s10:30:00
trending_up5.00.0330s10:30:00
trending_down5.00.0330s10:30:00

Execution algorithms

Pass an exec_algos list to run() to trade a strategy against the simulated book. Several configs compare strategies against one shared baseline; sim_ids then come back as baseline[0:n_runs], then strategy_0[n_runs:2*n_runs], and so on.

TypeWhat it doesRequired keys
twapTime-weighted: a constant rate across the windoworder_size, horizon
vwapVolume-weighted: same config as TWAP, schedule follows the calibrated volume profileorder_size, horizon
cssCustom static schedule you supply as a pd.Series indexed by full datetimesorders
KeyMeaning
order_sizeTotal size in LOTS, not shares (× lot_size from calibrated_data() for shares: 50 lots of 700.HK = 5,000 shares). Positive sells, negative buys, unless side is given.
horizonExecution window in seconds, e.g. 7200 for two hours.
orderscss only. Sizes indexed by datetime; orders outside HKEX continuous hours (09:30–12:00, 13:00–16:00) are dropped.
start_time"HH:MM:SS" to begin. Defaults to market open.
frequencySeconds between child orders (default 1).
side"buy" or "sell". Inferred from the sign of order_size (or of the orders values) when omitted.
random_offsettwap/vwap: bool, randomise each order within its interval (default True). css: float seconds of jitter (default None, off).

Execution runs write extra output files (exec_schedule, schedule_by_min, exec_results) — see Download data.

How a job progresses

Each simulation in a job moves through queued → running → complete, or fails. get_job_status() reports every run plus two server-derived flags: is_complete (every run finished) and has_errors (any run failed). Prefer those flags to reading statuses yourself: success is reported as complete (older jobs: completed) and failure as failed or error, so test membership, e.g. status in ("complete", "completed").

A failed run carries its own error_message; for more, get_job_logs() returns the worker's engine log (written once the job reaches a worker). get_jobs() recovers job IDs from earlier sessions, and get_job_results() returns params, metrics and the available files for every run without downloading any data.

python
# the most recent job of yours
job_id = client.simulation.get_jobs(limit=1)["jobs"][0]["job_id"]

status = client.simulation.get_job_status(job_id)
print(f"Total: {status['total_simulations']}")
print(f"Status: {status['status_summary']}")
print(f"Complete: {status['is_complete']}")
Output
Total: 2
Status: {'complete': 1, 'running': 1}
Complete: False

Understanding simulation IDs

Each Monte Carlo run has a unique sim_id with the format:

text
{provider}:{exchange}:{symbol}:{cal_date}:{gen_method}:{cal_hash}:{time}:{sim_hash}:{scenario}_{hash}:{algo}:{run}

For example: omd:hkex_securities:9999.HK:2025-09-02:ABM_v1.37.0:d96cf520:0930-1600:c8961b94:flash_crash_6e6d5b3a:baseline:0000

The sim_id encodes all configuration parameters (including gen_method, the engine version that produced the run), making it deterministic and reproducible. The final :{run} field is the zero-padded Monte Carlo run index — take an example_sim_id and swap this last field to address each run (…:0000, …:0001, …). Don't hand-build sim_ids field-by-field; always start from an example_sim_idreturned by list_cached() or run().

Recipes