> For the complete documentation index, see [llms.txt](https://support.tiger.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://support.tiger.com/razrabotka-dlya-tiger.trade-windows/tiger-api.md).

# Tiger API

Документация по работе с Tiger WebSocket API — подключения, ордера, позиции, события и управление окнами терминала

## Назначение

Tiger API — локальный WebSocket-сервер терминала Tiger.com Windows для связи с торговыми ботами, скринерами и другими внешними сервисами.

Через API можно:

* читать торговые подключения терминала, их счета, активные ордера, позиции и балансы;
* выставлять, отменять и изменять ордера через уже открытые подключения к биржам;
* получать поток событий: изменения ордеров, позиций, балансов и сделки;
* управлять окнами терминала: переключать инструмент в группе линковки и открывать мультибиржевой стакан.

API работает **только по WebSocket**, REST нет. Все сообщения — текстовые JSON-кадры.

{% hint style="info" %}
Команды `setLinkSymbol`, `OpenMultiExSymbol` и `ping` из предыдущей версии API работают без изменений. Торговые команды, чтение состояния и события добавлены в Terminal 7.4.
{% endhint %}

## Где включать в терминале

1. Откройте меню: **Настройки → Локальный сервер сигналов**.
2. Включите **Включить сервер сигналов** и нажмите **Сохранить**.
3. По умолчанию используется порт **7819**. Порт можно изменить только при выключенном сервере.

| Настройка                              | Назначение                                                                                                                                                                           |
| -------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **Включить сервер сигналов**           | Запускает WebSocket-сервер                                                                                                                                                           |
| **Порт**                               | Порт сервера, по умолчанию `7819`                                                                                                                                                    |
| **Разрешить торговлю** (Allow trading) | Разрешает торговые команды `PLACEORDER`, `CANCELORDER`, `CANCELALL`, `MODIFYORDER`. По умолчанию выключена. Чтение, подписка на события и команды управления окнами работают без неё |

{% hint style="warning" %}
У API нет авторизации: подключиться может любая программа на этом компьютере, в том числе веб-страница в браузере. Включайте **Разрешить торговлю** только на то время, когда API действительно торгует.
{% endhint %}

## Подключение

### Адрес

```
ws://127.0.0.1:7819/
```

* Сервер слушает только `127.0.0.1` — подключиться можно только с того же компьютера.
* Шифрования (`wss://`) и авторизации нет.
* Одновременно можно держать несколько подключений; у каждого своя подписка на события.
* Принимаются только **текстовые** кадры. Бинарный кадр закрывает соединение с кодом `1003`.

### Когда сервер закрывает соединение

| Причина                                                                  | Что происходит                                                                                                                     |
| ------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------- |
| Невалидный JSON, неизвестный или отсутствующий `type`, неверный тип поля | Сервер присылает ошибку `Invalid JSON` и закрывает соединение с кодом `1007` — см. [ошибки уровня сокета](#oshibki-urovnya-soketa) |
| Бинарный кадр                                                            | Закрытие с кодом `1003` без ответа                                                                                                 |
| Клиент не ответил на служебный WebSocket-ping за 1 секунду               | Закрытие с кодом `1002`, см. [проверку связи](#proverka-svyazi)                                                                    |
| Пользователь выключил API или сменил порт, терминал закрыт               | Закрываются все соединения                                                                                                         |

После переподключения подписку нужно оформить заново — она живёт вместе с сокетом.

### Проверка связи

**Со стороны сервера.** Раз в 60 секунд сервер шлёт служебный WebSocket-ping (control frame, не текстовое сообщение) и закрывает с кодом `1002` соединение, которое не ответило pong в течение **1 секунды**. Браузеры и распространённые библиотеки (`websockets` в Python, `ws` в Node.js, .NET `ClientWebSocket`) отвечают на такой ping автоматически. Если вы работаете с сокетом на низком уровне, отвечайте на control-ping сами и не блокируйте поток чтения надолго.

**Со стороны клиента** — два варианта:

* текстовый кадр `ping` → сервер отвечает текстовым кадром `pong` (**не JSON**, учтите это в парсере);
* JSON-команда [`PING`](#ping) → обычный JSON-ответ.

Простой как таковой сервер не отслеживает: соединение, которое отвечает на служебные ping, живёт сколько угодно.

## Формат сообщений

По сокету ходят три вида сообщений: запрос, ответ и пуш.

### Запрос (клиент → сервер)

```json
{ "type": "GETORDERS", "requestId": "42", "id": "3f2a9c1e-..." }
```

| Поле        | Тип    | Обяз. | Описание                                                                                                                                          |
| ----------- | ------ | ----- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| `type`      | string | да    | Имя команды, регистр не важен (`getorders` тоже подойдёт)                                                                                         |
| `requestId` | string | нет   | Ваш идентификатор запроса; возвращается в ответе без изменений. Сервер его не проверяет — он нужен только вам, чтобы сопоставить ответ с запросом |
| …           |        |       | Поля команды — см. [Команды](#komandy)                                                                                                            |

Лишние поля игнорируются. Ответы приходят в порядке обработки запросов; для надёжного сопоставления используйте `requestId`.

### Ответ (сервер → клиент)

На каждый JSON-запрос приходит ровно один ответ:

```json
{
  "title": "Orders",
  "message": "Active orders of the connection.",
  "requestId": "42",
  "request": { "type": "GETORDERS", "requestId": "42", "id": "3f2a9c1e-..." },
  "response": {
    "type": "successfully",
    "data": { "id": "3f2a9c1e-...", "orders": [] },
    "timestamp": "2026-09-10T12:00:00.1234567+03:00"
  }
}
```

| Поле                 | Описание                                                                                                                      |
| -------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
| `title`, `message`   | Текст для человека. **Не разбирайте его программно** — ориентируйтесь на `response.type` и `response.error.code`              |
| `requestId`          | Эхо вашего `requestId`; отсутствует, если вы его не передали                                                                  |
| `request`            | Эхо запроса в том виде, как его разобрал сервер (со всеми полями команды, в том числе незаданными)                            |
| `response.type`      | `successfully` или `error`                                                                                                    |
| `response.data`      | Полезная нагрузка успешного ответа. Отсутствует у ошибок и у команд без данных (`PING`, `SETLINKSYMBOL`, `OPENMULTIEXSYMBOL`) |
| `response.error`     | Только при ошибке: `{ "message": "...", "code": 400 }` — см. [Ошибки](#oshibki)                                               |
| `response.timestamp` | Время ответа — локальное время компьютера с терминалом, со смещением                                                          |

Ошибка:

```json
{
  "title": "Connection not found",
  "message": "Trading connection not found.",
  "requestId": "42",
  "request": { "...": "..." },
  "response": {
    "type": "error",
    "error": { "message": "Connection not found", "code": 400 },
    "timestamp": "2026-09-10T12:00:00.1234567+03:00"
  }
}
```

### Пуш (сервер → клиент)

Событие по подписке. Приходит без запроса, **без** `title`, `message`, `requestId` и `response`:

```json
{ "type": "ORDER_UPDATE", "data": { "...": "..." } }
```

### Как различить сообщения

| Признак                                                | Что это                   |
| ------------------------------------------------------ | ------------------------- |
| Текст `pong` (не JSON)                                 | Ответ на текстовый `ping` |
| Есть поле `response`                                   | Ответ на запрос           |
| Есть `type` и `data` на верхнем уровне, нет `response` | Пуш                       |

## Соглашения

### Идентификаторы

| Что              | Поле                       | Откуда взять                                                                      | Примечание                                                                                      |
| ---------------- | -------------------------- | --------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- |
| Подключение      | `id`                       | [`GETCONNECTIONS`](#getconnections) → `connections[].id`                          | GUID, не меняется между перезапусками терминала                                                 |
| Счёт             | `accountId`                | `connections[].accounts[].id` того же подключения                                 | Счёт всегда принадлежит одному подключению                                                      |
| Ордер (наш)      | `id` в объекте ордера      | Ответ [`PLACEORDER`](#placeorder), [`GETORDERS`](#getorders), пуши `ORDER_UPDATE` | Есть сразу, до ответа биржи. **Именно его принимают `CANCELORDER` и `MODIFYORDER`**             |
| Ордер (биржевой) | `orderId` в объекте ордера | Те же места                                                                       | `null`, пока биржа не подтвердила ордер. По нему связываются сделки: `EXECUTION_UPDATE.orderId` |

{% hint style="info" %}
В командах поле `id` — это **всегда id подключения**. Id ордера в `CANCELORDER` / `MODIFYORDER` передаётся в поле `orderId`, и это **наш** id (поле `id` объекта ордера), а не биржевой `orderId`.
{% endhint %}

### Инструмент: `e`, `m`, `symbol`

Инструмент в командах задаётся тройкой полей. Регистр значений не важен.

| Поле     | Значения                                                            | Откуда взять                                                 |
| -------- | ------------------------------------------------------------------- | ------------------------------------------------------------ |
| `e`      | Код биржи инструмента — см. [коды бирж](#kody-birzh)                | Поле `e` в ордерах, позициях и сделках                       |
| `m`      | `SPOT` — спот (крипта); `FUTURES` — фьючерсы и бессрочные контракты | Поле `m` в ордерах, позициях и сделках                       |
| `symbol` | Код инструмента, например `BTCUSDT`                                 | Поле `symbol` в ордерах, позициях и сделках; код в терминале |

* **Самый надёжный источник `e` и `symbol`** — ответы `GETORDERS` / `GETPOSITIONS` и пуши: там они отдаются ровно в том виде, в каком их ищет API. Если сомневаетесь в коде, выставьте ордер по инструменту вручную в терминале и посмотрите его в `GETORDERS`.
* **Название биржи в интерфейсе терминала не годится для `e`.** Например, спот и фьючерсы Binance терминал показывает одинаково — «Binance», а коды у них разные.
* `connections[].exchange` — **имя коннектора**, а не код биржи, и может не совпадать с `e` (например, «Binance via TT Broker»). Для `e` его не используйте.
* Акции, валюты и опционы через API не адресуются: `m` понимает только `SPOT` и `FUTURES`.
* Если тройке соответствует ноль или несколько инструментов, команда вернёт ошибку (`Symbol not found` / `More than one symbol found`).
* В поле `symbol` всегда используйте код тикера **конкретной биржи** из поля `e`.

#### Коды бирж

У большинства бирж спот и фьючерсы идут под **одним** кодом и различаются только `m`. **Исключение — Binance:** у каждого рынка свой код.

| Биржа       | `e` для `SPOT`                              | `e` для `FUTURES`                                           |
| ----------- | ------------------------------------------- | ----------------------------------------------------------- |
| **Binance** | `BINANCE`; Binance Alpha — `BINANCE-ALPHA`  | USDⓈ-M — **`BINANCE-FUT`**; COIN-M — **`BINANCE-FUT-COIN`** |
| Bybit       | `BYBIT`                                     | `BYBIT`                                                     |
| OKX         | `OKX`                                       | `OKX`                                                       |
| Bitget      | `Bitget`                                    | `Bitget`                                                    |
| Gate.io     | `Gate.io`                                   | `Gate.io`                                                   |
| MEXC        | `MEXC`                                      | `MEXC`                                                      |
| KuCoin      | `Kucoin`                                    | `Kucoin`                                                    |
| BingX       | `BingX`                                     | `BingX`                                                     |
| Hyperliquid | `Hyperliquid`                               | `Hyperliquid`                                               |
| Lighter     | `Lighter`                                   | `Lighter`                                                   |
| AsterDEX    | `AsterDEX`                                  | `AsterDEX`                                                  |
| Backpack    | `Backpack`                                  | `Backpack`                                                  |
| BitMEX      | `BITMEX`                                    | `BITMEX`                                                    |
| Kraken      | `Kraken`                                    | `Kraken`                                                    |
| TigerX      | `TigerX Binance`, `TigerX OKX` (с пробелом) | Те же                                                       |

Регистр не важен: `bitget` и `BITGET` тоже подойдут.

{% hint style="warning" %}
`{"e": "BINANCE", "m": "FUTURES", ...}` **всегда** даёт `Symbol not found`: фьючерсов с кодом биржи `BINANCE` нет. Для фьючерсов Binance используйте `BINANCE-FUT` или `BINANCE-FUT-COIN`.
{% endhint %}

Код фьючерсного инструмента (`symbol`) берётся из базы инструментов терминала и не обязательно совпадает со спотовым кодом той же пары. Если по спотовому коду фьючерс не находится, возьмите `symbol` из `GETORDERS` / `GETPOSITIONS`.

### Числа

* Цены и объёмы — обычные десятичные числа в JSON (`65000.1`, `0.001`), **не** шаги цены и не строки.
* При выставлении цена округляется до шага цены инструмента, объём — до шага объёма. В ответах и пушах — уже округлённые значения.
* Сервер отдаёт числа с точностью до 15 значащих цифр, как их показывает терминал (`0.3`, а не `0.30000000000000004`).
* `NaN` и `Infinity` не принимаются.

### Время

| Поле                                     | Формат                                                                                                                                                                                                                                                                                                                            |
| ---------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `response.timestamp`                     | ISO 8601, локальное время компьютера с терминалом, со смещением: `2026-09-10T12:00:00.1234567+03:00`                                                                                                                                                                                                                              |
| `time` в ордерах и сделках               | ISO 8601, **время биржи** по правилам терминала: криптобиржи — UTC; MOEX, SPBEX — московское; CME, CBOT, COMEX, NYMEX, EUREX, ICE, NYSE, NASDAQ — нью-йоркское (Eastern). Смещение, как правило, не указано (`2026-09-10T09:00:00.123`), у части криптобирж бывает суффикс `Z`. Время без смещения трактуйте по бирже из поля `e` |
| `time` у только что выставленного ордера | `0001-01-01T00:00:00` — время ещё не назначено, придёт в `ORDER_UPDATE`                                                                                                                                                                                                                                                           |

### Значения перечислений

| Поле                       | Значения                                                                                                 |
| -------------------------- | -------------------------------------------------------------------------------------------------------- |
| `side`                     | `Buy`, `Sell` (во входящих командах регистр не важен)                                                    |
| `orderType` (в запросе)    | `Market`, `Limit`, `Stop`, `StopLimit`                                                                   |
| `type` (в объекте ордера)  | `Market`, `Limit`, `Stop`, `StopLimit`, `Trigger` — см. [жизненный цикл ордера](#zhiznennyi-cikl-ordera) |
| `state` (в объекте ордера) | `Wait`, `Active`, `Completed`, `Canceled`, `Rejected`, `None`                                            |
| `validity`                 | `DAY`, `GTC` (регистр не важен)                                                                          |
| `m`                        | `SPOT`, `FUTURES`                                                                                        |

### Какие подключения видны

Все торговые подключения терминала, кроме **симулятора**, **Trust** и **плеера**. Их ордера, позиции и события в API не попадают, а их id дают `Connection not found`.

## Быстрый старт

```
1. Подключиться:            ws://127.0.0.1:7819/
2. GETCONNECTIONS           → взять id подключения и accountId
3. SUBSCRIBE                → включить события (снапшота нет)
4. GETORDERS / GETPOSITIONS / GETBALANCE  → текущее состояние
5. PLACEORDER               → в ответе наш id ордера
6. ждать ORDER_UPDATE с этим id и заполненным orderId → ордер на бирже
7. CANCELORDER / MODIFYORDER с orderId = наш id
```

Порядок 3 → 4 важен: сначала подписка, потом снимок — так не потеряются события, случившиеся между ними.

## Команды

| Команда                                   | Назначение                           | Нужна «Разрешить торговлю» | `data` в ответе            |
| ----------------------------------------- | ------------------------------------ | -------------------------- | -------------------------- |
| [`PING`](#ping)                           | Проверка связи                       | нет                        | —                          |
| [`GETCONNECTIONS`](#getconnections)       | Подключения и счета                  | нет                        | `{ connections }`          |
| [`GETORDERS`](#getorders)                 | Активные ордера подключения          | нет                        | `{ id, orders }`           |
| [`GETPOSITIONS`](#getpositions)           | Открытые позиции                     | нет                        | `{ id, positions }`        |
| [`GETBALANCE`](#getbalance)               | Балансы портфелей                    | нет                        | `{ id, balances }`         |
| [`PLACEORDER`](#placeorder)               | Выставить ордер                      | **да**                     | [Order](#order)            |
| [`CANCELORDER`](#cancelorder)             | Отменить ордер                       | **да**                     | [Order](#order)            |
| [`CANCELALL`](#cancelall)                 | Отменить несколько ордеров           | **да**                     | `{ cancelled }`            |
| [`MODIFYORDER`](#modifyorder)             | Изменить цену / объём                | **да**                     | [Order](#order)            |
| [`SUBSCRIBE`](#subscribe-unsubscribe)     | Включить каналы событий              | нет                        | `{ subscribed, channels }` |
| [`UNSUBSCRIBE`](#subscribe-unsubscribe)   | Выключить каналы                     | нет                        | `{ subscribed, channels }` |
| [`SETLINKSYMBOL`](#setlinksymbol)         | Сменить инструмент в связанных окнах | нет                        | —                          |
| [`OPENMULTIEXSYMBOL`](#openmultiexsymbol) | Открыть мультибиржевой стакан        | нет                        | —                          |

Ниже для каждой команды показаны поля запроса и `response.data`. Конверт ответа — как в разделе [Формат сообщений](#format-soobshenii).

### PING

Проверка связи через JSON. Для keep-alive проще текстовый `ping` → `pong` (см. [Проверка связи](#proverka-svyazi)).

```json
{ "type": "PING", "requestId": "1" }
```

Ответ: `response.type = "successfully"`, без `data`.

<details>

<summary>Пример ответа</summary>

```json
{
  "title": "Pong",
  "message": "Response 'pong' to 'ping'.",
  "requestId": "1",
  "request": { "type": "Ping", "requestId": "1" },
  "response": { "type": "successfully", "timestamp": "2026-09-10T12:00:00.0389026+03:00" }
}
```

</details>

### GETCONNECTIONS

Торговые подключения терминала и их счета. С этой команды начинается работа: отсюда берутся `id` и `accountId`.

Поля: только `type` (и `requestId`).

```json
{ "type": "GETCONNECTIONS", "requestId": "2" }
```

`data`:

```json
{
  "connections": [
    {
      "id": "3f2a9c1e-7b1d-4a6e-9c55-2d8f0e1a4b77",
      "name": "Binance Main",
      "exchange": "Binance",
      "marketLabel": "Unified",
      "connected": true,
      "viewOnly": false,
      "accounts": [
        { "id": "3f2a9c1e-7b1d-4a6e-9c55-2d8f0e1a4b77@USDT-M", "name": "USDT-M" }
      ]
    }
  ]
}
```

Поля — см. [Connection](#connection).

* `connected: false` — подключение сейчас отключено. Читать можно (данные могут быть устаревшими или пустыми), торговые команды вернут `Connection is offline`.
* `viewOnly: true` — подключение только для чтения, торговые команды вернут `Connection is view only`.

### GETORDERS

Активные ордера подключения (`state` = `Wait` или `Active`), **подтверждённые биржей**. Включает ордера, выставленные из любого источника — из терминала, через API, с других устройств, — если терминал о них знает.

| Поле | Тип    | Обяз. | Описание       |
| ---- | ------ | ----- | -------------- |
| `id` | string | да    | Id подключения |

```json
{ "type": "GETORDERS", "requestId": "3", "id": "3f2a9c1e-..." }
```

`data`:

```json
{
  "id": "3f2a9c1e-...",
  "orders": [ { "...": "объект Order" } ]
}
```

Объект — см. [Order](#order). Только что выставленный ордер попадает в список после подтверждения биржей (пришёл `ORDER_UPDATE` с заполненным `orderId`).

### GETPOSITIONS

Открытые позиции подключения (с ненулевым объёмом).

| Поле | Тип    | Обяз. | Описание       |
| ---- | ------ | ----- | -------------- |
| `id` | string | да    | Id подключения |

```json
{ "type": "GETPOSITIONS", "requestId": "4", "id": "3f2a9c1e-..." }
```

`data`:

```json
{
  "id": "3f2a9c1e-...",
  "positions": [
    {
      "id": "3f2a9c1e-...",
      "accountId": "3f2a9c1e-...@USDT-M",
      "e": "BINANCE-FUT",
      "m": "FUTURES",
      "symbol": "BTCUSDT",
      "side": "Buy",
      "size": 0.015,
      "avgPrice": 64210.5
    }
  ]
}
```

Объект — см. [Position](#position).

### GETBALANCE

Портфели (балансы) подключения.

| Поле | Тип    | Обяз. | Описание       |
| ---- | ------ | ----- | -------------- |
| `id` | string | да    | Id подключения |

```json
{ "type": "GETBALANCE", "requestId": "5", "id": "3f2a9c1e-..." }
```

`data`:

```json
{
  "id": "3f2a9c1e-...",
  "balances": [
    {
      "id": "3f2a9c1e-...",
      "currency": "USDT",
      "name": "USDT-M",
      "balance": 1520.35,
      "freeMargin": 1200.1,
      "usedMargin": 320.25
    }
  ]
}
```

Объект — см. [Balance](#balance). Баланс относится **к подключению, а не к счёту**: у одного подключения может быть несколько портфелей (по валютам, спот / фьючерсы), а связи портфеля со счётом в терминале нет, поэтому `accountId` здесь нет.

### PLACEORDER

Выставить ордер. Нужна настройка **Разрешить торговлю**, подключение должно быть `connected` и не `viewOnly`.

| Поле         | Тип    | Обяз.    | Описание                                                           |
| ------------ | ------ | -------- | ------------------------------------------------------------------ |
| `id`         | string | да       | Id подключения                                                     |
| `accountId`  | string | да       | Счёт этого подключения                                             |
| `e`          | string | да       | Биржа инструмента, см. [Инструмент](#instrument-e-m-symbol)        |
| `m`          | string | да       | `SPOT` / `FUTURES`                                                 |
| `symbol`     | string | да       | Код инструмента                                                    |
| `side`       | string | да       | `Buy` / `Sell`                                                     |
| `orderType`  | string | да       | `Market` / `Limit` / `Stop` / `StopLimit`                          |
| `size`       | number | да       | Объём, > 0, не меньше шага объёма инструмента                      |
| `price`      | number | см. ниже | Лимитная цена                                                      |
| `stopPrice`  | number | см. ниже | Стоп-цена (цена срабатывания)                                      |
| `validity`   | string | нет      | `DAY` (по умолчанию) или `GTC`. Любое другое значение — ошибка     |
| `comment`    | string | нет      | Комментарий к ордеру в терминале                                   |
| `reduceOnly` | bool   | нет      | Только уменьшение позиции. Поддерживается большинством коннекторов |
| `postOnly`   | bool   | нет      | Только мейкер. Поддерживается большинством коннекторов             |
| `hidden`     | bool   | нет      | Скрытый ордер. Только BitMEX                                       |
| `closeOnly`  | bool   | нет      | Только закрытие. Только BitMEX                                     |

Флаги, которые коннектор не поддерживает, **молча игнорируются**, ошибки не будет.

Какие цены нужны:

| `orderType` | `price`                                               | `stopPrice`     |
| ----------- | ----------------------------------------------------- | --------------- |
| `Market`    | Не используется, можно не передавать или передать `0` | Не используется |
| `Limit`     | **Обязательна**                                       | Не используется |
| `Stop`      | Не используется                                       | **Обязательна** |
| `StopLimit` | **Обязательна**                                       | **Обязательна** |

На криптоинструментах используемая цена должна быть **> 0**, иначе `Invalid price`. На биржевых фьючерсах ноль и отрицательные цены допускаются (спреды).

```json
{
  "type": "PLACEORDER",
  "requestId": "6",
  "id": "3f2a9c1e-...",
  "accountId": "3f2a9c1e-...@USDT-M",
  "e": "BINANCE-FUT",
  "m": "FUTURES",
  "symbol": "BTCUSDT",
  "side": "Buy",
  "orderType": "Limit",
  "size": 0.01,
  "price": 60000,
  "validity": "GTC",
  "postOnly": true
}
```

`data` — объект [Order](#order) **до отправки на биржу**:

```json
{
  "id": "b7c1d2e3-4f56-4789-a012-3456789abcde",
  "orderId": null,
  "accountId": "3f2a9c1e-...@USDT-M",
  "e": "BINANCE-FUT",
  "m": "FUTURES",
  "symbol": "BTCUSDT",
  "side": "Buy",
  "type": "Limit",
  "state": "Wait",
  "price": 60000,
  "stopPrice": null,
  "size": 0.01,
  "filled": 0,
  "remaining": 0.01,
  "initiator": "TigerAPI",
  "time": "0001-01-01T00:00:00"
}
```

{% hint style="warning" %}
**Успешный ответ означает, что ордер принят терминалом и отправляется на биржу, а не что биржа его приняла.** Дальнейшая судьба ордера приходит в `ORDER_UPDATE` с тем же `id`: подтверждение (`orderId` заполнен, `state: Active`), отклонение (`state: Rejected`, `error`), исполнение. Подпишитесь на `ORDER_UPDATE` **до** выставления.
{% endhint %}

Порядок проверок (первая сработавшая даёт ответ): поля → «Разрешить торговлю» → подключение (есть / подключено / не view only) → счёт → инструмент → счёт может торговать → инструмент разрешён → объём → цены.

### CANCELORDER

Отменить ордер. Нужна настройка **Разрешить торговлю**.

| Поле      | Тип    | Обяз. | Описание                                                           |
| --------- | ------ | ----- | ------------------------------------------------------------------ |
| `id`      | string | да    | Id подключения                                                     |
| `orderId` | string | да    | **Наш** id ордера (поле `id` объекта [Order](#order)), не биржевой |

```json
{ "type": "CANCELORDER", "requestId": "7", "id": "3f2a9c1e-...", "orderId": "b7c1d2e3-..." }
```

`data` — объект [Order](#order) в состоянии на момент запроса. Результат отмены приходит в `ORDER_UPDATE`: `state: Canceled` или `error`, если биржа отказала.

* Отменить можно только ордер, **подтверждённый биржей**. До подтверждения — ошибка `409 Order is pending`; повторите после `ORDER_UPDATE` с заполненным `orderId`.
* Если ордер уже отменяется — `409 Order can not be canceled`.
* Если это SL / TP-ордер позиции, он снимается и из защитной стратегии терминала — так же, как при отмене из панели ордеров.

### CANCELALL

Отменить активные ордера подключения, с необязательными фильтрами. Нужна настройка **Разрешить торговлю**.

| Поле        | Тип    | Обяз. | Описание                                |
| ----------- | ------ | ----- | --------------------------------------- |
| `id`        | string | да    | Id подключения                          |
| `accountId` | string | нет   | Только ордера этого счёта               |
| `symbol`    | string | нет   | Только ордера по этому коду инструмента |

* Пустая строка равносильна отсутствию фильтра.
* `symbol` фильтрует **только по коду**, на всех рынках подключения: если `BTCUSDT` есть и в споте, и во фьючерсах, отменятся ордера на обоих.
* Отменяются только ордера, подтверждённые биржей и не находящиеся уже в отмене.

```json
{ "type": "CANCELALL", "requestId": "8", "id": "3f2a9c1e-...", "symbol": "BTCUSDT" }
```

`data`:

```json
{ "cancelled": 3 }
```

`cancelled` — сколько ордеров отправлено на отмену. Что сделала биржа, приходит в `ORDER_UPDATE` по каждому ордеру. SL / TP снимаются и из защитной стратегии, как в `CANCELORDER`.

При фильтре по `symbol` терминал использует массовую отмену по инструменту, которая снимает **все** активные ордера этого инструмента на счёте.

### MODIFYORDER

Изменить цену, стоп-цену или объём активного немаркетного ордера. Нужна настройка **Разрешить торговлю**.

| Поле        | Тип    | Обяз. | Описание                                                                                                          |
| ----------- | ------ | ----- | ----------------------------------------------------------------------------------------------------------------- |
| `id`        | string | да    | Id подключения                                                                                                    |
| `orderId`   | string | да    | **Наш** id ордера                                                                                                 |
| `price`     | number | нет\* | Новая цена. Не передано — остаётся прежняя. Передано — должно быть **> 0**                                        |
| `stopPrice` | number | нет\* | Новая стоп-цена. Не передано — остаётся прежняя. Передано — должно быть **> 0**                                   |
| `size`      | number | нет\* | Новый объём (неисполненная часть). Не передано — остаётся текущий остаток. Передано — > 0 и не меньше шага объёма |

\* Нужно хотя бы одно из трёх.

Не передавайте `price: 0` или `null` как «без изменений» — для этого поле просто не отправляют. `0` и отрицательные значения — ошибка на любых инструментах.

```json
{ "type": "MODIFYORDER", "requestId": "9", "id": "3f2a9c1e-...", "orderId": "b7c1d2e3-...", "price": 60500 }
```

`data` — объект [Order](#order) **со старыми значениями**. Успешный ответ означает, что запрос принят терминалом и передан коннектору; изменение ещё не выполнено. Результат приходит в `ORDER_UPDATE`.

* Как и отмена, работает только для ордера, подтверждённого биржей: до этого — `409 Order is pending`.
* Если ордер уже изменяется — `409 Order can not be modified`.

#### Как выполняется изменение

Способ зависит от подключения. **На большинстве бирж ордер не изменяется, а отменяется и выставляется заново.**

| Подключение                                                                                                                    | Способ                   | Что приходит в `ORDER_UPDATE`                                                                                         |
| ------------------------------------------------------------------------------------------------------------------------------ | ------------------------ | --------------------------------------------------------------------------------------------------------------------- |
| BitMEX; Bitget (единый аккаунт); Rithmic; OEC                                                                                  | Изменение на бирже       | Тот же ордер (тот же `id` и `orderId`) с новыми значениями. При отказе — тот же ордер с прежними значениями и `error` |
| Локальные триггеры (`type: Trigger`)                                                                                           | Изменение в терминале    | Тот же ордер с новыми значениями. Исключение — см. [Локальные триггеры](#lokalnye-triggery)                           |
| **Все остальные**: Binance, Bybit, OKX, Gate.io, MEXC, KuCoin, BingX, Hyperliquid, TigerX, Bitget (классический аккаунт) и др. | **Отмена + новый ордер** | Старый ордер со `state: Canceled`, затем **новый ордер** с новыми `id` и `orderId`                                    |

Особенности изменения на бирже:

* OEC меняет только `price` / `stopPrice`; `size` игнорируется.
* Bitget (единый аккаунт) передаёт объём как **полный объём ордера**. Если `size` не передан, передаётся текущий остаток, и у частично исполненного ордера полный объём уменьшится до остатка.

#### Отмена + новый ордер

Последовательность:

```
MODIFYORDER (orderId = A) ──► ответ: ордер A со старыми значениями
   │
   ├─► ORDER_UPDATE: ордер A, state=Canceled
   │
   └─► ORDER_UPDATE: ордер B (новый id, orderId=null, initiator="TC-PlaceModifiedOrder")
          ├─► ORDER_UPDATE: ордер B, state=Active, orderId заполнен    выставлен
          └─► ORDER_UPDATE: ордер B, state=Rejected, error="…"        биржа отказала
```

Что нужно учитывать:

* **У нового ордера новый `id`, и связи со старым в API нет.** Ни одно поле не указывает, что B заменил A. Как его найти — см. [ниже](#kak-naiti-order-zamenu). Дальше отменять и изменять нужно уже **новый** `id`.
* **`initiator` нового ордера — `TC-PlaceModifiedOrder`, а не `TigerAPI`.** Если вы отбираете свои ордера по `initiator`, учитывайте оба значения.
* **Операция не атомарна.** Между отменой и выставлением ордера на бирже нет, место в очереди теряется. Если новый ордер биржа отклонит (не хватает средств, `postOnly` при сдвинувшейся цене, лимиты), ордера **не будет вовсе**, хотя ответ на `MODIFYORDER` был успешным. Отслеживайте `state: Rejected` у нового ордера.
* **Объём нового ордера** — переданный `size`, а если его нет — остаток старого ордера **на момент запроса**. Если старый ордер успел частично исполниться до отмены, суммарно исполнится больше задуманного. Проверяйте `filled` старого ордера в его `ORDER_UPDATE` со `state: Canceled`.
* В новый ордер переносятся тип, сторона, `validity` и флаги (`reduceOnly`, `postOnly`, `hidden`, `closeOnly`). **`comment` не переносится.**
* Новый ордер создаёт терминал, а не API: `CANCELORDER` / `MODIFYORDER` по нему до подтверждения биржей ответят `Order not found`, а не `Order is pending`. Дождитесь `ORDER_UPDATE` с заполненным `orderId`.
* Если биржа **отказала в отмене** старого ордера, придёт `ORDER_UPDATE` по нему с `error`, ордер останется с прежними параметрами, новый выставлен не будет. После этого `MODIFYORDER` по этому ордеру отвечает `409 Order can not be modified`; `CANCELORDER` работает — отмените ордер и выставьте новый через `PLACEORDER`.
* **Подпишитесь на `ORDER_UPDATE` до `MODIFYORDER`.** Ответ на `MODIFYORDER` содержит старый ордер, и id замены приходит только пушем. Без подписки новый ордер можно найти лишь через `GETORDERS` после подтверждения биржей, по тем же признакам.

#### Как найти ордер-замену

Ответ на `MODIFYORDER` возвращает старый ордер: запомните его и ждите в `ORDER_UPDATE` ордер с **другим** `id`, `initiator: "TC-PlaceModifiedOrder"` и теми же `accountId`, `symbol`, `side`. Если по старому ордеру пришёл `error`, замены не будет. Если изменение выполнено на месте, замены тоже не будет, а id остаётся прежним.

```js
let replacing = null;   // ордер, который сейчас заменяется

async function modify(connectionId, orderId, fields) {
  const old = await request('MODIFYORDER', { id: connectionId, orderId, ...fields });
  replacing = { id: old.id, accountId: old.accountId, symbol: old.symbol, side: old.side };
}

function onOrderUpdate(order) {           // вызывать на каждый пуш ORDER_UPDATE
  if (!replacing) return;

  if (order.id !== replacing.id
      && order.initiator === 'TC-PlaceModifiedOrder'
      && order.accountId === replacing.accountId
      && order.symbol === replacing.symbol
      && order.side === replacing.side) {
    console.log(`order ${replacing.id} replaced by ${order.id}`);   // дальше работаем с order.id
    replacing = null;
    return;
  }

  if (order.id === replacing.id && order.error) {
    replacing = null;                     // биржа отказала: старый ордер остался, замены не будет
  }
}
```

Признаки не уникальны. Если одновременно изменяются несколько ордеров одного счёта, инструмента и стороны — через API или вручную в терминале, — дополнительно сверяйте новые `price` / `stopPrice` / `size` с переданными в `MODIFYORDER`.

#### Локальные триггеры

Триггер меняется на месте. Но если по инструменту уже есть **другой** локальный триггер с новой `stopPrice`, изменяемый триггер отменяется, а его объём объединяется с тем триггером (см. [Жизненный цикл ордера](#zhiznennyi-cikl-ordera)). Пуши тогда придут по существующему триггеру.

### SUBSCRIBE / UNSUBSCRIBE

Управление событиями. Одна подписка на сокет, она охватывает **все** видимые подключения, в том числе подключённые позже.

| Поле       | Тип       | Обяз. | Описание                                            |
| ---------- | --------- | ----- | --------------------------------------------------- |
| `channels` | string\[] | нет   | Каналы; без поля или с пустым массивом — все каналы |

Каналы: `ORDER_UPDATE`, `POSITION_UPDATE`, `EXECUTION_UPDATE`, `BALANCE_UPDATE` (регистр не важен). Неизвестный канал — ошибка, подписка не меняется.

| Запрос                       | Действие                                    |
| ---------------------------- | ------------------------------------------- |
| `SUBSCRIBE` без `channels`   | Подписка на все каналы                      |
| `SUBSCRIBE` с `channels`     | **Добавляет** перечисленные к уже имеющимся |
| `UNSUBSCRIBE` без `channels` | Снимает всё                                 |
| `UNSUBSCRIBE` с `channels`   | Снимает только перечисленные                |

```json
{ "type": "SUBSCRIBE", "requestId": "10", "channels": ["ORDER_UPDATE", "EXECUTION_UPDATE"] }
```

`data` — итоговый набор каналов этого сокета, а не изменение:

```json
{ "subscribed": true, "channels": ["ORDER_UPDATE", "EXECUTION_UPDATE"] }
```

После `UNSUBSCRIBE` всего: `{ "subscribed": false, "channels": [] }`.

Снапшот текущего состояния при подписке **не присылается** — возьмите его командами `GET*`. Закрытие сокета снимает подписку.

### SETLINKSYMBOL

Переключает инструмент во всех окнах терминала выбранной группы линковки (цветные буквы / цифры на окнах). Инструмент на графике и/или в стакане обновляется без ручных действий.

| Поле        | Тип    | Обяз. | Описание                                                                                              |
| ----------- | ------ | ----- | ----------------------------------------------------------------------------------------------------- |
| `e`         | string | да    | Биржа                                                                                                 |
| `m`         | string | да    | `SPOT` / `FUTURES`                                                                                    |
| `symbol`    | string | да    | Код инструмента                                                                                       |
| `linkGroup` | string | да    | Группа линковки: буква `A`–`Z` или число `0`–`18` — см. [Группы линковки](#gruppy-linkovki-linkgroup) |

```json
{ "type": "SETLINKSYMBOL", "requestId": "11", "e": "BINANCE", "m": "SPOT", "symbol": "BTCUSDT", "linkGroup": "A" }
```

Ответ без `data`. Успех означает, что команда принята; окна переключаются сразу после ответа.

<details>

<summary>Пример успешного ответа</summary>

```json
{
  "title": "Successful request",
  "message": "Symbol linked successfully.",
  "requestId": "11",
  "request": { "e": "BINANCE", "m": "Spot", "symbol": "BTCUSDT", "linkGroup": "A", "type": "SetLinkSymbol", "requestId": "11" },
  "response": { "type": "successfully", "timestamp": "2026-09-10T12:00:00.2327892+03:00" }
}
```

</details>

<details>

<summary>Примеры запросов для разных бирж</summary>

```js
socket.send('{"type": "setLinkSymbol", "e": "BINANCE", "m": "SPOT", "symbol": "BTCUSDT", "linkGroup": "A"}');
socket.send('{"type": "setLinkSymbol", "e": "BINANCE-FUT", "m": "FUTURES", "symbol": "BTCUSDT", "linkGroup": "A"}');
socket.send('{"type": "setLinkSymbol", "e": "TigerX Binance", "m": "SPOT", "symbol": "BTCUSDT", "linkGroup": "A"}');
socket.send('{"type": "setLinkSymbol", "e": "TigerX OKX", "m": "FUTURES", "symbol": "BTCUSDT", "linkGroup": "A"}');
socket.send('{"type": "setLinkSymbol", "e": "Bitget", "m": "FUTURES", "symbol": "PARTIUSDT", "linkGroup": "A"}');
socket.send('{"type": "setLinkSymbol", "e": "MOEX", "m": "FUTURES", "symbol": "CRZ5", "linkGroup": "A"}');
```

</details>

### OPENMULTIEXSYMBOL

Открывает мультибиржевой стакан по инструменту — по настройкам пользователя, в отдельном окне или вкладке.

| Поле     | Тип    | Обяз. | Описание           |
| -------- | ------ | ----- | ------------------ |
| `e`      | string | да    | Биржа              |
| `m`      | string | да    | `SPOT` / `FUTURES` |
| `symbol` | string | да    | Код инструмента    |

```json
{ "type": "OPENMULTIEXSYMBOL", "requestId": "12", "e": "BINANCE", "m": "SPOT", "symbol": "BTCUSDT" }
```

Ответ без `data`. Успех означает, что команда принята; окно открывается сразу после ответа. При обработке система проверяет, что по `e` + `m` + `symbol` найден **ровно один** активный инструмент; иначе — `Symbol not found` или `More than one symbol found`.

<details>

<summary>Пример успешного ответа</summary>

```json
{
  "title": "MultiEx window opened",
  "message": "Symbol opened in MultiEx window successfully.",
  "requestId": "12",
  "request": { "e": "BINANCE", "m": "Spot", "symbol": "BTCUSDT", "type": "OPENMULTIEXSYMBOL", "requestId": "12" },
  "response": { "type": "successfully", "timestamp": "2026-09-10T12:00:00.0000000+03:00" }
}
```

</details>

## Объекты данных

«Может отсутствовать» — поле не приходит вовсе, а не приходит как `null`.

### Connection

| Поле          | Тип                    | Описание                                                                                    |
| ------------- | ---------------------- | ------------------------------------------------------------------------------------------- |
| `id`          | string                 | Id подключения                                                                              |
| `name`        | string                 | Имя подключения, заданное пользователем. Может отсутствовать                                |
| `exchange`    | string                 | Имя коннектора. Может отсутствовать. **Не код биржи** — для `e` не использовать             |
| `marketLabel` | string                 | Подпись коннектора (`Unified`, `All`, `Spot`…), только для отображения. Может отсутствовать |
| `connected`   | bool                   | Подключено сейчас                                                                           |
| `viewOnly`    | bool                   | Только чтение, торговать нельзя                                                             |
| `accounts`    | [Account](#account)\[] | Счета подключения                                                                           |

### Account

| Поле   | Тип    | Описание                       |
| ------ | ------ | ------------------------------ |
| `id`   | string | `accountId` для команд         |
| `name` | string | Имя счёта. Может отсутствовать |

### Order

Один формат для `GETORDERS`, ответов `PLACEORDER` / `CANCELORDER` / `MODIFYORDER` и пуша `ORDER_UPDATE`.

| Поле        | Тип            | Описание                                                                                                                                                                                                           |
| ----------- | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `id`        | string         | **Наш** id ордера. Его принимают `CANCELORDER` / `MODIFYORDER`                                                                                                                                                     |
| `orderId`   | string \| null | Биржевой id. `null`, пока биржа не подтвердила                                                                                                                                                                     |
| `accountId` | string         | Счёт                                                                                                                                                                                                               |
| `e`         | string         | Биржа инструмента                                                                                                                                                                                                  |
| `m`         | string         | `SPOT` / `FUTURES`. Может отсутствовать у инструментов других типов                                                                                                                                                |
| `symbol`    | string         | Код инструмента                                                                                                                                                                                                    |
| `side`      | string         | `Buy` / `Sell`                                                                                                                                                                                                     |
| `type`      | string         | `Market`, `Limit`, `Stop`, `StopLimit`, `Trigger`                                                                                                                                                                  |
| `state`     | string         | `Wait`, `Active`, `Completed`, `Canceled`, `Rejected`, `None`                                                                                                                                                      |
| `price`     | number \| null | Цена; `null`, если не задана                                                                                                                                                                                       |
| `stopPrice` | number \| null | Стоп-цена; `null`, если не задана                                                                                                                                                                                  |
| `size`      | number         | Объём ордера                                                                                                                                                                                                       |
| `filled`    | number         | Исполнено                                                                                                                                                                                                          |
| `remaining` | number         | Осталось исполнить                                                                                                                                                                                                 |
| `initiator` | string         | Кто выставил: `TigerAPI` — через этот API; `TC-PlaceModifiedOrder` — ордер, выставленный заново при [изменении через отмену](#otmena--novyi-order). Может отсутствовать (например, у ордеров, загруженных с биржи) |
| `error`     | string         | Причина отказа — в `ORDER_UPDATE` при отклонении ордера, отмены или изменения. Может отсутствовать                                                                                                                 |
| `time`      | string         | Время последнего изменения, время биржи, см. [Время](#vremya)                                                                                                                                                      |

### Position

| Поле        | Тип    | Описание                                                     |
| ----------- | ------ | ------------------------------------------------------------ |
| `id`        | string | Id подключения                                               |
| `accountId` | string | Счёт                                                         |
| `e`         | string | Биржа                                                        |
| `m`         | string | `SPOT` / `FUTURES`. Может отсутствовать                      |
| `symbol`    | string | Код инструмента                                              |
| `side`      | string | Направление: `Buy` — лонг, `Sell` — шорт                     |
| `size`      | number | Объём, **всегда ≥ 0**; `0` — позиция закрыта (только в пуше) |
| `avgPrice`  | number | Средняя цена входа                                           |

### Balance

| Поле         | Тип    | Описание                             |
| ------------ | ------ | ------------------------------------ |
| `id`         | string | Id подключения                       |
| `currency`   | string | Валюта портфеля. Может отсутствовать |
| `name`       | string | Имя портфеля. Может отсутствовать    |
| `balance`    | number | Баланс                               |
| `freeMargin` | number | Свободная маржа                      |
| `usedMargin` | number | Задействованная маржа                |

У портфеля нет собственного id: различайте портфели по паре `currency` + `name` в пределах подключения.

### Execution

Сделка (исполнение ордера). Приходит только пушем `EXECUTION_UPDATE`.

| Поле         | Тип    | Описание                                                                                                     |
| ------------ | ------ | ------------------------------------------------------------------------------------------------------------ |
| `id`         | string | Id подключения                                                                                               |
| `accountId`  | string | Счёт                                                                                                         |
| `orderId`    | string | **Биржевой** id ордера — совпадает с `orderId` объекта [Order](#order), а не с его `id`. Может отсутствовать |
| `e`          | string | Биржа                                                                                                        |
| `m`          | string | `SPOT` / `FUTURES`. Может отсутствовать                                                                      |
| `symbol`     | string | Код инструмента                                                                                              |
| `side`       | string | `Buy` / `Sell`                                                                                               |
| `price`      | number | Цена сделки                                                                                                  |
| `size`       | number | Объём сделки                                                                                                 |
| `commission` | number | Комиссия, если биржа её сообщила. Может отсутствовать                                                        |
| `time`       | string | Время сделки, время биржи, см. [Время](#vremya)                                                              |

## Пуши (события)

Формат: `{ "type": "<канал>", "data": { ... } }`.

| Канал              | `data`                  | Когда приходит                                                                                                                        |
| ------------------ | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| `ORDER_UPDATE`     | [Order](#order)         | Любое изменение ордера: выставлен, подтверждён, частично / полностью исполнен, отменён, отклонён, изменён, отказ в отмене / изменении |
| `POSITION_UPDATE`  | [Position](#position)   | Изменились объём, направление или средняя цена позиции; закрытие — `size: 0`                                                          |
| `EXECUTION_UPDATE` | [Execution](#execution) | Каждая сделка                                                                                                                         |
| `BALANCE_UPDATE`   | [Balance](#balance)     | Изменились цифры портфеля                                                                                                             |

Что важно знать:

* **Все ордера, не только ваши.** `ORDER_UPDATE` и `EXECUTION_UPDATE` приходят по всем ордерам видимых подключений, в том числе выставленным вручную в терминале. Свои можно отличить по `initiator: "TigerAPI"` или по сохранённому `id`. Ордер, заново выставленный при `MODIFYORDER`, приходит с новым `id` и `initiator: "TC-PlaceModifiedOrder"` — см. [Отмена + новый ордер](#otmena--novyi-order).
* **Возможны повторы.** Одно и то же состояние ордера может прийти несколько раз (например, при выставлении — дважды). Обрабатывайте `ORDER_UPDATE` как снимок состояния ордера по ключу `id`, а не как приращение.
* **Позиции и балансы — только при реальном изменении.** Биржи пересылают их каждые пару секунд; повторы с теми же цифрами сервер отсекает. Отсекание общее для всех подключений к API, поэтому клиент, подписавшийся позже, не получит текущее значение, пока оно не изменится, — берите его через `GETPOSITIONS` / `GETBALANCE`.
* **Порядок.** Пуши приходят в том порядке, в каком терминал получил события. Ответ на торговую команду всегда приходит раньше пушей, которые она вызвала.
* **Читайте сокет постоянно.** Пуши всем клиентам отправляет один поток. Клиент, который перестал читать, задерживает пуши для всех; при переполнении очереди (10 000 сообщений) самые старые пуши выбрасываются. Клиент, который перестал отвечать, сервер отключит при ближайшей проверке связи (до 60 секунд).
* Симулятор, Trust и плеер событий не присылают.

## Жизненный цикл ордера

```
PLACEORDER ──► ответ: state=Wait, orderId=null
                 │
                 ├─► ORDER_UPDATE: state=Active, orderId="123…"   биржа приняла
                 │        │
                 │        ├─► EXECUTION_UPDATE (orderId="123…") + ORDER_UPDATE: filled растёт
                 │        ├─► ORDER_UPDATE: state=Completed        исполнен полностью
                 │        └─► ORDER_UPDATE: state=Canceled         отменён
                 │
                 └─► ORDER_UPDATE: state=Rejected, error="…"      биржа отказала
```

| Этап                                      | Что можно сделать                                                                    |
| ----------------------------------------- | ------------------------------------------------------------------------------------ |
| `orderId = null` (ждём биржу)             | Ничего: `CANCELORDER` / `MODIFYORDER` дадут `409 Order is pending` — повторить позже |
| `state = Wait` / `Active`, `orderId` есть | Отменить, изменить                                                                   |
| `Completed` / `Canceled` / `Rejected`     | Ордер завершён, `Order not found`                                                    |

Особые случаи:

* **`MODIFYORDER` на большинстве бирж** отменяет ордер и выставляет новый — с новым `id`, `initiator: "TC-PlaceModifiedOrder"` и без связи со старым. Подробно — [Отмена + новый ордер](#otmena--novyi-order).
* **Стоп-ордер при срабатывании** может породить у биржи новый ордер. В терминале это новый объект с **новым** `id`, связи с исходным по id нет.
* **Крупный ордер**, превышающий максимальный объём биржи, некоторые коннекторы (Binance, Bybit, OKX, AsterDEX, TigerX) делят на несколько ордеров. Они приходят с новыми `id`, а исходный `id` из ответа `PLACEORDER` в пушах не появится.
* **`type: Trigger`** — стоп, который терминал держит у себя, а не на бирже. Так терминал выставляет `Stop` / `StopLimit` на **споте** TigerX, Hyperliquid, BingX, Lighter и Bitget (единый аккаунт): в ответе `PLACEORDER` будет `type: Stop`, а в пушах и `GETORDERS` — `Trigger`. Выставить тип `Trigger` напрямую нельзя.
* **Локальные триггеры с одинаковой стоп-ценой объединяются.** Если по инструменту уже есть такой триггер с той же `stopPrice`, новый ордер не создаётся: объём добавляется к существующему (та же сторона) или вычитается из него (противоположная сторона; при равных объёмах существующий триггер отменяется). Пуши придут по **существующему** ордеру, а `id` из ответа `PLACEORDER` не появится нигде.
* Если за минуту после `PLACEORDER` ордер так и не подтвердился, отмена и изменение перестают отвечать `Order is pending` и отвечают `Order not found`.

## Ошибки

### Формат

```json
"response": {
  "type": "error",
  "error": { "message": "Connection not found", "code": 400 },
  "timestamp": "..."
}
```

| `code` | Смысл                                                                                          | Что делать клиенту                                   |
| ------ | ---------------------------------------------------------------------------------------------- | ---------------------------------------------------- |
| `400`  | Ошибка в запросе: поле пустое или неверное, не найдены подключение / счёт / инструмент / ордер | Исправить запрос                                     |
| `403`  | Запрещено: выключена настройка «Разрешить торговлю» или подключение view only                  | Не повторять; пользователь должен изменить настройки |
| `409`  | Запрос верный, но сейчас его выполнить нельзя                                                  | Повторить позже или обновить состояние               |

Ориентируйтесь на `code` и `error.message`; `title` и `message` — текст для человека.

### Ошибки проверки полей

`title: "Missing required fields"`, `code: 400`. В `error.message` через запятую перечислены **все** найденные проблемы. Соединение не закрывается.

| `error.message` (фрагмент)                                                       | Команды                                      |
| -------------------------------------------------------------------------------- | -------------------------------------------- |
| `'id' is empty`                                                                  | Все, где есть `id`                           |
| `'accountId' is empty`                                                           | PLACEORDER                                   |
| `'e' is empty` · `'m' is empty` · `'m' - invalid value` · `'symbol' is empty`    | PLACEORDER, SETLINKSYMBOL, OPENMULTIEXSYMBOL |
| `'linkGroup' is empty`                                                           | SETLINKSYMBOL                                |
| `'side' - invalid value, expected 'Buy' or 'Sell'`                               | PLACEORDER                                   |
| `'orderType' - invalid value, expected 'Market', 'Limit', 'Stop' or 'StopLimit'` | PLACEORDER                                   |
| `'size' is empty` · `'size' must be greater than zero`                           | PLACEORDER, MODIFYORDER                      |
| `'price' is required for limit orders`                                           | PLACEORDER                                   |
| `'stopPrice' is required for stop orders`                                        | PLACEORDER                                   |
| `'price' - invalid value` · `'stopPrice' - invalid value`                        | PLACEORDER (`NaN` / `Infinity`)              |
| `'validity' - invalid value, expected 'Day' or 'Gtc'`                            | PLACEORDER                                   |
| `'orderId' is empty`                                                             | CANCELORDER, MODIFYORDER                     |
| `at least one of 'size', 'price', 'stopPrice' is required`                       | MODIFYORDER                                  |
| `'price' must be greater than zero, or omitted to keep the current one`          | MODIFYORDER                                  |
| `'stopPrice' must be greater than zero, or omitted to keep the current one`      | MODIFYORDER                                  |
| `'channels' - unknown channel '…', expected any of …`                            | SUBSCRIBE, UNSUBSCRIBE                       |

<details>

<summary>Пример: пропущены обязательные поля</summary>

```js
socket.send('{"type":"setLinkSymbol","e":"","m":"SPOT","symbol":"","linkGroup":""}');
```

```json
{
  "title": "Missing required fields",
  "message": "Some fields are missing.",
  "request": { "e": "", "m": "Spot", "symbol": "", "linkGroup": "", "type": "SetLinkSymbol" },
  "response": {
    "type": "error",
    "error": { "message": "'e' is empty, 'symbol' is empty, 'linkGroup' is empty", "code": 400 },
    "timestamp": "2026-09-10T12:00:00.6306133+03:00"
  }
}
```

</details>

### Ошибки выполнения

| `title`                    | `error.message`                                   | `code` | Команды                                      | Причина                                                                                                                                         |
| -------------------------- | ------------------------------------------------- | ------ | -------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| Trading is not allowed     | Trading is not allowed                            | 403    | Торговые                                     | В терминале выключена настройка **Разрешить торговлю**                                                                                          |
| Connection not found       | Connection not found                              | 400    | Все с `id`                                   | Нет такого подключения, или это симулятор / Trust / плеер                                                                                       |
| Connection is offline      | Connection is offline                             | 409    | Торговые                                     | Подключение отключено                                                                                                                           |
| Connection is view only    | Connection is view only                           | 403    | Торговые                                     | Подключение только для чтения                                                                                                                   |
| Account not found          | Account not found                                 | 400    | PLACEORDER, CANCELALL                        | `accountId` не принадлежит подключению `id`                                                                                                     |
| Symbol not found           | Symbol not found                                  | 400    | PLACEORDER, SETLINKSYMBOL, OPENMULTIEXSYMBOL | Нет инструмента с такими `e` / `m` / `symbol`. Частая причина — `e: "BINANCE"` для фьючерсов вместо `BINANCE-FUT`, см. [коды бирж](#kody-birzh) |
| More than one symbol found | Several symbols found: …                          | 400    | Те же                                        | Тройке соответствует несколько инструментов                                                                                                     |
| Account is not available   | Account is not available for trading              | 409    | PLACEORDER                                   | Счёт сейчас не может торговать                                                                                                                  |
| Symbol is not allowed      | Symbol is not allowed for trading                 | 409    | PLACEORDER                                   | Инструмент запрещён к торговле на реальном счёте                                                                                                |
| Invalid size               | 'size' is smaller than the instrument size step   | 400    | PLACEORDER, MODIFYORDER                      | Объём меньше шага инструмента                                                                                                                   |
| Invalid price              | 'price' and 'stopPrice' must be greater than zero | 400    | PLACEORDER                                   | Используемая цена на крипте ≤ 0                                                                                                                 |
| Order not found            | Order not found                                   | 400    | CANCELORDER, MODIFYORDER                     | Нет активного ордера с таким **нашим** id на этом подключении                                                                                   |
| Order is pending           | Order is not acknowledged yet                     | 409    | CANCELORDER, MODIFYORDER                     | Биржа ещё не подтвердила ордер — повторить после `ORDER_UPDATE` с `orderId`                                                                     |
| Order can not be canceled  | Order can not be canceled                         | 409    | CANCELORDER                                  | Ордер уже в процессе отмены                                                                                                                     |
| Order can not be modified  | Order can not be modified                         | 409    | MODIFYORDER                                  | Ордер уже в процессе изменения, либо раньше биржа отказала в отмене при изменении через отмену — отмените ордер и выставьте заново              |
| Invalid link group         | linkedGroup not found                             | 400    | SETLINKSYMBOL                                | Нет такой группы линковки                                                                                                                       |

Отказы биржи (недостаточно средств, неверная цена, лимиты и т. п.) приходят **не ответом на команду**, а пушем `ORDER_UPDATE` с `state: Rejected` (или без смены состояния для отказа в отмене / изменении) и текстом в `error`.

<details>

<summary>Пример: несуществующий символ</summary>

```js
socket.send('{"type":"setLinkSymbol","e":"BINANCE","m":"SPOT","symbol":"BTCUSDT1","linkGroup":"A"}');
```

```json
{
  "title": "Symbol not found",
  "message": "Trading symbol not found.",
  "request": { "e": "BINANCE", "m": "Spot", "symbol": "BTCUSDT1", "linkGroup": "A", "type": "SetLinkSymbol" },
  "response": {
    "type": "error",
    "error": { "message": "Symbol not found", "code": 400 },
    "timestamp": "2026-09-10T12:00:00.9152168+03:00"
  }
}
```

</details>

<details>

<summary>Пример: некорректная группа линковки</summary>

```js
socket.send('{"type":"setLinkSymbol","e":"BINANCE","m":"SPOT","symbol":"BTCUSDT","linkGroup":"AX"}');
```

```json
{
  "title": "Invalid link group",
  "message": "Link group not found.",
  "request": { "e": "BINANCE", "m": "Spot", "symbol": "BTCUSDT", "linkGroup": "AX", "type": "SetLinkSymbol" },
  "response": {
    "type": "error",
    "error": { "message": "linkedGroup not found", "code": 400 },
    "timestamp": "2026-09-10T12:00:00.1017820+03:00"
  }
}
```

</details>

### Ошибки уровня сокета

Эти ошибки **закрывают соединение**.

| Ситуация                                                                                         | Что приходит                                                                  |
| ------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------- |
| Невалидный JSON; нет `type`; неизвестный `type`; поле неверного типа (например, `"size": "abc"`) | Ответ ниже, затем закрытие с кодом `1007`                                     |
| Бинарный кадр                                                                                    | Закрытие с кодом `1003`, причина `Only text messages are allowed`, без ответа |

```json
{
  "title": "Invalid JSON (syntax error)",
  "message": "Invalid JSON. Connection will close.",
  "request": null,
  "response": {
    "type": "error",
    "error": { "message": "Invalid JSON format", "code": 400 },
    "timestamp": "..."
  },
  "connection_close": { "code": 1007, "reason": "Invalid JSON format" }
}
```

`requestId` в этом ответе нет; `request` — ваш JSON, если его удалось разобрать, иначе `null`.

<details>

<summary>Пример: неизвестный тип команды (ключи валидные, но значение type неверно)</summary>

```js
socket.send('{"type":"InvalidType","e":"BINANCE","m":"SPOT","symbol":"BTCUSDT","linkGroup":"A"}');
```

```json
{
  "title": "Invalid JSON (syntax error)",
  "message": "Invalid JSON. Connection will close.",
  "request": { "type": "InvalidType", "e": "BINANCE", "m": "SPOT", "symbol": "BTCUSDT", "linkGroup": "A" },
  "response": {
    "type": "error",
    "error": { "message": "Invalid JSON format", "code": 400 },
    "timestamp": "2026-09-10T12:00:00.6586708+03:00"
  },
  "connection_close": { "code": 1007, "reason": "Invalid JSON format" }
}
```

</details>

### Таймауты

Ставьте таймаут на ожидание ответа по `requestId` (например, 10 секунд). При внутренней ошибке терминала ответ может не прийти вовсе — такая ситуация пишется в лог терминала.

## Справочник по значениям

### Биржи (e)

См. таблицу [Коды бирж](#kody-birzh). Передаваемое значение регистронезависимо.

### Тип рынка (m)

`SPOT` или `FUTURES` (регистр не важен; в ответе нормализуется до `Spot` / `Futures`).

### Группы линковки (linkGroup)

Передаётся строка. Допустимые группы — буквенные `A`–`Z` и цифровые `0`–`18`. Ниже — цвета групп, которыми они обозначены на окнах терминала.

<details>

<summary>Буквенные группы</summary>

| Группа | Цвет      | Группа | Цвет      |
| ------ | --------- | ------ | --------- |
| A      | `#FFF9C4` | N      | `#7455F2` |
| B      | `#93FF9D` | O      | `#A95A7D` |
| C      | `#1DEAC5` | P      | `#EBA14C` |
| D      | `#D380FF` | Q      | `#08DA8E` |
| E      | `#FFA5A9` | R      | `#2586E0` |
| F      | `#EFE95F` | S      | `#1A46DF` |
| G      | `#30F74C` | T      | `#905BA0` |
| H      | `#01AC8D` | U      | `#F56334` |
| I      | `#9CA6FF` | V      | `#05A964` |
| J      | `#C2595B` | W      | `#00879A` |
| K      | `#CFC937` | X      | `#4D23F5` |
| L      | `#05D759` | Y      | `#6C26EE` |
| M      | `#0DB9D7` | Z      | `#3C3C3C` |

</details>

<details>

<summary>Цифровые группы</summary>

| Группа | Цвет      | Группа | Цвет      |
| ------ | --------- | ------ | --------- |
| 0      | `#FFAA00` | 6      | `#00E0FF` |
| 1      | `#FF4E00` | 7      | `#0A7AE0` |
| 2      | `#CB3400` | 8      | `#0A42D2` |
| 3      | `#87EA09` | 9      | `#9655F2` |
| 4      | `#31DA06` | 10     | `#A700FF` |
| 5      | `#178905` | 11     | `#6509D5` |

Группы 12–18 также принимаются командой `SETLINKSYMBOL`; их цвета смотрите на окнах терминала.

</details>

## Ограничения и безопасность

* **Нет авторизации.** Подключиться может любая программа на этом компьютере, в том числе веб-страница в браузере. Включайте **Разрешить торговлю** только на время, когда API действительно торгует.
* Только `127.0.0.1`, только `ws://`.
* Нет REST, нет исторических данных, нет рыночных данных (стаканов, ленты сделок) — только торговое состояние и события.
* Адресуются только инструменты `SPOT` и `FUTURES`.
* Нет плеча, PnL, комиссий по позиции и маржинальных параметров позиции.
* Лимита частоты запросов на стороне API нет, но у бирж он есть: частые `PLACEORDER` / `MODIFYORDER` упрутся в ограничения биржи, и отказы придут в `ORDER_UPDATE`.

## Рекомендации клиенту

* На ошибки уровня сокета (закрытие `1007`) — переподключение с экспоненциальной задержкой и локальная валидация JSON перед отправкой.
* На ошибки валидации содержимого — исправление полей и повторная отправка без переподключения.
* Поддерживайте keep-alive через периодический `ping` (текстовый или JSON).
* Подписывайтесь на события **до** снимка состояния и **до** торговых команд.
* Обрабатывайте `ORDER_UPDATE` как снимок по ключу `id`, а не как приращение; учитывайте `initiator: "TC-PlaceModifiedOrder"` после `MODIFYORDER`.
* При `Symbol not found` и `More than one symbol found` проверьте `e`, `m`, `symbol` — для фьючерсов Binance код биржи `BINANCE-FUT`.

## Пример клиента

### JavaScript (браузер или Node.js 22+)

```js
const ws = new WebSocket('ws://127.0.0.1:7819/');
const pending = new Map();
let seq = 0;

function request(type, fields = {}, timeoutMs = 10000) {
  const requestId = String(++seq);
  ws.send(JSON.stringify({ type, requestId, ...fields }));

  return new Promise((resolve, reject) => {
    const timer = setTimeout(() => {
      pending.delete(requestId);
      reject(new Error(`${type}: no answer in ${timeoutMs} ms`));
    }, timeoutMs);

    pending.set(requestId, { resolve, reject, timer });
  });
}

ws.onmessage = (event) => {
  if (event.data === 'pong') return;                 // ответ на текстовый ping

  const msg = JSON.parse(event.data);

  if (msg.response) {                                // ответ на запрос
    const waiter = pending.get(msg.requestId);
    if (!waiter) return;
    pending.delete(msg.requestId);
    clearTimeout(waiter.timer);

    if (msg.response.type === 'successfully') waiter.resolve(msg.response.data);
    else waiter.reject(Object.assign(new Error(msg.response.error.message), { code: msg.response.error.code }));
    return;
  }

  onPush(msg.type, msg.data);                        // пуш
};

const orders = new Map();                            // состояние ордеров по нашему id

function onPush(channel, data) {
  if (channel === 'ORDER_UPDATE') orders.set(data.id, data);   // снимок, повторы безопасны
  console.log(channel, data);
}

ws.onopen = async () => {
  const { connections } = await request('GETCONNECTIONS');
  const conn = connections.find(c => c.connected && !c.viewOnly);
  const accountId = conn.accounts[0].id;

  await request('SUBSCRIBE');                        // сначала подписка...
  const { orders: active } = await request('GETORDERS', { id: conn.id });   // ...потом снимок
  active.forEach(o => orders.set(o.id, o));

  const placed = await request('PLACEORDER', {
    id: conn.id, accountId,
    e: 'BINANCE-FUT', m: 'FUTURES', symbol: 'BTCUSDT',   // фьючерсы Binance: BINANCE-FUT, не BINANCE
    side: 'Buy', orderType: 'Limit', size: 0.01, price: 60000, validity: 'GTC'
  });
  console.log('our order id', placed.id);            // дальше ждём ORDER_UPDATE с этим id
};

setInterval(() => ws.readyState === WebSocket.OPEN && ws.send('ping'), 30000);
```

### Python (`pip install websockets`)

```python
import asyncio, itertools, json
import websockets

class TigerApi:
    def __init__(self, url="ws://127.0.0.1:7819/"):
        self.url, self._seq, self._pending = url, itertools.count(1), {}

    async def __aenter__(self):
        self.ws = await websockets.connect(self.url)
        self._reader = asyncio.create_task(self._read())
        return self

    async def __aexit__(self, *exc):
        self._reader.cancel()
        await self.ws.close()

    async def request(self, type_, timeout=10, **fields):
        request_id = str(next(self._seq))
        future = asyncio.get_running_loop().create_future()
        self._pending[request_id] = future
        await self.ws.send(json.dumps({"type": type_, "requestId": request_id, **fields}))
        try:
            response = await asyncio.wait_for(future, timeout)
        finally:
            self._pending.pop(request_id, None)
        if response["type"] != "successfully":
            raise RuntimeError(f'{response["error"]["code"]}: {response["error"]["message"]}')
        return response.get("data")

    async def _read(self):
        async for raw in self.ws:
            if raw == "pong":
                continue
            msg = json.loads(raw)
            if "response" in msg:
                future = self._pending.get(msg.get("requestId"))
                if future and not future.done():
                    future.set_result(msg["response"])
            else:
                self.on_push(msg["type"], msg["data"])

    def on_push(self, channel, data):
        print(channel, data)

async def main():
    async with TigerApi() as api:
        connections = (await api.request("GETCONNECTIONS"))["connections"]
        conn = next(c for c in connections if c["connected"] and not c["viewOnly"])

        await api.request("SUBSCRIBE", channels=["ORDER_UPDATE", "EXECUTION_UPDATE"])
        print(await api.request("GETPOSITIONS", id=conn["id"]))

        await asyncio.sleep(60)   # слушаем пуши

asyncio.run(main())
```


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://support.tiger.com/razrabotka-dlya-tiger.trade-windows/tiger-api.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
