Polymarket Bot Tutorial · 32개 중 7장
Polymarket Gamma API 심층 분석: /events 및 /markets endpoint, pagination, tag ID(864 Tennis, 745 NBA 등), 24h volume 기준 필터링, rate limits, 그리고 Python과 Node 코드 샘플.
이 장에서 다루는 내용
Gamma는 Polymarket의 catalog API입니다. 모든 event, 모든 market, tag, image, 그리고 front-end에 표시되는 resolved outcome을 나열합니다. CLOB API는 거래를 담당하고, Gamma는 무엇이 거래 가능한지를 설명합니다. bot의 discovery layer에서 발생하는 대부분의 버그는 이 둘을 혼동하거나 pagination contract를 놓쳐서 생깁니다. 이 장은 프로덕션 fetcher가 의존하는 정확한 parameter 동작과 함께 Gamma의 주요 endpoint를 정리한 field reference입니다.
- Gamma vs CLOB: 언제 무엇을 사용할지
- /events endpoint 구조
- /markets endpoint 구조
- Tag와 tag ID (검증된 목록)
- 필터링: active, closed, volume24hr 정렬
- Pagination과 limits
- Rate limits와 caching
- Code: 상위 24h-volume market 가져오기
Gamma vs CLOB: 언제 무엇을 사용할지
서로 다른 작업을 위한 두 개의 다른 서비스입니다.
Gamma (gamma-api.polymarket.com): catalog. event, market, tag, description, image, resolved outcome, 24-hour volume, total volume, end date를 나열합니다. Read-only HTTP이며, 대부분의 read에는 authentication이 필요하지 않습니다. 계속 업데이트되지만 eventually consistent이므로, 막 닫힌 market은 몇 초 동안 여전히 closed: false로 보일 수 있습니다.
CLOB (clob.polymarket.com): trading + order book. 현재 best bid/ask, top-N book depth, recent trades를 나열합니다. write endpoint(order placement, cancellation)에는 authentication이 필요합니다. book update를 위한 real-time WebSocket channel도 사용할 수 있습니다.
기본 원칙: 무엇을 거래할지 찾을 때는 Gamma를 사용하고, 실제 거래는 CLOB에서 수행하세요. Gamma에서 price를 읽는 bot은 stale data를 사용하는 것입니다. Gamma의 price field는 CLOB의 order book보다 덜 자주 업데이트됩니다. CLOB에서 market metadata를 읽는 bot은 불필요하게 더 많은 request를 보내게 됩니다.
/events endpoint 구조
GET /events는 event-level 데이터를 반환합니다. "event"는 Polymarket page이며, 하나의 event에는 여러 market이 포함될 수 있습니다(예: 2024 Presidential Election event는 후보자별로 하나씩 market이 있습니다).
핵심 field:
slug: URL-safe identifier로, event의 수명 동안 안정적입니다.title,description: 사람이 읽는 표시용입니다.endDate(ISO 8601): event가 종료되는 시점입니다.active,closed: boolean입니다. live event를 위해?active=true&closed=false로 query에 함께 사용합니다.volume,volume24hr: USD 총액입니다.tags: tag object 배열입니다(아래 tags 섹션 참조).markets: 하위 market object 배열입니다(/markets구조 참조).
가장 흔한 discovery 패턴은 GET /events?active=true&closed=false&order=volume24hr&ascending=false&limit=100입니다. 현재 live 상태인 event 중 volume이 가장 높은 상위 100개를 반환합니다.
/markets endpoint 구조
GET /markets는 market-level 데이터를 반환합니다. market은 하나의 Y/N 또는 multi-outcome contract이며, event 내부에 존재합니다.
핵심 field:
slug: URL-safe identifier입니다.question: trading page에 표시되는 제목입니다(예: "Will Trump be president on January 1, 2027?").outcomes: outcome 이름의 JSON string입니다. 예:'["Yes","No"]'. binary의 경우 항상 두 요소이며, NegRisk의 경우 더 많을 수 있습니다.outcomePrices: 현재 가격을 decimal로 담은 JSON string입니다. 예:'["0.62","0.38"]'. 양쪽 합은 spread를 제외하고 대략 1.0이 됩니다.clobTokenIds: outcome에 대응하는 ERC-1155 token ID의 JSON string입니다. 실제로 사고파는 token은 이것들입니다.negRisk: boolean입니다. 합이 1이 되는 multi-outcome market이면 true입니다. order placement에서 중요합니다(11장).
outcomes / outcomePrices / clobTokenIds field는 파싱된 배열이 아니라 JSON string으로 전달됩니다. 사용하기 전에 JSON decode 하세요.
Tag와 tag ID (검증된 목록)
Tag는 범주형 label입니다(Sports, Crypto, Tennis, NBA 등). 가장 많이 쓰는 category에 대한 검증된 프로덕션 tag ID는 다음과 같습니다.
| Tag | ID | Tag | ID |
|---|---|---|---|
| Sports | 1 | NBA | 745 |
| Crypto | 21 | NFL | 450 |
| Politics | 2 | Tennis | 864 |
| Bitcoin | 100196 | Esports | 702 |
| Ethereum | 100383 | Soccer | 1059 |
| Election | 3 | EPL | 739 |
| Middle East | 1432 | UCL | 2186 |
?tag_id=745처럼 특정 tag로 필터링하거나, slug를 사용해 ?tag_slug=nba로 필터링할 수 있습니다. slug 기반 필터링은 코드에서 더 읽기 쉽지만 약간 느립니다. 프로덕션 기본값은 ID 기반입니다.
필터링: active, closed, volume24hr 정렬
95%의 경우에 사용할 네 가지 필터 차원입니다.
active=true|false:true는 Polymarket 팀이 UI에서 숨긴 market을 제외합니다.closed=true|false:false는 resolved market을 제외합니다.active=true&closed=false조합이 가장 흔한 live 필터입니다.order=volume24hr,order=volume,order=endDate: 정렬 기준입니다. 현재 활성 market을 찾을 때는volume24hr가 가장 유용합니다.ascending=true|false: 대부분의 endpoint에서 기본값은 true입니다. volume 정렬에서는 거의 항상false를 원합니다.
주의: tag_id를 order=volume24hr 및 ascending=false와 함께 필터링하면, 해당 tag에 live market이 매우 적을 때 빈 page가 반환되기도 합니다. 항상 over-fetch(표시할 양보다 더 많이 요청)한 뒤 post-filter로 처리하세요.
Pagination과 limits
limit parameter는 호출당 1-500을 허용합니다. 지정하지 않으면 기본값은 100입니다. 500을 초과해도 서버는 조용히 상한을 적용하며, 응답에는 더 많은 데이터가 있다는 표시가 없습니다.
Pagination은 offset 기반입니다. 두 번째 page는 ?limit=500&offset=500처럼 사용합니다. cursor 기반 pagination은 없으므로, 동시에 새 항목이 들어오면 호출 사이에 page 경계가 이동할 수 있습니다. 대부분의 bot discovery 목적에는 이 정도면 충분합니다. archive scrape의 경우, 안정적인 field(endDate 또는 createdAt)로 정렬하고 slug로 overlap을 감지하세요.
"모든 live market"을 위한 실용적인 패턴은 limit=500&order=volume24hr&ascending=false로 가져오는 것입니다. 이렇게 하면 거래량 상위 500개를 커버하는데, 사실상 의미 있는 활동이 있는 모든 market을 포함합니다. 1페이지를 넘어서는 경우는 드뭅니다. volume 분포의 하위 구간에 있는 market은 정의상 큰 움직임이 있는 곳이 아닙니다.
Rate limits와 caching
Gamma는 Cloudflare 뒤에 있으며 IP별 soft rate limit이 있습니다. 프로덕션 부하에서 관찰된 경험적 threshold는 다음과 같습니다.
- 한 IP에서 초당 약 30 req/sec까지 지속: 문제 없음.
- 100+ req/sec burst: 가끔 429가 발생하지만 retry는 성공.
- 초당 약 500 req/sec 지속: rate-limit page 또는 temporary block(10-60초).
공개된 response header에는 대부분의 endpoint에 대해 Cache-Control 값이 30-60초로 포함됩니다. 이를 준수하세요. 같은 event를 1분에 10번 다시 가져와도 이득은 없습니다. 프로덕션 caching 패턴은 full URL을 key로 하는 in-process LRU + TTL이며, TTL은 30초입니다. request 수를 줄이고 latency도 낮춥니다.
고빈도 전략의 경우, 단일 fetcher process가 갱신하는 local store로 Gamma data를 복제하고, 여러 consumer가 그 store를 읽도록 하세요. fetcher 1개 × consumer 여러 개가 fetcher 여러 개 × Gamma보다 낫습니다.
Code: 상위 24h-volume market 가져오기
24시간 volume 기준 상위 50개 live market을 반환하는 3개 언어의 reference fetcher입니다.
Python:
import requests
r = requests.get("https://gamma-api.polymarket.com/events",
params={"active":"true","closed":"false",
"order":"volume24hr","ascending":"false","limit":50},
timeout=10)
for ev in r.json()[:50]:
print(ev["slug"], ev.get("volume24hr"))
Node:
const url = "https://gamma-api.polymarket.com/events?active=true&closed=false" +
"&order=volume24hr&ascending=false&limit=50";
const events = await fetch(url).then(r => r.json());
for (const ev of events) console.log(ev.slug, ev.volume24hr);
Curl:
curl -s "https://gamma-api.polymarket.com/events?active=true&closed=false&order=volume24hr&ascending=false&limit=50" \
| jq '.[].slug'
Polymarket gamma /events endpoint는 자유 텍스트 search parameter를 지원하지 않습니다. ?q=foo 또는 ?search=foo를 추가해도 기본 정렬이 조용히 반환될 뿐입니다. 대신 slug 또는 tag로 필터링하세요.












