Trading Guide
This guide covers how to trade on Hash Power Perps: the order book model, placing and matching orders, and managing their lifecycle.
Prerequisites
- Collateral deposited in the shared vault — see Collateral & Accounts.
- Wallet connected to the correct network.
- Enough free margin to cover the Initial Margin of the order you place.
The order book model
Hash Power Perps is a fully on-chain central limit order book (CLOB):
- Orders rest on a fixed price grid. Every price must be a multiple of
minimumPriceIncrement(the tick size); off-grid prices revertInvalidPrice. - Each side keeps sorted price levels — bids high-to-low, asks low-to-high — and within a level orders queue FIFO (price-time priority).
- Quantities use
QUANTITY_DECIMALS = 6and are signed: positive = long (buy), negative = short (sell).
There is a single entry point for trading — createOrder — which behaves as
both a marketable (aggressive) order and a resting (passive) order depending
on the book.
Placing an order
createOrder(uint256 price, int256 quantity, TimeInForce tif)
price— your limit price (must be on the tick grid).quantity— signed size. Positive buys/longs, negative sells/shorts.tif— how long the order lives:GTCrests the unfilled remainder,IOCcancels it,FOKrequires the whole size to fill at once.
What happens, in order:
- Global funding is refreshed and your funding is settled.
- The order matches immediately against the opposite side, walking
prices from best toward your limit:
- A buy fills against asks priced at or below your limit.
- A sell fills against bids priced at or above your limit.
- Any quantity left after matching rests on the book at your limit price (subject to the limits below).
- Unless the order is reduce-only, your Initial Margin is checked
(
_ensureInitialMargin); insufficient free margin reverts.
To trade like a market order, submit an aggressive limit price (e.g. far through the book); it fills against everything up to that price and rests the remainder. To trade passively, price it away from the top of book so it rests as a maker order.
Simulating first
simulateOrder(price, quantity) is a read-only preview that returns the
filledQuantity, the volume-weighted averageFillPrice, and the
remainingQuantity that would rest — without sending a transaction.
How matching works
Orders match when they cross:
- Crossing price — buy limit ≥ ask, or sell limit ≤ bid.
- Opposite direction — buys take from the ask queue, sells from the bid queue.
- FIFO within a level — the oldest maker order at a price fills first.
Trades execute at the maker's price, so an aggressive taker gets price improvement when the resting order is better than its limit.
flowchart TD
A["createOrder(price, quantity, tif)"] --> B["Refresh and settle funding"]
B --> C{"Crosses the book?<br/>buy: ask at or below limit<br/>sell: bid at or above limit"}
C -->|Yes| D["Fill best levels first, FIFO within a level,<br/>at the maker's price"]
D --> E{"Quantity left?"}
C -->|No| E
E -->|Yes| F["Rest remainder on the book at your limit"]
E -->|No| G["Fully filled"]
F --> H{"Reduce-only?"}
G --> H
H -->|No| I["Check Initial Margin (revert if short)"]
H -->|Yes| J["Skip margin check"]
Example book depth (asks high→low, bids high→low; each level is a FIFO queue):
| Side | Price | Resting queue (oldest first) |
|---|---|---|
| Ask | 4.15 | Seller A |
| Ask | 4.14 | Seller B |
| Ask | 4.12 | Seller C |
| Bid | 4.11 | Buyer Y, Buyer Z |
| Bid | 4.10 | Buyer W |
- New buy @ 4.12 → fills Seller C at 4.12.
- New sell @ 4.11 → fills Buyer Y first (oldest in the queue).
getBestBidPrice() / getBestAskPrice() return the top of book, and
getQuantityAtPrice(price, isBid) the resting size at a level.
Positions from fills
Each fill updates your single net position rather than creating separate lots:
- Adding in the same direction updates your weighted-average entry price.
- Trading the opposite direction reduces the position (realizing PnL on the closed part), and if it exceeds your size, flips you to the other side at the new price.
See Positions & Funding for the details.
Reduce-only fills skip the margin check
If an order is on the opposite side of your current position and does not exceed its size, it is treated as reduce-only and skips the Initial Margin check — you can always de-risk, even when close to your margin limits.
Closing or reducing a position
There is no expiry and no settlement step. To close, submit an opposite-side order:
Open: +1.0 long @ 4.10
Close: -1.0 sell @ 4.14
Realized PnL = (4.14 − 4.10) × 1.0 = 0.04 per unit (before fees/funding)
Fully offsetting flattens the position; partially offsetting leaves a smaller position at the same entry price.
Cancelling resting orders
cancelOrder(bytes32 orderId)
Removes your unmatched order from the book and releases the margin it
reserved. Only the order's owner can cancel it (OrderNotBelongToSender).
Order limits
| Limit | Value / rule |
|---|---|
| Price grid | Multiple of minimumPriceIncrement (else InvalidPrice) |
| Max resting orders per user | MAX_ORDERS_PER_PARTICIPANT = 100 (MaxOrdersPerParticipantReached) |
| Max price levels per side | MAX_PRICE_LEVELS_PER_SIDE = 200 (MaxPriceLevelsReached) |
| Margin | Portfolio IM computed by the PME |
| Quantity | Non-zero, QUANTITY_DECIMALS = 6 (InvalidSize) |
minimumMarginPerOrder, its setter/event, and OrderMarginTooLow are deprecated compatibility ABI only; the configured value is not enforced.
Useful views
| View | Returns |
|---|---|
simulateOrder(price, qty) |
Preview fill / average price / resting remainder |
getBestBidPrice() / getBestAskPrice() |
Top of book |
getQuantityAtPrice(price, isBid) |
Resting quantity at a price level |
getOrder(orderId) |
A single order |
getUserOrders(user) |
A user's open order ids |
Read next
- Positions & Funding — what happens after a fill.
- Fees — what each trade costs.