Builtins: level 3 individual orders
Market by order: every resting order whose life overlapped the bar, each one seen exactly as it stood at the end of that bar. Nothing the order did later leaks into an earlier bar, so a bar at 09:31 never knows what the order became at 15:59.
| Signature | Returns |
|---|---|
| mbo.supported() -> bool | Whether order level data is wired on this chart |
| mbo.available() -> bool | Whether this bar carries orders |
| mbo.count() -> int | How many orders overlapped the bar, 0 without data |
| mbo.order_id(index: int) -> string | The exchange order id, empty when out of range |
| mbo.side(index: int) -> side | BUY for a resting bid, SELL for a resting offer |
| mbo.price(index: int) -> float | Its resting price at the end of the bar |
| mbo.size(index: int) -> float | Its displayed size at the end of the bar |
| mbo.peak_size(index: int) -> float | The largest size it had shown so far |
| mbo.filled(index: int) -> float | How much of it had traded so far |
| mbo.refills(index: int) -> float | How many times its size grew back at the same price so far |
| mbo.first_time(index: int) -> float | When it was first seen, epoch milliseconds |
| mbo.last_time(index: int) -> float | Its last change seen so far |
| mbo.status(index: int) -> string | "active" while it rests, "executed" or "cancelled" once it ended inside the bar |
| mbo.largest_index() -> int | Index of the largest displayed order on the bar, or -1 |
| mbo.largest_size() -> float | Its size |
| mbo.bid_size_total() -> float | Total displayed size of the resting bids |
| mbo.ask_size_total() -> float | Total displayed size of the resting offers |
| mbo.iceberg_count(min_refills: float) -> int | How many orders on the bar were refilled at least min_refills times |
Order events
Every change to an order stamped inside the bar, oldest first: when it was placed, when its price or size changed, each fill and its cancel. This is the stream to read when the question is what happened during the bar, such as how much size was pulled from the bid.
| Signature | Returns |
|---|---|
| mbo.event_count() -> int | How many events happened inside the bar, 0 without data |
| mbo.event_type(index: int) -> string | "add", "modify", "fill" or "cancel", empty when out of range |
| mbo.event_price(index: int) -> float | The resting price, or for a fill the execution price |
| mbo.event_size(index: int) -> float | The new resting size, the contracts filled, or the size removed by a cancel |
| mbo.event_side(index: int) -> side | The side of the resting order: BUY for a bid, SELL for an offer |
| mbo.event_time(index: int) -> float | When it happened, epoch milliseconds |
| mbo.event_order_id(index: int) -> string | The order it belongs to |
| mbo.added_size(side: string) -> float | Total size placed on that side during the bar; side is "BUY" or "SELL" |
| mbo.cancelled_size(side: string) -> float | Total size pulled from that side |
| mbo.filled_size(side: string) -> float | Total size filled against resting orders on that side |
- An order that was already resting when the chart started watching has no add event; its later changes are there.
- Without data the three totals answer nan, the count 0 and the accessors their empty values.
- Order events are a lot of rows on a busy bar. Filter inside the loop and draw only what matters.
let has_orders: bool = mbo.supported() && mbo.available()
let pulled_bid: float = mbo.cancelled_size("BUY")
let pulled_ask: float = mbo.cancelled_size("SELL")
for i = 0 to (has_orders ? mbo.event_count() : 0) - 1 by 1 {
if mbo.event_type(i) == "cancel" && mbo.event_size(i) >= 50 {
plot.dot(true, mbo.event_price(i), mbo.event_side(i) == "BUY" ? "#F97316" : "#38BDF8", 5)
}
}
plot.label(str.concat("PULLED BID ", format.compact(nz(pulled_bid, 0)), " ASK ", format.compact(nz(pulled_ask, 0))), "#E5E7EB")- Order indexes run from 0 to mbo.count() minus 1, oldest order first. The whole bar answers (largest, totals, iceberg count) are one call; loop over the indexes when you need every order.
- mbo.largest_index() answers -1 on a bar with no orders, and the per order accessors answer the empty values for -1, so mbo.side(mbo.largest_index()) needs no guard.
- Order data reaches back one trading day by default. With the Iceberg MBO indicator on the chart, its Days to load setting decides.
- A bar with no data answers zero counts, -1, UNDEFINED, empty strings and nan.
let has_orders: bool = mbo.supported() && mbo.available()
let big: int = mbo.largest_index()
let big_side: side = mbo.side(big)
let refilled: float = mbo.refills(big)
let icebergs: float = mbo.iceberg_count(3)
plot.marker(has_orders && icebergs > 0 && big_side == "BUY", low, "#22C55E", "circle", "below", 8, 2)
plot.marker(has_orders && icebergs > 0 && big_side == "SELL", high, "#EF4444", "circle", "above", 8, 2)