Action glossary¶
The engine validates every action name against a registry before it starts. That registry
is the list of verbs a .strategy file can call. The registry is not much fun to read.
This page is the version written for people.
There is one warning before the tables: the registry still contains older names that the current runtime does not handle. They pass validation, then do nothing. The Reserved names section lists them. Do not use those in a live file.
How to write an action¶
Actions use dots between words. The engine turns dots into underscores, so order.place is
the action order_place internally.
Arguments are always named:
order.place(market: "spot", side: "buy", qty: $params.qty,
price: $local.buy.px, tif: "ALO", dedup_key: "buy:initial")
Positional arguments are rejected. Values can be literals (1.0, true, "text") or
expressions ($platform.spot.bid * 0.99).
The tif argument controls how a limit order behaves at the venue:
| value | what the venue does |
|---|---|
ALO |
Post-only. The order rests as a maker. If it would cross, the venue rejects it. |
GTC |
Normal limit. It rests if it can, and takes if the market is already through the price. |
IOC |
Take what is available now, cancel the rest. |
price is optional. If you leave it out of order.place, the engine sends a market order.
For order.replace, pass a price; the action is meant for resting orders. market defaults
to "spot"; use "perp" for a perpetual. symbol is optional and overrides the asset from
the config entry.
The defaults for tif are worth remembering: order.place defaults to IOC, and
order.replace defaults to ALO. Pass the value explicitly if the order type matters to
you.
Effectful order actions need a dedup_key. The key is the order's identity. Reusing a key
means "this is the same order slot," not "send another one."
The small set¶
Most custom overlays need only these.
These order actions are the overlay path. The primary grid has its own buy and sell ledger
and its own fill routing; an order.* fill is tagged as an overlay order, so the grid will
not track it. Use them in an overlay file, not in a primary strategy.
| action | arguments | what it does |
|---|---|---|
market.facts |
none | Refresh the market and perpetual facts for this cycle: bid, ask, spot bid and ask, mark, funding, position, margin, minimum notional. Call it before reading $platform.* market facts. |
order.place |
side, qty, dedup_key; optional market, symbol, price, tif, reduce_only, post_only |
Send one order. Use reduce_only: true when closing a position. |
order.replace |
same as order.place |
Keep one resting order for the key. It places the order if it is not there, amends it if the price or size changed, and cancels it when qty is 0. This is the action to use when a strategy maintains a resting order. |
order.cancel |
order_id, dedup_key; optional market |
Cancel one order by venue id. You need the id, so most overlays use order.replace(qty: 0) instead. |
quote.reserve |
amount |
Reserve that much quote for the overlay. The primary grid's available quote drops by the same amount. |
quote.release |
none | Release the overlay's quote reservation (sets it to zero). |
reserve.base |
qty |
Move base into the reserved ledger so the grid will not sell it. A negative quantity releases; the ledger clamps at zero. |
set.gate |
name, value |
Write a remembered variable. You will normally write name = expression in the step body; the compiler turns that into set_gate. Calling it directly is only useful if you are generating steps. |
post_only is there for the rare case where you need to pass the flag separately from
tif. In normal use, tif: "ALO" is the clearer way to ask for a maker order.
Perpetuals¶
A perp position is opened and closed with the same two actions. A sell opens or adds to a
short; a buy closes it. Put reduce_only: true on the buy so the venue refuses any size
past flat. The position is signed, so the closing quantity is 0.0 - $platform.perp.qty.
order.replace is the one to use for a resting exit: it amends the same order when the
price or size moves, and cancels it at qty: 0. order.place sends one order and is better
for a fill-or-cancel action. market.facts must run before you read $platform.perp.*.
See A short position, start to finish for the full sequence.
Grid scaffolding¶
These are the phases the shipped jacobs_ladder.strategy composes. They assume the grid's
context: the asset, the strategy state, the open-order ledger, and the cycle facts. They are
not a general API for a new strategy, but they are the easiest way to see how the grid is
put together. The grid actions take no arguments; they publish their results as
$platform.* facts, which is why the strategy reads flags between phases.
If you are writing a small overlay, you do not need any of them. Use market.facts and the
order actions above.
Preamble and cleanup¶
| action | what it does |
|---|---|
cycle.prepare |
Run the grid preamble for this cycle. |
init.venue.state |
Initialise venue-specific state such as fees and lot size. |
prepare.recovery |
Restore the TIF and order recovery latch after a restart. |
resolve.book |
Read the current book into the cycle context. |
refresh.maker.fee |
Refresh the maker fee. |
expire.tif.recovery |
Clear an expired TIF-recovery latch. |
mark.stale.cycle |
Record that this cycle used a stale balance. |
cycle.cleanup |
Run the grid cleanup phase. |
expire.amend.cooldowns |
Clear expired amend cooldowns. |
evict.ghost.orders |
Drop orders the venue no longer has. |
scan.open.orders |
Sync the grid's open orders. |
Facts and gates¶
| action | what it does |
|---|---|
early.facts |
Publish the early cycle facts (price missing, balance freshness). |
cycle.facts |
Publish the rest of the cycle facts (buy and sell posture). |
cycle.guard |
Publish whether the cycle may continue. |
buy.gate |
Publish whether the buy leg is active. |
Buy leg¶
| action | what it does |
|---|---|
cancel.excess.buys |
Cancel extra buys so only one is open. |
buy.place.plan |
Compute the fresh-buy plan and publish its facts. |
buy.place.send |
Send the planned buy. |
buy.place.send.insufficient |
Handle an underfunded buy when the balance is stale. |
buy.place.latch.capital.low |
Latch the capital-low state. |
buy.place.warn.quote |
Log the missing-quote warning. |
buy.place |
Run the full buy-placement phase. |
buy.amend |
Run the buy-amend phase. |
buy.amend.has.sell |
Publish whether a resting or pending sell is tracked. |
buy.amend.with.sell |
Amend the buy when a sell is present. |
buy.amend.no.sell |
Amend the buy when no sell is present. |
Sell leg¶
| action | what it does |
|---|---|
plan.sell.order |
Compute the next sell rung and reconcile the persisted ladder. |
sell.place |
Run the gated sell-placement phase. |
sell.place.should |
Publish whether a fresh sell is placeable this cycle. |
sell.place.body |
Place the owed or restored sell. |
sell.finalize |
Run the sell finalize phase. |
sell.finalize.facts |
Publish the excess-sweep gate. |
sell.finalize.latch |
Retry-latch bookkeeping. |
sell.excess.sweep.phase |
Run the excess-inventory sweep. |
sell.finalize.end |
Clear the resume marker. |
Events¶
| action | what it does |
|---|---|
apply.order.event |
Route the current order event into the grid handler. jacobs_ladder.strategy pairs this with stop so the cycle ends after the event. |
Reserved names¶
These names are registered, so dio strategy validate will accept them. The current runtime
handler does not implement them, so they do nothing. Some are older names from the reference
engine; some were declared for a refactor that has not landed. Do not build on them.
Pricing and read helpers:
compute.grid.price compute.buy.ref.price owed.sell.price
available.base rung.price compute.sell.price
compute.amend.price read.book read.capacity
read.open.orders buy.facts
Direct order verbs:
place.buy place.sell amend.buy cancel.all cancel.order
State and accounting:
accumulate track.buy track.sell set.time set.cooldown
update.reserved.base notify.oracle
Gates and reconciliation:
gate.balance gate.capital.halted is.ghost
reconcile.position reconcile.persisted.sell.levels
If you find yourself reaching for one of these, use the wired equivalent instead:
order.place, order.replace, or order.cancel. If there is no equivalent, ask an
engineer to wire the action rather than relying on a name that validates and then silently
does nothing.
Where the names come from¶
The declaration (name, argument schema, class) lives in
src/engine/strategies/actions/strategy_actions_builtin.ml. The runtime handler lives in
src/engine/strategies/actions/strategy_actions_cycle.ml. The validator checks the
declaration; the runtime calls the handler. Adding an action means changing both files and
adding a row here.