Перейти к содержимому
Справочник SDK

Python SDK

Клиент, стрим, поиск терминала и ошибки пакета spectre-terminal.

Установка

pip install spectre-terminal

Python-клиент для 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: str

Loopback-хост, с которым говорит клиент.

port

port: int

Порт листенера.

base_url

base_url: str

http://<host>:<port>/v1.

stream_url

stream_url: str

ws://<host>:<port>/v1/stream.

meta()

meta() -> Meta

GET /v1/meta: версия API, сборка, права токена, лимиты, топики стрима.

Операция HTTP: GET /v1/meta

health()

health() -> Health

GET /v1/health: время работы листенера и счётчики хаба рыночных данных.

Операция HTTP: GET /v1/health

connections()

connections() -> list[Connection]

GET /v1/connections: все подключения терминала (connections:read).

Операция HTTP: GET /v1/connections

connection()

connection(id: int) -> Connection

GET /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() -> Intervals

GET /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,
) -> Candles

GET /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,
) -> Trades

GET /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) -> Snapshot

GET /v1/market/snapshot: лучшие bid и ask, последняя сделка, цена маркировки и тикер.

Операция HTTP: GET /v1/market/snapshot

orderbook()

orderbook(
    instrument: str,
    *,
    depth: int | None = None,
    connection_id: int | None = None,
) -> OrderBook

GET /v1/market/orderbook: верхние depth уровней стакана (1–200, по умолчанию 50).

Нет полного стакана за 3 с — ApiError unavailable: пустой стакан выглядел бы как пустой рынок.

Операция HTTP: GET /v1/market/orderbook

clusters()

clusters(instrument: str, *, connection_id: int | None = None) -> Clusters

GET /v1/market/clusters: последние бакеты кластеров с интервалом терминала.

Инструмент, не открытый в окне, считается по запросу, а его история засевается с бекенда; если засев занимает дольше 3 с, ответ — ApiError not_active, и повтор через несколько секунд находит кластеры.

Операция HTTP: GET /v1/market/clusters

account_orders()

account_orders(connection_id: int) -> AccountOrders

GET /v1/account/orders: открытые заявки и триггерные заявки одного подключения.

connection_id обязателен: у операций счёта нет подключения по умолчанию. Неизвестный id — ApiError not_found.

Операция HTTP: GET /v1/account/orders

account_positions()

account_positions(connection_id: int) -> AccountPositions

GET /v1/account/positions: открытые позиции одного подключения.

Операция HTTP: GET /v1/account/positions

account_balances()

account_balances(connection_id: int) -> AccountBalances

GET /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,
) -> AccountFills

GET /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,
) -> Accepted

POST /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) -> Accepted

DELETE /v1/orders/{id}: снять одну заявку по ключу терминала (t-...) или биржевому id.

Снятие уходит на биржу напрямую; подтверждение приходит через стрим. Accepted.key равен None.

Операция HTTP: DELETE /v1/orders/{id}

cancel_all()

cancel_all(connection_id: int, instrument: str) -> Accepted

POST /v1/orders/cancel-all: снять все заявки инструмента (пробел).

Операция HTTP: POST /v1/orders/cancel-all

close_position()

close_position(connection_id: int, instrument: str) -> Accepted

POST /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,
) -> Accepted

POST /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) -> Accepted

DELETE /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) -> Accepted

POST /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,
) -> Accepted

PATCH /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) -> Leverage

GET /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,
) -> Accepted

POST /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) -> Accepted

POST /v1/account/margin-mode (trading:write): cross или isolated.

Смену при открытой позиции биржа отклоняет, как и в окне; ответ Accepted этого ещё не знает.

Операция HTTP: POST /v1/account/margin-mode

workspace()

workspace() -> Workspace

GET /v1/workspace (workspace:read): все окна, их вкладки, стеки и графики.

Читается из снимка, который окно публикует при каждом изменении структуры, поэтому вызов никогда не ждёт окно. Идентификаторы — внутренние для этого запуска терминала.

Операция HTTP: GET /v1/workspace

workspace_window()

workspace_window(window_id: int) -> Window

GET /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,
) -> WorkspaceOutcome

POST /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,
) -> WorkspaceOutcome

POST /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],
) -> WorkspaceOutcome

POST /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,
) -> WorkspaceOutcome

POST /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,
) -> WorkspaceOutcome

PATCH /v1/workspace/stacks/{widget_id}: другой инструмент в группе стека.

Операция HTTP: PATCH /v1/workspace/stacks/{widget_id}

close_stack()

close_stack(widget_id: int) -> WorkspaceOutcome

DELETE /v1/workspace/stacks/{widget_id}: закрыть один стек, как его кнопка закрытия.

Операция HTTP: DELETE /v1/workspace/stacks/{widget_id}

close_tab()

close_tab(window_id: int, tab_id: int) -> WorkspaceOutcome

DELETE /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) -> WorkspaceOutcome

POST /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) -> WorkspaceOutcome

DELETE /v1/workspace/windows/{id}: главное окно — invalid_request.

Операция HTTP: DELETE /v1/workspace/windows/{id}

activate_window()

activate_window(window_id: int) -> WorkspaceOutcome

POST /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,
) -> Settings

GET /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) -> OrderbookSettings

GET /v1/settings/orderbook (settings:read): настройки стека с подставленными умолчаниями.

Лестница, которой пользуется окно, — строка пары на подключении, умолчание подключения, строка пары без подключения, глобальное умолчание — с resolved_from у каждого ключа.

Операция HTTP: GET /v1/settings/orderbook

update_settings()

update_settings(changes: Sequence[SettingChange]) -> SettingsApplied

PATCH /v1/settings (settings:write): записать настройки так, как это делает Settings App.

Каждое изменение сначала сверяется со словарём терминала (неизвестный ключ, не тот тип, вне диапазона, не та форма — invalid_request с указанием changes[i], ничего не записано); плечо здесь — unsupported (используйте set_leverage). Затем изменения применяются по порядку: та же строка, событие окну и кэши сервисов, что у Settings App. Первое неудавшееся изменение останавливает список и выбрасывает исключение; предыдущие остаются применёнными.

Операция HTTP: PATCH /v1/settings

stream()

stream() -> Stream

Stream для /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: str

URL стрима.

connected

connected: bool

True между успешным рукопожатием и закрытием.

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.

На этой странице