WebSocket 스트림
시장 데이터, 계정, 창 레이아웃, 설정을 하나의 연결로 받습니다.
주소: ws://127.0.0.1:48180/v1/stream · AsyncAPI 파일
연결
- 주소:
ws://127.0.0.1:48180/v1/stream. - 토큰은 핸드셰이크의
Authorization: Bearer <token>헤더로 보냅니다. 핸드셰이크에는market:read권한이 필요합니다. HTTP 요청과 같은 규칙에 따라401또는403으로 거부됩니다. - 서버는 20초마다 WebSocket ping을 보냅니다. 50초 동안 pong이 오지 않으면 서버가 연결을 닫습니다.
- 클라이언트가 연결을 닫으면 서버는 클라이언트의 코드를 담은 close 프레임으로 응답합니다.
- 소켓을 닫으면 연결의 모든 구독이 제거됩니다. 명시적인
unsubscribe는 구독 하나를 즉시 제거합니다. - 구독자가 없는 페어는 30초 뒤 거래소에서 해제됩니다.
종료 코드
| 코드 | 의미 |
|---|---|
1001 | API가 꺼졌거나 토큰이 재발급됨 |
4008 | 클라이언트가 너무 느려 5초 넘게 따라오지 못함 |
4029 | 토큰당 스트림 연결 수 제한 |
제한
GET /v1/meta가 제한을 반환합니다. 거부는 요청에 대한 응답인 error 프레임에 rate_limited가 담겨 오거나, 4029 종료로 나타납니다.
| 제한 | 값 |
|---|---|
| 토큰당 스트림 연결 수 | 8 |
| 연결당 구독 수 | 100 |
| API만을 위해 열린 종목 수 | 50, 창에 열려 있는 종목은 제외 |
| 초당 신규 거래소 구독 수 | 5 |
느린 클라이언트
읽지 않는 클라이언트는 터미널도 다른 클라이언트도 느리게 만들지 않습니다. 제때 가져가지 않은 프레임은 버려집니다. trades의 경우 gap 프레임이 잃어버린 번호를 알려 줍니다. 다른 토픽에서는 다음 프레임이 그저 더 최신일 뿐입니다.
시퀀스 번호
seq는 구독의 이벤트에 번호를 매깁니다. trades에서는 1씩 증가합니다. 최신 값만 전송되는 토픽에서는 번호에 누락이 있는 것이 정상입니다.
클라이언트 요청
프로그램이 터미널로 보내는 메시지입니다.
subscribe
토픽을 요청합니다.
토픽에 필요한 것은 토픽의 종류에 따라 다릅니다:
- 시장 토픽은
trades,ticker,mark_price,book.top,clusters입니다.instrument는 필수입니다.connection_id는 공통 규칙에 따라 선택됩니다. 권한은market:read입니다. - 계정 토픽은
account.*입니다.connection_id는 필수입니다.instrument는 사용하지 않습니다. 권한은account:read입니다. workspace및settings토픽은 둘 다 받지 않습니다. 권한은workspace:read와settings:read입니다.
book.top만 params를 사용합니다. 다른 토픽은 이를 무시합니다.
| 필드 | 타입 | 설명 |
|---|---|---|
type필수 | "subscribe" | |
request_id | string | 선택 사항입니다. 응답에 그대로 반환됩니다. |
topic필수 | Topic | |
instrument | string | 종목 주소, exchange:market:symbol. 시장 토픽에는 필수입니다. |
connection_id | integer | 계정 토픽에는 필수입니다. 시장 토픽에는 선택 사항입니다. |
params | object | book.top의 매개변수입니다. |
params.depth | integer | 각 방향의 레벨 수입니다. Range: 1–200. 기본값: 50. |
params.interval_ms | integer | 두 프레임 사이의 최소 시간, 밀리초입니다. Range: ≥ 50. 기본값: 100. |
{
"type": "subscribe",
"request_id": "r1",
"topic": "trades",
"instrument": "binance:futures:BTCUSDT",
"connection_id": 12
}{
"type": "subscribe",
"request_id": "r2",
"topic": "book.top",
"instrument": "binance:futures:BTCUSDT",
"params": {
"depth": 10,
"interval_ms": 250
}
}{
"type": "subscribe",
"request_id": "r3",
"topic": "account.fills",
"connection_id": 12
}{
"type": "subscribe",
"request_id": "r4",
"topic": "workspace"
}unsubscribe
구독을 해제합니다.
subscribed가 반환한 sub만 받습니다.
| 필드 | 타입 | 설명 |
|---|---|---|
type필수 | "unsubscribe" | |
request_id | string | |
sub필수 | string | subscribed에서 받은 식별자입니다. |
{
"type": "unsubscribe",
"request_id": "r5",
"sub": "7:trades"
}서버 프레임
이벤트 외에 터미널이 응답하거나 직접 보내는 메시지입니다.
subscribed
구독이 설정되었습니다.
sub는 이후의 모든 프레임에서 구독을 식별합니다. 시장 토픽은 "{stream}:{topic}", 계정 토픽은 "{connection}:account.<topic>", 나머지 둘은 "workspace" 또는 "settings"입니다. 같은 페어와 토픽을 구독한 모든 클라이언트는 같은 sub를 받습니다. connection_id는 시장 토픽과 계정 토픽에 있습니다. workspace와 settings에는 없습니다.
| 필드 | 타입 | 설명 |
|---|---|---|
type필수 | "subscribed" | |
request_id | string | |
sub필수 | string | |
connection_id | integer | 선택된 연결입니다. workspace와 settings에는 없습니다. |
{
"type": "subscribed",
"request_id": "r1",
"sub": "7:trades",
"connection_id": 12
}unsubscribed
구독이 제거되었습니다.
| 필드 | 타입 | 설명 |
|---|---|---|
type필수 | "unsubscribed" | |
request_id | string | |
sub필수 | string |
{
"type": "unsubscribed",
"request_id": "r5",
"sub": "7:trades"
}error
요청이 거부되었거나 서버가 구독을 제거했습니다.
request_id가 있으면 클라이언트의 요청에 대한 응답입니다. sub는 있고 request_id가 없으면 서버가 구독을 제거한 것입니다. 코드는 HTTP 작업의 코드와 같습니다.
| 필드 | 타입 | 설명 |
|---|---|---|
type필수 | "error" | |
request_id | string | 프레임이 요청에 대한 응답일 때 있습니다. |
sub | string | 프레임이 구독과 관련될 때 있습니다. |
code필수 | ErrorCode | |
message필수 | string | 사람이 읽기 위한 텍스트입니다. |
{
"type": "error",
"request_id": "r1",
"code": "not_found",
"message": "no connection for the instrument"
}{
"type": "error",
"sub": "7:trades",
"code": "unavailable",
"message": "the exchange refused the subscription"
}gap
클라이언트가 놓친 체결입니다.
trades에만 전송되며, 손실 뒤 첫 프레임 앞에 옵니다. 클라이언트가 읽지 않고 있었고, from_seq부터 to_seq까지의 이벤트가 손실되었습니다. GET /v1/market/trades와 since_ts로 다시 읽습니다.
| 필드 | 타입 | 설명 |
|---|---|---|
type필수 | "gap" | |
sub필수 | string | |
from_seq필수 | integer | |
to_seq필수 | integer | |
reason필수 | string | slow_consumer. |
{
"type": "gap",
"sub": "7:trades",
"from_seq": 42,
"to_seq": 57,
"reason": "slow_consumer"
}토픽
모든 이벤트는 같은 봉투를 사용하며 data는 토픽에 따라 다릅니다.
| 필드 | 타입 | 설명 |
|---|---|---|
type필수 | "event" | |
sub필수 | string | 이벤트가 속한 구독입니다. |
seq필수 | integer | 구독 내 이벤트의 번호입니다. |
ts필수 | integer | 프레임이 만들어진 시각, UTC 밀리초입니다. |
data필수 | any | 이벤트의 데이터입니다. 형태는 토픽에 따라 다릅니다. |
| 토픽 | 필수 항목 | 권한 | 전달 방식 |
|---|---|---|---|
trades | instrument | market:read | 순서대로 전달, 누락은 통지됨 |
ticker | instrument | market:read | 최신 값만 전달 |
mark_price | instrument | market:read | 최신 값만 전달 |
book.top | instrument | market:read | 최신 값만 전달 |
clusters | instrument | market:read | 최신 값만 전달 |
account.orders | connection_id | account:read | 최신 값만 전달 |
account.positions | connection_id | account:read | 최신 값만 전달 |
account.balances | connection_id | account:read | 최신 값만 전달 |
account.fills | connection_id | account:read | 이벤트마다 프레임 하나 |
workspace | 없음 | workspace:read | 최신 값만 전달 |
settings | 없음 | settings:read | 이벤트마다 프레임 하나 |
trades
페어의 체결이며, 순서대로 전달됩니다.
data는 체결 배열이며, GET /v1/market/trades에서와 같습니다. 체결은 순서대로 옵니다. 클라이언트가 뒤처지면 gap 프레임이 무엇을 놓쳤는지 알려 줍니다.
데이터: Trade[]
{
"type": "event",
"sub": "7:trades",
"seq": 41,
"ts": 1730294400130,
"data": [
{
"id": null,
"price": "67000.1",
"qty": "0.012",
"side": "buy",
"ts": 1730294400125,
"exchange_ts": 1730294400119
}
]
}ticker
페어의 24시간 티커입니다.
data는 티커이며, GET /v1/market/snapshot의 ticker와 같습니다. 최신 값만 전송됩니다.
데이터: Ticker
{
"type": "event",
"sub": "7:ticker",
"seq": 12,
"ts": 1730294400130,
"data": {
"price": "67000.1",
"change_24h_pct": "1.25",
"volume_24h": "81234.5",
"high_24h": null,
"low_24h": null,
"best_bid": null,
"best_ask": null,
"ts": 1730294400120
}
}mark_price
페어의 마크 가격입니다.
최신 값만 전송됩니다.
데이터: MarkPrice
{
"type": "event",
"sub": "7:mark_price",
"seq": 5,
"ts": 1730294400130,
"data": {
"mark_price": "67001.0",
"ts": 1730294400120
}
}book.top
페어의 호가창 상단입니다.
구독자마다 자신의 depth와 interval_ms를 설정합니다. 범위를 벗어난 값은 invalid_request로 거부됩니다. 첫 프레임은 호가창이 준비되는 즉시 옵니다. 그 뒤로는 interval_ms마다 최대 한 프레임이 최신 호가창과 함께 옵니다. seq는 호가창의 버전입니다.
데이터: BookTop
{
"type": "event",
"sub": "7:book.top",
"seq": 9031,
"ts": 1730294400130,
"data": {
"ts": 1730294400123,
"depth": 10,
"bids": [
[
"67000.1",
"1.25"
]
],
"asks": [
[
"67000.2",
"0.40"
]
]
}
}clusters
종목의 거래량 클러스터입니다.
data는 버킷 하나이며, GET /v1/market/clusters의 data 요소와 같습니다. 변경된 버킷마다 최대 16ms에 한 번, 최신 상태를 담은 프레임이 옵니다. 터미널에서 인터벌을 바꾸면 interval에 나타납니다.
데이터: ClusterBucket
account.orders
변경 후 한 종목의 주문과 트리거 주문입니다.
종목의 최신 상태만 전송됩니다. 구독은 거래소를 건드리지 않으며, 이벤트는 연결이 보내는 시점부터 옵니다. 구독 시 스냅샷 프레임은 없습니다. 현재 상태는 GET /v1/account/orders로 읽습니다.
데이터: InstrumentOrders
account.positions
변경 후 한 종목의 포지션입니다.
최신 상태만 전송됩니다. 청산된 포지션은 "position": null로 도착합니다. 구독 시 스냅샷 프레임은 없습니다.
데이터: InstrumentPosition
account.balances
한 통화의 잔고입니다.
data는 잔고 하나이며, GET /v1/account/balances의 data 요소와 같습니다. 최신 값만 전송됩니다. 구독 시 스냅샷 프레임은 없습니다.
데이터: Balance
account.fills
체결 내역 하나입니다.
체결 내역마다 프레임 하나가 옵니다. 클라이언트가 뒤처지면 프레임이 손실되고 seq에 누락이 나타납니다. GET /v1/account/fills와 since_ts로 체결 내역을 다시 읽습니다.
데이터: Fill
workspace
창의 전체 레이아웃입니다.
data는 레이아웃이며, GET /v1/workspace의 data와 같습니다. 첫 프레임은 현재 레이아웃이며 구독 즉시 전송됩니다. 그 뒤로는 변경이 있을 때마다 프레임이 옵니다. 최신 상태만 전송됩니다.
데이터: WorkspaceSnapshot
settings
기록된 설정 행 하나입니다.
설정 테이블에 쓸 때마다 프레임 하나가 옵니다: API, Settings App, 창 자체에서 쓴 경우 모두 해당합니다. 구독 시 스냅샷은 없습니다. 현재 값은 GET /v1/settings로 읽습니다. 클라이언트가 뒤처지면 프레임이 손실되고 seq에 누락이 나타납니다.
데이터: SettingWritten
{
"type": "event",
"sub": "settings",
"seq": 3,
"ts": 1730294400130,
"data": {
"scope": "global",
"scope_id": "default",
"connection_id": null,
"category": "multiview",
"key": "stack_width_px",
"value": 300
}
}데이터 타입
이벤트에 담기는 객체입니다. HTTP 작업도 같은 객체를 반환합니다.
Topic
tradestickermark_pricebook.topclustersaccount.ordersaccount.positionsaccount.balancesaccount.fillsworkspacesettings
ErrorCode
unauthorizedforbiddenforbidden_scopenot_foundinvalid_requestunsupportednot_activenot_tradablerate_limitedunavailable
안정적인 코드이며 계약의 일부입니다. 새 코드가 추가될 수 있습니다.
| 코드 | HTTP | 발생 조건 |
|---|---|---|
unauthorized | 401 | 토큰이 틀리거나 API가 꺼져 있음 |
forbidden | 403 | Origin 헤더 또는 외부 Host |
forbidden_scope | 403 | 토큰에 해당 작업의 권한이 없음 |
not_found | 404 | 그런 작업이나 연결이 없거나, 종목에 대한 연결이 없음 |
invalid_request | 400 | 잘못된 매개변수: 종목, 인터벌, 깊이, 다른 거래소나 마켓의 connection_id |
unsupported | 501 | 거래소가 MOEX이거나, 터미널이 시장 데이터 없이 빌드되었거나, API로 쓸 수 없는 설정임 |
not_active | 409 | 종목의 클러스터가 아직 계산되지 않았거나, 레버리지를 아직 불러오지 않음 |
not_tradable | 409 | 연결이 거래할 수 없음: 비활성화되어 있거나, 읽기 전용이거나, 자격 증명이 없음 |
rate_limited | 429 | 제한을 초과함: 연결, 구독, 종목, 초당 신규 구독, 거래 명령, 워크스페이스 명령 또는 설정 쓰기 |
unavailable | 503 | 저장소, 거래소 또는 창이 응답하지 않았거나, 터미널이 바쁘거나, 아직 데이터가 없음 |
Trade
| 필드 | 타입 | 설명 |
|---|---|---|
id필수 | string | null | v1에서는 항상 null입니다. |
price필수 | string | |
qty필수 | string | |
side필수 | "buy" | "sell" | |
ts필수 | integer | 터미널이 체결을 받은 시각, UTC 밀리초입니다. |
exchange_ts필수 | integer | 거래소 시간입니다. |
Ticker
24시간 티커입니다.
| 필드 | 타입 | 설명 |
|---|---|---|
price필수 | string | |
change_24h_pct필수 | string | null | |
volume_24h필수 | string | |
high_24h필수 | string | null | |
low_24h필수 | string | null | |
best_bid필수 | string | null | |
best_ask필수 | string | null | |
ts필수 | integer |
MarkPrice
| 필드 | 타입 | 설명 |
|---|---|---|
mark_price필수 | string | |
ts필수 | integer |
BookTop
호가창 상단입니다: 매수 호가는 최우선 가격부터 아래로, 매도 호가는 최우선 가격부터 위로.
| 필드 | 타입 | 설명 |
|---|---|---|
ts필수 | integer | |
depth필수 | integer | |
bids필수 | PriceLevel[] | |
asks필수 | PriceLevel[] |
ClusterBucket
| 필드 | 타입 | 설명 |
|---|---|---|
interval필수 | string | |
ts필수 | integer | 버킷의 시작 시각, UTC 밀리초입니다. |
end_ts필수 | integer | |
volume필수 | string | |
buy_volume필수 | string | |
sell_volume필수 | string | |
trades필수 | integer | |
vwap필수 | string | null | |
min_price필수 | string | null | |
max_price필수 | string | null | |
completed필수 | boolean | |
last_trade_ts필수 | integer | 버킷의 마지막 체결 시각입니다. 히스토리에서 온 버킷에 이 시각이 없으면 0입니다. |
levels필수 | ClusterLevel[] | 버킷의 모든 가격 레벨이며, 가장 낮은 가격부터 가장 높은 가격 순입니다. |
InstrumentOrders
InstrumentPosition
| 필드 | 타입 | 설명 |
|---|---|---|
instrument필수 | string | |
position필수 | Position | null |
Balance
| 필드 | 타입 | 설명 |
|---|---|---|
currency필수 | string | |
total필수 | string | |
available필수 | string | null | |
locked필수 | string | null | |
ts필수 | integer |
Fill
| 필드 | 타입 | 설명 |
|---|---|---|
trade_id필수 | string | |
order_id필수 | string | 거래소의 주문 id입니다. |
instrument필수 | string | |
side필수 | string | buy 또는 sell. |
price필수 | string | |
qty필수 | string | |
fee필수 | string | |
fee_currency필수 | string | |
position_side필수 | string | null | 헤지 모드가 있는 거래소에서는 long 또는 short, 그 외에는 null입니다. |
ts필수 | integer | 거래소 시간입니다. |
WorkspaceSnapshot
| 필드 | 타입 | 설명 |
|---|---|---|
windows필수 | Window[] | id 오름차순입니다. |
SettingWritten
기록된 그대로의 설정 행이며, writable은 없습니다.
| 필드 | 타입 | 설명 |
|---|---|---|
scope필수 | string | |
scope_id필수 | string | null | |
connection_id필수 | integer | null | |
category필수 | string | |
key필수 | string | |
value필수 | any | 임의의 JSON 값입니다. |
PriceLevel
string[]
[price, size], 둘 다 10진수 문자열입니다.
ClusterLevel
| 필드 | 타입 | 설명 |
|---|---|---|
price필수 | string | |
volume필수 | string | |
buy_volume필수 | string | |
sell_volume필수 | string | |
buy_trades필수 | integer | |
sell_trades필수 | integer |
Order
| 필드 | 타입 | 설명 |
|---|---|---|
key필수 | string | null | 터미널의 주문 키 t-…입니다. 알려질 때까지 null입니다. |
exchange_id필수 | string | null | 거래소의 주문 id입니다. 거래소가 주문을 접수할 때까지 null입니다. |
instrument필수 | string | |
side필수 | "buy" | "sell" | |
type필수 | "limit" | "market" | |
price필수 | string | |
size필수 | string | 기준 코인 단위입니다. |
filled필수 | string | |
remaining필수 | string | |
notional필수 | string | null | 트레이더가 입력한 호가 통화 금액입니다. 거래소 스냅샷에서 온 주문은 null입니다. |
status필수 | "pending" | "open" | "partially_filled" | |
created_ts필수 | integer | null | 거래소 스냅샷에서 온 주문에 시간이 없으면 null입니다. |
Trigger
| 필드 | 타입 | 설명 |
|---|---|---|
id필수 | string | 트리거를 취소하고 수정할 때 쓰는 숫자 id입니다. |
instrument필수 | string | |
type필수 | TriggerKind | |
trigger_price필수 | string | |
rule필수 | TriggerRule | |
size필수 | string | null | 기준 코인 단위입니다. |
notional필수 | string | null | 호가 통화 단위입니다. |
status필수 | "open" | "finished" | "inactive" | "invalid" | |
order_type필수 | string | null | 거래소의 주문 유형이며, 받은 그대로입니다. |
client_id필수 | string | null | 트리거를 넣을 때 반환된 키입니다. |
created_ts필수 | integer | null |
Position
| 필드 | 타입 | 설명 |
|---|---|---|
instrument필수 | string | |
side필수 | "long" | "short" | |
size필수 | string | 기준 코인 단위입니다. |
lot_size필수 | string | 1랏(lot)에 해당하는 기준 코인 수량입니다. |
entry_price필수 | string | |
notional필수 | string | 호가 통화 단위입니다. |
unrealized_pnl필수 | string | 터미널이 계산한 값입니다. |
mark_price필수 | string | null | 롱이면 최우선 매수 호가, 숏이면 최우선 매도 호가입니다. 호가창의 첫 틱이 올 때까지 null입니다. |
Window
| 필드 | 타입 | 설명 |
|---|---|---|
id필수 | integer | |
main필수 | boolean | 메인 창입니다. 이 창을 닫으면 터미널이 종료됩니다. |
chart_window필수 | boolean | 분리된 차트 창입니다. |
focused필수 | boolean | OS 포커스를 가진 창입니다. |
geometry필수 | object | 화면의 논리 픽셀 단위로 나타낸 위치와 크기입니다. |
geometry.x필수 | integer | |
geometry.y필수 | integer | |
geometry.width필수 | integer | |
geometry.height필수 | integer | |
geometry.maximized필수 | boolean | |
monitor필수 | string | null | OS가 알려 주는 모니터 이름입니다. |
tabs필수 | Tab[] |
TriggerKind
stop_losstake_profitstop
TriggerRule
price_above_or_equalprice_below_or_equal
Tab
Stack
체결 테이프와 클러스터가 있는 호가창입니다.
| 필드 | 타입 | 설명 |
|---|---|---|
widget_id필수 | integer | |
instrument필수 | string | null | 스택이 비어 있는 동안에는 null입니다. |
connection_id필수 | integer | null | |
empty필수 | boolean | |
frozen필수 | boolean | 스택이 동결된 상태입니다. |
position필수 | Cell | null |
Chart
| 필드 | 타입 | 설명 |
|---|---|---|
widget_id필수 | integer | |
instrument필수 | string | null | |
connection_id필수 | integer | null | |
interval필수 | string | 캔들 인터벌입니다: 1m, 5m, … |
owner_widget_id필수 | integer | null | 차트가 따르는 스택입니다. 독립된 차트는 null입니다. |
position필수 | Cell | null |
Cell
탭 그리드의 셀입니다: 왼쪽부터의 열, 위부터의 행, 행 안의 셀.
| 필드 | 타입 | 설명 |
|---|---|---|
column필수 | integer | |
row필수 | integer | |
cell필수 | integer |