Перейти к содержанию

Engine gRPC API

Торговый движок (engine) предоставляет gRPC-интерфейс для управления ордерами и получения рыночных данных. Сервер слушает на Unix-сокете для минимизации задержек при взаимодействии с сервисом api.

Стриминговые методы (SubscribeOrderbookSnapshots, SubscribeTrades) реализованы как server-side streaming: сервер непрерывно отправляет данные клиенту до разрыва соединения.


Определение сервиса

syntax = "proto3";

package exchange.engine.v1;

option go_package = "exchange/gen/engine/v1";

service EngineService {
  // Проверка доступности
  rpc Ping(PingRequest) returns (PingResponse);

  // Получение текущей книги ордеров (L2-агрегация)
  rpc GetOrderbook(GetOrderbookRequest) returns (GetOrderbookResponse);

  // Размещение нового ордера
  rpc AddOrder(AddOrderRequest) returns (AddOrderResponse);

  // Отмена конкретного ордера
  rpc CancelOrder(CancelOrderRequest) returns (CancelOrderResponse);

  // Отмена всех ордеров по торговой паре
  rpc CancelAllOrders(CancelAllOrdersRequest) returns (CancelAllOrdersResponse);

  // Стрим снапшотов книги ордеров (server-side streaming)
  rpc SubscribeOrderbookSnapshots(SubscribeOrderbookSnapshotsRequest)
      returns (stream OrderbookSnapshot);

  // Стрим публичных сделок (server-side streaming)
  rpc SubscribeTrades(SubscribeTradesRequest)
      returns (stream Trade);
}

Перечисления (Enums)

// Сторона ордера
enum OrderSide {
  ORDER_SIDE_UNSPECIFIED = 0;
  ORDER_SIDE_BUY         = 1;
  ORDER_SIDE_SELL        = 2;
}

// Тип ордера
enum OrderType {
  ORDER_TYPE_UNSPECIFIED = 0;
  ORDER_TYPE_LIMIT       = 1;
  ORDER_TYPE_MARKET      = 2;
}

// Статус ордера
enum OrderStatus {
  ORDER_STATUS_UNSPECIFIED = 0;
  ORDER_STATUS_OPEN        = 1;
  ORDER_STATUS_PARTIAL     = 2;
  ORDER_STATUS_FILLED      = 3;
  ORDER_STATUS_CANCELLED   = 4;
}

// Тип исполнения (Time-in-Force)
enum TimeInForce {
  TIME_IN_FORCE_UNSPECIFIED = 0;
  TIME_IN_FORCE_GTC         = 1; // Good Till Cancelled
  TIME_IN_FORCE_IOC         = 2; // Immediate Or Cancel
  TIME_IN_FORCE_FOK         = 3; // Fill Or Kill
}

Типы сообщений

Ping

message PingRequest {}

message PingResponse {
  string status = 1; // "ok"
}

GetOrderbook

Запрос текущего состояния книги ордеров с L2-агрегацией (уровни цен с суммарными объёмами).

message GetOrderbookRequest {
  string ticker = 1; // торговая пара, например "BTC-USDT"
  int32  depth  = 2; // количество уровней с каждой стороны (1–100)
}

message GetOrderbookResponse {
  string            ticker    = 1;
  repeated PriceLevel bids    = 2; // заявки на покупку, сортировка по убыванию цены
  repeated PriceLevel asks    = 3; // заявки на продажу, сортировка по возрастанию цены
  int64             timestamp = 4; // Unix timestamp в миллисекундах
}

// Ценовой уровень в агрегированной книге (L2)
message PriceLevel {
  string price    = 1; // decimal-строка
  string quantity = 2; // суммарный объём на уровне
}

AddOrder

Размещение нового ордера в книге. Если ордер немедленно исполняется (matching), возвращается статус FILLED или PARTIAL.

message AddOrderRequest {
  string      account_id         = 1;
  string      balance_account_id = 2;
  string      ticker             = 3;
  OrderSide   side               = 4;
  OrderType   type               = 5;
  string      price              = 6; // decimal; обязательно для LIMIT, игнорируется для MARKET
  string      quantity           = 7; // decimal
  TimeInForce time_in_force      = 8; // по умолчанию GTC
  string      client_order_id    = 9; // опционально; уникальный ID клиента для идемпотентности
}

message AddOrderResponse {
  string      order_id      = 1; // UUID ордера в системе
  string      client_order_id = 2;
  OrderStatus status        = 3;
  string      filled_qty    = 4; // decimal; объём, исполненный немедленно
  int64       exchange_time = 5; // Unix timestamp (мс) принятия ордера движком
}

CancelOrder

Отмена одного открытого ордера по системному ID или клиентскому ID.

message CancelOrderRequest {
  string account_id      = 1;
  string order_id        = 2; // UUID ордера в системе
  string client_order_id = 3; // альтернатива order_id
  // Одно из двух полей (order_id или client_order_id) обязательно
}

message CancelOrderResponse {
  string      order_id = 1;
  OrderStatus status   = 2; // ORDER_STATUS_CANCELLED при успехе
}

CancelAllOrders

Отмена всех открытых ордеров аккаунта по указанной торговой паре.

message CancelAllOrdersRequest {
  string account_id = 1;
  string ticker     = 2; // торговая пара
}

message CancelAllOrdersResponse {
  int32 orders_canceled = 1; // количество отменённых ордеров
}

SubscribeOrderbookSnapshots

Серверный стрим снапшотов книги ордеров. Движок отправляет полный снапшот при каждом изменении книги (добавление/отмена/исполнение ордера).

message SubscribeOrderbookSnapshotsRequest {
  string ticker = 1;
  int32  depth  = 2; // количество уровней (по умолчанию 20)
}

// Полный снапшот книги ордеров (L2)
message OrderbookSnapshot {
  string              ticker    = 1;
  repeated PriceLevel bids      = 2;
  repeated PriceLevel asks      = 3;
  int64               sequence  = 4; // монотонно возрастающий номер снапшота
  int64               timestamp = 5; // Unix timestamp (мс)
}

Поведение стрима:

  • При подключении движок немедленно отправляет первый снапшот с текущим состоянием книги.
  • Последующие снапшоты отправляются при каждом изменении книги.
  • Поле sequence позволяет клиенту обнаружить пропущенные обновления.
  • При разрыве соединения клиент должен переподключиться и запросить новый начальный снапшот.

SubscribeTrades

Серверный стрим исполненных сделок в реальном времени.

message SubscribeTradesRequest {
  string ticker = 1;
}

// Исполненная сделка
message Trade {
  string    id              = 1; // UUID сделки
  string    ticker          = 2;
  string    price           = 3; // decimal — цена исполнения
  string    quantity        = 4; // decimal — объём
  OrderSide aggressor_side  = 5; // сторона агрессивного ордера (инициировавшего матч)
  int64     executed_at     = 6; // Unix timestamp (мс)
}

Поведение стрима:

  • Движок отправляет сообщение Trade при каждом новом матче в книге ордеров.
  • Стрим публичный: данные не содержат идентификаторов аккаунтов.

Внутренние типы данных

// L3-ордер (уровень хранения в движке, не экспортируется напрямую)
// Используется внутри движка для представления отдельного ордера в книге
message L3Order {
  string      order_id      = 1;
  string      account_id    = 2;
  string      ticker        = 3;
  OrderSide   side          = 4;
  OrderType   type          = 5;
  string      price         = 6;
  string      quantity      = 7;
  string      remaining_qty = 8;
  TimeInForce time_in_force = 9;
  int64       created_at    = 10;
}

Обработка ошибок

Движок использует стандартные gRPC-коды статусов:

Код Название Ситуация
0 OK Успешное выполнение
3 INVALID_ARGUMENT Неверные параметры (отсутствует тикер, некорректная цена, нулевой объём)
5 NOT_FOUND Ордер не найден при отмене
6 ALREADY_EXISTS client_order_id уже используется
9 FAILED_PRECONDITION Ордер не может быть отменён (уже исполнен или отменён)
13 INTERNAL Внутренняя ошибка движка
14 UNAVAILABLE Движок перегружен или недоступен

Примечания по производительности

Аспект Описание
Транспорт Unix-сокет вместо TCP устраняет сетевой стек ОС и снижает задержки.
L3 книга ордеров Движок хранит каждый ордер отдельно (L3), что обеспечивает точный матчинг и аудиторский след. Клиентам возвращается L2-агрегация (уровни цен).
Стримы SubscribeOrderbookSnapshots и SubscribeTrades используют server-side streaming gRPC — одно постоянное соединение вместо поллинга.
Персистентность Движок записывает ордера и сделки в PostgreSQL синхронно перед подтверждением клиенту, обеспечивая консистентность при перезапуске.

Пример использования (псевдокод Go)

// Подключение через Unix-сокет
conn, err := grpc.Dial(
    "unix:///var/run/engine.sock",
    grpc.WithTransportCredentials(insecure.NewCredentials()),
)
client := enginev1.NewEngineServiceClient(conn)

// Размещение лимитного ордера
resp, err := client.AddOrder(ctx, &enginev1.AddOrderRequest{
    AccountId:        "acc-uuid",
    BalanceAccountId: "bal-uuid",
    Ticker:           "BTC-USDT",
    Side:             enginev1.OrderSide_ORDER_SIDE_BUY,
    Type:             enginev1.OrderType_ORDER_TYPE_LIMIT,
    Price:            "65000.00",
    Quantity:         "0.001",
    TimeInForce:      enginev1.TimeInForce_TIME_IN_FORCE_GTC,
    ClientOrderId:    "my-order-001",
})

// Подписка на стрим книги ордеров
stream, err := client.SubscribeOrderbookSnapshots(ctx, &enginev1.SubscribeOrderbookSnapshotsRequest{
    Ticker: "BTC-USDT",
    Depth:  20,
})
for {
    snapshot, err := stream.Recv()
    if err != nil {
        break
    }
    // обработка снапшота
}