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
| Name | Type | Required | Description |
|---|---|---|---|
symbol | string | No | Filter 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.
bucket | Window (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
| Field | Type | Description |
|---|---|---|
symbol | string | null | Echo of the symbol query parameter. |
buckets | PerformanceBucket[] | Always 6 entries, in the order: today, yesterday, week, month, year, all. |
PerformanceBucket
| Field | Type | Description |
|---|---|---|
bucket | string | One of today, yesterday, week, month, year, all. |
start_ts | int64 | Bucket start as a UTC Unix timestamp in seconds. For all, the lower bound is the unix epoch (0). |
end_ts | int64 | Bucket end as a UTC Unix timestamp in seconds, exclusive. Equal to "now" for the open-ended buckets (today, week, month, year, all). |
volume | Decimal | SUM(price × amount) over trades in [start_ts, end_ts) where the user is maker or taker. |
trade_count | int64 | Count of those trades rows. |
realized_pnl | Decimal | SUM(realized_pnl_events.realized_pnl) in [start_ts, end_ts). |
unrealized_pnl | Decimal | Floating 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_pnl | Decimal | Intended 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. |
pnl | Decimal | realized_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). |
wins | int64 | Count of realized_pnl_events rows in the window with realized_pnl > 0. |
losses | int64 | Count of realized_pnl_events rows in the window with realized_pnl ≤ 0. |
win_rate | Decimal | wins / (wins + losses) × 100. 0 when there are no closes in the window. |
used_capital | Decimal | Capital used during the window (USD). See used_capital semantics. |
pnl_bps | int64 | pnl / 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_pnldoes not look at what was open atstart_tsat all. The v1 implementation computes the unrealized PnL of all currently open positions at the current mark price and uses that same value asstart_unrealized_pnlfor every bucket exceptall(where it is0). Consequence:pnl = realized_pnl + unrealized_pnl − start_unrealized_pnlcancels to plainrealized_pnlfor thetoday/yesterday/week/month/yearrows — unrealized PnL effectively only contributes to theallrow. This is a known v1 limitation; a faithful "carried-in PnL" needs a historicalopen_positions_snapshottable, which does not exist yet.used_capitalis point-in-time (end of bucket) rather than peak-in-bucket; see above.max_capitalover 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
| HTTP | code | When |
|---|---|---|
| 500 | PERFORMANCE_SUMMARY_FAILED | DB 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.