Перейти до вмісту
Довідник 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 — порт Local API, 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

Порт Local API.

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: час роботи Local API і лічильники хабу ринкових даних.

Операція 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.

На цій сторінці