Monte Carlo API
Submit a forward wealth simulation against a succeeded optimization run, poll its lifecycle, and fetch its artifact and exports. Simulations are child runs: they inherit the parent's frozen optimizer-time inputs and never refetch market data.
Read this before publishing results
Every probability returned here is conditional on the selected model, the frozen optimizer-time inputs, the effective configuration, the engine version and the seed. It is not a calibrated forecast, and no generator has passed out-of-sample validation. See rolling-origin validation.
Any surface presenting these numbers must present the assumptions block alongside them.
/runs/{run_id}/monte_carloEnqueue a simulation on a succeeded parent run. Returns 202 with the child run id.
curl -X POST https://api.foliolab.ai/runs/$RUN_ID/monte_carlo \ -H "Authorization: Bearer $ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "method": "HRP", "horizon_years": 20, "path_count": 25000, "starting_wealth": 2500000, "goal_amount": 12000000, "goal_basis": "real_pre_tax", "contributions": { "amount": 40000, "frequency": "monthly", "escalation_pct": 0.08 }, "rebalance_frequency": "annual", "rgp": "fhs_gjr_garch_vt", "scenarios": ["correlation_spike"] }'
{
"mc_run_id": "6f2c0b6e-6b6a-4a1e-9d2f-6c9b0f6a2b41",
"parent_run_id": "1a5b8e2c-77d1-4f3a-b0c2-9e1d3f4a5b6c",
"status": "queued"
}Request body
The body is a strict schema. Unknown fields are rejected rather than ignored, so a typo cannot silently run a different configuration.
Core
| Field | Type | Description |
|---|---|---|
method | string, required | The parent run's optimization method whose weights are simulated. |
horizon_years | integer 1 to 40, default 15 | Simulation horizon. |
path_count | integer 1,000 to 100,000, default 10,000 | Paths to simulate. Capped by your plan. |
starting_wealth | number, default 1,000,000 | Initial portfolio value in rupees. |
seed | integer or decimal string, optional | Omit to draw and record a fresh seed. Results are byte-reproducible from the echoed config. |
batch_size | integer 128 to 5,000, default 2,000 | Path batch size. Part of the reproducibility contract. |
Return process
| Field | Type | Description |
|---|---|---|
rgp | enum, default fhs_gjr_garch_vt | Return-generating process. See the table below. |
expected_block_length_days | number, optional | Stationary bootstrap expected block length. Omit for automatic Politis-White selection. |
student_t_dof | number > 2, default 5 | Degrees of freedom for the student_t process. |
regime | object | Regime-bootstrap fit settings. |
fhs | object | Filtered historical simulation settings: ewma_lambda, long_run_blend_half_life_days, estimation_window_sessions (default 1,260), long_run_volatility and volatility_levels. long_run_volatility applies to fhs_gjr_garch_vt only. It is market_long_run (the default) or estimation_window. volatility_levels is both (the default, the two levels on the same paths) or single. No level is validated out of sample, and no level is a forecast. |
Planning and accounting
| Field | Type | Description |
|---|---|---|
goal_amount | number, optional | Target terminal wealth for the goal probability. |
goal_basis | enum | nominal_pre_tax, nominal_post_tax, real_pre_tax or real_post_tax. |
wealth_floor | number, optional | Solvency floor. A breach is recorded at any month or discontinuous cash-flow event, including t=0. |
wealth_floor_basis | enum, default nominal_pre_tax | Series the floor is evaluated against. |
contributions | object, optional | SIP: amount, frequency, annual escalation_pct. |
withdrawals | object, optional | SWP: amount, frequency, start_year, escalation_mode, escalation_pct. |
inflation | object | enabled and annual_rate_override. Deflates real-basis series. |
tax | object | Indian listed-equity capital gains on a monthly FIFO basis. |
slippage_bps | number 0 to 500, default 0 | Proportional cost on traded value at every modeled trade, not only rebalances. |
Policy and evidence
| Field | Type | Description |
|---|---|---|
rebalance_frequency | none, annual, quarterly, monthly | Default annual. |
rebalance_mode | static or reoptimize | Default static. |
walk_forward | object | reoptimize_frequency, estimation_window, estimation_window_years, weight_observation_grid. |
paired_static_baseline | boolean, default false | Simulate the frozen-weight arm on identical shock coordinates. Needs rebalance_mode reoptimize. |
policy_comparison | boolean, default false | Needs rebalance_mode reoptimize and paired_static_baseline true. Adds a buy-and-hold arm on the same paths, seed, frozen inputs and configuration, and a policy_comparison block with three signed gaps: the rebalancing effect, the re-optimization effect and the total policy effect. Admission counts it as a third full simulation. The gaps come from simulated paths. They are not a realized or out-of-sample test, and they are not evidence that one policy performs better. |
drift | object | mode plus anchor fields. See drift modes below. |
risk_free | object | source: parent_run (default), explicit with annual_rate, or none. Metric benchmark only. |
scenarios | array, max 9 | Named scenario arms run beside the base arm under common random numbers: 2008_repeat, covid_repeat, single_name_blowup, bear_regime_start, permanent_loss, liquidity_freeze, multi_session_price_band, correlation_spike, fast_crisis_entry. No scenario carries an event probability. The two calendar-window scenarios run only on the retired block bootstrap, so they are normally reported as skipped; bear_regime_start needs regime_bootstrap. fast_crisis_entry applies the deterministic state fhs-stress-fast-entry-v1 to the FHS processes: every path starts at 3x its conditional volatility, and the excess log variance halves every 21 sessions. The engine skips it on gbm_lognormal and the simple-return processes, and gives the reason. It is a stress severity, not a calibrated forecast. |
precision | object, optional | tier (pro_standard, enterprise_standard or enterprise_tail) and optional max_paths. Enables adaptive sequential stopping. |
parameter_uncertainty | object, optional | outer_draws and paired_fixed_control. Mutually exclusive with precision. |
Return processes
Which values are accepted also depends on your plan's mc_allowed_processes entitlement.
| Field | Type | Description |
|---|---|---|
fhs_gjr_garch_vt | Default | Filtered historical simulation with a variance-targeted GJR-GARCH volatility model. Needs about 5 years of jointly aligned history. When rgp is omitted and the panel is shorter, the run falls back to the stationary block bootstrap and the result reports generator_fallback. That bootstrap was rejected by the rolling-origin study, and its dispersion is materially wider than realized. A run that names rgp explicitly gets 422 instead. |
fhs_gjr_regime | Selectable | The same filter under a slow-moving variance-regime layer. |
fhs_gjr_regime_uv_fallback | Selectable | The regime layer, but an asset whose GJR fit fails admission falls back to its unconditional variance instead of EWMA. |
block_bootstrap | Retired | Politis-Romano stationary block bootstrap. Retired: rolling-origin testing measured simulated volatility 27% to 56% above realized at every horizon. No plan grants it. |
regime_bootstrap | Selectable | Markov regime chain over portfolio returns; blocks drawn from the active state's labelled history. A failed fit falls back to the block bootstrap, with the reason in the result's regime block. |
fhs_ewma | Selectable | Filtered historical simulation with EWMA conditional volatility. Needs about 3 years of history. |
fhs_gjr_garch | Selectable | The same filter with a GJR-GARCH volatility model. Failed fits fall back to EWMA per asset. |
gbm_lognormal | Selectable | Lognormal geometric Brownian motion with Ledoit-Wolf shrinkage covariance. |
gaussian | Comparison | Multivariate normal on simple returns. Understates Indian-market tails. |
student_t | Comparison | Multivariate Student-t on simple returns. |
Each process has a minimum history requirement and its own admission checks. The full mathematics for each one is documented under engine methodology.
Drift modes
drift.mode accepts historical (default), override, shrunk and forward_shrunk. A fifth mode, market_forward_shrunk, resolves a versioned market-level Indian equity assumption and accepts no user-supplied anchor values. No plan includes it today.
Drift modes are plan-gated by mc_allowed_drift_modes. The gate has one deliberate asymmetry: a "*" wildcard expands to every mode except the house-view modes, so the wildcard cannot switch one on by analogy. A missing or malformed key degrades to the four caller-supplied modes, which means the fail-closed direction is “no house view” rather than “no Monte Carlo”. See drift and estimation risk.
/runs/{run_id}/monte_carloEvery simulation submitted against one parent optimization run.
curl https://api.foliolab.ai/runs/$RUN_ID/monte_carlo \ -H "Authorization: Bearer $ACCESS_TOKEN"
/mcAll of the caller's simulations across every parent run, newest first, each with parent_name and parent_status. Cursor paginated.
curl "https://api.foliolab.ai/mc?limit=20&cursor=$NEXT_CURSOR" \ -H "Authorization: Bearer $ACCESS_TOKEN"
limit is 1 to 100 and defaults to 20. Pass the next_cursor from the previous response to page forward.
/mc/{mc_run_id}Lifecycle, inline summary, timing, and signed artifact and export links once the run has succeeded.
{
"mc_run_id": "6f2c0b6e-6b6a-4a1e-9d2f-6c9b0f6a2b41",
"parent_run_id": "1a5b8e2c-77d1-4f3a-b0c2-9e1d3f4a5b6c",
"status": "succeeded",
"method": "HRP",
"engine_version": "mc-v1.25.0",
"seed": "8127364512873645",
"attempt_count": 1,
"created_at": "2026-08-11T09:14:02Z",
"started_at": "2026-08-11T09:14:19Z",
"finished_at": "2026-08-11T09:17:41Z",
"error_code": null,
"error_message": null,
"heartbeat_at": "2026-08-11T09:17:40Z",
"lease_expires_at": null,
"timing": {
"queued_seconds": 17.4,
"running_seconds": 202.1,
"total_seconds": 219.5,
"attempts": 1,
"semantics": "observed_worker_execution_time_not_a_service_level_guarantee_..."
},
"summary": {
"run_information": { "...": "..." },
"metric_suite": { "...": "..." },
"model_risk": { "...": "..." },
"assumptions": { "...": "..." }
},
"config": { "...": "the echoed immutable configuration" },
"artifact": {
"signed_url": "https://...",
"bytes": 4821904,
"content_type": "application/json"
},
"exports": {
"xlsx": { "signed_url": "https://...", "bytes": 284512, "content_type": "...", "filename": "..." }
}
}Timing is two numbers, not one. A run that sat in the queue for four minutes and simulated for forty seconds is not a slow simulation. Both are observations rather than a service level, and a retried run reports the last attempt's execution, which is why the attempt count sits beside them.
Signed links are short lived and are regenerated on each detail request. Do not cache them.
Exports are best effort. A failed export never turns a completed simulation into a failed run, so the exports object may be empty on a succeeded run. It carries the workbook only. The Monte Carlo PDF is requested on the parent run with POST /jobs/{run_id}/report/pdf?kind=monte_carlo and arrives as a run artifact labelled monte_carlo_report_pdf.
Errors
| Status | Detail | Cause |
|---|---|---|
| 400 | Invalid Monte Carlo config: <reasons> | The body failed the strict schema, for example an unknown field or a value out of range. The reasons follow the colon. |
| 403 | mc_api_key_not_supported | Simulations require a user session or an MCP OAuth grant. Programmatic API keys are not accepted. |
| 403 | mc_not_allowed | The plan has no Monte Carlo access. |
| 403 | mc_path_limit_exceeded:<cap> | path_count exceeds the plan's path cap. The cap is in the detail. |
| 403 | mc_process_not_allowed:<process> | The plan does not include the requested return process. |
| 403 | mc_drift_mode_not_allowed:<mode> | The plan does not include the requested drift mode. |
| 409 | parent_not_ready | The parent optimization run has not succeeded. |
| 422 | MC_METHOD_REQUIRED | method was not pinned on the request. |
| 422 | <eligibility code>: ... | The portfolio or method cannot be simulated: for example a signed (long/short) portfolio, a non-Indian or non-equity asset scope, invalid weights, a method the parent did not produce, a workload above the complexity limit, or a reoptimize request for a method that cannot be re-fitted per path. The code leads the detail. |
| 422 | mc_insufficient_history_for_process | The chosen process needs more jointly aligned daily history than the portfolio has. |
| 429 | mc_monthly_limit_exceeded | The plan's monthly simulation meter is exhausted. |
| 429 | mc_inflight_limit_exceeded | At most two simulations may be queued or running at once. |
| 503 | MC_GOVERNANCE_UNAVAILABLE | The service could not load or apply its model-risk governance record. Retry unchanged. |
Admission and the worker apply the same workload and optimizer-attestation checks, so a row queued under an older policy cannot bypass a newer safety gate when it finally executes. A request the engine cannot honour exactly is refused rather than downgraded.
Plan limits
| Plan | Simulations / month | Paths / run | Generators |
|---|---|---|---|
| Free | None | None | None |
| Pro | 20 | 50,000 | fhs_gjr_garch_vt, regime_bootstrap, fhs_ewma, gbm_lognormal, gaussian, student_t |
| Enterprise | Unlimited | 100,000 | All |
Monte Carlo has its own monthly meter, separate from optimizations and backtests. GET /billing/me reports all three.
Related
Backtests API for the other child-run workflow. Jobs for the parent optimization lifecycle.
Walk-forward re-optimization explains the rebalance_mode and walk_forward fields. Adaptive precision explains precision, and parameter uncertainty explains why the two cannot be combined.
Not investment advice. Past performance is not indicative of future results.