# Trade History & Portfolio

Base URL: `https://service.leverup.xyz`

## Trade history

Every protocol event affecting a trader, decoded and joined against the position it belongs to. This
is the easiest way to build an activity feed without replaying onchain logs.

```http
GET /v1/user/{user_address}/trade/history
```

| Parameter | Default | Notes |
| :--- | :--- | :--- |
| `page` | `0` | |
| `size` | `10` | |
| `block_chain` | `MONAD` | |

### Response

```json
{
  "content": [
    {
      "id": "…",
      "transactionHash": "0x…",
      "blockNumber": 1234567,
      "transactionIndex": 3,
      "logIndex": 7,
      "hash": "0x…",
      "positionHash": "0x…",
      "pairBase": "0xcf5a…",
      "qty": "10000000",
      "entryPrice": "100123450000000000000000",
      "closePrice": null,
      "pnl": null,
      "tradeType": "MARKET",
      "operationType": "OPEN_POSITION",
      "blockTime": "2026-07-27T10:00:00Z",
      "isLong": true,
      "tokenIn": "0x7547…",
      "lvToken": "0xfd44…",
      "amountIn": "10000000",
      "margin": "9930000000000000000",
      "tokenInPrice": "1000000000000000000",
      "position": { },
      "detail": { }
    }
  ],
  "pageNumber": 0,
  "pageSize": 10,
  "totalPages": 12,
  "totalElements": 116
}
```

`position` embeds the same object returned by [Positions](/api/positions), so a single history page
gives you both the event and the position state.

Units follow the onchain conventions: `qty` at 1e10, `entryPrice` / `closePrice` at 1e18, `amountIn`
in `tokenIn` decimals, and `margin` / `pnl` in `lvToken` decimals. See
[Precision & Units](/introduction/precision).

### `operationType`

Each value corresponds to the onchain event of the same name — see [Events](/onchain/events).

| Group | Values |
| :--- | :--- |
| Opening | `MARKET_PENDING_TRADE`, `OPEN_POSITION`, `POSITION_INCREASED`, `PENDING_TRADE_REFUND` |
| Closing | `CLOSE_POSITION`, `POSITION_DECREASED`, `EXECUTE_CLOSE_SUCCESSFUL`, `EXECUTE_CLOSE_REJECTED` |
| Limit orders | `OPEN_LIMIT_ORDER`, `EXECUTE_LIMIT_ORDER_SUCCESSFUL`, `EXECUTE_LIMIT_ORDER_REJECTED`, `CANCEL_LIMIT_ORDER`, `LIMIT_ORDER_REFUND`, `UPDATE_ORDER_TP`, `UPDATE_ORDER_SL` |
| TP/SL orders | `DECREASE_ORDER_CREATED`, `DECREASE_ORDER_UPDATED`, `DECREASE_ORDER_CANCELLED`, `EXECUTE_DECREASE_ORDER_SUCCESSFUL` |
| Position management | `UPDATE_MARGIN`, `UPDATE_TRADE_TP`, `UPDATE_TRADE_SL` |

Older records may carry `OPEN_MARKET_TRADE` and `CLOSE_TRADE_SUCCESSFUL` from the pre-merge event
model. Treat them as equivalent to `OPEN_POSITION` and `CLOSE_POSITION` when rendering history.

```ts
async function activityFeed(address: string) {
  const res = await fetch(`${API}/v1/user/${address}/trade/history?size=50`)
  const { content } = await res.json()

  return content.map((e: any) => ({
    at: e.blockTime,
    what: e.operationType,
    pair: e.position?.pair ?? e.pairBase,
    tx: e.transactionHash,
  }))
}
```

## Trade overview

Headline PnL numbers for a trader.

```http
GET /v1/portfolio/{user_address}/trade_overview?timeframe=DAYS_30&is_after_fee=true
```

| Parameter | Required | Notes |
| :--- | :--- | :--- |
| `timeframe` | yes | `ALL_TIME`, `DAYS_7`, `DAYS_30`, `DAYS_60` |
| `is_after_fee` | yes | Whether PnL is net of fees |
| `blockchain` | no | Defaults to `MONAD` |

```json
{
  "realizedPnlUsd": "1234.56",
  "biggestWinUsd": "890.12",
  "winRate": 0.62,
  "updatedAt": "2026-07-27T10:00:00Z"
}
```

`winRate` is a fraction, not a percentage. Cached for 30 seconds.

## PnL chart

Time series for a PnL chart.

```http
GET /v1/portfolio/{user_address}/pnl_chart_data?timeframe=DAYS_30&is_after_fee=true&timezone=UTC
```

| Parameter | Required | Notes |
| :--- | :--- | :--- |
| `timeframe` | yes | `ALL_TIME`, `DAYS_7`, `DAYS_30`, `DAYS_60` |
| `is_after_fee` | yes | |
| `timezone` | yes | Determines day boundaries, e.g. `UTC`, `Asia/Shanghai` |
| `blockchain` | no | |

```json
{
  "data": [
    { "t": 1785225600, "pnl": "120.50" },
    { "t": 1785312000, "pnl": "-45.20" }
  ],
  "updatedAt": "2026-07-27T10:00:00Z",
  "resolution": "DAY_1"
}
```

`t` is a Unix second bucket start. `resolution` is `DAY_1`, `DAY_7` or `DAY_30`, chosen by the server
based on the timeframe. Cached for 30 seconds.

## Portfolio data

Aggregate breakdown across markets.

```http
GET /v1/portfolio/{user_address}/portfolio_data
```

```json
{
  "trader": "0x…",
  "realizedPNL": "1500.00",
  "realizedPNLAfterFees": "1234.56",
  "tradingVolumeUSD": "250000.00",
  "gain": 31,
  "loss": 19,
  "metrics": {
    "positionLiveCount": 2,
    "orderLiveCount": 1
  },
  "tradingPairs": [
    {
      "pairBase": "0xcf5a…",
      "pairName": "BTC/USD",
      "pairSymbol": "BTC",
      "pairType": "CRYPTO",
      "tradingVolumeQty": "12.5",
      "tradingVolumeUSD": "180000.00"
    }
  ]
}
```

`gain` and `loss` are counts of profitable and unprofitable closed positions.
