Exchange Concepts¶
Before writing strategies, understand how the exchange works.
Prices: raw ticks vs display units¶
All prices on the wire are integers in raw ticks.
| You see | Internal value | Display |
|---|---|---|
bid = 1_000_000 |
1 000 000 ticks | 100.0000 |
ask = 1_000_400 |
1 000 400 ticks | 100.0400 |
spread = 400 |
400 ticks | 0.0400 |
Always divide by 10_000 when printing prices to users.
print(f"bid = {md['bid'] / 10_000:.4f}") # ✓ correct
print(f"bid = {md['bid'] / 100:.2f}") # ✗ 100× too large
The tick_size for JTW is 100 — the smallest allowed price increment is 100 raw ticks (= 0.0100 display).
Order types¶
FastBoxEx uses a unified order framework: every order shares the same wire struct. Three control bytes select the pricing regime, remainder policy, and delta direction.
| SDK method | Behaviour |
|---|---|
submit_order(asset_id, price, qty, side) |
Limit — rests at explicit price |
submit_market(asset_id, qty, side) |
Market — sweeps book at any price |
submit_delta(asset_id, delta_ticks, qty, side) |
Delta — executes at BBO ± delta_ticks × tick_size; rests if partial |
submit_ioc(asset_id, delta_ticks, qty, side) |
IOC — same as delta, cancel remainder |
delta_ticks=0 → touch order — executes at the best available quote.
The unified price function
All four methods are derived from one formula:
Classical market orders are undefined on empty books. The delta framework is fully
deterministic: an empty book returns BookEmpty, an overflow returns DeltaOverflow.
No silent failures.
→ See the Order Types guide for the complete treatment.
The order lifecycle¶
stateDiagram-v2
direction LR
[*] --> Accepted : submit_order
Accepted --> PartialFill : partial match
PartialFill --> Filled : fully matched
Accepted --> Filled : fully matched
Accepted --> Cancelled : cancel_order / ctx.cancel_all
Accepted --> Rejected : risk limit / bad price
PartialFill --> Cancelled : cancel remaining
Filled --> [*]
Cancelled --> [*]
Rejected --> [*]
Each state transition generates an OrderAck message delivered to your on_fill / ack ring. order_status values:
| Value | Status | What to do |
|---|---|---|
1 |
New (accepted) | Order is live on the book |
2 |
Partially filled | Part filled; remainder still resting |
3 |
Rejected | Check the reject reason code |
4 |
Cancelled | Order removed (by you or IOC) |
5 |
Filled | Fully matched |
Handling acks robustly in your strategy
Most strategies only need to handle on_fill — which fires for status 2 and 5.
But for production-grade logic:
- Status 3 (Rejected): the
reject_reasonfield tells you why —BookEmpty,RiskCheckFailed,OutsidePriceBand, etc. Log it; don't silently retry in a loop. - Status 4 (Cancelled): arrives for explicit
cancel_all, but also for the IOC remainder of asubmit_ioccall. Theremaining_volumefield tells you how much was cancelled. - Status 2 (Partial): if you're tracking inventory, update on every partial fill — don't wait for the final fill.
Exchange lifecycle (sessions)¶
The exchange runs in sessions. Within each session, the matching engine moves through states:
| Phase | What happens | Can you trade? |
|---|---|---|
| BookOnly | Orders enter the book, no matching | Submit / Cancel only |
| Suspended | Paused — orders held | No |
| Auction | Equilibrium price discovery — matched at close | Submit only |
| Trade | Continuous double-auction matching | ✓ Full trading |
| SessionClosed | All open orders cancelled | No |
| Settlement | P&L computed, balances updated | No |
Your strategy runs during Trade phase. The StrategyEngine buffers ticks that arrive in other phases and delivers them once trading opens.
Arenas¶
Arenas are ephemeral, isolated matching engines you create on demand.
- Your own asset (separate from JTW), your own participants
- No settlement — balances reset on destroy
- Designed for AI training: no session timing, no real-world constraints
An arena has two participant roles:
| Role | Purpose |
|---|---|
| Generator | Provides continuous liquidity — posts the 10-level bid/ask book |
| Trader | Your strategy — interacts against the generator's book |
→ See the Arenas guide for complete examples.
Price convention summary¶
PRICE_SCALE = 10_000 # ticks per currency unit
TICK_SIZE = 100 # minimum price increment for JTW
# Convert raw → display
display_price = raw_price / PRICE_SCALE
# Convert display → raw
raw_price = int(display_price * PRICE_SCALE)
# Spread (raw ticks → display units)
spread_display = (md["ask"] - md["bid"]) / PRICE_SCALE
# PnL from fills (raw → display)
pnl_display = realized_pnl_raw / PRICE_SCALE