EXIT AFTER¶
EXIT AFTER <duration> closes an entry a fixed time after it fills:
The clock starts when the entry fills and runs on the engine clock. It is checked on every
tick, so the close lands on the first tick at or after fill time + duration. It does not wait
for a bar close: a 90-second exit on a 5-minute stream closes about 90 seconds after the fill,
not at the next 5-minute boundary.
Grammar¶
<duration>is a literal:90s,4m,1h,2d. It must be positive. Expressions are not accepted.- The clause is order-independent like the other action clauses, and may appear once per action.
What it closes¶
The timed exit closes this entry's own leg at market, for whatever quantity of it is still
open. Other positions on the same symbol are not touched, which is what separates it from a
CLOSE <stream> rule (that flattens the stream).
- No bracket. The leg is closed at market when the time is up.
- With a
BRACKET. The bracket's stop and target are cancelled first, then the leg is closed at market, so a resting exit cannot fire later against a position that is already gone. If the stop or target fills first, the timer is dropped and nothing else is sent. - Pending entries (
ORDER_TYPE = LIMIT/STOP). The timer starts only if the entry fills. If it is cancelled, rejected or expires unfilled, the timer is dropped. STACK_ATlegs. Each stack leg gets the same hold, timed from its own fill. A leg that fills 30 seconds after the primary closes 30 seconds after the primary does.
Worked example¶
A bracket-less seed that scales in with a burst, where every trade lives at most four minutes:
STRATEGY gold_burst VERSION 1
SYMBOLS
gold = EXNESS:XAUUSD EVERY 5m WARMUP 60 BARS
RULES
WHEN ema(gold.close, 5) CROSSES ABOVE ema(gold.close, 13) AND POSITION.gold = 0
THEN BUY gold SIZING 0.01
STACK_AT MFE >= 0.30 WITHIN 4m SIZING 0.05 BRACKET { STOP LOSS BY 15.00, TAKE PROFIT BY 1.00 }
STACK_AT MFE >= 0.30 WITHIN 4m SIZING 0.05 BRACKET { STOP LOSS BY 15.00, TAKE PROFIT BY 1.00 }
EXIT AFTER 4m
The seed closes four minutes after it fills. Each burst leg closes four minutes after its own fill, unless its take-profit fills first.
EXIT AFTER versus holding_duration¶
A rule such as WHEN POSITION.gold.holding_duration >= 240 THEN CLOSE gold is evaluated only
when the stream's bar closes, like every WHEN rule. On a 5-minute stream it can close up to one
bar late, and because entries also fire at bar close, a 4-minute hold on 5-minute bars closes at
about 5 minutes. Use EXIT AFTER when the exit time itself matters.
Not supported¶
These combinations are rejected at compile time:
STACKpyramiding (useSTACK_AT)- an exit
OCOon the same action ON_FILL,ON_STOP,ON_TPandON_CLOSE- legs of an
OCO_ENTRY BASKETorders
Restarts¶
An armed timer is persisted with the daemon's strategy state: the leg it closes, the leg's venue ticket, and the absolute deadline. After a restart it is re-armed for the restored leg:
- A deadline still ahead fires at the original time, not a fresh hold from the restart.
- A deadline that passed while the daemon was down closes the leg on the first tick after
startup, and the log says so (
passed its deadline during downtime). - A leg that no longer exists at the venue (closed by its stop or target, or by hand, while the daemon was down) drops its timer without sending anything.
The record is removed when the timer's close fills, when the leg exits first, or when the entry
never fills. OrderManagerTimeExitRestartTest covers each case against the on-disk state store.
A close the venue does not fill¶
The close can end at the venue with nothing or only part filled: a thin book, a price band (Deribit rests a market order at its band price and the gateway cancels it), a transient refusal. The timer therefore stays armed until its close fills (#1360):
- A minute after each close is sent, a close the venue cancelled or rejected is sent again
(
<exit>-close-r1,-r2, ...), for only what is still open of the leg. A close still working is waited for; a filled one ends the timer. - After 3 failures in a row, and again at 6, 12, ..., qkt raises the operator alert (as for rule exits, see conditions) naming the strategy, symbol and position still open. Retrying goes on.
- The retry state is persisted with the timer, so a restart resumes it.
Backtests are unchanged: the simulators fill every market close.
STACK_AT tiers that have not fired yet keep the hold too: it is saved with the tier state, so a
leg that fires after a restart still gets EXIT AFTER, timed from its own fill
(StackExitAfterRestoreTest).
Known limitations¶
- The close is a market order sent on the first tick after the deadline. In live trading it fills at the venue's price at that moment; in backtest it fills at the tick's executable price.
Parity¶
Backtest and live run the same engine component for this exit, checked per tick in both modes.
DslExitAfterParityTest runs each case through both and asserts identical trades, including the
close timestamps.