Python SDK
Клиент, стрим, поиск терминала и ошибки пакета spectre-terminal.
Установка
pip install spectre-terminalPython-клиент для Local API Spectre Terminal.
from spectre_terminal import Terminal
terminal = Terminal.discover()
for connection in terminal.connections():
print(connection.id, connection.exchange, connection.market, connection.status.state)Публичная поверхность — Terminal (HTTP: рыночные данные, счёт,
торговые команды и рабочее пространство), Stream (WebSocket),
модели из models, ошибки из
errors и помощники обнаружения из
discovery. Импорт пакета к сети не обращается.
Terminal
Запущенный Spectre Terminal, доступный через его Local API на loopback-интерфейсе.
Все методы синхронные и возвращают типизированные модели из
models. Ошибки — подклассы
SpectreTerminalError: отказы самого
терминала — ApiError, отсутствующий
терминал — TerminalUnreachable.
Аргументы
token— токен, скопированный из Settings App -> Общие -> Local API.port— порт листенера,48180, если терминал не запущен сSPECTRE_LOCAL_API_PORT;discoverчитает его изinstance.json.host—127.0.0.1илиlocalhost; всё остальное отклоняется.timeout— сколько секунд ждать ответа. Холодные рыночные операции могут занимать до 3 с, пока терминал открывает поток на бирже.
Terminal()
Terminal(
token: str,
*,
port: int = discovery.DEFAULT_PORT,
host: str = discovery.DEFAULT_HOST,
timeout: float = 10.0,
)discover()
Terminal.discover(token: str | None = None, *, port: int | None = None) -> TerminalСобрать клиент из окружения и проверить, что терминал говорит на API v1.
Токен — token или SPECTRE_LOCAL_API_TOKEN; без того и другого
выбрасывается TokenMissing. Порт —
port, иначе local_api_port из instance.json терминала
(см. discovery), иначе 48180. Затем
один раз читается /v1/meta: терминал с другой версией API
выбрасывает IncompatibleTerminal; устаревший
токен или выключенный API выбрасывает
ApiError с кодом unauthorized.
host
host: strLoopback-хост, с которым говорит клиент.
port
port: intПорт листенера.
base_url
base_url: strhttp://<host>:<port>/v1.
stream_url
stream_url: strws://<host>:<port>/v1/stream.
meta()
meta() -> MetaGET /v1/meta: версия API, сборка, права токена, лимиты, топики стрима.
Операция HTTP: GET /v1/meta
health()
health() -> HealthGET /v1/health: время работы листенера и счётчики хаба рыночных данных.
Операция HTTP: GET /v1/health
connections()
connections() -> list[Connection]GET /v1/connections: все подключения терминала (connections:read).
Операция HTTP: GET /v1/connections
connection()
connection(id: int) -> ConnectionGET /v1/connections/{id}: одно подключение; not_found, если такого id нет.
Операция HTTP: GET /v1/connections/{id}
instruments()
instruments(
*,
connection_id: int | None = None,
exchange: str | None = None,
market: str | None = None,
q: str | None = None,
limit: int | None = None,
cursor: str | None = None,
) -> Page[Instrument]GET /v1/instruments: одна страница справочника биржи и рынка.
Пару задают connection_id или exchange и market
(одно из двух обязательно). q фильтрует по подстроке символа
или базовой монеты без учёта регистра; limit — 1–2000
(по умолчанию 500); cursor — next_cursor предыдущей страницы.
Операция HTTP: GET /v1/instruments
iter_instruments()
iter_instruments(
*,
connection_id: int | None = None,
exchange: str | None = None,
market: str | None = None,
q: str | None = None,
limit: int | None = None,
cursor: str | None = None,
) -> Iterator[Instrument]Все инструменты справочника, страница за страницей по next_cursor.
Параметры те же, что у instruments; cursor — откуда начать.
intervals()
intervals() -> IntervalsGET /v1/market/intervals: интервалы свечей и кластеров, которые знает терминал.
Операция HTTP: GET /v1/market/intervals
candles()
candles(
instrument: str,
interval: str,
*,
limit: int | None = None,
end: int | None = None,
connection_id: int | None = None,
) -> CandlesGET /v1/market/candles: история баров тем же путём, что у графика в окне.
interval — код из intervals (1m, 5m, ...);
limit — до 2000 (по умолчанию 500); end — правая граница
в миллисекундах UTC (по умолчанию сейчас). Биржа не ответила —
ApiError unavailable.
Операция HTTP: GET /v1/market/candles
trades()
trades(
instrument: str,
*,
limit: int | None = None,
since_ts: int | None = None,
connection_id: int | None = None,
) -> TradesGET /v1/market/trades: последние сделки инструмента, от старых к новым.
limit — 1–1000 (по умолчанию 100); since_ts отбрасывает сделки,
полученные раньше этой миллисекунды UTC. Холодный инструмент ждёт данных
до 3 с; 3 с без единой сделки — честно пустой список.
Операция HTTP: GET /v1/market/trades
snapshot()
snapshot(instrument: str, *, connection_id: int | None = None) -> SnapshotGET /v1/market/snapshot: лучшие bid и ask, последняя сделка, цена маркировки и тикер.
Операция HTTP: GET /v1/market/snapshot
orderbook()
orderbook(
instrument: str,
*,
depth: int | None = None,
connection_id: int | None = None,
) -> OrderBookGET /v1/market/orderbook: верхние depth уровней стакана (1–200, по умолчанию 50).
Нет полного стакана за 3 с — ApiError unavailable: пустой
стакан выглядел бы как пустой рынок.
Операция HTTP: GET /v1/market/orderbook
clusters()
clusters(instrument: str, *, connection_id: int | None = None) -> ClustersGET /v1/market/clusters: последние бакеты кластеров с интервалом терминала.
Инструмент, не открытый в окне, считается по запросу, а его
история засевается с бекенда; если засев занимает дольше 3 с,
ответ — ApiError not_active, и повтор через несколько секунд
находит кластеры.
Операция HTTP: GET /v1/market/clusters
account_orders()
account_orders(connection_id: int) -> AccountOrdersGET /v1/account/orders: открытые заявки и триггерные заявки одного подключения.
connection_id обязателен: у операций счёта нет подключения по
умолчанию. Неизвестный id — ApiError not_found.
Операция HTTP: GET /v1/account/orders
account_positions()
account_positions(connection_id: int) -> AccountPositionsGET /v1/account/positions: открытые позиции одного подключения.
Операция HTTP: GET /v1/account/positions
account_balances()
account_balances(connection_id: int) -> AccountBalancesGET /v1/account/balances: по каждой валюте последний баланс, который прислала биржа.
Балансы копятся с момента включения API: валюты, с которой с тех пор ничего не происходило, в списке нет.
Операция HTTP: GET /v1/account/balances
account_fills()
account_fills(
connection_id: int,
*,
limit: int | None = None,
since_ts: int | None = None,
) -> AccountFillsGET /v1/account/fills: последние исполнения подключения, от старых к новым.
limit — 1–1000 (по умолчанию 100); since_ts оставляет исполнения
не раньше этого времени биржи в миллисекундах UTC. Буфер —
последняя тысяча исполнений с момента включения API.
Операция HTTP: GET /v1/account/fills
place_order()
place_order(
connection_id: int,
instrument: str,
*,
side: str,
order_type: str,
price: Money | None = None,
size: Money | None = None,
notional: Money | None = None,
reduce_only: bool = False,
post_only: bool = False,
time_in_force: str | None = None,
client_key: str | None = None,
) -> AcceptedPOST /v1/orders: поставить заявку так, как это делает окно.
side — buy или sell; order_type — limit (нужна
price) или market (без price); передаётся ровно одно из size
(базовая монета) и notional (сумма в котировке). Деньги —
Decimal, str или int, и едут строкой;
float отклоняется. time_in_force — gtc, ioc, fok
или post_only; без него действует умолчание биржи. client_key —
ключ идемпотентности: повтор с тем же ключом возвращает прежний
ответ и ничего не ставит.
Ответ — Accepted: принято
терминалом, а не биржей; исход приходит в
account.orders или в account_orders. Подключение, которое
не торгует, — ApiError not_tradable; отказ по минимумам и
шагам биржи — invalid_request; быстрее
limits.commands_per_second — rate_limited.
Операция HTTP: POST /v1/orders
cancel_order()
cancel_order(connection_id: int, instrument: str, order_id: str) -> AcceptedDELETE /v1/orders/{id}: снять одну заявку по ключу терминала (t-...) или биржевому id.
Снятие уходит на биржу напрямую; подтверждение
приходит через стрим. Accepted.key равен None.
Операция HTTP: DELETE /v1/orders/{id}
cancel_all()
cancel_all(connection_id: int, instrument: str) -> AcceptedPOST /v1/orders/cancel-all: снять все заявки инструмента (пробел).
Операция HTTP: POST /v1/orders/cancel-all
close_position()
close_position(connection_id: int, instrument: str) -> AcceptedPOST /v1/positions/close: закрыть позицию по инструменту так, как это делает окно.
На фьючерсах — рыночная заявка на размер позиции с
close_position; на споте — «снять всё и закрыть» одной атомарной
командой. Без открытой позиции — ApiError invalid_request.
Операция HTTP: POST /v1/positions/close
place_trigger()
place_trigger(
connection_id: int,
instrument: str,
*,
kind: str,
trigger_price: Money,
side: str | None = None,
notional: Money | None = None,
rule: str | None = None,
client_key: str | None = None,
) -> AcceptedPOST /v1/orders/trigger: стоп-лосс, тейк-профит или стоп, как их ставит окно.
kind — stop_loss или take_profit — ставятся на открытую
позицию (сторона, размер и правило берутся из неё; без позиции
ApiError invalid_request) — или stop, которому нужны side
(buy/sell) и notional (сумма в котировке) и необязательное
rule (price_above_or_equal или
price_below_or_equal; без него — выше рынка для покупки,
ниже для продажи). client_key — ключ идемпотентности, как в
place_order.
Accepted.key — ключ триггера (sl_…, tp_…,
stop_…), который появляется как client_id триггера в
account_orders; снятие и изменение используют числовой
id оттуда. Биржа без триггерных заявок в терминале —
ApiError unsupported.
Операция HTTP: POST /v1/orders/trigger
cancel_trigger()
cancel_trigger(connection_id: int, instrument: str, trigger_id: int | str) -> AcceptedDELETE /v1/orders/trigger/{id}: снять один триггер по его числовому id.
id — тот, что в account_orders. Accepted.key равен None.
Операция HTTP: DELETE /v1/orders/trigger/{id}
cancel_all_triggers()
cancel_all_triggers(connection_id: int, instrument: str) -> AcceptedPOST /v1/orders/trigger/cancel-all: снять все триггеры инструмента.
Обычные заявки остаются; их снимает cancel_all.
Операция HTTP: POST /v1/orders/trigger/cancel-all
amend_trigger()
amend_trigger(
connection_id: int,
instrument: str,
trigger_id: int | str,
*,
kind: str,
trigger_price: Money,
side: str | None = None,
rule: str | None = None,
) -> AcceptedPATCH /v1/orders/trigger/{id}: перенести триггер на новую цену.
Последовательная замена, как в окне: прежний триггер снимается,
ставится новый. kind обязателен (stop_loss,
take_profit или stop); для stop нужен ещё и side, а
rule необязателен. Accepted.key равен None.
Операция HTTP: PATCH /v1/orders/trigger/{id}
leverage()
leverage(connection_id: int, instrument: str) -> LeverageGET /v1/account/leverage (account:read): плечо и режим маржи.
Читается из кэша терминала: инструмент, которого терминал ещё
не касался, — ApiError not_active; откройте его в окне или
вызовите set_leverage — так он попадает в кэш.
Операция HTTP: GET /v1/account/leverage
set_leverage()
set_leverage(
connection_id: int,
instrument: str,
*,
leverage: int | None = None,
mode: str | None = None,
) -> AcceptedPOST /v1/account/leverage (trading:write): задать плечо так, как это делает окно.
Ровно одно из leverage (целое не меньше 1) или mode
(max или smart). Ответ — Accepted с key
None; применённое значение видно в leverage после
ответа биржи.
Операция HTTP: POST /v1/account/leverage
set_margin_mode()
set_margin_mode(connection_id: int, instrument: str, margin_mode: str) -> AcceptedPOST /v1/account/margin-mode (trading:write): cross или isolated.
Смену при открытой позиции биржа отклоняет, как и в
окне; ответ Accepted этого ещё не знает.
Операция HTTP: POST /v1/account/margin-mode
workspace()
workspace() -> WorkspaceGET /v1/workspace (workspace:read): все окна, их вкладки, стеки и графики.
Читается из снимка, который окно публикует при каждом изменении структуры, поэтому вызов никогда не ждёт окно. Идентификаторы — внутренние для этого запуска терминала.
Операция HTTP: GET /v1/workspace
workspace_window()
workspace_window(window_id: int) -> WindowGET /v1/workspace/windows/{id}: одно окно; неизвестный id — not_found.
Операция HTTP: GET /v1/workspace/windows/{id}
open_stack()
open_stack(
instrument: str,
*,
connection_id: int | None = None,
chart: bool = False,
target: str = 'new_tab',
window_id: int | None = None,
activate: bool = True,
) -> WorkspaceOutcomePOST /v1/workspace/stacks (workspace:write): стек в новой вкладке или окне.
target — new_tab (в окне window_id, а без него — в окне
в фокусе) или new_window. chart открывает график того же
инструмента над стеком. activate=False открывает вкладку в
фоне; новое окно всегда получает фокус. Ответ называет
окно, вкладку и новые виджеты. Инструмент, которого нет в справочнике
терминала, — invalid_request.
Операция HTTP: POST /v1/workspace/stacks
open_tab()
open_tab(
items: Sequence[TabItem | str],
*,
name: str | None = None,
target: str = 'new_tab',
window_id: int | None = None,
activate: bool = True,
) -> WorkspaceOutcomePOST /v1/workspace/tabs (workspace:write): вкладка с колонкой на каждый инструмент.
Каждый элемент items — TabItem или голый адрес
инструмента — становится колонкой в порядке списка: стек и, с
chart, график над ним. От одного до пятидесяти элементов. name — имя
вкладки (до 64 символов); без него терминал называет вкладку
по единственному инструменту или по первому плюс число остальных
(BTCUSDT +1). target, window_id и activate — как
в open_stack. Отказ называет элемент (items[1]: …),
и до окна не доходит ничего.
Операция HTTP: POST /v1/workspace/tabs
add_stacks()
add_stacks(
window_id: int,
tab_id: int,
items: Sequence[TabItem | str],
) -> WorkspaceOutcomePOST /v1/workspace/windows/{id}/tabs/{tab}/stacks: колонки после последней колонки вкладки.
Те же items, что у open_tab; вкладка сохраняет имя и
ширины колонок. Нет такого окна или вкладки — not_found.
Ответ перечисляет только добавленные stacks и charts.
Операция HTTP: POST /v1/workspace/windows/{id}/tabs/{tab}/stacks
open_multiview()
open_multiview(
base: str,
*,
target: str | None = None,
window_id: int | None = None,
activate: bool = True,
) -> WorkspaceOutcomePOST /v1/workspace/multiview (workspace:write): монета на всех биржах.
Тот же план, что у кнопки «Мультивью» в окне. base — монета
(BTC) или пара (BTC/USDT); target, оставленный None, следует
настройке мультивью «Открывать». Нет ни одной ячейки для монеты — not_found.
Операция HTTP: POST /v1/workspace/multiview
set_stack_instrument()
set_stack_instrument(
widget_id: int,
instrument: str,
*,
connection_id: int | None = None,
) -> WorkspaceOutcomePATCH /v1/workspace/stacks/{widget_id}: другой инструмент в группе стека.
Операция HTTP: PATCH /v1/workspace/stacks/{widget_id}
close_stack()
close_stack(widget_id: int) -> WorkspaceOutcomeDELETE /v1/workspace/stacks/{widget_id}: закрыть один стек, как его кнопка закрытия.
Операция HTTP: DELETE /v1/workspace/stacks/{widget_id}
close_tab()
close_tab(window_id: int, tab_id: int) -> WorkspaceOutcomeDELETE /v1/workspace/windows/{id}/tabs/{tab}: закрыть вкладку.
Единственную вкладку окна закрыть нельзя (invalid_request).
Операция HTTP: DELETE /v1/workspace/windows/{id}/tabs/{tab}
activate_tab()
activate_tab(window_id: int, tab_id: int) -> WorkspaceOutcomePOST /v1/workspace/windows/{id}/tabs/{tab}/activate: выбрать вкладку.
Фокус окна не меняется; см. activate_window.
Операция HTTP: POST /v1/workspace/windows/{id}/tabs/{tab}/activate
close_window()
close_window(window_id: int) -> WorkspaceOutcomeDELETE /v1/workspace/windows/{id}: главное окно — invalid_request.
Операция HTTP: DELETE /v1/workspace/windows/{id}
activate_window()
activate_window(window_id: int) -> WorkspaceOutcomePOST /v1/workspace/windows/{id}/activate: развернуть и вывести на передний план.
Операция HTTP: POST /v1/workspace/windows/{id}/activate
settings()
settings(
*,
scope: str | None = None,
category: str | None = None,
key: str | None = None,
connection_id: int | None = None,
instrument: str | None = None,
) -> SettingsGET /v1/settings (settings:read): строки, как они хранятся, без лестницы умолчаний.
Без параметров — глобальные строки без подключения.
instrument перечисляет строки пары (область order_book);
connection_id — строки подключения. Каждая строка сообщает, доступна ли
она для записи (writable) через update_settings.
Операция HTTP: GET /v1/settings
orderbook_settings()
orderbook_settings(connection_id: int, instrument: str) -> OrderbookSettingsGET /v1/settings/orderbook (settings:read): настройки стека с подставленными умолчаниями.
Лестница, которой пользуется окно, — строка пары на подключении,
умолчание подключения, строка пары без подключения, глобальное
умолчание — с resolved_from у каждого ключа.
Операция HTTP: GET /v1/settings/orderbook
update_settings()
update_settings(changes: Sequence[SettingChange]) -> SettingsAppliedPATCH /v1/settings (settings:write): записать настройки так, как это делает Settings App.
Каждое изменение сначала сверяется со словарём терминала
(неизвестный ключ, не тот тип, вне диапазона, не та форма —
invalid_request с указанием changes[i], ничего не записано); плечо
здесь — unsupported (используйте set_leverage). Затем изменения
применяются по порядку: та же строка, событие окну и кэши сервисов,
что у Settings App. Первое неудавшееся изменение останавливает список
и выбрасывает исключение; предыдущие остаются применёнными.
Операция HTTP: PATCH /v1/settings
stream()
stream() -> StreamStream для /v1/stream, для async with.
Stream
Одно соединение с /v1/stream.
Используйте его как асинхронный контекстный менеджер (или connect / close):
subscribe и unsubscribe отправляют запросы и ждут
ответов, а async for frame in stream отдаёт
Event,
Gap и
SubscriptionError в порядке прихода.
Итерация заканчивается, когда соединение закрывается; закрытие сервером с кодом из
CLOSE_RAISES выбрасывает StreamClosed,
чтобы вызывающий это заметил, а close_code / close_reason хранят
то, что сказал сервер.
Кадры буферизуются до max_pending, пока вызывающий не
итерирует; дальше клиент перестаёт читать сокет — так
терминал узнаёт, что клиент медленный (кадры gap, затем закрытие 4008).
Аргументы
url—ws://127.0.0.1:<port>/v1/stream;streamсобирает его.token— bearer-токен для заголовка рукопожатия.open_timeout— сколько секунд ждать рукопожатия.request_timeout— сколько секунд ждать ответа наsubscribe/unsubscribe;Noneждёт до ответа или закрытия.max_pending— сколько кадров буферизуется перед итератором.max_size— наибольший принимаемый кадр, в байтах.
Stream()
Stream(
url: str,
token: str,
*,
open_timeout: float = 10.0,
request_timeout: float | None = None,
max_pending: int = 1000,
max_size: int | None = 16 * 1024 * 1024,
)url
url: strURL стрима.
connected
connected: boolTrue между успешным рукопожатием и закрытием.
close_code
close_code: int | NoneКод закрытия WebSocket после закрытия соединения, иначе None.
close_reason
close_reason: str | NoneПричина закрытия после закрытия соединения, иначе None.
connect()
async connect() -> StreamОткрыть соединение. Вызывается async with; при уже открытом соединении ничего не делает.
close()
async close(code: int = 1000, reason: str = '') -> NoneЗакрыть соединение и дождаться, пока сервер завершит рукопожатие.
subscribe()
async subscribe(
topic: str,
instrument: str | None = None,
*,
connection_id: int | None = None,
**params: Any,
) -> SubscribedПодписаться на topic и дождаться ответа сервера.
topic — один из topics в /v1/meta. Рыночным топикам
(trades, ticker, mark_price, book.top, clusters)
нужен instrument; connection_id необязателен и без него выбирается
по общему правилу. Топикам счёта (account.orders,
account.positions, account.balances, account.fills) нужен
connection_id, а инструмент они не принимают: запрос его не несёт.
workspace и settings не принимают ни того, ни другого: они одни на
терминал; workspace сначала шлёт текущий снимок, settings —
кадр на каждую записанную строку. params важны только для book.top
(depth 1–200, interval_ms >= 50); остальные топики их игнорируют.
Ответ называет id подписки и подключение (None у
топиков терминала); ответ error выбрасывает
StreamRequestError.
unsubscribe()
async unsubscribe(sub: Subscribed | str) -> UnsubscribedСнять подписку по её Subscribed или по её id.
На неизвестный id подписки сервер отвечает not_found, и выбрасывается
StreamRequestError.
Поиск терминала
instance_path()
instance_path() -> PathПуть к instance.json терминала на этой платформе.
Linux: $XDG_DATA_HOME или ~/.local/share; macOS:
~/Library/Application Support; Windows: %LOCALAPPDATA%, затем
%APPDATA% — и к каждому добавляется spectre-terminal/instance.json.
Существование файла не требуется.
read_port()
read_port(path: Path | None = None) -> int | NoneПорт Local API, опубликованный в instance.json, или None, если он неизвестен.
None значит, что файла нет или он нечитаем — вызывающий откатывается
на DEFAULT_PORT. Файл, в котором local_api_port равен null или
отсутствует, значит, что терминал работает с выключенным API, и это
выбрасывает LocalApiDisabled.
Ошибки
SpectreTerminalError
class SpectreTerminalError(Exception)Базовый класс всех ошибок, которые выбрасывает spectre_terminal.
ApiError
class ApiError(SpectreTerminalError)
ApiError(code: str, message: str, status: int)Терминал ответил на запрос конвертом ошибки.
code — стабильный код из контракта (unauthorized,
not_found, invalid_request, unsupported, not_active,
rate_limited, unavailable, ...); message — текст для человека,
пришедший с ним; status — HTTP-статус. Тело ошибки не в JSON
даёт code="unknown" с текстом тела в качестве сообщения.
TerminalUnreachable
class TerminalUnreachable(SpectreTerminalError)Ни один терминал не ответил на loopback-порту: соединение отклонено или истёк таймаут.
LocalApiDisabled
class LocalApiDisabled(SpectreTerminalError)instance.json говорит, что терминал запущен, но его Local API выключен.
TokenMissing
class TokenMissing(SpectreTerminalError)Токен не передан, и SPECTRE_LOCAL_API_TOKEN не задана.
IncompatibleTerminal
class IncompatibleTerminal(SpectreTerminalError)
IncompatibleTerminal(api_version: str, supported: str)Терминал отдаёт версию API, которую этот клиент не поддерживает.
StreamRequestError
class StreamRequestError(SpectreTerminalError)
StreamRequestError(code: str, message: str)Стрим ответил на запрос subscribe или unsubscribe кадром ошибки.
StreamClosed
class StreamClosed(SpectreTerminalError)
StreamClosed(code: int | None, reason: str | None)Сервер закрыл стрим с кодом, который вызывающий обязан заметить.
1001 — API выключен или токен перевыпущен; 4008 —
этот клиент читал слишком медленно; 4029 — лимит соединений
на токен; 1006 — соединение оборвалось без кадра закрытия.
Собрано из кода spectre-terminal, коммит 2d1c324.