curl --request GET \
--url http://localhost:8000/performance/history \
--header 'Authorization: Basic <encoded-value>'import requests
url = "http://localhost:8000/performance/history"
headers = {"Authorization": "Basic <encoded-value>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {Authorization: 'Basic <encoded-value>'}};
fetch('http://localhost:8000/performance/history', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_PORT => "8000",
CURLOPT_URL => "http://localhost:8000/performance/history",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"Authorization: Basic <encoded-value>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "http://localhost:8000/performance/history"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("Authorization", "Basic <encoded-value>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("http://localhost:8000/performance/history")
.header("Authorization", "Basic <encoded-value>")
.asString();require 'uri'
require 'net/http'
url = URI("http://localhost:8000/performance/history")
http = Net::HTTP.new(url.host, url.port)
request = Net::HTTP::Get.new(url)
request["Authorization"] = 'Basic <encoded-value>'
response = http.request(request)
puts response.read_body{
"data": [
{
"timestamp": "<string>",
"subject": "<string>",
"scope_id": "<string>",
"status": "<string>",
"realized_pnl_quote": 123,
"unrealized_pnl_quote": 123,
"global_pnl_quote": 123,
"global_pnl_pct": 123,
"volume_quote": 123,
"is_terminal": false,
"cum_fees_quote": 123,
"bot_name": "<string>",
"controller_id": "<string>",
"executor_id": "<string>",
"executor_type": "<string>",
"account_name": "<string>",
"connector_name": "<string>",
"trading_pair": "<string>",
"close_type": "<string>",
"performance": {},
"custom_info": {}
}
],
"pagination": {},
"status": "success"
}{
"detail": [
{
"loc": [
"<string>"
],
"msg": "<string>",
"type": "<string>",
"input": "<unknown>",
"ctx": {}
}
]
}Get Performance History
Historical performance for one subject, newest first, in one normalized row shape.
Both subjects page identically: descending timestamp, cursor is the last row’s
timestamp, has_more says whether another page exists.
interval is a floor, not a guarantee. The controller series is written on a
5-minute grain, so asking it for 1m returns that native grain; the executor series
is written on PERFORMANCE_EXECUTOR_SNAPSHOT_INTERVAL (60s by default). The echoed
interval says what was asked for, the timestamps say what was served.
It is a claim about resolution only. Sampling is per scope — per executor, per (bot, controller) — so a coarser interval thins every scope’s own series and never drops a scope from the answer. An unnarrowed query over a fleet returns the whole fleet at every interval, at a lower resolution.
Realized vs unrealized, for executors: an executor reports one net PnL, and the split
comes from whether its position is really settled. A POSITION_HOLD close is NOT
settled — it hands the position on to position_holds, which report it in their own
right — so its PnL stays in unrealized_pnl_quote even though is_terminal is true
and status is TERMINATED. Counting it realized here would double-count it, the same
exclusion /executors/performance applies. Any other terminal close is realized.
An executor’s series is answered from executor_performance_snapshots alone, including
its final value: completion writes a terminal row, so there is no join to the
executors table and no “and then append the last point” rule. An executor that was
live when the API crashed gets its terminal row from the startup reap instead, carrying
close_type: SYSTEM_CLEANUP and the last figures observed before the crash — an
approximated close, marked as one, rather than a series that never ends.
curl --request GET \
--url http://localhost:8000/performance/history \
--header 'Authorization: Basic <encoded-value>'import requests
url = "http://localhost:8000/performance/history"
headers = {"Authorization": "Basic <encoded-value>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {Authorization: 'Basic <encoded-value>'}};
fetch('http://localhost:8000/performance/history', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_PORT => "8000",
CURLOPT_URL => "http://localhost:8000/performance/history",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"Authorization: Basic <encoded-value>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "http://localhost:8000/performance/history"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("Authorization", "Basic <encoded-value>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("http://localhost:8000/performance/history")
.header("Authorization", "Basic <encoded-value>")
.asString();require 'uri'
require 'net/http'
url = URI("http://localhost:8000/performance/history")
http = Net::HTTP.new(url.host, url.port)
request = Net::HTTP::Get.new(url)
request["Authorization"] = 'Basic <encoded-value>'
response = http.request(request)
puts response.read_body{
"data": [
{
"timestamp": "<string>",
"subject": "<string>",
"scope_id": "<string>",
"status": "<string>",
"realized_pnl_quote": 123,
"unrealized_pnl_quote": 123,
"global_pnl_quote": 123,
"global_pnl_pct": 123,
"volume_quote": 123,
"is_terminal": false,
"cum_fees_quote": 123,
"bot_name": "<string>",
"controller_id": "<string>",
"executor_id": "<string>",
"executor_type": "<string>",
"account_name": "<string>",
"connector_name": "<string>",
"trading_pair": "<string>",
"close_type": "<string>",
"performance": {},
"custom_info": {}
}
],
"pagination": {},
"status": "success"
}{
"detail": [
{
"loc": [
"<string>"
],
"msg": "<string>",
"type": "<string>",
"input": "<unknown>",
"ctx": {}
}
]
}Authorizations
Basic authentication header of the form Basic <encoded-value>, where <encoded-value> is the base64-encoded string username:password.
Query Parameters
Which population to read: "controller" or "executor"
^(controller|executor)$Filter by bot name (controller subject only)
Filter by controller ID (either subject)
Filter by executor ID (executor subject only)
Filter by executor type (executor subject only)
Filter by account name (executor subject only)
Filter by connector (executor subject only)
Filter by trading pair (executor subject only)
ISO 8601 start of the window
ISO 8601 end of the window
^(1m|5m|15m|30m|1h|4h|12h|1d)$x <= 1000Cursor from a previous page's next_cursor
Was this page helpful?

