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

# Portfolio & Positions

> Track positions and history with the TypeScript SDK

## PortfolioFetcher Setup

`PortfolioFetcher` provides access to your profile, positions, trade history, and accumulated points. Authentication is required.

```typescript theme={null}
import { HttpClient, PortfolioFetcher } from '@limitless-exchange/sdk';

const httpClient = new HttpClient({
  baseURL: 'https://api.limitless.exchange',
  hmacCredentials: {
    tokenId: process.env.LMTS_TOKEN_ID,
    secret: process.env.LMTS_TOKEN_SECRET,
  },
});

const portfolio = new PortfolioFetcher(httpClient);
```

## Current Profile

Call `getProfile()` without an address to fetch the authenticated caller's private profile via `GET /profiles/me`. This avoids passing or persisting the wallet address when the client is already authenticated.

```typescript theme={null}
const currentProfile = await portfolio.getProfile();

console.log(currentProfile.id); // numeric profile ID
console.log(currentProfile.account); // authenticated wallet address
console.log(currentProfile.rank?.feeRateBps); // fee rate used for signing
```

To keep the existing address-based lookup, pass an address:

```typescript theme={null}
const profileByAddress = await portfolio.getProfile('0xYOUR_WALLET_ADDRESS');
```

<Note>
  `getProfile()` without an address calls `GET /profiles/me`; `getProfile(address)` calls `GET /profiles/:account`. Both return the private authenticated profile shape and require authentication.
</Note>

## All Positions

Fetch all positions across CLOB and AMM markets in a single call:

```typescript theme={null}
const positions = await portfolio.getPositions();

console.log('CLOB positions:', positions.clob.length);
console.log('AMM positions:', positions.amm.length);
console.log('Accumulative points:', positions.accumulativePoints);
```

### Response Structure

```typescript theme={null}
interface PositionsResponse {
  clob: ClobPosition[];
  amm: AmmPosition[];
  accumulativePoints: number;
}
```

## CLOB Positions

Retrieve only your CLOB market positions:

```typescript theme={null}
const clobPositions = await portfolio.getCLOBPositions();

for (const position of clobPositions) {
  console.log(`Market: ${position.market.slug}`);
  console.log(`  YES — size: ${position.positions.yes.size}, PnL: ${position.positions.yes.unrealizedPnl}`);
  console.log(`  NO  — size: ${position.positions.no.size}, PnL: ${position.positions.no.unrealizedPnl}`);
}
```

### CLOB Position Structure

```typescript theme={null}
interface ClobPosition {
  market: {
    slug: string;
    title: string;
  };
  positions: {
    yes: {
      size: number;
      collateral: number;
      unrealizedPnl: number;
    };
    no: {
      size: number;
      collateral: number;
      unrealizedPnl: number;
    };
  };
}
```

| Field           | Description                                                      |
| --------------- | ---------------------------------------------------------------- |
| `size`          | Number of shares held                                            |
| `collateral`    | USDC value of collateral locked                                  |
| `unrealizedPnl` | Unrealized profit/loss in USDC based on current orderbook prices |

## AMM Positions

Retrieve only your AMM market positions:

```typescript theme={null}
const ammPositions = await portfolio.getAMMPositions();

for (const position of ammPositions) {
  console.log(`Market: ${position.market.slug}`);
  console.log(`  YES — size: ${position.positions.yes.size}`);
  console.log(`  NO  — size: ${position.positions.no.size}`);
}
```

## Trade History

Fetch paginated trade history:

```typescript theme={null}
const history = await portfolio.getUserHistory();

for (const entry of history.data) {
  console.log(
    entry.strategy,            // e.g. "Market Buy" / "Market Sell" / "Split" / "Convert"
    entry.market?.slug,
    entry.outcomeTokenAmount,  // filled size (shares)
    entry.outcomeTokenPrice,   // size-weighted average price
    entry.transactionHash,     // settlement tx hash — your join key to a placed order
  );
}

// Next page: pass the returned cursor
if (history.nextCursor) {
  const page2 = await portfolio.getUserHistory(history.nextCursor, 20);
}
```

### Parameters

| Parameter | Type     | Default | Description                                                                       |
| --------- | -------- | ------- | --------------------------------------------------------------------------------- |
| `cursor`  | `string` | —       | Opaque cursor from the previous response's `nextCursor`. Omit for the first page. |
| `limit`   | `number` | `20`    | Number of entries per page                                                        |

### Response

`getUserHistory()` resolves to `{ data, nextCursor }`. The feed is a merged, newest-first activity stream (CLOB trades, AMM trades, splits/merges, NegRisk conversions, claims); tell entry kinds apart by `strategy`. Each entry in `data`:

| Field                 | Type       | Description                                                                                                          |
| --------------------- | ---------- | -------------------------------------------------------------------------------------------------------------------- |
| `blockTimestamp`      | `number`   | Mined time, Unix seconds                                                                                             |
| `market`              | `object`   | Market snapshot (`id`, `slug`, `title`, `deadline`)                                                                  |
| `outcomeIndex`        | `number`   | `0` = YES, `1` = NO                                                                                                  |
| `outcomeTokenAmount`  | `string`   | Filled size (shares)                                                                                                 |
| `outcomeTokenAmounts` | `string[]` | Per-outcome split `[yes, no]`                                                                                        |
| `outcomeTokenPrice`   | `number`   | Size-weighted average fill price                                                                                     |
| `collateralAmount`    | `string`   | USDC spent / received                                                                                                |
| `strategy`            | `string`   | `Buy` · `Sell` · `Limit Buy` · `Limit Sell` · `Market Buy` · `Market Sell` · `Split` · `Merge` · `Convert` · `Claim` |
| `transactionHash`     | `string`   | Settlement tx hash — the key to match an entry to an order you placed                                                |

<Note>
  A filled order maps to a single trade entry, keyed by `transactionHash` — the same value returned as `execution.txHash` on the mined `POST /orders` response and on the `MINED` settlement WebSocket event. There is no order id on history entries, so `transactionHash` is the join key.
</Note>

## Combining Positions with Market Data

For a richer view, combine position data with live market information:

```typescript theme={null}
import { MarketFetcher } from '@limitless-exchange/sdk';

const marketFetcher = new MarketFetcher(httpClient);
const positions = await portfolio.getCLOBPositions();

for (const position of positions) {
  const market = await marketFetcher.getMarket(position.market.slug);
  const orderbook = await marketFetcher.getOrderBook(position.market.slug);

  const bestBid = orderbook.bids[0]?.price ?? 0;
  const bestAsk = orderbook.asks[0]?.price ?? 1;
  const midPrice = (bestBid + bestAsk) / 2;

  console.log(`${market.title}`);
  console.log(`  Mid price: ${midPrice.toFixed(4)}`);
  console.log(`  YES: ${position.positions.yes.size} shares, PnL: ${position.positions.yes.unrealizedPnl}`);
  console.log(`  NO:  ${position.positions.no.size} shares, PnL: ${position.positions.no.unrealizedPnl}`);
}
```

<Tip>
  For real-time PnL tracking, combine `PortfolioFetcher` with WebSocket orderbook updates rather than repeatedly polling `getOrderBook()`. See the [WebSocket Streaming](/developers/sdk/typescript/websocket) guide.
</Tip>

## Error Handling

Portfolio endpoints require a valid API key. If the key is missing or invalid, the SDK throws an `APIError` with status `401`.

```typescript theme={null}
import { APIError } from '@limitless-exchange/sdk';

try {
  const positions = await portfolio.getPositions();
} catch (error) {
  if (error instanceof APIError) {
    if (error.status === 401) {
      console.error('Authentication failed. Check your API token credentials.');
    } else {
      console.error(`API error ${error.status}: ${error.message}`);
    }
  } else {
    throw error;
  }
}
```

See the [Error Handling and Retry](/developers/sdk/typescript/error-handling) guide for retry strategies and common error codes.
