Polymarket WebSocket: 끊기지 않는 실시간 데이터
오더북을 폴링하는 것은 아무 이득 없이 요청 한도를 소모하는 가장 흔한 방법입니다. 네 가지 스트림, 아무도 문서화하지 않은 구독 옵션, 그리고 봇이 무응답을 받고도 평온하다고 여기는 실패 유형을 설명합니다.
봇이 타이머로 GET /book을 폴링한다면, 이 페이지가 요청 한도의 대부분과 상당한 지연 시간을 아껴 줄 것입니다. Polymarket은 WebSocket으로 오더북 업데이트를 푸시하며, 구독 하나는 수천 건의 요청 대신 연결 하나로 충분합니다.
짧은 요약
wss://ws-subscriptions-clob.polymarket.com/ws/market에 연결하고, assets_ids를 지정한 구독 메시지를 보낸 다음, 10초마다 PING을 보내세요. 그렇지 않으면 연결이 끊깁니다. best_bid_ask, new_market, market_resolved 이벤트가 필요하면 custom_feature_enabled: true를 설정하세요. 이 이벤트들은 기본적으로 꺼져 있으며, 이벤트가 오지 않는 상태는 조용한 마켓과 똑같아 보입니다.
네 가지 스트림
| 스트림 | URL | 전달 내용 | 인증 |
|---|---|---|---|
| Market | wss://ws-subscriptions-clob.polymarket.com/ws/market | 공개 오더북, 가격, 수명 주기 업데이트 | 없음 |
| User | wss://ws-subscriptions-clob.polymarket.com/ws/user | 내 주문 및 체결 업데이트 | 예 |
| RTDS (레거시) | wss://ws-live-data.polymarket.com | 댓글. 기준 가격은 PolyBolt로 이전됨 | 없음 |
| PolyBolt | wss://ws-live-v2.polymarket.com/ws | 암호화폐, 주식, TWAP 기준 가격 | 예 |
| Sports | wss://sports-api.polymarket.com/ws | 실시간 경기 상태와 점수 | 없음 |
사람들이 놓치는 것이 User 채널입니다
봇이 주문 체결 여부를 알기 위해 /data/orders를 폴링한다면, User 채널이 발생 즉시 더 낮은 지연 시간으로 푸시해 주는 정보에 요청 한도를 쓰고 있는 것입니다. 주문을 넣는 봇이라면 모두 이 스트림을 사용해야 합니다.
기준 가격은 2026년 9월 15일에 이전되었습니다
Polymarket은 PolyBolt를 출시하고 기준 가격에 대해 RTDS를 레거시로 전환했습니다. crypto_prices, crypto_prices_chainlink, equity_prices는 이제 wss://ws-live-v2.polymarket.com/ws에 있으며, 소스는 Binance/Chainlink에서 Pyth로 바뀌었고, 심볼은 btcusdt나 btc/usd가 아니라 소문자 btcusd이며, 이제 인증이 필요합니다. 전체 대응표는 PolyBolt 마이그레이션 가이드에 있습니다. 이 페이지의 나머지 내용, 즉 CLOB market 및 user 채널은 영향을 받지 않습니다.
market 채널 구독하기
연결한 뒤 JSON 구독 메시지 하나를 보내세요. assets_ids에는 마켓 메타데이터에서 얻는 것과 같은 토큰 ID를 넣고, type은 반드시 "market"이어야 합니다.
{
"assets_ids": ["65818619657568813474341868652308942079804919287380422192892211131408793125422"],
"type": "market",
"initial_dump": true,
"level": 2,
"custom_feature_enabled": true
}| 필드 | 필수 | 기본값 | 기능 |
|---|---|---|---|
assets_ids | 예 | — | 구독할 토큰 ID |
type | 예 | — | 반드시 "market" |
initial_dump | 아니요 | true | 구독 시 전체 오더북 스냅샷 전송 |
level | 아니요 | 2 | 구독 수준: 1, 2, 3 |
custom_feature_enabled | 아니요 | false | best_bid_ask, new_market, market_resolved 이벤트 활성화 |
custom_feature_enabled의 기본값은 false입니다
사람들이 오후 한나절을 날리는 구독 함정입니다. 세 가지 이벤트 유형, best_bid_ask, new_market, market_resolved는 직접 켜지 않으면 전달되지 않습니다. 오류도 경고도 없이 그냥 받지 못합니다. 오지 않는 market_resolved 이벤트를 기다리고 있다면 이것이 이유입니다.
특별한 이유가 없다면 initial_dump: true를 유지하세요. 이것이 없으면 오더북 상태 없이 시작해 증분 업데이트로 재구성될 때까지 기다려야 하므로, 봇이 불완전한 그림으로 시작하게 됩니다.
메시지 유형
모든 메시지에는 event_type이 있습니다. 빠짐없이 분기 처리하세요. 인식하지 못한 유형은 조용히 버리지 말고 로그로 남겨야 합니다. 그래야 프로토콜이 바뀐 것을 알 수 있습니다.
event_type | 의미 | 활성화 필요 |
|---|---|---|
book | 전체 오더북 스냅샷 | 아니요 |
price_change | 가격/호가 단계 증분 업데이트 | 아니요 |
last_trade_price | 거래 체결 | 아니요 |
tick_size_change | 마켓 틱 그리드 변경 | 아니요 |
best_bid_ask | 최우선 호가 업데이트 | 예 |
new_market | 마켓 생성 | 예 |
market_resolved | 마켓 정산 | 예 |
tick_size_change를 무시하지 마세요
틱 크기는 마켓마다 다르고 마켓이 성숙하면서 좁아질 수 있습니다. 시작 시 틱 크기를 캐시했는데 마켓이 이를 바꾸면, 이후의 모든 지정가 주문이 최소 틱 크기 위반으로 거부됩니다. 가격 로직의 버그처럼 보이는 400입니다. 이벤트를 처리하고 캐시를 업데이트하세요.
연결 유지하기: 10초마다 PING
모두가 걸리는 요구 사항입니다. 클라이언트는 10초마다 PING을 보내야 하고, 서버는 PONG으로 응답합니다. 하트비트를 놓치면 연결이 끊깁니다.
방향에 유의하세요. 이것은 클라이언트 하트비트입니다. 서버가 ping을 보내기를 기다리는 것이 아닙니다. 라이브러리에 내장된 다른 간격의 WebSocket keepalive로는 대체할 수 없으니, 애플리케이션 수준의 PING을 직접 보내세요.
import asyncio, json, time
import websockets
URL = "wss://ws-subscriptions-clob.polymarket.com/ws/market"
STALE_AFTER = 30 # seconds without any message = treat as dead
async def stream(token_ids, on_event):
backoff = 1
while True:
try:
async with websockets.connect(URL) as ws:
await ws.send(json.dumps({
"assets_ids": token_ids,
"type": "market",
"initial_dump": True,
"custom_feature_enabled": True, # or lose 3 event types
}))
backoff = 1 # reconnected cleanly
last_msg = time.monotonic()
async def heartbeat():
while True:
await asyncio.sleep(10) # required cadence
await ws.send("PING")
hb = asyncio.create_task(heartbeat())
try:
while True:
# Silence is a failure mode, not a quiet market.
raw = await asyncio.wait_for(ws.recv(), timeout=STALE_AFTER)
last_msg = time.monotonic()
if raw == "PONG":
continue
for msg in json.loads(raw) if raw.startswith("[") else [json.loads(raw)]:
on_event(msg)
finally:
hb.cancel()
except Exception as exc:
# Reconnect with capped exponential backoff + jitter.
wait = min(backoff, 30) * (0.5 + 0.5 * asyncio.get_event_loop().time() % 1)
print(f"ws down ({exc}); reconnecting in {wait:.1f}s")
await asyncio.sleep(wait)
backoff = min(backoff * 2, 30)특별한 이유가 없다면 SDK를 사용하세요
polymarket-client(Python)와 @polymarket/client(TypeScript)는 하트비트, 재연결, 메시지 프레이밍을 구현하고, 여러 피드를 타입이 지정된 하나의 이벤트 스트림으로 병합합니다. 직접 구현하는 것은 SDK가 노출하지 않는 제어가 필요할 때만 가치가 있습니다. 구독은 비동기 전용이라는 점에 유의하세요. 동기 클라이언트는 subscribe()를 구현하지 않습니다.
재연결 없이 구독 변경하기
5분 마켓은 끊임없이 만료되므로, "현재 BTC 5m 마켓"을 추적하는 봇은 구독 집합을 계속 바꿔야 합니다. 이를 위해 연결을 끊을 필요는 없습니다. market 채널은 살아 있는 연결에서 구독과 구독 해제를 지원합니다.
생각보다 중요한 부분입니다. 마켓이 바뀔 때마다 재연결하면 연결 설정 비용을 반복해서 치르고, 초기 덤프를 다시 요청하며, 하필 새 마켓이 열리는 순간에 아무것도 받지 못하는 공백이 생깁니다.
돈을 잃게 만드는 실패 유형
끊긴 WebSocket은 예외를 발생시키지 않습니다. 메시지 전달을 멈출 뿐입니다. 순진한 봇에게 "업데이트 없음"은 "마켓이 움직이지 않음"과 구별되지 않습니다. 그래서 포지션을 보유한 봇은 아무것도 바뀌지 않았다고 확신하며 낡은 가격에 무기한 머물게 됩니다.
- 마지막 메시지의 경과 시간을 추적하세요. 활발한 마켓에서 약 30초 동안 아무것도 오지 않았다면 평온한 것이 아니라 연결이 끊긴 것입니다. 위의
asyncio.wait_for타임아웃이 정확히 그 역할을 합니다. - 데이터가 오래된 상태를 오류 조건으로 취급하세요. 호가 제시를 멈추고, 위험 규칙상 필요하면 포지션을 정리하고, 재연결하세요. 얼어붙은 오더북으로 계속 거래하지 마세요.
- 재연결할 때는 지터를 포함해 백오프하세요. 장애 중의 촘촘한 재연결 루프는 연결이 끊긴 데다 요청 한도까지 걸리게 만드는 방법이며, 여러 인스턴스가 동시에 재연결하면 더 나빠집니다.
- 재연결 후 상태를 다시 동기화하세요. 연결이 끊긴 동안 업데이트를 놓쳤습니다. 낡은 스냅샷에 증분 업데이트를 합치지 말고, 새
initial_dump를 기준으로 삼고 이전 오더북은 버리세요. - 공백이 있었다면 포지션을 대조하세요. User 채널 연결이 끊겼다면 체결을 놓쳤을 수 있습니다. 가정한 상태로 행동하기 전에 포지션을 다시 읽으세요.
WebSocket과 요청 한도
자주 나오는 질문이며, 그 답이 이 모든 작업의 핵심입니다. 구독은 폴링처럼 요청당 한도 예산을 소모하지 않습니다. Polymarket의 문서화된 한도는 엔드포인트별 HTTP 한도이며, 스트리밍은 그것을 거의 완전히 피해 갑니다.
| 방식 | 20개 마켓을 한 시간 동안 추적하는 비용 |
|---|---|
매초 /book 폴링 | 10초에 1,500회 예산에 대해 요청 72,000건 |
매초 /books(배치) 폴링 | 10초에 500회 예산에 대해 요청 3,600건 |
| WebSocket 구독 | 연결 하나와 하트비트 360회 |
마켓 탐색, 틱 크기, 주문 제출, 포지션 대조를 위한 HTTP 호출은 여전히 해야 하며, 이들은 문서화된 요청 한도를 따릅니다. 스트리밍은 가장 크면서도 가장 쓸모없는 요청량의 원천을 없애 줄 뿐입니다.
지연 시간은 위장한 속도 제한입니다
Cloudflare 계층은 거부하지 않고 지연시키는 방식으로 속도를 제한하므로, 한도를 넘은 폴링 봇은 429를 보지 못하고 응답 시간이 조용히 올라가는 것을 봅니다. 오더북 데이터가 40ms에서 3초로 늘어났다면 장애를 겪는 것이 아니라 자신의 요청 속도를 겪고 있는 것입니다.
스트림이 주지 않는 것
사람들이 다음에 시도하는 것이므로 분명히 말해 둘 경계가 하나 있습니다. WebSocket은 구독한 순간부터의 오더북을 줍니다. 지난 화요일의 오더북은 줄 수 없습니다.
Polymarket은 과거 오더북을 보관하지 않습니다. 마켓 결과가 확정되면 오더북은 404를 반환하고, prices-history는 약 1분에 한 포인트만 보관합니다. 마켓이 실제로 어떻게 거래되었는지에 대해 전략을 테스트하려면 누군가 당시에 기록한 스냅샷이 필요합니다. PolyTest는 암호화폐 Up/Down 마켓에 대해 한쪽당 8단계 깊이와 함께 그것을 기록하며, 결과가 확정된 마켓도 보관합니다. 아니면 오늘 직접 수집기를 시작하고, 기록이 전혀 없는 상태에서 시작한다는 것을 받아들이고, 기다리세요.
자주 묻는 질문
- Polymarket WebSocket URL은 무엇인가요?
- 공개 market 채널은 wss://ws-subscriptions-clob.polymarket.com/ws/market이고, 인증이 필요한 user 채널은 wss://ws-subscriptions-clob.polymarket.com/ws/user입니다. 그 밖에 두 개의 스트림이 더 있습니다. 기준 가격, 댓글, 거래 활동을 위한 wss://ws-live-data.polymarket.com의 RTDS와, wss://sports-api.polymarket.com/ws의 스포츠 피드입니다.
- Polymarket WebSocket에 요청 한도가 있나요?
- WebSocket 구독은 HTTP 폴링처럼 요청당 한도 예산을 소모하지 않습니다. Polymarket의 문서화된 한도는 엔드포인트별 HTTP 한도입니다. 스트리밍은 바로 그 한도를 피하기 위해 권장되는 방법입니다. 연결에서 지켜야 할 요구 사항은 하트비트입니다. 10초마다 PING을 보내지 않으면 연결이 끊깁니다. 탐색, 틱 크기, 주문 제출을 위해 여전히 하는 HTTP 호출은 일반 한도를 따릅니다.
- Polymarket WebSocket 연결은 어떻게 유지하나요?
- 10초마다 PING 메시지를 보내면 서버가 PONG으로 응답합니다. 이는 클라이언트 측 애플리케이션 수준 하트비트이므로, 다른 간격으로 동작하는 WebSocket 라이브러리의 내장 keepalive로는 대체할 수 없습니다. 마지막으로 받은 메시지의 경과 시간도 추적하세요. 끊긴 연결은 오류 없이 데이터 전달을 멈추므로, 활발한 마켓에서 약 30초간의 무응답은 조용한 마켓이 아니라 연결 끊김으로 취급하세요.
- best_bid_ask나 market_resolved 이벤트를 받지 못하는 이유는 무엇인가요?
- 이 이벤트들은 기본적으로 꺼져 있습니다. best_bid_ask, new_market, market_resolved를 활성화하려면 구독 메시지에서 custom_feature_enabled를 true로 설정하세요. 플래그가 없어도 오류나 경고가 없고 이벤트가 그냥 오지 않으므로, 비활성 마켓으로 착각하기 쉽습니다.
- Polymarket 봇은 WebSocket을 써야 하나요, 아니면 REST API를 폴링해야 하나요?
- 실시간 오더북과 가격 데이터에는 WebSocket을 사용하세요. 20개 마켓을 매초 /book 폴링으로 추적하면 10초에 1,500회 예산에 대해 시간당 72,000건의 요청이 들지만, 구독은 연결 하나와 하트비트면 됩니다. 또한 폴링은 마지막 변경 시점이 아니라 마지막 폴링 시점의 오더북을 보여주므로 더 낡은 답을 줍니다. REST는 마켓 탐색, 틱 크기, 주문 제출, 포지션 대조에 사용하세요.
- 재연결 없이 구독할 마켓을 바꿀 수 있나요?
- 네. market 채널은 살아 있는 연결에서 자산 구독과 구독 해제를 지원하며, 이는 끊임없이 교체되는 5분 Up/Down 계약 같은 수명이 짧은 마켓에 중요합니다. 교체될 때마다 재연결하면 연결 설정 비용을 반복해서 치르고, 하필 새 마켓이 열리는 순간에 공백이 생깁니다.
Polymarket이 보관하지 않는 과거 데이터를 확보하세요
PolyTest는 Polymarket 암호화폐 Up/Down 마켓을 실시간으로 기록합니다. 8단계 오더북 깊이와 1초 미만 단위 타임스탬프를 갖춘 240M+개의 스냅샷을 제공하며, 결과가 확정된 마켓도 보존합니다. 3일 무료 체험으로 시작하세요.
더 읽어보기
Polymarket이 RTDS를 대체했습니다: PolyBolt로 마이그레이션하기
9월 15일, Polymarket은 기준 가격에 대해 RTDS를 조용히 레거시로 전환했습니다. 봇에 현물 가격을 스트리밍한다면 가격 소스와 심볼 형식을 포함해 여섯 가지가 한꺼번에 바뀌었고, 대부분은 요란하게가 아니라 조용히 실패합니다.
Polymarket 트레이딩 봇 만드는 방법
대부분의 Polymarket 봇 튜토리얼은 더 이상 존재하지 않는 SDK를 기반으로 합니다. 이 글은 처음부터 끝까지 현재 기준의 방법을 다룹니다. 2026년에 무엇을 설치해야 하는지, 인증이 실제로 어떻게 동작하는지, 데이터 공백이 어디에 있는지, 그리고 첫 주에 봇을 죽이는 실패 유형까지.
Polymarket API 요청 한도: 완벽 레퍼런스
Polymarket은 요청 한도를 여러 페이지에 걸쳐, 완전히 다른 두 시스템으로, 거의 아무도 예상하지 못하는 10초 구간 기준으로 공개합니다. 그 전부를 표 하나로 모았습니다. 한도를 넘으면 실제로 무슨 일이 일어나는지도 함께 다룹니다.
2026년 Polymarket Python SDK (그리고 py-clob-client에서 옮겨 가기)
코드가 아직 py_clob_client에서 ClobClient를 import한다면, 대체된 패키지 위에 만들어진 것입니다. AI 어시스턴트가 작성해 주는 Polymarket 코드도 대부분 마찬가지입니다. 현재 SDK와 옮겨 가는 방법을 소개합니다.