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://cirrus.pocketful.in/v2/backtests" \
-H "Authorization: token $CIRRUS_API_KEY"import os
import requests
response = requests.get(
"https://cirrus.pocketful.in/v2/backtests",
headers={
"Authorization": f"token {os.environ['CIRRUS_API_KEY']}",
},
timeout=10,
)
print(response.status_code, response.json())const response = await fetch('https://cirrus.pocketful.in/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": "01M59PT8M6T5T1BSBJJS32JB8S",
"strategy_id": "01M59PT8JWJCXWS94297VJ7A4Q",
"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-19T09:10:23.622Z",
"started_at": null,
"finished_at": "2026-10-19T09:10:23.629Z",
"expires_at": "2026-11-18T09:10:23.622Z",
"error": null,
"summary": null,
"labels": null,
"sources": [],
"counts": {
"trades": 0,
"cycles": 0
}
},
{
"id": "01M59PT8KPYQBV80ZN03CSEDSQ",
"strategy_id": "01M59PT8JWJCXWS94297VJ7A4Q",
"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-19T09:10:23.610Z",
"started_at": "2026-10-19T09:10:23.637Z",
"finished_at": "2026-10-19T09:10:24.072Z",
"expires_at": "2026-11-18T09:10:23.610Z",
"error": null,
"summary": {
"capital": 500000,
"final_equity": 496377.27,
"net_pnl": -3622.73,
"charges": 311.48,
"cycles": 3,
"trades": 12,
"wins": 0,
"losses": 3,
"win_rate": 0,
"best_cycle": -92.62,
"worst_cycle": -3166.72,
"avg_cycle": -1207.58,
"max_drawdown": 3794.11,
"max_drawdown_pct": 0.0076,
"days": 3,
"days_traded": 3,
"metrics": {
"total_return": -0.0065,
"cagr": -0.4229,
"annualized_vol": 0.0442,
"sharpe": -13.7918,
"sortino": -10.4113,
"calmar": -64.83,
"max_drawdown": 0.0065,
"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 366 days per run, 3 queued or running and 50 a day per user, 10 submissions a minute.
Body fields
- strategy_id string required
- from string required
First day (
YYYY-MM-DD, IST). - to string required
Last day, inclusive; before today. At most 366 days after
from. - 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://cirrus.pocketful.in/v2/backtests" \
-H "Authorization: token $CIRRUS_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"strategy_id": "01M59PT8JWJCXWS94297VJ7A4Q",
"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://cirrus.pocketful.in/v2/backtests",
headers={
"Authorization": f"token {os.environ['CIRRUS_API_KEY']}",
},
json={
"strategy_id": "01M59PT8JWJCXWS94297VJ7A4Q",
"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://cirrus.pocketful.in/v2/backtests', {
method: 'POST',
headers: {
Authorization: `token ${process.env.CIRRUS_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"strategy_id": "01M59PT8JWJCXWS94297VJ7A4Q",
"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": "01M59PT8KPYQBV80ZN03CSEDSQ",
"strategy_id": "01M59PT8JWJCXWS94297VJ7A4Q",
"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-19T09:10:23.610Z",
"started_at": null,
"finished_at": null,
"expires_at": "2026-11-18T09:10:23.610Z",
"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://cirrus.pocketful.in/v2/backtests/<backtest_id>" \
-H "Authorization: token $CIRRUS_API_KEY"import os
import requests
response = requests.get(
"https://cirrus.pocketful.in/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://cirrus.pocketful.in/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": "01M59PT8KPYQBV80ZN03CSEDSQ",
"strategy_id": "01M59PT8JWJCXWS94297VJ7A4Q",
"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-19T09:10:23.610Z",
"started_at": "2026-10-19T09:10:23.637Z",
"finished_at": "2026-10-19T09:10:24.072Z",
"expires_at": "2026-11-18T09:10:23.610Z",
"error": null,
"summary": {
"capital": 500000,
"final_equity": 496377.27,
"net_pnl": -3622.73,
"charges": 311.48,
"cycles": 3,
"trades": 12,
"wins": 0,
"losses": 3,
"win_rate": 0,
"best_cycle": -92.62,
"worst_cycle": -3166.72,
"avg_cycle": -1207.58,
"max_drawdown": 3794.11,
"max_drawdown_pct": 0.0076,
"days": 3,
"days_traded": 3,
"metrics": {
"total_return": -0.0065,
"cagr": -0.4229,
"annualized_vol": 0.0442,
"sharpe": -13.7918,
"sortino": -10.4113,
"calmar": -64.83,
"max_drawdown": 0.0065,
"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": 499636.61,
"pnl": -363.39,
"drawdown": 534.77
},
{
"day": "2026-10-15",
"equity": 499543.99,
"pnl": -92.62,
"drawdown": 627.39
},
{
"day": "2026-10-16",
"equity": 496377.27,
"pnl": -3166.72,
"drawdown": 3794.11
}
],
"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://cirrus.pocketful.in/v2/backtests/<backtest_id>/trades" \
-H "Authorization: token $CIRRUS_API_KEY"import os
import requests
response = requests.get(
"https://cirrus.pocketful.in/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://cirrus.pocketful.in/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:20261020:24450:CE",
"side": "SELL",
"qty": 75,
"price": 46.6,
"charges": 28.87,
"at_ms": 1791950400000,
"purpose": "entry",
"estimated": true
},
{
"n": 2,
"day": "2026-10-14",
"cycle": 1,
"leg_id": "sp",
"symbol": "BT:NFO:NIFTY:20261020:23800:PE",
"side": "SELL",
"qty": 75,
"price": 43.35,
"charges": 28.5,
"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://cirrus.pocketful.in/v2/backtests/<backtest_id>/cycles" \
-H "Authorization: token $CIRRUS_API_KEY"import os
import requests
response = requests.get(
"https://cirrus.pocketful.in/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://cirrus.pocketful.in/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": -363.39,
"charges": 104.64,
"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:20261020:24450:CE at ₹46.60, sold 75 × BT:NFO:NIFTY:20261020:23800:PE at ₹43.35. 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 -₹229. Closing the trade."
},
{
"at_ms": 1791971100000,
"code": "cycle_closed",
"phase": "cooldown",
"text": "Closed every leg. Trade P&L -₹259 (charges excluded). Today: -₹259 · 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.
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://cirrus.pocketful.in/v2/backtests/<backtest_id>/cancel" \
-H "Authorization: token $CIRRUS_API_KEY"import os
import requests
response = requests.post(
"https://cirrus.pocketful.in/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://cirrus.pocketful.in/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": "01M59PT8M6T5T1BSBJJS32JB8S",
"strategy_id": "01M59PT8JWJCXWS94297VJ7A4Q",
"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-19T09:10:23.622Z",
"started_at": null,
"finished_at": "2026-10-19T09:10:23.629Z",
"expires_at": "2026-11-18T09:10:23.622Z",
"error": null,
"summary": null,
"labels": null,
"sources": [],
"counts": {
"trades": 0,
"cycles": 0
}
},
"error": null
}