Strategy builder
What a strategy can use, ready-made templates, and checks before you save or deploy.
Validate a strategy document #
/v2/strategies/validate scope strategiesChecks a document (same body as create) with the code create, import and versions use, and answers 200 whatever it finds: errors (refused), blockers (saved, not deployable yet), warnings (needs_ack: to acknowledge at deploy), each with the document path (field), the wizard label and a plain-English message; the summary; and the hash of the compiled strategy (equal for any way of writing it).
More about this
Everything a deploy would refuse for the document is a blocker here first: the engine plan compiled for a stand-in paper account and checked by the engine (engine_refuses), symbols not in today's instrument list (not_in_instrument_list), underlyings not open, alerts not available. With accounts (and mode, default paper), the deploy's account rules come back as warnings with the deploy's own code and words. Fast enough to call as the user types (no broker calls).
Body fields
- document any required
The portable document: an object, or its JSON or YAML text.
- definition object | null
Instead of
document: the definition (spec) alone. - name string | null
With
definition. - description string | null
With
definition. - tags string[] | null
With
definition. - revision integer (int64) | null
Ignored (accepted as for update).
- mode string | null
Deploy mode to check against (default
paper).One of: paper, live
- accounts object[] | null
Accounts to check against (as for deploy); none: account rules are left for the deploy.
Errors
| HTTP | Code | When |
|---|---|---|
| 400 | malformed_json | The body is not JSON, or does not fit the request shape (unknown or missing fields, wrong types). |
| 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). |
| 422 | invalid_document | The strategy document has problems; |
| 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/strategies/validate" \
-H "Authorization: token $CIRRUS_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"document": {
"schema_version": 1,
"kind": "cirrus.strategy",
"metadata": {
"name": "NIFTY straddle",
"tags": [
"weekly"
]
},
"spec": {
"underlying": {
"name": "NIFTY",
"exchange": "NFO"
},
"legs": {
"selector": "chain@1",
"items": [
{
"id": "sc",
"instrument": "option",
"side": "sell",
"option_type": "call",
"expiry": {
"kind": "weekly"
},
"strike": {
"by": "atm"
}
},
{
"id": "sp",
"instrument": "option",
"side": "sell",
"option_type": "put",
"expiry": {
"kind": "weekly"
},
"strike": {
"by": "atm"
}
}
]
},
"entry": {
"window": {
"start": "09:30",
"end": "13:30"
}
},
"risk": {
"combined": {
"type": "combined@1",
"stop_loss": {
"rupees": 3000
},
"target": {
"rupees": 2000
}
}
}
}
}
}'import os
import requests
response = requests.post(
"https://cirrus.pocketful.in/v2/strategies/validate",
headers={
"Authorization": f"token {os.environ['CIRRUS_API_KEY']}",
},
json={
"document": {
"schema_version": 1,
"kind": "cirrus.strategy",
"metadata": {
"name": "NIFTY straddle",
"tags": [
"weekly",
],
},
"spec": {
"underlying": {
"name": "NIFTY",
"exchange": "NFO",
},
"legs": {
"selector": "chain@1",
"items": [
{
"id": "sc",
"instrument": "option",
"side": "sell",
"option_type": "call",
"expiry": {
"kind": "weekly",
},
"strike": {
"by": "atm",
},
},
{
"id": "sp",
"instrument": "option",
"side": "sell",
"option_type": "put",
"expiry": {
"kind": "weekly",
},
"strike": {
"by": "atm",
},
},
],
},
"entry": {
"window": {
"start": "09:30",
"end": "13:30",
},
},
"risk": {
"combined": {
"type": "combined@1",
"stop_loss": {
"rupees": 3000,
},
"target": {
"rupees": 2000,
},
},
},
},
},
},
timeout=10,
)
print(response.status_code, response.json())const response = await fetch('https://cirrus.pocketful.in/v2/strategies/validate', {
method: 'POST',
headers: {
Authorization: `token ${process.env.CIRRUS_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"document": {
"schema_version": 1,
"kind": "cirrus.strategy",
"metadata": {
"name": "NIFTY straddle",
"tags": [
"weekly"
]
},
"spec": {
"underlying": {
"name": "NIFTY",
"exchange": "NFO"
},
"legs": {
"selector": "chain@1",
"items": [
{
"id": "sc",
"instrument": "option",
"side": "sell",
"option_type": "call",
"expiry": {
"kind": "weekly"
},
"strike": {
"by": "atm"
}
},
{
"id": "sp",
"instrument": "option",
"side": "sell",
"option_type": "put",
"expiry": {
"kind": "weekly"
},
"strike": {
"by": "atm"
}
}
]
},
"entry": {
"window": {
"start": "09:30",
"end": "13:30"
}
},
"risk": {
"combined": {
"type": "combined@1",
"stop_loss": {
"rupees": 3000
},
"target": {
"rupees": 2000
}
}
}
}
}
}),
});
const body = await response.json();
console.log(response.status, body);{
"status": "success",
"data": {
"ok": true,
"deployable": true,
"schema_version": 1,
"migrated_from": null,
"errors": [],
"blockers": [],
"warnings": [],
"needs_ack": [],
"summary": "Sell 1 lot of an ATM call (weekly expiry) and sell 1 lot of an ATM put (weekly expiry) on NIFTY. Enter as soon as the window opens, between 09:30 and 13:30, Monday to Friday. Everything closes at 15:15. Size: the legs' lots times each account's multiplier, at most 10 lots per leg. Orders: hedges first when there are any, limit prices 0.5% beyond the market, chasing up to 1% slippage; if only some legs fill: close a sold option left without its hedge, keep the rest. Protection: stop at ₹3,000 loss, take ₹2,000 profit; no per-leg stops; at most 3 trades a day. No re-entries.",
"compiled_hash": "sha256:c030cdecce518bdf50067237a17de0b5508d3e962e743a87dd41e7df3ef01ff9",
"components": [
"chain@1",
"combined@1",
"conditions@1",
"fixed_lots@1",
"limits@1",
"per_leg@1",
"protected_limit@1"
],
"warm_up": null
},
"error": null
}Dry-run a strategy #
/v2/strategies/dry-run scope strategiesWhat the strategy would do right now, without orders: wait (outside its window or days, or for a signal), enter (with the contracts it would pick from the live chain), skip (and why), or cannot_decide when something it needs is not available on this server yet (the strategy engine, live chain picking).
More about this
Body: a document as for validate, or strategy_id (and version). The validation result is included.
Body fields
- document any required
- definition object | null
- name string | null
- description string | null
- tags string[] | null
- strategy_id string | null
Instead of a document: one of the caller's strategies.
- version integer (int32) | null
With
strategy_id(default: the draft). - account string | null
Account to size and check against.
- at string | null
Check the entry window and days as at this time (RFC 3339; default now). Contracts are always picked from live prices.
Errors
| HTTP | Code | When |
|---|---|---|
| 400 | malformed_json | The body is not JSON, or does not fit the request shape (unknown or missing fields, wrong types). |
| 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 | strategy_not_found | No automated strategy with this id for the caller. |
| 404 | version_not_found | No such version of the strategy. |
| 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/strategies/dry-run" \
-H "Authorization: token $CIRRUS_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"document": {
"schema_version": 1,
"kind": "cirrus.strategy",
"metadata": {
"name": "Broken",
"tags": [
"weekly"
]
},
"spec": {
"underlying": {
"name": "NIFTY",
"exchange": "NFO"
},
"legs": {
"selector": "chain@1",
"items": [
{
"id": "sc",
"instrument": "option",
"side": "sell",
"option_type": "call",
"expiry": {
"kind": "weekly"
},
"strike": {
"by": "atm"
}
},
{
"id": "sp",
"instrument": "option",
"side": "sell",
"option_type": "put",
"expiry": {
"kind": "weekly"
},
"strike": {
"by": "atm"
}
}
]
},
"entry": {
"window": {
"start": "14:00",
"end": "10:00"
}
},
"risk": {
"combined": {
"type": "combined@1",
"stop_loss": {
"rupees": 3000
},
"target": {
"rupees": 2000
}
}
}
}
}
}'import os
import requests
response = requests.post(
"https://cirrus.pocketful.in/v2/strategies/dry-run",
headers={
"Authorization": f"token {os.environ['CIRRUS_API_KEY']}",
},
json={
"document": {
"schema_version": 1,
"kind": "cirrus.strategy",
"metadata": {
"name": "Broken",
"tags": [
"weekly",
],
},
"spec": {
"underlying": {
"name": "NIFTY",
"exchange": "NFO",
},
"legs": {
"selector": "chain@1",
"items": [
{
"id": "sc",
"instrument": "option",
"side": "sell",
"option_type": "call",
"expiry": {
"kind": "weekly",
},
"strike": {
"by": "atm",
},
},
{
"id": "sp",
"instrument": "option",
"side": "sell",
"option_type": "put",
"expiry": {
"kind": "weekly",
},
"strike": {
"by": "atm",
},
},
],
},
"entry": {
"window": {
"start": "14:00",
"end": "10:00",
},
},
"risk": {
"combined": {
"type": "combined@1",
"stop_loss": {
"rupees": 3000,
},
"target": {
"rupees": 2000,
},
},
},
},
},
},
timeout=10,
)
print(response.status_code, response.json())const response = await fetch('https://cirrus.pocketful.in/v2/strategies/dry-run', {
method: 'POST',
headers: {
Authorization: `token ${process.env.CIRRUS_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"document": {
"schema_version": 1,
"kind": "cirrus.strategy",
"metadata": {
"name": "Broken",
"tags": [
"weekly"
]
},
"spec": {
"underlying": {
"name": "NIFTY",
"exchange": "NFO"
},
"legs": {
"selector": "chain@1",
"items": [
{
"id": "sc",
"instrument": "option",
"side": "sell",
"option_type": "call",
"expiry": {
"kind": "weekly"
},
"strike": {
"by": "atm"
}
},
{
"id": "sp",
"instrument": "option",
"side": "sell",
"option_type": "put",
"expiry": {
"kind": "weekly"
},
"strike": {
"by": "atm"
}
}
]
},
"entry": {
"window": {
"start": "14:00",
"end": "10:00"
}
},
"risk": {
"combined": {
"type": "combined@1",
"stop_loss": {
"rupees": 3000
},
"target": {
"rupees": 2000
}
}
}
}
}
}),
});
const body = await response.json();
console.log(response.status, body);{
"status": "success",
"data": {
"decision": "invalid",
"text": "Would not run: fix the problems first.",
"legs": [],
"net_premium": null,
"validation": {
"ok": false,
"deployable": false,
"schema_version": 1,
"migrated_from": null,
"errors": [
{
"field": "spec.entry.window",
"label": "When · Entry · Window",
"message": "The entry window must start before it ends.",
"code": "invalid_combination",
"needs_ack": false
}
],
"blockers": [],
"warnings": [],
"needs_ack": [],
"summary": "Sell 1 lot of an ATM call (weekly expiry) and sell 1 lot of an ATM put (weekly expiry) on NIFTY. Enter as soon as the window opens, between 14:00 and 10:00, Monday to Friday. Everything closes at 15:15. Size: the legs' lots times each account's multiplier, at most 10 lots per leg. Orders: hedges first when there are any, limit prices 0.5% beyond the market, chasing up to 1% slippage; if only some legs fill: close a sold option left without its hedge, keep the rest. Protection: stop at ₹3,000 loss, take ₹2,000 profit; no per-leg stops; at most 3 trades a day. No re-entries.",
"compiled_hash": null,
"components": [
"chain@1",
"combined@1",
"conditions@1",
"fixed_lots@1",
"limits@1",
"per_leg@1",
"protected_limit@1"
],
"warm_up": null
},
"analytics_note": "No analytics: no contracts were picked."
},
"error": null
}A condition's values now #
/v2/strategies/condition-values scope strategiesFor the strategy builder: the indicator values a condition reads and whether it is true right now, e.g.
More about this
RSI(14,15m) = 62.3. The condition is compiled, its symbols resolved and its values computed by the same code a running strategy uses (the document's entry / exit condition rules, the engine's indicators and evaluator), from recent history and the last price; nothing is saved and nothing trades. true_now is null when it cannot be known yet (reason): an indicator still warming up, no price yet, or a value only a running trade has (cycle.minutes). A crossing is true when it crossed at the last candle close; a hold is not waited for. Poll at most every few seconds while the builder shows it.
Body fields
- when string required
The condition as the document writes it:
u.rsi(14, '15m') > 60,crosses_above(u.ema(9, '5m'), u.ema(21, '5m')). - underlying object required
The underlying as the document's
spec.underlyingnames it. - on string | null
closeortick; absent:closewhen it reads an indicator, elsetick(as the document's default).One of: close, tick
- place string
Which part of the strategy the condition belongs to.
One of: entry, exit
- watch object
Other watched names and their document symbols (
{"bank": "NSE_INDEX:NIFTY BANK"},@future(NIFTY, 0));umay override the underlying's default.
Errors
| HTTP | Code | When |
|---|---|---|
| 400 | malformed_json | The body is not JSON, or does not fit the request shape (unknown or missing fields, wrong types). |
| 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). |
| 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 |
| 503 | service_unavailable | A dependency is temporarily unavailable, or the feature is off on this server; retry later. |
All fields (request and response schema)
Loading fields…
curl -X POST "https://cirrus.pocketful.in/v2/strategies/condition-values" \
-H "Authorization: token $CIRRUS_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"when": "cycle.minutes > 30",
"underlying": {
"name": "NIFTY",
"exchange": "NFO"
},
"place": "exit"
}'import os
import requests
response = requests.post(
"https://cirrus.pocketful.in/v2/strategies/condition-values",
headers={
"Authorization": f"token {os.environ['CIRRUS_API_KEY']}",
},
json={
"when": "cycle.minutes > 30",
"underlying": {
"name": "NIFTY",
"exchange": "NFO",
},
"place": "exit",
},
timeout=10,
)
print(response.status_code, response.json())const response = await fetch('https://cirrus.pocketful.in/v2/strategies/condition-values', {
method: 'POST',
headers: {
Authorization: `token ${process.env.CIRRUS_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"when": "cycle.minutes > 30",
"underlying": {
"name": "NIFTY",
"exchange": "NFO"
},
"place": "exit"
}),
});
const body = await response.json();
console.log(response.status, body);{
"status": "success",
"data": {
"true_now": null,
"status": "ok",
"reason": "cycle.minutes is known only while a trade is open",
"on": "tick",
"at": "2026-10-20T10:33:10.157Z",
"values": []
},
"error": null
}List strategy components #
/v2/strategies/components scope readEvery component type a strategy can use (sources, leg selectors, sizers, executors, risk rules, adjusters, notifiers), each with its parameters' JSON Schema, release status and phase, and whether a version using it can be deployed now.
More about this
The wizard, import and validation read this one list.
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/strategies/components" \
-H "Authorization: token $CIRRUS_API_KEY"import os
import requests
response = requests.get(
"https://cirrus.pocketful.in/v2/strategies/components",
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/strategies/components', {
method: 'GET',
headers: {
Authorization: `token ${process.env.CIRRUS_API_KEY}`,
},
});
const body = await response.json();
console.log(response.status, body);{
"status": "success",
"data": [
{
"id": "conditions@1",
"name": "conditions",
"major": 1,
"slots": [
"entry_source",
"exit_source"
],
"label": "Conditions",
"explain": "Indicators, prices and time, checked by the server.",
"group": "Built in",
"status": "preview",
"phase": "a",
"deployable": true,
"deprecated": false,
"sunset_after": null,
"needs": [],
"bare": true,
"params_schema": {
"type": "object",
"properties": {
"type": {
"const": "conditions@1"
},
"when": {
"anyOf": [
{
"type": "string",
"minLength": 1,
"maxLength": 2048,
"x-expression": true
},
{
"type": "null"
}
],
"title": "Condition",
"description": "Leave empty to enter as soon as the window opens (time-only).",
"default": null
},
"on": {
"enum": [
"close",
"tick"
],
"x-labels": {
"close": "At each candle close",
"tick": "On every price change"
},
"title": "Checked on",
"description": "close: at each bar close (default); tick: on every price change.",
"default": "close"
},
"hold_s": {
"type": "integer",
"minimum": 0,
"maximum": 3600,
"title": "Hold for",
"description": "Seconds the condition must stay true before it counts.",
"default": 0
}
},
"additionalProperties": false,
"required": [
"type"
],
"title": "Conditions",
"description": "Indicators, prices and time, checked by the server.",
"x-status": "preview",
"x-phase": "a"
}
},
{
"id": "manual@1",
"name": "manual",
"major": 1,
"slots": [
"entry_source",
"exit_source"
],
"label": "Manual",
"explain": "You decide when, with a button in the app.",
"group": "You",
"status": "preview",
"phase": "a",
"deployable": true,
"deprecated": false,
"sunset_after": null,
"needs": [],
"bare": true,
"params_schema": {
"type": "object",
"properties": {
"type": {
"const": "manual@1"
}
},
"additionalProperties": false,
"required": [
"type"
],
"title": "Manual",
"description": "You decide when, with a button in the app.",
"x-status": "preview",
"x-phase": "a"
}
}
],
"error": null
}Strategy capabilities #
/v2/strategies/capabilities scope readWhat the wizard may offer: slots and their components, enums and strike methods (with their phase), limits, timeframes, the indicator and variable catalogue (and whether each is available on this server), live brokers, underlyings (and underlying_details: per underlying its derivatives segment, lot size, strike step, expiry kinds listed, next expiries and current spot, from today's instrument list; mcx_underlyings, only while MCX strategies are on: every MCX commodity with a live future, priced off its nearest future), plain-English labels for every enum value, and the caller's usage (active deployments left).
More about this
With indicator conditions on, indicator_functions: the engine's indicator registry as /v2/triggers/capabilities lists it (labels, parameters with defaults and ranges, outputs, presets, timeframes, limits). switches: this server's switches, each true / false only (gallery, portfolio_risk, mcx, stock_fno, equity, stock_universe: a stock universe is configured). mcx_sessions: the MCX hours strategies are held to: late (bullion, energy, base metals: open, entry window close, square-off cap and default, the close in each US season, the commodities in it), early (every other commodity: the agri close and its square-off cap and default) and commodities: per commodity in mcx_underlyings its session and hours (empty while MCX is off).
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 |
curl "https://cirrus.pocketful.in/v2/strategies/capabilities" \
-H "Authorization: token $CIRRUS_API_KEY"import os
import requests
response = requests.get(
"https://cirrus.pocketful.in/v2/strategies/capabilities",
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/strategies/capabilities', {
method: 'GET',
headers: {
Authorization: `token ${process.env.CIRRUS_API_KEY}`,
},
});
const body = await response.json();
console.log(response.status, body);{
"status": "success",
"data": {
"schema_version": 1,
"schema_versions": [
1
],
"phase": "a",
"slots": [
{
"slot": "entry_source",
"label": "Entry",
"tag_key": "type",
"components": [
"conditions@1",
"manual@1"
]
},
{
"slot": "exit_source",
"label": "Exit",
"tag_key": "type",
"components": [
"conditions@1",
"manual@1"
]
}
],
"enums": {
"instruments": [
"option",
"future"
],
"sides": [
"buy",
"sell"
],
"option_types": [
"call",
"put"
],
"expiry_kinds": [
"weekly",
"monthly"
],
"premium_modes": [
"nearest",
"at_least"
],
"strike_methods": [
{
"by": "atm",
"label": "At the money",
"phase": "a",
"deployable": true
},
{
"by": "pct_otm",
"label": "Percent out of the money",
"phase": "a",
"deployable": true
}
],
"exchanges": [
"NFO",
"BFO"
],
"deployable_exchanges": [
"NFO",
"BFO"
],
"hedge_with": [
"future",
"roll_short"
],
"references": [
"future",
"spot"
],
"days": [
"mon",
"tue"
],
"expiry_day": [
"normal",
"skip"
],
"evaluate_on": [
"close",
"tick"
],
"reentry_on": [
"stop_loss",
"target"
],
"cost_models": [
"auto",
"options_india"
],
"orders": [
"auto",
"hedge_first"
],
"on_incomplete": [
"safest",
"keep_and_manage"
],
"on_leg_stop": [
"nothing",
"move_others_to_cost"
],
"combined_scopes": [
"account",
"deployment"
],
"bases": [
"rupees",
"pct_premium"
],
"on_breaker": [
"nothing",
"close_open_trade"
],
"notify_events": [
"entered",
"exited"
]
},
"limits": {
"max_document_bytes": 65536,
"max_depth": 20,
"max_entry_legs": 6,
"max_legs": 8,
"max_expr_bytes": 2048,
"max_indicator_calls": 20,
"max_timeframes": 3,
"max_adjustments": 10,
"max_watch_symbols": 5,
"max_notifiers": 4,
"max_name_chars": 60,
"max_description_chars": 500,
"max_tags": 10,
"max_accounts_per_deployment": 4,
"max_active_deployments": 3,
"max_cycles_per_day": 20,
"max_orders_per_day": 200,
"min_reentry_delay_s": 60,
"latest_mis_square_off": "15:20",
"session_open": "09:15",
"session_close": "15:30",
"min_multiplier": 0.1,
"max_multiplier": 10
},
"segments": [
{
"segment": "NSE",
"exchanges": [
"NSE",
"NFO"
],
"open": "09:15",
"close": "15:30",
"phase": "a",
"available": true,
"latest_square_off": "15:20",
"default_square_off": "15:15"
},
{
"segment": "BSE",
"exchanges": [
"BSE",
"BFO"
],
"open": "09:15",
"close": "15:30",
"phase": "a",
"available": true,
"latest_square_off": "15:20",
"default_square_off": "15:15"
}
],
"timeframes": [
"1m",
"3m"
],
"indicators": [
{
"name": "sma",
"args": "p, iv[, sym]",
"kind": "streaming",
"phase": "a",
"label": "Simple moving average",
"available": false
},
{
"name": "ema",
"args": "p, iv[, sym]",
"kind": "streaming",
"phase": "a",
"label": "Exponential moving average",
"available": false
}
],
"variables": [
{
"name": "cycle.pnl",
"phase": "a",
"available": false
},
{
"name": "cycle.minutes",
"phase": "a",
"available": true
}
],
"adjustment_triggers": [
{
"on": "loss",
"label": "Loss reaches (rupees)",
"fields": [
{
"key": "rupees",
"label": "Rupees",
"help": "",
"required": true,
"default": null
}
],
"phase": "a",
"deployable": true
},
{
"on": "loss_pct",
"label": "Loss reaches (percent of premium)",
"fields": [
{
"key": "pct",
"label": "Percent",
"help": "",
"required": true,
"default": null
}
],
"phase": "a",
"deployable": true
}
],
"adjustment_targets": [
{
"target": "tested",
"label": "The leg being tested",
"explain": "the sold option whose price rose most against its entry, from fresh prices",
"adjusters": [
"close_leg@1",
"reduce@1"
]
},
{
"target": "sold",
"label": "All sold legs",
"explain": "every open sold option (hedges stay)",
"adjusters": [
"close_leg@1",
"reduce@1"
]
}
],
"labels": {
"instruments": [
{
"value": "option",
"label": "Option"
},
{
"value": "future",
"label": "Future"
}
],
"sides": [
{
"value": "buy",
"label": "Buy"
},
{
"value": "sell",
"label": "Sell"
}
],
"signal_instruments": [
{
"value": "signal",
"label": "As the alert says"
},
{
"value": "stock",
"label": "The stock"
}
],
"signal_sides": [
{
"value": "signal",
"label": "As the alert says"
},
{
"value": "buy",
"label": "Buy"
}
],
"option_types": [
{
"value": "call",
"label": "Call"
},
{
"value": "put",
"label": "Put"
}
],
"stock_cover": [
{
"value": "strategy",
"label": "Shares this strategy bought"
},
{
"value": "any",
"label": "Any shares held"
}
],
"expiry_kinds": [
{
"value": "weekly",
"label": "Weekly"
},
{
"value": "monthly",
"label": "Monthly"
}
],
"premium_modes": [
{
"value": "nearest",
"label": "Nearest to"
},
{
"value": "at_least",
"label": "At least"
}
],
"exchanges": [
{
"value": "NFO",
"label": "NSE futures & options"
},
{
"value": "BFO",
"label": "BSE futures & options"
}
],
"references": [
{
"value": "future",
"label": "Futures price"
},
{
"value": "spot",
"label": "Spot (index or stock) price"
}
],
"days": [
{
"value": "mon",
"label": "Monday"
},
{
"value": "tue",
"label": "Tuesday"
}
],
"expiry_day": [
{
"value": "normal",
"label": "Trade as usual"
},
{
"value": "skip",
"label": "Skip expiry day"
}
],
"evaluate_on": [
{
"value": "close",
"label": "At each candle close"
},
{
"value": "tick",
"label": "On every price change"
}
],
"reentry_on": [
{
"value": "stop_loss",
"label": "After a stop-loss"
},
{
"value": "target",
"label": "After a target"
}
],
"cost_models": [
{
"value": "auto",
"label": "Automatic"
},
{
"value": "options_india",
"label": "Indian options charges"
}
],
"orders": [
{
"value": "auto",
"label": "Automatic"
},
{
"value": "hedge_first",
"label": "Hedges first"
}
],
"on_incomplete": [
{
"value": "safest",
"label": "Do the safest thing"
},
{
"value": "keep_and_manage",
"label": "Keep and manage what filled"
}
],
"on_leg_stop": [
{
"value": "nothing",
"label": "Do nothing"
},
{
"value": "move_others_to_cost",
"label": "Move the other legs' stops to cost"
}
],
"combined_scopes": [
{
"value": "account",
"label": "Each account"
},
{
"value": "deployment",
"label": "The whole deployment"
}
],
"bases": [
{
"value": "rupees",
"label": "Rupees"
},
{
"value": "pct_premium",
"label": "% of premium"
}
],
"on_breaker": [
{
"value": "nothing",
"label": "Do nothing"
},
{
"value": "close_open_trade",
"label": "Close the open trade"
}
],
"leg_targets": [
{
"value": "tested",
"label": "The leg being tested"
},
{
"value": "sold",
"label": "All sold legs"
}
],
"notify_events": [
{
"value": "entered",
"label": "Entered a trade"
},
{
"value": "exited",
"label": "Exited a trade"
}
],
"hedge_with": [
{
"value": "future",
"label": "With futures"
},
{
"value": "roll_short",
"label": "By rolling the sold leg"
}
],
"timeframes": [
{
"value": "1m",
"label": "1 minute"
},
{
"value": "3m",
"label": "3 minutes"
}
],
"deployable_exchanges": [
{
"value": "NFO",
"label": "NSE futures & options"
},
{
"value": "BFO",
"label": "BSE futures & options"
}
],
"strike_methods": [
{
"value": "atm",
"label": "At the money"
},
{
"value": "pct_otm",
"label": "Percent out of the money"
}
],
"slots": [
{
"value": "entry_source",
"label": "Entry"
},
{
"value": "exit_source",
"label": "Exit"
}
],
"components": [
{
"value": "conditions@1",
"label": "Conditions"
},
{
"value": "manual@1",
"label": "Manual"
}
],
"segments": [
{
"value": "NSE",
"label": "NSE: stocks, index futures and options"
},
{
"value": "BSE",
"label": "BSE: stocks, index futures and options"
}
],
"indicators": [
{
"value": "sma",
"label": "Simple moving average"
},
{
"value": "ema",
"label": "Exponential moving average"
}
],
"template_categories": [
{
"value": "income",
"label": "Earn from a quiet market"
},
{
"value": "hedged_income",
"label": "Earn with protection"
}
],
"risk": [
{
"value": "limited",
"label": "Limited loss"
},
{
"value": "unlimited",
"label": "Unlimited loss"
}
]
},
"live_brokers": [
"zerodha"
],
"underlyings": [
"NIFTY",
"BANKNIFTY"
],
"underlying_details": [
{
"name": "NIFTY",
"exchange": "NFO",
"lot_size": 75,
"strike_step": 50,
"expiry_kinds": [
"weekly",
"monthly"
],
"next_expiry": "2030-12-19",
"next_monthly_expiry": "2030-12-26",
"spot": 25000,
"spot_stale": false,
"note": null
},
{
"name": "BANKNIFTY",
"exchange": null,
"lot_size": null,
"strike_step": null,
"expiry_kinds": [],
"next_expiry": null,
"next_monthly_expiry": null,
"spot": null,
"spot_stale": false,
"note": "No live options or futures in today's instrument list."
}
],
"mcx_sessions": {
"late": {
"open": "09:00",
"window_close": "23:30",
"latest_square_off": "23:00",
"default_square_off": "22:45",
"close_us_dst": "23:30",
"close_us_standard": "23:55",
"commodities": [
"GOLD",
"GOLDM"
]
},
"early": {
"open": "09:00",
"window_close": "17:00",
"latest_square_off": "16:30",
"default_square_off": "16:15",
"close": "17:00"
},
"commodities": []
},
"switches": {
"gallery": true,
"portfolio_risk": true,
"mcx": false,
"stock_fno": false,
"equity": false,
"stock_universe": false
},
"engine_connected": false,
"usage": {
"active_deployments": 0,
"active_deployments_left": 3
}
},
"error": null
}Strategy document JSON Schema #
/v2/strategies/schema/{schema_file} no authThe JSON Schema (draft 2020-12) of the strategy document format, as {schema_version}.json (e.g.
More about this
1.json), so editors can validate and autocomplete documents. The raw schema, not the usual envelope; no credential needed.
Parameters
- schema_file path · string required
{schema_version}.json, e.g.1.json.
Errors
| HTTP | Code | When |
|---|---|---|
| 404 | schema_not_found | No such strategy document format on this server. |
curl "https://cirrus.pocketful.in/v2/strategies/schema/<schema_file>"import os
import requests
response = requests.get(
"https://cirrus.pocketful.in/v2/strategies/schema/<schema_file>",
timeout=10,
)
print(response.status_code, response.json())const response = await fetch('https://cirrus.pocketful.in/v2/strategies/schema/<schema_file>', {
method: 'GET',
});
const body = await response.json();
console.log(response.status, body);List strategy templates #
/v2/strategies/templates scope readReady strategies to start from (the wizard's goal step): each with a plain-English name, the goal it serves, a one-line description, a category, a risk badge (limited or unlimited loss), the components and indicators it needs, its summary, whether this server can deploy it now (with the blockers when not), and the document to create from.
More about this
Templates are data in the component registry: a new one appears here without an app release.
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/strategies/templates" \
-H "Authorization: token $CIRRUS_API_KEY"import os
import requests
response = requests.get(
"https://cirrus.pocketful.in/v2/strategies/templates",
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/strategies/templates', {
method: 'GET',
headers: {
Authorization: `token ${process.env.CIRRUS_API_KEY}`,
},
});
const body = await response.json();
console.log(response.status, body);{
"status": "success",
"data": [
{
"id": "short_strangle",
"name": "Short strangle",
"goal": "Earn from a quiet market",
"description": "Sell an out-of-the-money call and put each morning; keep the premium while the market stays in a range.",
"category": "income",
"category_label": "Earn from a quiet market",
"risk": "unlimited",
"risk_label": "Unlimited loss",
"components": [
"chain@1",
"combined@1"
],
"indicators": [],
"needs": [],
"summary": "Sell 1 lot of a 0.2 delta call (weekly expiry) and sell 1 lot of a 0.2 delta put (weekly expiry) on NIFTY. Enter as soon as the window opens, between 09:30 and 13:30, Monday to Friday. Everything closes at 15:15. Size: the legs' lots times each account's multiplier, at most 10 lots per leg. Orders: hedges first when there are any, limit prices 0.5% beyond the market, chasing up to 1% slippage; if only some legs fill: close a sold option left without its hedge, keep the rest. Protection: stop at ₹3,000 loss, take ₹2,000 profit; no per-leg stops; at most 3 trades a day. No re-entries.",
"deployable": true,
"blockers": [],
"warnings": [],
"document": {
"$schema": "/v2/strategies/schema/1.json",
"schema_version": 1,
"kind": "cirrus.strategy",
"metadata": {
"name": "NIFTY short strangle",
"description": "Sell a 0.2 delta call and put at 09:30 with a combined stop.",
"tags": [
"nifty",
"weekly"
]
},
"spec": {
"underlying": {
"name": "NIFTY",
"exchange": "NFO",
"reference": "future"
},
"watch": {},
"positional": false,
"legs": {
"selector": "chain@1",
"items": [
{
"id": "sc",
"instrument": "option",
"side": "sell",
"option_type": "call",
"expiry": {
"kind": "weekly",
"n": 0
},
"strike": {
"by": "delta",
"delta": 0.2
},
"lots": 1,
"hedge_for": null,
"risk": null,
"liquidity": {
"max_spread_pct": 3,
"min_oi": 0,
"min_premium": 1
}
},
{
"id": "sp",
"instrument": "option",
"side": "sell",
"option_type": "put",
"expiry": {
"kind": "weekly",
"n": 0
},
"strike": {
"by": "delta",
"delta": 0.2
},
"lots": 1,
"hedge_for": null,
"risk": null,
"liquidity": {
"max_spread_pct": 3,
"min_oi": 0,
"min_premium": 1
}
}
]
},
"entry": {
"source": {
"type": "conditions@1",
"when": null,
"on": "close",
"hold_s": 0
},
"window": {
"start": "09:30",
"end": "13:30"
},
"skip_first_min": 0,
"skip_last_min": 0,
"days": [
"mon",
"tue"
],
"expiry_day": "normal",
"active_from": null,
"active_until": null
},
"exit": {
"sources": [],
"square_off": "15:15",
"expiry_day_exit": null,
"max_hold_min": null
},
"sizing": {
"type": "fixed_lots@1",
"max_lots": 10
},
"execution": {
"type": "protected_limit@1",
"order": "auto",
"hedge_wait_ms": 5000,
"stagger_ms": 0,
"buffer_pct": 0.5,
"chase": {
"every_ms": 1000,
"max_slippage_pct": 1,
"max_attempts": 10
},
"on_incomplete": "safest",
"retry_next_signal": null
},
"risk": {
"per_leg": {
"type": "per_leg@1",
"stop_loss": null,
"target": null,
"trail": null,
"on_leg_stop": "nothing",
"at_broker": false
},
"combined": {
"type": "combined@1",
"scope": "account",
"basis": "rupees",
"stop_loss": {
"rupees": 3000
},
"target": {
"rupees": 2000
},
"breakeven": null,
"trail": null,
"lock_ladder": [],
"deployment": null
},
"limits": {
"type": "limits@1",
"max_loss_per_day": null,
"max_profit_per_day": null,
"max_loss_per_week": null,
"max_cycles_per_day": 3,
"max_orders_per_day": 50,
"exit_on_limit": true,
"on_breaker": "nothing"
}
},
"reentry": {
"on": [],
"max": 0,
"delay_s": 300,
"require_fresh_signal": true,
"same_direction_only": true,
"until": null
},
"adjustments": [],
"notify": [],
"costs": {
"model": "auto",
"slippage_pct": null
}
}
}
},
{
"id": "rsi_strangle",
"name": "RSI-gated strangle",
"goal": "Earn from a quiet market",
"description": "Sell a 0.2 delta strangle only when the 5-minute RSI says the market is calm (between 45 and 60).",
"category": "income",
"category_label": "Earn from a quiet market",
"risk": "unlimited",
"risk_label": "Unlimited loss",
"components": [
"app@1",
"chain@1"
],
"indicators": [
"rsi"
],
"needs": [],
"summary": "Sell 1 lot of a 0.2 delta call (weekly expiry) and sell 1 lot of a 0.2 delta put (weekly expiry) on NIFTY. Enter when rsi(14, '5m') > 45 && rsi(14, '5m') < 60 (checked at each bar close), between 09:30 and 13:30, Monday to Friday, not on expiry day. Everything closes at 15:15. Size: the legs' lots times each account's multiplier, at most 4 lots per leg. Orders: all legs together, limit prices 0.5% beyond the market, chasing up to 1% slippage; if only some legs fill: keep and manage whatever filled. Protection: stop at ₹3,000 loss, take ₹2,000 profit, move the stop to cost at ₹1,000 profit; each leg: stop at 50% against it; when a leg stops, move the others' stops to cost; stops are also placed at the broker; at most 3 trades and ₹6,000 loss a day. Try again up to 2 times after a stop, 10 minutes later, until 13:30, only on a fresh signal. Updates to app: entered, exited, stops, adjusted, needs attention and daily summary.",
"deployable": false,
"blockers": [
{
"field": "spec.entry.source.when",
"label": "When · Entry",
"message": "`rsi(14, '5m') > 45 && rsi(14, '5m') < 60` uses rsi, which isn't available on this server yet (indicator conditions are coming). This strategy can be saved but not deployed until then.",
"code": "not_available_yet",
"needs_ack": false
}
],
"warnings": [],
"document": {
"$schema": "/v2/strategies/schema/1.json",
"schema_version": 1,
"kind": "cirrus.strategy",
"metadata": {
"name": "NIFTY RSI strangle",
"description": "Sell a 0.2 delta strangle when NIFTY's 5-minute RSI is between 45 and 60.",
"tags": [
"weekly",
"nifty"
]
},
"spec": {
"underlying": {
"name": "NIFTY",
"exchange": "NFO",
"reference": "future"
},
"watch": {
"u": "NSE_INDEX:NIFTY 50"
},
"positional": false,
"legs": {
"selector": "chain@1",
"items": [
{
"id": "sc",
"instrument": "option",
"side": "sell",
"option_type": "call",
"expiry": {
"kind": "weekly",
"n": 0
},
"strike": {
"by": "delta",
"delta": 0.2
},
"lots": 1,
"hedge_for": null,
"risk": null,
"liquidity": {
"max_spread_pct": 3,
"min_oi": 0,
"min_premium": 1
}
},
{
"id": "sp",
"instrument": "option",
"side": "sell",
"option_type": "put",
"expiry": {
"kind": "weekly",
"n": 0
},
"strike": {
"by": "delta",
"delta": 0.2
},
"lots": 1,
"hedge_for": null,
"risk": null,
"liquidity": {
"max_spread_pct": 3,
"min_oi": 0,
"min_premium": 1
}
}
]
},
"entry": {
"source": {
"type": "conditions@1",
"when": "rsi(14, '5m') > 45 && rsi(14, '5m') < 60",
"on": "close",
"hold_s": 0
},
"window": {
"start": "09:30",
"end": "13:30"
},
"skip_first_min": 0,
"skip_last_min": 0,
"days": [
"mon",
"tue"
],
"expiry_day": "skip",
"active_from": null,
"active_until": null
},
"exit": {
"sources": [],
"square_off": "15:15",
"expiry_day_exit": null,
"max_hold_min": null
},
"sizing": {
"type": "fixed_lots@1",
"max_lots": 4
},
"execution": {
"type": "protected_limit@1",
"order": "simultaneous",
"hedge_wait_ms": 5000,
"stagger_ms": 0,
"buffer_pct": 0.5,
"chase": {
"every_ms": 1000,
"max_slippage_pct": 1,
"max_attempts": 10
},
"on_incomplete": "keep_and_manage",
"retry_next_signal": 0
},
"risk": {
"per_leg": {
"type": "per_leg@1",
"stop_loss": {
"pct": 50
},
"target": null,
"trail": null,
"on_leg_stop": "move_others_to_cost",
"at_broker": true
},
"combined": {
"type": "combined@1",
"scope": "account",
"basis": "rupees",
"stop_loss": {
"rupees": 3000
},
"target": {
"rupees": 2000
},
"breakeven": {
"at_rupees": 1000
},
"trail": null,
"lock_ladder": [],
"deployment": null
},
"limits": {
"type": "limits@1",
"max_loss_per_day": 6000,
"max_profit_per_day": null,
"max_loss_per_week": null,
"max_cycles_per_day": 3,
"max_orders_per_day": 50,
"exit_on_limit": true,
"on_breaker": "nothing"
}
},
"reentry": {
"on": [
"stop_loss"
],
"max": 2,
"delay_s": 600,
"require_fresh_signal": true,
"same_direction_only": true,
"until": "13:30"
},
"adjustments": [],
"notify": [
{
"type": "app@1",
"events": [
"entered",
"exited"
]
}
],
"costs": {
"model": "auto",
"slippage_pct": null
}
}
}
}
],
"error": null
}Today's stock safety checks #
/v2/strategies/stock-status scope readFor each stock asked (1 to 50 NSE symbols), the checks the engine runs before a new stock entry today: fno_ban (NSE's F&O ban list; options and futures), ex_date (today's corporate actions; shares and F&O) and circuit (the price within circuit_gap_pct of a circuit limit; shares).
More about this
Each check is clear, blocked or unknown (a list not written for today, stale or unreadable, a stock not in today's list, or no fresh price: new entries wait, as in the engine), with the engine's waiting code and a reason. new_entries is the worst of the three. Open trades keep every exit whatever these say.
Parameters
- symbols query · string required
NSE symbols, comma separated (1 to 50), e.g.
RELIANCE,TCS. - circuit_gap_pct query · number (double)
The circuit gap to check (0.5 to 10 percent of the limit, default 2): a strategy's
stock.circuit_gap_pct.
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). |
| 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/strategies/stock-status?symbols=<symbols>" \
-H "Authorization: token $CIRRUS_API_KEY"import os
import requests
response = requests.get(
"https://cirrus.pocketful.in/v2/strategies/stock-status",
headers={
"Authorization": f"token {os.environ['CIRRUS_API_KEY']}",
},
params={
"symbols": "<symbols>",
},
timeout=10,
)
print(response.status_code, response.json())const response = await fetch('https://cirrus.pocketful.in/v2/strategies/stock-status?symbols=<symbols>', {
method: 'GET',
headers: {
Authorization: `token ${process.env.CIRRUS_API_KEY}`,
},
});
const body = await response.json();
console.log(response.status, body);{
"status": "success",
"data": {
"trading_day": "2026-10-26",
"circuit_gap_pct": 2,
"stocks": [
{
"name": "RELIANCE",
"new_entries": "blocked",
"fno_ban": {
"state": "clear",
"code": null,
"reason": null
},
"ex_date": {
"state": "blocked",
"code": "ex_date",
"reason": "RELIANCE goes ex-date today"
},
"corporate_actions": [
"Dividend - Rs 10 Per Share"
],
"circuit": {
"state": "clear",
"code": null,
"reason": null,
"ltp": 1400,
"lower_circuit": 1260,
"upper_circuit": 1540
}
},
{
"name": "TCS",
"new_entries": "blocked",
"fno_ban": {
"state": "clear",
"code": null,
"reason": null
},
"ex_date": {
"state": "clear",
"code": null,
"reason": null
},
"corporate_actions": [],
"circuit": {
"state": "blocked",
"code": "circuit",
"reason": "TCS: it trades at 3500.00, within 2% of its upper circuit 3520.00",
"ltp": 3500,
"lower_circuit": 3150,
"upper_circuit": 3520
}
},
{
"name": "IDEA",
"new_entries": "blocked",
"fno_ban": {
"state": "blocked",
"code": "fno_ban",
"reason": "IDEA is in NSE's F&O ban period today"
},
"ex_date": {
"state": "clear",
"code": null,
"reason": null
},
"corporate_actions": [],
"circuit": {
"state": "unknown",
"code": "circuit_unknown",
"reason": "IDEA: its circuit limits or price are not known",
"ltp": null,
"lower_circuit": null,
"upper_circuit": null
}
}
]
},
"error": null
}