Backtests
Run a strategy version over past days and read its trades.
List backtests #
/v2/backtests scope readThe caller's backtests, newest first (at most 50; kept 30 days), without their daily equity.
Errors
| HTTP | Code | When |
|---|---|---|
| 401 | unauthorized | No credential, or the credential is invalid, expired or revoked. |
| 403 | forbidden | The credential may not use this route (missing scope, app-session-only route, IP not allowed). |
| 429 | rate_limited | Too many requests for this credential (or too many failed attempts); retry after the |
All fields (request and response schema)
Loading fields…
curl "https://api.cirrus.trade/v2/backtests" \
-H "Authorization: token $CIRRUS_API_KEY"import os
import requests
response = requests.get(
"https://api.cirrus.trade/v2/backtests",
headers={
"Authorization": f"token {os.environ['CIRRUS_API_KEY']}",
},
timeout=10,
)
print(response.status_code, response.json())const response = await fetch('https://api.cirrus.trade/v2/backtests', {
method: 'GET',
headers: {
Authorization: `token ${process.env.CIRRUS_API_KEY}`,
},
});
const body = await response.json();
console.log(response.status, body);{
"status": "success",
"data": [
{
"id": "01M66QA2YJGRWYHB9VJRAW6BRW",
"strategy_id": "01M66QA2V4VGYTC47SA6EEBXZM",
"version": 1,
"status": "cancelled",
"from": "2026-10-14",
"to": "2026-10-16",
"capital": 500000,
"costs": {
"charges": "india",
"slippage_bps": 2,
"spread_proxy_bps": 10
},
"progress": {
"done": 0,
"total": 0
},
"cancel_requested": true,
"created_at": "2026-10-30T15:37:00.627Z",
"started_at": null,
"finished_at": "2026-10-30T15:37:00.640Z",
"expires_at": "2026-11-29T15:37:00.627Z",
"error": null,
"summary": null,
"labels": null,
"sources": [],
"counts": {
"trades": 0,
"cycles": 0
}
},
{
"id": "01M66QA2X382CT2C47RR5XN0FY",
"strategy_id": "01M66QA2V4VGYTC47SA6EEBXZM",
"version": 1,
"status": "done",
"from": "2026-10-14",
"to": "2026-10-16",
"capital": 500000,
"costs": {
"charges": "india",
"slippage_bps": 2,
"spread_proxy_bps": 10
},
"progress": {
"done": 3,
"total": 3
},
"cancel_requested": false,
"created_at": "2026-10-30T15:37:00.588Z",
"started_at": "2026-10-30T15:37:00.752Z",
"finished_at": "2026-10-30T15:37:02.016Z",
"expires_at": "2026-11-29T15:37:00.588Z",
"error": null,
"summary": {
"capital": 500000,
"final_equity": 496635.79,
"net_pnl": -3364.21,
"charges": 307.96,
"cycles": 3,
"trades": 12,
"wins": 0,
"losses": 3,
"win_rate": 0,
"best_cycle": -54.99,
"worst_cycle": -3176.51,
"avg_cycle": -1121.4,
"max_drawdown": 3592.73,
"max_drawdown_pct": 0.0072,
"days": 3,
"days_traded": 3,
"metrics": {
"total_return": -0.0066,
"cagr": -0.4276,
"annualized_vol": 0.0462,
"sharpe": -12.247,
"sortino": -9.6967,
"calmar": -64.5952,
"max_drawdown": 0.0066,
"profit_factor": 0
},
"band": null
},
"labels": {
"estimated": true,
"text": "Estimated: option prices are modelled from NIFTY and India VIX, not real trades",
"notes": [
"Expiries follow the exchange's weekday rules, strikes are 50 points apart and one lot is 75 units (today's lot size).",
"Every strike uses the same volatility: India VIX × 1; real prices have a skew.",
"Fills follow the forward test's rules: protected limits on entry, crossing the spread on exit, 2 bps slippage."
]
},
"sources": [
{
"series": "underlying",
"symbol": "CT:EX:BTNIFTY50",
"source": "chart_api",
"estimated": false
},
{
"series": "vix",
"symbol": "CT:EX:BTINDIAVIX",
"source": "chart_api",
"estimated": false
},
{
"series": "options",
"symbol": "NFO:NIFTY",
"source": "estimator",
"estimated": true,
"note": "no option history yet: prices are modelled"
},
{
"series": "futures",
"symbol": "NFO:NIFTY",
"source": "estimator",
"estimated": true,
"note": "no option history yet: prices are modelled"
}
],
"counts": {
"trades": 12,
"cycles": 3
}
}
],
"error": null
}Start a backtest #
/v2/backtests scope strategiesQueues a backtest of a saved strategy version over past days.
More about this
It runs the same engine as live deployments over 1-minute history, with simulated fills by the forward test's rules; option prices are modelled from the underlying and India VIX when no option history exists, and the result says so (labels). Poll GET /v2/backtests/{backtest_id}. Limits: at most 1098 days (3 years) per run, 3 queued or running and 50 a day per user, 10 submissions a minute.
Parameters
- Idempotency-Key header · string
Optional: a new UUID per request (1-128 printable characters). A repeat with the same key gets the first answer back instead of acting again.
Body fields
- strategy_id string required
- from string required
First day (
YYYY-MM-DD, IST). - to string required
Last day, inclusive; before today.
fromtotocovers at most 1098 days (3 years), both included. - version integer (int32) | null
The version to test; the latest saved one when absent.
- capital number (double) | null
Virtual capital (₹); default 5,00,000.
- costs object | null
Costs to apply; defaults: Indian charges, 2 bps slippage.
Errors
| HTTP | Code | When |
|---|---|---|
| 401 | unauthorized | No credential, or the credential is invalid, expired or revoked. |
| 403 | forbidden | The credential may not use this route (missing scope, app-session-only route, IP not allowed). |
| 404 | version_not_found | No such version of the strategy. |
| 409 | backtest_limit_reached | The most backtests that may be queued or running at once, or started today, are reached. |
| 422 | invalid_request | The request is well-formed but not valid; |
| 429 | rate_limited | Too many requests for this credential (or too many failed attempts); retry after the |
All fields (request and response schema)
Loading fields…
curl -X POST "https://api.cirrus.trade/v2/backtests" \
-H "Authorization: token $CIRRUS_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"strategy_id": "01M66QA2V4VGYTC47SA6EEBXZM",
"version": 1,
"from": "2026-10-14",
"to": "2026-10-16",
"capital": 500000,
"costs": {
"charges": "india",
"slippage_bps": 2
}
}'import os
import requests
response = requests.post(
"https://api.cirrus.trade/v2/backtests",
headers={
"Authorization": f"token {os.environ['CIRRUS_API_KEY']}",
},
json={
"strategy_id": "01M66QA2V4VGYTC47SA6EEBXZM",
"version": 1,
"from": "2026-10-14",
"to": "2026-10-16",
"capital": 500000,
"costs": {
"charges": "india",
"slippage_bps": 2,
},
},
timeout=10,
)
print(response.status_code, response.json())const response = await fetch('https://api.cirrus.trade/v2/backtests', {
method: 'POST',
headers: {
Authorization: `token ${process.env.CIRRUS_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"strategy_id": "01M66QA2V4VGYTC47SA6EEBXZM",
"version": 1,
"from": "2026-10-14",
"to": "2026-10-16",
"capital": 500000,
"costs": {
"charges": "india",
"slippage_bps": 2
}
}),
});
const body = await response.json();
console.log(response.status, body);{
"status": "success",
"data": {
"id": "01M66QA2X382CT2C47RR5XN0FY",
"strategy_id": "01M66QA2V4VGYTC47SA6EEBXZM",
"version": 1,
"status": "queued",
"from": "2026-10-14",
"to": "2026-10-16",
"capital": 500000,
"costs": {
"charges": "india",
"slippage_bps": 2,
"spread_proxy_bps": 10
},
"progress": {
"done": 0,
"total": 0
},
"cancel_requested": false,
"created_at": "2026-10-30T15:37:00.588Z",
"started_at": null,
"finished_at": null,
"expires_at": "2026-11-29T15:37:00.588Z",
"error": null,
"summary": null,
"labels": null,
"sources": [],
"counts": {
"trades": 0,
"cycles": 0
}
},
"error": null
}Get a backtest #
/v2/backtests/{backtest_id} scope readWhere the backtest is (queued, running with progress, done, failed, cancelled) and, once done, its summary, daily equity, labels and sources.
Parameters
- backtest_id path · string required
Backtest id.
Errors
| HTTP | Code | When |
|---|---|---|
| 401 | unauthorized | No credential, or the credential is invalid, expired or revoked. |
| 403 | forbidden | The credential may not use this route (missing scope, app-session-only route, IP not allowed). |
| 404 | backtest_not_found | No backtest with this id for the caller (results are kept 30 days). |
| 429 | rate_limited | Too many requests for this credential (or too many failed attempts); retry after the |
All fields (request and response schema)
Loading fields…
curl "https://api.cirrus.trade/v2/backtests/<backtest_id>" \
-H "Authorization: token $CIRRUS_API_KEY"import os
import requests
response = requests.get(
"https://api.cirrus.trade/v2/backtests/<backtest_id>",
headers={
"Authorization": f"token {os.environ['CIRRUS_API_KEY']}",
},
timeout=10,
)
print(response.status_code, response.json())const response = await fetch('https://api.cirrus.trade/v2/backtests/<backtest_id>', {
method: 'GET',
headers: {
Authorization: `token ${process.env.CIRRUS_API_KEY}`,
},
});
const body = await response.json();
console.log(response.status, body);{
"status": "success",
"data": {
"id": "01M66QA2X382CT2C47RR5XN0FY",
"strategy_id": "01M66QA2V4VGYTC47SA6EEBXZM",
"version": 1,
"status": "done",
"from": "2026-10-14",
"to": "2026-10-16",
"capital": 500000,
"costs": {
"charges": "india",
"slippage_bps": 2,
"spread_proxy_bps": 10
},
"progress": {
"done": 3,
"total": 3
},
"cancel_requested": false,
"created_at": "2026-10-30T15:37:00.588Z",
"started_at": "2026-10-30T15:37:00.752Z",
"finished_at": "2026-10-30T15:37:02.016Z",
"expires_at": "2026-11-29T15:37:00.588Z",
"error": null,
"summary": {
"capital": 500000,
"final_equity": 496635.79,
"net_pnl": -3364.21,
"charges": 307.96,
"cycles": 3,
"trades": 12,
"wins": 0,
"losses": 3,
"win_rate": 0,
"best_cycle": -54.99,
"worst_cycle": -3176.51,
"avg_cycle": -1121.4,
"max_drawdown": 3592.73,
"max_drawdown_pct": 0.0072,
"days": 3,
"days_traded": 3,
"metrics": {
"total_return": -0.0066,
"cagr": -0.4276,
"annualized_vol": 0.0462,
"sharpe": -12.247,
"sortino": -9.6967,
"calmar": -64.5952,
"max_drawdown": 0.0066,
"profit_factor": 0
},
"band": null
},
"labels": {
"estimated": true,
"text": "Estimated: option prices are modelled from NIFTY and India VIX, not real trades",
"notes": [
"Expiries follow the exchange's weekday rules, strikes are 50 points apart and one lot is 75 units (today's lot size).",
"Every strike uses the same volatility: India VIX × 1; real prices have a skew.",
"Fills follow the forward test's rules: protected limits on entry, crossing the spread on exit, 2 bps slippage."
]
},
"sources": [
{
"series": "underlying",
"symbol": "CT:EX:BTNIFTY50",
"source": "chart_api",
"estimated": false
},
{
"series": "vix",
"symbol": "CT:EX:BTINDIAVIX",
"source": "chart_api",
"estimated": false
},
{
"series": "options",
"symbol": "NFO:NIFTY",
"source": "estimator",
"estimated": true,
"note": "no option history yet: prices are modelled"
},
{
"series": "futures",
"symbol": "NFO:NIFTY",
"source": "estimator",
"estimated": true,
"note": "no option history yet: prices are modelled"
}
],
"equity": [
{
"day": "2026-10-14",
"equity": 499945.01,
"pnl": -54.99,
"drawdown": 283.51
},
{
"day": "2026-10-15",
"equity": 499812.3,
"pnl": -132.71,
"drawdown": 416.22
},
{
"day": "2026-10-16",
"equity": 496635.79,
"pnl": -3176.51,
"drawdown": 3592.73
}
],
"counts": {
"trades": 12,
"cycles": 3
}
},
"error": null
}List a backtest's trades #
/v2/backtests/{backtest_id}/trades scope readThe simulated fills of a finished backtest in time order, a page at a time (offset, limit up to 200).
More about this
estimated marks modelled prices.
Parameters
- backtest_id path · string required
Backtest id.
- offset query · integer (int32)
- limit query · integer (int32)
Errors
| HTTP | Code | When |
|---|---|---|
| 401 | unauthorized | No credential, or the credential is invalid, expired or revoked. |
| 403 | forbidden | The credential may not use this route (missing scope, app-session-only route, IP not allowed). |
| 404 | backtest_not_found | No backtest with this id for the caller (results are kept 30 days). |
| 422 | invalid_request | The request is well-formed but not valid; |
| 429 | rate_limited | Too many requests for this credential (or too many failed attempts); retry after the |
All fields (request and response schema)
Loading fields…
curl "https://api.cirrus.trade/v2/backtests/<backtest_id>/trades" \
-H "Authorization: token $CIRRUS_API_KEY"import os
import requests
response = requests.get(
"https://api.cirrus.trade/v2/backtests/<backtest_id>/trades",
headers={
"Authorization": f"token {os.environ['CIRRUS_API_KEY']}",
},
timeout=10,
)
print(response.status_code, response.json())const response = await fetch('https://api.cirrus.trade/v2/backtests/<backtest_id>/trades', {
method: 'GET',
headers: {
Authorization: `token ${process.env.CIRRUS_API_KEY}`,
},
});
const body = await response.json();
console.log(response.status, body);{
"status": "success",
"data": {
"items": [
{
"n": 1,
"day": "2026-10-14",
"cycle": 1,
"leg_id": "sc",
"symbol": "BT:NFO:NIFTY:20261019:24450:CE",
"side": "SELL",
"qty": 75,
"price": 36.6,
"charges": 27.74,
"at_ms": 1791950400000,
"purpose": "entry",
"estimated": true
},
{
"n": 2,
"day": "2026-10-14",
"cycle": 1,
"leg_id": "sp",
"symbol": "BT:NFO:NIFTY:20261019:23850:PE",
"side": "SELL",
"qty": 75,
"price": 45.5,
"charges": 28.74,
"at_ms": 1791950400000,
"purpose": "entry",
"estimated": true
}
],
"offset": 0,
"limit": 2,
"total": 12
},
"error": null
}List a backtest's cycles #
/v2/backtests/{backtest_id}/cycles scope readThe cycles of a finished backtest in time order, each with its P&L and journey (the engine's own steps and sentences), a page at a time (offset, limit up to 200).
Parameters
- backtest_id path · string required
Backtest id.
- offset query · integer (int32)
- limit query · integer (int32)
Errors
| HTTP | Code | When |
|---|---|---|
| 401 | unauthorized | No credential, or the credential is invalid, expired or revoked. |
| 403 | forbidden | The credential may not use this route (missing scope, app-session-only route, IP not allowed). |
| 404 | backtest_not_found | No backtest with this id for the caller (results are kept 30 days). |
| 422 | invalid_request | The request is well-formed but not valid; |
| 429 | rate_limited | Too many requests for this credential (or too many failed attempts); retry after the |
All fields (request and response schema)
Loading fields…
curl "https://api.cirrus.trade/v2/backtests/<backtest_id>/cycles" \
-H "Authorization: token $CIRRUS_API_KEY"import os
import requests
response = requests.get(
"https://api.cirrus.trade/v2/backtests/<backtest_id>/cycles",
headers={
"Authorization": f"token {os.environ['CIRRUS_API_KEY']}",
},
timeout=10,
)
print(response.status_code, response.json())const response = await fetch('https://api.cirrus.trade/v2/backtests/<backtest_id>/cycles', {
method: 'GET',
headers: {
Authorization: `token ${process.env.CIRRUS_API_KEY}`,
},
});
const body = await response.json();
console.log(response.status, body);{
"status": "success",
"data": {
"items": [
{
"n": 1,
"day": "2026-10-14",
"cycle": 1,
"opened_at_ms": 1791950400000,
"closed_at_ms": 1791971100000,
"exit_reason": "square_off",
"pnl": -54.99,
"charges": 103.74,
"steps": [
{
"at_ms": 1791950400000,
"code": "entry_sent",
"phase": "entering",
"text": "The entry window is open (time-based entry). Entering: sell 1 lot call, sell 1 lot put."
},
{
"at_ms": 1791950400000,
"code": "entered",
"phase": "managing",
"text": "Entered: sold 75 × BT:NFO:NIFTY:20261019:24450:CE at ₹36.60, sold 75 × BT:NFO:NIFTY:20261019:23850:PE at ₹45.50. Protected: combined stop at -₹3,000, target at +₹2,000, square-off at 15:15."
},
{
"at_ms": 1791971100000,
"code": "exit_sent",
"phase": "exiting",
"text": "Square-off time: P&L +₹75. Closing the trade."
},
{
"at_ms": 1791971100000,
"code": "cycle_closed",
"phase": "cooldown",
"text": "Closed every leg. Trade P&L +₹49 (charges excluded). Today: +₹49 · 1 of 3 trades."
},
{
"at_ms": 1791971100000,
"code": "finished",
"phase": "finished",
"text": "No more trades today (re-entry rules)."
}
]
}
],
"offset": 0,
"limit": 1,
"total": 3
},
"error": null
}Cancel a backtest #
/v2/backtests/{backtest_id}/cancel scope strategiesA queued backtest is cancelled at once; a running one stops within seconds (cancel_requested until it does).
More about this
A finished one answers 409 backtest_finished.
Parameters
- backtest_id path · string required
Backtest id.
- Idempotency-Key header · string
Optional: a new UUID per request (1-128 printable characters). A repeat with the same key gets the first answer back instead of acting again.
Errors
| HTTP | Code | When |
|---|---|---|
| 401 | unauthorized | No credential, or the credential is invalid, expired or revoked. |
| 403 | forbidden | The credential may not use this route (missing scope, app-session-only route, IP not allowed). |
| 404 | backtest_not_found | No backtest with this id for the caller (results are kept 30 days). |
| 409 | backtest_finished | The backtest has already finished (done, failed or cancelled); nothing to cancel. |
| 429 | rate_limited | Too many requests for this credential (or too many failed attempts); retry after the |
All fields (request and response schema)
Loading fields…
curl -X POST "https://api.cirrus.trade/v2/backtests/<backtest_id>/cancel" \
-H "Authorization: token $CIRRUS_API_KEY"import os
import requests
response = requests.post(
"https://api.cirrus.trade/v2/backtests/<backtest_id>/cancel",
headers={
"Authorization": f"token {os.environ['CIRRUS_API_KEY']}",
},
timeout=10,
)
print(response.status_code, response.json())const response = await fetch('https://api.cirrus.trade/v2/backtests/<backtest_id>/cancel', {
method: 'POST',
headers: {
Authorization: `token ${process.env.CIRRUS_API_KEY}`,
},
});
const body = await response.json();
console.log(response.status, body);{
"status": "success",
"data": {
"id": "01M66QA2YJGRWYHB9VJRAW6BRW",
"strategy_id": "01M66QA2V4VGYTC47SA6EEBXZM",
"version": 1,
"status": "cancelled",
"from": "2026-10-14",
"to": "2026-10-16",
"capital": 500000,
"costs": {
"charges": "india",
"slippage_bps": 2,
"spread_proxy_bps": 10
},
"progress": {
"done": 0,
"total": 0
},
"cancel_requested": true,
"created_at": "2026-10-30T15:37:00.627Z",
"started_at": null,
"finished_at": "2026-10-30T15:37:00.640Z",
"expires_at": "2026-11-29T15:37:00.627Z",
"error": null,
"summary": null,
"labels": null,
"sources": [],
"counts": {
"trades": 0,
"cycles": 0
}
},
"error": null
}List sweeps #
/v2/backtests/sweeps scope readThe caller's sweeps, newest first (at most 50; kept 30 days), without their versions.
Errors
| HTTP | Code | When |
|---|---|---|
| 401 | unauthorized | No credential, or the credential is invalid, expired or revoked. |
| 403 | forbidden | The credential may not use this route (missing scope, app-session-only route, IP not allowed). |
| 429 | rate_limited | Too many requests for this credential (or too many failed attempts); retry after the |
All fields (request and response schema)
Loading fields…
curl "https://api.cirrus.trade/v2/backtests/sweeps" \
-H "Authorization: token $CIRRUS_API_KEY"import os
import requests
response = requests.get(
"https://api.cirrus.trade/v2/backtests/sweeps",
headers={
"Authorization": f"token {os.environ['CIRRUS_API_KEY']}",
},
timeout=10,
)
print(response.status_code, response.json())const response = await fetch('https://api.cirrus.trade/v2/backtests/sweeps', {
method: 'GET',
headers: {
Authorization: `token ${process.env.CIRRUS_API_KEY}`,
},
});
const body = await response.json();
console.log(response.status, body);{
"status": "success",
"data": [
{
"id": "01M6729QKEQ99A4TXHNMZAA7NG",
"strategy_id": "01M6729PTSDV47N0JD25NBZQVX",
"version": 1,
"status": "done",
"from": "2026-10-14",
"to": "2026-10-16",
"capital": 500000,
"costs": {
"charges": "india",
"slippage_bps": 2,
"spread_proxy_bps": 10
},
"axes": [
{
"path": "/spec/legs/items/0/strike/delta",
"values": [
0.15,
0.2
]
},
{
"path": "/spec/risk/combined/stop_loss/rupees",
"values": [
2000,
3000
]
}
],
"progress": {
"total": 4,
"queued": 0,
"running": 0,
"done": 3,
"failed": 1,
"cancelled": 0
},
"cost": 1,
"cancel_requested": false,
"created_at": "2026-10-30T18:49:03.347Z",
"finished_at": "2026-10-30T18:49:04.277Z",
"expires_at": "2026-11-29T18:49:03.347Z",
"error": null,
"recommended": 0,
"warnings": [
"Best result may be luck: after testing 3 versions, the chance the best one is really better than average is low (deflated Sharpe 0.17).",
"Fewer than 30 trades: too few to judge."
],
"notes": [
"Walk-forward test skipped: it needs at least 100 trading days of history (20 per window), and this sweep has 3.",
"Overfitting check (PBO) skipped: it needs at least 16 versions, and this sweep has 3.",
"1 of 4 versions did not finish (failed or cancelled) and are left out of the checks."
],
"deflated_sharpe": 0.1691982360279356,
"pbo": null,
"walk_forward": null
}
],
"error": null
}Start a sweep #
/v2/backtests/sweeps scope strategiesBacktests every version of up to 3 settings of a saved strategy version over the same past days (one backtest per version, same history and fill rules as POST /v2/backtests).
More about this
Each setting is a JSON pointer to a number in the strategy document and the values to try; at most 200 versions, each checked as a strategy document. Poll GET /v2/backtests/sweeps/{sweep_id}: once done it names the recommended version (the best plateau of neighbouring settings, not the best single result) and the overfitting warnings to show before deploy. One sweep at a time per user; its versions run at most 3 at a time, sharing workers fairly with other users. A sweep counts as 1 backtest plus 1 per 20 versions against the 50 a day, and as one of the 3 running at once.
Parameters
- Idempotency-Key header · string
Optional: a new UUID per request (1-128 printable characters). A repeat with the same key gets the first answer back instead of acting again.
Body fields
- strategy_id string required
- axes object[] required
1 to 3 settings; at most 200 versions in all.
- from string required
First day (
YYYY-MM-DD, IST). - to string required
Last day, inclusive; before today.
fromtotocovers at most 1098 days (3 years), both included. - version integer (int32) | null
The saved version the sweep starts from; the latest when absent.
- capital number (double) | null
Virtual capital (₹); default 5,00,000.
- costs object | null
Costs to apply; defaults: Indian charges, 2 bps slippage.
Errors
| HTTP | Code | When |
|---|---|---|
| 401 | unauthorized | No credential, or the credential is invalid, expired or revoked. |
| 403 | forbidden | The credential may not use this route (missing scope, app-session-only route, IP not allowed). |
| 404 | version_not_found | No such version of the strategy. |
| 409 | backtest_limit_reached | The most backtests that may be queued or running at once, or started today, are reached. |
| 422 | invalid_request | The request is well-formed but not valid; |
| 429 | rate_limited | Too many requests for this credential (or too many failed attempts); retry after the |
All fields (request and response schema)
Loading fields…
curl -X POST "https://api.cirrus.trade/v2/backtests/sweeps" \
-H "Authorization: token $CIRRUS_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"strategy_id": "01M66YJSSC5QFRPRW0AF5Y9HXM",
"version": 1,
"from": "2026-10-14",
"to": "2026-10-16",
"axes": [
{
"path": "/spec/legs/items/0/strike/delta",
"values": [
0.15,
0.2
]
},
{
"path": "/spec/risk/combined/stop_loss/rupees",
"values": [
2000,
3000
]
}
]
}'import os
import requests
response = requests.post(
"https://api.cirrus.trade/v2/backtests/sweeps",
headers={
"Authorization": f"token {os.environ['CIRRUS_API_KEY']}",
},
json={
"strategy_id": "01M66YJSSC5QFRPRW0AF5Y9HXM",
"version": 1,
"from": "2026-10-14",
"to": "2026-10-16",
"axes": [
{
"path": "/spec/legs/items/0/strike/delta",
"values": [
0.15,
0.2,
],
},
{
"path": "/spec/risk/combined/stop_loss/rupees",
"values": [
2000,
3000,
],
},
],
},
timeout=10,
)
print(response.status_code, response.json())const response = await fetch('https://api.cirrus.trade/v2/backtests/sweeps', {
method: 'POST',
headers: {
Authorization: `token ${process.env.CIRRUS_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"strategy_id": "01M66YJSSC5QFRPRW0AF5Y9HXM",
"version": 1,
"from": "2026-10-14",
"to": "2026-10-16",
"axes": [
{
"path": "/spec/legs/items/0/strike/delta",
"values": [
0.15,
0.2
]
},
{
"path": "/spec/risk/combined/stop_loss/rupees",
"values": [
2000,
3000
]
}
]
}),
});
const body = await response.json();
console.log(response.status, body);{
"status": "success",
"data": {
"id": "01M66YJTX11JWSA4K90RGYJPMA",
"strategy_id": "01M66YJSSC5QFRPRW0AF5Y9HXM",
"version": 1,
"status": "running",
"from": "2026-10-14",
"to": "2026-10-16",
"capital": 500000,
"costs": {
"charges": "india",
"slippage_bps": 2,
"spread_proxy_bps": 10
},
"axes": [
{
"path": "/spec/legs/items/0/strike/delta",
"values": [
0.15,
0.2
]
},
{
"path": "/spec/risk/combined/stop_loss/rupees",
"values": [
2000,
3000
]
}
],
"progress": {
"total": 4,
"queued": 4,
"running": 0,
"done": 0,
"failed": 0,
"cancelled": 0
},
"cost": 1,
"cancel_requested": false,
"created_at": "2026-10-30T17:44:07.342Z",
"finished_at": null,
"expires_at": "2026-11-29T17:44:07.342Z",
"error": null,
"recommended": null,
"warnings": [],
"notes": [],
"deflated_sharpe": null,
"pbo": null,
"walk_forward": null
},
"error": null
}Get a sweep #
/v2/backtests/sweeps/{sweep_id} scope readWhere the sweep is (running with progress, then done, failed or cancelled), every version with its values, status and summary, and once done the recommended version, its scores, the warnings to show before deploy and notes on checks that did not run or versions left out.
Parameters
- sweep_id path · string required
Sweep id.
Errors
| HTTP | Code | When |
|---|---|---|
| 401 | unauthorized | No credential, or the credential is invalid, expired or revoked. |
| 403 | forbidden | The credential may not use this route (missing scope, app-session-only route, IP not allowed). |
| 404 | backtest_not_found | No backtest with this id for the caller (results are kept 30 days). |
| 429 | rate_limited | Too many requests for this credential (or too many failed attempts); retry after the |
All fields (request and response schema)
Loading fields…
curl "https://api.cirrus.trade/v2/backtests/sweeps/<sweep_id>" \
-H "Authorization: token $CIRRUS_API_KEY"import os
import requests
response = requests.get(
"https://api.cirrus.trade/v2/backtests/sweeps/<sweep_id>",
headers={
"Authorization": f"token {os.environ['CIRRUS_API_KEY']}",
},
timeout=10,
)
print(response.status_code, response.json())const response = await fetch('https://api.cirrus.trade/v2/backtests/sweeps/<sweep_id>', {
method: 'GET',
headers: {
Authorization: `token ${process.env.CIRRUS_API_KEY}`,
},
});
const body = await response.json();
console.log(response.status, body);{
"status": "success",
"data": {
"id": "01M6729QKEQ99A4TXHNMZAA7NG",
"strategy_id": "01M6729PTSDV47N0JD25NBZQVX",
"version": 1,
"status": "done",
"from": "2026-10-14",
"to": "2026-10-16",
"capital": 500000,
"costs": {
"charges": "india",
"slippage_bps": 2,
"spread_proxy_bps": 10
},
"axes": [
{
"path": "/spec/legs/items/0/strike/delta",
"values": [
0.15,
0.2
]
},
{
"path": "/spec/risk/combined/stop_loss/rupees",
"values": [
2000,
3000
]
}
],
"progress": {
"total": 4,
"queued": 0,
"running": 0,
"done": 3,
"failed": 1,
"cancelled": 0
},
"cost": 1,
"cancel_requested": false,
"created_at": "2026-10-30T18:49:03.347Z",
"finished_at": "2026-10-30T18:49:04.277Z",
"expires_at": "2026-11-29T18:49:03.347Z",
"error": null,
"recommended": 0,
"warnings": [
"Best result may be luck: after testing 3 versions, the chance the best one is really better than average is low (deflated Sharpe 0.17).",
"Fewer than 30 trades: too few to judge."
],
"notes": [
"Walk-forward test skipped: it needs at least 100 trading days of history (20 per window), and this sweep has 3.",
"Overfitting check (PBO) skipped: it needs at least 16 versions, and this sweep has 3.",
"1 of 4 versions did not finish (failed or cancelled) and are left out of the checks."
],
"deflated_sharpe": 0.1691982360279356,
"pbo": null,
"walk_forward": null,
"versions": [
{
"n": 0,
"values": [
0.15,
2000
],
"backtest_id": "01M6729QKFE6A2XEC6Q2PBZZ26",
"status": "done",
"error": null,
"summary": {
"capital": 500000,
"final_equity": 498079.58,
"net_pnl": -1920.42,
"charges": 304.17,
"cycles": 3,
"trades": 12,
"wins": 2,
"losses": 1,
"win_rate": 0.67,
"best_cycle": 182.21,
"worst_cycle": -2193.13,
"avg_cycle": -640.14,
"max_drawdown": 2394.76,
"max_drawdown_pct": 0.0048,
"days": 3,
"days_traded": 3,
"metrics": {
"total_return": -0.0042,
"cagr": -0.298,
"annualized_vol": 0.0349,
"sharpe": -9.2477,
"sortino": -8.025,
"calmar": -67.9822,
"max_drawdown": 0.0044,
"profit_factor": 0
},
"band": null
},
"sharpe": -0.475648339981058,
"plateau": -0.5498070234280426,
"recommended": true
},
{
"n": 1,
"values": [
0.15,
3000
],
"backtest_id": "01M6729QKG9GG46QSCPWZ7S1Q8",
"status": "done",
"error": null,
"summary": {
"capital": 500000,
"final_equity": 497097.07,
"net_pnl": -2902.93,
"charges": 304.18,
"cycles": 3,
"trades": 12,
"wins": 2,
"losses": 1,
"win_rate": 0.67,
"best_cycle": 182.21,
"worst_cycle": -3175.64,
"avg_cycle": -967.64,
"max_drawdown": 3377.27,
"max_drawdown_pct": 0.0067,
"days": 3,
"days_traded": 3,
"metrics": {
"total_return": -0.0062,
"cagr": -0.4053,
"annualized_vol": 0.0496,
"sharpe": -9.8352,
"sortino": -8.3778,
"calmar": -63.8507,
"max_drawdown": 0.0063,
"profit_factor": 0
},
"band": null
},
"sharpe": -0.5058669394394733,
"plateau": -0.5498070234280426,
"recommended": false
},
{
"n": 2,
"values": [
0.2,
2000
],
"backtest_id": "01M6729QKHZPHC8KN8E4K30RKP",
"status": "done",
"error": null,
"summary": {
"capital": 500000,
"final_equity": 497933.3,
"net_pnl": -2066.7,
"charges": 307.95,
"cycles": 3,
"trades": 12,
"wins": 0,
"losses": 3,
"win_rate": 0,
"best_cycle": -54.99,
"worst_cycle": -1879,
"avg_cycle": -688.9,
"max_drawdown": 2397.95,
"max_drawdown_pct": 0.0048,
"days": 3,
"days_traded": 3,
"metrics": {
"total_return": -0.004,
"cagr": -0.2873,
"annualized_vol": 0.0267,
"sharpe": -12.9856,
"sortino": -10.0511,
"calmar": -71.3976,
"max_drawdown": 0.004,
"profit_factor": 0
},
"band": null
},
"sharpe": -0.6679057908635965,
"plateau": -0.5498070234280426,
"recommended": false
},
{
"n": 3,
"values": [
0.2,
3000
],
"backtest_id": "01M6729QKJ72C8CF01BZ1ND5C9",
"status": "failed",
"error": "History for NIFTY 50 is missing on 2026-10-15",
"summary": null,
"sharpe": null,
"plateau": null,
"recommended": false
}
]
},
"error": null
}Cancel a sweep #
/v2/backtests/sweeps/{sweep_id} scope strategiesQueued versions are cancelled at once and running ones stop within seconds; the sweep is cancelled when the last one has (cancel_requested until then).
More about this
The sweep and the versions that finished stay readable. A finished sweep answers 409 backtest_finished.
Parameters
- sweep_id path · string required
Sweep id.
- Idempotency-Key header · string
Optional: a new UUID per request (1-128 printable characters). A repeat with the same key gets the first answer back instead of acting again.
Errors
| HTTP | Code | When |
|---|---|---|
| 401 | unauthorized | No credential, or the credential is invalid, expired or revoked. |
| 403 | forbidden | The credential may not use this route (missing scope, app-session-only route, IP not allowed). |
| 404 | backtest_not_found | No backtest with this id for the caller (results are kept 30 days). |
| 409 | backtest_finished | The backtest has already finished (done, failed or cancelled); nothing to cancel. |
| 429 | rate_limited | Too many requests for this credential (or too many failed attempts); retry after the |
All fields (request and response schema)
Loading fields…
curl -X DELETE "https://api.cirrus.trade/v2/backtests/sweeps/<sweep_id>" \
-H "Authorization: token $CIRRUS_API_KEY"import os
import requests
response = requests.delete(
"https://api.cirrus.trade/v2/backtests/sweeps/<sweep_id>",
headers={
"Authorization": f"token {os.environ['CIRRUS_API_KEY']}",
},
timeout=10,
)
print(response.status_code, response.json())const response = await fetch('https://api.cirrus.trade/v2/backtests/sweeps/<sweep_id>', {
method: 'DELETE',
headers: {
Authorization: `token ${process.env.CIRRUS_API_KEY}`,
},
});
const body = await response.json();
console.log(response.status, body);{
"status": "success",
"data": {
"id": "01M66YJW74ZVAWVXYG8XH6E9KA",
"strategy_id": "01M66YJSSC5QFRPRW0AF5Y9HXM",
"version": 1,
"status": "cancelled",
"from": "2026-10-14",
"to": "2026-10-16",
"capital": 500000,
"costs": {
"charges": "india",
"slippage_bps": 2,
"spread_proxy_bps": 10
},
"axes": [
{
"path": "/spec/legs/items/0/strike/delta",
"values": [
0.15,
0.2
]
},
{
"path": "/spec/risk/combined/stop_loss/rupees",
"values": [
2000,
3000
]
}
],
"progress": {
"total": 4,
"queued": 0,
"running": 0,
"done": 0,
"failed": 0,
"cancelled": 4
},
"cost": 1,
"cancel_requested": true,
"created_at": "2026-10-30T17:44:08.683Z",
"finished_at": "2026-10-30T17:44:08.693Z",
"expires_at": "2026-11-29T17:44:08.683Z",
"error": null,
"recommended": null,
"warnings": [],
"notes": [],
"deflated_sharpe": null,
"pbo": null,
"walk_forward": null,
"versions": [
{
"n": 0,
"values": [
0.15,
2000
],
"backtest_id": "01M66YJW76BYE5CF5QP9WSQYKM",
"status": "cancelled",
"error": null,
"summary": null,
"sharpe": null,
"plateau": null,
"recommended": false
},
{
"n": 1,
"values": [
0.15,
3000
],
"backtest_id": "01M66YJW78S2SFS8FPXBTX3GWA",
"status": "cancelled",
"error": null,
"summary": null,
"sharpe": null,
"plateau": null,
"recommended": false
},
{
"n": 2,
"values": [
0.2,
2000
],
"backtest_id": "01M66YJW79PPVQMH0ERFK033DJ",
"status": "cancelled",
"error": null,
"summary": null,
"sharpe": null,
"plateau": null,
"recommended": false
},
{
"n": 3,
"values": [
0.2,
3000
],
"backtest_id": "01M66YJW7BQT1DP0N3MPMV006A",
"status": "cancelled",
"error": null,
"summary": null,
"sharpe": null,
"plateau": null,
"recommended": false
}
]
},
"error": null
}