Skip to main content

The nine order gates

Who it’s for
Anyone configuring risk on a deployed strategy
Assumes
You have read How risk is enforced
Applies to
Every order from a deployed strategy, paper and live alike

Every order a deployed strategy tries to place passes through a pipeline of nine validators before it reaches a broker. This page is the complete reference: what each one measures, what unit its limit is in, whether it is on by default, and exactly what happens when it trips.

Read the badge on each gate. Off until you set it means the rule does not run.


How the pipeline evaluates

An order arrives, each applicable validator runs in sequence, and the first violation stops the order. The order is rejected with the violating rule's name, its reason code, the current value and the limit.

Two properties of the pipeline are worth understanding before the gates themselves:

Order shape is checked before metrics are loaded. max_order_value and per_trade_risk need nothing but the order itself. The gates that need position counts, today's P&L or capital figures declare that requirement, and those reads happen only if an order survives the cheap checks. A malformed order never costs a database query.

Entries are gated; exits are not. Every gate that caps exposure or velocity applies to entries only. A strategy at its position cap must still be able to close a position. This is the single most important asymmetry in the system. And its consequence is that a tripped loss cap leaves your open position running to its own stop.


Gate 1: Maximum order value

max_order_valueOff until you set it
Measures
This single order's notional, quantity × price
Unit
Quote currency (₹ for Indian equity, USDT/USDC for crypto spot)

An absolute ceiling on how large any one order can be. It is a pure order-shape check: it needs no market state, no position history and no capital read, which is why it runs first.

When it trips: the order is rejected with MAX_ORDER_VALUE_EXCEEDED. The strategy keeps running; the next signal that produces a smaller order is placed normally.

Set it when you want a hard cap on single-order size regardless of what the sizing logic computes, the protection against a sizing bug, a bad price tick, or a strategy that scaled up more than you intended.

Gate 2: Per-trade risk

per_trade_riskOff until you set it
Measures
|entry price - stop price| × quantity, against a percent of allocated capital
Unit
Percent of allocated capital

The gate that makes "never risk more than 1% on a trade" a real constraint rather than an intention. It computes the actual money at risk between entry and stop and compares it to a budget derived from your allocation.

It only applies to an entry that carries a usable stop price. A market entry with no stop has no measurable risk distance, so the gate cannot evaluate and skips rather than guessing. That is a real limitation worth knowing: a strategy with no stop-loss is not protected by this gate.

When it trips: the order is rejected with PER_TRADE_RISK_EXCEEDED.

Also skips when the strategy's allocated capital is unknown. It cannot resolve a percent into an amount, and it will not invent one.

Gate 3: Maximum open positions

max_open_positionsOff until you set it
Measures
Concurrent open entry exposure: FIFO open buy lots plus fully-unfilled working buy entries
Unit
Count of positions

Caps how many positions the strategy can hold at once. Working orders count, an entry sitting unfilled on the book occupies a slot, so the cap cannot be exceeded by racing two entries.

When it trips: the entry is rejected with MAX_OPEN_POSITIONS_REACHED. Exits are always permitted, so the strategy can close a position to free a slot and continue.

Historically this control was called max_trades in the strategy configuration, and that key is still read for compatibility.

Gate 4: Maximum trades per day

max_trades_per_dayOff until you set it
Measures
New entries opened so far today
Unit
Count of entries per UTC day

A velocity brake. Counts entries, not round trips, and resets at UTC midnight. Not at the local market open. For an Indian equity strategy this means the counter boundary falls at 05:30 IST, inside the overnight gap rather than at the session open.

When it trips: the entry is rejected with MAX_TRADES_PER_DAY_REACHED until the next UTC day.

Set it when you want a bound on how much a strategy can churn, the protection against a signal that starts firing every candle in a choppy market.

Gate 5: Maximum consecutive losses

max_consecutive_lossesOff until you set it
Measures
Losing round-trips in an unbroken run
Unit
Count of consecutive losing round-trips

Halts new entries after N losing trades in a row. The streak is derived from the realised sell-P&L timeline and resets on the next winning round-trip. You do not have to clear it manually.

When it trips: the entry is rejected with MAX_CONSECUTIVE_LOSSES_REACHED.

How trading resumes: this gate works with a cooldown. Rather than blocking until a win that cannot happen while entries are blocked, the halt is time-boxed: after cooldown_bars_after_loss bars at the strategy's timeframe have elapsed since the losing trade, the violation is suppressed and entries resume. The default is 3 bars when you have not set a value. See Alerts & breaches.

Set it when you want the strategy to stand down in conditions it is clearly misreading, rather than paying for the same mistake ten times.

Gate 6: Daily loss limit

daily_loss_limitOff until you set it
Measures
Today's REALISED profit and loss, against a percent of allocated capital
Unit
Percent of allocated capital

The classic circuit breaker: stop trading once today has cost more than a set fraction of the strategy's capital.

Realised only. An open position that is deeply underwater does not count here. This gate reads closed-trade P&L for the day. If you want unrealised loss to halt entries, that is gate 7.

When it trips: the entry is rejected with DAILY_LOSS_LIMIT_EXCEEDED. New entries are blocked; the open position is left to its own stop and target. Blocking exits would trap capital in a losing position, which is the opposite of what this gate exists to do.

Skips when allocated capital is unknown, because a percent cannot be resolved into an amount.

Gate 7: Mark-to-market loss

mark_to_market_lossOff until you set it
Measures
Today's realised P&L plus the open position's unrealised P&L
Unit
Absolute amount in quote currency

The unrealised-loss brake. Halts new entries once realised plus unrealised loss is down by a set amount.

Stated as an amount, not a percent, deliberately. A percent silently moves the breaker when your capital changes; an amount is the number you actually meant. It mirrors the backtest control of the same name, so a strategy behaves the same researched and deployed.

The mark it uses is the incoming entry's own price, the entry is for the same instrument the strategy already holds, so its limit or trigger price is the current market for the position being valued. A market order with no price hint carries no usable mark, and the gate then skips rather than guessing a valuation.

Two known narrowings versus the backtest control, stated rather than hidden:

  • "Realised" here is today's realised P&L. The backtest measures from the run's starting balance. Closing that gap needs a lifetime-realised metric the live path does not carry yet.
  • Open exposure is derived from the buy-lot FIFO ledger, so a short-side position contributes quantity but not a mirrored unrealised sign.

When it trips: the entry is rejected with MARK_TO_MARKET_LOSS_EXCEEDED. As with the daily cap, the open position is left to its own exits.

Gate 8: Maximum capital allocation

max_capital_allocationAlways enforced
Measures
Capital already deployed plus this order's notional
Unit
Quote currency

The ceiling that makes a capital allocation mean something. Rejects an opening order that would push total deployed capital past the limit.

This one is not optional in practice. A configured value caps it explicitly; absent one, the strategy's allocated capital is still the effective ceiling, enforced through gate 9. A strategy cannot trade capital it was not given, and cannot reach into another strategy's allocation.

When it trips: the order is rejected with MAX_CAPITAL_ALLOCATION_EXCEEDED.

Gate 9: Available capital

available_capitalAlways enforced
Measures
The strategy's free (unlocked) capital against this order's notional
Unit
Quote currency

The last gate, and the one that cannot be turned off. An opening order the strategy cannot fund from its free capital is rejected.

This mirrors the ledger-backed capital check used everywhere else in the platform, so the same rule governs whether an order can be funded regardless of which path asks.

When it trips: the order is rejected with INSUFFICIENT_AVAILABLE_CAPITAL, reporting what was needed and what was available.

With gate 8, this pair is why capital isolation holds. Together they are the guarantee that a strategy stays inside its allocation, the one risk property on this platform that does not depend on your configuration.


Three controls alongside the pipeline

Cooldown after a losing streak

Not a validator but a modifier on gate 5. Converts "blocked until a win" into "blocked for N bars", so a consecutive-loss halt is self-clearing. Configured as gates.cooldown_bars_after_loss; defaults to 3 bars at the strategy's timeframe. Cooldown state is computed on every evaluation, not persisted. There is no stuck pause to clear.

Trading window

A time-of-day range, interpreted in IST, outside which the strategy does not enter. It comes from the strategy's risk_and_execution.trading_window and carries no day-of-week or holiday information, the source string does not encode any, so market calendars are handled separately. See Trading hours & holidays.

A window that wraps past midnight is handled correctly as spanning two days, though every real window seen in practice is same-day.

Margin validation

For margined instruments, a margin validator checks the order against available margin before placement and rejects with INSUFFICIENT_MARGIN. See Margin, leverage & lot size.


Reason codes

Every rejection carries a stable code. These are what you will see in the risk panel, the strategy timeline, and Telegram notifications if you have them enabled.

CodeGate
MAX_ORDER_VALUE_EXCEEDED1
PER_TRADE_RISK_EXCEEDED2
MAX_OPEN_POSITIONS_REACHED3
MAX_TRADES_PER_DAY_REACHED4
MAX_CONSECUTIVE_LOSSES_REACHED5
DAILY_LOSS_LIMIT_EXCEEDED6
MARK_TO_MARKET_LOSS_EXCEEDED7
MAX_CAPITAL_ALLOCATION_EXCEEDED8
INSUFFICIENT_AVAILABLE_CAPITAL9
INSUFFICIENT_BROKER_BALANCEBroker-side funding
INSUFFICIENT_MARGINMargin validator
DUPLICATE_ORDER / DUPLICATE_STRATEGY_EXECUTIONIdempotency, not a risk limit

Next