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는 다음과 같습니다.

TagIDTagID
Sports1NBA745
Crypto21NFL450
Politics2Tennis864
Bitcoin100196Esports702
Ethereum100383Soccer1059
Election3EPL739
Middle East1432UCL2186

?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_idorder=volume24hrascending=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로 필터링하세요.

자주 묻는 질문

Polymarket Gamma API란 무엇인가요?
Gamma는 Polymarket의 metadata 및 discovery API입니다. event/market 목록, title, description, end date, tag, aggregate volume을 제공합니다. real-time order book data는 제공하지 않습니다. 그 데이터는 CLOB API에 있습니다. 대부분의 discovery 및 analytics 사용 사례는 Gamma에서 시작합니다.
Polymarket에서 event와 market의 차이는 무엇인가요?
event는 하나의 질문 주제를 공유하는 하나 이상의 market을 묶습니다. market은 자체 order book과 token ID를 가진 특정 Yes/No outcome입니다. "2026 NBA Champion" event에는 30개의 market(팀별 1개씩)이 포함됩니다. binary Yes/No event의 경우 event에는 1개의 underlying market이 있습니다.
Polymarket tag ID는 어떻게 찾나요?
Tag는 gamma /tags endpoint를 통해 노출됩니다. 프로덕션에서 사용하는 검증된 ID는 864 Tennis, 745 NBA입니다. URL의 tag slug(?tag_slug=tennis)와 자유 텍스트 query(?q=tennis)는 신뢰할 수 없습니다. numerical tag_id만 사용하세요. slug를 신뢰하기 전에 /tags를 확인하세요.
Polymarket event를 24h volume 기준으로 어떻게 정렬하나요?
/events?order=volume24hr&ascending=false - 이것이 대부분의 "trending markets" 위젯을 구동합니다. 만료되었거나 일시 중지된 market을 제외하려면 active=true&closed=false와 함께 사용하세요. Limit의 기본값은 25이며, 호출당 최대 500입니다.
Gamma에 pagination 제한이 있나요?
네. /events와 /markets endpoint는 pagination을 위해 limit(호출당 최대 500)과 offset을 지원합니다. 대부분의 library는 자동으로 paginate하지 않습니다. 전체 범위가 필요하다면 limit보다 적은 결과를 받는 page가 나올 때까지 offset을 증가시키며 반복하세요.
Gamma는 WebSocket을 지원하나요?
아니요. Gamma는 REST만 지원합니다. real-time update(price 변화, 새 market, order book)는 CLOB WebSocket을 사용하세요. 이 시리즈의 8장에서 다룹니다.