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: strLoopback-хост, з яким говорить клієнт.
port
port: intПорт Local API.
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: час роботи Local API і лічильники хабу ринкових даних.
Операція 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.