> ## Documentation Index
> Fetch the complete documentation index at: https://docs.0xmonaco.com/llms.txt
> Use this file to discover all available pages before exploring further.

# usePositions

> React hook for managing perp positions

```tsx theme={null}
import { usePositions } from "@0xmonaco/react";

const {
  listPositions,
  getPosition,
  getPositionRisk,
  closePosition,
  batchCloseAllPositions,
  attachPositionTpSl,
  listPositionHistory,
} = usePositions();
```

The hook returns the same surface as [`sdk.positions`](/sdk/typescript/positions). Each method is the same async function, ready to call from components or effects. There's no state, subscriptions, or auto-refresh built in — wrap calls in your own `useEffect` / SWR / React Query as suits your app.

For end-to-end flow and UI patterns, see [Positions](/developers/positions).

***

## Read

<ResponseField name="listPositions" type="(params?: ListPositionsParams) => Promise<ListPositionsResponse>">
  Paginated list of positions.

  **Parameters:**

  * `params?`:
    * `marginAccountId?`: string
    * `tradingPairId?`: string
    * `status?`: `PositionStatusFilter` — `"OPEN" | "CLOSED" | "LIQUIDATING" | "LIQUIDATED"`
    * `pageSize?`: number — 1 to 1000, defaults to 20
    * `pageToken?`: string — pagination cursor: omitted or `""` starts the walk; a previous `nextPageToken` resumes it. There is no `page`: the SDK rejects one with a `ValidationError` before sending, and a raw REST request carrying it gets `400`

  **Returns:** `{ positions: Position[]; total: number; pageSize: number; nextPageToken: string; totalCapped: boolean }`. Follow `nextPageToken` until it comes back `""`; each page over-fetches one row, so an empty token is definitive.

  **Example — positions panel:**

  ```tsx theme={null}
  function PositionsPanel({ marginAccountId }: { marginAccountId: string }) {
    const { listPositions } = usePositions();
    const [positions, setPositions] = useState<Position[]>([]);

    useEffect(() => {
      let cancelled = false;
      (async () => {
        const result = await listPositions({
          marginAccountId: marginAccountId,
          status: "OPEN",
        });
        if (!cancelled) setPositions(result.positions);
      })();
      return () => { cancelled = true; };
    }, [marginAccountId, listPositions]);

    return (
      <ul>
        {positions.map((p) => (
          <li key={p.positionId}>
            {p.side} {p.size} @ {p.entryPrice} — PnL {p.unrealizedPnl}
          </li>
        ))}
      </ul>
    );
  }
  ```
</ResponseField>

<ResponseField name="getPosition" type="(positionId: string) => Promise<Position>">
  Full snapshot of a single position. Includes side, size, entry/mark/liquidation prices, isolated margin, leverage, and PnL. See [`Position` type](/sdk/typescript/positions).
</ResponseField>

<ResponseField name="getPositionRisk" type="(positionId: string) => Promise<PositionRisk>">
  Volatile risk fields (mark price, liquidation price, margin ratio, unrealized PnL). Cheaper than `getPosition` for live displays.

  **Example — live liquidation distance:**

  ```tsx theme={null}
  function LiquidationBar({ positionId }: { positionId: string }) {
    const { getPositionRisk } = usePositions();
    const [distance, setDistance] = useState<number | null>(null);

    useEffect(() => {
      const tick = async () => {
        const risk = await getPositionRisk(positionId);
        const mark = parseFloat(risk.markPrice);
        const liq = parseFloat(risk.liquidationPrice);
        setDistance(mark > 0 ? (mark - liq) / mark : null);
      };
      tick();
      const id = setInterval(tick, 2000);
      return () => clearInterval(id);
    }, [positionId, getPositionRisk]);

    if (distance == null) return null;
    return <div>Liquidation distance: {(distance * 100).toFixed(2)}%</div>;
  }
  ```
</ResponseField>

<ResponseField name="listPositionHistory" type="(params?: ListPositionHistoryParams) => Promise<ListPositionHistoryResponse>">
  Lifecycle events for one position or all positions in scope.

  **Parameters:**

  * `params?`:
    * `positionId?`: string
    * `marginAccountId?`: string
    * `tradingPairId?`: string
    * `reductionOnly?`: boolean — when `true`, returns only reductions, closes and liquidations
    * `pageSize?`: number — 1 to 1000, defaults to 20
    * `pageToken?`: string — pagination cursor: omitted or `""` starts the walk; a previous `nextPageToken` resumes it. There is no `page`: the SDK rejects one with a `ValidationError` before sending, and a raw REST request carrying it gets `400`

  **Returns:** `{ events: PositionHistoryEvent[]; pageSize: number; nextPageToken: string }`. This listing carries no `total`: the relation spans the archive, so it is not counted behind a filter.
</ResponseField>

***

## Modify

<Note>
  This hook has no margin-adjustment method. Adjust a position's margin through the margin-account SDK surface — every leg
  moves collateral between a risk bucket and the parent margin account, never to
  or from the spot wallet:

  * `transferCollateralToRiskBucket` adds margin: by default it commits the amount to the open position's stored margin (leverage drops and stays down); pass `applyToPositionMargin: false` to fund the bucket with spendable free collateral instead. The pair-scoped `transferCollateralToMarginAccount` is deprecated and matches the default.
  * `transferCollateralFromMarginAccount` (with `tradingPairId`) releases collateral back to the parent.
</Note>

<ResponseField name="attachPositionTpSl" type="(positionId, request) => Promise<AttachPositionTpSlResponse>">
  Attach take-profit and/or stop-loss conditional orders to the position. At least one leg is required. Pass `oco: true` to cancel the sibling once one leg triggers (requires both legs).

  **Parameters:**

  * `positionId`: string
  * `request`:
    * `takeProfit?`: `TpSlLeg`
    * `stopLoss?`: `TpSlLeg`
    * `trailingStop?`: `TrailingStopLeg` — `{ trailBps, activationPrice?, closePosition?, quantity?, slippageToleranceBps?, expiresAt? }`; one per position, never part of `oco`. See [Trailing Stop](/developers/trade#trailing-stop)
    * `oco?`: boolean — cancel the sibling leg after one triggers; requires both fixed legs

  **`TpSlLeg`:** `{ triggerPrice, orderType: "MARKET" | "LIMIT", limitPrice?, closePosition?, quantity?, timeInForce?, slippageToleranceBps?, expiresAt? }`. Set `closePosition: true` to resolve the full live position size on trigger (omit `quantity`). See [Order Management](/developers/trade#conditional-perps-orders).

  **Example — TP/SL modal submit:**

  ```tsx theme={null}
  const { attachPositionTpSl } = usePositions();

  async function onSubmit(values: { tp: string; sl: string }) {
    await attachPositionTpSl(positionId, {
      takeProfit: { triggerPrice: values.tp, orderType: "MARKET", closePosition: true },
      stopLoss: { triggerPrice: values.sl, orderType: "MARKET", closePosition: true },
      oco: true,
    });
  }
  ```
</ResponseField>

***

## Close

<ResponseField name="closePosition" type="(positionId, request) => Promise<ClosePositionResponse>">
  Submit a closing order. Position transitions to `CLOSED` only after the closing order fills.

  **Parameters:**

  * `positionId`: string
  * `request`:
    * `closeType`: `"MARKET" | "LIMIT" | "IOC"`
    * `limitPrice?`: string — required for `LIMIT` and `IOC`, forbidden only for `MARKET`
    * `quantity?`: string — defaults to full position size
    * `slippageToleranceBps?`: number — for `MARKET` / `IOC`

  **Example — partial market close:**

  ```tsx theme={null}
  const { closePosition } = usePositions();

  await closePosition(positionId, {
    closeType: "MARKET",
    quantity: "0.5",
  });
  ```
</ResponseField>

<ResponseField name="batchCloseAllPositions" type="(request?: BatchCloseAllRequest) => Promise<BatchCloseAllResponse>">
  Submit a MARKET reduce-only close for every open position owned by the caller — a one-call "panic close". Best-effort and partial: each position closes independently, and per-position failures are reported in `results` instead of aborting the batch. See [`BatchCloseAllResponse` type](/sdk/typescript/positions).

  **Parameters:**

  * `request?`:

    * `tradingPairId?`: string — limit the close to one trading pair. Cannot be combined with `tradingPairIds`
    * `tradingPairIds?`: string\[] — limit the close to any of these trading pairs (1 to 256), in one request. An empty array is rejected; omit the field to close every pair
    * `side?`: `"LONG" | "SHORT"` — limit the close to one side
    * `slippageToleranceBps?`: number — applied to each MARKET close

    With no pair filter, every open position is closed.

  **Example — close-all button:**

  ```tsx theme={null}
  const { batchCloseAllPositions } = usePositions();

  async function onCloseAll() {
    const { totalClosed, totalFailed } = await batchCloseAllPositions();
    if (totalFailed > 0) {
      toast.warn(`${totalClosed} closing, ${totalFailed} failed`);
    }
  }
  ```
</ResponseField>

***

## Subscribing to lifecycle events

`usePositions` doesn't ship a built-in subscription. For live updates of conditional-order lifecycle (TP/SL state changes), use `sdk.ws.conditionalOrders` — see the [WebSocket reference](/sdk/typescript/websockets).

For order-fill confirmations after a `closePosition` or attached-TP/SL trigger, subscribe to `sdk.ws.userOrders`.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.