Risk checks¶
Risk helpers report whether a condition is reached. Your strategy decides whether to stop opening positions or close them.
Before selecting live flatten policy, check the execution and instrument requirements. Held FX/crypto or derivative positions need authoritative contract terms, and non-unit multipliers are rejected by the current portfolio accounting. A valid risk rule cannot supply missing instrument metadata by itself.
Pause after a daily loss threshold¶
def on_tick():
if daily_loss_limit(2.0):
return
if len(close) >= 20 and position.is_flat and close[0] > mean(close, 20):
long(10)
daily_loss_limit(2.0) means 2%, unlike entry sizing fractions. This example pauses its signal logic; it does not flatten existing holdings. The helper compares with the first-bar daily equity baseline and resets at midnight UTC.
Pause after consecutive losses¶
max_consecutive_losses(3) reports three negative realised closed trades in a row. It returns false when there are too few records or no available ledger.
close() closes the current instrument only. A portfolio-wide flatten needs an explicit supported portfolio action. These checks do not independently cancel orders or guarantee an account-level loss bound.
See risk-check reference for parameters and behaviour.
Pause entries while preserving exits¶
An early return pauses the remainder of on_tick(). If you want ordinary signal exits to continue while new entries are paused, evaluate them first:
symbol = "TCS"
def on_tick():
if len(close) < 20:
return
average = mean(close, 20)
if position.is_long and close[0] < average:
close()
elif position.is_flat and not daily_loss_limit(2.0):
if close[0] > average:
long(10)
This checks a 2% daily loss threshold before entry. It does not independently cancel pending orders or close every instrument. Registered protection and signal exits are separate actions.
Read the threshold in the correct units¶
With a first-observed daily equity of 10,000, current equity of 9,800 reaches daily_loss_limit(2.0). The comparison includes the equity change, including marked open P&L where provided. The first observed bar establishes the daily baseline; it is not necessarily an exchange opening auction.
daily_loss_limit(0.02) means 0.02%, not 2%. Entry risk_pct=0.02 uses a fraction and does mean 2%. Keep these conventions visible when moving between helpers.
The baseline belongs to the whole strategy, not each instrument. A second instrument observes the same daily equity baseline. Separate strategy runs do not share it. A same-day checkpoint restart retains the baseline; a new UTC date starts a new one at the first call. Timezone-aware timestamps are converted to UTC and naive timestamps are treated as UTC for this helper.
For example, a 10,000 baseline followed by 9,700 returns true for a 2% limit, including after a same-day restart. On the next UTC day, a first observation of 9,700 establishes that day's baseline and returns false. A recovery above the threshold returns false again: this is a comparison, not a latched daily halt. Zero or negative equity still triggers a loss against an existing positive baseline. A new run without a usable positive baseline cannot establish one.
Before Atom 4.10.0, repeated dispatches could reset the baseline on every bar. Rerun affected backtests rather than relying on their previous loss-gate results.
Understand missing observations¶
The loss helper returns false when the required portfolio, timestamp or baseline tracking is unavailable. The consecutive-loss helper needs an actual closed-trade ledger and negative realised P&L for every one of the last N records. Missing records, unknown P&L, zero-profit or winning trades break that check. False therefore does not prove the account is healthy in an environment missing those inputs.
Flatten after an async live breach¶
In an async live integration configured with a risk supervisor and the
flatten breach policy, a breach submits market exits for the tracked open
positions. This is different from the boolean helpers above: the hosting
integration must explicitly enable that policy and supply an order adapter,
portfolio and fill callbacks. Merely calling daily_loss_limit() does not
enable it. The default breach policy remains halt.
For example, suppose one group holds 10 units of A long and another holds 3 units of A short. Flatten submits a sell of 10 for the first group and a buy of 3 for the second, preserving each strategy's ownership. It does not replace those exits with a single sell of the net 7 units. If only 4 of the sell's 10 units fill, 6 remain open. Subsequent bars do not submit another sell while that exit is outstanding. An accepted order is not evidence of a closed position: completion requires observed fills for both groups.
The strategy's tick callback pauses while flattening is pending. Rejected,
cancelled or uncertain exits raise an error instead of reporting success or
automatically retrying. The integration's flatten_timeout defaults to 30
seconds per exit, measured from its submission attempt through observed fills;
it is not a guarantee that a venue can close a position within that time.
After a failure, reconcile executed fills and confirm the old exit cannot
still fill before resuming. In the example, a terminal rejection after the
4-unit fill leaves a 6-unit position to close, not a fresh 10-unit exit.
Do not clear the request simply because the submit call returned.
The policy does not cancel previously submitted entry orders or provide broker-side reduce-only guarantees. Multi-leg exits are individual market orders, not an atomic group execution. Asset contracts requiring information absent from the tracked position must be handled by the hosting integration; an unsupported exit must not be silently treated as complete.
When a configured supervisor rule needs equity or exposure, those inputs must
be finite. NaN or infinity cannot mean "within limits." Unknown required money
data produces an invalid_risk_data halt even if the configured valid-data
policy is flatten: first restore trustworthy accounting, then choose reducing
actions. For example, after valid equity of 1,000, NaN equity with a 10% drawdown
rule stops the run; it does not reset the baseline or automatically submit exits.
Finite-data halt/flatten behavior is unchanged. Supervision remains a post-tick
check, not a promise that every intrabar loss is prevented.
Earlier async runs could record a flatten request without submitting any exits. That behavior is corrected; a pending request now pauses strategy dispatch until completion or reconciliation. This does not change the TradeScript language version or turn the command-line runner into a turnkey broker integration.
Order-rate checks in async live strategies¶
Through the integration's injected order client, an accepted buy, sell,
submit or submit_group request counts once toward the configured
orders-per-minute limit. A group request counts once, not once per leg; fill
partials and replays do not add requests. Rejected or unacknowledged requests
do not count as accepted orders. Emergency flatten exits are excluded so a
rate breach does not prevent reducing exposure.
The existing breach policy is evaluated after the tick, using current UTC time. For example, with a limit of 2, three accepted requests in one tick trigger the policy after that tick; the third request is not prevented by this check. Calls through a separately retained raw broker adapter bypass this boundary. Keep submissions in the awaited strategy callback and use the injected client; independently scheduled background work is not a substitute for the supervised tick lifecycle.
Detect excessive order submission¶
The event-driven simulator can supervise submissions independently of your signal conditions:
def on_start():
return {"max_orders_per_minute": 30, "on_breach": "halt"}, {}
The supervisor counts submitted requests in the trailing 60 seconds of feed time, including requests queued for next-open execution. A multi-leg group is one submission; later fill attempts do not count again. Orders exactly 60 seconds old are still in the window. Current checkpoints preserve that window.
The check runs after the bar callback. Thirty requests are allowed by this threshold; the thirty-first produces a breach at that bar's check. This is a runaway-strategy detector, not a pre-submission broker rate limiter: several requests made within one callback can already be queued or filled before the check. A halt prevents subsequent new-risk fills while leaving reducing exits possible. Use your broker adapter's admission limiter for hard API quotas.
In the local simulator, a reduction belongs to the existing position's symbol,
strategy and group, not just its symbol's account-wide net quantity. If strategy
A owns +10 X, strategy B selling 5 X opens B's short; it does not close A's long.
A halt therefore rejects B's order, while A can sell up to its own 10 to reduce
or close. Selling 15 would reverse A and is new risk. The same rule applies to
each composite leg and to cash and margin accounts. Aggregate positions()
queries still show symbol totals; they do not transfer ownership between groups.
Local flatten submissions also preserve each owned row rather than closing only the symbol's net amount. For +10 and -3 held by separate owners, the response submits SELL10 and BUY3. These are market-order requests subject to the normal next-open rule, not evidence that both positions have already closed.