Skip to content

Stops and targets

An exit condition reacts to a signal. A stop or target describes a price level for closing the position.

Add percentage protection

//@version=1
symbol: TCS
run: daily
size: 5% per instrument
stop: 2%
target: 4%
entry:
    long when: close > ema(20)
exit:
    long when: close < ema(20)

For a long, the stop is 2% below entry and the target 4% above entry. For a short, the directions reverse. A signal exit can close before either level is reached.

Entry at 100 stop: 2% target: 4%
Long Stop level 98 Target level 104
Short Stop level 102 Target level 96

These percentages measure a price move from entry, not the percentage of portfolio equity lost or gained. The position quantity determines the money impact. A 2% price stop on a 500 position represents approximately 10 before costs, not 2% of a 10,000 portfolio.

Use stop: 2%, not stop: 2. Gaps, costs, slippage and execution availability affect realised fills and losses.

When protection starts and ends

Percentage protection starts at the confirmed fill, including entry slippage and excluding commission. A stop: 5% entry signalled at close 100 but filled at 110 has a long stop of 104.5, or a short stop of 115.5. Pending entries have no active stop or holding countdown. The entry_price expression still means the entry decision's close; stop_at: and target_at: retain their authored price snapshots. ATR/points distances freeze at the decision and anchor to the owner's confirmed fill. Separate purchases use the same confirmed-owner lifecycle.

Protection belongs to the symbol, strategy and entry group that opened the position. Another strategy's opposite holding cannot cancel it, even if the combined quantity is zero. When a stop reserves that owner's one-unit exit, an exit condition in the same decision does not sell or buy that unit again. A partial reduction keeps protection for the remainder. Confirmed closure or reversal removes the old levels and timer; a new entry inherits none of them. Engine checkpoint resume in backtest and paper runs restores each owner's saved protection. If A entered at 100 with stop: 5%, its saved long stop is 95 even if B entered the same symbol at 200 or holds an opposing position. A short entry at 100 instead has its saved stop at 105. A long one and B short one need two tracker allocations and two matching owners despite their zero symbol total. B retains its own saved settings. Starting the declaration again does not recreate protection from retained positions: the standalone rehydration helper and direct queue-restore entry point are removed in 5.0.0. Continue through Engine checkpoint resume, or start a fresh run.

Python position.set_stop, set_target and set_trail used alongside a script bind to that same confirmed owner. Setting protection without an open owner raises ATOM-PROTECTION-STATE. Invalid ordinary or batch entry protection rejects that submission; deferred simulation finishes unrelated queued orders before raising the error. If activation fails after a fill, the fill remains real and the run fails rather than silently continuing. Checkpoint continuation requires matching owner state and confirmed average entry prices for every held allocation, even without a stop; an owner without a matching holding also rejects. Hosts exposing only symbol totals reject any protection or owner payload, even when all owners are missing and the total is zero. Tracker inventory must match retained portfolio inventory. Invalid saved prices or levels also raise ATOM-PROTECTION-STATE before changing live state or pending resume data. A missing owner rejects the entire restore before any state is installed; start a fresh run with such a checkpoint. An empty protection payload does nothing. A retained position missing its confirmed owner raises ATOM-PROTECTION-STATE before any protection is installed.

This refusal also covers errors before the owner check can run. For example, a checkpoint holding A's one share at 100 with a saved stop at 95 cannot resume if it declares three differential frames but contains only two. The same applies to a malformed portfolio or pending/working queue, or a zero saved fill price, even when its owner records are missing. Atom raises ATOM-PROTECTION-STATE with the original error as its cause, before changing portfolio, queues, strategy state or pending resume data. No strategy ticks, new orders or fills follow the refusal. Use an intact checkpoint for continuation; a fresh run does not recover the saved holding.

A flat checkpoint with no protection or owner state keeps the previous behavior: where a corrupt checkpoint produced a warning and continued from the initial state, it still does. Live clients without an Atom stop registry also retain their existing resume behavior.

These changes retain TradeScript version 1. In an Engine event backtest or paper run using Atom's simulator, price protection now fills on the eligible breach bar. An entry filled at the open is protected against that bar's subsequent range. Ordinary entry signals, exit conditions and time exits still request a fill at the next open. Live adapter execution is unchanged.

For example, buy one at open 110 with stop: 5%. A fill-bar low of 106 leaves the 104.5 stop untouched. On the next bar (open 111, high 114, low 103, close 108), sell at 104.5: cash becomes 994.5 from 1,000, realised loss is 5.5 and quantity becomes zero, before costs. There is no extra exit on the following bar. If the breach bar instead opens at 101, sell at 101 for a loss of 9. A short at 110 mirrors this: buy back at stop 115.5 when high reaches it, or at open 119 if the bar gaps above the stop.

Targets require one tick of trade-through, including at the open. With an equity tick of 0.01, a long target at 121 needs high ≥ 121.01; high exactly 121 leaves it waiting. A short target at 99 needs low ≤ 98.99. An open at least one tick through the target fills at that open; an exact touch or sub-tick move rests until the range trades through by a full tick. This replaces touch-only fills, with no setting to restore them. Tick size comes from the instrument metadata; missing or invalid tick data raises ATOM-FILL-DATA. The same error rejects missing, nonfinite, nonpositive or inconsistent executable OHLC. Supply valid bars and instrument terms for the held symbol.

Market/stop slippage applies once. A target fill cannot become worse than its limit: a long target at 105 opening at 105.5 with 1% adverse slippage still sells at 105, rather than 104.445. Commissions apply only to actual fills.

An eligible opening target wins over a later intrabar stop. For one long entered at 100 with stop 95, target 105 and tick 0.01, (105.01, 106, 94, 100) sells at open 105.01 for profit 5.01 before costs. On (105, 106, 94, 100), the open exactly at the limit rests. Both levels then qualify intrabar: sell at 95 for a loss of 5 and record ambiguity. Opening at 105.005 gives the same adverse result. Previously these opens incorrectly filled the target for a profit. Shorts mirror this: target 95 fills at open 94.99; open 95 rests and, with high 106 and low 94, stop 105 wins for a loss of 5 with ambiguity recorded.

When both levels qualify only intrabar, OHLC cannot establish their order: two longs entered at 100 on (100, 106, 94, 102) sell two at 95, leaving cash 990 from 1,000 and realised P&L −10 before costs. Two shorts with stop 105 and target 95 buy back at 105 with the same cash and loss. The adverse eligible exit wins. When both a stop and trail are crossed, the tighter level is encountered first.

Competing intrabar hits record protection_ambiguity with symbol, bar, owner, eligible levels and selected exit. Python hosts can inspect the bounded wiring.order_client.stop_registry.ambiguities history after Engine.start(); it includes ordinary entries and independent purchases. These records describe the simulator's choice, not an observed order of trades inside the bar.

Trails check the prior anchor before advancing it. A long at 100 with a 5% trail survives (100, 110, 97, 108) against its old stop 95, then carries stop 104.5. The next bar (108, 112, 103, 106) sells at 104.5: cash 1,004.5 and profit 4.5 for one unit before costs. It does not use that bar's new high to tighten first.

Python simulator STOP_LIMIT orders activated intrabar wait until the next bar for limit matching; opening activation can use the same bar's subsequent range. See execution modes for the numerical example. This does not add pending-entry syntax to TradeScript.

Rejected and partially filled exits

In the simulator, a rejected protective exit keeps the owner's levels and releases that attempt's quantity reservation. From the next eligible bar, the remaining holding uses the normal stop, target and trail selection again; it cannot fire twice on the rejection bar. A bar for another symbol cannot fill it. Acknowledgement alone changes neither the holding nor cash. Ordinary entries and independent purchases follow this rule.

For example, buy two at 100 with stop: 5%, starting with 1,000 and zero costs. If the exit at 95 is rejected, two shares remain protected, cash stays 800 and realised P&L stays zero. A following bar (100, 101, 99, 100) does not reach the stop. The next (100, 106, 94, 100) sells two at 95: cash becomes 990 and realised P&L −10.

With one share at 100, stop 95 and target 105, reject the stop on (100, 104, 94, 100). A next bar (110, 112, 90, 100) exits at the opening target price 110: cash 1,010 and P&L +10. The target also wins if the low is 108. If instead both levels are reached only intrabar on (100, 106, 94, 100), the adverse stop wins at 95 and ambiguity is recorded. Previously retries could stay fixed at the first triggered level and miss these later choices.

A rejected 5% trail at 95 also remains active. After (100, 106, 94, 100) rejects, (100, 110, 96, 108) survives and carries a new trail of 104.5. (108, 112, 100, 106) then exits at 104.5 for P&L +4.5 before costs.

For a partial confirmation, buy three at 100 and confirm only one exit at 95. Cash becomes 795 and realised P&L −5. The other two stay protected. Their in-flight reservation ends before selection on the next observed bar; all retained levels compete again. If that bar gaps to 90, selling those two leaves cash 975 and realised P&L −25. With a fee of 0.10 per filled share, entry costs 0.30 and the exits cost 0.10 and 0.20: final cash is 974.40. An exit exceeding its owner's confirmed quantity raises ATOM-PROTECTION-STATE before accounting. entry_price continues to mean the entry decision's close, not its fill price.

Partial confirmation support does not add a liquidity model or TradeScript partial-fill setting. Simulator entries fill once; same-order partial entry fills and re-anchoring are not supported by this simulator.

Checkpoints retain outstanding protective exits separately by order identity, including each partially filled remainder. Canceling one releases only its own reservation; the other exits and the remaining holding stay protected. A late confirmation of a saved pending order after restart applies only its newly confirmed quantity. Repeated confirmations cannot close it twice. Unknown or contradictory identities raise ATOM-PROTECTION-STATE before accounting. Ambiguity history, cumulative commissions and rejection counts also survive restart. A checkpoint lacking the saved reservation evidence refuses to resume held pending exits; rerun from the original bars instead of guessing the exit.

Replay the six worked examples

Use this complete strategy for the normal and gap examples. The synthetic bars start at 09:15 UTC on 2026-10-19 and advance one minute at a time. With no market timezone override, the entry decision is made only on the first bar. Use initial cash 1,000 and zero costs except for the partial example's 0.10 commission per filled share.

//@version=1
symbol: AAA
run: each bar
size: 1 unit
stop: 5%
entry:
    long at 09:15

For each row, replace size: with the quantity shown. Add target: 5% for ambiguity; for trail, replace stop: 5% with trail: 5%. Use the bars and controlled rejection/confirmation described above. Rejections and partial confirmations are simulator test inputs, not additional TradeScript clauses.

Example Quantity Exit prices Gross P&L Final cash
normal 1 104.5 -5.5 994.5
gap 1 101 -9 991
ambiguity 2 95 -10 990
trail 1 104.5 4.5 1004.5
reject 2 95 -10 990
partial 3 95 then 90 -25 974.4

The normal sequence is B0 (99,102,98,100), B1 (110,113,106,112), B2 (111,114,103,108), B3 (120,123,118,121). For gap, replace only B2 with (101,105,99,103). The other examples decide on (100,100,100,100) before their described fill bars. In the normal and gap examples, entry_price at the entry decision is 100 while the confirmed entry fill is 110; percentage protection uses 110. The examples end flat, and later bars cannot repeat the exit.

Percentage, distance or absolute level?

Form Interpretation
stop: 2% Two percent from entry.
stop: 2 ATR Twice the default 14-period ATR as a price distance. Requires valid ATR inputs.
stop: 5 points Five price units from entry, not five percent.
stop_at: 95 An absolute stop price of 95, not a distance.
target_at: 110 An absolute target price of 110.

Distance stops sit below a long entry and above a short entry; targets reverse that direction. Absolute levels must be appropriate for the side and entry price. Use either a distance/percentage stop or stop_at: for a position, rather than conflicting declarations. The same distinction applies to targets.

For example, freeze a volatility distance and a seven-point profit distance:

//@version=1
symbol: AAA
run: each bar
size: 1 unit
stop: 2 ATR
target: 7 points
entry:
    long when: close > 99

Suppose the decision close is 100 and ATR(14) is 2.5. The submitted stop distance is five price units. If the order fills at 110 after ATR rises to 20, the stop is 105 and target 117. Changing long to short gives stop 115 and target 103. Writing stop: 5 points gives the same levels without needing ATR history. With initial cash 1,000 and zero costs, the long leaves 890 cash; the short leaves 1,110. No level or holding countdown exists until that owner's fill confirms.

In 5.0.0 these distances move with the fill anchor: the earlier close-based long levels in this example were 95 and 107. To keep a decision snapshot deliberately, replace the distance declarations with stop_at: close - 5 and target_at: close + 7; they remain 95 and 107 after the fill at 110. A literal stop_at: 95 stays 95 too. A short stop_at: 125 stays 125. Even a gap across an absolute level does not move it. TradeScript remains //@version=1.

To make each matching decision an independent purchase, append even if holding to the entry line. For example, with stop: 5%, purchases of one unit at 100 and one at 120 own stops 95 and 114. Only the newer unit is selected when price reaches 110. If the breach bar opens at 116 and reaches low 110, that newer unit exits at 114, leaving cash 894 from initial cash 1,000 and the older unit protected at 95. An opening gap to 110 instead fills at 110, leaving cash 890. Ordinary entries from distinct strategies likewise keep separate protection; increasing one ordinary position instead uses that owner's confirmed average entry price.

For side-specific levels, use a block:

stop_at:
    long: 95
    short: 105

Level expressions are supplied when the entry is made; writing an indicator expression here does not automatically turn it into a continuously moving trailing stop. Exact protection availability and continuation requirements depend on the execution environment.

Choose a complete protection value

stop:, target: and trail: use the same closed set of units:

Accepted spelling Meaning
2%, 2 percent, 2 pct, 2 percentage A two-percent price move.
ATR, 2 ATR, .5 ATR One, two or half the default 14-period ATR.
5 point, 5 points, 5 pt, 5 pts Five price units.

Units are case-insensitive; 2ATR and 2percent also work. The number must be a positive finite decimal, such as 2, 2.5 or .5. Zero, negative values, scientific notation and numbers too small or large to represent are rejected for these distance forms. There is no new 100% ceiling for standalone protections; an inline stop used to calculate risk sizing has its own percentage limit below.

Python entry hold= used alongside a script must be a positive whole number. For example, long(1, stop=.05, hold=float("inf")) rejects before an entry fill. If an unrelated one-unit buy is queued and fills at 110, it still completes: cash becomes 890 from 1,000, and then Atom raises ATOM-PROTECTION-STATE. Use hold=5 for five bars; do not use infinity to mean no time limit—omit hold instead. A five-point long stop filled at 4 has the impossible level −1: that fill remains real, no partial protection is published, and the error follows the drain of unrelated orders.

For example, combine a fixed stop, a target and a trailing distance:

symbol: TCS
run: daily
size: 10 units
stop: 2 percent
target: 5 pts
trail: 1 ATR
entry:
    long when: close > ema(20)

For an entry at 100, the fixed stop is 98 and the target is 105. If the entry ATR is 3, the trail starts three price units below its favourable anchor and follows that anchor upward. These are concurrent exit protections on the same holding; an earlier exit reduces what remains for the others. ATR requires valid OHLC history.

stop: 2 bananas, target: 2..5%, trail: 2% extra, and an empty declaration are errors (TS-PROTECTION-VALUE). The message shows the offending value, its line/column and the allowed forms. Previously an unrecognised value could compile without the requested protection; it now blocks compilation. Replace it with the intended unit before running. Bare stop: 2 still produces the existing TS-STOP-AMBIGUOUS error; add %, ATR or a points unit to state your intent.

Absolute stop_at: and target_at: instead accept a positive finite price or a numeric price expression, such as 95, close - 5, or lowest(low, 20). Keep both side-specific prices inside one block. Empty values, malformed expressions, literal strings, boolean expressions and trailing text are rejected. Dynamic expressions still need available inputs and a price appropriate for the position side at execution; static validation cannot predict their future numeric result.

Size from planned stop risk

size: risk 1% with stop 5%

This calculates quantity from a 1% planned loss budget and the 5% stop distance, and supplies that protection. It differs from allocating 1% to the position. Do not add a contradictory separate stop. The inline value must be N% with 0 < N <= 100; word units, ATR and points are not accepted inline. For example, size: risk 1% with stop 2 bananas is a protection-value error, not an instruction to size or enter without a stop.

Trailing protection and continuation

trail: 2% follows favourable price movement instead of a fixed entry level. For a long whose favourable anchor reaches 110, a 2% trailing level is 107.8. A later price decline does not move that anchor down. Shorts track favourable downward movement in the opposite direction.

You can also use trail: 2 ATR or trail: 5 points for a price-distance trail. The ATR form uses a 14-period ATR calculation for its distance at entry; the trailing anchor then follows favourable movement. This differs from a continuously recalculated ATR distance. Read strategy sections for the declaration overview.

//@version=1
symbol: TCS
run: daily
size: 5% per instrument
trail: 2 ATR
entry:
    long when: close > ema(20)
exit:
    long when: close < ema(20)

This needs the ATR provider and valid OHLC history as well as the EMA input. The distance is in price units, while a percentage trail scales with its favourable anchor.

Checkpoint continuation requires saved owner protection for fixed percentage stops and targets as well as trailing, ATR and dynamic levels. A retained entry price alone is insufficient; a restart does not invent an equivalent protective level.

Shared-budget strategies have additional restrictions. Consult sizing details for exact combinations.

Close after a time limit

Time stops live inside exit:. Write after N days or after N bars with a positive whole number; singular day and bar are also accepted. A clock exit uses at HH:MM (optionally long at or short at). A session-relative exit uses after N sessions at HH:MM, optionally prefixed by long or short, with 1–366 sessions and a valid 24-hour clock. Their timing differs; read instruments and schedules.

Use the complete clause: after 2 bars garbage, after 2..5 days, after 0 bars and at 25:00 are TS-PROTECTION-VALUE errors. Previously a day/bar time stop could silently accept trailing text; remove it or write the intended valid limit.

Protection for separate purchases

With even if holding, each purchase keeps its own confirmed quantity, entry price and protective levels. A 5% stop on Monday's purchase at 100 is 95; a Tuesday purchase at 120 has its own stop at 114. A fall to 110 can stop Tuesday's purchase while leaving Monday's open. Trailing anchors are retained separately too.

These purchases share the same protection lifecycle as ordinary entries. Each purchase's confirmed average fill price includes entry slippage and excludes commission. Closing an ordinary position on the same symbol leaves batch protection intact, and closing a batch leaves the ordinary position protected. A fully closed purchase loses its levels and timer; a later purchase starts with only its own settings.

//@version=1
symbol: TCS
run: 5min, Mon-Fri, 09:15-15:30
size: 1 share per instrument
stop: 5%
entry:
    long at 09:15 even if holding
exit:
    after 2 bars

If the 09:15 decision fills at 100 on 09:20, its stop is 95 and that fill bar consumes the first of two holding bars. The 09:25 observation requests a close, filled at the next open, 09:30, unless another exit closes it earlier. The next day's purchase gets a new timer. Duplicate callbacks and other symbols' bars do not advance a batch timer. Write a positive whole number for after N bars. Python callers supplying the equivalent hold= also reject fractional values, booleans, strings, NaN and infinity with ATOM-PROTECTION-STATE.

Partial fills count only when confirmed. If 4 of a requested 10 shares fill, protection can close those 4; it cannot sell the unfilled 6. Pending closes reserve that purchase's quantity. An early stop or partial close reduces what remains for its scheduled exit. A newer purchase is preserved even when the broker shows one combined instrument position.

Retained protection is checked on incoming bars independently of the authored entry schedule and indicator warmup. Recovery requires the saved purchase identity, remaining inventory and protection state. Missing evidence is an explicit execution error; it is never replaced with guessed levels or quantities.

Resume through an Engine checkpoint containing the purchase's owner protection. A checkpoint with held batch inventory and protection only in the former batch ledger now raises ATOM-PROTECTION-STATE before installation. Rerun the backtest from its source bars when no complete checkpoint is available. Flat batch checkpoints are unaffected. Batch price protection uses the same breach-bar pricing as ordinary positions: a long stop at 114 fills at 114 after an open above it and a low at or below it, or at open 110 when gapped through it.

Continue learning Risk and exposure limits →