실시간 시세 (WebSocket)
종목 시세가 바뀔 때마다 서버가 밀어주는 WebSocket이에요. 퍼스톡 웹 화면도 이걸로 시세를 받아요. 여러 종목을 짧은 주기로 폴링하는 대신 이걸 쓰면 빠르고, REST 요청 한도도 아낄 수 있어요.
| 항목 | 값 |
|---|---|
| 주소 | wss://ws.furstock.trade/socket |
| 프로토콜 | Phoenix Channels (JSON 직렬화 v2) |
| 인증 | 필요 없음(시세는 공개 데이터) |
| 방향 | 수신 전용. 주문은 항상 REST(POST /v1/orders)로 |
| 토픽 | stock:{symbol} 종목마다 하나 |
서버·봇 프로그램(Node, Bun, Python 등)은 바로 접속돼요. 브라우저에서 접속할 땐 현재 퍼스톡 도메인만 허용돼 있어서, 다른 도메인에서 서비스하는 웹 클라이언트는 시세를 REST로 폴링하세요.
이벤트
| 이벤트 | 언제 | payload |
|---|---|---|
snapshot | 처음 구독했을 때, 끊겼다 다시 붙어 놓친 게 많을 때 | 현재가 스냅샷 전체 |
price | 체결로 시세가 바뀔 때마다(종목당 최대 초당 약 10회로 묶여서 옴) | 현재가 스냅샷 전체 |
cold | 서버가 아직 이 종목 시세를 모를 때(최근 거래 없음) | { "symbol": "..." } |
snapshot과 price의 payload는 현재가 API(GET /v1/market/price/{symbol}) 응답과 같은 모양이에요: symbol, sessionId, seq, lastPrice, openPrice, high, low, bandLow, bandHigh, volume, orderbook, ts.
cold를 받았다면 REST로 현재가를 한 번 조회해 두세요. 그 종목에 체결이 생기면 그때부터 price가 와요.
재접속
구독할 때 마지막으로 받은 seq를 last_seq로 넘기면, 서버가 그 뒤의 변경분만 보내거나(짧은 끊김) snapshot으로 전체를 다시 보내줘요(긴 끊김).
JavaScript / TypeScript (phoenix 패키지)
npm install phoenix import { Socket } from "phoenix";
const socket = new Socket("wss://ws.furstock.trade/socket");
socket.connect();
let lastSeq = 0;
const channel = socket.channel("stock:FOXX", () => ({ last_seq: lastSeq }));
const onPrice = (snap: { seq: number; lastPrice: number; volume: number }) => {
lastSeq = snap.seq;
console.log("FOXX", snap.lastPrice, "vol", snap.volume);
};
channel.on("snapshot", onPrice);
channel.on("price", onPrice);
channel.on("cold", () => console.log("아직 시세 없음 — REST로 조회"));
channel
.join()
.receive("ok", () => console.log("구독 시작"))
.receive("error", (e) => console.error("구독 실패", e)); Node 20 이하에서는 전역 WebSocket이 없어서 new Socket(url, { transport: WebSocket })에 ws 패키지의 WebSocket을 넘겨야 해요. Node 22+, Bun, Deno, 브라우저는 그대로 됩니다.
라이브러리 없이 (원시 프로토콜)
Phoenix v2 직렬화는 메시지 하나가 JSON 배열 [join_ref, ref, topic, event, payload]이에요.
wss://ws.furstock.trade/socket/websocket?vsn=2.0.0에 접속- 구독:
["1", "1", "stock:FOXX", "phx_join", {"last_seq": 0}]전송 →phx_reply(statusok) 수신 - 수신:
[join_ref, null, "stock:FOXX", "price", { ...스냅샷 }] - 30초마다 하트비트:
[null, "<ref>", "phoenix", "heartbeat", {}]. 안 보내면 60초 뒤 서버가 연결을 끊어요. - 구독 해제:
["1", "<ref>", "stock:FOXX", "phx_leave", {}]
ref는 보낼 때마다 1씩 늘리는 문자열이면 돼요. 한 연결로 여러 종목을 구독할 수 있어요(종목마다 다른 join_ref).
Python (websockets)
pip install websockets import asyncio, json, itertools
import websockets
URL = "wss://ws.furstock.trade/socket/websocket?vsn=2.0.0"
SYMBOLS = ["FOXX", "WOLF"]
async def main():
refs = itertools.count(1)
async with websockets.connect(URL) as ws:
for i, sym in enumerate(SYMBOLS, start=1):
await ws.send(json.dumps([str(i), str(next(refs)), f"stock:{sym}", "phx_join", {"last_seq": 0}]))
async def heartbeat():
while True:
await asyncio.sleep(30)
await ws.send(json.dumps([None, str(next(refs)), "phoenix", "heartbeat", {}]))
asyncio.create_task(heartbeat())
async for raw in ws:
join_ref, ref, topic, event, payload = json.loads(raw)
if event in ("snapshot", "price"):
print(topic, payload["lastPrice"], payload["volume"])
asyncio.run(main()) 체결 테이프
WebSocket은 현재가 스냅샷만 보내요. 개별 체결 목록이 필요하면 GET /v1/market/trades/{symbol}을 폴링하고 seq로 새 체결만 골라내세요. 내 주문 체결 여부도 REST(GET /v1/orders)로 확인해요.