跳到主要内容

Workflow

Chain operators, loops, conditionals, sleeps, waits, retries, and chain modifiers. The composition primitives that turn the CLI into a scripting language.


Chain operators​

buy $100 ; cancel ; close
buy $100 & cancel
OperatorBehavior
;Sequential — waits for all prior steps to finish before this one runs.
&Parallel — launches without waiting; runs alongside the prior step.
sleep / waitAlways block the chain (carry block: true), regardless of separator.
legacy @cmd prefixStripped silently (one-time warning per call). Skipped when @<word> is a known user var.

; is a barrier: the runner drains every in-flight step before dispatching the next, so close all; buy $1000 waits for the close to complete before buying. & is parallel — close all & buy $1000 fires both at once. Mix them freely: a & b; c runs a and b together, then waits for both before c. repeat joins its iterations with ;, so iterations are sequential by default. For condition-based waits use wait; to act on a specific order use capture syntax.


sleep​

FormEffect
sleep 55 seconds (default unit is seconds)
sleep 250ms250 ms
sleep 5s5 seconds (explicit)
sleep 2m2 minutes

Hard cap 10 minutes. Source: SleepCommand.ts, preprocess/sleep.ts.


wait​

Condition-blocked sleep. Polls the local exchange store at 100 ms.

wait <condition> [timeout=<dur>] [for=<dur>]

Condition forms​

FormMeaning
wait 5s / wait 250msBare duration — same as sleep
wait time HHMMWait until next occurrence of HHMM UTC
wait position [SYM]Any position, focused or named
wait long [SYM] / wait short [SYM]Side-filtered
wait anypositionAny account, any symbol
wait price > 50000Spot the trigger — uses ask on upside, bid on downside, falls back to last
wait mark > 50000 / wait index < 50000Mark / index price
wait pnl > 200 / pnl < -50uPnL on focused symbol
wait fundingrate > 0.0001Funding rate
wait premium > 5Premium
wait fill / wait fill BTCUSDTWait for any fill on the symbol
wait fill <id> / <id1>,<id2>Wait for a specific order to fill
wait @stop > 0LHS can be a user var (resolves via tracker/live/static)

Comparators​

> < >= <= == !=. The exact-compare forms are rarely useful for prices but they exist for integer / zero checks.

Combinators​

wait both (price > 50000) and (pnl > 0)
wait either (price < 49000) or (price > 51000)

Combinators nest. Source: preprocess/conditions.ts.

Modifiers​

ModifierDefaultCapBehavior
timeout=<dur>30 s10 mGive up after this long
for=<dur>——Condition must hold continuously for this window (anti-fakeout)

wait time 1200 and it's currently 12:00:00 UTC — the parser interprets that as "next 12:00 UTC", i.e. ~24 hours from now. Use wait time 1201 if you want the next-minute case.


when​

Arm a command to run once, later — without holding the prompt.

when (<condition>) (<command>)

wait blocks the line it is typed on and gives up after 30 seconds by default — and cannot be told to wait longer than ten minutes — so it cannot sit there waiting for a take-profit that fills in two hours. when arms and returns immediately; you keep trading.

when (price >= 0.051533) (be +3%)
when (price >= 0.053169) (trail 20%)
when (both (price > 50000) and (pnl > 0)) (close 50%)

The condition vocabulary is exactly wait's — everything in the table above, combinators included.

Why the parentheses​

A condition is variable-length and so is a command, so there is no separator a parser could find on its own: when price >= 100 close 50% has no unambiguous split. The parens are the same ones if already uses, and they are paren-aware, so a ; inside the action belongs to the action:

when (price >= 100) (be +3%; trail 20%) # both run when it fires
when (price >= 100) (be +3%); buy $50 # the buy runs NOW

What resolves when​

HalfResolvedWhy
the conditionwhen armed$entry and $roe101 mean the position you are looking at now
the commandwhen it firesbe +3% has to read the position as it will be then — that is the point

So when (price >= $roe100) (trail 20%) pins the target price at the moment you arm it, and the trail is computed against whatever the market is doing when it triggers.

Managing armed triggers​

tasks lists them alongside chasers and TWAPs; kill <id> drops one. Bare kill still targets the most recent chaser or TWAP rather than a trigger.

> when (price >= 51000) (be +3%)
Armed [a3f9c1] price >= 51000 → be +3%
> tasks
[a3f9c1] when BTCUSDT price >= 51000 -> be +3% armed
> kill a3f9c1

Limits​

LimitValueWhy
FiresonceIt disarms itself; arm another if you want it again
Expiry12 hA forgotten trigger is one that fires a market order next week
Nesting5A trigger whose command arms a trigger is fine; one that arms itself is a loop

A trigger is not persisted. It lives in the session that armed it, so closing the CLI drops it. It also needs the interactive REPL — a one-shot tealstreet "when (…) (…)" refuses rather than arming something that would be destroyed the moment the command returns.

Whatever it fires goes through the same path a typed command does: preprocessing, your aliases, the safety guards, and the audit log.

Worked end to end in Recipe: Signal ladder.

Source: WhenCommand.ts, triggers/triggerRegistry.ts.


loop​

Fan out one step per matching position. Symbol is appended to the body.

loop <variant>: <body>
<variant>Matches
longsAll long positions
shortsAll short positions
openposAll open positions (open accepted as alias)
allposEvery position (synonym for openpos)
allSame
loop longs: buy 1$
loop shorts: close
loop allpos: cancel

Multi-statement bodies are not supported. If you need one, alias the body and loop the alias:

alias cleanup "cancel; close"
loop longs: cleanup

Source: preprocess/loop.ts.


repeat​

repeat 5 (buy 1$; sleep 1m)
CapValue
Max iterations1000
Max nesting depth8

Source: preprocess/repeat.ts.


if / else / end​

if (<condition>) (<then-body>) [else (<else-body>)]

<condition> uses the same vocabulary as wait. Evaluated once at preprocess time against current store state, so this is a one-shot conditional, not a runtime loop.

The body is fully reprocessed — nested if, repeat, loop, retry, set ec all compose inside it.

if (long) (close)
if (pnl > 50) (close) else (sleep 5m)
if (position) (chase sell %all% 100% reduce) else (echo "nothing to close")

end​

A bare end in the chain (or inside an if branch) terminates the outer chain. Anything after is dropped.

buy $100 at $entry -1%
wait fill
if (pnl < -10) (close; end)
buy $100 at $entry +1%

If the if fires, end cancels the trailing buy.

Source: preprocess/ifElse.ts, preprocess.ts:204-213 (end), :218-275 (if recursion).


retry​

retry N <body>

Re-runs <body> up to N times after a failure. First attempt is "try 0", so retry 3 means up to 4 total attempts.

BoundBehavior
N = 0Same as not using retry
N ∈ [0, 10]Accepted
N > 10Falls through as a normal step — the dispatcher will reject it
retry 3 chase buy $100 to 1%
retry 5 close

Source: preprocess/preprocess.ts:31-32, 146-157. Runner side at apps/cli/src/repl.ts:1618+.


Chain modifiers​

Mid-chain flags that apply to every subsequent step in the same chain. Idempotent — won't double-inject if the keyword is already on the step.

ModifierEffect
set ecContinue-on-error — subsequent failures print as warnings, chain runs on
set ro / unset roInject reduce into subsequent buy/sell/scale/swarm/chase/twap
set po / unset poInject po into subsequent buy/sell heads. Market orders ignore it
set ec
cancel
close
nuke # all three continue even if one fails
set ro
close # reduce-only is implicit now
cancel
buy $100 # still gets reduce injected
set po
buy $100 at 50000
sell $100 at 50100
unset po
buy $50 best # market-best, post-only no longer active

Source: preprocess/preprocess.ts:20-57, 133-142, 332-356.


po keyword on a single order​

You can mark one order post-only without affecting the rest of the chain:

buy $100 at 50000 po

Same effect as set po; buy $100 at 50000; unset po.


Composition cheatsheet​

A few canonical combinations:

# retry × repeat × sleep × set ec
repeat 5 (set ec; retry 2 buy 1$; sleep 1m)

# one-shot conditional
if (long) (close)

# fan-out reduce-only chase
loop longs: chase sell 50% reduce

# capture + wait fill + close
@o = buy $100 at 50000; wait fill @o; close

# soft-error cleanup
set ec; cancel; close; nuke

# chain reduce-only
set ro; close; cancel; buy $100

# mid-chain post-only toggle
set po; buy $100 at 50000; sell $100 at 50100; unset po; buy $50 best