A ladder places several GTC limit orders, called levels, at evenly spaced prices between a minimum
and a maximum. Other venues call this a scaled order. Use it to spread an entry or an exit across a
price range in one request, with CreateTrigger (POST /v1/triggers) and a ladder strategy. A ladder does not watch a
price: all its levels are placed when it is created.
Fields
| Field | Rule |
|---|---|
qty_scaled | Required, positive. The total quantity of all levels |
ladder.side | Required: BUY or SELL |
ladder.price_min_ticks | Required, positive, and on the Pair's tick size |
ladder.price_max_ticks | Required, above price_min_ticks, and on the Pair's tick size |
ladder.levels | Required, 2 to 100 |
ladder.post_only | Optional. Each level is rejected instead of taking liquidity |
Levels are always distributed linearly. ladder_state.ladder_distribution reports LINEAR; other
distributions cannot be requested. In REST, the range is priceMin and priceMax and the quantity qty, as decimal strings.
How levels are priced and sized
- Prices. The levels are spread evenly from
price_min_tickstoprice_max_ticks, both included, on the Pair's tick grid. Neighbouring levels are at least one tick apart, so the range must hold at leastlevels - 1ticks. - Quantities. Each level gets the total divided by the number of levels, rounded down to the Pair's quantity step. The last level, at the maximum price, also takes the remainder.
- Small totals. If the total divided by the number of levels rounds down to zero, only the last level is placed, with the whole quantity.
Every level must also meet the Pair's minimum order rules on its own.
Placement is all or nothing
Polyester checks and places every level when it accepts the ladder. If any level cannot be placed,
for example because the balance cannot fund it, the whole ladder is refused with LADDER_LEVEL_REJECTED and nothing is created or reserved.
The error names the level in its field violation: field_path is trigger.strategy.ladder.levels[<index>], counted from zero, and rule_id is that level's own
error code, such as INSUFFICIENT_FUNDS.
Each placed level is an ordinary GTC limit order with its own reservation, fills, and final state.
Its client order ID is trg:<trigger_id>:<child_seq>, and Polyester records an EVENT_FIRED event
for it with the level's price and order ID.
How a ladder ends
A live ladder is RUNNING. ladder_state.executed_qty_scaled is the quantity its levels have
filled, and ladder_state.executed_levels the number of levels with at least one fill. A partly
filled level counts once.
Canceling the ladder ends it at once. Otherwise it ends only after every level has ended:
| Final status | When |
|---|---|
COMPLETED | Any level filled, however the other levels ended. Also when nothing filled and only self-trade prevention, or Polyester while it recovered your account, canceled the levels |
FAILED | The book rejected every level before it was placed, for example post-only levels that would cross. failure_reason is the first rejected level's |
CANCELED | You canceled the ladder itself, or its Pair was disabled (PAIR_DRAIN_LADDER) or halted (MARKET_HALTED). Its open levels are canceled with it. Also USER_REQUEST when you canceled its levels and none of them filled |
COMPLETED ladder may have filled only part of its quantity. Read executed_qty_scaled, executed_levels, and each level's order status for what filled.Canceling a level, alone, with a cancel-all, or by the dead-man switch, does not cancel the ladder
while other levels stay open. Once none remains open, a ladder that filled nothing ends CANCELED with USER_REQUEST and one EVENT_CANCELED; one that filled any quantity ends COMPLETED. Fills are
never reversed: a canceled ladder keeps what its levels already filled.
A level that Polyester cancels while it recovers your account is not your cancel. Its order ends with
cancel reason ACCOUNT_REPAIR, and a ladder whose levels end this way with nothing filled ends COMPLETED with nothing executed and no cancel reason. If the recovery ends the ladder itself, the
ladder ends CANCELED with ACCOUNT_REPAIR.
Managing a ladder
A ladder takes only CancelTrigger. Pause, resume, and modify are refused with TRIGGER_NOT_MODIFIABLE. You can amend or cancel a level like any order, but not raise its quantity
above the level's planned quantity.
Limits and rate limits
An account can have up to 5 ladders on one Pair at once, and each counts toward its 100-trigger
limit on that Pair. A ladder beyond either limit is refused with MARKET_LADDER_LIMIT_EXCEEDED or MARKET_TRIGGER_LIMIT_EXCEEDED.
Creating a ladder costs one unit of TRADING_PLACE plus one per level, charged together. If your
placement budget cannot cover them all, the whole ladder is refused and nothing is charged for its
levels. See Rate limits.
Example
A ladder that buys 0.01 BTC in five levels from 98,000 to 102,000. With a quantity step of 0.001 BTC or finer, it places 0.002 BTC at 98,000, 99,000, 100,000, 101,000, and 102,000.
POST /v1/triggers
{
"trigger": {
"symbol": "BTC-USDT",
"qty": "0.01",
"clientTriggerId": "ladder-btc-1",
"ladder": {
"side": "BUY",
"priceMin": "98000",
"priceMax": "102000",
"levels": 5,
"postOnly": true
}
}
}/triggers.v1.TriggersService/CreateTrigger, assuming Pair ID 1 and a base_quantity_scale of 8.
{
"trigger": {
"symbolId": 1,
"qtyScaled": "1000000",
"clientTriggerId": "ladder-btc-1",
"ladder": {
"side": "BUY",
"priceMinTicks": "98000000000000",
"priceMaxTicks": "102000000000000",
"levels": 5,
"postOnly": true
}
}
}Errors
| Stable code | Cause |
|---|---|
LADDER_LEVEL_REJECTED | One level cannot be placed. The field violation names the level and its own code |
VALIDATION_ERROR | levels is outside 2 to 100, the minimum is not below the maximum, or the range holds fewer ticks than the levels need |
PRICE_TICK_SIZE | The minimum or maximum price is not on the Pair's tick size |
MARKET_LADDER_LIMIT_EXCEEDED | The account already has 5 ladders on the Pair |
TRIGGER_NOT_MODIFIABLE | A pause, resume, or modify was sent for a ladder |
See Error codes for HTTP and ConnectRPC statuses.