Skip to main content

Account Performance Summary

Returns the user's trading performance bucketed by a fixed set of time windows. Used by the account dashboard's General Performance Details table to render the Today / Yesterday / Last 7d / Last 30d / This Year / All Time rows.

GET /account/performance-summary
Authorization: Bearer <token>

Query Parameters

NameTypeRequiredDescription
symbolstringNoFilter to a single market (e.g. BTC-USDT). When omitted, all markets are aggregated.

There are no date parameters: the bucket set is fixed and resolved server-side in UTC.

Buckets

All bucket boundaries are in UTC. Let today_start = midnight UTC of the current day.

bucketWindow (inclusive start, exclusive end)
today[today_start, now)
yesterday[today_start − 1d, today_start)
week[today_start − 7d, now)
month[today_start − 30d, now)
year[Jan 1 UTC of current year, now)
all(−∞, now)

Buckets overlap on purpose: today is contained in week, week in month, etc. Each row is computed independently from the underlying tables.

Response

{
"symbol": null,
"buckets": [
{
"bucket": "today",
"start_ts": 1778716800,
"end_ts": 1778740012,
"volume": "12500.00",
"trade_count": 18,
"realized_pnl": "42.10",
"unrealized_pnl": "-3.25",
"start_unrealized_pnl": "-3.25",
"pnl": "42.10",
"wins": 7,
"losses": 4,
"win_rate": "63.64",
"used_capital": "850.00",
"pnl_bps": 495
},
{ "bucket": "yesterday", "...": "..." },
{ "bucket": "week", "...": "..." },
{ "bucket": "month", "...": "..." },
{ "bucket": "year", "...": "..." },
{ "bucket": "all", "...": "..." }
]
}

buckets is always a 6-element array in the order shown above. A bucket with no activity has all numeric fields equal to 0, win_rate equal to 0, and pnl_bps equal to 0.

Response Fields

Top level

FieldTypeDescription
symbolstring | nullEcho of the symbol query parameter.
bucketsPerformanceBucket[]Always 6 entries, in the order: today, yesterday, week, month, year, all.

PerformanceBucket

FieldTypeDescription
bucketstringOne of today, yesterday, week, month, year, all.
start_tsint64Bucket start as a UTC Unix timestamp in seconds. For all, the lower bound is the unix epoch (0).
end_tsint64Bucket end as a UTC Unix timestamp in seconds, exclusive. Equal to "now" for the open-ended buckets (today, week, month, year, all).
volumeDecimalSUM(price × amount) over trades in [start_ts, end_ts) where the user is maker or taker.
trade_countint64Count of those trades rows.
realized_pnlDecimalSUM(realized_pnl_events.realized_pnl) in [start_ts, end_ts).
unrealized_pnlDecimalFloating PnL of the user's currently open positions valued at the latest mark price. Independent of the bucket window; provided per row only for convenience.
start_unrealized_pnlDecimalIntended semantics: floating PnL the user carried into start_ts. v1 actual behavior: for every bucket except all, this is simply the unrealized PnL of the user's currently open positions valued at the current mark price — identical to the unrealized_pnl field and unrelated to what was open at start_ts. For all, always 0. See v1 simplifications.
pnlDecimalrealized_pnl + unrealized_pnl − start_unrealized_pnl. The "period PnL" shown in the table. Because of the v1 start_unrealized_pnl behavior, this degenerates to realized_pnl for every bucket except all (where it is realized_pnl + unrealized_pnl).
winsint64Count of realized_pnl_events rows in the window with realized_pnl > 0.
lossesint64Count of realized_pnl_events rows in the window with realized_pnl ≤ 0.
win_rateDecimalwins / (wins + losses) × 100. 0 when there are no closes in the window.
used_capitalDecimalCapital used during the window (USD). See used_capital semantics.
pnl_bpsint64pnl / used_capital × 10000, rounded to integer basis points. 0 when used_capital is 0.

used_capital semantics

The intended definition is "peak [open-collateral + unsettled-PnL] observed during the bucket", which matches how the Subsquid-backed dashboard renders the column today.

The v1 implementation returns a simplified figure:

used_capital = max(
sum_open_position_collateral_at_end_of_bucket
- realized_pnl_in_bucket
+ start_unrealized_pnl,
0
)

This is computed from values already available at request time (current open positions, the bucket's realized PnL, and the bucket's start-unrealized PnL). It is not a peak-over-time figure and may understate the true capital used when positions were larger mid-bucket than at bucket end.

used_capital may be 0 when the user has no open positions and a flat realized PnL in the bucket. In that case pnl_bps is also 0.

v1 simplifications

The following are explicit v1 trade-offs, scheduled to be tightened later without changing the response shape:

  • start_unrealized_pnl does not look at what was open at start_ts at all. The v1 implementation computes the unrealized PnL of all currently open positions at the current mark price and uses that same value as start_unrealized_pnl for every bucket except all (where it is 0). Consequence: pnl = realized_pnl + unrealized_pnl − start_unrealized_pnl cancels to plain realized_pnl for the today/yesterday/week/month/year rows — unrealized PnL effectively only contributes to the all row. This is a known v1 limitation; a faithful "carried-in PnL" needs a historical open_positions_snapshot table, which does not exist yet.
  • used_capital is point-in-time (end of bucket) rather than peak-in-bucket; see above.
  • max_capital over a window is not part of this endpoint. If/when peak-collateral becomes available, it will be added as a new field rather than changing existing fields.

These simplifications affect the month/year/all rows more than today/yesterday/week. Clients should treat the simplified rows as a directional indicator, not a precise audit.

Errors

HTTPcodeWhen
500PERFORMANCE_SUMMARY_FAILEDDB error aggregating any of the six buckets.

Error body:

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

Notes

  • All money is reported as a decimal string.
  • The response is not cached on the server in v1. Callers should rate-limit on their side (the dashboard polls every 60 s).
  • The endpoint computes the two window-independent aggregates (current unrealized PnL, open collateral) via tokio::try_join!, then fires the per-bucket aggregation queries concurrently as spawned tasks (tokio::spawn, two per bucket). Adding markets or users does not multiply round-trips.