Skip to content
Cirrus home
Sign in

Reference

Backtests

Run a strategy version over past days and read its trades.

Read the guide first

List backtests #

GET /v2/backtests scope read

The caller's backtests, newest first (at most 50; kept 30 days), without their daily equity.

Errors

HTTPCodeWhen
401unauthorized

No credential, or the credential is invalid, expired or revoked.

403forbidden

The credential may not use this route (missing scope, app-session-only route, IP not allowed).

429rate_limited

Too many requests for this credential (or too many failed attempts); retry after the Retry-After header (seconds; error.retry_after_ms has the exact wait).

All fields (request and response schema)

Loading fields…

GET
curl "https://cirrus.pocketful.in/v2/backtests" \
  -H "Authorization: token $CIRRUS_API_KEY"
Response · 200
{
  "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 #

POST /v2/backtests scope strategies

Queues 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

HTTPCodeWhen
401unauthorized

No credential, or the credential is invalid, expired or revoked.

403forbidden

The credential may not use this route (missing scope, app-session-only route, IP not allowed).

404version_not_found

No such version of the strategy.

409backtest_limit_reached

The most backtests that may be queued or running at once, or started today, are reached.

422invalid_request

The request is well-formed but not valid; field names the input.

429rate_limited

Too many requests for this credential (or too many failed attempts); retry after the Retry-After header (seconds; error.retry_after_ms has the exact wait).

All fields (request and response schema)

Loading fields…

POST
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
    }
  }'
Response · 202
{
  "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 #

GET /v2/backtests/{backtest_id} scope read

Where 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

HTTPCodeWhen
401unauthorized

No credential, or the credential is invalid, expired or revoked.

403forbidden

The credential may not use this route (missing scope, app-session-only route, IP not allowed).

404backtest_not_found

No backtest with this id for the caller (results are kept 30 days).

429rate_limited

Too many requests for this credential (or too many failed attempts); retry after the Retry-After header (seconds; error.retry_after_ms has the exact wait).

All fields (request and response schema)

Loading fields…

GET
curl "https://cirrus.pocketful.in/v2/backtests/<backtest_id>" \
  -H "Authorization: token $CIRRUS_API_KEY"
Response · 200
{
  "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 #

GET /v2/backtests/{backtest_id}/trades scope read

The 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

HTTPCodeWhen
401unauthorized

No credential, or the credential is invalid, expired or revoked.

403forbidden

The credential may not use this route (missing scope, app-session-only route, IP not allowed).

404backtest_not_found

No backtest with this id for the caller (results are kept 30 days).

422invalid_request

The request is well-formed but not valid; field names the input.

429rate_limited

Too many requests for this credential (or too many failed attempts); retry after the Retry-After header (seconds; error.retry_after_ms has the exact wait).

All fields (request and response schema)

Loading fields…

GET
curl "https://cirrus.pocketful.in/v2/backtests/<backtest_id>/trades" \
  -H "Authorization: token $CIRRUS_API_KEY"
Response · 200
{
  "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 #

GET /v2/backtests/{backtest_id}/cycles scope read

The 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

HTTPCodeWhen
401unauthorized

No credential, or the credential is invalid, expired or revoked.

403forbidden

The credential may not use this route (missing scope, app-session-only route, IP not allowed).

404backtest_not_found

No backtest with this id for the caller (results are kept 30 days).

422invalid_request

The request is well-formed but not valid; field names the input.

429rate_limited

Too many requests for this credential (or too many failed attempts); retry after the Retry-After header (seconds; error.retry_after_ms has the exact wait).

All fields (request and response schema)

Loading fields…

GET
curl "https://cirrus.pocketful.in/v2/backtests/<backtest_id>/cycles" \
  -H "Authorization: token $CIRRUS_API_KEY"
Response · 200
{
  "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 #

POST /v2/backtests/{backtest_id}/cancel scope strategies

A 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

HTTPCodeWhen
401unauthorized

No credential, or the credential is invalid, expired or revoked.

403forbidden

The credential may not use this route (missing scope, app-session-only route, IP not allowed).

404backtest_not_found

No backtest with this id for the caller (results are kept 30 days).

409backtest_finished

The backtest has already finished (done, failed or cancelled); nothing to cancel.

429rate_limited

Too many requests for this credential (or too many failed attempts); retry after the Retry-After header (seconds; error.retry_after_ms has the exact wait).

All fields (request and response schema)

Loading fields…

POST
curl -X POST "https://cirrus.pocketful.in/v2/backtests/<backtest_id>/cancel" \
  -H "Authorization: token $CIRRUS_API_KEY"
Response · 200
{
  "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
}