---
title: "Get Latest Performance"
method: GET
path: "/performance/latest"
tags: ["Performance"]
---

# Get Latest Performance

`GET /performance/latest`

## Request

- Base URL: `http://localhost:8000`
- URL: `http://localhost:8000/performance/latest`
- Auth: HTTP basic

The most recent snapshot of every scope in one subject, newest first.

One row per scope -- per (bot, controller), or per executor -- in the same normalized
shape `/history` returns, so a dashboard's live tiles and its charts read the same
fields off the same client.

`limit` caps how many scopes come back, it is not a page boundary: there is no cursor
here because this is not a series. Newest-first ordering means the scopes that are
still reporting come first, which matters far more for executors than for controllers
-- every executor that ever ran leaves a terminal row behind, so the executor
population grows without bound while the controller one does not.

This reads the stored series, not live memory. An executor younger than one snapshot
interval has no row yet and does not appear, and a live one's figures are up to one
interval stale -- by design, so that this row and the last row of `/history` are the
same row. Live in-memory figures are what `/executors/` serves.

A closed executor's latest row is its terminal row, carrying `is_terminal: true` and
its `close_type`, so "the final value" needs no separate call. That holds for every
way an executor can close, including the startup reap of one a crash left behind
(`close_type: SYSTEM_CLEANUP`): this row and `GET /executors/{executor_id}` are written
in the same transaction, so the two surfaces cannot disagree about whether an executor
is done.

## Query parameters

- `subject` string, required: Which population to read: "controller" or "executor"
- `bot_name` string, nullable: Filter by bot name (controller subject only)
- `controller_id` string, nullable: Filter by controller ID (either subject)
- `executor_id` string, nullable: Filter by executor ID (executor subject only)
- `executor_type` string, nullable: Filter by executor type (executor subject only)
- `account_name` string, nullable: Filter by account name (executor subject only)
- `connector_name` string, nullable: Filter by connector (executor subject only)
- `trading_pair` string, nullable: Filter by trading pair (executor subject only)
- `limit` integer: Cap on how many scopes come back, newest first

## Response `200`

Successful Response

- PerformanceLatestResponse: Envelope of GET /performance/latest. No pagination block: this is one row per scope, not a series, so there is no cursor to walk and no interval to sample. `limit` is a cap on how many scopes come back, newest-first, not a page boundary.
  - `status` string
  - `data` PerformanceRow[], required
    - `timestamp` string, required: ISO 8601 timestamp of the snapshot
    - `subject` string, required: Which population this row came from: "controller" or "executor"
    - `scope_id` string, required: controller_id for controllers, executor_id for executors
    - `status` string, required: Status at the moment of the snapshot
    - `is_terminal` boolean: True only on the final row of a completed executor's series. Always false for controllers, which have no terminal row.
    - `realized_pnl_quote` number, required: Realized PnL in quote currency. For an executor this is its net PnL once the position is really settled, and 0 while it is not. A POSITION_HOLD close is not settled: the position carries on in position_holds, so a held executor's PnL stays unrealized here even though is_terminal is true.
    - `unrealized_pnl_quote` number, required: Unrealized PnL in quote currency. For an executor this is its net PnL while the position is open, and after a POSITION_HOLD close.
    - `global_pnl_quote` number, required: Total PnL (realized + unrealized) in quote currency
    - `global_pnl_pct` number, required: Total PnL as a fraction of the capital deployed
    - `volume_quote` number, required: Volume traded in quote currency. Volume GENERATED, not capital deployed: an LP position's deposit is excluded.
    - `cum_fees_quote` number, nullable: Cumulative fees in quote currency. NULL for controllers: their PerformanceReport genuinely has no fees field, and unknown is not zero.
    - `bot_name` string, nullable: Docker bot name; controllers only
    - `controller_id` string, nullable: Controller ID, on both subjects
    - `executor_id` string, nullable: Executor ID; executors only
    - `executor_type` string, nullable: Executor type; executors only
    - `account_name` string, nullable: Account name; executors only
    - `connector_name` string, nullable: Connector name; executors only
    - `trading_pair` string, nullable: Trading pair; executors only
    - `close_type` string, nullable: Close type; set on an executor's terminal row
    - `performance` object: Controllers: the raw stored PerformanceReport. Empty for executors, whose metrics are all typed columns above.
    - `custom_info` object: Controllers: the raw stored custom_info. Empty for executors -- these payloads carry fill_events and grid levels, which have no business in a per-minute row.

## Other responses

- `422`: Validation Error

## Changes

- **2026-10-14** `66c2e6c1da2e`: 1 info
  - endpoint added

[Operation history](https://skmtc.dev/hummingbot/apis/hummingbot-api/changes/performance/latest/get.md)

---

[API](https://skmtc.dev/hummingbot/apis/hummingbot-api.md) - [All operations](https://skmtc.dev/hummingbot/apis/hummingbot-api/llms.txt) - [OpenAPI document](https://skmtc.dev/hummingbot/apis/hummingbot-api/revisions/66c2e6c1da2e?raw)
