Skip to content

Order Types

FastBoxEx has four ways to submit an order. This page explains each one, when to use it, and why the design is different from a traditional exchange.


The short version

Method What it does When to use it
submit_order(price, qty, side) Limit — rests at an explicit price Market-making, passive quoting
submit_market(qty, side) Market — fills at whatever is available Immediate fill, don't care about price
submit_delta(delta_ticks, qty, side) Delta — delta_ticks inside/through the spread Aggressive fill with price control
submit_ioc(delta_ticks, qty, side) IOC delta — same, but cancel the rest if partially filled One-shot execution, no resting orders

delta_ticks=0 on a buy → executes at the best ask. On a sell → executes at the best bid. This is called a touch order — the most common aggressive pattern.


1. Limit order

The baseline. Place a bid or offer at an explicit price. The order rests on the book until it is filled or cancelled.

# Limit buy at 99.95 display (= 999_500 raw ticks)
slot.submit_order(asset_id, price=999_500, qty=100, side=0)

# Limit sell at 100.05
slot.submit_order(asset_id, price=1_000_500, qty=100, side=1)

Price scale reminder

price is always in raw ticks. 1 display unit = 10 000 ticks. Divide by 10_000 to get the price you see on screen.


2. Market order

Sweeps the opposite side of the book at whatever prices are available — you don't care what you pay, you just want the fill.

slot.submit_market(asset_id, qty=100, side=0)   # buy 100 at market
slot.submit_market(asset_id, qty=100, side=1)   # sell 100 at market

Empty book → rejection, not silence

If the opposite side of the book is empty, you get a BookEmpty rejection back immediately. Unlike many real exchanges, nothing is silently dropped or queued.

Under the hood — how market differs from limit

Internally, a market order is a delta order with an infinite delta. On the wire, this is encoded as price = u64::MAX (the sentinel value 0xFFFF_FFFF_FFFF_FFFF). The matcher sees a BBO-relative order that is infinitely aggressive and can therefore sweep any price level that exists. No special code path — same routing, same guarantees.


3. Delta order — BBO-relative pricing

The most powerful primitive. Instead of naming an absolute price, you say: "I want to be N ticks inside/through the spread".

The exchange computes the final price at the moment the order arrives:

Buy delta:   effective_price = best_ask + delta_ticks × tick_size
Sell delta:  effective_price = best_bid + delta_ticks × tick_size
# Touch buy — execute at the current best ask, rest the remainder
slot.submit_delta(asset_id, delta_ticks=0, qty=200, side=0)

# Two ticks above the ask — more aggressive, useful in fast markets
slot.submit_delta(asset_id, delta_ticks=2, qty=200, side=0)

If only part of the order fills, the remainder rests at the computed price — it becomes a regular limit order sitting at effective_price.

Why delta_ticks is in ticks, not in price

delta_ticks counts increments of tick_size, not raw ticks. So delta_ticks=1 always means "one minimum price step", regardless of what the tick_size is on a given asset.

If it were expressed in raw ticks you'd have to know the tick size of every asset to write portable strategy logic. With tick-count deltas:

  • delta_ticks=0 is always "touch"
  • delta_ticks=1 is always "one tick past touch"
  • Works identically across JTW, arena assets, and future instruments

Another consequence: the computed price is always on the tick grid by definition — no rounding, no remainder, no off-grid rejections.


4. IOC delta — execute and cancel the rest

Same as a delta order, but the unfilled remainder is cancelled immediately rather than resting on the book.

# Try to buy 500 at the best ask — fill what you can, cancel the rest
slot.submit_ioc(asset_id, delta_ticks=0, qty=500, side=0)

You get one or more fills (possibly none), then a Cancelled ack for the unfilled portion. No order is left on the book.

When to use IOC vs delta

  • Delta (GTC): you want the full quantity but are willing to wait — typical for size that the top-of-book can't absorb in one shot.
  • IOC: you want opportunistic fills — take what's there, leave no trace, no future exposure. Good for TWAP slices or signalling-sensitive strategies.

5. Passive peg (advanced)

Place at N ticks behind your own side of the spread:

Buy passive:   effective_price = best_bid − delta_ticks × tick_size
Sell passive:  effective_price = best_ask − delta_ticks × tick_size

delta_ticks=0 → join the best bid/ask exactly.
delta_ticks=1 → one tick behind — you're in the queue but not leading.

A convenience wrapper (submit_passive) is coming in v0.28. For now, use the raw wire:

from fastboxex.protocol.wire import Order, REFERENCE_MODE_DELTA, REMAINDER_REST

order = Order(
    user_id=slot.user_id,
    asset_id=asset_id,
    price=delta_ticks,     # interpreted as Δ in tick units
    quantity=qty,
    side=0,                # BUY
    _padding=[REFERENCE_MODE_DELTA, REMAINDER_REST, 1, 0, 0, 0],
    #                                               ^ delta_sign=1 → passive
)
slot.submit_raw(order)
Why passive pegs are useful for market-makers

A pure market-maker strategy wants to be at the top of the book but prefers to avoid being the only one there (adverse selection risk). A passive peg with delta_ticks=1 lets you sit one tick behind the best bid/ask — you earn liquidity-provider rebates without being the first to trade against an informed flow.

As the BBO moves, you can cancel and re-peg each tick rather than chasing it with absolute prices, which avoids race conditions where your cancel arrives after a fill.


Why this design is different from traditional exchanges

Traditional exchanges enumerate order types as a flat list: Limit, Market, IOC, FOK, Peg, Mid-Peg, Discretionary, TWAP… Each type has its own code path and its own edge cases. The interactions between them are often undocumented, and the definitions shift between venues.

FastBoxEx derives all of these from three binary parameters:

Parameter Wire field 0 1
reference_mode _padding[0] Absolute price (Limit) BBO-relative (Delta)
remainder_mode _padding[1] Rest on book (GTC) Cancel immediately (IOC)
delta_sign _padding[2] Aggressive (through spread) Passive (trail own side)

Every combination is valid. The four methods in the SDK (submit_order, submit_market, submit_delta, submit_ioc) are convenience wrappers for the most common vertices.

The big practical benefit is determinism:

  • Empty book on a delta/market order → BookEmpty rejection. Always. Never a silent drop.
  • Delta that would produce an astronomically wrong price → DeltaOverflow rejection.
  • Price outside the order collar → OutsidePriceBand rejection.

You always get an ack. You always know what happened.

Mathematical foundation

Let \(\tau\) be the tick size, \(B = \beta_\text{bid}\) and \(A = \beta_\text{ask}\) the current best bid and ask. The effective price is:

\[ p_\text{eff} = \begin{cases} p_\text{wire} & \texttt{reference\_mode} = 0 \quad (\text{Limit}) \\[4pt] \beta_\text{opp} + \sigma \cdot \delta \cdot \tau & \texttt{reference\_mode} = 1 \quad (\text{Delta}) \end{cases} \]

where \(\beta_\text{opp}\) is the best price on the opposite side for aggressive orders (\(\sigma = +1\)) and the same side for passive pegs (\(\sigma = -1\)), and \(\delta \in \mathbb{Z}_{\geq 0}\) is delta_ticks.

The market sentinel is \(\delta = \infty\) encoded as price = u64::MAX.

The remainder policy \(\rho \in \{0,1\}\) is applied after matching: \(\rho=1\) (IOC) cancels any unfilled volume with a RemainderCancelled engine event, serialised as OrderAck(Cancelled) with price = p_eff and remaining_volume = cancelled_qty.

The function is total: every valid input produces a deterministic output. The only partial inputs are the two explicit error cases (empty BBO, overflow) which map to named reject codes rather than undefined behaviour.


Rejection codes

Code Name Meaning What to do
11 BookEmpty Market/delta on empty opposite side Wait for liquidity or use a limit
12 DeltaOverflow delta_ticks × tick_size overflows Use a smaller delta_ticks
5 OutsidePriceBand p_eff outside order collar Reduce delta_ticks
7 RiskCheckFailed Not enough cash (buy) or inventory (sell) Check your balance
8 ZeroQuantity qty rounds to 0 after lot-size alignment Increase qty

Quick reference

# ── Limit ──────────────────────────────────────────────────────────
slot.submit_order(asset_id, price=1_000_000, qty=100, side=0)   # BUY at 100.00

# ── Market ─────────────────────────────────────────────────────────
slot.submit_market(asset_id, qty=100, side=0)   # BUY at any price

# ── Delta (GTC) ────────────────────────────────────────────────────
slot.submit_delta(asset_id, delta_ticks=0, qty=100, side=0)   # touch buy
slot.submit_delta(asset_id, delta_ticks=2, qty=100, side=0)   # 2 ticks above ask

# ── IOC delta ──────────────────────────────────────────────────────
slot.submit_ioc(asset_id, delta_ticks=0, qty=100, side=0)     # fill or cancel