LATCH — directional-trigger entry¶
Arm a pair of price trip-wires (ref ± offset) and wait. By default, the first wire the market crosses sets a direction (BUY on the up-wire, SELL on the down-wire) and locks in an anchor price O. Optional CONFIRM modes can require bar-close, dwell-time, or retest evidence before direction commits. If no side confirms before the arm window elapses, the latch is silently dropped.
The classic use case: a session level sits at $2,000. You don't want to trade the level itself — you want to trade the first confirmed break above or below. LATCH arms two wires, waits for the break, then places a retrace limit at a defined distance so you enter after the breakout pullback, not at the spike.
Shape¶
WHEN <condition>
THEN LATCH <stream> OFFSET <d> [ FROM <ref_expr> ] ARM <duration> [ AS <name> ]
[ CONFIRM CLOSE_BEYOND | TIME_IN_BREACH <duration> | RETEST_HOLD <d> WITHIN <duration> ] {
ENTER [ ON <stream> ] MARKET | LIMIT <dir_rel> | STOP <dir_rel>
[ BRACKET { [ STOP LOSS <dir_rel>, ] [ TAKE PROFIT <dir_rel> ] } ]
[ SIZING <spec> ]
[ EXPIRE <duration> ]
[ ; ENTER ... ]
}
<stream>— the stream alias whose ticks the latch watches.OFFSET <d>— distance from the reference to each trip-wire. e.g.OFFSET 0.50→ up-wire atref + 0.50, down-wire atref - 0.50.FROM <ref_expr>— optional: overrides the reference. Omit to use<stream>.close(the most recent bar's close). e.g.FROM gold.high.ARM <duration>— how long the wires stay active after arming. Required; there is no default. Supported suffixes:s,m,h,d.AS <name>— optional name, useful for logging and future addressing.CONFIRM ...— optional evidence requirement. Omit for the historical first-tick behavior.{ ENTER ... }— one or more entry clauses separated by;.
A WHEN condition fires the latch arm on each matching candle close. If the condition fires while an arm is already active for this stream, a second arm is queued independently — each arm is tracked separately and fires at most once.
Direction-relative price notation (WITH/AGAINST/RETRACE)¶
Entry prices, brackets, and stop distances are written relative to the break direction and the anchor O. The table below shows how the keywords resolve for both directions.
| Keyword | Long break (BUY, dir=+1) | Short break (SELL, dir=-1) |
|---|---|---|
WITH d |
O + d (extends in break direction) |
O - d (extends in break direction) |
AGAINST d |
O - d (retraces from break) |
O + d (retraces from break) |
RETRACE d |
same as AGAINST d |
same as AGAINST d |
WITH means "move with the break". AGAINST/RETRACE mean "move against the break" — used to place retrace entries and take profits that give back distance from O.
Worked example — $2,000 level¶
A tick at $2000.60 crosses the up-wire → direction = BUY, anchor O = 2000.50.
ENTER LIMIT RETRACE 4 → entry = 2000.50 - 4 = 1996.50 (BUY LIMIT at the pullback)
BRACKET {
STOP LOSS AGAINST 12 → SL = 2000.50 - 12 = 1988.50
TAKE PROFIT WITH 5 → TP = 2000.50 + 5 = 2005.50
}
If the down-wire had been crossed instead (tick at $1999.40 → SELL, O = 1999.50):
ENTER LIMIT RETRACE 4 → entry = 1999.50 + 4 = 2003.50 (SELL LIMIT at the pullback)
BRACKET {
STOP LOSS AGAINST 12 → SL = 1999.50 + 12 = 2011.50
TAKE PROFIT WITH 5 → TP = 1999.50 - 5 = 1994.50
}
The same source text adapts automatically to whichever direction breaks first.
Entry order types¶
| Syntax | Meaning |
|---|---|
ENTER MARKET |
Market order at anchor O |
ENTER LIMIT <dir_rel> |
Limit at resolve(dir_rel) — use RETRACE d for below-the-break buy |
ENTER STOP <dir_rel> |
Stop at resolve(dir_rel) — use WITH d for above-the-break buy |
Inverted geometry (a BUY LIMIT above the anchor, or a BUY LIMIT above the SL) is skipped at runtime with a WARN log. The remaining entries in the block still fire.
ENTER ON <stream>¶
By default entries submit on the latch stream. ENTER ON <stream> watches one stream for the break and submits the entry on another declared stream:
LATCH gold OFFSET 2 FROM gold.high ARM 10m CONFIRM TIME_IN_BREACH 10s {
ENTER ON silver MARKET SIZING 0.5 ;
ENTER ON silver LIMIT AGAINST 5 SIZING 0.5 BRACKET { STOP LOSS AGAINST 8, TAKE PROFIT WITH 20 }
}
Direction maps directly: an up-break on gold submits BUY entries on silver; a down-break submits SELL entries. Inverse mapping is not part of v2.
For same-stream entries, O remains the trigger wire price for backward compatibility. For ENTER ON <stream> entries, the runtime uses two anchors:
O_trigger— the watched stream's wire price; sets direction only.O_entry— the entry stream's latest price snapshot at the direction-commit instant; allWITH/AGAINST/RETRACE, SL, and TP distances resolve from this price in entry-stream points.
The entry stream must be declared in SYMBOLS. SYNCHRONIZE is recommended for tightly coupled lead-lag systems, but not required.
Modifiers¶
OFFSET <d> and FROM <ref_expr>¶
-- Default: reference is gold.close of the closing bar
LATCH gold OFFSET 2 ARM 10m { ENTER MARKET SIZING 0.1 }
-- Override reference to today's high
LATCH gold OFFSET 2 FROM gold.high ARM 10m { ENTER MARKET SIZING 0.1 }
ARM <duration>¶
How long the wires stay armed after the signal fires. Starts at the clock time of the WHEN rule fire (candle close). If no wire is crossed before the arm expires, the latch is dropped silently. The clause is required — omitting it is a parse error (expected ARM <duration> in LATCH):
LATCH gold OFFSET 0.50 ARM 5m { ENTER MARKET SIZING 0.1 } -- 5-minute arm window
LATCH gold OFFSET 0.50 ARM 1h { ENTER MARKET SIZING 0.1 } -- 1-hour arm window
CONFIRM¶
Confirmation is bounded by the same ARM window. EXPIRE on emitted LIMIT/STOP entries starts from the confirmation instant, not from the original arm time.
LATCH gold OFFSET 0.50 ARM 15m CONFIRM CLOSE_BEYOND { ENTER MARKET SIZING 0.1 }
LATCH gold OFFSET 0.50 ARM 15m CONFIRM TIME_IN_BREACH 10s { ENTER MARKET SIZING 0.1 }
LATCH gold OFFSET 0.50 ARM 15m CONFIRM RETEST_HOLD 0.25 WITHIN 2m { ENTER MARKET SIZING 0.1 }
CLOSE_BEYOND— direction commits only when the watched stream's completed bar closes beyond a wire. A spike through the wire that closes back inside does not fire.TIME_IN_BREACH <duration>— price must remain beyond the same wire continuously for the duration. A tick back inside the wires resets the timer.RETEST_HOLD <d> WITHIN <duration>— after a breach, price must return to withindof the breached wire without crossing back through it, before the retest window elapses.
If one side starts confirmation and then the other wire breaches first, pending direction hands over. The first side to satisfy its confirmation wins.
EXPIRE <duration> on entries¶
Limits how long a placed order (LIMIT or STOP entry) stays working before auto-cancelling. Measured from the time the wire is crossed (when the order is placed).
AS <name>¶
Optional label for the latch. Currently used in log output.
Multiple entries (laddering)¶
Separate entries with ; inside the { } block. All entries resolve independently when the wire is crossed. This is the retrace-ladder pattern: three limit orders at increasing depths, all anchored to the same break.
LATCH gold OFFSET 0.50 ARM 5m {
ENTER LIMIT RETRACE 2 BRACKET { STOP LOSS AGAINST 12, TAKE PROFIT WITH 5 } SIZING 1 ;
ENTER LIMIT RETRACE 4 BRACKET { STOP LOSS AGAINST 12, TAKE PROFIT WITH 5 } SIZING 1 ;
ENTER LIMIT RETRACE 6 BRACKET { STOP LOSS AGAINST 12, TAKE PROFIT WITH 5 } SIZING 1
}
Tiebreak rule¶
If a single tick straddles both wires (i.e., crosses both up and down simultaneously — unusual but possible with a wide spread or gap), the up-wire wins: direction is BUY, anchor is the up-wire price.
Constraints¶
- Distances must be compile-time constants.
OFFSET,RETRACE,WITH, andAGAINSTdistances must resolve to a constant — a numeric literal, or aLETbound to one (LET near = 4thenRETRACE nearworks; theLETis inlined before compilation). Genuine runtime expressions — indicator calls,NOW.<field>, price refs — are rejected with a compile error, because the risk-sizing stop distance (|SL_dist − entry_dist|) is computed statically at compile time. RETEST_HOLDdistance must be constant. Same constant-distance rule as entry/bracket distances.- Geometry validation. A BUY LIMIT above the entry anchor, or a SL above a BUY entry, is skipped at runtime with an WARN log. The compiler cannot detect this because the direction is only known at wire-cross time.
- Transient arm — no persistence. Armed latches live in memory only. A restart mid-arm drops them silently. The strategy re-arms on the next qualifying candle close.
- At most one fire per arm. A latch fires on the first confirmed side, then removes itself. It does not re-arm automatically.
- No price snapshot, no cross-entry.
ENTER ON <stream>needs a latest price for the entry stream. If direction confirms before that stream has printed a price, that entry is skipped with a WARN log.
Full example¶
STRATEGY latch_demo VERSION 1
SYMBOLS
gold = BACKTEST:XAUUSD EVERY 5m
RULES
WHEN NOW.minute_utc = 0 AND POSITION.gold = 0
THEN LATCH gold OFFSET 0.50 ARM 5m {
ENTER LIMIT RETRACE 4
BRACKET { STOP LOSS AGAINST 12, TAKE PROFIT WITH 5 }
SIZING RISK $ 250
EXPIRE 2h
}
At the top of every hour, if flat, the strategy arms wires 0.50 points either side of the current bar's close. The first break within 5 minutes places a retrace LIMIT buy (or sell) with a shared bracket. The limit auto-cancels after 2 hours if unfilled.
What this composes with¶
- SIZING — all sizing forms supported; stop distance for risk sizing is computed statically as
|SL_dist - entry_dist|so distances must be constants - BRACKET — per-entry bracket; each entry in the
{ }block has its own - LET and DEFAULTS —
LETbindings can supply the constant distances - Actions —
LATCHis an action; it can appear alongsideLOGin aBLOCK - Conditions (WHEN) — the
WHENcondition gates arm frequency