Skip to main content

Overview

  • Bracket (TP/SL): Attach Take Profit and/or Stop Loss to an entry or position. The legs form an OCO group (one-cancels-the-other).
  • Modes:
    • FULL: Bracket attaches at the position level for the full absolute position size; legs are reduce-only and typically execute IOC.
    • PARTIAL: Bracket attaches per fill of an entry order. Each taker/maker fill creates its own OCO legs sized to that fill; any resting remainder can later create legs when filled as maker.
  • Trigger: Each leg specifies a trigger_price and a direction is derived from the side and price relation. Activation occurs on boundary equality.
  • Reduce-only: Triggered legs never increase exposure. Size is clamped to the current position size; if no exposure to reduce, the leg is acknowledged and OCO is cancelled or re-armed.
  • Slippage guard: MARKET-type legs are converted to LIMIT IOC with a guard band (SLIPPAGE_GUARD_BPS, default 200 bps = 2%) anchored to the trigger or current mark.

Request shape (order_create)

{
  "payload": {
    "type": "order_create",
    "orders": [
      {
        "market_addr": "0x...",
        "side": "buy",
        "price": 100000000000,
        "size": 100000000,          // base units (scaled)
        "leverage": 10,
        "type": "limit",
        "is_cross": false,
        "time_in_force": "GTC",
        "bracket": {
          "mode": "FULL",          // or "PARTIAL"
          "take_profit": {          // optional
            "trigger_price": 105000000000,
            "order_type": "MARKET" // or "LIMIT"
            // "limit_price": 105000000000 // required when order_type == LIMIT
          },
          "stop_loss": {            // optional
            "trigger_price": 98000000000,
            "order_type": "LIMIT",
            "limit_price": 98000000000
          }
        },
        // Optional conditional entry (independent of bracket):
        // "trigger_price": 100000000000,
        // Optional reduce-only entry:
        // "reduce_only": true
      }
    ]
  },
  "nonce": 1,
  "signature": "0x..."
}

Behavior details

  • OCO lifecycle
    • When one leg executes, its sibling is cancelled by the trigger service upon acknowledgement.
    • Equality activates: Up-leg triggers when mark crosses up to trigger_price; Down-leg triggers when mark crosses down to trigger_price.
  • FULL mode
    • Bracket legs resize as the position size changes; cancelling/closing the position cancels the OCO group.
    • Legs are executed as reduce-only IOC. MARKET legs are converted to LIMIT IOC with guard: guard_price = trigger_price ± (trigger_price * GUARD_BPS / 10_000).
  • PARTIAL mode
    • Each fill (taker or maker) creates a distinct OCO group sized to that fill. All trigger IDs are unique across fills.
  • Reduce-only clamping
    • If current position size is smaller than the leg size, execution clamps to the closable amount. If zero, the service cancels the group (or re-arms based on exposure).

Environment and guard configuration

  • Env var: SLIPPAGE_GUARD_BPS (default 200 = 2%).

Examples

  • Attach FULL bracket to a BUY entry:
{
  "mode": "FULL",
  "take_profit": { "trigger_price": 105000000000, "order_type": "MARKET" },
  "stop_loss": { "trigger_price": 98000000000, "order_type": "LIMIT", "limit_price": 98000000000 }
}
  • Attach PARTIAL bracket to a BUY LIMIT entry:
{
  "mode": "PARTIAL",
  "take_profit": { "trigger_price": 110000000000, "order_type": "LIMIT", "limit_price": 110000000000 },
  "stop_loss": { "trigger_price": 95000000000, "order_type": "MARKET" }
}

Notes and constraints

  • For LIMIT legs, limit_price is required; invalid legs are ignored.
  • Triggered orders use time_in_force: IOC.
  • All prices and sizes are scaled integers; see market base_decimals and quote_decimals.

WebSocket examples

  • When a TP/SL leg executes, you will receive standard fill and order events on the private stream.
Order update (reduce-only IOC leg placed/cancelled/filled):
{
  "op": "event",
  "topic": "order",
  "data": [
    {
      "sid": "0xordLeg...",
      "side": "sell",
      "size": 100000000,
      "price": 105000000000,
      "leverage": 10,
      "initial_margin": null,
      "type": "limit",
      "status": "filled",
      "user_addr": "0xuser...",
      "market_addr": "0xmarket...",
      "is_cross": false,
      "seq": 1234,
      "timestamp": 1718000000,
      "timestamp_ms": 1718000000000
    }
  ]
}
Fill event generated by the leg execution:
{
  "op": "event",
  "topic": "fill",
  "data": [
    {
      "sid": "0xfill...",
      "side": "sell",
      "size": 100000000,
      "price": 105000000000,
      "maker_fee": 0,
      "taker_fee": 0,
      "taker_order_sid": "0xordLeg...",
      "taker_addr": "0xuser...",
      "maker_order_sid": "0xordMaker...",
      "maker_addr": "0xother...",
      "market_addr": "0xmarket...",
      "seq": 1234,
      "maker_leverage": 10,
      "taker_leverage": 10,
      "maker_is_cross": false,
      "taker_is_cross": false,
      "timestamp": 1718000000,
      "timestamp_ms": 1718000000000
    }
  ]
}
Position update reflecting reduced size:
{
  "op": "event",
  "topic": "position",
  "data": [
    {
      "sid": "0xpos...",
      "market_addr": "0xmarket...",
      "user_addr": "0xuser...",
      "size": 0,
      "price": 100000000000,
      "entry_price": 100000000000,
      "margin": 5000000000,
      "funding_index": 0,
      "epoch": 42,
      "is_cross": false,
      "initial_margin": 0,
      "initial_margin_mark": 0,
      "maintenance_margin": 0,
      "leverage": null,
      "mark_price": 105000000000,
      "seq": 1234,
      "timestamp": 1718000000,
      "timestamp_ms": 1718000000000,
      "side": "buy",
      "unrealized_pnl": 0,
      "liq_price": null
    }
  ]
}