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

Limit Open

A Limit Open order opens a new position when the oracle price reaches a specified threshold. Unlike stop-loss and take-profit orders — which close existing positions — a limit open is a deferred entry: the trader commits collateral up front, the keeper fires the open when the oracle reaches the target.

When it fires

DirectionFires when oracle is
LongBelow the trigger price (buy the dip)
ShortAbove the trigger price (sell the rally)

The condition is evaluated against market.oracle_price — the same raw oracle used by open_position directly. (The mark EMA is only used for liquidation eligibility.)

Placement and escrow

Placing a Limit Open is different from placing an SL/TP because collateral must be reserved up front — there’s no existing position to draw against.

await client.placeTriggerOrder({
  marketId,
  orderType: OrderType.LimitOpen,
  direction: Direction.Long,
  triggerPrice: new BN(450_000_000),
  size: new BN(500_000_000),              // desired 500 USDC notional
  collateralAmount: new BN(100_000_000),  // 100 USDC escrowed
  leverage: 500,                          // 5x at the 10^2 scale (5x = 500, 2x = 200)
  reduceOnly: false,                      // must be false
  expiry: unixNowSeconds + 7 * 24 * 3600, // 7 days, recommended
});

At placement:

  1. Validation — market not paused, oracle not stale, collateral meets minimums, leverage respects market cap and tier.
  2. Collateral transfer — the specified USDC is moved from the trader’s wallet into the global pool_token SPL account, earmarked to the TriggerOrder PDA. The placement is gated by pool_token.amount ≥ pool.risk_floor (RiskFloorBreached if the pool is below the floor). SL/TP placements are not gated.
  3. Reservation — the collateral is marked as reserved (not available for other positions or withdrawals) until the order executes, is cancelled, or expires.

The escrow is essential: without it, a trader could place 1,000 limit orders with no backing and grief the keeper or monopolize attention. Up-front reservation makes every limit order a genuine commitment.

Execution

When the oracle price crosses the trigger, the keeper calls execute_trigger_open:

  1. Pre-flight — trigger condition still satisfied at current oracle; market alive; oracle fresh.
  2. Leverage re-check — tier and confidence multipliers are recomputed at execution time. If the market’s effective_oi has contracted since placement (other traders closed positions on this market), the order may now exceed tier caps and the execution fails. The escrowed collateral is returned to the trader.
  3. Open at oracle — the position is opened at the current oracle price, not the trigger price. Same rationale as SL/TP: the trigger is a condition, not a price promise.
  4. Fee — taker fee (10 bps) deducted from the realized position value.
  5. Position created — a new UserPosition PDA is created with the escrowed collateral; the TriggerOrder PDA is closed.
  6. Keeper reward — paid to the keeper’s signer.

Expiry and cancellation

Cancellation (cancel_trigger_order): returns the full escrowed collateral to the trader’s wallet, closes the TriggerOrder account, and refunds the rent. Cancellable at any time before execution.

Expiry: if the order hasn’t fired by the expiry timestamp, anyone can call the cancellation instruction to expire it. The escrowed collateral returns to the trader, and the rent is returned to whoever expired it (a small incentive for cleanup).

Setting a reasonable expiry is strongly recommended for limit opens:

  • It ensures escrowed capital is eventually freed even if the trader forgets about the order.
  • It prevents dead orders from cluttering the keeper’s polling set.
  • In the extreme, a limit order without expiry would escrow collateral indefinitely if the trigger price never came.

Why execution price ≠ trigger price

Same rationale as Stop Loss & Take Profit. The trigger specifies the moment the order becomes eligible; the actual fill is at whatever the oracle price reads when the keeper transaction lands. In fast markets, this can differ slightly from the trigger — but it’s the economically correct fill price (what every other open/close in the same block also settles at).

Interaction with margin tiers

A limit order placed against deep effective_oi may run into tier caps when it executes later against a contracted effective_oi (or against an effective_oi floored at initial_capacity if real OI dropped to near zero). The execution-time re-check is strict: if the order’s size now exceeds its tier’s leverage cap, the order fails and escrow is returned. The trader is not silently downsized; they are notified by the keeper’s failed transaction (or the lack of execution and eventual expiry).

This is deliberate. Silent downsizing would change the risk profile of the position from what the trader specified. A failed order + refund is clearer.

Typical usage patterns

  • Laddered entry. Place three Limit Opens at 95%, 90%, 85% of current price. The dip fills them incrementally; partial fills are achieved by varying sizes across the ladder.
  • Contrarian entry. Long a team perp at a mark price well below current, betting on a specific event (e.g., “if Arsenal’s index drops below 580 after the next loss, I want in at 5x”).
  • Short into strength. Short a player perp when their index exceeds a specific overvaluation threshold.

Further reading