Skip to content
Cirrus home
Sign in

Changelog

Every change you can see, newest first. Additive changes never break a client that ignores unknown fields; breaking ones are announced before they ship.

  1. Additive

    Quantities that are not whole lots are rounded down

    POST /v1/orders, order modify, POST /v1/gtt, PATCH /v1/gtt/{trigger_id} and POST /v1/orders/bracket: a quantity in units that is not a multiple of the lot size is rounded down to whole lots instead of refused with quantity must be a multiple of lot size N (lot 20, quantity 28 sends 20). POST /v1/orders reports the units placed in each result's quantity and records them in the order's activity; modify, GTT and bracket answers carry no quantity. With an account multiplier, the units are scaled first and then rounded down per account (as before). Less than one lot is still refused with 422 invalid_request, now worded quantity is smaller than one lot (lot size N) (field orders[i].quantity on place, quantity elsewhere). POST /v1/positions/exit is unchanged: a position that is not whole lots is still refused with its remainder.

  2. Additive

    strategy-level risk limits never flatten

    Where strategy-level risk limits are enabled (OMS_TRIGGERS_V2_STRATEGY_RISK, off), POST /v2/triggers, PATCH /v2/triggers/{trigger_id} and POST /v2/triggers/validate accept them only with on_breach pause or notify; flatten (and no on_breach, which means flatten) is a 422 spec.on_breach. Nothing changes while the switch is off, and account / user limits are unchanged.

  3. Additive

    Nubra adapter; its conformance pack

    Nubra accounts (Broker Nubra; ACCOUNT_LIST_DATA Access Token and the new Device ID, written by broker-auth's Nubra login; a static IP is needed) are served like other brokers: same request and response shapes, GET /v1/accounts reports them supported, updates polling. Orders go to Nubra's REST V3 intent orders (NUBRA_BASE_URL, default https://api.nubra.io); MTF, GTT, bracket / cover and position conversion are refused as for any broker without them. No existing field, type, enum value or status code changes. The broker conformance corpus gains a new nubra pack at replay_green: synthetic recordings of the order book, trades, positions, holdings, funds, place / modify / cancel, margin and the 400 refusal from Nubra's API docs, their expectations, the documented order statuses frozen, and that 400 ("Enter valid inputs to proceed.") classed invalid_field. Live replies (2026-10-13/14) are read as Nubra sends them: positions netQty / buyQty / sellQty, holdings qty (the docs name netQuantity / quantity; both are read), a delivery buy's blocked cash totalMarginBlockedNew; holdings carry the ISIN. Nubra's ProtectionProfile (and the pack's [protection]) is every fact unverified, like Groww. One 2026-10-13 margin quote recording is revoked (a float that hashed differently by build features); a live margin quote replaces it.

  4. Additive

    strategy-level risk limits

    POST /v2/triggers, PATCH /v2/triggers/{trigger_id} and POST /v2/triggers/validate accept a risk_limit with level: strategy (and a strategy) when the server enables it (switch OMS_TRIGGERS_V2_STRATEGY_RISK, off); elsewhere the answer stays 422 spec.level with today's message. While the strategy's numbers cannot be trusted the limit does not fire and new entries of the strategy are refused (the trigger's entry shows failed with the reason); nothing is flattened. Nothing changes while the switch is off.

  5. Additive

    strategy on order rows

    Order rows (OrderData: GET /v1/orders, GET /v1/orders/{order_tag}, every response and /v1/stream message that embeds an order) may carry an optional strategy: the strategy of the v2 trigger that placed the order. Absent otherwise. Order webhooks are unchanged.

  6. Additive

    v2 entry updates and journey kinds (follow-up)

    Recorded for commit 171eaad, which shipped them without a Contract-Change: trailer: with OMS_TRIGGERS_V2_ENTER on, the owner's trigger_update (stream and opted-in webhooks) carries one update per entry outcome in the existing shape (from = to, no actions, reason in words, journey set, event_id {trigger_id}:entry:{event_id}:{leg}:{kind}). The journey (source entry) also gains entry_resting, entry_resting_late, entry_rebase_skipped and entry_held_by_server. Nothing changes while the switch is off.

  7. Additive

    v2 trigger entries

    GET /v2/triggers/{trigger_id} and GET /v2/triggers show an optional entry object (TriggerV2Entry) on triggers whose entry the server placed (switch OMS_TRIGGERS_V2_ENTER, off): status (sending, accepted, filled, partially_filled, failed, outcome_unknown; other values may appear), filled_qty, avg_price, order_ids, reason. Absent otherwise; nothing changes while the switch is off. With it on, the owner's trigger_update push (stream and opted-in webhooks) also carries one update per entry outcome, same shape, from = to, event_id {trigger_id}:entry:{event_id}:{leg}:{kind}, and the journey gains source entry (kinds entry_sent, entry_adopted, entry_filled, entry_filled_late, entry_failed, entry_unknown, entry_not_protected). New example entered on get_trigger_v2.

  8. Additive

    recorded error examples for v2 triggers

    Documentation only: recorded examples for documented error responses of the /v2/triggers operations that had none. not_enabled (403) on get_trigger_v2, cancel_trigger_v2, modify_trigger_v2, pause_trigger_v2, resume_trigger_v2, validate_trigger_v2, get_trigger_capabilities_v2, get_trigger_journey_v2; engine_unavailable (503) on cancel, modify, pause and resume; ended (409) on resume; client_id (400 malformed_json) on modify and validate; invalid_query (400) on the journey. Existing examples and response shapes unchanged.

  9. Additive

    partly filled exits in the v2 trigger journey

    The journey (GET /v2/triggers/{trigger_id}/journey, unreleased) records a new kind exit_partly_filled (source exit) when the exit worker reads part of a v2 exit filled: order_id (the order tags), filled_quantity, quantity (the exit's total) and average_price. One entry per distinct filled quantity of a placement. The kind description in openapi.json lists it. The legacy trigger history is unchanged.

  10. Additive

    Dhan / Zerodha margin requests, Upstox account record recorded

    No request or response changes. New Dhan (BSE F&O, NSE equity) and Zerodha margin-quote request shapes and an Upstox account record seen in production join the conformance corpus, so they no longer alert.

  11. Additive

    Pocketful tenant shapes recorded

    No request or response changes. New Pocketful order book, positions, trades, place request and stream frame shapes seen on the Pocketful tenant join the conformance corpus, so they no longer alert.

  12. Additive

    Paper, Alice Blue, Dhan, 5Paisa, Zerodha shapes recorded

    No request or response changes. New broker reply shapes seen in production (Paper's first full trading session, new Alice Blue / Dhan / 5Paisa / Zerodha order rows) join the conformance corpus, so they no longer alert. Alice Blue "No holdings found" is read as an empty holdings list, and a Paper "Instrument data unavailable" refusal as a passing failure (nothing was placed).

  13. Additive

    Pocketful MTF orders and positions are listed

    Pocketful orders and positions with product MTF (placed in Pocketful's own app) are now listed with product: "MTF", an existing value; they were left out of the order book and positions. Placing MTF through Pocketful is still refused. A Pocketful cancel or modify refused by the gateway's HTML 403 page is a passing gateway block, as reads and placements already are.

  14. Additive

    dead Motilal / 5Paisa logins pause; Dhan margin opening

    A Motilal Oswal login the broker no longer accepts (HTTP 200 MO2032 "Invalid AccessToken") and a 5Paisa login it refuses (head status 2 "Invalid head parameters.") now pause the account and ask for a new login, as an expired session already does at every other broker, instead of being polled every few seconds. Dhan's margin opening_balance is now used + available (both include collateral), as for Alice Blue and 5Paisa; it was the cash-only sodLimit, which made utilised read far above 100. Refusals newly classed from production replies (Zerodha, 5Paisa, Upstox, Pocketful, Alice Blue, Tradejini) keep the reasons users already see. No field, enum value or status code changes.

  15. Additive

    v2 trigger expression reference

    Documentation only: docs/api/trigger-expressions.md (variables incl. prev.* and legs[i].*, functions incl. crosses_above / crosses_below, operators, limits, error messages, examples per kind) and the expression summary in validate_trigger_v2's description. No shape changes.

  16. Additive

    v2 triggers show refused commands and ended protection

    GET /v2/triggers, GET /v2/triggers/{trigger_id} (and trigger_update's protection):

    • new optional last_result (TriggerV2LastResult: op, command_id, outcome applied | duplicate | rejected, reason, at): the engine's answer to the last command it handled, so a refused modify, pause, resume or cancel shows on the trigger with its reason. Absent until the engine answered one (recorded examples unchanged).
    • an ended trigger's protection parts the server held show status ended (an existing value) instead of held_by_server; broker parts keep what the broker shows.
  17. Additive

    exits and broker protection in the v2 trigger journey

    The journey (GET /v2/triggers/{trigger_id}/journey, unreleased) also records exit orders (source exit: exit_placed with the order tags and quantity, exit_filled, exit_refused, exit_unfilled, exit_repriced, exit_retried, exit_failed when dead-lettered) and the broker protection (source protection: gtt_placed, gtt_modified, gtt_cancelled, gtt_triggered, readback_matched, readback_mismatch, readback_unknown, degraded). Reasons are cut to 200 characters; no broker reply bodies. No other shape changes.

  18. Additive

    v2 engine notes in the trigger journey

    The trigger runtime publishes notes about a trigger (an input not known, a stale tick ignored, the evaluation budget run out, a stored trigger quarantined) as new record kind trg_note on core NATS {ENV}.trg.note.{bucket}.{user}.{trigger_id} (no stream), at most the first per trigger, kind and minute and 1000 per worker per minute. The api journals them (source engine, kind note) through one queue group. Fixtures and a reader test in

  19. Additive

    v2 trigger journey

    New operation GET /v2/triggers/{trigger_id}/journey (get_trigger_journey_v2, read scope; users /v2/triggers is enabled for): the caller's trigger's journey, newest first (TriggerV2Journey: entries[], next_cursor), ?limit= (1-200, default 50), ?cursor=, ?source= (api, engine, result, exit, protection); 400 invalid_query, 404 trigger_not_found (also for another user's trigger), 503. Each entry (TriggerV2JourneyEntry): entry_id, at, seq, source, kind, summary, actor (api entries: user, credential, API key id, partner client id, client IP), details (a modify's spec diff, a transition's explanation and values, a refusal's reason, ...). Kept 90 days by default (OMS_TRIGGERS_V2_JOURNEY_DAYS), also after the trigger ended. TriggerV2Update (stream / webhook trigger_update, opt-in) gains journey: the entry the update adds. New stored record kind trg2_journey (Mongo, append-only; fixtures and a

  20. Additive

    v2 engine events say why (explain)

    Stored record trg_event ({ENV}.trg.evt.…) gains an optional explain {condition, source, values} on transitions a condition or a level decided: the spec path (stop_loss.when, target.when, entry.when, when, targets[n].price, stop_loss, mtm_floor, profit_lock.floor), the condition as written (at most 256 bytes) and up to 16 values read at that input (ltp, prev.ltp, sl_px, ...). Absent on older events and on other transitions; readers ignore it. Shadow events (trg_shadow_event) are unchanged. No REST, webhook or stream shape changes.