Skip to content

Подключению к ISS+ через Websockets

ISS+ - система распространения биржевой информации в реальном времени. Подключение осуществляется через Websocket по протоколу STOMP

Подключение

Точка доступа

  • WebSocket URL: wss://iss.moex.com/infocx/v3/websocket

Процесс подключения

  1. Создайте объект WebSocket, указав URL.
  2. Отправьте STOMP-команду CONNECT с заголовками для аутентификации.
  3. При успешной аутентификации получите ответ CONNECTED, который включает информацию о доступных данных. В случае ошибки получите фрейм ERROR.

Аутентификация

Команда: CONNECT

  • domain: DEMO (для гостей) или passport (для подписчиков ALGOPACK)
  • login: guest или ваш логин
  • passcode: guest или ваш пароль

Ответы:

  • Успешная аутентификация: CONNECTED
  • Ошибка аутентификации: ERROR

Протокол STOMP и команды

STOMP (Simple Text Oriented Messaging Subscription Protocol) — протокол, основанный на текстовых сообщениях.

Клиентские команды

  • CONNECT / DISCONNECT
  • SUBSCRIBE / UNSUBSCRIBE
  • REQUEST
  • SEND

Ответы сервера

  • CONNECTED
  • ERROR
  • RECEIPT
  • MESSAGE
  • REPLY
  • CLOSED

Примеры команд

SUBSCRIBE: подписка на поток данных

SUBSCRIBE\n
id: уникальный_ID\n
receipt: уникальный_ID\n
destination: MXSE.securities\n
selector: TICKER="MXSE.TQBR.SBER" and LANGUAGE="en"\n
\n^@

Ответы сервера:

  • Успешная подписка: MESSAGE содержит данные
  • Ошибка: ERROR с описанием проблемы

UNSUBSCRIBE: отписаться от потока данных

UNSUBSCRIBE\n
id: уникальный_ID\n
\n^@

REQUEST: запрос данных из справочника

REQUEST\n
id: уникальный_ID\n
destination: SEARCH.ticker\n
selector: pattern=""\n
\n^@

Ответы сервера:

  • Успешный ответ: REPLY с данными в формате JSON
  • Ошибка: ERROR

Форматы данных

Пакет данных JSON

image

  • properties (тип, номер последовательности, временные метки)
  • columns (названия колонок)
  • data (массив данных)
properties.type Описание
snapshot Полное обновление данных. Предыдущие данные замещаются новыми.
updates Обновление (добавление или изменение) имеющихся данных. Решение принимается на основании поиска по ключевым полям.
removal Удаление по ключевым полям (если применимо для данного ресурса).

Коды ошибок

Код ошибки Описание
gateway.exception Неожиданная ошибка обработки на сервере доступа
gateway.timeout Таймаут исполнения запрошенной операции
gateway.transport Ошибка связи сервера доступа с RabbitMQ
gateway.access-denied Доступ запрещен - при аутентификации или при запросе ресурса
gateway.invalidrequest Неверный запрос ресурса - несуществующий/неразрешенный клиенту ресурс или неверные параметры
gateway.notimplemented Запрос еще не реализованного функционала
gateway.not-connected Попытка обращения к ресурсу без аутентификации

Пример подключения (Python)

import asyncio
import json
from websockets import connect, WebSocketClientProtocol, ConnectionClosed
from stomp.utils import Frame, convert_frame, parse_frame

async def connect_stomp(url, credentials):
    """ Create a STOMP WebSocket connection """
    websocket = await connect(url, subprotocols=['STOMP'])
    login_frame = Frame('CONNECT', headers=credentials)
    await websocket.send(b''.join(convert_frame(login_frame)))
    return websocket

async def receive_messages(websocket):
    """ Receive messages from the WebSocket connection """
    async for message in websocket:
        frame = parse_frame(message)
        print('Received frame', frame.cmd, frame.headers)
        body = json.loads(frame.body.decode('utf8').strip('\0'))
        return body  # Simplifying to return the first received message

async def main(url, credentials):
    try:
        websocket = await connect_stomp(url, credentials)
        try:
            body = await receive_messages(websocket)
            print("Received Data:", body)
        finally:
            await websocket.close()
    except ConnectionClosed:
        print("Connection was closed")
    except Exception as e:
       print(f"An error occurred: {e}")


if __name__ == '__main__':
    url = 'wss://iss.moex.com/infocx/v3/websocket'
    credentials = {
        'domain': 'passport',
        'login': 'login',
        'passcode': 'password'
    }

    asyncio.run(main(url, credentials))

Пример подписки на данные (Python)

import asyncio
import json
import uuid

from websockets import connect, ConnectionClosed
from stomp.utils import Frame, convert_frame, parse_frame

async def send_frame(websocket, cmd, headers):
    """ Helper function for sending a frame """
    frame = Frame(cmd, headers=headers)
    await websocket.send(b''.join(convert_frame(frame)))

async def receive_message(websocket):
    """ Helper function to receive a message and parse it """
    message = await websocket.recv()
    return parse_frame(message)

async def connect_stomp(websocket, domain, login, passcode):
    """ Connect to the STOMP server """
    await send_frame(websocket, 'CONNECT', {'domain': domain, 'login': login, 'passcode': passcode})
    frame = await receive_message(websocket)
    if frame.cmd != 'CONNECTED':
        raise ConnectionRefusedError(f"STOMP authentication failed; {frame.headers.get('message', 'No error message')}")
    return json.loads(frame.body.decode('utf8').strip('\0'))

async def subscribe_to_topic(websocket, destination, selector):
    """ Subscribe to a topic on the STOMP server """
    subscribe_header = {
        'id': str(uuid.uuid4()),
        'destination': destination,
        'selector': selector,
    }
    await send_frame(websocket, 'SUBSCRIBE', subscribe_header)

async def main(url):
    async with connect(url, subprotocols=['STOMP']) as websocket:
        try:
            # Credentials
            domain = 'passport'
            login = 'login'
            passcode = 'password'

            # Connect to STOMP with authentication
            metadata = await connect_stomp(websocket, domain, login, passcode)
            print("Connected with metadata:", metadata)

            # Subscribe to the desired topic
            await subscribe_to_topic(websocket, 'MXSE.orderbooks', 'TICKER="MXSE.TQBR.SBER"')
            print("Subscription successful")

            # Main message processing loop
            while True:
                frame = await receive_message(websocket)
                if frame.cmd == 'MESSAGE':
                    body = json.loads(frame.body.decode('utf8').strip('\0'))
                    print(body)

        except ConnectionClosed:
            print("Connection closed unexpectedly!")
            return  # Exit the main function if connection is closed
        except Exception as e:
            print(f"An error occurred: {e}")
            return  # Handle other exceptions gracefully

if __name__ == '__main__':
    url = 'ws://iss.moex.com/infocx/v3/websocket'
    asyncio.run(main(url))