Skip to main content

Account PnL

Returns the user's daily and cumulative profit-and-loss statistics within a date range. Used by the account dashboard's Daily and Cumulative PnL card and overview metrics.

GET /account/pnl
Authorization: Bearer <token>

Query Parameters

NameTypeRequiredDescription
symbolstringNoFilter to a single market (e.g. BTC-USDT). When omitted, all markets are aggregated.
start_datestringNoInclusive lower bound, YYYY-MM-DD (UTC).
end_datestringNoInclusive upper bound, YYYY-MM-DD (UTC).
daysint64NoLook-back window in days, used whenever start_date and end_date are not both provided. Supplying only one of the two dates causes it to be ignored and the days fallback to apply. Default 30.

Resolution order:

  1. If both start_date and end_date are set, that explicit range is used.
  2. Otherwise (including when only one of the two is set — the lone date is ignored), the server uses [today − days, today] in UTC. Today is always Utc::now().date_naive().

Response

{
"daily": [
{
"date": "2026-05-10",
"realized_pnl": "125.30",
"volume": "10000.00",
"trade_count": 12,
"fees": "5.00"
}
],
"cumulative": {
"total_realized_pnl": "320.50",
"total_unrealized_pnl": "-12.40",
"total_pnl": "308.10",
"total_volume": "85000.00",
"total_trades": 87,
"total_fees": "42.50",
"win_rate": "62.50",
"avg_profit_per_trade": "3.68"
},
"symbol": null
}

When the window contains a day with no activity, the day still appears in daily with all numeric fields equal to 0 and trade_count equal to 0. The series is contiguous from start_date to end_date in UTC.

When the user has no trades in the window at all, daily is a contiguous series of zero rows and cumulative is all zeros.

Response Fields

Top level

FieldTypeDescription
dailyDailyPnl[]One row per UTC day, contiguous between start_date and end_date.
cumulativeCumulativePnlAggregate stats over the same window.
symbolstring | nullEcho of the symbol query parameter.

DailyPnl

FieldTypeDescription
datestringUTC date in YYYY-MM-DD.
realized_pnlDecimalSum of realized_pnl_events.realized_pnl for the day (USDT).
volumeDecimalNotional traded that day, computed as SUM(price × amount) over trades where the user is maker or taker (USD).
trade_countint64Number of trades rows where the user is maker or taker.
feesDecimalThe user's portion of fees on trades (maker fee if user is maker, taker fee otherwise), summed for the day (USDT).

CumulativePnl

All fields cover the same window [start_date, end_date] (inclusive, UTC).

FieldTypeDescription
total_realized_pnlDecimalSum of realized_pnl_events.realized_pnl over the window.
total_unrealized_pnlDecimalFloating PnL of all currently open positions, computed at request time using the latest mark price for each symbol. Independent of the date window.
total_pnlDecimaltotal_realized_pnl + total_unrealized_pnl.
total_volumeDecimalSUM(price × amount) over trades in the window.
total_tradesint64Count of trades rows in the window where the user is maker or taker.
total_feesDecimalSum of the user's portion of fees on trades in the window.
win_rateDecimalcount(realized_pnl_events with realized_pnl > 0) / count(realized_pnl_events) × 100 over the window. Returned as a percentage (e.g. 62.50 means 62.5%). 0 when there are no closes in the window.
avg_profit_per_tradeDecimaltotal_realized_pnl / total_trades. 0 when total_trades is 0.

Errors

HTTPcodeWhen
400INVALID_START_DATEstart_date is not parseable as YYYY-MM-DD.
400INVALID_END_DATEend_date is not parseable as YYYY-MM-DD.
500DAILY_TRADE_STATS_FAILEDDB error aggregating daily trade rows.
500DAILY_PNL_FETCH_FAILEDDB error aggregating daily realized PnL.
500STATS_FETCH_FAILEDDB error computing cumulative trade stats.
500WIN_RATE_FETCH_FAILEDDB error computing win rate.

Error body:

{ "error": "...", "code": "..." }

Notes

  • All money is reported as a decimal string. Callers should parse with arbitrary-precision arithmetic, not float.
  • Day boundaries are UTC. There is no tz parameter; callers that need a local-time view should aggregate daily rows on the client.
  • total_unrealized_pnl does not depend on start_date / end_date — it always reflects the user's currently open positions and the latest mark prices.