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

# Math helpers

> Ostium liquidation price, PnL, and rollover fee as pure functions — no client, no network.

`liquidationPrice()`, `pnl()` and `rolloverFee()` are standalone functions over plain numbers — no client, no network. For bots, backtesters and risk dashboards.

All three run `@ostium/formulae`, the same math the contracts use.

```ts theme={null}
import {
  liquidationPrice, pnl, rolloverFee, maxWithdrawable, maxAddCollateral,
} from '@ostium/builder-sdk';
```

## liquidationPrice

```ts theme={null}
liquidationPrice({
  entryPx: 100_000,
  isLong: true,
  collateral: 1000,
  leverage: 10,
  maxLeverage: 100,
});  // → 90_250
```

| Parameter     | Type      | Default | Description                                                           |
| ------------- | --------- | ------- | --------------------------------------------------------------------- |
| `entryPx`     | `number`  | —       | Price the position opened at.                                         |
| `isLong`      | `boolean` | —       | Direction.                                                            |
| `collateral`  | `number`  | —       | Collateral backing the position, in USDC.                             |
| `leverage`    | `number`  | —       | Position leverage.                                                    |
| `maxLeverage` | `number`  | —       | Max leverage allowed for the pair — this sets the maintenance margin. |
| `rollover`    | `number`  | `0`     | Rollover accrued so far, in USDC.                                     |
| `funding`     | `number`  | `0`     | Funding accrued so far, in USDC.                                      |

Accrued fees push the liquidation price toward the entry price, so a long-held position liquidates sooner than it did at open. Pass `rollover` for the current figure.

## pnl

```ts theme={null}
pnl({ entryPx: 100_000, markPx: 105_000, isLong: true, collateral: 1000, leverage: 10 });
// → { netPnl: 500, netPnlPercent: 50, netValue: 1500 }
```

| Parameter         | Type      | Default    | Description                                                                                             |
| ----------------- | --------- | ---------- | ------------------------------------------------------------------------------------------------------- |
| `entryPx`         | `number`  | —          | Price the position opened at.                                                                           |
| `markPx`          | `number`  | —          | Current mark price.                                                                                     |
| `isLong`          | `boolean` | —          | Direction.                                                                                              |
| `collateral`      | `number`  | —          | Collateral backing the position, in USDC.                                                               |
| `leverage`        | `number`  | —          | Position leverage.                                                                                      |
| `highestLeverage` | `number`  | `leverage` | Highest leverage the position has ever run at. Only differs after collateral has been added or removed. |
| `rollover`        | `number`  | `0`        | Rollover accrued, in USDC.                                                                              |
| `funding`         | `number`  | `0`        | Funding accrued, in USDC.                                                                               |

Returns `netPnl` (USDC, after rollover and funding), `netPnlPercent` (as a percentage of collateral) and `netValue` (`collateral + netPnl` — what the position is worth if closed now).

## rolloverFee

```ts theme={null}
const { pairs } = await client.getPairs();
const { pairPositions } = await client.getOpenPositions({ user });
const { position } = pairPositions[0];
const pair = pairs.find(p => p.pairId === position.pairId)!;

const accrued = rolloverFee({
  rollover: pair.rollover,
  isLong: position.side === 'B',
  snapshot: position.rolloverSnapshot,
  collateral: Number(position.collateralUsed),
  leverage: Number(position.leverage),
  blockNumber: await publicClient.getBlockNumber(),   // your own viem client
});

const liq = liquidationPrice({
  entryPx: Number(position.entryPx),
  isLong: position.side === 'B',
  collateral: Number(position.collateralUsed),
  leverage: Number(position.leverage),
  maxLeverage: Number(position.maxLeverage),
  rollover: accrued,
});
```

| Parameter     | Type                         | Description                                                              |
| ------------- | ---------------------------- | ------------------------------------------------------------------------ |
| `rollover`    | `PairRollover`               | The pair's raw accumulators, from `Pair.rollover`.                       |
| `isLong`      | `boolean`                    | `true` for a long position.                                              |
| `snapshot`    | `string`                     | The position's `rolloverSnapshot` — the accumulator when it opened.      |
| `collateral`  | `number`                     | Collateral backing the position, in USDC.                                |
| `leverage`    | `number`                     | Position leverage.                                                       |
| `blockNumber` | `bigint \| number \| string` | Current chain block. Rollover accrues per block, so this sets the clock. |

Positive means the trader pays.

Rollover accumulators are global per pair, not per position, so one [`getPairs()`](/developer/reference/get-pairs) poll plus a block number lets you keep liquidation price and PnL current for any number of positions locally — no per-position read, and no re-fetching positions.

[`createStore()`](/developer/reference/create-store) does this for you.

## maxWithdrawable / maxAddCollateral

The two collateral-edit limits, for a ticket that has to bound its own input.

```ts theme={null}
maxWithdrawable({
  collateral: 1000, leverage: 10, maxLeverage: 100, notional: 10_000,
  entryPx: 100_000, exitPx: 100_000, isLong: true, pnl: -300,
});  // → 675

maxAddCollateral({ collateral: 1000, leverage: 10, minLeverage: 2 });  // → 4000
```

Removing collateral raises leverage, so it is bounded by three limits at once and `maxWithdrawable` returns the smallest: the leverage cap, liquidation safety (accrued fees and unrealised loss), and profit protection. The leverage cap alone overstates the answer on a losing position — 900 in the example above.

Adding collateral lowers leverage, so that direction is bounded by the pair group's minimum, `Pair.minLeverage`.

`Position.maxWithdrawable` is this number, already computed for an open position.

## Related

* [createStore](/developer/reference/create-store)
* [getPairs](/developer/reference/get-pairs)
