Order Types
SportsPerp v1 supports market orders (immediate execution at the current oracle price) and three types of trigger orders that wait for a price condition before executing. A limit order book is planned for a later release; for now, trigger orders cover the equivalent use cases.
All trade-path instructions — open, close, partial close, and trigger fills — settle against
market.oracle_price. Only liquidation eligibility usesmark_price_ema. See Mark Price → What each instruction prices against.
Order type summary
| Type | When it fires | Use case | Instruction |
|---|---|---|---|
| Market | Immediately | Open or close at the current oracle price | open_position / close_position |
| Stop Loss (SL) | Oracle crosses a price adverse to your position | Cap downside on an open position | place_trigger_order (type 0) |
| Take Profit (TP) | Oracle crosses a price favorable to your position | Lock in gains automatically | place_trigger_order (type 1) |
| Limit Open | Oracle reaches a specified entry price | Enter only if price comes to you | place_trigger_order (type 2) |
All three trigger order types are executed by the permissionless keeper bot (keeper/index.mjs) which polls the program every 15 seconds and calls execute_trigger_close or execute_trigger_open when a trigger condition is satisfied.
Market orders
A market order is an immediate open or close at the current market.oracle_price. The instruction sequence is:
- Pre-flight checks — market not paused, oracle not stale (> 2h).
- Leverage cap —
min(market.max_leverage, tier_max, confidence_multiplier × market_max). - Apply funding — any pending funding delta is settled before the new position is recorded.
- Entry — position opened at
oracle_price. The mark EMA is not consulted; it is reserved for liquidation eligibility. - Fee — taker fee (10 bps) deducted from collateral.
For close orders: PnL is realized at the current oracle_price, fees are deducted, and funding is settled one final time before the position account is closed.
Trigger orders — overview
Trigger orders are placed on-chain with a target price, a direction, and (for SL/TP) a reference to the open position they will close. They live in their own PDA account (TriggerOrder) until:
- Executed — keeper calls the execute instruction when the trigger condition is met
- Cancelled — user calls
cancel_trigger_order - Expired — optional expiry timestamp; execution is permissionlessly triggerable at expiry to return reserved margin
The trigger condition matrix
Each order type + direction pair resolves to a price condition:
| Order type | Direction | Fires when oracle price is |
|---|---|---|
| Stop Loss | Long | Below trigger |
| Stop Loss | Short | Above trigger |
| Take Profit | Long | Above trigger |
| Take Profit | Short | Below trigger |
| Limit Open | Long | Below trigger (buy the dip) |
| Limit Open | Short | Above trigger (sell the rally) |
This mapping is hard-coded in trigger_order.rs and cannot be overridden.
Ordering and reduce-only
SL and TP orders are implicitly reduce-only. They can only close existing positions, never open opposing ones. The program enforces this at placement time — attempting to set reduce_only = false on a type-0 or type-1 order will fail.
Limit Open orders are the opposite — they open new positions when triggered. They require the full collateral to be escrowed at placement time so there is no gap between “keeper executes” and “funds are available.” See Limit Open for the full escrow + refund mechanics.
Order count cap
A user may hold up to 10 trigger orders per market (MAX_ORDERS_PER_MARKET = 10). This prevents griefing the keeper with thousands of unfillable orders while comfortably accommodating laddered SL/TP setups (e.g., 3-tier TP ladder across 5 markets = 15 orders total).
Each user has a UserOrderCounter PDA that provides monotonic order indexing across all their orders in a market, so cancelled order slots are not reused.
Keeper execution flow
The keeper (keeper/index.mjs) runs continuously and:
- Loads all
TriggerOrderaccounts viagetProgramAccountswith anaccountTypememcmp filter. - Every 15 seconds, fetches the current oracle price for each active market.
- Evaluates each order’s trigger condition against the oracle price.
- Submits
execute_trigger_closeorexecute_trigger_opentransactions for any orders whose conditions are met. - Earns a small keeper reward on successful execution — aligns incentives to keep keepers running.
If the keeper is temporarily offline, orders remain safely on-chain. Execution is permissionless — any account can call the execute instruction, so even if our keeper goes down, a third party (or the user themselves) can execute an eligible order.
Further reading
- Stop Loss & Take Profit — placement, reduce-only enforcement, edge cases.
- Limit Open — collateral escrow, expiry refund, reservation mechanics.
- Mark Price — what exact price triggers are evaluated against.
- Entry Price & PnL — fee handling on open vs. close.