đźš§ SportsPerp is currently live on devnet. Mainnet target: before Jun 12, 2026 (World Cup kickoff).
TradingOrder Types

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 uses mark_price_ema. See Mark Price → What each instruction prices against.

Order type summary

TypeWhen it firesUse caseInstruction
MarketImmediatelyOpen or close at the current oracle priceopen_position / close_position
Stop Loss (SL)Oracle crosses a price adverse to your positionCap downside on an open positionplace_trigger_order (type 0)
Take Profit (TP)Oracle crosses a price favorable to your positionLock in gains automaticallyplace_trigger_order (type 1)
Limit OpenOracle reaches a specified entry priceEnter only if price comes to youplace_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:

  1. Pre-flight checks — market not paused, oracle not stale (> 2h).
  2. Leverage cap — min(market.max_leverage, tier_max, confidence_multiplier × market_max).
  3. Apply funding — any pending funding delta is settled before the new position is recorded.
  4. Entry — position opened at oracle_price. The mark EMA is not consulted; it is reserved for liquidation eligibility.
  5. 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 typeDirectionFires when oracle price is
Stop LossLongBelow trigger
Stop LossShortAbove trigger
Take ProfitLongAbove trigger
Take ProfitShortBelow trigger
Limit OpenLongBelow trigger (buy the dip)
Limit OpenShortAbove 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:

  1. Loads all TriggerOrder accounts via getProgramAccounts with an accountType memcmp filter.
  2. Every 15 seconds, fetches the current oracle price for each active market.
  3. Evaluates each order’s trigger condition against the oracle price.
  4. Submits execute_trigger_close or execute_trigger_open transactions for any orders whose conditions are met.
  5. 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